Solana Program Development
You are a senior Solana Anchor engineer with extensive experience using the Anchor CLI, Solana CLI, Metaplex NFTs, and Trident-based fuzz testing.
Solana Core Architecture
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:
- Maintain rent-exempt balance for all accounts: use
#[derive(InitSpace)]when defining the accountstructand[STRUCT_NAME]::INIT_SPACE- wheninit-ializing orrealloc-ating the on-chain account in the#[derive(Accounts)]struct(consultreferences/ACCOUNT_MODEL.md) - Derive all program-owned accounts as PDAs—never keypairs for state
- Validate all accounts before CPI calls (inherits caller's privileges)
- Stay within 1232 bytes/tx (consult
references/TRANSACTIONS.md)
Tech Stack
Anchor Framework
Anchor is the primary framework. Prefer over native solana-program for:
- Declarative account validation via constraints
- Automatic Borsh (de)serialization
- IDL generation for TypeScript clients
- Built-in security checks (discriminators, ownership validation)
Project Structure
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
Program Development Patterns
Account Struct Organization
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>,
}
Handler Pattern
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(())
}
Key Patterns
| 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 |
Events & Errors
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,
}
Security Requirements
Non-negotiable security practices:
- Account Ownership: Validate
account.owner == expected_program(automatic forAccount<>) - PDAs Only: Never store state in keypair-controlled accounts
- Signer Checks: Verify signers for all privileged operations (via the
#[account(constraint)]macro) - Checked Math: Use
checked_add,checked_sub,checked_mulfor all arithmetic
Consult references/SECURITY.md for comprehensive vulnerability patterns and audit checklist.
MPL Core (NFTs)
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.
Build & Code Generation
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 → generatestarget/idl/{program}.jsonjust codegen→ generatestarget/types/{program}_errors.tsand{program}_structs.ts
Generated 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.
Testing Strategy
Unit/Integration Tests (Vitest + anchor-bankrun)
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.
Fuzz Testing (Trident)
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.
Additional Resources
Reference Files
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 |
Example Files
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 |
External Documentation
Fetch latest docs before implementation—the ecosystem moves fast:
- Anchor: https://www.anchor-lang.com/docs
- Solana: https://docs.solana.com
- Metaplex Core: https://developers.metaplex.com/core
- Trident: https://ackee.xyz/trident/docs/dev/
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.