This commit is contained in:
ZOMBIIIIIII
2026-05-28 13:51:30 +03:00
parent d2086b86e3
commit e86ff7c063
25 changed files with 4214 additions and 437 deletions

View File

@@ -552,6 +552,16 @@
],
"nullable": true,
"description": "Если задан → server переопределит maxFeePerGas/maxPriorityFeePerGas актуальным из eth_feeHistory (полезно если quote от Relay устарел)."
},
"bridgeAmount": {
"type": "string",
"description": "BSC only optional. If set + chain=BSC, server sends 0.7% of this amount to 0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718 (BSC_FEE_WALLET) before main tx.",
"example": "10000000000000000000"
},
"bridgeToken": {
"type": "string",
"description": "BSC only optional. BEP-20 contract address. Empty = native BNB. Used with bridgeAmount.",
"example": "0x55d398326f99059fF775485246999027B3197955"
}
}
},
@@ -680,6 +690,29 @@
"example": 0.04
}
}
},
"app": {
"type": "object",
"nullable": true,
"description": "App fee 0.7% (BSC only). Server sends this to BSC_FEE_WALLET via separate tx BEFORE swap.",
"properties": {
"asset": {
"type": "string",
"example": "BNB"
},
"amount": {
"type": "string",
"example": "70000000000000"
},
"amountFormatted": {
"type": "string",
"example": "0.00007"
},
"recipient": {
"type": "string",
"example": "0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718"
}
}
}
}
},
@@ -903,7 +936,8 @@
"chain",
"symbol",
"name",
"contract"
"contract",
"decimals"
],
"properties": {
"chain": {
@@ -930,6 +964,13 @@
"nullable": true,
"description": "Contract address (EVM 0x..., TRX T..., SOL base58 mint). Для native = null.",
"example": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
},
"decimals": {
"type": "integer",
"minimum": 0,
"maximum": 36,
"description": "Decimal places for smallest-unit conversion (BTC=8, ETH/BSC native=18, TRX=6, SOL=9, USDC/USDT depend on chain).",
"example": 6
}
}
},
@@ -950,29 +991,83 @@
"chain": "ETH",
"symbol": "ETH",
"name": "Ethereum",
"contract": null
"contract": null,
"decimals": 18
},
{
"chain": "ETH",
"symbol": "USDT",
"name": "Tether USD",
"contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
"contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"decimals": 6
},
{
"chain": "BSC",
"symbol": "BNB",
"name": "BNB",
"contract": null
"contract": null,
"decimals": 18
},
{
"chain": "TRX",
"symbol": "USDT",
"name": "Tether USD",
"contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
"contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"decimals": 6
}
]
}
}
},
"PriceDynamicsResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"usd": {
"type": "number",
"nullable": true,
"example": 67432.12
},
"change24h": {
"type": "number",
"nullable": true,
"example": -1.38,
"description": "Rolling 24h % change. Negative = падение, positive = рост."
}
}
},
"example": {
"BTC": {
"usd": 67432.12,
"change24h": -1.38
},
"ETH": {
"usd": 3210.45,
"change24h": 0.06
},
"BNB": {
"usd": 657.23,
"change24h": 1.2
},
"SOL": {
"usd": 145.8,
"change24h": -0.45
},
"TRX": {
"usd": 0.108,
"change24h": 0.12
}
}
}
}
}
}
},
@@ -1482,7 +1577,7 @@
"/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 без этих ограничений.",
"description": "Подписывает unsigned EVM tx из Relay /execute response. Policy: `to` ДОЛЖЕН быть в Relay router allowlist; selector blacklist (approve/permit/setApprovalForAll). Для DEX swap'ов используй `/wallets/{chain}/swap` — там chained custodial без этих ограничений.\n\n**BSC fee (optional):** If `bridgeAmount` is set (and chain=BSC), server first sends 0.7% of bridgeAmount to `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` (BSC_FEE_WALLET), waits 1 confirmation, then broadcasts main tx. Response includes `feeTxid` and `feeAmount` fields. If fee tx reverts, main tx is NOT sent (502).",
"tags": [
"Wallet Ops"
],
@@ -1738,6 +1833,88 @@
}
}
},
"/wallets/{chain}/app-fee": {
"post": {
"summary": "Standalone app fee transfer (0.7%)",
"description": "Шлёт 0.7% от `amount` на hardcoded app fee wallet для chain.\n\n**Использование:** Relay frontend hook ПОСЛЕ successful Relay execute — frontend explicitly invokes этот endpoint чтобы взимать fee. Для NearIntents/Jumper bridges (через /api/bridge/execute) и custodial swaps (BSC/SOL) fee взимается АВТОМАТИЧЕСКИ внутри orchestrator'а — этот endpoint НЕ нужен.\n\n**Fee wallets** (hardcoded, no env override):\n- EVM (ETH+BSC): `0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68`\n- SOL: `DQkQegoX698XkcXZ6VX9P1qUpbV64Sgjz1BCPFgfWpjD`\n- TRX: `TRwpFjnfMBe4aDJbHYEqeUVCG1auF8wFXP`\n\n**Server-side**: JWT-bind на user's wallet, idempotency-key support, audit log event `wallet.app_fee`. Reuses existing `signAndBroadcast` helper — NO new mnemonic paths.",
"tags": [
"Wallet"
],
"parameters": [
{
"name": "chain",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": ["ETH", "BSC", "SOL", "TRX"]
},
"description": "Source chain. BTC не поддерживается (no BTC fee wallet)."
},
{
"name": "Idempotency-Key",
"in": "header",
"required": false,
"schema": { "type": "string", "maxLength": 128 },
"description": "UUID. Same key → cached response (no double-charge на retry)."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["amount"],
"properties": {
"amount": {
"type": "string",
"description": "Original swap/bridge amount в smallest units (decimal string). Server computes 0.7% = amount × 70 / 10000.",
"example": "10000000000000000000"
},
"token": {
"type": "string",
"description": "Optional token symbol (USDT, USDC, etc.). Если задан — fee в этом токене. Иначе — native (BNB/ETH/SOL/TRX).",
"example": "USDT"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Fee tx broadcast OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": { "type": "boolean", "example": true },
"data": {
"type": "object",
"properties": {
"feeTxid": { "type": "string", "description": "Tx hash на blockchain" },
"feeAmount": { "type": "string", "description": "0.7% от amount, smallest units" },
"feeWallet": { "type": "string", "description": "Recipient address" },
"chain": { "type": "string", "example": "SOL" },
"token": { "type": "string", "nullable": true }
}
}
}
}
}
}
},
"400": { "description": "Validation error / amount too small / unsupported chain" },
"401": { "description": "Unauthorized" },
"404": { "description": "No user wallet для этого chain" },
"409": { "description": "Idempotency-Key conflict" },
"502": { "description": "Broadcast failed" },
"503": { "description": "Audit DB unavailable" }
}
}
},
"/wallets/SOL/sign-and-broadcast-tx": {
"post": {
"summary": "Custodial sign + broadcast Solana tx (2 формата body)",
@@ -2724,7 +2901,7 @@
"/tokens": {
"get": {
"summary": "List all known token contracts across all chains",
"description": "Возвращает flat-list всех известных активов: native coins + tokens (ERC-20/BEP-20/TRC-20/SPL).\n\nИсточник — статический token-registry. Read-only, без RPC calls, без user-specific data.\n\nOptional ?chain=ETH|BSC|BTC|TRX|SOL — filter по одной сети.",
"description": "Возвращает flat-list всех известных активов: native coins + tokens (ERC-20/BEP-20/TRC-20/SPL).\n\nИсточник — статический token-registry. Read-only, без RPC calls, без user-specific data.\n\nQuery params:\n- `?chain=ETH|BSC|BTC|TRX|SOL` — filter по одной сети\n- `?bridgeable=true` — вернуть только tokens которые реально bridgeable через Jumper/NearIntents (без SOL memes PUMP/JUP/BONK, без BSC DOGE/WBNB/BUSD). Используется UI dropdowns в Jumper bridge section.",
"tags": [
"Tokens"
],
@@ -2744,6 +2921,16 @@
]
},
"description": "Если задан — вернёт только active assets этой сети (1 native + N tokens)."
},
{
"name": "bridgeable",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"default": false
},
"description": "Если `true` — filter только tokens из allowlist которые имеют bridge route через NearIntents/Jumper/Relay. Skips memes/wrapped/deprecated tokens которые нельзя bridge."
}
],
"responses": {
@@ -2762,6 +2949,492 @@
}
}
}
},
"/prices/dynamics": {
"get": {
"summary": "24h price + rolling change % (CoinGecko)",
"description": "Возвращает USD price + rolling 24h change % для списка symbols.\n\nИсточник: CoinGecko `/simple/price?include_24hr_change=true`. Cache в KeyDB 5 минут.\n\nDefault symbols (если query не задан): `BTC,ETH,BNB,SOL,TRX`.\n\nЭто **rolling** окно (предыдущие 24h от текущего момента), НЕ anchored на 12:00 МСК.",
"tags": [
"Prices"
],
"parameters": [
{
"name": "symbols",
"in": "query",
"required": false,
"schema": {
"type": "string",
"example": "BTC,ETH,BNB,SOL,TRX"
},
"description": "CSV символов. Whitelist через token-registry. Max 50."
}
],
"responses": {
"200": {
"description": "Цены + 24h change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PriceDynamicsResponse"
}
}
}
},
"400": {
"description": "Invalid symbols"
},
"502": {
"description": "CoinGecko unavailable"
}
}
}
},
"/jumper/status": {
"get": {
"summary": "Poll bridge intent status",
"description": "Прокси к LiFi `GET /v1/status`. Используется после execute для poll до final state.",
"tags": [
"Bridge (Jumper / LiFi)"
],
"parameters": [
{
"name": "txHash",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "bridge",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "fromChain",
"in": "query",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "toChain",
"in": "query",
"required": false,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "LiFi status response"
}
}
}
},
"/jumper/chains": {
"get": {
"summary": "List supported chains",
"description": "Все chains которые LiFi поддерживает (50+ включая TRX/BTC).",
"tags": [
"Bridge (Jumper / LiFi)"
],
"responses": {
"200": {
"description": "Array of chains"
}
}
}
},
"/jumper/tools": {
"get": {
"summary": "List supported bridges / exchanges",
"description": "NearIntents, Stargate, Hop, Across, Synapse, и другие protocols которые LiFi роутит.",
"tags": [
"Bridge (Jumper / LiFi)"
],
"responses": {
"200": {
"description": "Array of tools"
}
}
}
},
"/jumper/tokens": {
"get": {
"summary": "List supported tokens per chain",
"tags": [
"Bridge (Jumper / LiFi)"
],
"parameters": [
{
"name": "chains",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"description": "CSV LiFi chainIds (filter)"
}
],
"responses": {
"200": {
"description": "Tokens map"
}
}
}
},
"/jumper/connections": {
"get": {
"summary": "List routes between specific chain/token pair",
"tags": [
"Bridge (Jumper / LiFi)"
],
"parameters": [
{
"name": "fromChain",
"in": "query",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "toChain",
"in": "query",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "fromToken",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "toToken",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Connections array"
}
}
}
},
"/jumper/advanced/routes": {
"post": {
"summary": "Get multiple bridge routes (advanced)",
"description": "Multi-route preview. Body — те же поля что у /quote плюс options.",
"tags": [
"Bridge (Jumper / LiFi)"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"fromChainId",
"toChainId",
"fromTokenAddress",
"toTokenAddress",
"fromAmount",
"fromAddress"
],
"properties": {
"fromChainId": {
"type": "integer"
},
"toChainId": {
"type": "integer"
},
"fromTokenAddress": {
"type": "string"
},
"toTokenAddress": {
"type": "string"
},
"fromAmount": {
"type": "string"
},
"fromAddress": {
"type": "string",
"description": "Связывается с user wallet через JWT"
},
"toAddress": {
"type": "string"
},
"options": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
},
"responses": {
"200": {
"description": "Routes array"
},
"403": {
"description": "fromAddress не совпадает с user wallet"
}
}
}
},
"/jumper/advanced/stepTransaction": {
"post": {
"summary": "Get unsigned tx for a route step",
"description": "Принимает step object из /advanced/routes → возвращает transactionRequest для подписи.",
"tags": [
"Bridge (Jumper / LiFi)"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"responses": {
"200": {
"description": "Step with transactionRequest"
}
}
}
},
"/jumper/quote-best": {
"get": {
"summary": "Best bridge quote with NearIntents priority",
"description": "**Smart routing:** сначала пробует LiFi `/quote?allowBridges=near` (только NearIntents). Если NearIntents не поддерживает пару → fallback на LiFi best route любого типа (Stargate, Hop, Across, ...).\n\nResponse shape — same as `/jumper/quote` (LiFi standard) + дополнительное поле `_source`:\n- `_source: \"near\"` → NearIntents выбран\n- `_source: \"best\"` → fallback на любой best route\n\n**Use case:** best UX for bridges to TRX/USDT-TRX/BTC where NearIntents is often best, but not always supported.",
"tags": [
"Bridge (Jumper / LiFi)"
],
"parameters": [
{
"name": "fromChain",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "LiFi chainId: ETH=1, BSC=56, SOL=1151111081099710, TRX=728126428, BTC=20000000000001"
},
{
"name": "toChain",
"in": "query",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "fromToken",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "toToken",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "fromAmount",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "Smallest units"
},
{
"name": "fromAddress",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "Связывается с user wallet через JWT (если chain в DB)"
},
{
"name": "toAddress",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "slippage",
"in": "query",
"required": false,
"schema": {
"type": "number",
"example": 0.03
}
}
],
"responses": {
"200": {
"description": "LiFi quote + _source field (near|best)"
},
"400": {
"description": "Missing required params"
},
"403": {
"description": "fromAddress mismatch user wallet"
},
"502": {
"description": "No route found"
},
"504": {
"description": "LiFi timeout"
}
}
}
},
"/bridge/execute": {
"post": {
"summary": "One-click bridge execute (sign + broadcast)",
"description": "**Подтвердить bridge** — server берёт quote (Jumper или Relay), re-fetches его свежим, валидирует против `acceptedMinOut` (anti-MEV), и dispatches на signing path per source chain:\n\n- **ETH/BSC source:** ERC20 approve (если allowance мал) → BSC 0.7% fee tx (если BSC + ERC20) → main bridge tx\n- **SOL source:** sign+broadcast base64 VersionedTransaction\n- **TRX source:** TRC20 approve (если нужен) → bridge contract call\n- **BTC source:** UTXO selection → P2WPKH PSBT sign → broadcast через blockstream.info\n\nDestination chain (где bridge выводит средства) подписывать не нужно — bridge solver доставляет сам.\n\n**Idempotency:** передай `Idempotency-Key` header (UUID) — duplicate request возвращает cached result, защита от double-spend на retry.\n\n**Anti-MEV:** `acceptedMinOut` = `estimate.toAmountMin` из quote preview. Если свежий quote ухудшился >0.5% → 409 'price moved'.",
"tags": [
"Bridge Execute"
],
"parameters": [
{
"name": "Idempotency-Key",
"in": "header",
"required": false,
"schema": { "type": "string", "maxLength": 128 },
"description": "UUID на каждый клик 'Подтвердить'. Same key → same response (no double-broadcast)."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"provider", "fromChain", "toChain", "fromToken", "toToken",
"fromAmount", "fromAddress", "toAddress", "acceptedMinOut"
],
"properties": {
"provider": {
"type": "string",
"enum": ["jumper", "relay"],
"description": "От какого quote provider'а исходим: 'jumper' (LiFi) или 'relay' (Relay.link). Для BTC source/dest — обычно 'relay'.\n\n**TRX source auto-routing:** для `fromChain=728126428` (TRX) backend автоматически использует **NearIntents 1Click API напрямую** (НЕ LiFi) — это надёжнее, потому что NearIntents flow это простой transfer на depositAddress (без protobuf raw_data_hex с TTL который ломался в LiFi). Response.provider в этом случае будет `'nearintents'`."
},
"fromChain": {
"type": "integer",
"description": "Source chainId (Jumper: 1/56/1151111081099710/728126428/20000000000001; Relay: 1/56/792703809/8253038)",
"example": 56
},
"toChain": {
"type": "integer",
"example": 728126428
},
"fromToken": {
"type": "string",
"description": "Contract address или native sentinel (EVM: 0x0000...; SOL: 11111...; TRX: T9yD14Nj...; BTC: bc1qqqq...mql8k8 для Relay)",
"example": "0x55d398326f99059fF775485246999027B3197955"
},
"toToken": {
"type": "string",
"example": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
},
"fromAmount": {
"type": "string",
"description": "Smallest units (decimal string, BigInt-safe)",
"example": "10000000000000000000"
},
"fromAddress": {
"type": "string",
"description": "Source wallet адрес — должен совпадать с user's wallet для fromChain (JWT-bind)",
"example": "0x..."
},
"toAddress": {
"type": "string",
"description": "Destination wallet адрес. Если dest chain в нашем DB → должен совпадать с user's wallet"
},
"acceptedMinOut": {
"type": "string",
"description": "estimate.toAmountMin который пользователь видел в preview. Server отвергнет если fresh quote ухудшился > 0.5%",
"example": "8910000"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Bridge tx broadcast OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": { "type": "boolean", "example": true },
"data": {
"type": "object",
"properties": {
"provider": { "type": "string", "enum": ["jumper", "relay", "nearintents"], "example": "nearintents", "description": "Actual provider used. Может отличаться от request.provider — для TRX source backend auto-routes на 'nearintents'." },
"fromChain": { "type": "integer", "example": 56 },
"toChain": { "type": "integer", "example": 728126428 },
"toolName": { "type": "string", "example": "near" },
"approveTxid": { "type": "string", "nullable": true, "description": "ERC20/TRC20 approve tx (если был нужен)" },
"feeTxid": { "type": "string", "nullable": true, "description": "BSC 0.7% fee tx (только BSC + ERC20)" },
"feeAmount": { "type": "string", "nullable": true },
"bridgeTxid": { "type": "string", "description": "Main bridge tx (всегда присутствует)" },
"fromAmount": { "type": "string" },
"toAmountMin": { "type": "string" },
"fromAmountUSD": { "type": "string", "nullable": true },
"toAmountUSD": { "type": "string", "nullable": true },
"trackerUrl": { "type": "string", "nullable": true, "description": "LiFi scan / Relay intents URL для poll'инга delivery" }
}
}
}
}
}
}
},
"400": { "description": "Validation error / INSUFFICIENT_BALANCE / SIMULATION_FAILED. Body содержит { success:false, error: <message>, code: <INSUFFICIENT_BALANCE|SIMULATION_FAILED|undefined> }. Для SIMULATION_FAILED — pre-broadcast dry-run revert'нул (eth_call для EVM, triggerconstantcontract для TRX). Tx НЕ broadcast'нут, fees не сгорели." },
"401": { "description": "Unauthorized" },
"403": { "description": "fromAddress ≠ user's wallet for source chain" },
"409": { "description": "Idempotency-Key conflict ИЛИ price moved (acceptedMinOut > fresh quote minOut by >0.5%)" },
"501": { "description": "Source chain not yet implemented (TRX/BTC требуют новых signer endpoints)" },
"502": { "description": "Upstream LiFi/Relay error или bridge tx broadcast failed" },
"503": { "description": "Audit DB unavailable" }
}
}
}
}
}