Files

39 lines
2.2 KiB
TypeScript
Executable File

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};
/** Support only mainnet standard/subaddresses. Integrated/payment-ID addresses need a separate policy. */
async function validateMainnetAddressPrefix(address:string):Promise<number>{
if(typeof address!=='string'||address.length!==95)throw new Error('Expected a mainnet standard XMR address or subaddress');
const bytes:number[]=[];
for(let start=0;start<address.length;start+=11){
const chunk=address.slice(start,start+11),size=decodedSizes[chunk.length];
if(!size)throw new Error('Invalid XMR Base58 block');
let value=0n;
for(const c of chunk){const index=alphabet.indexOf(c);if(index<0)throw new Error('Invalid XMR Base58 alphabet');value=value*58n+BigInt(index);}
const output=Array<number>(size).fill(0);
for(let i=size-1;i>=0;i--){output[i]=Number(value&255n);value>>=8n;}
if(value)throw new Error('XMR Base58 block overflow');bytes.push(...output);
}
if(bytes.length!==69||![18,42].includes(bytes[0]))throw new Error('XMR address network/type is not supported');
const decoded=Uint8Array.from(bytes),expected=(await keccak(decoded.slice(0,-4),256)).slice(0,8);
if(Buffer.from(decoded.slice(-4)).toString('hex')!==expected)throw new Error('XMR checksum mismatch');
return bytes[0];
}
export async function validateXmrAddress(address:string):Promise<string>{
await validateMainnetAddressPrefix(address);
return address;
}
export async function validateXmrSubaddress(address:string):Promise<string>{
if(await validateMainnetAddressPrefix(address)!==42)throw new Error('Expected a Monero mainnet subaddress');
return address;
}
export function atomicXmr(value:string):bigint{
return parseXmr(value);
}
export function fiatCents(value:string):number{
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'));
}