diff --git a/sdk/typescript/docs/components/context/wallet.tsx b/sdk/typescript/docs/components/context/wallet.tsx new file mode 100644 index 0000000000..0e63f70905 --- /dev/null +++ b/sdk/typescript/docs/components/context/wallet.tsx @@ -0,0 +1,171 @@ +import * as React from 'react'; +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { Coin, GasPrice } from '@cosmjs/stargate'; +import { settings } from '../client'; + +const signerAccount = async (mnemonic: string) => { + // create a wallet to sign transactions with the mnemonic + const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { + prefix: 'n', + }); + + return signer; +}; + +const fetchSignerCosmosWasmClient = async (mnemonic: string) => { + const signer = await signerAccount(mnemonic); + + // create a signing client we don't need to set the gas price conversion for queries + const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, { + gasPrice: GasPrice.fromString('0.025unym'), + }); + + return cosmWasmClient; +}; + +const fetchSignerClient = async (mnemonic) => { + const signer = await signerAccount(mnemonic); + + // create a signing client we don't need to set the gas price conversion for queries + // if you want to connect without signer you'd write ".connect" and "url" as param + const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, { + gasPrice: GasPrice.fromString('0.025unym'), + }); + + /** create a mixnet contract client + * @param cosmWasmClient the client to use for signing and querying + * @param settings.address the bech32 address prefix (human readable part) + * @param settings.mixnetContractAddress the bech32 address prefix (human readable part) + * @returns the client in MixnetClient form + */ + + const mixnetClient = new contracts.Mixnet.MixnetClient( + cosmWasmClient, + settings.address, // sender (that account of the signer) + settings.mixnetContractAddress, // contract address (different on mainnet, QA, etc) + ); + + return mixnetClient; +}; + +interface ApiState { + isLoading: boolean; + data?: RESPONSE; + error?: Error; +} + +/** + * This context provides the state for wallet. + */ + +interface WalletState { + cosmWasmSigner?: { getAccounts: () => void }; + cosmWasmSignerClient?: { getBalance: (account: string, denom: string) => Coin }; + nymWasmSignerClient?: ApiState; + accountLoading: boolean; + account: string; + clientsAreLoading: boolean; + setConnectWithMnemonic?: (value: string) => void; + balance?: Coin; + balanceLoading: boolean; +} + +export const WalletContext = React.createContext({ + accountLoading: false, + account: '', + clientsAreLoading: false, + balanceLoading: false, +}); + +export const useWalletContext = (): React.ContextType => + React.useContext(WalletContext); + +export const WalletContextProvider = ({ children }: { children: JSX.Element }) => { + // wallet mnemonic + const [connectWithMnemonic, setConnectWithMnemonic] = React.useState(); + const [accountLoading, setAccountLoading] = React.useState(false); + const [account, setAccount] = React.useState(); + const [clientsAreLoading, setClientsAreLoading] = React.useState(false); + const [cosmWasmSignerClient, setCosmWasmSignerClient] = React.useState(); + const [nymWasmSignerClient, setNymWasmSignerClient] = React.useState(); + const [balance, setBalance] = React.useState(); + const [balanceLoading, setBalanceLoading] = React.useState(false); + + const getSignerAccount = async () => { + console.log('getSignerAccount'); + setAccountLoading(true); + try { + const signer = await signerAccount(connectWithMnemonic); + const accounts = await signer.getAccounts(); + if (accounts[0]) { + setAccount(accounts[0].address); + } + } catch (error) { + console.error(error); + } + setAccountLoading(false); + }; + const getClients = async () => { + setClientsAreLoading(true); + try { + setCosmWasmSignerClient(await fetchSignerCosmosWasmClient(connectWithMnemonic)); + setNymWasmSignerClient(await fetchSignerClient(connectWithMnemonic)); + } catch (error) { + console.error(error); + } + setClientsAreLoading(false); + }; + + const getBalance = React.useCallback(async () => { + setBalanceLoading(true); + try { + const newBalance = await cosmWasmSignerClient?.getBalance(account, 'unym'); + setBalance(newBalance); + } catch (error) { + console.error(error); + } + setBalanceLoading(false); + }, [account, cosmWasmSignerClient]); + + React.useEffect(() => { + if (connectWithMnemonic) { + // when the mnemonic changes, remove all previous data + Promise.all([getSignerAccount(), getClients()]); + } + }, [connectWithMnemonic]); + + React.useEffect(() => { + if (account && cosmWasmSignerClient) { + if (!balance) { + getBalance(); + } + } + }, [account, cosmWasmSignerClient, balance, getBalance]); + + const state = React.useMemo( + () => ({ + accountLoading, + account, + clientsAreLoading, + cosmWasmSignerClient, + nymWasmSignerClient, + setConnectWithMnemonic, + balance, + balanceLoading, + }), + [ + accountLoading, + account, + clientsAreLoading, + cosmWasmSignerClient, + nymWasmSignerClient, + setConnectWithMnemonic, + balance, + balanceLoading, + ], + ); + + return {children}; +}; diff --git a/sdk/typescript/docs/components/wallet.tsx b/sdk/typescript/docs/components/wallet.tsx index 3b8be9b787..4ec2d7cb09 100644 --- a/sdk/typescript/docs/components/wallet.tsx +++ b/sdk/typescript/docs/components/wallet.tsx @@ -1,283 +1,45 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { contracts } from '@nymproject/contract-clients'; -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; -import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; -import { Coin, GasPrice } from '@cosmjs/stargate'; +import React, { useCallback, useEffect, useState, createContext } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { settings } from './client'; import { ConnectWallet } from './wallet/connect'; import { SendTokes } from './wallet/sendTokens'; import { Delegations } from './wallet/delegations'; +import { WalletContextProvider } from './context/wallet'; -const signerAccount = async (mnemonic) => { - // create a wallet to sign transactions with the mnemonic - const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { - prefix: 'n', - }); - - return signer; -}; - -const fetchSignerCosmosWasmClient = async (mnemonic: string) => { - const signer = await signerAccount(mnemonic); - - // create a signing client we don't need to set the gas price conversion for queries - const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, { - gasPrice: GasPrice.fromString('0.025unym'), - }); - - return cosmWasmClient; -}; - -const fetchSignerClient = async (mnemonic) => { - const signer = await signerAccount(mnemonic); - - // create a signing client we don't need to set the gas price conversion for queries - // if you want to connect without signer you'd write ".connect" and "url" as param - const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, { - gasPrice: GasPrice.fromString('0.025unym'), - }); - - /** create a mixnet contract client - * @param cosmWasmClient the client to use for signing and querying - * @param settings.address the bech32 address prefix (human readable part) - * @param settings.mixnetContractAddress the bech32 address prefix (human readable part) - * @returns the client in MixnetClient form - */ - - const mixnetClient = new contracts.Mixnet.MixnetClient( - cosmWasmClient, - settings.address, // sender (that account of the signer) - settings.mixnetContractAddress, // contract address (different on mainnet, QA, etc) - ); - - return mixnetClient; -}; export const Wallet = ({ type }: { type: 'connect' | 'sendTokens' | 'delegations' }) => { - const [mnemonic, setMnemonic] = useState(); const [signerCosmosWasmClient, setSignerCosmosWasmClient] = useState(); const [signerClient, setSignerClient] = useState(); const [account, setAccount] = useState(); - const [accountLoading, setAccountLoading] = useState(false); - const [clientLoading, setClientLoading] = useState(false); - const [balance, setBalance] = useState(); - const [balanceLoading, setBalanceLoading] = useState(false); - const [log, setLog] = useState([]); - const [sendingTokensLoader, setSendingTokensLoader] = useState(false); - const [delegations, setDelegations] = useState(); - const [recipientAddress, setRecipientAddress] = useState(''); - const [delegationLoader, setDelegationLoader] = useState(false); - const [undeledationLoader, setUndeledationLoader] = useState(false); - const [withdrawLoading, setWithdrawLoading] = useState(false); - const [connectButtonText, setConnectButtonText] = useState('Connect'); - - const getBalance = useCallback(async () => { - setBalanceLoading(true); - try { - const newBalance = await signerCosmosWasmClient?.getBalance(account, 'unym'); - setBalance(newBalance); - } catch (error) { - console.error(error); - } - setBalanceLoading(false); - }, [account, signerCosmosWasmClient]); - - const getSignerAccount = async () => { - setAccountLoading(true); - try { - const signer = await signerAccount(mnemonic); - const accounts = await signer.getAccounts(); - if (accounts[0]) { - setAccount(accounts[0].address); - } - } catch (error) { - console.error(error); - } - setAccountLoading(false); - }; - - const getClients = async () => { - setClientLoading(true); - try { - setSignerCosmosWasmClient(await fetchSignerCosmosWasmClient(mnemonic)); - setSignerClient(await fetchSignerClient(mnemonic)); - } catch (error) { - console.error(error); - } - setClientLoading(false); - }; - - const getDelegations = useCallback(async () => { - const newDelegations = await signerClient.getDelegatorDelegations({ - delegator: settings.address, - }); - setDelegations(newDelegations); - }, [signerClient]); - - const connect = () => { - getSignerAccount(); - getClients(); - }; - - // Start Undelgate All - const doUndelegateAll = async () => { - if (!signerClient) { - return; - } - setUndeledationLoader(true); - try { - // eslint-disable-next-line no-restricted-syntax - for (const delegation of delegations.delegations) { - // eslint-disable-next-line no-await-in-loop - await signerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto'); - } - } catch (error) { - console.error(error); - } - setUndeledationLoader(false); - }; - // End Undelgate All - - // Start Delegate - const doDelegate = async ({ mixId, amount }: { mixId: number; amount: number }) => { - if (!signerClient) { - return; - } - setDelegationLoader(true); - try { - const res = await signerClient.delegateToMixnode({ mixId }, 'auto', undefined, [ - { amount: `${amount}`, denom: 'unym' }, - ]); - console.log('res', res); - setLog((prev) => [ - ...prev, -
- {new Date().toLocaleTimeString()} -
{JSON.stringify(res, null, 2)}
-
, - ]); - } catch (error) { - console.error(error); - } - setDelegationLoader(false); - }; - // End delegate - - // Sending tokens - const doSendTokens = async (amount: string) => { - const memo = 'test sending tokens'; - setSendingTokensLoader(true); - try { - const res = await signerCosmosWasmClient.sendTokens( - account, - recipientAddress, - [{ amount, denom: 'unym' }], - 'auto', - memo, - ); - setLog((prev) => [ - ...prev, -
- {new Date().toLocaleTimeString()} -
{JSON.stringify(res, null, 2)}
-
, - ]); - } catch (error) { - console.error(error); - } - setSendingTokensLoader(false); - }; - // End send tokens - - // Start Withdraw Rewards - const doWithdrawRewards = async () => { - const delegatorAddress = ''; - const validatorAdress = ''; - const memo = 'test sending tokens'; - setWithdrawLoading(true); - try { - const res = await signerCosmosWasmClient.withdrawRewards(delegatorAddress, validatorAdress, 'auto', memo); - setLog((prev) => [ - ...prev, -
- {new Date().toLocaleTimeString()} -
{JSON.stringify(res, null, 2)}
-
, - ]); - } catch (error) { - console.error(error); - } - setWithdrawLoading(false); - }; - // End Withdraw Rewards - - useEffect(() => { - if (account && signerCosmosWasmClient) { - if (!balance) { - setBalanceLoading(true); - getBalance(); - setBalanceLoading(false); - } - } - }, [account, signerCosmosWasmClient, balance, getBalance]); - - useEffect(() => { - if (signerClient && !delegations) { - console.log('getDelegations'); - getDelegations(); - } - }, [signerClient, getDelegations, delegations]); - - useEffect(() => { - if (accountLoading || clientLoading || balanceLoading) { - setConnectButtonText('Loading...'); - } else if (balance) { - setConnectButtonText('Connected'); - } - setConnectButtonText('Connect'); - }, [accountLoading, clientLoading, balanceLoading]); - return ( - - {type === 'connect' && ( - - )} - {type === 'sendTokens' && ( - + + + {type === 'connect' && ( + + )} + {/* {type === 'sendTokens' && ( + + + )} {type === 'delegations' && ( - - )} - {log.length > 0 && ( + + + + )} */} + {/* {log.length > 0 && ( Transaction Logs: {log} - )} - + )} */} + + ); }; diff --git a/sdk/typescript/docs/components/wallet/connect.tsx b/sdk/typescript/docs/components/wallet/connect.tsx index ec67bf0225..bfbc5d60c3 100644 --- a/sdk/typescript/docs/components/wallet/connect.tsx +++ b/sdk/typescript/docs/components/wallet/connect.tsx @@ -1,74 +1,69 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Coin } from '@cosmjs/stargate'; import Button from '@mui/material/Button'; import Paper from '@mui/material/Paper'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import TextField from '@mui/material/TextField'; +import { useWalletContext } from '../context/wallet'; -export const ConnectWallet = ({ - setMnemonic, - connect, - mnemonic, - accountLoading, - clientLoading, - balanceLoading, - account, - balance, - connectButtonText, - }: { - setMnemonic: (value: string) => void; - connect: () => void; - mnemonic: string; - accountLoading: boolean; - clientLoading: boolean; - balanceLoading: boolean; - account: string; - balance: Coin; - connectButtonText: string; - }) => { - return ( - - - Connect to your testnet account - - - Your testnet account: - - - Enter the mnemonic - - setMnemonic(e.target.value)} - fullWidth - multiline - maxRows={4} - sx={{ marginBottom: 3 }} - /> - - - {account && balance ? ( - - Address: {account} - - Balance: {balance?.amount} {balance?.denom} - - - ) : ( - - Please, enter your mnemonic to receive your account information - - )} +export const ConnectWallet = () => { + const { balance, balanceLoading, accountLoading, setConnectWithMnemonic, account, clientsAreLoading } = + useWalletContext(); + + const [mnemonic, setMnemonic] = useState(); + const [connectButtonText, setConnectButtonText] = useState('Connect'); + + useEffect(() => { + if (accountLoading || clientsAreLoading || balanceLoading) { + setConnectButtonText('Loading...'); + } else if (balance) { + setConnectButtonText('Connected'); + } + setConnectButtonText('Connect'); + }, [accountLoading, clientsAreLoading, balanceLoading]); + + return ( + + + Connect to your testnet account + + + Your testnet account: + + + Enter the mnemonic + + setMnemonic(e.target.value)} + fullWidth + multiline + maxRows={4} + sx={{ marginBottom: 3 }} + /> + - - ); - }; - \ No newline at end of file + {account && balance ? ( + + Address: {account} + + Balance: {balance?.amount} {balance?.denom} + + + ) : ( + + Please, enter your mnemonic to receive your account information + + )} + + + ); +}; diff --git a/sdk/typescript/docs/components/wallet/delegations.tsx b/sdk/typescript/docs/components/wallet/delegations.tsx index d0198a32cd..fd22b03b80 100644 --- a/sdk/typescript/docs/components/wallet/delegations.tsx +++ b/sdk/typescript/docs/components/wallet/delegations.tsx @@ -5,104 +5,178 @@ import Box from '@mui/material/Box'; import { TableBody, TableCell, TableHead, TableRow, TextField, Typography } from '@mui/material'; import Table from '@mui/material/Table'; -export const Delegations = ({ - delegations, - doDelegate, - delegationLoader, - doUndelegateAll, - undeledationLoader, - doWithdrawRewards, - withdrawLoading, -}: { - delegations: any; - doDelegate: ({ mixId, amount }: { mixId: number; amount: number }) => void; - delegationLoader: boolean; - doUndelegateAll: () => void; - undeledationLoader: boolean; - doWithdrawRewards: () => void; - withdrawLoading: boolean; -}) => { +export const Delegations = () => { const [delegationNodeId, setDelegationNodeId] = useState(); const [amountToBeDelegated, setAmountToBeDelegated] = useState(); + const [log, setLog] = useState([]); + const [delegations, setDelegations] = useState(); + const [delegationLoader, setDelegationLoader] = useState(false); + const [undeledationLoader, setUndeledationLoader] = useState(false); + const [withdrawLoading, setWithdrawLoading] = useState(false); + + + + // const getDelegations = useCallback(async () => { + // const newDelegations = await signerClient.getDelegatorDelegations({ + // delegator: settings.address, + // }); + // setDelegations(newDelegations); + // }, [signerClient]); + + // // Start Undelgate All + // const doUndelegateAll = async () => { + // if (!signerClient) { + // return; + // } + // setUndeledationLoader(true); + // try { + // // eslint-disable-next-line no-restricted-syntax + // for (const delegation of delegations.delegations) { + // // eslint-disable-next-line no-await-in-loop + // await signerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto'); + // } + // } catch (error) { + // console.error(error); + // } + // setUndeledationLoader(false); + // }; + // // End Undelgate All + + // // Start Delegate + // const doDelegate = async ({ mixId, amount }: { mixId: number; amount: number }) => { + // if (!signerClient) { + // return; + // } + // setDelegationLoader(true); + // try { + // const res = await signerClient.delegateToMixnode({ mixId }, 'auto', undefined, [ + // { amount: `${amount}`, denom: 'unym' }, + // ]); + // console.log('res', res); + // setLog((prev) => [ + // ...prev, + //
+ // {new Date().toLocaleTimeString()} + //
{JSON.stringify(res, null, 2)}
+ //
, + // ]); + // } catch (error) { + // console.error(error); + // } + // setDelegationLoader(false); + // }; + // // End delegate + + // // Start Withdraw Rewards + // const doWithdrawRewards = async () => { + // const delegatorAddress = ''; + // const validatorAdress = ''; + // const memo = 'test sending tokens'; + // setWithdrawLoading(true); + // try { + // const res = await signerCosmosWasmClient.withdrawRewards(delegatorAddress, validatorAdress, 'auto', memo); + // setLog((prev) => [ + // ...prev, + //
+ // {new Date().toLocaleTimeString()} + //
{JSON.stringify(res, null, 2)}
+ //
, + // ]); + // } catch (error) { + // console.error(error); + // } + // setWithdrawLoading(false); + // }; + // // End Withdraw Rewards + + // useEffect(() => { + // if (signerClient && !delegations) { + // getDelegations(); + // } + // }, [signerClient, getDelegations, delegations]); + + // return ( + // + // + // Delegations + // + // + // + // Make a delegation + // + // setDelegationNodeId(e.target.value)} + // size="small" + // /> + // + // setAmountToBeDelegated(e.target.value)} + // size="small" + // /> + // + // + // + // + // + // Your delegations + // + // {!delegations?.delegations?.length ? ( + // You do not have delegations + // ) : ( + // + // + // + // + // MixId + // Owner + // Amount + // Cumulative Reward Ratio + // + // + // + // {delegations?.delegations.map((delegation: any) => ( + // + // {delegation.mix_id} + // {delegation.owner} + // {delegation.amount.amount} + // {delegation.cumulative_reward_ratio} + // + // ))} + // + //
+ //
+ // )} + //
+ // {delegations && ( + // + // + // + // )} + // + // + // + //
+ //
+ //
+ // ); return ( - - - Delegations - - - - Make a delegation - - setDelegationNodeId(e.target.value)} - size="small" - /> - - setAmountToBeDelegated(e.target.value)} - size="small" - /> - - - - - - Your delegations - - {!delegations?.delegations?.length ? ( - You do not have delegations - ) : ( - - - - - MixId - Owner - Amount - Cumulative Reward Ratio - - - - {delegations?.delegations.map((delegation: any) => ( - - {delegation.mix_id} - {delegation.owner} - {delegation.amount.amount} - {delegation.cumulative_reward_ratio} - - ))} - -
-
- )} -
- {delegations && ( - - - - )} - - - -
-
-
- ); + Hello + ) }; diff --git a/sdk/typescript/docs/components/wallet/sendTokens.tsx b/sdk/typescript/docs/components/wallet/sendTokens.tsx index a1e4e421b1..6316f91480 100644 --- a/sdk/typescript/docs/components/wallet/sendTokens.tsx +++ b/sdk/typescript/docs/components/wallet/sendTokens.tsx @@ -1,45 +1,94 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import Button from '@mui/material/Button'; import Paper from '@mui/material/Paper'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import TextField from '@mui/material/TextField'; +import { useWalletContext } from '../context/wallet'; export const SendTokes = ({ - setRecipientAddress, - doSendTokens, - sendingTokensLoader, + // setRecipientAddress, + // signerCosmosWasmClient, + // account, + // recipientAddress, }: { - setRecipientAddress: (value: string) => void; - doSendTokens: (amount: string) => void; - sendingTokensLoader: boolean; + // setRecipientAddress: (value: string) => void; + // signerCosmosWasmClient: any; + // account: string; + // recipientAddress: string; }) => { + const { cosmWasmSigner, cosmWasmSignerClient, nymWasmSignerClient, setConnectWithMnemonic } = useWalletContext(); + const [recipientAddress, setRecipientAddress] = useState(''); const [tokensToSend, setTokensToSend] = useState(); + const [sendingTokensLoader, setSendingTokensLoader] = useState(false); + const [log, setLog] = useState([]); + + // Sending tokens + // const doSendTokens = async () => { + // const memo = 'test sending tokens'; + // setSendingTokensLoader(true); + // try { + // console.log('signerCosmosWasmClient', signerCosmosWasmClient); + // const res = await signerCosmosWasmClient.sendTokens( + // account, + // recipientAddress, + // [{ amount: tokensToSend, denom: 'unym' }], + // 'auto', + // memo, + // ); + // setLog((prev) => [ + // ...prev, + //
+ // {new Date().toLocaleTimeString()} + //
{JSON.stringify(res, null, 2)}
+ //
, + // ]); + // } catch (error) { + // console.error(error); + // } + // setSendingTokensLoader(false); + // }; + // End send tokens + + // console.log('signerCosmosWasmClient', signerCosmosWasmClient); + + // useEffect(() => { + // console.log('signerCosmosWasmClient', signerCosmosWasmClient, 'account', account); + // }, [signerCosmosWasmClient, account]); return ( - - - Send Tokens - - setRecipientAddress(e.target.value)} - size="small" - /> - + + + + Send Tokens + setTokensToSend(e.target.value)} + placeholder="Recipient Address" + onChange={(e) => setRecipientAddress(e.target.value)} size="small" /> - + + setTokensToSend(e.target.value)} + size="small" + /> + {/* */} + - - + + + {log.length > 0 && ( + + Transaction Logs: + {log} + + )} + ); -}; \ No newline at end of file +};