Skip to content

add pinocchio pda-mint-authority example#612

Open
MarkFeder wants to merge 1 commit into
solana-foundation:mainfrom
MarkFeder:tokens-pda-mint-authority-pinocchio
Open

add pinocchio pda-mint-authority example#612
MarkFeder wants to merge 1 commit into
solana-foundation:mainfrom
MarkFeder:tokens-pda-mint-authority-pinocchio

Conversation

@MarkFeder

Copy link
Copy Markdown
Contributor

Adds a Pinocchio port of the tokens/pda-mint-authority example, alongside the existing anchor and native versions.

What it does

A program-derived address — not a wallet — is the mint and freeze authority for every NFT this program creates. Three instructions, dispatched by a leading discriminator byte (matching the native MyInstruction enum):

  • Init (0) — creates the mint-authority PDA ([b"mint_authority"]), signed by its own seeds, and persists the canonical bump in the account.
  • Create (1) — creates a 0-decimal SPL mint whose authority is the PDA, then attaches a Metaplex metadata account via a hand-rolled CreateMetadataAccountV3 CPI. The metadata CPI is authorized with the PDA's seeds via invoke_signed.
  • Mint (2) — creates the payer's associated token account (idempotent), mints the single token, then creates the master edition via a hand-rolled CreateMasterEditionV3 CPI (max_supply = Some(1)). Both the MintTo and master-edition CPIs are signed by the PDA.

The new building block here versus the other token examples is PDA-as-signer: Init, the metadata CPI, the MintTo, and the master-edition CPI all sign as the PDA using pinocchio::cpi::{Seed, Signer} and invoke_signed, rather than relying on a wallet signature. The bump recorded by Init is read back from the PDA account to rebuild the signer seeds without re-deriving the address on-chain.

Tests

tests/test.ts runs under solana-bankrun, loading the program plus the Token Metadata program (dumped from mainnet into tests/fixtures by prepare.mjs). Three cases:

  • Init asserts the PDA account is owned by the program and stores the expected bump.
  • Create asserts the mint is owned by the Token program and the metadata account is owned by Token Metadata and contains the NFT name.
  • Mint asserts the ATA holds exactly 1 token and the master edition account exists and is owned by Token Metadata (proving the PDA-signed CreateMasterEditionV3 CPI succeeded).

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a Pinocchio port of the tokens/pda-mint-authority example alongside the existing anchor and native versions, implementing the same three-instruction flow (Init / Create / Mint) using low-level Pinocchio CPI primitives and hand-rolled Borsh serialization for the Metaplex CPIs.

  • Init creates the mint-authority PDA and persists its bump; Create initialises an SPL mint and attaches Metaplex metadata via a manually-constructed CreateMetadataAccountV3 CPI signed by the PDA; Mint creates the ATA idempotently, mints the single token, and locks the mint via CreateMasterEditionV3 — all three PDA-signed CPIs work correctly in tests.
  • prepare.mjs is wired as a postinstall hook to dump the mainnet Metaplex program into tests/fixtures; it permanently redirects the developer's Solana CLI cluster to mainnet without restoring the prior config.
  • init.rs accepts the canonical bump as a client-supplied argument rather than deriving it on-chain; a non-canonical (bump, PDA) pair would be accepted, creating the authority at a non-standard address that client-side findProgramAddressSync would not resolve to.

Confidence Score: 4/5

The three-instruction flow is functionally correct and all tests pass; the two findings are edge-case correctness and a developer UX side-effect.

The prepare.mjs hook silently mutates the developer's Solana CLI cluster config on every pnpm install, and init.rs will accept any valid (bump, PDA) pair rather than enforcing the canonical bump — a non-canonical init would produce a mint authority address that standard findProgramAddressSync calls won't resolve to, potentially breaking integrations. Neither issue affects the test suite or the primary happy-path flow, but both are real defects in the changed code worth addressing before broader use.

tokens/pda-mint-authority/pinocchio/prepare.mjs (cluster config side-effect) and tokens/pda-mint-authority/pinocchio/program/src/instructions/init.rs (non-canonical bump acceptance)

Important Files Changed

Filename Overview
tokens/pda-mint-authority/pinocchio/program/src/instructions/init.rs Creates the mint-authority PDA; accepts a client-supplied bump without canonicality validation, which can create the authority at a non-standard address.
tokens/pda-mint-authority/pinocchio/prepare.mjs Dumps mainnet Metaplex program for bankrun; permanently switches the developer's Solana CLI cluster to mainnet without restoring it.
tokens/pda-mint-authority/pinocchio/program/src/instructions/create.rs Hand-rolls CreateMetadataAccountV3 CPI; PDA address cross-check before use is correct; duplicate mint_authority ref in invoke_signed works at runtime.
tokens/pda-mint-authority/pinocchio/program/src/instructions/mint.rs Handles ATA creation (idempotent), PDA-signed MintTo, and CreateMasterEditionV3 CPI; account layout matches Metaplex spec and tests verify the full flow.
tokens/pda-mint-authority/pinocchio/tests/test.ts Comprehensive bankrun test covering all three instructions; correctly derives all PDA addresses client-side and verifies on-chain state after each instruction.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Program as pinocchio program
    participant System as System Program
    participant Token as SPL Token
    participant ATA as Associated Token
    participant Meta as Token Metadata

    Client->>Program: "Init (discriminator=0, bump)"
    Program->>System: CreateAccount (PDA, signed via seeds)
    Program-->>Client: PDA stores bump

    Client->>Program: "Create (discriminator=1, name/symbol/uri)"
    Program->>System: CreateAccount (mint)
    Program->>Token: "InitializeMint2 (authority=PDA)"
    Program->>Meta: CreateMetadataAccountV3 (invoke_signed w/ PDA seeds)
    Program-->>Client: Mint + Metadata accounts created

    Client->>Program: "Mint (discriminator=2)"
    Program->>ATA: CreateIdempotent (payer ATA)
    Program->>Token: MintTo 1 token (invoke_signed w/ PDA seeds)
    Program->>Meta: CreateMasterEditionV3 (invoke_signed w/ PDA seeds)
    Program-->>Client: ATA holds 1 token, master edition locked
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 pinocchio program
    participant System as System Program
    participant Token as SPL Token
    participant ATA as Associated Token
    participant Meta as Token Metadata

    Client->>Program: "Init (discriminator=0, bump)"
    Program->>System: CreateAccount (PDA, signed via seeds)
    Program-->>Client: PDA stores bump

    Client->>Program: "Create (discriminator=1, name/symbol/uri)"
    Program->>System: CreateAccount (mint)
    Program->>Token: "InitializeMint2 (authority=PDA)"
    Program->>Meta: CreateMetadataAccountV3 (invoke_signed w/ PDA seeds)
    Program-->>Client: Mint + Metadata accounts created

    Client->>Program: "Mint (discriminator=2)"
    Program->>ATA: CreateIdempotent (payer ATA)
    Program->>Token: MintTo 1 token (invoke_signed w/ PDA seeds)
    Program->>Meta: CreateMasterEditionV3 (invoke_signed w/ PDA seeds)
    Program-->>Client: ATA holds 1 token, master edition locked
Loading

Reviews (1): Last reviewed commit: "add pinocchio pda-mint-authority example" | Re-trigger Greptile

Comment on lines +22 to +25
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 solana config set -um is never reverted

pnpm install runs this script as a postinstall hook, permanently redirecting the developer's Solana CLI cluster to mainnet. A developer who normally works against devnet or localnet will silently end up with a wrong cluster config after installing. Saving the current cluster with solana config get json_rpc_url, pointing to mainnet only for the dump, then restoring it afterward would avoid the side-effect.

Comment on lines +30 to +37
let bump = *data.first().ok_or(ProgramError::InvalidInstructionData)?;

// Verify the supplied account is the canonical PDA for this bump.
let pda = derive_address(
&[MintAuthorityPda::SEED_PREFIX],
Some(bump),
program_id.as_array(),
);

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 Non-canonical bump is accepted without validation

The bump is taken directly from instruction data and passed to derive_address with Some(bump), which computes a PDA for whatever bump value was supplied rather than requiring the canonical one. A caller who deliberately (or accidentally) provides a non-canonical bump will create the mint-authority account at a different address than findProgramAddressSync would derive client-side. Downstream clients that recompute the PDA without knowing which bump was stored will then resolve a different address and be unable to interact with the mints this program created. Since pinocchio_pubkey::derive_address with bump: None finds the canonical bump on-chain, using it here would prevent this class of error.

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