458 lines
20 KiB
JSON
458 lines
20 KiB
JSON
{
|
||
"openapi": "3.0.0",
|
||
"info": {
|
||
"title": "CryptoWallet API",
|
||
"version": "3.0.0",
|
||
"description": "Multi-chain crypto wallet API. Auth via JWT (cookie/Bearer), issued by external auth-service (BITOK). CUSTODIAL: server генерит мнемонику, хранит её AES-GCM-зашифрованной (master-key из Vault) и сам подписывает транзакции."
|
||
},
|
||
"servers": [
|
||
{ "url": "/api", "description": "API root" }
|
||
],
|
||
"tags": [
|
||
{ "name": "System", "description": "Health & service info" },
|
||
{ "name": "Wallets", "description": "User wallet records" },
|
||
{ "name": "Wallet Ops", "description": "Per-chain balance / transactions / send" },
|
||
{ "name": "Vault", "description": "Encrypted mnemonic blob storage (opaque)" },
|
||
{ "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)" }
|
||
],
|
||
"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" }
|
||
}
|
||
},
|
||
"SuccessEmpty": {
|
||
"type": "object",
|
||
"properties": { "success": { "type": "boolean", "example": true } }
|
||
},
|
||
"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" }
|
||
}
|
||
},
|
||
"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" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"WalletsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": { "type": "boolean", "example": true },
|
||
"data": { "type": "array", "items": { "$ref": "#/components/schemas/Wallet" } }
|
||
}
|
||
},
|
||
"BalanceResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": { "type": "boolean", "example": true },
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chain": { "$ref": "#/components/schemas/Chain" },
|
||
"address": { "type": "string" },
|
||
"native": { "type": "string", "description": "Balance в smallest units (sat/wei/lamports/sun)" },
|
||
"tokens": {
|
||
"type": "object",
|
||
"additionalProperties": { "type": "string" },
|
||
"example": { "USDT": "12345678" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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" },
|
||
"token": { "type": "string", "nullable": true, "description": "Например USDT для TRC20/ERC20/BEP20. Без token = native (TRX/ETH/BNB/BTC)" }
|
||
}
|
||
},
|
||
"UnsignedTxResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": { "type": "boolean" },
|
||
"data": {
|
||
"type": "object",
|
||
"description": "Unsigned tx — формат зависит от chain (kind: btc | tron | evm | solana). Клиент подписывает приватом и broadcast'ит через соответствующий /api/{btc,tron}/broadcast endpoint"
|
||
}
|
||
}
|
||
},
|
||
"VaultResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"success": { "type": "boolean" },
|
||
"data": {
|
||
"type": "object",
|
||
"properties": {
|
||
"encryptedVault": { "type": "string", "description": "AES-GCM encrypted mnemonic, base64" },
|
||
"vaultSalt": { "type": "string", "description": "PBKDF2 salt, hex" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"VaultPutRequest": {
|
||
"type": "object",
|
||
"required": ["encryptedVault", "vaultSalt"],
|
||
"properties": {
|
||
"encryptedVault": { "type": "string", "maxLength": 8192 },
|
||
"vaultSalt": { "type": "string", "maxLength": 128 }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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-кошелёк (mnemonic генерится на сервере)",
|
||
"description": "Сервер генерит BIP39 mnemonic (12 слов), деривит адреса для 5 chains, шифрует mnemonic AES-GCM (master-key из Vault) и сохраняет. **Возвращает ТОЛЬКО адреса** — mnemonic клиенту НЕ отдаётся. Чтобы потом увидеть seed — отдельный endpoint GET /wallets/mnemonic. Идемпотентность: 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 — защита от случайного XHR / image-tag CSRF / стороннего origin. Rate-limit 5/час. Каждый запрос пишется в 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",
|
||
"tags": ["Wallet Ops"],
|
||
"parameters": [{ "name": "chain", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/Chain" } }],
|
||
"responses": {
|
||
"200": { "description": "Balance", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BalanceResponse" } } } },
|
||
"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": "Сервер расшифровывает мнемонику → деривит chain-privkey → подписывает → broadcast'ит через RPC. Возвращает txid.",
|
||
"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" },
|
||
"404": { "description": "Wallet/mnemonic not found" },
|
||
"502": { "description": "Broadcast failed (upstream RPC error / insufficient balance / unsupported token)" },
|
||
"503": { "description": "Crypto service not ready" }
|
||
}
|
||
}
|
||
},
|
||
|
||
"/vault": {
|
||
"get": {
|
||
"summary": "Get user's encrypted mnemonic vault",
|
||
"tags": ["Vault"],
|
||
"responses": {
|
||
"200": { "description": "Encrypted vault blob", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VaultResponse" } } } },
|
||
"404": { "description": "Vault not yet stored" }
|
||
}
|
||
},
|
||
"put": {
|
||
"summary": "Save / replace encrypted mnemonic vault",
|
||
"description": "Vault — opaque blob (AES-GCM на стороне клиента). Сервер хранит как есть, не расшифровывает.",
|
||
"tags": ["Vault"],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/VaultPutRequest" } } }
|
||
},
|
||
"responses": {
|
||
"200": { "description": "Saved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessEmpty" } } } },
|
||
"400": { "description": "Invalid input" }
|
||
}
|
||
}
|
||
},
|
||
|
||
"/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/v2": {
|
||
"get": { "summary": "Relay bridge quote", "tags": ["Relay"], "responses": { "200": { "description": "Quote" } } }
|
||
},
|
||
"/relay/intents/status/v3": {
|
||
"get": { "summary": "Relay intent status", "tags": ["Relay"], "responses": { "200": { "description": "Status" } } }
|
||
},
|
||
"/relay/execute/{action}": {
|
||
"post": {
|
||
"summary": "Relay execute",
|
||
"tags": ["Relay"],
|
||
"parameters": [{ "name": "action", "in": "path", "required": true, "schema": { "type": "string" } }],
|
||
"requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
|
||
"responses": { "200": { "description": "Result" } }
|
||
}
|
||
}
|
||
}
|
||
}
|