From 84a52ce2fe4178db799f0403ff936947f74d81c1 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Sun, 25 Aug 2024 06:25:26 -0400 Subject: [PATCH] refactor(tangle-dapp): Fetch all NFTs at once --- .../stakeAndUnstake/useAgnosticLsBalance.ts | 2 +- .../RebondLstUnstakeRequestButton.tsx | 2 + .../UnstakeRequestsTable.tsx | 22 +++---- .../WithdrawLstUnstakeRequestButton.tsx | 4 +- .../WithdrawUnlockNftButton.tsx | 28 +++++++-- .../constants/liquidStaking/constants.ts | 4 +- .../data/liquidStaking/useExchangeRate.ts | 17 +++-- .../data/liquidStaking/usePolling.ts | 63 ++++++++----------- .../data/liquifier/useContractRead.ts | 27 ++++++-- .../data/liquifier/useContractReadBatch.ts | 40 ++++++++---- .../data/liquifier/useLiquifierDeposit.ts | 4 +- .../data/liquifier/useLiquifierNftUnlocks.ts | 48 +++++--------- .../data/liquifier/useLiquifierWithdraw.ts | 8 ++- apps/tangle-dapp/hooks/useTxNotification.tsx | 9 +-- 14 files changed, 158 insertions(+), 120 deletions(-) diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts index 5c9721fae..8d2baf38b 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts @@ -77,7 +77,7 @@ const useAgnosticLsBalance = (isNative: boolean, protocolId: LsProtocolId) => { }); }, [evmAddress20, isNative, protocol, readErc20, readLiquidErc20]); - usePolling({ fetcher: erc20BalanceFetcher }); + usePolling({ effect: erc20BalanceFetcher }); useEffect(() => { if (protocol.type !== 'parachain' || parachainBalances === null) { diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx index 8549f3506..d9654fe68 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx @@ -48,6 +48,8 @@ const RebondLstUnstakeRequestButton: FC = ({ } onClick={() => setIsConfirmationModalOpen(true)} isFullWidth + isLoading={rebondTxStatus === TxStatus.PROCESSING} + loadingText="Processing" > Cancel Unstake diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx index 94c043ce8..675a20783 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx @@ -79,10 +79,10 @@ type UnstakeRequestTableRow = | LiquifierUnlockNftMetadata | ParachainUnstakeRequest; -const columnHelper = createColumnHelper(); +const COLUMN_HELPER = createColumnHelper(); -const columns = [ - columnHelper.accessor('unlockId', { +const COLUMNS = [ + COLUMN_HELPER.accessor('unlockId', { header: () => , cell: (props) => { return ( @@ -98,7 +98,7 @@ const columns = [ ); }, }), - columnHelper.accessor('progress', { + COLUMN_HELPER.accessor('progress', { header: () => , cell: (props) => { const progress = props.getValue(); @@ -135,7 +135,7 @@ const columns = [ return
{content}
; }, }), - columnHelper.accessor('amount', { + COLUMN_HELPER.accessor('amount', { header: () => , cell: (props) => { const unstakeRequest = props.row.original; @@ -162,9 +162,7 @@ const UnstakeRequestsTable: FC = () => { const { selectedProtocolId } = useLiquidStakingStore(); const substrateAddress = useSubstrateAddress(); const parachainRows = useLstUnlockRequestTableRows(); - - // TODO: Link table paging with paging options here. - const evmRows = useLiquifierNftUnlocks({ page: 1, pageSize: 5 }); + const evmRows = useLiquifierNftUnlocks(); // Select the table rows based on whether the selected protocol // is an EVM-based chain or a parachain-based Substrate chain. @@ -175,17 +173,18 @@ const UnstakeRequestsTable: FC = () => { // In case that the data is not loaded yet, use an empty array // to avoid TypeScript errors. data: rows ?? [], - columns, + columns: COLUMNS, filterFns: { fuzzy: fuzzyFilter, }, - // getRowId: (row) => row.unlockId.toString(), globalFilterFn: fuzzyFilter, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), enableRowSelection: true, + autoResetPageIndex: false, + getRowId: (row) => row.unlockId.toString(), }), [rows], ); @@ -237,6 +236,7 @@ const UnstakeRequestsTable: FC = () => { tdClassName="!bg-inherit !px-3 !py-2 whitespace-nowrap" tableProps={tableProps} totalRecords={rows.length} + isPaginated /> ); })(); @@ -265,7 +265,7 @@ const UnstakeRequestsTable: FC = () => { // request has completed its unlocking period. return request.type === 'parachainUnstakeRequest' ? request.progress === undefined - : request.progress === 100; + : request.progress === 1; }); }, [selectedRowsUnlockIds, rows]); diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx index 463ca65d3..bd1f8a8ea 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx @@ -43,7 +43,7 @@ const WithdrawLstUnstakeRequestButton: FC< return ( <> diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawUnlockNftButton.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawUnlockNftButton.tsx index c75a2d450..a819c761f 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawUnlockNftButton.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawUnlockNftButton.tsx @@ -1,5 +1,5 @@ import { Button } from '@webb-tools/webb-ui-components'; -import { FC, useCallback } from 'react'; +import { FC, useCallback, useState } from 'react'; import { LsErc20TokenId } from '../../../constants/liquidStaking/types'; import useLiquifierWithdraw from '../../../data/liquifier/useLiquifierWithdraw'; @@ -15,24 +15,42 @@ const WithdrawUnlockNftButton: FC = ({ canWithdraw, unlockIds, }) => { + const [isProcessing, setIsProcessing] = useState(false); const withdraw = useLiquifierWithdraw(); - const handleClick = useCallback(() => { + const handleClick = useCallback(async () => { if (withdraw === null) { return; } - for (const unlockId of unlockIds) { - withdraw(tokenId, unlockId); + setIsProcessing(true); + + for (const [index, unlockId] of unlockIds.entries()) { + const success = await withdraw(tokenId, unlockId, { + current: index + 1, + total: unlockIds.length, + }); + + if (!success) { + console.error( + 'Liquifier withdraw batch was aborted because one request failed', + ); + + break; + } } + + setIsProcessing(false); }, [tokenId, unlockIds, withdraw]); return ( diff --git a/apps/tangle-dapp/constants/liquidStaking/constants.ts b/apps/tangle-dapp/constants/liquidStaking/constants.ts index a9ad3d25c..8606e5448 100644 --- a/apps/tangle-dapp/constants/liquidStaking/constants.ts +++ b/apps/tangle-dapp/constants/liquidStaking/constants.ts @@ -21,11 +21,11 @@ import { * use dummy data. */ const SEPOLIA_TESTNET_CONTRACTS = { - LIQUIFIER: '0xCE0148DbA55c9719B59642f5cBf4F26e67F44E70', + LIQUIFIER: '0x55D942dC55b8b3bEE51C964c4985C46C7DF98Be0', ERC20: '0x2eE951c2d215ba1b3E0DF20764c96a0bC7809F41', // Use the same address as the dummy ERC20 contract. TG_TOKEN: '0x2eE951c2d215ba1b3E0DF20764c96a0bC7809F41', - UNLOCKS: '0x4EFd70b31c3bfC1824eD081AFFE8b107BcDf456A', + UNLOCKS: '0x32d70bC73d0965209Cf175711b010dE6A7650c2B', } as const satisfies Record; const CHAINLINK: LsErc20TokenDef = { diff --git a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts index e0c5a52c8..ca26fa1b0 100644 --- a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts +++ b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts @@ -1,5 +1,5 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { erc20Abi } from 'viem'; import { @@ -21,6 +21,8 @@ export enum ExchangeRateType { // TODO: This NEEDS to be based on subscription for sure, since exchange rates are always changing. Perhaps make it return whether it is re-fetching, so that an effect can be shown on the UI to indicate that it is fetching the latest exchange rate, and also have it be ran in a 3 or 5 second interval. Will also need de-duping logic, error handling, and also prevent spamming requests when the parent component is re-rendered many times (e.g. by using a ref to store the latest fetch timestamp). Might want to extract this pattern into its own hook, similar to a subscription. Also consider having a global store (Zustand) for that custom hook that uses caching to prevent spamming requests when the same hook is used in multiple components, might need to accept a custom 'key' parameter to use as the cache key. const useExchangeRate = (type: ExchangeRateType, protocolId: LsProtocolId) => { + const [exchangeRate, setExchangeRate] = useState(null); + const protocol = getLsProtocolDef(protocolId); const { result: tokenPoolAmount } = useApiRx((api) => { @@ -76,10 +78,13 @@ const useExchangeRate = (type: ExchangeRateType, protocolId: LsProtocolId) => { [], ); - const fetcher = useCallback(() => { - return protocol.type === 'parachain' - ? parachainExchangeRate - : fetchErc20ExchangeRate(protocol); + const fetcher = useCallback(async () => { + const promise = + protocol.type === 'parachain' + ? parachainExchangeRate + : fetchErc20ExchangeRate(protocol); + + setExchangeRate(await promise); }, [fetchErc20ExchangeRate, parachainExchangeRate, protocol]); const totalSupplyFetcher = useCallback((): ContractReadOptions< @@ -113,7 +118,7 @@ const useExchangeRate = (type: ExchangeRateType, protocolId: LsProtocolId) => { }, [protocol.type, setIsErc20TotalIssuancePaused]); // TODO: Use polling for the ERC20 exchange rate, NOT the parachain exchange rate which is already based on a subscription. Might need a mechanism to 'pause' polling when the selected protocol is a parachain chain, that way it doesn't make unnecessary requests until an ERC20 token is selected. - const { value: exchangeRate, isRefreshing } = usePolling({ fetcher }); + const isRefreshing = usePolling({ effect: fetcher }); return { exchangeRate, isRefreshing }; }; diff --git a/apps/tangle-dapp/data/liquidStaking/usePolling.ts b/apps/tangle-dapp/data/liquidStaking/usePolling.ts index 515ce6d8d..dab9775f7 100644 --- a/apps/tangle-dapp/data/liquidStaking/usePolling.ts +++ b/apps/tangle-dapp/data/liquidStaking/usePolling.ts @@ -1,55 +1,46 @@ -import { useEffect, useRef, useState } from 'react'; - -export enum PollingPrimaryCacheKey { - EXCHANGE_RATE, - CONTRACT_READ_SUBSCRIPTION, - LS_ERC20_BALANCE, -} +import { useCallback, useEffect, useState } from 'react'; export type PollingOptions = { - fetcher: (() => Promise | T) | null; + effect: (() => Promise | T) | null; refreshInterval?: number; }; const usePolling = ({ - fetcher, - // Default to a 6 second refresh interval. - refreshInterval = 6_000, + effect, + // Default to a 12 second refresh interval. + refreshInterval = 12_000, }: PollingOptions) => { - const [value, setValue] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); - const lastUpdatedTimestampRef = useRef(Date.now()); - useEffect(() => { - const intervalHandle = setInterval(async () => { - // Fetcher isn't ready to be called yet. - if (fetcher === null) { - return; - } + const refresh = useCallback(async () => { + // Fetcher isn't ready to be called yet. + if (effect === null) { + return; + } - const now = Date.now(); - const difference = now - lastUpdatedTimestampRef.current; + setIsRefreshing(true); + await effect(); + setIsRefreshing(false); + }, [effect]); - // Don't refresh if the last refresh was less than - // the refresh interval ago. This prevents issues where - // the fetcher is unstable and the setInterval gets called - // multiple times before the fetcher resolves. - if (difference < refreshInterval) { - return; - } + useEffect(() => { + let intervalHandle: ReturnType | null = null; + + (async () => { + // Call it immediately to avoid initial delay. + await refresh(); - setIsRefreshing(true); - setValue(await fetcher()); - lastUpdatedTimestampRef.current = now; - setIsRefreshing(false); - }, refreshInterval); + intervalHandle = setInterval(refresh, refreshInterval); + })(); return () => { - clearInterval(intervalHandle); + if (intervalHandle !== null) { + clearInterval(intervalHandle); + } }; - }, [fetcher, refreshInterval]); + }, [effect, refresh, refreshInterval]); - return { value, isRefreshing }; + return isRefreshing; }; export default usePolling; diff --git a/apps/tangle-dapp/data/liquifier/useContractRead.ts b/apps/tangle-dapp/data/liquifier/useContractRead.ts index d021520ad..9cd003982 100644 --- a/apps/tangle-dapp/data/liquifier/useContractRead.ts +++ b/apps/tangle-dapp/data/liquifier/useContractRead.ts @@ -1,6 +1,11 @@ import { PromiseOrT } from '@webb-tools/abstract-api-provider'; import { useCallback, useState } from 'react'; -import { Abi as ViemAbi, ContractFunctionName } from 'viem'; +import { + Abi as ViemAbi, + ContractFunctionArgs, + ContractFunctionName, + ContractFunctionReturnType, +} from 'viem'; import usePolling from '../liquidStaking/usePolling'; import useContractReadOnce, { @@ -22,6 +27,18 @@ const useContractRead = < // This is useful for when the options are dependent on some state. | (() => PromiseOrT | null>), ) => { + type ReturnType = + | Error + | Awaited< + ContractFunctionReturnType< + Abi, + 'pure' | 'view', + FunctionName, + ContractFunctionArgs + > + >; + + const [value, setValue] = useState(null); const [isPaused, setIsPaused] = useState(false); const readOnce = useContractReadOnce(abi); @@ -39,16 +56,16 @@ const useContractRead = < return null; } - return readOnce(options_); + setValue(await readOnce(options_)); }, [isPaused, options, readOnce]); - const { value, isRefreshing } = usePolling({ + usePolling({ // By providing null, it signals to the hook to maintain // its current value and not refresh. - fetcher: isPaused ? null : fetcher, + effect: isPaused ? null : fetcher, }); - return { value, isRefreshing, isPaused, setIsPaused }; + return { value, isPaused, setIsPaused }; }; export default useContractRead; diff --git a/apps/tangle-dapp/data/liquifier/useContractReadBatch.ts b/apps/tangle-dapp/data/liquifier/useContractReadBatch.ts index c7661f103..7ad49e6fb 100644 --- a/apps/tangle-dapp/data/liquifier/useContractReadBatch.ts +++ b/apps/tangle-dapp/data/liquifier/useContractReadBatch.ts @@ -1,6 +1,11 @@ import { PromiseOrT } from '@webb-tools/abstract-api-provider'; import { useCallback, useState } from 'react'; -import { Abi as ViemAbi, ContractFunctionName } from 'viem'; +import { + Abi as ViemAbi, + ContractFunctionArgs, + ContractFunctionName, + ContractFunctionReturnType, +} from 'viem'; import { IS_PRODUCTION_ENV } from '../../constants/env'; import usePolling from '../liquidStaking/usePolling'; @@ -26,12 +31,24 @@ const useContractReadBatch = < // This is useful for when the options are dependent on some state. | (() => PromiseOrT | null>), ) => { - const [isPaused, setIsPaused] = useState(false); + type ReturnType = ( + | Error + | Awaited< + ContractFunctionReturnType< + Abi, + 'pure' | 'view', + FunctionName, + ContractFunctionArgs + > + > + )[]; + + const [value, setValue] = useState(null); const readOnce = useContractReadOnce(abi); - const fetcher = useCallback(async () => { + const refresh = useCallback(async () => { // Not yet ready to fetch. - if (isPaused || readOnce === null) { + if (readOnce === null) { return null; } @@ -53,16 +70,15 @@ const useContractReadBatch = < readOnce({ ...options_, args }), ); - return Promise.all(promises); - }, [isPaused, options, readOnce]); + setValue(await Promise.all(promises)); + }, [options, readOnce]); - const { value, isRefreshing } = usePolling({ - // By providing null, it signals to the hook to maintain - // its current value and not refresh. - fetcher: isPaused ? null : fetcher, - }); + usePolling({ effect: refresh }); - return { value, isRefreshing, isPaused, setIsPaused }; + return { + value, + refresh, + }; }; export default useContractReadBatch; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index 0550aba5f..4d1120dc4 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -55,7 +55,7 @@ const useLiquifierDeposit = () => { address: tokenDef.address, functionName: 'approve', args: [tokenDef.liquifierAdapterAddress, BigInt(amount.toString())], - notificationStep: { current: 1, max: 2 }, + notificationStep: { current: 1, total: 2 }, }); if (!approveTxSucceeded) { @@ -69,7 +69,7 @@ const useLiquifierDeposit = () => { functionName: 'deposit', // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 args: [activeEvmAddress20, BigInt(amount.toString())], - notificationStep: { current: 2, max: 2 }, + notificationStep: { current: 2, total: 2 }, }); return depositTxSucceeded; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierNftUnlocks.ts b/apps/tangle-dapp/data/liquifier/useLiquifierNftUnlocks.ts index 07642e390..e65fcba1f 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierNftUnlocks.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierNftUnlocks.ts @@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react'; import { Address } from 'viem'; import { BaseUnstakeRequest } from '../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; +import { IS_PRODUCTION_ENV } from '../../constants/env'; import LIQUIFIER_UNLOCKS_ABI from '../../constants/liquidStaking/liquifierUnlocksAbi'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import getLsProtocolDef from '../../utils/liquidStaking/getLsProtocolDef'; @@ -37,15 +38,6 @@ export type LiquifierUnlockNftMetadata = BaseUnstakeRequest & { maturityTimestamp: number; }; -/** - * Note that the `page` and `pageSize` fields are assumed to be - * 1-indexed. - */ -export type PagingOptions = { - page: number; - pageSize: number; -}; - /** * In the case of liquifier unlock requests, they are represented * by ERC-721 NFTs owned by the user. @@ -53,12 +45,8 @@ export type PagingOptions = { * Each unlock NFT has associated metadata about the unlock request, * including the progress of the unlock request, the amount of underlying * stake tokens, and the maturity timestamp. - * - * @param paging The paging options for the unlock requests (1-based index). */ -const useLiquifierNftUnlocks = ( - paging?: PagingOptions, -): LiquifierUnlockNftMetadata[] | null => { +const useLiquifierNftUnlocks = (): LiquifierUnlockNftMetadata[] | null => { const { selectedProtocolId } = useLiquidStakingStore(); const activeEvmAddress20 = useEvmAddress20(); @@ -84,11 +72,6 @@ const useLiquifierNftUnlocks = ( getUnlockIdCountOptions, ); - // Extract the paging options from the object, to prevent - // possible issues due to unstable object references passed. - const page = paging?.page; - const pageSize = paging?.pageSize; - const unlockIds = useMemo(() => { if (rawUnlockIdCount === null || rawUnlockIdCount instanceof Error) { return null; @@ -103,18 +86,10 @@ const useLiquifierNftUnlocks = ( const unlockIdCount = Number(rawUnlockIdCount); - const from = - page === undefined || pageSize === undefined ? 0 : (page - 1) * pageSize; - - const to = - pageSize === undefined - ? unlockIdCount - : Math.min(from + pageSize, unlockIdCount); - return Array.from({ - length: to - from, - }).map((_, i) => BigInt(i + from)); - }, [page, pageSize, rawUnlockIdCount]); + length: unlockIdCount, + }).map((_, i) => BigInt(i)); + }, [rawUnlockIdCount]); const getMetadataOptions = useCallback((): ContractReadOptionsBatch< typeof LIQUIFIER_UNLOCKS_ABI, @@ -143,21 +118,26 @@ const useLiquifierNftUnlocks = ( getMetadataOptions, ); - const nftMetadatas = useMemo(() => { + const metadatas = useMemo(() => { if (rawMetadatas === null) { return null; } - return rawMetadatas.flatMap((metadata) => { + return rawMetadatas.flatMap((metadata, index) => { // Ignore failed metadata fetches and those that are still loading. if (metadata === null || metadata instanceof Error) { return []; } + // The Sepolia development contract always returns 0 for the + // unlock ID. Use the index number to differentiate between + // different unlock requests. + const unlockId = IS_PRODUCTION_ENV ? Number(metadata.unlockId) : index; + return { type: 'liquifierUnlockNft', decimals: protocol.decimals, - unlockId: Number(metadata.unlockId), + unlockId, symbol: metadata.symbol, name: metadata.name, validator: metadata.validator, @@ -168,7 +148,7 @@ const useLiquifierNftUnlocks = ( }); }, [protocol.decimals, rawMetadatas]); - return nftMetadatas; + return metadatas; }; export default useLiquifierNftUnlocks; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierWithdraw.ts b/apps/tangle-dapp/data/liquifier/useLiquifierWithdraw.ts index 99c0190bb..194bfe6c8 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierWithdraw.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierWithdraw.ts @@ -6,6 +6,7 @@ import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/constants'; import LIQUIFIER_ABI from '../../constants/liquidStaking/liquifierAbi'; import { LsErc20TokenId } from '../../constants/liquidStaking/types'; import useEvmAddress20 from '../../hooks/useEvmAddress'; +import { NotificationSteps } from '../../hooks/useTxNotification'; import useContractWrite from './useContractWrite'; const useLiquifierWithdraw = () => { @@ -15,7 +16,11 @@ const useLiquifierWithdraw = () => { const isReady = writeLiquifier !== null && activeEvmAddress20 !== null; const withdraw = useCallback( - async (tokenId: LsErc20TokenId, unlockId: number) => { + async ( + tokenId: LsErc20TokenId, + unlockId: number, + notificationStep?: NotificationSteps, + ) => { // TODO: Should the user balance check be done here or assume that the consumer of the hook will handle that? assert( @@ -31,6 +36,7 @@ const useLiquifierWithdraw = () => { address: tokenDef.liquifierAdapterAddress, functionName: 'withdraw', args: [activeEvmAddress20, BigInt(unlockId)], + notificationStep, }); return withdrawTxSucceeded; diff --git a/apps/tangle-dapp/hooks/useTxNotification.tsx b/apps/tangle-dapp/hooks/useTxNotification.tsx index bd787978c..109654034 100644 --- a/apps/tangle-dapp/hooks/useTxNotification.tsx +++ b/apps/tangle-dapp/hooks/useTxNotification.tsx @@ -35,6 +35,7 @@ const SUCCESS_MESSAGES: Record = { [TxName.LS_LIQUIFIER_DEPOSIT]: 'Liquifier deposit successful', [TxName.LS_LIQUIFIER_APPROVE]: 'Liquifier approval successful', [TxName.LS_LIQUIFIER_UNLOCK]: 'Liquifier unlock successful', + [TxName.LS_LIQUIFIER_WITHDRAW]: 'Liquifier withdrawal successful', }; const makeKey = (txName: TxName): `${TxName}-tx-notification` => @@ -42,7 +43,7 @@ const makeKey = (txName: TxName): `${TxName}-tx-notification` => export type NotificationSteps = { current: number; - max: number; + total: number; }; // TODO: Use a ref for the key to permit multiple rapid fire transactions from stacking under the same key. Otherwise, use a global state counter via Zustand. @@ -133,7 +134,7 @@ const useTxNotification = (explorerUrl?: string) => { const notifyProcessing = useCallback( (txName: TxName, steps?: NotificationSteps) => { // Sanity check. - if (steps !== undefined && steps.current > steps.max) { + if (steps !== undefined && steps.current > steps.total) { console.warn( 'Current transaction notification steps exceed the maximum steps (check for off-by-one errors)', ); @@ -145,8 +146,8 @@ const useTxNotification = (explorerUrl?: string) => { enqueueSnackbar( - {steps !== undefined && `(${steps.current}/${steps.max}) `}Processing{' '} - {txName} + {steps !== undefined && `(${steps.current}/${steps.total}) `} + Processing {txName} , { key,