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

Issue:
[TASK] Shell Manager - Listen for blueprints
### Overview

The shell manager once it starts, it should query the node to get all the blueprints that this operator is registered in, then it listens for a stream of finality notifications.

In other words, we do this at startup, to get a full view of the world (i.e sync):

```ts
let servicesWithBlueprint = await api.rpc.services.queryServicesWithBlueprintsByOperator(ALICE);
```
> See: https://github.com/webb-tools/tangle/pull/647

Then we should listen for events that are produced by the network, and here are some of them:

```rs
/// An new operator has been registered.
Registered {
  /// The account that registered as a operator.
  provider: T::AccountId,
  /// The ID of the service blueprint.
  blueprint_id: u64,
  /// The preferences for the operator for this specific blueprint.
  preferences: OperatorPreferences,
  /// The arguments used for registration.
  registration_args: Vec<Field<T::Constraints, T::AccountId>>,
}
```
```rs
/// An operator has been unregistered.
Unregistered {
  /// The account that unregistered as am operator.
  operator: T::AccountId,
  /// The ID of the service blueprint.
  blueprint_id: u64,
}
```
These events should tell you that if the current operator has registered or unregistered from a blueprint.
More could be found here: https://github.com/webb-tools/tangle/blob/74ef976802a68b960677277786b91bca13e260a3/pallets/services/src/lib.rs#L196-L330

### Task Checklist

- [x] #178
- [x] #179
- [x] #180



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml
index fbcffd90a..74fe6b243 100644
--- a/.github/workflows/validate_pr.yml
+++ b/.github/workflows/validate_pr.yml
@@ -54,7 +54,7 @@ jobs:
         run: sudo apt-get install protobuf-compiler
 
       - name: Run Clippy
-        run: cargo clippy --tests -- -D warnings
+        run: cargo clippy --package shell-manager --tests -- -D warnings
 
   build:
     timeout-minutes: 120
@@ -65,6 +65,7 @@ jobs:
             package: [
               gadget-core,
               gadget-common,
+              shell-manager,
             ]
     steps:
       - name: checkout code
@@ -83,7 +84,7 @@ jobs:
         run: brew install protobuf
 
       - name: build
-        run: cargo build --workspace --package ${{ matrix.package }}
+        run: cargo build --package ${{ matrix.package }}
 
   testing:
     timeout-minutes: 90
@@ -94,6 +95,7 @@ jobs:
             package: [
               gadget-core,
               gadget-common,
+              shell-manager,
             ]
     steps:
       - name: checkout code
@@ -147,7 +149,7 @@ jobs:
           export LIBRARY_PATH=$LIBRARY_PATH:/opt/homebrew/lib
           export INCLUDE_PATH=$INCLUDE_PATH:/opt/homebrew/include
 
-      - name: Check if wasm32-unknown-unknown is installed
+      - name: Ensure wasm32-unknown-unknown is installed
         run: rustup target add wasm32-unknown-unknown
 
       - name: Run wasm checks
diff --git a/Cargo.toml b/Cargo.toml
index f9d250ae2..b2622db92 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,14 +31,14 @@ shell-manager = { path = "./shell-manager" }
 tangle-environment = { path = "./environments/tangle" }
 environment-utils = { path = "./environments/utils" }
 
+# Tangle-related dependencies
+pallet-services = { git = "https://github.com/webb-tools/tangle.git", branch = "jobs/v2" }
+pallet-services-rpc = { git = "https://github.com/webb-tools/tangle.git", branch = "jobs/v2" }
+tangle-primitives = { git = "https://github.com/webb-tools/tangle.git", branch = "jobs/v2" }
+tangle-subxt = { git = "https://github.com/webb-tools/tangle.git", default-features = false, branch = "jobs/v2" }
+
 snowbridge-milagro-bls = { git = "https://github.com/Snowfork/milagro_bls", default-features = false, rev = "43a5d480ed6e3b83de4cf54888680d51604199e6" }
 gennaro-dkg = { git = "https://github.com/mikelodder7/gennaro-dkg.git", default-features = false, rev = "1c15242" }
-pallet-jobs-rpc-runtime-api = { git = "https://github.com/webb-tools/tangle.git", branch = "main" }
-pallet-jobs = { git = "https://github.com/webb-tools/tangle.git", branch = "main" }
-pallet-dkg = { git = "https://github.com/webb-tools/tangle.git", branch = "main" }
-pallet-zksaas = { git = "https://github.com/webb-tools/tangle.git", branch = "main" }
-tangle-primitives = { git = "https://github.com/webb-tools/tangle.git", branch = "main" }
-tangle-subxt = { git = "https://github.com/webb-tools/tangle.git", default-features = false, branch = "main" }
 subxt-signer = { version = "0.34.0", default-features = false }
 subxt = { version = "0.34.0", default-features = false }
 
@@ -108,6 +108,13 @@ frame-executive = { git = "https://github.com/paritytech/polkadot-sdk", branch =
 
 pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false  }
 pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false  }
+pallet-session = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false }
+pallet-staking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0" }
+pallet-session-historical = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false }
+pallet-evm = { git = "https://github.com/paritytech/frontier.git", branch = "polkadot-v1.7.0", default-features = false }
+sp-staking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false }
+sp-npos-elections = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0"}
+sp-session = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0"}
 
 # ARK Libraries
 ark-std = { version = "0.4.0", default-features = false, features = [ "print-trace", "std" ] }
@@ -164,7 +171,7 @@ starknet-crypto = "0.6.1"
 anyhow = "1.0.79"
 libsecp256k1 = "0.7.1"
 rayon = { version = "1.8.0" }
-thiserror = { version = "=1.0.57" }
+thiserror = { version = "1" }
 aes-gcm = { version = "0.10", default-features = false, features = ["aes", "alloc", "getrandom", "zeroize"] }
 zeroize = { version = "1", default-features = false, features = ["alloc", "zeroize_derive"] }
 substrate-prometheus-endpoint = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false }
diff --git a/common/Cargo.toml b/common/Cargo.toml
index f7543c0ea..75c8dd54d 100644
--- a/common/Cargo.toml
+++ b/common/Cargo.toml
@@ -37,23 +37,23 @@ color-eyre = { workspace = true }
 
 # Substrate
 tangle-subxt = { workspace = true, optional = true }
-subxt-signer = { workspace = true, default-features = false, features = ["subxt", "sr25519"], optional = true }
-subxt = { workspace = true, optional = true, default-features = false }
-sp-api = { workspace = true, optional = true, default-features = false }
-sp-io = { workspace = true, optional = true, default-features = false }
-sp-core = { workspace = true, optional = true, default-features = false }
-sp-application-crypto = { workspace = true, optional = true, default-features = false }
-sp-runtime = { workspace = true, optional = true, default-features = false }
-sp-tracing = { workspace = true, optional = true, default-features = false }
-sp-runtime-interface = { workspace = true, default-features = false}
-sc-service = { workspace = true, optional = true, default-features = false }
-sc-client-api = { workspace = true, optional = true, default-features = false }
-substrate-prometheus-endpoint = { workspace = true, optional = true, default-features = false }
-frame-support = { workspace = true, optional = true, default-features = false }
-linked-hash-map = { workspace = true, optional = true, default-features = false }
+subxt-signer = { workspace = true, features = ["subxt", "sr25519"], optional = true }
+subxt = { workspace = true, optional = true }
+sp-api = { workspace = true, optional = true }
+sp-io = { workspace = true, optional = true }
+sp-core = { workspace = true, optional = true }
+sp-application-crypto = { workspace = true, optional = true }
+sp-runtime = { workspace = true, optional = true }
+sp-tracing = { workspace = true, optional = true }
+sp-runtime-interface = { workspace = true}
+sc-service = { workspace = true, optional = true }
+sc-client-api = { workspace = true, optional = true }
+substrate-prometheus-endpoint = { workspace = true, optional = true }
+frame-support = { workspace = true, optional = true }
+linked-hash-map = { workspace = true, optional = true }
 
-parity-scale-codec = { workspace = true, optional = true, default-features = false }
-scale-info = { workspace = true, optional = true, default-features = false, features = ["derive"] }
+parity-scale-codec = { workspace = true, optional = true }
+scale-info = { workspace = true, optional = true, features = ["derive"] }
 thiserror = { workspace = true }
 
 [features]
@@ -73,7 +73,6 @@ substrate = [
   "sp-runtime/default",
   "sp-application-crypto/default",
   "sp-tracing/default",
-  "sc-service",
   "sc-client-api",
   "frame-support/std",
   "linked-hash-map",
@@ -87,6 +86,12 @@ std = [
   "serde_bytes/std",
   "tracing/std",
   "tracing/attributes",
+    "sp-api/std",
+    "sp-io/std",
+    "sp-core/std",
+    "sp-runtime/std",
+    "sp-application-crypto/std",
+    "sp-tracing/std",
 ]
 wasm = [
   "gadget-io/wasm",
diff --git a/common/src/client.rs b/common/src/client.rs
index bdfc3e00d..dc78a7d65 100644
--- a/common/src/client.rs
+++ b/common/src/client.rs
@@ -1,10 +1,10 @@
 use crate::debug_logger::DebugLogger;
 use crate::environments::GadgetEnvironment;
-use crate::tangle_runtime::*;
 use async_trait::async_trait;
 use gadget_core::gadget::general::Client;
 use sp_core::Pair;
 use std::sync::Arc;
+#[cfg(feature = "substrate")]
 use tangle_subxt::subxt::{self};
 
 pub struct JobsClient<Env: GadgetEnvironment> {
@@ -13,6 +13,12 @@ pub struct JobsClient<Env: GadgetEnvironment> {
     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 {
@@ -48,28 +54,6 @@ where
         .expect("Failed to spawn blocking task")
 }
 
-pub trait JobTypeExt {
-    /// Checks if the job type is a phase one job.
-    fn is_phase_one(&self) -> bool;
-    /// Gets the participants for the job type, if applicable.
-    fn get_participants(self) -> Option<Vec<AccountId32>>;
-    /// Gets the threshold value for the job type, if applicable.
-    fn get_threshold(self) -> Option<u8>;
-    /// Gets the role associated with the job type.
-    fn get_role_type(&self) -> roles::RoleType;
-    /// Gets the phase one ID for phase two jobs, if applicable.
-    fn get_phase_one_id(&self) -> Option<u64>;
-    /// Gets the permitted caller for the job type, if applicable.
-    fn get_permitted_caller(self) -> Option<AccountId32>;
-}
-
-pub trait PhaseResultExt {
-    /// Gets the participants for the phase result, if applicable.
-    fn participants(&self) -> Option<Vec<AccountId32>>;
-    /// Gets the threshold value for the phase result, if applicable.
-    fn threshold(&self) -> Option<u8>;
-}
-
 #[async_trait]
 impl<Env: GadgetEnvironment> Client<Env::Event> for JobsClient<Env> {
     async fn next_event(&self) -> Option<Env::Event> {
@@ -80,13 +64,16 @@ impl<Env: GadgetEnvironment> Client<Env::Event> for JobsClient<Env> {
         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]>,
@@ -97,6 +84,7 @@ where
     }
 }
 
+#[cfg(feature = "substrate")]
 impl<T: subxt::Config> subxt::tx::Signer<T> for PairSigner<T>
 where
     T::Signature: From<subxt::utils::MultiSignature>,
diff --git a/common/src/environments/mod.rs b/common/src/environments/mod.rs
index 86d4fcb22..61fe16e6a 100644
--- a/common/src/environments/mod.rs
+++ b/common/src/environments/mod.rs
@@ -52,7 +52,7 @@ where
         to_network_id: Option<ecdsa::Public>,
     ) -> Self::ProtocolMessage;
 
-    async fn setup_client(&self) -> Result<Self::Client, Self::Error>;
+    async fn setup_runtime(&self) -> Result<Self::Client, Self::Error>;
 
     fn transaction_manager(&self) -> Self::TransactionManager;
 
diff --git a/common/src/full_protocol.rs b/common/src/full_protocol.rs
index fb3a457cc..45d0bd04d 100644
--- a/common/src/full_protocol.rs
+++ b/common/src/full_protocol.rs
@@ -5,7 +5,6 @@ use crate::keystore::{ECDSAKeyStore, KeystoreBackend};
 use crate::module::WorkManagerConfig;
 use crate::prometheus::PrometheusConfig;
 use crate::protocol::AsyncProtocol;
-use crate::tangle_runtime::*;
 use crate::Error;
 use async_trait::async_trait;
 use gadget_core::gadget::manager::AbstractGadget;
@@ -75,21 +74,10 @@ pub trait FullProtocolConfig<Env: GadgetEnvironment>:
     }
 
     fn account_id(&self) -> &sr25519::Public;
-
     fn name(&self) -> String;
-
-    fn role_filter(&self, role: roles::RoleType) -> bool;
-
-    fn phase_filter(
-        &self,
-        job: jobs::JobType<AccountId32, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>,
-    ) -> bool;
-
     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()
@@ -207,17 +195,6 @@ where
         T::name(self)
     }
 
-    fn role_filter(&self, role: roles::RoleType) -> bool {
-        T::role_filter(self, role)
-    }
-
-    fn phase_filter(
-        &self,
-        job: jobs::JobType<AccountId32, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>,
-    ) -> bool {
-        T::phase_filter(self, job)
-    }
-
     fn client(&self) -> JobsClient<Env> {
         T::get_jobs_client(self)
     }
diff --git a/common/src/lib.rs b/common/src/lib.rs
index 6f0bb4722..95b7bcb4c 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -49,45 +49,12 @@ pub use color_eyre;
 pub use gadget_io;
 pub use tangle_subxt;
 
-#[cfg(feature = "tangle-testnet")]
+//#[cfg(feature = "tangle-mainnet")]
 pub mod tangle_runtime {
     pub use tangle_subxt::subxt::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,
-        tangle_primitives::jobs::{
-            self,
-            tss::{self, *},
-            zksaas::{self, *},
-            JobType::*,
-            PhaseResult, RpcResponseJobsData,
-        },
-        tangle_primitives::roles::{self, RoleType},
-        tangle_testnet_runtime::{
-            MaxAdditionalParamsLen, MaxDataLen, MaxKeyLen, MaxParticipants, MaxProofLen,
-            MaxSignatureLen, MaxSubmissionLen,
-        },
-    };
-}
-
-#[cfg(feature = "tangle-mainnet")]
-pub mod tangle_runtime {
-    pub use tangle_subxt::subxt::utils::AccountId32;
-    pub use tangle_subxt::tangle_mainnet_runtime::api;
-    pub use tangle_subxt::tangle_mainnet_runtime::api::runtime_types::{
-        bounded_collections::bounded_vec::BoundedVec,
-        tangle_primitives::jobs::{
-            self,
-            tss::{self, *},
-            zksaas::{self, *},
-            JobType::*,
-            PhaseResult,
-        },
-        tangle_primitives::roles::{self, RoleType},
-        tangle_runtime::{
-            MaxAdditionalParamsLen, MaxDataLen, MaxKeyLen, MaxParticipants, MaxProofLen,
-            MaxSignatureLen, MaxSubmissionLen,
-        },
     };
 }
 
diff --git a/common/src/module/mod.rs b/common/src/module/mod.rs
index a586cd8ed..203de71df 100644
--- a/common/src/module/mod.rs
+++ b/common/src/module/mod.rs
@@ -3,7 +3,6 @@ use crate::debug_logger::DebugLogger;
 use crate::environments::GadgetEnvironment;
 use crate::module::network::Network;
 use crate::protocol::{AsyncProtocol, AsyncProtocolRemote};
-use crate::tangle_runtime::*;
 use crate::Error;
 use async_trait::async_trait;
 use gadget_core::gadget::general::Client;
@@ -152,7 +151,7 @@ pub trait GadgetProtocol<Env: GadgetEnvironment>:
     async fn create_next_job(
         &self,
         job: <Env as GadgetEnvironment>::JobInitMetadata,
-        work_manager: &ProtocolWorkManager<Env::WorkManager>,
+        work_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
     ) -> Result<<Self as AsyncProtocol<Env>>::AdditionalParams, Error>;
 
     async fn process_event(
@@ -164,7 +163,7 @@ pub trait GadgetProtocol<Env: GadgetEnvironment>:
     async fn process_error(
         &self,
         error: Env::Error,
-        job_manager: &ProtocolWorkManager<Env::WorkManager>,
+        job_manager: &ProtocolWorkManager<<Env as GadgetEnvironment>::WorkManager>,
     );
 
     async fn generate_work_manager(
@@ -177,28 +176,6 @@ pub trait GadgetProtocol<Env: GadgetEnvironment>:
     /// The Protocol Name.
     /// Used for logging and debugging purposes
     fn name(&self) -> String;
-    /// Filter queried jobs by role type.
-    /// ## Example
-    ///
-    /// ```rust,ignore
-    /// fn role_filter(&self, role: RoleType) -> bool {
-    ///   matches!(role, RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1))
-    /// }
-    /// ```
-    fn role_filter(&self, role: roles::RoleType) -> bool;
-
-    /// Filter queried jobs by Job type & Phase.
-    /// ## Example
-    ///
-    /// ```rust,ignore
-    /// fn phase_filter(&self, job: JobType<AccountId, MaxParticipants, MaxSubmissionLen>) -> bool {
-    ///   matches!(job, JobType::DKGTSSPhaseOne(_))
-    /// }
-    /// ```
-    fn phase_filter(
-        &self,
-        job: jobs::JobType<AccountId32, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>,
-    ) -> bool;
     fn client(&self) -> JobsClient<Env>;
     fn logger(&self) -> DebugLogger;
     fn get_work_manager_config(&self) -> WorkManagerConfig {
diff --git a/core/src/gadget/substrate/mod.rs b/core/src/gadget/substrate/mod.rs
index dc7380c3f..e369a6a2d 100644
--- a/core/src/gadget/substrate/mod.rs
+++ b/core/src/gadget/substrate/mod.rs
@@ -1,14 +1,6 @@
 use std::error::Error;
 use std::fmt::{Debug, Display, Formatter};
 
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct FinalityNotification {
-    /// Finalized block number.
-    pub number: u64,
-    /// Finalized block header hash.
-    pub hash: [u8; 32],
-}
-
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub struct SubstrateGadgetError {}
 
diff --git a/environments/tangle/Cargo.toml b/environments/tangle/Cargo.toml
index 4841cf566..46f116677 100644
--- a/environments/tangle/Cargo.toml
+++ b/environments/tangle/Cargo.toml
@@ -7,8 +7,19 @@ license.workspace = true
 homepage.workspace = true
 repository.workspace = true
 
+[features]
+default = ["std"]
+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 }
+gadget-common = { workspace = true, features = ["substrate"] }
 environment-utils = { workspace = true, features = ["tangle"] }
 serde = { workspace = true }
 gadget-core = { workspace = true }
diff --git a/environments/tangle/src/api.rs b/environments/tangle/src/api.rs
index 027d10379..cbb0b8fb7 100644
--- a/environments/tangle/src/api.rs
+++ b/environments/tangle/src/api.rs
@@ -1,131 +1,57 @@
-use auto_impl::auto_impl;
-use gadget_common::async_trait::async_trait;
-use gadget_common::environments::GadgetEnvironment;
-use gadget_common::tangle_runtime;
-use gadget_common::tangle_runtime::{
-    AccountId32, MaxAdditionalParamsLen, MaxDataLen, MaxKeyLen, MaxParticipants, MaxProofLen,
-    MaxSignatureLen, MaxSubmissionLen,
-};
-use gadget_core::gadget::general::Client;
+use gadget_common::config::DebugLogger;
+use gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services;
+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;
 
-#[async_trait]
-#[auto_impl(Arc)]
-pub trait ClientWithApi<Env: GadgetEnvironment>: Client<Env::Event> + 'static {
-    /// Query jobs associated with a specific validator.
-    ///
-    /// This function takes a `validator` parameter of type `AccountId` and attempts
-    /// to retrieve a list of jobs associated with the provided validator. If successful,
-    /// it constructs a vector of `RpcResponseJobsData` containing information
-    /// about the jobs and returns it as a `Result`.
-    ///
-    /// # Arguments
-    ///
-    /// * `validator` - The account ID of the validator whose jobs are to be queried.
-    ///
-    /// # Returns
-    ///
-    /// An optional vec of `RpcResponseJobsData` of jobs assigned to validator
-    async fn query_jobs_by_validator(
-        &self,
-        at: [u8; 32],
-        validator: AccountId32,
-    ) -> Result<
-        Option<
-            Vec<
-                tangle_runtime::RpcResponseJobsData<
-                    AccountId32,
-                    u64,
-                    MaxParticipants,
-                    MaxSubmissionLen,
-                    MaxAdditionalParamsLen,
-                >,
-            >,
-        >,
-        gadget_common::Error,
-    >;
-    /// Queries a job by its key and ID.
-    ///
-    /// # Arguments
-    ///
-    /// * `role_type` - The role of the job.
-    /// * `job_id` - The ID of the job.
-    ///
-    /// # Returns
-    ///
-    /// An optional `RpcResponseJobsData` containing the account ID of the job.
-    async fn query_job_by_id(
-        &self,
-        at: [u8; 32],
-        role_type: tangle_runtime::RoleType,
-        job_id: u64,
-    ) -> Result<
-        Option<
-            tangle_runtime::RpcResponseJobsData<
-                AccountId32,
-                u64,
-                MaxParticipants,
-                MaxSubmissionLen,
-                MaxAdditionalParamsLen,
-            >,
-        >,
-        gadget_common::Error,
-    >;
+pub type BlockHash = [u8; 32];
 
-    /// Queries the result of a job by its role_type and ID.
-    ///
-    /// # Arguments
-    ///
-    /// * `role_type` - The role of the job.
-    /// * `job_id` - The ID of the job.
-    ///
-    /// # Returns
-    ///
-    /// An `Option` containing the phase one result of the job, wrapped in an `PhaseResult`.
-    async fn query_job_result(
-        &self,
-        at: [u8; 32],
-        role_type: tangle_runtime::RoleType,
-        job_id: u64,
-    ) -> Result<
-        Option<
-            tangle_runtime::PhaseResult<
-                AccountId32,
-                u64,
-                MaxParticipants,
-                MaxKeyLen,
-                MaxDataLen,
-                MaxSignatureLen,
-                MaxSubmissionLen,
-                MaxProofLen,
-                MaxAdditionalParamsLen,
-            >,
-        >,
-        gadget_common::Error,
-    >;
+/// A client for interacting with the services API
+/// TODO: Make this the object that is used for getting services and submitting transactions as necessary.
+pub struct ServicesClient<C: Config> {
+    rpc_client: OnlineClient<C>,
+    #[allow(dead_code)]
+    logger: DebugLogger,
+}
 
-    /// Queries next job ID.
-    ///
-    ///  # Returns
-    ///  Next job ID.
-    async fn query_next_job_id(&self, at: [u8; 32]) -> Result<u64, gadget_common::Error>;
+impl<C: Config> ServicesClient<C> {
+    pub fn new(logger: DebugLogger, rpc_client: OnlineClient<C>) -> Self {
+        Self { logger, rpc_client }
+    }
 
-    /// Queries restaker's role key
-    ///
-    ///  # Returns
-    ///  Role key
-    async fn query_restaker_role_key(
-        &self,
-        at: [u8; 32],
-        address: AccountId32,
-    ) -> Result<Option<Vec<u8>>, gadget_common::Error>;
+    pub fn rpc_client(&self) -> &OnlineClient<C> {
+        &self.rpc_client
+    }
+}
+
+pub type RpcServicesWithBlueprint = services::RpcServicesWithBlueprint<AccountId32, u64>;
 
-    /// Queries restaker's roles
-    ///
-    /// # Returns
-    /// List of roles enabled for restaker
-    async fn query_restaker_roles(
+impl<C: Config> ServicesClient<C>
+where
+    BlockRef<<C as Config>::Hash>: From<BlockRef<H256>>,
+{
+    pub async fn query_operator_blueprints(
         &self,
         at: [u8; 32],
         address: AccountId32,
-    ) -> Result<Vec<tangle_runtime::RoleType>, gadget_common::Error>;
+    ) -> Result<Vec<RpcServicesWithBlueprint>, gadget_common::Error> {
+        let call = api::apis()
+            .services_api()
+            .query_services_with_blueprints_by_operator(address);
+        let at = BlockRef::from_hash(H256::from_slice(&at));
+        let ret: Vec<RpcServicesWithBlueprint> = self
+            .rpc_client
+            .runtime_api()
+            .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:?}"),
+            })?;
+
+        Ok(ret)
+    }
 }
diff --git a/environments/tangle/src/gadget.rs b/environments/tangle/src/gadget.rs
index a7a142729..55ac472f1 100644
--- a/environments/tangle/src/gadget.rs
+++ b/environments/tangle/src/gadget.rs
@@ -1,26 +1,19 @@
-use gadget_common::{
-    tangle_runtime::{AccountId32, MaxAdditionalParamsLen, MaxParticipants, MaxSubmissionLen},
-    tangle_subxt::tangle_testnet_runtime::api::runtime_types::{
-        sp_core::ecdsa,
-        tangle_primitives::{jobs, roles},
-    },
-    WorkManagerInterface,
-};
-use gadget_core::gadget::substrate::FinalityNotification;
-
 use crate::work_manager::TangleWorkManager;
+use gadget_common::tangle_subxt::subxt::events::Events;
+use gadget_common::tangle_subxt::subxt::PolkadotConfig;
+use gadget_common::WorkManagerInterface;
 
-pub type TangleEvent = FinalityNotification;
+#[derive(Clone)]
+pub struct TangleEvent {
+    /// Finalized block number.
+    pub number: u64,
+    /// Finalized block header hash.
+    pub hash: [u8; 32],
+    /// Events
+    pub events: Events<PolkadotConfig>,
+}
 
 pub struct TangleJobMetadata {
-    pub job_type:
-        jobs::JobType<AccountId32, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>,
-    pub role_type: roles::RoleType,
-    /// This value only exists if this is a stage2 job
-    pub phase1_job: Option<
-        jobs::JobType<AccountId32, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>,
-    >,
-    pub participants_role_ids: Vec<ecdsa::Public>,
     pub task_id: <TangleWorkManager as WorkManagerInterface>::TaskID,
     pub retry_id: <TangleWorkManager as WorkManagerInterface>::RetryID,
     pub job_id: u64,
diff --git a/environments/tangle/src/lib.rs b/environments/tangle/src/lib.rs
index 848be3384..040705e09 100644
--- a/environments/tangle/src/lib.rs
+++ b/environments/tangle/src/lib.rs
@@ -9,6 +9,7 @@ use gadget_common::config::DebugLogger;
 use gadget_common::environments::{EventMetadata, GadgetEnvironment};
 use gadget_common::sp_core::serde::Serialize;
 use gadget_common::sp_core::{ecdsa, sr25519};
+use gadget_common::tangle_subxt::subxt::tx::Signer;
 use gadget_common::tangle_subxt::subxt::PolkadotConfig;
 use gadget_common::utils::serialize;
 use gadget_common::WorkManagerInterface;
@@ -32,6 +33,17 @@ pub struct TangleEnvironment {
     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")
@@ -78,7 +90,7 @@ impl GadgetEnvironment for TangleEnvironment {
         }
     }
 
-    async fn setup_client(&self) -> Result<Self::Client, Self::Error> {
+    async fn setup_runtime(&self) -> Result<Self::Client, Self::Error> {
         let subxt_client =
             gadget_common::tangle_subxt::subxt::OnlineClient::<PolkadotConfig>::from_url(
                 &self.subxt_config.endpoint,
@@ -89,18 +101,19 @@ impl GadgetEnvironment for TangleEnvironment {
             })?;
 
         let pair_signer = PairSigner::new(self.account_key.clone());
-        let tx_manager_submitter = SubxtPalletSubmitter::with_client(
-            subxt_client.clone(),
-            pair_signer,
-            self.logger.clone(),
-        );
+        let account_id = pair_signer.account_id();
+        let mut lock = self.tx_manager.lock();
 
-        self.tx_manager
-            .lock()
-            .replace(Arc::new(tx_manager_submitter));
+        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))
-        // let runtime = TangleRuntime::new(runtime.client());
+        Ok(TangleRuntime::new(subxt_client, account_id))
     }
 
     fn transaction_manager(&self) -> Self::TransactionManager {
diff --git a/environments/tangle/src/runtime.rs b/environments/tangle/src/runtime.rs
index 584369e8a..5e1c3b93e 100644
--- a/environments/tangle/src/runtime.rs
+++ b/environments/tangle/src/runtime.rs
@@ -1,22 +1,17 @@
 use std::sync::Arc;
 use std::time::Duration;
 
-use crate::api::ClientWithApi;
 use crate::gadget::TangleEvent;
-use crate::TangleEnvironment;
 use gadget_common::locks::TokioMutexExt;
 use gadget_common::tangle_subxt::subxt::blocks::{Block, BlockRef};
-use gadget_common::tangle_subxt::subxt::ext::futures::TryFutureExt;
 use gadget_common::tangle_subxt::subxt::{self, PolkadotConfig};
-use gadget_common::tangle_subxt::tangle_testnet_runtime::api;
 use gadget_common::{async_trait, tangle_runtime::*};
 use gadget_core::gadget::general::Client;
-use gadget_core::gadget::substrate::FinalityNotification;
 
 pub type TangleConfig = subxt::PolkadotConfig;
-type TangleClient = subxt::OnlineClient<TangleConfig>;
-type TangleBlock = Block<TangleConfig, TangleClient>;
-type TangleBlockStream = subxt::backend::StreamOfResults<TangleBlock>;
+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};
@@ -36,18 +31,19 @@ pub mod crypto {
     }
 }
 
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct TangleRuntime {
     client: subxt::OnlineClient<PolkadotConfig>,
     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<FinalityNotification>>>,
+        Arc<gadget_common::gadget_io::tokio::sync::Mutex<Option<TangleEvent>>>,
+    account_id: AccountId32,
 }
 
 impl TangleRuntime {
     /// Create a new TangleRuntime instance.
-    pub fn new(client: subxt::OnlineClient<PolkadotConfig>) -> Self {
+    pub fn new(client: subxt::OnlineClient<PolkadotConfig>, account_id: AccountId32) -> Self {
         Self {
             client,
             finality_notification_stream: Arc::new(
@@ -56,6 +52,7 @@ impl TangleRuntime {
             latest_finality_notification: Arc::new(
                 gadget_common::gadget_io::tokio::sync::Mutex::new(None),
             ),
+            account_id,
         }
     }
 
@@ -71,18 +68,22 @@ impl TangleRuntime {
         Ok(())
     }
 
-    fn runtime_api(
+    pub fn runtime_api(
         &self,
         at: [u8; 32],
     ) -> subxt::runtime_api::RuntimeApi<TangleConfig, TangleClient> {
         let block_ref = BlockRef::from_hash(sp_core::hash::H256::from_slice(&at));
         self.client.runtime_api().at(block_ref)
     }
+
+    pub fn account_id(&self) -> &AccountId32 {
+        &self.account_id
+    }
 }
 
 #[async_trait::async_trait]
 impl Client<TangleEvent> for TangleRuntime {
-    async fn next_event(&self) -> Option<FinalityNotification> {
+    async fn next_event(&self) -> Option<TangleEvent> {
         let mut lock = self
             .finality_notification_stream
             .try_lock_timeout(Duration::from_millis(500))
@@ -91,9 +92,11 @@ impl Client<TangleEvent> for TangleRuntime {
         match lock.as_mut() {
             Some(stream) => {
                 let block = stream.next().await?.ok()?;
-                let notification = FinalityNotification {
+                let events = block.events().await.ok()?;
+                let notification = TangleEvent {
                     number: block.number().into(),
                     hash: block.hash().into(),
+                    events,
                 };
                 let mut lock2 = self
                     .latest_finality_notification
@@ -111,7 +114,7 @@ impl Client<TangleEvent> for TangleRuntime {
         }
     }
 
-    async fn latest_event(&self) -> Option<FinalityNotification> {
+    async fn latest_event(&self) -> Option<TangleEvent> {
         let lock = self
             .latest_finality_notification
             .try_lock_timeout(Duration::from_millis(500))
@@ -126,145 +129,3 @@ impl Client<TangleEvent> for TangleRuntime {
         }
     }
 }
-
-#[async_trait::async_trait]
-impl ClientWithApi<TangleEnvironment> for TangleRuntime {
-    async fn query_jobs_by_validator(
-        &self,
-        at: [u8; 32],
-        validator: AccountId32,
-    ) -> core::result::Result<
-        Option<
-            Vec<
-                RpcResponseJobsData<
-                    AccountId32,
-                    u64,
-                    MaxParticipants,
-                    MaxSubmissionLen,
-                    MaxAdditionalParamsLen,
-                >,
-            >,
-        >,
-        gadget_common::Error,
-    > {
-        let q = api::apis().jobs_api().query_jobs_by_validator(validator);
-        self.runtime_api(at)
-            .call(q)
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })
-            .await
-    }
-
-    async fn query_job_by_id(
-        &self,
-        at: [u8; 32],
-        role_type: RoleType,
-        job_id: u64,
-    ) -> core::result::Result<
-        Option<
-            RpcResponseJobsData<
-                AccountId32,
-                u64,
-                MaxParticipants,
-                MaxSubmissionLen,
-                MaxAdditionalParamsLen,
-            >,
-        >,
-        gadget_common::Error,
-    > {
-        let q = api::apis().jobs_api().query_job_by_id(role_type, job_id);
-        self.runtime_api(at)
-            .call(q)
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })
-            .await
-    }
-
-    async fn query_job_result(
-        &self,
-        at: [u8; 32],
-        role_type: RoleType,
-        job_id: u64,
-    ) -> core::result::Result<
-        Option<
-            PhaseResult<
-                AccountId32,
-                u64,
-                MaxParticipants,
-                MaxKeyLen,
-                MaxDataLen,
-                MaxSignatureLen,
-                MaxSubmissionLen,
-                MaxProofLen,
-                MaxAdditionalParamsLen,
-            >,
-        >,
-        gadget_common::Error,
-    > {
-        let q = api::apis().jobs_api().query_job_result(role_type, job_id);
-        self.runtime_api(at)
-            .call(q)
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })
-            .await
-    }
-
-    async fn query_next_job_id(
-        &self,
-        at: [u8; 32],
-    ) -> core::result::Result<u64, gadget_common::Error> {
-        let q = api::apis().jobs_api().query_next_job_id();
-        self.runtime_api(at)
-            .call(q)
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })
-            .await
-    }
-
-    async fn query_restaker_role_key(
-        &self,
-        at: [u8; 32],
-        address: AccountId32,
-    ) -> core::result::Result<Option<Vec<u8>>, gadget_common::Error> {
-        let q = api::apis().jobs_api().query_restaker_role_key(address);
-        self.runtime_api(at)
-            .call(q)
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })
-            .await
-    }
-
-    async fn query_restaker_roles(
-        &self,
-        at: [u8; 32],
-        address: AccountId32,
-    ) -> std::result::Result<Vec<RoleType>, gadget_common::Error> {
-        let storage_address = api::storage().roles().account_roles_mapping(address);
-        let block_ref = BlockRef::from_hash(sp_core::hash::H256::from_slice(&at));
-        self.client
-            .storage()
-            .at(block_ref)
-            .fetch_or_default(&storage_address)
-            .map_ok(|v| v.0)
-            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })
-            .await
-    }
-}
-
-#[cfg(test)]
-#[cfg(not(target_family = "wasm"))]
-mod tests {
-    use super::*;
-    use gadget_common::color_eyre::eyre::OptionExt;
-    use gadget_common::gadget_io::tokio;
-
-    #[ignore = "requires a running node"]
-    #[tokio::test]
-    async fn client() -> gadget_common::color_eyre::Result<()> {
-        let subxt_client = subxt::OnlineClient::new().await?;
-        let runtime = TangleRuntime::new(subxt_client);
-
-        let notification = runtime
-            .next_event()
-            .await
-            .ok_or_eyre("Finality notification not found")?;
-        let job_id = runtime.query_next_job_id(notification.hash).await?;
-        eprintln!("Next job id: {}", job_id);
-        Ok(())
-    }
-}
diff --git a/environments/utils/Cargo.toml b/environments/utils/Cargo.toml
index bc671a735..b7bc4fa1a 100644
--- a/environments/utils/Cargo.toml
+++ b/environments/utils/Cargo.toml
@@ -14,8 +14,8 @@ tangle = []
 gadget-common = { workspace = true }
 anyhow = { workspace = true }
 auto_impl = { workspace = true }
+tangle-subxt = { workspace = true }
 
 [dev-dependencies]
 gadget-io = { workspace = true }
-tokio = { workspace = true }
-tangle-subxt = { workspace = true }
\ No newline at end of file
+tokio = { workspace = true }
\ No newline at end of file
diff --git a/environments/utils/src/transaction_manager/tangle/mod.rs b/environments/utils/src/transaction_manager/tangle/mod.rs
index bf8063afc..fe34fc452 100644
--- a/environments/utils/src/transaction_manager/tangle/mod.rs
+++ b/environments/utils/src/transaction_manager/tangle/mod.rs
@@ -1,32 +1,20 @@
 use auto_impl::auto_impl;
 use gadget_common::async_trait::async_trait;
 use gadget_common::config::DebugLogger;
-use gadget_common::tangle_runtime;
-use gadget_common::tangle_runtime::{
-    api, jobs, MaxAdditionalParamsLen, MaxDataLen, MaxKeyLen, MaxParticipants, MaxProofLen,
-    MaxSignatureLen,
-};
+use gadget_common::tangle_runtime::api;
 use gadget_common::tangle_subxt::subxt;
-use gadget_common::tangle_subxt::subxt::tx::TxPayload;
 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_result(
+    async fn submit_job_call_result(
         &self,
-        role_type: tangle_runtime::RoleType,
-        job_id: u64,
-
-        result: jobs::JobResult<
-            MaxParticipants,
-            MaxKeyLen,
-            MaxSignatureLen,
-            MaxDataLen,
-            MaxProofLen,
-            MaxAdditionalParamsLen,
-        >,
+        service_id: u64,
+        call_id: u64,
+        result: api::services::calls::types::submit_result::Result,
     ) -> Result<(), gadget_common::Error>;
 }
 
@@ -55,36 +43,29 @@ where
     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>>::OtherParams:
+    <C::ExtrinsicParams as subxt::config::ExtrinsicParams<C>>::Params:
         Default + Send + Sync + 'static,
 {
-    async fn submit_job_result(
+    async fn submit_job_call_result(
         &self,
-        role_type: tangle_runtime::RoleType,
-        job_id: u64,
-        result: jobs::JobResult<
-            MaxParticipants,
-            MaxKeyLen,
-            MaxSignatureLen,
-            MaxDataLen,
-            MaxProofLen,
-            MaxAdditionalParamsLen,
-        >,
+        service_id: u64,
+        call_id: u64,
+        result: api::services::calls::types::submit_result::Result,
     ) -> Result<(), gadget_common::Error> {
         let tx = api::tx()
-            .jobs()
-            .submit_job_result(role_type, job_id, result);
+            .services()
+            .submit_result(service_id, call_id, result);
         match self.submit(&tx).await {
             Ok(hash) => {
                 self.logger.info(format!(
-                    "({}) Job result submitted for job_id: {job_id} at block: {hash}",
+                    "({}) 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!(
-                    "({}) Job not found for job_id: {job_id}",
+                    "({}) Service not found for service-id/call-id: {service_id}/{call_id}",
                     self.signer.account_id(),
                 ));
                 Ok(())
@@ -104,7 +85,7 @@ where
     C::AccountId: std::fmt::Display,
     S: subxt::tx::Signer<C>,
     C::Hash: std::fmt::Display,
-    <C::ExtrinsicParams as subxt::config::ExtrinsicParams<C>>::OtherParams: Default,
+    <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 =
@@ -124,7 +105,7 @@ where
         }
     }
 
-    async fn submit<Call: TxPayload>(&self, call: &Call) -> anyhow::Result<C::Hash> {
+    async fn submit<Call: Payload>(&self, call: &Call) -> anyhow::Result<C::Hash> {
         if let Some(details) = call.validation_details() {
             self.logger.trace(format!(
                 "({}) Submitting {}.{}",
@@ -140,53 +121,6 @@ where
             .await?
             .wait_for_finalized_success()
             .await?
-            .block_hash())
-    }
-}
-
-#[cfg(test)]
-#[cfg(not(target_family = "wasm"))]
-mod tests {
-
-    use crate::transaction_manager::tangle::SubxtPalletSubmitter;
-    use gadget_common::prelude::*;
-    use gadget_common::subxt_signer;
-    use gadget_common::tangle_subxt::{
-        subxt::{tx::Signer, utils::AccountId32, PolkadotConfig},
-        tangle_testnet_runtime::api,
-        tangle_testnet_runtime::api::runtime_types::{
-            bounded_collections::bounded_vec::BoundedVec,
-            tangle_primitives::{jobs, roles},
-        },
-    };
-
-    #[gadget_io::tokio::test]
-    #[ignore = "This test requires a running general node"]
-    async fn subxt_pallet_submitter() -> anyhow::Result<()> {
-        let logger = DebugLogger { id: "test".into() };
-        let alice = subxt_signer::sr25519::dev::alice();
-        let bob = subxt_signer::sr25519::dev::bob();
-        let alice_account_id =
-            <subxt_signer::sr25519::Keypair as Signer<PolkadotConfig>>::account_id(&alice);
-        let bob_account_id =
-            <subxt_signer::sr25519::Keypair as Signer<PolkadotConfig>>::account_id(&bob);
-        let tx_manager =
-            SubxtPalletSubmitter::<PolkadotConfig, _>::new(alice.clone(), logger).await?;
-        let dkg_phase_one = jobs::JobSubmission {
-            expiry: 100u64,
-            ttl: 100u64,
-            fallback: jobs::FallbackOptions::Destroy,
-            job_type: jobs::JobType::DKGTSSPhaseOne(jobs::tss::DKGTSSPhaseOneJobType {
-                participants: BoundedVec::<AccountId32>(vec![alice_account_id, bob_account_id]),
-                threshold: 1u8,
-                permitted_caller: None,
-                role_type: roles::tss::ThresholdSignatureRoleType::DfnsCGGMP21Secp256k1,
-                hd_wallet: false,
-                __ignore: Default::default(),
-            }),
-        };
-        let tx = api::tx().jobs().submit_job(dkg_phase_one);
-        let _hash = tx_manager.submit(&tx).await?;
-        Ok(())
+            .extrinsic_hash())
     }
 }
diff --git a/shell-manager/src/config.rs b/shell-manager/src/config.rs
index 7cad57eb5..211ae947b 100644
--- a/shell-manager/src/config.rs
+++ b/shell-manager/src/config.rs
@@ -18,7 +18,4 @@ pub struct ShellManagerOpts {
     /// Whether to use pretty logging
     #[structopt(long)]
     pub pretty: bool,
-    /// Wether this is debug mode or not
-    #[structopt(long, short = "t")]
-    pub test: bool,
 }
diff --git a/shell-manager/src/gadget/mod.rs b/shell-manager/src/gadget/mod.rs
new file mode 100644
index 000000000..11d3e475a
--- /dev/null
+++ b/shell-manager/src/gadget/mod.rs
@@ -0,0 +1,7 @@
+use std::collections::HashMap;
+use std::sync::atomic::AtomicBool;
+use std::sync::Arc;
+
+pub type ActiveShells =
+    HashMap<u64, HashMap<u64, (Arc<AtomicBool>, Option<tokio::sync::oneshot::Sender<()>>)>>;
+pub mod native;
diff --git a/shell-manager/src/gadget/native.rs b/shell-manager/src/gadget/native.rs
new file mode 100644
index 000000000..f49af2e76
--- /dev/null
+++ b/shell-manager/src/gadget/native.rs
@@ -0,0 +1,190 @@
+use crate::config::ShellManagerOpts;
+use crate::gadget::ActiveShells;
+use crate::protocols::resolver::NativeGithubMetadata;
+use crate::utils;
+use crate::utils::get_service_str;
+use color_eyre::eyre::OptionExt;
+use gadget_common::prelude::DebugLogger;
+use gadget_io::ShellTomlConfig;
+use std::fmt::Write;
+use tangle_environment::api::RpcServicesWithBlueprint;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::{
+    GadgetBinary, GithubFetcher,
+};
+use tokio::io::AsyncWriteExt;
+
+pub async fn maybe_handle(
+    blueprints: &Vec<RpcServicesWithBlueprint>,
+    onchain_services: &[NativeGithubMetadata],
+    onchain_gh_fetchers: &[&GithubFetcher],
+    shell_config: &ShellTomlConfig,
+    shell_manager_opts: &ShellManagerOpts,
+    active_shells: &mut ActiveShells,
+    logger: &DebugLogger,
+) -> color_eyre::Result<()> {
+    for (gh, fetcher) in onchain_services.iter().zip(onchain_gh_fetchers) {
+        let native_wasm_metadata = NativeGithubMetadata {
+            git: gh.git.clone(),
+            tag: gh.tag.clone(),
+            owner: gh.owner.clone(),
+            repo: gh.repo.clone(),
+            gadget_binaries: fetcher.binaries.0.clone(),
+            blueprint_id: gh.blueprint_id,
+        };
+
+        if let Err(err) = handle_github_source(
+            blueprints,
+            &native_wasm_metadata,
+            shell_config,
+            shell_manager_opts,
+            fetcher,
+            active_shells,
+            logger,
+        )
+        .await
+        {
+            logger.warn(err)
+        }
+    }
+
+    Ok(())
+}
+
+async fn handle_github_source(
+    blueprints: &Vec<RpcServicesWithBlueprint>,
+    service: &NativeGithubMetadata,
+    shell_config: &ShellTomlConfig,
+    shell_manager_opts: &ShellManagerOpts,
+    github: &GithubFetcher,
+    active_shells: &mut ActiveShells,
+    logger: &DebugLogger,
+) -> color_eyre::Result<()> {
+    let blueprint_id = service.blueprint_id;
+    let service_str = get_service_str(service);
+    if !active_shells.contains_key(&blueprint_id) {
+        // Maybe add in the protocol to the active shells
+        let relevant_binary =
+            get_gadget_binary(&github.binaries.0).ok_or_eyre("Unable to find matching binary")?;
+        let expected_hash = slice_32_to_sha_hex_string(relevant_binary.sha256);
+
+        let current_dir = std::env::current_dir()?;
+        let mut binary_download_path =
+            format!("{}/protocol-{:?}", current_dir.display(), github.tag);
+
+        if utils::is_windows() {
+            binary_download_path += ".exe"
+        }
+
+        logger.info(format!("Downloading to {binary_download_path}"));
+
+        // Check if the binary exists, if not download it
+        let retrieved_hash =
+            if !utils::valid_file_exists(&binary_download_path, &expected_hash).await {
+                let url = utils::get_download_url(&service.git, &service.tag);
+
+                let download = reqwest::get(&url)
+                    .await
+                    .map_err(|err| utils::msg_to_error(err.to_string()))?
+                    .bytes()
+                    .await
+                    .map_err(|err| utils::msg_to_error(err.to_string()))?;
+                let retrieved_hash = utils::hash_bytes_to_hex(&download);
+
+                // Write the binary to disk
+                let mut file = tokio::fs::File::create(&binary_download_path).await?;
+                file.write_all(&download).await?;
+                file.flush().await?;
+                Some(retrieved_hash)
+            } else {
+                None
+            };
+
+        if let Some(retrieved_hash) = retrieved_hash {
+            if retrieved_hash.trim() != expected_hash.trim() {
+                logger.error(format!(
+                    "Binary hash {} mismatched expected hash of {} for protocol: {}",
+                    retrieved_hash, expected_hash, service_str
+                ));
+                return Ok(());
+            }
+        }
+
+        if !utils::is_windows() {
+            if let Err(err) = utils::chmod_x_file(&binary_download_path).await {
+                logger.warn(format!("Failed to chmod +x the binary: {err}"));
+            }
+        }
+
+        for blueprint in blueprints {
+            if blueprint.blueprint_id == blueprint_id {
+                for svc in &blueprint.services {
+                    let service_id = svc.id;
+                    let sub_service_str = format!("{service_str}-{service_id}");
+                    // Each spawned binary will effectively run a single "RoleType" in old parlance
+                    let arguments = utils::generate_process_arguments(
+                        shell_config,
+                        shell_manager_opts,
+                        blueprint_id,
+                        service_id,
+                    )?;
+
+                    logger.info(format!("Starting protocol: {sub_service_str}"));
+
+                    // Now that the file is loaded, spawn the process
+                    let process_handle = tokio::process::Command::new(&binary_download_path)
+                        .kill_on_drop(true)
+                        .stdout(std::process::Stdio::inherit()) // Inherit the stdout of this process
+                        .stderr(std::process::Stdio::inherit()) // Inherit the stderr of this process
+                        .stdin(std::process::Stdio::null())
+                        .current_dir(&std::env::current_dir()?)
+                        .envs(std::env::vars().collect::<Vec<_>>())
+                        .args(arguments)
+                        .spawn()?;
+
+                    let (status_handle, abort) = utils::generate_running_process_status_handle(
+                        process_handle,
+                        logger,
+                        &sub_service_str,
+                    );
+
+                    active_shells
+                        .entry(blueprint_id)
+                        .or_default()
+                        .insert(service_id, (status_handle, Some(abort)));
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
+
+fn slice_32_to_sha_hex_string(hash: [u8; 32]) -> String {
+    hash.iter().fold(String::new(), |mut acc, byte| {
+        write!(&mut acc, "{:02x}", byte).expect("Should be able to write");
+        acc
+    })
+}
+
+fn get_gadget_binary(gadget_binaries: &[GadgetBinary]) -> Option<&GadgetBinary> {
+    let os = utils::get_formatted_os_string().to_lowercase();
+    let arch = std::env::consts::ARCH.to_lowercase();
+    for binary in gadget_binaries {
+        let binary_str = format!("{:?}", binary.os).to_lowercase();
+        if binary_str.contains(&os) || os.contains(&binary_str) || binary_str == os {
+            let mut arch_str = format!("{:?}", binary.arch).to_lowercase();
+
+            if arch_str == "amd" {
+                arch_str = "x86".to_string()
+            } else if arch_str == "amd64" {
+                arch_str = "x86_64".to_string()
+            }
+
+            if arch_str == arch {
+                return Some(binary);
+            }
+        }
+    }
+
+    None
+}
diff --git a/shell-manager/src/main.rs b/shell-manager/src/main.rs
index d25ff66f5..2d4a69b52 100644
--- a/shell-manager/src/main.rs
+++ b/shell-manager/src/main.rs
@@ -1,224 +1,105 @@
-use crate::protocols::resolver::{load_global_config_file, str_to_role_type, ProtocolMetadata};
+use crate::gadget::ActiveShells;
+use crate::utils::github_fetcher_to_native_github_metadata;
+use color_eyre::Report;
 use config::ShellManagerOpts;
 use gadget_common::gadget_io;
+use gadget_common::prelude::GadgetEnvironment;
 use gadget_common::sp_core::Pair;
-use gadget_io::tokio::io::AsyncWriteExt;
 use gadget_io::ShellTomlConfig;
 use shell_sdk::entry::keystore_from_base_path;
 use shell_sdk::keystore::load_keys_from_keystore;
-use shell_sdk::Client;
-use shell_sdk::{entry, DebugLogger};
+use shell_sdk::{entry, Client, DebugLogger};
 use std::collections::HashMap;
 use std::sync::atomic::Ordering;
 use structopt::StructOpt;
-use tangle_environment::runtime::TangleRuntime;
-use tangle_subxt::subxt;
+use tangle_environment::api::{RpcServicesWithBlueprint, ServicesClient};
+use tangle_environment::gadget::{SubxtConfig, TangleEvent};
+use tangle_environment::TangleEnvironment;
 use tangle_subxt::subxt::utils::AccountId32;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::GadgetSourceFetcher;
+use tangle_subxt::tangle_testnet_runtime::api::services::events::{
+    JobCalled, JobResultSubmitted, Registered, ServiceInitiated, Unregistered,
+};
 
 pub mod config;
 pub mod error;
+pub mod gadget;
 pub mod protocols;
 pub mod utils;
 
-#[gadget_io::tokio::main]
+#[tokio::main]
 async fn main() -> color_eyre::Result<()> {
     //color_eyre::install()?;
     let opt = &ShellManagerOpts::from_args();
-    let test_mode = opt.test;
-    //    entry::setup_shell_logger(opt.verbose, opt.pretty, "gadget")
-    //        .map_err(|err| msg_to_error(err.to_string()))?;
     entry::setup_shell_logger(opt.verbose, opt.pretty, "gadget")?;
     let shell_config_contents = std::fs::read_to_string(opt.shell_config.clone())?;
     let shell_config: ShellTomlConfig = toml::from_str(&shell_config_contents)
         .map_err(|err| utils::msg_to_error(err.to_string()))?;
-    let global_protocols = load_global_config_file(opt.protocols_config.clone())
-        .map_err(|err| utils::msg_to_error(err.to_string()))?;
     let logger = &DebugLogger {
         id: "Gadget Shell Manager".into(),
     };
 
-    logger.info(format!(
-        "Initializing with {} possible protocols",
-        global_protocols.len()
-    ));
-
-    let subxt_client = subxt::OnlineClient::<subxt::PolkadotConfig>::from_url(&shell_config.url)
-        .await
-        .map_err(|err| utils::msg_to_error(err.to_string()))?;
-    let runtime = TangleRuntime::new(subxt_client);
-
     let keystore = keystore_from_base_path(
         &shell_config.base_path,
         shell_config.chain,
         shell_config.keystore_password.clone(),
     );
+
     let (_role_key, acco_key) = load_keys_from_keystore(&keystore)?;
     let sub_account_id = AccountId32(acco_key.public().0);
+    let subxt_config = SubxtConfig {
+        endpoint: shell_config.url.clone(),
+    };
 
-    let mut active_shells = HashMap::<String, _>::new();
+    let tangle_environment = TangleEnvironment::new(subxt_config, acco_key, logger.clone());
 
-    let manager_task = async move {
-        while let Some(notification) = runtime.next_event().await {
-            logger.info(format!("Received notification {}", notification.number));
-            // TODO: Fetch blueprints instead of role types
-            let onchain_roles = utils::get_subscribed_role_types(
-                &runtime,
-                notification.hash,
-                sub_account_id.clone(),
-                &global_protocols,
-                test_mode,
-            )
-            .await?;
-            logger.trace(format!("OnChain roles: {onchain_roles:?}"));
-            // Check to see if local does not have any running on-chain roles
-            for role in &onchain_roles {
-                let role_str = utils::get_role_type_str(role);
-                if !active_shells.contains_key(&role_str) {
-                    // Add in the protocol
-                    for global_protocol in &global_protocols {
-                        if global_protocol.role_types().contains(&role.clone()) {
-                            let ProtocolMetadata {
-                                role_types: _,
-                                git,
-                                rev,
-                                package,
-                                bin_hashes,
-                            } = global_protocol;
-                            // The hash is sha_256 of the binary
-                            let host_os = utils::get_formatted_os_string();
-                            let expected_hash = bin_hashes.get(&host_os).ok_or_else(|| {
-                                utils::msg_to_error(format!("No hash for this OS ({host_os})"))
-                            })?;
-
-                            let sha_url = utils::get_sha_download_url(git, rev, package);
-
-                            logger.info(format!("Downloading {role_str} SHA from {sha_url}"));
-                            let sha_downloaded = reqwest::get(&sha_url)
-                                .await
-                                .map_err(|err| utils::msg_to_error(err.to_string()))?
-                                .text()
-                                .await
-                                .map_err(|err| utils::msg_to_error(err.to_string()))?;
-
-                            if sha_downloaded.trim() != expected_hash.trim() {
-                                logger.error(format!(
-                                    "Retrieved hash {} mismatches the declared hash {} for protocol: {}",
-                                    sha_downloaded,
-                                    expected_hash,
-                                    role_str
-                                ));
-                                continue;
-                            }
-
-                            let current_dir = std::env::current_dir()?;
-                            let mut binary_download_path =
-                                format!("{}/protocol-{rev}", current_dir.display());
-
-                            if utils::is_windows() {
-                                binary_download_path += ".exe"
-                            }
-
-                            logger.info(format!("Downloading to {binary_download_path}"));
-
-                            // Check if the binary exists, if not download it
-                            let retrieved_hash =
-                                if !utils::valid_file_exists(&binary_download_path, expected_hash)
-                                    .await
-                                {
-                                    let url = utils::get_download_url(git, rev, package);
-
-                                    let download = reqwest::get(&url)
-                                        .await
-                                        .map_err(|err| utils::msg_to_error(err.to_string()))?
-                                        .bytes()
-                                        .await
-                                        .map_err(|err| utils::msg_to_error(err.to_string()))?;
-                                    let retrieved_hash = utils::hash_bytes_to_hex(&download);
-
-                                    // Write the binary to disk
-                                    let mut file =
-                                        gadget_io::tokio::fs::File::create(&binary_download_path)
-                                            .await?;
-                                    file.write_all(&download).await?;
-                                    file.flush().await?;
-                                    Some(retrieved_hash)
-                                } else {
-                                    None
-                                };
-
-                            if let Some(retrieved_hash) = retrieved_hash {
-                                if retrieved_hash.trim() != expected_hash.trim() {
-                                    logger.error(format!(
-                                        "Binary hash {} mismatched expected hash of {} for protocol: {}",
-                                        retrieved_hash,
-                                        expected_hash,
-                                        role_str
-                                    ));
-                                    continue;
-                                }
-                            }
-
-                            if !utils::is_windows() {
-                                if let Err(err) = utils::chmod_x_file(&binary_download_path).await {
-                                    logger.warn(format!("Failed to chmod +x the binary: {err}"));
-                                }
-                            }
-
-                            let arguments = utils::generate_process_arguments(&shell_config, opt)?;
-
-                            logger.info(format!("Starting protocol: {role_str}"));
-
-                            // Now that the file is loaded, spawn the process
-                            let process_handle =
-                                gadget_io::tokio::process::Command::new(&binary_download_path)
-                                    .kill_on_drop(true)
-                                    .stdout(std::process::Stdio::inherit()) // Inherit the stdout of this process
-                                    .stderr(std::process::Stdio::inherit()) // Inherit the stderr of this process
-                                    .stdin(std::process::Stdio::null())
-                                    .current_dir(&std::env::current_dir()?)
-                                    .envs(std::env::vars().collect::<Vec<_>>())
-                                    .args(arguments)
-                                    .spawn()?;
-
-                            let (status_handle, abort) =
-                                utils::generate_running_process_status_handle(
-                                    process_handle,
-                                    logger,
-                                    &role_str,
-                                );
-
-                            active_shells.insert(role_str.clone(), (status_handle, Some(abort)));
-                        }
-                    }
-                }
-            }
+    let tangle_runtime = tangle_environment.setup_runtime().await?;
+    let runtime = ServicesClient::new(logger.clone(), tangle_runtime.client());
+    let mut active_shells = HashMap::new();
 
-            // Check to see if local is running protocols that are not on-chain
-            let mut to_remove = vec![];
-            for (role, process_handle) in &mut active_shells {
-                let role_type = str_to_role_type(role)
-                    .ok_or_else(|| utils::msg_to_error(format!("Invalid role type: {role}")))?;
-                if !onchain_roles.contains(&role_type) {
-                    logger.warn(format!("Killing protocol: {role}"));
-                    if let Some(abort_handle) = process_handle.1.take() {
-                        let _ = abort_handle.send(());
-                    }
+    // With the basics setup, we must now implement the main logic
+    /*
+       * Query to get Vec<RpcServicesWithBlueprint>
+       * For each RpcServicesWithBlueprint, fetch the associated gadget binary (fetch/download)
+        -> If the services field is empty, just emit and log inside the executed binary "that states a new service instance got created by one of these blueprints"
+        -> 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")
+    */
 
-                    to_remove.push(role.clone());
-                }
-            }
+    let (blueprints, init_event) = if let Some(event) = tangle_runtime.next_event().await {
+        (
+            utils::get_blueprints(&runtime, event.hash, sub_account_id.clone()).await?,
+            event,
+        )
+    } else {
+        return Err(Report::msg("Failed to get initial block hash"));
+    };
 
-            // Check to see if any process handles have died
-            for (role, process_handle) in &mut active_shells {
-                if !process_handle.0.load(Ordering::Relaxed) {
-                    // By removing any killed processes, we will auto-restart them on the next finality notification if required
-                    to_remove.push(role.clone());
-                }
-            }
+    logger.info(format!("Received {} blueprints", blueprints.len()));
+    handle_tangle_event(
+        &init_event,
+        &blueprints,
+        logger,
+        &shell_config,
+        opt,
+        &mut active_shells,
+    )
+    .await?;
 
-            for role in to_remove {
-                active_shells.remove(&role);
-            }
+    let manager_task = async move {
+        // Step 2: Listen to FinalityNotifications and poll for new/deleted services that correspond to the blueprints above
+        while let Some(event) = tangle_runtime.next_event().await {
+            handle_tangle_event(
+                &event,
+                &blueprints,
+                logger,
+                &shell_config,
+                opt,
+                &mut active_shells,
+            )
+            .await?;
+
+            handle_tangle_block(event, logger, &mut active_shells, &sub_account_id).await;
         }
 
         Err::<(), _>(utils::msg_to_error("Finality Notification stream died"))
@@ -237,3 +118,200 @@ async fn main() -> color_eyre::Result<()> {
         }
     }
 }
+
+async fn handle_tangle_block(
+    event: TangleEvent,
+    logger: &DebugLogger,
+    active_shells: &mut ActiveShells,
+    account_id: &AccountId32,
+) {
+    let registered_events = event.events.find::<Registered>();
+    let unregistered_events = event.events.find::<Unregistered>();
+    let service_initiated_events = event.events.find::<ServiceInitiated>();
+    let job_called_events = event.events.find::<JobCalled>();
+    let job_result_submitted_events = event.events.find::<JobResultSubmitted>();
+
+    // Handle registered events
+    for evt in registered_events {
+        match evt {
+            Ok(evt) => {
+                logger.info(format!("Registered event: {evt:?}"));
+            }
+            Err(err) => {
+                logger.warn(format!("Error handling registered event: {err:?}"));
+            }
+        }
+    }
+
+    // Handle unregistered events
+    for evt in unregistered_events {
+        match evt {
+            Ok(evt) => {
+                logger.info(format!("Unregistered event: {evt:?}"));
+                if &evt.operator == account_id && active_shells.remove(&evt.blueprint_id).is_some()
+                {
+                    logger.info(format!(
+                        "Removed all services for blueprint_id: {}",
+                        evt.blueprint_id,
+                    ));
+                }
+            }
+            Err(err) => {
+                logger.warn(format!("Error handling unregistered event: {err:?}"));
+            }
+        }
+    }
+
+    // Handle service initiated events
+    for evt in service_initiated_events {
+        match evt {
+            Ok(evt) => {
+                logger.info(format!("Service initiated event: {evt:?}"));
+            }
+            Err(err) => {
+                logger.warn(format!("Error handling service initiated event: {err:?}"));
+            }
+        }
+    }
+
+    // Handle job called events
+    for evt in job_called_events {
+        match evt {
+            Ok(evt) => {
+                logger.info(format!("Job called event: {evt:?}"));
+            }
+            Err(err) => {
+                logger.warn(format!("Error handling job called event: {err:?}"));
+            }
+        }
+    }
+
+    // Handle job result submitted events
+    for evt in job_result_submitted_events {
+        match evt {
+            Ok(evt) => {
+                logger.info(format!("Job result submitted event: {evt:?}"));
+            }
+            Err(err) => {
+                logger.warn(format!(
+                    "Error handling job result submitted event: {err:?}"
+                ));
+            }
+        }
+    }
+}
+
+async fn handle_tangle_event(
+    event: &TangleEvent,
+    blueprints: &Vec<RpcServicesWithBlueprint>,
+    logger: &DebugLogger,
+    shell_config: &ShellTomlConfig,
+    shell_manager_opts: &ShellManagerOpts,
+    active_shells: &mut ActiveShells,
+) -> color_eyre::Result<()> {
+    logger.info(format!("Received notification {}", event.number));
+    // TODO: Refactor into Vec<SourceMetadata<T>> where T: NativeGithubMetadata + [...]
+    let mut onchain_services = vec![];
+    let mut fetchers = vec![];
+    let mut service_ids = vec![];
+
+    for blueprint in blueprints {
+        let mut services_for_this_blueprint = vec![];
+        if let runtime_types::tangle_primitives::services::Gadget::Native(gadget) =
+            &blueprint.blueprint.gadget
+        {
+            let gadget_source = &gadget.soruces.0[0];
+            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);
+
+                for service in &blueprint.services {
+                    services_for_this_blueprint.push(service.id);
+                }
+            } else {
+                logger.warn(
+                    "Blueprint does not contain a Github fetcher and thus currently unsupported",
+                );
+            }
+
+            service_ids.push(services_for_this_blueprint);
+        } else {
+            logger
+                .warn("Blueprint does not contain a native gadget and thus currently unsupported");
+        }
+    }
+
+    logger.trace(format!(
+        "OnChain services: {:?}",
+        onchain_services
+            .iter()
+            .map(|r| r.git.clone())
+            .collect::<Vec<_>>()
+    ));
+
+    // Step 3: Check to see if we need to start any new services
+    gadget::native::maybe_handle(
+        blueprints,
+        &onchain_services,
+        &fetchers,
+        shell_config,
+        shell_manager_opts,
+        active_shells,
+        logger,
+    )
+    .await?;
+
+    // Check to see if local is running services that are not on-chain
+    let mut to_remove: Vec<(u64, u64)> = vec![];
+
+    // Loop through every (blueprint_id, service_id) running. See if the service is still on-chain. If not, kill it and add it to to_remove
+    for (blueprint_id, process_handles) in &mut *active_shells {
+        for (service_id, process_handle) in process_handles {
+            if !onchain_services
+                .iter()
+                .zip(&service_ids)
+                .all(|(r, s)| r.blueprint_id != *blueprint_id && !s.contains(service_id))
+            {
+                logger.warn(format!(
+                    "Killing service that is no longer on-chain: bid={blueprint_id}//sid={service_id}",
+                ));
+                if let Some(abort_handle) = process_handle.1.take() {
+                    if abort_handle.send(()).is_err() {
+                        logger.error(format!(
+                            "Failed to send abort signal to service: bid={blueprint_id}//sid={service_id}",
+                        ));
+                    }
+                }
+
+                to_remove.push((*blueprint_id, *service_id));
+            }
+        }
+    }
+
+    // Check to see if any process handles have died
+    for (blueprint_id, process_handles) in &mut *active_shells {
+        for (service_id, process_handle) in process_handles {
+            if !process_handle.0.load(Ordering::Relaxed) {
+                // By removing any killed processes, we will auto-restart them on the next finality notification if required
+                to_remove.push((*blueprint_id, *service_id));
+            }
+        }
+    }
+
+    for (blueprint_id, service_id) in to_remove {
+        let mut should_delete_blueprint = false;
+        if let Some(shells) = active_shells.get_mut(&blueprint_id) {
+            shells.remove(&service_id);
+            if shells.is_empty() {
+                should_delete_blueprint = true;
+            }
+        }
+
+        if should_delete_blueprint {
+            active_shells.remove(&blueprint_id);
+        }
+    }
+
+    Ok(())
+}
diff --git a/shell-manager/src/protocols/config.rs b/shell-manager/src/protocols/config.rs
index 2ad9d1eef..84e04104c 100644
--- a/shell-manager/src/protocols/config.rs
+++ b/shell-manager/src/protocols/config.rs
@@ -9,7 +9,6 @@ pub struct ProtocolConfig {
 #[derive(Debug, Serialize, Deserialize)]
 pub struct ProtocolToml {
     pub package: String,
-    pub role_types: Vec<String>,
     pub repository: Option<HashMap<String, String>>,
     pub bin_hashes: Option<HashMap<String, String>>,
 }
diff --git a/shell-manager/src/protocols/resolver.rs b/shell-manager/src/protocols/resolver.rs
index 7822a5de3..405c39128 100644
--- a/shell-manager/src/protocols/resolver.rs
+++ b/shell-manager/src/protocols/resolver.rs
@@ -1,123 +1,11 @@
-use crate::error::Error;
-use crate::protocols::config::ProtocolConfig;
-use std::collections::HashMap;
-use std::path::Path;
-use tangle_subxt::tangle_testnet_runtime::api::jobs::events::job_submitted::RoleType;
-use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::roles::tss::ThresholdSignatureRoleType;
-use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::roles::zksaas::ZeroKnowledgeRoleType;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::GadgetBinary;
 
 #[derive(Debug)]
-pub struct ProtocolMetadata {
-    pub role_types: Vec<RoleType>,
+pub struct NativeGithubMetadata {
     pub git: String,
-    pub rev: String,
-    pub package: String,
-    pub bin_hashes: HashMap<String, String>,
-}
-
-impl ProtocolMetadata {
-    pub fn role_types(&self) -> &Vec<RoleType> {
-        &self.role_types
-    }
-}
-
-pub fn load_global_config_file<P: AsRef<Path>>(path: P) -> Result<Vec<ProtocolMetadata>, Error> {
-    let config: ProtocolConfig = toml::from_str(&std::fs::read_to_string(path)?)
-        .map_err(|err| Error::msg(err.to_string()))?;
-    let mut ret = vec![];
-
-    for protocol in config.protocols {
-        if protocol.role_types.is_empty() {
-            return Err(Error::msg(
-                "Protocol does not have any role types specified",
-            ));
-        }
-
-        if let (Some(bin_hashes), Some(repository)) = (protocol.bin_hashes, protocol.repository) {
-            if bin_hashes.is_empty() {
-                return Err(Error::msg(
-                    "External protocol does not have any binaries hashes specified",
-                ));
-            }
-            let git = repository.get("git").ok_or(Error::msg(
-                "External protocol does not have a git repository specified",
-            ))?;
-            let rev = repository.get("rev").cloned().ok_or(Error::msg(
-                "External protocol does not have a revision specified",
-            ))?;
-            ret.push(ProtocolMetadata {
-                git: git.clone(),
-                role_types: convert_str_vec_to_role_types(protocol.role_types)?,
-                rev,
-                bin_hashes,
-                package: protocol.package,
-            })
-        } else {
-            return Err(Error::msg(format!(
-                "External protocol does not have bin_hashes and a repository specified: {:?}",
-                protocol.role_types
-            )));
-        }
-    }
-
-    Ok(ret)
-}
-
-pub fn convert_str_vec_to_role_types(input: Vec<String>) -> Result<Vec<RoleType>, Error> {
-    let mut ret = vec![];
-    for role in input {
-        ret.push(
-            str_to_role_type(&role)
-                .ok_or_else(|| Error::msg(format!("Unregistered RoleType: {role:?}")))?,
-        );
-    }
-
-    Ok(ret)
-}
-
-pub fn str_to_role_type<T: AsRef<str>>(role_type: T) -> Option<RoleType> {
-    let role_type = role_type.as_ref();
-    match role_type {
-        // ZkSaaS types
-        "ZkSaaSGroth16" => Some(RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16)),
-        "ZkSaaSMarlin" => Some(RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSMarlin)),
-
-        // DFNS
-        "DfnsCGGMP21Secp256k1" => Some(RoleType::Tss(
-            ThresholdSignatureRoleType::DfnsCGGMP21Secp256k1,
-        )),
-        "DfnsCGGMP21Secp256r1" => Some(RoleType::Tss(
-            ThresholdSignatureRoleType::DfnsCGGMP21Secp256r1,
-        )),
-        "DfnsCGGMP21Stark" => Some(RoleType::Tss(ThresholdSignatureRoleType::DfnsCGGMP21Stark)),
-
-        // Silent Shard
-        "SilentShardDKLS23Secp256k1" => Some(RoleType::Tss(
-            ThresholdSignatureRoleType::SilentShardDKLS23Secp256k1,
-        )),
-
-        // ZcashFrost
-        "ZcashFrostP256" => Some(RoleType::Tss(ThresholdSignatureRoleType::ZcashFrostP256)),
-        "ZcashFrostP384" => Some(RoleType::Tss(ThresholdSignatureRoleType::ZcashFrostP384)),
-        "ZcashFrostSecp256k1" => Some(RoleType::Tss(
-            ThresholdSignatureRoleType::ZcashFrostSecp256k1,
-        )),
-        "ZcashFrostRistretto255" => Some(RoleType::Tss(
-            ThresholdSignatureRoleType::ZcashFrostRistretto255,
-        )),
-        "ZcashFrostEd25519" => Some(RoleType::Tss(ThresholdSignatureRoleType::ZcashFrostEd25519)),
-        "ZcashFrostEd448" => Some(RoleType::Tss(ThresholdSignatureRoleType::ZcashFrostEd448)),
-
-        // BLS
-        "GennaroDKGBls381" => Some(RoleType::Tss(ThresholdSignatureRoleType::GennaroDKGBls381)),
-
-        // WSTS
-        "WstsV2" => Some(RoleType::Tss(ThresholdSignatureRoleType::WstsV2)),
-
-        // Relaying
-        "LightClientRelaying" => Some(RoleType::LightClientRelaying),
-
-        // Faulty types
-        _ => None,
-    }
+    pub tag: String,
+    pub owner: String,
+    pub repo: String,
+    pub gadget_binaries: Vec<GadgetBinary>,
+    pub blueprint_id: u64,
 }
diff --git a/shell-manager/src/utils.rs b/shell-manager/src/utils.rs
index db0c492e5..0d3552e96 100644
--- a/shell-manager/src/utils.rs
+++ b/shell-manager/src/utils.rs
@@ -1,39 +1,56 @@
 use crate::config::ShellManagerOpts;
-use crate::protocols::resolver::ProtocolMetadata;
+use crate::protocols::resolver::NativeGithubMetadata;
 use gadget_common::config::DebugLogger;
+use gadget_common::sp_core::H256;
 use gadget_common::tangle_runtime::AccountId32;
 use gadget_io::{defaults, ShellTomlConfig};
 use sha2::Digest;
 use std::path::Path;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
-use tangle_environment::api::ClientWithApi;
-use tangle_environment::runtime::TangleRuntime;
-use tangle_subxt::tangle_testnet_runtime::api::jobs::events::job_refunded::RoleType;
+use tangle_environment::api::{RpcServicesWithBlueprint, ServicesClient};
+use tangle_subxt::subxt::backend::BlockRef;
+use tangle_subxt::subxt::Config;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::GithubFetcher;
 
-pub async fn get_subscribed_role_types(
-    runtime: &TangleRuntime,
+pub async fn get_blueprints<C: Config>(
+    runtime: &ServicesClient<C>,
     block_hash: [u8; 32],
     account_id: AccountId32,
-    global_protocols: &[ProtocolMetadata],
-    test_mode: bool,
-) -> color_eyre::Result<Vec<RoleType>> {
-    if test_mode {
-        return Ok(global_protocols
-            .iter()
-            .flat_map(|r| r.role_types().clone())
-            .collect());
-    }
-
+) -> color_eyre::Result<Vec<RpcServicesWithBlueprint>>
+where
+    BlockRef<<C as Config>::Hash>: From<BlockRef<H256>>,
+{
     runtime
-        .query_restaker_roles(block_hash, account_id)
+        .query_operator_blueprints(block_hash, account_id)
         .await
         .map_err(|err| msg_to_error(err.to_string()))
 }
 
+pub fn github_fetcher_to_native_github_metadata(
+    gh: &GithubFetcher,
+    blueprint_id: u64,
+) -> NativeGithubMetadata {
+    let owner = bytes_to_utf8_string(gh.owner.0 .0.clone()).expect("Should be valid");
+    let repo = bytes_to_utf8_string(gh.repo.0 .0.clone()).expect("Should be valid");
+    let tag = bytes_to_utf8_string(gh.tag.0 .0.clone()).expect("Should be valid");
+    let git = format!("https://github.com/{owner}/{repo}");
+
+    NativeGithubMetadata {
+        git,
+        tag,
+        repo,
+        owner,
+        gadget_binaries: gh.binaries.0.clone(),
+        blueprint_id,
+    }
+}
+
 pub fn generate_process_arguments(
     shell_config: &ShellTomlConfig,
     opt: &ShellManagerOpts,
+    blueprint_id: u64,
+    service_id: u64,
 ) -> color_eyre::Result<Vec<String>> {
     let mut arguments = vec![
         format!("--bind-ip={}", shell_config.bind_ip),
@@ -54,6 +71,8 @@ pub fn generate_process_arguments(
                 .clone()
                 .unwrap_or_else(|| { hex::encode(defaults::generate_node_key()) })
         ),
+        format!("--blueprint-id={}", blueprint_id),
+        format!("--service-id={}", service_id),
         format!("--base-path={}", shell_config.base_path.display()),
         format!("--chain={}", shell_config.chain.to_string()),
         format!("--verbose={}", opt.verbose),
@@ -95,7 +114,7 @@ pub fn get_formatted_os_string() -> String {
     }
 }
 
-pub fn get_download_url<T: Into<String>>(git: T, rev: &str, package: &str) -> String {
+pub fn get_download_url<T: Into<String>>(git: T, tag: &str) -> String {
     let os = get_formatted_os_string();
     let arch = std::env::consts::ARCH;
 
@@ -111,27 +130,18 @@ pub fn get_download_url<T: Into<String>>(git: T, rev: &str, package: &str) -> St
     }
 
     let ext = if os == "windows" { ".exe" } else { "" };
-
-    // https://github.com/webb-tools/protocols/releases/download/protocol-aarch64-apple-darwin-d1cc78ee4652696a2228f62b735e55ef23bf3f8e/protocol-threshold-bls-protocol-d1cc78ee4652696a2228f62b735e55ef23bf3f8e.sha256
-    // https://github.com/webb-tools/protocols/releases/download/aarch64-apple-darwin-d1cc78ee4652696a2228f62b735e55ef23bf3f8e/protocol-threshold-bls-protocol-d1cc78ee4652696a2228f62b735e55ef23bf3f8e.sha256
-    format!("{git}releases/download/{arch}-{os}-{rev}/protocol-{package}-{rev}{ext}")
-}
-
-pub fn get_sha_download_url<T: Into<String>>(git: T, rev: &str, package: &str) -> String {
-    let base_url = get_download_url(git, rev, package);
-    format!("{base_url}.sha256")
+    // https://github.com/<owner>/<repo>/releases/download/v<tag>/<path>
+    format!("{git}releases/download/v{tag}/protocol-{os}-{arch}{ext}")
 }
 
 pub fn msg_to_error<T: Into<String>>(msg: T) -> color_eyre::Report {
     color_eyre::Report::msg(msg.into())
 }
 
-pub fn get_role_type_str(role_type: &RoleType) -> String {
-    match role_type {
-        RoleType::Tss(tss) => format!("{tss:?}"),
-        RoleType::ZkSaaS(zksaas) => format!("{zksaas:?}"),
-        RoleType::LightClientRelaying => format!("{role_type:?}"),
-    }
+pub fn get_service_str(svc: &NativeGithubMetadata) -> String {
+    let repo = svc.git.clone();
+    let vals: Vec<&str> = repo.split(".com/").collect();
+    vals[1].to_string()
 }
 
 pub async fn chmod_x_file<P: AsRef<Path>>(path: P) -> color_eyre::Result<()> {
@@ -185,3 +195,7 @@ pub fn generate_running_process_status_handle(
     gadget_io::tokio::spawn(task);
     (status, stop_tx)
 }
+
+pub fn bytes_to_utf8_string<T: Into<Vec<u8>>>(input: T) -> color_eyre::Result<String> {
+    String::from_utf8(input.into()).map_err(|err| msg_to_error(err.to_string()))
+}
diff --git a/shell-sdk/Cargo.toml b/shell-sdk/Cargo.toml
index 8397a6ac9..249777758 100644
--- a/shell-sdk/Cargo.toml
+++ b/shell-sdk/Cargo.toml
@@ -83,10 +83,7 @@ itertools = { workspace = true }
 env_logger = { workspace = true }
 
 # Pallets
-pallet-jobs-rpc-runtime-api = { workspace = true, features = ["default"] }
-pallet-jobs = { workspace = true, features = ["default"] }
-pallet-dkg = { workspace = true }
-pallet-zksaas = { workspace = true }
+pallet-services = { workspace = true }
 tangle-primitives = { workspace = true }
 
 [target.'cfg(not(target_family = "wasm"))'.dependencies.libp2p]
diff --git a/shell-sdk/src/config.rs b/shell-sdk/src/config.rs
index 5e4d0107b..1ca6cb377 100644
--- a/shell-sdk/src/config.rs
+++ b/shell-sdk/src/config.rs
@@ -3,7 +3,7 @@ pub use gadget_io::KeystoreConfig;
 use libp2p::Multiaddr;
 use std::net::IpAddr;
 use std::path::PathBuf;
-use tangle_subxt::tangle_testnet_runtime::api::jobs::events::job_refunded::RoleType;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
 
 #[derive(Debug)]
 pub struct ShellConfig<KBE: Clone, Env: GadgetEnvironment> {
@@ -14,7 +14,7 @@ pub struct ShellConfig<KBE: Clone, Env: GadgetEnvironment> {
     pub bind_port: u16,
     pub bootnodes: Vec<Multiaddr>,
     pub node_key: [u8; 32],
-    pub role_types: Vec<RoleType>,
+    pub services: Vec<ServiceBlueprint>,
     pub n_protocols: usize,
     pub keystore_backend: KBE,
 }
diff --git a/shell-sdk/src/entry.rs b/shell-sdk/src/entry.rs
index 755d0f298..167d59d2f 100644
--- a/shell-sdk/src/entry.rs
+++ b/shell-sdk/src/entry.rs
@@ -4,7 +4,7 @@ use gadget_common::prelude::{DebugLogger, KeystoreBackend};
 use gadget_core::job_manager::SendFuture;
 use gadget_io::{defaults, ShellTomlConfig, SupportedChains};
 use structopt::StructOpt;
-use tangle_subxt::tangle_testnet_runtime::api::jobs::events::job_refunded::RoleType;
+use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
 use tracing_subscriber::fmt::SubscriberBuilder;
 use tracing_subscriber::util::SubscriberInitExt;
 use tracing_subscriber::EnvFilter;
@@ -33,7 +33,7 @@ pub async fn run_shell_for_protocol<
     F2: SendFuture<'static, KBE>,
 >(
     environment: Env,
-    role_types: Vec<RoleType>,
+    services: Vec<ServiceBlueprint>,
     n_protocols: usize,
     keystore_backend: T2,
     executor: T,
@@ -64,7 +64,7 @@ where
 
     let (node_input, network_handle) = crate::generate_node_input(crate::ShellConfig {
         keystore_backend,
-        role_types,
+        services,
         keystore,
         environment,
         base_path: config.base_path,
diff --git a/shell-sdk/src/lib.rs b/shell-sdk/src/lib.rs
index f92be8c59..f13964a2b 100644
--- a/shell-sdk/src/lib.rs
+++ b/shell-sdk/src/lib.rs
@@ -31,20 +31,11 @@ pub mod prelude {
 
     pub use gadget_common::prelude::InMemoryBackend;
     pub use gadget_common::tangle_runtime::AccountId32;
-    pub use gadget_common::tangle_runtime::MaxAdditionalParamsLen;
-    pub use gadget_common::tangle_runtime::MaxParticipants;
-    pub use gadget_common::tangle_runtime::MaxSubmissionLen;
-    pub use gadget_common::tangle_runtime::{jobs, RoleType};
     pub use gadget_common::Error;
     pub use gadget_common::{generate_protocol, generate_setup_and_run_command};
     pub use std::sync::Arc;
-    pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::roles;
 
     pub use color_eyre;
-    pub use pallet_dkg;
-    pub use pallet_jobs;
-    pub use pallet_jobs_rpc_runtime_api;
-    pub use pallet_zksaas;
     pub use tangle_primitives;
     pub use tangle_subxt;
 }
diff --git a/shell-sdk/src/network/gossip.rs b/shell-sdk/src/network/gossip.rs
index 2ca0da2c2..3fca8e0f0 100644
--- a/shell-sdk/src/network/gossip.rs
+++ b/shell-sdk/src/network/gossip.rs
@@ -468,7 +468,7 @@ mod tests {
                 bootnodes,
                 base_path: std::path::PathBuf::new(),
                 node_key: [0u8; 32],
-                role_types: vec![RoleType::LightClientRelaying], // Dummy value
+                services: vec![RoleType::LightClientRelaying], // Dummy value
                 n_protocols: 1,
                 keystore_backend: InMemoryBackend::default(),
             };
diff --git a/shell-sdk/src/shell.rs b/shell-sdk/src/shell.rs
index 1c47521a5..53a8b4bd1 100644
--- a/shell-sdk/src/shell.rs
+++ b/shell-sdk/src/shell.rs
@@ -39,7 +39,7 @@ pub async fn generate_node_input<KBE: KeystoreBackend, Env: GadgetEnvironment>(
 
     // Create a network for each subprotocol in the protocol (e.g., keygen, signing, refresh, rotate, = 4 total subprotocols = n_protocols)
     let network_ids = (0..config.n_protocols)
-        .map(|_| format!("{:?}", config.role_types[0]))
+        .map(|_| format!("{:?}", config.services[0]))
         .map(|r| keccak_256(r.as_bytes()))
         .map(hex::encode)
         .enumerate()
@@ -116,7 +116,7 @@ where
     KBE: KeystoreBackend,
 {
     let runtime = env
-        .setup_client()
+        .setup_runtime()
         .await
         .map_err(|err| color_eyre::Report::msg(format!("Failed to setup runtime: {err}")))?;
     let tx_manager = env.transaction_manager();


Test patch:
diff --git a/tangle-test-utils/Cargo.toml b/tangle-test-utils/Cargo.toml
index 7dbfc2c70..e0aace3fa 100644
--- a/tangle-test-utils/Cargo.toml
+++ b/tangle-test-utils/Cargo.toml
@@ -22,12 +22,8 @@ std = [
     "tangle-primitives/default",
     "sc-client-api",
     "sc-utils/default",
-    "pallet-jobs/default",
-    "pallet-dkg/default",
-    "pallet-zksaas/default",
+    "pallet-services/default",
     "pallet-balances/default",
-    "pallet-timestamp/default",
-    "pallet-jobs-rpc-runtime-api/default"
 ]
 wasm = [
     "sp-core/serde",
@@ -56,7 +52,6 @@ gadget-common = { workspace = true, default-features = false }
 gadget-core = { workspace = true }
 gadget-io = { workspace = true, default-features = false }
 tangle-environment.workspace = true
-pallet-jobs-rpc-runtime-api = { workspace = true }
 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 }
@@ -65,14 +60,20 @@ sp-runtime = { workspace = true, default-features = false }
 sp-keystore = { workspace = true, default-features = false }
 sp-std = { workspace = true, default-features = false }
 sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] }
-pallet-balances = { workspace = true }
-pallet-timestamp = { workspace = true }
-pallet-jobs = { workspace = true }
-pallet-dkg = { workspace = true}
-pallet-zksaas = { workspace = true }
 sp-externalities = { workspace = true, default-features = false }
 tangle-primitives = { workspace = true, features = ["verifying"] }
 scale-info = { workspace = true, default-features = false, features = ["derive"] }
 parity-scale-codec = { workspace = true }
 serde = { workspace = true }
 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
+sp-npos-elections = { workspace = true }
+sp-session = { workspace = true }
+
+# Pallets
+pallet-timestamp = { workspace = true }
+pallet-balances = { workspace = true }
+pallet-services = { workspace = true }
+pallet-session = { workspace = true }
+pallet-staking = { workspace = true }
+pallet-evm = { workspace = true }
+sp-staking = { workspace = true }
diff --git a/tangle-test-utils/src/lib.rs b/tangle-test-utils/src/lib.rs
index 8f8ea762e..adddc5ddf 100644
--- a/tangle-test-utils/src/lib.rs
+++ b/tangle-test-utils/src/lib.rs
@@ -2,7 +2,6 @@ use crate::mock::{Jobs, Runtime};
 use crate::sync::substrate_test_channel::MultiThreadedTestExternalities;
 pub use gadget_core::job_manager::SendFuture;
 pub use log;
-use pallet_jobs::{SubmittedJobs, SubmittedJobsRole};
 use std::time::Duration;
 use tangle_primitives::jobs::{JobId, PhaseResult};
 use tangle_primitives::roles::RoleType;
diff --git a/tangle-test-utils/src/mock.rs b/tangle-test-utils/src/mock.rs
index bce3b399c..08c0e6cf8 100644
--- a/tangle-test-utils/src/mock.rs
+++ b/tangle-test-utils/src/mock.rs
@@ -14,9 +14,12 @@
 // You should have received a copy of the GNU General Public License
 // along with Tangle.  If not, see <http://www.gnu.org/licenses/>.
 
+use crate::mock::mock_wrapper_client::{MockClient, TestExternalitiesPalletSubmitter};
+use crate::sync::substrate_test_channel::MultiThreadedTestExternalities;
 use async_trait::async_trait;
+use environment_utils::transaction_manager::tangle::TanglePalletSubmitter;
 use frame_support::pallet_prelude::*;
-use frame_support::traits::Hooks;
+use frame_support::traits::{Contains, Hooks, OneSessionHandler};
 use frame_support::{
     construct_runtime, parameter_types,
     traits::{ConstU128, ConstU32, ConstU64, Everything},
@@ -24,15 +27,30 @@ use frame_support::{
 };
 use frame_system::EnsureSigned;
 use gadget_common::config::Network;
-use pallet_jobs_rpc_runtime_api::BlockNumberOf;
+use gadget_common::locks::TokioMutexExt;
+use gadget_common::prelude::{
+    DebugLogger, ECDSAKeyStore, EventMetadata, GadgetEnvironment, InMemoryBackend, NodeInput,
+    PrometheusConfig, UnboundedReceiver, UnboundedSender,
+};
+use gadget_common::tangle_subxt::subxt::OfflineClient;
+use gadget_common::utils::serialize;
+use gadget_common::Error;
+use gadget_core::job_manager::{ProtocolMessageMetadata, SendFuture, WorkManagerInterface};
+use pallet_services::EvmGasWeightMapping;
 use sc_client_api::{FinalityNotification, FinalizeSummary};
 use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
 use serde::{Deserialize, Serialize};
 use sp_api::{ApiRef, ProvideRuntimeApi};
+use sp_application_crypto::ecdsa;
 use sp_core::{sr25519, ByteArray, Pair, H256};
+use sp_keystore::testing::MemoryKeystore;
+use sp_keystore::{KeystoreExt, KeystorePtr};
+use sp_runtime::testing::UintAuthorityId;
+use sp_runtime::traits::ConvertInto;
 use sp_runtime::RuntimeDebug;
 use sp_runtime::{traits::Block as BlockT, traits::IdentityLookup, BuildStorage, DispatchResult};
 use std::collections::HashMap;
+use std::sync::Arc;
 use std::time::Duration;
 use tangle_environment::gadget::{TangleEvent, TangleJobMetadata};
 use tangle_environment::message::{TangleProtocolMessage, UserID};
@@ -42,39 +60,6 @@ use tangle_primitives::AccountId;
 pub type Balance = u128;
 pub type BlockNumber = u64;
 
-pub use crate::mock::mock_wrapper_client::{MockClient, TestExternalitiesPalletSubmitter};
-use crate::sync::substrate_test_channel::MultiThreadedTestExternalities;
-use environment_utils::transaction_manager::tangle::TanglePalletSubmitter;
-use gadget_common::debug_logger::DebugLogger;
-use gadget_common::environments::{EventMetadata, GadgetEnvironment};
-use gadget_common::full_protocol::NodeInput;
-use gadget_common::keystore::{ECDSAKeyStore, InMemoryBackend};
-use gadget_common::locks::TokioMutexExt;
-use gadget_common::prelude::PrometheusConfig;
-use gadget_common::utils::serialize;
-use gadget_common::Error;
-use gadget_core::job_manager::{ProtocolMessageMetadata, SendFuture, WorkManagerInterface};
-use gadget_io::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
-use sp_core::ecdsa;
-use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr};
-use sp_std::sync::Arc;
-use tangle_primitives::jobs::traits::{JobToFee, MPCHandler};
-use tangle_primitives::jobs::{
-    JobId, JobResult, JobSubmission, JobType, JobWithResult, PhaseResult, ReportRestakerOffence,
-    RpcResponseJobsData, ValidatorOffenceType,
-};
-use tangle_primitives::misbehavior::{MisbehaviorHandler, MisbehaviorSubmission};
-use tangle_primitives::roles::traits::RolesHandler;
-use tangle_primitives::roles::RoleType;
-use tangle_primitives::verifier::{
-    arkworks::ArkworksVerifierGroth16Bn254, circom::CircomVerifierGroth16Bn254,
-};
-
-/// Key type for DKG keys
-pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"role");
-
-pub type Block = frame_system::mocking::MockBlock<Runtime>;
-
 impl frame_system::Config for Runtime {
     type RuntimeOrigin = RuntimeOrigin;
     type Nonce = u64;
@@ -103,291 +88,318 @@ impl frame_system::Config for Runtime {
 }
 
 impl pallet_balances::Config for Runtime {
-    type RuntimeEvent = RuntimeEvent;
-    type WeightInfo = ();
     type Balance = Balance;
     type DustRemoval = ();
+    type RuntimeEvent = RuntimeEvent;
     type ExistentialDeposit = ConstU128<1>;
     type AccountStore = System;
+    type MaxLocks = ();
+    type MaxReserves = ConstU32<50>;
     type ReserveIdentifier = ();
+    type WeightInfo = ();
     type RuntimeHoldReason = RuntimeHoldReason;
+    type RuntimeFreezeReason = ();
     type FreezeIdentifier = ();
-    type MaxLocks = ();
-    type MaxReserves = ConstU32<50>;
     type MaxFreezes = ();
-    type RuntimeFreezeReason = ();
 }
 
-impl pallet_timestamp::Config for Runtime {
-    type Moment = u64;
-    type OnTimestampSet = ();
-    type MinimumPeriod = ();
-    type WeightInfo = ();
+parameter_types! {
+    pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default()
+        .voters_count(5_000.into()).targets_count(1_250.into()).build();
+    pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default()
+        .voters_count(10_000.into()).targets_count(1_500.into()).build();
 }
 
-pub struct JobToFeeHandler;
+impl pallet_session::historical::Config for Runtime {
+    type FullIdentification = AccountId;
+    type FullIdentificationOf = ConvertInto;
+}
 
-impl JobToFee<AccountId, BlockNumber, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>
-    for JobToFeeHandler
-{
-    type Balance = Balance;
+pub struct BaseFilter;
+impl Contains<RuntimeCall> for BaseFilter {
+    fn contains(call: &RuntimeCall) -> bool {
+        let is_stake_unbond_call = matches!(
+            call,
+            RuntimeCall::Staking(pallet_staking::Call::unbond { .. })
+        );
+
+        if is_stake_unbond_call {
+            // no unbond call
+            return false;
+        }
 
-    fn job_to_fee(
-        job: &JobSubmission<
-            AccountId,
-            BlockNumber,
-            MaxParticipants,
-            MaxSubmissionLen,
-            MaxAdditionalParamsLen,
-        >,
-    ) -> Balance {
-        match job.job_type {
-            JobType::DKGTSSPhaseOne(_)
-            | JobType::DKGTSSPhaseTwo(_)
-            | JobType::DKGTSSPhaseThree(_)
-            | JobType::DKGTSSPhaseFour(_) => Dkg::job_to_fee(job),
-            JobType::ZkSaaSPhaseOne(_) | JobType::ZkSaaSPhaseTwo(_) => ZkSaaS::job_to_fee(job),
+        // no chill call
+        if matches!(
+            call,
+            RuntimeCall::Staking(pallet_staking::Call::chill { .. })
+        ) {
+            return false;
         }
-    }
 
-    fn calculate_result_extension_fee(_result: Vec<u8>, _extension_time: BlockNumber) -> Balance {
-        20
-    }
-}
+        // no withdraw_unbonded call
+        let is_stake_withdraw_call = matches!(
+            call,
+            RuntimeCall::Staking(pallet_staking::Call::withdraw_unbonded { .. })
+        );
 
-pub struct MockRolesHandler;
+        if is_stake_withdraw_call {
+            return false;
+        }
 
-impl RolesHandler<AccountId> for MockRolesHandler {
-    type Balance = Balance;
-    fn record_job_by_validators(_: Vec<AccountId>) -> DispatchResult {
-        Ok(())
+        true
     }
+}
 
-    fn get_max_active_service_for_restaker(_: AccountId) -> std::option::Option<u32> {
-        Some(u32::MAX)
-    }
-    fn report_offence(_offence_report: ReportRestakerOffence<AccountId>) -> DispatchResult {
-        Ok(())
+sp_runtime::impl_opaque_keys! {
+    pub struct MockSessionKeys {
+        pub other: MockSessionHandler,
     }
+}
 
-    fn is_restaker(address: AccountId) -> bool {
-        let restakers = (0..8)
-            .map(id_to_sr25519_public)
-            .map(Into::into)
-            .collect::<Vec<_>>();
-        restakers.contains(&address)
-    }
+pub struct MockSessionHandler;
+impl OneSessionHandler<AccountId> for MockSessionHandler {
+    type Key = UintAuthorityId;
 
-    fn is_restaker_with_role(address: AccountId, _role: RoleType) -> bool {
-        let restakers = (0..8)
-            .map(id_to_sr25519_public)
-            .map(Into::into)
-            .collect::<Vec<_>>();
-        restakers.contains(&address)
+    fn on_genesis_session<'a, I: 'a>(_: I)
+    where
+        I: Iterator<Item = (&'a AccountId, Self::Key)>,
+        AccountId: 'a,
+    {
     }
 
-    fn get_validator_role_key(address: AccountId) -> Option<Vec<u8>> {
-        let validators = (0..8).map(id_to_ecdsa_pair).collect::<Vec<_>>();
-        let restakers = (0..8)
-            .map(id_to_sr25519_public)
-            .map(AccountId::from)
-            .collect::<Vec<_>>();
-        let idx = restakers.iter().position(|p| p == &address);
-        idx.map(|i| validators[i].public().to_raw_vec())
+    fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I)
+    where
+        I: Iterator<Item = (&'a AccountId, Self::Key)>,
+        AccountId: 'a,
+    {
     }
-}
 
-pub struct MockMPCHandler;
-
-impl
-    MPCHandler<
-        AccountId,
-        BlockNumber,
-        Balance,
-        MaxParticipants,
-        MaxSubmissionLen,
-        MaxKeyLen,
-        MaxDataLen,
-        MaxSignatureLen,
-        MaxProofLen,
-        MaxAdditionalParamsLen,
-    > for MockMPCHandler
-{
-    fn verify(
-        data: JobWithResult<
-            AccountId,
-            MaxParticipants,
-            MaxSubmissionLen,
-            MaxKeyLen,
-            MaxDataLen,
-            MaxSignatureLen,
-            MaxProofLen,
-            MaxAdditionalParamsLen,
-        >,
-    ) -> DispatchResult {
-        match data.result {
-            JobResult::DKGPhaseOne(_)
-            | JobResult::DKGPhaseTwo(_)
-            | JobResult::DKGPhaseThree(_)
-            | JobResult::DKGPhaseFour(_) => Dkg::verify(data.result),
-            JobResult::ZkSaaSPhaseOne(_) | JobResult::ZkSaaSPhaseTwo(_) => ZkSaaS::verify(data),
-        }
-    }
+    fn on_disabled(_validator_index: u32) {}
+}
 
-    fn verify_validator_report(
-        _validator: AccountId,
-        _offence: ValidatorOffenceType,
-        _signatures: Vec<Vec<u8>>,
-    ) -> DispatchResult {
-        Ok(())
-    }
+impl sp_runtime::BoundToRuntimeAppPublic for MockSessionHandler {
+    type Public = UintAuthorityId;
+}
 
-    fn validate_authority_key(_validator: AccountId, _authority_key: Vec<u8>) -> DispatchResult {
-        Ok(())
+pub struct MockSessionManager;
+
+impl pallet_session::SessionManager<AccountId> for MockSessionManager {
+    fn end_session(_: sp_staking::SessionIndex) {}
+    fn start_session(_: sp_staking::SessionIndex) {}
+    fn new_session(idx: sp_staking::SessionIndex) -> Option<Vec<AccountId>> {
+        if idx == 0 || idx == 1 || idx == 2 {
+            Some(vec![
+                mock_pub_key(1),
+                mock_pub_key(2),
+                mock_pub_key(3),
+                mock_pub_key(4),
+            ])
+        } else {
+            None
+        }
     }
 }
 
 parameter_types! {
-    pub const JobsPalletId: PalletId = PalletId(*b"py/jobss");
+    pub const Period: u64 = 1;
+    pub const Offset: u64 = 0;
 }
 
-const KB: u32 = 1024;
-const MB: u32 = 1024 * KB;
+impl pallet_session::Config for Runtime {
+    type SessionManager = MockSessionManager;
+    type Keys = MockSessionKeys;
+    type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
+    type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
+    type SessionHandler = (MockSessionHandler,);
+    type RuntimeEvent = RuntimeEvent;
+    type ValidatorId = AccountId;
+    type ValidatorIdOf = pallet_staking::StashOf<Runtime>;
+    type WeightInfo = ();
+}
 
-parameter_types! {
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxSubmissionLen: u32 = 64 * MB;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxParticipants: u32 = 10;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxKeyLen: u32 = 2048;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxDataLen: u32 = 256;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxSignatureLen: u32 = 256;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxProofLen: u32 = 256;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxActiveJobsPerValidator: u32 = 100;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxRolesPerValidator: u32 = 100;
-    #[derive(Clone, RuntimeDebug, Eq, PartialEq, TypeInfo, Encode, Decode)]
-    #[derive(Serialize, Deserialize)]
-    pub const MaxAdditionalParamsLen: u32 = 256;
+pub struct OnChainSeqPhragmen;
+impl onchain::Config for OnChainSeqPhragmen {
+    type System = Runtime;
+    type Solver = SequentialPhragmen<AccountId, Perbill>;
+    type DataProvider = Staking;
+    type WeightInfo = ();
+    type MaxWinners = ConstU32<100>;
+    type Bounds = ElectionBoundsOnChain;
 }
 
-impl pallet_jobs::Config for Runtime {
-    type RuntimeEvent = RuntimeEvent;
+/// Upper limit on the number of NPOS nominations.
+const MAX_QUOTA_NOMINATIONS: u32 = 16;
+
+impl pallet_staking::Config for Runtime {
     type Currency = Balances;
-    type JobToFee = JobToFeeHandler;
-    type RolesHandler = MockRolesHandler;
-    type MPCHandler = MockMPCHandler;
-    type ForceOrigin = EnsureSigned<AccountId>;
-    type MaxParticipants = MaxParticipants;
-    type MaxSubmissionLen = MaxSubmissionLen;
-    type MaxSignatureLen = MaxSignatureLen;
-    type MaxDataLen = MaxDataLen;
-    type MaxKeyLen = MaxKeyLen;
-    type MaxProofLen = MaxProofLen;
-    type MaxActiveJobsPerValidator = MaxActiveJobsPerValidator;
-    type MaxAdditionalParamsLen = MaxAdditionalParamsLen;
-    type PalletId = JobsPalletId;
-    type MisbehaviorHandler = MockMisbehaviorHandler;
+    type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
+    type UnixTime = pallet_timestamp::Pallet<Self>;
+    type CurrencyToVote = ();
+    type RewardRemainder = ();
+    type RuntimeEvent = RuntimeEvent;
+    type Slash = ();
+    type Reward = ();
+    type SessionsPerEra = ();
+    type SlashDeferDuration = ();
+    type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
+    type BondingDuration = ();
+    type SessionInterface = ();
+    type EraPayout = ();
+    type NextNewSession = Session;
+    type MaxExposurePageSize = ConstU32<64>;
+    type MaxControllersInDeprecationBatch = ConstU32<100>;
+    type OffendingValidatorsThreshold = ();
+    type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
+    type GenesisElectionProvider = Self::ElectionProvider;
+    type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
+    type TargetList = pallet_staking::UseValidatorsMap<Self>;
+    type MaxUnlockingChunks = ConstU32<32>;
+    type HistoryDepth = ConstU32<84>;
+    type EventListeners = ();
+    type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
+    type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
     type WeightInfo = ();
 }
 
-pub struct MockMisbehaviorHandler;
+parameter_types! {
+    pub const ServicesPalletId: PalletId = PalletId(*b"py/servs");
+}
+
+pub struct PalletEVMGasWeightMapping;
 
-impl MisbehaviorHandler for MockMisbehaviorHandler {
-    fn verify(_data: MisbehaviorSubmission) -> DispatchResult {
-        Ok(())
+impl EvmGasWeightMapping for PalletEVMGasWeightMapping {
+    fn gas_to_weight(gas: u64, without_base_weight: bool) -> Weight {
+        pallet_evm::FixedGasWeightMapping::<Runtime>::gas_to_weight(gas, without_base_weight)
+    }
+
+    fn weight_to_gas(weight: Weight) -> u64 {
+        pallet_evm::FixedGasWeightMapping::<Runtime>::weight_to_gas(weight)
     }
 }
 
-impl pallet_dkg::Config for Runtime {
-    type RuntimeEvent = RuntimeEvent;
-    type Currency = Balances;
-    type UpdateOrigin = EnsureSigned<AccountId>;
-    type MaxParticipants = MaxParticipants;
-    type MaxSubmissionLen = MaxSubmissionLen;
-    type MaxSignatureLen = MaxSignatureLen;
-    type MaxDataLen = MaxDataLen;
-    type MaxKeyLen = MaxKeyLen;
-    type MaxProofLen = MaxProofLen;
-    type MaxAdditionalParamsLen = MaxAdditionalParamsLen;
-    type WeightInfo = ();
+parameter_types! {
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxFields: u32 = 256;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxFieldsSize: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxMetadataLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxJobsPerService: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxOperatorsPerService: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxPermittedCallers: u32 = 256;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxServicesPerOperator: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxBlueprintsPerOperator: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxServicesPerUser: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxBinariesPerGadget: u32 = 64;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxSourcesPerGadget: u32 = 64;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxGitOwnerLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxGitRepoLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxGitTagLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxBinaryNameLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxIpfsHashLength: u32 = 46;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxContainerRegistryLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxContainerImageNameLength: u32 = 1024;
+
+    #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+    #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+    pub const MaxContainerImageTagLength: u32 = 1024;
 }
 
-impl pallet_zksaas::Config for Runtime {
+impl pallet_services::Config for Runtime {
     type RuntimeEvent = RuntimeEvent;
+    type ForceOrigin = frame_system::EnsureRoot<AccountId>;
     type Currency = Balances;
-
-    type UpdateOrigin = EnsureSigned<AccountId>;
-    type Verifier = (ArkworksVerifierGroth16Bn254, CircomVerifierGroth16Bn254);
-    type MaxParticipants = MaxParticipants;
-    type MaxSubmissionLen = MaxSubmissionLen;
-    type MaxSignatureLen = MaxSignatureLen;
-    type MaxDataLen = MaxDataLen;
-    type MaxKeyLen = MaxKeyLen;
-    type MaxProofLen = MaxProofLen;
-    type MaxAdditionalParamsLen = MaxAdditionalParamsLen;
+    type PalletId = ServicesPalletId;
+    type EvmRunner = MockedEvmRunner;
+    type EvmGasWeightMapping = PalletEVMGasWeightMapping;
+    type MaxFields = MaxFields;
+    type MaxFieldsSize = MaxFieldsSize;
+    type MaxMetadataLength = MaxMetadataLength;
+    type MaxJobsPerService = MaxJobsPerService;
+    type MaxOperatorsPerService = MaxOperatorsPerService;
+    type MaxPermittedCallers = MaxPermittedCallers;
+    type MaxServicesPerOperator = MaxServicesPerOperator;
+    type MaxBlueprintsPerOperator = MaxBlueprintsPerOperator;
+    type MaxServicesPerUser = MaxServicesPerUser;
+    type MaxBinariesPerGadget = MaxBinariesPerGadget;
+    type MaxSourcesPerGadget = MaxSourcesPerGadget;
+    type MaxGitOwnerLength = MaxGitOwnerLength;
+    type MaxGitRepoLength = MaxGitRepoLength;
+    type MaxGitTagLength = MaxGitTagLength;
+    type MaxBinaryNameLength = MaxBinaryNameLength;
+    type MaxIpfsHashLength = MaxIpfsHashLength;
+    type MaxContainerRegistryLength = MaxContainerRegistryLength;
+    type MaxContainerImageNameLength = MaxContainerImageNameLength;
+    type MaxContainerImageTagLength = MaxContainerImageTagLength;
+    type Constraints = pallet_services::types::ConstraintsOf<Self>;
     type WeightInfo = ();
 }
 
+type Block = frame_system::mocking::MockBlock<Runtime>;
+
+#[allow(non_camel_case_types)]
 construct_runtime!(
     pub enum Runtime
     {
         System: frame_system,
         Timestamp: pallet_timestamp,
         Balances: pallet_balances,
-        Jobs: pallet_jobs,
-        Dkg: pallet_dkg,
-        ZkSaaS: pallet_zksaas,
+        Services: pallet_services,
+        Session: pallet_session,
+        Staking: pallet_staking,
     }
 );
 
-sp_api::mock_impl_runtime_apis! {
-    impl pallet_jobs_rpc_runtime_api::JobsApi<Block, AccountId, MaxParticipants, MaxSubmissionLen, MaxKeyLen, MaxDataLen, MaxSignatureLen, MaxProofLen, MaxAdditionalParamsLen> for Runtime {
-        fn query_jobs_by_validator(&self, validator: AccountId) -> Option<Vec<RpcResponseJobsData<AccountId, BlockNumberOf<Block>, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>>> {
-            TEST_EXTERNALITIES.lock().as_ref().unwrap().execute_with(move || {
-                Jobs::query_jobs_by_validator(validator)
-            })
-        }
-
-        fn query_job_by_id(role_type: RoleType, job_id: JobId) -> Option<RpcResponseJobsData<AccountId, BlockNumberOf<Block>, MaxParticipants, MaxSubmissionLen, MaxAdditionalParamsLen>> {
-            TEST_EXTERNALITIES.lock().as_ref().unwrap().execute_with(move || {
-                Jobs::query_job_by_id(role_type, job_id)
-            })
-        }
-
-        fn query_job_result(role_type: RoleType, job_id: JobId) -> Option<PhaseResult<AccountId, BlockNumberOf<Block>, MaxParticipants, MaxKeyLen, MaxDataLen, MaxSignatureLen, MaxSubmissionLen, MaxProofLen, MaxAdditionalParamsLen>> {
-            TEST_EXTERNALITIES.lock().as_ref().unwrap().execute_with(move || {
-                Jobs::query_job_result(role_type, job_id)
-            })
-        }
-
-        fn query_next_job_id() -> JobId {
-            TEST_EXTERNALITIES.lock().as_ref().unwrap().execute_with(move || {
-                Jobs::query_next_job_id()
-            })
-        }
-
-        fn query_restaker_role_key(address: AccountId) -> Option<Vec<u8>> {
-            TEST_EXTERNALITIES.lock().as_ref().unwrap().execute_with(move || {
-                MockRolesHandler::get_validator_role_key(address)
-            })
-        }
-    }
-}
-
 pub struct ExtBuilder;
 
 impl Default for ExtBuilder {
@@ -527,11 +539,11 @@ static TEST_EXTERNALITIES: parking_lot::Mutex<Option<MultiThreadedTestExternalit
 pub fn advance_to_block(block_number: u64) {
     while System::block_number() < block_number {
         System::on_finalize(System::block_number());
-        Jobs::on_finalize(System::block_number());
+        Services::on_finalize(System::block_number());
         Balances::on_finalize(System::block_number());
         System::set_block_number(System::block_number() + 1);
         System::on_initialize(System::block_number());
-        Jobs::on_initialize(System::block_number());
+        Services::on_initialize(System::block_number());
         Balances::on_initialize(System::block_number());
     }
 }
@@ -616,9 +628,7 @@ pub async fn new_test_ext<
     let ext = MultiThreadedTestExternalities::new(ext);
     assert!(TEST_EXTERNALITIES.lock().replace(ext.clone()).is_none(), "Make sure to run tests serially with -- --test-threads=1 or with nextest to ensure separate program spaces per test");
 
-    let finality_notification_txs = Arc::new(parking_lot::Mutex::new(Vec::<
-        TracingUnboundedSender<FinalityNotification<Block>>,
-    >::new()));
+    let finality_notification_txs = Arc::new(parking_lot::Mutex::new(Vec::new()));
     let sinks = finality_notification_txs.clone();
     let externalities = ext.clone();
 
@@ -641,17 +651,7 @@ pub async fn new_test_ext<
 
                 let lock = sinks.lock();
                 for sink in lock.iter() {
-                    let (faux_sink, faux_stream) = tracing_unbounded("faux_sink", 1024);
-                    std::mem::forget(faux_stream);
-
-                    let header = <Block as BlockT>::Header::new_from_number(number);
-                    let summary = FinalizeSummary::<Block> {
-                        finalized: vec![header.hash()],
-                        header,
-                        stale_heads: vec![],
-                    };
-
-                    let notification = FinalityNotification::from_summary(summary, faux_sink);
+                    let notification = generate_finality_notification_at(number);
                     if sink.unbounded_send(notification).is_err() {
                         log::warn!(target: "gadget", "Will not deliver FinalityNotification because the receiver is gone");
                     }
@@ -677,9 +677,11 @@ pub async fn new_test_ext<
             id: format!("Peer {node_index}"),
         };
 
+        let genesis_hash = generate_finality_notification_at(0).hash;
         let tx_manager = Arc::new(TestExternalitiesPalletSubmitter {
             id: account_id.clone(),
             ext: ext.clone(),
+            subxt_client: OfflineClient::new(genesis_hash, "1.0.0".into(), []),
         });
 
         let keystore = ECDSAKeyStore::in_memory(role_pair);
@@ -704,21 +706,34 @@ pub async fn new_test_ext<
     ext
 }
 
+fn generate_finality_notification_at(number: u64) -> FinalityNotification<Block> {
+    let (faux_sink, faux_stream) = tracing_unbounded("faux_sink", 1024);
+    std::mem::forget(faux_stream);
+    let header = <Block as BlockT>::Header::new_from_number(number);
+    let summary = FinalizeSummary::<Block> {
+        finalized: vec![header.hash()],
+        header,
+        stale_heads: vec![],
+    };
+
+    FinalityNotification::from_summary(summary, faux_sink)
+}
+
 pub mod mock_wrapper_client {
-    use crate::mock::{Runtime, RuntimeOrigin, TangleExtEnvironment};
+    use crate::mock::{Runtime, TangleExtEnvironment};
     use crate::sync::substrate_test_channel::MultiThreadedTestExternalities;
     use async_trait::async_trait;
+    use environment_utils::transaction_manager::tangle::TanglePalletSubmitter;
     use futures::StreamExt;
     use gadget_common::client::exec_client_function;
-    use tangle_environment::api::ClientWithApi;
     use gadget_common::locks::TokioMutexExt;
-    use gadget_common::tangle_subxt::subxt::utils::AccountId32;
-    use gadget_common::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::{
-        jobs, roles,
-    };
     use gadget_common::tangle_runtime::*;
+    use gadget_common::tangle_subxt;
+    use gadget_common::tangle_subxt::subxt::utils::AccountId32;
+    use gadget_common::tangle_subxt::subxt::{Config, OfflineClient, OnlineClient};
+    use gadget_core::gadget::general::Client;
     use gadget_core::gadget::substrate;
-    use pallet_jobs_rpc_runtime_api::JobsApi;
+    use gadget_io::tokio;
     use parity_scale_codec::{Decode, Encode};
     use sc_client_api::{
         BlockchainEvents, FinalityNotification, FinalityNotifications, ImportNotifications,
@@ -730,27 +745,24 @@ pub mod mock_wrapper_client {
     use sp_runtime::traits::Header;
     use std::sync::Arc;
     use std::time::Duration;
-    use tangle_primitives::jobs::JobId;
+    use tangle_environment::gadget::TangleEvent;
     use tangle_primitives::AccountId;
-    use environment_utils::transaction_manager::tangle::TanglePalletSubmitter;
-    use gadget_core::gadget::general::Client;
 
     #[derive(Clone)]
     pub struct MockClient<R, B: Block> {
         runtime: Arc<R>,
         finality_notification_stream:
             Arc<gadget_io::tokio::sync::Mutex<Option<FinalityNotifications<B>>>>,
-        latest_finality_notification:
-            Arc<gadget_io::tokio::sync::Mutex<Option<FinalityNotification<B>>>>,
+        latest_finality_notification: Arc<gadget_io::tokio::sync::Mutex<Option<TangleEvent>>>,
         finality_notification_txs:
-            Arc<parking_lot::Mutex<Vec<TracingUnboundedSender<FinalityNotification<B>>>>>,
+            Arc<parking_lot::Mutex<Vec<TracingUnboundedSender<TangleEvent>>>>,
     }
 
     impl<R, B: Block> MockClient<R, B> {
         pub async fn new(
             runtime: R,
             finality_notification_txs: Arc<
-                parking_lot::Mutex<Vec<TracingUnboundedSender<FinalityNotification<B>>>>,
+                parking_lot::Mutex<Vec<TracingUnboundedSender<TangleEvent>>>,
             >,
         ) -> Self {
             let runtime = Arc::new(runtime);
@@ -772,10 +784,8 @@ pub mod mock_wrapper_client {
     }
 
     #[async_trait]
-    impl<R: Send + Sync + Clone, B: Block> Client<substrate::FinalityNotification>
-        for MockClient<R, B>
-    {
-        async fn next_event(&self) -> Option<substrate::FinalityNotification> {
+    impl<R: Send + Sync + Clone, B: Block> Client<TangleEvent> for MockClient<R, B> {
+        async fn next_event(&self) -> Option<TangleEvent> {
             let mut lock = self
                 .finality_notification_stream
                 .lock_timeout(Duration::from_millis(500))
@@ -792,21 +802,28 @@ pub mod mock_wrapper_client {
                 hash.copy_from_slice(n.header.hash().as_ref());
                 let number =
                     Decode::decode(&mut Encode::encode(&n.header.number()).as_slice()).unwrap();
-                substrate::FinalityNotification { hash, number }
+                TangleEvent {
+                    hash,
+                    number,
+                    events: n,
+                }
             })
         }
 
-        async fn latest_event(&self) -> Option<substrate::FinalityNotification> {
+        async fn latest_event(&self) -> Option<TangleEvent> {
             let lock = self
                 .latest_finality_notification
                 .lock_timeout(Duration::from_millis(500))
                 .await;
             if let Some(n) = lock.clone() {
                 let mut hash = [0u8; 32];
-                hash.copy_from_slice(n.header.hash().as_ref());
-                let number =
-                    Decode::decode(&mut Encode::encode(&n.header.number()).as_slice()).unwrap();
-                Some(substrate::FinalityNotification { hash, number })
+                hash.copy_from_slice(&n.hash);
+                let number = Decode::decode(&mut Encode::encode(&n.number).as_slice()).unwrap();
+                Some(TangleEvent {
+                    hash,
+                    number,
+                    events: n,
+                })
             } else {
                 drop(lock);
                 self.next_event().await
@@ -814,147 +831,6 @@ pub mod mock_wrapper_client {
         }
     }
 
-    #[async_trait]
-    impl ClientWithApi<TangleExtEnvironment> for MockClient<Runtime, crate::mock::Block> {
-        async fn query_jobs_by_validator(
-            &self,
-            at: [u8; 32],
-            validator: AccountId32,
-        ) -> Result<
-            Option<
-                Vec<
-                    jobs::RpcResponseJobsData<
-                        AccountId32,
-                        u64,
-                        MaxParticipants,
-                        MaxSubmissionLen,
-                        MaxAdditionalParamsLen,
-                    >,
-                >,
-            >,
-            gadget_common::Error,
-        > {
-            let at = Decode::decode(&mut Encode::encode(&at).as_slice()).unwrap();
-            let validator = tangle_primitives::AccountId::from(validator.0);
-            exec_client_function(&self.runtime, move |r| {
-                r.runtime_api()
-                    .query_jobs_by_validator(at, validator)
-                    .map_err(|err| gadget_common::Error::ClientError {
-                        err: format!("{err:?}"),
-                    })
-                    .map(|r| {
-                        r.map(|r| {
-                            r.into_iter()
-                                .flat_map(|r| Decode::decode(&mut Encode::encode(&r).as_slice()))
-                                .collect()
-                        })
-                    })
-            })
-            .await
-        }
-        async fn query_job_by_id(
-            &self,
-            at: [u8; 32],
-            role_type: roles::RoleType,
-            job_id: u64,
-        ) -> Result<
-            Option<
-                jobs::RpcResponseJobsData<
-                    AccountId32,
-                    u64,
-                    MaxParticipants,
-                    MaxSubmissionLen,
-                    MaxAdditionalParamsLen,
-                >,
-            >,
-            gadget_common::Error,
-        > {
-            let at = Decode::decode(&mut Encode::encode(&at).as_slice()).unwrap();
-            let role_type = Decode::decode(&mut Encode::encode(&role_type).as_slice()).unwrap();
-            exec_client_function(&self.runtime, move |r| {
-                r.runtime_api()
-                    .query_job_by_id(at, role_type, job_id)
-                    .map_err(|err| gadget_common::Error::ClientError {
-                        err: format!("{err:?}"),
-                    })
-                    .map(|r| r.map(|r| Decode::decode(&mut Encode::encode(&r).as_slice()).unwrap()))
-            })
-            .await
-        }
-
-        async fn query_job_result(
-            &self,
-            at: [u8; 32],
-            role_type: roles::RoleType,
-            job_id: u64,
-        ) -> Result<
-            Option<
-                jobs::PhaseResult<
-                    AccountId32,
-                    u64,
-                    MaxParticipants,
-                    MaxKeyLen,
-                    MaxDataLen,
-                    MaxSignatureLen,
-                    MaxSubmissionLen,
-                    MaxProofLen,
-                    MaxAdditionalParamsLen,
-                >,
-            >,
-            gadget_common::Error,
-        > {
-            let at = Decode::decode(&mut Encode::encode(&at).as_slice()).unwrap();
-            let role_type = Decode::decode(&mut Encode::encode(&role_type).as_slice()).unwrap();
-            exec_client_function(&self.runtime, move |r| {
-                r.runtime_api()
-                    .query_job_result(at, role_type, job_id)
-                    .map_err(|err| gadget_common::Error::ClientError {
-                        err: format!("{err:?}"),
-                    })
-                    .map(|r| r.map(|r| Decode::decode(&mut Encode::encode(&r).as_slice()).unwrap()))
-            })
-            .await
-        }
-
-        async fn query_next_job_id(&self, at: [u8; 32]) -> Result<u64, gadget_common::Error> {
-            let at = Decode::decode(&mut Encode::encode(&at).as_slice()).unwrap();
-            exec_client_function(&self.runtime, move |r| {
-                r.runtime_api().query_next_job_id(at).map_err(|err| {
-                    gadget_common::Error::ClientError {
-                        err: format!("{err:?}"),
-                    }
-                })
-            })
-            .await
-        }
-
-        async fn query_restaker_role_key(
-            &self,
-            at: [u8; 32],
-            address: AccountId32,
-        ) -> Result<Option<Vec<u8>>, gadget_common::Error> {
-            let at = Decode::decode(&mut Encode::encode(&at).as_slice()).unwrap();
-            let address = tangle_primitives::AccountId::from(address.0);
-            exec_client_function(&self.runtime, move |r| {
-                r.runtime_api()
-                    .query_restaker_role_key(at, address)
-                    .map_err(|err| gadget_common::Error::ClientError {
-                        err: format!("{err:?}"),
-                    })
-                    .map(|r| r.map(|r| r.to_vec()))
-            })
-            .await
-        }
-
-        async fn query_restaker_roles(
-            &self,
-            _at: [u8; 32],
-            _address: AccountId32,
-        ) -> Result<Vec<roles::RoleType>, gadget_common::Error> {
-            Ok(Default::default())
-        }
-    }
-
     impl<R: ProvideRuntimeApi<B>, B: Block> ProvideRuntimeApi<B> for MockClient<R, B> {
         type Api = R::Api;
         fn runtime_api(&self) -> ApiRef<Self::Api> {
@@ -990,12 +866,13 @@ pub mod mock_wrapper_client {
         }
     }
 
-    pub struct TestExternalitiesPalletSubmitter {
+    pub struct TestExternalitiesPalletSubmitter<C: Config> {
         pub ext: MultiThreadedTestExternalities,
         pub id: AccountId,
+        pub subxt_client: OfflineClient<C>,
     }
 
-    impl std::fmt::Debug for TestExternalitiesPalletSubmitter {
+    impl<C: Config> std::fmt::Debug for TestExternalitiesPalletSubmitter<C> {
         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
             f.debug_struct("TestExternalitiesPalletSubmitter")
                 .field("id", &self.id.to_string())
@@ -1004,43 +881,31 @@ pub mod mock_wrapper_client {
     }
 
     #[async_trait]
-    impl TanglePalletSubmitter for TestExternalitiesPalletSubmitter {
-        async fn submit_job_result(
+    impl<C: Config> TanglePalletSubmitter for TestExternalitiesPalletSubmitter<C> {
+        async fn submit_job_call_result(
             &self,
-            role_type: roles::RoleType,
-            job_id: JobId,
-            result: jobs::JobResult<
-                MaxParticipants,
-                MaxKeyLen,
-                MaxSignatureLen,
-                MaxDataLen,
-                MaxProofLen,
-                MaxAdditionalParamsLen,
-            >,
+            service_id: u64,
+            call_id: u64,
+            result: api::services::calls::types::submit_result::Result,
         ) -> Result<(), gadget_common::Error> {
             let id = self.id.clone();
-            self.ext
-                .execute_with_async(move || {
-                    let origin = RuntimeOrigin::signed(id);
-                    let res = crate::mock::Jobs::submit_job_result(
-                        origin,
-                        Decode::decode(&mut Encode::encode(&role_type).as_slice()).unwrap(),
-                        job_id,
-                        Decode::decode(&mut Encode::encode(&result).as_slice()).unwrap(),
-                    );
-                    if let Err(err) = res {
-                        let err = format!("Pallet tx error: {err:?}");
-                        if err.contains("JobNotFound") {
-                            // Job has already been submitted (assumption only for tests)
-                            Ok(())
-                        } else {
-                            Err(gadget_common::Error::ClientError { err })
-                        }
-                    } else {
-                        Ok(())
-                    }
-                })
-                .await
+            let client = self.subxt_client.clone();
+            let function = async move {
+                let call = api::tx()
+                    .services()
+                    .submit_result(service_id, call_id, result);
+                let _res = client
+                    .tx()
+                    .sign_and_submit_then_watch_default(&call, &id)
+                    .await?
+                    .wait_for_finalized_success()
+                    .await?
+                    .block_hash();
+
+                Ok(())
+            };
+
+            function.await
         }
     }
 }
@@ -1091,7 +956,7 @@ impl GadgetEnvironment for TangleExtEnvironment {
         }
     }
 
-    async fn setup_client(&self) -> Result<Self::Client, Self::Error> {
+    async fn setup_runtime(&self) -> Result<Self::Client, Self::Error> {
         let finality_notification_txs = self.finality_notification_txs.clone();
         Ok(MockClient::<Runtime, Block>::new(Runtime, finality_notification_txs).await)
     }
@@ -1110,3 +975,7 @@ impl EventMetadata<TangleExtEnvironment> for TangleEvent {
         self.number
     }
 }
+
+pub fn mock_pub_key(id: u8) -> AccountId {
+    sr25519::Public::from_raw([id; 32]).into()
+}