@@ -40,6 +40,20 @@ sol! {
40
40
bytes paymasterAndData;
41
41
bytes signature;
42
42
}
43
+
44
+ // v0.8 UserOperation struct with packed fields
45
+ #[ derive( Debug , Default , PartialEq , Eq ) ]
46
+ struct PackedUserOperation {
47
+ address sender;
48
+ uint256 nonce;
49
+ bytes initCode;
50
+ bytes callData;
51
+ bytes32 accountGasLimits; // packed verificationGasLimit (16 bytes) and callGasLimit (16 bytes)
52
+ uint256 preVerificationGas;
53
+ bytes32 gasFees; // packed maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes)
54
+ bytes paymasterAndData;
55
+ bytes signature;
56
+ }
43
57
}
44
58
45
59
// Define contract interfaces
@@ -2305,42 +2319,20 @@ impl UserOperationBuilder {
2305
2319
self ,
2306
2320
entry_point : EthAddress ,
2307
2321
signer : & S ,
2308
- ) -> Result < UserOperation , WalletError > {
2309
- // Create the UserOperation struct
2310
- let user_op = UserOperation {
2311
- sender : self . sender ,
2312
- nonce : self . nonce ,
2313
- initCode : Bytes :: from ( self . init_code . clone ( ) ) ,
2314
- callData : Bytes :: from ( self . call_data . clone ( ) ) ,
2315
- callGasLimit : self . call_gas_limit ,
2316
- verificationGasLimit : self . verification_gas_limit ,
2317
- preVerificationGas : self . pre_verification_gas ,
2318
- maxFeePerGas : self . max_fee_per_gas ,
2319
- maxPriorityFeePerGas : self . max_priority_fee_per_gas ,
2320
- paymasterAndData : Bytes :: from ( self . paymaster_and_data . clone ( ) ) ,
2321
- signature : Bytes :: default ( ) , // Will be filled after signing
2322
- } ;
2322
+ ) -> Result < PackedUserOperation , WalletError > {
2323
+ // Create the v0.8 PackedUserOperation struct
2324
+ let mut packed_op = build_packed_user_operation ( & self ) ;
2323
2325
2324
2326
// Get the UserOp hash for signing
2325
- let user_op_hash = self . get_user_op_hash ( & user_op , entry_point, self . chain_id ) ;
2327
+ let user_op_hash = self . get_user_op_hash_v08 ( & packed_op , entry_point, self . chain_id ) ;
2326
2328
2327
2329
// Sign the hash
2328
2330
let signature = signer. sign_message ( & user_op_hash) ?;
2329
2331
2330
- // Create final UserOp with signature
2331
- Ok ( UserOperation {
2332
- sender : self . sender ,
2333
- nonce : self . nonce ,
2334
- initCode : Bytes :: from ( self . init_code ) ,
2335
- callData : Bytes :: from ( self . call_data ) ,
2336
- callGasLimit : self . call_gas_limit ,
2337
- verificationGasLimit : self . verification_gas_limit ,
2338
- preVerificationGas : self . pre_verification_gas ,
2339
- maxFeePerGas : self . max_fee_per_gas ,
2340
- maxPriorityFeePerGas : self . max_priority_fee_per_gas ,
2341
- paymasterAndData : Bytes :: from ( self . paymaster_and_data ) ,
2342
- signature : Bytes :: from ( signature) ,
2343
- } )
2332
+ // Set the signature
2333
+ packed_op. signature = Bytes :: from ( signature) ;
2334
+
2335
+ Ok ( packed_op)
2344
2336
}
2345
2337
2346
2338
/// Calculate the UserOp hash according to ERC-4337 spec
@@ -2365,6 +2357,28 @@ impl UserOperationBuilder {
2365
2357
hasher. finalize ( ) . to_vec ( )
2366
2358
}
2367
2359
2360
+ /// Calculate the UserOp hash for v0.8 according to ERC-4337 spec
2361
+ fn get_user_op_hash_v08 (
2362
+ & self ,
2363
+ user_op : & PackedUserOperation ,
2364
+ entry_point : EthAddress ,
2365
+ chain_id : u64 ,
2366
+ ) -> Vec < u8 > {
2367
+ use sha3:: { Digest , Keccak256 } ;
2368
+
2369
+ // Pack the UserOp for hashing (without signature)
2370
+ let packed = self . pack_user_op_for_hash_v08 ( user_op) ;
2371
+ let user_op_hash = Keccak256 :: digest ( & packed) ;
2372
+
2373
+ // Create the final hash with entry point and chain ID
2374
+ let mut hasher = Keccak256 :: new ( ) ;
2375
+ hasher. update ( user_op_hash) ;
2376
+ hasher. update ( entry_point. as_slice ( ) ) ;
2377
+ hasher. update ( & chain_id. to_be_bytes ( ) ) ;
2378
+
2379
+ hasher. finalize ( ) . to_vec ( )
2380
+ }
2381
+
2368
2382
/// Pack UserOp fields for hashing (ERC-4337 specification)
2369
2383
fn pack_user_op_for_hash ( & self , user_op : & UserOperation ) -> Vec < u8 > {
2370
2384
use sha3:: { Digest , Keccak256 } ;
@@ -2405,6 +2419,46 @@ impl UserOperationBuilder {
2405
2419
2406
2420
packed
2407
2421
}
2422
+
2423
+ /// Pack UserOp fields for hashing v0.8 (ERC-4337 specification)
2424
+ fn pack_user_op_for_hash_v08 ( & self , user_op : & PackedUserOperation ) -> Vec < u8 > {
2425
+ use sha3:: { Digest , Keccak256 } ;
2426
+
2427
+ let mut packed = Vec :: new ( ) ;
2428
+
2429
+ // Pack all fields except signature
2430
+ packed. extend_from_slice ( user_op. sender . as_slice ( ) ) ;
2431
+ packed. extend_from_slice ( & user_op. nonce . to_be_bytes :: < 32 > ( ) ) ;
2432
+
2433
+ // For initCode and paymasterAndData, we hash them if non-empty
2434
+ if !user_op. initCode . is_empty ( ) {
2435
+ let hash = Keccak256 :: digest ( & user_op. initCode ) ;
2436
+ packed. extend_from_slice ( & hash) ;
2437
+ } else {
2438
+ packed. extend_from_slice ( & [ 0u8 ; 32 ] ) ;
2439
+ }
2440
+
2441
+ if !user_op. callData . is_empty ( ) {
2442
+ let hash = Keccak256 :: digest ( & user_op. callData ) ;
2443
+ packed. extend_from_slice ( & hash) ;
2444
+ } else {
2445
+ packed. extend_from_slice ( & [ 0u8 ; 32 ] ) ;
2446
+ }
2447
+
2448
+ // Pack the packed fields directly (accountGasLimits and gasFees)
2449
+ packed. extend_from_slice ( & user_op. accountGasLimits . 0 ) ;
2450
+ packed. extend_from_slice ( & user_op. preVerificationGas . to_be_bytes :: < 32 > ( ) ) ;
2451
+ packed. extend_from_slice ( & user_op. gasFees . 0 ) ;
2452
+
2453
+ if !user_op. paymasterAndData . is_empty ( ) {
2454
+ let hash = Keccak256 :: digest ( & user_op. paymasterAndData ) ;
2455
+ packed. extend_from_slice ( & hash) ;
2456
+ } else {
2457
+ packed. extend_from_slice ( & [ 0u8 ; 32 ] ) ;
2458
+ }
2459
+
2460
+ packed
2461
+ }
2408
2462
}
2409
2463
2410
2464
/// Helper to create calldata for TBA execute through UserOp
@@ -2424,6 +2478,48 @@ pub fn create_tba_userop_calldata(
2424
2478
call. abi_encode ( )
2425
2479
}
2426
2480
2481
+ /// Pack two 16-byte values into a single bytes32 for v0.8 UserOperation
2482
+ fn pack_gas_values ( high : U256 , low : U256 ) -> B256 {
2483
+ let mut packed = [ 0u8 ; 32 ] ;
2484
+ // Take the lower 16 bytes of each value
2485
+ let high_bytes = high. to_be_bytes :: < 32 > ( ) ;
2486
+ let low_bytes = low. to_be_bytes :: < 32 > ( ) ;
2487
+
2488
+ // Pack high value in first 16 bytes
2489
+ packed[ 0 ..16 ] . copy_from_slice ( & high_bytes[ 16 ..32 ] ) ;
2490
+ // Pack low value in last 16 bytes
2491
+ packed[ 16 ..32 ] . copy_from_slice ( & low_bytes[ 16 ..32 ] ) ;
2492
+
2493
+ B256 :: from ( packed)
2494
+ }
2495
+
2496
+ /// Build a v0.8 PackedUserOperation from the builder values
2497
+ pub fn build_packed_user_operation ( builder : & UserOperationBuilder ) -> PackedUserOperation {
2498
+ // Pack gas limits: verificationGasLimit (high) and callGasLimit (low)
2499
+ let account_gas_limits = pack_gas_values (
2500
+ builder. verification_gas_limit ,
2501
+ builder. call_gas_limit
2502
+ ) ;
2503
+
2504
+ // Pack gas fees: maxPriorityFeePerGas (high) and maxFeePerGas (low)
2505
+ let gas_fees = pack_gas_values (
2506
+ builder. max_priority_fee_per_gas ,
2507
+ builder. max_fee_per_gas
2508
+ ) ;
2509
+
2510
+ PackedUserOperation {
2511
+ sender : builder. sender ,
2512
+ nonce : builder. nonce ,
2513
+ initCode : Bytes :: from ( builder. init_code . clone ( ) ) ,
2514
+ callData : Bytes :: from ( builder. call_data . clone ( ) ) ,
2515
+ accountGasLimits : account_gas_limits,
2516
+ preVerificationGas : builder. pre_verification_gas ,
2517
+ gasFees : gas_fees,
2518
+ paymasterAndData : Bytes :: from ( builder. paymaster_and_data . clone ( ) ) ,
2519
+ signature : Bytes :: default ( ) ,
2520
+ }
2521
+ }
2522
+
2427
2523
/// Get the ERC-4337 EntryPoint address for a given chain
2428
2524
pub fn get_entry_point_address ( chain_id : u64 ) -> Option < EthAddress > {
2429
2525
match chain_id {
0 commit comments