1use alloc::string::{String, ToString};
2use alloc::sync::Arc;
3use std::boxed::Box;
4
5use miden_protocol::crypto::rand::RpoRandomCoin;
6use miden_protocol::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
7use miden_tx::ExecutionOptions;
8use miden_tx::auth::TransactionAuthenticator;
9use rand::Rng;
10
11use crate::keystore::FilesystemKeyStore;
12use crate::note_transport::NoteTransportClient;
13use crate::rpc::NodeRpcClient;
14use crate::store::{Store, StoreError};
15use crate::transaction::TransactionProver;
16use crate::{Client, ClientError, ClientRngBox, DebugMode};
17
18const TX_GRACEFUL_BLOCKS: u32 = 20;
23
24enum AuthenticatorConfig<AUTH> {
35 Path(String),
36 Instance(Arc<AUTH>),
37}
38
39pub enum StoreBuilder {
45 Store(Arc<dyn Store>),
46 Factory(Box<dyn StoreFactory>),
47}
48
49#[async_trait::async_trait]
51pub trait StoreFactory {
52 async fn build(&self) -> Result<Arc<dyn Store>, StoreError>;
54}
55
56pub struct ClientBuilder<AUTH> {
65 rpc_api: Option<Arc<dyn NodeRpcClient>>,
67 pub store: Option<StoreBuilder>,
69 rng: Option<ClientRngBox>,
71 keystore: Option<AuthenticatorConfig<AUTH>>,
73 in_debug_mode: DebugMode,
75 tx_graceful_blocks: Option<u32>,
78 max_block_number_delta: Option<u32>,
81 note_transport_api: Option<Arc<dyn NoteTransportClient>>,
83 tx_prover: Option<Arc<dyn TransactionProver + Send + Sync>>,
85}
86
87impl<AUTH> Default for ClientBuilder<AUTH> {
88 fn default() -> Self {
89 Self {
90 rpc_api: None,
91 store: None,
92 rng: None,
93 keystore: None,
94 in_debug_mode: DebugMode::Disabled,
95 tx_graceful_blocks: Some(TX_GRACEFUL_BLOCKS),
96 max_block_number_delta: None,
97 note_transport_api: None,
98 tx_prover: None,
99 }
100 }
101}
102
103impl<AUTH> ClientBuilder<AUTH>
104where
105 AUTH: BuilderAuthenticator,
106{
107 #[must_use]
109 pub fn new() -> Self {
110 Self::default()
111 }
112
113 #[must_use]
115 pub fn in_debug_mode(mut self, debug: DebugMode) -> Self {
116 self.in_debug_mode = debug;
117 self
118 }
119
120 #[must_use]
122 pub fn rpc(mut self, client: Arc<dyn NodeRpcClient>) -> Self {
123 self.rpc_api = Some(client);
124 self
125 }
126
127 #[must_use]
129 #[cfg(feature = "tonic")]
130 pub fn grpc_client(mut self, endpoint: &crate::rpc::Endpoint, timeout_ms: Option<u64>) -> Self {
131 self.rpc_api =
132 Some(Arc::new(crate::rpc::GrpcClient::new(endpoint, timeout_ms.unwrap_or(10_000))));
133 self
134 }
135
136 #[must_use]
138 pub fn store(mut self, store: Arc<dyn Store>) -> Self {
139 self.store = Some(StoreBuilder::Store(store));
140 self
141 }
142
143 #[must_use]
145 pub fn rng(mut self, rng: ClientRngBox) -> Self {
146 self.rng = Some(rng);
147 self
148 }
149
150 #[must_use]
152 pub fn authenticator(mut self, authenticator: Arc<AUTH>) -> Self {
153 self.keystore = Some(AuthenticatorConfig::Instance(authenticator));
154 self
155 }
156
157 #[must_use]
160 pub fn max_block_number_delta(mut self, delta: u32) -> Self {
161 self.max_block_number_delta = Some(delta);
162 self
163 }
164
165 #[must_use]
169 pub fn tx_graceful_blocks(mut self, delta: Option<u32>) -> Self {
170 self.tx_graceful_blocks = delta;
171 self
172 }
173
174 #[must_use]
179 pub fn filesystem_keystore(mut self, keystore_path: &str) -> Self {
180 self.keystore = Some(AuthenticatorConfig::Path(keystore_path.to_string()));
181 self
182 }
183
184 #[must_use]
186 pub fn note_transport(mut self, client: Arc<dyn NoteTransportClient>) -> Self {
187 self.note_transport_api = Some(client);
188 self
189 }
190
191 #[must_use]
193 pub fn prover(mut self, prover: Arc<dyn TransactionProver + Send + Sync>) -> Self {
194 self.tx_prover = Some(prover);
195 self
196 }
197
198 #[allow(clippy::unused_async, unused_mut)]
206 pub async fn build(mut self) -> Result<Client<AUTH>, ClientError> {
207 let rpc_api: Arc<dyn NodeRpcClient> = if let Some(client) = self.rpc_api {
209 client
210 } else {
211 return Err(ClientError::ClientInitializationError(
212 "RPC client or endpoint is required. Call `.rpc(...)` or `.tonic_rpc_client(...)`."
213 .into(),
214 ));
215 };
216
217 let store = if let Some(store_builder) = self.store {
219 match store_builder {
220 StoreBuilder::Store(store) => store,
221 StoreBuilder::Factory(factory) => factory.build().await?,
222 }
223 } else {
224 return Err(ClientError::ClientInitializationError(
225 "Store must be specified. Call `.store(...)`.".into(),
226 ));
227 };
228
229 let rng = if let Some(user_rng) = self.rng {
231 user_rng
232 } else {
233 let mut seed_rng = rand::rng();
234 let coin_seed: [u64; 4] = seed_rng.random();
235 Box::new(RpoRandomCoin::new(coin_seed.map(Felt::new).into()))
236 };
237
238 let authenticator = match self.keystore {
240 Some(AuthenticatorConfig::Instance(authenticator)) => Some(authenticator),
241 Some(AuthenticatorConfig::Path(ref path)) => {
242 let keystore = FilesystemKeyStore::new(path.into())
243 .map_err(|err| ClientError::ClientInitializationError(err.to_string()))?;
244 Some(Arc::new(AUTH::from(keystore)))
245 },
246 None => None,
247 };
248
249 Client::new(
250 rpc_api,
251 rng,
252 store,
253 authenticator,
254 ExecutionOptions::new(
255 Some(MAX_TX_EXECUTION_CYCLES),
256 MIN_TX_EXECUTION_CYCLES,
257 false,
258 self.in_debug_mode.into(),
259 )
260 .expect("Default executor's options should always be valid"),
261 self.tx_graceful_blocks,
262 self.max_block_number_delta,
263 self.note_transport_api,
264 self.tx_prover,
265 )
266 .await
267 }
268}
269
270pub trait BuilderAuthenticator:
276 TransactionAuthenticator + From<FilesystemKeyStore> + 'static
277{
278}
279impl<T> BuilderAuthenticator for T where
280 T: TransactionAuthenticator + From<FilesystemKeyStore> + 'static
281{
282}