anvil/eth/
sign.rs

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