/** * Choosing which notes to spend. * * Two constraints drive everything here: * * 1. The transaction circuit is 2-in-2-out, so a single transaction can spend * at most TWO notes. * 2. Notes can only be co-spent if they live in the SAME Merkle tree, and the * tree is derived from the MINT — see `canonicalTreeId`. */ /** Inputs the transaction circuit accepts in one proof. */ export declare const MAX_INPUT_NOTES = 2; /** * The tree a note belongs to, derived from its mint. * * Mirrors the relayer's `mintAddressToTreeId` byte for byte: the all-zero * native-SOL mint maps to 0, anything else to its first four mint bytes as a * big-endian uint32. * * DERIVE THIS, NEVER TRUST A STORED `treeId`. Change notes are written with the * on-chain shard index (a small 0/1/2…) while deposits and synced notes carry * the mint-derived id. For native SOL both are 0 so the difference hides; for * SPL tokens the two land in different buckets, and co-spending across them * fails at proof time as a commitment mismatch. * * An undecodable mint falls back to the SOL bucket rather than inventing a * phantom tree, matching the server. */ export declare function canonicalTreeId(mintAddress?: string | null): number; /** The minimum a note must carry to be selectable. */ export interface SelectableNote { /** Base units. bigint preferred; strings and safe integers accepted. */ amount: bigint | string | number; /** Mint address; determines the tree. Absent means native SOL. */ mint?: string | null; } export interface NoteSelection { ok: true; /** The chosen notes, your own objects. */ notes: T[]; /** Tree every chosen note belongs to. */ treeId: number; /** Sum of the chosen notes. */ total: bigint; /** total - amount. Becomes the change note. */ change: bigint; /** * True when more than MAX_INPUT_NOTES were needed, so this cannot be proved * as one transaction — the notes must be combined first. */ requiresMerge: boolean; } export interface NoteSelectionFailure { ok: false; reason: "NO_NOTES" | "INSUFFICIENT_FUNDS"; /** Largest total reachable within a single tree. */ available: bigint; message: string; } export type NoteSelectionResult = NoteSelection | NoteSelectionFailure; /** * Pick notes to cover `amount`, preferring the fewest notes and least change. * * Considers each tree separately, since notes cannot be co-spent across trees, * and returns the best single-tree selection: * * 1. one note that covers the amount (smallest such note, least change); * 2. otherwise the best PAIR, found by a two-pointer scan over the * amount-sorted notes — O(n) rather than an O(n²) all-pairs search; * 3. otherwise the largest notes greedily, flagged `requiresMerge` because * more than two inputs cannot go into one proof. * * @param amount base units to cover */ export declare function selectNotesForAmount(notes: T[], amount: bigint | string | number, options?: { mint?: string | null; }): NoteSelectionResult; /** * One input to a merge step: an existing note, or the output of an earlier step. * * Steps chain, so a plan for five notes refers to notes that do not exist yet. * Modelling that explicitly beats returning amounts and leaving the caller to * work out which is which. */ export type MergeInput = { kind: "note"; note: T; amount: bigint; } | { kind: "step"; step: number; amount: bigint; }; export interface MergeStep { /** 1-based, and the number `{ kind: "step" }` inputs refer to. */ step: number; inputs: [MergeInput, MergeInput]; /** Value of the single note this step produces. */ outputAmount: bigint; } export interface NoteConsolidationPlan { ok: true; treeId: number; /** * Merges to run in order, each a 2-in-1-out self-transfer. Empty when the * amount is already spendable within MAX_INPUT_NOTES. */ steps: MergeStep[]; /** Notes the plan consumes, in the order it consumes them. */ notes: T[]; /** Sum of those notes. */ total: bigint; /** total - amount, the change left after the final spend. */ change: bigint; } /** * Plan how to make `amount` spendable when no one or two notes cover it. * * `selectNotesForAmount` reports `requiresMerge` but cannot act on it: the * circuit takes two inputs, so spending five notes means merging them down * first. Each step here is one 2-in-1-out transfer to yourself, and k notes * need k-2 of them before a final two-input spend. * * Merges the two SMALLEST notes each round. That retires dust first and leaves * the large notes untouched, so the plan is short and the final spend has the * least change. * * The plan is pure — it performs nothing. Execute the steps with * `privateTransfer`, feeding each step's output note into the next. */ export declare function planNoteConsolidation(notes: T[], amount: bigint | string | number, options?: { mint?: string | null; }): NoteConsolidationPlan | NoteSelectionFailure;