diff --git a/lib/config/config.types.ts b/lib/config/config.types.ts index d3097f1e1..71e273c2d 100644 --- a/lib/config/config.types.ts +++ b/lib/config/config.types.ts @@ -43,6 +43,7 @@ export interface ContractsConfig { feeDistributor?: Address veDelegationProxy?: Address veBAL?: Address + permit2?: Address } export interface PoolsConfig { issues: Partial> diff --git a/lib/config/networks/sepolia.ts b/lib/config/networks/sepolia.ts index 6a8c36c87..97805ca46 100644 --- a/lib/config/networks/sepolia.ts +++ b/lib/config/networks/sepolia.ts @@ -31,11 +31,12 @@ const networkConfig: NetworkConfig = { multicall2: '0xca11bde05977b3631167028862be2a173976ca11', balancer: { vaultV2: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', - vaultV3: '0xD5584b37D1845fFeD958C2d94bC675603DdCce68', + vaultV3: '0x0EF1c156a7986F394d90eD1bEeA6483Cc435F542', relayerV6: '0x7852fB9d0895e6e8b3EedA553c03F6e2F9124dF9', minter: '0x1783Cd84b3d01854A96B4eD5843753C2CcbD574A', }, veBAL: '0x150A72e4D4d81BbF045565E232c50Ed0931ad795', + permit2: '0x000000000022D473030F116dDEE9F6B43aC78BA3', }, pools: convertHexToLowerCase({ issues: {}, diff --git a/lib/debug-helpers.ts b/lib/debug-helpers.ts index a399cf86d..a1111c7df 100644 --- a/lib/debug-helpers.ts +++ b/lib/debug-helpers.ts @@ -36,6 +36,8 @@ export const vaultV3Address = sepoliaNetworkConfig.contracts.balancer.vaultV3 as export const poolId = '0x68e3266c9c8bbd44ad9dca5afbfe629022aee9fe000200000000000000000512' as const // Balancer Weighted wjAura and WETH +export const sepoliaRouter = '0x1c58cc548a23956469c7C528Bb3a846c842dfaF9' + /* Used to pretty print objects when debugging */ diff --git a/lib/modules/pool/actions/LiquidityActionHelpers.ts b/lib/modules/pool/actions/LiquidityActionHelpers.ts index c19b9ad49..cfdc4a7d8 100644 --- a/lib/modules/pool/actions/LiquidityActionHelpers.ts +++ b/lib/modules/pool/actions/LiquidityActionHelpers.ts @@ -75,10 +75,12 @@ export class LiquidityActionHelpers { } public getAmountsToApprove( - humanAmountsIn: HumanTokenAmountWithAddress[] + humanAmountsIn: HumanTokenAmountWithAddress[], + isPermit2 = false ): TokenAmountToApprove[] { return this.toInputAmounts(humanAmountsIn).map(({ address, rawAmount }) => { return { + isPermit2, tokenAddress: address, requiredRawAmount: rawAmount, requestedRawAmount: rawAmount, //This amount will be probably replaced by MAX_BIGINT depending on the approval rules @@ -232,7 +234,7 @@ export function toPoolStateWithBalances(pool: Pool): PoolStateWithBalances { * - is native and the wrapped native token is already in the array and * - is wrapped native and the native token is already in the array * - * @param {HumanAmoHumanTokenAmountWithAddressuntIn[]} humanAmountsIn - The array of human amounts to filter. + * @param {HumanTokenAmountWithAddress[]} humanAmountsIn - The array of human amounts to filter. * @param {Address} tokenAddress - The token address to compare against. * @param {GqlChain} chain - The chain type for comparison. * @return {HumanTokenAmountWithAddress[]} The filtered array of human amounts. @@ -309,3 +311,12 @@ export function injectNativeAsset( export function hasNoLiquidity(pool: Pool): boolean { return isZero(pool.dynamicData.totalShares) } + +// When the pool has version < v3, it adds extra buildCall params (sender and recipient) that must be present only in V1/V2 +export function formatBuildCallParams(buildCallParams: T, isV3Pool: boolean, account: Address) { + // sender must be undefined for v3 pools + if (isV3Pool) return buildCallParams + + // sender and recipient must be defined only for v1 and v2 pools + return { ...buildCallParams, sender: account, recipient: account } +} diff --git a/lib/modules/pool/actions/add-liquidity/handlers/UnbalancedAddLiquidity.handler.ts b/lib/modules/pool/actions/add-liquidity/handlers/UnbalancedAddLiquidity.handler.ts index f71f483a1..c33ee4716 100644 --- a/lib/modules/pool/actions/add-liquidity/handlers/UnbalancedAddLiquidity.handler.ts +++ b/lib/modules/pool/actions/add-liquidity/handlers/UnbalancedAddLiquidity.handler.ts @@ -11,9 +11,13 @@ import { Slippage, } from '@balancer/sdk' import { Pool } from '../../../PoolProvider' -import { LiquidityActionHelpers, areEmptyAmounts } from '../../LiquidityActionHelpers' -import { SdkBuildAddLiquidityInput, SdkQueryAddLiquidityOutput } from '../add-liquidity.types' +import { + LiquidityActionHelpers, + formatBuildCallParams, + areEmptyAmounts, +} from '../../LiquidityActionHelpers' import { AddLiquidityHandler } from './AddLiquidity.handler' +import { SdkBuildAddLiquidityInput, SdkQueryAddLiquidityOutput } from '../add-liquidity.types' /** * UnbalancedAddLiquidityHandler is a handler that implements the @@ -64,13 +68,19 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler { }: SdkBuildAddLiquidityInput): Promise { const addLiquidity = new AddLiquidity() - const { callData, to, value } = addLiquidity.buildCall({ + const baseBuildCallParams = { ...queryOutput.sdkQueryOutput, slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`), - sender: account, - recipient: account, wethIsEth: this.helpers.isNativeAssetIn(humanAmountsIn), - }) + } + + const buildCallParams = formatBuildCallParams( + baseBuildCallParams, + this.helpers.isV3Pool(), + account + ) + + const { callData, to, value } = addLiquidity.buildCall(buildCallParams) return { account, diff --git a/lib/modules/pool/actions/add-liquidity/useAddLiquiditySteps.tsx b/lib/modules/pool/actions/add-liquidity/useAddLiquiditySteps.tsx index 74a46d7e8..5363a6f92 100644 --- a/lib/modules/pool/actions/add-liquidity/useAddLiquiditySteps.tsx +++ b/lib/modules/pool/actions/add-liquidity/useAddLiquiditySteps.tsx @@ -3,14 +3,13 @@ import { useShouldSignRelayerApproval } from '@/lib/modules/relayer/signRelayerA import { useApproveRelayerStep } from '@/lib/modules/relayer/useApproveRelayerStep' import { useRelayerMode } from '@/lib/modules/relayer/useRelayerMode' import { useTokenApprovalSteps } from '@/lib/modules/tokens/approvals/useTokenApprovalSteps' -import { useContractAddress } from '@/lib/modules/web3/contracts/useContractAddress' import { useMemo } from 'react' import { usePool } from '../../PoolProvider' import { LiquidityActionHelpers } from '../LiquidityActionHelpers' import { AddLiquidityStepParams, useAddLiquidityStep } from './useAddLiquidityStep' +import { isV3Pool } from '../../pool.helpers' import { useSignRelayerStep } from '@/lib/modules/transactions/transaction-steps/useSignRelayerStep' -import { Address } from 'viem' -import { isCowAmmPool } from '../../pool.helpers' +import { getTokenSpenderAddress } from '@/lib/modules/tokens/token.helpers' type AddLiquidityStepsParams = AddLiquidityStepParams & { helpers: LiquidityActionHelpers @@ -21,10 +20,10 @@ export function useAddLiquiditySteps({ humanAmountsIn, simulationQuery, }: AddLiquidityStepsParams) { - const vaultAddress = useContractAddress('balancer.vaultV2') const { pool, chainId, chain } = usePool() const relayerMode = useRelayerMode(pool) const shouldSignRelayerApproval = useShouldSignRelayerApproval(chainId, relayerMode) + const { step: approveRelayerStep, isLoading: isLoadingRelayerApproval } = useApproveRelayerStep(chainId) const signRelayerStep = useSignRelayerStep(chain) @@ -36,10 +35,11 @@ export function useAddLiquiditySteps({ const { isLoading: isLoadingTokenApprovalSteps, steps: tokenApprovalSteps } = useTokenApprovalSteps({ - spenderAddress: isCowAmmPool(pool.type) ? (pool.address as Address) : vaultAddress, + spenderAddress: getTokenSpenderAddress(pool), chain: pool.chain, approvalAmounts: inputAmounts, actionType: 'AddLiquidity', + isPermit2: isV3Pool(pool), }) const addLiquidityStep = useAddLiquidityStep({ diff --git a/lib/modules/pool/actions/remove-liquidity/handlers/ProportionalRemoveLiquidity.handler.ts b/lib/modules/pool/actions/remove-liquidity/handlers/ProportionalRemoveLiquidity.handler.ts index a0370fd47..b3e36e89e 100644 --- a/lib/modules/pool/actions/remove-liquidity/handlers/ProportionalRemoveLiquidity.handler.ts +++ b/lib/modules/pool/actions/remove-liquidity/handlers/ProportionalRemoveLiquidity.handler.ts @@ -10,7 +10,7 @@ import { import { Address, parseEther } from 'viem' import { BPT_DECIMALS } from '../../../pool.constants' import { Pool } from '../../../PoolProvider' -import { LiquidityActionHelpers } from '../../LiquidityActionHelpers' +import { LiquidityActionHelpers, formatBuildCallParams } from '../../LiquidityActionHelpers' import { QueryRemoveLiquidityInput, SdkBuildRemoveLiquidityInput, @@ -50,13 +50,19 @@ export class ProportionalRemoveLiquidityHandler implements RemoveLiquidityHandle }: SdkBuildRemoveLiquidityInput): Promise { const removeLiquidity = new RemoveLiquidity() - const { callData, to, value } = removeLiquidity.buildCall({ + const baseBuildCallParams = { ...queryOutput.sdkQueryOutput, slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`), - sender: account, - recipient: account, wethIsEth, - }) + } + + const buildCallParams = formatBuildCallParams( + baseBuildCallParams, + this.helpers.isV3Pool(), + account + ) + + const { callData, to, value } = removeLiquidity.buildCall(buildCallParams) return { account, diff --git a/lib/modules/pool/actions/remove-liquidity/handlers/SingleTokenRemoveLiquidity.handler.ts b/lib/modules/pool/actions/remove-liquidity/handlers/SingleTokenRemoveLiquidity.handler.ts index 3ee78fee1..a0c8736f7 100644 --- a/lib/modules/pool/actions/remove-liquidity/handlers/SingleTokenRemoveLiquidity.handler.ts +++ b/lib/modules/pool/actions/remove-liquidity/handlers/SingleTokenRemoveLiquidity.handler.ts @@ -12,7 +12,11 @@ import { import { Address, parseEther } from 'viem' import { BPT_DECIMALS } from '../../../pool.constants' import { Pool } from '../../../PoolProvider' -import { LiquidityActionHelpers, isEmptyHumanAmount } from '../../LiquidityActionHelpers' +import { + LiquidityActionHelpers, + formatBuildCallParams, + isEmptyHumanAmount, +} from '../../LiquidityActionHelpers' import { SdkBuildRemoveLiquidityInput, SdkQueryRemoveLiquidityOutput, @@ -72,13 +76,19 @@ export class SingleTokenRemoveLiquidityHandler implements RemoveLiquidityHandler }: SdkBuildRemoveLiquidityInput): Promise { const removeLiquidity = new RemoveLiquidity() - const { callData, to, value } = removeLiquidity.buildCall({ + const baseBuildCallParams = { ...queryOutput.sdkQueryOutput, slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`), - sender: account, - recipient: account, wethIsEth, - }) + } + + const buildCallParams = formatBuildCallParams( + baseBuildCallParams, + this.helpers.isV3Pool(), + account + ) + + const { callData, to, value } = removeLiquidity.buildCall(buildCallParams) return { account, diff --git a/lib/modules/tokens/approvals/approval-rules.spec.ts b/lib/modules/tokens/approvals/approval-rules.spec.ts index bb4dc207e..2d51073e5 100644 --- a/lib/modules/tokens/approvals/approval-rules.spec.ts +++ b/lib/modules/tokens/approvals/approval-rules.spec.ts @@ -52,11 +52,13 @@ describe('getRequiredTokenApprovals', () => { }) ).toEqual([ { + isPermit2: false, tokenAddress: wETHAddress, requiredRawAmount: 10000000000000000000n, requestedRawAmount: MAX_BIGINT, }, { + isPermit2: false, tokenAddress: wjAuraAddress, requiredRawAmount: 20000000000000000000n, requestedRawAmount: MAX_BIGINT, @@ -87,11 +89,13 @@ describe('getRequiredTokenApprovals', () => { requiredRawAmount: 0n, requestedRawAmount: 0n, tokenAddress: usdtAddress, + isPermit2: false, }, { tokenAddress: usdtAddress, requiredRawAmount: 10000000000000000000n, requestedRawAmount: MAX_BIGINT, + isPermit2: false, }, ]) }) @@ -116,6 +120,7 @@ describe('getRequiredTokenApprovals', () => { }) ).toEqual([ { + isPermit2: false, requiredRawAmount: 10000000000000000000n, requestedRawAmount: MAX_BIGINT, tokenAddress: usdtAddress, diff --git a/lib/modules/tokens/approvals/approval-rules.ts b/lib/modules/tokens/approvals/approval-rules.ts index 998db2233..cc8987ec0 100644 --- a/lib/modules/tokens/approvals/approval-rules.ts +++ b/lib/modules/tokens/approvals/approval-rules.ts @@ -10,6 +10,7 @@ export type TokenAmountToApprove = { tokenAddress: Address requiredRawAmount: bigint // actual amount that the transaction requires requestedRawAmount: bigint // amount that we are going to request (normally MAX_BIGINT) + isPermit2: boolean // whether the approval is for Permit2 or standard token approval } // This is a subtype of InputAmount as we only need rawAmount and address @@ -19,6 +20,7 @@ type TokenApprovalParams = { chainId: GqlChain | SupportedChainId | null rawAmounts: RawAmount[] allowanceFor: (tokenAddress: Address) => bigint + isPermit2?: boolean approveMaxBigInt?: boolean skipAllowanceCheck?: boolean } @@ -30,6 +32,7 @@ export function getRequiredTokenApprovals({ chainId, rawAmounts, allowanceFor, + isPermit2 = false, approveMaxBigInt = true, skipAllowanceCheck = false, }: TokenApprovalParams): TokenAmountToApprove[] { @@ -42,6 +45,7 @@ export function getRequiredTokenApprovals({ requiredRawAmount: rawAmount, // The transaction only requires requiredRawAmount but we will normally request MAX_BIGINT requestedRawAmount: approveMaxBigInt ? MAX_BIGINT : rawAmount, + isPermit2, } }) @@ -59,6 +63,7 @@ export function getRequiredTokenApprovals({ requiredRawAmount: 0n, requestedRawAmount: 0n, tokenAddress: t.tokenAddress, + isPermit2, } // Prepend approval for ZERO amount return [zeroTokenAmountToApprove, t] diff --git a/lib/modules/tokens/approvals/useTokenApprovalSteps.tsx b/lib/modules/tokens/approvals/useTokenApprovalSteps.tsx index 48294da53..0bf7eb358 100644 --- a/lib/modules/tokens/approvals/useTokenApprovalSteps.tsx +++ b/lib/modules/tokens/approvals/useTokenApprovalSteps.tsx @@ -20,11 +20,12 @@ export type Params = { chain: GqlChain approvalAmounts: RawAmount[] actionType: ApprovalAction + isPermit2?: boolean bptSymbol?: string //Edge-case for approving } /* - Generic hook to creates a Token Approval Step Config for different flows defined by the actionType property + Generic hook to create a Token Approval Step Config for different flows defined by the actionType property */ export function useTokenApprovalSteps({ spenderAddress, @@ -32,6 +33,7 @@ export function useTokenApprovalSteps({ approvalAmounts, actionType, bptSymbol, + isPermit2 = false, }: Params): { isLoading: boolean; steps: TransactionStep[] } { const { userAddress } = useUserAccount() const { getToken } = useTokens() @@ -58,6 +60,7 @@ export function useTokenApprovalSteps({ chainId: chain, rawAmounts: _approvalAmounts, allowanceFor: tokenAllowances.allowanceFor, + isPermit2, }) const steps = useMemo(() => { diff --git a/lib/modules/tokens/token.helpers.ts b/lib/modules/tokens/token.helpers.ts index 37abe8259..624ee784f 100644 --- a/lib/modules/tokens/token.helpers.ts +++ b/lib/modules/tokens/token.helpers.ts @@ -10,6 +10,7 @@ import { Address } from 'viem' import { HumanTokenAmountWithAddress, TokenBase } from './token.types' import { InputAmount } from '@balancer/sdk' import { Pool } from '../pool/PoolProvider' +import { getVaultConfig, isCowAmmPool, isV3Pool } from '../pool/pool.helpers' export function isNativeAsset(token: TokenBase | string, chain: GqlChain | SupportedChainId) { return nativeAssetFilter(chain)(token) @@ -130,3 +131,16 @@ export function getLeafTokens(poolTokens: PoolToken[]) { return leafTokens } + +export function getTokenSpenderAddress(pool: Pool): Address { + if (isCowAmmPool(pool.type)) return pool.address as Address + if (isV3Pool(pool)) { + const permit2Address = getNetworkConfig(pool.chain).contracts.permit2 + if (!permit2Address) { + throw new Error(`Permit2 feature is not yet available for this chain (${pool.chain}) `) + } + return permit2Address + } + const { vaultAddress } = getVaultConfig(pool) + return vaultAddress +}