/** * Hardcoded reference set of well-known asset symbols/names for imposter detection. * These are the identities a scam B20 is most likely to mimic. Extensible; the phase-2 * moat is a curated issuer registry that links on-chain creators to real entities. */ export interface KnownAsset { symbol: string; name: string; } export const KNOWN_ASSETS: KnownAsset[] = [ { symbol: "USDC", name: "USD Coin" }, { symbol: "USDT", name: "Tether USD" }, { symbol: "DAI", name: "Dai Stablecoin" }, { symbol: "USDS", name: "USDS Stablecoin" }, { symbol: "PYUSD", name: "PayPal USD" }, { symbol: "FDUSD", name: "First Digital USD" }, { symbol: "TUSD", name: "TrueUSD" }, { symbol: "USDE", name: "Ethena USDe" }, { symbol: "GUSD", name: "Gemini Dollar" }, { symbol: "EURC", name: "Euro Coin" }, { symbol: "USDP", name: "Pax Dollar" }, { symbol: "CRVUSD", name: "Curve USD" }, { symbol: "GHO", name: "GHO Stablecoin" }, { symbol: "LUSD", name: "Liquity USD" }, { symbol: "FRAX", name: "Frax" }, { symbol: "WETH", name: "Wrapped Ether" }, { symbol: "ETH", name: "Ether" }, { symbol: "CBETH", name: "Coinbase Wrapped Staked ETH" }, { symbol: "WSTETH", name: "Wrapped liquid staked Ether" }, { symbol: "RETH", name: "Rocket Pool ETH" }, { symbol: "WBTC", name: "Wrapped BTC" }, { symbol: "CBBTC", name: "Coinbase Wrapped BTC" }, { symbol: "TBTC", name: "tBTC" }, { symbol: "LBTC", name: "Lombard Staked BTC" }, { symbol: "AERO", name: "Aerodrome" }, { symbol: "USDBC", name: "USD Base Coin" }, { symbol: "DEGEN", name: "Degen" }, { symbol: "BRETT", name: "Brett" }, { symbol: "MORPHO", name: "Morpho Token" }, { symbol: "WELL", name: "Moonwell" }, { symbol: "PENDLE", name: "Pendle" }, { symbol: "LINK", name: "ChainLink Token" }, { symbol: "UNI", name: "Uniswap" }, { symbol: "AAVE", name: "Aave Token" }, { symbol: "COMP", name: "Compound" }, { symbol: "CRV", name: "Curve DAO Token" }, { symbol: "SNX", name: "Synthetix Network Token" }, { symbol: "MKR", name: "Maker" }, { symbol: "LDO", name: "Lido DAO Token" }, { symbol: "RPL", name: "Rocket Pool Protocol" }, { symbol: "GMX", name: "GMX" }, { symbol: "ARB", name: "Arbitrum" }, { symbol: "OP", name: "Optimism" }, { symbol: "MATIC", name: "Polygon" }, { symbol: "POL", name: "Polygon Ecosystem Token" }, { symbol: "AXLUSDC", name: "Axelar Wrapped USDC" }, { symbol: "PAXG", name: "PAX Gold" }, { symbol: "XAUT", name: "Tether Gold" }, { symbol: "RLUSD", name: "Ripple USD" }, { symbol: "USD1", name: "World Liberty USD" }, ]; /** * Canonical Base MAINNET deployments of known assets, matched STRICTLY by contract * address (never by symbol/name). A known ticker served from one of these exact * addresses is the genuine article, not an imposter; the same ticker anywhere else * still gets NAME_IMPOSTER + danger. Immutable at runtime by design: no env override, * no file — a compromised config must not be able to whitelist a scam. * Every entry was verified before adding; a wrong address here is worse than a missing * one. Two verification tiers, named in `source`: * - issuer-doc: confirmed against an official issuer-controlled source (strongest); * - registry+chain: the CoinGecko coin registry's platforms.base address AND an * on-chain symbol/name/decimals read at that address agree (two independent * sources). Candidates where either source was missing or disagreed were NOT added * (USDT, wstETH, USDS as of 2026-07-11). */ export interface CanonicalAsset extends KnownAsset { address: string; source: string; // what the address was verified against } export const CANONICAL_ASSETS_MAINNET: readonly CanonicalAsset[] = Object.freeze([ // issuer-doc tier { symbol: "USDC", name: "USD Coin", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", source: "developers.circle.com/stablecoins/usdc-contract-addresses" }, { symbol: "WETH", name: "Wrapped Ether", address: "0x4200000000000000000000000000000000000006", source: "docs.base.org/base-chain/network-information/base-contracts (WETH9 predeploy)" }, { symbol: "cbBTC", name: "Coinbase Wrapped BTC", address: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", source: "coinbase.com/blog/coinbase-wrapped-btc-cbbtc-is-now-live" }, { symbol: "EURC", name: "EURC", address: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42", source: "developers.circle.com/stablecoins/eurc-contract-addresses" }, { symbol: "DAI", name: "Dai Stablecoin", address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", source: "Superchain token list (ethereum-optimism.github.io data/DAI); bridged, no native issuer deployment" }, // registry+chain tier (added 2026-07-11 after the genuine-AERO-flagged-as-imposter report) { symbol: "AERO", name: "Aerodrome", address: "0x940181a94A35A4569E4529A3CDfB74e38FD98631", source: "registry+chain: coingecko coins/aerodrome-finance platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "cbETH", name: "Coinbase Wrapped Staked ETH", address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", source: "registry+chain: coingecko coins/coinbase-wrapped-staked-eth platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "rETH", name: "Rocket Pool ETH", address: "0xB6fe221Fe9EeF5aBa221c348bA20A1Bf5e73624c", source: "registry+chain: coingecko coins/rocket-pool-eth platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "tBTC", name: "Base tBTC v2", address: "0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", source: "registry+chain: coingecko coins/tbtc platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "LBTC", name: "Lombard Staked Bitcoin", address: "0xecAc9C5F704e954931349Da37F60E39f515c11c1", source: "registry+chain: coingecko coins/lombard-staked-btc platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "USDbC", name: "USD Base Coin", address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", source: "registry+chain: coingecko coins/bridged-usd-coin-base platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "DEGEN", name: "Degen", address: "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed", source: "registry+chain: coingecko coins/degen-base platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "BRETT", name: "Brett", address: "0x532f27101965dd16442E59d40670FaF5eBB142E4", source: "registry+chain: coingecko coins/based-brett platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "MORPHO", name: "Morpho Token", address: "0xBAa5CC21fd487B8Fcc2F632f3F4E8D37262a0842", source: "registry+chain: coingecko coins/morpho platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "WELL", name: "Moonwell", address: "0xA88594D404727625A9437C3f886C7643872296AE", source: "registry+chain: coingecko coins/moonwell-artemis platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "LINK", name: "ChainLink Token", address: "0x88Fb150BDc53A65fe94Dea0c9BA0a6dAf8C6e196", source: "registry+chain: coingecko coins/chainlink platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "crvUSD", name: "Curve.Fi USD Stablecoin", address: "0x417Ac0e078398C154EdFadD9Ef675d30Be60Af93", source: "registry+chain: coingecko coins/crvusd platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "GHO", name: "Gho Token", address: "0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee", source: "registry+chain: coingecko coins/gho platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "USDe", name: "USDe", address: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", source: "registry+chain: coingecko coins/ethena-usde platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "axlUSDC", name: "Axelar Wrapped USDC", address: "0xEB466342C4d449BC9f53A865D5Cb90586f405215", source: "registry+chain: coingecko coins/axlusdc platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "PENDLE", name: "Pendle", address: "0xA99F6e6785Da0F5d6fB42495Fe424BCE029Eeb3E", source: "registry+chain: coingecko coins/pendle platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "LUSD", name: "LUSD Stablecoin", address: "0x368181499736d0c0CC614DBB145E2EC1AC86b8c6", source: "registry+chain: coingecko coins/liquity-usd platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "COMP", name: "Compound", address: "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", source: "registry+chain: coingecko coins/compound-governance-token platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "CRV", name: "Curve DAO Token", address: "0x8Ee73c484A26e0A5df2Ee2a4960B789967dd0415", source: "registry+chain: coingecko coins/curve-dao-token platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, { symbol: "SNX", name: "Synthetix Network Token", address: "0x22e6966B799c4D5B13BE962E1D117b56327FDa66", source: "registry+chain: coingecko coins/havven platforms.base + on-chain symbol/name/decimals, 2026-07-11" }, ]); const CANONICAL_BY_ADDRESS = new Map(CANONICAL_ASSETS_MAINNET.map((a) => [a.address.toLowerCase(), a])); /** Canonical-asset lookup by exact address. Mainnet-only: these are Base mainnet deployments. */ export function canonicalAssetAt(address: string, network: string): CanonicalAsset | null { if (network !== "mainnet") return null; return CANONICAL_BY_ADDRESS.get(address.toLowerCase()) ?? null; } /** * Cyrillic/Greek homoglyphs that render identically to ASCII letters. Confusables * substitution is a primary token-impersonation technique (e.g. Cyrillic "С" in "USDС"), * and neither .toUpperCase() nor NFKC folds these to ASCII, so we map them explicitly. */ const CONFUSABLES: Record = { "А": "A", "В": "B", "С": "C", "Е": "E", "Н": "H", "К": "K", "М": "M", "О": "O", "Р": "P", "Т": "T", "Х": "X", "Ѕ": "S", "І": "I", "Ј": "J", "Α": "A", "Β": "B", "Ε": "E", "Η": "H", "Ι": "I", "Κ": "K", "Μ": "M", "Ν": "N", "Ο": "O", "Ρ": "P", "Τ": "T", "Χ": "X", "Υ": "Y", "Ζ": "Z", // Symbol/lunate/final-form variants that NFKC would collapse to a NON-ascii letter (must be // substituted BEFORE normalization): lunate sigmas -> C, final sigma, etc. "Ϲ": "C", "ϲ": "C", "ς": "S", "ϐ": "B", "ϖ": "P", }; /** True if the string contains any non-ASCII character. Legit tickers are ASCII. */ export function hasNonAscii(s: string): boolean { return /[^\x00-\x7F]/.test(s); } /** * Fold to a canonical ASCII-ish skeleton for comparison. Confusable substitution runs BEFORE NFKC * normalization, so glyphs like U+03F9 (lunate sigma, renders as C) map to C rather than being * collapsed by NFKC to a non-ASCII letter first. Table completeness is not safety-critical: the * analyzer additionally treats ANY non-ASCII near-collision as impersonation (see deriveFlags). */ export function foldSymbol(symbol: string): string { const raw = symbol.trim(); let sub = ""; for (const ch of raw) sub += CONFUSABLES[ch] ?? ch; return sub.normalize("NFKC").toUpperCase(); } /** Folded-symbol -> known asset lookup (homoglyph- and case-insensitive). */ const BY_SYMBOL = new Map(KNOWN_ASSETS.map((a) => [foldSymbol(a.symbol), a])); export function exactSymbolMatch(symbol: string): KnownAsset | null { return BY_SYMBOL.get(foldSymbol(symbol)) ?? null; } /** Classic Levenshtein edit distance. */ export function levenshtein(a: string, b: string): number { const m = a.length; const n = b.length; if (m === 0) return n; if (n === 0) return m; let prev = new Array(n + 1); let curr = new Array(n + 1); for (let j = 0; j <= n; j++) prev[j] = j; for (let i = 1; i <= m; i++) { curr[0] = i; for (let j = 1; j <= n; j++) { const cost = a[i - 1] === b[j - 1] ? 0 : 1; curr[j] = Math.min(prev[j]! + 1, curr[j - 1]! + 1, prev[j - 1]! + cost); } [prev, curr] = [curr, prev]; } return prev[n]!; } /** Nearest known symbol within `maxDistance` edits (excluding exact matches). */ export function nearestSymbol( symbol: string, maxDistance = 1, ): { asset: KnownAsset; distance: number } | null { const s = foldSymbol(symbol); if (s.length === 0) return null; let best: { asset: KnownAsset; distance: number } | null = null; for (const asset of KNOWN_ASSETS) { const d = levenshtein(s, foldSymbol(asset.symbol)); if (d === 0) continue; // exact handled separately if (d <= maxDistance && (best === null || d < best.distance)) { best = { asset, distance: d }; } } return best; }