p-token: how Solana rewrote its most-called program (4,645 CU → 76)
Anza rewrote SPL Token in Pinocchio — same program ID, same accounts, but a transfer dropped from 4,645 CU to 76. The rewrite, the rollout, and the gotchas.
SPL Token is the most-called program on Solana — token operations consumed roughly 10% of the compute units in every block, despite being conceptually trivial. The cost wasn't the logic; it was the overhead of the standard Solana program model: deserialization into owned Rust structs, reference-counted account wrappers, the solana-program dependency tree, even the per-instruction log line.
p-token (proposed as SIMD-0266) is the from-scratch reimplementation in Pinocchio. Byte-for-byte compatible instruction set and account layouts, deployed under the same canonical program ID — and a transfer that used to cost 4,645 CU now costs 76.
The real numbers (SIMD-0266 benchmarks)
Instruction p-token SPL Token Reduction
──────────────────────────────────────────────────────────
transfer 76 CU 4,645 CU ~98%
transfer_checked 105 CU 6,200 CU ~98%
initialize_mint 105 CU 2,967 CU ~96%
initialize_account 154 CU 4,527 CU ~97%
mint_to 119 CU 4,538 CU ~97%
burn 126 CU 4,753 CU ~97%
approve 124 CU 2,904 CU ~96%
close_account 120 CU 2,916 CU ~96%
freeze_account 146 CU 4,265 CU ~97%
sync_native 61 CU 3,045 CU ~98%These aren't "a few times cheaper" — they're 50-60x cheaper on the hot paths. For perspective: the old program's single Program log: Instruction: Transfer line alone cost ~103 CU — more than the entire p-token transfer (76 CU). The logging overhead exceeded the new program's full execution.
What stayed identical
- Program ID.
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA— the swap happened via the Upgradeable BPF Loader at an epoch boundary. Every existing mint, ATA, and CPI caller works unchanged. - Instruction discriminators. Transfer is still
3, MintTo still7. Existing serialized instructions parse the same way. - Account schemas. The 82-byte Mint and 165-byte Account layouts are byte-for-byte identical.
Why Pinocchio is this much cheaper
Pinocchio — created by Fernando "Febo" Otero at Anza — throws out the standard program model entirely:
- Zero-copy. Accounts are typed pointers into the runtime's input buffer, not heap-allocated structs. No deserialize-into-owned-struct, no re-serialize on write.
no_std, zero external crates. Nosolana-programdependency tree, no standard library. Less code compiled in, less code executed per call.- No smart pointers. The standard entrypoint wraps accounts in
Rc<RefCell<>>. Pinocchio eliminates that refcounting overhead. - Stack allocation. Fixed-size arrays instead of heap
Vecs. A macro can forbid heap allocation entirely at compile time.
The result also shrank the binary: 131 KB → 95 KB, a 27% reduction.
SIMD-0266 also added three new instructions
This is the part most "it's just a rewrite" summaries miss — p-token is not purely the old instruction set. SIMD-0266 introduced three:
batch(discriminator 255) — bundles multiple token instructions into one CPI invocation, amortising the per-entry cost. The biggest lever for programs that do many token ops per transaction.withdraw_excess_lamports(38) — recovers SOL accidentally sent to mint and multisig accounts.unwrap_lamports(45) — direct lamport withdrawal from native (wrapped SOL) token accounts.
The one thing that breaks: log-scraping indexers
The old program emitted a log line per instruction — Program log: Instruction: Transfer. p-token omits these (the log was ~103 CU of pure overhead). Any indexer that detected token transfers by string-matching logs silently stops seeing them.
// BROKEN after p-token — the log line no longer exists
const isTransfer = tx.meta.logMessages.some(
(l) => l.includes("Instruction: Transfer")
)
// CORRECT — decode the instruction data against the program's IDL
import { identifyTokenInstruction, TokenInstruction } from "@solana-program/token"
for (const ix of tx.transaction.message.instructions) {
if (ix.programId.equals(TOKEN_PROGRAM_ID)) {
const kind = identifyTokenInstruction(ix.data) // decode by discriminator
if (kind === TokenInstruction.Transfer) { /* … */ }
}
}If you run an indexer, a webhook pipeline, or analytics that depend on token-program logs, this is the migration to do. Decode instruction data by discriminator, never log-scrape.
How it was verified before going live
Replacing the most-called program on a $-billions chain is not a casual deploy. The verification stack:
- Neodyme differential testing. Replayed essentially every mainnet transaction that ever touched the token program through both the old and new programs against real chain history. Result: zero divergences across months of testing. Over one sample window (Aug 3-11, 2025) the new program would have saved 8.9-9.1 trillion CUs — about 12% of the entire chain's blockspace.
- Zellic audit. 8 findings — one Critical, three High — including out-of-bounds reads in memory helpers and unsafe pointer issues. All fixed.
- Asymmetric Research. Found a loss-of-funds bug in the new
batchinstruction involving deferred ownership checks and fake native-token-account manipulation before runtime validation. Fixed. - Anza's internal differential fuzzer + Firedancer fuzzing.
The batch bug is the cautionary tale: the one genuinely new instruction was where the one loss-of-funds bug lived. The byte-compatible rewrite of existing instructions was provably safe; the net-new surface needed the most scrutiny.
The rollout
1. Verified bytecode staged at:
ptok6rngomXrDbWf5v5Mkmu5CEbB51hzSCPDoj9DrvF
2. Released behind feature gate:
ptokFjwyJtrwCa9Kgo9xoDS59V4QccBGEaRFnRPnSdP
3. Passed a supermajority validator stake vote (early 2026)
4. Activated at an epoch boundary — epoch 971, spring 2026
5. Required client versions: Agave v3.1.7+ / Firedancer v0.812.30108+Built by Febo and Jon Cinque at Anza. The swap went through the Upgradeable BPF Loader at the epoch boundary — no frozen upgrade authority required, because the feature gate gated activation on validator consensus rather than a unilateral upgrade.
What to actually do
App developers: nothing — your calls to the Token program work unchanged and just got ~98% cheaper.
High-throughput protocols: re-measure your CU usage and lower your setComputeUnitLimit. Solana charges priority fees on allocated CUs, not used CUs — if your budgets were sized against the old 4,645-CU transfer, you're now massively over-allocating and overpaying. A swap that touched the Token program 6-10× just reclaimed ~25-45k CUs.
Indexers / analytics: migrate off log-scraping to instruction-data decoding (see above). This is the one non-optional change.
New program authors: p-token is the best production reference for a real Pinocchio program under a load-bearing program ID. Read p-token/src/processor/ for the zero-allocation instruction handlers.
What it doesn't cover
- Token-2022 is separate. p-token is the rewrite of the legacy Token program. Token-2022 (
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) with its extensions is a different program; a Pinocchio rewrite of it is a separate effort. - Account semantics are unchanged. Authority checks, freeze rules, delegation — all behave identically. Only the implementation and (slightly) the instruction surface changed.
References
- SIMD-0266: Efficient Token Program — the proposal + benchmarks
- solana-program/token — p-token source
- anza-xyz/pinocchio — the framework (Febo / Anza)
- OrbitFlare — p-token deep dive (rollout + audit detail)
- Anchor vs Pinocchio vs Steel
Same program ID, same accounts, ~98% cheaper, plus a batch instruction and one log-scraping gotcha. The most consequential Solana program change of the year, and most apps didn't have to lift a finger for it.