Generated Code Examples
This page shows complete examples of what LUMOS generates from .lumos schemas. Each example includes the input schema and both Rust and TypeScript outputs.
Simple Account Structure
Section titled “Simple Account Structure”Input Schema
Section titled “Input Schema”#[solana]#[account]struct PlayerAccount { wallet: PublicKey, level: u16, experience: u64,}Generated Rust
Section titled “Generated Rust”use anchor_lang::prelude::*;
#[account]pub struct PlayerAccount { pub wallet: Pubkey, pub level: u16, pub experience: u64,}Key points:
#[account]macro applied (Anchor integration)PublicKey→Pubkeyconversion- All fields are
pub - No manual Borsh derives needed (handled by Anchor)
Generated TypeScript
Section titled “Generated TypeScript”import { PublicKey } from '@solana/web3.js';import * as borsh from '@coral-xyz/borsh';
export interface PlayerAccount { wallet: PublicKey; level: number; experience: number;}
export const PlayerAccountBorshSchema = borsh.struct([ borsh.publicKey('wallet'), borsh.u16('level'), borsh.u64('experience'),]);Key points:
- Interface for TypeScript type safety
- Borsh schema for serialization/deserialization
u16,u64→number(JavaScript safe integers)
Event Data (No #[account])
Section titled “Event Data (No #[account])”Input Schema
Section titled “Input Schema”#[solana]struct MatchResult { winner: PublicKey, loser: Option<PublicKey>, score: u64, timestamp: i64,}Generated Rust
Section titled “Generated Rust”use anchor_lang::prelude::*;
pub struct MatchResult { pub winner: Pubkey, pub loser: Option<Pubkey>, pub score: u64, pub timestamp: i64,}
impl borsh::ser::BorshSerialize for MatchResult { fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> { borsh::BorshSerialize::serialize(&self.winner, writer)?; borsh::BorshSerialize::serialize(&self.loser, writer)?; borsh::BorshSerialize::serialize(&self.score, writer)?; borsh::BorshSerialize::serialize(&self.timestamp, writer)?; Ok(()) }}
impl borsh::de::BorshDeserialize for MatchResult { fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> { Ok(Self { winner: borsh::BorshDeserialize::deserialize_reader(reader)?, loser: borsh::BorshDeserialize::deserialize_reader(reader)?, score: borsh::BorshDeserialize::deserialize_reader(reader)?, timestamp: borsh::BorshDeserialize::deserialize_reader(reader)?, }) }}Key differences:
- NO
#[account]macro - Manual Borsh trait implementations
- Used for events, instruction arguments, return values
Generated TypeScript
Section titled “Generated TypeScript”import { PublicKey } from '@solana/web3.js';import * as borsh from '@coral-xyz/borsh';
export interface MatchResult { winner: PublicKey; loser: PublicKey | undefined; score: number; timestamp: number;}
export const MatchResultBorshSchema = borsh.struct([ borsh.publicKey('winner'), borsh.option(borsh.publicKey(), 'loser'), borsh.u64('score'), borsh.i64('timestamp'),]);Key points:
Option<PublicKey>→PublicKey | undefinedborsh.option()handles nullable fields
Complex Types (Vectors)
Section titled “Complex Types (Vectors)”Input Schema
Section titled “Input Schema”#[solana]#[account]struct Inventory { owner: PublicKey, items: [PublicKey], quantities: [u32],}Generated Rust
Section titled “Generated Rust”use anchor_lang::prelude::*;
#[account]pub struct Inventory { pub owner: Pubkey, pub items: Vec<Pubkey>, pub quantities: Vec<u32>,}Key points:
[T]→Vec<T>in Rust- Dynamic-length arrays
Generated TypeScript
Section titled “Generated TypeScript”import { PublicKey } from '@solana/web3.js';import * as borsh from '@coral-xyz/borsh';
export interface Inventory { owner: PublicKey; items: PublicKey[]; quantities: number[];}
export const InventoryBorshSchema = borsh.struct([ borsh.publicKey('owner'), borsh.vec(borsh.publicKey(), 'items'), borsh.vec(borsh.u32(), 'quantities'),]);Key points:
[T]→T[]in TypeScriptborsh.vec()for dynamic arrays
Enum Types
Section titled “Enum Types”Input Schema
Section titled “Input Schema”#[solana]enum GameState { Active, Paused { reason: String }, Finished { winner: PublicKey, score: u64 },}Generated Rust
Section titled “Generated Rust”use anchor_lang::prelude::*;
pub enum GameState { Active, Paused { reason: String }, Finished { winner: Pubkey, score: u64 },}
impl borsh::ser::BorshSerialize for GameState { fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> { match self { Self::Active => { 0u8.serialize(writer)?; } Self::Paused { reason } => { 1u8.serialize(writer)?; reason.serialize(writer)?; } Self::Finished { winner, score } => { 2u8.serialize(writer)?; winner.serialize(writer)?; score.serialize(writer)?; } } Ok(()) }}
impl borsh::de::BorshDeserialize for GameState { fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> { let variant: u8 = borsh::BorshDeserialize::deserialize_reader(reader)?; match variant { 0 => Ok(Self::Active), 1 => Ok(Self::Paused { reason: borsh::BorshDeserialize::deserialize_reader(reader)?, }), 2 => Ok(Self::Finished { winner: borsh::BorshDeserialize::deserialize_reader(reader)?, score: borsh::BorshDeserialize::deserialize_reader(reader)?, }), _ => Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("Invalid GameState variant: {}", variant), )), } }}Key points:
- Sequential discriminants (0, 1, 2…)
- Match-based serialization
- Error handling for invalid variants
Generated TypeScript
Section titled “Generated TypeScript”import { PublicKey } from '@solana/web3.js';import * as borsh from '@coral-xyz/borsh';
export type GameState = | { kind: 'Active' } | { kind: 'Paused'; reason: string } | { kind: 'Finished'; winner: PublicKey; score: number };
export const GameStateBorshSchema = borsh.rustEnum([ borsh.unit('Active'), borsh.struct([borsh.string('reason')], 'Paused'), borsh.struct([ borsh.publicKey('winner'), borsh.u64('score'), ], 'Finished'),]);Key points:
- Discriminated union type with
kindfield - TypeScript type narrowing support
borsh.rustEnum()for Rust-style enums
Non-Solana Types (Pure Borsh)
Section titled “Non-Solana Types (Pure Borsh)”Input Schema
Section titled “Input Schema”struct GenericData { id: u64, value: String, metadata: Option<String>,}Note: No #[solana] attribute
Generated Rust
Section titled “Generated Rust”use borsh::{BorshSerialize, BorshDeserialize};
#[derive(BorshSerialize, BorshDeserialize)]pub struct GenericData { pub id: u64, pub value: String, pub metadata: Option<String>,}Key differences:
- Uses
borshcrate directly (notanchor_lang) #[derive]macros for serialization- No Solana-specific types available
Generated TypeScript
Section titled “Generated TypeScript”import * as borsh from '@coral-xyz/borsh';
export interface GenericData { id: number; value: string; metadata: string | undefined;}
export const GenericDataBorshSchema = borsh.struct([ borsh.u64('id'), borsh.string('value'), borsh.option(borsh.string(), 'metadata'),]);Same as Solana types - Borsh schema is identical whether #[solana] is used or not.
Import Strategy
Section titled “Import Strategy”LUMOS intelligently chooses imports based on attributes in your entire module:
Scenario 1: Any #[account] Present
Section titled “Scenario 1: Any #[account] Present”// ANY struct has #[account] → Use Anchor imports for ENTIRE moduleuse anchor_lang::prelude::*;
#[account]pub struct PlayerAccount { /* ... */ }
pub struct GameEvent { /* ... */ } // Also uses Anchor importsScenario 2: Only #[solana] (No #[account])
Section titled “Scenario 2: Only #[solana] (No #[account])”// Has #[solana] but no #[account] → Still use Anchoruse anchor_lang::prelude::*;
pub struct MatchResult { /* ... */ }// Manual Borsh impls addedScenario 3: No Solana Attributes
Section titled “Scenario 3: No Solana Attributes”// Pure Borsh typesuse borsh::{BorshSerialize, BorshDeserialize};
#[derive(BorshSerialize, BorshDeserialize)]pub struct GenericData { /* ... */ }Context-Aware Generation
Section titled “Context-Aware Generation”LUMOS detects the context of your entire schema file to generate optimal code.
Example: Mixed Module
Section titled “Example: Mixed Module”Input:
#[solana]#[account]struct PlayerAccount { wallet: PublicKey, level: u16,}
#[solana]struct GameEvent { player: PublicKey, action: String,}
#[solana]enum GameState { Active, Paused,}Generated Rust:
use anchor_lang::prelude::*;
#[account]pub struct PlayerAccount { pub wallet: Pubkey, pub level: u16,}
pub struct GameEvent { pub player: Pubkey, pub action: String,}
impl borsh::ser::BorshSerialize for GameEvent { /* ... */ }impl borsh::de::BorshDeserialize for GameEvent { /* ... */ }
pub enum GameState { Active, Paused,}
impl borsh::ser::BorshSerialize for GameState { /* ... */ }impl borsh::de::BorshDeserialize for GameState { /* ... */ }Key insight: Since ONE type has #[account], LUMOS uses anchor_lang::prelude::* for the entire module.
Using Generated Code
Section titled “Using Generated Code”In Anchor Programs (Rust)
Section titled “In Anchor Programs (Rust)”use anchor_lang::prelude::*;
// Import your generated typesuse crate::generated::*;
#[program]pub mod my_game { use super::*;
pub fn create_player(ctx: Context<CreatePlayer>) -> Result<()> { let player = &mut ctx.accounts.player; player.wallet = *ctx.accounts.user.key; player.level = 1; player.experience = 0; Ok(()) }}
#[derive(Accounts)]pub struct CreatePlayer<'info> { #[account( init, payer = user, space = 8 + std::mem::size_of::<PlayerAccount>() )] pub player: Account<'info, PlayerAccount>, // ← Generated type #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System>,}In TypeScript Clients
Section titled “In TypeScript Clients”import { PlayerAccount, PlayerAccountBorshSchema } from './generated';import { PublicKey } from '@solana/web3.js';
// Serialize account dataconst accountData: PlayerAccount = { wallet: new PublicKey('...'), level: 5, experience: 1000,};
const serialized = borsh.serialize( PlayerAccountBorshSchema, accountData);
// Deserialize from on-chain dataconst buffer = await connection.getAccountInfo(accountAddress);const deserialized = borsh.deserialize( PlayerAccountBorshSchema, buffer.data) as PlayerAccount;
console.log(`Level: ${deserialized.level}`);console.log(`XP: ${deserialized.experience}`);Best Practices
Section titled “Best Practices”Formatting
Section titled “Formatting”Generated code is automatically formatted if you have the formatters installed:
- Rust:
rustfmt(viacargo fmt) - TypeScript:
prettier
Disable formatting:
lumos generate schema.lumos --no-formatSee Also
Section titled “See Also”- CLI Commands - How to generate code
- Type System - All supported types
- Attributes -
#[solana],#[account] - Quick Start - End-to-end tutorial