1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use js_export_macro::js_export;
use miden_client::Word;
use miden_client::note::{Note as NativeNote, NoteId};
use crate::platform::{JsErr, from_str_err};
use crate::{WebClient, js_error_with_context};
#[js_export]
impl WebClient {
/// Relay a private note through the note-transport layer with an explicit block hint.
///
/// `scan_after_block_num` is the block from which the recipient starts scanning FORWARD for the
/// note's on-chain commitment. It MUST be at or below the note's commitment block — a hint
/// above the commitment is never scanned back to, so the recipient silently never receives
/// the note. A safe, always-valid choice is the chain tip at the moment the note's
/// transaction was submitted (the note cannot have committed earlier); a tighter value just
/// means the recipient scans fewer blocks.
///
/// For one of this client's own output notes, prefer [`WebClient::send_private_output_note`],
/// which derives this block from the note's stored `expected_height` for you.
#[js_export(js_name = "sendPrivateNote")]
pub async fn send_private_note(
&self,
note: crate::models::note::Note,
address: crate::models::address::Address,
scan_after_block_num: u32,
) -> 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."))?;
let native_note: NativeNote = note.into();
client
.send_private_note_with_block_hint(
native_note,
&address.into(),
scan_after_block_num.into(),
)
.await
.map_err(|e| js_error_with_context(e, "failed sending private note"))?;
Ok(())
}
/// Relay one of this client's own private output notes through the note-transport layer.
///
/// The recipient's scan-start block is derived from the output note's stored `expected_height`
/// (the chain tip when the note's transaction was submitted), so delivery is correct regardless
/// of how far this client has since synced past the note — unlike a bare sync-height hint,
/// which overshoots the commitment once the sender advances past it (e.g. relaying after
/// waiting for the transaction to commit) and silently drops delivery. The note must exist
/// in this client's store as an output note (i.e. its transaction has been applied).
#[js_export(js_name = "sendPrivateOutputNote")]
pub async fn send_private_output_note(
&self,
note_id: String,
address: crate::models::address::Address,
) -> 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."))?;
let note_id: NoteId = NoteId::from_raw(
Word::try_from(note_id)
.map_err(|err| js_error_with_context(err, "failed to parse output note id"))?,
);
let record = client
.get_output_note(note_id)
.await
.map_err(|e| js_error_with_context(e, "failed reading output note"))?
.ok_or_else(|| from_str_err("No output note found for the given id"))?;
let scan_after_block_num = record.expected_height();
let native_note: NativeNote = record.try_into().map_err(|e| {
js_error_with_context(e, "output note has no details to relay (recipient unknown)")
})?;
client
.send_private_note_with_block_hint(native_note, &address.into(), scan_after_block_num)
.await
.map_err(|e| js_error_with_context(e, "failed sending private output note"))?;
Ok(())
}
/// Fetch private notes from the note transport layer
///
/// 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;
let client = guard
.as_mut()
.ok_or_else(|| from_str_err("Client not initialized. Call createClient() first."))?;
client
.fetch_private_notes()
.await
.map_err(|e| js_error_with_context(e, "failed fetching private notes"))?;
Ok(())
}
}