diff --git a/config.mainnet.json b/config.mainnet.json index 4aa113e3..a545d9ed 100644 --- a/config.mainnet.json +++ b/config.mainnet.json @@ -28,6 +28,16 @@ "name": "Smart Bitcoin Labs", "addresses": ["0x46166fA874AAEDEA8d98b15F9A72C84e22Abe2A1"], "logo": "/images/logos/sbl-logotype.svg" + }, + { + "name": "Waterdrip Capital", + "addresses": ["0x2816f3528AD324E6089714DA8E89455f58739e68"], + "logo": "/images/logos/waterdrip-capital-logo.svg" + }, + { + "name": "Pattern Research", + "addresses": ["0x707A141c5c19c25E2e6D50b214e39A4293B63234"], + "logo": "/images/logos/pattern-research-logo.svg" } ], "protocols": [ diff --git a/public/images/logos/fordefi-logo.svg b/public/images/logos/fordefi-logo.svg new file mode 100644 index 00000000..0b9e22af --- /dev/null +++ b/public/images/logos/fordefi-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/images/logos/pattern-research-logo.svg b/public/images/logos/pattern-research-logo.svg new file mode 100644 index 00000000..ce361332 --- /dev/null +++ b/public/images/logos/pattern-research-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/logos/waterdrip-capital-logo.svg b/public/images/logos/waterdrip-capital-logo.svg new file mode 100644 index 00000000..73ee4f5a --- /dev/null +++ b/public/images/logos/waterdrip-capital-logo.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/components/modals/ledger-modal/components/ledger-modal-error-box.tsx b/src/app/components/modals/ledger-modal/components/ledger-modal-error-box.tsx index 61bb04c6..9ee0c172 100644 --- a/src/app/components/modals/ledger-modal/components/ledger-modal-error-box.tsx +++ b/src/app/components/modals/ledger-modal/components/ledger-modal-error-box.tsx @@ -5,13 +5,19 @@ interface LedgerModalErrorBoxProps { } function formatErrorMessage(error: string): string { - if (error.includes('0x6985')) { - return 'Action Rejected by User'; - } else if (error.includes('0x5515')) { - return 'Locked Device'; - } else { - return error; + const errorMessages: Record = { + '0x6985': 'Action Rejected by User', + '0x5515': 'Locked Device', + '0x6a80': + "Invalid data received. Please ensure your Ledger hardware's firmware and Bitcoin app are up to date", + }; + + for (const [code, message] of Object.entries(errorMessages)) { + if (error.includes(code)) { + return message; + } } + return error; } export function LedgerModalErrorBox({ diff --git a/src/app/components/modals/select-bitcoin-wallet-modal/components/update-bitcoin-wallet-message.tsx b/src/app/components/modals/select-bitcoin-wallet-modal/components/update-bitcoin-wallet-message.tsx new file mode 100644 index 00000000..debe56df --- /dev/null +++ b/src/app/components/modals/select-bitcoin-wallet-modal/components/update-bitcoin-wallet-message.tsx @@ -0,0 +1,33 @@ +import { Link, Text, VStack } from '@chakra-ui/react'; + +export function UpdateBitcoinWalletMessage(): React.JSX.Element { + return ( + + + Before proceeding, please make sure your Bitcoin Wallet is up to date. + + + If you are using Ledger, ensure both the device firmware and Bitcoin App are updated to + avoid errors. + + + Use{' '} + + Ledger Live + {' '} + to update both firmware and the app easily. + + + ); +} diff --git a/src/app/components/modals/select-bitcoin-wallet-modal/select-bitcoin-wallet-modal.tsx b/src/app/components/modals/select-bitcoin-wallet-modal/select-bitcoin-wallet-modal.tsx index 7af33fc0..6e7899db 100644 --- a/src/app/components/modals/select-bitcoin-wallet-modal/select-bitcoin-wallet-modal.tsx +++ b/src/app/components/modals/select-bitcoin-wallet-modal/select-bitcoin-wallet-modal.tsx @@ -9,6 +9,7 @@ import { BitcoinWalletType, bitcoinWallets } from '@models/wallet'; import { modalActions } from '@store/slices/modal/modal.actions'; import { SelectBitcoinWalletMenu } from './components/select-bitcoin-wallet-modal-menu'; +import { UpdateBitcoinWalletMessage } from './components/update-bitcoin-wallet-message'; export function SelectBitcoinWalletModal({ isOpen, @@ -47,6 +48,19 @@ export function SelectBitcoinWalletModal({ }); } break; + case BitcoinWalletType.Fordefi: + try { + await connectUnisatWallet(true); + } catch (error: any) { + toast({ + title: 'Failed to connect to Unisat Wallet', + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + }); + } + break; case BitcoinWalletType.Ledger: dispatch(modalActions.toggleLedgerModalVisibility()); break; @@ -66,6 +80,7 @@ export function SelectBitcoinWalletModal({ handleClick={() => handleLogin(wallet.id)} /> ))} + ); diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx index 848fdcca..6af6bb24 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx @@ -1,12 +1,5 @@ -import { HStack, Text, keyframes } from '@chakra-ui/react'; - -const borderColorKeyframes = keyframes` - 0% { border-color: rgba(255, 168, 0, 0.11); } - 25% { border-color: rgba(255, 168, 0, 0.3); } - 50% { border-color: rgba(255, 168, 0, 0.5); } - 75% { border-color: rgba(255, 168, 0, 0.7); } - 100% { border-color: rgba(255, 168, 0, 0.1); } -`; +import { HStack, Text } from '@chakra-ui/react'; +import { orangeBoxShadowAnimation } from '@styles/css-styles'; interface TransactionScreenWalletInformationProps { isBitcoinWalletLoading: [boolean, string]; @@ -22,9 +15,11 @@ export function TransactionScreenWalletInformation({ w={'100%'} bgColor={'background.content.01'} justifyContent={'space-between'} - border={'1px solid'} + border={'2px solid transparent'} borderRadius={'md'} - animation={`${borderColorKeyframes} 2s linear infinite`} + css={{ + animation: `${orangeBoxShadowAnimation} 1.5s infinite ease-in-out`, + }} > diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx index b93af6e5..917c90c5 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx @@ -8,6 +8,7 @@ import { BitcoinTransactionConfirmationsContext } from '@providers/bitcoin-query import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-provider'; import { useForm } from '@tanstack/react-form'; import Decimal from 'decimal.js'; +import { isEmpty } from 'ramda'; import { TransactionFormNavigateButtonGroup } from './components/transaction-screen.transaction-form.navigate-button-group'; import { TransactionFormProgressStack } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack'; @@ -140,7 +141,8 @@ export function VaultTransactionForm({ }, validators: { onChange: ({ value }) => { - setCurrentFieldValue(new Decimal(value.assetAmount).toNumber()); + const assetAmount = value.assetAmount; + setCurrentFieldValue(isEmpty(assetAmount) ? 0 : new Decimal(value.assetAmount).toNumber()); return { fields: { assetAmount: validateFormAmount( diff --git a/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx b/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx index 45d00dce..7d63307b 100644 --- a/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx +++ b/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx @@ -1,4 +1,4 @@ -import { Divider, HStack, VStack } from '@chakra-ui/react'; +import { HStack, VStack } from '@chakra-ui/react'; import { VaultState } from 'dlc-btc-lib/models'; import { VaultExpandedInformationButton } from './components/vault.details.button-group.button'; @@ -36,8 +36,7 @@ export function VaultExpandedInformationButtonGroup({ return ( - - + - - + + + + + Promise; + connectUnisatWallet: (isFordefi?: boolean) => Promise; handleFundingTransaction: ( dlcHandler: SoftwareWalletDLCHandler, vault: RawVault, @@ -131,10 +131,16 @@ export function useUnisat(): UseUnisatReturnType { * * @returns A promise that resolves to the user's taproot address. */ - async function getBitcoinAddresses(): Promise { + async function getBitcoinAddresses(isFordefi: boolean = false): Promise { try { if (!window.unisat) { - throw new UnisatError('Unisat Wallet is not installed'); + throw new UnisatError( + isFordefi ? 'Fordefi Wallet is Not Installed' : 'Unisat Wallet is Not Installed' + ); + } else if (isFordefi && !window?.unisat?.is_fordefi) { + throw new UnisatError('Please disable Unisat Wallet and enable Fordefi Wallet'); + } else if (!isFordefi && window?.unisat?.is_fordefi) { + throw new UnisatError('Please disable Fordefi Wallet and enable Unisat Wallet'); } const userAddresses: string[] = await window.unisat.requestAccounts(); @@ -159,11 +165,11 @@ export function useUnisat(): UseUnisatReturnType { * * @returns A promise that resolves to the User's Taproot Address. */ - async function connectUnisatWallet(): Promise { + async function connectUnisatWallet(isFordefi: boolean = false): Promise { try { setIsLoading([true, 'Connecting To Unisat Wallet']); - const taprootAccount = await getBitcoinAddresses(); + const taprootAccount = await getBitcoinAddresses(isFordefi); const unisatDLCHandler = new SoftwareWalletDLCHandler( taprootAccount.publicKey, diff --git a/src/shared/models/wallet.ts b/src/shared/models/wallet.ts index 31189491..2b7fbb1f 100644 --- a/src/shared/models/wallet.ts +++ b/src/shared/models/wallet.ts @@ -2,6 +2,7 @@ export enum BitcoinWalletType { Leather = 'Leather', Ledger = 'Ledger', Unisat = 'Unisat', + Fordefi = 'Fordefi', } export interface BitcoinWallet { @@ -28,4 +29,10 @@ const unisat: BitcoinWallet = { logo: '/images/logos/unisat-logo.svg', }; -export const bitcoinWallets: BitcoinWallet[] = [leather, ledger, unisat]; +const fordefi: BitcoinWallet = { + id: BitcoinWalletType.Fordefi, + name: 'Fordefi', + logo: '/images/logos/fordefi-logo.svg', +}; + +export const bitcoinWallets: BitcoinWallet[] = [leather, ledger, unisat, fordefi]; diff --git a/src/shared/utils.ts b/src/shared/utils.ts index be70a651..bdf6bdc2 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -37,11 +37,14 @@ export function findEthereumNetworkByName(ethereumNetworkName: string): Chain { export function formatEvent(event: DetailedEvent): FormattedEvent { const isMint = event.eventType === 'mint'; + const date = new Date(event.timestamp * 1000); return { dlcBTCAmount: isMint ? event.value : -event.value, merchant: isMint ? event.to : event.from, txHash: event.txHash, - date: new Date(event.timestamp * 1000).toDateString(), + date: date + .toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) + .replace(',', ''), isMint, chain: event.chain, isCCIP: event.isCCIP, diff --git a/src/styles/css-styles.ts b/src/styles/css-styles.ts index 54675a27..8a71de02 100644 --- a/src/styles/css-styles.ts +++ b/src/styles/css-styles.ts @@ -20,3 +20,11 @@ export const boxShadowAnimation = keyframes` 75% { box-shadow: 0 0 10px rgba(255,255,255,0.5); } 100% { box-shadow: 0 0 5px rgba(7,232,216,0); } `; + +export const orangeBoxShadowAnimation = keyframes` +0% { box-shadow: 0 0 5px rgba(255, 168, 0, 0); } +25% { box-shadow: 0 0 10px rgba(255, 168, 0,0.5); } +50% { box-shadow: 0 0 15px rgba(255, 168, 0,0.75); } +75% { box-shadow: 0 0 10px rgba(255, 168, 0,0.5); } +100% { box-shadow: 0 0 5px rgba(7,232,216,0); } +`; diff --git a/src/styles/modal-theme.ts b/src/styles/modal-theme.ts index 04be30e7..97e04200 100644 --- a/src/styles/modal-theme.ts +++ b/src/styles/modal-theme.ts @@ -5,7 +5,7 @@ const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpe const baseStyle = definePartsStyle({ dialogContainer: { - top: '19.5%', + top: '10.5%', }, dialog: { padding: '15px',