From 6288cbe9c6bb2d88c397baef8a7214404a6881c1 Mon Sep 17 00:00:00 2001 From: Juan Munoz Date: Mon, 27 Jul 2026 10:19:39 -0300 Subject: [PATCH 01/15] chore: update rust-sdk deps --- CHANGELOG.md | 7 ++++ Cargo.lock | 6 ++-- Cargo.toml | 6 ++++ .../js/__tests__/resources/notes.test.js | 35 ++++++++++++------- crates/web-client/js/index.js | 1 - crates/web-client/js/resources/notes.js | 8 ++--- crates/web-client/js/types/api-types.d.ts | 11 +++--- crates/web-client/src/models/note_sync.rs | 2 +- crates/web-client/src/note_transport.rs | 24 ++----------- 9 files changed, 48 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ff2e6d..cbd8d081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.16.0-alpha.2 (TBD) + +### Changes + +* [BREAKING][web] Removed `notes.fetchPrivate({ mode: "all" })` (`WasmWebClient.fetchAllPrivateNotes`). `fetchPrivate()` now takes no arguments and always fetches incrementally from the stored pagination cursor. The full re-scan is no longer needed: historical notes for a newly tracked tag sit below the shared cursor and are now backfilled automatically during `sync()`, one tag at a time, so callers that previously reached for `mode: "all"` after adding a tag should just sync. Callers passing the option get a type error; the argument is otherwise ignored at runtime. +* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to rust-sdk `c39d2f0`, 17 commits past the `0.16.0-alpha.1` release, which adds the `debug-output` feature (routing MASM `debug` print events to a custom sink). Inherited upstream changes include note-transport attachment support, a note-screener batch cache, and faster historical-note retrieval. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). + ## 0.16.0-alpha.1 (2026-07-19) ### Changes diff --git a/Cargo.lock b/Cargo.lock index 8685bd98..92835874 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,8 +1752,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd62f7d71b6cdda10ac53030c5f47ee50beecfa04ab6b855bbf495413446b" +source = "git+https://github.com/0xMiden/rust-sdk?rev=c39d2f0747f0f48233f98e28a6187168f7050929#c39d2f0747f0f48233f98e28a6187168f7050929" dependencies = [ "anyhow", "async-trait", @@ -1791,8 +1790,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6edb298af467ce3b6c7628fab94ee3d998b86dad6b1efb525d55aedfa3951e9" +source = "git+https://github.com/0xMiden/rust-sdk?rev=c39d2f0747f0f48233f98e28a6187168f7050929#c39d2f0747f0f48233f98e28a6187168f7050929" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 483f5b71..306b794a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,9 @@ module_name_repetitions = "allow" # Many triggers, must_use_candidate = "allow" # This marks many fn's which isn't helpful. should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. + +# TEMPORARY: points the client crates at the unreleased rust-sdk rev that carries the +# `debug-output` feature (MASM debug print routing) on top of 0.16.0-alpha.1. +[patch.crates-io] +miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "c39d2f0747f0f48233f98e28a6187168f7050929" } +miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "c39d2f0747f0f48233f98e28a6187168f7050929" } diff --git a/crates/web-client/js/__tests__/resources/notes.test.js b/crates/web-client/js/__tests__/resources/notes.test.js index 92cef467..278c178b 100644 --- a/crates/web-client/js/__tests__/resources/notes.test.js +++ b/crates/web-client/js/__tests__/resources/notes.test.js @@ -39,7 +39,6 @@ function makeInner() { getConsumableNotes: vi.fn(), importNoteFile: vi.fn(), exportNoteFile: vi.fn(), - fetchAllPrivateNotes: vi.fn(), fetchPrivateNotes: vi.fn(), sendPrivateNote: vi.fn(), }; @@ -250,28 +249,38 @@ describe("NotesResource", () => { }); describe("fetchPrivate", () => { - it("calls fetchAllPrivateNotes when mode is 'all'", async () => { - inner.fetchAllPrivateNotes.mockResolvedValue(undefined); + it("calls fetchPrivateNotes and guards against a terminated client", async () => { + inner.fetchPrivateNotes.mockResolvedValue(undefined); const resource = makeResource(); - await resource.fetchPrivate({ mode: "all" }); + await resource.fetchPrivate(); expect(client.assertNotTerminated).toHaveBeenCalledOnce(); - expect(inner.fetchAllPrivateNotes).toHaveBeenCalledOnce(); - expect(inner.fetchPrivateNotes).not.toHaveBeenCalled(); + expect(inner.fetchPrivateNotes).toHaveBeenCalledOnce(); + expect(inner.fetchPrivateNotes).toHaveBeenCalledWith(); }); - it("calls fetchPrivateNotes by default (no opts)", async () => { + it("ignores any argument passed by legacy callers", async () => { inner.fetchPrivateNotes.mockResolvedValue(undefined); const resource = makeResource(); - await resource.fetchPrivate(); + await resource.fetchPrivate({ mode: "all" }); expect(inner.fetchPrivateNotes).toHaveBeenCalledOnce(); - expect(inner.fetchAllPrivateNotes).not.toHaveBeenCalled(); + expect(inner.fetchPrivateNotes).toHaveBeenCalledWith(); }); - it("calls fetchPrivateNotes when mode is not 'all'", async () => { - inner.fetchPrivateNotes.mockResolvedValue(undefined); + it("does not call the client when the client is terminated", async () => { + client.assertNotTerminated.mockImplementation(() => { + throw new Error("client terminated"); + }); const resource = makeResource(); - await resource.fetchPrivate({ mode: "partial" }); - expect(inner.fetchPrivateNotes).toHaveBeenCalledOnce(); + await expect(resource.fetchPrivate()).rejects.toThrow( + "client terminated" + ); + expect(inner.fetchPrivateNotes).not.toHaveBeenCalled(); + }); + + it("propagates a rejection from the underlying client", async () => { + inner.fetchPrivateNotes.mockRejectedValue(new Error("transport down")); + const resource = makeResource(); + await expect(resource.fetchPrivate()).rejects.toThrow("transport down"); }); }); diff --git a/crates/web-client/js/index.js b/crates/web-client/js/index.js index b303511c..11739535 100644 --- a/crates/web-client/js/index.js +++ b/crates/web-client/js/index.js @@ -89,7 +89,6 @@ const WRITE_METHODS = new Set([ "addTag", "executeForSummary", "executeProgram", - "fetchAllPrivateNotes", "fetchPrivateNotes", "forceImportStore", "importAccountById", diff --git a/crates/web-client/js/resources/notes.js b/crates/web-client/js/resources/notes.js index 91894241..db7b4a79 100644 --- a/crates/web-client/js/resources/notes.js +++ b/crates/web-client/js/resources/notes.js @@ -55,13 +55,9 @@ export class NotesResource { return await this.#inner.exportNoteFile(resolveNoteIdHex(noteId), format); } - async fetchPrivate(opts) { + async fetchPrivate() { this.#client.assertNotTerminated(); - if (opts?.mode === "all") { - await this.#inner.fetchAllPrivateNotes(); - } else { - await this.#inner.fetchPrivateNotes(); - } + await this.#inner.fetchPrivateNotes(); } async sendPrivate(opts) { diff --git a/crates/web-client/js/types/api-types.d.ts b/crates/web-client/js/types/api-types.d.ts index e86406f9..9f5823d1 100644 --- a/crates/web-client/js/types/api-types.d.ts +++ b/crates/web-client/js/types/api-types.d.ts @@ -587,10 +587,6 @@ export interface ExportNoteOptions { format?: NoteExportFormat; } -export interface FetchPrivateNotesOptions { - mode?: "incremental" | "all"; -} - export interface SendPrivateOptions { note: NoteInput; to: AccountRef; @@ -869,9 +865,12 @@ export interface NotesResource { /** * Fetch private notes from the note transport service. * - * @param options - Optional fetch mode: `"incremental"` (default) or `"all"`. + * Fetches incrementally: only notes past the stored pagination cursor are + * downloaded. Historical notes for a newly tracked tag sit below that cursor + * and are recovered automatically by {@link MidenClient.sync}, which + * backfills each newly tracked tag. */ - fetchPrivate(options?: FetchPrivateNotesOptions): Promise; + fetchPrivate(): Promise; /** * Send a private note to a recipient via the note transport service. * diff --git a/crates/web-client/src/models/note_sync.rs b/crates/web-client/src/models/note_sync.rs index 9343a6c4..e1e3c47c 100644 --- a/crates/web-client/src/models/note_sync.rs +++ b/crates/web-client/src/models/note_sync.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use js_export_macro::js_export; use miden_client::block::BlockNumber; -use miden_client::rpc::domain::note::NoteSyncBlock as NativeNoteSyncBlock; +use miden_client::rpc::domain::note::SyncNotesBlock as NativeNoteSyncBlock; use super::block_header::BlockHeader; use super::committed_note::CommittedNote; diff --git a/crates/web-client/src/note_transport.rs b/crates/web-client/src/note_transport.rs index ba7d9d60..443fc10b 100644 --- a/crates/web-client/src/note_transport.rs +++ b/crates/web-client/src/note_transport.rs @@ -33,7 +33,9 @@ impl WebClient { /// Fetch private notes from the note transport layer /// - /// Uses an internal pagination mechanism to avoid fetching duplicate notes. + /// Uses an internal pagination mechanism to avoid fetching duplicate notes: only notes past + /// the stored cursor are fetched. Historical notes for a newly tracked tag sit below that + /// cursor and are recovered automatically during `syncState`, which backfills each new tag. #[js_export(js_name = "fetchPrivateNotes")] pub async fn fetch_private_notes(&self) -> Result<(), JsErr> { let mut guard = self.get_mut_inner().await; @@ -48,24 +50,4 @@ impl WebClient { Ok(()) } - - /// Fetch all private notes from the note transport layer - /// - /// Fetches all notes stored in the transport layer, with no pagination. - /// Prefer using [`WebClient::fetch_private_notes`] for a more efficient, on-going, - /// fetching mechanism. - #[js_export(js_name = "fetchAllPrivateNotes")] - pub async fn fetch_all_private_notes(&self) -> Result<(), JsErr> { - let mut guard = self.get_mut_inner().await; - let client = guard - .as_mut() - .ok_or_else(|| from_str_err("Client not initialized. Call createClient() first."))?; - - client - .fetch_all_private_notes() - .await - .map_err(|e| js_error_with_context(e, "failed fetching all private notes"))?; - - Ok(()) - } } From eda0c44f42f83cd115e03ce9b7259cc908a93742 Mon Sep 17 00:00:00 2001 From: Juan Munoz Date: Tue, 28 Jul 2026 16:18:10 -0300 Subject: [PATCH 02/15] chore: re-pin rust-sdk version --- CHANGELOG.md | 2 +- Cargo.lock | 4 ++-- Cargo.toml | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd8d081..aa92f5e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Changes * [BREAKING][web] Removed `notes.fetchPrivate({ mode: "all" })` (`WasmWebClient.fetchAllPrivateNotes`). `fetchPrivate()` now takes no arguments and always fetches incrementally from the stored pagination cursor. The full re-scan is no longer needed: historical notes for a newly tracked tag sit below the shared cursor and are now backfilled automatically during `sync()`, one tag at a time, so callers that previously reached for `mode: "all"` after adding a tag should just sync. Callers passing the option get a type error; the argument is otherwise ignored at runtime. -* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to rust-sdk `c39d2f0`, 17 commits past the `0.16.0-alpha.1` release, which adds the `debug-output` feature (routing MASM `debug` print events to a custom sink). Inherited upstream changes include note-transport attachment support, a note-screener batch cache, and faster historical-note retrieval. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). +* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to rust-sdk `1bda89d`, 15 commits past the `0.16.0-alpha.1` release. Inherited upstream changes include note-transport attachment support, a note-screener batch cache, faster historical-note retrieval, and single-account note screening: `notes.listAvailable({ account })` now screens the given account only, instead of screening every tracked account and discarding the rest, so its cost no longer grows with the number of tracked accounts. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). ## 0.16.0-alpha.1 (2026-07-19) diff --git a/Cargo.lock b/Cargo.lock index 92835874..84fa7b36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?rev=c39d2f0747f0f48233f98e28a6187168f7050929#c39d2f0747f0f48233f98e28a6187168f7050929" +source = "git+https://github.com/0xMiden/rust-sdk?rev=1bda89d83d0df46c7ec85db0e41d24359a61c804#1bda89d83d0df46c7ec85db0e41d24359a61c804" dependencies = [ "anyhow", "async-trait", @@ -1790,7 +1790,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?rev=c39d2f0747f0f48233f98e28a6187168f7050929#c39d2f0747f0f48233f98e28a6187168f7050929" +source = "git+https://github.com/0xMiden/rust-sdk?rev=1bda89d83d0df46c7ec85db0e41d24359a61c804#1bda89d83d0df46c7ec85db0e41d24359a61c804" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 306b794a..8cc473a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,8 @@ must_use_candidate = "allow" # This marks man should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. -# TEMPORARY: points the client crates at the unreleased rust-sdk rev that carries the -# `debug-output` feature (MASM debug print routing) on top of 0.16.0-alpha.1. +# TEMPORARY: points the client crates at an unreleased rust-sdk `next` rev ahead of +# 0.16.0-alpha.1, pending the next alpha release. [patch.crates-io] -miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "c39d2f0747f0f48233f98e28a6187168f7050929" } -miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "c39d2f0747f0f48233f98e28a6187168f7050929" } +miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "1bda89d83d0df46c7ec85db0e41d24359a61c804" } +miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "1bda89d83d0df46c7ec85db0e41d24359a61c804" } From 89ca478da322f1616ea5257630ed8e6c0d1707d6 Mon Sep 17 00:00:00 2001 From: Juan Munoz Date: Tue, 28 Jul 2026 18:43:16 -0300 Subject: [PATCH 03/15] chore: update to support encrypted tx inputs --- Cargo.lock | 7 ++-- Cargo.toml | 4 +-- crates/web-client/src/lib.rs | 11 ++++--- crates/web-client/src/mock.rs | 43 +++++++++++++++++++++++++ crates/web-client/src/rpc_client/mod.rs | 7 ++-- 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84fa7b36..40553d16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?rev=1bda89d83d0df46c7ec85db0e41d24359a61c804#1bda89d83d0df46c7ec85db0e41d24359a61c804" +source = "git+https://github.com/0xMiden/rust-sdk?rev=e990171d7e5c64f8db0e225fa02d23fb760dd59b#e990171d7e5c64f8db0e225fa02d23fb760dd59b" dependencies = [ "anyhow", "async-trait", @@ -1790,7 +1790,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?rev=1bda89d83d0df46c7ec85db0e41d24359a61c804#1bda89d83d0df46c7ec85db0e41d24359a61c804" +source = "git+https://github.com/0xMiden/rust-sdk?rev=e990171d7e5c64f8db0e225fa02d23fb760dd59b#e990171d7e5c64f8db0e225fa02d23fb760dd59b" dependencies = [ "anyhow", "async-trait", @@ -2085,8 +2085,7 @@ dependencies = [ [[package]] name = "miden-node-proto-build" version = "0.16.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1027cbad81e3f7a59b50be02cd27c22766573388fce96a807615f2bec0d5fc" +source = "git+https://github.com/0xMiden/node.git?rev=65fbf686a501237309483c1db577ba935b876167#65fbf686a501237309483c1db577ba935b876167" dependencies = [ "build-rs", "codegen", diff --git a/Cargo.toml b/Cargo.toml index 8cc473a8..ea2876eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,5 +76,5 @@ should_panic_without_expect = "allow" # We don't care # TEMPORARY: points the client crates at an unreleased rust-sdk `next` rev ahead of # 0.16.0-alpha.1, pending the next alpha release. [patch.crates-io] -miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "1bda89d83d0df46c7ec85db0e41d24359a61c804" } -miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "1bda89d83d0df46c7ec85db0e41d24359a61c804" } +miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "e990171d7e5c64f8db0e225fa02d23fb760dd59b" } +miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "e990171d7e5c64f8db0e225fa02d23fb760dd59b" } diff --git a/crates/web-client/src/lib.rs b/crates/web-client/src/lib.rs index 9c9a23fe..8766d82f 100644 --- a/crates/web-client/src/lib.rs +++ b/crates/web-client/src/lib.rs @@ -26,7 +26,7 @@ use miden_client::crypto::RandomCoin; use miden_client::keystore::FilesystemKeyStore; use miden_client::note_transport::NoteTransportClient; use miden_client::note_transport::grpc::GrpcNoteTransportClient; -use miden_client::rpc::{Endpoint, GrpcClient, NodeRpcClient}; +use miden_client::rpc::{Endpoint, GrpcClient, NodeRpcClient, VerifyingRpcClient}; use miden_client::store::Store; use miden_client::testing::mock::MockRpcApi; use miden_client::testing::note_transport::MockNoteTransportApi; @@ -377,7 +377,8 @@ impl WebClient { Endpoint::try_from(url.as_str()).map_err(|_| JsValue::from_str("Invalid node URL")) })?; - let web_rpc_client = Arc::new(GrpcClient::new(&endpoint, DEFAULT_GRPC_TIMEOUT_MS)); + let web_rpc_client = + Arc::new(VerifyingRpcClient::new(GrpcClient::new(&endpoint, DEFAULT_GRPC_TIMEOUT_MS))); let note_transport_client = node_note_transport_url.map(|url| { Arc::new(GrpcNoteTransportClient::new(url, DEFAULT_GRPC_TIMEOUT_MS)) @@ -429,7 +430,8 @@ impl WebClient { Endpoint::try_from(url.as_str()).map_err(|_| JsValue::from_str("Invalid node URL")) })?; - let web_rpc_client = Arc::new(GrpcClient::new(&endpoint, DEFAULT_GRPC_TIMEOUT_MS)); + let web_rpc_client = + Arc::new(VerifyingRpcClient::new(GrpcClient::new(&endpoint, DEFAULT_GRPC_TIMEOUT_MS))); let note_transport_client = node_note_transport_url.map(|url| { Arc::new(GrpcNoteTransportClient::new(url, DEFAULT_GRPC_TIMEOUT_MS)) @@ -513,7 +515,8 @@ impl WebClient { Endpoint::try_from(url.as_str()).map_err(|_| from_str_err("Invalid node URL")) })?; - let rpc_client = Arc::new(GrpcClient::new(&endpoint, DEFAULT_GRPC_TIMEOUT_MS)); + let rpc_client = + Arc::new(VerifyingRpcClient::new(GrpcClient::new(&endpoint, DEFAULT_GRPC_TIMEOUT_MS))); let note_transport_client = if let Some(url) = node_note_transport_url { let client = GrpcNoteTransportClient::new(url, DEFAULT_GRPC_TIMEOUT_MS); diff --git a/crates/web-client/src/mock.rs b/crates/web-client/src/mock.rs index 11494cc0..0d2ec568 100644 --- a/crates/web-client/src/mock.rs +++ b/crates/web-client/src/mock.rs @@ -3,11 +3,16 @@ use alloc::sync::Arc; #[cfg(feature = "browser")] use idxdb_store::IdxdbStore; use js_export_macro::js_export; +use miden_client::block::BlockNumber; +use miden_client::crypto::eddsa_25519_sha512::KeyExchangeKey; +use miden_client::rpc::encryption::TransactionEncryptionKey; use miden_client::store::Store; use miden_client::testing::MockChain; use miden_client::testing::mock::MockRpcApi; use miden_client::testing::note_transport::{MockNoteTransportApi, MockNoteTransportNode}; use miden_client::utils::{Deserializable, RwLock, Serializable}; +use rand::SeedableRng; +use rand::rngs::StdRng; #[cfg(feature = "browser")] use crate::WebKeyStore; @@ -63,6 +68,8 @@ impl WebClient { ) .await?; + self.seed_mock_transaction_encryption_key().await?; + *self.mock_rpc_api.lock().await = Some(mock_rpc_api); *self.mock_note_transport_api.lock().await = Some(mock_note_transport_api); @@ -122,6 +129,8 @@ impl WebClient { ) .await?; + self.seed_mock_transaction_encryption_key().await?; + *self.mock_rpc_api.lock().await = Some(mock_rpc_api); *self.mock_note_transport_api.lock().await = Some(mock_note_transport_api); @@ -129,6 +138,40 @@ impl WebClient { } } +impl WebClient { + /// Gives a mock-backed client the transaction encryption key that submission seals against. + /// + /// `MockRpcApi` refuses to serve a key, because attesting one needs a validator signature the + /// mock chain cannot produce. The mock also discards the sealed inputs it receives, so an + /// unattested key is enough: sealing still runs its real transcript and wire path, only the + /// attestation check is skipped. + /// + /// Must run after the client is in place and its genesis header stored, since the key is + /// scoped to the genesis commitment. + async fn seed_mock_transaction_encryption_key(&self) -> Result<(), JsErr> { + let mut guard = self.get_mut_inner().await; + let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?; + + let genesis_commitment = client + .get_block_header_by_num(BlockNumber::GENESIS) + .await + .map_err(|err| js_error_with_context(err, "failed to read the genesis block header"))? + .ok_or_else(|| { + from_str_err("genesis block header must be in place before the mock client can seal transaction inputs") + })? + .0 + .commitment(); + + client.seed_transaction_encryption_key(TransactionEncryptionKey::new_unattested( + b"mock-key-id".to_vec(), + KeyExchangeKey::with_rng(&mut StdRng::from_rng(&mut rand::rng())).public_key(), + genesis_commitment, + )); + + Ok(()) + } +} + #[js_export] impl WebClient { /// Returns the inner serialized mock chain if it exists. diff --git a/crates/web-client/src/rpc_client/mod.rs b/crates/web-client/src/rpc_client/mod.rs index 3b692e5a..0aa27722 100644 --- a/crates/web-client/src/rpc_client/mod.rs +++ b/crates/web-client/src/rpc_client/mod.rs @@ -12,7 +12,7 @@ use miden_client::builder::DEFAULT_GRPC_TIMEOUT_MS; use miden_client::note::{NoteId as NativeNoteId, Nullifier}; use miden_client::rpc::domain::account::{GetAccountRequest, StorageMapFetch, VaultFetch}; use miden_client::rpc::domain::note::FetchedNote as NativeFetchedNote; -use miden_client::rpc::{AccountStateAt, GrpcClient, NodeRpcClient}; +use miden_client::rpc::{AccountStateAt, GrpcClient, NodeRpcClient, VerifyingRpcClient}; use note::FetchedNote; use crate::js_error_with_context; @@ -46,7 +46,10 @@ impl RpcClient { /// @param endpoint - Endpoint to connect to. #[js_export(constructor)] pub fn new(endpoint: Endpoint) -> Result { - let rpc_client = Arc::new(GrpcClient::new(&endpoint.into(), DEFAULT_GRPC_TIMEOUT_MS)); + let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new( + &endpoint.into(), + DEFAULT_GRPC_TIMEOUT_MS, + ))); Ok(RpcClient { inner: rpc_client }) } From d130199fd5ffa5c0d70aff7ca24cbd85d0833550 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Tue, 28 Jul 2026 22:32:36 -0300 Subject: [PATCH 04/15] chore: point client crates at rust-sdk#2341 (encrypted tx inputs) --- Cargo.lock | 4 ++-- Cargo.toml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40553d16..c3d04303 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?rev=e990171d7e5c64f8db0e225fa02d23fb760dd59b#e990171d7e5c64f8db0e225fa02d23fb760dd59b" +source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#7d1cbb2e26c6f5df3dee82c2314226162f0d3bce" dependencies = [ "anyhow", "async-trait", @@ -1790,7 +1790,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?rev=e990171d7e5c64f8db0e225fa02d23fb760dd59b#e990171d7e5c64f8db0e225fa02d23fb760dd59b" +source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#7d1cbb2e26c6f5df3dee82c2314226162f0d3bce" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ea2876eb..13f24014 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,8 @@ must_use_candidate = "allow" # This marks man should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. -# TEMPORARY: points the client crates at an unreleased rust-sdk `next` rev ahead of -# 0.16.0-alpha.1, pending the next alpha release. +# TEMPORARY: points the client crates at the encrypted-transaction-inputs branch +# (0xMiden/rust-sdk#2341), pending the next alpha release. [patch.crates-io] -miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "e990171d7e5c64f8db0e225fa02d23fb760dd59b" } -miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "e990171d7e5c64f8db0e225fa02d23fb760dd59b" } +miden-client = { branch = "encrypted-tx-inputs", git = "https://github.com/0xMiden/rust-sdk" } +miden-client-sqlite-store = { branch = "encrypted-tx-inputs", git = "https://github.com/0xMiden/rust-sdk" } From e03c103f360136b618ce2a17d3a41d69d3232e0b Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Tue, 28 Jul 2026 23:11:50 -0300 Subject: [PATCH 05/15] chore: bump client crates to the current rust-sdk branch tip --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3d04303..2ef2605a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#7d1cbb2e26c6f5df3dee82c2314226162f0d3bce" +source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#32dfba8df1eb5910a189b023365aa644f8efbdcb" dependencies = [ "anyhow", "async-trait", @@ -1790,7 +1790,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#7d1cbb2e26c6f5df3dee82c2314226162f0d3bce" +source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#32dfba8df1eb5910a189b023365aa644f8efbdcb" dependencies = [ "anyhow", "async-trait", @@ -2085,7 +2085,7 @@ dependencies = [ [[package]] name = "miden-node-proto-build" version = "0.16.0-alpha.2" -source = "git+https://github.com/0xMiden/node.git?rev=65fbf686a501237309483c1db577ba935b876167#65fbf686a501237309483c1db577ba935b876167" +source = "git+https://github.com/0xMiden/node.git?rev=74e4f327b8f654d95afefaea0e9f74a7a4571cd9#74e4f327b8f654d95afefaea0e9f74a7a4571cd9" dependencies = [ "build-rs", "codegen", From f9e51df20be978244ec8b1bbb24fb5b14ae3549a Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Tue, 28 Jul 2026 22:55:23 -0300 Subject: [PATCH 06/15] fix(web-client): re-import the note against a genesis-bearing store snapshot The test wiped IndexedDB under the live client and imported into the empty store, which cannot resolve any block header. Restoring a pre-mint snapshot keeps the scenario (note gone, chain known) without relying on client state that a store wipe invalidates. --- crates/web-client/test/import_export.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/web-client/test/import_export.test.ts b/crates/web-client/test/import_export.test.ts index 3bac38ae..a7667410 100644 --- a/crates/web-client/test/import_export.test.ts +++ b/crates/web-client/test/import_export.test.ts @@ -200,12 +200,22 @@ test.describe("export and import note", () => { }); test(`exporting and then importing note`, async ({ page }) => { + // Capture a store snapshot that already carries the genesis block header + // but predates the minted note. + await page.evaluate(async () => { + await window.client.syncState(); + }); + const genesisOnlyDb = await exportDb(page); + const { createdNoteId: noteId } = await setupMintedNote(page); const serializedNoteFile = await exportNoteSerialized(page, noteId, "Full"); - // Clear store and assert that the output note cannot be found + // Clear the store and restore the pre-mint snapshot, so the note import + // below runs against a store that knows the chain's genesis but not the + // note. await clearStore(page); + await importDb(genesisOnlyDb, page); await expect(async () => { return await page.evaluate( async ({ noteId }) => { From ebd7af0352ac144809d457df3905788c216f9820 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Tue, 28 Jul 2026 23:23:52 -0300 Subject: [PATCH 07/15] fix(web-client): await the mock encryption key seeding seed_transaction_encryption_key is async and fallible on the pinned rust-sdk rev; the un-awaited call dropped the future, so mock clients never actually held a key and every submission would fail to seal. --- crates/web-client/src/mock.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/web-client/src/mock.rs b/crates/web-client/src/mock.rs index 0d2ec568..f8374a98 100644 --- a/crates/web-client/src/mock.rs +++ b/crates/web-client/src/mock.rs @@ -162,11 +162,16 @@ impl WebClient { .0 .commitment(); - client.seed_transaction_encryption_key(TransactionEncryptionKey::new_unattested( - b"mock-key-id".to_vec(), - KeyExchangeKey::with_rng(&mut StdRng::from_rng(&mut rand::rng())).public_key(), - genesis_commitment, - )); + client + .seed_transaction_encryption_key(TransactionEncryptionKey::new_unattested( + b"mock-key-id".to_vec(), + KeyExchangeKey::with_rng(&mut StdRng::from_rng(&mut rand::rng())).public_key(), + genesis_commitment, + )) + .await + .map_err(|err| { + js_error_with_context(err, "failed to seed the mock transaction encryption key") + })?; Ok(()) } From 369991256491c6e72c1d531b03fa1c75d565d315 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Tue, 28 Jul 2026 23:31:10 -0300 Subject: [PATCH 08/15] docs: changelog entries for encrypted transaction submission --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa92f5e7..6b9d4f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ ### Changes +* [BREAKING][web] Transaction submissions now encrypt their private inputs, so the RPC operator relaying them cannot read them: only holders of the validator set's shared encryption secret can. On first submission the client fetches the validator set's transaction encryption key from the node, verifies a validator attestation for it against the validator set committed in a trusted block header (bound to the chain's genesis commitment, so an attestation cannot be replayed from another network), and caches the verified key in the store; a submission rejected for having been sealed against a retired key evicts the cached key and the next submission re-fetches. Requires a node that unseals submitted inputs — such nodes reject plaintext submissions, and older nodes reject sealed ones, so client and node must be upgraded together. ([#252](https://github.com/0xMiden/web-sdk/pull/252), client [#2341](https://github.com/0xMiden/rust-sdk/pull/2341)) * [BREAKING][web] Removed `notes.fetchPrivate({ mode: "all" })` (`WasmWebClient.fetchAllPrivateNotes`). `fetchPrivate()` now takes no arguments and always fetches incrementally from the stored pagination cursor. The full re-scan is no longer needed: historical notes for a newly tracked tag sit below the shared cursor and are now backfilled automatically during `sync()`, one tag at a time, so callers that previously reached for `mode: "all"` after adding a tag should just sync. Callers passing the option get a type error; the argument is otherwise ignored at runtime. -* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to rust-sdk `1bda89d`, 15 commits past the `0.16.0-alpha.1` release. Inherited upstream changes include note-transport attachment support, a note-screener batch cache, faster historical-note retrieval, and single-account note screening: `notes.listAvailable({ account })` now screens the given account only, instead of screening every tracked account and discarding the rest, so its cost no longer grows with the number of tracked accounts. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). +* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to the rust-sdk `encrypted-tx-inputs` branch ([#2341](https://github.com/0xMiden/rust-sdk/pull/2341), `32dfba8d`), ahead of the `0.16.0-alpha.1` release, pending the next alpha. Inherited upstream changes include encrypted transaction submission (see above), note-transport attachment support, a note-screener batch cache, faster historical-note retrieval, and single-account note screening: `notes.listAvailable({ account })` now screens the given account only, instead of screening every tracked account and discarding the rest, so its cost no longer grows with the number of tracked accounts. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). ## 0.16.0-alpha.1 (2026-07-19) From 0ef8ba31f8cb769c8002f8ae34deef8c0f6be3d1 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Wed, 29 Jul 2026 00:25:43 -0300 Subject: [PATCH 09/15] fix(web-client): keep the mock seeding future Send for the Node.js binding The ThreadRng temporary used to derive the mock encryption key lived across the seeding await, and napi requires Send futures. Generate the key before the call so the RNG is dropped first. --- crates/web-client/src/mock.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/web-client/src/mock.rs b/crates/web-client/src/mock.rs index f8374a98..8e704ae3 100644 --- a/crates/web-client/src/mock.rs +++ b/crates/web-client/src/mock.rs @@ -162,10 +162,15 @@ impl WebClient { .0 .commitment(); + // Generated ahead of the call so the non-`Send` `ThreadRng` temporary is dropped + // before the await; the Node.js binding requires the future to be `Send`. + let public_key = + KeyExchangeKey::with_rng(&mut StdRng::from_rng(&mut rand::rng())).public_key(); + client .seed_transaction_encryption_key(TransactionEncryptionKey::new_unattested( b"mock-key-id".to_vec(), - KeyExchangeKey::with_rng(&mut StdRng::from_rng(&mut rand::rng())).public_key(), + public_key, genesis_commitment, )) .await From 867d92e1ce1da7731037bc5ee3034efe9b6f6724 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Wed, 29 Jul 2026 01:29:30 -0300 Subject: [PATCH 10/15] ci: build the test node from the encrypted-tx-inputs client branch --- .github/workflows/test.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a151e55..12efe94e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,10 +20,12 @@ env: # cryptic 'invalid wire type' decode errors on the client side (e.g. Digest # fields encoded as bytes vs fixed64), or 'accept header validation failed' # if the protocol version differs. - # Pinned to the commit of the rust-sdk v0.16.0-alpha.1 release tag, matching - # the miden-client 0.16.0-alpha.1 crates.io dep in Cargo.toml. A commit sha (not the tag - # name) keeps cache keys stable. - MIDEN_CLIENT_REF: 0f89705c926d0f8aa442d267341b0ce6661e53ba + # Pinned to the head of the rust-sdk `encrypted-tx-inputs` branch, matching + # the miden-client git patch in Cargo.toml: its Cargo.lock pins the node rev + # that serves `GetTransactionEncryptionKey`, which the patched client calls + # on every first submission. A commit sha (not the branch name) keeps cache + # keys stable. Re-pin to the release tag once the next alpha ships. + MIDEN_CLIENT_REF: 32dfba8df1eb5910a189b023365aa644f8efbdcb jobs: # Pre-flight: detect whether any non-docs files changed. See build.yml's From 114fce4a37627cc27797c11fc14fe581bdfb9e0e Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 22 Jul 2026 17:15:09 -0300 Subject: [PATCH 11/15] feat: add note filter by script root --- CHANGELOG.md | 4 ++ crates/idxdb-store/src/js/notes.js | 13 +++++ crates/idxdb-store/src/note/js_bindings.rs | 6 +++ crates/idxdb-store/src/note/mod.rs | 51 +++++++++++++------ crates/idxdb-store/src/ts/notes.test.ts | 36 +++++++++++++ crates/idxdb-store/src/ts/notes.ts | 16 ++++++ .../js/__tests__/resources/notes.test.js | 17 +++++++ crates/web-client/js/resources/notes.js | 11 ++++ crates/web-client/js/types/api-types.d.ts | 18 +++++-- crates/web-client/src/models/note_filter.rs | 50 +++++++++--------- 10 files changed, 177 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9d4f66..6daa1098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ * [BREAKING][web] Removed `notes.fetchPrivate({ mode: "all" })` (`WasmWebClient.fetchAllPrivateNotes`). `fetchPrivate()` now takes no arguments and always fetches incrementally from the stored pagination cursor. The full re-scan is no longer needed: historical notes for a newly tracked tag sit below the shared cursor and are now backfilled automatically during `sync()`, one tag at a time, so callers that previously reached for `mode: "all"` after adding a tag should just sync. Callers passing the option get a type error; the argument is otherwise ignored at runtime. * [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to the rust-sdk `encrypted-tx-inputs` branch ([#2341](https://github.com/0xMiden/rust-sdk/pull/2341), `32dfba8d`), ahead of the `0.16.0-alpha.1` release, pending the next alpha. Inherited upstream changes include encrypted transaction submission (see above), note-transport attachment support, a note-screener batch cache, faster historical-note retrieval, and single-account note screening: `notes.listAvailable({ account })` now screens the given account only, instead of screening every tracked account and discarding the rest, so its cost no longer grows with the number of tracked accounts. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). +### Enhancements + +* [FEATURE][web] `notes.list({ scriptRoots: [...] })` filters received notes by note script root, given as hex strings or `Word` instances (e.g. from `NoteScript.root()`). This narrows candidate notes at the store level, without loading and screening unrelated notes. `notes.listSent` returns an empty list for this query, since script roots are only tracked for received notes. (web-sdk#TBD, client #TBD) + ## 0.16.0-alpha.1 (2026-07-19) ### Changes diff --git a/crates/idxdb-store/src/js/notes.js b/crates/idxdb-store/src/js/notes.js index d1f49312..d52a2470 100644 --- a/crates/idxdb-store/src/js/notes.js +++ b/crates/idxdb-store/src/js/notes.js @@ -83,6 +83,19 @@ export async function getInputNotesFromDetailsCommitments(dbId, detailsCommitmen logWebStoreError(err, "Failed to get input notes from details commitments"); } } +export async function getInputNotesFromScriptRoots(dbId, scriptRoots) { + try { + const db = getDatabase(dbId); + const roots = new Set(scriptRoots); + let notes = await db.inputNotes + .filter((note) => roots.has(note.scriptRoot)) + .toArray(); + return await processInputNotes(dbId, notes); + } + catch (err) { + logWebStoreError(err, "Failed to get input notes from script roots"); + } +} export async function getOutputNotesFromDetailsCommitments(dbId, detailsCommitments) { try { const db = getDatabase(dbId); diff --git a/crates/idxdb-store/src/note/js_bindings.rs b/crates/idxdb-store/src/note/js_bindings.rs index 9f372991..9e89af0b 100644 --- a/crates/idxdb-store/src/note/js_bindings.rs +++ b/crates/idxdb-store/src/note/js_bindings.rs @@ -29,6 +29,12 @@ extern "C" { details_commitments: Vec, ) -> js_sys::Promise; + #[wasm_bindgen(js_name = getInputNotesFromScriptRoots)] + pub fn idxdb_get_input_notes_from_script_roots( + db_id: &str, + script_roots: Vec, + ) -> js_sys::Promise; + #[wasm_bindgen(js_name = getOutputNotes)] pub fn idxdb_get_output_notes(db_id: &str, states: Vec) -> js_sys::Promise; diff --git a/crates/idxdb-store/src/note/mod.rs b/crates/idxdb-store/src/note/mod.rs index 484ea917..6f663118 100644 --- a/crates/idxdb-store/src/note/mod.rs +++ b/crates/idxdb-store/src/note/mod.rs @@ -3,7 +3,13 @@ use alloc::vec::Vec; use miden_client::Word; use miden_client::account::AccountId; -use miden_client::note::{BlockNumber, NoteDetailsCommitment, NoteScript, Nullifier}; +use miden_client::note::{ + BlockNumber, + NoteDetailsCommitment, + NoteScript, + NoteScriptRoot, + Nullifier, +}; use miden_client::store::{ InputNoteRecord, InputNoteState, @@ -26,6 +32,7 @@ use js_bindings::{ idxdb_get_input_notes_from_details_commitments, idxdb_get_input_notes_from_ids, idxdb_get_input_notes_from_nullifiers, + idxdb_get_input_notes_from_script_roots, idxdb_get_note_script, idxdb_get_output_notes, idxdb_get_output_notes_from_details_commitments, @@ -51,7 +58,7 @@ impl IdxdbStore { filter: NoteFilter, ) -> Result, StoreError> { let input_notes_idxdb: Vec = - await_js(filter.to_input_notes_promise(self.db_id()), "failed to get input notes") + await_js(filter.to_input_notes_promise(self.db_id())?, "failed to get input notes") .await?; input_notes_idxdb @@ -65,7 +72,7 @@ impl IdxdbStore { filter: NoteFilter, ) -> Result, StoreError> { let output_notes_idxdb: Vec = - await_js(filter.to_output_note_promise(self.db_id()), "failed to get output notes") + await_js(filter.to_output_note_promise(self.db_id())?, "failed to get output notes") .await?; output_notes_idxdb @@ -175,22 +182,19 @@ fn input_note_state_discriminants(filter: &NoteFilter) -> Option> { InputNoteState::STATE_PROCESSING_AUTHENTICATED, InputNoteState::STATE_PROCESSING_UNAUTHENTICATED, ]), - NoteFilter::List(_) - | NoteFilter::Unique(_) - | NoteFilter::Nullifiers(_) - | NoteFilter::DetailsCommitments(_) => None, + _ => None, } } // Provide extension methods for NoteFilter via a local trait pub(crate) trait NoteFilterExt { - fn to_input_notes_promise(&self, db_id: &str) -> Promise; - fn to_output_note_promise(&self, db_id: &str) -> Promise; + fn to_input_notes_promise(&self, db_id: &str) -> Result; + fn to_output_note_promise(&self, db_id: &str) -> Result; } impl NoteFilterExt for NoteFilter { - fn to_input_notes_promise(&self, db_id: &str) -> Promise { - match self { + fn to_input_notes_promise(&self, db_id: &str) -> Result { + let promise = match self { NoteFilter::All | NoteFilter::Consumed | NoteFilter::Committed @@ -223,11 +227,21 @@ impl NoteFilterExt for NoteFilter { commitments.iter().map(NoteDetailsCommitment::to_hex).collect(); idxdb_get_input_notes_from_details_commitments(db_id, commitments_as_str) }, - } + NoteFilter::ScriptRoots(script_roots) => { + let script_roots_as_str: Vec = + script_roots.iter().map(NoteScriptRoot::to_hex).collect(); + idxdb_get_input_notes_from_script_roots(db_id, script_roots_as_str) + }, + filter => { + return Err(StoreError::QueryError(format!("unsupported note filter: {filter:?}"))); + }, + }; + + Ok(promise) } - fn to_output_note_promise(&self, db_id: &str) -> Promise { - match self { + fn to_output_note_promise(&self, db_id: &str) -> Result { + let promise = match self { NoteFilter::All | NoteFilter::Consumed | NoteFilter::Committed @@ -255,7 +269,7 @@ impl NoteFilterExt for NoteFilter { idxdb_get_output_notes(db_id, states) }, - NoteFilter::Processing | NoteFilter::Unverified => { + NoteFilter::Processing | NoteFilter::ScriptRoots(_) | NoteFilter::Unverified => { Promise::resolve(&JsValue::from(Array::new())) }, NoteFilter::List(ids) => { @@ -279,6 +293,11 @@ impl NoteFilterExt for NoteFilter { commitments.iter().map(NoteDetailsCommitment::to_hex).collect(); idxdb_get_output_notes_from_details_commitments(db_id, commitments_as_str) }, - } + filter => { + return Err(StoreError::QueryError(format!("unsupported note filter: {filter:?}"))); + }, + }; + + Ok(promise) } } diff --git a/crates/idxdb-store/src/ts/notes.test.ts b/crates/idxdb-store/src/ts/notes.test.ts index b75ee9e2..de412099 100644 --- a/crates/idxdb-store/src/ts/notes.test.ts +++ b/crates/idxdb-store/src/ts/notes.test.ts @@ -8,6 +8,7 @@ import { getInputNotes, getInputNotesFromIds, getInputNotesFromNullifiers, + getInputNotesFromScriptRoots, getOutputNotes, getOutputNotesFromIds, getOutputNotesFromNullifiers, @@ -538,6 +539,41 @@ describe("getInputNotesFromIds", () => { }); }); +// ================================================================================================ +// getInputNotesFromScriptRoots +// ================================================================================================ + +describe("getInputNotesFromScriptRoots", () => { + it("returns notes matching the given script roots", async () => { + const dbId = await openTestDb(); + await insertNote(dbId, "root-note-1", { scriptRoot: "0xroot1" }); + await insertNote(dbId, "root-note-2", { scriptRoot: "0xroot1" }); + await insertNote(dbId, "root-note-3", { scriptRoot: "0xroot2" }); + + const result = await getInputNotesFromScriptRoots(dbId, ["0xroot1"]); + expect(result).toHaveLength(2); + // createdAt holds the noteId (see insertNote) + expect(result?.map((note) => note.createdAt).sort()).toEqual([ + "root-note-1", + "root-note-2", + ]); + + const combined = await getInputNotesFromScriptRoots(dbId, [ + "0xroot1", + "0xroot2", + ]); + expect(combined).toHaveLength(3); + }); + + it("returns empty array for unmatched script roots", async () => { + const dbId = await openTestDb(); + await insertNote(dbId, "root-note-1", { scriptRoot: "0xroot1" }); + + const result = await getInputNotesFromScriptRoots(dbId, ["0xother"]); + expect(result).toEqual([]); + }); +}); + // ================================================================================================ // getInputNotesFromNullifiers // ================================================================================================ diff --git a/crates/idxdb-store/src/ts/notes.ts b/crates/idxdb-store/src/ts/notes.ts index 5826a08a..34198183 100644 --- a/crates/idxdb-store/src/ts/notes.ts +++ b/crates/idxdb-store/src/ts/notes.ts @@ -98,6 +98,22 @@ export async function getInputNotesFromDetailsCommitments( } } +export async function getInputNotesFromScriptRoots( + dbId: string, + scriptRoots: string[] +) { + try { + const db = getDatabase(dbId); + const roots = new Set(scriptRoots); + let notes = await db.inputNotes + .filter((note) => roots.has(note.scriptRoot)) + .toArray(); + return await processInputNotes(dbId, notes); + } catch (err) { + logWebStoreError(err, "Failed to get input notes from script roots"); + } +} + export async function getOutputNotesFromDetailsCommitments( dbId: string, detailsCommitments: string[] diff --git a/crates/web-client/js/__tests__/resources/notes.test.js b/crates/web-client/js/__tests__/resources/notes.test.js index 278c178b..1a3028b6 100644 --- a/crates/web-client/js/__tests__/resources/notes.test.js +++ b/crates/web-client/js/__tests__/resources/notes.test.js @@ -10,6 +10,7 @@ function makeWasm(overrides = {}) { Processing: "Processing", Unverified: "Unverified", List: "List", + ScriptRoots: "ScriptRoots", }; const filterInstance = { type: "filter" }; return { @@ -18,6 +19,9 @@ function makeWasm(overrides = {}) { NoteId: { fromHex: vi.fn((hex) => ({ hex })), }, + Word: { + fromHex: vi.fn((hex) => ({ hex })), + }, NoteExportFormat: { Full: "Full" }, AccountId: { fromHex: vi.fn((hex) => ({ hex })), @@ -125,6 +129,19 @@ describe("NotesResource", () => { expect(wasm.NoteFilter).toHaveBeenCalledWith("List", expect.any(Array)); }); + it("builds NoteFilter with script roots when query.scriptRoots provided", async () => { + inner.getInputNotes.mockResolvedValue([]); + const resource = makeResource(); + const wordRoot = { word: true }; + await resource.list({ scriptRoots: ["0xabc", wordRoot] }); + expect(wasm.Word.fromHex).toHaveBeenCalledTimes(1); + expect(wasm.Word.fromHex).toHaveBeenCalledWith("0xabc"); + expect(wasm.NoteFilter).toHaveBeenCalledWith("ScriptRoots", undefined, [ + { hex: "0xabc" }, + wordRoot, + ]); + }); + it("falls back to All when empty query object", async () => { inner.getInputNotes.mockResolvedValue([]); const resource = makeResource(); diff --git a/crates/web-client/js/resources/notes.js b/crates/web-client/js/resources/notes.js index db7b4a79..d7caf2ff 100644 --- a/crates/web-client/js/resources/notes.js +++ b/crates/web-client/js/resources/notes.js @@ -101,6 +101,17 @@ function buildNoteFilter(query, wasm) { return new wasm.NoteFilter(wasm.NoteFilterTypes.List, noteIds); } + if (query.scriptRoots) { + const scriptRoots = query.scriptRoots.map((root) => + typeof root === "string" ? wasm.Word.fromHex(root) : root + ); + return new wasm.NoteFilter( + wasm.NoteFilterTypes.ScriptRoots, + undefined, + scriptRoots + ); + } + if (query.status) { const statusMap = { consumed: wasm.NoteFilterTypes.Consumed, diff --git a/crates/web-client/js/types/api-types.d.ts b/crates/web-client/js/types/api-types.d.ts index 9f5823d1..f3b77544 100644 --- a/crates/web-client/js/types/api-types.d.ts +++ b/crates/web-client/js/types/api-types.d.ts @@ -566,7 +566,14 @@ export type NoteQuery = | "processing" | "unverified"; } - | { ids: (string | NoteId)[] }; + | { ids: (string | NoteId)[] } + /** + * Filter received notes by note script root, given as hex strings or Word + * instances (e.g. from `NoteScript.root()`). Notes match regardless of their + * state. Only supported by `notes.list`; `notes.listSent` returns an empty + * list for this query. + */ + | { scriptRoots: (string | Word)[] }; /** Options for standalone note creation utilities. */ export interface NoteOptions { @@ -817,9 +824,10 @@ export interface TransactionsResource { export interface NotesResource { /** - * List received (input) notes, optionally filtered by status or IDs. + * List received (input) notes, optionally filtered by status, IDs, or note + * script roots. * - * @param query - Optional filter by note status or note IDs. + * @param query - Optional filter by note status, note IDs, or script roots. */ list(query?: NoteQuery): Promise; /** @@ -830,7 +838,9 @@ export interface NotesResource { get(noteId: NoteInput): Promise; /** - * List sent (output) notes, optionally filtered by status or IDs. + * List sent (output) notes, optionally filtered by status or IDs. A script + * root query returns an empty list, since script roots are only tracked for + * received notes. * * @param query - Optional filter by note status or note IDs. */ diff --git a/crates/web-client/src/models/note_filter.rs b/crates/web-client/src/models/note_filter.rs index 5d6bebda..5c199e55 100644 --- a/crates/web-client/src/models/note_filter.rs +++ b/crates/web-client/src/models/note_filter.rs @@ -1,7 +1,9 @@ use js_export_macro::js_export; +use miden_client::note::NoteScriptRoot; use miden_client::store::NoteFilter as NativeNoteFilter; use super::note_id::NoteId; +use super::word::Word; // TODO: Add nullifier support @@ -11,14 +13,19 @@ use super::note_id::NoteId; pub struct NoteFilter { note_type: NoteFilterTypes, note_ids: Option>, + script_roots: Option>, } #[js_export] impl NoteFilter { - /// Creates a new filter for the given type and optional note IDs. + /// Creates a new filter for the given type and optional note IDs or script roots. #[js_export(constructor)] - pub fn new(note_type: NoteFilterTypes, note_ids: Option>) -> NoteFilter { - NoteFilter { note_type, note_ids } + pub fn new( + note_type: NoteFilterTypes, + note_ids: Option>, + script_roots: Option>, + ) -> NoteFilter { + NoteFilter { note_type, note_ids, script_roots } } } @@ -34,6 +41,7 @@ pub enum NoteFilterTypes { Unique, Nullifiers, Unverified, + ScriptRoots, } // CONVERSIONS @@ -41,28 +49,7 @@ pub enum NoteFilterTypes { impl From for NativeNoteFilter { fn from(filter: NoteFilter) -> Self { - match filter.note_type { - NoteFilterTypes::All => NativeNoteFilter::All, - NoteFilterTypes::Consumed => NativeNoteFilter::Consumed, - NoteFilterTypes::Committed => NativeNoteFilter::Committed, - NoteFilterTypes::Expected => NativeNoteFilter::Expected, - NoteFilterTypes::Processing => NativeNoteFilter::Processing, - NoteFilterTypes::List => { - let note_ids = - filter.note_ids.unwrap_or_else(|| panic!("Note IDs required for List filter")); - NativeNoteFilter::List(note_ids.iter().map(Into::into).collect()) - }, - NoteFilterTypes::Unique => { - let note_ids = - filter.note_ids.unwrap_or_else(|| panic!("Note ID required for Unique filter")); - - assert!(note_ids.len() == 1, "Only one Note ID can be provided"); - - NativeNoteFilter::Unique(note_ids.first().unwrap().into()) - }, - NoteFilterTypes::Nullifiers => NativeNoteFilter::Nullifiers(vec![]), - NoteFilterTypes::Unverified => NativeNoteFilter::Unverified, - } + (&filter).into() } } @@ -93,6 +80,19 @@ impl From<&NoteFilter> for NativeNoteFilter { }, NoteFilterTypes::Nullifiers => NativeNoteFilter::Nullifiers(vec![]), NoteFilterTypes::Unverified => NativeNoteFilter::Unverified, + NoteFilterTypes::ScriptRoots => { + let script_roots = filter + .script_roots + .clone() + .unwrap_or_else(|| panic!("Script roots required for ScriptRoots filter")); + + NativeNoteFilter::ScriptRoots( + script_roots + .iter() + .map(|script_root| NoteScriptRoot::from_raw(script_root.into())) + .collect(), + ) + }, } } } From 610ea05efbc8cc865271a4254d6886f75b4c827f Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 22 Jul 2026 17:21:32 -0300 Subject: [PATCH 12/15] chore: rename changelog section to Unreleased --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6daa1098..dac8e132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.16.0-alpha.2 (TBD) +## Unreleased ### Changes From 12845aea4f0cdeb54e771c754e00089873025002 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 23 Jul 2026 16:51:51 -0300 Subject: [PATCH 13/15] perf: index inputNotes by scriptRoot --- crates/idxdb-store/src/js/notes.js | 4 ++-- crates/idxdb-store/src/js/schema.js | 2 +- crates/idxdb-store/src/ts/notes.ts | 4 ++-- crates/idxdb-store/src/ts/schema.ts | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/idxdb-store/src/js/notes.js b/crates/idxdb-store/src/js/notes.js index d52a2470..8a374bd3 100644 --- a/crates/idxdb-store/src/js/notes.js +++ b/crates/idxdb-store/src/js/notes.js @@ -86,9 +86,9 @@ export async function getInputNotesFromDetailsCommitments(dbId, detailsCommitmen export async function getInputNotesFromScriptRoots(dbId, scriptRoots) { try { const db = getDatabase(dbId); - const roots = new Set(scriptRoots); let notes = await db.inputNotes - .filter((note) => roots.has(note.scriptRoot)) + .where("scriptRoot") + .anyOf(scriptRoots) .toArray(); return await processInputNotes(dbId, notes); } diff --git a/crates/idxdb-store/src/js/schema.js b/crates/idxdb-store/src/js/schema.js index 314f7a94..f29f33d6 100644 --- a/crates/idxdb-store/src/js/schema.js +++ b/crates/idxdb-store/src/js/schema.js @@ -81,7 +81,7 @@ const V1_STORES = { [Table.Addresses]: indexes("address", "id"), [Table.Transactions]: indexes("id", "statusVariant"), [Table.TransactionScripts]: indexes("scriptRoot"), - [Table.InputNotes]: indexes("detailsCommitment", "noteId", "nullifier", "stateDiscriminant", "[consumedBlockHeight+consumedTxOrder+noteId]"), + [Table.InputNotes]: indexes("detailsCommitment", "noteId", "nullifier", "scriptRoot", "stateDiscriminant", "[consumedBlockHeight+consumedTxOrder+noteId]"), [Table.OutputNotes]: indexes("detailsCommitment", "noteId", "recipientDigest", "stateDiscriminant", "nullifier"), [Table.NotesScripts]: indexes("scriptRoot"), [Table.BlockchainCheckpoint]: indexes("id"), diff --git a/crates/idxdb-store/src/ts/notes.ts b/crates/idxdb-store/src/ts/notes.ts index 34198183..da044369 100644 --- a/crates/idxdb-store/src/ts/notes.ts +++ b/crates/idxdb-store/src/ts/notes.ts @@ -104,9 +104,9 @@ export async function getInputNotesFromScriptRoots( ) { try { const db = getDatabase(dbId); - const roots = new Set(scriptRoots); let notes = await db.inputNotes - .filter((note) => roots.has(note.scriptRoot)) + .where("scriptRoot") + .anyOf(scriptRoots) .toArray(); return await processInputNotes(dbId, notes); } catch (err) { diff --git a/crates/idxdb-store/src/ts/schema.ts b/crates/idxdb-store/src/ts/schema.ts index fe406a3d..b617319e 100644 --- a/crates/idxdb-store/src/ts/schema.ts +++ b/crates/idxdb-store/src/ts/schema.ts @@ -314,6 +314,7 @@ const V1_STORES: Record = { "detailsCommitment", "noteId", "nullifier", + "scriptRoot", "stateDiscriminant", "[consumedBlockHeight+consumedTxOrder+noteId]" ), From 477ca5562f9fbe0e35b0d01511cb4a7e72092ce6 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 29 Jul 2026 12:23:26 -0300 Subject: [PATCH 14/15] chore: point client crates at rust-sdk#2335 (note filter by script root) --- CHANGELOG.md | 4 ++-- Cargo.lock | 4 ++-- Cargo.toml | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dac8e132..cbcc9aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,11 @@ * [BREAKING][web] Transaction submissions now encrypt their private inputs, so the RPC operator relaying them cannot read them: only holders of the validator set's shared encryption secret can. On first submission the client fetches the validator set's transaction encryption key from the node, verifies a validator attestation for it against the validator set committed in a trusted block header (bound to the chain's genesis commitment, so an attestation cannot be replayed from another network), and caches the verified key in the store; a submission rejected for having been sealed against a retired key evicts the cached key and the next submission re-fetches. Requires a node that unseals submitted inputs — such nodes reject plaintext submissions, and older nodes reject sealed ones, so client and node must be upgraded together. ([#252](https://github.com/0xMiden/web-sdk/pull/252), client [#2341](https://github.com/0xMiden/rust-sdk/pull/2341)) * [BREAKING][web] Removed `notes.fetchPrivate({ mode: "all" })` (`WasmWebClient.fetchAllPrivateNotes`). `fetchPrivate()` now takes no arguments and always fetches incrementally from the stored pagination cursor. The full re-scan is no longer needed: historical notes for a newly tracked tag sit below the shared cursor and are now backfilled automatically during `sync()`, one tag at a time, so callers that previously reached for `mode: "all"` after adding a tag should just sync. Callers passing the option get a type error; the argument is otherwise ignored at runtime. -* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to the rust-sdk `encrypted-tx-inputs` branch ([#2341](https://github.com/0xMiden/rust-sdk/pull/2341), `32dfba8d`), ahead of the `0.16.0-alpha.1` release, pending the next alpha. Inherited upstream changes include encrypted transaction submission (see above), note-transport attachment support, a note-screener batch cache, faster historical-note retrieval, and single-account note screening: `notes.listAvailable({ account })` now screens the given account only, instead of screening every tracked account and discarding the rest, so its cost no longer grows with the number of tracked accounts. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). +* [CHANGE][web] `miden-client` and `miden-client-sqlite-store` are pinned to the rust-sdk `note_filter_script_root` branch ([#2335](https://github.com/0xMiden/rust-sdk/pull/2335), `64a35ec3`), which is stacked on the `encrypted-tx-inputs` branch ([#2341](https://github.com/0xMiden/rust-sdk/pull/2341)), ahead of the `0.16.0-alpha.1` release, pending the next alpha. Inherited upstream changes include encrypted transaction submission (see above), the store-level note script root filter (see below), note-transport attachment support, a note-screener batch cache, faster historical-note retrieval, and single-account note screening: `notes.listAvailable({ account })` now screens the given account only, instead of screening every tracked account and discarding the rest, so its cost no longer grows with the number of tracked accounts. Protocol-layer versions are unchanged (`miden-protocol` / `miden-standards` / `miden-tx` at `0.16.0-alpha.4`). ### Enhancements -* [FEATURE][web] `notes.list({ scriptRoots: [...] })` filters received notes by note script root, given as hex strings or `Word` instances (e.g. from `NoteScript.root()`). This narrows candidate notes at the store level, without loading and screening unrelated notes. `notes.listSent` returns an empty list for this query, since script roots are only tracked for received notes. (web-sdk#TBD, client #TBD) +* [FEATURE][web] `notes.list({ scriptRoots: [...] })` filters received notes by note script root, given as hex strings or `Word` instances (e.g. from `NoteScript.root()`). This narrows candidate notes at the store level, without loading and screening unrelated notes. `notes.listSent` returns an empty list for this query, since script roots are only tracked for received notes. ([#249](https://github.com/0xMiden/web-sdk/pull/249), client [#2335](https://github.com/0xMiden/rust-sdk/pull/2335)) ## 0.16.0-alpha.1 (2026-07-19) diff --git a/Cargo.lock b/Cargo.lock index 2ef2605a..ae6eb3b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#32dfba8df1eb5910a189b023365aa644f8efbdcb" +source = "git+https://github.com/0xMiden/rust-sdk?rev=64a35ec319bd8d4e4e0358d38dbe777d4d6dd2b3#64a35ec319bd8d4e4e0358d38dbe777d4d6dd2b3" dependencies = [ "anyhow", "async-trait", @@ -1790,7 +1790,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-alpha.1" -source = "git+https://github.com/0xMiden/rust-sdk?branch=encrypted-tx-inputs#32dfba8df1eb5910a189b023365aa644f8efbdcb" +source = "git+https://github.com/0xMiden/rust-sdk?rev=64a35ec319bd8d4e4e0358d38dbe777d4d6dd2b3#64a35ec319bd8d4e4e0358d38dbe777d4d6dd2b3" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 13f24014..02b87b6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,9 @@ must_use_candidate = "allow" # This marks man should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. -# TEMPORARY: points the client crates at the encrypted-transaction-inputs branch -# (0xMiden/rust-sdk#2341), pending the next alpha release. +# TEMPORARY: points the client crates at the note-filter-by-script-root branch +# (0xMiden/rust-sdk#2335), which is stacked on the encrypted-transaction-inputs +# branch (0xMiden/rust-sdk#2341), pending the next alpha release. [patch.crates-io] -miden-client = { branch = "encrypted-tx-inputs", git = "https://github.com/0xMiden/rust-sdk" } -miden-client-sqlite-store = { branch = "encrypted-tx-inputs", git = "https://github.com/0xMiden/rust-sdk" } +miden-client = { git = "https://github.com/0xMiden/rust-sdk", rev = "64a35ec319bd8d4e4e0358d38dbe777d4d6dd2b3" } +miden-client-sqlite-store = { git = "https://github.com/0xMiden/rust-sdk", rev = "64a35ec319bd8d4e4e0358d38dbe777d4d6dd2b3" } From 704dbc5224aea0ad251a125ace8a8ab1d643d798 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 29 Jul 2026 15:35:36 -0300 Subject: [PATCH 15/15] Drop unreachable catch-all arms in note filter matches --- crates/idxdb-store/src/note/mod.rs | 36 +++++++++++++----------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/crates/idxdb-store/src/note/mod.rs b/crates/idxdb-store/src/note/mod.rs index 6f663118..4586ad2f 100644 --- a/crates/idxdb-store/src/note/mod.rs +++ b/crates/idxdb-store/src/note/mod.rs @@ -58,7 +58,7 @@ impl IdxdbStore { filter: NoteFilter, ) -> Result, StoreError> { let input_notes_idxdb: Vec = - await_js(filter.to_input_notes_promise(self.db_id())?, "failed to get input notes") + await_js(filter.to_input_notes_promise(self.db_id()), "failed to get input notes") .await?; input_notes_idxdb @@ -72,7 +72,7 @@ impl IdxdbStore { filter: NoteFilter, ) -> Result, StoreError> { let output_notes_idxdb: Vec = - await_js(filter.to_output_note_promise(self.db_id())?, "failed to get output notes") + await_js(filter.to_output_note_promise(self.db_id()), "failed to get output notes") .await?; output_notes_idxdb @@ -182,19 +182,23 @@ fn input_note_state_discriminants(filter: &NoteFilter) -> Option> { InputNoteState::STATE_PROCESSING_AUTHENTICATED, InputNoteState::STATE_PROCESSING_UNAUTHENTICATED, ]), - _ => None, + NoteFilter::List(_) + | NoteFilter::Unique(_) + | NoteFilter::Nullifiers(_) + | NoteFilter::DetailsCommitments(_) + | NoteFilter::ScriptRoots(_) => None, } } // Provide extension methods for NoteFilter via a local trait pub(crate) trait NoteFilterExt { - fn to_input_notes_promise(&self, db_id: &str) -> Result; - fn to_output_note_promise(&self, db_id: &str) -> Result; + fn to_input_notes_promise(&self, db_id: &str) -> Promise; + fn to_output_note_promise(&self, db_id: &str) -> Promise; } impl NoteFilterExt for NoteFilter { - fn to_input_notes_promise(&self, db_id: &str) -> Result { - let promise = match self { + fn to_input_notes_promise(&self, db_id: &str) -> Promise { + match self { NoteFilter::All | NoteFilter::Consumed | NoteFilter::Committed @@ -232,16 +236,11 @@ impl NoteFilterExt for NoteFilter { script_roots.iter().map(NoteScriptRoot::to_hex).collect(); idxdb_get_input_notes_from_script_roots(db_id, script_roots_as_str) }, - filter => { - return Err(StoreError::QueryError(format!("unsupported note filter: {filter:?}"))); - }, - }; - - Ok(promise) + } } - fn to_output_note_promise(&self, db_id: &str) -> Result { - let promise = match self { + fn to_output_note_promise(&self, db_id: &str) -> Promise { + match self { NoteFilter::All | NoteFilter::Consumed | NoteFilter::Committed @@ -293,11 +292,6 @@ impl NoteFilterExt for NoteFilter { commitments.iter().map(NoteDetailsCommitment::to_hex).collect(); idxdb_get_output_notes_from_details_commitments(db_id, commitments_as_str) }, - filter => { - return Err(StoreError::QueryError(format!("unsupported note filter: {filter:?}"))); - }, - }; - - Ok(promise) + } } }