/** Exact, browser-safe amounts. No wallet RPC, exchange rate, custody or signing policy. */ export const XMR_ATOMIC_UNITS = 1_000_000_000_000n; // Preserve the existing processor decimal parser's ten-whole-digit encoding bound. // This is NOT a supply limit, an exposure approval, or a wallet-RPC uint64 guarantee. export const MAX_XMR_AMOUNT = 10_000_000_000n * XMR_ATOMIC_UNITS - 1n; export const XMR_RPC_UINT64_MAX = (1n << 64n) - 1n; export function assertXmrAmount(amount: bigint): void { if (typeof amount !== 'bigint' || amount < 0n || amount > MAX_XMR_AMOUNT) { throw new Error('Invalid bounded atomic XMR amount'); } } /** Decimal user/processor input; never coerce, trim, round, or accept floating point. */ export function parseXmr(value: string): bigint { if (typeof value !== 'string' || value.length > 23 || !/^(0|[1-9]\d{0,9})(\.\d{1,12})?(?![\s\S])/.test(value)) { throw new Error('Invalid bounded XMR decimal'); } const [whole, fraction = ''] = value.split('.'); return BigInt(whole) * XMR_ATOMIC_UNITS + BigInt(fraction.padEnd(12, '0')); } /** Canonical display/processor decimal: always twelve fractional places. */ export function formatXmr(amount: bigint): string { assertXmrAmount(amount); return `${amount / XMR_ATOMIC_UNITS}.${(amount % XMR_ATOMIC_UNITS).toString().padStart(12, '0')}`; } /** JSON/signature/storage representation: canonical base-ten integer string, not a JSON number. */ export function parseAtomicXmr(value: string): bigint { if (typeof value !== 'string' || value.length > 22 || !/^(0|[1-9]\d{0,21})(?![\s\S])/.test(value)) { throw new Error('Invalid canonical atomic XMR string'); } const amount = BigInt(value); assertXmrAmount(amount); return amount; } export function atomicXmrString(amount: bigint): string { assertXmrAmount(amount); return amount.toString(); } /** RPC amounts need lossless JSON encoding too; Number(amount) is not safe. */ export function assertXmrRpcAmount(amount: bigint): void { assertXmrAmount(amount); if (amount > XMR_RPC_UINT64_MAX) throw new Error('XMR amount exceeds wallet-RPC uint64'); } /** Independently round commissions down; all sub-atomic remainders stay with the seller. */ export function xmrCommission(gross: bigint, bps: number): bigint { assertXmrAmount(gross); if (!Number.isSafeInteger(bps) || bps < 0 || bps > 10_000) throw new Error('Invalid commission basis points'); return gross * BigInt(bps) / 10_000n; }