alloy_provider/fillers/
wallet.rs

1use std::fmt::Debug;
2
3use crate::{provider::SendableTx, Provider};
4use alloy_json_rpc::RpcError;
5use alloy_network::{Network, NetworkWallet, TransactionBuilder};
6use alloy_transport::TransportResult;
7
8use super::{FillerControlFlow, TxFiller};
9
10/// A layer that signs transactions locally.
11///
12/// The layer uses a [`NetworkWallet`] to sign transactions sent using
13/// [`Provider::send_transaction`] locally before passing them to the node with
14/// [`Provider::send_raw_transaction`].
15///
16/// # Example
17///
18/// ```
19/// # use alloy_network::{IntoWallet, EthereumWallet, Ethereum};
20/// # use alloy_rpc_types_eth::TransactionRequest;
21/// # use alloy_signer_local::PrivateKeySigner;
22/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider};
23/// # async fn test(url: url::Url) -> Result<(), Box<dyn std::error::Error>> {
24/// let pk: PrivateKeySigner = "0x...".parse()?;
25/// let provider = ProviderBuilder::new().wallet(pk).on_http(url);
26///
27/// provider.send_transaction(TransactionRequest::default()).await;
28/// # Ok(())
29/// # }
30/// ```
31#[derive(Clone, Debug)]
32pub struct WalletFiller<W> {
33    wallet: W,
34}
35
36impl<W> AsRef<W> for WalletFiller<W> {
37    fn as_ref(&self) -> &W {
38        &self.wallet
39    }
40}
41
42impl<W> AsMut<W> for WalletFiller<W> {
43    fn as_mut(&mut self) -> &mut W {
44        &mut self.wallet
45    }
46}
47
48impl<W> WalletFiller<W> {
49    /// Creates a new wallet layer with the given wallet.
50    pub const fn new(wallet: W) -> Self {
51        Self { wallet }
52    }
53}
54
55impl<W, N> TxFiller<N> for WalletFiller<W>
56where
57    N: Network,
58    W: NetworkWallet<N> + Clone,
59{
60    type Fillable = ();
61
62    fn status(&self, tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
63        if tx.from().is_none() {
64            return FillerControlFlow::Ready;
65        }
66
67        match tx.complete_preferred() {
68            Ok(_) => FillerControlFlow::Ready,
69            Err(e) => FillerControlFlow::Missing(vec![("Wallet", e)]),
70        }
71    }
72
73    fn fill_sync(&self, tx: &mut SendableTx<N>) {
74        if let Some(builder) = tx.as_mut_builder() {
75            if builder.from().is_none() {
76                builder.set_from(self.wallet.default_signer_address());
77            }
78        }
79    }
80
81    async fn prepare<P>(
82        &self,
83        _provider: &P,
84        _tx: &<N as Network>::TransactionRequest,
85    ) -> TransportResult<Self::Fillable>
86    where
87        P: Provider<N>,
88    {
89        Ok(())
90    }
91
92    async fn fill(
93        &self,
94        _fillable: Self::Fillable,
95        tx: SendableTx<N>,
96    ) -> TransportResult<SendableTx<N>> {
97        let builder = match tx {
98            SendableTx::Builder(builder) => builder,
99            _ => return Ok(tx),
100        };
101
102        let envelope = builder.build(&self.wallet).await.map_err(RpcError::local_usage)?;
103
104        Ok(SendableTx::Envelope(envelope))
105    }
106
107    async fn prepare_call(&self, tx: &mut N::TransactionRequest) -> TransportResult<()> {
108        self.prepare_call_sync(tx)?;
109        Ok(())
110    }
111
112    fn prepare_call_sync(
113        &self,
114        tx: &mut <N as Network>::TransactionRequest,
115    ) -> TransportResult<()> {
116        if tx.from().is_none() {
117            tx.set_from(self.wallet.default_signer_address());
118        }
119        Ok(())
120    }
121}
122
123#[cfg(feature = "reqwest")]
124#[cfg(test)]
125mod tests {
126    use crate::{Provider, ProviderBuilder, WalletProvider};
127    use alloy_node_bindings::Anvil;
128    use alloy_primitives::{address, b256, U256};
129    use alloy_rpc_types_eth::TransactionRequest;
130    use alloy_signer_local::PrivateKeySigner;
131
132    #[tokio::test]
133    async fn poc() {
134        let provider = ProviderBuilder::new().on_anvil_with_wallet();
135
136        let tx = TransactionRequest {
137            nonce: Some(0),
138            value: Some(U256::from(100)),
139            to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
140            gas_price: Some(20e9 as u128),
141            gas: Some(21000),
142            ..Default::default()
143        };
144
145        let builder = provider.send_transaction(tx).await.unwrap();
146        let node_hash = *builder.tx_hash();
147        assert_eq!(
148            node_hash,
149            b256!("4b56f1a6bdceb76d1b843e978c70ab88e38aa19f1a67be851b10ce4eec65b7d4")
150        );
151
152        let pending = builder.register().await.unwrap();
153        let local_hash = *pending.tx_hash();
154        assert_eq!(local_hash, node_hash);
155
156        let local_hash2 = pending.await.unwrap();
157        assert_eq!(local_hash2, node_hash);
158
159        let receipt =
160            provider.get_transaction_receipt(local_hash2).await.unwrap().expect("no receipt");
161        let receipt_hash = receipt.transaction_hash;
162        assert_eq!(receipt_hash, node_hash);
163    }
164
165    #[tokio::test]
166    async fn ingest_pk_signer() {
167        let pk: PrivateKeySigner =
168            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
169
170        let anvil = Anvil::new().spawn();
171
172        let provider = ProviderBuilder::new().wallet(pk.clone()).on_http(anvil.endpoint_url());
173
174        let tx = TransactionRequest {
175            nonce: Some(0),
176            value: Some(U256::from(100)),
177            to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()),
178            gas_price: Some(20e9 as u128),
179            gas: Some(21000),
180            ..Default::default()
181        };
182
183        let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
184
185        // Can access wallet via provider
186        let wallet = provider.wallet();
187
188        let default_address = wallet.default_signer().address();
189
190        assert_eq!(pk.address(), default_address);
191        assert_eq!(receipt.from, default_address);
192    }
193}