Skip to content

Commit f79dea4

Browse files
authored
7702 execution session keys (#21)
* Add engine-eip7702-core package and update dependencies - Introduced the `engine-eip7702-core` package with its dependencies in `Cargo.lock` and `Cargo.toml`. - Updated the `executors` module to include `engine-eip7702-core` as a dependency. - Refactored execution options in `eip7702.rs` to support new execution models for EIP-7702. - Enhanced transaction handling in the `eip7702_executor` to accommodate new sender details and improve compatibility with existing job structures. - Updated webhook and job data structures to reflect changes in sender details and execution options. - Improved error handling and retry logic in job processing to enhance robustness. * clippy
1 parent 9cb1163 commit f79dea4

File tree

17 files changed

+729
-272
lines changed

17 files changed

+729
-272
lines changed

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ members = [
33
"aa-types",
44
"aa-core",
55
"core",
6+
"eip7702-core",
67
"executors",
78
"server",
89
"thirdweb-core",

core/src/execution_options/eip7702.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
use alloy::primitives::Address;
2-
use schemars::JsonSchema;
32
use serde::{Deserialize, Serialize};
43

54
use crate::defs::AddressDef;
65

7-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, utoipa::ToSchema)]
6+
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
87
#[schema(title = "EIP-7702 Execution Options")]
8+
#[serde(rename_all = "camelCase", untagged)]
9+
pub enum Eip7702ExecutionOptions {
10+
/// Execute the transaction as the owner of the account
11+
Owner(Eip7702OwnerExecution),
12+
/// Execute a transaction on a different delegated account (`account_address`), which has granted a session key to the `session_key_address`
13+
/// `session_key_address` is the signer for this transaction
14+
SessionKey(Eip7702SessionKeyExecution),
15+
}
16+
17+
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
18+
#[schema(title = "EIP-7702 Owner Execution")]
919
#[serde(rename_all = "camelCase")]
10-
pub struct Eip7702ExecutionOptions {
11-
/// The EOA address that will sign the EIP-7702 transaction
12-
#[schemars(with = "AddressDef")]
20+
pub struct Eip7702OwnerExecution {
1321
#[schema(value_type = AddressDef)]
22+
/// The delegated EOA address
1423
pub from: Address,
1524
}
25+
26+
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
27+
#[schema(title = "EIP-7702 Session Key Execution")]
28+
#[serde(rename_all = "camelCase")]
29+
pub struct Eip7702SessionKeyExecution {
30+
#[schema(value_type = AddressDef)]
31+
/// The session key address is your server wallet, which has been granted a session key to the `account_address`
32+
pub session_key_address: Address,
33+
#[schema(value_type = AddressDef)]
34+
/// 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`
35+
pub account_address: Address,
36+
}

core/src/execution_options/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fn default_idempotency_key() -> String {
2727
}
2828

2929
/// All supported specific execution options are contained here
30-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, utoipa::ToSchema)]
30+
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
3131
#[serde(tag = "type")]
3232
#[schema(title = "Execution Option Variants")]
3333
pub enum SpecificExecutionOptions {

core/src/signer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ impl AccountSigner for EoaSigner {
338338
} => {
339339
let iaw_result = self
340340
.iaw_client
341-
.sign_transaction(auth_token, thirdweb_auth, &transaction)
341+
.sign_transaction(auth_token, thirdweb_auth, transaction)
342342
.await
343343
.map_err(|e| {
344344
tracing::error!("Error signing transaction with EOA (IAW): {:?}", e);

eip7702-core/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "engine-eip7702-core"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
alloy = { workspace = true, features = ["serde"] }
8+
tokio = "1.44.2"
9+
engine-core = { path = "../core" }
10+
serde = { version = "1.0.219", features = ["derive"] }
11+
serde_json = "1.0"
12+
tracing = "0.1.41"
13+
rand = "0.9"
14+
thiserror = "2.0"

eip7702-core/src/constants.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use alloy::primitives::{Address, address};
2+
3+
/// The minimal account implementation address used for EIP-7702 delegation
4+
pub const MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS: Address =
5+
address!("0xD6999651Fc0964B9c6B444307a0ab20534a66560");
6+
7+
/// EIP-7702 delegation prefix bytes
8+
pub const EIP_7702_DELEGATION_PREFIX: [u8; 3] = [0xef, 0x01, 0x00];
9+
10+
/// EIP-7702 delegation code length (prefix + address)
11+
pub const EIP_7702_DELEGATION_CODE_LENGTH: usize = 23;

eip7702-core/src/delegated_account.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use alloy::{
2+
primitives::{Address, FixedBytes},
3+
providers::Provider,
4+
};
5+
use engine_core::{
6+
chain::Chain,
7+
credentials::SigningCredential,
8+
error::{AlloyRpcErrorToEngineError, EngineError},
9+
signer::{AccountSigner, EoaSigner, EoaSigningOptions},
10+
};
11+
use rand::Rng;
12+
13+
use crate::constants::{
14+
EIP_7702_DELEGATION_CODE_LENGTH, EIP_7702_DELEGATION_PREFIX,
15+
MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS,
16+
};
17+
18+
/// Represents an EOA address that can have EIP-7702 delegation, associated with a specific chain
19+
#[derive(Clone, Debug)]
20+
pub struct DelegatedAccount<C: Chain> {
21+
/// The EOA address that may have delegation
22+
pub eoa_address: Address,
23+
/// The chain this account operates on
24+
pub chain: C,
25+
}
26+
27+
impl<C: Chain> DelegatedAccount<C> {
28+
/// Create a new delegated account from an EOA address and chain
29+
pub fn new(eoa_address: Address, chain: C) -> Self {
30+
Self { eoa_address, chain }
31+
}
32+
33+
/// Check if the EOA has EIP-7702 delegation to the minimal account implementation
34+
pub async fn is_minimal_account(&self) -> Result<bool, EngineError> {
35+
// Get the bytecode at the EOA address using eth_getCode
36+
let code = self
37+
.chain
38+
.provider()
39+
.get_code_at(self.eoa_address)
40+
.await
41+
.map_err(|e| e.to_engine_error(self.chain()))?;
42+
43+
tracing::debug!(
44+
eoa_address = ?self.eoa_address,
45+
code_length = code.len(),
46+
code_hex = ?alloy::hex::encode(&code),
47+
"Checking EIP-7702 delegation"
48+
);
49+
50+
// Check if code exists and starts with EIP-7702 delegation prefix "0xef0100"
51+
if code.len() < EIP_7702_DELEGATION_CODE_LENGTH
52+
|| !code.starts_with(&EIP_7702_DELEGATION_PREFIX)
53+
{
54+
tracing::debug!(
55+
eoa_address = ?self.eoa_address,
56+
has_delegation = false,
57+
reason = "Code too short or doesn't start with EIP-7702 prefix",
58+
"EIP-7702 delegation check result"
59+
);
60+
return Ok(false);
61+
}
62+
63+
// Extract the target address from bytes 3-23 (20 bytes for address)
64+
// EIP-7702 format: 0xef0100 + 20 bytes address
65+
let target_bytes = &code[3..23];
66+
let target_address = Address::from_slice(target_bytes);
67+
68+
// Compare with the minimal account implementation address
69+
let is_delegated = target_address == MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS;
70+
71+
tracing::debug!(
72+
eoa_address = ?self.eoa_address,
73+
target_address = ?target_address,
74+
minimal_account_address = ?MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS,
75+
has_delegation = is_delegated,
76+
"EIP-7702 delegation check result"
77+
);
78+
79+
Ok(is_delegated)
80+
}
81+
82+
/// Get the EOA address
83+
pub fn address(&self) -> Address {
84+
self.eoa_address
85+
}
86+
87+
/// Get the current nonce for the EOA
88+
pub async fn get_nonce(&self) -> Result<u64, EngineError> {
89+
self.chain
90+
.provider()
91+
.get_transaction_count(self.eoa_address)
92+
.await
93+
.map_err(|e| e.to_engine_error(self.chain()))
94+
}
95+
96+
/// Get a reference to the chain
97+
pub fn chain(&self) -> &C {
98+
&self.chain
99+
}
100+
101+
/// Sign authorization for EIP-7702 delegation (automatically fetches nonce)
102+
pub async fn sign_authorization(
103+
&self,
104+
eoa_signer: &EoaSigner,
105+
credentials: &SigningCredential,
106+
) -> Result<alloy::eips::eip7702::SignedAuthorization, EngineError> {
107+
let nonce = self.get_nonce().await?;
108+
109+
let signing_options = EoaSigningOptions {
110+
from: self.eoa_address,
111+
chain_id: Some(self.chain.chain_id()),
112+
};
113+
114+
eoa_signer
115+
.sign_authorization(
116+
signing_options,
117+
self.chain.chain_id(),
118+
MINIMAL_ACCOUNT_IMPLEMENTATION_ADDRESS,
119+
nonce,
120+
credentials,
121+
)
122+
.await
123+
}
124+
125+
/// Generate a random UID for wrapped calls
126+
pub fn generate_random_uid() -> FixedBytes<32> {
127+
let mut rng = rand::rng();
128+
let mut bytes = [0u8; 32];
129+
rng.fill(&mut bytes);
130+
FixedBytes::from(bytes)
131+
}
132+
}

eip7702-core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod constants;
2+
pub mod delegated_account;
3+
pub mod transaction;

0 commit comments

Comments
 (0)