From d8411fd8e2d23319d2833bdd3f28a018021ced8c Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Jan 2026 12:45:06 -0500 Subject: [PATCH 1/2] feat(client): cli crate --- Cargo.lock | 18 ++++++++++ Cargo.toml | 2 ++ crates/client/cli/Cargo.toml | 20 +++++++++++ crates/client/cli/README.md | 34 ++++++++++++++++++ crates/client/cli/src/l1.rs | 47 ++++++++++++++++++++++++ crates/client/cli/src/l2.rs | 69 ++++++++++++++++++++++++++++++++++++ crates/client/cli/src/lib.rs | 12 +++++++ 7 files changed, 202 insertions(+) create mode 100644 crates/client/cli/Cargo.toml create mode 100644 crates/client/cli/README.md create mode 100644 crates/client/cli/src/l1.rs create mode 100644 crates/client/cli/src/l2.rs create mode 100644 crates/client/cli/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c52e0c3e..e705cd01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1926,6 +1926,15 @@ dependencies = [ "tracing-subscriber 0.3.22", ] +[[package]] +name = "base-client-cli" +version = "0.2.1" +dependencies = [ + "base-jwt", + "clap", + "url", +] + [[package]] name = "base-client-node" version = "0.2.1" @@ -2064,6 +2073,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "base-jwt" +version = "0.2.1" +dependencies = [ + "alloy-primitives 1.5.2", + "alloy-rpc-types-engine", + "thiserror 2.0.17", +] + [[package]] name = "base-metering" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index e727a771..9b912aef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,9 +57,11 @@ base-access-lists = { path = "crates/shared/access-lists" } base-bundles = { path = "crates/shared/bundles" } base-cli-utils = { path = "crates/shared/cli-utils" } base-flashtypes = { path = "crates/shared/flashtypes" } +base-jwt = { path = "crates/shared/jwt" } base-primitives = { path = "crates/shared/primitives" } base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client +base-client-cli = { path = "crates/client/cli" } base-client-node = { path = "crates/client/node" } base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } diff --git a/crates/client/cli/Cargo.toml b/crates/client/cli/Cargo.toml new file mode 100644 index 00000000..80ba7947 --- /dev/null +++ b/crates/client/cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "base-client-cli" +description = "CLI argument types for Base node consensus clients" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# Shared +base-jwt.workspace = true + +# CLI +url.workspace = true +clap.workspace = true diff --git a/crates/client/cli/README.md b/crates/client/cli/README.md new file mode 100644 index 00000000..6c318034 --- /dev/null +++ b/crates/client/cli/README.md @@ -0,0 +1,34 @@ +# `base-client-cli` + +CLI argument types for Base node consensus clients. + +## Overview + +This crate provides reusable CLI argument types for configuring Base node consensus clients: + +- **`L1ClientArgs`**: L1 execution client RPC configuration +- **`L2ClientArgs`**: L2 engine API configuration with JWT handling + +## Usage + +```toml +[dependencies] +base-client-cli = { workspace = true } +``` + +```rust +use base_client_cli::{L1ClientArgs, L2ClientArgs}; +use clap::Parser; + +#[derive(Parser)] +struct Cli { + #[clap(flatten)] + l1_args: L1ClientArgs, + #[clap(flatten)] + l2_args: L2ClientArgs, +} +``` + +## License + +Licensed under the MIT License. diff --git a/crates/client/cli/src/l1.rs b/crates/client/cli/src/l1.rs new file mode 100644 index 00000000..b4a36d27 --- /dev/null +++ b/crates/client/cli/src/l1.rs @@ -0,0 +1,47 @@ +//! L1 Client CLI arguments. + +use url::Url; + +const DEFAULT_L1_TRUST_RPC: bool = true; + +/// L1 client arguments. +#[derive(Clone, Debug, clap::Args)] +pub struct L1ClientArgs { + /// URL of the L1 execution client RPC API. + #[arg(long, visible_alias = "l1", env = "KONA_NODE_L1_ETH_RPC")] + pub l1_eth_rpc: Url, + /// Whether to trust the L1 RPC. + /// If false, block hash verification is performed for all retrieved blocks. + #[arg( + long, + visible_alias = "l1.trust-rpc", + env = "KONA_NODE_L1_TRUST_RPC", + default_value_t = DEFAULT_L1_TRUST_RPC + )] + pub l1_trust_rpc: bool, + /// URL of the L1 beacon API. + #[arg(long, visible_alias = "l1.beacon", env = "KONA_NODE_L1_BEACON")] + pub l1_beacon: Url, + /// Duration in seconds of an L1 slot. + /// + /// This is an optional argument that can be used to use a fixed slot duration for l1 blocks + /// and bypass the initial beacon spec fetch. This is useful for testing purposes when the + /// l1-beacon spec endpoint is not available (with anvil for example). + #[arg( + long, + visible_alias = "l1.slot-duration-override", + env = "KONA_NODE_L1_SLOT_DURATION_OVERRIDE" + )] + pub l1_slot_duration_override: Option, +} + +impl Default for L1ClientArgs { + fn default() -> Self { + Self { + l1_eth_rpc: Url::parse("http://localhost:8545").unwrap(), + l1_trust_rpc: DEFAULT_L1_TRUST_RPC, + l1_beacon: Url::parse("http://localhost:5052").unwrap(), + l1_slot_duration_override: None, + } + } +} diff --git a/crates/client/cli/src/l2.rs b/crates/client/cli/src/l2.rs new file mode 100644 index 00000000..f9ba1623 --- /dev/null +++ b/crates/client/cli/src/l2.rs @@ -0,0 +1,69 @@ +//! L2 Client CLI arguments with JWT handling. + +use std::path::PathBuf; + +use base_jwt::{JwtError, JwtSecret, resolve_jwt_secret}; +use url::Url; + +const DEFAULT_L2_ENGINE_TIMEOUT: u64 = 30_000; +const DEFAULT_L2_TRUST_RPC: bool = true; + +/// L2 client arguments. +#[derive(Clone, Debug, clap::Args)] +pub struct L2ClientArgs { + /// URI of the engine API endpoint of an L2 execution client. + #[arg(long, visible_alias = "l2", env = "KONA_NODE_L2_ENGINE_RPC")] + pub l2_engine_rpc: Url, + /// JWT secret for the auth-rpc endpoint of the execution client. + /// This MUST be a valid path to a file containing the hex-encoded JWT secret. + #[arg(long, visible_alias = "l2.jwt-secret", env = "KONA_NODE_L2_ENGINE_AUTH")] + pub l2_engine_jwt_secret: Option, + /// Hex encoded JWT secret to use for the authenticated engine-API RPC server. + /// This MUST be a valid hex-encoded JWT secret of 64 digits. + #[arg(long, visible_alias = "l2.jwt-secret-encoded", env = "KONA_NODE_L2_ENGINE_AUTH_ENCODED")] + pub l2_engine_jwt_encoded: Option, + /// Timeout for http calls in milliseconds. + #[arg( + long, + visible_alias = "l2.timeout", + env = "KONA_NODE_L2_ENGINE_TIMEOUT", + default_value_t = DEFAULT_L2_ENGINE_TIMEOUT + )] + pub l2_engine_timeout: u64, + /// If false, block hash verification is performed for all retrieved blocks. + #[arg( + long, + visible_alias = "l2.trust-rpc", + env = "KONA_NODE_L2_TRUST_RPC", + default_value_t = DEFAULT_L2_TRUST_RPC + )] + pub l2_trust_rpc: bool, +} + +impl Default for L2ClientArgs { + fn default() -> Self { + Self { + l2_engine_rpc: Url::parse("http://localhost:8551").unwrap(), + l2_engine_jwt_secret: None, + l2_engine_jwt_encoded: None, + l2_engine_timeout: DEFAULT_L2_ENGINE_TIMEOUT, + l2_trust_rpc: DEFAULT_L2_TRUST_RPC, + } + } +} + +impl L2ClientArgs { + /// Returns the L2 JWT secret for the engine API. + /// + /// Resolution order: + /// 1. Read from file path if `l2_engine_jwt_secret` is set + /// 2. Use encoded secret if `l2_engine_jwt_encoded` is set + /// 3. Fall back to default JWT file `l2_jwt.hex` + pub fn jwt_secret(&self) -> Result { + resolve_jwt_secret( + self.l2_engine_jwt_secret.as_deref(), + self.l2_engine_jwt_encoded, + "l2_jwt.hex", + ) + } +} diff --git a/crates/client/cli/src/lib.rs b/crates/client/cli/src/lib.rs new file mode 100644 index 00000000..c680b88a --- /dev/null +++ b/crates/client/cli/src/lib.rs @@ -0,0 +1,12 @@ +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +pub use base_jwt::{JwtError, JwtSecret, default_jwt_secret}; + +mod l1; +pub use l1::L1ClientArgs; + +mod l2; +pub use l2::L2ClientArgs; From a07bf343a15655085480f9f4639272c788d822b8 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Jan 2026 13:01:54 -0500 Subject: [PATCH 2/2] fix(cli): rm jwt dep --- Cargo.lock | 11 +---------- Cargo.toml | 1 - crates/client/cli/Cargo.toml | 6 ++---- crates/client/cli/src/l2.rs | 18 +----------------- crates/client/cli/src/lib.rs | 2 -- 5 files changed, 4 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e705cd01..0db1501f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1930,7 +1930,7 @@ dependencies = [ name = "base-client-cli" version = "0.2.1" dependencies = [ - "base-jwt", + "alloy-rpc-types-engine", "clap", "url", ] @@ -2073,15 +2073,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "base-jwt" -version = "0.2.1" -dependencies = [ - "alloy-primitives 1.5.2", - "alloy-rpc-types-engine", - "thiserror 2.0.17", -] - [[package]] name = "base-metering" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 9b912aef..e626e08b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,6 @@ base-access-lists = { path = "crates/shared/access-lists" } base-bundles = { path = "crates/shared/bundles" } base-cli-utils = { path = "crates/shared/cli-utils" } base-flashtypes = { path = "crates/shared/flashtypes" } -base-jwt = { path = "crates/shared/jwt" } base-primitives = { path = "crates/shared/primitives" } base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client diff --git a/crates/client/cli/Cargo.toml b/crates/client/cli/Cargo.toml index 80ba7947..463d0954 100644 --- a/crates/client/cli/Cargo.toml +++ b/crates/client/cli/Cargo.toml @@ -12,9 +12,7 @@ repository.workspace = true workspace = true [dependencies] -# Shared -base-jwt.workspace = true - -# CLI +# General url.workspace = true clap.workspace = true +alloy-rpc-types-engine.workspace = true diff --git a/crates/client/cli/src/l2.rs b/crates/client/cli/src/l2.rs index f9ba1623..ed0198d8 100644 --- a/crates/client/cli/src/l2.rs +++ b/crates/client/cli/src/l2.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; -use base_jwt::{JwtError, JwtSecret, resolve_jwt_secret}; +use alloy_rpc_types_engine::JwtSecret; use url::Url; const DEFAULT_L2_ENGINE_TIMEOUT: u64 = 30_000; @@ -51,19 +51,3 @@ impl Default for L2ClientArgs { } } } - -impl L2ClientArgs { - /// Returns the L2 JWT secret for the engine API. - /// - /// Resolution order: - /// 1. Read from file path if `l2_engine_jwt_secret` is set - /// 2. Use encoded secret if `l2_engine_jwt_encoded` is set - /// 3. Fall back to default JWT file `l2_jwt.hex` - pub fn jwt_secret(&self) -> Result { - resolve_jwt_secret( - self.l2_engine_jwt_secret.as_deref(), - self.l2_engine_jwt_encoded, - "l2_jwt.hex", - ) - } -} diff --git a/crates/client/cli/src/lib.rs b/crates/client/cli/src/lib.rs index c680b88a..ad91a070 100644 --- a/crates/client/cli/src/lib.rs +++ b/crates/client/cli/src/lib.rs @@ -3,8 +3,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -pub use base_jwt::{JwtError, JwtSecret, default_jwt_secret}; - mod l1; pub use l1::L1ClientArgs;