> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust SDK Best Practices

> Recommended patterns for AI agents using the Helius Rust SDK. Covers transaction history, sending transactions, batching, webhooks, pagination, incremental fetching, common mistakes, and error handling.

Best practices and recommended patterns for agents using the [Helius Rust SDK](https://github.com/helius-labs/helius-rust-sdk). For installation and getting started, see the [overview](/agents/rust-sdk).

## 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.

```rust theme={"system"}
// 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.

```rust theme={"system"}
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.

```rust theme={"system"}
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.

```rust theme={"system"}
// 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.

```rust theme={"system"}
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)

```rust theme={"system"}
// 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)

```rust theme={"system"}
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:

| 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

`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`.

```rust theme={"system"}
// 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-indexed** — `page: 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 mandatory** — `send_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:

```rust theme={"system"}
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:

```rust theme={"system"}
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 Variant       | HTTP Status | Action                         |
| ------------------- | ----------- | ------------------------------ |
| `Unauthorized`      | 401         | Check API key                  |
| `RateLimitExceeded` | 429         | Back off and retry             |
| `InternalError`     | 5xx         | Retry with exponential backoff |
| `BadRequest`        | 400         | Fix request parameters         |
| `NotFound`          | 404         | Check resource exists          |
| `Timeout`           | —           | Increase timeout or retry      |
