Skip to content

Commit fee97d9

Browse files
Create new executor for 7702 transactions (#6)
* Checkpoint before follow-up message * Implement EIP-7702 executor with send and confirm stages Co-authored-by: joaquim.verges <joaquim.verges@gmail.com> * Implement EIP-7702 delegation check for minimal account authorization Co-authored-by: joaquim.verges <joaquim.verges@gmail.com> * fix authorization sign * types * compiles * registry * wire it up * works * cleanup --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent f1d53d8 commit fee97d9

File tree

12 files changed

+1224
-22
lines changed

12 files changed

+1224
-22
lines changed

core/src/execution_options/eip7702.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use alloy::primitives::Address;
2+
use schemars::JsonSchema;
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::defs::AddressDef;
6+
7+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, utoipa::ToSchema)]
8+
#[serde(rename_all = "camelCase")]
9+
pub struct Eip7702ExecutionOptions {
10+
/// The EOA address that will sign the EIP-7702 transaction
11+
#[schemars(with = "AddressDef")]
12+
#[schema(value_type = AddressDef)]
13+
pub from: Address,
14+
}

core/src/execution_options/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::collections::HashMap;
77
use crate::transaction::InnerTransaction;
88
pub mod aa;
99
pub mod auto;
10+
pub mod eip7702;
1011

1112
// Base execution options for all transactions
1213
// All specific execution options share this
@@ -35,6 +36,9 @@ pub enum SpecificExecutionOptions {
3536

3637
#[schema(title = "ERC-4337 Execution Options")]
3738
ERC4337(aa::Erc4337ExecutionOptions),
39+
40+
#[schema(title = "EIP-7702 Execution Options")]
41+
EIP7702(eip7702::Eip7702ExecutionOptions),
3842
}
3943

4044
fn deserialize_with_default_auto<'de, D>(
@@ -118,13 +122,16 @@ pub struct QueuedTransactionsResponse {
118122
pub enum ExecutorType {
119123
#[serde(rename = "ERC4337")]
120124
Erc4337,
125+
#[serde(rename = "EIP7702")]
126+
Eip7702,
121127
}
122128

123129
impl ExecutionOptions {
124130
pub fn executor_type(&self) -> ExecutorType {
125131
match &self.specific {
126132
SpecificExecutionOptions::ERC4337(_) => ExecutorType::Erc4337,
127133
SpecificExecutionOptions::Auto(_) => ExecutorType::Erc4337,
134+
SpecificExecutionOptions::EIP7702(_) => ExecutorType::Eip7702,
128135
}
129136
}
130137

core/src/rpc_clients/bundler.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use alloy::consensus::{Receipt, ReceiptWithBloom};
2+
use alloy::eips::eip7702::SignedAuthorization;
23
use alloy::primitives::{Address, Bytes, U256};
34
use alloy::rpc::client::RpcClient;
45
use alloy::rpc::types::{Log, TransactionReceipt};
56
use alloy::transports::{IntoBoxTransport, TransportResult};
67
use serde::{Deserialize, Serialize};
8+
use serde_json::Value;
79
use std::collections::HashMap;
810

911
use crate::userop::VersionedUserOp;
@@ -60,6 +62,22 @@ pub struct UserOperationReceipt {
6062
pub receipt: TransactionReceipt<ReceiptWithBloom<Receipt<Log>>>,
6163
}
6264

65+
/// Response from tw_execute bundler method
66+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67+
#[serde(rename_all = "camelCase")]
68+
pub struct TwExecuteResponse {
69+
/// The queue ID returned by the bundler
70+
pub queue_id: String,
71+
}
72+
73+
/// Response from tw_getTransactionHash bundler method
74+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75+
#[serde(rename_all = "camelCase")]
76+
pub struct TwGetTransactionHashResponse {
77+
/// The transaction hash
78+
pub transaction_hash: String,
79+
}
80+
6381
impl BundlerClient {
6482
/// Create a new bundler client with the given transport
6583
pub fn new(transport: impl IntoBoxTransport) -> Self {
@@ -114,4 +132,29 @@ impl BundlerClient {
114132

115133
Ok(result)
116134
}
135+
136+
/// Execute an EIP-7702 transaction via the bundler
137+
pub async fn tw_execute(
138+
&self,
139+
eoa_address: Address,
140+
wrapped_calls: &Value,
141+
signature: &str,
142+
authorization: Option<&SignedAuthorization>,
143+
) -> TransportResult<String> {
144+
let params = serde_json::json!([eoa_address, wrapped_calls, signature, authorization]);
145+
146+
let response: TwExecuteResponse = self.inner.request("tw_execute", params).await?;
147+
148+
Ok(response.queue_id)
149+
}
150+
151+
/// Get transaction hash from bundler using transaction ID
152+
pub async fn tw_get_transaction_hash(&self, transaction_id: &str) -> TransportResult<String> {
153+
let params = serde_json::json!([transaction_id]);
154+
155+
let response: TwGetTransactionHashResponse =
156+
self.inner.request("tw_getTransactionHash", params).await?;
157+
158+
Ok(response.transaction_hash)
159+
}
117160
}

core/src/signer.rs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use alloy::{
22
dyn_abi::TypedData,
3+
eips::eip7702::SignedAuthorization,
34
hex::FromHex,
4-
primitives::{Address, Bytes, ChainId},
5+
primitives::{Address, Bytes, ChainId, U256},
6+
rpc::types::Authorization,
57
};
68
use serde::{Deserialize, Serialize};
79
use serde_with::{DisplayFromStr, PickFirst, serde_as};
@@ -176,6 +178,16 @@ pub trait AccountSigner {
176178
typed_data: &TypedData,
177179
credentials: SigningCredential,
178180
) -> impl std::future::Future<Output = Result<String, EngineError>> + Send;
181+
182+
/// Sign EIP-7702 authorization
183+
fn sign_authorization(
184+
&self,
185+
options: Self::SigningOptions,
186+
chain_id: u64,
187+
address: Address,
188+
nonce: alloy::primitives::U256,
189+
credentials: SigningCredential,
190+
) -> impl std::future::Future<Output = Result<SignedAuthorization, EngineError>> + Send;
179191
}
180192

181193
/// EOA signer implementation
@@ -188,7 +200,10 @@ pub struct EoaSigner {
188200
impl EoaSigner {
189201
/// Create a new EOA signer
190202
pub fn new(vault_client: VaultClient, iaw_client: IAWClient) -> Self {
191-
Self { vault_client, iaw_client }
203+
Self {
204+
vault_client,
205+
iaw_client,
206+
}
192207
}
193208
}
194209

@@ -221,7 +236,10 @@ impl AccountSigner for EoaSigner {
221236

222237
Ok(vault_result.signature)
223238
}
224-
SigningCredential::Iaw { auth_token, thirdweb_auth } => {
239+
SigningCredential::Iaw {
240+
auth_token,
241+
thirdweb_auth,
242+
} => {
225243
// Convert MessageFormat to IAW MessageFormat
226244
let iaw_format = match format {
227245
MessageFormat::Text => thirdweb_core::iaw::MessageFormat::Text,
@@ -268,7 +286,10 @@ impl AccountSigner for EoaSigner {
268286

269287
Ok(vault_result.signature)
270288
}
271-
SigningCredential::Iaw { auth_token, thirdweb_auth } => {
289+
SigningCredential::Iaw {
290+
auth_token,
291+
thirdweb_auth,
292+
} => {
272293
let iaw_result = self
273294
.iaw_client
274295
.sign_typed_data(
@@ -287,6 +308,54 @@ impl AccountSigner for EoaSigner {
287308
}
288309
}
289310
}
311+
312+
async fn sign_authorization(
313+
&self,
314+
options: EoaSigningOptions,
315+
chain_id: u64,
316+
address: Address,
317+
nonce: U256,
318+
credentials: SigningCredential,
319+
) -> Result<SignedAuthorization, EngineError> {
320+
// Create the Authorization struct that both clients expect
321+
let authorization = Authorization {
322+
chain_id: U256::from(chain_id),
323+
address,
324+
nonce: nonce.to::<u64>(),
325+
};
326+
327+
match credentials {
328+
SigningCredential::Vault(auth_method) => {
329+
let vault_result = self
330+
.vault_client
331+
.sign_authorization(auth_method, options.from, authorization)
332+
.await
333+
.map_err(|e| {
334+
tracing::error!("Error signing authorization with EOA (Vault): {:?}", e);
335+
e
336+
})?;
337+
338+
// Return the signed authorization as Authorization
339+
Ok(vault_result.signed_authorization)
340+
}
341+
SigningCredential::Iaw {
342+
auth_token,
343+
thirdweb_auth,
344+
} => {
345+
let iaw_result = self
346+
.iaw_client
347+
.sign_authorization(auth_token, thirdweb_auth, options.from, authorization)
348+
.await
349+
.map_err(|e| {
350+
tracing::error!("Error signing authorization with EOA (IAW): {:?}", e);
351+
EngineError::from(e)
352+
})?;
353+
354+
// Return the signed authorization as Authorization
355+
Ok(iaw_result.signed_authorization)
356+
}
357+
}
358+
}
290359
}
291360

292361
/// Parameters for signing a message (used in routes)

0 commit comments

Comments
 (0)