Skip to main content

Discover your notes from chain

What this lets you do

Sync your local view of shielded balances with what's actually on-chain. The SDK's NoteManager keeps a per-wallet local index of notes (commitment, value, status, asset, label), and discoverNotes() scans the chain for events that affect your account and reconciles the two.

This is the entry point to every read-side flow: displaying balances and picking which notes to spend.

Constraints & limits

  • Cold sync can be slow if the ASP is unavailable: the first discoverNotes() starts from the sync cursor, which is 0x0 on a fresh NoteManager. On the ASP fast-path (next bullet) that's cheap, because the ASP serves its indexed event history and only the tail past its last-synced block is fetched over RPC. But on the pure-RPC fallback (no aspUrl, or ASP unavailable) the SDK paginates eth_getLogs from block 0 at 10K blocks per page, which can hit free-tier rate limits. Bound the scan by passing fromBlock (e.g. the pool's deploy block) to discoverNotes(). There is no deployment-block config, and the SDK never learns it on its own.
  • ASP note-events fast-path: when aspUrl is configured, the SDK pulls encrypted Note events from the ASP's /{chainId}/public/note-events endpoint instead of paginating eth_getLogs itself, then gap-fills any range past the ASP's last-synced block over RPC. (The separate /{chainId}/public/event-snapshot/payload endpoint backs status reconciliation, the spent/ragequit/phantom-purge checks, not the primary Note-event scan.)
  • Cursor and notes live in the persistent-storage adapter you configure, typically the browser's localStorage (the SDK's LocalStorageService). The stored state includes the secrets needed to spend, so protect it like any wallet data. Storage is not configured by default, so without an adapter the cursor and notes stay in memory only and are lost on reload, forcing a full rescan next time. Configure a persistent adapter to keep subsequent syncs fast.
  • Local state can lag behind chain state: a spent note may still look active locally after a failed or partial flow. Before picking inputs to spend, always verify spent status on-chain.

What it unlocks next

How to use it

// Reconcile from chain. Returns immediately with the up-to-date set.
await session.discoverNotes();

// Read the current view.
const account = await session.exportAccount();
const active = account.notes.filter((n) => n.status === "ACTIVE");
console.log(`${active.length} spendable notes`);

Avoid tight polling: calling discoverNotes() in a tight loop (e.g. from a polling hook with no debounce) reruns chain scans and can starve out RPC quota. Sync on explicit user action or on a long stale-time.

Behind the scenes

SDK call session.discoverNotes()
Data sources ASP /{chainId}/public/note-events for the encrypted Note stream (with RPC gap-fill past its last-synced block), falling back to paginated eth_getLogs when no aspUrl is set. Status reconciliation additionally reads the ASP event-snapshot payload (transacts, ragequits, snapshot block)
Decryption Per-note ECDH with your viewingPrivateKey against the event's ephemeral pubkey
State update NoteManager flushes new notes and status transitions to the persistent-storage adapter