use std::{
io::{self, Error, ErrorKind},
ops::Mul,
};
use crate::{
key,
wallet::{self, evm},
};
use ethers::{prelude::Eip1559TransactionRequest, utils::Units::Gwei};
use ethers_providers::Middleware;
use lazy_static::lazy_static;
use primitive_types::{H160, H256, U256};
use tokio::time::Duration;
lazy_static! {
pub static ref URGENT_MAX_FEE_PER_GAS: U256 = {
let gwei = U256::from(10).checked_pow(Gwei.as_num().into()).unwrap();
U256::from(700).mul(gwei) };
pub static ref URGENT_MAX_PRIORITY_FEE_PER_GAS: U256 = {
let gwei = U256::from(10).checked_pow(Gwei.as_num().into()).unwrap();
U256::from(10).mul(gwei) };
}
impl<'a, T, S> evm::Evm<'a, T, S>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
S: ethers_signers::Signer + Clone,
S::Error: 'static,
{
#[must_use]
pub fn eip1559(&self) -> Tx<'a, T, S> {
Tx::new(self)
}
}
#[derive(Clone, Debug)]
pub struct Tx<'a, T, S>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
S: ethers_signers::Signer + Clone,
S::Error: 'static,
{
pub inner: wallet::evm::Evm<'a, T, S>,
pub signer_nonce: Option<U256>,
pub max_priority_fee_per_gas: Option<U256>,
pub max_fee_per_gas: Option<U256>,
pub gas_limit: Option<U256>,
pub recipient: Option<H160>,
pub value: Option<U256>,
pub data: Option<Vec<u8>>,
pub check_acceptance: bool,
pub poll_initial_wait: Duration,
pub poll_interval: Duration,
pub poll_timeout: Duration,
pub dry_mode: bool,
}
impl<'a, T, S> Tx<'a, T, S>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
S: ethers_signers::Signer + Clone,
S::Error: 'static,
{
pub fn new(ev: &wallet::evm::Evm<'a, T, S>) -> Self {
Self {
inner: ev.clone(),
signer_nonce: None,
max_priority_fee_per_gas: None,
max_fee_per_gas: None,
gas_limit: None,
recipient: None,
value: None,
data: None,
check_acceptance: false,
poll_initial_wait: Duration::from_millis(500),
poll_interval: Duration::from_millis(700),
poll_timeout: Duration::from_secs(300),
dry_mode: false,
}
}
#[must_use]
pub fn signer_nonce(mut self, signer_nonce: impl Into<U256>) -> Self {
self.signer_nonce = Some(signer_nonce.into());
self
}
#[must_use]
pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: impl Into<U256>) -> Self {
self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas.into());
self
}
#[must_use]
pub fn max_fee_per_gas(mut self, max_fee_per_gas: impl Into<U256>) -> Self {
self.max_fee_per_gas = Some(max_fee_per_gas.into());
self
}
#[must_use]
pub fn gas_limit(mut self, gas_limit: impl Into<U256>) -> Self {
self.gas_limit = Some(gas_limit.into());
self
}
#[must_use]
pub fn urgent(mut self) -> Self {
self.max_priority_fee_per_gas = Some(*URGENT_MAX_PRIORITY_FEE_PER_GAS);
self.max_fee_per_gas = Some(*URGENT_MAX_FEE_PER_GAS);
self
}
#[must_use]
pub fn recipient(mut self, to: impl Into<H160>) -> Self {
self.recipient = Some(to.into());
self
}
#[must_use]
pub fn value(mut self, value: impl Into<U256>) -> Self {
self.value = Some(value.into());
self
}
#[must_use]
pub fn data(mut self, data: impl Into<Vec<u8>>) -> Self {
self.data = Some(data.into());
self
}
#[must_use]
pub fn check_acceptance(mut self, check_acceptance: bool) -> Self {
self.check_acceptance = check_acceptance;
self
}
#[must_use]
pub fn poll_initial_wait(mut self, poll_initial_wait: Duration) -> Self {
self.poll_initial_wait = poll_initial_wait;
self
}
#[must_use]
pub fn poll_interval(mut self, poll_interval: Duration) -> Self {
self.poll_interval = poll_interval;
self
}
#[must_use]
pub fn poll_timeout(mut self, poll_timeout: Duration) -> Self {
self.poll_timeout = poll_timeout;
self
}
#[must_use]
pub fn dry_mode(mut self, dry_mode: bool) -> Self {
self.dry_mode = dry_mode;
self
}
pub async fn submit(&self) -> io::Result<H256> {
let max_priority_fee_per_gas = if let Some(v) = self.max_priority_fee_per_gas {
format!("{} GWEI", super::wei_to_gwei(v))
} else {
"default".to_string()
};
let max_fee_per_gas = if let Some(v) = self.max_fee_per_gas {
format!("{} GWEI", super::wei_to_gwei(v))
} else {
"default".to_string()
};
log::info!(
"submitting transaction [chain Id {}, value {:?}, from {}, recipient {:?}, chain RPC URL {}, max_priority_fee_per_gas {max_priority_fee_per_gas}, max_fee_per_gas {max_fee_per_gas}, gas_limit {:?}]",
self.inner.chain_id,
self.value,
self.inner.inner.h160_address,
self.recipient,
self.inner.chain_rpc_url,
self.gas_limit,
);
let signer_nonce = if let Some(signer_nonce) = self.signer_nonce {
signer_nonce
} else {
log::info!("nonce not specified -- fetching latest");
self.inner
.middleware
.initialize_nonce(None)
.await
.map_err(|e| {
Error::new(ErrorKind::Other, format!("failed initialize_nonce '{}'", e))
})?
};
log::info!("latest signer nonce {}", signer_nonce);
let mut tx_request = Eip1559TransactionRequest::new()
.from(ethers::prelude::H160::from(
self.inner.inner.h160_address.as_fixed_bytes(),
))
.chain_id(ethers::prelude::U64::from(self.inner.chain_id.as_u64()))
.nonce(ethers::prelude::U256::from(signer_nonce.as_u128()));
if let Some(to) = &self.recipient {
tx_request = tx_request.to(ethers::prelude::H160::from(to.as_fixed_bytes()));
}
if let Some(value) = &self.value {
let converted: ethers::prelude::U256 = value.into();
tx_request = tx_request.value(converted);
}
if let Some(max_priority_fee_per_gas) = &self.max_priority_fee_per_gas {
let converted: ethers::prelude::U256 = max_priority_fee_per_gas.into();
tx_request = tx_request.max_priority_fee_per_gas(converted);
}
if let Some(max_fee_per_gas) = &self.max_fee_per_gas {
let converted: ethers::prelude::U256 = max_fee_per_gas.into();
tx_request = tx_request.max_fee_per_gas(converted);
}
if let Some(gas_limit) = &self.gas_limit {
let converted: ethers::prelude::U256 = gas_limit.into();
tx_request = tx_request.gas(converted);
}
if let Some(data) = &self.data {
tx_request = tx_request.data(data.clone());
}
let pending_tx = self
.inner
.middleware
.send_transaction(tx_request, None)
.await
.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to send_transaction '{}'", e),
)
})?;
let tx_receipt = pending_tx.await.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to wait for pending tx '{}'", e),
)
})?;
if tx_receipt.is_none() {
return Err(Error::new(ErrorKind::Other, "tx dropped from mempool"));
}
let tx_receipt = tx_receipt.unwrap();
let tx_hash = H256(tx_receipt.transaction_hash.0);
let tx = self
.inner
.middleware
.get_transaction(tx_receipt.transaction_hash)
.await
.map_err(|e| Error::new(ErrorKind::Other, format!("failed get_transaction '{}'", e)))?;
if let Some(inner) = &tx {
assert_eq!(inner.hash(), tx_receipt.transaction_hash);
log::info!("successfully issued transaction '0x{:x}'", inner.hash());
} else {
log::warn!("transaction not found in get_transaction");
}
if !self.check_acceptance {
log::debug!("skipping checking acceptance...");
return Ok(tx_hash);
}
Ok(tx_hash)
}
}