Sub-issue of #357. Spike #412 validated the reconstruction path; this issue productizes it as an SDK recovery primitive.
Context
v2 consume_notes proposals embed serialized note bytes (consume_notes_notes, base64 Note serialization, introduced by #229). After recovery, pending proposals are already restored via syncProposals() — the embedded note bytes are opportunistic recovery material for notes the account was in the middle of consuming.
Spike findings (#412), on a fresh store against testnet:
- Local note bytes + node-fetched inclusion proof →
NoteFile::NoteWithProof import succeeds without using any node-held body; the record lands in Unverified state and the next sync verifies it. This works for private notes too — the node serves inclusion proofs without bodies (FetchedNote::Private).
- An uncommitted note returns 0 results from
get_notes_by_id — cleanly classifiable as not-committed/retryable, no error — and can be parked in Expected state via a NoteDetails import.
import_notes batches are atomic upstream: one invalid note fails the whole batch. Imports must be per-note.
Scope
- TS:
importNotesFromProposals(midenClient, proposals, { midenRpcEndpoint }).
- Rust:
import_notes_from_proposals(&[Proposal]) on MultisigClient.
- Per note: decode base64 → deserialize
Note → check already-present/consumed → fetch inclusion proof → import individually as NoteWithProof (or NoteDetails/Expected for not-yet-committed) → outcome.
- Per-note
NoteImportOutcome { identifier, source: 'proposal', status: imported | already-present | already-consumed | not-committed | invalid | failed, retryable?, reason? }. A malformed note must not block the others.
Acceptance criteria
Unblocked (spike complete).
Sub-issue of #357. Spike #412 validated the reconstruction path; this issue productizes it as an SDK recovery primitive.
Context
v2
consume_notesproposals embed serialized note bytes (consume_notes_notes, base64Noteserialization, introduced by #229). After recovery, pending proposals are already restored viasyncProposals()— the embedded note bytes are opportunistic recovery material for notes the account was in the middle of consuming.Spike findings (#412), on a fresh store against testnet:
NoteFile::NoteWithProofimport succeeds without using any node-held body; the record lands inUnverifiedstate and the next sync verifies it. This works for private notes too — the node serves inclusion proofs without bodies (FetchedNote::Private).get_notes_by_id— cleanly classifiable asnot-committed/retryable, no error — and can be parked inExpectedstate via aNoteDetailsimport.import_notesbatches are atomic upstream: one invalid note fails the whole batch. Imports must be per-note.Scope
importNotesFromProposals(midenClient, proposals, { midenRpcEndpoint }).import_notes_from_proposals(&[Proposal])onMultisigClient.Note→ check already-present/consumed → fetch inclusion proof → import individually asNoteWithProof(orNoteDetails/Expectedfor not-yet-committed) → outcome.NoteImportOutcome { identifier, source: 'proposal', status: imported | already-present | already-consumed | not-committed | invalid | failed, retryable?, reason? }. A malformed note must not block the others.Acceptance criteria
Unblocked (spike complete).