inithipk;
This commit is contained in:
@@ -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 {
|
||||||
...(init as any),
|
return (await undiciFetch(input as any, {
|
||||||
dispatcher: agent,
|
...(init as any),
|
||||||
}) as unknown as Response;
|
dispatcher: agent,
|
||||||
|
})) 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)",
|
||||||
|
|||||||
Reference in New Issue
Block a user