solace 0.0.3 → 0.0.5
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 +4 -4
- data/CHANGELOG +12 -0
- data/README.md +140 -285
- data/lib/solace/address_lookup_table.rb +34 -18
- data/lib/solace/composers/base.rb +16 -11
- data/lib/solace/composers/spl_token_program_transfer_checked_composer.rb +27 -1
- data/lib/solace/composers/system_program_transfer_composer.rb +22 -1
- data/lib/solace/concerns/binary_serializable.rb +39 -0
- data/lib/solace/connection.rb +69 -38
- data/lib/solace/constants.rb +7 -14
- data/lib/solace/instruction.rb +30 -19
- data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +18 -3
- data/lib/solace/instructions/spl_token/initialize_account_instruction.rb +24 -3
- data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +18 -1
- data/lib/solace/instructions/spl_token/mint_to_instruction.rb +16 -3
- data/lib/solace/instructions/spl_token/transfer_checked_instruction.rb +17 -1
- data/lib/solace/instructions/spl_token/transfer_instruction.rb +15 -2
- data/lib/solace/instructions/system_program/create_account_instruction.rb +18 -3
- data/lib/solace/instructions/system_program/transfer_instruction.rb +15 -7
- data/lib/solace/keypair.rb +64 -31
- data/lib/solace/message.rb +22 -10
- data/lib/solace/programs/associated_token_account.rb +45 -20
- data/lib/solace/programs/base.rb +6 -0
- data/lib/solace/programs/spl_token.rb +52 -14
- data/lib/solace/public_key.rb +45 -20
- data/lib/solace/serializers/address_lookup_table_deserializer.rb +3 -5
- data/lib/solace/serializers/address_lookup_table_serializer.rb +7 -7
- data/lib/solace/serializers/base_deserializer.rb +29 -19
- data/lib/solace/serializers/base_serializer.rb +18 -9
- data/lib/solace/serializers/instruction_deserializer.rb +5 -7
- data/lib/solace/serializers/instruction_serializer.rb +4 -6
- data/lib/solace/serializers/message_deserializer.rb +3 -5
- data/lib/solace/serializers/message_serializer.rb +3 -5
- data/lib/solace/serializers/transaction_deserializer.rb +5 -7
- data/lib/solace/serializers/transaction_serializer.rb +5 -7
- data/lib/solace/transaction.rb +38 -23
- data/lib/solace/transaction_composer.rb +47 -13
- data/lib/solace/utils/account_context.rb +64 -65
- data/lib/solace/utils/codecs.rb +56 -128
- data/lib/solace/utils/curve25519_dalek.rb +9 -4
- data/lib/solace/utils/pda.rb +22 -24
- data/lib/solace/version.rb +2 -1
- data/lib/solace.rb +4 -9
- metadata +7 -10
- data/lib/solace/instructions/base.rb +0 -21
- data/lib/solace/serializable_record.rb +0 -26
- data/lib/solace/serializers/base.rb +0 -31
@@ -2,32 +2,57 @@
|
|
2
2
|
|
3
3
|
module Solace
|
4
4
|
module Utils
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# Utility for managing account context for composers
|
6
|
+
#
|
7
|
+
# This utility is used to manage the accounts in a transaction composer and instructions composer. It
|
8
|
+
# provides methods for managing the accounts and their permissions, as well as compiling the accounts
|
9
|
+
# into the final format required by the instruction builders. Concerns like deduplication and ordering
|
10
|
+
# are handled by this utility.
|
11
|
+
#
|
12
|
+
# @example Usage
|
13
|
+
# # Create a new account context
|
14
|
+
# context = Solace::Utils::AccountContext.new
|
15
|
+
#
|
16
|
+
# # Add accounts
|
17
|
+
# context.add_writable_signer('pubkey1')
|
18
|
+
# context.add_readonly_nonsigner('pubkey2')
|
19
|
+
#
|
20
|
+
# # Merge accounts from another context
|
21
|
+
# context = context.merge_from(other_context)
|
22
|
+
#
|
23
|
+
# # Set fee payer
|
24
|
+
# context.set_fee_payer('pubkey3')
|
25
|
+
#
|
26
|
+
# # Compile the accounts
|
27
|
+
# context.compile
|
28
|
+
#
|
29
|
+
# @see Solace::TransactionComposer
|
30
|
+
# @see Solace::Composers::Base
|
31
|
+
# @since 0.0.3
|
7
32
|
class AccountContext
|
8
|
-
# @!attribute
|
33
|
+
# @!attribute DEFAULT_ACCOUNT
|
9
34
|
# The default account data
|
10
35
|
#
|
11
36
|
# @return [Hash] The default account data with lowest level of permissions
|
12
37
|
DEFAULT_ACCOUNT = {
|
13
38
|
signer: false,
|
14
39
|
writable: false,
|
15
|
-
fee_payer: false
|
16
|
-
}
|
17
|
-
|
18
|
-
# @!attribute
|
40
|
+
fee_payer: false
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
# @!attribute header
|
19
44
|
# The header for the transaction
|
20
45
|
#
|
21
46
|
# @return [Array<Integer>] The header for the transaction
|
22
47
|
attr_accessor :header
|
23
|
-
|
24
|
-
# @!attribute
|
48
|
+
|
49
|
+
# @!attribute accounts
|
25
50
|
# The accounts in the transaction
|
26
51
|
#
|
27
52
|
# @return [Array<String>] The accounts
|
28
53
|
attr_accessor :accounts
|
29
54
|
|
30
|
-
# @!attribute
|
55
|
+
# @!attribute pubkey_account_map
|
31
56
|
# The map of accounts
|
32
57
|
#
|
33
58
|
# @return [Hash] The map of accounts
|
@@ -42,36 +67,35 @@ module Solace
|
|
42
67
|
|
43
68
|
# Set the fee payer account
|
44
69
|
#
|
45
|
-
# @param pubkey [Solace::Keypair
|
70
|
+
# @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the fee payer account
|
46
71
|
def set_fee_payer(pubkey)
|
47
72
|
merge_account(pubkey, signer: true, writable: true, fee_payer: true)
|
48
73
|
end
|
49
74
|
|
50
75
|
# Add a signer account
|
51
76
|
#
|
52
|
-
# @param pubkey [Solace::Keypair
|
77
|
+
# @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the signer account
|
53
78
|
def add_writable_signer(pubkey)
|
54
79
|
merge_account(pubkey, signer: true, writable: true)
|
55
80
|
end
|
56
81
|
|
57
82
|
# Add a writable account
|
58
83
|
#
|
59
|
-
# @param pubkey [Solace::Keypair
|
84
|
+
# @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the writable account
|
60
85
|
def add_writable_nonsigner(pubkey)
|
61
86
|
merge_account(pubkey, signer: false, writable: true)
|
62
87
|
end
|
63
|
-
|
88
|
+
|
64
89
|
# Add a readonly signer account
|
65
90
|
#
|
66
|
-
# @param pubkey [Solace::Keypair
|
91
|
+
# @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the readonly signer account
|
67
92
|
def add_readonly_signer(pubkey)
|
68
93
|
merge_account(pubkey, signer: true, writable: false)
|
69
94
|
end
|
70
|
-
|
71
95
|
|
72
96
|
# Add a readonly account
|
73
97
|
#
|
74
|
-
# @param pubkey [Solace::Keypair
|
98
|
+
# @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the readonly account
|
75
99
|
def add_readonly_nonsigner(pubkey)
|
76
100
|
merge_account(pubkey, signer: false, writable: false)
|
77
101
|
end
|
@@ -99,7 +123,7 @@ module Solace
|
|
99
123
|
def writable?(pubkey)
|
100
124
|
@pubkey_account_map[pubkey].try { |acc| acc[:writable] }
|
101
125
|
end
|
102
|
-
|
126
|
+
|
103
127
|
# Predicate to check if an account is a writable signer
|
104
128
|
#
|
105
129
|
# @param pubkey [String] The pubkey of the account
|
@@ -107,7 +131,7 @@ module Solace
|
|
107
131
|
def writable_signer?(pubkey)
|
108
132
|
@pubkey_account_map[pubkey].try { |acc| acc[:signer] && acc[:writable] }
|
109
133
|
end
|
110
|
-
|
134
|
+
|
111
135
|
# Predicate to check if an account is writable and not a signer
|
112
136
|
#
|
113
137
|
# @param pubkey [String] The pubkey of the account
|
@@ -131,23 +155,19 @@ module Solace
|
|
131
155
|
def readonly_nonsigner?(pubkey)
|
132
156
|
@pubkey_account_map[pubkey].try { |acc| !acc[:signer] && !acc[:writable] }
|
133
157
|
end
|
134
|
-
|
158
|
+
|
135
159
|
# Merge all accounts from another AccountContext into this one
|
136
160
|
#
|
137
161
|
# @param other_context [AccountContext] The other context to merge from
|
138
162
|
def merge_from(other_context)
|
139
163
|
other_context.pubkey_account_map.each do |pubkey, data|
|
140
|
-
|
141
|
-
|
142
|
-
signer: data[:signer],
|
143
|
-
writable: data[:writable],
|
144
|
-
fee_payer: data[:fee_payer]
|
145
|
-
)
|
164
|
+
signer, writable, fee_payer = data.values_at(:signer, :writable, :fee_payer)
|
165
|
+
merge_account(pubkey, signer: signer, writable: writable, fee_payer: fee_payer)
|
146
166
|
end
|
147
167
|
end
|
148
168
|
|
149
169
|
# Compile accounts into final format
|
150
|
-
#
|
170
|
+
#
|
151
171
|
# Gets unique accounts and sorts them in the following order:
|
152
172
|
# - Signers first (Solana requirement)
|
153
173
|
# - Then writable accounts
|
@@ -156,10 +176,10 @@ module Solace
|
|
156
176
|
# @return [Hash] The compiled accounts and header
|
157
177
|
def compile
|
158
178
|
self.header = calculate_header
|
159
|
-
self.accounts = order_accounts
|
179
|
+
self.accounts = order_accounts
|
160
180
|
self
|
161
181
|
end
|
162
|
-
|
182
|
+
|
163
183
|
# Index of a pubkey in the accounts array
|
164
184
|
#
|
165
185
|
# @param pubkey_str [String] The public key of the account
|
@@ -170,7 +190,7 @@ module Solace
|
|
170
190
|
|
171
191
|
# Get map of indicies for pubkeys in accounts array
|
172
192
|
#
|
173
|
-
# @return [Hash
|
193
|
+
# @return [Hash{String => Integer}] The indices of the pubkeys in the accounts array
|
174
194
|
def indices
|
175
195
|
accounts.each_with_index.to_h
|
176
196
|
end
|
@@ -179,22 +199,18 @@ module Solace
|
|
179
199
|
|
180
200
|
# Add or merge an account into the context
|
181
201
|
#
|
182
|
-
# @param pubkey [String
|
202
|
+
# @param pubkey [String, Solace::PublicKey, Solace::Keypair] The public key of the account
|
183
203
|
# @param signer [Boolean] Whether the account is a signer
|
184
204
|
# @param writable [Boolean] Whether the account is writable
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
writable:,
|
189
|
-
fee_payer: false
|
190
|
-
)
|
191
|
-
pubkey_str = resolve_pubkey(pubkey)
|
205
|
+
# @param [Boolean] fee_payer
|
206
|
+
def merge_account(pubkey, signer:, writable:, fee_payer: false)
|
207
|
+
pubkey_str = pubkey.is_a?(String) ? pubkey : pubkey.address
|
192
208
|
|
193
209
|
@pubkey_account_map[pubkey_str] ||= DEFAULT_ACCOUNT.dup
|
194
210
|
@pubkey_account_map[pubkey_str][:signer] ||= signer
|
195
211
|
@pubkey_account_map[pubkey_str][:writable] ||= writable
|
196
212
|
@pubkey_account_map[pubkey_str][:fee_payer] ||= fee_payer
|
197
|
-
|
213
|
+
|
198
214
|
self
|
199
215
|
end
|
200
216
|
|
@@ -203,16 +219,11 @@ module Solace
|
|
203
219
|
# @return [Array<String>] The ordered accounts
|
204
220
|
def order_accounts
|
205
221
|
@pubkey_account_map.keys.sort_by do |pubkey|
|
206
|
-
if fee_payer?(pubkey)
|
207
|
-
|
208
|
-
elsif
|
209
|
-
|
210
|
-
elsif
|
211
|
-
2
|
212
|
-
elsif writable_nonsigner?(pubkey)
|
213
|
-
2
|
214
|
-
elsif readonly_nonsigner?(pubkey)
|
215
|
-
3
|
222
|
+
if fee_payer?(pubkey) then 0
|
223
|
+
elsif writable_signer?(pubkey) then 1
|
224
|
+
elsif readonly_signer?(pubkey) then 2
|
225
|
+
elsif writable_nonsigner?(pubkey) then 3
|
226
|
+
elsif readonly_nonsigner?(pubkey) then 4
|
216
227
|
else
|
217
228
|
raise ArgumentError, "Unknown account type for pubkey: #{pubkey}"
|
218
229
|
end
|
@@ -228,26 +239,14 @@ module Solace
|
|
228
239
|
#
|
229
240
|
# @return [Array] The header for the transaction
|
230
241
|
def calculate_header
|
231
|
-
@pubkey_account_map.keys.
|
242
|
+
@pubkey_account_map.keys.each_with_object([0, 0, 0]) do |pubkey, acc|
|
232
243
|
acc[0] += 1 if signer?(pubkey)
|
233
|
-
|
234
|
-
if readonly_signer?(pubkey)
|
235
|
-
acc[1] += 1
|
236
|
-
elsif readonly_nonsigner?(pubkey)
|
237
|
-
acc[2] += 1
|
238
|
-
end
|
239
244
|
|
240
|
-
acc
|
245
|
+
if readonly_signer?(pubkey) then acc[1] += 1
|
246
|
+
elsif readonly_nonsigner?(pubkey) then acc[2] += 1
|
247
|
+
end
|
241
248
|
end
|
242
249
|
end
|
243
|
-
|
244
|
-
# Resolve the pubkey from a Solace::PublicKey or String
|
245
|
-
#
|
246
|
-
# @param pubkey [String|Solace::PublicKey] The pubkey to resolve
|
247
|
-
# @return [String] The resolved pubkey
|
248
|
-
def resolve_pubkey(pubkey)
|
249
|
-
pubkey.is_a?(String) ? pubkey : pubkey.address
|
250
|
-
end
|
251
250
|
end
|
252
251
|
end
|
253
252
|
end
|
data/lib/solace/utils/codecs.rb
CHANGED
@@ -7,206 +7,134 @@ require 'stringio'
|
|
7
7
|
|
8
8
|
module Solace
|
9
9
|
module Utils
|
10
|
+
# Module for encoding and decoding data
|
11
|
+
#
|
12
|
+
# @since 0.0.1
|
10
13
|
module Codecs
|
11
|
-
# =============================
|
12
|
-
# Helper: IO Stream
|
13
|
-
# =============================
|
14
|
-
#
|
15
14
|
# Creates a StringIO from a base64 string.
|
16
15
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# Returns:
|
21
|
-
# StringIO: A StringIO object containing the decoded bytes
|
22
|
-
#
|
16
|
+
# @param base64 [String] The base64 string to decode
|
17
|
+
# @return [StringIO] A StringIO object containing the decoded bytes
|
23
18
|
def self.base64_to_bytestream(base64)
|
24
19
|
StringIO.new(Base64.decode64(base64))
|
25
20
|
end
|
26
21
|
|
27
|
-
#
|
28
|
-
# Helper: Compact-u16 Encoding (ShortVec)
|
29
|
-
# =============================
|
30
|
-
#
|
31
|
-
# Encodes a u16 value in a compact form
|
32
|
-
#
|
33
|
-
# Args:
|
34
|
-
# n (Integer): The u16 value to encode
|
22
|
+
# Encodes a compact-u16 value in a compact form (shortvec)
|
35
23
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
def self.encode_compact_u16(n)
|
24
|
+
# @param u16 [Integer] The compact-u16 value to encode
|
25
|
+
# @return [String] The compactly encoded compact-u16 value
|
26
|
+
def self.encode_compact_u16(u16)
|
40
27
|
out = []
|
28
|
+
|
41
29
|
loop do
|
42
30
|
# In general, n >> 7 shifts the bits of n to the right by
|
43
31
|
# 7 positions, effectively dividing n by 128 and discarding
|
44
32
|
# the remainder (integer division). This is commonly used in
|
45
33
|
# encoding schemes to process one "byte" (7 bits) at a time.
|
46
|
-
if (
|
47
|
-
out <<
|
34
|
+
if (u16 >> 7).zero?
|
35
|
+
out << u16
|
48
36
|
break
|
49
|
-
else
|
50
|
-
# The expression out << ((n & 0x7F) | 0x80) is used in variable-length
|
51
|
-
# integer encoding, such as the compact-u16 encoding.
|
52
|
-
#
|
53
|
-
# n & 0x7F:
|
54
|
-
# - 0x7F is 127 in decimal, or 0111 1111 in binary.
|
55
|
-
# - n & 0x7F masks out all but the lowest 7 bits of n. This extracts the least significant 7 bits of n.
|
56
|
-
#
|
57
|
-
# (n & 0x7F) | 0x80:
|
58
|
-
# - 0x80 is 128 in decimal, or 1000 0000 in binary.
|
59
|
-
# - | (bitwise OR) sets the highest bit (the 8th bit) to 1.
|
60
|
-
# - This is a signal that there are more bytes to come in the encoding (i.e., the value hasn't been fully encoded yet).
|
61
|
-
#
|
62
|
-
# out << ...:
|
63
|
-
# - This appends the resulting byte to the out array.
|
64
|
-
out << ((n & 0x7F) | 0x80)
|
65
|
-
n >>= 7
|
66
37
|
end
|
38
|
+
# The expression out << ((n & 0x7F) | 0x80) is used in variable-length
|
39
|
+
# integer encoding, such as the compact-u16 encoding.
|
40
|
+
#
|
41
|
+
# n & 0x7F:
|
42
|
+
# - 0x7F is 127 in decimal, or 0111 1111 in binary.
|
43
|
+
# - n & 0x7F masks out all but the lowest 7 bits of n. This extracts the least significant 7 bits of n.
|
44
|
+
#
|
45
|
+
# (n & 0x7F) | 0x80:
|
46
|
+
# - 0x80 is 128 in decimal, or 1000 0000 in binary.
|
47
|
+
# - | (bitwise OR) sets the highest bit (the 8th bit) to 1.
|
48
|
+
# - This is a signal that there are more bytes to come in the encoding (i.e., the value hasn't been fully
|
49
|
+
# encoded yet).
|
50
|
+
#
|
51
|
+
# out << ...:
|
52
|
+
# - This appends the resulting byte to the out array.
|
53
|
+
out << ((u16 & 0x7F) | 0x80)
|
54
|
+
u16 >>= 7
|
67
55
|
end
|
56
|
+
|
68
57
|
out.pack('C*')
|
69
58
|
end
|
70
59
|
|
71
|
-
# =============================
|
72
|
-
# Helper: Compact-u16 Decoding (ShortVec)
|
73
|
-
# =============================
|
74
|
-
#
|
75
60
|
# Decodes a compact-u16 (ShortVec) value from an IO-like object.
|
76
|
-
# Reads bytes one at a time, accumulating the result until the MSB is 0.
|
77
|
-
#
|
78
|
-
# Args:
|
79
|
-
# io (IO or StringIO): The input to read bytes from.
|
80
61
|
#
|
81
|
-
#
|
82
|
-
# [Integer, Integer]: The decoded value and the number of bytes read.
|
62
|
+
# Reads bytes one at a time, accumulating the result until the MSB is 0.
|
83
63
|
#
|
84
|
-
|
64
|
+
# @param stream [IO, StringIO] The input to read bytes from.
|
65
|
+
# @return [Integer, Integer] The decoded value and the number of bytes read.
|
66
|
+
def self.decode_compact_u16(stream)
|
85
67
|
value = 0
|
86
68
|
shift = 0
|
87
69
|
bytes_read = 0
|
70
|
+
|
88
71
|
loop do
|
89
|
-
byte =
|
72
|
+
byte = stream.read(1)
|
90
73
|
raise EOFError, 'Unexpected end of input while decoding compact-u16' unless byte
|
91
74
|
|
92
75
|
byte = byte.ord
|
93
76
|
value |= (byte & 0x7F) << shift
|
94
77
|
bytes_read += 1
|
95
|
-
break if (
|
78
|
+
break if byte.nobits?(0x80)
|
96
79
|
|
97
80
|
shift += 7
|
98
81
|
end
|
82
|
+
|
99
83
|
[value, bytes_read]
|
100
84
|
end
|
101
85
|
|
102
|
-
# =============================
|
103
|
-
# Helper: Little-Endian u64 Encoding
|
104
|
-
# =============================
|
105
|
-
#
|
106
86
|
# Encodes a u64 value in little-endian format
|
107
87
|
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
# String: The little-endian encoded u64 value
|
113
|
-
#
|
114
|
-
def self.encode_le_u64(n)
|
115
|
-
[n].pack('Q<') # 64-bit little-endian
|
88
|
+
# @param u64 [Integer] The u64 value to encode
|
89
|
+
# @return [String] The little-endian encoded u64 value
|
90
|
+
def self.encode_le_u64(u64)
|
91
|
+
[u64].pack('Q<') # 64-bit little-endian
|
116
92
|
end
|
117
93
|
|
118
|
-
# =============================
|
119
|
-
# Helper: Little-Endian u64 Decoding
|
120
|
-
# =============================
|
121
|
-
#
|
122
94
|
# Decodes a little-endian u64 value from a sequence of bytes
|
123
95
|
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
# Integer: The decoded u64 value
|
129
|
-
#
|
130
|
-
def self.decode_le_u64(io)
|
131
|
-
io.read(8).unpack1('Q<')
|
96
|
+
# @param stream [IO, StringIO] The input to read bytes from.
|
97
|
+
# @return [Integer] The decoded u64 value
|
98
|
+
def self.decode_le_u64(stream)
|
99
|
+
stream.read(8).unpack1('Q<')
|
132
100
|
end
|
133
101
|
|
134
|
-
# =============================
|
135
|
-
# Helper: Binary to Base58 Encoding
|
136
|
-
# =============================
|
137
|
-
#
|
138
102
|
# Encodes a sequence of bytes in Base58 format
|
139
103
|
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
# Returns:
|
144
|
-
# String: The Base58 encoded string
|
145
|
-
#
|
104
|
+
# @param binary [String] The bytes to encode
|
105
|
+
# @return [String] The Base58 encoded string
|
146
106
|
def self.binary_to_base58(binary)
|
147
107
|
Base58.binary_to_base58(binary, :bitcoin)
|
148
108
|
end
|
149
109
|
|
150
|
-
# =============================
|
151
|
-
# Helper: Base58 Decoding
|
152
|
-
# =============================
|
153
|
-
#
|
154
110
|
# Decodes a Base58 string into a binary string
|
155
111
|
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# Returns:
|
160
|
-
# String: The decoded binary string
|
161
|
-
#
|
112
|
+
# @param string [String] The Base58 encoded string
|
113
|
+
# @return [String] The decoded binary string
|
162
114
|
def self.base58_to_binary(string)
|
163
115
|
base58_to_bytes(string).pack('C*')
|
164
116
|
end
|
165
117
|
|
166
|
-
# =============================
|
167
|
-
# Helper: Base58 Encoding
|
168
|
-
# =============================
|
169
|
-
#
|
170
118
|
# Encodes a sequence of bytes in Base58 format
|
171
119
|
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
# Returns:
|
176
|
-
# String: The Base58 encoded string
|
177
|
-
#
|
120
|
+
# @param bytes [String] The bytes to encode
|
121
|
+
# @return [String] The Base58 encoded string
|
178
122
|
def self.bytes_to_base58(bytes)
|
179
123
|
binary_to_base58(bytes.pack('C*'))
|
180
124
|
end
|
181
125
|
|
182
|
-
# =============================
|
183
|
-
# Helper: Base58 Decoding
|
184
|
-
# =============================
|
185
|
-
#
|
186
126
|
# Decodes a Base58 string into a sequence of bytes
|
187
127
|
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
# Returns:
|
192
|
-
# String: The decoded bytes
|
193
|
-
#
|
128
|
+
# @param string [String] The Base58 encoded string
|
129
|
+
# @return [String] The decoded bytes
|
194
130
|
def self.base58_to_bytes(string)
|
195
131
|
Base58.base58_to_binary(string, :bitcoin).bytes
|
196
132
|
end
|
197
133
|
|
198
|
-
# =============================
|
199
|
-
# Helper: Base58 Validation
|
200
|
-
# =============================
|
201
|
-
#
|
202
134
|
# Checks if a string is a valid Base58 string
|
203
135
|
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
# Returns:
|
208
|
-
# Boolean: True if the string is a valid Base58 string, false otherwise
|
209
|
-
#
|
136
|
+
# @param string [String] The string to check
|
137
|
+
# @return [Boolean] True if the string is a valid Base58 string, false otherwise
|
210
138
|
def self.valid_base58?(string)
|
211
139
|
return false if string.nil? || string.empty?
|
212
140
|
|
@@ -5,6 +5,14 @@ require 'ffi'
|
|
5
5
|
|
6
6
|
module Solace
|
7
7
|
module Utils
|
8
|
+
# Module for interacting with the Curve25519 Dalek library
|
9
|
+
#
|
10
|
+
# This module provides a wrapper around the Curve25519 Dalek library, which is a pure-Rust
|
11
|
+
# implementation of the Curve25519 elliptic curve. It uses FFI to interface with the native
|
12
|
+
# rust library when checking if a point is on the curve.
|
13
|
+
#
|
14
|
+
# @see Solace::Utils::PDA
|
15
|
+
# @since 0.0.2
|
8
16
|
module Curve25519Dalek
|
9
17
|
extend FFI::Library
|
10
18
|
|
@@ -22,8 +30,7 @@ module Solace
|
|
22
30
|
else raise 'Unsupported platform'
|
23
31
|
end
|
24
32
|
|
25
|
-
#
|
26
|
-
#
|
33
|
+
# !@attribute LIB_PATH
|
27
34
|
# @return [String] The path to the native library
|
28
35
|
LIB_PATH = File.expand_path(libfile, __dir__)
|
29
36
|
|
@@ -31,8 +38,6 @@ module Solace
|
|
31
38
|
ffi_lib LIB_PATH
|
32
39
|
|
33
40
|
# Attach the native function
|
34
|
-
#
|
35
|
-
# @return [FFI::Function] The native function
|
36
41
|
attach_function :is_on_curve, [:pointer], :int
|
37
42
|
|
38
43
|
# Checks if a point is on the curve
|
data/lib/solace/utils/pda.rb
CHANGED
@@ -5,27 +5,34 @@ require 'digest'
|
|
5
5
|
|
6
6
|
module Solace
|
7
7
|
module Utils
|
8
|
+
# Module for generating program addresses
|
9
|
+
#
|
10
|
+
# This module provides methods for generating program addresses from seeds and program IDs. It interfaces
|
11
|
+
# with the Curve25519 Dalek library to check if a point is on the curve. It also provides a method for
|
12
|
+
# converting seeds to bytes and a method for checking if a string looks like a base58 address.
|
13
|
+
#
|
14
|
+
# @see Solace::Utils::Curve25519Dalek
|
15
|
+
# @since 0.0.1
|
8
16
|
module PDA
|
9
|
-
#
|
10
|
-
# An error raised when an invalid PDA is generated
|
11
|
-
#
|
12
|
-
# @return [StandardError]
|
17
|
+
# InvalidPDAError is an error raised when an invalid PDA is generated
|
13
18
|
class InvalidPDAError < StandardError; end
|
14
19
|
|
15
|
-
# !@
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# @return [String]
|
20
|
+
# !@attribute PDA_MARKER
|
21
|
+
# PDA_MARKER is the marker used in PDA calculations
|
19
22
|
PDA_MARKER = 'ProgramDerivedAddress'
|
20
23
|
|
21
|
-
# !@
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# @return [Integer]
|
24
|
+
# !@attribute MAX_BUMP_SEED
|
25
|
+
# The maximum seed value for PDA calculations
|
25
26
|
MAX_BUMP_SEED = 255
|
26
27
|
|
27
28
|
# Finds a valid program address by trying different seeds
|
28
29
|
#
|
30
|
+
# @example Find a PDA with bump seed
|
31
|
+
# seeds = ['metadata', mint_address, 'edition']
|
32
|
+
# program_id = 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
|
33
|
+
#
|
34
|
+
# address, bump = Solace::Utils::PDA.find_program_address(seeds, program_id)
|
35
|
+
#
|
29
36
|
# @param seeds [Array] The seeds to use in the calculation
|
30
37
|
# @param program_id [String] The program ID to use in the calculation
|
31
38
|
# @return [Array] The program address and bump seed
|
@@ -68,17 +75,9 @@ module Solace
|
|
68
75
|
def self.seed_to_bytes(seed)
|
69
76
|
case seed
|
70
77
|
when String
|
71
|
-
|
72
|
-
Solace::Utils::Codecs.base58_to_bytes(seed)
|
73
|
-
else
|
74
|
-
seed.bytes
|
75
|
-
end
|
78
|
+
looks_like_base58_address?(seed) ? Solace::Utils::Codecs.base58_to_bytes(seed) : seed.bytes
|
76
79
|
when Integer
|
77
|
-
|
78
|
-
[seed]
|
79
|
-
else
|
80
|
-
seed.digits(256)
|
81
|
-
end
|
80
|
+
seed.between?(0, 255) ? [seed] : seed.digits(256)
|
82
81
|
when Array
|
83
82
|
seed
|
84
83
|
else
|
@@ -91,8 +90,7 @@ module Solace
|
|
91
90
|
# @param string [String] The string to check
|
92
91
|
# @return [Boolean] True if the string looks like a base58 address, false otherwise
|
93
92
|
def self.looks_like_base58_address?(string)
|
94
|
-
string.length
|
95
|
-
string.length <= 44 &&
|
93
|
+
string.length.between?(32, 44) &&
|
96
94
|
Solace::Utils::Codecs.valid_base58?(string)
|
97
95
|
end
|
98
96
|
end
|
data/lib/solace/version.rb
CHANGED
data/lib/solace.rb
CHANGED
@@ -13,13 +13,9 @@ require_relative 'solace/utils/curve25519_dalek'
|
|
13
13
|
require_relative 'solace/concerns/binary_serializable'
|
14
14
|
|
15
15
|
# ✨ Serializers
|
16
|
-
require_relative 'solace/serializers/base'
|
17
16
|
require_relative 'solace/serializers/base_serializer'
|
18
17
|
require_relative 'solace/serializers/base_deserializer'
|
19
18
|
|
20
|
-
# Base classes
|
21
|
-
require_relative 'solace/serializable_record'
|
22
|
-
|
23
19
|
# 🧬 Primitives
|
24
20
|
require_relative 'solace/keypair'
|
25
21
|
require_relative 'solace/public_key'
|
@@ -30,17 +26,16 @@ require_relative 'solace/address_lookup_table'
|
|
30
26
|
require_relative 'solace/transaction_composer'
|
31
27
|
|
32
28
|
# 📦 Composers (Builders)
|
33
|
-
#
|
29
|
+
#
|
34
30
|
# Glob require all instructions
|
35
|
-
Dir[File.join(__dir__, 'solace/composers', '**', '*.rb')].
|
31
|
+
Dir[File.join(__dir__, 'solace/composers', '**', '*.rb')].each { |file| require file }
|
36
32
|
|
37
33
|
# 📦 Instructions (Builders)
|
38
|
-
#
|
34
|
+
#
|
39
35
|
# Glob require all instructions
|
40
|
-
Dir[File.join(__dir__, 'solace/instructions', '**', '*.rb')].
|
36
|
+
Dir[File.join(__dir__, 'solace/instructions', '**', '*.rb')].each { |file| require file }
|
41
37
|
|
42
38
|
# 📦 Programs
|
43
39
|
require_relative 'solace/programs/base'
|
44
40
|
require_relative 'solace/programs/spl_token'
|
45
41
|
require_relative 'solace/programs/associated_token_account'
|
46
|
-
|