TLDR:Documentation Index
Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt
Use this file to discover all available pages before exploring further.
- p-token is a compute-optimized, drop-in rewrite of the SPL Token program. It runs at the same program ID, so your existing code and Chainstack endpoints already use it — a transfer now costs ~76 compute units instead of ~4,645.
- It adds a
batchinstruction that bundles several token operations into one token-program call. - Batching only pays off through cross-program invocation (CPI): it amortizes the fixed per-CPI base cost across all the bundled operations.
- At the top level, batching is pure overhead — instructions there are already cheap, so send them separately.
What p-token changes
p-token is a Pinocchio-based reimplementation of the SPL Token program from Anza, shipped through SIMD-0266. It went live on Solana mainnet at epoch 971 and is fully backwards compatible. p-token activates as a feature-gate swap at the same program ID (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA). Account layouts, instruction discriminators, and account structures are byte-for-byte identical to the old program. You do not change a single address, and transactions you send through your Chainstack node already run the optimized program.
What you get is a large drop in compute units. The figures below are from the SIMD-0266 governance proposal:
| Instruction | SPL Token (CU) | p-token (CU) |
|---|---|---|
| Transfer | 4,645 | 76 |
| TransferChecked | 6,200 | 105 |
| MintTo | 4,538 | 119 |
| Burn | 4,753 | 126 |
| InitializeAccount | 4,527 | 154 |
| CloseAccount | 2,916 | 120 |
| SyncNative | 3,045 | 61 |
batch, withdraw_excess_lamports, and unwrap_lamports. This page focuses on batch.
The batch instruction
batch (discriminator 255) lets you pack several token-program instructions into a single instruction. The wire format is a flat sequence: the discriminator, then one entry per sub-instruction, repeating until the data buffer is exhausted. There is no count prefix — the program loops until it runs out of bytes.
Each sub-instruction entry is a 2-byte header followed by its data:
numberOfAccounts— how many accounts this sub-instruction consumes from the flat account list.dataLength— the byte length of the instruction data that follows.data— the raw token instruction bytes, including that instruction’s own discriminator.
numberOfAccounts accounts, sub-instruction 2 takes the next batch, and so on.
What you can batch
Almost every token-program instruction can go into a batch —transfer and transferChecked, mintTo and burn, approve and revoke, freeze and thaw, the initializeMint and initializeAccount variants, closeAccount, syncNative, and withdrawExcessLamports. They execute sequentially, in the order you add them.
Two things you cannot batch:
- A batch itself — the
batchinstruction is not batchable, so no nesting. - Associated token account (ATA) creation — the Associated Token Account program is a separate program that in turn calls the token program, so its instructions cannot be folded into a token-program batch. If you want account creation inside the same batch, create a plain token account (the System program’s create-account plus
initializeAccount) rather than an ATA.
- The 255-account-reference limit — a single instruction can reference at most 255 accounts, and a batch counts accounts across all its sub-instructions (they are not deduplicated). Instructions that touch few accounts let you pack the most in.
- The ~1,232-byte transaction size — each sub-instruction’s header, data, and account references add up.
revoke touches two accounts) are bound by the account cap, so well over a hundred fit; a batch of plain transfers hits the transaction-size limit first, at around 70 per batch.
When batching pays off
The rule is simple:batch is only worth it when you call the token program through CPI.
Every invoke call on Solana carries a fixed base cost of about 1,000 compute units, regardless of the instruction inside it. When a program transfers tokens twice, it pays that base cost twice — once per CPI. batch collapses those into a single CPI, so you pay the 1,000 CU base cost once and then only the (now tiny) cost of each token operation:
- Two separate token CPIs —
2 × 1,000base CU+ 2 ×operation cost. - One batch CPI —
1 × 1,000base CU+ 2 ×operation cost.
batch CPI eliminates the redundant per-CPI base costs. The effect compounds: bundling about a dozen token operations into a single CPI rather than a dozen separate CPIs has been measured to cut total compute roughly threefold — from around 18,000 CU to about 6,000. A batch’s single CPI is not free, and its cost rises with the number of accounts it carries, but it is far cheaper than paying the base cost on every invocation.
At the top level of a transaction there is no CPI, so there is nothing to amortize. A batch of top-level instructions just adds the wrapper overhead and runs slower than sending the instructions separately. Use batch from inside a program, not from your client, unless you have a specific reason to.
In practice the gains are real but modest, so treat batch as a compute optimization, not a necessity — the big efficiency win on token operations came from p-token itself, which you already get for free. Reach for batch when your program issues two or more token instructions back-to-back through CPI with nothing else in between. Because they run sequentially in order, you cannot interleave other logic between batched operations.
Batch via CPI in Rust
For new programs, thepinocchio-token crate gives you an idiomatic Batch builder and an IntoBatch trait implemented for every instruction type.
Cargo.toml
Rust
spl-token crate can still use batch by constructing the instruction data in the wire format above and issuing a normal invoke to the token program ID — it works because p-token runs at that same address. For new code, pinocchio-token is the cleaner path.
Build a batch instruction in TypeScript
If you do need to construct abatch from a client, the @solana-program/token client exposes getBatchInstruction. This is a low-level API — you supply each sub-instruction’s raw data and account count, and you attach the flat account list yourself.
TypeScript
instructionData. Remember that the account count per entry must match the accounts you append, in order.
Caveats and gotchas
- No instruction-name logs — p-token does not emit the
Program log: Instruction: Transferlines the old program logged, because logging cost ~103 CU and was a large share of each instruction’s total. If you parse logsSubscribe output or Geyser streams, parse instruction data instead of instruction-name logs. - Indexers must look inside batches — a
batchis a single top-level instruction (discriminator255) that hides its real operations as sub-instructions. To catch every transfer or mint, descend into discriminator-255instructions and walk their sub-instructions; scanning only top-level instructions will miss anything bundled in a batch. - 255-byte data cap per sub-instruction — the
dataLengthheader is a single byte, so each bundled instruction’s data must be 255 bytes or less. Transfers (9 bytes) are far under this, but parameter-heavy instructions can approach it. - Account ordering is positional — accounts are consumed in sub-instruction order from one flat list. A misordered list is a silent bug, not an error.
- Token-2022 has no equivalent —
batchexists in p-token only. Token-2022 (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) is a separate program with no batch mechanism.
The other new instructions
withdraw_excess_lamports— recovers SOL mistakenly sent to a mint or multisig account above its rent-exemption minimum, which the old program left stranded.unwrap_lamports— moves lamports directly out of a wrapped-SOL token account to a destination, without the create-and-close dance.
Get your own node endpoint today
Start for free and get your app to production levels immediately. No credit card required.You can sign up with your GitHub, X, Google, or Microsoft account.Additional resources
- Transferring SPL tokens on Solana — the single-transfer baseline batching builds on.
- Compute budget — set compute unit limits and prices for your transactions.
- Listening to programs using Geyser and Yellowstone gRPC — where the no-logs change matters most.
- SIMD-0266: efficient token program — the proposal behind p-token.
- solana-program/token — p-token source and the generated clients.