encoding

package
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package encoding implements Solana's binary wire formats.

Three formats live here:

  • bincode: Rust bincode-crate output. Vec / String use u64 LE length prefixes. Used by Solana built-ins (System nonce, Vote, Stake).
  • Borsh: Vec / String use u32 LE length prefixes. Used by Anchor programs, SPL Token-2022 extension state, Token Metadata, Address Lookup Table state, and most modern third-party programs.
  • shortvec: compact-u16 length prefix. Used inside transaction and message bodies (signature count, account-keys count, etc.).

Three layers of API, by use case:

  • One-shot reflection: BinDecodeTo / BorshDecodeTo. Pick by program; see those godocs for the full picker. Cheapest to write, slowest to run. The two formats only differ in slice / string length prefix width — structs without slice or string fields decode the same with either function.
  • Hand-written hot-path decoding: NewReader + r.U64() / r.Bytes32() / r.Shortvec(). Canonical path for performance-sensitive code and for wire layouts that reflection can't express (TLV, COption<Pubkey>, OptionalNonZeroPubkey, custom enum tags).
  • Hand-written instruction-data builders: NewEncoder + chained New().U8(tag).U64(amount).Bytes(). The convention every program in programs/* follows.

The Decoder type underpins the reflective entry points and the Reader API; it never copies the input buffer.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidShortvec = errors.New("solana/encoding: invalid shortvec")

ErrInvalidShortvec is returned when a compact-u16 encoding is structurally invalid: either the third byte sets the continuation bit (attempting a 4-byte encoding) or its value would overflow the destination uint16. The canonical Solana shortvec is 1-3 bytes and the third byte must be in the range 0x00-0x03.

View Source
var ErrShortBuffer = errors.New("solana/encoding: short buffer")

ErrShortBuffer is returned when a Decoder runs out of bytes mid-value or when a caller asks for a negative read length.

Functions

func BinDecodeTo added in v0.1.3

func BinDecodeTo(data []byte, v any) error

BinDecodeTo decodes data into v using Rust bincode wire conventions.

What's different from Borsh (BorshDecodeTo) — exactly two field shapes:

  • []T / Vec<T> length: 8-byte u64 LE prefix (Borsh: 4-byte u32)
  • string / String length: 8-byte u64 LE prefix (Borsh: 4-byte u32)

Everything else (fixed arrays, primitives, *T / Option<T>, struct concatenation) is identical, so structs with no slice or string fields decode the same either way.

Pick BinDecodeTo for accounts produced by Solana built-ins serialised with the Rust bincode crate:

  • System Program nonce accounts
  • Vote Program state
  • Stake Program state

Most modern Solana programs use Borsh; reach for BorshDecodeTo by default and only fall back to BinDecodeTo for the built-ins listed above.

For fixed-shape, performance-sensitive decoders prefer the Reader API.

func BorshDecodeTo

func BorshDecodeTo(data []byte, v any) error

BorshDecodeTo decodes data into v using Borsh wire conventions.

What's different from bincode (BinDecodeTo) — exactly two field shapes:

  • []T / Vec<T> length: 4-byte u32 LE prefix (bincode: 8-byte u64)
  • string / String length: 4-byte u32 LE prefix (bincode: 8-byte u64)

Everything else (fixed [N]byte arrays, all primitives, *T / Option<T> as 1-byte tag + optional payload, structs as concatenated fields) is byte- for-byte identical between the two — so structs with no slice or string fields decode the same either way.

Pick BorshDecodeTo for accounts produced by:

  • Anchor programs (#[derive(BorshSerialize, BorshDeserialize)])
  • SPL Token-2022 extension state
  • Token Metadata
  • Address Lookup Table state
  • most modern third-party programs

If unsure, check the Rust source: BorshSerialize/BorshDeserialize derives mean Borsh; the bincode crate or solana_program::serialization means BinDecodeTo.

func DecodeShortvec

func DecodeShortvec(b []byte) (uint16, int, error)

DecodeShortvec reads a compact-u16 from b and returns the decoded value, the number of bytes consumed, and an error. The error is ErrShortBuffer when b is truncated mid-value, and ErrInvalidShortvec when the encoding is structurally invalid (fourth-byte continuation or uint16 overflow).

func EncodeShortvec

func EncodeShortvec(b []byte, v uint16) int

EncodeShortvec writes v into b using Solana's compact-u16 (shortvec) encoding and returns the number of bytes written. b must have length at least 3. The encoding is 1-3 bytes: each byte holds 7 data bits in the low bits and a continuation flag in the high bit.

Types

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder is a zero-copy reader for Solana's little-endian wire format. It holds a cursor into a caller-owned byte slice; ReadBytes returns slices aliasing that buffer.

func NewDecoder

func NewDecoder(b []byte) *Decoder

NewDecoder returns a Decoder that reads from b. The caller retains ownership of b and must not mutate it for the lifetime of the Decoder or of any slice returned by ReadBytes.

func (*Decoder) DecodeFast

func (d *Decoder) DecodeFast(v any) error

DecodeFast is Decode using a cached, compiled per-type plan.

func (*Decoder) DecodeTo

func (d *Decoder) DecodeTo(v any) error

DecodeTo decodes the next value from the stream into v, which must be a non-nil pointer. It is the entry point for reflection-based decoding.

If the target type (or *T) implements Unmarshaler, the plan-cache emits a single dispatch op and reflection never descends into the type's fields.

Otherwise the supported kinds are: uint8/16/32/64, int8/16/32/64, bool, [N]byte and [N]T fixed arrays, []byte and []T slices (length-prefixed), string (length-prefixed), pointer (bincode Option: 1-byte tag + optional payload), and struct (recursively, all exported fields).

The length prefix is bincode's u64 by default; call UseBorsh on the decoder to switch to Borsh's u32. Bespoke widths (shortvec, u8, …) are not exposed via tags — hand-write the instruction with Reader / Encoder.

func (*Decoder) PeekUint8

func (d *Decoder) PeekUint8() (uint8, error)

PeekUint8 returns the next byte without advancing the cursor.

func (*Decoder) Pos

func (d *Decoder) Pos() int

Pos returns the number of bytes consumed so far.

func (*Decoder) ReadBytes

func (d *Decoder) ReadBytes(n int) ([]byte, error)

ReadBytes returns a zero-copy subslice of n bytes aliasing the underlying buffer. The returned slice is valid only as long as the buffer passed to NewDecoder is valid, and its capacity is clamped to n so that a rogue append cannot overwrite neighbouring bytes.

func (*Decoder) ReadInt8

func (d *Decoder) ReadInt8() (int8, error)

ReadInt8 reads a signed int8.

func (*Decoder) ReadInt16

func (d *Decoder) ReadInt16() (int16, error)

ReadInt16 reads a little-endian int16.

func (*Decoder) ReadInt32

func (d *Decoder) ReadInt32() (int32, error)

ReadInt32 reads a little-endian int32.

func (*Decoder) ReadInt64

func (d *Decoder) ReadInt64() (int64, error)

ReadInt64 reads a little-endian int64.

func (*Decoder) ReadShortvec

func (d *Decoder) ReadShortvec() (uint16, error)

ReadShortvec decodes a Solana compact-u16 length prefix and advances the cursor by the number of bytes consumed. On error the cursor is left unchanged.

func (*Decoder) ReadU128

func (d *Decoder) ReadU128() (U128, error)

ReadU128 reads a 16-byte little-endian U128. The bytes are copied, so the returned value is independent of the Decoder's underlying buffer.

func (*Decoder) ReadU256

func (d *Decoder) ReadU256() (U256, error)

ReadU256 reads a 32-byte little-endian U256. See ReadU128 for ownership.

func (*Decoder) ReadUint8

func (d *Decoder) ReadUint8() (uint8, error)

ReadUint8 reads a single byte.

func (*Decoder) ReadUint16

func (d *Decoder) ReadUint16() (uint16, error)

ReadUint16 reads a little-endian uint16.

func (*Decoder) ReadUint32

func (d *Decoder) ReadUint32() (uint32, error)

ReadUint32 reads a little-endian uint32.

func (*Decoder) ReadUint64

func (d *Decoder) ReadUint64() (uint64, error)

ReadUint64 reads a little-endian uint64.

func (*Decoder) Remaining

func (d *Decoder) Remaining() int

Remaining returns the number of bytes left to read.

func (*Decoder) UseBorsh

func (d *Decoder) UseBorsh() *Decoder

UseBorsh switches the Decoder's reflective length prefix from bincode u64 to Borsh u32 for slice and string fields. Returns the receiver for chaining.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder is an append-based writer for Solana's little-endian wire format. It holds a growable byte buffer and never reflects on its arguments.

func New

func New() *Encoder

New returns an Encoder with a 128-byte default capacity — large enough that essentially every Solana instruction encodes without a single buffer re-allocation. Use this when you don't care to compute the exact instruction-data size yourself; reach for NewEncoder(N) only when N is much larger than 128 (e.g. a variable-length address list).

func NewEncoder

func NewEncoder(capacity int) *Encoder

NewEncoder returns an Encoder whose internal buffer starts with the given initial capacity. Passing 0 is legal — the buffer grows on first write — but providing an estimate avoids the small re-alloc cost.

func (*Encoder) Bool

func (e *Encoder) Bool(v bool) *Encoder

Bool writes 1 byte: 1 for true, 0 for false (Rust bincode bool encoding).

func (*Encoder) Bytes

func (e *Encoder) Bytes() []byte

Bytes returns the accumulated bytes. The returned slice aliases the internal buffer and is valid only until the next write.

func (*Encoder) I8

func (e *Encoder) I8(v int8) *Encoder

I8 appends a signed int8 and returns e for chaining.

func (*Encoder) I16

func (e *Encoder) I16(v int16) *Encoder

I16 appends a little-endian int16 and returns e for chaining.

func (*Encoder) I32

func (e *Encoder) I32(v int32) *Encoder

I32 appends a little-endian int32 and returns e for chaining.

func (*Encoder) I64

func (e *Encoder) I64(v int64) *Encoder

I64 appends a little-endian int64 and returns e for chaining.

func (*Encoder) Len

func (e *Encoder) Len() int

Len returns the number of bytes written so far.

func (*Encoder) OptBool

func (e *Encoder) OptBool(v *bool) *Encoder

OptBool writes Option<bool>.

func (*Encoder) OptI64

func (e *Encoder) OptI64(v *int64) *Encoder

OptI64 writes Option<i64>.

func (*Encoder) OptRaw

func (e *Encoder) OptRaw(b []byte) *Encoder

OptRaw writes a Rust Option<[N]byte>: 1-byte tag, then b verbatim when non-nil. Pass nil to encode None. The slice length is not length-prefixed: this is intended for fixed-size optional fields such as Option<Pubkey> via OptRaw(pk[:]).

func (*Encoder) OptU8

func (e *Encoder) OptU8(v *uint8) *Encoder

OptU8 writes Option<u8>.

func (*Encoder) OptU32

func (e *Encoder) OptU32(v *uint32) *Encoder

OptU32 writes Option<u32>.

func (*Encoder) OptU64

func (e *Encoder) OptU64(v *uint64) *Encoder

OptU64 writes Option<u64>.

func (*Encoder) Raw

func (e *Encoder) Raw(b []byte) *Encoder

Raw appends b verbatim (no length prefix). Use for fixed-size byte arrays such as pubkeys (caller passes pk[:]) or pre-encoded discriminators.

func (*Encoder) StrU64 added in v0.1.3

func (e *Encoder) StrU64(s string) *Encoder

StrU64 writes a bincode string: u64 little-endian length, then UTF-8 bytes. For Borsh strings (u32 length) compose e.U32(uint32(len(s))).Raw([]byte(s)).

func (*Encoder) U8

func (e *Encoder) U8(v uint8) *Encoder

U8 appends a single byte and returns e for chaining.

func (*Encoder) U16

func (e *Encoder) U16(v uint16) *Encoder

U16 appends a little-endian uint16 and returns e for chaining.

func (*Encoder) U32

func (e *Encoder) U32(v uint32) *Encoder

U32 appends a little-endian uint32 and returns e for chaining.

func (*Encoder) U64

func (e *Encoder) U64(v uint64) *Encoder

U64 appends a little-endian uint64 and returns e for chaining.

func (*Encoder) U128c

func (e *Encoder) U128c(v U128) *Encoder

U128c is the chainable form of WriteU128. (U128 is taken by the type name.)

func (*Encoder) U256c

func (e *Encoder) U256c(v U256) *Encoder

U256c is the chainable form of WriteU256.

func (*Encoder) WriteBytes

func (e *Encoder) WriteBytes(b []byte)

WriteBytes appends b verbatim, without any length prefix.

func (*Encoder) WriteInt8

func (e *Encoder) WriteInt8(v int8)

WriteInt8 appends a signed int8.

func (*Encoder) WriteInt16

func (e *Encoder) WriteInt16(v int16)

WriteInt16 appends a little-endian int16.

func (*Encoder) WriteInt32

func (e *Encoder) WriteInt32(v int32)

WriteInt32 appends a little-endian int32.

func (*Encoder) WriteInt64

func (e *Encoder) WriteInt64(v int64)

WriteInt64 appends a little-endian int64.

func (*Encoder) WriteShortvec

func (e *Encoder) WriteShortvec(v uint16)

WriteShortvec appends a Solana compact-u16 length prefix (1-3 bytes), encoding v using EncodeShortvec.

func (*Encoder) WriteU128

func (e *Encoder) WriteU128(u U128)

WriteU128 appends u verbatim in 16 little-endian bytes.

func (*Encoder) WriteU256

func (e *Encoder) WriteU256(u U256)

WriteU256 appends u verbatim in 32 little-endian bytes.

func (*Encoder) WriteUint8

func (e *Encoder) WriteUint8(v uint8)

WriteUint8 appends a single byte.

func (*Encoder) WriteUint16

func (e *Encoder) WriteUint16(v uint16)

WriteUint16 appends a little-endian uint16.

func (*Encoder) WriteUint32

func (e *Encoder) WriteUint32(v uint32)

WriteUint32 appends a little-endian uint32.

func (*Encoder) WriteUint64

func (e *Encoder) WriteUint64(v uint64)

WriteUint64 appends a little-endian uint64.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader is a sticky-error wrapper around Decoder for chained, fluent decoding of fixed-shape Solana wire data. Each accessor reads one field; the first short-buffer (or invalid-shortvec) error is stored on the Reader and silences subsequent reads — so callers can sequence the happy path without per-call err checks and check Err() once at the end.

r := encoding.NewReader(data)
flag := r.U32()
var pk [32]byte
r.Read(pk[:])
supply := r.U64()
decimals := r.U8()
if err := r.Err(); err != nil {
    return nil, err
}

The underlying Decoder is exposed via Decoder() for callers who need shortvec, position, or the reflective Decode path mid-stream.

func NewReader

func NewReader(b []byte) *Reader

NewReader wraps b in a Reader. The bytes are not copied.

func (*Reader) Bool

func (r *Reader) Bool() bool

Bool reads 1 byte and reports whether it is non-zero.

func (*Reader) Bytes

func (r *Reader) Bytes(n int) []byte

Bytes reads exactly n bytes and returns them as a fresh, independent slice (the data is copied out of the underlying buffer). Use Read when you already have a destination slice.

func (*Reader) Bytes32

func (r *Reader) Bytes32() [32]byte

Bytes32 reads exactly 32 bytes and returns them by value. Convenient for fields backed by solana.PublicKey or solana.Hash, which are both [32]byte.

func (*Reader) Bytes64

func (r *Reader) Bytes64() [64]byte

Bytes64 reads exactly 64 bytes and returns them by value. Convenient for fields backed by solana.Signature, which is [64]byte.

func (*Reader) Decoder

func (r *Reader) Decoder() *Decoder

Decoder returns the underlying Decoder. Mutations through it are visible to subsequent Reader calls.

func (*Reader) Done

func (r *Reader) Done() error

Done returns nil if all bytes were consumed exactly. If a sticky read error is pending it returns that. Otherwise, if any unread bytes remain, it returns a *TrailingBytesError reporting how many.

Use this to enforce "no extra data" at the end of a decode. Callers that want to allow trailing data should check Err() instead.

func (*Reader) Err

func (r *Reader) Err() error

Err returns the first error encountered, or nil. Once non-nil, all subsequent reads are no-ops and return zero values.

func (*Reader) I8

func (r *Reader) I8() int8

func (*Reader) I16

func (r *Reader) I16() int16

func (*Reader) I32

func (r *Reader) I32() int32

func (*Reader) I64

func (r *Reader) I64() int64

func (*Reader) Pos

func (r *Reader) Pos() int

Pos reports the current read offset.

func (*Reader) Read

func (r *Reader) Read(out []byte)

Read fills out from the stream. The slice length determines how many bytes are consumed; passing pubkey[:] reads exactly 32 bytes.

func (*Reader) Remaining

func (r *Reader) Remaining() int

Remaining reports the number of unread bytes.

func (*Reader) Skip

func (r *Reader) Skip(n int)

Skip advances the position by n bytes without copying.

func (*Reader) StrU64 added in v0.1.3

func (r *Reader) StrU64() string

StrU64 reads a bincode string: u64 little-endian length, then UTF-8 bytes. For Borsh strings (u32 length) compose r.U32() + r.Bytes(int(n)).

func (*Reader) U8

func (r *Reader) U8() uint8

U8 reads a single byte. Returns 0 if the Reader is in an error state.

func (*Reader) U16

func (r *Reader) U16() uint16

U16 reads a little-endian uint16.

func (*Reader) U32

func (r *Reader) U32() uint32

U32 reads a little-endian uint32.

func (*Reader) U64

func (r *Reader) U64() uint64

U64 reads a little-endian uint64.

func (*Reader) U128

func (r *Reader) U128() U128

U128 reads a 16-byte little-endian unsigned 128-bit integer.

func (*Reader) U256

func (r *Reader) U256() U256

U256 reads a 32-byte little-endian unsigned 256-bit integer.

type TrailingBytesError

type TrailingBytesError struct {
	// Remaining is the number of unread bytes at the end of the buffer.
	Remaining int
}

TrailingBytesError is returned by Reader.Done when the buffer was not fully consumed. Match it via errors.As:

var te *encoding.TrailingBytesError
if errors.As(err, &te) {
    // log te.Remaining, recover, etc.
}

func (*TrailingBytesError) Error

func (e *TrailingBytesError) Error() string

type U128

type U128 [16]byte

U128 is a 128-bit unsigned integer stored in Rust / Solana little-endian byte order: u[0] is the least-significant byte, u[15] the most-significant. This matches the wire representation, so ReadU128 / WriteU128 are a single 16-byte memcpy on little-endian hosts.

func (U128) BigInt

func (u U128) BigInt() *big.Int

BigInt returns u as a new non-negative *big.Int.

func (U128) Hi

func (u U128) Hi() uint64

Hi returns the high 64 bits of u.

func (U128) IsZero

func (u U128) IsZero() bool

IsZero reports whether u == 0. Branch-free on amd64/arm64.

func (U128) Lo

func (u U128) Lo() uint64

Lo returns the low 64 bits of u.

func (U128) MarshalJSON

func (u U128) MarshalJSON() ([]byte, error)

MarshalJSON emits u as a JSON *string* decimal, so JavaScript consumers that lose precision above 2^53 still receive the exact value.

func (U128) MarshalText

func (u U128) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler (decimal string).

func (*U128) SetBigInt

func (u *U128) SetBigInt(x *big.Int) error

SetBigInt sets u from x, which must be non-negative and fit in 128 bits. Returns an error otherwise; u is left unchanged on error.

func (*U128) SetLoHi

func (u *U128) SetLoHi(lo, hi uint64)

SetLoHi sets u from two 64-bit words.

func (*U128) SetUint64

func (u *U128) SetUint64(v uint64)

SetUint64 sets u to v (high 64 bits zero).

func (U128) String

func (u U128) String() string

String renders u as a decimal string, like %d for built-in uints.

func (*U128) UnmarshalJSON

func (u *U128) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts either a JSON string or a JSON number encoding a non-negative decimal U128.

func (*U128) UnmarshalText

func (u *U128) UnmarshalText(b []byte) error

UnmarshalText parses a decimal U128 produced by MarshalText.

type U256

type U256 [32]byte

U256 is a 256-bit unsigned integer in Rust / Solana little-endian byte order (u[0] least-significant, u[31] most-significant). Used by SPL programs that expose u256 fields (oracle prices, curve parameters, some concentrated-liquidity math) and by Ethereum-style bridged state.

func (U256) BigInt

func (u U256) BigInt() *big.Int

BigInt returns u as a new non-negative *big.Int.

func (U256) IsZero

func (u U256) IsZero() bool

IsZero reports whether u == 0.

func (U256) MarshalJSON

func (u U256) MarshalJSON() ([]byte, error)

MarshalJSON emits u as a JSON string decimal. See U128.MarshalJSON.

func (U256) MarshalText

func (u U256) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler (decimal string).

func (*U256) SetBigInt

func (u *U256) SetBigInt(x *big.Int) error

SetBigInt sets u from x, which must be non-negative and fit in 256 bits. Returns an error otherwise; u is left unchanged on error.

func (*U256) SetWord

func (u *U256) SetWord(i int, v uint64)

SetWord sets the i-th 64-bit word of u. Panics if i is out of range.

func (U256) String

func (u U256) String() string

String renders u as a decimal string.

func (*U256) UnmarshalJSON

func (u *U256) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts either a JSON string or a JSON number encoding a non-negative decimal U256.

func (*U256) UnmarshalText

func (u *U256) UnmarshalText(b []byte) error

UnmarshalText parses a decimal U256 produced by MarshalText.

func (U256) Word

func (u U256) Word(i int) uint64

Word returns the i-th 64-bit word of u (i = 0 is least significant, i = 3 most significant). Panics if i is out of range.

type Unmarshaler

type Unmarshaler interface {
	UnmarshalFromDecoder(d *Decoder) error
}

Unmarshaler lets a type plug in hand-written decoding logic and bypass reflection entirely. Types whose addressable value satisfies this interface are dispatched directly by DecodeTo; reflection never descends into them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL