On 28 October the Token-2022 repository released v12. It rewrites the program on Pinocchio, roughly halving the compute cost of a transfer, and publishes seven crates with breaking version bumps. The program every Solana wallet and exchange calls did not change: all three clusters still run v11.1.0, byte for byte.
So for now, “v12” means Rust crates. We diffed the v11.1.0 and v12.0.0 tags, read the deployed program on mainnet, devnet and testnet, and resolved both versions with Cargo. What follows is what breaks for Rust code, what does not, and how to tell when the new program actually ships.
Published is not deployed
The deployed Token-2022 binary carries a security.txt that names the release it was built from. Read it from the program-data account and hash the same bytes, and you know exactly what a cluster runs:
Mainnet took v11.1.0 on 23 October at 15:39 UTC, a month after devnet and testnet. The v11.1.0 release notes carry a deployment section, a verifiable-build hash and an audit link. The v12.0.0 notes list changes only. Until a v12 build is verified and deployed, every Token-2022 instruction on chain runs the v11.1.0 code, whatever crate version your project depends on. To check for yourself:
import base64, hashlib, json, struct, urllib.request
RPC = "https://api.mainnet-beta.solana.com"
PROGRAMDATA = "DoU57AYuPFu2QU514RktNPG22QhApEjnKxnBcu4BHDTY" # Token-2022's program-data account
def rpc(method, params):
req = urllib.request.Request(RPC, json.dumps({"jsonrpc": "2.0", "id": 1, "method": method,
"params": params}).encode(), {"content-type": "application/json"})
return json.load(urllib.request.urlopen(req))["result"]
data = base64.b64decode(rpc("getAccountInfo", [PROGRAMDATA, {"encoding": "base64"}])["value"]["data"][0])
slot = struct.unpack_from("<Q", data, 4)[0] # last deploy slot
elf = data[45:].rstrip(b"\x00") # 45-byte header, zero padding stripped
tag = elf.find(b"source_release\x00") + 15
print("deployed at slot", slot) # 460497271
print("source_release ", elf[tag:elf.index(b"\x00", tag)].decode()) # program@v11.1.0
print("sha256 ", hashlib.sha256(elf).hexdigest()) # 5e888b29…6413d9The Pinocchio rewrite, and the savings still waiting
Pull request #1469 replaces the SDK entrypoint with Pinocchio’s and switches the processor from AccountInfo to Pinocchio’s AccountView, the zero-copy view of the input buffer. It is the same move that made p-token cheap for the original token program. The pull request reports these instruction counts from its own tests:
These are the pull request’s measurements from its instruction-count tests, not ours, and extensions add their own cost on top. They become real only when a v12 binary is deployed. Until then, a transfer of any Token-2022 asset on mainnet costs what v11.1.0 charges.
What breaks in Rust
Most Rust code depends on spl-token-2022-interface, the crate with instruction builders and account layouts. Its builders and state types did not change. The breaks are in four places.
1. The processor signature
In the spl-token-2022 program crate, Processor::process changed from &[AccountInfo] to &mut [AccountView]. Test suites that run Token-2022 as a native processor, with processor!(spl_token_2022::processor::Processor::process), stop compiling on 12.0.0. Load the program’s binary into the test instead, or keep the test on 11.
2. The zk-ops feature is gone
#1524 removed zk-ops, a default feature its own manifest said to remove “once the underlying syscalls are released on all networks”. It gated the confidential-transfer and confidential mint-and-burn processors, which are now always compiled in. A manifest that still names the feature does not build. We asked Cargo to resolve both versions with it:
# spl-token-2022 = { version = "=11.1.0", default-features = false, features = ["no-entrypoint", "zk-ops"] }
# resolves
# spl-token-2022 = { version = "=12.0.0", default-features = false, features = ["no-entrypoint", "zk-ops"] }
error: failed to select a version for `spl-token-2022`.
package `featcheck` depends on `spl-token-2022` with feature `zk-ops` but `spl-token-2022` does not have that feature.
help: available features: no-entrypoint, test-sbf3. Two interface helpers take AccountView
In spl-token-2022-interface 4.0, alloc_and_serialize and alloc_and_serialize_variable_len_extension now take &mut AccountView. They grow an account and write an extension into its TLV data; Token-2022 itself uses them for its token-metadata and token-group extensions, and a program that stores TLV data the same way can call them too. Callers holding an AccountInfo need to move to Pinocchio’s types or stay on interface 3.
4. The zero-knowledge types moved a major version
The interface re-exports its proof types from solana-zk-elgamal-proof-interface, now 1.0.0 instead of 0.1. Its confidential builders take those types, for example ProofLocation<CiphertextCommitmentEqualityProofData>. Proof data built against the old crate will not type-check. The confidential stack moves together: interface 4.0, proof-generation and proof-extraction 0.7, and solana-zk-sdk 8.
Confidential transfers: new call, same keys
Changing how confidential keys are derived would be the dangerous kind of break: a wallet that derives different keys can no longer decrypt balances it already holds. zk-sdk 8 changed the derivation API, but not the keys. In 7.0.1, derive_confidential_keys(signer, seed) signs "solana-conf-bal/v1" ‖ seed. In 8.0.1, STANDARD_DERIVATION_MESSAGE is defined as that same "solana-conf-bal/v1", so the seedless call signs the same bytes the old empty-seed call did.
// zk-sdk 7 (Token-2022 v11 stack)
let (elgamal, ae) = derive_confidential_keys(&signer, b"")?;
// zk-sdk 8 (v12 stack): the seed parameter is gone; seeded keys use
// derive_confidential_keys_with_seed(&signer, seed)
let (elgamal, ae) = derive_confidential_keys(&signer)?;
// zk-sdk 8 takes a solana-signer v4 Signer. Signers from the Agave 4.3 stack
// are v3, so sign the standard message yourself (same keys):
let signature = signer.try_sign_message(STANDARD_DERIVATION_MESSAGE)?;
let (elgamal, ae) = derive_confidential_keys_from_signature(&signature)?;The last pattern is the one Token-2022’s own CLI and client tests now use, per #1584. zk-sdk 8.0.1 depends on solana-signer 4.0.0, while 7.0.1 used 3.0.0, and a v3 keypair does not satisfy the v4 trait.
Two smaller fixes ride along. The interface documentation listed the multisig accounts for confidential mint and burn wrongly, naming the mint twice and dropping the token account. The builders and the IDL that generates the JavaScript client were already right, so only hand-written integrations that followed the comments are affected (#1509). And spl-token-client 0.21.0 no longer builds proof-record transactions that exceed the packet limit when a memo, nonce or compute-budget instruction is added (#1550).
An upgrade order that works
Every crate in the release took a breaking version bump, so nothing moves until you change a version requirement: Cargo will not pick 12.0.0 to satisfy "11". When you do move:
None of this changes what happens on chain. The mint and account layouts did not change, and the instruction builders encode every instruction exactly as interface 3 did.
What this does not show
We did not build or run v12, so the compute figures are the pull request’s. Our Cargo check covered dependency resolution, not a compile of downstream code. Where we say a signature or type changed, we read it from the tagged source. The deployment facts come from each cluster’s program-data account, and a later upgrade would show up there first.
Token-2022 v12: Questions People Actually Ask
Is Token-2022 v12 live on Solana mainnet?
No. Mainnet, devnet and testnet all run the same v11.1.0 binary: its embedded security.txt says source_release program@v11.1.0 and its hash matches the v11.1.0 verified build, 5e888b29…. Mainnet was upgraded to it on 23 October 2026. v12.0.0 was published as Rust crates on 28 October, and its release notes have no deployment or verification section.
Will upgrading the spl-token-2022 crate to 12.0.0 break my build?
Only if you use the program crate itself. Processor::process now takes &mut [AccountView] instead of &[AccountInfo], so registering Token-2022 as a native processor in tests no longer compiles, and the zk-ops feature no longer exists, so a Cargo.toml that names it fails to resolve. Instruction builders, state parsing and the onchain CPI helpers are unchanged.
Does solana-zk-sdk 8 change my confidential transfer keys?
No. zk-sdk 8’s derive_confidential_keys(signer) signs STANDARD_DERIVATION_MESSAGE, which is the constant "solana-conf-bal/v1", and zk-sdk 7’s derive_confidential_keys(signer, b"") signed the same bytes. The keys are identical; only the function signature changed, and seeded derivation moved to derive_confidential_keys_with_seed.
How much cheaper is Token-2022 v12?
According to the instruction-count tests in the pull request that moved the program to Pinocchio, transfer falls from 1,444 to 719 compute units and transfer_checked from 1,782 to 856, roughly half. None of that applies on chain until a v12 binary is deployed.
How do I check which Token-2022 version a cluster runs?
Read the program-data account DoU57AYuPFu2QU514RktNPG22QhApEjnKxnBcu4BHDTY, skip its 45-byte header and trailing zero bytes, and read the source_release field of the embedded security.txt. Hashing the same bytes with SHA-256 gives the value solana-verify reports for a verified build.
The one-line version
v12 is a Pinocchio rewrite that halves transfer costs, but for now it exists only as crates: it breaks native test processors, the zk-ops feature and two TLV helpers, keeps confidential keys identical, and changes nothing on chain until a verified v12 binary replaces the v11.1.0 that every cluster runs.
For the extensions themselves, see pausable and permissioned burn and how confidential transfers compare.
Sources: the v12.0.0 and v11.1.0 releases and the tagged source (1bc00fa5, 8867f751); pull requests #1469, #1524, #1584, #1509 and #1550; the solana-zk-sdk 7.0.1 and 8.0.1 crate sources; and the Token-2022 program-data account on mainnet, devnet and testnet.