alloy_network/ethereum/
wallet.rs

1use crate::{AnyNetwork, AnyTxEnvelope, AnyTypedTransaction, Network, NetworkWallet, TxSigner};
2use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction};
3use alloy_primitives::{map::AddressHashMap, Address, Signature};
4use std::{fmt::Debug, sync::Arc};
5
6use super::Ethereum;
7
8/// A wallet capable of signing any transaction for the Ethereum network.
9#[derive(Clone, Default)]
10pub struct EthereumWallet {
11    default: Address,
12    signers: AddressHashMap<Arc<dyn TxSigner<Signature> + Send + Sync>>,
13}
14
15impl std::fmt::Debug for EthereumWallet {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        f.debug_struct("EthereumWallet")
18            .field("default_signer", &self.default)
19            .field("credentials", &self.signers.len())
20            .finish()
21    }
22}
23
24impl<S> From<S> for EthereumWallet
25where
26    S: TxSigner<Signature> + Send + Sync + 'static,
27{
28    fn from(signer: S) -> Self {
29        Self::new(signer)
30    }
31}
32
33impl EthereumWallet {
34    /// Create a new signer with the given signer as the default signer.
35    pub fn new<S>(signer: S) -> Self
36    where
37        S: TxSigner<Signature> + Send + Sync + 'static,
38    {
39        let mut this = Self::default();
40        this.register_default_signer(signer);
41        this
42    }
43
44    /// Register a new signer on this object. This signer will be used to sign
45    /// [`TransactionRequest`] and [`TypedTransaction`] object that specify the
46    /// signer's address in the `from` field.
47    ///
48    /// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
49    pub fn register_signer<S>(&mut self, signer: S)
50    where
51        S: TxSigner<Signature> + Send + Sync + 'static,
52    {
53        self.signers.insert(signer.address(), Arc::new(signer));
54    }
55
56    /// Register a new signer on this object, and set it as the default signer.
57    /// This signer will be used to sign [`TransactionRequest`] and
58    /// [`TypedTransaction`] objects that do not specify a signer address in the
59    /// `from` field.
60    ///
61    /// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
62    pub fn register_default_signer<S>(&mut self, signer: S)
63    where
64        S: TxSigner<Signature> + Send + Sync + 'static,
65    {
66        self.default = signer.address();
67        self.register_signer(signer);
68    }
69
70    /// Sets the default signer to the given address.
71    ///
72    /// The default signer is used to sign [`TransactionRequest`] and [`TypedTransaction`] objects
73    /// that do not specify a signer address in the `from` field.
74    ///
75    /// The provided address must be a registered signer otherwise an error is returned.
76    ///
77    /// If you're looking to add a new signer and set it as default, use
78    /// [`EthereumWallet::register_default_signer`].
79    ///
80    /// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
81    pub fn set_default_signer(&mut self, address: Address) -> alloy_signer::Result<()> {
82        if self.signers.contains_key(&address) {
83            self.default = address;
84            Ok(())
85        } else {
86            Err(alloy_signer::Error::message(format!(
87                "{address} is not a registered signer. Use `register_default_signer`"
88            )))
89        }
90    }
91
92    /// Get the default signer.
93    pub fn default_signer(&self) -> Arc<dyn TxSigner<Signature> + Send + Sync + 'static> {
94        self.signers.get(&self.default).cloned().expect("invalid signer")
95    }
96
97    /// Get the signer for the given address.
98    pub fn signer_by_address(
99        &self,
100        address: Address,
101    ) -> Option<Arc<dyn TxSigner<Signature> + Send + Sync + 'static>> {
102        self.signers.get(&address).cloned()
103    }
104
105    #[doc(alias = "sign_tx_inner")]
106    async fn sign_transaction_inner(
107        &self,
108        sender: Address,
109        tx: &mut dyn SignableTransaction<Signature>,
110    ) -> alloy_signer::Result<Signature> {
111        self.signer_by_address(sender)
112            .ok_or_else(|| {
113                alloy_signer::Error::other(format!("Missing signing credential for {sender}"))
114            })?
115            .sign_transaction(tx)
116            .await
117    }
118}
119
120impl<N> NetworkWallet<N> for EthereumWallet
121where
122    N: Network<UnsignedTx = TypedTransaction, TxEnvelope = TxEnvelope>,
123{
124    fn default_signer_address(&self) -> Address {
125        self.default
126    }
127
128    fn has_signer_for(&self, address: &Address) -> bool {
129        self.signers.contains_key(address)
130    }
131
132    fn signer_addresses(&self) -> impl Iterator<Item = Address> {
133        self.signers.keys().copied()
134    }
135
136    #[doc(alias = "sign_tx_from")]
137    async fn sign_transaction_from(
138        &self,
139        sender: Address,
140        tx: TypedTransaction,
141    ) -> alloy_signer::Result<TxEnvelope> {
142        match tx {
143            TypedTransaction::Legacy(mut t) => {
144                let sig = self.sign_transaction_inner(sender, &mut t).await?;
145                Ok(t.into_signed(sig).into())
146            }
147            TypedTransaction::Eip2930(mut t) => {
148                let sig = self.sign_transaction_inner(sender, &mut t).await?;
149                Ok(t.into_signed(sig).into())
150            }
151            TypedTransaction::Eip1559(mut t) => {
152                let sig = self.sign_transaction_inner(sender, &mut t).await?;
153                Ok(t.into_signed(sig).into())
154            }
155            TypedTransaction::Eip4844(mut t) => {
156                let sig = self.sign_transaction_inner(sender, &mut t).await?;
157                Ok(t.into_signed(sig).into())
158            }
159            TypedTransaction::Eip7702(mut t) => {
160                let sig = self.sign_transaction_inner(sender, &mut t).await?;
161                Ok(t.into_signed(sig).into())
162            }
163        }
164    }
165}
166
167impl NetworkWallet<AnyNetwork> for EthereumWallet {
168    fn default_signer_address(&self) -> Address {
169        self.default
170    }
171
172    fn has_signer_for(&self, address: &Address) -> bool {
173        self.signers.contains_key(address)
174    }
175
176    fn signer_addresses(&self) -> impl Iterator<Item = Address> {
177        self.signers.keys().copied()
178    }
179
180    #[doc(alias = "sign_tx_from")]
181    async fn sign_transaction_from(
182        &self,
183        sender: Address,
184        tx: AnyTypedTransaction,
185    ) -> alloy_signer::Result<AnyTxEnvelope> {
186        match tx {
187            AnyTypedTransaction::Ethereum(t) => Ok(AnyTxEnvelope::Ethereum(
188                NetworkWallet::<Ethereum>::sign_transaction_from(self, sender, t).await?,
189            )),
190            _ => Err(alloy_signer::Error::other("cannot sign UnknownTypedTransaction")),
191        }
192    }
193}
194
195/// A trait for converting a signer into a [`NetworkWallet`].
196pub trait IntoWallet<N: Network = Ethereum>: Send + Sync + Debug {
197    /// The wallet type for the network.
198    type NetworkWallet: NetworkWallet<N>;
199    /// Convert the signer into a wallet.
200    fn into_wallet(self) -> Self::NetworkWallet;
201}
202
203impl<W: NetworkWallet<N>, N: Network> IntoWallet<N> for W {
204    type NetworkWallet = W;
205
206    fn into_wallet(self) -> Self::NetworkWallet {
207        self
208    }
209}