solace 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG +0 -0
- data/LICENSE +0 -0
- data/README.md +661 -0
- data/lib/solace/address_lookup_table.rb +50 -0
- data/lib/solace/concerns/binary_serializable.rb +30 -0
- data/lib/solace/connection.rb +187 -0
- data/lib/solace/constants.rb +52 -0
- data/lib/solace/instruction.rb +38 -0
- data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +68 -0
- data/lib/solace/instructions/spl_token/initialize_account_instruction.rb +46 -0
- data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +68 -0
- data/lib/solace/instructions/spl_token/mint_to_instruction.rb +48 -0
- data/lib/solace/instructions/spl_token/transfer_instruction.rb +48 -0
- data/lib/solace/instructions/system_program/create_account_instruction.rb +58 -0
- data/lib/solace/instructions/transfer_checked_instruction.rb +58 -0
- data/lib/solace/instructions/transfer_instruction.rb +48 -0
- data/lib/solace/keypair.rb +121 -0
- data/lib/solace/message.rb +95 -0
- data/lib/solace/programs/associated_token_account.rb +96 -0
- data/lib/solace/programs/base.rb +22 -0
- data/lib/solace/programs/spl_token.rb +187 -0
- data/lib/solace/public_key.rb +74 -0
- data/lib/solace/serializable_record.rb +26 -0
- data/lib/solace/serializers/address_lookup_table_deserializer.rb +62 -0
- data/lib/solace/serializers/address_lookup_table_serializer.rb +54 -0
- data/lib/solace/serializers/base.rb +31 -0
- data/lib/solace/serializers/base_deserializer.rb +56 -0
- data/lib/solace/serializers/base_serializer.rb +52 -0
- data/lib/solace/serializers/instruction_deserializer.rb +62 -0
- data/lib/solace/serializers/instruction_serializer.rb +54 -0
- data/lib/solace/serializers/message_deserializer.rb +116 -0
- data/lib/solace/serializers/message_serializer.rb +95 -0
- data/lib/solace/serializers/transaction_deserializer.rb +49 -0
- data/lib/solace/serializers/transaction_serializer.rb +60 -0
- data/lib/solace/transaction.rb +98 -0
- data/lib/solace/utils/codecs.rb +220 -0
- data/lib/solace/utils/curve25519_dalek.rb +59 -0
- data/lib/solace/utils/libcurve25519_dalek-linux/libcurve25519_dalek.so +0 -0
- data/lib/solace/utils/libcurve25519_dalek-macos/libcurve25519_dalek.dylib +0 -0
- data/lib/solace/utils/libcurve25519_dalek-windows/curve25519_dalek.dll +0 -0
- data/lib/solace/utils/pda.rb +100 -0
- data/lib/solace/version.rb +5 -0
- data/lib/solace.rb +39 -0
- metadata +165 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# PublicKey
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Represents a Solana Ed25519 Public Key
|
8
|
+
# Provides utility methods for encoding, decoding, and validation
|
9
|
+
module Solace
|
10
|
+
class PublicKey
|
11
|
+
include Solace::Utils::PDA
|
12
|
+
|
13
|
+
# !@const LENGTH
|
14
|
+
# The length of a Solana public key in bytes
|
15
|
+
#
|
16
|
+
# @return [Integer] The length of a public key
|
17
|
+
LENGTH = 32
|
18
|
+
|
19
|
+
# !@const MAX_BUMP_SEED
|
20
|
+
# The maximum seed value for a Program Derived Address
|
21
|
+
#
|
22
|
+
# @return [Integer] The maximum seed value
|
23
|
+
MAX_BUMP_SEED = 255
|
24
|
+
|
25
|
+
# !@const PDA_MARKER
|
26
|
+
# The marker for a Program Derived Address
|
27
|
+
#
|
28
|
+
# @return [String] The marker for a PDA
|
29
|
+
PDA_MARKER = 'ProgramDerivedAddress'
|
30
|
+
|
31
|
+
# !@attribute bytes
|
32
|
+
# @return [Array<Integer>] The bytes of the public key
|
33
|
+
attr_reader :bytes
|
34
|
+
|
35
|
+
# Initialize with a 32-byte array or string
|
36
|
+
#
|
37
|
+
# @param bytes [String, Array<Integer>] 32-byte array or string
|
38
|
+
# @return [PublicKey]
|
39
|
+
def initialize(bytes)
|
40
|
+
raise ArgumentError, "Public key must be #{LENGTH} bytes" unless bytes.length == LENGTH
|
41
|
+
|
42
|
+
@bytes = bytes.freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return the base58 representation of the public key
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
def to_base58
|
49
|
+
Solace::Utils::Codecs.bytes_to_base58(@bytes)
|
50
|
+
end
|
51
|
+
|
52
|
+
# String representation (base58)
|
53
|
+
#
|
54
|
+
# @return [String]
|
55
|
+
def to_s
|
56
|
+
to_base58
|
57
|
+
end
|
58
|
+
|
59
|
+
# Compare two public keys for equality
|
60
|
+
#
|
61
|
+
# @param other [PublicKey]
|
62
|
+
# @return [Boolean]
|
63
|
+
def ==(other)
|
64
|
+
other.is_a?(Solace::PublicKey) && other.bytes == bytes
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return the public key as a byte array
|
68
|
+
#
|
69
|
+
# @return [Array<Integer>]
|
70
|
+
def to_bytes
|
71
|
+
@bytes.dup
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
class SerializableRecord
|
5
|
+
include Solace::Concerns::BinarySerializable
|
6
|
+
|
7
|
+
# Parse instruction from io stream
|
8
|
+
#
|
9
|
+
# @param io [IO or StringIO] The input to read bytes from.
|
10
|
+
# @return [Solace::Instruction] Parsed instruction object
|
11
|
+
def self.deserialize(io)
|
12
|
+
self::DESERIALIZER.call(io)
|
13
|
+
rescue NameError => e
|
14
|
+
raise "DESERIALIZER must be defined: #{e.message}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Serializes the transaction to a binary format
|
18
|
+
#
|
19
|
+
# @return [String] The serialized transaction (binary)
|
20
|
+
def serialize
|
21
|
+
self.class::SERIALIZER.call(self)
|
22
|
+
rescue NameError => e
|
23
|
+
raise "SERIALIZER must be defined: #{e.message}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Address Lookup Table Deserializer
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Deserializes a Solana address lookup table from a binary format.
|
8
|
+
module Solace
|
9
|
+
module Serializers
|
10
|
+
class AddressLookupTableDeserializer < Solace::Serializers::BaseDeserializer
|
11
|
+
# @!attribute record_class
|
12
|
+
# The class of the record being deserialized
|
13
|
+
#
|
14
|
+
# @return [Class] The class of the record
|
15
|
+
self.record_class = Solace::AddressLookupTable
|
16
|
+
|
17
|
+
# @!attribute steps
|
18
|
+
# An ordered list of methods to deserialize the address lookup table
|
19
|
+
#
|
20
|
+
# @return [Array] The steps to deserialize the address lookup table
|
21
|
+
self.steps = %i[
|
22
|
+
next_extract_account
|
23
|
+
next_extract_writable_indexes
|
24
|
+
next_extract_readonly_indexes
|
25
|
+
]
|
26
|
+
|
27
|
+
# Extract the account key from the transaction
|
28
|
+
#
|
29
|
+
# The BufferLayout is:
|
30
|
+
# - [Account key (32 bytes)]
|
31
|
+
#
|
32
|
+
# @return [String] The account key
|
33
|
+
def next_extract_account
|
34
|
+
record.account = Codecs.bytes_to_base58 io.read(32).bytes
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extract the writable indexes from the transaction
|
38
|
+
#
|
39
|
+
# The BufferLayout is:
|
40
|
+
# - [Number of writable indexes (compact u16)]
|
41
|
+
# - [Writable indexes (variable length u8)]
|
42
|
+
#
|
43
|
+
# @return [Array<Integer>] The writable indexes
|
44
|
+
def next_extract_writable_indexes
|
45
|
+
length, = Codecs.decode_compact_u16(io)
|
46
|
+
record.writable_indexes = io.read(length).unpack('C*')
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extract the readonly indexes from the transaction
|
50
|
+
#
|
51
|
+
# The BufferLayout is:
|
52
|
+
# - [Number of readonly indexes (compact u16)]
|
53
|
+
# - [Readonly indexes (variable length u8)]
|
54
|
+
#
|
55
|
+
# @return [Array<Integer>] The readonly indexes
|
56
|
+
def next_extract_readonly_indexes
|
57
|
+
length, = Codecs.decode_compact_u16(io)
|
58
|
+
record.readonly_indexes = io.read(length).unpack('C*')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Address Lookup Table Serializer
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Serializes a Solana address lookup table to a binary format.
|
8
|
+
module Solace
|
9
|
+
module Serializers
|
10
|
+
class AddressLookupTableSerializer < Solace::Serializers::BaseSerializer
|
11
|
+
# @!attribute steps
|
12
|
+
# An ordered list of methods to serialize the address lookup table
|
13
|
+
#
|
14
|
+
# @return [Array] The steps to serialize the address lookup table
|
15
|
+
self.steps = %i[
|
16
|
+
encode_account
|
17
|
+
encode_writable_indexes
|
18
|
+
encode_readonly_indexes
|
19
|
+
]
|
20
|
+
|
21
|
+
# Encodes the account of the address lookup table
|
22
|
+
#
|
23
|
+
# The BufferLayout is:
|
24
|
+
# - [Account key (32 bytes)]
|
25
|
+
#
|
26
|
+
# @return [Array<Integer>] The bytes of the encoded account
|
27
|
+
def encode_account
|
28
|
+
Codecs.base58_to_bytes(record.account)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Encodes the writable indexes of the address lookup table
|
32
|
+
#
|
33
|
+
# The BufferLayout is:
|
34
|
+
# - [Number of writable indexes (compact u16)]
|
35
|
+
# - [Writable indexes (variable length u8)]
|
36
|
+
#
|
37
|
+
# @return [Array<Integer>] The bytes of the encoded writable indexes
|
38
|
+
def encode_writable_indexes
|
39
|
+
Codecs.encode_compact_u16(record.writable_indexes.size).bytes + record.writable_indexes
|
40
|
+
end
|
41
|
+
|
42
|
+
# Encodes the readonly indexes of the address lookup table
|
43
|
+
#
|
44
|
+
# The BufferLayout is:
|
45
|
+
# - [Number of readonly indexes (compact u16)]
|
46
|
+
# - [Readonly indexes (variable length u8)]
|
47
|
+
#
|
48
|
+
# @return [Array<Integer>] The bytes of the encoded readonly indexes
|
49
|
+
def encode_readonly_indexes
|
50
|
+
Codecs.encode_compact_u16(record.readonly_indexes.size).bytes + record.readonly_indexes
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Serializers
|
5
|
+
class Base
|
6
|
+
include Solace::Utils
|
7
|
+
|
8
|
+
# Proxy method to call the serializer and create a new instance
|
9
|
+
#
|
10
|
+
# @return [String] The serialized record (base64)
|
11
|
+
def self.call(*args, **kwargs)
|
12
|
+
new(*args, **kwargs).call
|
13
|
+
end
|
14
|
+
|
15
|
+
# Serializes the record
|
16
|
+
#
|
17
|
+
# @return [String] The serialized record (base64)
|
18
|
+
def call
|
19
|
+
bin = self.class::STEPS
|
20
|
+
.map { |m| send(m) }
|
21
|
+
.flatten
|
22
|
+
.compact
|
23
|
+
.pack('C*')
|
24
|
+
|
25
|
+
Base64.strict_encode64(bin)
|
26
|
+
rescue NameError => e
|
27
|
+
raise "STEPS must be defined: #{e.message}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Serializers
|
5
|
+
# Autoload deserializers
|
6
|
+
autoload :TransactionDeserializer, 'solace/serializers/transaction_deserializer'
|
7
|
+
autoload :MessageDeserializer, 'solace/serializers/message_deserializer'
|
8
|
+
autoload :InstructionDeserializer, 'solace/serializers/instruction_deserializer'
|
9
|
+
autoload :AddressLookupTableDeserializer, 'solace/serializers/address_lookup_table_deserializer'
|
10
|
+
|
11
|
+
# Base deserializer class
|
12
|
+
class BaseDeserializer < Serializers::Base
|
13
|
+
class << self
|
14
|
+
# @!attribute STEPS
|
15
|
+
# An ordered list of methods to deserialize the record
|
16
|
+
#
|
17
|
+
# @return [Array] The steps to deserialize the record
|
18
|
+
attr_accessor :steps
|
19
|
+
|
20
|
+
# @!attribute RECORD_CLASS
|
21
|
+
# The class of the record being deserialized
|
22
|
+
#
|
23
|
+
# @return [Class] The class of the record
|
24
|
+
attr_accessor :record_class
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!attribute io
|
28
|
+
# The input to read bytes from.
|
29
|
+
#
|
30
|
+
# @return [IO or StringIO] The input to read bytes from.
|
31
|
+
#
|
32
|
+
# @!attribute record
|
33
|
+
# The record instance being deserialized.
|
34
|
+
#
|
35
|
+
# @return [Record] The deserialized record.
|
36
|
+
attr_reader :io, :record
|
37
|
+
|
38
|
+
# Initialize a new deserializer
|
39
|
+
#
|
40
|
+
# @param io [IO or StringIO] The input to read bytes from.
|
41
|
+
# @return [BaseDeserializer] The new deserializer object
|
42
|
+
def initialize(io)
|
43
|
+
@io = io
|
44
|
+
@record = self.class.record_class.new
|
45
|
+
end
|
46
|
+
|
47
|
+
# Deserializes the record
|
48
|
+
#
|
49
|
+
# @return [Record] The deserialized record
|
50
|
+
def call
|
51
|
+
self.class.steps.each { send(_1) }
|
52
|
+
record
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Serializers
|
5
|
+
# Autoload serializers
|
6
|
+
autoload :TransactionSerializer, 'solace/serializers/transaction_serializer'
|
7
|
+
autoload :MessageSerializer, 'solace/serializers/message_serializer'
|
8
|
+
autoload :InstructionSerializer, 'solace/serializers/instruction_serializer'
|
9
|
+
autoload :AddressLookupTableSerializer, 'solace/serializers/address_lookup_table_serializer'
|
10
|
+
|
11
|
+
# Base serializer class
|
12
|
+
class BaseSerializer < Serializers::Base
|
13
|
+
class << self
|
14
|
+
# @!attribute STEPS
|
15
|
+
# An ordered list of methods to deserialize the record
|
16
|
+
#
|
17
|
+
# @return [Array] The steps to deserialize the record
|
18
|
+
attr_accessor :steps
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!attribute record
|
22
|
+
# The record instance being serialized.
|
23
|
+
#
|
24
|
+
# @return [Record] The serialized record.
|
25
|
+
attr_reader :record
|
26
|
+
|
27
|
+
# Initialize a new serializer
|
28
|
+
#
|
29
|
+
# @param record [Record] The record to serialize
|
30
|
+
# @return [BaseSerializer] The new serializer object
|
31
|
+
def initialize(record)
|
32
|
+
@record = record
|
33
|
+
end
|
34
|
+
|
35
|
+
# Serializes the record
|
36
|
+
#
|
37
|
+
# @return [String] The serialized record (base64)
|
38
|
+
def call
|
39
|
+
bin = self.class
|
40
|
+
.steps
|
41
|
+
.map { |m| send(m) }
|
42
|
+
.flatten
|
43
|
+
.compact
|
44
|
+
.pack('C*')
|
45
|
+
|
46
|
+
Base64.strict_encode64(bin)
|
47
|
+
rescue NameError => e
|
48
|
+
raise "STEPS must be defined: #{e.message}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Instruction Deserializer
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Deserializes a binary instruction into a Solace::Instruction object.
|
8
|
+
module Solace
|
9
|
+
module Serializers
|
10
|
+
class InstructionDeserializer < Solace::Serializers::BaseDeserializer
|
11
|
+
# @!attribute record_class
|
12
|
+
# The class of the record being deserialized
|
13
|
+
#
|
14
|
+
# @return [Class] The class of the record
|
15
|
+
self.record_class = Solace::Instruction
|
16
|
+
|
17
|
+
# @!attribute steps
|
18
|
+
# An ordered list of methods to deserialize the instruction
|
19
|
+
#
|
20
|
+
# @return [Array] The steps to deserialize the instruction
|
21
|
+
self.steps = %i[
|
22
|
+
next_extract_program_index
|
23
|
+
next_extract_accounts
|
24
|
+
next_extract_data
|
25
|
+
]
|
26
|
+
|
27
|
+
# Extracts the program index from the instruction
|
28
|
+
#
|
29
|
+
# The BufferLayout is:
|
30
|
+
# - [Program index (1 byte)]
|
31
|
+
#
|
32
|
+
# @return [Integer] The program index
|
33
|
+
def next_extract_program_index
|
34
|
+
record.program_index = io.read(1).ord
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extracts the accounts from the instruction
|
38
|
+
#
|
39
|
+
# The BufferLayout is:
|
40
|
+
# - [Number of accounts (compact u16)]
|
41
|
+
# - [Accounts (variable length u8)]
|
42
|
+
#
|
43
|
+
# @return [Array] The accounts
|
44
|
+
def next_extract_accounts
|
45
|
+
length, = Codecs.decode_compact_u16(io)
|
46
|
+
record.accounts = io.read(length).unpack('C*')
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extracts the instruction data from the instruction
|
50
|
+
#
|
51
|
+
# The BufferLayout is:
|
52
|
+
# - [Number of data bytes (compact u16)]
|
53
|
+
# - [Data bytes (variable length u8)]
|
54
|
+
#
|
55
|
+
# @return [Array] The instruction data
|
56
|
+
def next_extract_data
|
57
|
+
length, = Codecs.decode_compact_u16(io)
|
58
|
+
record.data = io.read(length).unpack('C*')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Instruction Serializer
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Serializes a Solana instruction to a binary format.
|
8
|
+
module Solace
|
9
|
+
module Serializers
|
10
|
+
class InstructionSerializer < Solace::Serializers::BaseSerializer
|
11
|
+
# @!attribute steps
|
12
|
+
# An ordered list of methods to serialize the instruction
|
13
|
+
#
|
14
|
+
# @return [Array] The steps to serialize the instruction
|
15
|
+
self.steps = %i[
|
16
|
+
encode_program_index
|
17
|
+
encode_accounts
|
18
|
+
encode_data
|
19
|
+
]
|
20
|
+
|
21
|
+
# Encodes the program index of the instruction
|
22
|
+
#
|
23
|
+
# The BufferLayout is:
|
24
|
+
# - [Program index (u8)]
|
25
|
+
#
|
26
|
+
# @return [Integer] The bytes of the encoded program index
|
27
|
+
def encode_program_index
|
28
|
+
record.program_index
|
29
|
+
end
|
30
|
+
|
31
|
+
# Encodes the accounts of the instruction
|
32
|
+
#
|
33
|
+
# The BufferLayout is:
|
34
|
+
# - [Number of accounts (compact u16)]
|
35
|
+
# - [Accounts (variable length u8)]
|
36
|
+
#
|
37
|
+
# @return [Array<Integer>] The bytes of the encoded accounts
|
38
|
+
def encode_accounts
|
39
|
+
Codecs.encode_compact_u16(record.accounts.size).bytes + record.accounts
|
40
|
+
end
|
41
|
+
|
42
|
+
# Encodes the data of the instruction
|
43
|
+
#
|
44
|
+
# The BufferLayout is:
|
45
|
+
# - [Number of data bytes (compact u16)]
|
46
|
+
# - [Data bytes (variable length u8)]
|
47
|
+
#
|
48
|
+
# @return [Array<Integer>] The bytes of the encoded data
|
49
|
+
def encode_data
|
50
|
+
Codecs.encode_compact_u16(record.data.size).bytes + record.data
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Message Deserializer
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Deserializes a binary message into a Solace::Message object.
|
8
|
+
module Solace
|
9
|
+
module Serializers
|
10
|
+
class MessageDeserializer < Solace::Serializers::BaseDeserializer
|
11
|
+
# @!attribute record_class
|
12
|
+
# The class of the record being deserialized
|
13
|
+
#
|
14
|
+
# @return [Class] The class of the record
|
15
|
+
self.record_class = Solace::Message
|
16
|
+
|
17
|
+
# @!attribute steps
|
18
|
+
# An ordered list of methods to deserialize the message
|
19
|
+
#
|
20
|
+
# @return [Array] The steps to deserialize the message
|
21
|
+
self.steps = %i[
|
22
|
+
next_extract_version
|
23
|
+
next_extract_message_header
|
24
|
+
next_extract_accounts
|
25
|
+
next_extract_recent_blockhash
|
26
|
+
next_extract_instructions
|
27
|
+
next_extract_address_lookup_table
|
28
|
+
]
|
29
|
+
|
30
|
+
# Extract version from the message
|
31
|
+
#
|
32
|
+
# Checks for version prefix and extracts version. If the prefix is not found, it
|
33
|
+
# assumes a legacy message and sets no version.
|
34
|
+
#
|
35
|
+
# The BufferLayout is:
|
36
|
+
# - [Version prefix (1 byte)]
|
37
|
+
# - [Version (variable length)]
|
38
|
+
#
|
39
|
+
# @return [Integer] The version of the message
|
40
|
+
def next_extract_version
|
41
|
+
next_byte = io.read(1).unpack1('C')
|
42
|
+
|
43
|
+
if next_byte & 0x80 == 0x80
|
44
|
+
record.version = next_byte & 0x7F
|
45
|
+
else
|
46
|
+
io.seek(-1, IO::SEEK_CUR)
|
47
|
+
record.version = nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Extract message header from the message
|
52
|
+
#
|
53
|
+
# The BufferLayout is:
|
54
|
+
# - [Message header (3 bytes)]
|
55
|
+
#
|
56
|
+
# @return [Array<Integer>] The message header of the message
|
57
|
+
def next_extract_message_header
|
58
|
+
record.header = io.read(3).bytes
|
59
|
+
end
|
60
|
+
|
61
|
+
# Extract account keys from the message
|
62
|
+
#
|
63
|
+
# The BufferLayout is:
|
64
|
+
# - [Number of accounts (compact u16)]
|
65
|
+
# - [Accounts (variable length u8)]
|
66
|
+
#
|
67
|
+
# @return [Array<String>] The account keys of the message
|
68
|
+
def next_extract_accounts
|
69
|
+
count, = Codecs.decode_compact_u16(io)
|
70
|
+
record.accounts = count.times.map do
|
71
|
+
Codecs.bytes_to_base58 io.read(32).bytes
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Extract recent blockhash from the message
|
76
|
+
#
|
77
|
+
# The BufferLayout is:
|
78
|
+
# - [Recent blockhash (32 bytes)]
|
79
|
+
#
|
80
|
+
# @return [String] The recent blockhash of the message
|
81
|
+
def next_extract_recent_blockhash
|
82
|
+
record.recent_blockhash = Codecs.bytes_to_base58 io.read(32).bytes
|
83
|
+
end
|
84
|
+
|
85
|
+
# Extract instructions from the message
|
86
|
+
#
|
87
|
+
# The BufferLayout is:
|
88
|
+
# - [Number of instructions (compact u16)]
|
89
|
+
# - [Instructions (variable length)]
|
90
|
+
#
|
91
|
+
# @return [Array<Solace::Instruction>] The instructions of the message
|
92
|
+
def next_extract_instructions
|
93
|
+
count, = Codecs.decode_compact_u16(io)
|
94
|
+
record.instructions = count.times.map do
|
95
|
+
Solace::Instruction.deserialize(io)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Extract address lookup table from the message
|
100
|
+
#
|
101
|
+
# The BufferLayout is:
|
102
|
+
# - [Number of address lookup tables (compact u16)]
|
103
|
+
# - [Address lookup tables (variable length)]
|
104
|
+
#
|
105
|
+
# @return [Array<Solace::AddressLookupTable>] The address lookup table of the message
|
106
|
+
def next_extract_address_lookup_table
|
107
|
+
return unless record.versioned?
|
108
|
+
|
109
|
+
count, = Codecs.decode_compact_u16(io)
|
110
|
+
record.address_lookup_tables = count.times.map do
|
111
|
+
Solace::AddressLookupTable.deserialize(io)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Message Serializer
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Serializes a Solana message to a binary format.
|
8
|
+
module Solace
|
9
|
+
module Serializers
|
10
|
+
class MessageSerializer < Solace::Serializers::BaseSerializer
|
11
|
+
# @!attribute steps
|
12
|
+
# An ordered list of methods to serialize the message
|
13
|
+
#
|
14
|
+
# @return [Array] The steps to serialize the message
|
15
|
+
self.steps = %i[
|
16
|
+
encode_version
|
17
|
+
encode_message_header
|
18
|
+
encode_accounts
|
19
|
+
encode_recent_blockhash
|
20
|
+
encode_instructions
|
21
|
+
encode_address_lookup_table
|
22
|
+
]
|
23
|
+
|
24
|
+
# Encodes the version of the message
|
25
|
+
#
|
26
|
+
# The BufferLayout is:
|
27
|
+
# - [Version (1 byte)]
|
28
|
+
#
|
29
|
+
# @return [Array<Integer>] | nil The bytes of the encoded version
|
30
|
+
def encode_version
|
31
|
+
[0x80 | record.version] if record.versioned?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Encodes the message header of the transaction
|
35
|
+
#
|
36
|
+
# The BufferLayout is:
|
37
|
+
# - [Message header (3 bytes)]
|
38
|
+
#
|
39
|
+
# @return [Array<Integer>] The bytes of the encoded message header
|
40
|
+
def encode_message_header
|
41
|
+
record.header
|
42
|
+
end
|
43
|
+
|
44
|
+
# Encodes the accounts of the transaction
|
45
|
+
#
|
46
|
+
# The BufferLayout is:
|
47
|
+
# - [Number of accounts (compact u16)]
|
48
|
+
# - [Accounts (variable length)]
|
49
|
+
#
|
50
|
+
# @return [Array<Integer>] The bytes of the encoded accounts
|
51
|
+
def encode_accounts
|
52
|
+
Codecs.encode_compact_u16(record.accounts.size).bytes +
|
53
|
+
record.accounts.map { Codecs.base58_to_bytes(_1) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Encodes the recent blockhash of the transaction
|
57
|
+
#
|
58
|
+
# The BufferLayout is:
|
59
|
+
# - [Recent blockhash (32 bytes)]
|
60
|
+
#
|
61
|
+
# @return [Array<Integer>] The bytes of the encoded recent blockhash
|
62
|
+
def encode_recent_blockhash
|
63
|
+
raise 'Failed to serialize message: recent blockhash is nil' if record.recent_blockhash.nil?
|
64
|
+
|
65
|
+
Codecs.base58_to_bytes(record.recent_blockhash)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Encodes the instructions of the transaction
|
69
|
+
#
|
70
|
+
# The BufferLayout is:
|
71
|
+
# - [Number of instructions (compact u16)]
|
72
|
+
# - [Instructions (variable length)]
|
73
|
+
#
|
74
|
+
# @return [Array<Integer>] The bytes of the encoded instructions
|
75
|
+
def encode_instructions
|
76
|
+
Codecs.encode_compact_u16(record.instructions.size).bytes +
|
77
|
+
record.instructions.map(&:to_bytes)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Encodes the address lookup table of the transaction
|
81
|
+
#
|
82
|
+
# The BufferLayout is:
|
83
|
+
# - [Number of address lookup tables (compact u16)]
|
84
|
+
# - [Address lookup tables (variable length)]
|
85
|
+
#
|
86
|
+
# @return [Array<Integer>] The bytes of the encoded address lookup table
|
87
|
+
def encode_address_lookup_table
|
88
|
+
return unless record.versioned?
|
89
|
+
|
90
|
+
Codecs.encode_compact_u16(record.address_lookup_tables.size).bytes +
|
91
|
+
record.address_lookup_tables.map(&:to_bytes)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|