init
This commit is contained in:
83
apps/api/src/lib/app-fee.ts
Normal file
83
apps/api/src/lib/app-fee.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user