From 10dbed30eeaf35446754e2f2c62eca89c3189877 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 1 Oct 2024 11:35:39 +0200 Subject: [PATCH 01/39] feat: add xrpl --- package.json | 5 +- public/images/logos/xpr-logo.svg | 1 + .../account/components/account-menu.tsx | 8 +- src/app/components/header/header.tsx | 2 +- .../deposit-transaction-screen.tsx | 4 + .../setup-vault-screen/setup-vault-screen.tsx | 4 +- .../my-vaults-small/my-vaults-small.tsx | 23 +-- src/app/hooks/use-nfts.ts | 166 ++++++++++++++++++ src/app/hooks/use-psbt.ts | 5 +- src/app/providers/vault-context-provider.tsx | 15 +- yarn.lock | 160 +++++++++++++---- 11 files changed, 318 insertions(+), 75 deletions(-) create mode 100644 public/images/logos/xpr-logo.svg create mode 100644 src/app/hooks/use-nfts.ts diff --git a/package.json b/package.json index 63b19aed..79bc3bb3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "concurrently": "^8.2.2", "d3": "^7.9.0", "decimal.js": "^10.4.3", - "dlc-btc-lib": "2.2.7", + "dlc-btc-lib": "file:../dlc-btc-lib", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", @@ -64,7 +64,8 @@ "redux-persist-expire": "^1.1.1", "viem": "2.x", "vite-plugin-toml": "^0.7.0", - "wagmi": "^2.12.2" + "wagmi": "^2.12.2", + "xrpl": "^4.0.0" }, "devDependencies": { "@ls-lint/ls-lint": "^2.2.2", diff --git a/public/images/logos/xpr-logo.svg b/public/images/logos/xpr-logo.svg new file mode 100644 index 00000000..9a2c7c63 --- /dev/null +++ b/public/images/logos/xpr-logo.svg @@ -0,0 +1 @@ +x \ No newline at end of file diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index a34ca27e..b3905e21 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -21,12 +21,8 @@ export function AccountMenu({ {wagmiConnector.name} {truncateAddress(address)} diff --git a/src/app/components/header/header.tsx b/src/app/components/header/header.tsx index bde833cd..c8c8d810 100644 --- a/src/app/components/header/header.tsx +++ b/src/app/components/header/header.tsx @@ -48,7 +48,7 @@ export function Header(): React.JSX.Element { - + {/* */} 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 c6feae00..d8c600b2 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 @@ -39,8 +39,12 @@ export function DepositTransactionScreen({ const { mintStep } = useSelector((state: RootState) => state.mintunmint); + console.log('allVaultsDEPOSIT', allVaults); + const currentVault = allVaults.find(vault => vault.uuid === mintStep[1]); + console.log('currentVaultDEPOSIT', currentVault); + const [isSubmitting, setIsSubmitting] = useState(false); async function handleDeposit(depositAmount: number) { diff --git a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx index c4c6bc1e..c958ddbf 100644 --- a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx +++ b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx @@ -3,6 +3,7 @@ import { useContext, useState } from 'react'; import { Button, VStack, useToast } from '@chakra-ui/react'; import { useEthersSigner } from '@functions/configuration.functions'; import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { RippleHandler } from 'dlc-btc-lib'; import { setupVault } from 'dlc-btc-lib/ethereum-functions'; import { SetupVaultScreenVaultGraphics } from './components/setup-vault-screen.vault-graphics'; @@ -19,7 +20,8 @@ export function SetupVaultScreen(): React.JSX.Element { async function handleSetup() { try { setIsSubmitting(true); - await setupVault(ethereumNetworkConfiguration.dlcManagerContract.connect(signer!)); + const xrplHandler = RippleHandler.fromWhatever(); + await xrplHandler.setupVault(); } catch (error: any) { setIsSubmitting(false); toast({ diff --git a/src/app/components/my-vaults-small/my-vaults-small.tsx b/src/app/components/my-vaults-small/my-vaults-small.tsx index 54fbe30c..9a2b13c0 100644 --- a/src/app/components/my-vaults-small/my-vaults-small.tsx +++ b/src/app/components/my-vaults-small/my-vaults-small.tsx @@ -1,9 +1,8 @@ -import { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Skeleton } from '@chakra-ui/react'; import { VaultsList } from '@components/vaults-list/vaults-list'; -import { VaultContext } from '@providers/vault-context-provider'; +import { useNFTs } from '@hooks/use-nfts'; import { VaultsListGroupContainer } from '../vaults-list/components/vaults-list-group-container'; import { MyVaultsSmallLayout } from './components/my-vaults-small.layout'; @@ -11,25 +10,13 @@ import { MyVaultsSmallLayout } from './components/my-vaults-small.layout'; export function MyVaultsSmall(): React.JSX.Element { const navigate = useNavigate(); - const vaultContext = useContext(VaultContext); - const { - readyVaults, - pendingVaults, - fundedVaults, - closingVaults, - closedVaults, - isLoading, - allVaults, - } = vaultContext; + const { readyVaults, pendingVaults, fundedVaults, closingVaults, closedVaults, allVaults } = + useNFTs(); return ( - 0} - > - + 0}> + diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts new file mode 100644 index 00000000..9bc924e7 --- /dev/null +++ b/src/app/hooks/use-nfts.ts @@ -0,0 +1,166 @@ +import { useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { formatVault } from '@functions/vault.functions'; +import { Vault } from '@models/vault'; +import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { RippleHandler } from 'dlc-btc-lib'; +import { VaultState } from 'dlc-btc-lib/models'; +import { isEmpty } from 'ramda'; + +interface useNFTsReturnType { + allVaults: Vault[]; + readyVaults: Vault[]; + pendingVaults: Vault[]; + fundedVaults: Vault[]; + closingVaults: Vault[]; + closedVaults: Vault[]; +} + +export function useNFTs(): useNFTsReturnType { + const queryClient = useQueryClient(); + const dispatch = useDispatch(); + const [dispatchTuple, setDispatchTuple] = useState< + [string, 'deposit' | 'burn' | 'depositPending' | 'withdrawPending'] + >(['', 'deposit']); + + async function fetchXRPLVaults(): Promise { + // eslint-disable-next-line react-hooks/rules-of-hooks + const xrplHandler = RippleHandler.fromWhatever(); + const xrplNFTs = await xrplHandler.getContractVaults(); + const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); + + const xrplVaults = xrplNFTs.map(vault => { + return formatVault(vault); + }); + + // Update the xrplVaults state first + queryClient.setQueryData(['xrpl-vaults'], xrplVaults); + + if (previousVaults && previousVaults.length > 0) { + console.log('Previous vaults', previousVaults); + const newVaults = xrplVaults.filter((vault: Vault) => { + const previousVault = previousVaults.find( + (previousVault: Vault) => previousVault.uuid === vault.uuid + ); + return !previousVault || previousVault.state !== vault.state; + }); + + console.log('New vaults', newVaults); + + newVaults.forEach((vault: Vault) => { + console.log('checking new vault'); + const previousVault = previousVaults.find( + (previousVault: Vault) => previousVault.uuid === vault.uuid + ); + console.log('previous vault', previousVault); + if (!previousVault) { + console.log('New vault', vault); + // Dispatch action for new vault + setDispatchTuple([vault.uuid, 'deposit']); + } else if (previousVault.state !== vault.state) { + if (vault.state === VaultState.PENDING) { + if (previousVault.valueLocked !== vault.valueMinted) { + setDispatchTuple([vault.uuid, 'withdrawPending']); + } else { + setDispatchTuple([vault.uuid, 'depositPending']); + } + } else if ( + previousVault.state === VaultState.PENDING && + vault.state === VaultState.FUNDED + ) { + console.log('SUCCESSFUL FUNDING'); + } else { + console.log('Vault state change', vault); + setDispatchTuple([vault.uuid, 'deposit']); + } + } + }); + } + + return xrplVaults; + } + + const { data: vaults } = useQuery({ + queryKey: ['xrpl-vaults'], + initialData: [], + queryFn: fetchXRPLVaults, + refetchInterval: 10000, + }); + + function dispatchVaultAction() { + if (!isEmpty(dispatchTuple[0])) { + const updatedVault = vaults.find(vault => vault.uuid === dispatchTuple[0]); + switch (dispatchTuple[1]) { + case 'deposit': + dispatch(mintUnmintActions.setMintStep([1, dispatchTuple[0]])); + break; + case 'burn': + dispatch(mintUnmintActions.setUnmintStep([1, dispatchTuple[0]])); + break; + case 'pending': + if (!updatedVault) return; + if (updatedVault.valueLocked !== updatedVault.valueMinted) { + dispatch(mintUnmintActions.setUnmintStep([2, dispatchTuple[0]])); + } else { + dispatch(mintUnmintActions.setMintStep([2, dispatchTuple[0]])); + } + break; + } + } + } + const { data } = useQuery({ + queryKey: ['newVault', dispatchTuple], + queryFn: dispatchVaultAction, + }); + + const allVaults = useMemo(() => { + const networkVaults = vaults || []; + return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); + }, [vaults]); + + const readyVaults = useMemo(() => { + const networkVaults = vaults || []; + return networkVaults + .filter(vault => vault.state === VaultState.READY) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults]); + + const fundedVaults = useMemo(() => { + const networkVaults = vaults || []; + return networkVaults + .filter(vault => vault.state === VaultState.FUNDED) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults]); + + const pendingVaults = useMemo(() => { + const networkVaults = vaults || []; + return networkVaults + .filter(vault => vault.state === VaultState.PENDING) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults]); + + const closingVaults = useMemo(() => { + const networkVaults = vaults || []; + return networkVaults + .filter(vault => vault.state === VaultState.CLOSING) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults]); + + const closedVaults = useMemo(() => { + const networkVaults = vaults || []; + return networkVaults + .filter(vault => vault.state === VaultState.CLOSED) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults]); + + return { + allVaults, + readyVaults, + pendingVaults, + closingVaults, + fundedVaults, + closedVaults, + }; +} diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 745aa1bd..fceb09b5 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -11,7 +11,7 @@ import { submitWithdrawDepositPSBT, } from 'dlc-btc-lib/attestor-request-functions'; import { getAttestorGroupPublicKey, getRawVault } from 'dlc-btc-lib/ethereum-functions'; -import { AttestorChainID, Transaction, VaultState } from 'dlc-btc-lib/models'; +import { Transaction, VaultState } from 'dlc-btc-lib/models'; import { useAccount } from 'wagmi'; import { useLeather } from './use-leather'; @@ -149,8 +149,7 @@ export function usePSBT(): UsePSBTReturnType { fundingPSBT: bytesToHex(fundingTransaction.toPSBT()), userEthereumAddress: ethereumUserAddress, userBitcoinTaprootPublicKey: dlcHandler.getTaprootDerivedPublicKey(), - attestorChainID: - ethereumNetworkConfiguration.ethereumAttestorChainID as AttestorChainID, + attestorChainID: 'ripple-xrpl-testnet', }); break; default: diff --git a/src/app/providers/vault-context-provider.tsx b/src/app/providers/vault-context-provider.tsx index c31f5885..7b3d7000 100644 --- a/src/app/providers/vault-context-provider.tsx +++ b/src/app/providers/vault-context-provider.tsx @@ -1,5 +1,6 @@ import { createContext } from 'react'; +import { useNFTs } from '@hooks/use-nfts'; import { useVaults } from '@hooks/use-vaults'; import { HasChildren } from '@models/has-children'; import { Vault } from '@models/vault'; @@ -11,7 +12,6 @@ interface VaultContextType { fundedVaults: Vault[]; closingVaults: Vault[]; closedVaults: Vault[]; - isLoading: boolean; } export const VaultContext = createContext({ @@ -21,19 +21,11 @@ export const VaultContext = createContext({ fundedVaults: [], closingVaults: [], closedVaults: [], - isLoading: true, }); export function VaultContextProvider({ children }: HasChildren): React.JSX.Element { - const { - allVaults, - readyVaults, - fundedVaults, - pendingVaults, - closingVaults, - closedVaults, - isLoading, - } = useVaults(); + const { allVaults, readyVaults, fundedVaults, pendingVaults, closingVaults, closedVaults } = + useNFTs(); return ( {children} diff --git a/yarn.lock b/yarn.lock index 9f6d3072..04706c25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1943,12 +1943,12 @@ rxjs "^7.8.1" semver "^7.3.5" -"@ledgerhq/devices@^8.4.2": - version "8.4.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.4.2.tgz#f1c56194cf1343d56cad49c8feba92ad93432e14" - integrity sha512-oWNTp3jCMaEvRHsXNYE/yo+PFMgXAJGFHLOU1UdE4/fYkniHbD9wdxwyZrZvrxr9hNw4/9wHiThyITwPtMzG7g== +"@ledgerhq/devices@^8.4.4": + version "8.4.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.4.4.tgz#0d195c1650fe57da2fad7f0d9074a0190947cd6f" + integrity sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A== dependencies: - "@ledgerhq/errors" "^6.18.0" + "@ledgerhq/errors" "^6.19.1" "@ledgerhq/logs" "^6.12.0" rxjs "^7.8.1" semver "^7.3.5" @@ -1958,17 +1958,17 @@ resolved "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.16.4.tgz" integrity sha512-M57yFaLYSN+fZCX0E0zUqOmrV6eipK+s5RhijHoUNlHUqrsvUz7iRQgpd5gRgHB5VkIjav7KdaZjKiWGcHovaQ== -"@ledgerhq/errors@^6.18.0": - version "6.18.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.18.0.tgz#d55d6a57430d7a86532a9033ce0b45103264c620" - integrity sha512-L3jQWAGyooxRDk/MRlW2v4Ji9+kloBtdmz9wBkHaj2j0n+05rweJSV3GHw9oye1BYMbVFqFffmT4H3hlXlCasw== +"@ledgerhq/errors@^6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.19.1.tgz#d9ac45ad4ff839e468b8f63766e665537aaede58" + integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw== "@ledgerhq/hw-app-btc@^10.2.4": - version "10.4.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-10.4.1.tgz#c78d97ec2515ae897bf3256a046934ddcd924aa9" - integrity sha512-8EpI59hT9N+kAN5kUE9F2DVzBTH7RSdXoyK5+l4UulJTzhJSh7888YvE//iDY+IiUOsNwMiHWxgAmZN3LucKQQ== + version "10.4.3" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-10.4.3.tgz#9b7bbefe61e8580a4b2fcf84f4f05fa64d9f1fd3" + integrity sha512-UAbq7AE2ApdIIpgkmVPxtI2r9YigE2VD+q8W4rZxvQbfnRAwBtKRxSn+1IVta6NgY1WzbqO1zKPDXvsY9ys47g== dependencies: - "@ledgerhq/hw-transport" "^6.31.2" + "@ledgerhq/hw-transport" "^6.31.4" "@ledgerhq/logs" "^6.12.0" bip32-path "^0.4.2" bitcoinjs-lib "^5.2.0" @@ -2001,13 +2001,13 @@ "@ledgerhq/logs" "^6.12.0" events "^3.3.0" -"@ledgerhq/hw-transport@^6.31.2": - version "6.31.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.2.tgz#79c95f7928a64a0e3b5bc4ea7b5be04b9f738322" - integrity sha512-B27UIzMzm2IXPGYnEB95R7eHxpXBkTBHh6MUJJQZVknt8LilEz1tfpTYUdzAKDGQ+Z5MZyYb01Eh3Zqm3kn3uw== +"@ledgerhq/hw-transport@^6.31.4": + version "6.31.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz#9b23a6de4a4caaa5c24b149c2dea8adde46f0eb1" + integrity sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A== dependencies: - "@ledgerhq/devices" "^8.4.2" - "@ledgerhq/errors" "^6.18.0" + "@ledgerhq/devices" "^8.4.4" + "@ledgerhq/errors" "^6.19.1" "@ledgerhq/logs" "^6.12.0" events "^3.3.0" @@ -2316,11 +2316,23 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@^1.0.0", "@noble/curves@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + "@noble/hashes@1.4.0", "@noble/hashes@^1.1.5", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@1.5.0", "@noble/hashes@^1.0.0", "@noble/hashes@~1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@noble/secp256k1@^1.7.1": version "1.7.1" resolved "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz" @@ -2655,10 +2667,10 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== -"@scure/base@^1.1.6", "@scure/base@~1.1.5": - version "1.1.8" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" - integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== +"@scure/base@^1.1.6", "@scure/base@~1.1.5", "@scure/base@~1.1.7", "@scure/base@~1.1.8", "@scure/base@~1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== "@scure/bip32@1.4.0": version "1.4.0" @@ -2669,6 +2681,15 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip32@^1.3.1": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== + dependencies: + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" + "@scure/bip39@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" @@ -2677,14 +2698,22 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip39@^1.2.1": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + "@scure/btc-signer@^1.3.1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@scure/btc-signer/-/btc-signer-1.3.2.tgz#56cf02a2e318097b1e4f531fac8ef114bdf4ddc8" - integrity sha512-BmcQHvxaaShKwgbFC0vDk0xzqbMhNtNmgXm6u7cz07FNtGsVItUuHow6NbgHmc+oJSBZJRym5dz8+Uu0JoEJhQ== + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/btc-signer/-/btc-signer-1.4.0.tgz#b8e87b7c2bee51f28cb991c6987708338a683438" + integrity sha512-uSZqmiWWm1+wflQbiob0CrzQSCwDO9MmAxqsqk+tkiRcUv8GbC3Ptv9/2nUbsoUBuPN/6mBQJ/KOBzHjc5Bgow== dependencies: - "@noble/curves" "~1.4.0" - "@noble/hashes" "~1.4.0" - "@scure/base" "~1.1.6" + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.9" micro-packed "~0.6.2" "@sinclair/typebox@^0.27.8": @@ -3752,6 +3781,23 @@ resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== +"@xrplf/isomorphic@^1.0.0", "@xrplf/isomorphic@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@xrplf/isomorphic/-/isomorphic-1.0.1.tgz#d7676e0ec0e55a39f37ddc1f3cc30eeab52e0739" + integrity sha512-0bIpgx8PDjYdrLFeC3csF305QQ1L7sxaWnL5y71mCvhenZzJgku9QsA+9QCXBC1eNYtxWO/xR91zrXJy2T/ixg== + dependencies: + "@noble/hashes" "^1.0.0" + eventemitter3 "5.0.1" + ws "^8.13.0" + +"@xrplf/secret-numbers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@xrplf/secret-numbers/-/secret-numbers-1.0.0.tgz#cc19ff84236cc2737b38f2e42a29924f2b8ffc0e" + integrity sha512-qsCLGyqe1zaq9j7PZJopK+iGTGRbk6akkg6iZXJJgxKwck0C5x5Gnwlb1HKYGOwPKyrXWpV6a2YmcpNpUFctGg== + dependencies: + "@xrplf/isomorphic" "^1.0.0" + ripple-keypairs "^2.0.0" + "@yr/monotone-cubic-spline@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz" @@ -4071,6 +4117,11 @@ bech32@^2.0.0: resolved "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== +bignumber.js@^9.0.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -5126,10 +5177,8 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dlc-btc-lib@2.2.7: +"dlc-btc-lib@file:../dlc-btc-lib": version "2.2.7" - resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.2.7.tgz#aa1d4e493ecc8a45e6ee7d81237083db14d80049" - integrity sha512-aIDgqoE2415znJKzO/Ola3kK7Ah6b1ln3A4tjhTFSd1CcbiU/AE7ADZaPMlw+GnFgMju38SUExCQOBnygG5dtA== dependencies: "@ledgerhq/hw-app-btc" "^10.2.4" "@noble/hashes" "^1.4.0" @@ -5146,6 +5195,7 @@ dlc-btc-lib@2.2.7: ramda "^0.30.1" scure "^1.6.0" tiny-secp256k1 "^2.2.3" + xrpl "^4.0.0" doctrine@^2.1.0: version "2.1.0" @@ -7931,6 +7981,32 @@ ripemd160@2, ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +ripple-address-codec@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-5.0.0.tgz#97059f7bba6f9ed7a52843de8aa427723fb529f6" + integrity sha512-de7osLRH/pt5HX2xw2TRJtbdLLWHu0RXirpQaEeCnWKY5DYHykh3ETSkofvm0aX0LJiV7kwkegJxQkmbO94gWw== + dependencies: + "@scure/base" "^1.1.3" + "@xrplf/isomorphic" "^1.0.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== + dependencies: + "@xrplf/isomorphic" "^1.0.1" + bignumber.js "^9.0.0" + ripple-address-codec "^5.0.0" + +ripple-keypairs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-2.0.0.tgz#4a1a8142e9a58c07e61b3cc6cfe7317db718d289" + integrity sha512-b5rfL2EZiffmklqZk1W+dvSy97v3V/C7936WxCCgDynaGPp7GE6R2XO7EU9O2LlM/z95rj870IylYnOQs+1Rag== + dependencies: + "@noble/curves" "^1.0.0" + "@xrplf/isomorphic" "^1.0.0" + ripple-address-codec "^5.0.0" + robust-predicates@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz" @@ -9140,11 +9216,31 @@ ws@^7.5.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@^8.13.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xmlhttprequest-ssl@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== +xrpl@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-4.0.0.tgz#c031b848c2a3e955b69b1dd438b1156e6826a2d6" + integrity sha512-VZm1lQWHQ6PheAAFGdH+ISXKvqB2hZDQ0w4ZcdAEtmqZQXtSIVQHOKPz95rEgGANbos7+XClxJ73++joPhA8Cw== + dependencies: + "@scure/bip32" "^1.3.1" + "@scure/bip39" "^1.2.1" + "@xrplf/isomorphic" "^1.0.1" + "@xrplf/secret-numbers" "^1.0.0" + bignumber.js "^9.0.0" + eventemitter3 "^5.0.1" + ripple-address-codec "^5.0.0" + ripple-binary-codec "^2.1.0" + ripple-keypairs "^2.0.0" + xtend@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From f073de6959dff0d2632d4381e28f5af7bbe158c4 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 2 Oct 2024 16:27:58 +0200 Subject: [PATCH 02/39] feat: modify vault query --- config.devnet.json | 2 +- .../account/components/account-menu.tsx | 13 +- src/app/components/header/header.tsx | 6 +- .../burn-transaction-screen.tsx | 50 ++-- .../deposit-transaction-screen.tsx | 2 +- .../setup-vault-screen/setup-vault-screen.tsx | 9 +- .../modals/components/modal-container.tsx | 13 +- .../successful-flow-modal.tsx | 12 +- ...n.transaction-form.submit-button-group.tsx | 7 +- .../vault.detaills/vault.details.tsx | 11 +- src/app/components/vault/vault.tsx | 2 +- src/app/functions/configuration.functions.ts | 38 ++- src/app/hooks/use-nfts.ts | 161 ++++++++--- src/app/hooks/use-proof-of-reserve.ts | 1 + src/app/hooks/use-psbt.ts | 21 +- src/app/hooks/use-vaults.ts | 252 +++++++++--------- src/app/providers/vault-context-provider.tsx | 1 - .../slices/mintunmint/mintunmint.slice.ts | 13 +- src/app/store/slices/modal/modal.slice.ts | 8 +- 19 files changed, 343 insertions(+), 279 deletions(-) diff --git a/config.devnet.json b/config.devnet.json index 524e8cd6..d758b434 100644 --- a/config.devnet.json +++ b/config.devnet.json @@ -1,6 +1,6 @@ { "appEnvironment": "devnet", - "coordinatorURL": "https://devnet.dlc.link/attestor-1", + "coordinatorURL": "http://localhost:8811", "enabledEthereumNetworkIDs": ["421614", "84532", "11155111"], "bitcoinNetwork": "regtest", "bitcoinNetworkIndex": 1, diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index b3905e21..e6af7b3c 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -1,5 +1,5 @@ import { ChevronDownIcon } from '@chakra-ui/icons'; -import { HStack, Image, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { HStack, Image, Menu, MenuButton, MenuItem, MenuList, Stack, Text } from '@chakra-ui/react'; import { truncateAddress } from 'dlc-btc-lib/utilities'; import { Connector } from 'wagmi'; @@ -19,13 +19,10 @@ export function AccountMenu({ - {;'xrpl'} - {truncateAddress(address)} + + {'xrpl'} + + {truncateAddress('rNHKCxi2FNFrj9jsz3aagJ64Si8v14Knx')} diff --git a/src/app/components/header/header.tsx b/src/app/components/header/header.tsx index c8c8d810..d69c879c 100644 --- a/src/app/components/header/header.tsx +++ b/src/app/components/header/header.tsx @@ -19,6 +19,8 @@ export function Header(): React.JSX.Element { const [showBanner, setShowBanner] = useState(false); const [isNetworkMenuOpen, setIsNetworkMenuOpen] = useState(false); + console.log('isNetworkMenuOpen', isNetworkMenuOpen); + const handleTabClick = (route: string) => { navigate(route); }; @@ -48,7 +50,9 @@ export function Header(): React.JSX.Element { - {/* */} + {(0 + 1 === 2) === true && ( + + )} 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 ff94e13c..e1345f48 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 @@ -4,19 +4,12 @@ import { useDispatch, useSelector } from 'react-redux'; 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 { getAndFormatVault } from '@functions/vault.functions'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; -import { VaultContext } from '@providers/vault-context-provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import { vaultActions } from '@store/slices/vault/vault.actions'; -import { withdraw } from 'dlc-btc-lib/ethereum-functions'; -import { EthereumNetworkID } from 'dlc-btc-lib/models'; +import { RippleHandler } from 'dlc-btc-lib'; import { shiftValue } from 'dlc-btc-lib/utilities'; -import { useAccount } from 'wagmi'; interface BurnTokenTransactionFormProps { isBitcoinWalletLoading: [boolean, string]; @@ -36,18 +29,12 @@ export function BurnTokenTransactionForm({ const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); - const { allVaults } = useContext(VaultContext); - - const { chainId } = useAccount(); - - const signer = useEthersSigner(); const { unmintStep } = useSelector((state: RootState) => state.mintunmint); const [isSubmitting, setIsSubmitting] = useState(false); - const currentVault = allVaults.find(vault => vault.uuid === unmintStep[1]); + const currentVault = unmintStep[2]; async function handleButtonClick(withdrawAmount: number): Promise { if (!currentVault) return; @@ -57,25 +44,22 @@ export function BurnTokenTransactionForm({ if (currentRisk === 'High') throw new Error('Risk Level is too high'); const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - await withdraw( - ethereumNetworkConfiguration.dlcManagerContract.connect(signer!), - currentVault.uuid, - formattedWithdrawAmount - ); + const xrplHandler = RippleHandler.fromWhatever(); + await xrplHandler.createCheck(formattedWithdrawAmount.toString(), currentVault.uuid); - const updatedVault = await getAndFormatVault( - currentVault.uuid, - ethereumNetworkConfiguration.dlcManagerContract - ); - dispatch( - vaultActions.swapVault({ - vaultUUID: currentVault.uuid, - updatedVault: updatedVault, - networkID: chainId?.toString() as EthereumNetworkID, - }) - ); - dispatch(mintUnmintActions.setUnmintStep([1, currentVault.uuid])); - setIsSubmitting(false); + // const updatedVault = await getAndFormatVault( + // currentVault.uuid, + // ethereumNetworkConfiguration.dlcManagerContract + // ); + // dispatch( + // vaultActions.swapVault({ + // vaultUUID: currentVault.uuid, + // updatedVault: updatedVault, + // networkID: chainId?.toString() as EthereumNetworkID, + // }) + // ); + // dispatch(mintUnmintActions.setUnmintStep([1, currentVault.uuid])); + // setIsSubmitting(false); } catch (error) { setIsSubmitting(false); toast({ 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 d8c600b2..c14effc9 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 @@ -41,7 +41,7 @@ export function DepositTransactionScreen({ console.log('allVaultsDEPOSIT', allVaults); - const currentVault = allVaults.find(vault => vault.uuid === mintStep[1]); + const currentVault = mintStep[2]; console.log('currentVaultDEPOSIT', currentVault); diff --git a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx index c958ddbf..5d05a480 100644 --- a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx +++ b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx @@ -1,20 +1,13 @@ -import { useContext, useState } from 'react'; +import { useState } from 'react'; import { Button, VStack, useToast } from '@chakra-ui/react'; -import { useEthersSigner } from '@functions/configuration.functions'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; import { RippleHandler } from 'dlc-btc-lib'; -import { setupVault } from 'dlc-btc-lib/ethereum-functions'; import { SetupVaultScreenVaultGraphics } from './components/setup-vault-screen.vault-graphics'; export function SetupVaultScreen(): React.JSX.Element { const toast = useToast(); - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - - const signer = useEthersSigner(); - const [isSubmitting, setIsSubmitting] = useState(false); async function handleSetup() { diff --git a/src/app/components/modals/components/modal-container.tsx b/src/app/components/modals/components/modal-container.tsx index 761fa989..e1f26500 100644 --- a/src/app/components/modals/components/modal-container.tsx +++ b/src/app/components/modals/components/modal-container.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { SelectWalletModal } from '@components/modals/select-wallet-modal/select-wallet-modal'; @@ -27,6 +28,10 @@ export function ModalContainer(): React.JSX.Element { dispatch(actionCreator()); }; + useEffect(() => { + console.log(isSuccesfulFlowModalOpen); + }, [isSuccesfulFlowModalOpen]); + return ( <> handleClosingModal(() => modalActions.toggleSuccessfulFlowModalVisibility({ vaultUUID: '', + vault: undefined, flow: 'mint', assetAmount: 0, }) ) } - vaultUUID={isSuccesfulFlowModalOpen[1] ? isSuccesfulFlowModalOpen[1] : ''} + vaultUUID={isSuccesfulFlowModalOpen[2] ? isSuccesfulFlowModalOpen[2] : ''} /> vault.uuid === vaultUUID); + console.log('vaultUUID', vaultUUID); return ( handleClose()}> @@ -41,7 +39,7 @@ export function SuccessfulFlowModal({ {getModalText(flow, assetAmount)} - + diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx index cbd31d90..c2930d11 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx @@ -49,6 +49,7 @@ export function TransactionFormSubmitButtonGroup({ handleCancelButtonClick, isSubmitting, }: TransactionFormSubmitButtonGroupProps): React.JSX.Element { + console.log('userEthereumAddressRiskLevel', userEthereumAddressRiskLevel); const formProperties = getFormProperties(flow, currentStep); return ( @@ -63,11 +64,7 @@ export function TransactionFormSubmitButtonGroup({ bgColor={formProperties.color} _hover={{ bgColor: 'accent.lightBlue.01' }} type="submit" - isDisabled={ - userEthereumAddressRiskLevel - ? ['High', 'Severe'].includes(userEthereumAddressRiskLevel) || !canSubmit - : !canSubmit || isSubmitting - } + isDisabled={!canSubmit || isSubmitting} > {getButtonLabel(flow, currentStep, isSubmitting, bitcoinWalletContextState)} diff --git a/src/app/components/vault/components/vault.detaills/vault.details.tsx b/src/app/components/vault/components/vault.detaills/vault.details.tsx index 4eb08e74..afe9ed4f 100644 --- a/src/app/components/vault/components/vault.detaills/vault.details.tsx +++ b/src/app/components/vault/components/vault.detaills/vault.details.tsx @@ -1,7 +1,9 @@ +import { useContext } from 'react'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import { Collapse, Stack, VStack } from '@chakra-ui/react'; +import { VaultContext } from '@providers/vault-context-provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { VaultState } from 'dlc-btc-lib/models'; @@ -32,17 +34,20 @@ export function VaultDetails({ const navigate = useNavigate(); const dispatch = useDispatch(); + const { allVaults } = useContext(VaultContext); + const vault = allVaults.find(vault => vault.uuid === vaultUUID); + function handleDepositClick() { navigate('/mint-withdraw'); - dispatch(mintUnmintActions.setMintStep([1, vaultUUID])); + dispatch(mintUnmintActions.setMintStep([1, vaultUUID, vault])); } function handleWithdrawClick() { navigate('/mint-withdraw'); if (vaultTotalLockedValue === vaultTotalMintedValue) { - dispatch(mintUnmintActions.setUnmintStep([0, vaultUUID])); + dispatch(mintUnmintActions.setUnmintStep([0, vaultUUID, vault])); } else { - dispatch(mintUnmintActions.setUnmintStep([1, vaultUUID])); + dispatch(mintUnmintActions.setUnmintStep([1, vaultUUID, vault])); } } diff --git a/src/app/components/vault/vault.tsx b/src/app/components/vault/vault.tsx index a4fdbde1..257a1e48 100644 --- a/src/app/components/vault/vault.tsx +++ b/src/app/components/vault/vault.tsx @@ -23,7 +23,7 @@ export function Vault({ vault, variant }: VaultProps): React.JSX.Element { function handleMainButtonClick() { if (variant === 'select') { const step = vault.valueLocked === vault.valueMinted ? 0 : 1; - dispatch(mintUnmintActions.setUnmintStep([step, vault.uuid])); + dispatch(mintUnmintActions.setUnmintStep([step, vault.uuid, vault])); } else { setIsVaultExpanded(!isVaultExpanded); } diff --git a/src/app/functions/configuration.functions.ts b/src/app/functions/configuration.functions.ts index b3064d8c..1b1cfbe7 100644 --- a/src/app/functions/configuration.functions.ts +++ b/src/app/functions/configuration.functions.ts @@ -1,12 +1,10 @@ -import { useMemo } from 'react'; - import { supportedEthereumNetworks } from 'dlc-btc-lib/constants'; import { getEthereumContract, getProvider } from 'dlc-btc-lib/ethereum-functions'; import { EthereumDeploymentPlan, EthereumNetwork, EthereumNetworkID } from 'dlc-btc-lib/models'; -import { Contract, providers } from 'ethers'; +import { Contract } from 'ethers'; import { filter, fromPairs, includes, map, pipe } from 'ramda'; -import { Account, Chain, Client, HttpTransport, Transport, http } from 'viem'; -import { Config, createConfig, useConnectorClient } from 'wagmi'; +import { Chain, HttpTransport, http } from 'viem'; +import { Config, createConfig } from 'wagmi'; import { walletConnect } from 'wagmi/connectors'; import { SUPPORTED_VIEM_CHAINS } from '@shared/constants/ethereum.constants'; @@ -74,20 +72,20 @@ export function getWagmiConfiguration(ethereumNetworkIDs: EthereumNetworkID[]): }); } -function clientToSigner(client: Client): providers.JsonRpcSigner { - const { account, chain, transport } = client; +// function clientToSigner(client: Client): providers.JsonRpcSigner { +// const { account, chain, transport } = client; - const network = { - chainId: chain.id, - name: chain.name, - ensAddress: chain.contracts?.ensRegistry?.address, - }; - return new providers.Web3Provider(transport, network).getSigner(account.address); -} +// const network = { +// chainId: chain.id, +// name: chain.name, +// ensAddress: chain.contracts?.ensRegistry?.address, +// }; +// return new providers.Web3Provider(transport, network).getSigner(account.address); +// } -export function useEthersSigner({ chainId }: { chainId?: number } = {}): - | providers.JsonRpcSigner - | undefined { - const { data: client } = useConnectorClient({ chainId }); - return useMemo(() => (client ? clientToSigner(client) : undefined), [client]); -} +// export function useEthersSigner({ chainId }: { chainId?: number } = {}): +// | providers.JsonRpcSigner +// | undefined { +// const { data: client } = useConnectorClient({ chainId }); +// return useMemo(() => (client ? clientToSigner(client) : undefined), [client]); +// } diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts index 9bc924e7..6d0c7bb1 100644 --- a/src/app/hooks/use-nfts.ts +++ b/src/app/hooks/use-nfts.ts @@ -4,7 +4,9 @@ import { useDispatch } from 'react-redux'; import { formatVault } from '@functions/vault.functions'; import { Vault } from '@models/vault'; 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 { RippleHandler } from 'dlc-btc-lib'; import { VaultState } from 'dlc-btc-lib/models'; import { isEmpty } from 'ramda'; @@ -22,59 +24,115 @@ export function useNFTs(): useNFTsReturnType { const queryClient = useQueryClient(); const dispatch = useDispatch(); const [dispatchTuple, setDispatchTuple] = useState< - [string, 'deposit' | 'burn' | 'depositPending' | 'withdrawPending'] + [ + string, + ( + | 'deposit' + | 'withdraw' + | 'depositPending' + | 'withdrawPending' + | 'depositSuccess' + | 'withdrawSuccess' + ), + number?, + ] >(['', 'deposit']); async function fetchXRPLVaults(): Promise { - // eslint-disable-next-line react-hooks/rules-of-hooks const xrplHandler = RippleHandler.fromWhatever(); - const xrplNFTs = await xrplHandler.getContractVaults(); + console.log('xrplHandler', xrplHandler); + console.log('Fetching XRPL Vaults'); + let xrplRawVaults: any[] = []; + try { + xrplRawVaults = await xrplHandler.getContractVaults(); + } catch (error) { + console.error('Error fetching XRPL Vaults', error); + return []; + } + console.log('xrpl vaults', xrplRawVaults); + const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); - const xrplVaults = xrplNFTs.map(vault => { + const xrplVaults = xrplRawVaults.map(vault => { return formatVault(vault); }); + if (previousVaults) { + const missingVaults = previousVaults.filter((previousVault: Vault) => { + return !xrplVaults.some((vault: Vault) => vault.uuid === previousVault.uuid); + }); + + if (missingVaults.length > 0) { + return previousVaults; + } + } + // Update the xrplVaults state first + console.log('Setting XRPL Vaults'); queryClient.setQueryData(['xrpl-vaults'], xrplVaults); + // await delay(5000); + if (previousVaults && previousVaults.length > 0) { - console.log('Previous vaults', previousVaults); const newVaults = xrplVaults.filter((vault: Vault) => { - const previousVault = previousVaults.find( - (previousVault: Vault) => previousVault.uuid === vault.uuid + return !previousVaults.some( + (previousVault: Vault) => + previousVault.uuid === vault.uuid && + previousVault.state === vault.state && + previousVault.valueLocked === vault.valueLocked && + previousVault.valueMinted === vault.valueMinted ); - return !previousVault || previousVault.state !== vault.state; }); - - console.log('New vaults', newVaults); + console.log('New XRPL Vaults', newVaults); newVaults.forEach((vault: Vault) => { - console.log('checking new vault'); const previousVault = previousVaults.find( (previousVault: Vault) => previousVault.uuid === vault.uuid ); - console.log('previous vault', previousVault); + if (!previousVault) { - console.log('New vault', vault); - // Dispatch action for new vault + console.log('New XRPL Vault', vault); + setDispatchTuple([vault.uuid, 'deposit']); - } else if (previousVault.state !== vault.state) { - if (vault.state === VaultState.PENDING) { - if (previousVault.valueLocked !== vault.valueMinted) { - setDispatchTuple([vault.uuid, 'withdrawPending']); - } else { - setDispatchTuple([vault.uuid, 'depositPending']); - } - } else if ( - previousVault.state === VaultState.PENDING && - vault.state === VaultState.FUNDED - ) { - console.log('SUCCESSFUL FUNDING'); - } else { - console.log('Vault state change', vault); - setDispatchTuple([vault.uuid, 'deposit']); + return; + } + + if (previousVault.state !== vault.state) { + switch (vault.state) { + case VaultState.FUNDED: + if (previousVault.valueMinted < previousVault.valueLocked) { + console.log('XRPL Vault Withdraw Success'); + setDispatchTuple([ + vault.uuid, + 'withdrawSuccess', + new Decimal(previousVault.valueLocked).minus(vault.valueLocked).toNumber(), + ]); + } else { + console.log('XRPL Vault Deposit Success'); + setDispatchTuple([ + vault.uuid, + 'depositSuccess', + new Decimal(vault.valueLocked).minus(previousVault.valueLocked).toNumber(), + ]); + } + break; + case VaultState.PENDING: + if (vault.valueLocked !== vault.valueMinted) { + console.log('XRPL Vault Withdraw Pending'); + setDispatchTuple([vault.uuid, 'withdrawPending']); + } else { + console.log('XRPL Vault Deposit Pending'); + setDispatchTuple([vault.uuid, 'depositPending']); + } + break; } + return; + } + + if (previousVault.valueMinted !== vault.valueMinted) { + console.log('XRPL Vault Withdraw'); + setDispatchTuple([vault.uuid, 'withdraw']); + return; } }); } @@ -92,25 +150,48 @@ export function useNFTs(): useNFTsReturnType { function dispatchVaultAction() { if (!isEmpty(dispatchTuple[0])) { const updatedVault = vaults.find(vault => vault.uuid === dispatchTuple[0]); + if (!updatedVault) { + throw new Error('Vault not found'); + } switch (dispatchTuple[1]) { case 'deposit': - dispatch(mintUnmintActions.setMintStep([1, dispatchTuple[0]])); + dispatch(mintUnmintActions.setMintStep([1, dispatchTuple[0], updatedVault])); break; - case 'burn': - dispatch(mintUnmintActions.setUnmintStep([1, dispatchTuple[0]])); + case 'depositSuccess': + dispatch( + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: updatedVault.uuid, + vault: updatedVault, + flow: 'mint', + assetAmount: dispatchTuple[2]!, + }) + ); + dispatch(mintUnmintActions.setMintStep([0, ''])); break; - case 'pending': - if (!updatedVault) return; - if (updatedVault.valueLocked !== updatedVault.valueMinted) { - dispatch(mintUnmintActions.setUnmintStep([2, dispatchTuple[0]])); - } else { - dispatch(mintUnmintActions.setMintStep([2, dispatchTuple[0]])); - } + case 'withdrawSuccess': + dispatch( + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: updatedVault.uuid, + vault: updatedVault, + flow: 'burn', + assetAmount: dispatchTuple[2]!, + }) + ); + dispatch(mintUnmintActions.setMintStep([0, ''])); + break; + case 'depositPending': + dispatch(mintUnmintActions.setMintStep([2, updatedVault.uuid, updatedVault])); + break; + case 'withdrawPending': + dispatch(mintUnmintActions.setUnmintStep([2, updatedVault.uuid, updatedVault])); + break; + case 'withdraw': + dispatch(mintUnmintActions.setUnmintStep([1, updatedVault.uuid, updatedVault])); break; } } } - const { data } = useQuery({ + useQuery({ queryKey: ['newVault', dispatchTuple], queryFn: dispatchVaultAction, }); diff --git a/src/app/hooks/use-proof-of-reserve.ts b/src/app/hooks/use-proof-of-reserve.ts index 5d353f49..b640a88c 100644 --- a/src/app/hooks/use-proof-of-reserve.ts +++ b/src/app/hooks/use-proof-of-reserve.ts @@ -13,6 +13,7 @@ export function useProofOfReserve(): UseProofOfReserveReturnType { queryKey: ['proofOfReserve'], queryFn: fetchAllProofOfReserve, refetchInterval: 60000, + enabled: false, }); async function fetchProofOfReserve(merchantAddress?: string): Promise { diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index fceb09b5..c4c3a724 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -4,13 +4,11 @@ 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 { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; +import { LedgerDLCHandler, RippleHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; import { submitFundingPSBT, submitWithdrawDepositPSBT, } from 'dlc-btc-lib/attestor-request-functions'; -import { getAttestorGroupPublicKey, getRawVault } from 'dlc-btc-lib/ethereum-functions'; import { Transaction, VaultState } from 'dlc-btc-lib/models'; import { useAccount } from 'wagmi'; @@ -52,8 +50,6 @@ export function usePSBT(): UsePSBTReturnType { isLoading: isUnisatLoading, } = useUnisat(); - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - const [bitcoinDepositAmount, setBitcoinDepositAmount] = useState(0); async function handleSignFundingTransaction( @@ -66,10 +62,11 @@ export function usePSBT(): UsePSBTReturnType { const feeRateMultiplier = import.meta.env.VITE_FEE_RATE_MULTIPLIER; - const dlcManagerContract = ethereumNetworkConfiguration.dlcManagerContract; + const attestorGroupPublicKey = + 'tpubDD8dCy2CrA7VgZdyLLmJB75nxWaokiCSZsPpqkj1uWjbLtxzuBCZQBtBMHpq9GU16v5RrhRz9EfhyK8QyenS3EtL7DAeEi6EBXRiaM2Usdm'; - const attestorGroupPublicKey = await getAttestorGroupPublicKey(dlcManagerContract); - const vault = await getRawVault(dlcManagerContract, vaultUUID); + const rippleHandler = RippleHandler.fromWhatever(); + const vault = await rippleHandler.getRawVault(vaultUUID); let fundingTransaction: Transaction; switch (bitcoinWalletType) { @@ -176,10 +173,10 @@ export function usePSBT(): UsePSBTReturnType { const feeRateMultiplier = import.meta.env.VITE_FEE_RATE_MULTIPLIER; - const dlcManagerContract = ethereumNetworkConfiguration.dlcManagerContract; - - const attestorGroupPublicKey = await getAttestorGroupPublicKey(dlcManagerContract); - const vault = await getRawVault(dlcManagerContract, vaultUUID); + const attestorGroupPublicKey = + 'tpubDD8dCy2CrA7VgZdyLLmJB75nxWaokiCSZsPpqkj1uWjbLtxzuBCZQBtBMHpq9GU16v5RrhRz9EfhyK8QyenS3EtL7DAeEi6EBXRiaM2Usdm'; + const rippleHandler = RippleHandler.fromWhatever(); + const vault = await rippleHandler.getRawVault(vaultUUID); if (!bitcoinWalletType) throw new Error('Bitcoin Wallet is not setup'); diff --git a/src/app/hooks/use-vaults.ts b/src/app/hooks/use-vaults.ts index 872245e6..3580331f 100644 --- a/src/app/hooks/use-vaults.ts +++ b/src/app/hooks/use-vaults.ts @@ -1,126 +1,126 @@ -import { useContext, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { formatVault } from '@functions/vault.functions'; -import { Vault } from '@models/vault'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; -import { RootState } from '@store/index'; -import { vaultActions } from '@store/slices/vault/vault.actions'; -import { getAllAddressVaults } from 'dlc-btc-lib/ethereum-functions'; -import { EthereumNetworkID, VaultState } from 'dlc-btc-lib/models'; -import { useAccount } from 'wagmi'; - -interface UseVaultsReturnType { - allVaults: Vault[]; - readyVaults: Vault[]; - pendingVaults: Vault[]; - fundedVaults: Vault[]; - closingVaults: Vault[]; - closedVaults: Vault[]; - isLoading: boolean; -} - -export function useVaults(): UseVaultsReturnType { - const dispatch = useDispatch(); - - const { isConnected, address: ethereumUserAddress } = useAccount(); - - const { vaults } = useSelector((state: RootState) => state.vault); - - const [isLoading, setIsLoading] = useState(true); - - const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( - EthereumNetworkConfigurationContext - ); - - const fetchVaultsIfReady = async (ethereumAddress: string) => { - setIsLoading(true); - - await getAllAddressVaults(ethereumNetworkConfiguration.dlcManagerContract, ethereumAddress) - .then(vaults => { - const formattedVaults = vaults.map(vault => { - return formatVault(vault); - }); - - dispatch( - vaultActions.setVaults({ - newVaults: formattedVaults, - networkID: ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID, - }) - ); - }) - .catch(error => { - // eslint-disable-next-line no-console - console.error('Error fetching vaults', error); - }); - setIsLoading(false); - }; - - useEffect(() => { - if (isConnected && ethereumUserAddress && !isEthereumNetworkConfigurationLoading) { - void fetchVaultsIfReady(ethereumUserAddress); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - isConnected, - ethereumUserAddress, - isEthereumNetworkConfigurationLoading, - ethereumNetworkConfiguration, - ]); - - const allVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const readyVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.READY) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const fundedVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.FUNDED) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const pendingVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.PENDING) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const closingVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.CLOSING) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const closedVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.CLOSED) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - return { - allVaults, - readyVaults, - pendingVaults, - closingVaults, - fundedVaults, - closedVaults, - isLoading, - }; -} +// import { useContext, useEffect, useMemo, useState } from 'react'; +// import { useDispatch, useSelector } from 'react-redux'; + +// import { formatVault } from '@functions/vault.functions'; +// import { Vault } from '@models/vault'; +// import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +// import { RootState } from '@store/index'; +// import { vaultActions } from '@store/slices/vault/vault.actions'; +// import { getAllAddressVaults } from 'dlc-btc-lib/ethereum-functions'; +// import { EthereumNetworkID, VaultState } from 'dlc-btc-lib/models'; +// import { useAccount } from 'wagmi'; + +// interface UseVaultsReturnType { +// allVaults: Vault[]; +// readyVaults: Vault[]; +// pendingVaults: Vault[]; +// fundedVaults: Vault[]; +// closingVaults: Vault[]; +// closedVaults: Vault[]; +// isLoading: boolean; +// } + +// export function useVaults(): UseVaultsReturnType { +// const dispatch = useDispatch(); + +// const { isConnected, address: ethereumUserAddress } = useAccount(); + +// const { vaults } = useSelector((state: RootState) => state.vault); + +// const [isLoading, setIsLoading] = useState(true); + +// const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( +// EthereumNetworkConfigurationContext +// ); + +// const fetchVaultsIfReady = async (ethereumAddress: string) => { +// setIsLoading(true); + +// await getAllAddressVaults(ethereumNetworkConfiguration.dlcManagerContract, ethereumAddress) +// .then(vaults => { +// const formattedVaults = vaults.map(vault => { +// return formatVault(vault); +// }); + +// dispatch( +// vaultActions.setVaults({ +// newVaults: formattedVaults, +// networkID: ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID, +// }) +// ); +// }) +// .catch(error => { +// // eslint-disable-next-line no-console +// console.error('Error fetching vaults', error); +// }); +// setIsLoading(false); +// }; + +// useEffect(() => { +// if (isConnected && ethereumUserAddress && !isEthereumNetworkConfigurationLoading) { +// void fetchVaultsIfReady(ethereumUserAddress); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [ +// isConnected, +// ethereumUserAddress, +// isEthereumNetworkConfigurationLoading, +// ethereumNetworkConfiguration, +// ]); + +// const allVaults = useMemo(() => { +// const networkVaults = +// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; +// return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); +// }, [vaults, ethereumNetworkConfiguration]); + +// const readyVaults = useMemo(() => { +// const networkVaults = +// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; +// return networkVaults +// .filter(vault => vault.state === VaultState.READY) +// .sort((a, b) => b.timestamp - a.timestamp); +// }, [vaults, ethereumNetworkConfiguration]); + +// const fundedVaults = useMemo(() => { +// const networkVaults = +// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; +// return networkVaults +// .filter(vault => vault.state === VaultState.FUNDED) +// .sort((a, b) => b.timestamp - a.timestamp); +// }, [vaults, ethereumNetworkConfiguration]); + +// const pendingVaults = useMemo(() => { +// const networkVaults = +// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; +// return networkVaults +// .filter(vault => vault.state === VaultState.PENDING) +// .sort((a, b) => b.timestamp - a.timestamp); +// }, [vaults, ethereumNetworkConfiguration]); + +// const closingVaults = useMemo(() => { +// const networkVaults = +// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; +// return networkVaults +// .filter(vault => vault.state === VaultState.CLOSING) +// .sort((a, b) => b.timestamp - a.timestamp); +// }, [vaults, ethereumNetworkConfiguration]); + +// const closedVaults = useMemo(() => { +// const networkVaults = +// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; +// return networkVaults +// .filter(vault => vault.state === VaultState.CLOSED) +// .sort((a, b) => b.timestamp - a.timestamp); +// }, [vaults, ethereumNetworkConfiguration]); + +// return { +// allVaults, +// readyVaults, +// pendingVaults, +// closingVaults, +// fundedVaults, +// closedVaults, +// isLoading, +// }; +// } diff --git a/src/app/providers/vault-context-provider.tsx b/src/app/providers/vault-context-provider.tsx index 7b3d7000..45f2cbc5 100644 --- a/src/app/providers/vault-context-provider.tsx +++ b/src/app/providers/vault-context-provider.tsx @@ -1,7 +1,6 @@ import { createContext } from 'react'; import { useNFTs } from '@hooks/use-nfts'; -import { useVaults } from '@hooks/use-vaults'; import { HasChildren } from '@models/has-children'; import { Vault } from '@models/vault'; diff --git a/src/app/store/slices/mintunmint/mintunmint.slice.ts b/src/app/store/slices/mintunmint/mintunmint.slice.ts index df846a2f..91103444 100644 --- a/src/app/store/slices/mintunmint/mintunmint.slice.ts +++ b/src/app/store/slices/mintunmint/mintunmint.slice.ts @@ -1,14 +1,15 @@ +import { Vault } from '@models/vault'; import { createSlice } from '@reduxjs/toolkit'; interface MintUnmintState { - mintStep: [number, string]; - unmintStep: [number, string]; + mintStep: [number, string, Vault | undefined]; + unmintStep: [number, string, Vault | undefined]; activeTab: 0 | 1; } const initialMintUnmintState: MintUnmintState = { - mintStep: [0, ''], - unmintStep: [0, ''], + mintStep: [0, '', undefined], + unmintStep: [0, '', undefined], activeTab: 0, }; @@ -28,8 +29,8 @@ export const mintUnmintSlice = createSlice({ state.activeTab = action.payload; }, resetMintUnmintState: state => { - state.mintStep = [0, '']; - state.unmintStep = [0, '']; + state.mintStep = [0, '', undefined]; + state.unmintStep = [0, '', undefined]; state.activeTab = 0; }, }, diff --git a/src/app/store/slices/modal/modal.slice.ts b/src/app/store/slices/modal/modal.slice.ts index 31989030..ef32b01b 100644 --- a/src/app/store/slices/modal/modal.slice.ts +++ b/src/app/store/slices/modal/modal.slice.ts @@ -1,15 +1,16 @@ +import { Vault } from '@models/vault'; import { createSlice } from '@reduxjs/toolkit'; interface ModalState { isSelectWalletModalOpen: boolean; - isSuccesfulFlowModalOpen: [boolean, string, string, number]; + isSuccesfulFlowModalOpen: [boolean, Vault | undefined, string, string, number]; isSelectBitcoinWalletModalOpen: boolean; isLedgerModalOpen: boolean; } const initialModalState: ModalState = { isSelectWalletModalOpen: false, - isSuccesfulFlowModalOpen: [false, '', 'mint', 0], + isSuccesfulFlowModalOpen: [false, undefined, '', 'mint', 0], isSelectBitcoinWalletModalOpen: false, isLedgerModalOpen: false, }; @@ -22,9 +23,10 @@ export const modalSlice = createSlice({ state.isSelectWalletModalOpen = !state.isSelectWalletModalOpen; }, toggleSuccessfulFlowModalVisibility: (state, action) => { - const { vaultUUID, flow, assetAmount } = action.payload; + const { vaultUUID, vault, flow, assetAmount } = action.payload; state.isSuccesfulFlowModalOpen = [ !state.isSuccesfulFlowModalOpen[0], + vault, vaultUUID, flow, assetAmount, From 82cb2093df4ab357e440329c23682a010f37d91a Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 3 Oct 2024 12:56:54 +0200 Subject: [PATCH 03/39] feat: modify use nft hook to make it more stable --- .../account/components/account-menu.tsx | 2 +- src/app/hooks/use-nfts.ts | 26 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index e6af7b3c..df1ca4e1 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -22,7 +22,7 @@ export function AccountMenu({ {'xrpl'} - {truncateAddress('rNHKCxi2FNFrj9jsz3aagJ64Si8v14Knx')} + {truncateAddress('rfvtbrXSxLsxVWDktR4sdzjJgv8EnMKFKG')} diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts index 6d0c7bb1..96040393 100644 --- a/src/app/hooks/use-nfts.ts +++ b/src/app/hooks/use-nfts.ts @@ -23,6 +23,8 @@ interface useNFTsReturnType { export function useNFTs(): useNFTsReturnType { const queryClient = useQueryClient(); const dispatch = useDispatch(); + const xrplHandler = RippleHandler.fromWhatever(); + const [dispatchTuple, setDispatchTuple] = useState< [ string, @@ -39,19 +41,21 @@ export function useNFTs(): useNFTsReturnType { >(['', 'deposit']); async function fetchXRPLVaults(): Promise { - const xrplHandler = RippleHandler.fromWhatever(); console.log('xrplHandler', xrplHandler); console.log('Fetching XRPL Vaults'); let xrplRawVaults: any[] = []; + + console.log('xrpl vaults', xrplRawVaults); + + const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); + try { xrplRawVaults = await xrplHandler.getContractVaults(); } catch (error) { console.error('Error fetching XRPL Vaults', error); - return []; + return previousVaults ?? []; } - console.log('xrpl vaults', xrplRawVaults); - - const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); + console.log('Previous XRPL Vaults', previousVaults); const xrplVaults = xrplRawVaults.map(vault => { return formatVault(vault); @@ -68,7 +72,12 @@ export function useNFTs(): useNFTsReturnType { } // Update the xrplVaults state first - console.log('Setting XRPL Vaults'); + console.log( + 'Setting XRPL Vaults', + xrplVaults.filter( + vault => vault.uuid !== '0x0000000000000000000000000000000000000000000000000000000000000000' + ) + ); queryClient.setQueryData(['xrpl-vaults'], xrplVaults); // await delay(5000); @@ -92,7 +101,8 @@ export function useNFTs(): useNFTsReturnType { if (!previousVault) { console.log('New XRPL Vault', vault); - + if (vault.uuid === '0x0000000000000000000000000000000000000000000000000000000000000000') + return; setDispatchTuple([vault.uuid, 'deposit']); return; } @@ -144,7 +154,7 @@ export function useNFTs(): useNFTsReturnType { queryKey: ['xrpl-vaults'], initialData: [], queryFn: fetchXRPLVaults, - refetchInterval: 10000, + refetchInterval: 20000, }); function dispatchVaultAction() { From 49591c058901ef26e55d5c91f2265622074700c9 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 8 Oct 2024 13:19:35 +0200 Subject: [PATCH 04/39] wip: adding xrpl conditions --- config.devnet.json | 1 + package.json | 2 +- src/app/app.tsx | 63 +++-- src/app/components/account/account.tsx | 23 +- .../account/components/account-menu.tsx | 40 ++- src/app/components/header/header.tsx | 25 +- .../burn-transaction-screen.tsx | 2 +- .../setup-vault-screen/setup-vault-screen.tsx | 4 +- ...nu.tsx => select-ethereum-wallet-menu.tsx} | 6 +- .../components/select-ripple-wallet-menu.tsx | 34 +++ .../select-wallet-modal.radio-card.tsx | 33 +++ .../select-wallet-modal.tsx | 123 +++++++-- .../my-vaults-small/my-vaults-small.tsx | 5 +- .../network/components/networks-menu.tsx | 20 +- .../select-network-button.tsx | 53 +++- src/app/functions/configuration.functions.ts | 10 + src/app/hooks/use-active-tabs.ts | 33 ++- src/app/hooks/use-connected.ts | 23 ++ src/app/hooks/use-nfts.ts | 15 +- src/app/hooks/use-psbt.ts | 4 +- src/app/hooks/use-vaults.ts | 258 +++++++++--------- src/app/pages/dashboard/dashboard.tsx | 20 +- .../network-configuration.provider.tsx | 31 +++ .../ripple-network-configuration.provider.tsx | 105 +++++++ src/app/providers/vault-context-provider.tsx | 27 +- src/shared/constants/ripple.constants.ts | 15 + src/shared/models/configuration.ts | 2 + src/shared/models/ripple.models.ts | 15 + yarn.lock | 139 +++++----- 29 files changed, 820 insertions(+), 311 deletions(-) rename src/app/components/modals/select-wallet-modal/components/{select-wallet-menu.tsx => select-ethereum-wallet-menu.tsx} (91%) create mode 100644 src/app/components/modals/select-wallet-modal/components/select-ripple-wallet-menu.tsx create mode 100644 src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx create mode 100644 src/app/hooks/use-connected.ts create mode 100644 src/app/providers/network-configuration.provider.tsx create mode 100644 src/app/providers/ripple-network-configuration.provider.tsx create mode 100644 src/shared/constants/ripple.constants.ts create mode 100644 src/shared/models/ripple.models.ts diff --git a/config.devnet.json b/config.devnet.json index d758b434..1b1f0317 100644 --- a/config.devnet.json +++ b/config.devnet.json @@ -2,6 +2,7 @@ "appEnvironment": "devnet", "coordinatorURL": "http://localhost:8811", "enabledEthereumNetworkIDs": ["421614", "84532", "11155111"], + "enabledRippleNetworkIDs": ["1"], "bitcoinNetwork": "regtest", "bitcoinNetworkIndex": 1, "bitcoinNetworkPreFix": "bcrt1", diff --git a/package.json b/package.json index 79bc3bb3..dd80f0e9 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,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.2.10", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", diff --git a/src/app/app.tsx b/src/app/app.tsx index d2f0d892..93ab9361 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -13,7 +13,12 @@ import { BitcoinTransactionConfirmationsProvider } from '@providers/bitcoin-quer import { BitcoinWalletContextProvider } from '@providers/bitcoin-wallet-context-provider'; import { EthereumNetworkConfigurationContextProvider } from '@providers/ethereum-network-configuration.provider'; import { EthereumObserverProvider } from '@providers/ethereum-observer-provider'; +import { NetworkConfigurationContextProvider } from '@providers/network-configuration.provider'; import { ProofOfReserveContextProvider } from '@providers/proof-of-reserve-context-provider'; +import { + RippleNetworkConfigurationContext, + RippleNetworkConfigurationContextProvider, +} from '@providers/ripple-network-configuration.provider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WagmiProvider } from 'wagmi'; @@ -28,33 +33,37 @@ export function App(): React.JSX.Element { return ( - - - - - - - - - } /> - } /> - {/* } /> */} - } /> - } /> - } - /> - } /> - } /> - - - - - - - - + + + + + + + + + + + } /> + } /> + {/* } /> */} + } /> + } /> + } + /> + } /> + } /> + + + + + + + + + + ); diff --git a/src/app/components/account/account.tsx b/src/app/components/account/account.tsx index db2dd48b..b797fa10 100644 --- a/src/app/components/account/account.tsx +++ b/src/app/components/account/account.tsx @@ -1,15 +1,30 @@ +import { useContext, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Button, HStack } from '@chakra-ui/react'; import { AccountMenu } from '@components/account/components/account-menu'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; import { useAccount, useDisconnect } from 'wagmi'; export function Account(): React.JSX.Element { const dispatch = useDispatch(); + const [isConnected, setIsConnected] = useState(false); - const { address, connector, isConnected } = useAccount(); + const { address, connector, isConnected: isEthereumWalletConnected } = useAccount(); + const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( + RippleNetworkConfigurationContext + ); + const { networkType } = useContext(NetworkConfigurationContext); + useEffect(() => { + if (networkType === 'evm') { + setIsConnected(isEthereumWalletConnected); + } else { + setIsConnected(isRippleWalletConnected); + } + }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); const { disconnect } = useDisconnect(); function onConnectWalletClick(): void { @@ -17,7 +32,11 @@ export function Account(): React.JSX.Element { } function onDisconnectWalletClick(): void { - disconnect(); + if (networkType === 'evm') { + disconnect(); + } else { + setIsRippleWalletConnected(false); + } dispatch(mintUnmintActions.resetMintUnmintState()); } diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index df1ca4e1..45b26fcf 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -1,5 +1,14 @@ import { ChevronDownIcon } from '@chakra-ui/icons'; -import { HStack, Image, Menu, MenuButton, MenuItem, MenuList, Stack, Text } from '@chakra-ui/react'; +import { + HStack, + Image, + Menu, + MenuButton, + MenuItem, + MenuList, + Text, + useBreakpointValue, +} from '@chakra-ui/react'; import { truncateAddress } from 'dlc-btc-lib/utilities'; import { Connector } from 'wagmi'; @@ -14,16 +23,31 @@ export function AccountMenu({ wagmiConnector, handleDisconnectWallet, }: AccountMenuProps): React.JSX.Element | false { - if (!address || !wagmiConnector) return false; + const isMobile = useBreakpointValue({ base: true, md: false }); + + if (!address) return false; return ( - + - - {'xrpl'} - - {truncateAddress('rfvtbrXSxLsxVWDktR4sdzjJgv8EnMKFKG')} - + {!isMobile ? ( + <> + {wagmiConnector.name} + {truncateAddress(address)} + + + ) : ( + {truncateAddress(address)} + )} diff --git a/src/app/components/header/header.tsx b/src/app/components/header/header.tsx index d69c879c..1f8f0161 100644 --- a/src/app/components/header/header.tsx +++ b/src/app/components/header/header.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { HStack, VStack } from '@chakra-ui/react'; @@ -6,6 +6,9 @@ import { Account } from '@components/account/account'; import { CompanyWebsiteButton } from '@components/company-website-button/company-website-button'; import { HeaderLayout } from '@components/header/components/header.layout'; import { NetworkBox } from '@components/network/network'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { chain } from 'ramda'; import { useAccount } from 'wagmi'; import { Banner } from './components/banner'; @@ -14,7 +17,19 @@ import { NavigationTabs } from './components/tabs'; export function Header(): React.JSX.Element { const navigate = useNavigate(); const location = useLocation(); - const { chain, isConnected } = useAccount(); + const [isConnected, setIsConnected] = useState(false); + const { address, connector, isConnected: isEthereumWalletConnected } = useAccount(); + const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( + RippleNetworkConfigurationContext + ); + const { networkType } = useContext(NetworkConfigurationContext); + useEffect(() => { + if (networkType === 'evm') { + setIsConnected(isEthereumWalletConnected); + } else { + setIsConnected(isRippleWalletConnected); + } + }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); const [showBanner, setShowBanner] = useState(false); const [isNetworkMenuOpen, setIsNetworkMenuOpen] = useState(false); @@ -26,7 +41,7 @@ export function Header(): React.JSX.Element { }; useEffect(() => { - if (isConnected && !chain) { + if (networkType === 'evm' && isEthereumWalletConnected && !chain) { setShowBanner(true); } else { setShowBanner(false); @@ -50,9 +65,7 @@ export function Header(): React.JSX.Element { - {(0 + 1 === 2) === true && ( - - )} + 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 e1345f48..7cde70ee 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 @@ -44,7 +44,7 @@ export function BurnTokenTransactionForm({ if (currentRisk === 'High') throw new Error('Risk Level is too high'); const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - const xrplHandler = RippleHandler.fromWhatever(); + const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); await xrplHandler.createCheck(formattedWithdrawAmount.toString(), currentVault.uuid); // const updatedVault = await getAndFormatVault( diff --git a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx index 5d05a480..44650f9a 100644 --- a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx +++ b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx @@ -13,8 +13,8 @@ export function SetupVaultScreen(): React.JSX.Element { async function handleSetup() { try { setIsSubmitting(true); - const xrplHandler = RippleHandler.fromWhatever(); - await xrplHandler.setupVault(); + const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); + await xrplHandler.setupVault(''); } catch (error: any) { setIsSubmitting(false); toast({ diff --git a/src/app/components/modals/select-wallet-modal/components/select-wallet-menu.tsx b/src/app/components/modals/select-wallet-modal/components/select-ethereum-wallet-menu.tsx similarity index 91% rename from src/app/components/modals/select-wallet-modal/components/select-wallet-menu.tsx rename to src/app/components/modals/select-wallet-modal/components/select-ethereum-wallet-menu.tsx index feb3c575..4a489799 100644 --- a/src/app/components/modals/select-wallet-modal/components/select-wallet-menu.tsx +++ b/src/app/components/modals/select-wallet-modal/components/select-ethereum-wallet-menu.tsx @@ -1,7 +1,7 @@ import { Box, Button, HStack, Image, Spinner, Text } from '@chakra-ui/react'; import { Connector } from 'wagmi'; -interface SelectWalletMenuProps { +interface SelectEthereumWalletMenuProps { wagmiConnector: Connector; selectedWagmiConnectorID?: string; isConnectWalletPending: boolean; @@ -9,13 +9,13 @@ interface SelectWalletMenuProps { handleConnectWallet: (wagmiConnector: Connector) => void; } -export function SelectWalletMenu({ +export function SelectEthereumWalletMenu({ wagmiConnector, selectedWagmiConnectorID, isConnectWalletPending, isConnectWalletSuccess, handleConnectWallet, -}: SelectWalletMenuProps): React.JSX.Element { +}: SelectEthereumWalletMenuProps): React.JSX.Element { const { id, icon, name } = wagmiConnector; const isThisWalletSelected = selectedWagmiConnectorID === id; 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 new file mode 100644 index 00000000..ec82733c --- /dev/null +++ b/src/app/components/modals/select-wallet-modal/components/select-ripple-wallet-menu.tsx @@ -0,0 +1,34 @@ +import { Box, Button, HStack, Image, Spinner, Text } from '@chakra-ui/react'; + +import { RippleWallet } from '../select-wallet-modal'; + +interface SelectRippleWalletMenuProps { + rippleWallet: RippleWallet; + handleConnectWallet: (id: string) => void; +} + +export function SelectRippleWalletMenu({ + rippleWallet, + handleConnectWallet, +}: SelectRippleWalletMenuProps): React.JSX.Element { + const { id, icon, name } = rippleWallet; + + return ( + + ); +} diff --git a/src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx b/src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx new file mode 100644 index 00000000..77af6fe9 --- /dev/null +++ b/src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx @@ -0,0 +1,33 @@ +import { Box, RadioProps, useRadio } from '@chakra-ui/react'; + +export function RadioCard(radioProps: RadioProps): React.JSX.Element { + const { getInputProps, getRadioProps } = useRadio(radioProps); + + const input = getInputProps(); + const checkbox = getRadioProps(); + + return ( + + + + {radioProps.children} + + + ); +} 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 ea08d08d..bb8c412d 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,26 +1,61 @@ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { CheckIcon } from '@chakra-ui/icons'; -import { HStack, ScaleFade, Text, VStack } from '@chakra-ui/react'; +import { HStack, ScaleFade, Tab, TabList, Tabs, Text, VStack } from '@chakra-ui/react'; import { ModalComponentProps } from '@components/modals/components/modal-container'; import { ModalLayout } from '@components/modals/components/modal.layout'; import { SelectWalletMenu } from '@components/modals/select-wallet-modal/components/select-wallet-menu'; import { SelectNetworkButton } from '@components/select-network-button/select-network-button'; +import { RippleNetworkID } from '@models/ripple.models'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { EthereumNetworkID } from 'dlc-btc-lib/models'; import { delay } from 'dlc-btc-lib/utilities'; -import { Chain } from 'viem'; import { Connector, useConfig, useConnect } from 'wagmi'; +import { SelectEthereumWalletMenu } from './components/select-ethereum-wallet-menu'; +import { SelectRippleWalletMenu } from './components/select-ripple-wallet-menu'; + +export interface RippleWallet { + id: string; + name: string; + icon: string; +} + +const seedWallet: RippleWallet = { + id: 'seed', + name: 'Seed Phrase', + icon: './images/logos/xpr-logo.svg', +}; + +const rippleWallets = [seedWallet]; + export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { const { connect, isPending, isSuccess, connectors } = useConnect(); - const { chains } = useConfig(); + const { chains: ethereumNetworks } = useConfig(); - const [selectedEthereumNetwork, setSelectedEthereumNetwork] = useState( - undefined + const { setNetworkType } = useContext(NetworkConfigurationContext); + const { enabledRippleNetworks, setIsRippleWalletConnected } = useContext( + RippleNetworkConfigurationContext + ); + + const ethereumNetworkIDs = ethereumNetworks.map( + ethereumNetwork => ethereumNetwork.id.toString() as EthereumNetworkID ); + const rippleNetworkIDs = enabledRippleNetworks.map(rippleNetwork => rippleNetwork.id); + + const [selectedNetworkType, setSelectedNetworkType] = useState<'evm' | 'xrpl'>('evm'); + + const [selectedNetworkID, setSelectedNetworkID] = useState< + EthereumNetworkID | RippleNetworkID | undefined + >(undefined); + const [selectedWagmiConnectorID, setSelectedWagmiConnectorID] = useState( undefined ); + const networkTypes = ['evm', 'xrpl']; + useEffect(() => { if (isSuccess) { void handleCloseAfterSuccess(); @@ -30,27 +65,36 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): async function handleCloseAfterSuccess() { await delay(1000); - setSelectedEthereumNetwork(undefined); + setSelectedNetworkID(undefined); setSelectedWagmiConnectorID(undefined); if (selectedWagmiConnectorID && selectedWagmiConnectorID !== 'walletConnect') handleClose(); } - async function handleConnectWallet(wagmiConnector: Connector) { + async function handleConnectEthereumWallet(wagmiConnector: Connector) { + if (selectedNetworkID === undefined) return; setSelectedWagmiConnectorID(wagmiConnector.id); - connect({ chainId: selectedEthereumNetwork?.id, connector: wagmiConnector }); + connect({ chainId: Number(selectedNetworkID), connector: wagmiConnector }); + setNetworkType('evm'); if (wagmiConnector.id === 'walletConnect') { handleClose(); } } - const handleChangeNetwork = (ethereumNetwork: Chain) => { - setSelectedEthereumNetwork(ethereumNetwork); + async function handleConnectRippleWallet(id: string) { + console.log('handleConnectRippleWallet', id); + setNetworkType('xrpl'); + setIsRippleWalletConnected(true); + handleClose(); + } + + const handleChangeNetwork = (networkID: EthereumNetworkID | RippleNetworkID) => { + setSelectedNetworkID(networkID); }; return ( handleClose()}> - {!selectedEthereumNetwork ? ( + {!selectedNetworkID ? ( Select Network ) : ( @@ -58,16 +102,29 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): )} + { + setSelectedNetworkType(networkTypes[index] as 'evm' | 'xrpl'); + setSelectedNetworkID(undefined); + }} + > + + Ethereum + Ripple + + - + {!isSuccess ? ( Select Wallet @@ -77,16 +134,24 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): )} - {connectors.map(wagmiConnector => ( - - ))} + {selectedNetworkType === 'evm' + ? connectors.map(wagmiConnector => ( + + )) + : rippleWallets.map(rippleWallet => ( + + ))} diff --git a/src/app/components/my-vaults-small/my-vaults-small.tsx b/src/app/components/my-vaults-small/my-vaults-small.tsx index 9a2b13c0..6d1a6d1f 100644 --- a/src/app/components/my-vaults-small/my-vaults-small.tsx +++ b/src/app/components/my-vaults-small/my-vaults-small.tsx @@ -1,8 +1,9 @@ +import { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Skeleton } from '@chakra-ui/react'; import { VaultsList } from '@components/vaults-list/vaults-list'; -import { useNFTs } from '@hooks/use-nfts'; +import { VaultContext } from '@providers/vault-context-provider'; import { VaultsListGroupContainer } from '../vaults-list/components/vaults-list-group-container'; import { MyVaultsSmallLayout } from './components/my-vaults-small.layout'; @@ -11,7 +12,7 @@ export function MyVaultsSmall(): React.JSX.Element { const navigate = useNavigate(); const { readyVaults, pendingVaults, fundedVaults, closingVaults, closedVaults, allVaults } = - useNFTs(); + useContext(VaultContext); return ( diff --git a/src/app/components/network/components/networks-menu.tsx b/src/app/components/network/components/networks-menu.tsx index f2e15f62..1f468e6e 100644 --- a/src/app/components/network/components/networks-menu.tsx +++ b/src/app/components/network/components/networks-menu.tsx @@ -1,4 +1,8 @@ +import { useContext, useEffect, useState } from 'react'; + import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { useAccount, useConfig, useSwitchChain } from 'wagmi'; interface NetworksMenuProps { @@ -11,10 +15,22 @@ export function NetworksMenu({ setIsMenuOpen, }: NetworksMenuProps): React.JSX.Element | null { const { chains } = useConfig(); - const { chain, isConnected } = useAccount(); + const [isConnected, setIsConnected] = useState(false); + const { address, chain, isConnected: isEthereumWalletConnected } = useAccount(); + const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( + RippleNetworkConfigurationContext + ); + const { networkType } = useContext(NetworkConfigurationContext); + useEffect(() => { + if (networkType === 'evm') { + setIsConnected(isEthereumWalletConnected); + } else { + setIsConnected(isRippleWalletConnected); + } + }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); const { switchChain } = useSwitchChain(); - if (!isConnected) { + if (!isConnected || networkType === 'xrpl') { return null; } diff --git a/src/app/components/select-network-button/select-network-button.tsx b/src/app/components/select-network-button/select-network-button.tsx index 8d62e150..b38b2962 100644 --- a/src/app/components/select-network-button/select-network-button.tsx +++ b/src/app/components/select-network-button/select-network-button.tsx @@ -1,24 +1,53 @@ +import { useEffect, useState } from 'react'; + import { ChevronDownIcon } from '@chakra-ui/icons'; import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { getEthereumNetworkByID, getRippleNetworkByID } from '@functions/configuration.functions'; +import { RippleNetwork, RippleNetworkID } from '@models/ripple.models'; +import { EthereumNetwork, EthereumNetworkID } from 'dlc-btc-lib/models'; import { Chain } from 'viem'; interface SelectNetworkButtonProps { - handleChangeNetwork: (ethereumNetwork: Chain) => void; - ethereumNetworks: readonly [Chain, ...Chain[]]; - selectedEthereumNetwork?: Chain; + handleChangeNetwork: (networkID: EthereumNetworkID | RippleNetworkID) => void; + ethereumNetworkIDs: EthereumNetworkID[]; + rippleNetworkIDs: RippleNetworkID[]; + selectedNetworkType?: 'evm' | 'xrpl'; + selectedNetworkID?: EthereumNetworkID | RippleNetworkID; } export function SelectNetworkButton({ handleChangeNetwork, - ethereumNetworks, - selectedEthereumNetwork, + ethereumNetworkIDs, + rippleNetworkIDs, + selectedNetworkID, + selectedNetworkType, }: SelectNetworkButtonProps): React.JSX.Element { + const [networks, setNetworks] = useState<(EthereumNetwork | RippleNetwork)[]>([]); + const ethereumNetworks = ethereumNetworkIDs.map((networkID: EthereumNetworkID) => { + return getEthereumNetworkByID(networkID as EthereumNetworkID); + }); + + const rippleNetworks = rippleNetworkIDs.map((networkID: RippleNetworkID) => { + return getRippleNetworkByID(networkID as RippleNetworkID); + }); + + const selectedNetwork = + selectedNetworkType === 'evm' + ? ethereumNetworks.find(network => network.id === selectedNetworkID) + : rippleNetworks.find(network => network.id === selectedNetworkID); + + useEffect(() => { + const currentNetworks = selectedNetworkType === 'evm' ? ethereumNetworks : rippleNetworks; + setNetworks(currentNetworks); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedNetworkType]); + return ( - {selectedEthereumNetwork ? ( - {selectedEthereumNetwork.name} + {selectedNetworkID ? ( + {selectedNetwork?.displayName} ) : ( {'SELECT NETWORK'} )} @@ -26,14 +55,14 @@ export function SelectNetworkButton({ - {ethereumNetworks.map(ethereumNetwork => { + {networks.map(network => { return ( handleChangeNetwork(ethereumNetwork)} + key={network.id} + value={network.id} + onClick={() => handleChangeNetwork(network.id)} > - {ethereumNetwork.name} + {network.displayName} ); })} diff --git a/src/app/functions/configuration.functions.ts b/src/app/functions/configuration.functions.ts index 1b1cfbe7..8f9e4a56 100644 --- a/src/app/functions/configuration.functions.ts +++ b/src/app/functions/configuration.functions.ts @@ -1,3 +1,4 @@ +import { RippleNetwork, RippleNetworkID } from '@models/ripple.models'; import { supportedEthereumNetworks } from 'dlc-btc-lib/constants'; import { getEthereumContract, getProvider } from 'dlc-btc-lib/ethereum-functions'; import { EthereumDeploymentPlan, EthereumNetwork, EthereumNetworkID } from 'dlc-btc-lib/models'; @@ -8,6 +9,7 @@ import { Config, createConfig } from 'wagmi'; import { walletConnect } from 'wagmi/connectors'; import { SUPPORTED_VIEM_CHAINS } from '@shared/constants/ethereum.constants'; +import { supportedRippleNetworks } from '@shared/constants/ripple.constants'; export function getEthereumNetworkDeploymentPlans(ethereumChain: Chain): EthereumDeploymentPlan[] { const ethereumNetwork: EthereumNetwork | undefined = supportedEthereumNetworks.find( @@ -41,6 +43,14 @@ export function getEthereumNetworkByID(ethereumNetworkID: EthereumNetworkID): Et return ethereumNetwork; } +export function getRippleNetworkByID(rippleNetworkID: RippleNetworkID): RippleNetwork { + const rippleNetwork = supportedRippleNetworks.find(network => network.id === rippleNetworkID); + if (!rippleNetwork) { + throw new Error(`Unsupported Ripple network: ${rippleNetworkID}`); + } + return rippleNetwork; +} + export function getEthereumContractWithProvider( contractDeploymentPlans: EthereumDeploymentPlan[], ethereumNetwork: Chain, diff --git a/src/app/hooks/use-active-tabs.ts b/src/app/hooks/use-active-tabs.ts index 5cde4144..0633ba7f 100644 --- a/src/app/hooks/use-active-tabs.ts +++ b/src/app/hooks/use-active-tabs.ts @@ -1,7 +1,9 @@ -import { useContext } from 'react'; +import { useContext, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { useQuery } from '@tanstack/react-query'; import { isUserWhitelisted, isWhitelistingEnabled } from 'dlc-btc-lib/ethereum-functions'; import { useAccount } from 'wagmi'; @@ -17,20 +19,35 @@ export function useActiveTabs(): UseActiveTabsReturnType { EthereumNetworkConfigurationContext ); + const { isRippleWalletConnected, isRippleNetworkConfigurationLoading } = useContext( + RippleNetworkConfigurationContext + ); + const { networkType } = useContext(NetworkConfigurationContext); + async function shouldActivateTabs(): Promise { - if (!address || !chain) { + console.log('networkType', networkType); + if (networkType === 'evm') { + if (!address || !chain) { + navigate('/'); + return false; + } + const dlcManagerContract = ethereumNetworkConfiguration.dlcManagerContract; + if (!(await isWhitelistingEnabled(dlcManagerContract))) return true; + return await isUserWhitelisted(dlcManagerContract, address); + } else { + console.log('isRippleWalletConnected', isRippleWalletConnected); navigate('/'); - return false; + return isRippleWalletConnected; } - const dlcManagerContract = ethereumNetworkConfiguration.dlcManagerContract; - if (!(await isWhitelistingEnabled(dlcManagerContract))) return true; - return await isUserWhitelisted(dlcManagerContract, address); } const { data: isActiveTabs } = useQuery({ - queryKey: ['activeTabs', chain, address], + queryKey: ['activeTabs', chain, address, networkType, isRippleWalletConnected], queryFn: shouldActivateTabs, - enabled: !isEthereumNetworkConfigurationLoading, + enabled: + networkType === 'evm' + ? !isEthereumNetworkConfigurationLoading + : !isRippleNetworkConfigurationLoading, }); return { diff --git a/src/app/hooks/use-connected.ts b/src/app/hooks/use-connected.ts new file mode 100644 index 00000000..e46208ae --- /dev/null +++ b/src/app/hooks/use-connected.ts @@ -0,0 +1,23 @@ +import { useContext, useEffect, useState } from 'react'; + +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { useAccount } from 'wagmi'; + +export function useConnected(): boolean { + const [isConnected, setIsConnected] = useState(false); + + const { isConnected: isEthereumWalletConnected } = useAccount(); + const { isRippleWalletConnected } = useContext(RippleNetworkConfigurationContext); + const { networkType } = useContext(NetworkConfigurationContext); + + useEffect(() => { + if (networkType === 'evm') { + setIsConnected(isEthereumWalletConnected); + } else { + setIsConnected(isRippleWalletConnected); + } + }, [networkType, isRippleWalletConnected, isEthereumWalletConnected]); + + return isConnected; +} diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts index 96040393..aa4cba1c 100644 --- a/src/app/hooks/use-nfts.ts +++ b/src/app/hooks/use-nfts.ts @@ -1,15 +1,16 @@ -import { useMemo, useState } from 'react'; +import { useContext, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { formatVault } from '@functions/vault.functions'; import { Vault } from '@models/vault'; +import { NetworkConfigurationContext } from '@providers/network-configuration.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 { RippleHandler } from 'dlc-btc-lib'; import { VaultState } from 'dlc-btc-lib/models'; -import { isEmpty } from 'ramda'; +import { isEmpty, set } from 'ramda'; interface useNFTsReturnType { allVaults: Vault[]; @@ -18,12 +19,16 @@ interface useNFTsReturnType { fundedVaults: Vault[]; closingVaults: Vault[]; closedVaults: Vault[]; + isLoading: boolean; } export function useNFTs(): useNFTsReturnType { const queryClient = useQueryClient(); const dispatch = useDispatch(); - const xrplHandler = RippleHandler.fromWhatever(); + const [isLoading, setIsLoading] = useState(true); + + const { networkType } = useContext(NetworkConfigurationContext); + const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); const [dispatchTuple, setDispatchTuple] = useState< [ @@ -41,6 +46,7 @@ export function useNFTs(): useNFTsReturnType { >(['', 'deposit']); async function fetchXRPLVaults(): Promise { + setIsLoading(true); console.log('xrplHandler', xrplHandler); console.log('Fetching XRPL Vaults'); let xrplRawVaults: any[] = []; @@ -147,6 +153,7 @@ export function useNFTs(): useNFTsReturnType { }); } + setIsLoading(false); return xrplVaults; } @@ -155,6 +162,7 @@ export function useNFTs(): useNFTsReturnType { initialData: [], queryFn: fetchXRPLVaults, refetchInterval: 20000, + enabled: networkType === 'xrpl', }); function dispatchVaultAction() { @@ -253,5 +261,6 @@ export function useNFTs(): useNFTsReturnType { closingVaults, fundedVaults, closedVaults, + isLoading, }; } diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index c4c3a724..660782e6 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -65,7 +65,7 @@ export function usePSBT(): UsePSBTReturnType { const attestorGroupPublicKey = 'tpubDD8dCy2CrA7VgZdyLLmJB75nxWaokiCSZsPpqkj1uWjbLtxzuBCZQBtBMHpq9GU16v5RrhRz9EfhyK8QyenS3EtL7DAeEi6EBXRiaM2Usdm'; - const rippleHandler = RippleHandler.fromWhatever(); + const rippleHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); const vault = await rippleHandler.getRawVault(vaultUUID); let fundingTransaction: Transaction; @@ -175,7 +175,7 @@ export function usePSBT(): UsePSBTReturnType { const attestorGroupPublicKey = 'tpubDD8dCy2CrA7VgZdyLLmJB75nxWaokiCSZsPpqkj1uWjbLtxzuBCZQBtBMHpq9GU16v5RrhRz9EfhyK8QyenS3EtL7DAeEi6EBXRiaM2Usdm'; - const rippleHandler = RippleHandler.fromWhatever(); + const rippleHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); const vault = await rippleHandler.getRawVault(vaultUUID); if (!bitcoinWalletType) throw new Error('Bitcoin Wallet is not setup'); diff --git a/src/app/hooks/use-vaults.ts b/src/app/hooks/use-vaults.ts index 3580331f..9730b813 100644 --- a/src/app/hooks/use-vaults.ts +++ b/src/app/hooks/use-vaults.ts @@ -1,126 +1,132 @@ -// import { useContext, useEffect, useMemo, useState } from 'react'; -// import { useDispatch, useSelector } from 'react-redux'; - -// import { formatVault } from '@functions/vault.functions'; -// import { Vault } from '@models/vault'; -// import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; -// import { RootState } from '@store/index'; -// import { vaultActions } from '@store/slices/vault/vault.actions'; -// import { getAllAddressVaults } from 'dlc-btc-lib/ethereum-functions'; -// import { EthereumNetworkID, VaultState } from 'dlc-btc-lib/models'; -// import { useAccount } from 'wagmi'; - -// interface UseVaultsReturnType { -// allVaults: Vault[]; -// readyVaults: Vault[]; -// pendingVaults: Vault[]; -// fundedVaults: Vault[]; -// closingVaults: Vault[]; -// closedVaults: Vault[]; -// isLoading: boolean; -// } - -// export function useVaults(): UseVaultsReturnType { -// const dispatch = useDispatch(); - -// const { isConnected, address: ethereumUserAddress } = useAccount(); - -// const { vaults } = useSelector((state: RootState) => state.vault); - -// const [isLoading, setIsLoading] = useState(true); - -// const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( -// EthereumNetworkConfigurationContext -// ); - -// const fetchVaultsIfReady = async (ethereumAddress: string) => { -// setIsLoading(true); - -// await getAllAddressVaults(ethereumNetworkConfiguration.dlcManagerContract, ethereumAddress) -// .then(vaults => { -// const formattedVaults = vaults.map(vault => { -// return formatVault(vault); -// }); - -// dispatch( -// vaultActions.setVaults({ -// newVaults: formattedVaults, -// networkID: ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID, -// }) -// ); -// }) -// .catch(error => { -// // eslint-disable-next-line no-console -// console.error('Error fetching vaults', error); -// }); -// setIsLoading(false); -// }; - -// useEffect(() => { -// if (isConnected && ethereumUserAddress && !isEthereumNetworkConfigurationLoading) { -// void fetchVaultsIfReady(ethereumUserAddress); -// } -// // eslint-disable-next-line react-hooks/exhaustive-deps -// }, [ -// isConnected, -// ethereumUserAddress, -// isEthereumNetworkConfigurationLoading, -// ethereumNetworkConfiguration, -// ]); - -// const allVaults = useMemo(() => { -// const networkVaults = -// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; -// return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); -// }, [vaults, ethereumNetworkConfiguration]); - -// const readyVaults = useMemo(() => { -// const networkVaults = -// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; -// return networkVaults -// .filter(vault => vault.state === VaultState.READY) -// .sort((a, b) => b.timestamp - a.timestamp); -// }, [vaults, ethereumNetworkConfiguration]); - -// const fundedVaults = useMemo(() => { -// const networkVaults = -// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; -// return networkVaults -// .filter(vault => vault.state === VaultState.FUNDED) -// .sort((a, b) => b.timestamp - a.timestamp); -// }, [vaults, ethereumNetworkConfiguration]); - -// const pendingVaults = useMemo(() => { -// const networkVaults = -// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; -// return networkVaults -// .filter(vault => vault.state === VaultState.PENDING) -// .sort((a, b) => b.timestamp - a.timestamp); -// }, [vaults, ethereumNetworkConfiguration]); - -// const closingVaults = useMemo(() => { -// const networkVaults = -// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; -// return networkVaults -// .filter(vault => vault.state === VaultState.CLOSING) -// .sort((a, b) => b.timestamp - a.timestamp); -// }, [vaults, ethereumNetworkConfiguration]); - -// const closedVaults = useMemo(() => { -// const networkVaults = -// vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; -// return networkVaults -// .filter(vault => vault.state === VaultState.CLOSED) -// .sort((a, b) => b.timestamp - a.timestamp); -// }, [vaults, ethereumNetworkConfiguration]); - -// return { -// allVaults, -// readyVaults, -// pendingVaults, -// closingVaults, -// fundedVaults, -// closedVaults, -// isLoading, -// }; -// } +import { useContext, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { formatVault } from '@functions/vault.functions'; +import { Vault } from '@models/vault'; +import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { RootState } from '@store/index'; +import { vaultActions } from '@store/slices/vault/vault.actions'; +import { getAllAddressVaults } from 'dlc-btc-lib/ethereum-functions'; +import { EthereumNetworkID, VaultState } from 'dlc-btc-lib/models'; +import { useAccount } from 'wagmi'; + +interface UseVaultsReturnType { + allVaults: Vault[]; + readyVaults: Vault[]; + pendingVaults: Vault[]; + fundedVaults: Vault[]; + closingVaults: Vault[]; + closedVaults: Vault[]; + isLoading: boolean; +} + +export function useVaults(): UseVaultsReturnType { + const dispatch = useDispatch(); + + const { isConnected, address: ethereumUserAddress } = useAccount(); + + const { vaults } = useSelector((state: RootState) => state.vault); + + useEffect(() => { + console.log('stored vaults', vaults); + }, [vaults]); + + const [isLoading, setIsLoading] = useState(true); + + const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( + EthereumNetworkConfigurationContext + ); + + const fetchVaultsIfReady = async (ethereumAddress: string) => { + setIsLoading(true); + + await getAllAddressVaults(ethereumNetworkConfiguration.dlcManagerContract, ethereumAddress) + .then(vaults => { + console.log('vaults', vaults); + const formattedVaults = vaults.map(vault => { + return formatVault(vault); + }); + + dispatch( + vaultActions.setVaults({ + newVaults: formattedVaults, + networkID: ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID, + }) + ); + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Error fetching vaults', error); + }); + setIsLoading(false); + }; + + useEffect(() => { + if (isConnected && ethereumUserAddress && !isEthereumNetworkConfigurationLoading) { + console.log('fetching vaults'); + void fetchVaultsIfReady(ethereumUserAddress); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + isConnected, + ethereumUserAddress, + isEthereumNetworkConfigurationLoading, + ethereumNetworkConfiguration, + ]); + + const allVaults = useMemo(() => { + const networkVaults = + vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; + return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); + }, [vaults, ethereumNetworkConfiguration]); + + const readyVaults = useMemo(() => { + const networkVaults = + vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; + return networkVaults + .filter(vault => vault.state === VaultState.READY) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults, ethereumNetworkConfiguration]); + + const fundedVaults = useMemo(() => { + const networkVaults = + vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; + return networkVaults + .filter(vault => vault.state === VaultState.FUNDED) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults, ethereumNetworkConfiguration]); + + const pendingVaults = useMemo(() => { + const networkVaults = + vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; + return networkVaults + .filter(vault => vault.state === VaultState.PENDING) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults, ethereumNetworkConfiguration]); + + const closingVaults = useMemo(() => { + const networkVaults = + vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; + return networkVaults + .filter(vault => vault.state === VaultState.CLOSING) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults, ethereumNetworkConfiguration]); + + const closedVaults = useMemo(() => { + const networkVaults = + vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; + return networkVaults + .filter(vault => vault.state === VaultState.CLOSED) + .sort((a, b) => b.timestamp - a.timestamp); + }, [vaults, ethereumNetworkConfiguration]); + + return { + allVaults, + readyVaults, + pendingVaults, + closingVaults, + fundedVaults, + closedVaults, + isLoading, + }; +} diff --git a/src/app/pages/dashboard/dashboard.tsx b/src/app/pages/dashboard/dashboard.tsx index c6e9851b..b37a66c7 100644 --- a/src/app/pages/dashboard/dashboard.tsx +++ b/src/app/pages/dashboard/dashboard.tsx @@ -1,13 +1,29 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { LandingPage } from '@components/mint-unmint/components/landing-page/landing-page'; import { MintUnmint } from '@components/mint-unmint/mint-unmint'; import { MyVaultsSmall } from '@components/my-vaults-small/my-vaults-small'; import { PageLayout } from '@pages/components/page.layout'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { useAccount } from 'wagmi'; export function Dashboard(): React.JSX.Element { - const { isConnected } = useAccount(); + const [isConnected, setIsConnected] = useState(true); + const { address, connector, isConnected: isEthereumWalletConnected } = useAccount(); + const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( + RippleNetworkConfigurationContext + ); + const { networkType } = useContext(NetworkConfigurationContext); + useEffect(() => { + console.log('changing network type', networkType); + if (networkType === 'evm') { + setIsConnected(isEthereumWalletConnected); + } else { + console.log('connected', isRippleWalletConnected); + setIsConnected(isRippleWalletConnected); + } + }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); return ( diff --git a/src/app/providers/network-configuration.provider.tsx b/src/app/providers/network-configuration.provider.tsx new file mode 100644 index 00000000..d192c9ac --- /dev/null +++ b/src/app/providers/network-configuration.provider.tsx @@ -0,0 +1,31 @@ +import React, { createContext, useEffect, useState } from 'react'; + +import { HasChildren } from '@models/has-children'; + +interface NetworkConfigurationContext { + networkType: 'evm' | 'xrpl'; + setNetworkType: (networkType: 'evm' | 'xrpl') => void; +} +export const NetworkConfigurationContext = createContext({ + networkType: 'evm', + setNetworkType: () => {}, +}); + +export function NetworkConfigurationContextProvider({ children }: HasChildren): React.JSX.Element { + const [networkType, setNetworkType] = useState<'evm' | 'xrpl'>('evm'); + + useEffect(() => { + console.log('networkType in provioder', networkType); + }, [networkType]); + + return ( + + {children} + + ); +} diff --git a/src/app/providers/ripple-network-configuration.provider.tsx b/src/app/providers/ripple-network-configuration.provider.tsx new file mode 100644 index 00000000..96e10ab5 --- /dev/null +++ b/src/app/providers/ripple-network-configuration.provider.tsx @@ -0,0 +1,105 @@ +import React, { createContext, useEffect, useState } from 'react'; + +import { getRippleNetworkByID } from '@functions/configuration.functions'; +import { HasChildren } from '@models/has-children'; +import { RippleNetwork, RippleNetworkConfiguration, RippleNetworkID } from '@models/ripple.models'; +import { equals, find } from 'ramda'; + +import { supportedRippleNetworks } from '@shared/constants/ripple.constants'; + +const defaultRippleNetwork = (() => { + const defaultNetwork = find( + (chain: RippleNetwork) => equals(chain.id, appConfiguration.enabledRippleNetworkIDs.at(0)), + supportedRippleNetworks + ); + if (!defaultNetwork) { + throw new Error('Default Ripple Network not found'); + } + return defaultNetwork; +})(); + +const enabledRippleNetworks = appConfiguration.enabledRippleNetworkIDs.map(id => + getRippleNetworkByID(id) +); + +function getRippleNetworkConfiguration( + rippleNetworkID: RippleNetworkID +): RippleNetworkConfiguration { + switch (rippleNetworkID) { + case RippleNetworkID.Mainnet: + return { + rippleExplorerAPIURL: 'https://livenet.xrpl.org/', + websocketURL: 'wss://s1.ripple.com/', + ripplemAttestorChainID: 'xrpl-ripple-mainnet', + }; + case RippleNetworkID.Testnet: + return { + rippleExplorerAPIURL: 'https://testnet.xrpl.org/', + websocketURL: 'wss://s.altnet.rippletest.net:51233', + ripplemAttestorChainID: 'xrpl-ripple-testnet', + }; + + default: + throw new Error(`Unsupported Ethereum network ID: ${rippleNetworkID}`); + } +} + +const defaultRippleNetworkConfiguration = getRippleNetworkConfiguration( + defaultRippleNetwork.id.toString() as RippleNetworkID +); + +interface RippleNetworkConfigurationContext { + rippleNetworkConfiguration: RippleNetworkConfiguration; + setRippleNetworkID: (rippleNetworkID: RippleNetworkID) => void; + enabledRippleNetworks: RippleNetwork[]; + isRippleWalletConnected: boolean; + setIsRippleWalletConnected: (isConnected: boolean) => void; + isRippleNetworkConfigurationLoading: boolean; +} +export const RippleNetworkConfigurationContext = createContext({ + setRippleNetworkID: () => {}, + rippleNetworkConfiguration: defaultRippleNetworkConfiguration, + enabledRippleNetworks, + isRippleWalletConnected: false, + setIsRippleWalletConnected: () => {}, + isRippleNetworkConfigurationLoading: false, +}); + +export function RippleNetworkConfigurationContextProvider({ + children, +}: HasChildren): React.JSX.Element { + const [rippleNetworkID, setRippleNetworkID] = useState(defaultRippleNetwork.id); + const [rippleNetworkConfiguration, setRippleNetworkConfiguration] = + useState(defaultRippleNetworkConfiguration); + + const [isRippleNetworkConfigurationLoading, setIsRippleNetworkConfigurationLoading] = + useState(false); + + const [isRippleWalletConnected, setIsRippleWalletConnected] = useState(false); + useEffect(() => { + setIsRippleNetworkConfigurationLoading(true); + + const currentRippleNetworkConfiguration = getRippleNetworkConfiguration( + rippleNetworkID.toString() as RippleNetworkID + ); + + setRippleNetworkConfiguration(currentRippleNetworkConfiguration); + setIsRippleNetworkConfigurationLoading(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rippleNetworkID]); + + return ( + + {children} + + ); +} diff --git a/src/app/providers/vault-context-provider.tsx b/src/app/providers/vault-context-provider.tsx index 45f2cbc5..e2326989 100644 --- a/src/app/providers/vault-context-provider.tsx +++ b/src/app/providers/vault-context-provider.tsx @@ -1,9 +1,12 @@ -import { createContext } from 'react'; +import { createContext, useContext } from 'react'; import { useNFTs } from '@hooks/use-nfts'; +import { useVaults } from '@hooks/use-vaults'; import { HasChildren } from '@models/has-children'; import { Vault } from '@models/vault'; +import { NetworkConfigurationContext } from './network-configuration.provider'; + interface VaultContextType { allVaults: Vault[]; readyVaults: Vault[]; @@ -23,18 +26,24 @@ export const VaultContext = createContext({ }); export function VaultContextProvider({ children }: HasChildren): React.JSX.Element { - const { allVaults, readyVaults, fundedVaults, pendingVaults, closingVaults, closedVaults } = - useNFTs(); + const rippleVaults = useNFTs(); + const ethereumVaults = useVaults(); + const { networkType } = useContext(NetworkConfigurationContext); + + const getVaults = (vaultType: keyof VaultContextType) => + networkType === 'evm' ? ethereumVaults[vaultType] : rippleVaults[vaultType]; + + console.log('ethereumVaults', ethereumVaults); return ( {children} diff --git a/src/shared/constants/ripple.constants.ts b/src/shared/constants/ripple.constants.ts new file mode 100644 index 00000000..10039df5 --- /dev/null +++ b/src/shared/constants/ripple.constants.ts @@ -0,0 +1,15 @@ +import { RippleNetwork, RippleNetworkID } from '@models/ripple.models'; + +export const RippleMainnet: RippleNetwork = { + id: RippleNetworkID.Mainnet, + name: 'Mainnet', + displayName: 'Mainnet', +}; + +export const RippleTestnet: RippleNetwork = { + id: RippleNetworkID.Testnet, + name: 'Testnet', + displayName: 'Testnet', +}; + +export const supportedRippleNetworks: RippleNetwork[] = [RippleMainnet, RippleTestnet]; diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index d2dbde22..18bcca2e 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -2,6 +2,7 @@ import { EthereumDeploymentPlan, EthereumNetworkID } from 'dlc-btc-lib/models'; import { Merchant } from './merchant'; import { Protocol } from './protocol'; +import { RippleNetworkID } from './ripple.models'; enum BitcoinNetworkName { MAINNET = 'mainnet', @@ -23,6 +24,7 @@ export interface Configuration { appEnvironment: AppEnvironment; coordinatorURL: string; enabledEthereumNetworkIDs: EthereumNetworkID[]; + enabledRippleNetworkIDs: RippleNetworkID[]; ethereumContractInformations: { name: string; deploymentPlans: EthereumDeploymentPlan[] }[]; l1Websocket: string; l1HTTP: string; diff --git a/src/shared/models/ripple.models.ts b/src/shared/models/ripple.models.ts new file mode 100644 index 00000000..7d4795c2 --- /dev/null +++ b/src/shared/models/ripple.models.ts @@ -0,0 +1,15 @@ +export interface RippleNetwork { + id: RippleNetworkID; + name: string; + displayName: string; +} +export enum RippleNetworkID { + Mainnet = '0', + Testnet = '1', +} + +export interface RippleNetworkConfiguration { + rippleExplorerAPIURL: string; + websocketURL: string; + ripplemAttestorChainID: 'xrpl-ripple-mainnet' | 'xrpl-ripple-testnet'; +} diff --git a/yarn.lock b/yarn.lock index 04706c25..53d82aee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1963,12 +1963,12 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.19.1.tgz#d9ac45ad4ff839e468b8f63766e665537aaede58" integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw== -"@ledgerhq/hw-app-btc@^10.2.4": - version "10.4.3" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-10.4.3.tgz#9b7bbefe61e8580a4b2fcf84f4f05fa64d9f1fd3" - integrity sha512-UAbq7AE2ApdIIpgkmVPxtI2r9YigE2VD+q8W4rZxvQbfnRAwBtKRxSn+1IVta6NgY1WzbqO1zKPDXvsY9ys47g== +"@ledgerhq/hw-app-btc@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-10.4.1.tgz#c78d97ec2515ae897bf3256a046934ddcd924aa9" + integrity sha512-8EpI59hT9N+kAN5kUE9F2DVzBTH7RSdXoyK5+l4UulJTzhJSh7888YvE//iDY+IiUOsNwMiHWxgAmZN3LucKQQ== dependencies: - "@ledgerhq/hw-transport" "^6.31.4" + "@ledgerhq/hw-transport" "^6.31.2" "@ledgerhq/logs" "^6.12.0" bip32-path "^0.4.2" bitcoinjs-lib "^5.2.0" @@ -2001,7 +2001,7 @@ "@ledgerhq/logs" "^6.12.0" events "^3.3.0" -"@ledgerhq/hw-transport@^6.31.4": +"@ledgerhq/hw-transport@^6.31.2": version "6.31.4" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz#9b23a6de4a4caaa5c24b149c2dea8adde46f0eb1" integrity sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A== @@ -2657,6 +2657,11 @@ resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.1.tgz#4d5dac21c6e044b68b13b53468633ec771f30e3b" integrity sha512-YApSpx+3h6uejrJVh8PSqXRRAwmsWz8PZERObMGJNC9NPoMhZG/Rvqb2UWmVLrjFh880rqutsB+GrTmJP351PA== +"@scure/base@1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" + integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== + "@scure/base@^1.1.1": version "1.1.6" resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz" @@ -2667,7 +2672,7 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== -"@scure/base@^1.1.6", "@scure/base@~1.1.5", "@scure/base@~1.1.7", "@scure/base@~1.1.8", "@scure/base@~1.1.9": +"@scure/base@~1.1.5", "@scure/base@~1.1.7", "@scure/base@~1.1.8": version "1.1.9" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== @@ -2706,14 +2711,14 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.8" -"@scure/btc-signer@^1.3.1": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scure/btc-signer/-/btc-signer-1.4.0.tgz#b8e87b7c2bee51f28cb991c6987708338a683438" - integrity sha512-uSZqmiWWm1+wflQbiob0CrzQSCwDO9MmAxqsqk+tkiRcUv8GbC3Ptv9/2nUbsoUBuPN/6mBQJ/KOBzHjc5Bgow== +"@scure/btc-signer@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scure/btc-signer/-/btc-signer-1.3.2.tgz#56cf02a2e318097b1e4f531fac8ef114bdf4ddc8" + integrity sha512-BmcQHvxaaShKwgbFC0vDk0xzqbMhNtNmgXm6u7cz07FNtGsVItUuHow6NbgHmc+oJSBZJRym5dz8+Uu0JoEJhQ== dependencies: - "@noble/curves" "~1.6.0" - "@noble/hashes" "~1.5.0" - "@scure/base" "~1.1.9" + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" micro-packed "~0.6.2" "@sinclair/typebox@^0.27.8": @@ -3283,7 +3288,7 @@ resolved "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz" integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw== -"@types/ramda@^0.30.1": +"@types/ramda@0.30.1", "@types/ramda@^0.30.1": version "0.30.1" resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.30.1.tgz#316257fec12747bb39a2e921df48a9dcb8c164a9" integrity sha512-aoyF/ADPL6N+/NXXfhPWF+Qj6w1Cql59m9wX0Gi15uyF+bpzXeLd63HPdiTDE2bmLXfNcVufsDPKmbfOrOzTBA== @@ -4144,6 +4149,16 @@ bip32-path@^0.4.2: resolved "https://registry.npmjs.org/bip32-path/-/bip32-path-0.4.2.tgz" integrity sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ== +bip32@4.0.0, bip32@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz" + integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== + dependencies: + "@noble/hashes" "^1.2.0" + "@scure/base" "^1.1.1" + typeforce "^1.11.5" + wif "^2.0.6" + bip32@^2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134" @@ -4157,16 +4172,6 @@ bip32@^2.0.4: typeforce "^1.11.5" wif "^2.0.6" -bip32@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz" - integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== - dependencies: - "@noble/hashes" "^1.2.0" - "@scure/base" "^1.1.1" - typeforce "^1.11.5" - wif "^2.0.6" - bip66@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" @@ -4184,6 +4189,18 @@ bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.0: resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278" integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow== +bitcoinjs-lib@6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz#f57c17c82511f860f11946d784c18da39f8618a8" + integrity sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA== + dependencies: + "@noble/hashes" "^1.2.0" + bech32 "^2.0.0" + bip174 "^2.1.1" + bs58check "^3.0.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.1.2" + bitcoinjs-lib@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz#caf8b5efb04274ded1b67e0706960b93afb9d332" @@ -4217,18 +4234,6 @@ bitcoinjs-lib@^6.1.3: typeforce "^1.11.3" varuint-bitcoin "^1.1.2" -bitcoinjs-lib@^6.1.5: - version "6.1.6" - resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz#f57c17c82511f860f11946d784c18da39f8618a8" - integrity sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA== - dependencies: - "@noble/hashes" "^1.2.0" - bech32 "^2.0.0" - bip174 "^2.1.1" - bs58check "^3.0.1" - typeforce "^1.11.3" - varuint-bitcoin "^1.1.2" - bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" @@ -4398,6 +4403,11 @@ chai@^4.3.10: pathval "^1.1.1" type-detect "^4.0.8" +chalk@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -4415,11 +4425,6 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - check-error@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz" @@ -5063,7 +5068,7 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.4.3: +decimal.js@10.4.3, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -5177,25 +5182,27 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -"dlc-btc-lib@file:../dlc-btc-lib": - version "2.2.7" +dlc-btc-lib@^2.2.10: + version "2.2.10" + resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.2.10.tgz#34bee9ee687c7ead3ec27498f1f80539003525a3" + integrity sha512-SzqnGptBUGexr5rbvc183sTY30SP0MUVX7wQAW0W5wJsuUvNgYfgboc+EC0fLxScwnm1H92mrvMWlovNu8d8iQ== dependencies: - "@ledgerhq/hw-app-btc" "^10.2.4" - "@noble/hashes" "^1.4.0" - "@scure/base" "^1.1.6" - "@scure/btc-signer" "^1.3.1" - "@types/ramda" "^0.30.1" - bip32 "^4.0.0" - bitcoinjs-lib "^6.1.5" - chalk "^5.3.0" - decimal.js "^10.4.3" + "@ledgerhq/hw-app-btc" "10.4.1" + "@noble/hashes" "1.4.0" + "@scure/base" "1.1.8" + "@scure/btc-signer" "1.3.2" + "@types/ramda" "0.30.1" + bip32 "4.0.0" + bitcoinjs-lib "6.1.6" + chalk "5.3.0" + decimal.js "10.4.3" ethers "5.7.2" - ledger-bitcoin "^0.2.3" - prompts "^2.4.2" - ramda "^0.30.1" - scure "^1.6.0" - tiny-secp256k1 "^2.2.3" - xrpl "^4.0.0" + ledger-bitcoin "0.2.3" + prompts "2.4.2" + ramda "0.30.1" + scure "1.6.0" + tiny-secp256k1 "2.2.3" + xrpl "4.0.0" doctrine@^2.1.0: version "2.1.0" @@ -6781,7 +6788,7 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" -ledger-bitcoin@^0.2.3: +ledger-bitcoin@0.2.3, ledger-bitcoin@^0.2.3: version "0.2.3" resolved "https://registry.npmjs.org/ledger-bitcoin/-/ledger-bitcoin-0.2.3.tgz" integrity sha512-sWdvMTR5CkebNlM0Mam9ROdpsD7Y4087kj4cbIaCCq8IXShCQ44vE3j0wTmt+sHp13eETgY63OWN1rkuIfMfuQ== @@ -7556,7 +7563,7 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -prompts@^2.4.2: +prompts@2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -7650,7 +7657,7 @@ radix3@^1.1.2: resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.2.tgz#fd27d2af3896c6bf4bcdfab6427c69c2afc69ec0" integrity sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA== -ramda@^0.30.1: +ramda@0.30.1, ramda@^0.30.1: version "0.30.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.30.1.tgz#7108ac95673062b060025052cd5143ae8fc605bf" integrity sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw== @@ -8123,7 +8130,7 @@ scrypt-js@3.0.1: resolved "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== -scure@^1.6.0: +scure@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/scure/-/scure-1.6.0.tgz#1d16ac36c0ae601860f28cc01c202fef796eb1da" integrity sha512-IiQqkgU1J7gqDPIjUd1EpPQ4HAyqRXFrva+/IMh0u6dHNVdzpoOS4NvehZi4HAM3Op8DzsAmbQSCskCDgffJcw== @@ -8568,7 +8575,7 @@ tiny-secp256k1@1.1.6, tiny-secp256k1@^1.1.1, tiny-secp256k1@^1.1.3: elliptic "^6.4.0" nan "^2.13.2" -tiny-secp256k1@^2.2.3: +tiny-secp256k1@2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz#fe1dde11a64fcee2091157d4b78bcb300feb9b65" integrity sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q== @@ -9226,7 +9233,7 @@ xmlhttprequest-ssl@~2.0.0: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== -xrpl@^4.0.0: +xrpl@4.0.0, xrpl@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-4.0.0.tgz#c031b848c2a3e955b69b1dd438b1156e6826a2d6" integrity sha512-VZm1lQWHQ6PheAAFGdH+ISXKvqB2hZDQ0w4ZcdAEtmqZQXtSIVQHOKPz95rEgGANbos7+XClxJ73++joPhA8Cw== From c850a0773ea68a6d4f4a3b0f005dc67b0dabdccd Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 9 Oct 2024 10:45:22 +0200 Subject: [PATCH 05/39] feat: modfiy isConnected conditions --- public/images/logos/xpr-logo.svg | 17 ++++- src/app/components/account/account.tsx | 54 +++++++++------ .../account/components/account-menu.tsx | 13 ++-- src/app/components/header/header.tsx | 25 ++----- .../select-wallet-modal.tsx | 20 +++--- .../my-vaults-header/my-vaults-header.tsx | 8 +-- .../components/my-vaults/my-vaults-large.tsx | 24 +++---- .../network/components/networks-menu.tsx | 19 ++---- src/app/hooks/use-active-tabs.ts | 6 +- src/app/hooks/use-connected.ts | 24 +++++-- src/app/hooks/use-nfts.ts | 68 +++++++------------ src/app/hooks/use-vaults.ts | 5 -- src/app/pages/dashboard/dashboard.tsx | 22 +----- .../ripple-network-configuration.provider.tsx | 16 +++++ 14 files changed, 159 insertions(+), 162 deletions(-) diff --git a/public/images/logos/xpr-logo.svg b/public/images/logos/xpr-logo.svg index 9a2c7c63..b340e958 100644 --- a/public/images/logos/xpr-logo.svg +++ b/public/images/logos/xpr-logo.svg @@ -1 +1,16 @@ -x \ No newline at end of file + + + + + x + + + + + + + diff --git a/src/app/components/account/account.tsx b/src/app/components/account/account.tsx index b797fa10..5d4aa616 100644 --- a/src/app/components/account/account.tsx +++ b/src/app/components/account/account.tsx @@ -1,41 +1,57 @@ -import { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { Button, HStack } from '@chakra-ui/react'; import { AccountMenu } from '@components/account/components/account-menu'; +import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; +import { useNetworkConnection } from '@hooks/use-connected'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; -import { useAccount, useDisconnect } from 'wagmi'; +import { Connector, useAccount, useDisconnect } from 'wagmi'; export function Account(): React.JSX.Element { const dispatch = useDispatch(); - const [isConnected, setIsConnected] = useState(false); - const { address, connector, isConnected: isEthereumWalletConnected } = useAccount(); - const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( + const { isConnected } = useNetworkConnection(); + const { networkType } = useContext(NetworkConfigurationContext); + + const { address: ethereumUserAddress, connector: ethereumWallet } = useAccount(); + const { disconnect: disconnectEthereumWallet } = useDisconnect(); + const { setIsRippleWalletConnected, rippleUserAddress, rippleWallet } = useContext( RippleNetworkConfigurationContext ); - const { networkType } = useContext(NetworkConfigurationContext); - useEffect(() => { - if (networkType === 'evm') { - setIsConnected(isEthereumWalletConnected); - } else { - setIsConnected(isRippleWalletConnected); + + function getWalletInformation(): + | { address: string; wallet: RippleWallet | Connector } + | undefined { + switch (networkType) { + case 'evm': + if (!ethereumUserAddress || !ethereumWallet) return undefined; + return { address: ethereumUserAddress, wallet: ethereumWallet }; + case 'xrpl': + if (!rippleUserAddress || !rippleWallet) return undefined; + return { address: rippleUserAddress, wallet: rippleWallet }; + default: + throw new Error('Invalid Network Type'); } - }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); - const { disconnect } = useDisconnect(); + } function onConnectWalletClick(): void { dispatch(modalActions.toggleSelectWalletModalVisibility()); } function onDisconnectWalletClick(): void { - if (networkType === 'evm') { - disconnect(); - } else { - setIsRippleWalletConnected(false); + switch (networkType) { + case 'evm': + disconnectEthereumWallet(); + break; + case 'xrpl': + setIsRippleWalletConnected(false); + break; + default: + break; } dispatch(mintUnmintActions.resetMintUnmintState()); } @@ -44,8 +60,8 @@ export function Account(): React.JSX.Element { {isConnected ? ( onDisconnectWalletClick()} /> ) : ( diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index 45b26fcf..e79af9ca 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -1,3 +1,5 @@ +import { useContext } from 'react'; + import { ChevronDownIcon } from '@chakra-ui/icons'; import { HStack, @@ -9,18 +11,19 @@ import { Text, useBreakpointValue, } from '@chakra-ui/react'; +import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; import { truncateAddress } from 'dlc-btc-lib/utilities'; import { Connector } from 'wagmi'; interface AccountMenuProps { address?: string; - wagmiConnector?: Connector; + wallet?: Connector | RippleWallet; handleDisconnectWallet: () => void; } export function AccountMenu({ address, - wagmiConnector, + wallet, handleDisconnectWallet, }: AccountMenuProps): React.JSX.Element | false { const isMobile = useBreakpointValue({ base: true, md: false }); @@ -35,11 +38,11 @@ export function AccountMenu({ {wagmiConnector.name} {truncateAddress(address)} diff --git a/src/app/components/header/header.tsx b/src/app/components/header/header.tsx index 1f8f0161..8425a362 100644 --- a/src/app/components/header/header.tsx +++ b/src/app/components/header/header.tsx @@ -6,9 +6,8 @@ import { Account } from '@components/account/account'; import { CompanyWebsiteButton } from '@components/company-website-button/company-website-button'; import { HeaderLayout } from '@components/header/components/header.layout'; import { NetworkBox } from '@components/network/network'; +import { useNetworkConnection } from '@hooks/use-connected'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; -import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; -import { chain } from 'ramda'; import { useAccount } from 'wagmi'; import { Banner } from './components/banner'; @@ -17,38 +16,26 @@ import { NavigationTabs } from './components/tabs'; export function Header(): React.JSX.Element { const navigate = useNavigate(); const location = useLocation(); - const [isConnected, setIsConnected] = useState(false); - const { address, connector, isConnected: isEthereumWalletConnected } = useAccount(); - const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( - RippleNetworkConfigurationContext - ); + const { isConnected } = useNetworkConnection(); + const { networkType } = useContext(NetworkConfigurationContext); - useEffect(() => { - if (networkType === 'evm') { - setIsConnected(isEthereumWalletConnected); - } else { - setIsConnected(isRippleWalletConnected); - } - }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); + const { chain: ethereumNetwork } = useAccount(); const [showBanner, setShowBanner] = useState(false); const [isNetworkMenuOpen, setIsNetworkMenuOpen] = useState(false); - console.log('isNetworkMenuOpen', isNetworkMenuOpen); - const handleTabClick = (route: string) => { navigate(route); }; useEffect(() => { - if (networkType === 'evm' && isEthereumWalletConnected && !chain) { + if (networkType === 'evm' && isConnected && !ethereumNetwork) { setShowBanner(true); } else { setShowBanner(false); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chain]); + }, [isConnected, ethereumNetwork]); return ( 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 bb8c412d..54f983a4 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 @@ -4,7 +4,6 @@ import { CheckIcon } from '@chakra-ui/icons'; import { HStack, ScaleFade, Tab, TabList, Tabs, Text, VStack } from '@chakra-ui/react'; import { ModalComponentProps } from '@components/modals/components/modal-container'; import { ModalLayout } from '@components/modals/components/modal.layout'; -import { SelectWalletMenu } from '@components/modals/select-wallet-modal/components/select-wallet-menu'; import { SelectNetworkButton } from '@components/select-network-button/select-network-button'; import { RippleNetworkID } from '@models/ripple.models'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; @@ -35,9 +34,12 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): const { chains: ethereumNetworks } = useConfig(); const { setNetworkType } = useContext(NetworkConfigurationContext); - const { enabledRippleNetworks, setIsRippleWalletConnected } = useContext( - RippleNetworkConfigurationContext - ); + const { + enabledRippleNetworks, + isRippleWalletConnected, + setIsRippleWalletConnected, + setRippleWallet, + } = useContext(RippleNetworkConfigurationContext); const ethereumNetworkIDs = ethereumNetworks.map( ethereumNetwork => ethereumNetwork.id.toString() as EthereumNetworkID @@ -57,17 +59,18 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): const networkTypes = ['evm', 'xrpl']; useEffect(() => { - if (isSuccess) { + if (isSuccess || isRippleWalletConnected) { void handleCloseAfterSuccess(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSuccess]); + }, [isSuccess, isRippleWalletConnected]); async function handleCloseAfterSuccess() { await delay(1000); setSelectedNetworkID(undefined); setSelectedWagmiConnectorID(undefined); - if (selectedWagmiConnectorID && selectedWagmiConnectorID !== 'walletConnect') handleClose(); + if (selectedWagmiConnectorID !== 'walletConnect') handleClose(); + setSelectedNetworkType('evm'); } async function handleConnectEthereumWallet(wagmiConnector: Connector) { @@ -81,10 +84,9 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): } async function handleConnectRippleWallet(id: string) { - console.log('handleConnectRippleWallet', id); setNetworkType('xrpl'); + setRippleWallet(seedWallet); setIsRippleWalletConnected(true); - handleClose(); } const handleChangeNetwork = (networkID: EthereumNetworkID | RippleNetworkID) => { diff --git a/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx b/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx index cf0b1f83..6c6d5b6b 100644 --- a/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx +++ b/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx @@ -3,13 +3,13 @@ import { Divider, HStack, Text, VStack } from '@chakra-ui/react'; import { MyVaultsHeaderBalanceInfo } from './components/my-vaults-header-balance-info'; interface MyVaultsLargeHeaderProps { - address?: string; + isConnected: boolean; dlcBTCBalance?: number; lockedBTCBalance?: number; } export function MyVaultsLargeHeader({ - address, + isConnected, dlcBTCBalance, lockedBTCBalance, }: MyVaultsLargeHeaderProps): React.JSX.Element { @@ -26,7 +26,7 @@ export function MyVaultsLargeHeader({ imageSrc={'/images/logos/dlc-btc-logo.svg'} altText={'dlcBTC Logo'} assetAmount={dlcBTCBalance} - showNone={!address} + showNone={!isConnected} /> diff --git a/src/app/components/my-vaults/my-vaults-large.tsx b/src/app/components/my-vaults/my-vaults-large.tsx index ec528f26..76ef0af0 100644 --- a/src/app/components/my-vaults/my-vaults-large.tsx +++ b/src/app/components/my-vaults/my-vaults-large.tsx @@ -4,34 +4,34 @@ import { HStack } from '@chakra-ui/react'; import { VaultsListGroupBlankContainer } from '@components/vaults-list/components/vaults-list-group-blank-container'; import { VaultsListGroupContainer } from '@components/vaults-list/components/vaults-list-group-container'; import { VaultsList } from '@components/vaults-list/vaults-list'; +import { useNetworkConnection } from '@hooks/use-connected'; import { BalanceContext } from '@providers/balance-context-provider'; import { VaultContext } from '@providers/vault-context-provider'; -import { useAccount } from 'wagmi'; import { MyVaultsLargeHeader } from './components/my-vaults-header/my-vaults-header'; import { MyVaultsLargeLayout } from './components/my-vaults-large.layout'; import { MyVaultsSetupInformationStack } from './components/my-vaults-setup-information-stack'; export function MyVaultsLarge(): React.JSX.Element { - const { address } = useAccount(); + const { isConnected } = useNetworkConnection(); const { dlcBTCBalance, lockedBTCBalance } = useContext(BalanceContext); - const vaultContext = useContext(VaultContext); - const { allVaults, readyVaults, fundedVaults, closingVaults, closedVaults, pendingVaults } = - vaultContext; + + const { readyVaults, pendingVaults, fundedVaults, closingVaults, closedVaults, allVaults } = + useContext(VaultContext); return ( - {address ? ( + {isConnected ? ( 0} + isScrollable={!isConnected && allVaults.length > 0} > @@ -43,9 +43,9 @@ export function MyVaultsLarge(): React.JSX.Element { 0} + isScrollable={!isConnected && fundedVaults.length > 0} > - {address ? ( + {isConnected ? ( ) : ( @@ -54,9 +54,9 @@ export function MyVaultsLarge(): React.JSX.Element { 0} + isScrollable={!isConnected && closedVaults.length > 0} > - {address ? ( + {isConnected ? ( ) : ( diff --git a/src/app/components/network/components/networks-menu.tsx b/src/app/components/network/components/networks-menu.tsx index 1f468e6e..dad2339b 100644 --- a/src/app/components/network/components/networks-menu.tsx +++ b/src/app/components/network/components/networks-menu.tsx @@ -1,6 +1,7 @@ import { useContext, useEffect, useState } from 'react'; import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; +import { useNetworkConnection } from '@hooks/use-connected'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { useAccount, useConfig, useSwitchChain } from 'wagmi'; @@ -15,19 +16,11 @@ export function NetworksMenu({ setIsMenuOpen, }: NetworksMenuProps): React.JSX.Element | null { const { chains } = useConfig(); - const [isConnected, setIsConnected] = useState(false); - const { address, chain, isConnected: isEthereumWalletConnected } = useAccount(); - const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( - RippleNetworkConfigurationContext - ); + const { isConnected } = useNetworkConnection(); + const { chain: ethereumNetwork } = useAccount(); + const { networkType } = useContext(NetworkConfigurationContext); - useEffect(() => { - if (networkType === 'evm') { - setIsConnected(isEthereumWalletConnected); - } else { - setIsConnected(isRippleWalletConnected); - } - }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); + const { switchChain } = useSwitchChain(); if (!isConnected || networkType === 'xrpl') { @@ -38,7 +31,7 @@ export function NetworksMenu({ setIsMenuOpen(!isMenuOpen)}> - {chain ? chain?.name : 'Not Connected'} + {ethereumNetwork ? ethereumNetwork?.name : 'Not Connected'} diff --git a/src/app/hooks/use-active-tabs.ts b/src/app/hooks/use-active-tabs.ts index 0633ba7f..31266a47 100644 --- a/src/app/hooks/use-active-tabs.ts +++ b/src/app/hooks/use-active-tabs.ts @@ -25,18 +25,16 @@ export function useActiveTabs(): UseActiveTabsReturnType { const { networkType } = useContext(NetworkConfigurationContext); async function shouldActivateTabs(): Promise { - console.log('networkType', networkType); if (networkType === 'evm') { if (!address || !chain) { - navigate('/'); + navigate('/mint-withdraw'); return false; } const dlcManagerContract = ethereumNetworkConfiguration.dlcManagerContract; if (!(await isWhitelistingEnabled(dlcManagerContract))) return true; return await isUserWhitelisted(dlcManagerContract, address); } else { - console.log('isRippleWalletConnected', isRippleWalletConnected); - navigate('/'); + navigate('/mint-withdraw'); return isRippleWalletConnected; } } diff --git a/src/app/hooks/use-connected.ts b/src/app/hooks/use-connected.ts index e46208ae..0620e33d 100644 --- a/src/app/hooks/use-connected.ts +++ b/src/app/hooks/use-connected.ts @@ -4,20 +4,30 @@ import { NetworkConfigurationContext } from '@providers/network-configuration.pr import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { useAccount } from 'wagmi'; -export function useConnected(): boolean { +interface UseNetworkConnectionReturnType { + isConnected: boolean; +} + +export function useNetworkConnection(): UseNetworkConnectionReturnType { const [isConnected, setIsConnected] = useState(false); + const { networkType } = useContext(NetworkConfigurationContext); + const { isConnected: isEthereumWalletConnected } = useAccount(); const { isRippleWalletConnected } = useContext(RippleNetworkConfigurationContext); - const { networkType } = useContext(NetworkConfigurationContext); useEffect(() => { - if (networkType === 'evm') { - setIsConnected(isEthereumWalletConnected); - } else { - setIsConnected(isRippleWalletConnected); + switch (networkType) { + case 'evm': + setIsConnected(isEthereumWalletConnected); + break; + case 'xrpl': + setIsConnected(isRippleWalletConnected); + break; + default: + setIsConnected(false); } }, [networkType, isRippleWalletConnected, isEthereumWalletConnected]); - return isConnected; + return { isConnected }; } diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts index aa4cba1c..0bf4c4c0 100644 --- a/src/app/hooks/use-nfts.ts +++ b/src/app/hooks/use-nfts.ts @@ -10,7 +10,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import Decimal from 'decimal.js'; import { RippleHandler } from 'dlc-btc-lib'; import { VaultState } from 'dlc-btc-lib/models'; -import { isEmpty, set } from 'ramda'; +import { isEmpty } from 'ramda'; interface useNFTsReturnType { allVaults: Vault[]; @@ -22,6 +22,15 @@ interface useNFTsReturnType { isLoading: boolean; } +enum VaultAction { + DEPOSIT = 'deposit', + WITHDRAW = 'withdraw', + DEPOSIT_PENDING = 'depositPending', + WITHDRAW_PENDING = 'withdrawPending', + DEPOSIT_SUCCESS = 'depositSuccess', + WITHDRAW_SUCCESS = 'withdrawSuccess', +} + export function useNFTs(): useNFTsReturnType { const queryClient = useQueryClient(); const dispatch = useDispatch(); @@ -30,29 +39,15 @@ export function useNFTs(): useNFTsReturnType { const { networkType } = useContext(NetworkConfigurationContext); const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - const [dispatchTuple, setDispatchTuple] = useState< - [ - string, - ( - | 'deposit' - | 'withdraw' - | 'depositPending' - | 'withdrawPending' - | 'depositSuccess' - | 'withdrawSuccess' - ), - number?, - ] - >(['', 'deposit']); + const [dispatchTuple, setDispatchTuple] = useState<[string, VaultAction, number?]>([ + '', + VaultAction.DEPOSIT, + ]); async function fetchXRPLVaults(): Promise { setIsLoading(true); - console.log('xrplHandler', xrplHandler); - console.log('Fetching XRPL Vaults'); let xrplRawVaults: any[] = []; - console.log('xrpl vaults', xrplRawVaults); - const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); try { @@ -61,7 +56,6 @@ export function useNFTs(): useNFTsReturnType { console.error('Error fetching XRPL Vaults', error); return previousVaults ?? []; } - console.log('Previous XRPL Vaults', previousVaults); const xrplVaults = xrplRawVaults.map(vault => { return formatVault(vault); @@ -77,17 +71,8 @@ export function useNFTs(): useNFTsReturnType { } } - // Update the xrplVaults state first - console.log( - 'Setting XRPL Vaults', - xrplVaults.filter( - vault => vault.uuid !== '0x0000000000000000000000000000000000000000000000000000000000000000' - ) - ); queryClient.setQueryData(['xrpl-vaults'], xrplVaults); - // await delay(5000); - if (previousVaults && previousVaults.length > 0) { const newVaults = xrplVaults.filter((vault: Vault) => { return !previousVaults.some( @@ -98,7 +83,6 @@ export function useNFTs(): useNFTsReturnType { previousVault.valueMinted === vault.valueMinted ); }); - console.log('New XRPL Vaults', newVaults); newVaults.forEach((vault: Vault) => { const previousVault = previousVaults.find( @@ -106,10 +90,9 @@ export function useNFTs(): useNFTsReturnType { ); if (!previousVault) { - console.log('New XRPL Vault', vault); - if (vault.uuid === '0x0000000000000000000000000000000000000000000000000000000000000000') - return; - setDispatchTuple([vault.uuid, 'deposit']); + if (vault.uuid !== '0x0000000000000000000000000000000000000000000000000000000000000000') { + setDispatchTuple([vault.uuid, VaultAction.DEPOSIT]); + } return; } @@ -117,28 +100,24 @@ export function useNFTs(): useNFTsReturnType { switch (vault.state) { case VaultState.FUNDED: if (previousVault.valueMinted < previousVault.valueLocked) { - console.log('XRPL Vault Withdraw Success'); setDispatchTuple([ vault.uuid, - 'withdrawSuccess', + VaultAction.WITHDRAW_SUCCESS, new Decimal(previousVault.valueLocked).minus(vault.valueLocked).toNumber(), ]); } else { - console.log('XRPL Vault Deposit Success'); setDispatchTuple([ vault.uuid, - 'depositSuccess', + VaultAction.DEPOSIT_SUCCESS, new Decimal(vault.valueLocked).minus(previousVault.valueLocked).toNumber(), ]); } break; case VaultState.PENDING: if (vault.valueLocked !== vault.valueMinted) { - console.log('XRPL Vault Withdraw Pending'); - setDispatchTuple([vault.uuid, 'withdrawPending']); + setDispatchTuple([vault.uuid, VaultAction.WITHDRAW_PENDING]); } else { - console.log('XRPL Vault Deposit Pending'); - setDispatchTuple([vault.uuid, 'depositPending']); + setDispatchTuple([vault.uuid, VaultAction.DEPOSIT_PENDING]); } break; } @@ -146,8 +125,7 @@ export function useNFTs(): useNFTsReturnType { } if (previousVault.valueMinted !== vault.valueMinted) { - console.log('XRPL Vault Withdraw'); - setDispatchTuple([vault.uuid, 'withdraw']); + setDispatchTuple([vault.uuid, VaultAction.WITHDRAW]); return; } }); @@ -161,7 +139,7 @@ export function useNFTs(): useNFTsReturnType { queryKey: ['xrpl-vaults'], initialData: [], queryFn: fetchXRPLVaults, - refetchInterval: 20000, + refetchInterval: 10000, enabled: networkType === 'xrpl', }); diff --git a/src/app/hooks/use-vaults.ts b/src/app/hooks/use-vaults.ts index 9730b813..b7fcb624 100644 --- a/src/app/hooks/use-vaults.ts +++ b/src/app/hooks/use-vaults.ts @@ -27,10 +27,6 @@ export function useVaults(): UseVaultsReturnType { const { vaults } = useSelector((state: RootState) => state.vault); - useEffect(() => { - console.log('stored vaults', vaults); - }, [vaults]); - const [isLoading, setIsLoading] = useState(true); const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( @@ -42,7 +38,6 @@ export function useVaults(): UseVaultsReturnType { await getAllAddressVaults(ethereumNetworkConfiguration.dlcManagerContract, ethereumAddress) .then(vaults => { - console.log('vaults', vaults); const formattedVaults = vaults.map(vault => { return formatVault(vault); }); diff --git a/src/app/pages/dashboard/dashboard.tsx b/src/app/pages/dashboard/dashboard.tsx index b37a66c7..71f46300 100644 --- a/src/app/pages/dashboard/dashboard.tsx +++ b/src/app/pages/dashboard/dashboard.tsx @@ -1,29 +1,13 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React from 'react'; import { LandingPage } from '@components/mint-unmint/components/landing-page/landing-page'; import { MintUnmint } from '@components/mint-unmint/mint-unmint'; import { MyVaultsSmall } from '@components/my-vaults-small/my-vaults-small'; +import { useNetworkConnection } from '@hooks/use-connected'; import { PageLayout } from '@pages/components/page.layout'; -import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; -import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; -import { useAccount } from 'wagmi'; export function Dashboard(): React.JSX.Element { - const [isConnected, setIsConnected] = useState(true); - const { address, connector, isConnected: isEthereumWalletConnected } = useAccount(); - const { isRippleWalletConnected, setIsRippleWalletConnected } = useContext( - RippleNetworkConfigurationContext - ); - const { networkType } = useContext(NetworkConfigurationContext); - useEffect(() => { - console.log('changing network type', networkType); - if (networkType === 'evm') { - setIsConnected(isEthereumWalletConnected); - } else { - console.log('connected', isRippleWalletConnected); - setIsConnected(isRippleWalletConnected); - } - }, [isEthereumWalletConnected, isRippleWalletConnected, networkType]); + const { isConnected } = useNetworkConnection(); return ( diff --git a/src/app/providers/ripple-network-configuration.provider.tsx b/src/app/providers/ripple-network-configuration.provider.tsx index 96e10ab5..e7e4e7d9 100644 --- a/src/app/providers/ripple-network-configuration.provider.tsx +++ b/src/app/providers/ripple-network-configuration.provider.tsx @@ -1,5 +1,6 @@ import React, { createContext, useEffect, useState } from 'react'; +import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; import { getRippleNetworkByID } from '@functions/configuration.functions'; import { HasChildren } from '@models/has-children'; import { RippleNetwork, RippleNetworkConfiguration, RippleNetworkID } from '@models/ripple.models'; @@ -52,7 +53,10 @@ interface RippleNetworkConfigurationContext { rippleNetworkConfiguration: RippleNetworkConfiguration; setRippleNetworkID: (rippleNetworkID: RippleNetworkID) => void; enabledRippleNetworks: RippleNetwork[]; + rippleUserAddress: string | undefined; isRippleWalletConnected: boolean; + rippleWallet: RippleWallet | undefined; + setRippleWallet: (rippleWallet: RippleWallet) => void; setIsRippleWalletConnected: (isConnected: boolean) => void; isRippleNetworkConfigurationLoading: boolean; } @@ -60,7 +64,10 @@ export const RippleNetworkConfigurationContext = createContext {}, rippleNetworkConfiguration: defaultRippleNetworkConfiguration, enabledRippleNetworks, + rippleUserAddress: undefined, + rippleWallet: undefined, isRippleWalletConnected: false, + setRippleWallet: () => {}, setIsRippleWalletConnected: () => {}, isRippleNetworkConfigurationLoading: false, }); @@ -72,6 +79,12 @@ export function RippleNetworkConfigurationContextProvider({ const [rippleNetworkConfiguration, setRippleNetworkConfiguration] = useState(defaultRippleNetworkConfiguration); + const [rippleUserAddress, setRippleUserAddress] = useState( + 'rfvtbrXSxLsxVWDktR4sdzjJgv8EnMKFKG' + ); + + const [rippleWallet, setRippleWallet] = useState(undefined); + const [isRippleNetworkConfigurationLoading, setIsRippleNetworkConfigurationLoading] = useState(false); @@ -94,6 +107,9 @@ export function RippleNetworkConfigurationContextProvider({ rippleNetworkConfiguration, isRippleNetworkConfigurationLoading, enabledRippleNetworks, + rippleUserAddress, + rippleWallet, + setRippleWallet, isRippleWalletConnected, setIsRippleWalletConnected, setRippleNetworkID, From e3c6075e4234fd601eb8c683176c70079fb1409a Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 10 Oct 2024 15:50:31 +0200 Subject: [PATCH 06/39] wip: modify flow --- config.devnet.json | 1 + .../fetch-attestor-group-public-key.ts | 42 +++++++++++ .../functions/submit-xrpl-vault-request.ts | 51 +++++++++++++ package.json | 2 +- .../burn-transaction-screen.tsx | 73 ++++++++++++++----- .../deposit-transaction-screen.tsx | 5 -- .../setup-vault-screen/setup-vault-screen.tsx | 24 +++++- .../functions/attestor-request.functions.ts | 31 ++++++++ src/app/functions/configuration.functions.ts | 42 ++++++----- src/shared/models/configuration.ts | 1 + yarn.lock | 12 ++- 11 files changed, 227 insertions(+), 57 deletions(-) create mode 100644 netlify/functions/fetch-attestor-group-public-key.ts create mode 100644 netlify/functions/submit-xrpl-vault-request.ts create mode 100644 src/app/functions/attestor-request.functions.ts diff --git a/config.devnet.json b/config.devnet.json index 1b1f0317..295ed3d7 100644 --- a/config.devnet.json +++ b/config.devnet.json @@ -9,6 +9,7 @@ "bitcoinBlockchainURL": "https://devnet.dlc.link/electrs", "bitcoinBlockchainExplorerURL": "https://devnet.dlc.link/electrs", "bitcoinBlockchainFeeEstimateURL": "https://devnet.dlc.link/electrs/fee-estimates", + "rippleIssuerAddress": "ra9epzthPkNXykgfadCwu8D7mtajj8DVCP", "ledgerApp": "Bitcoin Test", "merchants": [ { diff --git a/netlify/functions/fetch-attestor-group-public-key.ts b/netlify/functions/fetch-attestor-group-public-key.ts new file mode 100644 index 00000000..ad9b605f --- /dev/null +++ b/netlify/functions/fetch-attestor-group-public-key.ts @@ -0,0 +1,42 @@ +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/netlify/functions/submit-xrpl-vault-request.ts b/netlify/functions/submit-xrpl-vault-request.ts new file mode 100644 index 00000000..ee4053e8 --- /dev/null +++ b/netlify/functions/submit-xrpl-vault-request.ts @@ -0,0 +1,51 @@ +import { Handler, HandlerEvent } from '@netlify/functions'; +import { submitSetupXRPLVaultRequest } 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', + }), + }; + } + + if (!event.queryStringParameters.userXRPLAddress) { + return { + statusCode: 400, + body: JSON.stringify({ + message: 'No XRPL User Address was provided', + }), + }; + } + + const coordinatorURL = event.queryStringParameters.coordinatorURL; + const userXRPLAddress = event.queryStringParameters.userXRPLAddress; + + await submitSetupXRPLVaultRequest(coordinatorURL, userXRPLAddress); + + return { + statusCode: 200, + }; + } catch (error: any) { + return { + statusCode: 500, + body: JSON.stringify({ + message: `Failed to submit Setup XRPL Vault Request: ${error.message}`, + }), + }; + } +}; + +export { handler }; diff --git a/package.json b/package.json index dd80f0e9..79bc3bb3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "concurrently": "^8.2.2", "d3": "^7.9.0", "decimal.js": "^10.4.3", - "dlc-btc-lib": "^2.2.10", + "dlc-btc-lib": "file:../dlc-btc-lib", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", 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 7cde70ee..61a1c692 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 @@ -4,12 +4,20 @@ import { useDispatch, useSelector } from 'react-redux'; 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 { getAndFormatVault } from '@functions/vault.functions'; 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 { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import { RippleHandler } from 'dlc-btc-lib'; +import { vaultActions } from '@store/slices/vault/vault.actions'; +import { withdraw } from 'dlc-btc-lib/ethereum-functions'; +import { EthereumNetworkID } from 'dlc-btc-lib/models'; +import { createCheck, getRippleClient, getRippleWallet } from 'dlc-btc-lib/ripple-functions'; import { shiftValue } from 'dlc-btc-lib/utilities'; +import { useAccount } from 'wagmi'; interface BurnTokenTransactionFormProps { isBitcoinWalletLoading: [boolean, string]; @@ -27,39 +35,64 @@ export function BurnTokenTransactionForm({ const toast = useToast(); const dispatch = useDispatch(); + const { networkType } = useContext(NetworkConfigurationContext); const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); + const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); + + const { chainId } = useAccount(); + + const signer = useEthersSigner(); + const { unmintStep } = useSelector((state: RootState) => state.mintunmint); const [isSubmitting, setIsSubmitting] = useState(false); const currentVault = unmintStep[2]; async function handleButtonClick(withdrawAmount: number): Promise { - if (!currentVault) return; try { + if (!currentVault) return; setIsSubmitting(true); - const currentRisk = await fetchUserEthereumAddressRiskLevel(); - if (currentRisk === 'High') throw new Error('Risk Level is too high'); - const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); + if (networkType === 'xrpl') { + const xrplWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); + const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); + await createCheck( + xrplClient, + xrplWallet, + appConfiguration.rippleIssuerAddress, + undefined, + withdrawAmount.toString(), + currentVault.uuid + ); + } else if (networkType === 'evm') { + const currentRisk = await fetchUserEthereumAddressRiskLevel(); + if (currentRisk === 'High') throw new Error('Risk Level is too high'); + const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - await xrplHandler.createCheck(formattedWithdrawAmount.toString(), currentVault.uuid); + await withdraw( + ethereumNetworkConfiguration.dlcManagerContract.connect(signer!), + currentVault.uuid, + formattedWithdrawAmount + ); - // const updatedVault = await getAndFormatVault( - // currentVault.uuid, - // ethereumNetworkConfiguration.dlcManagerContract - // ); - // dispatch( - // vaultActions.swapVault({ - // vaultUUID: currentVault.uuid, - // updatedVault: updatedVault, - // networkID: chainId?.toString() as EthereumNetworkID, - // }) - // ); - // dispatch(mintUnmintActions.setUnmintStep([1, currentVault.uuid])); - // setIsSubmitting(false); + const updatedVault = await getAndFormatVault( + currentVault.uuid, + ethereumNetworkConfiguration.dlcManagerContract + ); + dispatch( + vaultActions.swapVault({ + vaultUUID: currentVault.uuid, + updatedVault: updatedVault, + networkID: chainId?.toString() as EthereumNetworkID, + }) + ); + dispatch(mintUnmintActions.setUnmintStep([1, currentVault.uuid])); + } else { + throw new Error('Unsupported Network Type'); + } + setIsSubmitting(false); } catch (error) { setIsSubmitting(false); toast({ 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 c14effc9..8eb7eb55 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 @@ -35,16 +35,11 @@ export function DepositTransactionScreen({ const { bitcoinWalletContextState, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); - const { allVaults } = useContext(VaultContext); const { mintStep } = useSelector((state: RootState) => state.mintunmint); - console.log('allVaultsDEPOSIT', allVaults); - const currentVault = mintStep[2]; - console.log('currentVaultDEPOSIT', currentVault); - const [isSubmitting, setIsSubmitting] = useState(false); async function handleDeposit(depositAmount: number) { diff --git a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx index 44650f9a..c3275fdd 100644 --- a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx +++ b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx @@ -1,20 +1,36 @@ -import { useState } from 'react'; +import { useContext, useState } from 'react'; import { Button, VStack, useToast } from '@chakra-ui/react'; -import { RippleHandler } from 'dlc-btc-lib'; +import { submitSetupXRPLVaultRequest } from '@functions/attestor-request.functions'; +import { useEthersSigner } from '@functions/configuration.functions'; +import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { setupVault } from 'dlc-btc-lib/ethereum-functions'; +import { getRippleWallet } from 'dlc-btc-lib/ripple-functions'; import { SetupVaultScreenVaultGraphics } from './components/setup-vault-screen.vault-graphics'; export function SetupVaultScreen(): React.JSX.Element { const toast = useToast(); + const { networkType } = useContext(NetworkConfigurationContext); + + const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); + + const signer = useEthersSigner(); const [isSubmitting, setIsSubmitting] = useState(false); async function handleSetup() { try { setIsSubmitting(true); - const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - await xrplHandler.setupVault(''); + if (networkType === 'xrpl') { + const xrplWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); + await submitSetupXRPLVaultRequest(xrplWallet.classicAddress); + } else if (networkType === 'evm') { + await setupVault(ethereumNetworkConfiguration.dlcManagerContract.connect(signer!)); + } else { + throw new Error('Unsupported Network Type'); + } } catch (error: any) { setIsSubmitting(false); toast({ diff --git a/src/app/functions/attestor-request.functions.ts b/src/app/functions/attestor-request.functions.ts new file mode 100644 index 00000000..3ca4e673 --- /dev/null +++ b/src/app/functions/attestor-request.functions.ts @@ -0,0 +1,31 @@ +export async function getAttestorExtendedGroupPublicKey(): Promise { + 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}`); + } +} + +export async function submitSetupXRPLVaultRequest(userAddress: string): Promise { + try { + const netlifyFunctionEndpoint = `/.netlify/functions/submit-xrpl-vault-request?coordinatorURL=${appConfiguration.coordinatorURL}&userXRPLAddress=${userAddress}`; + + const response = await fetch(netlifyFunctionEndpoint); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(`HTTP Error: ${errorMessage}`); + } + } catch (error: any) { + throw new Error(`Failed to get Attestor Group Public Key: ${error.message}`); + } +} diff --git a/src/app/functions/configuration.functions.ts b/src/app/functions/configuration.functions.ts index 8f9e4a56..263dc1f7 100644 --- a/src/app/functions/configuration.functions.ts +++ b/src/app/functions/configuration.functions.ts @@ -1,11 +1,13 @@ +import { useMemo } from 'react'; + import { RippleNetwork, RippleNetworkID } from '@models/ripple.models'; import { supportedEthereumNetworks } from 'dlc-btc-lib/constants'; import { getEthereumContract, getProvider } from 'dlc-btc-lib/ethereum-functions'; import { EthereumDeploymentPlan, EthereumNetwork, EthereumNetworkID } from 'dlc-btc-lib/models'; -import { Contract } from 'ethers'; +import { Contract, providers } from 'ethers'; import { filter, fromPairs, includes, map, pipe } from 'ramda'; -import { Chain, HttpTransport, http } from 'viem'; -import { Config, createConfig } from 'wagmi'; +import { Account, Chain, Client, HttpTransport, Transport, http } from 'viem'; +import { Config, createConfig, useConnectorClient } from 'wagmi'; import { walletConnect } from 'wagmi/connectors'; import { SUPPORTED_VIEM_CHAINS } from '@shared/constants/ethereum.constants'; @@ -82,20 +84,20 @@ export function getWagmiConfiguration(ethereumNetworkIDs: EthereumNetworkID[]): }); } -// function clientToSigner(client: Client): providers.JsonRpcSigner { -// const { account, chain, transport } = client; - -// const network = { -// chainId: chain.id, -// name: chain.name, -// ensAddress: chain.contracts?.ensRegistry?.address, -// }; -// return new providers.Web3Provider(transport, network).getSigner(account.address); -// } - -// export function useEthersSigner({ chainId }: { chainId?: number } = {}): -// | providers.JsonRpcSigner -// | undefined { -// const { data: client } = useConnectorClient({ chainId }); -// return useMemo(() => (client ? clientToSigner(client) : undefined), [client]); -// } +function clientToSigner(client: Client): providers.JsonRpcSigner { + const { account, chain, transport } = client; + + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + }; + return new providers.Web3Provider(transport, network).getSigner(account.address); +} + +export function useEthersSigner({ chainId }: { chainId?: number } = {}): + | providers.JsonRpcSigner + | undefined { + const { data: client } = useConnectorClient({ chainId }); + return useMemo(() => (client ? clientToSigner(client) : undefined), [client]); +} diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index 18bcca2e..643301db 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -39,6 +39,7 @@ export interface Configuration { bitcoinBlockchainURL: string; bitcoinBlockchainExplorerURL: string; bitcoinBlockchainFeeEstimateURL: string; + rippleIssuerAddress: string; ledgerApp: string; merchants: Merchant[]; protocols: Protocol[]; diff --git a/yarn.lock b/yarn.lock index 53d82aee..d9ac1d9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5182,10 +5182,8 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dlc-btc-lib@^2.2.10: - version "2.2.10" - resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.2.10.tgz#34bee9ee687c7ead3ec27498f1f80539003525a3" - integrity sha512-SzqnGptBUGexr5rbvc183sTY30SP0MUVX7wQAW0W5wJsuUvNgYfgboc+EC0fLxScwnm1H92mrvMWlovNu8d8iQ== +"dlc-btc-lib@file:../dlc-btc-lib": + version "2.3.0" dependencies: "@ledgerhq/hw-app-btc" "10.4.1" "@noble/hashes" "1.4.0" @@ -7109,9 +7107,9 @@ multiformats@^9.4.2: integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== nan@^2.13.2: - version "2.20.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" - integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + version "2.21.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.21.0.tgz#203ab765a02e6676c8cb92e1cad9503e7976d55b" + integrity sha512-MCpOGmdWvAOMi4RWnpxS5G24l7dVMtdSHtV87I3ltjaLdFOTO74HVJ+DfYiAXjxGKsYR/UCmm1rBwhMN7KqS1A== nanoid@^3.3.7: version "3.3.7" From e324babcb8bda753c848a589c120a685ef0d9b08 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 11 Oct 2024 12:36:18 +0200 Subject: [PATCH 07/39] feat: remove ethereum observer, modify vault fetching --- config.devnet.json | 3 +- src/app/app.tsx | 46 ++-- .../burn-transaction-screen.tsx | 31 +-- .../deposit-transaction-screen.tsx | 8 +- ...on-screen.transaction-form.field-input.tsx | 2 +- src/app/hooks/use-ethereum-observer.ts | 149 ----------- src/app/hooks/use-evm-vaults.ts | 193 ++++++++++++++ src/app/hooks/use-nfts.ts | 244 ------------------ src/app/hooks/use-psbt.ts | 73 ++++-- src/app/hooks/use-vaults.ts | 127 --------- src/app/hooks/use-xrpl-vaults.ts | 192 ++++++++++++++ .../providers/ethereum-observer-provider.tsx | 8 - src/app/providers/vault-context-provider.tsx | 12 +- src/app/store/index.ts | 3 - src/app/store/slices/vault/vault.actions.ts | 3 - src/app/store/slices/vault/vault.slice.ts | 55 ---- src/shared/models/configuration.ts | 1 + 17 files changed, 488 insertions(+), 662 deletions(-) delete mode 100644 src/app/hooks/use-ethereum-observer.ts create mode 100644 src/app/hooks/use-evm-vaults.ts delete mode 100644 src/app/hooks/use-nfts.ts delete mode 100644 src/app/hooks/use-vaults.ts create mode 100644 src/app/hooks/use-xrpl-vaults.ts delete mode 100644 src/app/providers/ethereum-observer-provider.tsx delete mode 100644 src/app/store/slices/vault/vault.actions.ts delete mode 100644 src/app/store/slices/vault/vault.slice.ts diff --git a/config.devnet.json b/config.devnet.json index 295ed3d7..01ce4260 100644 --- a/config.devnet.json +++ b/config.devnet.json @@ -1,6 +1,6 @@ { "appEnvironment": "devnet", - "coordinatorURL": "http://localhost:8811", + "coordinatorURL": "https://devnet-ripple.dlc.link/attestor-1", "enabledEthereumNetworkIDs": ["421614", "84532", "11155111"], "enabledRippleNetworkIDs": ["1"], "bitcoinNetwork": "regtest", @@ -10,6 +10,7 @@ "bitcoinBlockchainExplorerURL": "https://devnet.dlc.link/electrs", "bitcoinBlockchainFeeEstimateURL": "https://devnet.dlc.link/electrs/fee-estimates", "rippleIssuerAddress": "ra9epzthPkNXykgfadCwu8D7mtajj8DVCP", + "xrplWebsocket": "wss://s.altnet.rippletest.net:51233", "ledgerApp": "Bitcoin Test", "merchants": [ { diff --git a/src/app/app.tsx b/src/app/app.tsx index 93ab9361..e007f2fd 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -12,13 +12,9 @@ import { BalanceContextProvider } from '@providers/balance-context-provider'; import { BitcoinTransactionConfirmationsProvider } from '@providers/bitcoin-query-provider'; import { BitcoinWalletContextProvider } from '@providers/bitcoin-wallet-context-provider'; import { EthereumNetworkConfigurationContextProvider } from '@providers/ethereum-network-configuration.provider'; -import { EthereumObserverProvider } from '@providers/ethereum-observer-provider'; import { NetworkConfigurationContextProvider } from '@providers/network-configuration.provider'; import { ProofOfReserveContextProvider } from '@providers/proof-of-reserve-context-provider'; -import { - RippleNetworkConfigurationContext, - RippleNetworkConfigurationContextProvider, -} from '@providers/ripple-network-configuration.provider'; +import { RippleNetworkConfigurationContextProvider } from '@providers/ripple-network-configuration.provider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WagmiProvider } from 'wagmi'; @@ -38,27 +34,25 @@ export function App(): React.JSX.Element { - - - - - - } /> - } /> - {/* } /> */} - } /> - } /> - } - /> - } /> - } /> - - - - - + + + + + } /> + } /> + {/* } /> */} + } /> + } /> + } + /> + } /> + } /> + + + + 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 61a1c692..45a32db2 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 @@ -12,11 +12,15 @@ import { NetworkConfigurationContext } from '@providers/network-configuration.pr import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import { vaultActions } from '@store/slices/vault/vault.actions'; import { withdraw } from 'dlc-btc-lib/ethereum-functions'; import { EthereumNetworkID } from 'dlc-btc-lib/models'; -import { createCheck, getRippleClient, getRippleWallet } from 'dlc-btc-lib/ripple-functions'; -import { shiftValue } from 'dlc-btc-lib/utilities'; +import { + connectRippleClient, + createCheck, + getRippleClient, + getRippleWallet, +} from 'dlc-btc-lib/ripple-functions'; +import { shiftValue, unshiftValue } from 'dlc-btc-lib/utilities'; import { useAccount } from 'wagmi'; interface BurnTokenTransactionFormProps { @@ -58,13 +62,16 @@ export function BurnTokenTransactionForm({ if (networkType === 'xrpl') { const xrplWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); + await connectRippleClient(xrplClient); + const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); + await createCheck( xrplClient, xrplWallet, appConfiguration.rippleIssuerAddress, undefined, - withdrawAmount.toString(), - currentVault.uuid + formattedWithdrawAmount.toString(), + currentVault.uuid.slice(2) ); } else if (networkType === 'evm') { const currentRisk = await fetchUserEthereumAddressRiskLevel(); @@ -76,23 +83,9 @@ export function BurnTokenTransactionForm({ currentVault.uuid, formattedWithdrawAmount ); - - const updatedVault = await getAndFormatVault( - currentVault.uuid, - ethereumNetworkConfiguration.dlcManagerContract - ); - dispatch( - vaultActions.swapVault({ - vaultUUID: currentVault.uuid, - updatedVault: updatedVault, - networkID: chainId?.toString() as EthereumNetworkID, - }) - ); - dispatch(mintUnmintActions.setUnmintStep([1, currentVault.uuid])); } else { throw new Error('Unsupported Network Type'); } - setIsSubmitting(false); } catch (error) { setIsSubmitting(false); toast({ 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 8eb7eb55..5c00adbe 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 @@ -8,6 +8,7 @@ import { BitcoinWalletContext, BitcoinWalletContextState, } from '@providers/bitcoin-wallet-context-provider'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; import { VaultContext } from '@providers/vault-context-provider'; import { RootState } from '@store/index'; @@ -35,6 +36,7 @@ export function DepositTransactionScreen({ const { bitcoinWalletContextState, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); + const { networkType } = useContext(NetworkConfigurationContext); const { mintStep } = useSelector((state: RootState) => state.mintunmint); @@ -47,8 +49,10 @@ export function DepositTransactionScreen({ try { setIsSubmitting(true); - const currentRisk = await fetchUserEthereumAddressRiskLevel(); - if (currentRisk === 'High') throw new Error('Risk Level is too high'); + if (networkType === 'evm') { + const currentRisk = await fetchUserEthereumAddressRiskLevel(); + if (currentRisk === 'High') throw new Error('Risk Level is too high'); + } await handleSignFundingTransaction(currentVault.uuid, depositAmount); } catch (error: any) { setIsSubmitting(false); diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx index d13c3aee..40b77ea5 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx @@ -28,11 +28,11 @@ export function TransactionFormFieldInput({ autoFocus allowMouseWheel={false} value={formField.state.value} + onChange={e => formField.handleChange(e)} onBlur={formField.handleBlur} borderColor={'white.01'} focusBorderColor={'rgba(50, 201, 247, 1)'} // accent.lightBlue.01 isInvalid={formField.state.meta.errors.length > 0} - onChange={e => formField.handleChange(e)} > diff --git a/src/app/hooks/use-ethereum-observer.ts b/src/app/hooks/use-ethereum-observer.ts deleted file mode 100644 index 7b629bd2..00000000 --- a/src/app/hooks/use-ethereum-observer.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* eslint-disable no-console */ -import { useContext, useEffect, useRef } from 'react'; -import { useDispatch } from 'react-redux'; - -import { getAndFormatVault } from '@functions/vault.functions'; -import { Vault } from '@models/vault'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; -import { VaultContext } from '@providers/vault-context-provider'; -import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import { modalActions } from '@store/slices/modal/modal.actions'; -import { vaultActions } from '@store/slices/vault/vault.actions'; -import Decimal from 'decimal.js'; -import { EthereumNetworkID } from 'dlc-btc-lib/models'; -import { equals } from 'ramda'; -import { useAccount } from 'wagmi'; - -export function useEthereumObserver(): void { - const dispatch = useDispatch(); - - const vaultContext = useContext(VaultContext); - const allVaults = useRef(vaultContext.allVaults); - - useEffect(() => { - allVaults.current = vaultContext.allVaults; - }, [vaultContext.allVaults]); - - const { address: ethereumUserAddress, chain } = useAccount(); - - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - - function getCurrentVault(vaultUUID: string): Vault { - const currentVault = allVaults.current.find(vault => vault.uuid === vaultUUID); - if (!currentVault) throw new Error(`Vault ${vaultUUID} not found`); - return currentVault; - } - - useEffect(() => { - if (!ethereumUserAddress) return; - - const dlcManagerContract = ethereumNetworkConfiguration.dlcManagerContract; - - console.log(`Listening to [${chain?.name}]`); - console.log(`Listening to [${dlcManagerContract.address}]`); - - const handleCreateDLC = async (...args: any[]) => { - const vaultOwner: string = args[1]; - - if (!equals(vaultOwner, ethereumUserAddress)) return; - - const vaultUUID = args[0]; - - console.log(`Vault ${vaultUUID} is ready`); - - const updatedVault = await getAndFormatVault(vaultUUID, dlcManagerContract); - - dispatch( - vaultActions.swapVault({ - vaultUUID, - updatedVault: updatedVault, - networkID: chain?.id.toString() as EthereumNetworkID, - }) - ); - - dispatch(mintUnmintActions.setMintStep([1, vaultUUID])); - }; - - const handleSetStatusFunded = async (...args: any[]) => { - const vaultOwner = args[2]; - - if (!equals(vaultOwner, ethereumUserAddress)) return; - - const vaultUUID = args[0]; - - console.log(`Vault ${vaultUUID} is funded`); - - const currentVault = getCurrentVault(vaultUUID); - const updatedVault = await getAndFormatVault(vaultUUID, dlcManagerContract); - - dispatch( - vaultActions.swapVault({ - vaultUUID, - updatedVault: updatedVault, - networkID: chain?.id.toString() as EthereumNetworkID, - }) - ); - - if (currentVault.valueMinted < currentVault.valueLocked) { - dispatch( - modalActions.toggleSuccessfulFlowModalVisibility({ - vaultUUID, - flow: 'burn', - assetAmount: new Decimal(currentVault.valueLocked) - .minus(updatedVault.valueLocked) - .toNumber(), - }) - ); - dispatch(mintUnmintActions.setMintStep([0, ''])); - } else { - dispatch( - modalActions.toggleSuccessfulFlowModalVisibility({ - vaultUUID, - flow: 'mint', - assetAmount: new Decimal(updatedVault.valueLocked) - .minus(currentVault.valueLocked) - .toNumber(), - }) - ); - dispatch(mintUnmintActions.setMintStep([0, ''])); - } - }; - - const handleSetStatusPending = async (...args: any[]) => { - const vaultOwner = args[2]; - - if (!equals(vaultOwner, ethereumUserAddress)) return; - - const vaultUUID = args[0]; - - console.log(`Vault ${vaultUUID} is pending`); - - const updatedVault = await getAndFormatVault(vaultUUID, dlcManagerContract); - - dispatch( - vaultActions.swapVault({ - vaultUUID, - updatedVault: updatedVault, - networkID: chain?.id.toString() as EthereumNetworkID, - }) - ); - - if (updatedVault.valueLocked !== updatedVault.valueMinted) { - dispatch(mintUnmintActions.setUnmintStep([2, vaultUUID])); - } else { - dispatch(mintUnmintActions.setMintStep([2, vaultUUID])); - } - }; - - dlcManagerContract.on('CreateDLC', handleCreateDLC); - dlcManagerContract.on('SetStatusFunded', handleSetStatusFunded); - dlcManagerContract.on('SetStatusPending', handleSetStatusPending); - - return () => { - dlcManagerContract.off('CreateDLC', handleCreateDLC); - dlcManagerContract.off('SetStatusFunded', handleSetStatusFunded); - dlcManagerContract.off('SetStatusPending', handleSetStatusPending); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ethereumNetworkConfiguration, ethereumUserAddress, chain?.id]); -} diff --git a/src/app/hooks/use-evm-vaults.ts b/src/app/hooks/use-evm-vaults.ts new file mode 100644 index 00000000..9b8660a1 --- /dev/null +++ b/src/app/hooks/use-evm-vaults.ts @@ -0,0 +1,193 @@ +import { useContext, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { formatVault } from '@functions/vault.functions'; +import { Vault } from '@models/vault'; +import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { NetworkConfigurationContext } from '@providers/network-configuration.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 { getAllAddressVaults } from 'dlc-btc-lib/ethereum-functions'; +import { VaultState } from 'dlc-btc-lib/models'; +import { useAccount } from 'wagmi'; + +interface useEVMVaultsReturnType { + allVaults: Vault[]; + readyVaults: Vault[]; + pendingVaults: Vault[]; + fundedVaults: Vault[]; + closingVaults: Vault[]; + closedVaults: Vault[]; + isLoading: boolean; +} + +const filterVaultsByState = (vaults: Vault[], state: VaultState) => { + return (vaults || []) + .filter(vault => vault.state === state) + .sort((a, b) => b.timestamp - a.timestamp); +}; + +const isVaultMissing = (previousVaults: Vault[], newVaults: Vault[]) => { + return ( + previousVaults.filter(previousVault => { + return !newVaults.some(vault => vault.uuid === previousVault.uuid); + }).length > 0 + ); +}; + +const getUpdatedVaults = (xrplVaults: Vault[], previousVaults: Vault[]) => { + return xrplVaults.filter(vault => { + return !previousVaults.some( + previousVault => + previousVault.uuid === vault.uuid && + previousVault.state === vault.state && + previousVault.valueLocked === vault.valueLocked && + previousVault.valueMinted === vault.valueMinted + ); + }); +}; + +export function useEVMVaults(): useEVMVaultsReturnType { + const dispatch = useDispatch(); + const queryClient = useQueryClient(); + + const [isLoading, setIsLoading] = useState(true); + + const { networkType } = useContext(NetworkConfigurationContext); + + const { isConnected, address: ethereumUserAddress } = useAccount(); + + const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( + EthereumNetworkConfigurationContext + ); + + const { data: evmVaults } = useQuery({ + queryKey: ['evm-vaults', ethereumUserAddress, ethereumNetworkConfiguration.chain.id], + initialData: [], + queryFn: fetchEVMVaults, + refetchInterval: 10000, + enabled: isConnected && networkType === 'evm' && !isEthereumNetworkConfigurationLoading, + }); + + async function fetchEVMVaults(): Promise { + if (!ethereumUserAddress) return []; + setIsLoading(true); + + const previousVaults: Vault[] | undefined = queryClient.getQueryData([ + 'evm-vaults', + ethereumUserAddress, + ethereumNetworkConfiguration.chain.id, + ]); + + try { + const evmRawVaults = await getAllAddressVaults( + ethereumNetworkConfiguration.dlcManagerContract, + ethereumUserAddress + ); + const evmVaults = evmRawVaults.map(formatVault); + + if (previousVaults?.length === 0) { + return evmVaults; + } + + if (previousVaults && isVaultMissing(previousVaults, evmVaults)) { + return previousVaults; + } + + const updatedVaults = previousVaults ? getUpdatedVaults(evmVaults, previousVaults) : []; + + updatedVaults.forEach(vault => { + const previousVault = previousVaults?.find( + previousVault => previousVault.uuid === vault.uuid + ); + + handleVaultStateChange(previousVault, vault); + }); + + return evmVaults; + } catch (error) { + console.error('Error fetching EVM Vaults', error); + return previousVaults ?? []; + } finally { + setIsLoading(false); + } + } + + const handleVaultStateChange = (previousVault: Vault | undefined, vault: Vault) => { + if (!previousVault) { + dispatch(mintUnmintActions.setMintStep([1, vault.uuid, vault])); + return; + } else if (previousVault && previousVault.state !== vault.state) { + switch (vault.state) { + case VaultState.FUNDED: + if (previousVault.valueMinted < previousVault.valueLocked) { + dispatch( + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: vault.uuid, + vault: vault, + flow: 'burn', + assetAmount: new Decimal(previousVault.valueLocked) + .minus(vault.valueLocked) + .toNumber(), + }) + ); + dispatch(mintUnmintActions.setMintStep([0, ''])); + } else { + dispatch( + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: vault.uuid, + vault: vault, + flow: 'mint', + assetAmount: new Decimal(vault.valueLocked) + .minus(previousVault.valueLocked) + .toNumber(), + }) + ); + dispatch(mintUnmintActions.setMintStep([0, ''])); + } + break; + + case VaultState.PENDING: + dispatch( + vault.valueLocked !== vault.valueMinted + ? mintUnmintActions.setUnmintStep([2, vault.uuid, vault]) + : mintUnmintActions.setMintStep([2, vault.uuid, vault]) + ); + break; + } + } else if (previousVault && previousVault.valueMinted !== vault.valueMinted) { + dispatch(mintUnmintActions.setUnmintStep([1, vault.uuid, vault])); + } + }; + + const allVaults = useMemo(() => evmVaults || [], [evmVaults]); + const readyVaults = useMemo(() => filterVaultsByState(evmVaults, VaultState.READY), [evmVaults]); + const fundedVaults = useMemo( + () => filterVaultsByState(evmVaults, VaultState.FUNDED), + [evmVaults] + ); + const pendingVaults = useMemo( + () => filterVaultsByState(evmVaults, VaultState.PENDING), + [evmVaults] + ); + const closingVaults = useMemo( + () => filterVaultsByState(evmVaults, VaultState.CLOSING), + [evmVaults] + ); + const closedVaults = useMemo( + () => filterVaultsByState(evmVaults, VaultState.CLOSED), + [evmVaults] + ); + + return { + allVaults, + readyVaults, + pendingVaults, + closingVaults, + fundedVaults, + closedVaults, + isLoading, + }; +} diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts deleted file mode 100644 index 0bf4c4c0..00000000 --- a/src/app/hooks/use-nfts.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { useContext, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; - -import { formatVault } from '@functions/vault.functions'; -import { Vault } from '@models/vault'; -import { NetworkConfigurationContext } from '@providers/network-configuration.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 { RippleHandler } from 'dlc-btc-lib'; -import { VaultState } from 'dlc-btc-lib/models'; -import { isEmpty } from 'ramda'; - -interface useNFTsReturnType { - allVaults: Vault[]; - readyVaults: Vault[]; - pendingVaults: Vault[]; - fundedVaults: Vault[]; - closingVaults: Vault[]; - closedVaults: Vault[]; - isLoading: boolean; -} - -enum VaultAction { - DEPOSIT = 'deposit', - WITHDRAW = 'withdraw', - DEPOSIT_PENDING = 'depositPending', - WITHDRAW_PENDING = 'withdrawPending', - DEPOSIT_SUCCESS = 'depositSuccess', - WITHDRAW_SUCCESS = 'withdrawSuccess', -} - -export function useNFTs(): useNFTsReturnType { - const queryClient = useQueryClient(); - const dispatch = useDispatch(); - const [isLoading, setIsLoading] = useState(true); - - const { networkType } = useContext(NetworkConfigurationContext); - const xrplHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - - const [dispatchTuple, setDispatchTuple] = useState<[string, VaultAction, number?]>([ - '', - VaultAction.DEPOSIT, - ]); - - async function fetchXRPLVaults(): Promise { - setIsLoading(true); - let xrplRawVaults: any[] = []; - - const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); - - try { - xrplRawVaults = await xrplHandler.getContractVaults(); - } catch (error) { - console.error('Error fetching XRPL Vaults', error); - return previousVaults ?? []; - } - - const xrplVaults = xrplRawVaults.map(vault => { - return formatVault(vault); - }); - - if (previousVaults) { - const missingVaults = previousVaults.filter((previousVault: Vault) => { - return !xrplVaults.some((vault: Vault) => vault.uuid === previousVault.uuid); - }); - - if (missingVaults.length > 0) { - return previousVaults; - } - } - - queryClient.setQueryData(['xrpl-vaults'], xrplVaults); - - if (previousVaults && previousVaults.length > 0) { - const newVaults = xrplVaults.filter((vault: Vault) => { - return !previousVaults.some( - (previousVault: Vault) => - previousVault.uuid === vault.uuid && - previousVault.state === vault.state && - previousVault.valueLocked === vault.valueLocked && - previousVault.valueMinted === vault.valueMinted - ); - }); - - newVaults.forEach((vault: Vault) => { - const previousVault = previousVaults.find( - (previousVault: Vault) => previousVault.uuid === vault.uuid - ); - - if (!previousVault) { - if (vault.uuid !== '0x0000000000000000000000000000000000000000000000000000000000000000') { - setDispatchTuple([vault.uuid, VaultAction.DEPOSIT]); - } - return; - } - - if (previousVault.state !== vault.state) { - switch (vault.state) { - case VaultState.FUNDED: - if (previousVault.valueMinted < previousVault.valueLocked) { - setDispatchTuple([ - vault.uuid, - VaultAction.WITHDRAW_SUCCESS, - new Decimal(previousVault.valueLocked).minus(vault.valueLocked).toNumber(), - ]); - } else { - setDispatchTuple([ - vault.uuid, - VaultAction.DEPOSIT_SUCCESS, - new Decimal(vault.valueLocked).minus(previousVault.valueLocked).toNumber(), - ]); - } - break; - case VaultState.PENDING: - if (vault.valueLocked !== vault.valueMinted) { - setDispatchTuple([vault.uuid, VaultAction.WITHDRAW_PENDING]); - } else { - setDispatchTuple([vault.uuid, VaultAction.DEPOSIT_PENDING]); - } - break; - } - return; - } - - if (previousVault.valueMinted !== vault.valueMinted) { - setDispatchTuple([vault.uuid, VaultAction.WITHDRAW]); - return; - } - }); - } - - setIsLoading(false); - return xrplVaults; - } - - const { data: vaults } = useQuery({ - queryKey: ['xrpl-vaults'], - initialData: [], - queryFn: fetchXRPLVaults, - refetchInterval: 10000, - enabled: networkType === 'xrpl', - }); - - function dispatchVaultAction() { - if (!isEmpty(dispatchTuple[0])) { - const updatedVault = vaults.find(vault => vault.uuid === dispatchTuple[0]); - if (!updatedVault) { - throw new Error('Vault not found'); - } - switch (dispatchTuple[1]) { - case 'deposit': - dispatch(mintUnmintActions.setMintStep([1, dispatchTuple[0], updatedVault])); - break; - case 'depositSuccess': - dispatch( - modalActions.toggleSuccessfulFlowModalVisibility({ - vaultUUID: updatedVault.uuid, - vault: updatedVault, - flow: 'mint', - assetAmount: dispatchTuple[2]!, - }) - ); - dispatch(mintUnmintActions.setMintStep([0, ''])); - break; - case 'withdrawSuccess': - dispatch( - modalActions.toggleSuccessfulFlowModalVisibility({ - vaultUUID: updatedVault.uuid, - vault: updatedVault, - flow: 'burn', - assetAmount: dispatchTuple[2]!, - }) - ); - dispatch(mintUnmintActions.setMintStep([0, ''])); - break; - case 'depositPending': - dispatch(mintUnmintActions.setMintStep([2, updatedVault.uuid, updatedVault])); - break; - case 'withdrawPending': - dispatch(mintUnmintActions.setUnmintStep([2, updatedVault.uuid, updatedVault])); - break; - case 'withdraw': - dispatch(mintUnmintActions.setUnmintStep([1, updatedVault.uuid, updatedVault])); - break; - } - } - } - useQuery({ - queryKey: ['newVault', dispatchTuple], - queryFn: dispatchVaultAction, - }); - - const allVaults = useMemo(() => { - const networkVaults = vaults || []; - return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); - }, [vaults]); - - const readyVaults = useMemo(() => { - const networkVaults = vaults || []; - return networkVaults - .filter(vault => vault.state === VaultState.READY) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults]); - - const fundedVaults = useMemo(() => { - const networkVaults = vaults || []; - return networkVaults - .filter(vault => vault.state === VaultState.FUNDED) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults]); - - const pendingVaults = useMemo(() => { - const networkVaults = vaults || []; - return networkVaults - .filter(vault => vault.state === VaultState.PENDING) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults]); - - const closingVaults = useMemo(() => { - const networkVaults = vaults || []; - return networkVaults - .filter(vault => vault.state === VaultState.CLOSING) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults]); - - const closedVaults = useMemo(() => { - const networkVaults = vaults || []; - return networkVaults - .filter(vault => vault.state === VaultState.CLOSED) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults]); - - return { - allVaults, - readyVaults, - pendingVaults, - closingVaults, - fundedVaults, - closedVaults, - isLoading, - }; -} diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 660782e6..db5fe366 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -1,15 +1,21 @@ import { useContext, useState } 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 { LedgerDLCHandler, RippleHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; +import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; import { submitFundingPSBT, submitWithdrawDepositPSBT, } from 'dlc-btc-lib/attestor-request-functions'; -import { Transaction, VaultState } from 'dlc-btc-lib/models'; +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 { useAccount } from 'wagmi'; import { useLeather } from './use-leather'; @@ -24,11 +30,17 @@ interface UsePSBTReturnType { } export function usePSBT(): UsePSBTReturnType { + const { + ethereumNetworkConfiguration: { dlcManagerContract, ethereumAttestorChainID }, + } = useContext(EthereumNetworkConfigurationContext); const { address: ethereumUserAddress } = useAccount(); + const { rippleUserAddress } = useContext(RippleNetworkConfigurationContext); const { bitcoinWalletType, dlcHandler, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); + const { networkType } = useContext(NetworkConfigurationContext); + const { handleFundingTransaction: handleFundingTransactionWithLedger, handleWithdrawalTransaction: handleWithdrawalTransactionWithLedger, @@ -52,21 +64,54 @@ export function usePSBT(): UsePSBTReturnType { const [bitcoinDepositAmount, setBitcoinDepositAmount] = useState(0); + const getRequiredPSBTInformation = async ( + vaultUUID: string + ): Promise<{ userAddress: string; vault: RawVault; attestorGroupPublicKey: string }> => { + if (networkType === 'evm') { + if (!ethereumUserAddress) throw new Error('User Address is not setup'); + const vault = await getRawVault(dlcManagerContract, vaultUUID); + const attestorGroupPublicKey = await getAttestorGroupPublicKey(dlcManagerContract); + 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(); + return { + userAddress: rippleUserAddress, + vault, + attestorGroupPublicKey: attestorGroupPublicKey, + }; + } else { + throw new Error('Network Type is not setup'); + } + }; + + const getAttestorChainID = (): string => { + if (networkType === 'evm') { + return ethereumAttestorChainID; + } else if (networkType === 'xrpl') { + return 'ripple-xrpl-testnet'; + } else { + throw new Error('Network Type is not setup'); + } + }; + async function handleSignFundingTransaction( vaultUUID: string, depositAmount: number ): Promise { try { if (!dlcHandler) throw new Error('DLC Handler is not setup'); - if (!ethereumUserAddress) throw new Error('User Address is not setup'); const feeRateMultiplier = import.meta.env.VITE_FEE_RATE_MULTIPLIER; - const attestorGroupPublicKey = - 'tpubDD8dCy2CrA7VgZdyLLmJB75nxWaokiCSZsPpqkj1uWjbLtxzuBCZQBtBMHpq9GU16v5RrhRz9EfhyK8QyenS3EtL7DAeEi6EBXRiaM2Usdm'; - - const rippleHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - const vault = await rippleHandler.getRawVault(vaultUUID); + const { userAddress, vault, attestorGroupPublicKey } = + await getRequiredPSBTInformation(vaultUUID); let fundingTransaction: Transaction; switch (bitcoinWalletType) { @@ -144,9 +189,9 @@ export function usePSBT(): UsePSBTReturnType { await submitFundingPSBT([appConfiguration.coordinatorURL], { vaultUUID, fundingPSBT: bytesToHex(fundingTransaction.toPSBT()), - userEthereumAddress: ethereumUserAddress, + userEthereumAddress: userAddress, userBitcoinTaprootPublicKey: dlcHandler.getTaprootDerivedPublicKey(), - attestorChainID: 'ripple-xrpl-testnet', + attestorChainID: getAttestorChainID() as AttestorChainID, }); break; default: @@ -169,16 +214,10 @@ export function usePSBT(): UsePSBTReturnType { ): Promise { try { if (!dlcHandler) throw new Error('DLC Handler is not setup'); - if (!ethereumUserAddress) throw new Error('User Address is not setup'); const feeRateMultiplier = import.meta.env.VITE_FEE_RATE_MULTIPLIER; - const attestorGroupPublicKey = - 'tpubDD8dCy2CrA7VgZdyLLmJB75nxWaokiCSZsPpqkj1uWjbLtxzuBCZQBtBMHpq9GU16v5RrhRz9EfhyK8QyenS3EtL7DAeEi6EBXRiaM2Usdm'; - const rippleHandler = RippleHandler.fromSeed('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - const vault = await rippleHandler.getRawVault(vaultUUID); - - if (!bitcoinWalletType) throw new Error('Bitcoin Wallet is not setup'); + const { vault, attestorGroupPublicKey } = await getRequiredPSBTInformation(vaultUUID); let withdrawalTransactionHex: string; switch (bitcoinWalletType) { diff --git a/src/app/hooks/use-vaults.ts b/src/app/hooks/use-vaults.ts deleted file mode 100644 index b7fcb624..00000000 --- a/src/app/hooks/use-vaults.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { useContext, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { formatVault } from '@functions/vault.functions'; -import { Vault } from '@models/vault'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; -import { RootState } from '@store/index'; -import { vaultActions } from '@store/slices/vault/vault.actions'; -import { getAllAddressVaults } from 'dlc-btc-lib/ethereum-functions'; -import { EthereumNetworkID, VaultState } from 'dlc-btc-lib/models'; -import { useAccount } from 'wagmi'; - -interface UseVaultsReturnType { - allVaults: Vault[]; - readyVaults: Vault[]; - pendingVaults: Vault[]; - fundedVaults: Vault[]; - closingVaults: Vault[]; - closedVaults: Vault[]; - isLoading: boolean; -} - -export function useVaults(): UseVaultsReturnType { - const dispatch = useDispatch(); - - const { isConnected, address: ethereumUserAddress } = useAccount(); - - const { vaults } = useSelector((state: RootState) => state.vault); - - const [isLoading, setIsLoading] = useState(true); - - const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( - EthereumNetworkConfigurationContext - ); - - const fetchVaultsIfReady = async (ethereumAddress: string) => { - setIsLoading(true); - - await getAllAddressVaults(ethereumNetworkConfiguration.dlcManagerContract, ethereumAddress) - .then(vaults => { - const formattedVaults = vaults.map(vault => { - return formatVault(vault); - }); - - dispatch( - vaultActions.setVaults({ - newVaults: formattedVaults, - networkID: ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID, - }) - ); - }) - .catch(error => { - // eslint-disable-next-line no-console - console.error('Error fetching vaults', error); - }); - setIsLoading(false); - }; - - useEffect(() => { - if (isConnected && ethereumUserAddress && !isEthereumNetworkConfigurationLoading) { - console.log('fetching vaults'); - void fetchVaultsIfReady(ethereumUserAddress); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - isConnected, - ethereumUserAddress, - isEthereumNetworkConfigurationLoading, - ethereumNetworkConfiguration, - ]); - - const allVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return [...networkVaults].sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const readyVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.READY) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const fundedVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.FUNDED) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const pendingVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.PENDING) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const closingVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.CLOSING) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - const closedVaults = useMemo(() => { - const networkVaults = - vaults[ethereumNetworkConfiguration.chain.id.toString() as EthereumNetworkID] || []; - return networkVaults - .filter(vault => vault.state === VaultState.CLOSED) - .sort((a, b) => b.timestamp - a.timestamp); - }, [vaults, ethereumNetworkConfiguration]); - - return { - allVaults, - readyVaults, - pendingVaults, - closingVaults, - fundedVaults, - closedVaults, - isLoading, - }; -} diff --git a/src/app/hooks/use-xrpl-vaults.ts b/src/app/hooks/use-xrpl-vaults.ts new file mode 100644 index 00000000..927c9aab --- /dev/null +++ b/src/app/hooks/use-xrpl-vaults.ts @@ -0,0 +1,192 @@ +import { useContext, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { formatVault } from '@functions/vault.functions'; +import { Vault } from '@models/vault'; +import { NetworkConfigurationContext } from '@providers/network-configuration.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'; + +const INITIAL_VAULT_UUID = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +interface useXRPLVaultsReturnType { + allVaults: Vault[]; + readyVaults: Vault[]; + pendingVaults: Vault[]; + fundedVaults: Vault[]; + closingVaults: Vault[]; + closedVaults: Vault[]; + isLoading: boolean; +} + +const filterVaultsByState = (vaults: Vault[], state: VaultState) => { + return (vaults || []) + .filter(vault => vault.state === state) + .sort((a, b) => b.timestamp - a.timestamp); +}; + +const isVaultMissing = (previousVaults: Vault[], newVaults: Vault[]) => { + return ( + previousVaults.filter(previousVault => { + return !newVaults.some(vault => vault.uuid === previousVault.uuid); + }).length > 0 + ); +}; + +const getUpdatedVaults = (xrplVaults: Vault[], previousVaults: Vault[]) => { + return xrplVaults.filter(vault => { + return !previousVaults.some( + previousVault => + previousVault.uuid === vault.uuid && + previousVault.state === vault.state && + previousVault.valueLocked === vault.valueLocked && + previousVault.valueMinted === vault.valueMinted + ); + }); +}; + +export function useXRPLVaults(): useXRPLVaultsReturnType { + const dispatch = useDispatch(); + const queryClient = useQueryClient(); + + const [isLoading, setIsLoading] = useState(true); + + const { networkType } = useContext(NetworkConfigurationContext); + + const issuerAddress = appConfiguration.rippleIssuerAddress; + const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); + + const { data: xrplVaults } = useQuery({ + queryKey: ['xrpl-vaults'], + initialData: [], + queryFn: fetchXRPLVaults, + refetchInterval: 10000, + enabled: networkType === 'xrpl', + }); + + async function fetchXRPLVaults(): Promise { + setIsLoading(true); + + const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); + + try { + await connectRippleClient(xrplClient); + + const xrplRawVaults = await getAllRippleVaults(xrplClient, issuerAddress); + const xrplVaults = xrplRawVaults.map(formatVault); + + if (previousVaults?.length === 0) { + return xrplVaults; + } + + if (previousVaults && isVaultMissing(previousVaults, xrplVaults)) { + return previousVaults; + } + + const updatedVaults = previousVaults ? getUpdatedVaults(xrplVaults, previousVaults) : []; + + updatedVaults.forEach(vault => { + const previousVault = previousVaults?.find( + previousVault => previousVault.uuid === vault.uuid + ); + + handleVaultStateChange(previousVault, vault); + }); + + return xrplVaults; + } catch (error) { + console.error('Error fetching XRPL Vaults', error); + return previousVaults ?? []; + } finally { + setIsLoading(false); + } + } + + const handleVaultStateChange = (previousVault: Vault | undefined, vault: Vault) => { + if (!previousVault && vault.uuid !== INITIAL_VAULT_UUID) { + console.log('New Vault', vault); + dispatch(mintUnmintActions.setMintStep([1, vault.uuid, vault])); + return; + } else if (previousVault && previousVault.state !== vault.state) { + switch (vault.state) { + case VaultState.FUNDED: + if (previousVault.valueMinted < previousVault.valueLocked) { + dispatch( + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: vault.uuid, + vault: vault, + flow: 'burn', + assetAmount: new Decimal(previousVault.valueLocked) + .minus(vault.valueLocked) + .toNumber(), + }) + ); + dispatch(mintUnmintActions.setMintStep([0, ''])); + } else { + dispatch( + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: vault.uuid, + vault: vault, + flow: 'mint', + assetAmount: new Decimal(vault.valueLocked) + .minus(previousVault.valueLocked) + .toNumber(), + }) + ); + dispatch(mintUnmintActions.setMintStep([0, ''])); + } + break; + + case VaultState.PENDING: + dispatch( + vault.valueLocked !== vault.valueMinted + ? mintUnmintActions.setUnmintStep([2, vault.uuid, vault]) + : mintUnmintActions.setMintStep([2, vault.uuid, vault]) + ); + break; + } + } else if (previousVault && previousVault.valueMinted !== vault.valueMinted) { + dispatch(mintUnmintActions.setUnmintStep([1, vault.uuid, vault])); + } + }; + + const allVaults = useMemo(() => xrplVaults || [], [xrplVaults]); + const readyVaults = useMemo( + () => filterVaultsByState(xrplVaults, VaultState.READY), + [xrplVaults] + ); + const fundedVaults = useMemo( + () => filterVaultsByState(xrplVaults, VaultState.FUNDED), + [xrplVaults] + ); + const pendingVaults = useMemo( + () => filterVaultsByState(xrplVaults, VaultState.PENDING), + [xrplVaults] + ); + const closingVaults = useMemo( + () => filterVaultsByState(xrplVaults, VaultState.CLOSING), + [xrplVaults] + ); + const closedVaults = useMemo( + () => filterVaultsByState(xrplVaults, VaultState.CLOSED), + [xrplVaults] + ); + + return { + allVaults, + readyVaults, + pendingVaults, + closingVaults, + fundedVaults, + closedVaults, + isLoading, + }; +} diff --git a/src/app/providers/ethereum-observer-provider.tsx b/src/app/providers/ethereum-observer-provider.tsx deleted file mode 100644 index 3308b552..00000000 --- a/src/app/providers/ethereum-observer-provider.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import { useEthereumObserver } from '@hooks/use-ethereum-observer'; -import { HasChildren } from '@models/has-children'; - -export function EthereumObserverProvider({ children }: HasChildren): React.JSX.Element { - useEthereumObserver(); - return {children}; -} diff --git a/src/app/providers/vault-context-provider.tsx b/src/app/providers/vault-context-provider.tsx index e2326989..628d53be 100644 --- a/src/app/providers/vault-context-provider.tsx +++ b/src/app/providers/vault-context-provider.tsx @@ -1,7 +1,7 @@ import { createContext, useContext } from 'react'; -import { useNFTs } from '@hooks/use-nfts'; -import { useVaults } from '@hooks/use-vaults'; +import { useEVMVaults } from '@hooks/use-evm-vaults'; +import { useXRPLVaults } from '@hooks/use-xrpl-vaults'; import { HasChildren } from '@models/has-children'; import { Vault } from '@models/vault'; @@ -26,14 +26,12 @@ export const VaultContext = createContext({ }); export function VaultContextProvider({ children }: HasChildren): React.JSX.Element { - const rippleVaults = useNFTs(); - const ethereumVaults = useVaults(); + const xrplVaults = useXRPLVaults(); + const evmVaults = useEVMVaults(); const { networkType } = useContext(NetworkConfigurationContext); const getVaults = (vaultType: keyof VaultContextType) => - networkType === 'evm' ? ethereumVaults[vaultType] : rippleVaults[vaultType]; - - console.log('ethereumVaults', ethereumVaults); + networkType === 'evm' ? evmVaults[vaultType] : xrplVaults[vaultType]; return ( ; modal: ReturnType; mintunmint: ReturnType; } const rootReducer = combineReducers({ - vault: vaultSlice.reducer, modal: modalSlice.reducer, mintunmint: mintUnmintSlice.reducer, }); diff --git a/src/app/store/slices/vault/vault.actions.ts b/src/app/store/slices/vault/vault.actions.ts deleted file mode 100644 index d32454dc..00000000 --- a/src/app/store/slices/vault/vault.actions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { vaultSlice } from '@store/slices/vault/vault.slice'; - -export const vaultActions = vaultSlice.actions; diff --git a/src/app/store/slices/vault/vault.slice.ts b/src/app/store/slices/vault/vault.slice.ts deleted file mode 100644 index e509c781..00000000 --- a/src/app/store/slices/vault/vault.slice.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Vault } from '@models/vault'; -import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { EthereumNetworkID } from 'dlc-btc-lib/models'; - -interface VaultSliceState { - vaults: { [key in EthereumNetworkID]: Vault[] }; - status: string; - error: string | null; -} - -const initialVaultState: VaultSliceState = { - vaults: { - [EthereumNetworkID.Mainnet]: [], - [EthereumNetworkID.Sepolia]: [], - [EthereumNetworkID.Arbitrum]: [], - [EthereumNetworkID.ArbitrumSepolia]: [], - [EthereumNetworkID.Base]: [], - [EthereumNetworkID.BaseSepolia]: [], - [EthereumNetworkID.Hardhat]: [], - }, - status: 'idle', - error: null, -}; - -export const vaultSlice = createSlice({ - name: 'vault', - initialState: initialVaultState, - reducers: { - setVaults: ( - state, - action: PayloadAction<{ newVaults: Vault[]; networkID: EthereumNetworkID }> - ) => { - const { newVaults, networkID } = action.payload; - - state.vaults[networkID] = newVaults; - }, - swapVault: ( - state, - action: PayloadAction<{ - vaultUUID: string; - updatedVault: Vault; - networkID: EthereumNetworkID; - }> - ) => { - const { vaultUUID, updatedVault, networkID } = action.payload; - const vaultIndex = state.vaults[networkID].findIndex(vault => vault.uuid === vaultUUID); - - if (vaultIndex === -1) { - state.vaults[networkID].push(updatedVault); - } else { - state.vaults[networkID][vaultIndex] = updatedVault; - } - }, - }, -}); diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index 643301db..14ef6743 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -32,6 +32,7 @@ export interface Configuration { arbitrumHTTP: string; baseWebsocket: string; baseHTTP: string; + xrplWebsocket: string; walletConnectProjectID: string; bitcoinNetwork: BitcoinNetworkName; bitcoinNetworkIndex: number; From 4d9a5f30e384bb1c81e9440ae11af7c1ef309c31 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 11 Oct 2024 15:29:16 +0200 Subject: [PATCH 08/39] feat: modify balance fetching --- src/app/components/account/account.tsx | 2 +- .../account/components/account-menu.tsx | 2 - .../burn-transaction-screen.tsx | 7 +- .../deposit-transaction-screen.tsx | 1 - .../components/select-ripple-wallet-menu.tsx | 2 +- .../select-wallet-modal.radio-card.tsx | 33 --------- .../select-wallet-modal.tsx | 1 + .../my-vaults-header/my-vaults-header.tsx | 6 ++ .../components/my-vaults/my-vaults-large.tsx | 7 +- .../network/components/networks-menu.tsx | 3 +- .../select-network-button.tsx | 1 - src/app/functions/vault.functions.ts | 9 --- src/app/hooks/use-active-tabs.ts | 2 +- .../providers/balance-context-provider.tsx | 73 ++++++++++++------- .../ripple-network-configuration.provider.tsx | 24 ++++++ src/shared/constants/ripple.constants.ts | 5 +- 16 files changed, 89 insertions(+), 89 deletions(-) delete mode 100644 src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx diff --git a/src/app/components/account/account.tsx b/src/app/components/account/account.tsx index 5d4aa616..6e748dc5 100644 --- a/src/app/components/account/account.tsx +++ b/src/app/components/account/account.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect } from 'react'; +import { useContext } from 'react'; import { useDispatch } from 'react-redux'; import { Button, HStack } from '@chakra-ui/react'; diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index e79af9ca..70aa8411 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -1,5 +1,3 @@ -import { useContext } from 'react'; - import { ChevronDownIcon } from '@chakra-ui/icons'; import { HStack, 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 45a32db2..dd163fb0 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,7 +5,6 @@ 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 { getAndFormatVault } from '@functions/vault.functions'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; @@ -13,15 +12,13 @@ import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provi import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { withdraw } from 'dlc-btc-lib/ethereum-functions'; -import { EthereumNetworkID } from 'dlc-btc-lib/models'; import { connectRippleClient, createCheck, getRippleClient, getRippleWallet, } from 'dlc-btc-lib/ripple-functions'; -import { shiftValue, unshiftValue } from 'dlc-btc-lib/utilities'; -import { useAccount } from 'wagmi'; +import { shiftValue } from 'dlc-btc-lib/utilities'; interface BurnTokenTransactionFormProps { isBitcoinWalletLoading: [boolean, string]; @@ -46,8 +43,6 @@ export function BurnTokenTransactionForm({ const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - const { chainId } = useAccount(); - const signer = useEthersSigner(); const { unmintStep } = useSelector((state: RootState) => state.mintunmint); 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 5c00adbe..5f13f06b 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 @@ -10,7 +10,6 @@ import { } from '@providers/bitcoin-wallet-context-provider'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; -import { VaultContext } from '@providers/vault-context-provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; 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 ec82733c..f2a759f3 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,4 +1,4 @@ -import { Box, Button, HStack, Image, Spinner, Text } from '@chakra-ui/react'; +import { Box, Button, HStack, Image, Text } from '@chakra-ui/react'; import { RippleWallet } from '../select-wallet-modal'; diff --git a/src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx b/src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx deleted file mode 100644 index 77af6fe9..00000000 --- a/src/app/components/modals/select-wallet-modal/components/select-wallet-modal.radio-card.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Box, RadioProps, useRadio } from '@chakra-ui/react'; - -export function RadioCard(radioProps: RadioProps): React.JSX.Element { - const { getInputProps, getRadioProps } = useRadio(radioProps); - - const input = getInputProps(); - const checkbox = getRadioProps(); - - return ( - - - - {radioProps.children} - - - ); -} 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 54f983a4..5077aa5e 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 @@ -84,6 +84,7 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): } async function handleConnectRippleWallet(id: string) { + console.log('id', id); setNetworkType('xrpl'); setRippleWallet(seedWallet); setIsRippleWalletConnected(true); diff --git a/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx b/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx index 6c6d5b6b..09def6aa 100644 --- a/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx +++ b/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx @@ -1,3 +1,5 @@ +import { useEffect } from 'react'; + import { Divider, HStack, Text, VStack } from '@chakra-ui/react'; import { MyVaultsHeaderBalanceInfo } from './components/my-vaults-header-balance-info'; @@ -13,6 +15,10 @@ export function MyVaultsLargeHeader({ dlcBTCBalance, lockedBTCBalance, }: MyVaultsLargeHeaderProps): React.JSX.Element { + useEffect(() => { + console.log('dlcBTCBalance', dlcBTCBalance); + console.log('lockedBTCBalance', lockedBTCBalance); + }, [dlcBTCBalance, lockedBTCBalance]); return ( diff --git a/src/app/components/my-vaults/my-vaults-large.tsx b/src/app/components/my-vaults/my-vaults-large.tsx index 76ef0af0..01592006 100644 --- a/src/app/components/my-vaults/my-vaults-large.tsx +++ b/src/app/components/my-vaults/my-vaults-large.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useEffect } from 'react'; import { HStack } from '@chakra-ui/react'; import { VaultsListGroupBlankContainer } from '@components/vaults-list/components/vaults-list-group-blank-container'; @@ -16,6 +16,11 @@ export function MyVaultsLarge(): React.JSX.Element { const { isConnected } = useNetworkConnection(); const { dlcBTCBalance, lockedBTCBalance } = useContext(BalanceContext); + useEffect(() => { + console.log('dlcBTCBalance', dlcBTCBalance); + console.log('lockedBTCBalance', lockedBTCBalance); + }, [dlcBTCBalance, lockedBTCBalance]); + const { readyVaults, pendingVaults, fundedVaults, closingVaults, closedVaults, allVaults } = useContext(VaultContext); diff --git a/src/app/components/network/components/networks-menu.tsx b/src/app/components/network/components/networks-menu.tsx index dad2339b..07ce9c2a 100644 --- a/src/app/components/network/components/networks-menu.tsx +++ b/src/app/components/network/components/networks-menu.tsx @@ -1,9 +1,8 @@ -import { useContext, useEffect, useState } from 'react'; +import { useContext } from 'react'; import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; import { useNetworkConnection } from '@hooks/use-connected'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; -import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { useAccount, useConfig, useSwitchChain } from 'wagmi'; interface NetworksMenuProps { diff --git a/src/app/components/select-network-button/select-network-button.tsx b/src/app/components/select-network-button/select-network-button.tsx index b38b2962..bd18046a 100644 --- a/src/app/components/select-network-button/select-network-button.tsx +++ b/src/app/components/select-network-button/select-network-button.tsx @@ -5,7 +5,6 @@ import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/r import { getEthereumNetworkByID, getRippleNetworkByID } from '@functions/configuration.functions'; import { RippleNetwork, RippleNetworkID } from '@models/ripple.models'; import { EthereumNetwork, EthereumNetworkID } from 'dlc-btc-lib/models'; -import { Chain } from 'viem'; interface SelectNetworkButtonProps { handleChangeNetwork: (networkID: EthereumNetworkID | RippleNetworkID) => void; diff --git a/src/app/functions/vault.functions.ts b/src/app/functions/vault.functions.ts index df8f6e5f..604d7621 100644 --- a/src/app/functions/vault.functions.ts +++ b/src/app/functions/vault.functions.ts @@ -1,8 +1,6 @@ import { Vault } from '@models/vault'; -import { getRawVault } from 'dlc-btc-lib/ethereum-functions'; import { RawVault } from 'dlc-btc-lib/models'; import { unshiftValue } from 'dlc-btc-lib/utilities'; -import { Contract } from 'ethers'; export function formatVault(vault: RawVault): Vault { return { @@ -21,10 +19,3 @@ export function formatVault(vault: RawVault): Vault { taprootPubKey: vault.taprootPubKey, }; } - -export async function getAndFormatVault( - vaultUUID: string, - dlcManagerContract: Contract -): Promise { - return getRawVault(dlcManagerContract, vaultUUID).then((vault: RawVault) => formatVault(vault)); -} diff --git a/src/app/hooks/use-active-tabs.ts b/src/app/hooks/use-active-tabs.ts index 31266a47..a666510d 100644 --- a/src/app/hooks/use-active-tabs.ts +++ b/src/app/hooks/use-active-tabs.ts @@ -1,4 +1,4 @@ -import { useContext, useEffect } from 'react'; +import { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; diff --git a/src/app/providers/balance-context-provider.tsx b/src/app/providers/balance-context-provider.tsx index af47e656..f19c6a10 100644 --- a/src/app/providers/balance-context-provider.tsx +++ b/src/app/providers/balance-context-provider.tsx @@ -1,15 +1,22 @@ -import { createContext, useContext, useEffect, useState } from 'react'; +import { createContext, useContext } from 'react'; +import { useNetworkConnection } from '@hooks/use-connected'; import { HasChildren } from '@models/has-children'; +import { useQuery } from '@tanstack/react-query'; import { getAddressDLCBTCBalance, getAllAddressVaults, getLockedBTCBalance, } from 'dlc-btc-lib/ethereum-functions'; +import { + getDLCBTCBalance, + getLockedBTCBalance as getLockedBTCBalanceXRPL, +} from 'dlc-btc-lib/ripple-functions'; import { useAccount } from 'wagmi'; import { EthereumNetworkConfigurationContext } from './ethereum-network-configuration.provider'; -import { VaultContext } from './vault-context-provider'; +import { NetworkConfigurationContext } from './network-configuration.provider'; +import { RippleNetworkConfigurationContext } from './ripple-network-configuration.provider'; interface VaultContextType { dlcBTCBalance: number | undefined; @@ -22,44 +29,54 @@ export const BalanceContext = createContext({ }); export function BalanceContextProvider({ children }: HasChildren): React.JSX.Element { - const { ethereumNetworkConfiguration, isEthereumNetworkConfigurationLoading } = useContext( - EthereumNetworkConfigurationContext + const { + ethereumNetworkConfiguration: { dlcBTCContract, dlcManagerContract }, + } = useContext(EthereumNetworkConfigurationContext); + const { networkType } = useContext(NetworkConfigurationContext); + const { isConnected } = useNetworkConnection(); + const { rippleUserAddress, rippleClient, rippleWalletClient } = useContext( + RippleNetworkConfigurationContext ); + const { address: ethereumUserAddress } = useAccount(); - const { fundedVaults } = useContext(VaultContext); - const [dlcBTCBalance, setDLCBTCBalance] = useState(undefined); - const [lockedBTCBalance, setLockedBTCBalance] = useState(undefined); + const fetchEVMBalances = async () => { + const dlcBTCBalance = await getAddressDLCBTCBalance(dlcBTCContract, ethereumUserAddress!); - const fetchBalancesIfReady = async (ethereumAddress: string) => { - const currentTokenBalance = await getAddressDLCBTCBalance( - ethereumNetworkConfiguration.dlcBTCContract, - ethereumAddress + const lockedBTCBalance = await getLockedBTCBalance( + await getAllAddressVaults(dlcManagerContract, ethereumUserAddress!) ); - if (currentTokenBalance !== dlcBTCBalance) { - setDLCBTCBalance(currentTokenBalance); - } + return { dlcBTCBalance, lockedBTCBalance }; + }; - const userFundedVaults = await getAllAddressVaults( - ethereumNetworkConfiguration.dlcManagerContract, - ethereumAddress + const fetchXRPLBalances = async () => { + const dlcBTCBalance = await getDLCBTCBalance( + rippleClient, + rippleWalletClient!, + appConfiguration.rippleIssuerAddress + ); + console.log('dlcBTCBalance', dlcBTCBalance); + const lockedBTCBalance = await getLockedBTCBalanceXRPL( + rippleClient, + rippleWalletClient!, + appConfiguration.rippleIssuerAddress ); - const currentLockedBTCBalance = await getLockedBTCBalance(userFundedVaults); - if (currentLockedBTCBalance !== lockedBTCBalance) { - setLockedBTCBalance(currentLockedBTCBalance); - } + console.log('lockedBTCBalance', lockedBTCBalance); + return { dlcBTCBalance, lockedBTCBalance }; }; - useEffect(() => { - ethereumUserAddress && - !isEthereumNetworkConfigurationLoading && - void fetchBalancesIfReady(ethereumUserAddress); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ethereumUserAddress, fundedVaults]); + const { data } = useQuery({ + queryKey: ['balances', networkType === 'evm' ? ethereumUserAddress : rippleUserAddress], + queryFn: networkType === 'evm' ? fetchEVMBalances : fetchXRPLBalances, + enabled: isConnected, + refetchInterval: 10000, + }); return ( - + {children} ); diff --git a/src/app/providers/ripple-network-configuration.provider.tsx b/src/app/providers/ripple-network-configuration.provider.tsx index e7e4e7d9..ae47314b 100644 --- a/src/app/providers/ripple-network-configuration.provider.tsx +++ b/src/app/providers/ripple-network-configuration.provider.tsx @@ -4,6 +4,8 @@ import { RippleWallet } from '@components/modals/select-wallet-modal/select-wall import { getRippleNetworkByID } from '@functions/configuration.functions'; import { HasChildren } from '@models/has-children'; import { RippleNetwork, RippleNetworkConfiguration, RippleNetworkID } from '@models/ripple.models'; +import { Client, Wallet } from 'dlc-btc-lib/models'; +import { getRippleClient, getRippleWallet } from 'dlc-btc-lib/ripple-functions'; import { equals, find } from 'ramda'; import { supportedRippleNetworks } from '@shared/constants/ripple.constants'; @@ -56,6 +58,9 @@ interface RippleNetworkConfigurationContext { rippleUserAddress: string | undefined; isRippleWalletConnected: boolean; rippleWallet: RippleWallet | undefined; + rippleWalletClient: Wallet | undefined; + setRippleWalletClient: (seed: string) => void; + rippleClient: Client; setRippleWallet: (rippleWallet: RippleWallet) => void; setIsRippleWalletConnected: (isConnected: boolean) => void; isRippleNetworkConfigurationLoading: boolean; @@ -66,6 +71,9 @@ export const RippleNetworkConfigurationContext = createContext {}, + rippleClient: new Client(appConfiguration.xrplWebsocket), isRippleWalletConnected: false, setRippleWallet: () => {}, setIsRippleWalletConnected: () => {}, @@ -83,8 +91,21 @@ export function RippleNetworkConfigurationContextProvider({ 'rfvtbrXSxLsxVWDktR4sdzjJgv8EnMKFKG' ); + const [rippleWalletClient, setRippleWalletClient] = useState(undefined); + console.log('rippleWalletClient', rippleWalletClient); + + const rippleClient = getRippleClient(rippleNetworkConfiguration.websocketURL); + const [rippleWallet, setRippleWallet] = useState(undefined); + function setRippleWalletClientForUser(seed: string) { + const rippleWalletClient = getRippleWallet(seed); + setRippleUserAddress(rippleWalletClient.classicAddress); + setRippleWalletClient(rippleWalletClient); + } + + const rippleWalletC = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); + const [isRippleNetworkConfigurationLoading, setIsRippleNetworkConfigurationLoading] = useState(false); @@ -109,6 +130,9 @@ export function RippleNetworkConfigurationContextProvider({ enabledRippleNetworks, rippleUserAddress, rippleWallet, + rippleWalletClient: rippleWalletC, + setRippleWalletClient: setRippleWalletClientForUser, + rippleClient, setRippleWallet, isRippleWalletConnected, setIsRippleWalletConnected, diff --git a/src/shared/constants/ripple.constants.ts b/src/shared/constants/ripple.constants.ts index 10039df5..ee8d7b06 100644 --- a/src/shared/constants/ripple.constants.ts +++ b/src/shared/constants/ripple.constants.ts @@ -1,12 +1,11 @@ import { RippleNetwork, RippleNetworkID } from '@models/ripple.models'; -export const RippleMainnet: RippleNetwork = { +const RippleMainnet: RippleNetwork = { id: RippleNetworkID.Mainnet, name: 'Mainnet', displayName: 'Mainnet', }; - -export const RippleTestnet: RippleNetwork = { +const RippleTestnet: RippleNetwork = { id: RippleNetworkID.Testnet, name: 'Testnet', displayName: 'Testnet', From 7a2052a2e670d2167e73618436e4033a8b7dbf08 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Mon, 14 Oct 2024 17:05:25 +0200 Subject: [PATCH 09/39] feat: add ledger support --- config.testnet.json | 5 +- .../functions/submit-xrpl-vault-request.ts | 5 + package.json | 1 + src/app/app.tsx | 2 + src/app/components/account/account.tsx | 30 +-- .../burn-transaction-screen.tsx | 19 +- .../setup-vault-screen/setup-vault-screen.tsx | 5 +- .../select-wallet-modal.tsx | 17 +- src/app/hooks/use-connected.ts | 1 + src/app/hooks/use-psbt.ts | 1 + src/app/hooks/use-xrpl-ledger.ts | 111 ++++++++ src/app/hooks/use-xrpl-vaults.ts | 7 +- .../ripple-network-configuration.provider.tsx | 50 ++-- .../providers/xrp-wallet-context-provider.tsx | 64 +++++ src/shared/models/wallet.ts | 24 ++ yarn.lock | 241 +++++++++++++++++- 16 files changed, 514 insertions(+), 69 deletions(-) create mode 100644 src/app/hooks/use-xrpl-ledger.ts create mode 100644 src/app/providers/xrp-wallet-context-provider.tsx diff --git a/config.testnet.json b/config.testnet.json index 5b62a442..ff81f86d 100644 --- a/config.testnet.json +++ b/config.testnet.json @@ -1,13 +1,16 @@ { "appEnvironment": "testnet", - "coordinatorURL": "https://testnet.dlc.link/attestor-1", + "coordinatorURL": "https://testnet-ripple.dlc.link/attestor-1", "enabledEthereumNetworkIDs": ["421614", "84532", "11155111"], + "enabledRippleNetworkIDs": ["1"], "bitcoinNetwork": "testnet", "bitcoinNetworkIndex": 1, "bitcoinNetworkPreFix": "tb1", "bitcoinBlockchainURL": "https://testnet.dlc.link/electrs", "bitcoinBlockchainExplorerURL": "https://mempool.space/testnet", "bitcoinBlockchainFeeEstimateURL": "https://mempool.space/testnet/api/v1/fees/recommended", + "rippleIssuerAddress": "ra9epzthPkNXykgfadCwu8D7mtajj8DVCP", + "xrplWebsocket": "wss://s.altnet.rippletest.net:51233", "ledgerApp": "Bitcoin Test", "merchants": [ { diff --git a/netlify/functions/submit-xrpl-vault-request.ts b/netlify/functions/submit-xrpl-vault-request.ts index ee4053e8..03882eb0 100644 --- a/netlify/functions/submit-xrpl-vault-request.ts +++ b/netlify/functions/submit-xrpl-vault-request.ts @@ -2,6 +2,7 @@ import { Handler, HandlerEvent } from '@netlify/functions'; import { submitSetupXRPLVaultRequest } from 'dlc-btc-lib/attestor-request-functions'; const handler: Handler = async (event: HandlerEvent) => { + console.log('event', event); try { if (!event.queryStringParameters) { return { @@ -33,12 +34,16 @@ const handler: Handler = async (event: HandlerEvent) => { const coordinatorURL = event.queryStringParameters.coordinatorURL; const userXRPLAddress = event.queryStringParameters.userXRPLAddress; + console.log('coordinatorURL', coordinatorURL); + console.log('userXRPLAddress', userXRPLAddress); + await submitSetupXRPLVaultRequest(coordinatorURL, userXRPLAddress); return { statusCode: 200, }; } catch (error: any) { + console.log('IDE NEEEEEEZZZZZZZZ', error); return { statusCode: 500, body: JSON.stringify({ diff --git a/package.json b/package.json index 79bc3bb3..57ee10a7 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", + "@ledgerhq/hw-app-xrp": "^6.29.4", "@ledgerhq/hw-transport-webusb": "^6.28.6", "@netlify/functions": "^2.8.1", "@reduxjs/toolkit": "^1.9.7", diff --git a/src/app/app.tsx b/src/app/app.tsx index e007f2fd..12bb7ceb 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -32,6 +32,7 @@ export function App(): React.JSX.Element { + {/* */} @@ -55,6 +56,7 @@ export function App(): React.JSX.Element { + {/* */} diff --git a/src/app/components/account/account.tsx b/src/app/components/account/account.tsx index 6e748dc5..38776b7e 100644 --- a/src/app/components/account/account.tsx +++ b/src/app/components/account/account.tsx @@ -19,9 +19,7 @@ export function Account(): React.JSX.Element { const { address: ethereumUserAddress, connector: ethereumWallet } = useAccount(); const { disconnect: disconnectEthereumWallet } = useDisconnect(); - const { setIsRippleWalletConnected, rippleUserAddress, rippleWallet } = useContext( - RippleNetworkConfigurationContext - ); + const { rippleUserAddress, rippleWallet } = useContext(RippleNetworkConfigurationContext); function getWalletInformation(): | { address: string; wallet: RippleWallet | Connector } @@ -31,8 +29,8 @@ export function Account(): React.JSX.Element { if (!ethereumUserAddress || !ethereumWallet) return undefined; return { address: ethereumUserAddress, wallet: ethereumWallet }; case 'xrpl': - if (!rippleUserAddress || !rippleWallet) return undefined; - return { address: rippleUserAddress, wallet: rippleWallet }; + if (!rippleUserAddress) return undefined; + return { address: rippleUserAddress, wallet: rippleWallet! }; default: throw new Error('Invalid Network Type'); } @@ -43,17 +41,17 @@ export function Account(): React.JSX.Element { } function onDisconnectWalletClick(): void { - switch (networkType) { - case 'evm': - disconnectEthereumWallet(); - break; - case 'xrpl': - setIsRippleWalletConnected(false); - break; - default: - break; - } - dispatch(mintUnmintActions.resetMintUnmintState()); + // switch (networkType) { + // case 'evm': + // disconnectEthereumWallet(); + // break; + // case 'xrpl': + // setIsRippleWalletConnected(false); + // break; + // default: + // break; + // } + // dispatch(mintUnmintActions.resetMintUnmintState()); } return ( 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 dd163fb0..225f739d 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 @@ -9,6 +9,7 @@ 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 { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { withdraw } from 'dlc-btc-lib/ethereum-functions'; @@ -19,6 +20,7 @@ import { getRippleWallet, } from 'dlc-btc-lib/ripple-functions'; import { shiftValue } from 'dlc-btc-lib/utilities'; +import { decode } from 'ripple-binary-codec'; interface BurnTokenTransactionFormProps { isBitcoinWalletLoading: [boolean, string]; @@ -38,6 +40,9 @@ export function BurnTokenTransactionForm({ const { networkType } = useContext(NetworkConfigurationContext); const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); + const { rippleUserAddress, rippleClient, signTransaction } = useContext( + RippleNetworkConfigurationContext + ); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); @@ -55,19 +60,21 @@ export function BurnTokenTransactionForm({ if (!currentVault) return; setIsSubmitting(true); if (networkType === 'xrpl') { - const xrplWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); - await connectRippleClient(xrplClient); + await connectRippleClient(rippleClient); const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - await createCheck( - xrplClient, - xrplWallet, + const check = await createCheck( + rippleClient, + rippleUserAddress!, appConfiguration.rippleIssuerAddress, undefined, formattedWithdrawAmount.toString(), currentVault.uuid.slice(2) ); + const signedCheck = await signTransaction(check); + console.log('signedCheck', signedCheck); + const submitResponse = await rippleClient.submitAndWait(signedCheck); + console.log('Check submitted', submitResponse); } 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/setup-vault-screen/setup-vault-screen.tsx b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx index c3275fdd..44fbfd0f 100644 --- a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx +++ b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx @@ -5,6 +5,7 @@ import { submitSetupXRPLVaultRequest } from '@functions/attestor-request.functio import { useEthersSigner } from '@functions/configuration.functions'; import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; import { setupVault } from 'dlc-btc-lib/ethereum-functions'; import { getRippleWallet } from 'dlc-btc-lib/ripple-functions'; @@ -13,6 +14,7 @@ import { SetupVaultScreenVaultGraphics } from './components/setup-vault-screen.v export function SetupVaultScreen(): React.JSX.Element { const toast = useToast(); const { networkType } = useContext(NetworkConfigurationContext); + const { rippleUserAddress } = useContext(RippleNetworkConfigurationContext); const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); @@ -24,8 +26,7 @@ export function SetupVaultScreen(): React.JSX.Element { try { setIsSubmitting(true); if (networkType === 'xrpl') { - const xrplWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - await submitSetupXRPLVaultRequest(xrplWallet.classicAddress); + await submitSetupXRPLVaultRequest(rippleUserAddress!); } else if (networkType === 'evm') { await setupVault(ethereumNetworkConfiguration.dlcManagerContract.connect(signer!)); } else { 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 5077aa5e..31bc5639 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 @@ -27,7 +27,13 @@ const seedWallet: RippleWallet = { icon: './images/logos/xpr-logo.svg', }; -const rippleWallets = [seedWallet]; +const ledgerWallet: RippleWallet = { + id: 'ledger', + name: 'Ledger', + icon: './images/logos/ledger-logo.svg', +}; + +const rippleWallets = [ledgerWallet]; export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { const { connect, isPending, isSuccess, connectors } = useConnect(); @@ -37,8 +43,9 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): const { enabledRippleNetworks, isRippleWalletConnected, - setIsRippleWalletConnected, setRippleWallet, + rippleWallet, + connectLedgerWallet, } = useContext(RippleNetworkConfigurationContext); const ethereumNetworkIDs = ethereumNetworks.map( @@ -84,10 +91,10 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): } async function handleConnectRippleWallet(id: string) { - console.log('id', id); setNetworkType('xrpl'); - setRippleWallet(seedWallet); - setIsRippleWalletConnected(true); + setRippleWallet(rippleWallets[0]); + + await connectLedgerWallet("44'/144'/0'/0/0"); } const handleChangeNetwork = (networkID: EthereumNetworkID | RippleNetworkID) => { diff --git a/src/app/hooks/use-connected.ts b/src/app/hooks/use-connected.ts index 0620e33d..a5b2a185 100644 --- a/src/app/hooks/use-connected.ts +++ b/src/app/hooks/use-connected.ts @@ -22,6 +22,7 @@ export function useNetworkConnection(): UseNetworkConnectionReturnType { setIsConnected(isEthereumWalletConnected); break; case 'xrpl': + console.log('isRippleWalletConnected', isRippleWalletConnected); setIsConnected(isRippleWalletConnected); break; default: diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index db5fe366..8021b389 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -81,6 +81,7 @@ export function usePSBT(): UsePSBTReturnType { vaultUUID ); const attestorGroupPublicKey = await getAttestorExtendedGroupPublicKey(); + console.log('attestorGroupPublicKey', attestorGroupPublicKey); return { userAddress: rippleUserAddress, vault, diff --git a/src/app/hooks/use-xrpl-ledger.ts b/src/app/hooks/use-xrpl-ledger.ts new file mode 100644 index 00000000..646af1d5 --- /dev/null +++ b/src/app/hooks/use-xrpl-ledger.ts @@ -0,0 +1,111 @@ +import { useState } from 'react'; + +import Xrp from '@ledgerhq/hw-app-xrp'; +import Transport from '@ledgerhq/hw-transport-webusb'; +import { Client } from 'dlc-btc-lib/models'; +import { getRippleClient, setTrustLine } from 'dlc-btc-lib/ripple-functions'; +import { encode } from 'ripple-binary-codec'; + +interface useXRPLLedgerReturnType { + xrplAddress: string | undefined; + isConnected: boolean; + connectLedgerWallet: (derivationPath: string) => Promise; + fetchXRPLAddress: () => Promise; + signTransaction: (transaction: any) => Promise; +} + +export function useXRPLLedger(): useXRPLLedgerReturnType { + const [derivationPath, setDerivationPath] = useState(undefined); + const [xrplWallet, setXRPLWallet] = useState(undefined); + const [xrplAddress, setXRPLAddress] = useState(undefined); + const [isConnected, setIsConnected] = useState(false); + + async function connectLedgerWallet(derivationPath: string) { + try { + console.log('Connecting ledger wallet'); + const transport = await Transport.create(); + console.log('Transport created'); + const xrplWallet = new Xrp(transport); + console.log('XRPL wallet created'); + const xrplAddress = await xrplWallet.getAddress(derivationPath); + console.log('XRPL address fetched', xrplAddress); + // const trustset = await setTrustLine( + // getRippleClient(appConfiguration.xrplWebsocket), + // xrplAddress.address, + // appConfiguration.rippleIssuerAddress + // ); + // const trustset2 = { + // SigningPubKey: xrplAddress.publicKey.toUpperCase(), + // ...trustset, + // }; + // console.log('Trustline set', trustset2); + // const encoded = encode(trustset2); + // console.log('Trustline encoded', encoded); + // const signtrustset = await xrplWallet.signTransaction("44'/144'/0'/0/0", encoded); + // console.log('Trustline signed', signtrustset); + // const xrplClient = getRippleClient(appConfiguration.xrplWebsocket); + // const trustline = await xrplClient.submitAndWait(signtrustset); + // console.log('Trustline', trustline); + + setDerivationPath(derivationPath); + setXRPLWallet(xrplWallet); + setXRPLAddress(xrplAddress.address); + setIsConnected(true); + console.log('Ledger wallet connected'); + } catch (error) { + console.error('Error connecting ledger wallet', error); + } + } + + function fetchXRPLAddress() { + if (!xrplWallet) { + throw new Error('Ledger wallet not connected'); + } + + if (!derivationPath) { + throw new Error('Derivation path not set'); + } + return xrplWallet.getAddress(derivationPath); + } + + async function signTransaction(transaction: any) { + if (!xrplWallet) { + throw new Error('Ledger wallet not connected'); + } + + if (!derivationPath) { + throw new Error('Derivation path not set'); + } + + const deviceData = await xrplWallet.getAddress(derivationPath); + + console.log('transaction', transaction); + + const updatedTransaction = { + ...transaction, + Flags: 2147483648, + SigningPubKey: deviceData.publicKey.toUpperCase(), + }; + + console.log('Updated transaction', updatedTransaction); + + const encodedTransaction = encode(updatedTransaction); + console.log('encodedTransaction', encodedTransaction); + const signature = await xrplWallet.signTransaction(derivationPath, encodedTransaction); + + console.log('Signed transaction', signature); + const signedTransaction = { + ...updatedTransaction, + TxnSignature: signature, + }; + return signedTransaction; + } + + return { + xrplAddress, + connectLedgerWallet, + fetchXRPLAddress, + signTransaction, + isConnected, + }; +} diff --git a/src/app/hooks/use-xrpl-vaults.ts b/src/app/hooks/use-xrpl-vaults.ts index 927c9aab..c62025d8 100644 --- a/src/app/hooks/use-xrpl-vaults.ts +++ b/src/app/hooks/use-xrpl-vaults.ts @@ -4,6 +4,7 @@ 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 { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; import { useQuery, useQueryClient } from '@tanstack/react-query'; @@ -60,6 +61,7 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { const [isLoading, setIsLoading] = useState(true); const { networkType } = useContext(NetworkConfigurationContext); + const { rippleUserAddress } = useContext(RippleNetworkConfigurationContext); const issuerAddress = appConfiguration.rippleIssuerAddress; const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); @@ -69,7 +71,7 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { initialData: [], queryFn: fetchXRPLVaults, refetchInterval: 10000, - enabled: networkType === 'xrpl', + enabled: networkType === 'xrpl' && !!rippleUserAddress, }); async function fetchXRPLVaults(): Promise { @@ -80,7 +82,8 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { try { await connectRippleClient(xrplClient); - const xrplRawVaults = await getAllRippleVaults(xrplClient, issuerAddress); + const xrplRawVaults = await getAllRippleVaults(xrplClient, issuerAddress, rippleUserAddress); + console.log('xrpVaults', xrplRawVaults); const xrplVaults = xrplRawVaults.map(formatVault); if (previousVaults?.length === 0) { diff --git a/src/app/providers/ripple-network-configuration.provider.tsx b/src/app/providers/ripple-network-configuration.provider.tsx index ae47314b..60c20e8c 100644 --- a/src/app/providers/ripple-network-configuration.provider.tsx +++ b/src/app/providers/ripple-network-configuration.provider.tsx @@ -2,6 +2,8 @@ import React, { createContext, useEffect, useState } from 'react'; import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; import { getRippleNetworkByID } from '@functions/configuration.functions'; +import { useXRPLLedger } from '@hooks/use-xrpl-ledger'; +import Xrp from '@ledgerhq/hw-app-xrp'; import { HasChildren } from '@models/has-children'; import { RippleNetwork, RippleNetworkConfiguration, RippleNetworkID } from '@models/ripple.models'; import { Client, Wallet } from 'dlc-btc-lib/models'; @@ -57,59 +59,46 @@ interface RippleNetworkConfigurationContext { enabledRippleNetworks: RippleNetwork[]; rippleUserAddress: string | undefined; isRippleWalletConnected: boolean; + connectLedgerWallet: (derivationPath: string) => Promise; + signTransaction: (transaction: any) => Promise; rippleWallet: RippleWallet | undefined; - rippleWalletClient: Wallet | undefined; - setRippleWalletClient: (seed: string) => void; rippleClient: Client; setRippleWallet: (rippleWallet: RippleWallet) => void; - setIsRippleWalletConnected: (isConnected: boolean) => void; isRippleNetworkConfigurationLoading: boolean; } export const RippleNetworkConfigurationContext = createContext({ - setRippleNetworkID: () => {}, + setRippleNetworkID: (rippleNetworkID: RippleNetworkID) => {}, rippleNetworkConfiguration: defaultRippleNetworkConfiguration, enabledRippleNetworks, rippleUserAddress: undefined, - rippleWallet: undefined, - rippleWalletClient: undefined, - setRippleWalletClient: () => {}, + connectLedgerWallet: async (_derivationPath: string) => {}, + signTransaction: async (_transaction: any) => {}, rippleClient: new Client(appConfiguration.xrplWebsocket), + rippleWallet: undefined, + setRippleWallet: (_rippleWallet: RippleWallet) => {}, isRippleWalletConnected: false, - setRippleWallet: () => {}, - setIsRippleWalletConnected: () => {}, isRippleNetworkConfigurationLoading: false, }); export function RippleNetworkConfigurationContextProvider({ children, }: HasChildren): React.JSX.Element { + const { xrplAddress, connectLedgerWallet, signTransaction, isConnected } = useXRPLLedger(); const [rippleNetworkID, setRippleNetworkID] = useState(defaultRippleNetwork.id); const [rippleNetworkConfiguration, setRippleNetworkConfiguration] = useState(defaultRippleNetworkConfiguration); - const [rippleUserAddress, setRippleUserAddress] = useState( - 'rfvtbrXSxLsxVWDktR4sdzjJgv8EnMKFKG' - ); - - const [rippleWalletClient, setRippleWalletClient] = useState(undefined); - console.log('rippleWalletClient', rippleWalletClient); - const rippleClient = getRippleClient(rippleNetworkConfiguration.websocketURL); const [rippleWallet, setRippleWallet] = useState(undefined); - function setRippleWalletClientForUser(seed: string) { - const rippleWalletClient = getRippleWallet(seed); - setRippleUserAddress(rippleWalletClient.classicAddress); - setRippleWalletClient(rippleWalletClient); - } - - const rippleWalletC = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - const [isRippleNetworkConfigurationLoading, setIsRippleNetworkConfigurationLoading] = useState(false); - const [isRippleWalletConnected, setIsRippleWalletConnected] = useState(false); + useEffect(() => { + console.log('isConnected', isConnected); + }, [isConnected]); + useEffect(() => { setIsRippleNetworkConfigurationLoading(true); @@ -128,14 +117,13 @@ export function RippleNetworkConfigurationContextProvider({ rippleNetworkConfiguration, isRippleNetworkConfigurationLoading, enabledRippleNetworks, - rippleUserAddress, - rippleWallet, - rippleWalletClient: rippleWalletC, - setRippleWalletClient: setRippleWalletClientForUser, + rippleUserAddress: xrplAddress, + signTransaction, rippleClient, + rippleWallet, setRippleWallet, - isRippleWalletConnected, - setIsRippleWalletConnected, + connectLedgerWallet, + isRippleWalletConnected: isConnected, setRippleNetworkID, }} > diff --git a/src/app/providers/xrp-wallet-context-provider.tsx b/src/app/providers/xrp-wallet-context-provider.tsx new file mode 100644 index 00000000..fb1e0ee5 --- /dev/null +++ b/src/app/providers/xrp-wallet-context-provider.tsx @@ -0,0 +1,64 @@ +// import { createContext, useState } from 'react'; + +// import { HasChildren } from '@models/has-children'; +// import { BitcoinWalletType, XRPWalletType } from '@models/wallet'; +// import { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; + +// export enum XRPWalletContextState { +// INITIAL = 0, +// SELECTED = 1, +// READY = 2, +// } + +// interface XRPWalletContextProviderType { +// xrpWalletType: XRPWalletType | undefined; +// setXRPWalletType: React.Dispatch>; +// xrpWalletContextState: XRPWalletContextState; +// setXRPWalletContextState: React.Dispatch>; +// dlcHandler: SoftwareWalletDLCHandler | LedgerDLCHandler | undefined; +// setDLCHandler: React.Dispatch< +// React.SetStateAction +// >; +// resetBitcoinWalletContext: () => void; +// } + +// export const XRPWalletContext = createContext({ +// bitcoinWalletType: undefined, +// setBitcoinWalletType: () => {}, +// bitcoinWalletContextState: BitcoinWalletContextState.INITIAL, +// setBitcoinWalletContextState: () => {}, +// dlcHandler: undefined, +// setDLCHandler: () => {}, +// resetBitcoinWalletContext: () => {}, +// }); + +// export function BitcoinWalletContextProvider({ children }: HasChildren): React.JSX.Element { +// const [bitcoinWalletContextState, setBitcoinWalletContextState] = +// useState(BitcoinWalletContextState.INITIAL); +// const [bitcoinWalletType, setBitcoinWalletType] = useState( +// BitcoinWalletType.Leather +// ); +// const [dlcHandler, setDLCHandler] = useState(); + +// function resetBitcoinWalletContext() { +// setBitcoinWalletContextState(BitcoinWalletContextState.INITIAL); +// setBitcoinWalletType(undefined); +// setDLCHandler(undefined); +// } + +// return ( +// +// {children} +// +// ); +// } diff --git a/src/shared/models/wallet.ts b/src/shared/models/wallet.ts index 31189491..69dcb3e7 100644 --- a/src/shared/models/wallet.ts +++ b/src/shared/models/wallet.ts @@ -28,4 +28,28 @@ const unisat: BitcoinWallet = { logo: '/images/logos/unisat-logo.svg', }; +export enum XRPWalletType { + Ledger = 'Ledger', + Gem = 'Gem', +} + +export interface XRPWallet { + id: XRPWalletType; + name: string; + icon: string; +} + +const ledgerXRP: XRPWallet = { + id: XRPWalletType.Ledger, + name: 'Ledger', + icon: '/images/logos/ledger-logo.svg', +}; + +// const gemXRP: XRPWallet = { +// id: XRPWalletType.Gem, +// name: 'Gem', +// icon: '/images/logos/gem-logo.svg', +// }; + +export const xrpWallets: XRPWallet[] = [ledgerXRP]; export const bitcoinWallets: BitcoinWallet[] = [leather, ledger, unisat]; diff --git a/yarn.lock b/yarn.lock index d9ac1d9e..d580a930 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1981,6 +1981,39 @@ tiny-secp256k1 "1.1.6" varuint-bitcoin "1.1.2" +"@ledgerhq/hw-app-xrp@^6.29.4": + version "6.29.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-6.29.4.tgz#b90d847f2cf8ddca9ca0068f5079f199c334314d" + integrity sha512-fEnqkwmEmcThGVtxLUQX9x4KB1E659Ke1dYuCZSXX4346+h3PCa7cfeKN/VRZNH8HQJgiYi53LqqYzwWXB5zbg== + dependencies: + "@ledgerhq/hw-transport" "^6.31.4" + bip32-path "0.4.2" + +"@ledgerhq/hw-transport-node-hid-noevents@^6.30.5": + version "6.30.5" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.30.5.tgz#87feed538aa373a61e21e0c2849a5e4c9a07528b" + integrity sha512-nOPbhFU87LgLERVAQ+HhxV8E8c+7d8ptllkgiJUc4QwL2z9zkIOAEtgdvCaZ066Oi9XGnln/GF1oAgByYnYDPw== + dependencies: + "@ledgerhq/devices" "^8.4.4" + "@ledgerhq/errors" "^6.19.1" + "@ledgerhq/hw-transport" "^6.31.4" + "@ledgerhq/logs" "^6.12.0" + node-hid "2.1.2" + +"@ledgerhq/hw-transport-node-hid@^6.29.5": + version "6.29.5" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-6.29.5.tgz#4e140ba3fc152277ae8af2b133287fa803093fc9" + integrity sha512-2bAp4K50V1kdCufU9JdQPcw4aLyvA+yQRJU/X39B+PC+rnis40gEbqNh0henhzv876sXdbNk6G/MkDWXpwDIow== + dependencies: + "@ledgerhq/devices" "^8.4.4" + "@ledgerhq/errors" "^6.19.1" + "@ledgerhq/hw-transport" "^6.31.4" + "@ledgerhq/hw-transport-node-hid-noevents" "^6.30.5" + "@ledgerhq/logs" "^6.12.0" + lodash "^4.17.21" + node-hid "2.1.2" + usb "2.9.0" + "@ledgerhq/hw-transport-webusb@^6.28.6": version "6.28.6" resolved "https://registry.npmjs.org/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.28.6.tgz" @@ -2001,7 +2034,7 @@ "@ledgerhq/logs" "^6.12.0" events "^3.3.0" -"@ledgerhq/hw-transport@^6.31.2": +"@ledgerhq/hw-transport@^6.31.2", "@ledgerhq/hw-transport@^6.31.4": version "6.31.4" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz#9b23a6de4a4caaa5c24b149c2dea8adde46f0eb1" integrity sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A== @@ -3353,6 +3386,11 @@ resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/w3c-web-usb@^1.0.6": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz#cf89cccd2d93b6245e784c19afe0a9f5038d4528" + integrity sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ== + "@typescript-eslint/eslint-plugin@^6.9.1": version "6.21.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz" @@ -4132,7 +4170,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -bindings@^1.3.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -4144,7 +4182,7 @@ bip174@^2.0.1, bip174@^2.1.1: resolved "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz" integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== -bip32-path@^0.4.2: +bip32-path@0.4.2, bip32-path@^0.4.2: version "0.4.2" resolved "https://registry.npmjs.org/bip32-path/-/bip32-path-0.4.2.tgz" integrity sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ== @@ -4234,6 +4272,15 @@ bitcoinjs-lib@^6.1.3: typeforce "^1.11.3" varuint-bitcoin "^1.1.2" +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" @@ -4329,6 +4376,14 @@ 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@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + buffer@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" @@ -4447,6 +4502,11 @@ chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz" @@ -5083,6 +5143,13 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz" @@ -5090,6 +5157,11 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -5160,6 +5232,11 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" @@ -5186,6 +5263,8 @@ dir-glob@^3.0.1: version "2.3.0" dependencies: "@ledgerhq/hw-app-btc" "10.4.1" + "@ledgerhq/hw-app-xrp" "^6.29.4" + "@ledgerhq/hw-transport-node-hid" "^6.29.5" "@noble/hashes" "1.4.0" "@scure/base" "1.1.8" "@scure/btc-signer" "1.3.2" @@ -5853,6 +5932,11 @@ execa@^8.0.1: signal-exit "^4.1.0" strip-final-newline "^3.0.0" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + extension-port-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extension-port-stream/-/extension-port-stream-3.0.0.tgz#00a7185fe2322708a36ed24843c81bd754925fef" @@ -6012,6 +6096,11 @@ framesync@6.1.2: dependencies: tslib "2.4.0" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -6102,6 +6191,11 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -6307,7 +6401,7 @@ idb-keyval@^6.2.1: resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== -ieee754@^1.2.1: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -6348,6 +6442,11 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz" @@ -7010,6 +7109,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" @@ -7034,7 +7138,7 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -7044,6 +7148,11 @@ mipd@0.0.7: resolved "https://registry.yarnpkg.com/mipd/-/mipd-0.0.7.tgz#bb5559e21fa18dc3d9fe1c08902ef14b7ce32fd9" integrity sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg== +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mlly@^1.2.0, mlly@^1.4.0: version "1.6.1" resolved "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz" @@ -7116,6 +7225,11 @@ nanoid@^3.3.7: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + napi-wasm@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" @@ -7126,16 +7240,33 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +node-abi@^3.3.0: + version "3.68.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.68.0.tgz#8f37fb02ecf4f43ebe694090dcb52e0c4cc4ba25" + integrity sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A== + dependencies: + semver "^7.3.5" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-addon-api@^3.0.2: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + node-addon-api@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== +node-addon-api@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + node-addon-api@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" @@ -7163,6 +7294,20 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== +node-gyp-build@^4.5.0: + version "4.8.2" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.2.tgz#4f802b71c1ab2ca16af830e6c1ea7dd1ad9496fa" + integrity sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw== + +node-hid@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-2.1.2.tgz#3145fa86ed4336a402a71e9f372c54213b88797c" + integrity sha512-qhCyQqrPpP93F/6Wc/xUR7L8mAJW0Z6R7HMQV8jCHHksAxNDe/4z4Un/H9CpLOT+5K39OPyt9tIQlavxWES3lg== + dependencies: + bindings "^1.5.0" + node-addon-api "^3.0.2" + prebuild-install "^7.1.1" + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" @@ -7527,6 +7672,24 @@ preact@^10.16.0: resolved "https://registry.yarnpkg.com/preact/-/preact-10.23.1.tgz#d400107289bc979881c5212cb5f5cd22cd1dc38c" integrity sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A== +prebuild-install@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -7667,6 +7830,16 @@ randombytes@^2.0.1, randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-apexcharts@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz" @@ -7843,7 +8016,7 @@ readable-stream@^2.3.3: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -8243,6 +8416,20 @@ signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + sister@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz" @@ -8442,6 +8629,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-literal@^1.0.1: version "1.3.0" resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz" @@ -8545,6 +8737,27 @@ system-architecture@^0.1.0: resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA== +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -8684,6 +8897,13 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + turbo-stream@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" @@ -8887,6 +9107,15 @@ urlpattern-polyfill@8.0.2: resolved "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz" integrity sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ== +usb@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/usb/-/usb-2.9.0.tgz#8ae3b175f93bee559400bff33491eee63406b6a2" + integrity sha512-G0I/fPgfHUzWH8xo2KkDxTTFruUWfppgSFJ+bQxz/kVY2x15EQ/XDB7dqD1G432G4gBG4jYQuF3U7j/orSs5nw== + dependencies: + "@types/w3c-web-usb" "^1.0.6" + node-addon-api "^6.0.0" + node-gyp-build "^4.5.0" + use-callback-ref@^1.3.0: version "1.3.2" resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz" From 07215b7fc5dfe526bf4a1c48576e8a21aba41920 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 15 Oct 2024 14:29:02 +0200 Subject: [PATCH 10/39] feat: add working ledger wallet for xrpl --- config.testnet.json | 2 +- .../functions/submit-xrpl-vault-request.ts | 4 - src/app/app.tsx | 51 ++-- src/app/components/account/account.tsx | 41 +-- .../account/components/account-menu.tsx | 4 +- .../burn-transaction-screen.tsx | 42 ++-- .../setup-vault-screen/setup-vault-screen.tsx | 12 +- .../modals/components/modal-container.tsx | 5 - .../components/select-ripple-wallet-menu.tsx | 5 +- .../select-wallet-modal.tsx | 55 ++--- .../successful-flow-modal.tsx | 5 +- .../my-vaults-header/my-vaults-header.tsx | 6 - .../components/my-vaults/my-vaults-large.tsx | 7 +- .../vault.detaills/vault.details.tsx | 4 +- src/app/hooks/use-active-tabs.ts | 10 +- src/app/hooks/use-connected.ts | 9 +- src/app/hooks/use-evm-vaults.ts | 8 +- src/app/hooks/use-psbt.ts | 4 +- src/app/hooks/use-xrpl-ledger.ts | 203 +++++++++------ src/app/hooks/use-xrpl-vaults.ts | 14 +- .../providers/balance-context-provider.tsx | 26 +- .../ripple-network-configuration.provider.tsx | 35 +-- .../providers/xrp-wallet-context-provider.tsx | 121 ++++----- src/shared/models/ledger.ts | 1 + src/shared/models/wallet.ts | 2 +- yarn.lock | 233 +----------------- 26 files changed, 340 insertions(+), 569 deletions(-) diff --git a/config.testnet.json b/config.testnet.json index ff81f86d..87c431c6 100644 --- a/config.testnet.json +++ b/config.testnet.json @@ -1,6 +1,6 @@ { "appEnvironment": "testnet", - "coordinatorURL": "https://testnet-ripple.dlc.link/attestor-1", + "coordinatorURL": "http://localhost:8811", "enabledEthereumNetworkIDs": ["421614", "84532", "11155111"], "enabledRippleNetworkIDs": ["1"], "bitcoinNetwork": "testnet", diff --git a/netlify/functions/submit-xrpl-vault-request.ts b/netlify/functions/submit-xrpl-vault-request.ts index 03882eb0..6bfdd12b 100644 --- a/netlify/functions/submit-xrpl-vault-request.ts +++ b/netlify/functions/submit-xrpl-vault-request.ts @@ -34,16 +34,12 @@ const handler: Handler = async (event: HandlerEvent) => { const coordinatorURL = event.queryStringParameters.coordinatorURL; const userXRPLAddress = event.queryStringParameters.userXRPLAddress; - console.log('coordinatorURL', coordinatorURL); - console.log('userXRPLAddress', userXRPLAddress); - await submitSetupXRPLVaultRequest(coordinatorURL, userXRPLAddress); return { statusCode: 200, }; } catch (error: any) { - console.log('IDE NEEEEEEZZZZZZZZ', error); return { statusCode: 500, body: JSON.stringify({ diff --git a/src/app/app.tsx b/src/app/app.tsx index 12bb7ceb..44aec720 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -15,6 +15,7 @@ import { EthereumNetworkConfigurationContextProvider } from '@providers/ethereum import { NetworkConfigurationContextProvider } from '@providers/network-configuration.provider'; import { ProofOfReserveContextProvider } from '@providers/proof-of-reserve-context-provider'; import { RippleNetworkConfigurationContextProvider } from '@providers/ripple-network-configuration.provider'; +import { XRPWalletContextProvider } from '@providers/xrp-wallet-context-provider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WagmiProvider } from 'wagmi'; @@ -32,31 +33,31 @@ export function App(): React.JSX.Element { - {/* */} - - - - - - - } /> - } /> - {/* } /> */} - } /> - } /> - } - /> - } /> - } /> - - - - - - - {/* */} + + + + + + + + } /> + } /> + {/* } /> */} + } /> + } /> + } + /> + } /> + } /> + + + + + + + diff --git a/src/app/components/account/account.tsx b/src/app/components/account/account.tsx index 38776b7e..878cd512 100644 --- a/src/app/components/account/account.tsx +++ b/src/app/components/account/account.tsx @@ -3,10 +3,10 @@ import { useDispatch } from 'react-redux'; import { Button, HStack } from '@chakra-ui/react'; import { AccountMenu } from '@components/account/components/account-menu'; -import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; import { useNetworkConnection } from '@hooks/use-connected'; +import { XRPWallet, xrpWallets } from '@models/wallet'; 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 { Connector, useAccount, useDisconnect } from 'wagmi'; @@ -19,18 +19,23 @@ export function Account(): React.JSX.Element { const { address: ethereumUserAddress, connector: ethereumWallet } = useAccount(); const { disconnect: disconnectEthereumWallet } = useDisconnect(); - const { rippleUserAddress, rippleWallet } = useContext(RippleNetworkConfigurationContext); + const { + userAddress: rippleUserAddress, + xrpWalletType, + resetXRPWalletContext, + } = useContext(XRPWalletContext); - function getWalletInformation(): - | { address: string; wallet: RippleWallet | Connector } - | undefined { + function getWalletInformation(): { address: string; wallet: XRPWallet | Connector } | undefined { switch (networkType) { case 'evm': if (!ethereumUserAddress || !ethereumWallet) return undefined; return { address: ethereumUserAddress, wallet: ethereumWallet }; case 'xrpl': if (!rippleUserAddress) return undefined; - return { address: rippleUserAddress, wallet: rippleWallet! }; + return { + address: rippleUserAddress, + wallet: xrpWallets.find(xrpWallet => xrpWallet.id === xrpWalletType)!, + }; default: throw new Error('Invalid Network Type'); } @@ -41,17 +46,17 @@ export function Account(): React.JSX.Element { } function onDisconnectWalletClick(): void { - // switch (networkType) { - // case 'evm': - // disconnectEthereumWallet(); - // break; - // case 'xrpl': - // setIsRippleWalletConnected(false); - // break; - // default: - // break; - // } - // dispatch(mintUnmintActions.resetMintUnmintState()); + switch (networkType) { + case 'evm': + disconnectEthereumWallet(); + break; + case 'xrpl': + resetXRPWalletContext(); + break; + default: + break; + } + dispatch(mintUnmintActions.resetMintUnmintState()); } return ( diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index 70aa8411..553f7cdb 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -9,13 +9,13 @@ import { Text, useBreakpointValue, } from '@chakra-ui/react'; -import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; +import { XRPWallet } from '@models/wallet'; import { truncateAddress } from 'dlc-btc-lib/utilities'; import { Connector } from 'wagmi'; interface AccountMenuProps { address?: string; - wallet?: Connector | RippleWallet; + wallet?: Connector | XRPWallet; handleDisconnectWallet: () => void; } 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 225f739d..b77930db 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,22 +5,18 @@ 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 { 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 { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.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 { - connectRippleClient, - createCheck, - getRippleClient, - getRippleWallet, -} from 'dlc-btc-lib/ripple-functions'; +import { getRippleClient, getRippleVault } from 'dlc-btc-lib/ripple-functions'; import { shiftValue } from 'dlc-btc-lib/utilities'; -import { decode } from 'ripple-binary-codec'; interface BurnTokenTransactionFormProps { isBitcoinWalletLoading: [boolean, string]; @@ -40,9 +36,8 @@ export function BurnTokenTransactionForm({ const { networkType } = useContext(NetworkConfigurationContext); const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); - const { rippleUserAddress, rippleClient, signTransaction } = useContext( - RippleNetworkConfigurationContext - ); + const { xrpHandler } = useContext(XRPWalletContext); + const { handleCreateCheck, connectLedgerWallet, isLoading } = useXRPLLedger(); const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); @@ -60,21 +55,16 @@ export function BurnTokenTransactionForm({ if (!currentVault) return; setIsSubmitting(true); if (networkType === 'xrpl') { - await connectRippleClient(rippleClient); - const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - - const check = await createCheck( + const rippleClient = getRippleClient(appConfiguration.xrplWebsocket); + const vault = await getRippleVault( rippleClient, - rippleUserAddress!, appConfiguration.rippleIssuerAddress, - undefined, - formattedWithdrawAmount.toString(), - currentVault.uuid.slice(2) + currentVault.uuid ); - const signedCheck = await signTransaction(check); - console.log('signedCheck', signedCheck); - const submitResponse = await rippleClient.submitAndWait(signedCheck); - console.log('Check submitted', submitResponse); + await connectLedgerWallet("44'/144'/0'/0/0"); + + if (!xrpHandler) throw new Error('No XRP Handler'); + await handleCreateCheck(xrpHandler, vault, withdrawAmount); } else if (networkType === 'evm') { const currentRisk = await fetchUserEthereumAddressRiskLevel(); if (currentRisk === 'High') throw new Error('Risk Level is too high'); @@ -115,7 +105,11 @@ export function BurnTokenTransactionForm({ handleButtonClick={handleButtonClick} depositLimit={depositLimit} bitcoinWalletContextState={bitcoinWalletContextState} - isBitcoinWalletLoading={isBitcoinWalletLoading} + isBitcoinWalletLoading={ + currentVault?.valueLocked === currentVault?.valueMinted + ? isLoading + : isBitcoinWalletLoading + } userEthereumAddressRiskLevel={userEthereumAddressRiskLevel} isUserEthereumAddressRiskLevelLoading={isUserEthereumAddressRiskLevelLoading} handleCancelButtonClick={handleCancel} diff --git a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx index 44fbfd0f..a2d3546b 100644 --- a/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx +++ b/src/app/components/mint-unmint/components/setup-vault-screen/setup-vault-screen.tsx @@ -1,20 +1,23 @@ import { useContext, useState } from 'react'; import { Button, VStack, useToast } from '@chakra-ui/react'; +import { TransactionScreenWalletInformation } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information'; import { submitSetupXRPLVaultRequest } from '@functions/attestor-request.functions'; import { useEthersSigner } from '@functions/configuration.functions'; +import { useXRPLLedger } from '@hooks/use-xrpl-ledger'; 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 { setupVault } from 'dlc-btc-lib/ethereum-functions'; -import { getRippleWallet } from 'dlc-btc-lib/ripple-functions'; import { SetupVaultScreenVaultGraphics } from './components/setup-vault-screen.vault-graphics'; export function SetupVaultScreen(): React.JSX.Element { const toast = useToast(); const { networkType } = useContext(NetworkConfigurationContext); - const { rippleUserAddress } = useContext(RippleNetworkConfigurationContext); + const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); + const { xrpHandler } = useContext(XRPWalletContext); + const { handleSetTrustLine, connectLedgerWallet, isLoading } = useXRPLLedger(); const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); @@ -26,6 +29,8 @@ export function SetupVaultScreen(): React.JSX.Element { try { setIsSubmitting(true); if (networkType === 'xrpl') { + await connectLedgerWallet("44'/144'/0'/0/1"); + await handleSetTrustLine(xrpHandler!); await submitSetupXRPLVaultRequest(rippleUserAddress!); } else if (networkType === 'evm') { await setupVault(ethereumNetworkConfiguration.dlcManagerContract.connect(signer!)); @@ -55,6 +60,7 @@ export function SetupVaultScreen(): React.JSX.Element { > Create Vault + ); } diff --git a/src/app/components/modals/components/modal-container.tsx b/src/app/components/modals/components/modal-container.tsx index e1f26500..84b71b2e 100644 --- a/src/app/components/modals/components/modal-container.tsx +++ b/src/app/components/modals/components/modal-container.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { SelectWalletModal } from '@components/modals/select-wallet-modal/select-wallet-modal'; @@ -28,10 +27,6 @@ export function ModalContainer(): React.JSX.Element { dispatch(actionCreator()); }; - useEffect(() => { - console.log(isSuccesfulFlowModalOpen); - }, [isSuccesfulFlowModalOpen]); - return ( <> void; } 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 31bc5639..0048f65e 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 @@ -5,9 +5,13 @@ import { HStack, ScaleFade, Tab, TabList, Tabs, Text, VStack } from '@chakra-ui/ 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 { useXRPLLedger } from '@hooks/use-xrpl-ledger'; import { RippleNetworkID } from '@models/ripple.models'; +import { 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'; import { EthereumNetworkID } from 'dlc-btc-lib/models'; import { delay } from 'dlc-btc-lib/utilities'; import { Connector, useConfig, useConnect } from 'wagmi'; @@ -15,38 +19,20 @@ import { Connector, useConfig, useConnect } from 'wagmi'; import { SelectEthereumWalletMenu } from './components/select-ethereum-wallet-menu'; import { SelectRippleWalletMenu } from './components/select-ripple-wallet-menu'; -export interface RippleWallet { - id: string; - name: string; - icon: string; -} - -const seedWallet: RippleWallet = { - id: 'seed', - name: 'Seed Phrase', - icon: './images/logos/xpr-logo.svg', -}; - -const ledgerWallet: RippleWallet = { - id: 'ledger', - name: 'Ledger', - icon: './images/logos/ledger-logo.svg', -}; - -const rippleWallets = [ledgerWallet]; - export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { const { connect, isPending, isSuccess, connectors } = useConnect(); const { chains: ethereumNetworks } = useConfig(); const { setNetworkType } = useContext(NetworkConfigurationContext); const { - enabledRippleNetworks, - isRippleWalletConnected, - setRippleWallet, - rippleWallet, - connectLedgerWallet, - } = useContext(RippleNetworkConfigurationContext); + setXRPWalletType, + setXRPWalletContextState, + setUserAddress, + xrpWalletContextState, + setXRPHandler, + } = useContext(XRPWalletContext); + const { connectLedgerWallet, isLoading } = useXRPLLedger(); + const { enabledRippleNetworks } = useContext(RippleNetworkConfigurationContext); const ethereumNetworkIDs = ethereumNetworks.map( ethereumNetwork => ethereumNetwork.id.toString() as EthereumNetworkID @@ -66,11 +52,11 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): const networkTypes = ['evm', 'xrpl']; useEffect(() => { - if (isSuccess || isRippleWalletConnected) { + if (isSuccess || xrpWalletContextState === XRPWalletContextState.READY) { void handleCloseAfterSuccess(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSuccess, isRippleWalletConnected]); + }, [isSuccess, xrpWalletContextState]); async function handleCloseAfterSuccess() { await delay(1000); @@ -90,11 +76,15 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): } } - async function handleConnectRippleWallet(id: string) { + async function handleConnectRippleWallet() { setNetworkType('xrpl'); - setRippleWallet(rippleWallets[0]); + setXRPWalletType(xrpWallets[0].id); + + const { xrpHandler, userAddress } = await connectLedgerWallet("44'/144'/0'/0/1"); - await connectLedgerWallet("44'/144'/0'/0/0"); + setXRPHandler(xrpHandler); + setUserAddress(userAddress); + setXRPWalletContextState(XRPWalletContextState.READY); } const handleChangeNetwork = (networkID: EthereumNetworkID | RippleNetworkID) => { @@ -155,13 +145,14 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): handleConnectWallet={handleConnectEthereumWallet} /> )) - : rippleWallets.map(rippleWallet => ( + : xrpWallets.map(rippleWallet => ( ))} + diff --git a/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx b/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx index a85e4eb4..38daf8d8 100644 --- a/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx +++ b/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx @@ -24,13 +24,10 @@ function getModalText(flow: 'mint' | 'burn', assetAmount?: number): string { export function SuccessfulFlowModal({ isOpen, handleClose, - vaultUUID, vault, flow, assetAmount, }: SuccessfulFlowModalProps): React.JSX.Element { - console.log('vaultUUID', vaultUUID); - return ( handleClose()}> @@ -39,7 +36,7 @@ export function SuccessfulFlowModal({ {getModalText(flow, assetAmount)} - + diff --git a/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx b/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx index 09def6aa..6c6d5b6b 100644 --- a/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx +++ b/src/app/components/my-vaults/components/my-vaults-header/my-vaults-header.tsx @@ -1,5 +1,3 @@ -import { useEffect } from 'react'; - import { Divider, HStack, Text, VStack } from '@chakra-ui/react'; import { MyVaultsHeaderBalanceInfo } from './components/my-vaults-header-balance-info'; @@ -15,10 +13,6 @@ export function MyVaultsLargeHeader({ dlcBTCBalance, lockedBTCBalance, }: MyVaultsLargeHeaderProps): React.JSX.Element { - useEffect(() => { - console.log('dlcBTCBalance', dlcBTCBalance); - console.log('lockedBTCBalance', lockedBTCBalance); - }, [dlcBTCBalance, lockedBTCBalance]); return ( diff --git a/src/app/components/my-vaults/my-vaults-large.tsx b/src/app/components/my-vaults/my-vaults-large.tsx index 01592006..76ef0af0 100644 --- a/src/app/components/my-vaults/my-vaults-large.tsx +++ b/src/app/components/my-vaults/my-vaults-large.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect } from 'react'; +import { useContext } from 'react'; import { HStack } from '@chakra-ui/react'; import { VaultsListGroupBlankContainer } from '@components/vaults-list/components/vaults-list-group-blank-container'; @@ -16,11 +16,6 @@ export function MyVaultsLarge(): React.JSX.Element { const { isConnected } = useNetworkConnection(); const { dlcBTCBalance, lockedBTCBalance } = useContext(BalanceContext); - useEffect(() => { - console.log('dlcBTCBalance', dlcBTCBalance); - console.log('lockedBTCBalance', lockedBTCBalance); - }, [dlcBTCBalance, lockedBTCBalance]); - const { readyVaults, pendingVaults, fundedVaults, closingVaults, closedVaults, allVaults } = useContext(VaultContext); diff --git a/src/app/components/vault/components/vault.detaills/vault.details.tsx b/src/app/components/vault/components/vault.detaills/vault.details.tsx index afe9ed4f..058d5dd4 100644 --- a/src/app/components/vault/components/vault.detaills/vault.details.tsx +++ b/src/app/components/vault/components/vault.detaills/vault.details.tsx @@ -54,9 +54,9 @@ export function VaultDetails({ function handleResumeClick() { navigate('/mint-withdraw'); if (vaultTotalLockedValue === vaultTotalMintedValue) { - dispatch(mintUnmintActions.setMintStep([2, vaultUUID])); + dispatch(mintUnmintActions.setMintStep([2, vaultUUID, vault])); } else { - dispatch(mintUnmintActions.setUnmintStep([2, vaultUUID])); + dispatch(mintUnmintActions.setUnmintStep([2, vaultUUID, vault])); } } diff --git a/src/app/hooks/use-active-tabs.ts b/src/app/hooks/use-active-tabs.ts index a666510d..bde3ed92 100644 --- a/src/app/hooks/use-active-tabs.ts +++ b/src/app/hooks/use-active-tabs.ts @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'; 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, XRPWalletContextState } from '@providers/xrp-wallet-context-provider'; import { useQuery } from '@tanstack/react-query'; import { isUserWhitelisted, isWhitelistingEnabled } from 'dlc-btc-lib/ethereum-functions'; import { useAccount } from 'wagmi'; @@ -19,9 +20,8 @@ export function useActiveTabs(): UseActiveTabsReturnType { EthereumNetworkConfigurationContext ); - const { isRippleWalletConnected, isRippleNetworkConfigurationLoading } = useContext( - RippleNetworkConfigurationContext - ); + const { isRippleNetworkConfigurationLoading } = useContext(RippleNetworkConfigurationContext); + const { xrpWalletContextState } = useContext(XRPWalletContext); const { networkType } = useContext(NetworkConfigurationContext); async function shouldActivateTabs(): Promise { @@ -35,12 +35,12 @@ export function useActiveTabs(): UseActiveTabsReturnType { return await isUserWhitelisted(dlcManagerContract, address); } else { navigate('/mint-withdraw'); - return isRippleWalletConnected; + return xrpWalletContextState === XRPWalletContextState.READY; } } const { data: isActiveTabs } = useQuery({ - queryKey: ['activeTabs', chain, address, networkType, isRippleWalletConnected], + queryKey: ['activeTabs', chain, address, networkType, xrpWalletContextState], queryFn: shouldActivateTabs, enabled: networkType === 'evm' diff --git a/src/app/hooks/use-connected.ts b/src/app/hooks/use-connected.ts index a5b2a185..9e57215a 100644 --- a/src/app/hooks/use-connected.ts +++ b/src/app/hooks/use-connected.ts @@ -1,7 +1,7 @@ import { useContext, useEffect, useState } from 'react'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; -import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { XRPWalletContext, XRPWalletContextState } from '@providers/xrp-wallet-context-provider'; import { useAccount } from 'wagmi'; interface UseNetworkConnectionReturnType { @@ -14,7 +14,7 @@ export function useNetworkConnection(): UseNetworkConnectionReturnType { const { networkType } = useContext(NetworkConfigurationContext); const { isConnected: isEthereumWalletConnected } = useAccount(); - const { isRippleWalletConnected } = useContext(RippleNetworkConfigurationContext); + const { xrpWalletContextState } = useContext(XRPWalletContext); useEffect(() => { switch (networkType) { @@ -22,13 +22,12 @@ export function useNetworkConnection(): UseNetworkConnectionReturnType { setIsConnected(isEthereumWalletConnected); break; case 'xrpl': - console.log('isRippleWalletConnected', isRippleWalletConnected); - setIsConnected(isRippleWalletConnected); + setIsConnected(xrpWalletContextState === XRPWalletContextState.READY); break; default: setIsConnected(false); } - }, [networkType, isRippleWalletConnected, isEthereumWalletConnected]); + }, [networkType, xrpWalletContextState, isEthereumWalletConnected]); return { isConnected }; } diff --git a/src/app/hooks/use-evm-vaults.ts b/src/app/hooks/use-evm-vaults.ts index 9b8660a1..571899d1 100644 --- a/src/app/hooks/use-evm-vaults.ts +++ b/src/app/hooks/use-evm-vaults.ts @@ -88,7 +88,13 @@ export function useEVMVaults(): useEVMVaultsReturnType { ); const evmVaults = evmRawVaults.map(formatVault); - if (previousVaults?.length === 0) { + if ( + previousVaults?.length === 0 && + evmVaults.length === 1 && + evmVaults[0].state === VaultState.READY + ) { + handleVaultStateChange(previousVaults[0], evmVaults[0]); + } else if (previousVaults?.length === 0) { return evmVaults; } diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 8021b389..d4431934 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -7,7 +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 { submitFundingPSBT, @@ -34,7 +34,7 @@ export function usePSBT(): UsePSBTReturnType { ethereumNetworkConfiguration: { dlcManagerContract, ethereumAttestorChainID }, } = useContext(EthereumNetworkConfigurationContext); const { address: ethereumUserAddress } = useAccount(); - const { rippleUserAddress } = useContext(RippleNetworkConfigurationContext); + const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); const { bitcoinWalletType, dlcHandler, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); diff --git a/src/app/hooks/use-xrpl-ledger.ts b/src/app/hooks/use-xrpl-ledger.ts index 646af1d5..97e77850 100644 --- a/src/app/hooks/use-xrpl-ledger.ts +++ b/src/app/hooks/use-xrpl-ledger.ts @@ -1,111 +1,152 @@ -import { useState } from 'react'; +import { useContext, useState } from 'react'; import Xrp from '@ledgerhq/hw-app-xrp'; import Transport from '@ledgerhq/hw-transport-webusb'; -import { Client } from 'dlc-btc-lib/models'; -import { getRippleClient, setTrustLine } from 'dlc-btc-lib/ripple-functions'; -import { encode } from 'ripple-binary-codec'; +import { LEDGER_APPS_MAP } from '@models/ledger'; +import { RippleNetworkConfigurationContext } from '@providers/ripple-network-configuration.provider'; +import { LedgerXRPHandler } from 'dlc-btc-lib'; +import { LedgerError, RawVault } from 'dlc-btc-lib/models'; +import { delay, shiftValue } from 'dlc-btc-lib/utilities'; +import AppClient from 'ledger-bitcoin'; interface useXRPLLedgerReturnType { - xrplAddress: string | undefined; - isConnected: boolean; - connectLedgerWallet: (derivationPath: string) => Promise; - fetchXRPLAddress: () => Promise; - signTransaction: (transaction: any) => Promise; + isLoading: [boolean, string]; + connectLedgerWallet: ( + derivationPath: string + ) => Promise<{ xrpHandler: LedgerXRPHandler; userAddress: string }>; + handleCreateCheck: ( + xrpHandler: LedgerXRPHandler, + vault: RawVault, + withdrawAmount: number + ) => Promise; + handleSetTrustLine: (xrpHandler: LedgerXRPHandler) => Promise; } +type TransportInstance = Awaited>; + export function useXRPLLedger(): useXRPLLedgerReturnType { - const [derivationPath, setDerivationPath] = useState(undefined); - const [xrplWallet, setXRPLWallet] = useState(undefined); - const [xrplAddress, setXRPLAddress] = useState(undefined); - const [isConnected, setIsConnected] = useState(false); + const { rippleClient } = useContext(RippleNetworkConfigurationContext); + + const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); + + // Reference: https://github.com/LedgerHQ/ledger-live/blob/v22.0.1/src/hw/quitApp.ts\ + /** + * Quits the Ledger App. + * @param transport The Ledger Transport. + * @returns A Promise that resolves when the Ledger App is quit. + */ + async function quitApp(transport: TransportInstance): Promise { + await transport.send(0xb0, 0xa7, 0x00, 0x00); + } + + // Reference: https://github.com/LedgerHQ/ledger-live/blob/v22.0.1/src/hw/openApp.ts + /** + * Opens the Ledger App. + * @param transport The Ledger Transport. + * @param name The name of the Ledger App. + * @returns A Promise that resolves when the Ledger App is opened. + */ + async function openApp(transport: TransportInstance, name: string): Promise { + await transport.send(0xe0, 0xd8, 0x00, 0x00, Buffer.from(name, 'ascii')); + } + + /** + * Gets the Ledger App. + * @param appName The name of the Ledger App. + * @returns The Ledger App. + */ + async function getLedgerApp(appName: string): Promise { + setIsLoading([true, `Opening Ledger ${appName} App`]); + const transport = await Transport.create(); + const ledgerApp = new AppClient(transport); + const appAndVersion = await ledgerApp.getAppAndVersion(); + + if (appAndVersion.name === appName) { + setIsLoading([false, '']); + return new AppClient(transport); + } + + if (appAndVersion.name === LEDGER_APPS_MAP.MAIN_MENU) { + setIsLoading([true, `Open ${appName} App on your Ledger Device`]); + await openApp(transport, appName); + await delay(1500); + setIsLoading([false, '']); + return new AppClient(await Transport.create()); + } + + if (appAndVersion.name !== appName) { + await quitApp(await Transport.create()); + await delay(1500); + setIsLoading([true, `Open ${appName} App on your Ledger Device`]); + await openApp(await Transport.create(), appName); + await delay(1500); + setIsLoading([false, '']); + return new AppClient(await Transport.create()); + } + + throw new LedgerError(`Could not open Ledger ${appName} App`); + } async function connectLedgerWallet(derivationPath: string) { try { - console.log('Connecting ledger wallet'); + setIsLoading([true, 'Connecting To Ledger Wallet']); + + await getLedgerApp(LEDGER_APPS_MAP.XRP); const transport = await Transport.create(); - console.log('Transport created'); const xrplWallet = new Xrp(transport); - console.log('XRPL wallet created'); const xrplAddress = await xrplWallet.getAddress(derivationPath); - console.log('XRPL address fetched', xrplAddress); - // const trustset = await setTrustLine( - // getRippleClient(appConfiguration.xrplWebsocket), - // xrplAddress.address, - // appConfiguration.rippleIssuerAddress - // ); - // const trustset2 = { - // SigningPubKey: xrplAddress.publicKey.toUpperCase(), - // ...trustset, - // }; - // console.log('Trustline set', trustset2); - // const encoded = encode(trustset2); - // console.log('Trustline encoded', encoded); - // const signtrustset = await xrplWallet.signTransaction("44'/144'/0'/0/0", encoded); - // console.log('Trustline signed', signtrustset); - // const xrplClient = getRippleClient(appConfiguration.xrplWebsocket); - // const trustline = await xrplClient.submitAndWait(signtrustset); - // console.log('Trustline', trustline); - - setDerivationPath(derivationPath); - setXRPLWallet(xrplWallet); - setXRPLAddress(xrplAddress.address); - setIsConnected(true); - console.log('Ledger wallet connected'); + const xrpHandler = new LedgerXRPHandler( + xrplWallet, + derivationPath, + rippleClient, + appConfiguration.rippleIssuerAddress + ); + + return { xrpHandler, userAddress: xrplAddress.address }; } catch (error) { - console.error('Error connecting ledger wallet', error); + throw new LedgerError(`Error connecting to Ledger Wallet: ${error}`); + } finally { + setIsLoading([false, '']); } } - function fetchXRPLAddress() { - if (!xrplWallet) { - throw new Error('Ledger wallet not connected'); - } + async function handleCreateCheck( + xrpHandler: LedgerXRPHandler, + vault: RawVault, + withdrawAmount: number + ) { + try { + setIsLoading([true, 'Sign Check on your Ledger Device']); - if (!derivationPath) { - throw new Error('Derivation path not set'); + const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); + + return await xrpHandler.createCheck(formattedWithdrawAmount.toString(), vault.uuid.slice(2)); + } catch (error) { + throw new LedgerError(`Error creating check: ${error}`); + } finally { + setIsLoading([false, '']); } - return xrplWallet.getAddress(derivationPath); } - async function signTransaction(transaction: any) { - if (!xrplWallet) { - throw new Error('Ledger wallet not connected'); - } + async function handleSetTrustLine(xrpHandler: LedgerXRPHandler) { + try { + setIsLoading([true, 'Set Trust Line on your Ledger Device']); - if (!derivationPath) { - throw new Error('Derivation path not set'); + console.log('Setting trust line'); + const trustLine = await xrpHandler.setTrustLine(); + console.log('Trust line set'); + return trustLine; + } catch (error) { + throw new LedgerError(`Error setting trust line: ${error}`); + } finally { + setIsLoading([false, '']); } - - const deviceData = await xrplWallet.getAddress(derivationPath); - - console.log('transaction', transaction); - - const updatedTransaction = { - ...transaction, - Flags: 2147483648, - SigningPubKey: deviceData.publicKey.toUpperCase(), - }; - - console.log('Updated transaction', updatedTransaction); - - const encodedTransaction = encode(updatedTransaction); - console.log('encodedTransaction', encodedTransaction); - const signature = await xrplWallet.signTransaction(derivationPath, encodedTransaction); - - console.log('Signed transaction', signature); - const signedTransaction = { - ...updatedTransaction, - TxnSignature: signature, - }; - return signedTransaction; } return { - xrplAddress, + isLoading, connectLedgerWallet, - fetchXRPLAddress, - signTransaction, - isConnected, + handleCreateCheck, + handleSetTrustLine, }; } diff --git a/src/app/hooks/use-xrpl-vaults.ts b/src/app/hooks/use-xrpl-vaults.ts index c62025d8..4d535de9 100644 --- a/src/app/hooks/use-xrpl-vaults.ts +++ b/src/app/hooks/use-xrpl-vaults.ts @@ -4,7 +4,7 @@ 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'; @@ -61,7 +61,7 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { const [isLoading, setIsLoading] = useState(true); const { networkType } = useContext(NetworkConfigurationContext); - const { rippleUserAddress } = useContext(RippleNetworkConfigurationContext); + const { userAddress: rippleUserAddress } = useContext(XRPWalletContext); const issuerAddress = appConfiguration.rippleIssuerAddress; const xrplClient = getRippleClient('wss://s.altnet.rippletest.net:51233'); @@ -83,10 +83,15 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { await connectRippleClient(xrplClient); const xrplRawVaults = await getAllRippleVaults(xrplClient, issuerAddress, rippleUserAddress); - console.log('xrpVaults', xrplRawVaults); const xrplVaults = xrplRawVaults.map(formatVault); - if (previousVaults?.length === 0) { + if ( + previousVaults?.length === 0 && + xrplVaults.length === 1 && + xrplVaults[0].state === VaultState.READY + ) { + handleVaultStateChange(previousVaults[0], xrplVaults[0]); + } else if (previousVaults?.length === 0) { return xrplVaults; } @@ -115,7 +120,6 @@ export function useXRPLVaults(): useXRPLVaultsReturnType { const handleVaultStateChange = (previousVault: Vault | undefined, vault: Vault) => { if (!previousVault && vault.uuid !== INITIAL_VAULT_UUID) { - console.log('New Vault', vault); dispatch(mintUnmintActions.setMintStep([1, vault.uuid, vault])); return; } else if (previousVault && previousVault.state !== vault.state) { diff --git a/src/app/providers/balance-context-provider.tsx b/src/app/providers/balance-context-provider.tsx index f19c6a10..b270c29e 100644 --- a/src/app/providers/balance-context-provider.tsx +++ b/src/app/providers/balance-context-provider.tsx @@ -8,15 +8,11 @@ import { getAllAddressVaults, getLockedBTCBalance, } from 'dlc-btc-lib/ethereum-functions'; -import { - getDLCBTCBalance, - getLockedBTCBalance as getLockedBTCBalanceXRPL, -} from 'dlc-btc-lib/ripple-functions'; import { useAccount } from 'wagmi'; import { EthereumNetworkConfigurationContext } from './ethereum-network-configuration.provider'; import { NetworkConfigurationContext } from './network-configuration.provider'; -import { RippleNetworkConfigurationContext } from './ripple-network-configuration.provider'; +import { XRPWalletContext } from './xrp-wallet-context-provider'; interface VaultContextType { dlcBTCBalance: number | undefined; @@ -34,9 +30,8 @@ export function BalanceContextProvider({ children }: HasChildren): React.JSX.Ele } = useContext(EthereumNetworkConfigurationContext); const { networkType } = useContext(NetworkConfigurationContext); const { isConnected } = useNetworkConnection(); - const { rippleUserAddress, rippleClient, rippleWalletClient } = useContext( - RippleNetworkConfigurationContext - ); + const { userAddress } = useContext(XRPWalletContext); + const { xrpHandler } = useContext(XRPWalletContext); const { address: ethereumUserAddress } = useAccount(); @@ -51,23 +46,14 @@ export function BalanceContextProvider({ children }: HasChildren): React.JSX.Ele }; const fetchXRPLBalances = async () => { - const dlcBTCBalance = await getDLCBTCBalance( - rippleClient, - rippleWalletClient!, - appConfiguration.rippleIssuerAddress - ); - console.log('dlcBTCBalance', dlcBTCBalance); - const lockedBTCBalance = await getLockedBTCBalanceXRPL( - rippleClient, - rippleWalletClient!, - appConfiguration.rippleIssuerAddress - ); + const dlcBTCBalance = await xrpHandler?.getDLCBTCBalance(); + const lockedBTCBalance = await xrpHandler?.getLockedBTCBalance(); console.log('lockedBTCBalance', lockedBTCBalance); return { dlcBTCBalance, lockedBTCBalance }; }; const { data } = useQuery({ - queryKey: ['balances', networkType === 'evm' ? ethereumUserAddress : rippleUserAddress], + queryKey: ['balances', networkType === 'evm' ? ethereumUserAddress : userAddress], queryFn: networkType === 'evm' ? fetchEVMBalances : fetchXRPLBalances, enabled: isConnected, refetchInterval: 10000, diff --git a/src/app/providers/ripple-network-configuration.provider.tsx b/src/app/providers/ripple-network-configuration.provider.tsx index 60c20e8c..b0f3c3dd 100644 --- a/src/app/providers/ripple-network-configuration.provider.tsx +++ b/src/app/providers/ripple-network-configuration.provider.tsx @@ -1,13 +1,10 @@ import React, { createContext, useEffect, useState } from 'react'; -import { RippleWallet } from '@components/modals/select-wallet-modal/select-wallet-modal'; import { getRippleNetworkByID } from '@functions/configuration.functions'; -import { useXRPLLedger } from '@hooks/use-xrpl-ledger'; -import Xrp from '@ledgerhq/hw-app-xrp'; import { HasChildren } from '@models/has-children'; import { RippleNetwork, RippleNetworkConfiguration, RippleNetworkID } from '@models/ripple.models'; -import { Client, Wallet } from 'dlc-btc-lib/models'; -import { getRippleClient, getRippleWallet } from 'dlc-btc-lib/ripple-functions'; +import { Client } from 'dlc-btc-lib/models'; +import { getRippleClient } from 'dlc-btc-lib/ripple-functions'; import { equals, find } from 'ramda'; import { supportedRippleNetworks } from '@shared/constants/ripple.constants'; @@ -57,48 +54,30 @@ interface RippleNetworkConfigurationContext { rippleNetworkConfiguration: RippleNetworkConfiguration; setRippleNetworkID: (rippleNetworkID: RippleNetworkID) => void; enabledRippleNetworks: RippleNetwork[]; - rippleUserAddress: string | undefined; - isRippleWalletConnected: boolean; - connectLedgerWallet: (derivationPath: string) => Promise; - signTransaction: (transaction: any) => Promise; - rippleWallet: RippleWallet | undefined; rippleClient: Client; - setRippleWallet: (rippleWallet: RippleWallet) => void; isRippleNetworkConfigurationLoading: boolean; } export const RippleNetworkConfigurationContext = createContext({ - setRippleNetworkID: (rippleNetworkID: RippleNetworkID) => {}, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setRippleNetworkID: (_rippleNetworkID: RippleNetworkID) => {}, rippleNetworkConfiguration: defaultRippleNetworkConfiguration, enabledRippleNetworks, - rippleUserAddress: undefined, - connectLedgerWallet: async (_derivationPath: string) => {}, - signTransaction: async (_transaction: any) => {}, rippleClient: new Client(appConfiguration.xrplWebsocket), - rippleWallet: undefined, - setRippleWallet: (_rippleWallet: RippleWallet) => {}, - isRippleWalletConnected: false, isRippleNetworkConfigurationLoading: false, }); export function RippleNetworkConfigurationContextProvider({ children, }: HasChildren): React.JSX.Element { - const { xrplAddress, connectLedgerWallet, signTransaction, isConnected } = useXRPLLedger(); const [rippleNetworkID, setRippleNetworkID] = useState(defaultRippleNetwork.id); const [rippleNetworkConfiguration, setRippleNetworkConfiguration] = useState(defaultRippleNetworkConfiguration); const rippleClient = getRippleClient(rippleNetworkConfiguration.websocketURL); - const [rippleWallet, setRippleWallet] = useState(undefined); - const [isRippleNetworkConfigurationLoading, setIsRippleNetworkConfigurationLoading] = useState(false); - useEffect(() => { - console.log('isConnected', isConnected); - }, [isConnected]); - useEffect(() => { setIsRippleNetworkConfigurationLoading(true); @@ -117,13 +96,7 @@ export function RippleNetworkConfigurationContextProvider({ rippleNetworkConfiguration, isRippleNetworkConfigurationLoading, enabledRippleNetworks, - rippleUserAddress: xrplAddress, - signTransaction, rippleClient, - rippleWallet, - setRippleWallet, - connectLedgerWallet, - isRippleWalletConnected: isConnected, setRippleNetworkID, }} > diff --git a/src/app/providers/xrp-wallet-context-provider.tsx b/src/app/providers/xrp-wallet-context-provider.tsx index fb1e0ee5..c2c72263 100644 --- a/src/app/providers/xrp-wallet-context-provider.tsx +++ b/src/app/providers/xrp-wallet-context-provider.tsx @@ -1,64 +1,71 @@ -// import { createContext, useState } from 'react'; +import { createContext, useState } from 'react'; -// import { HasChildren } from '@models/has-children'; -// import { BitcoinWalletType, XRPWalletType } from '@models/wallet'; -// import { LedgerDLCHandler, SoftwareWalletDLCHandler } from 'dlc-btc-lib'; +import { HasChildren } from '@models/has-children'; +import { XRPWalletType } from '@models/wallet'; +import { LedgerXRPHandler } from 'dlc-btc-lib'; -// export enum XRPWalletContextState { -// INITIAL = 0, -// SELECTED = 1, -// READY = 2, -// } +export enum XRPWalletContextState { + INITIAL = 0, + SELECTED = 1, + READY = 2, +} -// interface XRPWalletContextProviderType { -// xrpWalletType: XRPWalletType | undefined; -// setXRPWalletType: React.Dispatch>; -// xrpWalletContextState: XRPWalletContextState; -// setXRPWalletContextState: React.Dispatch>; -// dlcHandler: SoftwareWalletDLCHandler | LedgerDLCHandler | undefined; -// setDLCHandler: React.Dispatch< -// React.SetStateAction -// >; -// resetBitcoinWalletContext: () => void; -// } +interface XRPWalletContextProviderType { + xrpWalletType: XRPWalletType | undefined; + setXRPWalletType: React.Dispatch>; + xrpWalletContextState: XRPWalletContextState; + setXRPWalletContextState: React.Dispatch>; + xrpHandler: LedgerXRPHandler | undefined; + setXRPHandler: React.Dispatch>; + setUserAddress: React.Dispatch>; + userAddress: string | undefined; + resetXRPWalletContext: () => void; +} -// export const XRPWalletContext = createContext({ -// bitcoinWalletType: undefined, -// setBitcoinWalletType: () => {}, -// bitcoinWalletContextState: BitcoinWalletContextState.INITIAL, -// setBitcoinWalletContextState: () => {}, -// dlcHandler: undefined, -// setDLCHandler: () => {}, -// resetBitcoinWalletContext: () => {}, -// }); +export const XRPWalletContext = createContext({ + xrpWalletType: undefined, + setXRPWalletType: () => {}, + xrpWalletContextState: XRPWalletContextState.INITIAL, + setXRPWalletContextState: () => {}, + xrpHandler: undefined, + setXRPHandler: () => {}, + setUserAddress: () => {}, + userAddress: undefined, + resetXRPWalletContext: () => {}, +}); -// export function BitcoinWalletContextProvider({ children }: HasChildren): React.JSX.Element { -// const [bitcoinWalletContextState, setBitcoinWalletContextState] = -// useState(BitcoinWalletContextState.INITIAL); -// const [bitcoinWalletType, setBitcoinWalletType] = useState( -// BitcoinWalletType.Leather -// ); -// const [dlcHandler, setDLCHandler] = useState(); +export function XRPWalletContextProvider({ children }: HasChildren): React.JSX.Element { + const [xrpWalletContextState, setXRPWalletContextState] = useState( + XRPWalletContextState.INITIAL + ); + const [xrpWalletType, setXRPWalletType] = useState( + XRPWalletType.Ledger + ); + const [xrpHandler, setXRPHandler] = useState(); + const [userAddress, setUserAddress] = useState(undefined); -// function resetBitcoinWalletContext() { -// setBitcoinWalletContextState(BitcoinWalletContextState.INITIAL); -// setBitcoinWalletType(undefined); -// setDLCHandler(undefined); -// } + function resetXRPWalletContext() { + setXRPWalletContextState(XRPWalletContextState.INITIAL); + setXRPWalletType(undefined); + setXRPHandler(undefined); + setUserAddress(undefined); + } -// return ( -// -// {children} -// -// ); -// } + return ( + + {children} + + ); +} diff --git a/src/shared/models/ledger.ts b/src/shared/models/ledger.ts index 923cbeb1..419b3de9 100644 --- a/src/shared/models/ledger.ts +++ b/src/shared/models/ledger.ts @@ -1,5 +1,6 @@ export const LEDGER_APPS_MAP = { BITCOIN_MAINNET: 'Bitcoin', BITCOIN_TESTNET: 'Bitcoin Test', + XRP: 'XRP', MAIN_MENU: 'BOLOS', } as const; diff --git a/src/shared/models/wallet.ts b/src/shared/models/wallet.ts index 69dcb3e7..83cfd917 100644 --- a/src/shared/models/wallet.ts +++ b/src/shared/models/wallet.ts @@ -42,7 +42,7 @@ export interface XRPWallet { const ledgerXRP: XRPWallet = { id: XRPWalletType.Ledger, name: 'Ledger', - icon: '/images/logos/ledger-logo.svg', + icon: './images/logos/ledger-logo.svg', }; // const gemXRP: XRPWallet = { diff --git a/yarn.lock b/yarn.lock index d580a930..a03ab7cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1981,7 +1981,7 @@ tiny-secp256k1 "1.1.6" varuint-bitcoin "1.1.2" -"@ledgerhq/hw-app-xrp@^6.29.4": +"@ledgerhq/hw-app-xrp@6.29.4", "@ledgerhq/hw-app-xrp@^6.29.4": version "6.29.4" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-6.29.4.tgz#b90d847f2cf8ddca9ca0068f5079f199c334314d" integrity sha512-fEnqkwmEmcThGVtxLUQX9x4KB1E659Ke1dYuCZSXX4346+h3PCa7cfeKN/VRZNH8HQJgiYi53LqqYzwWXB5zbg== @@ -1989,31 +1989,6 @@ "@ledgerhq/hw-transport" "^6.31.4" bip32-path "0.4.2" -"@ledgerhq/hw-transport-node-hid-noevents@^6.30.5": - version "6.30.5" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.30.5.tgz#87feed538aa373a61e21e0c2849a5e4c9a07528b" - integrity sha512-nOPbhFU87LgLERVAQ+HhxV8E8c+7d8ptllkgiJUc4QwL2z9zkIOAEtgdvCaZ066Oi9XGnln/GF1oAgByYnYDPw== - dependencies: - "@ledgerhq/devices" "^8.4.4" - "@ledgerhq/errors" "^6.19.1" - "@ledgerhq/hw-transport" "^6.31.4" - "@ledgerhq/logs" "^6.12.0" - node-hid "2.1.2" - -"@ledgerhq/hw-transport-node-hid@^6.29.5": - version "6.29.5" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-6.29.5.tgz#4e140ba3fc152277ae8af2b133287fa803093fc9" - integrity sha512-2bAp4K50V1kdCufU9JdQPcw4aLyvA+yQRJU/X39B+PC+rnis40gEbqNh0henhzv876sXdbNk6G/MkDWXpwDIow== - dependencies: - "@ledgerhq/devices" "^8.4.4" - "@ledgerhq/errors" "^6.19.1" - "@ledgerhq/hw-transport" "^6.31.4" - "@ledgerhq/hw-transport-node-hid-noevents" "^6.30.5" - "@ledgerhq/logs" "^6.12.0" - lodash "^4.17.21" - node-hid "2.1.2" - usb "2.9.0" - "@ledgerhq/hw-transport-webusb@^6.28.6": version "6.28.6" resolved "https://registry.npmjs.org/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.28.6.tgz" @@ -3386,11 +3361,6 @@ resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== -"@types/w3c-web-usb@^1.0.6": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz#cf89cccd2d93b6245e784c19afe0a9f5038d4528" - integrity sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ== - "@typescript-eslint/eslint-plugin@^6.9.1": version "6.21.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz" @@ -4170,7 +4140,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -bindings@^1.3.0, bindings@^1.5.0: +bindings@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -4272,15 +4242,6 @@ bitcoinjs-lib@^6.1.3: typeforce "^1.11.3" varuint-bitcoin "^1.1.2" -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" @@ -4376,14 +4337,6 @@ 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@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - buffer@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" @@ -4502,11 +4455,6 @@ chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz" @@ -5143,13 +5091,6 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz" @@ -5157,11 +5098,6 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -5232,11 +5168,6 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== -detect-libc@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" - integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== - detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" @@ -5263,8 +5194,7 @@ dir-glob@^3.0.1: version "2.3.0" dependencies: "@ledgerhq/hw-app-btc" "10.4.1" - "@ledgerhq/hw-app-xrp" "^6.29.4" - "@ledgerhq/hw-transport-node-hid" "^6.29.5" + "@ledgerhq/hw-app-xrp" "6.29.4" "@noble/hashes" "1.4.0" "@scure/base" "1.1.8" "@scure/btc-signer" "1.3.2" @@ -5277,6 +5207,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" scure "1.6.0" tiny-secp256k1 "2.2.3" xrpl "4.0.0" @@ -5932,11 +5863,6 @@ execa@^8.0.1: signal-exit "^4.1.0" strip-final-newline "^3.0.0" -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - extension-port-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extension-port-stream/-/extension-port-stream-3.0.0.tgz#00a7185fe2322708a36ed24843c81bd754925fef" @@ -6096,11 +6022,6 @@ framesync@6.1.2: dependencies: tslib "2.4.0" -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -6191,11 +6112,6 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -6401,7 +6317,7 @@ idb-keyval@^6.2.1: resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -6442,11 +6358,6 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz" @@ -7109,11 +7020,6 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" @@ -7138,7 +7044,7 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -7148,11 +7054,6 @@ mipd@0.0.7: resolved "https://registry.yarnpkg.com/mipd/-/mipd-0.0.7.tgz#bb5559e21fa18dc3d9fe1c08902ef14b7ce32fd9" integrity sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg== -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mlly@^1.2.0, mlly@^1.4.0: version "1.6.1" resolved "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz" @@ -7225,11 +7126,6 @@ nanoid@^3.3.7: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - napi-wasm@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" @@ -7240,33 +7136,16 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-abi@^3.3.0: - version "3.68.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.68.0.tgz#8f37fb02ecf4f43ebe694090dcb52e0c4cc4ba25" - integrity sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A== - dependencies: - semver "^7.3.5" - node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== -node-addon-api@^3.0.2: - version "3.2.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" - integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== - node-addon-api@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== -node-addon-api@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" - integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== - node-addon-api@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" @@ -7294,20 +7173,6 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== -node-gyp-build@^4.5.0: - version "4.8.2" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.2.tgz#4f802b71c1ab2ca16af830e6c1ea7dd1ad9496fa" - integrity sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw== - -node-hid@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-2.1.2.tgz#3145fa86ed4336a402a71e9f372c54213b88797c" - integrity sha512-qhCyQqrPpP93F/6Wc/xUR7L8mAJW0Z6R7HMQV8jCHHksAxNDe/4z4Un/H9CpLOT+5K39OPyt9tIQlavxWES3lg== - dependencies: - bindings "^1.5.0" - node-addon-api "^3.0.2" - prebuild-install "^7.1.1" - node-releases@^2.0.14: version "2.0.14" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" @@ -7672,24 +7537,6 @@ preact@^10.16.0: resolved "https://registry.yarnpkg.com/preact/-/preact-10.23.1.tgz#d400107289bc979881c5212cb5f5cd22cd1dc38c" integrity sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A== -prebuild-install@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" - integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -7830,16 +7677,6 @@ randombytes@^2.0.1, randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-apexcharts@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz" @@ -8016,7 +7853,7 @@ readable-stream@^2.3.3: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.1.1, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -8416,20 +8253,6 @@ signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - sister@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz" @@ -8629,11 +8452,6 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - strip-literal@^1.0.1: version "1.3.0" resolved "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz" @@ -8737,27 +8555,6 @@ system-architecture@^0.1.0: resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA== -tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -8897,13 +8694,6 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - turbo-stream@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" @@ -9107,15 +8897,6 @@ urlpattern-polyfill@8.0.2: resolved "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz" integrity sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ== -usb@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/usb/-/usb-2.9.0.tgz#8ae3b175f93bee559400bff33491eee63406b6a2" - integrity sha512-G0I/fPgfHUzWH8xo2KkDxTTFruUWfppgSFJ+bQxz/kVY2x15EQ/XDB7dqD1G432G4gBG4jYQuF3U7j/orSs5nw== - dependencies: - "@types/w3c-web-usb" "^1.0.6" - node-addon-api "^6.0.0" - node-gyp-build "^4.5.0" - use-callback-ref@^1.3.0: version "1.3.2" resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz" From ca811264b6da30df554f0effde8b1bcf18adb6ab Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 16 Oct 2024 16:57:26 +0200 Subject: [PATCH 11/39] feat: add gem wallet as xrp wallet option --- config.testnet.json | 4 +- package.json | 3 +- public/images/logos/gem-wallet-logo.svg | 1 + .../logos/{xpr-logo.svg => xrp-logo.svg} | 0 .../burn-transaction-screen.tsx | 19 +-- .../mint-unmint/components/mint/mint.tsx | 5 +- .../setup-vault-screen/setup-vault-screen.tsx | 8 +- .../mint-unmint/components/unmint/unmint.tsx | 5 +- .../components/walkthrough-blockchain-tag.tsx | 8 +- .../components/walkthrough-header.tsx | 2 +- .../components/walkthrough/walkthrough.tsx | 121 ++++++++++-------- .../components/select-ripple-wallet-menu.tsx | 4 +- .../select-wallet-modal.tsx | 57 ++++++++- .../functions/attestor-request.functions.ts | 2 +- src/app/hooks/use-psbt.ts | 6 +- src/app/hooks/use-xrp-wallet.ts | 89 +++++++++++++ src/app/hooks/use-xrpl-gem.ts | 91 +++++++++++++ src/app/hooks/use-xrpl-ledger.ts | 38 +++--- src/app/hooks/use-xrpl-vaults.ts | 27 ++-- .../providers/balance-context-provider.tsx | 1 - .../network-configuration.provider.tsx | 6 +- .../providers/xrp-wallet-context-provider.tsx | 8 +- src/shared/models/error-types.ts | 7 + src/shared/models/wallet.ts | 12 +- yarn.lock | 22 +++- 25 files changed, 395 insertions(+), 151 deletions(-) create mode 100644 public/images/logos/gem-wallet-logo.svg rename public/images/logos/{xpr-logo.svg => xrp-logo.svg} (100%) create mode 100644 src/app/hooks/use-xrp-wallet.ts create mode 100644 src/app/hooks/use-xrpl-gem.ts 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== From c31c722bb38191881f2f2021cb9fe91d34735f9a Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 17 Oct 2024 13:28:24 +0200 Subject: [PATCH 12/39] fix: modify value handling in form --- .../transaction-screen.transaction-form.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx index b93af6e5..917c90c5 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx @@ -8,6 +8,7 @@ import { BitcoinTransactionConfirmationsContext } from '@providers/bitcoin-query import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-provider'; import { useForm } from '@tanstack/react-form'; import Decimal from 'decimal.js'; +import { isEmpty } from 'ramda'; import { TransactionFormNavigateButtonGroup } from './components/transaction-screen.transaction-form.navigate-button-group'; import { TransactionFormProgressStack } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack'; @@ -140,7 +141,8 @@ export function VaultTransactionForm({ }, validators: { onChange: ({ value }) => { - setCurrentFieldValue(new Decimal(value.assetAmount).toNumber()); + const assetAmount = value.assetAmount; + setCurrentFieldValue(isEmpty(assetAmount) ? 0 : new Decimal(value.assetAmount).toNumber()); return { fields: { assetAmount: validateFormAmount( From 39fa67245906cf2d9d4dc4b4e6217285c35690e1 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 17 Oct 2024 19:59:28 +0200 Subject: [PATCH 13/39] chore: change dlc-btc-lib package version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index caa9c316..47629ef7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "concurrently": "^8.2.2", "d3": "^7.9.0", "decimal.js": "^10.4.3", - "dlc-btc-lib": "^2.4.0", + "dlc-btc-lib": "2.4.2", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", diff --git a/yarn.lock b/yarn.lock index ec2b78e8..156b0012 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5195,10 +5195,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.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== +dlc-btc-lib@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.2.tgz#1ef49a8d4ea631f3aca444502be102c7834ce4fa" + integrity sha512-Kwo3xcgO86uprDHBSo91hIOOmNqas35yp7KA6Fa51MIG0r/zvHFy0y89uzuMeyviUZM0xO4WwBCq3BSzT6HAMg== dependencies: "@gemwallet/api" "3.8.0" "@ledgerhq/hw-app-btc" "10.4.1" From b023bc8edbc3d6ec386c8a01096a216c93f8c45a Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 18 Oct 2024 08:20:44 +0200 Subject: [PATCH 14/39] feat: modify xrpl related texts and lib version --- config.devnet.json | 2 +- package.json | 2 +- .../walkthrough/components/walkthrough-blockchain-tag.tsx | 2 +- .../modals/select-wallet-modal/select-wallet-modal.tsx | 2 +- .../components/vaults-list-group-container.tsx | 6 +++++- yarn.lock | 8 ++++---- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/config.devnet.json b/config.devnet.json index 01ce4260..2ab3796b 100644 --- a/config.devnet.json +++ b/config.devnet.json @@ -9,7 +9,7 @@ "bitcoinBlockchainURL": "https://devnet.dlc.link/electrs", "bitcoinBlockchainExplorerURL": "https://devnet.dlc.link/electrs", "bitcoinBlockchainFeeEstimateURL": "https://devnet.dlc.link/electrs/fee-estimates", - "rippleIssuerAddress": "ra9epzthPkNXykgfadCwu8D7mtajj8DVCP", + "rippleIssuerAddress": "sEdVdXVaTV9nbEfyjtiowJtyagiE1it", "xrplWebsocket": "wss://s.altnet.rippletest.net:51233", "ledgerApp": "Bitcoin Test", "merchants": [ diff --git a/package.json b/package.json index 47629ef7..083e6278 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "concurrently": "^8.2.2", "d3": "^7.9.0", "decimal.js": "^10.4.3", - "dlc-btc-lib": "2.4.2", + "dlc-btc-lib": "2.4.3", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", diff --git a/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-blockchain-tag.tsx b/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-blockchain-tag.tsx index df68558e..c50e4954 100644 --- a/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-blockchain-tag.tsx +++ b/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-blockchain-tag.tsx @@ -11,7 +11,7 @@ const blockchainTagPropertyMap = { }, xrpl: { logo: '/images/logos/xrp-logo.svg', - text: 'ON XRP', + text: 'ON XRPL', }, }; 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 022c04cf..f21c0d45 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 @@ -161,7 +161,7 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): > Ethereum - Ripple + XRPL } {label} - {label === 'Minted dlcBTC' && ( + {label === 'Minted dlcBTC' && networkType === 'evm' && ( diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx index de9c567d..0c8b0f63 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx @@ -9,6 +9,8 @@ import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-pro import { useForm } from '@tanstack/react-form'; import Decimal from 'decimal.js'; +import { parseAssetAmount } from '@shared/utils'; + import { TransactionFormNavigateButtonGroup } from './components/transaction-screen.transaction-form.navigate-button-group'; import { TransactionFormProgressStack } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack'; import { TransactionFormProtocolFeeStack } from './components/transaction-screen.transaction-form.protocol-fee-box'; @@ -152,19 +154,6 @@ export function VaultTransactionForm({ }, }); - function parseAssetAmount(assetAmount: string): Decimal { - const isValidNumber = /^-?\d*\.?\d*$/.test(assetAmount); - - if (isValidNumber) { - try { - return new Decimal(assetAmount); - } catch { - return new Decimal(0); - } - } - return new Decimal(0); - } - return ( { diff --git a/src/app/components/vault/components/vault.detaills/vault.details.tsx b/src/app/components/vault/components/vault.detaills/vault.details.tsx index 00b02f37..92dee556 100644 --- a/src/app/components/vault/components/vault.detaills/vault.details.tsx +++ b/src/app/components/vault/components/vault.detaills/vault.details.tsx @@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom'; import { Collapse, Stack, VStack } from '@chakra-ui/react'; import { VaultContext } from '@providers/vault-context-provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; +import { MintSteps, RedeemSteps } from '@store/slices/mintunmint/mintunmint.slice'; import { VaultState } from 'dlc-btc-lib/models'; import { VaultExpandedInformationButtonGroup } from './components/vault.details.button-group/vault.details.button-group'; @@ -41,7 +42,7 @@ export function VaultDetails({ function handleDepositClick() { navigate('/mint-withdraw'); - dispatch(mintUnmintActions.setMintStep([1, vaultUUID, vault])); + dispatch(mintUnmintActions.setMintStep({ step: MintSteps.DEPOSIT, vault: vault })); if (handleClose) { handleClose(); } @@ -50,9 +51,9 @@ export function VaultDetails({ function handleWithdrawClick() { navigate('/mint-withdraw'); if (vaultTotalLockedValue === vaultTotalMintedValue) { - dispatch(mintUnmintActions.setUnmintStep([0, vaultUUID, vault])); + dispatch(mintUnmintActions.setUnmintStep({ step: RedeemSteps.BURN, vault: vault })); } else { - dispatch(mintUnmintActions.setUnmintStep([1, vaultUUID, vault])); + dispatch(mintUnmintActions.setUnmintStep({ step: RedeemSteps.WITHDRAW, vault: vault })); } if (handleClose) { handleClose(); @@ -62,9 +63,9 @@ export function VaultDetails({ function handleResumeClick() { navigate('/mint-withdraw'); if (vaultTotalLockedValue === vaultTotalMintedValue) { - dispatch(mintUnmintActions.setMintStep([2, vaultUUID, vault])); + dispatch(mintUnmintActions.setMintStep({ step: MintSteps.PENDING, vault: vault })); } else { - dispatch(mintUnmintActions.setUnmintStep([2, vaultUUID, vault])); + dispatch(mintUnmintActions.setUnmintStep({ step: RedeemSteps.PENDING, vault: vault })); } } diff --git a/src/app/components/vaults-list/components/vaults-list-group-container.tsx b/src/app/components/vaults-list/components/vaults-list-group-container.tsx index d7545ace..9b4d84c8 100644 --- a/src/app/components/vaults-list/components/vaults-list-group-container.tsx +++ b/src/app/components/vaults-list/components/vaults-list-group-container.tsx @@ -6,6 +6,8 @@ import { useAddToken } from '@hooks/use-add-token'; import { Vault as VaultModel } from '@models/vault'; import { NetworkConfigurationContext } from '@providers/network-configuration.provider'; +import { NetworkType } from '@shared/constants/network.constants'; + interface VaultsListGroupContainerProps { label?: string; variant?: 'select'; @@ -33,7 +35,7 @@ export function VaultsListGroupContainer({ {['Pending'].includes(label) && } {label} - {label === 'Minted dlcBTC' && networkType === 'evm' && ( + {label === 'Minted dlcBTC' && networkType === NetworkType.EVM && ( {bitcoinWallets.map(wallet => ( Date: Tue, 5 Nov 2024 10:27:43 +0100 Subject: [PATCH 28/39] wip: auth --- netlify/functions/get-dfns-auth-token.ts | 2 +- src/app/hooks/use-dfns.ts | 36 ++++++++++++------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/netlify/functions/get-dfns-auth-token.ts b/netlify/functions/get-dfns-auth-token.ts index a54511b6..5a62836f 100644 --- a/netlify/functions/get-dfns-auth-token.ts +++ b/netlify/functions/get-dfns-auth-token.ts @@ -11,7 +11,7 @@ const handler: Handler = async event => { }); const { token } = await dfnsAuth.login({ - username: 'us--o4ogd-943qhq5l5020410m', + username: 'us-12nks-o4ogd-943qhq5l5020410m', orgId: 'or-4bcun-fj3tb-8np9ivfu5jnstkks', }); diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index 85ea5045..b55e1671 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -1,28 +1,28 @@ -import { DfnsAuthenticator } from '@dfns/sdk'; -import { WebAuthnSigner } from '@dfns/sdk-browser'; +// import { DfnsAuthenticator } from '@dfns/sdk'; +// import { WebAuthnSigner } from '@dfns/sdk-browser'; export function useDFNS() { async function authenticateUser() { - // const netlifyFunctionEndpoint = `/.netlify/functions/get-dfns-auth-token`; + const netlifyFunctionEndpoint = `/.netlify/functions/get-dfns-auth-token`; - // const response = await fetch(netlifyFunctionEndpoint); + const response = await fetch(netlifyFunctionEndpoint); - // console.log('response', response); - const dfnsAuth = new DfnsAuthenticator({ - appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', - baseUrl: 'https://app.dfns.ninja/', - signer: new WebAuthnSigner(), - }); + console.log('response', response); + // const dfnsAuth = new DfnsAuthenticator({ + // appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', + // baseUrl: 'https://app.dfns.ninja/', + // signer: new WebAuthnSigner(), + // }); - const { token } = await dfnsAuth.login({ - username: 'dani@dlc.link', - orgId: 'or-4bcun-fj3tb-8np9ivfu5jnstkks', - }); + // const { token } = await dfnsAuth.login({ + // username: 'us-12nks-o4ogd-943qhq5l5020410m', + // orgId: 'or-4bcun-fj3tb-8np9ivfu5jnstkks', + // }); - console.log( - 'tokeEEEEEEEEEEEEEEEEEEEEEEEEEEnEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE', - token - ); + // console.log( + // 'tokeEEEEEEEEEEEEEEEEEEEEEEEEEEnEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE', + // token + // ); } return { authenticateUser, From 8833f1885c3134969f916fe883fa5288fa477a54 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 5 Nov 2024 10:51:24 +0100 Subject: [PATCH 29/39] wip: modify token auth --- src/app/hooks/use-dfns.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index b55e1671..281ed922 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -1,28 +1,28 @@ -// import { DfnsAuthenticator } from '@dfns/sdk'; -// import { WebAuthnSigner } from '@dfns/sdk-browser'; +import { DfnsAuthenticator } from '@dfns/sdk'; +import { WebAuthnSigner } from '@dfns/sdk-browser'; export function useDFNS() { async function authenticateUser() { - const netlifyFunctionEndpoint = `/.netlify/functions/get-dfns-auth-token`; + // const netlifyFunctionEndpoint = `/.netlify/functions/get-dfns-auth-token`; - const response = await fetch(netlifyFunctionEndpoint); + // const response = await fetch(netlifyFunctionEndpoint); - console.log('response', response); - // const dfnsAuth = new DfnsAuthenticator({ - // appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', - // baseUrl: 'https://app.dfns.ninja/', - // signer: new WebAuthnSigner(), - // }); + // console.log('response', response); + const dfnsAuth = new DfnsAuthenticator({ + appId: 'ap-6ao42-2e42m-9asohsp529h0sanm', + baseUrl: 'https://app.dfns.ninja', + signer: new WebAuthnSigner(), + }); - // const { token } = await dfnsAuth.login({ - // username: 'us-12nks-o4ogd-943qhq5l5020410m', - // orgId: 'or-4bcun-fj3tb-8np9ivfu5jnstkks', - // }); + const { token } = await dfnsAuth.login({ + username: 'us-12nks-o4ogd-943qhq5l5020410m', + orgId: 'or-4bcun-fj3tb-8np9ivfu5jnstkks', + }); - // console.log( - // 'tokeEEEEEEEEEEEEEEEEEEEEEEEEEEnEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE', - // token - // ); + console.log( + 'tokeEEEEEEEEEEEEEEEEEEEEEEEEEEnEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE', + token + ); } return { authenticateUser, From 7586becaf84d09a1886e87fbc49861f9648f70d3 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 5 Nov 2024 19:21:19 +0100 Subject: [PATCH 30/39] wip: add credentials --- netlify/functions/get-dfns-auth-token.ts | 2 +- netlify/functions/get-wallets.ts | 34 ++++ package.json | 2 +- public/images/logos/dfns-logo.svg | 1 + .../select-bitcoin-wallet-modal.tsx | 16 +- src/app/hooks/use-dfns.ts | 172 +++++++++++++++--- src/app/hooks/use-leather.ts | 2 + src/app/hooks/use-psbt.ts | 28 ++- .../bitcoin-wallet-context-provider.tsx | 10 +- src/shared/models/wallet.ts | 9 +- yarn.lock | 8 +- 11 files changed, 247 insertions(+), 37 deletions(-) create mode 100644 netlify/functions/get-wallets.ts create mode 100644 public/images/logos/dfns-logo.svg diff --git a/netlify/functions/get-dfns-auth-token.ts b/netlify/functions/get-dfns-auth-token.ts index 5a62836f..4f6c692c 100644 --- a/netlify/functions/get-dfns-auth-token.ts +++ b/netlify/functions/get-dfns-auth-token.ts @@ -5,7 +5,7 @@ import { Handler } from '@netlify/functions'; const handler: Handler = async event => { try { const dfnsAuth = new DfnsAuthenticator({ - appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', + appId: 'ap-7perp-pnqr6-8f5pgmmr6dtgh4n3', baseUrl: 'https://app.dfns.ninja', signer: new WebAuthnSigner(), }); diff --git a/netlify/functions/get-wallets.ts b/netlify/functions/get-wallets.ts new file mode 100644 index 00000000..db688888 --- /dev/null +++ b/netlify/functions/get-wallets.ts @@ -0,0 +1,34 @@ +import { DfnsAuthenticator, DfnsDelegatedApiClient } from '@dfns/sdk'; +import { WebAuthnSigner } from '@dfns/sdk-browser'; +import { Handler } from '@netlify/functions'; + +const handler: Handler = async event => { + try { + const dfnsDelegated = new DfnsDelegatedApiClient({ + baseUrl: 'https://app.dfns.ninja', + appId: 'ap-7perp-pnqr6-8f5pgmmr6dtgh4n3', // ID of the Application registered with DFNS + authToken: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai0xNDJmay02aHZvZi05M2Q5cGY3NzE3cm1sZjYxIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA3OTY3MDUsImV4cCI6MTczMDgxODMwNX0.MplBRIoMls9sxUakQn1bXfwXxOK6u4TGosoOYlREY5oX4A_7VInLzfQ9-uU8P_TAgX-lnf9nCH6_UtjDTqC9Bg', // Auth token of the User + }); + + console.log('dfnsDelegated', dfnsDelegated); + + const wallets = await dfnsDelegated.wallets.listWallets(); + console.log('wallets', wallets); + + return { + statusCode: 200, + body: JSON.stringify(wallets), + }; + } catch (error) { + console.error(error); + return { + statusCode: 500, + body: JSON.stringify({ + error: error.message, + }), + }; + } +}; + +export { handler }; diff --git a/package.json b/package.json index 2802d293..b555d2eb 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.8", + "dlc-btc-lib": "file:../dlc-btc-lib", "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 00000000..8859757b --- /dev/null +++ b/public/images/logos/dfns-logo.svg @@ -0,0 +1 @@ + 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 f68ac109..b89aab4b 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 @@ -20,7 +20,7 @@ export function SelectBitcoinWalletModal({ const toast = useToast(); const { connectLeatherWallet } = useLeather(); const { connectUnisatWallet } = useUnisat(); - const { authenticateUser } = useDFNS(); + const { connectDFNSWallet } = useDFNS(); async function handleLogin(walletType: BitcoinWalletType) { switch (walletType) { @@ -66,6 +66,19 @@ export function SelectBitcoinWalletModal({ case BitcoinWalletType.Ledger: dispatch(modalActions.toggleLedgerModalVisibility()); break; + case BitcoinWalletType.DFNS: + try { + await connectDFNSWallet(); + } catch (error: any) { + toast({ + title: 'Failed to connect to DFNS Wallet', + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + }); + } + break; default: break; } @@ -74,7 +87,6 @@ export function SelectBitcoinWalletModal({ return ( handleClose()}> - {bitcoinWallets.map(wallet => ( ([false, '']); + + async function connectDFNSWallet(): Promise { + try { + setIsLoading([true, 'Connecting to DFNS Wallet']); + + const dfnsDLCHandler = new DFNSDLCHandler( + 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', + 'http://localhost:3000/dfns-proxy/', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai01ampmdi0zaGZjby04czBycTB0djM3aHJybjdnIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA4MTkwMjgsImV4cCI6MTczMDg0MDYyOH0.aCS8ho4aKltqhDHnyKQ-QN5hAqsDsQgtoXSLAbyVrUkTzBGXBnJ61r-eu72_uZQkJIOeUJm2GFY-GhUds9kGAg', + BITCOIN_NETWORK_MAP[appConfiguration.bitcoinNetwork], + appConfiguration.bitcoinBlockchainURL, + appConfiguration.bitcoinBlockchainFeeEstimateURL + ); + console.log('dfnsDLCHandler', dfnsDLCHandler); + + const wallets = await dfnsDLCHandler.getWallets(); + console.log('wallets', wallets); + await dfnsDLCHandler.getWalletByID('wa-1vaij-cabue-9mrakrc7p8nntubp'); + + setDLCHandler(dfnsDLCHandler); + setBitcoinWalletType(BitcoinWalletType.DFNS); + setBitcoinWalletContextState(BitcoinWalletContextState.READY); + + setIsLoading([false, '']); + } catch (error) { + setIsLoading([false, '']); + throw new Error(`Error connecting to DFNS Wallet: ${error}`); + } + } + + /** + * 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 dfnsDelegatedAPIClient = new DfnsDelegatedApiClient({ + baseUrl: 'http://localhost:3000/dfns-proxy/', + appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', + authToken: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai01ampmdi0zaGZjby04czBycTB0djM3aHJybjdnIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA4MTkwMjgsImV4cCI6MTczMDg0MDYyOH0.aCS8ho4aKltqhDHnyKQ-QN5hAqsDsQgtoXSLAbyVrUkTzBGXBnJ61r-eu72_uZQkJIOeUJm2GFY-GhUds9kGAg', + }); + + // const challenge = await dfnsDelegatedAPIClient.auth.createCredentialChallengeWithCode({ + // body: { code: 'QKRXFOLCO', 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); + + // const credential = await dfnsDelegatedAPIClient.auth.createCredentialWithCode({ + // body: { + // credentialName: 'My new demo Cred - ' + new Date().toISOString(), + // challengeIdentifier: challenge.challengeIdentifier, + // credentialKind: attestation.credentialKind, + // credentialInfo: attestation.credentialInfo, + // }, + // }); + // console.log('credential', credential); + + const wallets = await dfnsDelegatedAPIClient.wallets.listWallets(); + console.log('wallets', wallets); + // ==> Sign Funding PSBT with Ledger + const response = await dfnsDelegatedAPIClient.wallets.generateSignatureInit({ + walletId: 'wa-1vaij-cabue-9mrakrc7p8nntubp', + body: { + kind: 'Psbt', + psbt: `0x${fundingPSBT.toHex()}`, + }, + }); + + console.log('response', response); + + const webauthn = new WebAuthnSigner(); + const assertion = await webauthn.sign(response); + + console.log('fundinPSBT', fundingPSBT); + + const signedPSBT = await dfnsDelegatedAPIClient.wallets.generateSignatureComplete( + { + walletId: 'wa-1vaij-cabue-9mrakrc7p8nntubp', + body: { + kind: 'Psbt', + psbt: `0x${fundingPSBT.toHex()}`, + }, + }, + { + challengeIdentifier: response.challengeIdentifier, + firstFactor: assertion, + } + ); + + console.log('signedPSBT', signedPSBT); + + // ==> Finalize Funding Transaction + const fundingTransaction = Transaction.fromPSBT(hexToBytes('0x')); + fundingTransaction.finalize(); + + setIsLoading([false, '']); + return fundingTransaction; + } catch (error) { + setIsLoading([false, '']); + throw new Error(`Error handling Funding Transaction: ${error}`); + } } + return { - authenticateUser, + connectDFNSWallet, + handleFundingTransaction, + isLoading, }; } diff --git a/src/app/hooks/use-leather.ts b/src/app/hooks/use-leather.ts index c8b13e6c..9c4780ce 100644 --- a/src/app/hooks/use-leather.ts +++ b/src/app/hooks/use-leather.ts @@ -95,6 +95,8 @@ export function useLeather(): UseLeatherReturnType { const rpcResponse: RpcResponse = await window.btc?.request('getAddresses'); const userAddresses = rpcResponse.result.addresses; + console.log('userAddresses', userAddresses); + checkUserWalletNetwork(userAddresses[0]); const bitcoinAddresses = userAddresses.filter( diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index d9217142..d6b56447 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,9 @@ export function usePSBT(): UsePSBTReturnType { isLoading: isUnisatLoading, } = useUnisat(); + const { handleFundingTransaction: handleFundingTransactionWithDFNS, isLoading: isDFNSLoading } = + useDFNS(); + const [bitcoinDepositAmount, setBitcoinDepositAmount] = useState(0); const attestorChainIDs = { @@ -138,6 +142,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 handleDepositTransactionWithLedger( + dlcHandler as LedgerDLCHandler, + vault, + depositAmount, + attestorGroupPublicKey, + feeRateMultiplier + ); + } + break; case 'Unisat': switch (vault.valueLocked.toNumber()) { case 0: @@ -270,6 +295,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 1baa1294..70a6f829 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/shared/models/wallet.ts b/src/shared/models/wallet.ts index 596427a5..49e649d1 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/yarn.lock b/yarn.lock index e11423d0..349b240f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5220,11 +5220,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dlc-btc-lib@2.4.8: - version "2.4.8" - resolved "https://registry.yarnpkg.com/dlc-btc-lib/-/dlc-btc-lib-2.4.8.tgz#dd59f49b5f3df6a788843b2501a60f7048fbc304" - integrity sha512-06SAymipYWEvb/23JUs+iU6hF7qmBs+ck6iI2kQoZT9dsqDre7w6JcmSGtzPFzwupycfV/z8S8XRJ1eR02lNgw== +"dlc-btc-lib@file:../dlc-btc-lib": + version "2.4.10" 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" From cd0d135c0addb7e7660b98d43e6f718dfd4b1888 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 6 Nov 2024 15:20:58 +0100 Subject: [PATCH 31/39] wip: add remaining psbt handlers for dfns --- src/app/hooks/use-dfns.ts | 141 ++++++++++++++++++++---------------- src/app/hooks/use-ledger.ts | 2 + src/app/hooks/use-psbt.ts | 21 +++++- 3 files changed, 96 insertions(+), 68 deletions(-) diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index a31b09b8..fb3f64b3 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -28,7 +28,7 @@ export function useDFNS() { const dfnsDLCHandler = new DFNSDLCHandler( 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', 'http://localhost:3000/dfns-proxy/', - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai01ampmdi0zaGZjby04czBycTB0djM3aHJybjdnIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA4MTkwMjgsImV4cCI6MTczMDg0MDYyOH0.aCS8ho4aKltqhDHnyKQ-QN5hAqsDsQgtoXSLAbyVrUkTzBGXBnJ61r-eu72_uZQkJIOeUJm2GFY-GhUds9kGAg', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai02NjNpZS1jdTY3cC05dXRhb2UyYm1za2pvMzJpIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA4ODU1MjcsImV4cCI6MTczMDkwNzEyN30.dEhmC4XuPHEztZAlysNfJAwqnaiOe9V3aGj2DXG24X6Vj3JnJC1dqc2cXJan2A6Uxgd-QB3eMkP5Ts9dv9YoCw', BITCOIN_NETWORK_MAP[appConfiguration.bitcoinNetwork], appConfiguration.bitcoinBlockchainURL, appConfiguration.bitcoinBlockchainFeeEstimateURL @@ -50,6 +50,9 @@ export function useDFNS() { } } + // async function registerCredentials(): Promise { + // const + /** * Creates the Funding Transaction and signs it with Leather Wallet. * @param vaultUUID The Vault UUID. @@ -75,82 +78,92 @@ export function useDFNS() { setIsLoading([true, 'Sign Funding Transaction in your DFNS Wallet']); - const dfnsDelegatedAPIClient = new DfnsDelegatedApiClient({ - baseUrl: 'http://localhost:3000/dfns-proxy/', - appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', - authToken: - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai01ampmdi0zaGZjby04czBycTB0djM3aHJybjdnIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA4MTkwMjgsImV4cCI6MTczMDg0MDYyOH0.aCS8ho4aKltqhDHnyKQ-QN5hAqsDsQgtoXSLAbyVrUkTzBGXBnJ61r-eu72_uZQkJIOeUJm2GFY-GhUds9kGAg', - }); - - // const challenge = await dfnsDelegatedAPIClient.auth.createCredentialChallengeWithCode({ - // body: { code: 'QKRXFOLCO', 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); - - // const credential = await dfnsDelegatedAPIClient.auth.createCredentialWithCode({ - // body: { - // credentialName: 'My new demo Cred - ' + new Date().toISOString(), - // challengeIdentifier: challenge.challengeIdentifier, - // credentialKind: attestation.credentialKind, - // credentialInfo: attestation.credentialInfo, - // }, - // }); - // console.log('credential', credential); - - const wallets = await dfnsDelegatedAPIClient.wallets.listWallets(); - console.log('wallets', wallets); - // ==> Sign Funding PSBT with Ledger - const response = await dfnsDelegatedAPIClient.wallets.generateSignatureInit({ - walletId: 'wa-1vaij-cabue-9mrakrc7p8nntubp', - body: { - kind: 'Psbt', - psbt: `0x${fundingPSBT.toHex()}`, - }, - }); - - console.log('response', response); - - const webauthn = new WebAuthnSigner(); - const assertion = await webauthn.sign(response); - - console.log('fundinPSBT', fundingPSBT); - - const signedPSBT = await dfnsDelegatedAPIClient.wallets.generateSignatureComplete( - { - walletId: 'wa-1vaij-cabue-9mrakrc7p8nntubp', - body: { - kind: 'Psbt', - psbt: `0x${fundingPSBT.toHex()}`, - }, - }, - { - challengeIdentifier: response.challengeIdentifier, - firstFactor: assertion, - } - ); + console.log('fundingPSBT', fundingPSBT); - console.log('signedPSBT', signedPSBT); + const signedFundingTransaction = await dlcHandler.signPSBT(fundingPSBT, 'funding'); - // ==> Finalize Funding Transaction - const fundingTransaction = Transaction.fromPSBT(hexToBytes('0x')); - fundingTransaction.finalize(); + console.log('signedFundingTransaction', signedFundingTransaction); setIsLoading([false, '']); - return fundingTransaction; + 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']); + + console.log('depositPSBT', depositPSBT); + + const signedDepositTransaction = await dlcHandler.signPSBT(depositPSBT, 'deposit'); + + console.log('signedDepositTransaction', signedDepositTransaction); + + 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 + ); + + console.log('withdrawalPSBT', withdrawalPSBT); + + console.log('withdrawPSBT', bytesToHex(withdrawalPSBT.toPSBT())); + + setIsLoading([true, 'Sign Withdraw Transaction in your DFNS Wallet']); + const signedWithdrawTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); + + setIsLoading([false, '']); + return bytesToHex(signedWithdrawTransaction.toPSBT()); + } catch (error) { + setIsLoading([false, '']); + throw new Error(`Error handling Withdrawal Transaction: ${error}`); + } + } + return { connectDFNSWallet, handleFundingTransaction, + handleDepositTransaction, + handleWithdrawalTransaction, isLoading, }; } diff --git a/src/app/hooks/use-ledger.ts b/src/app/hooks/use-ledger.ts index 4b8de688..6619327a 100644 --- a/src/app/hooks/use-ledger.ts +++ b/src/app/hooks/use-ledger.ts @@ -330,6 +330,8 @@ export function useLedger(): UseLedgerReturnType { feeRateMultiplier ); + console.log('withdrawalPSBT', withdrawalPSBT); + setIsLoading([true, 'Sign Withdrawal Transaction in your Leather Wallet']); // ==> Sign Withdrawal PSBT with Ledger const withdrawalTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index d6b56447..099838b4 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -70,8 +70,12 @@ export function usePSBT(): UsePSBTReturnType { isLoading: isUnisatLoading, } = useUnisat(); - const { handleFundingTransaction: handleFundingTransactionWithDFNS, isLoading: isDFNSLoading } = - useDFNS(); + const { + handleFundingTransaction: handleFundingTransactionWithDFNS, + handleDepositTransaction: handleDepositTransactionWithDFNS, + handleWithdrawalTransaction: handleWithdrawalTransactionWithDFNS, + isLoading: isDFNSLoading, + } = useDFNS(); const [bitcoinDepositAmount, setBitcoinDepositAmount] = useState(0); @@ -154,8 +158,8 @@ export function usePSBT(): UsePSBTReturnType { ); break; default: - fundingTransaction = await handleDepositTransactionWithLedger( - dlcHandler as LedgerDLCHandler, + fundingTransaction = await handleDepositTransactionWithDFNS( + dlcHandler as DFNSDLCHandler, vault, depositAmount, attestorGroupPublicKey, @@ -275,6 +279,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'); } From 1d99500c1ac57c84d39c8b82cb5de32a25dbf356 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 7 Nov 2024 13:47:49 +0100 Subject: [PATCH 32/39] feat: add dfns modal --- public/images/logos/dfns-logo.svg | 2 +- .../modals/components/modal-container.tsx | 6 ++ .../components/dfns-modal-connect-button.tsx | 19 ++++ .../dfns-modal/components/dfns-modal-form.tsx | 94 +++++++++++++++++++ .../components/dfns-modal-layout.tsx | 41 ++++++++ .../components/ledger-modal-error-box.tsx | 35 +++++++ .../components/ledger-modal-loading-stack.tsx | 19 ++++ .../ledger-modal-address-button.tsx | 23 +++++ ...elect-address-menu-account-index-input.tsx | 54 +++++++++++ ...-select-address-menu-address-paginator.tsx | 35 +++++++ ...modal-select-address-menu-table-header.tsx | 27 ++++++ .../ledger-modal-select-address-menu.tsx | 43 +++++++++ .../components/ledger-modal-success-icon.tsx | 16 ++++ .../modals/dfns-modal/dfns-modal.tsx | 79 ++++++++++++++++ .../select-bitcoin-wallet-modal.tsx | 15 +-- src/app/hooks/use-dfns.ts | 64 ++++++++++--- src/app/store/slices/modal/modal.slice.ts | 5 + src/styles/button-theme.ts | 13 +++ src/styles/modal-theme.ts | 19 ++++ 19 files changed, 583 insertions(+), 26 deletions(-) create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-loading-stack.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-address-button.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx create mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-success-icon.tsx create mode 100644 src/app/components/modals/dfns-modal/dfns-modal.tsx diff --git a/public/images/logos/dfns-logo.svg b/public/images/logos/dfns-logo.svg index 8859757b..0cbb18da 100644 --- a/public/images/logos/dfns-logo.svg +++ b/public/images/logos/dfns-logo.svg @@ -1 +1 @@ - + diff --git a/src/app/components/modals/components/modal-container.tsx b/src/app/components/modals/components/modal-container.tsx index 84b71b2e..53acc5be 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-connect-button.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx new file mode 100644 index 00000000..af425148 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx @@ -0,0 +1,19 @@ +import { Button } from '@chakra-ui/react'; + +interface DFNSModalConnectButtonProps { + error: string | undefined; + connectDFNSWallet: () => void; +} + +export function DFNSModalConnectButton({ + error, + connectDFNSWallet, +}: DFNSModalConnectButtonProps): React.JSX.Element | false { + return ( + !!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 00000000..883df1e4 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx @@ -0,0 +1,94 @@ +import { + Box, + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + VStack, +} from '@chakra-ui/react'; +import { useForm } from '@tanstack/react-form'; + +interface DFNSModalLoginFormProps { + onSubmit: (username: string, organizationID: string) => Promise; +} + +export function DFNSModalLoginForm({ onSubmit }: DFNSModalLoginFormProps): React.JSX.Element { + const formAPI = useForm({ + defaultValues: { + username: '', + organizationId: '', + }, + onSubmit: async ({ value }) => { + await onSubmit(value.username, value.organizationId); + console.log('Form submitted:', value); + }, + }); + + return ( + + { + e.preventDefault(); + e.stopPropagation(); + void formAPI.handleSubmit(); + }} + > + + { + if (!value) return 'Username is required'; + if (value.length < 3) return 'Username must be at least 3 characters'; + return undefined; + }, + }} + children={field => ( + + Username + field.handleChange(e.target.value)} + placeholder="john@doe.com" + /> + {field.state.meta.errorMap.onChange} + + )} + /> + + { + if (!value) return 'Organization ID is required'; + return undefined; + }, + }} + children={field => ( + + Organization ID + field.handleChange(e.target.value)} + placeholder="Enter organization ID" + /> + {field.state.meta.errorMap.onChange} + + )} + /> + + [state.canSubmit]} + children={([canSubmit]) => ( + + )} + /> + + + + ); +} diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx new file mode 100644 index 00000000..de06904f --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx @@ -0,0 +1,41 @@ +import { ReactNode } from 'react'; + +import { + Image, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + VStack, +} from '@chakra-ui/react'; + +interface DFNSModalLayoutProps { + logo: string; + isOpen: boolean; + onClose: () => void; + children: ReactNode; +} + +export function DFNSModalLayout({ + logo, + isOpen, + onClose, + children, +}: DFNSModalLayoutProps): React.JSX.Element { + return ( + + + + + {'DFNS + + + + {children} + + + + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx new file mode 100644 index 00000000..9ee0c172 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx @@ -0,0 +1,35 @@ +import { HStack, Text } from '@chakra-ui/react'; + +interface LedgerModalErrorBoxProps { + error: string | undefined; +} + +function formatErrorMessage(error: string): string { + const errorMessages: Record = { + '0x6985': 'Action Rejected by User', + '0x5515': 'Locked Device', + '0x6a80': + "Invalid data received. Please ensure your Ledger hardware's firmware and Bitcoin app are up to date", + }; + + for (const [code, message] of Object.entries(errorMessages)) { + if (error.includes(code)) { + return message; + } + } + return error; +} + +export function LedgerModalErrorBox({ + error, +}: LedgerModalErrorBoxProps): React.JSX.Element | false { + return ( + !!error && ( + + + {formatErrorMessage(error || '')} + + + ) + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-loading-stack.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-loading-stack.tsx new file mode 100644 index 00000000..95dc93be --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-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/ledger-modal-select-address-menu/components/ledger-modal-address-button.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-address-button.tsx new file mode 100644 index 00000000..1bf61e6f --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-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/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx new file mode 100644 index 00000000..c78fa1d7 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx @@ -0,0 +1,54 @@ +import { useState } from 'react'; + +import { Button, HStack, NumberInput, NumberInputField, Text } from '@chakra-ui/react'; + +interface LedgerModalSelectAddressMenuAccountIndexInputProps { + walletAccountIndex: number; + setWalletAccountIndex: React.Dispatch>; +} + +export function LedgerModalSelectAddressMenuAccountIndexInput({ + walletAccountIndex, + setWalletAccountIndex, +}: LedgerModalSelectAddressMenuAccountIndexInputProps): React.JSX.Element { + const [currentWalletAccountIndex, setCurrentWalletAccountIndex] = useState(walletAccountIndex); + + return ( + + + Account Index: + + setCurrentWalletAccountIndex(Number(walletAccountIndex))} + > + + + + + + + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx new file mode 100644 index 00000000..911968b9 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx @@ -0,0 +1,35 @@ +import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'; +import { HStack, IconButton, Text } from '@chakra-ui/react'; + +interface LedgerModalSelectAddressMenuAddressPaginatorProps { + displayedAddressesStartIndex: number; + setDisplayedAddressesStartIndex: React.Dispatch>; +} + +export function DFNSModalSelectAddressMenuAddressPaginator({ + displayedAddressesStartIndex, + setDisplayedAddressesStartIndex, +}: LedgerModalSelectAddressMenuAddressPaginatorProps): React.JSX.Element { + return ( + + } + onClick={() => setDisplayedAddressesStartIndex(displayedAddressesStartIndex - 5)} + /> + + {displayedAddressesStartIndex + 1} - {displayedAddressesStartIndex + 5} + + } + onClick={() => setDisplayedAddressesStartIndex(displayedAddressesStartIndex + 5)} + /> + + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx new file mode 100644 index 00000000..17d0ff5b --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx @@ -0,0 +1,27 @@ +import { HStack, Stack, Text } from '@chakra-ui/react'; + +export function LedgerModalSelectAddressMenuTableHeader(): React.JSX.Element { + return ( + + + + + address + + + + + balance + + + + + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx new file mode 100644 index 00000000..3cb07d6a --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx @@ -0,0 +1,43 @@ +import { ButtonGroup, Text, VStack } from '@chakra-ui/react'; + +import { DFNSModalAddressButton } from './components/ledger-modal-address-button'; +import { DFNSModalSelectAddressMenuAddressPaginator } from './components/ledger-modal-select-address-menu-address-paginator'; + +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 Wallet + + {taprootAddresses?.map(address => ( + + ))} + + {/* */} + + ) + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-success-icon.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-success-icon.tsx new file mode 100644 index 00000000..03b65be5 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/ledger-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 00000000..d078fbea --- /dev/null +++ b/src/app/components/modals/dfns-modal/dfns-modal.tsx @@ -0,0 +1,79 @@ +import { useContext, useState } from 'react'; + +import { Collapse } from '@chakra-ui/react'; +import { useDFNS } from '@hooks/use-dfns'; +import { + BitcoinWalletContext, + BitcoinWalletContextState, +} from '@providers/bitcoin-wallet-context-provider'; +import { DFNSDLCHandler } from 'dlc-btc-lib'; +import { delay } from 'dlc-btc-lib/utilities'; + +import { ModalComponentProps } from '../components/modal-container'; +import { DFNSModalLoginForm } from './components/dfns-modal-form'; +import { DFNSModalLayout } from './components/dfns-modal-layout'; +import { DFNSModalLoadingStack } from './components/ledger-modal-loading-stack'; +import { DFNSModalSelectAddressMenu } from './components/ledger-modal-select-address-menu/ledger-modal-select-address-menu'; +import { DFNSModalSuccessIcon } from './components/ledger-modal-success-icon'; + +export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { + const { connectDFNSWallet, selectWallet, isLoading } = useDFNS(); + + const { setBitcoinWalletContextState, dlcHandler } = useContext(BitcoinWalletContext); + + const [taprootAddresses, setTaprootAddresses] = useState< + { address: string | undefined; walletID: string }[] | undefined + >(undefined); + + const [isSuccesful, setIsSuccesful] = useState(false); + const [isLoadingAddressList, setIsLoadingAddressList] = useState(true); + const [dfnsError, setDFNSError] = useState(undefined); + + function resetDFNSModalValues() { + setIsSuccesful(false); + setTaprootAddresses(undefined); + setIsLoadingAddressList(true); + setDFNSError(undefined); + } + + async function connectDFNSWalletAndGetAddresses(username: string, organizationID: string) { + try { + const taprootAddresses = await connectDFNSWallet(username, organizationID); + setTaprootAddresses(taprootAddresses); + setIsLoadingAddressList(false); + } catch (error: any) { + setDFNSError(error.message); + } + } + + async function setWalletID(walletID: string) { + try { + await selectWallet(walletID); + setIsSuccesful(true); + await delay(2500); + resetDFNSModalValues(); + setBitcoinWalletContextState(BitcoinWalletContextState.READY); + handleClose(); + } catch (error: any) { + setDFNSError(error.message); + } + } + + 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 b89aab4b..66e521ac 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 @@ -1,6 +1,6 @@ import { useDispatch } from 'react-redux'; -import { Button, VStack, useToast } from '@chakra-ui/react'; +import { VStack, useToast } from '@chakra-ui/react'; import { ModalComponentProps } from '@components/modals/components/modal-container'; import { ModalLayout } from '@components/modals/components/modal.layout'; import { useDFNS } from '@hooks/use-dfns'; @@ -67,17 +67,8 @@ export function SelectBitcoinWalletModal({ dispatch(modalActions.toggleLedgerModalVisibility()); break; case BitcoinWalletType.DFNS: - try { - await connectDFNSWallet(); - } catch (error: any) { - toast({ - title: 'Failed to connect to DFNS Wallet', - description: error.message, - status: 'error', - duration: 9000, - isClosable: true, - }); - } + dispatch(modalActions.toggleDFNSModalVisibility()); + break; break; default: break; diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index fb3f64b3..07cad17b 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -1,6 +1,6 @@ import { useContext, useState } from 'react'; -import { DfnsAuthenticator, DfnsDelegatedApiClient } from '@dfns/sdk'; +import { DfnsApiClient, DfnsAuthenticator, DfnsDelegatedApiClient } from '@dfns/sdk'; import { WebAuthnSigner } from '@dfns/sdk-browser'; import { LeatherError } from '@models/error-types'; import { BitcoinWalletType } from '@models/wallet'; @@ -16,42 +16,79 @@ import { bytesToHex, hexToBytes } from 'viem'; import { BITCOIN_NETWORK_MAP } from '@shared/constants/bitcoin.constants'; export function useDFNS() { - const { setDLCHandler, setBitcoinWalletContextState, setBitcoinWalletType } = + const { setDLCHandler, setBitcoinWalletContextState, setBitcoinWalletType, dlcHandler } = useContext(BitcoinWalletContext); const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); - async function connectDFNSWallet(): Promise { + async function connectDFNSWallet( + userName: string, + organizationID: string + ): Promise<{ address: string | undefined; walletID: string }[]> { try { setIsLoading([true, 'Connecting to DFNS Wallet']); + const signer = new WebAuthnSigner(); + const authApi = new DfnsAuthenticator({ + appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', + baseUrl: 'http://localhost:3000/dfns-proxy/', + signer, + }); + + const { token } = await authApi.login({ + username: userName, + orgId: organizationID, + }); + const dfnsDLCHandler = new DFNSDLCHandler( 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', 'http://localhost:3000/dfns-proxy/', - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJhdXRoLmRmbnMubmluamEiLCJhdWQiOiJkZm5zOmF1dGg6dXNlciIsInN1YiI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJqdGkiOiJ1ai02NjNpZS1jdTY3cC05dXRhb2UyYm1za2pvMzJpIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJBdXRoOkFjdGlvbjpTaWduIiwiQXV0aDpBcHBzOkNyZWF0ZSIsIkF1dGg6QXBwczpSZWFkIiwiQXV0aDpBcHBzOlVwZGF0ZSIsIkF1dGg6Q3JlZHM6Q3JlYXRlIiwiQXV0aDpDcmVkczpSZWFkIiwiQXV0aDpDcmVkczpVcGRhdGUiLCJBdXRoOkNyZWRzOkNvZGU6Q3JlYXRlIiwiQXV0aDpUeXBlczpBcHBsaWNhdGlvbiIsIkF1dGg6VHlwZXM6RW1wbG95ZWUiLCJBdXRoOlR5cGVzOkVuZFVzZXIiLCJBdXRoOlR5cGVzOlBhdCIsIkF1dGg6VHlwZXM6U2VydmljZUFjY291bnQiLCJBdXRoOlVzZXJzOkNyZWF0ZSIsIkF1dGg6VXNlcnM6RGVsZWdhdGUiLCJBdXRoOlVzZXJzOlJlYWQiLCJBdXRoOlVzZXJzOlVwZGF0ZSIsIkV4Y2hhbmdlczpDcmVhdGUiLCJFeGNoYW5nZXM6UmVhZCIsIkV4Y2hhbmdlczpEZWxldGUiLCJFeGNoYW5nZXM6RGVwb3NpdHM6Q3JlYXRlIiwiRXhjaGFuZ2VzOldpdGhkcmF3YWxzOkNyZWF0ZSIsIk9yZ3M6UmVhZCIsIk9yZ3M6VXBkYXRlIiwiT3JnczpTZXR0aW5nczpSZWFkIiwiT3JnczpTZXR0aW5nczpVcGRhdGUiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6Q3JlYXRlIiwiUGVybWlzc2lvbkFzc2lnbm1lbnRzOlJlYWQiLCJQZXJtaXNzaW9uQXNzaWdubWVudHM6UmV2b2tlIiwiUGVybWlzc2lvblByZWRpY2F0ZXM6QXJjaGl2ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOkNyZWF0ZSIsIlBlcm1pc3Npb25QcmVkaWNhdGVzOlJlYWQiLCJQZXJtaXNzaW9uUHJlZGljYXRlczpVcGRhdGUiLCJQZXJtaXNzaW9uczpBcmNoaXZlIiwiUGVybWlzc2lvbnM6Q3JlYXRlIiwiUGVybWlzc2lvbnM6UmVhZCIsIlBlcm1pc3Npb25zOlVwZGF0ZSIsIlBvbGljaWVzOkFyY2hpdmUiLCJQb2xpY2llczpDcmVhdGUiLCJQb2xpY2llczpSZWFkIiwiUG9saWNpZXM6VXBkYXRlIiwiUG9saWNpZXM6QXBwcm92YWxzOlJlYWQiLCJQb2xpY2llczpBcHByb3ZhbHM6QXBwcm92ZSIsIlNpZ25lcnM6TGlzdFNpZ25lcnMiLCJXYWxsZXRzOkJyb2FkY2FzdFRyYW5zYWN0aW9uIiwiV2FsbGV0czpDcmVhdGUiLCJXYWxsZXRzOkRlbGVnYXRlIiwiV2FsbGV0czpFeHBvcnQiLCJXYWxsZXRzOkdlbmVyYXRlU2lnbmF0dXJlIiwiV2FsbGV0czpJbXBvcnQiLCJXYWxsZXRzOlJlYWQiLCJXYWxsZXRzOlJlYWRTaWduYXR1cmUiLCJXYWxsZXRzOlJlYWRUcmFuc2FjdGlvbiIsIldhbGxldHM6UmVhZFRyYW5zZmVyIiwiV2FsbGV0czpUcmFuc2ZlckFzc2V0IiwiV2FsbGV0czpVcGRhdGUiLCJXYWxsZXRzOlRhZ3M6QWRkIiwiV2FsbGV0czpUYWdzOkRlbGV0ZSIsIldlYmhvb2tzOkNyZWF0ZSIsIldlYmhvb2tzOlJlYWQiLCJXZWJob29rczpVcGRhdGUiLCJXZWJob29rczpEZWxldGUiLCJXZWJob29rczpQaW5nIiwiV2ViaG9va3M6RXZlbnRzOlJlYWQiXSwiaHR0cHM6Ly9jdXN0b20vdXNlcm5hbWUiOiJkYW5pQGRsYy5saW5rIiwiaHR0cHM6Ly9jdXN0b20vYXBwX21ldGFkYXRhIjp7InVzZXJJZCI6InVzLTEybmtzLW80b2dkLTk0M3FocTVsNTAyMDQxMG0iLCJvcmdJZCI6Im9yLTRiY3VuLWZqM3RiLThucDlpdmZ1NWpuc3Rra3MiLCJ0b2tlbktpbmQiOiJUb2tlbiJ9LCJpYXQiOjE3MzA4ODU1MjcsImV4cCI6MTczMDkwNzEyN30.dEhmC4XuPHEztZAlysNfJAwqnaiOe9V3aGj2DXG24X6Vj3JnJC1dqc2cXJan2A6Uxgd-QB3eMkP5Ts9dv9YoCw', + token, BITCOIN_NETWORK_MAP[appConfiguration.bitcoinNetwork], appConfiguration.bitcoinBlockchainURL, appConfiguration.bitcoinBlockchainFeeEstimateURL ); - console.log('dfnsDLCHandler', dfnsDLCHandler); - - const wallets = await dfnsDLCHandler.getWallets(); - console.log('wallets', wallets); - await dfnsDLCHandler.getWalletByID('wa-1vaij-cabue-9mrakrc7p8nntubp'); setDLCHandler(dfnsDLCHandler); setBitcoinWalletType(BitcoinWalletType.DFNS); - setBitcoinWalletContextState(BitcoinWalletContextState.READY); - setIsLoading([false, '']); + + return (await dfnsDLCHandler.getWallets()).items + .filter( + item => + item.network === 'BitcoinTestnet3' && + 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 registerCredentials(): Promise { - // const + async function selectWallet(walletId: string): Promise { + try { + console.log('walletId', walletId); + setIsLoading([true, 'Selecting DFNS Wallet']); + + await (dlcHandler as DFNSDLCHandler).getWalletByID(walletId); + + console.log('dlcHandler', dlcHandler); + + setIsLoading([false, '']); + } catch (error) { + setIsLoading([false, '']); + throw new Error(`Error selecting Wallet: ${error}`); + } + } + + async function registerCredentials(): Promise { + const dnfsAPIClient = new DfnsApiClient({}); + } /** * Creates the Funding Transaction and signs it with Leather Wallet. @@ -161,6 +198,7 @@ export function useDFNS() { return { connectDFNSWallet, + selectWallet, handleFundingTransaction, handleDepositTransaction, handleWithdrawalTransaction, diff --git a/src/app/store/slices/modal/modal.slice.ts b/src/app/store/slices/modal/modal.slice.ts index ef32b01b..03e94454 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: false, }; 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/styles/button-theme.ts b/src/styles/button-theme.ts index 56c887a7..addf6fce 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/modal-theme.ts b/src/styles/modal-theme.ts index 97e04200..4ec9205c 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({ From 11c4fe11fad935556d503df1fd1a059ab5203ab0 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 7 Nov 2024 20:07:08 +0100 Subject: [PATCH 33/39] feat: add logs --- src/app/hooks/use-dfns.ts | 7 +++++-- src/app/hooks/use-leather.ts | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index 07cad17b..c256d9d6 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -186,10 +186,13 @@ export function useDFNS() { console.log('withdrawPSBT', bytesToHex(withdrawalPSBT.toPSBT())); setIsLoading([true, 'Sign Withdraw Transaction in your DFNS Wallet']); - const signedWithdrawTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); + const signedWithdrawTransaction = await dlcHandler.signInput(withdrawalPSBT, 'withdraw'); + console.log('signedWithdrawTransaction', signedWithdrawTransaction); setIsLoading([false, '']); - return bytesToHex(signedWithdrawTransaction.toPSBT()); + const psbtHex = bytesToHex(signedWithdrawTransaction.toPSBT()); + console.log('psbtHex', psbtHex); + return psbtHex.slice(2); } catch (error) { setIsLoading([false, '']); throw new Error(`Error handling Withdrawal Transaction: ${error}`); diff --git a/src/app/hooks/use-leather.ts b/src/app/hooks/use-leather.ts index 9c4780ce..01d93617 100644 --- a/src/app/hooks/use-leather.ts +++ b/src/app/hooks/use-leather.ts @@ -250,10 +250,15 @@ export function useLeather(): UseLeatherReturnType { feeRateMultiplier ); + console.log('withdrawalTransaction', withdrawalTransaction); + setIsLoading([true, 'Sign Withdrawal Transaction in your Leather Wallet']); // ==> Sign Withdrawal PSBT with Ledger const withdrawalTransactionHex = await signPSBT(withdrawalTransaction.toPSBT()); - + console.log( + 'withdrawalTransactionHex', + Transaction.fromPSBT(hexToBytes(withdrawalTransactionHex)) + ); setIsLoading([false, '']); return withdrawalTransactionHex; } catch (error) { From 2739246284890f858ce078dad83aecea6857f261 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 19 Nov 2024 09:19:29 +0100 Subject: [PATCH 34/39] feat: modify withdraw handling --- src/app/hooks/use-dfns.ts | 24 ++++++++++++++++++++---- src/app/hooks/use-psbt.ts | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index c256d9d6..b7088ee9 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -9,8 +9,10 @@ import { BitcoinWalletContextState, } from '@providers/bitcoin-wallet-context-provider'; import { DFNSDLCHandler } from 'dlc-btc-lib'; -import { RawVault, Transaction } from 'dlc-btc-lib/models'; +import { broadcastTransaction } from 'dlc-btc-lib/bitcoin-functions'; +import { RawVault, Transaction, VaultState } from 'dlc-btc-lib/models'; import { shiftValue } from 'dlc-btc-lib/utilities'; +import { BigNumber } from 'ethers'; import { bytesToHex, hexToBytes } from 'viem'; import { BITCOIN_NETWORK_MAP } from '@shared/constants/bitcoin.constants'; @@ -110,7 +112,8 @@ export function useDFNS() { vault, BigInt(shiftValue(bitcoinAmount)), attestorGroupPublicKey, - feeRateMultiplier + feeRateMultiplier, + 500n ); setIsLoading([true, 'Sign Funding Transaction in your DFNS Wallet']); @@ -178,7 +181,8 @@ export function useDFNS() { BigInt(shiftValue(withdrawAmount)), attestorGroupPublicKey, vault.fundingTxId, - feeRateMultiplier + feeRateMultiplier, + 500n ); console.log('withdrawalPSBT', withdrawalPSBT); @@ -186,7 +190,19 @@ export function useDFNS() { console.log('withdrawPSBT', bytesToHex(withdrawalPSBT.toPSBT())); setIsLoading([true, 'Sign Withdraw Transaction in your DFNS Wallet']); - const signedWithdrawTransaction = await dlcHandler.signInput(withdrawalPSBT, 'withdraw'); + const input = withdrawalPSBT.getInput(0); + const tapMerkleRoot = input.tapMerkleRoot; + withdrawalPSBT.updateInput(0, { + ...input, + tapMerkleRoot: undefined, + }); + const signedWithdrawTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); + + const signedInput = signedWithdrawTransaction.getInput(0); + signedWithdrawTransaction.updateInput(0, { + ...signedInput, + tapMerkleRoot, + }); console.log('signedWithdrawTransaction', signedWithdrawTransaction); setIsLoading([false, '']); diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 099838b4..8db7e23f 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -221,7 +221,7 @@ export function usePSBT(): UsePSBTReturnType { vaultUUID, fundingPSBT: bytesToHex(fundingTransaction.toPSBT()), userEthereumAddress: userAddress, - userBitcoinTaprootPublicKey: dlcHandler.getTaprootDerivedPublicKey(), + userBitcoinTaprootPublicKey: (dlcHandler as DFNSDLCHandler).tweakedPublicKey, attestorChainID: attestorChainIDs[networkType] as AttestorChainID, }); break; From f326029826c1c66bdfb24f31100140b058d41674 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 19 Nov 2024 16:09:05 +0100 Subject: [PATCH 35/39] feat: modify withdraw and deposit dfns functions --- src/app/hooks/use-dfns.ts | 88 +++++++++++++++++++-------------------- src/app/hooks/use-psbt.ts | 6 ++- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index b7088ee9..6836ac01 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -1,25 +1,48 @@ import { useContext, useState } from 'react'; -import { DfnsApiClient, DfnsAuthenticator, DfnsDelegatedApiClient } from '@dfns/sdk'; +import { DfnsAuthenticator } from '@dfns/sdk'; import { WebAuthnSigner } from '@dfns/sdk-browser'; -import { LeatherError } from '@models/error-types'; import { BitcoinWalletType } from '@models/wallet'; -import { - BitcoinWalletContext, - BitcoinWalletContextState, -} from '@providers/bitcoin-wallet-context-provider'; +import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; import { DFNSDLCHandler } from 'dlc-btc-lib'; -import { broadcastTransaction } from 'dlc-btc-lib/bitcoin-functions'; -import { RawVault, Transaction, VaultState } from 'dlc-btc-lib/models'; +import { RawVault, Transaction } from 'dlc-btc-lib/models'; import { shiftValue } from 'dlc-btc-lib/utilities'; -import { BigNumber } from 'ethers'; -import { bytesToHex, hexToBytes } from 'viem'; +import { bytesToHex } from 'viem'; import { BITCOIN_NETWORK_MAP } from '@shared/constants/bitcoin.constants'; -export function useDFNS() { - const { setDLCHandler, setBitcoinWalletContextState, setBitcoinWalletType, dlcHandler } = - useContext(BitcoinWalletContext); +interface UseDFNSReturnType { + connectDFNSWallet: ( + userName: string, + organizationID: string + ) => Promise<{ address: string | undefined; walletID: string }[]>; + 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, '']); @@ -74,12 +97,9 @@ export function useDFNS() { async function selectWallet(walletId: string): Promise { try { - console.log('walletId', walletId); setIsLoading([true, 'Selecting DFNS Wallet']); - await (dlcHandler as DFNSDLCHandler).getWalletByID(walletId); - - console.log('dlcHandler', dlcHandler); + await (dlcHandler as DFNSDLCHandler).initializeWalletByID(walletId); setIsLoading([false, '']); } catch (error) { @@ -88,9 +108,9 @@ export function useDFNS() { } } - async function registerCredentials(): Promise { - const dnfsAPIClient = new DfnsApiClient({}); - } + // async function registerCredentials(): Promise { + // const dnfsAPIClient = new DfnsApiClient({}); + // } /** * Creates the Funding Transaction and signs it with Leather Wallet. @@ -118,12 +138,8 @@ export function useDFNS() { setIsLoading([true, 'Sign Funding Transaction in your DFNS Wallet']); - console.log('fundingPSBT', fundingPSBT); - const signedFundingTransaction = await dlcHandler.signPSBT(fundingPSBT, 'funding'); - console.log('signedFundingTransaction', signedFundingTransaction); - setIsLoading([false, '']); return signedFundingTransaction; } catch (error) { @@ -152,12 +168,8 @@ export function useDFNS() { setIsLoading([true, 'Sign Deposit Transaction in your DFNS Wallet']); - console.log('depositPSBT', depositPSBT); - const signedDepositTransaction = await dlcHandler.signPSBT(depositPSBT, 'deposit'); - console.log('signedDepositTransaction', signedDepositTransaction); - setIsLoading([false, '']); return signedDepositTransaction; } catch (error) { @@ -185,29 +197,13 @@ export function useDFNS() { 500n ); - console.log('withdrawalPSBT', withdrawalPSBT); - - console.log('withdrawPSBT', bytesToHex(withdrawalPSBT.toPSBT())); - setIsLoading([true, 'Sign Withdraw Transaction in your DFNS Wallet']); - const input = withdrawalPSBT.getInput(0); - const tapMerkleRoot = input.tapMerkleRoot; - withdrawalPSBT.updateInput(0, { - ...input, - tapMerkleRoot: undefined, - }); - const signedWithdrawTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); - const signedInput = signedWithdrawTransaction.getInput(0); - signedWithdrawTransaction.updateInput(0, { - ...signedInput, - tapMerkleRoot, - }); - console.log('signedWithdrawTransaction', signedWithdrawTransaction); + const signedWithdrawTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); setIsLoading([false, '']); const psbtHex = bytesToHex(signedWithdrawTransaction.toPSBT()); - console.log('psbtHex', psbtHex); + return psbtHex.slice(2); } catch (error) { setIsLoading([false, '']); diff --git a/src/app/hooks/use-psbt.ts b/src/app/hooks/use-psbt.ts index 8db7e23f..8f705eec 100644 --- a/src/app/hooks/use-psbt.ts +++ b/src/app/hooks/use-psbt.ts @@ -214,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 as DFNSDLCHandler).tweakedPublicKey, + userBitcoinTaprootPublicKey: + bitcoinWalletType === 'DFNS' + ? `02${(dlcHandler as DFNSDLCHandler).getTaprootTweakedPublicKey()}` + : dlcHandler.getTaprootDerivedPublicKey(), attestorChainID: attestorChainIDs[networkType] as AttestorChainID, }); break; From 1f66a2f4a1eee545e805f029166d8f345c7dfe90 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 19 Nov 2024 17:24:40 +0100 Subject: [PATCH 36/39] feat: add dfns credentials register form and logic --- config.testnet.json | 2 + .../components/dfns-modal-connect-button.tsx | 19 ------ .../dfns-modal-credentials-form.tsx | 62 +++++++++++++++++++ .../components/dfns-modal-error-box.tsx | 17 +++++ .../dfns-modal/components/dfns-modal-form.tsx | 12 +--- ...stack.tsx => dfns-modal-loading-stack.tsx} | 0 .../dfns-modal-navigator-button-stack.tsx | 28 +++++++++ .../components/dfns-modal-address-button.tsx} | 0 .../dfns-modal-select-address-menu.tsx} | 7 +-- ...s-icon.tsx => dfns-modal-success-icon.tsx} | 0 .../components/ledger-modal-error-box.tsx | 35 ----------- ...elect-address-menu-account-index-input.tsx | 54 ---------------- ...-select-address-menu-address-paginator.tsx | 35 ----------- ...modal-select-address-menu-table-header.tsx | 27 -------- .../modals/dfns-modal/dfns-modal.tsx | 47 +++++++++++--- .../select-bitcoin-wallet-modal.tsx | 2 - src/app/hooks/use-dfns.ts | 49 +++++++++++---- src/shared/models/configuration.ts | 2 + 18 files changed, 187 insertions(+), 211 deletions(-) delete mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-credentials-form.tsx create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx rename src/app/components/modals/dfns-modal/components/{ledger-modal-loading-stack.tsx => dfns-modal-loading-stack.tsx} (100%) create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-navigator-button-stack.tsx rename src/app/components/modals/dfns-modal/components/{ledger-modal-select-address-menu/components/ledger-modal-address-button.tsx => dfns-modal-select-address-menu/components/dfns-modal-address-button.tsx} (100%) rename src/app/components/modals/dfns-modal/components/{ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx => dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx} (69%) rename src/app/components/modals/dfns-modal/components/{ledger-modal-success-icon.tsx => dfns-modal-success-icon.tsx} (100%) delete mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx delete mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx delete mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx delete mode 100644 src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx diff --git a/config.testnet.json b/config.testnet.json index ac5dad86..56e8a5b8 100644 --- a/config.testnet.json +++ b/config.testnet.json @@ -12,6 +12,8 @@ "rippleIssuerAddress": "ra3oyRVfy4yD4NJPrVcewvDtisZ3FhkcYL", "xrplWebsocket": "wss://testnet.xrpl-labs.com/", "ledgerApp": "Bitcoin Test", + "dfnsAppID": "ap-7pvrc-mei7e-9u3pfedu9tidiq6p", + "dfnsBaseURL": "http://localhost:3000/dfns-proxy/", "merchants": [ { "name": "Amber", diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx deleted file mode 100644 index af425148..00000000 --- a/src/app/components/modals/dfns-modal/components/dfns-modal-connect-button.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Button } from '@chakra-ui/react'; - -interface DFNSModalConnectButtonProps { - error: string | undefined; - connectDFNSWallet: () => void; -} - -export function DFNSModalConnectButton({ - error, - connectDFNSWallet, -}: DFNSModalConnectButtonProps): React.JSX.Element | false { - return ( - !!error && ( - - ) - ); -} 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 00000000..ac3da033 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-credentials-form.tsx @@ -0,0 +1,62 @@ +import { Button, FormControl, FormErrorMessage, FormLabel, Input, VStack } from '@chakra-ui/react'; +import { useForm } from '@tanstack/react-form'; + +interface DFNSModalRegisterFormProps { + onSubmit: (credentialCode: string) => Promise; +} + +export function DFNSModalRegisterForm({ onSubmit }: DFNSModalRegisterFormProps): React.JSX.Element { + const formAPI = useForm({ + defaultValues: { + credentialCode: '', + }, + onSubmit: async ({ value }) => { + await onSubmit(value.credentialCode); + }, + }); + + return ( + +
{ + e.preventDefault(); + e.stopPropagation(); + void formAPI.handleSubmit(); + }} + > + + { + if (!value) return 'Credential Code is required'; + if (value.length < 9) return 'Credential Code must be at least 9 characters'; + return undefined; + }, + }} + children={field => ( + + Credential Code + field.handleChange(e.target.value)} + placeholder="xxxxxxxxx" + /> + {field.state.meta.errorMap.onChange} + + )} + /> + [state.canSubmit]} + children={([canSubmit]) => ( + + )} + /> + +
+
+ ); +} diff --git a/src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx new file mode 100644 index 00000000..95f8d20d --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-error-box.tsx @@ -0,0 +1,17 @@ +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 index 883df1e4..fd1c3218 100644 --- a/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx @@ -1,12 +1,4 @@ -import { - Box, - Button, - FormControl, - FormErrorMessage, - FormLabel, - Input, - VStack, -} from '@chakra-ui/react'; +import { Button, FormControl, FormErrorMessage, FormLabel, Input, VStack } from '@chakra-ui/react'; import { useForm } from '@tanstack/react-form'; interface DFNSModalLoginFormProps { @@ -21,7 +13,6 @@ export function DFNSModalLoginForm({ onSubmit }: DFNSModalLoginFormProps): React }, onSubmit: async ({ value }) => { await onSubmit(value.username, value.organizationId); - console.log('Form submitted:', value); }, }); @@ -78,7 +69,6 @@ export function DFNSModalLoginForm({ onSubmit }: DFNSModalLoginFormProps): React )} /> - [state.canSubmit]} children={([canSubmit]) => ( diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-loading-stack.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-loading-stack.tsx similarity index 100% rename from src/app/components/modals/dfns-modal/components/ledger-modal-loading-stack.tsx rename to src/app/components/modals/dfns-modal/components/dfns-modal-loading-stack.tsx 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 00000000..61a7dc2b --- /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 username and organization ID'} + + + + ); +} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-address-button.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/components/dfns-modal-address-button.tsx similarity index 100% rename from src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-address-button.tsx rename to src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/components/dfns-modal-address-button.tsx diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx similarity index 69% rename from src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx rename to src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx index 3cb07d6a..7e17b070 100644 --- a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/ledger-modal-select-address-menu.tsx +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-select-address-menu/dfns-modal-select-address-menu.tsx @@ -1,7 +1,6 @@ import { ButtonGroup, Text, VStack } from '@chakra-ui/react'; -import { DFNSModalAddressButton } from './components/ledger-modal-address-button'; -import { DFNSModalSelectAddressMenuAddressPaginator } from './components/ledger-modal-select-address-menu-address-paginator'; +import { DFNSModalAddressButton } from './components/dfns-modal-address-button'; interface DFNSModalSelectAddressMenuProps { taprootAddresses: { address: string | undefined; walletID: string }[] | undefined; @@ -33,10 +32,6 @@ export function DFNSModalSelectAddressMenu({ /> ))} - {/* */}
) ); diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-success-icon.tsx b/src/app/components/modals/dfns-modal/components/dfns-modal-success-icon.tsx similarity index 100% rename from src/app/components/modals/dfns-modal/components/ledger-modal-success-icon.tsx rename to src/app/components/modals/dfns-modal/components/dfns-modal-success-icon.tsx diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx deleted file mode 100644 index 9ee0c172..00000000 --- a/src/app/components/modals/dfns-modal/components/ledger-modal-error-box.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { HStack, Text } from '@chakra-ui/react'; - -interface LedgerModalErrorBoxProps { - error: string | undefined; -} - -function formatErrorMessage(error: string): string { - const errorMessages: Record = { - '0x6985': 'Action Rejected by User', - '0x5515': 'Locked Device', - '0x6a80': - "Invalid data received. Please ensure your Ledger hardware's firmware and Bitcoin app are up to date", - }; - - for (const [code, message] of Object.entries(errorMessages)) { - if (error.includes(code)) { - return message; - } - } - return error; -} - -export function LedgerModalErrorBox({ - error, -}: LedgerModalErrorBoxProps): React.JSX.Element | false { - return ( - !!error && ( - - - {formatErrorMessage(error || '')} - - - ) - ); -} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx deleted file mode 100644 index c78fa1d7..00000000 --- a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-account-index-input.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useState } from 'react'; - -import { Button, HStack, NumberInput, NumberInputField, Text } from '@chakra-ui/react'; - -interface LedgerModalSelectAddressMenuAccountIndexInputProps { - walletAccountIndex: number; - setWalletAccountIndex: React.Dispatch>; -} - -export function LedgerModalSelectAddressMenuAccountIndexInput({ - walletAccountIndex, - setWalletAccountIndex, -}: LedgerModalSelectAddressMenuAccountIndexInputProps): React.JSX.Element { - const [currentWalletAccountIndex, setCurrentWalletAccountIndex] = useState(walletAccountIndex); - - return ( - - - Account Index: - - setCurrentWalletAccountIndex(Number(walletAccountIndex))} - > - - - - - - - ); -} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx deleted file mode 100644 index 911968b9..00000000 --- a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-address-paginator.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'; -import { HStack, IconButton, Text } from '@chakra-ui/react'; - -interface LedgerModalSelectAddressMenuAddressPaginatorProps { - displayedAddressesStartIndex: number; - setDisplayedAddressesStartIndex: React.Dispatch>; -} - -export function DFNSModalSelectAddressMenuAddressPaginator({ - displayedAddressesStartIndex, - setDisplayedAddressesStartIndex, -}: LedgerModalSelectAddressMenuAddressPaginatorProps): React.JSX.Element { - return ( - - } - onClick={() => setDisplayedAddressesStartIndex(displayedAddressesStartIndex - 5)} - /> - - {displayedAddressesStartIndex + 1} - {displayedAddressesStartIndex + 5} - - } - onClick={() => setDisplayedAddressesStartIndex(displayedAddressesStartIndex + 5)} - /> - - ); -} diff --git a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx b/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx deleted file mode 100644 index 17d0ff5b..00000000 --- a/src/app/components/modals/dfns-modal/components/ledger-modal-select-address-menu/components/ledger-modal-select-address-menu-table-header.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { HStack, Stack, Text } from '@chakra-ui/react'; - -export function LedgerModalSelectAddressMenuTableHeader(): React.JSX.Element { - return ( - - - - - address - - - - - balance - - - - - ); -} diff --git a/src/app/components/modals/dfns-modal/dfns-modal.tsx b/src/app/components/modals/dfns-modal/dfns-modal.tsx index d078fbea..fc43d79d 100644 --- a/src/app/components/modals/dfns-modal/dfns-modal.tsx +++ b/src/app/components/modals/dfns-modal/dfns-modal.tsx @@ -6,26 +6,29 @@ import { BitcoinWalletContext, BitcoinWalletContextState, } from '@providers/bitcoin-wallet-context-provider'; -import { DFNSDLCHandler } from 'dlc-btc-lib'; 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/ledger-modal-loading-stack'; -import { DFNSModalSelectAddressMenu } from './components/ledger-modal-select-address-menu/ledger-modal-select-address-menu'; -import { DFNSModalSuccessIcon } from './components/ledger-modal-success-icon'; +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 { DFNSModalSuccessIcon } from './components/dfns-modal-success-icon'; export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { - const { connectDFNSWallet, selectWallet, isLoading } = useDFNS(); + const { connectDFNSWallet, registerCredentials, selectWallet, isLoading } = useDFNS(); - const { setBitcoinWalletContextState, dlcHandler } = useContext(BitcoinWalletContext); + const { setBitcoinWalletContextState } = useContext(BitcoinWalletContext); const [taprootAddresses, setTaprootAddresses] = useState< { address: string | undefined; walletID: string }[] | undefined >(undefined); const [isSuccesful, setIsSuccesful] = useState(false); + const [isRegister, setIsRegister] = useState(false); const [isLoadingAddressList, setIsLoadingAddressList] = useState(true); const [dfnsError, setDFNSError] = useState(undefined); @@ -36,13 +39,28 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J setDFNSError(undefined); } + async function setError(error: string) { + setDFNSError(error); + await delay(5000); + setDFNSError(undefined); + } + async function connectDFNSWalletAndGetAddresses(username: string, organizationID: string) { try { const taprootAddresses = await connectDFNSWallet(username, organizationID); setTaprootAddresses(taprootAddresses); setIsLoadingAddressList(false); } catch (error: any) { - setDFNSError(error.message); + await setError(error.message); + } + } + + async function handleRegisterCredentials(code: string) { + try { + await registerCredentials(code); + setIsRegister(false); + } catch (error: any) { + await setError(error.message); } } @@ -55,7 +73,7 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J setBitcoinWalletContextState(BitcoinWalletContextState.READY); handleClose(); } catch (error: any) { - setDFNSError(error.message); + await setError(error.message); } } @@ -63,7 +81,11 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J - + {!isRegister ? ( + + ) : ( + + )} - {/* */} + + ); } 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 66e521ac..64c7d8b4 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 @@ -3,7 +3,6 @@ import { useDispatch } from 'react-redux'; import { VStack, useToast } from '@chakra-ui/react'; import { ModalComponentProps } from '@components/modals/components/modal-container'; import { ModalLayout } from '@components/modals/components/modal.layout'; -import { useDFNS } from '@hooks/use-dfns'; import { useLeather } from '@hooks/use-leather'; import { useUnisat } from '@hooks/use-unisat'; import { BitcoinWalletType, bitcoinWallets } from '@models/wallet'; @@ -20,7 +19,6 @@ export function SelectBitcoinWalletModal({ const toast = useToast(); const { connectLeatherWallet } = useLeather(); const { connectUnisatWallet } = useUnisat(); - const { connectDFNSWallet } = useDFNS(); async function handleLogin(walletType: BitcoinWalletType) { switch (walletType) { diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index 6836ac01..5293cf1c 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -1,6 +1,6 @@ import { useContext, useState } from 'react'; -import { DfnsAuthenticator } from '@dfns/sdk'; +import { DfnsApiClient, DfnsAuthenticator } from '@dfns/sdk'; import { WebAuthnSigner } from '@dfns/sdk-browser'; import { BitcoinWalletType } from '@models/wallet'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; @@ -16,6 +16,7 @@ interface UseDFNSReturnType { userName: string, organizationID: string ) => Promise<{ address: string | undefined; walletID: string }[]>; + registerCredentials: (code: string) => Promise; selectWallet: (walletId: string) => Promise; handleFundingTransaction: ( dlcHandler: DFNSDLCHandler, @@ -55,8 +56,8 @@ export function useDFNS(): UseDFNSReturnType { const signer = new WebAuthnSigner(); const authApi = new DfnsAuthenticator({ - appId: 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', - baseUrl: 'http://localhost:3000/dfns-proxy/', + appId: appConfiguration.dfnsAppID, + baseUrl: appConfiguration.dfnsBaseURL, signer, }); @@ -66,8 +67,8 @@ export function useDFNS(): UseDFNSReturnType { }); const dfnsDLCHandler = new DFNSDLCHandler( - 'ap-7pvrc-mei7e-9u3pfedu9tidiq6p', - 'http://localhost:3000/dfns-proxy/', + appConfiguration.dfnsAppID, + appConfiguration.dfnsBaseURL, token, BITCOIN_NETWORK_MAP[appConfiguration.bitcoinNetwork], appConfiguration.bitcoinBlockchainURL, @@ -108,9 +109,34 @@ export function useDFNS(): UseDFNSReturnType { } } - // async function registerCredentials(): Promise { - // const dnfsAPIClient = new DfnsApiClient({}); - // } + async function registerCredentials(code: string): Promise { + const signer = new WebAuthnSigner(); + const dfnsAPI = new DfnsApiClient({ + appId: appConfiguration.dfnsAppID, + authToken: undefined, + baseUrl: appConfiguration.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. @@ -132,8 +158,7 @@ export function useDFNS(): UseDFNSReturnType { vault, BigInt(shiftValue(bitcoinAmount)), attestorGroupPublicKey, - feeRateMultiplier, - 500n + feeRateMultiplier ); setIsLoading([true, 'Sign Funding Transaction in your DFNS Wallet']); @@ -193,8 +218,7 @@ export function useDFNS(): UseDFNSReturnType { BigInt(shiftValue(withdrawAmount)), attestorGroupPublicKey, vault.fundingTxId, - feeRateMultiplier, - 500n + feeRateMultiplier ); setIsLoading([true, 'Sign Withdraw Transaction in your DFNS Wallet']); @@ -213,6 +237,7 @@ export function useDFNS(): UseDFNSReturnType { return { connectDFNSWallet, + registerCredentials, selectWallet, handleFundingTransaction, handleDepositTransaction, diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index 14ef6743..847bf7c7 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -42,6 +42,8 @@ export interface Configuration { bitcoinBlockchainFeeEstimateURL: string; rippleIssuerAddress: string; ledgerApp: string; + dfnsAppID: string; + dfnsBaseURL: string; merchants: Merchant[]; protocols: Protocol[]; } From 3286ffcc47295e3c0b56edc725d44a5137bf1864 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 20 Nov 2024 14:11:13 +0100 Subject: [PATCH 37/39] feat: add dfns network map --- src/app/hooks/use-dfns.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index 5293cf1c..77486e1e 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -11,6 +11,12 @@ 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, @@ -82,7 +88,7 @@ export function useDFNS(): UseDFNSReturnType { return (await dfnsDLCHandler.getWallets()).items .filter( item => - item.network === 'BitcoinTestnet3' && + item.network === BITCOIN_NETWORK_DFNS_MAP[appConfiguration.bitcoinNetwork] && item.signingKey.scheme === 'Schnorr' && item.signingKey.curve === 'secp256k1' ) From ada07ddc2720d404458ba02049b7c8682c72c3ce Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 3 Dec 2024 12:11:18 +0100 Subject: [PATCH 38/39] feat: modify dfns modal form and config --- config.devnet.json | 10 ++++ config.mainnet.json | 10 ++++ config.testnet.json | 12 ++++- package.json | 2 +- .../components/dfns-modal-error-box.tsx | 10 +++- .../dfns-modal/components/dfns-modal-form.tsx | 53 +++++++------------ .../components/dfns-modal-layout.tsx | 4 +- .../dfns-modal-navigator-button-stack.tsx | 6 +-- .../dfns-modal-select-address-menu.tsx | 14 +++-- .../modals/dfns-modal/dfns-modal.tsx | 4 +- .../select-bitcoin-wallet-modal-menu.tsx | 2 +- src/app/hooks/use-dfns.ts | 40 ++++++++++---- src/shared/models/configuration.ts | 14 ++++- src/styles/css-styles.ts | 13 +++++ yarn.lock | 6 ++- 15 files changed, 139 insertions(+), 61 deletions(-) diff --git a/config.devnet.json b/config.devnet.json index bf038547..5e908bf6 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 ecb72180..8e10ac67 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 ca683538..fdd81e6e 100644 --- a/config.testnet.json +++ b/config.testnet.json @@ -12,8 +12,16 @@ "rippleIssuerAddress": "ra3oyRVfy4yD4NJPrVcewvDtisZ3FhkcYL", "xrplWebsocket": "wss://testnet.xrpl-labs.com/", "ledgerApp": "Bitcoin Test", - "dfnsAppID": "ap-7pvrc-mei7e-9u3pfedu9tidiq6p", - "dfnsBaseURL": "http://localhost:3000/dfns-proxy/", + "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 b555d2eb..4251a8b7 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": "file:../dlc-btc-lib", + "dlc-btc-lib": "2.4.19-dfns-beta", "dotenv": "^16.3.1", "ethers": "5.7.2", "formik": "^2.4.5", 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 index 95f8d20d..24ba22a4 100644 --- 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 @@ -7,7 +7,15 @@ interface DFNSModalErrorBoxProps { 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 index fd1c3218..33ff6264 100644 --- a/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-form.tsx @@ -2,17 +2,29 @@ import { Button, FormControl, FormErrorMessage, FormLabel, Input, VStack } from import { useForm } from '@tanstack/react-form'; interface DFNSModalLoginFormProps { - onSubmit: (username: string, organizationID: string) => Promise; + onSubmit: (email: string) => Promise; } +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 }: DFNSModalLoginFormProps): React.JSX.Element { const formAPI = useForm({ defaultValues: { - username: '', - organizationId: '', + email: '', }, onSubmit: async ({ value }) => { - await onSubmit(value.username, value.organizationId); + await onSubmit(value.email); }, }); @@ -27,43 +39,18 @@ export function DFNSModalLoginForm({ onSubmit }: DFNSModalLoginFormProps): React > { - if (!value) return 'Username is required'; - if (value.length < 3) return 'Username must be at least 3 characters'; - return undefined; - }, + onChange: ({ value }) => validateEmail(value), }} children={field => ( - Username + E-Mail Address field.handleChange(e.target.value)} - placeholder="john@doe.com" - /> - {field.state.meta.errorMap.onChange} - - )} - /> - - { - if (!value) return 'Organization ID is required'; - return undefined; - }, - }} - children={field => ( - - Organization ID - field.handleChange(e.target.value)} - placeholder="Enter organization ID" + placeholder="example@domain.com" /> {field.state.meta.errorMap.onChange} 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 index de06904f..f04e6c79 100644 --- a/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-layout.tsx @@ -33,7 +33,9 @@ export function DFNSModalLayout({ - {children} + + {children} + 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 index 61a7dc2b..d7fb2d38 100644 --- 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 @@ -14,11 +14,11 @@ export function DFNSModalNavigatorButton({ 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 username and organization ID'} + ? '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/hooks/use-dfns.ts b/src/app/hooks/use-dfns.ts index 77486e1e..9fc91798 100644 --- a/src/app/hooks/use-dfns.ts +++ b/src/app/hooks/use-dfns.ts @@ -19,8 +19,7 @@ const BITCOIN_NETWORK_DFNS_MAP = { interface UseDFNSReturnType { connectDFNSWallet: ( - userName: string, - organizationID: string + userName: string ) => Promise<{ address: string | undefined; walletID: string }[]>; registerCredentials: (code: string) => Promise; selectWallet: (walletId: string) => Promise; @@ -54,16 +53,26 @@ export function useDFNS(): UseDFNSReturnType { const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); async function connectDFNSWallet( - userName: string, - organizationID: string + userName: string ): Promise<{ address: string | undefined; walletID: string }[]> { try { setIsLoading([true, 'Connecting to DFNS Wallet']); + const { dfnsBaseURL } = appConfiguration.dfnsConfiguration; + const dfnsConfiguration = appConfiguration.dfnsConfiguration.dfnsCustomerConfigurations.find( + ({ name }) => name === 'Tungsten' + ); + + if (!dfnsConfiguration) { + throw new Error('DFNS Configuration not found'); + } + + const { applicationID, organizationID } = dfnsConfiguration; + const signer = new WebAuthnSigner(); const authApi = new DfnsAuthenticator({ - appId: appConfiguration.dfnsAppID, - baseUrl: appConfiguration.dfnsBaseURL, + appId: applicationID, + baseUrl: dfnsBaseURL, signer, }); @@ -73,8 +82,8 @@ export function useDFNS(): UseDFNSReturnType { }); const dfnsDLCHandler = new DFNSDLCHandler( - appConfiguration.dfnsAppID, - appConfiguration.dfnsBaseURL, + applicationID, + dfnsBaseURL, token, BITCOIN_NETWORK_MAP[appConfiguration.bitcoinNetwork], appConfiguration.bitcoinBlockchainURL, @@ -116,11 +125,22 @@ export function useDFNS(): UseDFNSReturnType { } async function registerCredentials(code: string): Promise { + const { dfnsBaseURL } = appConfiguration.dfnsConfiguration; + const dfnsConfiguration = appConfiguration.dfnsConfiguration.dfnsCustomerConfigurations.find( + ({ name }) => name === 'Tungsten' + ); + + if (!dfnsConfiguration) { + throw new Error('DFNS Configuration not found'); + } + + const { applicationID } = dfnsConfiguration; + const signer = new WebAuthnSigner(); const dfnsAPI = new DfnsApiClient({ - appId: appConfiguration.dfnsAppID, + appId: applicationID, authToken: undefined, - baseUrl: appConfiguration.dfnsBaseURL, + baseUrl: dfnsBaseURL, signer, }); diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index 847bf7c7..2ee26cb3 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -17,6 +17,17 @@ enum AppEnvironment { LOCALHOST = 'localhost', } +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,8 +53,7 @@ export interface Configuration { bitcoinBlockchainFeeEstimateURL: string; rippleIssuerAddress: string; ledgerApp: string; - dfnsAppID: string; - dfnsBaseURL: string; + dfnsConfiguration: DFNSConfiguration; merchants: Merchant[]; protocols: Protocol[]; } diff --git a/src/styles/css-styles.ts b/src/styles/css-styles.ts index 8a71de02..873e0d7f 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/yarn.lock b/yarn.lock index 349b240f..e2dbc0f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5220,8 +5220,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -"dlc-btc-lib@file:../dlc-btc-lib": - version "2.4.10" +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" From 1c807ea53bb64ee17ff928fa5c0e8bdbef371d2e Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 4 Dec 2024 16:46:07 +0100 Subject: [PATCH 39/39] feat: add organization dropdown to dfns modal --- .../dfns-modal-credentials-form.tsx | 18 ++++++-- .../dfns-modal/components/dfns-modal-form.tsx | 15 +++++-- .../components/dfns-modal-layout.tsx | 2 +- .../dfns-modal-select-organization-menu.tsx | 44 +++++++++++++++++++ .../modals/dfns-modal/dfns-modal.tsx | 41 ++++++++++++----- src/app/hooks/use-dfns.ts | 27 ++++++------ src/app/hooks/use-leather.ts | 9 +--- src/app/hooks/use-ledger.ts | 2 - src/app/store/slices/modal/modal.slice.ts | 2 +- src/shared/models/configuration.ts | 2 +- src/styles/menu-theme.ts | 40 +++++++++++++++++ 11 files changed, 157 insertions(+), 45 deletions(-) create mode 100644 src/app/components/modals/dfns-modal/components/dfns-modal-select-organization-menu.tsx 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 index ac3da033..20dbe82a 100644 --- 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 @@ -1,17 +1,25 @@ 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) => Promise; + onSubmit: ( + credentialCode: string, + selectedDFNSOrganization: DFNSCustomerConfiguration + ) => Promise; + selectedDFNSOrganization: DFNSCustomerConfiguration; } -export function DFNSModalRegisterForm({ onSubmit }: DFNSModalRegisterFormProps): React.JSX.Element { +export function DFNSModalRegisterForm({ + onSubmit, + selectedDFNSOrganization, +}: DFNSModalRegisterFormProps): React.JSX.Element { const formAPI = useForm({ defaultValues: { credentialCode: '', }, onSubmit: async ({ value }) => { - await onSubmit(value.credentialCode); + await onSubmit(value.credentialCode, selectedDFNSOrganization); }, }); @@ -36,7 +44,9 @@ export function DFNSModalRegisterForm({ onSubmit }: DFNSModalRegisterFormProps): }} children={field => ( - Credential Code + + Credential Code + Promise; + onSubmit: (email: string, selectedDFNSOrganization: DFNSCustomerConfiguration) => Promise; + selectedDFNSOrganization: DFNSCustomerConfiguration; } const validateEmail = (email: string) => { @@ -18,13 +20,16 @@ const validateEmail = (email: string) => { return undefined; }; -export function DFNSModalLoginForm({ onSubmit }: DFNSModalLoginFormProps): React.JSX.Element { +export function DFNSModalLoginForm({ + onSubmit, + selectedDFNSOrganization, +}: DFNSModalLoginFormProps): React.JSX.Element { const formAPI = useForm({ defaultValues: { email: '', }, onSubmit: async ({ value }) => { - await onSubmit(value.email); + await onSubmit(value.email, selectedDFNSOrganization); }, }); @@ -45,7 +50,9 @@ export function DFNSModalLoginForm({ onSubmit }: DFNSModalLoginFormProps): React }} children={field => ( - E-Mail Address + + E-Mail Address + - {'DFNS + {'DFNS 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 00000000..1f7b73b0 --- /dev/null +++ b/src/app/components/modals/dfns-modal/components/dfns-modal-select-organization-menu.tsx @@ -0,0 +1,44 @@ +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { HStack, Menu, MenuButton, MenuItem, MenuList, Text, VStack } from '@chakra-ui/react'; +import { DFNSCustomerConfiguration } from '@models/configuration'; + +interface DFNSModalSelectOrganizationMenuProps { + handleChangeOrganization: (dfnsOrganization: DFNSCustomerConfiguration) => void; + dfnsOrganizations: DFNSCustomerConfiguration[]; + selectedDFNSOrganization: DFNSCustomerConfiguration; +} + +export function DFNSModalSelectOrganizationMenu({ + handleChangeOrganization, + dfnsOrganizations, + selectedDFNSOrganization, +}: DFNSModalSelectOrganizationMenuProps): React.JSX.Element { + return ( + + + Organization + + + + + {selectedDFNSOrganization.name} + + + + + {dfnsOrganizations.map(dfnsOrganization => { + return ( + handleChangeOrganization(dfnsOrganization)} + > + {dfnsOrganization.name} + + ); + })} + + + + ); +} diff --git a/src/app/components/modals/dfns-modal/dfns-modal.tsx b/src/app/components/modals/dfns-modal/dfns-modal.tsx index 7e6d71de..6e02cad3 100644 --- a/src/app/components/modals/dfns-modal/dfns-modal.tsx +++ b/src/app/components/modals/dfns-modal/dfns-modal.tsx @@ -1,7 +1,8 @@ import { useContext, useState } from 'react'; -import { Collapse } from '@chakra-ui/react'; +import { Collapse, VStack } from '@chakra-ui/react'; import { useDFNS } from '@hooks/use-dfns'; +import { DFNSCustomerConfiguration } from '@models/configuration'; import { BitcoinWalletContext, BitcoinWalletContextState, @@ -16,6 +17,7 @@ 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 { @@ -26,6 +28,9 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J 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); @@ -45,9 +50,12 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J setDFNSError(undefined); } - async function connectDFNSWalletAndGetAddresses(username: string) { + async function connectDFNSWalletAndGetAddresses( + username: string, + organization: DFNSCustomerConfiguration + ) { try { - const taprootAddresses = await connectDFNSWallet(username); + const taprootAddresses = await connectDFNSWallet(username, organization); setTaprootAddresses(taprootAddresses); setIsLoadingAddressList(false); } catch (error: any) { @@ -55,9 +63,9 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J } } - async function handleRegisterCredentials(code: string) { + async function handleRegisterCredentials(code: string, organization: DFNSCustomerConfiguration) { try { - await registerCredentials(code); + await registerCredentials(code, organization); setIsRegister(false); } catch (error: any) { await setError(error.message); @@ -81,11 +89,24 @@ export function DFNSModal({ isOpen, handleClose }: ModalComponentProps): React.J - {!isRegister ? ( - - ) : ( - - )} + + + {!isRegister ? ( + + ) : ( + + )} + Promise<{ address: string | undefined; walletID: string }[]>; - registerCredentials: (code: string) => Promise; + registerCredentials: ( + code: string, + dfnsConfiguration: DFNSCustomerConfiguration + ) => Promise; selectWallet: (walletId: string) => Promise; handleFundingTransaction: ( dlcHandler: DFNSDLCHandler, @@ -53,19 +58,13 @@ export function useDFNS(): UseDFNSReturnType { const [isLoading, setIsLoading] = useState<[boolean, string]>([false, '']); async function connectDFNSWallet( - userName: string + userName: string, + dfnsConfiguration: DFNSCustomerConfiguration ): Promise<{ address: string | undefined; walletID: string }[]> { try { setIsLoading([true, 'Connecting to DFNS Wallet']); const { dfnsBaseURL } = appConfiguration.dfnsConfiguration; - const dfnsConfiguration = appConfiguration.dfnsConfiguration.dfnsCustomerConfigurations.find( - ({ name }) => name === 'Tungsten' - ); - - if (!dfnsConfiguration) { - throw new Error('DFNS Configuration not found'); - } const { applicationID, organizationID } = dfnsConfiguration; @@ -124,11 +123,11 @@ export function useDFNS(): UseDFNSReturnType { } } - async function registerCredentials(code: string): Promise { + async function registerCredentials( + code: string, + dfnsConfiguration: DFNSCustomerConfiguration + ): Promise { const { dfnsBaseURL } = appConfiguration.dfnsConfiguration; - const dfnsConfiguration = appConfiguration.dfnsConfiguration.dfnsCustomerConfigurations.find( - ({ name }) => name === 'Tungsten' - ); if (!dfnsConfiguration) { throw new Error('DFNS Configuration not found'); diff --git a/src/app/hooks/use-leather.ts b/src/app/hooks/use-leather.ts index 48838842..2c9e4ade 100644 --- a/src/app/hooks/use-leather.ts +++ b/src/app/hooks/use-leather.ts @@ -95,8 +95,6 @@ export function useLeather(): UseLeatherReturnType { const rpcResponse: RpcResponse = await window.btc?.request('getAddresses'); const userAddresses = rpcResponse.result.addresses; - console.log('userAddresses', userAddresses); - checkUserWalletNetwork(userAddresses[0]); const bitcoinAddresses = userAddresses.filter( @@ -250,15 +248,10 @@ export function useLeather(): UseLeatherReturnType { feeRateMultiplier ); - console.log('withdrawalTransaction', withdrawalTransaction); - setIsLoading([true, 'Sign Withdrawal Transaction in your Leather Wallet']); // ==> Sign Withdrawal PSBT with Ledger const withdrawalTransactionHex = await signPSBT(withdrawalTransaction.toPSBT()); - console.log( - 'withdrawalTransactionHex', - Transaction.fromPSBT(hexToBytes(withdrawalTransactionHex)) - ); + setIsLoading([false, '']); return withdrawalTransactionHex; } catch (error) { diff --git a/src/app/hooks/use-ledger.ts b/src/app/hooks/use-ledger.ts index 6619327a..4b8de688 100644 --- a/src/app/hooks/use-ledger.ts +++ b/src/app/hooks/use-ledger.ts @@ -330,8 +330,6 @@ export function useLedger(): UseLedgerReturnType { feeRateMultiplier ); - console.log('withdrawalPSBT', withdrawalPSBT); - setIsLoading([true, 'Sign Withdrawal Transaction in your Leather Wallet']); // ==> Sign Withdrawal PSBT with Ledger const withdrawalTransaction = await dlcHandler.signPSBT(withdrawalPSBT, 'withdraw'); diff --git a/src/app/store/slices/modal/modal.slice.ts b/src/app/store/slices/modal/modal.slice.ts index 03e94454..f6a15234 100644 --- a/src/app/store/slices/modal/modal.slice.ts +++ b/src/app/store/slices/modal/modal.slice.ts @@ -14,7 +14,7 @@ const initialModalState: ModalState = { isSuccesfulFlowModalOpen: [false, undefined, '', 'mint', 0], isSelectBitcoinWalletModalOpen: false, isLedgerModalOpen: false, - isDFNSModalOpen: false, + isDFNSModalOpen: true, }; export const modalSlice = createSlice({ diff --git a/src/shared/models/configuration.ts b/src/shared/models/configuration.ts index 2ee26cb3..fe77457b 100644 --- a/src/shared/models/configuration.ts +++ b/src/shared/models/configuration.ts @@ -17,7 +17,7 @@ enum AppEnvironment { LOCALHOST = 'localhost', } -interface DFNSCustomerConfiguration { +export interface DFNSCustomerConfiguration { name: string; organizationID: string; applicationID: string; diff --git a/src/styles/menu-theme.ts b/src/styles/menu-theme.ts index f76b0c82..ea2cf2cd 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 });