Skip to content

Attributes

Attributes control how LUMOS generates code for structs and enums. They are written with #[attribute_name] syntax above type definitions.

AttributeApplies ToPurpose
#[solana]Structs, EnumsMark as Solana-specific type
#[account]Structs onlyGenerate Anchor #[account] macro

Marks a type as Solana-specific, enabling Solana type support (PublicKey, Signature) and proper imports.

#[solana]
struct TypeName {
// fields
}
#[solana]
enum EnumName {
// variants
}

With #[solana]:

// Imports Solana/Anchor types
use anchor_lang::prelude::*;
pub struct TypeName {
pub field: Pubkey, // ← PublicKey → Pubkey
}

Without #[solana]:

// Uses only standard Rust + Borsh
use borsh::{BorshSerialize, BorshDeserialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct TypeName {
// Cannot use Pubkey without #[solana]
}

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'),
]);

#[solana]
struct Account {
owner: PublicKey,
balance: u64,
}

Generated Rust:

use anchor_lang::prelude::*;
pub struct Account {
pub owner: Pubkey,
pub balance: u64,
}

#[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,
}

struct GenericData {
id: u64,
value: String,
}

Generated Rust:

use borsh::{BorshSerialize, BorshDeserialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct GenericData {
pub id: u64,
pub value: String,
}

Generates Anchor’s #[account] macro for on-chain account data.

#[solana]
#[account]
struct AccountName {
// fields
}

Important: #[account] requires #[solana] - they must be used together.


#[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

#[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.

Use CaseAttributeExample
On-chain account storage#[account]User profiles, game state
Event dataNo #[account]Transaction logs, notifications
Instruction argumentsNo #[account]Function parameters
Return valuesNo #[account]Query results

#[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>,
}

#[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(())
}
}

// ✅ Both attributes (on-chain account)
#[solana]
#[account]
struct Account { /* ... */ }
// ✅ Only #[solana] (event data, instructions)
#[solana]
struct Event { /* ... */ }
// ✅ No attributes (generic Borsh)
struct Data { /* ... */ }

// ❌ #[account] without #[solana]
#[account]
struct Account { /* ... */ }
// Error: #[account] requires #[solana]
// ❌ #[account] on enum
#[solana]
#[account]
enum Status { /* ... */ }
// Error: #[account] only applies to structs

LUMOS intelligently detects when to use Anchor imports based on attributes in the entire module.

schema.lumos
#[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.


LUMOS chooses imports based on attributes:

ScenarioImports Used
Any type has #[account]use anchor_lang::prelude::*;
Has #[solana] but no #[account]use anchor_lang::prelude::*;
No Solana attributesuse borsh::{BorshSerialize, BorshDeserialize};


Error:

cannot find type `Pubkey` in this scope

Fix: Add #[solana] attribute:

#[solana] // ← Add this
struct Account {
owner: PublicKey,
}

Error:

#[account] requires #[solana] attribute

Fix: Add #[solana] before #[account]:

#[solana] // ← Add this
#[account]
struct Account { /* ... */ }

Error:

cannot derive `BorshSerialize` when #[account] is present

Fix: Remove manual derives - Anchor handles serialization:

// ❌ Don't do this
#[solana]
#[account]
#[derive(BorshSerialize)] // ← Remove
struct Account { /* ... */ }
// ✅ Do this
#[solana]
#[account]
struct Account { /* ... */ }