162 lines
7.4 KiB
TypeScript
162 lines
7.4 KiB
TypeScript
/**
|
||
* Реестр известных токенов 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<ChainCode, string> = {
|
||
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;
|
||
}
|