/** * Реестр известных токенов per-chain. Используется в getBalance для * параллельного запроса баланса всех токенов адреса. * * Адреса контрактов / mint'ы — из публичных on-chain данных и используются * также в swap-proxy роутах (BSC TOKEN_MAP, SOL ALLOWED_MINTS). */ import type { ChainCode } from './address-validators'; export interface EvmToken { symbol: string; contractAddress: string; decimals: number; coingeckoId?: string; } export interface TrxToken { symbol: string; contractAddress: string; // T...base58 decimals: number; coingeckoId?: string; } export interface SolToken { symbol: string; mint: string; // SPL mint pubkey (base58) decimals: number; coingeckoId?: string; } /** * CoinGecko coin IDs для native монет каждой chain. * Используется в `price-oracle.service.ts` для USD-цен в `/balance`. */ export const NATIVE_COINGECKO_IDS: Record = { BTC: 'bitcoin', ETH: 'ethereum', BSC: 'binancecoin', TRX: 'tron', SOL: 'solana', }; export const ETH_TOKENS: EvmToken[] = [ { symbol: 'USDT', contractAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', decimals: 6, coingeckoId: 'tether' }, { symbol: 'USDC', contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6, coingeckoId: 'usd-coin' }, { symbol: 'DAI', contractAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', decimals: 18, coingeckoId: 'dai' }, { symbol: 'WBTC', contractAddress: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', decimals: 8, coingeckoId: 'wrapped-bitcoin' }, { symbol: 'LINK', contractAddress: '0x514910771AF9Ca656af840dff83E8264EcF986CA', decimals: 18, coingeckoId: 'chainlink' }, { symbol: 'UNI', contractAddress: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', decimals: 18, coingeckoId: 'uniswap' }, ]; export const BSC_TOKENS: EvmToken[] = [ { symbol: 'USDT', contractAddress: '0x55d398326f99059fF775485246999027B3197955', decimals: 18, coingeckoId: 'tether' }, { symbol: 'USDC', contractAddress: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', decimals: 18, coingeckoId: 'usd-coin' }, { symbol: 'DOGE', contractAddress: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', decimals: 8, coingeckoId: 'dogecoin' }, { symbol: 'WBNB', contractAddress: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, coingeckoId: 'wbnb' }, { symbol: 'BUSD', contractAddress: '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', decimals: 18, coingeckoId: 'binance-usd' }, ]; export const TRX_TOKENS: TrxToken[] = [ { symbol: 'USDT', contractAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', decimals: 6, coingeckoId: 'tether' }, { symbol: 'USDC', contractAddress: 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8', decimals: 6, coingeckoId: 'usd-coin' }, ]; export const SOL_TOKENS: SolToken[] = [ { symbol: 'USDT', mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', decimals: 6, coingeckoId: 'tether' }, { symbol: 'USDC', mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6, coingeckoId: 'usd-coin' }, { symbol: 'PUMP', mint: 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn', decimals: 6, coingeckoId: 'pump-fun' }, { symbol: 'JUP', mint: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', decimals: 6, coingeckoId: 'jupiter-exchange-solana' }, { symbol: 'WIF', mint: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', decimals: 6, coingeckoId: 'dogwifcoin' }, { symbol: 'POPCAT', mint: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', decimals: 9, coingeckoId: 'popcat' }, { symbol: 'TRUMP', mint: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', decimals: 6, coingeckoId: 'official-trump' }, { symbol: 'PYTH', mint: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', decimals: 6, coingeckoId: 'pyth-network' }, { symbol: 'JTO', mint: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', decimals: 9, coingeckoId: 'jito-governance-token' }, { symbol: 'W', mint: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', decimals: 6, coingeckoId: 'wormhole' }, { symbol: 'BONK', mint: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', decimals: 5, coingeckoId: 'bonk' }, { symbol: 'ORCA', mint: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', decimals: 6, coingeckoId: 'orca' }, { symbol: 'PENGU', mint: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', decimals: 6, coingeckoId: 'pudgy-penguins' }, { symbol: 'RAY', mint: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', decimals: 6, coingeckoId: 'raydium' }, ]; export function getEvmTokens(chain: ChainCode): EvmToken[] { if (chain === 'ETH') return ETH_TOKENS; if (chain === 'BSC') return BSC_TOKENS; return []; } export function getTrxTokens(): TrxToken[] { return TRX_TOKENS; } export function getSolTokens(): SolToken[] { return SOL_TOKENS; } /** * Universal lookup для send flow. Returns address+decimals или null если token не в registry. * Symbol comparison case-insensitive. * * Usage: * const info = getTokenInfo('BSC', 'USDC'); * // → { address: '0x8AC76a51...', decimals: 18 } * * const info = getTokenInfo('SOL', 'USDT'); * // → { address: 'Es9vMFrza...', decimals: 6 } (mint address) */ export function getTokenInfo(chain: ChainCode, symbol: string): { address: string; decimals: number } | null { const upper = String(symbol).toUpperCase(); if (chain === 'ETH' || chain === 'BSC') { const t = getEvmTokens(chain).find((x) => x.symbol.toUpperCase() === upper); return t ? { address: t.contractAddress, decimals: t.decimals } : null; } if (chain === 'TRX') { const t = TRX_TOKENS.find((x) => x.symbol.toUpperCase() === upper); return t ? { address: t.contractAddress, decimals: t.decimals } : null; } if (chain === 'SOL') { const t = SOL_TOKENS.find((x) => x.symbol.toUpperCase() === upper); return t ? { address: t.mint, decimals: t.decimals } : null; } return null; } /** * Resolves the CoinGecko coin id for a given (chain, symbol) pair. * * Если `symbol` совпадает с самим именем chain (BTC/ETH/BSC/TRX/SOL) — возвращает * native id (`NATIVE_COINGECKO_IDS[chain]`). * В остальных случаях ищет токен в реестре сети и возвращает его `coingeckoId`. * * Возвращает `null` если: * - chain неизвестен; * - symbol не найден в реестре сети; * - токен найден, но `coingeckoId` для него не задан. * * Используется исключительно как whitelist для price oracle (см. S1 в плане): * никакой свободный user-input не попадает в CoinGecko URL. */ export function getCoingeckoId(chain: ChainCode, symbol: string): string | null { if (!chain) return null; const upper = String(symbol || '').toUpperCase(); if (!upper) return null; // Native — symbol === chain code (BTC, ETH, ...). if (upper === chain) return NATIVE_COINGECKO_IDS[chain] ?? null; if (chain === 'ETH' || chain === 'BSC') { const t = getEvmTokens(chain).find((x) => x.symbol.toUpperCase() === upper); return t?.coingeckoId ?? null; } if (chain === 'TRX') { const t = TRX_TOKENS.find((x) => x.symbol.toUpperCase() === upper); return t?.coingeckoId ?? null; } if (chain === 'SOL') { const t = SOL_TOKENS.find((x) => x.symbol.toUpperCase() === upper); return t?.coingeckoId ?? null; } return null; }