Files
smom-dbis-138/services/token-aggregation/src/config/provider-capabilities.ts

704 lines
28 KiB
TypeScript

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<string, unknown>;
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
);
}