alloy_provider/provider/
web3_signer.rs

1use alloy_eips::Decodable2718;
2use alloy_network::{Ethereum, Network, TransactionBuilder};
3use alloy_primitives::{Address, Bytes};
4
5use super::Provider;
6
7/// A remote signer that leverages the underlying provider to sign transactions using
8/// `"eth_signTransaction"` requests.
9///
10/// For more information, please see [Web3Signer](https://docs.web3signer.consensys.io/)
11///
12/// Note:
13///
14/// `"eth_signTransaction"` is not supported by regular nodes.
15///
16/// [`ProviderBuilder`]: crate::ProviderBuilder
17#[derive(Debug, Clone)]
18pub struct Web3Signer<P: Provider<N> + Clone, N: Network = Ethereum> {
19    /// The provider used to make `"eth_signTransaction"` requests.
20    provider: P,
21    /// The address of the remote signer that will sign the transactions.
22    ///
23    /// This is set as the `from` field in the [`Network::TransactionRequest`]'s for the
24    /// `"eth_signTransaction"` requests.
25    address: Address,
26    _pd: std::marker::PhantomData<N>,
27}
28
29impl<P: Provider<N> + Clone, N: Network> Web3Signer<P, N> {
30    /// Instantiates a new [`Web3Signer`] with the given [`Provider`] and the signer address.
31    ///
32    /// The `address` is used to set the `from` field in the transaction requests.
33    ///
34    /// The remote signer's address _must_ be the same as the signer address provided here.
35    pub const fn new(provider: P, address: Address) -> Self {
36        Self { provider, address, _pd: std::marker::PhantomData }
37    }
38
39    /// Returns the underlying [`Provider`] used by the [`Web3Signer`].
40    pub fn provider(&self) -> P {
41        self.provider.clone()
42    }
43    /// Signs a transaction request and return the raw signed transaction in the form of [`Bytes`].
44    ///
45    /// The returned [`Bytes`] can be used to broadcast the transaction to the network using
46    /// [`Provider::send_raw_transaction`].
47    ///
48    /// Sets the `from` field to the provided `address`.
49    ///
50    /// If you'd like to receive a [`Network::TxEnvelope`] instead, use
51    /// [`Web3Signer::sign_and_decode`].
52    pub async fn sign_transaction(
53        &self,
54        mut tx: N::TransactionRequest,
55    ) -> alloy_signer::Result<Bytes> {
56        // Always overrides the `from` field with the web3 signer's address.
57        tx.set_from(self.address);
58        self.provider.sign_transaction(tx).await.map_err(alloy_signer::Error::other)
59    }
60
61    /// Signs a transaction request using [`Web3Signer::sign_transaction`] and decodes the raw bytes
62    /// returning a [`Network::TxEnvelope`].
63    pub async fn sign_and_decode(
64        &self,
65        tx: N::TransactionRequest,
66    ) -> alloy_signer::Result<N::TxEnvelope> {
67        let raw = self.sign_transaction(tx).await?;
68        N::TxEnvelope::decode_2718(&mut raw.as_ref()).map_err(alloy_signer::Error::other)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::{ext::test::async_ci_only, Provider, ProviderBuilder};
76    use alloy_consensus::{transaction::SignerRecoverable, TxEnvelope};
77    use alloy_node_bindings::{utils::run_with_tempdir, Reth};
78    use alloy_primitives::{Address, U256};
79
80    #[tokio::test]
81    #[cfg(not(windows))]
82    async fn eth_sign_transaction() {
83        async_ci_only(|| async {
84            run_with_tempdir("reth-sign-tx", |dir| async {
85                let reth = Reth::new().dev().disable_discovery().data_dir(dir).spawn();
86                let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
87
88                let accounts = provider.get_accounts().await.unwrap();
89                let from = accounts[0];
90                let signer = Web3Signer::new(provider, from);
91
92                let tx = signer
93                    .provider()
94                    .transaction_request()
95                    .from(from)
96                    .to(Address::ZERO)
97                    .value(U256::from(100))
98                    .gas_limit(21000);
99
100                let signed_tx = signer.sign_transaction(tx).await.unwrap();
101
102                let tx = TxEnvelope::decode_2718(&mut signed_tx.as_ref()).unwrap();
103
104                let signer = tx.recover_signer().unwrap();
105
106                assert_eq!(signer, from);
107            })
108            .await
109        })
110        .await;
111    }
112}