Skip to content
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

feat: eth_sendRawTransactionConditional #2595

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
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<String, String>),
}
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
19 changes: 18 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,21 @@ 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,
prefix: Option<String>,
options: ConditionalOptions,
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
self.inner()
.send_raw_transaction_conditional(tx, prefix, 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
30 changes: 25 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,25 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
Ok(PendingTransaction::new(tx_hash, self))
}

async fn send_raw_transaction_conditional<'a>(
&'a self,
tx: Bytes,
prefix: Option<String>,
options: ConditionalOptions,
) -> Result<PendingTransaction<'a, P>, ProviderError> {
let rlp = utils::serialize(&tx);
let options = utils::serialize(&options);

let method = if let Some(prefix) = prefix {
format!("{}_sendRawTransactionConditional", prefix)
} else {
"eth_sendRawTransactionConditional".to_string()
};

let tx_hash = self.request(&method, [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
52 changes: 52 additions & 0 deletions examples/transactions/examples/send_raw_conditional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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,
None,
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(())
}
Loading