Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: V4 Quoter Integration #10844

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/forty-beers-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pancakeswap/swap-sdk-core': minor
---

Add currency address getter
5 changes: 5 additions & 0 deletions .changeset/seven-adults-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pancakeswap/swap-sdk-core': minor
---

Support price wrapping
6 changes: 6 additions & 0 deletions .changeset/tame-elephants-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@pancakeswap/price-api-sdk': minor
'@pancakeswap/smart-router': minor
---

Add v4 pool types
6 changes: 6 additions & 0 deletions .changeset/wicked-bobcats-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@pancakeswap/price-api-sdk': minor
'@pancakeswap/smart-router': minor
---

Introduce v4 liquidity pools
73 changes: 73 additions & 0 deletions apps/web/src/hooks/useV4BinPools.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
73 changes: 73 additions & 0 deletions apps/web/src/hooks/useV4ClPools.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
5 changes: 4 additions & 1 deletion apps/web/src/utils/convertTrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 19 additions & 6 deletions apps/web/src/views/Swap/V3Swap/components/RouteDisplayModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<PairNode
pair={p}
key={key}
text={text}
className={isV3Pool ? 'highlight' : ''}
className={isV4Pool || isV3Pool ? 'highlight' : ''}
tooltipText={tooltipText}
/>
)
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/views/Swap/V3Swap/utils/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
ONE_HUNDRED_PERCENT,
Percent,
Price,
Token,
TradeType,
ZERO,
} from '@pancakeswap/sdk'
Expand Down Expand Up @@ -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(
Expand All @@ -85,7 +84,10 @@ export function computeTradePriceBreakdown(trade?: TradeEssentialForPriceBreakdo

const midPrice = SmartRouter.getMidPrice(route)
outputAmountWithoutPriceImpact = outputAmountWithoutPriceImpact.add(
midPrice.quote(routeInputAmount.wrapped) as CurrencyAmount<Token>,
CurrencyAmount.fromRawAmount(
trade.outputAmount.currency,
midPrice.wrapped.quote(routeInputAmount.wrapped).quotient,
),
)
}

Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/price-api-sdk/src/types/amm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading