Attributes
Attributes control how LUMOS generates code for structs and enums. They are written with #[attribute_name] syntax above type definitions.
Overview
Section titled “Overview”| Attribute | Applies To | Purpose |
|---|---|---|
#[solana] | Structs, Enums | Mark as Solana-specific type |
#[account] | Structs only | Generate Anchor #[account] macro |
#[solana]
Section titled “#[solana]”Marks a type as Solana-specific, enabling Solana type support (PublicKey, Signature) and proper imports.
Syntax
Section titled “Syntax”#[solana]struct TypeName { // fields}
#[solana]enum EnumName { // variants}Effects
Section titled “Effects”Rust Generation
Section titled “Rust Generation”With #[solana]:
// Imports Solana/Anchor typesuse anchor_lang::prelude::*;
pub struct TypeName { pub field: Pubkey, // ← PublicKey → Pubkey}Without #[solana]:
// Uses only standard Rust + Borshuse borsh::{BorshSerialize, BorshDeserialize};
#[derive(BorshSerialize, BorshDeserialize)]pub struct TypeName { // Cannot use Pubkey without #[solana]}TypeScript Generation
Section titled “TypeScript Generation”With #[solana]:
import { PublicKey } from '@solana/web3.js';import * as borsh from '@coral-xyz/borsh';
export interface TypeName { field: PublicKey;}
export const TypeNameBorshSchema = borsh.struct([ borsh.publicKey('field'),]);Examples
Section titled “Examples”Simple Solana Struct
Section titled “Simple Solana Struct”#[solana]struct Account { owner: PublicKey, balance: u64,}Generated Rust:
use anchor_lang::prelude::*;
pub struct Account { pub owner: Pubkey, pub balance: u64,}Solana Enum
Section titled “Solana Enum”#[solana]enum TransactionType { Transfer(PublicKey, u64), Stake(PublicKey, u64), Unstake,}Generated Rust:
use anchor_lang::prelude::*;
pub enum TransactionType { Transfer(Pubkey, u64), Stake(Pubkey, u64), Unstake,}Non-Solana Type (No Attribute)
Section titled “Non-Solana Type (No Attribute)”struct GenericData { id: u64, value: String,}Generated Rust:
use borsh::{BorshSerialize, BorshDeserialize};
#[derive(BorshSerialize, BorshDeserialize)]pub struct GenericData { pub id: u64, pub value: String,}#[account]
Section titled “#[account]”Generates Anchor’s #[account] macro for on-chain account data.
Syntax
Section titled “Syntax”#[solana]#[account]struct AccountName { // fields}Important: #[account] requires #[solana] - they must be used together.
Effects
Section titled “Effects”With #[account]
Section titled “With #[account]”#[solana]#[account]struct PlayerAccount { wallet: PublicKey, level: u16,}Generated Rust:
use anchor_lang::prelude::*;
#[account]pub struct PlayerAccount { pub wallet: Pubkey, pub level: u16,}Key points:
- Uses Anchor’s
#[account]macro - Automatically derives
AnchorSerialize,AnchorDeserialize - NO manual
#[derive(BorshSerialize, BorshDeserialize)] - Uses
anchor_lang::prelude::*imports
Without #[account]
Section titled “Without #[account]”#[solana]struct EventData { player: PublicKey, score: u64,}Generated Rust:
use anchor_lang::prelude::*;
pub struct EventData { pub player: Pubkey, pub score: u64,}
impl borsh::BorshSerialize for EventData { /* ... */ }impl borsh::BorshDeserialize for EventData { /* ... */ }Key points:
- NO
#[account]macro - Manual Borsh derives added
- Used for event data, instruction arguments, etc.
When to Use #[account]
Section titled “When to Use #[account]”| Use Case | Attribute | Example |
|---|---|---|
| On-chain account storage | #[account] | User profiles, game state |
| Event data | No #[account] | Transaction logs, notifications |
| Instruction arguments | No #[account] | Function parameters |
| Return values | No #[account] | Query results |
Examples
Section titled “Examples”Player Account (On-Chain Storage)
Section titled “Player Account (On-Chain Storage)”#[solana]#[account]struct PlayerAccount { wallet: PublicKey, level: u16, experience: u64, inventory: [PublicKey],}Usage in Anchor:
#[program]pub mod 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; player.inventory = Vec::new(); Ok(()) }}
#[derive(Accounts)]pub struct CreatePlayer<'info> { #[account( init, payer = user, space = 8 + std::mem::size_of::<PlayerAccount>() )] pub player: Account<'info, PlayerAccount>, // ← Uses generated type #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System>,}Match Result (Event Data, Not Stored)
Section titled “Match Result (Event Data, Not Stored)”#[solana]struct MatchResult { winner: PublicKey, loser: Option<PublicKey>, score: u64, timestamp: i64,}Usage in Anchor:
#[event]pub struct MatchFinished { pub result: MatchResult, // ← Uses generated type (no #[account])}
#[program]pub mod game { use super::*;
pub fn finish_match(ctx: Context<FinishMatch>, result: MatchResult) -> Result<()> { emit!(MatchFinished { result }); Ok(()) }}Attribute Combinations
Section titled “Attribute Combinations”Valid Combinations
Section titled “Valid Combinations”// ✅ Both attributes (on-chain account)#[solana]#[account]struct Account { /* ... */ }
// ✅ Only #[solana] (event data, instructions)#[solana]struct Event { /* ... */ }
// ✅ No attributes (generic Borsh)struct Data { /* ... */ }Invalid Combinations
Section titled “Invalid Combinations”// ❌ #[account] without #[solana]#[account]struct Account { /* ... */ }// Error: #[account] requires #[solana]
// ❌ #[account] on enum#[solana]#[account]enum Status { /* ... */ }// Error: #[account] only applies to structsContext-Aware Generation
Section titled “Context-Aware Generation”LUMOS intelligently detects when to use Anchor imports based on attributes in the entire module.
Example: Mixed Module
Section titled “Example: Mixed Module”#[solana]#[account]struct PlayerAccount { wallet: PublicKey, level: u16,}
#[solana]struct GameEvent { player: PublicKey, action: String,}
#[solana]enum GameState { Active, Paused, Finished,}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,}
pub enum GameState { Active, Paused, Finished,}Key insight: Since ONE type has #[account], LUMOS uses anchor_lang::prelude::* for the entire module.
Import Strategy
Section titled “Import Strategy”LUMOS chooses imports based on attributes:
| Scenario | Imports Used |
|---|---|
Any type has #[account] | use anchor_lang::prelude::*; |
Has #[solana] but no #[account] | use anchor_lang::prelude::*; |
| No Solana attributes | use borsh::{BorshSerialize, BorshDeserialize}; |
Best Practices
Section titled “Best Practices”Troubleshooting
Section titled “Troubleshooting””Pubkey not found”
Section titled “”Pubkey not found””Error:
cannot find type `Pubkey` in this scopeFix: Add #[solana] attribute:
#[solana] // ← Add thisstruct Account { owner: PublicKey,}”#[account] on non-Solana type”
Section titled “”#[account] on non-Solana type””Error:
#[account] requires #[solana] attributeFix: Add #[solana] before #[account]:
#[solana] // ← Add this#[account]struct Account { /* ... */ }“Manual derives with #[account]”
Section titled ““Manual derives with #[account]””Error:
cannot derive `BorshSerialize` when #[account] is presentFix: Remove manual derives - Anchor handles serialization:
// ❌ Don't do this#[solana]#[account]#[derive(BorshSerialize)] // ← Removestruct Account { /* ... */ }
// ✅ Do this#[solana]#[account]struct Account { /* ... */ }See Also
Section titled “See Also”- Type System - Supported types
- Generated Code - See output
- CLI Commands - Generate code