Skip to main content

Documentation Index

Fetch the complete documentation index at: https://helius-auto-translations.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Best practices and recommended patterns for agents using the Helius Rust SDK. For installation and getting started, see the overview.

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