87 lines
3.9 KiB
TypeScript
87 lines
3.9 KiB
TypeScript
/**
|
||
* 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;
|
||
}
|