Skip to content

Commit

Permalink
feat: support eth_sendRawTransactionConditional
Browse files Browse the repository at this point in the history
  • Loading branch information
Vid201 committed Apr 8, 2024
1 parent 5394d89 commit fef431a
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 7 deletions.
35 changes: 35 additions & 0 deletions ethers-core/src/types/transaction/conditional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::types::{Address, BlockNumber, H256};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Extra options parameter for `eth_sendRawTransactionConditional`
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConditionalOptions {
/// A map of accounts with expected storage
#[serde(rename = "knownAccounts")]
pub known_accounts: HashMap<Address, AccountStorage>,

/// Minimal block number for inclusion
#[serde(rename = "blockNumberMin", skip_serializing_if = "Option::is_none")]
pub block_number_min: Option<BlockNumber>,

/// Maximum block number for inclusion
#[serde(rename = "blockNumberMax", skip_serializing_if = "Option::is_none")]
pub block_number_max: Option<BlockNumber>,

/// Minimal block timestamp for inclusion
#[serde(rename = "timestampMin", skip_serializing_if = "Option::is_none")]
pub timestamp_min: Option<u64>,

/// Maximum block timestamp for inclusion
#[serde(rename = "timestampMax", skip_serializing_if = "Option::is_none")]
pub timestamp_max: Option<u64>,
}

/// Account storage
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AccountStorage {
RootHash(H256),
SlotValues(HashMap<H256, H256>),
}
2 changes: 2 additions & 0 deletions ethers-core/src/types/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod eip1559;
pub mod eip2718;
pub mod eip2930;

pub mod conditional;

#[cfg(feature = "optimism")]
pub mod optimism;

Expand Down
2 changes: 1 addition & 1 deletion ethers-middleware/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ where
/// If the transaction does not have a chain id set, it sets it to the signer's chain id.
/// Returns an error if the transaction's existing chain id does not match the signer's chain
/// id.
async fn sign_transaction(
pub async fn sign_transaction(
&self,
mut tx: TypedTransaction,
) -> Result<Bytes, SignerMiddlewareError<M, S>> {
Expand Down
18 changes: 17 additions & 1 deletion ethers-providers/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers_core::types::{
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
transaction::{
conditional::ConditionalOptions, eip2718::TypedTransaction, eip2930::AccessListWithGasUsed,
},
*,
};
use futures_util::future::join_all;
Expand Down Expand Up @@ -460,6 +462,20 @@ pub trait Middleware: Sync + Send + Debug {
self.inner().send_raw_transaction(tx).await.map_err(MiddlewareError::from_err)
}

/// Send the raw RLP encoded transaction to the entire Ethereum network with conditional options
/// and returns the transaction's hash This will consume gas from the account that signed the
/// transaction. <https://notes.ethereum.org/@yoav/SkaX2lS9j#>
async fn send_raw_transaction_conditional<'a>(
&'a self,
tx: Bytes,
options: ConditionalOptions,
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
self.inner()
.send_raw_transaction_conditional(tx, options)
.await
.map_err(MiddlewareError::from_err)
}

/// This returns true if either the middleware stack contains a `SignerMiddleware`, or the
/// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of
/// the above conditions are met, then the middleware stack is not capable of signing data.
Expand Down
22 changes: 17 additions & 5 deletions ethers-providers/src/rpc/provider.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use ethers_core::types::SyncingStatus;

use crate::{
call_raw::CallBuilder,
errors::ProviderError,
Expand All @@ -22,11 +20,14 @@ use async_trait::async_trait;
use ethers_core::{
abi::{self, Detokenize, ParamType},
types::{
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
transaction::{
conditional::ConditionalOptions, eip2718::TypedTransaction,
eip2930::AccessListWithGasUsed,
},
Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Chain, EIP1186ProofResponse,
FeeHistory, Filter, FilterBlockOption, GethDebugTracingCallOptions,
GethDebugTracingOptions, GethTrace, Log, NameOrAddress, Selector, Signature, Trace,
TraceFilter, TraceType, Transaction, TransactionReceipt, TransactionRequest, TxHash,
GethDebugTracingOptions, GethTrace, Log, NameOrAddress, Selector, Signature, SyncingStatus,
Trace, TraceFilter, TraceType, Transaction, TransactionReceipt, TransactionRequest, TxHash,
TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64,
},
utils,
Expand Down Expand Up @@ -581,6 +582,17 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(PendingTransaction::new(tx_hash, self))
}

async fn send_raw_transaction_conditional<'a>(
&'a self,
tx: Bytes,
options: ConditionalOptions,
) -> Result<PendingTransaction<'a, P>, ProviderError> {
let rlp = utils::serialize(&tx);
let options = utils::serialize(&options);
let tx_hash = self.request("eth_sendRawTransactionConditional", [rlp, options]).await?;
Ok(PendingTransaction::new(tx_hash, self))
}

async fn is_signer(&self) -> bool {
match self.from {
Some(sender) => self.sign(vec![], &sender).await.is_ok(),
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
- [x] Remove liquidity
- [ ] Set gas for a transaction
- [ ] Send raw transaction
- [x] Send raw transaction conditional
- [ ] Send typed transaction
- [x] Trace
- [ ] Transaction receipt
Expand Down
51 changes: 51 additions & 0 deletions examples/transactions/examples/send_raw_conditional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use ethers::{
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
types::{transaction::conditional::ConditionalOptions, BlockNumber, TransactionRequest},
};
use eyre::Result;

/// Use 'eth_sendRawTransactionConditional' to send a transaction with a conditional options
/// requires, a valid endpoint in `RPC_URL` env var that supports
/// `eth_sendRawTransactionConditional`
#[tokio::main]
async fn main() -> Result<()> {
if let Ok(url) = std::env::var("RPC_URL") {
let provider = Provider::<Http>::try_from(url)?;
let chain_id = provider.get_chainid().await?;
let wallet: LocalWallet =
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse()?;
let from = wallet.address();

let client = SignerMiddleware::new(provider, wallet.with_chain_id(chain_id.as_u64()));

let mut tx = TransactionRequest::default()
.from(from)
.to("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
.value(100)
.into();

client.fill_transaction(&mut tx, None).await.unwrap();

let signed_tx = client.sign_transaction(tx).await.unwrap();
let pending_tx = client
.send_raw_transaction_conditional(
signed_tx,
ConditionalOptions {
block_number_min: Some(BlockNumber::from(33285900)),
..Default::default()
},
)
.await
.unwrap();

let receipt = pending_tx.await?.ok_or_else(|| eyre::eyre!("tx not included"))?;
let tx = client.get_transaction(receipt.transaction_hash).await?;

println!("Sent transaction: {}\n", serde_json::to_string(&tx)?);
println!("Receipt: {}\n", serde_json::to_string(&receipt)?);
}

Ok(())
}

0 comments on commit fef431a

Please sign in to comment.