avalanche-sdk 0.58.0

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

use crate::p as api_p;
use avalanche_types::{
    avax, choices::status::Status, formatting, ids, key, platformvm, secp256k1fx,
};
use tokio::time::sleep;

/// Represents P-chain "CreateSubnet" transaction.
/// ref. https://github.com/ava-labs/avalanchego/blob/v1.9.0/wallet/chain/p/builder.go#L500-L525 "NewCreateSubnetTx"
/// ref. https://github.com/ava-labs/avalanchego/blob/v1.9.0/vms/platformvm/txs/builder/builder.go#L392 "NewCreateSubnetTx"
#[derive(Clone, Debug)]
pub struct Tx<T>
where
    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
    pub inner: crate::wallet::p::P<T>,

    /// Set "true" to poll transaction 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(p: &crate::wallet::p::P<T>) -> Self {
        Self {
            inner: p.clone(),
            check_acceptance: false,
        }
    }

    /// 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 add subnet validator transaction and returns the transaction Id.
    /// If the validator is already a validator, it returns an empty Id and false.
    pub async fn issue(&self) -> io::Result<(ids::Id, bool)> {
        let picked_http_rpc = self.inner.inner.pick_http_rpc();
        log::info!("creating a new subnet via {}", picked_http_rpc.1);

        let cur_balance_p = self.inner.balance().await?;
        if cur_balance_p < self.inner.inner.create_subnet_tx_fee {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                format!("key address {} (balance {} nano-AVAX, network {}) does not have enough to cover fee {}", self.inner.inner.p_address, cur_balance_p, self.inner.inner.network_name, self.inner.inner.create_subnet_tx_fee),
             ));
        };

        let (ins, unstaked_outs, _, signers) = self
            .inner
            .spend(0, self.inner.inner.create_subnet_tx_fee)
            .await?;

        let mut tx = platformvm::create_subnet::Tx {
            unsigned_tx: avax::BaseTx {
                network_id: self.inner.inner.network_id,
                blockchain_id: self.inner.inner.p_chain_id,
                transferable_outputs: Some(unstaked_outs),
                transferable_inputs: Some(ins),
                ..Default::default()
            },
            owner: secp256k1fx::OutputOwners {
                locktime: 0,
                threshold: 1,
                addrs: vec![self.inner.inner.short_address.clone()],
            },
            ..Default::default()
        };
        tx.sign(signers)?;

        let signed_bytes = tx.unsigned_tx.metadata.unwrap().bytes;
        let hex_tx = formatting::encode_hex_with_checksum(&signed_bytes);
        let resp = api_p::issue_tx(&picked_http_rpc.1, &hex_tx).await?;

        if let Some(e) = resp.error {
            return Err(Error::new(
                ErrorKind::Other,
                format!("failed to issue create subnet transaction {:?}", e),
            ));
        }

        let tx_id = resp.result.unwrap().tx_id;
        log::info!("{} successfully issued", tx_id);

        if !self.check_acceptance {
            log::debug!("skipping checking acceptance...");
            return Ok((tx_id, true));
        }

        // enough time for txs processing
        sleep(Duration::from_millis(500)).await;

        log::info!("polling to confirm create subnet transaction");
        loop {
            let resp = api_p::get_tx_status(&picked_http_rpc.1, &tx_id.to_string()).await?;

            let status = resp.result.unwrap().status;
            if status == Status::Accepted {
                log::info!("{} successfully accepted", tx_id);
                break;
            }

            log::warn!(
                "{} {} (not accepted yet in {})",
                tx_id,
                status,
                picked_http_rpc.1
            );
            sleep(Duration::from_millis(700)).await;
        }

        Ok((tx_id, true))
    }
}