ZK Compression: cheaper accounts on Solana (and why it's not cNFTs)
Light Protocol's ZK Compression stores account state as hashes with validity proofs, cutting token/PDA costs ~100×. Here's how it works, how it differs from state-compressed NFTs, and the CU cost.
ZK Compression (by Light Protocol, with indexer infra from Helius) stores account and token state as hashes in a Merkle tree, keeping the full data in the ledger rather than in rent-funded account space. A zero-knowledge validity proof proves the state is correct — so you get ~100× lower storage cost while keeping L1 security and composability.
First, the distinction everyone gets wrong
It is not the same as the state compression behind cNFTs:
- State compression (cNFTs) — concurrent Merkle trees, essentially NFT-only, data off-chain, no general on-chain read/write of arbitrary state.
- ZK Compression — the generalized successor: arbitrary account data (tokens, PDAs, program state) behaves like native accounts, with a ZK validity proof verified on-chain.
How it works
- Only a hash (commitment) of each account lives on-chain, inside a state Merkle tree. The full data is emitted as calldata in the transaction.
- A constant 128-byte Groth16 proof proves the touched accounts exist in the tree — regardless of how many accounts the tx reads/writes. Verified on-chain (~100k CU).
- The Photon indexer (Helius) indexes Light programs so clients can read compressed state; a prover generates the proofs; forester nodes maintain the trees.
The cost win, concretely
ZK-compressed regular SPL/account ~saving
token account 0.000017 SOL ~0.0029 SOL ~100×
token mint 0.000091 SOL ~0.0015 SOL ~200×
100-byte PDA ~0.000015 SOL ~0.0016 SOL ~100×Reading it client-side uses the ZK Compression RPC (a superset of standard Solana RPC):
import { createRpc } from "@lightprotocol/stateless.js"
const rpc = createRpc(HELIUS_RPC_URL) // Photon-enabled endpoint
// compressed-state read methods:
await rpc.getCompressedAccount(hashOrAddress)
await rpc.getCompressedTokenAccountsByOwner(owner)
await rpc.getCompressedBalanceByOwner(owner)
await rpc.getValidityProof([accountHash]) // the 128-byte proof for a txThe honest read
ZK Compression trades rent for compute. A compressed token transfer runs ≈292k CU (proof verification + system hashing + per- account costs), and 128 bytes of every transaction are reserved for the proof against the 1,232-byte limit. You also depend on a Photon indexer and a prover (added latency). It shines for many cheap, rarely-written accounts — airdrops to millions of wallets, long-tail token accounts, large PDA sets. Avoid it for hot accounts written every block, data >~1kB accessed frequently, or anything where the extra CU and RPC dependency outweigh the rent saved.
Live on mainnet-beta since September 2024; Photon endpoints are available from Helius, Triton, and Alchemy.
References
- zkcompression.com — docs & core concepts
- Lightprotocol/light-protocol (GitHub)
- State compression & cNFTs — the NFT-only predecessor
cNFTs proved Solana could put millions of assets on-chain for pennies. ZK Compression is the same bet, generalised to everything that isn't an NFT.