From 01a47bdb6362974b5bf159f816a159f5e4467c44 Mon Sep 17 00:00:00 2001 From: malcolmtan Date: Sat, 15 Jun 2024 00:23:49 +0800 Subject: [PATCH] feat: support loading private key --- bin/silius/src/bundler.rs | 42 ++++++++++--- bin/silius/src/cli/args.rs | 108 ++++++++++++++++++++++++++++++-- bin/silius/src/utils.rs | 16 +++++ crates/primitives/src/wallet.rs | 31 ++++++++- 4 files changed, 183 insertions(+), 14 deletions(-) diff --git a/bin/silius/src/bundler.rs b/bin/silius/src/bundler.rs index b5c412ce..d5e23632 100644 --- a/bin/silius/src/bundler.rs +++ b/bin/silius/src/bundler.rs @@ -121,15 +121,41 @@ where let chain_conn = Chain::from(chain_id); let wallet: Wallet; - if args.send_bundle_mode == SendStrategy::Flashbots { - wallet = Wallet::from_file(args.mnemonic_file.into(), chain_id, true) - .map_err(|error| eyre::format_err!("Could not load mnemonic file: {}", error))?; - info!("Wallet Signer {:?}", wallet.signer); - info!("Flashbots Signer {:?}", wallet.flashbots_signer); + + if let Some(mnemonic_file) = args.mnemonic_file { + if args.send_bundle_mode == SendStrategy::Flashbots { + wallet = Wallet::from_file(mnemonic_file.into(), chain_id, true) + .map_err(|error| eyre::format_err!("Could not load mnemonic file: {}", error))?; + info!("Wallet Signer {:?}", wallet.signer); + info!("Flashbots Signer {:?}", wallet.flashbots_signer); + } else { + wallet = Wallet::from_file(mnemonic_file.into(), chain_id, false) + .map_err(|error| eyre::format_err!("Could not load mnemonic file: {}", error))?; + info!("{:?}", wallet.signer); + } + } else if let Some(private_key) = args.private_key { + if args.send_bundle_mode == SendStrategy::Flashbots { + wallet = Wallet::from_private_key( + private_key.as_str(), + chain_id, + true, + args.flashbots_private_key.as_deref(), + ) + .map_err(|error| { + eyre::format_err!("Could not load from private key or flashbots key: {}", error) + })?; + info!("Wallet Signer {:?}", wallet.signer); + info!("Flashbots Signer {:?}", wallet.flashbots_signer); + } else { + if args.flashbots_private_key.is_some() { + info!("Flashbots key is ignored since send bundle mode is not Flashbots"); + } + wallet = Wallet::from_private_key(private_key.as_str(), chain_id, false, None) + .map_err(|error| eyre::format_err!("Could not load from private key: {}", error))?; + info!("{:?}", wallet.signer); + } } else { - wallet = Wallet::from_file(args.mnemonic_file.into(), chain_id, false) - .map_err(|error| eyre::format_err!("Could not load mnemonic file: {}", error))?; - info!("{:?}", wallet.signer); + panic!("Neither mnemonic file nor private key was found"); } info!("Connecting to uopool gRPC service..."); diff --git a/bin/silius/src/cli/args.rs b/bin/silius/src/cli/args.rs index 38d839e4..9b51b5b9 100644 --- a/bin/silius/src/cli/args.rs +++ b/bin/silius/src/cli/args.rs @@ -1,9 +1,9 @@ use crate::utils::{ parse_address, parse_duration, parse_enr, parse_label_value, parse_send_bundle_mode, - parse_u256, parse_uopool_mode, + parse_u256, parse_uopool_mode, validate_private_key, }; use alloy_chains::{Chain, NamedChain}; -use clap::{Parser, ValueEnum}; +use clap::{ArgGroup, Parser, ValueEnum}; use discv5::Enr; use ethers::types::{Address, U256}; use expanded_pathbuf::ExpandedPathBuf; @@ -37,6 +37,7 @@ pub enum StorageType { /// Bundler CLI args #[derive(Debug, Clone, Parser, PartialEq)] +#[clap(group(ArgGroup::new("account").required(true).args(&["mnemonic_file", "private_key"])))] pub struct BundlerArgs { /// Bundler gRPC address to listen on. #[clap(long = "bundler.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] @@ -47,8 +48,16 @@ pub struct BundlerArgs { pub bundler_port: u16, /// Path to the mnemonic file. - #[clap(long)] - pub mnemonic_file: PathBuf, + #[clap(long, group = "account")] + pub mnemonic_file: Option, + + /// Private key for the wallet + #[clap(long, value_parser=validate_private_key, group = "account")] + pub private_key: Option, + + /// Flashbots private key + #[clap(long, value_parser=validate_private_key, conflicts_with = "mnemonic_file")] + pub flashbots_private_key: Option, /// The bundler beneficiary address. #[clap(long, value_parser=parse_address)] @@ -364,8 +373,97 @@ mod tests { ]; assert_eq!( BundlerArgs { - mnemonic_file: PathBuf::from( + mnemonic_file: Some(PathBuf::from( "~/.silius/0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990" + )), + private_key: None, + flashbots_private_key: None, + beneficiary: Address::from_str("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990") + .unwrap(), + min_balance: U256::from(100000000000000000_u64), + bundle_interval: 10, + send_bundle_mode: SendStrategy::EthereumClient, + bundler_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + bundler_port: 3002, + enable_access_list: false, + }, + BundlerArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn bundler_args_private_key() { + let args = vec![ + "bundlerargs", + "--private-key", + "4c5e5d3076c425e8d8affe9c2a0da32b779820ef008fdda02d5c7b783674d8c4", + "--beneficiary", + "0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", + "--min-balance", + "100000000000000000", + "--bundler.addr", + "127.0.0.1", + "--bundler.port", + "3002", + "--bundle-interval", + "10", + ]; + assert_eq!( + BundlerArgs { + mnemonic_file: None, + private_key: Some( + String::from_str( + "4c5e5d3076c425e8d8affe9c2a0da32b779820ef008fdda02d5c7b783674d8c4" + ) + .unwrap() + ), + flashbots_private_key: None, + beneficiary: Address::from_str("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990") + .unwrap(), + min_balance: U256::from(100000000000000000_u64), + bundle_interval: 10, + send_bundle_mode: SendStrategy::EthereumClient, + bundler_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + bundler_port: 3002, + enable_access_list: false, + }, + BundlerArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn bundler_args_private_key_flashbots_private_key() { + let args = vec![ + "bundlerargs", + "--private-key", + "4c5e5d3076c425e8d8affe9c2a0da32b779820ef008fdda02d5c7b783674d8c4", + "--flashbots-private-key", + "df218be02efd744fc91f93d7f3c49676fb99b296e99c1410fccd65be79d608a7", + "--beneficiary", + "0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", + "--min-balance", + "100000000000000000", + "--bundler.addr", + "127.0.0.1", + "--bundler.port", + "3002", + "--bundle-interval", + "10", + ]; + assert_eq!( + BundlerArgs { + mnemonic_file: None, + private_key: Some( + String::from_str( + "4c5e5d3076c425e8d8affe9c2a0da32b779820ef008fdda02d5c7b783674d8c4" + ) + .unwrap() + ), + flashbots_private_key: Some( + String::from_str( + "df218be02efd744fc91f93d7f3c49676fb99b296e99c1410fccd65be79d608a7" + ) + .unwrap() ), beneficiary: Address::from_str("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990") .unwrap(), diff --git a/bin/silius/src/utils.rs b/bin/silius/src/utils.rs index 31a64dd6..6cd588d3 100644 --- a/bin/silius/src/utils.rs +++ b/bin/silius/src/utils.rs @@ -87,3 +87,19 @@ where Ok(()) } + +pub fn validate_private_key(hex_string: &str) -> Result { + let chars = hex_string.chars(); + + if chars.clone().count() != 64 { + return Err(format!("{hex_string} is not a valid private key")); + } + + for c in chars { + if !c.is_ascii_hexdigit() { + return Err(format!("{hex_string} is not a valid hexadecimal string")); + } + } + + Ok(String::from(hex_string)) +} diff --git a/crates/primitives/src/wallet.rs b/crates/primitives/src/wallet.rs index d08ac7fe..885bf2c0 100644 --- a/crates/primitives/src/wallet.rs +++ b/crates/primitives/src/wallet.rs @@ -2,7 +2,7 @@ use crate::{UserOperation, UserOperationSigned}; use ethers::{ - prelude::{k256::ecdsa::SigningKey, rand}, + prelude::{k256::ecdsa::SigningKey, rand, LocalWallet}, signers::{coins_bip39::English, MnemonicBuilder, Signer}, types::Address, }; @@ -140,6 +140,35 @@ impl Wallet { } } + /// Create a new wallet from the given private key + /// if `flashbots_key` is true, then `flashbots_private_key` must be provided + /// + /// # Arguments + /// * `private_key` - The private key + /// * `chain_id` - The chain id of the blockchain network to be used + /// * `flashbots_key` - Whether to create a Flashbots key + /// * `flashbots_private_key` - The private key for the Flashbots wallet + /// + /// # Returns + /// * `Self` - A new `Wallet` instance + pub fn from_private_key( + private_key: &str, + chain_id: u64, + flashbots_key: bool, + flashbots_private_key: Option<&str>, + ) -> eyre::Result { + let wallet = private_key.parse::()?.with_chain_id(chain_id); + if flashbots_key { + let flashbots_wallet = flashbots_private_key + .expect("Flashbots private key is required") + .parse::()? + .with_chain_id(chain_id); + Ok(Self { signer: wallet, flashbots_signer: Some(flashbots_wallet) }) + } else { + Ok(Self { signer: wallet, flashbots_signer: None }) + } + } + /// Signs the user operation /// /// # Arguments