704 lines
28 KiB
TypeScript
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
|
|
);
|
|
}
|