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
+6 -5
View File
@@ -1,4 +1,6 @@
import { ADDITIONAL_MARKET_FEE } from '../../shared/market.ts';
import { formatXmr, XMR_ATOMIC_UNITS } from '../../shared/xmr-amount.ts';
import { allocateXmrSale } from '../../shared/xmr-pricing.ts';
/** No live payment adapter is reachable in this release. Never return a payable mock address. */
export function paymentMode(env: NodeJS.ProcessEnv = process.env): 'mock' {
@@ -7,16 +9,15 @@ export function paymentMode(env: NodeJS.ProcessEnv = process.env): 'mock' {
return mode;
}
export function xmrString(atomic: bigint): string {
return `${atomic / 1_000_000_000_000n}.${(atomic % 1_000_000_000_000n).toString().padStart(12, '0')}`;
return formatXmr(atomic);
}
/** Deterministic simulation ONLY. Round gross up and both independent seller fees down. */
export function mockQuote(priceCents: number, feeBps: number, usdCentsPerXmr: number) {
const rate = BigInt(usdCentsPerXmr);
const gross = (BigInt(priceCents) * 1_000_000_000_000n + rate - 1n) / rate;
const fee = gross * BigInt(feeBps) / 10_000n;
const additionalFee = gross * BigInt(ADDITIONAL_MARKET_FEE.bps) / 10_000n;
const gross = (BigInt(priceCents) * XMR_ATOMIC_UNITS + rate - 1n) / rate;
const { hosting: fee, author: additionalFee, sellerBeforeNetworkCosts } = allocateXmrSale(gross, feeBps);
return { invoice: { provider: 'mock' as const, amountXmr: xmrString(gross), address: null,
label: 'SIMULATION ONLY — no Monero payment address. Never send real XMR.' },
feeXmr: xmrString(fee), sellerNetXmr: xmrString(gross - fee - additionalFee),
feeXmr: xmrString(fee), sellerNetXmr: xmrString(sellerBeforeNetworkCosts),
additionalFee: { ...ADDITIONAL_MARKET_FEE, amountXmr: xmrString(additionalFee), payout: 'not-started' as const } };
}
+3 -2
View File
@@ -3,6 +3,7 @@ import { mkdir,open,unlink,chmod } from 'node:fs/promises';
import { join } from 'node:path';
import { z } from 'zod';
import { ADDITIONAL_MARKET_FEE } from '../../shared/market.ts';
import { allocateXmrSale } from '../../shared/xmr-pricing.ts';
import { NETWORK } from '../../shared/network.ts';
import { CHAIN_ID,sha256,verifySignature,transactionId } from '../testnet/consensus.ts';
import { atomicXmr,fiatCents,validateXmrAddress } from './xmr.ts';
@@ -107,9 +108,9 @@ export class SettlementLab {
s.quote=await this.processor.createInvoice(id,s.terms.priceCents);
await validateXmrAddress(s.quote.address);const gross=atomicXmr(s.quote.amountXmr);
if(!s.quote.id||gross<=0n)throw new Error('Invalid quote');
const hosting=gross*BigInt(s.terms.feeBps)/10000n,fee=gross*100n/10000n;
const {hosting,author:fee,sellerBeforeNetworkCosts}=allocateXmrSale(gross,s.terms.feeBps);
s.hostingXmr=xmrString(hosting);
s.seller={amount:xmrString(gross-hosting-fee),destination:s.terms.sellerXmr,state:'ready'};
s.seller={amount:xmrString(sellerBeforeNetworkCosts),destination:s.terms.sellerXmr,state:'ready'};
s.fee={amount:xmrString(fee),destination:ADDITIONAL_MARKET_FEE.address,state:fee===0n?'skipped':'ready'};
s.quoteDigest=sha256(buyerInvoiceMessage(s));s.status='quote';this.save(s);return s;
}catch{return this.hold(s,'invoice-create-ambiguous-or-invalid');}
+3 -3
View File
@@ -1,4 +1,5 @@
import { keccak } from 'hash-wasm';
import { parseXmr } from '../../shared/xmr-amount.ts';
const alphabet='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const decodedSizes:Record<number,number>={2:1,3:2,5:3,6:4,7:5,9:6,10:7,11:8};
@@ -21,10 +22,9 @@ export async function validateXmrAddress(address:string):Promise<string>{
return address;
}
export function atomicXmr(value:string):bigint{
if(typeof value!=='string'||!/^(0|[1-9]\d{0,9})(\.\d{1,12})?$/.test(value))throw new Error('Invalid bounded XMR decimal');
const [whole,fraction='']=value.split('.');return BigInt(whole)*1_000_000_000_000n+BigInt(fraction.padEnd(12,'0'));
return parseXmr(value);
}
export function fiatCents(value:string):number{
if(typeof value!=='string'||!/^(0|[1-9]\d{0,6})(\.\d{1,2})?$/.test(value))throw new Error('Invalid USD decimal');
if(typeof value!=='string'||value.length>10||!/^(0|[1-9]\d{0,6})(\.\d{1,2})?(?![\s\S])/.test(value))throw new Error('Invalid USD decimal');
const [whole,fraction='']=value.split('.');return Number(whole)*100+Number(fraction.padEnd(2,'0'));
}