diff --git a/package.json b/package.json
index 4251a8b..c00b6a6 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 8f705ee..04467c6 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 0000000..95d6157
--- /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 (
++
++
++
++ );
++}
+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 (
++
++
++
++ );
++}
+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 (
++
++
++
++
++
++
++
++
++
++ {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
++
++
++
++ );
++}
+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 e2dbc0f..ebeb17c 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"