feat: eip2930/1559 response type adjustments (part 1) (#353)
* refactor: move tx types to subdirectory * feat(receipt): add tx-type and effective gas price for eip1559 * feat: add base_fee_per_gas to Block * feat(tx): add eip1559/2930/2718 ret values * fix(middleware): return signed raw rlp from tx signer / adjust tests * chore: remove unused vars * chore(core): remove funty lock from Cargo.toml * chore: rename json var / cargo fmt * add more context on eip1559 fees
This commit is contained in:
parent
f165c13009
commit
09ff480f19
|
@ -875,7 +875,6 @@ dependencies = [
|
|||
"elliptic-curve",
|
||||
"ethabi",
|
||||
"ethers",
|
||||
"funty",
|
||||
"futures-util",
|
||||
"generic-array 0.14.4",
|
||||
"glob",
|
||||
|
@ -885,6 +884,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"rand 0.8.4",
|
||||
"rlp",
|
||||
"rlp-derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
|
@ -2124,6 +2124,17 @@ dependencies = [
|
|||
"rustc-hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rlp-derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusb"
|
||||
version = "0.8.1"
|
||||
|
|
|
@ -13,6 +13,7 @@ keywords = ["ethereum", "web3", "celo", "ethers"]
|
|||
rlp = { version = "0.5.0", default-features = false }
|
||||
ethabi = { version = "14.1.0", default-features = false }
|
||||
arrayvec = { version = "0.7.1", default-features = false }
|
||||
rlp-derive = { version = "0.1.0", default-features = false }
|
||||
|
||||
# crypto
|
||||
ecdsa = { version = "0.12.3", default-features = false, features = ["std"] }
|
||||
|
@ -30,10 +31,6 @@ glob = { version = "0.3.0", default-features = false }
|
|||
bytes = { version = "1.0.1", features = ["serde"] }
|
||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||
|
||||
# bitvec compilation issue
|
||||
# https://github.com/bitvecto-rs/bitvec/issues/105#issuecomment-778570981
|
||||
funty = "=1.1.0"
|
||||
|
||||
# async
|
||||
tokio = { version = "1.5", default-features = false, optional = true}
|
||||
futures-util = { version = "0.3.16", default-features = false, optional = true}
|
||||
|
|
|
@ -71,6 +71,9 @@ pub struct Block<TX> {
|
|||
/// Nonce
|
||||
#[cfg(not(feature = "celo"))]
|
||||
pub nonce: Option<U64>,
|
||||
/// Base fee per unit of gas (if past London)
|
||||
#[serde(rename = "baseFeePerGas")]
|
||||
pub base_fee_per_gas: Option<U256>,
|
||||
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
|
@ -206,6 +209,44 @@ mod tests {
|
|||
let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
|
||||
let _block: Block<Transaction> = serde_json::from_str(block).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
// https://github.com/tomusdrw/rust-web3/commit/3a32ee962c0f2f8d50a5e25be9f2dfec7ae0750d
|
||||
fn post_london_block() {
|
||||
let json = serde_json::json!(
|
||||
{
|
||||
"baseFeePerGas": "0x7",
|
||||
"miner": "0x0000000000000000000000000000000000000001",
|
||||
"number": "0x1b4",
|
||||
"hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
|
||||
"parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
|
||||
"mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
|
||||
"nonce": "0x0000000000000000",
|
||||
"sealFields": [
|
||||
"0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
|
||||
"0x0000000000000042"
|
||||
],
|
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
|
||||
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
|
||||
"difficulty": "0x27f07",
|
||||
"totalDifficulty": "0x27f07",
|
||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"size": "0x27f07",
|
||||
"gasLimit": "0x9f759",
|
||||
"minGasPrice": "0x9f759",
|
||||
"gasUsed": "0x9f759",
|
||||
"timestamp": "0x54e34e8e",
|
||||
"transactions": [],
|
||||
"uncles": []
|
||||
}
|
||||
);
|
||||
|
||||
let block: Block<()> = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(block.base_fee_per_gas, Some(U256::from(7)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -8,7 +8,8 @@ pub use ethabi::ethereum_types::H256 as TxHash;
|
|||
pub use ethabi::ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64};
|
||||
|
||||
mod transaction;
|
||||
pub use transaction::{Transaction, TransactionReceipt, TransactionRequest};
|
||||
pub use transaction::request::TransactionRequest;
|
||||
pub use transaction::response::{Transaction, TransactionReceipt};
|
||||
|
||||
mod address_or_bytes;
|
||||
pub use address_or_bytes::AddressOrBytes;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
use crate::types::{Address, H256};
|
||||
|
||||
use rlp_derive::RlpEncodable;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Access list
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)]
|
||||
pub struct AccessList(pub Vec<AccessListItem>);
|
||||
|
||||
impl From<Vec<AccessListItem>> for AccessList {
|
||||
fn from(src: Vec<AccessListItem>) -> AccessList {
|
||||
AccessList(src)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access list item
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AccessListItem {
|
||||
/// Accessed address
|
||||
pub address: Address,
|
||||
/// Accessed storage keys
|
||||
pub storage_keys: Vec<H256>,
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub mod eip2930;
|
||||
|
||||
pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;
|
||||
|
||||
// Number of tx fields before signing
|
||||
#[cfg(not(feature = "celo"))]
|
||||
pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
|
||||
// Celo has 3 additional fields
|
||||
#[cfg(feature = "celo")]
|
||||
pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3;
|
||||
|
||||
pub(super) fn rlp_opt<T: rlp::Encodable>(rlp: &mut rlp::RlpStream, opt: Option<T>) {
|
||||
if let Some(ref inner) = opt {
|
||||
rlp.append(inner);
|
||||
} else {
|
||||
rlp.append(&"");
|
||||
}
|
||||
}
|
|
@ -1,21 +1,13 @@
|
|||
//! Transaction types
|
||||
use super::{rlp_opt, NUM_TX_FIELDS};
|
||||
use crate::{
|
||||
types::{Address, Bloom, Bytes, Log, NameOrAddress, Signature, H256, U256, U64},
|
||||
types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64},
|
||||
utils::keccak256,
|
||||
};
|
||||
|
||||
use rlp::RlpStream;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const BASE_NUM_TX_FIELDS: usize = 9;
|
||||
|
||||
// Number of tx fields before signing
|
||||
#[cfg(not(feature = "celo"))]
|
||||
const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
|
||||
// Celo has 3 additional fields
|
||||
#[cfg(feature = "celo")]
|
||||
const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3;
|
||||
|
||||
/// Parameters for sending a transaction
|
||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct TransactionRequest {
|
||||
|
@ -160,7 +152,7 @@ impl TransactionRequest {
|
|||
rlp.out().freeze().into()
|
||||
}
|
||||
|
||||
fn rlp_base(&self, rlp: &mut RlpStream) {
|
||||
pub(crate) fn rlp_base(&self, rlp: &mut RlpStream) {
|
||||
rlp_opt(rlp, self.nonce);
|
||||
rlp_opt(rlp, self.gas_price);
|
||||
rlp_opt(rlp, self.gas);
|
||||
|
@ -206,163 +198,6 @@ impl TransactionRequest {
|
|||
}
|
||||
}
|
||||
|
||||
fn rlp_opt<T: rlp::Encodable>(rlp: &mut RlpStream, opt: Option<T>) {
|
||||
if let Some(ref inner) = opt {
|
||||
rlp.append(inner);
|
||||
} else {
|
||||
rlp.append(&"");
|
||||
}
|
||||
}
|
||||
|
||||
/// Details of a signed transaction
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct Transaction {
|
||||
/// The transaction's hash
|
||||
pub hash: H256,
|
||||
|
||||
/// The transaction's nonce
|
||||
pub nonce: U256,
|
||||
|
||||
/// Block hash. None when pending.
|
||||
#[serde(rename = "blockHash")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub block_hash: Option<H256>,
|
||||
|
||||
/// Block number. None when pending.
|
||||
#[serde(rename = "blockNumber")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub block_number: Option<U64>,
|
||||
|
||||
/// Transaction Index. None when pending.
|
||||
#[serde(rename = "transactionIndex")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_index: Option<U64>,
|
||||
|
||||
/// Sender
|
||||
pub from: Address,
|
||||
|
||||
/// Recipient (None when contract creation)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub to: Option<Address>,
|
||||
|
||||
/// Transfered value
|
||||
pub value: U256,
|
||||
|
||||
/// Gas Price
|
||||
#[serde(rename = "gasPrice")]
|
||||
pub gas_price: U256,
|
||||
|
||||
/// Gas amount
|
||||
pub gas: U256,
|
||||
|
||||
/// Input data
|
||||
pub input: Bytes,
|
||||
|
||||
/// ECDSA recovery id
|
||||
pub v: U64,
|
||||
|
||||
/// ECDSA signature r
|
||||
pub r: U256,
|
||||
|
||||
/// ECDSA signature s
|
||||
pub s: U256,
|
||||
|
||||
///////////////// Celo-specific transaction fields /////////////////
|
||||
/// The currency fees are paid in (None for native currency)
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
|
||||
pub fee_currency: Option<Address>,
|
||||
|
||||
/// Gateway fee recipient (None for no gateway fee paid)
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
#[serde(
|
||||
skip_serializing_if = "Option::is_none",
|
||||
rename = "gatewayFeeRecipient"
|
||||
)]
|
||||
pub gateway_fee_recipient: Option<Address>,
|
||||
|
||||
/// Gateway fee amount (None for no gateway fee paid)
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
|
||||
pub gateway_fee: Option<U256>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
// modifies the RLP stream with the Celo-specific information
|
||||
// This is duplicated from TransactionRequest. Is there a good way to get rid
|
||||
// of this code duplication?
|
||||
#[cfg(feature = "celo")]
|
||||
fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
|
||||
rlp_opt(rlp, self.fee_currency);
|
||||
rlp_opt(rlp, self.gateway_fee_recipient);
|
||||
rlp_opt(rlp, self.gateway_fee);
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> H256 {
|
||||
keccak256(&self.rlp().as_ref()).into()
|
||||
}
|
||||
|
||||
pub fn rlp(&self) -> Bytes {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.begin_list(NUM_TX_FIELDS);
|
||||
rlp.append(&self.nonce);
|
||||
rlp.append(&self.gas_price);
|
||||
rlp.append(&self.gas);
|
||||
|
||||
#[cfg(feature = "celo")]
|
||||
self.inject_celo_metadata(&mut rlp);
|
||||
|
||||
rlp_opt(&mut rlp, self.to);
|
||||
rlp.append(&self.value);
|
||||
rlp.append(&self.input.as_ref());
|
||||
rlp.append(&self.v);
|
||||
rlp.append(&self.r);
|
||||
rlp.append(&self.s);
|
||||
|
||||
rlp.out().freeze().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// "Receipt" of an executed transaction: details of its execution.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TransactionReceipt {
|
||||
/// Transaction hash.
|
||||
#[serde(rename = "transactionHash")]
|
||||
pub transaction_hash: H256,
|
||||
/// Index within the block.
|
||||
#[serde(rename = "transactionIndex")]
|
||||
pub transaction_index: U64,
|
||||
/// Hash of the block this transaction was included within.
|
||||
#[serde(rename = "blockHash")]
|
||||
pub block_hash: Option<H256>,
|
||||
/// Number of the block this transaction was included within.
|
||||
#[serde(rename = "blockNumber")]
|
||||
pub block_number: Option<U64>,
|
||||
/// Cumulative gas used within the block after this was executed.
|
||||
#[serde(rename = "cumulativeGasUsed")]
|
||||
pub cumulative_gas_used: U256,
|
||||
/// Gas used by this transaction alone.
|
||||
///
|
||||
/// Gas used is `None` if the the client is running in light client mode.
|
||||
#[serde(rename = "gasUsed")]
|
||||
pub gas_used: Option<U256>,
|
||||
/// Contract address created, or `None` if not a deployment.
|
||||
#[serde(rename = "contractAddress")]
|
||||
pub contract_address: Option<Address>,
|
||||
/// Logs generated within this transaction.
|
||||
pub logs: Vec<Log>,
|
||||
/// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
|
||||
pub status: Option<U64>,
|
||||
/// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
|
||||
pub root: Option<H256>,
|
||||
/// Logs bloom
|
||||
#[serde(rename = "logsBloom")]
|
||||
pub logs_bloom: Bloom,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
mod tests {
|
||||
|
@ -385,47 +220,4 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_transaction_response() {
|
||||
let _res: Transaction = serde_json::from_str(
|
||||
r#"{
|
||||
"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
|
||||
"blockNumber":"0x5daf3b",
|
||||
"from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
|
||||
"gas":"0xc350",
|
||||
"gasPrice":"0x4a817c800",
|
||||
"hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
|
||||
"input":"0x68656c6c6f21",
|
||||
"nonce":"0x15",
|
||||
"to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
|
||||
"transactionIndex":"0x41",
|
||||
"value":"0xf3dbb76162000",
|
||||
"v":"0x25",
|
||||
"r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
|
||||
"s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _res: Transaction = serde_json::from_str(
|
||||
r#"{
|
||||
"hash":"0xdd79ab0f996150aa3c9f135bbb9272cf0dedb830fafcbbf0c06020503565c44f",
|
||||
"nonce":"0xe",
|
||||
"blockHash":"0xef3fe1f532c3d8783a6257619bc123e9453aa8d6614e4cdb4cc8b9e1ed861404",
|
||||
"blockNumber":"0xf",
|
||||
"transactionIndex":"0x0",
|
||||
"from":"0x1b67b03cdccfae10a2d80e52d3d026dbe2960ad0",
|
||||
"to":"0x986ee0c8b91a58e490ee59718cca41056cf55f24",
|
||||
"value":"0x2710",
|
||||
"gas":"0x5208",
|
||||
"gasPrice":"0x186a0",
|
||||
"input":"0x",
|
||||
"v":"0x25",
|
||||
"r":"0x75188beb2f601bb8cf52ef89f92a6ba2bb7edcf8e3ccde90548cc99cbea30b1e",
|
||||
"s":"0xc0559a540f16d031f3404d5df2bb258084eee56ed1193d8b534bb6affdb3c2c"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
//! Transaction types
|
||||
use super::{eip2930::AccessList, rlp_opt, NUM_TX_FIELDS};
|
||||
use crate::{
|
||||
types::{Address, Bloom, Bytes, Log, H256, U256, U64},
|
||||
utils::keccak256,
|
||||
};
|
||||
use rlp::RlpStream;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Details of a signed transaction
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct Transaction {
|
||||
/// The transaction's hash
|
||||
pub hash: H256,
|
||||
|
||||
/// The transaction's nonce
|
||||
pub nonce: U256,
|
||||
|
||||
/// Block hash. None when pending.
|
||||
#[serde(rename = "blockHash")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub block_hash: Option<H256>,
|
||||
|
||||
/// Block number. None when pending.
|
||||
#[serde(rename = "blockNumber")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub block_number: Option<U64>,
|
||||
|
||||
/// Transaction Index. None when pending.
|
||||
#[serde(rename = "transactionIndex")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_index: Option<U64>,
|
||||
|
||||
/// Sender
|
||||
pub from: Address,
|
||||
|
||||
/// Recipient (None when contract creation)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub to: Option<Address>,
|
||||
|
||||
/// Transfered value
|
||||
pub value: U256,
|
||||
|
||||
/// Gas Price
|
||||
#[serde(rename = "gasPrice")]
|
||||
// TODO: Should this be deprecated?
|
||||
// https://twitter.com/TimBeiko/status/1413536062429794304
|
||||
// If yes, how will we handle pre-calculating the transaction's hash keccak256(rlp(tx)),
|
||||
// given that it contains the gas price?
|
||||
pub gas_price: U256,
|
||||
|
||||
/// Gas amount
|
||||
pub gas: U256,
|
||||
|
||||
/// Input data
|
||||
pub input: Bytes,
|
||||
|
||||
/// ECDSA recovery id
|
||||
pub v: U64,
|
||||
|
||||
/// ECDSA signature r
|
||||
pub r: U256,
|
||||
|
||||
/// ECDSA signature s
|
||||
pub s: U256,
|
||||
|
||||
///////////////// Celo-specific transaction fields /////////////////
|
||||
/// The currency fees are paid in (None for native currency)
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
|
||||
pub fee_currency: Option<Address>,
|
||||
|
||||
/// Gateway fee recipient (None for no gateway fee paid)
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
#[serde(
|
||||
skip_serializing_if = "Option::is_none",
|
||||
rename = "gatewayFeeRecipient"
|
||||
)]
|
||||
pub gateway_fee_recipient: Option<Address>,
|
||||
|
||||
/// Gateway fee amount (None for no gateway fee paid)
|
||||
#[cfg(feature = "celo")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
|
||||
pub gateway_fee: Option<U256>,
|
||||
|
||||
// EIP2718
|
||||
/// Transaction type, Some(1) for AccessList transaction, None for Legacy
|
||||
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_type: Option<U64>,
|
||||
|
||||
// EIP2930
|
||||
#[serde(
|
||||
rename = "accessList",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub access_list: Option<AccessList>,
|
||||
|
||||
#[serde(
|
||||
rename = "maxPriorityFeePerGas",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
/// Represents the maximum tx fee that will go to the miner as part of the user's
|
||||
/// fee payment. It serves 3 purposes:
|
||||
/// 1. Compensates miners for the uncle/ommer risk + fixed costs of including transaction in a block;
|
||||
/// 2. Allows users with high opportunity costs to pay a premium to miners;
|
||||
/// 3. In times where demand exceeds the available block space (i.e. 100% full, 30mm gas),
|
||||
/// this component allows first price auctions (i.e. the pre-1559 fee model) to happen on the priority fee.
|
||||
///
|
||||
/// More context [here](https://hackmd.io/@q8X_WM2nTfu6nuvAzqXiTQ/1559-wallets)
|
||||
pub max_priority_fee_per_gas: Option<U256>,
|
||||
|
||||
#[serde(
|
||||
rename = "maxFeePerGas",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
/// Represents the maximum amount that a user is willing to pay for their tx (inclusive of baseFeePerGas and maxPriorityFeePerGas).
|
||||
/// The difference between maxFeePerGas and baseFeePerGas + maxPriorityFeePerGas is “refunded” to the user.
|
||||
pub max_fee_per_gas: Option<U256>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
// modifies the RLP stream with the Celo-specific information
|
||||
// This is duplicated from TransactionRequest. Is there a good way to get rid
|
||||
// of this code duplication?
|
||||
#[cfg(feature = "celo")]
|
||||
fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
|
||||
rlp_opt(rlp, self.fee_currency);
|
||||
rlp_opt(rlp, self.gateway_fee_recipient);
|
||||
rlp_opt(rlp, self.gateway_fee);
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> H256 {
|
||||
keccak256(&self.rlp().as_ref()).into()
|
||||
}
|
||||
|
||||
pub fn rlp(&self) -> Bytes {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.begin_list(NUM_TX_FIELDS);
|
||||
rlp.append(&self.nonce);
|
||||
rlp.append(&self.gas_price);
|
||||
rlp.append(&self.gas);
|
||||
|
||||
#[cfg(feature = "celo")]
|
||||
self.inject_celo_metadata(&mut rlp);
|
||||
|
||||
rlp_opt(&mut rlp, self.to);
|
||||
rlp.append(&self.value);
|
||||
rlp.append(&self.input.as_ref());
|
||||
rlp.append(&self.v);
|
||||
rlp.append(&self.r);
|
||||
rlp.append(&self.s);
|
||||
|
||||
rlp.out().freeze().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// "Receipt" of an executed transaction: details of its execution.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TransactionReceipt {
|
||||
/// Transaction hash.
|
||||
#[serde(rename = "transactionHash")]
|
||||
pub transaction_hash: H256,
|
||||
/// Index within the block.
|
||||
#[serde(rename = "transactionIndex")]
|
||||
pub transaction_index: U64,
|
||||
/// Hash of the block this transaction was included within.
|
||||
#[serde(rename = "blockHash")]
|
||||
pub block_hash: Option<H256>,
|
||||
/// Number of the block this transaction was included within.
|
||||
#[serde(rename = "blockNumber")]
|
||||
pub block_number: Option<U64>,
|
||||
/// Cumulative gas used within the block after this was executed.
|
||||
#[serde(rename = "cumulativeGasUsed")]
|
||||
pub cumulative_gas_used: U256,
|
||||
/// Gas used by this transaction alone.
|
||||
///
|
||||
/// Gas used is `None` if the the client is running in light client mode.
|
||||
#[serde(rename = "gasUsed")]
|
||||
pub gas_used: Option<U256>,
|
||||
/// Contract address created, or `None` if not a deployment.
|
||||
#[serde(rename = "contractAddress")]
|
||||
pub contract_address: Option<Address>,
|
||||
/// Logs generated within this transaction.
|
||||
pub logs: Vec<Log>,
|
||||
/// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
|
||||
pub status: Option<U64>,
|
||||
/// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
|
||||
pub root: Option<H256>,
|
||||
/// Logs bloom
|
||||
#[serde(rename = "logsBloom")]
|
||||
pub logs_bloom: Bloom,
|
||||
/// Transaction type, Some(1) for AccessList transaction, None for Legacy
|
||||
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_type: Option<U64>,
|
||||
/// The price paid post-execution by the transaction (i.e. base fee + priority fee).
|
||||
/// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the amount
|
||||
/// that's actually paid by users can only be determined post-execution
|
||||
#[serde(
|
||||
rename = "effectiveGasPrice",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub effective_gas_price: Option<U256>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
mod tests {
|
||||
use crate::types::transaction::eip2930::AccessListItem;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn decode_transaction_response() {
|
||||
let _res: Transaction = serde_json::from_str(
|
||||
r#"{
|
||||
"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
|
||||
"blockNumber":"0x5daf3b",
|
||||
"from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
|
||||
"gas":"0xc350",
|
||||
"gasPrice":"0x4a817c800",
|
||||
"hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
|
||||
"input":"0x68656c6c6f21",
|
||||
"nonce":"0x15",
|
||||
"to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
|
||||
"transactionIndex":"0x41",
|
||||
"value":"0xf3dbb76162000",
|
||||
"v":"0x25",
|
||||
"r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
|
||||
"s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _res: Transaction = serde_json::from_str(
|
||||
r#"{
|
||||
"hash":"0xdd79ab0f996150aa3c9f135bbb9272cf0dedb830fafcbbf0c06020503565c44f",
|
||||
"nonce":"0xe",
|
||||
"blockHash":"0xef3fe1f532c3d8783a6257619bc123e9453aa8d6614e4cdb4cc8b9e1ed861404",
|
||||
"blockNumber":"0xf",
|
||||
"transactionIndex":"0x0",
|
||||
"from":"0x1b67b03cdccfae10a2d80e52d3d026dbe2960ad0",
|
||||
"to":"0x986ee0c8b91a58e490ee59718cca41056cf55f24",
|
||||
"value":"0x2710",
|
||||
"gas":"0x5208",
|
||||
"gasPrice":"0x186a0",
|
||||
"input":"0x",
|
||||
"v":"0x25",
|
||||
"r":"0x75188beb2f601bb8cf52ef89f92a6ba2bb7edcf8e3ccde90548cc99cbea30b1e",
|
||||
"s":"0xc0559a540f16d031f3404d5df2bb258084eee56ed1193d8b534bb6affdb3c2c"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_london_receipt() {
|
||||
let receipt: TransactionReceipt = serde_json::from_value(serde_json::json!({"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","contractAddress":null,"cumulativeGasUsed":"0x207a5b","effectiveGasPrice":"0x3b9aca07","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gasUsed":"0x6a40","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionHash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","transactionIndex":"0xd","type":"0x2"})).unwrap();
|
||||
assert_eq!(receipt.transaction_type.unwrap().as_u64(), 2);
|
||||
assert_eq!(receipt.effective_gas_price.unwrap().as_u64(), 0x3b9aca07);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_london_tx() {
|
||||
let tx: Transaction = serde_json::from_value(serde_json::json!({"accessList":[{"address":"0x8ba1f109551bd432803012645ac136ddd64dba72","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000042"]}],"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","chainId":"0x3","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gas":"0x6a40","gasPrice":"0x3b9aca07","hash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","input":"0x","maxFeePerGas":"0x3b9aca0e","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x2","r":"0xf13b5088108f783f4b6048d4be456971118aabfb88be96bb541d734b6c2b20dc","s":"0x13fb7eb25a7d5df42a176cd4c6a086e19163ed7cd8ffba015f939d24f66bc17a","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionIndex":"0xd","type":"0x2","v":"0x1","value":"0x7b"})).unwrap();
|
||||
assert_eq!(tx.transaction_type.unwrap().as_u64(), 2);
|
||||
let lst = AccessList(vec![AccessListItem {
|
||||
address: "0x8ba1f109551bd432803012645ac136ddd64dba72"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
storage_keys: vec![
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000042"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
],
|
||||
}]);
|
||||
assert_eq!(tx.access_list.unwrap(), lst);
|
||||
assert_eq!(tx.max_fee_per_gas.unwrap().as_u64(), 0x3b9aca0e);
|
||||
assert_eq!(tx.max_priority_fee_per_gas.unwrap().as_u64(), 0x3b9aca00);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,4 @@
|
|||
use ethers_core::{
|
||||
types::{
|
||||
Address, BlockId, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, U256,
|
||||
},
|
||||
utils::keccak256,
|
||||
};
|
||||
use ethers_core::types::{Address, BlockId, Bytes, NameOrAddress, Signature, TransactionRequest};
|
||||
use ethers_providers::{FromErr, Middleware, PendingTransaction};
|
||||
use ethers_signers::Signer;
|
||||
|
||||
|
@ -120,64 +115,19 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Signs and returns the RLP encoding of the signed transaction
|
||||
async fn sign_transaction(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
) -> Result<Transaction, SignerMiddlewareError<M, S>> {
|
||||
// The nonce, gas and gasprice fields must already be populated
|
||||
let nonce = tx.nonce.ok_or(SignerMiddlewareError::NonceMissing)?;
|
||||
let gas_price = tx.gas_price.ok_or(SignerMiddlewareError::GasPriceMissing)?;
|
||||
let gas = tx.gas.ok_or(SignerMiddlewareError::GasMissing)?;
|
||||
|
||||
// Can't sign a transaction from a different address
|
||||
if tx.from.is_some() && tx.from != Some(self.address()) {
|
||||
return Err(SignerMiddlewareError::WrongSigner);
|
||||
}
|
||||
|
||||
) -> Result<Bytes, SignerMiddlewareError<M, S>> {
|
||||
let signature = self
|
||||
.signer
|
||||
.sign_transaction(&tx)
|
||||
.await
|
||||
.map_err(SignerMiddlewareError::SignerError)?;
|
||||
|
||||
// Get the actual transaction hash
|
||||
let rlp = tx.rlp_signed(&signature);
|
||||
let hash = keccak256(&rlp.as_ref());
|
||||
|
||||
// This function should not be called with ENS names
|
||||
let to = tx.to.map(|to| match to {
|
||||
NameOrAddress::Address(inner) => inner,
|
||||
NameOrAddress::Name(_) => {
|
||||
panic!("Expected `to` to be an Ethereum Address, not an ENS name")
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Transaction {
|
||||
hash: hash.into(),
|
||||
nonce,
|
||||
from: self.address(),
|
||||
to,
|
||||
value: tx.value.unwrap_or_default(),
|
||||
gas_price,
|
||||
gas,
|
||||
input: tx.data.unwrap_or_default(),
|
||||
v: signature.v.into(),
|
||||
r: U256::from_big_endian(signature.r.as_bytes()),
|
||||
s: U256::from_big_endian(signature.s.as_bytes()),
|
||||
|
||||
// Leave these empty as they're only used for included transactions
|
||||
block_hash: None,
|
||||
block_number: None,
|
||||
transaction_index: None,
|
||||
|
||||
// Celo support
|
||||
#[cfg(feature = "celo")]
|
||||
fee_currency: tx.fee_currency,
|
||||
#[cfg(feature = "celo")]
|
||||
gateway_fee: tx.gateway_fee,
|
||||
#[cfg(feature = "celo")]
|
||||
gateway_fee_recipient: tx.gateway_fee_recipient,
|
||||
})
|
||||
// Return the raw rlp-encoded signed transaction
|
||||
Ok(tx.rlp_signed(&signature))
|
||||
}
|
||||
|
||||
async fn fill_transaction(
|
||||
|
@ -282,7 +232,7 @@ where
|
|||
|
||||
// Submit the raw transaction
|
||||
self.inner
|
||||
.send_raw_transaction(&signed_tx)
|
||||
.send_raw_transaction(signed_tx)
|
||||
.await
|
||||
.map_err(SignerMiddlewareError::MiddlewareError)
|
||||
}
|
||||
|
@ -317,6 +267,7 @@ where
|
|||
mod tests {
|
||||
use super::*;
|
||||
use ethers::{providers::Provider, signers::LocalWallet};
|
||||
use ethers_core::utils::{self, keccak256, Ganache};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -349,51 +300,60 @@ mod tests {
|
|||
let tx = client.sign_transaction(tx).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tx.hash,
|
||||
"de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593"
|
||||
.parse()
|
||||
keccak256(&tx)[..],
|
||||
hex::decode("de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593")
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let expected_rlp = Bytes::from(hex::decode("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").unwrap());
|
||||
assert_eq!(tx.rlp(), expected_rlp);
|
||||
assert_eq!(tx, expected_rlp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handles_tx_from_field() {
|
||||
use ethers_core::types::Address;
|
||||
|
||||
// new SignerMiddleware
|
||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
||||
let ganache = Ganache::new().spawn();
|
||||
let acc = ganache.addresses()[0];
|
||||
let provider = Provider::try_from(ganache.endpoint()).unwrap();
|
||||
let key = LocalWallet::new(&mut rand::thread_rng()).with_chain_id(1u32);
|
||||
provider
|
||||
.send_transaction(
|
||||
TransactionRequest::pay(key.address(), utils::parse_ether(1u64).unwrap()).from(acc),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let client = SignerMiddleware::new(provider, key);
|
||||
|
||||
// an address that is not the signer address
|
||||
let other = "0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4"
|
||||
.parse::<Address>()
|
||||
.unwrap();
|
||||
|
||||
let request = TransactionRequest::new().nonce(0).gas_price(0).gas(0);
|
||||
let request = TransactionRequest::new();
|
||||
|
||||
// signing a TransactionRequest with a from field of None should yield
|
||||
// a signed transaction from the signer address
|
||||
let request_from_none = request.clone();
|
||||
let signing_result = client.sign_transaction(request_from_none).await;
|
||||
|
||||
assert_eq!(signing_result.unwrap().from, client.address());
|
||||
let hash = *client
|
||||
.send_transaction(request_from_none, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let tx = client.get_transaction(hash).await.unwrap().unwrap();
|
||||
assert_eq!(tx.from, client.address());
|
||||
|
||||
// signing a TransactionRequest with the signer as the from address
|
||||
// should yield a signed transaction from the signer
|
||||
let request_from_signer = request.clone().from(client.address());
|
||||
let signing_result = client.sign_transaction(request_from_signer.clone()).await;
|
||||
|
||||
assert_eq!(signing_result.unwrap().from, client.address());
|
||||
let hash = *client
|
||||
.send_transaction(request_from_signer, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let tx = client.get_transaction(hash).await.unwrap().unwrap();
|
||||
assert_eq!(tx.from, client.address());
|
||||
|
||||
// signing a TransactionRequest with a from address that is not the
|
||||
// signer should result in a WrongSigner error
|
||||
let request_from_other = request.from(other);
|
||||
let signing_result = client.sign_transaction(request_from_other.clone()).await;
|
||||
|
||||
assert!(signing_result.is_err());
|
||||
// signer should result in the default ganache account being used
|
||||
let request_from_other = request.from(acc);
|
||||
let hash = *client
|
||||
.send_transaction(request_from_other, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let tx = client.get_transaction(hash).await.unwrap().unwrap();
|
||||
assert_eq!(tx.from, acc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -313,7 +313,7 @@ pub trait Middleware: Sync + Send + Debug {
|
|||
|
||||
async fn send_raw_transaction<'a>(
|
||||
&'a self,
|
||||
tx: &Transaction,
|
||||
tx: Bytes,
|
||||
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
|
||||
self.inner()
|
||||
.send_raw_transaction(tx)
|
||||
|
|
|
@ -354,9 +354,9 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
/// This will consume gas from the account that signed the transaction.
|
||||
async fn send_raw_transaction<'a>(
|
||||
&'a self,
|
||||
tx: &Transaction,
|
||||
tx: Bytes,
|
||||
) -> Result<PendingTransaction<'a, P>, ProviderError> {
|
||||
let rlp = utils::serialize(&tx.rlp());
|
||||
let rlp = utils::serialize(&tx);
|
||||
let tx_hash = self.request("eth_sendRawTransaction", [rlp]).await?;
|
||||
Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue