|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +## Build & Test Commands |
| 10 | + |
| 11 | +```bash |
| 12 | +# Build all programs |
| 13 | +anchor build |
| 14 | + |
| 15 | +# Build specific program |
| 16 | +anchor build -p futarchy |
| 17 | +anchor build -p conditional_vault |
| 18 | +# ...et cetera. |
| 19 | + |
| 20 | +# Rebuild Programs, Rebuild SDK and lint (also surfaces any errors within SDK) |
| 21 | +./rebuild.sh |
| 22 | + |
| 23 | +# Run all tests (includes build) |
| 24 | +anchor test |
| 25 | + |
| 26 | +# Run tests without rebuilding (faster iteration) |
| 27 | +anchor test --skip-build |
| 28 | +``` |
| 29 | + |
| 30 | +## Project Structure |
| 31 | + |
| 32 | +``` |
| 33 | +programs/ # Solana programs (Anchor) |
| 34 | +├── futarchy/ # DAO governance with TWAP oracles |
| 35 | +├── conditional_vault/ # Conditional tokens for prediction markets |
| 36 | +├── v07_launchpad/ # Token launch platform (current) |
| 37 | +├── v06_launchpad/ # Previous launchpad version |
| 38 | +├── bid_wall/ # Price floor mechanism |
| 39 | +├── price_based_performance_package/ # Milestone-based rewards |
| 40 | +├── mint_governor/ # Delegated minting authority management |
| 41 | +└── damm_v2_cpi/ # Meteora AMM CPI wrapper |
| 42 | +
|
| 43 | +sdk/ # TypeScript client library |
| 44 | +├── src/v0.3/ - v0.7/ # Versioned SDKs (backward compatible) |
| 45 | +└── package.json |
| 46 | +
|
| 47 | +tests/ # TypeScript tests (bankrun + mocha) |
| 48 | +├── conditionalVault/ # Unit + integration tests per program |
| 49 | +├── futarchy/ |
| 50 | +├── launchpad/ |
| 51 | +├── bidWall/ |
| 52 | +├── integration/ # Cross-program workflow tests |
| 53 | +├── fixtures/ # Pre-compiled external programs (.so) |
| 54 | +└── utils.ts # Testing utilities |
| 55 | +
|
| 56 | +scripts/ # Deployment & setup scripts |
| 57 | +└── v0.3/ - v0.7/ # Version-specific scripts |
| 58 | +
|
| 59 | +vibes/ # Design documents and specs |
| 60 | +``` |
| 61 | + |
| 62 | +## Program Development Patterns |
| 63 | + |
| 64 | +### Instruction Structure (Anchor) |
| 65 | +```rust |
| 66 | +// In lib.rs - without params |
| 67 | +#[program] |
| 68 | +pub mod my_program { |
| 69 | + #[access_control(ctx.accounts.validate())] |
| 70 | + pub fn initialize(ctx: Context<Initialize>) -> Result<()> { |
| 71 | + Initialize::handle(ctx) |
| 72 | + } |
| 73 | + |
| 74 | + // With params - use an Args struct |
| 75 | + #[access_control(ctx.accounts.validate(&args))] |
| 76 | + pub fn do_something(ctx: Context<DoSomething>, args: DoSomethingArgs) -> Result<()> { |
| 77 | + DoSomething::handle(ctx, args) |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +// In instructions/initialize.rs - no params needed |
| 82 | +#[derive(Accounts)] |
| 83 | +pub struct Initialize<'info> { /* account constraints */ } |
| 84 | + |
| 85 | +impl Initialize<'_> { |
| 86 | + pub fn validate(&self) -> Result<()> { |
| 87 | + // Validation logic (or just Ok(())) |
| 88 | + Ok(()) |
| 89 | + } |
| 90 | + |
| 91 | + pub fn handle(ctx: Context<Self>) -> Result<()> { |
| 92 | + // Implementation |
| 93 | + Ok(()) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +// In instructions/do_something.rs - with params |
| 98 | +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] |
| 99 | +pub struct DoSomethingArgs { |
| 100 | + pub amount: u64, |
| 101 | +} |
| 102 | + |
| 103 | +#[derive(Accounts)] |
| 104 | +pub struct DoSomething<'info> { /* account constraints */ } |
| 105 | + |
| 106 | +impl DoSomething<'_> { |
| 107 | + pub fn validate(&self, args: &DoSomethingArgs) -> Result<()> { |
| 108 | + // Validation that needs args |
| 109 | + require_gte!(args.amount, 1, MyError::InvalidAmount); |
| 110 | + Ok(()) |
| 111 | + } |
| 112 | + |
| 113 | + pub fn handle(ctx: Context<Self>, args: DoSomethingArgs) -> Result<()> { |
| 114 | + // Implementation using args |
| 115 | + Ok(()) |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +### Account Constraints |
| 121 | +When writing Anchor account constraints, prefer more specific constraint types over generic `constraint`: |
| 122 | +1. `has_one` - when checking `account.field == other_account.key()` and field name matches account name |
| 123 | +2. `address` - when checking against a known/constant address |
| 124 | +3. `constraint` - only when the above don't apply (e.g., field name differs from account name) |
| 125 | + |
| 126 | +```rust |
| 127 | +// Good - uses has_one since field name matches account name |
| 128 | +#[account(has_one = mint @ MyError::InvalidMint)] |
| 129 | +pub mint_governor: Account<'info, MintGovernor>, |
| 130 | + |
| 131 | +// Necessary - field name (authorized_minter) differs from account name (performance_package) |
| 132 | +#[account(constraint = mint_authority.authorized_minter == performance_package.key() @ MyError::Invalid)] |
| 133 | +pub mint_authority: Account<'info, MintAuthority>, |
| 134 | +``` |
| 135 | + |
| 136 | +### Token Account Constraints |
| 137 | +For token accounts, prefer `associated_token::*` over `token::*` constraints: |
| 138 | +- `associated_token::mint` / `associated_token::authority` - enforces the account is at the canonical ATA address (safer, use for recipient/user-facing accounts) |
| 139 | +- `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) |
| 140 | + |
| 141 | +```rust |
| 142 | +// Good - enforces canonical ATA for recipient |
| 143 | +#[account(mut, associated_token::mint = mint, associated_token::authority = recipient)] |
| 144 | +pub recipient_ata: Account<'info, TokenAccount>, |
| 145 | + |
| 146 | +// OK - allows flexibility for source accounts |
| 147 | +#[account(mut, token::mint = mint, token::authority = funder)] |
| 148 | +pub funder_token_account: Account<'info, TokenAccount>, |
| 149 | +``` |
| 150 | + |
| 151 | +### Require Macros |
| 152 | +When writing validation checks, prefer specific require macros over generic `require!`: |
| 153 | +1. `require_keys_eq!` - when comparing two `Pubkey` values |
| 154 | +2. `require_eq!` - when comparing two values of the same type (requires `Display` trait) |
| 155 | +3. `require_neq!` - when asserting two values are not equal (requires `Display` trait) |
| 156 | +4. `require_gt!` / `require_gte!` - for greater than / greater than or equal comparisons |
| 157 | +5. `require!` - for boolean conditions, including enum comparisons where the type doesn't implement `Display` |
| 158 | + |
| 159 | +```rust |
| 160 | +// Good - specific macros provide better error messages |
| 161 | +require_keys_eq!(signer.key(), account.authority, MyError::Unauthorized); |
| 162 | +require_eq!(account.count, 0, MyError::InvalidCount); // integers implement Display |
| 163 | +require_gte!(args.amount, 1, MyError::InvalidAmount); |
| 164 | + |
| 165 | +// OK - enums typically don't implement Display, so use require! |
| 166 | +require!(account.status == Status::Active, MyError::InvalidStatus); |
| 167 | + |
| 168 | +// Avoid - generic require when a specific macro exists |
| 169 | +require!(signer.key() == account.authority, MyError::Unauthorized); |
| 170 | +``` |
| 171 | + |
| 172 | +### Adding New Instructions |
| 173 | +1. Add instruction to Rust program in `programs/[program]/src/instructions/` |
| 174 | +2. Update client methods in SDK (`sdk/src/v0.7/`) |
| 175 | +3. Add unit tests in `tests/[program]/unit/` |
| 176 | +4. Run `./rebuild.sh` to sync types |
| 177 | + |
| 178 | +### Testing with Bankrun |
| 179 | +Tests use `solana-bankrun` for deterministic testing without external RPC: |
| 180 | +- `setupBasicDao()` - Create a test DAO with mints |
| 181 | +- `advanceBySlots()` - Simulate time progression |
| 182 | +- Time constants: `TEN_SECONDS_IN_SLOTS`, `ONE_MINUTE_IN_SLOTS`, `HOUR_IN_SLOTS`, `DAY_IN_SLOTS` |
| 183 | + |
| 184 | +**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: |
| 185 | + |
| 186 | +```typescript |
| 187 | +// First call (200_000), second call (200_001), etc. |
| 188 | +await client |
| 189 | + .someIx({ ... }) |
| 190 | + .postInstructions([ |
| 191 | + ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), |
| 192 | + ]) |
| 193 | + .signers([signer]) |
| 194 | + .rpc(); |
| 195 | +``` |
| 196 | + |
| 197 | +Do NOT use `advanceBySlots()` for this purpose - it changes the clock which may affect time-dependent tests. |
| 198 | + |
| 199 | +**Token amounts in tests:** Use easy-to-read round numbers like hundreds or thousands of tokens. Our standard mint decimals is 6, so: |
| 200 | +- 100 tokens = `100_000_000` (100 * 10^6) |
| 201 | +- 1,000 tokens = `1_000_000_000` (1000 * 10^6) |
| 202 | + |
| 203 | +This makes test assertions and calculations much easier to verify at a glance. |
| 204 | + |
| 205 | +**Isolating tests during development:** When developing or debugging tests, use `.only` to run only the tests you're working on: |
| 206 | + |
| 207 | +```typescript |
| 208 | +// Run only this specific test |
| 209 | +it.only("throws error when trying to split tokens after question is resolved", async function () { |
| 210 | + // ... |
| 211 | +}); |
| 212 | + |
| 213 | +// Run only this describe block |
| 214 | +describe.only("#split_tokens", function () { |
| 215 | + // ... |
| 216 | +}); |
| 217 | +``` |
| 218 | + |
| 219 | +This significantly speeds up iteration and makes test output easier to read. Remember to remove `.only` before finishing development. |
| 220 | + |
| 221 | +**Assertion messages:** Do not include assertion messages for better readability. The assertion itself should be clear enough: |
| 222 | + |
| 223 | +```typescript |
| 224 | +// Good - no message needed |
| 225 | +assert.equal(recipientBalance.toString(), "500000000"); |
| 226 | +assert.isDefined(ppAccount.status.locked); |
| 227 | + |
| 228 | +// Avoid - unnecessary message |
| 229 | +assert.equal(recipientBalance.toString(), "500000000", "Recipient should have 500 tokens"); |
| 230 | +``` |
| 231 | + |
| 232 | +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. |
| 233 | + |
| 234 | +## SDK Usage |
| 235 | + |
| 236 | +```typescript |
| 237 | +// Import versioned clients |
| 238 | +import { FutarchyClient, ConditionalVaultClient } from "@metadaoproject/futarchy/v0.7"; |
| 239 | + |
| 240 | +// Key utilities in sdk/src/v0.7/ |
| 241 | +// - constants.ts: Program IDs, MAINNET_USDC, SQUADS_PROGRAM_ID |
| 242 | +// - PDA derivation: getDaoAddr, getProposalAddr, etc. |
| 243 | +// - PriceMath.getAmmPrice for price calculations |
| 244 | +``` |
| 245 | + |
| 246 | +**Important:** Always use SDK v0.7 imports (`@metadaoproject/futarchy/v0.7`) for new code. Do not use older SDK versions (v0.3-v0.6). |
| 247 | + |
| 248 | +## Key External Dependencies |
| 249 | + |
| 250 | +- **Squads Multisig v4** - Governance authority for admin functions |
| 251 | +- **Meteora DAMM** - Concentrated AMM for launches (via damm_v2_cpi) |
| 252 | +- **OpenBook v2** - DEX integration (fixture in tests) |
| 253 | + |
| 254 | +## Test Fixtures |
| 255 | + |
| 256 | +External programs required for tests. These are pre-compiled `.so` files in `tests/fixtures/`: |
| 257 | + |
| 258 | +**Critical dependencies (tests will fail without these):** |
| 259 | +- `squads_multisig.so` - Squads Multisig v4 (`SQUADS_PROGRAM_ID`) |
| 260 | +- `cp_amm.so` - Meteora DAMM v2 (`DAMM_V2_PROGRAM_ID`) |
| 261 | +- `mpl_token_metadata.so` - Metaplex token metadata |
| 262 | + |
| 263 | +**Other fixtures:** |
| 264 | +- `openbook_v2.so`, `openbook_twap.so` - OpenBook DEX integration |
| 265 | +- `raydium_cp_swap.so` - Raydium integration |
| 266 | + |
| 267 | +## Troubleshooting |
| 268 | + |
| 269 | +**"blockstore error"**: `rm -rf .anchor/test-ledger test-ledger` |
| 270 | + |
| 271 | +**Module resolution errors**: `cd sdk && yarn build-local && cd .. && yarn install --force` |
| 272 | + |
| 273 | +**Tests timeout**: Increase `startup_wait` in `Anchor.toml` |
| 274 | + |
| 275 | +**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. |
| 276 | + |
| 277 | +## Mainnet Program IDs |
| 278 | + |
| 279 | +| Program | Version | ID | |
| 280 | +|---------|---------|-----| |
| 281 | +| launchpad | v0.7.0 | `moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM` | |
| 282 | +| bid_wall | v0.7.0 | `WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx` | |
| 283 | +| futarchy | v0.6.0 | `FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq` | |
| 284 | +| conditional_vault | v0.4 | `VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg` | |
| 285 | +| price_based_performance_package | v0.6.0 | `pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS` | |
| 286 | +| mint_governor | v0.7.0 | `gvnr27cVeyW3AVf3acL7VCJ5WjGAphytnsgcK1feHyH` | |
0 commit comments