Skip to content

Go Types

LUMOS generates Go structs with struct tags for Borsh serialization.

LUMOS TypeGo TypeNotes
u8uint8
u16uint16
u32uint32
u64uint64
u128[16]byteGo lacks native 128-bit integers
i8int8
i16int16
i32int32
i64int64
i128[16]byteLittle-endian byte array
boolbool
Stringstring
PublicKey[32]byteFixed 32-byte array
Signature[64]byteFixed 64-byte array
Vec<T>[]TSlice
Option<T>*TPointer (nil = None)
[T; N][N]TFixed-size array

#[solana]
struct PlayerAccount {
wallet: PublicKey,
level: u16,
experience: u64,
username: String,
guild: Option<PublicKey>,
inventory: [u8],
}
player_account.lumos
// Code generated by LUMOS - DO NOT EDIT
package schema
// PlayerAccount represents the player account data
type PlayerAccount struct {
Wallet [32]byte `borsh:"wallet"` // PublicKey
Level uint16 `borsh:"level"` // u16
Experience uint64 `borsh:"experience"` // u64
Username string `borsh:"username"` // String
Guild *[32]byte `borsh:"guild"` // Option<PublicKey>
Inventory []uint8 `borsh:"inventory"` // Vec<u8>
}

Go doesn’t have a standard Borsh library, but you can use struct tags with custom serialization:

package schema
import (
"encoding/binary"
"bytes"
)
// Serialize serializes PlayerAccount to Borsh format
func (p *PlayerAccount) Serialize() ([]byte, error) {
buf := new(bytes.Buffer)
// Wallet (32 bytes, fixed)
buf.Write(p.Wallet[:])
// Level (u16, little-endian)
binary.Write(buf, binary.LittleEndian, p.Level)
// Experience (u64, little-endian)
binary.Write(buf, binary.LittleEndian, p.Experience)
// Username (length-prefixed string)
binary.Write(buf, binary.LittleEndian, uint32(len(p.Username)))
buf.WriteString(p.Username)
// Guild (Option: 0x00 for None, 0x01 + value for Some)
if p.Guild == nil {
buf.WriteByte(0x00)
} else {
buf.WriteByte(0x01)
buf.Write(p.Guild[:])
}
// Inventory (Vec: length prefix + elements)
binary.Write(buf, binary.LittleEndian, uint32(len(p.Inventory)))
buf.Write(p.Inventory)
return buf.Bytes(), nil
}
// Deserialize deserializes Borsh bytes to PlayerAccount
func DeserializePlayerAccount(data []byte) (*PlayerAccount, error) {
r := bytes.NewReader(data)
p := &PlayerAccount{}
// Wallet
if _, err := r.Read(p.Wallet[:]); err != nil {
return nil, err
}
// Level
if err := binary.Read(r, binary.LittleEndian, &p.Level); err != nil {
return nil, err
}
// Experience
if err := binary.Read(r, binary.LittleEndian, &p.Experience); err != nil {
return nil, err
}
// Username
var strLen uint32
binary.Read(r, binary.LittleEndian, &strLen)
strBytes := make([]byte, strLen)
r.Read(strBytes)
p.Username = string(strBytes)
// Guild (Option)
optByte, _ := r.ReadByte()
if optByte == 0x01 {
guild := [32]byte{}
r.Read(guild[:])
p.Guild = &guild
}
// Inventory (Vec)
var vecLen uint32
binary.Read(r, binary.LittleEndian, &vecLen)
p.Inventory = make([]uint8, vecLen)
r.Read(p.Inventory)
return p, nil
}

// GameStatus represents the game state
type GameStatus uint8
const (
GameStatusActive GameStatus = 0
GameStatusPaused GameStatus = 1
GameStatusTerminated GameStatus = 2
)
func (s GameStatus) String() string {
switch s {
case GameStatusActive:
return "Active"
case GameStatusPaused:
return "Paused"
case GameStatusTerminated:
return "Terminated"
default:
return "Unknown"
}
}
// Instruction is the enum interface
type Instruction interface {
isInstruction()
Discriminant() uint8
}
// Transfer variant
type Transfer struct {
Recipient [32]byte
Amount uint64
}
func (Transfer) isInstruction() {}
func (Transfer) Discriminant() uint8 { return 0 }
// Stake variant
type Stake struct {
Validator [32]byte
Amount uint64
}
func (Stake) isInstruction() {}
func (Stake) Discriminant() uint8 { return 1 }

Go lacks native 128-bit integers, so LUMOS uses [16]byte:

import "math/big"
// U128 represents a 128-bit unsigned integer
type U128 [16]byte
// ToBigInt converts U128 to *big.Int
func (u U128) ToBigInt() *big.Int {
// Reverse for little-endian
reversed := make([]byte, 16)
for i := 0; i < 16; i++ {
reversed[15-i] = u[i]
}
return new(big.Int).SetBytes(reversed)
}
// FromBigInt creates U128 from *big.Int
func FromBigInt(n *big.Int) U128 {
var u U128
bytes := n.Bytes()
// Copy in little-endian order
for i := 0; i < len(bytes) && i < 16; i++ {
u[i] = bytes[len(bytes)-1-i]
}
return u
}

import (
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
)
func GetAccountData(client *rpc.Client, pubkey solana.PublicKey) (*PlayerAccount, error) {
resp, err := client.GetAccountInfo(context.Background(), pubkey)
if err != nil {
return nil, err
}
data := resp.Value.Data.GetBinary()
// Skip 8-byte Anchor discriminator
return DeserializePlayerAccount(data[8:])
}

import "github.com/mr-tron/base58"
func PubkeyToBase58(pk [32]byte) string {
return base58.Encode(pk[:])
}
func Base58ToPubkey(s string) ([32]byte, error) {
var pk [32]byte
decoded, err := base58.Decode(s)
if err != nil {
return pk, err
}
copy(pk[:], decoded)
return pk, nil
}
// Safe option access
func (p *PlayerAccount) GetGuild() (string, bool) {
if p.Guild == nil {
return "", false
}
return PubkeyToBase58(*p.Guild), true
}
// Always handle serialization errors
data, err := account.Serialize()
if err != nil {
return fmt.Errorf("failed to serialize: %w", err)
}