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::serde::Serializable;
15use miden_tx::auth::TransactionAuthenticator;
16use miden_tx::utils::serde::{
17 ByteReader,
18 ByteWriter,
19 Deserializable,
20 DeserializationError,
21 SliceReader,
22};
23
24pub use self::errors::NoteTransportError;
25use crate::{Client, ClientError};
26
27pub const NOTE_TRANSPORT_TESTNET_ENDPOINT: &str = "https://transport.miden.io";
28pub const NOTE_TRANSPORT_DEVNET_ENDPOINT: &str = "https://transport.devnet.miden.io";
29pub const NOTE_TRANSPORT_CURSOR_STORE_SETTING: &str = "note_transport_cursor";
30
31impl<AUTH> Client<AUTH> {
33 pub fn is_note_transport_enabled(&self) -> bool {
35 self.note_transport_api.is_some()
36 }
37
38 pub(crate) fn get_note_transport_api(
42 &self,
43 ) -> Result<Arc<dyn NoteTransportClient>, NoteTransportError> {
44 self.note_transport_api.clone().ok_or(NoteTransportError::Disabled)
45 }
46
47 pub async fn send_private_note(
53 &mut self,
54 note: Note,
55 _address: &Address,
56 ) -> Result<(), ClientError> {
57 let api = self.get_note_transport_api()?;
58
59 let header = note.header().clone();
60 let details = NoteDetails::from(note);
61 let details_bytes = details.to_bytes();
62 api.send_note(header, details_bytes).await?;
65
66 Ok(())
67 }
68}
69
70impl<AUTH> Client<AUTH>
71where
72 AUTH: TransactionAuthenticator + Sync + 'static,
73{
74 pub async fn fetch_private_notes(&mut self) -> Result<(), ClientError> {
87 let note_tags = self.store.get_unique_note_tags().await?;
89 let cursor = self.store.get_note_transport_cursor().await?;
91
92 self.fetch_transport_notes(cursor, note_tags).await?;
93
94 Ok(())
95 }
96
97 pub async fn fetch_all_private_notes(&mut self) -> Result<(), ClientError> {
103 let note_tags = self.store.get_unique_note_tags().await?;
104
105 self.fetch_transport_notes(NoteTransportCursor::init(), note_tags).await?;
106
107 Ok(())
108 }
109
110 pub(crate) async fn fetch_transport_notes<I>(
115 &mut self,
116 cursor: NoteTransportCursor,
117 tags: I,
118 ) -> Result<(), ClientError>
119 where
120 I: IntoIterator<Item = NoteTag>,
121 {
122 const NOTE_LOOKBACK_BLOCKS: u32 = 20;
127
128 let mut notes = Vec::new();
129 let (note_infos, rcursor) = self
131 .get_note_transport_api()?
132 .fetch_notes(&tags.into_iter().collect::<Vec<_>>(), cursor)
133 .await?;
134 for note_info in ¬e_infos {
135 let note = rejoin_note(¬e_info.header, ¬e_info.details_bytes)?;
139 notes.push(note);
140 }
141
142 let sync_height = self.get_sync_height().await?;
143 let after_block_num =
144 BlockNumber::from(sync_height.as_u32().saturating_sub(NOTE_LOOKBACK_BLOCKS));
145
146 let mut note_requests = Vec::with_capacity(notes.len());
148 for note in notes {
149 let tag = note.metadata().tag();
150 let note_file = NoteFile::NoteDetails {
151 details: note.into(),
152 after_block_num,
153 tag: Some(tag),
154 };
155 note_requests.push(note_file);
156 }
157 self.import_notes(¬e_requests).await?;
158
159 self.store.update_note_transport_cursor(rcursor).await?;
161
162 Ok(())
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
171pub struct NoteTransportCursor(u64);
172
173pub struct NoteTransportUpdate {
175 pub cursor: NoteTransportCursor,
177 pub notes: Vec<Note>,
179}
180
181impl NoteTransportCursor {
182 pub fn new(value: u64) -> Self {
183 Self(value)
184 }
185
186 pub fn init() -> Self {
187 Self::new(0)
188 }
189
190 pub fn value(&self) -> u64 {
191 self.0
192 }
193}
194
195impl From<u64> for NoteTransportCursor {
196 fn from(value: u64) -> Self {
197 Self::new(value)
198 }
199}
200
201#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
203#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
204pub trait NoteTransportClient: Send + Sync {
205 async fn send_note(
207 &self,
208 header: NoteHeader,
209 details: Vec<u8>,
210 ) -> Result<(), NoteTransportError>;
211
212 async fn fetch_notes(
217 &self,
218 tag: &[NoteTag],
219 cursor: NoteTransportCursor,
220 ) -> Result<(Vec<NoteInfo>, NoteTransportCursor), NoteTransportError>;
221
222 async fn stream_notes(
224 &self,
225 tag: NoteTag,
226 cursor: NoteTransportCursor,
227 ) -> Result<Box<dyn NoteStream>, NoteTransportError>;
228}
229
230pub trait NoteStream:
232 Stream<Item = Result<Vec<NoteInfo>, NoteTransportError>> + Send + Unpin
233{
234}
235
236#[derive(Debug, Clone)]
238pub struct NoteInfo {
239 pub header: NoteHeader,
241 pub details_bytes: Vec<u8>,
243}
244
245impl Serializable for NoteInfo {
249 fn write_into<W: ByteWriter>(&self, target: &mut W) {
250 self.header.write_into(target);
251 self.details_bytes.write_into(target);
252 }
253}
254
255impl Deserializable for NoteInfo {
256 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
257 let header = NoteHeader::read_from(source)?;
258 let details_bytes = Vec::<u8>::read_from(source)?;
259 Ok(NoteInfo { header, details_bytes })
260 }
261}
262
263impl Serializable for NoteTransportCursor {
264 fn write_into<W: ByteWriter>(&self, target: &mut W) {
265 self.0.write_into(target);
266 }
267}
268
269impl Deserializable for NoteTransportCursor {
270 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
271 let value = u64::read_from(source)?;
272 Ok(Self::new(value))
273 }
274}
275
276fn rejoin_note(header: &NoteHeader, details_bytes: &[u8]) -> Result<Note, DeserializationError> {
277 let mut reader = SliceReader::new(details_bytes);
278 let details = NoteDetails::read_from(&mut reader)?;
279 let metadata = header.metadata().clone();
280 Ok(Note::new(details.assets().clone(), metadata, details.recipient().clone()))
281}