diff --git a/.changeset/forty-beers-carry.md b/.changeset/forty-beers-carry.md new file mode 100644 index 0000000000000..408161d59f4e6 --- /dev/null +++ b/.changeset/forty-beers-carry.md @@ -0,0 +1,5 @@ +--- +'@pancakeswap/swap-sdk-core': minor +--- + +Add currency address getter diff --git a/.changeset/seven-adults-switch.md b/.changeset/seven-adults-switch.md new file mode 100644 index 0000000000000..1419c54fe03be --- /dev/null +++ b/.changeset/seven-adults-switch.md @@ -0,0 +1,5 @@ +--- +'@pancakeswap/swap-sdk-core': minor +--- + +Support price wrapping diff --git a/.changeset/tame-elephants-lick.md b/.changeset/tame-elephants-lick.md new file mode 100644 index 0000000000000..1b1a9a68f662b --- /dev/null +++ b/.changeset/tame-elephants-lick.md @@ -0,0 +1,6 @@ +--- +'@pancakeswap/price-api-sdk': minor +'@pancakeswap/smart-router': minor +--- + +Add v4 pool types diff --git a/.changeset/wicked-bobcats-play.md b/.changeset/wicked-bobcats-play.md new file mode 100644 index 0000000000000..43e5bbcb5032b --- /dev/null +++ b/.changeset/wicked-bobcats-play.md @@ -0,0 +1,6 @@ +--- +'@pancakeswap/price-api-sdk': minor +'@pancakeswap/smart-router': minor +--- + +Introduce v4 liquidity pools diff --git a/apps/web/src/hooks/useV4BinPools.ts b/apps/web/src/hooks/useV4BinPools.ts new file mode 100644 index 0000000000000..77b377be4219e --- /dev/null +++ b/apps/web/src/hooks/useV4BinPools.ts @@ -0,0 +1,73 @@ +import { Currency, getCurrencyAddress, sortCurrencies } from '@pancakeswap/swap-sdk-core' +import { V4Router } from '@pancakeswap/smart-router' +import { useQuery } from '@tanstack/react-query' +import { useMemo } from 'react' + +import { POOLS_FAST_REVALIDATE } from 'config/pools' +import { getViemClients } from 'utils/viem' + +export type V4PoolsParams = { + // Used for caching + key?: string + blockNumber?: number + enabled?: boolean +} + +export function useV4BinCandidatePoolsWithoutBins(currencyA?: Currency, currencyB?: Currency, options?: V4PoolsParams) { + const key = useMemo(() => { + if ( + !currencyA || + !currencyB || + currencyA.chainId !== currencyB.chainId || + currencyA.wrapped.equals(currencyB.wrapped) + ) { + return '' + } + const [currency0, currency1] = sortCurrencies([currencyA, currencyB]) + return [ + currency0.isNative, + getCurrencyAddress(currency0), + currency1.isNative, + getCurrencyAddress(currency1), + currency0.chainId, + ].join('_') + }, [currencyA, currencyB]) + + const refetchInterval = useMemo(() => { + if (!currencyA?.chainId) { + return 0 + } + return POOLS_FAST_REVALIDATE[currencyA.chainId] || 0 + }, [currencyA?.chainId]) + + const { data, refetch, isPending, isFetching, error } = useQuery({ + queryKey: ['v4_bin_candidate_pools_without_bins', key], + queryFn: async () => { + const pools = await V4Router.getV4BinCandidatePools({ + currencyA, + currencyB, + clientProvider: getViemClients, + }) + return { + key, + pools, + blockNumber: options?.blockNumber, + } + }, + retry: 2, + staleTime: refetchInterval, + refetchInterval, + refetchOnWindowFocus: false, + enabled: Boolean(currencyA && currencyB && key && options?.enabled), + }) + + return { + refresh: refetch, + pools: data?.pools, + loading: isPending, + syncing: isFetching, + blockNumber: data?.blockNumber, + key: data?.key, + error, + } +} diff --git a/apps/web/src/hooks/useV4ClPools.ts b/apps/web/src/hooks/useV4ClPools.ts new file mode 100644 index 0000000000000..20d2a111c3c1d --- /dev/null +++ b/apps/web/src/hooks/useV4ClPools.ts @@ -0,0 +1,73 @@ +import { Currency, getCurrencyAddress, sortCurrencies } from '@pancakeswap/swap-sdk-core' +import { V4Router } from '@pancakeswap/smart-router' +import { useQuery } from '@tanstack/react-query' +import { useMemo } from 'react' + +import { POOLS_FAST_REVALIDATE } from 'config/pools' +import { getViemClients } from 'utils/viem' + +export type V4PoolsParams = { + // Used for caching + key?: string + blockNumber?: number + enabled?: boolean +} + +export function useV4CandidateClPoolsWithoutTicks(currencyA?: Currency, currencyB?: Currency, options?: V4PoolsParams) { + const key = useMemo(() => { + if ( + !currencyA || + !currencyB || + currencyA.chainId !== currencyB.chainId || + currencyA.wrapped.equals(currencyB.wrapped) + ) { + return '' + } + const [currency0, currency1] = sortCurrencies([currencyA, currencyB]) + return [ + currency0.isNative, + getCurrencyAddress(currency0), + currency1.isNative, + getCurrencyAddress(currency1), + currency0.chainId, + ].join('_') + }, [currencyA, currencyB]) + + const refetchInterval = useMemo(() => { + if (!currencyA?.chainId) { + return 0 + } + return POOLS_FAST_REVALIDATE[currencyA.chainId] || 0 + }, [currencyA?.chainId]) + + const { data, refetch, isPending, isFetching, error } = useQuery({ + queryKey: ['v4_cl_candidate_pools_without_ticks', key], + queryFn: async () => { + const pools = await V4Router.getV4ClCandidatePools({ + currencyA, + currencyB, + clientProvider: getViemClients, + }) + return { + key, + pools, + blockNumber: options?.blockNumber, + } + }, + retry: 2, + staleTime: refetchInterval, + refetchInterval, + refetchOnWindowFocus: false, + enabled: Boolean(currencyA && currencyB && key && options?.enabled), + }) + + return { + refresh: refetch, + pools: data?.pools, + loading: isPending, + syncing: isFetching, + blockNumber: data?.blockNumber, + key: data?.key, + error, + } +} diff --git a/apps/web/src/utils/convertTrade.ts b/apps/web/src/utils/convertTrade.ts index acfc3daba3019..c009896b2d496 100644 --- a/apps/web/src/utils/convertTrade.ts +++ b/apps/web/src/utils/convertTrade.ts @@ -18,7 +18,10 @@ export function toRoutingSDKPool(p: SmartRouterPool): Pool { if (SmartRouter.isV2Pool(p)) { return createV2Pool(p) } - return createStablePool(p) + if (SmartRouter.isStablePool(p)) { + return createStablePool(p) + } + throw new Error(`Unsupported pool type: ${p.type}`) } export function toSmartRouterPool(p: any): SmartRouterPool { diff --git a/apps/web/src/views/Swap/V3Swap/components/RouteDisplayModal.tsx b/apps/web/src/views/Swap/V3Swap/components/RouteDisplayModal.tsx index 27fb23dfd9fdb..ed1aa6e16c443 100644 --- a/apps/web/src/views/Swap/V3Swap/components/RouteDisplayModal.tsx +++ b/apps/web/src/views/Swap/V3Swap/components/RouteDisplayModal.tsx @@ -95,23 +95,36 @@ export const RouteDisplay = memo(function RouteDisplay({ route }: RouteDisplayPr ? pairs.map((p, index) => { const [input, output] = p const pool = pools[index] + const isV4ClPool = SmartRouter.isV4ClPool(pool) + const isV4BinPool = SmartRouter.isV4BinPool(pool) + const isV4Pool = isV4BinPool || isV4ClPool const isV3Pool = SmartRouter.isV3Pool(pool) const isV2Pool = SmartRouter.isV2Pool(pool) - const key = isV2Pool ? `v2_${pool.reserve0.currency.symbol}_${pool.reserve1.currency.symbol}` : pool.address + const key = isV2Pool + ? `v2_${pool.reserve0.currency.symbol}_${pool.reserve1.currency.symbol}` + : SmartRouter.isStablePool(pool) || isV3Pool + ? pool.address + : isV4Pool + ? pool.id + : undefined + if (!key) return null + const feeDisplay = isV3Pool || isV4Pool ? v3FeeToPercent(pool.fee).toSignificant(6) : '' const text = isV2Pool ? 'V2' : isV3Pool - ? `V3 (${v3FeeToPercent(pool.fee).toSignificant(6)}%)` + ? `V3 (${feeDisplay}%)` + : isV4ClPool + ? `V4CL (${feeDisplay}%)` + : isV4BinPool + ? `V4Bin (${feeDisplay}%)` : t('StableSwap') - const tooltipText = `${input.symbol}/${output.symbol}${ - isV3Pool ? ` (${v3FeeToPercent(pool.fee).toSignificant(6)}%)` : '' - }` + const tooltipText = `${input.symbol}/${output.symbol}${isV3Pool || isV4Pool ? ` (${feeDisplay}%)` : ''}` return ( ) diff --git a/apps/web/src/views/Swap/V3Swap/utils/exchange.ts b/apps/web/src/views/Swap/V3Swap/utils/exchange.ts index a8abcfe0cf8e1..46f9513156790 100644 --- a/apps/web/src/views/Swap/V3Swap/utils/exchange.ts +++ b/apps/web/src/views/Swap/V3Swap/utils/exchange.ts @@ -6,7 +6,6 @@ import { ONE_HUNDRED_PERCENT, Percent, Price, - Token, TradeType, ZERO, } from '@pancakeswap/sdk' @@ -61,7 +60,7 @@ export function computeTradePriceBreakdown(trade?: TradeEssentialForPriceBreakdo const { routes, outputAmount, inputAmount } = trade let feePercent = new Percent(0) - let outputAmountWithoutPriceImpact = CurrencyAmount.fromRawAmount(trade.outputAmount.wrapped.currency, 0) + let outputAmountWithoutPriceImpact = CurrencyAmount.fromRawAmount(trade.outputAmount.currency, 0) for (const route of routes) { const { inputAmount: routeInputAmount, pools, percent } = route const routeFeePercent = ONE_HUNDRED_PERCENT.subtract( @@ -85,7 +84,10 @@ export function computeTradePriceBreakdown(trade?: TradeEssentialForPriceBreakdo const midPrice = SmartRouter.getMidPrice(route) outputAmountWithoutPriceImpact = outputAmountWithoutPriceImpact.add( - midPrice.quote(routeInputAmount.wrapped) as CurrencyAmount, + CurrencyAmount.fromRawAmount( + trade.outputAmount.currency, + midPrice.wrapped.quote(routeInputAmount.wrapped).quotient, + ), ) } @@ -96,9 +98,7 @@ export function computeTradePriceBreakdown(trade?: TradeEssentialForPriceBreakdo } } - const priceImpactRaw = outputAmountWithoutPriceImpact - .subtract(outputAmount.wrapped) - .divide(outputAmountWithoutPriceImpact) + const priceImpactRaw = outputAmountWithoutPriceImpact.subtract(outputAmount).divide(outputAmountWithoutPriceImpact) const priceImpactPercent = new Percent(priceImpactRaw.numerator, priceImpactRaw.denominator) const priceImpactWithoutFee = priceImpactPercent.subtract(feePercent) const lpFeeAmount = inputAmount.multiply(feePercent) diff --git a/packages/price-api-sdk/src/types/amm.ts b/packages/price-api-sdk/src/types/amm.ts index d1d71bb2359a2..452418be41048 100644 --- a/packages/price-api-sdk/src/types/amm.ts +++ b/packages/price-api-sdk/src/types/amm.ts @@ -28,7 +28,7 @@ export type AMMOrder = V4Router.Transformer.SerializedV4Trade & { } export type AMMRequestConfig = { - protocols: ('V2' | 'V3' | 'STABLE')[] + protocols: PoolTypeKey[] routingType: OrderType.PCS_CLASSIC gasPriceWei?: string maxHops?: number diff --git a/packages/smart-router/evm/abis/IBinPoolManager.ts b/packages/smart-router/evm/abis/IBinPoolManager.ts new file mode 100644 index 0000000000000..08fe441f13e6b --- /dev/null +++ b/packages/smart-router/evm/abis/IBinPoolManager.ts @@ -0,0 +1,586 @@ +export const binPoolManagerAbi = [ + { + inputs: [{ internalType: 'contract IVault', name: 'vault', type: 'address' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'AmountSpecifiedIsZero', type: 'error' }, + { + inputs: [{ internalType: 'uint24', name: 'id', type: 'uint24' }], + name: 'BinHelper__CompositionFactorFlawed', + type: 'error', + }, + { inputs: [{ internalType: 'uint24', name: 'id', type: 'uint24' }], name: 'BinPool__BurnZeroAmount', type: 'error' }, + { inputs: [], name: 'BinPool__EmptyLiquidityConfigs', type: 'error' }, + { inputs: [], name: 'BinPool__InsufficientAmountUnSpecified', type: 'error' }, + { inputs: [], name: 'BinPool__InvalidBurnInput', type: 'error' }, + { inputs: [], name: 'BinPool__NoLiquidityToReceiveFees', type: 'error' }, + { inputs: [{ internalType: 'uint24', name: 'id', type: 'uint24' }], name: 'BinPool__ZeroAmountsOut', type: 'error' }, + { inputs: [{ internalType: 'uint24', name: 'id', type: 'uint24' }], name: 'BinPool__ZeroShares', type: 'error' }, + { inputs: [{ internalType: 'uint16', name: 'binStep', type: 'uint16' }], name: 'BinStepTooLarge', type: 'error' }, + { inputs: [{ internalType: 'uint16', name: 'binStep', type: 'uint16' }], name: 'BinStepTooSmall', type: 'error' }, + { + inputs: [ + { internalType: 'address', name: 'currency0', type: 'address' }, + { internalType: 'address', name: 'currency1', type: 'address' }, + ], + name: 'CurrenciesInitializedOutOfOrder', + type: 'error', + }, + { inputs: [], name: 'EnforcedPause', type: 'error' }, + { inputs: [], name: 'HookConfigValidationError', type: 'error' }, + { inputs: [], name: 'HookDeltaExceedsSwapAmount', type: 'error' }, + { inputs: [], name: 'HookPermissionsValidationError', type: 'error' }, + { + inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], + name: 'InsufficientBinShareForDonate', + type: 'error', + }, + { inputs: [], name: 'InvalidBips', type: 'error' }, + { inputs: [], name: 'InvalidCaller', type: 'error' }, + { inputs: [], name: 'InvalidHookResponse', type: 'error' }, + { inputs: [{ internalType: 'uint24', name: 'fee', type: 'uint24' }], name: 'LPFeeTooLarge', type: 'error' }, + { inputs: [], name: 'LiquidityConfigurations__InvalidConfig', type: 'error' }, + { + inputs: [{ internalType: 'uint16', name: 'maxBinStep', type: 'uint16' }], + name: 'MaxBinStepTooSmall', + type: 'error', + }, + { inputs: [], name: 'PackedUint128Math__AddOverflow', type: 'error' }, + { inputs: [], name: 'PackedUint128Math__SubUnderflow', type: 'error' }, + { inputs: [], name: 'PoolAlreadyInitialized', type: 'error' }, + { inputs: [], name: 'PoolInvalidParameter', type: 'error' }, + { inputs: [], name: 'PoolManagerMismatch', type: 'error' }, + { inputs: [], name: 'PoolNotInitialized', type: 'error' }, + { inputs: [], name: 'ProtocolFeeCannotBeFetched', type: 'error' }, + { inputs: [{ internalType: 'uint24', name: 'fee', type: 'uint24' }], name: 'ProtocolFeeTooLarge', type: 'error' }, + { + inputs: [ + { internalType: 'uint256', name: 'x', type: 'uint256' }, + { internalType: 'int256', name: 'y', type: 'int256' }, + ], + name: 'Uint128x128Math__PowUnderflow', + type: 'error', + }, + { inputs: [], name: 'Uint256x256Math__MulDivOverflow', type: 'error' }, + { inputs: [], name: 'Uint256x256Math__MulShiftOverflow', type: 'error' }, + { inputs: [], name: 'UnauthorizedDynamicLPFeeUpdate', type: 'error' }, + { inputs: [], name: 'UnusedBitsNonZero', type: 'error' }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, + { indexed: false, internalType: 'uint256[]', name: 'ids', type: 'uint256[]' }, + { indexed: false, internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + { indexed: false, internalType: 'bytes32[]', name: 'amounts', type: 'bytes32[]' }, + ], + name: 'Burn', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, + { indexed: false, internalType: 'int128', name: 'amount0', type: 'int128' }, + { indexed: false, internalType: 'int128', name: 'amount1', type: 'int128' }, + { indexed: false, internalType: 'uint24', name: 'binId', type: 'uint24' }, + ], + name: 'Donate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: false, internalType: 'uint24', name: 'dynamicLPFee', type: 'uint24' }, + ], + name: 'DynamicLPFeeUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: true, internalType: 'Currency', name: 'currency0', type: 'address' }, + { indexed: true, internalType: 'Currency', name: 'currency1', type: 'address' }, + { indexed: false, internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { indexed: false, internalType: 'uint24', name: 'fee', type: 'uint24' }, + { indexed: false, internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + { indexed: false, internalType: 'uint24', name: 'activeId', type: 'uint24' }, + ], + name: 'Initialize', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, + { indexed: false, internalType: 'uint256[]', name: 'ids', type: 'uint256[]' }, + { indexed: false, internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + { indexed: false, internalType: 'bytes32[]', name: 'amounts', type: 'bytes32[]' }, + { indexed: false, internalType: 'bytes32', name: 'compositionFeeAmount', type: 'bytes32' }, + { indexed: false, internalType: 'bytes32', name: 'feeAmountToProtocol', type: 'bytes32' }, + ], + name: 'Mint', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'previousOwner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'address', name: 'account', type: 'address' }], + name: 'Paused', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: true, internalType: 'address', name: 'protocolFeeController', type: 'address' }], + name: 'ProtocolFeeControllerUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: false, internalType: 'uint24', name: 'protocolFee', type: 'uint24' }, + ], + name: 'ProtocolFeeUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'uint16', name: 'maxBinStep', type: 'uint16' }], + name: 'SetMaxBinStep', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'uint256', name: 'minLiquidity', type: 'uint256' }], + name: 'SetMinBinSharesForDonate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, + { indexed: false, internalType: 'int128', name: 'amount0', type: 'int128' }, + { indexed: false, internalType: 'int128', name: 'amount1', type: 'int128' }, + { indexed: false, internalType: 'uint24', name: 'activeId', type: 'uint24' }, + { indexed: false, internalType: 'uint24', name: 'fee', type: 'uint24' }, + { indexed: false, internalType: 'uint16', name: 'protocolFee', type: 'uint16' }, + ], + name: 'Swap', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'address', name: 'account', type: 'address' }], + name: 'Unpaused', + type: 'event', + }, + { + inputs: [], + name: 'MAX_BIN_STEP', + outputs: [{ internalType: 'uint16', name: '', type: 'uint16' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'MIN_BIN_SHARE_FOR_DONATE', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'MIN_BIN_STEP', + outputs: [{ internalType: 'uint16', name: '', type: 'uint16' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { + components: [ + { internalType: 'uint256[]', name: 'ids', type: 'uint256[]' }, + { internalType: 'uint256[]', name: 'amountsToBurn', type: 'uint256[]' }, + { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + ], + internalType: 'struct IBinPoolManager.BurnParams', + name: 'params', + type: 'tuple', + }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + name: 'burn', + outputs: [{ internalType: 'BalanceDelta', name: 'delta', type: 'int256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'Currency', name: 'currency', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'collectProtocolFees', + outputs: [{ internalType: 'uint256', name: 'amountCollected', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { internalType: 'uint128', name: 'amount0', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + name: 'donate', + outputs: [ + { internalType: 'BalanceDelta', name: 'delta', type: 'int256' }, + { internalType: 'uint24', name: 'binId', type: 'uint24' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes32', name: 'slot', type: 'bytes32' }], + name: 'extsload', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes32[]', name: 'slots', type: 'bytes32[]' }], + name: 'extsload', + outputs: [{ internalType: 'bytes32[]', name: '', type: 'bytes32[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { internalType: 'uint24', name: 'binId', type: 'uint24' }, + ], + name: 'getBin', + outputs: [ + { internalType: 'uint128', name: 'binReserveX', type: 'uint128' }, + { internalType: 'uint128', name: 'binReserveY', type: 'uint128' }, + { internalType: 'uint256', name: 'binLiquidity', type: 'uint256' }, + { internalType: 'uint256', name: 'totalShares', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { internalType: 'bool', name: 'swapForY', type: 'bool' }, + { internalType: 'uint24', name: 'binId', type: 'uint24' }, + ], + name: 'getNextNonEmptyBin', + outputs: [{ internalType: 'uint24', name: 'nextId', type: 'uint24' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'PoolId', name: 'id', type: 'bytes32' }, + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'uint24', name: 'binId', type: 'uint24' }, + { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + ], + name: 'getPosition', + outputs: [ + { + components: [{ internalType: 'uint256', name: 'share', type: 'uint256' }], + internalType: 'struct BinPosition.Info', + name: 'position', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'PoolId', name: 'id', type: 'bytes32' }], + name: 'getSlot0', + outputs: [ + { internalType: 'uint24', name: 'activeId', type: 'uint24' }, + { internalType: 'uint24', name: 'protocolFee', type: 'uint24' }, + { internalType: 'uint24', name: 'lpFee', type: 'uint24' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { internalType: 'uint24', name: 'activeId', type: 'uint24' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { + components: [ + { internalType: 'bytes32[]', name: 'liquidityConfigs', type: 'bytes32[]' }, + { internalType: 'bytes32', name: 'amountIn', type: 'bytes32' }, + { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + ], + internalType: 'struct IBinPoolManager.MintParams', + name: 'params', + type: 'tuple', + }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + name: 'mint', + outputs: [ + { internalType: 'BalanceDelta', name: 'delta', type: 'int256' }, + { + components: [ + { internalType: 'uint256[]', name: 'ids', type: 'uint256[]' }, + { internalType: 'bytes32[]', name: 'amounts', type: 'bytes32[]' }, + { internalType: 'uint256[]', name: 'liquidityMinted', type: 'uint256[]' }, + ], + internalType: 'struct BinPool.MintArrays', + name: 'mintArray', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { inputs: [], name: 'pause', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [], + name: 'paused', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'PoolId', name: 'id', type: 'bytes32' }], + name: 'poolIdToPoolKey', + outputs: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'PoolId', name: 'id', type: 'bytes32' }], + name: 'pools', + outputs: [ + { + components: [ + { internalType: 'uint24', name: 'activeId', type: 'uint24' }, + { internalType: 'uint24', name: 'protocolFee', type: 'uint24' }, + { internalType: 'uint24', name: 'lpFee', type: 'uint24' }, + ], + internalType: 'struct BinPool.Slot0', + name: 'slot0', + type: 'tuple', + }, + { internalType: 'bytes32', name: 'level0', type: 'bytes32' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'protocolFeeController', + outputs: [{ internalType: 'contract IProtocolFeeController', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'Currency', name: 'currency', type: 'address' }], + name: 'protocolFeesAccrued', + outputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint16', name: 'maxBinStep', type: 'uint16' }], + name: 'setMaxBinStep', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'minBinShare', type: 'uint256' }], + name: 'setMinBinSharesForDonate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { internalType: 'uint24', name: 'newProtocolFee', type: 'uint24' }, + ], + name: 'setProtocolFee', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'contract IProtocolFeeController', name: 'controller', type: 'address' }], + name: 'setProtocolFeeController', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { internalType: 'bool', name: 'swapForY', type: 'bool' }, + { internalType: 'int128', name: 'amountSpecified', type: 'int128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + name: 'swap', + outputs: [{ internalType: 'BalanceDelta', name: 'delta', type: 'int256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'unpause', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'key', + type: 'tuple', + }, + { internalType: 'uint24', name: 'newDynamicLPFee', type: 'uint24' }, + ], + name: 'updateDynamicLPFee', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'vault', + outputs: [{ internalType: 'contract IVault', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/smart-router/evm/abis/IBinQuoter.ts b/packages/smart-router/evm/abis/IBinQuoter.ts new file mode 100644 index 0000000000000..0c93bd84a856e --- /dev/null +++ b/packages/smart-router/evm/abis/IBinQuoter.ts @@ -0,0 +1,294 @@ +export const binQuoterAbi = [ + { + inputs: [{ internalType: 'address', name: '_poolManager', type: 'address' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [{ internalType: 'PoolId', name: 'poolId', type: 'bytes32' }], name: 'NotEnoughLiquidity', type: 'error' }, + { inputs: [], name: 'NotSelf', type: 'error' }, + { inputs: [], name: 'NotVault', type: 'error' }, + { inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], name: 'QuoteSwap', type: 'error' }, + { inputs: [], name: 'UnexpectedCallSuccess', type: 'error' }, + { + inputs: [{ internalType: 'bytes', name: 'revertData', type: 'bytes' }], + name: 'UnexpectedRevertBytes', + type: 'error', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactInput', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactInputSingle', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactOutput', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactOutputSingle', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes', name: 'data', type: 'bytes' }], + name: 'lockAcquired', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'poolManager', + outputs: [{ internalType: 'contract IBinPoolManager', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInput', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactOutput', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactOutputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'vault', + outputs: [{ internalType: 'contract IVault', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/smart-router/evm/abis/ICLQuoter.ts b/packages/smart-router/evm/abis/ICLQuoter.ts new file mode 100644 index 0000000000000..031a400db453a --- /dev/null +++ b/packages/smart-router/evm/abis/ICLQuoter.ts @@ -0,0 +1,294 @@ +export const clQuoterAbi = [ + { + inputs: [{ internalType: 'address', name: '_poolManager', type: 'address' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [{ internalType: 'PoolId', name: 'poolId', type: 'bytes32' }], name: 'NotEnoughLiquidity', type: 'error' }, + { inputs: [], name: 'NotSelf', type: 'error' }, + { inputs: [], name: 'NotVault', type: 'error' }, + { inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], name: 'QuoteSwap', type: 'error' }, + { inputs: [], name: 'UnexpectedCallSuccess', type: 'error' }, + { + inputs: [{ internalType: 'bytes', name: 'revertData', type: 'bytes' }], + name: 'UnexpectedRevertBytes', + type: 'error', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactInput', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactInputSingle', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactOutput', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: '_quoteExactOutputSingle', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes', name: 'data', type: 'bytes' }], + name: 'lockAcquired', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'poolManager', + outputs: [{ internalType: 'contract ICLPoolManager', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInput', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'Currency', name: 'exactCurrency', type: 'address' }, + { + components: [ + { internalType: 'Currency', name: 'intermediateCurrency', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PathKey[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + ], + internalType: 'struct IQuoter.QuoteExactParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactOutput', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'Currency', name: 'currency0', type: 'address' }, + { internalType: 'Currency', name: 'currency1', type: 'address' }, + { internalType: 'contract IHooks', name: 'hooks', type: 'address' }, + { internalType: 'contract IPoolManager', name: 'poolManager', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'bytes32', name: 'parameters', type: 'bytes32' }, + ], + internalType: 'struct PoolKey', + name: 'poolKey', + type: 'tuple', + }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'uint128', name: 'exactAmount', type: 'uint128' }, + { internalType: 'bytes', name: 'hookData', type: 'bytes' }, + ], + internalType: 'struct IQuoter.QuoteExactSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactOutputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'vault', + outputs: [{ internalType: 'contract IVault', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/smart-router/evm/abis/IV4MixedRouteQuoter.ts b/packages/smart-router/evm/abis/IV4MixedRouteQuoter.ts new file mode 100644 index 0000000000000..9b0cba79684f2 --- /dev/null +++ b/packages/smart-router/evm/abis/IV4MixedRouteQuoter.ts @@ -0,0 +1,169 @@ +export const v4MixedRouteQuoterAbi = [ + { + inputs: [ + { internalType: 'address', name: '_factoryV3', type: 'address' }, + { internalType: 'address', name: '_factoryV2', type: 'address' }, + { internalType: 'address', name: '_factoryStable', type: 'address' }, + { internalType: 'address', name: '_WETH9', type: 'address' }, + { internalType: 'contract ICLQuoter', name: '_clQuoter', type: 'address' }, + { internalType: 'contract IBinQuoter', name: '_binQuoter', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'INVALID_ADDRESS', type: 'error' }, + { inputs: [], name: 'InputLengthMismatch', type: 'error' }, + { inputs: [], name: 'InvalidPath', type: 'error' }, + { inputs: [], name: 'InvalidPoolKeyCurrency', type: 'error' }, + { inputs: [], name: 'NoActions', type: 'error' }, + { + inputs: [ + { internalType: 'uint8', name: 'bits', type: 'uint8' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + ], + name: 'SafeCastOverflowedUintDowncast', + type: 'error', + }, + { + inputs: [{ internalType: 'uint256', name: 'value', type: 'uint256' }], + name: 'SafeCastOverflowedUintToInt', + type: 'error', + }, + { inputs: [{ internalType: 'uint256', name: 'action', type: 'uint256' }], name: 'UnsupportedAction', type: 'error' }, + { + inputs: [], + name: 'WETH9', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'binQuoter', + outputs: [{ internalType: 'contract IBinQuoter', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'clQuoter', + outputs: [{ internalType: 'contract ICLQuoter', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factoryStable', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factoryV2', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factoryV3', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'int256', name: 'amount0Delta', type: 'int256' }, + { internalType: 'int256', name: 'amount1Delta', type: 'int256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'pancakeV3SwapCallback', + outputs: [], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'flag', type: 'uint256' }, + ], + internalType: 'struct IMixedQuoter.QuoteExactInputSingleStableParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInputSingleStable', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + ], + internalType: 'struct IMixedQuoter.QuoteExactInputSingleV2Params', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInputSingleV2', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'uint160', name: 'sqrtPriceLimitX96', type: 'uint160' }, + ], + internalType: 'struct IMixedQuoter.QuoteExactInputSingleV3Params', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInputSingleV3', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint160', name: 'sqrtPriceX96After', type: 'uint160' }, + { internalType: 'uint32', name: 'initializedTicksCrossed', type: 'uint32' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: 'paths', type: 'address[]' }, + { internalType: 'bytes', name: 'actions', type: 'bytes' }, + { internalType: 'bytes[]', name: 'params', type: 'bytes[]' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + ], + name: 'quoteMixedExactInput', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/packages/smart-router/evm/constants/v4.ts b/packages/smart-router/evm/constants/v4.ts new file mode 100644 index 0000000000000..ffec8d762f4b5 --- /dev/null +++ b/packages/smart-router/evm/constants/v4.ts @@ -0,0 +1,15 @@ +import { ChainId } from '@pancakeswap/chains' + +export const V4_CL_QUOTER_ADDRESSES = { + [ChainId.BSC_TESTNET]: '0xD2a534e28efb7e44Da94ae942F892E71F7462142', +} + +export const V4_BIN_QUOTER_ADDRESSES = { + [ChainId.BSC_TESTNET]: '0x740469475C0e268F2110444BB8Bc3114E5321B90', +} + +export const V4_MIXED_ROUTE_QUOTER_ADDRESSES = { + [ChainId.BSC_TESTNET]: '0x5B5209dBFD9dB69Fcc14f6591fD23089c229E143', +} + +export const V4_SUPPORTED_CHAINS = [ChainId.BSC_TESTNET] diff --git a/packages/smart-router/evm/index.test.ts b/packages/smart-router/evm/index.test.ts index 565975b739564..cff85d9f7a5b1 100644 --- a/packages/smart-router/evm/index.test.ts +++ b/packages/smart-router/evm/index.test.ts @@ -40,6 +40,7 @@ test('exports', () => { "SwapRouter", "PoolType", "RouteType", + "V4MixedQuoterActions", "V4Router", ] `) @@ -54,6 +55,7 @@ test('SmartRouter exports', () => { "getV2PoolsOnChain", "getStablePoolsOnChain", "getV3PoolsWithoutTicksOnChain", + "createOnChainPoolFactory", "getV3PoolSubgraph", "getV2PoolSubgraph", "getAllV3PoolsFromSubgraph", @@ -94,6 +96,8 @@ test('SmartRouter exports', () => { "isV2Pool", "isV3Pool", "isStablePool", + "isV4ClPool", + "isV4BinPool", "getMidPrice", "involvesCurrency", "encodeMixedRouteToPath", diff --git a/packages/smart-router/evm/v3-router/functions/getBestRouteCombinationByQuotes.ts b/packages/smart-router/evm/v3-router/functions/getBestRouteCombinationByQuotes.ts index 3dcbff7a76541..ba7fbc4a40f3f 100644 --- a/packages/smart-router/evm/v3-router/functions/getBestRouteCombinationByQuotes.ts +++ b/packages/smart-router/evm/v3-router/functions/getBestRouteCombinationByQuotes.ts @@ -8,7 +8,7 @@ import Queue from 'mnemonist/queue.js' import { usdGasTokensByChain } from '../../constants' import { BestRoutes, L1ToL2GasCosts, RouteWithQuote } from '../types' -import { getPoolAddress, isV2Pool, isV3Pool, logger } from '../utils' +import { getPoolAddress, isStablePool, isV2Pool, isV3Pool, logger } from '../utils' interface Config { minSplits?: number @@ -257,7 +257,10 @@ export function getBestSwapRouteBy( if (isV3Pool(p)) { return `V3 fee ${p.fee} ${p.token0.symbol}-${p.token1.symbol}` } - return `Stable ${p.balances.map((b) => b.currency).join('-')}` + if (isStablePool(p)) { + return `Stable ${p.balances.map((b) => b.currency).join('-')}` + } + return `Unsupported pool ${p}` }) .join(', ')} ${r.quote.toExact()}`, ) diff --git a/packages/smart-router/evm/v3-router/providers/onChainQuoteProvider.ts b/packages/smart-router/evm/v3-router/providers/onChainQuoteProvider.ts index bc008dc921b38..a1bd03632d071 100644 --- a/packages/smart-router/evm/v3-router/providers/onChainQuoteProvider.ts +++ b/packages/smart-router/evm/v3-router/providers/onChainQuoteProvider.ts @@ -1,5 +1,5 @@ import { ChainId } from '@pancakeswap/chains' -import { BigintIsh, Currency, CurrencyAmount } from '@pancakeswap/sdk' +import { BigintIsh, Currency, CurrencyAmount, getCurrencyAddress } from '@pancakeswap/swap-sdk-core' import { AbortControl, isAbortError } from '@pancakeswap/utils/abortControl' import retry from 'async-retry' import { Abi, Address } from 'viem' @@ -21,6 +21,13 @@ import { import { encodeMixedRouteToPath, getQuoteCurrency, isStablePool, isV2Pool, isV3Pool } from '../utils' import { Result } from './multicallProvider' import { PancakeMulticallProvider } from './multicallSwapProvider' +import { V4_BIN_QUOTER_ADDRESSES, V4_CL_QUOTER_ADDRESSES, V4_MIXED_ROUTE_QUOTER_ADDRESSES } from '../../constants/v4' +import { clQuoterAbi } from '../../abis/ICLQuoter' +import { PathKey, encodeV4RouteToPath } from '../utils/encodeV4RouteToPath' +import { v4MixedRouteQuoterAbi } from '../../abis/IV4MixedRouteQuoter' +import { encodeV4MixedRouteActions } from '../utils/encodeV4MixedRouteActions' +import { encodeV4MixedRouteParams } from '../utils/encodeV4MixedRouteParams' +import { binQuoterAbi } from '../../abis/IBinQuoter' const DEFAULT_BATCH_RETRIES = 2 @@ -47,9 +54,17 @@ const SUCCESS_RATE_CONFIG = { [ChainId.BASE_SEPOLIA]: 0.1, } as const satisfies Record +type V4ClInputs = [ + { + exactAmount: string + exactCurrency: string + path: PathKey[] + }, +] type V3Inputs = [string, string] type MixedInputs = [string, number[], string] -type CallInputs = V3Inputs | MixedInputs +type V4MixedInputs = [string[], string, string[], string] +type CallInputs = V3Inputs | MixedInputs | V4ClInputs | V4MixedInputs type AdjustQuoteForGasHandler = (params: { isExactIn?: boolean @@ -325,7 +340,7 @@ function validateSuccessRate( // } function processQuoteResults( - quoteResults: (Result<[bigint, bigint[], number[], bigint]> | null)[], + quoteResults: (Result<[bigint, bigint[], number[], bigint] | [bigint, bigint]> | null)[], routes: RouteWithoutQuote[], gasModel: GasModel, adjustQuoteForGas: AdjustQuoteForGasHandler, @@ -358,14 +373,16 @@ function processQuoteResults( continue } + const [quoteRaw] = quoteResult.result + const initializedTickCrossedList = quoteResult.result.length === 4 ? quoteResult.result[2] : [] const quoteCurrency = getQuoteCurrency(route, route.amount.currency) - const quote = CurrencyAmount.fromRawAmount(quoteCurrency.wrapped, quoteResult.result[0].toString()) + const quote = CurrencyAmount.fromRawAmount(quoteCurrency, quoteRaw.toString()) const { gasEstimate, gasCostInToken, gasCostInUSD } = gasModel.estimateGasCost( { ...route, quote, }, - { initializedTickCrossedList: quoteResult.result[2] }, + { initializedTickCrossedList }, ) routesWithQuote.push({ @@ -439,3 +456,41 @@ export const createV3OnChainQuoteProvider = onChainQuoteProviderFactory({ `0x${route.amount.quotient.toString(16)}`, ], }) + +export const createV4ClOnChainQuoteProvider = onChainQuoteProviderFactory({ + getQuoterAddress: (chainId) => (V4_CL_QUOTER_ADDRESSES as any)[chainId], + getQuoteFunctionName: (isExactIn) => (isExactIn ? 'quoteExactInput' : 'quoteExactOutput'), + abi: clQuoterAbi, + getCallInputs: (route, isExactIn) => [ + { + exactCurrency: getCurrencyAddress(isExactIn ? route.input : route.output), + path: encodeV4RouteToPath(route, !isExactIn), + exactAmount: `0x${route.amount.quotient.toString(16)}`, + }, + ], +}) + +export const createMixedRouteOnChainQuoteProviderV2 = onChainQuoteProviderFactory({ + getQuoterAddress: (chainId) => (V4_MIXED_ROUTE_QUOTER_ADDRESSES as any)[chainId], + getQuoteFunctionName: () => 'quoteMixedExactInput', + abi: v4MixedRouteQuoterAbi, + getCallInputs: (route) => [ + route.path.map((p) => getCurrencyAddress(p)), + encodeV4MixedRouteActions(route), + encodeV4MixedRouteParams(route), + `0x${route.amount.quotient.toString(16)}`, + ], +}) + +export const createV4BinOnChainQuoteProvider = onChainQuoteProviderFactory({ + getQuoterAddress: (chainId) => (V4_BIN_QUOTER_ADDRESSES as any)[chainId], + getQuoteFunctionName: (isExactIn) => (isExactIn ? 'quoteExactInput' : 'quoteExactOutput'), + abi: binQuoterAbi, + getCallInputs: (route, isExactIn) => [ + { + exactCurrency: getCurrencyAddress(isExactIn ? route.input : route.output), + path: encodeV4RouteToPath(route, !isExactIn), + exactAmount: `0x${route.amount.quotient.toString(16)}`, + }, + ], +}) diff --git a/packages/smart-router/evm/v3-router/providers/poolProviders/internalTypes.ts b/packages/smart-router/evm/v3-router/providers/poolProviders/internalTypes.ts index 03661efd1306b..956f3e05569b8 100644 --- a/packages/smart-router/evm/v3-router/providers/poolProviders/internalTypes.ts +++ b/packages/smart-router/evm/v3-router/providers/poolProviders/internalTypes.ts @@ -1,12 +1,11 @@ import { Currency } from '@pancakeswap/sdk' import { FeeAmount } from '@pancakeswap/v3-sdk' -import { Address } from 'viem' // Information used to identify a pool export interface PoolMeta { currencyA: Currency currencyB: Currency - address: Address + id: `0x${string}` } export interface V3PoolMeta extends PoolMeta { diff --git a/packages/smart-router/evm/v3-router/providers/poolProviders/onChainPoolProviders.ts b/packages/smart-router/evm/v3-router/providers/poolProviders/onChainPoolProviders.ts index 8c9c86d94c5d5..388d181e9be6b 100644 --- a/packages/smart-router/evm/v3-router/providers/poolProviders/onChainPoolProviders.ts +++ b/packages/smart-router/evm/v3-router/providers/poolProviders/onChainPoolProviders.ts @@ -14,9 +14,9 @@ import { PoolMeta, V3PoolMeta } from './internalTypes' export const getV2PoolsOnChain = createOnChainPoolFactory({ abi: pancakePairABI, getPossiblePoolMetas: ([currencyA, currencyB]) => [ - { address: computeV2PoolAddress(currencyA.wrapped, currencyB.wrapped), currencyA, currencyB }, + { id: computeV2PoolAddress(currencyA.wrapped, currencyB.wrapped), currencyA, currencyB }, ], - buildPoolInfoCalls: ({ address }) => [ + buildPoolInfoCalls: ({ id: address }) => [ { address, functionName: 'getReserves', @@ -53,12 +53,12 @@ export const getStablePoolsOnChain = createOnChainPoolFactory ({ - address: stableSwapAddress, + id: stableSwapAddress, currencyA, currencyB, })) }, - buildPoolInfoCalls: ({ address }) => [ + buildPoolInfoCalls: ({ id: address }) => [ { address, functionName: 'balances', @@ -85,7 +85,7 @@ export const getStablePoolsOnChain = createOnChainPoolFactory { + buildPool: ({ currencyA, currencyB, id: address }, [balance0, balance1, a, fee, feeDenominator]) => { if (!balance0 || !balance1 || !a || !fee || !feeDenominator) { return null } @@ -113,7 +113,7 @@ export const getV3PoolsWithoutTicksOnChain = createOnChainPoolFactory ({ - address: computeV3PoolAddress({ + id: computeV3PoolAddress({ deployerAddress, tokenA: currencyA.wrapped, tokenB: currencyB.wrapped, @@ -124,7 +124,7 @@ export const getV3PoolsWithoutTicksOnChain = createOnChainPoolFactory [ + buildPoolInfoCalls: ({ id: address, currencyA, currencyB }) => [ { address, functionName: 'liquidity', @@ -146,7 +146,7 @@ export const getV3PoolsWithoutTicksOnChain = createOnChainPoolFactory { + buildPool: ({ currencyA, currencyB, fee, id: address }, [liquidity, slot0, balanceA, balanceB]) => { if (!slot0) { return null } @@ -187,7 +187,7 @@ interface OnChainPoolFactoryParams TPool | null } -function createOnChainPoolFactory< +export function createOnChainPoolFactory< TPool extends Pool, TPoolMeta extends PoolMeta = PoolMeta, TAbi extends Abi | unknown[] = Abi, @@ -213,9 +213,9 @@ function createOnChainPoolFactory< for (const pair of pairs) { const possiblePoolMetas = getPossiblePoolMetas(pair) for (const meta of possiblePoolMetas) { - if (!poolAddressSet.has(meta.address)) { + if (!poolAddressSet.has(meta.id)) { poolMetas.push(meta) - poolAddressSet.add(meta.address) + poolAddressSet.add(meta.id) } } } diff --git a/packages/smart-router/evm/v3-router/providers/poolProviders/subgraphPoolProviders.ts b/packages/smart-router/evm/v3-router/providers/poolProviders/subgraphPoolProviders.ts index 6b57177ecd8bd..3da6e5fcc15a7 100644 --- a/packages/smart-router/evm/v3-router/providers/poolProviders/subgraphPoolProviders.ts +++ b/packages/smart-router/evm/v3-router/providers/poolProviders/subgraphPoolProviders.ts @@ -56,7 +56,7 @@ function subgraphPoolProviderFactory({ for (const pair of pairs) { const metas = getPoolMetas(pair) for (const meta of metas) { - metaMap.set(meta.address.toLocaleLowerCase() as Address, meta) + metaMap.set(meta.id.toLocaleLowerCase() as Address, meta) } } const addresses = Array.from(metaMap.keys()) @@ -75,7 +75,7 @@ function subgraphPoolProviderFactory({ const getV3PoolMeta = memoize( ([currencyA, currencyB, feeAmount]: [Currency, Currency, FeeAmount]) => ({ - address: Pool.getAddress(currencyA.wrapped, currencyB.wrapped, feeAmount), + id: Pool.getAddress(currencyA.wrapped, currencyB.wrapped, feeAmount), currencyA, currencyB, fee: feeAmount, @@ -144,7 +144,7 @@ export const getV3PoolSubgraph = subgraphPoolProviderFactory { @@ -205,7 +205,7 @@ export const getV2PoolSubgraph = subgraphPoolProviderFactory { const { onChainProvider, multicallConfigs, gasLimit } = config const offChainQuoteProvider = createOffChainQuoteProvider() - const mixedRouteOnChainQuoteProvider = createMixedRouteOnChainQuoteProvider({ + const mixedRouteOnChainQuoteProviderV1 = createMixedRouteOnChainQuoteProvider({ + onChainProvider, + multicallConfigs, + gasLimit, + }) + const mixedRouteOnChainQuoteProviderV2 = createMixedRouteOnChainQuoteProviderV2({ onChainProvider, multicallConfigs, gasLimit, }) const v3OnChainQuoteProvider = createV3OnChainQuoteProvider({ onChainProvider, multicallConfigs, gasLimit }) + const v4ClOnChainQuoteProvider = createV4ClOnChainQuoteProvider({ onChainProvider, multicallConfigs, gasLimit }) + const v4BinOnChainQuoteProvider = createV4BinOnChainQuoteProvider({ onChainProvider, multicallConfigs, gasLimit }) const createGetRouteWithQuotes = (isExactIn = true) => { const getOffChainQuotes = isExactIn ? offChainQuoteProvider.getRouteWithQuotesExactIn : offChainQuoteProvider.getRouteWithQuotesExactOut - const getMixedRouteQuotes = isExactIn - ? mixedRouteOnChainQuoteProvider.getRouteWithQuotesExactIn - : mixedRouteOnChainQuoteProvider.getRouteWithQuotesExactOut const getV3Quotes = isExactIn ? v3OnChainQuoteProvider.getRouteWithQuotesExactIn : v3OnChainQuoteProvider.getRouteWithQuotesExactOut + const getV4ClQuotes = isExactIn + ? v4ClOnChainQuoteProvider.getRouteWithQuotesExactIn + : v4ClOnChainQuoteProvider.getRouteWithQuotesExactOut + const getV4BinQuotes = isExactIn + ? v4BinOnChainQuoteProvider.getRouteWithQuotesExactIn + : v4BinOnChainQuoteProvider.getRouteWithQuotesExactOut + const createMixedRouteQuoteFetcher = (chainId: ChainId) => { + const mixedRouteOnChainQuoteProvider = V4_SUPPORTED_CHAINS.includes(chainId) + ? mixedRouteOnChainQuoteProviderV2 + : mixedRouteOnChainQuoteProviderV1 + return isExactIn + ? mixedRouteOnChainQuoteProvider.getRouteWithQuotesExactIn + : mixedRouteOnChainQuoteProvider.getRouteWithQuotesExactOut + } return async function getRoutesWithQuotes( routes: RouteWithoutQuote[], { blockNumber, gasModel, signal }: QuoterOptions, ): Promise { + const { chainId } = routes[0].input + const getMixedRouteQuotes = createMixedRouteQuoteFetcher(chainId) + + const v4ClRoutes: RouteWithoutQuote[] = [] + const v4BinRoutes: RouteWithoutQuote[] = [] const v3SingleHopRoutes: RouteWithoutQuote[] = [] const v3MultihopRoutes: RouteWithoutQuote[] = [] const mixedRoutesHaveV3Pool: RouteWithoutQuote[] = [] @@ -46,6 +77,14 @@ export function createQuoteProvider(config: QuoterConfig): QuoteProvider isV3Pool(pool))) { mixedRoutesHaveV3Pool.push(route) @@ -59,6 +98,8 @@ export function createQuoteProvider(config: QuoterConfig): QuoteProvider result.status === 'rejected')) { throw new Error(results.map((result) => (result as PromiseRejectedResult).reason).join(',')) diff --git a/packages/smart-router/evm/v3-router/schema.ts b/packages/smart-router/evm/v3-router/schema.ts index 652b71f0f3eb9..fd604bb2503ef 100644 --- a/packages/smart-router/evm/v3-router/schema.ts +++ b/packages/smart-router/evm/v3-router/schema.ts @@ -11,6 +11,7 @@ const zTradeType = z.nativeEnum(TradeType) const zPoolType = z.nativeEnum(PoolType) const zPoolTypes = z.array(zPoolType) const zAddress = z.custom
((val) => /^0x[a-fA-F0-9]{40}$/.test(val as string)) +const zHex = z.custom
((val) => /^0x[a-fA-F0-9]*$/.test(val as string)) const zBigNumber = z.string().regex(/^[0-9]+$/) const zCurrency = z .object({ @@ -53,10 +54,43 @@ const zStablePool = z balances: z.array(zCurrencyAmount), amplifier: zBigNumber, fee: z.string(), + address: zAddress, + }) + .required() +const zV4ClPool = z + .object({ + type: z.literal(PoolType.V4CL), + currency0: zCurrency, + currency1: zCurrency, + fee: zFee, + liquidity: zBigNumber, + sqrtRatioX96: zBigNumber, + tick: z.number(), + tickSpacing: z.number(), + poolManager: zAddress, + id: zHex, }) .required() + .extend({ + hooks: zAddress.optional(), + }) +const zV4BinPool = z + .object({ + type: z.literal(PoolType.V4BIN), + currency0: zCurrency, + currency1: zCurrency, + fee: zFee, + activeId: z.number(), + binStep: z.number(), + poolManager: zAddress, + id: zHex, + }) + .required() + .extend({ + hooks: zAddress.optional(), + }) -export const zPools = z.array(z.union([zV2Pool, zV3Pool, zStablePool])) +export const zPools = z.array(z.union([zV2Pool, zV3Pool, zStablePool, zV4ClPool, zV4BinPool])) export const zRouterGetParams = z .object({ diff --git a/packages/smart-router/evm/v3-router/smartRouter.ts b/packages/smart-router/evm/v3-router/smartRouter.ts index 1a7d3f7960084..65869cca02baf 100644 --- a/packages/smart-router/evm/v3-router/smartRouter.ts +++ b/packages/smart-router/evm/v3-router/smartRouter.ts @@ -11,6 +11,8 @@ export { isV2Pool, isV3Pool, isStablePool, + isV4ClPool, + isV4BinPool, getMidPrice, involvesCurrency, encodeMixedRouteToPath, diff --git a/packages/smart-router/evm/v3-router/types/index.ts b/packages/smart-router/evm/v3-router/types/index.ts index 1a7ab90943b19..87d708a977ad8 100644 --- a/packages/smart-router/evm/v3-router/types/index.ts +++ b/packages/smart-router/evm/v3-router/types/index.ts @@ -5,3 +5,4 @@ export * from './providers' export * from './gasModel' export * from './gasCost' export * from './poolSelector' +export * from './v4MixedQuoter' diff --git a/packages/smart-router/evm/v3-router/types/pool.ts b/packages/smart-router/evm/v3-router/types/pool.ts index 00d2db831e502..251604d6095f3 100644 --- a/packages/smart-router/evm/v3-router/types/pool.ts +++ b/packages/smart-router/evm/v3-router/types/pool.ts @@ -6,6 +6,8 @@ export enum PoolType { V2, V3, STABLE, + V4CL, + V4BIN, } export interface BasePool { @@ -48,7 +50,49 @@ export interface V3Pool extends BasePool { reserve1?: CurrencyAmount } -export type Pool = V2Pool | V3Pool | StablePool +export type V4ClPool = BasePool & { + id: `0x${string}` + type: PoolType.V4CL + currency0: Currency + currency1: Currency + fee: number + tickSpacing: number + liquidity: bigint + sqrtRatioX96: bigint + tick: number + hooks?: Address + poolManager: Address + + // Allow pool with no ticks data provided + ticks?: Tick[] + + reserve0?: CurrencyAmount + reserve1?: CurrencyAmount +} + +type ActiveId = number +type Reserve = { + reserveX: bigint + reserveY: bigint +} +export type V4BinPool = BasePool & { + id: `0x${string}` + type: PoolType.V4BIN + currency0: Currency + currency1: Currency + fee: number + binStep: number + activeId: ActiveId + + hooks?: Address + poolManager: Address + reserveOfBin?: Record + + reserve0?: CurrencyAmount + reserve1?: CurrencyAmount +} + +export type Pool = V2Pool | V3Pool | StablePool | V4BinPool | V4ClPool export interface WithTvl { tvlUSD: bigint diff --git a/packages/smart-router/evm/v3-router/types/route.ts b/packages/smart-router/evm/v3-router/types/route.ts index 167ed0af5c431..570f641c40119 100644 --- a/packages/smart-router/evm/v3-router/types/route.ts +++ b/packages/smart-router/evm/v3-router/types/route.ts @@ -9,6 +9,8 @@ export enum RouteType { STABLE, MIXED, MM, + V4CL, + V4BIN, } export interface BaseRoute { diff --git a/packages/smart-router/evm/v3-router/types/v4MixedQuoter.ts b/packages/smart-router/evm/v3-router/types/v4MixedQuoter.ts new file mode 100644 index 0000000000000..fd19df0df70b9 --- /dev/null +++ b/packages/smart-router/evm/v3-router/types/v4MixedQuoter.ts @@ -0,0 +1,8 @@ +export enum V4MixedQuoterActions { + SS_2_EXACT_INPUT_SINGLE = 0, + SS_3_EXACT_INPUT_SINGLE = 1, + V2_EXACT_INPUT_SINGLE = 2, + V3_EXACT_INPUT_SINGLE = 3, + V4_CL_EXACT_INPUT_SINGLE = 4, + V4_BIN_EXACT_INPUT_SINGLE = 5, +} diff --git a/packages/smart-router/evm/v3-router/utils/encodeV4MixedRouteActions.ts b/packages/smart-router/evm/v3-router/utils/encodeV4MixedRouteActions.ts new file mode 100644 index 0000000000000..73950433885ca --- /dev/null +++ b/packages/smart-router/evm/v3-router/utils/encodeV4MixedRouteActions.ts @@ -0,0 +1,29 @@ +import { Hex, bytesToHex } from 'viem' + +import { BaseRoute, V4MixedQuoterActions } from '../types' +import { isStablePool, isV2Pool, isV3Pool, isV4BinPool, isV4ClPool } from './pool' + +export function encodeV4MixedRouteActions(route: BaseRoute): Hex { + return bytesToHex( + new Uint8Array( + route.pools.map((p) => { + if (isV2Pool(p)) { + return V4MixedQuoterActions.V2_EXACT_INPUT_SINGLE + } + if (isV3Pool(p)) { + return V4MixedQuoterActions.V3_EXACT_INPUT_SINGLE + } + if (isStablePool(p)) { + return V4MixedQuoterActions.SS_2_EXACT_INPUT_SINGLE + } + if (isV4ClPool(p)) { + return V4MixedQuoterActions.V4_CL_EXACT_INPUT_SINGLE + } + if (isV4BinPool(p)) { + return V4MixedQuoterActions.V4_BIN_EXACT_INPUT_SINGLE + } + throw new Error(`Unrecognized pool type ${p}`) + }), + ), + ) +} diff --git a/packages/smart-router/evm/v3-router/utils/encodeV4MixedRouteParams.ts b/packages/smart-router/evm/v3-router/utils/encodeV4MixedRouteParams.ts new file mode 100644 index 0000000000000..53d5c2e8274d7 --- /dev/null +++ b/packages/smart-router/evm/v3-router/utils/encodeV4MixedRouteParams.ts @@ -0,0 +1,76 @@ +import { PoolKey, encodePoolKey } from '@pancakeswap/v4-sdk' +import { getCurrencyAddress } from '@pancakeswap/swap-sdk-core' +import { Hex, encodeAbiParameters, parseAbiParameters } from 'viem' + +import { BaseRoute } from '../types' +import { isStablePool, isV2Pool, isV3Pool, isV4BinPool, isV4ClPool } from './pool' + +const v4RouteParamsAbi = [ + { + components: [ + { + components: [ + { name: 'currency0', type: 'address' }, + { name: 'currency1', type: 'address' }, + { name: 'hooks', type: 'address' }, + { name: 'poolManager', type: 'address' }, + { name: 'fee', type: 'uint24' }, + { name: 'parameters', type: 'bytes32' }, + ], + name: 'poolKey', + type: 'tuple', + }, + { name: 'hookData', type: 'bytes' }, + ], + name: 'params', + type: 'tuple', + }, +] as const + +export function encodeV4MixedRouteParams(route: BaseRoute): Hex[] { + return route.pools.map((p) => { + if (isV2Pool(p) || isStablePool(p)) { + return '0x' + } + if (isV3Pool(p)) { + return encodeAbiParameters(parseAbiParameters('uint24'), [p.fee]) + } + if (isV4ClPool(p)) { + const poolKey: PoolKey<'CL'> = { + currency0: getCurrencyAddress(p.currency0), + currency1: getCurrencyAddress(p.currency1), + fee: p.fee, + hooks: p.hooks, + poolManager: p.poolManager, + parameters: { + tickSpacing: p.tickSpacing, + }, + } + return encodeAbiParameters(v4RouteParamsAbi, [ + { + poolKey: encodePoolKey(poolKey), + hookData: '0x', + }, + ]) + } + if (isV4BinPool(p)) { + const poolKey: PoolKey<'Bin'> = { + currency0: getCurrencyAddress(p.currency0), + currency1: getCurrencyAddress(p.currency1), + fee: p.fee, + hooks: p.hooks, + poolManager: p.poolManager, + parameters: { + binStep: Number(p.binStep), + }, + } + return encodeAbiParameters(v4RouteParamsAbi, [ + { + poolKey: encodePoolKey(poolKey), + hookData: '0x', + }, + ]) + } + throw new Error(`Invalid pool type ${p}`) + }) +} diff --git a/packages/smart-router/evm/v3-router/utils/encodeV4RouteToPath.ts b/packages/smart-router/evm/v3-router/utils/encodeV4RouteToPath.ts new file mode 100644 index 0000000000000..83e70aad73e0e --- /dev/null +++ b/packages/smart-router/evm/v3-router/utils/encodeV4RouteToPath.ts @@ -0,0 +1,68 @@ +import { Address, zeroAddress } from 'viem' +import { Currency, getCurrencyAddress } from '@pancakeswap/swap-sdk-core' +import { encodePoolParameters } from '@pancakeswap/v4-sdk' + +import { BaseRoute, Pool } from '../types' +import { getOutputCurrency, isV4BinPool, isV4ClPool } from './pool' + +export type PathKey = { + intermediateCurrency: Address + fee: number + hooks: Address + poolManager: Address + hookData: `0x${string}` + parameters: `0x${string}` +} + +/** + * Converts a route to an array of path key + * @param route the mixed path to convert to an encoded path + * @returns the encoded path keys + */ +export function encodeV4RouteToPath(route: BaseRoute, exactOutput: boolean): PathKey[] { + if (route.pools.some((p) => !isV4ClPool(p) && !isV4BinPool(p))) { + throw new Error('Failed to encode path keys. Invalid v4 pool found in route.') + } + + const currencyStart = exactOutput ? route.output : route.input + const pools = exactOutput ? [...route.pools].reverse() : route.pools + + const { path } = pools.reduce( + ( + // eslint-disable-next-line @typescript-eslint/no-shadow + { baseCurrency, path }: { baseCurrency: Currency; path: PathKey[] }, + pool: Pool, + ): { baseCurrency: Currency; path: PathKey[] } => { + const isV4Cl = isV4ClPool(pool) + const isV4Bin = isV4BinPool(pool) + if (!isV4Cl && !isV4Bin) throw new Error(`Invalid v4 pool ${pool}`) + const quoteCurrency = getOutputCurrency(pool, baseCurrency) + const parameters = encodePoolParameters( + isV4Cl + ? { + tickSpacing: pool.tickSpacing, + } + : { + binStep: pool.binStep, + }, + ) + return { + baseCurrency: quoteCurrency, + path: [ + ...path, + { + intermediateCurrency: getCurrencyAddress(quoteCurrency), + fee: pool.fee, + hooks: pool.hooks ?? zeroAddress, + poolManager: pool.poolManager, + hookData: '0x', + parameters, + }, + ], + } + }, + { baseCurrency: currencyStart, path: [] }, + ) + + return exactOutput ? path.reverse() : path +} diff --git a/packages/smart-router/evm/v3-router/utils/getPriceImpact.ts b/packages/smart-router/evm/v3-router/utils/getPriceImpact.ts index 280e4eddd4290..ee6f2ddcc2e9c 100644 --- a/packages/smart-router/evm/v3-router/utils/getPriceImpact.ts +++ b/packages/smart-router/evm/v3-router/utils/getPriceImpact.ts @@ -8,13 +8,15 @@ export function getPriceImpact( routes: Pick[] }, ): Percent { - let spotOutputAmount = CurrencyAmount.fromRawAmount(trade.outputAmount.currency.wrapped, 0) + let spotOutputAmount = CurrencyAmount.fromRawAmount(trade.outputAmount.currency, 0) for (const route of trade.routes) { const { inputAmount } = route // FIXME typing const midPrice: any = getMidPrice(route) - spotOutputAmount = spotOutputAmount.add(midPrice.quote(inputAmount.wrapped)) + spotOutputAmount = spotOutputAmount.add( + CurrencyAmount.fromRawAmount(trade.outputAmount.currency, midPrice.wrapped.quote(inputAmount.wrapped).quotient), + ) } - const priceImpact = spotOutputAmount.subtract(trade.outputAmount.wrapped).divide(spotOutputAmount) + const priceImpact = spotOutputAmount.subtract(trade.outputAmount).divide(spotOutputAmount) return new Percent(priceImpact.numerator, priceImpact.denominator) } diff --git a/packages/smart-router/evm/v3-router/utils/pool.ts b/packages/smart-router/evm/v3-router/utils/pool.ts index 222ea07a5dc24..20455b8d422ac 100644 --- a/packages/smart-router/evm/v3-router/utils/pool.ts +++ b/packages/smart-router/evm/v3-router/utils/pool.ts @@ -1,3 +1,4 @@ +import { SCALE, getPriceFromId } from '@pancakeswap/v4-sdk' import { Currency, Pair, Price } from '@pancakeswap/sdk' import { Pool as SDKV3Pool, computePoolAddress } from '@pancakeswap/v3-sdk' import tryParseAmount from '@pancakeswap/utils/tryParseAmount' @@ -5,7 +6,7 @@ import { getSwapOutput } from '@pancakeswap/stable-swap-sdk' import memoize from 'lodash/memoize.js' import { Address } from 'viem' -import { Pool, PoolType, StablePool, V2Pool, V3Pool } from '../types' +import { Pool, PoolType, StablePool, V2Pool, V3Pool, V4BinPool, V4ClPool } from '../types' export function isV2Pool(pool: Pool): pool is V2Pool { return pool.type === PoolType.V2 @@ -19,6 +20,14 @@ export function isStablePool(pool: Pool): pool is StablePool { return pool.type === PoolType.STABLE && pool.balances.length >= 2 } +export function isV4BinPool(pool: Pool): pool is V4BinPool { + return pool.type === PoolType.V4BIN +} + +export function isV4ClPool(pool: Pool): pool is V4ClPool { + return pool.type === PoolType.V4CL +} + export function involvesCurrency(pool: Pool, currency: Currency) { const token = currency.wrapped if (isV2Pool(pool)) { @@ -29,6 +38,15 @@ export function involvesCurrency(pool: Pool, currency: Currency) { const { token0, token1 } = pool return token0.equals(token) || token1.equals(token) } + if (isV4ClPool(pool) || isV4BinPool(pool)) { + const { currency0, currency1 } = pool + return ( + currency0.equals(currency) || + currency1.equals(currency) || + currency0.wrapped.equals(token) || + currency1.wrapped.equals(token) + ) + } if (isStablePool(pool)) { const { balances } = pool return balances.some((b) => b.currency.equals(token)) @@ -36,7 +54,7 @@ export function involvesCurrency(pool: Pool, currency: Currency) { return false } -// FIXME current version is not working with stable pools that have more than 2 tokens +// FIXME: current version is not working with stable pools that have more than 2 tokens export function getOutputCurrency(pool: Pool, currencyIn: Currency): Currency { const tokenIn = currencyIn.wrapped if (isV2Pool(pool)) { @@ -51,6 +69,10 @@ export function getOutputCurrency(pool: Pool, currencyIn: Currency): Currency { const { balances } = pool return balances[0].currency.equals(tokenIn) ? balances[1].currency : balances[0].currency } + if (isV4ClPool(pool) || isV4BinPool(pool)) { + const { currency0, currency1 } = pool + return currency0.wrapped.equals(tokenIn) ? currency1 : currency0 + } throw new Error('Cannot get output currency by invalid pool') } @@ -84,9 +106,11 @@ export const getPoolAddress = memoize( } const [token0, token1] = isV2Pool(pool) ? [pool.reserve0.currency.wrapped, pool.reserve1.currency.wrapped] - : [pool.token0.wrapped, pool.token1.wrapped] + : isV3Pool(pool) + ? [pool.token0.wrapped, pool.token1.wrapped] + : [pool.currency0, pool.currency1] const fee = isV3Pool(pool) ? pool.fee : 'V2_FEE' - return `${pool.type}_${token0.chainId}_${token0.address}_${token1.address}_${fee}` + return `${pool.type}_${token0.chainId}_${token0.isNative}_${token0.wrapped.address}_${token1.isNative}_${token1.wrapped.address}_${fee}` }, ) @@ -97,6 +121,28 @@ export function getTokenPrice(pool: Pool, base: Currency, quote: Currency): Pric return v3Pool.priceOf(base.wrapped) } + if (isV4ClPool(pool)) { + const { currency0, currency1, fee, liquidity, sqrtRatioX96, tick } = pool + const v3Pool = new SDKV3Pool(currency0.wrapped, currency1.wrapped, fee, sqrtRatioX96, liquidity, tick) + const tokenPrice = v3Pool.priceOf(base.wrapped) + const [baseCurrency, quoteCurrency] = base.wrapped.equals(currency0.wrapped) + ? [currency0, currency1] + : [currency1, currency0] + return new Price(baseCurrency, quoteCurrency, tokenPrice.denominator, tokenPrice.numerator) + } + + if (isV4BinPool(pool)) { + const { activeId, binStep, currency0, currency1 } = pool + const rawPrice = getPriceFromId(BigInt(activeId), BigInt(binStep)) + const price = new Price( + currency0, + currency1, + rawPrice * 10n ** BigInt(currency0.decimals), + SCALE * 10n ** BigInt(currency1.decimals), + ) + return base.equals(price.baseCurrency) ? price : price.invert() + } + if (isV2Pool(pool)) { const pair = new Pair(pool.reserve0.wrapped, pool.reserve1.wrapped) return pair.priceOf(base.wrapped) diff --git a/packages/smart-router/evm/v3-router/utils/route.ts b/packages/smart-router/evm/v3-router/utils/route.ts index 0d11945fffe67..86cc4d3b9a90f 100644 --- a/packages/smart-router/evm/v3-router/utils/route.ts +++ b/packages/smart-router/evm/v3-router/utils/route.ts @@ -5,7 +5,7 @@ import { BaseRoute, Pool, RouteType, PoolType, Route } from '../types' import { getOutputCurrency, getTokenPrice } from './pool' export function buildBaseRoute(pools: Pool[], currencyIn: Currency, currencyOut: Currency): BaseRoute { - const path: Currency[] = [currencyIn.wrapped] + const path: Currency[] = [currencyIn] let prevIn = path[0] let routeType: RouteType | null = null const updateRouteType = (pool: Pool, currentRouteType: RouteType | null) => { @@ -17,10 +17,15 @@ export function buildBaseRoute(pools: Pool[], currencyIn: Currency, currencyOut: } return currentRouteType } + const lastPool = pools[pools.length - 1] for (const pool of pools) { + routeType = updateRouteType(pool, routeType) + if (pool === lastPool) { + path.push(currencyOut) + continue + } prevIn = getOutputCurrency(pool, prevIn) path.push(prevIn) - routeType = updateRouteType(pool, routeType) } if (routeType === null) { @@ -44,6 +49,10 @@ function getRouteTypeFromPool(pool: Pick) { return RouteType.V3 case PoolType.STABLE: return RouteType.STABLE + case PoolType.V4CL: + return RouteType.V4CL + case PoolType.V4BIN: + return RouteType.V4BIN default: return RouteType.MIXED } @@ -69,13 +78,19 @@ export function getQuoteCurrency({ input, output }: BaseRoute, baseCurrency: Cur return baseCurrency.equals(input) ? output : input } +function wrapPrice(price: Price): Price { + return new Price(price.baseCurrency.wrapped, price.quoteCurrency.wrapped, price.denominator, price.numerator) +} + export function getMidPrice({ path, pools }: Pick) { let i = 0 let price: Price | null = null + const currencyIn = path[0] + const currencyOut = path[path.length - 1] for (const pool of pools) { - const input = path[i].wrapped - const output = path[i + 1].wrapped - const poolPrice = getTokenPrice(pool, input, output) + const input = path[i] + const output = path[i + 1] + const poolPrice = wrapPrice(getTokenPrice(pool, input, output)) price = price ? price.multiply(poolPrice) : poolPrice i += 1 @@ -84,5 +99,5 @@ export function getMidPrice({ path, pools }: Pick) { if (!price) { throw new Error('Get mid price failed') } - return price + return new Price(currencyIn, currencyOut, price.denominator, price.numerator) } diff --git a/packages/smart-router/evm/v3-router/utils/transformer.ts b/packages/smart-router/evm/v3-router/utils/transformer.ts index 07677355cbacd..63846cab54cf5 100644 --- a/packages/smart-router/evm/v3-router/utils/transformer.ts +++ b/packages/smart-router/evm/v3-router/utils/transformer.ts @@ -2,8 +2,8 @@ import { ChainId } from '@pancakeswap/chains' import { Currency, CurrencyAmount, ERC20Token, Native, Percent, TradeType } from '@pancakeswap/sdk' import { ADDRESS_ZERO, Tick } from '@pancakeswap/v3-sdk' import { Address } from 'viem' -import { Pool, PoolType, Route, SmartRouterTrade, StablePool, V2Pool, V3Pool } from '../types' -import { isStablePool, isV2Pool, isV3Pool } from './pool' +import { Pool, PoolType, Route, SmartRouterTrade, StablePool, V2Pool, V3Pool, V4BinPool, V4ClPool } from '../types' +import { isStablePool, isV2Pool, isV3Pool, isV4BinPool, isV4ClPool } from './pool' const ONE_HUNDRED = 100n @@ -53,13 +53,36 @@ export interface SerializedV3Pool ticks?: SerializedTick[] } +export interface SerializedV4ClPool + extends Omit { + currency0: SerializedCurrency + currency1: SerializedCurrency + liquidity: string + sqrtRatioX96: string + reserve0?: SerializedCurrencyAmount + reserve1?: SerializedCurrencyAmount + ticks?: SerializedTick[] +} + +export interface SerializedV4BinPool extends Omit { + currency0: SerializedCurrency + currency1: SerializedCurrency + reserve0?: SerializedCurrencyAmount + reserve1?: SerializedCurrencyAmount +} + export interface SerializedStablePool extends Omit { balances: SerializedCurrencyAmount[] amplifier: string fee: string } -export type SerializedPool = SerializedV2Pool | SerializedV3Pool | SerializedStablePool +export type SerializedPool = + | SerializedV2Pool + | SerializedV3Pool + | SerializedStablePool + | SerializedV4ClPool + | SerializedV4BinPool export interface SerializedRoute extends Omit { @@ -134,6 +157,27 @@ export function serializePool(pool: Pool): SerializedPool { fee: pool.fee.toSignificant(6), } } + if (isV4ClPool(pool)) { + return { + ...pool, + currency0: serializeCurrency(pool.currency0), + currency1: serializeCurrency(pool.currency1), + liquidity: pool.liquidity.toString(), + sqrtRatioX96: pool.sqrtRatioX96.toString(), + ticks: pool.ticks?.map(serializeTick), + reserve0: pool.reserve0 && serializeCurrencyAmount(pool.reserve0), + reserve1: pool.reserve1 && serializeCurrencyAmount(pool.reserve1), + } + } + if (isV4BinPool(pool)) { + return { + ...pool, + currency0: serializeCurrency(pool.currency0), + currency1: serializeCurrency(pool.currency1), + reserve0: pool.reserve0 && serializeCurrencyAmount(pool.reserve0), + reserve1: pool.reserve1 && serializeCurrencyAmount(pool.reserve1), + } + } throw new Error('Cannot serialize unsupoorted pool') } @@ -204,6 +248,27 @@ export function parsePool(chainId: ChainId, pool: SerializedPool): Pool { fee: new Percent(parseFloat(pool.fee) * 1000000, ONE_HUNDRED * 1000000n), } } + if (pool.type === PoolType.V4CL) { + return { + ...pool, + currency0: parseCurrency(chainId, pool.currency0), + currency1: parseCurrency(chainId, pool.currency1), + liquidity: BigInt(pool.liquidity), + sqrtRatioX96: BigInt(pool.sqrtRatioX96), + ticks: pool.ticks?.map(parseTick), + reserve0: pool.reserve0 && parseCurrencyAmount(chainId, pool.reserve0), + reserve1: pool.reserve1 && parseCurrencyAmount(chainId, pool.reserve1), + } + } + if (pool.type === PoolType.V4BIN) { + return { + ...pool, + currency0: parseCurrency(chainId, pool.currency0), + currency1: parseCurrency(chainId, pool.currency1), + reserve0: pool.reserve0 && parseCurrencyAmount(chainId, pool.reserve0), + reserve1: pool.reserve1 && parseCurrencyAmount(chainId, pool.reserve1), + } + } throw new Error('Cannot parse unsupoorted pool') } diff --git a/packages/smart-router/evm/v4-router/constants/index.ts b/packages/smart-router/evm/v4-router/constants/index.ts index 560f1e57d1ce0..858f0c89642ff 100644 --- a/packages/smart-router/evm/v4-router/constants/index.ts +++ b/packages/smart-router/evm/v4-router/constants/index.ts @@ -1 +1,2 @@ export * from './v3PoolFetchGasLimit' +export * from './v4Pools' diff --git a/packages/smart-router/evm/v4-router/constants/v4Pools.ts b/packages/smart-router/evm/v4-router/constants/v4Pools.ts new file mode 100644 index 0000000000000..297cdaaeb3e34 --- /dev/null +++ b/packages/smart-router/evm/v4-router/constants/v4Pools.ts @@ -0,0 +1,35 @@ +// TODO: move to sdk + +import { zeroAddress, type Address } from 'viem' + +export const CL_PRESETS: { + fee: number + tickSpacing: number +}[] = [ + { + fee: 500, + tickSpacing: 10, + }, + { + fee: 100, + tickSpacing: 1000, + }, +] + +export const CL_HOOKS: Address[] = [zeroAddress] + +export const BIN_PRESETS: { + fee: number + binStep: number +}[] = [ + { + fee: 500, + binStep: 10, + }, + { + fee: 100, + binStep: 10, + }, +] + +export const BIN_HOOKS: Address[] = [zeroAddress] diff --git a/packages/smart-router/evm/v4-router/queries/getV4BinPools.ts b/packages/smart-router/evm/v4-router/queries/getV4BinPools.ts new file mode 100644 index 0000000000000..1526cde206a50 --- /dev/null +++ b/packages/smart-router/evm/v4-router/queries/getV4BinPools.ts @@ -0,0 +1,101 @@ +import { Currency, getCurrencyAddress, sortCurrencies } from '@pancakeswap/swap-sdk-core' +import { BinPoolManager, PoolKey, getPoolId } from '@pancakeswap/v4-sdk' +import { Native } from '@pancakeswap/sdk' +import { Address } from 'viem' + +import { getPairCombinations } from '../../v3-router/functions' +import { PoolType, V4BinPool } from '../../v3-router/types' +import { PoolMeta } from '../../v3-router/providers/poolProviders/internalTypes' +import { createOnChainPoolFactory } from '../../v3-router/providers' +import { BIN_HOOKS, BIN_PRESETS } from '../constants' +import { GetV4CandidatePoolsParams } from '../types' + +export async function getV4BinCandidatePools({ + currencyA, + currencyB, + clientProvider, + gasLimit, +}: GetV4CandidatePoolsParams) { + if (!currencyA || !currencyB) { + throw new Error(`Invalid currencyA ${currencyA} or currencyB ${currencyB}`) + } + const native = Native.onChain(currencyA?.chainId) + const wnative = native.wrapped + const pairs = await getPairCombinations(currencyA, currencyB) + const pairsWithNative = [...pairs] + for (const pair of pairs) { + const index = pair.findIndex((c) => c.wrapped.equals(wnative)) + if (index >= 0) { + const pairWithNative = [...pair] + pairWithNative[index] = native + pairsWithNative.push(pairWithNative as [Currency, Currency]) + } + } + return getV4BinPoolsWithoutBins(pairsWithNative, clientProvider) +} + +type V4BinPoolMeta = PoolMeta & { + fee: number + poolManager: Address + binStep: number + hooks: Address +} + +export const getV4BinPoolsWithoutBins = createOnChainPoolFactory({ + abi: BinPoolManager, + getPossiblePoolMetas: ([currencyA, currencyB]) => { + const [currency0, currency1] = sortCurrencies([currencyA, currencyB]) + const metas: V4BinPoolMeta[] = [] + for (const { fee, binStep } of BIN_PRESETS) { + for (const hooks of BIN_HOOKS) { + const poolKey: PoolKey<'Bin'> = { + currency0: getCurrencyAddress(currency0), + currency1: getCurrencyAddress(currency1), + fee, + parameters: { + binStep, + }, + // TODO: use constant from v4 sdk + poolManager: '0x1DF0be383e9d17DA4448E57712849aBE5b3Fa33b' as const, + hooks, + } + const id = getPoolId(poolKey) + metas.push({ + currencyA, + currencyB, + fee, + binStep, + hooks, + poolManager: poolKey.poolManager, + id, + }) + } + } + return metas + }, + buildPoolInfoCalls: ({ id, poolManager: address }) => [ + { + address, + functionName: 'getSlot0', + args: [id], + }, + ], + buildPool: ({ currencyA, currencyB, fee, id, binStep, poolManager, hooks }, [slot0]) => { + if (!slot0 || !slot0[0]) { + return null + } + const [activeId] = slot0 + const [currency0, currency1] = sortCurrencies([currencyA, currencyB]) + return { + id, + type: PoolType.V4BIN, + currency0, + currency1, + fee, + activeId, + binStep, + poolManager, + hooks, + } + }, +}) diff --git a/packages/smart-router/evm/v4-router/queries/getV4ClPools.ts b/packages/smart-router/evm/v4-router/queries/getV4ClPools.ts new file mode 100644 index 0000000000000..6df5b21b79219 --- /dev/null +++ b/packages/smart-router/evm/v4-router/queries/getV4ClPools.ts @@ -0,0 +1,108 @@ +import { Currency, getCurrencyAddress, sortCurrencies } from '@pancakeswap/swap-sdk-core' +import { PoolKey, getPoolId, CLPoolManager } from '@pancakeswap/v4-sdk' +import { Native } from '@pancakeswap/sdk' +import { Address } from 'viem' + +import { getPairCombinations } from '../../v3-router/functions' +import { PoolType, V4ClPool } from '../../v3-router/types' +import { PoolMeta } from '../../v3-router/providers/poolProviders/internalTypes' +import { createOnChainPoolFactory } from '../../v3-router/providers' +import { CL_HOOKS, CL_PRESETS } from '../constants' +import { GetV4CandidatePoolsParams } from '../types' + +export async function getV4ClCandidatePools({ + currencyA, + currencyB, + clientProvider, + gasLimit, +}: GetV4CandidatePoolsParams) { + if (!currencyA || !currencyB) { + throw new Error(`Invalid currencyA ${currencyA} or currencyB ${currencyB}`) + } + const native = Native.onChain(currencyA?.chainId) + const wnative = native.wrapped + const pairs = await getPairCombinations(currencyA, currencyB) + const pairsWithNative = [...pairs] + for (const pair of pairs) { + const index = pair.findIndex((c) => c.wrapped.equals(wnative)) + if (index >= 0) { + const pairWithNative = [...pair] + pairWithNative[index] = native + pairsWithNative.push(pairWithNative as [Currency, Currency]) + } + } + return getV4ClPoolsWithoutTicks(pairsWithNative, clientProvider) +} + +type V4ClPoolMeta = PoolMeta & { + fee: number + poolManager: Address + tickSpacing: number + hooks: Address +} + +export const getV4ClPoolsWithoutTicks = createOnChainPoolFactory({ + abi: CLPoolManager, + getPossiblePoolMetas: ([currencyA, currencyB]) => { + const [currency0, currency1] = sortCurrencies([currencyA, currencyB]) + const metas: V4ClPoolMeta[] = [] + for (const { fee, tickSpacing } of CL_PRESETS) { + for (const hooks of CL_HOOKS) { + const poolKey: PoolKey<'CL'> = { + currency0: getCurrencyAddress(currency0), + currency1: getCurrencyAddress(currency1), + fee, + parameters: { + tickSpacing, + }, + // TODO: use constant from v4 sdk + poolManager: '0x26Ca53c8C5CE90E22aA1FadDA68AB9a08f7BA06f' as const, + hooks, + } + const id = getPoolId(poolKey) + metas.push({ + currencyA, + currencyB, + fee, + tickSpacing, + hooks, + poolManager: poolKey.poolManager, + id, + }) + } + } + return metas + }, + buildPoolInfoCalls: ({ id, poolManager: address }) => [ + { + address, + functionName: 'getLiquidity', + args: [id], + }, + { + address, + functionName: 'getSlot0', + args: [id], + }, + ], + buildPool: ({ currencyA, currencyB, fee, id, tickSpacing, poolManager, hooks }, [liquidity, slot0]) => { + if (!slot0 || !slot0[0]) { + return null + } + const [sqrtPriceX96, tick] = slot0 + const [currency0, currency1] = sortCurrencies([currencyA, currencyB]) + return { + id, + type: PoolType.V4CL, + currency0, + currency1, + fee, + liquidity: BigInt(liquidity.toString()), + sqrtRatioX96: BigInt(sqrtPriceX96.toString()), + tick: Number(tick), + tickSpacing, + poolManager, + hooks, + } + }, +}) diff --git a/packages/smart-router/evm/v4-router/queries/index.ts b/packages/smart-router/evm/v4-router/queries/index.ts index 206d9dbe54939..d7a89f418634e 100644 --- a/packages/smart-router/evm/v4-router/queries/index.ts +++ b/packages/smart-router/evm/v4-router/queries/index.ts @@ -1 +1,3 @@ export * from './getV3Pools' +export * from './getV4ClPools' +export * from './getV4BinPools' diff --git a/packages/smart-router/evm/v4-router/schema.ts b/packages/smart-router/evm/v4-router/schema.ts index 8e670dbac9912..51813e424238f 100644 --- a/packages/smart-router/evm/v4-router/schema.ts +++ b/packages/smart-router/evm/v4-router/schema.ts @@ -12,6 +12,7 @@ const zTradeType = z.nativeEnum(TradeType) const zAddress = z.custom
((val) => /^0x[a-fA-F0-9]{40}$/.test(val as string)) const zBigNumber = z.string().regex(/^[0-9]+$/) const zSignedBigInt = z.string().regex(/^-?[0-9]+$/) +const zHex = z.custom
((val) => /^0x[a-fA-F0-9]*$/.test(val as string)) const zCurrency = z .object({ address: zAddress, @@ -74,10 +75,50 @@ const zStablePool = z balances: z.array(zCurrencyAmount), amplifier: zBigNumber, fee: z.string(), + address: zAddress, }) .required() -export const zPools = z.array(z.union([zV3Pool, zV2Pool, zStablePool])) +const zV4ClPool = z + .object({ + type: z.literal(PoolType.V4CL), + currency0: zCurrency, + currency1: zCurrency, + fee: zFee, + liquidity: zBigNumber, + sqrtRatioX96: zBigNumber, + tick: z.number(), + tickSpacing: z.number(), + poolManager: zAddress, + id: zHex, + }) + .required() + .extend({ + reserve0: zCurrencyAmountOptional, + reserve1: zCurrencyAmountOptional, + hooks: zAddress.optional(), + ticks: z.array(zTick).optional(), + }) + +const zV4BinPool = z + .object({ + type: z.literal(PoolType.V4BIN), + currency0: zCurrency, + currency1: zCurrency, + fee: zFee, + activeId: z.number(), + binStep: z.number(), + poolManager: zAddress, + id: zHex, + }) + .required() + .extend({ + reserve0: zCurrencyAmountOptional, + reserve1: zCurrencyAmountOptional, + hooks: zAddress.optional(), + }) + +export const zPools = z.array(z.union([zV3Pool, zV2Pool, zStablePool, zV4ClPool, zV4BinPool])) export const zRouterPostParams = z .object({ diff --git a/packages/smart-router/evm/v4-router/types/index.ts b/packages/smart-router/evm/v4-router/types/index.ts index 2b92c357aad5b..55a8c019ccd4f 100644 --- a/packages/smart-router/evm/v4-router/types/index.ts +++ b/packages/smart-router/evm/v4-router/types/index.ts @@ -1,2 +1,3 @@ export * from './trade' export * from './graph' +export * from './pool' diff --git a/packages/smart-router/evm/v4-router/types/pool.ts b/packages/smart-router/evm/v4-router/types/pool.ts new file mode 100644 index 0000000000000..962d331892416 --- /dev/null +++ b/packages/smart-router/evm/v4-router/types/pool.ts @@ -0,0 +1,17 @@ +import type { BigintIsh, Currency } from '@pancakeswap/swap-sdk-core' + +import type { OnChainProvider } from '../../v3-router/types' + +type WithMulticallGasLimit = { + gasLimit?: BigintIsh +} + +type WithClientProvider = { + clientProvider?: OnChainProvider +} + +export type GetV4CandidatePoolsParams = { + currencyA?: Currency + currencyB?: Currency +} & WithClientProvider & + WithMulticallGasLimit diff --git a/packages/smart-router/package.json b/packages/smart-router/package.json index cda923e2326b8..d65e06cd1115a 100644 --- a/packages/smart-router/package.json +++ b/packages/smart-router/package.json @@ -32,6 +32,7 @@ "@pancakeswap/token-lists": "workspace:*", "@pancakeswap/tokens": "workspace:*", "@pancakeswap/v3-sdk": "workspace:*", + "@pancakeswap/v4-sdk": "workspace:*", "async-retry": "^1.3.1", "debug": "^4.3.4", "graphql": "^16.8.1", diff --git a/packages/swap-sdk-core/src/constants.ts b/packages/swap-sdk-core/src/constants.ts index 375569d60a0bb..e2661038d0829 100644 --- a/packages/swap-sdk-core/src/constants.ts +++ b/packages/swap-sdk-core/src/constants.ts @@ -36,3 +36,5 @@ export const VM_TYPE_MAXIMA = { [VMType.uint8]: BigInt('0xff'), [VMType.uint256]: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), } + +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const diff --git a/packages/swap-sdk-core/src/fractions/price.ts b/packages/swap-sdk-core/src/fractions/price.ts index 572143d23f1a4..968e46d544209 100644 --- a/packages/swap-sdk-core/src/fractions/price.ts +++ b/packages/swap-sdk-core/src/fractions/price.ts @@ -4,6 +4,7 @@ import { BigintIsh, Rounding } from '../constants' import { Currency } from '../currency' import { Fraction } from './fraction' import { CurrencyAmount } from './currencyAmount' +import { Token } from '../token' export class Price extends Fraction { public readonly baseCurrency: TBase // input i.e. denominator @@ -87,4 +88,8 @@ export class Price extends Frac public toFixed(decimalPlaces = 4, format?: object, rounding?: Rounding): string { return this.adjustedForDecimals.toFixed(decimalPlaces, format, rounding) } + + public get wrapped(): Price { + return new Price(this.baseCurrency.wrapped, this.quoteCurrency.wrapped, this.denominator, this.numerator) + } } diff --git a/packages/swap-sdk-core/src/index.test.ts b/packages/swap-sdk-core/src/index.test.ts index f58ffd231bc1d..0bc00414456c8 100644 --- a/packages/swap-sdk-core/src/index.test.ts +++ b/packages/swap-sdk-core/src/index.test.ts @@ -19,6 +19,7 @@ test('exports', () => { "MaxUint256", "VMType", "VM_TYPE_MAXIMA", + "ZERO_ADDRESS", "BaseCurrency", "Fraction", "Percent", @@ -34,6 +35,7 @@ test('exports', () => { "computePriceImpact", "getTokenComparator", "sortCurrencies", + "getCurrencyAddress", ] `) }) diff --git a/packages/swap-sdk-core/src/utils.ts b/packages/swap-sdk-core/src/utils.ts index 14d04e1ac87f6..41f2e1c0e4684 100644 --- a/packages/swap-sdk-core/src/utils.ts +++ b/packages/swap-sdk-core/src/utils.ts @@ -1,5 +1,5 @@ import invariant from 'tiny-invariant' -import { ONE, THREE, TWO, VMType, VM_TYPE_MAXIMA, ZERO } from './constants' +import { ONE, THREE, TWO, VMType, VM_TYPE_MAXIMA, ZERO, ZERO_ADDRESS } from './constants' import { Currency } from './currency' import { CurrencyAmount, Percent, Price } from './fractions' import { Token } from './token' @@ -128,3 +128,10 @@ export function sortCurrencies(currencies: T[]): T[] { return a.sortsBefore(b) ? -1 : 1 }) } + +export function getCurrencyAddress(currency: Currency) { + if (currency.isNative) { + return ZERO_ADDRESS + } + return currency.address +} diff --git a/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts b/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts index 64b73083e8c0e..dc781901e0156 100644 --- a/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts +++ b/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts @@ -73,6 +73,14 @@ export class PancakeSwapTrade implements Command { addStableSwap(planner, singleRouteTrade, this.options, routerMustCustody, payerIsUser) continue } + if (route.type === RouteType.V4CL) { + // TODO: implementation + continue + } + if (route.type === RouteType.V4BIN) { + // TODO: implementation + continue + } addMixedSwap(planner, singleRouteTrade, this.options, payerIsUser, routerMustCustody) } diff --git a/packages/v4-sdk/src/index.ts b/packages/v4-sdk/src/index.ts index 8ec1cef061968..15a385b374483 100644 --- a/packages/v4-sdk/src/index.ts +++ b/packages/v4-sdk/src/index.ts @@ -6,3 +6,6 @@ export * from './functions/clamm' export * from './types' export * from './utils' + +export * from './abis/CLPoolManager' +export * from './abis/BinPoolManager' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e24767c7d3d0..a75f144f504ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2111,6 +2111,9 @@ importers: '@pancakeswap/v3-sdk': specifier: workspace:* version: link:../v3-sdk + '@pancakeswap/v4-sdk': + specifier: workspace:* + version: link:../v4-sdk async-retry: specifier: ^1.3.1 version: 1.3.3 @@ -10319,6 +10322,7 @@ packages: eciesjs@0.3.18: resolution: {integrity: sha512-RQhegEtLSyIiGJmFTZfvCTHER/fymipXFVx6OwSRYD6hOuy+6Kjpk0dGvIfP9kxn/smBpxQy71uxpGO406ITCw==} + deprecated: Please upgrade to v0.4+ ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -15894,10 +15898,6 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.20.0: - resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} - engines: {node: '>=12.18'} - undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} @@ -18729,7 +18729,7 @@ snapshots: dependencies: '@binance/w3w-types': 1.1.4 '@binance/w3w-utils': 1.1.4 - ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -21017,7 +21017,7 @@ snapshots: lodash: 4.17.21 semver: 6.3.1 table: 6.8.1 - undici: 5.20.0 + undici: 5.28.4 transitivePeerDependencies: - supports-color optional: true @@ -21517,9 +21517,9 @@ snapshots: '@types/ws': 8.5.10 axios: 1.7.2 axios-retry: 3.9.1 - isomorphic-ws: 4.0.1(ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + isomorphic-ws: 4.0.1(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) ts-log: 2.2.5 - ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - debug @@ -23299,7 +23299,7 @@ snapshots: util: 0.12.5 util-deprecate: 1.0.2 watchpack: 2.4.0 - ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding @@ -25363,7 +25363,7 @@ snapshots: '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/modal': 2.6.2(@types/react@18.2.37)(react@18.2.0) + '@walletconnect/modal': 2.7.0(@types/react@18.2.37)(react@18.2.0) '@walletconnect/sign-client': 2.11.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@walletconnect/types': 2.11.0 '@walletconnect/universal-provider': 2.11.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -30287,7 +30287,7 @@ snapshots: source-map-support: 0.5.21 stacktrace-parser: 0.1.10 tsort: 0.0.1 - undici: 5.20.0 + undici: 5.28.4 uuid: 8.3.2 ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10) optionalDependencies: @@ -30840,9 +30840,9 @@ snapshots: dependencies: ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10) - isomorphic-ws@4.0.1(ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)): + isomorphic-ws@4.0.1(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)): dependencies: - ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) isows@1.0.3(ws@8.13.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)): dependencies: @@ -35710,11 +35710,6 @@ snapshots: undici-types@5.26.5: {} - undici@5.20.0: - dependencies: - busboy: 1.6.0 - optional: true - undici@5.28.4: dependencies: '@fastify/busboy': 2.1.1 @@ -36927,11 +36922,6 @@ snapshots: bufferutil: 4.0.8 utf-8-validate: 5.0.10 - ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.3): optionalDependencies: bufferutil: 4.0.8