import _ from 'lodash' import Grid from '@tw/Grid' import Slider from 'react-input-slider' import { useEffect, useMemo, useState } from 'react' import { waitForTransactionReceipt } from '@wagmi/core' import { type Address } from 'viem' import { useTranslations } from 'next-intl' import { useAppDispatch } from '@/store/hooks' import { getCoinTextColorCombined } from '@styles/tokens' import { ALL } from '@constants/withdrawTypes' import { WithdrawTokenInput } from '@components/TokenInput' 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, 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, setWithdrawType, setIsLoading, 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' import { wagmiConfig } from '@/wagmiConfig' const Withdraw = ({ address }: { address: string }) => { const dispatch = useAppDispatch() const { synapseSDK } = useSynapseContext() const [percentage, setPercentage] = useState(0) const t = useTranslations('Pools') 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) // An ETH swap pool has nativeTokens vs. most other pools have poolTokens const poolSpecificTokens = pool ? pool.nativeTokens ?? pool.poolTokens : [] const isApproved = useMemo(() => { return ( withdrawQuote?.allowance && stringToBigInt(inputValue, poolDecimals) <= withdrawQuote.allowance ) }, [inputValue, withdrawQuote]) const calculateMaxWithdraw = async () => { if (poolUserData === null || address === null) { return } dispatch(setIsLoading(true)) try { const outputs: Record< string, { value: bigint index: number } > = {} const { virtualPrice } = poolData if (withdrawType === ALL) { const { amounts } = await synapseSDK.calculateRemoveLiquidity( chainId, poolAddress, stringToBigInt(inputValue, poolDecimals) ) outputs[withdrawType] = amounts.map(transformAmount) } else { const { amount } = await synapseSDK.calculateRemoveLiquidityOne( chainId, poolAddress, stringToBigInt(inputValue, poolDecimals), Number(withdrawType) ) outputs[withdrawType] = transformAmount(amount) } const outputTokensSum = sumBigInts(pool, outputs, withdrawType) const priceImpact = calculatePriceImpact( stringToBigInt(inputValue, poolDecimals), outputTokensSum, virtualPrice, true ) const allowance = await getTokenAllowance( poolAddress, pool.addresses[chainId] as Address, address as Address, chainId ) dispatch( setWithdrawQuote({ priceImpact, allowance, outputs, }) ) dispatch(setIsLoading(false)) } catch (e) { dispatch(setIsLoading(false)) console.log(e) } } useEffect(() => { if ( poolUserData && poolData && address && pool && stringToBigInt(inputValue, poolDecimals) > 0n ) { calculateMaxWithdraw() } }, [inputValue, withdrawType]) const onPercentChange = (percent: number) => { if (percent > 100) { percent = 100 } setPercentage(percent) const numericalOut: string = poolUserData.lpTokenBalance ? formatBigIntToString( (poolUserData.lpTokenBalance * BigInt(percent)) / BigInt(100), poolDecimals ) : '' dispatch(setInputValue(numericalOut)) } const onChangeInputValue = (token: Token, value: string) => { const bigInt = stringToBigInt(value, token.decimals[chainId]) if (poolUserData.lpTokenBalance === 0n) { dispatch(setInputValue(value)) setPercentage(0) return } const pn = bigInt ? Number( (bigInt * BigInt(100)) / BigInt(poolUserData.lpTokenBalance.toString()) ) : 0 dispatch(setInputValue(value)) if (pn > 100) { setPercentage(100) } else { setPercentage(pn) } } const approveTxn = async () => { try { const tx = approve( pool, withdrawQuote, stringToBigInt(inputValue, poolDecimals), chainId ) try { await tx calculateMaxWithdraw() } catch (error) { txErrorHandler(error) } } catch (error) { txErrorHandler(error) } } 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( pool, 'ONE_TENTH', null, stringToBigInt(inputValue, poolDecimals), chainId, withdrawType, withdrawQuote.outputs ) const resolvedTx = await tx if (isTransactionUserRejectedError(resolvedTx)) { throw Error(resolvedTx) } await waitForTransactionReceipt(wagmiConfig, { 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)) } } return ( pool && (
{t('Withdraw Percentage')} % onPercentChange(Number(e.currentTarget.value))} onFocus={(e) => e.target.select()} value={percentage ?? ''} />
{/* @ts-ignore */} { onPercentChange(i.x) }} styles={{ track: { backgroundColor: '#E0E7FF', width: '95%', }, active: { backgroundColor: '#B286FF', }, thumb: { backgroundColor: '#CE55FE', }, }} />
{ dispatch(setWithdrawType(ALL)) }} label="Combo" labelClassName={withdrawType === ALL && 'text-indigo-500'} /> {poolSpecificTokens && poolSpecificTokens.map((poolSpecificToken, index) => { const checked = withdrawType === index.toString() return ( { dispatch(setWithdrawType(index.toString())) }} labelClassName={ checked && `${getCoinTextColorCombined( poolSpecificToken.color )} opacity-90` } label={poolSpecificToken.name} /> ) })} onChangeInputValue(pool, value)} />
{stringToBigInt(inputValue, poolDecimals) > 0n && (
{withdrawQuote.priceImpact && ( )}
)}
) ) } const sumBigInts = ( pool: Token, bigIntMap: Record, withdrawType: string ) => { if (!pool?.poolTokens) { return 0n } const chainId = pool.chainId const currentTokens = withdrawType === ALL ? bigIntMap[withdrawType] : bigIntMap return pool.poolTokens.reduce((sum, token, index) => { if (!currentTokens[index]) { return sum } // Compute the power of 10 using pow10BigInt function const scaleFactor = pow10BigInt( BigInt(18) - BigInt(token.decimals[chainId]) ) const valueToAdd = currentTokens[index].value * scaleFactor return sum + valueToAdd }, 0n) } const pow10BigInt = (n: bigint) => { let result = 1n for (let i = 0n; i < n; i++) { result *= 10n } return result } const transformAmount = (amount) => { return { value: BigInt(amount.value.toString()), index: amount.index, } } const poolTokenByIndex = (poolTokens: Token[], index: number) => { return poolTokens.find((_poolToken, idx) => index === idx) } export default Withdraw