add pinocchio pda-mint-authority example#612
Conversation
Greptile SummaryThis PR adds a Pinocchio port of the
Confidence Score: 5/5Safe to merge; the three-instruction flow is correctly implemented, CPI encoding matches Metaplex layout, and the bankrun tests cover the full Init→Create→Mint sequence end-to-end. The instruction logic, PDA signer seed construction, Borsh encoding for both Metaplex CPIs, and ATA idempotency are all correct. The two comments left are educational suggestions for an example program rather than defects in the on-chain behavior. No files require special attention beyond the two inline suggestions; create.rs and state.rs have minor points worth a second look before this is used as a copy-paste starting point. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Program
participant SystemProgram
participant TokenProgram
participant ATAProgram
participant MetaplexProgram
Note over Client,MetaplexProgram: Instruction 0 - Init
Client->>Program: Init(bump)
Program->>Program: derive_address seeds+bump, verify account matches
Program->>SystemProgram: CreateAccount invoke_signed with PDA seeds
Program->>Program: Write bump into PDA data[0]
Note over Client,MetaplexProgram: Instruction 1 - Create
Client->>Program: Create(name, symbol, uri)
Program->>Program: Read bump from PDA data[0], re-derive and verify address
Program->>SystemProgram: CreateAccount for mint 82 bytes
Program->>TokenProgram: "InitializeMint2 decimals=0 authority=PDA"
Program->>MetaplexProgram: CreateMetadataAccountV3 invoke_signed with PDA seeds
Note over Client,MetaplexProgram: Instruction 2 - Mint
Client->>Program: Mint
Program->>Program: Read bump from PDA data[0], re-derive and verify address
Program->>ATAProgram: CreateIdempotent ATA for payer
Program->>TokenProgram: "MintTo amount=1 invoke_signed with PDA seeds"
Program->>MetaplexProgram: "CreateMasterEditionV3 max_supply=1 invoke_signed with PDA seeds"
%%{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
participant SystemProgram
participant TokenProgram
participant ATAProgram
participant MetaplexProgram
Note over Client,MetaplexProgram: Instruction 0 - Init
Client->>Program: Init(bump)
Program->>Program: derive_address seeds+bump, verify account matches
Program->>SystemProgram: CreateAccount invoke_signed with PDA seeds
Program->>Program: Write bump into PDA data[0]
Note over Client,MetaplexProgram: Instruction 1 - Create
Client->>Program: Create(name, symbol, uri)
Program->>Program: Read bump from PDA data[0], re-derive and verify address
Program->>SystemProgram: CreateAccount for mint 82 bytes
Program->>TokenProgram: "InitializeMint2 decimals=0 authority=PDA"
Program->>MetaplexProgram: CreateMetadataAccountV3 invoke_signed with PDA seeds
Note over Client,MetaplexProgram: Instruction 2 - Mint
Client->>Program: Mint
Program->>Program: Read bump from PDA data[0], re-derive and verify address
Program->>ATAProgram: CreateIdempotent ATA for payer
Program->>TokenProgram: "MintTo amount=1 invoke_signed with PDA seeds"
Program->>MetaplexProgram: "CreateMasterEditionV3 max_supply=1 invoke_signed with PDA seeds"
Reviews (2): Last reviewed commit: "pda-mint-authority pinocchio: dump fixtu..." | 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" }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Good catch — fixed in 1cfe23d. prepare.mjs now dumps the program with a per-command solana program dump -um … instead of solana config set -um, so pnpm install no longer mutates the developer's global Solana CLI cluster.
| 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(), | ||
| ); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Leaving this as-is, for two reasons:
-
Consistency with the established pinocchio pattern. This is exactly what the already-merged
tokens/escrow/pinocchiodoes (make_offer.rs): take the bump from instruction data, thenderive_address(&[seed], Some(bump), program_id)and reject the tx unless the supplied account equals that PDA. Every pinocchio example in the repo follows this; matching it keeps the teaching examples uniform. -
Toolchain constraint. Deriving the canonical bump on-chain needs
find_program_address/create_program_address, whose off-target implementation insolana-addressis gated behind thecurve25519feature. CI lint runscargo clippy -- -D warningson the host target (no--target sbf), so referencing it there fails to compile unless we pull in the curve25519 dependency — which the lightweight pinocchio stack intentionally avoids.derive_address(frompinocchio-pubkey) is the host-compatible primitive, and it only derives for a given bump.
On safety: the supplied mint-authority account is validated against derive_address(Some(bump)), and create/mint re-derive the signer seeds from the bump persisted in that account, so the program is internally consistent. A client that deliberately passes a non-canonical bump only affects its own address derivation; the test (and any normal client) sources the bump from findProgramAddressSync, which always returns the canonical one.
Use 'solana program dump -um' instead of 'solana config set -um', so running pnpm install no longer permanently switches the developer's Solana CLI cluster to mainnet.
Adds a Pinocchio port of the
tokens/pda-mint-authorityexample, alongside the existinganchorandnativeversions.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
MyInstructionenum):0) — creates the mint-authority PDA ([b"mint_authority"]), signed by its own seeds, and persists the canonical bump in the account.1) — creates a 0-decimal SPL mint whose authority is the PDA, then attaches a Metaplex metadata account via a hand-rolledCreateMetadataAccountV3CPI. The metadata CPI is authorized with the PDA's seeds viainvoke_signed.2) — creates the payer's associated token account (idempotent), mints the single token, then creates the master edition via a hand-rolledCreateMasterEditionV3CPI (max_supply = Some(1)). Both theMintToand 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, theMintTo, and the master-edition CPI all sign as the PDA usingpinocchio::cpi::{Seed, Signer}andinvoke_signed, rather than relying on a wallet signature. The bump recorded byInitis read back from the PDA account to rebuild the signer seeds without re-deriving the address on-chain.Tests
tests/test.tsruns undersolana-bankrun, loading the program plus the Token Metadata program (dumped from mainnet intotests/fixturesbyprepare.mjs). Three cases:CreateMasterEditionV3CPI succeeded).