Skip to content

Commit ca790e8

Browse files
7702 cleanup (#8)
* make transaction hash optional from tw_getTransactionHash * cleanup errors * schema title
1 parent 2608e5e commit ca790e8

File tree

6 files changed

+69
-49
lines changed

6 files changed

+69
-49
lines changed

core/src/execution_options/eip7702.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
55
use crate::defs::AddressDef;
66

77
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, utoipa::ToSchema)]
8+
#[schema(title = "EIP-7702 Execution Options")]
89
#[serde(rename_all = "camelCase")]
910
pub struct Eip7702ExecutionOptions {
1011
/// The EOA address that will sign the EIP-7702 transaction

core/src/rpc_clients/bundler.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub struct TwExecuteResponse {
7575
#[serde(rename_all = "camelCase")]
7676
pub struct TwGetTransactionHashResponse {
7777
/// The transaction hash
78-
pub transaction_hash: String,
78+
pub transaction_hash: Option<String>,
7979
}
8080

8181
impl BundlerClient {
@@ -149,7 +149,10 @@ impl BundlerClient {
149149
}
150150

151151
/// Get transaction hash from bundler using transaction ID
152-
pub async fn tw_get_transaction_hash(&self, transaction_id: &str) -> TransportResult<String> {
152+
pub async fn tw_get_transaction_hash(
153+
&self,
154+
transaction_id: &str,
155+
) -> TransportResult<Option<String>> {
153156
let params = serde_json::json!([transaction_id]);
154157

155158
let response: TwGetTransactionHashResponse =

core/src/signer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ impl AccountSigner for EoaSigner {
393393
.await
394394
.map_err(|e| {
395395
tracing::error!("Error signing authorization with EOA (IAW): {:?}", e);
396-
EngineError::from(e)
396+
e
397397
})?;
398398

399399
// Return the signed authorization as Authorization

executors/src/eip7702_executor/confirm.rs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use alloy::primitives::{Address, TxHash};
22
use alloy::providers::Provider;
3+
use engine_core::error::{AlloyRpcErrorToEngineError, EngineError};
34
use engine_core::{
45
chain::{Chain, ChainService, RpcCredentials},
56
execution_options::WebhookOptions,
@@ -62,13 +63,25 @@ pub struct Eip7702ConfirmationResult {
6263
#[serde(rename_all = "SCREAMING_SNAKE_CASE", tag = "errorCode")]
6364
pub enum Eip7702ConfirmationError {
6465
#[error("Chain service error for chainId {chain_id}: {message}")]
66+
#[serde(rename_all = "camelCase")]
6567
ChainServiceError { chain_id: u64, message: String },
6668

6769
#[error("Failed to get transaction hash from bundler: {message}")]
6870
TransactionHashError { message: String },
6971

7072
#[error("Failed to confirm transaction: {message}")]
71-
ConfirmationError { message: String },
73+
#[serde(rename_all = "camelCase")]
74+
ConfirmationError {
75+
message: String,
76+
inner_error: Option<EngineError>,
77+
},
78+
79+
#[error("Receipt not yet available for transaction: {message}")]
80+
#[serde(rename_all = "camelCase")]
81+
ReceiptNotAvailable {
82+
message: String,
83+
transaction_hash: TxHash,
84+
},
7285

7386
#[error("Transaction failed: {message}")]
7487
TransactionFailed { message: String },
@@ -169,26 +182,25 @@ where
169182
.bundler_client()
170183
.tw_get_transaction_hash(&job_data.bundler_transaction_id)
171184
.await
172-
.map_err(|e| {
173-
// Check if it's a "not found" or "pending" error
174-
let error_msg = e.to_string();
175-
if error_msg.contains("not found") || error_msg.contains("pending") {
176-
// Transaction not ready yet, nack and retry
177-
Eip7702ConfirmationError::TransactionHashError {
178-
message: format!("Transaction not ready: {}", error_msg),
179-
}
180-
.nack(Some(Duration::from_secs(5)), RequeuePosition::Last)
181-
} else {
182-
Eip7702ConfirmationError::TransactionHashError { message: error_msg }.fail()
183-
}
184-
})?;
185+
.map_err(|e| Eip7702ConfirmationError::TransactionHashError {
186+
message: e.to_string(),
187+
})
188+
.map_err_fail()?;
185189

186-
let transaction_hash = transaction_hash_str.parse::<TxHash>().map_err(|e| {
187-
Eip7702ConfirmationError::TransactionHashError {
188-
message: format!("Invalid transaction hash format: {}", e),
190+
let transaction_hash = match transaction_hash_str {
191+
Some(hash) => hash.parse::<TxHash>().map_err(|e| {
192+
Eip7702ConfirmationError::TransactionHashError {
193+
message: format!("Invalid transaction hash format: {}", e),
194+
}
195+
.fail()
196+
})?,
197+
None => {
198+
return Err(Eip7702ConfirmationError::TransactionHashError {
199+
message: "Transaction not found".to_string(),
200+
})
201+
.map_err_nack(Some(Duration::from_secs(2)), RequeuePosition::Last);
189202
}
190-
.fail()
191-
})?;
203+
};
192204

193205
tracing::debug!(
194206
transaction_hash = ?transaction_hash,
@@ -205,18 +217,20 @@ where
205217
// If transaction not found, nack and retry
206218
Eip7702ConfirmationError::ConfirmationError {
207219
message: format!("Failed to get transaction receipt: {}", e),
220+
inner_error: Some(e.to_engine_error(&chain)),
208221
}
209-
.nack(Some(Duration::from_secs(10)), RequeuePosition::Last)
222+
.nack(Some(Duration::from_secs(5)), RequeuePosition::Last)
210223
})?;
211224

212225
let receipt = match receipt {
213226
Some(receipt) => receipt,
214227
None => {
215228
// Transaction not mined yet, nack and retry
216-
return Err(Eip7702ConfirmationError::ConfirmationError {
229+
return Err(Eip7702ConfirmationError::ReceiptNotAvailable {
217230
message: "Transaction not mined yet".to_string(),
231+
transaction_hash,
218232
})
219-
.map_err_nack(Some(Duration::from_secs(10)), RequeuePosition::Last);
233+
.map_err_nack(Some(Duration::from_secs(2)), RequeuePosition::Last);
220234
}
221235
};
222236

executors/src/eip7702_executor/send.rs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use alloy::{
22
dyn_abi::TypedData,
33
eips::eip7702::Authorization,
4-
primitives::{Address, Bytes, ChainId, FixedBytes, U256},
4+
primitives::{Address, Bytes, ChainId, FixedBytes, U256, address},
55
providers::Provider,
66
sol_types::eip712_domain,
77
};
@@ -34,7 +34,8 @@ use crate::{
3434

3535
use super::confirm::{Eip7702ConfirmationHandler, Eip7702ConfirmationJobData};
3636

37-
const MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS: &str = "0xD6999651Fc0964B9c6B444307a0ab20534a66560";
37+
const MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS: Address =
38+
address!("0xD6999651Fc0964B9c6B444307a0ab20534a66560");
3839

3940
// --- Job Payload ---
4041
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -81,13 +82,18 @@ pub enum Eip7702SendError {
8182
ChainServiceError { chain_id: u64, message: String },
8283

8384
#[error("Failed to sign typed data: {message}")]
84-
SigningError { message: String },
85-
86-
#[error("Failed to sign authorization: {message}")]
87-
AuthorizationError { message: String },
85+
#[serde(rename_all = "camelCase")]
86+
SigningError {
87+
message: String,
88+
inner_error: Option<EngineError>,
89+
},
8890

8991
#[error("Failed to check 7702 delegation: {message}")]
90-
DelegationCheckError { message: String },
92+
#[serde(rename_all = "camelCase")]
93+
DelegationCheckError {
94+
message: String,
95+
inner_error: Option<EngineError>,
96+
},
9197

9298
#[error("Failed to call bundler: {message}")]
9399
BundlerCallError { message: String },
@@ -241,40 +247,37 @@ where
241247
)
242248
.await
243249
.map_err(|e| Eip7702SendError::SigningError {
244-
message: e.to_string(),
250+
message: format!("Failed to sign typed data: {e}"),
251+
inner_error: Some(e),
245252
})
246253
.map_err_fail()?;
247254

248255
// 4. Check if wallet has 7702 delegation set
249256
let is_minimal_account = check_is_7702_minimal_account(&chain, job_data.eoa_address)
250257
.await
251258
.map_err(|e| Eip7702SendError::DelegationCheckError {
252-
message: e.to_string(),
259+
message: format!("Failed to check if wallet has 7702 delegation: {e}"),
260+
inner_error: Some(e),
253261
})
254262
.map_err_fail()?;
255263

256264
// 5. Sign authorization if needed
257265
let authorization = if !is_minimal_account {
258266
let nonce = job_data.nonce.unwrap_or_default();
259-
let minimal_account_address: Address = MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS
260-
.parse()
261-
.map_err(|e| Eip7702SendError::AuthorizationError {
262-
message: format!("Invalid minimal account implementation address: {}", e),
263-
})
264-
.map_err_fail()?;
265267

266268
let auth = self
267269
.eoa_signer
268270
.sign_authorization(
269271
signing_options.clone(),
270272
job_data.chain_id,
271-
minimal_account_address,
273+
MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS,
272274
nonce,
273275
job_data.signing_credential.clone(),
274276
)
275277
.await
276-
.map_err(|e| Eip7702SendError::AuthorizationError {
277-
message: e.to_string(),
278+
.map_err(|e| Eip7702SendError::SigningError {
279+
message: format!("Failed to sign authorization: {e}"),
280+
inner_error: Some(e),
278281
})
279282
.map_err_fail()?;
280283

@@ -493,12 +496,7 @@ async fn check_is_7702_minimal_account(
493496
let target_address = Address::from_slice(target_bytes);
494497

495498
// Compare with the minimal account implementation address
496-
let minimal_account_address: Address =
497-
MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS
498-
.parse()
499-
.map_err(|e| EngineError::ValidationError {
500-
message: format!("Invalid minimal account implementation address: {}", e),
501-
})?;
499+
let minimal_account_address: Address = MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS;
502500

503501
let is_delegated = target_address == minimal_account_address;
504502

executors/src/external_bundler/confirm.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,26 @@ pub struct UserOpConfirmationResult {
5252
#[serde(rename_all = "SCREAMING_SNAKE_CASE", tag = "errorCode")]
5353
pub enum UserOpConfirmationError {
5454
#[error("Chain service error for chainId {chain_id}: {message}")]
55+
#[serde(rename_all = "camelCase")]
5556
ChainServiceError { chain_id: u64, message: String },
5657

5758
#[error("Receipt not yet available for user operation {user_op_hash}")]
59+
#[serde(rename_all = "camelCase")]
5860
ReceiptNotAvailable {
5961
user_op_hash: Bytes,
6062
attempt_number: u32,
6163
},
6264

6365
#[error("Failed to query user operation receipt: {message}")]
66+
#[serde(rename_all = "camelCase")]
6467
ReceiptQueryFailed {
6568
user_op_hash: Bytes,
6669
message: String,
6770
inner_error: Option<EngineError>,
6871
},
6972

7073
#[error("Internal error: {message}")]
74+
#[serde(rename_all = "camelCase")]
7175
InternalError { message: String },
7276

7377
#[error("Transaction cancelled by user")]

0 commit comments

Comments
 (0)