Clients and IDL
Build instructions from generated Rust account structs and the alpha IDL/TypeScript surface.
A client is the code that builds instructions for your program. It picks an instruction, serializes its arguments, orders the accounts, then signs and sends the transaction to a cluster or test VM.
Anchor clients are built around the IDL and the generated helper structs that the program crate emits. The Rust path is the most complete in alpha because it has typed instruction structs, account-meta builders, resolved account helpers, and LiteSVM test integration.
TypeScript support exists through the current IDL and the @anchor-lang/core scaffold. The stable v2 npm surface is not published yet, so use TypeScript for integration and inspection while the package settles, and rely on Rust tests for exact instruction construction.
For a new v2 program, start with Rust tests. Once the transaction shape is proven there, use the generated IDL and TypeScript type output for app integration.
Rust
Use InstructionData, ToAccountMetas, full accounts structs, and Resolved helpers in tests and Rust clients.
TypeScript
Understand the current TypeScript package status and how to use generated IDLs.
What to use today#
Rust
Build instructions in Rust with generated instruction, account, and resolved account structs.
Rust is the primary client and test surface while the TypeScript package is still alpha. An Anchor program crate generates typed modules that Rust tests and other Rust clients can use directly, without reading an IDL at runtime.
The generated Rust surface is useful even when you do not plan to ship a Rust client. It gives tests a strongly typed way to build the exact instruction bytes and account metas the program expects.
In the default LiteSVM scaffold, the Rust client is an integration test next to the program crate:
programs/
counter/
tests/
- test_initialize.rs Builds and sends instructions
src/
- lib.rs Program crate that emits generated modules
The generated modules mirror the shape Anchor users expect:
program::instruction::*structs encode instruction discriminators and arguments.program::accounts::*structs build full account metas.program::accounts::*Resolvedstructs fill in derivable PDAs and well-known programs when possible.- PDA helper methods such as
find_counter_address()derive addresses from the same seed metadata used by validation.
Generated instruction data#
Every handler gets an instruction struct under program::instruction. The struct implements InstructionData, so calling data() returns the discriminator plus serialized arguments:
use anchor_lang_v2::InstructionData;
let data = vault::instruction::Deposit { amount: 1_000_000 }.data();By default, instruction discriminators are the first 8 bytes of sha256("global:" + handler_name). Programs that opt into compact byte discriminators with #[discrim = N] still use the same InstructionData trait.
Account builders#
Full account structs include every account the instruction expects. This is the most explicit form and works for every account shape:
use { anchor_lang_v2::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, anchor_lang_v2::programs::System,};
let ix = Instruction::new_with_bytes( counter::id(), &counter::instruction::Initialize {}.data(), counter::accounts::Initialize { payer: payer.pubkey(), counter: counter.pubkey(), system_program: System::id(), } .to_account_metas(None),);Use this form when every address is already known, when an account cannot be derived from IDL seed metadata, or when a test should be explicit about the exact transaction shape.
When the derive can infer account addresses, Anchor also emits a Resolved struct. The resolved form asks only for accounts the caller must supply and fills in the rest:
use anchor_lang_v2::ToAccountMetas;
let metas = multisig_v2::accounts::CreateResolved { creator: creator.pubkey(),}.to_account_metas(None);Resolved structs derive PDAs whose seeds are visible to the macro, and insert well-known program accounts such as System or Token when the account wrapper determines the address.
The instruction struct also has a convenience to_instruction() method:
let ix = multisig_v2::instruction::Create { threshold: 2,}.to_instruction(multisig_v2::accounts::CreateResolved { creator: creator.pubkey(),});Several bench tests use this shape because it keeps the test focused on the accounts the caller actually controls.
PDA helpers#
For PDA fields, the generated accounts module includes helper methods named from the account field:
let (counter, bump) = counter::accounts::Initialize::find_counter_address( payer.pubkey().as_ref(),);When seeds have no runtime inputs, the helper has no arguments:
let (config, bump) = nested_v2::accounts::Initialize::find_config_address();Prefer these helpers in Rust tests when available. They keep client-side derivation locked to the program’s account declaration.
LiteSVM tests#
The default scaffold uses Rust integration tests with LiteSVM through anchor-v2-testing:
use anchor_v2_testing::{Keypair, LiteSVM, Message, Signer, VersionedMessage, VersionedTransaction};use anchor_lang_v2::{ solana_program::instruction::Instruction, InstructionData, ToAccountMetas,};
let mut svm: LiteSVM = anchor_v2_testing::svm();let payer = Keypair::new();let counter_account = Keypair::new();svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
let bytes = include_bytes!("../../../target/deploy/counter.so");svm.add_program(counter::id(), bytes).unwrap();
let ix = Instruction::new_with_bytes( counter::id(), &counter::instruction::Initialize {}.data(), counter::accounts::Initialize { payer: payer.pubkey(), counter: counter_account.pubkey(), system_program: anchor_lang_v2::programs::System::id(), } .to_account_metas(None),);
let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new( VersionedMessage::Legacy(msg), &[&payer, &counter_account],).unwrap();let result = svm.send_transaction(tx);assert!(result.is_ok());Use anchor_v2_testing::svm() instead of calling LiteSVM::new() directly. The wrapper turns into a trace-recording VM when the crate is built with the generated profile = ["anchor-v2-testing/profile"] feature, which powers anchor test --profile, anchor debugger, and anchor coverage.
When to use Rust first#
Use the Rust generated surface for:
- instruction tests that need to run against LiteSVM
- profiler, debugger, and coverage workflows
- client code that wants compile-time checking against the program crate
- account resolution features that TypeScript clients do not fully mirror yet
The IDL still matters for cross-language clients, but Rust is the most complete client surface during alpha.
Standalone Rust clients#
The in-crate generated modules above are the common path because integration tests share the program crate. For a Rust client that ships in a separate crate, anchor codama generate -l rust renders a standalone Rust crate from the IDL using the @codama/renderers-rust package:
anchor codama generate -l rust -p clients target/idl/counter.jsonThis is useful for off-chain services, SDKs, or downstream Rust apps that consume an Anchor program without depending on its program crate. See CLI reference: anchor codama for renderer details.
TypeScript
Use generated IDLs and TypeScript type output from Anchor programs.
The TypeScript surface is an alpha integration path. The CLI builds IDL JSON and emits TypeScript type files. Current scaffolds use @anchor-lang/core ^1.0.0 while the stable v2 npm surface is still settling.
This page covers connecting an app, script, or frontend to a v2 program. The examples show the familiar Anchor client flow, but the alpha package is less authoritative than the generated Rust helpers.
Warning (Alpha package surface)
Current scaffolds are pinned to @anchor-lang/core ^1.0.0 until a stable v2 package is
published. Treat TypeScript codegen as active development, and use Rust tests as the source of
truth for exact instruction construction.
During alpha, use TypeScript for application integration and IDL inspection. Prove exact instruction data, account ordering, PDA resolution, and optional-account behavior with generated Rust helpers and LiteSVM tests.
Generated artifacts#
anchor build writes the client-facing artifacts under target/:
Regenerate the IDL artifacts with:
anchor buildThe generated IDL includes instruction names, discriminators, args, accounts, account types, events, constants, errors, optional account markers, and PDA seed metadata when the macro can inspect the seed list.
Codama clients#
Anchor also ships a Codama integration for projects that prefer the Codama toolchain over @anchor-lang/core. anchor codama generate converts the IDL in-process, then renders client libraries through @codama/cli:
anchor codama generate -l js -p clients target/idl/counter.jsonThe JavaScript renderer pairs naturally with @solana/kit, and the same command supports js-umi, rust, and go outputs. See CLI reference: anchor codama for the full flag set.
A typical app or script imports the generated files from the Anchor workspace:
target/
idl/
- counter.json IDL JSON
types/
- counter.ts TypeScript IDL type
app/
src/
- program.ts Constructs the Program client
Client program#
A Program is built from an IDL and a provider. Read-only flows can use any provider-shaped object with a connection. Calls that send transactions need an AnchorProvider or another provider that implements sendAndConfirm.
In a frontend, wire the connection and wallet adapter into an Anchor provider before constructing the program:
import { AnchorProvider, Program, setProvider } from '@anchor-lang/core'import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react'import type { Counter } from '../target/types/counter'import idl from '../target/idl/counter.json'
const { connection } = useConnection()const wallet = useAnchorWallet()
if (!wallet) { throw new Error('Wallet not connected')}
const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed',})setProvider(provider)
export const program = new Program(idl as Counter, provider)A read-only script can construct a program from a connection without a wallet.
import { clusterApiUrl, Connection } from '@solana/web3.js'import { Program } from '@anchor-lang/core'import type { Counter } from '../target/types/counter'import idl from '../target/idl/counter.json'
const connection = new Connection(clusterApiUrl('devnet'), 'confirmed')
export const program = new Program(idl as Counter, { connection,})This is useful for account queries, IDL-backed name lookup, and early integration tests that do not sign transactions.
Invoke instructions#
The methods builder is the entry point for TypeScript clients. The builder chain has these pieces:
The basic chain looks like this:
const signature = await program.methods .increment() .accounts({ counter, authority: wallet.publicKey, }) .rpc()Start at program.methods and call the instruction by name with its args. Supply accounts with .accounts(), add extra keypair signers via .signers() when needed, then pick a terminal method.
const signature = await program.methods .increment() .accounts({ counter, authority: wallet.publicKey, }) .rpc()const transaction = await program.methods .increment() .accounts({ counter, authority: wallet.publicKey, }) .transaction()const ix = await program.methods .increment() .accounts({ counter, authority: wallet.publicKey, }) .instruction()During alpha, compare important TypeScript-built transactions against the generated Rust helper for the same instruction. The Rust helper is the most reliable reference for account ordering, PDA metadata, and any instruction shape that depends on code generated inside the program crate.
PDA seed metadata#
Generated clients can only auto-derive PDAs when the IDL contains enough seed metadata. Array-form seeds are the best shape:
#[account(seeds = [b"vault", authority.address().as_ref()], bump)]pub vault: Account<Vault>,Opaque expression seeds still validate at runtime, but clients may not know how to reproduce them:
#[account(seeds = Vault::seeds(authority.address()), bump)]pub vault: Account<Vault>,For public client-facing instructions, prefer array-form seeds whenever generated TypeScript clients should derive addresses automatically.
Account fetching#
Account clients are exposed under program.account:
Fetch and deserialize one account by address:
const counterAccount = await program.account.counter.fetch(counter)Fetch multiple accounts by address:
const manyCounters = await program.account.counter.fetchMultiple([counterA, counterB])Fetch all accounts of a given account type:
const allCounters = await program.account.counter.all()Filter accounts by matching bytes at an offset:
const counters = await program.account.counter.all([ { memcmp: { offset: 16, bytes: authority.toBase58(), }, },])Use the account wrapper to choose the right decoding and filter offsets. Not every account starts with an Anchor discriminator followed by a borsh payload.
For memcmp filters, calculate offsets from the actual wrapper layout. SPL token accounts start at byte offset 0, while Anchor-discriminated program accounts start after the discriminator.
Recommended workflow#
Keep Rust integration tests as the execution harness:
Define the instruction and accounts in Rust.
Run anchor build and inspect target/idl/ plus target/types/.
Write or update a LiteSVM test using the generated Rust helper.
Build the app integration against the same IDL and keep TypeScript assumptions small until the alpha package lands.
App developers get a usable IDL and type file, and the TypeScript package isn’t forced to carry guarantees the alpha code hasn’t earned yet.
Current limitations#
Some surfaces are still worth testing carefully in downstream clients:
- account-resolution paths that rely on generated PDA metadata
- generated CPI metadata
- opaque seed expressions
- Pod-backed account decoding conventions
- SPL and Token-2022 interface-account surfaces
Use the IDL for integration planning and early experiments, but keep Rust tests and generated account builders as the reference until the TypeScript package catches up.