From daed6b3e176df587df0236a11bbd5194bf592fb9 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Sat, 18 Oct 2025 05:40:08 +0530 Subject: [PATCH 1/3] Implement fleet ID support in EIP-7702 execution options - Added `fleet_id` field to `Eip7702OwnerExecution` and `Eip7702SessionKeyExecution` structs to allow routing transactions to specific bundler fleets. - Introduced `fleet_id` method in `Eip7702ExecutionOptions` for retrieving the fleet ID. - Updated bundler client creation in the executor to include the optional `fleet_id` header when present, enhancing transaction routing capabilities. --- core/src/execution_options/eip7702.rs | 19 +++++++++++++++ executors/src/eip7702_executor/send.rs | 33 ++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/core/src/execution_options/eip7702.rs b/core/src/execution_options/eip7702.rs index 0746f53..ba9a6ed 100644 --- a/core/src/execution_options/eip7702.rs +++ b/core/src/execution_options/eip7702.rs @@ -14,6 +14,16 @@ pub enum Eip7702ExecutionOptions { SessionKey(Eip7702SessionKeyExecution), } +impl Eip7702ExecutionOptions { + /// Get the fleet ID if present + pub fn fleet_id(&self) -> Option<&str> { + match self { + Eip7702ExecutionOptions::Owner(o) => o.fleet_id.as_deref(), + Eip7702ExecutionOptions::SessionKey(s) => s.fleet_id.as_deref(), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] #[schema(title = "EIP-7702 Owner Execution")] #[serde(rename_all = "camelCase")] @@ -21,6 +31,10 @@ pub struct Eip7702OwnerExecution { #[schema(value_type = AddressDef)] /// The delegated EOA address pub from: Address, + + /// Optional fleet ID to route the transaction to a specific bundler fleet + #[serde(skip_serializing_if = "Option::is_none")] + pub fleet_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] @@ -30,7 +44,12 @@ pub struct Eip7702SessionKeyExecution { #[schema(value_type = AddressDef)] /// The session key address is your server wallet, which has been granted a session key to the `account_address` pub session_key_address: Address, + #[schema(value_type = AddressDef)] /// The account address is the address of a delegated account you want to execute the transaction on. This account has granted a session key to the `session_key_address` pub account_address: Address, + + /// Optional fleet ID to route the transaction to a specific bundler fleet + #[serde(skip_serializing_if = "Option::is_none")] + pub fleet_id: Option, } diff --git a/executors/src/eip7702_executor/send.rs b/executors/src/eip7702_executor/send.rs index 6e1f160..af31075 100644 --- a/executors/src/eip7702_executor/send.rs +++ b/executors/src/eip7702_executor/send.rs @@ -280,10 +280,35 @@ where .map_err(|e| Eip7702SendError::SigningFailed { inner_error: e }) .map_err_fail()?; - let transaction_id = transactions - .account() - .chain() - .bundler_client() + // Create bundler client with optional fleet_id header for tw_execute + let bundler_client = if let Some(fleet_id) = job_data.execution_options.fleet_id() { + let mut headers = job_data + .rpc_credentials + .to_header_map() + .map_err(|e| Eip7702SendError::InvalidRpcCredentials { + message: e.to_string(), + }) + .map_err_fail()?; + + headers.insert( + "x-executor-fleet-id", + fleet_id.parse().map_err(|e| { + Eip7702SendError::InvalidRpcCredentials { + message: format!("Invalid fleet_id value: {e}"), + } + }) + .map_err_fail()?, + ); + + transactions + .account() + .chain() + .bundler_client_with_headers(headers) + } else { + transactions.account().chain().bundler_client().clone() + }; + + let transaction_id = bundler_client .tw_execute( owner_address, &wrapped_calls, From 8d437474dcfafaa7c70f6d32f0d92dcbe2e5d772 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Sat, 18 Oct 2025 05:46:37 +0530 Subject: [PATCH 2/3] dont clone --- executors/src/eip7702_executor/send.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/executors/src/eip7702_executor/send.rs b/executors/src/eip7702_executor/send.rs index af31075..55aa22f 100644 --- a/executors/src/eip7702_executor/send.rs +++ b/executors/src/eip7702_executor/send.rs @@ -230,7 +230,7 @@ where let mapped_error = Eip7702SendError::DelegationCheckFailed { inner_error: e.clone(), }; - + // Retry only if the error is retryable (network issues, 5xx, etc.) // Client errors (4xx) like "Invalid chain" or auth errors fail immediately if is_build_error_retryable(&e) { @@ -289,23 +289,23 @@ where message: e.to_string(), }) .map_err_fail()?; - + headers.insert( "x-executor-fleet-id", - fleet_id.parse().map_err(|e| { - Eip7702SendError::InvalidRpcCredentials { + fleet_id + .parse() + .map_err(|e| Eip7702SendError::InvalidRpcCredentials { message: format!("Invalid fleet_id value: {e}"), - } - }) - .map_err_fail()?, + }) + .map_err_fail()?, ); - - transactions + + &transactions .account() .chain() .bundler_client_with_headers(headers) } else { - transactions.account().chain().bundler_client().clone() + transactions.account().chain().bundler_client() }; let transaction_id = bundler_client From 5be70553d963b28941d17cb9b7d2a476fa1feb59 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Sat, 18 Oct 2025 05:49:59 +0530 Subject: [PATCH 3/3] add directly to chain auth headers --- executors/src/eip7702_executor/send.rs | 48 ++++++++++---------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/executors/src/eip7702_executor/send.rs b/executors/src/eip7702_executor/send.rs index 55aa22f..f531eab 100644 --- a/executors/src/eip7702_executor/send.rs +++ b/executors/src/eip7702_executor/send.rs @@ -192,7 +192,7 @@ where }) .map_err_fail()?; - let chain_auth_headers = job_data + let mut chain_auth_headers = job_data .rpc_credentials .to_header_map() .map_err(|e| Eip7702SendError::InvalidRpcCredentials { @@ -200,6 +200,19 @@ where }) .map_err_fail()?; + // Add optional fleet_id header for bundler routing + if let Some(fleet_id) = job_data.execution_options.fleet_id() { + chain_auth_headers.insert( + "x-executor-fleet-id", + fleet_id + .parse() + .map_err(|e| Eip7702SendError::InvalidRpcCredentials { + message: format!("Invalid fleet_id value: {e}"), + }) + .map_err_fail()?, + ); + } + let chain = chain.with_new_default_headers(chain_auth_headers); let owner_address = match &job_data.execution_options { @@ -280,35 +293,10 @@ where .map_err(|e| Eip7702SendError::SigningFailed { inner_error: e }) .map_err_fail()?; - // Create bundler client with optional fleet_id header for tw_execute - let bundler_client = if let Some(fleet_id) = job_data.execution_options.fleet_id() { - let mut headers = job_data - .rpc_credentials - .to_header_map() - .map_err(|e| Eip7702SendError::InvalidRpcCredentials { - message: e.to_string(), - }) - .map_err_fail()?; - - headers.insert( - "x-executor-fleet-id", - fleet_id - .parse() - .map_err(|e| Eip7702SendError::InvalidRpcCredentials { - message: format!("Invalid fleet_id value: {e}"), - }) - .map_err_fail()?, - ); - - &transactions - .account() - .chain() - .bundler_client_with_headers(headers) - } else { - transactions.account().chain().bundler_client() - }; - - let transaction_id = bundler_client + let transaction_id = transactions + .account() + .chain() + .bundler_client() .tw_execute( owner_address, &wrapped_calls,