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