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..3b31ba29 100644 --- a/bin/silius/src/cli/args.rs +++ b/bin/silius/src/cli/args.rs @@ -3,7 +3,7 @@ use crate::utils::{ parse_u256, parse_uopool_mode, }; 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, group = "account")] + pub private_key: Option, + + /// Flashbots private key + #[clap(long, 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/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