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
84 changes: 41 additions & 43 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,66 @@
# Contribution Guidelines

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.

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.
Thank you for considering a contribution to this repository. We welcome new examples, fixes, and improvements from the community.

## How to Contribute

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:

- **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.
- **Code:** Add new examples or improve existing ones (bug fixes, optimizations, additional features).
- **Bug reports, ideas, feedback:** Open an issue describing what you found or what you'd like to see.

- **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.
## Project structure

## General coding and writing guidelines
- Each example lives at `category/example-name/<framework>/`, e.g. `basics/counter/anchor/`.
- Supported frameworks: `anchor`, `quasar`, `pinocchio`, `native`. Use the existing layout as a reference.
- Tests live alongside the program in a `tests/` directory.

Please follow the [Contributing and Style Guide from the Developer Content Repo](https://github.com/solana-foundation/developer-content/blob/main/CONTRIBUTING.md).
## Tooling

Specifically for code in this repo:
- **Package manager:** `pnpm`. Commit `pnpm-lock.yaml`. Do not use yarn or npm here.
- **Formatter / linter:** [Biome](https://biomejs.dev/). Run `pnpm fix` from the repo root before submitting a PR.

1. Use pnpm as the default package manager for the project. You can [install pnpm by following the instructions](https://pnpm.io/installation). Commit `pnpm-lock.yaml` to the repository.
## Testing

2. Solana Programs written for the Anchor framework should be in directory [`anchor`](https://www.anchor-lang.com), Solana Native in [`native`](https://solana.com/developers/guides/getstarted/intro-to-native-rust), respectively.
- Project path structure: `/program-examples/category/example-name/<framework_name>`
- Project path structure example for anchor: `/program-examples/category/example-name/anchor`
This repo uses an in-process test runtime — no local validator boot, no `solana-test-validator`, no `anchor test --validator legacy`.

3. Tests for Anchor and Solana native programs should be written with [solana-bankrun](https://kevinheavey.github.io/solana-bankrun).
For Anchor and Quasar examples, tests are written in TypeScript and run with `node:test` via `tsx`:

4. For Solana native programs ensure adding these mandatory pnpm run scripts to your `package.json` file for successful CI/CD builds:

```json
"scripts": {
"test": "pnpm ts-mocha -p ./tests/tsconfig.test.json -t 1000000 ./tests/realloc.test.ts",
"build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures && pnpm test",
"build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./program/target/so",
"deploy": "solana program deploy ./program/target/so/program.so"
},
```bash
npx tsx --test --test-reporter=spec tests/*.ts
```

5. Test command for Anchor should execute `pnpm test` instead of `yarn run test` for anchor programs. Replace `yarn` with `pnpm` in `[script]` table inside [Anchor.toml file.](https://www.anchor-lang.com/docs/manifest#scripts-required-for-testing)
The conventional `Anchor.toml` `[scripts]` entry is:

```
```toml
[scripts]
test = "pnpm ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
test = "npx create-codama-clients; npx tsx --test --test-reporter=spec tests/*.ts"
```

6. TypeScript, JavaScript and JSON files are formatted and linted using
[Biome](https://biomejs.dev/). Execute the following command to format and lint your code at the root of this project before submitting a pull request:
The TypeScript tests use:

```bash
pnpm fix
```
- [`solana-kite`](https://solanakite.org) for the connection, wallet creation, token mint helpers, PDA derivation, and `sendTransactionFromInstructions`.
- [`@solana/kit`](https://solanakit.com) for the core types (`KeyPairSigner`, `Address`, `lamports`).
- A [Codama](https://github.com/codama-idl/codama)-generated client (via `npx create-codama-clients`) for invoking the program instructions. Do **not** use `anchor.workspace` or `program.methods.X().rpc()`.

Native and Pinocchio examples may use `litesvm` directly from Rust where appropriate.

## Style

Write American English in prose (e.g. "behavior", "initialize", "favor"). Code identifiers stay as-is.

Other conventions:

7. Some projects can be ignored from the building and testing process by adding the project name to the `.ghaignore` file.
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.
- One H1 per markdown file.
- Fenced code blocks include a language tag (` ```rust `, ` ```typescript `, ` ```bash `, ` ```toml `).
- Use full words rather than abbreviations (`transaction`, not `tx` or `txn`; `account`, not `acc`).
- Prefer `async`/`await` over `.then()`/`.catch()`.
- Use `Array<T>` rather than `T[]` in TypeScript.
- Avoid magic numbers — name or explain them.
- Write "onchain" / "offchain" as single words (no hyphen).

## Code of Conduct
## Excluding an example from CI

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:
Add the project path to `.ghaignore` to skip it during CI builds. If you remove or replace an example, update `.ghaignore` accordingly.

Be respectful and inclusive in your interactions with others.
Refrain from engaging in any form of harassment, discrimination, or offensive behavior. Be open to constructive feedback and be willing to learn from others.
Help create a positive and supportive community where everyone feels valued and respected.
## Code of conduct

If you encounter any behavior that violates our code of conduct, please report it to the project maintainers immediately.
Be respectful and inclusive. Constructive feedback only. Report any conduct issues to the maintainers.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Each example is available in one or more of the following frameworks:

- [⚓ Anchor](https://www.anchor-lang.com/) — the most popular framework for Solana development. Build with `anchor build`, test with `pnpm test` as defined in `Anchor.toml`.
- [💫 Quasar](https://quasar-lang.com/docs) — a newer, more performant framework with Anchor-compatible ergonomics. Run `pnpm test` to execute tests.
- [🤥 Pinocchio](https://github.com/febo/pinocchio) — a zero-copy, zero-allocation library for Solana programs. Run `pnpm test` to execute tests.
- [🦀 Native Rust](https://docs.solana.com/) — vanilla Rust using Solana's native crates. Run `pnpm test` to execute tests.
- [🤥 Pinocchio](https://github.com/anza-xyz/pinocchio) — a zero-copy, zero-allocation library for Solana programs. Run `pnpm test` to execute tests.
- [🦀 Native Rust](https://docs.anza.xyz/) — vanilla Rust using Solana's native crates. Run `pnpm test` to execute tests.
- [🧬 ASM](https://github.com/blueshift-gg/sbpf) — hand-written sBPF assembly built with the `sbpf` toolchain. Run `pnpm build-and-test` to build and test.

> [!NOTE]
Expand All @@ -25,7 +25,7 @@ Constant product AMM (x·y=k) — create liquidity pools, deposit and withdraw l

### Escrow

Peer-to-peer OTC trade — one user deposits token A and specifies how much token B they want. A counterparty fulfils the offer and both sides receive their tokens atomically.
Peer-to-peer OTC trade — one user deposits token A and specifies how much token B they want. A counterparty fulfills the offer and both sides receive their tokens atomically.

[⚓ Anchor](./tokens/escrow/anchor) [💫 Quasar](./tokens/escrow/quasar) [🦀 Native](./tokens/escrow/native)

Expand Down
14 changes: 5 additions & 9 deletions basics/checking-accounts/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Checking Accounts

Solana Programs should perform checks on instructions to ensure security and that required invariants
are not being violated.
Solana programs should check the instructions they receive to ensure security and to make sure required invariants hold.

These checks vary and depend on the exact task of the Solana Program.
The exact checks depend on what the program does. Common ones include:

In this example we see some of the common checks a Solana Program can perform:

- checking the program ID from the instruction is the program ID of your program
- checking that the order and number of accounts are correct
- checking the initialization state of an account
- etc.
- Verifying that the `program_id` on the instruction matches your own program.
- Verifying the order and number of accounts.
- Checking the initialization state of an account.
2 changes: 1 addition & 1 deletion basics/checking-accounts/asm/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# checking-account-asm-program

Created with [sbpf](https://github.com/blueshift-gg/sbpf)
A Solana SBPF assembly implementation, scaffolded with [sbpf](https://github.com/blueshift-gg/sbpf).
47 changes: 22 additions & 25 deletions basics/close-account/anchor/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
# Destroy an Account

1. We're creating a `PDA` using [create_user.rs](programs/destroy-an-account/src/instructions/create_user.rs)
instruction.
```rust
#[account(
init,
seeds=[User::PREFIX.as_bytes(), user.key().as_ref()],
payer=user,
space=User::SIZE,
bump
)]
pub user_account: Box<Account<'info, User>>,
```
1. A `PDA` is created using the [create_user.rs](programs/destroy-an-account/src/instructions/create_user.rs) instruction.

2. We're closing it using [destroy_user.rs](programs/destroy-an-account/src/instructions/destroy_user.rs)
instruction, which uses `Anchor` `AccoutClose` `trait`.
```rust
#[account(
init,
seeds = [User::PREFIX.as_bytes(), user.key().as_ref()],
payer = user,
space = User::SIZE,
bump,
)]
pub user_account: Box<Account<'info, User>>,
```

```rust
user_account.close(user.to_account_info())?;
```
2. The account is closed in [destroy_user.rs](programs/destroy-an-account/src/instructions/destroy_user.rs), using Anchor's `close` helper on the account info:

3. In our test [destroy-an-account.ts](tests/destroy-an-account.ts) we're using `fetchNullable` since we expect
the account to be `null` prior to creation and after closing.
```rust
user_account.close(user.to_account_info())?;
```

```typescript
const userAccountBefore = await program.account.user.fetchNullable(userAccountAddress, "processed");
assert.equal(userAccountBefore, null);
...
...
3. The test [destroy-an-account.ts](tests/destroy-an-account.ts) verifies that the account is null both before creation and after closing, via `fetchNullable`:

```typescript
const userAccountBefore = await program.account.user.fetchNullable(userAccountAddress, "processed");
assert.equal(userAccountBefore, null);
// ...
const userAccountAfter = await program.account.user.fetchNullable(userAccountAddress, "processed");
assert.notEqual(userAccountAfter, null);
```
```
13 changes: 1 addition & 12 deletions basics/counter/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
# Counter

This example program allows anyone to create a counter and increment it.

Any counter can be incremented by any key.

## Note: Seahorse

Seahorse currently does not allow the program to initialize anchor
accounts unless they are PDAs.

Seahorse example only allows users to increment the counter that corresponds to their public key.


Anyone can create a counter and increment it. Any counter can be incremented by any key.
4 changes: 2 additions & 2 deletions basics/counter/anchor/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Anchor Counter

Anchor enforces init constraints that enforces good programming paradigms.
Anchor enforces `init` constraints that nudge you towards good programming patterns.

This means this program has an additional initialization instruction for `Counter`s that the Solana native program does not.
This program has an additional initialization handler for `Counter`s that the Solana native equivalent does not.
16 changes: 8 additions & 8 deletions basics/counter/mpl-stack/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Counter: MPL Stack

This example program is written using Solana native using MPL stack.

A Solana-native counter built using the MPL (Metaplex) stack.

## Setup

1. Build the program with `cargo build-sbf`
2. Compile the idl with `shank build`
3. Build the typescript SDK with `yarn solita`
- Temporarily, we have to modify line 58 in ts/generated/accounts/Counter.ts
to `const accountInfo = await connection.getAccountInfo(address, { commitment: "confirmed" });` in order to allow the tests to pass. In the future versions of Solita, this will be fixed.
4. Run tests with `yarn test`
1. Build the program: `cargo build-sbf`
2. Build the IDL: `shank build`
3. Build the TypeScript SDK: `pnpm solita`
- Temporary workaround: edit `ts/generated/accounts/Counter.ts` line 58 to
`const accountInfo = await connection.getAccountInfo(address, { commitment: "confirmed" });`
so that the tests pass. Future Solita versions will fix this.
4. Run tests: `pnpm test`
12 changes: 6 additions & 6 deletions basics/counter/native/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Counter: Solana Native

This example program is written in Solana using only the Solana toolsuite.
Counter written in Solana native, using only the Solana toolchain.

## Setup

1. Build the program with `cargo build-sbf`
2. Run tests + local validator with `yarn test`
1. Build the program: `cargo build-sbf`
2. Run the tests: `pnpm test`

## Debugging

1. Start test validator with `yarn start-validator`
2. Start listening to program logs with `solana config set -ul && solana logs`
3. Run tests with `yarn run-tests`
1. Start a test validator: `pnpm start-validator`
2. Listen to program logs: `solana config set -ul && solana logs`
3. Run the tests: `pnpm run-tests`
12 changes: 6 additions & 6 deletions basics/counter/pinocchio/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Counter: Solana Pinocchio

This example program is written in Solana using only the Solana toolsuite.
Counter written using the Pinocchio framework, with only the Solana toolchain.

## Setup

1. Build the program with `cargo build-sbf`
2. Run tests + local validator with `yarn test`
1. Build the program: `cargo build-sbf`
2. Run the tests: `pnpm test`

## Debugging

1. Start test validator with `yarn start-validator`
2. Start listening to program logs with `solana config set -ul && solana logs`
3. Run tests with `yarn run-tests`
1. Start a test validator: `pnpm start-validator`
2. Listen to program logs: `solana config set -ul && solana logs`
3. Run the tests: `pnpm run-tests`
22 changes: 11 additions & 11 deletions basics/create-account/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Create Account

:wrench: We're going to create a Solana account. :wrench:

This account is going to be a **system account** - meaning it will be owned by the System Program. In short, this means only the System Program will be allowed to modify it's data.
Create a Solana account.

In the test, we use two methods for creating the accounts. One of the methods uses Cross program invocation and the other calls the System Program directly.
The account is a **system account** — owned by the System Program, which means only the System Program can modify its data. In this example, the account simply holds some SOL.

Cross program invocation means that we send the transaction to create the account first to our deployed Solana Program, which then calls the System Program. See [here](https://github.com/solana-developers/program-examples/tree/main/basics/cross-program-invocation) for more Cross Program Invocation examples.
The tests cover two ways to create the account:

Calling the System Program directly means that the client sends the transaction to create the account directly to the Solana Program

In this example, this account will simply hold some SOL.
1. **Via cross-program invocation (CPI):** the client sends a transaction to our deployed program, which in turn calls the System Program.
2. **Directly:** the client sends the create-account transaction straight to the System Program.

### Links:
- [Solana Cookbook - How to Create a System Account](https://solanacookbook.com/references/accounts.html#how-to-create-a-system-account)
- [Rust Docs - solana_program::system_instruction::create_account](https://docs.rs/solana-program/latest/solana_program/system_instruction/fn.create_account.html)
See [cross-program-invocation](../cross-program-invocation) for more CPI examples.

## Links

- [Solana Cookbook — How to Create a System Account](https://solana.com/developers/cookbook/accounts/create-account)
- [Rust Docs — `solana_system_interface::instruction::create_account`](https://docs.rs/solana-system-interface/latest/solana_system_interface/instruction/fn.create_account.html)
2 changes: 1 addition & 1 deletion basics/create-account/asm/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# create-account-asm-program

Created with [sbpf](https://github.com/blueshift-gg/sbpf)
A Solana SBPF assembly implementation, scaffolded with [sbpf](https://github.com/blueshift-gg/sbpf).
Loading
Loading