initiukyghv

This commit is contained in:
ZOMBIIIIIII
2026-06-23 22:09:30 +03:00
parent 489d56ad36
commit 32a642b924
3 changed files with 107 additions and 5 deletions

View File

@@ -21,11 +21,43 @@ import { WalletModel } from '../models/wallet.model';
import type { ChainCode } from '../lib/address-validators';
import { proxiedFetch } from '../lib/outbound-proxy';
import { getTokensForChains } from '../lib/token-registry';
import { computeAppFee } from '../lib/app-fee';
import { formatSmallestUnits } from '../lib/amount-units';
const router = Router();
const LIFI_API_URL = 'https://li.quest/v1';
const LIFI_TIMEOUT_MS = 20_000;
/**
* Вычисляет нашу app-комиссию 0.7% (в токене + USDT) из LiFi-ответа (action.fromToken + fromAmount,
* estimate.fromAmountUSD). Чисто информативно (показать в quote) — сбор не трогаем (0.7% берётся
* атомарно в /bridge/execute), поэтому LiFi fee-параметры НЕ инжектим (иначе двойное списание).
*/
function computeJumperAppCommission(body: any): {
ratePercent: number;
inToken: { symbol: string | null; amount: string | null };
usd: number | null;
} | null {
const act = body?.action;
const est = body?.estimate;
const fromToken = act?.fromToken;
const fromAmount = act?.fromAmount ?? est?.fromAmount;
if (!fromToken || fromAmount == null) return null;
let amountToken: string | null = null;
try {
const dec = Number(fromToken.decimals ?? 0);
const feeSmallest = computeAppFee(String(fromAmount)); // fromAmount × 0.7%
amountToken = formatSmallestUnits(feeSmallest.toString(), dec);
} catch { amountToken = null; }
const fromUsd = Number(est?.fromAmountUSD);
const usd = Number.isFinite(fromUsd) ? Math.round(fromUsd * 0.007 * 1e6) / 1e6 : null;
return {
ratePercent: 0.7,
inToken: { symbol: fromToken.symbol ?? null, amount: amountToken },
usd,
};
}
/**
* LiFi chainIds → наш ChainCode. LiFi использует custom IDs для не-EVM:
* - SOL: 1151111081099710 (КАРДИНАЛЬНО отличается от Relay's 792703809)
@@ -213,6 +245,17 @@ async function proxyJumperRequest(req: Request, res: Response, _next: NextFuncti
}
try {
// /quote — доинжектим вычисляемую appCommission (наша 0.7% в токене + USDT) для видимости.
if (jumperPath === '/quote') {
try {
const obj = JSON.parse(text);
obj.appCommission = computeJumperAppCommission(obj);
res.send(JSON.stringify(obj));
} catch {
res.send(text); // не JSON — passthrough
}
return;
}
const filtered = filterJumperMetadata(jumperPath, text);
if (filtered) {
res.json(filtered);
@@ -426,7 +469,7 @@ async function handleQuoteBest(req: Request, res: Response): Promise<void> {
// Step 1 — NearIntents only.
const nearRes = await tryLiFiQuote({ key: 'allowBridges', value: 'near' });
if (nearRes.ok && nearRes.body && (nearRes.body.estimate || nearRes.body.action)) {
res.status(200).json({ ...nearRes.body, _source: 'near' });
res.status(200).json({ ...nearRes.body, _source: 'near', appCommission: computeJumperAppCommission(nearRes.body) });
return;
}
@@ -435,7 +478,7 @@ async function handleQuoteBest(req: Request, res: Response): Promise<void> {
// Step 2 — fallback на любой best route.
const bestRes = await tryLiFiQuote();
if (bestRes.ok && bestRes.body && (bestRes.body.estimate || bestRes.body.action)) {
res.status(200).json({ ...bestRes.body, _source: 'best' });
res.status(200).json({ ...bestRes.body, _source: 'best', appCommission: computeJumperAppCommission(bestRes.body) });
return;
}

View File

@@ -5,12 +5,40 @@ import { WalletModel } from '../models/wallet.model';
import type { ChainCode } from '../lib/address-validators';
import { indexRelayExecuteResponse } from '../lib/relay-trusted-cache';
import { proxiedFetch } from '../lib/outbound-proxy';
import { getDecimalsByContract, parseHumanAmount } from '../lib/amount-units';
import { getDecimalsByContract, parseHumanAmount, formatSmallestUnits } from '../lib/amount-units';
import { computeAppFee } from '../lib/app-fee';
const router = Router();
const RELAY_API_URL = 'https://api.relay.link';
const RELAY_TIMEOUT_MS = 20_000;
/**
* Вычисляет нашу app-комиссию 0.7% (в токене + USDT) из Relay-ответа `details.currencyIn`.
* Чисто информативно (показать в quote/cost-estimate) — сбор комиссии не трогаем (отдельная /app-fee tx),
* поэтому НЕ инжектим Relay appFees (иначе было бы двойное списание).
*/
function computeRelayAppCommission(full: any): {
ratePercent: number;
inToken: { symbol: string | null; amount: string | null };
usd: number | null;
} | null {
const cIn = full?.details?.currencyIn;
if (!cIn || cIn.amount == null) return null;
let amountToken: string | null = null;
try {
const dec = Number(cIn.currency?.decimals ?? 0);
const feeSmallest = computeAppFee(String(cIn.amount)); // amount × 0.7%
amountToken = formatSmallestUnits(feeSmallest.toString(), dec);
} catch { amountToken = null; }
const inUsd = Number(cIn.amountUsd);
const usd = Number.isFinite(inUsd) ? Math.round(inUsd * 0.007 * 1e6) / 1e6 : null;
return {
ratePercent: 0.7,
inToken: { symbol: cIn.currency?.symbol ?? null, amount: amountToken },
usd,
};
}
// chainId → ChainCode. Relay использует EVM chainIds + custom большие для не-EVM.
const RELAY_CHAINID_TO_CHAIN: Record<number, ChainCode> = {
1: 'ETH',
@@ -235,6 +263,8 @@ async function proxyRelayRequest(req: Request, res: Response, next: NextFunction
app: fees.app ?? null,
total: { amountUsd: totalUsd },
},
// Наша app-комиссия 0.7% в токене + USDT (информативно; собирается отдельной /app-fee tx).
appCommission: computeRelayAppCommission(full),
rate: full?.details?.rate ?? null,
priceImpactPct: full?.details?.totalImpact?.percent ?? null,
priceImpactUsd: full?.details?.totalImpact?.usd ?? null,
@@ -250,6 +280,19 @@ async function proxyRelayRequest(req: Request, res: Response, next: NextFunction
return;
}
// /quote — доинжектим вычисляемую appCommission (наша 0.7% в токене + USDT) для видимости в Swagger.
// Остальное (steps[], fees, details) проходит без изменений. Сбор комиссии не меняется.
if (relayPath === '/quote') {
try {
const obj = JSON.parse(text);
obj.appCommission = computeRelayAppCommission(obj);
res.send(JSON.stringify(obj));
} catch {
res.send(text); // не JSON — passthrough как есть
}
return;
}
// Send raw text если это валидный JSON, иначе обернём
try {
res.send(text);

View File

@@ -885,6 +885,22 @@
}
}
},
"appCommission": {
"type": "object",
"nullable": true,
"description": "Наша app-комиссия 0.7% (в токене + USDT). Информативно; собирается отдельной /app-fee tx. Считается из details.currencyIn. Есть и в /relay/quote.",
"properties": {
"ratePercent": { "type": "number", "example": 0.7 },
"inToken": {
"type": "object",
"properties": {
"symbol": { "type": "string", "nullable": true, "example": "USDT" },
"amount": { "type": "string", "nullable": true, "example": "0.7", "description": "0.7% во входном токене" }
}
},
"usd": { "type": "number", "nullable": true, "example": 0.7, "description": "та же 0.7% в USDT (≈USD)" }
}
},
"rate": {
"type": "string",
"nullable": true
@@ -2862,7 +2878,7 @@
},
"responses": {
"200": {
"description": "Quote с steps[], fees, details, breakdown"
"description": "Quote с steps[], fees, details, breakdown + appCommission (наша 0.7% в токене+USDT, информативно — считается из details.currencyIn)"
},
"502": {
"description": "Relay upstream error (приложен upstream JSON для деталей)"
@@ -3921,7 +3937,7 @@
],
"responses": {
"200": {
"description": "LiFi quote + _source field (near|best)"
"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 }"
},
"400": {
"description": "Missing required params"