Module: Solace::Utils::Codecs
Overview
Module for encoding and decoding data.
The helpers are grouped by category: base64/base58 string encodings, fixed-width little-endian integers, the Solana compact-u16 (shortvec) varint, length-prefixed byte/pubkey collections, and optionals.
Serialization formats referenced below:
-
“Borsh” — the Anchor serialization spec. Borsh encodes ‘bool` as a single 0/1 byte, `Vec<T>`/`bytes` with a u32 little-endian length prefix, and `Option<T>` as a 1-byte discriminant (0 = None, 1 = Some) followed by the value. Methods that produce these layouts say so explicitly.
-
“SmallVec” — a Solana/Anchor-program convention (e.g. Squads) that prefixes a collection with a u8 or u16 length instead of Borsh’s u32. NOT Borsh.
-
“compact-u16” / “shortvec” — Solana’s own variable-length integer used in the transaction wire format. NOT Borsh.
‘extend self` exposes every method both as a module method (Solace::Utils::Codecs.<method>) and as an instance method when the module is included into a composed encoder/decoder.
Instance Method Summary collapse
-
#base58_to_binary(string) ⇒ String
Decodes a Base58 string into a binary string.
-
#base58_to_bytes(string) ⇒ Array<Integer>
Decodes a Base58 string into a sequence of bytes.
-
#base64_to_bytestream(base64) ⇒ StringIO
Creates a StringIO from a base64 string.
-
#binary_to_base58(binary) ⇒ String
Encodes a binary string in Base58 format.
-
#bytes_to_base58(bytes) ⇒ String
Encodes a byte array in Base58 format.
-
#decode_bytes(stream) ⇒ String
Decodes a Borsh bytes / Vec<u8> field: u32 LE length prefix + raw bytes.
-
#decode_compact_u16(stream) ⇒ Integer
Decodes a compact-u16 (shortvec) value from an IO-like object.
-
#decode_le_i64(stream) ⇒ Integer
Decodes an i64 from 8 little-endian bytes (two’s complement).
-
#decode_le_u128(stream) ⇒ Integer
Decodes a u128 from 16 little-endian bytes (two u64 words, low word first).
-
#decode_le_u16(stream) ⇒ Integer
Decodes a u16 from 2 little-endian bytes.
-
#decode_le_u32(stream) ⇒ Integer
Decodes a u32 from 4 little-endian bytes.
-
#decode_le_u64(stream) ⇒ Integer
Decodes a little-endian u64 value from a sequence of bytes.
-
#decode_option_i64(stream) ⇒ Integer?
Decodes a Borsh Option<i64>: None → nil, Some(i64) → integer.
-
#decode_option_pubkey(stream) ⇒ String?
Decodes a Borsh Option<publicKey>: None → nil, Some(key) → base58 pubkey.
-
#decode_pubkey(stream) ⇒ String
Decodes a public key from 32 bytes.
-
#decode_u8(stream) ⇒ Integer
Decodes a u8 from 1 byte.
-
#decode_vec_pubkeys(stream) ⇒ Array<String>
Decodes a Borsh Vec<publicKey>: u32 LE count prefix followed by each 32-byte pubkey.
-
#encode_bool(bool) ⇒ Array<Integer>
Encodes a Borsh bool as a single byte: false → 0, true → 1.
-
#encode_bytes(bytes) ⇒ Array<Integer>
Encodes a Borsh bytes field / Vec<u8>: u32 LE length prefix + raw bytes.
-
#encode_compact_u16(u16) ⇒ String
Encodes an integer as a compact-u16 (shortvec) varint.
-
#encode_le_i64(i64) ⇒ String
Encodes an i64 as 8 little-endian bytes (two’s complement).
-
#encode_le_u128(u128) ⇒ String
Encodes a u128 as 16 little-endian bytes (two u64 words, low word first).
-
#encode_le_u16(u16) ⇒ String
Encodes a u16 as 2 little-endian bytes.
-
#encode_le_u32(u32) ⇒ String
Encodes a u32 as 4 little-endian bytes.
-
#encode_le_u64(u64) ⇒ String
Encodes a u64 value in little-endian format.
-
#encode_option_i64(i64) ⇒ Array<Integer>
Encodes a Borsh Option<i64>: None → [0], Some(i64) → [1] + 8 LE bytes.
-
#encode_option_pubkey(pubkey) ⇒ Array<Integer>
Encodes a Borsh Option<publicKey>: None → [0], Some(key) → [1] + 32 bytes.
-
#encode_option_string(str) ⇒ Array<Integer>
Encodes a Borsh Option<String>: None → [0], Some(str) → [1] + u32 LE length + UTF-8 bytes.
-
#encode_pubkey(pubkey) ⇒ Array<Integer>
Encodes a public key as 32 raw bytes (a Solana primitive, not a Borsh type).
-
#encode_smallvec_u16_bytes(bytes) ⇒ Array<Integer>
Encodes a SmallVec<u16, u8>: u16 LE length prefix + raw bytes.
-
#encode_smallvec_u8_bytes(bytes) ⇒ Array<Integer>
Encodes a SmallVec<u8, u8>: u8 length prefix + raw bytes.
-
#encode_smallvec_u8_pubkeys(pubkeys) ⇒ Array<Integer>
Encodes a SmallVec<u8, Pubkey>: u8 count prefix followed by each 32-byte pubkey.
-
#encode_u8(byte) ⇒ Array<Integer>
Encodes a u8 as a single byte.
-
#encode_vec_pubkeys(pubkeys) ⇒ Array<Integer>
Encodes a Borsh Vec<publicKey>: u32 LE count prefix followed by each 32-byte pubkey.
-
#valid_base58?(string) ⇒ Boolean
Checks if a string is a valid Base58 string.
Instance Method Details
#base58_to_binary(string) ⇒ String
Decodes a Base58 string into a binary string
75 76 77 |
# File 'lib/solace/utils/codecs.rb', line 75 def base58_to_binary(string) base58_to_bytes(string).pack('C*') end |
#base58_to_bytes(string) ⇒ Array<Integer>
Decodes a Base58 string into a sequence of bytes
91 92 93 |
# File 'lib/solace/utils/codecs.rb', line 91 def base58_to_bytes(string) Base58.base58_to_binary(string, :bitcoin).bytes end |
#base64_to_bytestream(base64) ⇒ StringIO
Creates a StringIO from a base64 string.
57 58 59 |
# File 'lib/solace/utils/codecs.rb', line 57 def base64_to_bytestream(base64) StringIO.new(Base64.decode64(base64)) end |
#binary_to_base58(binary) ⇒ String
Encodes a binary string in Base58 format.
67 68 69 |
# File 'lib/solace/utils/codecs.rb', line 67 def binary_to_base58(binary) Base58.binary_to_base58(binary, :bitcoin) end |
#bytes_to_base58(bytes) ⇒ String
Encodes a byte array in Base58 format
83 84 85 |
# File 'lib/solace/utils/codecs.rb', line 83 def bytes_to_base58(bytes) binary_to_base58(bytes.pack('C*')) end |
#decode_bytes(stream) ⇒ String
Decodes a Borsh bytes / Vec<u8> field: u32 LE length prefix + raw bytes.
298 299 300 |
# File 'lib/solace/utils/codecs.rb', line 298 def decode_bytes(stream) stream.read(decode_le_u32(stream)) end |
#decode_compact_u16(stream) ⇒ Integer
Decodes a compact-u16 (shortvec) value from an IO-like object.
Reads bytes one at a time, accumulating the result until the MSB is 0.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/solace/utils/codecs.rb', line 264 def decode_compact_u16(stream) value = 0 shift = 0 bytes_read = 0 loop do byte = stream.read(1) raise EOFError, 'Unexpected end of input while decoding compact-u16' unless byte byte = byte.ord value |= (byte & 0x7F) << shift bytes_read += 1 break if byte.nobits?(0x80) shift += 7 end [value, bytes_read] end |
#decode_le_i64(stream) ⇒ Integer
Decodes an i64 from 8 little-endian bytes (two’s complement).
203 204 205 |
# File 'lib/solace/utils/codecs.rb', line 203 def decode_le_i64(stream) stream.read(8).unpack1('q<') end |
#decode_le_u128(stream) ⇒ Integer
Decodes a u128 from 16 little-endian bytes (two u64 words, low word first).
186 187 188 189 |
# File 'lib/solace/utils/codecs.rb', line 186 def decode_le_u128(stream) lo, hi = stream.read(16).unpack('Q<Q<') lo + (hi << 64) end |
#decode_le_u16(stream) ⇒ Integer
Decodes a u16 from 2 little-endian bytes.
138 139 140 |
# File 'lib/solace/utils/codecs.rb', line 138 def decode_le_u16(stream) stream.read(2).unpack1('S<') end |
#decode_le_u32(stream) ⇒ Integer
Decodes a u32 from 4 little-endian bytes.
154 155 156 |
# File 'lib/solace/utils/codecs.rb', line 154 def decode_le_u32(stream) stream.read(4).unpack1('L<') end |
#decode_le_u64(stream) ⇒ Integer
Decodes a little-endian u64 value from a sequence of bytes
170 171 172 |
# File 'lib/solace/utils/codecs.rb', line 170 def decode_le_u64(stream) stream.read(8).unpack1('Q<') end |
#decode_option_i64(stream) ⇒ Integer?
Decodes a Borsh Option<i64>: None → nil, Some(i64) → integer.
406 407 408 409 410 |
# File 'lib/solace/utils/codecs.rb', line 406 def decode_option_i64(stream) return nil if decode_u8(stream).zero? decode_le_i64(stream) end |
#decode_option_pubkey(stream) ⇒ String?
Decodes a Borsh Option<publicKey>: None → nil, Some(key) → base58 pubkey.
386 387 388 389 390 |
# File 'lib/solace/utils/codecs.rb', line 386 def decode_option_pubkey(stream) return nil if decode_u8(stream).zero? decode_pubkey(stream) end |
#decode_pubkey(stream) ⇒ String
Decodes a public key from 32 bytes.
336 337 338 |
# File 'lib/solace/utils/codecs.rb', line 336 def decode_pubkey(stream) bytes_to_base58(stream.read(32).bytes) end |
#decode_u8(stream) ⇒ Integer
Decodes a u8 from 1 byte.
122 123 124 |
# File 'lib/solace/utils/codecs.rb', line 122 def decode_u8(stream) stream.read(1).unpack1('C') end |
#decode_vec_pubkeys(stream) ⇒ Array<String>
Decodes a Borsh Vec<publicKey>: u32 LE count prefix followed by each 32-byte pubkey.
355 356 357 |
# File 'lib/solace/utils/codecs.rb', line 355 def decode_vec_pubkeys(stream) Array.new(decode_le_u32(stream)) { decode_pubkey(stream) } end |
#encode_bool(bool) ⇒ Array<Integer>
Encodes a Borsh bool as a single byte: false → 0, true → 1.
213 214 215 |
# File 'lib/solace/utils/codecs.rb', line 213 def encode_bool(bool) [bool ? 1 : 0] end |
#encode_bytes(bytes) ⇒ Array<Integer>
Encodes a Borsh bytes field / Vec<u8>: u32 LE length prefix + raw bytes.
290 291 292 |
# File 'lib/solace/utils/codecs.rb', line 290 def encode_bytes(bytes) encode_le_u32(bytes.length).bytes + bytes end |
#encode_compact_u16(u16) ⇒ String
Encodes an integer as a compact-u16 (shortvec) varint. This is Solana’s transaction wire-format length prefix, NOT Borsh.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/solace/utils/codecs.rb', line 224 def encode_compact_u16(u16) out = [] loop do # In general, n >> 7 shifts the bits of n to the right by # 7 positions, effectively dividing n by 128 and discarding # the remainder (integer division). This is commonly used in # encoding schemes to process one "byte" (7 bits) at a time. if (u16 >> 7).zero? out << u16 break end # The expression out << ((n & 0x7F) | 0x80) is used in variable-length # integer encoding, such as the compact-u16 encoding. # # n & 0x7F: # - 0x7F is 127 in decimal, or 0111 1111 in binary. # - n & 0x7F masks out all but the lowest 7 bits of n. This extracts the least significant 7 bits of n. # # (n & 0x7F) | 0x80: # - 0x80 is 128 in decimal, or 1000 0000 in binary. # - | (bitwise OR) sets the highest bit (the 8th bit) to 1. # - This is a signal that there are more bytes to come in the encoding (i.e., the value hasn't been fully # encoded yet). # # out << ...: # - This appends the resulting byte to the out array. out << ((u16 & 0x7F) | 0x80) u16 >>= 7 end out.pack('C*') end |
#encode_le_i64(i64) ⇒ String
Encodes an i64 as 8 little-endian bytes (two’s complement).
195 196 197 |
# File 'lib/solace/utils/codecs.rb', line 195 def encode_le_i64(i64) [i64].pack('q<') end |
#encode_le_u128(u128) ⇒ String
Encodes a u128 as 16 little-endian bytes (two u64 words, low word first).
178 179 180 |
# File 'lib/solace/utils/codecs.rb', line 178 def encode_le_u128(u128) [u128 & 0xFFFFFFFFFFFFFFFF, u128 >> 64].pack('Q<Q<') end |
#encode_le_u16(u16) ⇒ String
Encodes a u16 as 2 little-endian bytes.
130 131 132 |
# File 'lib/solace/utils/codecs.rb', line 130 def encode_le_u16(u16) [u16].pack('S<') end |
#encode_le_u32(u32) ⇒ String
Encodes a u32 as 4 little-endian bytes.
146 147 148 |
# File 'lib/solace/utils/codecs.rb', line 146 def encode_le_u32(u32) [u32].pack('L<') end |
#encode_le_u64(u64) ⇒ String
Encodes a u64 value in little-endian format
162 163 164 |
# File 'lib/solace/utils/codecs.rb', line 162 def encode_le_u64(u64) [u64].pack('Q<') # 64-bit little-endian end |
#encode_option_i64(i64) ⇒ Array<Integer>
Encodes a Borsh Option<i64>: None → [0], Some(i64) → [1] + 8 LE bytes.
396 397 398 399 400 |
# File 'lib/solace/utils/codecs.rb', line 396 def encode_option_i64(i64) return [0] if i64.nil? [1] + encode_le_i64(i64).bytes end |
#encode_option_pubkey(pubkey) ⇒ Array<Integer>
Encodes a Borsh Option<publicKey>: None → [0], Some(key) → [1] + 32 bytes.
376 377 378 379 380 |
# File 'lib/solace/utils/codecs.rb', line 376 def encode_option_pubkey(pubkey) return [0] if pubkey.nil? [1] + encode_pubkey(pubkey) end |
#encode_option_string(str) ⇒ Array<Integer>
Encodes a Borsh Option<String>: None → [0], Some(str) → [1] + u32 LE length + UTF-8 bytes.
417 418 419 420 421 422 |
# File 'lib/solace/utils/codecs.rb', line 417 def encode_option_string(str) return [0] if str.nil? bytes = str.encode('UTF-8').bytes [1] + encode_le_u32(bytes.length).bytes + bytes end |
#encode_pubkey(pubkey) ⇒ Array<Integer>
Encodes a public key as 32 raw bytes (a Solana primitive, not a Borsh type). Accepts any representation that resolves to a base58 string via #to_s (String, Keypair, PublicKey).
328 329 330 |
# File 'lib/solace/utils/codecs.rb', line 328 def encode_pubkey(pubkey) base58_to_bytes(pubkey.to_s) end |
#encode_smallvec_u16_bytes(bytes) ⇒ Array<Integer>
Encodes a SmallVec<u16, u8>: u16 LE length prefix + raw bytes. SmallVec is a Solana/Anchor-program convention, NOT Borsh (which would use a u32 prefix).
316 317 318 |
# File 'lib/solace/utils/codecs.rb', line 316 def encode_smallvec_u16_bytes(bytes) encode_le_u16(bytes.length).bytes + bytes end |
#encode_smallvec_u8_bytes(bytes) ⇒ Array<Integer>
Encodes a SmallVec<u8, u8>: u8 length prefix + raw bytes. SmallVec is a Solana/Anchor-program convention, NOT Borsh (which would use a u32 prefix).
307 308 309 |
# File 'lib/solace/utils/codecs.rb', line 307 def encode_smallvec_u8_bytes(bytes) [bytes.length] + bytes end |
#encode_smallvec_u8_pubkeys(pubkeys) ⇒ Array<Integer>
Encodes a SmallVec<u8, Pubkey>: u8 count prefix followed by each 32-byte pubkey. SmallVec is a Solana convention (NOT Borsh); used by the transaction message header’s account_keys (distinct from encode_vec_pubkeys, which uses Borsh’s u32 count).
366 367 368 |
# File 'lib/solace/utils/codecs.rb', line 366 def encode_smallvec_u8_pubkeys(pubkeys) [pubkeys.length] + pubkeys.flat_map { |pubkey| encode_pubkey(pubkey) } end |
#encode_u8(byte) ⇒ Array<Integer>
Encodes a u8 as a single byte.
114 115 116 |
# File 'lib/solace/utils/codecs.rb', line 114 def encode_u8(byte) [byte] end |
#encode_vec_pubkeys(pubkeys) ⇒ Array<Integer>
Encodes a Borsh Vec<publicKey>: u32 LE count prefix followed by each 32-byte pubkey.
345 346 347 348 |
# File 'lib/solace/utils/codecs.rb', line 345 def encode_vec_pubkeys(pubkeys) encode_le_u32(pubkeys.length).bytes + pubkeys.flat_map { |pubkey| encode_pubkey(pubkey) } end |
#valid_base58?(string) ⇒ Boolean
Checks if a string is a valid Base58 string
99 100 101 102 103 104 105 106 |
# File 'lib/solace/utils/codecs.rb', line 99 def valid_base58?(string) return false if string.nil? || string.empty? Base58.decode(string) true rescue StandardError => _e false end |