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

Issue:
[TASK] Handle `PreRegistered` Event in the Shell
### Overview

The Shell should listen for `PreRegistered` events, this event includes a blueprint id and the operator account id, if the account id matches ours, we should download the blueprint and execute its gadget in the Registration Mode by setting `REGISTRATION_MODE_ON` env. The gadget will now start in the Registration mode and will try to register the current operator on that blueprint,  the required envs for this mode are the following:

1. `RPC_URL`: The remote RPC url for tangle
2. `KEYSTORE_URI`: the keystore file where the keys are stored and could be retrieved.
3. `DATA_DIR`: is an isolated path for where this gadget should store its data (database, secrets, …etc)
4. `BLUEPRINT_ID`: the active blueprint ID for this gadget
5. `REGISTRATION_MODE_ON` set to any value.

### References
See: https://github.com/webb-tools/tangle/issues/710


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2335e4aed..65ee43e20 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -152,6 +152,7 @@ serde = { version = "1.0.197", default-features = false }
 serde_json = "1.0"
 url = { version = "2.5", default-features = false }
 async-trait = "0.1.73"
+async-recursion2 = "1.0.5"
 log = "0.4.20"
 tracing = { version = "0.1", default-features = false }
 tracing-log = { version = "0.2", default-features = false }
diff --git a/blueprints/incredible-squaring/CODEGEN_LOCK b/blueprints/incredible-squaring/CODEGEN_LOCK
deleted file mode 100644
index e69de29bb..000000000
diff --git a/blueprints/incredible-squaring/src/main.rs b/blueprints/incredible-squaring/src/main.rs
index 16317f836..892a61c07 100644
--- a/blueprints/incredible-squaring/src/main.rs
+++ b/blueprints/incredible-squaring/src/main.rs
@@ -1,7 +1,11 @@
-use color_eyre::{eyre::OptionExt, Result};
+use color_eyre::{eyre::eyre, eyre::OptionExt, Result};
 use gadget_sdk::{
-    events_watcher::tangle::TangleEventsWatcher, events_watcher::SubstrateEventWatcher,
-    keystore::Backend, tangle_subxt::subxt,
+    events_watcher::{tangle::TangleEventsWatcher, SubstrateEventWatcher},
+    keystore::Backend,
+    tangle_subxt::tangle_testnet_runtime::api::{
+        self, runtime_types::sp_core::ecdsa, runtime_types::tangle_primitives::services,
+    },
+    tx,
 };
 
 use incredible_squaring_blueprint as blueprint;
@@ -17,35 +21,46 @@ async fn main() -> Result<()> {
     logger.init();
 
     let env = gadget_sdk::env::load()?;
-    let keystore = env.keystore()?;
-    let client = subxt::OnlineClient::from_url(&env.tangle_rpc_endpoint).await?;
+    let client = env.client().await?;
+    let signer = env.first_signer()?;
+    if env.should_run_registration() {
+        // We should register our operator on the blueprint.
+        let keystore = env.keystore()?;
+        // get the first ECDSA key from the keystore and register with it.
+        let ecdsa_key = keystore
+            .iter_ecdsa()
+            .next()
+            .map(|v| v.to_sec1_bytes().to_vec().try_into())
+            .transpose()
+            .map_err(|_| eyre!("Failed to convert ECDSA key"))?
+            .ok_or_eyre("No ECDSA key found")?;
+        let xt = api::tx().services().register(
+            env.blueprint_id,
+            services::OperatorPreferences {
+                key: ecdsa::Public(ecdsa_key),
+                approval: services::ApprovalPrefrence::None,
+            },
+            Default::default(),
+        );
+        // send the tx to the tangle and exit.
+        tx::tangle::send(&client, &signer, &xt).await?;
+    } else {
+        // otherwise, we should start the event watcher and run the gadget.
+        let x_square = blueprint::XsquareEventHandler {
+            service_id: env.service_id.unwrap(),
+            signer,
+        };
 
-    let sr25519_pubkey = keystore
-        .iter_sr25519()
-        .next()
-        .ok_or_eyre("No sr25519 keys found in the keystore")?;
-    let sr25519_secret = keystore
-        .expose_sr25519_secret(&sr25519_pubkey)?
-        .ok_or_eyre("No sr25519 secret found in the keystore")?;
+        tracing::info!("Starting the event watcher ...");
 
-    let mut seed = [0u8; 32];
-    seed.copy_from_slice(&sr25519_secret.to_bytes()[0..32]);
-    let signer = subxt_signer::sr25519::Keypair::from_secret_key(seed)?;
-
-    let x_square = blueprint::XsquareEventHandler {
-        service_id: env.service_id,
-        signer,
-    };
-
-    tracing::info!("Starting the event watcher ...");
-
-    SubstrateEventWatcher::run(
-        &TangleEventsWatcher,
-        client,
-        // Add more handler here if we have more functions.
-        vec![Box::new(x_square)],
-    )
-    .await?;
+        SubstrateEventWatcher::run(
+            &TangleEventsWatcher,
+            client,
+            // Add more handler here if we have more functions.
+            vec![Box::new(x_square)],
+        )
+        .await?;
+    }
 
     Ok(())
 }
diff --git a/environments/tangle/src/api.rs b/environments/tangle/src/api.rs
index cbb0b8fb7..68f009f7f 100644
--- a/environments/tangle/src/api.rs
+++ b/environments/tangle/src/api.rs
@@ -1,5 +1,6 @@
 use gadget_common::config::DebugLogger;
 use gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services;
+use gadget_common::tangle_runtime::api::runtime_types::tangle_primitives::services::ServiceBlueprint;
 use gadget_common::tangle_runtime::AccountId32;
 use gadget_common::tangle_subxt::subxt::backend::BlockRef;
 use gadget_common::tangle_subxt::subxt::utils::H256;
@@ -32,6 +33,25 @@ impl<C: Config> ServicesClient<C>
 where
     BlockRef<<C as Config>::Hash>: From<BlockRef<H256>>,
 {
+    pub async fn get_blueprint_by_id(
+        &self,
+        at: [u8; 32],
+        blueprint_id: u64,
+    ) -> Result<Option<ServiceBlueprint>, gadget_common::Error> {
+        let call = api::storage().services().blueprints(blueprint_id);
+        let at = BlockRef::from_hash(H256::from_slice(&at));
+        let ret: Option<ServiceBlueprint> = self
+            .rpc_client
+            .storage()
+            .at(at)
+            .fetch(&call)
+            .await
+            .map_err(|e| gadget_common::Error::ClientError { err: e.to_string() })?
+            .map(|r| r.1);
+
+        Ok(ret)
+    }
+
     pub async fn query_operator_blueprints(
         &self,
         at: [u8; 32],
diff --git a/sdk/src/env.rs b/sdk/src/env.rs
index 4309c9e55..aa900649d 100644
--- a/sdk/src/env.rs
+++ b/sdk/src/env.rs
@@ -1,3 +1,5 @@
+use crate::events_watcher::tangle::TangleConfig;
+
 /// Gadget environment.
 #[derive(Debug, Clone)]
 #[non_exhaustive]
@@ -16,7 +18,18 @@ pub struct GadgetEnvironment {
     /// Blueprint ID for this gadget.
     pub blueprint_id: u64,
     /// Service ID for this gadget.
-    pub service_id: u64,
+    ///
+    /// This is only set to `None` when the gadget is in the registration mode.
+    /// Always check for is `is_registration` flag before using this.
+    pub service_id: Option<u64>,
+
+    /// The Current Environment is for the `PreRegisteration` of the Gadget
+    ///
+    /// The gadget will now start in the Registration mode and will try to register the current operator on that blueprint
+    /// There is no Service ID for this mode, since we need to register the operator first on the blueprint.
+    ///
+    /// If this is set to true, the gadget should do some work and register the operator on the blueprint.
+    pub is_registration: bool,
 }
 
 /// An error type for the gadget environment.
@@ -47,6 +60,16 @@ pub enum Error {
     /// Error opening the filesystem keystore.
     #[error(transparent)]
     Keystore(#[from] crate::keystore::Error),
+    /// Subxt error.
+    #[error(transparent)]
+    Subxt(#[from] subxt::Error),
+
+    /// No Sr25519 keypair found in the keystore.
+    #[error("No Sr25519 keypair found in the keystore")]
+    NoSr25519Keypair,
+    /// Invalid Sr25519 keypair found in the keystore.
+    #[error("Invalid Sr25519 keypair found in the keystore")]
+    InvalidSr25519Keypair,
 }
 
 /// Loads the [`GadgetEnvironment`] from the current environment.
@@ -55,6 +78,7 @@ pub enum Error {
 /// This function will return an error if any of the required environment variables are missing.
 #[cfg(feature = "std")]
 pub fn load() -> Result<GadgetEnvironment, Error> {
+    let is_registration = std::env::var("REGISTRATION_MODE_ON").is_ok();
     Ok(GadgetEnvironment {
         tangle_rpc_endpoint: std::env::var("RPC_URL")
             .map_err(|_| Error::MissingTangleRpcEndpoint)?,
@@ -64,10 +88,18 @@ pub fn load() -> Result<GadgetEnvironment, Error> {
             .map_err(|_| Error::MissingBlueprintId)?
             .parse()
             .map_err(Error::MalformedBlueprintId)?,
-        service_id: std::env::var("SERVICE_ID")
-            .map_err(|_| Error::MissingServiceId)?
-            .parse()
-            .map_err(Error::MalformedServiceId)?,
+        // If the registration mode is on, we don't need the service ID
+        service_id: if is_registration {
+            None
+        } else {
+            Some(
+                std::env::var("SERVICE_ID")
+                    .map_err(|_| Error::MissingServiceId)?
+                    .parse()
+                    .map_err(Error::MalformedServiceId)?,
+            )
+        },
+        is_registration,
     })
 }
 
@@ -78,6 +110,7 @@ pub fn load() -> Result<GadgetEnvironment, Error> {
 
 impl GadgetEnvironment {
     /// Loads the `KeyStore` from the current environment.
+    ///
     /// # Errors
     ///
     /// This function will return an error if the keystore URI is unsupported.
@@ -100,9 +133,47 @@ impl GadgetEnvironment {
         }
     }
 
+    /// Returns the first Sr25519 signer keypair from the keystore.
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if no Sr25519 keypair is found in the keystore.
+    /// or if the keypair seed is invalid.
+    #[doc(alias = "sr25519_signer")]
+    pub fn first_signer(&self) -> Result<tangle_subxt::subxt_signer::sr25519::Keypair, Error> {
+        let keystore = self.keystore()?;
+        let sr25519_pubkey = crate::keystore::Backend::iter_sr25519(&keystore)
+            .next()
+            .ok_or_else(|| Error::NoSr25519Keypair)?;
+        let sr25519_secret =
+            crate::keystore::Backend::expose_sr25519_secret(&keystore, &sr25519_pubkey)?
+                .ok_or_else(|| Error::NoSr25519Keypair)?;
+
+        let mut seed = [0u8; 32];
+        seed.copy_from_slice(&sr25519_secret.to_bytes()[0..32]);
+        tangle_subxt::subxt_signer::sr25519::Keypair::from_secret_key(seed)
+            .map_err(|_| Error::InvalidSr25519Keypair)
+    }
+
     /// Returns whether the gadget should run in memory.
     #[must_use]
     pub const fn should_run_in_memory(&self) -> bool {
         self.data_dir_path.is_none()
     }
+
+    /// Returns whether the gadget should run in registration mode.
+    #[must_use]
+    pub const fn should_run_registration(&self) -> bool {
+        self.is_registration
+    }
+
+    /// Returns a new [`subxt::OnlineClient`] for the Tangle.
+    ///
+    /// # Errors
+    /// This function will return an error if we are unable to connect to the Tangle RPC endpoint.
+    pub async fn client(&self) -> Result<subxt::OnlineClient<TangleConfig>, Error> {
+        let client =
+            subxt::OnlineClient::<TangleConfig>::from_url(self.tangle_rpc_endpoint.clone()).await?;
+        Ok(client)
+    }
 }
diff --git a/sdk/src/events_watcher/tangle.rs b/sdk/src/events_watcher/tangle.rs
index bf96ce4c0..5e9641a4d 100644
--- a/sdk/src/events_watcher/tangle.rs
+++ b/sdk/src/events_watcher/tangle.rs
@@ -3,7 +3,7 @@
 #[derive(Debug, Default, Clone, Copy)]
 pub struct TangleEventsWatcher;
 
-/// A Type alias for the Tangle configuration [`subxt::SubstrateConfig`].
+/// A Type alias for the Tangle configuration [`subxt::PolkadotConfig`].
 pub type TangleConfig = subxt::PolkadotConfig;
 
 #[async_trait::async_trait]
diff --git a/sdk/src/keystore/mod.rs b/sdk/src/keystore/mod.rs
index 89d434475..cdeb1dea5 100644
--- a/sdk/src/keystore/mod.rs
+++ b/sdk/src/keystore/mod.rs
@@ -158,7 +158,7 @@ pub trait Backend {
     ) -> Result<Option<bls381::Secret>, Error>;
     /// Returns an iterator over all [`sr25519::Public`] keys that exist in the keystore.
     fn iter_sr25519(&self) -> impl Iterator<Item = sr25519::Public>;
-    /// Returns an iterator over all [`bls381::Public`] keys that exist in the keystore.
+    /// Returns an iterator over all [`ecdsa::Public`] keys that exist in the keystore.
     fn iter_ecdsa(&self) -> impl Iterator<Item = ecdsa::Public>;
     /// Returns an iterator over all [`ed25519::Public`] keys that exist in the keystore.
     fn iter_ed25519(&self) -> impl Iterator<Item = ed25519::Public>;
diff --git a/shell-manager/src/config.rs b/shell-manager/src/config.rs
index 211ae947b..a44c107ca 100644
--- a/shell-manager/src/config.rs
+++ b/shell-manager/src/config.rs
@@ -12,6 +12,9 @@ pub struct ShellManagerOpts {
     pub shell_config: PathBuf,
     #[structopt(parse(from_os_str), short = "p", long = "protocols-config")]
     pub protocols_config: PathBuf,
+    /// The path to the keystore
+    #[structopt(short = "k", long = "keystore-uri")]
+    pub keystore_uri: String,
     /// The verbosity level, can be used multiple times
     #[structopt(long, short = "v", parse(from_occurrences))]
     pub verbose: i32,
diff --git a/shell-manager/src/gadget/native.rs b/shell-manager/src/gadget/native.rs
index 759640898..f61129900 100644
--- a/shell-manager/src/gadget/native.rs
+++ b/shell-manager/src/gadget/native.rs
@@ -7,16 +7,23 @@ 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,
+    Gadget, GadgetBinary, GithubFetcher,
 };
 use tokio::io::AsyncWriteExt;
 
+pub struct FilteredBlueprint {
+    pub blueprint_id: u64,
+    pub services: Vec<u64>,
+    pub gadget: Gadget,
+    pub registration_mode: bool,
+}
+
+#[allow(clippy::too_many_arguments)]
 pub async fn maybe_handle(
-    blueprints: &Vec<RpcServicesWithBlueprint>,
+    blueprints: &[FilteredBlueprint],
     onchain_services: &[NativeGithubMetadata],
-    onchain_gh_fetchers: &[&GithubFetcher],
+    onchain_gh_fetchers: &[GithubFetcher],
     shell_config: &ShellTomlConfig,
     shell_manager_opts: &ShellManagerOpts,
     active_shells: &mut ActiveShells,
@@ -50,8 +57,9 @@ pub async fn maybe_handle(
     Ok(())
 }
 
+#[allow(clippy::too_many_arguments)]
 async fn handle_github_source(
-    blueprints: &Vec<RpcServicesWithBlueprint>,
+    blueprints: &[FilteredBlueprint],
     service: &NativeGithubMetadata,
     shell_config: &ShellTomlConfig,
     shell_manager_opts: &ShellManagerOpts,
@@ -117,17 +125,39 @@ async fn handle_github_source(
 
         for blueprint in blueprints {
             if blueprint.blueprint_id == blueprint_id {
-                for svc in &blueprint.services {
-                    let service_id = svc.id;
+                for service_id in &blueprint.services {
                     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,
+                        *service_id,
                     )?;
 
+                    let env_vars = if blueprint.registration_mode {
+                        let mut env_vars = std::env::vars().collect::<Vec<_>>();
+                        // RPC_URL: The remote RPC url for tangle
+                        // KEYSTORE_URI: the keystore file where the keys are stored and could be retrieved.
+                        // DATA_DIR: is an isolated path for where this gadget should store its data (database, secrets, …etc)
+                        // BLUEPRINT_ID: the active blueprint ID for this gadget
+                        // REGISTRATION_MODE_ON set to any value.
+                        env_vars.push(("RPC_URL".to_string(), shell_config.url.to_string()));
+                        env_vars.push((
+                            "KEYSTORE_URI".to_string(),
+                            shell_manager_opts.keystore_uri.clone(),
+                        ));
+                        env_vars.push((
+                            "DATA_DIR".to_string(),
+                            format!("{}", shell_config.base_path.display()),
+                        ));
+                        env_vars.push(("BLUEPRINT_ID".to_string(), format!("{}", blueprint_id)));
+                        env_vars.push(("REGISTRATION_MODE_ON".to_string(), "true".to_string()));
+                        env_vars
+                    } else {
+                        std::env::vars().collect::<Vec<_>>()
+                    };
+
                     logger.info(format!("Starting protocol: {sub_service_str}"));
 
                     // Now that the file is loaded, spawn the process
@@ -137,20 +167,35 @@ async fn handle_github_source(
                         .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<_>>())
+                        .envs(env_vars)
                         .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)));
+                    if blueprint.registration_mode {
+                        // We must wait for the process to exit successfully
+                        let status = process_handle.wait_with_output().await?;
+                        if !status.status.success() {
+                            logger.error(format!(
+                                "Protocol (registration mode) {sub_service_str} failed to execute: {status:?}"
+                            ));
+                        } else {
+                            logger.info(format!(
+                                "***Protocol (registration mode) {sub_service_str} executed successfully***"
+                            ));
+                        }
+                    } else {
+                        // A normal running gadget binary. Store the process handle and let the event loop handle the rest
+                        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)));
+                    }
                 }
             }
         }
diff --git a/shell-manager/src/main.rs b/shell-manager/src/main.rs
index de6a89d7a..7c5531e81 100644
--- a/shell-manager/src/main.rs
+++ b/shell-manager/src/main.rs
@@ -1,9 +1,12 @@
+use crate::gadget::native::FilteredBlueprint;
 use crate::gadget::ActiveShells;
 use crate::utils::github_fetcher_to_native_github_metadata;
+use color_eyre::eyre::OptionExt;
 use color_eyre::Report;
 use config::ShellManagerOpts;
 use gadget_common::gadget_io;
 use gadget_common::prelude::GadgetEnvironment;
+use gadget_common::tangle_runtime::api::services::events::PreRegistration;
 use gadget_io::ShellTomlConfig;
 use shell_sdk::entry::keystore_from_base_path;
 use shell_sdk::keystore::load_keys_from_keystore;
@@ -16,6 +19,7 @@ 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::subxt::PolkadotConfig;
 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::{
@@ -66,7 +70,7 @@ async fn main() -> color_eyre::Result<()> {
         -> If the services field is not empty, for each service in RpcServicesWithBlueprint.services, spawn the gadget binary, using params to set the job type to listen to (in terms of our old language, each spawned service represents a single "RoleType")
     */
 
-    let (blueprints, init_event) = if let Some(event) = tangle_runtime.next_event().await {
+    let (mut blueprints, init_event) = if let Some(event) = tangle_runtime.next_event().await {
         (
             utils::get_blueprints(&runtime, event.hash, sub_account_id.clone()).await?,
             event,
@@ -76,6 +80,9 @@ async fn main() -> color_eyre::Result<()> {
     };
 
     logger.info(format!("Received {} blueprints", blueprints.len()));
+
+    let poll_result =
+        handle_tangle_block(&init_event, logger, &mut active_shells, &sub_account_id).await;
     handle_tangle_event(
         &init_event,
         &blueprints,
@@ -83,12 +90,22 @@ async fn main() -> color_eyre::Result<()> {
         &shell_config,
         opt,
         &mut active_shells,
+        poll_result,
+        &runtime,
     )
     .await?;
 
     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 {
+            let result =
+                handle_tangle_block(&event, logger, &mut active_shells, &sub_account_id).await;
+
+            if result.needs_update {
+                blueprints =
+                    utils::get_blueprints(&runtime, event.hash, sub_account_id.clone()).await?;
+            }
+
             handle_tangle_event(
                 &event,
                 &blueprints,
@@ -96,10 +113,10 @@ async fn main() -> color_eyre::Result<()> {
                 &shell_config,
                 opt,
                 &mut active_shells,
+                result,
+                &runtime,
             )
             .await?;
-
-            handle_tangle_block(event, logger, &mut active_shells, &sub_account_id).await;
         }
 
         Err::<(), _>(utils::msg_to_error("Finality Notification stream died"))
@@ -119,23 +136,48 @@ async fn main() -> color_eyre::Result<()> {
     }
 }
 
+#[derive(Default, Debug)]
+struct EventPollResult {
+    needs_update: bool,
+    // A vec of blueprints we have not yet become registered to
+    blueprint_registrations: Vec<u64>,
+}
+
 async fn handle_tangle_block(
-    event: TangleEvent,
+    event: &TangleEvent,
     logger: &DebugLogger,
     active_shells: &mut ActiveShells,
     account_id: &AccountId32,
-) {
+) -> EventPollResult {
+    let pre_registation_events = event.events.find::<PreRegistration>();
     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>();
 
+    let mut result = EventPollResult::default();
+
+    for evt in pre_registation_events {
+        match evt {
+            Ok(evt) => {
+                if &evt.operator == account_id {
+                    result.blueprint_registrations.push(evt.blueprint_id);
+                    logger.info(format!("Pre-registered event: {evt:?}"));
+                }
+            }
+            Err(err) => {
+                logger.warn(format!("Error handling pre-registered event: {err:?}"));
+            }
+        }
+    }
+
     // Handle registered events
     for evt in registered_events {
         match evt {
             Ok(evt) => {
                 logger.info(format!("Registered event: {evt:?}"));
+                result.needs_update = true;
             }
             Err(err) => {
                 logger.warn(format!("Error handling registered event: {err:?}"));
@@ -151,9 +193,11 @@ async fn handle_tangle_block(
                 if &evt.operator == account_id && active_shells.remove(&evt.blueprint_id).is_some()
                 {
                     logger.info(format!(
-                        "Removed all services for blueprint_id: {}",
+                        "Removed services for blueprint_id: {}",
                         evt.blueprint_id,
                     ));
+
+                    result.needs_update = true;
                 }
             }
             Err(err) => {
@@ -199,36 +243,81 @@ async fn handle_tangle_block(
             }
         }
     }
+
+    result
 }
 
+#[allow(clippy::too_many_arguments)]
 async fn handle_tangle_event(
     event: &TangleEvent,
-    blueprints: &Vec<RpcServicesWithBlueprint>,
+    blueprints: &[RpcServicesWithBlueprint],
     logger: &DebugLogger,
     shell_config: &ShellTomlConfig,
     shell_manager_opts: &ShellManagerOpts,
     active_shells: &mut ActiveShells,
+    poll_result: EventPollResult,
+    client: &ServicesClient<PolkadotConfig>,
 ) -> color_eyre::Result<()> {
     logger.info(format!("Received notification {}", event.number));
+
+    let mut registration_blueprints = vec![];
+    // First, check to see if we need to register any new services invoked by the PreRegistration event
+    if !poll_result.blueprint_registrations.is_empty() {
+        for blueprint_id in &poll_result.blueprint_registrations {
+            let blueprint = client
+                .get_blueprint_by_id(event.hash, *blueprint_id)
+                .await?
+                .ok_or_eyre("Unable to retrieve blueprint for registration mode")?;
+
+            let general_blueprint = FilteredBlueprint {
+                blueprint_id: *blueprint_id,
+                services: vec![0], // Add a dummy service id for now, since it does not matter for registration mode
+                gadget: blueprint.gadget,
+                registration_mode: true,
+            };
+
+            registration_blueprints.push(general_blueprint);
+        }
+    }
+
     // 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 valid_blueprint_ids = vec![];
+
+    let mut blueprints_filtered = vec![];
+
+    for blueprint in blueprints
+        .iter()
+        .map(|r| {
+            let r = FilteredBlueprint {
+                blueprint_id: r.blueprint_id,
+                services: r.services.iter().map(|r| r.id).collect(),
+                gadget: r.blueprint.gadget.clone(),
+                registration_mode: false,
+            };
+
+            r
+        })
+        .chain(registration_blueprints)
+    {
         let mut services_for_this_blueprint = vec![];
         if let runtime_types::tangle_primitives::services::Gadget::Native(gadget) =
-            &blueprint.blueprint.gadget
+            &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);
+                fetchers.push(gh.clone());
+                valid_blueprint_ids.push(blueprint.blueprint_id);
 
                 for service in &blueprint.services {
-                    services_for_this_blueprint.push(service.id);
+                    services_for_this_blueprint.push(*service);
                 }
+
+                blueprints_filtered.push(blueprint);
             } else {
                 logger.warn(
                     "Blueprint does not contain a Github fetcher and thus currently unsupported",
@@ -252,7 +341,7 @@ async fn handle_tangle_event(
 
     // Step 3: Check to see if we need to start any new services
     gadget::native::maybe_handle(
-        blueprints,
+        &blueprints_filtered,
         &onchain_services,
         &fetchers,
         shell_config,


Test patch: