Skip to content

Range uses Codama to generate type-safe clients from the Anchor IDL. This guide walks you through setting up client generation for your project.

Codama is a code generation framework that creates type-safe clients from Solana program IDLs. It generates:

  • TypeScript clients: Full type safety, autocompletion, and runtime validation
  • Rust clients: For CPI from other programs
  • Node.js 18+
  • The Range program IDL file (generated by anchor build)
  1. Install Codama CLI

    Terminal window
    npm install -g @codama/cli
    # or
    yarn global add @codama/cli
  2. Create Codama Configuration

    Create codama.config.ts in your project root:

    import { createFromRoot } from '@codama/nodes';
    import { renderJavaScriptVisitor } from '@codama/renderers-js';
    import { renderRustVisitor } from '@codama/renderers-rust';
    import { rootNodeFromAnchor } from '@codama/nodes-from-anchor';
    import anchorIdl from './target/idl/range.json';
    // Create Codama tree from Anchor IDL
    const codama = createFromRoot(rootNodeFromAnchor(anchorIdl));
    // Generate TypeScript client
    codama.accept(
    renderJavaScriptVisitor('./codama-ts-range/src/generated', {
    prettier: { semi: true, singleQuote: true },
    })
    );
    // Generate Rust CPI client
    codama.accept(
    renderRustVisitor('./codama-rust-range/src/generated', {
    crateFolder: './codama-rust-range',
    })
    );
  3. Add Generation Script

    In package.json:

    {
    "scripts": {
    "codama": "ts-node codama.config.ts",
    "build:idl": "anchor build && npm run codama"
    }
    }
  4. Generate Clients

    Terminal window
    # Build program and generate IDL
    anchor build
    # Generate clients
    npm run codama

After generation, you’ll have:

codama-ts-range/
├── src/
│ └── generated/
│ ├── accounts/ # Account types
│ │ └── settings.ts
│ ├── instructions/ # Instruction builders
│ │ ├── initializeSettings.ts
│ │ ├── updateSettings.ts
│ │ ├── transferAdmin.ts
│ │ ├── verifyRange.ts
│ │ └── verifyRangeWithCallback.ts
│ ├── types/ # Custom types
│ ├── errors.ts # Error definitions
│ └── index.ts # Re-exports
import {
getInitializeSettingsInstruction,
getVerifyRangeInstruction,
Settings,
RANGE_PROGRAM_ADDRESS,
} from './codama-ts-range';
import { PublicKey, Connection } from '@solana/web3.js';
const connection = new Connection('https://api.devnet.solana.com');
// Initialize Settings
const initInstruction = getInitializeSettingsInstruction({
admin: adminPublicKey,
settings: settingsPda,
systemProgram: SystemProgram.programId,
rangeSigner: backendSignerPublicKey,
windowSize: 60n,
});
// Verify Range
const verifyInstruction = getVerifyRangeInstruction({
signer: userPublicKey,
settings: settingsPda,
instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
signature: signatureBytes,
message: messageBytes,
});
import { fetchSettings, Settings } from './codama-ts-range';
// Fetch Settings account
const settings = await fetchSettings(connection, settingsPda);
console.log('Admin:', settings.admin.toBase58());
console.log('Range Signer:', settings.rangeSigner.toBase58());
console.log('Window Size:', settings.windowSize.toString());

For convenience, create wrapper functions that handle common patterns:

codama-ts-range-custom/wrappers/verifyRange.ts
import { getVerifyRangeInstruction } from '../generated';
import { PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY } from '@solana/web3.js';
export interface BuildVerifyRangeInput {
signer: PublicKey;
admin: PublicKey; // Derives Settings PDA
signature: Uint8Array;
message: Uint8Array;
}
export async function buildVerifyRangeInstruction(
input: BuildVerifyRangeInput
) {
// Derive Settings PDA from admin
const [settingsPda] = PublicKey.findProgramAddressSync(
[Buffer.from('settings'), input.admin.toBuffer()],
RANGE_PROGRAM_ADDRESS
);
return getVerifyRangeInstruction({
signer: input.signer,
settings: settingsPda,
instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
signature: Array.from(input.signature),
message: Array.from(input.message),
});
}

The Rust client is useful for calling Range from other Solana programs:

// In your Cargo.toml
[dependencies]
range = { path = "../codama-rust-range" }
use range::cpi::accounts::VerifyRange;
use range::cpi::verify_range;
use range::program::Range;
pub fn my_instruction(ctx: Context<MyInstruction>) -> Result<()> {
let cpi_accounts = VerifyRange {
signer: ctx.accounts.signer.to_account_info(),
settings: ctx.accounts.settings.to_account_info(),
instructions_sysvar: ctx.accounts.instructions_sysvar.to_account_info(),
};
let cpi_program = ctx.accounts.range_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
verify_range(cpi_ctx, signature, message)?;
Ok(())
}

When you modify the Range program:

Terminal window
# Rebuild and regenerate
anchor build
npm run codama
# Or use the combined script
npm run build:idl

Ensure you’ve generated the client:

Terminal window
npm run codama

Regenerate after IDL changes:

Terminal window
anchor build && npm run codama

Check that all required accounts are provided in instruction calls. Codama generates strict types that enforce required accounts.