feat: security audit fixes
This commit is contained in:
@@ -83,6 +83,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FormattedAmount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"raw": { "type": "string", "description": "Smallest units (wei/sat/sun/lamports), string-encoded BigInt" },
|
||||
"formatted": { "type": "string", "description": "Human-readable decimal", "example": "0.003" },
|
||||
"decimals": { "type": "integer", "description": "Decimals of the chain/token", "example": 18 }
|
||||
}
|
||||
},
|
||||
"BalanceResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -92,11 +100,11 @@
|
||||
"properties": {
|
||||
"chain": { "$ref": "#/components/schemas/Chain" },
|
||||
"address": { "type": "string" },
|
||||
"native": { "type": "string", "description": "Balance в smallest units (sat/wei/lamports/sun)" },
|
||||
"native": { "$ref": "#/components/schemas/FormattedAmount" },
|
||||
"tokens": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "string" },
|
||||
"example": { "USDT": "12345678" }
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,8 +134,52 @@
|
||||
"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." }
|
||||
"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 устарел)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +284,7 @@
|
||||
"/wallets/{chain}/send": {
|
||||
"post": {
|
||||
"summary": "Custodial send: server signs + broadcasts",
|
||||
"description": "Юзер на клиенте жмёт 'подтвердить' → клиент шлёт {to, amount, token?}. Сервер расшифровывает мнемонику, деривит chain privkey, подписывает, broadcast'ит. Возвращает txid. Защита: TRX MITM check, EVM gas cap 500 gwei, SOL confirmTransaction, BTC timeout + safety multiplier.",
|
||||
"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": {
|
||||
@@ -241,13 +293,48 @@
|
||||
},
|
||||
"responses": {
|
||||
"200": { "description": "Broadcast successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TxBroadcastResponse" } } } },
|
||||
"400": { "description": "Invalid input" },
|
||||
"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/Swap unsigned tx)",
|
||||
"description": "Подписывает произвольную EVM tx (например `steps[0].items[0].data` из `/relay/execute/swap`). Сервер расшифровывает mnemonic, деривит privkey, ставит nonce, подписывает type-2 EIP-1559 tx, broadcast'ит. Если задан `feeTier` → переопределяет maxFeePerGas/maxPriority из тела актуальным из eth_feeHistory. ⚠️ Security: подписывает arbitrary `to`+`data` — в production надо whitelist'ить `to` (Relay routers) или требовать Relay attestation. Только ETH(1)/BSC(56).",
|
||||
"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": "Invalid input (bad to/data/value, chainId mismatch, invalid feeTier)" },
|
||||
"404": { "description": "Wallet/mnemonic not found" },
|
||||
"502": { "description": "Broadcast failed" },
|
||||
"503": { "description": "Crypto service not ready" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"/btc/utxos/{address}": {
|
||||
"get": {
|
||||
@@ -378,19 +465,57 @@
|
||||
}
|
||||
},
|
||||
|
||||
"/relay/quote/v2": {
|
||||
"get": { "summary": "Relay bridge quote", "tags": ["Relay"], "responses": { "200": { "description": "Quote" } } }
|
||||
"/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"], "responses": { "200": { "description": "Status" } } }
|
||||
"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",
|
||||
"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" } }],
|
||||
"requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } },
|
||||
"responses": { "200": { "description": "Result" } }
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user