hedera 0.43.0

The SDK for interacting with Hedera Hashgraph.
Documentation
use assert_matches::assert_matches;
use hedera::{
    Hbar,
    Key,
    PrivateKey,
    Status,
    TokenCreateTransaction,
    TokenInfoQuery,
    TokenSupplyType,
    TokenType,
};
use time::{
    Duration,
    OffsetDateTime,
};

use crate::account::Account;
use crate::common::{
    setup_nonfree,
    TestEnvironment,
};
use crate::token::{
    CreateFungibleToken,
    FungibleToken,
    Nft,
    TokenKeys,
};

#[tokio::test]

async fn query_all_different_keys() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;

    let freeze_key = PrivateKey::generate_ed25519();
    let wipe_key = PrivateKey::generate_ed25519();
    let kyc_key = PrivateKey::generate_ed25519();
    let supply_key = PrivateKey::generate_ed25519();
    let fee_schedule_key = PrivateKey::generate_ed25519();
    let pause_key = PrivateKey::generate_ed25519();

    let token = FungibleToken::create(
        &client,
        &account,
        CreateFungibleToken {
            initial_supply: 0,
            keys: TokenKeys {
                admin: Some(super::Key::Owner),
                freeze: Some(super::Key::Custom(freeze_key.public_key())),
                wipe: Some(super::Key::Custom(wipe_key.public_key())),
                kyc: Some(super::Key::Custom(kyc_key.public_key())),
                supply: Some(super::Key::Custom(supply_key.public_key())),
                fee_schedule: Some(super::Key::Custom(fee_schedule_key.public_key())),
                pause: Some(super::Key::Custom(pause_key.public_key())),
            },
        },
    )
    .await?;

    let info = TokenInfoQuery::new().token_id(token.id).execute(&client).await?;

    assert_eq!(info.token_id, token.id);
    assert_eq!(info.name, "ffff");
    assert_eq!(info.symbol, "F");
    assert_eq!(info.decimals, 3);
    assert_eq!(info.treasury_account_id, account.id);
    assert_eq!(info.admin_key, Some(Key::Single(account.key.public_key())));
    assert_eq!(info.freeze_key, Some(Key::Single(freeze_key.public_key())));
    assert_eq!(info.wipe_key, Some(Key::Single(wipe_key.public_key())));
    assert_eq!(info.kyc_key, Some(Key::Single(kyc_key.public_key())));
    assert_eq!(info.supply_key, Some(Key::Single(supply_key.public_key())));
    assert_eq!(info.fee_schedule_key, Some(Key::Single(fee_schedule_key.public_key())));
    assert_eq!(info.pause_key, Some(Key::Single(pause_key.public_key())));
    assert_eq!(info.default_freeze_status, Some(false));
    assert_eq!(info.default_kyc_status, Some(false));
    assert_eq!(info.token_type, TokenType::FungibleCommon);
    assert_eq!(info.supply_type, TokenSupplyType::Infinite);

    token.delete(&client).await?;
    account.delete(&client).await?;

    Ok(())
}

#[tokio::test]

async fn query_minimal() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;

    let token_id = TokenCreateTransaction::new()
        .name("ffff")
        .symbol("F")
        .treasury_account_id(account.id)
        .expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
        .sign(account.key.clone())
        .execute(&client)
        .await?
        .get_receipt(&client)
        .await?
        .token_id
        .unwrap();

    let info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;

    assert_eq!(info.token_id, token_id);
    assert_eq!(info.name, "ffff");
    assert_eq!(info.symbol, "F");
    assert_eq!(info.decimals, 0);
    assert_eq!(info.treasury_account_id, account.id);
    assert_eq!(info.admin_key, None);
    assert_eq!(info.freeze_key, None);
    assert_eq!(info.wipe_key, None);
    assert_eq!(info.kyc_key, None);
    assert_eq!(info.supply_key, None);
    assert_eq!(info.default_freeze_status, None);
    assert_eq!(info.default_kyc_status, None);
    assert_eq!(info.token_type, TokenType::FungibleCommon);
    assert_eq!(info.supply_type, TokenSupplyType::Infinite);

    // we have to leave this account, for it is a treasury.
    // account.delete(&client).await?;

    Ok(())
}

#[tokio::test]

async fn query_nft() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;

    let token_id = TokenCreateTransaction::new()
        .name("ffff")
        .symbol("F")
        .treasury_account_id(account.id)
        .admin_key(account.key.public_key())
        .supply_key(account.key.public_key())
        .token_type(TokenType::NonFungibleUnique)
        .token_supply_type(TokenSupplyType::Finite)
        .max_supply(5000)
        .expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
        .sign(account.key.clone())
        .execute(&client)
        .await?
        .get_receipt(&client)
        .await?
        .token_id
        .unwrap();

    let token = Nft { id: token_id, owner: account.clone() };

    let serials = token.mint_incremental(&client, 10).await?;

    assert_eq!(serials.len(), 10);

    let info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;

    assert_eq!(info.token_id, token_id);
    assert_eq!(info.name, "ffff");
    assert_eq!(info.symbol, "F");
    assert_eq!(info.decimals, 0);
    assert_eq!(info.total_supply, 10);
    assert_eq!(info.treasury_account_id, account.id);
    assert_eq!(info.admin_key, Some(Key::Single(account.key.public_key())));
    assert_eq!(info.supply_key, Some(Key::Single(account.key.public_key())));
    assert_eq!(info.default_freeze_status, None);
    assert_eq!(info.default_kyc_status, None);
    assert_eq!(info.token_type, TokenType::NonFungibleUnique);
    assert_eq!(info.supply_type, TokenSupplyType::Finite);
    assert_eq!(info.max_supply, 5000);

    token.burn(&client, serials).await?;
    token.delete(&client).await?;
    account.delete(&client).await?;

    Ok(())
}

#[tokio::test]

async fn query_cost() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;
    let token = super::FungibleToken::create(&client, &account, Default::default()).await?;

    let mut query = TokenInfoQuery::new();

    query.token_id(token.id);

    let cost = query.get_cost(&client).await?;

    query.payment_amount(cost).execute(&client).await?;

    token.delete(&client).await?;
    account.delete(&client).await?;

    Ok(())
}

#[tokio::test]

async fn query_cost_big_max() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;
    let token = super::FungibleToken::create(&client, &account, Default::default()).await?;

    let mut query = TokenInfoQuery::new();

    query.token_id(token.id).max_payment_amount(Hbar::new(1000));

    let cost = query.get_cost(&client).await?;

    query.payment_amount(cost).execute(&client).await?;

    token.delete(&client).await?;
    account.delete(&client).await?;

    Ok(())
}

#[tokio::test]

async fn query_cost_small_max_fails() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;
    let token = super::FungibleToken::create(&client, &account, Default::default()).await?;

    let mut query = TokenInfoQuery::new();

    query.token_id(token.id).max_payment_amount(Hbar::from_tinybars(1));

    let cost = query.get_cost(&client).await?;

    let res = query.execute(&client).await;

    let (max_query_payment, query_cost) = assert_matches!(
        res,
        Err(hedera::Error::MaxQueryPaymentExceeded {
            max_query_payment,
            query_cost
        }) => (max_query_payment, query_cost)
    );

    assert_eq!(max_query_payment, Hbar::from_tinybars(1));
    // note: there's a very small chance this fails if the cost of a FileContentsQuery changes right when we execute it.
    assert_eq!(query_cost, cost);

    token.delete(&client).await?;
    account.delete(&client).await?;

    Ok(())
}

#[tokio::test]
#[ignore]
async fn query_cost_insufficient_tx_fee_fails() -> anyhow::Result<()> {
    let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
        return Ok(());
    };

    let account = Account::create(Hbar::new(0), &client).await?;
    let token = super::FungibleToken::create(&client, &account, Default::default()).await?;

    let res = TokenInfoQuery::new()
        .token_id(token.id)
        .max_payment_amount(Hbar::from_tinybars(10000))
        .payment_amount(Hbar::from_tinybars(1))
        .execute(&client)
        .await;

    assert_matches!(
        res,
        Err(hedera::Error::QueryPaymentPreCheckStatus { status: Status::InsufficientTxFee, .. })
    );

    token.delete(&client).await?;
    account.delete(&client).await?;

    Ok(())
}