From 59f23f80c3a630cb2739da67c4dda93491498f95 Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 3 Feb 2026 16:45:33 -0800 Subject: [PATCH] add CLAUDE.md --- CLAUDE.md | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..b0b3d1732 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,286 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +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 & Test Commands + +```bash +# 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-build +``` + +## Project Structure + +``` +programs/ # 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 +``` + +## Program Development Patterns + +### Instruction Structure (Anchor) +```rust +// In lib.rs - without params +#[program] +pub mod my_program { + #[access_control(ctx.accounts.validate())] + pub fn initialize(ctx: Context) -> Result<()> { + Initialize::handle(ctx) + } + + // With params - use an Args struct + #[access_control(ctx.accounts.validate(&args))] + pub fn do_something(ctx: Context, 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) -> 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, args: DoSomethingArgs) -> Result<()> { + // Implementation using args + Ok(()) + } +} +``` + +### Account Constraints +When writing Anchor account constraints, prefer more specific constraint types over generic `constraint`: +1. `has_one` - when checking `account.field == other_account.key()` and field name matches account name +2. `address` - when checking against a known/constant address +3. `constraint` - only when the above don't apply (e.g., field name differs from account name) + +```rust +// 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>, +``` + +### Token Account Constraints +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) + +```rust +// 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>, +``` + +### Require Macros +When writing validation checks, prefer specific require macros over generic `require!`: +1. `require_keys_eq!` - when comparing two `Pubkey` values +2. `require_eq!` - when comparing two values of the same type (requires `Display` trait) +3. `require_neq!` - when asserting two values are not equal (requires `Display` trait) +4. `require_gt!` / `require_gte!` - for greater than / greater than or equal comparisons +5. `require!` - for boolean conditions, including enum comparisons where the type doesn't implement `Display` + +```rust +// 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); +``` + +### Adding New Instructions +1. Add instruction to Rust program in `programs/[program]/src/instructions/` +2. Update client methods in SDK (`sdk/src/v0.7/`) +3. Add unit tests in `tests/[program]/unit/` +4. Run `./rebuild.sh` to sync types + +### Testing with Bankrun +Tests use `solana-bankrun` for deterministic testing without external RPC: +- `setupBasicDao()` - Create a test DAO with mints +- `advanceBySlots()` - 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: + +```typescript +// 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: + +```typescript +// 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: + +```typescript +// 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. + +## SDK Usage + +```typescript +// 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 calculations +``` + +**Important:** Always use SDK v0.7 imports (`@metadaoproject/futarchy/v0.7`) for new code. Do not use older SDK versions (v0.3-v0.6). + +## Key External Dependencies + +- **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) + +## Test Fixtures + +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 integration +- `raydium_cp_swap.so` - Raydium integration + +## Troubleshooting + +**"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. + +## Mainnet Program IDs + +| 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` |