Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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<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(())
}
}
```

### 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` |
Loading