Skip to main content

alloy_network/ethereum/
wallet.rs

1use crate::{Network, NetworkWallet, TxSigner};
2use alloy_consensus::{SignableTransaction, Signed};
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`] objects that specify the
46    /// signer's address in the `from` field.
47    ///
48    /// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
49    /// [`TypedTransaction`]: alloy_consensus::TypedTransaction
50    pub fn register_signer<S>(&mut self, signer: S)
51    where
52        S: TxSigner<Signature> + Send + Sync + 'static,
53    {
54        self.signers.insert(signer.address(), Arc::new(signer));
55    }
56
57    /// Register a new signer on this object, and set it as the default signer.
58    /// This signer will be used to sign [`TransactionRequest`] and
59    /// [`TypedTransaction`] objects that do not specify a signer address in the
60    /// `from` field.
61    ///
62    /// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
63    /// [`TypedTransaction`]: alloy_consensus::TypedTransaction
64    pub fn register_default_signer<S>(&mut self, signer: S)
65    where
66        S: TxSigner<Signature> + Send + Sync + 'static,
67    {
68        self.default = signer.address();
69        self.register_signer(signer);
70    }
71
72    /// Sets the default signer to the given address.
73    ///
74    /// The default signer is used to sign [`TransactionRequest`] and [`TypedTransaction`] objects
75    /// that do not specify a signer address in the `from` field.
76    ///
77    /// The provided address must be a registered signer otherwise an error is returned.
78    ///
79    /// If you're looking to add a new signer and set it as default, use
80    /// [`EthereumWallet::register_default_signer`].
81    ///
82    /// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
83    /// [`TypedTransaction`]: alloy_consensus::TypedTransaction
84    pub fn set_default_signer(&mut self, address: Address) -> alloy_signer::Result<()> {
85        if self.signers.contains_key(&address) {
86            self.default = address;
87            Ok(())
88        } else {
89            Err(alloy_signer::Error::message(format!(
90                "{address} is not a registered signer. Use `register_default_signer`"
91            )))
92        }
93    }
94
95    /// Get the default signer.
96    pub fn default_signer(&self) -> Arc<dyn TxSigner<Signature> + Send + Sync + 'static> {
97        self.signers.get(&self.default).cloned().expect("invalid signer")
98    }
99
100    /// Get the signer for the given address.
101    pub fn signer_by_address(
102        &self,
103        address: Address,
104    ) -> Option<Arc<dyn TxSigner<Signature> + Send + Sync + 'static>> {
105        self.signers.get(&address).cloned()
106    }
107
108    #[doc(alias = "sign_tx_inner")]
109    async fn sign_transaction_inner(
110        &self,
111        sender: Address,
112        tx: &mut dyn SignableTransaction<Signature>,
113    ) -> alloy_signer::Result<Signature> {
114        self.signer_by_address(sender)
115            .ok_or_else(|| {
116                alloy_signer::Error::other(format!("Missing signing credential for {sender}"))
117            })?
118            .sign_transaction(tx)
119            .await
120    }
121}
122
123impl<N: Network> NetworkWallet<N> for EthereumWallet
124where
125    N::TxEnvelope: From<Signed<N::UnsignedTx>>,
126    N::UnsignedTx: SignableTransaction<Signature>,
127{
128    fn default_signer_address(&self) -> Address {
129        self.default
130    }
131
132    fn has_signer_for(&self, address: &Address) -> bool {
133        self.signers.contains_key(address)
134    }
135
136    fn signer_addresses(&self) -> impl Iterator<Item = Address> {
137        self.signers.keys().copied()
138    }
139
140    async fn sign_transaction_from(
141        &self,
142        sender: Address,
143        mut tx: N::UnsignedTx,
144    ) -> alloy_signer::Result<N::TxEnvelope> {
145        let sig = self.sign_transaction_inner(sender, &mut tx).await?;
146        Ok(tx.into_signed(sig).into())
147    }
148}
149
150/// A trait for converting a signer into a [`NetworkWallet`].
151pub trait IntoWallet<N: Network = Ethereum>: Send + Sync + Debug {
152    /// The wallet type for the network.
153    type NetworkWallet: NetworkWallet<N>;
154    /// Convert the signer into a wallet.
155    fn into_wallet(self) -> Self::NetworkWallet;
156}
157
158impl<W: NetworkWallet<N>, N: Network> IntoWallet<N> for W {
159    type NetworkWallet = W;
160
161    fn into_wallet(self) -> Self::NetworkWallet {
162        self
163    }
164}