use crate::common_types::EvmAddress;
use crate::network::ChainId;
use crate::wallet::{WalletError, WalletManagerTrait};
use alloy::consensus::TypedTransaction;
use alloy::dyn_abi::TypedData;
use alloy::network::TxSigner;
use alloy::primitives::Signature;
use alloy::signers::{local::PrivateKeySigner, Signer};
use async_trait::async_trait;
use std::collections::HashMap;
use tokio::sync::Mutex;
pub struct PrivateKeyWalletManager {
wallets: Mutex<HashMap<u32, PrivateKeySigner>>,
private_keys: Vec<String>,
}
impl PrivateKeyWalletManager {
pub fn new(private_keys: Vec<String>) -> Self {
PrivateKeyWalletManager { wallets: Mutex::new(HashMap::new()), private_keys }
}
fn convert_to_internal_index(&self, wallet_index: u32) -> Result<usize, WalletError> {
if wallet_index >= u32::MAX - 1000 {
let internal_index = (u32::MAX - wallet_index) as usize;
if internal_index < self.private_keys.len() {
Ok(internal_index)
} else {
Err(WalletError::InvalidIndex {
index: wallet_index,
max_index: self.private_keys.len() as u32,
})
}
} else {
if (wallet_index as usize) < self.private_keys.len() {
Ok(wallet_index as usize)
} else {
Err(WalletError::InvalidIndex {
index: wallet_index,
max_index: self.private_keys.len() as u32,
})
}
}
}
async fn get_wallet(
&self,
index: u32,
chain_id: &ChainId,
) -> Result<PrivateKeySigner, WalletError> {
let mut wallets = self.wallets.lock().await;
if let Some(wallet) = wallets.get(&index) {
Ok(wallet.clone())
} else {
let internal_index = self.convert_to_internal_index(index)?;
let private_key = self.private_keys.get(internal_index).ok_or_else(|| {
WalletError::InvalidIndex { index, max_index: self.private_keys.len() as u32 }
})?;
let wallet = private_key
.parse::<PrivateKeySigner>()
.map_err(|e| WalletError::PrivateKeyError(format!("Invalid private key: {}", e)))?
.with_chain_id(Some((*chain_id).into()));
wallets.insert(index, wallet.clone());
Ok(wallet)
}
}
}
#[async_trait]
impl WalletManagerTrait for PrivateKeyWalletManager {
async fn create_wallet(
&self,
wallet_index: u32,
chain_id: &ChainId,
) -> Result<EvmAddress, WalletError> {
let wallet = self.get_wallet(wallet_index, chain_id).await?;
Ok(EvmAddress::new(wallet.address()))
}
async fn get_address(
&self,
wallet_index: u32,
chain_id: &ChainId,
) -> Result<EvmAddress, WalletError> {
let wallet = self.get_wallet(wallet_index, chain_id).await?;
Ok(EvmAddress::new(wallet.address()))
}
async fn sign_transaction(
&self,
wallet_index: u32,
transaction: &TypedTransaction,
chain_id: &ChainId,
) -> Result<Signature, WalletError> {
let wallet = self.get_wallet(wallet_index, chain_id).await?;
let signature = match transaction {
TypedTransaction::Legacy(tx) => {
let mut tx = tx.clone();
wallet.sign_transaction(&mut tx).await?
}
TypedTransaction::Eip2930(tx) => {
let mut tx = tx.clone();
wallet.sign_transaction(&mut tx).await?
}
TypedTransaction::Eip1559(tx) => {
let mut tx = tx.clone();
wallet.sign_transaction(&mut tx).await?
}
TypedTransaction::Eip4844(tx) => {
let mut tx = tx.clone();
wallet.sign_transaction(&mut tx).await?
}
TypedTransaction::Eip7702(tx) => {
let mut tx = tx.clone();
wallet.sign_transaction(&mut tx).await?
}
};
Ok(signature)
}
async fn sign_text(
&self,
wallet_index: u32,
text: &str,
chain_id: &ChainId,
) -> Result<Signature, WalletError> {
let wallet = self.get_wallet(wallet_index, chain_id).await?;
let signature = wallet.sign_message(text.as_bytes()).await?;
Ok(signature)
}
async fn sign_typed_data(
&self,
wallet_index: u32,
typed_data: &TypedData,
chain_id: &ChainId,
) -> Result<Signature, WalletError> {
let wallet = self.get_wallet(wallet_index, chain_id).await?;
let signature = wallet.sign_dynamic_typed_data(typed_data).await?;
Ok(signature)
}
fn supports_blobs(&self) -> bool {
true
}
}