miden_client/note_transport/
mod.rs1pub mod errors;
2pub mod generated;
3#[cfg(feature = "tonic")]
4pub mod grpc;
5
6use alloc::boxed::Box;
7use alloc::sync::Arc;
8use alloc::vec::Vec;
9
10use futures::Stream;
11use miden_protocol::address::Address;
12use miden_protocol::block::BlockNumber;
13use miden_protocol::note::{Note, NoteDetails, NoteFile, NoteHeader, NoteTag};
14use miden_protocol::utils::Serializable;
15use miden_tx::auth::TransactionAuthenticator;
16use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, SliceReader};
17
18pub use self::errors::NoteTransportError;
19use crate::store::Store;
20use crate::{Client, ClientError};
21
22pub const NOTE_TRANSPORT_DEFAULT_ENDPOINT: &str = "https://transport.miden.io";
23pub const NOTE_TRANSPORT_CURSOR_STORE_SETTING: &str = "note_transport_cursor";
24
25impl<AUTH> Client<AUTH> {
27 pub fn is_note_transport_enabled(&self) -> bool {
29 self.note_transport_api.is_some()
30 }
31
32 pub(crate) fn get_note_transport_api(
36 &self,
37 ) -> Result<Arc<dyn NoteTransportClient>, NoteTransportError> {
38 self.note_transport_api.clone().ok_or(NoteTransportError::Disabled)
39 }
40
41 pub async fn send_private_note(
47 &mut self,
48 note: Note,
49 _address: &Address,
50 ) -> Result<(), ClientError> {
51 let api = self.get_note_transport_api()?;
52
53 let header = note.header().clone();
54 let details = NoteDetails::from(note);
55 let details_bytes = details.to_bytes();
56 api.send_note(header, details_bytes).await?;
59
60 Ok(())
61 }
62}
63
64impl<AUTH> Client<AUTH>
65where
66 AUTH: TransactionAuthenticator + Sync + 'static,
67{
68 pub async fn fetch_private_notes(&mut self) -> Result<(), ClientError> {
81 let note_tags = self.store.get_unique_note_tags().await?;
83 let cursor = self.store.get_note_transport_cursor().await?;
85
86 self.fetch_transport_notes(cursor, note_tags).await?;
87
88 Ok(())
89 }
90
91 pub async fn fetch_all_private_notes(&mut self) -> Result<(), ClientError> {
97 let note_tags = self.store.get_unique_note_tags().await?;
98
99 self.fetch_transport_notes(NoteTransportCursor::init(), note_tags).await?;
100
101 Ok(())
102 }
103
104 pub(crate) async fn fetch_transport_notes<I>(
109 &mut self,
110 cursor: NoteTransportCursor,
111 tags: I,
112 ) -> Result<(), ClientError>
113 where
114 I: IntoIterator<Item = NoteTag>,
115 {
116 const NOTE_LOOKBACK_BLOCKS: u32 = 20;
121
122 let mut notes = Vec::new();
123 let (note_infos, rcursor) = self
125 .get_note_transport_api()?
126 .fetch_notes(&tags.into_iter().collect::<Vec<_>>(), cursor)
127 .await?;
128 for note_info in ¬e_infos {
129 let note = rejoin_note(¬e_info.header, ¬e_info.details_bytes)?;
133 notes.push(note);
134 }
135
136 let sync_height = self.get_sync_height().await?;
137 let after_block_num =
138 BlockNumber::from(sync_height.as_u32().saturating_sub(NOTE_LOOKBACK_BLOCKS));
139
140 let mut note_requests = Vec::with_capacity(notes.len());
142 for note in notes {
143 let tag = note.metadata().tag();
144 let note_file = NoteFile::NoteDetails {
145 details: note.into(),
146 after_block_num,
147 tag: Some(tag),
148 };
149 note_requests.push(note_file);
150 }
151 self.import_notes(¬e_requests).await?;
152
153 self.store.update_note_transport_cursor(rcursor).await?;
155
156 Ok(())
157 }
158}
159
160pub(crate) async fn init_note_transport_cursor(store: Arc<dyn Store>) -> Result<(), ClientError> {
162 let setting = NOTE_TRANSPORT_CURSOR_STORE_SETTING;
163 if store.get_setting(setting.into()).await?.is_none() {
164 let initial_cursor = 0u64.to_be_bytes().to_vec();
165 store.set_setting(setting.into(), initial_cursor).await?;
166 }
167 Ok(())
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
175pub struct NoteTransportCursor(u64);
176
177pub struct NoteTransportUpdate {
179 pub cursor: NoteTransportCursor,
181 pub notes: Vec<Note>,
183}
184
185impl NoteTransportCursor {
186 pub fn new(value: u64) -> Self {
187 Self(value)
188 }
189
190 pub fn init() -> Self {
191 Self::new(0)
192 }
193
194 pub fn value(&self) -> u64 {
195 self.0
196 }
197}
198
199impl From<u64> for NoteTransportCursor {
200 fn from(value: u64) -> Self {
201 Self::new(value)
202 }
203}
204
205#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
207#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
208pub trait NoteTransportClient: Send + Sync {
209 async fn send_note(
211 &self,
212 header: NoteHeader,
213 details: Vec<u8>,
214 ) -> Result<(), NoteTransportError>;
215
216 async fn fetch_notes(
221 &self,
222 tag: &[NoteTag],
223 cursor: NoteTransportCursor,
224 ) -> Result<(Vec<NoteInfo>, NoteTransportCursor), NoteTransportError>;
225
226 async fn stream_notes(
228 &self,
229 tag: NoteTag,
230 cursor: NoteTransportCursor,
231 ) -> Result<Box<dyn NoteStream>, NoteTransportError>;
232}
233
234pub trait NoteStream:
236 Stream<Item = Result<Vec<NoteInfo>, NoteTransportError>> + Send + Unpin
237{
238}
239
240#[derive(Debug, Clone)]
242pub struct NoteInfo {
243 pub header: NoteHeader,
245 pub details_bytes: Vec<u8>,
247}
248
249impl Serializable for NoteInfo {
253 fn write_into<W: ByteWriter>(&self, target: &mut W) {
254 self.header.write_into(target);
255 self.details_bytes.write_into(target);
256 }
257}
258
259impl Deserializable for NoteInfo {
260 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
261 let header = NoteHeader::read_from(source)?;
262 let details_bytes = Vec::<u8>::read_from(source)?;
263 Ok(NoteInfo { header, details_bytes })
264 }
265}
266
267impl Serializable for NoteTransportCursor {
268 fn write_into<W: ByteWriter>(&self, target: &mut W) {
269 self.0.write_into(target);
270 }
271}
272
273impl Deserializable for NoteTransportCursor {
274 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
275 let value = u64::read_from(source)?;
276 Ok(Self::new(value))
277 }
278}
279
280fn rejoin_note(header: &NoteHeader, details_bytes: &[u8]) -> Result<Note, DeserializationError> {
281 let mut reader = SliceReader::new(details_bytes);
282 let details = NoteDetails::read_from(&mut reader)?;
283 let metadata = header.metadata().clone();
284 Ok(Note::new(details.assets().clone(), metadata, details.recipient().clone()))
285}