diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index c671cd2f263..164c442100f 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -98,6 +98,7 @@ anvil-node = [ "dep:alloy-signer-local", ] debug-api = ["dep:alloy-rpc-types-trace"] +erc4337-api = [] engine-api = ["dep:alloy-rpc-types-engine"] net-api = [] trace-api = ["dep:alloy-rpc-types-trace"] diff --git a/crates/provider/src/ext/erc4337.rs b/crates/provider/src/ext/erc4337.rs new file mode 100644 index 00000000000..ffd0b050dd3 --- /dev/null +++ b/crates/provider/src/ext/erc4337.rs @@ -0,0 +1,96 @@ +use crate::Provider; +use alloy_network::Network; +use alloy_primitives::{Address, Bytes}; +use alloy_rpc_types_eth::erc4337::{ + SendUserOperation, SendUserOperationResponse, UserOperationGasEstimation, UserOperationReceipt, +}; +use alloy_transport::{Transport, TransportResult}; + +/// ERC-4337 Account Abstraction API +/// +/// This module provides support for the `eth_sendUserOperation` RPC method +/// as defined in ERC-4337. +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +pub trait Erc4337Api: Send + Sync { + /// Sends a user operation to the bundler, as defined in ERC-4337. + /// + /// Entry point changes based on the user operation type. + async fn send_user_operation( + &self, + user_op: SendUserOperation, + entry_point: Address, + ) -> TransportResult; + + /// Returns the list of supported entry points. + async fn supported_entry_points(&self) -> TransportResult>; + + /// Returns the receipt for any user operation. + /// + /// Hash is the same returned by any user operation. + async fn get_user_operation_receipt( + &self, + user_op_hash: Bytes, + ) -> TransportResult; + + /// Estimates the gas for a user operation. + /// + /// Entry point changes based on the user operation type. + async fn estimate_user_operation_gas( + &self, + user_op: SendUserOperation, + entry_point: Address, + ) -> TransportResult; +} + +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +impl Erc4337Api for P +where + N: Network, + T: Transport + Clone, + P: Provider, +{ + async fn send_user_operation( + &self, + user_op: SendUserOperation, + entry_point: Address, + ) -> TransportResult { + match user_op { + SendUserOperation::EntryPointV06(user_op) => { + self.client().request("eth_sendUserOperation", (user_op, entry_point)).await + } + SendUserOperation::EntryPointV07(packed_user_op) => { + self.client().request("eth_sendUserOperation", (packed_user_op, entry_point)).await + } + } + } + + async fn supported_entry_points(&self) -> TransportResult> { + self.client().request("eth_supportedEntryPoints", ()).await + } + + async fn get_user_operation_receipt( + &self, + user_op_hash: Bytes, + ) -> TransportResult { + self.client().request("eth_getUserOperationReceipt", (user_op_hash,)).await + } + + async fn estimate_user_operation_gas( + &self, + user_op: SendUserOperation, + entry_point: Address, + ) -> TransportResult { + match user_op { + SendUserOperation::EntryPointV06(user_op) => { + self.client().request("eth_estimateUserOperationGas", (user_op, entry_point)).await + } + SendUserOperation::EntryPointV07(packed_user_op) => { + self.client() + .request("eth_estimateUserOperationGas", (packed_user_op, entry_point)) + .await + } + } + } +} diff --git a/crates/provider/src/ext/mod.rs b/crates/provider/src/ext/mod.rs index 7b6c0ffc1bf..a5f5c185a20 100644 --- a/crates/provider/src/ext/mod.rs +++ b/crates/provider/src/ext/mod.rs @@ -39,3 +39,8 @@ pub use rpc::RpcApi; mod txpool; #[cfg(feature = "txpool-api")] pub use txpool::TxPoolApi; + +#[cfg(feature = "erc4337-api")] +mod erc4337; +#[cfg(feature = "erc4337-api")] +pub use erc4337::Erc4337Api; diff --git a/crates/rpc-types-eth/CHANGELOG.md b/crates/rpc-types-eth/CHANGELOG.md index 132541f26d3..79ffa4ceb88 100644 --- a/crates/rpc-types-eth/CHANGELOG.md +++ b/crates/rpc-types-eth/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Eth_simulateV1 Request / Response types ([#1042](https://github.com/alloy-rs/alloy/issues/1042)) - Feat(rpc-type-eth) convert vec TxReq to bundle ([#1091](https://github.com/alloy-rs/alloy/issues/1091)) -- Feat(provider) : introduction to eth_sendRawTransactionConditional RPC endpoint type ([#1009](https://github.com/alloy-rs/alloy/issues/1009)) +- Feat(provider) : introduction to eth_sendRawTransactionConditional RPC endpoint type ([#1009](https://github.com/alloy-rs/alloy/issues/1009)) - [rpc-types-eth] Serde flatten `BlobTransactionSidecar` in tx req ([#1054](https://github.com/alloy-rs/alloy/issues/1054)) - Add authorization list to rpc transaction and tx receipt types ([#1051](https://github.com/alloy-rs/alloy/issues/1051)) @@ -40,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Refactor - Add network-primitives ([#1101](https://github.com/alloy-rs/alloy/issues/1101)) -- Replace `U64` with `u64` ([#1057](https://github.com/alloy-rs/alloy/issues/1057)) +- Replace `U64` with `u64` ([#1057](https://github.com/alloy-rs/alloy/issues/1057)) ### Styling @@ -112,7 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks - Rm unused txtype mod ([#879](https://github.com/alloy-rs/alloy/issues/879)) -- [other] Use type aliases where possible to improve clarity ([#859](https://github.com/alloy-rs/alloy/issues/859)) +- [other] Use type aliases where possible to improve clarity ([#859](https://github.com/alloy-rs/alloy/issues/859)) - [docs] Crate completeness and fix typos ([#861](https://github.com/alloy-rs/alloy/issues/861)) ### Other diff --git a/crates/rpc-types-eth/src/eip4337.rs b/crates/rpc-types-eth/src/eip4337.rs deleted file mode 100644 index 0a43da2f44f..00000000000 --- a/crates/rpc-types-eth/src/eip4337.rs +++ /dev/null @@ -1,41 +0,0 @@ -use alloy_primitives::{Address, BlockNumber, B256, U256}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// Options for conditional raw transaction submissions. -// reference for the implementation -// See also -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct ConditionalOptions { - /// A map of account addresses to their expected storage states. - /// Each account can have a specified storage root or explicit slot-value pairs. - #[serde(default)] - pub known_accounts: HashMap, - /// The minimal block number at which the transaction can be included. - /// `None` indicates no minimum block number constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub block_number_min: Option, - /// The maximal block number at which the transaction can be included. - /// `None` indicates no maximum block number constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub block_number_max: Option, - /// The minimal timestamp at which the transaction can be included. - /// `None` indicates no minimum timestamp constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub timestamp_min: Option, - /// The maximal timestamp at which the transaction can be included. - /// `None` indicates no maximum timestamp constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub timestamp_max: Option, -} - -/// Represents the expected state of an account for a transaction to be conditionally accepted. -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -pub enum AccountStorage { - /// Expected storage root hash of the account. - RootHash(B256), - /// Explicit storage slots and their expected values. - Slots(HashMap), -} diff --git a/crates/rpc-types-eth/src/erc4337.rs b/crates/rpc-types-eth/src/erc4337.rs new file mode 100644 index 00000000000..a4a3ea201bf --- /dev/null +++ b/crates/rpc-types-eth/src/erc4337.rs @@ -0,0 +1,171 @@ +use crate::{Log, TransactionReceipt}; +use alloy_primitives::{Address, BlockNumber, Bytes, B256, U256}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Options for conditional raw transaction submissions. +// reference for the implementation +// See also +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct ConditionalOptions { + /// A map of account addresses to their expected storage states. + /// Each account can have a specified storage root or explicit slot-value pairs. + #[serde(default)] + pub known_accounts: HashMap, + /// The minimal block number at which the transaction can be included. + /// `None` indicates no minimum block number constraint. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_number_min: Option, + /// The maximal block number at which the transaction can be included. + /// `None` indicates no maximum block number constraint. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_number_max: Option, + /// The minimal timestamp at which the transaction can be included. + /// `None` indicates no minimum timestamp constraint. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timestamp_min: Option, + /// The maximal timestamp at which the transaction can be included. + /// `None` indicates no maximum timestamp constraint. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timestamp_max: Option, +} + +/// Represents the expected state of an account for a transaction to be conditionally accepted. +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum AccountStorage { + /// Expected storage root hash of the account. + RootHash(B256), + /// Explicit storage slots and their expected values. + Slots(HashMap), +} + +/// [`UserOperation`] in the spec: Entry Point V0.6 +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserOperation { + /// The address of the smart contract account + pub sender: Address, + /// Anti-replay protection; also used as the salt for first-time account creation + pub nonce: U256, + /// Code used to deploy the account if not yet on-chain + pub init_code: Bytes, + /// Data that's passed to the sender for execution + pub call_data: Bytes, + /// Gas limit for execution phase + pub call_gas_limit: U256, + /// Gas limit for verification phase + pub verification_gas_limit: U256, + /// Gas to compensate the bundler + pub pre_verification_gas: U256, + /// Maximum fee per gas + pub max_fee_per_gas: U256, + /// Maximum priority fee per gas + pub max_priority_fee_per_gas: U256, + /// Paymaster Contract address and any extra data required for verification and execution + /// (empty for self-sponsored transaction) + pub paymaster_and_data: Bytes, + /// Used to validate a UserOperation along with the nonce during verification + pub signature: Bytes, +} + +/// [`PackedUserOperation`] in the spec: Entry Point V0.7 +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PackedUserOperation { + /// The account making the operation. + pub sender: Address, + /// Prevents message replay attacks and serves as a randomizing element for initial user + /// registration. + pub nonce: U256, + /// Deployer contract address: Required exclusively for deploying new accounts that don't yet + /// exist on the blockchain. + pub factory: Address, + /// Factory data for the account creation process, applicable only when using a deployer + /// contract. + pub factory_data: Bytes, + /// The call data. + pub call_data: Bytes, + /// The gas limit for the call. + pub call_gas_limit: U256, + /// The gas limit for the verification. + pub verification_gas_limit: U256, + /// Prepaid gas fee: Covers the bundler's costs for initial transaction validation and data + /// transmission. + pub pre_verification_gas: U256, + /// The maximum fee per gas. + pub max_fee_per_gas: U256, + /// The maximum priority fee per gas. + pub max_priority_fee_per_gas: U256, + /// Paymaster contract address: Needed if a third party is covering transaction costs; left + /// blank for self-funded accounts. + pub paymaster: Address, + /// The gas limit for the paymaster verification. + pub paymaster_verification_gas_limit: U256, + /// The gas limit for the paymaster post-operation. + pub paymaster_post_op_gas_limit: U256, + /// The paymaster data. + pub paymaster_data: Bytes, + /// The signature of the transaction. + pub signature: Bytes, +} + +/// Send User Operation +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SendUserOperation { + /// User Operation + EntryPointV06(UserOperation), + /// Packed User Operation + EntryPointV07(PackedUserOperation), +} + +/// Response to sending a user operation. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendUserOperationResponse { + /// The hash of the user operation. + pub user_op_hash: Bytes, +} + +/// Represents the receipt of a user operation. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserOperationReceipt { + /// The hash of the user operation. + pub user_op_hash: Bytes, + /// The entry point address for the user operation. + pub entry_point: Address, + /// The address of the sender of the user operation. + pub sender: Address, + /// The nonce of the user operation. + pub nonce: U256, + /// The address of the paymaster, if any. + pub paymaster: Address, + /// The actual gas cost incurred by the user operation. + pub actual_gas_cost: U256, + /// The actual gas used by the user operation. + pub actual_gas_used: U256, + /// Indicates whether the user operation was successful. + pub success: bool, + /// The reason for failure, if any. + pub reason: Bytes, + /// The logs generated by the user operation. + pub logs: Vec, + /// The transaction receipt of the user operation. + pub receipt: TransactionReceipt, +} + +/// Represents the gas estimation for a user operation. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserOperationGasEstimation { + /// The gas limit for the pre-verification. + pub pre_verification_gas: U256, + /// The gas limit for the verification. + pub verification_gas: U256, + /// The gas limit for the paymaster verification. + pub paymaster_verification_gas: U256, + /// The gas limit for the call. + pub call_gas_limit: U256, +} diff --git a/crates/rpc-types-eth/src/lib.rs b/crates/rpc-types-eth/src/lib.rs index 85870935fc9..27b58ae63be 100644 --- a/crates/rpc-types-eth/src/lib.rs +++ b/crates/rpc-types-eth/src/lib.rs @@ -52,6 +52,10 @@ mod work; pub use work::Work; /// This module provides implementations for EIP-4337. -pub mod eip4337; +pub mod erc4337; +pub use erc4337::{ + PackedUserOperation, SendUserOperation, SendUserOperationResponse, UserOperation, + UserOperationGasEstimation, UserOperationReceipt, +}; pub mod simulate;