If you're writing high-performance Solana clients — backend trading bots, latency-sensitive infrastructure, or just code where Rust's correctness guarantees are valuable — you'll be using the Solana Rust SDK. The ecosystem is mature (Solana itself is written in Rust), the tooling is good, and for production code where every millisecond counts, Rust is often the right choice.
This piece covers building Solana clients in Rust: the libraries, the patterns, the gotchas, and when Rust is the right answer versus when TypeScript or Python serves you better.
The Solana Rust Ecosystem
Core crates:
solana-client. RPC client implementation. The Rust equivalent of web3.js's Connection.
solana-sdk. Core types — Pubkey, Keypair, Transaction, etc. Used everywhere.
solana-program. Used inside on-chain programs but parts are useful in clients.
anchor-client. For interacting with Anchor programs from Rust. Auto-generates client code from Anchor IDLs.
spl-token. SPL Token program client helpers.
For most clients, solana-client + solana-sdk covers the basics; add others as needed.
Cargo.toml Setup
[dependencies]
solana-client = "1.18"
solana-sdk = "1.18"
anyhow = "1"
tokio = { version = "1", features = ["full"] }
Adjust versions; the ecosystem moves. For latest, check crates.io.
Basic Client
use solana_client::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
let client = RpcClient::new_with_commitment(
"https://your-rpc-url.example/?api-key=...".to_string(),
CommitmentConfig::confirmed(),
);
let balance = client.get_balance(&pubkey)?;
println!("Balance: {} lamports", balance);
For async (which you'll usually want in production):
use solana_client::nonblocking::rpc_client::RpcClient;
let client = RpcClient::new_with_commitment(
"https://your-rpc-url.example/?api-key=...".to_string(),
CommitmentConfig::confirmed(),
);
let balance = client.get_balance(&pubkey).await?;
The async client is preferred for any code that does meaningful concurrent operations.
Sending Transactions
use solana_sdk::transaction::Transaction;
use solana_sdk::system_instruction;
use solana_client::rpc_config::RpcSendTransactionConfig;
let recent_blockhash = client.get_latest_blockhash().await?;
let ix = system_instruction::transfer(
&sender.pubkey(),
&recipient,
1_000_000,
);
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&sender.pubkey()),
&[&sender],
recent_blockhash,
);
let signature = client.send_transaction_with_config(
&tx,
RpcSendTransactionConfig {
skip_preflight: true,
max_retries: Some(0),
..Default::default()
},
).await?;
The patterns are similar to other languages but with Rust's verbosity for explicit error handling.
Why Rust for Solana Clients
When Rust is worth it:
Performance. Sub-millisecond signing, efficient transaction batch building. For HFT-adjacent workloads, the difference is real.
Memory safety. Long-running services without GC pauses. Bots that run 24/7 benefit.
Type safety at compile time. Catches whole categories of bugs before deployment.
Sharing code with on-chain programs. If you're writing both client and program, types can be shared.
Concurrency. Tokio + async Rust is excellent for high-concurrency network services.
When Rust is overkill:
Simple scripts and prototypes. TypeScript is faster to iterate.
dApp frontends. Browsers don't run Rust well (WASM is workable but adds complexity).
Quick analysis or research. Python or TypeScript will get you there faster.
For production bots where the build time and verbosity are acceptable, Rust is excellent.
Common Rust Solana Patterns
Concurrent reads:
use futures::future::join_all;
let pubkeys = vec![key1, key2, key3];
let results: Vec<_> = join_all(
pubkeys.iter().map(|k| client.get_account(k))
).await;
For better performance, prefer get_multiple_accounts for batched reads.
Anchor client integration:
use anchor_client::{Client, Cluster};
let client = Client::new_with_options(
Cluster::Custom(rpc_url, ws_url),
Rc::new(payer),
CommitmentConfig::confirmed(),
);
let program = client.program(program_id)?;
// Call an Anchor instruction
let tx_signature = program
.request()
.accounts(YourAccounts { ... })
.args(YourArgs { ... })
.send()?;
Cleaner than constructing instructions manually for Anchor programs.
Streaming with WebSocket:
use solana_client::nonblocking::pubsub_client::PubsubClient;
let pubsub = PubsubClient::new(ws_url).await?;
let (mut subscription, _unsub) = pubsub
.account_subscribe(&pubkey, None)
.await?;
while let Some(update) = subscription.next().await {
// Handle account update
}
Async streams are clean in Rust; the type system helps.
Performance Tips for Rust Solana Code
If performance is the reason you chose Rust:
Avoid allocations in hot paths. Pre-allocate buffers; reuse them.
Use get_multiple_accounts for batched reads. Same advice as elsewhere.
Pre-build transaction templates. Don't construct from scratch in the hot path.
Tune your tokio runtime. Default settings might not match your workload.
Profile before optimising. Use cargo flamegraph or similar. Most performance issues aren't where you think.
Watch your dependencies. Some crates pull in heavy dependencies; check what you're actually using.
Error Handling Patterns
Rust's explicit error handling is verbose but useful for production code:
use anyhow::{Context, Result};
async fn submit_swap(client: &RpcClient, tx: Transaction) -> Result<Signature> {
let signature = client
.send_transaction(&tx)
.await
.context("failed to submit swap")?;
let status = client
.confirm_transaction(&signature)
.await
.context("failed to confirm swap")?;
if !status {
anyhow::bail!("transaction not confirmed");
}
Ok(signature)
}
Each operation can fail; the ? operator + context gives you traceable errors.
Common Rust Solana Mistakes
Using sync client when async is needed. For high-volume code, sync blocks the runtime.
Not handling network errors. RPC calls fail; build retry logic.
Cloning Pubkeys unnecessarily. They're 32 bytes; cloning is cheap but visible in profiling. Pass by reference where possible.
Building Transaction objects inefficiently. Pre-build templates; fill in details in the hot path.
Ignoring tokio runtime tuning. Default behaviour might not match your workload.
Treating solana-sdk types as stable. They evolve; pin versions and test on upgrades.
What to Do This Week
If you're starting a Solana Rust project:
- Use the async
RpcClient. Sync only for one-off scripts. - Use
solana-sdkfor core types. Don't reimplement what's there. - Use Anchor's client crate if interacting with Anchor programs.
- Set up a tokio runtime explicitly. Don't rely on
#[tokio::main]defaults at production scale. - Pre-build transaction templates. Don't construct from scratch each time.
- Profile your hot paths.
cargo flamegraphis your friend. - Pin all dependencies. Solana's ecosystem moves; pin in production.
Try BoltTx From Rust
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_client::rpc_config::RpcSendTransactionConfig;
let client = RpcClient::new_with_commitment(
"https://bolttx.io/?api-key=YOUR_API_KEY".to_string(),
CommitmentConfig::processed(),
);
let signature = client.send_transaction_with_config(
&tx,
RpcSendTransactionConfig {
skip_preflight: true,
max_retries: Some(0),
..Default::default()
},
).await?;
Free tier signup. Native Rust experience for production trading bots and infrastructure.
FAQ
Should I use Rust for my Solana bot? For production HFT-style or high-volume bots, often yes. For moderate-frequency or research bots, TypeScript is faster to iterate.
Can I share code between Rust client and Solana program? Yes — types can be shared (often via a separate crate). Useful for ensuring client and program agree on data structures.
What's the difference between solana-client sync and async? Sync uses blocking HTTP; async uses tokio + reqwest. Async for production; sync for scripts.
Is anchor-client production-grade? Yes. The major Anchor ecosystem programs use it.
What's the build time like? First build is slow (cargo + Rust). Incremental builds are fast. Plan for cold-build CI time.