alloy_network/ethereum/
wallet.rs1use crate::{AnyNetwork, AnyTxEnvelope, AnyTypedTransaction, Network, NetworkWallet, TxSigner};
2use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction};
3use alloy_primitives::{map::AddressHashMap, Address, PrimitiveSignature as Signature};
4use std::{fmt::Debug, sync::Arc};
5
6use super::Ethereum;
7
8#[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    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    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    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    pub fn default_signer(&self) -> Arc<dyn TxSigner<Signature> + Send + Sync + 'static> {
72        self.signers.get(&self.default).cloned().expect("invalid signer")
73    }
74
75    pub fn signer_by_address(
77        &self,
78        address: Address,
79    ) -> Option<Arc<dyn TxSigner<Signature> + Send + Sync + 'static>> {
80        self.signers.get(&address).cloned()
81    }
82
83    #[doc(alias = "sign_tx_inner")]
84    async fn sign_transaction_inner(
85        &self,
86        sender: Address,
87        tx: &mut dyn SignableTransaction<Signature>,
88    ) -> alloy_signer::Result<Signature> {
89        self.signer_by_address(sender)
90            .ok_or_else(|| {
91                alloy_signer::Error::other(format!("Missing signing credential for {}", sender))
92            })?
93            .sign_transaction(tx)
94            .await
95    }
96}
97
98impl<N> NetworkWallet<N> for EthereumWallet
99where
100    N: Network<UnsignedTx = TypedTransaction, TxEnvelope = TxEnvelope>,
101{
102    fn default_signer_address(&self) -> Address {
103        self.default
104    }
105
106    fn has_signer_for(&self, address: &Address) -> bool {
107        self.signers.contains_key(address)
108    }
109
110    fn signer_addresses(&self) -> impl Iterator<Item = Address> {
111        self.signers.keys().copied()
112    }
113
114    #[doc(alias = "sign_tx_from")]
115    async fn sign_transaction_from(
116        &self,
117        sender: Address,
118        tx: TypedTransaction,
119    ) -> alloy_signer::Result<TxEnvelope> {
120        match tx {
121            TypedTransaction::Legacy(mut t) => {
122                let sig = self.sign_transaction_inner(sender, &mut t).await?;
123                Ok(t.into_signed(sig).into())
124            }
125            TypedTransaction::Eip2930(mut t) => {
126                let sig = self.sign_transaction_inner(sender, &mut t).await?;
127                Ok(t.into_signed(sig).into())
128            }
129            TypedTransaction::Eip1559(mut t) => {
130                let sig = self.sign_transaction_inner(sender, &mut t).await?;
131                Ok(t.into_signed(sig).into())
132            }
133            TypedTransaction::Eip4844(mut t) => {
134                let sig = self.sign_transaction_inner(sender, &mut t).await?;
135                Ok(t.into_signed(sig).into())
136            }
137            TypedTransaction::Eip7702(mut t) => {
138                let sig = self.sign_transaction_inner(sender, &mut t).await?;
139                Ok(t.into_signed(sig).into())
140            }
141        }
142    }
143}
144
145impl NetworkWallet<AnyNetwork> for EthereumWallet {
146    fn default_signer_address(&self) -> Address {
147        self.default
148    }
149
150    fn has_signer_for(&self, address: &Address) -> bool {
151        self.signers.contains_key(address)
152    }
153
154    fn signer_addresses(&self) -> impl Iterator<Item = Address> {
155        self.signers.keys().copied()
156    }
157
158    #[doc(alias = "sign_tx_from")]
159    async fn sign_transaction_from(
160        &self,
161        sender: Address,
162        tx: AnyTypedTransaction,
163    ) -> alloy_signer::Result<AnyTxEnvelope> {
164        match tx {
165            AnyTypedTransaction::Ethereum(t) => Ok(AnyTxEnvelope::Ethereum(
166                NetworkWallet::<Ethereum>::sign_transaction_from(self, sender, t).await?,
167            )),
168            _ => Err(alloy_signer::Error::other("cannot sign UnknownTypedTransaction")),
169        }
170    }
171}
172
173pub trait IntoWallet<N: Network = Ethereum>: Send + Sync + Debug {
175    type NetworkWallet: NetworkWallet<N>;
177    fn into_wallet(self) -> Self::NetworkWallet;
179}
180
181impl<W: NetworkWallet<N>, N: Network> IntoWallet<N> for W {
182    type NetworkWallet = W;
183
184    fn into_wallet(self) -> Self::NetworkWallet {
185        self
186    }
187}