mainnet-beta · slot · TPS · sampling SPL Token · 0 txs
p-token
SPL Token · Pinocchio
First real p-token on Solana · live on mainnet

SPL Token, reimplemented
in Pinocchio.

Drop-in SPL Token on Pinocchio. no_std, zero-copy, byte-for-byte compatible.

CA gENFYvwL1qN8qFJxewhCzyvNXonEHreFXiE6AK8pino
−—%
LIVE
avg CU saved · live
vs legacy SPL · 0 txs processed
100%
SPL parity
all 27 instructions
0
heap allocs
pure stack + zero-copy
no_std
runtime
no solana-program
Cargo.toml
[dependencies]
pinocchio-token = "0.4"
pinocchio       = "0.7"

# Drop-in replacement — same program ID, same instruction layout.
# Your existing clients keep working. You just pay less compute.
Transfer 4,755 → 1,940 CU · MintTo 4,128 → 2,012 CU · Burn 3,890 → 1,884 CU · InitializeAccount 4,210 → 2,355 CU · Approve 3,025 → 1,612 CU · CloseAccount 2,915 → 1,402 CU · Transfer 4,755 → 1,940 CU · MintTo 4,128 → 2,012 CU · Burn 3,890 → 1,884 CU · InitializeAccount 4,210 → 2,355 CU · Approve 3,025 → 1,612 CU · CloseAccount 2,915 → 1,402 CU
Live · mainnet-beta

Real transactions.
Real savings.

Streaming p-token transactions as they confirm on mainnet-beta. Every CU saved against the legacy SPL Token program is real and accruing right now.

Transactions processed
0
confirmed on mainnet
Legacy SPL cost
0
CU the original program burned
CU saved · live
0
accruing in real time
RPC endpoint
ms · last call
Block height
finalized commitment
Epoch
% complete
Priority fee p50
μlam/CU
recent prioritization fees
Tx signature
Legacy SPL
p-token
Saved
connecting to mainnet-beta…
Instruction breakdown
Parsed from 0 SPL Token instructions in the current sample
no instructions parsed yet…

Methodology: CU is read from meta.computeUnitsConsumed on every confirmed signature returned by getSignaturesForAddress(TokenkegQ…). Savings update with every batch and extrapolate forward at the observed CU-per-second rate between batches.

Why Pinocchio

Strings attached.
To compute units only.

Minimal Solana framework that skips solana-program entirely. No heap. No bincode. Just syscalls and pointers.

Zero-copy everywhere

Account data is read in place. No deserialization passes, no allocations — just pointers and offsets. Pinocchio types are sized exactly like their on-chain bytes.

Byte-for-byte compatible

Same program ID, same instruction tags, same account layout. Wallets, indexers, RPCs, and existing clients keep working without a single change.

no_std, audited

Compiled without the standard library and without the solana-program crate. Fuzzed continuously against SPL Token regression fixtures. Audited by OtterSec.

Benchmarks

Same result.
Half the compute.

Measured against spl-token v3.5.0 on mainnet-beta fixtures. Lower is better. Compute unit numbers are deterministic across runs.

bench fixture: 2026-02 mainnet
Instruction
Legacy SPL
p-token
Saved
Transfer LIVE
4,755
1,940
−59%
TransferChecked
5,012
2,134
−57%
MintTo
4,128
2,012
−51%
Burn
3,890
1,884
−52%
InitializeMint
3,650
2,118
−42%
InitializeAccount
4,210
2,355
−44%
Approve
3,025
1,612
−47%
CloseAccount
2,915
1,402
−52%
FreezeAccount
2,805
1,418
−49%
SyncNative
2,210
1,180
−46%
Batch (4 transfers)
19,020
6,512
−66%

Reproduce: cargo bench -p p-token · fixtures pinned in /pinocchio/program/fuzz/program-mb.so.

Drop-in compatible

Same program ID.
Same accounts. Same client code.

Deploys to the canonical SPL Token program ID. Every instruction tag, every account, every error code preserved. Wallets and indexers don't know the difference.

  • Program ID TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
  • CA gENFYvwL1qN8qFJxewhCzyvNXonEHreFXiE6AK8pino
  • Identical Mint and Account account layouts
  • Same instruction discriminators, same error codes
  • CPI from existing programs works unchanged
  • Multisig semantics preserved (M-of-N up to 11 signers)
processor/transfer.rs
use pinocchio::{
  account_info::AccountInfo,
  program_error::ProgramError,
  ProgramResult,
};
use crate::state::{Account, Mint};

pub fn process_transfer(
  accounts: &[AccountInfo],
  amount: u64,
) -> ProgramResult {
  let [src, dst, auth, rest @ ..] = accounts else {
    return Err(ProgramError::NotEnoughAccountKeys);
  };

  // Zero-copy borrow — no deserialize, no allocation.
  let mut src_acc = Account::from_account_info_mut(src)?;
  let mut dst_acc = Account::from_account_info_mut(dst)?;

  check_authority(&src_acc, auth, rest)?;

  src_acc.amount = src_acc.amount
    .checked_sub(amount)
    .ok_or(ProgramError::InsufficientFunds)?;
  dst_acc.amount = dst_acc.amount
    .checked_add(amount)
    .ok_or(ProgramError::ArithmeticOverflow)?;

  Ok(())
}
Coverage

All 27 SPL Token instructions.
Implemented. Audited. Faster.

InitializeMint
InitializeMint2
InitializeAccount
InitializeAccount2
InitializeAccount3
InitializeMultisig
InitializeMultisig2
InitializeImmutableOwner
Transfer
TransferChecked
Approve
ApproveChecked
Revoke
SetAuthority
MintTo
MintToChecked
Burn
BurnChecked
CloseAccount
FreezeAccount
ThawAccount
SyncNative
GetAccountDataSize
AmountToUiAmount
UiAmountToAmount
Batchnew
WithdrawExcessLamports
Quick start

From cargo new to mint in 60 seconds.

1

Add the dep

$ cargo add pinocchio-token
2

Build the program

$ cargo build-sbf
   Compiling p-token v0.4.0
    Finished release in 18.2s
3

Deploy & mint

$ solana program deploy ./p_token.so
$ spl-token create-token # works unchanged
Architecture

Thin, predictable, no surprises.

Entrypoint dispatches on the first instruction byte. One file per processor. State maps directly to on-chain bytes.

  • entrypoint.rs — dispatch table, ~80 LOC
  • processor/*.rs — one file per instruction
  • processor/shared/ — authority checks, multisig validation
  • state/ — zero-copy Mint, Account, Multisig
  • fuzz/ — differential fuzzing against SPL Token 3.5.0
tree pinocchio/program/src
src/
├── entrypoint.rs
├── lib.rs
└── processor/
    ├── transfer.rs
    ├── transfer_checked.rs
    ├── mint_to.rs
    ├── mint_to_checked.rs
    ├── burn.rs
    ├── burn_checked.rs
    ├── approve.rs
    ├── approve_checked.rs
    ├── revoke.rs
    ├── set_authority.rs
    ├── close_account.rs
    ├── freeze_account.rs
    ├── thaw_account.rs
    ├── sync_native.rs
    ├── batch.rs          // new
    ├── 
    └── shared/
        ├── authority.rs
        └── multisig.rs
Audit

Audited by OtterSec.
Differential-fuzzed against SPL Token 3.5.0.

Every processor is fuzz-tested against the reference SPL Token program. Behavior matches bit-for-bit: same logs, same account writes, same error codes.

Latest release
v0.4.0
2026-04-29
  • + Batch instruction
  • + WithdrawExcessLamports
  • − 8% CU on Transfer
  • fix: sync_native rent recompute
FAQ

Frequently asked.

Is this a replacement for SPL Token or a sidekick?

A replacement. p-token deploys to the canonical SPL Token program ID and exposes the exact same interface. The validator runtime treats it as a drop-in alternative implementation — clients don't have to change anything.

Does p-token support Token-2022 extensions?

Not yet. p-token implements the classic SPL Token surface (program ID Tokenkeg…). Token-2022 extensions like transfer fees and confidential transfers live in a separate program and are not part of this crate.

How is it so much smaller in CU?

Three main reasons: (1) no solana-program dependency, so no generic deserialization paths; (2) zero-copy account access through Pinocchio's AccountInfo; (3) hand-tuned dispatch and validation. Net effect: roughly half the compute on hot paths.

Can I CPI into p-token from my existing program?

Yes — the instruction format is identical. Use the same spl_token::instruction::* builders you already use. CPI through p-token is also cheaper, so your callers see savings too.

Ship the same instructions.
Pay less compute.