XMR native

This commit is contained in:
2026-10-20 12:09:58 -04:00
parent 558427545f
commit a1de67ea14
9 changed files with 341 additions and 12 deletions
+55
View File
@@ -0,0 +1,55 @@
/** 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;
}
+30
View File
@@ -0,0 +1,30 @@
import { ADDITIONAL_MARKET_FEE } from './market.ts';
import { assertXmrAmount, xmrCommission } from './xmr-amount.ts';
/** Allocation only, not proof of payment or an instruction to send/retain funds. */
export function allocateXmrSale(gross: bigint, hostingBps: number) {
assertXmrAmount(gross);
if (gross === 0n) throw new Error('Sale price must be positive; use a free transfer for gifts');
if (!Number.isSafeInteger(hostingBps) || hostingBps < 0 || hostingBps > 5000) {
throw new Error('Hosting commission must be between 0 and 5000 basis points');
}
const hosting = xmrCommission(gross, hostingBps);
const author = xmrCommission(gross, ADDITIONAL_MARKET_FEE.bps);
return { gross, hosting, author, sellerBeforeNetworkCosts: gross - hosting - author };
}
/**
* Evaluate an explicitly supplied total seller-funded network-cost budget, including
* every applicable leg. Does not choose who funds an author leg or approve a quote.
* The budget is a maximum, not revenue; actual costs/unused budget need accounting.
*/
export function assessXmrSaleBudget(gross: bigint, hostingBps: number, sellerNetworkBudget: bigint) {
const allocation = allocateXmrSale(gross, hostingBps);
assertXmrAmount(sellerNetworkBudget);
const available = allocation.sellerBeforeNetworkCosts;
const viable = sellerNetworkBudget < available;
return { ...allocation, sellerNetworkBudget, viable,
sellerMinimumNet: viable ? available - sellerNetworkBudget : 0n,
// Additional gross-independent funding required to retain at least one atomic unit.
fundingShortfall: viable ? 0n : sellerNetworkBudget - available + 1n };
}