Skip to content

Latest commit

 

History

History
547 lines (435 loc) · 21.7 KB

File metadata and controls

547 lines (435 loc) · 21.7 KB

Helius Rust SDK

Machine-readable guide for AI agents using the Helius Rust SDK to build on Solana.

  • Crate: helius (crates.io)
  • Version: 1.x (uses solana-sdk 3.0, solana-client 3.0)
  • Runtime: Async (tokio 1.x)
  • Rust: 1.85+
  • License: MIT

Getting an API Key

Get an API key from https://dashboard.helius.dev, or sign up programmatically via the Helius CLI. See the CLI agent guide for automated signup instructions.

Quick Start

# Cargo.toml
[dependencies]
helius = "1.0.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
solana-sdk = "3.0.0"
use helius::Helius;
use helius::types::Cluster;

#[tokio::main]
async fn main() -> helius::error::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: "wallet_address".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(
        "wallet_address".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, US_SLC, US_EAST, EU_WEST, EU_CENTRAL, EU_NORTH, AP_SINGAPORE, AP_TOKYO
            ..Default::default()
        },
    ).await?;

    Ok(())
}

Client Constructors

Helius::new — Basic Sync Client

let helius = Helius::new("your-api-key", Cluster::MainnetBeta)?;

Simplest constructor. Synchronous — no .await needed. Provides RPC methods, webhooks, Enhanced Transactions, smart transactions, and Wallet API. No async Solana client or WebSocket support.

Helius::new_async — Full-Featured Async Client

let helius = Helius::new_async("your-api-key", Cluster::MainnetBeta).await?;

Recommended for production. Includes async Solana RPC client, and Enhanced WebSocket streaming, using a confirmed commitment level. 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?;

Builder methods: with_api_key(), with_cluster(), with_custom_url(), with_custom_api_url(), with_custom_ws_url(), with_commitment(), with_async_solana(), with_websocket(), with_http_client().

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

// Synchronous Solana RPC client
let balance = helius.connection().get_balance(&pubkey)?;

// Asynchronous Solana RPC client (requires new_async or HeliusBuilder with_async_solana)
let async_client = helius.async_connection()?;
let balance = async_client.get_balance(&pubkey).await?;

// Enhanced WebSocket client (requires new_async or HeliusBuilder with_websocket)
let ws = helius.ws().expect("WebSocket available");

Recommendations

Use get_transactions_for_address Instead of get_signatures_for_address + get_transaction

get_transactions_for_address combines signature lookup and transaction fetching into a single call with server-side filtering. It supports time/slot ranges, token account filtering, and pagination. This is always preferable to the two-step approach.

// 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)?;
let txs: Vec<_> = futures::future::join_all(
    sigs.iter().map(|s| helius.connection().get_transaction(&s.signature, ...))
).await;

Use send_smart_transaction for Standard Sends

It automatically simulates, estimates compute units, fetches priority fees, and confirms. Don't manually build ComputeBudget instructions.

let sig = helius.send_smart_transaction(SmartTransactionConfig {
    create_config: CreateSmartTransactionConfig {
        instructions: vec![your_instruction],
        signers: vec![wallet_signer],
        priority_fee_cap: Some(100_000),    // Optional: cap fees in microlamports/CU
        cu_buffer_multiplier: Some(1.1),    // 10% compute unit headroom (default: 1.25)
        ..Default::default()
    },
    ..Default::default()
}).await?;

Use Helius Sender for Ultra-Low Latency

For time-sensitive transactions (arbitrage, sniping, liquidations), reliability, and the fastest landing speeds on Solana, 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(),
        swqos_only: false,          // true = SWQOS only, 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. Don't 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
let assets: Vec<_> = futures::future::join_all(
    mints.iter().map(|id| helius.rpc().get_asset(GetAsset { id: id.clone(), ..Default::default() }))
).await;

Use Webhooks Instead of Polling

Don't 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

The SDK uses different pagination strategies depending on the method:

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; }
}

// get_program_accounts_v2 — uses pagination_key
let mut pagination_key: Option<String> = None;
loop {
    let result = helius.rpc().get_program_accounts_v2(
        program_id.to_string(),
        GetProgramAccountsV2Config {
            limit: Some(1000),
            pagination_key: pagination_key.clone(),
            ..Default::default()
        },
    ).await?;
    // process result.accounts
    pagination_key = result.pagination_key;
    if pagination_key.is_none() { break; }
}

// Or use the auto-paginating variant:
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;
}

tokenAccounts Filter

When querying get_transactions_for_address, the token_accounts filter controls whether token account activity is included:

Value Behavior Use When
None Only transactions directly involving the address You only care about SOL transfers and program calls
BalanceChanged Also includes token transactions that changed a balance Recommended for most agents — shows token sends/receives without noise
All Includes all token account transactions You need complete token activity (can return many results)

changed_since_slot — Incremental Account Fetching

get_program_accounts_v2 and get_token_accounts_by_owner_v2 (and their auto-paginating get_all_* variants) support changed_since_slot, a Helius-specific parameter that returns only accounts modified after a given slot.

// 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; // save the slot you fetched at

// 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?;
Use changed_since_slot when Don't use it when
Polling for account updates on a schedule Doing a one-time full fetch
Building an indexer that tracks state changes You need the complete set regardless of recency
Reducing response size on large programs The program has very few accounts

Rate Limits & Plans

Feature Free Developer $49/mo Business $499/mo Professional $999/mo
Monthly Credits 1M 10M 100M 200M
RPC Rate Limit 10 req/s 50 req/s 200 req/s 500 req/s
DAS & Enhanced API 2 req/s 10 req/s 50 req/s 100 req/s
Helius Sender 15/s 15/s 15/s 15/s
Enhanced WebSockets No Yes Yes Yes
LaserStream gRPC No Devnet Devnet + Mainnet Devnet + Mainnet

LaserStream is billed at 2 credits per 0.1 MB of data received.

Monitor usage via the Helius CLI using helius usage --json.

Error Handling & Retries

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. The SDK provides typed error variants, so you can match on them directly:

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!()
}

Common Gotchas

  1. Don't forget transaction_details: Some(TransactionDetails::Full) — By default, get_transactions_for_address returns signatures only, not full transaction data.

  2. Don't manually add ComputeBudget instructions with send_smart_transaction — The SDK adds them automatically. Adding your own will result in a HeliusError::InvalidInput error.

  3. Priority fees are in microlamports per compute unit — Not lamports. When using get_priority_fee_estimate, the returned values are already in the right unit for SetComputeUnitPrice.

  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.

API Quick Reference

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, page, .. })  // 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

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.create_smart_transaction_without_signers(config)                  // Unsigned transaction
helius.get_compute_units(instructions, payer, lookup_tables, signers)    // Simulate CU usage
helius.poll_transaction_confirmation(signature)                          // Poll confirmation status
helius.send_and_confirm_transaction(tx, config, last_block, timeout)     // Send + confirm loop

Helius Sender

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

Enhanced Transactions

helius.parse_transactions(ParseTransactionsRequest { transactions })     // Parse by signatures
helius.parsed_transaction_history(ParsedTransactionHistoryRequest { address, .. })  // 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 (unsigned tx)
helius.create_unstake_transaction(owner, stake_account)                  // Deactivate stake (unsigned tx)
helius.create_withdraw_transaction(owner, stake_acct, dest, lamports)    // Withdraw (unsigned tx)
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
helius.ws()                                                              // Enhanced WebSocket (Option)
helius.rpc()                                                             // Helius RpcClient (Arc)
helius.config()                                                          // Config (Arc)

Documentation


Contributing

For AI agents contributing to this repository, see CLAUDE.md for full details. Quick reference:

cargo build --release          # Build
cargo test                     # Run all tests
cargo fmt && cargo clippy      # Format and lint

Before submitting a PR: cargo fmt && cargo clippy && cargo test

Key architecture decisions, code style conventions, and PR process are documented in CLAUDE.md and CONTRIBUTIONS.md.