import { AbiCoder } from 'ethers'; import { PlannerProvider, ProviderCapabilityRecord, ProviderPairCapability, } from '../services/planner-v2-types'; import { encodeChain138DodoV3ProviderData, isChain138DodoV3ExecutionLive } from '../services/dodo-v3-pilot'; import { getChain138PilotVenueEdges } from '../services/chain138-pilot-venues'; import { getChain138RoutingAssets } from './routing-assets'; const abiCoder = AbiCoder.defaultAbiCoder(); const CHAIN_138 = 138; const CHAIN138_UNISWAP_V3_ROUTER = '0xde9cd8ee2811e6e64a41d5f68be315d33995975e'; const CHAIN138_UNISWAP_V3_QUOTER = '0x6abbb1ceb2468e748a03a00cd6aa9bfe893afa1f'; const CHAIN138_PILOT_BALANCER_VAULT = '0x96423d7c1727698d8a25ebfb88131e9422d1a3c3'; const CHAIN138_PILOT_CURVE_3POOL = '0xe440ec15805be4c7babcd17a63b8c8a08a492e0f'; const CHAIN138_PILOT_ONEINCH_ROUTER = '0x500b84b1bc6f59c1898a5fe538ea20a758757a4f'; const CHAIN138_PILOT_BALANCER_WETH_USDT_POOL_ID = '0x877cd220759e8c94b82f55450c85d382ae06856c426b56d93092a420facbc324'; const CHAIN138_PILOT_BALANCER_WETH_USDC_POOL_ID = '0xd8dfb18a6baf9b29d8c2dbd74639db87ac558af120df5261dab8e2a5de69013b'; function normalizeAddress(value?: string): string { return String(value || '').trim().toLowerCase(); } function liveOrPlannedAddress(value?: string): 'live' | 'planned' { return normalizeAddress(value) ? 'live' : 'planned'; } function bidirectionalPair(args: { chainId: number; provider: PlannerProvider; tokenASymbol: string; tokenAAddress: string; tokenBSymbol: string; tokenBAddress: string; status: 'live' | 'planned' | 'blocked'; target?: string; providerData?: Record; providerDataHex?: string; notes?: string[]; reason?: string; }): ProviderPairCapability[] { return [ { chainId: args.chainId, provider: args.provider, legType: 'swap', status: args.status, tokenInSymbol: args.tokenASymbol, tokenInAddress: normalizeAddress(args.tokenAAddress), tokenOutSymbol: args.tokenBSymbol, tokenOutAddress: normalizeAddress(args.tokenBAddress), target: normalizeAddress(args.target), providerData: args.providerData, providerDataHex: args.providerDataHex, notes: args.notes, reason: args.reason, }, { chainId: args.chainId, provider: args.provider, legType: 'swap', status: args.status, tokenInSymbol: args.tokenBSymbol, tokenInAddress: normalizeAddress(args.tokenBAddress), tokenOutSymbol: args.tokenASymbol, tokenOutAddress: normalizeAddress(args.tokenAAddress), target: normalizeAddress(args.target), providerData: args.providerData, providerDataHex: args.providerDataHex, notes: args.notes, reason: args.reason, }, ]; } function encodeDodoPool(poolAddress: string): string { return abiCoder.encode(['address'], [poolAddress]); } function encodeUniswapRoute(fee: number, quoter: string): string { return abiCoder.encode(['bytes', 'uint24', 'address', 'bool'], ['0x', fee, quoter, false]); } function encodeBalancerRoute(poolId: string): string { return abiCoder.encode(['bytes32'], [poolId]); } function encodeCurveRoute(i: number, j: number, useUnderlying: boolean): string { return abiCoder.encode(['int128', 'int128', 'bool'], [i, j, useUnderlying]); } function encodeOneInchRoute(router: string): string { return abiCoder.encode(['address', 'address', 'bytes'], [router, router, '0x']); } function encodeRouterV2Route(factory: string, router: string): string { return abiCoder.encode(['address', 'address'], [factory, router]); } function chain138DodoCapabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const dodoProvider = normalizeAddress(process.env.DODO_PMM_PROVIDER_ADDRESS) || normalizeAddress(process.env.DODO_PMM_PROVIDER) || '0x3f729632e9553ebaccde2e9b4c8f2b285b014f2e'; const stablePool = '0x9e89bae009adf128782e19e8341996c596ac40dc'; const cusdtUsdtPool = '0x866cb44b59303d8dc5f4f9e3e7a8e8b0bf238d66'; const cusdcUsdcPool = '0xc39b7d0f40838cbfb54649d327f49a6dac964062'; const cusdtXaucPool = '0x1aa55e2001e5651349aff5a63fd7a7ae44f0f1b0'; const cusdcXaucPool = '0xea9ac6357cacb42a83b9082b870610363b177cba'; const ceurtXaucPool = '0xba99bc1eaac164569d5aca96c806934ddaf970cf'; const cbtcCusdtPool = normalizeAddress(process.env.CHAIN138_POOL_CBTC_CUSDT); const cbtcCusdcPool = normalizeAddress(process.env.CHAIN138_POOL_CBTC_CUSDC); const cbtcXaucPool = normalizeAddress(process.env.CHAIN138_POOL_CBTC_CXAUC); const wethUsdtPool = normalizeAddress(process.env.CHAIN138_POOL_WETH_USDT); const wethUsdcPool = normalizeAddress(process.env.CHAIN138_POOL_WETH_USDC); const pairs: ProviderPairCapability[] = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cUSDT', tokenAAddress: assets.cUSDT.address, tokenBSymbol: 'cUSDC', tokenBAddress: assets.cUSDC.address, status: 'live', target: dodoProvider, providerData: { poolAddress: stablePool }, providerDataHex: encodeDodoPool(stablePool), notes: ['Canonical stable pool on the official DODO V2 DVM-backed stack.'], }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cUSDT', tokenAAddress: assets.cUSDT.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status: 'live', target: dodoProvider, providerData: { poolAddress: cusdtUsdtPool }, providerDataHex: encodeDodoPool(cusdtUsdtPool), notes: ['Canonical stable pool on the official DODO V2 DVM-backed stack.'], }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cUSDC', tokenAAddress: assets.cUSDC.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status: 'live', target: dodoProvider, providerData: { poolAddress: cusdcUsdcPool }, providerDataHex: encodeDodoPool(cusdcUsdcPool), notes: ['Canonical stable pool on the official DODO V2 DVM-backed stack.'], }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cUSDT', tokenAAddress: assets.cUSDT.address, tokenBSymbol: 'cXAUC', tokenBAddress: assets.cXAUC.address, status: 'live', target: dodoProvider, providerData: { poolAddress: cusdtXaucPool }, providerDataHex: encodeDodoPool(cusdtXaucPool), notes: ['Commodity route; excluded unless policy allows commodity intermediates.'], }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cUSDC', tokenAAddress: assets.cUSDC.address, tokenBSymbol: 'cXAUC', tokenBAddress: assets.cXAUC.address, status: 'live', target: dodoProvider, providerData: { poolAddress: cusdcXaucPool }, providerDataHex: encodeDodoPool(cusdcXaucPool), notes: ['Commodity route; excluded unless policy allows commodity intermediates.'], }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cEURT', tokenAAddress: assets.cEURT.address, tokenBSymbol: 'cXAUC', tokenBAddress: assets.cXAUC.address, status: 'live', target: dodoProvider, providerData: { poolAddress: ceurtXaucPool }, providerDataHex: encodeDodoPool(ceurtXaucPool), notes: ['Commodity route; excluded unless policy allows commodity intermediates.'], }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cBTC', tokenAAddress: assets.cBTC.address, tokenBSymbol: 'cUSDT', tokenBAddress: assets.cUSDT.address, status: liveOrPlannedAddress(cbtcCusdtPool), target: dodoProvider, providerData: cbtcCusdtPool ? { poolAddress: cbtcCusdtPool } : undefined, providerDataHex: cbtcCusdtPool ? encodeDodoPool(cbtcCusdtPool) : undefined, notes: ['Bitcoin monetary-unit route for the jewelry-box program.'], reason: cbtcCusdtPool ? undefined : 'Set CHAIN138_POOL_CBTC_CUSDT after the canonical cBTC/cUSDT PMM pool is created and funded.', }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cBTC', tokenAAddress: assets.cBTC.address, tokenBSymbol: 'cUSDC', tokenBAddress: assets.cUSDC.address, status: liveOrPlannedAddress(cbtcCusdcPool), target: dodoProvider, providerData: cbtcCusdcPool ? { poolAddress: cbtcCusdcPool } : undefined, providerDataHex: cbtcCusdcPool ? encodeDodoPool(cbtcCusdcPool) : undefined, notes: ['Bitcoin monetary-unit route for the jewelry-box program.'], reason: cbtcCusdcPool ? undefined : 'Set CHAIN138_POOL_CBTC_CUSDC after the canonical cBTC/cUSDC PMM pool is created and funded.', }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'cBTC', tokenAAddress: assets.cBTC.address, tokenBSymbol: 'cXAUC', tokenBAddress: assets.cXAUC.address, status: liveOrPlannedAddress(cbtcXaucPool), target: dodoProvider, providerData: cbtcXaucPool ? { poolAddress: cbtcXaucPool } : undefined, providerDataHex: cbtcXaucPool ? encodeDodoPool(cbtcXaucPool) : undefined, notes: ['Bitcoin-to-gold route for jewelry-box rebalances; excluded unless policy allows commodity intermediates.'], reason: cbtcXaucPool ? undefined : 'Set CHAIN138_POOL_CBTC_CXAUC after the canonical cBTC/cXAUC PMM pool is created and funded.', }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status: wethUsdtPool ? 'live' : 'planned', target: dodoProvider, providerData: wethUsdtPool ? { poolAddress: wethUsdtPool } : undefined, providerDataHex: wethUsdtPool ? encodeDodoPool(wethUsdtPool) : undefined, notes: ['Phase 1 WETH lane for router-v2 stable execution.'], reason: wethUsdtPool ? undefined : 'Set CHAIN138_POOL_WETH_USDT after the canonical WETH/USDT pool is created and funded.', }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status: wethUsdcPool ? 'live' : 'planned', target: dodoProvider, providerData: wethUsdcPool ? { poolAddress: wethUsdcPool } : undefined, providerDataHex: wethUsdcPool ? encodeDodoPool(wethUsdcPool) : undefined, notes: ['Phase 1 WETH lane for router-v2 stable execution.'], reason: wethUsdcPool ? undefined : 'Set CHAIN138_POOL_WETH_USDC after the canonical WETH/USDC pool is created and funded.', }), ]; const livePairs = pairs.filter((pair) => pair.status === 'live'); return { chainId: CHAIN_138, provider: 'dodo', executionMode: 'onchain', live: livePairs.length > 0, quoteLive: livePairs.length > 0, executionLive: livePairs.length > 0, supportedLegTypes: ['swap'], pairs, notes: ['DODO is the first production executor for Chain 138 router-v2 rollout.'], }; } function chain138DodoV3Capabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const enabled = process.env.CHAIN138_ENABLE_DODO_V3_ROUTING !== '0'; const proxy = normalizeAddress(process.env.CHAIN138_D3_PROXY_ADDRESS) || '0xc9a11abb7c63d88546be24d58a6d95e3762cb843'; const pool = normalizeAddress(process.env.CHAIN138_D3_MM_ADDRESS) || '0x6550a3a59070061a262a893a1d6f3f490affdbda'; const status = enabled && proxy && pool ? 'live' : 'planned'; const executionLive = status === 'live' && isChain138DodoV3ExecutionLive(); const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'dodo_v3', tokenASymbol: 'WETH10', tokenAAddress: assets.WETH10.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status, target: proxy, providerData: status === 'live' ? { poolAddress: pool, proxyAddress: proxy, quoteMethod: 'querySellTokens' } : undefined, providerDataHex: executionLive ? encodeChain138DodoV3ProviderData(pool) : undefined, notes: [ 'Canonical Chain 138 DODO v3 / D3MM pilot route.', executionLive ? 'Planner visibility, quote selection, and EnhancedSwapRouterV2 execution are live for the canonical pilot pair.' : 'Planner visibility and quote selection are live; EnhancedSwapRouterV2 adapter support is still pending.', ], reason: status === 'planned' ? 'Set CHAIN138_ENABLE_DODO_V3_ROUTING=1 and ensure CHAIN138_D3_MM_ADDRESS / CHAIN138_D3_PROXY_ADDRESS are configured to expose the pilot venue.' : undefined, }), ]; return { chainId: CHAIN_138, provider: 'dodo_v3', executionMode: 'onchain', live: status === 'live', quoteLive: status === 'live', executionLive, supportedLegTypes: ['swap'], pairs, notes: [ executionLive ? 'Private DODO v3 / D3MM Chain 138 pilot is live in planner-v2 visibility and internal execution-plan calldata.' : 'Private DODO v3 / D3MM Chain 138 pilot promoted into planner-v2 visibility.', executionLive ? 'Route discovery and execution-plan generation are live for the canonical pilot pair.' : 'Route discovery is live, but internal execution-plan calldata is intentionally withheld until a dedicated D3 route executor adapter exists.', ], }; } function chain138UniswapCapabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const router = normalizeAddress(process.env.UNISWAP_V3_ROUTER || CHAIN138_UNISWAP_V3_ROUTER); const quoter = normalizeAddress(process.env.UNISWAP_QUOTER_ADDRESS || process.env.UNISWAP_QUOTER || CHAIN138_UNISWAP_V3_QUOTER); const wethUsdtFee = Number(process.env.UNISWAP_V3_WETH_USDT_FEE || '500'); const wethUsdcFee = Number(process.env.UNISWAP_V3_WETH_USDC_FEE || '500'); const status = router && quoter ? 'live' : 'planned'; const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'uniswap_v3', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status, target: router, providerData: status === 'live' ? { fee: wethUsdtFee, quoter } : undefined, providerDataHex: status === 'live' ? encodeUniswapRoute(wethUsdtFee, quoter) : undefined, notes: ['Canonical Chain 138 upstream-native Uniswap v3 WETH/USDT venue.'], reason: status === 'planned' ? 'Configure UNISWAP_V3_ROUTER and UNISWAP_QUOTER_ADDRESS after Chain 138 native venue deployment.' : undefined, }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'uniswap_v3', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status, target: router, providerData: status === 'live' ? { fee: wethUsdcFee, quoter } : undefined, providerDataHex: status === 'live' ? encodeUniswapRoute(wethUsdcFee, quoter) : undefined, notes: ['Canonical Chain 138 upstream-native Uniswap v3 WETH/USDC venue.'], reason: status === 'planned' ? 'Configure UNISWAP_V3_ROUTER and UNISWAP_QUOTER_ADDRESS after Chain 138 native venue deployment.' : undefined, }), ]; return { chainId: CHAIN_138, provider: 'uniswap_v3', executionMode: 'onchain', live: status === 'live', quoteLive: status === 'live', executionLive: status === 'live', supportedLegTypes: ['swap'], pairs, notes: ['Canonical Chain 138 upstream-native Uniswap v3 router/quoter path.'], }; } function chain138UniswapV2Capabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const factory = normalizeAddress(process.env.CHAIN_138_UNISWAP_V2_FACTORY); const router = normalizeAddress(process.env.CHAIN_138_UNISWAP_V2_ROUTER); const wethUsdtPair = normalizeAddress(process.env.CHAIN138_UNISWAP_V2_NATIVE_WETH_USDT_PAIR); const wethUsdcPair = normalizeAddress(process.env.CHAIN138_UNISWAP_V2_NATIVE_WETH_USDC_PAIR); const cusdtCusdcPair = normalizeAddress(process.env.CHAIN138_UNISWAP_V2_NATIVE_CUSDT_CUSDC_PAIR); const status = factory && router ? 'live' : 'planned'; const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'uniswap_v2', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status, target: router, providerData: status === 'live' ? { factory, router, pair: wethUsdtPair } : undefined, providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined, notes: ['Canonical Chain 138 native Uniswap v2 WETH/USDT venue.'], reason: status === 'planned' ? 'Configure CHAIN_138_UNISWAP_V2_FACTORY and CHAIN_138_UNISWAP_V2_ROUTER after Chain 138 native venue deployment.' : undefined, }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'uniswap_v2', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status, target: router, providerData: status === 'live' ? { factory, router, pair: wethUsdcPair } : undefined, providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined, notes: ['Canonical Chain 138 native Uniswap v2 WETH/USDC venue.'], reason: status === 'planned' ? 'Configure CHAIN_138_UNISWAP_V2_FACTORY and CHAIN_138_UNISWAP_V2_ROUTER after Chain 138 native venue deployment.' : undefined, }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'uniswap_v2', tokenASymbol: 'cUSDT', tokenAAddress: assets.cUSDT.address, tokenBSymbol: 'cUSDC', tokenBAddress: assets.cUSDC.address, status, target: router, providerData: status === 'live' ? { factory, router, pair: cusdtCusdcPair } : undefined, providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined, notes: ['Canonical Chain 138 native Uniswap v2 GRU stable venue.'], reason: status === 'planned' ? 'Configure CHAIN_138_UNISWAP_V2_FACTORY and CHAIN_138_UNISWAP_V2_ROUTER after Chain 138 native venue deployment.' : undefined, }), ]; return { chainId: CHAIN_138, provider: 'uniswap_v2', executionMode: 'onchain', live: status === 'live', quoteLive: status === 'live', executionLive: status === 'live', supportedLegTypes: ['swap'], pairs, notes: ['Canonical Chain 138 native Uniswap v2 router/factory path.'], }; } function chain138SushiswapCapabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const factory = normalizeAddress(process.env.CHAIN_138_SUSHISWAP_FACTORY); const router = normalizeAddress(process.env.CHAIN_138_SUSHISWAP_ROUTER); const wethUsdtPair = normalizeAddress(process.env.CHAIN138_SUSHISWAP_NATIVE_WETH_USDT_PAIR); const wethUsdcPair = normalizeAddress(process.env.CHAIN138_SUSHISWAP_NATIVE_WETH_USDC_PAIR); const cusdtCusdcPair = normalizeAddress(process.env.CHAIN138_SUSHISWAP_NATIVE_CUSDT_CUSDC_PAIR); const status = factory && router ? 'live' : 'planned'; const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'sushiswap', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status, target: router, providerData: status === 'live' ? { factory, router, pair: wethUsdtPair } : undefined, providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined, notes: ['Canonical Chain 138 native SushiSwap-compatible WETH/USDT venue.'], reason: status === 'planned' ? 'Configure CHAIN_138_SUSHISWAP_FACTORY and CHAIN_138_SUSHISWAP_ROUTER after Chain 138 Sushi deployment.' : undefined, }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'sushiswap', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status, target: router, providerData: status === 'live' ? { factory, router, pair: wethUsdcPair } : undefined, providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined, notes: ['Canonical Chain 138 native SushiSwap-compatible WETH/USDC venue.'], reason: status === 'planned' ? 'Configure CHAIN_138_SUSHISWAP_FACTORY and CHAIN_138_SUSHISWAP_ROUTER after Chain 138 Sushi deployment.' : undefined, }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'sushiswap', tokenASymbol: 'cUSDT', tokenAAddress: assets.cUSDT.address, tokenBSymbol: 'cUSDC', tokenBAddress: assets.cUSDC.address, status, target: router, providerData: status === 'live' ? { factory, router, pair: cusdtCusdcPair } : undefined, providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined, notes: ['Canonical Chain 138 native SushiSwap-compatible GRU stable venue.'], reason: status === 'planned' ? 'Configure CHAIN_138_SUSHISWAP_FACTORY and CHAIN_138_SUSHISWAP_ROUTER after Chain 138 Sushi deployment.' : undefined, }), ]; return { chainId: CHAIN_138, provider: 'sushiswap', executionMode: 'onchain', live: status === 'live', quoteLive: status === 'live', executionLive: status === 'live', supportedLegTypes: ['swap'], pairs, notes: ['Canonical Chain 138 native SushiSwap-compatible router/factory path.'], }; } function chain138BalancerCapabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const vault = normalizeAddress(process.env.BALANCER_VAULT || CHAIN138_PILOT_BALANCER_VAULT); const wethUsdtPoolId = process.env.BALANCER_WETH_USDT_POOL_ID || CHAIN138_PILOT_BALANCER_WETH_USDT_POOL_ID; const wethUsdcPoolId = process.env.BALANCER_WETH_USDC_POOL_ID || CHAIN138_PILOT_BALANCER_WETH_USDC_POOL_ID; const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'balancer', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status: vault && wethUsdtPoolId ? 'live' : 'planned', target: vault, providerData: vault && wethUsdtPoolId ? { poolId: wethUsdtPoolId } : undefined, providerDataHex: vault && wethUsdtPoolId ? encodeBalancerRoute(wethUsdtPoolId) : undefined, notes: ['Enabled only after Chain 138 Balancer pool IDs are set.'], reason: vault && wethUsdtPoolId ? undefined : 'Configure BALANCER_VAULT and BALANCER_WETH_USDT_POOL_ID once the pool exists.', }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'balancer', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status: vault && wethUsdcPoolId ? 'live' : 'planned', target: vault, providerData: vault && wethUsdcPoolId ? { poolId: wethUsdcPoolId } : undefined, providerDataHex: vault && wethUsdcPoolId ? encodeBalancerRoute(wethUsdcPoolId) : undefined, notes: ['Enabled only after Chain 138 Balancer pool IDs are set.'], reason: vault && wethUsdcPoolId ? undefined : 'Configure BALANCER_VAULT and BALANCER_WETH_USDC_POOL_ID once the pool exists.', }), ]; return { chainId: CHAIN_138, provider: 'balancer', executionMode: 'onchain', live: pairs.some((pair) => pair.status === 'live'), quoteLive: pairs.some((pair) => pair.status === 'live'), executionLive: pairs.some((pair) => pair.status === 'live'), supportedLegTypes: ['swap'], pairs, notes: ['Balancer stays disabled until the minimum viable Chain 138 venue set exists.'], }; } function chain138CurveCapabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const curvePool = normalizeAddress(process.env.CURVE_3POOL || CHAIN138_PILOT_CURVE_3POOL); const status = liveOrPlannedAddress(curvePool); const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'curve', tokenASymbol: 'USDT', tokenAAddress: assets.USDT.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status, target: curvePool, providerData: status === 'live' ? { i: 0, j: 1, useUnderlying: false } : undefined, providerDataHex: status === 'live' ? encodeCurveRoute(0, 1, false) : undefined, notes: ['Curve is reserved for stable-stable legs; no direct WETH path is configured.'], reason: status === 'planned' ? 'Configure CURVE_3POOL once the Chain 138 stable-stable venue is live.' : undefined, }), ]; return { chainId: CHAIN_138, provider: 'curve', executionMode: 'onchain', live: status === 'live', quoteLive: status === 'live', executionLive: status === 'live', supportedLegTypes: ['swap'], pairs, notes: ['Curve is intentionally constrained to stable-stable execution for router-v2.'], }; } function chain138PartnerCapabilities(): ProviderCapabilityRecord { return { chainId: CHAIN_138, provider: 'partner', executionMode: 'partner', live: false, quoteLive: false, executionLive: false, supportedLegTypes: ['swap', 'bridge'], pairs: [], notes: ['1inch, 0x, and LiFi remain partner payload adapters until explicit Chain 138 live support is verified.'], }; } function chain138OneInchCapabilities(): ProviderCapabilityRecord { const assets = getChain138RoutingAssets(); const router = normalizeAddress(process.env.ONEINCH_ROUTER || CHAIN138_PILOT_ONEINCH_ROUTER); const status = router ? 'live' : 'planned'; const pairs = [ ...bidirectionalPair({ chainId: CHAIN_138, provider: 'one_inch', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDT', tokenBAddress: assets.USDT.address, status, target: router, providerData: status === 'live' ? { executor: router, allowanceTarget: router } : undefined, providerDataHex: status === 'live' ? encodeOneInchRoute(router) : undefined, notes: ['Enabled after the Chain 138 pilot-compatible 1inch router is deployed and funded.'], reason: status === 'planned' ? 'Configure ONEINCH_ROUTER once the Chain 138 pilot-compatible router is live.' : undefined, }), ...bidirectionalPair({ chainId: CHAIN_138, provider: 'one_inch', tokenASymbol: 'WETH', tokenAAddress: assets.WETH.address, tokenBSymbol: 'USDC', tokenBAddress: assets.USDC.address, status, target: router, providerData: status === 'live' ? { executor: router, allowanceTarget: router } : undefined, providerDataHex: status === 'live' ? encodeOneInchRoute(router) : undefined, notes: ['Enabled after the Chain 138 pilot-compatible 1inch router is deployed and funded.'], reason: status === 'planned' ? 'Configure ONEINCH_ROUTER once the Chain 138 pilot-compatible router is live.' : undefined, }), ]; return { chainId: CHAIN_138, provider: 'one_inch', executionMode: 'onchain', live: status === 'live', quoteLive: status === 'live', executionLive: status === 'live', supportedLegTypes: ['swap'], pairs, notes: ['1inch is promoted from partner-only placeholder to an executable Chain 138 pilot-compatible router when configured.'], }; } export function getProviderCapabilities(chainId: number): ProviderCapabilityRecord[] { if (chainId !== CHAIN_138) return []; return [ chain138DodoCapabilities(), chain138DodoV3Capabilities(), chain138UniswapCapabilities(), chain138UniswapV2Capabilities(), chain138SushiswapCapabilities(), chain138BalancerCapabilities(), chain138CurveCapabilities(), chain138OneInchCapabilities(), chain138PartnerCapabilities(), ]; } export function findProviderPairCapability( chainId: number, provider: PlannerProvider, tokenInAddress: string, tokenOutAddress: string ): ProviderPairCapability | undefined { const normalizedIn = normalizeAddress(tokenInAddress); const normalizedOut = normalizeAddress(tokenOutAddress); return getProviderCapabilities(chainId) .find((record) => record.provider === provider) ?.pairs.find( (pair) => pair.tokenInAddress === normalizedIn && pair.tokenOutAddress === normalizedOut ); }