From d0488abe2d02080eea883380e909900183f5588d Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 13 Feb 2025 16:56:59 +0100 Subject: [PATCH 1/2] feat: modify usePSBT hook, abstract logic into hooks, dry functions --- .../mint-unmint/components/mint/mint.tsx | 2 +- .../mint-unmint/components/unmint/unmint.tsx | 2 +- src/app/hooks/use-get-vault.ts | 49 ++ src/app/hooks/use-leather.ts | 35 +- src/app/hooks/use-ledger.ts | 22 +- src/app/hooks/use-psbt.ts | 468 +++++++----------- src/app/hooks/use-unisat-fordefi.ts | 30 +- src/app/hooks/use-user-address.ts | 37 ++ src/shared/models/configuration.ts | 1 + vite.config.ts | 1 + 10 files changed, 312 insertions(+), 335 deletions(-) create mode 100644 src/app/hooks/use-get-vault.ts create mode 100644 src/app/hooks/use-user-address.ts diff --git a/src/app/components/mint-unmint/components/mint/mint.tsx b/src/app/components/mint-unmint/components/mint/mint.tsx index b3c932be..1f057c38 100644 --- a/src/app/components/mint-unmint/components/mint/mint.tsx +++ b/src/app/components/mint-unmint/components/mint/mint.tsx @@ -29,7 +29,7 @@ export function Mint(): React.JSX.Element { {[1, 2].includes(mintStep.step) && ( )} diff --git a/src/app/hooks/use-get-vault.ts b/src/app/hooks/use-get-vault.ts new file mode 100644 index 00000000..095eb006 --- /dev/null +++ b/src/app/hooks/use-get-vault.ts @@ -0,0 +1,49 @@ +import { useContext } from 'react'; + +import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { getRawVault } from 'dlc-btc-lib/ethereum-functions'; +import { RawVault } from 'dlc-btc-lib/models'; +import { getRippleVault } from 'dlc-btc-lib/ripple-functions'; + +import { NetworkType } from '@shared/constants/network.constants'; + +interface UseFetchVault { + fetchVault: (vaultUUID: string) => Promise; +} + +export function useFetchVault(): UseFetchVault { + const { networkType } = useContext(NetworkConfigurationContext); + + const { + ethereumNetworkConfiguration: { dlcManagerContract }, + } = useContext(EthereumNetworkConfigurationContext); + + const { rippleClient } = useContext(RippleNetworkConfigurationContext); + + const fetchEVMVault = async (vaultUUID: string): Promise => { + return await getRawVault(dlcManagerContract, vaultUUID); + }; + + const fetchXRPLVault = async (vaultUUID: string): Promise => { + return await getRippleVault(rippleClient, appConfiguration.rippleIssuerAddress, vaultUUID); + }; + + const fetchVault = async (vaultUUID: string): Promise => { + try { + switch (networkType) { + case NetworkType.EVM: + return fetchEVMVault(vaultUUID); + case NetworkType.XRPL: + return fetchXRPLVault(vaultUUID); + } + } catch (error) { + throw new Error('Error getting Vault: ' + error); + } + }; + + return { + fetchVault, + }; +} diff --git a/src/app/hooks/use-leather.ts b/src/app/hooks/use-leather.ts index 94777e43..3da1a27c 100644 --- a/src/app/hooks/use-leather.ts +++ b/src/app/hooks/use-leather.ts @@ -10,7 +10,6 @@ import { RpcResponse, } from '@models/software-wallet.models'; import { BitcoinWalletAction, BitcoinWalletType } from '@models/wallet'; -import { bytesToHex } from '@noble/hashes/utils'; import { BitcoinWalletContext, BitcoinWalletContextState, @@ -24,7 +23,6 @@ import { BITCOIN_NETWORK_MAP, walletLoadingState } from '@shared/constants/bitco interface UseLeatherReturnType { connectLeatherWallet: () => Promise; handleFundingTransaction: ( - dlcHandler: LeatherDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -32,7 +30,6 @@ interface UseLeatherReturnType { feeRateMultiplier: number ) => Promise; handleDepositTransaction: ( - dlcHandler: LeatherDLCHandler, vault: RawVault, bitcoinAmount: number, attestorGroupPublicKey: string, @@ -40,19 +37,23 @@ interface UseLeatherReturnType { feeRateMultiplier: number ) => Promise; handleWithdrawTransaction: ( - dlcHandler: LeatherDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, feeRecipient: string, feeRateMultiplier: number - ) => Promise; + ) => Promise; isLoading: [boolean, string]; } export function useLeather(): UseLeatherReturnType { - const { setDLCHandler, setBitcoinWalletContextState, setBitcoinWalletType, bitcoinWalletType } = - useContext(BitcoinWalletContext); + const { + setDLCHandler, + dlcHandler, + setBitcoinWalletContextState, + setBitcoinWalletType, + bitcoinWalletType, + } = useContext(BitcoinWalletContext); const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); @@ -130,7 +131,6 @@ export function useLeather(): UseLeatherReturnType { /** * Creates the Funding Transaction and signs it with Leather Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param bitcoinAmount The Bitcoin Amount to fund the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -139,7 +139,6 @@ export function useLeather(): UseLeatherReturnType { * @returns The Signed Funding Transaction. */ async function handleFundingTransaction( - dlcHandler: LeatherDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -147,12 +146,14 @@ export function useLeather(): UseLeatherReturnType { feeRateMultiplier: number ): Promise { try { + if (!dlcHandler) throw new LeatherError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.CREATING_TRANSACTION, bitcoinWalletType, 'Funding') ); const formattedDepositAmount = BigInt(shiftValue(depositAmount)); - const fundingPSBT = await dlcHandler?.createFundingPSBT( + const fundingPSBT = await dlcHandler.createFundingPSBT( vault, formattedDepositAmount, attestorGroupPublicKey, @@ -176,7 +177,6 @@ export function useLeather(): UseLeatherReturnType { /** * Creates a Deposit Transaction and signs it with Leather Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param depositAmount The Bitcoin Amount to deposit into the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -185,7 +185,6 @@ export function useLeather(): UseLeatherReturnType { * @returns The Signed Deposit Transaction. */ async function handleDepositTransaction( - dlcHandler: LeatherDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -193,13 +192,15 @@ export function useLeather(): UseLeatherReturnType { feeRateMultiplier: number ): Promise { try { + if (!dlcHandler) throw new LeatherError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.CREATING_TRANSACTION, bitcoinWalletType, 'Deposit') ); const formattedDepositAmount = BigInt(shiftValue(depositAmount)); - const depositPSBT = await dlcHandler?.createDepositPSBT( + const depositPSBT = await dlcHandler.createDepositPSBT( vault, formattedDepositAmount, attestorGroupPublicKey, @@ -224,7 +225,6 @@ export function useLeather(): UseLeatherReturnType { /** * Creates a Withdraw Transaction and signs it with Unisat or Fordefi Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param withdrawAmount The Bitcoin Amount to withdraw from the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -233,14 +233,15 @@ export function useLeather(): UseLeatherReturnType { * @returns The Signed Withdraw Transaction. */ async function handleWithdrawTransaction( - dlcHandler: LeatherDLCHandler, vault: RawVault, withdrawAmount: number, attestorGroupPublicKey: string, feeRecipient: string, feeRateMultiplier: number - ): Promise { + ): Promise { try { + if (!dlcHandler) throw new LeatherError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.CREATING_TRANSACTION, bitcoinWalletType, 'Withdraw') ); @@ -265,7 +266,7 @@ export function useLeather(): UseLeatherReturnType { 'withdraw' ); - return bytesToHex(signedWithdrawTransaction.toPSBT()); + return signedWithdrawTransaction; } catch (error) { throw new LeatherError(`Error handling Withdrawal Transaction: ${error}`); } finally { diff --git a/src/app/hooks/use-ledger.ts b/src/app/hooks/use-ledger.ts index e5b52528..17413cda 100644 --- a/src/app/hooks/use-ledger.ts +++ b/src/app/hooks/use-ledger.ts @@ -42,7 +42,6 @@ interface UseLedgerReturnType { paymentType: SupportedPaymentType ) => Promise; handleFundingTransaction: ( - dlcHandler: LedgerDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -50,7 +49,6 @@ interface UseLedgerReturnType { feeRateMultiplier: number ) => Promise; handleDepositTransaction: ( - dlcHandler: LedgerDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -58,18 +56,17 @@ interface UseLedgerReturnType { feeRateMultiplier: number ) => Promise; handleWithdrawTransaction: ( - dlcHandler: LedgerDLCHandler, vault: RawVault, withdrawAmount: number, attestorGroupPublicKey: string, feeRecipient: string, feeRateMultiplier: number - ) => Promise; + ) => Promise; isLoading: [boolean, string]; } export function useLedger(): UseLedgerReturnType { - const { setBitcoinWalletContextState, setDLCHandler, bitcoinWalletType } = + const { setDLCHandler, dlcHandler, setBitcoinWalletContextState, bitcoinWalletType } = useContext(BitcoinWalletContext); const [ledgerApp, setLedgerApp] = useState(undefined); @@ -281,7 +278,6 @@ export function useLedger(): UseLedgerReturnType { * @returns The Signed Funding Transaction. */ async function handleFundingTransaction( - dlcHandler: LedgerDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -289,6 +285,8 @@ export function useLedger(): UseLedgerReturnType { feeRateMultiplier: number ): Promise { try { + if (!dlcHandler) throw new LedgerError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.ACCEPT_MULTI_SIG_WALLET_POLICY, bitcoinWalletType) ); @@ -319,7 +317,6 @@ export function useLedger(): UseLedgerReturnType { /** * Creates a Deposit Transaction and signs it with Ledger Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param depositAmount The Bitcoin Amount to deposit into the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -328,7 +325,6 @@ export function useLedger(): UseLedgerReturnType { * @returns The Signed Deposit Transaction. */ async function handleDepositTransaction( - dlcHandler: LedgerDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -336,6 +332,8 @@ export function useLedger(): UseLedgerReturnType { feeRateMultiplier: number ): Promise { try { + if (!dlcHandler) throw new LedgerError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.ACCEPT_MULTI_SIG_WALLET_POLICY, bitcoinWalletType) ); @@ -367,7 +365,6 @@ export function useLedger(): UseLedgerReturnType { /** * Creates a Withdraw Transaction and signs it with Ledger Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param withdrawAmount The Bitcoin Amount to withdraw from the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -376,14 +373,15 @@ export function useLedger(): UseLedgerReturnType { * @returns The Signed Withdraw Transaction. */ async function handleWithdrawTransaction( - dlcHandler: LedgerDLCHandler, vault: RawVault, withdrawAmount: number, attestorGroupPublicKey: string, feeRecipient: string, feeRateMultiplier: number - ): Promise { + ): Promise { try { + if (!dlcHandler) throw new LedgerError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.ACCEPT_MULTI_SIG_WALLET_POLICY, bitcoinWalletType) ); @@ -405,7 +403,7 @@ export function useLedger(): UseLedgerReturnType { const withdrawalTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); - return bytesToHex(withdrawalTransaction.toPSBT()); + return withdrawalTransaction; } catch (error) { throw new LedgerError(`Error handling Withdrawal Transaction: ${error}`); } finally { diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 2fd0f05e..2eaeb3a5 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -1,342 +1,230 @@ -import { useContext, useState } from 'react'; +import { useContext } from 'react'; -import { getAttestorExtendedGroupPublicKey } from '@functions/attestor-request.functions'; import { BitcoinError } from '@models/error-types'; import { BitcoinWalletType } from '@models/wallet'; import { bytesToHex } from '@noble/hashes/utils'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; -import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; -import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; -import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; import { LeatherDLCHandler, LedgerDLCHandler, UnisatFordefiDLCHandler } from 'dlc-btc-lib'; import { - getAttestorConfigurationForChain, submitFundingPSBT, submitWithdrawDepositPSBT, } from 'dlc-btc-lib/attestor-request-functions'; -import { - getAttestorGroupPublicKey, - getFeeRecipient, - getRawVault, -} from 'dlc-btc-lib/ethereum-functions'; import { AttestorChainID, RawVault, Transaction, VaultState } from 'dlc-btc-lib/models'; -import { getRippleVault } from 'dlc-btc-lib/ripple-functions'; -import { useAccount } from 'wagmi'; - -import { NetworkType } from '@shared/constants/network.constants'; +import { equals } from 'ramda'; +import { useAttestorChainID } from './use-attestor-chain-id'; +import { useExtendedAttestorGroupPublicKey } from './use-extended-attestor-group-public-key'; +import { useFeeRecipient } from './use-fee-recipient'; +import { useFetchVault } from './use-get-vault'; import { useLeather } from './use-leather'; import { useLedger } from './use-ledger'; import { useUnisatFordefi } from './use-unisat-fordefi'; +import { useUserAddress } from './use-user-address'; + +interface TransactionParams { + vault: RawVault; + amount: number; + extendedAttestorGroupPublicKey: string; + feeRecipient: string; + bitcoinFeeRateMultiplier: number; +} + +type TransactionHandler = ( + vault: RawVault, + amount: number, + extendedAttestorGroupPublicKey: string, + feeRecipient: string, + bitcoinFeeRateMultiplier: number +) => Promise; + +interface PSBTSubmissionParams { + transaction: Transaction; + vault: RawVault; + userAddress: string; + dlcHandler: LeatherDLCHandler | LedgerDLCHandler | UnisatFordefiDLCHandler; + coordinatorURL: string; + attestorChainID: AttestorChainID; +} + +interface TransactionHandlers { + handleFundingTransaction: Record; + handleWithdrawTransaction: Record; + handleDepositTransaction: Record; +} + +type HandlerType = keyof TransactionHandlers; + +interface RequiredDependencies { + dlcHandler: LeatherDLCHandler | LedgerDLCHandler | UnisatFordefiDLCHandler; + bitcoinWalletType: BitcoinWalletType; + userAddress: string; +} interface UsePSBTReturnType { handleSignFundingTransaction: (vaultUUID: string, depositAmount: number) => Promise; handleSignWithdrawTransaction: (vaultUUID: string, withdrawAmount: number) => Promise; - bitcoinDepositAmount: number; - isLoading: [boolean, string]; -} - -interface NetworkConfig { - getUserAddress: () => string | undefined; - getVault: (vaultUUID: string) => Promise; - getAttestorGroupPublicKey: () => Promise; - getFeeRecipient: () => Promise; + isLoading: [boolean, string] | undefined; } export function usePSBT(): UsePSBTReturnType { - const { - ethereumNetworkConfiguration: { dlcManagerContract, ethereumAttestorChainID }, - } = useContext(EthereumNetworkConfigurationContext); - const { address: ethereumUserAddress } = useAccount(); - const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); - const { - rippleClient, - rippleNetworkConfiguration: { rippleAttestorChainID }, - } = useContext(RippleNetworkConfigurationContext); - - const { bitcoinWalletType, dlcHandler, resetBitcoinWalletContext } = + const { dlcHandler, bitcoinWalletType, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); - const { networkType } = useContext(NetworkConfigurationContext); - - const { - handleFundingTransaction: handleFundingTransactionWithLedger, - handleWithdrawTransaction: handleWithdrawTransactionWithLedger, - handleDepositTransaction: handleDepositTransactionWithLedger, - isLoading: isLedgerLoading, - } = useLedger(); - - const { - handleFundingTransaction: handleFundingTransactionWithLeather, - handleWithdrawTransaction: handleWithdrawTransactionWithLeather, - handleDepositTransaction: handleDepositTransactionWithLeather, - isLoading: isLeatherLoading, - } = useLeather(); - - const { - handleFundingTransaction: handleFundingTransactionWithUnisatFordefi, - handleWithdrawTransaction: handleWithdrawTransactionWithUnisatFordefi, - handleDepositTransaction: handleDepositTransactionWithUnisatFordefi, - isLoading: isUnisatLoading, - } = useUnisatFordefi(); - - const [bitcoinDepositAmount, setBitcoinDepositAmount] = useState(0); - - const attestorChainIDs = { - [NetworkType.EVM]: ethereumAttestorChainID, - [NetworkType.XRPL]: rippleAttestorChainID, - [NetworkType.BTC]: '', - }; + const { coordinatorURL, bitcoinFeeRateMultiplier } = appConfiguration; - type SupportedNetworkType = Exclude; + const { data: feeRecipient } = useFeeRecipient(); + const { data: extendedAttestorGroupPublicKey } = useExtendedAttestorGroupPublicKey(); - const isSupportedNetwork = (network: NetworkType): network is SupportedNetworkType => { - return network === NetworkType.EVM || network === NetworkType.XRPL; - }; + const { fetchVault } = useFetchVault(); + + const { data: userAddress } = useUserAddress(); + + const attestorChainID = useAttestorChainID(); - const networkConfigs: Record = { - [NetworkType.EVM]: { - getUserAddress: () => ethereumUserAddress, - getVault: vaultUUID => getRawVault(dlcManagerContract, vaultUUID), - getAttestorGroupPublicKey: () => getAttestorGroupPublicKey(dlcManagerContract), - getFeeRecipient: () => getFeeRecipient(dlcManagerContract), + const ledgerTransactionHandler = useLedger(); + const leatherTransactionHandler = useLeather(); + const unisatFordefiTransactionHandler = useUnisatFordefi(); + + const transactionHandlers: TransactionHandlers = { + handleFundingTransaction: { + [BitcoinWalletType.Leather]: leatherTransactionHandler.handleFundingTransaction, + [BitcoinWalletType.Ledger]: ledgerTransactionHandler.handleFundingTransaction, + [BitcoinWalletType.Unisat]: unisatFordefiTransactionHandler.handleFundingTransaction, + [BitcoinWalletType.Fordefi]: unisatFordefiTransactionHandler.handleFundingTransaction, }, - [NetworkType.XRPL]: { - getUserAddress: () => rippleUserAddress, - getVault: vaultUUID => - getRippleVault(rippleClient, appConfiguration.rippleIssuerAddress, vaultUUID), - getAttestorGroupPublicKey: getAttestorExtendedGroupPublicKey, - getFeeRecipient: async () => { - const config = await getAttestorConfigurationForChain( - appConfiguration.attestorSharedConfigurationURL, - 'ripple', - attestorChainIDs[NetworkType.XRPL] - ); - return config.btcFeeRecipient; - }, + handleWithdrawTransaction: { + [BitcoinWalletType.Leather]: leatherTransactionHandler.handleWithdrawTransaction, + [BitcoinWalletType.Ledger]: ledgerTransactionHandler.handleWithdrawTransaction, + [BitcoinWalletType.Unisat]: unisatFordefiTransactionHandler.handleWithdrawTransaction, + [BitcoinWalletType.Fordefi]: unisatFordefiTransactionHandler.handleWithdrawTransaction, }, + handleDepositTransaction: { + [BitcoinWalletType.Leather]: leatherTransactionHandler.handleDepositTransaction, + [BitcoinWalletType.Ledger]: ledgerTransactionHandler.handleDepositTransaction, + [BitcoinWalletType.Unisat]: unisatFordefiTransactionHandler.handleDepositTransaction, + [BitcoinWalletType.Fordefi]: unisatFordefiTransactionHandler.handleDepositTransaction, + }, + }; + + const submitPSBT = async ({ + transaction, + vault, + userAddress, + dlcHandler, + coordinatorURL, + attestorChainID, + }: PSBTSubmissionParams): Promise => { + const transactionPSBT = bytesToHex(transaction.toPSBT()); + + return equals(vault.status, VaultState.READY) + ? submitFundingPSBT([coordinatorURL], { + vaultUUID: vault.uuid, + fundingPSBT: transactionPSBT, + userEthereumAddress: userAddress, + userBitcoinTaprootPublicKey: dlcHandler.getUserTaprootPublicKey(), + attestorChainID, + }) + : submitWithdrawDepositPSBT([coordinatorURL], { + vaultUUID: vault.uuid, + withdrawDepositPSBT: transactionPSBT, + }); }; - const getRequiredPSBTInformation = async ( - vaultUUID: string - ): Promise<{ - userAddress: string; - vault: RawVault; - attestorGroupPublicKey: string; - feeRecipient: string; - }> => { - if (!isSupportedNetwork(networkType)) { - throw new Error('Network Type is not supported'); - } - - const config = networkConfigs[networkType]; - if (!config) { - throw new Error('Network Type is not setup'); - } - - const userAddress = config.getUserAddress(); - if (!userAddress) { - throw new Error('User Address is not setup'); - } - - const [vault, attestorGroupPublicKey, feeRecipient] = await Promise.all([ - config.getVault(vaultUUID), - config.getAttestorGroupPublicKey(), - config.getFeeRecipient(), - ]); + const getPSBTParameters = async ( + vaultUUID: string, + amount: number + ): Promise => { + if (!feeRecipient) throw new Error('Fee Recipient is not setup'); + if (!extendedAttestorGroupPublicKey) + throw new Error('Extended Attestor Group Public Key is not setup'); + + const vault = await fetchVault(vaultUUID); return { - userAddress, vault, - attestorGroupPublicKey, + amount, + extendedAttestorGroupPublicKey, feeRecipient, + bitcoinFeeRateMultiplier, }; }; - async function handleSignFundingTransaction( - vaultUUID: string, - depositAmount: number - ): Promise { - try { - if (!dlcHandler) throw new Error('DLC Handler is not setup'); - - const feeRateMultiplier = import.meta.env.VITE_FEE_RATE_MULTIPLIER; - - const { userAddress, vault, attestorGroupPublicKey, feeRecipient } = - await getRequiredPSBTInformation(vaultUUID); - - let fundingTransaction: Transaction; - switch (bitcoinWalletType) { - case 'Ledger': - switch (vault.valueLocked.toNumber()) { - case 0: - fundingTransaction = await handleFundingTransactionWithLedger( - dlcHandler as LedgerDLCHandler, - vault, - depositAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - default: - fundingTransaction = await handleDepositTransactionWithLedger( - dlcHandler as LedgerDLCHandler, - vault, - depositAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - } - break; - case 'Unisat': - switch (vault.valueLocked.toNumber()) { - case 0: - fundingTransaction = await handleFundingTransactionWithUnisatFordefi( - dlcHandler as UnisatFordefiDLCHandler, - vault, - depositAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - default: - fundingTransaction = await handleDepositTransactionWithUnisatFordefi( - dlcHandler as UnisatFordefiDLCHandler, - vault, - depositAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - } - break; - case 'Leather': - switch (vault.valueLocked.toNumber()) { - case 0: - fundingTransaction = await handleFundingTransactionWithLeather( - dlcHandler as LeatherDLCHandler, - vault, - depositAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - default: - fundingTransaction = await handleDepositTransactionWithLeather( - dlcHandler as LeatherDLCHandler, - vault, - depositAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - } - break; - default: - throw new BitcoinError('Invalid Bitcoin Wallet Type'); - } + const getRequiredDependencies = (): RequiredDependencies => { + if (!dlcHandler) throw new Error('DLC Handler is not setup'); + if (!bitcoinWalletType) throw new BitcoinError('Bitcoin Wallet Type is not setup'); + if (!userAddress) throw new Error('User Address is not setup'); - switch (vault.status) { - case VaultState.READY: - await submitFundingPSBT([appConfiguration.coordinatorURL], { - vaultUUID, - fundingPSBT: bytesToHex(fundingTransaction.toPSBT()), - userEthereumAddress: userAddress, - userBitcoinTaprootPublicKey: dlcHandler.getUserTaprootPublicKey(), - attestorChainID: attestorChainIDs[networkType] as AttestorChainID, - }); - break; - default: - await submitWithdrawDepositPSBT([appConfiguration.coordinatorURL], { - vaultUUID, - withdrawDepositPSBT: bytesToHex(fundingTransaction.toPSBT()), - }); - } + return { dlcHandler, bitcoinWalletType, userAddress }; + }; - setBitcoinDepositAmount(depositAmount); - resetBitcoinWalletContext(); - } catch (error) { - throw new BitcoinError(`Error signing Funding Transaction: ${error}`); - } - } + const handlerTypeMap = { + withdraw: (): HandlerType => 'handleWithdrawTransaction', + funding: (valueLocked: number): HandlerType => + equals(valueLocked, 0) ? 'handleFundingTransaction' : 'handleDepositTransaction', + } as const; + + const getHandlerType = (type: TransactionType, valueLocked: number): HandlerType => + handlerTypeMap[type](valueLocked); + + type TransactionType = 'withdraw' | 'funding'; + + const createTransactionHandler = + (type: TransactionType) => + async (vaultUUID: string, value: number): Promise => { + try { + const { dlcHandler, bitcoinWalletType, userAddress } = getRequiredDependencies(); + + const { + vault, + amount, + extendedAttestorGroupPublicKey, + feeRecipient, + bitcoinFeeRateMultiplier, + } = await getPSBTParameters(vaultUUID, value); + + const handlerType = getHandlerType(type, vault.valueLocked.toNumber()); + + const transactionHandler = transactionHandlers[handlerType][bitcoinWalletType]; + + const transaction = await transactionHandler( + vault, + amount, + extendedAttestorGroupPublicKey, + feeRecipient, + bitcoinFeeRateMultiplier + ); - async function handleSignWithdrawTransaction( - vaultUUID: string, - withdrawAmount: number - ): Promise { - try { - if (!dlcHandler) throw new Error('DLC Handler is not setup'); - - const feeRateMultiplier = import.meta.env.VITE_FEE_RATE_MULTIPLIER; - - const { vault, attestorGroupPublicKey, feeRecipient } = - await getRequiredPSBTInformation(vaultUUID); - - let withdrawalTransactionHex: string; - switch (bitcoinWalletType) { - case 'Ledger': - withdrawalTransactionHex = await handleWithdrawTransactionWithLedger( - dlcHandler as LedgerDLCHandler, - vault, - withdrawAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - case 'Unisat': - withdrawalTransactionHex = await handleWithdrawTransactionWithUnisatFordefi( - dlcHandler as UnisatFordefiDLCHandler, - vault, - withdrawAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - case 'Leather': - withdrawalTransactionHex = await handleWithdrawTransactionWithLeather( - dlcHandler as LeatherDLCHandler, - vault, - withdrawAmount, - attestorGroupPublicKey, - feeRecipient, - feeRateMultiplier - ); - break; - default: - throw new BitcoinError('Invalid Bitcoin Wallet Type'); + await submitPSBT({ + transaction, + vault, + userAddress, + dlcHandler, + coordinatorURL, + attestorChainID, + }); + + resetBitcoinWalletContext(); + } catch (error) { + if (error instanceof Error) { + throw new BitcoinError(`Error signing ${type} Transaction: ${error.message}`); + } + throw new BitcoinError(`Unknown error signing ${type} Transaction`); } + }; - await submitWithdrawDepositPSBT([appConfiguration.coordinatorURL], { - vaultUUID, - withdrawDepositPSBT: withdrawalTransactionHex, - }); - - resetBitcoinWalletContext(); - } catch (error) { - throw new BitcoinError(`Error signing Withdraw Transaction: ${error}`); - } - } + const handleSignFundingTransaction = createTransactionHandler('funding'); + const handleSignWithdrawTransaction = createTransactionHandler('withdraw'); const loadingStates = { - [BitcoinWalletType.Ledger]: isLedgerLoading, - [BitcoinWalletType.Leather]: isLeatherLoading, - [BitcoinWalletType.Unisat]: isUnisatLoading, - [BitcoinWalletType.Fordefi]: isUnisatLoading, + [BitcoinWalletType.Ledger]: ledgerTransactionHandler.isLoading, + [BitcoinWalletType.Leather]: leatherTransactionHandler.isLoading, + [BitcoinWalletType.Unisat]: unisatFordefiTransactionHandler.isLoading, + [BitcoinWalletType.Fordefi]: unisatFordefiTransactionHandler.isLoading, }; return { handleSignFundingTransaction, handleSignWithdrawTransaction, - bitcoinDepositAmount, - isLoading: bitcoinWalletType ? loadingStates[bitcoinWalletType] : [false, ''], + isLoading: loadingStates[bitcoinWalletType!], }; } diff --git a/src/app/hooks/use-unisat-fordefi.ts b/src/app/hooks/use-unisat-fordefi.ts index d9bebbd0..a6a4a0ac 100644 --- a/src/app/hooks/use-unisat-fordefi.ts +++ b/src/app/hooks/use-unisat-fordefi.ts @@ -18,7 +18,6 @@ import { BITCOIN_NETWORK_MAP, walletLoadingState } from '@shared/constants/bitco interface UseUnisatFordefiReturnType { connectUnisatOrFordefiWallet: (isFordefi?: boolean) => Promise; handleFundingTransaction: ( - dlcHandler: UnisatFordefiDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -26,7 +25,6 @@ interface UseUnisatFordefiReturnType { feeRateMultiplier: number ) => Promise; handleDepositTransaction: ( - dlcHandler: UnisatFordefiDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -34,19 +32,23 @@ interface UseUnisatFordefiReturnType { feeRateMultiplier: number ) => Promise; handleWithdrawTransaction: ( - dlcHandler: UnisatFordefiDLCHandler, vault: RawVault, withdrawAmount: number, attestorGroupPublicKey: string, feeRecipient: string, feeRateMultiplier: number - ) => Promise; + ) => Promise; isLoading: [boolean, string]; } export function useUnisatFordefi(): UseUnisatFordefiReturnType { - const { setDLCHandler, setBitcoinWalletContextState, setBitcoinWalletType, bitcoinWalletType } = - useContext(BitcoinWalletContext); + const { + setDLCHandler, + dlcHandler, + setBitcoinWalletContextState, + setBitcoinWalletType, + bitcoinWalletType, + } = useContext(BitcoinWalletContext); const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); @@ -147,7 +149,6 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { /** * Creates the Funding Transaction and signs it with Unisat or Fordefi Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param depositAmount The Bitcoin Amount to fund the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -156,7 +157,6 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { * @returns The Signed Funding Transaction. */ async function handleFundingTransaction( - dlcHandler: UnisatFordefiDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -164,6 +164,8 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { feeRateMultiplier: number ): Promise { try { + if (!dlcHandler) throw new UnisatFordefiError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.CREATING_TRANSACTION, bitcoinWalletType, 'Funding') ); @@ -194,7 +196,6 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { /** * Creates a Deposit Transaction and signs it with Unisat or Fordefi Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param depositAmount The Bitcoin Amount to deposit into the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -203,7 +204,6 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { * @returns The Signed Deposit Transaction. */ async function handleDepositTransaction( - dlcHandler: UnisatFordefiDLCHandler, vault: RawVault, depositAmount: number, attestorGroupPublicKey: string, @@ -211,6 +211,8 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { feeRateMultiplier: number ): Promise { try { + if (!dlcHandler) throw new UnisatFordefiError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.CREATING_TRANSACTION, bitcoinWalletType, 'Deposit') ); @@ -242,7 +244,6 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { /** * Creates a Withdraw Transaction and signs it with Unisat or Fordefi Wallet. - * @param dlcHandler The DLC Handler. * @param vault The Vault to interact with. * @param withdrawAmount The Bitcoin Amount to withdraw from the Vault. * @param attestorGroupPublicKey The Attestor Group Public Key. @@ -251,14 +252,15 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { * @returns The Signed Withdraw Transaction. */ async function handleWithdrawTransaction( - dlcHandler: UnisatFordefiDLCHandler, vault: RawVault, withdrawAmount: number, attestorGroupPublicKey: string, feeRecipient: string, feeRateMultiplier: number - ): Promise { + ): Promise { try { + if (!dlcHandler) throw new UnisatFordefiError('DLC Handler is not set'); + setIsLoading( walletLoadingState(BitcoinWalletAction.CREATING_TRANSACTION, bitcoinWalletType, 'Withdraw') ); @@ -283,7 +285,7 @@ export function useUnisatFordefi(): UseUnisatFordefiReturnType { 'withdraw' ); - return bytesToHex(signedWithdrawTransaction.toPSBT()); + return signedWithdrawTransaction; } catch (error) { throw new UnisatFordefiError(`Error handling Withdraw Transaction: ${error}`); } finally { diff --git a/src/app/hooks/use-user-address.ts b/src/app/hooks/use-user-address.ts new file mode 100644 index 00000000..b5f4470d --- /dev/null +++ b/src/app/hooks/use-user-address.ts @@ -0,0 +1,37 @@ +import { useContext } from 'react'; + +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; +import { UseQueryResult, useQuery } from '@tanstack/react-query'; +import { isNil } from 'ramda'; +import { useAccount } from 'wagmi'; + +import { NetworkType } from '@shared/constants/network.constants'; + +export function useUserAddress(): UseQueryResult { + const { networkType } = useContext(NetworkConfigurationContext); + + const { address: ethereumUserAddress } = useAccount(); + const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); + + const fetchUserAddress = async (): Promise => { + try { + switch (networkType) { + case NetworkType.EVM: + if (isNil(ethereumUserAddress)) throw new Error('EVM user address is not set'); + return ethereumUserAddress; + case NetworkType.XRPL: + if (isNil(rippleUserAddress)) throw new Error('XRPL user address is not set'); + return rippleUserAddress; + } + } catch (error) { + throw new Error('Error getting User Address: ' + error); + } + }; + + return useQuery({ + queryKey: ['userAddress', networkType], + queryFn: fetchUserAddress, + enabled: !!networkType, + }); +} diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index 5dca8cfb..b44ba173 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -46,6 +46,7 @@ export interface Configuration { bitcoinBlockchainURL: string; bitcoinBlockchainExplorerURL: string; bitcoinBlockchainFeeEstimateURL: string; + bitcoinFeeRateMultiplier: number; rippleIssuerAddress: string; ledgerApp: string; merchants: Merchant[]; diff --git a/vite.config.ts b/vite.config.ts index 7a61a907..3a985f08 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -82,6 +82,7 @@ export default defineConfig(async ({ mode }) => { appConfiguration.bscHTTP = bscURLs[1]; appConfiguration.walletConnectProjectID = env.VITE_WALLET_CONNECT_PROJECT_ID; appConfiguration.localAttestorExtendedGroupPublicKey = env.VITE_LOCAL_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY; + appConfiguration.feeRateMultiplier = Number(env.VITE_FEE_RATE_MULTIPLIER); return { plugins: [react(), wasm(), ViteToml()], From 257a506caf570c551e9c89f284b65f6684456382 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 14 Feb 2025 10:39:32 +0100 Subject: [PATCH 2/2] feat: move transaction models to it's own file, rename funding to deposit --- .../fetch-attestor-group-public-key.ts | 42 ---------- .../deposit-transaction-screen.tsx | 6 +- .../mint-unmint/components/mint/mint.tsx | 4 +- .../functions/attestor-request.functions.ts | 16 ---- src/app/hooks/use-ledger.ts | 1 - src/app/hooks/use-psbt.ts | 83 +++++++------------ src/app/hooks/use-unisat-fordefi.ts | 1 - src/shared/models/transaction.models.ts | 39 +++++++++ 8 files changed, 72 insertions(+), 120 deletions(-) delete mode 100644 netlify/functions/fetch-attestor-group-public-key.ts delete mode 100644 src/app/functions/attestor-request.functions.ts create mode 100644 src/shared/models/transaction.models.ts diff --git a/netlify/functions/fetch-attestor-group-public-key.ts b/netlify/functions/fetch-attestor-group-public-key.ts deleted file mode 100644 index ad9b605f..00000000 --- a/netlify/functions/fetch-attestor-group-public-key.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Handler, HandlerEvent } from '@netlify/functions'; -import { getAttestorExtendedGroupPublicKey } from 'dlc-btc-lib/attestor-request-functions'; - -const handler: Handler = async (event: HandlerEvent) => { - try { - if (!event.queryStringParameters) { - return { - statusCode: 400, - body: JSON.stringify({ - message: 'No Parameters were provided', - }), - }; - } - - if (!event.queryStringParameters.coordinatorURL) { - return { - statusCode: 400, - body: JSON.stringify({ - message: 'No Coordinator URL was provided', - }), - }; - } - - const coordinatorURL = event.queryStringParameters.coordinatorURL; - - const attestorGroupPublicKey = await getAttestorExtendedGroupPublicKey(coordinatorURL); - - return { - statusCode: 200, - body: attestorGroupPublicKey, - }; - } catch (error: any) { - return { - statusCode: 500, - body: JSON.stringify({ - message: `Failed to get Attestor Group Public Key: ${error.message}`, - }), - }; - } -}; - -export { handler }; diff --git a/src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx b/src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx index 7f19585b..3b03c84f 100644 --- a/src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx +++ b/src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx @@ -18,7 +18,7 @@ import { modalActions } from '@store/slices/modal/modal.actions'; import { NetworkType } from '@shared/constants/network.constants'; interface DepositTransactionScreenProps { - handleSignFundingTransaction: (vaultUUID: string, depositAmount: number) => Promise; + handleSignDepositTransaction: (vaultUUID: string, depositAmount: number) => Promise; isBitcoinWalletLoading: [boolean, string]; userEthereumAddressRiskLevel: string; fetchUserEthereumAddressRiskLevel: () => Promise; @@ -26,7 +26,7 @@ interface DepositTransactionScreenProps { } export function DepositTransactionScreen({ - handleSignFundingTransaction, + handleSignDepositTransaction, isBitcoinWalletLoading, userEthereumAddressRiskLevel, fetchUserEthereumAddressRiskLevel, @@ -62,7 +62,7 @@ export function DepositTransactionScreen({ const currentRisk = await fetchUserEthereumAddressRiskLevel(); if (currentRisk === 'High') throw new Error('Risk Level is too high'); } - await handleSignFundingTransaction(currentVault.uuid, depositAmount); + await handleSignDepositTransaction(currentVault.uuid, depositAmount); } catch (error: any) { setIsSubmitting(false); toast({ diff --git a/src/app/components/mint-unmint/components/mint/mint.tsx b/src/app/components/mint-unmint/components/mint/mint.tsx index 1f057c38..78cd4f12 100644 --- a/src/app/components/mint-unmint/components/mint/mint.tsx +++ b/src/app/components/mint-unmint/components/mint/mint.tsx @@ -14,7 +14,7 @@ import { Walkthrough } from '../walkthrough/walkthrough'; import { MintLayout } from './components/mint.layout'; export function Mint(): React.JSX.Element { - const { handleSignFundingTransaction, isLoading: isBitcoinWalletLoading } = usePSBT(); + const { handleSignDepositTransaction, isLoading: isBitcoinWalletLoading } = usePSBT(); const { networkType } = useContext(NetworkConfigurationContext); const { mintStep } = useSelector((state: RootState) => state.mintunmint); @@ -28,7 +28,7 @@ export function Mint(): React.JSX.Element { {[0].includes(mintStep.step) && } {[1, 2].includes(mintStep.step) && ( { - try { - const netlifyFunctionEndpoint = `/.netlify/functions/fetch-attestor-group-public-key?coordinatorURL=${appConfiguration.coordinatorURL}`; - - const response = await fetch(netlifyFunctionEndpoint); - - if (!response.ok) { - const errorMessage = await response.text(); - throw new Error(`HTTP Error: ${errorMessage}`); - } - - return await response.text(); - } catch (error: any) { - throw new Error(`Failed to get Attestor Group Public Key: ${error.message}`); - } -} diff --git a/src/app/hooks/use-ledger.ts b/src/app/hooks/use-ledger.ts index 17413cda..f3b5aa0b 100644 --- a/src/app/hooks/use-ledger.ts +++ b/src/app/hooks/use-ledger.ts @@ -6,7 +6,6 @@ import { LedgerError } from '@models/error-types'; import { LEDGER_APPS_MAP } from '@models/ledger'; import { SupportedPaymentType } from '@models/supported-payment-types'; import { BitcoinWalletAction, BitcoinWalletType } from '@models/wallet'; -import { bytesToHex } from '@noble/hashes/utils'; import { BitcoinWalletContext, BitcoinWalletContextState, diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 2eaeb3a5..f4509972 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -1,6 +1,13 @@ import { useContext } from 'react'; import { BitcoinError } from '@models/error-types'; +import { + HandlerType, + PSBTSubmissionParams, + TransactionHandlers, + TransactionParams, + TransactionType, +} from '@models/transaction.models'; import { BitcoinWalletType } from '@models/wallet'; import { bytesToHex } from '@noble/hashes/utils'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; @@ -9,7 +16,7 @@ import { submitFundingPSBT, submitWithdrawDepositPSBT, } from 'dlc-btc-lib/attestor-request-functions'; -import { AttestorChainID, RawVault, Transaction, VaultState } from 'dlc-btc-lib/models'; +import { VaultState } from 'dlc-btc-lib/models'; import { equals } from 'ramda'; import { useAttestorChainID } from './use-attestor-chain-id'; @@ -21,39 +28,6 @@ import { useLedger } from './use-ledger'; import { useUnisatFordefi } from './use-unisat-fordefi'; import { useUserAddress } from './use-user-address'; -interface TransactionParams { - vault: RawVault; - amount: number; - extendedAttestorGroupPublicKey: string; - feeRecipient: string; - bitcoinFeeRateMultiplier: number; -} - -type TransactionHandler = ( - vault: RawVault, - amount: number, - extendedAttestorGroupPublicKey: string, - feeRecipient: string, - bitcoinFeeRateMultiplier: number -) => Promise; - -interface PSBTSubmissionParams { - transaction: Transaction; - vault: RawVault; - userAddress: string; - dlcHandler: LeatherDLCHandler | LedgerDLCHandler | UnisatFordefiDLCHandler; - coordinatorURL: string; - attestorChainID: AttestorChainID; -} - -interface TransactionHandlers { - handleFundingTransaction: Record; - handleWithdrawTransaction: Record; - handleDepositTransaction: Record; -} - -type HandlerType = keyof TransactionHandlers; - interface RequiredDependencies { dlcHandler: LeatherDLCHandler | LedgerDLCHandler | UnisatFordefiDLCHandler; bitcoinWalletType: BitcoinWalletType; @@ -61,7 +35,7 @@ interface RequiredDependencies { } interface UsePSBTReturnType { - handleSignFundingTransaction: (vaultUUID: string, depositAmount: number) => Promise; + handleSignDepositTransaction: (vaultUUID: string, depositAmount: number) => Promise; handleSignWithdrawTransaction: (vaultUUID: string, withdrawAmount: number) => Promise; isLoading: [boolean, string] | undefined; } @@ -116,18 +90,21 @@ export function usePSBT(): UsePSBTReturnType { }: PSBTSubmissionParams): Promise => { const transactionPSBT = bytesToHex(transaction.toPSBT()); - return equals(vault.status, VaultState.READY) - ? submitFundingPSBT([coordinatorURL], { + switch (equals(vault.status, VaultState.READY)) { + case true: + return submitFundingPSBT([coordinatorURL], { vaultUUID: vault.uuid, fundingPSBT: transactionPSBT, userEthereumAddress: userAddress, userBitcoinTaprootPublicKey: dlcHandler.getUserTaprootPublicKey(), attestorChainID, - }) - : submitWithdrawDepositPSBT([coordinatorURL], { + }); + default: + return submitWithdrawDepositPSBT([coordinatorURL], { vaultUUID: vault.uuid, withdrawDepositPSBT: transactionPSBT, }); + } }; const getPSBTParameters = async ( @@ -157,17 +134,19 @@ export function usePSBT(): UsePSBTReturnType { return { dlcHandler, bitcoinWalletType, userAddress }; }; + const throwTransactionError = (type: TransactionType, error: any): never => { + if (error instanceof Error) { + throw new BitcoinError(`Error signing ${type} Transaction: ${error.message}`); + } + throw new BitcoinError(`Unknown error signing ${type} Transaction`); + }; + const handlerTypeMap = { withdraw: (): HandlerType => 'handleWithdrawTransaction', - funding: (valueLocked: number): HandlerType => + deposit: (valueLocked: number): HandlerType => equals(valueLocked, 0) ? 'handleFundingTransaction' : 'handleDepositTransaction', } as const; - const getHandlerType = (type: TransactionType, valueLocked: number): HandlerType => - handlerTypeMap[type](valueLocked); - - type TransactionType = 'withdraw' | 'funding'; - const createTransactionHandler = (type: TransactionType) => async (vaultUUID: string, value: number): Promise => { @@ -182,7 +161,7 @@ export function usePSBT(): UsePSBTReturnType { bitcoinFeeRateMultiplier, } = await getPSBTParameters(vaultUUID, value); - const handlerType = getHandlerType(type, vault.valueLocked.toNumber()); + const handlerType = handlerTypeMap[type](vault.valueLocked.toNumber()); const transactionHandler = transactionHandlers[handlerType][bitcoinWalletType]; @@ -205,16 +184,10 @@ export function usePSBT(): UsePSBTReturnType { resetBitcoinWalletContext(); } catch (error) { - if (error instanceof Error) { - throw new BitcoinError(`Error signing ${type} Transaction: ${error.message}`); - } - throw new BitcoinError(`Unknown error signing ${type} Transaction`); + throwTransactionError(type, error); } }; - const handleSignFundingTransaction = createTransactionHandler('funding'); - const handleSignWithdrawTransaction = createTransactionHandler('withdraw'); - const loadingStates = { [BitcoinWalletType.Ledger]: ledgerTransactionHandler.isLoading, [BitcoinWalletType.Leather]: leatherTransactionHandler.isLoading, @@ -223,8 +196,8 @@ export function usePSBT(): UsePSBTReturnType { }; return { - handleSignFundingTransaction, - handleSignWithdrawTransaction, + handleSignDepositTransaction: createTransactionHandler('deposit'), + handleSignWithdrawTransaction: createTransactionHandler('withdraw'), isLoading: loadingStates[bitcoinWalletType!], }; } diff --git a/src/app/hooks/use-unisat-fordefi.ts b/src/app/hooks/use-unisat-fordefi.ts index a6a4a0ac..cda1a24e 100644 --- a/src/app/hooks/use-unisat-fordefi.ts +++ b/src/app/hooks/use-unisat-fordefi.ts @@ -4,7 +4,6 @@ import { ALL_SUPPORTED_BITCOIN_NETWORK_PREFIX } from '@models/configuration'; import { UnisatFordefiError } from '@models/error-types'; import { BitcoinTaprootAccount } from '@models/software-wallet.models'; import { BitcoinWalletAction, BitcoinWalletType } from '@models/wallet'; -import { bytesToHex } from '@noble/hashes/utils'; import { BitcoinWalletContext, BitcoinWalletContextState, diff --git a/src/shared/models/transaction.models.ts b/src/shared/models/transaction.models.ts new file mode 100644 index 00000000..0302a84c --- /dev/null +++ b/src/shared/models/transaction.models.ts @@ -0,0 +1,39 @@ +import { LeatherDLCHandler, LedgerDLCHandler, UnisatFordefiDLCHandler } from 'dlc-btc-lib'; +import { AttestorChainID, RawVault, Transaction } from 'dlc-btc-lib/models'; + +import { BitcoinWalletType } from './wallet'; + +export interface TransactionParams { + vault: RawVault; + amount: number; + extendedAttestorGroupPublicKey: string; + feeRecipient: string; + bitcoinFeeRateMultiplier: number; +} + +type TransactionHandler = ( + vault: RawVault, + amount: number, + extendedAttestorGroupPublicKey: string, + feeRecipient: string, + bitcoinFeeRateMultiplier: number +) => Promise; + +export interface PSBTSubmissionParams { + transaction: Transaction; + vault: RawVault; + userAddress: string; + dlcHandler: LeatherDLCHandler | LedgerDLCHandler | UnisatFordefiDLCHandler; + coordinatorURL: string; + attestorChainID: AttestorChainID; +} + +export interface TransactionHandlers { + handleFundingTransaction: Record; + handleWithdrawTransaction: Record; + handleDepositTransaction: Record; +} + +export type HandlerType = keyof TransactionHandlers; + +export type TransactionType = 'withdraw' | 'deposit';