import type { Asset, Chain } from './types'; import { CHAIN_ASSET } from './types'; import { pegCryptoAmount } from './peg'; import { applyCentDust } from './dust'; import { deriveBaseAddress, deriveTronAddress } from './derivation'; export interface WalletSource { kind: 'xpub' | 'static'; xpub?: string | null; staticAddress?: string | null; /** Next unused derivation index (xpub wallets). */ nextIndex: number; } export interface BuildOptionInput { chain: Chain; /** Decimal string, e.g. "100" or "99.50". */ fiatAmount: string; /** Fiat per whole token, 1e6-scaled (RateQuote.rateUnits). */ rateUnits: bigint; wallet: WalletSource; /** Static mode: reports whether a candidate dusted amount collides with an active invoice on the same address. */ isAmountTaken?: (candidate: bigint) => boolean; random?: () => number; } export interface BuiltOption { chain: Chain; asset: Asset; cryptoAmount: bigint; address: string; derivationIndex: number | null; dustApplied: boolean; } /** * Build one invoice option (address + pegged amount) for the asset the payer selected. * Options are created lazily — the caller invokes this only on first selection. */ export function buildInvoiceOption(input: BuildOptionInput): BuiltOption { const asset = CHAIN_ASSET[input.chain]; const baseAmount = pegCryptoAmount(input.fiatAmount, input.rateUnits); if (input.wallet.kind === 'xpub') { if (!input.wallet.xpub) throw new Error('xpub wallet is missing its extended public key'); const index = input.wallet.nextIndex; const address = input.chain === 'base' ? deriveBaseAddress(input.wallet.xpub, index) : deriveTronAddress(input.wallet.xpub, index); return { chain: input.chain, asset, cryptoAmount: baseAmount, address, derivationIndex: index, dustApplied: false }; } if (!input.wallet.staticAddress) throw new Error('static wallet is missing its address'); const cryptoAmount = applyCentDust(baseAmount, input.isAmountTaken ?? (() => false), input.random); return { chain: input.chain, asset, cryptoAmount, address: input.wallet.staticAddress, derivationIndex: null, dustApplied: true, }; }