anvil/eth/
sign.rs

1use crate::eth::error::BlockchainError;
2use alloy_consensus::{Sealed, SignableTransaction};
3use alloy_dyn_abi::TypedData;
4use alloy_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};
9use tempo_primitives::TempoSignature;
10
11/// A transaction signer
12#[async_trait::async_trait]
13pub trait Signer: Send + Sync {
14    /// returns the available accounts for this signer
15    fn accounts(&self) -> Vec<Address>;
16
17    /// Returns `true` whether this signer can sign for this address
18    fn is_signer_for(&self, addr: Address) -> bool {
19        self.accounts().contains(&addr)
20    }
21
22    /// Returns the signature
23    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError>;
24
25    /// Encodes and signs the typed data according EIP-712. Payload must conform to the EIP-712
26    /// standard.
27    async fn sign_typed_data(
28        &self,
29        address: Address,
30        payload: &TypedData,
31    ) -> Result<Signature, BlockchainError>;
32
33    /// Signs the given hash.
34    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError>;
35
36    /// signs a transaction request using the given account in request
37    fn sign_transaction(
38        &self,
39        request: FoundryTypedTx,
40        address: &Address,
41    ) -> Result<Signature, BlockchainError>;
42}
43
44/// Maintains developer keys
45pub struct DevSigner {
46    addresses: Vec<Address>,
47    accounts: AddressHashMap<PrivateKeySigner>,
48}
49
50impl DevSigner {
51    pub fn new(accounts: Vec<PrivateKeySigner>) -> Self {
52        let addresses = accounts.iter().map(|wallet| wallet.address()).collect::<Vec<_>>();
53        let accounts = addresses.iter().copied().zip(accounts).collect();
54        Self { addresses, accounts }
55    }
56}
57
58#[async_trait::async_trait]
59impl Signer for DevSigner {
60    fn accounts(&self) -> Vec<Address> {
61        self.addresses.clone()
62    }
63
64    fn is_signer_for(&self, addr: Address) -> bool {
65        self.accounts.contains_key(&addr)
66    }
67
68    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError> {
69        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
70
71        Ok(signer.sign_message(message).await?)
72    }
73
74    async fn sign_typed_data(
75        &self,
76        address: Address,
77        payload: &TypedData,
78    ) -> Result<Signature, BlockchainError> {
79        let mut signer =
80            self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned();
81
82        // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing
83        // typed data.
84        signer.set_chain_id(None);
85
86        Ok(signer.sign_dynamic_typed_data(payload).await?)
87    }
88
89    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError> {
90        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
91
92        Ok(signer.sign_hash(&hash).await?)
93    }
94
95    fn sign_transaction(
96        &self,
97        request: FoundryTypedTx,
98        address: &Address,
99    ) -> Result<Signature, BlockchainError> {
100        let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?;
101        match request {
102            FoundryTypedTx::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
103            FoundryTypedTx::Eip2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
104            FoundryTypedTx::Eip1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
105            FoundryTypedTx::Eip7702(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
106            FoundryTypedTx::Eip4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
107            FoundryTypedTx::Deposit(_) => {
108                unreachable!("op deposit txs should not be signed")
109            }
110            FoundryTypedTx::Tempo(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
111        }
112    }
113}
114
115/// converts the `request` into a [`FoundryTypedTx`] with the given signature
116///
117/// # Errors
118///
119/// This will fail if the `signature` contains an erroneous recovery id.
120pub fn build_typed_transaction(
121    request: FoundryTypedTx,
122    signature: Signature,
123) -> Result<FoundryTxEnvelope, BlockchainError> {
124    let tx = match request {
125        FoundryTypedTx::Legacy(tx) => FoundryTxEnvelope::Legacy(tx.into_signed(signature)),
126        FoundryTypedTx::Eip2930(tx) => FoundryTxEnvelope::Eip2930(tx.into_signed(signature)),
127        FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)),
128        FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)),
129        FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)),
130        FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)),
131        FoundryTypedTx::Tempo(tx) => {
132            let tempo_sig: TempoSignature = signature.into();
133            FoundryTxEnvelope::Tempo(tx.into_signed(tempo_sig))
134        }
135    };
136
137    Ok(tx)
138}