add pinocchio pda-mint-authority example#612
Conversation
Greptile SummaryAdds a Pinocchio port of the
Confidence Score: 5/5Safe to merge; the three-instruction flow, PDA signing, and hand-rolled Metaplex CPI payloads are all implemented correctly and validated by the bankrun test suite. The PDA-as-signer pattern, bump verification, Borsh payload construction for both Metaplex CPIs, and account ordering are all correct. The two findings are a silent error swallow in the postinstall fixture script and a small over-allocation in the PDA account — neither affects runtime correctness. No files require special attention; Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Program as pda-mint-authority (pinocchio)
participant System as System Program
participant Token as Token Program
participant Meta as Token Metadata Program
Note over Client,Meta: Instruction 0 — Init
Client->>Program: Init(bump)
Program->>Program: derive_address([mint_authority], bump), verify address
Program->>System: CreateAccount invoke_signed(PDA seeds)
Program->>Program: MintAuthorityPda.serialize(bump)
Note over Client,Meta: Instruction 1 — Create
Client->>Program: Create(name, symbol, uri)
Program->>Program: read bump from PDA, verify address
Program->>System: CreateAccount (payer to mint keypair)
Program->>Token: "InitializeMint2 (decimals=0, authority=PDA)"
Program->>Meta: CreateMetadataAccountV3 invoke_signed(PDA seeds)
Note over Client,Meta: Instruction 2 — Mint
Client->>Program: Mint
Program->>Program: read bump from PDA, verify address
Program->>Token: CreateIdempotent ATA
Program->>Token: "MintTo(amount=1) invoke_signed(PDA seeds)"
Program->>Meta: "CreateMasterEditionV3(max_supply=Some(1)) invoke_signed(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 as pda-mint-authority (pinocchio)
participant System as System Program
participant Token as Token Program
participant Meta as Token Metadata Program
Note over Client,Meta: Instruction 0 — Init
Client->>Program: Init(bump)
Program->>Program: derive_address([mint_authority], bump), verify address
Program->>System: CreateAccount invoke_signed(PDA seeds)
Program->>Program: MintAuthorityPda.serialize(bump)
Note over Client,Meta: Instruction 1 — Create
Client->>Program: Create(name, symbol, uri)
Program->>Program: read bump from PDA, verify address
Program->>System: CreateAccount (payer to mint keypair)
Program->>Token: "InitializeMint2 (decimals=0, authority=PDA)"
Program->>Meta: CreateMetadataAccountV3 invoke_signed(PDA seeds)
Note over Client,Meta: Instruction 2 — Mint
Client->>Program: Mint
Program->>Program: read bump from PDA, verify address
Program->>Token: CreateIdempotent ATA
Program->>Token: "MintTo(amount=1) invoke_signed(PDA seeds)"
Program->>Meta: "CreateMasterEditionV3(max_supply=Some(1)) invoke_signed(PDA seeds)"
Reviews (4): 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.
1cfe23d to
0be01f1
Compare
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.
0be01f1 to
0f6fa01
Compare
|
@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 🙏 |
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).