import { PublicKey, type Connection } from "@solana/web3.js"; import type { TransactionProofBuilder } from "../proofs/types.js"; import { type ClientProvingDeps, type ClientProvingFailure, type ClientProvingNote, type ClientProvingOutputNote, type ClientSpendRecovery, type NoteCipherRecipient } from "./shared.js"; export interface ConsolidateNotesParams { connection: Connection; /** The whole unspent note set for this mint. */ notes: ClientProvingNote[]; mintAddress?: PublicKey; userPublicKey: string; /** Who can decrypt each merged note from chain data. */ cipherRecipient: NoteCipherRecipient; /** Owner key of every merged note. Defaults to the second input of each pair. */ ownerPrivateKey?: bigint; /** * Durably store a round's merges before any of them is proved or sent. * Awaited; if it throws, the round is not submitted. */ persistRecovery(round: MergeRoundRecovery): Promise | void; /** * Called after every submitted round with what it did, before the next round * starts. Update the app's note store here. If it throws, consolidation stops * and the result still reports the round. */ onRoundSettled?(report: MergeRoundReport): Promise | void; prover?: TransactionProofBuilder; indexTimeoutMs?: number; /** Safety bound on quote-prove-submit cycles, re-plans included. Default 16. */ maxRounds?: number; } export interface MergeRoundRecovery { roundId: string; merges: ClientSpendRecovery[]; } /** A merge whose outcome is not known. Its inputs are neither spent nor spendable. */ export interface PendingMerge { quoteId: string; /** Present when the relayer sent it and reported the signature. */ txSignature?: string; consumed: ClientProvingNote[]; output: ClientProvingOutputNote; inputNullifiers: string[]; } export interface MergeRoundReport { roundId: string; landed: Array<{ quoteId: string; txSignature: string; consumedCommitments: string[]; output: ClientProvingOutputNote; /** False when the merge landed without its on-chain note cipher. */ noteCiphersIncluded: boolean; }>; /** Refused by the relayer before sending; those inputs remain spendable. */ failed: Array<{ error: string; message: string; }>; unconfirmed: PendingMerge[]; } /** * - `complete`: at most two notes remain; `notes` is ready for a withdrawal. * - `unconfirmed` / `unknown`: a round was sent and not every merge's outcome * is known. `notes` are safe to use; `pending` are not, until * `resolveClientSpend` settles each one. Re-planning before then may try to * spend a note that already moved. * - `stopped`: consolidation could not continue (bad quote, no progress, a * callback failed). Nothing is in flight; `notes` is the current set. */ export type ConsolidateNotesResult = { status: "complete"; notes: ClientProvingNote[]; rounds: MergeRoundReport[]; } | { status: "unconfirmed"; notes: ClientProvingNote[]; pending: PendingMerge[]; rounds: MergeRoundReport[]; } | { status: "unknown"; notes: ClientProvingNote[]; pending: PendingMerge[]; rounds: MergeRoundReport[]; error: ClientProvingFailure; } | { status: "stopped"; notes: ClientProvingNote[]; rounds: MergeRoundReport[]; error: ClientProvingFailure; }; /** * Merge a note set down to at most two notes with proofs generated on this * device, one relayer round at a time. * * Each round pairs notes, proves every pair locally (serially — each proof * holds the full proving key in memory), and submits the round together. A * round may land partially; the next round is planned from the notes actually * held, never by replaying the old one. * * Only invalid parameters throw. Every other outcome is returned with the * current note set, so progress from landed rounds is never lost. */ export declare function consolidateNotes(params: ConsolidateNotesParams): Promise; /** @internal */ export declare function runConsolidateNotes(params: ConsolidateNotesParams, deps: ClientProvingDeps): Promise;