@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
- Keep IDLs up-to-date: Run
anchor build
before generating clients - Use TypeScript: The generated code provides full type safety
- Add custom PDAs: Define program-derived addresses in your config
- Document your programs: Add descriptions in your Anchor programs
- Version your clients: Generate clients for each program version
Troubleshooting
IDL Not Found
If Coda can't find your IDL:
- Check the default location:
./idls/*.json
- Specify explicit path:
coda generate -i path/to/idl.json
- Ensure Anchor program is built:
anchor build
Import Errors
If you see module resolution errors:
- Ensure
"type": "module"
is in your package.json - Check that tsconfig.json uses
"moduleResolution": "NodeNext"
- Verify all local imports use
.js
extensions
Type Errors
If TypeScript shows errors:
- Run
bun install
to ensure dependencies are installed - Check that
@solana/web3.js
is installed - 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.
Related Packages
- @macalinao/codama-renderers-js-esm - ESM-native TypeScript renderer
- @macalinao/codama-instruction-accounts-dedupe-visitor - Flatten nested accounts
- @macalinao/codama-rename-visitor - Rename instructions and accounts
- @macalinao/codama-renderers-markdown - Generate documentation
- @macalinao/codama-nodes-from-anchor-x - Parse multiple IDLs