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

Issue:
[TASK] Improve directory structure and clean repo
# Overview
- Remove unused directories here (fixtures, etc.).
- Reorganize the repo structure: (modules, gadget-sdk, core, common, shell, etc.). Let's identify a more sensible organization here.


Discussion / hints:
Last thing to clean up is moving the executor into SDK, packaging it like an SDK library/feature. Moving the tangle validator executor example into an examples directory -> `tangle-avs` as a blueprint makes sense.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e09e3c938..7a4bf602a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,9 +40,7 @@ jobs:
     strategy:
       matrix:
         package: [
-          gadget-core,
-          gadget-common,
-          gadget-sdk,
+          gadget-sdk
         ]
     steps:
       - name: Checkout code
@@ -77,8 +75,7 @@ jobs:
     strategy:
       matrix:
         package: [
-          gadget-core,
-          gadget-common,
+          gadget-sdk,
           blueprint-manager
         ]
     steps:
@@ -107,8 +104,7 @@ jobs:
     strategy:
       matrix:
         package: [
-          gadget-core,
-          gadget-common,
+          gadget-sdk,
           blueprint-manager,
         ]
     steps:
@@ -136,4 +132,4 @@ jobs:
         run: cargo install cargo-nextest --locked
 
       - name: tests
-        run: cargo nextest run --nocapture --package ${{ matrix.package }}
\ No newline at end of file
+        run: cargo nextest run --nocapture --package ${{ matrix.package }}
diff --git a/.gitignore b/.gitignore
index 7770faf84..f8ce18f1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ target/
 certs
 .DS_Store
 CODEGEN_LOCK
+blueprints/**/lib
diff --git a/.gitmodules b/.gitmodules
index acbfe2635..e9122f6b5 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,16 @@
+# For the incredible-squaring blueprint
 [submodule "blueprints/incredible-squaring/contracts/lib/forge-std"]
 	path = blueprints/incredible-squaring/contracts/lib/forge-std
 	url = https://github.com/foundry-rs/forge-std
 [submodule "blueprints/incredible-squaring/contracts/lib/eigenlayer-middleware"]
 	path = blueprints/incredible-squaring/contracts/lib/eigenlayer-middleware
-	url = https://github.com/Layr-Labs/eigenlayer-middleware
\ No newline at end of file
+	url = https://github.com/Layr-Labs/eigenlayer-middleware
+
+# For the ecdsa-threshold-mpc blueprint
+[submodule "blueprints/ecdsa-threshold-mpc/contracts/lib/forge-std"]
+	path = blueprints/ecdsa-threshold-mpc/contracts/lib/forge-std
+	url = https://github.com/foundry-rs/forge-std
+[submodule "blueprints/ecdsa-threshold-mpc/contracts/lib/tnt-core"]
+	path = blueprints/ecdsa-threshold-mpc/contracts/lib/tnt-core
+	url = https://github.com/webb-tools/tnt-core
+	branch = main
diff --git a/Cargo.lock b/Cargo.lock
index 50b5df257..41f69a847 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1733,8 +1733,6 @@ version = "0.1.1"
 dependencies = [
  "color-eyre",
  "futures",
- "gadget-common",
- "gadget-core",
  "gadget-io",
  "gadget-sdk",
  "hex",
@@ -1746,7 +1744,6 @@ dependencies = [
  "sha2 0.10.8",
  "sp-core 34.0.0",
  "structopt",
- "tangle-environment",
  "tangle-primitives",
  "tangle-subxt",
  "tokio",
@@ -1774,14 +1771,12 @@ dependencies = [
  "blueprint-manager",
  "cargo-tangle",
  "cargo_metadata",
- "environment-utils",
  "frame-support 37.0.0",
  "frame-system 37.1.0",
  "futures",
  "gadget-blueprint-proc-macro-core",
- "gadget-common",
- "gadget-core",
  "gadget-io",
+ "gadget-sdk",
  "getrandom",
  "libp2p",
  "log",
@@ -1808,7 +1803,6 @@ dependencies = [
  "sp-session",
  "sp-staking 34.0.0",
  "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tangle-environment",
  "tangle-primitives",
  "tokio",
  "tracing-subscriber 0.3.18",
@@ -3247,18 +3241,6 @@ dependencies = [
  "log",
 ]
 
-[[package]]
-name = "environment-utils"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "auto_impl",
- "gadget-common",
- "gadget-io",
- "tangle-subxt",
- "tokio",
-]
-
 [[package]]
 name = "environmental"
 version = "1.1.4"
@@ -4283,53 +4265,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "gadget-common"
-version = "0.1.0"
-dependencies = [
- "async-trait",
- "auto_impl",
- "cfg-if",
- "futures",
- "gadget-core",
- "gadget-io",
- "getrandom",
- "hex",
- "lazy_static",
- "libsecp256k1",
- "log",
- "parity-scale-codec 3.6.12",
- "parking_lot",
- "prometheus",
- "round-based",
- "serde",
- "serde_json",
- "sp-core 34.0.0",
- "sp-io 38.0.0",
- "sp-runtime 39.0.0",
- "sqlx",
- "substrate-prometheus-endpoint",
- "subxt-signer",
- "tangle-subxt",
- "thiserror 1.0.61",
-]
-
-[[package]]
-name = "gadget-core"
-version = "0.0.1"
-dependencies = [
- "async-trait",
- "auto_impl",
- "futures-timer",
- "gadget-io",
- "hex",
- "log",
- "parking_lot",
- "serde",
- "sp-core 34.0.0",
- "tokio",
-]
-
 [[package]]
 name = "gadget-executor"
 version = "0.1.0"
@@ -4384,6 +4319,7 @@ dependencies = [
  "alloy-sol-types",
  "alloy-transport",
  "async-trait",
+ "auto_impl",
  "backoff",
  "bincode",
  "derive_more 1.0.0",
@@ -4391,24 +4327,27 @@ dependencies = [
  "elliptic-curve",
  "futures",
  "gadget-blueprint-proc-macro",
- "gadget-common",
- "gadget-core",
  "gadget-io",
  "getrandom",
  "hex",
+ "http-body-util",
  "hyper 1.4.1",
+ "hyper-util",
  "k256",
+ "lazy_static",
  "libp2p",
  "lock_api",
  "log",
  "parking_lot",
  "prometheus",
  "rand",
+ "round-based",
  "schnorrkel",
  "serde",
  "serde_json",
  "sp-core 34.0.0",
  "sp-io 38.0.0",
+ "sqlx",
  "subxt",
  "subxt-signer",
  "sysinfo",
@@ -8135,15 +8074,6 @@ dependencies = [
  "unarray",
 ]
 
-[[package]]
-name = "protocol-macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.76",
-]
-
 [[package]]
 name = "psm"
 version = "0.1.21"
@@ -11776,24 +11706,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "tangle-environment"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "auto_impl",
- "environment-utils",
- "gadget-common",
- "gadget-core",
- "parity-scale-codec 3.6.12",
- "parking_lot",
- "scale-info",
- "serde",
- "sp-application-crypto 38.0.0",
- "sp-core 34.0.0",
- "url 2.5.2 (git+https://github.com/domenukk/rust-url?branch=no_std)",
-]
-
 [[package]]
 name = "tangle-primitives"
 version = "2.0.0"
@@ -11825,6 +11737,8 @@ dependencies = [
 [[package]]
 name = "tangle-subxt"
 version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6021db4ae4f20114d6ab52365a3dce19c884dbfe1f4aba546639e53ed6ad052"
 dependencies = [
  "parity-scale-codec 3.6.12",
  "scale-info",
diff --git a/Cargo.toml b/Cargo.toml
index bb6fddee0..1c2a2f3bb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,16 +2,11 @@
 resolver = "2"
 members = [
     "blueprint-metadata",
-    "blueprints/*",
+    "blueprints/incredible-squaring",
     "cli",
-    "core",
-    "common",
     "executor",
-    "environments/tangle",
-    "environments/utils",
     "gadget-io",
     "blueprint-test-utils",
-    "protocol-macros",
     "blueprint-manager",
     "sdk",
     # Commented out, since it uses jobs-v1 code
@@ -45,16 +40,11 @@ single_match_else = "allow"
 broken_intra_doc_links = "deny"
 
 [workspace.dependencies]
-gadget-core = { version = "0.0.1", path = "./core", default-features = false }
-gadget-common = { version = "0.1.0", path = "./common", default-features = false }
 gadget-io = { version = "0.0.1", path = "./gadget-io", default-features = false }
 gadget-executor = { version = "0.1.0", path = "./executor" }
 blueprint-manager = { version = "0.1.1", path = "./blueprint-manager" }
 blueprint-test-utils = { path = "./blueprint-test-utils" }
 gadget-sdk = { path = "./sdk", default-features = false }
-protocol-macros = { path = "./protocol-macros" }
-tangle-environment = { path = "./environments/tangle", default-features = false }
-environment-utils = { path = "./environments/utils", default-features = false }
 
 gadget-blueprint-proc-macro = { path = "./macros/blueprint-proc-macro", default-features = false, version = "0.1.0" }
 gadget-blueprint-proc-macro-core = { path = "./macros/blueprint-proc-macro-core", default-features = false, version = "0.1.0" }
@@ -192,6 +182,7 @@ hex = { version = "0.4.3", default-features = false }
 hex-literal = "0.4.1"
 http-body-util = "0.1.2"
 hyper = { version = "1.4.1", default-features = false }
+hyper-util = "0.1.7"
 itertools = "0.13.0"
 jsonrpsee = "0.24.3"
 jsonwebtoken = "9.3.0"
diff --git a/blueprint-manager/Cargo.toml b/blueprint-manager/Cargo.toml
index 9ed34409b..c23a6a6a4 100644
--- a/blueprint-manager/Cargo.toml
+++ b/blueprint-manager/Cargo.toml
@@ -9,8 +9,6 @@ path = "src/cli.rs"
 
 [dependencies]
 sp-core = { workspace = true }
-gadget-core = { workspace = true }
-gadget-common = { workspace = true }
 gadget-io = { workspace = true }
 gadget-sdk = { workspace = true }
 color-eyre = { workspace = true, features = ["tracing-error", "color-spantrace", "issue-url"] }
@@ -22,7 +20,6 @@ hex = { workspace = true }
 tokio = { workspace = true, features = ["process", "io-util", "signal"] }
 reqwest = { workspace = true }
 sha2 = { workspace = true }
-tangle-environment = { workspace = true }
 futures = { workspace = true }
 
 log = { workspace = true }
@@ -35,7 +32,6 @@ libp2p = { workspace = true }
 [features]
 default = ["std"]
 std = [
-    "gadget-common/std",
     "gadget-io/std",
     "gadget-sdk/default",
 ]
diff --git a/blueprint-manager/src/cli.rs b/blueprint-manager/src/cli.rs
index 58680ed66..32e2e2c95 100644
--- a/blueprint-manager/src/cli.rs
+++ b/blueprint-manager/src/cli.rs
@@ -3,7 +3,6 @@ use blueprint_manager::config::BlueprintManagerConfig;
 use blueprint_manager::run_blueprint_manager;
 use blueprint_manager::sdk;
 use blueprint_manager::sdk::utils::msg_to_error;
-use gadget_common::gadget_io;
 use gadget_io::GadgetConfig;
 use structopt::StructOpt;
 
diff --git a/blueprint-manager/src/executor/event_handler.rs b/blueprint-manager/src/executor/event_handler.rs
index e5bcc75cb..85afbe927 100644
--- a/blueprint-manager/src/executor/event_handler.rs
+++ b/blueprint-manager/src/executor/event_handler.rs
@@ -8,16 +8,16 @@ use crate::sdk::utils::{
     is_windows, msg_to_error, valid_file_exists,
 };
 use color_eyre::eyre::OptionExt;
-use gadget_common::prelude::DebugLogger;
-use gadget_common::tangle_runtime::AccountId32;
 use gadget_io::GadgetConfig;
+use gadget_sdk::clients::tangle::runtime::TangleEvent;
+use gadget_sdk::clients::tangle::services::{RpcServicesWithBlueprint, ServicesClient};
+use gadget_sdk::logger::Logger;
 use std::fmt::Write;
 use std::sync::atomic::Ordering;
-use tangle_environment::api::{RpcServicesWithBlueprint, ServicesClient};
-use tangle_environment::gadget::TangleEvent;
+use tangle_subxt::subxt::utils::AccountId32;
 use tangle_subxt::subxt::SubstrateConfig;
 use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::{
-    Gadget, GithubFetcher,
+    Gadget, GadgetSourceFetcher, GithubFetcher,
 };
 use tangle_subxt::tangle_testnet_runtime::api::services::events::{
     JobCalled, JobResultSubmitted, PreRegistration, Registered, ServiceInitiated, Unregistered,
@@ -32,7 +32,7 @@ pub async fn maybe_handle(
     gadget_config: &GadgetConfig,
     blueprint_manager_opts: &BlueprintManagerConfig,
     active_gadgets: &mut ActiveGadgets,
-    logger: &DebugLogger,
+    logger: &Logger,
 ) -> color_eyre::Result<()> {
     for (gh, fetcher) in onchain_services.iter().zip(onchain_gh_fetchers) {
         let native_github_metadata = NativeGithubMetadata {
@@ -70,7 +70,7 @@ async fn handle_github_source(
     blueprint_manager_opts: &BlueprintManagerConfig,
     github: &GithubFetcher,
     active_gadgets: &mut ActiveGadgets,
-    logger: &DebugLogger,
+    logger: &Logger,
 ) -> color_eyre::Result<()> {
     let blueprint_id = service.blueprint_id;
     let service_str = get_service_str(service);
@@ -224,7 +224,7 @@ pub struct EventPollResult {
 
 pub(crate) async fn check_blueprint_events(
     event: &TangleEvent,
-    logger: &DebugLogger,
+    logger: &Logger,
     active_gadgets: &mut ActiveGadgets,
     account_id: &AccountId32,
 ) -> EventPollResult {
@@ -330,7 +330,7 @@ pub(crate) async fn check_blueprint_events(
 pub(crate) async fn handle_tangle_event(
     event: &TangleEvent,
     blueprints: &[RpcServicesWithBlueprint],
-    logger: &DebugLogger,
+    logger: &Logger,
     gadget_config: &GadgetConfig,
     gadget_manager_opts: &BlueprintManagerConfig,
     active_gadgets: &mut ActiveGadgets,
@@ -380,7 +380,7 @@ pub(crate) async fn handle_tangle_event(
         let mut services_for_this_blueprint = vec![];
         if let Gadget::Native(gadget) = &blueprint.gadget {
             let gadget_source = &gadget.sources.0[0];
-            if let gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services::GadgetSourceFetcher::Github(gh) = &gadget_source.fetcher {
+            if let GadgetSourceFetcher::Github(gh) = &gadget_source.fetcher {
                 let metadata = github_fetcher_to_native_github_metadata(gh, blueprint.blueprint_id);
                 onchain_services.push(metadata);
                 fetchers.push(gh.clone());
diff --git a/blueprint-manager/src/executor/mod.rs b/blueprint-manager/src/executor/mod.rs
index 810509649..fda7228df 100644
--- a/blueprint-manager/src/executor/mod.rs
+++ b/blueprint-manager/src/executor/mod.rs
@@ -1,48 +1,30 @@
 use crate::config::BlueprintManagerConfig;
-use crate::sdk::entry::keystore_from_base_path;
+use crate::sdk::entry::{keystore_from_base_path, SendFuture};
 use crate::sdk::utils::msg_to_error;
 use crate::sdk::{keystore::load_keys_from_keystore, utils};
-use crate::sdk::{Client, SendFuture};
 use color_eyre::eyre::OptionExt;
 use color_eyre::Report;
-use gadget_common::config::DebugLogger;
-use gadget_common::environments::GadgetEnvironment;
-use gadget_common::subxt_signer::sr25519;
-use gadget_common::tangle_runtime::AccountId32;
 use gadget_io::{GadgetConfig, SubstrateKeystore};
-use sp_core::{ecdsa, Pair, H256};
+use gadget_sdk::clients::tangle::runtime::TangleRuntimeClient;
+use gadget_sdk::clients::tangle::services::ServicesClient;
+use gadget_sdk::clients::Client;
+use gadget_sdk::logger::Logger;
+use sp_core::{ecdsa, Pair};
 use std::collections::HashMap;
 use std::future::Future;
 use std::pin::Pin;
 use std::task::{Context, Poll};
-use tangle_environment::api::{RpcServicesWithBlueprint, ServicesClient};
-use tangle_environment::gadget::SubxtConfig;
-use tangle_environment::TangleEnvironment;
-use tangle_subxt::subxt::blocks::BlockRef;
-use tangle_subxt::subxt::Config;
+use tangle_subxt::subxt::utils::AccountId32;
+use tangle_subxt::subxt_signer::sr25519;
 use tokio::task::JoinHandle;
 
 pub(crate) mod event_handler;
 
-pub async fn get_blueprints<C: Config>(
-    runtime: &ServicesClient<C>,
-    block_hash: [u8; 32],
-    account_id: AccountId32,
-) -> color_eyre::Result<Vec<RpcServicesWithBlueprint>>
-where
-    BlockRef<<C as Config>::Hash>: From<BlockRef<H256>>,
-{
-    runtime
-        .query_operator_blueprints(block_hash, account_id)
-        .await
-        .map_err(|err| msg_to_error(err.to_string()))
-}
-
 pub struct BlueprintManagerHandle {
     shutdown_call: Option<tokio::sync::oneshot::Sender<()>>,
     start_tx: Option<tokio::sync::oneshot::Sender<()>>,
     process: JoinHandle<color_eyre::Result<()>>,
-    logger: DebugLogger,
+    logger: Logger,
     sr25519_id: sr25519::Keypair,
     ecdsa_id: ecdsa::Pair,
 }
@@ -129,7 +111,8 @@ pub async fn run_blueprint_manager<F: SendFuture<'static, ()>>(
         "local"
     };
 
-    let logger = DebugLogger {
+    let logger = Logger {
+        target: "blueprint-manager",
         id: format!("blueprint-manager-{}", logger_id),
     };
 
@@ -152,18 +135,13 @@ pub async fn run_blueprint_manager<F: SendFuture<'static, ()>>(
     let sr25519_private_key = sr25519::Keypair::from_secret_key(secret_key)
         .map_err(|err| Report::msg(format!("Failed to create SR25519 keypair: {err:?}")))?;
 
-    let (role_key, acco_key) = load_keys_from_keystore(&keystore)?;
-    let ecdsa_key = role_key.clone();
+    let (ecdsa_key, acco_key) = load_keys_from_keystore(&keystore)?;
 
-    let sub_account_id = AccountId32(acco_key.public().0);
-    let subxt_config = SubxtConfig {
-        endpoint: gadget_config.url.clone(),
-    };
+    let account_id = AccountId32(acco_key.public().0);
 
-    let tangle_environment = TangleEnvironment::new(subxt_config, acco_key, logger.clone());
-
-    let tangle_runtime = tangle_environment.setup_runtime().await?;
-    let runtime = ServicesClient::new(logger.clone(), tangle_runtime.client());
+    let tangle_client =
+        TangleRuntimeClient::from_url(gadget_config.url.as_str(), account_id.clone()).await?;
+    let services_client = ServicesClient::new(logger.clone(), tangle_client.client());
     let mut active_gadgets = HashMap::new();
 
     // With the basics setup, we must now implement the main logic
@@ -174,11 +152,12 @@ pub async fn run_blueprint_manager<F: SendFuture<'static, ()>>(
     //   -> If the services field is not empty, for each service in RpcServicesWithBlueprint.services, spawn the gadget binary, using params to set the job type to listen to (in terms of our old language, each spawned service represents a single "RoleType")
 
     let (mut operator_subscribed_blueprints, init_event) =
-        if let Some(event) = tangle_runtime.next_event().await {
-            (
-                get_blueprints(&runtime, event.hash, sub_account_id.clone()).await?,
-                event,
-            )
+        if let Some(event) = tangle_client.next_event().await {
+            let blueprints = services_client
+                .query_operator_blueprints(event.hash, account_id.clone())
+                .await
+                .map_err(|err| msg_to_error(err.to_string()))?;
+            (blueprints, event)
         } else {
             return Err(Report::msg("Failed to get initial block hash"));
         };
@@ -193,7 +172,7 @@ pub async fn run_blueprint_manager<F: SendFuture<'static, ()>>(
         &init_event,
         &logger,
         &mut active_gadgets,
-        &sub_account_id,
+        &account_id,
     )
     .await;
     event_handler::handle_tangle_event(
@@ -204,25 +183,27 @@ pub async fn run_blueprint_manager<F: SendFuture<'static, ()>>(
         &blueprint_manager_config,
         &mut active_gadgets,
         poll_result,
-        &runtime,
+        &services_client,
     )
     .await?;
 
     let logger_manager = logger.clone();
     let manager_task = async move {
         // Listen to FinalityNotifications and poll for new/deleted services that correspond to the blueprints above
-        while let Some(event) = tangle_runtime.next_event().await {
+        while let Some(event) = tangle_client.next_event().await {
             let result = event_handler::check_blueprint_events(
                 &event,
                 &logger_manager,
                 &mut active_gadgets,
-                &sub_account_id,
+                &account_id.clone(),
             )
             .await;
 
             if result.needs_update {
-                operator_subscribed_blueprints =
-                    get_blueprints(&runtime, event.hash, sub_account_id.clone()).await?;
+                operator_subscribed_blueprints = services_client
+                    .query_operator_blueprints(event.hash, account_id.clone())
+                    .await
+                    .map_err(|err| msg_to_error(err.to_string()))?;
             }
 
             event_handler::handle_tangle_event(
@@ -233,7 +214,7 @@ pub async fn run_blueprint_manager<F: SendFuture<'static, ()>>(
                 &blueprint_manager_config,
                 &mut active_gadgets,
                 result,
-                &runtime,
+                &services_client,
             )
             .await?;
         }
diff --git a/blueprint-manager/src/sdk/config.rs b/blueprint-manager/src/sdk/config.rs
index 67a3e885b..2c2fad7da 100644
--- a/blueprint-manager/src/sdk/config.rs
+++ b/blueprint-manager/src/sdk/config.rs
@@ -1,4 +1,3 @@
-use gadget_common::environments::GadgetEnvironment;
 pub use gadget_io::KeystoreConfig;
 use libp2p::Multiaddr;
 use std::net::IpAddr;
@@ -6,10 +5,9 @@ use std::path::PathBuf;
 use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
 
 #[derive(Debug)]
-pub struct SingleGadgetConfig<KBE: Clone, Env: GadgetEnvironment> {
+pub struct SingleGadgetConfig<KBE: Clone> {
     pub base_path: PathBuf,
     pub keystore: KeystoreConfig,
-    pub environment: Env,
     pub bind_ip: IpAddr,
     pub bind_port: u16,
     pub bootnodes: Vec<Multiaddr>,
diff --git a/blueprint-manager/src/sdk/entry.rs b/blueprint-manager/src/sdk/entry.rs
index 02fc19853..f34df089b 100644
--- a/blueprint-manager/src/sdk/entry.rs
+++ b/blueprint-manager/src/sdk/entry.rs
@@ -1,9 +1,10 @@
-use crate::sdk::setup::SingleGadgetInput;
-use crate::sdk::{generate_node_input, SingleGadgetConfig};
-use gadget_common::environments::GadgetEnvironment;
-use gadget_common::prelude::{DebugLogger, KeystoreBackend};
-use gadget_core::job::SendFuture;
+use crate::sdk::config::SingleGadgetConfig;
+use crate::sdk::setup::{generate_node_input, SingleGadgetInput};
+use futures::Future;
 use gadget_io::{GadgetConfig, KeystoreConfig, SupportedChains};
+use gadget_sdk::clients::tangle::runtime::TangleRuntimeClient;
+use gadget_sdk::logger::Logger;
+use gadget_sdk::store::KeyValueStoreBackend;
 use structopt::StructOpt;
 use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
 use tracing_subscriber::fmt::SubscriberBuilder;
@@ -24,16 +25,18 @@ pub fn keystore_from_base_path(
     }
 }
 
+pub trait SendFuture<'a, T>: Send + Future<Output = T> + 'a {}
+impl<'a, F: Send + Future<Output = T> + 'a, T> SendFuture<'a, T> for F {}
+
 /// Runs a gadget for a given protocol.
 pub async fn run_gadget_for_protocol<
-    Env: GadgetEnvironment,
-    KBE: KeystoreBackend,
-    T: FnOnce(SingleGadgetInput<KBE, Env>) -> F,
+    KBE: KeyValueStoreBackend,
+    T: FnOnce(SingleGadgetInput<KBE>) -> F,
     F,
     T2: FnOnce() -> F2,
     F2: SendFuture<'static, KBE>,
 >(
-    environment: Env,
+    _client: TangleRuntimeClient,
     services: Vec<ServiceBlueprint>,
     n_protocols: usize,
     keystore_backend: T2,
@@ -57,8 +60,9 @@ where
     let keystore =
         keystore_from_base_path(&config.base_path, config.chain, config.keystore_password);
 
-    let logger = DebugLogger {
-        id: "test".to_string(),
+    let logger = Logger {
+        target: "blueprint-manager",
+        id: "run-gadget".to_string(),
     };
 
     logger.info("Starting gadget with config: {config:?}");
@@ -67,7 +71,6 @@ where
         keystore_backend,
         services,
         keystore,
-        environment,
         base_path: config.base_path,
         bind_ip: config.bind_ip,
         bind_port: config.bind_port,
diff --git a/blueprint-manager/src/sdk/mod.rs b/blueprint-manager/src/sdk/mod.rs
index 31381eaa9..3996e0c5f 100644
--- a/blueprint-manager/src/sdk/mod.rs
+++ b/blueprint-manager/src/sdk/mod.rs
@@ -3,39 +3,3 @@ pub mod entry;
 pub mod keystore;
 pub mod setup;
 pub mod utils;
-
-pub use config::*;
-pub use entry::run_gadget_for_protocol;
-pub use gadget_common::prelude::*;
-pub use gadget_core::gadget::general::Client;
-pub use gadget_sdk::network::setup::{start_p2p_network, NetworkConfig};
-pub use setup::generate_node_input;
-
-pub mod prelude {
-    pub use crate::sdk::async_trait;
-    pub use crate::sdk::BuiltExecutableJobWrapper;
-    pub use crate::sdk::DebugLogger;
-    pub use crate::sdk::ECDSAKeyStore;
-    pub use crate::sdk::FullProtocolConfig;
-    pub use crate::sdk::JobError;
-    pub use crate::sdk::JobsClient;
-    pub use crate::sdk::KeystoreBackend;
-    pub use crate::sdk::Mutex;
-    pub use crate::sdk::Network;
-    pub use crate::sdk::NodeInput;
-    pub use crate::sdk::ProtocolWorkManager;
-    pub use crate::sdk::SendFuture;
-    pub use crate::sdk::UnboundedReceiver;
-    pub use crate::sdk::WorkManagerInterface;
-    pub use gadget_common::full_protocol::SharedOptional;
-
-    pub use gadget_common::prelude::InMemoryBackend;
-    pub use gadget_common::tangle_runtime::AccountId32;
-    pub use gadget_common::Error;
-    pub use gadget_common::{generate_protocol, generate_setup_and_run_command};
-    pub use std::sync::Arc;
-
-    pub use color_eyre;
-    pub use tangle_primitives;
-    pub use tangle_subxt;
-}
diff --git a/blueprint-manager/src/sdk/setup.rs b/blueprint-manager/src/sdk/setup.rs
index 387669dcf..f963c5652 100644
--- a/blueprint-manager/src/sdk/setup.rs
+++ b/blueprint-manager/src/sdk/setup.rs
@@ -2,15 +2,12 @@ use std::collections::HashMap;
 use std::time::Duration;
 
 use crate::sdk::keystore::load_keys_from_keystore;
-
-use gadget_common::environments::GadgetEnvironment;
-use gadget_common::keystore::KeystoreBackend;
-use gadget_common::{
-    config::{DebugLogger, PrometheusConfig},
-    full_protocol::NodeInput,
-    keystore::ECDSAKeyStore,
-};
 use gadget_io::tokio::task::JoinHandle;
+use gadget_sdk::clients::tangle::runtime::TangleRuntimeClient;
+use gadget_sdk::logger::Logger;
+use gadget_sdk::network::Network;
+use gadget_sdk::prometheus::PrometheusConfig;
+use gadget_sdk::store::{ECDSAKeyStore, KeyValueStoreBackend};
 use sp_core::{keccak_256, sr25519, Pair};
 
 use crate::sdk::config::SingleGadgetConfig;
@@ -20,17 +17,33 @@ use gadget_sdk::network::setup::NetworkConfig;
 use itertools::Itertools;
 use libp2p::Multiaddr;
 
-pub type SingleGadgetInput<KBE, Env> = NodeInput<Env, GossipHandle, KBE, ()>;
+/// Used for constructing an instance of a node.
+///
+/// If there is both a keygen and a signing protocol, then the length of the vectors are 2.
+/// The length of the vector is equal to the numbers of protocols that the constructed node
+/// is going to concurrently execute.
+pub struct NodeInput<N: Network, KBE: KeyValueStoreBackend, D> {
+    pub clients: Vec<TangleRuntimeClient>,
+    pub networks: Vec<N>,
+    pub account_id: sr25519::Public,
+    pub logger: Logger,
+    pub keystore: ECDSAKeyStore<KBE>,
+    pub node_index: usize,
+    pub additional_params: D,
+    pub prometheus_config: PrometheusConfig,
+}
+
+pub type SingleGadgetInput<KBE> = NodeInput<GossipHandle, KBE, ()>;
 
 /// Generates the NodeInput and handle to the networking layer for the given config.
 #[tracing::instrument(skip(config))]
-pub async fn generate_node_input<KBE: KeystoreBackend, Env: GadgetEnvironment>(
-    config: SingleGadgetConfig<KBE, Env>,
-) -> color_eyre::Result<(SingleGadgetInput<KBE, Env>, JoinHandle<()>)> {
-    let (role_key, acco_key) = load_keys_from_keystore(&config.keystore)?;
+pub async fn generate_node_input<KBE: KeyValueStoreBackend>(
+    config: SingleGadgetConfig<KBE>,
+) -> color_eyre::Result<(SingleGadgetInput<KBE>, JoinHandle<()>)> {
+    let (ecdsa_key, acco_key) = load_keys_from_keystore(&config.keystore)?;
     //let network_key = ed25519::Pair::from_seed(&config.node_key).to_raw_vec();
-    let logger = DebugLogger::default();
-    let wrapped_keystore = ECDSAKeyStore::new(config.keystore_backend.clone(), role_key.clone());
+    let logger = Logger::default();
+    let keystore = ECDSAKeyStore::new(config.keystore_backend.clone(), ecdsa_key.clone());
 
     // Use the first 32 bytes of the sr25519 account key as the network key. We discard the 32 remaining nonce seed bytes
     // thus ensuring that the network key, when used, will actually have slightly different properties than the original
@@ -51,7 +64,7 @@ pub async fn generate_node_input<KBE: KeystoreBackend, Env: GadgetEnvironment>(
 
     let libp2p_config = NetworkConfig::new(
         libp2p_key,
-        role_key,
+        ecdsa_key,
         config.bootnodes.clone(),
         config.bind_ip,
         config.bind_port,
@@ -66,80 +79,36 @@ pub async fn generate_node_input<KBE: KeystoreBackend, Env: GadgetEnvironment>(
     logger.debug("Successfully initialized network, now waiting for bootnodes to connect ...");
     wait_for_connection_to_bootnodes(&config.bootnodes, &networks, &logger).await?;
 
-    let node_input = generate_node_input_for_required_protocols(
-        &config.environment,
-        networks,
-        acco_key,
-        logger,
-        wrapped_keystore,
-    )
-    .await?;
-
-    Ok((node_input, network_task))
-}
-
-pub fn generate_node_input_for_role_group<KBE, Env: GadgetEnvironment>(
-    runtime: Env::Client,
-    networks: HashMap<String, GossipHandle>,
-    account_id: sr25519::Public,
-    logger: DebugLogger,
-    tx_manager: Env::TransactionManager,
-    keystore: ECDSAKeyStore<KBE>,
-) -> color_eyre::Result<SingleGadgetInput<KBE, Env>>
-where
-    KBE: KeystoreBackend,
-{
+    let client =
+        TangleRuntimeClient::from_url(&config.bind_ip.to_string(), acco_key.public().0.into())
+            .await?;
     let networks = networks
         .into_iter()
         .sorted_by_key(|r| r.0.clone())
         .map(|r| r.1)
         .collect::<Vec<_>>();
     let clients = (0..networks.len())
-        .map(|_| runtime.clone())
+        .map(|_| client.clone())
         .collect::<Vec<_>>();
 
-    Ok(NodeInput::<Env, GossipHandle, KBE, ()> {
+    let node_input = NodeInput::<GossipHandle, KBE, ()> {
         clients,
-        account_id,
+        account_id: acco_key.public(),
         logger,
-        tx_manager,
         keystore,
         node_index: 0,
         additional_params: (),
         prometheus_config: PrometheusConfig::Disabled,
         networks,
-    })
-}
+    };
 
-pub async fn generate_node_input_for_required_protocols<KBE, Env: GadgetEnvironment>(
-    env: &Env,
-    networks: HashMap<String, GossipHandle>,
-    acco_key: sr25519::Pair,
-    logger: DebugLogger,
-    keystore: ECDSAKeyStore<KBE>,
-) -> color_eyre::Result<SingleGadgetInput<KBE, Env>>
-where
-    KBE: KeystoreBackend,
-{
-    let runtime = env
-        .setup_runtime()
-        .await
-        .map_err(|err| color_eyre::Report::msg(format!("Failed to setup runtime: {err}")))?;
-    let tx_manager = env.transaction_manager();
-    generate_node_input_for_role_group(
-        runtime,
-        networks.clone(),
-        acco_key.public(),
-        logger.clone(),
-        tx_manager.clone(),
-        keystore.clone(),
-    )
+    Ok((node_input, network_task))
 }
 
 pub async fn wait_for_connection_to_bootnodes(
     bootnodes: &[Multiaddr],
     handles: &HashMap<String, GossipHandle>,
-    logger: &DebugLogger,
+    logger: &Logger,
 ) -> color_eyre::Result<()> {
     let n_required = bootnodes.len();
     let n_networks = handles.len();
@@ -153,7 +122,7 @@ pub async fn wait_for_connection_to_bootnodes(
     // For each network, we start a task that checks if we have enough peers connected
     // and then we wait for all of them to finish.
 
-    let wait_for_peers = |handle: GossipHandle, n_required, logger: DebugLogger| async move {
+    let wait_for_peers = |handle: GossipHandle, n_required, logger: Logger| async move {
         'inner: loop {
             let n_connected = handle.connected_peers();
             if n_connected >= n_required {
diff --git a/blueprint-manager/src/sdk/utils.rs b/blueprint-manager/src/sdk/utils.rs
index 204595f96..820c37241 100644
--- a/blueprint-manager/src/sdk/utils.rs
+++ b/blueprint-manager/src/sdk/utils.rs
@@ -1,7 +1,7 @@
 use crate::config::BlueprintManagerConfig;
 use crate::protocols::resolver::NativeGithubMetadata;
-use gadget_common::config::DebugLogger;
 use gadget_io::GadgetConfig;
+use gadget_sdk::logger::Logger;
 use sha2::Digest;
 use std::path::Path;
 use std::sync::atomic::{AtomicBool, Ordering};
@@ -144,7 +144,7 @@ pub fn is_windows() -> bool {
 
 pub fn generate_running_process_status_handle(
     process: gadget_io::tokio::process::Child,
-    logger: &DebugLogger,
+    logger: &Logger,
     role_type: &str,
 ) -> (Arc<AtomicBool>, gadget_io::tokio::sync::oneshot::Sender<()>) {
     let (stop_tx, stop_rx) = gadget_io::tokio::sync::oneshot::channel::<()>();
diff --git a/blueprints/ecdsa-threshold-mpc/.gitignore b/blueprints/ecdsa-threshold-mpc/.gitignore
new file mode 100644
index 000000000..c046cc391
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/.gitignore
@@ -0,0 +1,30 @@
+# Generated by Cargo
+target/
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+.idea
+.vscode
+.zed
+
+.direnv
+.DS_Store
+# Solc Compiler files
+cache/
+out/
+
+# Ignores development broadcast logs
+!/broadcast
+/broadcast/*/31337/
+/broadcast/**/dry-run/
+
+# Docs
+docs/
+
+# Dotenv file
+.env
+blueprint.lock
diff --git a/blueprints/ecdsa-threshold-mpc/Cargo.toml b/blueprints/ecdsa-threshold-mpc/Cargo.toml
new file mode 100644
index 000000000..4452445d6
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "ecdsa-threshold-mpc"
+version = "0.1.0"
+edition = "2021"
+description = "A threshold MPC blueprint allowing key generation, threshold signing, and key refreshes."
+authors = ["drewstone <drewstone329@gmail.com>"]
+license = "Unlicense"
+homepage = "https://tangle.tools"
+repository = "https://github.com/webb-tools/ecdsa-threshold-mpc"
+readme = "README.md"
+categories = ["cryptography", "cryptography::cryptocurrencies"]
+keywords = ["tangle", "blueprint", "avs"]
+rust-version = "1.79"
+
+[dependencies]
+tracing = { workspace = true }
+async-trait = { workspace = true }
+thiserror = { workspace = true }
+futures = { workspace = true }
+rand = { workspace = true, default-features = false, features = ["alloc"] }
+gadget-sdk = { workspace = true, features = ["std"] }
+
+color-eyre = { workspace = true }
+tokio = { workspace = true, default-features = false, features = ["full"] }
+subxt-signer = { workspace = true, features = ["sr25519", "subxt", "std"] }
+tracing-subscriber = { workspace = true, features = ["parking_lot", "env-filter"] }
+serde = { workspace = true, features = ["derive"] }
+serde_json = { workspace = true }
+sp-core = { workspace = true, features = ["std"] }
+itertools = { workspace = true }
+
+cggmp21 = { version = "0.4", features = ["curve-stark", "curve-secp256k1", "curve-secp256r1"] }
+cggmp21-keygen = { version = "0.3", features = ["hd-wallets"] }
+key-share = { version = "0.4", default-features = false }
+
+generic-ec = { version = "0.4.1", default-features = false }
+generic-ec-zkp = { version = "0.4.1", default-features = false }
+round-based = { version = "0.3", default-features = false }
+udigest = { version = "0.2.1", default-features = false }
+digest = { version = "0.10", default-features = false }
+sha2 = { version = "0.10", default-features = false }
+
+[build-dependencies]
+blueprint-metadata = "0.1"
+
+[features]
+default = ["std"]
+std = []
diff --git a/blueprints/ecdsa-threshold-mpc/LICENSE b/blueprints/ecdsa-threshold-mpc/LICENSE
new file mode 100644
index 000000000..fdddb29aa
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/blueprints/ecdsa-threshold-mpc/README.md b/blueprints/ecdsa-threshold-mpc/README.md
new file mode 100644
index 000000000..c96216ccd
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/README.md
@@ -0,0 +1,57 @@
+# <h1 align="center"> Tangle Blueprint 🌐 </h1>
+
+**A simple Hello World Blueprint for Tangle**
+
+## 📚 Prerequisites
+
+Before you can run this project, you will need to have the following software installed on your machine:
+
+- [Rust](https://www.rust-lang.org/tools/install)
+- [Forge](https://getfoundry.sh)
+- [Tangle](https://github.com/webb-tools/tangle?tab=readme-ov-file#-getting-started-)
+
+You will also need to install `cargo-tangle`:
+
+```sh
+cargo install cargo-tangle
+```
+
+## 🚀 Getting Started
+
+Once `cargo-tangle` is installed, you can create a new project with the following command:
+
+```sh
+cargo tangle gadget create --name <project-name>
+```
+
+and follow the instructions to create a new project.
+
+## 🛠️ Development
+
+Once you have created a new project, you can run the following command to start the project:
+
+```sh
+cargo build
+```
+to build the project, and
+
+```sh
+cargo tangle gadget deploy
+```
+to deploy the blueprint to the Tangle network.
+
+## 📚 Overview
+
+This project is about creating a simple Hello World Blueprint for Tangle and EigenLayer. Blueprints are specifications for Actively Validated Services (AVS) on the Tangle Network. An AVS is an off-chain service that runs arbitrary computations for a user-specified period of time.
+
+Blueprints provide a useful abstraction, allowing developers to create reusable service infrastructures as if they were smart contracts. This enables developers to monetize their work and align long-term incentives with the success of their creations, benefiting proportionally to their Blueprint's usage.
+
+For more details, please refer to the [project documentation](https://docs.tangle.tools/developers/blueprints).
+
+## 📬 Feedback
+
+If you have any feedback or issues, please feel free to open an issue on our [GitHub repository](https://github.com/webb-tools/blueprint-template/issues).
+
+## 📜 License
+
+This project is licensed under the unlicense License. See the [LICENSE](./LICENSE) file for more details.
diff --git a/blueprints/ecdsa-threshold-mpc/blueprint.json b/blueprints/ecdsa-threshold-mpc/blueprint.json
new file mode 100644
index 000000000..2c9863423
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/blueprint.json
@@ -0,0 +1,23 @@
+{
+  "metadata": {
+    "name": "ecdsa-threshold-mpc",
+    "description": "A threshold MPC blueprint allowing key generation, threshold signing, and key refreshes.",
+    "author": "drewstone <drewstone329@gmail.com>",
+    "category": null,
+    "code_repository": "https://github.com/webb-tools/ecdsa-threshold-mpc",
+    "logo": null,
+    "website": "https://tangle.tools",
+    "license": "Unlicense"
+  },
+  "jobs": [],
+  "registration_hook": "None",
+  "registration_params": [],
+  "request_hook": "None",
+  "request_params": [],
+  "gadget": {
+    "Wasm": {
+      "runtime": "Wasmtime",
+      "sources": []
+    }
+  }
+}
\ No newline at end of file
diff --git a/blueprints/ecdsa-threshold-mpc/build.rs b/blueprints/ecdsa-threshold-mpc/build.rs
new file mode 100644
index 000000000..c77402e14
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+    println!("cargo:rerun-if-changed=src/main.rs");
+    blueprint_metadata::generate_json();
+}
diff --git a/blueprints/ecdsa-threshold-mpc/contracts/.gitignore b/blueprints/ecdsa-threshold-mpc/contracts/.gitignore
new file mode 100644
index 000000000..85198aaa5
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/contracts/.gitignore
@@ -0,0 +1,14 @@
+# Compiler files
+cache/
+out/
+
+# Ignores development broadcast logs
+!/broadcast
+/broadcast/*/31337/
+/broadcast/**/dry-run/
+
+# Docs
+docs/
+
+# Dotenv file
+.env
diff --git a/blueprints/ecdsa-threshold-mpc/contracts/README.md b/blueprints/ecdsa-threshold-mpc/contracts/README.md
new file mode 100644
index 000000000..9265b4558
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/contracts/README.md
@@ -0,0 +1,66 @@
+## Foundry
+
+**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
+
+Foundry consists of:
+
+-   **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
+-   **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
+-   **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
+-   **Chisel**: Fast, utilitarian, and verbose solidity REPL.
+
+## Documentation
+
+https://book.getfoundry.sh/
+
+## Usage
+
+### Build
+
+```shell
+$ forge build
+```
+
+### Test
+
+```shell
+$ forge test
+```
+
+### Format
+
+```shell
+$ forge fmt
+```
+
+### Gas Snapshots
+
+```shell
+$ forge snapshot
+```
+
+### Anvil
+
+```shell
+$ anvil
+```
+
+### Deploy
+
+```shell
+$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
+```
+
+### Cast
+
+```shell
+$ cast <subcommand>
+```
+
+### Help
+
+```shell
+$ forge --help
+$ anvil --help
+$ cast --help
+```
diff --git a/blueprints/ecdsa-threshold-mpc/contracts/remappings.txt b/blueprints/ecdsa-threshold-mpc/contracts/remappings.txt
new file mode 100644
index 000000000..d4921f786
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/contracts/remappings.txt
@@ -0,0 +1,10 @@
+@uniswap/v3-core/=lib/tnt-core/lib/uniswap-v3-core/contracts/
+clones/=lib/tnt-core/lib/clones-with-immutable-args/src/
+forge-std/=lib/forge-std/src/
+forge-test/=lib/tnt-core/lib/prb-test/src/
+math/=lib/tnt-core/lib/prb-math/src/
+openzeppelin-contracts-upgradeable/=lib/tnt-core/lib/openzeppelin-contracts-upgradeable/contracts/
+openzeppelin-contracts/=lib/tnt-core/lib/openzeppelin-contracts/contracts/
+solmate/=lib/tnt-core/lib/solmate/src/
+test/=lib/tnt-core/test/
+tnt-core/=lib/tnt-core/src/
\ No newline at end of file
diff --git a/blueprints/ecdsa-threshold-mpc/contracts/src/HelloBlueprint.sol b/blueprints/ecdsa-threshold-mpc/contracts/src/HelloBlueprint.sol
new file mode 100644
index 000000000..c159a3c03
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/contracts/src/HelloBlueprint.sol
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: UNLICENSE
+pragma solidity >=0.8.13;
+
+import "tnt-core/BlueprintServiceManager.sol";
+
+/**
+ * @title HelloBlueprint
+ * @dev This contract is an example of a service blueprint that provides a single service.
+ */
+contract HelloBlueprint is BlueprintServiceManager {
+    /**
+     * @dev Hook for service operator registration. Called when a service operator
+     * attempts to register with the blueprint.
+     * @param operator The operator's details.
+     * @param _registrationInputs Inputs required for registration.
+     */
+    function onRegister(bytes calldata operator, bytes calldata _registrationInputs)
+        public
+        payable
+        override
+        onlyFromRootChain
+    {
+        // Do something with the operator's details
+    }
+
+    /**
+     * @dev Hook for service instance requests. Called when a user requests a service
+     * instance from the blueprint.
+     * @param serviceId The ID of the requested service.
+     * @param operators The operators involved in the service.
+     * @param _requestInputs Inputs required for the service request.
+     */
+    function onRequest(uint64 serviceId, bytes[] calldata operators, bytes calldata _requestInputs)
+        public
+        payable
+        override
+        onlyFromRootChain
+    {
+        // Do something with the service request
+    }
+
+    /**
+     * @dev Hook for handling job call results. Called when operators send the result
+     * of a job execution.
+     * @param serviceId The ID of the service related to the job.
+     * @param job The job identifier.
+     * @param _jobCallId The unique ID for the job call.
+     * @param participant The participant (operator) sending the result.
+     * @param _inputs Inputs used for the job execution.
+     * @param _outputs Outputs resulting from the job execution.
+     */
+    function onJobCallResult(
+        uint64 serviceId,
+        uint8 job,
+        uint64 _jobCallId,
+        bytes calldata participant,
+        bytes calldata _inputs,
+        bytes calldata _outputs
+    ) public virtual override onlyFromRootChain {
+        // Do something with the job call result
+    }
+
+    /**
+     * @dev Verifies the result of a job call. This function is used to validate the
+     * outputs of a job execution against the expected results.
+     * @param serviceId The ID of the service related to the job.
+     * @param job The job identifier.
+     * @param jobCallId The unique ID for the job call.
+     * @param participant The participant (operator) whose result is being verified.
+     * @param inputs Inputs used for the job execution.
+     * @param outputs Outputs resulting from the job execution.
+     * @return bool Returns true if the job call result is verified successfully,
+     * otherwise false.
+     */
+    function verifyJobCallResult(
+        uint64 serviceId,
+        uint8 job,
+        uint64 jobCallId,
+        bytes calldata participant,
+        bytes calldata inputs,
+        bytes calldata outputs
+    ) public view virtual override onlyFromRootChain returns (bool) {
+        // Verify the job call result here
+        return true;
+    }
+
+    /**
+     * @dev Converts a public key to an operator address.
+     * @param publicKey The public key to convert.
+     * @return address The operator address.
+     */
+    function operatorAddressFromPublicKey(bytes calldata publicKey) internal pure returns (address) {
+        return address(uint160(uint256(keccak256(publicKey))));
+    }
+}
diff --git a/blueprints/ecdsa-threshold-mpc/foundry.toml b/blueprints/ecdsa-threshold-mpc/foundry.toml
new file mode 100644
index 000000000..26bed0549
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/foundry.toml
@@ -0,0 +1,10 @@
+[profile.default]
+src = "contracts/src"
+test = "contracts/test"
+out = "contracts/out"
+script = "contracts/script"
+cache_path = "contracts/cache"
+broadcast = "contracts/broadcast"
+libs = ["contracts/lib"]
+
+# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
diff --git a/blueprints/ecdsa-threshold-mpc/rust-toolchain.toml b/blueprints/ecdsa-threshold-mpc/rust-toolchain.toml
new file mode 100644
index 000000000..288fa153b
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/rust-toolchain.toml
@@ -0,0 +1,5 @@
+[toolchain]
+channel = "stable"
+components = ["rustfmt", "clippy", "rust-src"]
+targets = ["wasm32-unknown-unknown"]
+profile = "minimal"
diff --git a/blueprints/ecdsa-threshold-mpc/src/jobs/keygen.rs b/blueprints/ecdsa-threshold-mpc/src/jobs/keygen.rs
new file mode 100644
index 000000000..86e02cd96
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/jobs/keygen.rs
@@ -0,0 +1,125 @@
+use cggmp21::{
+    progress::PerfProfiler,
+    supported_curves::{Secp256k1, Secp256r1, Stark},
+};
+use gadget_sdk::{
+    self as sdk,
+    logger::Logger,
+    network::{channels::UserID, IdentifierInfo, ProtocolMessage},
+};
+use generic_ec::Curve;
+use rand::SeedableRng;
+use sdk::tangle_subxt::subxt::ext::futures::future::try_join_all;
+use sp_core::{ecdsa, keccak_256};
+use std::sync::Arc;
+use std::{collections::HashMap, convert::Infallible, fmt::format};
+use tokio::spawn;
+
+use color_eyre::Result;
+
+use crate::mpc::{keygen::run_full_keygen_protocol, DefaultCryptoHasher, DefaultSecurityLevel};
+
+use super::Context;
+
+/// The execution of these jobs represent a service instance with specific operators.
+///
+/// Things that are appealing to provide the developer are:
+/// - For networked protocols we need a unique identifier for protocol execution. Each job/task should have a unique identifier.
+///   If multiple messaging protocols are created in a single task, they should have unique identifiers. This should be clearly
+///   provided/exposed to the developer using our SDK. The variable names should likely indicate uniqueness.
+#[sdk::job(
+    id = 0,
+    params(curve, t, num_keys, hd_wallet),
+    result(_),
+    verifier(evm = "HelloBlueprint")
+)]
+pub async fn keygen(
+    ctx: Context,
+    curve: u8,
+    t: u16,
+    num_keys: u16,
+    hd_wallet: bool,
+) -> Result<String, crate::mpc::Error> {
+    // TODO: How to grab the specific operators? What index am I?
+    let n = 3 * (t + 1);
+    let i = 0;
+    // TODO: Constructing mapping from user IDs to ecdsa keys?
+    let mapping: HashMap<UserID, ecdsa::Public> = HashMap::new();
+    // TODO: How to get my ecdsa key?
+    let my_ecdsa_key = ecdsa::Public::from_raw([0u8; 33]);
+    // TODO: How to grab api / task / job specific onchain metadata?
+    // TODO: How to get restake information about this instance?
+    // TODO: How to get the service ID?
+    let service_id = 1u64;
+    let (session_id, block_id, task_id, retry_id) =
+        (Some(0u64), Some(0u64), Some(0u64), Some(0u64));
+    let identifier_info: IdentifierInfo = IdentifierInfo {
+        session_id,
+        block_id,
+        task_id,
+        retry_id,
+    };
+    let rng = rand::rngs::StdRng::from_entropy();
+    // TODO: How to get the raw job bytes?
+    let job_id_bytes = vec![0u8; 32];
+    let mix = keccak_256(b"cggmp21-keygen");
+    let eid_bytes = [&job_id_bytes[..], &mix[..]].concat();
+    let eid = cggmp21::ExecutionId::new(&eid_bytes);
+    let mix = keccak_256(b"cggmp21-keygen-aux");
+    let aux_eid_bytes = [&job_id_bytes[..], &mix[..]].concat();
+    let aux_eid = cggmp21::ExecutionId::new(&aux_eid_bytes);
+    let tracer = PerfProfiler::new();
+    let logger = Logger {
+        target: "cggmp21-keygen",
+        id: format!("{}", service_id),
+    };
+
+    // TODO: What is the protocol message channel? Can I get it from the network?
+    let (_dummy_sender, protocol_message_channel) =
+        futures::channel::mpsc::unbounded::<ProtocolMessage>();
+    let mut handles = Vec::new();
+    macro_rules! run_keygen_for_curve {
+        ($curve:ty) => {
+            for _ in 0..num_keys {
+                let handle = spawn(async move {
+                    run_full_keygen_protocol::<
+                        $curve,
+                        DefaultSecurityLevel,
+                        DefaultCryptoHasher,
+                        _,
+                        _,
+                    >(
+                        protocol_message_channel,
+                        identifier_info,
+                        Arc::new(mapping),
+                        my_ecdsa_key,
+                        ctx.network,
+                        tracer,
+                        eid,
+                        aux_eid,
+                        i,
+                        n,
+                        t,
+                        hd_wallet,
+                        rng,
+                        &logger,
+                        &job_id_bytes[..],
+                    )
+                    .await
+                });
+                handles.push(handle);
+            }
+        };
+    }
+
+    match curve {
+        0 => run_keygen_for_curve!(Secp256k1),
+        1 => run_keygen_for_curve!(Secp256r1),
+        2 => run_keygen_for_curve!(Stark),
+        _ => panic!("Invalid curve"),
+    }
+
+    let results = try_join_all(handles).await?;
+
+    Ok("Hello World!".to_string())
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/jobs/mod.rs b/blueprints/ecdsa-threshold-mpc/src/jobs/mod.rs
new file mode 100644
index 000000000..c6de69284
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/jobs/mod.rs
@@ -0,0 +1,19 @@
+use gadget_sdk::network::gossip::GossipHandle;
+
+pub mod keygen;
+pub mod refresh;
+pub mod sign;
+
+pub use keygen::*;
+pub use refresh::*;
+pub use sign::*;
+
+#[derive(Clone)]
+pub struct Context {
+    pub network: GossipHandle,
+}
+
+impl Context {
+    // TODO: Get my index in the network (deterministically)
+    // TODO: Get the mapping of all peers to their keys
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/jobs/refresh.rs b/blueprints/ecdsa-threshold-mpc/src/jobs/refresh.rs
new file mode 100644
index 000000000..873cab3c5
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/jobs/refresh.rs
@@ -0,0 +1,14 @@
+use gadget_sdk as sdk;
+use std::convert::Infallible;
+
+use color_eyre::Result;
+
+#[sdk::job(
+    id = 2,
+    params(keygen_id, new_parties, t),
+    result(_),
+    verifier(evm = "HelloBlueprint")
+)]
+pub fn refresh(keygen_id: u32, new_parties: Vec<u8>, t: u16) -> Result<String, Infallible> {
+    Ok("Hello World!".to_string())
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/jobs/sign.rs b/blueprints/ecdsa-threshold-mpc/src/jobs/sign.rs
new file mode 100644
index 000000000..c6cd6666d
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/jobs/sign.rs
@@ -0,0 +1,14 @@
+use gadget_sdk as sdk;
+use std::convert::Infallible;
+
+use color_eyre::Result;
+
+#[sdk::job(
+    id = 1,
+    params(msgs, is_prehashed),
+    result(_),
+    verifier(evm = "HelloBlueprint")
+)]
+pub fn sign(msgs: Vec<u8>, is_prehashed: bool) -> Result<String, Infallible> {
+    Ok("Hello World!".to_string())
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/main.rs b/blueprints/ecdsa-threshold-mpc/src/main.rs
new file mode 100644
index 000000000..b5b2e2a1f
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/main.rs
@@ -0,0 +1,63 @@
+use gadget_sdk as sdk;
+use std::convert::Infallible;
+use subxt::PolkadotConfig;
+
+use color_eyre::{eyre::OptionExt, Result};
+use sdk::{
+    events_watcher::tangle::TangleEventsWatcher, keystore::backend::GenericKeyStore,
+    keystore::Backend, tangle_subxt::*,
+};
+
+pub mod jobs;
+pub mod mpc;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    color_eyre::install()?;
+
+    // Initialize the logger
+    let env_filter = tracing_subscriber::EnvFilter::from_default_env();
+    tracing_subscriber::fmt()
+        .compact()
+        .with_target(true)
+        .with_env_filter(env_filter)
+        .init();
+
+    // Initialize the environment
+    let env = sdk::config::load(None)?;
+    let keystore = env.keystore()?;
+    let signer = env.first_signer()?;
+    let client: subxt::OnlineClient<PolkadotConfig> =
+        subxt::OnlineClient::from_url(&env.rpc_endpoint).await?;
+
+    // // Create the event handler from the job
+    // let keygen_job = KeygenEventHandler {
+    //     service_id: env.service_id,
+    //     signer,
+    // };
+
+    // let signing_job = SigningEventHandler {
+    //     service_id: env.service_id,
+    //     signer,
+    // };
+
+    // let key_refresh_job = KeyRefreshEventHandler {
+    //     service_id: env.service_id,
+    //     signer,
+    // };
+
+    // tracing::info!("Starting the event watcher ...");
+
+    // SubstrateEventWatcher::run(
+    //     &TangleEventsWatcher,
+    //     client,
+    //     // Add more handler here if we have more functions.
+    //     vec![
+    //         Box::new(keygen_job),
+    //         Box::new(signing_job),
+    //         Box::new(key_refresh_job),
+    //     ],
+    // )
+    // .await?;
+    Ok(())
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/mpc/keygen.rs b/blueprints/ecdsa-threshold-mpc/src/mpc/keygen.rs
new file mode 100644
index 000000000..4c5b7304b
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/mpc/keygen.rs
@@ -0,0 +1,213 @@
+use cggmp21::generic_ec::Curve;
+use cggmp21::key_refresh::msg::aux_only;
+use cggmp21::key_refresh::AuxInfoGenerationBuilder;
+use cggmp21::key_share::DirtyKeyShare;
+use cggmp21::key_share::Validate;
+use cggmp21::keygen::msg::threshold::Msg;
+use cggmp21::keygen::KeygenBuilder;
+use cggmp21::progress::PerfProfiler;
+use cggmp21::security_level::SecurityLevel;
+use cggmp21::supported_curves::{Secp256k1, Secp256r1, Stark};
+use cggmp21::KeyShare;
+use cggmp21::PregeneratedPrimes;
+use digest::typenum::U32;
+use digest::Digest;
+use gadget_sdk::logger::Logger;
+use gadget_sdk::network::channels::create_job_manager_to_async_protocol_channel_split_io;
+use gadget_sdk::network::channels::UserID;
+use gadget_sdk::network::deserialize;
+use gadget_sdk::network::serialize;
+use gadget_sdk::network::IdentifierInfo;
+use gadget_sdk::network::Network;
+use gadget_sdk::network::ProtocolMessage;
+use gadget_sdk::store::KeyValueStoreBackend;
+use itertools::Itertools;
+use rand::rngs::{OsRng, StdRng};
+use rand::{CryptoRng, RngCore, SeedableRng};
+use round_based::{Delivery, Incoming, MpcParty, Outgoing};
+use serde::Serialize;
+use sp_core::keccak_256;
+use sp_core::{ecdsa, Pair};
+use std::collections::{BTreeMap, HashMap};
+use std::sync::Arc;
+use tokio::sync::mpsc::UnboundedReceiver;
+
+use crate::mpc::{DefaultCryptoHasher, DefaultSecurityLevel};
+
+use super::Error;
+
+#[allow(clippy::too_many_arguments)]
+pub async fn run_and_serialize_keygen<
+    'r,
+    E: Curve,
+    S: SecurityLevel,
+    H: Digest<OutputSize = U32> + Clone + Send + 'static,
+    D,
+    R,
+>(
+    logger: &Logger,
+    tracer: &mut PerfProfiler,
+    eid: cggmp21::ExecutionId<'r>,
+    i: u16,
+    n: u16,
+    t: u16,
+    hd_wallet: bool,
+    party: MpcParty<Msg<E, S, H>, D>,
+    mut rng: R,
+) -> Result<Vec<u8>, Error>
+where
+    D: Delivery<Msg<E, S, H>>,
+    R: RngCore + CryptoRng,
+{
+    let builder = KeygenBuilder::<E, S, H>::new(eid, i, n);
+    let incomplete_key_share = builder
+        .set_progress_tracer(tracer)
+        .set_threshold(t)
+        .hd_wallet(hd_wallet)
+        .start(&mut rng, party)
+        .await?;
+    logger.debug("Finished AsyncProtocol - Keygen");
+    serialize(&incomplete_key_share).map_err(Into::into)
+}
+
+#[allow(clippy::too_many_arguments)]
+pub async fn run_and_serialize_keyrefresh<
+    'r,
+    E: Curve,
+    S: SecurityLevel,
+    H: Digest<OutputSize = U32> + Clone + Send + 'static,
+    D,
+>(
+    logger: &Logger,
+    incomplete_key_share: Vec<u8>,
+    pregenerated_primes: PregeneratedPrimes<S>,
+    tracer: &mut PerfProfiler,
+    aux_eid: cggmp21::ExecutionId<'r>,
+    i: u16,
+    n: u16,
+    party: MpcParty<aux_only::Msg<H, S>, D>,
+    mut rng: StdRng,
+) -> Result<(Vec<u8>, Vec<u8>), Error>
+where
+    D: Delivery<aux_only::Msg<H, S>>,
+{
+    let incomplete_key_share: cggmp21::key_share::Valid<
+        cggmp21::key_share::DirtyIncompleteKeyShare<E>,
+    > = deserialize(&incomplete_key_share)?;
+
+    let aux_info_builder =
+        AuxInfoGenerationBuilder::<S, H>::new_aux_gen(aux_eid, i, n, pregenerated_primes);
+
+    let aux_info = aux_info_builder
+        .set_progress_tracer(tracer)
+        .start(&mut rng, party)
+        .await?;
+    let perf_report = tracer.get_report()?;
+    logger.trace(format!("Aux info protocol report: {perf_report}"));
+    logger.debug("Finished AsyncProtocol - Aux Info");
+
+    let key_share: KeyShare<E, S> = DirtyKeyShare {
+        core: incomplete_key_share.into_inner(),
+        aux: aux_info.into_inner(),
+    }
+    .validate()
+    .map_err(|e| Error::ValidateError(e.to_string()))?;
+    // Serialize the key share and the public key
+    serialize(&key_share)
+        .map(|ks| (ks, key_share.shared_public_key.to_bytes(true).to_vec()))
+        .map_err(Into::into)
+}
+
+async fn pregenerate_primes<S: SecurityLevel, KBE: KeyValueStoreBackend>(
+    tracer: &PerfProfiler,
+    logger: &Logger,
+    job_id_bytes: &[u8],
+) -> Result<(PerfProfiler, PregeneratedPrimes<S>), Error> {
+    let perf_report = tracer.get_report()?;
+    logger.trace(format!("Incomplete Keygen protocol report: {perf_report}"));
+    logger.debug("Finished AsyncProtocol - Incomplete Keygen");
+    let tracer = PerfProfiler::new();
+
+    let pregenerated_primes_key =
+        keccak_256(&[&b"dfns-cggmp21-keygen-primes"[..], job_id_bytes].concat());
+    let now = tokio::time::Instant::now();
+    let pregenerated_primes = tokio::task::spawn_blocking(|| {
+        let mut rng = OsRng;
+        cggmp21::PregeneratedPrimes::<S>::generate(&mut rng)
+    })
+    .await?;
+
+    let elapsed = now.elapsed();
+    logger.debug(format!("Pregenerated primes took {elapsed:?}"));
+
+    Ok((tracer, pregenerated_primes))
+}
+
+#[allow(clippy::too_many_arguments)]
+pub async fn run_full_keygen_protocol<
+    'a,
+    E: Curve,
+    S: SecurityLevel,
+    H: Digest<OutputSize = U32> + Clone + Send + 'static,
+    KBE: KeyValueStoreBackend,
+    N: Network,
+>(
+    protocol_message_channel: UnboundedReceiver<ProtocolMessage>,
+    identifier_info: IdentifierInfo,
+    mapping: Arc<HashMap<UserID, ecdsa::Public>>,
+    my_role_id: ecdsa::Public,
+    network: N,
+    mut tracer: PerfProfiler,
+    eid: cggmp21::ExecutionId<'a>,
+    aux_eid: cggmp21::ExecutionId<'a>,
+    i: u16,
+    n: u16,
+    t: u16,
+    hd_wallet: bool,
+    rng: StdRng,
+    logger: &Logger,
+    job_id_bytes: &[u8],
+) -> Result<(Vec<u8>, Vec<u8>), Error> {
+    let (tx0, rx0, tx1, rx1) = create_job_manager_to_async_protocol_channel_split_io(
+        protocol_message_channel,
+        identifier_info,
+        mapping,
+        my_role_id,
+        network,
+        logger.clone(),
+        i,
+    );
+    let delivery = (rx0, tx0);
+    let party = MpcParty::<Msg<E, S, H>, _, _>::connected(delivery);
+    let incomplete_key_share = run_and_serialize_keygen::<E, S, H, _, _>(
+        logger,
+        &mut tracer,
+        eid,
+        i,
+        n,
+        t,
+        hd_wallet,
+        party,
+        rng.clone(),
+    )
+    .await?;
+    let (mut tracer, pregenerated_primes) =
+        pregenerate_primes::<S, KBE>(&tracer, logger, job_id_bytes).await?;
+
+    let delivery = (rx1, tx1);
+    let party = MpcParty::<aux_only::Msg<H, S>, _, _>::connected(delivery);
+    let (key_share, serialized_public_key) = run_and_serialize_keyrefresh::<E, S, H, _>(
+        logger,
+        incomplete_key_share,
+        pregenerated_primes,
+        &mut tracer,
+        aux_eid,
+        i,
+        n,
+        party,
+        rng,
+    )
+    .await?;
+
+    Ok((key_share, serialized_public_key))
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/mpc/mod.rs b/blueprints/ecdsa-threshold-mpc/src/mpc/mod.rs
new file mode 100644
index 000000000..59b67f033
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/mpc/mod.rs
@@ -0,0 +1,36 @@
+use std::collections::HashMap;
+
+use cggmp21::security_level::SecurityLevel128;
+use cggmp21::{key_share::InvalidKeyShare, progress::ProfileError, security_level::SecurityLevel};
+use generic_ec::Curve;
+use sha2::Sha256;
+use sp_core::ecdsa;
+
+pub mod keygen;
+pub mod refresh;
+pub mod sign;
+
+pub type DefaultSecurityLevel = SecurityLevel128;
+pub type DefaultCryptoHasher = Sha256;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("IO error")]
+    Cggmp21IoError(String),
+    #[error("SDK error")]
+    SdkError(#[from] gadget_sdk::Error),
+    #[error("Serialization error")]
+    SerializationError(#[from] serde_json::Error),
+    #[error("Keygen error")]
+    KeygenError(#[from] cggmp21::keygen::KeygenError),
+    #[error("Sign error")]
+    SignError(#[from] cggmp21::signing::SigningError),
+    #[error("Key refresh error")]
+    KeyRefreshError(#[from] cggmp21::key_refresh::KeyRefreshError),
+    #[error("Invalid key share")]
+    ValidateError(String),
+    #[error("Tokio join error")]
+    JoinError(#[from] tokio::task::JoinError),
+    #[error("Tracer profile error")]
+    TracerProfileError(#[from] cggmp21::progress::ProfileError),
+}
diff --git a/blueprints/ecdsa-threshold-mpc/src/mpc/refresh.rs b/blueprints/ecdsa-threshold-mpc/src/mpc/refresh.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/mpc/refresh.rs
@@ -0,0 +1,1 @@
+
diff --git a/blueprints/ecdsa-threshold-mpc/src/mpc/sign.rs b/blueprints/ecdsa-threshold-mpc/src/mpc/sign.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/src/mpc/sign.rs
@@ -0,0 +1,1 @@
+
diff --git a/blueprints/ecdsa-threshold-mpc/taplo.toml b/blueprints/ecdsa-threshold-mpc/taplo.toml
new file mode 100644
index 000000000..3b9f0df9a
--- /dev/null
+++ b/blueprints/ecdsa-threshold-mpc/taplo.toml
@@ -0,0 +1,4 @@
+[formatting]
+align_entries = false
+array_auto_expand = true
+column_width = 120
diff --git a/blueprints/incredible-squaring/Cargo.toml b/blueprints/incredible-squaring/Cargo.toml
index fe234a7be..d1e036887 100644
--- a/blueprints/incredible-squaring/Cargo.toml
+++ b/blueprints/incredible-squaring/Cargo.toml
@@ -7,7 +7,6 @@ edition.workspace = true
 license.workspace = true
 homepage.workspace = true
 repository.workspace = true
-spec_version.workspace = true
 
 [[bin]]
 name = "incredible-squaring-gadget"
diff --git a/common/Cargo.toml b/common/Cargo.toml
deleted file mode 100644
index 8a98b3f09..000000000
--- a/common/Cargo.toml
+++ /dev/null
@@ -1,61 +0,0 @@
-[package]
-name = "gadget-common"
-version = "0.1.0"
-edition = "2021"
-description = "Common utilities for writing gadgets for Tangle blueprint"
-homepage.workspace = true
-license.workspace = true
-
-[dependencies]
-async-trait = { workspace = true }
-auto_impl = { workspace = true }
-cfg-if = { workspace = true }
-futures = { workspace = true }
-gadget-core = { workspace = true }
-gadget-io = { workspace = true }
-hex = { workspace = true }
-lazy_static = { workspace = true }
-libsecp256k1 = { workspace = true }
-log = { workspace = true }
-parking_lot = { workspace = true }
-prometheus = { workspace = true }
-round-based = { workspace = true, features = ["derive"] }
-serde = { workspace = true, features = ["derive", "alloc"] }
-serde_json = { workspace = true }
-sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"], optional = true }
-thiserror = { workspace = true }
-
-# Substrate
-parity-scale-codec = { workspace = true, optional = true }
-sp-core = { workspace = true, features = ["full_crypto"] }
-sp-io = { workspace = true, features = ["disable_allocator", "disable_panic_handler", "disable_oom"] }
-sp-runtime = { workspace = true, features = ["serde"] }
-substrate-prometheus-endpoint = { workspace = true, optional = true }
-subxt-signer = { workspace = true, features = ["subxt", "sr25519"], optional = true }
-tangle-subxt = { workspace = true, optional = true }
-
-[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
-getrandom = { workspace = true, features = ["js"] }
-
-[features]
-default = ["substrate", "std", "tangle-testnet"]
-tangle-testnet = []
-tangle-mainnet = []
-substrate = [
-    "dep:parity-scale-codec",
-    "dep:tangle-subxt",
-    "dep:substrate-prometheus-endpoint",
-    "dep:subxt-signer",
-    "gadget-core/substrate",
-]
-std = [
-    "dep:sqlx",
-    "gadget-io/std",
-    "gadget-core/std",
-    "serde/std",
-    "sp-io/std",
-    "sp-runtime/std",
-    "tangle-subxt?/std",
-]
-wasm = ["subxt-signer?/web", "tangle-subxt?/web"]
-testing = []
diff --git a/common/src/client.rs b/common/src/client.rs
deleted file mode 100644
index 9b95c0b2e..000000000
--- a/common/src/client.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use crate::debug_logger::DebugLogger;
-use crate::environments::GadgetEnvironment;
-use async_trait::async_trait;
-use gadget_core::gadget::general::Client;
-#[cfg(feature = "substrate")]
-use sp_core::Pair;
-use std::sync::Arc;
-#[cfg(feature = "substrate")]
-use tangle_subxt::subxt::{self};
-
-pub struct JobsClient<Env: GadgetEnvironment> {
-    pub client: Arc<Env::Client>,
-    logger: DebugLogger,
-    pub(crate) tx_manager: Env::TransactionManager,
-}
-
-impl<Env: GadgetEnvironment> JobsClient<Env> {
-    pub fn logger(&self) -> &DebugLogger {
-        &self.logger
-    }
-}
-
-impl<Env: GadgetEnvironment> Clone for JobsClient<Env> {
-    fn clone(&self) -> Self {
-        Self {
-            client: self.client.clone(),
-            logger: self.logger.clone(),
-            tx_manager: self.tx_manager.clone(),
-        }
-    }
-}
-
-pub async fn create_client<Env: GadgetEnvironment>(
-    client: Env::Client,
-    logger: DebugLogger,
-    tx_manager: Env::TransactionManager,
-) -> Result<JobsClient<Env>, crate::Error> {
-    Ok(JobsClient {
-        client: Arc::new(client),
-        logger,
-        tx_manager,
-    })
-}
-
-pub async fn exec_client_function<C, F, T>(client: &C, function: F) -> T
-where
-    for<'a> F: FnOnce(&'a C) -> T,
-    C: Clone + Send + Sync + 'static,
-    T: Send + 'static,
-    F: Send + 'static,
-{
-    let client = client.clone();
-    gadget_io::tokio::task::spawn_blocking(move || function(&client))
-        .await
-        .expect("Failed to spawn blocking task")
-}
-
-#[async_trait]
-impl<Env: GadgetEnvironment> Client<Env::Event> for JobsClient<Env> {
-    async fn next_event(&self) -> Option<Env::Event> {
-        self.client.next_event().await
-    }
-
-    async fn latest_event(&self) -> Option<Env::Event> {
-        self.client.latest_event().await
-    }
-}
-
-/// A [`Signer`] implementation that can be constructed from an [`sp_core::Pair`].
-#[cfg(feature = "substrate")]
-#[derive(Clone)]
-pub struct PairSigner<T: subxt::Config> {
-    account_id: T::AccountId,
-    signer: sp_core::sr25519::Pair,
-}
-
-#[cfg(feature = "substrate")]
-impl<T: subxt::Config> PairSigner<T>
-where
-    T::AccountId: From<[u8; 32]>,
-{
-    pub fn new(signer: sp_core::sr25519::Pair) -> Self {
-        let account_id = T::AccountId::from(signer.public().into());
-        Self { account_id, signer }
-    }
-}
-
-#[cfg(feature = "substrate")]
-impl<T: subxt::Config> subxt::tx::Signer<T> for PairSigner<T>
-where
-    T::Signature: From<subxt::utils::MultiSignature>,
-{
-    fn account_id(&self) -> T::AccountId {
-        self.account_id.clone()
-    }
-
-    fn address(&self) -> T::Address {
-        self.account_id.clone().into()
-    }
-
-    fn sign(&self, signer_payload: &[u8]) -> T::Signature {
-        subxt::utils::MultiSignature::Sr25519(self.signer.sign(signer_payload).0).into()
-    }
-}
diff --git a/common/src/config.rs b/common/src/config.rs
deleted file mode 100644
index 2d130f3bd..000000000
--- a/common/src/config.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-use crate::client::{create_client, JobsClient};
-pub use crate::debug_logger::DebugLogger;
-use crate::environments::GadgetEnvironment;
-pub use crate::module::network::Network;
-pub use crate::module::GadgetProtocol;
-pub use crate::prometheus::PrometheusConfig;
-use async_trait::async_trait;
-
-#[async_trait]
-pub trait ProtocolConfig<Env: GadgetEnvironment>
-where
-    Self: Sized,
-{
-    type Network: Network<Env>;
-    type Protocol: GadgetProtocol<Env>;
-    type ProtocolSpecificConfiguration: Clone + NetworkAndProtocolSetup<Env> + Sync;
-    fn params(&self) -> &Self::ProtocolSpecificConfiguration;
-
-    fn take_network(&mut self) -> Self::Network;
-    fn take_protocol(&mut self) -> Self::Protocol;
-    fn take_client(&mut self) -> <Env as GadgetEnvironment>::Client;
-    fn prometheus_config(&self) -> PrometheusConfig;
-
-    async fn build(&self) -> Result<Self, crate::Error> {
-        let jobs_client = self.params().build_jobs_client().await?;
-        let (network, protocol) = self
-            .params()
-            .build_network_and_protocol(jobs_client)
-            .await?;
-        let client = self.params().client();
-        let params = self.params().clone();
-        let tx_manager = self.tx_manager();
-        let logger = self.logger();
-        let prometheus_config = self.prometheus_config();
-
-        Ok(Self::new(
-            network,
-            client,
-            protocol,
-            params,
-            tx_manager,
-            logger,
-            prometheus_config,
-        ))
-    }
-
-    fn new(
-        network: <Self::ProtocolSpecificConfiguration as NetworkAndProtocolSetup<Env>>::Network,
-        client: <Env as GadgetEnvironment>::Client,
-        protocol: <<Self as ProtocolConfig<Env>>::ProtocolSpecificConfiguration as NetworkAndProtocolSetup<Env>>::Protocol,
-        params: Self::ProtocolSpecificConfiguration,
-        tx_manager: <Env as GadgetEnvironment>::TransactionManager,
-        logger: DebugLogger,
-        prometheus_config: PrometheusConfig,
-    ) -> Self;
-
-    fn tx_manager(&self) -> <Env as GadgetEnvironment>::TransactionManager {
-        self.params().tx_manager()
-    }
-    fn logger(&self) -> DebugLogger {
-        self.params().logger()
-    }
-
-    async fn run(self) -> Result<(), crate::Error> {
-        crate::run_protocol(self).await
-    }
-}
-
-#[async_trait]
-pub trait NetworkAndProtocolSetup<Env: GadgetEnvironment> {
-    type Network;
-    type Protocol;
-
-    async fn build_jobs_client(&self) -> Result<JobsClient<Env>, crate::Error> {
-        create_client(self.client(), self.logger(), self.tx_manager()).await
-    }
-
-    async fn build_network_and_protocol(
-        &self,
-        jobs_client: JobsClient<Env>,
-    ) -> Result<(Self::Network, Self::Protocol), crate::Error>;
-    fn tx_manager(&self) -> <Env as GadgetEnvironment>::TransactionManager;
-    fn logger(&self) -> DebugLogger;
-    fn client(&self) -> <Env as GadgetEnvironment>::Client;
-}
diff --git a/common/src/debug_logger.rs b/common/src/debug_logger.rs
deleted file mode 100644
index b1d577476..000000000
--- a/common/src/debug_logger.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use alloc::string::String;
-use core::fmt::Display;
-
-#[derive(Clone, Default)]
-pub struct DebugLogger {
-    pub id: String,
-}
-
-impl DebugLogger {
-    pub fn trace<T: Display>(&self, msg: T) {
-        if self.id.is_empty() {
-            log::trace!(target: "gadget", "{msg}");
-            return;
-        }
-        log::trace!(target: "gadget", "[{}] {msg}", &self.id);
-    }
-
-    pub fn debug<T: Display>(&self, msg: T) {
-        if self.id.is_empty() {
-            log::debug!(target: "gadget", "{msg}");
-            return;
-        }
-        log::debug!(target: "gadget", "[{}] {msg}", &self.id);
-    }
-
-    pub fn info<T: Display>(&self, msg: T) {
-        if self.id.is_empty() {
-            log::info!(target: "gadget", "{msg}");
-            return;
-        }
-        log::info!(target: "gadget", "[{}] {msg}", &self.id);
-    }
-
-    pub fn warn<T: Display>(&self, msg: T) {
-        if self.id.is_empty() {
-            log::warn!(target: "gadget", "{msg}");
-            return;
-        }
-        log::warn!(target: "gadget", "[{}] {msg}", &self.id);
-    }
-
-    pub fn error<T: Display>(&self, msg: T) {
-        if self.id.is_empty() {
-            log::error!(target: "gadget", "{msg}");
-            return;
-        }
-        log::error!(target: "gadget", "[{}] {msg}", &self.id);
-    }
-}
diff --git a/common/src/environments/mod.rs b/common/src/environments/mod.rs
deleted file mode 100644
index e65c491c4..000000000
--- a/common/src/environments/mod.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use alloc::boxed::Box;
-use alloc::fmt::{Debug, Display};
-use alloc::string::String;
-use alloc::vec::Vec;
-use async_trait::async_trait;
-use core::error::Error;
-use gadget_core::gadget::general::Client;
-use gadget_core::job::protocol::{ProtocolMessageMetadata, WorkManagerInterface};
-use serde::{Deserialize, Serialize};
-use sp_core::ecdsa;
-
-pub trait EventMetadata<Env: GadgetEnvironment> {
-    fn number(&self) -> <Env as GadgetEnvironment>::Clock;
-}
-
-#[async_trait]
-pub trait GadgetEnvironment: Debug + Sized + 'static
-where
-    Self::WorkManager: WorkManagerInterface<
-        Clock = Self::Clock,
-        RetryID = Self::RetryID,
-        TaskID = Self::TaskID,
-        SessionID = Self::SessionID,
-        Error = Self::Error,
-        ProtocolMessage = Self::ProtocolMessage,
-    >,
-{
-    type Event: EventMetadata<Self> + Send + Sync + 'static;
-    type ProtocolMessage: Serialize
-        + for<'de> Deserialize<'de>
-        + Send
-        + Sync
-        + 'static
-        + ProtocolMessageMetadata<Self::WorkManager>;
-    type Client: Client<<Self as GadgetEnvironment>::Event> + Send + Sync + 'static;
-    type WorkManager: WorkManagerInterface;
-    type Error: Error + Send + Sync + From<String> + Into<crate::Error> + 'static;
-    type Clock: Display + Copy + Send + Sync + 'static;
-    type RetryID: Display + Copy + Send + Sync + 'static;
-    type TaskID: Debug + Copy + Send + Sync + 'static;
-    type SessionID: Display + Copy + Send + Sync + 'static;
-    type TransactionManager: Clone + Send + Sync + 'static;
-    type JobInitMetadata: Send + Sync + 'static;
-
-    #[allow(clippy::too_many_arguments)]
-    fn build_protocol_message<Payload: Serialize>(
-        associated_block_id: <Self::WorkManager as WorkManagerInterface>::Clock,
-        associated_session_id: <Self::WorkManager as WorkManagerInterface>::SessionID,
-        associated_retry_id: <Self::WorkManager as WorkManagerInterface>::RetryID,
-        associated_task_id: <Self::WorkManager as WorkManagerInterface>::TaskID,
-        from: u16,
-        to: Option<u16>,
-        payload: &Payload,
-        from_account_id: Option<ecdsa::Public>,
-        to_network_id: Option<ecdsa::Public>,
-    ) -> Self::ProtocolMessage;
-
-    async fn setup_runtime(&self) -> Result<Self::Client, Self::Error>;
-
-    fn transaction_manager(&self) -> Self::TransactionManager;
-
-    fn set_payload(&mut self, input: Vec<u8>, output: &mut Vec<u8>) {
-        *output = input;
-    }
-}
diff --git a/common/src/full_protocol.rs b/common/src/full_protocol.rs
deleted file mode 100644
index 628995f55..000000000
--- a/common/src/full_protocol.rs
+++ /dev/null
@@ -1,292 +0,0 @@
-use crate::client::JobsClient;
-use crate::config::{DebugLogger, GadgetProtocol, Network, NetworkAndProtocolSetup};
-use crate::environments::GadgetEnvironment;
-use crate::keystore::{ECDSAKeyStore, KeystoreBackend};
-use crate::module::WorkManagerConfig;
-use crate::prometheus::PrometheusConfig;
-use crate::protocol::AsyncProtocol;
-use crate::Error;
-use async_trait::async_trait;
-use gadget_core::gadget::AbstractGadget;
-use gadget_core::job::builder::BuiltExecutableJobWrapper;
-use gadget_core::job::error::JobError;
-use gadget_core::job::manager::ProtocolWorkManager;
-use gadget_core::job::protocol::ProtocolMessageMetadata;
-use gadget_io::tokio::sync::mpsc::UnboundedReceiver;
-use parity_scale_codec::{Decode, Encode};
-use parking_lot::Mutex;
-use parking_lot::RwLock;
-use sp_core::ecdsa::{Public, Signature};
-use sp_core::{keccak_256, sr25519};
-use std::sync::Arc;
-
-pub type SharedOptional<T> = Arc<Mutex<Option<T>>>;
-
-#[async_trait]
-pub trait FullProtocolConfig<Env: GadgetEnvironment>:
-    Clone + Send + Sync + Sized + 'static
-{
-    type AsyncProtocolParameters: Send + Sync + Clone + 'static;
-    type Network: Network<Env>;
-    type AdditionalNodeParameters: Clone + Send + Sync + 'static;
-    type KeystoreBackend: KeystoreBackend;
-
-    async fn new(
-        client: <Env as GadgetEnvironment>::Client,
-        tx_manager: <Env as GadgetEnvironment>::TransactionManager,
-        network: Self::Network,
-        logger: DebugLogger,
-        account_id: sr25519::Public,
-        key_store: ECDSAKeyStore<Self::KeystoreBackend>,
-        prometheus_config: PrometheusConfig,
-    ) -> Result<Self, <Env as GadgetEnvironment>::Error>;
-
-    async fn generate_protocol_from(
-        &self,
-        associated_block_id: <Env as GadgetEnvironment>::Clock,
-        associated_retry_id: <Env as GadgetEnvironment>::RetryID,
-        associated_session_id: <Env as GadgetEnvironment>::SessionID,
-        associated_task_id: <Env as GadgetEnvironment>::TaskID,
-        protocol_message_rx: UnboundedReceiver<<Env as GadgetEnvironment>::ProtocolMessage>,
-        additional_params: Self::AsyncProtocolParameters,
-    ) -> Result<BuiltExecutableJobWrapper, JobError>;
-
-    /// This function returns the internally-used network for the protocol.
-    ///
-    /// Important: This function should never be used directly, it is for internal use only.
-    /// Instead, `Self` implements `Network` and should instead be used for networking, as it
-    /// grants metrics and message signing and verification to messages
-    fn internal_network(&self) -> &Self::Network;
-    /// Given an input of metadata for a job, return a set of parameters that can be used to
-    /// construct a protocol.
-    ///
-    /// The provided work manager should only be used for querying recorded_messages
-    async fn create_next_job(
-        &self,
-        job: <Env as GadgetEnvironment>::JobInitMetadata,
-        work_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
-    ) -> Result<Self::AsyncProtocolParameters, <Env as GadgetEnvironment>::Error>;
-
-    async fn process_error(
-        &self,
-        error: Error,
-        _job_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
-    ) {
-        self.logger().error(format!("Error in protocol: {error}"));
-    }
-
-    fn account_id(&self) -> &sr25519::Public;
-    fn name(&self) -> String;
-    fn jobs_client(&self) -> &SharedOptional<JobsClient<Env>>;
-    fn tx_manager(&self) -> <Env as GadgetEnvironment>::TransactionManager;
-    fn logger(&self) -> DebugLogger;
-    fn client(&self) -> <Env as GadgetEnvironment>::Client;
-    fn get_jobs_client(&self) -> JobsClient<Env> {
-        self.jobs_client()
-            .lock()
-            .clone()
-            .expect("Jobs client not initialized")
-    }
-    fn key_store(&self) -> &ECDSAKeyStore<Self::KeystoreBackend>;
-    fn get_work_manager_config(&self) -> WorkManagerConfig {
-        Default::default()
-    }
-}
-
-#[async_trait]
-impl<Env: GadgetEnvironment, T: FullProtocolConfig<Env>> AsyncProtocol<Env> for T
-where
-    T: FullProtocolConfig<Env>,
-{
-    type AdditionalParams = T::AsyncProtocolParameters;
-
-    async fn generate_protocol_from(
-        &self,
-        associated_block_id: <Env as GadgetEnvironment>::Clock,
-        associated_retry_id: <Env as GadgetEnvironment>::RetryID,
-        associated_session_id: <Env as GadgetEnvironment>::SessionID,
-        associated_task_id: <Env as GadgetEnvironment>::TaskID,
-        protocol_message_rx: UnboundedReceiver<<Env as GadgetEnvironment>::ProtocolMessage>,
-        additional_params: <T as FullProtocolConfig<Env>>::AsyncProtocolParameters,
-    ) -> Result<BuiltExecutableJobWrapper, JobError> {
-        T::generate_protocol_from(
-            self,
-            associated_block_id,
-            associated_retry_id,
-            associated_session_id,
-            associated_task_id,
-            protocol_message_rx,
-            additional_params,
-        )
-        .await
-    }
-}
-
-#[async_trait]
-impl<Env, T> NetworkAndProtocolSetup<Env> for T
-where
-    Env: GadgetEnvironment,
-    T: FullProtocolConfig<Env>,
-{
-    type Network = T;
-    type Protocol = T;
-
-    async fn build_network_and_protocol(
-        &self,
-        jobs_client: JobsClient<Env>,
-    ) -> Result<(Self::Network, Self::Protocol), Error> {
-        let jobs_client_store = T::jobs_client(self);
-        jobs_client_store.lock().replace(jobs_client);
-        Ok((self.clone(), self.clone()))
-    }
-
-    fn tx_manager(&self) -> <Env as GadgetEnvironment>::TransactionManager {
-        T::tx_manager(self)
-    }
-
-    fn logger(&self) -> DebugLogger {
-        T::logger(self)
-    }
-
-    fn client(&self) -> <Env as GadgetEnvironment>::Client {
-        T::client(self)
-    }
-}
-
-#[async_trait]
-impl<Env: GadgetEnvironment<Error = Error>, T: FullProtocolConfig<Env> + AsyncProtocol<Env>>
-    GadgetProtocol<Env> for T
-where
-    T: AsyncProtocol<Env, AdditionalParams = T::AsyncProtocolParameters>,
-{
-    async fn create_next_job(
-        &self,
-        job: <Env as GadgetEnvironment>::JobInitMetadata,
-        work_manager: &ProtocolWorkManager<Env::WorkManager>,
-    ) -> Result<<Self as AsyncProtocol<Env>>::AdditionalParams, Env::Error> {
-        T::create_next_job(self, job, work_manager).await
-    }
-
-    async fn process_event(
-        &self,
-        notification: <Env as GadgetEnvironment>::Event,
-    ) -> Result<(), Env::Error> {
-        T::process_event(self, notification).await
-    }
-
-    async fn process_error(
-        &self,
-        error: Env::Error,
-        job_manager: &ProtocolWorkManager<Env::WorkManager>,
-    ) {
-        T::process_error(self, error, job_manager).await;
-    }
-
-    async fn generate_work_manager(
-        &self,
-        clock: Arc<RwLock<Option<<Env as GadgetEnvironment>::Clock>>>,
-    ) -> <Env as GadgetEnvironment>::WorkManager {
-        T::generate_work_manager(self, clock).await
-    }
-
-    fn account_id(&self) -> &sr25519::Public {
-        T::account_id(self)
-    }
-
-    fn name(&self) -> String {
-        T::name(self)
-    }
-
-    fn client(&self) -> JobsClient<Env> {
-        T::get_jobs_client(self)
-    }
-
-    fn logger(&self) -> DebugLogger {
-        T::logger(self)
-    }
-
-    fn get_work_manager_config(&self) -> WorkManagerConfig {
-        T::get_work_manager_config(self)
-    }
-}
-
-#[async_trait]
-impl<Env: GadgetEnvironment, T: FullProtocolConfig<Env> + AbstractGadget> Network<Env> for T
-where
-    T: AbstractGadget<ProtocolMessage = Env::ProtocolMessage>,
-{
-    async fn next_message(&self) -> Option<Env::ProtocolMessage> {
-        let mut message = T::internal_network(self).next_message().await?;
-        let peer_public_key = message.sender_network_id();
-        if let Some(peer_public_key) = peer_public_key {
-            if let Ok(payload_and_signature) =
-                <PayloadAndSignature as Decode>::decode(&mut message.payload().as_slice())
-            {
-                let hashed_message = keccak_256(&payload_and_signature.payload);
-                if sp_core::ecdsa::Pair::verify_prehashed(
-                    &payload_and_signature.signature,
-                    &hashed_message,
-                    &sp_core::ecdsa::Public::from_raw(peer_public_key.0),
-                ) {
-                    *message.payload_mut() = payload_and_signature.payload;
-                    crate::prometheus::BYTES_RECEIVED.inc_by(message.payload().len() as u64);
-                    return Some(message);
-                } else {
-                    self.logger()
-                        .warn("Received a message with an invalid signature.")
-                }
-            } else {
-                self.logger()
-                    .warn("Received a message without a valid payload and signature.")
-            }
-        } else {
-            self.logger()
-                .warn("Received a message without a valid public key from sender");
-        }
-
-        // This message was invalid. Thus, poll the next message
-        self.next_message().await
-    }
-
-    async fn send_message(&self, mut message: Env::ProtocolMessage) -> Result<(), Error> {
-        // Sign the hash of the message
-        let hashed_message = keccak_256(message.payload());
-        let signature = self.key_store().pair().sign_prehashed(&hashed_message);
-        let payload_and_signature = PayloadAndSignature {
-            payload: message.payload().to_vec(),
-            signature,
-        };
-
-        let serialized_message = Encode::encode(&payload_and_signature);
-        *message.payload_mut() = serialized_message;
-        crate::prometheus::BYTES_SENT.inc_by(message.payload().len() as u64);
-        T::internal_network(self).send_message(message).await
-    }
-
-    fn greatest_authority_id(&self) -> Option<Public> {
-        T::internal_network(self).greatest_authority_id()
-    }
-}
-
-/// Used for constructing an instance of a node.
-///
-/// If there is both a keygen and a signing protocol, then the length of the vectors are 2.
-/// The length of the vector is equal to the numbers of protocols that the constructed node
-/// is going to concurrently execute.
-pub struct NodeInput<Env: GadgetEnvironment, N: Network<Env>, KBE: KeystoreBackend, D> {
-    pub clients: Vec<Env::Client>,
-    pub networks: Vec<N>,
-    pub account_id: sr25519::Public,
-    pub logger: DebugLogger,
-    pub tx_manager: <Env as GadgetEnvironment>::TransactionManager,
-    pub keystore: ECDSAKeyStore<KBE>,
-    pub node_index: usize,
-    pub additional_params: D,
-    pub prometheus_config: PrometheusConfig,
-}
-
-#[derive(Encode, Decode)]
-pub struct PayloadAndSignature {
-    payload: Vec<u8>,
-    signature: Signature,
-}
diff --git a/common/src/helpers.rs b/common/src/helpers.rs
deleted file mode 100644
index 7f1f22543..000000000
--- a/common/src/helpers.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use gadget_core::job::builder::{BuiltExecutableJobWrapper, JobBuilder};
-use gadget_core::job::error::JobError;
-use gadget_core::job::protocol::ShutdownReason;
-use gadget_core::job::{ExecutableJob, ProceedWithExecution};
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::Arc;
-
-/// Wraps an async protocol with logic that makes it compatible with the job manager
-pub fn create_job_manager_compatible_job(
-    is_done: Arc<AtomicBool>,
-    start_rx: gadget_io::tokio::sync::oneshot::Receiver<()>,
-    shutdown_rx: gadget_io::tokio::sync::oneshot::Receiver<ShutdownReason>,
-    mut async_protocol: BuiltExecutableJobWrapper,
-) -> BuiltExecutableJobWrapper {
-    let pre_hook = async move {
-        match start_rx.await {
-            Ok(_) => Ok(ProceedWithExecution::True),
-            Err(err) => Err(JobError::from(format!("Failed to start job: {err:?}"))),
-        }
-    };
-
-    let post_hook = async move {
-        // Mark the task as done
-        is_done.store(true, Ordering::SeqCst);
-        Ok(())
-    };
-
-    // This wrapped future enables proper functionality between the async protocol and the
-    // job manager
-    let wrapped_future = async move {
-        gadget_io::tokio::select! {
-            res0 = async_protocol.execute() => res0,
-            res1 = shutdown_rx => {
-                match res1 {
-                    Ok(reason) => {
-                        match reason {
-                            ShutdownReason::DropCode => {
-                                Ok(())
-                            },
-                            ShutdownReason::Stalled => {
-                                Err(JobError::from("Stalled".to_string()))
-                            },
-                        }
-                    },
-                    Err(err) => {
-                        Err(JobError::from(err.to_string()))
-                    },
-                }
-            }
-        }
-    };
-
-    JobBuilder::default()
-        .pre(pre_hook)
-        .protocol(wrapped_future)
-        .post(post_hook)
-        .build()
-}
diff --git a/common/src/keystore.rs b/common/src/keystore.rs
deleted file mode 100644
index 1c6136be4..000000000
--- a/common/src/keystore.rs
+++ /dev/null
@@ -1,233 +0,0 @@
-use crate::Error;
-use alloc::boxed::Box;
-use async_trait::async_trait;
-use serde::de::DeserializeOwned;
-use serde::Serialize;
-use sp_core::ecdsa::Pair as EcdsaPair;
-use sp_core::sr25519::Pair as Sr25519Pair;
-use sp_core::{keccak_256, Pair};
-
-cfg_if::cfg_if! {
-    if #[cfg(feature = "std")] {
-        use crate::utils::{deserialize, serialize};
-
-        use parking_lot::RwLock;
-        use sqlx::sqlite::SqlitePoolOptions;
-        use sqlx::{Pool, Row, Sqlite};
-
-        use std::collections::HashMap;
-        use std::sync::Arc;
-    }
-}
-
-pub type ECDSAKeyStore<BE> = GenericKeyStore<BE, EcdsaPair>;
-pub type Sr25519KeyStore<BE> = GenericKeyStore<BE, Sr25519Pair>;
-
-#[derive(Clone)]
-pub struct GenericKeyStore<BE: KeystoreBackend, P: Pair> {
-    backend: BE,
-    pair: P,
-}
-
-#[cfg(feature = "std")]
-impl<P: Pair> GenericKeyStore<InMemoryBackend, P> {
-    pub fn in_memory(pair: P) -> Self {
-        GenericKeyStore {
-            backend: InMemoryBackend::new(),
-            pair,
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl<P: Pair> GenericKeyStore<SqliteBackend, P> {
-    pub async fn sqlite_in_memory(pair: P) -> Result<Self, Box<dyn std::error::Error>> {
-        let backend = SqliteBackend::in_memory().await?;
-        Ok(GenericKeyStore { backend, pair })
-    }
-}
-
-impl<P: Pair, Backend: KeystoreBackend> GenericKeyStore<Backend, P> {
-    pub fn new(backend: Backend, pair: P) -> Self {
-        GenericKeyStore { backend, pair }
-    }
-}
-
-impl<P: Pair, BE: KeystoreBackend> GenericKeyStore<BE, P> {
-    pub fn pair(&self) -> &P {
-        &self.pair
-    }
-}
-
-impl<P: Pair, BE: KeystoreBackend> GenericKeyStore<BE, P> {
-    pub async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error> {
-        self.backend.get(key).await
-    }
-
-    pub async fn get_job_result<T: DeserializeOwned>(
-        &self,
-        job_id: u64,
-    ) -> Result<Option<T>, Error> {
-        let key = keccak_256(&job_id.to_be_bytes());
-        self.get(&key).await
-    }
-
-    pub async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error> {
-        self.backend.set(key, value).await
-    }
-
-    pub async fn set_job_result<T: Serialize + Send>(
-        &self,
-        job_id: u64,
-        value: T,
-    ) -> Result<(), Error> {
-        let key = keccak_256(&job_id.to_be_bytes());
-        self.set(&key, value).await
-    }
-}
-
-#[async_trait]
-pub trait KeystoreBackend: Clone + Send + Sync + 'static {
-    async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error>;
-    async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error>;
-}
-
-#[derive(Clone)]
-#[cfg(feature = "std")]
-pub struct InMemoryBackend {
-    map: Arc<RwLock<HashMap<[u8; 32], Vec<u8>>>>,
-}
-
-#[cfg(feature = "std")]
-impl Default for InMemoryBackend {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-#[cfg(feature = "std")]
-impl InMemoryBackend {
-    pub fn new() -> Self {
-        Self {
-            map: Arc::new(RwLock::new(HashMap::new())),
-        }
-    }
-}
-
-#[async_trait]
-#[cfg(feature = "std")]
-impl KeystoreBackend for InMemoryBackend {
-    async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error> {
-        if let Some(bytes) = self.map.read().get(key).cloned() {
-            let value: T = deserialize(&bytes).map_err(|rr| Error::KeystoreError {
-                err: format!("Failed to deserialize value: {:?}", rr),
-            })?;
-            Ok(Some(value))
-        } else {
-            Ok(None)
-        }
-    }
-
-    async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error> {
-        let serialized = serialize(&value).map_err(|rr| Error::KeystoreError {
-            err: format!("Failed to serialize value: {:?}", rr),
-        })?;
-        self.map.write().insert(*key, serialized);
-        Ok(())
-    }
-}
-
-#[derive(Clone)]
-#[cfg(feature = "std")]
-pub struct SqliteBackend {
-    pool: Pool<Sqlite>,
-}
-#[cfg(feature = "std")]
-impl SqliteBackend {
-    pub async fn in_memory() -> Result<Self, Box<dyn std::error::Error>> {
-        Self::new("sqlite://:memory:").await
-    }
-
-    // Initialize a new key-value store with a SqlitePool
-    pub async fn new(database_url: &str) -> Result<Self, Box<dyn std::error::Error>> {
-        let pool = SqlitePoolOptions::new().connect(database_url).await?;
-
-        // Ensure the table exists
-        sqlx::query(
-            r"CREATE TABLE IF NOT EXISTS key_value_store (
-                key TEXT PRIMARY KEY,
-                value BLOB NOT NULL
-              )",
-        )
-        .execute(&pool)
-        .await?;
-
-        Ok(Self { pool })
-    }
-}
-
-#[async_trait]
-#[cfg(all(feature = "std", not(target_family = "wasm")))]
-impl KeystoreBackend for SqliteBackend {
-    async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error> {
-        let key = key_to_string(key);
-        let result = sqlx::query("SELECT value FROM key_value_store WHERE key = ?")
-            .bind(key)
-            .fetch_optional(&self.pool)
-            .await
-            .map_err(|err| Error::KeystoreError {
-                err: format!("Failed to fetch value: {:?}", err),
-            })?;
-
-        match result {
-            Some(row) => {
-                let value: Vec<u8> = row.get("value");
-                let value: T = deserialize(&value).map_err(|rr| Error::KeystoreError {
-                    err: format!("Failed to deserialize value: {:?}", rr),
-                })?;
-                Ok(Some(value))
-            }
-            None => Ok(None),
-        }
-    }
-
-    async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error> {
-        let key = key_to_string(key);
-        let value = serialize(&value).map_err(|rr| Error::KeystoreError {
-            err: format!("Failed to serialize value: {:?}", rr),
-        })?;
-
-        sqlx::query("INSERT INTO key_value_store (key, value) VALUES (?, ?)")
-            .bind(key)
-            .bind(value)
-            .execute(&self.pool)
-            .await
-            .map_err(|err| Error::KeystoreError {
-                err: format!("Failed to insert value: {:?}", err),
-            })?;
-        Ok(())
-    }
-}
-
-#[cfg(all(feature = "std", not(target_family = "wasm")))]
-fn key_to_string(key: &[u8; 32]) -> String {
-    hex::encode(key)
-}
-
-#[cfg(test)]
-#[cfg(feature = "std")]
-mod tests {
-    use crate::keystore::KeystoreBackend;
-    use gadget_io::tokio;
-
-    #[tokio::test]
-    #[cfg(feature = "std")]
-    async fn test_in_memory_kv_store() {
-        let store = super::SqliteBackend::in_memory().await.unwrap();
-        let key = [0u8; 32];
-        let value = "hello".to_string();
-        store.set(&key, value.clone()).await.unwrap();
-        let result: String = store.get(&key).await.unwrap().unwrap();
-        assert_eq!(value, result);
-    }
-}
diff --git a/common/src/lib.rs b/common/src/lib.rs
deleted file mode 100644
index 296907e4d..000000000
--- a/common/src/lib.rs
+++ /dev/null
@@ -1,490 +0,0 @@
-#![cfg_attr(not(feature = "std"), no_std)]
-
-extern crate alloc;
-
-use alloc::fmt::{Debug, Display, Formatter};
-use alloc::string::String;
-
-use gadget_core::gadget::error::GadgetError;
-#[cfg(feature = "std")]
-pub use gadget_core::job::manager::{ProtocolWorkManager, WorkManagerError};
-pub use gadget_core::job::{
-    error::JobError,
-    protocol::{PollMethod, WorkManagerInterface},
-};
-
-use sp_core::ecdsa;
-
-cfg_if::cfg_if! {
-    if #[cfg(all(feature = "std", feature = "substrate"))] {
-        use crate::config::ProtocolConfig;
-        use crate::prelude::PrometheusConfig;
-        use crate::module::network::Network;
-        use crate::module::{GeneralModule, GadgetProtocol};
-        use crate::environments::{EventMetadata, GadgetEnvironment};
-        use gadget_core::gadget::general::GeneralGadget;
-        use gadget_core::gadget::AbstractGadget;
-        use gadget_core::gadget::manager::GadgetManager;
-        use gadget_core::gadget::general::Client;
-        use parking_lot::RwLock;
-        use std::sync::Arc;
-
-        pub mod module;
-        pub mod config;
-        pub mod full_protocol;
-        pub mod protocol;
-    }
-}
-
-pub mod environments;
-
-#[allow(ambiguous_glob_reexports)]
-pub mod prelude {
-    #[cfg(feature = "std")]
-    pub use crate::client::*;
-    #[cfg(all(feature = "std", feature = "substrate"))]
-    pub use crate::config::*;
-    pub use crate::debug_logger::DebugLogger;
-    pub use crate::environments::*;
-    #[cfg(all(feature = "std", feature = "substrate"))]
-    pub use crate::full_protocol::{FullProtocolConfig, NodeInput};
-    pub use crate::generate_setup_and_run_command;
-    #[cfg(feature = "std")]
-    pub use crate::keystore::InMemoryBackend;
-    pub use crate::keystore::{ECDSAKeyStore, KeystoreBackend};
-    #[cfg(all(feature = "std", feature = "substrate"))]
-    pub use crate::module::WorkManagerConfig;
-    pub use alloc::sync::Arc;
-    pub use async_trait::async_trait;
-    pub use core::pin::Pin;
-    pub use gadget_core::job::builder::{BuiltExecutableJobWrapper, JobBuilder};
-    pub use gadget_core::job::error::JobError;
-    #[cfg(feature = "std")]
-    pub use gadget_core::job::manager::{ProtocolWorkManager, WorkManagerError};
-    pub use gadget_core::job::protocol::WorkManagerInterface;
-    pub use gadget_core::job::SendFuture;
-    #[cfg(feature = "std")]
-    pub use gadget_io::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
-    pub use parking_lot::Mutex;
-    pub use sp_runtime::traits::Block;
-}
-
-// Convenience re-exports
-pub use async_trait;
-pub use gadget_io;
-#[cfg(feature = "substrate")]
-pub use subxt_signer;
-#[cfg(feature = "substrate")]
-pub use tangle_subxt;
-
-#[cfg(feature = "substrate")]
-pub mod tangle_runtime {
-    #[cfg(feature = "std")]
-    pub use tangle_subxt::subxt::ext::subxt_core::utils::AccountId32;
-    #[cfg(not(feature = "std"))]
-    pub use tangle_subxt::subxt_core::utils::AccountId32;
-    pub use tangle_subxt::tangle_testnet_runtime::api;
-    pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec;
-}
-
-pub mod channels;
-#[cfg(feature = "std")]
-pub mod client;
-pub mod debug_logger;
-#[cfg(feature = "std")]
-pub mod helpers;
-pub mod keystore;
-#[cfg(feature = "std")]
-pub mod locks;
-#[cfg(feature = "substrate")]
-pub mod prometheus;
-#[cfg(feature = "std")]
-pub mod tracer;
-pub mod utils;
-
-#[derive(Debug)]
-pub enum Error {
-    RegistryCreateError {
-        err: String,
-    },
-    RegistrySendError {
-        err: String,
-    },
-    RegistryRecvError {
-        err: String,
-    },
-    RegistrySerializationError {
-        err: String,
-    },
-    RegistryListenError {
-        err: String,
-    },
-    GadgetManagerError {
-        err: GadgetError,
-    },
-    InitError {
-        err: String,
-    },
-    #[cfg(feature = "std")]
-    WorkManagerError {
-        err: WorkManagerError,
-    },
-    ProtocolRemoteError {
-        err: String,
-    },
-    ClientError {
-        err: String,
-    },
-    JobError {
-        err: JobError,
-    },
-    NetworkError {
-        err: String,
-    },
-    KeystoreError {
-        err: String,
-    },
-    MissingNetworkId,
-    PeerNotFound {
-        id: ecdsa::Public,
-    },
-    #[cfg(feature = "std")]
-    JoinError {
-        err: gadget_io::tokio::task::JoinError,
-    },
-    ParticipantNotSelected {
-        id: ecdsa::Public,
-        reason: String,
-    },
-    PrometheusError {
-        err: String,
-    },
-    #[cfg(feature = "substrate")]
-    Subxt {
-        #[cfg(all(feature = "substrate", feature = "std"))]
-        err: tangle_subxt::subxt::Error,
-        #[cfg(all(feature = "substrate", not(feature = "std")))]
-        err: tangle_subxt::subxt_core::Error,
-    },
-    Other {
-        err: String,
-    },
-}
-
-impl From<String> for Error {
-    fn from(err: String) -> Self {
-        Self::Other { err }
-    }
-}
-
-impl Display for Error {
-    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
-        Debug::fmt(self, f)
-    }
-}
-
-impl core::error::Error for Error {}
-
-impl From<JobError> for Error {
-    fn from(err: JobError) -> Self {
-        Error::JobError { err }
-    }
-}
-
-#[cfg(all(feature = "substrate", not(feature = "std")))]
-impl From<tangle_subxt::subxt_core::Error> for Error {
-    fn from(err: tangle_subxt::subxt_core::Error) -> Self {
-        Error::Subxt { err }
-    }
-}
-
-#[cfg(all(feature = "substrate", feature = "std"))]
-impl From<tangle_subxt::subxt::Error> for Error {
-    fn from(err: tangle_subxt::subxt::Error) -> Self {
-        Error::Subxt { err }
-    }
-}
-
-#[cfg(all(feature = "std", feature = "substrate"))]
-pub async fn run_protocol<Env: GadgetEnvironment, T: ProtocolConfig<Env>>(
-    mut protocol_config: T,
-) -> Result<(), Error> {
-    let client = protocol_config.take_client();
-    let network = protocol_config.take_network();
-    let protocol = protocol_config.take_protocol();
-
-    let prometheus_config = protocol_config.prometheus_config();
-
-    // Before running, wait for the first finality notification we receive
-    let latest_finality_notification = get_latest_event_from_client::<Env>(&client).await?;
-    let work_manager = create_work_manager(&latest_finality_notification, &protocol).await?;
-    let proto_module = GeneralModule::new(network.clone(), protocol, work_manager);
-    // Plug the module into the general gadget to interface the WebbGadget with Substrate
-    let substrate_gadget = GeneralGadget::new(client, proto_module);
-    let network_future = network.run();
-    let gadget_future = async move {
-        // Poll the first finality notification to ensure clients can execute without having to wait
-        // for another block to be produced
-        if let Err(err) = substrate_gadget
-            .on_event_received(latest_finality_notification)
-            .await
-        {
-            substrate_gadget.process_error(err).await;
-        }
-
-        GadgetManager::new(substrate_gadget)
-            .await
-            .map_err(|err| Error::GadgetManagerError { err })
-    };
-
-    if let Err(err) = prometheus::setup(prometheus_config.clone()).await {
-        protocol_config
-            .logger()
-            .warn(format!("Error setting up prometheus: {err:?}"));
-    } else if let PrometheusConfig::Enabled { bind_addr } = prometheus_config {
-        protocol_config
-            .logger()
-            .info(format!("Prometheus enabled on {bind_addr}"));
-    }
-
-    // Run both the network and the gadget together
-    gadget_io::tokio::try_join!(network_future, gadget_future).map(|_| ())
-}
-
-#[cfg(all(feature = "std", feature = "substrate"))]
-/// Creates a work manager
-pub async fn create_work_manager<Env: GadgetEnvironment, P: GadgetProtocol<Env>>(
-    latest_event: &<Env as GadgetEnvironment>::Event,
-    protocol: &P,
-) -> Result<ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>, Error> {
-    let now = latest_event.number();
-
-    let work_manager_config = protocol.get_work_manager_config();
-
-    let clock = Arc::new(RwLock::new(Some(now)));
-
-    let job_manager = protocol.generate_work_manager(clock.clone()).await;
-
-    let poll_method = match work_manager_config.interval {
-        Some(interval) => PollMethod::Interval {
-            millis: interval.as_millis() as u64,
-        },
-        None => PollMethod::Manual,
-    };
-
-    Ok(ProtocolWorkManager::new(
-        job_manager,
-        work_manager_config.max_active_tasks,
-        work_manager_config.max_pending_tasks,
-        poll_method,
-    ))
-}
-
-#[cfg(all(feature = "std", feature = "substrate"))]
-async fn get_latest_event_from_client<Env: GadgetEnvironment>(
-    client: &<Env as GadgetEnvironment>::Client,
-) -> Result<<Env as GadgetEnvironment>::Event, Error> {
-    Client::<Env::Event>::latest_event(client)
-        .await
-        .ok_or_else(|| Error::InitError {
-            err: "No event received".to_string(),
-        })
-}
-
-/// Generates a `run` and `setup_node` function that runs all the protocols concurrently
-///
-/// - `run`: runs all the protocols concurrently
-/// - `setup_node`: sets up a future that runs all the protocols concurrently
-#[macro_export]
-#[allow(clippy::crate_in_macro_def)]
-macro_rules! generate_setup_and_run_command {
-    ($( $config:ident ),*) => {
-        /// Sets up a future that runs all the protocols concurrently
-        pub fn setup_node<Env: GadgetEnvironment, N: Network<Env>, KBE: $crate::keystore::KeystoreBackend, D: Send + Clone + 'static>(node_input: NodeInput<Env, N, KBE, D>) -> impl SendFuture<'static, ()>
-        {
-            async move {
-                if let Err(err) = run(
-                    node_input.clients,
-                    node_input.tx_manager,
-                    node_input.networks,
-                    node_input.logger.clone(),
-                    node_input.account_id,
-                    node_input.keystore,
-                    node_input.prometheus_config,
-                )
-                .await
-                {
-                    node_input
-                        .logger
-                        .error(format!("Error running gadget: {:?}", err));
-                }
-            }
-        }
-
-        pub async fn run<Env: GadgetEnvironment, N: Network<Env>, KBE: $crate::keystore::KeystoreBackend>(
-            client: Vec<Env::Client>,
-            tx_manager: <Env as GadgetEnvironment>::TransactionManager,
-            networks: Vec<N>,
-            logger: DebugLogger,
-            account_id: sp_core::sr25519::Public,
-            key_store: ECDSAKeyStore<KBE>,
-            prometheus_config: $crate::prometheus::PrometheusConfig,
-        ) -> Result<(), Error>
-        {
-            use futures::TryStreamExt;
-            let futures = futures::stream::FuturesUnordered::new();
-            let mut networks: std::collections::VecDeque<_> = networks.into_iter().collect();
-            let mut clients: std::collections::VecDeque<_> = client.into_iter().collect();
-
-            $(
-                let config = crate::$config::new(clients.pop_front().expect("Not enough clients"), tx_manager.clone(), networks.pop_front().expect("Not enough networks"), logger.clone(), account_id.clone(), key_store.clone(), prometheus_config.clone()).await?;
-                futures.push(Box::pin(config.execute()) as std::pin::Pin<Box<dyn SendFuture<'static, Result<(), $crate::Error>>>>);
-            )*
-
-            if let Err(err) = futures.try_collect::<Vec<_>>().await.map(|_| ()) {
-                Err(err)
-            } else {
-                Ok(())
-            }
-        }
-    };
-}
-
-#[macro_export]
-macro_rules! generate_protocol {
-    ($name:expr, $struct_name:ident, $async_proto_params:ty, $proto_gen_path:expr, $create_job_path:expr, $phase_filter:pat, $( $role_filter:pat ),*) => {
-        #[protocol]
-        pub struct $struct_name<
-            Env: GadgetEnvironment,
-            N: Network<Env>,
-            KBE: KeystoreBackend,
-        > {
-            tx_manager: <Env as GadgetEnvironment>::TransactionManager,
-            logger: DebugLogger,
-            client: <Env as GadgetEnvironment>::Client,
-            /// This field should NEVER be used directly. Use Self instead as the network
-            network_inner: N,
-            account_id: sp_core::sr25519::Public,
-            key_store: ECDSAKeyStore<KBE>,
-            jobs_client: Arc<Mutex<Option<JobsClient<Env>>>>,
-            prometheus_config: $crate::prometheus::PrometheusConfig,
-        }
-
-        #[async_trait]
-        impl<
-                Env: GadgetEnvironment,
-                N: Network<Env>,
-                KBE: KeystoreBackend,
-            > FullProtocolConfig<Env> for $struct_name<Env, N, KBE>
-        {
-            type AsyncProtocolParameters = $async_proto_params;
-            type Network = N;
-            type AdditionalNodeParameters = ();
-            type KeystoreBackend = KBE;
-
-            async fn new(
-                client: <Env as GadgetEnvironment>::Client,
-                tx_manager: <Env as GadgetEnvironment>::TransactionManager,
-                network_inner: Self::Network,
-                logger: DebugLogger,
-                account_id: sp_core::sr25519::Public,
-                key_store: ECDSAKeyStore<Self::KeystoreBackend>,
-                prometheus_config: $crate::prometheus::PrometheusConfig,
-            ) -> Result<Self, Error> {
-                let logger = if logger.id.is_empty() {
-                    DebugLogger { id: stringify!($name).replace("\"", "").into() }
-                } else {
-                    DebugLogger { id: (logger.id + " | " + stringify!($name)).replace("\"", "") }
-                };
-                Ok(Self {
-                    tx_manager,
-                    logger,
-                    client,
-                    network_inner,
-                    account_id,
-                    key_store,
-                    prometheus_config,
-                    jobs_client: Arc::new(parking_lot::Mutex::new(None)),
-                })
-            }
-
-            async fn generate_protocol_from(
-                &self,
-                associated_block_id: <<Env as GadgetEnvironment>::WorkManager as WorkManagerInterface>::Clock,
-                associated_retry_id: <<Env as GadgetEnvironment>::WorkManager as WorkManagerInterface>::RetryID,
-                associated_session_id: <<Env as GadgetEnvironment>::WorkManager as WorkManagerInterface>::SessionID,
-                associated_task_id: <<Env as GadgetEnvironment>::WorkManager as WorkManagerInterface>::TaskID,
-                protocol_message_rx: UnboundedReceiver<<Env as GadgetEnvironment>::ProtocolMessage>,
-                additional_params: Self::AsyncProtocolParameters,
-            ) -> Result<BuiltExecutableJobWrapper, JobError> {
-                $proto_gen_path(
-                    self,
-                    associated_block_id,
-                    associated_retry_id,
-                    associated_session_id,
-                    associated_task_id,
-                    protocol_message_rx,
-                    additional_params,
-                )
-                .await
-            }
-
-            fn internal_network(&self) -> &Self::Network {
-                &self.network_inner
-            }
-
-            async fn create_next_job(
-                &self,
-                job: JobInitMetadata,
-                work_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
-            ) -> Result<Self::AsyncProtocolParameters, Error> {
-                $create_job_path(self, job, work_manager).await
-            }
-
-            fn account_id(&self) -> &sp_core::sr25519::Public {
-                &self.account_id
-            }
-
-            fn name(&self) -> String {
-                $name.to_string()
-            }
-
-            fn role_filter(&self, role: roles::RoleType) -> bool {
-                $(
-                    if matches!(role, $role_filter) {
-                        return true;
-                    }
-                )*
-
-                false
-            }
-
-            fn phase_filter(
-                &self,
-                job: jobs::JobType<AccountId32, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>,
-            ) -> bool {
-                matches!(job, $phase_filter)
-            }
-
-            fn jobs_client(&self) -> &SharedOptional<JobsClient<Env>> {
-                &self.jobs_client
-            }
-
-            fn tx_manager(&self) -> <Env as GadgetEnvironment>::TransactionManager {
-                self.tx_manager.clone()
-            }
-
-            fn logger(&self) -> DebugLogger {
-                self.logger.clone()
-            }
-
-            fn key_store(&self) -> &ECDSAKeyStore<Self::KeystoreBackend> {
-                &self.key_store
-            }
-
-            fn client(&self) -> <Env as GadgetEnvironment>::Client {
-                self.client.clone()
-            }
-        }
-    };
-}
diff --git a/common/src/module/mod.rs b/common/src/module/mod.rs
deleted file mode 100644
index f9eee2fc5..000000000
--- a/common/src/module/mod.rs
+++ /dev/null
@@ -1,235 +0,0 @@
-use super::AbstractGadget;
-use crate::client::JobsClient;
-use crate::debug_logger::DebugLogger;
-use crate::environments::GadgetEnvironment;
-use crate::module::network::Network;
-use crate::protocol::{AsyncProtocol, AsyncProtocolRemote};
-use crate::Error;
-use async_trait::async_trait;
-use gadget_core::gadget::general::Client;
-use gadget_core::gadget::general::GadgetWithClient;
-use gadget_core::job::builder::{BuiltExecutableJobWrapper, JobBuilder};
-use gadget_core::job::manager::ProtocolWorkManager;
-use gadget_core::job::protocol::WorkManagerInterface;
-use gadget_core::job::ExecutableJob;
-use parking_lot::{Mutex, RwLock};
-use sp_core::sr25519;
-use std::sync::Arc;
-use std::time::Duration;
-
-pub mod network;
-
-/// Used as a module to place inside the SubstrateGadget
-pub struct GeneralModule<N, M, Env: GadgetEnvironment> {
-    pub protocol: M,
-    pub network: N,
-    pub job_manager: ProtocolWorkManager<Env::WorkManager>,
-    #[allow(dead_code)]
-    clock: Arc<RwLock<Option<Env::Clock>>>,
-}
-
-const DEFAULT_MAX_ACTIVE_TASKS: usize = 4;
-const DEFAULT_MAX_PENDING_TASKS: usize = 4;
-const DEFAULT_POLL_INTERVAL: Option<Duration> = Some(Duration::from_millis(200));
-
-#[derive(Debug)]
-pub struct WorkManagerConfig {
-    pub interval: Option<Duration>,
-    pub max_active_tasks: usize,
-    pub max_pending_tasks: usize,
-}
-
-impl Default for WorkManagerConfig {
-    fn default() -> Self {
-        WorkManagerConfig {
-            interval: DEFAULT_POLL_INTERVAL,
-            max_active_tasks: DEFAULT_MAX_ACTIVE_TASKS,
-            max_pending_tasks: DEFAULT_MAX_PENDING_TASKS,
-        }
-    }
-}
-
-impl<Env: GadgetEnvironment, N: Network<Env>, M: GadgetProtocol<Env>> GeneralModule<N, M, Env> {
-    pub fn new(network: N, module: M, job_manager: ProtocolWorkManager<Env::WorkManager>) -> Self {
-        let clock = Arc::new(RwLock::new(Some(job_manager.utility.clock())));
-        GeneralModule {
-            protocol: module,
-            job_manager,
-            network,
-            clock,
-        }
-    }
-}
-
-#[async_trait]
-impl<Env: GadgetEnvironment, N: Network<Env>, M: GadgetProtocol<Env>> AbstractGadget
-    for GeneralModule<N, M, Env>
-{
-    type Event = Env::Event;
-    type ProtocolMessage = Env::ProtocolMessage;
-    type Error = Env::Error;
-
-    async fn next_event(&self) -> Option<Env::Event> {
-        self.protocol.client().client.next_event().await
-    }
-
-    async fn get_next_protocol_message(&self) -> Option<Env::ProtocolMessage> {
-        self.network.next_message().await
-    }
-
-    async fn on_event_received(&self, notification: Self::Event) -> Result<(), Self::Error> {
-        self.protocol.process_event(notification).await
-    }
-
-    async fn process_protocol_message(
-        &self,
-        message: Self::ProtocolMessage,
-    ) -> Result<(), Self::Error> {
-        self.job_manager
-            .deliver_message(message)
-            .map(|_| ())
-            .map_err(|err| Self::Error::from(format!("{err:?}")))
-    }
-
-    async fn process_error(&self, error: Self::Error) {
-        self.protocol.process_error(error, &self.job_manager).await
-    }
-}
-
-#[async_trait]
-#[auto_impl::auto_impl(Box)]
-pub trait EventHandler<Env: GadgetEnvironment>: Send + Sync + 'static {
-    async fn process_event(
-        &self,
-        notification: <Env as GadgetEnvironment>::Event,
-    ) -> Result<(), <Env as GadgetEnvironment>::Error>;
-}
-
-// Redirection to the `AbstractGadget` trait, with a placeholder for a client.
-#[async_trait]
-impl<Env: GadgetEnvironment, N: Network<Env>, M: GadgetProtocol<Env>>
-    GadgetWithClient<Env::ProtocolMessage, Env::Event, Env::Error> for GeneralModule<N, M, Env>
-where
-    Self: AbstractGadget<
-        Event = Env::Event,
-        Error = Env::Error,
-        ProtocolMessage = Env::ProtocolMessage,
-    >,
-{
-    type Client = <Env as GadgetEnvironment>::Client;
-
-    async fn get_next_protocol_message(&self) -> Option<Env::ProtocolMessage> {
-        <Self as AbstractGadget>::get_next_protocol_message(self).await
-    }
-
-    /// Provided by the developer
-    async fn process_event(&self, notification: Env::Event) -> Result<(), Env::Error> {
-        <Self as AbstractGadget>::on_event_received(self, notification).await
-    }
-
-    async fn process_protocol_message(
-        &self,
-        message: Env::ProtocolMessage,
-    ) -> Result<(), Env::Error> {
-        <Self as AbstractGadget>::process_protocol_message(self, message).await
-    }
-
-    async fn process_error(&self, error: Env::Error) {
-        <Self as AbstractGadget>::process_error(self, error).await
-    }
-}
-
-pub type Job<Env> = (AsyncProtocolRemote<Env>, BuiltExecutableJobWrapper);
-
-#[async_trait]
-pub trait GadgetProtocol<Env: GadgetEnvironment>:
-    AsyncProtocol<Env> + Send + Sync + 'static
-{
-    /// Given an input of a valid and relevant job, return the parameters needed to start the async protocol
-    /// Note: the parameters returned must be relevant to the `AsyncProtocol` implementation of this protocol
-    ///
-    /// In case the participant is not selected for some reason, return an [`Error::ParticipantNotSelected`]
-    /// TODO: move some code back to common, and use Env::JobInitMetadata type
-    async fn create_next_job(
-        &self,
-        job: <Env as GadgetEnvironment>::JobInitMetadata,
-        work_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
-    ) -> Result<<Self as AsyncProtocol<Env>>::AdditionalParams, Error>;
-
-    async fn process_event(
-        &self,
-        event: <Env as GadgetEnvironment>::Event,
-    ) -> Result<(), Env::Error>;
-
-    /// Process an error that may arise from the work manager, async protocol, or the executor
-    async fn process_error(
-        &self,
-        error: Env::Error,
-        job_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
-    );
-
-    async fn generate_work_manager(
-        &self,
-        clock: Arc<RwLock<Option<<Env as GadgetEnvironment>::Clock>>>,
-    ) -> <Env as GadgetEnvironment>::WorkManager;
-    /// The account ID of this node. Jobs queried will be filtered by this account ID
-    fn account_id(&self) -> &sr25519::Public;
-
-    /// The Protocol Name.
-    /// Used for logging and debugging purposes
-    fn name(&self) -> String;
-    fn client(&self) -> JobsClient<Env>;
-    fn logger(&self) -> DebugLogger;
-    fn get_work_manager_config(&self) -> WorkManagerConfig {
-        Default::default()
-    }
-}
-
-#[allow(dead_code)]
-trait MetricizedJob: ExecutableJob {
-    fn with_metrics(self) -> BuiltExecutableJobWrapper
-    where
-        Self: Sized,
-    {
-        let job = Arc::new(gadget_io::tokio::sync::Mutex::new(self));
-        let job2 = job.clone();
-        let job3 = job.clone();
-        let job4 = job.clone();
-        let _tokio_metrics = gadget_io::tokio::runtime::Handle::current().metrics();
-        // crate::prometheus::TOKIO_ACTIVE_TASKS.set(tokio_metrics.active_tasks_count() as f64);
-        let now_init = Arc::new(Mutex::new(None));
-        let now_clone = now_init.clone();
-        let now_clone2 = now_init.clone();
-
-        JobBuilder::default()
-            .pre(async move {
-                now_init.lock().replace(std::time::Instant::now());
-                job.lock().await.pre_job_hook().await
-            })
-            .protocol(async move {
-                // At this point, we know the job will be executed
-                crate::prometheus::JOBS_STARTED.inc();
-                crate::prometheus::JOBS_RUNNING.inc();
-                job2.lock().await.job().await
-            })
-            .post(async move {
-                // Run the job's post hook
-                job3.lock().await.post_job_hook().await?;
-                crate::prometheus::JOBS_COMPLETED_SUCCESS.inc();
-                crate::prometheus::JOBS_RUNNING.dec();
-                let elapsed = now_clone.lock().take().unwrap().elapsed();
-                crate::prometheus::JOB_RUN_TIME.observe(elapsed.as_secs_f64());
-                Ok(())
-            })
-            .catch(async move {
-                job4.lock().await.catch().await;
-                crate::prometheus::JOBS_COMPLETED_FAILED.inc();
-                crate::prometheus::JOBS_RUNNING.dec();
-                let elapsed = now_clone2.lock().take().unwrap().elapsed();
-                crate::prometheus::JOB_RUN_TIME.observe(elapsed.as_secs_f64());
-            })
-            .build()
-    }
-}
-
-impl<T: ExecutableJob> MetricizedJob for T {}
diff --git a/common/src/module/network.rs b/common/src/module/network.rs
deleted file mode 100644
index 316b743fa..000000000
--- a/common/src/module/network.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use crate::environments::GadgetEnvironment;
-use crate::Error;
-use async_trait::async_trait;
-use sp_core::ecdsa;
-
-#[async_trait]
-pub trait Network<Env: GadgetEnvironment>: Send + Sync + Clone + 'static {
-    async fn next_message(&self) -> Option<Env::ProtocolMessage>;
-    async fn send_message(&self, message: Env::ProtocolMessage) -> Result<(), Error>;
-
-    /// The ID of the king. Only relevant for ZkSaaS networks
-    fn greatest_authority_id(&self) -> Option<ecdsa::Public> {
-        None
-    }
-    /// If the network implementation requires a custom runtime, this function
-    /// should be manually implemented to keep the network alive
-    async fn run(self) -> Result<(), Error> {
-        Ok(())
-    }
-}
diff --git a/common/src/prometheus/mod.rs b/common/src/prometheus/mod.rs
deleted file mode 100644
index 55af6769e..000000000
--- a/common/src/prometheus/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-mod shared;
-#[cfg(not(target_family = "wasm"))]
-mod standard;
-#[cfg(target_family = "wasm")]
-mod wasm;
-
-pub use shared::*;
-#[cfg(not(target_family = "wasm"))]
-pub use standard::*; //{setup, PrometheusConfig, BYTES_RECEIVED, BYTES_SENT, REGISTRY};
-#[cfg(target_family = "wasm")]
-pub use wasm::*; //{setup, PrometheusConfig, BYTES_RECEIVED, BYTES_SENT};
diff --git a/common/src/prometheus/wasm.rs b/common/src/prometheus/wasm.rs
deleted file mode 100644
index fbb2ee4b0..000000000
--- a/common/src/prometheus/wasm.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use alloc::string::ToString;
-use core::net::SocketAddr;
-use prometheus::IntCounter;
-
-#[derive(Debug, Clone)]
-pub enum PrometheusConfig {
-    Enabled { bind_addr: SocketAddr },
-    Disabled,
-}
-
-impl Default for PrometheusConfig {
-    fn default() -> Self {
-        Self::Disabled
-    }
-}
-
-pub async fn setup(_config: PrometheusConfig) -> Result<(), crate::Error> {
-    Err(crate::Error::PrometheusError {
-        err: "Prometheus is not yet supported on WASM targets".to_string(),
-    })
-}
diff --git a/common/src/protocol.rs b/common/src/protocol.rs
deleted file mode 100644
index 87e0bae61..000000000
--- a/common/src/protocol.rs
+++ /dev/null
@@ -1,133 +0,0 @@
-use crate::environments::GadgetEnvironment;
-use crate::module::Job;
-use async_trait::async_trait;
-use gadget_core::job::builder::BuiltExecutableJobWrapper;
-use gadget_core::job::error::JobError;
-use gadget_core::job::protocol::{ProtocolRemote, ShutdownReason, WorkManagerInterface};
-use gadget_io::tokio::sync::mpsc::UnboundedReceiver;
-use parking_lot::Mutex;
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::Arc;
-
-pub struct AsyncProtocolRemote<Env: GadgetEnvironment> {
-    pub start_tx: Mutex<Option<gadget_io::tokio::sync::oneshot::Sender<()>>>,
-    pub shutdown_tx: Mutex<Option<gadget_io::tokio::sync::oneshot::Sender<ShutdownReason>>>,
-    pub associated_session_id: <Env::WorkManager as WorkManagerInterface>::SessionID,
-    pub associated_block_id: <Env::WorkManager as WorkManagerInterface>::Clock,
-    pub associated_retry_id: <Env::WorkManager as WorkManagerInterface>::RetryID,
-    pub associated_task_id: <Env::WorkManager as WorkManagerInterface>::TaskID,
-    pub to_async_protocol:
-        gadget_io::tokio::sync::mpsc::UnboundedSender<<Env as GadgetEnvironment>::ProtocolMessage>,
-    pub is_done: Arc<AtomicBool>,
-}
-
-#[async_trait]
-pub trait AsyncProtocol<Env: GadgetEnvironment> {
-    type AdditionalParams: Send + Sync + 'static;
-    async fn generate_protocol_from(
-        &self,
-        associated_block_id: <Env::WorkManager as WorkManagerInterface>::Clock,
-        associated_retry_id: <Env::WorkManager as WorkManagerInterface>::RetryID,
-        associated_session_id: <Env::WorkManager as WorkManagerInterface>::SessionID,
-        associated_task_id: <Env::WorkManager as WorkManagerInterface>::TaskID,
-        protocol_message_rx: UnboundedReceiver<Env::ProtocolMessage>,
-        additional_params: Self::AdditionalParams,
-    ) -> Result<BuiltExecutableJobWrapper, JobError>;
-
-    async fn create(
-        &self,
-        session_id: <Env::WorkManager as WorkManagerInterface>::SessionID,
-        now: <Env::WorkManager as WorkManagerInterface>::Clock,
-        retry_id: <Env::WorkManager as WorkManagerInterface>::RetryID,
-        task_id: <Env::WorkManager as WorkManagerInterface>::TaskID,
-        additional_params: Self::AdditionalParams,
-    ) -> Result<Job<Env>, JobError> {
-        let is_done = Arc::new(AtomicBool::new(false));
-        let (to_async_protocol, protocol_message_rx) =
-            gadget_io::tokio::sync::mpsc::unbounded_channel();
-        let (start_tx, start_rx) = gadget_io::tokio::sync::oneshot::channel();
-        let (shutdown_tx, shutdown_rx) = gadget_io::tokio::sync::oneshot::channel();
-        let async_protocol = self
-            .generate_protocol_from(
-                now,
-                retry_id,
-                session_id,
-                task_id,
-                protocol_message_rx,
-                additional_params,
-            )
-            .await?;
-
-        let remote = AsyncProtocolRemote {
-            start_tx: Mutex::new(Some(start_tx)),
-            shutdown_tx: Mutex::new(Some(shutdown_tx)),
-            associated_block_id: now,
-            associated_retry_id: retry_id,
-            associated_task_id: task_id,
-            associated_session_id: session_id,
-            to_async_protocol,
-            is_done: is_done.clone(),
-        };
-
-        let job_manager_compatible_protocol = crate::helpers::create_job_manager_compatible_job(
-            is_done,
-            start_rx,
-            shutdown_rx,
-            async_protocol,
-        );
-
-        Ok((remote, job_manager_compatible_protocol))
-    }
-}
-
-impl<Env: GadgetEnvironment> ProtocolRemote<<Env as GadgetEnvironment>::WorkManager>
-    for AsyncProtocolRemote<Env>
-{
-    fn start(&self) -> Result<(), Env::Error> {
-        self.start_tx
-            .lock()
-            .take()
-            .ok_or_else(|| Env::Error::from("Protocol already started".to_string()))?
-            .send(())
-            .map_err(|_err| Env::Error::from("Unable to start protocol".to_string()))
-    }
-
-    fn session_id(&self) -> <Env::WorkManager as WorkManagerInterface>::SessionID {
-        self.associated_session_id
-    }
-
-    fn started_at(&self) -> <Env::WorkManager as WorkManagerInterface>::Clock {
-        self.associated_block_id
-    }
-
-    fn shutdown(&self, reason: ShutdownReason) -> Result<(), Env::Error> {
-        self.shutdown_tx
-            .lock()
-            .take()
-            .ok_or_else(|| Env::Error::from("Protocol already shutdown".to_string()))?
-            .send(reason)
-            .map_err(|reason| {
-                Env::Error::from(format!(
-                    "Unable to shutdown protocol with status {reason:?}"
-                ))
-            })
-    }
-
-    fn is_done(&self) -> bool {
-        self.is_done.load(Ordering::SeqCst)
-    }
-
-    fn deliver_message(&self, message: Env::ProtocolMessage) -> Result<(), Env::Error> {
-        self.to_async_protocol
-            .send(message)
-            .map_err(|err| Env::Error::from(err.to_string()))
-    }
-
-    fn has_started(&self) -> bool {
-        self.start_tx.lock().is_none()
-    }
-
-    fn retry_id(&self) -> <Env::WorkManager as WorkManagerInterface>::RetryID {
-        self.associated_retry_id
-    }
-}
diff --git a/common/src/utils.rs b/common/src/utils.rs
deleted file mode 100644
index 25124c675..000000000
--- a/common/src/utils.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-use alloc::vec::Vec;
-use serde::{Deserialize, Serialize};
-use sp_core::ecdsa;
-use sp_io::EcdsaVerifyError;
-
-cfg_if::cfg_if! {
-    if #[cfg(feature = "std")] {
-        use futures::Stream;
-        use gadget_io::tokio::sync::mpsc::UnboundedReceiver;
-        use core::sync::atomic::AtomicBool;
-        use std::sync::Arc;
-    }
-}
-
-pub const ECDSA_SIGNATURE_LENGTH: usize = 65;
-
-// constrain output types to have the `Deserialize` trait
-pub fn deserialize<'a, T>(data: &'a [u8]) -> Result<T, serde_json::Error>
-where
-    T: Deserialize<'a>,
-{
-    serde_json::from_slice::<T>(data)
-}
-
-// shorthand for the above when `T` isn't needed in the function body
-pub fn serialize(object: &impl Serialize) -> Result<Vec<u8>, serde_json::Error> {
-    serde_json::to_vec(object)
-}
-
-#[cfg(feature = "std")]
-/// A Channel Receiver that can be cloned.
-///
-/// On the second clone, the original channel will stop receiving new messages
-/// and the new channel will start receiving any new messages after the clone.
-pub struct CloneableUnboundedReceiver<T> {
-    rx: Arc<gadget_io::tokio::sync::Mutex<UnboundedReceiver<T>>>,
-    is_in_use: Arc<AtomicBool>,
-}
-
-#[cfg(feature = "std")]
-impl<T: Clone> Clone for CloneableUnboundedReceiver<T> {
-    fn clone(&self) -> Self {
-        // on the clone, we switch the is_in_use flag to false
-        // and we return a new channel
-        self.is_in_use
-            .store(false, core::sync::atomic::Ordering::SeqCst);
-        Self {
-            rx: self.rx.clone(),
-            is_in_use: Arc::new(AtomicBool::new(true)),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl<T> From<UnboundedReceiver<T>> for CloneableUnboundedReceiver<T> {
-    fn from(rx: UnboundedReceiver<T>) -> Self {
-        Self {
-            rx: Arc::new(gadget_io::tokio::sync::Mutex::new(rx)),
-            is_in_use: Arc::new(AtomicBool::new(false)),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl<T> Stream for CloneableUnboundedReceiver<T> {
-    type Item = T;
-    fn poll_next(
-        self: core::pin::Pin<&mut Self>,
-        cx: &mut core::task::Context<'_>,
-    ) -> core::task::Poll<Option<Self::Item>> {
-        if !self.is_in_use.load(core::sync::atomic::Ordering::SeqCst) {
-            return core::task::Poll::Ready(None);
-        }
-        let mut rx = match self.rx.try_lock() {
-            Ok(rx) => rx,
-            Err(_) => return core::task::Poll::Pending,
-        };
-        let rx = &mut *rx;
-        gadget_io::tokio::pin!(rx);
-        rx.poll_recv(cx)
-    }
-}
-
-/// Recovers the ECDSA public key from a given message and signature.
-///
-/// # Arguments
-///
-/// * `data` - The message for which the signature is being verified.
-/// * `signature` - The ECDSA signature to be verified.
-///
-/// # Returns
-///
-/// Returns a `Result` containing the recovered ECDSA public key as a `Vec<u8>` or an
-/// `EcdsaVerifyError` if verification fails.
-pub fn recover_ecdsa_pub_key(
-    data: &[u8],
-    signature: &[u8],
-) -> Result<Vec<u8>, sp_io::EcdsaVerifyError> {
-    if signature.len() == ECDSA_SIGNATURE_LENGTH {
-        let mut sig = [0u8; ECDSA_SIGNATURE_LENGTH];
-        sig[..ECDSA_SIGNATURE_LENGTH].copy_from_slice(signature);
-
-        let hash = sp_core::keccak_256(data);
-
-        // let pub_key = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &hash)?;
-
-        let msg = hash;
-        let rid =
-            libsecp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] })
-                .map_err(|_| EcdsaVerifyError::BadV)?;
-        let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[..64])
-            .map_err(|_| EcdsaVerifyError::BadRS)?;
-        let msg = libsecp256k1::Message::parse(&msg);
-        let pubkey =
-            libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?;
-        let mut pub_key = [0u8; 64];
-        pub_key.copy_from_slice(&pubkey.serialize()[1..65]);
-
-        return Ok(pub_key.to_vec());
-    }
-    Err(sp_io::EcdsaVerifyError::BadSignature)
-}
-
-/// Verifies the signer of a given message using a set of ECDSA public keys.
-///
-/// Given a vector of ECDSA public keys (`maybe_signers`), a message (`msg`), and an ECDSA
-/// signature (`signature`), this function checks if any of the public keys in the set can be a
-/// valid signer for the provided message and signature.
-///
-/// # Arguments
-///
-/// * `maybe_signers` - A vector of ECDSA public keys that may represent the potential signers.
-/// * `msg` - The message for which the signature is being verified.
-/// * `signature` - The ECDSA signature to be verified.
-///
-/// # Returns
-///
-/// Returns a tuple containing:
-/// * An optional ECDSA public key (`Option<ecdsa::Public>`) representing the verified signer. It is
-///   `None` if no valid signer is found.
-/// * A boolean value (`bool`) indicating whether the verification was successful (`true`) or not
-///   (`false`).
-pub fn verify_signer_from_set_ecdsa(
-    maybe_signers: Vec<ecdsa::Public>,
-    msg: &[u8],
-    signature: &[u8],
-) -> (Option<ecdsa::Public>, bool) {
-    let mut signer = None;
-    let recovered_result = recover_ecdsa_pub_key(msg, signature);
-    let res = if let Ok(data) = recovered_result {
-        let recovered = &data[..32];
-        maybe_signers.iter().any(|x| {
-            if x.0[1..].to_vec() == recovered.to_vec() {
-                signer = Some(*x);
-                true
-            } else {
-                false
-            }
-        })
-    } else {
-        false
-    };
-
-    (signer, res)
-}
-
-/// Utility function to create slice of fixed size
-pub fn to_slice_33(val: &[u8]) -> Option<[u8; 33]> {
-    if val.len() == 33 {
-        let mut key = [0u8; 33];
-        key[..33].copy_from_slice(val);
-
-        return Some(key);
-    }
-    None
-}
diff --git a/core/Cargo.toml b/core/Cargo.toml
deleted file mode 100644
index 6dc2b6347..000000000
--- a/core/Cargo.toml
+++ /dev/null
@@ -1,33 +0,0 @@
-[package]
-name = "gadget-core"
-version = "0.0.1"
-authors = ["Webb Developers <hello@webb.tools>"]
-license.workspace = true
-edition = "2021"
-description = "Tangle's gadget core library for writing Tangle blueprints"
-repository.workspace = true
-homepage.workspace = true
-
-[dependencies]
-async-trait = { workspace = true }
-auto_impl = { workspace = true }
-futures-timer = { workspace = true }
-gadget-io = { workspace = true }
-hex = { workspace = true, features = ["alloc"] }
-log = { workspace = true }
-parking_lot = { workspace = true }
-serde = { workspace = true, features = ["derive"] }
-sp-core = { workspace = true }
-
-[dev-dependencies]
-tokio = { workspace = true, features = ["macros"] }
-
-[features]
-default = ["std", "substrate"]
-substrate = []
-std = [
-    "gadget-io/std",
-    "log/std",
-    "serde/std",
-    "sp-core/std",
-]
diff --git a/core/src/gadget/error.rs b/core/src/gadget/error.rs
deleted file mode 100644
index 920071fab..000000000
--- a/core/src/gadget/error.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use core::error::Error;
-use core::fmt::{Debug, Display, Formatter};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum GadgetError {
-    FinalityNotificationStreamEnded,
-    ProtocolMessageStreamEnded,
-}
-
-impl Display for GadgetError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
-        Debug::fmt(self, f)
-    }
-}
-
-impl Error for GadgetError {}
diff --git a/core/src/gadget/general/mod.rs b/core/src/gadget/general/mod.rs
deleted file mode 100644
index 7e41424d7..000000000
--- a/core/src/gadget/general/mod.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-use crate::gadget::AbstractGadget;
-use alloc::boxed::Box;
-use alloc::sync::Arc;
-use async_trait::async_trait;
-use auto_impl::auto_impl;
-use core::marker::PhantomData;
-
-/// Endows the abstract gadget with a client
-#[async_trait]
-pub trait GadgetWithClient<ProtocolMessage, Event, Error>: Send + Sync + 'static {
-    type Client: Client<Event>;
-
-    async fn get_next_protocol_message(&self) -> Option<ProtocolMessage>;
-    async fn process_event(&self, notification: Event) -> Result<(), Error>;
-    async fn process_protocol_message(&self, message: ProtocolMessage) -> Result<(), Error>;
-    async fn process_error(&self, error: Error);
-}
-
-#[async_trait]
-#[auto_impl(Arc)]
-pub trait Client<Event>: Clone + Send + Sync {
-    async fn next_event(&self) -> Option<Event>;
-    async fn latest_event(&self) -> Option<Event>;
-}
-
-pub struct GeneralGadget<
-    Module: GadgetWithClient<ProtocolMessage, Event, Error>,
-    Event,
-    ProtocolMessage,
-    Error,
-> {
-    module: Module,
-    client: Arc<Module::Client>,
-    phantom_data: PhantomData<(Event, ProtocolMessage, Error)>,
-}
-
-impl<Module, Event, ProtocolMessage, Error> GeneralGadget<Module, Event, ProtocolMessage, Error>
-where
-    Module: GadgetWithClient<ProtocolMessage, Event, Error>,
-{
-    pub fn new(client: Module::Client, module: Module) -> Self {
-        Self {
-            module,
-            client: Arc::new(client),
-            phantom_data: Default::default(),
-        }
-    }
-}
-
-#[async_trait]
-impl<Module, Event, ProtocolMessage, Error> AbstractGadget
-    for GeneralGadget<Module, Event, ProtocolMessage, Error>
-where
-    Module: GadgetWithClient<ProtocolMessage, Event, Error>,
-    Event: Send + Sync + 'static,
-    ProtocolMessage: Send + Sync + 'static,
-    Error: core::error::Error + Send + Sync + 'static,
-{
-    type Event = Event;
-    type ProtocolMessage = ProtocolMessage;
-    type Error = Error;
-
-    async fn next_event(&self) -> Option<Event> {
-        self.client.next_event().await
-    }
-
-    async fn get_next_protocol_message(&self) -> Option<Self::ProtocolMessage> {
-        GadgetWithClient::get_next_protocol_message(&self.module).await
-    }
-
-    async fn on_event_received(&self, notification: Self::Event) -> Result<(), Self::Error> {
-        self.module.process_event(notification).await
-    }
-
-    async fn process_protocol_message(
-        &self,
-        message: Self::ProtocolMessage,
-    ) -> Result<(), Self::Error> {
-        GadgetWithClient::process_protocol_message(&self.module, message).await
-    }
-
-    async fn process_error(&self, error: Self::Error) {
-        GadgetWithClient::process_error(&self.module, error).await
-    }
-}
diff --git a/core/src/gadget/manager.rs b/core/src/gadget/manager.rs
deleted file mode 100644
index d0e6915b6..000000000
--- a/core/src/gadget/manager.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use super::error::GadgetError;
-use super::AbstractGadget;
-use gadget_io::tokio;
-use std::future::Future;
-use std::pin::Pin;
-
-pub struct GadgetManager<'a> {
-    gadget: Pin<Box<dyn Future<Output = Result<(), GadgetError>> + Send + 'a>>,
-}
-
-impl<'a> GadgetManager<'a> {
-    pub fn new<T: AbstractGadget + 'a>(gadget: T) -> Self {
-        let gadget_task = async move {
-            let gadget = &gadget;
-
-            let finality_notification_task = async move {
-                loop {
-                    if let Some(notification) = gadget.next_event().await {
-                        if let Err(err) = gadget.on_event_received(notification).await {
-                            gadget.process_error(err).await;
-                        }
-                    } else {
-                        return Err(GadgetError::FinalityNotificationStreamEnded);
-                    }
-                }
-            };
-
-            let protocol_message_task = async move {
-                loop {
-                    if let Some(message) = gadget.get_next_protocol_message().await {
-                        if let Err(err) = gadget.process_protocol_message(message).await {
-                            gadget.process_error(err).await;
-                        }
-                    } else {
-                        return Err(GadgetError::ProtocolMessageStreamEnded);
-                    }
-                }
-            };
-
-            tokio::select! {
-                res0 = finality_notification_task => res0,
-                res1 = protocol_message_task => res1
-            }
-        };
-
-        Self {
-            gadget: Box::pin(gadget_task),
-        }
-    }
-}
-
-impl Future for GadgetManager<'_> {
-    type Output = Result<(), GadgetError>;
-    fn poll(
-        mut self: Pin<&mut Self>,
-        cx: &mut std::task::Context<'_>,
-    ) -> std::task::Poll<Self::Output> {
-        self.gadget.as_mut().poll(cx)
-    }
-}
-
-impl<'a, T: AbstractGadget + 'a> From<T> for GadgetManager<'a> {
-    fn from(gadget: T) -> Self {
-        Self::new(gadget)
-    }
-}
diff --git a/core/src/gadget/mod.rs b/core/src/gadget/mod.rs
deleted file mode 100644
index f9f5d8eab..000000000
--- a/core/src/gadget/mod.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use alloc::boxed::Box;
-use async_trait::async_trait;
-use core::error::Error;
-
-pub mod error;
-pub mod general;
-#[cfg(feature = "std")]
-pub mod manager;
-#[cfg(feature = "substrate")]
-pub mod substrate;
-
-#[async_trait]
-pub trait AbstractGadget: Send + Sync + 'static {
-    type Event: Send + Sync + 'static;
-    type ProtocolMessage: Send + Sync + 'static;
-    type Error: Error + Send + Sync + 'static;
-
-    async fn next_event(&self) -> Option<Self::Event>;
-    async fn get_next_protocol_message(&self) -> Option<Self::ProtocolMessage>;
-
-    async fn on_event_received(&self, notification: Self::Event) -> Result<(), Self::Error>;
-    async fn process_protocol_message(
-        &self,
-        message: Self::ProtocolMessage,
-    ) -> Result<(), Self::Error>;
-
-    async fn process_error(&self, error: Self::Error);
-}
diff --git a/core/src/gadget/substrate/mod.rs b/core/src/gadget/substrate/mod.rs
deleted file mode 100644
index 60535f98f..000000000
--- a/core/src/gadget/substrate/mod.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use core::error::Error;
-use core::fmt::{Debug, Display, Formatter};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub struct SubstrateGadgetError {}
-
-impl Display for SubstrateGadgetError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
-        Debug::fmt(self, f)
-    }
-}
-
-impl Error for SubstrateGadgetError {}
diff --git a/core/src/job/builder.rs b/core/src/job/builder.rs
deleted file mode 100644
index 39543cddf..000000000
--- a/core/src/job/builder.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-use crate::job::error::JobError;
-use crate::job::{ExecutableJobWrapper, ProceedWithExecution, SendFuture};
-use alloc::boxed::Box;
-use core::future::Future;
-use core::pin::Pin;
-use core::task::{Context, Poll};
-
-#[derive(Default)]
-pub struct JobBuilder {
-    pre: Option<Pin<Box<PreJobHook>>>,
-    protocol: Option<Pin<Box<ProtocolJobHook>>>,
-    post: Option<Pin<Box<PostJobHook>>>,
-    catch: Option<Pin<Box<CatchJobHook>>>,
-}
-
-pub type PreJobHook = dyn SendFuture<'static, Result<ProceedWithExecution, JobError>>;
-pub type PostJobHook = dyn SendFuture<'static, Result<(), JobError>>;
-pub type ProtocolJobHook = dyn SendFuture<'static, Result<(), JobError>>;
-pub type CatchJobHook = dyn SendFuture<'static, ()>;
-
-pub struct DefaultPreJobHook;
-impl Future for DefaultPreJobHook {
-    type Output = Result<ProceedWithExecution, JobError>;
-
-    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
-        Poll::Ready(Ok(ProceedWithExecution::True))
-    }
-}
-
-pub struct DefaultPostJobHook;
-impl Future for DefaultPostJobHook {
-    type Output = Result<(), JobError>;
-
-    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
-        Poll::Ready(Ok(()))
-    }
-}
-
-struct DefaultCatchJobHook;
-
-impl Future for DefaultCatchJobHook {
-    type Output = ();
-
-    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
-        Poll::Ready(())
-    }
-}
-
-pub type BuiltExecutableJobWrapper = ExecutableJobWrapper<
-    dyn SendFuture<'static, Result<ProceedWithExecution, JobError>>,
-    dyn SendFuture<'static, Result<(), JobError>>,
-    dyn SendFuture<'static, Result<(), JobError>>,
-    dyn SendFuture<'static, ()>,
->;
-
-impl JobBuilder {
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    pub fn pre<Pre>(mut self, pre: Pre) -> Self
-    where
-        Pre: SendFuture<'static, Result<ProceedWithExecution, JobError>>,
-    {
-        self.pre = Some(Box::pin(pre));
-        self
-    }
-
-    pub fn protocol<Protocol>(mut self, protocol: Protocol) -> Self
-    where
-        Protocol: SendFuture<'static, Result<(), JobError>>,
-    {
-        self.protocol = Some(Box::pin(protocol));
-        self
-    }
-
-    pub fn post<Post>(mut self, post: Post) -> Self
-    where
-        Post: SendFuture<'static, Result<(), JobError>>,
-    {
-        self.post = Some(Box::pin(post));
-        self
-    }
-
-    pub fn catch<Catch>(mut self, catch: Catch) -> Self
-    where
-        Catch: SendFuture<'static, ()>,
-    {
-        self.catch = Some(Box::pin(catch));
-        self
-    }
-
-    pub fn build(self) -> BuiltExecutableJobWrapper {
-        let pre = if let Some(pre) = self.pre {
-            pre
-        } else {
-            Box::pin(DefaultPreJobHook)
-        };
-
-        let post = if let Some(post) = self.post {
-            post
-        } else {
-            Box::pin(DefaultPostJobHook)
-        };
-
-        let catch = if let Some(catch) = self.catch {
-            catch
-        } else {
-            Box::pin(DefaultCatchJobHook)
-        };
-
-        let protocol = Box::pin(self.protocol.expect("Must specify protocol"));
-
-        ExecutableJobWrapper {
-            pre,
-            protocol,
-            post,
-            catch,
-        }
-    }
-}
diff --git a/core/src/job/error.rs b/core/src/job/error.rs
deleted file mode 100644
index 55a6d55e5..000000000
--- a/core/src/job/error.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use alloc::string::String;
-use core::error::Error;
-use core::fmt::Display;
-
-#[derive(Debug)]
-pub struct JobError {
-    pub reason: String,
-}
-
-impl<T: Into<String>> From<T> for JobError {
-    fn from(value: T) -> Self {
-        Self {
-            reason: value.into(),
-        }
-    }
-}
-
-impl Display for JobError {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        write!(f, "{reason}", reason = self.reason)
-    }
-}
-
-impl Error for JobError {}
diff --git a/core/src/job/manager.rs b/core/src/job/manager.rs
deleted file mode 100644
index f1eef1a02..000000000
--- a/core/src/job/manager.rs
+++ /dev/null
@@ -1,1475 +0,0 @@
-use super::protocol::{
-    DeliveryType, PollMethod, ProtocolMessageMetadata, ProtocolRemote, ShutdownReason,
-    WorkManagerInterface,
-};
-use super::ExecutableJob;
-use parking_lot::RwLock;
-use std::fmt::Debug;
-use std::{
-    collections::{HashMap, VecDeque},
-    hash::{Hash, Hasher},
-    sync::Arc,
-};
-
-pub struct ProtocolWorkManager<WM: WorkManagerInterface> {
-    inner: Arc<RwLock<WorkManagerInner<WM>>>,
-    pub utility: Arc<WM>,
-    // for now, use a hard-coded value for the number of tasks
-    max_tasks: Arc<usize>,
-    max_enqueued_tasks: Arc<usize>,
-    poll_method: Arc<PollMethod>,
-}
-
-impl<WM: WorkManagerInterface> Clone for ProtocolWorkManager<WM> {
-    fn clone(&self) -> Self {
-        Self {
-            inner: self.inner.clone(),
-            utility: self.utility.clone(),
-            max_tasks: self.max_tasks.clone(),
-            max_enqueued_tasks: self.max_enqueued_tasks.clone(),
-            poll_method: self.poll_method.clone(),
-        }
-    }
-}
-
-pub struct WorkManagerInner<WM: WorkManagerInterface> {
-    pub active_tasks: HashMap<WM::TaskID, Job<WM>>,
-    pub enqueued_tasks: VecDeque<Job<WM>>,
-    // task hash => SSID => enqueued messages
-    pub enqueued_messages: EnqueuedMessage<WM::TaskID, WM::RetryID, WM::ProtocolMessage>,
-    pub retry_id_tracker: HashMap<WM::TaskID, WM::RetryID>,
-}
-
-pub type EnqueuedMessage<A, B, C> = HashMap<A, HashMap<B, VecDeque<C>>>;
-
-#[derive(Debug, Eq, PartialEq)]
-pub struct JobMetadata<WM: WorkManagerInterface> {
-    pub session_id: WM::SessionID,
-    pub is_stalled: bool,
-    pub is_finished: bool,
-    pub has_started: bool,
-    pub is_active: bool,
-}
-
-impl<WM: WorkManagerInterface> ProtocolWorkManager<WM> {
-    pub fn new(
-        utility: WM,
-        max_tasks: usize,
-        max_enqueued_tasks: usize,
-        poll_method: PollMethod,
-    ) -> Self {
-        let this = Self {
-            inner: Arc::new(RwLock::new(WorkManagerInner {
-                active_tasks: HashMap::new(),
-                enqueued_tasks: VecDeque::new(),
-                enqueued_messages: HashMap::new(),
-                retry_id_tracker: HashMap::new(),
-            })),
-            utility: Arc::new(utility),
-            max_tasks: Arc::new(max_tasks),
-            max_enqueued_tasks: Arc::new(max_enqueued_tasks),
-            poll_method: Arc::new(poll_method),
-        };
-
-        if let PollMethod::Interval { millis } = poll_method {
-            let this_worker = this.clone();
-            let logger = this_worker.utility.clone();
-
-            let handler = async move {
-                let periodic_poller = async move {
-                    loop {
-                        let _ = futures_timer::Delay::new(std::time::Duration::from_millis(millis))
-                            .await;
-                        this_worker.poll();
-                    }
-                };
-
-                periodic_poller.await;
-                logger.error("[worker] periodic_poller exited".to_string());
-            };
-
-            gadget_io::spawn(handler);
-        }
-
-        this
-    }
-
-    pub fn clear_enqueued_tasks(&self) {
-        let mut lock = self.inner.write();
-        lock.enqueued_tasks.clear();
-    }
-
-    /// Pushes the task, but does not necessarily start it
-    pub fn push_task<T: ExecutableJob>(
-        &self,
-        task_hash: WM::TaskID,
-        force_start: bool,
-        handle: Arc<dyn ProtocolRemote<WM>>,
-        task: T,
-    ) -> Result<(), WorkManagerError> {
-        if self.job_exists(&task_hash) {
-            return Err(WorkManagerError::PushTaskFailed {
-                reason: "Task already exists".to_string(),
-            });
-        }
-
-        let mut lock = self.inner.write();
-        let job = Job {
-            task: Arc::new(parking_lot::Mutex::new(Some(Box::new(task)))),
-            handle,
-            task_hash,
-            utility: self.utility.clone(),
-            recorded_messages: vec![],
-        };
-
-        if force_start {
-            // This job has priority over the max_tasks limit
-            self.utility.debug(format!(
-                "[FORCE START] Force starting task {}",
-                hex::encode(task_hash)
-            ));
-            return self.start_job_unconditional(job, &mut *lock);
-        }
-
-        if lock.enqueued_tasks.len() + lock.active_tasks.len()
-            >= *self.max_enqueued_tasks + *self.max_tasks
-        {
-            return Err(WorkManagerError::PushTaskFailed {
-                reason: "Too many active and enqueued tasks".to_string(),
-            });
-        }
-
-        lock.enqueued_tasks.push_back(job);
-
-        drop(lock);
-
-        if *self.poll_method != PollMethod::Manual {
-            self.poll();
-        }
-
-        Ok(())
-    }
-
-    pub fn can_submit_more_tasks(&self) -> bool {
-        let lock = self.inner.read();
-        lock.enqueued_tasks.len() + lock.active_tasks.len()
-            < *self.max_enqueued_tasks + *self.max_tasks
-    }
-
-    // Only relevant for keygen
-    pub fn get_active_sessions_metadata(&self, now: WM::Clock) -> Vec<JobMetadata<WM>> {
-        self.inner
-            .read()
-            .active_tasks
-            .iter()
-            .map(|r| r.1.metadata(now))
-            .collect()
-    }
-
-    // This will shutdown and drop all tasks and enqueued messages
-    pub fn force_shutdown_all(&self) {
-        let mut lock = self.inner.write();
-        lock.active_tasks.clear();
-        lock.enqueued_tasks.clear();
-        lock.enqueued_messages.clear();
-    }
-
-    pub fn poll(&self) {
-        // Go through each task and see if it's done
-        let now = self.utility.clock();
-        let mut lock = self.inner.write();
-        let cur_count = lock.active_tasks.len();
-        lock.active_tasks.retain(|_, job| {
-            let is_stalled = job.handle.has_stalled(now);
-            let is_done = job.handle.is_done();
-
-            if is_stalled && !is_done {
-                // If stalled, lets log the start and now blocks for logging purposes
-                self.utility.debug(format!(
-                    "[worker] Job {:?} | Started at {:?} | Now {:?} | is stalled, shutting down",
-                    hex::encode(job.task_hash),
-                    job.handle.started_at(),
-                    now
-                ));
-
-                // The task is stalled, lets be pedantic and shutdown
-                let _ = job.handle.shutdown(ShutdownReason::Stalled);
-                // Return false so that the proposals are released from the currently signing
-                // proposals
-                return false;
-            }
-
-            let is_done = job.handle.is_done();
-
-            !is_done
-        });
-
-        let new_count = lock.active_tasks.len();
-        if cur_count != new_count {
-            self.utility
-                .debug(format!("[worker] {} jobs dropped", cur_count - new_count));
-        }
-
-        // Now, check to see if there is room to start a new task
-        let tasks_to_start = self.max_tasks.saturating_sub(lock.active_tasks.len());
-        for _ in 0..tasks_to_start {
-            if let Some(job) = lock.enqueued_tasks.pop_front() {
-                let task_hash = job.task_hash;
-                if let Err(err) = self.start_job_unconditional(job, &mut *lock) {
-                    self.utility.error(format!(
-                        "[worker] Failed to start job {:?}: {err:?}",
-                        hex::encode(task_hash)
-                    ));
-                }
-            } else {
-                break;
-            }
-        }
-
-        // Next, remove any outdated enqueued messages to prevent RAM bloat
-        let mut to_remove = vec![];
-        for (hash, queue) in lock.enqueued_messages.iter_mut() {
-            for (retry_id, queue) in queue.iter_mut() {
-                let before = queue.len();
-                // Only keep the messages that are not outdated
-                queue.retain(|msg| {
-                    WM::associated_block_id_acceptable(now, msg.associated_block_id())
-                });
-                let after = queue.len();
-
-                if before != after {
-                    self.utility.debug(format!(
-                        "[worker] Removed {} outdated enqueued messages from the queue for {:?}",
-                        before - after,
-                        hex::encode(*hash)
-                    ));
-                }
-
-                if queue.is_empty() {
-                    to_remove.push((*hash, *retry_id));
-                }
-            }
-        }
-
-        // Next, to prevent the existence of piling-up empty *inner* queues, remove them
-        for (hash, retry_id) in to_remove {
-            lock.enqueued_messages
-                .get_mut(&hash)
-                .expect("Should be available")
-                .remove(&retry_id);
-        }
-
-        // Finally, remove any empty outer maps
-        lock.enqueued_messages.retain(|_, v| !v.is_empty());
-    }
-
-    fn start_job_unconditional(
-        &self,
-        job: Job<WM>,
-        lock: &mut WorkManagerInner<WM>,
-    ) -> Result<(), WorkManagerError> {
-        self.utility.debug(format!(
-            "[worker] Starting job {:?}",
-            hex::encode(job.task_hash)
-        ));
-        if let Err(err) = job.handle.start() {
-            return Err(WorkManagerError::PushTaskFailed {
-                reason: format!(
-                    "Failed to start job {:?}: {err:?}",
-                    hex::encode(job.task_hash)
-                ),
-            });
-        } else {
-            // deliver all the enqueued messages to the protocol now
-            if let Some(mut enqueued_messages_map) = lock.enqueued_messages.remove(&job.task_hash) {
-                let job_retry_id = job.handle.retry_id();
-                if let Some(mut enqueued_messages) = enqueued_messages_map.remove(&job_retry_id) {
-                    self.utility.debug(format!(
-                        "Will now deliver {} enqueued message(s) to the async protocol for {:?}",
-                        enqueued_messages.len(),
-                        hex::encode(job.task_hash)
-                    ));
-
-                    while let Some(message) = enqueued_messages.pop_front() {
-                        if should_deliver(&job, &message, job.task_hash) {
-                            if let Err(err) = job.handle.deliver_message(message.clone()) {
-                                self.utility.error(format!(
-                                    "Unable to deliver message for job {:?}: {err:?}",
-                                    hex::encode(job.task_hash)
-                                ));
-                            } else {
-                                lock.record_message(message)
-                            }
-                        } else {
-                            self.utility.warn("Will not deliver enqueued message to async protocol since the message is no longer acceptable".to_string())
-                        }
-                    }
-                }
-
-                // If there are any other messages for other SSIDs, put them back in the map
-                if !enqueued_messages_map.is_empty() {
-                    lock.enqueued_messages
-                        .insert(job.task_hash, enqueued_messages_map);
-                }
-            }
-        }
-        let task = job.task.clone();
-        // Put the job inside here, that way the drop code does not get called right away,
-        // killing the process
-        let task_hash = job.task_hash;
-        *lock
-            .retry_id_tracker
-            .entry(task_hash)
-            .or_insert(job.handle.retry_id()) = job.handle.retry_id();
-        lock.active_tasks.insert(task_hash, job);
-
-        let mut task = task.lock().take().expect("Should not happen");
-        let logger = self.utility.clone();
-        let on_task_finish_handle = self.inner.clone();
-        let task = async move {
-            let result = task.execute().await;
-            if let Err(err) = result {
-                logger.error(format!(
-                    "[worker] Job {:?} finished with error: {err:?}",
-                    hex::encode(task_hash)
-                ));
-            } else {
-                logger.debug(format!(
-                    "[worker] Job {:?} finished successfully",
-                    hex::encode(task_hash)
-                ));
-
-                on_task_finish_handle
-                    .write()
-                    .retry_id_tracker
-                    .remove(&task_hash);
-            }
-        };
-
-        // Spawn the task. When it finishes, it will clean itself up
-        gadget_io::tokio::task::spawn(task);
-        Ok(())
-    }
-
-    pub fn latest_retry_id(&self, task_hash: &WM::TaskID) -> Option<WM::RetryID> {
-        let lock = self.inner.read();
-        lock.retry_id_tracker.get(task_hash).copied()
-    }
-
-    pub fn job_exists(&self, job: &WM::TaskID) -> bool {
-        let lock = self.inner.read();
-        lock.active_tasks.iter().any(|r| &r.1.task_hash == job)
-            || lock.enqueued_tasks.iter().any(|j| &j.task_hash == job)
-    }
-
-    pub fn deliver_message(
-        &self,
-        msg: WM::ProtocolMessage,
-    ) -> Result<DeliveryType, WorkManagerError> {
-        if let Some(recv) = msg.associated_recipient_user_id() {
-            if recv == msg.associated_sender_user_id() {
-                self.utility
-                    .warn("Message sent to self, which is not allowed".to_string());
-                return Err(WorkManagerError::DeliverMessageFailed {
-                    reason: "Message sent to self, which is not allowed".to_string(),
-                });
-            }
-        }
-
-        let message_task_hash = msg.associated_task();
-        let mut lock = self.inner.write();
-
-        // Check the enqueued
-        for task in lock.enqueued_tasks.iter() {
-            if should_deliver(task, &msg, message_task_hash) {
-                self.utility.debug(format!(
-                    "Message is for this ENQUEUED execution in session: {}",
-                    task.handle.session_id()
-                ));
-                if let Err(err) = task.handle.deliver_message(msg.clone()) {
-                    return Err(WorkManagerError::DeliverMessageFailed {
-                        reason: format!("{err:?}"),
-                    });
-                }
-
-                lock.record_message(msg);
-                return Ok(DeliveryType::EnqueuedProtocol);
-            }
-        }
-
-        // Check the currently signing
-        for (_, task) in lock.active_tasks.iter() {
-            if should_deliver(task, &msg, message_task_hash) {
-                self.utility.debug(format!(
-                    "Message is for this CURRENT execution in session: {}",
-                    task.handle.session_id()
-                ));
-                if let Err(err) = task.handle.deliver_message(msg.clone()) {
-                    return Err(WorkManagerError::DeliverMessageFailed {
-                        reason: format!("{err:?}"),
-                    });
-                }
-
-                lock.record_message(msg);
-                return Ok(DeliveryType::ActiveProtocol);
-            }
-        }
-
-        // if the protocol is neither started nor enqueued, then, this message may be for a future
-        // async protocol. Store the message
-        let current_running_session_ids: Vec<WM::SessionID> = lock
-            .active_tasks
-            .iter()
-            .map(|job| job.1.handle.session_id())
-            .collect();
-        let enqueued_session_ids: Vec<WM::SessionID> = lock
-            .enqueued_tasks
-            .iter()
-            .map(|job| job.handle.session_id())
-            .collect();
-        self.utility
-            .debug(format!("Enqueuing message for {:?} | current_running_session_ids: {current_running_session_ids:?} | enqueued_session_ids: {enqueued_session_ids:?}", hex::encode(message_task_hash)));
-        lock.enqueued_messages
-            .entry(message_task_hash)
-            .or_default()
-            .entry(msg.associated_retry_id())
-            .or_default()
-            .push_back(msg);
-
-        Ok(DeliveryType::EnqueuedMessage)
-    }
-
-    #[cfg(test)]
-    pub fn simulate_job_stall(&self, task_id: &WM::TaskID)
-    where
-        WM::RetryID: std::ops::AddAssign<u32>,
-    {
-        let mut lock = self.inner.write();
-        lock.active_tasks.retain(|_, job| {
-            let should_keep = &job.task_hash != task_id;
-
-            if !should_keep {
-                let _ = job.handle.shutdown(ShutdownReason::Stalled);
-            }
-
-            should_keep
-        });
-
-        *lock.retry_id_tracker.get_mut(task_id).expect("Must exist") += 1;
-    }
-
-    pub fn visit_recorded_messages<V: FnOnce(&Vec<WM::ProtocolMessage>) -> T, T>(
-        &self,
-        task_hash: &WM::TaskID,
-        visitor: V,
-    ) -> Option<T> {
-        let lock = self.inner.read();
-        if let Some(active_job) = lock.active_tasks.get(task_hash) {
-            return Some(visitor(&active_job.recorded_messages));
-        }
-
-        if let Some(enqueued_job) = lock
-            .enqueued_tasks
-            .iter()
-            .find(|j| j.task_hash == *task_hash)
-        {
-            return Some(visitor(&enqueued_job.recorded_messages));
-        }
-
-        None
-    }
-
-    pub fn poll_method(&self) -> PollMethod {
-        *self.poll_method
-    }
-}
-
-impl<WM: WorkManagerInterface> WorkManagerInner<WM> {
-    fn record_message(&mut self, msg: WM::ProtocolMessage) {
-        if let Some(active_job) = self.active_tasks.get_mut(&msg.associated_task()) {
-            active_job.recorded_messages.push(msg);
-            return;
-        }
-
-        if let Some(enqueued_job) = self
-            .enqueued_tasks
-            .iter_mut()
-            .find(|j| j.task_hash == msg.associated_task())
-        {
-            enqueued_job.recorded_messages.push(msg);
-        }
-    }
-}
-
-pub struct Job<WM: WorkManagerInterface> {
-    task_hash: WM::TaskID,
-    utility: Arc<WM>,
-    handle: Arc<dyn ProtocolRemote<WM>>,
-    task: Arc<parking_lot::Mutex<Option<Box<dyn ExecutableJob>>>>,
-    recorded_messages: Vec<WM::ProtocolMessage>,
-}
-
-impl<WM: WorkManagerInterface> Job<WM> {
-    fn metadata(&self, now: WM::Clock) -> JobMetadata<WM> {
-        JobMetadata::<WM> {
-            session_id: self.handle.session_id(),
-            is_stalled: self.handle.has_stalled(now),
-            is_finished: self.handle.is_done(),
-            has_started: self.handle.has_started(),
-            is_active: self.handle.is_active(),
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum WorkManagerError {
-    PushTaskFailed { reason: String },
-    DeliverMessageFailed { reason: String },
-}
-
-impl<WM: WorkManagerInterface> PartialEq for Job<WM> {
-    fn eq(&self, other: &Self) -> bool {
-        self.task_hash == other.task_hash
-    }
-}
-
-impl<WM: WorkManagerInterface> Eq for Job<WM> {}
-
-impl<WM: WorkManagerInterface> Hash for Job<WM> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.task_hash.hash(state);
-    }
-}
-
-impl<WM: WorkManagerInterface> Drop for Job<WM> {
-    fn drop(&mut self) {
-        self.utility.debug(format!(
-            "Will remove job {:?} from JobManager",
-            hex::encode(self.task_hash)
-        ));
-        let _ = self.handle.shutdown(ShutdownReason::DropCode);
-    }
-}
-
-fn should_deliver<WM: WorkManagerInterface>(
-    task: &Job<WM>,
-    msg: &WM::ProtocolMessage,
-    message_task_hash: WM::TaskID,
-) -> bool {
-    task.handle.session_id() == msg.associated_session_id()
-        && task.task_hash == message_task_hash
-        && task.handle.retry_id() == msg.associated_retry_id()
-        && WM::associated_block_id_acceptable(
-            task.handle.started_at(), // use to be associated_block_id
-            msg.associated_block_id(),
-        )
-}
-
-#[cfg(test)]
-#[cfg(not(target_family = "wasm"))]
-mod tests {
-    use super::*;
-    use crate::job::builder::{BuiltExecutableJobWrapper, JobBuilder};
-    use crate::job::error::JobError;
-    use gadget_io::tokio;
-    use gadget_io::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
-    use parking_lot::Mutex;
-    use sp_core::ecdsa::Public;
-    use sp_core::serde::{Deserialize, Serialize};
-    use std::sync::atomic::{AtomicBool, Ordering};
-    use std::time::Duration;
-
-    #[derive(Debug, Eq, PartialEq)]
-    struct TestWorkManager;
-    #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
-    pub struct TestMessage {
-        message: String,
-        associated_block_id: u64,
-        associated_session_id: u32,
-        associated_retry_id: u32,
-        associated_task: [u8; 32],
-        associated_sender: u64,
-        associated_recipient: Option<u64>,
-    }
-
-    impl ProtocolMessageMetadata<TestWorkManager> for TestMessage {
-        fn associated_block_id(&self) -> u64 {
-            self.associated_block_id
-        }
-        fn associated_session_id(&self) -> u32 {
-            self.associated_session_id
-        }
-        fn associated_retry_id(&self) -> u32 {
-            self.associated_retry_id
-        }
-        fn associated_task(&self) -> [u8; 32] {
-            self.associated_task
-        }
-
-        fn associated_sender_user_id(&self) -> u16 {
-            self.associated_sender as _
-        }
-
-        fn associated_recipient_user_id(&self) -> Option<u16> {
-            self.associated_recipient.map(|r| r as _)
-        }
-
-        fn payload(&self) -> &Vec<u8> {
-            unimplemented!()
-        }
-
-        fn sender_network_id(&self) -> Option<Public> {
-            None
-        }
-
-        fn recipient_network_id(&self) -> Option<Public> {
-            None
-        }
-
-        fn payload_mut(&mut self) -> &mut Vec<u8> {
-            unimplemented!("Not implemented")
-        }
-    }
-
-    impl WorkManagerInterface for TestWorkManager {
-        type RetryID = u32;
-        type Clock = u64;
-        type ProtocolMessage = TestMessage;
-        type Error = ();
-        type SessionID = u32;
-        type TaskID = [u8; 32];
-
-        fn debug(&self, input: String) {
-            log::debug!("{input}")
-        }
-        fn error(&self, input: String) {
-            log::error!("{input}")
-        }
-        fn warn(&self, input: String) {
-            log::warn!("{input}")
-        }
-        fn clock(&self) -> Self::Clock {
-            0
-        }
-
-        fn acceptable_block_tolerance() -> Self::Clock {
-            0
-        }
-    }
-
-    #[derive(Clone)]
-    pub struct TestProtocolRemote {
-        session_id: u32,
-        retry_id: u32,
-        started_at: u64,
-        delivered_messages: UnboundedSender<TestMessage>,
-        start_tx: Arc<Mutex<Option<gadget_io::tokio::sync::oneshot::Sender<()>>>>,
-        is_done: Arc<AtomicBool>,
-        has_started: Arc<AtomicBool>,
-    }
-
-    impl TestProtocolRemote {
-        pub fn new(
-            session_id: u32,
-            retry_id: u32,
-            started_at: u64,
-            task_tx: UnboundedSender<TestMessage>,
-            start_tx: gadget_io::tokio::sync::oneshot::Sender<()>,
-            is_done: Arc<AtomicBool>,
-        ) -> Arc<Self> {
-            Arc::new(Self {
-                session_id,
-                retry_id,
-                started_at,
-                delivered_messages: task_tx,
-                start_tx: Arc::new(Mutex::new(Some(start_tx))),
-                is_done,
-                has_started: Arc::new(AtomicBool::new(false)),
-            })
-        }
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn generate_async_protocol(
-        session_id: u32,
-        retry_id: u32,
-        started_at: u64,
-    ) -> (
-        Arc<TestProtocolRemote>,
-        BuiltExecutableJobWrapper,
-        UnboundedReceiver<TestMessage>,
-    ) {
-        let (tx, rx) = gadget_io::tokio::sync::mpsc::unbounded_channel();
-        let (start_tx, start_rx) = gadget_io::tokio::sync::oneshot::channel();
-        let is_done = Arc::new(AtomicBool::new(false));
-        let remote = TestProtocolRemote::new(
-            session_id,
-            retry_id,
-            started_at,
-            tx,
-            start_tx,
-            is_done.clone(),
-        );
-        let protocol = async move {
-            start_rx.await.unwrap();
-            is_done.store(true, Ordering::SeqCst);
-            Ok(())
-        };
-        let task = JobBuilder::new().protocol(protocol).build();
-        (remote, task, rx)
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn generate_async_protocol_error(
-        session_id: u32,
-        retry_id: u32,
-        started_at: u64,
-    ) -> (
-        Arc<TestProtocolRemote>,
-        BuiltExecutableJobWrapper,
-        UnboundedReceiver<TestMessage>,
-    ) {
-        let (tx, rx) = gadget_io::tokio::sync::mpsc::unbounded_channel();
-        let (start_tx, start_rx) = gadget_io::tokio::sync::oneshot::channel();
-        let is_done = Arc::new(AtomicBool::new(false));
-        let remote = TestProtocolRemote::new(
-            session_id,
-            retry_id,
-            started_at,
-            tx,
-            start_tx,
-            is_done.clone(),
-        );
-        let protocol = async move {
-            start_rx.await.unwrap();
-            is_done.store(true, Ordering::SeqCst);
-            Err(JobError {
-                reason: "Intentional error".to_string(),
-            })
-        };
-        let task = JobBuilder::new().protocol(protocol).build();
-        (remote, task, rx)
-    }
-
-    impl ProtocolRemote<TestWorkManager> for TestProtocolRemote {
-        fn start(&self) -> Result<(), ()> {
-            self.start_tx.lock().take().unwrap().send(())?;
-            self.has_started.store(true, Ordering::SeqCst);
-            Ok(())
-        }
-        fn session_id(&self) -> u32 {
-            self.session_id
-        }
-        fn has_stalled(&self, now: u64) -> bool {
-            now > self.started_at
-        }
-        fn started_at(&self) -> u64 {
-            self.started_at
-        }
-        fn shutdown(&self, _reason: ShutdownReason) -> Result<(), ()> {
-            Ok(())
-        }
-        fn is_done(&self) -> bool {
-            self.is_done.load(Ordering::SeqCst)
-        }
-        fn deliver_message(&self, message: TestMessage) -> Result<(), ()> {
-            self.delivered_messages.send(message).map_err(|_| ())
-        }
-        fn has_started(&self) -> bool {
-            self.has_started.load(Ordering::SeqCst)
-        }
-        fn is_active(&self) -> bool {
-            self.has_started() && !self.is_done()
-        }
-        fn retry_id(&self) -> u32 {
-            self.retry_id
-        }
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_push_task() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 0);
-        let result = work_manager.push_task([0; 32], false, remote, task);
-        assert!(result.is_ok());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_deliver_message() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote, task, mut rx) = generate_async_protocol(0, 0, 0);
-
-        work_manager
-            .push_task(Default::default(), true, remote.clone(), task)
-            .unwrap();
-
-        let message = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 0,
-            associated_retry_id: 0,
-            associated_task: [0; 32],
-            associated_recipient: None,
-            associated_sender: 0,
-        };
-        assert_ne!(
-            DeliveryType::EnqueuedMessage,
-            work_manager.deliver_message(message).unwrap()
-        );
-        let _ = rx.recv().await.unwrap();
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_deliver_self_ref_message() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(0, 0, 0);
-
-        work_manager
-            .push_task(Default::default(), true, remote.clone(), task)
-            .unwrap();
-
-        let message = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 0,
-            associated_retry_id: 0,
-            associated_task: [0; 32],
-            associated_recipient: Some(0),
-            associated_sender: 0,
-        };
-        assert!(work_manager.deliver_message(message).is_err());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_job_exists() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 0);
-
-        let result = work_manager.job_exists(&[0; 32]);
-        assert!(!result);
-
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-
-        let result = work_manager.job_exists(&[0; 32]);
-        assert!(result);
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_add_multiple_tasks() {
-        let work_manager = ProtocolWorkManager::new(
-            TestWorkManager,
-            2, // max 2 tasks
-            0,
-            PollMethod::Manual,
-        );
-
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-        let (remote3, task3, _rx) = generate_async_protocol(3, 3, 0);
-
-        // Add 2 tasks, should succeed
-        assert!(work_manager
-            .push_task([1; 32], false, remote1, task1)
-            .is_ok());
-        assert!(work_manager
-            .push_task([2; 32], false, remote2, task2)
-            .is_ok());
-
-        // Try to add a third, should fail
-        assert!(work_manager
-            .push_task([3; 32], false, remote3, task3)
-            .is_err());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_deliver_to_queued_task() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote, task, mut rx) = generate_async_protocol(1, 1, 0);
-
-        // Add a queued task
-        work_manager
-            .push_task([0; 32], false, remote.clone(), task)
-            .unwrap();
-
-        // Deliver message, should succeed
-        let msg = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [0; 32],
-            associated_recipient: None,
-            associated_sender: 0,
-        };
-        assert_ne!(
-            DeliveryType::EnqueuedMessage,
-            work_manager.deliver_message(msg.clone()).unwrap()
-        );
-        let next_message = rx.recv().await.unwrap();
-        assert_eq!(next_message, msg);
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_get_task_metadata() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-
-        work_manager
-            .push_task([1; 32], true, remote1, task1)
-            .unwrap();
-        work_manager
-            .push_task([2; 32], true, remote2, task2)
-            .unwrap();
-
-        let now = 0;
-        let metadata = work_manager.get_active_sessions_metadata(now);
-
-        assert_eq!(metadata.len(), 2);
-        let expected1 = JobMetadata {
-            session_id: 1,
-            is_stalled: false,
-            is_finished: false,
-            has_started: true,
-            is_active: true,
-        };
-
-        let expected2 = JobMetadata {
-            session_id: 2,
-            is_stalled: false,
-            is_finished: false,
-            has_started: true,
-            is_active: true,
-        };
-
-        assert!(metadata.contains(&expected1));
-        assert!(metadata.contains(&expected2));
-
-        // Now, start the tasks
-        work_manager.poll();
-        // Wait some time for the tasks to finish
-        gadget_io::tokio::time::sleep(Duration::from_millis(100)).await;
-        // Poll again to cleanup
-        work_manager.poll();
-        // Re-check the statuses
-        let metadata = work_manager.get_active_sessions_metadata(now);
-
-        assert!(metadata.is_empty());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_get_task_metadata_no_force_start() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-
-        let now = 0;
-
-        work_manager
-            .push_task([1; 32], false, remote1, task1)
-            .unwrap();
-        work_manager
-            .push_task([2; 32], false, remote2, task2)
-            .unwrap();
-
-        let metadata = work_manager.get_active_sessions_metadata(now);
-        assert!(metadata.is_empty());
-
-        // Now, poll to start the tasks
-        work_manager.poll();
-
-        let metadata = work_manager.get_active_sessions_metadata(now);
-
-        assert_eq!(metadata.len(), 2);
-        let expected1 = JobMetadata {
-            session_id: 1,
-            is_stalled: false,
-            is_finished: false,
-            has_started: true,
-            is_active: true,
-        };
-
-        let expected2 = JobMetadata {
-            session_id: 2,
-            is_stalled: false,
-            is_finished: false,
-            has_started: true,
-            is_active: true,
-        };
-
-        assert!(metadata.contains(&expected1));
-        assert!(metadata.contains(&expected2));
-
-        // Wait some time for the tasks to finish
-        gadget_io::tokio::time::sleep(Duration::from_millis(100)).await;
-        // Poll again to cleanup
-        work_manager.poll();
-        // Re-check the statuses
-        let metadata = work_manager.get_active_sessions_metadata(now);
-
-        assert!(metadata.is_empty());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_force_shutdown_all() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-        work_manager
-            .push_task([1; 32], true, remote1, task1)
-            .unwrap();
-        work_manager
-            .push_task([2; 32], true, remote2, task2)
-            .unwrap();
-
-        // Verify that the tasks were added
-        assert!(work_manager.job_exists(&[1; 32]));
-        assert!(work_manager.job_exists(&[2; 32]));
-
-        // Force shutdown all tasks
-        work_manager.force_shutdown_all();
-
-        // Verify that the tasks were removed
-        assert!(!work_manager.job_exists(&[1; 32]));
-        assert!(!work_manager.job_exists(&[2; 32]));
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_clear_enqueued_tasks() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-        work_manager
-            .push_task([1; 32], false, remote1, task1)
-            .unwrap();
-        work_manager
-            .push_task([2; 32], false, remote2, task2)
-            .unwrap();
-
-        // Verify that the tasks were added
-        assert!(work_manager.job_exists(&[1; 32]));
-        assert!(work_manager.job_exists(&[2; 32]));
-
-        // Clear enqueued tasks
-        work_manager.clear_enqueued_tasks();
-
-        // Verify that the tasks were removed
-        assert!(!work_manager.job_exists(&[1; 32]));
-        assert!(!work_manager.job_exists(&[2; 32]));
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_max_tasks_limit() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 2, 2, PollMethod::Manual);
-
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-        let (remote3, task3, _rx) = generate_async_protocol(3, 3, 0);
-        let (remote4, task4, _rx) = generate_async_protocol(4, 4, 0);
-        let (remote5, task5, _rx) = generate_async_protocol(5, 5, 0);
-
-        assert!(work_manager
-            .push_task([1; 32], true, remote1, task1)
-            .is_ok());
-        assert!(work_manager
-            .push_task([2; 32], true, remote2, task2)
-            .is_ok());
-        assert!(work_manager
-            .push_task([3; 32], false, remote3, task3)
-            .is_ok());
-        assert!(work_manager
-            .push_task([4; 32], false, remote4, task4)
-            .is_ok());
-
-        // Try to add a fifth task, should fail
-        assert!(work_manager
-            .push_task([5; 32], false, remote5, task5)
-            .is_err());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_task_completion() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-
-        work_manager
-            .push_task([1; 32], true, remote1, task1)
-            .unwrap();
-
-        // Wait some time for the tasks to finish
-        gadget_io::tokio::time::sleep(Duration::from_millis(100)).await;
-        // Poll again to cleanup
-        work_manager.poll();
-
-        // Check that the task has completed
-        assert!(!work_manager.job_exists(&[1; 32]));
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_job_removal_on_drop() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-
-        work_manager
-            .push_task([1; 32], true, remote1, task1)
-            .unwrap();
-
-        // Manual drop of all jobs
-        work_manager.force_shutdown_all();
-
-        // Check that the job has been removed
-        assert!(!work_manager.job_exists(&[1; 32]));
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_message_delivery_to_non_existent_job() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-
-        let message = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [0; 32],
-            associated_sender: 0,
-            associated_recipient: None,
-        };
-
-        // Deliver a message to a non-existent job
-        let delivery_type = work_manager.deliver_message(message).unwrap();
-
-        // The message should be enqueued for future use
-        assert_eq!(delivery_type, DeliveryType::EnqueuedMessage);
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_message_delivery_to_job_with_outdated_block_id() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-
-        work_manager
-            .push_task([0; 32], true, remote1, task1)
-            .unwrap();
-
-        let message = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 10, // Outdated block ID
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [0; 32],
-            associated_sender: 0,
-            associated_recipient: None,
-        };
-
-        // Try to deliver a message with an outdated block ID
-        let delivery_type = work_manager.deliver_message(message).unwrap();
-
-        // The message should be enqueued for future use
-        assert_eq!(delivery_type, DeliveryType::EnqueuedMessage);
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_can_submit_more_tasks() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 0, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 0);
-
-        assert!(work_manager.can_submit_more_tasks());
-        work_manager
-            .push_task([1; 32], false, remote, task)
-            .unwrap();
-        assert!(!work_manager.can_submit_more_tasks());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_multiple_messages_single_task() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 10, 10, PollMethod::Manual);
-        let (remote, task, mut rx) = generate_async_protocol(1, 1, 0);
-
-        work_manager
-            .push_task([0; 32], true, remote.clone(), task)
-            .unwrap();
-
-        let message = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [0; 32],
-            associated_sender: 0,
-            associated_recipient: None,
-        };
-
-        for _ in 0..10 {
-            assert_ne!(
-                DeliveryType::EnqueuedMessage,
-                work_manager.deliver_message(message.clone()).unwrap()
-            );
-        }
-
-        for _ in 0..10 {
-            let next_message = rx.recv().await.unwrap();
-            assert_eq!(next_message, message);
-        }
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_task_not_started() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 0, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 0);
-
-        work_manager
-            .push_task([1; 32], false, remote, task)
-            .unwrap(); // task is enqueued but not started
-        assert!(!work_manager
-            .get_active_sessions_metadata(0)
-            .contains(&JobMetadata {
-                session_id: 1,
-                is_stalled: false,
-                is_finished: false,
-                has_started: false,
-                is_active: false,
-            }));
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_can_submit_more_tasks_with_enqueued_tasks() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 1, PollMethod::Manual);
-        let (remote1, task1, _rx) = generate_async_protocol(1, 1, 0);
-        let (remote2, task2, _rx) = generate_async_protocol(2, 2, 0);
-
-        assert!(work_manager.can_submit_more_tasks());
-        work_manager
-            .push_task([1; 32], true, remote1, task1)
-            .unwrap();
-        assert!(work_manager.can_submit_more_tasks()); // one more task can be enqueued
-        work_manager
-            .push_task([2; 32], false, remote2, task2)
-            .unwrap();
-        assert!(!work_manager.can_submit_more_tasks()); // no more tasks can be added
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_message_delivery_to_stalled_task() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 0, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 1); // Task will stall immediately
-        let msg = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [0; 32],
-            associated_sender: 0,
-            associated_recipient: None,
-        };
-
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-        work_manager.poll(); // should identify and remove the stalled task
-
-        let delivery_type = work_manager.deliver_message(msg).unwrap();
-        assert_eq!(delivery_type, DeliveryType::EnqueuedMessage); // message should be enqueued because task is stalled and removed
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_message_delivery_with_incorrect_task_hash() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 0, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 0);
-        let msg = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [1; 32], // incorrect task hash, not 0;32 as below
-            associated_sender: 0,
-            associated_recipient: None,
-        };
-
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-
-        let delivery_type = work_manager.deliver_message(msg).unwrap(); // incorrect task hash
-        assert_eq!(delivery_type, DeliveryType::EnqueuedMessage); // message should be enqueued because the task hash is incorrect
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_retry_id() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 0, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol_error(1, 0, 0);
-        assert!(work_manager.latest_retry_id(&[0; 32]).is_none());
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-        gadget_io::tokio::time::sleep(Duration::from_millis(100)).await;
-        assert_eq!(
-            work_manager
-                .latest_retry_id(&[0; 32])
-                .expect("Should exist"),
-            0
-        );
-
-        work_manager.simulate_job_stall(&[0; 32]);
-
-        // Simulate a retry
-        let (remote, task, _rx) = generate_async_protocol_error(1, 1, 0);
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-        gadget_io::tokio::time::sleep(Duration::from_millis(100)).await;
-
-        assert_eq!(
-            work_manager
-                .latest_retry_id(&[0; 32])
-                .expect("Should exist"),
-            1
-        );
-
-        work_manager.simulate_job_stall(&[0; 32]);
-
-        // Simulate that the protocol works on the third attempt
-        let (remote, task, _rx) = generate_async_protocol(1, 2, 0);
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-        gadget_io::tokio::time::sleep(Duration::from_millis(100)).await;
-        // Since the protocol was a success, the retry id should be cleared
-        assert!(work_manager.latest_retry_id(&[0; 32]).is_none());
-    }
-
-    #[gadget_io::tokio::test]
-    async fn test_record_message() {
-        let work_manager = ProtocolWorkManager::new(TestWorkManager, 1, 0, PollMethod::Manual);
-        let (remote, task, _rx) = generate_async_protocol(1, 1, 0);
-        work_manager.push_task([0; 32], true, remote, task).unwrap();
-        let message = TestMessage {
-            message: "test".to_string(),
-            associated_block_id: 0,
-            associated_session_id: 1,
-            associated_retry_id: 1,
-            associated_task: [0; 32],
-            associated_sender: 0,
-            associated_recipient: None,
-        };
-
-        work_manager.deliver_message(message.clone()).unwrap();
-        let lock = work_manager.inner.read();
-        let active_job = lock.active_tasks.get(&[0; 32]).unwrap();
-        assert_eq!(active_job.recorded_messages.len(), 1);
-        assert_eq!(active_job.recorded_messages[0], message);
-
-        drop(lock);
-        assert!(work_manager
-            .visit_recorded_messages(&[0; 32], |messages| {
-                assert_eq!(messages.len(), 1);
-                assert_eq!(messages[0], message);
-            })
-            .is_some());
-    }
-
-    struct DummyRangeChecker<const N: u64>;
-    #[derive(Clone, Serialize, Deserialize)]
-    struct DummyProtocolMessage;
-
-    impl<const N: u64> ProtocolMessageMetadata<DummyRangeChecker<N>> for DummyProtocolMessage {
-        fn associated_block_id(&self) -> u64 {
-            0u64
-        }
-        fn associated_session_id(&self) -> u64 {
-            0u64
-        }
-        fn associated_retry_id(&self) -> u64 {
-            0u64
-        }
-        fn associated_task(&self) -> [u8; 32] {
-            [0; 32]
-        }
-
-        fn associated_sender_user_id(&self) -> u16 {
-            todo!()
-        }
-
-        fn associated_recipient_user_id(&self) -> Option<u16> {
-            todo!()
-        }
-
-        fn payload(&self) -> &Vec<u8> {
-            todo!()
-        }
-
-        fn sender_network_id(&self) -> Option<Public> {
-            todo!()
-        }
-
-        fn recipient_network_id(&self) -> Option<Public> {
-            todo!()
-        }
-
-        fn payload_mut(&mut self) -> &mut Vec<u8> {
-            todo!()
-        }
-    }
-
-    impl<const N: u64> WorkManagerInterface for DummyRangeChecker<N> {
-        type RetryID = u64;
-        type Clock = u64;
-        type ProtocolMessage = DummyProtocolMessage;
-        type Error = ();
-        type SessionID = u64;
-        type TaskID = [u8; 32];
-
-        fn debug(&self, _input: String) {
-            todo!()
-        }
-
-        fn error(&self, _input: String) {
-            todo!()
-        }
-
-        fn warn(&self, _input: String) {
-            todo!()
-        }
-
-        fn clock(&self) -> Self::Clock {
-            todo!()
-        }
-
-        fn acceptable_block_tolerance() -> Self::Clock {
-            N
-        }
-    }
-
-    #[test]
-    fn test_range_above() {
-        let current_block: u64 = 10;
-        const TOL: u64 = 5;
-        assert!(DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block
-        ));
-        assert!(DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block + 1
-        ));
-        assert!(DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block + TOL
-        ));
-        assert!(!DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block + TOL + 1
-        ));
-    }
-
-    #[test]
-    fn test_range_below() {
-        let current_block: u64 = 10;
-        const TOL: u64 = 5;
-        assert!(DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block
-        ));
-        assert!(DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block - 1
-        ));
-        assert!(DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block - TOL
-        ));
-        assert!(!DummyRangeChecker::<TOL>::associated_block_id_acceptable(
-            current_block,
-            current_block - TOL - 1
-        ));
-    }
-}
diff --git a/core/src/job/mod.rs b/core/src/job/mod.rs
deleted file mode 100644
index 2f284a9e0..000000000
--- a/core/src/job/mod.rs
+++ /dev/null
@@ -1,278 +0,0 @@
-pub mod builder;
-pub mod error;
-#[cfg(feature = "std")]
-pub mod manager;
-pub mod protocol;
-
-use alloc::boxed::Box;
-use async_trait::async_trait;
-use core::future::Future;
-use core::pin::Pin;
-use error::JobError;
-
-pub enum ProceedWithExecution {
-    True,
-    False,
-}
-
-pub trait SendFuture<'a, T>: Send + Future<Output = T> + 'a {}
-impl<'a, F: Send + Future<Output = T> + 'a, T> SendFuture<'a, T> for F {}
-
-#[async_trait]
-pub trait ExecutableJob: Send + 'static {
-    async fn pre_job_hook(&mut self) -> Result<ProceedWithExecution, JobError>;
-    async fn job(&mut self) -> Result<(), JobError>;
-    async fn post_job_hook(&mut self) -> Result<(), JobError>;
-    async fn catch(&mut self);
-
-    async fn execute(&mut self) -> Result<(), JobError> {
-        match self.pre_job_hook().await? {
-            ProceedWithExecution::True => match self.job().await {
-                Ok(_) => match self.post_job_hook().await {
-                    Ok(_) => Ok(()),
-                    Err(err) => {
-                        self.catch().await;
-                        Err(err)
-                    }
-                },
-                Err(err) => {
-                    self.catch().await;
-                    Err(err)
-                }
-            },
-            ProceedWithExecution::False => Ok(()),
-        }
-    }
-}
-
-pub struct ExecutableJobWrapper<Pre: ?Sized, Protocol: ?Sized, Post: ?Sized, Catch: ?Sized> {
-    pre: Pin<Box<Pre>>,
-    protocol: Pin<Box<Protocol>>,
-    post: Pin<Box<Post>>,
-    catch: Pin<Box<Catch>>,
-}
-
-#[async_trait]
-impl<Pre: ?Sized, Protocol: ?Sized, Post: ?Sized, Catch: ?Sized> ExecutableJob
-    for ExecutableJobWrapper<Pre, Protocol, Post, Catch>
-where
-    Pre: SendFuture<'static, Result<ProceedWithExecution, JobError>>,
-    Protocol: SendFuture<'static, Result<(), JobError>>,
-    Post: SendFuture<'static, Result<(), JobError>>,
-    Catch: SendFuture<'static, ()>,
-{
-    async fn pre_job_hook(&mut self) -> Result<ProceedWithExecution, JobError> {
-        self.pre.as_mut().await
-    }
-
-    async fn job(&mut self) -> Result<(), JobError> {
-        self.protocol.as_mut().await
-    }
-
-    async fn post_job_hook(&mut self) -> Result<(), JobError> {
-        self.post.as_mut().await
-    }
-
-    async fn catch(&mut self) {
-        self.catch.as_mut().await
-    }
-}
-
-impl<Pre, Protocol, Post, Catch> ExecutableJobWrapper<Pre, Protocol, Post, Catch>
-where
-    Pre: SendFuture<'static, Result<ProceedWithExecution, JobError>>,
-    Protocol: SendFuture<'static, Result<(), JobError>>,
-    Post: SendFuture<'static, Result<(), JobError>>,
-    Catch: SendFuture<'static, ()>,
-{
-    pub fn new(pre: Pre, protocol: Protocol, post: Post, catch: Catch) -> Self {
-        Self {
-            pre: Box::pin(pre),
-            protocol: Box::pin(protocol),
-            post: Box::pin(post),
-            catch: Box::pin(catch),
-        }
-    }
-}
-
-#[cfg(test)]
-#[cfg(feature = "std")]
-mod tests {
-    use crate::job::builder::JobBuilder;
-    use crate::job::ExecutableJob;
-    use alloc::sync::Arc;
-    use core::sync::atomic::{AtomicUsize, Ordering};
-    use gadget_io::tokio;
-
-    #[tokio::test]
-    async fn test_executable_job_wrapper_proceed() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_clone2 = counter.clone();
-        let counter_final = counter.clone();
-
-        let pre = async move {
-            counter.fetch_add(1, Ordering::SeqCst);
-            Ok(super::ProceedWithExecution::True)
-        };
-
-        let protocol = async move {
-            counter_clone.fetch_add(1, Ordering::SeqCst);
-            Ok(())
-        };
-
-        let post = async move {
-            counter_clone2.fetch_add(1, Ordering::SeqCst);
-            Ok(())
-        };
-
-        let catch = async move {};
-
-        let mut job = super::ExecutableJobWrapper::new(pre, protocol, post, catch);
-        job.execute().await.unwrap();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 3);
-    }
-
-    #[tokio::test]
-    async fn test_executable_job_wrapper_no_proceed() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_clone2 = counter.clone();
-        let counter_final = counter.clone();
-
-        let pre = async move {
-            counter.fetch_add(1, Ordering::SeqCst);
-            Ok(super::ProceedWithExecution::False)
-        };
-
-        let protocol = async move {
-            counter_clone.fetch_add(1, Ordering::SeqCst);
-            Ok(())
-        };
-
-        let post = async move {
-            counter_clone2.fetch_add(1, Ordering::SeqCst);
-            Ok(())
-        };
-
-        let catch = async move {};
-
-        let mut job = super::ExecutableJobWrapper::new(pre, protocol, post, catch);
-        job.execute().await.unwrap();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 1);
-    }
-
-    #[tokio::test]
-    async fn test_job_builder() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_clone2 = counter.clone();
-        let counter_final = counter.clone();
-
-        let mut job = JobBuilder::new()
-            .pre(async move {
-                counter.fetch_add(1, Ordering::SeqCst);
-                Ok(super::ProceedWithExecution::True)
-            })
-            .protocol(async move {
-                counter_clone.fetch_add(1, Ordering::SeqCst);
-                Ok(())
-            })
-            .post(async move {
-                counter_clone2.fetch_add(1, Ordering::SeqCst);
-                Ok(())
-            })
-            .build();
-
-        job.execute().await.unwrap();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 3);
-    }
-
-    #[tokio::test]
-    async fn test_job_builder_no_pre() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_clone2 = counter.clone();
-        let counter_final = counter.clone();
-
-        let mut job = JobBuilder::default()
-            .protocol(async move {
-                counter_clone.fetch_add(1, Ordering::SeqCst);
-                Ok(())
-            })
-            .post(async move {
-                counter_clone2.fetch_add(1, Ordering::SeqCst);
-                Ok(())
-            })
-            .build();
-
-        job.execute().await.unwrap();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 2);
-    }
-
-    #[tokio::test]
-    async fn test_job_builder_no_post() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_final = counter.clone();
-
-        let mut job = JobBuilder::default()
-            .pre(async move {
-                counter.fetch_add(1, Ordering::SeqCst);
-                Ok(super::ProceedWithExecution::True)
-            })
-            .protocol(async move {
-                counter_clone.fetch_add(1, Ordering::SeqCst);
-                Ok(())
-            })
-            .build();
-
-        job.execute().await.unwrap();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 2);
-    }
-
-    #[tokio::test]
-    async fn test_job_builder_no_pre_no_post() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_final = counter.clone();
-
-        let mut job = JobBuilder::default()
-            .protocol(async move {
-                counter_clone.fetch_add(1, Ordering::SeqCst);
-                Ok(())
-            })
-            .build();
-
-        job.execute().await.unwrap();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 1);
-    }
-
-    #[tokio::test]
-    async fn test_protocol_err_catch_performs_increment() {
-        let counter = Arc::new(AtomicUsize::new(0));
-        let counter_clone = counter.clone();
-        let counter_clone2 = counter.clone();
-        let counter_final = counter.clone();
-
-        let pre = async move {
-            counter.fetch_add(1, Ordering::SeqCst);
-            Ok(super::ProceedWithExecution::True)
-        };
-
-        let protocol = async move {
-            counter_clone.fetch_add(1, Ordering::SeqCst);
-            Err(super::JobError::from("Protocol error"))
-        };
-
-        let post = async move { unreachable!("Post should not be called") };
-
-        let catch = async move {
-            counter_clone2.fetch_add(1, Ordering::SeqCst);
-        };
-
-        let mut job = super::ExecutableJobWrapper::new(pre, protocol, post, catch);
-        job.execute().await.unwrap_err();
-        assert_eq!(counter_final.load(Ordering::SeqCst), 3);
-    }
-}
diff --git a/core/src/lib.rs b/core/src/lib.rs
deleted file mode 100644
index cfd8aaff6..000000000
--- a/core/src/lib.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-#![cfg_attr(not(feature = "std"), no_std)]
-
-extern crate alloc;
-
-pub mod gadget;
-pub mod job;
diff --git a/environments/tangle/Cargo.toml b/environments/tangle/Cargo.toml
deleted file mode 100644
index b65d2edbe..000000000
--- a/environments/tangle/Cargo.toml
+++ /dev/null
@@ -1,34 +0,0 @@
-[package]
-name = "tangle-environment"
-version = "0.1.0"
-authors.workspace = true
-edition.workspace = true
-license.workspace = true
-homepage.workspace = true
-repository.workspace = true
-
-[features]
-default = ["std"]
-testing = []
-std = [
-    "gadget-common/std",
-    "sp-core/std",
-    "sp-application-crypto/std",
-    "scale-info/std",
-    "parity-scale-codec/std",
-    "anyhow/std",
-]
-
-[dependencies]
-gadget-common = { workspace = true, features = ["substrate"] }
-environment-utils = { workspace = true, features = ["tangle"] }
-serde = { workspace = true, features = ["derive"] }
-gadget-core = { workspace = true }
-parking_lot = { workspace = true }
-sp-core = { workspace = true }
-sp-application-crypto = { workspace = true }
-scale-info = { workspace = true }
-parity-scale-codec = { workspace = true }
-url = { workspace = true }
-auto_impl = { workspace = true }
-anyhow = { workspace = true }
\ No newline at end of file
diff --git a/environments/tangle/src/gadget.rs b/environments/tangle/src/gadget.rs
deleted file mode 100644
index d9c63bca7..000000000
--- a/environments/tangle/src/gadget.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use crate::runtime::TangleConfig;
-use crate::work_manager::TangleWorkManager;
-use gadget_common::tangle_subxt::subxt::events::Events;
-use gadget_common::WorkManagerInterface;
-
-#[derive(Clone)]
-pub struct TangleEvent {
-    /// Finalized block number.
-    pub number: u64,
-    /// Finalized block header hash.
-    pub hash: [u8; 32],
-    /// Events
-    pub events: Events<TangleConfig>,
-}
-
-pub struct TangleJobMetadata {
-    pub task_id: <TangleWorkManager as WorkManagerInterface>::TaskID,
-    pub retry_id: <TangleWorkManager as WorkManagerInterface>::RetryID,
-    pub job_id: u64,
-    pub now: <TangleWorkManager as WorkManagerInterface>::Clock,
-    pub at: [u8; 32],
-}
-
-#[derive(Debug, Clone)]
-pub struct SubxtConfig {
-    /// The URL of the Tangle Node.
-    pub endpoint: url::Url,
-}
diff --git a/environments/tangle/src/lib.rs b/environments/tangle/src/lib.rs
deleted file mode 100644
index 1ba40b92b..000000000
--- a/environments/tangle/src/lib.rs
+++ /dev/null
@@ -1,133 +0,0 @@
-use crate::gadget::TangleEvent;
-use crate::runtime::TangleConfig;
-use crate::work_manager::TangleWorkManager;
-use environment_utils::transaction_manager::tangle::{SubxtPalletSubmitter, TanglePalletSubmitter};
-use gadget::{SubxtConfig, TangleJobMetadata};
-use gadget_common::async_trait::async_trait;
-use gadget_common::channels::UserID;
-use gadget_common::client::PairSigner;
-use gadget_common::config::DebugLogger;
-use gadget_common::environments::{EventMetadata, GadgetEnvironment};
-use gadget_common::tangle_subxt::subxt::tx::Signer;
-use gadget_common::utils::serialize;
-use gadget_common::WorkManagerInterface;
-use message::TangleProtocolMessage;
-use runtime::TangleRuntime;
-use sp_core::serde::Serialize;
-use sp_core::{ecdsa, sr25519};
-use std::sync::Arc;
-
-pub mod api;
-pub mod gadget;
-pub mod message;
-pub mod runtime;
-pub mod work_manager;
-
-pub type TangleTransactionManager = Arc<dyn TanglePalletSubmitter>;
-
-#[derive(Clone)]
-pub struct TangleEnvironment {
-    pub subxt_config: SubxtConfig,
-    pub account_key: sr25519::Pair,
-    pub logger: DebugLogger,
-    pub tx_manager: Arc<parking_lot::Mutex<Option<TangleTransactionManager>>>,
-}
-
-impl TangleEnvironment {
-    pub fn new(subxt_config: SubxtConfig, account_key: sr25519::Pair, logger: DebugLogger) -> Self {
-        Self {
-            subxt_config,
-            account_key,
-            logger,
-            tx_manager: Arc::new(parking_lot::Mutex::new(None)),
-        }
-    }
-}
-
-impl std::fmt::Debug for TangleEnvironment {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("TangleEnvironment")
-            .field("subxt_config", &self.subxt_config)
-            .finish()
-    }
-}
-
-#[async_trait]
-impl GadgetEnvironment for TangleEnvironment {
-    type Event = TangleEvent;
-    type ProtocolMessage = TangleProtocolMessage;
-    type Client = TangleRuntime;
-    type WorkManager = TangleWorkManager;
-    type Error = gadget_common::Error;
-    type Clock = <Self::WorkManager as WorkManagerInterface>::Clock;
-    type RetryID = <Self::WorkManager as WorkManagerInterface>::RetryID;
-    type TaskID = <Self::WorkManager as WorkManagerInterface>::TaskID;
-    type SessionID = <Self::WorkManager as WorkManagerInterface>::SessionID;
-    type TransactionManager = TangleTransactionManager;
-    type JobInitMetadata = TangleJobMetadata;
-
-    fn build_protocol_message<Payload: Serialize>(
-        associated_block_id: <Self::WorkManager as WorkManagerInterface>::Clock,
-        associated_session_id: <Self::WorkManager as WorkManagerInterface>::SessionID,
-        associated_retry_id: <Self::WorkManager as WorkManagerInterface>::RetryID,
-        associated_task_id: <Self::WorkManager as WorkManagerInterface>::TaskID,
-        from: UserID,
-        to: Option<UserID>,
-        payload: &Payload,
-        from_account_id: Option<ecdsa::Public>,
-        to_network_id: Option<ecdsa::Public>,
-    ) -> Self::ProtocolMessage {
-        TangleProtocolMessage {
-            associated_block_id,
-            associated_session_id,
-            associated_retry_id,
-            task_hash: associated_task_id,
-            from,
-            to,
-            payload: serialize(payload).expect("Failed to serialize message"),
-            from_network_id: from_account_id,
-            to_network_id,
-        }
-    }
-
-    async fn setup_runtime(&self) -> Result<Self::Client, Self::Error> {
-        let subxt_client =
-            gadget_common::tangle_subxt::subxt::OnlineClient::<TangleConfig>::from_url(
-                &self.subxt_config.endpoint,
-            )
-            .await
-            .map_err(|err| gadget_common::Error::ClientError {
-                err: err.to_string(),
-            })?;
-
-        let pair_signer = PairSigner::new(self.account_key.clone());
-        let account_id = pair_signer.account_id();
-        let mut lock = self.tx_manager.lock();
-
-        if lock.is_none() {
-            let tx_manager_submitter = SubxtPalletSubmitter::with_client(
-                subxt_client.clone(),
-                pair_signer,
-                self.logger.clone(),
-            );
-            *lock = Some(Arc::new(tx_manager_submitter));
-        }
-
-        Ok(TangleRuntime::new(subxt_client, account_id))
-    }
-
-    fn transaction_manager(&self) -> Self::TransactionManager {
-        let lock = self.tx_manager.lock();
-        if let Some(tx_manager) = &*lock {
-            tx_manager.clone()
-        } else {
-            panic!("Transaction manager not initialized")
-        }
-    }
-}
-
-impl EventMetadata<TangleEnvironment> for TangleEvent {
-    fn number(&self) -> <TangleEnvironment as GadgetEnvironment>::Clock {
-        self.number
-    }
-}
diff --git a/environments/tangle/src/message.rs b/environments/tangle/src/message.rs
deleted file mode 100644
index 05437d041..000000000
--- a/environments/tangle/src/message.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-use crate::work_manager::TangleWorkManager;
-use gadget_common::utils::serialize;
-use gadget_core::job::protocol::{ProtocolMessageMetadata, WorkManagerInterface};
-use serde::{Deserialize, Serialize};
-use sp_core::ecdsa;
-
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct TangleProtocolMessage {
-    pub associated_block_id: <TangleWorkManager as WorkManagerInterface>::Clock,
-    pub associated_session_id: <TangleWorkManager as WorkManagerInterface>::SessionID,
-    pub associated_retry_id: <TangleWorkManager as WorkManagerInterface>::RetryID,
-    // A unique marker for the associated task this message belongs to
-    pub task_hash: <TangleWorkManager as WorkManagerInterface>::TaskID,
-    pub from: UserID,
-    // If None, this is a broadcasted message
-    pub to: Option<UserID>,
-    pub payload: Vec<u8>,
-    // Some protocols need this additional data
-    pub from_network_id: Option<ecdsa::Public>,
-    // Some protocol need this additional data
-    pub to_network_id: Option<ecdsa::Public>,
-}
-
-pub type UserID = u16;
-
-impl ProtocolMessageMetadata<TangleWorkManager> for TangleProtocolMessage {
-    fn associated_block_id(&self) -> <TangleWorkManager as WorkManagerInterface>::Clock {
-        self.associated_block_id
-    }
-
-    fn associated_session_id(&self) -> <TangleWorkManager as WorkManagerInterface>::SessionID {
-        self.associated_session_id
-    }
-
-    fn associated_retry_id(&self) -> <TangleWorkManager as WorkManagerInterface>::RetryID {
-        self.associated_retry_id
-    }
-
-    fn associated_task(&self) -> <TangleWorkManager as WorkManagerInterface>::TaskID {
-        self.task_hash
-    }
-
-    fn associated_sender_user_id(&self) -> u16 {
-        self.from
-    }
-
-    fn associated_recipient_user_id(&self) -> Option<u16> {
-        self.to
-    }
-
-    fn payload(&self) -> &Vec<u8> {
-        &self.payload
-    }
-
-    fn payload_mut(&mut self) -> &mut Vec<u8> {
-        &mut self.payload
-    }
-
-    fn sender_network_id(&self) -> Option<ecdsa::Public> {
-        self.from_network_id
-    }
-
-    fn recipient_network_id(&self) -> Option<ecdsa::Public> {
-        self.to_network_id
-    }
-}
-
-impl TangleProtocolMessage {
-    /// Returns a hash of the message
-    pub fn hash(&self) -> Vec<u8> {
-        let serialized = serialize(&self).expect("Should serialize");
-        sp_core::keccak_256(&serialized).to_vec()
-    }
-}
diff --git a/environments/tangle/src/work_manager.rs b/environments/tangle/src/work_manager.rs
deleted file mode 100644
index c937149bf..000000000
--- a/environments/tangle/src/work_manager.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use crate::message::TangleProtocolMessage;
-use gadget_common::config::DebugLogger;
-use gadget_core::job::protocol::WorkManagerInterface;
-use parking_lot::RwLock;
-use std::sync::Arc;
-
-pub struct TangleWorkManager {
-    pub(crate) clock: Arc<RwLock<Option<<TangleWorkManager as WorkManagerInterface>::Clock>>>,
-    pub(crate) logger: DebugLogger,
-}
-
-/// Acceptable delivery tolerance for a message. Messages that fall out
-/// of this range will be rejected by the work manager
-#[cfg(not(feature = "testing"))]
-const ACCEPTABLE_BLOCK_TOLERANCE: u64 = 20;
-#[cfg(feature = "testing")]
-const ACCEPTABLE_BLOCK_TOLERANCE: u64 = 60;
-
-impl WorkManagerInterface for TangleWorkManager {
-    type RetryID = u16;
-    type Clock = u64;
-    type ProtocolMessage = TangleProtocolMessage;
-    type Error = gadget_common::Error;
-    type SessionID = u64;
-    type TaskID = [u8; 32];
-
-    fn debug(&self, input: String) {
-        self.logger.debug(input)
-    }
-
-    fn error(&self, input: String) {
-        self.logger.error(input)
-    }
-
-    fn warn(&self, input: String) {
-        self.logger.warn(input)
-    }
-
-    fn clock(&self) -> Self::Clock {
-        self.clock
-            .read()
-            .expect("No finality notification received")
-    }
-
-    fn acceptable_block_tolerance() -> Self::Clock {
-        ACCEPTABLE_BLOCK_TOLERANCE
-    }
-}
diff --git a/environments/utils/Cargo.toml b/environments/utils/Cargo.toml
deleted file mode 100644
index b7bc4fa1a..000000000
--- a/environments/utils/Cargo.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[package]
-name = "environment-utils"
-version = "0.1.0"
-authors.workspace = true
-edition.workspace = true
-license.workspace = true
-homepage.workspace = true
-repository.workspace = true
-
-[features]
-tangle = []
-
-[dependencies]
-gadget-common = { workspace = true }
-anyhow = { workspace = true }
-auto_impl = { workspace = true }
-tangle-subxt = { workspace = true }
-
-[dev-dependencies]
-gadget-io = { workspace = true }
-tokio = { workspace = true }
\ No newline at end of file
diff --git a/environments/utils/src/lib.rs b/environments/utils/src/lib.rs
deleted file mode 100644
index 6e1d5ac4a..000000000
--- a/environments/utils/src/lib.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-pub mod transaction_manager;
diff --git a/environments/utils/src/transaction_manager/mod.rs b/environments/utils/src/transaction_manager/mod.rs
deleted file mode 100644
index c0158d109..000000000
--- a/environments/utils/src/transaction_manager/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-#[cfg(feature = "tangle")]
-pub mod tangle;
diff --git a/environments/utils/src/transaction_manager/tangle/mod.rs b/environments/utils/src/transaction_manager/tangle/mod.rs
deleted file mode 100644
index fe34fc452..000000000
--- a/environments/utils/src/transaction_manager/tangle/mod.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-use auto_impl::auto_impl;
-use gadget_common::async_trait::async_trait;
-use gadget_common::config::DebugLogger;
-use gadget_common::tangle_runtime::api;
-use gadget_common::tangle_subxt::subxt;
-use gadget_common::tangle_subxt::subxt::OnlineClient;
-use std::fmt::Debug;
-use tangle_subxt::subxt::tx::Payload;
-
-#[async_trait]
-#[auto_impl(Arc)]
-pub trait TanglePalletSubmitter: Send + Sync + Debug + 'static {
-    async fn submit_job_call_result(
-        &self,
-        service_id: u64,
-        call_id: u64,
-        result: api::services::calls::types::submit_result::Result,
-    ) -> Result<(), gadget_common::Error>;
-}
-
-pub struct SubxtPalletSubmitter<C, S>
-where
-    C: subxt::Config,
-    S: subxt::tx::Signer<C>,
-{
-    subxt_client: OnlineClient<C>,
-    signer: S,
-    logger: DebugLogger,
-}
-
-impl<C: subxt::Config, S: subxt::tx::Signer<C>> Debug for SubxtPalletSubmitter<C, S> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("SubxtPalletSubmitter")
-            .field("signer", &self.signer.account_id())
-            .finish()
-    }
-}
-
-#[async_trait]
-impl<C, S> TanglePalletSubmitter for SubxtPalletSubmitter<C, S>
-where
-    C: subxt::Config + Send + Sync + 'static,
-    S: subxt::tx::Signer<C> + Send + Sync + 'static,
-    C::AccountId: std::fmt::Display + Send + Sync + 'static,
-    C::Hash: std::fmt::Display,
-    <C::ExtrinsicParams as subxt::config::ExtrinsicParams<C>>::Params:
-        Default + Send + Sync + 'static,
-{
-    async fn submit_job_call_result(
-        &self,
-        service_id: u64,
-        call_id: u64,
-        result: api::services::calls::types::submit_result::Result,
-    ) -> Result<(), gadget_common::Error> {
-        let tx = api::tx()
-            .services()
-            .submit_result(service_id, call_id, result);
-        match self.submit(&tx).await {
-            Ok(hash) => {
-                self.logger.info(format!(
-                    "({}) Service result submitted for service-id/call-id: {service_id}/{call_id} at block: {hash}",
-                    self.signer.account_id(),
-                ));
-                Ok(())
-            }
-            Err(err) if err.to_string().contains("JobNotFound") => {
-                self.logger.warn(format!(
-                    "({}) Service not found for service-id/call-id: {service_id}/{call_id}",
-                    self.signer.account_id(),
-                ));
-                Ok(())
-            }
-            Err(err) => {
-                return Err(gadget_common::Error::ClientError {
-                    err: format!("Failed to submit job result: {err:?}"),
-                })
-            }
-        }
-    }
-}
-
-impl<C, S> SubxtPalletSubmitter<C, S>
-where
-    C: subxt::Config,
-    C::AccountId: std::fmt::Display,
-    S: subxt::tx::Signer<C>,
-    C::Hash: std::fmt::Display,
-    <C::ExtrinsicParams as subxt::config::ExtrinsicParams<C>>::Params: Default,
-{
-    pub async fn new(signer: S, logger: DebugLogger) -> Result<Self, gadget_common::Error> {
-        let subxt_client =
-            OnlineClient::<C>::new()
-                .await
-                .map_err(|err| gadget_common::Error::ClientError {
-                    err: format!("Failed to setup api: {err:?}"),
-                })?;
-        Ok(Self::with_client(subxt_client, signer, logger))
-    }
-
-    pub fn with_client(subxt_client: OnlineClient<C>, signer: S, logger: DebugLogger) -> Self {
-        Self {
-            subxt_client,
-            signer,
-            logger,
-        }
-    }
-
-    async fn submit<Call: Payload>(&self, call: &Call) -> anyhow::Result<C::Hash> {
-        if let Some(details) = call.validation_details() {
-            self.logger.trace(format!(
-                "({}) Submitting {}.{}",
-                self.signer.account_id(),
-                details.pallet_name,
-                details.call_name
-            ));
-        }
-        Ok(self
-            .subxt_client
-            .tx()
-            .sign_and_submit_then_watch_default(call, &self.signer)
-            .await?
-            .wait_for_finalized_success()
-            .await?
-            .extrinsic_hash())
-    }
-}
diff --git a/macros/blueprint-proc-macro-playground/src/lib.rs b/macros/blueprint-proc-macro-playground/src/lib.rs
index dd89157bb..8044502ff 100644
--- a/macros/blueprint-proc-macro-playground/src/lib.rs
+++ b/macros/blueprint-proc-macro-playground/src/lib.rs
@@ -124,8 +124,8 @@ mod tests {
         eprintln!("{}", super::REGISTRATION_HOOK);
     }
 
-    #[test]
-    fn example_benchmark() {
-        super::keygen_2_of_3_benchmark();
-    }
+    // #[test]
+    // fn example_benchmark() {
+    //     super::keygen_2_of_3_benchmark();
+    // }
 }
diff --git a/macros/blueprint-proc-macro/src/job.rs b/macros/blueprint-proc-macro/src/job.rs
index be32296d5..1f3963ab0 100644
--- a/macros/blueprint-proc-macro/src/job.rs
+++ b/macros/blueprint-proc-macro/src/job.rs
@@ -129,6 +129,8 @@ pub(crate) fn job_impl(args: &JobArgs, input: &ItemFn) -> syn::Result<TokenStrea
         #event_handler_gen
     };
 
+    // println!("{}", gen);
+
     Ok(gen.into())
 }
 
diff --git a/protocol-macros/Cargo.toml b/protocol-macros/Cargo.toml
deleted file mode 100644
index 36b169dae..000000000
--- a/protocol-macros/Cargo.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[package]
-name = "protocol-macros"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[lib]
-proc-macro = true
-
-[dependencies]
-syn = { workspace = true, features = ["full"] }
-quote = { workspace = true }
-proc-macro2 = { workspace = true }
diff --git a/protocol-macros/src/lib.rs b/protocol-macros/src/lib.rs
deleted file mode 100644
index 8c6955685..000000000
--- a/protocol-macros/src/lib.rs
+++ /dev/null
@@ -1,192 +0,0 @@
-use proc_macro::TokenStream;
-use quote::{quote, ToTokens};
-use syn::{parse_macro_input, GenericParam, Generics, ItemStruct};
-
-fn generate_generic_params_with_bounds(
-    generics: &Generics,
-    excluded: Option<&[&str]>,
-) -> proc_macro2::TokenStream {
-    generics
-        .params
-        .iter()
-        .filter_map(|param| {
-            if let GenericParam::Type(ty) = param {
-                if let Some(excluded) = excluded {
-                    if excluded.contains(&ty.ident.to_string().as_str()) {
-                        return None;
-                    }
-                }
-                let ident = ty.ident.clone();
-                let bounds = ty.bounds.clone();
-                if bounds.is_empty() {
-                    Some(quote! { #ident, })
-                } else {
-                    Some(quote! { #ident: #bounds, })
-                }
-            } else {
-                None
-            }
-        })
-        .collect()
-}
-
-fn generate_generic_params(
-    generics: &Generics,
-    excluded: Option<&[&str]>,
-) -> proc_macro2::TokenStream {
-    let generic_idents: Vec<_> = generics
-        .params
-        .iter()
-        .filter_map(|param| {
-            if let GenericParam::Type(ty) = param {
-                if let Some(excluded) = excluded {
-                    if excluded.contains(&ty.ident.to_string().as_str()) {
-                        return None;
-                    }
-                }
-                Some(&ty.ident)
-            } else {
-                None
-            }
-        })
-        .collect();
-
-    // Create a TokenStream from the generic identifiers
-    let generics_ts: proc_macro2::TokenStream = generic_idents
-        .clone()
-        .into_iter()
-        .enumerate()
-        .map(|(i, ident)| {
-            if i < generic_idents.len() - 1 {
-                quote! { #ident , }
-            } else {
-                quote! { #ident }
-            }
-        })
-        .collect();
-
-    generics_ts
-}
-
-#[proc_macro_attribute]
-pub fn protocol(_args: TokenStream, input: TokenStream) -> TokenStream {
-    let input_struct = parse_macro_input!(input as ItemStruct);
-
-    let struct_ident = &input_struct.ident;
-    let new_struct = syn::Ident::new(&format!("_Internal{}", struct_ident), struct_ident.span());
-    let struct_generics = &input_struct.generics;
-    let where_bounds = &struct_generics
-        .where_clause
-        .clone()
-        .map(|r| r.to_token_stream())
-        .unwrap_or_default();
-    let fields = &input_struct.fields;
-
-    // Create a function that takes each field and clones it
-    let clone_fields: proc_macro2::TokenStream = fields
-        .iter()
-        .map(|field| {
-            let field_ident = field.ident.as_ref().unwrap();
-            quote! { #field_ident: self.#field_ident.clone(), }
-        })
-        .collect();
-
-    // Generate the generic parameters
-    let generics_token_stream = generate_generic_params(struct_generics, None);
-    let generics_token_stream_unique =
-        generate_generic_params(struct_generics, Some(&["Env", "B", "BE"]));
-    let generics_token_stream_unique_with_bounds =
-        generate_generic_params_with_bounds(struct_generics, Some(&["Env", "B", "BE"]));
-    let generic_token_stream_with_bounds =
-        generate_generic_params_with_bounds(struct_generics, None);
-    // Create the implementation
-    let generated = quote! {
-        #input_struct
-
-        pub struct #new_struct<Env: gadget_common::environments::GadgetEnvironment + 'static, #generics_token_stream_unique_with_bounds>
-            #where_bounds {
-            pub network: Option<<#struct_ident<#generics_token_stream> as gadget_common::config::NetworkAndProtocolSetup<Env>>::Network>,
-            pub protocol: Option<<#struct_ident<#generics_token_stream> as gadget_common::config::NetworkAndProtocolSetup<Env>>::Protocol>,
-            pub client: Option<Env::Client>,
-            pub params: #struct_ident<#generics_token_stream>,
-            pub tx_manager: Arc<dyn gadget_common::client::PalletSubmitter>,
-            pub logger: gadget_common::config::DebugLogger,
-            pub prometheus_config: gadget_common::prometheus::PrometheusConfig,
-        }
-
-        impl<Env: gadget_common::environments::GadgetEnvironment + 'static, #generics_token_stream_unique_with_bounds> gadget_common::config::ProtocolConfig<Env> for #new_struct <Env, #generics_token_stream_unique>
-            #where_bounds
-        {
-            type Network = <Self::ProtocolSpecificConfiguration as gadget_common::config::NetworkAndProtocolSetup<Env>>::Network;
-            type Protocol = <Self::ProtocolSpecificConfiguration as gadget_common::config::NetworkAndProtocolSetup<Env>>::Protocol;
-            type ProtocolSpecificConfiguration = #struct_ident <#generics_token_stream>;
-
-            fn new(network: Self::Network, client: <Env as gadget_common::environments::GadgetEnvironment>::Client, protocol: Self::Protocol, params: Self::ProtocolSpecificConfiguration, tx_manager: Arc<dyn gadget_common::client::PalletSubmitter>, logger: DebugLogger, prometheus_config: gadget_common::prometheus::PrometheusConfig) -> Self {
-                Self {
-                    network: Some(network),
-                    client: Some(client),
-                    protocol: Some(protocol),
-                    params,
-                    tx_manager,
-                    logger,
-                    prometheus_config,
-                }
-            }
-
-            fn take_network(&mut self) -> Self::Network {
-                self.network.take().expect("Network not set")
-            }
-
-            fn take_protocol(&mut self) -> Self::Protocol {
-                self.protocol.take().expect("Protocol not set")
-            }
-
-            fn take_client(&mut self) -> <Env as gadget_common::environments::GadgetEnvironment>::Client {
-                self.client.take().expect("Client not set")
-            }
-
-            fn prometheus_config(&self) -> gadget_common::config::PrometheusConfig {
-                self.prometheus_config.clone()
-            }
-
-            fn params(&self) -> &Self::ProtocolSpecificConfiguration {
-                &self.params
-            }
-        }
-
-        impl<Env: gadget_common::environments::GadgetEnvironment + 'static, #generics_token_stream_unique_with_bounds> #struct_ident <#generics_token_stream>
-            #where_bounds
-        {
-            pub fn setup(self) -> #new_struct <Env, #generics_token_stream_unique> {
-                let tx_manager = <Self as gadget_common::config::NetworkAndProtocolSetup<Env>>::tx_manager(&self);
-                let logger = <Self as gadget_common::config::NetworkAndProtocolSetup<Env>>::logger(&self);
-                let prometheus_config = self.prometheus_config.clone();
-
-                #new_struct {
-                    network: None,
-                    protocol: None,
-                    client: None,
-                    params: self,
-                    tx_manager,
-                    logger,
-                    prometheus_config,
-                }
-            }
-
-            pub async fn execute(self) -> Result<(), Error> {
-                use gadget_common::config::ProtocolConfig;
-                self.setup().build().await?.run().await
-            }
-        }
-
-        impl<#generic_token_stream_with_bounds> Clone for #struct_ident <#generics_token_stream> #where_bounds {
-            fn clone(&self) -> Self {
-                Self {
-                    #clone_fields
-                }
-            }
-        }
-    };
-
-    generated.into()
-}
diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml
index 9eebc9deb..0af1846fd 100644
--- a/sdk/Cargo.toml
+++ b/sdk/Cargo.toml
@@ -10,10 +10,14 @@ license.workspace = true
 
 
 [dependencies]
+auto_impl = { workspace = true }
 elliptic-curve = { workspace = true, features = ["alloc", "sec1"] }
 getrandom = { workspace = true }
 hex = { workspace = true, features = ["alloc"] }
+http-body-util = { workspace = true }
+lazy_static = { workspace = true }
 lock_api = { workspace = true }
+sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"], optional = true }
 parking_lot = { workspace = true, optional = true }
 rand = { version = "0.8", default-features = false, features = ["alloc"] }
 thiserror = { workspace = true }
@@ -26,6 +30,7 @@ w3f-bls = { workspace = true }
 
 # Metrics deps
 hyper = { workspace = true, features = ["http1", "server"], optional = true }
+hyper-util = { workspace = true, features = ["server"], optional = true }
 prometheus = { workspace = true }
 tokio = { workspace = true, features = ["rt-multi-thread", "parking_lot"], optional = true }
 
@@ -37,17 +42,15 @@ tracing = { workspace = true }
 async-trait = { workspace = true }
 bincode = { workspace = true }
 futures = { workspace = true }
-gadget-core = { workspace = true, features = ["substrate"] }
-gadget-common = { workspace = true, features = ["substrate"] }
-gadget-io = { workspace = true }
-serde = { workspace = true }
+gadget-io = { workspace = true, features = ["std"] }
+round-based = { workspace = true, features = ["derive"] }
+serde = { workspace = true, features = ["derive"] }
 serde_json = { workspace = true }
 
 # Substrate
 sp-core = { workspace = true, features = ["full_crypto"] }
 sp-io = { workspace = true }
 
-
 # Event Watchers and Handlers
 backoff = { workspace = true, optional = true }
 subxt = { workspace = true, optional = true }
@@ -107,16 +110,17 @@ std = [
     "dep:backoff",
     "dep:parking_lot",
     "dep:hyper",
+    "dep:hyper-util",
     "dep:subxt",
     "dep:tokio",
     "backoff/tokio",
     "getrandom",
-    "gadget-common/std",
     "gadget-io/std",
     "hex/std",
     "rand/std",
     "rand/std_rng",
     "sp-io/std",
+    "sqlx",
     "subxt/native",
     "tangle-subxt/std",
 ]
diff --git a/sdk/src/clients/mod.rs b/sdk/src/clients/mod.rs
new file mode 100644
index 000000000..0059f0362
--- /dev/null
+++ b/sdk/src/clients/mod.rs
@@ -0,0 +1,12 @@
+use alloc::boxed::Box;
+use async_trait::async_trait;
+use auto_impl::auto_impl;
+
+pub mod tangle;
+
+#[async_trait]
+#[auto_impl(Arc)]
+pub trait Client<Event>: Clone + Send + Sync {
+    async fn next_event(&self) -> Option<Event>;
+    async fn latest_event(&self) -> Option<Event>;
+}
diff --git a/sdk/src/clients/tangle/mod.rs b/sdk/src/clients/tangle/mod.rs
new file mode 100644
index 000000000..637153f3d
--- /dev/null
+++ b/sdk/src/clients/tangle/mod.rs
@@ -0,0 +1,2 @@
+pub mod runtime;
+pub mod services;
diff --git a/environments/tangle/src/runtime.rs b/sdk/src/clients/tangle/runtime.rs
similarity index 66%
rename from environments/tangle/src/runtime.rs
rename to sdk/src/clients/tangle/runtime.rs
index bdbb29f6b..b8cffe404 100644
--- a/environments/tangle/src/runtime.rs
+++ b/sdk/src/clients/tangle/runtime.rs
@@ -1,57 +1,50 @@
 use std::sync::Arc;
 use std::time::Duration;
 
-use crate::gadget::TangleEvent;
-use gadget_common::locks::TokioMutexExt;
-use gadget_common::tangle_subxt::subxt::blocks::{Block, BlockRef};
-use gadget_common::tangle_subxt::subxt::{self, SubstrateConfig};
-use gadget_common::{async_trait, tangle_runtime::*};
-use gadget_core::gadget::general::Client;
+use crate::clients::Client;
+use crate::error::Error;
+use crate::mutex_ext::TokioMutexExt;
+use subxt::blocks::{Block, BlockRef};
+use subxt::events::Events;
+use subxt::utils::AccountId32;
+use subxt::{self, SubstrateConfig};
 
 pub type TangleConfig = SubstrateConfig;
 pub type TangleClient = subxt::OnlineClient<TangleConfig>;
 pub type TangleBlock = Block<TangleConfig, TangleClient>;
 pub type TangleBlockStream = subxt::backend::StreamOfResults<TangleBlock>;
 
-pub mod crypto {
-    use sp_application_crypto::{app_crypto, ecdsa, sr25519};
-    pub mod acco {
-        use super::*;
-        pub use sp_core::crypto::key_types::ACCOUNT as KEY_TYPE;
-        app_crypto!(sr25519, KEY_TYPE);
-    }
-
-    pub mod role {
-        use super::*;
-        /// Key type for ROLE keys
-        pub const KEY_TYPE: sp_application_crypto::KeyTypeId =
-            sp_application_crypto::KeyTypeId(*b"role");
-
-        app_crypto!(ecdsa, KEY_TYPE);
-    }
+#[derive(Clone, Debug)]
+pub struct TangleEvent {
+    /// Finalized block number.
+    pub number: u64,
+    /// Finalized block header hash.
+    pub hash: [u8; 32],
+    /// Events
+    pub events: Events<TangleConfig>,
 }
 
-#[derive(Clone)]
-pub struct TangleRuntime {
+#[derive(Clone, Debug)]
+pub struct TangleRuntimeClient {
     client: subxt::OnlineClient<SubstrateConfig>,
-    finality_notification_stream:
-        Arc<gadget_common::gadget_io::tokio::sync::Mutex<Option<TangleBlockStream>>>,
-    latest_finality_notification:
-        Arc<gadget_common::gadget_io::tokio::sync::Mutex<Option<TangleEvent>>>,
+    finality_notification_stream: Arc<gadget_io::tokio::sync::Mutex<Option<TangleBlockStream>>>,
+    latest_finality_notification: Arc<gadget_io::tokio::sync::Mutex<Option<TangleEvent>>>,
     account_id: AccountId32,
 }
 
-impl TangleRuntime {
+impl TangleRuntimeClient {
+    /// Create a new Tangle runtime client from a RPC url.
+    pub async fn from_url(url: &str, account_id: AccountId32) -> Result<Self, Error> {
+        let client = subxt::OnlineClient::<SubstrateConfig>::from_url(url).await?;
+        Ok(Self::new(client, account_id))
+    }
+
     /// Create a new TangleRuntime instance.
     pub fn new(client: subxt::OnlineClient<SubstrateConfig>, account_id: AccountId32) -> Self {
         Self {
             client,
-            finality_notification_stream: Arc::new(
-                gadget_common::gadget_io::tokio::sync::Mutex::new(None),
-            ),
-            latest_finality_notification: Arc::new(
-                gadget_common::gadget_io::tokio::sync::Mutex::new(None),
-            ),
+            finality_notification_stream: Arc::new(gadget_io::tokio::sync::Mutex::new(None)),
+            latest_finality_notification: Arc::new(gadget_io::tokio::sync::Mutex::new(None)),
             account_id,
         }
     }
@@ -62,7 +55,7 @@ impl TangleRuntime {
 
     /// Initialize the TangleRuntime instance by listening for finality notifications.
     /// This method must be called before using the instance.
-    async fn initialize(&self) -> Result<(), gadget_common::Error> {
+    async fn initialize(&self) -> Result<(), Error> {
         let finality_notification_stream = self.client.blocks().subscribe_finalized().await?;
         *self.finality_notification_stream.lock().await = Some(finality_notification_stream);
         Ok(())
@@ -82,7 +75,7 @@ impl TangleRuntime {
 }
 
 #[async_trait::async_trait]
-impl Client<TangleEvent> for TangleRuntime {
+impl Client<TangleEvent> for TangleRuntimeClient {
     async fn next_event(&self) -> Option<TangleEvent> {
         let mut lock = self
             .finality_notification_stream
diff --git a/environments/tangle/src/api.rs b/sdk/src/clients/tangle/services.rs
similarity index 55%
rename from environments/tangle/src/api.rs
rename to sdk/src/clients/tangle/services.rs
index 68f009f7f..a69579b67 100644
--- a/environments/tangle/src/api.rs
+++ b/sdk/src/clients/tangle/services.rs
@@ -1,24 +1,23 @@
-use gadget_common::config::DebugLogger;
-use gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services;
-use gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
-use gadget_common::tangle_runtime::AccountId32;
-use gadget_common::tangle_subxt::subxt::backend::BlockRef;
-use gadget_common::tangle_subxt::subxt::utils::H256;
-use gadget_common::tangle_subxt::subxt::{Config, OnlineClient};
-use gadget_common::tangle_subxt::tangle_testnet_runtime::api;
-
-pub type BlockHash = [u8; 32];
+use crate::error::Error;
+use crate::logger::Logger;
+use subxt::utils::AccountId32;
+use tangle_subxt::subxt::backend::BlockRef;
+use tangle_subxt::subxt::utils::H256;
+use tangle_subxt::subxt::{Config, OnlineClient};
+use tangle_subxt::tangle_testnet_runtime::api;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
 
 /// A client for interacting with the services API
-/// TODO: Make this the object that is used for getting services and submitting transactions as necessary.
+#[derive(Debug)]
 pub struct ServicesClient<C: Config> {
     rpc_client: OnlineClient<C>,
     #[allow(dead_code)]
-    logger: DebugLogger,
+    logger: Logger,
 }
 
 impl<C: Config> ServicesClient<C> {
-    pub fn new(logger: DebugLogger, rpc_client: OnlineClient<C>) -> Self {
+    pub fn new(logger: Logger, rpc_client: OnlineClient<C>) -> Self {
         Self { logger, rpc_client }
     }
 
@@ -37,7 +36,7 @@ where
         &self,
         at: [u8; 32],
         blueprint_id: u64,
-    ) -> Result<Option<ServiceBlueprint>, gadget_common::Error> {
+    ) -> Result<Option<ServiceBlueprint>, Error> {
         let call = api::storage().services().blueprints(blueprint_id);
         let at = BlockRef::from_hash(H256::from_slice(&at));
         let ret: Option<ServiceBlueprint> = self
@@ -46,7 +45,7 @@ where
             .at(at)
             .fetch(&call)
             .await
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })?
+            .map_err(|e| Error::Client(e.to_string()))?
             .map(|r| r.1);
 
         Ok(ret)
@@ -56,7 +55,7 @@ where
         &self,
         at: [u8; 32],
         address: AccountId32,
-    ) -> Result<Vec<RpcServicesWithBlueprint>, gadget_common::Error> {
+    ) -> Result<Vec<RpcServicesWithBlueprint>, Error> {
         let call = api::apis()
             .services_api()
             .query_services_with_blueprints_by_operator(address);
@@ -67,10 +66,8 @@ where
             .at(at)
             .call(call)
             .await
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })?
-            .map_err(|err| gadget_common::Error::ClientError {
-                err: format!("{err:?}"),
-            })?;
+            .map_err(|e| Error::Client(e.to_string()))?
+            .map_err(|e| Error::Client(format!("{e:?}")))?;
 
         Ok(ret)
     }
diff --git a/sdk/src/env.rs b/sdk/src/config.rs
similarity index 98%
rename from sdk/src/env.rs
rename to sdk/src/config.rs
index 4c93dfba3..267d82f1c 100644
--- a/sdk/src/env.rs
+++ b/sdk/src/config.rs
@@ -248,10 +248,10 @@ impl<RwLock: lock_api::RawRwLock> GadgetConfiguration<RwLock> {
         let keystore = self.keystore()?;
         let sr25519_pubkey = crate::keystore::Backend::iter_sr25519(&keystore)
             .next()
-            .ok_or_else(|| Error::NoSr25519Keypair)?;
+            .ok_or(Error::NoSr25519Keypair)?;
         let sr25519_secret =
             crate::keystore::Backend::expose_sr25519_secret(&keystore, &sr25519_pubkey)?
-                .ok_or_else(|| Error::NoSr25519Keypair)?;
+                .ok_or(Error::NoSr25519Keypair)?;
 
         let mut seed = [0u8; 32];
         seed.copy_from_slice(&sr25519_secret.to_bytes()[0..32]);
@@ -270,9 +270,9 @@ impl<RwLock: lock_api::RawRwLock> GadgetConfiguration<RwLock> {
         let keystore = self.keystore()?;
         let ecdsa_pubkey = crate::keystore::Backend::iter_ecdsa(&keystore)
             .next()
-            .ok_or_else(|| Error::NoEcdsaKeypair)?;
+            .ok_or(Error::NoEcdsaKeypair)?;
         let ecdsa_secret = crate::keystore::Backend::expose_ecdsa_secret(&keystore, &ecdsa_pubkey)?
-            .ok_or_else(|| Error::NoEcdsaKeypair)?;
+            .ok_or(Error::NoEcdsaKeypair)?;
 
         let mut seed = [0u8; 32];
         seed.copy_from_slice(&ecdsa_secret.to_bytes()[0..32]);
diff --git a/core/src/job/protocol.rs b/sdk/src/context.rs
similarity index 56%
rename from core/src/job/protocol.rs
rename to sdk/src/context.rs
index d79619582..e765b01ab 100644
--- a/core/src/job/protocol.rs
+++ b/sdk/src/context.rs
@@ -1,9 +1,15 @@
+use alloc::boxed::Box;
+use alloc::fmt::{Debug, Display};
 use alloc::string::String;
 use alloc::vec::Vec;
-use core::fmt::{Debug, Display};
+use async_trait::async_trait;
+use core::error::Error;
 use core::hash::Hash;
 use core::ops::{Add, Sub};
+use gadget_core::gadget::general::Client;
+use gadget_core::job::protocol::{ProtocolMessageMetadata, WorkManagerInterface};
 use serde::{Deserialize, Serialize};
+use sp_core::ecdsa;
 
 #[derive(Copy, Clone, PartialEq, Eq)]
 pub enum PollMethod {
@@ -88,24 +94,52 @@ pub trait ProtocolMessageMetadata<WM: WorkManagerInterface> {
     fn payload_mut(&mut self) -> &mut Vec<u8>;
 }
 
-/// The [`ProtocolRemote`] is the interface between the [`ProtocolWorkManager`] and the async protocol.
-/// It *must* be unique between each async protocol.
-pub trait ProtocolRemote<WM: WorkManagerInterface>: Send + Sync + 'static {
-    fn start(&self) -> Result<(), WM::Error>;
-    fn session_id(&self) -> WM::SessionID;
-    fn started_at(&self) -> WM::Clock;
-    fn shutdown(&self, reason: ShutdownReason) -> Result<(), WM::Error>;
-    fn is_done(&self) -> bool;
-    fn deliver_message(&self, message: WM::ProtocolMessage) -> Result<(), WM::Error>;
-    fn has_started(&self) -> bool;
-    fn retry_id(&self) -> WM::RetryID;
+#[async_trait]
+pub trait GadgetContext: Debug + Sized + 'static
+where
+    Self::WorkManager: WorkManagerInterface<
+        Clock = Self::Clock,
+        RetryID = Self::RetryID,
+        TaskID = Self::TaskID,
+        SessionID = Self::SessionID,
+        Error = Self::Error,
+        ProtocolMessage = Self::ProtocolMessage,
+    >,
+{
+    type ProtocolMessage: Serialize
+        + for<'de> Deserialize<'de>
+        + Send
+        + Sync
+        + 'static
+        + ProtocolMessageMetadata<Self::WorkManager>;
+    type Client: Client<<Self as GadgetContext>::Event> + Send + Sync + 'static;
+    type WorkManager: WorkManagerInterface;
+    type Error: Error + Send + Sync + From<String> + Into<crate::Error> + 'static;
+    type Clock: Display + Copy + Send + Sync + 'static;
+    type RetryID: Display + Copy + Send + Sync + 'static;
+    type TaskID: Debug + Copy + Send + Sync + 'static;
+    type SessionID: Display + Copy + Send + Sync + 'static;
+    type TransactionManager: Clone + Send + Sync + 'static;
+    type JobInitMetadata: Send + Sync + 'static;
 
-    fn has_stalled(&self, now: WM::Clock) -> bool {
-        now >= self.started_at() + WM::acceptable_block_tolerance()
-    }
+    #[allow(clippy::too_many_arguments)]
+    fn build_protocol_message<Payload: Serialize>(
+        associated_block_id: <Self::WorkManager as WorkManagerInterface>::Clock,
+        associated_session_id: <Self::WorkManager as WorkManagerInterface>::SessionID,
+        associated_retry_id: <Self::WorkManager as WorkManagerInterface>::RetryID,
+        associated_task_id: <Self::WorkManager as WorkManagerInterface>::TaskID,
+        from: u16,
+        to: Option<u16>,
+        payload: &Payload,
+        from_account_id: Option<ecdsa::Public>,
+        to_network_id: Option<ecdsa::Public>,
+    ) -> Self::ProtocolMessage;
+
+    async fn setup_runtime(&self) -> Result<Self::Client, Self::Error>;
+
+    fn transaction_manager(&self) -> Self::TransactionManager;
 
-    fn is_active(&self) -> bool {
-        // If the protocol has started, is not done, and has not stalled, then it is active
-        self.has_started() && !self.is_done() && !self.has_started()
+    fn set_payload(&mut self, input: Vec<u8>, output: &mut Vec<u8>) {
+        *output = input;
     }
 }
diff --git a/sdk/src/error.rs b/sdk/src/error.rs
new file mode 100644
index 000000000..4dfe0ed9f
--- /dev/null
+++ b/sdk/src/error.rs
@@ -0,0 +1,58 @@
+use alloc::string::String;
+use sp_core::ecdsa;
+use thiserror::Error;
+
+/// Represents errors that can occur in the SDK.
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("Client error: {0}")]
+    Client(String),
+
+    #[error("Job error: {reason}")]
+    Job { reason: String },
+
+    #[error("Network error: {reason}")]
+    Network { reason: String },
+
+    #[error("Storage error: {reason}")]
+    Store { reason: String },
+
+    #[error("Keystore error: {0}")]
+    Keystore(#[from] crate::keystore::error::Error),
+
+    #[error("Missing network ID")]
+    MissingNetworkId,
+
+    #[error("Peer not found: {id:?}")]
+    PeerNotFound { id: ecdsa::Public },
+
+    #[cfg(feature = "std")]
+    #[error("Join error: {0}")]
+    Join(#[from] tokio::task::JoinError),
+
+    // TODO: Add feature flag for substrate/tangle
+    #[error("Subxt error: {0}")]
+    #[cfg(any(feature = "std", feature = "wasm"))]
+    Subxt(#[from] subxt::Error),
+
+    #[cfg(feature = "std")]
+    #[error("Events watcher error: {0}")]
+    EventsWatcher(#[from] crate::events_watcher::error::Error),
+
+    #[cfg(feature = "std")]
+    #[error("Prometheus error: {err}")]
+    Prometheus { err: String },
+
+    #[cfg(feature = "std")]
+    #[error("Metrics error: {0}")]
+    Metrics(#[from] crate::metrics::Error),
+
+    #[error("Other error: {0}")]
+    Other(String),
+}
+
+impl From<String> for Error {
+    fn from(s: String) -> Self {
+        Error::Other(s)
+    }
+}
diff --git a/sdk/src/events_watcher/error.rs b/sdk/src/events_watcher/error.rs
index ea63ae02f..791d08803 100644
--- a/sdk/src/events_watcher/error.rs
+++ b/sdk/src/events_watcher/error.rs
@@ -1,3 +1,5 @@
+use alloc::boxed::Box;
+
 /// An error type for the event watcher.
 #[derive(Debug, thiserror::Error)]
 #[non_exhaustive]
@@ -8,6 +10,7 @@ pub enum Error {
     Subxt(#[from] subxt::Error),
     /// An error occurred in the backoff mechanism.
     #[error(transparent)]
+    #[cfg(any(feature = "std", feature = "wasm"))]
     Backoff(#[from] backoff::Error<subxt::Error>),
     /// An error occurred in Alloy transport.
     // TODO: Add feature flag for EVM/eigenlayer/etc.
@@ -21,10 +24,10 @@ pub enum Error {
     /// TODO: Add feature flag for EVM/eigenlayer/etc.
     #[error(transparent)]
     SolTypes(#[from] alloy_sol_types::Error),
-    /// An error occurred in the event watcher and we need to restart it.
+    /// An error occurred in the event watcher, and we need to restart it.
     #[error("An error occurred in the event watcher and we need to restart it.")]
     ForceRestart,
     /// An error occurred in the event handler.
     #[error(transparent)]
-    Handler(#[from] Box<dyn std::error::Error + Send + Sync>),
+    Handler(#[from] Box<dyn core::error::Error + Send + Sync>),
 }
diff --git a/sdk/src/events_watcher/mod.rs b/sdk/src/events_watcher/mod.rs
index 41fde151d..4a3a60bce 100644
--- a/sdk/src/events_watcher/mod.rs
+++ b/sdk/src/events_watcher/mod.rs
@@ -43,6 +43,7 @@ impl ConstantWithMaxRetryCount {
     }
 }
 
+#[cfg(any(feature = "std", feature = "wasm"))]
 impl backoff::backoff::Backoff for ConstantWithMaxRetryCount {
     fn next_backoff(&mut self) -> Option<Duration> {
         (self.count < self.max_retry_count).then(|| {
diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs
index 07400f935..4af4b4bb1 100644
--- a/sdk/src/lib.rs
+++ b/sdk/src/lib.rs
@@ -11,45 +11,50 @@
 
 extern crate alloc;
 
-/// Keystore Module
-pub mod keystore;
-
-/// Metrics Module
-// pub mod metrics;
-
-/// Randomness generation module
-pub mod random;
-
+/// Benchmark Module
+#[cfg(feature = "std")]
+pub mod benchmark;
+/// Blockchain clients
+#[cfg(any(feature = "std", feature = "wasm"))]
+pub mod clients;
+/// Gadget configuration
+pub mod config;
+mod error;
 /// Blockchain Events Watcher Module
 #[cfg(any(feature = "std", feature = "wasm"))]
 pub mod events_watcher;
-
-/// Gadget Environment Module
-pub mod env;
-
-/// Local database storage
+/// Keystore Module
+pub mod keystore;
+/// Debug logger
+pub mod logger;
+/// Metrics Module
 #[cfg(feature = "std")]
-pub mod store;
-
-/// Transaction Management Module
+pub mod metrics;
 #[cfg(any(feature = "std", feature = "wasm"))]
-pub mod tx;
-
-pub use tangle_subxt;
-
+pub mod mutex_ext;
 /// Network Module
 #[cfg(feature = "std")]
 pub mod network;
-
-pub mod slashing;
-
-/// Benchmark Module
+/// Prometheus metrics configuration
 #[cfg(feature = "std")]
-pub mod benchmark;
-
-// TODO: Currently uses `StdGadgetConfiguration`
+pub mod prometheus;
+/// Randomness generation module
+pub mod random;
 /// Gadget Runner Module
 #[cfg(feature = "std")]
 pub mod run;
+/// Slashing and quality of service utilities
+pub mod slashing;
+/// Database storage
+#[cfg(feature = "std")]
+pub mod store;
+/// Protocol execution tracer
+#[cfg(any(feature = "std", feature = "wasm"))]
+pub mod tracer;
+/// Transaction Management Module
+#[cfg(any(feature = "std", feature = "wasm"))]
+pub mod tx;
 
+/// Re-exports
 pub use gadget_blueprint_proc_macro::*;
+pub use tangle_subxt;
diff --git a/sdk/src/logger.rs b/sdk/src/logger.rs
new file mode 100644
index 000000000..bc8e6ec29
--- /dev/null
+++ b/sdk/src/logger.rs
@@ -0,0 +1,51 @@
+#[cfg(not(feature = "std"))]
+use alloc::string::String;
+use core::fmt::Display;
+
+#[derive(Clone, Debug, Default)]
+pub struct Logger {
+    pub target: &'static str,
+    pub id: String,
+}
+
+impl Logger {
+    pub fn trace<T: Display>(&self, msg: T) {
+        if self.id.is_empty() {
+            log::trace!(target: self.target, "{msg}");
+            return;
+        }
+        log::trace!(target: self.target, "[{}] {msg}", &self.id);
+    }
+
+    pub fn debug<T: Display>(&self, msg: T) {
+        if self.id.is_empty() {
+            log::debug!(target: self.target, "{msg}");
+            return;
+        }
+        log::debug!(target: self.target, "[{}] {msg}", &self.id);
+    }
+
+    pub fn info<T: Display>(&self, msg: T) {
+        if self.id.is_empty() {
+            log::info!(target: self.target, "{msg}");
+            return;
+        }
+        log::info!(target: self.target, "[{}] {msg}", &self.id);
+    }
+
+    pub fn warn<T: Display>(&self, msg: T) {
+        if self.id.is_empty() {
+            log::warn!(target: self.target, "{msg}");
+            return;
+        }
+        log::warn!(target: self.target, "[{}] {msg}", &self.id);
+    }
+
+    pub fn error<T: Display>(&self, msg: T) {
+        if self.id.is_empty() {
+            log::error!(target: self.target, "{msg}");
+            return;
+        }
+        log::error!(target: self.target, "[{}] {msg}", &self.id);
+    }
+}
diff --git a/sdk/src/metrics/mod.rs b/sdk/src/metrics/mod.rs
index 8c1fdaccc..ddc43ee1b 100644
--- a/sdk/src/metrics/mod.rs
+++ b/sdk/src/metrics/mod.rs
@@ -1,10 +1,178 @@
-#[cfg(not(target_family = "wasm"))]
-mod standard;
-#[cfg(target_family = "wasm")]
-mod wasm;
+// This file is part of Substrate.
 
-#[cfg(not(target_family = "wasm"))]
-pub use standard::Metrics;
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
 
-#[cfg(target_family = "wasm")]
-pub use wasm::Metrics;
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod sourced;
+
+use hyper::{http::StatusCode, Request, Response};
+use prometheus::{core::Collector, Encoder, TextEncoder};
+use std::net::SocketAddr;
+
+pub use prometheus::{
+    self,
+    core::{
+        AtomicF64 as F64, AtomicI64 as I64, AtomicU64 as U64, GenericCounter as Counter,
+        GenericCounterVec as CounterVec, GenericGauge as Gauge, GenericGaugeVec as GaugeVec,
+    },
+    exponential_buckets, Error as PrometheusError, Histogram, HistogramOpts, HistogramVec, Opts,
+    Registry,
+};
+pub use sourced::{MetricSource, SourcedCounter, SourcedGauge, SourcedMetric};
+
+type Body = http_body_util::Full<hyper::body::Bytes>;
+
+pub fn register<T: Clone + Collector + 'static>(
+    metric: T,
+    registry: &Registry,
+) -> Result<T, PrometheusError> {
+    registry.register(Box::new(metric.clone()))?;
+    Ok(metric)
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    /// Hyper internal error.
+    #[error(transparent)]
+    Hyper(#[from] hyper::Error),
+
+    /// Http request error.
+    #[error(transparent)]
+    Http(#[from] hyper::http::Error),
+
+    /// i/o error.
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+
+    #[error("Prometheus port {0} already in use.")]
+    PortInUse(SocketAddr),
+}
+
+async fn request_metrics(
+    req: Request<hyper::body::Incoming>,
+    registry: Registry,
+) -> Result<Response<Body>, Error> {
+    if req.uri().path() == "/metrics" {
+        let metric_families = registry.gather();
+        let mut buffer = vec![];
+        let encoder = TextEncoder::new();
+        encoder.encode(&metric_families, &mut buffer).unwrap();
+
+        Response::builder()
+            .status(StatusCode::OK)
+            .header("Content-Type", encoder.format_type())
+            .body(Body::from(buffer))
+            .map_err(Error::Http)
+    } else {
+        Response::builder()
+            .status(StatusCode::NOT_FOUND)
+            .body(Body::from("Not found."))
+            .map_err(Error::Http)
+    }
+}
+
+/// Initializes the metrics context, and starts an HTTP server
+/// to serve metrics.
+pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> {
+    let listener = tokio::net::TcpListener::bind(&prometheus_addr)
+        .await
+        .map_err(|_| Error::PortInUse(prometheus_addr))?;
+
+    init_prometheus_with_listener(listener, registry).await
+}
+
+/// Init prometheus using the given listener.
+async fn init_prometheus_with_listener(
+    listener: tokio::net::TcpListener,
+    registry: Registry,
+) -> Result<(), Error> {
+    log::info!(target: "prometheus", "〽️ Prometheus exporter started at {}", listener.local_addr()?);
+
+    let server = hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
+
+    loop {
+        let io = match listener.accept().await {
+            Ok((sock, _)) => hyper_util::rt::TokioIo::new(sock),
+            Err(e) => {
+                log::debug!(target: "prometheus", "Error accepting connection: {:?}", e);
+                continue;
+            }
+        };
+
+        let registry = registry.clone();
+
+        let conn = server
+            .serve_connection_with_upgrades(
+                io,
+                hyper::service::service_fn(move |req| request_metrics(req, registry.clone())),
+            )
+            .into_owned();
+
+        #[allow(clippy::let_underscore_future)]
+        let _ = tokio::spawn(async move {
+            if let Err(err) = conn.await {
+                log::debug!(target: "prometheus", "connection error: {:?}", err);
+            }
+        });
+    }
+}
+
+#[cfg(test)]
+#[allow(clippy::let_underscore_future)]
+mod tests {
+    use super::*;
+    use http_body_util::BodyExt;
+    use hyper::Uri;
+    use hyper_util::{client::legacy::Client, rt::TokioExecutor};
+
+    const METRIC_NAME: &str = "test_test_metric_name_test_test";
+
+    #[tokio::test]
+    async fn prometheus_works() {
+        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
+            .await
+            .expect("Creates listener");
+
+        let local_addr = listener.local_addr().expect("Returns the local addr");
+
+        let registry = Registry::default();
+        let _ = register(
+            prometheus::Counter::new(METRIC_NAME, "yeah").expect("Creates test counter"),
+            &registry,
+        )
+        .expect("Registers the test metric");
+
+        let _ = tokio::spawn(init_prometheus_with_listener(listener, registry));
+
+        let client = Client::builder(TokioExecutor::new()).build_http::<Body>();
+
+        let res = client
+            .get(Uri::try_from(&format!("http://{}/metrics", local_addr)).expect("Parses URI"))
+            .await
+            .expect("Requests metrics");
+
+        assert!(res.status().is_success());
+
+        let buf = res
+            .into_body()
+            .collect()
+            .await
+            .expect("Failed to read HTTP body")
+            .to_bytes();
+        let body = String::from_utf8(buf.to_vec()).expect("Converts body to String");
+
+        assert!(body.contains(&format!("{} 0", METRIC_NAME)));
+    }
+}
diff --git a/sdk/src/metrics/standard/sourced.rs b/sdk/src/metrics/sourced.rs
similarity index 96%
rename from sdk/src/metrics/standard/sourced.rs
rename to sdk/src/metrics/sourced.rs
index d10ac2926..48d9ceba9 100644
--- a/sdk/src/metrics/standard/sourced.rs
+++ b/sdk/src/metrics/sourced.rs
@@ -34,11 +34,11 @@ pub type SourcedCounter<S> = SourcedMetric<Counter, S>;
 pub type SourcedGauge<S> = SourcedMetric<Gauge, S>;
 
 /// The type of a sourced counter.
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug)]
 pub enum Counter {}
 
 /// The type of a sourced gauge.
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug)]
 pub enum Gauge {}
 
 /// A metric whose values are obtained from an existing source,
@@ -103,13 +103,13 @@ impl<T: SourcedType, S: MetricSource> Collector for SourcedMetric<T, S> {
                     log::warn!(
                         "Missing label values for sourced metric {}",
                         self.desc.fq_name
-                    );
+                    )
                 }
                 Ordering::Less => {
                     log::warn!(
                         "Too many label values for sourced metric {}",
                         self.desc.fq_name
-                    );
+                    )
                 }
                 Ordering::Equal => {}
             }
@@ -122,7 +122,7 @@ impl<T: SourcedType, S: MetricSource> Collector for SourcedMetric<T, S> {
                     .map(|(l_name, l_value)| {
                         let mut l = proto::LabelPair::default();
                         l.set_name(l_name.to_string());
-                        l.set_value((*l_value).to_string());
+                        l.set_value(l_value.to_string());
                         l
                     })
                     .chain(self.desc.const_label_pairs.iter().cloned())
diff --git a/sdk/src/metrics/standard/metrics.rs b/sdk/src/metrics/standard/metrics.rs
deleted file mode 100644
index 1fc2fb358..000000000
--- a/sdk/src/metrics/standard/metrics.rs
+++ /dev/null
@@ -1,169 +0,0 @@
-// This file is part of Substrate.
-
-// Copyright (C) Parity Technologies (UK) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 	http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use hyper::{
-    http::StatusCode,
-    server::Server,
-    service::{make_service_fn, service_fn},
-    Body, Request, Response,
-};
-pub use prometheus::{
-    self,
-    core::{AtomicU64 as U64, GenericCounter as Counter},
-    Error as PrometheusError, Registry,
-};
-use prometheus::{core::Collector, Encoder, TextEncoder};
-use std::net::SocketAddr;
-
-#[allow(unused)]
-pub use super::sourced::{MetricSource, SourcedCounter, SourcedGauge, SourcedMetric};
-
-pub fn register<T: Clone + Collector + 'static>(
-    metric: T,
-    registry: &Registry,
-) -> Result<T, PrometheusError> {
-    registry.register(Box::new(metric.clone()))?;
-    Ok(metric)
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum Error {
-    /// Hyper internal error.
-    #[error(transparent)]
-    Hyper(#[from] hyper::Error),
-
-    /// Http request error.
-    #[error(transparent)]
-    Http(#[from] hyper::http::Error),
-
-    /// i/o error.
-    #[error(transparent)]
-    Io(#[from] std::io::Error),
-
-    #[error("Prometheus port {0} already in use.")]
-    PortInUse(SocketAddr),
-}
-
-async fn request_metrics(req: Request<Body>, registry: Registry) -> Result<Response<Body>, Error> {
-    if req.uri().path() == "/metrics" {
-        let metric_families = registry.gather();
-        let mut buffer = vec![];
-        let encoder = TextEncoder::new();
-        encoder.encode(&metric_families, &mut buffer).unwrap();
-
-        Response::builder()
-            .status(StatusCode::OK)
-            .header("Content-Type", encoder.format_type())
-            .body(Body::from(buffer))
-            .map_err(Error::Http)
-    } else {
-        Response::builder()
-            .status(StatusCode::NOT_FOUND)
-            .body(Body::from("Not found."))
-            .map_err(Error::Http)
-    }
-}
-
-/// Initializes the metrics context, and starts an HTTP server
-/// to serve metrics.
-pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> {
-    let listener = tokio::net::TcpListener::bind(&prometheus_addr)
-        .await
-        .map_err(|_| Error::PortInUse(prometheus_addr))?;
-
-    init_prometheus_with_listener(listener, registry).await
-}
-
-/// Init prometheus using the given listener.
-async fn init_prometheus_with_listener(
-    listener: tokio::net::TcpListener,
-    registry: Registry,
-) -> Result<(), Error> {
-    let listener = hyper::server::conn::AddrIncoming::from_listener(listener)?;
-    log::info!(
-        "〽️ Prometheus exporter started at {}",
-        listener.local_addr()
-    );
-
-    let service = make_service_fn(move |_| {
-        let registry = registry.clone();
-
-        async move {
-            Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| {
-                request_metrics(req, registry.clone())
-            }))
-        }
-    });
-
-    let (signal, on_exit) = tokio::sync::oneshot::channel::<()>();
-    let server = Server::builder(listener)
-        .serve(service)
-        .with_graceful_shutdown(async {
-            let _ = on_exit.await;
-        });
-
-    let result = server.await.map_err(Into::into);
-
-    // Gracefully shutdown server, otherwise the server does not stop if it has open connections
-    let _ = signal.send(());
-
-    result
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use hyper::{Client, Uri};
-
-    #[test]
-    fn prometheus_works() {
-        const METRIC_NAME: &str = "test_test_metric_name_test_test";
-
-        let runtime = tokio::runtime::Runtime::new().expect("Creates the runtime");
-
-        let listener = runtime
-            .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
-            .expect("Creates listener");
-
-        let local_addr = listener.local_addr().expect("Returns the local addr");
-
-        let registry = Registry::default();
-        let _ = register(
-            prometheus::Counter::new(METRIC_NAME, "yeah").expect("Creates test counter"),
-            &registry,
-        )
-        .expect("Registers the test metric");
-
-        let _ = runtime.spawn(init_prometheus_with_listener(listener, registry));
-
-        runtime.block_on(async {
-            let client = Client::new();
-
-            let res = client
-                .get(Uri::try_from(&format!("http://{local_addr}/metrics")).expect("Parses URI"))
-                .await
-                .expect("Requests metrics");
-
-            let buf = hyper::body::to_bytes(res)
-                .await
-                .expect("Converts body to bytes");
-
-            let body = String::from_utf8(buf.to_vec()).expect("Converts body to String");
-            assert!(body.contains(&format!("{METRIC_NAME} 0")));
-        });
-    }
-}
diff --git a/sdk/src/metrics/standard/mod.rs b/sdk/src/metrics/standard/mod.rs
deleted file mode 100644
index 368fb7c90..000000000
--- a/sdk/src/metrics/standard/mod.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-#![allow(dead_code)]
-
-pub mod metrics;
-pub mod sourced;
-
-pub use metrics::*;
-
-/// Standard metrics for the gadget gossip service.
-///
-/// This module provides a set of standard metrics that are used to monitor
-/// the behavior of the gadget gossip service. It includes counters for both
-/// registered and expired messages, which can be used to track message
-/// throughput and lifecycle within the service.
-#[derive(Debug)]
-pub struct Metrics {
-    registered_messages: Counter<U64>,
-    expired_messages: Counter<U64>,
-}
-
-impl Metrics {
-    fn register(registry: &Registry) -> Result<Self, PrometheusError> {
-        Ok(Self {
-            registered_messages: register(
-                Counter::new(
-                    "gadget_gossip_registered_messages_total",
-                    "Number of registered messages by the gossip service.",
-                )?,
-                registry,
-            )?,
-            expired_messages: register(
-                Counter::new(
-                    "gadget_gossip_expired_messages_total",
-                    "Number of expired messages by the gossip service.",
-                )?,
-                registry,
-            )?,
-        })
-    }
-}
diff --git a/sdk/src/metrics/wasm.rs b/sdk/src/metrics/wasm.rs
deleted file mode 100644
index 0919676c7..000000000
--- a/sdk/src/metrics/wasm.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use prometheus::{Counter, Registry};
-
-pub struct Metrics {
-    registered_messages: Counter,
-    expired_messages: Counter,
-}
-
-impl Metrics {
-    fn register(_registry: &Registry) -> Result<Self, crate::Error> {
-        Err(crate::Error::PrometheusError {
-            err: "Prometheus is not yet supported on WASM targets".to_string(),
-        })
-    }
-}
diff --git a/common/src/locks.rs b/sdk/src/mutex_ext.rs
similarity index 60%
rename from common/src/locks.rs
rename to sdk/src/mutex_ext.rs
index 708d4aa7b..84e97bc52 100644
--- a/common/src/locks.rs
+++ b/sdk/src/mutex_ext.rs
@@ -1,21 +1,20 @@
-use async_trait::async_trait;
-use gadget_io::time::error::Elapsed;
-use gadget_io::tokio::sync::MutexGuard;
 use std::time::Duration;
+use tokio::sync::MutexGuard;
+use tokio::time::error::Elapsed;
 
-#[async_trait]
+#[async_trait::async_trait]
 pub trait TokioMutexExt<T: Send> {
-    async fn try_lock_timeout(&self, timeout: Duration) -> Result<MutexGuard<'_, T>, Elapsed>;
-    async fn lock_timeout(&self, timeout: Duration) -> MutexGuard<'_, T> {
+    async fn try_lock_timeout(&self, timeout: Duration) -> Result<MutexGuard<T>, Elapsed>;
+    async fn lock_timeout(&self, timeout: Duration) -> MutexGuard<T> {
         self.try_lock_timeout(timeout)
             .await
             .expect("Timeout on mutex lock")
     }
 }
 
-#[async_trait]
-impl<T: Send> TokioMutexExt<T> for gadget_io::tokio::sync::Mutex<T> {
-    async fn try_lock_timeout(&self, timeout: Duration) -> Result<MutexGuard<'_, T>, Elapsed> {
+#[async_trait::async_trait]
+impl<T: Send> TokioMutexExt<T> for tokio::sync::Mutex<T> {
+    async fn try_lock_timeout(&self, timeout: Duration) -> Result<MutexGuard<T>, Elapsed> {
         gadget_io::time::timeout(timeout, self.lock()).await
     }
 }
diff --git a/common/src/channels.rs b/sdk/src/network/channels.rs
similarity index 73%
rename from common/src/channels.rs
rename to sdk/src/network/channels.rs
index 40cec8db6..246fd711c 100644
--- a/common/src/channels.rs
+++ b/sdk/src/network/channels.rs
@@ -1,32 +1,16 @@
-//! TODO
-
+use crate::network::{deserialize, IdentifierInfo, Network, ProtocolMessage};
+use futures::StreamExt;
+use gadget_io::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
 use round_based::{Incoming, MessageDestination, MessageType, MsgId, Outgoing, PartyIndex};
 use serde::de::DeserializeOwned;
 use serde::{Deserialize, Serialize};
+use sp_core::ecdsa;
 
-use alloc::vec::Vec;
-
-cfg_if::cfg_if! {
-    if #[cfg(feature = "std")] {
-        use crate::environments::GadgetEnvironment;
-        #[cfg(feature = "substrate")]
-        use crate::module::network::Network;
-        use crate::prelude::DebugLogger;
-        #[cfg(feature = "substrate")]
-        use crate::utils::deserialize;
-        #[cfg(feature = "substrate")]
-        use futures::StreamExt;
-        #[cfg(feature = "substrate")]
-        use gadget_core::job::protocol::ProtocolMessageMetadata;
-        #[cfg(feature = "substrate")]
-        use gadget_io::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
-        use sp_core::ecdsa;
-
-        use std::collections::HashMap;
-        #[cfg(feature = "substrate")]
-        use std::sync::Arc;
-    }
-}
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use crate::error::Error;
+use crate::logger::Logger;
 
 pub type UserID = u16;
 
@@ -60,23 +44,19 @@ impl<B> Msg<B> {
     }
 }
 
-#[allow(clippy::too_many_arguments)]
-#[cfg(all(feature = "std", feature = "substrate"))]
+#[cfg(feature = "std")]
+#[allow(clippy::too_many_arguments, clippy::let_underscore_future)]
 pub fn create_job_manager_to_async_protocol_channel_split<
-    Env: GadgetEnvironment,
-    N: Network<Env>,
+    N: Network,
     C1: Serialize + DeserializeOwned + MaybeSenderReceiver + Send + 'static,
     C2: Serialize + DeserializeOwned + MaybeSenderReceiver + Send + 'static,
 >(
-    mut rx_gadget: UnboundedReceiver<Env::ProtocolMessage>,
-    associated_block_id: Env::Clock,
-    associated_retry_id: Env::RetryID,
-    associated_session_id: Env::SessionID,
-    associated_task_id: Env::TaskID,
+    mut rx_gadget: UnboundedReceiver<ProtocolMessage>,
+    identifier_info: IdentifierInfo,
     user_id_mapping: Arc<HashMap<u16, ecdsa::Public>>,
     my_account_id: ecdsa::Public,
     network: N,
-    logger: DebugLogger,
+    logger: Logger,
 ) -> (
     futures::channel::mpsc::UnboundedSender<C1>,
     futures::channel::mpsc::UnboundedReceiver<std::io::Result<C1>>,
@@ -88,9 +68,9 @@ pub fn create_job_manager_to_async_protocol_channel_split<
         gadget_io::tokio::sync::mpsc::unbounded_channel();
     let logger_outgoing = logger.clone();
     // Take the messages from the gadget and send them to the async protocol
-    gadget_io::tokio::task::spawn(async move {
+    let _ = gadget_io::tokio::task::spawn(async move {
         while let Some(msg) = rx_gadget.recv().await {
-            match deserialize::<MultiplexedChannelMessage<C1, C2>>(msg.payload()) {
+            match deserialize::<MultiplexedChannelMessage<C1, C2>>(&msg.payload[..]) {
                 Ok(msg) => match msg {
                     MultiplexedChannelMessage::Channel1(msg) => {
                         if tx_to_async_proto_1.unbounded_send(Ok(msg)).is_err() {
@@ -131,19 +111,16 @@ pub fn create_job_manager_to_async_protocol_channel_split<
         .expect("Failed to find my user id");
 
     // Take the messages the async protocol sends to the outbound channel and send them to the gadget
-    gadget_io::tokio::task::spawn(async move {
+    let _ = gadget_io::tokio::task::spawn(async move {
         let logger = &logger_outgoing;
         let channel_1_task = async move {
             while let Some(msg) = rx_to_outbound_1.next().await {
-                if let Err(err) = wrap_message_and_forward_to_network::<Env, _, C1, C2, (), _>(
+                if let Err(err) = wrap_message_and_forward_to_network::<_, C1, C2, (), _>(
                     msg,
                     &network,
                     &user_id_mapping,
                     my_user_id,
-                    associated_block_id,
-                    associated_session_id,
-                    associated_retry_id,
-                    associated_task_id,
+                    identifier_info,
                     MultiplexedChannelMessage::Channel1,
                     logger,
                 )
@@ -156,15 +133,12 @@ pub fn create_job_manager_to_async_protocol_channel_split<
 
         let channel_2_task = async move {
             while let Some(msg) = rx_to_outbound_2.recv().await {
-                if let Err(err) = wrap_message_and_forward_to_network::<Env, _, C1, C2, (), _>(
+                if let Err(err) = wrap_message_and_forward_to_network::<_, C1, C2, (), _>(
                     msg,
                     &network_clone,
                     &user_id_mapping_clone,
                     my_user_id,
-                    associated_block_id,
-                    associated_session_id,
-                    associated_retry_id,
-                    associated_task_id,
+                    identifier_info,
                     MultiplexedChannelMessage::Channel2,
                     logger,
                 )
@@ -186,28 +160,6 @@ pub fn create_job_manager_to_async_protocol_channel_split<
     )
 }
 
-#[cfg(feature = "std")]
-pub fn get_to_and_from_account_id<Env: GadgetEnvironment>(
-    mapping: &HashMap<u16, ecdsa::Public>,
-    from: u16,
-    to: Option<u16>,
-    logger: &DebugLogger,
-) -> (Option<ecdsa::Public>, Option<ecdsa::Public>) {
-    let from_account_id = mapping.get(&from).cloned();
-    let to_account_id = if let Some(to) = to {
-        mapping.get(&to).cloned()
-    } else {
-        None
-    };
-
-    logger.trace(format!(
-        "From (mapped): {:?}, To: {:?}",
-        from_account_id, to_account_id
-    ));
-
-    (to_account_id, from_account_id)
-}
-
 impl<T> MaybeSenderReceiver for Msg<T> {
     fn maybe_sender(&self) -> MaybeSender {
         MaybeSender::SomeoneElse(self.sender as UserID)
@@ -228,23 +180,8 @@ pub enum MultiplexedChannelMessage<C1, C2, C3 = ()> {
     Channel3(C3),
 }
 
-#[derive(Debug, Serialize, Deserialize)]
-pub struct VotingMessage {
-    pub from: UserID,
-    pub to: Option<UserID>,
-    pub payload: Vec<u8>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct PublicKeyGossipMessage {
-    pub from: UserID,
-    pub to: Option<UserID>,
-    pub signature: Vec<u8>,
-    pub id: sp_core::ecdsa::Public,
-}
-
 /// All possible senders of a message
-#[derive(Debug, Default, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone)]
 pub enum MaybeSender {
     /// We are the sender of the message
     Myself,
@@ -306,7 +243,7 @@ impl MaybeSender {
     }
 }
 
-#[derive(Debug, Default, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone)]
 pub enum MaybeReceiver {
     /// The message is broadcasted to everyone
     Broadcast,
@@ -332,7 +269,7 @@ impl MaybeReceiver {
     ///
     /// [`P2P`]: MaybeReceiver::P2P
     #[must_use]
-    pub fn is_p2_p(&self) -> bool {
+    pub fn is_p2p(&self) -> bool {
         matches!(self, Self::P2P(..))
     }
 
@@ -414,30 +351,6 @@ pub trait MaybeSenderReceiver {
     fn maybe_receiver(&self) -> MaybeReceiver;
 }
 
-impl MaybeSenderReceiver for PublicKeyGossipMessage {
-    fn maybe_sender(&self) -> MaybeSender {
-        MaybeSender::SomeoneElse(self.from)
-    }
-    fn maybe_receiver(&self) -> MaybeReceiver {
-        match self.to {
-            Some(id) => MaybeReceiver::P2P(id),
-            None => MaybeReceiver::Broadcast,
-        }
-    }
-}
-
-impl MaybeSenderReceiver for VotingMessage {
-    fn maybe_sender(&self) -> MaybeSender {
-        MaybeSender::SomeoneElse(self.from)
-    }
-    fn maybe_receiver(&self) -> MaybeReceiver {
-        match self.to {
-            Some(id) => MaybeReceiver::P2P(id),
-            None => MaybeReceiver::Broadcast,
-        }
-    }
-}
-
 impl<M> MaybeSenderReceiver for Outgoing<M> {
     fn maybe_sender(&self) -> MaybeSender {
         MaybeSender::Myself
@@ -497,24 +410,20 @@ pub type DuplexedChannel<O, I, C2> = (
     futures::channel::mpsc::UnboundedReceiver<C2>,
 );
 
-#[allow(clippy::too_many_arguments)]
-#[cfg(all(feature = "std", feature = "substrate"))]
+#[cfg(feature = "std")]
+#[allow(clippy::too_many_arguments, clippy::let_underscore_future)]
 pub fn create_job_manager_to_async_protocol_channel_split_io<
-    Env: GadgetEnvironment,
-    N: Network<Env>,
+    N: Network,
     C2: Serialize + DeserializeOwned + MaybeSenderReceiver + Send + 'static,
     O: InnerMessage<Inner = I::Inner> + MaybeSenderReceiver + Send + 'static,
     I: InnerMessage + InnerMessageFromInbound + MaybeSenderReceiver + Send + 'static,
 >(
-    mut rx_gadget: UnboundedReceiver<Env::ProtocolMessage>,
-    associated_block_id: Env::Clock,
-    associated_retry_id: Env::RetryID,
-    associated_session_id: Env::SessionID,
-    associated_task_id: Env::TaskID,
+    mut rx_gadget: UnboundedReceiver<ProtocolMessage>,
+    identifier_info: IdentifierInfo,
     user_id_mapping: Arc<HashMap<u16, ecdsa::Public>>,
     my_account_id: ecdsa::Public,
     network: N,
-    logger: DebugLogger,
+    logger: Logger,
     i: u16,
 ) -> DuplexedChannel<O, I, C2> {
     let (tx_to_async_proto_1, rx_for_async_proto_1) = futures::channel::mpsc::unbounded();
@@ -541,28 +450,27 @@ pub fn create_job_manager_to_async_protocol_channel_split_io<
     }
 
     // Take the messages from the gadget and send them to the async protocol
-    gadget_io::tokio::task::spawn(async move {
+    let _ = gadget_io::tokio::task::spawn(async move {
         let mut id = 0;
         while let Some(msg_orig) = rx_gadget.recv().await {
-            if msg_orig.payload().is_empty() {
+            if msg_orig.payload.is_empty() {
                 logger.warn(format!(
                     "Received empty message from Peer {:?}",
-                    msg_orig.associated_sender_user_id()
+                    msg_orig.sender.user_id
                 ));
                 continue;
             }
 
-            match deserialize::<MultiplexedChannelMessage<O::Inner, C2>>(msg_orig.payload()) {
+            match deserialize::<MultiplexedChannelMessage<O::Inner, C2>>(&msg_orig.payload[..]) {
                 Ok(msg) => match msg {
                     MultiplexedChannelMessage::Channel1(msg) => {
                         logger.trace(format!("Received message count: {id}", id = id + 1));
                         logger.trace(format!(
                             "Received message from {:?} as {:?}",
-                            msg_orig.associated_sender_user_id(),
-                            msg_orig.associated_recipient_user_id()
+                            msg_orig.sender.user_id, msg_orig.recipient
                         ));
-                        let msg_type = if let Some(to) = msg_orig.associated_recipient_user_id() {
-                            if let Some(to_account_id) = mapping_clone.get(&to) {
+                        let msg_type = if let Some(to) = msg_orig.recipient {
+                            if let Some(to_account_id) = mapping_clone.get(&to.user_id) {
                                 if *to_account_id != my_account_id {
                                     logger.error("Invalid message received");
                                     continue;
@@ -578,12 +486,7 @@ pub fn create_job_manager_to_async_protocol_channel_split_io<
                             MessageType::Broadcast
                         };
 
-                        let incoming = I::from_inbound(
-                            id,
-                            msg_orig.associated_sender_user_id(),
-                            msg_type,
-                            msg,
-                        );
+                        let incoming = I::from_inbound(id, msg_orig.sender.user_id, msg_type, msg);
 
                         if tx_to_async_proto_1.unbounded_send(Ok(incoming)).is_err() {
                             logger.error("Failed to send Incoming message to protocol");
@@ -613,24 +516,20 @@ pub fn create_job_manager_to_async_protocol_channel_split_io<
     let user_id_mapping_clone = user_id_mapping.clone();
 
     // Take the messages from the async protocol and send them to the gadget
-    gadget_io::tokio::task::spawn(async move {
+    let _ = gadget_io::tokio::task::spawn(async move {
         let logger = &logger_outgoing;
         let channel_1_task = async move {
             while let Some(msg) = rx_to_outbound_1.next().await {
-                if let Err(err) =
-                    wrap_message_and_forward_to_network::<Env, _, O::Inner, C2, (), _>(
-                        msg,
-                        &network,
-                        &user_id_mapping,
-                        my_user_id,
-                        associated_block_id,
-                        associated_session_id,
-                        associated_retry_id,
-                        associated_task_id,
-                        |m| MultiplexedChannelMessage::Channel1(m.inner_message()),
-                        logger,
-                    )
-                    .await
+                if let Err(err) = wrap_message_and_forward_to_network::<_, O::Inner, C2, (), _>(
+                    msg,
+                    &network,
+                    &user_id_mapping,
+                    my_user_id,
+                    identifier_info,
+                    |m| MultiplexedChannelMessage::Channel1(m.inner_message()),
+                    logger,
+                )
+                .await
                 {
                     logger.error(format!("Failed to send message to outbound: {err:?}"));
                 }
@@ -641,20 +540,16 @@ pub fn create_job_manager_to_async_protocol_channel_split_io<
 
         let channel_2_task = async move {
             while let Some(msg) = rx_to_outbound_2.next().await {
-                if let Err(err) =
-                    wrap_message_and_forward_to_network::<Env, _, O::Inner, C2, (), _>(
-                        msg,
-                        &network_clone,
-                        &user_id_mapping_clone,
-                        my_user_id,
-                        associated_block_id,
-                        associated_session_id,
-                        associated_retry_id,
-                        associated_task_id,
-                        |m| MultiplexedChannelMessage::Channel2(m),
-                        logger,
-                    )
-                    .await
+                if let Err(err) = wrap_message_and_forward_to_network::<_, O::Inner, C2, (), _>(
+                    msg,
+                    &network_clone,
+                    &user_id_mapping_clone,
+                    my_user_id,
+                    identifier_info,
+                    |m| MultiplexedChannelMessage::Channel2(m),
+                    logger,
+                )
+                .await
                 {
                     logger.error(format!("Failed to send message to outbound: {err:?}"));
                 }
@@ -683,26 +578,22 @@ pub type TriplexedChannel<O1, I1, O2, I2, C2> = (
     futures::channel::mpsc::UnboundedReceiver<C2>,
 );
 
-#[allow(clippy::too_many_arguments)]
-#[cfg(all(feature = "std", feature = "substrate"))]
+#[cfg(feature = "std")]
+#[allow(clippy::too_many_arguments, clippy::let_underscore_future)]
 pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
-    Env: GadgetEnvironment,
-    N: Network<Env> + 'static,
+    N: Network + 'static,
     C3: Serialize + DeserializeOwned + MaybeSenderReceiver + Send + 'static,
     O1: InnerMessage<Inner = I1::Inner> + MaybeSenderReceiver + Send + 'static,
     I1: InnerMessage + InnerMessageFromInbound + MaybeSenderReceiver + Send + 'static,
     O2: InnerMessage<Inner = I2::Inner> + MaybeSenderReceiver + Send + 'static,
     I2: InnerMessage + InnerMessageFromInbound + MaybeSenderReceiver + Send + 'static,
 >(
-    mut rx_gadget: UnboundedReceiver<Env::ProtocolMessage>,
-    associated_block_id: Env::Clock,
-    associated_retry_id: Env::RetryID,
-    associated_session_id: Env::SessionID,
-    associated_task_id: Env::TaskID,
+    mut rx_gadget: UnboundedReceiver<ProtocolMessage>,
+    identifier_info: IdentifierInfo,
     user_id_mapping: Arc<HashMap<u16, ecdsa::Public>>,
     my_account_id: ecdsa::Public,
     network: N,
-    logger: DebugLogger,
+    logger: Logger,
 ) -> TriplexedChannel<O1, I1, O2, I2, C3> {
     let (tx_to_async_proto_1, rx_for_async_proto_1) = futures::channel::mpsc::unbounded();
     let (tx_to_async_proto_2, rx_for_async_proto_2) = futures::channel::mpsc::unbounded();
@@ -710,34 +601,29 @@ pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
 
     let logger_outgoing = logger.clone();
     // Take the messages from the gadget and send them to the async protocol
-    gadget_io::tokio::task::spawn(async move {
+    let _ = gadget_io::tokio::task::spawn(async move {
         let mut id = 0;
         while let Some(msg_orig) = rx_gadget.recv().await {
-            if msg_orig.payload().is_empty() {
+            if msg_orig.payload.is_empty() {
                 logger.warn(format!(
                     "Received empty message from Peer {:?}",
-                    msg_orig.associated_sender_user_id()
+                    msg_orig.sender.user_id
                 ));
                 continue;
             }
 
             match deserialize::<MultiplexedChannelMessage<O1::Inner, O2::Inner, C3>>(
-                msg_orig.payload(),
+                &msg_orig.payload[..],
             ) {
                 Ok(msg) => match msg {
                     MultiplexedChannelMessage::Channel1(msg) => {
-                        let msg_type = if msg_orig.associated_recipient_user_id().is_some() {
+                        let msg_type = if msg_orig.recipient.is_some() {
                             MessageType::P2P
                         } else {
                             MessageType::Broadcast
                         };
 
-                        let incoming = I1::from_inbound(
-                            id,
-                            msg_orig.associated_sender_user_id(),
-                            msg_type,
-                            msg,
-                        );
+                        let incoming = I1::from_inbound(id, msg_orig.sender.user_id, msg_type, msg);
 
                         if tx_to_async_proto_1.unbounded_send(Ok(incoming)).is_err() {
                             logger.error("Failed to send Incoming message to protocol");
@@ -746,18 +632,13 @@ pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
                         id += 1;
                     }
                     MultiplexedChannelMessage::Channel2(msg) => {
-                        let msg_type = if msg_orig.associated_recipient_user_id().is_some() {
+                        let msg_type = if msg_orig.recipient.is_some() {
                             MessageType::P2P
                         } else {
                             MessageType::Broadcast
                         };
 
-                        let incoming = I2::from_inbound(
-                            id,
-                            msg_orig.associated_sender_user_id(),
-                            msg_type,
-                            msg,
-                        );
+                        let incoming = I2::from_inbound(id, msg_orig.sender.user_id, msg_type, msg);
 
                         if tx_to_async_proto_2.unbounded_send(Ok(incoming)).is_err() {
                             logger.error("Failed to send Incoming message to protocol");
@@ -794,22 +675,19 @@ pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
         })
         .expect("Failed to find my user id");
     // Take the messages from the async protocol and send them to the gadget
-    gadget_io::tokio::task::spawn(async move {
+    let _ = gadget_io::tokio::task::spawn(async move {
         let user_id_mapping = &user_id_mapping;
         let network = &network;
         let logger = &logger_outgoing;
         let task0 = async move {
             while let Some(msg) = rx_to_outbound_1.next().await {
                 if let Err(err) =
-                    wrap_message_and_forward_to_network::<Env, _, O1::Inner, O2::Inner, C3, _>(
+                    wrap_message_and_forward_to_network::<_, O1::Inner, O2::Inner, C3, _>(
                         msg,
                         network,
                         user_id_mapping,
                         my_user_id,
-                        associated_block_id,
-                        associated_session_id,
-                        associated_retry_id,
-                        associated_task_id,
+                        identifier_info,
                         |m| MultiplexedChannelMessage::Channel1(m.inner_message()),
                         logger,
                     )
@@ -823,15 +701,12 @@ pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
         let task1 = async move {
             while let Some(msg) = rx_to_outbound_2.next().await {
                 if let Err(err) =
-                    wrap_message_and_forward_to_network::<Env, _, O1::Inner, O2::Inner, C3, _>(
+                    wrap_message_and_forward_to_network::<_, O1::Inner, O2::Inner, C3, _>(
                         msg,
                         network,
                         user_id_mapping,
                         my_user_id,
-                        associated_block_id,
-                        associated_session_id,
-                        associated_retry_id,
-                        associated_task_id,
+                        identifier_info,
                         |m| MultiplexedChannelMessage::Channel2(m.inner_message()),
                         logger,
                     )
@@ -845,15 +720,12 @@ pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
         let task2 = async move {
             while let Some(msg) = rx_to_outbound_3.next().await {
                 if let Err(err) =
-                    wrap_message_and_forward_to_network::<Env, _, O1::Inner, O2::Inner, C3, _>(
+                    wrap_message_and_forward_to_network::<_, O1::Inner, O2::Inner, C3, _>(
                         msg,
                         network,
                         user_id_mapping,
                         my_user_id,
-                        associated_block_id,
-                        associated_session_id,
-                        associated_retry_id,
-                        associated_task_id,
+                        identifier_info,
                         |m| MultiplexedChannelMessage::Channel3(m),
                         logger,
                     )
@@ -878,10 +750,9 @@ pub fn create_job_manager_to_async_protocol_channel_split_io_triplex<
 }
 
 #[allow(clippy::too_many_arguments)]
-#[cfg(all(feature = "std", feature = "substrate"))]
+#[cfg(feature = "std")]
 async fn wrap_message_and_forward_to_network<
-    Env: GadgetEnvironment,
-    N: Network<Env>,
+    N: Network,
     C1: Serialize,
     C2: Serialize,
     C3: Serialize,
@@ -891,36 +762,29 @@ async fn wrap_message_and_forward_to_network<
     network: &N,
     user_id_mapping: &HashMap<u16, ecdsa::Public>,
     my_user_id: u16,
-    associated_block_id: Env::Clock,
-    associated_session_id: Env::SessionID,
-    associated_retry_id: Env::RetryID,
-    associated_task_id: Env::TaskID,
+    identifier_info: IdentifierInfo,
     splitter: impl FnOnce(M) -> MultiplexedChannelMessage<C1, C2, C3>,
-    logger: &DebugLogger,
-) -> Result<(), crate::Error>
+    logger: &Logger,
+) -> Result<(), Error>
 where
     M: MaybeSenderReceiver + Send + 'static,
 {
     let from = msg.maybe_sender();
     let to = msg.maybe_receiver();
     logger.trace(format!("Sending message from {:?} to {:?}", from, to));
-    let (to_account_id, from_account_id) = get_to_and_from_account_id::<Env>(
-        user_id_mapping,
-        from.as_user_id().unwrap_or(my_user_id),
-        to.as_user_id(),
-        logger,
-    );
 
-    let message_multiplexed = splitter(msg);
+    let from_account_id = user_id_mapping
+        .get(&from.as_user_id().unwrap_or(my_user_id))
+        .cloned();
+    let to_account_id = to
+        .as_user_id()
+        .and_then(|to| user_id_mapping.get(&to).cloned());
 
-    let msg = Env::build_protocol_message(
-        associated_block_id,
-        associated_session_id,
-        associated_retry_id,
-        associated_task_id,
+    let msg = N::build_protocol_message(
+        identifier_info,
         my_user_id,
         to.as_user_id(),
-        &message_multiplexed,
+        &splitter(msg),
         from_account_id,
         to_account_id,
     );
diff --git a/sdk/src/network/gossip.rs b/sdk/src/network/gossip.rs
index 97970825c..24e4fe290 100644
--- a/sdk/src/network/gossip.rs
+++ b/sdk/src/network/gossip.rs
@@ -6,9 +6,6 @@
 )]
 use async_trait::async_trait;
 use ecdsa::Public;
-use gadget_common::environments::GadgetEnvironment;
-use gadget_common::prelude::{DebugLogger, Network};
-use gadget_core::job::protocol::{ProtocolMessageMetadata, WorkManagerInterface};
 use gadget_io::tokio::sync::mpsc::UnboundedSender;
 use gadget_io::tokio::sync::{Mutex, RwLock};
 use libp2p::gossipsub::IdentTopic;
@@ -22,6 +19,11 @@ use std::collections::HashMap;
 use std::sync::atomic::AtomicU32;
 use std::sync::Arc;
 
+use crate::error::Error;
+use crate::logger::Logger;
+
+use super::{Network, ParticipantInfo, ProtocolMessage};
+
 /// Maximum allowed size for a Signed Message.
 pub const MAX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
 
@@ -42,7 +44,7 @@ pub struct MyBehaviour {
 pub type InboundMapping = (IdentTopic, UnboundedSender<Vec<u8>>, Arc<AtomicU32>);
 
 pub struct NetworkServiceWithoutSwarm<'a> {
-    pub logger: &'a DebugLogger,
+    pub logger: &'a Logger,
     pub inbound_mapping: &'a [InboundMapping],
     pub ecdsa_peer_id_to_libp2p_id: Arc<RwLock<HashMap<ecdsa::Public, PeerId>>>,
     pub role_key: &'a ecdsa::Pair,
@@ -67,7 +69,7 @@ impl<'a> NetworkServiceWithoutSwarm<'a> {
 
 pub struct NetworkService<'a> {
     pub swarm: &'a mut libp2p::Swarm<MyBehaviour>,
-    pub logger: &'a DebugLogger,
+    pub logger: &'a Logger,
     pub inbound_mapping: &'a [InboundMapping],
     pub ecdsa_peer_id_to_libp2p_id: &'a Arc<RwLock<HashMap<ecdsa::Public, PeerId>>>,
     pub role_key: &'a ecdsa::Pair,
@@ -268,7 +270,7 @@ pub struct GossipHandle {
     pub topic: IdentTopic,
     pub tx_to_outbound: UnboundedSender<IntraNodePayload>,
     pub rx_from_inbound: Arc<Mutex<gadget_io::tokio::sync::mpsc::UnboundedReceiver<Vec<u8>>>>,
-    pub logger: DebugLogger,
+    pub logger: Logger,
     pub connected_peers: Arc<AtomicU32>,
     pub ecdsa_peer_id_to_libp2p_id: Arc<RwLock<HashMap<ecdsa::Public, PeerId>>>,
 }
@@ -343,10 +345,8 @@ enum MessageType {
 }
 
 #[async_trait]
-impl<Env: GadgetEnvironment> Network<Env> for GossipHandle {
-    async fn next_message(
-        &self,
-    ) -> Option<<Env::WorkManager as WorkManagerInterface>::ProtocolMessage> {
+impl Network for GossipHandle {
+    async fn next_message(&self) -> Option<ProtocolMessage> {
         let mut lock = self
             .rx_from_inbound
             .try_lock()
@@ -359,24 +359,25 @@ impl<Env: GadgetEnvironment> Network<Env> for GossipHandle {
                 self.logger
                     .error(format!("Failed to deserialize message: {e}"));
                 drop(lock);
-                Network::<Env>::next_message(self).await
+                Network::next_message(self).await
             }
         }
     }
 
-    async fn send_message(
-        &self,
-        message: <Env::WorkManager as WorkManagerInterface>::ProtocolMessage,
-    ) -> Result<(), gadget_common::Error> {
-        let message_type = if let Some(to) = message.recipient_network_id() {
+    async fn send_message(&self, message: ProtocolMessage) -> Result<(), Error> {
+        let message_type = if let Some(ParticipantInfo {
+            ecdsa_key: Some(to),
+            ..
+        }) = message.recipient
+        {
             let libp2p_id = self
                 .ecdsa_peer_id_to_libp2p_id
                 .read()
                 .await
                 .get(&Public::from_raw(to.0))
                 .copied()
-                .ok_or_else(|| gadget_common::Error::NetworkError {
-                    err: format!(
+                .ok_or_else(|| Error::Network {
+                    reason: format!(
                         "No libp2p ID found for ecdsa public key: {to}. No handshake happened?"
                     ),
                 })?;
@@ -405,8 +406,8 @@ impl<Env: GadgetEnvironment> Network<Env> for GossipHandle {
 
         self.tx_to_outbound
             .send(payload)
-            .map_err(|e| gadget_common::Error::NetworkError {
-                err: format!("Failed to send intra-node payload: {e}"),
+            .map_err(|e| Error::Network {
+                reason: format!("Failed to send intra-node payload: {e}"),
             })
     }
 }
diff --git a/sdk/src/network/matchbox.rs b/sdk/src/network/matchbox.rs
index c1a365910..1946da376 100644
--- a/sdk/src/network/matchbox.rs
+++ b/sdk/src/network/matchbox.rs
@@ -1,6 +1,6 @@
+use crate::logging::Logger;
 use crate::network::matchbox::MatchboxEvent::P2p;
 use crate::network::network::NetworkHandle;
-use gadget_common::{debug_logger::DebugLogger, prelude::*};
 use gadget_io::tokio::sync::{Mutex, RwLock};
 use matchbox_socket::PeerId;
 use serde::{Deserialize, Serialize};
@@ -13,7 +13,7 @@ use std::{
 pub type InboundMapping = (String, UnboundedSender<Vec<u8>>, Arc<AtomicU32>);
 
 pub struct MatchboxNetworkService<'a> {
-    pub logger: &'a DebugLogger,
+    pub logger: &'a Logger,
     pub inbound_mapping: &'a [InboundMapping],
     pub ecdsa_peer_id_to_matchbox_id:
         &'a Arc<RwLock<HashMap<ecdsa::Public, matchbox_socket::PeerId>>>,
@@ -133,7 +133,7 @@ impl Network for MatchboxHandle {
     async fn send_message(
         &self,
         message: <WorkManager as WorkManagerInterface>::ProtocolMessage,
-    ) -> Result<(), gadget_common::Error> {
+    ) -> Result<(), gadget_sdk::Error> {
         let message_type = if let Some(to) = message.to_network_id {
             let matchbox_id = self
                 .ecdsa_peer_id_to_matchbox_id
@@ -141,7 +141,7 @@ impl Network for MatchboxHandle {
                 .await
                 .get(&to)
                 .cloned()
-                .ok_or_else(|| gadget_common::Error::NetworkError {
+                .ok_or_else(|| gadget_sdk::Error::NetworkError {
                     err: format!(
                         "No Matchbox ID found for ecdsa public key: {to:?}. No handshake happened?"
                     ),
@@ -173,7 +173,7 @@ impl Network for MatchboxHandle {
 
         self.tx_to_outbound
             .send(payload)
-            .map_err(|e| gadget_common::Error::NetworkError {
+            .map_err(|e| gadget_sdk::Error::NetworkError {
                 err: format!("Failed to send intra-node payload: {e}"),
             })
     }
diff --git a/sdk/src/network/mod.rs b/sdk/src/network/mod.rs
index 9dbfdedb4..593cacd6d 100644
--- a/sdk/src/network/mod.rs
+++ b/sdk/src/network/mod.rs
@@ -1,7 +1,127 @@
-#[cfg(not(target_family = "wasm"))]
+use core::fmt::Display;
+
+use async_trait::async_trait;
+use serde::{Deserialize, Serialize};
+use sp_core::ecdsa;
+
+use crate::error::Error;
+
+use self::channels::UserID;
+
+pub mod channels;
 pub mod gossip;
-#[cfg(not(target_family = "wasm"))]
 pub mod handlers;
 #[cfg(target_family = "wasm")]
 pub mod matchbox;
 pub mod setup;
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
+pub struct IdentifierInfo {
+    pub block_id: Option<u64>,
+    pub session_id: Option<u64>,
+    pub retry_id: Option<u64>,
+    pub task_id: Option<u64>,
+}
+
+impl Display for IdentifierInfo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let block_id = self
+            .block_id
+            .map(|id| format!("block_id: {}", id))
+            .unwrap_or_default();
+        let session_id = self
+            .session_id
+            .map(|id| format!("session_id: {}", id))
+            .unwrap_or_default();
+        let retry_id = self
+            .retry_id
+            .map(|id| format!("retry_id: {}", id))
+            .unwrap_or_default();
+        let task_id = self
+            .task_id
+            .map(|id| format!("task_id: {}", id))
+            .unwrap_or_default();
+        write!(f, "{} {} {} {}", block_id, session_id, retry_id, task_id)
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
+pub struct ParticipantInfo {
+    pub user_id: u16,
+    pub ecdsa_key: Option<sp_core::ecdsa::Public>,
+}
+
+impl Display for ParticipantInfo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let ecdsa_key = self
+            .ecdsa_key
+            .map(|key| format!("ecdsa_key: {}", key))
+            .unwrap_or_default();
+        write!(f, "user_id: {}, {}", self.user_id, ecdsa_key)
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct ProtocolMessage {
+    pub identifier_info: IdentifierInfo,
+    pub sender: ParticipantInfo,
+    pub recipient: Option<ParticipantInfo>,
+    pub payload: Vec<u8>,
+}
+
+impl Display for ProtocolMessage {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "identifier_info: {}, sender: {}, recipient: {:?}, payload: {:?}",
+            self.identifier_info, self.sender, self.recipient, self.payload
+        )
+    }
+}
+
+#[async_trait]
+pub trait Network: Send + Sync + Clone + 'static {
+    async fn next_message(&self) -> Option<ProtocolMessage>;
+    async fn send_message(&self, message: ProtocolMessage) -> Result<(), Error>;
+
+    /// If the network implementation requires a custom runtime, this function
+    /// should be manually implemented to keep the network alive
+    async fn run(self) -> Result<(), Error> {
+        Ok(())
+    }
+
+    fn build_protocol_message<Payload: Serialize>(
+        identifier_info: IdentifierInfo,
+        from: UserID,
+        to: Option<UserID>,
+        payload: &Payload,
+        from_account_id: Option<ecdsa::Public>,
+        to_network_id: Option<ecdsa::Public>,
+    ) -> ProtocolMessage {
+        let sender_participant_info = ParticipantInfo {
+            user_id: from,
+            ecdsa_key: from_account_id,
+        };
+        let receiver_participant_info = to.map(|to| ParticipantInfo {
+            user_id: to,
+            ecdsa_key: to_network_id,
+        });
+        ProtocolMessage {
+            identifier_info,
+            sender: sender_participant_info,
+            recipient: receiver_participant_info,
+            payload: serialize(payload).expect("Failed to serialize message"),
+        }
+    }
+}
+
+pub fn deserialize<'a, T>(data: &'a [u8]) -> Result<T, serde_json::Error>
+where
+    T: Deserialize<'a>,
+{
+    serde_json::from_slice::<T>(data)
+}
+
+pub fn serialize(object: &impl Serialize) -> Result<Vec<u8>, serde_json::Error> {
+    serde_json::to_vec(object)
+}
diff --git a/sdk/src/network/setup.rs b/sdk/src/network/setup.rs
index df52878ee..297c7bb32 100644
--- a/sdk/src/network/setup.rs
+++ b/sdk/src/network/setup.rs
@@ -1,10 +1,10 @@
 #![allow(unused_results, missing_docs)]
+use crate::logger::Logger;
 #[cfg(not(target_family = "wasm"))]
 use crate::network::gossip::{
     GossipHandle, IntraNodePayload, MyBehaviour, NetworkServiceWithoutSwarm, MAX_MESSAGE_SIZE,
 };
 use futures::StreamExt;
-use gadget_common::config::DebugLogger;
 
 #[cfg(not(target_family = "wasm"))]
 use libp2p::{
@@ -42,7 +42,7 @@ pub struct NetworkConfig {
     pub bind_ip: IpAddr,
     pub bind_port: u16,
     pub topics: Vec<String>,
-    pub logger: DebugLogger,
+    pub logger: Logger,
 }
 
 impl std::fmt::Debug for NetworkConfig {
@@ -68,7 +68,7 @@ impl NetworkConfig {
         bind_ip: IpAddr,
         bind_port: u16,
         topics: Vec<String>,
-        logger: DebugLogger,
+        logger: Logger,
     ) -> Self {
         Self {
             identity,
@@ -90,7 +90,7 @@ impl NetworkConfig {
         bind_ip: IpAddr,
         bind_port: u16,
         service_name: T,
-        logger: DebugLogger,
+        logger: Logger,
     ) -> Self {
         Self::new(
             identity,
diff --git a/sdk/src/prometheus/mod.rs b/sdk/src/prometheus/mod.rs
new file mode 100644
index 000000000..b77ea0aba
--- /dev/null
+++ b/sdk/src/prometheus/mod.rs
@@ -0,0 +1,5 @@
+mod shared;
+mod standard;
+
+pub use shared::*;
+pub use standard::*;
diff --git a/common/src/prometheus/shared.rs b/sdk/src/prometheus/shared.rs
similarity index 100%
rename from common/src/prometheus/shared.rs
rename to sdk/src/prometheus/shared.rs
diff --git a/common/src/prometheus/standard.rs b/sdk/src/prometheus/standard.rs
similarity index 58%
rename from common/src/prometheus/standard.rs
rename to sdk/src/prometheus/standard.rs
index 9dc92a33c..5a85f18dd 100644
--- a/common/src/prometheus/standard.rs
+++ b/sdk/src/prometheus/standard.rs
@@ -1,10 +1,12 @@
-use crate::prometheus::{BYTES_RECEIVED, BYTES_SENT, REGISTRY};
+use crate::error::Error;
+use crate::metrics;
+use crate::prometheus::shared::{BYTES_RECEIVED, BYTES_SENT, REGISTRY};
 use alloc::string::ToString;
 use core::net::SocketAddr;
 use core::str::FromStr;
 use core::sync::atomic::{AtomicBool, Ordering};
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
 pub enum PrometheusConfig {
     Enabled { bind_addr: SocketAddr },
     Disabled,
@@ -18,7 +20,7 @@ impl Default for PrometheusConfig {
     }
 }
 
-pub async fn setup(config: PrometheusConfig) -> Result<(), crate::Error> {
+pub async fn setup(config: PrometheusConfig) -> Result<(), Error> {
     if let PrometheusConfig::Enabled { bind_addr } = config {
         static HAS_REGISTERED: AtomicBool = AtomicBool::new(false);
         if HAS_REGISTERED.load(Ordering::SeqCst) {
@@ -27,21 +29,20 @@ pub async fn setup(config: PrometheusConfig) -> Result<(), crate::Error> {
 
         HAS_REGISTERED.store(true, Ordering::SeqCst);
 
-        substrate_prometheus_endpoint::register(BYTES_RECEIVED.clone(), &REGISTRY).map_err(
-            |err| crate::Error::PrometheusError {
-                err: err.to_string(),
-            },
-        )?;
-
-        substrate_prometheus_endpoint::register(BYTES_SENT.clone(), &REGISTRY).map_err(|err| {
-            crate::Error::PrometheusError {
+        let _ = metrics::register(BYTES_RECEIVED.clone(), &REGISTRY).map_err(|err| {
+            Error::Prometheus {
                 err: err.to_string(),
             }
         })?;
 
-        substrate_prometheus_endpoint::init_prometheus(bind_addr, REGISTRY.clone())
+        let _ =
+            metrics::register(BYTES_SENT.clone(), &REGISTRY).map_err(|err| Error::Prometheus {
+                err: err.to_string(),
+            })?;
+
+        metrics::init_prometheus(bind_addr, REGISTRY.clone())
             .await
-            .map_err(|err| crate::Error::PrometheusError {
+            .map_err(|err| Error::Prometheus {
                 err: err.to_string(),
             })?;
     }
diff --git a/sdk/src/random.rs b/sdk/src/random.rs
index 74e9f51fc..f5b27d4ad 100644
--- a/sdk/src/random.rs
+++ b/sdk/src/random.rs
@@ -20,7 +20,7 @@ pub fn getrandom_or_panic() -> impl RngCore + CryptoRng {
 /// Returns `OsRng` with `getrandom`, or a `CryptoRng` which panics without `getrandom`.
 #[cfg(not(feature = "getrandom"))]
 pub fn getrandom_or_panic() -> impl RngCore + CryptoRng {
-    const PRM: &'static str = "Attempted to use functionality that requires system randomness!!";
+    const PRM: &str = "Attempted to use functionality that requires system randomness!!";
 
     // TODO: Should we panic when invoked or when used?
 
diff --git a/sdk/src/run.rs b/sdk/src/run.rs
index 28198730f..d73f2ed62 100644
--- a/sdk/src/run.rs
+++ b/sdk/src/run.rs
@@ -1,4 +1,4 @@
-use crate::env::StdGadgetConfiguration;
+use crate::config::StdGadgetConfiguration;
 
 /// A trait defining the interface for running a gadget.
 ///
@@ -13,7 +13,7 @@ pub trait GadgetRunner {
     ///
     /// # Returns
     /// A reference to the `StdGadgetConfiguration` for this gadget.
-    fn env(&self) -> &StdGadgetConfiguration;
+    fn config(&self) -> &StdGadgetConfiguration;
 
     /// Registers the current operator with the the blueprint.
     ///
diff --git a/sdk/src/store/local_database.rs b/sdk/src/store/local_database.rs
new file mode 100644
index 000000000..ae9fe7504
--- /dev/null
+++ b/sdk/src/store/local_database.rs
@@ -0,0 +1,52 @@
+use serde::{de::DeserializeOwned, Serialize};
+use std::collections::HashMap;
+use std::fs;
+use std::path::Path;
+use std::sync::Mutex;
+
+#[derive(Debug)]
+pub struct LocalDatabase<T> {
+    path: String,
+    data: Mutex<HashMap<String, T>>,
+}
+
+impl<T> LocalDatabase<T>
+where
+    T: Serialize + DeserializeOwned + Clone + Default,
+{
+    /// Creates a new `LocalDatabase` instance with the given path.
+    #[must_use]
+    pub fn new(path: &str) -> Self {
+        let data = if Path::new(path).exists() {
+            let content = fs::read_to_string(path).expect("Failed to read the file");
+            serde_json::from_str(&content).unwrap_or_default()
+        } else {
+            HashMap::new()
+        };
+
+        Self {
+            path: path.to_string(),
+            data: Mutex::new(data),
+        }
+    }
+
+    /// Adds or updates a key-value pair in the database.
+    pub fn set(&self, key: &str, value: T) {
+        let mut data = self.data.lock().unwrap();
+        let _old = data.insert(key.to_string(), value);
+        self.save();
+    }
+
+    /// Retrieves a value associated with the given key.
+    pub fn get(&self, key: &str) -> Option<T> {
+        let data = self.data.lock().unwrap();
+        data.get(key).cloned()
+    }
+
+    /// Saves the current state of the database to the file.
+    fn save(&self) {
+        let data = self.data.lock().unwrap();
+        let json_string = serde_json::to_string(&*data).expect("Failed to serialize data to JSON");
+        fs::write(&self.path, json_string).expect("Failed to write to the file");
+    }
+}
diff --git a/sdk/src/store/mod.rs b/sdk/src/store/mod.rs
index ae9fe7504..4598f92d1 100644
--- a/sdk/src/store/mod.rs
+++ b/sdk/src/store/mod.rs
@@ -1,52 +1,214 @@
-use serde::{de::DeserializeOwned, Serialize};
+use crate::error::Error;
+use alloc::boxed::Box;
+use serde::de::DeserializeOwned;
+use serde::Serialize;
+use sp_core::ecdsa::Pair as EcdsaPair;
+use sp_core::sr25519::Pair as Sr25519Pair;
+use sp_core::Pair;
+
+use parking_lot::RwLock;
+use sqlx::sqlite::SqlitePoolOptions;
+use sqlx::{Pool, Row, Sqlite};
+
 use std::collections::HashMap;
-use std::fs;
-use std::path::Path;
-use std::sync::Mutex;
-
-#[derive(Debug)]
-pub struct LocalDatabase<T> {
-    path: String,
-    data: Mutex<HashMap<String, T>>,
-}
-
-impl<T> LocalDatabase<T>
-where
-    T: Serialize + DeserializeOwned + Clone + Default,
-{
-    /// Creates a new `LocalDatabase` instance with the given path.
-    #[must_use]
-    pub fn new(path: &str) -> Self {
-        let data = if Path::new(path).exists() {
-            let content = fs::read_to_string(path).expect("Failed to read the file");
-            serde_json::from_str(&content).unwrap_or_default()
-        } else {
-            HashMap::new()
-        };
+use std::sync::Arc;
+
+use crate::network::{deserialize, serialize};
+
+pub mod local_database;
+pub use local_database::LocalDatabase;
+
+#[async_trait::async_trait]
+pub trait KeyValueStoreBackend: Clone + Send + Sync + 'static {
+    async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error>;
+    async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error>;
+}
+
+pub type ECDSAKeyStore<BE> = GenericKeyStore<BE, EcdsaPair>;
+pub type Sr25519KeyStore<BE> = GenericKeyStore<BE, Sr25519Pair>;
+
+#[derive(Clone, Debug)]
+pub struct GenericKeyStore<BE: KeyValueStoreBackend, P: Pair> {
+    backend: BE,
+    pair: P,
+}
+
+#[cfg(feature = "std")]
+impl<P: Pair> GenericKeyStore<InMemoryBackend, P> {
+    pub fn in_memory(pair: P) -> Self {
+        GenericKeyStore {
+            backend: InMemoryBackend::new(),
+            pair,
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl<P: Pair> GenericKeyStore<SqliteBackend, P> {
+    pub async fn sqlite_in_memory(pair: P) -> Result<Self, Box<dyn std::error::Error>> {
+        let backend = SqliteBackend::in_memory().await?;
+        Ok(GenericKeyStore { backend, pair })
+    }
+}
+
+impl<P: Pair, Backend: KeyValueStoreBackend> GenericKeyStore<Backend, P> {
+    pub fn new(backend: Backend, pair: P) -> Self {
+        GenericKeyStore { backend, pair }
+    }
+}
+
+impl<P: Pair, BE: KeyValueStoreBackend> GenericKeyStore<BE, P> {
+    pub fn pair(&self) -> &P {
+        &self.pair
+    }
+}
+
+impl<P: Pair, BE: KeyValueStoreBackend> GenericKeyStore<BE, P> {
+    pub async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error> {
+        self.backend.get(key).await
+    }
+
+    pub async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error> {
+        self.backend.set(key, value).await
+    }
+}
 
+#[derive(Clone, Debug)]
+#[cfg(feature = "std")]
+pub struct InMemoryBackend {
+    map: Arc<RwLock<HashMap<[u8; 32], Vec<u8>>>>,
+}
+
+#[cfg(feature = "std")]
+impl Default for InMemoryBackend {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[cfg(feature = "std")]
+impl InMemoryBackend {
+    pub fn new() -> Self {
         Self {
-            path: path.to_string(),
-            data: Mutex::new(data),
+            map: Arc::new(RwLock::new(HashMap::new())),
+        }
+    }
+}
+
+#[async_trait::async_trait]
+#[cfg(feature = "std")]
+impl KeyValueStoreBackend for InMemoryBackend {
+    async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error> {
+        if let Some(bytes) = self.map.read().get(key).cloned() {
+            let value: T = deserialize(&bytes).map_err(|rr| Error::Store {
+                reason: format!("Failed to deserialize value: {:?}", rr),
+            })?;
+            Ok(Some(value))
+        } else {
+            Ok(None)
         }
     }
 
-    /// Adds or updates a key-value pair in the database.
-    pub fn set(&self, key: &str, value: T) {
-        let mut data = self.data.lock().unwrap();
-        let _old = data.insert(key.to_string(), value);
-        self.save();
+    async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error> {
+        let serialized = serialize(&value).map_err(|rr| Error::Store {
+            reason: format!("Failed to serialize value: {:?}", rr),
+        })?;
+        let _ = self.map.write().insert(*key, serialized);
+        Ok(())
+    }
+}
+
+#[derive(Clone, Debug)]
+#[cfg(feature = "std")]
+pub struct SqliteBackend {
+    pool: Pool<Sqlite>,
+}
+#[cfg(feature = "std")]
+impl SqliteBackend {
+    pub async fn in_memory() -> Result<Self, Box<dyn std::error::Error>> {
+        Self::new("sqlite://:memory:").await
+    }
+
+    // Initialize a new key-value store with a SqlitePool
+    pub async fn new(database_url: &str) -> Result<Self, Box<dyn std::error::Error>> {
+        let pool = SqlitePoolOptions::new().connect(database_url).await?;
+
+        // Ensure the table exists
+        let _ = sqlx::query(
+            r"CREATE TABLE IF NOT EXISTS key_value_store (
+                key TEXT PRIMARY KEY,
+                value BLOB NOT NULL
+              )",
+        )
+        .execute(&pool)
+        .await?;
+
+        Ok(Self { pool })
+    }
+}
+
+#[async_trait::async_trait]
+#[cfg(all(feature = "std", not(target_family = "wasm")))]
+impl KeyValueStoreBackend for SqliteBackend {
+    async fn get<T: DeserializeOwned>(&self, key: &[u8; 32]) -> Result<Option<T>, Error> {
+        let key = key_to_string(key);
+        let result = sqlx::query("SELECT value FROM key_value_store WHERE key = ?")
+            .bind(key)
+            .fetch_optional(&self.pool)
+            .await
+            .map_err(|err| Error::Store {
+                reason: format!("Failed to fetch value: {:?}", err),
+            })?;
+
+        match result {
+            Some(row) => {
+                let value: Vec<u8> = row.get("value");
+                let value: T = deserialize(&value).map_err(|rr| Error::Store {
+                    reason: format!("Failed to deserialize value: {:?}", rr),
+                })?;
+                Ok(Some(value))
+            }
+            None => Ok(None),
+        }
     }
 
-    /// Retrieves a value associated with the given key.
-    pub fn get(&self, key: &str) -> Option<T> {
-        let data = self.data.lock().unwrap();
-        data.get(key).cloned()
+    async fn set<T: Serialize + Send>(&self, key: &[u8; 32], value: T) -> Result<(), Error> {
+        let key = key_to_string(key);
+        let value = serialize(&value).map_err(|rr| Error::Store {
+            reason: format!("Failed to serialize value: {:?}", rr),
+        })?;
+
+        let _ = sqlx::query("INSERT INTO key_value_store (key, value) VALUES (?, ?)")
+            .bind(key)
+            .bind(value)
+            .execute(&self.pool)
+            .await
+            .map_err(|err| Error::Store {
+                reason: format!("Failed to insert value: {:?}", err),
+            })?;
+        Ok(())
     }
+}
+
+#[cfg(all(feature = "std", not(target_family = "wasm")))]
+fn key_to_string(key: &[u8; 32]) -> String {
+    hex::encode(key)
+}
+
+#[cfg(test)]
+#[cfg(not(target_family = "wasm"))]
+mod tests {
+    use crate::store::KeyValueStoreBackend;
+    use gadget_io::tokio;
 
-    /// Saves the current state of the database to the file.
-    fn save(&self) {
-        let data = self.data.lock().unwrap();
-        let json_string = serde_json::to_string(&*data).expect("Failed to serialize data to JSON");
-        fs::write(&self.path, json_string).expect("Failed to write to the file");
+    #[gadget_io::tokio::test]
+    #[cfg(feature = "std")]
+    async fn test_in_memory_kv_store() {
+        let store = super::SqliteBackend::in_memory().await.unwrap();
+        let key = [0u8; 32];
+        let value = "hello".to_string();
+        store.set(&key, value.clone()).await.unwrap();
+        let result: String = store.get(&key).await.unwrap().unwrap();
+        assert_eq!(value, result);
     }
 }
diff --git a/common/src/tracer.rs b/sdk/src/tracer.rs
similarity index 99%
rename from common/src/tracer.rs
rename to sdk/src/tracer.rs
index 1a0cd140d..10cfcb35c 100644
--- a/common/src/tracer.rs
+++ b/sdk/src/tracer.rs
@@ -118,6 +118,7 @@ impl<T: Tracer> Tracer for Option<T> {
 /// Implements [`Tracer`] trait so it can be embedded into protocol execution. `PerfProfiler` keeps track of time
 /// passed between each step of protocol. After protocol is completed, you can obtain a [`PerfReport`] via
 /// [`.get_report()`](PerfProfiler::get_report) method that contains all the measurements.
+#[derive(Debug)]
 pub struct PerfProfiler {
     last_timestamp: Option<Instant>,
     ongoing_stage: Option<usize>,
@@ -154,7 +155,7 @@ pub struct RoundDuration {
 }
 
 /// Performance of specific stage (part of [`PerfReport`])
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
 pub struct StageDuration {
     /// Stage name
     pub name: &'static str,


Test patch:
diff --git a/blueprint-test-utils/Cargo.toml b/blueprint-test-utils/Cargo.toml
index 44eed973f..0bb3c22cc 100644
--- a/blueprint-test-utils/Cargo.toml
+++ b/blueprint-test-utils/Cargo.toml
@@ -9,21 +9,21 @@ edition = "2021"
 default = ["std"]
 std = [
     "sp-core/std",
-    "sp-application-crypto/default",
-    "sp-io/default",
-    "sp-runtime/default",
-    "sp-std/default",
-    "sp-keystore/default",
-    "sp-externalities/default",
-    "frame-support/default",
-    "frame-system/default",
-    "gadget-common/default",
-    "gadget-io/default",
-    "tangle-primitives/default",
+    "sp-application-crypto/std",
+    "sp-io/std",
+    "sp-runtime/std",
+    "sp-std/std",
+    "sp-keystore/std",
+    "sp-externalities/std",
+    "frame-support/std",
+    "frame-system/std",
+    "gadget-io/std",
+    "gadget-sdk/std",
+    "tangle-primitives/std",
     "sc-client-api",
-    "sc-utils/default",
-    "pallet-services/default",
-    "pallet-balances/default",
+    "sc-utils",
+    "pallet-services/std",
+    "pallet-balances/std",
 ]
 wasm = [
     "sp-application-crypto/serde",
@@ -31,15 +31,14 @@ wasm = [
     "sp-io/disable_panic_handler",
     "sp-io/disable_oom",
     "sp-io/disable_allocator",
-    "gadget-common/wasm",
     "gadget-io/wasm-bindgen",
+    "gadget-sdk/wasm",
     "getrandom/js",
 ]
 
 [dependencies]
 blueprint-manager = { workspace = true }
 getrandom = { workspace = true, optional = true, default-features = false }
-environment-utils = { workspace = true }
 parking_lot = { workspace = true }
 tokio = { workspace = true, features = ["full"] }
 futures = { workspace = true }
@@ -49,10 +48,8 @@ log = { workspace = true }
 async-trait = { workspace = true }
 frame-support = { workspace = true, default-features = false }
 frame-system = { workspace = true, default-features = false }
-gadget-common = { workspace = true, default-features = false }
-gadget-core = { workspace = true }
 gadget-io = { workspace = true, default-features = false }
-tangle-environment.workspace = true
+gadget-sdk = { workspace = true, default-features = false }
 sc-client-api = { workspace = true, optional = true, default-features = false }
 sc-utils = { workspace = true, optional = true, default-features = false }
 sp-api = { workspace = true, default-features = false }
diff --git a/blueprint-test-utils/src/lib.rs b/blueprint-test-utils/src/lib.rs
index 4bbd7e9cf..dc2229393 100644
--- a/blueprint-test-utils/src/lib.rs
+++ b/blueprint-test-utils/src/lib.rs
@@ -2,31 +2,30 @@ use blueprint_manager::config::BlueprintManagerConfig;
 use blueprint_manager::executor::BlueprintManagerHandle;
 #[allow(unused_imports)]
 use cargo_tangle::deploy::Opts;
-use gadget_common::subxt_signer::sr25519;
-use gadget_common::tangle_runtime::api::services::calls::types::call::{Args, Job};
-use gadget_common::tangle_runtime::api::services::calls::types::create_blueprint::Blueprint;
-use gadget_common::tangle_runtime::api::services::calls::types::register::{
-    Preferences, RegistrationArgs,
-};
-use gadget_common::tangle_runtime::api::services::storage::types::job_results::JobResults;
-use gadget_common::tangle_runtime::{api, AccountId32};
-use gadget_common::tangle_subxt::subxt::OnlineClient;
-pub use gadget_core::job::SendFuture;
 use gadget_io::{GadgetConfig, SupportedChains};
+use gadget_sdk::clients::tangle::runtime::TangleConfig;
+use gadget_sdk::tangle_subxt::subxt::utils::AccountId32;
+use gadget_sdk::tangle_subxt::subxt::OnlineClient;
+use gadget_sdk::tangle_subxt::subxt_signer::sr25519;
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api;
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types;
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::call::{Args, Job};
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::create_blueprint::Blueprint;
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::{Preferences, RegistrationArgs};
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::storage::types::job_results::JobResults;
 use libp2p::Multiaddr;
 pub use log;
 use std::error::Error;
 use std::net::IpAddr;
 use std::path::PathBuf;
 use std::time::Duration;
-use tangle_environment::runtime::TangleConfig;
 use tracing_subscriber::filter::EnvFilter;
 use tracing_subscriber::fmt::SubscriberBuilder;
 use tracing_subscriber::util::SubscriberInitExt;
 use url::Url;
 
-pub type InputValue = api::runtime_types::tangle_primitives::services::field::Field<AccountId32>;
-pub type OutputValue = api::runtime_types::tangle_primitives::services::field::Field<AccountId32>;
+pub type InputValue = runtime_types::tangle_primitives::services::field::Field<AccountId32>;
+pub type OutputValue = runtime_types::tangle_primitives::services::field::Field<AccountId32>;
 
 pub mod sync;
 pub mod test_ext;
diff --git a/blueprint-test-utils/src/test_ext.rs b/blueprint-test-utils/src/test_ext.rs
index 5e84ecee3..4ac6687d5 100644
--- a/blueprint-test-utils/src/test_ext.rs
+++ b/blueprint-test-utils/src/test_ext.rs
@@ -15,39 +15,33 @@
 // along with Tangle.  If not, see <http://www.gnu.org/licenses/>.
 
 use crate::PerTestNodeInput;
-use async_trait::async_trait;
+use crate::runtime_types::tangle_primitives::services::PriceTargets;
 use blueprint_manager::executor::BlueprintManagerHandle;
+use blueprint_manager::sdk::entry::SendFuture;
+use blueprint_manager::sdk::setup::NodeInput;
 use cargo_tangle::deploy::{Opts, PrivateKeySigner};
-use environment_utils::transaction_manager::tangle::SubxtPalletSubmitter;
-use gadget_common::config::Network;
-use gadget_common::locks::TokioMutexExt;
-use gadget_common::prelude::{
-    DebugLogger, ECDSAKeyStore, GadgetEnvironment, InMemoryBackend, NodeInput, PairSigner,
-    PrometheusConfig, UnboundedReceiver, UnboundedSender,
-};
-use gadget_common::tangle_runtime::api;
-use gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services::{
-    ApprovalPrefrence, PriceTargets,
-};
-use gadget_common::tangle_runtime::api::services::calls::types::register::{
-    Preferences, RegistrationArgs,
-};
-use gadget_common::tangle_subxt::subxt::ext::subxt_core::utils;
-use gadget_common::tangle_subxt::subxt::{OnlineClient, SubstrateConfig};
-use gadget_common::Error;
-use gadget_core::job::protocol::{ProtocolMessageMetadata, WorkManagerInterface};
-use gadget_core::job::SendFuture;
+use gadget_sdk::clients::tangle::runtime::TangleRuntimeClient;
+use gadget_sdk::logger::Logger;
+use gadget_sdk::network::{Network, ParticipantInfo, ProtocolMessage};
+use gadget_sdk::prometheus::PrometheusConfig;
+use gadget_sdk::store::{ECDSAKeyStore, InMemoryBackend};
+use gadget_sdk::tangle_subxt::subxt::utils::AccountId32;
+use gadget_sdk::tangle_subxt::subxt::{OnlineClient, SubstrateConfig};
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types;
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::ApprovalPrefrence;
+use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::{Preferences, RegistrationArgs};
+use gadget_sdk::TokioMutexExt;
+use gadget_sdk::Error;
 use libp2p::Multiaddr;
 use sp_application_crypto::ecdsa;
 use sp_core::{sr25519, Pair};
+use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
 use std::collections::HashMap;
 use std::future::Future;
 use std::net::IpAddr;
 use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;
-use tangle_environment::runtime::{TangleConfig, TangleRuntime};
-use tangle_environment::TangleEnvironment;
 use url::Url;
 
 pub fn id_to_ecdsa_pair(id: u8) -> ecdsa::Pair {
@@ -69,18 +63,13 @@ pub fn id_to_sr25519_public(id: u8) -> sr25519::Public {
 type PeersRx<Env> =
     Arc<HashMap<ecdsa::Public, gadget_io::tokio::sync::Mutex<UnboundedReceiver<Env>>>>;
 
-pub struct MockNetwork<Env: GadgetEnvironment> {
-    peers_tx: Arc<
-        HashMap<
-            ecdsa::Public,
-            UnboundedSender<<Env::WorkManager as WorkManagerInterface>::ProtocolMessage>,
-        >,
-    >,
-    peers_rx: PeersRx<<Env::WorkManager as WorkManagerInterface>::ProtocolMessage>,
+pub struct MockNetwork {
+    peers_tx: Arc<HashMap<ecdsa::Public, UnboundedSender<ProtocolMessage>>>,
+    peers_rx: PeersRx<ProtocolMessage>,
     my_id: ecdsa::Public,
 }
 
-impl<Env: GadgetEnvironment> Clone for MockNetwork<Env> {
+impl Clone for MockNetwork {
     fn clone(&self) -> Self {
         Self {
             peers_tx: self.peers_tx.clone(),
@@ -90,7 +79,7 @@ impl<Env: GadgetEnvironment> Clone for MockNetwork<Env> {
     }
 }
 
-impl<Env: GadgetEnvironment> MockNetwork<Env> {
+impl MockNetwork {
     pub fn setup(ids: &Vec<ecdsa::Public>) -> Vec<Self> {
         let mut peers_tx = HashMap::new();
         let mut peers_rx = HashMap::new();
@@ -118,14 +107,9 @@ impl<Env: GadgetEnvironment> MockNetwork<Env> {
     }
 }
 
-#[async_trait]
-impl<Env: GadgetEnvironment> Network<Env> for MockNetwork<Env>
-where
-    Env::ProtocolMessage: Clone,
-{
-    async fn next_message(
-        &self,
-    ) -> Option<<Env::WorkManager as WorkManagerInterface>::ProtocolMessage> {
+#[async_trait::async_trait]
+impl Network for MockNetwork {
+    async fn next_message(&self) -> Option<ProtocolMessage> {
         self.peers_rx
             .get(&self.my_id)?
             .lock_timeout(Duration::from_millis(500))
@@ -134,18 +118,19 @@ where
             .await
     }
 
-    async fn send_message(
-        &self,
-        message: <Env::WorkManager as WorkManagerInterface>::ProtocolMessage,
-    ) -> Result<(), Error> {
-        let _check_message_has_ids = message.sender_network_id().ok_or(Error::MissingNetworkId)?;
-        if let Some(peer_id) = message.recipient_network_id() {
+    async fn send_message(&self, message: ProtocolMessage) -> Result<(), Error> {
+        let _check_message_has_ids = message.sender.ecdsa_key.ok_or(Error::MissingNetworkId)?;
+        if let Some(ParticipantInfo {
+            ecdsa_key: Some(peer_id),
+            ..
+        }) = message.recipient
+        {
             let tx = self
                 .peers_tx
                 .get(&peer_id)
                 .ok_or(Error::PeerNotFound { id: peer_id })?;
             tx.send(message).map_err(|err| Error::NetworkError {
-                err: err.to_string(),
+                reason: err.to_string(),
             })?;
         } else {
             // Broadcast to everyone except ourself
@@ -153,7 +138,7 @@ where
                 if peer_id != &self.my_id {
                     tx.send(message.clone())
                         .map_err(|err| Error::NetworkError {
-                            err: err.to_string(),
+                            reason: err.to_string(),
                         })?;
                 }
             }
@@ -252,7 +237,7 @@ pub async fn new_test_ext_blueprint_manager<
 
     for handle in handles.iter() {
         let keypair = handle.sr25519_id().clone();
-        let key = api::runtime_types::sp_core::ecdsa::Public(handle.ecdsa_id().public().0);
+        let key = runtime_types::sp_core::ecdsa::Public(handle.ecdsa_id().public().0);
 
         let preferences = Preferences {
             key,
@@ -305,7 +290,7 @@ pub async fn new_test_ext<
     const N: usize,
     const K: usize,
     D: Send + Clone + 'static,
-    F: Fn(NodeInput<TangleEnvironment, MockNetwork<TangleEnvironment>, InMemoryBackend, D>) -> Fut,
+    F: Fn(NodeInput<MockNetwork, InMemoryBackend, D>) -> Fut,
     Fut: SendFuture<'static, BlueprintManagerHandle>,
 >(
     additional_params: D,
@@ -323,18 +308,6 @@ pub async fn new_test_ext<
         .map(|i| id_to_sr25519_pair(i as u8))
         .collect::<Vec<_>>();
 
-    /*
-    let account_ids = pairs
-        .iter()
-        .map(|pair| pair.public().into())
-        .collect::<Vec<AccountId>>();
-
-
-    let balances = account_ids
-        .iter()
-        .map(|public| (public.clone(), 100u128))
-        .collect::<Vec<_>>();*/
-
     let networks = (0..K)
         .map(|_| MockNetwork::setup(&roles_identities))
         .collect::<Vec<_>>();
@@ -359,7 +332,7 @@ pub async fn new_test_ext<
     for (node_index, ((role_pair, pair), networks)) in
         role_pairs.into_iter().zip(pairs).zip(networks).enumerate()
     {
-        let account_id: utils::AccountId32 = pair.public().0.into();
+        let account_id: AccountId32 = pair.public().0.into();
         let mut localhost_clients = Vec::new();
 
         for _ in 0..K {
@@ -368,22 +341,15 @@ pub async fn new_test_ext<
                 .await
                 .expect("Failed to create localhost client");
 
-            let client = TangleRuntime::new(client, account_id.clone());
+            let client = TangleRuntimeClient::new(client, account_id.clone());
             localhost_clients.push(client);
         }
 
-        let logger = DebugLogger {
+        let logger = Logger {
+            target: "blueprint-test-utils",
             id: format!("Peer {node_index}"),
         };
 
-        let pair_signer = PairSigner::<TangleConfig>::new(pair);
-
-        let tx_manager = Arc::new(
-            SubxtPalletSubmitter::new(pair_signer, logger.clone())
-                .await
-                .expect("Failed to create tx manager"),
-        );
-
         let keystore = ECDSAKeyStore::in_memory(role_pair);
         let prometheus_config = PrometheusConfig::Disabled;
 
@@ -392,7 +358,6 @@ pub async fn new_test_ext<
             networks,
             account_id: sr25519::Public::from_raw(account_id.0),
             logger,
-            tx_manager: tx_manager as _,
             keystore,
             node_index,
             additional_params: additional_params.clone(),
diff --git a/gadget-configs/local-testnet-0.toml b/gadget-configs/local-testnet-0.toml
deleted file mode 100644
index fdea77dd0..000000000
--- a/gadget-configs/local-testnet-0.toml
+++ /dev/null
@@ -1,7 +0,0 @@
-bind_ip = "0.0.0.0"
-bind_port = 30555
-bootnodes = []
-chain = "local_testnet"
-base_path = "../tangle/tmp/alice"
-verbose=1
-pretty=true
diff --git a/gadget-configs/local-testnet-1.toml b/gadget-configs/local-testnet-1.toml
deleted file mode 100644
index 25ee07c19..000000000
--- a/gadget-configs/local-testnet-1.toml
+++ /dev/null
@@ -1,7 +0,0 @@
-bind_ip = "0.0.0.0"
-bind_port = 30556
-bootnodes = []
-chain = "local_testnet"
-base_path = "../tangle/tmp/bob"
-verbose=1
-pretty=true
\ No newline at end of file
diff --git a/gadget-configs/local-testnet-2.toml b/gadget-configs/local-testnet-2.toml
deleted file mode 100644
index 8ab019bf6..000000000
--- a/gadget-configs/local-testnet-2.toml
+++ /dev/null
@@ -1,7 +0,0 @@
-bind_ip = "0.0.0.0"
-bind_port = 30557
-bootnodes = []
-chain = "local_testnet"
-base_path = "../tangle/tmp/charlie"
-verbose=1
-pretty=true
\ No newline at end of file