miden_client/rpc/mod.rs
1//! Provides an interface for the client to communicate with a Miden node using
2//! Remote Procedure Calls (RPC).
3//!
4//! This module defines the [`NodeRpcClient`] trait which abstracts calls to the RPC protocol used
5//! to:
6//!
7//! - Submit proven transactions.
8//! - Submit proven batches.
9//! - Retrieve block headers (optionally with MMR proofs).
10//! - Sync state updates (including notes, nullifiers, and account updates).
11//! - Fetch details for specific notes and accounts.
12//!
13//! The client implementation adapts to the target environment automatically:
14//! - Native targets use `tonic` transport with TLS.
15//! - `wasm32` targets use `tonic-web-wasm-client` transport.
16//!
17//! ## Example
18//!
19//! ```no_run
20//! # use miden_client::rpc::{Endpoint, NodeRpcClient, GrpcClient};
21//! # use miden_protocol::block::BlockNumber;
22//! # #[tokio::main]
23//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! // Create a gRPC client instance (assumes default endpoint configuration).
25//! let endpoint = Endpoint::new("https".into(), "localhost".into(), Some(57291));
26//! let mut rpc_client = GrpcClient::new(&endpoint, 1000);
27//!
28//! // Fetch the latest block header (by passing None).
29//! let (block_header, mmr_proof) = rpc_client.get_block_header_by_number(None, true).await?;
30//!
31//! println!("Latest block number: {}", block_header.block_num());
32//! if let Some(proof) = mmr_proof {
33//! println!("MMR proof received accordingly");
34//! }
35//!
36//! # Ok(())
37//! # }
38//! ```
39//! The client also makes use of this component in order to communicate with the node.
40//!
41//! For further details and examples, see the documentation for the individual methods in the
42//! [`NodeRpcClient`] trait.
43
44use alloc::boxed::Box;
45use alloc::collections::{BTreeMap, BTreeSet};
46use alloc::string::String;
47use alloc::vec::Vec;
48use core::fmt;
49
50use domain::account::{
51 AccountDetails,
52 AccountProof,
53 GetAccountRequest,
54 StorageMapEntries,
55 StorageMapEntry,
56 StorageMapFetch,
57 VaultFetch,
58};
59use domain::note::{FetchedNote, NoteSyncBlock, SyncedNoteDetails};
60use domain::nullifier::NullifierUpdate;
61use domain::sync::{ChainMmrInfo, SyncTarget};
62use miden_protocol::Word;
63use miden_protocol::account::{Account, AccountId};
64use miden_protocol::address::NetworkId;
65use miden_protocol::batch::{ProposedBatch, ProvenBatch};
66use miden_protocol::block::{BlockHeader, BlockNumber, ProvenBlock};
67use miden_protocol::crypto::merkle::mmr::MmrProof;
68use miden_protocol::note::{NoteId, NoteMetadata, NoteScript, NoteTag, NoteType, Nullifier};
69use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
70
71use crate::rpc::domain::storage_map::StorageMapInfo;
72
73/// Contains domain types related to RPC requests and responses, as well as utility functions
74/// for dealing with them.
75pub mod domain;
76
77mod errors;
78pub use errors::*;
79
80mod endpoint;
81pub(crate) use domain::limits::RPC_LIMITS_STORE_SETTING;
82pub use domain::limits::RpcLimits;
83pub use domain::status::{NetworkNoteStatus, NetworkNoteStatusInfo, RpcStatusInfo};
84pub use endpoint::Endpoint;
85
86#[cfg(not(feature = "testing"))]
87mod generated;
88#[cfg(feature = "testing")]
89pub mod generated;
90
91#[cfg(feature = "tonic")]
92mod tonic_client;
93#[cfg(feature = "tonic")]
94pub use tonic_client::GrpcClient;
95
96use crate::rpc::domain::account_vault::AccountVaultInfo;
97use crate::rpc::domain::transaction::TransactionRecord;
98use crate::store::InputNoteRecord;
99use crate::store::input_note_states::UnverifiedNoteState;
100
101/// Represents the state that we want to retrieve from the network
102#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
103pub enum AccountStateAt {
104 /// Gets the latest state, for the current chain tip
105 #[default]
106 ChainTip,
107 /// Gets the state at a specific block number
108 Block(BlockNumber),
109}
110
111/// Returns `true` if the note's metadata advertises at least one attachment.
112///
113/// Sync records carry attachment scheme markers (not the attachment content), so a present scheme
114/// in any header slot indicates the note has attachments whose content must be fetched separately.
115fn metadata_has_attachments(metadata: &NoteMetadata) -> bool {
116 metadata.attachment_headers().iter().any(|header| header.scheme().is_some())
117}
118
119// NODE RPC CLIENT TRAIT
120// ================================================================================================
121
122/// Defines the interface for communicating with the Miden node.
123///
124/// The implementers are responsible for connecting to the Miden node, handling endpoint
125/// requests/responses, and translating responses into domain objects relevant for each of the
126/// endpoints.
127#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
128#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
129pub trait NodeRpcClient: Send + Sync {
130 /// Sets the genesis commitment for the client and reconnects to the node providing the
131 /// genesis commitment in the request headers. If the genesis commitment is already set,
132 /// this method does nothing.
133 async fn set_genesis_commitment(&self, commitment: Word) -> Result<(), RpcError>;
134
135 /// Returns the genesis commitment if it has been set, without fetching from the node.
136 fn has_genesis_commitment(&self) -> Option<Word>;
137
138 /// Given a Proven Transaction, send it to the node for it to be included in a future block
139 /// using the `/SubmitProvenTransaction` RPC endpoint.
140 ///
141 /// Returns the node's chain tip at submission (not the block the transaction is committed in).
142 async fn submit_proven_transaction(
143 &self,
144 proven_transaction: ProvenTransaction,
145 transaction_inputs: TransactionInputs,
146 ) -> Result<BlockNumber, RpcError>;
147
148 /// Given a Proven Batch together with the corresponding [`ProposedBatch`] and the list of
149 /// [`TransactionInputs`] (one per transaction, matching the ordering of the batch), sends
150 /// the batch to the node for inclusion in a future block using the `/SubmitProvenBatch`
151 /// RPC endpoint. All transactions in the batch must build on the current mempool state
152 /// following normal transaction submission rules.
153 ///
154 /// Returns the node's chain tip at submission (not the block the batch is committed in).
155 async fn submit_proven_batch(
156 &self,
157 proven_batch: ProvenBatch,
158 proposed_batch: ProposedBatch,
159 transaction_inputs: Vec<TransactionInputs>,
160 ) -> Result<BlockNumber, RpcError>;
161
162 /// Given a block number, fetches the block header corresponding to that height from the node
163 /// using the `/GetBlockHeaderByNumber` endpoint.
164 /// If `include_mmr_proof` is set to true and the function returns an `Ok`, the second value
165 /// of the return tuple should always be Some(MmrProof).
166 ///
167 /// When `None` is provided, returns info regarding the latest block.
168 ///
169 /// When `block_num` is `Some`, implementations must verify that the returned header's block
170 /// number matches the requested one and return [`RpcError::InvalidResponse`] otherwise.
171 ///
172 /// Errors:
173 /// - [`RpcError::InvalidResponse`] if the node returns a header whose block number does not
174 /// match the requested `block_num`.
175 async fn get_block_header_by_number(
176 &self,
177 block_num: Option<BlockNumber>,
178 include_mmr_proof: bool,
179 ) -> Result<(BlockHeader, Option<MmrProof>), RpcError>;
180
181 /// Given a block number, fetches the block corresponding to that height from the node using
182 /// the `/GetBlockByNumber` RPC endpoint.
183 ///
184 /// If `include_proof` is set to true, the block proof will be included in the response.
185 ///
186 /// Implementations must verify that the returned block's number matches the requested
187 /// `block_num` and return [`RpcError::InvalidResponse`] otherwise.
188 ///
189 /// # Errors:
190 /// - [`RpcError::InvalidResponse`] if the node returns a block whose number does not match the
191 /// requested `block_num`.
192 async fn get_block_by_number(
193 &self,
194 block_num: BlockNumber,
195 include_proof: bool,
196 ) -> Result<ProvenBlock, RpcError>;
197
198 /// Fetches note-related data for a list of [`NoteId`] using the `/GetNotesById`
199 /// RPC endpoint.
200 ///
201 /// For [`miden_protocol::note::NoteType::Private`] notes, the response includes only the
202 /// [`miden_protocol::note::NoteMetadata`].
203 ///
204 /// For [`miden_protocol::note::NoteType::Public`] notes, the response includes all note details
205 /// (recipient, assets, script, etc.).
206 ///
207 /// In both cases, a [`miden_protocol::note::NoteInclusionProof`] is returned so the caller can
208 /// verify that each note is part of the block's note tree.
209 ///
210 /// Implementations must verify that every returned note's ID was present in `note_ids` and
211 /// return [`RpcError::InvalidResponse`] otherwise.
212 ///
213 /// Errors:
214 /// - [`RpcError::InvalidResponse`] if the node returns a note whose ID was not requested.
215 async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError>;
216
217 /// Fetches the MMR delta for a given block range using the `/SyncChainMmr` RPC endpoint.
218 ///
219 /// - `current_block_height` is the last block number already present in the caller's MMR.
220 /// - `upper_bound` determines the upper bound of the sync range. Can be a specific block number
221 /// (`BlockNumber`), or a chain tip finality level: `CommittedChainTip` syncs up to the latest
222 /// committed block (the chain tip), while `ProvenChainTip` syncs up to the latest proven
223 /// block which may be behind the committed tip.
224 async fn sync_chain_mmr(
225 &self,
226 current_block_height: BlockNumber,
227 upper_bound: SyncTarget,
228 ) -> Result<ChainMmrInfo, RpcError>;
229
230 /// Fetches the full state of a public account from the node using the `/GetAccount` endpoint,
231 /// and then resolves oversized vault and storage map entries via the `SyncVault` and
232 /// `SyncStorageMap` endpoints when needed.
233 ///
234 /// - `account_id` is the ID of the wanted account.
235 ///
236 /// Returns `Ok(None)` for accounts without public state.
237 async fn get_account_details(
238 &self,
239 account_id: AccountId,
240 ) -> Result<Option<Account>, RpcError> {
241 // Accounts without public state have no full state to fetch; only a commitment is on-chain.
242 if !account_id.is_public() {
243 return Ok(None);
244 }
245
246 // A single request fetches the full public state: every storage map's entries plus the
247 // vault, with the storage layout discovered server-side.
248 let (block_number, mut proof) = self
249 .get_account(
250 account_id,
251 GetAccountRequest::new()
252 .with_storage(StorageMapFetch::All)
253 .with_vault(VaultFetch::Always),
254 )
255 .await?;
256
257 if let Some(details) = proof.details_mut() {
258 self.resolve_oversize_vault(account_id, block_number, details).await?;
259 self.resolve_oversize_storage_maps(account_id, block_number, details).await?;
260 }
261
262 let details = proof.into_details().ok_or(RpcError::ExpectedDataMissing(
263 "public account returned without details".into(),
264 ))?;
265
266 Ok(Some(Account::try_from(&details)?))
267 }
268
269 /// Fetches notes related to the specified tags using the `/SyncNotes` RPC endpoint,
270 /// paginating over the full block range and returning, in block-number order, every block in
271 /// that range that contains at least one note matching the requested tags.
272 ///
273 /// - `block_from`: The starting block number for the range (inclusive).
274 /// - `block_to`: The ending block number for the range (inclusive).
275 /// - `note_tags` is the set of tags used to filter the notes the client is interested in.
276 ///
277 /// Notes with attachments will have header-only metadata after this call; use
278 /// [`NodeRpcClient::sync_notes_with_details`] to also resolve their full metadata and
279 /// fetch public note bodies in a single follow-up call.
280 ///
281 /// Implementations must verify that every returned note's tag was present in `note_tags` and
282 /// return [`RpcError::InvalidResponse`] otherwise.
283 ///
284 /// # Errors
285 /// - [`RpcError::InvalidResponse`] if the node returns a note whose tag was not requested.
286 async fn sync_notes(
287 &self,
288 block_from: BlockNumber,
289 block_to: BlockNumber,
290 note_tags: &BTreeSet<NoteTag>,
291 ) -> Result<Vec<NoteSyncBlock>, RpcError>;
292
293 /// Calls [`NodeRpcClient::sync_notes`] for the requested range, then makes a single
294 /// [`NodeRpcClient::get_notes_by_id`] call to fetch full note bodies (scripts, assets,
295 /// recipient) for public notes and attachment content for private notes that carry
296 /// attachments.
297 ///
298 /// All public notes in the range are fetched (not just the ones the client tracks) to
299 /// avoid revealing which specific notes the client is interested in. Private notes are only
300 /// fetched when their synced metadata indicates non-empty attachments, since the sync record
301 /// carries attachment scheme markers but not the attachment content, which is needed to
302 /// reconstruct the note's ID.
303 ///
304 /// Returns the resolved note blocks paired with a map of the fetched content (public note
305 /// bodies and private-note attachments), keyed by note ID.
306 async fn sync_notes_with_details(
307 &self,
308 block_from: BlockNumber,
309 block_to: BlockNumber,
310 note_tags: &BTreeSet<NoteTag>,
311 ) -> Result<(Vec<NoteSyncBlock>, BTreeMap<NoteId, SyncedNoteDetails>), RpcError> {
312 let blocks = self.sync_notes(block_from, block_to, note_tags).await?;
313
314 let note_ids: Vec<NoteId> = blocks
315 .iter()
316 .flat_map(|b| b.notes.values())
317 .filter(|n| n.note_type() == NoteType::Public || metadata_has_attachments(n.metadata()))
318 .map(|n| *n.note_id())
319 .collect();
320
321 let mut synced_notes: BTreeMap<NoteId, SyncedNoteDetails> = BTreeMap::new();
322
323 if !note_ids.is_empty() {
324 let fetched = self.get_notes_by_id(¬e_ids).await?;
325
326 for fetched_note in fetched {
327 match fetched_note {
328 FetchedNote::Public(note, _) => {
329 synced_notes.insert(note.id(), SyncedNoteDetails::Public(note));
330 },
331 FetchedNote::Private(note_id, _, attachments, _) => {
332 let attachments = (!attachments.is_empty()).then_some(attachments);
333 synced_notes.insert(note_id, SyncedNoteDetails::Private(attachments));
334 },
335 }
336 }
337 }
338
339 Ok((blocks, synced_notes))
340 }
341
342 /// Fetches the nullifiers corresponding to a list of prefixes using the
343 /// `/SyncNullifiers` RPC endpoint.
344 ///
345 /// - `prefix` is a list of nullifiers prefixes to search for.
346 /// - `block_from`: The starting block number for the range (inclusive).
347 /// - `block_to`: The ending block number for the range (inclusive).
348 ///
349 /// Implementations must verify that every returned nullifier's prefix was present in `prefix`
350 /// and return [`RpcError::InvalidResponse`] otherwise.
351 ///
352 /// # Errors
353 /// - [`RpcError::InvalidResponse`] if the node returns a nullifier whose prefix was not
354 /// requested.
355 async fn sync_nullifiers(
356 &self,
357 prefix: &[u16],
358 block_from: BlockNumber,
359 block_to: BlockNumber,
360 ) -> Result<Vec<NullifierUpdate>, RpcError>;
361
362 /// Fetches the account from the node, using the `/GetAccount` endpoint.
363 ///
364 /// The response carries an
365 /// [`AccountWitness`](miden_protocol::block::account_tree::AccountWitness) and the target
366 /// block. Public accounts additionally get [`AccountDetails`]; for private accounts the
367 /// other `request` fields are ignored.
368 ///
369 /// For a fully oversize-resolved account, use [`NodeRpcClient::get_account_details`].
370 ///
371 /// Errors if the account isn't found or the block number of the requested account doesn't match
372 /// # Errors
373 ///
374 /// - If the account isn't found in the network
375 /// - If the response block number does not match the requested one
376 async fn get_account(
377 &self,
378 account_id: AccountId,
379 request: GetAccountRequest,
380 ) -> Result<(BlockNumber, AccountProof), RpcError>;
381
382 /// Fills in the asset list when the vault came back flagged `too_many_assets`, by
383 /// querying [`NodeRpcClient::sync_account_vault`] over `[GENESIS, block_to]`. No-op when
384 /// the flag isn't set.
385 async fn resolve_oversize_vault(
386 &self,
387 account_id: AccountId,
388 block_to: BlockNumber,
389 details: &mut AccountDetails,
390 ) -> Result<(), RpcError> {
391 if !details.vault_details.too_many_assets {
392 return Ok(());
393 }
394 let vault_info =
395 self.sync_account_vault(BlockNumber::GENESIS, block_to, account_id).await?;
396 // Syncing from genesis merges the full vault history into an absolute patch, so its
397 // updated (non-removed) assets are the account's current vault contents.
398 details.vault_details.assets = vault_info.vault_patch.updated_assets().collect();
399 details.vault_details.too_many_assets = false;
400 Ok(())
401 }
402
403 /// Fills in the entries of any storage map flagged `too_many_entries`, by querying
404 /// [`NodeRpcClient::sync_storage_maps`] over `[GENESIS, block_to]`. No-op when no map
405 /// has the flag set.
406 async fn resolve_oversize_storage_maps(
407 &self,
408 account_id: AccountId,
409 block_to: BlockNumber,
410 details: &mut AccountDetails,
411 ) -> Result<(), RpcError> {
412 if !details.storage_details.map_details.iter().any(|m| m.too_many_entries) {
413 return Ok(());
414 }
415 let info = self.sync_storage_maps(BlockNumber::GENESIS, block_to, account_id).await?;
416 for map_details in &mut details.storage_details.map_details {
417 if !map_details.too_many_entries {
418 continue;
419 }
420 // Syncing from genesis merges the full history of each slot into its absolute
421 // current entries, so the result is the complete map content.
422 let entries: Vec<StorageMapEntry> = info
423 .map_entries
424 .get(&map_details.slot_name)
425 .map(|entries| {
426 entries
427 .as_map()
428 .iter()
429 .map(|(key, value)| StorageMapEntry { key: *key, value: *value })
430 .collect()
431 })
432 .unwrap_or_default();
433 map_details.too_many_entries = false;
434 map_details.entries = StorageMapEntries::AllEntries(entries);
435 }
436 Ok(())
437 }
438
439 /// Fetches the commit height where the nullifier was consumed. If the nullifier isn't found,
440 /// then `None` is returned.
441 /// The `block_num` parameter is the block number to start the search from (inclusive).
442 ///
443 /// The default implementation of this method makes two RPC requests: one to
444 /// [`NodeRpcClient::get_block_header_by_number`] to resolve the chain tip, and one to
445 /// [`NodeRpcClient::sync_nullifiers`] to search up to that tip.
446 async fn get_nullifier_commit_heights(
447 &self,
448 requested_nullifiers: BTreeSet<Nullifier>,
449 block_from: BlockNumber,
450 ) -> Result<BTreeMap<Nullifier, Option<BlockNumber>>, RpcError> {
451 let prefixes: Vec<u16> =
452 requested_nullifiers.iter().map(crate::note::Nullifier::prefix).collect();
453 let (chain_tip, _) = self.get_block_header_by_number(None, false).await?;
454 let retrieved_nullifiers =
455 self.sync_nullifiers(&prefixes, block_from, chain_tip.block_num()).await?;
456
457 let mut nullifiers_height = BTreeMap::new();
458 for nullifier in requested_nullifiers {
459 if let Some(update) =
460 retrieved_nullifiers.iter().find(|update| update.nullifier == nullifier)
461 {
462 nullifiers_height.insert(nullifier, Some(update.block_num));
463 } else {
464 nullifiers_height.insert(nullifier, None);
465 }
466 }
467
468 Ok(nullifiers_height)
469 }
470
471 /// Fetches public note-related data for a list of [`NoteId`] and builds [`InputNoteRecord`]s
472 /// with it. If a note is not found or it's private, it is ignored and will not be included
473 /// in the returned list.
474 ///
475 /// The default implementation of this method uses [`NodeRpcClient::get_notes_by_id`].
476 async fn get_public_note_records(
477 &self,
478 note_ids: &[NoteId],
479 current_timestamp: Option<u64>,
480 ) -> Result<Vec<InputNoteRecord>, RpcError> {
481 if note_ids.is_empty() {
482 return Ok(vec![]);
483 }
484
485 let mut public_notes = Vec::with_capacity(note_ids.len());
486 let note_details = self.get_notes_by_id(note_ids).await?;
487
488 for detail in note_details {
489 if let FetchedNote::Public(note, inclusion_proof) = detail {
490 let state = UnverifiedNoteState {
491 metadata: *note.metadata(),
492 inclusion_proof,
493 }
494 .into();
495 let attachments = note.attachments().clone();
496 let note = InputNoteRecord::new(note.into(), attachments, current_timestamp, state);
497
498 public_notes.push(note);
499 }
500 }
501
502 Ok(public_notes)
503 }
504
505 /// Given a block number, fetches the block header corresponding to that height from the node
506 /// along with the MMR proof.
507 ///
508 /// The default implementation of this method uses
509 /// [`NodeRpcClient::get_block_header_by_number`].
510 async fn get_block_header_with_proof(
511 &self,
512 block_num: BlockNumber,
513 ) -> Result<(BlockHeader, MmrProof), RpcError> {
514 let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?;
515 Ok((header, proof.ok_or(RpcError::ExpectedDataMissing(String::from("MmrProof")))?))
516 }
517
518 /// Fetches the note with the specified ID.
519 ///
520 /// The default implementation of this method uses [`NodeRpcClient::get_notes_by_id`].
521 ///
522 /// Errors:
523 /// - [`RpcError::NoteNotFound`] if the note with the specified ID is not found.
524 async fn get_note_by_id(&self, note_id: NoteId) -> Result<FetchedNote, RpcError> {
525 let notes = self.get_notes_by_id(&[note_id]).await?;
526 notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id))
527 }
528
529 /// Fetches the note script with the specified root, returning `None` if the node has no script
530 /// registered for that root.
531 ///
532 /// Implementations must verify that a returned script's root matches the requested `root` and
533 /// return [`RpcError::InvalidResponse`] otherwise; callers may rely on this invariant.
534 ///
535 /// Errors:
536 /// - [`RpcError::InvalidResponse`] if the node returns a script whose root does not match the
537 /// requested `root`.
538 async fn get_note_script_by_root(&self, root: Word) -> Result<Option<NoteScript>, RpcError>;
539
540 /// Fetches storage map updates for specified account and storage slots within a block range,
541 /// using the `/SyncStorageMaps` RPC endpoint.
542 ///
543 /// - `block_from`: The starting block number for the range (inclusive).
544 /// - `block_to`: The ending block number for the range (inclusive). The node rejects values
545 /// greater than the chain tip.
546 /// - `account_id`: The account ID for which to fetch storage map updates.
547 async fn sync_storage_maps(
548 &self,
549 block_from: BlockNumber,
550 block_to: BlockNumber,
551 account_id: AccountId,
552 ) -> Result<StorageMapInfo, RpcError>;
553
554 /// Fetches account vault updates for specified account within a block range,
555 /// using the `/SyncAccountVault` RPC endpoint.
556 ///
557 /// - `block_from`: The starting block number for the range (inclusive).
558 /// - `block_to`: The ending block number for the range (inclusive). The node rejects values
559 /// greater than the chain tip.
560 /// - `account_id`: The account ID for which to fetch storage map updates.
561 async fn sync_account_vault(
562 &self,
563 block_from: BlockNumber,
564 block_to: BlockNumber,
565 account_id: AccountId,
566 ) -> Result<AccountVaultInfo, RpcError>;
567
568 /// Fetches transaction records for specific accounts within a block range using the
569 /// `/SyncTransactions` RPC endpoint.
570 ///
571 /// - `block_from`: The starting block number for the range (inclusive).
572 /// - `block_to`: The ending block number for the range (inclusive).
573 /// - `account_ids`: The account IDs for which to fetch transactions.
574 async fn sync_transactions(
575 &self,
576 block_from: BlockNumber,
577 block_to: BlockNumber,
578 account_ids: Vec<AccountId>,
579 ) -> Result<Vec<TransactionRecord>, RpcError>;
580
581 /// Fetches the network ID of the node.
582 /// Errors:
583 /// - [`RpcError::ExpectedDataMissing`] if the note with the specified root is not found.
584 async fn get_network_id(&self) -> Result<NetworkId, RpcError>;
585
586 /// Fetches the RPC limits configured on the node.
587 ///
588 /// Implementations may cache the result internally to avoid repeated network calls.
589 async fn get_rpc_limits(&self) -> Result<RpcLimits, RpcError>;
590
591 /// Returns the RPC limits if they have been set, without fetching from the node.
592 fn has_rpc_limits(&self) -> Option<RpcLimits>;
593
594 /// Sets the RPC limits internally to be used by the client.
595 async fn set_rpc_limits(&self, limits: RpcLimits);
596
597 /// Fetches the RPC status without requiring Accept header validation.
598 ///
599 /// This is useful for diagnostics when version negotiation fails, as it allows
600 /// retrieving node information even when there's a version mismatch.
601 async fn get_status_unversioned(&self) -> Result<RpcStatusInfo, RpcError>;
602
603 /// Fetches the status of a specific network note ID.
604 ///
605 /// This is useful for debugging when a network note fails.
606 async fn get_network_note_status(
607 &self,
608 note_id: NoteId,
609 ) -> Result<NetworkNoteStatusInfo, RpcError>;
610}
611
612// RPC API ENDPOINT
613// ================================================================================================
614//
615/// RPC methods for the Miden protocol.
616#[derive(Debug, Clone, Copy)]
617pub enum RpcEndpoint {
618 Status,
619 SyncNullifiers,
620 GetAccount,
621 GetBlockByNumber,
622 GetBlockHeaderByNumber,
623 GetNotesById,
624 SyncChainMmr,
625 SubmitProvenTx,
626 SubmitProvenBatch,
627 SyncNotes,
628 GetNoteScriptByRoot,
629 SyncStorageMaps,
630 SyncAccountVault,
631 SyncTransactions,
632 GetLimits,
633 GetNetworkNoteStatus,
634}
635
636impl RpcEndpoint {
637 /// Returns the endpoint name as used in the RPC service definition.
638 pub fn proto_name(&self) -> &'static str {
639 match self {
640 RpcEndpoint::Status => "Status",
641 RpcEndpoint::SyncNullifiers => "SyncNullifiers",
642 RpcEndpoint::GetAccount => "GetAccount",
643 RpcEndpoint::GetBlockByNumber => "GetBlockByNumber",
644 RpcEndpoint::GetBlockHeaderByNumber => "GetBlockHeaderByNumber",
645 RpcEndpoint::GetNotesById => "GetNotesById",
646 RpcEndpoint::SyncChainMmr => "SyncChainMmr",
647 RpcEndpoint::SubmitProvenTx => "SubmitProvenTransaction",
648 RpcEndpoint::SubmitProvenBatch => "SubmitProvenBatch",
649 RpcEndpoint::SyncNotes => "SyncNotes",
650 RpcEndpoint::GetNoteScriptByRoot => "GetNoteScriptByRoot",
651 RpcEndpoint::SyncStorageMaps => "SyncStorageMaps",
652 RpcEndpoint::SyncAccountVault => "SyncAccountVault",
653 RpcEndpoint::SyncTransactions => "SyncTransactions",
654 RpcEndpoint::GetLimits => "GetLimits",
655 RpcEndpoint::GetNetworkNoteStatus => "GetNetworkNoteStatus",
656 }
657 }
658}
659
660impl fmt::Display for RpcEndpoint {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 match self {
663 RpcEndpoint::Status => write!(f, "status"),
664 RpcEndpoint::SyncNullifiers => {
665 write!(f, "sync_nullifiers")
666 },
667 RpcEndpoint::GetAccount => write!(f, "get_account"),
668 RpcEndpoint::GetBlockByNumber => write!(f, "get_block_by_number"),
669 RpcEndpoint::GetBlockHeaderByNumber => {
670 write!(f, "get_block_header_by_number")
671 },
672 RpcEndpoint::GetNotesById => write!(f, "get_notes_by_id"),
673 RpcEndpoint::SyncChainMmr => write!(f, "sync_chain_mmr"),
674 RpcEndpoint::SubmitProvenTx => write!(f, "submit_proven_transaction"),
675 RpcEndpoint::SubmitProvenBatch => write!(f, "submit_proven_batch"),
676 RpcEndpoint::SyncNotes => write!(f, "sync_notes"),
677 RpcEndpoint::GetNoteScriptByRoot => write!(f, "get_note_script_by_root"),
678 RpcEndpoint::SyncStorageMaps => write!(f, "sync_storage_maps"),
679 RpcEndpoint::SyncAccountVault => write!(f, "sync_account_vault"),
680 RpcEndpoint::SyncTransactions => write!(f, "sync_transactions"),
681 RpcEndpoint::GetLimits => write!(f, "get_limits"),
682 RpcEndpoint::GetNetworkNoteStatus => write!(f, "get_network_note_status"),
683 }
684 }
685}