1use alloy::{
2 eips::BlockId,
3 network::EthereumWallet,
4 primitives::{utils::parse_ether, Address, U256},
5 signers::local::{coins_bip39::English, MnemonicBuilder},
6};
7use anyhow::{bail, Result};
8use clap::{Args as ClapArgs, Parser, Subcommand};
9use clap_serde_derive::ClapSerde;
10use demo::DelegationConfig;
11use espresso_contract_deployer::provider::connect_ledger;
12pub(crate) use hotshot_types::{light_client::StateSignKey, signature_key::BLSPrivKey};
13pub(crate) use jf_signature::bls_over_bn254::KeyPair as BLSKeyPair;
14use metadata::MetadataUri;
15use parse::Commission;
16use sequencer_utils::logging;
17use serde::{Deserialize, Serialize};
18use url::Url;
19
20pub mod claim;
21pub mod delegation;
22pub mod demo;
23pub mod funding;
24pub mod info;
25pub mod l1;
26pub mod metadata;
27pub mod output;
28pub mod parse;
29pub mod receipt;
30pub mod registration;
31pub mod signature;
32
33#[cfg(feature = "testing")]
34pub mod deploy;
35
36pub const DEV_MNEMONIC: &str = "test test test test test test test test test test test junk";
37
38#[derive(ClapSerde, Clone, Debug, Deserialize, Serialize)]
40#[command(version, about, long_about = None)]
41pub struct Config {
42 #[clap(long, env = "L1_PROVIDER")]
44 #[default(Url::parse("http://localhost:8545").unwrap())]
45 pub rpc_url: Url,
46
47 #[clap(long, env = "ESP_TOKEN_ADDRESS")]
51 pub token_address: Option<Address>,
52
53 #[clap(long, env = "STAKE_TABLE_ADDRESS")]
55 pub stake_table_address: Address,
56
57 #[clap(long, env = "ESPRESSO_URL")]
59 pub espresso_url: Option<Url>,
60
61 #[clap(flatten)]
62 pub signer: SignerConfig,
63
64 #[clap(flatten)]
65 #[serde(skip)]
66 pub logging: logging::Config,
67
68 #[command(subcommand)]
69 #[serde(skip)]
70 pub commands: Commands,
71}
72
73#[derive(ClapSerde, Parser, Clone, Debug, Deserialize, Serialize)]
74pub struct SignerConfig {
75 #[clap(long, env = "MNEMONIC")]
77 pub mnemonic: Option<String>,
78
79 #[clap(long, env = "ACCOUNT_INDEX")]
81 #[default(Some(0))]
82 pub account_index: Option<u32>,
83
84 #[clap(long, env = "USE_LEDGER")]
89 pub ledger: bool,
90}
91
92#[derive(Clone, Debug)]
93pub enum ValidSignerConfig {
94 Mnemonic {
95 mnemonic: String,
96 account_index: u32,
97 },
98 Ledger {
99 account_index: usize,
100 },
101}
102
103impl TryFrom<SignerConfig> for ValidSignerConfig {
104 type Error = anyhow::Error;
105
106 fn try_from(config: SignerConfig) -> Result<Self> {
107 let account_index = config
108 .account_index
109 .ok_or_else(|| anyhow::anyhow!("Account index must be provided"))?;
110 if let Some(mnemonic) = config.mnemonic {
111 Ok(ValidSignerConfig::Mnemonic {
112 mnemonic,
113 account_index,
114 })
115 } else if config.ledger {
116 Ok(ValidSignerConfig::Ledger {
117 account_index: account_index as usize,
118 })
119 } else {
120 bail!("Either mnemonic or --ledger flag must be provided")
121 }
122 }
123}
124
125impl ValidSignerConfig {
126 pub async fn wallet(&self) -> Result<(EthereumWallet, Address)> {
127 match self {
128 ValidSignerConfig::Mnemonic {
129 mnemonic,
130 account_index,
131 } => {
132 let signer = MnemonicBuilder::<English>::default()
133 .phrase(mnemonic)
134 .index(*account_index)?
135 .build()?;
136 let account = signer.address();
137 let wallet = EthereumWallet::from(signer);
138 Ok((wallet, account))
139 },
140 ValidSignerConfig::Ledger { account_index } => {
141 let signer = connect_ledger(*account_index).await?;
142 let account = signer.get_address().await?;
143 let wallet = EthereumWallet::from(signer);
144 Ok((wallet, account))
145 },
146 }
147 }
148}
149
150#[derive(ClapArgs, Debug, Clone)]
151#[group(required = true, multiple = false)]
152pub struct MetadataUriArgs {
153 #[clap(long, env = "METADATA_URI")]
154 metadata_uri: Option<String>,
155
156 #[clap(long, env = "NO_METADATA_URI")]
157 no_metadata_uri: bool,
158}
159
160impl TryFrom<MetadataUriArgs> for MetadataUri {
161 type Error = anyhow::Error;
162
163 fn try_from(args: MetadataUriArgs) -> Result<Self> {
164 if args.no_metadata_uri {
165 Ok(MetadataUri::empty())
166 } else if let Some(uri_str) = args.metadata_uri {
167 uri_str.parse()
168 } else {
169 bail!("Either --metadata-uri or --no-metadata-uri must be provided")
170 }
171 }
172}
173
174impl Default for Commands {
175 fn default() -> Self {
176 Commands::StakeTable {
177 l1_block_number: None,
178 compact: false,
179 }
180 }
181}
182
183impl Config {
184 pub fn apply_env_var_overrides(self) -> Result<Self> {
185 let mut config = self.clone();
186 if self.stake_table_address == Address::ZERO {
187 let stake_table_env_var = "ESPRESSO_SEQUENCER_STAKE_TABLE_PROXY_ADDRESS";
188 if let Ok(stake_table_address) = std::env::var(stake_table_env_var) {
189 config.stake_table_address = stake_table_address.parse()?;
190 tracing::info!(
191 "Using stake table address from env {stake_table_env_var}: \
192 {stake_table_address}",
193 );
194 }
195 }
196 Ok(config)
197 }
198}
199
200#[derive(Subcommand, Debug, Clone)]
201pub enum Commands {
202 Version,
204 Config,
206 Init {
208 #[clap(long, env = "MNEMONIC", required_unless_present = "ledger")]
210 mnemonic: Option<String>,
211
212 #[clap(long, env = "ACCOUNT_INDEX", default_value_t = 0)]
214 account_index: u32,
215
216 #[clap(long, env = "LEDGER_INDEX", required_unless_present = "mnemonic")]
218 ledger: bool,
219 },
220 Purge {
222 #[clap(long)]
224 force: bool,
225 },
226 StakeTable {
228 #[clap(long)]
232 l1_block_number: Option<BlockId>,
233
234 #[clap(long)]
236 compact: bool,
237 },
238 Account,
240 RegisterValidator {
242 #[clap(flatten)]
243 signature_args: signature::NodeSignatureArgs,
244
245 #[clap(long, value_parser = parse::parse_commission, env = "COMMISSION")]
247 commission: Commission,
248
249 #[clap(flatten)]
250 metadata_uri_args: MetadataUriArgs,
251 },
252 UpdateConsensusKeys {
254 #[clap(flatten)]
255 signature_args: signature::NodeSignatureArgs,
256 },
257 DeregisterValidator {},
259 UpdateCommission {
261 #[clap(long, value_parser = parse::parse_commission, env = "NEW_COMMISSION")]
263 new_commission: Commission,
264 },
265 UpdateMetadataUri {
267 #[clap(flatten)]
268 metadata_uri_args: MetadataUriArgs,
269 },
270 Approve {
272 #[clap(long, value_parser = parse_ether)]
273 amount: U256,
274 },
275 Delegate {
277 #[clap(long)]
278 validator_address: Address,
279
280 #[clap(long, value_parser = parse_ether)]
281 amount: U256,
282 },
283 Undelegate {
285 #[clap(long)]
286 validator_address: Address,
287
288 #[clap(long, value_parser = parse_ether)]
289 amount: U256,
290 },
291 ClaimWithdrawal {
293 #[clap(long)]
294 validator_address: Address,
295 },
296 ClaimValidatorExit {
298 #[clap(long)]
299 validator_address: Address,
300 },
301 ClaimRewards,
303 UnclaimedRewards {
305 #[clap(long)]
307 address: Option<Address>,
308 },
309 TokenBalance {
311 #[clap(long)]
313 address: Option<Address>,
314 },
315 TokenAllowance {
317 #[clap(long)]
319 owner: Option<Address>,
320 },
321 Transfer {
323 #[clap(long)]
325 to: Address,
326
327 #[clap(long, value_parser = parse_ether)]
329 amount: U256,
330 },
331 StakeForDemo {
333 #[clap(long, default_value_t = 5)]
337 num_validators: u16,
338
339 #[clap(long, env = "NUM_DELEGATORS_PER_VALIDATOR", value_parser = clap::value_parser!(u64).range(..=100000))]
344 num_delegators_per_validator: Option<u64>,
345
346 #[arg(long, value_enum, env = "DELEGATION_CONFIG", default_value_t = DelegationConfig::default())]
347 delegation_config: DelegationConfig,
348 },
349 ExportNodeSignatures {
351 #[clap(long)]
353 address: Address,
354
355 #[clap(long, value_parser = parse::parse_bls_priv_key, env = "BLS_PRIVATE_KEY")]
357 consensus_private_key: BLSPrivKey,
358
359 #[clap(long, value_parser = parse::parse_state_priv_key, env = "SCHNORR_PRIVATE_KEY")]
361 state_private_key: StateSignKey,
362
363 #[clap(flatten)]
364 output_args: signature::OutputArgs,
365 },
366}