alloy_provider/fillers/chain_id.rs
1use std::sync::{Arc, OnceLock};
2
3use alloy_network::{Network, TransactionBuilder};
4use alloy_primitives::ChainId;
5use alloy_transport::TransportResult;
6
7use crate::{
8 fillers::{FillerControlFlow, TxFiller},
9 provider::SendableTx,
10};
11
12/// A [`TxFiller`] that populates the chain ID of a transaction.
13///
14/// If a chain ID is provided, it will be used for filling. If a chain ID
15/// is not provided, the filler will attempt to fetch the chain ID from the
16/// provider the first time a transaction is prepared, and will cache it for
17/// future transactions.
18///
19/// Transactions that already have a chain_id set by the user will not be
20/// modified.
21///
22/// # Example
23///
24/// ```
25/// # use alloy_network::{Ethereum};
26/// # use alloy_rpc_types_eth::TransactionRequest;
27/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider};
28/// # use alloy_signer_local::PrivateKeySigner;
29/// # async fn test(url: url::Url) -> Result<(), Box<dyn std::error::Error>> {
30/// let pk: PrivateKeySigner = "0x...".parse()?;
31/// let provider =
32/// ProviderBuilder::<_, _, Ethereum>::default().with_chain_id(1).wallet(pk).on_http(url);
33///
34/// provider.send_transaction(TransactionRequest::default()).await;
35/// # Ok(())
36/// # }
37/// ```
38#[derive(Clone, Debug, Default, PartialEq, Eq)]
39pub struct ChainIdFiller(Arc<OnceLock<ChainId>>);
40
41impl ChainIdFiller {
42 /// Create a new [`ChainIdFiller`] with an optional chain ID.
43 ///
44 /// If a chain ID is provided, it will be used for filling. If a chain ID
45 /// is not provided, the filler will attempt to fetch the chain ID from the
46 /// provider the first time a transaction is prepared.
47 pub fn new(chain_id: Option<ChainId>) -> Self {
48 let lock = OnceLock::new();
49 if let Some(chain_id) = chain_id {
50 lock.set(chain_id).expect("brand new");
51 }
52 Self(Arc::new(lock))
53 }
54}
55
56impl<N: Network> TxFiller<N> for ChainIdFiller {
57 type Fillable = ChainId;
58
59 fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
60 if tx.chain_id().is_some() {
61 FillerControlFlow::Finished
62 } else {
63 FillerControlFlow::Ready
64 }
65 }
66
67 fn fill_sync(&self, tx: &mut SendableTx<N>) {
68 if let Some(chain_id) = self.0.get() {
69 if let Some(builder) = tx.as_mut_builder() {
70 if builder.chain_id().is_none() {
71 builder.set_chain_id(*chain_id)
72 }
73 }
74 }
75 }
76
77 async fn prepare<P>(
78 &self,
79 provider: &P,
80 _tx: &N::TransactionRequest,
81 ) -> TransportResult<Self::Fillable>
82 where
83 P: crate::Provider<N>,
84 {
85 match self.0.get().copied() {
86 Some(chain_id) => Ok(chain_id),
87 None => {
88 let chain_id = provider.get_chain_id().await?;
89 Ok(*self.0.get_or_init(|| chain_id))
90 }
91 }
92 }
93
94 async fn fill(
95 &self,
96 _fillable: Self::Fillable,
97 mut tx: SendableTx<N>,
98 ) -> TransportResult<SendableTx<N>> {
99 self.fill_sync(&mut tx);
100 Ok(tx)
101 }
102}