hyperspace-rs 0.2.0

A Rust library to interact with [Hyperspace](https://avax.hyperspace.xyz/) NFT marketplace on Avalanche
Documentation
use ethers::{
    providers::Middleware,
    types::{Address, Eip1559TransactionRequest, TxHash, H160, U256},
};
use std::str::FromStr;

use crate::{
    bindings::iwavax::IWAVAX,
    constants::{GET_ACCOUNT_INVENTORY, WAVAX_CONTRACT_ADDRESS},
    types::{MarketplaceSnapshot, PaginationConfig},
};

use super::{HyperspaceClient, HyperspaceClientError, HyperspaceSignerMiddleware};

mod types;

use types::{GetInventoryRequest, GetInventoryResponse};

pub struct Account<'a>(pub(crate) &'a HyperspaceClient);

impl<'a> Account<'a> {
    async fn get_wavax_contract(&self) -> IWAVAX<HyperspaceSignerMiddleware> {
        let wavax_contract_address = Address::from_str(WAVAX_CONTRACT_ADDRESS).unwrap();

        IWAVAX::new(wavax_contract_address, self.0.signer.clone())
    }

    pub fn get_address(&self) -> H160 {
        self.0.signer.address()
    }

    pub async fn get_avax_balance(&self) -> Result<U256, HyperspaceClientError> {
        Ok(self.0.signer.get_balance(self.get_address(), None).await?)
    }

    pub async fn get_wavax_balance(&self) -> Result<U256, HyperspaceClientError> {
        let wavax_contract = self.get_wavax_contract().await;

        Ok(wavax_contract.balance_of(self.get_address()).call().await?)
    }

    /// NOTE: If not specified, the amount will be the entire AVAX balance.
    pub async fn swap_avax_for_wavax(&self, amount: U256) -> Result<TxHash, HyperspaceClientError> {
        let avax_balance = self.get_avax_balance().await?;
        if avax_balance.is_zero() {
            return Err(HyperspaceClientError::InsufficientAvaxBalance(avax_balance));
        }

        let wavax_contract = self.get_wavax_contract().await;

        let typed_transaction = Eip1559TransactionRequest::new()
            .to(wavax_contract.address())
            .value(amount);

        let tx_receipt = match self
            .0
            .signer
            .send_transaction(typed_transaction, None)
            .await?
            .await?
        {
            Some(receipt) => receipt,
            None => {
                return Err(HyperspaceClientError::NoTransactionReceipt);
            }
        };

        Ok(tx_receipt.transaction_hash)
    }

    /// NOTE: If not specified, the amount will be the entire WAVAX balance.
    pub async fn swap_wavax_for_avax(
        &self,
        amount: Option<U256>,
    ) -> Result<TxHash, HyperspaceClientError> {
        let wavax_contract = self.get_wavax_contract().await;

        let wavax_balance = self.get_wavax_balance().await?;
        if wavax_balance.is_zero() {
            return Err(HyperspaceClientError::InsufficientWavaxBalance(
                wavax_balance,
            ));
        }

        let amount = match amount {
            Some(amount) => {
                if amount > wavax_balance {
                    return Err(HyperspaceClientError::InsufficientWavaxBalance(
                        wavax_balance,
                    ));
                } else {
                    amount
                }
            }
            None => wavax_balance,
        };

        let tx_receipt = match wavax_contract.withdraw(amount).send().await?.await? {
            Some(receipt) => receipt,
            None => {
                return Err(HyperspaceClientError::NoTransactionReceipt);
            }
        };

        Ok(tx_receipt.transaction_hash)
    }

    /// Get this account's owned NFTs.
    pub async fn get_inventory(
        &self,
        project_id: Option<impl AsRef<str>>,
    ) -> Result<Vec<MarketplaceSnapshot>, HyperspaceClientError> {
        let project_id = project_id.as_ref().map(|s| s.as_ref());

        let body = GetInventoryRequest::new(
            self.get_address(),
            project_id,
            Some(PaginationConfig::new(1, None)),
        );
        let res = self
            .0
            .post::<GetInventoryRequest, GetInventoryResponse>(GET_ACCOUNT_INVENTORY, body)
            .await?;

        Ok(res.marketplace_snapshots)
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    async fn setup() -> HyperspaceClient {
        dotenv::dotenv().ok();

        let api_key = dotenv::var("API_KEY").unwrap();
        let private_key = dotenv::var("PRIVATE_KEY").unwrap();

        HyperspaceClient::new(api_key, private_key, None::<&str>).unwrap()
    }

    #[tokio::test]
    async fn test_get_avax_balance() {
        let hs_client = setup().await;

        let balance = hs_client.account().get_avax_balance().await.unwrap();
        dbg!(balance);
    }

    #[tokio::test]
    async fn test_get_wavax_balance() {
        let hs_client = setup().await;

        let balance = hs_client.account().get_wavax_balance().await.unwrap();
        dbg!(balance);
    }

    #[tokio::test]
    async fn test_swap_avax_for_wavax() {
        let hs_client = setup().await;

        let tx_hash = hs_client
            .account()
            .swap_avax_for_wavax(U256::from_str_radix("1000000000000000000", 10).unwrap())
            .await
            .unwrap();

        assert_ne!(tx_hash, TxHash::zero());
    }

    #[tokio::test]
    async fn test_swap_wavax_for_avax() {
        let hs_client = setup().await;

        let tx_hash = hs_client
            .account()
            .swap_wavax_for_avax(Some(
                U256::from_str_radix("1000000000000000000", 10).unwrap(),
            ))
            .await
            .unwrap();

        assert_ne!(tx_hash, TxHash::zero());
    }

    #[tokio::test]
    async fn test_swap_all_wavax_for_avax() {
        let hs_client = setup().await;

        let tx_hash = hs_client.account().swap_wavax_for_avax(None).await.unwrap();

        assert_ne!(tx_hash, TxHash::zero());
    }

    #[tokio::test]
    async fn test_get_inventory() {
        let hs_client = setup().await;

        let owned_nfts = hs_client
            .account()
            .get_inventory(None::<&str>)
            .await
            .unwrap();

        assert_eq!(owned_nfts.len(), 856);
    }
}