/** Actual positive payer legs of a single paid physical Treachery auction. * The engine supplies already-authorized, funded contributors and owns the * debit, escrow, income responses and any later diversion or cancellation. */ export type AuctionPaymentInput = Readonly<{ advanced: boolean; free: boolean; price: number; buyer: string; emperor: string | null; /** Null is an ordinary auction; a Richese seller may retain its own card. */ seller: string | null; /** Existing seller-income prevention may route the entire invoice to Bank. */ bankOnly?: boolean; contributions: readonly Readonly<{ payer: string; amount: number }>[]; }>; export type AuctionPaymentLeg = { payer: string; amount: number; recipient: 'bank' | 'emperor' | 'seller'; }; export class AuctionPaymentRoutingError extends Error { constructor(message: string) { super(message); this.name = 'AuctionPaymentRoutingError'; } } function check(condition: unknown, message: string): asserts condition { if (!condition) throw new AuctionPaymentRoutingError(message); } const id = (value: unknown): value is string => typeof value === 'string' && value.length > 0 && value.trim() === value; const amount = (value: unknown): value is number => Number.isSafeInteger(value) && Number(value) >= 0; /** * Route the exact already-funded invoice without touching any wallet. * `seller` identifies Richese, not a generic normal-auction bank seller. * In Advanced, Emperor's direct contribution to another buyer goes to Bank; * Richese's contribution to another buyer of its own card goes to Emperor or * Bank. A direct Emperor gift is part of the buyer's own leg, not aid. * * Payer identities/alliances and escrow authority are checked by the caller; * this helper ensures the invoice has unique, positive, exactly funded legs. * Free acquisitions carry no paid legs, even when their nominal price is > 0. */ export function routeAuctionPayment(input: AuctionPaymentInput): AuctionPaymentLeg[] { check(!!input && typeof input === 'object' && !Array.isArray(input) && typeof input.advanced === 'boolean' && typeof input.free === 'boolean' && amount(input.price) && id(input.buyer) && (input.emperor === null || id(input.emperor)) && (input.seller === null || id(input.seller)) && (input.bankOnly === undefined || typeof input.bankOnly === 'boolean') && Array.isArray(input.contributions), 'Supply one valid auction invoice.'); check(input.seller === null || input.seller !== input.emperor, 'The Richese seller and Emperor must be distinct factions.'); const seen = new Set(); let total = 0; for (const leg of input.contributions) { check(!!leg && typeof leg === 'object' && !Array.isArray(leg) && id(leg.payer) && amount(leg.amount) && leg.amount > 0 && !seen.has(leg.payer), 'Every actual payer must have one distinct positive contribution.'); seen.add(leg.payer); total += leg.amount; check(Number.isSafeInteger(total), 'The total auction invoice exceeds safe integer bounds.'); } if (input.free) { check(input.contributions.length === 0, 'A free acquisition cannot carry paid contributions.'); return []; } check(total === input.price, 'Actual auction contributions must exactly fund the price.'); const emperorOrBank = (): AuctionPaymentLeg['recipient'] => input.emperor ? 'emperor' : 'bank'; return input.contributions.map(leg => { let recipient: AuctionPaymentLeg['recipient']; if (input.bankOnly) recipient = 'bank'; else if (input.advanced && input.emperor && leg.payer === input.emperor && leg.payer !== input.buyer) recipient = 'bank'; else if (input.seller === null || input.seller === input.buyer) recipient = input.emperor && input.emperor !== input.buyer ? 'emperor' : 'bank'; else if (input.advanced && leg.payer === input.seller) recipient = emperorOrBank(); else recipient = 'seller'; return {payer:leg.payer, amount:leg.amount, recipient}; }); }