Anchor Framework
LUMOS provides deep integration with the Anchor framework, allowing you to generate complete Solana programs with account contexts, IDL, and TypeScript clients.
LUMOS + Anchor: Better Together
Section titled “LUMOS + Anchor: Better Together”Think of it this way:
- Anchor = The framework that runs your Solana program
- LUMOS = The tool that generates Anchor code from a schema
Why use both?
| Without LUMOS | With LUMOS |
|---|---|
| Manually write Rust structs | Define once in .lumos schema |
| Manually write TypeScript types | Auto-generate TypeScript |
| Hope Rust ↔ TS stay in sync | Guaranteed sync (same source) |
| Calculate account space by hand | Auto-calculated LEN constants |
| Write IDL manually or extract | Auto-generate IDL from schema |
LUMOS generates standard Anchor code - your program still uses anchor_lang, #[account], #[program], and everything else you know. The difference is you define your types once and generate the rest.
When to Use LUMOS vs Anchor Alone
Section titled “When to Use LUMOS vs Anchor Alone”| Scenario | Recommendation |
|---|---|
| Starting a new Anchor project | ✅ Use LUMOS - define schema first, generate code |
| Existing Anchor project, want type sync | ✅ Use LUMOS - adopt gradually for new types |
| Simple program, few account types | ⚠️ Either works - Anchor alone is fine |
| Complex program, many shared types | ✅ Use LUMOS - prevents drift between Rust/TS |
| Need custom IDL modifications | ⚠️ Start with LUMOS, customize after |
| Team with mixed Rust/TS developers | ✅ Use LUMOS - schema is readable by all |
Bottom line: If you’re writing TypeScript clients that interact with your Anchor program, LUMOS saves you from maintaining duplicate type definitions.
Overview
Section titled “Overview”When your schema uses #[account] attributes, LUMOS automatically generates Anchor-compatible code:
#[solana]#[account]struct PlayerAccount { wallet: PublicKey, level: u16, experience: u64,}This generates:
- Rust structs with
#[account]derive - Account
LENconstants for space calculation - Anchor IDL JSON
- Optional TypeScript client
Quick Start
Section titled “Quick Start”1. Define Schema
Section titled “1. Define Schema”#[solana]#[account]struct GameConfig { authority: PublicKey, max_players: u32, entry_fee: u64, prize_pool: u64,}
#[solana]#[account]struct Player { wallet: PublicKey, game: PublicKey, score: u64, joined_at: i64,}
#[solana]enum GameInstruction { Initialize { max_players: u32, entry_fee: u64 }, JoinGame, UpdateScore { score: u64 }, ClaimPrize,}2. Generate Anchor Program
Section titled “2. Generate Anchor Program”lumos anchor generate game.lumos \ --name game_program \ --typescript3. Generated Structure
Section titled “3. Generated Structure”game_program/├── programs/game_program/│ ├── src/│ │ ├── lib.rs # Program entry point│ │ └── state.rs # Account structs│ └── Cargo.toml├── target/idl/│ └── game_program.json # Anchor IDL└── client/ └── game_program.ts # TypeScript clientCLI Commands
Section titled “CLI Commands”lumos anchor generate
Section titled “lumos anchor generate”Generate a complete Anchor program:
lumos anchor generate <SCHEMA> [OPTIONS]
Options: -o, --output <DIR> Output directory -n, --name <NAME> Program name -V, --version <VER> Program version (default: 0.1.0) -a, --address <ADDR> Program address --typescript Generate TypeScript client --dry-run Preview without writingExamples:
# Basic generationlumos anchor generate game.lumos
# Full optionslumos anchor generate game.lumos \ --name my_game \ --version 1.0.0 \ --address "Game111111111111111111111111111111111111111" \ --typescript
# Preview generationlumos anchor generate game.lumos --dry-runlumos anchor idl
Section titled “lumos anchor idl”Generate only the Anchor IDL:
lumos anchor idl <SCHEMA> [OPTIONS]
Options: -o, --output <FILE> Output file path -n, --name <NAME> Program name -p, --pretty Pretty print JSONExample:
lumos anchor idl game.lumos --pretty -o target/idl/game.jsonlumos anchor space
Section titled “lumos anchor space”Calculate account space constants:
lumos anchor space <SCHEMA> [OPTIONS]
Options: -f, --format <FMT> Output format (text or rust) -a, --account <NAME> Specific account typeExample:
lumos anchor space game.lumos --format rustGenerated Code
Section titled “Generated Code”Account Structs
Section titled “Account Structs”use anchor_lang::prelude::*;
#[account]#[derive(Default)]pub struct GameConfig { pub authority: Pubkey, pub max_players: u32, pub entry_fee: u64, pub prize_pool: u64,}
impl GameConfig { pub const LEN: usize = 8 // discriminator + 32 // authority + 4 // max_players + 8 // entry_fee + 8; // prize_pool}Program Entry
Section titled “Program Entry”use anchor_lang::prelude::*;
mod state;use state::*;
declare_id!("Game111111111111111111111111111111111111111");
#[program]pub mod game_program { use super::*;
pub fn initialize( ctx: Context<Initialize>, max_players: u32, entry_fee: u64, ) -> Result<()> { let config = &mut ctx.accounts.config; config.authority = ctx.accounts.authority.key(); config.max_players = max_players; config.entry_fee = entry_fee; config.prize_pool = 0; Ok(()) }
// ... other instructions}
#[derive(Accounts)]pub struct Initialize<'info> { #[account( init, payer = authority, space = 8 + GameConfig::LEN )] pub config: Account<'info, GameConfig>, #[account(mut)] pub authority: Signer<'info>, pub system_program: Program<'info, System>,}IDL Output
Section titled “IDL Output”{ "version": "1.0.0", "name": "game_program", "instructions": [ { "name": "initialize", "accounts": [ { "name": "config", "isMut": true, "isSigner": false }, { "name": "authority", "isMut": true, "isSigner": true }, { "name": "systemProgram", "isMut": false, "isSigner": false } ], "args": [ { "name": "maxPlayers", "type": "u32" }, { "name": "entryFee", "type": "u64" } ] } ], "accounts": [ { "name": "GameConfig", "type": { "kind": "struct", "fields": [ { "name": "authority", "type": "publicKey" }, { "name": "maxPlayers", "type": "u32" }, { "name": "entryFee", "type": "u64" }, { "name": "prizePool", "type": "u64" } ] } } ]}TypeScript Client
Section titled “TypeScript Client”import * as anchor from "@coral-xyz/anchor";import { Program } from "@coral-xyz/anchor";import { GameProgram } from "./types/game_program";
export class GameProgramClient { program: Program<GameProgram>;
constructor( connection: anchor.web3.Connection, wallet: anchor.Wallet ) { const provider = new anchor.AnchorProvider(connection, wallet, {}); this.program = new Program(IDL, PROGRAM_ID, provider); }
async initialize( maxPlayers: number, entryFee: anchor.BN ): Promise<string> { const config = anchor.web3.Keypair.generate();
return await this.program.methods .initialize(maxPlayers, entryFee) .accounts({ config: config.publicKey, authority: this.program.provider.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([config]) .rpc(); }
// ... other methods}Account Attributes
Section titled “Account Attributes”#[account]
Section titled “#[account]”Marks a struct as an Anchor account:
#[solana]#[account]struct MyAccount { data: u64,}Account Constraints (Schema Annotations)
Section titled “Account Constraints (Schema Annotations)”LUMOS supports Anchor account constraints via schema annotations:
#[solana]#[account]#[init]struct NewAccount { owner: PublicKey, data: u64,}
#[solana]#[account]#[mut]struct MutableAccount { balance: u64,}
#[solana]#[account]#[signer]struct AuthorityAccount { authority: PublicKey,}Space Calculation
Section titled “Space Calculation”LUMOS automatically calculates account space including the 8-byte discriminator:
| Type | Size |
|---|---|
bool | 1 byte |
u8 / i8 | 1 byte |
u16 / i16 | 2 bytes |
u32 / i32 | 4 bytes |
u64 / i64 | 8 bytes |
u128 / i128 | 16 bytes |
PublicKey | 32 bytes |
String | 4 + length |
Vec<T> | 4 + (count × size) |
Option<T> | 1 + size |
Example:
lumos anchor space game.lumosAccount Space Constants=======================
GameConfig: DISCRIMINATOR: 8 bytes authority: 32 bytes max_players: 4 bytes entry_fee: 8 bytes prize_pool: 8 bytes ────────────────────────── TOTAL (LEN): 60 bytesIntegration with Existing Projects
Section titled “Integration with Existing Projects”Add to Existing Anchor Project
Section titled “Add to Existing Anchor Project”# In your Anchor project rootlumos anchor generate schemas/game.lumos \ --output programs/game/src/lumos_generated.rs
# Import in lib.rsmod lumos_generated;use lumos_generated::*;Build and Test
Section titled “Build and Test”# Build the programanchor build
# Run testsanchor testBest Practices
Section titled “Best Practices”1. Schema-First Development
Section titled “1. Schema-First Development”Define your schema first, then generate code:
// Define complete data model#[solana]#[account]struct User { ... }
#[solana]#[account]struct Post { ... }
// Generate, then build2. Version Control
Section titled “2. Version Control”Track both schema and generated code:
# Keep generated code in version control# Don't ignore: programs/*/src/generated.rs3. Regenerate on Schema Changes
Section titled “3. Regenerate on Schema Changes”Add to your build process:
{ "scripts": { "codegen": "lumos anchor generate schema.lumos", "build": "npm run codegen && anchor build" }}4. Use Type Aliases for Clarity
Section titled “4. Use Type Aliases for Clarity”type UserId = PublicKey;type Lamports = u64;
#[solana]#[account]struct Stake { user: UserId, amount: Lamports,}Common Patterns
Section titled “Common Patterns”PDA (Program Derived Address) Accounts
Section titled “PDA (Program Derived Address) Accounts”PDAs are accounts whose address is derived from seeds. Define the account structure in LUMOS, derive the PDA in your Anchor code:
#[solana]#[account]struct UserProfile { authority: PublicKey, username: String, reputation: u64, created_at: i64,}// In your Anchor program#[derive(Accounts)]#[instruction(username: String)]pub struct CreateProfile<'info> { #[account( init, payer = authority, space = 8 + UserProfile::LEN, seeds = [b"profile", authority.key().as_ref()], bump )] pub profile: Account<'info, UserProfile>, #[account(mut)] pub authority: Signer<'info>, pub system_program: Program<'info, System>,}One-to-Many Relationships
Section titled “One-to-Many Relationships”Model relationships using PublicKey references:
#[solana]#[account]struct Collection { authority: PublicKey, name: String, item_count: u32,}
#[solana]#[account]struct Item { collection: PublicKey, // Reference to parent owner: PublicKey, name: String, attributes: Vec<u8>,}Token Account Integration
Section titled “Token Account Integration”When working with SPL tokens alongside LUMOS-generated accounts:
#[solana]#[account]struct Vault { authority: PublicKey, token_mint: PublicKey, // SPL token mint token_account: PublicKey, // Associated token account total_deposited: u64, is_locked: bool,}// Anchor context#[derive(Accounts)]pub struct Deposit<'info> { #[account(mut)] pub vault: Account<'info, Vault>, #[account(mut)] pub vault_token_account: Account<'info, TokenAccount>, #[account(mut)] pub user_token_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, pub authority: Signer<'info>,}Enum-Based State Machines
Section titled “Enum-Based State Machines”Use LUMOS enums for program state:
#[solana]enum AuctionState { Created, Active { start_time: i64 }, Ended { winner: PublicKey, final_bid: u64 }, Cancelled,}
#[solana]#[account]struct Auction { authority: PublicKey, item_mint: PublicKey, state: AuctionState, min_bid: u64, current_bid: u64,}Troubleshooting
Section titled “Troubleshooting””Cannot find type” error
Section titled “”Cannot find type” error”Ensure all referenced types are defined in the schema or imported.
// Bad - UndefinedType not in schemastruct MyAccount { data: UndefinedType, // Error!}
// Good - all types definedstruct MyData { value: u64,}
struct MyAccount { data: MyData, // Works}IDL mismatch
Section titled “IDL mismatch”Regenerate IDL after schema changes:
lumos anchor idl schema.lumos -o target/idl/program.jsonSpace calculation wrong
Section titled “Space calculation wrong”Use lumos anchor space to verify:
lumos anchor space schema.lumos --format rust“Discriminator mismatch” at runtime
Section titled ““Discriminator mismatch” at runtime”This happens when the account struct definition changed but the on-chain account wasn’t migrated. Solutions:
- Development: Close and recreate the account
- Production: Implement account migration logic
Generated code has compile errors
Section titled “Generated code has compile errors”Check your schema for:
- Reserved Rust keywords used as field names (
type,move,ref) - Circular type references
- Missing
#[solana]attribute
// Bad - 'type' is reservedstruct Token { type: u8, // Error!}
// Good - use different namestruct Token { token_type: u8,}TypeScript types don’t match runtime values
Section titled “TypeScript types don’t match runtime values”Ensure you regenerated TypeScript after schema changes:
lumos generate schema.lumos --lang ts -o client/types.tsAnchor test fails with “Account not found”
Section titled “Anchor test fails with “Account not found””The account might not be initialized. Check:
- Account is created before being accessed
- PDA seeds match between init and access
- Program ID is correct
”Program failed to complete” without details
Section titled “”Program failed to complete” without details”Enable Anchor’s verbose logging:
RUST_LOG=solana_runtime::system_instruction_processor=trace anchor testHow to update schema without breaking existing accounts?
Section titled “How to update schema without breaking existing accounts?”Follow these rules for backward compatibility:
- ✅ Add new optional fields at the end
- ✅ Add new enum variants at the end
- ❌ Don’t remove or reorder fields
- ❌ Don’t change field types
See Schema Migrations for detailed guidance.