@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/codaOr with npm:
npm install -D @macalinao/codaFeatures
- 🚀 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
.jsextensions - 🎯 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 generateConfiguration 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 exportsInstructions
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 codegenAdvanced 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-docsWith 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 buildbefore 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
.jsextensions
Type Errors
If TypeScript shows errors:
- Run
bun installto ensure dependencies are installed - Check that
@solana/web3.jsis 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