COption vs Option vs OptionalNonZeroPubkey
Solana has three different ways to encode an optional value, and they're not interchangeable. SPL's COption is 4 bytes of tag, Borsh's Option is 1, and Token-2022's OptionalNonZeroPubkey is 0 — the single most common source of off-by-N decoder bugs.
What it is
“This field is optional” is encoded three different ways on Solana depending on which serialization the program uses. Confusing them is the most common reason a hand-written decoder drifts off by a few bytes and reads garbage.
Why it exists
Different layers adopted different conventions: SPL Token uses its own Pack format with a fixed-width tag for alignment, Borsh (Anchor, most app programs) uses a compact 1-byte tag, and Token-2022 extensions optimize pubkey-options to zero overhead. They all coexist, sometimes in the same account.
Byte layout
| Encoding | Where | None | Some(value) | Total for Option<Pubkey> |
|---|---|---|---|---|
COption<T> (SPL Pack) |
SPL Token base layout | 4-byte tag 00 00 00 00 + zeroed value space |
4-byte tag 01 00 00 00 + value |
36 bytes (4 + 32) |
Option<T> (Borsh) |
Anchor, Metaplex, app programs | 1-byte tag 00 |
1-byte tag 01 + value |
33 bytes (1 + 32) |
OptionalNonZeroPubkey |
Token-2022 extensions | 32 all-zero bytes | the 32-byte pubkey | 32 bytes (no tag) |
The key differences:
- COption reserves space for the value even when None. SPL Token’s
Mint.freeze_authorityis always 36 bytes on disk whether set or not — the layout is fixed-size. - Borsh Option omits the value when None. A None is a single
00byte; the value bytes aren’t present at all. This makes Borsh structs variable-length. - OptionalNonZeroPubkey has no tag at all. All-zero pubkey is the None sentinel. Zero overhead, but you can’t represent “Some(all-zero pubkey)” — fine for pubkeys, which are never all-zero in practice.
Where you see it
- COption: Mint and Token Account authorities.
- Borsh Option: Metaplex metadata fields, Anchor account fields.
- OptionalNonZeroPubkey: nearly every Token-2022 extension authority.
Common gotchas
- A Token-2022 mint can contain all three. The base layout uses COption (4-byte tags), an embedded Borsh field could use Option (1-byte), and the extensions use OptionalNonZeroPubkey (0 tag). Decoding one section with the wrong convention slides everything after it.
- COption is little-endian 4 bytes, value 0 or 1. Don’t read it as a single byte — bytes 1–3 being zero is what makes a naive 1-byte read accidentally work for None and then break for Some.
- Borsh None changes the struct’s size. You can’t compute a Borsh struct’s byte size from its type alone if it has Options — the size depends on the values.
- All-zero pubkey means None for OptionalNonZeroPubkey. Never treat the system program (also all-1s in base58, but all-zero in bytes is the default Pubkey) — the all-zero 32 bytes is the None marker, not a real address.
Last verified: 2026-05-20