Skip to content
Merged
Show file tree
Hide file tree
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
47 changes: 15 additions & 32 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
# Agent guide

Solana program examples in three flavors per example: `anchor/`, `native/`, `pinocchio/` (a few have `asm/`). Path convention: `<category>/<example-name>/<framework>`.
Solana program examples, one per framework flavor (`anchor/`, `native/`, `pinocchio/`, a few `asm/`). Everything below is a decision or trap you cannot see from the files.

## Layout rules
## Deliberate structure

- **Not a pnpm workspace, on purpose.** Every example has its own `package.json` and `pnpm-lock.yaml` so it can be copied out and run standalone. Never introduce cross-example imports or shared JS helpers. Align dependency versions with `pnpm sync-package-json` from the root.
- **Rust is one workspace.** Most program crates are members of the root `Cargo.toml`. Crates that can't be members are listed in `.github/.workspace-ignore` (CI enforces one or the other). `tokens/token-2022/transfer-hook/block-list/pinocchio` and `games/world-cup/pinocchio` have their own workspaces.
- Toolchain pins: `rust-toolchain.toml`, `.nvmrc`, `packageManager` in the root `package.json`, `anchor_version`/`solana_version` in every `Anchor.toml`.

## Build and test

| Framework | Build + test |
| ------------------ | --------------------------------------------------------------------------------------------------- |
| anchor | `anchor test` in the project (`[scripts] test` in `Anchor.toml` runs mocha) |
| native / pinocchio | `pnpm build-and-test` (cargo build-sbf into `tests/fixtures/`, then `pnpm test`) |
| asm | same script shape; programs assemble with `sbpf` (rev pinned in `.github/actions/setup/action.yml`) |
| world-cup | `just setup && just build && just test` — excluded from the pinocchio workflow by design |

Rust integration tests live in `program/tests/*.rs` (litesvm) and run with `cargo test --manifest-path=./program/Cargo.toml`; CI runs them only when `program/Cargo.toml` exists.
- **Not a pnpm workspace, on purpose.** Per-example `package.json` + `pnpm-lock.yaml` so an example can be copied out and run standalone. No cross-example imports, no shared JS helpers. Version alignment is `pnpm sync-package-json` from the root, not hoisting.
- Rust program crates must be root-workspace members or listed in `.github/.workspace-ignore` (CI enforces one or the other).
- `.github/.ghaignore` lists CI-skipped projects; every entry needs a comment with the real reason, and reasons rot: verify before trusting one.
- `games/world-cup` is excluded from the pinocchio workflow by design (own workspace, `just` build).

## Test stack (do not deviate)

- Runner: **mocha 11 via tsx** (`mocha --import=tsx …`). Never ts-mocha, ts-node, jest, or `node:test` imports — `node:test` suites under mocha exit 0 even when failing.
- Runtime: **LiteSVM**. Non-anchor tests use `@solana/kit` + `litesvm` 1.x (pattern: `basics/hello-solana/native/tests/index.test.ts`, full version `tokens/create-token/pinocchio/tests/test.ts`). Anchor tests use `@anchor-lang/core` + `anchor-litesvm` + `litesvm` 0.8 with `@solana/web3.js` — deliberate, anchor's JS client is web3.js-based until it moves to kit.
- litesvm never throws on a failed transaction: assert `result instanceof FailedTransactionMetadata`. Sending the same bytes twice needs `svm.expireBlockhash()` in between.
- anchor-litesvm pins its own old litesvm; every anchor project carries `pnpm.overrides { "litesvm": "^0.8.0" }`. Without it, failed transactions pass `instanceof` checks silently.
- Tests must assert real post-state (account bytes, lamport deltas) and fail loudly — verify by breaking an assertion once.

## Formatting and CI

- TS/MD/JSON: `pnpm format` / `pnpm run check` at the root (prettier, `@solana/prettier-config-solana`). Rust: `cargo fmt` at the root (shared `rustfmt.toml`); clippy runs with `-D warnings` in CI.
- Workflows discover projects by directory name (`anchor`, `native`, `pinocchio`, `asm`). `.github/.ghaignore` lists CI-skipped projects — every entry needs a comment with the real reason; verify a reason still holds before trusting it.
- Per-project CI: `pnpm install --frozen-lockfile` (commit lockfiles), `tsc --noEmit` when a `tsconfig.json` exists (keep `skipLibCheck`), build, test.
- Runner is mocha via tsx. Never `node:test` imports: `node:test` suites under mocha exit 0 even when failing.
- Non-anchor tests: `@solana/kit` + litesvm 1.x. Anchor tests: `@anchor-lang/core` + `anchor-litesvm` + litesvm **0.8** with `@solana/web3.js`: deliberate, anchor's JS client stays web3.js-based until it moves to kit.
- anchor-litesvm pins its own old litesvm, so every anchor project carries `pnpm.overrides { "litesvm": "^0.8.0" }`. Without it, failed transactions pass `instanceof` checks silently and tests go green.
- litesvm never throws on a failed transaction: assert `result instanceof FailedTransactionMetadata`. Resending identical bytes needs `svm.expireBlockhash()` in between.
- bankrun and the older harnesses were removed deliberately; don't reintroduce them from upstream examples or old tutorials.

## Gotchas that have bitten before
## Traps

- **Resolver-2 feature unification:** a crate must declare every feature-gated dependency it uses itself (e.g. `solana-address` with `curve25519`/`decode`). Whole-workspace builds mask missing features that per-crate CI builds expose.
- **`anchor keys sync` rewrites `declare_id!` and strips Anchor.toml comments.** Don't run it casually; `basics/cross-program-invocation/anchor` has committed keypairs with a drift guard — never resync it.
- Fixtures under `tests/fixtures/` are gitignored and built on demand; Metaplex `token_metadata.so` is dumped from mainnet by each project's `prepare.mjs` postinstall. The metadata natives build Metaplex instructions by hand in `mpl_util.rs` — there is no mpl crate dependency; keep it that way.
- Old runtimes (bankrun) and old harnesses were removed deliberately; don't reintroduce them from upstream examples or old tutorials.
- **Resolver-2 feature unification:** each crate must declare every feature-gated dependency it uses itself (e.g. `solana-address` with `curve25519`/`decode`). Whole-workspace builds mask what per-crate CI builds expose.
- **`anchor keys sync` rewrites `declare_id!` and strips Anchor.toml comments.** `basics/cross-program-invocation/anchor` has committed keypairs with a drift guard: never resync it.
- Metaplex `token_metadata.so` is dumped from mainnet by each project's `prepare.mjs` postinstall. The metadata natives hand-build Metaplex instructions in `mpl_util.rs`, with no mpl crate dependency: keep it that way.
96 changes: 89 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
# Contribution Guidelines
# Contributing

Thank you for considering contributing to the Solana Program Examples repository. We greatly appreciate your interest and efforts in helping us improve and expand this valuable resource for the Solana developer community.
Thank you for considering contributing to the Solana Program Examples repository. These examples are a teaching resource: every one of them should be small enough to read in one sitting, runnable standalone, and correct enough that someone can copy it into their own project.

We believe that a welcoming and inclusive environment fosters collaboration and encourages participation from developers of all backgrounds and skill levels.

To ensure a smooth and effective contribution process, please take a moment to review and follow the guidelines outlined below.
## Before you start

## How to Contribute
- Search existing issues and pull requests before opening a new one.
- For substantial changes, such as a new example or a new framework flavor, open an issue or discussion first so maintainers can confirm the approach. Small PRs are preferred.
- Do not include secrets, private keys, seed phrases, or production credentials in issues, pull requests, commits, logs, or screenshots. Program keypairs belong in gitignored `keys/` directories, never in the diff.
- All commits into a Solana Foundation repository require [commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) to be enabled. Your PRs will not be merged without this.

We welcome contributions in the form of code, documentation, bug reports, feature requests, and other forms of feedback. Here are some ways you can contribute:
## Security vulnerabilities

- **Code Contributions:** You can contribute code examples in Rust that demonstrate various Solana program functionalities. You can also contribute improvements to existing examples, such as bug fixes, optimizations, or additional features.
Do not report security vulnerabilities in public issues. Use [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) on this repository instead.

- **Bug Reports, Ideas or Feedback:** If you encounter any issues or have ideas for new examples, please submit a bug report or feature request. Your feedback is valuable and helps us improve the quality and relevance of the examples.
Note that the examples here are educational and deliberately minimal. A missing production hardening measure in an example is a normal issue or PR, not a vulnerability report.

## How to contribute

- **Code contributions:** new examples that demonstrate a Solana program pattern, or improvements to existing ones such as bug fixes, optimizations, or additional coverage.
- **Bug reports, ideas, or feedback:** if an example does not build, is out of date, or is missing, open an issue.

## Development setup

Use the toolchain versions checked into the repository: `rust-toolchain.toml`, `.nvmrc`, `packageManager` in the root `package.json`, and the `anchor_version` / `solana_version` fields in each `Anchor.toml`. Do not bump language runtimes, the Solana CLI, Anchor, or the package manager as an incidental part of another change.

```bash
pnpm install # at the root, for formatting tooling
pnpm format # prettier write
pnpm check # prettier check
pnpm sync-package-json # align dependency versions across examples
```

Per example, run the commands documented in that example's `package.json` or `Anchor.toml`: `anchor test` for Anchor projects, `pnpm build-and-test` for native and Pinocchio projects.
Comment thread
dev-jodee marked this conversation as resolved.

## General coding and writing guidelines

Expand Down Expand Up @@ -60,6 +81,63 @@ pnpm format
When removing or updating an example, please ensure that the example is removed from the `.ghaignore` file
and there's a change in that example's directory.

## Making a change

Keep changes focused. A pull request should solve one problem and carry the tests, documentation, and generated artifacts needed to keep the repository usable.

Before opening a pull request:

- Format, lint, build, and test the affected examples with the commands above. `cargo fmt` at the root for Rust; clippy runs with `-D warnings` in CI.
- Add or update tests when behavior changes. Tests must assert real post-state (account data, lamport deltas) and fail loudly: break an assertion once to confirm the test can actually fail.
- Update the README and any example-level docs when the user-facing contract changes.
- Regenerate committed derived files (IDLs, generated clients) with the repository's documented tooling, and commit updated lockfiles.
- Explain any new dependency and why the existing dependency set is insufficient.

Because these are onchain programs, document the account validation, authority checks, state transitions, and value movement your change relies on. Include a threat-model note when the change creates or modifies a trust boundary. An example that teaches an unsafe pattern without flagging it is a bug.

## Pull requests

Write a clear title and description that explain the problem, the approach, and how you tested it. Link related issues and call out behavior changes, compatibility concerns, or follow-up work. Use [Conventional Commits](https://www.conventionalcommits.org/) for commit and PR titles. See [AI use](#ai-use) for how to disclose AI assistance.

By default, [Greptile](https://www.greptile.com) is enabled on all Solana Foundation repositories. Before maintainers review, all Greptile comments must be resolved with either a code fix or an explanation of why no change is needed.

Once CI is approved to run by maintainers, all CI errors must be addressed before the PR will be merged.

Maintainers may ask you to rebase, split a broad change, add tests, or revise documentation before merging.

## AI use

You may use AI-assisted tools, but you should review the generated code, understand its behavior, and run the same checks expected of any other contribution.

If you are building with AI on Solana, check out the [Solana Dev Skill](https://github.com/solana-foundation/solana-dev-skill) or the [Solana MCP](https://mcp.solana.com/) to aid in your work.

Ensure that the generated code adheres to the project's coding standards and best practices. Maintainers can close PRs if they appear to be low-effort AI slop. In particular, audit your changes for the following AI code smells that increase maintenance burden:

- Comments that explain why the _previous_ behavior was wrong and the new behavior is correct. This can be helpful context for reviewers as a GitHub comment in the review, but we do not need a history of every code change living in the codebase.
- Large blocks of comments with a high density of technical jargon; comments should be distilled to clearly explain _why_ this code is doing something (if it's not obvious), not _what_ (the code should speak for itself).
- Drive-by refactoring of code that is not relevant to the actual change being made.

Two more that matter specifically here:

- Examples are read as teaching material, so generated code that works but obscures the pattern being taught is worse than none. Prefer the shortest version that shows the mechanism.
- Do not let a tool spread a change across every framework flavor or every example unless the change genuinely applies to all of them. Bulk edits are hard to review and easy to get subtly wrong per example.

### Disclosure

It can be helpful to note the extent to which AI was used in the change. For example, adding

> I wrote all of the code for this feature, and had Claude update the documentation and create tests accordingly

or

> I architected the change and handed all implementation over to Codex

to the pull request description can be helpful context for reviewers.

### Communication

If maintainers have suggested changes, feedback, or questions about your code, you should not be copy/pasting the questions to an LLM and copy/pasting the response. You being able to distill the information that AI produces is what makes your contribution valuable.

## Code of Conduct

We are committed to providing a friendly, safe, and welcoming environment for all contributors, regardless of their background, experience level, or personal characteristics. As a contributor, you are expected to:
Expand All @@ -69,3 +147,7 @@ Refrain from engaging in any form of harassment, discrimination, or offensive be
Help create a positive and supportive community where everyone feels valued and respected.

If you encounter any behavior that violates our code of conduct, please report it to the project maintainers immediately.

## License

By contributing, you agree that your contributions are licensed under the project's [LICENSE](./LICENSE).
Loading
Loading