From 7a2577be3debf6ea2c8f5e79938b2b9e38b810c0 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 10 Oct 2024 18:09:11 +0200 Subject: [PATCH] feat: custom seed enabled --- src/app/app.tsx | 53 +++---- src/app/components/account/account.tsx | 43 +++--- .../account/components/account-menu.tsx | 14 +- .../burn-transaction-screen.tsx | 11 +- .../setup-vault-screen/setup-vault-screen.tsx | 8 +- .../components/select-wallet-menu.tsx | 92 ++++++------ .../select-wallet-modal.tsx | 140 +++++++++++++----- .../select-network-button.tsx | 82 +++++----- src/app/hooks/use-nfts.ts | 17 ++- .../ripple-user-wallet-context-provider.tsx | 73 +++++++++ 10 files changed, 353 insertions(+), 180 deletions(-) create mode 100644 src/app/providers/ripple-user-wallet-context-provider.tsx diff --git a/src/app/app.tsx b/src/app/app.tsx index d2f0d892..5b329940 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -14,6 +14,7 @@ import { BitcoinWalletContextProvider } from '@providers/bitcoin-wallet-context- import { EthereumNetworkConfigurationContextProvider } from '@providers/ethereum-network-configuration.provider'; import { EthereumObserverProvider } from '@providers/ethereum-observer-provider'; import { ProofOfReserveContextProvider } from '@providers/proof-of-reserve-context-provider'; +import { RippleWalletContextProvider } from '@providers/ripple-user-wallet-context-provider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WagmiProvider } from 'wagmi'; @@ -29,31 +30,33 @@ export function App(): React.JSX.Element { - - - - - - - - } /> - } /> - {/* } /> */} - } /> - } /> - } - /> - } /> - } /> - - - - - - - + + + + + + + + + } /> + } /> + {/* } /> */} + } /> + } /> + } + /> + } /> + } /> + + + + + + + + diff --git a/src/app/components/account/account.tsx b/src/app/components/account/account.tsx index f3c57694..2d5539b9 100644 --- a/src/app/components/account/account.tsx +++ b/src/app/components/account/account.tsx @@ -1,39 +1,44 @@ +import { useContext } from 'react'; import { useDispatch } from 'react-redux'; -import { HStack } from '@chakra-ui/react'; +import { Button, HStack } from '@chakra-ui/react'; import { AccountMenu } from '@components/account/components/account-menu'; +import { RippleWalletContext } from '@providers/ripple-user-wallet-context-provider'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; +import { modalActions } from '@store/slices/modal/modal.actions'; + // import { modalActions } from '@store/slices/modal/modal.actions'; -import { useAccount, useDisconnect } from 'wagmi'; +// import { useAccount, useDisconnect } from 'wagmi'; export function Account(): React.JSX.Element { const dispatch = useDispatch(); - const { address, connector } = useAccount(); - const { disconnect } = useDisconnect(); + // const { address, connector } = useAccount(); + // const { disconnect } = useDisconnect(); + const { isRippleWalletInitialized, resetRippleWallet } = useContext(RippleWalletContext); - // function onConnectWalletClick(): void { - // dispatch(modalActions.toggleSelectWalletModalVisibility()); - // } + function onConnectWalletClick(): void { + dispatch(modalActions.toggleSelectWalletModalVisibility()); + } function onDisconnectWalletClick(): void { - disconnect(); + resetRippleWallet(); dispatch(mintUnmintActions.resetMintUnmintState()); } return ( - {/* {isConnected ? ( */} - onDisconnectWalletClick()} - /> - {/* ) : ( */} - {/* */} - {/* )} */} + {isRippleWalletInitialized ? ( + onDisconnectWalletClick()} + /> + ) : ( + + )} ); } diff --git a/src/app/components/account/components/account-menu.tsx b/src/app/components/account/components/account-menu.tsx index ab526d82..8feedd32 100644 --- a/src/app/components/account/components/account-menu.tsx +++ b/src/app/components/account/components/account-menu.tsx @@ -1,11 +1,15 @@ +import { useContext } from 'react'; + import { ChevronDownIcon } from '@chakra-ui/icons'; import { HStack, Image, Menu, MenuButton, MenuItem, MenuList, Stack, Text } from '@chakra-ui/react'; +import { RippleWalletContext } from '@providers/ripple-user-wallet-context-provider'; import { truncateAddress } from 'dlc-btc-lib/utilities'; -import { Connector } from 'wagmi'; + +// import { Connector } from 'wagmi'; interface AccountMenuProps { - address?: string; - wagmiConnector?: Connector; + // address?: string; + // wagmiConnector?: Connector; handleDisconnectWallet: () => void; } @@ -14,7 +18,7 @@ export function AccountMenu({ // wagmiConnector, handleDisconnectWallet, }: AccountMenuProps): React.JSX.Element | false { - // if (!address || !wagmiConnector) return false; + const { rippleWallet } = useContext(RippleWalletContext); return ( @@ -22,7 +26,7 @@ export function AccountMenu({ {'xrpl'} - {truncateAddress('rfvtbrXSxLsxVWDktR4sdzjJgv8EnMKFKG')} + {truncateAddress(rippleWallet?.classicAddress!)} 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 099e8034..cca5d3eb 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 @@ -6,10 +6,11 @@ import { VaultTransactionForm } from '@components/transaction-screen/transaction import { Vault } from '@components/vault/vault'; import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; +import { RippleWalletContext } from '@providers/ripple-user-wallet-context-provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { Client } from 'dlc-btc-lib/models'; -import { createCheck, getRippleClient, getRippleWallet } from 'dlc-btc-lib/ripple-functions'; +import { createCheck, getRippleClient } from 'dlc-btc-lib/ripple-functions'; import { shiftValue } from 'dlc-btc-lib/utilities'; interface BurnTokenTransactionFormProps { @@ -35,6 +36,8 @@ export function BurnTokenTransactionForm({ const { unmintStep } = useSelector((state: RootState) => state.mintunmint); const [isSubmitting, setIsSubmitting] = useState(false); + const { rippleWallet } = useContext(RippleWalletContext); + const currentVault = unmintStep[2]; async function handleButtonClick(withdrawAmount: number): Promise { @@ -45,12 +48,14 @@ export function BurnTokenTransactionForm({ // if (currentRisk === 'High') throw new Error('Risk Level is too high'); const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - const rippleWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); const issuerAddress = 'ra9epzthPkNXykgfadCwu8D7mtajj8DVCP'; const rippleClient: Client = getRippleClient('wss://s.altnet.rippletest.net:51233'); + if (!rippleClient.isConnected()) { + await rippleClient.connect(); + } await createCheck( rippleClient, - rippleWallet, + rippleWallet!, issuerAddress, undefined, formattedWithdrawAmount.toString(), 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 d9cb3984..1b881065 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,8 +1,8 @@ -import { useState } from 'react'; +import { useContext, useState } from 'react'; import { Button, VStack, useToast } from '@chakra-ui/react'; import { setupXRPLVault } from '@functions/fetch.functions'; -import { getRippleWallet } from 'dlc-btc-lib/ripple-functions'; +import { RippleWalletContext } from '@providers/ripple-user-wallet-context-provider'; import { SetupVaultScreenVaultGraphics } from './components/setup-vault-screen.vault-graphics'; @@ -10,12 +10,12 @@ export function SetupVaultScreen(): React.JSX.Element { const toast = useToast(); const [isSubmitting, setIsSubmitting] = useState(false); + const { rippleWallet } = useContext(RippleWalletContext); async function handleSetup() { try { setIsSubmitting(true); - const xrplWallet = getRippleWallet('sEdSKUhR1Hhwomo7CsUzAe2pv7nqUXT'); - await setupXRPLVault(xrplWallet.classicAddress); + await setupXRPLVault(rippleWallet?.classicAddress!); } 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-wallet-menu.tsx index feb3c575..d1958819 100644 --- a/src/app/components/modals/select-wallet-modal/components/select-wallet-menu.tsx +++ b/src/app/components/modals/select-wallet-modal/components/select-wallet-menu.tsx @@ -1,50 +1,50 @@ -import { Box, Button, HStack, Image, Spinner, Text } from '@chakra-ui/react'; -import { Connector } from 'wagmi'; +// import { Box, Button, HStack, Image, Spinner, Text } from '@chakra-ui/react'; +// import { Connector } from 'wagmi'; -interface SelectWalletMenuProps { - wagmiConnector: Connector; - selectedWagmiConnectorID?: string; - isConnectWalletPending: boolean; - isConnectWalletSuccess: boolean; - handleConnectWallet: (wagmiConnector: Connector) => void; -} +// interface SelectWalletMenuProps { +// wagmiConnector: Connector; +// selectedWagmiConnectorID?: string; +// isConnectWalletPending: boolean; +// isConnectWalletSuccess: boolean; +// handleConnectWallet: (wagmiConnector: Connector) => void; +// } -export function SelectWalletMenu({ - wagmiConnector, - selectedWagmiConnectorID, - isConnectWalletPending, - isConnectWalletSuccess, - handleConnectWallet, -}: SelectWalletMenuProps): React.JSX.Element { - const { id, icon, name } = wagmiConnector; +// export function SelectWalletMenu({ +// wagmiConnector, +// selectedWagmiConnectorID, +// isConnectWalletPending, +// isConnectWalletSuccess, +// handleConnectWallet, +// }: SelectWalletMenuProps): React.JSX.Element { +// const { id, icon, name } = wagmiConnector; - const isThisWalletSelected = selectedWagmiConnectorID === id; +// const isThisWalletSelected = selectedWagmiConnectorID === id; - return ( - - ); -} +// 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 ea08d08d..d15d01e3 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,56 +1,126 @@ -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 { Button, HStack, Input, 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 { delay } from 'dlc-btc-lib/utilities'; -import { Chain } from 'viem'; -import { Connector, useConfig, useConnect } from 'wagmi'; +import { RippleWalletContext } from '@providers/ripple-user-wallet-context-provider'; +import { useForm } from '@tanstack/react-form'; + +function validateXRPLSeed(userSeed: string): string | undefined { + if (userSeed.length !== 31) { + return 'Please enter a valid XRPL address'; + } +} export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): React.JSX.Element { - const { connect, isPending, isSuccess, connectors } = useConnect(); - const { chains } = useConfig(); + // const { connect, isPending, isSuccess, connectors } = useConnect(); - const [selectedEthereumNetwork, setSelectedEthereumNetwork] = useState( - undefined - ); - const [selectedWagmiConnectorID, setSelectedWagmiConnectorID] = useState( - undefined - ); + // const { chains } = useConfig(); + + const [isSubmitting, setIsSubmitting] = useState(false); + + const { setRippleWallet, isRippleWalletInitialized } = useContext(RippleWalletContext); + + // const [selectedEthereumNetwork, setSelectedEthereumNetwork] = useState( + // undefined + // ); + // const [selectedWagmiConnectorID, setSelectedWagmiConnectorID] = useState( + // undefined + // ); useEffect(() => { - if (isSuccess) { + if (isRippleWalletInitialized) { + console.log('isRippleWalletInitialized', isRippleWalletInitialized); void handleCloseAfterSuccess(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSuccess]); + }, [isRippleWalletInitialized]); async function handleCloseAfterSuccess() { - await delay(1000); - setSelectedEthereumNetwork(undefined); - setSelectedWagmiConnectorID(undefined); - if (selectedWagmiConnectorID && selectedWagmiConnectorID !== 'walletConnect') handleClose(); + handleClose(); } - async function handleConnectWallet(wagmiConnector: Connector) { - setSelectedWagmiConnectorID(wagmiConnector.id); - connect({ chainId: selectedEthereumNetwork?.id, connector: wagmiConnector }); - if (wagmiConnector.id === 'walletConnect') { - handleClose(); - } + // async function handleConnectWallet(wagmiConnector: Connector) { + // setSelectedWagmiConnectorID(wagmiConnector.id); + // connect({ chainId: selectedEthereumNetwork?.id, connector: wagmiConnector }); + // if (wagmiConnector.id === 'walletConnect') { + // handleClose(); + // } + // } + + // const handleChangeNetwork = (ethereumNetwork: Chain) => { + // setSelectedEthereumNetwork(ethereumNetwork); + // }; + + async function handleButtonClick(userSeed: string) { + setIsSubmitting(true); + console.log('setting it to true'); + await setRippleWallet(userSeed); + setIsSubmitting(false); } - const handleChangeNetwork = (ethereumNetwork: Chain) => { - setSelectedEthereumNetwork(ethereumNetwork); - }; + useEffect(() => { + console.log('isSubmitting', isSubmitting); + }, [isSubmitting]); + + const form = useForm({ + defaultValues: { + userSeed: '', + }, + onSubmit: async ({ value }) => { + await handleButtonClick(value.userSeed); + }, + validators: { + onChange: ({ value }) => { + return { + fields: { + userSeed: validateXRPLSeed(value.userSeed), + }, + }; + }, + }, + }); return ( handleClose()}> - {!selectedEthereumNetwork ? ( + Enter XRPL Seed +
{ + e.preventDefault(); + e.stopPropagation(); + await form.handleSubmit(); + }} + > + + {field => ( + + field.handleChange(e.target.value)} + /> + [state.canSubmit]} + children={([canSubmit]) => ( + + )} + /> + + )} + +
+ {/* {!selectedEthereumNetwork ? ( Select Network ) : ( @@ -86,9 +156,9 @@ export function SelectWalletModal({ isOpen, handleClose }: ModalComponentProps): isConnectWalletSuccess={isSuccess} handleConnectWallet={handleConnectWallet} /> - ))} -
- + ))} */} + {/* */} + {/* */}
); 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..cdc84071 100644 --- a/src/app/components/select-network-button/select-network-button.tsx +++ b/src/app/components/select-network-button/select-network-button.tsx @@ -1,43 +1,43 @@ -import { ChevronDownIcon } from '@chakra-ui/icons'; -import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; -import { Chain } from 'viem'; +// import { ChevronDownIcon } from '@chakra-ui/icons'; +// import { HStack, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; +// import { Chain } from 'viem'; -interface SelectNetworkButtonProps { - handleChangeNetwork: (ethereumNetwork: Chain) => void; - ethereumNetworks: readonly [Chain, ...Chain[]]; - selectedEthereumNetwork?: Chain; -} +// interface SelectNetworkButtonProps { +// handleChangeNetwork: (ethereumNetwork: Chain) => void; +// ethereumNetworks: readonly [Chain, ...Chain[]]; +// selectedEthereumNetwork?: Chain; +// } -export function SelectNetworkButton({ - handleChangeNetwork, - ethereumNetworks, - selectedEthereumNetwork, -}: SelectNetworkButtonProps): React.JSX.Element { - return ( - - - - {selectedEthereumNetwork ? ( - {selectedEthereumNetwork.name} - ) : ( - {'SELECT NETWORK'} - )} - - - - - {ethereumNetworks.map(ethereumNetwork => { - return ( - handleChangeNetwork(ethereumNetwork)} - > - {ethereumNetwork.name} - - ); - })} - - - ); -} +// export function SelectNetworkButton({ +// handleChangeNetwork, +// ethereumNetworks, +// selectedEthereumNetwork, +// }: SelectNetworkButtonProps): React.JSX.Element { +// return ( +// +// +// +// {selectedEthereumNetwork ? ( +// {selectedEthereumNetwork.name} +// ) : ( +// {'SELECT NETWORK'} +// )} +// +// +// +// +// {ethereumNetworks.map(ethereumNetwork => { +// return ( +// handleChangeNetwork(ethereumNetwork)} +// > +// {ethereumNetwork.name} +// +// ); +// })} +// +// +// ); +// } diff --git a/src/app/hooks/use-nfts.ts b/src/app/hooks/use-nfts.ts index 0d3cc746..3b856542 100644 --- a/src/app/hooks/use-nfts.ts +++ b/src/app/hooks/use-nfts.ts @@ -1,8 +1,9 @@ -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 { RippleWalletContext } from '@providers/ripple-user-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'; @@ -25,6 +26,8 @@ export function useNFTs(): useNFTsReturnType { const dispatch = useDispatch(); const rippleClient: Client = getRippleClient('wss://s.altnet.rippletest.net:51233'); + const { rippleWallet, isRippleWalletInitialized } = useContext(RippleWalletContext); + const [dispatchTuple, setDispatchTuple] = useState< [ string, @@ -47,9 +50,18 @@ export function useNFTs(): useNFTsReturnType { const previousVaults: Vault[] | undefined = queryClient.getQueryData(['xrpl-vaults']); try { + if (!rippleClient.isConnected()) { + await rippleClient.connect(); + } const issuerAddress = 'ra9epzthPkNXykgfadCwu8D7mtajj8DVCP'; xrplRawVaults = await getAllRippleVaults(rippleClient, issuerAddress); + console.log(rippleWallet?.classicAddress); + console.log('XRPL Vaults', xrplRawVaults); + xrplRawVaults = xrplRawVaults.filter( + vault => vault.creator === rippleWallet?.classicAddress.toLowerCase() + ); + console.log('filtered XRPL Vaults', xrplRawVaults); } catch (error) { console.error('Error fetching XRPL Vaults', error); return previousVaults ?? []; @@ -140,10 +152,11 @@ export function useNFTs(): useNFTsReturnType { } const { data: vaults } = useQuery({ - queryKey: ['xrpl-vaults'], + queryKey: ['xrpl-vaults', rippleWallet?.classicAddress], initialData: [], queryFn: fetchXRPLVaults, refetchInterval: 10000, + enabled: isRippleWalletInitialized, }); function dispatchVaultAction() { diff --git a/src/app/providers/ripple-user-wallet-context-provider.tsx b/src/app/providers/ripple-user-wallet-context-provider.tsx new file mode 100644 index 00000000..b248d839 --- /dev/null +++ b/src/app/providers/ripple-user-wallet-context-provider.tsx @@ -0,0 +1,73 @@ +import { createContext, useState } from 'react'; + +import { HasChildren } from '@models/has-children'; +import { getRippleWallet } from 'dlc-btc-lib/ripple-functions'; +import { Client, TransactionMetadataBase, TrustSet, Wallet } from 'xrpl'; + +interface RippleWalletContextType { + isRippleWalletInitialized: boolean; + rippleWallet: Wallet | undefined; + setRippleWallet: (userSeed: string) => Promise; + resetRippleWallet: () => void; +} + +export const RippleWalletContext = createContext({ + isRippleWalletInitialized: false, + rippleWallet: undefined, + setRippleWallet: async () => { + throw new Error('Not Implemented'); + }, + resetRippleWallet: () => { + throw new Error('Not Implemented'); + }, +}); + +export function RippleWalletContextProvider({ children }: HasChildren): React.JSX.Element { + const [isRippleWalletInitialized, setIsRippleWalletInitialized] = useState(false); + const [rippleWallet, setRippleWallet] = useState(undefined); + + async function initializeRippleWallet(userSeed: string) { + const rippleWallet = getRippleWallet(userSeed); + const client = new Client('wss://s.altnet.rippletest.net:51233'); + if (!client.isConnected()) { + await client.connect(); + } + + const trust_set_tx: TrustSet = { + TransactionType: 'TrustSet', + Account: rippleWallet.address, + LimitAmount: { + currency: 'DLC', + issuer: 'ra9epzthPkNXykgfadCwu8D7mtajj8DVCP', + value: '10000000000', // Large limit, arbitrarily chosen + }, + }; + const ts_prepared = await client.autofill(trust_set_tx); + const ts_signed = rippleWallet.sign(ts_prepared); + const ts_result = await client.submitAndWait(ts_signed.tx_blob); + const ts_result_meta = ts_result.result.meta as TransactionMetadataBase; + if (ts_result_meta.TransactionResult !== 'tesSUCCESS') { + throw `Error sending transaction: ${ts_result_meta.TransactionResult}`; + } + setRippleWallet(rippleWallet); + setIsRippleWalletInitialized(true); + } + + function resetRippleWallet() { + setRippleWallet(undefined); + setIsRippleWalletInitialized(false); + } + + return ( + + {children} + + ); +}