diff --git a/apps/frontend-v3/app/api/rpc/[chain]/route.ts b/apps/frontend-v3/app/api/rpc/[chain]/route.ts index cbd60b37a..204f256b3 100644 --- a/apps/frontend-v3/app/api/rpc/[chain]/route.ts +++ b/apps/frontend-v3/app/api/rpc/[chain]/route.ts @@ -19,6 +19,8 @@ const chainToRpcMap: Record = { [GqlChain.Avalanche]: dRpcUrl('avalanche'), [GqlChain.Fantom]: dRpcUrl('fantom'), [GqlChain.Sepolia]: dRpcUrl('sepolia'), + // DEBUG: + // [GqlChain.Sepolia]: 'https://eth-sepolia.g.alchemy.com/v2/', [GqlChain.Fraxtal]: dRpcUrl('fraxtal'), [GqlChain.Gnosis]: dRpcUrl('gnosis'), [GqlChain.Mode]: dRpcUrl('mode'), diff --git a/packages/lib/modules/pool/PoolDetail/PoolMyLiquidity.tsx b/packages/lib/modules/pool/PoolDetail/PoolMyLiquidity.tsx index b3082bc27..4602b474e 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolMyLiquidity.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolMyLiquidity.tsx @@ -17,7 +17,7 @@ import { Tooltip, useDisclosure, } from '@chakra-ui/react' -import React, { useMemo, useState, useLayoutEffect } from 'react' +import { useMemo, useState, useLayoutEffect } from 'react' import { usePool } from '../PoolProvider' import { Address } from 'viem' import { usePathname, useRouter } from 'next/navigation' diff --git a/packages/lib/modules/pool/actions/LiquidityActionHelpers.integration.spec.ts b/packages/lib/modules/pool/actions/LiquidityActionHelpers.integration.spec.ts index eef8b9171..ed14f9c2c 100644 --- a/packages/lib/modules/pool/actions/LiquidityActionHelpers.integration.spec.ts +++ b/packages/lib/modules/pool/actions/LiquidityActionHelpers.integration.spec.ts @@ -14,6 +14,7 @@ import { GqlChain, GqlPoolElement } from '@repo/lib/shared/services/api/generate import { getPoolMock } from '../__mocks__/getPoolMock' import { allPoolTokens } from '../pool.helpers' import { LiquidityActionHelpers } from './LiquidityActionHelpers' +import { Pool } from '../PoolProvider' describe('Calculates toInputAmounts from allPoolTokens', () => { it('for v2 weighted pool with no nested tokens', async () => { @@ -293,3 +294,163 @@ describe.skip('Liquidity helpers for V3 NESTED pool', async () => { ]) }) }) + +// Unskip when sepolia V3 pools are available in production api +test.skip('Nested pool state for V3 BOOSTED POOL', async () => { + // const poolId = '0xbb83ba331c3254c8c44645430126797dceda89c0' // Sepolia Balancer 50 WETH 50 stataUSDC + + // const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia) + const v3Pool = {} as Pool + + const helpers = new LiquidityActionHelpers(v3Pool) + + const state = helpers.boostedPoolState + + expect(state).toMatchInlineSnapshot(` + { + "address": "0xbb83ba331c3254c8c44645430126797dceda89c0", + "id": "0xbb83ba331c3254c8c44645430126797dceda89c0", + "protocolVersion": 3, + "tokens": [ + { + "address": "0x7b79995e5f793a07bc00c21412e50ecae098e7f9", + "balance": "10.030375954528889", + "balanceUSD": "35947.96468719563", + "decimals": 18, + "erc4626ReviewData": null, + "hasNestedPool": false, + "id": "0xbb83ba331c3254c8c44645430126797dceda89c0-0x7b79995e5f793a07bc00c21412e50ecae098e7f9", + "index": 0, + "isAllowed": true, + "isErc4626": false, + "name": "Wrapped Ether", + "nestedPool": null, + "priceRate": "1", + "priceRateProvider": "0x0000000000000000000000000000000000000000", + "priceRateProviderData": null, + "symbol": "WETH", + "underlyingToken": null, + "weight": "0.5", + }, + { + "address": "0x8a88124522dbbf1e56352ba3de1d9f78c143751e", + "balance": "25922.046716", + "balanceUSD": "30847.23559204", + "decimals": 6, + "erc4626ReviewData": null, + "hasNestedPool": false, + "id": "0xbb83ba331c3254c8c44645430126797dceda89c0-0x8a88124522dbbf1e56352ba3de1d9f78c143751e", + "index": 1, + "isAllowed": true, + "isErc4626": true, + "name": "Static Aave Ethereum USDC", + "nestedPool": null, + "priceRate": "1.188770181245210492", + "priceRateProvider": "0x34101091673238545de8a846621823d9993c3085", + "priceRateProviderData": { + "address": "0x34101091673238545de8a846621823d9993c3085", + "factory": null, + "name": "waUSDC Rate Provider", + "reviewFile": "./StatATokenTestnetRateProvider.md", + "reviewed": true, + "summary": "safe", + "upgradeableComponents": [], + "warnings": [ + "", + ], + }, + "symbol": "stataEthUSDC", + "underlyingToken": { + "address": "0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8", + "chainId": 11155111, + "decimals": 6, + "index": 1, + "name": "USDC (AAVE Faucet)", + "symbol": "usdc-aave", + }, + "weight": "0.5", + }, + ], + "totalShares": "555.900851855167757901", + "type": "Weighted", + } + `) +}) + +// Unskip when sepolia V3 pools are available in production api +test.skip('Nested pool state for V3 NESTED POOL', async () => { + // const poolId = '0xc9233cc69435591b193b50f702ac31e404a08b10' // Sepolia Balancer 50 WETH 50 USD + + const usdcSepoliaAddress = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8' + const daiSepoliaAddress = '0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357' + const wethSepoliaAddress = '0x7b79995e5f793a07bc00c21412e50ecae098e7f9' + // const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia) + const v3Pool = {} as Pool + + const helpers = new LiquidityActionHelpers(v3Pool) + + const state = helpers.nestedPoolStateV3 + + expect(state).toEqual({ + mainTokens: [ + { + address: wethSepoliaAddress, + decimals: 18, + index: 0, + }, + { + address: usdcSepoliaAddress, + decimals: 6, + index: 0, + }, + { + address: daiSepoliaAddress, + decimals: 18, + index: 1, + }, + ], + pools: [ + { + address: '0xc9233cc69435591b193b50f702ac31e404a08b10', + id: '0xc9233cc69435591b193b50f702ac31e404a08b10', + level: 1, + tokens: [ + { + address: wethSepoliaAddress, + decimals: 18, + index: 0, + underlyingToken: null, + }, + { + address: '0x946e59e9637f44eb122fe64b372aaf6ed0441da1', + decimals: 18, + index: 1, + underlyingToken: null, + }, + ], + type: 'Weighted', + }, + { + address: '0x946e59e9637f44eb122fe64b372aaf6ed0441da1', + id: '0x946e59e9637f44eb122fe64b372aaf6ed0441da1', + level: 0, + tokens: [ + { + address: usdcSepoliaAddress, + decimals: 6, + index: 0, + underlyingToken: null, + }, + { + address: daiSepoliaAddress, + decimals: 18, + index: 1, + underlyingToken: null, + }, + ], + type: 'Weighted', + }, + ], + protocolVersion: 3, + }) +}) diff --git a/packages/lib/modules/pool/actions/LiquidityActionHelpers.ts b/packages/lib/modules/pool/actions/LiquidityActionHelpers.ts index 761d37902..629d622ab 100644 --- a/packages/lib/modules/pool/actions/LiquidityActionHelpers.ts +++ b/packages/lib/modules/pool/actions/LiquidityActionHelpers.ts @@ -43,7 +43,7 @@ import { isUnbalancedLiquidityDisabled, isV2Pool, isV3Pool, - isV3WithNestedActionsPool, + isV3NotSupportingWethIsEth, } from '../pool.helpers' import { TokenAmountIn } from '../../tokens/approvals/permit2/useSignPermit2' @@ -84,16 +84,18 @@ export class LiquidityActionHelpers { /* Used by V3 boosted SDK handlers */ public get boostedPoolState(): PoolStateWithUnderlyings & { totalShares: HumanAmount } { const poolTokensWithUnderlyings: PoolTokenWithUnderlying[] = this.pool.poolTokens.map( - (token, index) => ({ + token => ({ ...token, address: token.address as Address, balance: token.balance as HumanAmount, - underlyingToken: { - ...token.underlyingToken, - address: token.underlyingToken?.address as Address, - decimals: token.underlyingToken?.decimals as number, - index, //TODO: review that this index is always the expected one - }, + underlyingToken: token.underlyingToken?.address + ? { + ...token.underlyingToken, + address: token.underlyingToken?.address as Address, + decimals: token.underlyingToken?.decimals as number, + index: token.index, //TODO: review that this index is always the expected one + } + : null, }) ) const state: PoolStateWithUnderlyings & { totalShares: HumanAmount } = { @@ -359,7 +361,7 @@ export function emptyTokenAmounts(pool: Pool): TokenAmount[] { export function shouldShowNativeWrappedSelector(token: GqlToken, pool: Pool) { return ( - !isV3WithNestedActionsPool(pool) && // V3 nested actions don't support wethIsEth currently + !isV3NotSupportingWethIsEth(pool) && // V3 boosted/nested actions don't support wethIsEth currently !isCowAmmPool(pool.type) && // Cow AMM pools don't support wethIsEth isNativeOrWrappedNative(token.address as Address, token.chain) ) diff --git a/packages/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider.tsx b/packages/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider.tsx index c251f2266..63eee9949 100644 --- a/packages/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider.tsx +++ b/packages/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider.tsx @@ -3,7 +3,7 @@ import { useTokens } from '@repo/lib/modules/tokens/TokensProvider' import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { HumanAmount } from '@balancer/sdk' +import { HumanAmount, isSameAddress } from '@balancer/sdk' import { PropsWithChildren, createContext, useEffect, useMemo, useState } from 'react' import { Address, Hash } from 'viem' import { usePool } from '../../PoolProvider' @@ -28,7 +28,7 @@ import { useTotalUsdValue } from '@repo/lib/modules/tokens/useTotalUsdValue' import { HumanTokenAmountWithAddress } from '@repo/lib/modules/tokens/token.types' import { isUnhandledAddPriceImpactError } from '@repo/lib/modules/price-impact/price-impact.utils' import { useModalWithPoolRedirect } from '../../useModalWithPoolRedirect' -import { getPoolActionableTokens, isV3WithNestedActionsPool } from '../../pool.helpers' +import { getPoolActionableTokens, isV3NotSupportingWethIsEth } from '../../pool.helpers' import { useUserSettings } from '@repo/lib/modules/user/settings/UserSettingsProvider' import { isUnbalancedAddErrorMessage } from '@repo/lib/shared/utils/error-filters' @@ -37,6 +37,8 @@ export const AddLiquidityContext = createContext export function _useAddLiquidity(urlTxHash?: Hash) { const [humanAmountsIn, setHumanAmountsIn] = useState([]) + // only used by Proportional handlers that require a referenceAmount + const [referenceAmountAddress, setReferenceAmountAddress] = useState
() const [needsToAcceptHighPI, setNeedsToAcceptHighPI] = useState(false) const [acceptPoolRisks, setAcceptPoolRisks] = useState(false) const [wethIsEth, setWethIsEth] = useState(false) @@ -91,6 +93,19 @@ export function _useAddLiquidity(urlTxHash?: Hash) { ]) } + function clearAmountsIn(changedAmount?: HumanTokenAmountWithAddress) { + setHumanAmountsIn( + humanAmountsIn.map(({ tokenAddress }) => { + // Keeps user inputs like '0' or '0.' instead of replacing them with '' + if (changedAmount && isSameAddress(changedAmount.tokenAddress, tokenAddress)) { + return changedAmount + } + + return { tokenAddress, humanAmount: '' } + }) + ) + } + const tokensWithNativeAsset = replaceWrappedWithNativeAsset(tokens, nativeAsset) const validTokens = injectNativeAsset(tokens, nativeAsset, pool) @@ -110,6 +125,7 @@ export function _useAddLiquidity(urlTxHash?: Hash) { handler, humanAmountsIn, enabled: !urlTxHash, + referenceAmountAddress, }) const priceImpactQuery = useAddLiquidityPriceImpactQuery({ handler, @@ -179,7 +195,7 @@ export function _useAddLiquidity(urlTxHash?: Hash) { return { transactionSteps, humanAmountsIn, - tokens: wethIsEth && !isV3WithNestedActionsPool(pool) ? tokensWithNativeAsset : tokens, + tokens: wethIsEth && !isV3NotSupportingWethIsEth(pool) ? tokensWithNativeAsset : tokens, validTokens, totalUSDValue, simulationQuery, @@ -202,11 +218,14 @@ export function _useAddLiquidity(urlTxHash?: Hash) { proportionalSlippage, isForcedProportionalAdd, wantsProportional, + referenceAmountAddress, setWantsProportional, setProportionalSlippage, refetchQuote, setHumanAmountIn, setHumanAmountsIn, + clearAmountsIn, + setReferenceAmountAddress, setNeedsToAcceptHighPI, setAcceptPoolRisks, setWethIsEth, diff --git a/packages/lib/modules/pool/actions/add-liquidity/form/AddLiquidityForm.tsx b/packages/lib/modules/pool/actions/add-liquidity/form/AddLiquidityForm.tsx index 0b0e5530b..2b5d7e8eb 100644 --- a/packages/lib/modules/pool/actions/add-liquidity/form/AddLiquidityForm.tsx +++ b/packages/lib/modules/pool/actions/add-liquidity/form/AddLiquidityForm.tsx @@ -19,7 +19,7 @@ import { VStack, useDisclosure, } from '@chakra-ui/react' -import { useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { Address } from 'viem' import { AddLiquidityModal } from '../modal/AddLiquidityModal' import { useAddLiquidity } from '../AddLiquidityProvider' @@ -28,8 +28,6 @@ import { ProportionalTransactionSettings, TransactionSettings, } from '@repo/lib/modules/user/settings/TransactionSettings' -import { TokenInputs } from './TokenInputs' -import { TokenInputsWithAddable } from './TokenInputsWithAddable' import { usePool } from '../../../PoolProvider' import { hasNoLiquidity, @@ -55,7 +53,10 @@ import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet' import { BalAlert } from '@repo/lib/shared/components/alerts/BalAlert' import { SafeAppAlert } from '@repo/lib/shared/components/alerts/SafeAppAlert' import { useTokens } from '@repo/lib/modules/tokens/TokensProvider' +import { AddLiquidityFormTabs } from './AddLiquidityFormTabs' import { UnbalancedAddError } from '@repo/lib/shared/components/errors/UnbalancedAddError' +import { isUnbalancedAddError } from '@repo/lib/shared/utils/error-filters' +import { isV3NotSupportingWethIsEth } from '../../../pool.helpers' // small wrapper to prevent out of context error export function AddLiquidityForm() { @@ -88,6 +89,7 @@ function AddLiquidityMainForm() { proportionalSlippage, slippage, setProportionalSlippage, + setWantsProportional, wantsProportional, } = useAddLiquidity() @@ -100,6 +102,16 @@ function AddLiquidityMainForm() { const { balanceFor, isBalancesLoading } = useTokenBalances() const { isConnected } = useUserAccount() const { startTokenPricePolling } = useTokens() + const [tabIndex, setTabIndex] = useState(0) + + const setUnbalancedTab = () => { + setTabIndex(0) + setWantsProportional(false) + } + const setProportionalTab = () => { + setTabIndex(1) + setWantsProportional(true) + } useEffect(() => { setPriceImpact(priceImpactQuery.data) @@ -108,9 +120,12 @@ function AddLiquidityMainForm() { const hasPriceImpact = priceImpact !== undefined && priceImpact !== null const priceImpactLabel = hasPriceImpact ? fNum('priceImpact', priceImpact) : '-' + const nestedAddLiquidityEnabled = supportsNestedActions(pool) // TODO && !userToggledEscapeHatch + + const isUnbalancedError = isUnbalancedAddError(simulationQuery.error || priceImpactQuery.error) + const weeklyYield = calcPotentialYieldFor(pool, totalUSDValue) - const nestedAddLiquidityEnabled = supportsNestedActions(pool) // TODO && !userToggledEscapeHatch const isLoading = simulationQuery.isLoading || priceImpactQuery.isLoading const isFetching = simulationQuery.isFetching || priceImpactQuery.isFetching @@ -128,7 +143,7 @@ function AddLiquidityMainForm() { // if native asset balance is higher set that asset as the 'default' useEffect(() => { - if (!isBalancesLoading && nativeAsset && wNativeAsset) { + if (!isBalancesLoading && nativeAsset && wNativeAsset && !isV3NotSupportingWethIsEth(pool)) { const nativeAssetBalance = balanceFor(nativeAsset.address) const wNativeAssetBalance = balanceFor(wNativeAsset.address) if ( @@ -190,14 +205,20 @@ function AddLiquidityMainForm() { )} - {!nestedAddLiquidityEnabled ? ( - tokenSelectDisclosure.onOpen()} - totalUSDValue={totalUSDValue} + + {!wantsProportional && isUnbalancedError && ( + - ) : ( - tokenSelectDisclosure.onOpen()} /> )} {!simulationQuery.isError && ( @@ -225,6 +246,7 @@ function AddLiquidityMainForm() { totalUSDValue={totalUSDValue} /> } + avoidPriceImpactAlert={isUnbalancedError && !nestedAddLiquidityEnabled} cannotCalculatePriceImpact={cannotCalculatePriceImpactError(priceImpactQuery.error)} isDisabled={!priceImpactQuery.data} setNeedsToAcceptPIRisk={setNeedsToAcceptHighPI} @@ -263,11 +285,10 @@ function AddLiquidityMainForm() { )} - {(simulationQuery.isError || priceImpactQuery.isError) && ( - - )} + {isConnected ? ( + + ) : ( + ) })} ) } + +function GroupOptionButton({ + option, + isActive, + size, + width, + groupId, + hasLargeTextLabel, + onChange, +}: { option: ButtonGroupOption; isActive: boolean } & Props) { + return ( + + ) +} diff --git a/packages/lib/shared/components/errors/GenericError.tsx b/packages/lib/shared/components/errors/GenericError.tsx index 25e51691d..96517593c 100644 --- a/packages/lib/shared/components/errors/GenericError.tsx +++ b/packages/lib/shared/components/errors/GenericError.tsx @@ -6,7 +6,6 @@ import { isNotEnoughGasError, isPausedError, isTooManyRequestsError, - isUnbalancedAddError, isUserRejectedError, isViemHttpFetchError, } from '../../utils/error-filters' @@ -17,15 +16,15 @@ type ErrorWithOptionalShortMessage = Error & { shortMessage?: string } type Props = AlertProps & { error: ErrorWithOptionalShortMessage customErrorName?: string + skipError?: boolean } -export function GenericError({ error: _error, customErrorName, ...rest }: Props) { +export function GenericError({ error: _error, customErrorName, skipError, ...rest }: Props) { + if (skipError) return const error = ensureError(_error) if (isUserRejectedError(error)) return null const errorName = customErrorName ? `${customErrorName} (${error.name})` : error.name - if (isUnbalancedAddError(_error)) return null //We handle this specific error in UnbalancedAddError component - if (isViemHttpFetchError(_error)) { return ( diff --git a/packages/lib/shared/components/errors/UnbalancedAddError.tsx b/packages/lib/shared/components/errors/UnbalancedAddError.tsx index 0e8ac364c..776e51c2e 100644 --- a/packages/lib/shared/components/errors/UnbalancedAddError.tsx +++ b/packages/lib/shared/components/errors/UnbalancedAddError.tsx @@ -1,30 +1,94 @@ 'use client' import { AlertProps, Text } from '@chakra-ui/react' -import { isUnbalancedAddError } from '../../utils/error-filters' +import { + isInvariantRatioAboveMaxSimulationErrorMessage, + isInvariantRatioAboveMinSimulationErrorMessage, + isInvariantRatioPIErrorMessage, + isUnbalancedAddError, + isUnbalancedAddErrorMessage, +} from '../../utils/error-filters' import { BalAlertLink } from '../alerts/BalAlertLink' import { ErrorAlert } from './ErrorAlert' +import { useAddLiquidity } from '@repo/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider' type Props = AlertProps & { error?: Error | null + goToProportionalAdds: () => void + isProportionalSupported?: boolean } -export function UnbalancedAddError({ error }: Props) { - function goToProportionalAdds() { - // TODO: implement when we have the add tabs (unbalanced and proportional). - console.log('Go to proportional adds') +export function UnbalancedAddError({ + error, + goToProportionalAdds, + isProportionalSupported = true, +}: Props) { + const { clearAmountsIn } = useAddLiquidity() + const goToProportionalMode = () => { + clearAmountsIn() + goToProportionalAdds() } - if (isUnbalancedAddError(error)) { - return ( - - - Your input(s) would excessively unbalance the pool.{' '} - Please use proportional mode. - - - ) + if (!isUnbalancedAddError(error)) return null + + const errorLabels = getErrorLabels(isProportionalSupported, error) + + if (!errorLabels) return null + + return ( + + + {errorLabels.errorMessage}{' '} + {isProportionalSupported ? ( + + {errorLabels.proportionalLabel} + + ) : ( + errorLabels.proportionalLabel + )} + + + ) +} + +export type UnbalancedErrorLabels = { + errorTitle: string + errorMessage: string + proportionalLabel: string +} + +export function getErrorLabels( + isProportionalSupported: boolean, + error?: Error | null +): UnbalancedErrorLabels | undefined { + let errorTitle = 'Token amounts error' + let errorMessage = 'Unexpected error. Please ask for support' + let proportionalLabel = 'Please use proportional mode.' + + if (!error) return + + if (isInvariantRatioAboveMaxSimulationErrorMessage(error.message)) { + errorTitle = 'Amount exceeds pool limits' + errorMessage = 'Your input(s) would cause an invariant error in the vault.' + } else if (isInvariantRatioAboveMinSimulationErrorMessage(error.message)) { + errorTitle = 'Amount is below pool limits' + } else if (isInvariantRatioPIErrorMessage(error.message)) { + errorTitle = 'Unknown price impact' + errorMessage = isProportionalSupported + ? 'The price impact cannot be calculated. Proceed if you know exactly what you are doing or' + : 'The price impact cannot be calculated. Proceed if you know exactly what you are doing.' + proportionalLabel = 'try proportional mode.' + } else if (isUnbalancedAddErrorMessage(error)) { + errorMessage = 'Your input(s) would excessively unbalance the pool.' } - return null + if (!isProportionalSupported) { + proportionalLabel = 'Please try different amounts.' + } + + return { + errorTitle, + errorMessage, + proportionalLabel, + } } diff --git a/packages/lib/shared/components/errors/UnbalancedError.test.tsx b/packages/lib/shared/components/errors/UnbalancedError.test.tsx new file mode 100644 index 000000000..fbd34005a --- /dev/null +++ b/packages/lib/shared/components/errors/UnbalancedError.test.tsx @@ -0,0 +1,73 @@ +import { describe, it, expect } from 'vitest' +import { getErrorLabels } from './UnbalancedAddError' + +describe('getErrorLabels', () => { + const invariantRatioAboveMaxError = new Error('InvariantRatioAboveMax') + const invariantRatioAboveMinError = new Error('InvariantRatioBelowMin') + const invariantRatioPIError = new Error( + 'addLiquidityUnbalanced operation will fail at SC level with user defined input.' + ) + const unbalancedAddErrorV2 = new Error('BAL#304') + const unbalancedAddErrorV3 = new Error('queryAddLiquidityUnbalanced') + + it('should return default error labels when no error is provided', () => { + const result = getErrorLabels(true, null) + expect(result).toBeUndefined() + }) + + it('should return correct labels for isInvariantRatioAboveMaxSimulationErrorMessage', () => { + const result = getErrorLabels(true, invariantRatioAboveMaxError) + expect(result).toEqual({ + errorTitle: 'Amount exceeds pool limits', + errorMessage: 'Your input(s) would cause an invariant error in the vault.', + proportionalLabel: 'Please use proportional mode.', + }) + }) + + it('should return correct labels for isInvariantRatioAboveMinSimulationErrorMessage', () => { + const result = getErrorLabels(true, invariantRatioAboveMinError) + expect(result).toEqual({ + errorTitle: 'Amount is below pool limits', + errorMessage: 'Unexpected error. Please ask for support', + proportionalLabel: 'Please use proportional mode.', + }) + }) + + it('should return correct labels for isInvariantRatioPIErrorMessage when proportional is supported', () => { + const result = getErrorLabels(true, invariantRatioPIError) + expect(result).toEqual({ + errorTitle: 'Unknown price impact', + errorMessage: + 'The price impact cannot be calculated. Proceed if you know exactly what you are doing or', + proportionalLabel: 'try proportional mode.', + }) + }) + + it('should return correct labels for isInvariantRatioPIErrorMessage when proportional is not supported', () => { + const result = getErrorLabels(false, invariantRatioPIError) + expect(result).toEqual({ + errorTitle: 'Unknown price impact', + errorMessage: + 'The price impact cannot be calculated. Proceed if you know exactly what you are doing.', + proportionalLabel: 'Please try different amounts.', + }) + }) + + it('should return correct labels for isUnbalancedAddErrorMessage (v2)', () => { + const result = getErrorLabels(true, unbalancedAddErrorV2) + expect(result).toEqual({ + errorTitle: 'Token amounts error', + errorMessage: 'Your input(s) would excessively unbalance the pool.', + proportionalLabel: 'Please use proportional mode.', + }) + }) + + it('should return correct labels for isUnbalancedAddErrorMessage (v3)', () => { + const result = getErrorLabels(true, unbalancedAddErrorV3) + expect(result).toEqual({ + errorTitle: 'Token amounts error', + errorMessage: 'Your input(s) would excessively unbalance the pool.', + proportionalLabel: 'Please use proportional mode.', + }) + }) +}) diff --git a/packages/lib/shared/utils/error-filters.ts b/packages/lib/shared/utils/error-filters.ts index 6db7f6bfd..acad457db 100644 --- a/packages/lib/shared/utils/error-filters.ts +++ b/packages/lib/shared/utils/error-filters.ts @@ -57,7 +57,7 @@ export function isUnbalancedAddError(error?: Error | null): boolean { return false } -export function isUnbalancedAddErrorMessage(error: Error | null): boolean { +export function isUnbalancedAddErrorMessage(error?: Error | null): boolean { const errorStrings = ['BAL#304', 'queryAddLiquidityUnbalanced'] // [v2 error, v3 error] const hasErrors = (errorString: string) => error?.message.includes(errorString) @@ -66,16 +66,25 @@ export function isUnbalancedAddErrorMessage(error: Error | null): boolean { export function isInvariantRatioSimulationErrorMessage(errorMessage?: string): boolean { return ( - !!errorMessage?.includes('InvariantRatioAboveMax') || - !!errorMessage?.includes('InvariantRatioBelowMin') + isInvariantRatioAboveMaxSimulationErrorMessage(errorMessage) || + isInvariantRatioAboveMinSimulationErrorMessage(errorMessage) ) } +export function isInvariantRatioAboveMaxSimulationErrorMessage(errorMessage?: string): boolean { + return !!errorMessage?.includes('InvariantRatioAboveMax') +} +export function isInvariantRatioAboveMinSimulationErrorMessage(errorMessage?: string): boolean { + return !!errorMessage?.includes('InvariantRatioBelowMin') +} + export function isInvariantRatioPIErrorMessage(errorMessage?: string): boolean { + if (!errorMessage) return false if ( - errorMessage?.includes( + errorMessage.includes( 'addLiquidityUnbalanced operation will fail at SC level with user defined input.' - ) + ) || + errorMessage.includes('Arithmetic operation resulted in underflow or overflow.') ) { return true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b2469eec..092c388b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -326,8 +326,8 @@ importers: specifier: ^3.11.8 version: 3.11.8(@types/react@18.2.34)(graphql-ws@5.16.0(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@balancer/sdk': - specifier: 0.33.1 - version: 0.33.1(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + specifier: 0.33.2 + version: 0.33.2(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) '@chakra-ui/anatomy': specifier: ^2.2.2 version: 2.2.2 @@ -1709,8 +1709,8 @@ packages: resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} engines: {node: '>=6.9.0'} - '@balancer/sdk@0.33.1': - resolution: {integrity: sha512-JwS6wva9g2Hf8RsV3MoJIwHcctFnS77MUlrp/pC/7GfUeKwu++veWGJbVxjO0xo0PYcALSnvHGqp3YmZlPtP3g==} + '@balancer/sdk@0.33.2': + resolution: {integrity: sha512-NaM/EFFPGYiTrLDK7WoKuEWb9ntfGTyTbfsZMyE63q4ui0C5gOGNVoPlZjsHJ9S719Dk13HRW4GwV/Vn17ysJQ==} engines: {node: '>=18.x'} '@bcoe/v8-coverage@0.2.3': @@ -11719,7 +11719,7 @@ snapshots: '@babel/helper-validator-identifier': 7.25.7 to-fast-properties: 2.0.0 - '@balancer/sdk@0.33.1(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@balancer/sdk@0.33.2(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: decimal.js-light: 2.5.1 lodash.clonedeep: 4.5.0