Skip to main content
The anchor-client crate provides a Rust client library for interacting with Anchor programs. It offers a type-safe, ergonomic API for building transactions, sending them to the cluster, and fetching program accounts.

Installation

Add anchor-client to your Cargo.toml:
[dependencies]
anchor-client = "0.32.1"
anchor-lang = "0.32.1"
The client is blocking by default. Enable the async feature for asynchronous operations.

Core Concepts

The Anchor Rust client is built around these key components:

Client

Configures cluster connection and payer for transactions

Program

Provides methods to interact with a specific on-chain program

RequestBuilder

Builder pattern for constructing and sending transactions

Client Setup

Create a Client to manage your cluster connection and transaction payer.

API Signature

impl<C: Deref<Target = impl Signer> + Clone> Client<C> {
    pub fn new(cluster: Cluster, payer: C) -> Self
    
    pub fn new_with_options(
        cluster: Cluster, 
        payer: C, 
        options: CommitmentConfig
    ) -> Self
    
    pub fn program(&self, program_id: Pubkey) -> Result<Program<C>, ClientError>
}

Basic Example

use anchor_client::{
    Client, Cluster,
    solana_sdk::{
        signature::{read_keypair_file, Keypair},
        signer::Signer,
    },
};
use std::rc::Rc;

// Read keypair from file
let payer = read_keypair_file("~/.config/solana/id.json")
    .expect("Failed to read keypair");

// Create client for localnet
let client = Client::new(Cluster::Localnet, Rc::new(payer));

// Or with custom commitment
let client = Client::new_with_options(
    Cluster::Devnet,
    Rc::new(payer),
    CommitmentConfig::confirmed(),
);

Using Dynamic Signers

If you need to work with Box<dyn Signer>, use the DynSigner wrapper:
use anchor_client::{Client, DynSigner};
use std::sync::Arc;

let signer: Box<dyn Signer> = /* ... */;
let payer = DynSigner(Arc::from(signer));
let client = Client::new(Cluster::Devnet, payer);

Cluster Configuration

The Cluster enum defines which Solana cluster to connect to:
pub enum Cluster {
    Localnet,   // http://127.0.0.1:8899
    Devnet,     // https://api.devnet.solana.com
    Testnet,    // https://api.testnet.solana.com
    Mainnet,    // https://api.mainnet-beta.solana.com
    Custom(String, String), // (http_url, ws_url)
}

Usage Examples

use anchor_client::Cluster;
use std::str::FromStr;

// Built-in clusters
let cluster = Cluster::Devnet;
let cluster = Cluster::Mainnet;

// Parse from string
let cluster = Cluster::from_str("devnet")?;
let cluster = Cluster::from_str("https://api.devnet.solana.com")?;

// Custom RPC endpoint
let cluster = Cluster::Custom(
    "https://my-rpc.com".to_string(),
    "wss://my-rpc.com".to_string(),
);

// Get URLs
println!("HTTP URL: {}", cluster.url());
println!("WebSocket URL: {}", cluster.ws_url());

Program Instance

Create a Program instance to interact with a specific on-chain program.

API

impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
    pub fn payer(&self) -> Pubkey
    pub fn id(&self) -> Pubkey
    pub fn request(&self) -> RequestBuilder<'_, C, Box<dyn Signer>>
    pub fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T>
    pub fn accounts<T: AccountDeserialize + Discriminator>(
        &self,
        filters: Vec<RpcFilterType>,
    ) -> Result<ProgramAccountsIterator<T>>
}

Creating a Program

use anchor_client::Client;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;

let client = Client::new(Cluster::Devnet, Rc::new(payer));

// Create program instance
let program_id = Pubkey::from_str("YourProgramID...").unwrap();
let program = client.program(program_id)?;

// Access program properties
println!("Program ID: {}", program.id());
println!("Payer: {}", program.payer());

Using declare_program!

The declare_program! macro generates type-safe client code from your program’s IDL.
1

Place IDL in idls/ directory

Create an idls/ folder in your client project root and place your program’s IDL JSON file there:
my-client/
├── idls/
│   └── my_program.json
├── src/
│   └── main.rs
└── Cargo.toml
2

Declare the program

Use the macro in your code:
use anchor_lang::prelude::*;

declare_program!(my_program);
3

Import generated modules

The macro generates client::accounts and client::args modules:
use my_program::{
    accounts::Counter,          // Account types
    client::accounts,           // Account structs for instructions  
    client::args,               // Argument structs for instructions
};

Generated Modules

The declare_program! macro generates:
  • client::accounts - Structs matching instruction account requirements
  • client::args - Structs for instruction arguments
  • Account types - Deserializable account data structures
  • ID - The program’s public key constant

Building Transactions

Use the RequestBuilder to construct transactions with the builder pattern.

RequestBuilder API

impl<C, S> RequestBuilder<'_, C, S> {
    pub fn accounts(self, accounts: impl ToAccountMetas) -> Self
    pub fn args(self, args: impl InstructionData) -> Self
    pub fn signer(self, signer: &S) -> Self
    pub fn instruction(self, ix: Instruction) -> Self
    pub fn instructions(&self) -> Vec<Instruction>
    pub fn transaction(&self) -> Transaction
    pub fn send(self) -> Result<Signature>
    pub fn signed_transaction(self) -> Result<Transaction>
}

Single Instruction

use anchor_client::solana_sdk::system_program;

declare_program!(counter_program);
use counter_program::client::{accounts, args};

let counter_keypair = Keypair::new();

// Build and send transaction
let signature = program
    .request()
    .accounts(accounts::Initialize {
        counter: counter_keypair.pubkey(),
        payer: program.payer(),
        system_program: system_program::ID,
    })
    .args(args::Initialize)
    .signer(&counter_keypair)
    .send()
    .await?;

println!("Transaction signature: {}", signature);

Multiple Instructions

Combine multiple instructions into a single transaction:
// Build individual instructions
let init_ix = program
    .request()
    .accounts(accounts::Initialize { /* ... */ })
    .args(args::Initialize)
    .instructions()?
    .remove(0);

let increment_ix = program
    .request()
    .accounts(accounts::Increment { /* ... */ })
    .args(args::Increment)
    .instructions()?
    .remove(0);

// Combine and send
let signature = program
    .request()
    .instruction(init_ix)
    .instruction(increment_ix)
    .signer(&counter_keypair)
    .send()
    .await?;

Remaining Accounts

For instructions that accept remaining accounts:
use solana_instruction::AccountMeta;

let remaining = vec![
    AccountMeta {
        pubkey: additional_account,
        is_signer: false,
        is_writable: true,
    },
];

let signature = program
    .request()
    .accounts(accounts::MyInstruction { /* ... */ })
    .accounts(remaining)  // Add remaining accounts
    .args(args::MyInstruction { /* ... */ })
    .send()
    .await?;

Fetching Accounts

Fetch and deserialize program accounts using type-safe methods.

Single Account

use my_program::accounts::Counter;

// Fetch and deserialize account
let counter: Counter = program.account(counter_address).await?;
println!("Count: {}", counter.count);

Multiple Accounts with Filters

use anchor_client::solana_rpc_client_api::filter::{Memcmp, RpcFilterType};

// Filter accounts where bytes 8-16 match a specific value
let filters = vec![
    RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
        8,  // offset (skip 8-byte discriminator)
        &expected_value,
    )),
];

let accounts_iter = program.accounts::<Counter>(filters).await?;

// Iterate over matching accounts
for result in accounts_iter {
    let (pubkey, account) = result?;
    println!("Account {}: count = {}", pubkey, account.count);
}

Event Listeners

Subscribe to program events emitted via logs:
use my_program::events::CounterUpdated;

let unsubscriber = program
    .on::<CounterUpdated>(|ctx, event| {
        println!("Event received!");
        println!("  Slot: {}", ctx.slot);
        println!("  Signature: {}", ctx.signature);
        println!("  Counter: {}", event.count);
    })
    .await?;

// Later, unsubscribe
unsubscriber.unsubscribe().await;

Complete Example

Here’s a full example demonstrating the Rust client in action:
use anchor_client::{
    solana_client::rpc_client::RpcClient,
    solana_sdk::{
        commitment_config::CommitmentConfig,
        native_token::LAMPORTS_PER_SOL,
        signature::Keypair,
        system_program,
    },
    solana_signer::Signer,
    Client, Cluster,
};
use anchor_lang::prelude::*;
use std::rc::Rc;

// Generate client code from IDL
declare_program!(counter);
use counter::{accounts::Counter, client::accounts, client::args};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Setup
    let connection = RpcClient::new_with_commitment(
        "http://127.0.0.1:8899",
        CommitmentConfig::confirmed(),
    );

    let payer = Keypair::new();
    let counter_kp = Keypair::new();

    // Airdrop SOL
    println!("Requesting airdrop...");
    let sig = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
    loop {
        if connection.confirm_transaction(&sig)? {
            break;
        }
        std::thread::sleep(std::time::Duration::from_millis(100));
    }
    println!("Airdrop confirmed");

    // Create client and program
    let client = Client::new_with_options(
        Cluster::Localnet,
        Rc::new(payer),
        CommitmentConfig::confirmed(),
    );
    let program = client.program(counter::ID)?;

    // Initialize counter
    println!("\nInitializing counter...");
    let init_sig = program
        .request()
        .accounts(accounts::Initialize {
            counter: counter_kp.pubkey(),
            payer: program.payer(),
            system_program: system_program::ID,
        })
        .args(args::Initialize)
        .signer(&counter_kp)
        .send()
        .await?;
    println!("Initialize tx: {}", init_sig);

    // Increment counter
    println!("\nIncrementing counter...");
    let increment_sig = program
        .request()
        .accounts(accounts::Increment {
            counter: counter_kp.pubkey(),
        })
        .args(args::Increment)
        .send()
        .await?;
    println!("Increment tx: {}", increment_sig);

    // Fetch account
    println!("\nFetching counter account...");
    let counter_account: Counter = program
        .account(counter_kp.pubkey())
        .await?;
    println!("Counter value: {}", counter_account.count);

    Ok(())
}

Error Handling

The client uses the ClientError enum for error handling:
use anchor_client::ClientError;

match program.account::<Counter>(address).await {
    Ok(account) => println!("Count: {}", account.count),
    Err(ClientError::AccountNotFound) => {
        println!("Account doesn't exist");
    }
    Err(ClientError::AnchorError(e)) => {
        println!("Anchor error: {:?}", e);
    }
    Err(ClientError::SolanaClientError(e)) => {
        println!("RPC error: {:?}", e);
    }
    Err(e) => println!("Error: {:?}", e),
}

Features

The anchor-client crate supports optional features:

async

Enables asynchronous client operations:
anchor-client = { version = "0.32.1", features = ["async"] }
With async enabled, all I/O operations return Futures and require an async runtime like Tokio.

mock

Allows passing custom RPC clients for testing:
anchor-client = { version = "0.32.1", features = ["mock"] }
Useful for mocking RPC responses in tests using RpcClient::new_mock.

API Reference

Client

MethodDescription
new(cluster, payer)Create new client
new_with_options(cluster, payer, options)Create client with custom commitment
program(program_id)Create program instance

Program

MethodDescription
request()Create new request builder
account<T>(address)Fetch and deserialize account
accounts<T>(filters)Fetch multiple accounts with filters
payer()Get payer public key
id()Get program ID

RequestBuilder

MethodDescription
accounts(accounts)Set instruction accounts
args(args)Set instruction arguments
signer(signer)Add signer
instruction(ix)Add instruction
send()Build, sign, and send transaction
transaction()Build transaction without sending
instructions()Get list of instructions

Next Steps

TypeScript Client

Learn about the TypeScript client library

Testing Programs

Write tests for your Anchor programs

Build docs developers (and LLMs) love