Skip to main content

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(&note_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}