This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
MetaDAO Futarchy Protocol - Solana programs for market-driven governance and token launches. Uses Anchor 0.29.0, Solana 1.17.34, Rust 1.78.0.
# Build all programs
anchor build
# Build specific program
anchor build -p futarchy
anchor build -p conditional_vault
# ...et cetera.
# Rebuild Programs, Rebuild SDK and lint (also surfaces any errors within SDK)
./rebuild.sh
# Run all tests (includes build)
anchor test
# Run tests without rebuilding (faster iteration)
anchor test --skip-buildprograms/ # Solana programs (Anchor)
├── futarchy/ # DAO governance with TWAP oracles
├── conditional_vault/ # Conditional tokens for prediction markets
├── v07_launchpad/ # Token launch platform (current)
├── v06_launchpad/ # Previous launchpad version
├── bid_wall/ # Price floor mechanism
├── price_based_performance_package/ # Milestone-based rewards
├── mint_governor/ # Delegated minting authority management
└── damm_v2_cpi/ # Meteora AMM CPI wrapper
sdk/ # TypeScript client library
├── src/v0.3/ - v0.7/ # Versioned SDKs (backward compatible)
└── package.json
tests/ # TypeScript tests (bankrun + mocha)
├── conditionalVault/ # Unit + integration tests per program
├── futarchy/
├── launchpad/
├── bidWall/
├── integration/ # Cross-program workflow tests
├── fixtures/ # Pre-compiled external programs (.so)
└── utils.ts # Testing utilities
scripts/ # Deployment & setup scripts
└── v0.3/ - v0.7/ # Version-specific scripts
vibes/ # Design documents and specs
// In lib.rs - without params
#[program]
pub mod my_program {
#[access_control(ctx.accounts.validate())]
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Initialize::handle(ctx)
}
// With params - use an Args struct
#[access_control(ctx.accounts.validate(&args))]
pub fn do_something(ctx: Context<DoSomething>, args: DoSomethingArgs) -> Result<()> {
DoSomething::handle(ctx, args)
}
}
// In instructions/initialize.rs - no params needed
#[derive(Accounts)]
pub struct Initialize<'info> { /* account constraints */ }
impl Initialize<'_> {
pub fn validate(&self) -> Result<()> {
// Validation logic (or just Ok(()))
Ok(())
}
pub fn handle(ctx: Context<Self>) -> Result<()> {
// Implementation
Ok(())
}
}
// In instructions/do_something.rs - with params
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct DoSomethingArgs {
pub amount: u64,
}
#[derive(Accounts)]
pub struct DoSomething<'info> { /* account constraints */ }
impl DoSomething<'_> {
pub fn validate(&self, args: &DoSomethingArgs) -> Result<()> {
// Validation that needs args
require_gte!(args.amount, 1, MyError::InvalidAmount);
Ok(())
}
pub fn handle(ctx: Context<Self>, args: DoSomethingArgs) -> Result<()> {
// Implementation using args
Ok(())
}
}When writing Anchor account constraints, prefer more specific constraint types over generic constraint:
has_one- when checkingaccount.field == other_account.key()and field name matches account nameaddress- when checking against a known/constant addressconstraint- only when the above don't apply (e.g., field name differs from account name)
// Good - uses has_one since field name matches account name
#[account(has_one = mint @ MyError::InvalidMint)]
pub mint_governor: Account<'info, MintGovernor>,
// Necessary - field name (authorized_minter) differs from account name (performance_package)
#[account(constraint = mint_authority.authorized_minter == performance_package.key() @ MyError::Invalid)]
pub mint_authority: Account<'info, MintAuthority>,For token accounts, prefer associated_token::* over token::* constraints:
associated_token::mint/associated_token::authority- enforces the account is at the canonical ATA address (safer, use for recipient/user-facing accounts)token::mint/token::authority- allows any token account with matching mint/authority (use only when flexibility is intentionally needed, e.g., source accounts where user may fund from non-ATA)
// Good - enforces canonical ATA for recipient
#[account(mut, associated_token::mint = mint, associated_token::authority = recipient)]
pub recipient_ata: Account<'info, TokenAccount>,
// OK - allows flexibility for source accounts
#[account(mut, token::mint = mint, token::authority = funder)]
pub funder_token_account: Account<'info, TokenAccount>,Always use CPI events (#[event_cpi] on accounts structs, emit_cpi! for emission) rather than regular emit!.
When writing validation checks, prefer specific require macros over generic require!:
require_keys_eq!- when comparing twoPubkeyvaluesrequire_eq!- when comparing two values of the same type (requiresDisplaytrait)require_neq!- when asserting two values are not equal (requiresDisplaytrait)require_gt!/require_gte!- for greater than / greater than or equal comparisonsrequire!- for boolean conditions, including enum comparisons where the type doesn't implementDisplay
// Good - specific macros provide better error messages
require_keys_eq!(signer.key(), account.authority, MyError::Unauthorized);
require_eq!(account.count, 0, MyError::InvalidCount); // integers implement Display
require_gte!(args.amount, 1, MyError::InvalidAmount);
// OK - enums typically don't implement Display, so use require!
require!(account.status == Status::Active, MyError::InvalidStatus);
// Avoid - generic require when a specific macro exists
require!(signer.key() == account.authority, MyError::Unauthorized);Always append new error variants to the end of #[error_code] enums. Anchor assigns error codes based on variant position (index), so inserting in the middle shifts all subsequent codes and breaks indexing/client-side error matching for deployed programs.
Always run ./rebuild.sh after modifying any Rust code under programs/. This rebuilds all programs, regenerates the SDK types, and lints — ensuring tests run against your latest changes.
- Add instruction to Rust program in
programs/[program]/src/instructions/ - Update client methods in SDK (
sdk/src/v0.7/) - Add unit tests in
tests/[program]/unit/
Tests use solana-bankrun for deterministic testing without external RPC:
setupBasicDao()- Create a test DAO with mintsadvanceBySlots()- Simulate time progression- Time constants:
TEN_SECONDS_IN_SLOTS,ONE_MINUTE_IN_SLOTS,HOUR_IN_SLOTS,DAY_IN_SLOTS
Getting unique transaction signatures: When testing error cases that call the same instruction multiple times (e.g., verifying an action fails after state changes), add a ComputeBudgetProgram.setComputeUnitLimit() instruction with incrementing values to produce different transaction signatures:
// First call (200_000), second call (200_001), etc.
await client
.someIx({ ... })
.postInstructions([
ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
])
.signers([signer])
.rpc();Do NOT use advanceBySlots() for this purpose - it changes the clock which may affect time-dependent tests.
Token amounts in tests: Use easy-to-read round numbers like hundreds or thousands of tokens. Our standard mint decimals is 6, so:
- 100 tokens =
100_000_000(100 * 10^6) - 1,000 tokens =
1_000_000_000(1000 * 10^6)
This makes test assertions and calculations much easier to verify at a glance.
Isolating tests during development: When developing or debugging tests, use .only to run only the tests you're working on:
// Run only this specific test
it.only("throws error when trying to split tokens after question is resolved", async function () {
// ...
});
// Run only this describe block
describe.only("#split_tokens", function () {
// ...
});This significantly speeds up iteration and makes test output easier to read. Remember to remove .only before finishing development.
Assertion messages: Do not include assertion messages for better readability. The assertion itself should be clear enough:
// Good - no message needed
assert.equal(recipientBalance.toString(), "500000000");
assert.isDefined(ppAccount.status.locked);
// Avoid - unnecessary message
assert.equal(recipientBalance.toString(), "500000000", "Recipient should have 500 tokens");Exceptions: Keep messages in expectError() calls and assert.fail() within try-catch blocks, since those are part of error handling patterns and help identify which check failed.
// Import versioned clients
import { FutarchyClient, ConditionalVaultClient } from "@metadaoproject/futarchy/v0.7";
// Key utilities in sdk/src/v0.7/
// - constants.ts: Program IDs, MAINNET_USDC, SQUADS_PROGRAM_ID
// - PDA derivation: getDaoAddr, getProposalAddr, etc.
// - PriceMath.getAmmPrice for price calculationsImportant: Always use SDK v0.7 imports (@metadaoproject/futarchy/v0.7) for new code. Do not use older SDK versions (v0.3-v0.6).
- Squads Multisig v4 - Governance authority for admin functions
- Meteora DAMM - Concentrated AMM for launches (via damm_v2_cpi)
- OpenBook v2 - DEX integration (fixture in tests)
External programs required for tests. These are pre-compiled .so files in tests/fixtures/:
Critical dependencies (tests will fail without these):
squads_multisig.so- Squads Multisig v4 (SQUADS_PROGRAM_ID)cp_amm.so- Meteora DAMM v2 (DAMM_V2_PROGRAM_ID)mpl_token_metadata.so- Metaplex token metadata
Other fixtures:
openbook_v2.so,openbook_twap.so- OpenBook DEX integrationraydium_cp_swap.so- Raydium integration
"blockstore error": rm -rf .anchor/test-ledger test-ledger
Module resolution errors: cd sdk && yarn build-local && cd .. && yarn install --force
Tests timeout: Increase startup_wait in Anchor.toml
Cargo.lock version error: If Cargo.lock ends up with version = 4, simply change it back to version = 3 to fix lockfile issues. You don't have to remove the lockfile.
| Program | Version | ID |
|---|---|---|
| launchpad | v0.7.0 | moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM |
| bid_wall | v0.7.0 | WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx |
| futarchy | v0.6.0 | FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq |
| conditional_vault | v0.4 | VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg |
| price_based_performance_package | v0.6.0 | pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS |
| mint_governor | v0.7.0 | gvnr27cVeyW3AVf3acL7VCJ5WjGAphytnsgcK1feHyH |
| liquidation | v0.1.0 | LiQnowFbFQdYyZhF4pUbpsrZCjxRTQ1upKJxZ2VXjde |