Skip to content

Commit 97208c0

Browse files
authored
feat(web-client): note transport integration (0xMiden#1374)
* feat: web-client note transport * Fix typo * Use `js_error_with_context` in web-client's note-transport * Use paginated fetch-notes in fetchPrivateNotes
1 parent 17f78b0 commit 97208c0

File tree

15 files changed

+425
-37
lines changed

15 files changed

+425
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* [BREAKING] Implemented `AccountFile` in the WebClient ([#1258](https://github.com/0xMiden/miden-client/pull/1258)).
3838
* [BREAKING] Added remote key storage and signature requesting to the `WebKeyStore` ([#1371](https://github.com/0xMiden/miden-client/pull/1371))
3939
* Added `sqlite_store` under `ClientBuilderSqliteExt` method to the `ClientBuilder` ([#1416](https://github.com/0xMiden/miden-client/pull/1416))
40+
* [BREAKING] Updated the Web Client to integrate Note Transport ([#1374](https://github.com/0xMiden/miden-client/pull/1374))
4041

4142
## 0.11.8 (2025-09-29)
4243

crates/rust-client/src/note_transport/grpc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,15 @@ impl GrpcNoteTransportClient {
6363

6464
/// gRPC client (WASM) constructor
6565
#[cfg(target_arch = "wasm32")]
66-
pub fn connect(endpoint: String, _timeout_ms: u64) -> Result<Self, NoteTransportError> {
66+
pub fn new(endpoint: String) -> Self {
6767
let wasm_client = tonic_web_wasm_client::Client::new(endpoint);
6868
let health_client = HealthClient::new(wasm_client.clone());
6969
let client = MidenNoteTransportClient::new(wasm_client);
7070

71-
Ok(Self {
71+
Self {
7272
client: RwLock::new(client),
7373
health_client: RwLock::new(health_client),
74-
})
74+
}
7575
}
7676

7777
/// Get a lock to the main client

crates/rust-client/src/note_transport/mod.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use futures::Stream;
1111
use miden_lib::utils::Serializable;
1212
use miden_objects::address::Address;
1313
use miden_objects::note::{Note, NoteDetails, NoteHeader, NoteTag};
14-
use miden_tx::utils::{Deserializable, DeserializationError, SliceReader};
14+
use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, SliceReader};
1515

1616
pub use self::errors::NoteTransportError;
1717
use crate::store::{InputNoteRecord, Store};
@@ -223,7 +223,7 @@ pub trait NoteStream:
223223
{
224224
}
225225

226-
/// Information about a note in API responses
226+
/// Information about a note fetched from the note transport network
227227
#[derive(Debug, Clone)]
228228
pub struct NoteInfo {
229229
/// Note header
@@ -232,6 +232,37 @@ pub struct NoteInfo {
232232
pub details_bytes: Vec<u8>,
233233
}
234234

235+
// SERIALIZATION
236+
// ================================================================================================
237+
238+
impl Serializable for NoteInfo {
239+
fn write_into<W: ByteWriter>(&self, target: &mut W) {
240+
self.header.write_into(target);
241+
self.details_bytes.write_into(target);
242+
}
243+
}
244+
245+
impl Deserializable for NoteInfo {
246+
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
247+
let header = NoteHeader::read_from(source)?;
248+
let details_bytes = Vec::<u8>::read_from(source)?;
249+
Ok(NoteInfo { header, details_bytes })
250+
}
251+
}
252+
253+
impl Serializable for NoteTransportCursor {
254+
fn write_into<W: ByteWriter>(&self, target: &mut W) {
255+
self.0.write_into(target);
256+
}
257+
}
258+
259+
impl Deserializable for NoteTransportCursor {
260+
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
261+
let value = u64::read_from(source)?;
262+
Ok(Self::new(value))
263+
}
264+
}
265+
235266
fn rejoin_note(header: &NoteHeader, details_bytes: &[u8]) -> Result<Note, DeserializationError> {
236267
let mut reader = SliceReader::new(details_bytes);
237268
let details = NoteDetails::read_from(&mut reader)?;

crates/rust-client/src/test_utils/note_transport.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use chrono::Utc;
99
use futures::Stream;
1010
use miden_objects::note::{NoteHeader, NoteTag};
1111
use miden_tx::utils::sync::RwLock;
12+
use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
1213

1314
use crate::note_transport::{
1415
NoteInfo,
@@ -84,8 +85,9 @@ impl Default for MockNoteTransportNode {
8485
/// Mock Note Transport API
8586
///
8687
/// Simulates communications with the note transport node.
88+
#[derive(Clone, Default)]
8789
pub struct MockNoteTransportApi {
88-
mock_node: Arc<RwLock<MockNoteTransportNode>>,
90+
pub mock_node: Arc<RwLock<MockNoteTransportNode>>,
8991
}
9092

9193
impl MockNoteTransportApi {
@@ -146,3 +148,20 @@ impl NoteTransportClient for MockNoteTransportApi {
146148
Ok(Box::new(DummyNoteStream {}))
147149
}
148150
}
151+
152+
// SERIALIZATION
153+
// ================================================================================================
154+
155+
impl Serializable for MockNoteTransportNode {
156+
fn write_into<W: ByteWriter>(&self, target: &mut W) {
157+
self.notes.write_into(target);
158+
}
159+
}
160+
161+
impl Deserializable for MockNoteTransportNode {
162+
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
163+
let notes = BTreeMap::<NoteTag, Vec<(NoteInfo, NoteTransportCursor)>>::read_from(source)?;
164+
165+
Ok(Self { notes })
166+
}
167+
}

crates/web-client/js/index.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,9 @@ export class WebClient {
205205
* `SigningInputs.serialize()`. Must return an array of numeric values (numbers or numeric
206206
* strings) representing the signature elements, either directly or wrapped in a `Promise`.
207207
*/
208-
constructor(rpcUrl, seed, getKeyCb, insertKeyCb, signCb) {
208+
constructor(rpcUrl, noteTransportUrl, seed, getKeyCb, insertKeyCb, signCb) {
209209
this.rpcUrl = rpcUrl;
210+
this.noteTransportUrl = noteTransportUrl;
210211
this.seed = seed;
211212
this.getKeyCb = getKeyCb;
212213
this.insertKeyCb = insertKeyCb;
@@ -278,6 +279,7 @@ export class WebClient {
278279
action: WorkerAction.INIT,
279280
args: [
280281
this.rpcUrl,
282+
this.noteTransportUrl,
281283
this.seed,
282284
this.getKeyCb,
283285
this.insertKeyCb,
@@ -303,15 +305,16 @@ export class WebClient {
303305
* This method is async so you can await the asynchronous call to createClient().
304306
*
305307
* @param {string} rpcUrl - The RPC URL.
308+
* @param {string} noteTransportUrl - The note transport URL (optional).
306309
* @param {string} seed - The seed for the account.
307310
* @returns {Promise<WebClient>} The fully initialized WebClient.
308311
*/
309-
static async createClient(rpcUrl, seed) {
312+
static async createClient(rpcUrl, noteTransportUrl, seed) {
310313
// Construct the instance (synchronously).
311-
const instance = new WebClient(rpcUrl, seed);
314+
const instance = new WebClient(rpcUrl, noteTransportUrl, seed);
312315

313316
// Wait for the underlying wasmWebClient to be initialized.
314-
await instance.wasmWebClient.createClient(rpcUrl, seed);
317+
await instance.wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed);
315318

316319
// Wait for the worker to be ready
317320
await instance.ready;
@@ -341,6 +344,7 @@ export class WebClient {
341344
* This method is async so you can await the asynchronous call to createClientWithExternalKeystore().
342345
*
343346
* @param {string} rpcUrl - The RPC URL.
347+
* @param {string | undefined} noteTransportUrl - The note transport URL (optional).
344348
* @param {string | undefined} seed - The seed for the account.
345349
* @param {Function | undefined} getKeyCb - The get key callback.
346350
* @param {Function | undefined} insertKeyCb - The insert key callback.
@@ -349,15 +353,24 @@ export class WebClient {
349353
*/
350354
static async createClientWithExternalKeystore(
351355
rpcUrl,
356+
noteTransportUrl,
352357
seed,
353358
getKeyCb,
354359
insertKeyCb,
355360
signCb
356361
) {
357362
// Construct the instance (synchronously).
358-
const instance = new WebClient(rpcUrl, seed, getKeyCb, insertKeyCb, signCb);
363+
const instance = new WebClient(
364+
rpcUrl,
365+
noteTransportUrl,
366+
seed,
367+
getKeyCb,
368+
insertKeyCb,
369+
signCb
370+
);
359371
await instance.wasmWebClient.createClientWithExternalKeystore(
360372
rpcUrl,
373+
noteTransportUrl,
361374
seed,
362375
getKeyCb,
363376
insertKeyCb,
@@ -551,15 +564,24 @@ export class MockWebClient extends WebClient {
551564
* Factory method to create a WebClient with a mock chain for testing purposes.
552565
*
553566
* @param serializedMockChain - Serialized mock chain data (optional). Will use an empty chain if not provided.
567+
* @param serializedMockNoteTransportNode - Serialized mock note transport node data (optional). Will use a new instance if not provided.
554568
* @param seed - The seed for the account (optional).
555569
* @returns A promise that resolves to a MockWebClient.
556570
*/
557-
static async createClient(serializedMockChain, seed) {
571+
static async createClient(
572+
serializedMockChain,
573+
serializedMockNoteTransportNode,
574+
seed
575+
) {
558576
// Construct the instance (synchronously).
559577
const instance = new MockWebClient(seed);
560578

561579
// Wait for the underlying wasmWebClient to be initialized.
562-
await instance.wasmWebClient.createMockClient(seed, serializedMockChain);
580+
await instance.wasmWebClient.createMockClient(
581+
seed,
582+
serializedMockChain,
583+
serializedMockNoteTransportNode
584+
);
563585

564586
// Wait for the worker to be ready
565587
await instance.ready;
@@ -591,10 +613,13 @@ export class MockWebClient extends WebClient {
591613
}
592614

593615
let serializedMockChain = this.wasmWebClient.serializeMockChain().buffer;
616+
let serializedMockNoteTransportNode =
617+
this.wasmWebClient.serializeMockNoteTransportNode().buffer;
594618

595619
const serializedSyncSummaryBytes = await this.callMethodWithWorker(
596620
MethodName.SYNC_STATE_MOCK,
597-
serializedMockChain
621+
serializedMockChain,
622+
serializedMockNoteTransportNode
598623
);
599624

600625
return wasm.SyncSummary.deserialize(
@@ -625,17 +650,25 @@ export class MockWebClient extends WebClient {
625650
}
626651

627652
args.push(this.wasmWebClient.serializeMockChain().buffer);
653+
args.push(this.wasmWebClient.serializeMockNoteTransportNode().buffer);
628654

629655
// Always call the same worker method.
630-
let serializedMockChain = await this.callMethodWithWorker(
656+
let result = await this.callMethodWithWorker(
631657
MethodName.SUBMIT_TRANSACTION_MOCK,
632658
...args
633659
);
634660

635-
serializedMockChain = new Uint8Array(serializedMockChain);
661+
const serializedMockChain = new Uint8Array(result.serializedMockChain);
662+
const serializedMockNoteTransportNode = new Uint8Array(
663+
result.serializedMockNoteTransportNode
664+
);
636665

637666
this.wasmWebClient = new WasmWebClient();
638-
await this.wasmWebClient.createMockClient(this.seed, serializedMockChain);
667+
await this.wasmWebClient.createMockClient(
668+
this.seed,
669+
serializedMockChain,
670+
serializedMockNoteTransportNode
671+
);
639672
} catch (error) {
640673
console.error("INDEX.JS: Error in submitTransaction:", error.toString());
641674
throw error;

crates/web-client/js/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ export declare class WebClient extends WasmWebClient {
8787
* Factory method to create and initialize a new wrapped WebClient.
8888
*
8989
* @param rpcUrl - The RPC URL (optional).
90+
* @param noteTransportUrl - The note transport URL (optional).
9091
* @param seed - The seed for the account (optional).
9192
* @returns A promise that resolves to a fully initialized WebClient.
9293
*/
9394
static createClient(
9495
rpcUrl?: string,
96+
noteTransportUrl?: string,
9597
seed?: string
9698
): Promise<WebClient & WasmWebClient>;
9799

crates/web-client/js/workers/web-client-methods-worker.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,41 @@ const methodHandlers = {
123123

124124
// Add mock methods to the handler mapping.
125125
methodHandlers[MethodName.SYNC_STATE_MOCK] = async (args) => {
126-
let [serializedMockChain] = args;
126+
let [serializedMockChain, serializedMockNoteTransportNode] = args;
127127
serializedMockChain = new Uint8Array(serializedMockChain);
128-
await wasmWebClient.createMockClient(wasmSeed, serializedMockChain);
128+
serializedMockNoteTransportNode = serializedMockNoteTransportNode
129+
? new Uint8Array(serializedMockNoteTransportNode)
130+
: null;
131+
await wasmWebClient.createMockClient(
132+
wasmSeed,
133+
serializedMockChain,
134+
serializedMockNoteTransportNode
135+
);
129136

130137
return await methodHandlers[MethodName.SYNC_STATE]();
131138
};
132139

133140
methodHandlers[MethodName.SUBMIT_TRANSACTION_MOCK] = async (args) => {
141+
let serializedMockNoteTransportNode = args.pop();
134142
let serializedMockChain = args.pop();
135143
serializedMockChain = new Uint8Array(serializedMockChain);
144+
serializedMockNoteTransportNode = serializedMockNoteTransportNode
145+
? new Uint8Array(serializedMockNoteTransportNode)
146+
: null;
136147
wasmWebClient = new wasm.WebClient();
137-
await wasmWebClient.createMockClient(wasmSeed, serializedMockChain);
148+
await wasmWebClient.createMockClient(
149+
wasmSeed,
150+
serializedMockChain,
151+
serializedMockNoteTransportNode
152+
);
138153

139154
await methodHandlers[MethodName.SUBMIT_TRANSACTION](args);
140155

141-
return wasmWebClient.serializeMockChain().buffer;
156+
return {
157+
serializedMockChain: wasmWebClient.serializeMockChain().buffer,
158+
serializedMockNoteTransportNode:
159+
wasmWebClient.serializeMockNoteTransportNode().buffer,
160+
};
142161
};
143162

144163
/**
@@ -148,10 +167,10 @@ async function processMessage(event) {
148167
const { action, args, methodName, requestId } = event.data;
149168
try {
150169
if (action === WorkerAction.INIT) {
151-
const [rpcUrl, seed] = args;
170+
const [rpcUrl, noteTransportUrl, seed] = args;
152171
// Initialize the WASM WebClient.
153172
wasmWebClient = new wasm.WebClient();
154-
await wasmWebClient.createClient(rpcUrl, seed);
173+
await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed);
155174

156175
wasmSeed = seed;
157176
ready = true;

crates/web-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@demox-labs/miden-sdk",
3-
"version": "0.12.0-next.30",
3+
"version": "0.12.0-next.31",
44
"description": "Miden Wasm SDK",
55
"collaborators": [
66
"Miden",

0 commit comments

Comments
 (0)