Client Setup (Codama)
Generate type-safe TypeScript and Rust clients from the Range IDL
Range uses Codama to generate type-safe clients from the Anchor IDL. This guide walks you through setting up client generation for your project.
What is Codama?
Section titled “What is Codama?”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
Prerequisites
Section titled “Prerequisites”- Node.js 18+
- The Range program IDL file (generated by
anchor build)
-
Install Codama CLI
Terminal window npm install -g @codama/cli# oryarn global add @codama/cli -
Create Codama Configuration
Create
codama.config.tsin 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 IDLconst codama = createFromRoot(rootNodeFromAnchor(anchorIdl));// Generate TypeScript clientcodama.accept(renderJavaScriptVisitor('./codama-ts-range/src/generated', {prettier: { semi: true, singleQuote: true },}));// Generate Rust CPI clientcodama.accept(renderRustVisitor('./codama-rust-range/src/generated', {crateFolder: './codama-rust-range',})); -
Add Generation Script
In
package.json:{"scripts": {"codama": "ts-node codama.config.ts","build:idl": "anchor build && npm run codama"}} -
Generate Clients
Terminal window # Build program and generate IDLanchor build# Generate clientsnpm run codama
Generated TypeScript Structure
Section titled “Generated TypeScript Structure”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-exportsUsing Generated TypeScript Client
Section titled “Using Generated TypeScript Client”Import and Setup
Section titled “Import and Setup”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');Build Instructions
Section titled “Build Instructions”// Initialize Settingsconst initInstruction = getInitializeSettingsInstruction({ admin: adminPublicKey, settings: settingsPda, systemProgram: SystemProgram.programId, rangeSigner: backendSignerPublicKey, windowSize: 60n,});
// Verify Rangeconst verifyInstruction = getVerifyRangeInstruction({ signer: userPublicKey, settings: settingsPda, instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, signature: signatureBytes, message: messageBytes,});Fetch and Parse Accounts
Section titled “Fetch and Parse Accounts”import { fetchSettings, Settings } from './codama-ts-range';
// Fetch Settings accountconst 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());Custom Wrappers
Section titled “Custom Wrappers”For convenience, create wrapper functions that handle common patterns:
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), });}Generated Rust Client (for CPI)
Section titled “Generated Rust Client (for CPI)”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(())}Regenerating After Changes
Section titled “Regenerating After Changes”When you modify the Range program:
# Rebuild and regenerateanchor buildnpm run codama
# Or use the combined scriptnpm run build:idlTroubleshooting
Section titled “Troubleshooting””Cannot find module” errors
Section titled “”Cannot find module” errors”Ensure you’ve generated the client:
npm run codamaType mismatches
Section titled “Type mismatches”Regenerate after IDL changes:
anchor build && npm run codamaMissing accounts
Section titled “Missing accounts”Check that all required accounts are provided in instruction calls. Codama generates strict types that enforce required accounts.
See Also
Section titled “See Also”- Codama Documentation
- Quick Start - Using the generated client
- Testing Locally - Running tests with generated client