diff --git a/apps/web/src/pages/api/configs/farms/v2/[chain].ts b/apps/web/src/pages/api/configs/farms/v2/[chain].ts new file mode 100644 index 0000000000000..a5621619cea39 --- /dev/null +++ b/apps/web/src/pages/api/configs/farms/v2/[chain].ts @@ -0,0 +1,42 @@ +import { ChainId, chainNames, chainNameToChainId } from '@pancakeswap/chains' +import { formatUniversalFarmToSerializedFarm, UNIVERSAL_FARMS } from '@pancakeswap/farms' +import { NextApiHandler } from 'next' +import { stringify } from 'viem' +import { enum as enum_, nativeEnum } from 'zod' + +const allChainNames = Object.values(chainNames) as [string, ...string[]] + +const zChain = nativeEnum(ChainId).or(enum_(allChainNames)) + +const handler: NextApiHandler = async (req, res) => { + const isChainInt = !Number.isNaN(parseInt(req.query.chain as string, 10)) + const chainQuery = isChainInt ? Number(req.query.chain) : req.query.chain + const parsedChain = zChain.safeParse(chainQuery) + + if (!parsedChain.success) { + return res.status(400).json({ error: parsedChain.error }) + } + + const chainId = isChainInt ? Number(parsedChain.data) : Number(chainNameToChainId[parsedChain.data]) + + if (!chainId) { + return res.status(400).json({ error: 'Invalid chain' }) + } + + try { + const farmConfig = UNIVERSAL_FARMS.filter((farm) => farm.chainId === chainId) + const legacyFarmConfig = formatUniversalFarmToSerializedFarm(farmConfig) + // cache for long time, it should revalidate on every deployment + res.setHeader('Cache-Control', `max-age=10800, s-maxage=31536000`) + + return res.status(200).json({ + data: JSON.parse(stringify(legacyFarmConfig)), + lastUpdatedAt: new Date().toISOString, + }) + } catch (error) { + console.error(error) + return res.status(500).json({ error: JSON.parse(stringify(error)) }) + } +} + +export default handler diff --git a/apps/web/src/pages/api/configs/farms/v2/index.ts b/apps/web/src/pages/api/configs/farms/v2/index.ts new file mode 100644 index 0000000000000..8deaf1120182f --- /dev/null +++ b/apps/web/src/pages/api/configs/farms/v2/index.ts @@ -0,0 +1,24 @@ +import { formatUniversalFarmToSerializedFarm, UNIVERSAL_FARMS, UNIVERSAL_FARMS_WITH_TESTNET } from '@pancakeswap/farms' +import { NextApiHandler } from 'next' +import { stringify } from 'viem' + +const handler: NextApiHandler = async (req, res) => { + const includeTestnet = !!req.query.includeTestnet + + try { + const farmConfig = includeTestnet ? UNIVERSAL_FARMS : UNIVERSAL_FARMS_WITH_TESTNET + const legacyFarmConfig = formatUniversalFarmToSerializedFarm(farmConfig) + // cache for long time, it should revalidate on every deployment + res.setHeader('Cache-Control', `max-age=10800, s-maxage=31536000`) + + return res.status(200).json({ + data: JSON.parse(stringify(legacyFarmConfig)), + lastUpdatedAt: new Date().toISOString, + }) + } catch (error) { + console.error(error) + return res.status(500).json({ error: JSON.parse(stringify(error)) }) + } +} + +export default handler diff --git a/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts b/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts index 02755d032d615..23edce12e6865 100644 --- a/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts +++ b/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts @@ -36,7 +36,7 @@ export const useAccountPositionDetailByPool = { - return isAddressEqual(pair.stableSwapAddress, poolInfo?.stableLpAddress as Address) + return isAddressEqual(pair.stableSwapAddress, poolInfo?.stableSwapAddress as Address) }) return getStablePairDetails(chainId, account!, stablePair ? [stablePair] : []) } diff --git a/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts b/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts index 91ca2073c3b57..571234029ad16 100644 --- a/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts +++ b/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts @@ -432,7 +432,7 @@ const getV2PoolsCakeAprByChainId = async ( address: pool.token0.wrapped.address, functionName: 'balanceOf', abi: erc20Abi, - args: [pool.stableLpAddress ?? pool.lpAddress], + args: [pool.stableSwapAddress ?? pool.lpAddress], } as const }) const reserve1Calls = validPools.map((pool) => { @@ -440,7 +440,7 @@ const getV2PoolsCakeAprByChainId = async ( address: pool.token1.wrapped.address, functionName: 'balanceOf', abi: erc20Abi, - args: [pool.stableLpAddress ?? pool.lpAddress], + args: [pool.stableSwapAddress ?? pool.lpAddress], } as const }) diff --git a/apps/web/src/state/farmsV4/state/type.ts b/apps/web/src/state/farmsV4/state/type.ts index 5c1807aa44285..672082925682d 100644 --- a/apps/web/src/state/farmsV4/state/type.ts +++ b/apps/web/src/state/farmsV4/state/type.ts @@ -12,7 +12,7 @@ export type BasePoolInfo = { pid?: number chainId: number lpAddress: Address - stableLpAddress?: Address + stableSwapAddress?: Address protocol: Protocol token0: Currency token1: Token diff --git a/apps/web/src/state/farmsV4/state/utils.ts b/apps/web/src/state/farmsV4/state/utils.ts index ebf00498bcd2c..a493997b8029a 100644 --- a/apps/web/src/state/farmsV4/state/utils.ts +++ b/apps/web/src/state/farmsV4/state/utils.ts @@ -49,7 +49,7 @@ export const parseFarmPools = ( chainId: pool.chainId, pid, lpAddress, - stableLpAddress: stableSwapAddress, + stableSwapAddress, protocol: pool.protocol as Protocol, token0, token1, diff --git a/apps/web/src/views/PoolDetail/components/PoolStatus.tsx b/apps/web/src/views/PoolDetail/components/PoolStatus.tsx index c73d519b51aa7..a3a083427acee 100644 --- a/apps/web/src/views/PoolDetail/components/PoolStatus.tsx +++ b/apps/web/src/views/PoolDetail/components/PoolStatus.tsx @@ -45,7 +45,7 @@ export const PoolStatus: React.FC = ({ poolInfo }) => { } const stablePair = LegacyRouter.stableSwapPairsByChainId[poolInfo.chainId].find((pair) => { - return isAddressEqual(pair.stableSwapAddress, poolInfo?.stableLpAddress as Address) + return isAddressEqual(pair.stableSwapAddress, poolInfo?.stableSwapAddress as Address) }) if (!stablePair) return 0 diff --git a/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx b/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx index ad30be771192d..dd2ae5fd252fd 100644 --- a/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx +++ b/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx @@ -52,8 +52,8 @@ export const PoolListItemAction = memo(({ pool }: { pool: PoolInfo }) => { export const getPoolDetailPageLink = (pool: PoolInfo) => { const linkPrefix = `/liquidity/pool${multiChainPaths[pool.chainId] || '/bsc'}` if (pool.protocol === Protocol.STABLE) { - if (pool.stableLpAddress) { - return `${linkPrefix}/${pool.stableLpAddress}` + if (pool.stableSwapAddress) { + return `${linkPrefix}/${pool.stableSwapAddress}` } const ssPair = LegacyRouter.stableSwapPairsByChainId[pool.chainId]?.find((pair) => { return isAddressEqual(pair.lpAddress, pool.lpAddress) diff --git a/packages/farms/index.test.ts b/packages/farms/index.test.ts index 1dfbf6ecbf03a..0ec72f2740419 100644 --- a/packages/farms/index.test.ts +++ b/packages/farms/index.test.ts @@ -14,6 +14,7 @@ test('exports', () => { "isStableFarm", "Protocol", "isActiveV3Farm", + "formatUniversalFarmToSerializedFarm", "deserializeFarm", "deserializeFarmUserData", "deserializeFarmBCakeUserData", diff --git a/packages/farms/package.json b/packages/farms/package.json index fc28add553b2c..36c0cd3739fec 100644 --- a/packages/farms/package.json +++ b/packages/farms/package.json @@ -17,6 +17,7 @@ "@pancakeswap/price-api-sdk": "workspace:*", "@pancakeswap/chains": "workspace:*", "@pancakeswap/sdk": "workspace:*", + "@pancakeswap/stable-swap-sdk": "workspace:*", "@pancakeswap/swap-sdk-core": "workspace:*", "@pancakeswap/token-lists": "workspace:*", "@pancakeswap/tokens": "workspace:*", diff --git a/packages/farms/src/farms/arb.ts b/packages/farms/src/farms/arb.ts index 9da263fda0678..5892c0dbb0cf6 100644 --- a/packages/farms/src/farms/arb.ts +++ b/packages/farms/src/farms/arb.ts @@ -825,6 +825,7 @@ export const arbFarmConfig: UniversalFarmConfig[] = [ token0: arbitrumTokens.dlp, token1: arbitrumTokens.mdlp, lpAddress: '0x0db5e247ab73FBaE16d9301f2977f974EC0AA336', + stableSwapAddress: '0xd0f0be815a76eFE677c92b07b39a5e972BAf22bD', }, { pid: 178, @@ -833,6 +834,7 @@ export const arbFarmConfig: UniversalFarmConfig[] = [ token0: arbitrumTokens.pendle, token1: arbitrumTokens.mpendle, lpAddress: '0x1A2329546f11e4fE55b853D98Bba2c4678E3105A', + stableSwapAddress: '0x73ed25e04Aa673ddf7411441098fC5ae19976CE0', }, ] diff --git a/packages/farms/src/farms/bsc.ts b/packages/farms/src/farms/bsc.ts index a3066cc9d031a..3a38854e4ab22 100644 --- a/packages/farms/src/farms/bsc.ts +++ b/packages/farms/src/farms/bsc.ts @@ -1687,6 +1687,7 @@ export const bscFarmConfig: UniversalFarmConfig[] = [ chainId: ChainId.BSC, protocol: Protocol.STABLE, lpAddress: '0x4cBEa76B4A1c42C356B4c52B0314A98313fFE9df', + stableSwapAddress: '0xfF5Ce4846A3708EA9befa6c3Ab145e63f65DC045', token0: bscTokens.mwbeth, token1: bscTokens.wbeth, }, @@ -1713,6 +1714,7 @@ export const bscFarmConfig: UniversalFarmConfig[] = [ token0: bscTokens.mdlp, token1: bscTokens.dlp, lpAddress: '0xA2915ae3bc8C6C03f59496B6Dd26aa6a4335b788', + stableSwapAddress: '0x25d0eD3b1cE5aF0F3Ac7da4b39B46FC409bF67e2', }, { pid: 178, @@ -1721,6 +1723,7 @@ export const bscFarmConfig: UniversalFarmConfig[] = [ token0: bscTokens.mpendle, token1: bscTokens.pendle, lpAddress: '0x183F325b33d190597D80d1B46D865d0250fD9BF2', + stableSwapAddress: '0xD8CB82059da7215b1a9604E845d49D3e78d0f95A', }, { pid: 177, @@ -1750,25 +1753,28 @@ export const bscFarmConfig: UniversalFarmConfig[] = [ pid: 173, chainId: ChainId.BSC, protocol: Protocol.STABLE, - lpAddress: '0xB1D54d76E2cB9425Ec9c018538cc531440b55dbB', token0: bscTokens.cake, token1: bscTokens.sdcake, + lpAddress: '0xB1D54d76E2cB9425Ec9c018538cc531440b55dbB', + stableSwapAddress: '0xb8204D31379A9B317CD61C833406C972F58ecCbC', }, { pid: 174, chainId: ChainId.BSC, protocol: Protocol.STABLE, - lpAddress: '0xb9dC6396AcFFD24E0f69Dfd3231fDaeB31514D02', token0: bscTokens.cake, token1: bscTokens.mcake, + lpAddress: '0xb9dC6396AcFFD24E0f69Dfd3231fDaeB31514D02', + stableSwapAddress: '0xc54d35a8Cfd9f6dAe50945Df27A91C9911A03ab1', }, { pid: 163, chainId: ChainId.BSC, protocol: Protocol.STABLE, - lpAddress: '0xB2Aa63f363196caba3154D4187949283F085a488', token0: bscTokens.hay, token1: bscTokens.usdt, + lpAddress: '0xB2Aa63f363196caba3154D4187949283F085a488', + stableSwapAddress: '0xb1Da7D2C257c5700612BdE35C8d7187dc80d79f1', }, { pid: 42, diff --git a/packages/farms/src/types.ts b/packages/farms/src/types.ts index b50206b0dbae6..30347fe82f464 100644 --- a/packages/farms/src/types.ts +++ b/packages/farms/src/types.ts @@ -332,8 +332,13 @@ export type FarmBaseConfig = { token1: Token } -export type UniversalFarmConfigV2AndStableSwap = { - protocol: Protocol.V2 | Protocol.STABLE +export type UniversalFarmConfigStableSwap = { + protocol: Protocol.STABLE + stableSwapAddress: Address +} & FarmBaseConfig + +export type UniversalFarmConfigV2 = { + protocol: Protocol.V2 } & FarmBaseConfig export type UniversalFarmConfigV3 = { @@ -344,7 +349,7 @@ export type UniversalFarmConfigV3 = { /** * minimal pool info for a farm */ -export type UniversalFarmConfig = UniversalFarmConfigV2AndStableSwap | UniversalFarmConfigV3 +export type UniversalFarmConfig = UniversalFarmConfigV2 | UniversalFarmConfigStableSwap | UniversalFarmConfigV3 // only v2/ss farms have bCakeWrapperAddress export type BCakeWrapperFarmConfig = { diff --git a/packages/farms/src/utils.ts b/packages/farms/src/utils.ts index 4c4d412b1be40..4818ab1760060 100644 --- a/packages/farms/src/utils.ts +++ b/packages/farms/src/utils.ts @@ -1,5 +1,101 @@ -import { FarmV3Data } from './types' +import { ChainId } from '@pancakeswap/chains' +import { getStableSwapPools } from '@pancakeswap/stable-swap-sdk' +import { isAddressEqual } from 'viem' +import { UNIVERSAL_BCAKEWRAPPER_FARMS } from './farms' +import { + ComputedFarmConfigV3, + FarmV3Data, + SerializedClassicFarmConfig, + SerializedFarmConfig, + SerializedStableFarmConfig, + UniversalFarmConfig, + UniversalFarmConfigStableSwap, + UniversalFarmConfigV2, + UniversalFarmConfigV3, +} from './types' export function isActiveV3Farm(farm: FarmV3Data, poolLength: number) { return farm.pid !== 0 && farm.multiplier !== '0X' && poolLength && poolLength >= farm.pid } + +type LegacyFarmConfig = SerializedFarmConfig & { chainId: ChainId; version: 2 | 3 } +type LegacyStableFarmConfig = SerializedStableFarmConfig & { chainId: ChainId; version: 2 | 3 } +type LegacyClassicFarmConfig = SerializedClassicFarmConfig & { chainId: ChainId; version: 2 | 3 } +type LegacyV3FarmConfig = ComputedFarmConfigV3 & { chainId: ChainId; version: 2 | 3 } +export function formatUniversalFarmToSerializedFarm(farms: UniversalFarmConfig[]): Array { + return farms + .map((farm) => { + switch (farm.protocol) { + case 'stable': + return formatStableUniversalFarmToSerializedFarm(farm as UniversalFarmConfigStableSwap) + case 'v2': + return formatV2UniversalFarmToSerializedFarm(farm as UniversalFarmConfigV2) + case 'v3': + return formatV3UniversalFarmToSerializedFarm(farm as UniversalFarmConfigV3) + default: + return undefined + } + }) + .filter((farm): farm is LegacyFarmConfig => farm !== undefined) +} + +const formatStableUniversalFarmToSerializedFarm = ( + farm: UniversalFarmConfigStableSwap, +): LegacyStableFarmConfig | undefined => { + const { chainId, lpAddress, pid, token0, token1, stableSwapAddress } = farm + const stablePair = getStableSwapPools(chainId).find((pair) => { + return isAddressEqual(pair.stableSwapAddress, stableSwapAddress) + }) + const bCakeConfig = UNIVERSAL_BCAKEWRAPPER_FARMS.find((config) => { + return chainId === config.chainId && isAddressEqual(config.lpAddress, lpAddress) + }) + + if (!stablePair) { + console.warn(`Could not find stable pair for farm with stableSwapAddress ${stableSwapAddress}`) + return undefined + } + + return { + pid, + lpAddress, + lpSymbol: `${token0.symbol}-${token1.symbol} LP`, + token: token0, + quoteToken: token1, + stableSwapAddress, + stableLpFee: stablePair.stableLpFee, + stableLpFeeRateOfTotalFee: stablePair.stableLpFeeRateOfTotalFee, + infoStableSwapAddress: stablePair.infoStableSwapAddress, + bCakeWrapperAddress: bCakeConfig?.bCakeWrapperAddress, + chainId, + version: 2, + } +} + +const formatV2UniversalFarmToSerializedFarm = (farm: UniversalFarmConfigV2): LegacyClassicFarmConfig => { + const { chainId, pid, lpAddress, token0, token1 } = farm + return { + pid, + lpAddress, + lpSymbol: `${token0.symbol}-${token1.symbol} LP`, + token: token0, + quoteToken: token1, + chainId, + version: 2, + } +} + +const formatV3UniversalFarmToSerializedFarm = (farm: UniversalFarmConfigV3): LegacyV3FarmConfig => { + const { chainId, pid, lpAddress, token0, token1, feeAmount } = farm + return { + pid, + lpAddress, + lpSymbol: `${token0.symbol}-${token1.symbol} LP`, + token0, + token1, + token: token0, + quoteToken: token1, + feeAmount, + chainId, + version: 3, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cae5ad2b48483..a4de97e49cc0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1541,6 +1541,9 @@ importers: '@pancakeswap/sdk': specifier: workspace:* version: link:../swap-sdk + '@pancakeswap/stable-swap-sdk': + specifier: workspace:* + version: link:../stable-swap-sdk '@pancakeswap/swap-sdk-core': specifier: workspace:* version: link:../swap-sdk-core