Skip to content

Add Token-2022 transfer-fee Pinocchio example#653

Open
MarkFeder wants to merge 2 commits into
solana-foundation:mainfrom
MarkFeder:tokens-token-2022-transfer-fee-pinocchio
Open

Add Token-2022 transfer-fee Pinocchio example#653
MarkFeder wants to merge 2 commits into
solana-foundation:mainfrom
MarkFeder:tokens-token-2022-transfer-fee-pinocchio

Conversation

@MarkFeder

Copy link
Copy Markdown
Contributor

What

Adds a Pinocchio implementation of the Token-2022 transfer-fee example, alongside the existing anchor and native versions. Continues the Token-2022 Pinocchio sub-series (after mint-close-authority #624, non-transferable #630, and default-account-state #652), following the same kit + litesvm + official-@solana-program-packages template.

How it works

The single instruction creates a Token-2022 mint carrying the TransferFeeConfig extension. Transfers of the mint's tokens then withhold a fee — a percentage of the transferred amount capped at maximum_fee — which the withdraw authority can later collect.

To exercise both transfer-fee instructions (mirroring the native example), the program hand-rolls three CPIs. The transfer-fee instructions are namespaced under the TransferFeeExtension wrapper (Token-2022 instruction variant 26) with a second discriminator byte:

  1. InitializeTransferFeeConfig (sub 0) — fee 1% (100 bp), max fee 5 tokens, with the payer as both the config and withdraw-withheld authority. Before InitializeMint.
  2. InitializeMint.
  3. SetTransferFee (sub 5) — update the fee to 10% (1000 bp), after InitializeMint, signed by the transfer-fee config authority.

The mint authority doubles as the freeze authority and is supplied as the same key as the payer, so a single signature covers funding the account and signing the fee update. The extended mint is 278 bytes (base 166 + a 112-byte TLV entry whose 108-byte value holds the two optional authorities, the withheld amount, and the older/newer TransferFees).

Test

litesvm + @solana/kit. The test builds the mint, then decodes it with the official @solana-program/token-2022 codec and asserts:

  • the account is owned by Token-2022 and sized to 278 bytes,
  • the decoded decimals,
  • both authorities equal the payer, and
  • the newer (current) transfer fee is 10% (1000 bp) with a max fee of 5 tokens.
Token-2022 Transfer Fee (Pinocchio)
  ✔ Creates a Token-2022 mint with a transfer fee config
1 passing

Verified locally: cargo build-sbf, ts-mocha, tsc --noEmit, biome, cargo fmt --check, clippy, and pnpm install --frozen-lockfile all clean.

Ports the transfer-fee Token-2022 example to Pinocchio, matching the
mint-close-authority template (kit + litesvm, official @solana-program
packages, real Rent::get()).

The program hand-rolls three Token-2022 CPIs to create a mint with the
TransferFeeConfig extension: it initializes the fee config to 1% before
InitializeMint, then (mirroring the native example) updates it to 10%
afterwards via SetTransferFee, which the transfer-fee config authority signs.
Both the config and withdraw-withheld authorities are set to the payer. Mint
size is 278 bytes (a 108-byte TransferFeeConfig TLV value). The litesvm test
decodes the mint with the official Token-2022 codec and asserts the stored
authorities and the newer transfer fee (10%, max 5 tokens).
@MarkFeder
MarkFeder requested a review from dev-jodee as a code owner July 26, 2026 16:39
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a Pinocchio Token-2022 transfer-fee example.

  • Creates an extended mint and initializes its transfer-fee configuration before mint initialization.
  • Updates the configured fee after initialization through a signed Token-2022 CPI.
  • Adds LiteSVM coverage for mint ownership, account size, authorities, decimals, and the resulting fee configuration.
  • Registers the program in the Rust workspace and adds its build, deployment, and package configuration.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported fee-scaling overflow now returns InvalidInstructionData through checked arithmetic.

Important Files Changed

Filename Overview
tokens/token-2022/transfer-fee/pinocchio/program/src/instructions/create_mint.rs Implements account creation and the ordered Token-2022 CPIs, with checked maximum-fee scaling that resolves the previously reported overflow.
tokens/token-2022/transfer-fee/pinocchio/program/src/processor.rs Routes the program's single instruction directly to the mint-creation handler.
tokens/token-2022/transfer-fee/pinocchio/tests/test.ts Exercises mint creation in LiteSVM and decodes the resulting extension using the official Token-2022 codec.
tokens/token-2022/transfer-fee/pinocchio/package.json Defines the required build, test, deployment, and local dependency configuration.
Cargo.toml Registers the new Pinocchio program as a workspace member.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Example as Pinocchio Program
    participant System as System Program
    participant Token as Token-2022
    Client->>Example: CreateMint(decimals)
    Example->>System: CreateAccount(278 bytes)
    Example->>Token: InitializeTransferFeeConfig(1%, max fee)
    Example->>Token: InitializeMint(decimals, authorities)
    Example->>Token: SetTransferFee(10%, max fee)
    Example-->>Client: Mint created
Loading

Reviews (3): Last reviewed commit: "token-2022 transfer-fee pinocchio: guard..." | Re-trigger Greptile

.invoke()?;

// The max fee is 5 tokens, scaled by the mint's decimals.
let max_fee = 5u64 * 10u64.pow(args.decimals as u32);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unchecked fee scaling overflows

When the instruction supplies decimals of 19 or greater, this unvalidated u8 causes the u64 fee calculation to overflow under the enabled release overflow checks, panicking the program and aborting the create-mint transaction instead of returning a controlled ProgramError.

Knowledge Base Used: Tokens Directory Overview

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.

Good catch — fixed in c81a76e. decimals is unvalidated instruction input and the workspace sets overflow-checks = true, so 5 * 10^decimals would panic and abort the transaction for decimals >= 19. It now scales with checked_pow/checked_mul and returns a controlled ProgramError::InvalidInstructionData:

let max_fee = 10u64
    .checked_pow(args.decimals as u32)
    .and_then(|scale| scale.checked_mul(5))
    .ok_or(ProgramError::InvalidInstructionData)?;

Verified: 1 test passing (decimals 9 → max fee unchanged), plus tsc/biome/fmt/clippy and frozen-lockfile all clean.

decimals is unvalidated u8 instruction input and the workspace enables
overflow-checks, so 5 * 10^decimals would panic and abort the transaction
for large decimals (>= 19). Compute the max fee with checked_pow/checked_mul
and return a controlled ProgramError::InvalidInstructionData instead.
@MarkFeder
MarkFeder force-pushed the tokens-token-2022-transfer-fee-pinocchio branch from 4fe6423 to c81a76e Compare July 26, 2026 16:46
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