This commit is contained in:
ZOMBIIIIIII
2026-05-28 13:51:30 +03:00
parent d2086b86e3
commit e86ff7c063
25 changed files with 4214 additions and 437 deletions

View File

@@ -0,0 +1,83 @@
/**
* 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 → не collectable (no wallet provided — bridge in BTC не имеет fee tx layer)
*
* Изменение 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';
/** 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. Throws для unsupported chain (BTC).
*/
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;
throw new Error(
`getAppFeeWallet: chain '${chain}' has no fee wallet (BTC bridges не имеют collectable fee layer)`,
);
}
/**
* Check если для chain есть fee wallet. Used для conditional fee tx (skip BTC).
*/
export function hasAppFee(chain: ChainCode): boolean {
return chain === 'ETH' || chain === 'BSC' || chain === 'SOL' || chain === 'TRX';
}
/**
* 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;
}