Anchor
Anchor for Dummies

Anchor for Dummies

Client Side Development

Most dApps use TypeScript to interact with deployed Solana programs. Understanding how to integrate your program client-side is essential for building functional applications.

Anchor Client SDK

Anchor simplifies client interaction with Solana programs through an Interface Description Language (IDL) file that mirrors your program's structure.

When combined with Anchor's TypeScript library (@coral-xyz/anchor), the IDL provides a streamlined approach to building instructions and transactions.

Setup

The @coral-xyz/anchor package installs automatically when creating an Anchor program. After running anchor build, Anchor generates:

  • An IDL at target/idl/<program-name>.json
  • A TypeScript SDK at target/types/<program-name>.ts

These files abstract away much of the underlying complexity. Transfer them to your TypeScript client using this structure:

src
├── anchor
│     ├── <program-name>.json
│     └── <program-name>.ts
└── integration.ts

The integration.ts file contains program interaction logic. The <program-name>.json file is the IDL, and <program-name>.ts contains generated TypeScript types.

To use the wallet adapter with Anchor's TypeScript SDK, create a Provider object that combines the Connection (localhost, devnet, or mainnet) and the Wallet (the address that pays for and signs transactions).

Set up the Wallet and Connection:

import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
 
const { connection } = useConnection();
const wallet = useAnchorWallet();

The useWallet hook from @solana/wallet-adapter-react is incompatible with the Wallet object that Anchor's Provider expects. This is why we're using the useAnchorWallet hook.

Create the Provider object and set it as the default:

import { AnchorProvider, setProvider } from "@coral-xyz/anchor";

const provider = new AnchorProvider(connection, wallet, {
  commitment: "confirmed",
});

setProvider(provider);

Program

Anchor's Program object creates a custom API for interacting with Solana programs. This API serves as the central interface for all onchain program communication:

  • Send transactions,
  • Fetch deserialized accounts,
  • Decode instruction data,
  • Subscribe to account changes,
  • Listen to events

Create the Program object by importing the types and IDL:

import <program-name> from "./<program-name>.json";
import type { <Program-Type> } from "./<program-name>.ts";
import { Program, Idl } from "@coral-xyz/anchor";
 
const program = new Program(<program-name> as <Program-Type>);

If you haven't set a default provider, specify it explicitly:

const program = new Program(<program-name> as <Program-Type>, provider);

Once configured, use the Anchor Methods Builder to construct instructions and transactions. The MethodsBuilder uses the IDL to provide a streamlined format for building transactions that invoke program instructions.

The basic MethodsBuilder pattern:

await program.methods
  .instructionName(instructionDataInputs)
  .accounts({})
  .signers([])
  .rpc();

The API uses camelCase naming instead of Rust's snake_case convention. Call instructions using dot syntax with the instruction name, passing arguments as comma-separated values.

Pass additional signers beyond the provider using .signers().

Accounts

Use dot syntax to call .accounts on the MethodsBuilder, passing an object with each account the instruction expects based on the IDL.

From Anchor 0.30.0, accounts that can be resolved automatically (like PDAs or explicit addresses) are included in the IDL and aren't required in the .accounts call (.accountPartial() becomes the default). To pass all accounts manually, use .accountsStrict().

Transactions

The default method for sending transactions through Anchor is .rpc(), which sends the transaction directly to the blockchain.

For scenarios requiring backend signing (like creating a transaction on the frontend with the user's wallet, then securely signing with a backend keypair), use .transaction():

const transaction = await program.methods
  .instructionName(instructionDataInputs)
  .accounts({})
  .transaction();
 
//... Sign the transaction in the backend
 
// Send the transaction to the chain
await sendTransaction(transaction, connection);

To bundle multiple Anchor instructions, use .instruction() to get instruction objects:

// Create first instruction
const instructionOne = await program.methods
  .instructionOneName(instructionOneDataInputs)
  .accounts({})
  .instruction();
 
// Create second instruction
const instructionTwo = await program.methods
  .instructionTwoName(instructionTwoDataInputs)
  .accounts({})
  .instruction();
 
// Add both instructions to one transaction
const transaction = new Transaction().add(instructionOne, instructionTwo);
 
// Send transaction
await sendTransaction(transaction, connection);

Fetch and Filter Accounts

When your program creates hundreds of accounts, tracking them becomes challenging. The Program object provides methods to efficiently fetch and filter program accounts.

Fetch all addresses of a specific account type:

const accounts = await program.account.counter.all();

Filter specific accounts using the memcmp flag:

const accounts = await program.account.counter.all([
  {
    memcmp: {
      offset: 8,
      bytes: bs58.encode(new BN(0, "le").toArray()),
    },
  },
]);

This fetches all Counter accounts where the first field equals 0.

For checking if account data changed, fetch deserialized account data for a specific account using fetch:

const account = await program.account.counter.fetch(ACCOUNT_ADDRESS);

Fetch multiple accounts simultaneously:

const accounts = await program.account.counter.fetchMultiple([
  ACCOUNT_ADDRESS_ONE,
  ACCOUNT_ADDRESS_TWO,
]);

Events and Webhooks

Rather than fetching onchain data every time users connect their wallets, set up systems that listen to the blockchain and store relevant data in a database.

Two main approaches exist for listening to onchain events:

  • Polling: The client repeatedly checks for new data at intervals. The server responds with the latest data regardless of changes, potentially returning duplicate information.
  • Streaming: The server pushes data to the client only when updates occur. This provides more efficient, real-time data transfer since only relevant changes are transmitted.

For streaming Anchor instructions, use webhooks that listen to events and send them to your server when they occur. For example, update a database entry whenever an NFT sale happens on your marketplace.

For extremely low-latency applications where 5ms differences matter, webhooks may not provide sufficient speed.

Anchor provides two macros for emitting events:

  • emit!(): Emits events directly to program logs using the sol_log_data() syscall, encoding event data as base64 strings prefixed with "Program Data"
  • emit_cpi!(): Emits events through Cross Program Invocations (CPIs). Event data is encoded and included in the CPI's instruction data instead of program logs

emit!() macro

Program implementation:

use anchor_lang::prelude::*;
 
declare_id!("8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy");
 
#[program]
pub mod event {
    use super::*;
 
    pub fn emit_event(_ctx: Context<EmitEvent>, input: String) -> Result<()> {
        emit!(CustomEvent { message: input });
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct EmitEvent {}
 
#[event]
pub struct CustomEvent {
    pub message: String,
}

Client-side event listening with Anchor SDK helpers for base64 decoding:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Event } from "../target/types/event";
 
describe("event", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());
 
  const program = anchor.workspace.Event as Program<Event>;
 
  it("Emits custom event", async () => {
    // Set up listener before sending transaction
    const listenerId = program.addEventListener("customEvent", event => {
      // Process the event data
      console.log("Event Data:", event);
    });
  });
});

emit_cpi!() macro

Program implementation:

use anchor_lang::prelude::*;
 
declare_id!("2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1");
 
#[program]
pub mod event_cpi {
    use super::*;
 
    pub fn emit_event(ctx: Context<EmitEvent>, input: String) -> Result<()> {
        emit_cpi!(CustomEvent { message: input });
        Ok(())
    }
}
 
#[event_cpi]
#[derive(Accounts)]
pub struct EmitEvent {}
 
#[event]
pub struct CustomEvent {
    pub message: String,
}

Client-side event decoding:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { EventCpi } from "../target/types/event_cpi";
 
describe("event-cpi", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());
  const program = anchor.workspace.EventCpi as Program<EventCpi>;
 
  it("Emits custom event", async () => { 
    // Fetch the transaction data
    const transactionData = await program.provider.connection.getTransaction(
      transactionSignature,
      { commitment: "confirmed" },
    );
 
    // Decode the event data from the CPI instruction data
    const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
    const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
    const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
    const event = program.coder.events.decode(base64Data);
 
    console.log(event);
  });
});
Contents
View Source
Blueshift © 2025Commit: fd080b2