4075 lines
154 KiB
JSON
4075 lines
154 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 мин)"
|
||
},
|
||
{
|
||
"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: '<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 + 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 + _source (near|best) + appCommission (наша 0.7% в токене + USDT; информативно, считается из action.fromToken/estimate.fromAmountUSD; сбор — атомарно в /bridge/execute). Пример appCommission: { ratePercent: 0.7, inToken: { symbol: \"USDT\", amount: \"0.7\" }, usd: 0.7 }"
|
||
},
|
||
"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" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|