avalanche-sdk 0.60.0

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

use avalanche_types::key;
use ethers::prelude::*;

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::subnet_evm::SubnetEvm<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,
}

impl<T> Tx<T>
where
    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
    pub fn new(ev: &crate::wallet::evm::subnet_evm::SubnetEvm<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,
        }
    }

    /// 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
    }

    /// Issues the transfer transaction and returns the transaction Id.
    pub async fn issue(&self) -> io::Result<H256> {
        let subnet_evm_providers = self.inner.inner.subnet_evm_providers.clone().unwrap();
        let subnet_evm_chain_id_u256 = self.inner.inner.subnet_evm_chain_id_u256.unwrap();

        let picked_http_rpc = self.inner.inner.pick_http_rpc();
        log::info!(
            "transferring subnet-evm {} native token from {} to {} via {}",
            self.amount,
            self.inner.inner.h160_address,
            self.receiver,
            picked_http_rpc.1
        );

        let latest_nonce = subnet_evm_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(subnet_evm_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(
            subnet_evm_providers[picked_http_rpc.0].clone(),
            self.inner
                .inner
                .local_wallet
                .clone()
                .with_chain_id(subnet_evm_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)
    }
}