Clients and IDL
Learn how to interact with Anchor programs using client libraries in TypeScript and Rust.
TypeScript
Rust
Dependency-free composability
declare_program macro to interact with programs without additional dependencies.TypeScript
Learn how to use Anchor's TypeScript client library to interact with Solana programs.
Anchor provides a TypeScript client library, @anchor-lang/core, that simplifies interacting with Solana programs from JavaScript or TypeScript clients.
Important (@solana/web3.js v1 only)
@anchor-lang/core is only compatible with the legacy version (v1) of @solana/web3.js and
@solana/spl-token. It is not compatible with the new (v2) version of @solana/web3.js.
Client program#
Interacting with an Anchor program through @anchor-lang/core requires a Program instance built from the program’s IDL file.
A Program is constructed from the IDL and an AnchorProvider, which bundles a Connection to a Solana cluster (localhost, devnet, mainnet) with an optional default Wallet used to pay for and sign transactions.
A frontend integration with the Solana wallet adapter wires the AnchorProvider and Program together:
import { Program, AnchorProvider, setProvider } from '@anchor-lang/core'import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react'import type { HelloAnchor } from './idlType'import idl from './idl.json'
const { connection } = useConnection()const wallet = useAnchorWallet()
const provider = new AnchorProvider(connection, wallet, {})setProvider(provider)
export const program = new Program(idl as HelloAnchor, { connection,})The snippet imports two files Anchor generates at anchor build: idl.json (written to /target/idl/<program-name>.json) and idlType.ts (the corresponding TypeScript type definition, written to /target/types/<program-name>.ts).
A Program can also be created from the IDL and a Connection alone. There is no default Wallet, so the instance can fetch accounts and build instructions without a connected wallet:
import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'import { Program } from '@anchor-lang/core'import type { HelloAnchor } from './idlType'import idl from './idl.json'
const connection = new Connection(clusterApiUrl('devnet'), 'confirmed')
export const program = new Program(idl as HelloAnchor, { connection,})A new Anchor project ships with a default test file that already includes a Program instance. Applications outside the Anchor workspace, such as React or Node.js apps, must construct the Program manually:
import * as anchor from '@anchor-lang/core'import { Program } from '@anchor-lang/core'import { HelloAnchor } from '../target/types/hello_anchor'
describe('hello_anchor', () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.HelloAnchor as Program<HelloAnchor>
it('Is initialized!', async () => { // Add your test here. const tx = await program.methods.initialize().rpc() console.log('Your transaction signature', tx) })})Invoke instructions#
Once the Program is set up, Anchor’s MethodsBuilder builds and sends instructions. The builder supports building individual instructions, building transactions, and building and sending transactions in one call.
The basic chain looks like this:
program.methods is the builder API for creating instruction calls from the program’s IDL:
await program.methods.instructionName(instructionData).accounts({}).signers([]).rpc()After .methods, name an instruction from the IDL and pass any required arguments as comma-separated values:
await program.methods.instructionName(instructionData).accounts({}).signers([]).rpc().accounts() takes the addresses of the accounts the instruction requires, as specified in the IDL:
await program.methods.instructionName(instructionData).accounts({}).signers([]).rpc()Some account addresses do not need to be supplied explicitly because Anchor resolves them from the IDL. These typically include common accounts such as the System Program, and accounts whose address is a Program Derived Address (PDA).
.signers([]) optionally takes an array of keypairs that must sign the instruction in addition to the provider wallet. This is most often used when creating a new account whose address is the public key of a freshly generated keypair:
await program.methods.instructionName(instructionData).accounts({}).signers([]).rpc()Note
.signers([]) should only be used with .rpc(). When using .transaction() or .instruction(), signers should be added to the transaction before sending.
Anchor offers three terminal methods for completing the builder chain:
.rpc() sends a signed transaction with the specified instruction and returns a TransactionSignature. The Wallet from the Provider is automatically included as a signer:
// Generate keypair for the new accountconst newAccountKp = new Keypair()
const data = new BN(42)const transactionSignature = await program.methods .initialize(data) .accounts({ newAccount: newAccountKp.publicKey, signer: wallet.publicKey, systemProgram: SystemProgram.programId, }) .signers([newAccountKp]) .rpc().transaction() builds a Transaction containing the specified instruction without sending it:
// Generate keypair for the new accountconst newAccountKp = new Keypair()
const data = new BN(42)const transaction = await program.methods .initialize(data) .accounts({ newAccount: newAccountKp.publicKey, signer: wallet.publicKey, systemProgram: SystemProgram.programId, }) .transaction()
const transactionSignature = await connection.sendTransaction(transaction, [ wallet.payer, newAccountKp,]).instruction() builds a TransactionInstruction, useful when manually composing a transaction from multiple instructions:
// Generate keypair for the new accountconst newAccountKp = new Keypair()
const data = new BN(42)const instruction = await program.methods .initialize(data) .accounts({ newAccount: newAccountKp.publicKey, signer: wallet.publicKey, systemProgram: SystemProgram.programId, }) .instruction()
const transaction = new Transaction().add(instruction)
const transactionSignature = await connection.sendTransaction(transaction, [ wallet.payer, newAccountKp,])Fetch accounts#
The Program client also fetches and deserializes accounts created by the program. Use program.account followed by the name of the account type defined in the IDL.
program.account.newAccount.all() fetches every existing account of a given type:
const accounts = await program.account.newAccount.all()memcmp (memory compare) filters for account data that matches a specific value at a specific offset. Using memcmp requires knowing the byte layout of the account’s data field.
The first 8 bytes of any Anchor account are reserved for the discriminator, so offsets into the account’s own fields start at 8:
const accounts = await program.account.newAccount.all([ { memcmp: { offset: 8, bytes: '', }, },])program.account.newAccount.fetch() fetches and deserializes a single account by address:
const account = await program.account.newAccount.fetch(ACCOUNT_ADDRESS)program.account.newAccount.fetchMultiple() fetches and deserializes multiple accounts at once, taking an array of addresses:
const accounts = await program.account.newAccount.fetchMultiple([ ACCOUNT_ADDRESS_ONE, ACCOUNT_ADDRESS_TWO,])Example#
The example below uses @anchor-lang/core to interact with a simple Anchor program. The program exposes an initialize instruction that creates and initializes a counter account, and an increment instruction that increments the stored value.
use anchor_lang::prelude::*;
declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC");
#[program]pub mod example { use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { let counter = &ctx.accounts.counter; msg!("Counter account created! Current count: {}", counter.count); Ok(()) }
pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; msg!("Previous counter: {}", counter.count);
counter.count += 1; msg!("Counter incremented! Current count: {}", counter.count); Ok(()) }}
#[derive(Accounts)]pub struct Initialize<'info> { #[account(mut)] pub payer: Signer<'info>,
#[account( init, payer = payer, space = 8 + 8 )] pub counter: Account<'info, Counter>, pub system_program: Program<'info, System>,}
#[derive(Accounts)]pub struct Increment<'info> { #[account(mut)] pub counter: Account<'info, Counter>,}
#[account]pub struct Counter { pub count: u64,}A typical folder layout for a TypeScript client that interacts with the program looks like this:
ts/
idl/
- example.json
- example.ts
- example.ts
- package.json
The /idl directory contains the IDL itself in example.json and a generated TypeScript type definition in example.ts. Both files are reproduced below for reference:
{ "address": "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC", "metadata": { "name": "example", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor" }, "instructions": [ { "name": "increment", "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], "accounts": [ { "name": "counter", "writable": true } ], "args": [] }, { "name": "initialize", "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], "accounts": [ { "name": "payer", "writable": true, "signer": true }, { "name": "counter", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" } ], "args": [] } ], "accounts": [ { "name": "Counter", "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] } ], "types": [ { "name": "Counter", "type": { "kind": "struct", "fields": [ { "name": "count", "type": "u64" } ] } } ]}/** * Program IDL in camelCase format in order to be used in JS/TS. * * Note that this is only a type helper and is not the actual IDL. The original * IDL can be found at `target/idl/example.json`. */export type Example = { address: '6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC' metadata: { name: 'example' version: '0.1.0' spec: '0.1.0' description: 'Created with Anchor' } instructions: [ { name: 'increment' discriminator: [11, 18, 104, 9, 104, 174, 59, 33] accounts: [ { name: 'counter' writable: true }, ] args: [] }, { name: 'initialize' discriminator: [175, 175, 109, 31, 13, 152, 155, 237] accounts: [ { name: 'payer' writable: true signer: true }, { name: 'counter' writable: true signer: true }, { name: 'systemProgram' address: '11111111111111111111111111111111' }, ] args: [] }, ] accounts: [ { name: 'counter' discriminator: [255, 176, 4, 245, 188, 253, 124, 25] }, ] types: [ { name: 'counter' type: { kind: 'struct' fields: [ { name: 'count' type: 'u64' }, ] } }, ]}Note
Running anchor build in an Anchor project regenerates both files automatically: the IDL file at target/idl/<program-name>.json (e.g., target/idl/example.json) and the TypeScript type definitions at target/types/<program-name>.ts (e.g., target/types/example.ts).
The script that drives the program follows:
import { Connection, Keypair, LAMPORTS_PER_SOL, Transaction, sendAndConfirmTransaction,} from '@solana/web3.js'import { Program } from '@anchor-lang/core'import type { Example } from './idl/example.ts'import idl from './idl/example.json'
// Set up a connection to the clusterconst connection = new Connection('http://127.0.0.1:8899', 'confirmed')
// Create a Program instance using the IDL and connectionconst program = new Program(idl as Example, { connection,})
// Generate new Keypairs for the payer and the counter accountconst payer = Keypair.generate()const counter = Keypair.generate()
// Airdrop SOL to fund the payer's account for transaction feesconst airdropTransactionSignature = await connection.requestAirdrop( payer.publicKey, LAMPORTS_PER_SOL,)await connection.confirmTransaction(airdropTransactionSignature)
// Build the initialize instructionconst initializeInstruction = await program.methods .initialize() .accounts({ payer: payer.publicKey, counter: counter.publicKey, }) .instruction()
// Build the increment instructionconst incrementInstruction = await program.methods .increment() .accounts({ counter: counter.publicKey, }) .instruction()
// Add both instructions to a single transactionconst transaction = new Transaction().add(initializeInstruction, incrementInstruction)
// Send the transactionconst transactionSignature = await sendAndConfirmTransaction(connection, transaction, [ payer, counter,])console.log('Transaction Signature', transactionSignature)
// Fetch the counter accountconst counterAccount = await program.account.counter.fetch(counter.publicKey)console.log('Count:', counterAccount.count)Rust
Learn how to use Anchor's Rust client library to interact with Solana programs.
The anchor-client crate is the Rust client library for interacting with Anchor programs.
Example#
The following example uses the anchor-client crate to interact with a simple Anchor program. The declare_program! macro generates dependency-free client modules from the program’s IDL, exposing typed builders for the program’s instructions and accounts.
The program below exposes two instructions: initialize creates and initializes a counter account that stores a value, and increment increments the stored value.
use anchor_lang::prelude::*;
declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC");
#[program]pub mod example { use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { let counter = &ctx.accounts.counter; msg!("Counter account created! Current count: {}", counter.count); Ok(()) }
pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; msg!("Previous counter: {}", counter.count);
counter.count += 1; msg!("Counter incremented! Current count: {}", counter.count); Ok(()) }}
#[derive(Accounts)]pub struct Initialize<'info> { #[account(mut)] pub payer: Signer<'info>,
#[account( init, payer = payer, space = 8 + 8 )] pub counter: Account<'info, Counter>, pub system_program: Program<'info, System>,}
#[derive(Accounts)]pub struct Increment<'info> { #[account(mut)] pub counter: Account<'info, Counter>,}
#[account]pub struct Counter { pub count: u64,}An example folder structure for a Rust client:
client-example/
idls/
- example.json
src/
- main.rs
- target/
- Cargo.toml
The program IDL must live in an idls/ folder. The declare_program! macro reads it from there to generate the client modules:
{ "address": "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC", "metadata": { "name": "example", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor" }, "instructions": [ { "name": "increment", "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], "accounts": [ { "name": "counter", "writable": true } ], "args": [] },21 collapsed lines
{ "name": "initialize", "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], "accounts": [ { "name": "payer", "writable": true, "signer": true }, { "name": "counter", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" } ], "args": [] } ], "accounts": [ { "name": "Counter", "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] } ],14 collapsed lines
"types": [ { "name": "Counter", "type": { "kind": "struct", "fields": [ { "name": "count", "type": "u64" } ] } } ]}The src/main.rs file drives the program. Two pieces do the work:
- The
declare_program!macro generates client modules for the program from the IDL. - The
anchor-clientcrate provides utilities to build instructions, send transactions, and fetch program accounts.
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;
declare_program!(example);use example::{accounts::Counter, client::accounts, client::args};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", // Local validator URL CommitmentConfig::confirmed(), );
// Generate Keypairs and request airdrop let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
// Wait for airdrop confirmation while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
// Create program client let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
// Build and send instructions println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}The Cargo.toml dependencies:
[package]name = "client-example"version = "0.1.0"edition = "2021"
[dependencies]anchor-client = { version = "1.0.1", features = ["async"] }anchor-lang = "1.0.1"anyhow = "1.0.93"tokio = { version = "1.0", features = ["full"] }Dependency-free composability
Use Anchor's declare_program macro to interact with programs without additional dependencies.
The declare_program!() macro generates Rust modules from a program’s IDL, letting on-chain and off-chain code interact with an Anchor program without depending on its source crate. A reference example lives in the Anchor repository.
The macro generates the following modules:
Examples#
The following examples demonstrate two scenarios for declare_program!():
- Making cross program invocations (CPIs) from one program to another.
- Building client-side transactions to invoke a program’s instructions.
Both scenarios show how the generated modules simplify program interactions, whether the calling code is on-chain or off-chain.
On-chain CPI#
The macro reads its IDL from a directory named idls/ placed anywhere in the project. A typical layout for a CPI between two programs:
idls/
- example.json
programs/
example-cpi/
src/
- lib.rs
- Cargo.toml
The callee program defines the instructions the caller will invoke. Its IDL (produced by anchor build) is the input to declare_program!() in the caller:
use anchor_lang::prelude::*;
declare_id!("8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8");
#[program]pub mod example { use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { let counter = &ctx.accounts.counter; msg!("Counter account created! Current count: {}", counter.count); Ok(()) }
pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; msg!("Previous counter: {}", counter.count);
counter.count += 1; msg!("Counter incremented! Current count: {}", counter.count); Ok(()) }}
#[derive(Accounts)]pub struct Initialize<'info> { #[account(mut)] pub payer: Signer<'info>,
#[account( init, payer = payer, space = 8 + 8 )] pub counter: Account<'info, Counter>, pub system_program: Program<'info, System>,}
#[derive(Accounts)]pub struct Increment<'info> { #[account(mut)] pub counter: Account<'info, Counter>,}
#[account]pub struct Counter { pub count: u64,}{ "address": "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8", "metadata": { "name": "example", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor" },38 collapsed lines
"instructions": [ { "name": "increment", "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], "accounts": [ { "name": "counter", "writable": true } ], "args": [] }, { "name": "initialize", "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], "accounts": [ { "name": "payer", "writable": true, "signer": true }, { "name": "counter", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" } ], "args": [] } ], "accounts": [ { "name": "Counter", "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] } ],10 collapsed lines
"types": [ { "name": "Counter", "type": { "kind": "struct", "fields": [ { "name": "count", "type": "u64" } ] } } ]}The caller program imports the generated cpi, accounts, and program modules and uses them to invoke the callee’s instructions:
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
declare_program!(example);use example::{ accounts::Counter, // Account types cpi::{ // Cross program invocation helpers self, accounts::{Increment, Initialize}, }, program::Example, // Program type};
#[program]pub mod example_cpi {
use super::*;
pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Initialize { payer: ctx.accounts.payer.to_account_info(), counter: ctx.accounts.counter.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, );
cpi::initialize(cpi_ctx)?; Ok(()) }
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Increment { counter: ctx.accounts.counter.to_account_info(), }, );
cpi::increment(cpi_ctx)?; Ok(()) }}
#[derive(Accounts)]pub struct InitializeCpi<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] pub counter: Signer<'info>, pub system_program: Program<'info, System>, pub example_program: Program<'info, Example>,}
#[derive(Accounts)]pub struct IncrementCpi<'info> { // Counter type from accounts module #[account(mut)] pub counter: Account<'info, Counter>,
// Example type from program module pub example_program: Program<'info, Example>,}import * as anchor from '@anchor-lang/core'import { Program } from '@anchor-lang/core'import { Example } from '../target/types/example'import { ExampleCpi } from '../target/types/example_cpi'import { Keypair } from '@solana/web3.js'
describe('example', () => { anchor.setProvider(anchor.AnchorProvider.env())
const program = anchor.workspace.Example as Program<Example> const cpiProgram = anchor.workspace.ExampleCpi as Program<ExampleCpi>
const counterAccount = Keypair.generate()
it('Is initialized!', async () => { const transactionSignature = await cpiProgram.methods .initializeCpi() .accounts({ counter: counterAccount.publicKey, }) .signers([counterAccount]) .rpc({ skipPreflight: true })
const accountData = await program.account.counter.fetch(counterAccount.publicKey)
console.log(`Transaction Signature: ${transactionSignature}`) console.log(`Count: ${accountData.count}`) })
it('Increment', async () => { const transactionSignature = await cpiProgram.methods .incrementCpi() .accounts({ counter: counterAccount.publicKey, }) .rpc()
const accountData = await program.account.counter.fetch(counterAccount.publicKey)
console.log(`Transaction Signature: ${transactionSignature}`) console.log(`Count: ${accountData.count}`) })})Explanation#
declare_program!() takes the IDL filename (without extension) as its single argument:
4 collapsed lines
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
declare_program!(example);use example::{59 collapsed lines
accounts::Counter, // Account types cpi::{ // Cross program invocation helpers self, accounts::{Increment, Initialize}, }, program::Example, // Program type};
#[program]pub mod example_cpi {
use super::*;
pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Initialize { payer: ctx.accounts.payer.to_account_info(), counter: ctx.accounts.counter.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, );
cpi::initialize(cpi_ctx)?; Ok(()) }
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Increment { counter: ctx.accounts.counter.to_account_info(), }, );
cpi::increment(cpi_ctx)?; Ok(()) }}
#[derive(Accounts)]pub struct InitializeCpi<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] pub counter: Signer<'info>, pub system_program: Program<'info, System>, pub example_program: Program<'info, Example>,}
#[derive(Accounts)]pub struct IncrementCpi<'info> { // Counter type from accounts module #[account(mut)] pub counter: Account<'info, Counter>,
// Example type from program module pub example_program: Program<'info, Example>,}Bring the generated modules into scope:
4 collapsed lines
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
declare_program!(example);use example::{ accounts::Counter, // Account types cpi::{ // Cross program invocation helpers self, accounts::{Increment, Initialize}, }, program::Example, // Program type};52 collapsed lines
#[program]pub mod example_cpi {
use super::*;
pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Initialize { payer: ctx.accounts.payer.to_account_info(), counter: ctx.accounts.counter.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, );
cpi::initialize(cpi_ctx)?; Ok(()) }
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Increment { counter: ctx.accounts.counter.to_account_info(), }, );
cpi::increment(cpi_ctx)?; Ok(()) }}
#[derive(Accounts)]pub struct InitializeCpi<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] pub counter: Signer<'info>, pub system_program: Program<'info, System>, pub example_program: Program<'info, Example>,}
#[derive(Accounts)]pub struct IncrementCpi<'info> { // Counter type from accounts module #[account(mut)] pub counter: Account<'info, Counter>,
// Example type from program module pub example_program: Program<'info, Example>,}Use the imported types in the account validation struct:
56 collapsed lines
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
declare_program!(example);use example::{ accounts::Counter, // Account types cpi::{ // Cross program invocation helpers self, accounts::{Increment, Initialize}, }, program::Example, // Program type};
#[program]pub mod example_cpi {
use super::*;
pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Initialize { payer: ctx.accounts.payer.to_account_info(), counter: ctx.accounts.counter.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, );
cpi::initialize(cpi_ctx)?; Ok(()) }
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Increment { counter: ctx.accounts.counter.to_account_info(), }, );
cpi::increment(cpi_ctx)?; Ok(()) }}
#[derive(Accounts)]pub struct InitializeCpi<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] pub counter: Signer<'info>, pub system_program: Program<'info, System>, pub example_program: Program<'info, Example>,}
#[derive(Accounts)]pub struct IncrementCpi<'info> { // Counter type from accounts module #[account(mut)] pub counter: Account<'info, Counter>,
// Example type from program module pub example_program: Program<'info, Example>,}Use the cpi module to invoke the callee’s instructions:
19 collapsed lines
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
declare_program!(example);use example::{ accounts::Counter, // Account types cpi::{ // Cross program invocation helpers self, accounts::{Increment, Initialize}, }, program::Example, // Program type};
#[program]pub mod example_cpi {
use super::*;
pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Initialize { payer: ctx.accounts.payer.to_account_info(), counter: ctx.accounts.counter.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, );
cpi::initialize(cpi_ctx)?; Ok(()) }33 collapsed lines
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Increment { counter: ctx.accounts.counter.to_account_info(), }, );
cpi::increment(cpi_ctx)?; Ok(()) }}
#[derive(Accounts)]pub struct InitializeCpi<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] pub counter: Signer<'info>, pub system_program: Program<'info, System>, pub example_program: Program<'info, Example>,}
#[derive(Accounts)]pub struct IncrementCpi<'info> { // Counter type from accounts module #[account(mut)] pub counter: Account<'info, Counter>,
// Example type from program module pub example_program: Program<'info, Example>,}19 collapsed lines
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
declare_program!(example);use example::{ accounts::Counter, // Account types cpi::{ // Cross program invocation helpers self, accounts::{Increment, Initialize}, }, program::Example, // Program type};
#[program]pub mod example_cpi {
use super::*;
pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Initialize { payer: ctx.accounts.payer.to_account_info(), counter: ctx.accounts.counter.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, );
cpi::initialize(cpi_ctx)?; Ok(()) }
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.key(), Increment { counter: ctx.accounts.counter.to_account_info(), }, );
cpi::increment(cpi_ctx)?; Ok(()) }21 collapsed lines
}
#[derive(Accounts)]pub struct InitializeCpi<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] pub counter: Signer<'info>, pub system_program: Program<'info, System>, pub example_program: Program<'info, Example>,}
#[derive(Accounts)]pub struct IncrementCpi<'info> { // Counter type from accounts module #[account(mut)] pub counter: Account<'info, Counter>,
// Example type from program module pub example_program: Program<'info, Example>,}Off-chain client#
A Rust client can use the same macro to drive a program from outside the chain. A typical layout:
idls/
- example.json
src/
- main.rs
- Cargo.toml
The callee program and its IDL are the same shape as before:
use anchor_lang::prelude::*;
declare_id!("8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8");
#[program]pub mod example { use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { let counter = &ctx.accounts.counter; msg!("Counter account created! Current count: {}", counter.count); Ok(()) }
pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; msg!("Previous counter: {}", counter.count);
counter.count += 1; msg!("Counter incremented! Current count: {}", counter.count); Ok(()) }}
#[derive(Accounts)]pub struct Initialize<'info> { #[account(mut)] pub payer: Signer<'info>,
#[account( init, payer = payer, space = 8 + 8 )] pub counter: Account<'info, Counter>, pub system_program: Program<'info, System>,}
#[derive(Accounts)]pub struct Increment<'info> { #[account(mut)] pub counter: Account<'info, Counter>,}
#[account]pub struct Counter { pub count: u64,}{ "address": "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8", "metadata": { "name": "example", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor" },38 collapsed lines
"instructions": [ { "name": "increment", "discriminator": [11, 18, 104, 9, 104, 174, 59, 33], "accounts": [ { "name": "counter", "writable": true } ], "args": [] }, { "name": "initialize", "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], "accounts": [ { "name": "payer", "writable": true, "signer": true }, { "name": "counter", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" } ], "args": [] } ], "accounts": [ { "name": "Counter", "discriminator": [255, 176, 4, 245, 188, 253, 124, 25] } ],10 collapsed lines
"types": [ { "name": "Counter", "type": { "kind": "struct", "fields": [ { "name": "count", "type": "u64" } ] } } ]}The client script uses the generated client module to build instructions and the accounts module to deserialize fetched account data:
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;
declare_program!(example);use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}[package]name = "rs"version = "0.1.0"edition = "2021"
[dependencies]anchor-client = { version = "1.0.1", features = ["async"] }anchor-lang = "1.0.1"anyhow = "1.0.93"tokio = { version = "1.0", features = ["full"] }declare_program!() takes the IDL filename (without extension) as its single argument:
12 collapsed lines
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;
declare_program!(example);69 collapsed lines
use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}Bring the generated modules into scope:
13 collapsed lines
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;
declare_program!(example);use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};64 collapsed lines
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}Use the client module to build the program’s instructions:
48 collapsed lines
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;
declare_program!(example);use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);24 collapsed lines
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}48 collapsed lines
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;
declare_program!(example);use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);15 collapsed lines
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}Add the instructions to a transaction and send it:
68 collapsed lines
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;
declare_program!(example);use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?;7 collapsed lines
println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?; println!(" Counter value: {}", counter_account.count); Ok(())}Use the accounts module to fetch and deserialize the program’s account data:
78 collapsed lines
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;
declare_program!(example);use example::{ accounts::Counter, // Program account types client::accounts, // Accounts for program instructions client::args, // Arguments for program instructions};
#[tokio::main]async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), );
let payer = Keypair::new(); let counter = Keypair::new(); println!("Generated Keypairs:"); println!(" Payer: {}", payer.pubkey()); println!(" Counter: {}", counter.pubkey());
println!("\nRequesting 1 SOL airdrop to payer"); let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
while !connection.confirm_transaction(&airdrop_signature)? { std::thread::sleep(std::time::Duration::from_millis(100)); } println!(" Airdrop confirmed!");
let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?;
println!("\nSend transaction with initialize and increment instructions"); let initialize_ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0);
let increment_ix = program .request() .accounts(accounts::Increment { counter: counter.pubkey(), }) .args(args::Increment) .instructions()? .remove(0);
let signature = program .request() .instruction(initialize_ix) .instruction(increment_ix) .signer(&counter) .send() .await?; println!(" Transaction confirmed: {}", signature);
println!("\nFetch counter account data"); let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?;3 collapsed lines
println!(" Counter value: {}", counter_account.count); Ok(())}