From 2a95040c7a6535d21e004b4bf4bcb7d022a9cda7 Mon Sep 17 00:00:00 2001 From: bigboydiamonds <57741810+bigboydiamonds@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:22:24 -0800 Subject: [PATCH] feat(synapse-interface): post transaction balances (#2079) * Track when transaction receipt error occurs * isTransactionReceiptError util, fetch origin balances if error is transaction receipt * Bump timeout to 60s * Create more robust error handling for checking if transaction receipt error * Clean, revert back to 60s timeout * Log when isTransactionReceiptError * Refetch pool data after withdraw tx * Update pool balances after Deposit * Add isFetching prop to PoolDataState to track initial load, separate fetching from mount * Add stakedBalance to poolUserData slice * Refetch pool token balances after withdraw * Post deposit + withdraw updates all pool data including user * onSuccessWithdraw() * onSuccessDeposit() * Trim lp balance in Stake pool * Set initial stakedBalance amount/reward to bigint * Update User pool token staked balance on individual pool body * Refactor; use poolUserData slice to display staked balance in individual pool page * Update Pool LP balances after stake/unstake so pools page shows accurate balances * Remove isFetching, use isLoading for fetched pool attributes instead * Add pools convenience store hooks * Use pools state hooks to replace useSelector instances * Remove unused imports * Clean + organize imports * Update User LP staked balances when navigating to new pool * Use isLoading to display loading dots on pool data * isTransactionUserRejectedError * Throw error if transaction is rejected by User, ensure depositTxn() finally sets isLoading to false * Apply User rejected flow in withdrawTxn() * Update isTransactionReceiptError to also check for TransactionNotFoundError * Update balances on Swap after transacion receipt returned * onSuccessSwap * Clear swap input after successful swap executed * Rename withdraw/deposit reset function * getUserStakedBalance() * Update claim flow to refetch staked balance after tx executes * resetUserStakeData() * Refactor; getUserLpTokenAllowance, clean up useEffects with refactored fns * Remove unused tx state * Clarify function that catches error in Stake * Clean imports on StakeCard * Refetch allowances after allowance tx executes * Improve error handling * Remove unnecessary prop for LoadingDots --------- Co-authored-by: abtestingalpha --- .../StateManagedSwap/SwapInputContainer.tsx | 6 +- .../pages/pool/NoPoolBody.tsx | 4 +- .../synapse-interface/pages/pool/PoolBody.tsx | 50 ++---- .../pages/pool/PoolInfoSection/index.tsx | 21 ++- .../synapse-interface/pages/pool/[poolId].tsx | 36 +++-- .../pages/pool/poolManagement/Deposit.tsx | 80 ++++++---- .../pool/poolManagement/DepositButton.tsx | 26 +-- .../pages/pool/poolManagement/Withdraw.tsx | 81 ++++++---- .../pool/poolManagement/WithdrawButton.tsx | 27 ++-- .../pages/pool/poolManagement/index.tsx | 22 ++- .../pages/pools/PoolCard.tsx | 14 +- .../pages/pools/PoolCards.tsx | 3 +- .../synapse-interface/pages/pools/index.tsx | 26 ++- .../pages/stake/StakeCard.tsx | 149 +++++++++++------- .../pages/state-managed-bridge/index.tsx | 19 ++- .../synapse-interface/pages/swap/index.tsx | 85 ++++++---- .../synapse-interface/slices/poolDataSlice.ts | 2 +- .../slices/poolUserDataSlice.ts | 13 ++ .../synapse-interface/slices/pools/hooks.ts | 18 +++ .../slices/portfolio/reducer.ts | 7 +- .../utils/hooks/usePendingTxWrapper.tsx | 2 +- .../utils/isTransactionReceiptError.ts | 27 ++++ .../utils/isTransactionUserRejectedError.ts | 19 +++ .../synapse-interface/utils/types/index.tsx | 4 + 24 files changed, 458 insertions(+), 283 deletions(-) create mode 100644 packages/synapse-interface/slices/pools/hooks.ts create mode 100644 packages/synapse-interface/utils/isTransactionReceiptError.ts create mode 100644 packages/synapse-interface/utils/isTransactionUserRejectedError.ts diff --git a/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx b/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx index 9d6450607e..eb156d4053 100644 --- a/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx +++ b/packages/synapse-interface/components/StateManagedSwap/SwapInputContainer.tsx @@ -13,7 +13,7 @@ import { import { SwapChainSelector } from './SwapChainSelector' import { SwapFromTokenSelector } from './SwapFromTokenSelector' import { usePortfolioState } from '@/slices/portfolio/hooks' -import { updateSwapFromValue } from '@/slices/swap/reducer' +import { initialState, updateSwapFromValue } from '@/slices/swap/reducer' import { useSwapState } from '@/slices/swap/hooks' export const SwapInputContainer = () => { @@ -51,6 +51,10 @@ export const SwapInputContainer = () => { ) { setShowValue(swapFromValue) } + + if (swapFromValue === initialState.swapFromValue) { + setShowValue(initialState.swapFromValue) + } }, [swapFromValue, swapChainId, swapFromToken]) const handleFromValueChange = ( diff --git a/packages/synapse-interface/pages/pool/NoPoolBody.tsx b/packages/synapse-interface/pages/pool/NoPoolBody.tsx index 7bdd5d07d1..1727b61c57 100644 --- a/packages/synapse-interface/pages/pool/NoPoolBody.tsx +++ b/packages/synapse-interface/pages/pool/NoPoolBody.tsx @@ -1,8 +1,8 @@ import Card from '@tw/Card' import Grid from '@tw/Grid' -import { getNetworkTextColor } from '@styles/chains' -import { CHAINS_BY_ID } from '@constants/chains' import { Token } from '@types' +import { CHAINS_BY_ID } from '@constants/chains' +import { getNetworkTextColor } from '@styles/chains' const NoPoolBody = ({ pool, diff --git a/packages/synapse-interface/pages/pool/PoolBody.tsx b/packages/synapse-interface/pages/pool/PoolBody.tsx index f5e9e879d2..b8f3e75682 100644 --- a/packages/synapse-interface/pages/pool/PoolBody.tsx +++ b/packages/synapse-interface/pages/pool/PoolBody.tsx @@ -1,24 +1,22 @@ import numeral from 'numeral' import Link from 'next/link' +import { Address } from '@wagmi/core' +import { useEffect, useState } from 'react' +import { useAccount, useSwitchNetwork } from 'wagmi' import { ChevronLeftIcon } from '@heroicons/react/outline' -import { POOLS_PATH } from '@urls' import Card from '@tw/Card' import Grid from '@tw/Grid' -import PoolInfoSection from './PoolInfoSection' -import PoolManagement from './poolManagement' import { zeroAddress } from 'viem' -import { useSelector } from 'react-redux' -import { RootState } from '@/store/store' -import { Address } from '@wagmi/core' -import { useAccount, useSwitchNetwork } from 'wagmi' +import { PoolActionOptions } from '@/components/Pools/PoolActionOptions' import { TransactionButton } from '@/components/buttons/TransactionButton' import { useConnectModal } from '@rainbow-me/rainbowkit' import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider' -import { useEffect, useState } from 'react' -import { PoolActionOptions } from '@/components/Pools/PoolActionOptions' -import PoolTitle from './components/PoolTitle' import { DisplayBalances } from '../pools/PoolCard' -import { getStakedBalance } from '@/utils/actions/getStakedBalance' +import { usePoolDataState, usePoolUserDataState } from '@/slices/pools/hooks' +import { POOLS_PATH } from '@urls' +import PoolTitle from './components/PoolTitle' +import PoolInfoSection from './PoolInfoSection' +import PoolManagement from './poolManagement' const PoolBody = ({ address, @@ -30,16 +28,10 @@ const PoolBody = ({ const [isClient, setIsClient] = useState(false) const { chains, switchNetwork } = useSwitchNetwork() const { openConnectModal } = useConnectModal() - const { isConnected } = useAccount() - const { pool, poolAPYData } = useSelector( - (state: RootState) => state.poolData - ) - const [stakedBalance, setStakedBalance] = useState({ - amount: 0n, - reward: 0n, - }) + const { poolUserData } = usePoolUserDataState() + const { pool, poolAPYData } = usePoolDataState() useEffect(() => { setIsClient(true) @@ -51,29 +43,13 @@ const PoolBody = ({ poolName: pool?.poolName, }) } - if (address && isClient) { - getStakedBalance( - address as Address, - pool.chainId, - pool.poolId[pool.chainId], - pool - ) - .then((res) => { - setStakedBalance(res) - }) - .catch((err) => { - console.log('Could not get staked balances: ', err) - }) - } else { - setStakedBalance({ amount: 0n, reward: 0n }) - } }, [isClient, address, pool]) if (!pool) return null return ( <> -
+
@@ -87,7 +63,7 @@ const PoolBody = ({
diff --git a/packages/synapse-interface/pages/pool/PoolInfoSection/index.tsx b/packages/synapse-interface/pages/pool/PoolInfoSection/index.tsx index 5700d3abf6..5574aeb032 100644 --- a/packages/synapse-interface/pages/pool/PoolInfoSection/index.tsx +++ b/packages/synapse-interface/pages/pool/PoolInfoSection/index.tsx @@ -7,11 +7,10 @@ import { formatBigIntToPercentString, formatBigIntToString, } from '@/utils/bigint/format' -import { useSelector } from 'react-redux' -import { RootState } from '@/store/store' +import { usePoolDataState } from '@/slices/pools/hooks' const PoolInfoSection = () => { - const { pool, poolData } = useSelector((state: RootState) => state.poolData) + const { pool, poolData, isLoading } = usePoolDataState() const usdFormat = poolData.totalLockedUSD > 1000000 ? '$0,0.0' : '$0,0' @@ -22,49 +21,49 @@ const PoolInfoSection = () => { + ) } /> ) : ( - + ) } /> ) : ( - + ) } /> ) : ( - + ) } /> diff --git a/packages/synapse-interface/pages/pool/[poolId].tsx b/packages/synapse-interface/pages/pool/[poolId].tsx index 47221fae0c..d6b7607003 100644 --- a/packages/synapse-interface/pages/pool/[poolId].tsx +++ b/packages/synapse-interface/pages/pool/[poolId].tsx @@ -1,19 +1,23 @@ import _ from 'lodash' import { useEffect, useState } from 'react' -import { useAccount, useNetwork } from 'wagmi' -import { useDispatch, useSelector } from 'react-redux' import { useRouter } from 'next/router' -import StandardPageContainer from '@layouts/StandardPageContainer' -import { LandingPageWrapper } from '@layouts/LandingPageWrapper' -import { DEFAULT_FROM_CHAIN } from '@/constants/swap' -import PoolBody from './PoolBody' -import NoPoolBody from './NoPoolBody' +import { useAccount, useNetwork } from 'wagmi' +import { useAppDispatch } from '@/store/hooks' import { fetchPoolData, resetPoolData } from '@/slices/poolDataSlice' -import { RootState } from '@/store/store' import { resetPoolDeposit } from '@/slices/poolDepositSlice' import { resetPoolWithdraw } from '@/slices/poolWithdrawSlice' -import LoadingDots from '@/components/ui/tailwind/LoadingDots' +import { + fetchPoolUserData, + resetPoolUserData, +} from '@/slices/poolUserDataSlice' +import { usePoolDataState } from '@/slices/pools/hooks' +import { DEFAULT_FROM_CHAIN } from '@/constants/swap' +import { LandingPageWrapper } from '@layouts/LandingPageWrapper' import { POOL_BY_ROUTER_INDEX } from '@constants/tokens' +import PoolBody from './PoolBody' +import NoPoolBody from './NoPoolBody' +import LoadingDots from '@/components/ui/tailwind/LoadingDots' +import StandardPageContainer from '@layouts/StandardPageContainer' export const getStaticPaths = async () => { const paths = Object.keys(POOL_BY_ROUTER_INDEX).map((key) => ({ @@ -31,6 +35,7 @@ export const getStaticProps = async (context) => { } const PoolPage = () => { + const dispatch = useAppDispatch() const router = useRouter() const { poolId } = router.query const { address } = useAccount() @@ -38,9 +43,7 @@ const PoolPage = () => { const [connectedChainId, setConnectedChainId] = useState(0) const [isClient, setIsClient] = useState(false) - const { pool, isLoading } = useSelector((state: RootState) => state.poolData) - - const dispatch: any = useDispatch() + const { pool } = usePoolDataState() useEffect(() => { setIsClient(true) @@ -51,6 +54,7 @@ const PoolPage = () => { dispatch(resetPoolData()) dispatch(resetPoolDeposit()) dispatch(resetPoolWithdraw()) + dispatch(resetPoolUserData()) } router.events.on('routeChangeStart', handleRouteChange) @@ -71,13 +75,19 @@ const PoolPage = () => { } }, [poolId, address, isClient]) + useEffect(() => { + if (pool && address) { + dispatch(fetchPoolUserData({ pool, address })) + } + }, [pool]) + return ( - {!pool || isLoading || !poolId ? ( + {!pool || !poolId ? (
diff --git a/packages/synapse-interface/pages/pool/poolManagement/Deposit.tsx b/packages/synapse-interface/pages/pool/poolManagement/Deposit.tsx index 7c6a86f3f8..c932b46a85 100644 --- a/packages/synapse-interface/pages/pool/poolManagement/Deposit.tsx +++ b/packages/synapse-interface/pages/pool/poolManagement/Deposit.tsx @@ -1,29 +1,26 @@ import _ from 'lodash' - import { ETH, WETHE, WETH } from '@constants/tokens/bridgeable' import { AVWETH } from '@/constants/tokens/auxilliary' -import { stringToBigInt } from '@/utils/bigint/format' -import { DepositTokenInput } from '@components/TokenInput' -import PriceImpactDisplay from '../components/PriceImpactDisplay' -import { Token } from '@types' -import { useState, useEffect } from 'react' +import { stringToBigInt, formatBigIntToString } from '@/utils/bigint/format' import { getTokenAllowance } from '@/utils/actions/getTokenAllowance' import { approve, deposit, emptyPoolDeposit, } from '@/utils/actions/approveAndDeposit' -import LoadingTokenInput from '@components/loading/LoadingTokenInput' -import { Address, fetchBalance } from '@wagmi/core' +import { getAddress } from '@ethersproject/address' +import { fetchBalance, waitForTransaction } from '@wagmi/core' import { getSwapDepositContractFields } from '@/utils/getSwapDepositContractFields' import { calculatePriceImpact } from '@/utils/priceImpact' import { transformCalculateLiquidityInput } from '@/utils/transformCalculateLiquidityInput' -import { formatBigIntToString } from '@/utils/bigint/format' - -import { getAddress } from '@ethersproject/address' -import { useSelector } from 'react-redux' -import { RootState } from '@/store/store' - +import { isTransactionReceiptError } from '@/utils/isTransactionReceiptError' +import { isTransactionUserRejectedError } from '@/utils/isTransactionUserRejectedError' +import { useState, useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { DepositTokenInput } from '@components/TokenInput' +import { Token } from '@types' +import { Address } from '@wagmi/core' +import { zeroAddress } from 'viem' import { resetPoolDeposit, setDepositQuote, @@ -31,13 +28,19 @@ import { setIsLoading, setPool, } from '@/slices/poolDepositSlice' - -import { useDispatch } from 'react-redux' -import DepositButton from './DepositButton' -import { txErrorHandler } from '@/utils/txErrorHandler' +import { fetchPoolData } from '@/slices/poolDataSlice' import { fetchPoolUserData } from '@/slices/poolUserDataSlice' +import { fetchAndStoreSingleNetworkPortfolioBalances } from '@/slices/portfolio/hooks' +import { + usePoolDataState, + usePoolUserDataState, + usePoolDepositState, +} from '@/slices/pools/hooks' import { swapPoolCalculateAddLiquidity } from '@/actions/swapPoolCalculateAddLiquidity' -import { zeroAddress } from 'viem' +import { txErrorHandler } from '@/utils/txErrorHandler' +import LoadingTokenInput from '@components/loading/LoadingTokenInput' +import PriceImpactDisplay from '../components/PriceImpactDisplay' +import DepositButton from './DepositButton' export const DEFAULT_DEPOSIT_QUOTE = { priceImpact: 0n, @@ -54,10 +57,10 @@ const Deposit = ({ }) => { const dispatch: any = useDispatch() - const { pool, poolData } = useSelector((state: RootState) => state.poolData) - const { poolUserData } = useSelector((state: RootState) => state.poolUserData) + const { pool, poolData } = usePoolDataState() + const { poolUserData } = usePoolUserDataState() const { depositQuote, inputValue, inputSum, filteredInputValue } = - useSelector((state: RootState) => state.poolDeposit) + usePoolDepositState() const { poolAddress } = getSwapDepositContractFields(pool, chainId) @@ -171,6 +174,13 @@ const Deposit = ({ } } + const onResetDeposit = () => { + dispatch(fetchPoolData({ poolName: String(pool.routerIndex) })) + dispatch(fetchPoolUserData({ pool, address: address as Address })) + dispatch(fetchAndStoreSingleNetworkPortfolioBalances({ address, chainId })) + dispatch(resetPoolDeposit()) + } + const depositTxn = async () => { try { let tx @@ -181,15 +191,29 @@ const Deposit = ({ tx = deposit(pool, 'ONE_TENTH', null, filteredInputValue.bi, chainId) } - try { - await tx - dispatch(fetchPoolUserData({ pool, address: address as Address })) - dispatch(resetPoolDeposit()) - } catch (error) { - txErrorHandler(error) + const resolvedTx = await tx + + if (isTransactionUserRejectedError(resolvedTx)) { + throw Error(resolvedTx) } + + await waitForTransaction({ + hash: resolvedTx?.transactionHash as Address, + timeout: 60_000, + }) + + onResetDeposit() } catch (error) { + /** + * Assume transaction success if transaction receipt error + * Likely to be rpc related issue + */ + if (isTransactionReceiptError(error)) { + onResetDeposit() + } txErrorHandler(error) + } finally { + dispatch(setIsLoading(false)) } } diff --git a/packages/synapse-interface/pages/pool/poolManagement/DepositButton.tsx b/packages/synapse-interface/pages/pool/poolManagement/DepositButton.tsx index ceb9146f1b..02b181319e 100644 --- a/packages/synapse-interface/pages/pool/poolManagement/DepositButton.tsx +++ b/packages/synapse-interface/pages/pool/poolManagement/DepositButton.tsx @@ -1,13 +1,15 @@ -import { useSelector } from 'react-redux' -import { useConnectModal } from '@rainbow-me/rainbowkit' -import { useAccount, useNetwork, useSwitchNetwork } from 'wagmi' import { useEffect, useMemo, useState } from 'react' -import { RootState } from '@/store/store' - -import LoadingDots from '@/components/ui/tailwind/LoadingDots' +import { useAccount, useNetwork, useSwitchNetwork } from 'wagmi' +import { useConnectModal } from '@rainbow-me/rainbowkit' +import { + usePoolDataState, + usePoolDepositState, + usePoolUserDataState, +} from '@/slices/pools/hooks' +import { stringToBigInt } from '@/utils/bigint/format' import { TransactionButton } from '@/components/buttons/TransactionButton' import { DEFAULT_DEPOSIT_QUOTE } from './Deposit' -import { stringToBigInt } from '@/utils/bigint/format' +import LoadingDots from '@/components/ui/tailwind/LoadingDots' const DepositButton = ({ approveTxn, depositTxn }) => { const [isConnected, setIsConnected] = useState(false) // Initialize to false @@ -26,12 +28,10 @@ const DepositButton = ({ approveTxn, depositTxn }) => { setIsConnected(isConnectedInit) }, [isConnectedInit]) - const { pool, poolData } = useSelector((state: RootState) => state.poolData) - - const { depositQuote, inputValue, isLoading, inputSum } = useSelector( - (state: RootState) => state.poolDeposit - ) - const { poolUserData } = useSelector((state: RootState) => state.poolUserData) + const { pool, poolData } = usePoolDataState() + const { depositQuote, inputValue, isLoading, inputSum } = + usePoolDepositState() + const { poolUserData } = usePoolUserDataState() const isBalanceEnough = Object.entries(inputValue.bi).every( ([tokenAddr, amount]) => diff --git a/packages/synapse-interface/pages/pool/poolManagement/Withdraw.tsx b/packages/synapse-interface/pages/pool/poolManagement/Withdraw.tsx index 07e966b01c..5a3d14a466 100644 --- a/packages/synapse-interface/pages/pool/poolManagement/Withdraw.tsx +++ b/packages/synapse-interface/pages/pool/poolManagement/Withdraw.tsx @@ -1,28 +1,21 @@ import _ from 'lodash' -import { useEffect, useMemo, useState } from 'react' -import Slider from 'react-input-slider' -import { Address } from '@wagmi/core' -import { useDispatch, useSelector } from 'react-redux' -import { Token } from '@types' -import { RootState } from '@/store/store' - import Grid from '@tw/Grid' +import Slider from 'react-input-slider' +import { useEffect, useMemo, useState } from 'react' +import { Address, waitForTransaction } from '@wagmi/core' +import { useAppDispatch } from '@/store/hooks' import { getCoinTextColorCombined } from '@styles/tokens' import { ALL } from '@constants/withdrawTypes' import { WithdrawTokenInput } from '@components/TokenInput' -import RadioButton from '@components/buttons/RadioButton' -import ReceivedTokenSection from '../components/ReceivedTokenSection' -import PriceImpactDisplay from '../components/PriceImpactDisplay' - import { approve, withdraw } from '@/utils/actions/approveAndWithdraw' import { getTokenAllowance } from '@/utils/actions/getTokenAllowance' import { getSwapDepositContractFields } from '@/utils/getSwapDepositContractFields' import { calculatePriceImpact } from '@/utils/priceImpact' -import { formatBigIntToString } from '@/utils/bigint/format' -import { stringToBigInt } from '@/utils/bigint/format' +import { formatBigIntToString, stringToBigInt } from '@/utils/bigint/format' import { useSynapseContext } from '@/utils/providers/SynapseProvider' import { txErrorHandler } from '@/utils/txErrorHandler' - +import { isTransactionReceiptError } from '@/utils/isTransactionReceiptError' +import { isTransactionUserRejectedError } from '@/utils/isTransactionUserRejectedError' import { setInputValue, setWithdrawQuote, @@ -31,22 +24,31 @@ import { resetPoolWithdraw, } from '@/slices/poolWithdrawSlice' import { fetchPoolUserData } from '@/slices/poolUserDataSlice' - +import { fetchPoolData } from '@/slices/poolDataSlice' +import { fetchAndStoreSingleNetworkPortfolioBalances } from '@/slices/portfolio/hooks' +import { + usePoolDataState, + usePoolUserDataState, + usePoolWithdrawState, +} from '@/slices/pools/hooks' +import { Token } from '@types' +import RadioButton from '@components/buttons/RadioButton' +import ReceivedTokenSection from '../components/ReceivedTokenSection' +import PriceImpactDisplay from '../components/PriceImpactDisplay' import WithdrawButton from './WithdrawButton' const Withdraw = ({ address }: { address: string }) => { + const dispatch = useAppDispatch() + const { synapseSDK } = useSynapseContext() const [percentage, setPercentage] = useState(0) - const { pool, poolData } = useSelector((state: RootState) => state.poolData) - const { poolUserData } = useSelector((state: RootState) => state.poolUserData) - const { withdrawQuote, inputValue, withdrawType } = useSelector( - (state: RootState) => state.poolWithdraw - ) + + const { pool, poolData } = usePoolDataState() + const { poolUserData } = usePoolUserDataState() + const { withdrawQuote, inputValue, withdrawType } = usePoolWithdrawState() + const chainId = pool?.chainId const poolDecimals = pool?.decimals[pool?.chainId] const { poolAddress } = getSwapDepositContractFields(pool, chainId) - const { synapseSDK } = useSynapseContext() - - const dispatch: any = useDispatch() // An ETH swap pool has nativeTokens vs. most other pools have poolTokens const poolSpecificTokens = pool ? pool.nativeTokens ?? pool.poolTokens : [] @@ -197,6 +199,13 @@ const Withdraw = ({ address }: { address: string }) => { } } + const onResetWithdraw = () => { + dispatch(fetchPoolUserData({ pool, address: address as Address })) + dispatch(fetchPoolData({ poolName: String(pool.routerIndex) })) + dispatch(fetchAndStoreSingleNetworkPortfolioBalances({ address, chainId })) + dispatch(resetPoolWithdraw()) + } + const withdrawTxn = async () => { try { const tx = withdraw( @@ -209,15 +218,29 @@ const Withdraw = ({ address }: { address: string }) => { withdrawQuote.outputs ) - try { - await tx - dispatch(fetchPoolUserData({ pool, address: address as Address })) - dispatch(resetPoolWithdraw()) - } catch (error) { - txErrorHandler(error) + const resolvedTx = await tx + + if (isTransactionUserRejectedError(resolvedTx)) { + throw Error(resolvedTx) } + + await waitForTransaction({ + hash: resolvedTx?.transactionHash as Address, + timeout: 60_000, + }) + + onResetWithdraw() } catch (error) { + /** + * Assume transaction success if transaction receipt error + * Likely to be rpc related issue + */ + if (isTransactionReceiptError(error)) { + onResetWithdraw() + } txErrorHandler(error) + } finally { + dispatch(setIsLoading(false)) } } diff --git a/packages/synapse-interface/pages/pool/poolManagement/WithdrawButton.tsx b/packages/synapse-interface/pages/pool/poolManagement/WithdrawButton.tsx index 27293873bf..29ef872d4b 100644 --- a/packages/synapse-interface/pages/pool/poolManagement/WithdrawButton.tsx +++ b/packages/synapse-interface/pages/pool/poolManagement/WithdrawButton.tsx @@ -1,19 +1,21 @@ -import { useSelector } from 'react-redux' -import { TransactionButton } from '@/components/buttons/TransactionButton' -import { RootState } from '@/store/store' -import { useAccount, useNetwork, useSwitchNetwork } from 'wagmi' import { useEffect, useState } from 'react' +import { useAccount, useNetwork, useSwitchNetwork } from 'wagmi' import { useConnectModal } from '@rainbow-me/rainbowkit' +import { DEFAULT_WITHDRAW_QUOTE } from '@/slices/poolWithdrawSlice' +import { + usePoolDataState, + usePoolWithdrawState, + usePoolUserDataState, +} from '@/slices/pools/hooks' import { stringToBigInt } from '@/utils/bigint/format' +import { TransactionButton } from '@/components/buttons/TransactionButton' import LoadingDots from '@/components/ui/tailwind/LoadingDots' -import { DEFAULT_WITHDRAW_QUOTE } from '@/slices/poolWithdrawSlice' const WithdrawButton = ({ approveTxn, withdrawTxn, isApproved }) => { - const [isConnected, setIsConnected] = useState(false) // Initialize to false - const { openConnectModal } = useConnectModal() - const { chain } = useNetwork() const { chains, switchNetwork } = useSwitchNetwork() + const { openConnectModal } = useConnectModal() + const [isConnected, setIsConnected] = useState(false) // Initialize to false const { isConnected: isConnectedInit } = useAccount({ onDisconnect() { @@ -25,15 +27,12 @@ const WithdrawButton = ({ approveTxn, withdrawTxn, isApproved }) => { setIsConnected(isConnectedInit) }, [isConnectedInit]) - const { pool } = useSelector((state: RootState) => state.poolData) + const { pool } = usePoolDataState() + const { poolUserData } = usePoolUserDataState() + const { withdrawQuote, inputValue, isLoading } = usePoolWithdrawState() const poolDecimals = pool?.decimals[pool?.chainId] - const { withdrawQuote, inputValue, isLoading } = useSelector( - (state: RootState) => state.poolWithdraw - ) - const { poolUserData } = useSelector((state: RootState) => state.poolUserData) - const needsInput = stringToBigInt(inputValue, poolDecimals) === 0n const isBalanceEnough = diff --git a/packages/synapse-interface/pages/pool/poolManagement/index.tsx b/packages/synapse-interface/pages/pool/poolManagement/index.tsx index ba1f4b0853..ee7d5e8d74 100644 --- a/packages/synapse-interface/pages/pool/poolManagement/index.tsx +++ b/packages/synapse-interface/pages/pool/poolManagement/index.tsx @@ -1,16 +1,15 @@ import { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' import { Address } from 'wagmi' - -import { RootState } from '@/store/store' -import LiquidityManagementTabs from '../components/LiquidityManagementTabs' -import Deposit from './Deposit' -import Withdraw from './Withdraw' -import LoadingDots from '@/components/ui/tailwind/LoadingDots' +import { useAppDispatch } from '@/store/hooks' import { fetchPoolUserData, resetPoolUserData, } from '@/slices/poolUserDataSlice' +import { usePoolDataState, usePoolUserDataState } from '@/slices/pools/hooks' +import LoadingDots from '@/components/ui/tailwind/LoadingDots' +import LiquidityManagementTabs from '../components/LiquidityManagementTabs' +import Deposit from './Deposit' +import Withdraw from './Withdraw' const PoolManagement = ({ address, @@ -19,14 +18,11 @@ const PoolManagement = ({ address: Address chainId: number }) => { + const dispatch = useAppDispatch() const [cardNav, setCardNav] = useState(getLiquidityMode('#addLiquidity')) // 'addLiquidity' - const { pool } = useSelector((state: RootState) => state.poolData) - const { poolUserData, isLoading } = useSelector( - (state: RootState) => state.poolUserData - ) - - const dispatch: any = useDispatch() + const { pool } = usePoolDataState() + const { poolUserData, isLoading } = usePoolUserDataState() useEffect(() => { if (pool && address) { diff --git a/packages/synapse-interface/pages/pools/PoolCard.tsx b/packages/synapse-interface/pages/pools/PoolCard.tsx index a4c9b58982..d7d8e71bce 100644 --- a/packages/synapse-interface/pages/pools/PoolCard.tsx +++ b/packages/synapse-interface/pages/pools/PoolCard.tsx @@ -1,20 +1,19 @@ import _ from 'lodash' -import { useEffect, useMemo, useState, memo } from 'react' import Link from 'next/link' +import { useEffect, useMemo, useState, memo } from 'react' import { Address, useAccount } from 'wagmi' import { LoaderIcon, toast } from 'react-hot-toast' - -import { Token } from '@types' -import { STAKE_PATH, getPoolUrl } from '@urls' +import { useAppSelector } from '@/store/hooks' +import { usePortfolioState } from '@/slices/portfolio/hooks' import { getSinglePoolData } from '@utils/actions/getPoolData' import { getPoolApyData } from '@utils/actions/getPoolApyData' -import { usePortfolioState } from '@/slices/portfolio/hooks' import { getStakedBalance } from '@/utils/actions/getStakedBalance' import { formatBigIntToString } from '@/utils/bigint/format' import { PoolActionOptions } from '../../components/Pools/PoolActionOptions' import { PoolHeader } from '../../components/Pools/PoolHeader' import { PoolCardBody } from '../../components/Pools/PoolCardBody' -import { useAppSelector } from '@/store/hooks' +import { STAKE_PATH, getPoolUrl } from '@urls' +import { Token } from '@types' const PoolCard = memo(({ pool, address }: { pool: Token; address: string }) => { const [isClient, setIsClient] = useState(false) @@ -24,6 +23,7 @@ const PoolCard = memo(({ pool, address }: { pool: Token; address: string }) => { amount: 0n, reward: 0n, }) + const { isDisconnected } = useAccount() const { synPrices, ethPrice, avaxPrice, metisPrice } = useAppSelector( (state) => state.priceData @@ -211,7 +211,7 @@ export const DisplayBalances = ({ pool, stakedBalance, showIcon, address }) => { } return ( -
+
{showIcon && ( )} diff --git a/packages/synapse-interface/pages/pools/PoolCards.tsx b/packages/synapse-interface/pages/pools/PoolCards.tsx index 4556ee4649..6c612973a5 100644 --- a/packages/synapse-interface/pages/pools/PoolCards.tsx +++ b/packages/synapse-interface/pages/pools/PoolCards.tsx @@ -1,7 +1,6 @@ import React, { memo } from 'react' - -import PoolCard from './PoolCard' import { LoaderIcon } from 'react-hot-toast' +import PoolCard from './PoolCard' const PoolCards = memo( ({ pools, address }: { pools: any; address: string }) => { diff --git a/packages/synapse-interface/pages/pools/index.tsx b/packages/synapse-interface/pages/pools/index.tsx index 380476048d..e1e4e16b95 100644 --- a/packages/synapse-interface/pages/pools/index.tsx +++ b/packages/synapse-interface/pages/pools/index.tsx @@ -1,28 +1,19 @@ import _ from 'lodash' +import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import { useAccount, useNetwork } from 'wagmi' - -import { - DISPLAY_POOLS_BY_CHAIN, - USD_POOLS_BY_CHAIN, - ETH_POOLS_BY_CHAIN, - LEGACY_POOLS_BY_CHAIN, -} from '@constants/tokens' - -import StandardPageContainer from '@layouts/StandardPageContainer' -import { LandingPageWrapper } from '@layouts/LandingPageWrapper' +import { DISPLAY_POOLS_BY_CHAIN } from '@constants/tokens' import { DEFAULT_FROM_CHAIN } from '@/constants/swap' - -import PoolCards from './PoolCards' -import { useRouter } from 'next/router' - -import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider' -import { PageHeader } from '@/components/PageHeader' -import Grid from '@/components/ui/tailwind/Grid' import { METIS_POOL_SWAP_TOKEN_MIGRATED, METIS_WETH_SWAP_TOKEN_MIGRATED, } from '@/constants/tokens/poolMaster' +import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider' +import { LandingPageWrapper } from '@layouts/LandingPageWrapper' +import StandardPageContainer from '@layouts/StandardPageContainer' +import { PageHeader } from '@/components/PageHeader' +import Grid from '@/components/ui/tailwind/Grid' +import PoolCards from './PoolCards' import * as CHAINS from '@/constants/chains/master' const PoolsPage = () => { @@ -64,6 +55,7 @@ const PoolsPage = () => { useEffect(() => { setConnectedChainId(chain?.id ?? DEFAULT_FROM_CHAIN) }, [chain]) + useEffect(() => { setAddress(currentAddress) }, [currentAddress]) diff --git a/packages/synapse-interface/pages/stake/StakeCard.tsx b/packages/synapse-interface/pages/stake/StakeCard.tsx index 87f6801621..06847657a8 100644 --- a/packages/synapse-interface/pages/stake/StakeCard.tsx +++ b/packages/synapse-interface/pages/stake/StakeCard.tsx @@ -1,6 +1,7 @@ -import { useState, useEffect, useMemo } from 'react' +import { useState, useEffect } from 'react' import { Address } from '@wagmi/core' - +import { useAppDispatch } from '@/store/hooks' +import { fetchAndStoreSingleNetworkPortfolioBalances } from '@/slices/portfolio/hooks' import { usePendingTxWrapper } from '@/utils/hooks/usePendingTxWrapper' import { getTokenAllowance } from '@/utils/actions/getTokenAllowance' import { getStakedBalance } from '@/utils/actions/getStakedBalance' @@ -10,19 +11,17 @@ import { withdrawStake } from '@/utils/actions/withdrawStake' import { getTokenOnChain } from '@/utils/hooks/useTokenInfo' import { cleanNumberInput } from '@/utils/cleanNumberInput' import { claimStake } from '@/utils/actions/claimStake' +import { formatBigIntToString } from '@/utils/bigint/format' +import { stringToBigInt } from '@/utils/bigint/format' import { Token } from '@/utils/types' - +import { InteractiveInputRowButton } from '@/components/InteractiveInputRowButton' import ButtonLoadingDots from '@/components/buttons/ButtonLoadingDots' import InteractiveInputRow from '@/components/InteractiveInputRow' import Button from '@/components/ui/tailwind/Button' - -import StakeCardTitle from './StakeCardTitle' -import { formatBigIntToString } from '@/utils/bigint/format' -import { stringToBigInt } from '@/utils/bigint/format' -import InfoSectionCard from '../pool/PoolInfoSection/InfoSectionCard' -import Tabs from '@/components/ui/tailwind/Tabs' import TabItem from '@/components/ui/tailwind/TabItem' -import { InteractiveInputRowButton } from '@/components/InteractiveInputRowButton' +import Tabs from '@/components/ui/tailwind/Tabs' +import InfoSectionCard from '../pool/PoolInfoSection/InfoSectionCard' +import StakeCardTitle from './StakeCardTitle' interface StakeCardProps { address: string @@ -31,6 +30,7 @@ interface StakeCardProps { } const StakeCard = ({ address, chainId, pool }: StakeCardProps) => { + const dispatch = useAppDispatch() const tokenInfo = getTokenOnChain(chainId, pool) const stakingPoolLabel: string = tokenInfo?.poolName const stakingPoolTokens: Token[] = tokenInfo?.poolTokens @@ -52,63 +52,71 @@ const StakeCard = ({ address, chainId, pool }: StakeCardProps) => { amount: 0n, reward: 0n, }) - const [tx, setTx] = useState(undefined) const miniChefAddress = pool.miniChefAddress + const resetUserStakeData = () => { + setUserStakeData({ + amount: 0n, + reward: 0n, + }) + } + + const getUserStakedBalance = async ( + address: Address, + stakingPoolId: number, + pool: Token + ) => { + try { + const data = await getStakedBalance( + address as Address, + pool.chainId, + stakingPoolId, + pool + ) + setUserStakeData(data) + } catch (err) { + console.error('Error fetching user staked balance:', err) + } + } + + const getUserLpTokenAllowance = async ( + address: Address, + chainId: number, + pool: Token + ) => { + try { + const tkAllowance = await getTokenAllowance( + miniChefAddress as Address, + pool.addresses[chainId] as Address, + address as Address, + chainId + ) + setAllowance(tkAllowance) + } catch (err) { + console.error('Error fetching user LP token allowance:', err) + } + } + useEffect(() => { if (!address || !chainId || stakingPoolId === null) { - setUserStakeData({ - amount: 0n, - reward: 0n, - }) + resetUserStakeData() return } - getStakedBalance(address as Address, pool.chainId, stakingPoolId, pool) - .then((data) => { - setUserStakeData(data) - }) - .catch((err) => { - console.log(err) - }) + getUserStakedBalance(address as Address, stakingPoolId, pool) }, [address, chainId, stakingPoolId]) useEffect(() => { if (!address) { - setUserStakeData({ - amount: 0n, - reward: 0n, - }) + resetUserStakeData() return } - ;(async () => { - const tkAllowance = await getTokenAllowance( - miniChefAddress as Address, - pool.addresses[chainId] as Address, - address as Address, - chainId - ) - setAllowance(tkAllowance) - getStakedBalance(address as Address, pool.chainId, stakingPoolId, pool) - .then((data) => { - setUserStakeData(data) - }) - .catch((err) => { - console.log(err) - }) - })() + getUserLpTokenAllowance(address as Address, chainId, pool) + getUserStakedBalance(address as Address, stakingPoolId, pool) }, [lpTokenBalance]) useEffect(() => { if (!address) return - ;(async () => { - const tkAllowance = await getTokenAllowance( - miniChefAddress as Address, - pool.addresses[chainId] as Address, - address as Address, - chainId - ) - setAllowance(tkAllowance) - })() + getUserLpTokenAllowance(address as Address, chainId, pool) }, [deposit]) return ( @@ -164,16 +172,23 @@ const StakeCard = ({ address, chainId, pool }: StakeCardProps) => { bg-[#564f58] w-full my-2 px-4 py-3 tracking-wide rounded-sm - border border-transparent - hover:border-[#AC8FFF] + border border-transparent + hover:border-[#AC8FFF] disabled:opacity-100 disabled:from-bgLight disabled:to-bgLight `} - onClick={() => - pendingTxWrapFunc( + onClick={async (e) => { + const tx = await pendingTxWrapFunc( claimStake(chainId, address as Address, stakingPoolId, pool) ) - } + if (tx?.status === 'success') { + await getUserStakedBalance( + address as Address, + stakingPoolId, + pool + ) + } + }} > {isPending ? (
@@ -294,8 +309,13 @@ const StakeCard = ({ address, chainId, pool }: StakeCardProps) => { const tx = await pendingApproveTxWrapFunc( approve(pool, deposit.bi, chainId) ) - - setTx(tx?.transactionHash) + if (tx?.status === 'success') { + getUserLpTokenAllowance( + address as Address, + chainId, + pool + ) + } } : async (e) => { const tx = await pendingStakeTxWrapFunc( @@ -309,8 +329,13 @@ const StakeCard = ({ address, chainId, pool }: StakeCardProps) => { ) if (tx?.status === 'success') { setDeposit({ bi: 0n, str: '' }) + dispatch( + fetchAndStoreSingleNetworkPortfolioBalances({ + address, + chainId, + }) + ) } - setTx(tx?.transactionHash) } } /> @@ -333,6 +358,12 @@ const StakeCard = ({ address, chainId, pool }: StakeCardProps) => { ) if (tx?.status === 1) { setWithdraw('') + dispatch( + fetchAndStoreSingleNetworkPortfolioBalances({ + address, + chainId, + }) + ) } }} /> diff --git a/packages/synapse-interface/pages/state-managed-bridge/index.tsx b/packages/synapse-interface/pages/state-managed-bridge/index.tsx index beaf7f5007..534b00f88f 100644 --- a/packages/synapse-interface/pages/state-managed-bridge/index.tsx +++ b/packages/synapse-interface/pages/state-managed-bridge/index.tsx @@ -79,6 +79,12 @@ import { FromTokenListOverlay } from '@/components/StateManagedBridge/FromTokenL import { ToTokenListOverlay } from '@/components/StateManagedBridge/ToTokenListOverlay' import { waitForTransaction } from '@wagmi/core' +import { + fetchArbPrice, + fetchEthPrice, + fetchGmxPrice, +} from '@/slices/priceDataSlice' +import { isTransactionReceiptError } from '@/utils/isTransactionReceiptError' import { SwitchButton } from '@/components/buttons/SwitchButton' const StateManagedBridge = () => { @@ -472,7 +478,7 @@ const StateManagedBridge = () => { const transactionReceipt = await waitForTransaction({ hash: tx as Address, - timeout: 30_000, + timeout: 60_000, }) console.log('Transaction Receipt: ', transactionReceipt) @@ -494,6 +500,17 @@ const StateManagedBridge = () => { dispatch(removePendingBridgeTransaction(currentTimestamp)) console.log('Error executing bridge', error) toast.dismiss(pendingPopup) + + /** Fetch balances if await transaction receipt times out */ + if (isTransactionReceiptError(error)) { + dispatch( + fetchAndStoreSingleNetworkPortfolioBalances({ + address, + chainId: fromChainId, + }) + ) + } + return txErrorHandler(error) } } diff --git a/packages/synapse-interface/pages/swap/index.tsx b/packages/synapse-interface/pages/swap/index.tsx index 448bcf9977..3cbdedae52 100644 --- a/packages/synapse-interface/pages/swap/index.tsx +++ b/packages/synapse-interface/pages/swap/index.tsx @@ -15,7 +15,7 @@ import { formatBigIntToString } from '@/utils/bigint/format' import { calculateExchangeRate } from '@/utils/calculateExchangeRate' import { useEffect, useRef, useState } from 'react' import { Token } from '@/utils/types' -import { getWalletClient } from '@wagmi/core' +import { getWalletClient, waitForTransaction } from '@wagmi/core' import { txErrorHandler } from '@/utils/txErrorHandler' import { CHAINS_BY_ID } from '@/constants/chains' import { approveToken } from '@/utils/approveToken' @@ -30,7 +30,10 @@ import ExplorerToastLink from '@/components/ExplorerToastLink' import { Address, zeroAddress } from 'viem' import { stringToBigInt } from '@/utils/bigint/format' import { useAppDispatch } from '@/store/hooks' -import { useFetchPortfolioBalances } from '@/slices/portfolio/hooks' +import { + useFetchPortfolioBalances, + fetchAndStoreSingleNetworkPortfolioBalances, +} from '@/slices/portfolio/hooks' import { SwapTransactionButton } from '@/components/StateManagedSwap/SwapTransactionButton' import SwapExchangeRateInfo from '@/components/StateManagedSwap/SwapExchangeRateInfo' import { useSwapState } from '@/slices/swap/hooks' @@ -43,6 +46,7 @@ import { DEFAULT_FROM_CHAIN, EMPTY_SWAP_QUOTE_ZERO } from '@/constants/swap' import { SwapToTokenListOverlay } from '@/components/StateManagedSwap/SwapToTokenListOverlay' import { LandingPageWrapper } from '@/components/layouts/LandingPageWrapper' import useSyncQueryParamsWithSwapState from '@/utils/hooks/useSyncQueryParamsWithSwapState' +import { isTransactionReceiptError } from '@/utils/isTransactionReceiptError' const StateManagedSwap = () => { const { address } = useAccount() @@ -237,6 +241,16 @@ const StateManagedSwap = () => { } } + const onSuccessSwap = () => { + dispatch( + fetchAndStoreSingleNetworkPortfolioBalances({ + address, + chainId: swapChainId, + }) + ) + dispatch(setSwapQuote(EMPTY_SWAP_QUOTE_ZERO)) + dispatch(updateSwapFromValue('')) + } const executeSwap = async () => { const currentChainName = CHAINS_BY_ID[swapChainId]?.name @@ -293,46 +307,51 @@ const StateManagedSwap = () => { { id: 'swap-in-progress-popup', duration: Infinity } ) - try { - const successTx = await tx + const transactionReceipt = await waitForTransaction({ + hash: tx as Address, + timeout: 60_000, + }) + console.log('Transaction Receipt: ', transactionReceipt) + + onSuccessSwap() - segmentAnalyticsEvent(`[Swap] swaps successfully`, { - address, - originChainId: swapChainId, - inputAmount: swapFromValue, - expectedReceivedAmount: swapQuote.outputAmountString, - exchangeRate: swapQuote.exchangeRate, - }) + segmentAnalyticsEvent(`[Swap] swaps successfully`, { + address, + originChainId: swapChainId, + inputAmount: swapFromValue, + expectedReceivedAmount: swapQuote.outputAmountString, + exchangeRate: swapQuote.exchangeRate, + }) - toast.dismiss(pendingPopup) + toast.dismiss(pendingPopup) - const successToastContent = ( + const successToastContent = ( +
-
- Successfully swapped from {swapFromToken.symbol} to{' '} - {swapToToken.symbol} on {currentChainName} -
- + Successfully swapped from {swapFromToken.symbol} to{' '} + {swapToToken.symbol} on {currentChainName}
- ) + +
+ ) - toast.success(successToastContent, { - id: 'swap-successful-popup', - duration: 10000, - }) + toast.success(successToastContent, { + id: 'swap-successful-popup', + duration: 10000, + }) - dispatch(setSwapQuote(EMPTY_SWAP_QUOTE_ZERO)) - dispatch(updateSwapFromValue()) - return tx - } catch (error) { - toast.dismiss(pendingPopup) - console.log(`Transaction failed with error: ${error}`) - } + return tx } catch (error) { console.log(`Swap Execution failed with error: ${error}`) + + /** Assume successful swap tx if await transaction receipt times out */ + if (isTransactionReceiptError(error)) { + onSuccessSwap() + } + toast.dismiss(pendingPopup) txErrorHandler(error) } diff --git a/packages/synapse-interface/slices/poolDataSlice.ts b/packages/synapse-interface/slices/poolDataSlice.ts index 01954d6ccc..199cba991a 100644 --- a/packages/synapse-interface/slices/poolDataSlice.ts +++ b/packages/synapse-interface/slices/poolDataSlice.ts @@ -38,7 +38,7 @@ const initialState: PoolDataState = { weeklyAPR: undefined, yearlyAPRUnvested: undefined, }, - isLoading: false, + isLoading: true, } export const fetchPoolData = createAsyncThunk( diff --git a/packages/synapse-interface/slices/poolUserDataSlice.ts b/packages/synapse-interface/slices/poolUserDataSlice.ts index b31a717459..07a2847af4 100644 --- a/packages/synapse-interface/slices/poolUserDataSlice.ts +++ b/packages/synapse-interface/slices/poolUserDataSlice.ts @@ -3,6 +3,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { Address } from '@wagmi/core' import { getBalanceData } from '@/utils/actions/getPoolData' +import { getStakedBalance } from '@/utils/actions/getStakedBalance' export interface PoolDataState { poolUserData: PoolUserData @@ -15,6 +16,10 @@ const initialState: PoolDataState = { tokens: undefined, lpTokenBalance: undefined, nativeTokens: undefined, + stakedBalance: { + amount: 0n, + reward: 0n, + }, }, isLoading: false, } @@ -40,10 +45,18 @@ export const fetchPoolUserData = createAsyncThunk( const tokens = tokenBalances.filter((token) => !token.isLP) + const stakedBalance = await getStakedBalance( + address, + pool.chainId, + pool.poolId[pool.chainId], + pool + ) + return { name: pool.name, tokens, lpTokenBalance, + stakedBalance, nativeTokens: pool.nativeTokens, } } diff --git a/packages/synapse-interface/slices/pools/hooks.ts b/packages/synapse-interface/slices/pools/hooks.ts new file mode 100644 index 0000000000..bd549dd577 --- /dev/null +++ b/packages/synapse-interface/slices/pools/hooks.ts @@ -0,0 +1,18 @@ +import { RootState } from '@/store/store' +import { useAppSelector } from '@/store/hooks' + +export const usePoolDataState = (): RootState['poolData'] => { + return useAppSelector((state: RootState) => state.poolData) +} + +export const usePoolUserDataState = (): RootState['poolUserData'] => { + return useAppSelector((state: RootState) => state.poolUserData) +} + +export const usePoolWithdrawState = (): RootState['poolWithdraw'] => { + return useAppSelector((state: RootState) => state.poolWithdraw) +} + +export const usePoolDepositState = (): RootState['poolDeposit'] => { + return useAppSelector((state: RootState) => state.poolDeposit) +} diff --git a/packages/synapse-interface/slices/portfolio/reducer.ts b/packages/synapse-interface/slices/portfolio/reducer.ts index 262ef2b24e..bacbdbe02a 100644 --- a/packages/synapse-interface/slices/portfolio/reducer.ts +++ b/packages/synapse-interface/slices/portfolio/reducer.ts @@ -85,11 +85,16 @@ export const portfolioSlice = createSlice({ .addCase( fetchAndStoreSingleNetworkPortfolioBalances.fulfilled, (state, action) => { - const { balances } = action.payload + const { balances, poolTokenBalances } = action.payload Object.entries(balances).forEach(([chainId, tokenBalances]) => { state.balances[chainId] = [...tokenBalances] }) + Object.entries(poolTokenBalances).forEach( + ([chainId, tokenBalances]) => { + state.poolTokenBalances[chainId] = [...tokenBalances] + } + ) } ) .addCase(resetSearchState, (state) => { diff --git a/packages/synapse-interface/utils/hooks/usePendingTxWrapper.tsx b/packages/synapse-interface/utils/hooks/usePendingTxWrapper.tsx index 516e098b60..169844664c 100644 --- a/packages/synapse-interface/utils/hooks/usePendingTxWrapper.tsx +++ b/packages/synapse-interface/utils/hooks/usePendingTxWrapper.tsx @@ -22,6 +22,6 @@ export function usePendingTxWrapper(): usePendingTxWrapperReturnType { } return tx } - // return { isPending, pendingTxWrapFunc } + return [isPending, pendingTxWrapFunc] } diff --git a/packages/synapse-interface/utils/isTransactionReceiptError.ts b/packages/synapse-interface/utils/isTransactionReceiptError.ts new file mode 100644 index 0000000000..877fb6e850 --- /dev/null +++ b/packages/synapse-interface/utils/isTransactionReceiptError.ts @@ -0,0 +1,27 @@ +import { + WaitForTransactionReceiptTimeoutError, + TransactionNotFoundError, +} from 'viem' + +export const isTransactionReceiptError = (error: unknown): boolean => { + if (error instanceof WaitForTransactionReceiptTimeoutError) { + return true + } + + if (error instanceof TransactionNotFoundError) { + return true + } + + // If the error type check is not possible or not specific enough, use properties or regex + if (typeof error === 'object' && error !== null) { + const message = (error as { message?: string }).message + + if (typeof message === 'string') { + const regex = + /Timed out while waiting for transaction with hash "0x[0-9a-fA-F]+" | Transaction with hash "0x[0-9a-fA-F]+" could not be found\./ + return regex.test(message) + } + } + + return false +} diff --git a/packages/synapse-interface/utils/isTransactionUserRejectedError.ts b/packages/synapse-interface/utils/isTransactionUserRejectedError.ts new file mode 100644 index 0000000000..f58ba76a16 --- /dev/null +++ b/packages/synapse-interface/utils/isTransactionUserRejectedError.ts @@ -0,0 +1,19 @@ +import { UserRejectedRequestError } from 'viem' + +export const isTransactionUserRejectedError = (error: unknown): boolean => { + if (error instanceof UserRejectedRequestError) { + return true + } + + // If the error type check is not possible or not specific enough, use properties or regex + if (typeof error === 'object' && error !== null) { + const message = (error as { message?: string }).message + + if (typeof message === 'string') { + const regex = /User rejected the request/ + return regex.test(message) + } + } + + return false +} diff --git a/packages/synapse-interface/utils/types/index.tsx b/packages/synapse-interface/utils/types/index.tsx index e897feac2f..0e95aa25d5 100644 --- a/packages/synapse-interface/utils/types/index.tsx +++ b/packages/synapse-interface/utils/types/index.tsx @@ -39,6 +39,10 @@ export type PoolUserData = { name: string tokens: PoolToken[] lpTokenBalance: bigint + stakedBalance: { + amount: bigint + reward: bigint + } nativeTokens?: any } export type PoolData = {