diff --git a/.claude/skills/solana-anchor-claude-skill/LICENSE.md b/.claude/skills/solana-anchor-claude-skill/LICENSE.md deleted file mode 100644 index f52a392fb..000000000 --- a/.claude/skills/solana-anchor-claude-skill/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) 2026 Quiknode Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.claude/skills/solana-anchor-claude-skill/RUST.md b/.claude/skills/solana-anchor-claude-skill/RUST.md deleted file mode 100644 index 6bd2bab9b..000000000 --- a/.claude/skills/solana-anchor-claude-skill/RUST.md +++ /dev/null @@ -1,164 +0,0 @@ -# Rust Guidelines (Anchor Programs) - -These guidelines apply to Anchor programs and any Rust crates that use Solana dependencies. Read this alongside the general rules in [SKILL.md](SKILL.md). - -## Anchor Version - -- Write all code like the latest stable Anchor (currently 1.0.2 but there may be a newer version by the time you read this) -- Use LiteSVM and Rust tests for new Anchor programs. `anchor init` uses LiteSVM by default. -- Do not use unnecessary macros that are not needed in the latest stable Anchor -- Don't implement instruction handlers as methods on account structs. There's no reason to tie state to functions, the function is not modifying the state (if we did like OOP, which we don't), and the functions and structs work without doing this, so there's no reason to implement instruction handlers as methods on account structs. - -## Anchor has silly defaults - -Every project will need an IDL. - -```toml -[features] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] -``` - -and if it uses Tokens (like almost every Anchor project) it will need this dependency (insert whatever version is applicable): - -```toml -[dependencies] -anchor-spl = "1.0.2" -``` - -## Project Structure - -- **Never modify the program ID** in `lib.rs` or `Anchor.toml` when making changes -- Create files inside the `state` folder for whatever state is needed -- Create files inside the `instructions` or `handlers` folders (whichever exists) for whatever instruction handlers are needed -- Put Account Constraints in instruction files, but ensure the names end with `AccountConstraints` rather than just naming them the same thing as the function -- Handlers that are only for the admin should be in a new folder called `admin` inside whichever parent folder exists (`instructions/admin/` or `handlers/admin/`) - -## Account Constraints - -- Use a newline after each key in the account constraints struct, so the macro and the matching key/value have some space from other macros and their matching key/value - -## Bumps - -- Use `context.bumps.foo` not `context.bumps.get("foo").unwrap()` - the latter is outdated - -## Data Structures - -- When making structs ensure strings and Vectors have a `max_len` attribute -- Vectors have two numbers for `max_len`: the first is the max length of the vector, the second is the max length of the items in the vector - -## Space Calculation (CRITICAL - NO MAGIC NUMBERS) - -- **Do not use magic numbers anywhere**. I don't want to see `8 + 32` or whatever. -- **Do not make constants for the sizes of various data structures** -- For `space`, use syntax like: `space = SomeStruct::DISCRIMINATOR.len() + SomeStruct::INIT_SPACE,` -- All structs should have `#[derive(InitSpace)]` added to them, to get the `INIT_SPACE` trait -- **DO NOT use magic numbers** - -**Example:** - -```rust -#[derive(InitSpace)] -#[account] -pub struct UserProfile { - pub authority: Pubkey, - - #[max_len(50)] - pub username: String, - - pub bump: u8, -} - -#[derive(Accounts)] -pub struct InitializeProfile<'info> { - #[account( - init, - payer = authority, - space = UserProfile::DISCRIMINATOR.len() + UserProfile::INIT_SPACE, - seeds = [b"profile", authority.key().as_ref()], - bump - )] - pub profile: Account<'info, UserProfile>, - - #[account(mut)] - pub authority: Signer<'info>, - - pub system_program: Program<'info, System>, -} -``` - -## Error Handling - -- Return useful error messages -- Write code to handle common errors like insufficient funds, bad values for parameters, and other obvious situations -- All arithmetic in onchain code is `checked_*` — never raw `+ - * /`. Solana's BPF doesn't trap on overflow in release builds; silent wraps are how hacks happen. `checked_*` returns `Option`; force the error with `.ok_or(MyError::MathOverflow)?`. Reserve `saturating_*` for cosmetic/UX display values, never for balances. - -## Onchain Financial Math - -Applies to any code touching money, balances, prices, shares, fees, or token amounts. These rules are non-negotiable. - -- **Integers only — no floats, no fixed-point libraries.** Floats are non-deterministic across platforms (different validators could disagree on state). `fixed::types::I64F64`, `rust_decimal`, `bnum`-fixed-point and similar are also out — they add audit surface, burn compute, and hide the rounding/precision decisions you should be making explicitly. Token amounts are integers (base units), prices are ratios of integers. The system is discrete. Production Solana AMMs (Orca, Raydium, Meteora, Saber, Phoenix) all use raw `u128`. If you find yourself reaching for a decimal type, stop — the right tool is `u128` with discipline. -- **Multiply before you divide.** `a * b / c`, not `(a / c) * b`. Division truncates; dividing first throws away precision permanently. -- **Use `u128` (or wider) for intermediate products.** `u64 * u64` overflows at ~1.8e19. Cast both operands to `u128` _before_ multiplying, then narrow the final result with `try_into().map_err(|_| MyError::MathOverflow)?`. -- **Round in the protocol's favour, never the user's.** Value-to-share and share-to-value conversions: user gets floor, protocol gets ceil. Otherwise you leak 1 base unit per transaction forever, and attackers will industrialise it. -- **Validate ranges before doing the math.** Reject zero inputs, `amount > balance`, ratios that would mint zero shares. Cheap, prevents the inflation/donation attack on empty pools and other whole bug classes. -- **Check invariants after the math, not just before.** "K must not decrease" on a swap, "total LP shares == sum of holdings", "reserves >= owed fees". Compute, then `require!()` the invariant. -- **Decimals are tracked, not assumed.** USDC=6, SOL=9, SPL tokens vary. Use `transfer_checked` (carries decimals in the CPI). Reserves hold raw base units; the UI does cosmetic conversion. Never hard-code `* 10^9`. -- **Oracle/price freshness is part of the math.** Check `last_updated_slot` and reject if older than N slots. A stale price means the calculation is wrong. -- **Oracle confidence is part of the math too.** Pull oracles (Pyth, Switchboard) report a price *and* a confidence/uncertainty band. Reject the update when the band is too wide relative to the price (e.g. `confidence * 10_000 / price > max_error_bps`); a wide band means the price is unreliable, and skipping this check is one of the most common oracle exploits. Where the feed offers it, prefer the EMA/TWAP price over the latest spot price for a mark that is harder to manipulate within a single block. (See `solana-labs/perpetuals` for a worked example.) -- **Checks-effects-interactions.** Update state before the token transfer CPI, not after. -- **Treat client-supplied values as adversarial.** If a handler takes `(amount_a, amount_b)`, verify each against onchain state, not against each other. -- **Test the branch the bug lives in.** Standard AMM/lending bugs sit in the _non-empty pool_, _post-swap_, _post-fee_, _rounding-edge_ branches. The happy path almost always works. Write the test that exercises the branch where the bug actually lives. -- **LP shares use different formulas for first deposit vs subsequent.** First deposit: shares = `sqrt(amount_a * amount_b)` (geometric mean bootstraps the pool). Subsequent deposits: shares = `min(amount_a * supply / reserve_a, amount_b * supply / reserve_b)` (proportional to share-of-pool). Using the geometric mean for every deposit is a real, repeated bug — test both branches separately. -- **For integer sqrt, hand-code Newton's method on `u128`** (~15 lines, as Uniswap V2 in Solidity / Saber in Rust do). Don't reach for a fixed-point crate for one sqrt. -- **Slippage protection: accept a `min_output_*` from the user and verify before the CPI.** Swaps, deposits, and withdraws all need it. Without it, sandwich attackers steal value across the price gap they create. -- **Never silently clamp user input to balance.** If a user asks to swap 100 and you clamp to 80 because that's the balance, the user's slippage check passes against the wrong amount. Either fail the instruction or return the actual amount so the client can validate. -- **Use `transfer_checked`, never raw `transfer`.** `transfer_checked` carries the mint and decimals through the CPI, so a wrong-mint or wrong-decimals account causes a CPI failure instead of a silent miscalculation. -- **For token program compatibility, use `anchor_spl::token_interface`** (`InterfaceAccount`, `InterfaceAccount`, `Interface`). The same code then works against both the Classic Token Program and the Token Extensions Program. -- **Oracle freshness uses slots, not unix time.** Slot count is what the runtime guarantees; `Clock::get()?.unix_timestamp` is validator-influenced. Check `last_updated_slot` against `Clock::get()?.slot` and reject if older than N slots. If you must use a unix timestamp (because the oracle only exposes one), state why in a comment. -- **Canonical pubkey ordering for two-asset pools.** Order mints so `mint_a.key() < mint_b.key()` (lexicographic on the 32-byte key). Same pool whether the user passes `(USDC, SOL)` or `(SOL, USDC)`. Enforce in the constraint, don't rely on the client. - -### Escrows, Vaults, and Escape Hatches - -- **Every escrow needs a cancel/withdraw instruction.** An escrow with no cancel locks abandoned offers forever — funds become unrecoverable when the counterparty disappears. The cancel must be callable by the maker (and only the maker) at any time before the trade settles. -- **Don't use `init_if_needed` for an account the wrong party would pay rent for.** Common bug: the taker's instruction lazily creates the maker's destination ATA via `init_if_needed`, so the taker pays the maker's rent. Either require the maker to pre-create their ATA or pass the rent payer explicitly. -- **Update state before the CPI.** Already in the list above, but worth repeating in the vault context: write the new balance/share count first, then transfer. A CPI that re-enters (rare on Solana but possible via callbacks) sees current state, not stale state. - -**Pattern to copy when ratio-clamping (Uniswap V2 style):** - -```rust -let pool_a = pool_a_amount as u128; -let pool_b = pool_b_amount as u128; -let amount_a_u128 = amount_a as u128; -let amount_b_u128 = amount_b as u128; - -// Multiply before divide; u128 prevents overflow. -let amount_b_required = amount_a_u128 - .checked_mul(pool_b).ok_or(ErrorCode::MathOverflow)? - .checked_div(pool_a).ok_or(ErrorCode::MathOverflow)?; - -let (final_a, final_b) = if amount_b_required <= amount_b_u128 { - (amount_a_u128, amount_b_required) -} else { - let amount_a_required = amount_b_u128 - .checked_mul(pool_a).ok_or(ErrorCode::MathOverflow)? - .checked_div(pool_b).ok_or(ErrorCode::MathOverflow)?; - (amount_a_required, amount_b_u128) -}; - -let final_a: u64 = final_a.try_into().map_err(|_| ErrorCode::MathOverflow)?; -let final_b: u64 = final_b.try_into().map_err(|_| ErrorCode::MathOverflow)?; -``` - -## Cargo hygiene - -- Run `cargo clean` after finishing with a Rust project. Anchor `target/` directories accumulate fast (multi-GiB per project). -- If disk usage hits 85%, clean before doing more work. - -## PDA Management - -- Add `pub bump: u8` to every struct stored in PDA -- Save the bumps inside each when the struct inside the PDA is created - -## System Functions - -- When you get the time via Clock, use `Clock::get()?;` rather than `anchor_lang::solana_program::clock` diff --git a/.claude/skills/solana-anchor-claude-skill/SKILL.md b/.claude/skills/solana-anchor-claude-skill/SKILL.md deleted file mode 100644 index c4f237e6d..000000000 --- a/.claude/skills/solana-anchor-claude-skill/SKILL.md +++ /dev/null @@ -1,439 +0,0 @@ ---- -name: solana-anchor-claude-skill -description: "Use when working on Solana software, including one or more of: Solana client code using TypeScript, Rust libraries that use Solana crates, Anchor programs, Quasar programs, LiteSVM tests, including Rust program files, TypeScript tests, and Anchor.toml configuration. Designed to create minimal, reusable code without unnecessary duplication." ---- - -# Coding Guidelines - -Apply these rules to ensure code quality, maintainability, and adherence to project standards. - -## Fight for Truth - -Don't write things that aren't currently true — anywhere. Chat, code comments, variable names, PR titles, READMEs, commit messages. - -- Documentation and comments that do not match the code are considered untrue. -- Variable names that do not match the purpose of the variable are considered untrue. -- Temporary workarounds that aren't labelled as such are lying through omission - there is an issue you aren't telling the next programmer about. Mark them with a `TODO` comment with a link to a git issue (if it exists) and telling the next programmer when they can delete the workaround. -- If unsure of something, say so. Bluffing is lying. -- **Ambiguity is a soft lie:** if a phrase could be read two ways and only one is true, it's misleading. Disambiguate before sending — pick the term that says exactly what's meant, name the antecedent of every "it"/"this"/"that". -- A wrong statement is worse than no statement. -- Separate scratch labels from real identifiers. - -Actively fix untrue things when you see them. Don't let "close enough" wording stand in for the truthful one. - -**Grep before naming.** Before sending any prose, walkthrough, README, comment, or commit message that names a specific identifier (function, struct, file, account, module, field, constant), grep the source for that exact identifier and confirm it exists. "I'm pretty sure that's the name" is not enough. If the identifier doesn't exist, either use the real name or apply the rename to the code first, then write the prose. - -**Describe what is, not what was removed.** READMEs, doc-comments, and code comments document current state — not history. Lines like "no floats", "no longer uses X", "replaces the previous Y approach" belong in CHANGELOGs and PR descriptions, not source artefacts. A first-time reader has no history and "no longer uses I64F64" creates ambient confusion ("wait, should I be worried?"). Sweep before sending: grep for `no longer`, `removed`, `previously`, `used to`, `formerly`, `dropped`, `now uses`, `replaces the previous` — each hit is a candidate for deletion. - -## Do the whole thing - -The marginal cost of completeness is near zero with AI. Do the whole thing. - -Do it right. Do it with tests. Do it with documentation. Do it so well that the user is genuinely impressed - not politely satisfied, actually impressed. Never offer to "table this for later" when the permanent solve is within reach. Never leave a dangling thread when tying it off takes five more minutes. Never present a workaround when the real fix exists. - -The standard isn't "good enough" - it's "holy shit, that's done." Search before building. Test before shipping. - -Ship the complete thing. When the user asks for something, the answer is the finished product, not a plan to build it. Time is not an excuse. Fatigue is not an excuse. Complexity is not an excuse. Boil the ocean. - -## Success Criteria - -- Before declaring success, declaring that work is complete, or celebrating, run the project's actual tests using the correct command for that project (for example: `anchor test` for Anchor workspaces, the project's TypeScript test command for TypeScript clients/tests, or `cargo test` for Rust crates). If the tests fail, there is more work to do. Don't stop until the relevant test command passes on the code you have made. -- Do not write placeholder tests. Placeholder tests don't count as tests, placeholder tests passing does not achieve your task. - - Tests that just do `assert.ok(true)` or similar are placeholder tests and do not count as tests - - Tests that do not call the program's instruction handlers are placeholder tests and do not count as tests - - Tests must: initialize accounts, send transactions, verify state changes, check balances - - If you find yourself writing placeholder tests, stop and write real integration tests instead - - DO NOT mark "Write tests" as complete until tests actually call the program instructions - - DO NOT ask "should I write real tests now?" - if the tests are placeholders, write real ones immediately - -- Do not stop until documentation like `README.md` and `CHANGELOG.md` are also updated with your changes. If you have made a feature, and it is not documented in the README or changelog, there is more work to do and you must continue working. - -- When summarizing your work, show the work items you have achieved with this symbol '✅' and if there is any more work to do, add a '❌' for each remaining work item. - -## Documentation Sources - -Use these official documentation sources: - -- **Anchor**: https://www.anchor-lang.com/docs -- **LiteSVM**: https://www.anchor-lang.com/docs/testing/litesvm -- **Anchor Error Codes**: https://raw.githubusercontent.com/coral-xyz/anchor/master/lang/src/error.rs -- **Solana Kite**: https://solanakite.org -- **Solana Kit**: https://solanakit.com -- **Agave (Solana CLI)**: https://docs.anza.xyz/ (Anza makes the Solana CLI and Agave). -- **Switchboard** (if used): https://docs.switchboard.xyz/docs-by-chain/solana-svm -- **Arcium** (if used): https://docs.arcium.com/developers - -## Terminology - -- Remember this is Solana not Ethereum. Ethereum is not relevant to any documentation you write. Do not assume people know or care about Ethereum. - - Don't tell me about 'smart contracts' or 'protocols' (use 'programs' instead) - - Don't tell me about 'gas' (use 'transaction fees' instead) - - There are no 'mempools'. - - Do not tell me about other things that are not relevant to Solana. - -- Token program terminology: - - Use 'Token Extensions Program' or 'Token extensions' for the newer token program (not 'Token 2022' which is just a code name) - - Use 'Classic Token Program' for the older token program - - Use 'Token' rather than 'SPL Token' unless you are specifically discussing the distinction between the native token (SOL) and all other tokens (SPL Tokens) - -- Onchain / offchain (one word, no hyphen) - - Always write 'onchain' and 'offchain' as single, unhyphenated words — like 'online' and 'offline'. - - Never write 'on-chain' or 'off-chain'. The hyphenated forms are wrong. - - Apply the same rule to related terms: 'crosschain' (not 'cross-chain'), etc. - - Sources: - - [Solana Foundation style guide](https://solana.com/docs/references/terminology) - - [US Government usage](https://www.sec.gov/files/rules/interp/2026/33-11412.pdf) - - [Cat (catmcgee) will make fun of you if you write 'on-chain'](https://x.com/catmcgee/status/2028153588715761825) - -- Some tools in Solana unfortunately use the same word 'instructions' for both the input and the functions. To avoid confusion, use 'instruction handlers' for the functions that handle instructions, and 'instructions' for the input to those functions. - -## Do not use - -- Do not use 'Solana Labs' documentation. The company has been replaced by Anza. - -- Do not use 'Coral XYZ' documentation. Coral used to maintain Anchor, but Anchor is now maintained by the Solana Foundation (solana.org) - -- Do not use any documentaton or tools from Project Serum, which collapsed many years ago. - -- Do not use yarn. Yarn has no reason to exist and only adds unnecessary dependencies and is not commonly used for new JS/TS projects in 2026. Replace Yarn with npm everywhere you see it. Use npm for new projects as it does not require additional dependencies. Keep using pnpm if the project already uses pnpm. - -- Do not use **Switchboard Functions** - this product is dead and no longer maintained. (Note: Switchboard oracles are still active and usable.) - -- Do not use **Clockwork** - this product is dead. For scheduled instruction handler invocation, use [TukTuk](https://github.com/helium/tuktuk/tree/main/typescript-examples) instead. - -## Library versions - -Use the latest stable Anchor, Rust, TypeScript, Solana Kit, and Kite you can. If a bug occurs, favor updating rather than rolling back. - -## Project Documentation - -Every project must have a `README.md` file in the project root that includes: - -- **Purpose**: Why the project exists and what problem it solves -- **Major Concepts**: Key architectural concepts, important PDAs, state structures, and program logic -- **Testing**: How to run the tests (e.g., `anchor test`) -- **Setup**: Any prerequisites or setup steps needed to work with the project -- **Usage**: Basic usage examples or deployment instructions if applicable - -Keep the README focused and practical. Avoid generic boilerplate - write documentation that would actually help someone understand and work with this specific project. - -## Writing About Financial Software - -These apply to READMEs, docs, blog posts, and PR descriptions for finance-related projects (AMMs, escrows, lending, leasing, CLOBs, prediction markets, stablecoins). - -- **"Non-custodial" is a loaded word.** If the program locks funds in vaults during its lifecycle (every escrow, lending, AMM, leasing program does), don't claim "non-custodial" — it contradicts itself. What you usually mean is "no admin override, the rules are the deployed bytecode". Say that directly, or just describe the custody arrangement (program-owned vault, PDA signers, no admin escape hatch). -- **Upgrade authority is normal on Solana** — programs are usually upgradable so authors can ship security fixes. Don't apologise for it or treat it as disqualifying for "trustless" claims. Trust in the author/multisig is baseline; "trustless" means the documented rules can't be bypassed, not "bytecode frozen forever". -- **"Token" not "mint" in economic prose.** A mint is the onchain account that controls supply; a token is the asset. In economic descriptions ("post token A as collateral, borrow token B"), say "token A" and "token B". Reserve "mint account" for technical descriptions of what gets passed to instructions. -- **Tokens are fungible by default — don't say so.** Don't write "fungible token" or sentences explaining that tokens are fungible. The reader knows. Only qualify when contrasting ("non-fungible token" / NFT). Same rule as not explaining what an integer is. -- **One name per role/concept, enforced everywhere.** Pick a single term for each party (lessor/lessee, maker/taker, long/short, borrower/lender) and use ONLY that term throughout. Mixing terminology mid-document is how readers lose track of who owes what to whom. -- **Don't conflate "long the collateral" with "long the trade".** Anyone who posts collateral wants it to hold value (otherwise margin call), so every borrower is long their collateral. The directional bet is on the _borrowed_ asset, separately. Be precise about which "long" you mean. -- **Be careful with the word "securities".** It's a legal term. SOL is not a security. Asset-leasing is not "securities lending" even when the mechanics are analogous. Prefer "asset lending", "token lending", or "directional token lending" — and ask before picking one. -- **Spell out two-asset flows with concrete examples.** "Posts collateral and takes delivery of borrowed tokens" reads circular. "Posts USDC as collateral, borrows NVDAx" makes the asymmetry obvious. Don't make the reader infer that mints A and B are different things. -- **Name the instruction handlers in lifecycle prose.** When walking through "what the user does" (open position, close position, liquidate), name the actual handler (`take_lease`, `return_lease`, `liquidate`). Plain-English mechanics without handler names leave the reader unable to connect the narrative to the code. - -## General Coding Guidelines - -### You are a deletionist - -Your golden rule is "perfection isn't achieved when there's nothing more to add, rather perfection is achieved when there is nothing more to be taken away". - -Remove: - -- Comments that simply repeat what the code is doing, or the name of a variable, and do not add further insight. -- Repeated code that should be turned into a named function. -- Unused imports, unused constants, unused files, and comments that no longer apply. -- Doc-comments whose first line just paraphrases the identifier. `/// Pool authority PDA.` above `pub pool_authority` is noise. Either explain something the name doesn't (seed derivation, mutability rationale, type-choice reason, an invariant the reader can't see from the type) or delete the line. - -Don't remove existing comments unless they are no longer useful or accurate. - -### Communication Style - -- Do not make disclaimers about being a "complete project" or state what works -- It is expected that work is complete and functional - no need to state this explicitly -- Avoid phrases like "This is a complete implementation" or "All features are working" -- Just deliver the work without meta-commentary about its completeness - -### Config files: leave a comment explaining WHY - -When you change a configuration value, or pin a version in any config file (`Anchor.toml`, `Cargo.toml`, `package.json`, CI workflows, `.gitignore`, `rust-toolchain.toml`), leave a comment explaining _why_. The next reader needs the rationale, not just the value. - -- **Pinned versions:** what breaks without the pin? when can it be unpinned? -- **Non-default timeouts / limits:** why this number? -- **Removed sections:** what was it doing? why was it removed? -- **`.gitignore` exceptions:** why is this file tracked despite the rule? -- **Workarounds:** what's the proper fix? when can this be replaced? (mark with `TODO`) - -Example: - -```toml -# Pinned: 0.8.7 conflicts with litesvm's dep tree. -# Unpin when litesvm upgrades its ahash requirement. -ahash = "=0.8.6" -``` - -When you remove a section, only add why to the git commit, so the file is free of information that does not apply to its existing state. - -### Working with Generated or Unfamiliar Code - -**CRITICAL - Verify Before Use:** - -- Before calling ANY function whose signature you don't know with certainty, read the actual source code/type definitions first -- NEVER guess or assume what parameters a function accepts based on what seems logical -- Don't invent convenience parameters that don't exist -- Generated code, third-party libraries, and unfamiliar codebases often have different APIs than you expect -- Common mistake: Assuming a function accepts high-level parameters → WRONG. Check the actual signature in the source files first - -### Variable Naming - -Ensure good variable naming. Rather than add comments to explain what things are, give them useful names. - -**Don't do this:** - -```typescript -// Foo -const shlerg = getFoo(); -``` - -**Do this instead:** - -```typescript -const foo = getFoo(); -``` - -**Naming conventions:** - -- Arrays should be plurals (`shoes`), items within arrays should be the singular (`shoes.forEach((shoe) => {...})`) -- Functions should be verby, like `calculateFoo` or `getBar` -- Avoid abbreviations, use full words (e.g., use `context` rather than `ctx`). Never use `e` for something thrown, use `thrownObject`, never use `v` when you mean `value`. There is almost no case where a single character variable is a good idea outside maths (eg `p` and `q` for cryptography). -- Name a transaction some variant of `transaction`. Name instructions some variant of `instruction`. Name signatures some variant of `signature`. Do not confuse them - eg if the type looks like an instruction, you should not call it a 'transaction' because that is deceptive. - -You can still add comments for additional context, just be careful to avoid comments that are explaining things that would be better conveyed by good variable naming. - -### Code Quality - -- Avoid 'magic numbers'. Make numbers either have a good variable name, a comment explaining why they are that value, or a reference to the URL you got the value from. If the values come from an IDL, download the IDL, import it, and make a function that gets the value from the IDL rather than copying the value into the source code - -This is a magic number. Don't do this: - -```ts -const FINALIZE_EVENT_DISCRIMINATOR = new Uint8Array([ - 27, 75, 117, 221, 191, 213, 253, 249, -]); -``` - -Instead do this: - -```ts -const FINALIZE_EVENT_DISCRIMINATOR = getEventDiscriminator( - arciumIdl, - "FinalizeComputationEvent", -); -``` - -- The code you are making is for production. You shouldn't have comments like `// In production we'd do this differently` or `**Implementation incomplete** - Needs program config handling and proper PDA derivations` or `**WORK IN PROGRESS**` in the final code you produce, or functions that return placeholder data. Instead: do the fucking work. - -## Language-Specific Guidelines - -The rules above apply to every file in the project. In addition, read the file that matches the language you are editing: - -- **TypeScript** (Solana Kit clients, Solana Kit tests, browser code, anything `.ts`): see [TYPESCRIPT.md](TYPESCRIPT.md) -- **Rust** (Anchor programs, LiteSVM tests, Solana crates, anything `.rs`): see [RUST.md](RUST.md) - -If a task touches both sides, read both. - -### Testing (Rust + LiteSVM) - -Anchor 1.0+ ships Rust + LiteSVM tests by default — `anchor init` now scaffolds a Rust integration test under `programs//tests/`, and `Anchor.toml` sets `test = "cargo test"`. Use this as the sole test pattern for Anchor programs. Do not write TypeScript tests for Anchor programs. - -#### How to initialise a new project - -Always initialise new Anchor projects with both flags pinned explicitly: - -```sh -anchor init --package-manager npm --test-template litesvm -``` - -- `--package-manager npm` — `anchor init`'s default is `yarn`, which this skill bans. Pin npm at init time so you don't have to fix `Anchor.toml` afterwards. -- `--test-template litesvm` — currently the default in `anchor-cli`, but pin it explicitly so the project doesn't break if the default changes. The other templates (`mocha`, `jest`, `rust`, `mollusk`) are not used for new Anchor programs in this skill. - -The `--template` flag defaults to `multiple` (multi-file program layout with `instructions/`, `state.rs`, `error.rs`); keep that default. `--template single` is a single `lib.rs` and Anchor itself flags it as "not recommended for production". - -#### What `anchor init` gives you - -A fresh `anchor init` produces these test-related defaults: - -`Anchor.toml`: - -```toml -[toolchain] -package_manager = "yarn" - -[features] -resolution = true -skip-lint = false - -[scripts] -test = "cargo test" - -[hooks] -``` - -`programs//Cargo.toml` `[dev-dependencies]`: - -```toml -[dev-dependencies] -litesvm = "0.10.0" -solana-message = "3.0.1" -solana-transaction = "3.0.2" -solana-signer = "3.0.0" -solana-keypair = "3.0.1" -``` - -`programs//tests/test_initialize.rs`: - -```rust -use { - anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, - litesvm::LiteSVM, - solana_message::{Message, VersionedMessage}, - solana_signer::Signer, - solana_keypair::Keypair, - solana_transaction::versioned::VersionedTransaction, -}; - -#[test] -fn test_initialize() { - let program_id = anchor_scaffold_probe::id(); - let payer = Keypair::new(); - let mut svm = LiteSVM::new(); - let bytes = include_bytes!("../../../target/deploy/anchor_scaffold_probe.so"); - svm.add_program(program_id, bytes).unwrap(); - svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap(); - - let instruction = Instruction::new_with_bytes( - program_id, - &anchor_scaffold_probe::instruction::Initialize {}.data(), - anchor_scaffold_probe::accounts::Initialize {}.to_account_metas(None), - ); - - let blockhash = svm.latest_blockhash(); - let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash); - let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap(); - - let res = svm.send_transaction(tx); - assert!(res.is_ok()); -} -``` - -Before the program binary exists, run `anchor build` so `target/deploy/.so` is on disk; the test loads it via `include_bytes!`. - -#### Two scaffold fixes to apply immediately after `anchor init` - -`anchor init`'s defaults conflict with this skill's rules. Fix them straight away: - -1. **Set `package_manager = "npm"` in `Anchor.toml`** — `anchor init` defaults to yarn, but yarn is banned in this skill. If you used `--package-manager npm` at init time you can skip this step. - - ```toml - [toolchain] - package_manager = "npm" - ``` - -2. **Delete `ts-mocha`, `mocha`, `chai` (and their `@types`) from `package.json`** — `--package-manager npm` does not remove the JS test dev-dependencies; you still need this step. The default JS test scaffold is stale. Anchor programs (since 1.0.0) use Rust + LiteSVM instead of TypeScript, not Mocha. If you keep a `package.json` at all (for offchain client code or scripts), it should not pull in Mocha-era dependencies. - -#### Minimal bare-bones test - -The `anchor init` scaffold above is already the minimal pattern — `litesvm` plus the `solana-*` primitives, no extra dependencies. Use this when you want zero indirection and complete control over the transaction. New tests can follow the same shape: build an `Instruction`, wrap in a `Message` with the latest blockhash, sign as a `VersionedTransaction`, and call `svm.send_transaction(tx)`. - -#### Optional ergonomic helpers via solana-kite - -[`solana-kite`](https://crates.io/crates/solana-kite) is an optional thin layer on top of `litesvm` that removes most of the manual transaction wiring. Used in the wild by [`quiknode-labs/solana-program-examples/basics/counter/anchor`](https://github.com/quiknode-labs/solana-program-examples/tree/main/basics/counter/anchor). - -Add to `[dev-dependencies]`: - -```toml -[dev-dependencies] -litesvm = "0.10.0" -solana-kite = "0.3.0" -borsh = "1.6.1" -``` - -The same test, rewritten with kite: - -```rust -use { - anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}, - litesvm::LiteSVM, - solana_kite::{create_wallet, send_transaction_from_instructions}, -}; - -#[test] -fn test_initialize() { - let program_id = anchor_scaffold_probe::id(); - let mut svm = LiteSVM::new(); - let bytes = include_bytes!("../../../target/deploy/anchor_scaffold_probe.so"); - svm.add_program(program_id, bytes).unwrap(); - - let payer = create_wallet(&mut svm, 1_000_000_000).unwrap(); - - let instruction = Instruction::new_with_bytes( - program_id, - &anchor_scaffold_probe::instruction::Initialize {}.data(), - anchor_scaffold_probe::accounts::Initialize {}.to_account_metas(None), - ); - - send_transaction_from_instructions(&mut svm, &[instruction], &payer, &[&payer]).unwrap(); -} -``` - -`create_wallet` replaces the `Keypair::new()` + `svm.airdrop(...)` pair, and `send_transaction_from_instructions` replaces the `Message` / `VersionedMessage` / `VersionedTransaction` construction. Bare `litesvm` is still the baseline — reach for kite when you have repeated boilerplate worth removing. - -#### Account deserialisation - -Anchor account data is `[8-byte discriminator][borsh-serialised struct]`. To read state from a LiteSVM test, fetch the account, skip the first 8 bytes, and `borsh`-decode the rest. Define a mirror struct (or import the program's own) that derives `BorshDeserialize`. - -```rust -use borsh::BorshDeserialize; - -#[derive(BorshDeserialize)] -struct CounterAccount { - pub count: u64, -} - -let account = svm.get_account(&counter_pda).unwrap(); -let counter = CounterAccount::try_from_slice(&account.data[8..]).unwrap(); -assert_eq!(counter.count, 1); -``` - -`8` here is the Anchor account discriminator length, not a magic number — it is fixed by Anchor's account layout. - -#### Re-expiring blockhash between repeated identical transactions - -LiteSVM, like a real validator, will reject a second transaction with the same blockhash + signer + message because the signature is identical to one it has already processed. If a test sends the *same* instruction twice (for example, calling `increment` in a loop), call `svm.expire_blockhash()` between sends so the next transaction picks up a fresh blockhash and is treated as new: - -```rust -send_transaction_from_instructions(&mut svm, &[increment.clone()], &payer, &[&payer]).unwrap(); -svm.expire_blockhash(); -send_transaction_from_instructions(&mut svm, &[increment], &payer, &[&payer]).unwrap(); -``` - -This is only needed when the message bytes would otherwise be byte-identical. Different instructions, different accounts, or different signers do not need it. - -#### Do not use - -- `solana-test-validator` — slow, stateful, replaced by LiteSVM for tests. -- `anchor test --validator legacy` — same reason; the default `anchor test` runs `cargo test` against LiteSVM. -- `anchor.setProvider`, `anchor.AnchorProvider.env()` — TS Anchor client wiring, no longer used for tests. -- `program.methods.X().rpc()`, `program.methods.X().sendAndConfirm()` — the TS `@coral-xyz/anchor` client; do not use it for tests. -- `ts-mocha`, `mocha`, `chai` — the stale `anchor init` JS test scaffold. -- `tsx`-based `node:test` for Anchor program tests — fine for offchain scripts, not for testing programs. -- `@solana/web3.js` v1 — legacy in any context. -- `@coral-xyz/anchor` — Anchor's old TS client; not used in this test pattern. -- `kit-plugin-litesvm` (the TypeScript LiteSVM plugin) — superseded by using the `litesvm` Rust crate directly. - -## Git commits - -Do not add "Co-Authored-By: Claude" or similar attribution when creating git commits. - -## Acknowledgment - -- Acknowledge these guidelines have been applied when working on this project to indicate you have read these rules and found that they do apply to this project. diff --git a/.claude/skills/solana-anchor-claude-skill/TYPESCRIPT.md b/.claude/skills/solana-anchor-claude-skill/TYPESCRIPT.md deleted file mode 100644 index c58f32ce7..000000000 --- a/.claude/skills/solana-anchor-claude-skill/TYPESCRIPT.md +++ /dev/null @@ -1,91 +0,0 @@ -# TypeScript Guidelines - -These guidelines apply to TypeScript unit tests, browser code, Solana Kit clients, and any other places where TypeScript is used in the project. Read this alongside the general rules in [SKILL.md](SKILL.md). - -## General TypeScript - -Use `"type": "module"` in `package.json` files. - -Avoid using a `tsconfig.json` unless it's needed, as we use `tsx` to run most typescript and it doesn't usually need one. If you do need a `tsconfig.json`, state why at the top of the file, and you can use the most modern version of ECMAScript/JavaScript you want - up to say 2023. - -## Async/await - -Favor `async`/`await` and `try/catch` over `.then()` or `.catch()` or using callbacks for flow control. `tsx` has top level `await` so you don't need to wrap top level `await` in IIFEs. - -## Type System - -- **Always use `Array`**, never use `item[]` for consistency with other generic syntax like `Promise`, `Map`, and `Set` -- **Don't use `any`** - -## Comments - -- Most comments should use `//` and be above (not beside) the code -- The only exception is JSDoc/TSDoc comments which MUST use `/* */` syntax - -## Solana-Specific TypeScript - -- Don't make new `@solana/web3.js` version 1 code. Do not make new code using `@coral-xyz/anchor` package. Don't replace Solana Kit with web3.js version 1 code. web3.js version 1 is legacy and should be eventually removed. Solana Kit used to be called web3.js version 2. Use Solana Kit, preferably via Solana Kite. -- Use Kite's `connection.getPDAAndBump()` to turn seeds into PDAs and bumps -- There is no need to use offsets that you set to decode Solana account data - either download an npm package for the program like `@solana-program/token` for the token program or make one using Codama. -- In Solana Kit, you make instructions by making TS clients from IDLs using Codama. You can easily make Codama clients for installed IDLs using: - -`npx create-codama-clients` - -- Do not use the `bs58` npm package. - -Don't do this: - -```typescript -import bs58 from "bs58"; -const signature = bs58.encode(signatureBytes); -``` - -Do this instead: - -```typescript -import { getBase58Decoder } from "@solana/codecs"; -const signature = getBase58Decoder().decode(signatureBytes); -``` - -Yes, `bs58` and `@solana/codecs` packages have different concepts of 'encode' and 'decode'. - -## Unit Tests - -- Create unit tests in TS in the `tests` directory -- Use the Node.js inbuilt test and assertion libraries (then start the tests using `tsx` instead of `ts-mocha`) - -**Unit testing imports:** - -```typescript -import { before, describe, test } from "node:test"; -import assert from "node:assert"; -``` - -- Use `test` rather than `it` - -## Thrown object handling - -- JavaScript allows arbitrary items - strings, array, numbers etc to be 'thrown'. However you can assume that any non-Error item that is thrown is a programmer error. Handle it like this (including the comment since most TypeScript developers don't know this): - -```ts -// In JS it's possible to throw *anything*. A sensible programmer -// will only throw Errors but we must still check to satisfy -// TypeScript (and flag any craziness) -const ensureError = function (thrownObject: unknown): Error { - if (thrownObject instanceof Error) { - return thrownObject; - } - return new Error(`Non-Error thrown: ${String(thrownObject)}`); -}; -``` - -and - -```ts -try { - // some code that might throw -} catch (thrownObject) { - const error = ensureError(thrownObject); - throw error; -} -```