avalanche-sdk 0.73.1

Avalanche API/SDK
Documentation
use std::io::{self, Error, ErrorKind};

use avalanche_types::key;
use ethers::prelude::*;
use tokio::time::Duration;

pub const DEFAULT_GAS: u64 = 21000;

#[derive(Clone, Debug)]
pub struct Tx<T>
where
    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
    pub inner: crate::wallet::evm::c::C<T>,

    /// Transfer fund receiver address.
    pub receiver: H160,
    /// Transfer amount.
    pub amount: U256,
    /// Gas.
    /// "Max priority fee (GWEI)" in Metamask maps to "GasTipCap".
    /// "Max fee(GWEI)" in Metamask maps to "GasPrice".
    pub gas: U256,
    /// Gas price.
    /// "Max priority fee (GWEI)" in Metamask maps to "GasTipCap".
    /// "Max fee(GWEI)" in Metamask maps to "GasPrice".
    pub gas_price: U256,

    /// Set "true" to poll transfer status after issuance for its acceptance.
    pub check_acceptance: bool,

    /// Initial wait duration before polling for acceptance.
    pub poll_initial_wait: Duration,
    /// Wait between each poll intervals for acceptance.
    pub poll_interval: Duration,
    /// Maximum duration for polling.
    pub poll_timeout: Duration,
}

impl<T> Tx<T>
where
    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
    pub fn new(ev: &crate::wallet::evm::c::C<T>) -> Self {
        Self {
            inner: ev.clone(),
            receiver: H160::zero(),
            amount: U256::from(0),
            gas: U256::from(DEFAULT_GAS),
            gas_price: U256::from(0),
            check_acceptance: false,
            poll_initial_wait: Duration::from_millis(500),
            poll_interval: Duration::from_millis(700),
            poll_timeout: Duration::from_secs(300),
        }
    }

    /// Sets the transfer fund receiver address.
    #[must_use]
    pub fn receiver(mut self, receiver: H160) -> Self {
        self.receiver = receiver;
        self
    }

    /// Sets the transfer amount.
    #[must_use]
    pub fn amount(mut self, amount: U256) -> Self {
        self.amount = amount;
        self
    }

    #[must_use]
    pub fn gas(mut self, gas: U256) -> Self {
        self.gas = gas;
        self
    }

    #[must_use]
    pub fn gas_u64(mut self, gas: u64) -> Self {
        self.gas = U256::from(gas);
        self
    }

    #[must_use]
    pub fn gas_price(mut self, gas_price: U256) -> Self {
        self.gas_price = gas_price;
        self
    }

    #[must_use]
    pub fn gas_price_u64(mut self, gas_price: u64) -> Self {
        self.gas_price = U256::from(gas_price);
        self
    }

    /// Sets the check acceptance boolean flag.
    #[must_use]
    pub fn check_acceptance(mut self, check_acceptance: bool) -> Self {
        self.check_acceptance = check_acceptance;
        self
    }

    /// Sets the initial poll wait time.
    #[must_use]
    pub fn poll_initial_wait(mut self, poll_initial_wait: Duration) -> Self {
        self.poll_initial_wait = poll_initial_wait;
        self
    }

    /// Sets the poll wait time between intervals.
    #[must_use]
    pub fn poll_interval(mut self, poll_interval: Duration) -> Self {
        self.poll_interval = poll_interval;
        self
    }

    /// Sets the poll timeout.
    #[must_use]
    pub fn poll_timeout(mut self, poll_timeout: Duration) -> Self {
        self.poll_timeout = poll_timeout;
        self
    }

    /// Issues the transfer transaction and returns the transaction Id.
    pub async fn issue(&self) -> io::Result<H256> {
        let picked_http_rpc = self.inner.inner.pick_http_rpc();
        log::info!(
            "transferring C-chain {} AVAX from {} to {} via {}",
            self.amount,
            self.inner.inner.h160_address,
            self.receiver,
            picked_http_rpc.1
        );

        let latest_nonce = self.inner.inner.c_providers[picked_http_rpc.0]
            .get_transaction_count(
                self.inner.inner.h160_address,
                Some(BlockNumber::Latest.into()),
            )
            .await
            .map_err(|e| {
                Error::new(
                    ErrorKind::Other,
                    format!("failed get_transaction_count '{}'", e),
                )
            })?;
        log::debug!("latest nonce for sender: {}", latest_nonce);

        let mut tx_request = TransactionRequest::new()
            .from(self.inner.inner.h160_address)
            .to(self.receiver)
            .value(self.amount)
            .chain_id(self.inner.inner.c_chain_id_u256.as_u64())
            .nonce(latest_nonce);
        if !self.gas.is_zero() {
            tx_request = tx_request.gas(self.gas)
        }
        if !self.gas_price.is_zero() {
            // set this to avoid
            // "transaction underpriced: address 0xabc have gas tip cap (0) < pool gas tip cap (225000000000)"
            tx_request = tx_request.gas_price(self.gas_price)
        }

        let signer = SignerMiddleware::new(
            self.inner.inner.c_providers[picked_http_rpc.0].clone(),
            self.inner
                .inner
                .local_wallet
                .clone()
                .with_chain_id(self.inner.inner.c_chain_id_u256.as_u64()),
        );

        let pending_tx = signer
            .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 receipt = tx_receipt.unwrap();

        let tx = signer
            .get_transaction(receipt.transaction_hash)
            .await
            .map_err(|e| Error::new(ErrorKind::Other, format!("failed get_transaction '{}'", e)))?;

        // serde_json::to_string(&tx).unwrap()
        if let Some(v) = &tx {
            log::info!("{} successfully issued", v.hash());
        } else {
            log::warn!("transaction not found in get_transaction");
        }

        if !self.check_acceptance {
            log::debug!("skipping checking acceptance...");
            return Ok(receipt.transaction_hash);
        }

        Ok(receipt.transaction_hash)
    }
}