sdk/signing_client/builder.rs
1use std::sync::{Arc, Mutex};
2
3use super::SigningClient;
4use crate::{client::Client, crypto::Signer};
5
6/// Where a signing client sends transactions: a URL to build a fresh [`Client`]
7/// from, or an existing one to reuse.
8enum Endpoint {
9 Url(String),
10 Client(Client),
11}
12
13/// Builds a [`SigningClient`] whose chain id is optional.
14///
15/// Omit [`chain_id`](Self::chain_id) and the client reads it from the endpoint
16/// itself on first submission, then caches it. Supply it and no lookup ever
17/// happens.
18///
19/// # Examples
20///
21/// ```rust,no_run
22/// use sdk::{Client, SigningClient, crypto::Signer};
23///
24/// # fn example(signer: Signer) -> anyhow::Result<()> {
25/// // chain id read from the endpoint
26/// let client = SigningClient::builder(signer.clone())
27/// .url("http://localhost:1789")
28/// .build()?;
29///
30/// // chain id supplied — no round-trip
31/// let client = SigningClient::builder(signer.clone())
32/// .url("http://localhost:1789")
33/// .chain_id("real-t-0001")
34/// .build()?;
35///
36/// // many signers over one connection pool, sharing its chain-id lookup
37/// let shared = Client::new("http://localhost:1789")?;
38/// let client = SigningClient::builder(signer).client(shared).build()?;
39/// # Ok(())
40/// # }
41/// ```
42pub struct SigningClientBuilder {
43 signer: Signer,
44 endpoint: Option<Endpoint>,
45 chain_id: Option<String>,
46}
47
48impl SigningClientBuilder {
49 pub(super) fn new(signer: Signer) -> SigningClientBuilder {
50 return SigningClientBuilder {
51 signer,
52 endpoint: None,
53 chain_id: None,
54 };
55 }
56
57 /// Endpoint to trade against, replacing any endpoint already set.
58 pub fn url(mut self, url: impl Into<String>) -> SigningClientBuilder {
59 self.endpoint = Some(Endpoint::Url(url.into()));
60 return self;
61 }
62
63 /// Reuses an existing [`Client`], sharing its connection pool and its
64 /// chain-id cache. Prefer this when building many signers against one
65 /// endpoint — it avoids connection storms and repeated chain-id lookups.
66 pub fn client(mut self, client: Client) -> SigningClientBuilder {
67 self.endpoint = Some(Endpoint::Client(client));
68 return self;
69 }
70
71 /// Chain id to sign with. Omit to read it from the endpoint instead.
72 pub fn chain_id(mut self, chain_id: impl Into<String>) -> SigningClientBuilder {
73 self.chain_id = Some(chain_id.into());
74 return self;
75 }
76
77 /// # Errors
78 ///
79 /// Fails if no endpoint was set, or if the URL set is not a valid one.
80 pub fn build(self) -> anyhow::Result<SigningClient> {
81 let http_client = match self.endpoint {
82 Some(Endpoint::Client(client)) => client,
83 Some(Endpoint::Url(url)) => Client::new(url)?,
84 None => anyhow::bail!("signing client requires either `url` or `client`"),
85 };
86
87 return Ok(assemble(http_client, self.signer, self.chain_id));
88 }
89}
90
91/// The one place a [`SigningClient`] is assembled. `from_client` is infallible
92/// and so cannot route through [`SigningClientBuilder::build`]; both land here.
93pub(super) fn assemble(
94 http_client: Client,
95 signer: Signer,
96 chain_id: Option<String>,
97) -> SigningClient {
98 return SigningClient {
99 http_client,
100 signer,
101 chain_id,
102 nonce: Arc::new(Mutex::new(SigningClient::now())),
103 };
104}