Repository: tangle-network/gadget (language: rust)

Issue:
[TASK] Remove structopt and just use clap v4
https://docs.rs/structopt/latest/structopt/#maintenance

As clap v3 is now out, and the structopt features are integrated into (almost as-is), structopt is now in maintenance mode: no new feature will be added.




Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7c36399a5..23a4c6178 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1379,17 +1379,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi 0.1.19",
- "libc",
- "winapi",
-]
-
 [[package]]
 name = "auth-git2"
 version = "0.5.5"
@@ -1936,6 +1925,7 @@ version = "0.1.1"
 dependencies = [
  "async-trait",
  "auto_impl",
+ "clap",
  "color-eyre",
  "failure",
  "futures",
@@ -1949,7 +1939,6 @@ dependencies = [
  "serde",
  "sha2 0.10.8",
  "sp-core",
- "structopt",
  "tangle-subxt",
  "tokio",
  "toml",
@@ -2183,7 +2172,7 @@ dependencies = [
  "anstyle",
  "anyhow",
  "auth-git2",
- "clap 4.5.20",
+ "clap",
  "console",
  "dialoguer",
  "env_logger",
@@ -2237,7 +2226,7 @@ dependencies = [
  "alloy-signer-local",
  "cargo-generate",
  "cargo_metadata",
- "clap 4.5.20",
+ "clap",
  "clap-cargo",
  "color-eyre",
  "escargot",
@@ -2402,21 +2391,6 @@ dependencies = [
  "libloading",
 ]
 
-[[package]]
-name = "clap"
-version = "2.34.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
-dependencies = [
- "ansi_term",
- "atty",
- "bitflags 1.3.2",
- "strsim 0.8.0",
- "textwrap",
- "unicode-width",
- "vec_map",
-]
-
 [[package]]
 name = "clap"
 version = "4.5.20"
@@ -2434,7 +2408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23b2ea69cefa96b848b73ad516ad1d59a195cdf9263087d977f648a818c8b43e"
 dependencies = [
  "anstyle",
- "clap 4.5.20",
+ "clap",
 ]
 
 [[package]]
@@ -4739,6 +4713,7 @@ name = "gadget-io"
 version = "0.0.4"
 dependencies = [
  "cfg-if 1.0.0",
+ "clap",
  "hex",
  "js-sys",
  "multiaddr",
@@ -4750,7 +4725,6 @@ dependencies = [
  "sp-application-crypto",
  "sp-core",
  "sp-keystore",
- "structopt",
  "thiserror",
  "tokio",
  "tracing",
@@ -4784,6 +4758,7 @@ dependencies = [
  "backon",
  "bincode",
  "bollard",
+ "clap",
  "ed25519-zebra 4.0.3",
  "eigensdk",
  "elliptic-curve",
@@ -4814,7 +4789,6 @@ dependencies = [
  "sp-core",
  "sp-io",
  "sqlx",
- "structopt",
  "subxt",
  "subxt-core",
  "subxt-signer",
@@ -5344,15 +5318,6 @@ dependencies = [
  "hashbrown 0.14.5",
 ]
 
-[[package]]
-name = "heck"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
-dependencies = [
- "unicode-segmentation",
-]
-
 [[package]]
 name = "heck"
 version = "0.4.1"
@@ -5368,15 +5333,6 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
-[[package]]
-name = "hermit-abi"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "hermit-abi"
 version = "0.3.9"
@@ -5944,7 +5900,6 @@ dependencies = [
  "parking_lot 0.12.3",
  "serde_json",
  "sp-core",
- "structopt",
  "subxt-signer",
  "tokio",
  "tokio-util 0.7.12",
@@ -5976,6 +5931,7 @@ dependencies = [
  "async-trait",
  "bip39",
  "blueprint-test-utils",
+ "clap",
  "color-eyre",
  "ed25519-zebra 4.0.3",
  "eigensdk",
@@ -5993,7 +5949,6 @@ dependencies = [
  "serde",
  "serde_json",
  "sp-core",
- "structopt",
  "subxt-signer",
  "thiserror",
  "tokio",
@@ -8567,7 +8522,6 @@ dependencies = [
  "reqwest 0.12.8",
  "serde_json",
  "sp-core",
- "structopt",
  "subxt-signer",
  "tokio",
  "tokio-util 0.7.12",
@@ -11512,12 +11466,6 @@ dependencies = [
  "unicode-properties",
 ]
 
-[[package]]
-name = "strsim"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -11553,30 +11501,6 @@ dependencies = [
  "syn 2.0.85",
 ]
 
-[[package]]
-name = "structopt"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
-dependencies = [
- "clap 2.34.0",
- "lazy_static",
- "structopt-derive",
-]
-
-[[package]]
-name = "structopt-derive"
-version = "0.4.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
-dependencies = [
- "heck 0.3.3",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "strum"
 version = "0.26.3"
@@ -12057,15 +11981,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "textwrap"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-dependencies = [
- "unicode-width",
-]
-
 [[package]]
 name = "thin-vec"
 version = "0.2.13"
@@ -12853,12 +12768,6 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
-[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
-
 [[package]]
 name = "version_check"
 version = "0.9.5"
diff --git a/Cargo.toml b/Cargo.toml
index bc20356f6..6d93d0286 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,10 +15,7 @@ members = [
     "macros/playground",
     "macros/context-derive",
 ]
-exclude = [
-    "tangle-test-utils",
-    "example",
-]
+exclude = ["tangle-test-utils", "example"]
 
 [workspace.package]
 authors = ["Webb Technologies Inc."]
@@ -123,7 +120,6 @@ serde = { version = "1.0.208", default-features = false }
 serde_json = "1.0"
 sha2 = "0.10.8"
 sqlx = "=0.7.3"
-structopt = "0.3.26"
 syn = "2.0.75"
 sysinfo = "0.31.2"
 thiserror = { version = "1.0.64", default-features = false }
diff --git a/blueprint-manager/Cargo.toml b/blueprint-manager/Cargo.toml
index bc8eec78e..8ec9f5e6e 100644
--- a/blueprint-manager/Cargo.toml
+++ b/blueprint-manager/Cargo.toml
@@ -12,9 +12,9 @@ path = "src/main.rs"
 sp-core = { workspace = true }
 gadget-io = { workspace = true }
 gadget-sdk = { workspace = true, default-features = true }
+clap = { workspace = true, features = ["derive", "wrap_help"] }
 color-eyre = { workspace = true, features = ["tracing-error", "color-spantrace", "issue-url"] }
 serde = { workspace = true }
-structopt = { workspace = true }
 tangle-subxt = { workspace = true }
 toml = { workspace = true }
 hex = { workspace = true }
diff --git a/blueprint-manager/src/config.rs b/blueprint-manager/src/config.rs
index f54e0ab0f..112e01d0f 100644
--- a/blueprint-manager/src/config.rs
+++ b/blueprint-manager/src/config.rs
@@ -1,31 +1,31 @@
+use clap::Parser;
 use std::path::PathBuf;
-use structopt::StructOpt;
 
-#[derive(Debug, StructOpt)]
-#[structopt(
+#[derive(Debug, Parser)]
+#[command(
     name = "Blueprint Manager",
     about = "An program executor that connects to the Tangle network and runs protocols dynamically on the fly"
 )]
 pub struct BlueprintManagerConfig {
     /// The path to the gadget configuration file
-    #[structopt(parse(from_os_str), short = "s", long)]
+    #[arg(short = 's', long)]
     pub gadget_config: Option<PathBuf>,
     /// The path to the keystore
-    #[structopt(short = "k", long)]
+    #[arg(short = 'k', long)]
     pub keystore_uri: String,
     /// The directory in which all gadgets will store their data
-    #[structopt(long, short = "d", parse(from_os_str), default_value = "./data")]
+    #[arg(long, short = 'd', default_value = "./data")]
     pub data_dir: PathBuf,
     /// The verbosity level, can be used multiple times
-    #[structopt(long, short = "v", parse(from_occurrences))]
-    pub verbose: i32,
+    #[arg(long, short = 'v', action = clap::ArgAction::Count)]
+    pub verbose: u8,
     /// Whether to use pretty logging
-    #[structopt(long)]
+    #[arg(long)]
     pub pretty: bool,
     /// An optional unique string identifier for the blueprint manager to differentiate between multiple
     /// running instances of a BlueprintManager (mostly for debugging purposes)
-    #[structopt(long, short = "id")]
+    #[arg(long, alias = "id")]
     pub instance_id: Option<String>,
-    #[structopt(long, short = "t")]
+    #[arg(long, short = 't')]
     pub test_mode: bool,
 }
diff --git a/blueprint-manager/src/main.rs b/blueprint-manager/src/main.rs
index d248f90b5..11c2f6a6b 100644
--- a/blueprint-manager/src/main.rs
+++ b/blueprint-manager/src/main.rs
@@ -2,15 +2,15 @@ use blueprint_manager::config::BlueprintManagerConfig;
 use blueprint_manager::run_blueprint_manager;
 use blueprint_manager::sdk;
 use blueprint_manager::sdk::utils::msg_to_error;
+use clap::Parser;
 use gadget_io::GadgetConfig;
 use sdk::entry;
-use structopt::StructOpt;
 
 #[tokio::main]
 #[allow(clippy::needless_return)]
 async fn main() -> color_eyre::Result<()> {
     color_eyre::install()?;
-    let mut blueprint_manager_config = BlueprintManagerConfig::from_args();
+    let mut blueprint_manager_config = BlueprintManagerConfig::parse();
 
     blueprint_manager_config.data_dir = std::path::absolute(&blueprint_manager_config.data_dir)?;
 
diff --git a/blueprint-manager/src/sdk/entry.rs b/blueprint-manager/src/sdk/entry.rs
index 5ae99f6a1..7a1b0798f 100644
--- a/blueprint-manager/src/sdk/entry.rs
+++ b/blueprint-manager/src/sdk/entry.rs
@@ -21,7 +21,7 @@ impl<'a, F: Send + Future<Output = T> + 'a, T> SendFuture<'a, T> for F {}
 
 /// Sets up the logger for the blueprint manager, based on the verbosity level passed in.
 pub fn setup_blueprint_manager_logger(
-    verbose: i32,
+    verbose: u8,
     pretty: bool,
     filter: &str,
 ) -> color_eyre::Result<()> {
diff --git a/blueprints/incredible-squaring-eigenlayer/Cargo.toml b/blueprints/incredible-squaring-eigenlayer/Cargo.toml
index 380142ee6..bf17399f4 100644
--- a/blueprints/incredible-squaring-eigenlayer/Cargo.toml
+++ b/blueprints/incredible-squaring-eigenlayer/Cargo.toml
@@ -42,7 +42,7 @@ ark-ec = { workspace = true }
 parking_lot = { workspace = true }
 libp2p = { workspace = true }
 ed25519-zebra = { workspace = true, features = ["pkcs8", "default", "der", "std", "serde", "pem"] }
-structopt = { workspace = true }
+clap = { workspace = true, features = ["derive", "wrap_help"] }
 hex = { workspace = true }
 k256 = { workspace = true }
 reqwest = { workspace = true }
diff --git a/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std b/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std
index 035de35f5..1de6eecf8 160000
--- a/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std
+++ b/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std
@@ -1,1 +1,1 @@
-Subproject commit 035de35f5e366c8d6ed142aec4ccb57fe2dd87d4
+Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f
diff --git a/blueprints/incredible-squaring-eigenlayer/src/runner.rs b/blueprints/incredible-squaring-eigenlayer/src/runner.rs
index 718b7da2e..3450b1a97 100644
--- a/blueprints/incredible-squaring-eigenlayer/src/runner.rs
+++ b/blueprints/incredible-squaring-eigenlayer/src/runner.rs
@@ -22,10 +22,10 @@ use eigensdk::client_elcontracts::writer::ELChainWriter;
 use eigensdk::crypto_bls::BlsKeyPair;
 use eigensdk::logging::get_test_logger;
 use eigensdk::types::operator::Operator;
+use gadget_sdk::clap::Parser;
 use gadget_sdk::events_watcher::InitializableEventHandler;
 use gadget_sdk::info;
 use gadget_sdk::run::GadgetRunner;
-use gadget_sdk::structopt::StructOpt;
 use gadget_sdk::{
     config::{ContextConfig, GadgetConfiguration},
     events_watcher::evm::DefaultNodeConfig,
@@ -211,7 +211,7 @@ impl GadgetRunner for EigenlayerGadgetRunner<parking_lot::RawRwLock> {
 
 pub async fn execute_runner() -> Result<()> {
     gadget_sdk::logging::setup_log();
-    let config = ContextConfig::from_args();
+    let config = ContextConfig::parse();
     let env = gadget_sdk::config::load(config).expect("Failed to load environment");
     let mut runner = Box::new(EigenlayerGadgetRunner::new(env.clone()).await);
 
diff --git a/blueprints/incredible-squaring/Cargo.toml b/blueprints/incredible-squaring/Cargo.toml
index 55e1264b0..ec765f715 100644
--- a/blueprints/incredible-squaring/Cargo.toml
+++ b/blueprints/incredible-squaring/Cargo.toml
@@ -26,7 +26,6 @@ ark-ec = { workspace = true }
 parking_lot = { workspace = true }
 libp2p = { workspace = true }
 ed25519-zebra = { workspace = true, features = ["pkcs8", "default", "der", "std", "serde", "pem"] }
-structopt = { workspace = true }
 hex = { workspace = true }
 k256 = { workspace = true }
 serde_json = { workspace = true }
diff --git a/blueprints/incredible-squaring/contracts/lib/forge-std b/blueprints/incredible-squaring/contracts/lib/forge-std
index 035de35f5..1de6eecf8 160000
--- a/blueprints/incredible-squaring/contracts/lib/forge-std
+++ b/blueprints/incredible-squaring/contracts/lib/forge-std
@@ -1,1 +1,1 @@
-Subproject commit 035de35f5e366c8d6ed142aec4ccb57fe2dd87d4
+Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f
diff --git a/blueprints/periodic-web-poller/Cargo.toml b/blueprints/periodic-web-poller/Cargo.toml
index 67cc66240..b83aef245 100644
--- a/blueprints/periodic-web-poller/Cargo.toml
+++ b/blueprints/periodic-web-poller/Cargo.toml
@@ -21,7 +21,6 @@ sp-core = { workspace = true }
 subxt-signer = { workspace = true, features = ["sr25519", "subxt", "std"] }
 parking_lot = { workspace = true }
 ed25519-zebra = { workspace = true, features = ["pkcs8", "default", "der", "std", "serde", "pem"] }
-structopt = { workspace = true }
 hex = { workspace = true }
 k256 = { workspace = true }
 serde_json = { workspace = true }
diff --git a/cli/src/create.rs b/cli/src/create.rs
index cc1b621d2..1bb31cc7a 100644
--- a/cli/src/create.rs
+++ b/cli/src/create.rs
@@ -14,11 +14,11 @@ pub struct Source {
 #[derive(Args, Debug, Clone)]
 #[group(requires = "repo")]
 pub struct RepoArgs {
-    #[arg(short, long, required = false, group = "source")]
+    #[arg(short, long, env, required = false, group = "source")]
     repo: String,
-    #[arg(short, long)]
+    #[arg(short, long, env)]
     branch: Option<String>,
-    #[arg(short, long, conflicts_with = "branch")]
+    #[arg(short, long, env, conflicts_with = "branch")]
     tag: Option<String>,
 }
 
diff --git a/cli/src/keys.rs b/cli/src/keys.rs
index a11dbf38e..ae5854201 100644
--- a/cli/src/keys.rs
+++ b/cli/src/keys.rs
@@ -136,10 +136,10 @@ pub fn generate_key(
         }
     };
 
-    println!("Generated {:?} key:", key_type);
-    println!("Public key: {}", public);
-    if show_secret {
-        println!("Private key: {}", secret);
+    eprintln!("Generated {:?} key:", key_type);
+    eprintln!("Public key: {}", public);
+    if show_secret || keystore.is_mem() {
+        eprintln!("Private key: {}", secret);
     }
 
     Ok(())
diff --git a/cli/src/main.rs b/cli/src/main.rs
index d52214150..e44c83ef6 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -37,7 +37,7 @@ pub enum GadgetCommands {
     #[command(visible_alias = "c")]
     Create {
         /// The name of the blueprint
-        #[arg(short, long)]
+        #[arg(short, long, value_name = "NAME", env = "NAME")]
         name: String,
 
         #[command(flatten)]
@@ -48,13 +48,23 @@ pub enum GadgetCommands {
     #[command(visible_alias = "d")]
     Deploy {
         /// HTTP RPC URL to use
-        #[arg(long, value_name = "URL", default_value = "https://rpc.tangle.tools")]
+        #[arg(
+            long,
+            value_name = "URL",
+            default_value = "https://rpc.tangle.tools",
+            env
+        )]
         http_rpc_url: String,
         /// Tangle RPC URL to use
-        #[arg(long, value_name = "URL", default_value = "wss://rpc.tangle.tools")]
+        #[arg(
+            long,
+            value_name = "URL",
+            default_value = "wss://rpc.tangle.tools",
+            env
+        )]
         ws_rpc_url: String,
         /// The package to deploy (if the workspace has multiple packages).
-        #[arg(short, long, value_name = "PACKAGE")]
+        #[arg(short, long, value_name = "PACKAGE", env = "CARGO_PACKAGE")]
         package: Option<String>,
     },
     /// Generate a key
@@ -63,12 +73,14 @@ pub enum GadgetCommands {
         #[arg(short, long, value_enum)]
         key_type: KeyType,
 
-        /// The path to save the key (optional)
+        /// The path to save the key to, if not provided, the key will be printed
+        /// to the console instead
         #[arg(short, long)]
         path: Option<PathBuf>,
 
-        /// The SURI or seed to use for the generation of the key (optional)
-        #[arg(short, long)]
+        /// The seed to use for the generation of the key in hex format without 0x prefix,
+        /// if not provided, a random seed will be generated
+        #[arg(short, long, value_name = "SEED_HEX", env = "SEED")]
         seed: Option<String>,
 
         /// If true, the secret key will be printed along with the public key
@@ -127,12 +139,8 @@ async fn main() -> color_eyre::Result<()> {
                 seed,
                 show_secret,
             } => {
-                keys::generate_key(
-                    key_type,
-                    path,
-                    seed.as_deref().map(str::as_bytes),
-                    show_secret,
-                )?;
+                let seed = seed.map(hex::decode).transpose()?;
+                keys::generate_key(key_type, path, seed.as_deref(), show_secret)?;
             }
         },
     }
@@ -153,3 +161,14 @@ fn init_tracing_subscriber() {
         .with(fmt_layer)
         .init();
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn verify_cli() {
+        use clap::CommandFactory;
+        Cli::command().debug_assert();
+    }
+}
diff --git a/gadget-io/Cargo.toml b/gadget-io/Cargo.toml
index 2b58485f3..7328f8426 100644
--- a/gadget-io/Cargo.toml
+++ b/gadget-io/Cargo.toml
@@ -8,6 +8,7 @@ repository.workspace = true
 homepage.workspace = true
 
 [dependencies]
+clap = { workspace = true, features = ["derive", "wrap_help", "env"], optional = true }
 cfg-if = { workspace = true }
 hex = { workspace = true }
 multiaddr = { workspace = true, default-features = false }
@@ -18,7 +19,6 @@ serde = { workspace = true }
 sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] }
 sp-core = { workspace = true, features = ["serde", "full_crypto"] }
 sp-keystore = { workspace = true, optional = true }
-structopt = { workspace = true, optional = true }
 thiserror = { workspace = true }
 tokio = { workspace = true, optional = true, features = ["time", "rt", "sync", "macros"] }
 tracing = { workspace = true, default-features = false, features = ["log", "attributes"] }
@@ -37,9 +37,9 @@ default = ["std"]
 std = [
     "dep:tokio",
     "sp-application-crypto/std",
+    "dep:clap",
     "dep:sp-keystore",
     "dep:sc-keystore",
-    "dep:structopt",
     "dep:scale-info",
     "dep:parity-scale-codec",
     "tracing/std",
diff --git a/gadget-io/src/imp/standard/shell.rs b/gadget-io/src/imp/standard/shell.rs
index 4dc7af8dd..8b97ac24f 100644
--- a/gadget-io/src/imp/standard/shell.rs
+++ b/gadget-io/src/imp/standard/shell.rs
@@ -1,77 +1,62 @@
 use crate::shared::shell::SupportedChains;
+use clap::Parser;
 use multiaddr::Multiaddr;
 use serde::{Deserialize, Serialize};
 use std::{net::IpAddr, path::PathBuf};
-use structopt::StructOpt;
 
-#[derive(Debug, StructOpt)]
-#[structopt(
+#[derive(Debug, Parser)]
+#[command(
     name = "Gadget",
     about = "An MPC executor that connects to the Tangle network to perform work"
 )]
 pub struct Opt {
     /// The path to the configuration file. If not provided, the default configuration will be used.
     /// Note that if the configuration file is provided, the command line arguments will be ignored.
-    #[structopt(global = true, parse(from_os_str), short = "c", long = "config")]
+    #[arg(global = true, short = 'c', long = "config")]
     pub config: Option<PathBuf>,
-    /// The verbosity level, can be used multiple times
-    #[structopt(long, short = "v", global = true, parse(from_occurrences))]
-    pub verbose: i32,
-    /// Whether to use pretty logging
-    #[structopt(global = true, long)]
-    pub pretty: bool,
     /// The options for the shell
-    #[structopt(flatten)]
+    #[command(flatten)]
     pub options: GadgetConfig,
 }
 
-#[derive(Debug, StructOpt, Serialize, Deserialize)]
+#[derive(Debug, Parser, Serialize, Deserialize)]
 /// All shells should expect this as CLI input. The Blueprint Manager will be responsible for passing these values to this gadget binary
 pub struct GadgetConfig {
     /// The IP address to bind to for the libp2p node.
-    #[structopt(long = "bind-ip", short = "i", default_value = defaults::BIND_IP)]
+    #[arg(long = "bind-ip", short = 'i', default_value = defaults::BIND_IP)]
     #[serde(default = "defaults::bind_ip")]
     pub bind_addr: IpAddr,
     /// The port to bind to for the libp2p node.
-    #[structopt(long = "port", short = "p", default_value = defaults::BIND_PORT)]
+    #[arg(long = "port", short = 'p', default_value = defaults::BIND_PORT)]
     #[serde(default = "defaults::bind_port")]
     pub bind_port: u16,
     /// The HTTP RPC URL of the Tangle Node.
-    #[structopt(long = "url", parse(try_from_str = url::Url::parse), default_value = defaults::HTTP_RPC_URL)]
+    #[arg(long = "url", value_parser = url::Url::parse, default_value = defaults::HTTP_RPC_URL)]
     #[serde(default = "defaults::http_rpc_url")]
     pub http_rpc_url: url::Url,
     /// The WS RPC URL of the Tangle Node.
-    #[structopt(long = "url", parse(try_from_str = url::Url::parse), default_value = defaults::WS_RPC_URL)]
+    #[arg(long = "url", value_parser = url::Url::parse, default_value = defaults::WS_RPC_URL)]
     #[serde(default = "defaults::ws_rpc_url")]
     pub ws_rpc_url: url::Url,
     /// The List of bootnodes to connect to
-    #[structopt(long = "bootnodes", parse(try_from_str = <Multiaddr as std::str::FromStr>::from_str))]
+    #[arg(long = "bootnodes", value_parser  = <Multiaddr as std::str::FromStr>::from_str, action = clap::ArgAction::Append)]
     #[serde(default)]
     pub bootnodes: Vec<Multiaddr>,
     /// The base path to store the blueprint manager data, and read data from the keystore.
-    #[structopt(long, short = "d")]
+    #[arg(long, short = 'd')]
     pub keystore_uri: String,
     /// Keystore Password, if not provided, the password will be read from the environment variable.
-    #[structopt(long = "keystore-password", env)]
+    #[arg(long = "keystore-password", env)]
     pub keystore_password: Option<String>,
     /// The chain to connect to, must be one of the supported chains.
-    #[structopt(
-        long,
-        default_value,
-        possible_values = &[
-        "local_testnet",
-        "local_mainnet",
-        "testnet",
-        "mainnet"
-        ]
-    )]
+    #[arg(long, default_value_t = SupportedChains::LocalTestnet)]
     #[serde(default)]
     pub chain: SupportedChains,
     /// The verbosity level, can be used multiple times
-    #[structopt(long, short = "v", global = true, parse(from_occurrences))]
-    pub verbose: i32,
+    #[arg(long, short = 'v', global = true, action = clap::ArgAction::Count)]
+    pub verbose: u8,
     /// Whether to use pretty logging
-    #[structopt(global = true, long)]
+    #[arg(global = true, long)]
     pub pretty: bool,
 }
 
diff --git a/gadget-io/src/shared/shell.rs b/gadget-io/src/shared/shell.rs
index 889f5dbde..6ce883c5f 100644
--- a/gadget-io/src/shared/shell.rs
+++ b/gadget-io/src/shared/shell.rs
@@ -7,8 +7,8 @@ use core::str::FromStr;
 use serde::{Deserialize, Serialize};
 
 #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
-#[cfg_attr(feature = "std", derive(structopt::StructOpt))]
-#[cfg_attr(feature = "std", structopt(rename_all = "snake_case"))]
+#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
+#[cfg_attr(feature = "std", clap(rename_all = "snake_case"))]
 #[serde(rename_all = "snake_case")]
 pub enum SupportedChains {
     #[default]
diff --git a/macros/blueprint-proc-macro/src/lib.rs b/macros/blueprint-proc-macro/src/lib.rs
index caf59443a..6579dacb8 100644
--- a/macros/blueprint-proc-macro/src/lib.rs
+++ b/macros/blueprint-proc-macro/src/lib.rs
@@ -122,6 +122,19 @@ pub fn load_abi(input: TokenStream) -> TokenStream {
 }
 
 /// A procedural macro that annotates a function as a main function for the blueprint.
+///
+/// ```rust,no_run
+/// # use gadget_sdk as sdk;
+/// #[sdk::main(env)]
+/// pub async fn main() {
+///    // Your main function code here
+/// }
+/// ```
+///
+/// # Parameters
+/// - `env`: Sets up the environment for the main function.
+/// - `skip_logging`: A flag to skip the logging setup for the main function.
+/// - ...: are passes as-is to the `#[tokio::main]` attribute.
 #[proc_macro_attribute]
 pub fn main(args: TokenStream, input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as syn::ItemFn);
diff --git a/macros/blueprint-proc-macro/src/sdk_main.rs b/macros/blueprint-proc-macro/src/sdk_main.rs
index 1ae56f1ec..3c637ab6f 100644
--- a/macros/blueprint-proc-macro/src/sdk_main.rs
+++ b/macros/blueprint-proc-macro/src/sdk_main.rs
@@ -13,12 +13,15 @@ use syn::{Expr, ItemFn, Token};
 mod kw {
     // Presented as #[sdk::main(env)]
     syn::custom_keyword!(env);
+    // Presented as #[sdk::main(skip_logger)]
+    syn::custom_keyword!(skip_logger);
 }
 
 // Add fields which are passed onto tokio, and those that are reserved for the sdk_main macro
 pub(crate) struct SdkMainArgs {
     tokio_args: Option<Vec<Expr>>,
     env: bool,
+    skip_logger: bool,
 }
 
 pub(crate) fn sdk_main_impl(args: &SdkMainArgs, input: &ItemFn) -> syn::Result<TokenStream> {
@@ -43,7 +46,7 @@ pub(crate) fn sdk_main_impl(args: &SdkMainArgs, input: &ItemFn) -> syn::Result<T
     let standard_setup = if args.env {
         quote! {
             // Load the environment and create the gadget runner
-            let config: gadget_sdk::config::ContextConfig = gadget_sdk::structopt::StructOpt::from_args();
+            let config: gadget_sdk::config::ContextConfig = gadget_sdk::clap::Parser::parse();
             let env = gadget_sdk::config::load(config.clone()).expect("Failed to load environment");
             gadget_sdk::utils::check_for_test(&config).expect("Failed to check for test");
         }
@@ -51,6 +54,14 @@ pub(crate) fn sdk_main_impl(args: &SdkMainArgs, input: &ItemFn) -> syn::Result<T
         proc_macro2::TokenStream::default()
     };
 
+    let logger = if args.skip_logger {
+        quote! {}
+    } else {
+        quote! {
+            gadget_sdk::logging::setup_log();
+        }
+    };
+
     // Next, we need to consider the input. It should be in the form "async fn main() { ... }"
     // We must remove all the async fn main() and keep everything else
     let input = input.block.clone();
@@ -59,7 +70,7 @@ pub(crate) fn sdk_main_impl(args: &SdkMainArgs, input: &ItemFn) -> syn::Result<T
         use gadget_sdk::tokio;
         #[tokio::main #tokio_args]
         async fn main() -> Result<(), Box<dyn std::error::Error>> {
-            gadget_sdk::logging::setup_log();
+            #logger
             #standard_setup
             inner_main(#env_passed_var).await?;
             Ok(())
@@ -79,6 +90,7 @@ impl Parse for SdkMainArgs {
         // to the SdkMainArgs struct. Otherwise, add it to the tokio args
         let mut tokio_args = vec![];
         let mut env = false;
+        let mut skip_logger = false;
         // Parse through everything
         while !input.is_empty() {
             if input.peek(Token![,]) {
@@ -88,7 +100,11 @@ impl Parse for SdkMainArgs {
             if input.peek(kw::env) {
                 let _ = input.parse::<kw::env>()?;
                 env = true;
+            } else if input.peek(kw::skip_logger) {
+                let _ = input.parse::<kw::skip_logger>()?;
+                skip_logger = true;
             } else {
+                // Parse the input as an expression
                 tokio_args.push(input.parse()?);
             }
         }
@@ -99,6 +115,10 @@ impl Parse for SdkMainArgs {
             Some(tokio_args)
         };
 
-        Ok(SdkMainArgs { tokio_args, env })
+        Ok(SdkMainArgs {
+            tokio_args,
+            env,
+            skip_logger,
+        })
     }
 }
diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml
index 75ca7a33c..1a481bd8d 100644
--- a/sdk/Cargo.toml
+++ b/sdk/Cargo.toml
@@ -23,7 +23,7 @@ parking_lot = { workspace = true, optional = true }
 rand = { workspace = true, features = ["alloc"] }
 thiserror = { workspace = true }
 tokio-stream = { workspace = true, features = ["io-util", "sync"] }
-structopt = { workspace = true }
+clap = { workspace = true, features = ["derive", "wrap_help"] }
 url = { workspace = true, features = ["serde"] }
 uuid = { workspace = true }
 failure = { workspace = true }
@@ -93,7 +93,6 @@ gadget-blueprint-proc-macro-core = { workspace = true, default-features = false
 sysinfo = { workspace = true }
 
 
-
 [target.'cfg(not(target_family = "wasm"))'.dependencies.libp2p]
 workspace = true
 features = [
diff --git a/sdk/src/config.rs b/sdk/src/config.rs
index cb74c1081..dfb68d88e 100644
--- a/sdk/src/config.rs
+++ b/sdk/src/config.rs
@@ -12,11 +12,15 @@ use gadget_io::SupportedChains;
 use libp2p::Multiaddr;
 use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
-use structopt::StructOpt;
 use url::Url;
 
 /// The protocol on which a gadget will be executed.
 #[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
+#[cfg_attr(
+    feature = "std",
+    derive(clap::ValueEnum),
+    clap(rename_all = "lowercase")
+)]
 pub enum Protocol {
     #[default]
     Tangle,
@@ -45,14 +49,18 @@ impl Protocol {
     pub fn from_env() -> Result<Self, Error> {
         Ok(Protocol::default())
     }
+
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Self::Tangle => "tangle",
+            Self::Eigenlayer => "eigenlayer",
+        }
+    }
 }
 
 impl core::fmt::Display for Protocol {
     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
-        match self {
-            Self::Tangle => write!(f, "tangle"),
-            Self::Eigenlayer => write!(f, "eigenlayer"),
-        }
+        write!(f, "{}", self.as_str())
     }
 }
 
@@ -110,6 +118,7 @@ pub struct GadgetConfiguration<RwLock: lock_api::RawRwLock> {
     pub bind_port: u16,
     /// The Address of the Network that will be interacted with
     pub bind_addr: IpAddr,
+    /// Specifies custom tracing span for the gadget
     pub span: tracing::Span,
     /// Whether the gadget is in test mode
     pub test_mode: bool,
@@ -118,37 +127,18 @@ pub struct GadgetConfiguration<RwLock: lock_api::RawRwLock> {
     _lock: core::marker::PhantomData<RwLock>,
 }
 
-#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
+#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
 pub struct EigenlayerContractAddresses {
+    /// The address of the registry coordinator contract
     pub registry_coordinator_addr: Address,
+    /// The address of the operator state retriever contract
     pub operator_state_retriever_addr: Address,
+    /// The address of the operator registry contract
     pub delegation_manager_addr: Address,
+    /// The address of the strategy manager contract
     pub strategy_manager_addr: Address,
 }
 
-impl Default for EigenlayerContractAddresses {
-    fn default() -> Self {
-        EigenlayerContractAddresses {
-            registry_coordinator_addr: std::env::var("REGISTRY_COORDINATOR_ADDR")
-                .unwrap_or_default()
-                .parse()
-                .unwrap(),
-            operator_state_retriever_addr: std::env::var("OPERATOR_STATE_RETRIEVER_ADDR")
-                .unwrap_or_default()
-                .parse()
-                .unwrap(),
-            delegation_manager_addr: std::env::var("DELEGATION_MANAGER_ADDR")
-                .unwrap_or_default()
-                .parse()
-                .unwrap(),
-            strategy_manager_addr: std::env::var("STRATEGY_MANAGER_ADDR")
-                .unwrap_or_default()
-                .parse()
-                .unwrap(),
-        }
-    }
-}
-
 impl<RwLock: lock_api::RawRwLock> Debug for GadgetConfiguration<RwLock> {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("GadgetConfiguration")
@@ -261,72 +251,95 @@ pub enum Error {
     /// Invalid ECDSA keypair found in the keystore.
     #[error("Invalid ECDSA keypair found in the keystore")]
     InvalidEcdsaKeypair,
-    /// Missing `KEYSTORE_URI` environment
-    #[error("Missing keystore URI")]
+    /// Test setup error
+    #[error("Test setup error: {0}")]
     TestSetup(String),
     /// Missing `EigenlayerContractAddresses`
     #[error("Missing EigenlayerContractAddresses")]
     MissingEigenlayerContractAddresses,
 }
 
-#[derive(Debug, Clone, StructOpt, Serialize, Deserialize)]
-#[structopt(name = "General CLI Context")]
+#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)]
+#[command(name = "General CLI Context")]
 #[cfg(feature = "std")]
 pub struct ContextConfig {
     /// Pass through arguments to another command
-    #[structopt(subcommand)]
+    #[command(subcommand)]
     pub gadget_core_settings: GadgetCLICoreSettings,
 }
 
-#[derive(Debug, Clone, StructOpt, Serialize, Deserialize)]
+#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)]
 #[cfg(feature = "std")]
 pub enum GadgetCLICoreSettings {
-    #[structopt(name = "run")]
+    #[command(name = "run")]
     Run {
-        #[structopt(long, short = "b", parse(try_from_str), env)]
+        #[arg(long, short = 'b', env)]
         bind_addr: IpAddr,
-        #[structopt(long, short = "p", env)]
+        #[arg(long, short = 'p', env)]
         bind_port: u16,
-        #[structopt(long, short = "t", env)]
+        #[arg(long, short = 't', env)]
         test_mode: bool,
-        #[structopt(long, short = "l", env)]
+        #[arg(long, short = 'l', env)]
         log_id: Option<String>,
-        #[structopt(long, short = "u", parse(try_from_str = url::Url::parse), env)]
+        #[arg(long, env)]
         #[serde(default = "gadget_io::defaults::http_rpc_url")]
         http_rpc_url: Url,
-        #[structopt(long, short = "ws", parse(try_from_str = url::Url::parse), env)]
+        #[arg(long, env)]
         #[serde(default = "gadget_io::defaults::ws_rpc_url")]
         ws_rpc_url: Url,
-        #[structopt(long, parse(try_from_str = <Multiaddr as std::str::FromStr>::from_str), env)]
+        #[arg(long, value_parser = <Multiaddr as std::str::FromStr>::from_str, action = clap::ArgAction::Append, env)]
         #[serde(default)]
         bootnodes: Option<Vec<Multiaddr>>,
-        #[structopt(long, short = "d", env)]
+        #[arg(long, short = 'd', env)]
         keystore_uri: String,
-        #[structopt(
-            long,
-            default_value,
-            possible_values = &[
-                "local_testnet",
-                "local_mainnet",
-                "testnet",
-                "mainnet"
-            ],
-            env
-        )]
+        #[arg(long, value_enum, env)]
         chain: SupportedChains,
-        #[structopt(long, short = "v", parse(from_occurrences), env)]
-        verbose: i32,
+        #[arg(long, short = 'v', action = clap::ArgAction::Count, env)]
+        verbose: u8,
         /// Whether to use pretty logging
-        #[structopt(long, env)]
+        #[arg(long, env)]
         pretty: bool,
-        #[structopt(long, env)]
+        #[arg(long, env)]
         keystore_password: Option<String>,
-        #[structopt(long, env)]
+        #[arg(long, env)]
         blueprint_id: u64,
-        #[structopt(long, env)]
+        #[arg(long, env)]
         service_id: Option<u64>,
-        #[structopt(long, parse(try_from_str), env)]
+        /// The protocol to use
+        #[arg(long, value_enum, env)]
         protocol: Protocol,
+        /// The address of the registry coordinator
+        #[arg(
+            long,
+            value_name = "ADDR",
+            env = "REGISTRY_COORDINATOR_ADDR",
+            required_if_eq("protocol", Protocol::Eigenlayer.as_str()),
+        )]
+        registry_coordinator: Option<Address>,
+        /// The address of the operator state retriever
+        #[arg(
+            long,
+            value_name = "ADDR",
+            env = "OPERATOR_STATE_RETRIEVER_ADDR",
+            required_if_eq("protocol", Protocol::Eigenlayer.as_str())
+        )]
+        operator_state_retriever: Option<Address>,
+        /// The address of the delegation manager
+        #[arg(
+            long,
+            value_name = "ADDR",
+            env = "DELEGATION_MANAGER_ADDR",
+            required_if_eq("protocol", Protocol::Eigenlayer.as_str())
+        )]
+        delegation_manager: Option<Address>,
+        /// The address of the strategy manager
+        #[arg(
+            long,
+            value_name = "ADDR",
+            env = "STRATEGY_MANAGER_ADDR",
+            required_if_eq("protocol", Protocol::Eigenlayer.as_str())
+        )]
+        strategy_manager: Option<Address>,
     },
 }
 
@@ -373,6 +386,10 @@ fn load_inner<RwLock: lock_api::RawRwLock>(
                 blueprint_id,
                 service_id,
                 protocol,
+                registry_coordinator,
+                operator_state_retriever,
+                delegation_manager,
+                strategy_manager,
                 ..
             },
         ..
@@ -402,7 +419,14 @@ fn load_inner<RwLock: lock_api::RawRwLock>(
         },
         is_registration,
         protocol,
-        eigenlayer_contract_addrs: Default::default(),
+        // Eigenlayer contract addresses will be None if the protocol is not Eigenlayer
+        // otherwise, they will be the values provided by the user
+        eigenlayer_contract_addrs: EigenlayerContractAddresses {
+            registry_coordinator_addr: registry_coordinator.unwrap_or_default(),
+            operator_state_retriever_addr: operator_state_retriever.unwrap_or_default(),
+            delegation_manager_addr: delegation_manager.unwrap_or_default(),
+            strategy_manager_addr: strategy_manager.unwrap_or_default(),
+        },
         _lock: core::marker::PhantomData,
     })
 }
@@ -494,9 +518,20 @@ impl<RwLock: lock_api::RawRwLock> GadgetConfiguration<RwLock> {
     pub async fn client(&self) -> Result<crate::clients::tangle::runtime::TangleClient, Error> {
         let client =
             subxt::OnlineClient::<crate::clients::tangle::runtime::TangleConfig>::from_url(
-                self.http_rpc_endpoint.clone(),
+                self.ws_rpc_endpoint.clone(),
             )
             .await?;
         Ok(client)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn verify_cli() {
+        use clap::CommandFactory;
+        ContextConfig::command().debug_assert();
+    }
+}
diff --git a/sdk/src/keystore/backend/mod.rs b/sdk/src/keystore/backend/mod.rs
index 4197a853b..ce6432118 100644
--- a/sdk/src/keystore/backend/mod.rs
+++ b/sdk/src/keystore/backend/mod.rs
@@ -72,6 +72,22 @@ impl<RwLock: lock_api::RawRwLock> GenericKeyStore<RwLock> {
             ),
         })
     }
+
+    /// Returns `true` if the generic key store is [`Mem`].
+    ///
+    /// [`Mem`]: GenericKeyStore::Mem
+    #[must_use]
+    pub fn is_mem(&self) -> bool {
+        matches!(self, Self::Mem(..))
+    }
+
+    /// Returns `true` if the generic key store is [`Fs`].
+    ///
+    /// [`Fs`]: GenericKeyStore::Fs
+    #[must_use]
+    pub fn is_fs(&self) -> bool {
+        matches!(self, Self::Fs(..))
+    }
 }
 
 impl<RwLock: lock_api::RawRwLock> super::Backend for GenericKeyStore<RwLock> {
diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs
index 9d6db2440..30acaebcc 100644
--- a/sdk/src/lib.rs
+++ b/sdk/src/lib.rs
@@ -70,12 +70,12 @@ pub mod utils;
 
 // Re-exports
 pub use alloy_rpc_types;
+pub use clap;
 pub use error::Error;
 pub use futures;
 pub use gadget_blueprint_proc_macro::*;
 pub use libp2p;
 pub use parking_lot;
-pub use structopt;
 pub use subxt_core;
 pub use tangle_subxt;
 pub use tokio;


Test patch:
diff --git a/blueprint-test-utils/src/lib.rs b/blueprint-test-utils/src/lib.rs
index a6dbc17f9..6bef8f046 100644
--- a/blueprint-test-utils/src/lib.rs
+++ b/blueprint-test-utils/src/lib.rs
@@ -46,7 +46,7 @@ pub struct PerTestNodeInput<T> {
     bind_ip: IpAddr,
     bind_port: u16,
     bootnodes: Vec<Multiaddr>,
-    verbose: i32,
+    verbose: u8,
     pretty: bool,
     #[allow(dead_code)]
     extra_input: T,