From bfcf1c7d4de0994e0cb6d322cfc0426b297e7648 Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Sat, 28 Dec 2024 00:04:34 -0300 Subject: [PATCH] feat: refactoring ibc transfer and withdraw to redirect to timeline page (#1451) --- apps/namadillo/src/App/Ibc/IbcTransfer.tsx | 141 ++++------ apps/namadillo/src/App/Ibc/IbcWithdraw.tsx | 257 +++++++++--------- .../namadillo/src/hooks/useIbcTransaction.tsx | 2 +- 3 files changed, 182 insertions(+), 218 deletions(-) diff --git a/apps/namadillo/src/App/Ibc/IbcTransfer.tsx b/apps/namadillo/src/App/Ibc/IbcTransfer.tsx index 1a8f607c4..76961ceb1 100644 --- a/apps/namadillo/src/App/Ibc/IbcTransfer.tsx +++ b/apps/namadillo/src/App/Ibc/IbcTransfer.tsx @@ -1,7 +1,7 @@ import { Chain } from "@chain-registry/types"; import { AccountType } from "@namada/types"; import { mapUndefined } from "@namada/utils"; -import { TransferTransactionTimeline } from "App/Transactions/TransferTransactionTimeline"; +import { routes } from "App/routes"; import { OnSubmitTransferParams, TransferModule, @@ -13,22 +13,25 @@ import { ibcChannelsFamily, } from "atoms/integrations"; import BigNumber from "bignumber.js"; -import clsx from "clsx"; import { useIbcTransaction } from "hooks/useIbcTransaction"; import { useTransactionActions } from "hooks/useTransactionActions"; import { useWalletManager } from "hooks/useWalletManager"; import { wallets } from "integrations"; import { KeplrWalletManager } from "integrations/Keplr"; +import invariant from "invariant"; import { useAtomValue } from "jotai"; import { useEffect, useMemo, useState } from "react"; +import { generatePath, useNavigate } from "react-router-dom"; import namadaChain from "registry/namada.json"; -import { Address, PartialTransferTransactionData, TransferStep } from "types"; +import { Address, TransferTransactionData } from "types"; import { IbcTopHeader } from "./IbcTopHeader"; const keplr = new KeplrWalletManager(); const defaultChainId = "cosmoshub-4"; export const IbcTransfer = (): JSX.Element => { + const navigate = useNavigate(); + // Global & Atom states const availableChains = useAtomValue(availableChainsAtom); const defaultAccounts = useAtomValue(allDefaultAccountsAtom); @@ -59,8 +62,6 @@ export const IbcTransfer = (): JSX.Element => { const [generalErrorMessage, setGeneralErrorMessage] = useState(""); const [sourceChannel, setSourceChannel] = useState(""); const [destinationChannel, setDestinationChannel] = useState(""); - const [transaction, setTransaction] = - useState(); // Derived data const availableAmount = mapUndefined( @@ -74,7 +75,7 @@ export const IbcTransfer = (): JSX.Element => { ); // Manage the history of transactions - const { findByHash, storeTransaction } = useTransactionActions(); + const { storeTransaction } = useTransactionActions(); // Utils for IBC transfers const { transferToNamada, gasConfig, transferStatus } = useIbcTransaction({ @@ -86,6 +87,11 @@ export const IbcTransfer = (): JSX.Element => { selectedAsset, }); + const redirectToTimeline = (tx: TransferTransactionData): void => { + invariant(tx.hash, "Invalid TX hash"); + navigate(generatePath(routes.transaction, { hash: tx.hash })); + }; + const namadaAddress = useMemo(() => { return ( defaultAccounts.data?.find( @@ -100,14 +106,6 @@ export const IbcTransfer = (): JSX.Element => { useEffect(() => setSelectedAssetAddress(undefined), [registry]); - // Update transaction if its hash is known and it exists in stored transactions - useEffect(() => { - if (transaction?.hash) { - const tx = findByHash(transaction.hash); - tx && setTransaction(tx); - } - }, [transaction?.hash, findByHash]); - // Set source and destination channels based on IBC channels data useEffect(() => { setSourceChannel(ibcChannels?.cosmosChannelId || ""); @@ -120,19 +118,11 @@ export const IbcTransfer = (): JSX.Element => { }: OnSubmitTransferParams): Promise => { try { setGeneralErrorMessage(""); - const txPromise = transferToNamada(destinationAddress, displayAmount); - setTransaction({ - type: shielded ? "IbcToShielded" : "IbcToTransparent", - asset: selectedAsset!.asset, - chainId: registry!.chain.chain_id, - currentStep: TransferStep.Sign, - }); - const result = await txPromise; - setTransaction(result); + const result = await transferToNamada(destinationAddress, displayAmount); storeTransaction(result); + redirectToTimeline(result); } catch (err) { setGeneralErrorMessage(err + ""); - setTransaction(undefined); } }; @@ -149,64 +139,49 @@ export const IbcTransfer = (): JSX.Element => { }; return ( - <> -
- {!transaction && ( - <> -
- -

IBC Transfer to Namada

-
- - - )} - {transaction && ( -
- -
- )} -
- +
+
+ +

IBC Transfer to Namada

+
+ +
); }; diff --git a/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx b/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx index 8ef820ded..abb19f68d 100644 --- a/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx +++ b/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx @@ -1,13 +1,14 @@ -import { Chain } from "@chain-registry/types"; +import { Asset, Chain } from "@chain-registry/types"; import { IbcTransferMsgValue } from "@namada/types"; import { mapUndefined } from "@namada/utils"; -import { TransferTransactionTimeline } from "App/Transactions/TransferTransactionTimeline"; +import { routes } from "App/routes"; import { OnSubmitTransferParams, TransferModule, } from "App/Transfer/TransferModule"; import { defaultAccountAtom } from "atoms/accounts"; import { namadaTransparentAssetsAtom } from "atoms/balance"; +import { chainAtom } from "atoms/chain"; import { defaultGasConfigFamily } from "atoms/fees"; import { availableChainsAtom, @@ -16,17 +17,23 @@ import { ibcChannelsFamily, } from "atoms/integrations"; import BigNumber from "bignumber.js"; -import clsx from "clsx"; import { useTransaction } from "hooks/useTransaction"; import { useTransactionActions } from "hooks/useTransactionActions"; import { useWalletManager } from "hooks/useWalletManager"; import { wallets } from "integrations"; import { KeplrWalletManager } from "integrations/Keplr"; +import invariant from "invariant"; import { useAtomValue } from "jotai"; import { TransactionPair } from "lib/query"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; +import { generatePath, useNavigate } from "react-router-dom"; import namadaChainRegistry from "registry/namada.json"; -import { Address, IbcTransferTransactionData, TransferStep } from "types"; +import { + Address, + IbcTransferTransactionData, + TransferStep, + TransferTransactionData, +} from "types"; import { toBaseAmount } from "utils"; import { IbcTopHeader } from "./IbcTopHeader"; @@ -37,18 +44,17 @@ export const IbcWithdraw: React.FC = () => { const namadaAccount = useAtomValue(defaultAccountAtom); const chainRegistry = useAtomValue(chainRegistryAtom); const availableChains = useAtomValue(availableChainsAtom); + const namadaChain = useAtomValue(chainAtom); + const [generalErrorMessage, setGeneralErrorMessage] = useState(""); const [selectedAssetAddress, setSelectedAssetAddress] = useState
(); const [amount, setAmount] = useState(); const [customAddress, setCustomAddress] = useState(""); const [sourceChannel, setSourceChannel] = useState(""); - const [transaction, setTransaction] = useState(); - const tempTransaction = useRef(); const { data: availableAssets } = useAtomValue(namadaTransparentAssetsAtom); - - const { storeTransaction, findByHash, transactions } = - useTransactionActions(); + const { storeTransaction } = useTransactionActions(); + const navigate = useNavigate(); const availableAmount = mapUndefined( (address) => availableAssets?.[address]?.amount, @@ -70,6 +76,10 @@ export const IbcWithdraw: React.FC = () => { connectToChainId(chainId || defaultChainId); }; + const onChangeChain = (chain: Chain): void => { + connectToChainId(chain.chain_id); + }; + const { data: ibcChannels } = useAtomValue( ibcChannelsFamily(registry?.chain.chain_name) ); @@ -78,29 +88,6 @@ export const IbcWithdraw: React.FC = () => { setSourceChannel(ibcChannels?.namadaChannelId || ""); }, [ibcChannels]); - // Keep local transaction up to date with the stored transaction - useEffect(() => { - if (transaction?.hash) { - const storedTx = findByHash(transaction.hash); - if (storedTx) { - setTransaction(storedTx as IbcTransferTransactionData); - } - } - }, [transactions]); - - const onSuccess = useCallback( - (tx: TransactionPair) => { - if (!tempTransaction.current) return; - const transactionData: IbcTransferTransactionData = { - ...tempTransaction.current, - hash: tx.encodedTxData.txs[0].innerTxHashes[0].toLowerCase(), - currentStep: TransferStep.WaitingConfirmation, - }; - storeTransaction(transactionData); - }, - [transaction] - ); - const { execute: performWithdraw, isPending } = useTransaction({ eventType: "IbcTransfer", createTxAtom: createIbcTxAtom, @@ -113,9 +100,46 @@ export const IbcWithdraw: React.FC = () => { title: "IBC withdrawal failed", description: "", }), - onSuccess, }); + const storeTransferTransaction = ( + tx: TransactionPair, + displayAmount: BigNumber, + destinationChainId: string, + asset: Asset + ): IbcTransferTransactionData => { + const props = tx.encodedTxData.meta?.props[0]; + invariant(props, "Invalid transaction data"); + + const transferTransaction: IbcTransferTransactionData = { + hash: tx.encodedTxData.txs[0].innerTxHashes[0].toLowerCase(), + currentStep: TransferStep.WaitingConfirmation, + rpc: "", + type: "TransparentToIbc", + status: "pending", + sourcePort: "transfer", + asset, + chainId: namadaChain.data?.chainId || "", + destinationChainId, + memo: tx.encodedTxData.wrapperTxProps.memo || props.memo, + displayAmount, + sourceAddress: props.source, + sourceChannel: props.channelId, + destinationAddress: props.receiver, + createdAt: new Date(), + updatedAt: new Date(), + sequence: new BigNumber(0), + }; + + storeTransaction(transferTransaction); + return transferTransaction; + }; + + const redirectToTimeline = (tx: TransferTransactionData): void => { + invariant(tx.hash, "Invalid TX hash"); + navigate(generatePath(routes.transaction, { hash: tx.hash })); + }; + const submitIbcTransfer = async ({ displayAmount, destinationAddress, @@ -127,48 +151,18 @@ export const IbcWithdraw: React.FC = () => { selectedAssetAddress ); - if (typeof selectedAsset === "undefined") { - throw new Error("No selected asset"); - } - - if (typeof sourceChannel === "undefined") { - throw new Error("No channel ID is set"); - } - - if (typeof gasConfig === "undefined") { - throw new Error("No gas config"); - } - - if (typeof keplrAddress === "undefined") { - throw new Error("No address selected"); - } + invariant(selectedAsset, "No asset is selected"); + invariant(sourceChannel, "No channel ID is set"); + invariant(chainId, "No chain is selected"); + invariant(gasConfig, "No gas config"); + invariant(keplrAddress, "No address is selected"); const amountInBaseDenom = toBaseAmount( selectedAsset.asset, displayAmount ); - const tx: IbcTransferTransactionData = { - rpc: "", - type: "TransparentToIbc", - asset: selectedAsset.asset, - chainId: namadaChainRegistry.chain_id, - sourcePort: "transfer", - sourceChannel: sourceChannel.trim(), - status: "pending", - memo, - displayAmount, - destinationChainId: chainId!, - sourceAddress: keplrAddress, - destinationAddress, - sequence: new BigNumber(0), - createdAt: new Date(), - updatedAt: new Date(), - currentStep: TransferStep.Sign, - }; - setTransaction({ ...tx }); - tempTransaction.current = { ...tx }; - await performWithdraw({ + const tx = await performWithdraw({ params: [ { amountInBaseDenom, @@ -181,78 +175,73 @@ export const IbcWithdraw: React.FC = () => { }, ], }); - setTransaction({ ...tx, currentStep: TransferStep.WaitingConfirmation }); + + if (tx) { + const transferTransaction = storeTransferTransaction( + tx, + displayAmount, + chainId, + selectedAsset.asset + ); + redirectToTimeline(transferTransaction); + } } catch (err) { - setGeneralErrorMessage(err + ""); - setTransaction(undefined); + setGeneralErrorMessage(String(err)); } }; - const onChangeChain = (chain: Chain): void => { - connectToChainId(chain.chain_id); - }; - const requiresIbcChannels = !ibcChannels?.cosmosChannelId; return (
- {!transaction && ( - <> -
- -
-

- Withdraw assets from Namada via IBC -

-

- To withdraw shielded assets please unshield them to your - transparent account -

-
-
- chainRegistry[id]?.chain, chainId), - onChangeWallet, - onChangeChain, - isShielded: false, - }} - isSubmitting={isPending} - isIbcTransfer={true} - requiresIbcChannels={requiresIbcChannels} - ibcOptions={{ - sourceChannel, - onChangeSourceChannel: setSourceChannel, - }} - onSubmitTransfer={submitIbcTransfer} - gasConfig={gasConfig} - errorMessage={generalErrorMessage} - /> - - )} - {transaction && ( -
- +
+ +
+

+ Withdraw assets from Namada via IBC +

+

+ To withdraw shielded assets please unshield them to your transparent + account +

- )} +
+ chainRegistry[id]?.chain, chainId), + onChangeWallet, + onChangeChain, + isShielded: false, + }} + isSubmitting={isPending} + isIbcTransfer={true} + requiresIbcChannels={requiresIbcChannels} + ibcOptions={{ + sourceChannel, + onChangeSourceChannel: setSourceChannel, + }} + onSubmitTransfer={submitIbcTransfer} + gasConfig={gasConfig} + errorMessage={generalErrorMessage} + />
); }; diff --git a/apps/namadillo/src/hooks/useIbcTransaction.tsx b/apps/namadillo/src/hooks/useIbcTransaction.tsx index 8bfc6be89..3d3bd17cc 100644 --- a/apps/namadillo/src/hooks/useIbcTransaction.tsx +++ b/apps/namadillo/src/hooks/useIbcTransaction.tsx @@ -67,7 +67,7 @@ export const useIbcTransaction = ({ invariant(registry, "Invalid chain"); invariant(sourceChannel, "Invalid IBC source channel"); invariant( - shielded && !destinationChannel, + !shielded || destinationChannel, "Invalid IBC destination channel" ); invariant(gasConfig, "No transaction fee is set");