use builder pattern for txs
This commit is contained in:
parent
f8ca81ab22
commit
96add97869
|
@ -1,6 +1,5 @@
|
|||
use ethers::{types::TransactionRequest, HttpProvider, MainnetWallet};
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), failure::Error> {
|
||||
|
@ -8,28 +7,17 @@ async fn main() -> Result<(), failure::Error> {
|
|||
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
||||
|
||||
// create a wallet and connect it to the provider
|
||||
let client = MainnetWallet::from_str(
|
||||
"15c42bf2987d5a8a73804a8ea72fb4149f88adf73e98fc3f8a8ce9f24fcb7774",
|
||||
)?
|
||||
let client = "15c42bf2987d5a8a73804a8ea72fb4149f88adf73e98fc3f8a8ce9f24fcb7774"
|
||||
.parse::<MainnetWallet>()?
|
||||
.connect(&provider);
|
||||
|
||||
// get the account's nonce (we abuse the Deref to access the provider's functions)
|
||||
let nonce = client.get_transaction_count(client.address(), None).await?;
|
||||
dbg!(nonce);
|
||||
|
||||
// craft the transaction
|
||||
let tx = TransactionRequest {
|
||||
from: None,
|
||||
to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()),
|
||||
gas: Some(21000.into()),
|
||||
gas_price: Some(100_000.into()),
|
||||
value: Some(10000.into()),
|
||||
data: Some(vec![].into()),
|
||||
nonce: Some(nonce),
|
||||
};
|
||||
let tx = TransactionRequest::new()
|
||||
.send_to_str("986eE0C8B91A58e490Ee59718Cca41056Cf55f24")?
|
||||
.value(10000);
|
||||
|
||||
// send it!
|
||||
let tx = client.sign_and_send_transaction(tx).await?;
|
||||
let tx = client.sign_and_send_transaction(tx, None).await?;
|
||||
|
||||
// get the mined tx
|
||||
let tx = client.get_transaction(tx.hash).await?;
|
||||
|
|
|
@ -12,15 +12,10 @@ async fn main() -> Result<(), failure::Error> {
|
|||
let from = accounts[0];
|
||||
|
||||
// craft the tx
|
||||
let tx = TransactionRequest {
|
||||
from: Some(from),
|
||||
to: Some("9A7e5d4bcA656182e66e33340d776D1542143006".parse()?),
|
||||
value: Some(1000u64.into()),
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
data: None,
|
||||
nonce: None,
|
||||
};
|
||||
let tx = TransactionRequest::new()
|
||||
.send_to_str("9A7e5d4bcA656182e66e33340d776D1542143006")?
|
||||
.value(1000)
|
||||
.from(from); // specify the `from` field so that the client knows which account to use
|
||||
|
||||
let balance_before = provider.get_balance(from, None).await?;
|
||||
|
||||
|
|
|
@ -46,6 +46,29 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
}
|
||||
}
|
||||
|
||||
// Cost related
|
||||
|
||||
/// Gets the current gas price as estimated by the node
|
||||
pub async fn get_gas_price(&self) -> Result<U256, P::Error> {
|
||||
self.0.request("eth_gasPrice", None::<()>).await
|
||||
}
|
||||
|
||||
/// Tries to estimate the gas for the transaction
|
||||
pub async fn estimate_gas(
|
||||
&self,
|
||||
tx: &TransactionRequest,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<U256, P::Error> {
|
||||
let tx = utils::serialize(tx);
|
||||
|
||||
let args = match block {
|
||||
Some(block) => vec![tx, utils::serialize(&block)],
|
||||
None => vec![tx],
|
||||
};
|
||||
|
||||
self.0.request("eth_estimateGas", Some(args)).await
|
||||
}
|
||||
|
||||
/// Gets the accounts on the node
|
||||
pub async fn get_accounts(&self) -> Result<Vec<Address>, P::Error> {
|
||||
self.0.request("eth_accounts", None::<()>).await
|
||||
|
@ -65,6 +88,8 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
self.0.request("eth_getTransactionByHash", Some(hash)).await
|
||||
}
|
||||
|
||||
// State mutations
|
||||
|
||||
/// Broadcasts the transaction request via the `eth_sendTransaction` API
|
||||
pub async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
||||
self.0.request("eth_sendTransaction", Some(tx)).await
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
providers::{JsonRpcClient, Provider},
|
||||
signers::Signer,
|
||||
types::{Address, Transaction, TransactionRequest},
|
||||
types::{Address, BlockNumber, Transaction, TransactionRequest},
|
||||
};
|
||||
|
||||
use std::ops::Deref;
|
||||
|
@ -17,8 +17,30 @@ impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> {
|
|||
/// API
|
||||
pub async fn sign_and_send_transaction(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
mut tx: TransactionRequest,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<Transaction, P::Error> {
|
||||
// TODO: Convert to join'ed futures
|
||||
// get the gas price
|
||||
if tx.gas_price.is_none() {
|
||||
tx.gas_price = Some(self.provider.get_gas_price().await?);
|
||||
}
|
||||
|
||||
// estimate the gas
|
||||
if tx.gas.is_none() {
|
||||
tx.from = Some(self.address());
|
||||
tx.gas = Some(self.provider.estimate_gas(&tx, block).await?);
|
||||
}
|
||||
|
||||
// set our nonce
|
||||
if tx.nonce.is_none() {
|
||||
tx.nonce = Some(
|
||||
self.provider
|
||||
.get_transaction_count(self.address(), block)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
// sign the transaction
|
||||
let signed_tx = self.signer.sign_transaction(tx).unwrap(); // TODO
|
||||
|
||||
|
|
|
@ -59,13 +59,18 @@ impl PrivateKey {
|
|||
self.sign_with_eip155(&sig_message, None)
|
||||
}
|
||||
|
||||
/// RLP encodes and then signs the stransaction. If no chain_id is provided, then EIP-155 is
|
||||
/// not used.
|
||||
/// RLP encodes and then signs the stransaction.
|
||||
///
|
||||
/// If no chain_id is provided, then EIP-155 is not used.
|
||||
///
|
||||
/// This will return an error if called if any of the `nonce`, `gas_price` or `gas`
|
||||
/// fields are not populated.
|
||||
pub fn sign_transaction(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
chain_id: Option<U64>,
|
||||
) -> Result<Transaction, TxError> {
|
||||
// Calling `
|
||||
let nonce = tx.nonce.ok_or(TxError::NonceMissing)?;
|
||||
let gas_price = tx.gas_price.ok_or(TxError::NonceMissing)?;
|
||||
let gas = tx.gas.ok_or(TxError::NonceMissing)?;
|
||||
|
|
|
@ -5,9 +5,10 @@ use crate::{
|
|||
};
|
||||
use rlp::RlpStream;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Parameters for sending a transaction
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct TransactionRequest {
|
||||
/// Sender address or ENS name
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -41,27 +42,89 @@ pub struct TransactionRequest {
|
|||
}
|
||||
|
||||
impl TransactionRequest {
|
||||
fn rlp_base(&self, rlp: &mut RlpStream) {
|
||||
rlp_opt(rlp, self.nonce);
|
||||
rlp_opt(rlp, self.gas_price);
|
||||
rlp_opt(rlp, self.gas);
|
||||
rlp_opt(rlp, self.to);
|
||||
rlp_opt(rlp, self.value);
|
||||
rlp_opt(rlp, self.data.as_ref().map(|d| &d.0[..]));
|
||||
/// Creates an empty transaction request with all fields left empty
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn hash(&self, chain_id: Option<U64>) -> H256 {
|
||||
/// Convenience function for sending a new payment transaction to the receiver. The
|
||||
/// `gas`, `gas_price` and `nonce` fields are left empty, to be populated
|
||||
pub fn pay<T: Into<Address>, V: Into<U256>>(to: T, value: V) -> Self {
|
||||
TransactionRequest {
|
||||
from: None,
|
||||
to: Some(to.into()),
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
value: Some(value.into()),
|
||||
data: None,
|
||||
nonce: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Builder pattern helpers
|
||||
|
||||
/// Sets the `from` field in the transaction to the provided value
|
||||
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
||||
self.from = Some(from.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `to` field in the transaction to the provided value
|
||||
pub fn send_to_str(mut self, to: &str) -> Result<Self, rustc_hex::FromHexError> {
|
||||
self.to = Some(Address::from_str(to)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets the `to` field in the transaction to the provided value
|
||||
pub fn to<T: Into<Address>>(mut self, to: T) -> Self {
|
||||
self.to = Some(to.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `gas` field in the transaction to the provided value
|
||||
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
||||
self.gas = Some(gas.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `gas_price` field in the transaction to the provided value
|
||||
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
||||
self.gas_price = Some(gas_price.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `value` field in the transaction to the provided value
|
||||
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
||||
self.value = Some(value.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `data` field in the transaction to the provided value
|
||||
pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
|
||||
self.data = Some(data.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `nonce` field in the transaction to the provided value
|
||||
pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
|
||||
self.nonce = Some(nonce.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Hashes the transaction's data with the provided chain id
|
||||
pub fn hash<T: Into<U64>>(&self, chain_id: Option<T>) -> H256 {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.begin_list(9);
|
||||
self.rlp_base(&mut rlp);
|
||||
|
||||
rlp.append(&chain_id.unwrap_or_else(U64::zero));
|
||||
rlp.append(&chain_id.map(|c| c.into()).unwrap_or_else(U64::zero));
|
||||
rlp.append(&0u8);
|
||||
rlp.append(&0u8);
|
||||
|
||||
keccak256(rlp.out().as_ref()).into()
|
||||
}
|
||||
|
||||
/// Produces the RLP encoding of the transaction with the provided signature
|
||||
pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.begin_list(9);
|
||||
|
@ -73,6 +136,15 @@ impl TransactionRequest {
|
|||
|
||||
rlp.out().into()
|
||||
}
|
||||
|
||||
fn rlp_base(&self, rlp: &mut RlpStream) {
|
||||
rlp_opt(rlp, self.nonce);
|
||||
rlp_opt(rlp, self.gas_price);
|
||||
rlp_opt(rlp, self.gas);
|
||||
rlp_opt(rlp, self.to);
|
||||
rlp_opt(rlp, self.value);
|
||||
rlp_opt(rlp, self.data.as_ref().map(|d| &d.0[..]));
|
||||
}
|
||||
}
|
||||
|
||||
fn rlp_opt<T: rlp::Encodable>(rlp: &mut RlpStream, opt: Option<T>) {
|
||||
|
|
Loading…
Reference in New Issue