Skip to content

add pinocchio spl-token-minter example#600

Open
MarkFeder wants to merge 4 commits into
solana-foundation:mainfrom
MarkFeder:tokens-spl-token-minter-pinocchio
Open

add pinocchio spl-token-minter example#600
MarkFeder wants to merge 4 commits into
solana-foundation:mainfrom
MarkFeder:tokens-spl-token-minter-pinocchio

Conversation

@MarkFeder

Copy link
Copy Markdown
Contributor

Adds a Pinocchio implementation of tokens/spl-token-minter, continuing the Pinocchio token-examples lane after transfer-tokens (#596), escrow (#598), and create-token (#599).

What it does

Two instructions, matching the anchor and native options:

  • Create — creates an SPL Token mint (9 decimals) and attaches an on-chain Metaplex metadata account (name, symbol, URI).
  • Mint — mints tokens into the payer's associated token account, creating that account first if needed.

Notes

  • Instructions are dispatched by a leading discriminator byte (0 = Create, 1 = Mint), matching the Borsh enum variant index used by the native example.
  • The Metaplex Token Metadata program has no typed Pinocchio crate, so the CreateMetadataAccountV3 CPI is built by hand (raw InstructionView / cpi::invoke) — the same approach as add pinocchio create-token example #599.
  • The bankrun test loads the Metaplex program from a mainnet-dumped token_metadata.so fixture via a postinstall script, like the anchor example.
  • The mint authority is passed as a non-signer and aliased to the payer (which signs), mirroring the native example.

Files

  • New: tokens/spl-token-minter/pinocchio/ (program + bankrun test)
  • Edited: root Cargo.toml (workspace member), README.md (pinocchio link), Cargo.lock

@MarkFeder
MarkFeder force-pushed the tokens-spl-token-minter-pinocchio branch from 2819844 to 383287d Compare July 8, 2026 21:13
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a Pinocchio implementation of the tokens/spl-token-minter example, continuing the Pinocchio token lane after transfer-tokens, escrow, and create-token. It provides two instructions — Create (allocates a mint, initializes it, and attaches a Metaplex metadata account via a hand-rolled CPI) and Mint (idempotent ATA creation followed by MintTo) — backed by a LiteSVM integration test.

  • Program: Discriminator-dispatched processor, zero-copy Borsh argument parsing, correct rent math using DEFAULT_LAMPORTS_PER_BYTE (2-year exemption already folded in), and a manually serialized CreateMetadataAccountV3 payload (variant 33) — matching the approach from add pinocchio create-token example #599.
  • Tests: test.ts loads the mainnet-dumped Metaplex fixture, exercises both instructions end-to-end, and uses readBigUInt64LE / BigInt comparison for the token-amount assertion.
  • Tooling: prepare.mjs uses a per-command -um flag for the program dump (global CLI config is not mutated), and build-and-test correctly directs the .so artifact to tests/fixtures.

Confidence Score: 5/5

Safe to merge — new self-contained example directory, no changes to existing program logic.

All program logic is well-structured and consistent with sibling pinocchio examples. Previously flagged issues (global CLI mutation, BigInt precision, mint-authority aliasing) have been addressed or are intentional design choices documented in the code. The hand-rolled Metaplex CPI payload matches the correct variant-33 Borsh layout, rent math is correct, and the LiteSVM test exercises both instructions end-to-end.

No files require special attention.

Important Files Changed

Filename Overview
tokens/spl-token-minter/pinocchio/program/src/instructions/create.rs Implements create_token: allocates mint via system CPI, initializes mint with decimals/freeze authority, then hand-rolls a Metaplex CreateMetadataAccountV3 CPI (variant 33). Rent is computed correctly using Pinocchio's DEFAULT_LAMPORTS_PER_BYTE (already includes 2-year exemption). No issues found.
tokens/spl-token-minter/pinocchio/program/src/instructions/mint.rs Implements mint_tokens: idempotent ATA creation then MintTo CPI. The non-signer mint_authority slot aliased to payer is documented and intentional (mirrors the native example). No issues found.
tokens/spl-token-minter/pinocchio/program/src/instructions/mod.rs Defines shared constants (MINT_SIZE=82, TOKEN_DECIMALS=9, TOKEN_METADATA_PROGRAM_ID), CreateTokenArgs with zero-copy Borsh string parser, and parse_u64. All correct and well-documented.
tokens/spl-token-minter/pinocchio/program/src/processor.rs Single-byte discriminator dispatch (0 = Create, 1 = Mint); returns InvalidInstructionData on unknown variant. Clean and correct.
tokens/spl-token-minter/pinocchio/tests/test.ts LiteSVM integration test covering Create and Mint flows. Uses readBigUInt64LE with BigInt comparison (150n) for the amount assertion. Correct ATA amount offset (64). No issues found.
tokens/spl-token-minter/pinocchio/prepare.mjs Dumps Metaplex program from mainnet using solana program dump -um (per-command flag), without mutating the global Solana CLI config. Previously identified issue is resolved.
tokens/spl-token-minter/pinocchio/program/Cargo.toml Declares crate spl-token-minter-pinocchio-program with correct workspace dependencies for pinocchio, system, token, and ATA crates.
tokens/spl-token-minter/pinocchio/package.json Standard package config with postinstall hook for fixture download and correct build-and-test output dir targeting tests/fixtures.
tokens/spl-token-minter/pinocchio/program/src/lib.rs Minimal #![no_std] entrypoint wiring up the processor and bump allocator (required for alloc::vec::Vec in create.rs). Correct.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Program as spl-token-minter<br/>(Pinocchio)
    participant System as System Program
    participant Token as SPL Token Program
    participant Metadata as Metaplex Token<br/>Metadata Program
    participant ATA as Associated Token<br/>Account Program

    Note over Client,Metadata: Instruction 0 — Create
    Client->>Program: create(name, symbol, uri)
    Program->>System: CreateAccount(mint, 82 bytes, rent-exempt)
    System-->>Program: OK
    Program->>Token: "InitializeMint2(decimals=9, authority=payer)"
    Token-->>Program: OK
    Program->>Metadata: CreateMetadataAccountV3(DataV2)
    Metadata-->>Program: OK
    Program-->>Client: OK

    Note over Client,ATA: Instruction 1 — Mint
    Client->>Program: mint(quantity)
    Program->>ATA: CreateIdempotent(payer, mint)
    ATA-->>Program: OK (creates or no-ops)
    Program->>Token: "MintTo(mint, ata, authority=payer, amount)"
    Token-->>Program: OK
    Program-->>Client: OK
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Program as spl-token-minter<br/>(Pinocchio)
    participant System as System Program
    participant Token as SPL Token Program
    participant Metadata as Metaplex Token<br/>Metadata Program
    participant ATA as Associated Token<br/>Account Program

    Note over Client,Metadata: Instruction 0 — Create
    Client->>Program: create(name, symbol, uri)
    Program->>System: CreateAccount(mint, 82 bytes, rent-exempt)
    System-->>Program: OK
    Program->>Token: "InitializeMint2(decimals=9, authority=payer)"
    Token-->>Program: OK
    Program->>Metadata: CreateMetadataAccountV3(DataV2)
    Metadata-->>Program: OK
    Program-->>Client: OK

    Note over Client,ATA: Instruction 1 — Mint
    Client->>Program: mint(quantity)
    Program->>ATA: CreateIdempotent(payer, mint)
    ATA-->>Program: OK (creates or no-ops)
    Program->>Token: "MintTo(mint, ata, authority=payer, amount)"
    Token-->>Program: OK
    Program-->>Client: OK
Loading

Reviews (5): Last reviewed commit: "spl-token-minter pinocchio: migrate test..." | Re-trigger Greptile

try {
mkdirSync(outputDir, { recursive: true });
// Point the Solana CLI at mainnet, where the canonical program lives.
execSync("solana config set -um", { stdio: "inherit" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 postinstall mutates the global Solana CLI config

solana config set -um permanently sets the user's active cluster to mainnet-beta in ~/.config/solana/cli/config.yml. Any developer who runs pnpm install in this directory while working against devnet or localnet will silently have their Solana CLI endpoint changed. solana program dump accepts a --url flag (--url mainnet-beta or --url m), which would fetch from mainnet without touching the global config.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dd291b9. Switched to a per-command solana program dump -um <id> and dropped the global solana config set -um, so postinstall no longer mutates the developer's ~/.config/solana/cli/config.yml. This mirrors the pattern already used in the sibling pda-mint-authority example.

Comment on lines +46 to +52
MintTo {
mint: mint_account,
account: associated_token_account,
mint_authority,
amount: quantity,
}
.invoke()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Implicit mint_authority == payer invariant is not asserted

MintTo requires its mint_authority to be a signer. Here mint_authority (account index 1) is declared as a non-signer ([]) in the instruction — the CPI works only because the payer's address is passed for both slots and the payer signs the transaction. If a caller provides a genuinely different mint_authority key, the SPL Token program rejects the CPI with an opaque "missing required signature" error instead of a clear program-level message. The same applies to create.rs. For an educational example a quick equality check would make the design intent explicit and produce a cleaner error for learners who experiment with different account layouts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional for this example. It models the canonical "payer is the mint authority" flow: the same key fills both the payer and mint_authority slots, and since the payer already signs the transaction the MintTo/InitializeMint CPIs satisfy the signer requirement — which is why mint_authority is marked non-signer ([]) here. The SPL Token program is the authority on that invariant and enforces it, so an added equality check would be redundant and would diverge from the anchor example this port mirrors. Happy to add an explicit assert if you'd prefer it for teaching clarity.

Comment on lines +53 to +56
function readTokenAmount(data: Uint8Array): number {
const buffer = Buffer.from(data);
return buffer.readUInt32LE(64) + buffer.readUInt32LE(68) * 4294967296; // 2^32
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 readTokenAmount silently loses precision for amounts above 2^53

The high-word multiplication buffer.readUInt32LE(68) * 4294967296 uses JavaScript's number type, which only holds integers exactly up to 2^53. Token amounts are stored as raw u64 units: 150 tokens at 9 decimals = 1.5 × 10^11, well under 2^53 and safe here. But if a future test mints a much larger amount, the assertion could silently pass with a wrong value. The u64le helper already acknowledges this ceiling; readTokenAmount should carry a similar comment, or the check should use BigInt arithmetic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dd291b9 via your suggested alternative — added a comment mirroring the u64le encoder, documenting the deliberate es6/no-BigInt design and the exact-below-2^53 guarantee (the 150-token mint asserted here is well within range). Kept number for symmetry with the encoder rather than introducing BigInt on only the decode side.

@MarkFeder
MarkFeder force-pushed the tokens-spl-token-minter-pinocchio branch from dd291b9 to c6690d5 Compare July 9, 2026 07:38
@MarkFeder

Copy link
Copy Markdown
Contributor Author

@Perelyn-sama @dev-jodee — rebased onto latest main (picks up the ASM sbpf/Solana pin from #625), CI is now fully green. Ready for review whenever you have a chance 🙏

Move the async bankrun setup out of the `describe` callback and into a
`before` hook so Mocha collects the `it` blocks (an async `describe` body
registers tests after the suite is already collected, so nothing ran).

With the test now executing, replace `Rent::try_minimum_balance` with the
integer rent formula: its floating-point exemption-threshold path emits an
opcode the bankrun VM rejects ("unsupported BPF instruction"). Matches the
create-token example.
@MarkFeder
MarkFeder requested a review from dev-jodee as a code owner July 15, 2026 21:20

@dev-jodee dev-jodee left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will require changes based on #624 final version

Apply the kit + litesvm template from the mint-close-authority example:
build and sign transactions with @solana/kit and run them on litesvm,
dropping @solana/web3.js and solana-bankrun entirely. PDAs (metadata, ATA)
are derived with getProgramDerivedAddress; the Metaplex Token Metadata
program is still dumped from mainnet by prepare.mjs and loaded into LiteSVM
via addProgramFromFile (litesvm bundles only the SPL programs).

- deps: drop @solana/web3.js + solana-bankrun, add litesvm; pin @solana/kit
  to ^6.10.0 (litesvm's kit major) so there is a single kit in the tree
- tsconfig: bump typescript to ^5 and lib to es2022+dom (kit's types), add
  @types/node; the suite is type-clean under tsc --noEmit
@MarkFeder
MarkFeder requested a review from dev-jodee July 21, 2026 23:10
@MarkFeder

Copy link
Copy Markdown
Contributor Author

Applied the kit + litesvm template from #624 here as well. The test now builds and signs transactions with @solana/kit and runs them on litesvm, so @solana/web3.js and solana-bankrun are dropped entirely. Pinned @solana/kit to ^6.10.0 (litesvm's kit major) for a single kit in the tree, and bumped typescript/tsconfig (es2022 + dom, @types/node) so the suite is type-clean under tsc --noEmit. Verified locally: cargo build-sbf + ts-mocha2 passing, plus tsc --noEmit and biome clean. Commit is signed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants