This skill should be used when the user asks to "build a Solana program", "write Anchor code", "create a PDA", "work with SPL tokens", "test with anchor-bankrun", "fuzz test with Trident", "secure my Solana program", "create an NFT with MPL Core", "optimize compute units", or mentions Anchor constraints, account validation, CPI patterns, Vitest testing, or Solana security auditing.
You are a senior Solana Anchor engineer with extensive experience using the Anchor CLI, Solana CLI, Metaplex NFTs, and Trident-based fuzz testing.
Account Model Mindset: Programs are stateless executables operating on accounts passed to them. Program state/data
lives in those accounts. Consult references/ACCOUNT_MODEL.md for Program Derived Addresses (PDA) patterns and rent
calculations.
Critical Constraints:
#[derive(InitSpace)] when defining the account struct and
[STRUCT_NAME]::INIT_SPACE - when init-ializing or realloc-ating the on-chain account in the
#[derive(Accounts)] struct (consult references/ACCOUNT_MODEL.md)references/TRANSACTIONS.md)Anchor is the primary framework. Prefer over native solana-program for:
programs/{name}/src/
├── lib.rs # module declarations, "#[program]", instruction exports
├── instructions/
│ ├── mod.rs # re-exports all Instructions
│ ├── *.rs # state-changing Ixs with "#[derive(Accounts)]" + "handler()"
│ └── view/ # read-only instructions (no state mutation)
├── state/ # account structs for on-chain data
└── utils/
├── errors.rs # "#[error_code]" enum
├── events.rs # "#[event]" structs
├── constants.rs # seeds, program IDs
└── validations.rs # validation functions
Structure account validation in logical sections (see create_with_timestamps.rs, claim.rs):
| Category | Description | Examples |
|---|---|---|
| USER ACCOUNTS | Signers, protocol roles and their ATAs | creator: Signer, creator_ata, recipient |
| PROTOCOL ACCOUNTS | Global protocol state (treasury, etc.) | treasury: Account<Treasury> |
| COLLECTION ACCOUNTS | NFT collection state (if applicable) | nft_collection_data, nft_collection_mint |
| ENTITY ACCOUNTS | Per-entity state (either for stream or campaign) | stream_data, stream_data_ata, campaign |
| PROGRAM ACCOUNTS | External programs | token_program, associated_token_program |
| SYSTEM ACCOUNTS | System-level | system_program, rent |
Key account type patterns (use #[instruction() if input parameters are needed for seeds or PDA derivation):
#[derive(Accounts)]
#[instruction(salt: u128)]
pub struct CreateWithTimestamps<'info> {
// ------------------------------------------------------------------------ //
// USER ACCOUNTS //
// ------------------------------------------------------------------------ //
#[account(mut)]
pub creator: Signer<'info>,
#[account(
mut,
associated_token::mint = deposit_token_mint,
associated_token::authority = creator,
associated_token::token_program = deposit_token_program
)]
pub creator_ata: Box<InterfaceAccount<'info, TokenAccount>>,
/// CHECK: The recipient may be any account
pub recipient: UncheckedAccount<'info>,
// ------------------------------------------------------------------------ //
// STREAM ACCOUNTS //
// ------------------------------------------------------------------------ //
#[account(
init,
payer = creator,
space = 8 + StreamData::INIT_SPACE,
seeds = [STREAM_DATA, stream_nft_mint.key().as_ref()],
bump
)]
pub stream_data: Box<Account<'info, StreamData>>,
// ------------------------------------------------------------------------ //
// PROGRAM ACCOUNTS //
// ------------------------------------------------------------------------ //
pub deposit_token_program: Interface<'info, TokenInterface>,
}
Structure handlers with validation first, then state updates and interactions, then event emission:
pub fn handler(ctx: Context<CreateWithTimestamps>, deposit_amount: u64, ...) -> Result<()> {
// Validate parameters
check_create(deposit_amount, start_time, cliff_time, end_time, ...)?;
// Update state
ctx.accounts.stream_data.create(...)?;
// Transfer tokens
transfer_tokens(creator_ata, stream_data_ata, creator, ...)?;
// Emit event for indexers
emit!(CreateLockupLinearStream { salt, deposit_token_mint, ... });
Ok(())
}
| Pattern | When to Use |
|---|---|
Box<Account<>> | Large accounts to reduce stack usage |
InterfaceAccount<TokenAccount> | Token/Token2022 compatibility |
UncheckedAccount + /// CHECK | Flexible validation (document the check) |
Extract to utils/validations.rs | Instruction validation logic |
Emit events for all state changes (critical for indexers):
#[event]
pub struct StreamCreated { pub stream_id: Pubkey, pub amount: u64 }
Define contextual error messages:
#[error_code]
pub enum ErrorCode {
#[msg("Deposit amount must be greater than zero")]
DepositAmountZero,
}
Non-negotiable security practices:
account.owner == expected_program (automatic for Account<>)#[account(constraint)] macro)checked_add, checked_sub, checked_mul for all arithmeticConsult references/SECURITY.md for comprehensive vulnerability patterns and audit checklist.
For NFT ownership tokens, Metaplex Core provides efficient single-account NFTs (~0.0029 SOL vs ~0.022 SOL for Token Metadata).
Consult references/MPL_CORE.md for CPI builders and collection patterns.
Building programs automatically generates TypeScript bindings:
just build # Build all programs + generate TS types
just build sablier_lockup # Build specific program
The build process:
anchor build → compiles Rust → generates target/idl/{program}.jsonjust codegen → generates target/types/{program}_errors.ts and {program}_structs.tsGenerated types provide type-safe access to on-chain data in tests:
import type { StreamData } from "../target/types/sablier_lockup_structs";
import { ProgramErrorCode } from "../target/types/sablier_lockup_errors";
const streamData: StreamData = await program.account.streamData.fetch(pda);
await expectToThrow(ctx.withdraw(), ProgramErrorCode.StreamDepleted);
Consult references/CODEGEN.md for type mappings, script architecture and troubleshooting.
Fast, deterministic testing without validator startup. Key pattern:
class LockupTestContext extends TestContext {
async setUp() {
await super.setUp("sablier_lockup", programId);
this.program = new Program<SablierLockup>(IDL, this.provider);
}
}
Consult references/TESTING.md for complete test context patterns, time travel, and assertions.
Property-based fuzzing for edge case discovery:
trident-tests/fuzz_{program}/
├── test_fuzz.rs # Entry point with flows
├── instructions/*.rs # TridentInstruction definitions
└── helpers/*_math.rs # Replicated math for invariants
Consult references/FUZZ_TESTING.md for instruction hooks, invariants, and time warping.
Detailed documentation for specific domains:
| File | Content |
|---|---|
references/ACCOUNT_MODEL.md | PDA derivation, rent, account creation |
references/TRANSACTIONS.md | Tx limits, CU optimization |
references/SECURITY.md | Vulnerabilities, audit checklist, protections |
references/TESTING.md | Vitest + anchor-bankrun patterns |
references/FUZZ_TESTING.md | Trident setup, invariants, flows |
references/MPL_CORE.md | Metaplex Core NFT integration |
references/CODEGEN.md | TypeScript codegen from IDL, type mappings |
Working code examples demonstrating key patterns:
| File | Content |
|---|---|
examples/withdraw_instruction.rs | Complete Anchor instruction example |
examples/test_pattern.ts | Vitest + anchor-bankrun test structure |
Fetch latest docs before implementation—the ecosystem moves fast:
If you don't find the information you're looking for in this Skill, use Context7 MCP, as a default backup, to retrieve the current documentation for Anchor, Solana, Metaplex and Trident.