Skip to main content
The Helius Rust SDK provides async Rust bindings for all Helius APIs, ideal for high-performance agent workloads.
  • Crate: helius (crates.io)
  • Version: 1.x (uses solana-client 3.0, solana-sdk 3.0)
  • Runtime: Async (tokio 1.x)
  • Rust: 1.85+ (edition 2021)
  • HTTP Client: reqwest
  • License: MIT

Installation

[dependencies]
helius = "1.0.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
solana-sdk = "3.0.0"
The crate defaults to native-tls. For pure-Rust TLS (useful when OpenSSL is unavailable), use:
helius = { version = "1.0.0", default-features = false, features = ["rustls"] }

Quick Start

use helius::error::Result;
use helius::types::*;
use helius::Helius;

#[tokio::main]
async fn main() -> Result<()> {
    let helius = Helius::new("YOUR_API_KEY", Cluster::MainnetBeta)?;

    // Get all NFTs owned by a wallet
    let assets = helius.rpc().get_assets_by_owner(GetAssetsByOwner {
        owner_address: "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY".to_string(),
        page: 1,
        limit: Some(50),
        ..Default::default()
    }).await?;

    // Get transaction history (with token account activity)
    let txs = helius.rpc().get_transactions_for_address(
        "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY".to_string(),
        GetTransactionsForAddressOptions {
            limit: Some(100),
            transaction_details: Some(TransactionDetails::Full),
            filters: Some(GetTransactionsFilters {
                token_accounts: Some(TokenAccountsFilter::BalanceChanged),
                ..Default::default()
            }),
            ..Default::default()
        },
    ).await?;

    // Send a transaction via Helius Sender (ultra-low latency)
    let sig = helius.send_smart_transaction_with_sender(
        SmartTransactionConfig {
            create_config: CreateSmartTransactionConfig {
                instructions: vec![transfer_instruction],
                signers: vec![wallet_signer],
                ..Default::default()
            },
            ..Default::default()
        },
        SenderSendOptions {
            region: "US_EAST".to_string(),
            ..Default::default()
        },
    ).await?;

    Ok(())
}

Client Constructors

Helius::new — Basic sync client

let helius = Helius::new("YOUR_API_KEY", Cluster::MainnetBeta)?;
Simplest constructor. No .await needed. Provides RPC methods, webhooks, Enhanced Transactions, smart transactions, and Wallet API. No async Solana client or WebSocket support.
let helius = Helius::new_async("YOUR_API_KEY", Cluster::MainnetBeta).await?;
Recommended for production. Includes async Solana RPC client and Enhanced WebSocket streaming. Requires .await because it establishes a WebSocket connection.

Helius::new_with_url — Custom RPC endpoint

let helius = Helius::new_with_url("http://localhost:8899")?;
For dedicated RPC nodes, proxies, or local development. No API key required.

HeliusBuilder — Advanced configuration

use helius::HeliusBuilder;

let helius = HeliusBuilder::new()
    .with_api_key("YOUR_API_KEY")?
    .with_cluster(Cluster::MainnetBeta)
    .with_async_solana()
    .with_websocket(None, None)
    .with_commitment(CommitmentConfig::confirmed())
    .build()
    .await?;

HeliusFactory — Multi-cluster

let factory = HeliusFactory::new("YOUR_API_KEY");
let devnet_client = factory.create(Cluster::Devnet)?;
let mainnet_client = factory.create(Cluster::MainnetBeta)?;

Accessing embedded Solana clients

helius.connection()          // Sync SolanaRpcClient (Arc)
helius.async_connection()?   // Async SolanaRpcClient (requires new_async or HeliusBuilder)
helius.ws()                  // Enhanced WebSocket (Option)
helius.rpc()                 // Helius RpcClient (Arc)
helius.config()              // Config (Arc)

Recommendations for Agents

Use get_transactions_for_address instead of two-step lookup

get_transactions_for_address combines signature lookup and transaction fetching into a single call with server-side filtering.
// GOOD: Single call, server-side filtering
let txs = helius.rpc().get_transactions_for_address(
    "address".to_string(),
    GetTransactionsForAddressOptions {
        transaction_details: Some(TransactionDetails::Full),
        limit: Some(100),
        filters: Some(GetTransactionsFilters {
            token_accounts: Some(TokenAccountsFilter::BalanceChanged),
            ..Default::default()
        }),
        ..Default::default()
    },
).await?;

// BAD: Two calls, client-side filtering
let sigs = helius.connection().get_signatures_for_address(&address)?;

Use send_smart_transaction for standard sends

It automatically simulates, estimates compute units, fetches priority fees, and confirms. Do not manually build ComputeBudget instructions — the SDK adds them automatically.
let sig = helius.send_smart_transaction(SmartTransactionConfig {
    create_config: CreateSmartTransactionConfig {
        instructions: vec![your_instruction],
        signers: vec![wallet_signer],
        priority_fee_cap: Some(100_000),
        cu_buffer_multiplier: Some(1.1),
        ..Default::default()
    },
    ..Default::default()
}).await?;

Use Helius Sender for ultra-low latency

For time-sensitive transactions (arbitrage, sniping, liquidations), use send_smart_transaction_with_sender. It routes through Helius’s multi-region infrastructure and Jito.
let sig = helius.send_smart_transaction_with_sender(
    SmartTransactionConfig {
        create_config: CreateSmartTransactionConfig {
            instructions: vec![your_instruction],
            signers: vec![wallet_signer],
            ..Default::default()
        },
        ..Default::default()
    },
    SenderSendOptions {
        region: "US_EAST".to_string(),    // Default, US_SLC, US_EAST, EU_WEST, EU_CENTRAL, EU_NORTH, AP_SINGAPORE, AP_TOKYO
        swqos_only: false,                // true = SWQOS only (lower tip), false = Dual (SWQOS + Jito)
        poll_timeout_ms: 60_000,
        poll_interval_ms: 2_000,
    },
).await?;

Use get_asset_batch for multiple assets

When fetching more than one asset, batch them. Do not call get_asset in a loop.
// GOOD: Single request
let assets = helius.rpc().get_asset_batch(GetAssetBatch {
    ids: vec!["mint1".to_string(), "mint2".to_string(), "mint3".to_string()],
    ..Default::default()
}).await?;

// BAD: N requests
for id in mints {
    let asset = helius.rpc().get_asset(GetAsset { id, ..Default::default() }).await?;
}

Use webhooks instead of polling

Do not poll get_transactions_for_address in a loop. Use webhooks for server-to-server notifications.
let webhook = helius.create_webhook(CreateWebhookRequest {
    webhook_url: "https://your-server.com/webhook".to_string(),
    webhook_type: WebhookType::Enhanced,
    transaction_types: vec![TransactionType::Transfer, TransactionType::NftSale, TransactionType::Swap],
    account_addresses: vec!["address_to_monitor".to_string()],
    auth_header: Some("Bearer your-secret".to_string()),
    ..Default::default()
}).await?;

Pagination

Token/Cursor-Based (RPC V2 Methods)

// get_transactions_for_address uses pagination_token
let mut pagination_token: Option<String> = None;
let mut all_txs = Vec::new();
loop {
    let result = helius.rpc().get_transactions_for_address(
        "address".to_string(),
        GetTransactionsForAddressOptions {
            limit: Some(100),
            pagination_token: pagination_token.clone(),
            ..Default::default()
        },
    ).await?;
    all_txs.extend(result.data);
    pagination_token = result.pagination_token;
    if pagination_token.is_none() { break; }
}

// Or use auto-paginating variants:
let all_accounts = helius.rpc().get_all_program_accounts(
    program_id.to_string(),
    GetProgramAccountsV2Config::default(),
).await?;

Page-Based (DAS API)

let mut page = 1;
let mut all_assets = Vec::new();
loop {
    let result = helius.rpc().get_assets_by_owner(GetAssetsByOwner {
        owner_address: "...".to_string(),
        page,
        limit: Some(1000),
        ..Default::default()
    }).await?;
    let count = result.items.len();
    all_assets.extend(result.items);
    if count < 1000 { break; }
    page += 1;
}

token_accounts Filter

When querying get_transactions_for_address, the token_accounts filter controls whether token account activity is included:
ValueBehaviorUse When
NoneOnly transactions directly involving the addressYou only care about SOL transfers and program calls
BalanceChangedAlso includes token transactions that changed a balanceRecommended for most agents — shows token sends/receives without noise
AllIncludes all token account transactionsYou need complete token activity (can return many results)

changed_since_slot — Incremental Account Fetching

changed_since_slot returns only accounts modified after a given slot. Useful for syncing or indexing workflows. Supported by get_program_accounts_v2, get_token_accounts_by_owner_v2, get_account_info, get_multiple_accounts, get_program_accounts, and get_token_accounts_by_owner.
// First fetch: get all accounts
let baseline = helius.rpc().get_program_accounts_v2(
    program_id.to_string(),
    GetProgramAccountsV2Config { limit: Some(10_000), ..Default::default() },
).await?;
let last_slot = current_slot;

// Later: only get accounts that changed since your last fetch
let updates = helius.rpc().get_program_accounts_v2(
    program_id.to_string(),
    GetProgramAccountsV2Config {
        limit: Some(10_000),
        changed_since_slot: Some(last_slot),
        ..Default::default()
    },
).await?;

Common Mistakes

  1. transaction_details: Some(TransactionDetails::Full) is not the default — By default, get_transactions_for_address returns signatures only. Set TransactionDetails::Full to get full transaction data.
  2. Do not add ComputeBudget instructions with send_smart_transaction — The SDK adds them automatically. Adding your own causes a HeliusError::InvalidInput error.
  3. Priority fees are in microlamports per compute unit — Not lamports. Values from get_priority_fee_estimate are already in the correct unit.
  4. DAS pagination is 1-indexedpage: 1 is the first page, not page: 0.
  5. async_connection() requires new_async or HeliusBuilder — Calling helius.async_connection() on a client created with Helius::new() returns Err(HeliusError::ClientNotInitialized).
  6. get_asset returns Option<Asset> — A successful response may still be None if the asset doesn’t exist. Handle the Option explicitly.
  7. Sender tips are mandatorysend_smart_transaction_with_sender automatically determines and appends tips. Minimum 0.0002 SOL (Dual mode) or 0.000005 SOL (SWQOS-only).
  8. TLS feature flags — The crate defaults to native-tls. Use features = ["rustls"] (and default-features = false) for pure-Rust TLS when OpenSSL is unavailable.

Error Handling and Retries

The SDK provides typed error variants via the HeliusError enum, so you can match on them directly:
use helius::error::{HeliusError, Result};

match helius.rpc().get_asset(request).await {
    Ok(asset) => { /* success */ }
    Err(HeliusError::Unauthorized { .. }) => { /* 401: invalid or missing API key */ }
    Err(HeliusError::RateLimitExceeded { .. }) => { /* 429: too many requests or out of credits */ }
    Err(HeliusError::InternalError { .. }) => { /* 5xx: server error, retry with backoff */ }
    Err(HeliusError::NotFound { .. }) => { /* 404: resource not found */ }
    Err(HeliusError::BadRequest { .. }) => { /* 400: malformed request */ }
    Err(HeliusError::Timeout { .. }) => { /* transaction confirmation timed out */ }
    Err(e) => { /* other errors: Network, SerdeJson, etc. */ }
}

Retry strategy

Retry on RateLimitExceeded and InternalError with exponential backoff:
async fn with_retry<T, F, Fut>(f: F, max_retries: u32) -> Result<T>
where
    F: Fn() -> Fut,
    Fut: std::future::Future<Output = Result<T>>,
{
    for attempt in 0..=max_retries {
        match f().await {
            Ok(val) => return Ok(val),
            Err(HeliusError::RateLimitExceeded { .. })
            | Err(HeliusError::InternalError { .. }) if attempt < max_retries => {
                tokio::time::sleep(std::time::Duration::from_millis(1000 * 2u64.pow(attempt))).await;
            }
            Err(e) => return Err(e),
        }
    }
    unreachable!()
}
Error VariantHTTP StatusAction
Unauthorized401Check API key
RateLimitExceeded429Back off and retry
InternalError5xxRetry with exponential backoff
BadRequest400Fix request parameters
NotFound404Check resource exists
TimeoutIncrease timeout or retry

API Quick Reference

Here is a full list of all API methods supported by the Helius Rust SDK:

DAS API (Digital Asset Standard)

helius.rpc().get_asset(GetAsset { id, .. })                              // Single asset by mint
helius.rpc().get_asset_batch(GetAssetBatch { ids, .. })                  // Multiple assets
helius.rpc().get_assets_by_owner(GetAssetsByOwner { owner_address, .. }) // Assets by wallet
helius.rpc().get_assets_by_authority(GetAssetsByAuthority { .. })        // Assets by update authority
helius.rpc().get_assets_by_creator(GetAssetsByCreator { .. })            // Assets by creator
helius.rpc().get_assets_by_group(GetAssetsByGroup { .. })                // Assets by collection
helius.rpc().search_assets(SearchAssets { .. })                          // Flexible search
helius.rpc().get_asset_proof(GetAssetProof { id })                       // Merkle proof (cNFTs)
helius.rpc().get_asset_proof_batch(GetAssetProofBatch { ids })           // Batch Merkle proofs
helius.rpc().get_token_accounts(GetTokenAccounts { .. })                 // Token accounts
helius.rpc().get_nft_editions(GetNftEditions { .. })                     // Print editions
helius.rpc().get_signatures_for_asset(GetAssetSignatures { id, .. })     // Transaction history for asset

RPC V2 Methods

helius.rpc().get_transactions_for_address(address, options)              // Transaction history (pagination_token)
helius.rpc().get_program_accounts_v2(program_id, config)                 // Program accounts (pagination_key)
helius.rpc().get_all_program_accounts(program_id, config)                // Auto-paginating variant
helius.rpc().get_token_accounts_by_owner_v2(owner, filter, config)       // Token accounts v2 (pagination_key)
helius.rpc().get_all_token_accounts_by_owner(owner, filter, config)      // Auto-paginating variant
helius.rpc().get_priority_fee_estimate(request)                          // Fee estimates

Smart Transactions and Helius Sender

helius.send_smart_transaction(config)                                    // Auto-optimized send
helius.create_smart_transaction(config)                                  // Build without sending
helius.create_smart_transaction_with_seeds(config)                       // Thread-safe (seed-based)
helius.send_smart_transaction_with_seeds(config, send_opts, timeout)     // Thread-safe send
helius.send_smart_transaction_with_sender(config, sender_opts)           // Build + send via Sender
helius.create_smart_transaction_with_tip_for_sender(config, tip)         // Build with tip
helius.send_and_confirm_via_sender(tx, last_block, sender_opts)          // Send pre-built tx via Sender
helius.determine_tip_lamports(swqos_only)                                // Calculate tip amount
helius.fetch_tip_floor_75th()                                            // Get Jito tip floor
helius.warm_sender_connection(region)                                    // Warm connection via /ping
helius.get_compute_units(instructions, payer, lookup_tables, signers)    // Simulate CU usage
helius.poll_transaction_confirmation(signature)                          // Poll confirmation status

Enhanced Transactions

helius.parse_transactions(ParseTransactionsRequest { transactions })     // Parse by signatures
helius.parsed_transaction_history(ParsedTransactionHistoryRequest { .. })// Parse by address

Webhooks

helius.create_webhook(CreateWebhookRequest { .. })                       // Create webhook
helius.get_webhook_by_id(webhook_id)                                     // Get webhook config
helius.get_all_webhooks()                                                // List all webhooks
helius.edit_webhook(EditWebhookRequest { .. })                           // Update webhook
helius.delete_webhook(webhook_id)                                        // Delete webhook
helius.append_addresses_to_webhook(webhook_id, &addresses)               // Add monitored addresses
helius.remove_addresses_from_webhook(webhook_id, &addresses)             // Remove monitored addresses

Wallet API

helius.get_wallet_identity(wallet)                                       // Identity info
helius.get_batch_wallet_identity(&addresses)                             // Batch identity (max 100)
helius.get_wallet_balances(wallet, page, limit, zero_bal, native, nfts)  // Token balances
helius.get_wallet_history(wallet, limit, before, after, tx_type, token_accts)  // Transaction history
helius.get_wallet_transfers(wallet, limit, cursor)                       // Transfer history
helius.get_wallet_funding_source(wallet)                                 // Funding source

Staking

helius.create_stake_transaction(owner, amount_sol)                       // Create + delegate stake
helius.create_unstake_transaction(owner, stake_account)                  // Deactivate stake
helius.create_withdraw_transaction(owner, stake_acct, dest, lamports)    // Withdraw
helius.get_stake_instructions(owner, amount_sol)                         // Get raw instructions + keypair
helius.get_unstake_instruction(owner, stake_account)                     // Deactivate instruction
helius.get_withdraw_instruction(owner, stake_acct, dest, lamports)       // Withdraw instruction
helius.get_withdrawable_amount(stake_account, include_rent_exempt)       // Check withdrawable balance
helius.get_stake_accounts(wallet)                                        // List stake accounts

Embedded Solana Client

helius.connection()                  // Sync SolanaRpcClient (Arc)
helius.async_connection()?           // Async SolanaRpcClient (requires new_async)
helius.ws()                          // Enhanced WebSocket (Option)

// Standard Solana RPC via embedded client
let balance = helius.connection().get_balance(&pubkey)?;
let async_client = helius.async_connection()?;
let balance = async_client.get_balance(&pubkey).await?;

Resources