2649 lines
87 KiB
JSON
2649 lines
87 KiB
JSON
{
|
||
"openapi": "3.0.0",
|
||
"info": {
|
||
"title": "CryptoWallet API",
|
||
"version": "5.0.0",
|
||
"description": "Multi-chain custodial wallet API (ETH/BSC/BTC/TRX/SOL). Сервер генерит mnemonic, шифрует AES-256-GCM (master-key из HashiCorp Vault), хранит её и сам подписывает транзакции. Auth via JWT (cookie/Bearer), issued by external auth-service (BITOK)."
|
||
},
|
||
"servers": [
|
||
{
|
||
"url": "/api",
|
||
"description": "API root"
|
||
}
|
||
],
|
||
"tags": [
|
||
{
|
||
"name": "System",
|
||
"description": "Health & service info"
|
||
},
|
||
{
|
||
"name": "Wallets",
|
||
"description": "Custodial wallet lifecycle"
|
||
},
|
||
{
|
||
"name": "Wallet Ops",
|
||
"description": "Per-chain balance / transactions / send"
|
||
},
|
||
{
|
||
"name": "BTC",
|
||
"description": "Bitcoin RPC proxy (Blockstream)"
|
||
},
|
||
{
|
||
"name": "TRON",
|
||
"description": "TRON RPC proxy (TronGrid)"
|
||
},
|
||
{
|
||
"name": "Solana",
|
||
"description": "Solana swap proxy (Jupiter)"
|
||
},
|
||
{
|
||
"name": "TRON Swap",
|
||
"description": "TRON swap proxy (SunSwap + FeeSwapRouter)"
|
||
},
|
||
{
|
||
"name": "BSC",
|
||
"description": "BSC swap proxy (PancakeSwap V2)"
|
||
},
|
||
{
|
||
"name": "Relay",
|
||
"description": "Cross-chain bridges (Relay Protocol)"
|
||
},
|
||
{
|
||
"name": "Prices",
|
||
"description": "USD-цены (CoinGecko + KeyDB cache 5 мин)"
|
||
}
|
||
],
|
||
"components": {
|
||
"securitySchemes": {
|
||
"bearerAuth": {
|
||
"type": "http",
|
||
"scheme": "bearer",
|
||
"bearerFormat": "JWT"
|
||
},
|
||
"cookieAuth": {
|
||
"type": "apiKey",
|
||
"in": "cookie",
|
||
"name": "access_token"
|
||
}
|
||
},
|
||
"schemas": {
|
||
"Error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": false
|
||
},
|
||
"error": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
},
|
||
"HealthResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"status": {
|
||
"type": "string",
|
||
"example": "ok"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Chain": {
|
||
"type": "string",
|
||
"enum": [
|
||
"ETH",
|
||
"BTC",
|
||
"SOL",
|
||
"TRX",
|
||
"BSC"
|
||
]
|
||
},
|
||
"Wallet": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
},
|
||
"address": {
|
||
"type": "string"
|
||
},
|
||
"derivationPath": {
|
||
"type": "string",
|
||
"description": "BIP32 path"
|
||
}
|
||
}
|
||
},
|
||
"WalletsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/Wallet"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"MnemonicResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"mnemonic": {
|
||
"type": "string",
|
||
"description": "BIP39 mnemonic (12 words)"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"TxBroadcastResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"txid": {
|
||
"type": "string",
|
||
"description": "Идентификатор отправленной транзакции"
|
||
},
|
||
"chain": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"FormattedAmount": {
|
||
"type": "object",
|
||
"description": "Сумма с метаданными формата + USD-цена. Поля `usdPrice`/`usdValue` всегда присутствуют, но могут быть `null` если symbol не в registry или upstream price oracle (CoinGecko) недоступен.",
|
||
"required": [
|
||
"raw",
|
||
"formatted",
|
||
"decimals",
|
||
"usdPrice",
|
||
"usdValue"
|
||
],
|
||
"properties": {
|
||
"raw": {
|
||
"type": "string",
|
||
"description": "Smallest units (wei/sat/sun/lamports), string-encoded BigInt",
|
||
"example": "1500000000000000000"
|
||
},
|
||
"formatted": {
|
||
"type": "string",
|
||
"description": "Human-readable decimal",
|
||
"example": "1.5"
|
||
},
|
||
"decimals": {
|
||
"type": "integer",
|
||
"description": "Decimals of the chain/token",
|
||
"example": 18
|
||
},
|
||
"usdPrice": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"description": "Цена 1 целой единицы в USD по данным CoinGecko (cache 5 мин, KeyDB). `null` если symbol не в registry или upstream недоступен.",
|
||
"example": 3210.45
|
||
},
|
||
"usdValue": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"description": "Совокупная стоимость holding'а в USD = `Number(formatted) × usdPrice`, округлено до 8 знаков. `null` если `usdPrice === null` или результат не finite.",
|
||
"example": 4815.675
|
||
}
|
||
}
|
||
},
|
||
"PricesResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"description": "Map symbol → { usd: price | null }. `null` если symbol whitelist'ed но upstream не вернул котировку.",
|
||
"additionalProperties": {
|
||
"type": "object",
|
||
"properties": {
|
||
"usd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 67432.12
|
||
}
|
||
}
|
||
},
|
||
"example": {
|
||
"BTC": {
|
||
"usd": 67432.12
|
||
},
|
||
"ETH": {
|
||
"usd": 3210.45
|
||
},
|
||
"USDT": {
|
||
"usd": 1
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"BalanceResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
},
|
||
"address": {
|
||
"type": "string"
|
||
},
|
||
"native": {
|
||
"$ref": "#/components/schemas/FormattedAmount"
|
||
},
|
||
"tokens": {
|
||
"type": "object",
|
||
"description": "Map symbol → FormattedAmount. Содержит все известные токены chain'а (ETH: USDT/USDC/DAI/WBTC/LINK/UNI, BSC: USDT/USDC/DOGE/WBNB/BUSD, TRX: USDT/USDC, SOL: 14 токенов)",
|
||
"additionalProperties": {
|
||
"$ref": "#/components/schemas/FormattedAmount"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"ChainPortfolio": {
|
||
"type": "object",
|
||
"description": "Балансе одной сети в составе portfolio. Расширяет BalanceResponse.data полями totalUsd, stale, lastUpdated, error.",
|
||
"properties": {
|
||
"chain": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
},
|
||
"address": {
|
||
"type": "string"
|
||
},
|
||
"totalUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"description": "Сумма usdValue по native + всем токенам chain'а. null если все цены недоступны."
|
||
},
|
||
"native": {
|
||
"$ref": "#/components/schemas/FormattedAmount"
|
||
},
|
||
"tokens": {
|
||
"type": "object",
|
||
"additionalProperties": {
|
||
"$ref": "#/components/schemas/FormattedAmount"
|
||
}
|
||
},
|
||
"stale": {
|
||
"type": "boolean",
|
||
"description": "true = данные из KeyDB cache (RPC chain'а упал в этом запросе)"
|
||
},
|
||
"lastUpdated": {
|
||
"type": "integer",
|
||
"description": "Unix ms когда данные были обновлены fresh fetch'ем"
|
||
},
|
||
"error": {
|
||
"type": "string",
|
||
"nullable": true,
|
||
"description": "Причина почему stale (только если stale=true)"
|
||
}
|
||
}
|
||
},
|
||
"PortfolioResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"required": [
|
||
"totalUsd",
|
||
"hasErrors",
|
||
"perChain"
|
||
],
|
||
"properties": {
|
||
"totalUsd": {
|
||
"type": "number",
|
||
"description": "Grand sum USD по всем сетям (rounded к 8 знакам). 0 если все сети упали и нет cache."
|
||
},
|
||
"hasErrors": {
|
||
"type": "boolean",
|
||
"description": "true если хотя бы одна сеть в stale/error состоянии"
|
||
},
|
||
"perChain": {
|
||
"type": "object",
|
||
"description": "Per-chain breakdown. Ключ = chain code (ETH/BSC/BTC/TRX/SOL). Значение null если ни fresh, ни cache недоступны.",
|
||
"properties": {
|
||
"ETH": {
|
||
"$ref": "#/components/schemas/ChainPortfolio",
|
||
"nullable": true
|
||
},
|
||
"BSC": {
|
||
"$ref": "#/components/schemas/ChainPortfolio",
|
||
"nullable": true
|
||
},
|
||
"BTC": {
|
||
"$ref": "#/components/schemas/ChainPortfolio",
|
||
"nullable": true
|
||
},
|
||
"TRX": {
|
||
"$ref": "#/components/schemas/ChainPortfolio",
|
||
"nullable": true
|
||
},
|
||
"SOL": {
|
||
"$ref": "#/components/schemas/ChainPortfolio",
|
||
"nullable": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Transaction": {
|
||
"type": "object",
|
||
"properties": {
|
||
"txid": {
|
||
"type": "string"
|
||
},
|
||
"timestamp": {
|
||
"type": "integer",
|
||
"nullable": true,
|
||
"description": "Unix seconds"
|
||
},
|
||
"direction": {
|
||
"type": "string",
|
||
"enum": [
|
||
"in",
|
||
"out",
|
||
"self"
|
||
]
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"nullable": true
|
||
},
|
||
"token": {
|
||
"type": "string",
|
||
"nullable": true
|
||
},
|
||
"from": {
|
||
"type": "string",
|
||
"nullable": true
|
||
},
|
||
"to": {
|
||
"type": "string",
|
||
"nullable": true
|
||
}
|
||
}
|
||
},
|
||
"TransactionsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean"
|
||
},
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/Transaction"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"SendRequest": {
|
||
"type": "object",
|
||
"required": [
|
||
"to"
|
||
],
|
||
"properties": {
|
||
"to": {
|
||
"type": "string",
|
||
"description": "Recipient address"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"description": "Amount в smallest units (wei для EVM, lamports для SOL, sat для BTC, sun для TRX)"
|
||
},
|
||
"token": {
|
||
"type": "string",
|
||
"nullable": true,
|
||
"description": "USDT для TRC20/ERC20/BEP20. Без token = native."
|
||
},
|
||
"feeTier": {
|
||
"type": "string",
|
||
"enum": [
|
||
"slow",
|
||
"normal",
|
||
"fast"
|
||
],
|
||
"nullable": true,
|
||
"description": "Default 'normal'. ETH/BSC: eth_feeHistory p25/p50/p75 priority. BTC: blockstream targets 144/6/1 блок. TRX/SOL: игнорится."
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
}
|
||
}
|
||
},
|
||
"FeeQuote": {
|
||
"type": "object",
|
||
"properties": {
|
||
"maxFeePerGas": {
|
||
"type": "string",
|
||
"description": "wei (decimal string)"
|
||
},
|
||
"maxPriorityFeePerGas": {
|
||
"type": "string",
|
||
"description": "wei (decimal string)"
|
||
},
|
||
"gweiTotal": {
|
||
"type": "number"
|
||
},
|
||
"gweiPriority": {
|
||
"type": "number"
|
||
}
|
||
}
|
||
},
|
||
"FeeTiers": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": {
|
||
"type": "string",
|
||
"enum": [
|
||
"ETH",
|
||
"BSC"
|
||
]
|
||
},
|
||
"baseFeeGwei": {
|
||
"type": "number",
|
||
"description": "Из feeHistory.baseFeePerGas (на BSC ~0)"
|
||
},
|
||
"slow": {
|
||
"$ref": "#/components/schemas/FeeQuote"
|
||
},
|
||
"normal": {
|
||
"$ref": "#/components/schemas/FeeQuote"
|
||
},
|
||
"fast": {
|
||
"$ref": "#/components/schemas/FeeQuote"
|
||
}
|
||
}
|
||
},
|
||
"SignRawEvmTxRequest": {
|
||
"type": "object",
|
||
"required": [
|
||
"to",
|
||
"data",
|
||
"value",
|
||
"chainId",
|
||
"gas",
|
||
"maxFeePerGas",
|
||
"maxPriorityFeePerGas"
|
||
],
|
||
"properties": {
|
||
"to": {
|
||
"type": "string",
|
||
"description": "0x-prefixed 40-hex (контракт или EOA)"
|
||
},
|
||
"data": {
|
||
"type": "string",
|
||
"description": "Calldata 0x-hex (может быть пустым 0x для native send)"
|
||
},
|
||
"value": {
|
||
"type": "string",
|
||
"description": "wei (decimal string)"
|
||
},
|
||
"chainId": {
|
||
"type": "integer",
|
||
"description": "1 (ETH) или 56 (BSC) — должен совпадать с path :chain"
|
||
},
|
||
"gas": {
|
||
"type": "string",
|
||
"description": "gasLimit в decimal"
|
||
},
|
||
"maxFeePerGas": {
|
||
"type": "string",
|
||
"description": "wei"
|
||
},
|
||
"maxPriorityFeePerGas": {
|
||
"type": "string",
|
||
"description": "wei"
|
||
},
|
||
"feeTier": {
|
||
"type": "string",
|
||
"enum": [
|
||
"slow",
|
||
"normal",
|
||
"fast"
|
||
],
|
||
"nullable": true,
|
||
"description": "Если задан → server переопределит maxFeePerGas/maxPriorityFeePerGas актуальным из eth_feeHistory (полезно если quote от Relay устарел)."
|
||
}
|
||
}
|
||
},
|
||
"SwapQuoteResponse": {
|
||
"type": "object",
|
||
"required": [
|
||
"quoteId",
|
||
"expiresIn",
|
||
"expiresAt",
|
||
"chain",
|
||
"amountIn",
|
||
"amountInFormatted",
|
||
"expectedOut",
|
||
"expectedOutFormatted",
|
||
"minOut",
|
||
"minOutFormatted",
|
||
"slippageBps",
|
||
"fees",
|
||
"route",
|
||
"approveRequired"
|
||
],
|
||
"properties": {
|
||
"quoteId": {
|
||
"type": "string",
|
||
"example": "q_01KRKD8GA4XZJ5W4E7VFT2N9M3",
|
||
"description": "Opaque ULID. Pass to POST /:chain/swap для execute."
|
||
},
|
||
"expiresIn": {
|
||
"type": "integer",
|
||
"example": 30,
|
||
"description": "Seconds until cache eviction"
|
||
},
|
||
"expiresAt": {
|
||
"type": "integer",
|
||
"format": "int64",
|
||
"example": 1715600030000,
|
||
"description": "Unix ms when quote expires"
|
||
},
|
||
"chain": {
|
||
"type": "string",
|
||
"enum": [
|
||
"BSC",
|
||
"TRX",
|
||
"SOL"
|
||
]
|
||
},
|
||
"amountIn": {
|
||
"type": "string",
|
||
"example": "100000000000000000",
|
||
"description": "Smallest units"
|
||
},
|
||
"amountInFormatted": {
|
||
"type": "string",
|
||
"example": "0.1"
|
||
},
|
||
"amountInUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.1
|
||
},
|
||
"expectedOut": {
|
||
"type": "string",
|
||
"example": "164821000000000",
|
||
"description": "Mid-market quote (smallest units)"
|
||
},
|
||
"expectedOutFormatted": {
|
||
"type": "string",
|
||
"example": "0.000164821"
|
||
},
|
||
"expectedOutUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.0991
|
||
},
|
||
"minOut": {
|
||
"type": "string",
|
||
"example": "163996895000000",
|
||
"description": "expectedOut × (10000-slippageBps) / 10000"
|
||
},
|
||
"minOutFormatted": {
|
||
"type": "string",
|
||
"example": "0.000163996"
|
||
},
|
||
"slippageBps": {
|
||
"type": "integer",
|
||
"example": 50,
|
||
"description": "Slippage в basis points (50 = 0.5%)"
|
||
},
|
||
"priceImpactPct": {
|
||
"type": "string",
|
||
"nullable": true,
|
||
"example": "0.06",
|
||
"description": "SOL only (Jupiter exposes это поле)"
|
||
},
|
||
"fees": {
|
||
"type": "object",
|
||
"properties": {
|
||
"network": {
|
||
"type": "object",
|
||
"properties": {
|
||
"asset": {
|
||
"type": "string",
|
||
"example": "BNB"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"example": "65000000000000"
|
||
},
|
||
"amountFormatted": {
|
||
"type": "string",
|
||
"example": "0.000065"
|
||
},
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.04
|
||
}
|
||
}
|
||
},
|
||
"total": {
|
||
"type": "object",
|
||
"properties": {
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.04
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"route": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
},
|
||
"example": [
|
||
"USDT",
|
||
"BNB"
|
||
],
|
||
"description": "Symbol path (для BSC/TRX) или DEX labels (для SOL Jupiter)."
|
||
},
|
||
"approveRequired": {
|
||
"type": "boolean",
|
||
"example": true,
|
||
"description": "BSC: token-to-anything требует approve(amount). TRX: USDT→TRX requires approve(infinite)."
|
||
},
|
||
"estimatedGasUnits": {
|
||
"type": "string",
|
||
"nullable": true,
|
||
"example": "300000",
|
||
"description": "EVM gas units (BSC). Null для TRX/SOL."
|
||
}
|
||
}
|
||
},
|
||
"SendCostEstimateResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": {
|
||
"type": "string",
|
||
"example": "BSC"
|
||
},
|
||
"fee": {
|
||
"type": "object",
|
||
"properties": {
|
||
"asset": {
|
||
"type": "string",
|
||
"example": "BNB"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"example": "65000000000000"
|
||
},
|
||
"amountFormatted": {
|
||
"type": "string",
|
||
"example": "0.000065"
|
||
},
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.04
|
||
}
|
||
}
|
||
},
|
||
"total": {
|
||
"type": "object",
|
||
"properties": {
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true,
|
||
"example": 0.04
|
||
}
|
||
}
|
||
},
|
||
"breakdown": {
|
||
"type": "object",
|
||
"additionalProperties": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"SwapCostEstimateResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": {
|
||
"type": "string",
|
||
"example": "BSC"
|
||
},
|
||
"fee": {
|
||
"type": "object",
|
||
"properties": {
|
||
"asset": {
|
||
"type": "string",
|
||
"example": "BNB"
|
||
},
|
||
"amount": {
|
||
"type": "string"
|
||
},
|
||
"amountFormatted": {
|
||
"type": "string"
|
||
},
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true
|
||
}
|
||
}
|
||
},
|
||
"total": {
|
||
"type": "object",
|
||
"properties": {
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true
|
||
}
|
||
}
|
||
},
|
||
"route": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
},
|
||
"example": [
|
||
"USDT",
|
||
"BNB"
|
||
]
|
||
},
|
||
"approveRequired": {
|
||
"type": "boolean"
|
||
},
|
||
"estimatedGasUnits": {
|
||
"type": "string",
|
||
"nullable": true
|
||
},
|
||
"slippageBps": {
|
||
"type": "integer"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"BridgeCostEstimateResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"fees": {
|
||
"type": "object",
|
||
"properties": {
|
||
"gas": {
|
||
"type": "object",
|
||
"nullable": true,
|
||
"additionalProperties": true
|
||
},
|
||
"relayer": {
|
||
"type": "object",
|
||
"nullable": true,
|
||
"additionalProperties": true
|
||
},
|
||
"app": {
|
||
"type": "object",
|
||
"nullable": true,
|
||
"additionalProperties": true
|
||
},
|
||
"total": {
|
||
"type": "object",
|
||
"properties": {
|
||
"amountUsd": {
|
||
"type": "number",
|
||
"nullable": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"rate": {
|
||
"type": "string",
|
||
"nullable": true
|
||
},
|
||
"priceImpactPct": {
|
||
"type": "string",
|
||
"nullable": true
|
||
},
|
||
"priceImpactUsd": {
|
||
"type": "number",
|
||
"nullable": true
|
||
},
|
||
"timeEstimate": {
|
||
"type": "integer",
|
||
"nullable": true,
|
||
"description": "Estimate в секундах"
|
||
},
|
||
"currencyIn": {
|
||
"type": "object",
|
||
"nullable": true,
|
||
"additionalProperties": true
|
||
},
|
||
"currencyOut": {
|
||
"type": "object",
|
||
"nullable": true,
|
||
"additionalProperties": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"security": [
|
||
{
|
||
"cookieAuth": []
|
||
},
|
||
{
|
||
"bearerAuth": []
|
||
}
|
||
],
|
||
"paths": {
|
||
"/health": {
|
||
"get": {
|
||
"summary": "Liveness check",
|
||
"tags": [
|
||
"System"
|
||
],
|
||
"security": [],
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HealthResponse"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets": {
|
||
"get": {
|
||
"summary": "Get all wallets of authenticated user",
|
||
"tags": [
|
||
"Wallets"
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "List of wallets",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/WalletsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Error"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/create": {
|
||
"post": {
|
||
"summary": "Создать custodial-кошелёк (server-side mnemonic)",
|
||
"description": "**Тело запроса не требуется.** Сервер генерит BIP39 mnemonic (12 слов), деривит адреса для 5 chains (BIP44: ETH m/44'/60'/0'/0/0, BTC m/84'/0'/0'/0/0, TRX m/44'/195'/0'/0/0, SOL m/44'/501'/0'/0', BSC = ETH path), шифрует mnemonic AES-256-GCM (master-key из HashiCorp Vault) и атомарно сохраняет. **Возвращает ТОЛЬКО адреса** — mnemonic клиенту не отдаётся. Чтобы потом увидеть seed — отдельный endpoint POST /wallets/mnemonic/reveal. Идемпотентность: 409 если у юзера уже есть кошелёк.",
|
||
"tags": [
|
||
"Wallets"
|
||
],
|
||
"responses": {
|
||
"201": {
|
||
"description": "Wallet created (returns addresses only)",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/WalletsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated"
|
||
},
|
||
"409": {
|
||
"description": "Wallet already exists",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"503": {
|
||
"description": "Crypto service not ready"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/mnemonic/reveal": {
|
||
"post": {
|
||
"summary": "Раскрыть mnemonic (settings-screen)",
|
||
"description": "Расшифровывает и возвращает 12-словную BIP39 мнемонику юзера. POST + CSRF + body-confirmation. Rate-limit 5/час per-user. Каждый запрос пишется в audit-log.",
|
||
"tags": [
|
||
"Wallets"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": [
|
||
"confirm"
|
||
],
|
||
"properties": {
|
||
"confirm": {
|
||
"type": "string",
|
||
"enum": [
|
||
"I_UNDERSTAND_SEED_IS_SECRET"
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Mnemonic revealed",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/MnemonicResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Missing/invalid confirm token"
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated"
|
||
},
|
||
"404": {
|
||
"description": "Wallet not created yet"
|
||
},
|
||
"429": {
|
||
"description": "Rate limit (5/hour) exceeded"
|
||
},
|
||
"503": {
|
||
"description": "Crypto service not ready"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/portfolio": {
|
||
"get": {
|
||
"summary": "Aggregate balance по всем 5 сетям (общий баланс)",
|
||
"description": "Возвращает баланс всех 5 сетей + grand total USD в одном запросе. Параллельно дёргает `getBalance(chain, address)` для ETH/BSC/BTC/TRX/SOL. Каждая успешная сеть кэшируется в KeyDB (TTL 1 час). Если какая-то сеть упала (RPC timeout / network error) — возвращает последний кэшированный balance этой сети с пометкой `stale:true` и описанием `error`. UI всегда показывает осмысленный portfolio, не падая на 0 при transient outage.\n\n**Поведение при ошибках:**\n- 1 сеть упала + есть cache → totalUsd считается с cached + `hasErrors:true`\n- 1 сеть упала + НЕТ cache → perChain[chain]=null, остальное fresh\n- все 5 упали + нет cache → totalUsd=0, hasErrors=true, perChain[*]=null\n- 502 возвращается только при unrecoverable controller exception",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Aggregate portfolio",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/PortfolioResponse"
|
||
},
|
||
"example": {
|
||
"success": true,
|
||
"data": {
|
||
"totalUsd": 12.34,
|
||
"hasErrors": false,
|
||
"perChain": {
|
||
"ETH": {
|
||
"chain": "ETH",
|
||
"address": "0x9dB8Af1B...",
|
||
"totalUsd": 4.81,
|
||
"native": {
|
||
"raw": "1500000000000000000",
|
||
"formatted": "1.5",
|
||
"decimals": 18,
|
||
"usdPrice": 3210.45,
|
||
"usdValue": 4.81
|
||
},
|
||
"tokens": {},
|
||
"stale": false,
|
||
"lastUpdated": 1715600000000
|
||
},
|
||
"BSC": {
|
||
"chain": "BSC",
|
||
"address": "0x9dB8Af1B...",
|
||
"totalUsd": 2.1,
|
||
"native": {
|
||
"...": "..."
|
||
},
|
||
"tokens": {
|
||
"USDT": {
|
||
"...": "..."
|
||
}
|
||
},
|
||
"stale": false,
|
||
"lastUpdated": 1715600000000
|
||
},
|
||
"BTC": {
|
||
"chain": "BTC",
|
||
"address": "bc1q...",
|
||
"totalUsd": 3.96,
|
||
"native": {
|
||
"...": "..."
|
||
},
|
||
"stale": false,
|
||
"lastUpdated": 1715600000000
|
||
},
|
||
"TRX": {
|
||
"chain": "TRX",
|
||
"address": "T...",
|
||
"totalUsd": 0.49,
|
||
"native": {
|
||
"...": "..."
|
||
},
|
||
"tokens": {
|
||
"USDT": {
|
||
"...": "..."
|
||
}
|
||
},
|
||
"stale": true,
|
||
"lastUpdated": 1715500000000,
|
||
"error": "TronGrid timeout"
|
||
},
|
||
"SOL": {
|
||
"chain": "SOL",
|
||
"address": "3PJC...",
|
||
"totalUsd": 0.98,
|
||
"native": {
|
||
"...": "..."
|
||
},
|
||
"tokens": {},
|
||
"stale": false,
|
||
"lastUpdated": 1715600000000
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated"
|
||
},
|
||
"404": {
|
||
"description": "No wallets created (вызови POST /wallets/create сначала)"
|
||
},
|
||
"502": {
|
||
"description": "Portfolio fetch error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/balance": {
|
||
"get": {
|
||
"summary": "Balance for user wallet in chain (с USD-ценами)",
|
||
"description": "Возвращает количество и USD-стоимость для native монеты + всех известных токенов сети. Каждый `FormattedAmount` содержит `raw` (smallest units), `formatted` (human-readable), `decimals`, `usdPrice` (цена 1 единицы), `usdValue` (стоимость holding'а). Цены — CoinGecko с 5-минутным KeyDB-кэшем. Если упал price oracle — `usdPrice`/`usdValue` = `null`, но количества всё равно возвращаются.\n\n**Пример curl:**\n```\ncurl -H \"Authorization: Bearer $JWT\" https://api.example.com/api/wallets/ETH/balance\n```",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Balance + USD prices",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/BalanceResponse"
|
||
},
|
||
"example": {
|
||
"success": true,
|
||
"data": {
|
||
"chain": "ETH",
|
||
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f4F45A",
|
||
"native": {
|
||
"raw": "1500000000000000000",
|
||
"formatted": "1.5",
|
||
"decimals": 18,
|
||
"usdPrice": 3210.45,
|
||
"usdValue": 4815.675
|
||
},
|
||
"tokens": {
|
||
"USDT": {
|
||
"raw": "1000000",
|
||
"formatted": "1",
|
||
"decimals": 6,
|
||
"usdPrice": 1,
|
||
"usdValue": 1
|
||
},
|
||
"USDC": {
|
||
"raw": "0",
|
||
"formatted": "0",
|
||
"decimals": 6,
|
||
"usdPrice": 0.9999,
|
||
"usdValue": 0
|
||
},
|
||
"DAI": {
|
||
"raw": "0",
|
||
"formatted": "0",
|
||
"decimals": 18,
|
||
"usdPrice": 0.9998,
|
||
"usdValue": 0
|
||
},
|
||
"WBTC": {
|
||
"raw": "0",
|
||
"formatted": "0",
|
||
"decimals": 8,
|
||
"usdPrice": 67432.12,
|
||
"usdValue": 0
|
||
},
|
||
"LINK": {
|
||
"raw": "0",
|
||
"formatted": "0",
|
||
"decimals": 18,
|
||
"usdPrice": 14.32,
|
||
"usdValue": 0
|
||
},
|
||
"UNI": {
|
||
"raw": "0",
|
||
"formatted": "0",
|
||
"decimals": 18,
|
||
"usdPrice": 8.41,
|
||
"usdValue": 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated"
|
||
},
|
||
"404": {
|
||
"description": "Wallet for this chain not found"
|
||
},
|
||
"502": {
|
||
"description": "Upstream RPC error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/transactions": {
|
||
"get": {
|
||
"summary": "Transaction history for user wallet in chain",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"schema": {
|
||
"type": "integer",
|
||
"default": 20,
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "List of transactions",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/TransactionsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"404": {
|
||
"description": "Wallet for this chain not found"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/send": {
|
||
"post": {
|
||
"summary": "Custodial send: server signs + broadcasts",
|
||
"description": "Юзер на клиенте жмёт 'подтвердить' → клиент шлёт {to, amount, token?, feeTier?}. Сервер расшифровывает мнемонику, деривит chain privkey, подписывает, broadcast'ит. Возвращает txid. Защита: TRX MITM check, EVM gas cap 500 gwei, SOL confirmTransaction, BTC timeout + safety multiplier. На ETH/BSC gas теперь берётся из eth_feeHistory (slow/normal/fast).",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SendRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Broadcast successful",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/TxBroadcastResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Invalid input (incl. invalid feeTier)"
|
||
},
|
||
"404": {
|
||
"description": "Wallet/mnemonic not found"
|
||
},
|
||
"502": {
|
||
"description": "Broadcast failed (insufficient balance / RPC error / unsupported)"
|
||
},
|
||
"503": {
|
||
"description": "Crypto service not ready"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/gas-suggestions": {
|
||
"get": {
|
||
"summary": "EVM gas oracle (slow/normal/fast)",
|
||
"description": "Парсит fees через `eth_feeHistory` (последние 5 блоков, percentile p25/p50/p75 priority tips). Возвращает 3 тира с maxFeePerGas/maxPriorityFeePerGas в wei + gwei для display. Floor: ETH=0.5 gwei, BSC=0.05 gwei (защита от dust). Cap: 500 gwei. Только ETH и BSC.",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": [
|
||
"ETH",
|
||
"BSC"
|
||
]
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Fee tiers",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean"
|
||
},
|
||
"data": {
|
||
"$ref": "#/components/schemas/FeeTiers"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Non-EVM chain"
|
||
},
|
||
"502": {
|
||
"description": "Upstream RPC error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/sign-raw-evm-tx": {
|
||
"post": {
|
||
"summary": "Custodial sign + broadcast arbitrary EVM tx (Relay bridge)",
|
||
"description": "Подписывает unsigned EVM tx из Relay /execute response. Policy: `to` ДОЛЖЕН быть в Relay router allowlist; selector blacklist (approve/permit/setApprovalForAll). Для DEX swap'ов используй `/wallets/{chain}/swap` — там chained custodial без этих ограничений.",
|
||
"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/SOL/sign-and-broadcast-tx": {
|
||
"post": {
|
||
"summary": "Custodial sign + broadcast Solana tx (2 формата body)",
|
||
"description": "Custodial sign + broadcast Solana tx. **Два формата body:**\n\n(a) `{ transaction: '<base64>' }` — pre-built VersionedTransaction (Jupiter swap, Relay serialized).\n\n(b) `{ instructions[], addressLookupTableAddresses[]? }` — Relay SOL bridge instructions. Server compile'ит `TransactionMessage` → `VersionedTransaction` с `feePayer = user`.\n\n**Security:** валидирует что каждый `isSigner=true` key равен derived user SOL pubkey, resolve LUTs через RPC, partial-sign keypair'ом, broadcast, confirm.",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"oneOf": [
|
||
{
|
||
"title": "Pre-built VersionedTransaction (Jupiter / Relay serialized)",
|
||
"type": "object",
|
||
"required": [
|
||
"transaction"
|
||
],
|
||
"properties": {
|
||
"transaction": {
|
||
"type": "string",
|
||
"description": "Base64-encoded VersionedTransaction (max ~8KB)"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "Relay-style instructions (для SOL bridge)",
|
||
"type": "object",
|
||
"required": [
|
||
"instructions"
|
||
],
|
||
"properties": {
|
||
"instructions": {
|
||
"type": "array",
|
||
"description": "Array из {programId, keys, data}. Server compile'ит TransactionMessage → VersionedTransaction с feePayer=user.",
|
||
"items": {
|
||
"type": "object",
|
||
"required": [
|
||
"programId",
|
||
"keys",
|
||
"data"
|
||
],
|
||
"properties": {
|
||
"programId": {
|
||
"type": "string",
|
||
"description": "SPL program pubkey (base58)"
|
||
},
|
||
"keys": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"required": [
|
||
"pubkey",
|
||
"isSigner",
|
||
"isWritable"
|
||
],
|
||
"properties": {
|
||
"pubkey": {
|
||
"type": "string",
|
||
"description": "Account pubkey (base58)"
|
||
},
|
||
"isSigner": {
|
||
"type": "boolean",
|
||
"description": "Если true — pubkey ДОЛЖЕН равняться user'у (anti-drain)"
|
||
},
|
||
"isWritable": {
|
||
"type": "boolean"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"data": {
|
||
"type": "string",
|
||
"description": "Instruction data: hex (без префикса) или base64 — autodetect"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"addressLookupTableAddresses": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
},
|
||
"description": "Опционально. SPL Address Lookup Table accounts которые server разрезолвит через SOL RPC (getAddressLookupTable)."
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Signed and broadcast",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean"
|
||
},
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"signature": {
|
||
"type": "string"
|
||
},
|
||
"chain": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Invalid body / feePayer mismatch / signer-key mismatch / malformed instruction"
|
||
},
|
||
"404": {
|
||
"description": "SOL wallet/mnemonic not found"
|
||
},
|
||
"502": {
|
||
"description": "Sign or broadcast failed (включая RPC ошибки / blockhash expired / on-chain revert)"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/btc/utxos/{address}": {
|
||
"get": {
|
||
"summary": "Confirmed UTXOs for Bitcoin address",
|
||
"tags": [
|
||
"BTC"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "address",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "UTXOs"
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/btc/fee-estimates": {
|
||
"get": {
|
||
"summary": "Bitcoin fee estimates (sat/vB)",
|
||
"tags": [
|
||
"BTC"
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "fast/normal/slow"
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/btc/broadcast": {
|
||
"post": {
|
||
"summary": "Broadcast raw signed Bitcoin tx",
|
||
"tags": [
|
||
"BTC"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": [
|
||
"hex"
|
||
],
|
||
"properties": {
|
||
"hex": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "txid"
|
||
},
|
||
"400": {
|
||
"description": "Invalid hex"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/tron/account/{address}": {
|
||
"get": {
|
||
"summary": "TRON account info + USDT (TRC20) balance",
|
||
"tags": [
|
||
"TRON"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "address",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Account data"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/tron/createtransaction": {
|
||
"post": {
|
||
"summary": "Build unsigned TRX transfer",
|
||
"tags": [
|
||
"TRON"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": [
|
||
"owner_address",
|
||
"to_address",
|
||
"amount"
|
||
],
|
||
"properties": {
|
||
"owner_address": {
|
||
"type": "string"
|
||
},
|
||
"to_address": {
|
||
"type": "string"
|
||
},
|
||
"amount": {
|
||
"type": "integer"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Unsigned tx"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/tron/triggersmartcontract": {
|
||
"post": {
|
||
"summary": "Build unsigned TRC20 contract call",
|
||
"tags": [
|
||
"TRON"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Unsigned tx"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/tron/broadcasttransaction": {
|
||
"post": {
|
||
"summary": "Broadcast signed TRON tx",
|
||
"tags": [
|
||
"TRON"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Result"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/relay/quote": {
|
||
"post": {
|
||
"summary": "Relay bridge quote (POST с JSON body)",
|
||
"description": "Прокси к https://api.relay.link/quote. Параметры в body: user, recipient, originChainId, destinationChainId, originCurrency, destinationCurrency, amount (smallest units), tradeType (EXACT_INPUT|EXACT_OUTPUT).",
|
||
"tags": [
|
||
"Relay"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": [
|
||
"user",
|
||
"originChainId",
|
||
"destinationChainId",
|
||
"originCurrency",
|
||
"destinationCurrency",
|
||
"amount",
|
||
"tradeType"
|
||
],
|
||
"properties": {
|
||
"user": {
|
||
"type": "string",
|
||
"description": "Sender address (0x.. / T.. / SOL pubkey)"
|
||
},
|
||
"recipient": {
|
||
"type": "string",
|
||
"description": "Обычно тот же что user"
|
||
},
|
||
"originChainId": {
|
||
"type": "integer",
|
||
"description": "1=ETH, 56=BSC, 728126428=TRON, 792703809=SOL"
|
||
},
|
||
"destinationChainId": {
|
||
"type": "integer"
|
||
},
|
||
"originCurrency": {
|
||
"type": "string",
|
||
"description": "Token address (EVM: 0x.., SOL: mint, TRX: contract или 'TRX')"
|
||
},
|
||
"destinationCurrency": {
|
||
"type": "string"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"description": "smallest units"
|
||
},
|
||
"tradeType": {
|
||
"type": "string",
|
||
"enum": [
|
||
"EXACT_INPUT",
|
||
"EXACT_OUTPUT"
|
||
]
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Quote с steps[], fees, details, breakdown"
|
||
},
|
||
"502": {
|
||
"description": "Relay upstream error (приложен upstream JSON для деталей)"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/relay/intents/status/v3": {
|
||
"get": {
|
||
"summary": "Relay intent status",
|
||
"tags": [
|
||
"Relay"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "requestId",
|
||
"in": "query",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Из quote/execute response"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Status"
|
||
},
|
||
"502": {
|
||
"description": "Relay upstream error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/relay/execute/{action}": {
|
||
"post": {
|
||
"summary": "Relay execute (swap | bridge)",
|
||
"description": "Принимает ТОТ ЖЕ payload что и /quote и возвращает unsigned tx в steps[].items[].data. Эту tx надо потом подписать (для ETH/BSC — через /wallets/{chain}/sign-raw-evm-tx) и broadcast'нуть. Action whitelist: swap, bridge.",
|
||
"tags": [
|
||
"Relay"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "action",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": [
|
||
"swap",
|
||
"bridge"
|
||
]
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"description": "Same as /relay/quote body"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "steps[] with unsigned tx + fees + details"
|
||
},
|
||
"502": {
|
||
"description": "Relay upstream error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/prices": {
|
||
"get": {
|
||
"summary": "USD-цены для списка символов",
|
||
"description": "Возвращает котировки USD для указанных символов (max 50). Символы должны быть из реестра поддерживаемых токенов (см. tag описание сетей в /wallets/{chain}/balance). Источник — CoinGecko free API, кэшируется в KeyDB 5 минут.\n\n**Resolution:**\n- Native символ совпадающий с chain code (BTC/ETH/BSC/TRX/SOL) → используется native CoinGecko id.\n- Иначе: ищется в реестре сети из `chain` query param.\n- Если `chain` не задан → fallback порядок ETH → BSC → SOL → TRX → BTC. Первый matched chain wins.\n\n**Безопасность:** symbols whitelisted, никакого user-input в URL CoinGecko (защита от SSRF). Max 50 символов на запрос. Auth обязательна (JWT Bearer или cookie).\n\n**Пример curl:**\n```\ncurl -H \"Authorization: Bearer $JWT\" \"https://api.example.com/api/prices?symbols=BTC,ETH,USDT,SOL,BONK\"\n```",
|
||
"tags": [
|
||
"Prices"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "symbols",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Comma-separated список символов (макс 50). Каждый — `[A-Z0-9]{1,16}`. Только символы из registry: BTC, ETH, BSC, TRX, SOL (native) + USDT, USDC, DAI, WBTC, LINK, UNI, DOGE, WBNB, BUSD, PUMP, JUP, WIF, POPCAT, TRUMP, PYTH, JTO, W, BONK, ORCA, PENGU, RAY.",
|
||
"schema": {
|
||
"type": "string",
|
||
"example": "BTC,ETH,USDT"
|
||
}
|
||
},
|
||
{
|
||
"name": "chain",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Опционально: для disambiguation если symbol присутствует в нескольких сетях (USDT/USDC). Если не задан — fallback порядок: ETH → BSC → SOL → TRX → BTC.",
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Chain"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "USD prices",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/PricesResponse"
|
||
},
|
||
"example": {
|
||
"success": true,
|
||
"data": {
|
||
"BTC": {
|
||
"usd": 67432.12
|
||
},
|
||
"ETH": {
|
||
"usd": 3210.45
|
||
},
|
||
"USDT": {
|
||
"usd": 1
|
||
},
|
||
"SOL": {
|
||
"usd": 142.88
|
||
},
|
||
"BONK": {
|
||
"usd": 0.00002145
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Validation error: пустой/слишком большой/невалидный список, неизвестный chain или unknown symbol",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"401": {
|
||
"description": "Not authenticated",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"429": {
|
||
"description": "Rate limit exceeded"
|
||
},
|
||
"502": {
|
||
"description": "Upstream price oracle error (CoinGecko)",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/Error"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/swap/quote": {
|
||
"post": {
|
||
"summary": "Swap preview / quote (без broadcast)",
|
||
"description": "Считает expected output, slippage, network fee, route, approveRequired для custodial swap.\n\n**Read-only** — НЕ broadcast'ит ничего, mnemonic не расшифровывается.\n\nВозвращает `quoteId` (ULID) + preview-снимок. Юзер показывает preview, жмёт Confirm → клиент шлёт `POST /:chain/swap` с `{quoteId}`. Quote живёт **30 секунд** в KeyDB — после execute удаляется (anti-replay).\n\n**Use cases:**\n- Live debounced quote при вводе amount в UI.\n- \"How much would I get?\" — без обязательства execute.\n- Защита от MEV-frontrun: `minOut` зафиксирован между preview и confirm.",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": [
|
||
"BSC",
|
||
"TRX",
|
||
"SOL"
|
||
]
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"title": "BSC/TRX quote (symbols)",
|
||
"required": [
|
||
"from",
|
||
"to",
|
||
"amount"
|
||
],
|
||
"properties": {
|
||
"from": {
|
||
"type": "string",
|
||
"example": "USDT",
|
||
"description": "BSC: BNB|USDT|USDC|DOGE|WBNB|BUSD; TRX: TRX|USDT"
|
||
},
|
||
"to": {
|
||
"type": "string",
|
||
"example": "BNB"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"example": "100000000000000000",
|
||
"description": "smallest units"
|
||
},
|
||
"slippageBps": {
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 1000,
|
||
"default": 50
|
||
},
|
||
"feeTier": {
|
||
"type": "string",
|
||
"enum": [
|
||
"slow",
|
||
"normal",
|
||
"fast"
|
||
],
|
||
"description": "BSC only"
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"type": "object",
|
||
"title": "SOL quote (mints)",
|
||
"required": [
|
||
"inputMint",
|
||
"outputMint",
|
||
"amount"
|
||
],
|
||
"properties": {
|
||
"inputMint": {
|
||
"type": "string",
|
||
"example": "So11111111111111111111111111111111111111112"
|
||
},
|
||
"outputMint": {
|
||
"type": "string",
|
||
"example": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"example": "1000000",
|
||
"description": "smallest units"
|
||
},
|
||
"slippageBps": {
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 1000,
|
||
"default": 50
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Quote preview",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": {
|
||
"type": "boolean",
|
||
"example": true
|
||
},
|
||
"data": {
|
||
"$ref": "#/components/schemas/SwapQuoteResponse"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Validation error / unsupported pair"
|
||
},
|
||
"404": {
|
||
"description": "Wallet not found"
|
||
},
|
||
"502": {
|
||
"description": "Upstream RPC / quote failed (no liquidity, etc.)"
|
||
},
|
||
"503": {
|
||
"description": "Quote cache unavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/send/cost-estimate": {
|
||
"post": {
|
||
"summary": "Estimate USD cost of a /send call (read-only, без broadcast)",
|
||
"description": "Read-only USD-оценка сколько будет стоить broadcast tx (gas/network fee).\n\nНе дёргает mnemonic, не резервирует idempotency cache, не делает RPC broadcast.\n\nBody — те же поля что у /send МИНУС `to`. Можно прислать `amount` (smallest units, legacy) ИЛИ `amountHuman` (\"0.01\").",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": [
|
||
"ETH",
|
||
"BSC",
|
||
"BTC",
|
||
"TRX",
|
||
"SOL"
|
||
]
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"token": {
|
||
"type": "string",
|
||
"description": "Token symbol (USDT, USDC, ...). Пусто = native."
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"description": "Smallest units (legacy)"
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
},
|
||
"feeTier": {
|
||
"type": "string",
|
||
"enum": [
|
||
"slow",
|
||
"normal",
|
||
"fast"
|
||
],
|
||
"default": "normal",
|
||
"description": "EVM only (ETH/BSC); ignored для TRX/SOL/BTC"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Cost estimate",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SendCostEstimateResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Validation error"
|
||
},
|
||
"502": {
|
||
"description": "Gas oracle / price oracle unavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/wallets/{chain}/swap/cost-estimate": {
|
||
"post": {
|
||
"summary": "Estimate USD cost of a swap (без cache, без quoteId)",
|
||
"description": "Те же поля что у /swap/quote, но возвращает ТОЛЬКО fee + route + approveRequired (без quoteId/expiry/cache).\n\nIdempotent — можно вызывать много раз. Используется для отображения USD-цены свапа в UI ДО того как юзер решит подтвердить.",
|
||
"tags": [
|
||
"Wallet Ops"
|
||
],
|
||
"parameters": [
|
||
{
|
||
"name": "chain",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": [
|
||
"BSC",
|
||
"TRX",
|
||
"SOL"
|
||
]
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"title": "BSC/TRX",
|
||
"properties": {
|
||
"from": {
|
||
"type": "string"
|
||
},
|
||
"to": {
|
||
"type": "string"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"description": "Smallest units (legacy)"
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
},
|
||
"slippageBps": {
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 1000,
|
||
"default": 50
|
||
},
|
||
"feeTier": {
|
||
"type": "string",
|
||
"enum": [
|
||
"slow",
|
||
"normal",
|
||
"fast"
|
||
]
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"type": "object",
|
||
"title": "SOL",
|
||
"properties": {
|
||
"inputMint": {
|
||
"type": "string"
|
||
},
|
||
"outputMint": {
|
||
"type": "string"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"description": "Smallest units (legacy)"
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
},
|
||
"slippageBps": {
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 1000,
|
||
"default": 50
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Swap cost estimate",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SwapCostEstimateResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Validation error"
|
||
},
|
||
"404": {
|
||
"description": "Wallet not found"
|
||
},
|
||
"502": {
|
||
"description": "Upstream RPC / quote failed"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/relay/cost-estimate": {
|
||
"post": {
|
||
"summary": "Estimate USD cost of a bridge (Relay quote — trimmed, без steps[])",
|
||
"description": "Вызывает Relay /quote внутри и фильтрует response — отдаёт только fees + details (rate, time, impact, currencyIn/Out).\n\nБез `steps[]` (которые тяжёлые и содержат unsigned txs). Поведение JWT-binding (body.user, body.recipient) — то же что у /relay/quote.",
|
||
"tags": [
|
||
"Bridge (Relay)"
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"user": {
|
||
"type": "string",
|
||
"description": "Sender address (должен совпадать с user's wallet origin chain)"
|
||
},
|
||
"recipient": {
|
||
"type": "string"
|
||
},
|
||
"originChainId": {
|
||
"type": "integer",
|
||
"description": "1=ETH, 56=BSC, 792703809=SOL"
|
||
},
|
||
"destinationChainId": {
|
||
"type": "integer"
|
||
},
|
||
"originCurrency": {
|
||
"type": "string",
|
||
"description": "Contract address (для EVM) или mint (для SOL)"
|
||
},
|
||
"destinationCurrency": {
|
||
"type": "string"
|
||
},
|
||
"amount": {
|
||
"type": "string",
|
||
"description": "Smallest units (legacy)"
|
||
},
|
||
"amountHuman": {
|
||
"type": "string",
|
||
"description": "Human-readable amount (e.g. \"0.01\"). Server конвертит в smallest units через token decimals. Используется ВМЕСТО поля `amount` — НЕ передавай оба одновременно.",
|
||
"example": "0.01"
|
||
},
|
||
"tradeType": {
|
||
"type": "string",
|
||
"enum": [
|
||
"EXACT_INPUT",
|
||
"EXACT_OUTPUT"
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Bridge cost estimate",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/BridgeCostEstimateResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Validation error / unknown originCurrency для amountHuman"
|
||
},
|
||
"403": {
|
||
"description": "body.user/recipient не совпадает с user wallets"
|
||
},
|
||
"502": {
|
||
"description": "Relay upstream error"
|
||
},
|
||
"504": {
|
||
"description": "Relay timeout"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|