Skip to content

Create new executor for 7702 transactions #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions core/src/execution_options/eip7702.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use alloy::primitives::Address;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::defs::AddressDef;

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Eip7702ExecutionOptions {
/// The EOA address that will sign the EIP-7702 transaction
#[schemars(with = "AddressDef")]
#[schema(value_type = AddressDef)]
pub from: Address,
}
7 changes: 7 additions & 0 deletions core/src/execution_options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::collections::HashMap;
use crate::transaction::InnerTransaction;
pub mod aa;
pub mod auto;
pub mod eip7702;

// Base execution options for all transactions
// All specific execution options share this
Expand Down Expand Up @@ -35,6 +36,9 @@ pub enum SpecificExecutionOptions {

#[schema(title = "ERC-4337 Execution Options")]
ERC4337(aa::Erc4337ExecutionOptions),

#[schema(title = "EIP-7702 Execution Options")]
EIP7702(eip7702::Eip7702ExecutionOptions),
}

fn deserialize_with_default_auto<'de, D>(
Expand Down Expand Up @@ -118,13 +122,16 @@ pub struct QueuedTransactionsResponse {
pub enum ExecutorType {
#[serde(rename = "ERC4337")]
Erc4337,
#[serde(rename = "EIP7702")]
Eip7702,
}

impl ExecutionOptions {
pub fn executor_type(&self) -> ExecutorType {
match &self.specific {
SpecificExecutionOptions::ERC4337(_) => ExecutorType::Erc4337,
SpecificExecutionOptions::Auto(_) => ExecutorType::Erc4337,
SpecificExecutionOptions::EIP7702(_) => ExecutorType::Eip7702,
}
}

Expand Down
43 changes: 43 additions & 0 deletions core/src/rpc_clients/bundler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use alloy::consensus::{Receipt, ReceiptWithBloom};
use alloy::eips::eip7702::SignedAuthorization;
use alloy::primitives::{Address, Bytes, U256};
use alloy::rpc::client::RpcClient;
use alloy::rpc::types::{Log, TransactionReceipt};
use alloy::transports::{IntoBoxTransport, TransportResult};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

use crate::userop::VersionedUserOp;
Expand Down Expand Up @@ -60,6 +62,22 @@ pub struct UserOperationReceipt {
pub receipt: TransactionReceipt<ReceiptWithBloom<Receipt<Log>>>,
}

/// Response from tw_execute bundler method
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TwExecuteResponse {
/// The queue ID returned by the bundler
pub queue_id: String,
}

/// Response from tw_getTransactionHash bundler method
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TwGetTransactionHashResponse {
/// The transaction hash
pub transaction_hash: String,
}

impl BundlerClient {
/// Create a new bundler client with the given transport
pub fn new(transport: impl IntoBoxTransport) -> Self {
Expand Down Expand Up @@ -114,4 +132,29 @@ impl BundlerClient {

Ok(result)
}

/// Execute an EIP-7702 transaction via the bundler
pub async fn tw_execute(
&self,
eoa_address: Address,
wrapped_calls: &Value,
signature: &str,
authorization: Option<&SignedAuthorization>,
) -> TransportResult<String> {
let params = serde_json::json!([eoa_address, wrapped_calls, signature, authorization]);

let response: TwExecuteResponse = self.inner.request("tw_execute", params).await?;

Ok(response.queue_id)
}

/// Get transaction hash from bundler using transaction ID
pub async fn tw_get_transaction_hash(&self, transaction_id: &str) -> TransportResult<String> {
let params = serde_json::json!([transaction_id]);

let response: TwGetTransactionHashResponse =
self.inner.request("tw_getTransactionHash", params).await?;

Ok(response.transaction_hash)
}
}
77 changes: 73 additions & 4 deletions core/src/signer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use alloy::{
dyn_abi::TypedData,
eips::eip7702::SignedAuthorization,
hex::FromHex,
primitives::{Address, Bytes, ChainId},
primitives::{Address, Bytes, ChainId, U256},
rpc::types::Authorization,
};
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, PickFirst, serde_as};
Expand Down Expand Up @@ -176,6 +178,16 @@ pub trait AccountSigner {
typed_data: &TypedData,
credentials: SigningCredential,
) -> impl std::future::Future<Output = Result<String, EngineError>> + Send;

/// Sign EIP-7702 authorization
fn sign_authorization(
&self,
options: Self::SigningOptions,
chain_id: u64,
address: Address,
nonce: alloy::primitives::U256,
credentials: SigningCredential,
) -> impl std::future::Future<Output = Result<SignedAuthorization, EngineError>> + Send;
}

/// EOA signer implementation
Expand All @@ -188,7 +200,10 @@ pub struct EoaSigner {
impl EoaSigner {
/// Create a new EOA signer
pub fn new(vault_client: VaultClient, iaw_client: IAWClient) -> Self {
Self { vault_client, iaw_client }
Self {
vault_client,
iaw_client,
}
}
}

Expand Down Expand Up @@ -221,7 +236,10 @@ impl AccountSigner for EoaSigner {

Ok(vault_result.signature)
}
SigningCredential::Iaw { auth_token, thirdweb_auth } => {
SigningCredential::Iaw {
auth_token,
thirdweb_auth,
} => {
// Convert MessageFormat to IAW MessageFormat
let iaw_format = match format {
MessageFormat::Text => thirdweb_core::iaw::MessageFormat::Text,
Expand Down Expand Up @@ -268,7 +286,10 @@ impl AccountSigner for EoaSigner {

Ok(vault_result.signature)
}
SigningCredential::Iaw { auth_token, thirdweb_auth } => {
SigningCredential::Iaw {
auth_token,
thirdweb_auth,
} => {
let iaw_result = self
.iaw_client
.sign_typed_data(
Expand All @@ -287,6 +308,54 @@ impl AccountSigner for EoaSigner {
}
}
}

async fn sign_authorization(
&self,
options: EoaSigningOptions,
chain_id: u64,
address: Address,
nonce: U256,
credentials: SigningCredential,
) -> Result<SignedAuthorization, EngineError> {
// Create the Authorization struct that both clients expect
let authorization = Authorization {
chain_id: U256::from(chain_id),
address,
nonce: nonce.to::<u64>(),
};

match credentials {
SigningCredential::Vault(auth_method) => {
let vault_result = self
.vault_client
.sign_authorization(auth_method, options.from, authorization)
.await
.map_err(|e| {
tracing::error!("Error signing authorization with EOA (Vault): {:?}", e);
e
})?;

// Return the signed authorization as Authorization
Ok(vault_result.signed_authorization)
}
SigningCredential::Iaw {
auth_token,
thirdweb_auth,
} => {
let iaw_result = self
.iaw_client
.sign_authorization(auth_token, thirdweb_auth, options.from, authorization)
.await
.map_err(|e| {
tracing::error!("Error signing authorization with EOA (IAW): {:?}", e);
EngineError::from(e)
})?;

// Return the signed authorization as Authorization
Ok(iaw_result.signed_authorization)
}
}
}
}

/// Parameters for signing a message (used in routes)
Expand Down
Loading