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

Issue:
[BUG] Compiling SDK with eigenlayer and not evm feature fails
# Description
The build fails with this issue. Eigenlayer feature doesn't include "evm" but either it should or we should add eigenlayer to feature. If I also include `evm` feature it'll work, but this seems like a bug in the eigenlayer feature.

# TOML
```Cargo.toml
[dependencies]
blueprint-sdk = { git = "https://github.com/tangle-network/gadget", features = ["cronjob", "eigenlayer", "macros"] }
```

## Issue
- **`creates/macros/src/lib.rs`**
```rust
pub mod ext {
    pub use async_trait;
    pub use futures;
    #[cfg(any(feature = "tangle", feature = "evm", feature = "std"))]
    pub use tokio;
...
```

# Error
```bash
  error[E0432]: unresolved import `tokio`
    --> /Users/drew/.cargo/git/checkouts/gadget-84e3289516f70bdc/b40601a/crates/macros/src/lib.rs:11:13
     |
  11 |     pub use tokio;
     |             ^^^^^ no external crate `tokio`

  For more information about this error, try `rustc --explain E0432`.
  error: could not compile `gadget-macros` (lib) due to 1 previous error
  command failed: RUSTC_BOOTSTRAP="1" "cargo" "--quiet" "rustdoc" "-Z" "unstable-options" "--output-format" "json" "--package" "discord-summarizer-rig-blueprint" "--lib" "--target-dir" "/Users/drew/webb/discord-summarizer-rig-blueprint/target/blueprint" "--locked" "--" "--document-hidden-items"
  === stdout
  === stderr
  error[E0432]: unresolved import `tokio`
    --> /Users/drew/.cargo/git/checkouts/gadget-84e3289516f70bdc/b40601a/crates/macros/src/lib.rs:11:13
     |
  11 |     pub use tokio;
     |             ^^^^^ no external crate `tokio`

  For more information about this error, try `rustc --explain E0432`.
  error: could not compile `gadget-macros` (lib) due to 1 previous error
  ===

  thread 'main' panicked at /Users/drew/.cargo/git/checkouts/gadget-84e3289516f70bdc/b40601a/crates/blueprint/metadata/src/lib.rs:455:9:
  command returned status ExitStatus(unix_wait_status(25856)), command was: RUSTC_BOOTSTRAP="1" "cargo" "--quiet" "rustdoc" "-Z" "unstable-options" "--output-format" "json" "--package" "discord-summarizer-rig-blueprint" "--lib" "--target-dir" "/Users/drew/webb/discord-summarizer-rig-blueprint/target/blueprint" "--locked" "--" "--document-hidden-items"
```


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 427f19512..c1cbcd376 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3619,7 +3619,7 @@ version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
 dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -9206,7 +9206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
 dependencies = [
  "cfg-if 1.0.0",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -19057,7 +19057,7 @@ version = "1.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "digest 0.10.7",
  "rand",
  "static_assertions",
@@ -19868,7 +19868,7 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
 dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
diff --git a/blueprints/examples/Cargo.toml b/blueprints/examples/Cargo.toml
index 47821b9c3..c8ea78858 100644
--- a/blueprints/examples/Cargo.toml
+++ b/blueprints/examples/Cargo.toml
@@ -24,6 +24,9 @@ blueprint-sdk = { workspace = true, features = ["std", "testing", "evm", "tangle
 [build-dependencies]
 blueprint-sdk = { workspace = true, features = ["build"] }
 
+[package.metadata.blueprint]
+manager = { Evm = "ExampleBlueprint" }
+master_revision = "Latest"
 
 [features]
 default = ["std"]
diff --git a/blueprints/examples/build.rs b/blueprints/examples/build.rs
index ac1740da6..5d60b0cfb 100644
--- a/blueprints/examples/build.rs
+++ b/blueprints/examples/build.rs
@@ -1,5 +1,10 @@
+use blueprint_sdk::build::blueprint_metadata;
+
 fn main() {
     println!("cargo:rerun-if-changed=src/lib.rs");
     println!("cargo:rerun-if-changed=src/main.rs");
+    blueprint_sdk::build::utils::soldeer_install();
+    blueprint_sdk::build::utils::soldeer_update();
     blueprint_sdk::build::utils::build_contracts(vec!["contracts"]);
+    blueprint_metadata::generate_json();
 }
diff --git a/blueprints/examples/contracts/foundry.toml b/blueprints/examples/contracts/foundry.toml
deleted file mode 100644
index 25b918f9c..000000000
--- a/blueprints/examples/contracts/foundry.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[profile.default]
-src = "src"
-out = "out"
-libs = ["lib"]
-
-# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
diff --git a/blueprints/examples/contracts/src/ExampleBlueprint.sol b/blueprints/examples/contracts/src/ExampleBlueprint.sol
new file mode 100644
index 000000000..f382d6fad
--- /dev/null
+++ b/blueprints/examples/contracts/src/ExampleBlueprint.sol
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "tnt-core/src/BlueprintServiceManagerBase.sol";
+import "examples/ExampleInstance.sol";
+
+/**
+ * @title ExampleBlueprint
+ * @dev This contract is an example of a service blueprint that provides a single
+ * service to square a number. It demonstrates the lifecycle hooks that can be
+ * implemented in a service blueprint.
+ */
+contract ExampleBlueprint is BlueprintServiceManagerBase {
+    /**
+     * @dev A mapping of all service operators registered with the blueprint.
+     * The key is the operator's address and the value is the operator's details.
+     */
+    mapping(address => bytes) public operators;
+    /**
+     * @dev A mapping of all service instances requested from the blueprint.
+     * The key is the service ID and the value is the service operator's address.
+     */
+    mapping(uint64 => address) public serviceInstances;
+
+    /**
+     * @dev Hook for service initialization. Called when a service is initialized.
+     * This hook is called after the service is approved from all of the operators.
+     *
+     * @param requestId The ID of the request.
+     * @param serviceId The ID of the service.
+     * @param owner The owner of the service.
+     * @param permittedCallers  The list of permitted callers for the service.
+     * @param ttl The time-to-live for the service.
+     */
+    function onServiceInitialized(
+        uint64 requestId,
+        uint64 serviceId,
+        address owner,
+        address[] calldata permittedCallers,
+        uint64 ttl
+    )
+    external
+    virtual
+    override
+    onlyFromMaster
+    {
+        ExampleInstance deployed = new ExampleInstance(serviceId);
+        serviceInstances[serviceId] = address(deployed);
+    }
+
+    /**
+     * @dev Hook for handling job result. Called when operators send the result
+     * of a job execution.
+     * @param serviceId The ID of the service related to the job.
+     * @param job The job identifier.
+     * @param jobCallId The unique ID for the job call.
+     * @param operator The operator sending the result in bytes format.
+     * @param inputs Inputs used for the job execution in bytes format.
+     * @param outputs Outputs resulting from the job execution in bytes format.
+     */
+    function onJobResult(
+        uint64 serviceId,
+        uint8 job,
+        uint64 jobCallId,
+        ServiceOperators.OperatorPreferences calldata operator,
+        bytes calldata inputs,
+        bytes calldata outputs
+    )
+    external
+    payable
+    virtual
+    override
+    onlyFromMaster
+    {
+        if (jobCallId == 0) {
+            // Decode the output
+            uint256 output = abi.decode(outputs, (uint256));
+            // For this example, we're just requiring the result to be 1
+            bool isValid = output == 1;
+            require(isValid, "Invalid result");
+        }
+    }
+}
diff --git a/blueprints/examples/contracts/src/ExampleInstance.sol b/blueprints/examples/contracts/src/ExampleInstance.sol
new file mode 100644
index 000000000..0a25a0de8
--- /dev/null
+++ b/blueprints/examples/contracts/src/ExampleInstance.sol
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+/**
+ * @title ExampleInstance
+ * @dev This contract represents an instance of the Example service.
+ * It is deployed when a user requests a new service instance and handles the
+ * actual squaring computation.
+ */
+contract ExampleInstance {
+    // Address of the blueprint that created this instance
+    address public immutable blueprint;
+
+    // Service ID assigned by the root chain
+    uint64 public immutable serviceId;
+
+    constructor(uint64 _serviceId) {
+        blueprint = msg.sender;
+        serviceId = _serviceId;
+    }
+}
\ No newline at end of file
diff --git a/blueprints/examples/foundry.toml b/blueprints/examples/foundry.toml
new file mode 100644
index 000000000..d497e9a26
--- /dev/null
+++ b/blueprints/examples/foundry.toml
@@ -0,0 +1,10 @@
+[profile.default]
+src = "contracts/src"
+out = "contracts/out"
+libs = ["dependencies"]
+remappings = ["examples/=contracts/src/"]
+auto_detect_remappings = true
+solc_version = "0.8.20"
+
+[dependencies]
+tnt-core = "0.2.0"
diff --git a/blueprints/examples/remappings.txt b/blueprints/examples/remappings.txt
new file mode 100644
index 000000000..bd65f0707
--- /dev/null
+++ b/blueprints/examples/remappings.txt
@@ -0,0 +1,1 @@
+tnt-core/=dependencies/tnt-core-0.2.0/
diff --git a/blueprints/examples/soldeer.lock b/blueprints/examples/soldeer.lock
new file mode 100644
index 000000000..bcb749f8a
--- /dev/null
+++ b/blueprints/examples/soldeer.lock
@@ -0,0 +1,6 @@
+[[dependencies]]
+name = "tnt-core"
+version = "0.2.0"
+url = "https://soldeer-revisions.s3.amazonaws.com/tnt-core/0_2_0_28-01-2025_15:09:29_tnt-core.zip"
+checksum = "77f5a4fb2e7257f46ac8642ce7daa5ae98a2107a934fc0c65a6143ccca36ea7e"
+integrity = "db8abe72d2bada2a6730fbe0abf447ddb44a63d31e1a08ed97d049c9253d592a"
diff --git a/blueprints/examples/src/eigen_context.rs b/blueprints/examples/src/eigen_context.rs
index f94a1badc..c92d9ce0a 100644
--- a/blueprints/examples/src/eigen_context.rs
+++ b/blueprints/examples/src/eigen_context.rs
@@ -57,7 +57,7 @@ pub async fn constructor(
 
 #[job(
     id = 0,
-    params(event, log),
+    params(task_index, log_index, block_number),
     event_listener(
         listener = EvmContractEventListener<ExampleTaskManager::NewTaskCreated>,
         instance = ExampleTaskManager,
@@ -67,8 +67,9 @@ pub async fn constructor(
 )]
 pub async fn handle_job(
     ctx: ExampleEigenContext,
-    event: ExampleTaskManager::NewTaskCreated,
-    log: Log,
+    task_index: u32,
+    log_index: u64,
+    block_number: u64,
 ) -> Result<u32, Error> {
     // Example address, quorum number, and index
     let operator_addr = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8");
@@ -223,6 +224,10 @@ pub async fn handle_job(
 
 pub async fn handle_events(
     event: (ExampleTaskManager::NewTaskCreated, Log),
-) -> Result<Option<(ExampleTaskManager::NewTaskCreated, Log)>, ProcessorError> {
-    Ok(Some(event))
+) -> Result<Option<(u32, u64, u64)>, ProcessorError> {
+    let (event, log) = event;
+    let task_id = event.taskIndex;
+    let log_index = log.log_index.unwrap_or_default();
+    let block_number = log.block_number.unwrap_or_default();
+    Ok(Some((task_id, block_number, log_index)))
 }
diff --git a/blueprints/examples/src/periodic_web_poller.rs b/blueprints/examples/src/periodic_web_poller.rs
index aa61c8af1..a373ab9fa 100644
--- a/blueprints/examples/src/periodic_web_poller.rs
+++ b/blueprints/examples/src/periodic_web_poller.rs
@@ -1,10 +1,9 @@
 use blueprint_sdk::alloy::transports::http::reqwest;
-use blueprint_sdk::event_listeners::core::{EventListener, InitializableEventHandler};
+use blueprint_sdk::event_listeners::core::InitializableEventHandler;
 use blueprint_sdk::event_listeners::cronjob::{
     error::Error as CronJobError, CronJob, CronJobDefinition,
 };
 use blueprint_sdk::logging::info;
-use blueprint_sdk::macros::ext::async_trait::async_trait;
 use blueprint_sdk::{job, Error};
 
 type ProcessorError = blueprint_sdk::event_listeners::core::Error<CronJobError>;
@@ -34,7 +33,7 @@ impl CronJobDefinition for WebPollerContext {
 }
 
 #[job(
-    id = 0,
+    id = 1,
     event_listener(
         listener = CronJob<WebPollerContext>,
         post_processor = post_process,
@@ -68,6 +67,10 @@ pub async fn web_poller(context: WebPollerContext) -> Result<u8, Error> {
 pub async fn post_process(job_output: u8) -> Result<(), ProcessorError> {
     info!("Running web_poller post-processor on value: {job_output}");
     if job_output == 1 {
+        let result = std::env::var("WEB_POLLER_RESULT").unwrap_or("0".to_string());
+        let result = result.parse::<u64>().unwrap_or(0);
+        let result = result + 1;
+        std::env::set_var("WEB_POLLER_RESULT", result.to_string());
         Ok(())
     } else {
         Err(ProcessorError::EventHandler(
@@ -75,42 +78,3 @@ pub async fn post_process(job_output: u8) -> Result<(), ProcessorError> {
         ))
     }
 }
-
-pub struct WebPoller {
-    pub context: WebPollerContext,
-}
-
-#[async_trait]
-impl EventListener<serde_json::Value, WebPollerContext> for WebPoller {
-    type ProcessorError = CronJobError;
-
-    async fn new(context: &WebPollerContext) -> Result<Self, ProcessorError>
-    where
-        Self: Sized,
-    {
-        Ok(Self {
-            context: context.clone(),
-        })
-    }
-
-    /// Implement the logic that polls the web server
-    async fn next_event(&mut self) -> Option<serde_json::Value> {
-        // Send a GET request to the JSONPlaceholder API
-        let response = self
-            .context
-            .client
-            .get("https://jsonplaceholder.typicode.com/todos/10")
-            .send()
-            .await
-            .ok()?;
-
-        // Check if the request was successful
-        if response.status().is_success() {
-            // Parse the JSON response
-            let resp: serde_json::Value = response.json().await.ok()?;
-            Some(resp)
-        } else {
-            None
-        }
-    }
-}
diff --git a/blueprints/examples/src/raw_tangle_events.rs b/blueprints/examples/src/raw_tangle_events.rs
index 63ad64a11..b90cab575 100644
--- a/blueprints/examples/src/raw_tangle_events.rs
+++ b/blueprints/examples/src/raw_tangle_events.rs
@@ -39,7 +39,7 @@ pub async fn constructor(
 }
 
 #[job(
-    id = 0,
+    id = 2,
     event_listener(
         listener = TangleEventListener<MyContext>,
     ),
@@ -52,6 +52,13 @@ pub fn raw(event: TangleEvent<MyContext>, context: MyContext) -> Result<u64, blu
         .flatten()
     {
         info!("Found a balance transfer: {balance_transfer:?}");
+
+        let result = std::env::var("RAW_EVENT_RESULT").unwrap_or("0".to_string());
+        let result = result.parse::<u64>().unwrap_or(0);
+        let result = result + 1;
+        std::env::set_var("RAW_EVENT_RESULT", result.to_string());
+
+        return Ok(1);
     }
     Ok(0)
 }
diff --git a/blueprints/examples/src/services_context.rs b/blueprints/examples/src/services_context.rs
index f4815f1d9..62e4adcd5 100644
--- a/blueprints/examples/src/services_context.rs
+++ b/blueprints/examples/src/services_context.rs
@@ -45,7 +45,7 @@ pub async fn constructor(
 }
 
 #[job(
-    id = 2,
+    id = 3,
     params(job_details),
     event_listener(
         listener = TangleEventListener<ExampleServiceContext, JobCalled>,
@@ -57,25 +57,18 @@ pub async fn handle_job(
     context: ExampleServiceContext,
     job_details: Vec<u8>,
 ) -> Result<u64, blueprint_sdk::Error> {
-    let client = context.tangle_client().await.unwrap();
-    let blueprint_id = client.blueprint_id().await.unwrap();
+    let client = context.tangle_client().await?;
+    let blueprint_id = client.blueprint_id().await?;
     let block = client.now().await.unwrap();
-    let blueprint_owner = client
-        .current_blueprint_owner(block, blueprint_id)
-        .await
-        .unwrap();
-    let blueprint = client
-        .current_blueprint_owner(block, blueprint_id)
-        .await
-        .unwrap();
+    let blueprint_owner = client.current_blueprint_owner(block, blueprint_id).await?;
+    let blueprint = client.current_blueprint_owner(block, blueprint_id).await?;
     let operators_and_percents = client
         .current_service_operators(block, blueprint_id)
-        .await
-        .unwrap();
+        .await?;
     let operators = operators_and_percents
         .iter()
         .map(|(op, per)| op)
         .cloned()
         .collect::<Vec<AccountId32>>();
-    Ok(0)
+    Ok(1)
 }
diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml
index 4bb703a42..da709ceb8 100644
--- a/crates/macros/Cargo.toml
+++ b/crates/macros/Cargo.toml
@@ -94,6 +94,7 @@ evm = [
 
 eigenlayer = [
 	"gadget-contexts/eigenlayer",
+	"dep:tokio",
 ]
 
 networking = ["gadget-context-derive/networking", "gadget-contexts/networking"]
diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs
index 2cd7034ac..8340f31b4 100644
--- a/crates/macros/src/lib.rs
+++ b/crates/macros/src/lib.rs
@@ -7,7 +7,12 @@ compile_error!("evm feature is currently only supported on std");
 pub mod ext {
     pub use async_trait;
     pub use futures;
-    #[cfg(any(feature = "tangle", feature = "evm", feature = "std"))]
+    #[cfg(any(
+        feature = "tangle",
+        feature = "evm",
+        feature = "eigenlayer",
+        feature = "std"
+    ))]
     pub use tokio;
 
     #[cfg(feature = "std")]


Test patch:
diff --git a/blueprints/examples/src/tests.rs b/blueprints/examples/src/tests.rs
index e71714d5e..f415e2065 100644
--- a/blueprints/examples/src/tests.rs
+++ b/blueprints/examples/src/tests.rs
@@ -5,21 +5,32 @@ use blueprint_sdk::alloy::providers::Provider;
 use blueprint_sdk::alloy::transports::http::reqwest::Url;
 use blueprint_sdk::config::protocol::EigenlayerContractAddresses;
 use blueprint_sdk::config::supported_chains::SupportedChains;
-use blueprint_sdk::config::ContextConfig;
-use blueprint_sdk::logging::{info, setup_log};
+use blueprint_sdk::config::{ContextConfig, GadgetConfiguration};
+use blueprint_sdk::contexts::keystore::KeystoreContext;
+use blueprint_sdk::contexts::tangle::TangleClientContext;
+use blueprint_sdk::crypto::sp_core::SpSr25519;
+use blueprint_sdk::crypto::tangle_pair_signer::TanglePairSigner;
+use blueprint_sdk::keystore::backends::Backend;
+use blueprint_sdk::logging::{error, info, setup_log};
+use blueprint_sdk::macros::ext::blueprint_serde::BoundedVec;
 use blueprint_sdk::runners::core::runner::BlueprintRunner;
 use blueprint_sdk::runners::eigenlayer::bls::EigenlayerBLSConfig;
 use blueprint_sdk::std::path::Path;
+use blueprint_sdk::std::rand::random;
 use blueprint_sdk::std::time::Duration;
+use blueprint_sdk::tangle_subxt::subxt::utils::AccountId32;
+use blueprint_sdk::tangle_subxt::tangle_testnet_runtime::api::tx;
 use blueprint_sdk::testing::tempfile;
 use blueprint_sdk::testing::utils::anvil::keys::{inject_anvil_key, ANVIL_PRIVATE_KEYS};
 use blueprint_sdk::testing::utils::anvil::{get_receipt, start_default_anvil_testnet};
 use blueprint_sdk::testing::utils::harness::TestHarness;
 use blueprint_sdk::testing::utils::runner::TestEnv;
-use blueprint_sdk::testing::utils::tangle::{OutputValue, TangleTestHarness};
+use blueprint_sdk::testing::utils::tangle::{InputValue, OutputValue, TangleTestHarness};
 use blueprint_sdk::tokio;
+use blueprint_sdk::tokio::task::JoinHandle;
 use blueprint_sdk::tokio::time::timeout;
 use blueprint_sdk::utils::evm::get_provider_http;
+use blueprint_sdk::utils::tangle::send;
 use color_eyre::Result;
 
 #[tokio::test]
@@ -77,11 +88,13 @@ async fn test_eigenlayer_context() {
     let tmp_dir = tempfile::TempDir::new().unwrap();
     let keystore_path = &format!("{}", tmp_dir.path().display());
     let keystore_path = Path::new(keystore_path);
-    let keystore_uri = keystore_path.join(format!("keystores/{}", uuid::Uuid::new_v4()));
+    let keystore_uri = keystore_path.join(format!("keystores/{}/", uuid::Uuid::new_v4()));
+    // std::fs::create_dir_all(&keystore_uri).expect("Failed to create keystore directory");
     inject_anvil_key(&keystore_uri, ANVIL_PRIVATE_KEYS[1]).unwrap();
     let keystore_uri_normalized =
         std::path::absolute(&keystore_uri).expect("Failed to resolve keystore URI");
-    let keystore_uri_str = format!("file:{}", keystore_uri_normalized.display());
+    // Use the direct path without the file: prefix
+    let keystore_uri_str = keystore_uri_normalized.display().to_string();
 
     let config = ContextConfig::create_eigenlayer_config(
         url,
@@ -94,7 +107,8 @@ async fn test_eigenlayer_context() {
     let env = blueprint_sdk::config::load(config).expect("Failed to load environment");
 
     let mut blueprint = BlueprintRunner::new(
-        EigenlayerBLSConfig::new(Address::default(), Address::default()),
+        EigenlayerBLSConfig::new(Address::default(), Address::default())
+            .with_exit_after_register(false),
         env.clone(),
     );
 
@@ -108,7 +122,7 @@ async fn test_eigenlayer_context() {
             }
             _ = tokio::task::spawn(async move {
                 loop {
-                    tokio::time::sleep(Duration::from_millis(3000)).await;
+                    tokio::time::sleep(Duration::from_millis(1000)).await;
                     let result = std::env::var("EIGEN_CONTEXT_STATUS").unwrap_or_else(|_| "false".to_string());
                     match result.as_str() {
                         "true" => {
@@ -148,12 +162,64 @@ async fn test_periodic_web_poller() -> Result<()> {
     // Run the test environment
     test_env.run_runner().await.unwrap();
 
-    // Wait for a few seconds to allow the job to execute
-    tokio::time::sleep(Duration::from_secs(10)).await;
+    // Execute job and verify result
+    let result = tokio::select! {
+        result = harness.execute_job(service_id, 1, vec![], vec![OutputValue::Uint64(1)]) => {
+            match result {
+                Ok(_) => {Ok(())},
+                Err(e) => Err(e),
+            }
+        }
+        _ = tokio::task::spawn(async move {
+            loop {
+                tokio::time::sleep(Duration::from_millis(1000)).await;
+                let result = std::env::var("WEB_POLLER_RESULT").unwrap_or_else(|_| "0".to_string());
+                match result.as_str() {
+                    "3" => {
+                        break;
+                    }
+                    _ => {
+                        info!("Waiting for WEB_POLLER_RESULT to be 3...");
+                    }
+                }
+            }
+        }) => {
+            Ok(())
+        }
+    };
+    assert!(result.is_ok());
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn test_services_context() -> Result<()> {
+    setup_log();
+
+    // Initialize test harness
+    let temp_dir = tempfile::TempDir::new()?;
+    let harness = TangleTestHarness::setup(temp_dir).await?;
+    let env = harness.env().clone();
+
+    // Setup service
+    let (mut test_env, service_id, _blueprint_id) = harness.setup_services(false).await?;
+
+    // Add the raw tangle events job
+    test_env.add_job(crate::services_context::constructor(env.clone()).await?);
+
+    // Run the test environment
+    let _test_handle = tokio::spawn(async move {
+        test_env.run_runner().await.unwrap();
+    });
 
     // Execute job and verify result
     let results = harness
-        .execute_job(service_id, 0, vec![], vec![OutputValue::Uint64(1)])
+        .execute_job(
+            service_id,
+            3,
+            vec![InputValue::List(BoundedVec(vec![InputValue::Uint8(0)]))],
+            vec![OutputValue::Uint64(1)],
+        )
         .await?;
 
     assert_eq!(results.service_id, service_id);
@@ -175,17 +241,69 @@ async fn test_raw_tangle_events() -> Result<()> {
     // Add the raw tangle events job
     test_env.add_job(crate::raw_tangle_events::constructor(env.clone()).await?);
 
+    // Spawn the balance transfer task
+    let _handle = balance_transfer_event(env.clone()).await.unwrap();
+
     // Run the test environment
     test_env.run_runner().await.unwrap();
 
-    // Wait for a few seconds to allow the job to execute
-    tokio::time::sleep(Duration::from_secs(10)).await;
-
     // Execute job and verify result
-    let results = harness
-        .execute_job(service_id, 0, vec![], vec![OutputValue::Uint64(0)])
-        .await?;
+    let result = tokio::select! {
+        result = harness.execute_job(service_id, 2, vec![], vec![OutputValue::Uint64(1)]) => {
+            match result {
+                Ok(_) => {Ok(())},
+                Err(e) => Err(e),
+            }
+        }
+        _ = tokio::task::spawn(async move {
+            loop {
+                tokio::time::sleep(Duration::from_millis(1000)).await;
+                let result = std::env::var("RAW_EVENT_RESULT").unwrap_or_else(|_| "0".to_string());
+                match result.as_str() {
+                    "1" => {
+                        break;
+                    }
+                    _ => {
+                        info!("Waiting for RAW_EVENT_RESULT to be 1...");
+                    }
+                }
+            }
+        }) => {
+            Ok(())
+        }
+    };
+    assert!(result.is_ok());
 
-    assert_eq!(results.service_id, service_id);
     Ok(())
 }
+
+async fn balance_transfer_event(env: GadgetConfiguration) -> Result<JoinHandle<()>> {
+    let client = env.tangle_client().await?;
+    let transfer_client = client.clone();
+    let signer = env.keystore().first_local::<SpSr25519>().unwrap();
+    let sr_pair = TanglePairSigner::new(env.keystore().get_secret::<SpSr25519>(&signer).unwrap().0);
+    let transfer_id = AccountId32::from(signer.0 .0);
+    let receiver_id = AccountId32::from(random::<[u8; 32]>());
+
+    // Spawn task to transfer balance into Operator's account on Tangle
+    let transfer_task = async move {
+        tokio::time::sleep(Duration::from_secs(5)).await;
+        info!(
+            "Transferring balance from {:?} to {:?}",
+            transfer_id, receiver_id
+        );
+        let transfer_tx = tx()
+            .balances()
+            .transfer_allow_death(receiver_id.into(), 1_000_000_000_000_000_000);
+        match send(&transfer_client, &sr_pair, &transfer_tx).await {
+            Ok(result) => {
+                info!("Transfer Result: {:?}", result);
+            }
+            Err(e) => {
+                error!("Balance Transfer Error: {:?}", e);
+            }
+        }
+    };
+    let transfer_handle = tokio::task::spawn(transfer_task);
+    Ok(transfer_handle)
+}
diff --git a/blueprints/incredible-squaring-eigenlayer/src/tests.rs b/blueprints/incredible-squaring-eigenlayer/src/tests.rs
index f0239b97b..3e0960835 100644
--- a/blueprints/incredible-squaring-eigenlayer/src/tests.rs
+++ b/blueprints/incredible-squaring-eigenlayer/src/tests.rs
@@ -119,9 +119,6 @@ async fn run_eigenlayer_incredible_squaring_test(
 
     test_env.run_runner().await.unwrap();
 
-    // Wait for environment to initialize
-    tokio::time::sleep(Duration::from_secs(5)).await;
-
     // Wait for the process to complete or timeout
     let timeout_duration = Duration::from_secs(300);
     let result = wait_for_responses(
diff --git a/blueprints/incredible-squaring/src/tests.rs b/blueprints/incredible-squaring/src/tests.rs
index 243dbb02e..05c0eddc1 100644
--- a/blueprints/incredible-squaring/src/tests.rs
+++ b/blueprints/incredible-squaring/src/tests.rs
@@ -49,7 +49,7 @@ async fn test_incredible_squaring() -> Result<()> {
 }
 
 #[tokio::test]
-async fn test_pre_registration_incredible_squaring() -> Result<()> {
+async fn test_pre_register_incredible_squaring() -> Result<()> {
     setup_log();
 
     // Initialize test harness (node, keys, deployment)
diff --git a/crates/testing-utils/anvil/src/keys.rs b/crates/testing-utils/anvil/src/keys.rs
index feb27d7d0..c55336d33 100644
--- a/crates/testing-utils/anvil/src/keys.rs
+++ b/crates/testing-utils/anvil/src/keys.rs
@@ -27,6 +27,10 @@ pub const ANVIL_PRIVATE_KEYS: [&str; 10] = [
 /// - Fails if the given index is out of bounds
 /// - May fail if the keystore path cannot be created or accessed
 pub fn inject_anvil_key<P: AsRef<Path>>(keystore_path: P, seed: &str) -> Result<(), Error> {
+    let keystore_path = keystore_path.as_ref();
+    if !keystore_path.exists() {
+        std::fs::create_dir_all(keystore_path).map_err(|e| Error::Keystore(e.into()))?;
+    }
     let config = KeystoreConfig::new().fs_root(keystore_path);
     let keystore = Keystore::new(config)?;
 
diff --git a/crates/testing-utils/eigenlayer/src/runner.rs b/crates/testing-utils/eigenlayer/src/runner.rs
index ecc9a035d..74b4e6ada 100644
--- a/crates/testing-utils/eigenlayer/src/runner.rs
+++ b/crates/testing-utils/eigenlayer/src/runner.rs
@@ -5,11 +5,13 @@ use gadget_event_listeners::core::InitializableEventHandler;
 use gadget_runners::core::error::RunnerError as Error;
 use gadget_runners::core::runner::BackgroundService;
 use gadget_runners::eigenlayer::bls::EigenlayerBLSConfig;
+use tokio::task::JoinHandle;
 
 pub struct EigenlayerBLSTestEnv {
     runner: TestRunner,
     config: EigenlayerBLSConfig,
     gadget_config: GadgetConfiguration,
+    runner_handle: Option<JoinHandle<Result<(), Error>>>,
 }
 
 impl TestEnv for EigenlayerBLSTestEnv {
@@ -22,6 +24,7 @@ impl TestEnv for EigenlayerBLSTestEnv {
             runner,
             config,
             gadget_config: env,
+            runner_handle: None,
         })
     }
 
@@ -44,6 +47,49 @@ impl TestEnv for EigenlayerBLSTestEnv {
     }
 
     async fn run_runner(&mut self) -> Result<(), Error> {
-        self.runner.run().await
+        // Spawn the runner in a background task
+        let mut runner = self.runner.clone();
+        let handle = tokio::spawn(async move { runner.run().await });
+
+        self.runner_handle = Some(handle);
+
+        // Brief delay to allow for startup
+        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+
+        // Just check if it failed immediately
+        if let Some(handle) = self.runner_handle.take() {
+            if handle.is_finished() {
+                gadget_logging::info!("Runner task finished OK");
+                match handle.await {
+                    Ok(Ok(())) => Ok(()),
+                    Ok(Err(e)) => {
+                        gadget_logging::error!("Runner failed during startup: {}", e);
+                        Err(Error::Eigenlayer(format!(
+                            "Runner failed during startup: {}",
+                            e
+                        )))
+                    }
+                    Err(e) => {
+                        gadget_logging::error!("Runner task panicked: {}", e);
+                        Err(Error::Eigenlayer(format!("Runner task panicked: {}", e)))
+                    }
+                }
+            } else {
+                // Put the handle back since the runner is still running
+                self.runner_handle = Some(handle);
+                gadget_logging::info!("Runner started successfully");
+                Ok(())
+            }
+        } else {
+            Err(Error::Eigenlayer("Failed to spawn runner task".to_string()))
+        }
+    }
+}
+
+impl Drop for EigenlayerBLSTestEnv {
+    fn drop(&mut self) {
+        if let Some(handle) = &self.runner_handle {
+            handle.abort();
+        }
     }
 }