Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 201 additions & 132 deletions README.md

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions helium-lib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# helium-lib

Core Rust library for interacting with the Helium network on Solana. Used by
the `helium-wallet` CLI and available for integration into other Rust projects.

## Modules

| Module | Purpose |
|--------|---------|
| `asset` | Compressed NFT asset operations (fetch, transfer, burn, search) |
| `b64` | Base64 message encoding/decoding |
| `boosting` | Mobile hotspot hex boosting |
| `client` | Solana RPC client wrapper with DAS (Digital Asset Standard) and gRPC config support |
| `dao` | Helium DAO and SubDAO operations (HNT, IOT, MOBILE) |
| `dc` | Data credit minting, delegation, and burning |
| `entity_key` | Entity key management for hotspots |
| `error` | Error types (`Error`, `DecodeError`, `EncodeError`, `ConfirmationError`, `JupiterError`) |
| `hotspot` | Hotspot CRUD, transfer, and onboarding (submodules: `dataonly`, `info`, `cert`) |
| `jupiter` | Token swaps via Jupiter V2 API |
| `keypair` | Solana keypair wrappers with optional BIP39 mnemonic support |
| `kta` | KeyToAsset (KTA) lookups |
| `memo` | Memo encoding/decoding |
| `message` | Versioned transaction message building |
| `onboarding` | Hotspot onboarding API client |
| `priority_fee` | Compute budget and priority fee calculation |
| `programs` | On-chain program IDs (`helium_sub_daos`, `lazy_distributor`, etc.) |
| `queue` | Reward queue operations |
| `reward` | Reward claiming via lazy distributor |
| `schedule` | Schedule-based reward claiming |
| `token` | Token operations (HNT, MOBILE, IOT, DC, SOL, USDC) -- balances, transfers, burns, prices |
| `transaction` | Versioned transaction building and confirmation |

## Feature Flags

| Feature | Description |
|---------|-------------|
| `clap` | Adds CLI argument parsing support (value parsers for `Token` types) |
| `mnemonic` | Enables BIP39 mnemonic/seed phrase support for keypairs via `helium-mnemonic` |

## Re-exports

The crate re-exports several foundational Solana and Anchor crates for
convenience, so downstream consumers do not need to manage version alignment
themselves:

- `anchor_client`
- `anchor_lang`
- `anchor_spl`
- `solana_sdk` (includes `bs58`)
- `solana_program`
- `solana_client`
- `solana_transaction_status`
- `tuktuk_sdk`

## Usage

Add `helium-lib` to your `Cargo.toml`:

```toml
[dependencies]
helium-lib = { git = "https://github.com/helium/helium-wallet-rs" }
```

### Creating a client and querying a token balance

```rust
use helium_lib::{
client::Client,
keypair::Pubkey,
token::{self, Token},
};
use std::str::FromStr;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to mainnet (accepts "m", "mainnet-beta", "d", "devnet", or a URL)
let client = Client::try_from("m")?;

// Initialize the KeyToAsset cache
helium_lib::init(client.solana_client.clone())?;

// Look up the HNT associated token address for a wallet
let wallet = Pubkey::from_str("YOUR_SOLANA_PUBKEY")?;
let hnt_address = Token::Hnt.associated_token_address(&wallet);

// Fetch the balance
if let Some(balance) = token::balance_for_address(&client, &hnt_address).await? {
println!("HNT balance: {}", balance.amount);
}

Ok(())
}
```
38 changes: 36 additions & 2 deletions helium-lib/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize};
use solana_sdk::{signature::NullSigner, signer::Signer};
use std::{collections::HashMap, result::Result as StdResult, str::FromStr};

/// Fetches a compressed NFT asset for a given Helium entity key (e.g., hotspot public key).
pub async fn for_entity_key<E, C: AsRef<DasClient>>(
client: &C,
entity_key: &E,
Expand All @@ -32,6 +33,7 @@ where
for_kta(client, &kta).await
}

/// Fetches compressed NFT assets for multiple entity keys in batch.
pub async fn for_entity_keys<E, C: AsRef<DasClient>>(
client: &C,
entity_keys: &[E],
Expand All @@ -43,13 +45,15 @@ where
for_ktas(client, ktas.as_slice()).await
}

/// Fetches the compressed NFT asset referenced by a key-to-asset account.
pub async fn for_kta<C: AsRef<DasClient>>(
client: &C,
kta: &helium_entity_manager::accounts::KeyToAssetV0,
) -> Result<Asset, Error> {
get(client, &kta.asset).await
}

/// Fetches compressed NFT assets for multiple key-to-asset accounts in batch.
pub async fn for_ktas<C: AsRef<DasClient>>(
client: &C,
ktas: &[helium_entity_manager::accounts::KeyToAssetV0],
Expand All @@ -58,18 +62,21 @@ pub async fn for_ktas<C: AsRef<DasClient>>(
get_many(client, &pubkeys).await
}

/// Fetches both the asset and its Merkle proof from a key-to-asset account.
pub async fn for_kta_with_proof<C: AsRef<DasClient> + AsRef<SolanaRpcClient>>(
client: &C,
kta: &helium_entity_manager::accounts::KeyToAssetV0,
) -> Result<(Asset, AssetProof), Error> {
get_with_proof(client, &kta.asset).await
}

/// Fetches a single compressed NFT asset by its Solana public key.
pub async fn get<C: AsRef<DasClient>>(client: &C, pubkey: &Pubkey) -> Result<Asset, Error> {
let asset_response: Asset = client.as_ref().get_asset(pubkey).await?;
Ok(asset_response)
}

/// Fetches multiple compressed NFT assets by their Solana public keys (batched in chunks of 1000).
pub async fn get_many<C: AsRef<DasClient>>(
client: &C,
pubkeys: &[Pubkey],
Expand All @@ -86,6 +93,7 @@ pub async fn get_many<C: AsRef<DasClient>>(
Ok(assets)
}

/// Fetches an asset and its Merkle proof concurrently. Needed for transfer/burn operations.
pub async fn get_with_proof<C: AsRef<DasClient> + AsRef<SolanaRpcClient>>(
client: &C,
pubkey: &Pubkey,
Expand All @@ -94,6 +102,7 @@ pub async fn get_with_proof<C: AsRef<DasClient> + AsRef<SolanaRpcClient>>(
Ok((asset, asset_proof))
}

/// Derives the Metaplex metadata PDA for a collection mint.
pub fn collection_metadata_key(collection_key: &Pubkey) -> Pubkey {
let (collection_metadata, _bump) = Pubkey::find_program_address(
&[
Expand All @@ -106,6 +115,7 @@ pub fn collection_metadata_key(collection_key: &Pubkey) -> Pubkey {
collection_metadata
}

/// Derives the Metaplex master edition PDA for a collection mint.
pub fn collection_master_edition_key(collection_key: &Pubkey) -> Pubkey {
let (collection_master_edition, _cme_bump) = Pubkey::find_program_address(
&[
Expand All @@ -119,18 +129,21 @@ pub fn collection_master_edition_key(collection_key: &Pubkey) -> Pubkey {
collection_master_edition
}

/// Derives the Bubblegum tree authority PDA for a given Merkle tree.
pub fn merkle_tree_authority(merkle_tree: &Pubkey) -> Pubkey {
let (tree_authority, _ta_bump) =
Pubkey::find_program_address(&[merkle_tree.as_ref()], &bubblegum::ID);
tree_authority
}

/// Derives the Bubblegum collection CPI signer PDA.
pub fn bubblegum_signer() -> Pubkey {
let (bubblegum_signer, _bump) =
Pubkey::find_program_address(&[b"collection_cpi"], &bubblegum::ID);
bubblegum_signer
}

/// Utilities for determining Merkle tree canopy heights, used to trim proof sizes.
pub mod canopy {
use super::*;
use crate::spl_account_compression::types::{
Expand Down Expand Up @@ -169,6 +182,7 @@ pub mod canopy {
Ok(canopy_depth as usize)
}

/// Returns the canopy height for a single Merkle tree.
pub async fn height<C: AsRef<SolanaRpcClient>>(
client: &C,
tree: &Pubkey,
Expand All @@ -180,6 +194,7 @@ pub mod canopy {
height_from_account(&tree_account)
}

/// Returns canopy heights for multiple Merkle trees, using a remote cache when available.
pub async fn heights<C: AsRef<SolanaRpcClient>>(
client: &C,
trees: &[Pubkey],
Expand Down Expand Up @@ -325,9 +340,11 @@ pub mod canopy {
}
}

/// Functions for fetching Merkle proofs from DAS, with canopy height applied.
pub mod proof {
use super::*;

/// Fetches the Merkle proof for a single asset, including canopy height.
pub async fn get<C: AsRef<DasClient> + AsRef<SolanaRpcClient>>(
client: &C,
pubkey: &Pubkey,
Expand All @@ -339,6 +356,7 @@ pub mod proof {
Ok(asset_proof)
}

/// Fetches Merkle proofs for multiple assets in batch, including canopy heights.
pub async fn get_many<C: AsRef<DasClient> + AsRef<SolanaRpcClient>>(
client: &C,
pubkeys: &[Pubkey],
Expand Down Expand Up @@ -369,13 +387,15 @@ pub mod proof {
}
}

/// Searches for compressed NFT assets using DAS search parameters.
pub async fn search<C: AsRef<DasClient>>(
client: &C,
params: DasSearchAssetsParams,
) -> Result<AssetPage, Error> {
Ok(client.as_ref().search_assets(params).await?)
}

/// Returns all assets owned by a wallet that match a given creator, with automatic pagination.
pub async fn for_owner<C: AsRef<DasClient>>(
client: &C,
creator: &Pubkey,
Expand All @@ -402,6 +422,7 @@ pub async fn for_owner<C: AsRef<DasClient>>(
Ok(results)
}

/// Builds a Bubblegum transfer instruction for a compressed NFT asset.
pub fn transfer_instruction(
recipient: &Pubkey,
asset: &Asset,
Expand Down Expand Up @@ -441,7 +462,7 @@ pub fn transfer_instruction(
Ok(ix)
}

/// Get an unsigned transaction for an asset transfer
/// Gets an unsigned transaction for an asset transfer.
///
/// The asset is transferred from the owner to the given recipient
/// Note that the owner is currently expected to sign this transaction and pay for
Expand All @@ -467,6 +488,7 @@ pub async fn transfer_transaction<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
Ok((txn, block_height))
}

/// Signs and returns a transaction to transfer a compressed NFT to a new owner.
pub async fn transfer<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
client: &C,
pubkey: &Pubkey,
Expand All @@ -481,7 +503,7 @@ pub async fn transfer<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
Ok((txn, block_height))
}

/// Get an unsigned burn transaction for an asset
/// Gets an unsigned burn transaction for an asset.
pub async fn burn_message<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
client: &C,
pubkey: &Pubkey,
Expand Down Expand Up @@ -529,6 +551,7 @@ pub async fn burn_message<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
message::mk_message(client, ixs, &opts.lut_addresses, &asset.ownership.owner).await
}

/// Signs and returns a transaction to permanently burn (destroy) a compressed NFT.
pub async fn burn<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
client: &C,
pubkey: &Pubkey,
Expand All @@ -540,6 +563,7 @@ pub async fn burn<C: AsRef<SolanaRpcClient> + AsRef<DasClient>>(
Ok((txn, block_height))
}

/// A paginated list of compressed NFT assets returned from a DAS search.
#[derive(Deserialize, Serialize, Clone)]
pub struct AssetPage {
pub total: u32,
Expand All @@ -548,8 +572,10 @@ pub struct AssetPage {
pub items: Vec<Asset>,
}

/// A Solana compressed NFT (cNFT) representing a Helium entity such as a hotspot.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Asset {
/// The asset's on-chain address.
#[serde(with = "serde_pubkey")]
pub id: Pubkey,
pub compression: AssetCompression,
Expand All @@ -561,6 +587,7 @@ pub struct Asset {
pub burnt: bool,
}

/// A creator entry on a compressed NFT, with verification status and royalty share.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetCreator {
#[serde(with = "serde_pubkey")]
Expand All @@ -571,6 +598,7 @@ pub struct AssetCreator {

pub type Hash = [u8; 32];

/// Compression metadata for a cNFT: hashes, leaf position, and the Merkle tree it belongs to.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetCompression {
#[serde(with = "serde_hash")]
Expand All @@ -588,13 +616,15 @@ impl AssetCompression {
}
}

/// A collection group that a compressed NFT belongs to (e.g., the Helium hotspot collection).
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetGroup {
pub group_key: String,
#[serde(with = "serde_pubkey")]
pub group_value: Pubkey,
}

/// Ownership information for a compressed NFT: current owner and optional delegate.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetOwnership {
#[serde(with = "serde_pubkey")]
Expand All @@ -603,6 +633,7 @@ pub struct AssetOwnership {
pub delegate: Option<Pubkey>,
}

/// Merkle proof for a compressed NFT, required for on-chain operations (transfer, burn, etc.).
#[derive(Debug, Deserialize, Clone)]
pub struct AssetProof {
pub proof: Vec<String>,
Expand Down Expand Up @@ -665,12 +696,14 @@ impl AssetProof {
}
}

/// The off-chain content associated with a compressed NFT (metadata URI and parsed metadata).
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetContent {
pub metadata: AssetMetadata,
pub json_uri: url::Url,
}

/// Parsed JSON metadata for a compressed NFT, including name, symbol, and trait attributes.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetMetadata {
#[serde(default)]
Expand All @@ -690,6 +723,7 @@ impl AssetMetadata {
}
}

/// A single key-value trait attribute from an asset's metadata (e.g., `"trait_type": "entity_key"`).
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssetMetadataAttribute {
#[serde(default)]
Expand Down
Loading
Loading