746 lines
39 KiB
JSON
746 lines
39 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.0 }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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", "amount"],
|
||
"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: игнорится."
|
||
}
|
||
}
|
||
},
|
||
"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 устарел)."
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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/{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.0, "usdValue": 1.0 },
|
||
"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 chained swap (BSC PancakeSwap / TRX SunSwap+FeeSwapRouter / SOL Jupiter)",
|
||
"description": "Полностью custodial swap в один HTTP-вызов. Никакого client-side signing.\n\n**BSC** — PancakeSwap V2 approve+swap chained. Пары: BNB/USDT/USDC/DOGE/WBNB/BUSD.\n\n**TRX** — SunSwap V2 через FeeSwapRouter (0.7% fee). Только пары TRX↔USDT. Server делает approve(infinite, FeeSwapRouter) (если allowance < amount) + wait inclusion + swap. 4-layer MITM defense (txID/expiration/type/selector verify) — компрометированный TronGrid не сможет подсунуть `transfer` вместо `swap`.\n\n**SOL** — Jupiter aggregator. Любые mints из registry (USDT/USDC/PUMP/JUP/WIF/POPCAT/TRUMP/PYTH/JTO/W/BONK/ORCA/PENGU/RAY).\n\n**Slippage protection** — server computes `amountOutMin = quote × (10000-slippageBps)/10000` от actual quote (default 50 bps = 0.5%). Клиент НЕ задаёт amountOutMin напрямую (защита от MEV-sandwich). Optional `Idempotency-Key` header для anti double-spend.",
|
||
"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 swap (symbols)",
|
||
"required": ["from", "to", "amount"],
|
||
"properties": {
|
||
"from": { "type": "string", "description": "BSC: BNB|USDT|USDC|DOGE|WBNB|BUSD; TRX: TRX|USDT (только эта пара поддерживается на TRON)" },
|
||
"to": { "type": "string" },
|
||
"amount": { "type": "string", "description": "Smallest units (wei для 18-dec EVM, sun для TRX 6-dec). Max для TRX = 9_007_199_254_740_991 (~9B TRX)." },
|
||
"slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000, "description": "0.01%-10%. Default 50 (0.5%). Server вычислит amountOutMin сам — клиент НЕ задаёт его напрямую." },
|
||
"feeTier": { "type": "string", "enum": ["slow", "normal", "fast"], "description": "Только BSC (ETH/BSC). На TRX игнорится." }
|
||
}
|
||
},
|
||
{
|
||
"type": "object",
|
||
"title": "SOL swap (mints)",
|
||
"required": ["inputMint", "outputMint", "amount"],
|
||
"properties": {
|
||
"inputMint": { "type": "string", "description": "SPL mint address (base58)" },
|
||
"outputMint": { "type": "string" },
|
||
"amount": { "type": "string", "description": "Smallest units (lamports = 9-dec для SOL native)" },
|
||
"slippageBps": { "type": "integer", "minimum": 1, "maximum": 1000 }
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "BSC: { approveTxid?, swapTxid }. TRX/SOL: { txid | signature }",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": { "type": "boolean" },
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": { "type": "string" },
|
||
"approveTxid": { "type": "string", "nullable": true, "description": "BSC only, если token-to-X swap требовал approve" },
|
||
"swapTxid": { "type": "string", "description": "BSC swap txid" },
|
||
"txid": { "type": "string", "description": "TRX txid" },
|
||
"signature": { "type": "string", "description": "SOL tx signature" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": { "description": "Invalid pair / slippage / amount / unsupported chain" },
|
||
"404": { "description": "Wallet not found" },
|
||
"409": { "description": "Idempotency-Key reuse with different body, or operation in-flight" },
|
||
"502": { "description": "Swap failed (no liquidity / network error / contract revert)" },
|
||
"503": { "description": "Crypto service not ready" }
|
||
}
|
||
}
|
||
},
|
||
"/wallets/SOL/sign-and-broadcast-tx": {
|
||
"post": {
|
||
"summary": "Custodial sign + broadcast arbitrary Solana VersionedTransaction",
|
||
"description": "Подписывает unsigned serialized Solana tx (от Relay /execute SOL-side, или любого aggregator'а). Server verify feePayer === user's pubkey, partial-sign keypair'ом, broadcast, confirm.",
|
||
"tags": ["Wallet Ops"],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": ["transaction"],
|
||
"properties": {
|
||
"transaction": { "type": "string", "description": "Base64-encoded VersionedTransaction (max ~1500 bytes raw)" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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 base64 / tx size / feePayer mismatch" },
|
||
"404": { "description": "SOL wallet/mnemonic not found" },
|
||
"502": { "description": "Sign or broadcast failed" }
|
||
}
|
||
}
|
||
},
|
||
|
||
"/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" } }
|
||
}
|
||
},
|
||
|
||
"/sol/swap/quote": {
|
||
"get": {
|
||
"summary": "Jupiter swap quote (Solana)",
|
||
"tags": ["Solana"],
|
||
"parameters": [
|
||
{ "name": "inputMint", "in": "query", "required": true, "schema": { "type": "string" } },
|
||
{ "name": "outputMint", "in": "query", "required": true, "schema": { "type": "string" } },
|
||
{ "name": "amount", "in": "query", "required": true, "schema": { "type": "string" } },
|
||
{ "name": "slippageBps", "in": "query", "required": true, "schema": { "type": "integer" } }
|
||
],
|
||
"responses": { "200": { "description": "Quote" } }
|
||
}
|
||
},
|
||
"/sol/swap/build": {
|
||
"post": {
|
||
"summary": "Jupiter swap build",
|
||
"tags": ["Solana"],
|
||
"requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["quoteResponse", "userPublicKey"], "properties": { "quoteResponse": { "type": "object" }, "userPublicKey": { "type": "string" } } } } } },
|
||
"responses": { "200": { "description": "Swap tx" } }
|
||
}
|
||
},
|
||
|
||
"/tron/swap/quote": {
|
||
"get": {
|
||
"summary": "TRON swap quote (TRX <-> USDT)",
|
||
"tags": ["TRON Swap"],
|
||
"parameters": [
|
||
{ "name": "from", "in": "query", "required": true, "schema": { "type": "string", "enum": ["TRX", "USDT"] } },
|
||
{ "name": "to", "in": "query", "required": true, "schema": { "type": "string", "enum": ["TRX", "USDT"] } },
|
||
{ "name": "amount", "in": "query", "required": true, "schema": { "type": "string" } }
|
||
],
|
||
"responses": { "200": { "description": "Quote" } }
|
||
}
|
||
},
|
||
"/tron/swap/build": {
|
||
"post": {
|
||
"summary": "Build TRON swap transactions",
|
||
"tags": ["TRON Swap"],
|
||
"requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["from", "to", "amount", "amountOutMin", "userAddress"], "properties": { "from": { "type": "string" }, "to": { "type": "string" }, "amount": { "type": "string" }, "amountOutMin": { "type": "string" }, "userAddress": { "type": "string" } } } } } },
|
||
"responses": { "200": { "description": "Unsigned txs" } }
|
||
}
|
||
},
|
||
"/tron/swap/broadcast": {
|
||
"post": {
|
||
"summary": "Broadcast signed TRON swap",
|
||
"tags": ["TRON Swap"],
|
||
"requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["signedTransaction"], "properties": { "signedTransaction": { "type": "object" } } } } } },
|
||
"responses": { "200": { "description": "Result" } }
|
||
}
|
||
},
|
||
|
||
"/bsc/swap/quote": {
|
||
"get": {
|
||
"summary": "BSC swap quote (PancakeSwap V2)",
|
||
"tags": ["BSC"],
|
||
"parameters": [
|
||
{ "name": "from", "in": "query", "required": true, "schema": { "type": "string", "enum": ["BNB", "USDT", "DOGE"] } },
|
||
{ "name": "to", "in": "query", "required": true, "schema": { "type": "string", "enum": ["BNB", "USDT", "DOGE"] } },
|
||
{ "name": "amount", "in": "query", "required": true, "schema": { "type": "string" } }
|
||
],
|
||
"responses": { "200": { "description": "Quote" } }
|
||
}
|
||
},
|
||
"/bsc/swap/build": {
|
||
"post": {
|
||
"summary": "Build BSC swap transactions",
|
||
"tags": ["BSC"],
|
||
"requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["from", "to", "amount", "amountOutMin", "userAddress"], "properties": { "from": { "type": "string" }, "to": { "type": "string" }, "amount": { "type": "string" }, "amountOutMin": { "type": "string" }, "userAddress": { "type": "string" } } } } } },
|
||
"responses": { "200": { "description": "Unsigned txs" } }
|
||
}
|
||
},
|
||
|
||
"/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"] }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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.0 },
|
||
"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" } } }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|