inithipk;

This commit is contained in:
ZOMBIIIIIII
2026-06-26 18:46:21 +03:00
parent 32a642b924
commit 2beebb4968
4 changed files with 96 additions and 8 deletions

View File

@@ -71,10 +71,22 @@ export async function proxiedFetch(
} }
// undici.fetch поддерживает `dispatcher` для per-call routing через ProxyAgent. // undici.fetch поддерживает `dispatcher` для per-call routing через ProxyAgent.
// Возвращаемый тип Response совместим с native — приводим через unknown для TS. // Возвращаемый тип Response совместим с native — приводим через unknown для TS.
return undiciFetch(input as any, { try {
return (await undiciFetch(input as any, {
...(init as any), ...(init as any),
dispatcher: agent, dispatcher: agent,
}) as unknown as Response; })) as unknown as Response;
} catch (err: any) {
// Таймаут/отмена (AbortSignal) — это НЕ отказ прокси, fallback не нужен (сохраняем 504-семантику).
if (err?.name === 'AbortError') throw err;
// Прокси недоступен (connect ECONNREFUSED / timeout до прокси) → самовосстановление: пробуем
// прямой выход. Relay/Jumper/Jupiter — публичные API, прямой fetch их достаёт, если у сервера
// есть direct egress. Срабатывает ТОЛЬКО при throw (HTTP-ошибка ОТ прокси не бросает — это
// обычный Response с !ok). Идемпотентно: при живом прокси сюда не заходим, поведение прежнее.
const reason = err?.cause?.code ?? err?.code ?? err?.name ?? 'unknown';
logger.warn(`Outbound proxy failed (${reason}) — fallback на прямой fetch`);
return fetch(input as any, init as any);
}
} }
/** /**

View File

@@ -272,7 +272,9 @@ async function proxyJumperRequest(req: Request, res: Response, _next: NextFuncti
return; return;
} }
logger.error(`Jumper proxy failed: ${error?.stack || error?.message}`); logger.error(`Jumper proxy failed: ${error?.stack || error?.message}`);
res.status(502).json({ success: false, error: 'Jumper proxy error' }); // reason = код/имя сетевой ошибки (ECONNREFUSED/ENOTFOUND/UND_ERR_*) без хоста/IP/кредов прокси.
const reason = error?.cause?.code ?? error?.code ?? error?.name ?? 'unknown';
res.status(502).json({ success: false, error: 'Jumper proxy error', reason });
} }
} }
@@ -494,6 +496,8 @@ async function handleQuoteBest(req: Request, res: Response): Promise<void> {
return; return;
} }
logger.error(`handleQuoteBest failed: ${error?.stack || error?.message}`); logger.error(`handleQuoteBest failed: ${error?.stack || error?.message}`);
res.status(502).json({ success: false, error: 'Quote-best failed' }); // reason = код/имя сетевой ошибки (ECONNREFUSED/ENOTFOUND/UND_ERR_*) без хоста/IP/кредов прокси.
const reason = error?.cause?.code ?? error?.code ?? error?.name ?? 'unknown';
res.status(502).json({ success: false, error: 'Quote-best failed', reason });
} }
} }

View File

@@ -320,6 +320,8 @@ async function proxyRelayRequest(req: Request, res: Response, next: NextFunction
return; return;
} }
logger.error(`Relay proxy failed: ${error?.stack || error?.message}`); logger.error(`Relay proxy failed: ${error?.stack || error?.message}`);
res.status(502).json({ success: false, error: 'Relay proxy error' }); // reason = код/имя сетевой ошибки (ECONNREFUSED/ENOTFOUND/UND_ERR_*) без хоста/IP/кредов прокси.
const reason = error?.cause?.code ?? error?.code ?? error?.name ?? 'unknown';
res.status(502).json({ success: false, error: 'Relay proxy error', reason });
} }
} }

View File

@@ -3937,7 +3937,51 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "LiFi quote + _source (near|best) + appCommission (наша 0.7% в токене + USDT; информативно, считается из action.fromToken/estimate.fromAmountUSD; сбор — атомарно в /bridge/execute). Пример appCommission: { ratePercent: 0.7, inToken: { symbol: \"USDT\", amount: \"0.7\" }, usd: 0.7 }" "description": "LiFi quote (passthrough) + _source (near|best) + appCommission (наша 0.7%, информативно; сбор — атомарно в /bridge/execute)",
"content": { "application/json": { "schema": {
"type": "object",
"additionalProperties": true,
"description": "Сырой LiFi-quote + наши поля. Ниже задокументированы ключевые.",
"properties": {
"_source": { "type": "string", "enum": ["near", "best"], "description": "near = NearIntents, best = любой best route" },
"tool": { "type": "string", "example": "near" },
"toolDetails": { "type": "object", "additionalProperties": true },
"estimate": {
"type": "object",
"additionalProperties": true,
"properties": {
"fromAmount": { "type": "string" },
"fromAmountUSD": { "type": "string", "example": "10.03" },
"toAmount": { "type": "string", "description": "сколько получишь (smallest units)" },
"toAmountMin": { "type": "string", "description": "минимум с учётом slippage → передай как acceptedMinOut в /bridge/execute" },
"toAmountUSD": { "type": "string" },
"executionDuration": { "type": "number", "description": "оценка, сек" },
"gasCosts": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
"feeCosts": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
}
},
"action": {
"type": "object",
"additionalProperties": true,
"properties": {
"fromToken": { "type": "object", "additionalProperties": true, "properties": { "symbol": { "type": "string" }, "decimals": { "type": "integer" }, "address": { "type": "string" } } },
"toToken": { "type": "object", "additionalProperties": true },
"fromAmount": { "type": "string" }
}
},
"transactionRequest": { "type": "object", "nullable": true, "additionalProperties": true, "description": "Готовая EVM-tx (to/data/value/chainId) — для ручной подписи через /wallets/{chain}/sign-raw-evm-tx" },
"appCommission": {
"type": "object",
"nullable": true,
"description": "Наша комиссия 0.7% (в токене + USDT). Информативно; реально списывается в /bridge/execute. Считается из action.fromToken + action.fromAmount + estimate.fromAmountUSD.",
"properties": {
"ratePercent": { "type": "number", "example": 0.7 },
"inToken": { "type": "object", "properties": { "symbol": { "type": "string", "example": "USDC" }, "amount": { "type": "string", "example": "0.07", "description": "0.7% во входном токене" } } },
"usd": { "type": "number", "nullable": true, "example": 0.07, "description": "та же 0.7% в USDT (≈USD)" }
}
}
}
} } }
}, },
"400": { "400": {
"description": "Missing required params" "description": "Missing required params"
@@ -3954,6 +3998,32 @@
} }
} }
}, },
"/jumper/quote": {
"get": {
"summary": "LiFi single quote (passthrough; без NearIntents-приоритета)",
"description": "Прямой проброс на LiFi `/quote`. В отличие от `/jumper/quote-best` НЕ пробует NearIntents первым — отдаёт LiFi best route как есть. Ответ — сырой LiFi-quote + наше поле **appCommission** (наша 0.7% в токене + USDT, информативно; сбор — в /bridge/execute). Параметры те же, что у quote-best. Обычно используй quote-best.",
"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" }, "description": "contract / mint / native sentinel" },
{ "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": "должен совпадать с кошельком юзера для fromChain (JWT-bind)" },
{ "name": "toAddress", "in": "query", "required": false, "schema": { "type": "string" } },
{ "name": "slippage", "in": "query", "required": false, "schema": { "type": "number", "example": 0.005 } }
],
"responses": {
"200": {
"description": "LiFi quote (passthrough) + appCommission (наша 0.7% в токене + USDT). Поля — как у /jumper/quote-best (estimate, action, transactionRequest, appCommission)."
},
"400": { "description": "Missing required params" },
"403": { "description": "fromAddress mismatch user wallet" },
"502": { "description": "LiFi upstream / no route" },
"504": { "description": "LiFi timeout" }
}
}
},
"/bridge/execute": { "/bridge/execute": {
"post": { "post": {
"summary": "One-click bridge execute (sign + broadcast)", "summary": "One-click bridge execute (sign + broadcast)",