Skip to content

Ruby Types

LUMOS generates Ruby classes with borsh-rb for Borsh serialization.

Terminal window
gem install borsh-rb
LUMOS TypeRuby TypeBorsh Schema
u8Integer:u8
u16Integer:u16
u32Integer:u32
u64Integer:u64
u128Integer:u128
i8Integer:i8
i16Integer:i16
i32Integer:i32
i64Integer:i64
i128Integer:i128
boolTrueClass/FalseClass:bool
StringString:string
PublicKeyArray<Integer>[:u8, 32]
SignatureArray<Integer>[:u8, 64]
Vec<T>Array[:array, T]
Option<T>T / nil[:option, T]
[T; N]Array[:u8, N]

#[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
require 'borsh'
# PlayerAccount represents the player account data
# @attr wallet [Array<Integer>] The wallet public key (32 bytes)
# @attr level [Integer] The player level
# @attr experience [Integer] Total experience points
# @attr username [String] Player username
# @attr guild [Array<Integer>, nil] Optional guild public key
# @attr inventory [Array<Integer>] Player inventory items
class PlayerAccount
attr_accessor :wallet, :level, :experience, :username, :guild, :inventory
# Borsh serialization schema
SCHEMA = {
wallet: [:u8, 32],
level: :u16,
experience: :u64,
username: :string,
guild: [:option, [:u8, 32]],
inventory: [:array, :u8]
}
# Initialize with optional parameters
# @param opts [Hash] initialization options
def initialize(opts = {})
@wallet = opts[:wallet] || Array.new(32, 0)
@level = opts[:level] || 0
@experience = opts[:experience] || 0
@username = opts[:username] || ""
@guild = opts[:guild]
@inventory = opts[:inventory] || []
end
# Convert to hash representation
# @return [Hash] hash representation
def to_h
{
wallet: @wallet,
level: @level,
experience: @experience,
username: @username,
guild: @guild,
inventory: @inventory
}
end
end

require_relative 'player_schema'
# Create an account
account = PlayerAccount.new(
wallet: [0x11] * 32, # 32-byte public key
level: 42,
experience: 150_000,
username: "CryptoGamer",
guild: nil, # No guild
inventory: [1, 2, 3, 4, 5]
)
# Serialize to bytes
serialized = Borsh.serialize(PlayerAccount::SCHEMA, account.to_h)
puts "Serialized: #{serialized.unpack1('H*')}"
# Deserialize from bytes
data = [0x11, 0x22, ...].pack('C*') # Borsh-encoded data
parsed = Borsh.deserialize(PlayerAccount::SCHEMA, data)
account = PlayerAccount.new(parsed)
puts "Level: #{account.level}"
puts "XP: #{account.experience}"

# GameStatus enum
module GameStatus
ACTIVE = 0
PAUSED = 1
ENDED = 2
def self.from_value(val)
case val
when 0 then :active
when 1 then :paused
when 2 then :ended
else raise "Unknown GameStatus: #{val}"
end
end
def self.to_value(sym)
case sym
when :active then 0
when :paused then 1
when :ended then 2
else raise "Unknown GameStatus: #{sym}"
end
end
end
# Transfer variant
Transfer = Struct.new(:recipient, :amount, keyword_init: true) do
DISCRIMINANT = 0
SCHEMA = {
recipient: [:u8, 32],
amount: :u64
}
end
# Stake variant
Stake = Struct.new(:validator, :amount, keyword_init: true) do
DISCRIMINANT = 1
SCHEMA = {
validator: [:u8, 32],
amount: :u64
}
end
# Instruction enum helper
module Instruction
def self.deserialize(data)
discriminant = data[0]
payload = data[1..]
case discriminant
when 0
parsed = Borsh.deserialize(Transfer::SCHEMA, payload)
Transfer.new(**parsed)
when 1
parsed = Borsh.deserialize(Stake::SCHEMA, payload)
Stake.new(**parsed)
else
raise "Unknown instruction: #{discriminant}"
end
end
end

require 'base58'
class PlayerAccount
# Convert wallet to base58 string
# @return [String] base58-encoded public key
def wallet_base58
Base58.binary_to_base58(wallet.pack('C*'), :bitcoin)
end
# Set wallet from base58 string
# @param str [String] base58-encoded public key
def wallet_base58=(str)
@wallet = Base58.base58_to_binary(str, :bitcoin).bytes
end
end

Ruby’s Integer handles arbitrary precision natively:

# Safe in Ruby
max_u64 = 18_446_744_073_709_551_615
max_u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455
# Both work correctly
account.balance = max_u64
account.total_supply = max_u128
# Safe option handling
if account.guild
puts "Guild: #{Base58.binary_to_base58(account.guild.pack('C*'), :bitcoin)}"
else
puts "No guild"
end
# Or using Ruby's safe navigation
guild_base58 = account.guild&.then { |g| Base58.binary_to_base58(g.pack('C*'), :bitcoin) }
class PlayerAccount
# Validate account data
# @return [Boolean] true if valid
def valid?
wallet.length == 32 &&
level >= 0 && level <= 65535 &&
!username.empty? &&
(guild.nil? || guild.length == 32)
end
# Validate and raise on error
def validate!
raise "Invalid wallet length" unless wallet.length == 32
raise "Invalid level" unless level >= 0 && level <= 65535
raise "Username cannot be empty" if username.empty?
raise "Invalid guild length" if guild && guild.length != 32
true
end
end

require 'solana_ruby'
client = SolanaRuby::Client.new("https://api.devnet.solana.com")
def get_account_data(client, pubkey)
response = client.get_account_info(pubkey)
return nil unless response && response['value']
data = Base64.decode64(response['value']['data'][0])
# Skip 8-byte Anchor discriminator
account_data = data[8..]
parsed = Borsh.deserialize(PlayerAccount::SCHEMA, account_data)
PlayerAccount.new(parsed)
end

LUMOS generates YARD-compatible documentation:

# Generate documentation
# yard doc player_schema.rb
# View in browser
# yard server

Generated YARD tags:

  • @attr for each field
  • @return for methods
  • @param for method parameters