{ "openapi": "3.0.0", "info": { "title": "CryptoWallet API", "version": "5.0.0", "description": "Multi-chain custodial wallet API (ETH/BSC/BTC/TRX/SOL). Сервер генерит mnemonic, шифрует AES-256-GCM (master-key из HashiCorp Vault), хранит её и сам подписывает транзакции. Auth via JWT (cookie/Bearer), issued by external auth-service (BITOK)." }, "servers": [ { "url": "/api", "description": "API root" } ], "tags": [ { "name": "System", "description": "Health & service info" }, { "name": "Wallets", "description": "Custodial wallet lifecycle" }, { "name": "Wallet Ops", "description": "Per-chain balance / transactions / send" }, { "name": "BTC", "description": "Bitcoin RPC proxy (Blockstream)" }, { "name": "TRON", "description": "TRON RPC proxy (TronGrid)" }, { "name": "Solana", "description": "Solana swap proxy (Jupiter)" }, { "name": "TRON Swap", "description": "TRON swap proxy (SunSwap + FeeSwapRouter)" }, { "name": "BSC", "description": "BSC swap proxy (PancakeSwap V2)" }, { "name": "Relay", "description": "Cross-chain bridges (Relay Protocol)" }, { "name": "Prices", "description": "USD-цены (CoinGecko + KeyDB cache 5 мин)" }, { "name": "Staking", "description": "Custodial-стейкинг (ETH → Lido, SOL → native StakeProgram). Каждое сырое amount-поле (…Wei/…Lamports) имеет спутник …Human с человекочитаемым числом." }, { "name": "Liquidity", "description": "Uniswap v3 LP — предоставление ликвидности (ETH). Каждое сырое amount-поле имеет спутник …Human (по decimals токена)." } ], "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }, "cookieAuth": { "type": "apiKey", "in": "cookie", "name": "access_token" } }, "schemas": { "Error": { "type": "object", "properties": { "success": { "type": "boolean", "example": false }, "error": { "type": "string" } } }, "HealthResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "status": { "type": "string", "example": "ok" } } } } }, "Chain": { "type": "string", "enum": [ "ETH", "BTC", "SOL", "TRX", "BSC" ] }, "Wallet": { "type": "object", "properties": { "chain": { "$ref": "#/components/schemas/Chain" }, "address": { "type": "string" }, "derivationPath": { "type": "string", "description": "BIP32 path" } } }, "WalletsResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Wallet" } } } }, "MnemonicResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "mnemonic": { "type": "string", "description": "BIP39 mnemonic (12 words)" } } } } }, "TxBroadcastResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "txid": { "type": "string", "description": "Идентификатор отправленной транзакции" }, "chain": { "$ref": "#/components/schemas/Chain" } } } } }, "FormattedAmount": { "type": "object", "description": "Сумма с метаданными формата + USD-цена. Поля `usdPrice`/`usdValue` всегда присутствуют, но могут быть `null` если symbol не в registry или upstream price oracle (CoinGecko) недоступен.", "required": [ "raw", "formatted", "decimals", "usdPrice", "usdValue" ], "properties": { "raw": { "type": "string", "description": "Smallest units (wei/sat/sun/lamports), string-encoded BigInt", "example": "1500000000000000000" }, "formatted": { "type": "string", "description": "Human-readable decimal", "example": "1.5" }, "decimals": { "type": "integer", "description": "Decimals of the chain/token", "example": 18 }, "usdPrice": { "type": "number", "nullable": true, "description": "Цена 1 целой единицы в USD по данным CoinGecko (cache 5 мин, KeyDB). `null` если symbol не в registry или upstream недоступен.", "example": 3210.45 }, "usdValue": { "type": "number", "nullable": true, "description": "Совокупная стоимость holding'а в USD = `Number(formatted) × usdPrice`, округлено до 8 знаков. `null` если `usdPrice === null` или результат не finite.", "example": 4815.675 } } }, "PricesResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "description": "Map symbol → { usd: price | null }. `null` если symbol whitelist'ed но upstream не вернул котировку.", "additionalProperties": { "type": "object", "properties": { "usd": { "type": "number", "nullable": true, "example": 67432.12 } } }, "example": { "BTC": { "usd": 67432.12 }, "ETH": { "usd": 3210.45 }, "USDT": { "usd": 1 } } } } }, "BalanceResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "chain": { "$ref": "#/components/schemas/Chain" }, "address": { "type": "string" }, "native": { "$ref": "#/components/schemas/FormattedAmount" }, "tokens": { "type": "object", "description": "Map symbol → FormattedAmount. Содержит все известные токены chain'а (ETH: USDT/USDC/DAI/WBTC/LINK/UNI, BSC: USDT/USDC/DOGE/WBNB/BUSD, TRX: USDT/USDC, SOL: 14 токенов)", "additionalProperties": { "$ref": "#/components/schemas/FormattedAmount" } } } } } }, "ChainPortfolio": { "type": "object", "description": "Балансе одной сети в составе portfolio. Расширяет BalanceResponse.data полями totalUsd, stale, lastUpdated, error.", "properties": { "chain": { "$ref": "#/components/schemas/Chain" }, "address": { "type": "string" }, "totalUsd": { "type": "number", "nullable": true, "description": "Сумма usdValue по native + всем токенам chain'а. null если все цены недоступны." }, "native": { "$ref": "#/components/schemas/FormattedAmount" }, "tokens": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/FormattedAmount" } }, "stale": { "type": "boolean", "description": "true = данные из KeyDB cache (RPC chain'а упал в этом запросе)" }, "lastUpdated": { "type": "integer", "description": "Unix ms когда данные были обновлены fresh fetch'ем" }, "error": { "type": "string", "nullable": true, "description": "Причина почему stale (только если stale=true)" } } }, "PortfolioResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "required": [ "totalUsd", "hasErrors", "perChain" ], "properties": { "totalUsd": { "type": "number", "description": "Grand sum USD по всем сетям (rounded к 8 знакам). 0 если все сети упали и нет cache." }, "hasErrors": { "type": "boolean", "description": "true если хотя бы одна сеть в stale/error состоянии" }, "perChain": { "type": "object", "description": "Per-chain breakdown. Ключ = chain code (ETH/BSC/BTC/TRX/SOL). Значение null если ни fresh, ни cache недоступны.", "properties": { "ETH": { "$ref": "#/components/schemas/ChainPortfolio", "nullable": true }, "BSC": { "$ref": "#/components/schemas/ChainPortfolio", "nullable": true }, "BTC": { "$ref": "#/components/schemas/ChainPortfolio", "nullable": true }, "TRX": { "$ref": "#/components/schemas/ChainPortfolio", "nullable": true }, "SOL": { "$ref": "#/components/schemas/ChainPortfolio", "nullable": true } } } } } } }, "Transaction": { "type": "object", "properties": { "txid": { "type": "string" }, "timestamp": { "type": "integer", "nullable": true, "description": "Unix seconds" }, "direction": { "type": "string", "enum": [ "in", "out", "self" ] }, "amount": { "type": "string", "nullable": true }, "token": { "type": "string", "nullable": true }, "from": { "type": "string", "nullable": true }, "to": { "type": "string", "nullable": true } } }, "TransactionsResponse": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Transaction" } } } }, "SendRequest": { "type": "object", "required": [ "to" ], "properties": { "to": { "type": "string", "description": "Recipient address" }, "amount": { "type": "string", "description": "Amount в smallest units (wei для EVM, lamports для SOL, sat для BTC, sun для TRX)" }, "token": { "type": "string", "nullable": true, "description": "USDT для TRC20/ERC20/BEP20. Без token = native." }, "feeTier": { "type": "string", "enum": [ "slow", "normal", "fast" ], "nullable": true, "description": "Default 'normal'. ETH/BSC: eth_feeHistory p25/p50/p75 priority. BTC: blockstream targets 144/6/1 блок. TRX/SOL: игнорится." }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } }, "FeeQuote": { "type": "object", "properties": { "maxFeePerGas": { "type": "string", "description": "wei (decimal string)" }, "maxPriorityFeePerGas": { "type": "string", "description": "wei (decimal string)" }, "gweiTotal": { "type": "number" }, "gweiPriority": { "type": "number" } } }, "FeeTiers": { "type": "object", "properties": { "chain": { "type": "string", "enum": [ "ETH", "BSC" ] }, "baseFeeGwei": { "type": "number", "description": "Из feeHistory.baseFeePerGas (на BSC ~0)" }, "slow": { "$ref": "#/components/schemas/FeeQuote" }, "normal": { "$ref": "#/components/schemas/FeeQuote" }, "fast": { "$ref": "#/components/schemas/FeeQuote" } } }, "SignRawEvmTxRequest": { "type": "object", "required": [ "to", "data", "value", "chainId", "gas", "maxFeePerGas", "maxPriorityFeePerGas" ], "properties": { "to": { "type": "string", "description": "0x-prefixed 40-hex (контракт или EOA)" }, "data": { "type": "string", "description": "Calldata 0x-hex (может быть пустым 0x для native send)" }, "value": { "type": "string", "description": "wei (decimal string)" }, "chainId": { "type": "integer", "description": "1 (ETH) или 56 (BSC) — должен совпадать с path :chain" }, "gas": { "type": "string", "description": "gasLimit в decimal" }, "maxFeePerGas": { "type": "string", "description": "wei" }, "maxPriorityFeePerGas": { "type": "string", "description": "wei" }, "feeTier": { "type": "string", "enum": [ "slow", "normal", "fast" ], "nullable": true, "description": "Если задан → server переопределит maxFeePerGas/maxPriorityFeePerGas актуальным из eth_feeHistory (полезно если quote от Relay устарел)." }, "bridgeAmount": { "type": "string", "description": "BSC only optional. If set + chain=BSC, server sends 0.7% of this amount to 0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718 (BSC_FEE_WALLET) before main tx.", "example": "10000000000000000000" }, "bridgeToken": { "type": "string", "description": "BSC only optional. BEP-20 contract address. Empty = native BNB. Used with bridgeAmount.", "example": "0x55d398326f99059fF775485246999027B3197955" } } }, "SwapQuoteResponse": { "type": "object", "required": [ "quoteId", "expiresIn", "expiresAt", "chain", "amountIn", "amountInFormatted", "expectedOut", "expectedOutFormatted", "minOut", "minOutFormatted", "slippageBps", "fees", "route", "approveRequired" ], "properties": { "quoteId": { "type": "string", "example": "q_01KRKD8GA4XZJ5W4E7VFT2N9M3", "description": "Opaque ULID. Pass to POST /:chain/swap для execute." }, "expiresIn": { "type": "integer", "example": 30, "description": "Seconds until cache eviction" }, "expiresAt": { "type": "integer", "format": "int64", "example": 1715600030000, "description": "Unix ms when quote expires" }, "chain": { "type": "string", "enum": [ "BSC", "TRX", "SOL" ] }, "amountIn": { "type": "string", "example": "100000000000000000", "description": "Smallest units" }, "amountInFormatted": { "type": "string", "example": "0.1" }, "amountInUsd": { "type": "number", "nullable": true, "example": 0.1 }, "expectedOut": { "type": "string", "example": "164821000000000", "description": "Mid-market quote (smallest units)" }, "expectedOutFormatted": { "type": "string", "example": "0.000164821" }, "expectedOutUsd": { "type": "number", "nullable": true, "example": 0.0991 }, "minOut": { "type": "string", "example": "163996895000000", "description": "expectedOut × (10000-slippageBps) / 10000" }, "minOutFormatted": { "type": "string", "example": "0.000163996" }, "slippageBps": { "type": "integer", "example": 50, "description": "Slippage в basis points (50 = 0.5%)" }, "priceImpactPct": { "type": "string", "nullable": true, "example": "0.06", "description": "SOL only (Jupiter exposes это поле)" }, "fees": { "type": "object", "properties": { "network": { "type": "object", "properties": { "asset": { "type": "string", "example": "BNB" }, "amount": { "type": "string", "example": "65000000000000" }, "amountFormatted": { "type": "string", "example": "0.000065", "description": "Сетевая комиссия в native-монете" }, "amountUsd": { "type": "number", "nullable": true, "example": 0.04 }, "amountInInputToken": { "type": "string", "nullable": true, "example": "0.04", "description": "Та же сетевая комиссия в эквиваленте валюты свапа (входного токена)" }, "inputTokenSymbol": { "type": "string", "example": "USDT", "description": "Символ входного токена (валюты свапа)" } } }, "total": { "type": "object", "properties": { "amountUsd": { "type": "number", "nullable": true, "example": 0.04 } } }, "app": { "type": "object", "nullable": true, "description": "Наша комиссия 0.7%. Списывается отдельной tx ПЕРЕД свапом.", "properties": { "asset": { "type": "string", "example": "USDT", "description": "Токен комиссии (= входной токен)" }, "amount": { "type": "string", "example": "70000000000000" }, "amountFormatted": { "type": "string", "example": "0.07", "description": "Комиссия 0.7% в токене" }, "amountUsd": { "type": "number", "nullable": true, "example": 0.07, "description": "Та же 0.7% в USDT-эквиваленте" }, "recipient": { "type": "string", "example": "0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68" } } } } }, "route": { "type": "array", "items": { "type": "string" }, "example": [ "USDT", "BNB" ], "description": "Symbol path (для BSC/TRX) или DEX labels (для SOL Jupiter)." }, "approveRequired": { "type": "boolean", "example": true, "description": "BSC: token-to-anything требует approve(amount). TRX: USDT→TRX requires approve(infinite)." }, "estimatedGasUnits": { "type": "string", "nullable": true, "example": "300000", "description": "EVM gas units (BSC). Null для TRX/SOL." } } }, "SendCostEstimateResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "chain": { "type": "string", "example": "BSC" }, "fee": { "type": "object", "properties": { "asset": { "type": "string", "example": "BNB" }, "amount": { "type": "string", "example": "65000000000000" }, "amountFormatted": { "type": "string", "example": "0.000065" }, "amountUsd": { "type": "number", "nullable": true, "example": 0.04 } } }, "total": { "type": "object", "properties": { "amountUsd": { "type": "number", "nullable": true, "example": 0.04 } } }, "breakdown": { "type": "object", "additionalProperties": true } } } } }, "SwapCostEstimateResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "chain": { "type": "string", "example": "BSC" }, "fee": { "type": "object", "description": "Сетевая (gas) комиссия", "properties": { "asset": { "type": "string", "example": "BNB" }, "amount": { "type": "string" }, "amountFormatted": { "type": "string", "description": "в native-монете" }, "amountUsd": { "type": "number", "nullable": true }, "amountInInputToken": { "type": "string", "nullable": true, "description": "та же сетевая комиссия в эквиваленте валюты свапа (входного токена)" }, "inputTokenSymbol": { "type": "string", "nullable": true, "example": "USDT" } } }, "appFee": { "type": "object", "nullable": true, "description": "Наша комиссия 0.7% в токене + USDT", "properties": { "asset": { "type": "string", "example": "USDT" }, "amount": { "type": "string" }, "amountFormatted": { "type": "string", "description": "0.7% в токене" }, "amountUsd": { "type": "number", "nullable": true, "description": "та же 0.7% в USDT" } } }, "total": { "type": "object", "properties": { "amountUsd": { "type": "number", "nullable": true } } }, "route": { "type": "array", "items": { "type": "string" }, "example": [ "USDT", "BNB" ] }, "approveRequired": { "type": "boolean" }, "estimatedGasUnits": { "type": "string", "nullable": true }, "slippageBps": { "type": "integer" } } } } }, "BridgeCostEstimateResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "fees": { "type": "object", "properties": { "gas": { "type": "object", "nullable": true, "additionalProperties": true }, "relayer": { "type": "object", "nullable": true, "additionalProperties": true }, "app": { "type": "object", "nullable": true, "additionalProperties": true }, "total": { "type": "object", "properties": { "amountUsd": { "type": "number", "nullable": true } } } } }, "appCommission": { "type": "object", "nullable": true, "description": "Наша app-комиссия 0.7% (в токене + USDT). Информативно; собирается отдельной /app-fee tx. Считается из details.currencyIn. Есть и в /relay/quote.", "properties": { "ratePercent": { "type": "number", "example": 0.7 }, "inToken": { "type": "object", "properties": { "symbol": { "type": "string", "nullable": true, "example": "USDT" }, "amount": { "type": "string", "nullable": true, "example": "0.7", "description": "0.7% во входном токене" } } }, "usd": { "type": "number", "nullable": true, "example": 0.7, "description": "та же 0.7% в USDT (≈USD)" } } }, "rate": { "type": "string", "nullable": true }, "priceImpactPct": { "type": "string", "nullable": true }, "priceImpactUsd": { "type": "number", "nullable": true }, "timeEstimate": { "type": "integer", "nullable": true, "description": "Estimate в секундах" }, "currencyIn": { "type": "object", "nullable": true, "additionalProperties": true }, "currencyOut": { "type": "object", "nullable": true, "additionalProperties": true } } } } }, "TokenListEntry": { "type": "object", "required": [ "chain", "symbol", "name", "contract", "decimals" ], "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" }, "decimals": { "type": "integer", "minimum": 0, "maximum": 36, "description": "Decimal places for smallest-unit conversion (BTC=8, ETH/BSC native=18, TRX=6, SOL=9, USDC/USDT depend on chain).", "example": 6 } } }, "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, "decimals": 18 }, { "chain": "ETH", "symbol": "USDT", "name": "Tether USD", "contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "decimals": 6 }, { "chain": "BSC", "symbol": "BNB", "name": "BNB", "contract": null, "decimals": 18 }, { "chain": "TRX", "symbol": "USDT", "name": "Tether USD", "contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "decimals": 6 } ] } } }, "PriceDynamicsResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "additionalProperties": { "type": "object", "properties": { "usd": { "type": "number", "nullable": true, "example": 67432.12 }, "change24h": { "type": "number", "nullable": true, "example": -1.38, "description": "Rolling 24h % change. Negative = падение, positive = рост." } } }, "example": { "BTC": { "usd": 67432.12, "change24h": -1.38 }, "ETH": { "usd": 3210.45, "change24h": 0.06 }, "BNB": { "usd": 657.23, "change24h": 1.2 }, "SOL": { "usd": 145.8, "change24h": -0.45 }, "TRX": { "usd": 0.108, "change24h": 0.12 } } } } } } }, "security": [ { "cookieAuth": [] }, { "bearerAuth": [] } ], "paths": { "/wallets/{chain}/stake/quote": { "post": { "summary": "Stake preview (Lido ETH): ожидаемый stETH, app-fee, APR", "description": "Превью перед стейком. Считает app-fee 0.7%, сумму после fee, ожидаемый stETH (~1:1) и текущий Lido APR (public API, кэш 10 мин; null если недоступен). Ничего не подписывает. Только ETH.", "tags": [ "Staking" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "ETH", "SOL" ] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "amount" ], "properties": { "amount": { "type": "string", "description": "Сумма в wei (строка целых цифр)", "example": "100000000000000000" } } } } } }, "responses": { "200": { "description": "Quote", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "amountWei": { "type": "string" }, "amountWeiHuman": { "type": "string", "description": "human-форма (18 dec), напр. 0.1" }, "appFeeWei": { "type": "string" }, "appFeeWeiHuman": { "type": "string", "description": "наша 0.7% в токене (ETH). SOL-ответ: appFeeLamportsHuman" }, "appFeeUsd": { "type": "number", "nullable": true, "description": "наша 0.7% в USDT (≈USD). Есть и в SOL-ответе (appFeeUsd). null если цена недоступна" }, "stakeAmountWei": { "type": "string" }, "stakeAmountWeiHuman": { "type": "string" }, "expectedStEthWei": { "type": "string" }, "expectedStEthWeiHuman": { "type": "string" }, "aprPercent": { "type": "number", "nullable": true } } } } } } } }, "400": { "description": "Invalid amount / unsupported chain" } } } }, "/wallets/{chain}/stake": { "post": { "summary": "Custodial stake (ETH → Lido, SOL → native StakeProgram): один вызов", "description": "Сервер отнимает app-fee 0.7%, затем стейкает: ETH → Lido submit(referral) payable (amount в wei); SOL → создаёт stake-аккаунт + delegate на валидатора (amount в lamports). Возвращает txid'ы (для SOL — ещё stakeAccount). JWT-bind: адрес = кошелёк юзера для chain. Поддерживает header Idempotency-Key (анти-дабл-спенд). Chain: ETH или SOL.", "tags": [ "Staking" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "ETH", "SOL" ] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Опциональный ключ идемпотентности (анти-дабл-спенд на retry)" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "amount" ], "properties": { "amount": { "type": "string", "description": "Сумма в wei (строка целых цифр)", "example": "100000000000000000" } } } } } }, "responses": { "200": { "description": "Staked", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "feeTxid": { "type": "string" }, "stakeTxid": { "type": "string" }, "stakeAmountWei": { "type": "string" }, "appFeeWei": { "type": "string" } } } } } } } }, "400": { "description": "Invalid input / amount too small" }, "403": { "description": "No ETH wallet for user" }, "404": { "description": "Mnemonic not found" }, "409": { "description": "Idempotency conflict" }, "502": { "description": "Broadcast failed (insufficient balance / RPC error)" } } } }, "/wallets/{chain}/stake/positions": { "get": { "summary": "Stake positions (Lido ETH): stETH-баланс + APR", "description": "Читает live из чейна: stETH balanceOf(адрес юзера) = текущая позиция, плюс Lido APR. Без записи в БД. Только ETH.", "tags": [ "Staking" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "ETH", "SOL" ] } } ], "responses": { "200": { "description": "Positions", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "protocol": { "type": "string" }, "chain": { "type": "string" }, "stEthBalanceWei": { "type": "string" }, "stEthBalanceWeiHuman": { "type": "string" }, "aprPercent": { "type": "number", "nullable": true }, "withdrawalRequests": { "type": "array", "items": { "type": "object", "properties": { "requestId": { "type": "string" }, "amountStEthWei": { "type": "string" }, "amountStEthWeiHuman": { "type": "string" }, "isFinalized": { "type": "boolean" }, "isClaimed": { "type": "boolean" } } } } } } } } } } }, "403": { "description": "No ETH wallet for user" } } } }, "/wallets/{chain}/unstake": { "post": { "summary": "Custodial unstake (ETH Lido / SOL native)", "description": "Вывод БЕЗ комиссии. ETH: тело {amount, mode}. mode=\"swap\" (по умолч.) — мгновенно stETH→ETH через Curve, один вызов (slippage сервер ставит автоматически ~0.3%); mode=\"queue\" — Lido Withdrawal Queue (request → потом /unstake/claim). SOL: тело {stakeAccount} — если active → deactivate; если inactive → withdraw (между ними cooldown сети 1-2 эпохи). JWT-bind + Idempotency-Key.", "tags": ["Staking"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH", "SOL"] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "amount": { "type": "string", "description": "ETH: stETH в wei", "example": "100000000000000000" }, "mode": { "type": "string", "enum": ["swap", "queue"], "description": "ETH only; default swap" }, "stakeAccount": { "type": "string", "description": "SOL only: адрес stake-аккаунта" } } } } } }, "responses": { "200": { "description": "Unstake submitted", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object" } } } } } }, "400": { "description": "Invalid input" }, "403": { "description": "No wallet for user" }, "502": { "description": "Broadcast failed" } } } }, "/wallets/{chain}/unstake/claim": { "post": { "summary": "Claim Lido queue withdrawal (ETH only)", "description": "Клеймит финализированный withdrawal request (mode=queue). requestId берётся из GET /wallets/ETH/stake/positions (поле withdrawalRequests[].requestId, когда isFinalized=true).", "tags": ["Staking"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH"] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["requestId"], "properties": { "requestId": { "type": "string", "example": "12345" } } } } } }, "responses": { "200": { "description": "Claimed", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object" } } } } } }, "400": { "description": "Invalid / not finalized" }, "502": { "description": "Broadcast failed" } } } }, "/wallets/{chain}/lp/pools": { "get": { "summary": "Uniswap v3 LP: куриные пулы (TVL, цена, APR)", "description": "Список курируемых пулов: token0/token1 + символы + decimals, fee-tier, текущая цена (token1 per token0, human), текущий tick, TVL/volume/APR из subgraph (null если недоступен). Цену отсюда используй для задания диапазона в /lp/quote.", "tags": ["Liquidity"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH"] } } ], "responses": { "200": { "description": "Pools", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "array", "items": { "type": "object" } } } } } } }, "403": { "description": "No ETH wallet" } } } }, "/wallets/{chain}/lp/quote": { "post": { "summary": "LP preview: тики + amount0/amount1 + APR + наша комиссия 0.7% (native+USD) + проверка баланса", "description": "По [priceLower, priceUpper] и amount0Desired/amount1Desired считает тики, фактические суммы, slippage-мины, APR. ДОБАВЛЕНО: нашу app-комиссию 0.7% по каждой ноге (в токене + USD + ETH-эквивалент, всё в обычных числах) и проверку баланса (хватает ли token0/token1 на взнос И ETH на газ — с учётом useNativeEth для WETH-ноги). Баланс считается по кошельку юзера (JWT). Ничего не подписывает.", "tags": ["Liquidity"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH"] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["pool", "priceLower", "priceUpper", "amount0Desired", "amount1Desired"], "properties": { "pool": { "type": "string", "description": "адрес куриного пула" }, "priceLower": { "type": "number" }, "priceUpper": { "type": "number" }, "amount0Desired": { "type": "string", "description": "smallest units token0" }, "amount1Desired": { "type": "string", "description": "smallest units token1" }, "slippageBps": { "type": "integer", "description": "0..5000 (default 100)" }, "useNativeEth": { "type": "boolean", "description": "WETH-нога покрывается нативным ETH (авто-wrap) — влияет на проверку баланса (проверяет ETH вместо WETH)" } } } } } }, "responses": { "200": { "description": "Quote (+ комиссия + баланс)", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "poolAddress": { "type": "string" }, "tickLower": { "type": "integer" }, "tickUpper": { "type": "integer" }, "amount0DesiredHuman": { "type": "string" }, "amount1DesiredHuman": { "type": "string" }, "liquidity": { "type": "string" }, "aprPercent": { "type": "number", "nullable": true }, "appFee0Human": { "type": "string", "description": "0.7% от token0-ноги (в токене, обычное число)" }, "appFee1Human": { "type": "string", "description": "0.7% от token1-ноги" }, "appFee0Usd": { "type": "number", "nullable": true }, "appFee1Usd": { "type": "number", "nullable": true }, "appFeeTotalUsd": { "type": "number", "nullable": true }, "appFeeTotalEth": { "type": "string", "nullable": true, "description": "суммарная наша 0.7% в монете сети (ETH)" }, "networkFee": { "type": "object", "description": "Оценка сетевой (gas) комиссии всего LP-депозита (wrap?+fee×ноги+approve×ноги+mint) при текущем gasPrice", "properties": { "estGasUnits": { "type": "string", "example": "950000" }, "gasPriceGwei": { "type": "string", "nullable": true, "example": "5.2" }, "amountEthHuman": { "type": "string", "nullable": true, "example": "0.00494", "description": "оценка газа в ETH" }, "amountUsd": { "type": "number", "nullable": true, "example": 12.3, "description": "та же оценка в USD" } } }, "balance": { "type": "object", "description": "есть только если у юзера есть ETH-кошелёк", "properties": { "token0": { "type": "object", "properties": { "symbol": { "type": "string" }, "have": { "type": "string", "nullable": true }, "need": { "type": "string" }, "sufficient": { "type": "boolean", "nullable": true }, "shortfall": { "type": "string" } } }, "token1": { "type": "object", "properties": { "symbol": { "type": "string" }, "have": { "type": "string", "nullable": true }, "need": { "type": "string" }, "sufficient": { "type": "boolean", "nullable": true }, "shortfall": { "type": "string" } } }, "gas": { "type": "object", "properties": { "symbol": { "type": "string" }, "have": { "type": "string", "nullable": true }, "needReserve": { "type": "string" }, "sufficient": { "type": "boolean", "nullable": true } } }, "sufficient": { "type": "boolean" } } } } } } } } } }, "400": { "description": "Invalid input / pool not curated" } } } }, "/wallets/{chain}/lp/add": { "post": { "summary": "LP add: fee + approve×2 + NFPM.mint(), один вызов", "description": "Сервер отнимает app-fee 0.7% по каждому токену, аппрувит оба на NonfungiblePositionManager и минтит позицию в заданном диапазоне. Возвращает tokenId + txids. Оба токена — ERC20 (WETH как ERC20). JWT-bind + Idempotency-Key.", "tags": ["Liquidity"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH"] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["pool", "priceLower", "priceUpper", "amount0Desired", "amount1Desired"], "properties": { "pool": { "type": "string" }, "priceLower": { "type": "number" }, "priceUpper": { "type": "number" }, "amount0Desired": { "type": "string" }, "amount1Desired": { "type": "string" }, "slippageBps": { "type": "integer" }, "useNativeEth": { "type": "boolean", "description": "если пул с WETH-ногой — завернуть нативный ETH в WETH автоматически (нужен ETH на балансе вместо WETH)" } } } } } }, "responses": { "200": { "description": "Minted", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object" } } } } } }, "400": { "description": "Invalid input" }, "403": { "description": "No ETH wallet" }, "502": { "description": "Broadcast failed" } } } }, "/wallets/{chain}/lp/remove": { "post": { "summary": "LP remove: decreaseLiquidity + collect (+ burn), один вызов", "description": "Убирает percent% ликвидности позиции tokenId, собирает тело + накопленные комиссии юзеру (multicall). При percent=100 — также burn NFT. Проверка ownerOf(tokenId)=юзер. JWT-bind + Idempotency-Key.", "tags": ["Liquidity"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH"] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["tokenId"], "properties": { "tokenId": { "type": "string" }, "percent": { "type": "number", "description": "(0,100], default 100" } } } } } }, "responses": { "200": { "description": "Removed", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object" } } } } } }, "400": { "description": "Invalid input / not owner" }, "502": { "description": "Broadcast failed" } } } }, "/wallets/{chain}/lp/positions": { "get": { "summary": "LP positions: диапазон, тек. стоимость, накопленные комиссии", "description": "Читает live из чейна: NFT-позиции юзера в куриных пулах. Поля: tokenId, диапазон (тики+цены), liquidity, текущие amount0/amount1, накопленные комиссии (feesOwed0/1), inRange. Без БД.", "tags": ["Liquidity"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH"] } } ], "responses": { "200": { "description": "Positions", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object" } } } } } }, "403": { "description": "No ETH wallet" } } } }, "/wallets/{chain}/approvals": { "get": { "summary": "Скан открытых ERC20-allowance кошелька (видимость drain-векторов)", "description": "Read-only: читает on-chain allowance(owner, spender) по known-токенам (USDT/USDC/DAI/WBTC/WETH…) × known-спендерам (Relay Router v1/v2, Relay Depository, Permit2, Uniswap NFPM). Возвращает только НЕнулевые allowance с флагом `unlimited` (allowance > 2^200, т.е. MAX_UINT256). Открытый unlimited-approval = открытая дверь, через которую солвер может вывести будущие поступления токена — отзови его через POST /approvals/revoke.", "tags": ["Wallet Ops"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH", "BSC"] } } ], "responses": { "200": { "description": "Открытые allowance", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "chain": { "type": "string" }, "owner": { "type": "string" }, "approvals": { "type": "array", "items": { "type": "object", "properties": { "token": { "type": "string", "example": "USDT" }, "tokenAddress": { "type": "string" }, "spender": { "type": "string", "example": "Relay Router v2" }, "spenderAddress": { "type": "string" }, "allowance": { "type": "string", "description": "smallest units" }, "allowanceHuman": { "type": "string", "description": "'unlimited' либо человекочитаемое количество" }, "unlimited": { "type": "boolean" } } } } } } } } } } }, "400": { "description": "Approvals только ETH/BSC" }, "403": { "description": "Нет кошелька для chain" } } } }, "/wallets/{chain}/approvals/revoke": { "post": { "summary": "Custodial-отзыв ERC20-approval (approve(spender, 0))", "description": "Подписывает и броадкастит `token.approve(spender, 0)` кошельком пользователя (custodial — ключ на сервере). **amount захардкожен в 0** — endpoint может ТОЛЬКО снизить allowance, никогда не выдать (не attack surface). Останавливает утечку поступлений через повисший unlimited-approval (напр. USDT → Relay Router v2). Если allowance уже 0 — отвечает `alreadyZero:true` без транзакции (газ не тратится). JWT-bind + Idempotency-Key + audit.", "tags": ["Wallet Ops"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH", "BSC"] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["token", "spender"], "properties": { "token": { "type": "string", "description": "ERC20-контракт, у которого отзываем approval", "example": "0xdAC17F958D2ee523a2206206994597C13D831ec7" }, "spender": { "type": "string", "description": "адрес спендера (напр. Relay Router v2)", "example": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } } } } } }, "responses": { "200": { "description": "Отозвано (txid) либо allowance уже был 0 (alreadyZero)", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "chain": { "type": "string" }, "token": { "type": "string" }, "spender": { "type": "string" }, "txid": { "type": "string", "nullable": true }, "alreadyZero": { "type": "boolean" }, "previousAllowance": { "type": "string", "nullable": true } } } } } } } }, "400": { "description": "Невалидный token/spender или chain ≠ ETH/BSC" }, "403": { "description": "Нет кошелька для chain" }, "502": { "description": "Broadcast failed" } } } }, "/wallets/{chain}/approvals/revoke-all": { "post": { "summary": "«Отвязка от всего» — отозвать ВСЕ открытые approval одним действием", "description": "Сервер сканирует все открытые (ненулевые) allowance кошелька (Relay v1/v2, Depository, Permit2, Uniswap NFPM × известные токены) и подписывает `approve(spender, 0)` по каждой открытой двери — последовательно (ждёт 1 conf, корректный nonce). **amount всегда 0** — только отзыв, не выдача. По умолчанию отзывает ВСЕ ненулевые; `onlyUnlimited:true` — лишь unlimited/oversized (реальный drain-вектор). Если открытых дверей нет — `total:0` без транзакций. При нехватке газа отзывает сколько успел, остаток помечает `skipped` (пополни нативку и повтори). Нужно ~0.0003 native газа на каждую дверь. JWT-bind + Idempotency-Key + audit.", "tags": ["Wallet Ops"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH", "BSC"] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "onlyUnlimited": { "type": "boolean", "description": "true → отозвать только unlimited/oversized approval. По умолчанию false (все ненулевые).", "default": false } } } } } }, "responses": { "200": { "description": "Результат отзыва по каждой двери", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "chain": { "type": "string" }, "owner": { "type": "string" }, "total": { "type": "integer", "description": "сколько открытых дверей нашлось" }, "revoked": { "type": "array", "items": { "type": "object", "properties": { "token": { "type": "string" }, "spender": { "type": "string" }, "tokenAddress": { "type": "string" }, "spenderAddress": { "type": "string" }, "txid": { "type": "string" }, "previousAllowance": { "type": "string" } } } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "token": { "type": "string" }, "spender": { "type": "string" }, "error": { "type": "string" } } } }, "skipped": { "type": "array", "items": { "type": "object", "properties": { "token": { "type": "string" }, "spender": { "type": "string" }, "reason": { "type": "string" } } } } } } } } } } }, "400": { "description": "chain ≠ ETH/BSC" }, "403": { "description": "Нет кошелька для chain" }, "502": { "description": "Scan failed / fatal" } } } }, "/health": { "get": { "summary": "Liveness check", "tags": [ "System" ], "security": [], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } } } } }, "/wallets": { "get": { "summary": "Get all wallets of authenticated user", "tags": [ "Wallets" ], "responses": { "200": { "description": "List of wallets", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WalletsResponse" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/wallets/create": { "post": { "summary": "Создать custodial-кошелёк (server-side mnemonic)", "description": "**Тело запроса не требуется.** Сервер генерит BIP39 mnemonic (12 слов), деривит адреса для 5 chains (BIP44: ETH m/44'/60'/0'/0/0, BTC m/84'/0'/0'/0/0, TRX m/44'/195'/0'/0/0, SOL m/44'/501'/0'/0', BSC = ETH path), шифрует mnemonic AES-256-GCM (master-key из HashiCorp Vault) и атомарно сохраняет. **Возвращает ТОЛЬКО адреса** — mnemonic клиенту не отдаётся. Чтобы потом увидеть seed — отдельный endpoint POST /wallets/mnemonic/reveal. Идемпотентность: 409 если у юзера уже есть кошелёк.", "tags": [ "Wallets" ], "responses": { "201": { "description": "Wallet created (returns addresses only)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WalletsResponse" } } } }, "401": { "description": "Not authenticated" }, "409": { "description": "Wallet already exists", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "Crypto service not ready" } } } }, "/wallets/mnemonic/reveal": { "post": { "summary": "Раскрыть mnemonic (settings-screen)", "description": "Расшифровывает и возвращает 12-словную BIP39 мнемонику юзера. POST + CSRF + body-confirmation. Rate-limit 5/час per-user. Каждый запрос пишется в audit-log.", "tags": [ "Wallets" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "confirm" ], "properties": { "confirm": { "type": "string", "enum": [ "I_UNDERSTAND_SEED_IS_SECRET" ] } } } } } }, "responses": { "200": { "description": "Mnemonic revealed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MnemonicResponse" } } } }, "400": { "description": "Missing/invalid confirm token" }, "401": { "description": "Not authenticated" }, "404": { "description": "Wallet not created yet" }, "429": { "description": "Rate limit (5/hour) exceeded" }, "503": { "description": "Crypto service not ready" } } } }, "/wallets/portfolio": { "get": { "summary": "Aggregate balance по всем 5 сетям (общий баланс)", "description": "Возвращает баланс всех 5 сетей + grand total USD в одном запросе. Параллельно дёргает `getBalance(chain, address)` для ETH/BSC/BTC/TRX/SOL. Каждая успешная сеть кэшируется в KeyDB (TTL 1 час). Если какая-то сеть упала (RPC timeout / network error) — возвращает последний кэшированный balance этой сети с пометкой `stale:true` и описанием `error`. UI всегда показывает осмысленный portfolio, не падая на 0 при transient outage.\n\n**Поведение при ошибках:**\n- 1 сеть упала + есть cache → totalUsd считается с cached + `hasErrors:true`\n- 1 сеть упала + НЕТ cache → perChain[chain]=null, остальное fresh\n- все 5 упали + нет cache → totalUsd=0, hasErrors=true, perChain[*]=null\n- 502 возвращается только при unrecoverable controller exception", "tags": [ "Wallet Ops" ], "responses": { "200": { "description": "Aggregate portfolio", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PortfolioResponse" }, "example": { "success": true, "data": { "totalUsd": 12.34, "hasErrors": false, "perChain": { "ETH": { "chain": "ETH", "address": "0x9dB8Af1B...", "totalUsd": 4.81, "native": { "raw": "1500000000000000000", "formatted": "1.5", "decimals": 18, "usdPrice": 3210.45, "usdValue": 4.81 }, "tokens": {}, "stale": false, "lastUpdated": 1715600000000 }, "BSC": { "chain": "BSC", "address": "0x9dB8Af1B...", "totalUsd": 2.1, "native": { "...": "..." }, "tokens": { "USDT": { "...": "..." } }, "stale": false, "lastUpdated": 1715600000000 }, "BTC": { "chain": "BTC", "address": "bc1q...", "totalUsd": 3.96, "native": { "...": "..." }, "stale": false, "lastUpdated": 1715600000000 }, "TRX": { "chain": "TRX", "address": "T...", "totalUsd": 0.49, "native": { "...": "..." }, "tokens": { "USDT": { "...": "..." } }, "stale": true, "lastUpdated": 1715500000000, "error": "TronGrid timeout" }, "SOL": { "chain": "SOL", "address": "3PJC...", "totalUsd": 0.98, "native": { "...": "..." }, "tokens": {}, "stale": false, "lastUpdated": 1715600000000 } } } } } } }, "401": { "description": "Not authenticated" }, "404": { "description": "No wallets created (вызови POST /wallets/create сначала)" }, "502": { "description": "Portfolio fetch error" } } } }, "/wallets/{chain}/balance": { "get": { "summary": "Balance for user wallet in chain (с USD-ценами)", "description": "Возвращает количество и USD-стоимость для native монеты + всех известных токенов сети. Каждый `FormattedAmount` содержит `raw` (smallest units), `formatted` (human-readable), `decimals`, `usdPrice` (цена 1 единицы), `usdValue` (стоимость holding'а). Цены — CoinGecko с 5-минутным KeyDB-кэшем. Если упал price oracle — `usdPrice`/`usdValue` = `null`, но количества всё равно возвращаются.\n\n**Пример curl:**\n```\ncurl -H \"Authorization: Bearer $JWT\" https://api.example.com/api/wallets/ETH/balance\n```", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/Chain" } } ], "responses": { "200": { "description": "Balance + USD prices", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BalanceResponse" }, "example": { "success": true, "data": { "chain": "ETH", "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f4F45A", "native": { "raw": "1500000000000000000", "formatted": "1.5", "decimals": 18, "usdPrice": 3210.45, "usdValue": 4815.675 }, "tokens": { "USDT": { "raw": "1000000", "formatted": "1", "decimals": 6, "usdPrice": 1, "usdValue": 1 }, "USDC": { "raw": "0", "formatted": "0", "decimals": 6, "usdPrice": 0.9999, "usdValue": 0 }, "DAI": { "raw": "0", "formatted": "0", "decimals": 18, "usdPrice": 0.9998, "usdValue": 0 }, "WBTC": { "raw": "0", "formatted": "0", "decimals": 8, "usdPrice": 67432.12, "usdValue": 0 }, "LINK": { "raw": "0", "formatted": "0", "decimals": 18, "usdPrice": 14.32, "usdValue": 0 }, "UNI": { "raw": "0", "formatted": "0", "decimals": 18, "usdPrice": 8.41, "usdValue": 0 } } } } } } }, "401": { "description": "Not authenticated" }, "404": { "description": "Wallet for this chain not found" }, "502": { "description": "Upstream RPC error" } } } }, "/wallets/{chain}/transactions": { "get": { "summary": "Transaction history for user wallet in chain", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/Chain" } }, { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 100 } } ], "responses": { "200": { "description": "List of transactions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TransactionsResponse" } } } }, "404": { "description": "Wallet for this chain not found" } } } }, "/wallets/{chain}/send": { "post": { "summary": "Custodial send: server signs + broadcasts", "description": "Юзер на клиенте жмёт 'подтвердить' → клиент шлёт {to, amount, token?, feeTier?}. Сервер расшифровывает мнемонику, деривит chain privkey, подписывает, broadcast'ит. Возвращает txid. Защита: TRX MITM check, EVM gas cap 500 gwei, SOL confirmTransaction, BTC timeout + safety multiplier. На ETH/BSC gas теперь берётся из eth_feeHistory (slow/normal/fast).", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/Chain" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendRequest" } } } }, "responses": { "200": { "description": "Broadcast successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TxBroadcastResponse" } } } }, "400": { "description": "Invalid input (incl. invalid feeTier)" }, "404": { "description": "Wallet/mnemonic not found" }, "502": { "description": "Broadcast failed (insufficient balance / RPC error / unsupported)" }, "503": { "description": "Crypto service not ready" } } } }, "/wallets/{chain}/gas-suggestions": { "get": { "summary": "EVM gas oracle (slow/normal/fast)", "description": "Парсит fees через `eth_feeHistory` (последние 5 блоков, percentile p25/p50/p75 priority tips). Возвращает 3 тира с maxFeePerGas/maxPriorityFeePerGas в wei + gwei для display. Floor: ETH=0.5 gwei, BSC=0.05 gwei (защита от dust). Cap: 500 gwei. Только ETH и BSC.", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "ETH", "BSC" ] } } ], "responses": { "200": { "description": "Fee tiers", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "$ref": "#/components/schemas/FeeTiers" } } } } } }, "400": { "description": "Non-EVM chain" }, "502": { "description": "Upstream RPC error" } } } }, "/wallets/{chain}/sign-raw-evm-tx": { "post": { "summary": "Custodial sign + broadcast arbitrary EVM tx (Relay bridge)", "description": "Подписывает unsigned EVM tx из Relay /execute response. Policy: `to` ДОЛЖЕН быть в Relay router allowlist; selector blacklist (approve/permit/setApprovalForAll). Для DEX swap'ов используй `/wallets/{chain}/swap` — там chained custodial без этих ограничений.\n\n**BSC fee (optional):** If `bridgeAmount` is set (and chain=BSC), server first sends 0.7% of bridgeAmount to `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` (BSC_FEE_WALLET), waits 1 confirmation, then broadcasts main tx. Response includes `feeTxid` and `feeAmount` fields. If fee tx reverts, main tx is NOT sent (502).", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "ETH", "BSC" ] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignRawEvmTxRequest" } } } }, "responses": { "200": { "description": "Broadcast successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TxBroadcastResponse" } } } }, "400": { "description": "Policy violation: to not in allowlist OR forbidden selector OR cap exceeded" }, "404": { "description": "Wallet/mnemonic not found" }, "502": { "description": "Broadcast failed" }, "503": { "description": "Crypto service not ready" } } } }, "/wallets/{chain}/swap": { "post": { "summary": "Custodial swap execute (2-step: после /swap/quote)", "description": "Выполняет swap с locked-in параметрами из quote (anti-MEV).\n\n**Required flow:**\n1. `POST /api/wallets/{chain}/swap/quote` → возвращает `quoteId` + preview (expectedOut, minOut, fees, route).\n2. Юзер видит preview, жмёт \"Подтвердить\".\n3. `POST /api/wallets/{chain}/swap` с body `{quoteId}` → execute с locked params.\n\n**Quote TTL:** 30 секунд. Если истёк → 410 Gone, юзер refresh'ит quote.\n\n**Anti-replay:** quote удаляется после успешного execute.\n\n**Legacy mode (deprecated):** Body со старой схемой {from/to/amount/...} или {inputMint/outputMint/amount/...} всё ещё работает (re-quote on-chain, без anti-MEV gate). НЕ рекомендуется для UI.", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "BSC", "TRX", "SOL" ] } }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "title": "2-step execute (recommended)", "required": [ "quoteId" ], "properties": { "quoteId": { "type": "string", "description": "ULID quote id, полученный от POST /:chain/swap/quote.", "example": "q_01KRKD8GA4XZJ5W4E7VFT2N9M3" }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } }, { "type": "object", "title": "BSC/TRX legacy single-shot", "required": [ "from", "to", "amount" ], "properties": { "from": { "type": "string" }, "to": { "type": "string" }, "amount": { "type": "string" }, "slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 }, "feeTier": { "type": "string", "enum": [ "slow", "normal", "fast" ] }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } }, { "type": "object", "title": "SOL legacy single-shot", "required": [ "inputMint", "outputMint", "amount" ], "properties": { "inputMint": { "type": "string" }, "outputMint": { "type": "string" }, "amount": { "type": "string" }, "slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } } ] } } } }, "responses": { "200": { "description": "Swap broadcast", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "chain": { "type": "string", "example": "BSC" }, "approveTxid": { "type": "string", "nullable": true }, "swapTxid": { "type": "string" }, "signature": { "type": "string", "description": "SOL only" } } } } } } } }, "400": { "description": "Validation error / chain mismatch" }, "404": { "description": "Wallet not found" }, "410": { "description": "Quote expired or not found — request new one via /swap/quote", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": false }, "error": { "type": "string", "example": "Quote expired or not found — request a new one via POST /:chain/swap/quote" } } } } } }, "502": { "description": "Upstream RPC / swap failed" }, "503": { "description": "Crypto / audit service unavailable" } } } }, "/wallets/{chain}/app-fee": { "post": { "summary": "Standalone app fee transfer (0.7%)", "description": "Шлёт 0.7% от `amount` на hardcoded app fee wallet для chain.\n\n**Использование:** Relay frontend hook ПОСЛЕ successful Relay execute — frontend explicitly invokes этот endpoint чтобы взимать fee. Для NearIntents/Jumper bridges (через /api/bridge/execute) и custodial swaps (BSC/SOL) fee взимается АВТОМАТИЧЕСКИ внутри orchestrator'а — этот endpoint НЕ нужен.\n\n**Fee wallets** (hardcoded, no env override):\n- EVM (ETH+BSC): `0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68`\n- SOL: `DQkQegoX698XkcXZ6VX9P1qUpbV64Sgjz1BCPFgfWpjD`\n- TRX: `TRwpFjnfMBe4aDJbHYEqeUVCG1auF8wFXP`\n\n**Server-side**: JWT-bind на user's wallet, idempotency-key support, audit log event `wallet.app_fee`. Reuses existing `signAndBroadcast` helper — NO new mnemonic paths.", "tags": [ "Wallet" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH", "BSC", "SOL", "TRX"] }, "description": "Source chain. BTC не поддерживается (no BTC fee wallet)." }, { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 128 }, "description": "UUID. Same key → cached response (no double-charge на retry)." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["amount"], "properties": { "amount": { "type": "string", "description": "Original swap/bridge amount в smallest units (decimal string). Server computes 0.7% = amount × 70 / 10000.", "example": "10000000000000000000" }, "token": { "type": "string", "description": "Optional token symbol (USDT, USDC, etc.). Если задан — fee в этом токене. Иначе — native (BNB/ETH/SOL/TRX).", "example": "USDT" } } } } } }, "responses": { "200": { "description": "Fee tx broadcast OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "feeTxid": { "type": "string", "description": "Tx hash на blockchain" }, "feeAmount": { "type": "string", "description": "0.7% от amount, smallest units" }, "feeWallet": { "type": "string", "description": "Recipient address" }, "chain": { "type": "string", "example": "SOL" }, "token": { "type": "string", "nullable": true } } } } } } } }, "400": { "description": "Validation error / amount too small / unsupported chain" }, "401": { "description": "Unauthorized" }, "404": { "description": "No user wallet для этого chain" }, "409": { "description": "Idempotency-Key conflict" }, "502": { "description": "Broadcast failed" }, "503": { "description": "Audit DB unavailable" } } } }, "/wallets/SOL/sign-and-broadcast-tx": { "post": { "summary": "Custodial sign + broadcast Solana tx (2 формата body)", "description": "Custodial sign + broadcast Solana tx. **Два формата body:**\n\n(a) `{ transaction: '' }` — pre-built VersionedTransaction (Jupiter swap, Relay serialized).\n\n(b) `{ instructions[], addressLookupTableAddresses[]? }` — Relay SOL bridge instructions. Server compile'ит `TransactionMessage` → `VersionedTransaction` с `feePayer = user`.\n\n**Security:** валидирует что каждый `isSigner=true` key равен derived user SOL pubkey, resolve LUTs через RPC, partial-sign keypair'ом, broadcast, confirm.", "tags": [ "Wallet Ops" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [ { "title": "Pre-built VersionedTransaction (Jupiter / Relay serialized)", "type": "object", "required": [ "transaction" ], "properties": { "transaction": { "type": "string", "description": "Base64-encoded VersionedTransaction (max ~8KB)" } } }, { "title": "Relay-style instructions (для SOL bridge)", "type": "object", "required": [ "instructions" ], "properties": { "instructions": { "type": "array", "description": "Array из {programId, keys, data}. Server compile'ит TransactionMessage → VersionedTransaction с feePayer=user.", "items": { "type": "object", "required": [ "programId", "keys", "data" ], "properties": { "programId": { "type": "string", "description": "SPL program pubkey (base58)" }, "keys": { "type": "array", "items": { "type": "object", "required": [ "pubkey", "isSigner", "isWritable" ], "properties": { "pubkey": { "type": "string", "description": "Account pubkey (base58)" }, "isSigner": { "type": "boolean", "description": "Если true — pubkey ДОЛЖЕН равняться user'у (anti-drain)" }, "isWritable": { "type": "boolean" } } } }, "data": { "type": "string", "description": "Instruction data: hex (без префикса) или base64 — autodetect" } } } }, "addressLookupTableAddresses": { "type": "array", "items": { "type": "string" }, "description": "Опционально. SPL Address Lookup Table accounts которые server разрезолвит через SOL RPC (getAddressLookupTable)." } } } ] } } } }, "responses": { "200": { "description": "Signed and broadcast", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "signature": { "type": "string" }, "chain": { "type": "string" } } } } } } } }, "400": { "description": "Invalid body / feePayer mismatch / signer-key mismatch / malformed instruction" }, "404": { "description": "SOL wallet/mnemonic not found" }, "502": { "description": "Sign or broadcast failed (включая RPC ошибки / blockhash expired / on-chain revert)" } } } }, "/btc/utxos/{address}": { "get": { "summary": "Confirmed UTXOs for Bitcoin address", "tags": [ "BTC" ], "parameters": [ { "name": "address", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "UTXOs" }, "401": { "description": "Not authenticated" } } } }, "/btc/fee-estimates": { "get": { "summary": "Bitcoin fee estimates (sat/vB)", "tags": [ "BTC" ], "responses": { "200": { "description": "fast/normal/slow" }, "401": { "description": "Not authenticated" } } } }, "/btc/broadcast": { "post": { "summary": "Broadcast raw signed Bitcoin tx", "tags": [ "BTC" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "hex" ], "properties": { "hex": { "type": "string" } } } } } }, "responses": { "200": { "description": "txid" }, "400": { "description": "Invalid hex" } } } }, "/tron/account/{address}": { "get": { "summary": "TRON account info + USDT (TRC20) balance", "tags": [ "TRON" ], "parameters": [ { "name": "address", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Account data" } } } }, "/tron/createtransaction": { "post": { "summary": "Build unsigned TRX transfer", "tags": [ "TRON" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "owner_address", "to_address", "amount" ], "properties": { "owner_address": { "type": "string" }, "to_address": { "type": "string" }, "amount": { "type": "integer" } } } } } }, "responses": { "200": { "description": "Unsigned tx" } } } }, "/tron/triggersmartcontract": { "post": { "summary": "Build unsigned TRC20 contract call", "tags": [ "TRON" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Unsigned tx" } } } }, "/tron/broadcasttransaction": { "post": { "summary": "Broadcast signed TRON tx", "tags": [ "TRON" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Result" } } } }, "/relay/quote": { "post": { "summary": "Relay bridge quote (POST с JSON body)", "description": "Прокси к https://api.relay.link/quote. Параметры в body: user, recipient, originChainId, destinationChainId, originCurrency, destinationCurrency, amount (smallest units), tradeType (EXACT_INPUT|EXACT_OUTPUT).", "tags": [ "Relay" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "user", "originChainId", "destinationChainId", "originCurrency", "destinationCurrency", "amount", "tradeType" ], "properties": { "user": { "type": "string", "description": "Sender address (0x.. / T.. / SOL pubkey)" }, "recipient": { "type": "string", "description": "Обычно тот же что user" }, "originChainId": { "type": "integer", "description": "1=ETH, 56=BSC, 728126428=TRON, 792703809=SOL" }, "destinationChainId": { "type": "integer" }, "originCurrency": { "type": "string", "description": "Token address (EVM: 0x.., SOL: mint, TRX: contract или 'TRX')" }, "destinationCurrency": { "type": "string" }, "amount": { "type": "string", "description": "smallest units" }, "tradeType": { "type": "string", "enum": [ "EXACT_INPUT", "EXACT_OUTPUT" ] }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } } } } }, "responses": { "200": { "description": "Quote с steps[], fees, details, breakdown + appCommission (наша 0.7% в токене+USDT, информативно — считается из details.currencyIn)" }, "502": { "description": "Relay upstream error (приложен upstream JSON для деталей)" } } } }, "/relay/intents/status/v3": { "get": { "summary": "Relay intent status", "tags": [ "Relay" ], "parameters": [ { "name": "requestId", "in": "query", "required": true, "schema": { "type": "string", "description": "Из quote/execute response" } } ], "responses": { "200": { "description": "Status" }, "502": { "description": "Relay upstream error" } } } }, "/relay/execute/{action}": { "post": { "summary": "Relay execute (swap | bridge)", "description": "Принимает ТОТ ЖЕ payload что и /quote и возвращает unsigned tx в steps[].items[].data. Эту tx надо потом подписать (для ETH/BSC — через /wallets/{chain}/sign-raw-evm-tx) и broadcast'нуть. Action whitelist: swap, bridge.", "tags": [ "Relay" ], "parameters": [ { "name": "action", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "swap", "bridge" ] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "description": "Same as /relay/quote body" } } } }, "responses": { "200": { "description": "steps[] with unsigned tx + fees + details" }, "502": { "description": "Relay upstream error" } } } }, "/prices": { "get": { "summary": "USD-цены для списка символов", "description": "Возвращает котировки USD для указанных символов (max 50). Символы должны быть из реестра поддерживаемых токенов (см. tag описание сетей в /wallets/{chain}/balance). Источник — CoinGecko free API, кэшируется в KeyDB 5 минут.\n\n**Resolution:**\n- Native символ совпадающий с chain code (BTC/ETH/BSC/TRX/SOL) → используется native CoinGecko id.\n- Иначе: ищется в реестре сети из `chain` query param.\n- Если `chain` не задан → fallback порядок ETH → BSC → SOL → TRX → BTC. Первый matched chain wins.\n\n**Безопасность:** symbols whitelisted, никакого user-input в URL CoinGecko (защита от SSRF). Max 50 символов на запрос. Auth обязательна (JWT Bearer или cookie).\n\n**Пример curl:**\n```\ncurl -H \"Authorization: Bearer $JWT\" \"https://api.example.com/api/prices?symbols=BTC,ETH,USDT,SOL,BONK\"\n```", "tags": [ "Prices" ], "parameters": [ { "name": "symbols", "in": "query", "required": true, "description": "Comma-separated список символов (макс 50). Каждый — `[A-Z0-9]{1,16}`. Только символы из registry: BTC, ETH, BSC, TRX, SOL (native) + USDT, USDC, DAI, WBTC, LINK, UNI, DOGE, WBNB, BUSD, PUMP, JUP, WIF, POPCAT, TRUMP, PYTH, JTO, W, BONK, ORCA, PENGU, RAY.", "schema": { "type": "string", "example": "BTC,ETH,USDT" } }, { "name": "chain", "in": "query", "required": false, "description": "Опционально: для disambiguation если symbol присутствует в нескольких сетях (USDT/USDC). Если не задан — fallback порядок: ETH → BSC → SOL → TRX → BTC.", "schema": { "$ref": "#/components/schemas/Chain" } } ], "responses": { "200": { "description": "USD prices", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PricesResponse" }, "example": { "success": true, "data": { "BTC": { "usd": 67432.12 }, "ETH": { "usd": 3210.45 }, "USDT": { "usd": 1 }, "SOL": { "usd": 142.88 }, "BONK": { "usd": 0.00002145 } } } } } }, "400": { "description": "Validation error: пустой/слишком большой/невалидный список, неизвестный chain или unknown symbol", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "Rate limit exceeded" }, "502": { "description": "Upstream price oracle error (CoinGecko)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/wallets/{chain}/swap/quote": { "post": { "summary": "Swap preview / quote (без broadcast)", "description": "Считает expected output, slippage, network fee, route, approveRequired для custodial swap.\n\n**Read-only** — НЕ broadcast'ит ничего, mnemonic не расшифровывается.\n\nВозвращает `quoteId` (ULID) + preview-снимок. Юзер показывает preview, жмёт Confirm → клиент шлёт `POST /:chain/swap` с `{quoteId}`. Quote живёт **30 секунд** в KeyDB — после execute удаляется (anti-replay).\n\n**Use cases:**\n- Live debounced quote при вводе amount в UI.\n- \"How much would I get?\" — без обязательства execute.\n- Защита от MEV-frontrun: `minOut` зафиксирован между preview и confirm.", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "BSC", "TRX", "SOL" ] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "title": "BSC/TRX quote (symbols)", "required": [ "from", "to", "amount" ], "properties": { "from": { "type": "string", "example": "USDT", "description": "BSC: BNB|USDT|USDC|DOGE|WBNB|BUSD; TRX: TRX|USDT" }, "to": { "type": "string", "example": "BNB" }, "amount": { "type": "string", "example": "100000000000000000", "description": "smallest units" }, "slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 }, "feeTier": { "type": "string", "enum": [ "slow", "normal", "fast" ], "description": "BSC only" }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } }, { "type": "object", "title": "SOL quote (mints)", "required": [ "inputMint", "outputMint", "amount" ], "properties": { "inputMint": { "type": "string", "example": "So11111111111111111111111111111111111111112" }, "outputMint": { "type": "string", "example": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" }, "amount": { "type": "string", "example": "1000000", "description": "smallest units" }, "slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" } } } ] } } } }, "responses": { "200": { "description": "Quote preview", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "$ref": "#/components/schemas/SwapQuoteResponse" } } } } } }, "400": { "description": "Validation error / unsupported pair" }, "404": { "description": "Wallet not found" }, "502": { "description": "Upstream RPC / quote failed (no liquidity, etc.)" }, "503": { "description": "Quote cache unavailable" } } } }, "/wallets/{chain}/send/cost-estimate": { "post": { "summary": "Estimate USD cost of a /send call (read-only, без broadcast)", "description": "Read-only USD-оценка сколько будет стоить broadcast tx (gas/network fee).\n\nНе дёргает mnemonic, не резервирует idempotency cache, не делает RPC broadcast.\n\nBody — те же поля что у /send МИНУС `to`. Можно прислать `amount` (smallest units, legacy) ИЛИ `amountHuman` (\"0.01\").", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "ETH", "BSC", "BTC", "TRX", "SOL" ] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "token": { "type": "string", "description": "Token symbol (USDT, USDC, ...). Пусто = native." }, "amount": { "type": "string", "description": "Smallest units (legacy)" }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" }, "feeTier": { "type": "string", "enum": [ "slow", "normal", "fast" ], "default": "normal", "description": "EVM only (ETH/BSC); ignored для TRX/SOL/BTC" } } } } } }, "responses": { "200": { "description": "Cost estimate", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendCostEstimateResponse" } } } }, "400": { "description": "Validation error" }, "502": { "description": "Gas oracle / price oracle unavailable" } } } }, "/wallets/{chain}/swap/cost-estimate": { "post": { "summary": "Estimate USD cost of a swap (без cache, без quoteId)", "description": "Те же поля что у /swap/quote, но возвращает ТОЛЬКО fee + route + approveRequired (без quoteId/expiry/cache).\n\nIdempotent — можно вызывать много раз. Используется для отображения USD-цены свапа в UI ДО того как юзер решит подтвердить.", "tags": [ "Wallet Ops" ], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "BSC", "TRX", "SOL" ] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "title": "BSC/TRX", "properties": { "from": { "type": "string" }, "to": { "type": "string" }, "amount": { "type": "string", "description": "Smallest units (legacy)" }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" }, "slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 }, "feeTier": { "type": "string", "enum": [ "slow", "normal", "fast" ] } } }, { "type": "object", "title": "SOL", "properties": { "inputMint": { "type": "string" }, "outputMint": { "type": "string" }, "amount": { "type": "string", "description": "Smallest units (legacy)" }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" }, "slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 } } } ] } } } }, "responses": { "200": { "description": "Swap cost estimate", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwapCostEstimateResponse" } } } }, "400": { "description": "Validation error" }, "404": { "description": "Wallet not found" }, "502": { "description": "Upstream RPC / quote failed" } } } }, "/wallets/{chain}/op-cost": { "post": { "summary": "Стоимость операции (свап/бридж): наша 0.7% в native+USD + проверка баланса токена/газа", "description": "Read-only. По входному токену и сумме возвращает (всё в ОБЫЧНЫХ числах, не decimals): нашу комиссию 0.7% в самом токене, эквивалент в native-монете сети и в USD; и проверку баланса — хватает ли входного токена на операцию И нативной монеты на газ. Без mnemonic/broadcast/БД, JWT-bound (только свой кошелёк). Сетевую/gas-комиссию даёт swap/cost-estimate либо relay quote — здесь не дублируется. Если входной токен = native, токен-нога считается по нативному балансу, а газ-нога требует сумму + резерв.", "tags": ["Wallet Ops"], "parameters": [ { "name": "chain", "in": "path", "required": true, "schema": { "type": "string", "enum": ["ETH", "BSC", "SOL", "TRX"] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "token": { "type": "string", "description": "Символ входного токена (USDT/USDC/…). Пусто или символ нативной монеты = нативная (ETH/BNB/SOL/TRX).", "example": "USDT" }, "amount": { "type": "string", "description": "Сумма в smallest units (legacy). Используй ЛИБО amount, ЛИБО amountHuman." }, "amountHuman": { "type": "string", "description": "Сумма в обычном виде (напр. \"10\").", "example": "10" } } } } } }, "responses": { "200": { "description": "Стоимость операции + достаточность баланса (human-числа)", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "chain": { "type": "string" }, "input": { "type": "object", "properties": { "token": { "type": "string" }, "amount": { "type": "string" } } }, "appCommission": { "type": "object", "properties": { "ratePercent": { "type": "number", "example": 0.7 }, "inToken": { "type": "object", "properties": { "symbol": { "type": "string" }, "amount": { "type": "string", "example": "0.07" } } }, "inNative": { "type": "object", "properties": { "symbol": { "type": "string", "example": "ETH" }, "amount": { "type": "string", "nullable": true, "example": "0.0000218" } } }, "usd": { "type": "number", "nullable": true, "example": 0.07 } } }, "balance": { "type": "object", "properties": { "token": { "type": "object", "properties": { "symbol": { "type": "string" }, "have": { "type": "string", "nullable": true }, "need": { "type": "string" }, "sufficient": { "type": "boolean", "nullable": true }, "shortfall": { "type": "string" } } }, "gas": { "type": "object", "properties": { "symbol": { "type": "string" }, "have": { "type": "string", "nullable": true }, "needReserve": { "type": "string" }, "sufficient": { "type": "boolean", "nullable": true } } } } }, "sufficient": { "type": "boolean" } } } } } } } }, "400": { "description": "Невалидный token/amount или chain не ETH/BSC/SOL/TRX" }, "404": { "description": "Нет кошелька для chain" } } } }, "/relay/cost-estimate": { "post": { "summary": "Estimate USD cost of a bridge (Relay quote — trimmed, без steps[])", "description": "Вызывает Relay /quote внутри и фильтрует response — отдаёт только fees + details (rate, time, impact, currencyIn/Out).\n\nБез `steps[]` (которые тяжёлые и содержат unsigned txs). Поведение JWT-binding (body.user, body.recipient) — то же что у /relay/quote.", "tags": [ "Bridge (Relay)" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "user": { "type": "string", "description": "Sender address (должен совпадать с user's wallet origin chain)" }, "recipient": { "type": "string" }, "originChainId": { "type": "integer", "description": "1=ETH, 56=BSC, 792703809=SOL" }, "destinationChainId": { "type": "integer" }, "originCurrency": { "type": "string", "description": "Contract address (для EVM) или mint (для SOL)" }, "destinationCurrency": { "type": "string" }, "amount": { "type": "string", "description": "Smallest units (legacy)" }, "amountHuman": { "type": "string", "description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.", "example": "0.01" }, "tradeType": { "type": "string", "enum": [ "EXACT_INPUT", "EXACT_OUTPUT" ] } } } } } }, "responses": { "200": { "description": "Bridge cost estimate", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BridgeCostEstimateResponse" } } } }, "400": { "description": "Validation error / unknown originCurrency для amountHuman" }, "403": { "description": "body.user/recipient не совпадает с user wallets" }, "502": { "description": "Relay upstream error" }, "504": { "description": "Relay timeout" } } } }, "/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\nQuery params:\n- `?chain=ETH|BSC|BTC|TRX|SOL` — filter по одной сети\n- `?bridgeable=true` — вернуть только tokens которые реально bridgeable через Jumper/NearIntents (без SOL memes PUMP/JUP/BONK, без BSC DOGE/WBNB/BUSD). Используется UI dropdowns в Jumper bridge section.", "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)." }, { "name": "bridgeable", "in": "query", "required": false, "schema": { "type": "boolean", "default": false }, "description": "Если `true` — filter только tokens из allowlist которые имеют bridge route через NearIntents/Jumper/Relay. Skips memes/wrapped/deprecated tokens которые нельзя bridge." } ], "responses": { "200": { "description": "Token list", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TokensListResponse" } } } }, "400": { "description": "Invalid chain parameter" } } } }, "/prices/dynamics": { "get": { "summary": "24h price + rolling change % (CoinGecko)", "description": "Возвращает USD price + rolling 24h change % для списка symbols.\n\nИсточник: CoinGecko `/simple/price?include_24hr_change=true`. Cache в KeyDB 5 минут.\n\nDefault symbols (если query не задан): `BTC,ETH,BNB,SOL,TRX`.\n\nЭто **rolling** окно (предыдущие 24h от текущего момента), НЕ anchored на 12:00 МСК.", "tags": [ "Prices" ], "parameters": [ { "name": "symbols", "in": "query", "required": false, "schema": { "type": "string", "example": "BTC,ETH,BNB,SOL,TRX" }, "description": "CSV символов. Whitelist через token-registry. Max 50." } ], "responses": { "200": { "description": "Цены + 24h change", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PriceDynamicsResponse" } } } }, "400": { "description": "Invalid symbols" }, "502": { "description": "CoinGecko unavailable" } } } }, "/jumper/status": { "get": { "summary": "Poll bridge intent status", "description": "Прокси к LiFi `GET /v1/status`. Используется после execute для poll до final state.", "tags": [ "Bridge (Jumper / LiFi)" ], "parameters": [ { "name": "txHash", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "bridge", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "fromChain", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "toChain", "in": "query", "required": false, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "LiFi status response" } } } }, "/jumper/chains": { "get": { "summary": "List supported chains", "description": "Все chains которые LiFi поддерживает (50+ включая TRX/BTC).", "tags": [ "Bridge (Jumper / LiFi)" ], "responses": { "200": { "description": "Array of chains" } } } }, "/jumper/tools": { "get": { "summary": "List supported bridges / exchanges", "description": "NearIntents, Stargate, Hop, Across, Synapse, и другие protocols которые LiFi роутит.", "tags": [ "Bridge (Jumper / LiFi)" ], "responses": { "200": { "description": "Array of tools" } } } }, "/jumper/tokens": { "get": { "summary": "List supported tokens per chain", "tags": [ "Bridge (Jumper / LiFi)" ], "parameters": [ { "name": "chains", "in": "query", "required": false, "schema": { "type": "string" }, "description": "CSV LiFi chainIds (filter)" } ], "responses": { "200": { "description": "Tokens map" } } } }, "/jumper/connections": { "get": { "summary": "List routes between specific chain/token pair", "tags": [ "Bridge (Jumper / LiFi)" ], "parameters": [ { "name": "fromChain", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "toChain", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "fromToken", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "toToken", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Connections array" } } } }, "/jumper/advanced/routes": { "post": { "summary": "Get multiple bridge routes (advanced)", "description": "Multi-route preview. Body — те же поля что у /quote плюс options.", "tags": [ "Bridge (Jumper / LiFi)" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "fromChainId", "toChainId", "fromTokenAddress", "toTokenAddress", "fromAmount", "fromAddress" ], "properties": { "fromChainId": { "type": "integer" }, "toChainId": { "type": "integer" }, "fromTokenAddress": { "type": "string" }, "toTokenAddress": { "type": "string" }, "fromAmount": { "type": "string" }, "fromAddress": { "type": "string", "description": "Связывается с user wallet через JWT" }, "toAddress": { "type": "string" }, "options": { "type": "object", "additionalProperties": true } } } } } }, "responses": { "200": { "description": "Routes array" }, "403": { "description": "fromAddress не совпадает с user wallet" } } } }, "/jumper/advanced/stepTransaction": { "post": { "summary": "Get unsigned tx for a route step", "description": "Принимает step object из /advanced/routes → возвращает transactionRequest для подписи.", "tags": [ "Bridge (Jumper / LiFi)" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "responses": { "200": { "description": "Step with transactionRequest" } } } }, "/jumper/quote-best": { "get": { "summary": "Best bridge quote with NearIntents priority", "description": "**Smart routing:** сначала пробует LiFi `/quote?allowBridges=near` (только NearIntents). Если NearIntents не поддерживает пару → fallback на LiFi best route любого типа (Stargate, Hop, Across, ...).\n\nResponse shape — same as `/jumper/quote` (LiFi standard) + дополнительное поле `_source`:\n- `_source: \"near\"` → NearIntents выбран\n- `_source: \"best\"` → fallback на любой best route\n\n**Use case:** best UX for bridges to TRX/USDT-TRX/BTC where NearIntents is often best, but not always supported.", "tags": [ "Bridge (Jumper / LiFi)" ], "parameters": [ { "name": "fromChain", "in": "query", "required": true, "schema": { "type": "integer" }, "description": "LiFi chainId: ETH=1, BSC=56, SOL=1151111081099710, TRX=728126428, BTC=20000000000001" }, { "name": "toChain", "in": "query", "required": true, "schema": { "type": "integer" } }, { "name": "fromToken", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "toToken", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "fromAmount", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Smallest units" }, { "name": "fromAddress", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Связывается с user wallet через JWT (если chain в DB)" }, { "name": "toAddress", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "slippage", "in": "query", "required": false, "schema": { "type": "number", "example": 0.03 } } ], "responses": { "200": { "description": "LiFi quote (passthrough) + _source (near|best) + appCommission (наша 0.7%, информативно; сбор — атомарно в /bridge/execute)", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true, "description": "Сырой LiFi-quote + наши поля. Ниже задокументированы ключевые.", "properties": { "_source": { "type": "string", "enum": ["near", "best"], "description": "near = NearIntents, best = любой best route" }, "tool": { "type": "string", "example": "near" }, "toolDetails": { "type": "object", "additionalProperties": true }, "estimate": { "type": "object", "additionalProperties": true, "properties": { "fromAmount": { "type": "string" }, "fromAmountUSD": { "type": "string", "example": "10.03" }, "toAmount": { "type": "string", "description": "сколько получишь (smallest units)" }, "toAmountMin": { "type": "string", "description": "минимум с учётом slippage → передай как acceptedMinOut в /bridge/execute" }, "toAmountUSD": { "type": "string" }, "executionDuration": { "type": "number", "description": "оценка, сек" }, "gasCosts": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "feeCosts": { "type": "array", "items": { "type": "object", "additionalProperties": true } } } }, "action": { "type": "object", "additionalProperties": true, "properties": { "fromToken": { "type": "object", "additionalProperties": true, "properties": { "symbol": { "type": "string" }, "decimals": { "type": "integer" }, "address": { "type": "string" } } }, "toToken": { "type": "object", "additionalProperties": true }, "fromAmount": { "type": "string" } } }, "transactionRequest": { "type": "object", "nullable": true, "additionalProperties": true, "description": "Готовая EVM-tx (to/data/value/chainId) — для ручной подписи через /wallets/{chain}/sign-raw-evm-tx" }, "appCommission": { "type": "object", "nullable": true, "description": "Наша комиссия 0.7% (в токене + USDT). Информативно; реально списывается в /bridge/execute. Считается из action.fromToken + action.fromAmount + estimate.fromAmountUSD.", "properties": { "ratePercent": { "type": "number", "example": 0.7 }, "inToken": { "type": "object", "properties": { "symbol": { "type": "string", "example": "USDC" }, "amount": { "type": "string", "example": "0.07", "description": "0.7% во входном токене" } } }, "usd": { "type": "number", "nullable": true, "example": 0.07, "description": "та же 0.7% в USDT (≈USD)" } } } } } } } }, "400": { "description": "Missing required params" }, "403": { "description": "fromAddress mismatch user wallet" }, "502": { "description": "No route found" }, "504": { "description": "LiFi timeout" } } } }, "/jumper/quote": { "get": { "summary": "LiFi single quote (passthrough; без NearIntents-приоритета)", "description": "Прямой проброс на LiFi `/quote`. В отличие от `/jumper/quote-best` НЕ пробует NearIntents первым — отдаёт LiFi best route как есть. Ответ — сырой LiFi-quote + наше поле **appCommission** (наша 0.7% в токене + USDT, информативно; сбор — в /bridge/execute). Параметры те же, что у quote-best. Обычно используй quote-best.", "tags": ["Bridge (Jumper / LiFi)"], "parameters": [ { "name": "fromChain", "in": "query", "required": true, "schema": { "type": "integer" }, "description": "LiFi chainId: ETH=1, BSC=56, SOL=1151111081099710, TRX=728126428, BTC=20000000000001" }, { "name": "toChain", "in": "query", "required": true, "schema": { "type": "integer" } }, { "name": "fromToken", "in": "query", "required": true, "schema": { "type": "string" }, "description": "contract / mint / native sentinel" }, { "name": "toToken", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "fromAmount", "in": "query", "required": true, "schema": { "type": "string" }, "description": "smallest units" }, { "name": "fromAddress", "in": "query", "required": true, "schema": { "type": "string" }, "description": "должен совпадать с кошельком юзера для fromChain (JWT-bind)" }, { "name": "toAddress", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "slippage", "in": "query", "required": false, "schema": { "type": "number", "example": 0.005 } } ], "responses": { "200": { "description": "LiFi quote (passthrough) + appCommission (наша 0.7% в токене + USDT). Поля — как у /jumper/quote-best (estimate, action, transactionRequest, appCommission)." }, "400": { "description": "Missing required params" }, "403": { "description": "fromAddress mismatch user wallet" }, "502": { "description": "LiFi upstream / no route" }, "504": { "description": "LiFi timeout" } } } }, "/bridge/execute": { "post": { "summary": "One-click bridge execute (sign + broadcast)", "description": "**Подтвердить bridge** — server берёт quote (Jumper или Relay), re-fetches его свежим, валидирует против `acceptedMinOut` (anti-MEV), и dispatches на signing path per source chain:\n\n- **ETH/BSC source:** ERC20 approve (если allowance мал) → BSC 0.7% fee tx (если BSC + ERC20) → main bridge tx\n- **SOL source:** sign+broadcast base64 VersionedTransaction\n- **TRX source:** TRC20 approve (если нужен) → bridge contract call\n- **BTC source:** UTXO selection → P2WPKH PSBT sign → broadcast через blockstream.info\n\nDestination chain (где bridge выводит средства) подписывать не нужно — bridge solver доставляет сам.\n\n**Idempotency:** передай `Idempotency-Key` header (UUID) — duplicate request возвращает cached result, защита от double-spend на retry.\n\n**Anti-MEV:** `acceptedMinOut` = `estimate.toAmountMin` из quote preview. Если свежий quote ухудшился >0.5% → 409 'price moved'.", "tags": [ "Bridge Execute" ], "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 128 }, "description": "UUID на каждый клик 'Подтвердить'. Same key → same response (no double-broadcast)." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "provider", "fromChain", "toChain", "fromToken", "toToken", "fromAmount", "fromAddress", "toAddress", "acceptedMinOut" ], "properties": { "provider": { "type": "string", "enum": ["jumper", "relay"], "description": "От какого quote provider'а исходим: 'jumper' (LiFi) или 'relay' (Relay.link). Для BTC source/dest — обычно 'relay'.\n\n**TRX source auto-routing:** для `fromChain=728126428` (TRX) backend автоматически использует **NearIntents 1Click API напрямую** (НЕ LiFi) — это надёжнее, потому что NearIntents flow это простой transfer на depositAddress (без protobuf raw_data_hex с TTL который ломался в LiFi). Response.provider в этом случае будет `'nearintents'`." }, "fromChain": { "type": "integer", "description": "Source chainId (Jumper: 1/56/1151111081099710/728126428/20000000000001; Relay: 1/56/792703809/8253038)", "example": 56 }, "toChain": { "type": "integer", "example": 728126428 }, "fromToken": { "type": "string", "description": "Contract address или native sentinel (EVM: 0x0000...; SOL: 11111...; TRX: T9yD14Nj...; BTC: bc1qqqq...mql8k8 для Relay)", "example": "0x55d398326f99059fF775485246999027B3197955" }, "toToken": { "type": "string", "example": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" }, "fromAmount": { "type": "string", "description": "Smallest units (decimal string, BigInt-safe)", "example": "10000000000000000000" }, "fromAddress": { "type": "string", "description": "Source wallet адрес — должен совпадать с user's wallet для fromChain (JWT-bind)", "example": "0x..." }, "toAddress": { "type": "string", "description": "Destination wallet адрес. Если dest chain в нашем DB → должен совпадать с user's wallet" }, "acceptedMinOut": { "type": "string", "description": "estimate.toAmountMin который пользователь видел в preview. Server отвергнет если fresh quote ухудшился > 0.5%", "example": "8910000" } } } } } }, "responses": { "200": { "description": "Bridge tx broadcast OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean", "example": true }, "data": { "type": "object", "properties": { "provider": { "type": "string", "enum": ["jumper", "relay", "nearintents"], "example": "nearintents", "description": "Actual provider used. Может отличаться от request.provider — для TRX source backend auto-routes на 'nearintents'." }, "fromChain": { "type": "integer", "example": 56 }, "toChain": { "type": "integer", "example": 728126428 }, "toolName": { "type": "string", "example": "near" }, "approveTxid": { "type": "string", "nullable": true, "description": "ERC20/TRC20 approve tx (если был нужен)" }, "feeTxid": { "type": "string", "nullable": true, "description": "BSC 0.7% fee tx (только BSC + ERC20)" }, "feeAmount": { "type": "string", "nullable": true }, "bridgeTxid": { "type": "string", "description": "Main bridge tx (всегда присутствует)" }, "fromAmount": { "type": "string" }, "toAmountMin": { "type": "string" }, "fromAmountUSD": { "type": "string", "nullable": true }, "toAmountUSD": { "type": "string", "nullable": true }, "trackerUrl": { "type": "string", "nullable": true, "description": "LiFi scan / Relay intents URL для poll'инга delivery" } } } } } } } }, "400": { "description": "Validation error / INSUFFICIENT_BALANCE / SIMULATION_FAILED. Body содержит { success:false, error: , code: }. Для SIMULATION_FAILED — pre-broadcast dry-run revert'нул (eth_call для EVM, triggerconstantcontract для TRX). Tx НЕ broadcast'нут, fees не сгорели." }, "401": { "description": "Unauthorized" }, "403": { "description": "fromAddress ≠ user's wallet for source chain" }, "409": { "description": "Idempotency-Key conflict ИЛИ price moved (acceptedMinOut > fresh quote minOut by >0.5%)" }, "501": { "description": "Source chain not yet implemented (TRX/BTC требуют новых signer endpoints)" }, "502": { "description": "Upstream LiFi/Relay error или bridge tx broadcast failed" }, "503": { "description": "Audit DB unavailable" } } } } } }