spb/os-vault Public
Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.
Swift 96%
Shell 3.4%
Makefile 0.6%
1//2// Token.swift3// OS Vault4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//89import Foundation1011/// An ERC-20 stablecoin deployment set. Every address and decimal count below12/// was verified live on-chain via `symbol()`/`decimals()` eth_calls13/// (docs/STABLECOINS.md + docs/RESEARCH-MULTICHAIN.md, 2026-08-05).14///15/// Registry rules learned from that research:16/// - decimals are per-DEPLOYMENT data: USDT/USDC are 18 on BNB Chain, 617/// everywhere else — hence the separate BNB-peg entries.18/// - bridged USDC.e reports the same `symbol()` as native USDC on19/// Arbitrum/Optimism/Polygon; the registry distinguishes them by address20/// and shows "USDC.e" to the user.21/// - Arbitrum/Polygon USDT was upgraded in place to USDT0 (same address,22/// same 6 decimals) — never assert on-chain symbol strings.23public struct Token: Identifiable, Hashable, Sendable {24 public let symbol: String25 public let name: String26 public let decimals: Int27 /// Checksummed contract address per network the token is deployed on.28 public let addresses: [Network: String]2930 public var id: String { symbol }3132 public func address(on network: Network) -> String? {33 addresses[network]34 }3536 // MARK: - Registry3738 public static let usdc = Token(39 symbol: "USDC", name: "USD Coin", decimals: 6,40 addresses: [41 .baseSepolia: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",42 .baseMainnet: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",43 .ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",44 .arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",45 .optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",46 .polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",47 .avalanche: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",48 .linea: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff"49 ]50 )5152 /// Bridged USDC — same on-chain symbol as native USDC, different address.53 public static let usdce = Token(54 symbol: "USDC.e", name: "Bridged USDC", decimals: 6,55 addresses: [56 .arbitrum: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",57 .optimism: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",58 .polygon: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",59 .avalanche: "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664",60 .gnosis: "0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0",61 .scroll: "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4"62 ]63 )6465 public static let usdt = Token(66 symbol: "USDT", name: "Tether USD", decimals: 6,67 addresses: [68 .baseMainnet: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",69 .ethereum: "0xdAC17F958D2ee523a2206206994597C13D831ec7",70 .arbitrum: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT0 in-place upgrade71 .optimism: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",72 .polygon: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", // USDT0 in-place upgrade73 .avalanche: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", // native USDt74 .gnosis: "0x4ECaBa5870353805a9F068101A40E0f32ed605C6",75 .linea: "0xA219439258ca9da29E9Cc4cE5596924745e12B93",76 .scroll: "0xf55BEC9cafDbE8730f096Aa55dad6D22d44099Df"77 ]78 )7980 /// Binance-peg variants: 18 decimals, not issuer-native. Kept as separate81 /// registry entries because decimals differ from every other deployment.82 public static let usdtBNB = Token(83 symbol: "USDT", name: "Tether USD (BNB peg)", decimals: 18,84 addresses: [.bnb: "0x55d398326f99059fF775485246999027B3197955"]85 )8687 public static let usdcBNB = Token(88 symbol: "USDC", name: "USD Coin (BNB peg)", decimals: 18,89 addresses: [.bnb: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"]90 )9192 public static let dai = Token(93 symbol: "DAI", name: "Dai Stablecoin", decimals: 18,94 addresses: [95 .baseMainnet: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",96 .ethereum: "0x6B175474E89094C44Da98b954EedeAC495271d0F",97 .arbitrum: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",98 .optimism: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",99 .polygon: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",100 .bnb: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3",101 .avalanche: "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70", // DAI.e102 .linea: "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5",103 .scroll: "0xcA77eB3fEFe3725Dc33bccB54eDEFc3D9f764f97"104 ]105 )106107 public static let usds = Token(108 symbol: "USDS", name: "Sky Dollar", decimals: 18,109 addresses: [110 .ethereum: "0xdC035D45d973E3EC169d2276DDab16f1e407384F",111 .baseMainnet: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc"112 ]113 )114115 public static let eurc = Token(116 symbol: "EURC", name: "Euro Coin", decimals: 6,117 addresses: [118 .baseMainnet: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"119 ]120 )121122 public static let all: [Token] = [usdc, usdcBNB, usdce, usdt, usdtBNB, dai, usds, eurc]123124 /// Tokens usable on a given network, USDC-family first.125 public static func available(on network: Network) -> [Token] {126 all.filter { $0.addresses[network] != nil }127 }128}129