Skip to main content

anvil_polkadot/
config.rs

1use crate::api_server::revive_conversions::ReviveAddress;
2use alloy_genesis::Genesis;
3use alloy_primitives::{Address, U256, hex, map::HashMap, utils::Unit};
4use alloy_signer_local::{
5    MnemonicBuilder, PrivateKeySigner,
6    coins_bip39::{English, Mnemonic},
7};
8use anvil_server::ServerConfig;
9use eyre::{Context, Result};
10use foundry_common::{duration_since_unix_epoch, sh_println};
11use polkadot_sdk::{
12    pallet_revive::evm::Account,
13    sc_cli::{
14        self, CliConfiguration as SubstrateCliConfiguration, Cors, DEFAULT_WASM_EXECUTION_METHOD,
15        DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, RPC_DEFAULT_MAX_CONNECTIONS,
16        RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
17        RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
18    },
19    sc_service,
20};
21use rand_08::thread_rng;
22use serde_json::{Value, json};
23use std::{
24    fmt::Write as FmtWrite,
25    fs::File,
26    io,
27    net::{IpAddr, Ipv4Addr},
28    num::NonZeroU32,
29    path::PathBuf,
30    time::Duration,
31};
32use subxt_signer::eth::Keypair;
33use yansi::Paint;
34
35pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
36
37/// Default port the rpc will open
38pub const NODE_PORT: u16 = 8545;
39/// Default chain id of the node
40pub const CHAIN_ID: u64 = 31337;
41/// The default gas limit for all transactions
42pub const DEFAULT_GAS_LIMIT: u128 = 30_000_000;
43/// Default mnemonic for dev accounts
44pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
45
46/// The default IPC endpoint
47pub const DEFAULT_IPC_ENDPOINT: &str =
48    if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
49
50/// In anvil this is `1_000_000_000`, in 1e18 denomination. However,
51/// asset-hub-westend runtime sets it to `1_000_000`.
52pub const INITIAL_BASE_FEE: u128 = 1_000_000;
53
54const BANNER: &str = r"
55                             _   _
56                            (_) | |
57      __ _   _ __   __   __  _  | |
58     / _` | | '_ \  \ \ / / | | | |
59    | (_| | | | | |  \ V /  | | | |
60     \__,_| |_| |_|   \_/   |_| |_|
61";
62
63#[derive(Clone, Debug)]
64pub struct SubstrateNodeConfig {
65    shared_params: sc_cli::SharedParams,
66    rpc_params: sc_cli::RpcParams,
67    import_params: sc_cli::ImportParams,
68}
69
70impl SubstrateNodeConfig {
71    pub fn new(_anvil_config: &AnvilNodeConfig) -> Self {
72        let shared_params = sc_cli::SharedParams {
73            chain: None,
74            dev: true,
75            base_path: None,
76            log: vec![],
77            detailed_log_output: true,
78            disable_log_color: false,
79            enable_log_reloading: false,
80            tracing_targets: None,
81            tracing_receiver: sc_cli::TracingReceiver::Log,
82        };
83
84        let rpc_params = sc_cli::RpcParams {
85            rpc_external: false,
86            unsafe_rpc_external: false,
87            rpc_methods: sc_cli::RpcMethods::Auto,
88            rpc_rate_limit: None,
89            rpc_rate_limit_whitelisted_ips: vec![],
90            rpc_rate_limit_trust_proxy_headers: false,
91            rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB,
92            rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
93            rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN,
94            rpc_port: None,
95            experimental_rpc_endpoint: vec![],
96            rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS,
97            rpc_message_buffer_capacity_per_connection: RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
98            rpc_disable_batch_requests: false,
99            rpc_max_batch_request_len: None,
100            rpc_cors: None,
101        };
102
103        // Anvil node requires these cli params configured by default except the state_pruning and
104        // block_pruning params. They must be set to `DatabasePruningMode::Archive` because
105        // chain reversion RPCs must revert the state db for finalized blocks, which is a no
106        // operation when pruning is not configured as an archive for both blocks & state.
107        let import_params = sc_cli::ImportParams {
108            pruning_params: sc_cli::PruningParams {
109                state_pruning: Some(sc_cli::DatabasePruningMode::Archive),
110                blocks_pruning: sc_cli::DatabasePruningMode::Archive,
111            },
112            database_params: sc_cli::DatabaseParams { database: None, database_cache_size: None },
113            wasm_method: DEFAULT_WASM_EXECUTION_METHOD,
114            wasmtime_instantiation_strategy: DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
115            wasm_runtime_overrides: None,
116            execution_strategies: sc_cli::ExecutionStrategiesParams {
117                execution_syncing: None,
118                execution_import_block: None,
119                execution_block_construction: None,
120                execution_offchain_worker: None,
121                execution_other: None,
122                execution: None,
123            },
124            trie_cache_size: 1024 * 1024 * 1024,
125            warm_up_trie_cache: None,
126        };
127
128        Self { shared_params, rpc_params, import_params }
129    }
130
131    pub fn set_base_path(&mut self, base_path: Option<PathBuf>) {
132        self.shared_params.base_path = base_path;
133    }
134}
135
136impl SubstrateCliConfiguration for SubstrateNodeConfig {
137    fn shared_params(&self) -> &sc_cli::SharedParams {
138        &self.shared_params
139    }
140
141    fn import_params(&self) -> Option<&sc_cli::ImportParams> {
142        Some(&self.import_params)
143    }
144
145    fn network_params(&self) -> Option<&sc_cli::NetworkParams> {
146        None
147    }
148
149    fn keystore_params(&self) -> Option<&sc_cli::KeystoreParams> {
150        None
151    }
152
153    fn offchain_worker_params(&self) -> Option<&sc_cli::OffchainWorkerParams> {
154        None
155    }
156
157    fn node_name(&self) -> sc_cli::Result<String> {
158        Ok("anvil-substrate".to_string())
159    }
160
161    fn dev_key_seed(&self, _is_dev: bool) -> sc_cli::Result<Option<String>> {
162        Ok(Some("//Alice".into()))
163    }
164
165    fn telemetry_endpoints(
166        &self,
167        _chain_spec: &Box<dyn sc_service::ChainSpec>,
168    ) -> sc_cli::Result<Option<sc_service::config::TelemetryEndpoints>> {
169        Ok(None)
170    }
171
172    fn role(&self, _is_dev: bool) -> sc_cli::Result<sc_service::Role> {
173        Ok(sc_service::Role::Authority)
174    }
175
176    fn force_authoring(&self) -> sc_cli::Result<bool> {
177        Ok(true)
178    }
179
180    fn prometheus_config(
181        &self,
182        _default_listen_port: u16,
183        _chain_spec: &Box<dyn sc_service::ChainSpec>,
184    ) -> sc_cli::Result<Option<sc_service::config::PrometheusConfig>> {
185        Ok(None)
186    }
187
188    fn disable_grandpa(&self) -> sc_cli::Result<bool> {
189        Ok(true)
190    }
191
192    fn rpc_max_connections(&self) -> sc_cli::Result<u32> {
193        Ok(self.rpc_params.rpc_max_connections)
194    }
195
196    fn rpc_cors(&self, _is_dev: bool) -> sc_cli::Result<Option<Vec<String>>> {
197        Ok(self.rpc_params.rpc_cors.clone().unwrap_or(Cors::All).into())
198    }
199
200    fn rpc_addr(
201        &self,
202        default_listen_port: u16,
203    ) -> sc_cli::Result<Option<Vec<sc_cli::RpcEndpoint>>> {
204        self.rpc_params.rpc_addr(true, true, default_listen_port)
205    }
206
207    fn rpc_methods(&self) -> sc_cli::Result<sc_service::RpcMethods> {
208        Ok(self.rpc_params.rpc_methods.into())
209    }
210
211    fn rpc_max_request_size(&self) -> sc_cli::Result<u32> {
212        Ok(self.rpc_params.rpc_max_request_size)
213    }
214
215    fn rpc_max_response_size(&self) -> sc_cli::Result<u32> {
216        Ok(self.rpc_params.rpc_max_response_size)
217    }
218
219    fn rpc_max_subscriptions_per_connection(&self) -> sc_cli::Result<u32> {
220        Ok(self.rpc_params.rpc_max_subscriptions_per_connection)
221    }
222
223    fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result<u32> {
224        Ok(self.rpc_params.rpc_message_buffer_capacity_per_connection)
225    }
226
227    fn rpc_batch_config(&self) -> sc_cli::Result<sc_service::config::RpcBatchRequestConfig> {
228        self.rpc_params.rpc_batch_config()
229    }
230
231    fn rpc_rate_limit(&self) -> sc_cli::Result<Option<NonZeroU32>> {
232        Ok(self.rpc_params.rpc_rate_limit)
233    }
234
235    fn rpc_rate_limit_whitelisted_ips(&self) -> sc_cli::Result<Vec<sc_service::config::IpNetwork>> {
236        Ok(self.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
237    }
238
239    fn rpc_rate_limit_trust_proxy_headers(&self) -> sc_cli::Result<bool> {
240        Ok(self.rpc_params.rpc_rate_limit_trust_proxy_headers)
241    }
242
243    fn transaction_pool(
244        &self,
245        _is_dev: bool,
246    ) -> sc_cli::Result<sc_service::TransactionPoolOptions> {
247        Ok(sc_service::TransactionPoolOptions::new_with_params(
248            8192,
249            20480 * 1024,
250            None,
251            // Replace this back with TransactionPoolType::ForkAware
252            // when we start using polkadot-sdk::master
253            sc_cli::TransactionPoolType::SingleState.into(),
254            true,
255        ))
256    }
257
258    fn base_path(&self) -> sc_cli::Result<Option<sc_service::BasePath>> {
259        self.shared_params().base_path()
260    }
261}
262
263/// Configurations of the EVM node
264#[derive(Clone, Debug)]
265pub struct AnvilNodeConfig {
266    /// Chain ID of the EVM chain
267    pub chain_id: Option<u64>,
268    /// Default base fee
269    pub base_fee: Option<u128>,
270    /// Signer accounts that will be initialised with `genesis_balance` in the genesis block
271    pub genesis_accounts: Vec<Keypair>,
272    /// Native token balance of every genesis account in the genesis block
273    pub genesis_balance: U256,
274    /// Genesis block timestamp
275    pub genesis_timestamp: Option<u64>,
276    /// Genesis block number
277    pub genesis_block_number: Option<u64>,
278    /// Signer accounts that can sign messages/transactions from the EVM node
279    pub signer_accounts: Vec<Keypair>,
280    /// Configured block time for the EVM chain. Use `None` to mine a new block for every tx
281    pub block_time: Option<Duration>,
282    /// Disable auto, interval mining mode uns use `MiningMode::None` instead
283    pub no_mining: bool,
284    /// Enables auto and interval mining mode
285    pub mixed_mining: bool,
286    /// port to use for the server
287    pub port: u16,
288    /// The generator used to generate the dev accounts
289    pub account_generator: Option<AccountGenerator>,
290    /// whether to enable tracing
291    pub enable_tracing: bool,
292    /// How to configure the server
293    pub server_config: ServerConfig,
294    /// The host the server will listen on
295    pub host: Vec<IpAddr>,
296    /// Filename to write anvil output as json
297    pub config_out: Option<PathBuf>,
298    /// The genesis to use to initialize the node
299    pub genesis: Option<Genesis>,
300    /// The ipc path
301    pub ipc_path: Option<Option<String>>,
302    /// Enable auto impersonation of accounts on startup
303    pub enable_auto_impersonate: bool,
304    /// Max number of blocks to keep in memory for the eth revive rpc
305    pub revive_rpc_block_limit: Option<usize>,
306    /// Do not print log messages.
307    pub silent: bool,
308}
309
310impl AnvilNodeConfig {
311    fn as_string(&self) -> String {
312        let mut s: String = String::new();
313        let _ = write!(s, "\n{}", BANNER.green());
314        let _ = write!(s, "\n    {VERSION_MESSAGE}");
315        let _ = write!(s, "\n    {}", "https://github.com/paritytech/foundry-polkadot".green());
316
317        let _ = write!(
318            s,
319            r#"
320
321Available Accounts
322==================
323"#
324        );
325        let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
326        for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
327            write!(
328                s,
329                "\n({idx}) {} ({balance} ETH)",
330                Address::from(ReviveAddress::new(Account::from(wallet.clone()).address()))
331            )
332            .unwrap();
333        }
334
335        let _ = write!(
336            s,
337            r#"
338
339Private Keys
340==================
341"#
342        );
343
344        for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
345            let hex = hex::encode(wallet.secret_key());
346            let _ = write!(s, "\n({idx}) 0x{hex}");
347        }
348
349        if let Some(ref rng_gen) = self.account_generator {
350            let _ = write!(
351                s,
352                r#"
353
354Wallet
355==================
356Mnemonic:          {}
357Derivation path:   {}
358"#,
359                rng_gen.phrase,
360                rng_gen.get_derivation_path()
361            );
362        }
363
364        let _ = write!(
365            s,
366            r#"
367
368Chain ID
369==================
370
371{}
372"#,
373            self.get_chain_id().green()
374        );
375
376        let _ = write!(
377            s,
378            r#"
379Base Fee
380==================
381
382{}
383"#,
384            self.get_base_fee().green()
385        );
386
387        let _ = write!(
388            s,
389            r#"
390Genesis Timestamp
391==================
392
393{}
394"#,
395            self.get_genesis_timestamp().green()
396        );
397
398        let _ = write!(
399            s,
400            r#"
401Genesis Number
402==================
403
404{}
405"#,
406            self.get_genesis_number().green()
407        );
408
409        s
410    }
411
412    fn as_json(&self) -> Value {
413        let mut wallet_description = HashMap::new();
414        let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
415        let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
416
417        for wallet in &self.genesis_accounts {
418            available_accounts.push(format!("{:?}", Account::from(wallet.clone()).address()));
419            private_keys.push(hex::encode_prefixed(wallet.secret_key()));
420        }
421
422        if let Some(ref rng_gen) = self.account_generator {
423            let phrase = rng_gen.get_phrase().to_string();
424            let derivation_path = rng_gen.get_derivation_path().to_string();
425
426            wallet_description.insert("derivation_path".to_string(), derivation_path);
427            wallet_description.insert("mnemonic".to_string(), phrase);
428        };
429
430        json!({
431          "available_accounts": available_accounts,
432          "private_keys": private_keys,
433          "wallet": wallet_description,
434          "base_fee": format!("{}", self.get_base_fee()),
435          "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
436        })
437    }
438
439    pub fn test_config() -> Self {
440        let mut anvil_node_config = Self {
441            port: 0,
442            no_mining: true,
443            mixed_mining: false,
444            enable_tracing: false,
445            silent: true,
446            genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(10000u64)),
447            ..Default::default()
448        };
449        let dev_accounts =
450            vec![subxt_signer::eth::dev::alith(), subxt_signer::eth::dev::baltathar()];
451        anvil_node_config.genesis_accounts.extend(dev_accounts.clone());
452        anvil_node_config.signer_accounts.extend(dev_accounts);
453        anvil_node_config
454    }
455}
456
457impl Default for AnvilNodeConfig {
458    fn default() -> Self {
459        // generate some random wallets
460        let genesis_accounts = keypairs_from_private_keys(
461            &AccountGenerator::new(10)
462                .phrase(DEFAULT_MNEMONIC)
463                .rng_gen()
464                .expect("Invalid mnemonic."),
465        )
466        .expect("Invalid keys");
467
468        Self {
469            chain_id: None,
470            signer_accounts: genesis_accounts.clone(),
471            genesis_timestamp: None,
472            genesis_block_number: None,
473            genesis_accounts,
474            // 100ETH default balance
475            genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
476            block_time: None,
477            no_mining: false,
478            mixed_mining: false,
479            port: NODE_PORT,
480            account_generator: None,
481            base_fee: None,
482            enable_tracing: true,
483            enable_auto_impersonate: false,
484            revive_rpc_block_limit: None,
485            server_config: Default::default(),
486            host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
487            config_out: None,
488            genesis: None,
489            ipc_path: None,
490            silent: false,
491        }
492    }
493}
494
495impl AnvilNodeConfig {
496    /// Returns the base fee to use
497    pub fn get_base_fee(&self) -> u128 {
498        self.base_fee
499            .or_else(|| {
500                self.genesis.as_ref().and_then(|g| {
501                    // The base fee received via CLI will be transformed to 1e-12.
502                    g.base_fee_per_gas
503                })
504            })
505            .unwrap_or(INITIAL_BASE_FEE)
506    }
507
508    /// Sets the chain ID
509    #[must_use]
510    pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
511        self.set_chain_id(chain_id);
512        self
513    }
514
515    /// Returns the chain ID to use
516    pub fn get_chain_id(&self) -> u64 {
517        self.chain_id
518            .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
519            .unwrap_or(CHAIN_ID)
520    }
521
522    /// Sets the chain id
523    pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
524        self.chain_id = chain_id.map(Into::into);
525    }
526
527    /// Sets max number of blocks to keep in memory for the eth revive rpc
528    #[must_use]
529    pub fn with_revive_rpc_block_limit<U: Into<usize>>(
530        mut self,
531        revive_rpc_block_limit: Option<U>,
532    ) -> Self {
533        self.revive_rpc_block_limit = revive_rpc_block_limit.map(Into::into);
534        self
535    }
536
537    /// Sets the base fee
538    #[must_use]
539    pub fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
540        self.base_fee = base_fee.map(|bf| bf.into());
541        self
542    }
543
544    /// Sets the init genesis (genesis.json)
545    #[must_use]
546    pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
547        self.genesis = genesis;
548        self
549    }
550
551    /// Returns the genesis timestamp to use
552    pub fn get_genesis_timestamp(&self) -> u64 {
553        self.genesis_timestamp
554            .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
555            .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
556    }
557
558    /// Sets the genesis timestamp
559    #[must_use]
560    pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
561        if let Some(timestamp) = timestamp {
562            self.genesis_timestamp = Some(timestamp.into());
563        }
564        self
565    }
566
567    /// Sets the genesis number
568    #[must_use]
569    pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
570        if let Some(number) = number {
571            self.genesis_block_number = Some(number.into());
572        }
573        self
574    }
575
576    /// Returns the genesis number
577    pub fn get_genesis_number(&self) -> u64 {
578        self.genesis_block_number
579            .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
580            .unwrap_or(0)
581    }
582
583    /// Sets the genesis accounts
584    #[must_use]
585    pub fn with_genesis_accounts(mut self, accounts: Vec<Keypair>) -> Self {
586        self.genesis_accounts = accounts;
587        self
588    }
589
590    /// Sets the signer accounts
591    #[must_use]
592    pub fn with_signer_accounts(mut self, accounts: Vec<Keypair>) -> Self {
593        self.signer_accounts = accounts;
594        self
595    }
596
597    /// Sets both the genesis accounts and the signer accounts
598    /// so that `genesis_accounts == accounts`
599    pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
600        let accounts = generator.rng_gen()?;
601        self.account_generator = Some(generator);
602        let accounts = keypairs_from_private_keys(&accounts)?;
603        Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
604    }
605
606    /// Sets the balance of the genesis accounts in the genesis block
607    #[must_use]
608    pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
609        self.genesis_balance = balance.into();
610        self
611    }
612
613    /// Sets the block time to automine blocks
614    #[must_use]
615    pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
616        self.block_time = block_time.map(Into::into);
617        self
618    }
619
620    #[must_use]
621    pub fn with_mixed_mining<D: Into<Duration>>(
622        mut self,
623        mixed_mining: bool,
624        block_time: Option<D>,
625    ) -> Self {
626        self.block_time = block_time.map(Into::into);
627        self.mixed_mining = mixed_mining;
628        self
629    }
630
631    /// If set to `true` auto mining will be disabled
632    #[must_use]
633    pub fn with_no_mining(mut self, no_mining: bool) -> Self {
634        self.no_mining = no_mining;
635        self
636    }
637
638    /// Sets the port to use
639    #[must_use]
640    pub fn with_port(mut self, port: u16) -> Self {
641        self.port = port;
642        self
643    }
644
645    /// Sets the ipc path to use
646    ///
647    /// Note: this is a double Option for
648    ///     - `None` -> no ipc
649    ///     - `Some(None)` -> use default path
650    ///     - `Some(Some(path))` -> use custom path
651    #[must_use]
652    pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
653        self.ipc_path = ipc_path;
654        self
655    }
656
657    /// Sets the file path to write the Anvil node's config info to.
658    #[must_use]
659    pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
660        self.config_out = config_out;
661        self
662    }
663
664    /// Sets whether to enable tracing
665    #[must_use]
666    pub fn with_tracing(mut self, enable_tracing: bool) -> Self {
667        self.enable_tracing = enable_tracing;
668        self
669    }
670
671    /// Sets whether to enable autoImpersonate
672    #[must_use]
673    pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
674        self.enable_auto_impersonate = enable_auto_impersonate;
675        self
676    }
677
678    #[must_use]
679    pub fn with_server_config(mut self, config: ServerConfig) -> Self {
680        self.server_config = config;
681        self
682    }
683
684    /// Sets the host the server will listen on
685    #[must_use]
686    pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
687        self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
688        self
689    }
690
691    /// Returns the ipc path for the ipc endpoint if any
692    pub fn get_ipc_path(&self) -> Option<String> {
693        match &self.ipc_path {
694            Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
695            None => None,
696        }
697    }
698
699    /// Prints the config info
700    pub fn print(&self) -> Result<()> {
701        if let Some(path) = &self.config_out {
702            let file = io::BufWriter::new(
703                File::create(path).wrap_err("unable to create anvil config description file")?,
704            );
705            let value = self.as_json();
706            serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?;
707        }
708        if !self.silent {
709            sh_println!("{}", self.as_string())?;
710        }
711        Ok(())
712    }
713
714    /// Makes the node silent to not emit anything on stdout
715    #[must_use]
716    pub fn silent(self) -> Self {
717        self.set_silent(true)
718    }
719
720    #[must_use]
721    pub fn set_silent(mut self, silent: bool) -> Self {
722        self.silent = silent;
723        self
724    }
725}
726
727/// Can create dev accounts
728#[derive(Clone, Debug)]
729pub struct AccountGenerator {
730    amount: usize,
731    phrase: String,
732    derivation_path: Option<String>,
733}
734
735impl AccountGenerator {
736    pub fn new(amount: usize) -> Self {
737        Self {
738            amount,
739            phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
740            derivation_path: None,
741        }
742    }
743
744    #[must_use]
745    pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
746        self.phrase = phrase.into();
747        self
748    }
749
750    fn get_phrase(&self) -> &str {
751        &self.phrase
752    }
753
754    #[must_use]
755    pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
756        let mut derivation_path = derivation_path.into();
757        if !derivation_path.ends_with('/') {
758            derivation_path.push('/');
759        }
760        self.derivation_path = Some(derivation_path);
761        self
762    }
763
764    fn get_derivation_path(&self) -> &str {
765        self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
766    }
767}
768
769impl AccountGenerator {
770    pub fn rng_gen(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
771        let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
772
773        // use the derivation path
774        let derivation_path = self.get_derivation_path();
775
776        let mut wallets = Vec::with_capacity(self.amount);
777        for idx in 0..self.amount {
778            let builder =
779                builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
780            let wallet = builder.build()?;
781            wallets.push(wallet)
782        }
783        Ok(wallets)
784    }
785}
786
787fn keypairs_from_private_keys(
788    accounts: &[PrivateKeySigner],
789) -> Result<Vec<Keypair>, subxt_signer::eth::Error> {
790    accounts
791        .iter()
792        .map(|signer| {
793            let key = Keypair::from_secret_key(signer.credential().to_bytes().into())?;
794            Ok(key)
795        })
796        .collect()
797}