import type { Address, Chain } from "viem"; import { mainnet, base, arbitrum, baseSepolia } from "viem/chains"; import type { ChainName } from "./types.js"; import { UnknownTokenError } from "./errors.js"; export interface TokenInfo { address: Address; decimals: number; } export interface ChainConfig { name: ChainName; id: number; viemChain: Chain; /** Token symbol -> info. Symbols are case-insensitive at resolution time. */ tokens: Record; contracts: { /** Balancer Vault (flash-loan provider for the Manager). Same address on all three chains. */ balancerVault: Address; /** Aave V3 Pool (for the AaveV3 strategy verb). Verify against resources/chains.md before mainnet. */ aaveV3Pool?: Address; /** Uniswap V3 SwapRouter (the original deadline-bearing router; matches the decoder's exactInput). */ uniswapV3Router?: Address; /** Uniswap V3 NonfungiblePositionManager (decoder constructor arg; used for LP sanitization). */ uniswapV3PositionManager?: Address; /** Odos V2 router (constructor arg for the full syUSD decoder's Odos sanitizer). */ odosRouter?: Address; /** Magpie (fly.trade) router (constructor arg for the full syUSD decoder's Magpie sanitizer). */ magpieRouter?: Address; }; } const BALANCER_VAULT: Address = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"; export const CHAINS: Record = { ethereum: { name: "ethereum", id: 1, viemChain: mainnet, tokens: { WETH: { address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: 18 }, USDC: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals: 6 }, USDT: { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6 }, WSTETH: { address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", decimals: 18 }, }, contracts: { balancerVault: BALANCER_VAULT, aaveV3Pool: "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2", uniswapV3Router: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", // SwapRouter02 uniswapV3PositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", odosRouter: "0xCf5540fFFCdC3d510B18bFcA6d2b9987b0772559", magpieRouter: "0x20F6ee51340aDEed01A59B0e65cB3703f3dc860c", // fly.trade DexAggregator }, }, base: { name: "base", id: 8453, viemChain: base, tokens: { WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18 }, USDC: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6 }, WSTETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", decimals: 18 }, }, contracts: { balancerVault: BALANCER_VAULT, aaveV3Pool: "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", uniswapV3Router: "0x2626664c2603336E57B271c5C0b26F421741e481", // SwapRouter02 (Base) uniswapV3PositionManager: "0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1", odosRouter: "0x19cEeAd7105607Cd444F5ad10dd51356436095a1", magpieRouter: "0x20F6ee51340aDEed01A59B0e65cB3703f3dc860c", // fly.trade DexAggregator }, }, arbitrum: { name: "arbitrum", id: 42161, viemChain: arbitrum, tokens: { WETH: { address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", decimals: 18 }, USDC: { address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimals: 6 }, USDT: { address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", decimals: 6 }, WSTETH: { address: "0x5979D7b546E38E414F7E9822514be443A4800529", decimals: 18 }, }, contracts: { balancerVault: BALANCER_VAULT, aaveV3Pool: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", uniswapV3Router: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", // SwapRouter02 uniswapV3PositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", odosRouter: "0xa669e7A0d4b3e4Fa48af2dE86BD4CD7126Be4e13", magpieRouter: "0x20F6ee51340aDEed01A59B0e65cB3703f3dc860c", // fly.trade DexAggregator }, }, // Base Sepolia testnet — for go-live rehearsals with real (free) testnet ETH before mainnet. // Only the core vault contracts are needed to deploy; DeFi integration addresses are intentionally // omitted (use syUsdSeedMode "none" on testnet). USDC is Circle's official Base Sepolia token. baseSepolia: { name: "baseSepolia", id: 84532, viemChain: baseSepolia, tokens: { WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18 }, USDC: { address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", decimals: 6 }, }, contracts: { // Balancer V2 vault is stored by the Manager but only called on a flash-loan strategy (not at // deploy/finalize); the canonical address is kept for parity and is harmless on testnet. balancerVault: BALANCER_VAULT, }, }, }; const ID_TO_NAME: Record = { 1: "ethereum", 8453: "base", 42161: "arbitrum", 84532: "baseSepolia", }; /** Resolve a chain by name or numeric id. */ export function resolveChain(chain: ChainName | number): ChainConfig { if (typeof chain === "number") { const name = ID_TO_NAME[chain]; if (!name) throw new Error(`Unsupported chain id ${chain}. Known: 1, 8453, 42161, 84532.`); return CHAINS[name]; } const cfg = CHAINS[chain]; if (!cfg) throw new Error(`Unknown chain "${chain}". Known: ethereum, base, arbitrum, baseSepolia.`); return cfg; } const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/; /** Resolve a token symbol (case-insensitive) or raw 0x address to an Address on the given chain. */ export function resolveToken(chain: ChainConfig, symbolOrAddress: Address | string): Address { if (ADDRESS_RE.test(symbolOrAddress)) return symbolOrAddress as Address; const info = chain.tokens[symbolOrAddress.toUpperCase()]; if (!info) throw new UnknownTokenError(symbolOrAddress, chain.name); return info.address; } /** Look up known decimals for a token symbol or address on the given chain (undefined if unknown). */ export function tokenDecimals(chain: ChainConfig, symbolOrAddress: Address | string): number | undefined { if (ADDRESS_RE.test(symbolOrAddress)) { const hit = Object.values(chain.tokens).find( (t) => t.address.toLowerCase() === symbolOrAddress.toLowerCase(), ); return hit?.decimals; } return chain.tokens[symbolOrAddress.toUpperCase()]?.decimals; }