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..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 { @@ -230,7 +243,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) {