Skip to content

Conversation

d4mr
Copy link
Member

@d4mr d4mr commented Aug 27, 2025

7702 Changes:

  • Added TwGetDelegationContractResponse struct to encapsulate the delegation contract address.
  • Introduced tw_get_delegation_contract method in BundlerClient to fetch the delegation contract.
  • Updated transaction signing methods in DelegatedAccount and MinimalAccountTransaction to accept delegation contract as a parameter.
  • Created a DelegationContractCache for efficient retrieval of delegation contracts in the executor.

EOA Changes:

  • do not send gas bump transaction if out of funds

Monitoring Changes:

  • record "out_of_funds" as boolean field on stuck eoas metric

Summary by CodeRabbit

  • New Features

    • Network-discovered delegation contract and new RPC to fetch it (replaces hardcoded address).
    • Per-batch identifiers for safer batch popping to reduce lease collisions.
  • Performance

    • Per-chain caching of delegation contract reduces network calls and speeds EIP‑7702 flows.
  • Reliability

    • Skip gas-bump attempts when accounts lack funds to avoid unnecessary retries.
    • Improved retry/backoff and clearer logging around bundler transaction confirmation.
  • Metrics

    • Stuck EOA metric adds an “out_of_funds” label.

…er client

- Added `TwGetDelegationContractResponse` struct to encapsulate the delegation contract address.
- Introduced `tw_get_delegation_contract` method in `BundlerClient` to fetch the delegation contract.
- Updated transaction signing methods in `DelegatedAccount` and `MinimalAccountTransaction` to accept delegation contract as a parameter.
- Refactored integration tests to utilize the new delegation contract handling.
- Created a `DelegationContractCache` for efficient retrieval of delegation contracts in the executor.

These changes enhance the EIP-7702 framework by integrating delegation contract management, improving transaction processing and observability.
Copy link

coderabbitai bot commented Aug 27, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds an RPC to fetch the delegation contract, removes a hardcoded minimal-account address, threads delegation_contract through EIP-7702 signing/authorization, introduces a per-chain cached lookup, wires it into the send path and tests, adds an out-of-funds gate and metric label for EOA-stall handling, and adjusts related error logging and confirmations.

Changes

Cohort / File(s) Summary of changes
RPC client: delegation contract
core/src/rpc_clients/bundler.rs
Adds TwGetDelegationContractResponse (serde camelCase) and BundlerClient::tw_get_delegation_contract() calling RPC tw_getDelegationContract.
EIP-7702 core: constants and auth flow
eip7702-core/src/constants.rs, eip7702-core/src/delegated_account.rs, eip7702-core/src/transaction.rs
Removes public MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS. Simplifies minimal-account detection to prefix+length check (skips target-address comparison). Threads delegation_contract: Address into DelegatedAccount::sign_authorization and MinimalAccountTransaction::add_authorization_if_needed.
EIP-7702 tests
eip7702-core/tests/integration_tests.rs
TestSetup gains delegation_contract: Option<Address>; fetch_and_set_bytecode becomes &mut, fetches the delegation contract via bundler RPC, installs bytecode at that address, and test callsites pass delegation_contract.
Executors: delegation contract cache
executors/src/eip7702_executor/delegation_cache.rs, executors/src/eip7702_executor/mod.rs
New delegation_cache module with DelegationContractCacheKey and DelegationContractCache (moka::future::Cache wrapper). get_delegation_contract(chain) uses SWR-like fetch via bundler_client.tw_get_delegation_contract() and maps errors to EngineError.
Executors: send path integration
executors/src/eip7702_executor/send.rs, server/src/queue/manager.rs
Eip7702SendHandler gains delegation_contract_cache field; startup wiring constructs a moka cache (max_capacity=10000, TTL=24h, TTI=24h) and injects it. Send path fetches delegation_contract from cache and passes it to add_authorization_if_needed; fetch errors map to DelegationCheckFailed.
Executors: confirm error handling
executors/src/eip7702_executor/confirm.rs
Adds tracing.error logging on bundler transaction-hash fetch failure and maps failure to a nack-retry (10s); adds debug log on success, parses transaction hash with explicit error on parse failure, and refines receipt/error mapping and logging.
EOA worker: stall handling and metrics
executors/src/eoa/worker/confirm.rs, executors/src/eoa/worker/mod.rs, executors/src/metrics.rs
Adds an out-of-funds gate (skip gas-bump when balance <= threshold), records out_of_funds in stuck-EOA metric. EoaMetrics::record_stuck_eoa signature gains out_of_funds: bool and histogram adds out_of_funds label. Minor health-init timestamp changes and conditional skip of soft-release on specific store LockLost errors.
Batch-pop lease handling
twmq/src/lib.rs
Adds per-batch pop_id to batch pop flow: passes pop_id into Lua script args, appends pop_id into generated lease_token, and propagates returned lease token into popped job data.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller as EIP-7702 Send Handler
  participant Cache as DelegationContractCache
  participant Bundler as Bundler RPC
  participant Tx as MinimalAccountTransaction

  Caller->>Cache: get_delegation_contract(chain)
  alt cache hit
    Cache-->>Caller: delegation_contract (Address)
  else cache miss/expired
    Cache->>Bundler: tw_getDelegationContract()
    Bundler-->>Cache: { delegationContract }
    Cache-->>Caller: delegation_contract (Address)
  end

  Caller->>Tx: add_authorization_if_needed(signer, cred, delegation_contract)
  Tx->>Tx: sign_authorization(..., delegation_contract)
  Tx-->>Caller: authorized tx or error
Loading
sequenceDiagram
  autonumber
  participant Worker as EOA Worker
  participant Metrics as EoaMetrics

  Worker->>Worker: compute time_since_movement
  Worker->>Worker: is_out_of_funds = balance <= threshold
  alt stalled & has waiting & not is_out_of_funds
    Worker->>Worker: attempt gas bump
    opt failure
      Worker->>Worker: warn (gas bump failed)
    end
  else stalled & is_out_of_funds
    Worker->>Worker: warn (skip bump, out_of_funds)
  end
  Worker->>Metrics: record_stuck_eoa(eoa, chain_id, seconds, is_out_of_funds)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 80b47ef and 2cca023.

📒 Files selected for processing (2)
  • executors/src/eip7702_executor/confirm.rs (3 hunks)
  • executors/src/eip7702_executor/send.rs (4 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pb/eoa-monitoring-out-of-funds

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
executors/src/metrics.rs (1)

106-119: Critical: duplicate metric registration in get_metrics() fallback.

When no custom metrics are set, get_metrics() constructs a new ExecutorMetrics each call, re-registering the same metric names in the same registry and likely panicking (“already registered”). Reuse a single default Arc instead.

Apply:

 lazy_static! {
   /// Default metrics registry for executors (fallback if no external registry provided)
   static ref DEFAULT_EXECUTOR_METRICS_REGISTRY: Registry = Registry::new();
   
   /// Default executor metrics instance (used when no external metrics are provided)
-  static ref DEFAULT_EXECUTOR_METRICS: ExecutorMetrics = 
-      ExecutorMetrics::new(&DEFAULT_EXECUTOR_METRICS_REGISTRY)
-          .expect("Failed to create default executor metrics");
+  static ref DEFAULT_EXECUTOR_METRICS: std::sync::Arc<ExecutorMetrics> =
+      std::sync::Arc::new(
+          ExecutorMetrics::new(&DEFAULT_EXECUTOR_METRICS_REGISTRY)
+              .expect("Failed to create default executor metrics")
+      );
 
   /// Global metrics instance - can be set by the binary crate or uses default
   static ref EXECUTOR_METRICS_INSTANCE: std::sync::RwLock<Option<Arc<ExecutorMetrics>>> = 
       std::sync::RwLock::new(None);
 }
 
 /// Get the current metrics instance (either custom or default)
 fn get_metrics() -> Arc<ExecutorMetrics> {
   let instance = EXECUTOR_METRICS_INSTANCE.read().unwrap();
   match instance.as_ref() {
     Some(metrics) => metrics.clone(),
     None => {
       // Use default metrics if no custom metrics were set
       // This ensures backward compatibility
       drop(instance); // Release read lock
-      Arc::new(ExecutorMetrics::new(&DEFAULT_EXECUTOR_METRICS_REGISTRY)
-          .expect("Failed to create default metrics"))
+      DEFAULT_EXECUTOR_METRICS.clone()
     }
   }
 }
eip7702-core/tests/integration_tests.rs (1)

135-138: Test will panic: bundler_client() is unimplemented but invoked in fetch_and_set_bytecode()

TestChain::bundler_client() panics, yet fetch_and_set_bytecode() calls it. The test will always abort before setting delegation_contract.

Apply a minimal fix by instantiating a BundlerClient directly against Base Sepolia inside fetch_and_set_bytecode(), avoiding TestChain::bundler_client():

-        let delegation_contract_response = self
-            .chain
-            .bundler_client()
-            .tw_get_delegation_contract()
-            .await?;
+        // Use a real BundlerClient for Base Sepolia in tests
+        let bundler =
+            engine_core::rpc_clients::BundlerClient::new(base_sepolia_url, Default::default());
+        let delegation_contract_response = bundler.tw_get_delegation_contract().await?;

Alternatively, implement a real bundler_client() on TestChain (store a BundlerClient field) and point it to a valid bundler endpoint. Let me know which direction you prefer; I can provide a full patch for either option.

Also applies to: 380-390

🧹 Nitpick comments (15)
eip7702-core/src/constants.rs (2)

3-6: Fix RPC name in the note (snake_case).

The RPC is tw_get_delegation_contract throughout the codebase; here it’s written as tw_getDelegationContract. Align to avoid confusion.

Apply:

-// NOTE!: do not hardcode. If needed later, use tw_getDelegationContract
+// NOTE!: do not hardcode. If needed later, use tw_get_delegation_contract

1-1: Remove unused imports.

Address and address! aren’t used anymore after removing the hardcoded constant. This will trigger dead_code/unused_imports warnings.

-use alloy::primitives::{Address, address};
+// No imports needed here
executors/src/metrics.rs (1)

283-291: Strengthen test: assert presence of the new label (both values).

Validate export includes out_of_funds label and both True/False samples when recorded.

         let metrics_output = export_default_metrics().expect("Should be able to export default metrics");
         assert!(metrics_output.contains("tw_engine_executor_transaction_queued_to_sent_duration_seconds"));
         assert!(metrics_output.contains("tw_engine_executor_transaction_queued_to_confirmed_duration_seconds"));
         assert!(metrics_output.contains("tw_engine_eoa_executor_job_processing_duration_seconds"));
         assert!(metrics_output.contains("tw_engine_executor_eoa_degraded_send_duration_seconds"));
         assert!(metrics_output.contains("tw_engine_executor_eoa_degraded_confirmation_duration_seconds"));
         assert!(metrics_output.contains("tw_engine_executor_eoa_stuck_duration_seconds"));
+        // Verify the new label is present and both boolean values can appear
+        assert!(metrics_output.contains("out_of_funds"));
+        // Record a 'true' sample and re-export to check both values
+        eoa_metrics.record_stuck_eoa(test_address, 1, 900.0, true);
+        let metrics_output = export_default_metrics().expect("Should be able to export default metrics");
+        assert!(metrics_output.contains("out_of_funds=\"false\"") || metrics_output.contains("out_of_funds=\"true\""));
eip7702-core/src/delegated_account.rs (2)

30-33: Update doc to match behavior (prefix/length check only).

The function no longer validates the target equals a specific minimal account. Reflect this to avoid misleading callers.

-    /// Check if the EOA has EIP-7702 delegation to the minimal account implementation
+    /// Check if the EOA has EIP-7702 delegation (validates prefix/length only; does not validate the target contract)

63-66: Optional: log detected delegation target at trace level for debugging.

Lightweight visibility without re-introducing hardcoded checks.

-        // NOTE!: skip the actual delegated target address check for now
-        // extremely unlikely that an EOA being used with engine is delegated to a non-minimal account
-        // Potential source for fringe edge cases, please verify delegated target address if debugging 7702 execution issues
+        // NOTE: We intentionally skip asserting the target equals a specific implementation.
+        // For debug visibility, log the delegation target at trace level.
+        let target_address = Address::from_slice(&code[3..23]);
+        tracing::trace!(
+            eoa_address = ?self.eoa_address,
+            target_address = ?target_address,
+            "EIP-7702 delegation detected"
+        );
executors/src/eoa/worker/mod.rs (1)

27-28: Decouple time util from metrics module.

Prefer using the existing eoa::store::now() to avoid depending on metrics for time.

-use crate::metrics::{
-    EoaMetrics, calculate_duration_seconds, current_timestamp_ms, record_eoa_job_processing_time,
-};
+use crate::metrics::{EoaMetrics, calculate_duration_seconds, record_eoa_job_processing_time};
+use crate::eoa::store::now as current_timestamp_ms;
server/src/queue/manager.rs (1)

191-199: Make cache tuning configurable and consider warmup to avoid first-hit latency

  • Consider plumbing max_capacity/TTL/TTI from config (QueueConfig or a new cache section) to tune in prod without redeploys.
  • Optional: warm the cache per configured chain at startup to avoid the first tx paying the RPC latency.
eip7702-core/src/transaction.rs (1)

192-205: API change verified: all call sites updated; add documentation

I’ve confirmed that both usages of add_authorization_if_needed now include the delegation_contract argument:

  • eip7702-core/tests/integration_tests.rs (around line 697)
  • executors/src/eip7702_executor/send.rs (around line 233)

Please add a doc comment on the method to explain the new parameter. For example:

-    pub async fn add_authorization_if_needed<S: AccountSigner>(
+    /// If the target is not a minimal account, signs and attaches an EIP-7702
+    /// authorization payload using the given `delegation_contract` address.
+    ///
+    /// Minimal accounts short-circuit and return immediately without using it.
+    pub async fn add_authorization_if_needed<S: AccountSigner>(
         mut self,
         signer: &S,
         credentials: &SigningCredential,
         delegation_contract: Address,
     ) -> Result<Self, EngineError> {
executors/src/eip7702_executor/delegation_cache.rs (2)

19-21: Hide internal cache to maintain invariants

Expose a getter if needed; avoid letting external code mutate/replace the cache internals.

Apply this diff:

-#[derive(Clone)]
-pub struct DelegationContractCache {
-    pub inner: moka::future::Cache<DelegationContractCacheKey, Address>,
-}
+#[derive(Clone)]
+pub struct DelegationContractCache {
+    inner: moka::future::Cache<DelegationContractCacheKey, Address>,
+}
+
+impl DelegationContractCache {
+    pub fn inner(&self) -> &moka::future::Cache<DelegationContractCacheKey, Address> {
+        &self.inner
+    }
+}

38-66: Minor: avoid repeated chain_id() calls in the loader

Store once for logging clarity and tiny perf/readability win.

Apply this diff:

-        let result = self
+        let chain_id = chain.chain_id();
+        let result = self
             .inner
             .try_get_with(cache_key, async {
-                tracing::debug!(
-                    chain_id = chain.chain_id(),
+                tracing::debug!(
+                    chain_id = chain_id,
                     "Fetching delegation contract from bundler"
                 );
 
                 let TwGetDelegationContractResponse {
                     delegation_contract,
                 } = chain
                     .bundler_client()
                     .tw_get_delegation_contract()
                     .await
                     .map_err(|e| e.to_engine_bundler_error(chain))?;
 
-                tracing::debug!(
-                    chain_id = chain.chain_id(),
+                tracing::debug!(
+                    chain_id = chain_id,
                     delegation_contract = ?delegation_contract,
                     "Successfully fetched and cached delegation contract"
                 );
eip7702-core/tests/integration_tests.rs (4)

121-129: Bundler URL equals Anvil RPC URL; tw_get_delegation_contract won’t exist there

Returning rpc_url from bundler_url is misleading for this test because Anvil won’t implement the bundler RPC. If you wire TestChain::bundler_client(), ensure it targets a real bundler endpoint (e.g., via a separate bundler_url or env override).


391-406: Log/message terminology: say “delegation contract,” not “minimal account implementation”

The printed message still references “minimal account implementation,” which can confuse when debugging delegation contract issues.

-        println!(
-            "Set bytecode for minimal account implementation at {:?}",
-            delegation_contract_response.delegation_contract
-        );
+        println!(
+            "Set bytecode for delegation contract at {:?}",
+            delegation_contract_response.delegation_contract
+        );

661-663: Network dependency in tests may flake CI

Fetching bytecode from a public RPC at runtime makes tests non-hermetic. Consider vendoring the delegation contract bytecode (like MockERC20) or stubbing tw_get_delegation_contract during tests.


697-702: Early assertion for delegation_contract improves failure locality

Instead of expecting here, assert that delegation_contract is set immediately after fetch_and_set_bytecode() to fail fast if the bundler call or fetch fails.

-    setup.fetch_and_set_bytecode().await?;
+    setup.fetch_and_set_bytecode().await?;
+    assert!(setup.delegation_contract.is_some(), "delegation_contract must be set before building transactions");
executors/src/eip7702_executor/send.rs (1)

145-146: Prefer sharing DelegationContractCache via Arc

If the cache holds shared state/HTTP clients, storing by value risks unnecessary cloning. Passing Arc avoids duplication and keeps cache warm across handlers.

-    pub delegation_contract_cache: DelegationContractCache,
+    pub delegation_contract_cache: std::sync::Arc<DelegationContractCache>,

Note: update construction sites accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bf2d156 and 33a1cb4.

📒 Files selected for processing (12)
  • core/src/rpc_clients/bundler.rs (2 hunks)
  • eip7702-core/src/constants.rs (1 hunks)
  • eip7702-core/src/delegated_account.rs (4 hunks)
  • eip7702-core/src/transaction.rs (1 hunks)
  • eip7702-core/tests/integration_tests.rs (6 hunks)
  • executors/src/eip7702_executor/delegation_cache.rs (1 hunks)
  • executors/src/eip7702_executor/mod.rs (1 hunks)
  • executors/src/eip7702_executor/send.rs (4 hunks)
  • executors/src/eoa/worker/confirm.rs (2 hunks)
  • executors/src/eoa/worker/mod.rs (1 hunks)
  • executors/src/metrics.rs (3 hunks)
  • server/src/queue/manager.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
executors/src/eoa/worker/mod.rs (2)
executors/src/eoa/store/mod.rs (1)
  • now (780-782)
executors/src/metrics.rs (1)
  • current_timestamp_ms (236-238)
executors/src/eip7702_executor/send.rs (1)
executors/src/metrics.rs (3)
  • calculate_duration_seconds_from_twmq (230-233)
  • current_timestamp_ms (236-238)
  • record_transaction_queued_to_sent (132-137)
executors/src/eip7702_executor/delegation_cache.rs (2)
eip7702-core/tests/integration_tests.rs (2)
  • new (298-363)
  • chain_id (113-115)
server/src/queue/manager.rs (1)
  • new (46-256)
🔇 Additional comments (11)
executors/src/metrics.rs (2)

70-71: LGTM: added out_of_funds label.

The extra label improves diagnosability. Using boolean-to-string yields "true"/"false", which is Prometheus-friendly.


205-211: All record_stuck_eoa call sites include the new out_of_funds parameter

I’ve verified across the codebase that there are no remaining invocations of record_stuck_eoa with only three arguments:

  • ast-grep found zero matches for a 3-argument pattern
  • In executors/src/eoa/worker/confirm.rs, the call at lines 127–130 correctly passes is_out_of_funds as the fourth argument

No further updates are needed.

eip7702-core/src/delegated_account.rs (1)

104-126: Review Complete: Delegation Contract Argument Propagation

All call sites of sign_authorization and add_authorization_if_needed correctly include the delegation_contract argument:

  • eip7702-core/src/transaction.rs: both sign_authorization(...) (line 202) and add_authorization_if_needed(…) (line 192) accept and propagate delegation_contract.
  • Integration tests in eip7702-core/tests/integration_tests.rs and executors/src/eip7702_executor/send.rs pass setup.delegation_contract.expect(...) and delegation_contract, respectively.
  • No missing call sites were found.

No further changes are needed.

executors/src/eip7702_executor/mod.rs (1)

1-3: LGTM: export delegation_cache module.

Surface looks correct; no concerns.

executors/src/eoa/worker/mod.rs (1)

502-511: LGTM: consistent initialization timestamps.

Initializing all health timestamps to the same now reduces ordering ambiguities on first write.

server/src/queue/manager.rs (1)

200-208: LGTM: Cache is correctly threaded into the EIP-7702 send handler

Passing DelegationContractCache into Eip7702SendHandler looks correct and keeps concerns separated.

core/src/rpc_clients/bundler.rs (1)

91-96: LGTM: New response type is well-shaped and follows existing serde patterns

CamelCase rename aligns with bundler payloads; Address type is appropriate.

executors/src/eoa/worker/confirm.rs (2)

79-113: Good guard: skip gas bumps when out of funds

Conditioning the bump on balance prevents futile retries and noisy logs.


121-133: All record_stuck_eoa invocations now include the new out_of_funds parameter

  • In executors/src/metrics.rs (line 205), the signature was updated to accept out_of_funds: bool.
  • In the metrics test (line 281), the call now passes four arguments:
    eoa_metrics.record_stuck_eoa(test_address, 1, 900.0, false);
  • In executors/src/eoa/worker/confirm.rs (lines 127–133), the call was updated to:
    self.store.eoa_metrics.record_stuck_eoa(
        self.eoa,
        self.chain_id,
        time_since_movement_seconds,
        is_out_of_funds,
    );
  • A repository-wide search (rg '\brecord_stuck_eoa\s*\(') shows no other call sites requiring updates.

All callers have been confirmed. Feel free to resolve this comment.

eip7702-core/tests/integration_tests.rs (1)

576-582: Verify EIP-712 domain values for session signing

Confirm that name = "MinimalAccount" and verifying_contract = user_address are correct for the deployed delegation/minimal-account setup you’re exercising. If the domain has changed with the new delegation contract, signatures will fail subtly.

executors/src/eip7702_executor/send.rs (1)

301-306: Metrics LGTM

Queued→sent duration is computed with consistent timestamp units; labels match existing metrics.

Comment on lines +180 to +183
pub async fn tw_get_delegation_contract(&self) -> TransportResult<TwGetDelegationContractResponse> {
let response: TwGetDelegationContractResponse = self.inner.request("tw_getDelegationContract", ()).await?;
Ok(response)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Prefer sending [] over () for zero-arg JSON-RPC calls

Some servers reject params: null. Using an empty array is safer and matches other methods here.

Apply this diff:

-    pub async fn tw_get_delegation_contract(&self) -> TransportResult<TwGetDelegationContractResponse> {
-        let response: TwGetDelegationContractResponse = self.inner.request("tw_getDelegationContract", ()).await?;
-        Ok(response)
-    }
+    pub async fn tw_get_delegation_contract(
+        &self,
+    ) -> TransportResult<TwGetDelegationContractResponse> {
+        let params = serde_json::json!([]);
+        let response: TwGetDelegationContractResponse =
+            self.inner.request("tw_getDelegationContract", params).await?;
+        Ok(response)
+    }

To verify consistency repo-wide (and catch any other zero-arg calls using ()), run:


🏁 Script executed:

#!/bin/bash
# Find JSON-RPC calls passing unit params
rg -nP --type=rust 'request\(\s*"[^"]+",\s*\(\)\s*\)'

Length of output: 305


Use an empty JSON array for zero-arg RPC calls

Some JSON-RPC servers reject params: null. To ensure compatibility and match the rest of our clients, always send [] instead of () when there are no parameters.

Affected methods:

  • core/src/rpc_clients/bundler.rs: tw_get_delegation_contract
  • core/src/rpc_clients/paymaster.rs: thirdweb_getUserOperationGasPrice

Proposed diffs:

--- a/core/src/rpc_clients/bundler.rs
+++ b/core/src/rpc_clients/bundler.rs
@@ -180,7 +180,12 @@ impl BundlerClient {
-    pub async fn tw_get_delegation_contract(&self) -> TransportResult<TwGetDelegationContractResponse> {
-        let response: TwGetDelegationContractResponse = self.inner.request("tw_getDelegationContract", ()).await?;
-        Ok(response)
-    }
+    pub async fn tw_get_delegation_contract(
+        &self,
+    ) -> TransportResult<TwGetDelegationContractResponse> {
+        let params = serde_json::json!([]);
+        let response: TwGetDelegationContractResponse =
+            self.inner.request("tw_getDelegationContract", params).await?;
+        Ok(response)
+    }
--- a/core/src/rpc_clients/paymaster.rs
+++ b/core/src/rpc_clients/paymaster.rs
@@ -45,7 +45,11 @@ impl PaymasterClient {
-        let response: ThirdwebGetUserOperationGasPriceResponse = self
-            .inner
-            .request("thirdweb_getUserOperationGasPrice", ())
-            .await?;
+        let params = serde_json::json!([]);
+        let response: ThirdwebGetUserOperationGasPriceResponse = self
+            .inner
+            .request("thirdweb_getUserOperationGasPrice", params)
+            .await?;
         Ok(response)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn tw_get_delegation_contract(&self) -> TransportResult<TwGetDelegationContractResponse> {
let response: TwGetDelegationContractResponse = self.inner.request("tw_getDelegationContract", ()).await?;
Ok(response)
}
// core/src/rpc_clients/bundler.rs
pub async fn tw_get_delegation_contract(
&self,
) -> TransportResult<TwGetDelegationContractResponse> {
let params = serde_json::json!([]);
let response: TwGetDelegationContractResponse =
self.inner.request("tw_getDelegationContract", params).await?;
Ok(response)
}
Suggested change
pub async fn tw_get_delegation_contract(&self) -> TransportResult<TwGetDelegationContractResponse> {
let response: TwGetDelegationContractResponse = self.inner.request("tw_getDelegationContract", ()).await?;
Ok(response)
}
// core/src/rpc_clients/paymaster.rs
let params = serde_json::json!([]);
let response: ThirdwebGetUserOperationGasPriceResponse = self
.inner
.request("thirdweb_getUserOperationGasPrice", params)
.await?;
Ok(response)
🤖 Prompt for AI Agents
In core/src/rpc_clients/bundler.rs around lines 180 to 183, the RPC call uses ()
as the params which serializes to null; change the params to an empty JSON array
so the request sends [] instead of null — replace the request invocation to use
an empty-array params value to ensure compatibility with JSON-RPC servers that
reject null params (and apply the same pattern to other zero-arg RPC methods
such as the paymaster thirdweb_getUserOperationGasPrice).

Comment on lines 224 to 231
// Get delegation contract from cache
let delegation_contract = self
.delegation_contract_cache
.get_delegation_contract(transactions.account().chain())
.await
.map_err(|e| Eip7702SendError::DelegationCheckFailed { inner_error: e })
.map_err_fail()?;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make delegation-contract lookup retryable on transient errors

Currently, a failure in get_delegation_contract() always fails the job. Align with the retry policy used later (add_authorization_if_needed) so network blips don’t cause permanent failures.

-        let delegation_contract = self
-            .delegation_contract_cache
-            .get_delegation_contract(transactions.account().chain())
-            .await
-            .map_err(|e| Eip7702SendError::DelegationCheckFailed { inner_error: e })
-            .map_err_fail()?;
+        let delegation_contract = self
+            .delegation_contract_cache
+            .get_delegation_contract(transactions.account().chain())
+            .await
+            .map_err(|e| {
+                let mapped = Eip7702SendError::DelegationCheckFailed { inner_error: e.clone() };
+                if is_build_error_retryable(&e) {
+                    mapped.nack_with_max_retries(
+                        Some(Duration::from_secs(2)),
+                        twmq::job::RequeuePosition::Last,
+                        3,
+                        job.attempts(),
+                    )
+                } else {
+                    mapped.fail()
+                }
+            })?;

Additionally, add a debug log to aid triage:

+        tracing::debug!(delegation_contract = ?delegation_contract, "resolved delegation contract");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get delegation contract from cache
let delegation_contract = self
.delegation_contract_cache
.get_delegation_contract(transactions.account().chain())
.await
.map_err(|e| Eip7702SendError::DelegationCheckFailed { inner_error: e })
.map_err_fail()?;
// Get delegation contract from cache
let delegation_contract = self
.delegation_contract_cache
.get_delegation_contract(transactions.account().chain())
.await
.map_err(|e| {
let mapped = Eip7702SendError::DelegationCheckFailed { inner_error: e.clone() };
if is_build_error_retryable(&e) {
mapped.nack_with_max_retries(
Some(Duration::from_secs(2)),
twmq::job::RequeuePosition::Last,
3,
job.attempts(),
)
} else {
mapped.fail()
}
})?;
tracing::debug!(delegation_contract = ?delegation_contract, "resolved delegation contract");

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
executors/src/eip7702_executor/confirm.rs (1)

200-206: Disambiguate log fields: use bundler_transaction_id and include engine transaction_id

Current log uses transaction_id = job_data.bundler_transaction_id, which collides with the engine’s transaction_id used elsewhere and hurts log querying.

Apply:

-                tracing::error!(
-                    transaction_id = job_data.bundler_transaction_id,
-                    sender_details = ?job_data.sender_details,
-                    error = ?e,
-                    "Failed to get transaction hash from bundler"
-                );
+                tracing::error!(
+                    transaction_id = job_data.transaction_id,
+                    bundler_transaction_id = job_data.bundler_transaction_id,
+                    sender_details = ?job_data.sender_details,
+                    error = ?e,
+                    "Failed to get transaction hash from bundler"
+                );

Note: Ensure Eip7702Sender does not include secrets; if it might, redact or log minimal identifiers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 33a1cb4 and b6f91f5.

📒 Files selected for processing (1)
  • executors/src/eip7702_executor/confirm.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
executors/src/eip7702_executor/confirm.rs (1)
executors/src/metrics.rs (3)
  • calculate_duration_seconds_from_twmq (230-233)
  • current_timestamp_ms (236-238)
  • record_transaction_queued_to_confirmed (139-144)
🔇 Additional comments (3)
executors/src/eip7702_executor/confirm.rs (3)

211-211: Good nack/backoff on bundler fetch failures

10s nack with RequeuePosition::Last is reasonable and consistent with transient RPC issues. No changes needed.


21-24: Imports OK; will remain valid after unit-fix

No change needed if you keep calculate_duration_seconds_from_twmq and divide ms→s as suggested above.


280-290: The next step is to inspect the definition of calculate_duration_seconds_from_twmq to confirm whether it expects timestamps in seconds (which would validate the need to divide the millisecond value by 1000). Once we have that, we can finalize whether the proposed ms→s conversion is required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
twmq/src/lib.rs (1)

587-590: ARGV reindexing looks correct; document mapping to avoid regressions

The shift to ARGV[2..4] for pop_id/batch_size/lease_seconds matches the Rust call order. Add an inline note at the call site to keep this mapping obvious during future edits.

             .key(self.success_list_name())
-            .arg(now)
+            // ARGV: [1]=now, [2]=pop_id, [3]=batch_size, [4]=lease_seconds
+            .arg(now)
             .arg(pop_id)
             .arg(batch_size)
             .arg(self.options.lease_duration.as_secs())
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b6f91f5 and 80b47ef.

📒 Files selected for processing (2)
  • executors/src/eoa/worker/mod.rs (3 hunks)
  • twmq/src/lib.rs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • executors/src/eoa/worker/mod.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: coverage
🔇 Additional comments (2)
twmq/src/lib.rs (2)

713-713: Lease token scoped by batch pop_id — LGTM

Including pop_id in lease_token cleanly isolates per-batch leases without affecting existing parsing (none observed).


754-757: Lua invocation updated to pass pop_id — consistent with script

Argument order matches the script’s ARGV usage. No issues.

Comment on lines +577 to +578
let pop_id = nanoid::nanoid!(4);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Batch pop identifier added; consider length and dependency check

Good call adding a per-batch pop_id. Two small follow-ups:

  • Use a slightly longer id (e.g., 8) to reduce accidental collisions under very high QPS, even if collisions are low impact here.
  • Ensure the nanoid crate is declared in Cargo.toml.

Apply:

-        let pop_id = nanoid::nanoid!(4);
+        let pop_id = nanoid::nanoid!(8);

Verify dependency:


🏁 Script executed:

#!/bin/bash
rg -n '^\s*nanoid\s*=' Cargo.toml || { echo "Missing nanoid crate in Cargo.toml"; exit 1; }

Length of output: 123


Add missing nanoid dependency & increase pop_id length

The project currently uses the nanoid::nanoid! macro but does not declare the nanoid crate in Cargo.toml, causing a compilation error. Additionally, bumping the ID length from 4 to 8 will further reduce collision risk under high QPS.

Please apply the following changes:

• In Cargo.toml, add the nanoid dependency (choose the latest compatible version, for example 0.4):

 [dependencies]
+ nanoid = "0.4"

• In twmq/src/lib.rs, update the batch pop ID to length 8:

-        let pop_id = nanoid::nanoid!(4);
+        let pop_id = nanoid::nanoid!(8);

After applying these, verify that the crate compiles and the identifiers are generated at the new length.

🤖 Prompt for AI Agents
In twmq/src/lib.rs around lines 577-578 and Cargo.toml: the code uses the
nanoid::nanoid! macro but the crate is not declared and the generated ID length
is only 4; add the nanoid dependency to Cargo.toml (e.g., nanoid = "0.4") and
update the macro call in twmq/src/lib.rs to generate an 8-character ID (replace
nanoid!(4) with nanoid!(8)); after changes, run cargo build to verify the crate
compiles and that identifiers are produced at the new length.

@d4mr d4mr merged commit 3a2b65c into main Aug 27, 2025
2 of 4 checks passed
@d4mr d4mr deleted the pb/eoa-monitoring-out-of-funds branch August 27, 2025 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant