3441 lines
116 KiB
JSON
3441 lines
116 KiB
JSON
{
|
||
"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 мин)"
|
||
}
|
||
],
|
||
"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"
|
||
},
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.04
|
||
}
|
||
}
|
||
},
|
||
"total": {
|
||
"type": "object",
|
||
"properties": {
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.04
|
||
}
|
||
}
|
||
},
|
||
"app": {
|
||
"type": "object",
|
||
"nullable": true,
|
||
"description": "App fee 0.7% (BSC only). Server sends this to BSC_FEE_WALLET via separate tx BEFORE swap.",
|
||
"properties": {
|
||
"asset": {
|
||
"type": "string",
|
||
"example": "BNB"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"example": "70000000000000"
|
||
},
|
||
"amountFormatted": {
|
||
"type": "string",
|
||
"example": "0.00007"
|
||
},
|
||
"recipient": {
|
||
"type": "string",
|
||
"example": "0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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",
|
||
"properties": {
|
||
"asset": {
|
||
"type": "string",
|
||
"example": "BNB"
|
||
},
|
||
"amount": {
|
||
"type": "string"
|
||
},
|
||
"amountFormatted": {
|
||
"type": "string"
|
||
},
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true
|
||
}
|
||
}
|
||
},
|
||
"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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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": {
|
||
"/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: '<base64>' }` — 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"
|
||
},
|
||
"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"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/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 + _source field (near|best)"
|
||
},
|
||
"400": {
|
||
"description": "Missing required params"
|
||
},
|
||
"403": {
|
||
"description": "fromAddress mismatch user wallet"
|
||
},
|
||
"502": {
|
||
"description": "No route found"
|
||
},
|
||
"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: <message>, code: <INSUFFICIENT_BALANCE|SIMULATION_FAILED|undefined> }. Для 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" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|