miden_client/builder.rs
1use alloc::boxed::Box;
2use alloc::sync::Arc;
3use alloc::vec;
4use alloc::vec::Vec;
5
6use miden_protocol::assembly::{DefaultSourceManager, SourceManagerSync};
7use miden_protocol::block::BlockNumber;
8use miden_protocol::{MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
9use miden_tx::auth::TransactionAuthenticator;
10use miden_tx::{ExecutionOptions, LocalTransactionProver};
11use rand::SeedableRng;
12use rand_chacha::ChaCha20Rng;
13
14#[cfg(any(feature = "tonic", feature = "std"))]
15use crate::alloc::string::ToString;
16#[cfg(feature = "std")]
17use crate::keystore::FilesystemKeyStore;
18use crate::note_transport::NoteTransportClient;
19use crate::pswap::PswapTransactionObserver;
20use crate::rpc::{Endpoint, NodeRpcClient};
21#[cfg(feature = "tonic")]
22use crate::rpc::{GrpcClient, VerifyingRpcClient};
23use crate::store::{Store, StoreError};
24use crate::transaction::{TransactionObserver, TransactionProver};
25use crate::{Client, ClientError, ClientRng, ClientRngBox, grpc_support};
26
27// CONSTANTS
28// ================================================================================================
29
30/// The default number of blocks after which pending transactions are considered stale and
31/// discarded.
32const TX_DISCARD_DELTA: u32 = 20;
33/// The default number of synced blocks between automatic irrelevant-block pruning runs.
34const IRRELEVANT_BLOCK_PRUNE_INTERVAL: u32 = 1;
35/// Whether the client should cache the current Partial MMR in memory by default.
36const CACHE_PARTIAL_MMR_IN_MEMORY: bool = false;
37
38pub use grpc_support::*;
39
40// STORE BUILDER
41// ================================================================================================
42
43/// Allows the [`ClientBuilder`] to accept either an already built store instance or a factory for
44/// deferring the store instantiation.
45pub enum StoreBuilder {
46 Store(Arc<dyn Store>),
47 Factory(Box<dyn StoreFactory>),
48}
49
50/// Trait for building a store instance.
51#[async_trait::async_trait]
52pub trait StoreFactory {
53 /// Returns a new store instance.
54 async fn build(&self) -> Result<Arc<dyn Store>, StoreError>;
55}
56
57// CLIENT BUILDER
58// ================================================================================================
59
60/// A builder for constructing a Miden client.
61///
62/// This builder allows you to configure the various components required by the client, such as the
63/// RPC endpoint, store, RNG, and authenticator. It is generic over the authenticator type.
64///
65/// ## Network-Aware Constructors
66///
67/// Use one of the network-specific constructors to get sensible defaults for a specific network:
68/// - [`for_mainnet()`](Self::for_mainnet) - Pre-configured for Miden mainnet
69/// - [`for_testnet()`](Self::for_testnet) - Pre-configured for Miden testnet
70/// - [`for_devnet()`](Self::for_devnet) - Pre-configured for Miden devnet
71/// - [`for_localhost()`](Self::for_localhost) - Pre-configured for local development
72///
73/// The builder provides defaults for:
74/// - **RPC endpoint**: Automatically configured based on the network
75/// - **Transaction prover**: Remote for mainnet/testnet/devnet, local for localhost
76/// - **RNG**: Random seed-based prover randomness
77///
78/// ## Components
79///
80/// The client requires several components to function:
81///
82/// - **RPC client** ([`NodeRpcClient`]): Provides connectivity to the Miden node for submitting
83/// transactions, syncing state, and fetching account/note data. Configure via
84/// [`rpc()`](Self::rpc) or [`grpc_client()`](Self::grpc_client).
85///
86/// - **Store** ([`Store`]): Provides persistence for accounts, notes, and transaction history.
87/// Configure via [`store()`](Self::store).
88///
89/// - **RNG** ([`ClientCryptoRng`](crate::ClientCryptoRng)): Provides randomness for generating
90/// keys, serial numbers, and other cryptographic operations. It is always created from a random
91/// seed, so that a caller cannot make the keys it generates predictable. Under the `testing`
92/// feature it can be overridden with `rng()`.
93///
94/// - **Authenticator** ([`TransactionAuthenticator`]): Handles transaction signing when signatures
95/// are requested from within the VM. Configure via [`authenticator()`](Self::authenticator).
96///
97/// - **Transaction prover** ([`TransactionProver`]): Generates proofs for transactions. Defaults to
98/// a local prover if not specified. Configure via [`prover()`](Self::prover).
99///
100/// - **Note transport** ([`NoteTransportClient`]): Optional component for exchanging private notes
101/// through the Miden note transport network. Configure via
102/// [`note_transport()`](Self::note_transport).
103///
104/// - **Transaction discard delta**: Number of blocks after which pending transactions are
105/// considered stale and discarded. Configure via [`tx_discard_delta()`](Self::tx_discard_delta).
106///
107/// - **In-memory Partial MMR cache**: Reuses the current partial blockchain MMR instead of
108/// rebuilding it from store. Disabled by default. Configure via
109/// [`cache_partial_mmr_in_memory()`](Self::cache_partial_mmr_in_memory).
110///
111/// - **Max block number delta**: Maximum number of blocks the client can be behind the network for
112/// transactions and account proofs to be considered valid. Configure via
113/// [`max_block_number_delta()`](Self::max_block_number_delta).
114pub struct ClientBuilder<AUTH> {
115 /// An optional custom RPC client. If provided, this takes precedence over `rpc_endpoint`.
116 rpc_api: Option<Arc<dyn NodeRpcClient>>,
117 /// An optional store provided by the user.
118 pub store: Option<StoreBuilder>,
119 /// An optional RNG provided by the user. Only settable under the `testing` feature.
120 rng: Option<ClientRngBox>,
121 /// The authenticator provided by the user.
122 authenticator: Option<Arc<AUTH>>,
123 /// Number of blocks after which pending transactions are considered stale and discarded. If
124 /// `None`, there is no limit and transactions will be kept indefinitely.
125 tx_discard_delta: Option<u32>,
126 /// Number of synced blocks between automatic pruning runs for irrelevant block data. If `None`,
127 /// automatic irrelevant-block pruning is disabled.
128 irrelevant_block_prune_interval: Option<u32>,
129 /// Whether the current Partial MMR should be cached in memory between sync-related operations.
130 cache_partial_mmr_in_memory: bool,
131 /// Maximum number of blocks the client can be behind the network for transactions and account
132 /// proofs to be considered valid.
133 max_block_number_delta: Option<u32>,
134 /// An optional custom note transport client.
135 note_transport_api: Option<Arc<dyn NoteTransportClient>>,
136 /// Configuration for lazy note transport initialization (used by network constructors).
137 #[allow(unused)]
138 note_transport_config: Option<NoteTransportConfig>,
139 /// An optional custom transaction prover.
140 tx_prover: Option<Arc<dyn TransactionProver + Send + Sync>>,
141 /// The endpoint used by the builder for network configuration.
142 endpoint: Option<Endpoint>,
143 /// An optional shared source manager for MASM source information.
144 source_manager: Option<Arc<dyn SourceManagerSync>>,
145}
146
147impl<AUTH> Default for ClientBuilder<AUTH> {
148 fn default() -> Self {
149 Self {
150 rpc_api: None,
151 store: None,
152 rng: None,
153 authenticator: None,
154 tx_discard_delta: Some(TX_DISCARD_DELTA),
155 irrelevant_block_prune_interval: Some(IRRELEVANT_BLOCK_PRUNE_INTERVAL),
156 cache_partial_mmr_in_memory: CACHE_PARTIAL_MMR_IN_MEMORY,
157 max_block_number_delta: None,
158 note_transport_api: None,
159 note_transport_config: None,
160 tx_prover: None,
161 endpoint: None,
162 source_manager: None,
163 }
164 }
165}
166
167/// Network-specific constructors for [`ClientBuilder`].
168///
169/// These constructors automatically configure the builder for a specific network, including RPC
170/// endpoint, transaction prover, and note transport (where applicable).
171#[cfg(feature = "tonic")]
172impl<AUTH> ClientBuilder<AUTH>
173where
174 AUTH: BuilderAuthenticator,
175{
176 /// Creates a `ClientBuilder` pre-configured for Miden mainnet.
177 ///
178 /// This automatically configures:
179 /// - **RPC**: [`Endpoint::mainnet()`]
180 /// - **Prover**: Remote prover at [`MAINNET_PROVER_ENDPOINT`]
181 /// - **Note transport**:
182 /// [`NOTE_TRANSPORT_MAINNET_ENDPOINT`](crate::note_transport::NOTE_TRANSPORT_MAINNET_ENDPOINT)
183 ///
184 /// You still need to provide:
185 /// - A store (via `.store()`)
186 /// - An authenticator (via `.authenticator()`)
187 ///
188 /// All defaults can be overridden by calling the corresponding builder methods after
189 /// `for_mainnet()`.
190 ///
191 /// # Example
192 ///
193 /// ```ignore
194 /// let client = ClientBuilder::for_mainnet()
195 /// .store(store)
196 /// .authenticator(Arc::new(keystore))
197 /// .build()
198 /// .await?;
199 /// ```
200 #[must_use]
201 pub fn for_mainnet() -> Self {
202 let endpoint = Endpoint::mainnet();
203 Self {
204 rpc_api: Some(Arc::new(VerifyingRpcClient::new(GrpcClient::new(
205 &endpoint,
206 DEFAULT_GRPC_TIMEOUT_MS,
207 )))),
208 tx_prover: Some(Arc::new(RemoteTransactionProver::new(
209 MAINNET_PROVER_ENDPOINT.to_string(),
210 ))),
211 note_transport_config: Some(NoteTransportConfig {
212 endpoint: crate::note_transport::NOTE_TRANSPORT_MAINNET_ENDPOINT.to_string(),
213 timeout_ms: DEFAULT_GRPC_TIMEOUT_MS,
214 }),
215 endpoint: Some(endpoint),
216 ..Self::default()
217 }
218 }
219
220 /// Creates a `ClientBuilder` pre-configured for Miden testnet.
221 ///
222 /// This automatically configures:
223 /// - **RPC**: [`Endpoint::testnet()`]
224 /// - **Prover**: Remote prover at [`TESTNET_PROVER_ENDPOINT`]
225 /// - **Note transport**:
226 /// [`NOTE_TRANSPORT_TESTNET_ENDPOINT`](crate::note_transport::NOTE_TRANSPORT_TESTNET_ENDPOINT)
227 ///
228 /// You still need to provide:
229 /// - A store (via `.store()`)
230 /// - An authenticator (via `.authenticator()`)
231 ///
232 /// All defaults can be overridden by calling the corresponding builder methods after
233 /// `for_testnet()`.
234 ///
235 /// # Example
236 ///
237 /// ```ignore
238 /// let client = ClientBuilder::for_testnet()
239 /// .store(store)
240 /// .authenticator(Arc::new(keystore))
241 /// .build()
242 /// .await?;
243 /// ```
244 #[must_use]
245 pub fn for_testnet() -> Self {
246 let endpoint = Endpoint::testnet();
247 Self {
248 rpc_api: Some(Arc::new(VerifyingRpcClient::new(GrpcClient::new(
249 &endpoint,
250 DEFAULT_GRPC_TIMEOUT_MS,
251 )))),
252 tx_prover: Some(Arc::new(RemoteTransactionProver::new(
253 TESTNET_PROVER_ENDPOINT.to_string(),
254 ))),
255 note_transport_config: Some(NoteTransportConfig {
256 endpoint: crate::note_transport::NOTE_TRANSPORT_TESTNET_ENDPOINT.to_string(),
257 timeout_ms: DEFAULT_GRPC_TIMEOUT_MS,
258 }),
259 endpoint: Some(endpoint),
260 ..Self::default()
261 }
262 }
263
264 /// Creates a `ClientBuilder` pre-configured for Miden devnet.
265 ///
266 /// This automatically configures:
267 /// - **RPC**: [`Endpoint::devnet()`]
268 /// - **Prover**: Remote prover at [`DEVNET_PROVER_ENDPOINT`]
269 /// - **Note transport**:
270 /// [`NOTE_TRANSPORT_DEVNET_ENDPOINT`](crate::note_transport::NOTE_TRANSPORT_DEVNET_ENDPOINT)
271 ///
272 /// You still need to provide:
273 /// - A store (via `.store()`)
274 /// - An authenticator (via `.authenticator()`)
275 ///
276 /// All defaults can be overridden by calling the corresponding builder methods after
277 /// `for_devnet()`.
278 ///
279 /// # Example
280 ///
281 /// ```ignore
282 /// let client = ClientBuilder::for_devnet()
283 /// .store(store)
284 /// .authenticator(Arc::new(keystore))
285 /// .build()
286 /// .await?;
287 /// ```
288 #[must_use]
289 pub fn for_devnet() -> Self {
290 let endpoint = Endpoint::devnet();
291 Self {
292 rpc_api: Some(Arc::new(VerifyingRpcClient::new(GrpcClient::new(
293 &endpoint,
294 DEFAULT_GRPC_TIMEOUT_MS,
295 )))),
296 tx_prover: Some(Arc::new(RemoteTransactionProver::new(
297 DEVNET_PROVER_ENDPOINT.to_string(),
298 ))),
299 note_transport_config: Some(NoteTransportConfig {
300 endpoint: crate::note_transport::NOTE_TRANSPORT_DEVNET_ENDPOINT.to_string(),
301 timeout_ms: DEFAULT_GRPC_TIMEOUT_MS,
302 }),
303 endpoint: Some(endpoint),
304 ..Self::default()
305 }
306 }
307
308 /// Creates a `ClientBuilder` pre-configured for localhost.
309 ///
310 /// This automatically configures:
311 /// - **RPC**: `http://localhost:57291`
312 /// - **Prover**: Local (default)
313 ///
314 /// Note transport is not configured by default for localhost.
315 ///
316 /// You still need to provide:
317 /// - A store (via `.store()`)
318 /// - An authenticator (via `.authenticator()`)
319 ///
320 /// All defaults can be overridden by calling the corresponding builder methods after
321 /// `for_localhost()`.
322 ///
323 /// # Example
324 ///
325 /// ```ignore
326 /// let client = ClientBuilder::for_localhost()
327 /// .store(store)
328 /// .authenticator(Arc::new(keystore))
329 /// .build()
330 /// .await?;
331 /// ```
332 #[must_use]
333 pub fn for_localhost() -> Self {
334 let endpoint = Endpoint::localhost();
335 Self {
336 rpc_api: Some(Arc::new(VerifyingRpcClient::new(GrpcClient::new(
337 &endpoint,
338 DEFAULT_GRPC_TIMEOUT_MS,
339 )))),
340 endpoint: Some(endpoint),
341 ..Self::default()
342 }
343 }
344}
345
346impl<AUTH> ClientBuilder<AUTH>
347where
348 AUTH: BuilderAuthenticator,
349{
350 /// Create a new `ClientBuilder` with default settings.
351 #[must_use]
352 pub fn new() -> Self {
353 Self::default()
354 }
355
356 /// Sets a custom RPC client directly.
357 ///
358 /// The client is used as provided: wrap it in [`VerifyingRpcClient`] to have node responses
359 /// verified against the requests.
360 #[must_use]
361 pub fn rpc(mut self, client: Arc<dyn NodeRpcClient>) -> Self {
362 self.rpc_api = Some(client);
363 self
364 }
365
366 /// Sets a gRPC client from the endpoint and optional timeout, wrapped in a
367 /// [`VerifyingRpcClient`] so node responses are verified against the requests.
368 #[must_use]
369 #[cfg(feature = "tonic")]
370 pub fn grpc_client(mut self, endpoint: &Endpoint, timeout_ms: Option<u64>) -> Self {
371 self.rpc_api = Some(Arc::new(VerifyingRpcClient::new(GrpcClient::new(
372 endpoint,
373 timeout_ms.unwrap_or(DEFAULT_GRPC_TIMEOUT_MS),
374 ))));
375 self
376 }
377
378 /// Provide a store to be used by the client.
379 #[must_use]
380 pub fn store(mut self, store: Arc<dyn Store>) -> Self {
381 self.store = Some(StoreBuilder::Store(store));
382 self
383 }
384
385 /// Optionally provide a custom RNG.
386 ///
387 /// Restricted to the `testing` feature: the client's RNG generates secret keys and seals
388 /// transaction inputs, so outside of tests its output must not be predictable to a caller.
389 #[cfg(feature = "testing")]
390 #[must_use]
391 pub fn rng(mut self, rng: ClientRngBox) -> Self {
392 self.rng = Some(rng);
393 self
394 }
395
396 /// Optionally provide a custom authenticator instance.
397 #[must_use]
398 pub fn authenticator(mut self, authenticator: Arc<AUTH>) -> Self {
399 self.authenticator = Some(authenticator);
400 self
401 }
402
403 /// Overrides the source manager used to retain MASM source information for assembled programs.
404 ///
405 /// If not set, the client uses a default [`DefaultSourceManager`]. The same instance is
406 /// forwarded to the transaction executor and to every script compiled through the client (e.g.
407 /// via [`Client::code_builder`](crate::Client::code_builder)).
408 ///
409 /// Set this explicitly only when scripts or modules are compiled outside the client (for
410 /// example, using an external [`Assembler`](miden_protocol::assembly::Assembler)): pass the
411 /// same `Arc` used by that external assembler so all source spans resolve correctly at runtime.
412 #[must_use]
413 pub fn source_manager(mut self, sm: Arc<dyn SourceManagerSync>) -> Self {
414 self.source_manager = Some(sm);
415 self
416 }
417
418 /// Optionally set a maximum number of blocks that the client can be behind the network. By
419 /// default, there's no maximum.
420 #[must_use]
421 pub fn max_block_number_delta(mut self, delta: u32) -> Self {
422 self.max_block_number_delta = Some(delta);
423 self
424 }
425
426 /// Sets the number of blocks after which pending transactions are considered stale and
427 /// discarded.
428 ///
429 /// If a transaction has not been included in a block within this many blocks after submission,
430 /// it will be discarded. If `None`, transactions will be kept indefinitely.
431 ///
432 /// By default, the delta is set to `TX_DISCARD_DELTA` (20 blocks).
433 #[must_use]
434 pub fn tx_discard_delta(mut self, delta: Option<u32>) -> Self {
435 self.tx_discard_delta = delta;
436 self
437 }
438
439 /// Sets the number of synced blocks between automatic irrelevant-block pruning runs.
440 ///
441 /// Values defer pruning until the client has advanced by at least that many sync blocks since
442 /// the last prune. `None` disables automatic pruning entirely.
443 #[must_use]
444 pub fn irrelevant_block_prune_interval(mut self, interval: Option<u32>) -> Self {
445 self.irrelevant_block_prune_interval = interval;
446 self
447 }
448
449 /// Enables or disables the in-memory Partial MMR cache.
450 ///
451 /// When enabled, the client reuses the current Partial MMR between sync and pruning operations.
452 /// When disabled, it rebuilds the Partial MMR from the store each time it is needed.
453 #[must_use]
454 pub fn cache_partial_mmr_in_memory(mut self, enabled: bool) -> Self {
455 self.cache_partial_mmr_in_memory = enabled;
456 self
457 }
458
459 /// Sets a custom note transport client directly.
460 #[must_use]
461 pub fn note_transport(mut self, client: Arc<dyn NoteTransportClient>) -> Self {
462 self.note_transport_api = Some(client);
463 self
464 }
465
466 /// Sets a custom transaction prover.
467 #[must_use]
468 pub fn prover(mut self, prover: Arc<dyn TransactionProver + Send + Sync>) -> Self {
469 self.tx_prover = Some(prover);
470 self
471 }
472
473 /// Returns the endpoint configured for this builder, if any.
474 ///
475 /// This is set automatically when using network-specific constructors like
476 /// [`for_mainnet()`](Self::for_mainnet), [`for_testnet()`](Self::for_testnet),
477 /// [`for_devnet()`](Self::for_devnet), or [`for_localhost()`](Self::for_localhost).
478 #[must_use]
479 pub fn endpoint(&self) -> Option<&Endpoint> {
480 self.endpoint.as_ref()
481 }
482
483 /// Build and return the `Client`.
484 ///
485 /// # Errors
486 ///
487 /// - Returns an error if no RPC client was provided.
488 /// - Returns an error if the store cannot be instantiated.
489 #[allow(clippy::unused_async, unused_mut)]
490 pub async fn build(mut self) -> Result<Client<AUTH>, ClientError> {
491 // Determine the RPC client to use.
492 let rpc_api: Arc<dyn NodeRpcClient> = if let Some(client) = self.rpc_api {
493 client
494 } else {
495 return Err(ClientError::ClientInitializationError(
496 "RPC client is required. Call `.rpc(...)` or `.grpc_client(...)`.".into(),
497 ));
498 };
499
500 // Ensure a store was provided.
501 let store = if let Some(store_builder) = self.store {
502 match store_builder {
503 StoreBuilder::Store(store) => store,
504 StoreBuilder::Factory(factory) => factory.build().await?,
505 }
506 } else {
507 return Err(ClientError::ClientInitializationError(
508 "Store must be specified. Call `.store(...)`.".into(),
509 ));
510 };
511
512 // Use the provided RNG, or create a default one.
513 let rng: ClientRngBox = if let Some(user_rng) = self.rng {
514 user_rng
515 } else {
516 Box::new(ChaCha20Rng::from_rng(&mut rand::rng()))
517 };
518
519 let tx_prover: Arc<dyn TransactionProver + Send + Sync> =
520 self.tx_prover.unwrap_or_else(|| Arc::new(LocalTransactionProver::default()));
521
522 let source_manager: Arc<dyn SourceManagerSync> =
523 self.source_manager.unwrap_or_else(|| Arc::new(DefaultSourceManager::default()));
524
525 // Initialize genesis commitment in RPC client
526 if let Some((genesis, _)) = store.get_block_header_by_num(BlockNumber::GENESIS).await? {
527 rpc_api.set_genesis_commitment(genesis.commitment()).await?;
528 }
529
530 // Set the RPC client with persisted limits if available. If not present, they will be
531 // fetched from the node during sync_state.
532 if let Some(limits) = store.get_rpc_limits().await? {
533 rpc_api.set_rpc_limits(limits).await;
534 }
535
536 // Initialize note transport: prefer explicit client, fall back to config (tonic only)
537 #[cfg(feature = "tonic")]
538 if self.note_transport_api.is_none()
539 && let Some(config) = self.note_transport_config
540 {
541 let transport = crate::note_transport::grpc::GrpcNoteTransportClient::new(
542 config.endpoint,
543 config.timeout_ms,
544 );
545
546 self.note_transport_api = Some(Arc::new(transport) as Arc<dyn NoteTransportClient>);
547 }
548
549 // Built-in transaction observers fired by `apply_transaction`. Additional observers can be
550 // attached via `Client::with_transaction_observer`.
551 let transaction_observers: Vec<Arc<dyn TransactionObserver>> =
552 vec![Arc::new(PswapTransactionObserver::new(store.clone()))];
553
554 // Construct and return the Client
555 let client = Client {
556 store,
557 rng: ClientRng::new(rng),
558 rpc_api,
559 tx_prover,
560 authenticator: self.authenticator,
561 source_manager,
562 exec_options: ExecutionOptions::new(
563 Some(MAX_TX_EXECUTION_CYCLES),
564 MIN_TX_EXECUTION_CYCLES,
565 ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
566 )
567 .expect("Default executor's options should always be valid"),
568 tx_discard_delta: self.tx_discard_delta,
569 irrelevant_block_prune_interval: self.irrelevant_block_prune_interval,
570 last_irrelevant_block_prune_sync_height: None,
571 max_block_number_delta: self.max_block_number_delta,
572 note_transport_api: self.note_transport_api.clone(),
573 cache_partial_mmr_in_memory: self.cache_partial_mmr_in_memory,
574 partial_mmr: None,
575 transaction_observers,
576 };
577 Ok(client)
578 }
579}
580
581// BUILDER AUTHENTICATOR
582// ================================================================================================
583
584/// Marker trait for the authenticator type parameter of [`ClientBuilder`].
585///
586/// The builder stores the authenticator and passes it to the client. The client uses it only to
587/// sign transactions, so any [`TransactionAuthenticator`] with a `'static` lifetime qualifies. Key
588/// management is not required. A signer that holds no secret key, such as a remote signing service,
589/// can be used without implementing [`Keystore`](crate::keystore::Keystore).
590pub trait BuilderAuthenticator: TransactionAuthenticator + 'static {}
591impl<T> BuilderAuthenticator for T where T: TransactionAuthenticator + 'static {}
592
593// FILESYSTEM KEYSTORE CONVENIENCE METHOD
594// ================================================================================================
595
596/// Convenience method for [`ClientBuilder`] when using [`FilesystemKeyStore`] as the authenticator.
597#[cfg(feature = "std")]
598impl ClientBuilder<FilesystemKeyStore> {
599 /// Creates a [`FilesystemKeyStore`] from the given path and sets it as the authenticator.
600 ///
601 /// This is a convenience method that creates the keystore and configures it as the
602 /// authenticator in a single call. The keystore provides transaction signing capabilities using
603 /// keys stored on the filesystem.
604 ///
605 /// # Errors
606 ///
607 /// Returns an error if the keystore cannot be created from the given path.
608 ///
609 /// # Example
610 ///
611 /// ```ignore
612 /// let client = ClientBuilder::new()
613 /// .rpc(rpc_client)
614 /// .store(store)
615 /// .filesystem_keystore("path/to/keys")?
616 /// .build()
617 /// .await?;
618 /// ```
619 pub fn filesystem_keystore(
620 self,
621 keystore_path: impl Into<std::path::PathBuf>,
622 ) -> Result<Self, ClientError> {
623 let keystore = FilesystemKeyStore::new(keystore_path.into())
624 .map_err(|e| ClientError::ClientInitializationError(e.to_string()))?;
625 Ok(self.authenticator(Arc::new(keystore)))
626 }
627}