use crate::eth::error::BlockchainError;
use alloy_consensus::SignableTransaction;
use alloy_dyn_abi::TypedData;
use alloy_network::TxSignerSync;
use alloy_primitives::{map::AddressHashMap, Address, PrimitiveSignature as Signature, B256, U256};
use alloy_signer::Signer as AlloySigner;
use alloy_signer_local::PrivateKeySigner;
use anvil_core::eth::transaction::{
optimism::DepositTransaction, TypedTransaction, TypedTransactionRequest,
};
use op_alloy_consensus::TxDeposit;
#[async_trait::async_trait]
pub trait Signer: Send + Sync {
fn accounts(&self) -> Vec<Address>;
fn is_signer_for(&self, addr: Address) -> bool {
self.accounts().contains(&addr)
}
async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError>;
async fn sign_typed_data(
&self,
address: Address,
payload: &TypedData,
) -> Result<Signature, BlockchainError>;
async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError>;
fn sign_transaction(
&self,
request: TypedTransactionRequest,
address: &Address,
) -> Result<Signature, BlockchainError>;
}
pub struct DevSigner {
addresses: Vec<Address>,
accounts: AddressHashMap<PrivateKeySigner>,
}
impl DevSigner {
pub fn new(accounts: Vec<PrivateKeySigner>) -> Self {
let addresses = accounts.iter().map(|wallet| wallet.address()).collect::<Vec<_>>();
let accounts = addresses.iter().cloned().zip(accounts).collect();
Self { addresses, accounts }
}
}
#[async_trait::async_trait]
impl Signer for DevSigner {
fn accounts(&self) -> Vec<Address> {
self.addresses.clone()
}
fn is_signer_for(&self, addr: Address) -> bool {
self.accounts.contains_key(&addr)
}
async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError> {
let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
Ok(signer.sign_message(message).await?)
}
async fn sign_typed_data(
&self,
address: Address,
payload: &TypedData,
) -> Result<Signature, BlockchainError> {
let mut signer =
self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned();
signer.set_chain_id(None);
Ok(signer.sign_dynamic_typed_data(payload).await?)
}
async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError> {
let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
Ok(signer.sign_hash(&hash).await?)
}
fn sign_transaction(
&self,
request: TypedTransactionRequest,
address: &Address,
) -> Result<Signature, BlockchainError> {
let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?;
match request {
TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
TypedTransactionRequest::Deposit(_) => {
unreachable!("op deposit txs should not be signed")
}
}
}
}
pub fn build_typed_transaction(
request: TypedTransactionRequest,
signature: Signature,
) -> Result<TypedTransaction, BlockchainError> {
let tx = match request {
TypedTransactionRequest::Legacy(tx) => TypedTransaction::Legacy(tx.into_signed(signature)),
TypedTransactionRequest::EIP2930(tx) => {
TypedTransaction::EIP2930(tx.into_signed(signature))
}
TypedTransactionRequest::EIP1559(tx) => {
TypedTransaction::EIP1559(tx.into_signed(signature))
}
TypedTransactionRequest::EIP4844(tx) => {
TypedTransaction::EIP4844(tx.into_signed(signature))
}
TypedTransactionRequest::Deposit(tx) => {
let TxDeposit {
from,
gas_limit,
to,
value,
input,
source_hash,
mint,
is_system_transaction,
..
} = tx;
TypedTransaction::Deposit(DepositTransaction {
from,
gas_limit,
kind: to,
value,
input,
source_hash,
mint: mint.map_or(U256::ZERO, U256::from),
is_system_tx: is_system_transaction,
nonce: 0,
})
}
};
Ok(tx)
}