Coda
Packages

@macalinao/coda

The main CLI tool for automated client generation from Solana IDLs

The core Coda package provides a CLI that transforms Anchor IDLs into modern TypeScript clients with full type safety and ES modules support.

Installation

bun add -D @macalinao/coda

Or with npm:

npm install -D @macalinao/coda

Features

  • 🚀 Minimal configuration - Works out of the box with sensible defaults
  • 🔧 Fully customizable - Extend and transform generated code with visitors
  • 📦 ES modules native - Modern JavaScript with proper .js extensions
  • 🎯 Type-safe - Full TypeScript support with precise types
  • 🔄 Anchor compatible - Works with any Anchor IDL
  • 🏗️ Extensible - Add custom visitors to transform the Codama tree
  • Fast - Optimized code generation pipeline

CLI Commands

Generate Command

Generate TypeScript clients from an Anchor IDL:

coda generate [options]

Options:
  -i, --idl <path>      Path to Anchor IDL file (default: "./idls/*.json")
  -o, --output <path>   Output directory (default: "./src/generated")
  -c, --config <path>   Path to config file (default: "./coda.config.mjs")

Init Command

Initialize a new configuration file:

coda init [options]

Options:
  -c, --config <path>   Path for config file (default: "./coda.config.mjs")

Configuration

Default Configuration

Coda works without any configuration by looking for IDLs in ./idls/*.json:

# Just place your IDL files in the idls/ directory
coda generate

Configuration File

Create a coda.config.mjs to customize generation:

import { defineConfig } from "@macalinao/coda";
import { addPdasVisitor } from "codama";

export default defineConfig({
  // Custom paths (optional)
  idlPath: "./idls/my_program.json",
  outputDir: "./src/clients/my_program",
  
  // Add visitors to customize generation
  visitors: [
    // Add custom PDAs
    addPdasVisitor({
      myAccount: [
        {
          name: "authority",
          seeds: [
            { kind: "constant", value: "authority" },
            { kind: "variable", name: "user", type: publicKeyTypeNode() }
          ]
        }
      ]
    })
  ]
});

Multiple IDLs

Handle multiple programs with glob patterns:

export default defineConfig({
  // Use glob pattern to match all IDLs
  idlPath: "./idls/*.json",
  outputDir: "./src/generated",
});

Or specify an array of paths:

export default defineConfig({
  idlPath: [
    "./idls/program1.json",
    "./idls/program2.json",
  ],
  outputDir: "./src/generated",
});

Dynamic Configuration

Use a function to conditionally apply visitors:

export default defineConfig({
  visitors: ({ idl }) => {
    const visitors = [];
    
    // Conditionally add visitors based on IDL content
    if (idl.name === "my_program") {
      visitors.push(myCustomVisitor());
    }
    
    return visitors;
  }
});

Generated Code Structure

Coda generates a comprehensive client with the following structure:

src/generated/
├── accounts/           # Account decoders and fetchers
├── instructions/       # Instruction builders
├── types/             # TypeScript type definitions  
├── pdas/              # PDA helper functions
├── errors/            # Typed error codes
├── programs/          # Program ID constants
└── index.ts           # Main exports

Instructions

Fully typed instruction builders with proper serialization:

export function createTransferInstruction(
  input: TransferInstructionInput
): Instruction;

export function getTransferInstructionDataEncoder(): Encoder<TransferArgs>;

Accounts

Account decoders with type safety:

export function decodeTokenAccount(
  encodedAccount: EncodedAccount
): TokenAccount;

export async function fetchTokenAccount(
  rpc: Rpc,
  address: Address
): Promise<TokenAccount>;

Types

All types from your IDL with proper TypeScript definitions:

export type TokenAccount = {
  mint: Address;
  owner: Address;
  amount: bigint;
  // ... other fields
};

PDAs

Helper functions for program-derived addresses:

export async function findAuthorityPda(
  seeds: {
    user: Address;
  }
): Promise<[Address, number]>;

Errors

Typed error enums for better error handling:

export enum MyProgramError {
  InvalidAmount = 0x1770,
  Unauthorized = 0x1771,
  // ...
}

API Reference

defineConfig

Creates a configuration object for Coda:

function defineConfig(config: CodaConfig): CodaConfig;

interface CodaConfig {
  idlPath?: string | string[];
  outputDir?: string;
  visitors?: Visitor[] | ((context: VisitorContext) => Visitor[]);
}

VisitorContext

Context passed to dynamic visitor functions:

interface VisitorContext {
  idl: Idl;           // The parsed Anchor IDL
  idlPath: string;    // Path to the IDL file
  outputDir: string;  // Output directory path
}

Integration Examples

With Package.json Scripts

{
  "scripts": {
    "codegen": "coda generate",
    "codegen:watch": "coda generate --watch"
  }
}

With Turborepo

// turbo.json
{
  "tasks": {
    "codegen": {
      "outputs": ["./src/generated/**"],
      "cache": false
    }
  }
}

With GitHub Actions

- name: Generate clients
  run: |
    bun install
    bun run codegen

Advanced Features

Custom Visitors

Extend generated code with Codama visitors:

import { updateInstructionsVisitor } from "codama";

const customVisitor = updateInstructionsVisitor({
  transfer: {
    defaults: {
      amount: 0n
    }
  }
});

export default defineConfig({
  visitors: [customVisitor]
});

Flattening Nested Accounts

Use the dedupe visitor to flatten nested account structures:

import { instructionAccountsDedupeVisitor } from "@macalinao/codama-instruction-accounts-dedupe-visitor";

export default defineConfig({
  visitors: ({ idl }) => [
    instructionAccountsDedupeVisitor(idl)
  ]
});

Renaming Conflicts

Resolve naming conflicts between multiple programs:

import { renameVisitor } from "@macalinao/codama-rename-visitor";

export default defineConfig({
  visitors: [
    renameVisitor({
      programA: {
        instructions: {
          transfer: "transferProgramA"
        }
      },
      programB: {
        instructions: {
          transfer: "transferProgramB"
        }
      }
    })
  ]
});

Documentation Generation

Generate markdown documentation alongside your TypeScript clients:

coda docs [options]

Options:
  -i, --idl <path>      Path to Anchor IDL file (default: "./idls/*.json")
  -o, --output <path>   Output directory (default: "./docs")
  -c, --config <path>   Path to config file (default: "./coda.config.mjs")

Basic Usage

# Generate documentation
coda docs

# Custom output directory
coda docs -o ./api-docs

With Configuration

Add documentation generation to your config:

import { renderMarkdownVisitor } from "@macalinao/codama-renderers-markdown";

export default defineConfig({
  visitors: [
    renderMarkdownVisitor("./docs")
  ]
});

Best Practices

  1. Keep IDLs up-to-date: Run anchor build before generating clients
  2. Use TypeScript: The generated code provides full type safety
  3. Add custom PDAs: Define program-derived addresses in your config
  4. Document your programs: Add descriptions in your Anchor programs
  5. Version your clients: Generate clients for each program version

Troubleshooting

IDL Not Found

If Coda can't find your IDL:

  1. Check the default location: ./idls/*.json
  2. Specify explicit path: coda generate -i path/to/idl.json
  3. Ensure Anchor program is built: anchor build

Import Errors

If you see module resolution errors:

  1. Ensure "type": "module" is in your package.json
  2. Check that tsconfig.json uses "moduleResolution": "NodeNext"
  3. Verify all local imports use .js extensions

Type Errors

If TypeScript shows errors:

  1. Run bun install to ensure dependencies are installed
  2. Check that @solana/web3.js is installed
  3. Verify tsconfig includes the generated directory

Examples

Single Program

See the token-metadata client for a complete example of a single program setup.

Multiple Programs

See the Quarry client for an example with multiple programs and custom visitors.