From 079e271cc061d2950471e288435e1b36e60e0fd2 Mon Sep 17 00:00:00 2001 From: ZOMBIIIIIII <120676065+Metaton241@users.noreply.github.com> Date: Thu, 14 May 2026 21:40:36 +0300 Subject: [PATCH] initfmfijirfri --- apps/api/src/app.ts | 4 + apps/api/src/lib/token-registry.ts | 125 +++++++++++++++++++++------ apps/api/src/routes/tokens.routes.ts | 36 ++++++++ apps/api/swagger.json | 119 +++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 27 deletions(-) create mode 100644 apps/api/src/routes/tokens.routes.ts diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 5b7e88d..57da8f1 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -15,6 +15,7 @@ import relayProxyRoutes from './routes/relay-proxy.routes'; import tronProxyRoutes from './routes/tron-proxy.routes'; import btcProxyRoutes from './routes/btc-proxy.routes'; import pricesRoutes from './routes/prices.routes'; +import tokensRoutes from './routes/tokens.routes'; const app = express(); @@ -109,6 +110,9 @@ app.use('/api/btc', ...protect, mutateLimiter, btcProxyRoutes); // USD-цены (CoinGecko + KeyDB cache). GET-only, auth required, max 50 symbols. app.use('/api/prices', ...protect, mutateLimiter, pricesRoutes); +// Token registry — всех известных contracts/mints по всем chain'ам. GET-only, auth required. +app.use('/api/tokens', ...protect, mutateLimiter, tokensRoutes); + // 404 для всего что не сматчилось выше — единый JSON-ответ, не express default text app.use((_req, res) => { res.status(404).json({ success: false, error: 'Not found' }); diff --git a/apps/api/src/lib/token-registry.ts b/apps/api/src/lib/token-registry.ts index f50517e..ae0f684 100644 --- a/apps/api/src/lib/token-registry.ts +++ b/apps/api/src/lib/token-registry.ts @@ -10,6 +10,7 @@ import type { ChainCode } from './address-validators'; export interface EvmToken { symbol: string; + name: string; contractAddress: string; decimals: number; coingeckoId?: string; @@ -17,6 +18,7 @@ export interface EvmToken { export interface TrxToken { symbol: string; + name: string; contractAddress: string; // T...base58 decimals: number; coingeckoId?: string; @@ -24,11 +26,23 @@ export interface TrxToken { export interface SolToken { symbol: string; + name: string; mint: string; // SPL mint pubkey (base58) decimals: number; coingeckoId?: string; } +/** + * Flat shape для GET /api/tokens. + * Native coins имеют contract = null. + */ +export interface TokenListEntry { + chain: ChainCode; + symbol: string; + name: string; + contract: string | null; +} + /** * CoinGecko coin IDs для native монет каждой chain. * Используется в `price-oracle.service.ts` для USD-цен в `/balance`. @@ -41,45 +55,102 @@ export const NATIVE_COINGECKO_IDS: Record = { SOL: 'solana', }; +/** + * Native coin human names + tickers. На BSC ticker = "BNB" (не "BSC"). + * Используется в GET /api/tokens для native entries. + */ +export const NATIVE_NAMES: Record = { + BTC: 'Bitcoin', + ETH: 'Ethereum', + BSC: 'BNB', + TRX: 'Tron', + SOL: 'Solana', +}; + +export const NATIVE_SYMBOLS: Record = { + BTC: 'BTC', + ETH: 'ETH', + BSC: 'BNB', // ticker отличается от chain code + TRX: 'TRX', + SOL: 'SOL', +}; + 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' }, + { symbol: 'USDT', name: 'Tether USD', contractAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', decimals: 6, coingeckoId: 'tether' }, + { symbol: 'USDC', name: 'USD Coin', contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6, coingeckoId: 'usd-coin' }, + { symbol: 'DAI', name: 'Dai Stablecoin', contractAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', decimals: 18, coingeckoId: 'dai' }, + { symbol: 'WBTC', name: 'Wrapped Bitcoin', contractAddress: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', decimals: 8, coingeckoId: 'wrapped-bitcoin' }, + { symbol: 'LINK', name: 'Chainlink', contractAddress: '0x514910771AF9Ca656af840dff83E8264EcF986CA', decimals: 18, coingeckoId: 'chainlink' }, + { symbol: 'UNI', name: 'Uniswap', 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' }, + { symbol: 'USDT', name: 'Tether USD', contractAddress: '0x55d398326f99059fF775485246999027B3197955', decimals: 18, coingeckoId: 'tether' }, + { symbol: 'USDC', name: 'USD Coin', contractAddress: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', decimals: 18, coingeckoId: 'usd-coin' }, + { symbol: 'DOGE', name: 'Dogecoin', contractAddress: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', decimals: 8, coingeckoId: 'dogecoin' }, + { symbol: 'WBNB', name: 'Wrapped BNB', contractAddress: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', decimals: 18, coingeckoId: 'wbnb' }, + { symbol: 'BUSD', name: 'Binance USD', 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' }, + { symbol: 'USDT', name: 'Tether USD', contractAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', decimals: 6, coingeckoId: 'tether' }, + { symbol: 'USDC', name: 'USD Coin', 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' }, + { symbol: 'USDT', name: 'Tether USD', mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', decimals: 6, coingeckoId: 'tether' }, + { symbol: 'USDC', name: 'USD Coin', mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6, coingeckoId: 'usd-coin' }, + { symbol: 'PUMP', name: 'Pump.fun', mint: 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn', decimals: 6, coingeckoId: 'pump-fun' }, + { symbol: 'JUP', name: 'Jupiter', mint: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', decimals: 6, coingeckoId: 'jupiter-exchange-solana' }, + { symbol: 'WIF', name: 'dogwifhat', mint: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', decimals: 6, coingeckoId: 'dogwifcoin' }, + { symbol: 'POPCAT', name: 'Popcat', mint: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', decimals: 9, coingeckoId: 'popcat' }, + { symbol: 'TRUMP', name: 'Official Trump', mint: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', decimals: 6, coingeckoId: 'official-trump' }, + { symbol: 'PYTH', name: 'Pyth Network', mint: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', decimals: 6, coingeckoId: 'pyth-network' }, + { symbol: 'JTO', name: 'Jito', mint: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', decimals: 9, coingeckoId: 'jito-governance-token' }, + { symbol: 'W', name: 'Wormhole', mint: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', decimals: 6, coingeckoId: 'wormhole' }, + { symbol: 'BONK', name: 'Bonk', mint: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', decimals: 5, coingeckoId: 'bonk' }, + { symbol: 'ORCA', name: 'Orca', mint: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', decimals: 6, coingeckoId: 'orca' }, + { symbol: 'PENGU', name: 'Pudgy Penguins', mint: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', decimals: 6, coingeckoId: 'pudgy-penguins' }, + { symbol: 'RAY', name: 'Raydium', mint: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', decimals: 6, coingeckoId: 'raydium' }, ]; +const ALL_CHAINS_ORDERED: ChainCode[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL']; + +/** + * Возвращает flat-list всех известных активов: native + tokens, для всех (или одного) chain. + * Используется в GET /api/tokens. + */ +export function getAllTokens(filterChain?: ChainCode): TokenListEntry[] { + const out: TokenListEntry[] = []; + const chains: ChainCode[] = filterChain ? [filterChain] : ALL_CHAINS_ORDERED; + + for (const chain of chains) { + // Native first + out.push({ + chain, + symbol: NATIVE_SYMBOLS[chain], + name: NATIVE_NAMES[chain], + contract: null, + }); + // Tokens + if (chain === 'ETH' || chain === 'BSC') { + for (const tk of getEvmTokens(chain)) { + out.push({ chain, symbol: tk.symbol, name: tk.name, contract: tk.contractAddress }); + } + } else if (chain === 'TRX') { + for (const tk of TRX_TOKENS) { + out.push({ chain, symbol: tk.symbol, name: tk.name, contract: tk.contractAddress }); + } + } else if (chain === 'SOL') { + for (const tk of SOL_TOKENS) { + out.push({ chain, symbol: tk.symbol, name: tk.name, contract: tk.mint }); + } + } + // BTC — только native + } + return out; +} + export function getEvmTokens(chain: ChainCode): EvmToken[] { if (chain === 'ETH') return ETH_TOKENS; if (chain === 'BSC') return BSC_TOKENS; diff --git a/apps/api/src/routes/tokens.routes.ts b/apps/api/src/routes/tokens.routes.ts new file mode 100644 index 0000000..d10ced8 --- /dev/null +++ b/apps/api/src/routes/tokens.routes.ts @@ -0,0 +1,36 @@ +/** + * GET /api/tokens — реестр всех известных активов всех 5 сетей + native. + * + * Read-only. Источник — `lib/token-registry.ts`. Никаких RPC calls, + * никаких user-specific данных — только статический list контрактов с symbol + name. + * + * Optional query: ?chain=ETH|BSC|BTC|TRX|SOL — filter одной сетью. + */ + +import { Router, Request, Response } from 'express'; +import { getAllTokens } from '../lib/token-registry'; +import { ALL_CHAINS } from '../services/wallet-generator.service'; +import type { ChainCode } from '../lib/address-validators'; + +const router = Router(); +const ALLOWED = new Set(ALL_CHAINS); + +router.get('/', (req: Request, res: Response) => { + const chainParam = req.query.chain; + let filterChain: ChainCode | undefined; + if (chainParam !== undefined && chainParam !== null && chainParam !== '') { + const upper = String(chainParam).toUpperCase(); + if (!ALLOWED.has(upper as ChainCode)) { + res.status(400).json({ + success: false, + error: `Invalid chain "${chainParam}" (allowed: ETH, BSC, BTC, TRX, SOL)`, + }); + return; + } + filterChain = upper as ChainCode; + } + const data = getAllTokens(filterChain); + res.json({ success: true, data }); +}); + +export default router; diff --git a/apps/api/swagger.json b/apps/api/swagger.json index fbd42d2..fef37ff 100644 --- a/apps/api/swagger.json +++ b/apps/api/swagger.json @@ -896,6 +896,83 @@ } } } + }, + "TokenListEntry": { + "type": "object", + "required": [ + "chain", + "symbol", + "name", + "contract" + ], + "properties": { + "chain": { + "type": "string", + "enum": [ + "ETH", + "BSC", + "BTC", + "TRX", + "SOL" + ], + "example": "ETH" + }, + "symbol": { + "type": "string", + "example": "USDT" + }, + "name": { + "type": "string", + "example": "Tether USD" + }, + "contract": { + "type": "string", + "nullable": true, + "description": "Contract address (EVM 0x..., TRX T..., SOL base58 mint). Для native = null.", + "example": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + } + } + }, + "TokensListResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TokenListEntry" + }, + "example": [ + { + "chain": "ETH", + "symbol": "ETH", + "name": "Ethereum", + "contract": null + }, + { + "chain": "ETH", + "symbol": "USDT", + "name": "Tether USD", + "contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "chain": "BSC", + "symbol": "BNB", + "name": "BNB", + "contract": null + }, + { + "chain": "TRX", + "symbol": "USDT", + "name": "Tether USD", + "contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + } + ] + } + } } } }, @@ -2643,6 +2720,48 @@ } } } + }, + "/tokens": { + "get": { + "summary": "List all known token contracts across all chains", + "description": "Возвращает flat-list всех известных активов: native coins + tokens (ERC-20/BEP-20/TRC-20/SPL).\n\nИсточник — статический token-registry. Read-only, без RPC calls, без user-specific data.\n\nOptional ?chain=ETH|BSC|BTC|TRX|SOL — filter по одной сети.", + "tags": [ + "Tokens" + ], + "parameters": [ + { + "name": "chain", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ETH", + "BSC", + "BTC", + "TRX", + "SOL" + ] + }, + "description": "Если задан — вернёт только active assets этой сети (1 native + N tokens)." + } + ], + "responses": { + "200": { + "description": "Token list", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokensListResponse" + } + } + } + }, + "400": { + "description": "Invalid chain parameter" + } + } + } } } }