initkjbnkhj

This commit is contained in:
ZOMBIIIIIII
2026-06-23 21:30:54 +03:00
parent 6f01f4af34
commit 489d56ad36
6 changed files with 142 additions and 70 deletions

View File

@@ -1057,12 +1057,18 @@ export const WalletController = {
amount: raw.networkFee.amount,
amountFormatted: fmtUnits(raw.networkFee.amount, networkFeeAssetDecimals),
amountUsd: networkFeeUsd,
// Сетевая комиссия в эквиваленте валюты свапа (входного токена) + символ.
amountInInputToken: (networkFeeUsd != null && fromPriceUsd != null && fromPriceUsd > 0)
? trimNum(networkFeeUsd / fromPriceUsd) : null,
inputTokenSymbol: fromSymbol,
},
// App fee 0.7% (BSC only) — сервер шлёт это через off-chain double-tx ПЕРЕД swap.
app: raw.appFee ? {
asset: raw.appFee.asset,
amount: raw.appFee.amount,
amountFormatted: fmtUnits(raw.appFee.amount, raw.fromDecimals),
// USDT-эквивалент 0.7% (комиссия уже в токене через amountFormatted).
amountUsd: toUsd(raw.appFee.amount, raw.fromDecimals, fromPriceUsd),
recipient: raw.appFee.recipient,
} : null,
// dex fee включён в expectedOut (Pancake 0.25%, SunSwap 0.3%+0.7% fee router, Jupiter platform varied).
@@ -1423,26 +1429,34 @@ export const WalletController = {
feeAssetSymbolForPrice = 'SOL';
}
// USD enrichment fee asset
// USD enrichment: сетевой fee-asset + входной токен (для эквивалента в валюте свапа + USDT нашей 0.7%)
const inputSymbol = raw.appFee?.asset ?? null; // символ входного токена (= asset нашей 0.7%)
const priceMap = await getPricesBySymbols([
{ chain, symbol: feeAssetSymbolForPrice },
...(inputSymbol ? [{ chain, symbol: inputSymbol }] : []),
]).catch(() => new Map<string, number | null>());
const feePriceUsd = priceMap.get(`${chain}:${feeAssetSymbolForPrice}`) ?? null;
const inputPriceUsd = inputSymbol ? (priceMap.get(`${chain}:${inputSymbol}`) ?? null) : null;
const usdFromRaw = (rawAmt: string, dec: number, price: number | null): number | null => {
if (price == null) return null;
try {
const big = BigInt(rawAmt);
const divisor = 10n ** BigInt(dec);
return (Number(big / divisor) + Number(big % divisor) / Number(divisor)) * price;
} catch { return null; }
};
const feeAssetDecimals = raw.networkFee.asset === 'TRX' ? 6
: raw.networkFee.asset === 'SOL' ? 9
: 18;
const amountFormatted = formatSmallestUnits(raw.networkFee.amount, feeAssetDecimals);
let amountUsd: number | null = null;
if (feePriceUsd != null) {
try {
const big = BigInt(raw.networkFee.amount);
const divisor = 10n ** BigInt(feeAssetDecimals);
const whole = Number(big / divisor);
const frac = Number(big % divisor) / Number(divisor);
amountUsd = (whole + frac) * feePriceUsd;
} catch { amountUsd = null; }
}
const amountUsd = usdFromRaw(raw.networkFee.amount, feeAssetDecimals, feePriceUsd);
// Сетевая комиссия в эквиваленте входного токена (валюты свапа).
const networkInInputToken = (amountUsd != null && inputPriceUsd != null && inputPriceUsd > 0)
? trimNum(amountUsd / inputPriceUsd) : null;
// Наша 0.7% в USDT (в токене — formatSmallestUnits ниже).
const appFeeUsd = raw.appFee ? usdFromRaw(raw.appFee.amount, raw.fromDecimals, inputPriceUsd) : null;
res.json({
success: true,
@@ -1453,7 +1467,15 @@ export const WalletController = {
amount: raw.networkFee.amount,
amountFormatted,
amountUsd,
amountInInputToken: networkInInputToken,
inputTokenSymbol: inputSymbol,
},
appFee: raw.appFee ? {
asset: raw.appFee.asset,
amount: raw.appFee.amount,
amountFormatted: formatSmallestUnits(raw.appFee.amount, raw.fromDecimals),
amountUsd: appFeeUsd,
} : null,
total: { amountUsd },
route: raw.route,
approveRequired: raw.approveRequired,

View File

@@ -293,8 +293,10 @@ export function getCoingeckoId(chain: ChainCode, symbol: string): string | null
const upper = String(symbol || '').toUpperCase();
if (!upper) return null;
// Native — symbol === chain code (BTC, ETH, ...).
if (upper === chain) return NATIVE_COINGECKO_IDS[chain] ?? null;
// Native — symbol === chain code (BTC/ETH/SOL/TRX) ИЛИ символ нативной монеты.
// На BSC нативка = BNB (символ ≠ chain code 'BSC') — без этой ветки цена BNB не резолвилась
// (→ сетевая/0.7% комиссия в BNB приходила без USD). NATIVE_SYMBOLS[BSC]='BNB'.
if (upper === chain || upper === NATIVE_SYMBOLS[chain]) return NATIVE_COINGECKO_IDS[chain] ?? null;
if (chain === 'ETH' || chain === 'BSC') {
const t = getEvmTokens(chain).find((x) => x.symbol.toUpperCase() === upper);

View File

@@ -22,6 +22,7 @@ import {
} from '@solana/web3.js';
import { getAppFeeWallet, computeAppFee } from '../lib/app-fee';
import { formatSmallestUnits } from '../lib/amount-units';
import { getPricesBySymbols } from './price-oracle.service';
import { signAndBroadcastSolBuilt } from './wallet-signer.service';
import { StakingError } from './staking.service';
import { resolveStakeValidator } from '../lib/sol-validator';
@@ -47,6 +48,7 @@ export interface SolStakeQuote {
amountLamportsHuman: string;
appFeeLamports: string;
appFeeLamportsHuman: string;
appFeeUsd: number | null; // наша 0.7% в USDT (≈USD); null если цена SOL недоступна
stakeLamports: string;
stakeLamportsHuman: string;
validator: string;
@@ -62,11 +64,17 @@ export async function quoteSolStake(amountLamports: string): Promise<SolStakeQuo
const stake = BigInt(amountLamports) - fee;
if (stake <= 0n) throw badRequest('amount too small after 0.7% fee');
const validator = await resolveStakeValidator(getConn());
// USDT-эквивалент комиссии (≈USD). Цена SOL через whitelist; null если недоступна.
let solPrice: number | null = null;
try { solPrice = (await getPricesBySymbols([{ chain: 'SOL', symbol: 'SOL' }])).get('SOL:SOL') ?? null; } catch { /* optional */ }
const appFeeHuman = sol(fee.toString());
const appFeeUsd = solPrice != null ? Math.round(Number(appFeeHuman) * solPrice * 1e6) / 1e6 : null;
return {
amountLamports,
amountLamportsHuman: sol(amountLamports),
appFeeLamports: fee.toString(),
appFeeLamportsHuman: sol(fee.toString()),
appFeeLamportsHuman: appFeeHuman,
appFeeUsd,
stakeLamports: stake.toString(),
stakeLamportsHuman: sol(stake.toString()),
validator,

View File

@@ -23,6 +23,7 @@ import {
} from '../lib/defi-contracts';
import { computeAppFee } from '../lib/app-fee';
import { formatSmallestUnits } from '../lib/amount-units';
import { getPricesBySymbols } from './price-oracle.service';
import { signAndBroadcastEvmFeeTx, signAndBroadcastRawEvm } from './wallet-signer.service';
import { signAndBroadcastEvmApprove } from './wallet-signer-bridge';
@@ -52,6 +53,7 @@ export interface LidoQuote {
amountWeiHuman: string;
appFeeWei: string;
appFeeWeiHuman: string;
appFeeUsd: number | null; // наша 0.7% в USDT (≈USD); null если цена ETH недоступна
stakeAmountWei: string;
stakeAmountWeiHuman: string;
expectedStEthWei: string;
@@ -68,11 +70,17 @@ export async function quoteLidoStake(amountWei: string): Promise<LidoQuote> {
const stakeAmount = BigInt(amountWei) - fee;
if (stakeAmount <= 0n) throw badRequest('amount too small after 0.7% fee');
const apr = await fetchLidoApr();
// USDT-эквивалент комиссии (≈USD). Цена ETH через whitelist; null если недоступна.
let ethPrice: number | null = null;
try { ethPrice = (await getPricesBySymbols([{ chain: 'ETH', symbol: 'ETH' }])).get('ETH:ETH') ?? null; } catch { /* optional */ }
const appFeeHuman = eth(fee.toString());
const appFeeUsd = ethPrice != null ? Math.round(Number(appFeeHuman) * ethPrice * 1e6) / 1e6 : null;
return {
amountWei,
amountWeiHuman: eth(amountWei),
appFeeWei: fee.toString(),
appFeeWeiHuman: eth(fee.toString()),
appFeeWeiHuman: appFeeHuman,
appFeeUsd,
stakeAmountWei: stakeAmount.toString(),
stakeAmountWeiHuman: eth(stakeAmount.toString()),
// Lido минтит stETH ~1:1 к внесённому ETH (после fee)

View File

@@ -56,6 +56,12 @@ function badRequest(msg: string): StakingError {
const MINT_GAS_LIMIT = '600000';
const REMOVE_GAS_LIMIT = '500000';
// Оценочные gas-лимиты шагов LP-депозита (для networkFee в quote). Соответствуют реальным:
// mint=MINT_GAS_LIMIT, approve≈APPROVE_GAS_LIMIT, fee≈ERC20 transfer, wrap≈WETH.deposit.
const EST_GAS_MINT = 600_000n;
const EST_GAS_APPROVE = 80_000n;
const EST_GAS_FEE = 65_000n;
const EST_GAS_WRAP = 60_000n;
const DEFAULT_SLIPPAGE_BPS = 100n; // 1%
const MAX_SLIPPAGE_BPS = 500n; // 5% — верхняя граница (дефолт 1%)
const SLIPPAGE_DENOM = 10000n;
@@ -220,6 +226,13 @@ export interface AddQuote {
appFee1Usd: number | null;
appFeeTotalUsd: number | null;
appFeeTotalEth: string | null; // комиссия в монете сети (ETH-эквивалент)
// ── Оценка сетевой (gas) комиссии всего LP-депозита (wrap?+fee×ноги+approve×ноги+mint) ──
networkFee: {
estGasUnits: string;
gasPriceGwei: string | null;
amountEthHuman: string | null; // оценка газа в ETH
amountUsd: number | null; // та же оценка в USD
};
// ── Проверка баланса (только если передан ownerAddress) ──
balance?: {
token0: LegBalance;
@@ -328,6 +341,25 @@ export async function quoteAdd(p: AddParams): Promise<AddQuote> {
price1 = isWeth(pool.token1) ? ethPrice : (priceMap.get(`ETH:${pool.sym1}`) ?? null);
} catch { /* prices optional */ }
// ── Оценка сетевой (gas) комиссии: суммируем gas-лимиты шагов, которые реально пойдут ──
const t0IsWethN = isWeth(pool.token0);
const t1IsWethN = isWeth(pool.token1);
let estGas = EST_GAS_MINT; // mint всегда
if (in0 > 0n) estGas += EST_GAS_APPROVE + EST_GAS_FEE; // нога token0: approve + fee
if (in1 > 0n) estGas += EST_GAS_APPROVE + EST_GAS_FEE; // нога token1
if (p.useNativeEth && (t0IsWethN || t1IsWethN)) estGas += EST_GAS_WRAP; // авто-wrap ETH→WETH
let gasPriceWei = 0n;
try {
const fd = await provider.getFeeData();
const gp = fd.maxFeePerGas ?? fd.gasPrice ?? null;
if (gp) gasPriceWei = BigInt(gp.toString());
} catch { /* gas price optional */ }
const networkFeeWei = estGas * gasPriceWei;
const networkFeeEthHuman = gasPriceWei > 0n ? formatSmallestUnits(networkFeeWei.toString(), 18) : null;
const networkFeeUsd = (networkFeeEthHuman != null && ethPrice != null)
? roundUsd(Number(networkFeeEthHuman) * ethPrice) : null;
const gasPriceGwei = gasPriceWei > 0n ? formatSmallestUnits(gasPriceWei.toString(), 9) : null;
const fee0Usd = price0 != null ? roundUsd(Number(fee0Human) * price0) : null;
const fee1Usd = price1 != null ? roundUsd(Number(fee1Human) * price1) : null;
const usdParts = [fee0Usd, fee1Usd].filter((v): v is number => v != null);
@@ -405,6 +437,12 @@ export async function quoteAdd(p: AddParams): Promise<AddQuote> {
appFee1Usd: fee1Usd,
appFeeTotalUsd,
appFeeTotalEth,
networkFee: {
estGasUnits: estGas.toString(),
gasPriceGwei,
amountEthHuman: networkFeeEthHuman,
amountUsd: networkFeeUsd,
},
...(balance ? { balance } : {}),
};
}

View File

@@ -670,56 +670,30 @@
"network": {
"type": "object",
"properties": {
"asset": {
"type": "string",
"example": "BNB"
},
"amount": {
"type": "string",
"example": "65000000000000"
},
"amountFormatted": {
"type": "string",
"example": "0.000065"
},
"amountUsd": {
"type": "number",
"nullable": true,
"example": 0.04
}
"asset": { "type": "string", "example": "BNB" },
"amount": { "type": "string", "example": "65000000000000" },
"amountFormatted": { "type": "string", "example": "0.000065", "description": "Сетевая комиссия в native-монете" },
"amountUsd": { "type": "number", "nullable": true, "example": 0.04 },
"amountInInputToken": { "type": "string", "nullable": true, "example": "0.04", "description": "Та же сетевая комиссия в эквиваленте валюты свапа (входного токена)" },
"inputTokenSymbol": { "type": "string", "example": "USDT", "description": "Символ входного токена (валюты свапа)" }
}
},
"total": {
"type": "object",
"properties": {
"amountUsd": {
"type": "number",
"nullable": true,
"example": 0.04
}
"amountUsd": { "type": "number", "nullable": true, "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.",
"description": "Наша комиссия 0.7%. Списывается отдельной tx ПЕРЕД свапом.",
"properties": {
"asset": {
"type": "string",
"example": "BNB"
},
"amount": {
"type": "string",
"example": "70000000000000"
},
"amountFormatted": {
"type": "string",
"example": "0.00007"
},
"recipient": {
"type": "string",
"example": "0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718"
}
"asset": { "type": "string", "example": "USDT", "description": "Токен комиссии (= входной токен)" },
"amount": { "type": "string", "example": "70000000000000" },
"amountFormatted": { "type": "string", "example": "0.07", "description": "Комиссия 0.7% в токене" },
"amountUsd": { "type": "number", "nullable": true, "example": 0.07, "description": "Та же 0.7% в USDT-эквиваленте" },
"recipient": { "type": "string", "example": "0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68" }
}
}
}
@@ -818,21 +792,25 @@
},
"fee": {
"type": "object",
"description": "Сетевая (gas) комиссия",
"properties": {
"asset": {
"type": "string",
"example": "BNB"
},
"amount": {
"type": "string"
},
"amountFormatted": {
"type": "string"
},
"amountUsd": {
"type": "number",
"nullable": true
"asset": { "type": "string", "example": "BNB" },
"amount": { "type": "string" },
"amountFormatted": { "type": "string", "description": "в native-монете" },
"amountUsd": { "type": "number", "nullable": true },
"amountInInputToken": { "type": "string", "nullable": true, "description": "та же сетевая комиссия в эквиваленте валюты свапа (входного токена)" },
"inputTokenSymbol": { "type": "string", "nullable": true, "example": "USDT" }
}
},
"appFee": {
"type": "object",
"nullable": true,
"description": "Наша комиссия 0.7% в токене + USDT",
"properties": {
"asset": { "type": "string", "example": "USDT" },
"amount": { "type": "string" },
"amountFormatted": { "type": "string", "description": "0.7% в токене" },
"amountUsd": { "type": "number", "nullable": true, "description": "та же 0.7% в USDT" }
}
},
"total": {
@@ -1154,7 +1132,13 @@
"type": "string"
},
"appFeeWeiHuman": {
"type": "string"
"type": "string",
"description": "наша 0.7% в токене (ETH). SOL-ответ: appFeeLamportsHuman"
},
"appFeeUsd": {
"type": "number",
"nullable": true,
"description": "наша 0.7% в USDT (≈USD). Есть и в SOL-ответе (appFeeUsd). null если цена недоступна"
},
"stakeAmountWei": {
"type": "string"
@@ -1463,7 +1447,17 @@
"appFee0Usd": { "type": "number", "nullable": true },
"appFee1Usd": { "type": "number", "nullable": true },
"appFeeTotalUsd": { "type": "number", "nullable": true },
"appFeeTotalEth": { "type": "string", "nullable": true, "description": "суммарная комиссия в монете сети (ETH)" },
"appFeeTotalEth": { "type": "string", "nullable": true, "description": "суммарная наша 0.7% в монете сети (ETH)" },
"networkFee": {
"type": "object",
"description": "Оценка сетевой (gas) комиссии всего LP-депозита (wrap?+fee×ноги+approve×ноги+mint) при текущем gasPrice",
"properties": {
"estGasUnits": { "type": "string", "example": "950000" },
"gasPriceGwei": { "type": "string", "nullable": true, "example": "5.2" },
"amountEthHuman": { "type": "string", "nullable": true, "example": "0.00494", "description": "оценка газа в ETH" },
"amountUsd": { "type": "number", "nullable": true, "example": 12.3, "description": "та же оценка в USD" }
}
},
"balance": {
"type": "object",
"description": "есть только если у юзера есть ETH-кошелёк",