/** * App fee 0.7% — single source of truth для всех chains. * * Применяется в: * - swap-orchestrator.service.ts:executeBsc/Sol/Trx (custodial swap, atomic fee tx ДО main swap) * - bridge-execute.service.ts:executeEvm/Sol/Tron (bridge atomic fee) * - controllers/wallet.controller.ts:signRawEvmTx (Relay EVM bridge, when client передаёт bridgeAmount) * - controllers/wallet.controller.ts:appFeeTransfer (NEW endpoint /wallets/{chain}/app-fee для Relay frontend hook) * * Wallets захардкожены — НЕ через env. Security: нельзя переопределить через body или env, * нельзя перенаправить fee на adversary'ский адрес. Single source of truth для аудита. * * Address per chain family: * EVM (ETH+BSC) → 0xeb9f... (один EVM wallet, оба chain'а) * SOL → DQkQ... (Solana base58) * TRX → TRwp... (Tron base58) * BTC → bc1q... (bech32 P2WPKH, отдельная fee tx перед bridge) * * Изменение wallet → требует code review + новый release. */ import type { ChainCode } from './address-validators'; /** EVM (ETH + BSC). Single address для обеих chain. Заменил старый BSC-only 0xeDEb... */ export const APP_FEE_WALLET_EVM = '0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68'; /** Solana base58. */ export const APP_FEE_WALLET_SOL = 'DQkQegoX698XkcXZ6VX9P1qUpbV64Sgjz1BCPFgfWpjD'; /** Tron base58 (с T-prefix). */ export const APP_FEE_WALLET_TRX = 'TRwpFjnfMBe4aDJbHYEqeUVCG1auF8wFXP'; /** Bitcoin Taproot (bech32m P2TR, bc1p). Клиентские кошельки остаются SegWit — они платят сюда. * Требует initEccLib(ecc) (см. lib/btc-ecc.ts), иначе построение выхода на bc1p падает. */ export const APP_FEE_WALLET_BTC = 'bc1pgak39caltz9j3r3wk9c0sy89a29hax9navxvdvaj3nmwuxpgp6fq2rxm4m'; /** 70 bps = 0.7%. Изменение требует code review. */ export const APP_FEE_BPS = 70n; /** 10000 = 100% в bps notation. */ export const APP_FEE_DENOMINATOR = 10000n; /** * Resolve fee recipient для chain. */ export function getAppFeeWallet(chain: ChainCode): string { if (chain === 'ETH' || chain === 'BSC') return APP_FEE_WALLET_EVM; if (chain === 'SOL') return APP_FEE_WALLET_SOL; if (chain === 'TRX') return APP_FEE_WALLET_TRX; if (chain === 'BTC') return APP_FEE_WALLET_BTC; throw new Error(`getAppFeeWallet: unsupported chain '${chain}'`); } /** * Check если для chain есть fee wallet. */ export function hasAppFee(chain: ChainCode): boolean { return chain === 'ETH' || chain === 'BSC' || chain === 'SOL' || chain === 'TRX' || chain === 'BTC'; } /** * computeAppFee(amountSmallest): возвращает 0.7% от amount в smallest units (BigInt). * * Использует BigInt — никаких float precision losses. * Если amount < 144 (= 10000/70) → fee округляется до 0 (BigInt integer division). * Callers должны skip fee tx если result = 0n. * * @param amountSmallest строка цифр (positive integer in smallest units) * @returns BigInt fee amount * @throws если amount не валидный positive integer string */ export function computeAppFee(amountSmallest: string): bigint { if (typeof amountSmallest !== 'string' || !/^\d+$/.test(amountSmallest)) { throw new Error(`computeAppFee: invalid amount "${amountSmallest}" (must be positive integer string)`); } return (BigInt(amountSmallest) * APP_FEE_BPS) / APP_FEE_DENOMINATOR; } /** * computeAmountAfterFee(amount): возвращает (amount - fee) — то что реально пойдёт * в swap/bridge после удержания 0.7%. */ export function computeAmountAfterFee(amountSmallest: string): bigint { const fee = computeAppFee(amountSmallest); return BigInt(amountSmallest) - fee; }