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}