1use crate::eth::error::BlockchainError;
2use alloy_consensus::SignableTransaction;
3use alloy_dyn_abi::TypedData;
4use alloy_network::{Network, TxSignerSync};
5use alloy_primitives::{Address, B256, Signature, map::AddressHashMap};
6use alloy_signer::Signer as AlloySigner;
7use alloy_signer_local::PrivateKeySigner;
8use foundry_primitives::{FoundryTxEnvelope, FoundryTypedTx};
9
10#[async_trait::async_trait]
12pub trait MessageSigner: Send + Sync {
13 fn accounts(&self) -> Vec<Address>;
15
16 fn is_signer_for(&self, addr: Address) -> bool {
18 self.accounts().contains(&addr)
19 }
20
21 async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError>;
23
24 async fn sign_typed_data(
27 &self,
28 address: Address,
29 payload: &TypedData,
30 ) -> Result<Signature, BlockchainError>;
31
32 async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError>;
34}
35
36pub trait Signer<N: Network>: MessageSigner {
42 fn sign_transaction_from(
46 &self,
47 sender: &Address,
48 tx: N::UnsignedTx,
49 ) -> Result<N::TxEnvelope, BlockchainError>;
50}
51
52pub struct DevSigner {
54 addresses: Vec<Address>,
55 accounts: AddressHashMap<PrivateKeySigner>,
56}
57
58impl DevSigner {
59 pub fn new(accounts: Vec<PrivateKeySigner>) -> Self {
60 let addresses = accounts.iter().map(|wallet| wallet.address()).collect::<Vec<_>>();
61 let accounts = addresses.iter().copied().zip(accounts).collect();
62 Self { addresses, accounts }
63 }
64}
65
66#[async_trait::async_trait]
67impl MessageSigner for DevSigner {
68 fn accounts(&self) -> Vec<Address> {
69 self.addresses.clone()
70 }
71
72 fn is_signer_for(&self, addr: Address) -> bool {
73 self.accounts.contains_key(&addr)
74 }
75
76 async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError> {
77 let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
78
79 Ok(signer.sign_message(message).await?)
80 }
81
82 async fn sign_typed_data(
83 &self,
84 address: Address,
85 payload: &TypedData,
86 ) -> Result<Signature, BlockchainError> {
87 let mut signer =
88 self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned();
89
90 signer.set_chain_id(None);
93
94 Ok(signer.sign_dynamic_typed_data(payload).await?)
95 }
96
97 async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError> {
98 let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
99
100 Ok(signer.sign_hash(&hash).await?)
101 }
102}
103
104impl Signer<foundry_primitives::FoundryNetwork> for DevSigner {
105 fn sign_transaction_from(
106 &self,
107 sender: &Address,
108 tx: FoundryTypedTx,
109 ) -> Result<FoundryTxEnvelope, BlockchainError> {
110 let signer = self.accounts.get(sender).ok_or(BlockchainError::NoSignerAvailable)?;
111 let envelope = match tx {
112 FoundryTypedTx::Legacy(mut t) => {
113 let sig = signer.sign_transaction_sync(&mut t)?;
114 FoundryTxEnvelope::Legacy(t.into_signed(sig))
115 }
116 FoundryTypedTx::Eip2930(mut t) => {
117 let sig = signer.sign_transaction_sync(&mut t)?;
118 FoundryTxEnvelope::Eip2930(t.into_signed(sig))
119 }
120 FoundryTypedTx::Eip1559(mut t) => {
121 let sig = signer.sign_transaction_sync(&mut t)?;
122 FoundryTxEnvelope::Eip1559(t.into_signed(sig))
123 }
124 FoundryTypedTx::Eip7702(mut t) => {
125 let sig = signer.sign_transaction_sync(&mut t)?;
126 FoundryTxEnvelope::Eip7702(t.into_signed(sig))
127 }
128 FoundryTypedTx::Eip4844(mut t) => {
129 let sig = signer.sign_transaction_sync(&mut t)?;
130 FoundryTxEnvelope::Eip4844(t.into_signed(sig))
131 }
132 #[cfg(feature = "optimism")]
133 FoundryTypedTx::Deposit(_) => {
134 unreachable!("op deposit txs should not be signed")
135 }
136 #[cfg(feature = "optimism")]
137 FoundryTypedTx::PostExec(_) => {
138 unreachable!("op post-exec txs should not be signed")
139 }
140 FoundryTypedTx::Tempo(mut t) => {
141 let sig = signer.sign_transaction_sync(&mut t)?;
142 FoundryTxEnvelope::Tempo(t.into_signed(sig.into()))
143 }
144 };
145 Ok(envelope)
146 }
147}