diff --git a/config.testnet.json b/config.testnet.json index 87c431c6..8eac2541 100644 --- a/config.testnet.json +++ b/config.testnet.json @@ -1,6 +1,6 @@ { "appEnvironment": "testnet", - "coordinatorURL": "http://localhost:8811", + "coordinatorURL": "https://testnet-ripple.dlc.link/attestor-1", "enabledEthereumNetworkIDs": ["421614", "84532", "11155111"], "enabledRippleNetworkIDs": ["1"], "bitcoinNetwork": "testnet", @@ -10,7 +10,7 @@ "bitcoinBlockchainExplorerURL": "https://mempool.space/testnet", "bitcoinBlockchainFeeEstimateURL": "https://mempool.space/testnet/api/v1/fees/recommended", "rippleIssuerAddress": "ra9epzthPkNXykgfadCwu8D7mtajj8DVCP", - "xrplWebsocket": "wss://s.altnet.rippletest.net:51233", + "xrplWebsocket": "wss://testnet.xrpl-labs.com/", "ledgerApp": "Bitcoin Test", "merchants": [ { diff --git a/package.json b/package.json index 57ee10a7..caa9c316 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@fontsource/inter": "^5.0.18", "@fontsource/onest": "^5.0.3", "@fontsource/poppins": "^5.0.8", + "@gemwallet/api": "^3.8.0", "@ledgerhq/hw-app-xrp": "^6.29.4", "@ledgerhq/hw-transport-webusb": "^6.28.6", "@netlify/functions": "^2.8.1", @@ -45,7 +46,7 @@ "concurrently": "^8.2.2", "d3": "^7.9.0", "decimal.js": "^10.4.3", - "dlc-btc-lib": "file:../dlc-btc-lib", + "dlc-btc-lib": "^2.4.0", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", diff --git a/public/images/logos/gem-wallet-logo.svg b/public/images/logos/gem-wallet-logo.svg new file mode 100644 index 00000000..592bf617 --- /dev/null +++ b/public/images/logos/gem-wallet-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/logos/xpr-logo.svg b/public/images/logos/xrp-logo.svg similarity index 100% rename from public/images/logos/xpr-logo.svg rename to public/images/logos/xrp-logo.svg diff --git a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx index b77930db..9712f304 100644 --- a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx +++ b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx @@ -5,17 +5,14 @@ import { VStack, useToast } from '@chakra-ui/react'; import { VaultTransactionForm } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form'; import { Vault } from '@components/vault/vault'; import { useEthersSigner } from '@functions/configuration.functions'; -import { useXRPLLedger } from '@hooks/use-xrpl-ledger'; +import { useXRPWallet } from '@hooks/use-xrp-wallet'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; -import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; -// import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { withdraw } from 'dlc-btc-lib/ethereum-functions'; -import { getRippleClient, getRippleVault } from 'dlc-btc-lib/ripple-functions'; import { shiftValue } from 'dlc-btc-lib/utilities'; interface BurnTokenTransactionFormProps { @@ -36,8 +33,7 @@ export function BurnTokenTransactionForm({ const { networkType } = useContext(NetworkConfigurationContext); const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); - const { xrpHandler } = useContext(XRPWalletContext); - const { handleCreateCheck, connectLedgerWallet, isLoading } = useXRPLLedger(); + const { handleCreateCheck, isLoading } = useXRPWallet(); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); @@ -55,16 +51,7 @@ export function BurnTokenTransactionForm({ if (!currentVault) return; setIsSubmitting(true); if (networkType === 'xrpl') { - const rippleClient = getRippleClient(appConfiguration.xrplWebsocket); - const vault = await getRippleVault( - rippleClient, - appConfiguration.rippleIssuerAddress, - currentVault.uuid - ); - await connectLedgerWallet("44'/144'/0'/0/0"); - - if (!xrpHandler) throw new Error('No XRP Handler'); - await handleCreateCheck(xrpHandler, vault, withdrawAmount); + await handleCreateCheck(currentVault.uuid, withdrawAmount); } else if (networkType === 'evm') { const currentRisk = await fetchUserEthereumAddressRiskLevel(); if (currentRisk === 'High') throw new Error('Risk Level is too high'); diff --git a/src/app/components/mint-unmint/components/mint/mint.tsx b/src/app/components/mint-unmint/components/mint/mint.tsx index be8944d3..3d24aeb6 100644 --- a/src/app/components/mint-unmint/components/mint/mint.tsx +++ b/src/app/components/mint-unmint/components/mint/mint.tsx @@ -1,8 +1,10 @@ +import { useContext } from 'react'; import { useSelector } from 'react-redux'; import { HStack } from '@chakra-ui/react'; import { usePSBT } from '@hooks/use-psbt'; import { useRisk } from '@hooks/use-risk'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { RootState } from '@store/index'; import { DepositTransactionScreen } from '../deposit-transaction-screen/deposit-transaction-screen'; @@ -13,6 +15,7 @@ import { MintLayout } from './components/mint.layout'; export function Mint(): React.JSX.Element { const { handleSignFundingTransaction, isLoading: isBitcoinWalletLoading } = usePSBT(); + const { networkType } = useContext(NetworkConfigurationContext); const { mintStep } = useSelector((state: RootState) => state.mintunmint); const { risk, fetchUserAddressRisk, isLoading } = useRisk(); @@ -21,7 +24,7 @@ export function Mint(): React.JSX.Element { - + {[0].includes(mintStep[0]) && } {[1, 2].includes(mintStep[0]) && ( state.mintunmint); const { risk, fetchUserAddressRisk, isLoading } = useRisk(); @@ -21,7 +24,7 @@ export function Unmint(): React.JSX.Element { - + {[0].includes(unmintStep[0]) && ( - - Initiate a Vault on the blockchain and confirm it in your{' '} - - Ethereum Wallet - - . - + {networkType === 'evm' ? ( + + Initiate a Vault on the blockchain and confirm it in your{' '} + + Ethereum Wallet + + . + + ) : ( + + Initiate a Setup Vault request. If the TrustLine is not yet established, sign the + Set TrustLine Transaction in your wallet. Then, wait for the Attestors to confirm + your request and set up the Vault on the blockchain. + + )} ); @@ -49,16 +62,8 @@ export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX. /> Enter the Bitcoin amount you wish to deposit into the vault, then verify the - transaction through your{' '} - - Bitcoin Wallet{' '} - - which will lock your Bitcoin on-chain. You will receive equivalent amount of dlcBTC. + transaction through your Bitcoin Wallet which will lock your Bitcoin on-chain. You + will receive equivalent amount of dlcBTC. ); @@ -68,32 +73,33 @@ export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX. - Wait for Bitcoin to get locked on chain{' '} - - (~1 hour) - - . After 6 confirmations, dlcBTC tokens will appear in your Ethereum Wallet. - - - To ensure your dlcBTC tokens - are visible - simply add them - to your Ethereum Wallet. + Wait for Bitcoin to get locked on chain (~1 hour). After 6 confirmations, dlcBTC + tokens will appear in your Wallet. - + {networkType === 'evm' && ( + <> + + To ensure your dlcBTC tokens + are visible + simply add them + to your Ethereum Wallet. + + + + + )} ); default: @@ -102,7 +108,7 @@ export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX. ); @@ -115,12 +121,19 @@ export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX. - - Select the dlcBTC vault you would like to withdraw from. Burn the desired amount of - dlcBTC to receive the equivalent amount of BTC. - + {networkType === 'evm' ? ( + + Select the dlcBTC vault you would like to withdraw from. Burn the desired amount + of dlcBTC to receive the equivalent amount of BTC. + + ) : ( + + Select the dlcBTC vault you would like to withdraw from. Sign a check with the + desired amount of dlcBTC to receive the equivalent amount of BTC. + + )} ); case 1: @@ -161,7 +174,7 @@ export function Walkthrough({ flow, currentStep }: WalkthroughProps): React.JSX. ); diff --git a/src/app/components/modals/select-wallet-modal/components/select-ripple-wallet-menu.tsx b/src/app/components/modals/select-wallet-modal/components/select-ripple-wallet-menu.tsx index 30d30acc..f9c35fa1 100644 --- a/src/app/components/modals/select-wallet-modal/components/select-ripple-wallet-menu.tsx +++ b/src/app/components/modals/select-wallet-modal/components/select-ripple-wallet-menu.tsx @@ -1,9 +1,9 @@ import { Box, Button, HStack, Image, Text } from '@chakra-ui/react'; -import { XRPWallet } from '@models/wallet'; +import { XRPWallet, XRPWalletType } from '@models/wallet'; interface SelectRippleWalletMenuProps { rippleWallet: XRPWallet; - handleConnectWallet: (id: string) => void; + handleConnectWallet: (xrpWalletType: XRPWalletType) => void; } export function SelectRippleWalletMenu({ diff --git a/src/app/components/modals/select-wallet-modal/select-wallet-modal.tsx b/src/app/components/modals/select-wallet-modal/select-wallet-modal.tsx index 0048f65e..022c04cf 100644 --- a/src/app/components/modals/select-wallet-modal/select-wallet-modal.tsx +++ b/src/app/components/modals/select-wallet-modal/select-wallet-modal.tsx @@ -1,14 +1,15 @@ import { useContext, useEffect, useState } from 'react'; import { CheckIcon } from '@chakra-ui/icons'; -import { HStack, ScaleFade, Tab, TabList, Tabs, Text, VStack } from '@chakra-ui/react'; +import { HStack, ScaleFade, Tab, TabList, Tabs, Text, VStack, useToast } from '@chakra-ui/react'; import { ModalComponentProps } from '@components/modals/components/modal-container'; import { ModalLayout } from '@components/modals/components/modal.layout'; import { SelectNetworkButton } from '@components/select-network-button/select-network-button'; import { TransactionScreenWalletInformation } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information'; +import { useGemWallet } from '@hooks/use-xrpl-gem'; import { useXRPLLedger } from '@hooks/use-xrpl-ledger'; import { RippleNetworkID } from '@models/ripple.models'; -import { xrpWallets } from '@models/wallet'; +import { XRPWalletType, xrpWallets } from '@models/wallet'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { XRPWalletContext, XRPWalletContextState } from '@providers/xrp-wallet-context-provider'; @@ -19,7 +20,18 @@ import { Connector, useConfig, useConnect } from 'wagmi'; import { SelectEthereumWalletMenu } from './components/select-ethereum-wallet-menu'; import { SelectRippleWalletMenu } from './components/select-ripple-wallet-menu'; +function formatErrorMessage(error: string): string { + if (error.includes('0x6985')) { + return 'Action Rejected by User'; + } else if (error.includes('0x5515')) { + return 'Locked Device'; + } else { + return error; + } +} + export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { + const toast = useToast(); const { connect, isPending, isSuccess, connectors } = useConnect(); const { chains: ethereumNetworks } = useConfig(); @@ -32,6 +44,7 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): setXRPHandler, } = useContext(XRPWalletContext); const { connectLedgerWallet, isLoading } = useXRPLLedger(); + const { connectGemWallet } = useGemWallet(); const { enabledRippleNetworks } = useContext(RippleNetworkConfigurationContext); const ethereumNetworkIDs = ethereumNetworks.map( @@ -76,17 +89,51 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): } } - async function handleConnectRippleWallet() { + async function handleConnectGemWallet() { setNetworkType('xrpl'); - setXRPWalletType(xrpWallets[0].id); + setXRPWalletType(XRPWalletType.Gem); - const { xrpHandler, userAddress } = await connectLedgerWallet("44'/144'/0'/0/1"); + const { xrpHandler, userAddress } = await connectGemWallet(); setXRPHandler(xrpHandler); setUserAddress(userAddress); setXRPWalletContextState(XRPWalletContextState.READY); } + async function handleConnectLedgerWallet() { + try { + setNetworkType('xrpl'); + setXRPWalletType(XRPWalletType.Ledger); + + const { xrpHandler, userAddress } = await connectLedgerWallet("44'/144'/0'/0/0"); + + setXRPHandler(xrpHandler); + setUserAddress(userAddress); + setXRPWalletContextState(XRPWalletContextState.READY); + } catch (error: any) { + toast({ + title: 'Failed to connect Ledger Wallet', + description: error instanceof Error ? formatErrorMessage(error.message) : '', + status: 'error', + duration: 9000, + isClosable: true, + }); + } + } + + async function handleConnectRippleWallet(xrpWalletType: XRPWalletType) { + switch (xrpWalletType) { + case XRPWalletType.Gem: + await handleConnectGemWallet(); + break; + case XRPWalletType.Ledger: + await handleConnectLedgerWallet(); + break; + default: + break; + } + } + const handleChangeNetwork = (networkID: EthereumNetworkID | RippleNetworkID) => { setSelectedNetworkID(networkID); }; diff --git a/src/app/functions/attestor-request.functions.ts b/src/app/functions/attestor-request.functions.ts index 3ca4e673..bca47d69 100644 --- a/src/app/functions/attestor-request.functions.ts +++ b/src/app/functions/attestor-request.functions.ts @@ -26,6 +26,6 @@ export async function submitSetupXRPLVaultRequest(userAddress: string): Promise< throw new Error(`HTTP Error: ${errorMessage}`); } } catch (error: any) { - throw new Error(`Failed to get Attestor Group Public Key: ${error.message}`); + throw new Error(`Failed to submit Setup XRPL Vault Request: ${error.message}`); } } diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index d4431934..7068b852 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -7,6 +7,7 @@ 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 { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; import { @@ -15,7 +16,7 @@ import { } from 'dlc-btc-lib/attestor-request-functions'; import { getAttestorGroupPublicKey, getRawVault } from 'dlc-btc-lib/ethereum-functions'; import { AttestorChainID, RawVault, Transaction, VaultState } from 'dlc-btc-lib/models'; -import { getRippleClient, getRippleVault } from 'dlc-btc-lib/ripple-functions'; +import { getRippleVault } from 'dlc-btc-lib/ripple-functions'; import { useAccount } from 'wagmi'; import { useLeather } from './use-leather'; @@ -35,6 +36,7 @@ export function usePSBT(): UsePSBTReturnType { } = useContext(EthereumNetworkConfigurationContext); const { address: ethereumUserAddress } = useAccount(); const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); + const { rippleClient } = useContext(RippleNetworkConfigurationContext); const { bitcoinWalletType, dlcHandler, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); @@ -74,14 +76,12 @@ export function usePSBT(): UsePSBTReturnType { return { userAddress: ethereumUserAddress, vault, attestorGroupPublicKey }; } else if (networkType === 'xrpl') { if (!rippleUserAddress) throw new Error('User Address is not setup'); - const rippleClient = getRippleClient(appConfiguration.xrplWebsocket); const vault = await getRippleVault( rippleClient, appConfiguration.rippleIssuerAddress, vaultUUID ); const attestorGroupPublicKey = await getAttestorExtendedGroupPublicKey(); - console.log('attestorGroupPublicKey', attestorGroupPublicKey); return { userAddress: rippleUserAddress, vault, diff --git a/src/app/hooks/use-xrp-wallet.ts b/src/app/hooks/use-xrp-wallet.ts new file mode 100644 index 00000000..7686212a --- /dev/null +++ b/src/app/hooks/use-xrp-wallet.ts @@ -0,0 +1,89 @@ +import { useContext } from 'react'; + +import { BitcoinError } from '@models/error-types'; +import { XRPWalletType } from '@models/wallet'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; +import { GemXRPHandler } from 'dlc-btc-lib'; +import { getRippleVault } from 'dlc-btc-lib/ripple-functions'; + +import { useGemWallet } from './use-xrpl-gem'; +import { useXRPLLedger } from './use-xrpl-ledger'; + +interface UseXRPWalletReturnType { + handleCreateCheck: (vaultUUID: string, withdrawAmount: number) => Promise; + handleSetTrustLine: () => Promise; + isLoading: [boolean, string]; +} + +export function useXRPWallet(): UseXRPWalletReturnType { + const { rippleClient } = useContext(RippleNetworkConfigurationContext); + const { xrpWalletType, xrpHandler } = useContext(XRPWalletContext); + + const { + handleCreateCheck: handleCreateCheckWithLedger, + handleSetTrustLine: handleSetTrustLineWithLedger, + isLoading: isLedgerLoading, + } = useXRPLLedger(); + + const { + handleCreateCheck: handleCreateCheckWithGem, + handleSetTrustLine: handleSetTrustLineWithGem, + isLoading: isGemLoading, + } = useGemWallet(); + + async function handleCreateCheck(vaultUUID: string, withdrawAmount: number): Promise { + try { + if (!xrpHandler) throw new Error('XRP Handler is not setup'); + + const vault = await getRippleVault( + rippleClient, + appConfiguration.rippleIssuerAddress, + vaultUUID + ); + + switch (xrpWalletType) { + case 'Ledger': + await handleCreateCheckWithLedger(vault, withdrawAmount); + break; + case 'Gem': + await handleCreateCheckWithGem(xrpHandler as GemXRPHandler, vault, withdrawAmount); + break; + default: + throw new Error('Invalid XRP Wallet Type'); + } + } catch (error) { + throw new BitcoinError(`Error signing Funding Transaction: ${error}`); + } + } + + async function handleSetTrustLine(): Promise { + try { + if (!xrpHandler) throw new Error('XRP Handler is not setup'); + + switch (xrpWalletType) { + case 'Ledger': + await handleSetTrustLineWithLedger(); + break; + case 'Gem': + await handleSetTrustLineWithGem(xrpHandler as GemXRPHandler); + break; + default: + throw new Error('Invalid XRP Wallet Type'); + } + } catch (error) { + throw new BitcoinError(`Error signing Withdraw Transaction: ${error}`); + } + } + + const loadingStates = { + [XRPWalletType.Ledger]: isLedgerLoading, + [XRPWalletType.Gem]: isGemLoading, + }; + + return { + handleCreateCheck, + handleSetTrustLine, + isLoading: xrpWalletType ? loadingStates[xrpWalletType] : [false, ''], + }; +} diff --git a/src/app/hooks/use-xrpl-gem.ts b/src/app/hooks/use-xrpl-gem.ts new file mode 100644 index 00000000..a5a7412f --- /dev/null +++ b/src/app/hooks/use-xrpl-gem.ts @@ -0,0 +1,91 @@ +import { useContext, useState } from 'react'; + +import { getAddress, isInstalled } from '@gemwallet/api'; +import { GemError } from '@models/error-types'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { GemXRPHandler } from 'dlc-btc-lib'; +import { RawVault } from 'dlc-btc-lib/models'; +import { shiftValue } from 'dlc-btc-lib/utilities'; + +interface useXRPLGemReturnType { + isLoading: [boolean, string]; + connectGemWallet: () => Promise<{ xrpHandler: GemXRPHandler; userAddress: string }>; + handleCreateCheck: ( + xrpHandler: GemXRPHandler, + vault: RawVault, + withdrawAmount: number + ) => Promise; + handleSetTrustLine: (xrpHandler: GemXRPHandler) => Promise; +} + +export function useGemWallet(): useXRPLGemReturnType { + const { rippleClient } = useContext(RippleNetworkConfigurationContext); + + const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); + + async function connectGemWallet() { + try { + setIsLoading([true, 'Connecting To Gem Wallet']); + + const isGemWalletInstalled = await isInstalled(); + if (!isGemWalletInstalled.result.isInstalled) { + throw new GemError('Gem Wallet is not Installed'); + } + + const getAddressResponse = await getAddress(); + + if (!getAddressResponse.result) { + throw new GemError('No User Address Found'); + } + + const xrpHandler = new GemXRPHandler( + rippleClient, + appConfiguration.rippleIssuerAddress, + getAddressResponse.result.address + ); + + return { xrpHandler, userAddress: getAddressResponse.result.address }; + } catch (error) { + throw new GemError(`Error connecting to Gem Wallet: ${error}`); + } finally { + setIsLoading([false, '']); + } + } + + async function handleCreateCheck( + xrpHandler: GemXRPHandler, + vault: RawVault, + withdrawAmount: number + ) { + try { + setIsLoading([true, 'Sign Check in your Gem Wallet']); + + const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); + + return await xrpHandler.createCheck(formattedWithdrawAmount.toString(), vault.uuid.slice(2)); + } catch (error) { + throw new GemError(`Error creating Check: ${error}`); + } finally { + setIsLoading([false, '']); + } + } + + async function handleSetTrustLine(xrpHandler: GemXRPHandler) { + try { + setIsLoading([true, 'Set Trust Line in your Gem Wallet']); + + return await xrpHandler.setTrustLine(); + } catch (error) { + throw new GemError(`Error setting Trust Line: ${error}`); + } finally { + setIsLoading([false, '']); + } + } + + return { + isLoading, + connectGemWallet, + handleCreateCheck, + handleSetTrustLine, + }; +} diff --git a/src/app/hooks/use-xrpl-ledger.ts b/src/app/hooks/use-xrpl-ledger.ts index 97e77850..03a6d7c2 100644 --- a/src/app/hooks/use-xrpl-ledger.ts +++ b/src/app/hooks/use-xrpl-ledger.ts @@ -14,12 +14,8 @@ interface useXRPLLedgerReturnType { connectLedgerWallet: ( derivationPath: string ) => Promise<{ xrpHandler: LedgerXRPHandler; userAddress: string }>; - handleCreateCheck: ( - xrpHandler: LedgerXRPHandler, - vault: RawVault, - withdrawAmount: number - ) => Promise; - handleSetTrustLine: (xrpHandler: LedgerXRPHandler) => Promise; + handleCreateCheck: (vault: RawVault, withdrawAmount: number) => Promise; + handleSetTrustLine: () => Promise; } type TransportInstance = Awaited>; @@ -99,7 +95,9 @@ export function useXRPLLedger(): useXRPLLedgerReturnType { xrplWallet, derivationPath, rippleClient, - appConfiguration.rippleIssuerAddress + appConfiguration.rippleIssuerAddress, + xrplAddress.address, + xrplAddress.publicKey ); return { xrpHandler, userAddress: xrplAddress.address }; @@ -110,34 +108,34 @@ export function useXRPLLedger(): useXRPLLedgerReturnType { } } - async function handleCreateCheck( - xrpHandler: LedgerXRPHandler, - vault: RawVault, - withdrawAmount: number - ) { + async function handleCreateCheck(vault: RawVault, withdrawAmount: number) { try { + const { xrpHandler: currentXRPHandler } = await connectLedgerWallet("44'/144'/0'/0/0"); + setIsLoading([true, 'Sign Check on your Ledger Device']); const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - return await xrpHandler.createCheck(formattedWithdrawAmount.toString(), vault.uuid.slice(2)); + return await currentXRPHandler.createCheck( + formattedWithdrawAmount.toString(), + vault.uuid.slice(2) + ); } catch (error) { - throw new LedgerError(`Error creating check: ${error}`); + throw new LedgerError(`Error creating Check: ${error}`); } finally { setIsLoading([false, '']); } } - async function handleSetTrustLine(xrpHandler: LedgerXRPHandler) { + async function handleSetTrustLine() { try { + const { xrpHandler: currentXRPHandler } = await connectLedgerWallet("44'/144'/0'/0/0"); + setIsLoading([true, 'Set Trust Line on your Ledger Device']); - console.log('Setting trust line'); - const trustLine = await xrpHandler.setTrustLine(); - console.log('Trust line set'); - return trustLine; + return await currentXRPHandler.setTrustLine(); } catch (error) { - throw new LedgerError(`Error setting trust line: ${error}`); + throw new LedgerError(`Error setting Trust Line: ${error}`); } finally { setIsLoading([false, '']); } diff --git a/src/app/hooks/use-xrpl-vaults.ts b/src/app/hooks/use-xrpl-vaults.ts index 4d535de9..b8c56361 100644 --- a/src/app/hooks/use-xrpl-vaults.ts +++ b/src/app/hooks/use-xrpl-vaults.ts @@ -4,17 +4,14 @@ import { useDispatch } from 'react-redux'; import { formatVault } from '@functions/vault.functions'; import { Vault } from '@models/vault'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { XRPWalletContext } from '@providers/xrp-wallet-context-provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import Decimal from 'decimal.js'; import { VaultState } from 'dlc-btc-lib/models'; -import { - connectRippleClient, - getAllRippleVaults, - getRippleClient, -} from 'dlc-btc-lib/ripple-functions'; +import { connectRippleClient, getAllRippleVaults } from 'dlc-btc-lib/ripple-functions'; const INITIAL_VAULT_UUID = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -61,28 +58,31 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { const [isLoading, setIsLoading] = useState(true); const { networkType } = useContext(NetworkConfigurationContext); - const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); + const { userAddress: xrpUserAddress } = useContext(XRPWalletContext); + const { rippleClient } = useContext(RippleNetworkConfigurationContext); const issuerAddress = appConfiguration.rippleIssuerAddress; - const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); const { data: xrplVaults } = useQuery({ - queryKey: ['xrpl-vaults'], + queryKey: ['xrpl-vaults', xrpUserAddress], initialData: [], queryFn: fetchXRPLVaults, - refetchInterval: 10000, - enabled: networkType === 'xrpl' && !!rippleUserAddress, + refetchInterval: 20000, + enabled: networkType === 'xrpl' && !!xrpUserAddress, }); async function fetchXRPLVaults(): Promise { setIsLoading(true); - const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); + const previousVaults: Vault[] | undefined = queryClient.getQueryData([ + 'xrpl-vaults', + xrpUserAddress, + ]); try { - await connectRippleClient(xrplClient); + await connectRippleClient(rippleClient); - const xrplRawVaults = await getAllRippleVaults(xrplClient, issuerAddress, rippleUserAddress); + const xrplRawVaults = await getAllRippleVaults(rippleClient, issuerAddress, xrpUserAddress); const xrplVaults = xrplRawVaults.map(formatVault); if ( @@ -105,7 +105,6 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { const previousVault = previousVaults?.find( previousVault => previousVault.uuid === vault.uuid ); - handleVaultStateChange(previousVault, vault); }); diff --git a/src/app/providers/balance-context-provider.tsx b/src/app/providers/balance-context-provider.tsx index b270c29e..fb660264 100644 --- a/src/app/providers/balance-context-provider.tsx +++ b/src/app/providers/balance-context-provider.tsx @@ -48,7 +48,6 @@ export function BalanceContextProvider({ children }: HasChildren): React.JSX.Ele const fetchXRPLBalances = async () => { const dlcBTCBalance = await xrpHandler?.getDLCBTCBalance(); const lockedBTCBalance = await xrpHandler?.getLockedBTCBalance(); - console.log('lockedBTCBalance', lockedBTCBalance); return { dlcBTCBalance, lockedBTCBalance }; }; diff --git a/src/app/providers/network-configuration.provider.tsx b/src/app/providers/network-configuration.provider.tsx index d192c9ac..d65381c8 100644 --- a/src/app/providers/network-configuration.provider.tsx +++ b/src/app/providers/network-configuration.provider.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useEffect, useState } from 'react'; +import React, { createContext, useState } from 'react'; import { HasChildren } from '@models/has-children'; @@ -14,10 +14,6 @@ export const NetworkConfigurationContext = createContext('evm'); - useEffect(() => { - console.log('networkType in provioder', networkType); - }, [networkType]); - return ( >; xrpWalletContextState: XRPWalletContextState; setXRPWalletContextState: React.Dispatch>; - xrpHandler: LedgerXRPHandler | undefined; - setXRPHandler: React.Dispatch>; + xrpHandler: LedgerXRPHandler | GemXRPHandler | undefined; + setXRPHandler: React.Dispatch>; setUserAddress: React.Dispatch>; userAddress: string | undefined; resetXRPWalletContext: () => void; @@ -41,7 +41,7 @@ export function XRPWalletContextProvider({ children }: HasChildren): React.JSX.E const [xrpWalletType, setXRPWalletType] = useState( XRPWalletType.Ledger ); - const [xrpHandler, setXRPHandler] = useState(); + const [xrpHandler, setXRPHandler] = useState(); const [userAddress, setUserAddress] = useState(undefined); function resetXRPWalletContext() { diff --git a/src/shared/models/error-types.ts b/src/shared/models/error-types.ts index de18f5a3..cd0dd7a5 100644 --- a/src/shared/models/error-types.ts +++ b/src/shared/models/error-types.ts @@ -25,3 +25,10 @@ export class UnisatError extends Error { this.name = 'UnisatError'; } } + +export class GemError extends Error { + constructor(message: string) { + super(message); + this.name = 'GemError'; + } +} diff --git a/src/shared/models/wallet.ts b/src/shared/models/wallet.ts index 83cfd917..40bff0d4 100644 --- a/src/shared/models/wallet.ts +++ b/src/shared/models/wallet.ts @@ -45,11 +45,11 @@ const ledgerXRP: XRPWallet = { icon: './images/logos/ledger-logo.svg', }; -// const gemXRP: XRPWallet = { -// id: XRPWalletType.Gem, -// name: 'Gem', -// icon: '/images/logos/gem-logo.svg', -// }; +const gemXRP: XRPWallet = { + id: XRPWalletType.Gem, + name: 'Gem', + icon: '/images/logos/gem-wallet-logo.svg', +}; -export const xrpWallets: XRPWallet[] = [ledgerXRP]; +export const xrpWallets: XRPWallet[] = [ledgerXRP, gemXRP]; export const bitcoinWallets: BitcoinWallet[] = [leather, ledger, unisat]; diff --git a/yarn.lock b/yarn.lock index a03ab7cf..ec2b78e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1875,6 +1875,11 @@ resolved "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.0.12.tgz" integrity sha512-0qzBxtIJLh82iMJ9pCXKYwGs1zyS+jyUmwVJ59+JdYnEaFVkDsxVOk9yDWfVOs14ALUneodU2m5YSGma6dCYCw== +"@gemwallet/api@3.8.0", "@gemwallet/api@^3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@gemwallet/api/-/api-3.8.0.tgz#46bc47789848c7ac9cc620613e0a1757dc8668a1" + integrity sha512-hZ6XC0mVm3Q54cgonrzk6tHS/wUMjtPHyqsqbtlnNGPouCR7OIfEDo5Y802qLZ5ah6PskhsK0DouVnwUykEM8Q== + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" @@ -5190,9 +5195,12 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -"dlc-btc-lib@file:../dlc-btc-lib": - version "2.3.0" +dlc-btc-lib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.0.tgz#715a309baa6829451668dfe65f635f1299b4909b" + integrity sha512-1xYVd2fwb+AVhfC/fd8eArFkdyEJS7uwxnUQOcmBmfNPk/ElUI+U2nfOtfwgv73svZTEU/RMaPQR6psFimI3UQ== dependencies: + "@gemwallet/api" "3.8.0" "@ledgerhq/hw-app-btc" "10.4.1" "@ledgerhq/hw-app-xrp" "6.29.4" "@noble/hashes" "1.4.0" @@ -5207,7 +5215,7 @@ dir-glob@^3.0.1: ledger-bitcoin "0.2.3" prompts "2.4.2" ramda "0.30.1" - ripple-binary-codec "^2.1.0" + ripple-binary-codec "2.1.0" scure "1.6.0" tiny-secp256k1 "2.2.3" xrpl "4.0.0" @@ -7117,9 +7125,9 @@ multiformats@^9.4.2: integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== nan@^2.13.2: - version "2.21.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.21.0.tgz#203ab765a02e6676c8cb92e1cad9503e7976d55b" - integrity sha512-MCpOGmdWvAOMi4RWnpxS5G24l7dVMtdSHtV87I3ltjaLdFOTO74HVJ+DfYiAXjxGKsYR/UCmm1rBwhMN7KqS1A== + version "2.22.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3" + integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== nanoid@^3.3.7: version "3.3.7" @@ -8004,7 +8012,7 @@ ripple-address-codec@^5.0.0: "@scure/base" "^1.1.3" "@xrplf/isomorphic" "^1.0.0" -ripple-binary-codec@^2.1.0: +ripple-binary-codec@2.1.0, ripple-binary-codec@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-2.1.0.tgz#f1ef81f8d1f05a6cecc06fc6d9b13456569cafda" integrity sha512-q0GAx+hj3UVcDbhXVjk7qeNfgUMehlElYJwiCuIBwqs/51GVTOwLr39Ht3eNsX5ow2xPRaC5mqHwcFDvLRm6cA==