From b81c15fd055112203a9293fa99715f4b9166422d Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 5 Dec 2024 12:00:50 +0100 Subject: [PATCH] feat: modify dlc-btc-lib version, add function to get required taproot public key for the attestors --- package.json | 2 +- src/app/hooks/use-psbt.ts | 15 +- updates.patch | 1484 +++++++++++++++++++++++++++++++++++++ yarn.lock | 8 +- 4 files changed, 1500 insertions(+), 9 deletions(-) create mode 100644 updates.patch diff --git a/package.json b/package.json index 4251a8b7..c00b6a6d 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "concurrently": "^8.2.2", "d3": "^7.9.0", "decimal.js": "^10.4.3", - "dlc-btc-lib": "2.4.19-dfns-beta", + "dlc-btc-lib": "2.4.21", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 8f705eec..04467c60 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -33,6 +33,16 @@ interface UsePSBTReturnType { isLoading: [boolean, string]; } +const getRequiredTaprootPublicKey = ( + bitcoinWalletType: BitcoinWalletType, + dlcHandler: LedgerDLCHandler | SoftwareWalletDLCHandler | DFNSDLCHandler +): string => { + if (bitcoinWalletType === BitcoinWalletType.DFNS && dlcHandler instanceof DFNSDLCHandler) { + return `02${dlcHandler.getTaprootTweakedPublicKey()}`; + } + return dlcHandler.getTaprootDerivedPublicKey(); +}; + export function usePSBT(): UsePSBTReturnType { const { ethereumNetworkConfiguration: { dlcManagerContract, ethereumAttestorChainID }, @@ -220,10 +230,7 @@ export function usePSBT(): UsePSBTReturnType { vaultUUID, fundingPSBT: bytesToHex(fundingTransaction.toPSBT()), userEthereumAddress: userAddress, - userBitcoinTaprootPublicKey: - bitcoinWalletType === 'DFNS' - ? `02${(dlcHandler as DFNSDLCHandler).getTaprootTweakedPublicKey()}` - : dlcHandler.getTaprootDerivedPublicKey(), + userBitcoinTaprootPublicKey: getRequiredTaprootPublicKey(bitcoinWalletType, dlcHandler), attestorChainID: attestorChainIDs[networkType] as AttestorChainID, }); break; diff --git a/updates.patch b/updates.patch new file mode 100644 index 00000000..95d61573 --- /dev/null +++ b/updates.patch @@ -0,0 +1,1484 @@ +diff --git a/config.devnet.json b/config.devnet.json +index bf03854..5e908bf 100644 +--- a/config.devnet.json ++++ b/config.devnet.json +@@ -12,6 +12,16 @@ + "rippleIssuerAddress": "rLTBw1MEy45uE5qmkWseinbj7h4zmdQuR8", + "xrplWebsocket": "wss://s.altnet.rippletest.net:51233", + "ledgerApp": "Bitcoin Test", ++ "dfnsConfiguration": { ++ "dfnsBaseURL": "https://api.dfns.ninja", ++ "dfnsCustomerConfigurations": [ ++ { ++ "name": "Tungsten", ++ "organizationID": "or-3pqgf-ugmhq-8969lp77c7f8uf81", ++ "applicationID": "ap-513sv-f5knb-811qggt4oh6hd5s9" ++ } ++ ] ++ }, + "merchants": [ + { + "name": "Amber", +diff --git a/config.mainnet.json b/config.mainnet.json +index ecb7218..8e10ac6 100644 +--- a/config.mainnet.json ++++ b/config.mainnet.json +@@ -12,6 +12,16 @@ + "rippleIssuerAddress": "rGcyRGrZPaJAZbZDi4NqRFLA5GQH63iFpD", + "xrplWebsocket": "wss://xrpl.ws/", + "ledgerApp": "Bitcoin", ++ "dfnsConfiguration": { ++ "dfnsBaseURL": "https://api.dfns.ninja", ++ "dfnsCustomerConfigurations": [ ++ { ++ "name": "Tungsten", ++ "organizationID": "or-3pqgf-ugmhq-8969lp77c7f8uf81", ++ "applicationID": "ap-513sv-f5knb-811qggt4oh6hd5s9" ++ } ++ ] ++ }, + "merchants": [ + { + "name": "Amber", +diff --git a/config.testnet.json b/config.testnet.json +index 5e039a5..fdd81e6 100644 +--- a/config.testnet.json ++++ b/config.testnet.json +@@ -12,6 +12,16 @@ + "rippleIssuerAddress": "ra3oyRVfy4yD4NJPrVcewvDtisZ3FhkcYL", + "xrplWebsocket": "wss://testnet.xrpl-labs.com/", + "ledgerApp": "Bitcoin Test", ++ "dfnsConfiguration": { ++ "dfnsBaseURL": "https://api.dfns.ninja", ++ "dfnsCustomerConfigurations": [ ++ { ++ "name": "Tungsten", ++ "organizationID": "or-3pqgf-ugmhq-8969lp77c7f8uf81", ++ "applicationID": "ap-513sv-f5knb-811qggt4oh6hd5s9" ++ } ++ ] ++ }, + "merchants": [ + { + "name": "Amber", +diff --git a/package.json b/package.json +index 2d93448..4251a8b 100644 +--- a/package.json ++++ b/package.json +@@ -24,6 +24,8 @@ + "dependencies": { + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", ++ "@dfns/sdk": "^0.5.9", ++ "@dfns/sdk-browser": "^0.5.9", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fontsource-variable/onest": "^5.0.3", +@@ -46,7 +48,7 @@ + "concurrently": "^8.2.2", + "d3": "^7.9.0", + "decimal.js": "^10.4.3", +- "dlc-btc-lib": "2.4.18", ++ "dlc-btc-lib": "2.4.19-dfns-beta", + "dotenv": "^16.3.1", + "ethers": "5.7.2", + "formik": "^2.4.5", +diff --git a/public/images/logos/dfns-logo.svg b/public/images/logos/dfns-logo.svg +new file mode 100644 +index 0000000..0cbb18d +--- /dev/null ++++ b/public/images/logos/dfns-logo.svg +@@ -0,0 +1 @@ ++ +diff --git a/src/app/components/modals/components/modal-container.tsx b/src/app/components/modals/components/modal-container.tsx +index 84b71b2..53acc5b 100644 +--- a/src/app/components/modals/components/modal-container.tsx ++++ b/src/app/components/modals/components/modal-container.tsx +@@ -5,6 +5,7 @@ import { AnyAction } from '@reduxjs/toolkit'; + import { RootState } from '@store/index'; + import { modalActions } from '@store/slices/modal/modal.actions'; + ++import { DFNSModal } from '../dfns-modal/dfns-modal'; + import { LedgerModal } from '../ledger-modal/ledger-modal'; + import { SelectBitcoinWalletModal } from '../select-bitcoin-wallet-modal/select-bitcoin-wallet-modal'; + import { SuccessfulFlowModal } from '../successful-flow-modal/successful-flow-modal'; +@@ -21,6 +22,7 @@ export function ModalContainer(): React.JSX.Element { + isSuccesfulFlowModalOpen, + isSelectBitcoinWalletModalOpen, + isLedgerModalOpen, ++ isDFNSModalOpen, + } = useSelector((state: RootState) => state.modal); + + const handleClosingModal = (actionCreator: () => AnyAction) => { +@@ -60,6 +62,10 @@ export function ModalContainer(): React.JSX.Element { + isOpen={isLedgerModalOpen} + handleClose={() => handleClosingModal(modalActions.toggleLedgerModalVisibility)} + /> ++ handleClosingModal(modalActions.toggleDFNSModalVisibility)} ++ /> + + ); + } +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-credentials-form.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-credentials-form.tsx +new file mode 100644 +index 0000000..20dbe82 +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-credentials-form.tsx +@@ -0,0 +1,72 @@ ++import { Button, FormControl, FormErrorMessage, FormLabel, Input, VStack } from '@chakra-ui/react'; ++import { DFNSCustomerConfiguration } from '@models/configuration'; ++import { useForm } from '@tanstack/react-form'; ++ ++interface DFNSModalRegisterFormProps { ++ onSubmit: ( ++ credentialCode: string, ++ selectedDFNSOrganization: DFNSCustomerConfiguration ++ ) => Promise; ++ selectedDFNSOrganization: DFNSCustomerConfiguration; ++} ++ ++export function DFNSModalRegisterForm({ ++ onSubmit, ++ selectedDFNSOrganization, ++}: DFNSModalRegisterFormProps): React.JSX.Element { ++ const formAPI = useForm({ ++ defaultValues: { ++ credentialCode: '', ++ }, ++ onSubmit: async ({ value }) => { ++ await onSubmit(value.credentialCode, selectedDFNSOrganization); ++ }, ++ }); ++ ++ return ( ++ ++
{ ++ e.preventDefault(); ++ e.stopPropagation(); ++ void formAPI.handleSubmit(); ++ }} ++ > ++ ++ { ++ if (!value) return 'Credential Code is required'; ++ if (value.length < 9) return 'Credential Code must be at least 9 characters'; ++ return undefined; ++ }, ++ }} ++ children={field => ( ++ ++ ++ Credential Code ++ ++ field.handleChange(e.target.value)} ++ placeholder="xxxxxxxxx" ++ /> ++ {field.state.meta.errorMap.onChange} ++ ++ )} ++ /> ++ [state.canSubmit]} ++ children={([canSubmit]) => ( ++ ++ )} ++ /> ++ ++
++
++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx +new file mode 100644 +index 0000000..24ba22a +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx +@@ -0,0 +1,25 @@ ++import { HStack, Text } from '@chakra-ui/react'; ++ ++interface DFNSModalErrorBoxProps { ++ error: string | undefined; ++} ++ ++export function DFNSModalErrorBox({ error }: DFNSModalErrorBoxProps): React.JSX.Element | false { ++ return ( ++ !!error && ( ++ ++ ++ {error} ++ ++ ++ ) ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx +new file mode 100644 +index 0000000..80713c9 +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx +@@ -0,0 +1,78 @@ ++import { Button, FormControl, FormErrorMessage, FormLabel, Input, VStack } from '@chakra-ui/react'; ++import { DFNSCustomerConfiguration } from '@models/configuration'; ++import { useForm } from '@tanstack/react-form'; ++ ++interface DFNSModalLoginFormProps { ++ onSubmit: (email: string, selectedDFNSOrganization: DFNSCustomerConfiguration) => Promise; ++ selectedDFNSOrganization: DFNSCustomerConfiguration; ++} ++ ++const validateEmail = (email: string) => { ++ if (!email) return 'Email address is required'; ++ ++ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; ++ if (!emailRegex.test(email)) return 'Please enter a valid email address'; ++ ++ const [localPart] = email.split('@'); ++ if (localPart.length > 64) return 'Local part of email cannot exceed 64 characters'; ++ if (email.length > 254) return 'Email address cannot exceed 254 characters'; ++ ++ return undefined; ++}; ++ ++export function DFNSModalLoginForm({ ++ onSubmit, ++ selectedDFNSOrganization, ++}: DFNSModalLoginFormProps): React.JSX.Element { ++ const formAPI = useForm({ ++ defaultValues: { ++ email: '', ++ }, ++ onSubmit: async ({ value }) => { ++ await onSubmit(value.email, selectedDFNSOrganization); ++ }, ++ }); ++ ++ return ( ++ ++
{ ++ e.preventDefault(); ++ e.stopPropagation(); ++ void formAPI.handleSubmit(); ++ }} ++ > ++ ++ validateEmail(value), ++ }} ++ children={field => ( ++ ++ ++ E-Mail Address ++ ++ field.handleChange(e.target.value)} ++ placeholder="example@domain.com" ++ /> ++ {field.state.meta.errorMap.onChange} ++ ++ )} ++ /> ++ [state.canSubmit]} ++ children={([canSubmit]) => ( ++ ++ )} ++ /> ++ ++
++
++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx +new file mode 100644 +index 0000000..380cc2b +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx +@@ -0,0 +1,43 @@ ++import { ReactNode } from 'react'; ++ ++import { ++ Image, ++ Modal, ++ ModalBody, ++ ModalCloseButton, ++ ModalContent, ++ ModalHeader, ++ ModalOverlay, ++ VStack, ++} from '@chakra-ui/react'; ++ ++interface DFNSModalLayoutProps { ++ logo: string; ++ isOpen: boolean; ++ onClose: () => void; ++ children: ReactNode; ++} ++ ++export function DFNSModalLayout({ ++ logo, ++ isOpen, ++ onClose, ++ children, ++}: DFNSModalLayoutProps): React.JSX.Element { ++ return ( ++ ++ ++ ++ ++ {'DFNS ++ ++ ++ ++ ++ {children} ++ ++ ++ ++ ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-loading-stack.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-loading-stack.tsx +new file mode 100644 +index 0000000..95dc93b +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-loading-stack.tsx +@@ -0,0 +1,19 @@ ++import { HStack, Spinner, Text } from '@chakra-ui/react'; ++ ++interface DFNSModalLoadingStackProps { ++ isLoading: [boolean, string]; ++} ++export function DFNSModalLoadingStack({ ++ isLoading, ++}: DFNSModalLoadingStackProps): React.JSX.Element | false { ++ return ( ++ isLoading[0] && ( ++ ++ ++ {isLoading[1]} ++ ++ ++ ++ ) ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-navigator-button-stack.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-navigator-button-stack.tsx +new file mode 100644 +index 0000000..d7fb2d3 +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-navigator-button-stack.tsx +@@ -0,0 +1,28 @@ ++import { Button, Text, VStack } from '@chakra-ui/react'; ++ ++interface DFNSModalNavigatorButtonProps { ++ isRegister: boolean; ++ setIsRegister: (isRegister: boolean) => void; ++ isVisible: boolean; ++} ++ ++export function DFNSModalNavigatorButton({ ++ isRegister, ++ setIsRegister, ++ isVisible, ++}: DFNSModalNavigatorButtonProps): React.JSX.Element | false { ++ if (!isVisible) return false; ++ ++ return ( ++ ++ ++ {!isRegister ++ ? 'New user? Click the button below to register with your DFNS credential code.' ++ : 'Existing user? Sign in with your e-mail address.'} ++ ++ ++ ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/components/dfns-modal-address-button.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/components/dfns-modal-address-button.tsx +new file mode 100644 +index 0000000..1bf61e6 +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/components/dfns-modal-address-button.tsx +@@ -0,0 +1,23 @@ ++import { Button, HStack, Text } from '@chakra-ui/react'; ++import { truncateAddress } from 'dlc-btc-lib/utilities'; ++ ++interface DFNSModalAddressButtonProps { ++ addressInformation: { address: string | undefined; walletID: string }; ++ setWalletID: (walletID: string) => void; ++} ++ ++export function DFNSModalAddressButton({ ++ addressInformation, ++ setWalletID, ++}: DFNSModalAddressButtonProps): React.JSX.Element { ++ const { address, walletID } = addressInformation; ++ return ( ++ ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx +new file mode 100644 +index 0000000..4b461a9 +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx +@@ -0,0 +1,46 @@ ++import { ButtonGroup, Text, VStack } from '@chakra-ui/react'; ++import { scrollBarDFNSAddressCSS } from '@styles/css-styles'; ++ ++import { DFNSModalAddressButton } from './components/dfns-modal-address-button'; ++ ++interface DFNSModalSelectAddressMenuProps { ++ taprootAddresses: { address: string | undefined; walletID: string }[] | undefined; ++ isLoading: boolean; ++ isSuccesful: boolean; ++ error: string | undefined; ++ setWalletID: (walletID: string) => void; ++} ++ ++export function DFNSModalSelectAddressMenu({ ++ taprootAddresses, ++ isLoading, ++ isSuccesful, ++ error, ++ setWalletID, ++}: DFNSModalSelectAddressMenuProps): React.JSX.Element | false { ++ return ( ++ !isLoading && ++ !isSuccesful && ++ !error && ( ++ ++ Select Bitcoin Address ++ ++ {taprootAddresses?.map(address => ( ++ ++ ))} ++ ++ ++ ) ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-select-organization-menu.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-select-organization-menu.tsx +new file mode 100644 +index 0000000..1f7b73b +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-select-organization-menu.tsx +@@ -0,0 +1,44 @@ ++import { ChevronDownIcon } from '@chakra-ui/icons'; ++import { HStack, Menu, MenuButton, MenuItem, MenuList, Text, VStack } from '@chakra-ui/react'; ++import { DFNSCustomerConfiguration } from '@models/configuration'; ++ ++interface DFNSModalSelectOrganizationMenuProps { ++ handleChangeOrganization: (dfnsOrganization: DFNSCustomerConfiguration) => void; ++ dfnsOrganizations: DFNSCustomerConfiguration[]; ++ selectedDFNSOrganization: DFNSCustomerConfiguration; ++} ++ ++export function DFNSModalSelectOrganizationMenu({ ++ handleChangeOrganization, ++ dfnsOrganizations, ++ selectedDFNSOrganization, ++}: DFNSModalSelectOrganizationMenuProps): React.JSX.Element { ++ return ( ++ ++ ++ Organization ++ ++ ++ ++ ++ {selectedDFNSOrganization.name} ++ ++ ++ ++ ++ {dfnsOrganizations.map(dfnsOrganization => { ++ return ( ++ handleChangeOrganization(dfnsOrganization)} ++ > ++ {dfnsOrganization.name} ++ ++ ); ++ })} ++ ++ ++ ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-success-icon.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-success-icon.tsx +new file mode 100644 +index 0000000..03b65be +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/components/dfns-modal-success-icon.tsx +@@ -0,0 +1,16 @@ ++import { CheckCircleIcon } from '@chakra-ui/icons'; ++import { SlideFade } from '@chakra-ui/react'; ++ ++interface DFNSModalSuccessIconProps { ++ isSuccesful: boolean; ++} ++ ++export function DFNSModalSuccessIcon({ ++ isSuccesful, ++}: DFNSModalSuccessIconProps): React.JSX.Element { ++ return ( ++ ++ ++ ++ ); ++} +diff --git a/src/app/components/modals/dfns-modal/dfns-modal.tsx b/src/app/components/modals/dfns-modal/dfns-modal.tsx +new file mode 100644 +index 0000000..6e02cad +--- /dev/null ++++ b/src/app/components/modals/dfns-modal/dfns-modal.tsx +@@ -0,0 +1,127 @@ ++import { useContext, useState } from 'react'; ++ ++import { Collapse, VStack } from '@chakra-ui/react'; ++import { useDFNS } from '@hooks/use-dfns'; ++import { DFNSCustomerConfiguration } from '@models/configuration'; ++import { ++ BitcoinWalletContext, ++ BitcoinWalletContextState, ++} from '@providers/bitcoin-wallet-context-provider'; ++import { delay } from 'dlc-btc-lib/utilities'; ++ ++import { ModalComponentProps } from '../components/modal-container'; ++import { DFNSModalRegisterForm } from './components/dfns-modal-credentials-form'; ++import { DFNSModalErrorBox } from './components/dfns-modal-error-box'; ++import { DFNSModalLoginForm } from './components/dfns-modal-form'; ++import { DFNSModalLayout } from './components/dfns-modal-layout'; ++import { DFNSModalLoadingStack } from './components/dfns-modal-loading-stack'; ++import { DFNSModalNavigatorButton } from './components/dfns-modal-navigator-button-stack'; ++import { DFNSModalSelectAddressMenu } from './components/dfns-modal-select-address-menu/dfns-modal-select-address-menu'; ++import { DFNSModalSelectOrganizationMenu } from './components/dfns-modal-select-organization-menu'; ++import { DFNSModalSuccessIcon } from './components/dfns-modal-success-icon'; ++ ++export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { ++ const { connectDFNSWallet, registerCredentials, selectWallet, isLoading } = useDFNS(); ++ ++ const { setBitcoinWalletContextState } = useContext(BitcoinWalletContext); ++ ++ const [taprootAddresses, setTaprootAddresses] = useState< ++ { address: string | undefined; walletID: string }[] | undefined ++ >(undefined); ++ const [organization, setOrganization] = useState( ++ appConfiguration.dfnsConfiguration.dfnsCustomerConfigurations[0] ++ ); ++ ++ const [isSuccesful, setIsSuccesful] = useState(false); ++ const [isRegister, setIsRegister] = useState(false); ++ const [isLoadingAddressList, setIsLoadingAddressList] = useState(true); ++ const [dfnsError, setDFNSError] = useState(undefined); ++ ++ function resetDFNSModalValues() { ++ setIsSuccesful(false); ++ setTaprootAddresses(undefined); ++ setIsLoadingAddressList(true); ++ setDFNSError(undefined); ++ } ++ ++ async function setError(error: string) { ++ setDFNSError(error); ++ await delay(5000); ++ setDFNSError(undefined); ++ } ++ ++ async function connectDFNSWalletAndGetAddresses( ++ username: string, ++ organization: DFNSCustomerConfiguration ++ ) { ++ try { ++ const taprootAddresses = await connectDFNSWallet(username, organization); ++ setTaprootAddresses(taprootAddresses); ++ setIsLoadingAddressList(false); ++ } catch (error: any) { ++ await setError(error.message); ++ } ++ } ++ ++ async function handleRegisterCredentials(code: string, organization: DFNSCustomerConfiguration) { ++ try { ++ await registerCredentials(code, organization); ++ setIsRegister(false); ++ } catch (error: any) { ++ await setError(error.message); ++ } ++ } ++ ++ async function setWalletID(walletID: string) { ++ try { ++ await selectWallet(walletID); ++ setIsSuccesful(true); ++ await delay(2500); ++ resetDFNSModalValues(); ++ setBitcoinWalletContextState(BitcoinWalletContextState.READY); ++ handleClose(); ++ } catch (error: any) { ++ await setError(error.message); ++ } ++ } ++ ++ return ( ++ ++ ++ ++ ++ ++ {!isRegister ? ( ++ ++ ) : ( ++ ++ )} ++ ++ ++ ++ ++ ++ ++ ++ ); ++} +diff --git a/src/app/components/modals/select-bitcoin-wallet-modal/components/select-bitcoin-wallet-modal-menu.tsx b/src/app/components/modals/select-bitcoin-wallet-modal/components/select-bitcoin-wallet-modal-menu.tsx +index a8c78ea..2047c65 100644 +--- a/src/app/components/modals/select-bitcoin-wallet-modal/components/select-bitcoin-wallet-modal-menu.tsx ++++ b/src/app/components/modals/select-bitcoin-wallet-modal/components/select-bitcoin-wallet-modal-menu.tsx +@@ -15,7 +15,7 @@ export function SelectBitcoinWalletMenu({ + return ( + +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 6e7899d..64c7d8b 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 +@@ -64,6 +64,10 @@ export function SelectBitcoinWalletModal({ + case BitcoinWalletType.Ledger: + dispatch(modalActions.toggleLedgerModalVisibility()); + break; ++ case BitcoinWalletType.DFNS: ++ dispatch(modalActions.toggleDFNSModalVisibility()); ++ break; ++ break; + default: + break; + } +diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts +new file mode 100644 +index 0000000..9b3c92b +--- /dev/null ++++ b/src/app/hooks/use-dfns.ts +@@ -0,0 +1,272 @@ ++import { useContext, useState } from 'react'; ++ ++import { DfnsApiClient, DfnsAuthenticator } from '@dfns/sdk'; ++import { WebAuthnSigner } from '@dfns/sdk-browser'; ++import { DFNSCustomerConfiguration } from '@models/configuration'; ++import { BitcoinWalletType } from '@models/wallet'; ++import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; ++import { DFNSDLCHandler } from 'dlc-btc-lib'; ++import { RawVault, Transaction } from 'dlc-btc-lib/models'; ++import { shiftValue } from 'dlc-btc-lib/utilities'; ++import { bytesToHex } from 'viem'; ++ ++import { BITCOIN_NETWORK_MAP } from '@shared/constants/bitcoin.constants'; ++ ++const BITCOIN_NETWORK_DFNS_MAP = { ++ mainnet: 'Bitcoin', ++ testnet: 'BitcoinTestnet3', ++ regtest: 'BitcoinTestnet3', ++}; ++ ++interface UseDFNSReturnType { ++ connectDFNSWallet: ( ++ userName: string, ++ dfnsConfiguration: DFNSCustomerConfiguration ++ ) => Promise<{ address: string | undefined; walletID: string }[]>; ++ registerCredentials: ( ++ code: string, ++ dfnsConfiguration: DFNSCustomerConfiguration ++ ) => Promise; ++ selectWallet: (walletId: string) => Promise; ++ handleFundingTransaction: ( ++ dlcHandler: DFNSDLCHandler, ++ vault: RawVault, ++ bitcoinAmount: number, ++ attestorGroupPublicKey: string, ++ feeRateMultiplier: number ++ ) => Promise; ++ handleDepositTransaction: ( ++ dlcHandler: DFNSDLCHandler, ++ vault: RawVault, ++ depositAmount: number, ++ attestorGroupPublicKey: string, ++ feeRateMultiplier: number ++ ) => Promise; ++ handleWithdrawalTransaction: ( ++ dlcHandler: DFNSDLCHandler, ++ withdrawAmount: number, ++ attestorGroupPublicKey: string, ++ vault: RawVault, ++ feeRateMultiplier: number ++ ) => Promise; ++ isLoading: [boolean, string]; ++} ++ ++export function useDFNS(): UseDFNSReturnType { ++ const { setDLCHandler, setBitcoinWalletType, dlcHandler } = useContext(BitcoinWalletContext); ++ ++ const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); ++ ++ async function connectDFNSWallet( ++ userName: string, ++ dfnsConfiguration: DFNSCustomerConfiguration ++ ): Promise<{ address: string | undefined; walletID: string }[]> { ++ try { ++ setIsLoading([true, 'Connecting to DFNS Wallet']); ++ ++ const { dfnsBaseURL } = appConfiguration.dfnsConfiguration; ++ ++ const { applicationID, organizationID } = dfnsConfiguration; ++ ++ const signer = new WebAuthnSigner(); ++ const authApi = new DfnsAuthenticator({ ++ appId: applicationID, ++ baseUrl: dfnsBaseURL, ++ signer, ++ }); ++ ++ const { token } = await authApi.login({ ++ username: userName, ++ orgId: organizationID, ++ }); ++ ++ const dfnsDLCHandler = new DFNSDLCHandler( ++ applicationID, ++ dfnsBaseURL, ++ token, ++ BITCOIN_NETWORK_MAP[appConfiguration.bitcoinNetwork], ++ appConfiguration.bitcoinBlockchainURL, ++ appConfiguration.bitcoinBlockchainFeeEstimateURL ++ ); ++ ++ setDLCHandler(dfnsDLCHandler); ++ setBitcoinWalletType(BitcoinWalletType.DFNS); ++ setIsLoading([false, '']); ++ ++ return (await dfnsDLCHandler.getWallets()).items ++ .filter( ++ item => ++ item.network === BITCOIN_NETWORK_DFNS_MAP[appConfiguration.bitcoinNetwork] && ++ item.signingKey.scheme === 'Schnorr' && ++ item.signingKey.curve === 'secp256k1' ++ ) ++ .map(item => ({ ++ address: item.address, ++ walletID: item.id, ++ })); ++ } catch (error) { ++ setIsLoading([false, '']); ++ throw new Error(`Error connecting to DFNS Wallet: ${error}`); ++ } ++ } ++ ++ async function selectWallet(walletId: string): Promise { ++ try { ++ setIsLoading([true, 'Selecting DFNS Wallet']); ++ ++ await (dlcHandler as DFNSDLCHandler).initializeWalletByID(walletId); ++ ++ setIsLoading([false, '']); ++ } catch (error) { ++ setIsLoading([false, '']); ++ throw new Error(`Error selecting Wallet: ${error}`); ++ } ++ } ++ ++ async function registerCredentials( ++ code: string, ++ dfnsConfiguration: DFNSCustomerConfiguration ++ ): Promise { ++ const { dfnsBaseURL } = appConfiguration.dfnsConfiguration; ++ ++ if (!dfnsConfiguration) { ++ throw new Error('DFNS Configuration not found'); ++ } ++ ++ const { applicationID } = dfnsConfiguration; ++ ++ const signer = new WebAuthnSigner(); ++ const dfnsAPI = new DfnsApiClient({ ++ appId: applicationID, ++ authToken: undefined, ++ baseUrl: dfnsBaseURL, ++ signer, ++ }); ++ ++ const challenge = await dfnsAPI.auth.createCredentialChallengeWithCode({ ++ body: { code, credentialKind: 'Fido2' }, ++ }); ++ ++ if (challenge.kind !== 'Fido2') { ++ throw Error('Not a Fido2 challenge'); // this check is meant for proper typescript type inferrence ++ } ++ ++ const attestation = await new WebAuthnSigner().create(challenge); ++ ++ await dfnsAPI.auth.createCredentialWithCode({ ++ body: { ++ credentialName: 'iBTC App - ' + new Date().toISOString(), ++ challengeIdentifier: challenge.challengeIdentifier, ++ credentialKind: attestation.credentialKind, ++ credentialInfo: attestation.credentialInfo, ++ }, ++ }); ++ } ++ ++ /** ++ * Creates the Funding Transaction and signs it with Leather Wallet. ++ * @param vaultUUID The Vault UUID. ++ * @returns The Signed Funding Transaction. ++ */ ++ async function handleFundingTransaction( ++ dlcHandler: DFNSDLCHandler, ++ vault: RawVault, ++ bitcoinAmount: number, ++ attestorGroupPublicKey: string, ++ feeRateMultiplier: number ++ ): Promise { ++ try { ++ setIsLoading([true, 'Creating Funding Transaction']); ++ ++ // ==> Create Funding Transaction ++ const fundingPSBT = await dlcHandler?.createFundingPSBT( ++ vault, ++ BigInt(shiftValue(bitcoinAmount)), ++ attestorGroupPublicKey, ++ feeRateMultiplier ++ ); ++ ++ setIsLoading([true, 'Sign Funding Transaction in your DFNS Wallet']); ++ ++ const signedFundingTransaction = await dlcHandler.signPSBT(fundingPSBT, 'funding'); ++ ++ setIsLoading([false, '']); ++ return signedFundingTransaction; ++ } catch (error) { ++ setIsLoading([false, '']); ++ throw new Error(`Error handling Funding Transaction: ${error}`); ++ } ++ } ++ ++ async function handleDepositTransaction( ++ dlcHandler: DFNSDLCHandler, ++ vault: RawVault, ++ depositAmount: number, ++ attestorGroupPublicKey: string, ++ feeRateMultiplier: number ++ ): Promise { ++ try { ++ setIsLoading([true, 'Creating Deposit Transaction']); ++ ++ const depositPSBT = await dlcHandler.createDepositPSBT( ++ BigInt(shiftValue(depositAmount)), ++ vault, ++ attestorGroupPublicKey, ++ vault.fundingTxId, ++ feeRateMultiplier ++ ); ++ ++ setIsLoading([true, 'Sign Deposit Transaction in your DFNS Wallet']); ++ ++ const signedDepositTransaction = await dlcHandler.signPSBT(depositPSBT, 'deposit'); ++ ++ setIsLoading([false, '']); ++ return signedDepositTransaction; ++ } catch (error) { ++ setIsLoading([false, '']); ++ throw new Error(`Error handling Deposit Transaction: ${error}`); ++ } ++ } ++ ++ async function handleWithdrawalTransaction( ++ dlcHandler: DFNSDLCHandler, ++ withdrawAmount: number, ++ attestorGroupPublicKey: string, ++ vault: RawVault, ++ feeRateMultiplier: number ++ ): Promise { ++ try { ++ setIsLoading([true, 'Creating Withdraw Transaction']); ++ ++ const withdrawalPSBT = await dlcHandler.createWithdrawPSBT( ++ vault, ++ BigInt(shiftValue(withdrawAmount)), ++ attestorGroupPublicKey, ++ vault.fundingTxId, ++ feeRateMultiplier ++ ); ++ ++ setIsLoading([true, 'Sign Withdraw Transaction in your DFNS Wallet']); ++ ++ const signedWithdrawTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); ++ ++ setIsLoading([false, '']); ++ const psbtHex = bytesToHex(signedWithdrawTransaction.toPSBT()); ++ ++ return psbtHex.slice(2); ++ } catch (error) { ++ setIsLoading([false, '']); ++ throw new Error(`Error handling Withdrawal Transaction: ${error}`); ++ } ++ } ++ ++ return { ++ connectDFNSWallet, ++ registerCredentials, ++ selectWallet, ++ handleFundingTransaction, ++ handleDepositTransaction, ++ handleWithdrawalTransaction, ++ isLoading, ++ }; ++} +diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts +index d921714..8f705ee 100644 +--- a/src/app/hooks/use-psbt.ts ++++ b/src/app/hooks/use-psbt.ts +@@ -9,7 +9,7 @@ import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network + import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; + import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; + import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; +-import { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; ++import { DFNSDLCHandler, LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; + import { + submitFundingPSBT, + submitWithdrawDepositPSBT, +@@ -21,6 +21,7 @@ import { useAccount } from 'wagmi'; + + import { NetworkType } from '@shared/constants/network.constants'; + ++import { useDFNS } from './use-dfns'; + import { useLeather } from './use-leather'; + import { useLedger } from './use-ledger'; + import { useUnisat } from './use-unisat'; +@@ -69,6 +70,13 @@ export function usePSBT(): UsePSBTReturnType { + isLoading: isUnisatLoading, + } = useUnisat(); + ++ const { ++ handleFundingTransaction: handleFundingTransactionWithDFNS, ++ handleDepositTransaction: handleDepositTransactionWithDFNS, ++ handleWithdrawalTransaction: handleWithdrawalTransactionWithDFNS, ++ isLoading: isDFNSLoading, ++ } = useDFNS(); ++ + const [bitcoinDepositAmount, setBitcoinDepositAmount] = useState(0); + + const attestorChainIDs = { +@@ -138,6 +146,27 @@ export function usePSBT(): UsePSBTReturnType { + ); + } + break; ++ case 'DFNS': ++ switch (vault.valueLocked.toNumber()) { ++ case 0: ++ fundingTransaction = await handleFundingTransactionWithDFNS( ++ dlcHandler as DFNSDLCHandler, ++ vault, ++ depositAmount, ++ attestorGroupPublicKey, ++ feeRateMultiplier ++ ); ++ break; ++ default: ++ fundingTransaction = await handleDepositTransactionWithDFNS( ++ dlcHandler as DFNSDLCHandler, ++ vault, ++ depositAmount, ++ attestorGroupPublicKey, ++ feeRateMultiplier ++ ); ++ } ++ break; + case 'Unisat': + switch (vault.valueLocked.toNumber()) { + case 0: +@@ -185,14 +214,16 @@ export function usePSBT(): UsePSBTReturnType { + default: + throw new BitcoinError('Invalid Bitcoin Wallet Type'); + } +- + switch (vault.status) { + case VaultState.READY: + await submitFundingPSBT([appConfiguration.coordinatorURL], { + vaultUUID, + fundingPSBT: bytesToHex(fundingTransaction.toPSBT()), + userEthereumAddress: userAddress, +- userBitcoinTaprootPublicKey: dlcHandler.getTaprootDerivedPublicKey(), ++ userBitcoinTaprootPublicKey: ++ bitcoinWalletType === 'DFNS' ++ ? `02${(dlcHandler as DFNSDLCHandler).getTaprootTweakedPublicKey()}` ++ : dlcHandler.getTaprootDerivedPublicKey(), + attestorChainID: attestorChainIDs[networkType] as AttestorChainID, + }); + break; +@@ -250,6 +281,15 @@ export function usePSBT(): UsePSBTReturnType { + feeRateMultiplier + ); + break; ++ case 'DFNS': ++ withdrawalTransactionHex = await handleWithdrawalTransactionWithDFNS( ++ dlcHandler as DFNSDLCHandler, ++ withdrawAmount, ++ attestorGroupPublicKey, ++ vault, ++ feeRateMultiplier ++ ); ++ break; + default: + throw new BitcoinError('Invalid Bitcoin Wallet Type'); + } +@@ -270,6 +310,7 @@ export function usePSBT(): UsePSBTReturnType { + [BitcoinWalletType.Leather]: isLeatherLoading, + [BitcoinWalletType.Unisat]: isUnisatLoading, + [BitcoinWalletType.Fordefi]: isUnisatLoading, ++ [BitcoinWalletType.DFNS]: isDFNSLoading, + }; + + return { +diff --git a/src/app/providers/bitcoin-wallet-context-provider.tsx b/src/app/providers/bitcoin-wallet-context-provider.tsx +index 1baa129..70a6f82 100644 +--- a/src/app/providers/bitcoin-wallet-context-provider.tsx ++++ b/src/app/providers/bitcoin-wallet-context-provider.tsx +@@ -2,7 +2,7 @@ import { createContext, useState } from 'react'; + + import { HasChildren } from '@models/has-children'; + import { BitcoinWalletType } from '@models/wallet'; +-import { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; ++import { DFNSDLCHandler, LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; + + export enum BitcoinWalletContextState { + INITIAL = 0, +@@ -15,9 +15,9 @@ interface BitcoinWalletContextProviderType { + setBitcoinWalletType: React.Dispatch>; + bitcoinWalletContextState: BitcoinWalletContextState; + setBitcoinWalletContextState: React.Dispatch>; +- dlcHandler: SoftwareWalletDLCHandler | LedgerDLCHandler | undefined; ++ dlcHandler: SoftwareWalletDLCHandler | LedgerDLCHandler | DFNSDLCHandler | undefined; + setDLCHandler: React.Dispatch< +- React.SetStateAction ++ React.SetStateAction + >; + resetBitcoinWalletContext: () => void; + } +@@ -38,7 +38,9 @@ export function BitcoinWalletContextProvider({ children }: HasChildren): React.J + const [bitcoinWalletType, setBitcoinWalletType] = useState( + BitcoinWalletType.Leather + ); +- const [dlcHandler, setDLCHandler] = useState(); ++ const [dlcHandler, setDLCHandler] = useState< ++ SoftwareWalletDLCHandler | LedgerDLCHandler | DFNSDLCHandler ++ >(); + + function resetBitcoinWalletContext() { + setBitcoinWalletContextState(BitcoinWalletContextState.INITIAL); +diff --git a/src/app/store/slices/modal/modal.slice.ts b/src/app/store/slices/modal/modal.slice.ts +index ef32b01..f6a1523 100644 +--- a/src/app/store/slices/modal/modal.slice.ts ++++ b/src/app/store/slices/modal/modal.slice.ts +@@ -6,6 +6,7 @@ interface ModalState { + isSuccesfulFlowModalOpen: [boolean, Vault | undefined, string, string, number]; + isSelectBitcoinWalletModalOpen: boolean; + isLedgerModalOpen: boolean; ++ isDFNSModalOpen: boolean; + } + + const initialModalState: ModalState = { +@@ -13,6 +14,7 @@ const initialModalState: ModalState = { + isSuccesfulFlowModalOpen: [false, undefined, '', 'mint', 0], + isSelectBitcoinWalletModalOpen: false, + isLedgerModalOpen: false, ++ isDFNSModalOpen: true, + }; + + export const modalSlice = createSlice({ +@@ -38,5 +40,8 @@ export const modalSlice = createSlice({ + toggleLedgerModalVisibility: state => { + state.isLedgerModalOpen = !state.isLedgerModalOpen; + }, ++ toggleDFNSModalVisibility: state => { ++ state.isDFNSModalOpen = !state.isDFNSModalOpen; ++ }, + }, + }); +diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts +index 14ef674..fe77457 100644 +--- a/src/shared/models/configuration.ts ++++ b/src/shared/models/configuration.ts +@@ -17,6 +17,17 @@ enum AppEnvironment { + LOCALHOST = 'localhost', + } + ++export interface DFNSCustomerConfiguration { ++ name: string; ++ organizationID: string; ++ applicationID: string; ++} ++ ++interface DFNSConfiguration { ++ dfnsBaseURL: string; ++ dfnsCustomerConfigurations: DFNSCustomerConfiguration[]; ++} ++ + type BitcoinNetworkPrefix = 'bc1' | 'tb1' | 'bcrt1'; + export const ALL_SUPPORTED_BITCOIN_NETWORK_PREFIX: BitcoinNetworkPrefix[] = ['bc1', 'tb1', 'bcrt1']; + +@@ -42,6 +53,7 @@ export interface Configuration { + bitcoinBlockchainFeeEstimateURL: string; + rippleIssuerAddress: string; + ledgerApp: string; ++ dfnsConfiguration: DFNSConfiguration; + merchants: Merchant[]; + protocols: Protocol[]; + } +diff --git a/src/shared/models/wallet.ts b/src/shared/models/wallet.ts +index 596427a..49e649d 100644 +--- a/src/shared/models/wallet.ts ++++ b/src/shared/models/wallet.ts +@@ -3,6 +3,7 @@ export enum BitcoinWalletType { + Ledger = 'Ledger', + Unisat = 'Unisat', + Fordefi = 'Fordefi', ++ DFNS = 'DFNS', + } + + export interface BitcoinWallet { +@@ -35,6 +36,12 @@ const fordefi: BitcoinWallet = { + logo: '/images/logos/fordefi-logo.svg', + }; + ++const dfns: BitcoinWallet = { ++ id: BitcoinWalletType.DFNS, ++ name: 'DFNS', ++ logo: '/images/logos/dfns-logo.svg', ++}; ++ + export enum XRPWalletType { + Ledger = 'Ledger', + Gem = 'Gem', +@@ -59,4 +66,4 @@ const gemXRP: XRPWallet = { + }; + + export const xrpWallets: XRPWallet[] = [ledgerXRP, gemXRP]; +-export const bitcoinWallets: BitcoinWallet[] = [leather, ledger, unisat, fordefi]; ++export const bitcoinWallets: BitcoinWallet[] = [leather, ledger, unisat, fordefi, dfns]; +diff --git a/src/styles/button-theme.ts b/src/styles/button-theme.ts +index 56c887a..addf6fc 100644 +--- a/src/styles/button-theme.ts ++++ b/src/styles/button-theme.ts +@@ -111,6 +111,18 @@ const ledger = defineStyle({ + }, + }); + ++const dfns = defineStyle({ ++ p: '10px', ++ h: '50px', ++ w: '375px', ++ bg: 'transparent', ++ border: '1.5px solid', ++ borderColor: '#D6D7EB', ++ _hover: { ++ bgColor: 'white.03', ++ }, ++}); ++ + const bitcoinAddress = defineStyle({ + bg: 'transparent', + px: '2.5%', +@@ -170,6 +182,7 @@ export const buttonTheme = defineStyleConfig({ + navigate, + merchantHistory, + ledger, ++ dfns, + points, + merchantTableItem, + }, +diff --git a/src/styles/css-styles.ts b/src/styles/css-styles.ts +index 8a71de0..873e0d7 100644 +--- a/src/styles/css-styles.ts ++++ b/src/styles/css-styles.ts +@@ -13,6 +13,19 @@ export const scrollBarCSS = { + }, + }; + ++export const scrollBarDFNSAddressCSS = { ++ '&::-webkit-scrollbar': { ++ background: 'rgba(255,255,255,0.25)', ++ width: '3.5px', ++ }, ++ '&::-webkit-scrollbar-track': { ++ width: '2.5px', ++ }, ++ '&::-webkit-scrollbar-thumb': { ++ background: '#E1FF0B', ++ }, ++}; ++ + export const boxShadowAnimation = keyframes` + 0% { box-shadow: 0 0 5px rgba(255,255,255,0); } + 25% { box-shadow: 0 0 10px rgba(255,255,255,0.5); } +diff --git a/src/styles/menu-theme.ts b/src/styles/menu-theme.ts +index f76b0c8..ea2cf2c 100644 +--- a/src/styles/menu-theme.ts ++++ b/src/styles/menu-theme.ts +@@ -125,6 +125,45 @@ const network = definePartsStyle({ + }, + }); + ++const organization = definePartsStyle({ ++ button: { ++ justifyContent: 'center', ++ p: '10px', ++ h: '50px', ++ w: '375px', ++ bg: 'transparent', ++ border: '1px solid', ++ borderColor: 'white.01', ++ borderRadius: 'md', ++ color: 'white', ++ fontSize: 'sm', ++ fontWeight: 600, ++ _hover: { ++ background: 'white.03', ++ }, ++ }, ++ list: { ++ p: '10px', ++ w: '375px', ++ bgColor: '#170C33', ++ border: '1.5px solid', ++ borderColor: 'border.white.01', ++ borderRadius: 'md', ++ }, ++ item: { ++ justifyContent: 'center', ++ bgColor: 'inherit', ++ borderRadius: 'md', ++ color: 'white', ++ fontSize: 'xs', ++ fontWeight: 400, ++ _hover: { ++ background: 'white.03', ++ }, ++ transition: 'all 0.05s ease-in-out', ++ }, ++}); ++ + const ledgerAddress = definePartsStyle({ + button: { + justifyContent: 'center', +@@ -184,6 +223,7 @@ const variants = { + account, + ledgerAddress, + networkChange, ++ organization, + }; + + export const menuTheme = defineMultiStyleConfig({ sizes, variants }); +diff --git a/src/styles/modal-theme.ts b/src/styles/modal-theme.ts +index 97e0420..4ec9205 100644 +--- a/src/styles/modal-theme.ts ++++ b/src/styles/modal-theme.ts +@@ -37,8 +37,27 @@ const ledgerModalStyle = definePartsStyle({ + }, + }); + ++const dfnsModalStyle = definePartsStyle({ ++ dialogContainer: { ++ top: '13.5%', ++ }, ++ dialog: { ++ padding: '15px', ++ fontFamily: 'Inter', ++ height: 'auto', ++ width: '500px', ++ border: '1.5px solid', ++ borderColor: '#E1FF0B', ++ borderRadius: 'md', ++ backgroundColor: '#170C33', ++ color: '#D6D7EB', ++ alignItems: 'center', ++ }, ++}); ++ + const variants = { + ledger: ledgerModalStyle, ++ dfns: dfnsModalStyle, + }; + + export const modalTheme = defineMultiStyleConfig({ +diff --git a/yarn.lock b/yarn.lock +index 8395a43..e2dbc0f 100644 +--- a/yarn.lock ++++ b/yarn.lock +@@ -1117,6 +1117,24 @@ + preact "^10.16.0" + sha.js "^2.4.11" + ++"@dfns/sdk-browser@^0.5.9": ++ version "0.5.9" ++ resolved "https://registry.yarnpkg.com/@dfns/sdk-browser/-/sdk-browser-0.5.9.tgz#073bdc2fabb6b53eafd114a31ebf355be9d66062" ++ integrity sha512-lC0RnE61PC4wrDRAizyLk5gvgiPiK7dkLfETfJ+X8lO1B95+ovRcR9Md2so2z3EpCdBgGCQ4IKKk6ybYbp78Ag== ++ dependencies: ++ buffer "6.0.3" ++ cross-fetch "3.1.6" ++ uuid "9.0.0" ++ ++"@dfns/sdk@^0.5.9": ++ version "0.5.9" ++ resolved "https://registry.yarnpkg.com/@dfns/sdk/-/sdk-0.5.9.tgz#37994f90f3397d58e7e57982043e594d06b5d851" ++ integrity sha512-c4DDFMvVsx5EwPc9xLK8+0MitpTPFMmyvvuZJTAYSRlG9IdXNHw8eRDHA4qjltgbraYNXWB2YtxKod6BS8Witg== ++ dependencies: ++ buffer "6.0.3" ++ cross-fetch "3.1.6" ++ uuid "9.0.0" ++ + "@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz" +@@ -4342,7 +4360,7 @@ buffer-from@^1.0.0: + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +-buffer@^6.0.3: ++buffer@6.0.3, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== +@@ -4652,6 +4670,13 @@ create-hmac@^1.1.3, create-hmac@^1.1.7: + safe-buffer "^5.0.1" + sha.js "^2.4.8" + ++cross-fetch@3.1.6: ++ version "3.1.6" ++ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.6.tgz#bae05aa31a4da760969756318feeee6e70f15d6c" ++ integrity sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g== ++ dependencies: ++ node-fetch "^2.6.11" ++ + cross-fetch@^3.1.4: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" +@@ -5195,11 +5220,13 @@ dir-glob@^3.0.1: + dependencies: + path-type "^4.0.0" + +-dlc-btc-lib@2.4.18: +- version "2.4.18" +- resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.18.tgz#246c15af91bfcf03212f8abd440b122f575510f4" +- integrity sha512-Q7t8VGrrbA2ioyNvvNXxH8dfqQwFUDLpLNWHwqqY0H8zaD4gXpKswi1clDHy7gASZhcLvBBOKAYp5+PUkja/YA== ++dlc-btc-lib@2.4.19-dfns-beta: ++ version "2.4.19-dfns-beta" ++ resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.19-dfns-beta.tgz#4d9ddb7591a0d3f5cc99d09a7e25cea7aece77e3" ++ integrity sha512-niqR5KixLyrxgR91DzcDfpb5sW7IEWtHqpsLRTFFI679hcPabglYI5l5NsNc6UZ08DHI7ecd2/ZKnEXlMMxCpg== + dependencies: ++ "@dfns/sdk" "^0.5.9" ++ "@dfns/sdk-browser" "^0.5.9" + "@gemwallet/api" "3.8.0" + "@ledgerhq/hw-app-btc" "10.4.1" + "@ledgerhq/hw-app-xrp" "6.29.4" +@@ -7164,7 +7191,7 @@ node-fetch-native@^1.6.2, node-fetch-native@^1.6.3, node-fetch-native@^1.6.4: + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" + integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== + +-node-fetch@^2.6.1, node-fetch@^2.6.12: ++node-fetch@^2.6.1, node-fetch@^2.6.11, node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== +@@ -8964,6 +8991,11 @@ util@^0.12.3, util@^0.12.4: + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + ++uuid@9.0.0: ++ version "9.0.0" ++ resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" ++ integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== ++ + uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" diff --git a/yarn.lock b/yarn.lock index e2dbc0f7..ebeb17c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5220,10 +5220,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dlc-btc-lib@2.4.19-dfns-beta: - version "2.4.19-dfns-beta" - resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.19-dfns-beta.tgz#4d9ddb7591a0d3f5cc99d09a7e25cea7aece77e3" - integrity sha512-niqR5KixLyrxgR91DzcDfpb5sW7IEWtHqpsLRTFFI679hcPabglYI5l5NsNc6UZ08DHI7ecd2/ZKnEXlMMxCpg== +dlc-btc-lib@2.4.21: + version "2.4.21" + resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.21.tgz#5e7294c413bb4f8a66fe2ba0ef05ccd011faec24" + integrity sha512-56EyZ9Udd98pPWTKRj/PGIie9wxt1lupmOqqQD4CL1YKxobtIdmD8Uv9/d0uczpEXyQRyqnxAycTIBPvknPJxg== dependencies: "@dfns/sdk" "^0.5.9" "@dfns/sdk-browser" "^0.5.9"