Skip to content

Commit cc5ea7e

Browse files
authored
add CLAUDE.md (#416)
1 parent 6558679 commit cc5ea7e

1 file changed

Lines changed: 286 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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

Comments
 (0)