Skip to content

add pinocchio spl-token-minter example#600

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

add pinocchio spl-token-minter example#600
MarkFeder wants to merge 2 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 alongside transfer-tokens, escrow, and create-token. It mirrors the existing native example's wire format and account layout closely.

  • program/src/ — Two instructions dispatched by a leading discriminator byte: Create (initializes an SPL mint + Metaplex metadata account via hand-rolled CPI) and Mint (idempotently creates an ATA and calls MintTo); parsing helpers in mod.rs validate Borsh strings and u64 fields safely.
  • tests/test.ts — Bankrun test that loads a mainnet-dumped token_metadata.so fixture (fetched by prepare.mjs during postinstall) and exercises both instructions end-to-end, asserting mint ownership and token balance.
  • prepare.mjs — Dumps the Metaplex program with solana program dump -um (per-command flag, not global config mutation); workspace Cargo.toml and README.md are updated to include the new crate and link.

Confidence Score: 5/5

Safe to merge — new self-contained example with no changes to shared program logic.

The change adds a new Pinocchio example that is fully isolated from existing programs. The Metaplex CPI is hand-rolled correctly against the V3 instruction layout, parsing helpers are bounds-checked, and the bankrun test exercises both instructions end-to-end. All prior review concerns have been addressed in earlier commits.

No files require special attention.

Important Files Changed

Filename Overview
tokens/spl-token-minter/pinocchio/program/src/instructions/create.rs Creates SPL mint and Metaplex metadata via hand-rolled CPI; account layout and instruction data serialization match Metaplex V3 format correctly.
tokens/spl-token-minter/pinocchio/program/src/instructions/mint.rs Idempotently creates ATA then calls MintTo; non-signer mint_authority design is intentional and matches native example.
tokens/spl-token-minter/pinocchio/program/src/instructions/mod.rs Borsh string and u64 parsing helpers are bounds-checked and return InvalidInstructionData on truncation.
tokens/spl-token-minter/pinocchio/program/src/processor.rs Clean discriminator-based dispatch; returns InvalidInstructionData on empty or unknown instruction data.
tokens/spl-token-minter/pinocchio/tests/test.ts Bankrun end-to-end tests for both Create and Mint; readTokenAmount correctly reads u64 at offset 64 of the SPL token account layout.
tokens/spl-token-minter/pinocchio/prepare.mjs Dumps Metaplex program using per-command -um flag without mutating global Solana CLI config; intentionally non-fatal on failure.
tokens/spl-token-minter/pinocchio/package.json Standard pnpm setup with postinstall hook; deploy path references program.so, consistent with all other pinocchio examples in this repo.

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 ATA as Associated Token<br/>Account Program
    participant Metaplex as Token Metadata<br/>Program

    Note over Client,Metaplex: Instruction 0 — Create
    Client->>Program: Create(name, symbol, uri)
    Program->>System: "CreateAccount(mint_account, space=82)"
    System-->>Program: ok
    Program->>Token: "InitializeMint2(decimals=9, mint_authority=payer)"
    Token-->>Program: ok
    Program->>Metaplex: "CreateMetadataAccountV3(DataV2, is_mutable=false)"
    Metaplex-->>Program: ok
    Program-->>Client: ok

    Note over Client,Metaplex: Instruction 1 — Mint
    Client->>Program: Mint(quantity)
    Program->>ATA: CreateIdempotent(payer, mint)
    ATA-->>Program: ok (created or already exists)
    Program->>Token: "MintTo(mint, ata, authority=payer, amount=quantity)"
    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 ATA as Associated Token<br/>Account Program
    participant Metaplex as Token Metadata<br/>Program

    Note over Client,Metaplex: Instruction 0 — Create
    Client->>Program: Create(name, symbol, uri)
    Program->>System: "CreateAccount(mint_account, space=82)"
    System-->>Program: ok
    Program->>Token: "InitializeMint2(decimals=9, mint_authority=payer)"
    Token-->>Program: ok
    Program->>Metaplex: "CreateMetadataAccountV3(DataV2, is_mutable=false)"
    Metaplex-->>Program: ok
    Program-->>Client: ok

    Note over Client,Metaplex: Instruction 1 — Mint
    Client->>Program: Mint(quantity)
    Program->>ATA: CreateIdempotent(payer, mint)
    ATA-->>Program: ok (created or already exists)
    Program->>Token: "MintTo(mint, ata, authority=payer, amount=quantity)"
    Token-->>Program: ok
    Program-->>Client: ok
Loading

Reviews (3): Last reviewed commit: "spl-token-minter pinocchio: dump fixture..." | 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 🙏

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.

1 participant