// // Token.swift // OS Vault // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import Foundation /// An ERC-20 stablecoin deployment set. Every address and decimal count below /// was verified live on-chain via `symbol()`/`decimals()` eth_calls /// (docs/STABLECOINS.md + docs/RESEARCH-MULTICHAIN.md, 2026-08-05). /// /// Registry rules learned from that research: /// - decimals are per-DEPLOYMENT data: USDT/USDC are 18 on BNB Chain, 6 /// everywhere else — hence the separate BNB-peg entries. /// - bridged USDC.e reports the same `symbol()` as native USDC on /// Arbitrum/Optimism/Polygon; the registry distinguishes them by address /// and shows "USDC.e" to the user. /// - Arbitrum/Polygon USDT was upgraded in place to USDT0 (same address, /// same 6 decimals) — never assert on-chain symbol strings. public struct Token: Identifiable, Hashable, Sendable { public let symbol: String public let name: String public let decimals: Int /// Checksummed contract address per network the token is deployed on. public let addresses: [Network: String] public var id: String { symbol } public func address(on network: Network) -> String? { addresses[network] } // MARK: - Registry public static let usdc = Token( symbol: "USDC", name: "USD Coin", decimals: 6, addresses: [ .baseSepolia: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", .baseMainnet: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", .ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", .arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", .optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", .polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", .avalanche: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", .linea: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff" ] ) /// Bridged USDC — same on-chain symbol as native USDC, different address. public static let usdce = Token( symbol: "USDC.e", name: "Bridged USDC", decimals: 6, addresses: [ .arbitrum: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", .optimism: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", .polygon: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", .avalanche: "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664", .gnosis: "0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0", .scroll: "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4" ] ) public static let usdt = Token( symbol: "USDT", name: "Tether USD", decimals: 6, addresses: [ .baseMainnet: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2", .ethereum: "0xdAC17F958D2ee523a2206206994597C13D831ec7", .arbitrum: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT0 in-place upgrade .optimism: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", .polygon: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", // USDT0 in-place upgrade .avalanche: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", // native USDt .gnosis: "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", .linea: "0xA219439258ca9da29E9Cc4cE5596924745e12B93", .scroll: "0xf55BEC9cafDbE8730f096Aa55dad6D22d44099Df" ] ) /// Binance-peg variants: 18 decimals, not issuer-native. Kept as separate /// registry entries because decimals differ from every other deployment. public static let usdtBNB = Token( symbol: "USDT", name: "Tether USD (BNB peg)", decimals: 18, addresses: [.bnb: "0x55d398326f99059fF775485246999027B3197955"] ) public static let usdcBNB = Token( symbol: "USDC", name: "USD Coin (BNB peg)", decimals: 18, addresses: [.bnb: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"] ) public static let dai = Token( symbol: "DAI", name: "Dai Stablecoin", decimals: 18, addresses: [ .baseMainnet: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", .ethereum: "0x6B175474E89094C44Da98b954EedeAC495271d0F", .arbitrum: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", .optimism: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", .polygon: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", .bnb: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", .avalanche: "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70", // DAI.e .linea: "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5", .scroll: "0xcA77eB3fEFe3725Dc33bccB54eDEFc3D9f764f97" ] ) public static let usds = Token( symbol: "USDS", name: "Sky Dollar", decimals: 18, addresses: [ .ethereum: "0xdC035D45d973E3EC169d2276DDab16f1e407384F", .baseMainnet: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc" ] ) public static let eurc = Token( symbol: "EURC", name: "Euro Coin", decimals: 6, addresses: [ .baseMainnet: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42" ] ) public static let all: [Token] = [usdc, usdcBNB, usdce, usdt, usdtBNB, dai, usds, eurc] /// Tokens usable on a given network, USDC-family first. public static func available(on network: Network) -> [Token] { all.filter { $0.addresses[network] != nil } } }