solace 0.0.2 → 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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +57 -0
  3. data/LICENSE +21 -0
  4. data/README.md +142 -287
  5. data/lib/solace/address_lookup_table.rb +34 -18
  6. data/lib/solace/composers/base.rb +45 -0
  7. data/lib/solace/composers/spl_token_program_transfer_checked_composer.rb +113 -0
  8. data/lib/solace/composers/system_program_transfer_composer.rb +80 -0
  9. data/lib/solace/concerns/binary_serializable.rb +39 -0
  10. data/lib/solace/connection.rb +101 -44
  11. data/lib/solace/constants.rb +7 -14
  12. data/lib/solace/instruction.rb +30 -19
  13. data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +18 -3
  14. data/lib/solace/instructions/spl_token/initialize_account_instruction.rb +24 -3
  15. data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +18 -1
  16. data/lib/solace/instructions/spl_token/mint_to_instruction.rb +16 -3
  17. data/lib/solace/instructions/spl_token/transfer_checked_instruction.rb +76 -0
  18. data/lib/solace/instructions/spl_token/transfer_instruction.rb +15 -2
  19. data/lib/solace/instructions/system_program/create_account_instruction.rb +18 -3
  20. data/lib/solace/instructions/system_program/transfer_instruction.rb +58 -0
  21. data/lib/solace/keypair.rb +64 -31
  22. data/lib/solace/message.rb +22 -10
  23. data/lib/solace/programs/associated_token_account.rb +58 -11
  24. data/lib/solace/programs/base.rb +6 -0
  25. data/lib/solace/programs/spl_token.rb +52 -14
  26. data/lib/solace/public_key.rb +45 -20
  27. data/lib/solace/serializers/address_lookup_table_deserializer.rb +3 -5
  28. data/lib/solace/serializers/address_lookup_table_serializer.rb +7 -7
  29. data/lib/solace/serializers/base_deserializer.rb +29 -19
  30. data/lib/solace/serializers/base_serializer.rb +18 -9
  31. data/lib/solace/serializers/instruction_deserializer.rb +5 -7
  32. data/lib/solace/serializers/instruction_serializer.rb +4 -6
  33. data/lib/solace/serializers/message_deserializer.rb +3 -5
  34. data/lib/solace/serializers/message_serializer.rb +3 -5
  35. data/lib/solace/serializers/transaction_deserializer.rb +5 -7
  36. data/lib/solace/serializers/transaction_serializer.rb +5 -7
  37. data/lib/solace/transaction.rb +38 -23
  38. data/lib/solace/transaction_composer.rb +115 -0
  39. data/lib/solace/utils/account_context.rb +252 -0
  40. data/lib/solace/utils/codecs.rb +56 -128
  41. data/lib/solace/utils/curve25519_dalek.rb +9 -4
  42. data/lib/solace/utils/pda.rb +22 -24
  43. data/lib/solace/version.rb +2 -1
  44. data/lib/solace.rb +9 -7
  45. metadata +15 -12
  46. data/lib/solace/instructions/transfer_checked_instruction.rb +0 -58
  47. data/lib/solace/instructions/transfer_instruction.rb +0 -48
  48. data/lib/solace/serializable_record.rb +0 -26
  49. data/lib/solace/serializers/base.rb +0 -31
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/solace/transaction_composer.rb
4
+ module Solace
5
+ # Composes multi-instruction transactions with automatic account management
6
+ #
7
+ # @example
8
+ # # Initialize a transaction composer
9
+ # composer = Solace::TransactionComposer.new(connection: connection)
10
+ #
11
+ # # Add an instruction composer
12
+ # composer.add_instruction(
13
+ # Solace::Composers::SystemProgramTransferComposer.new(
14
+ # to: 'pubkey1',
15
+ # from: 'pubkey2',
16
+ # lamports: 100
17
+ # )
18
+ # )
19
+ #
20
+ # # Add another instruction composer
21
+ # composer.add_instruction(
22
+ # Solace::Composers::SplTokenProgramTransferCheckedComposer.new(
23
+ # from: 'pubkey4',
24
+ # to: 'pubkey5',
25
+ # mint: 'pubkey6',
26
+ # authority: 'pubkey7',
27
+ # amount: 1_000_000,
28
+ # decimals: 6
29
+ # )
30
+ # )
31
+ #
32
+ # # Set the fee payer
33
+ # composer.set_fee_payer('pubkey8')
34
+ #
35
+ # # Compose the transaction
36
+ # tx = composer.compose_transaction
37
+ #
38
+ # # Sign the transaction with all required signers
39
+ # tx.sign(*required_signers)
40
+ #
41
+ # @since 0.0.1
42
+ class TransactionComposer
43
+ # @!attribute connection
44
+ # The connection to the Solana cluster
45
+ attr_reader :connection
46
+
47
+ # @!attribute context
48
+ # The account context
49
+ attr_reader :context
50
+
51
+ # @!attribute instruction_composers
52
+ # The instruction composers
53
+ attr_reader :instruction_composers
54
+
55
+ # Initialize the composer
56
+ #
57
+ # @param connection [Solace::Connection] The connection to the Solana cluster
58
+ def initialize(connection:)
59
+ @connection = connection
60
+ @instruction_composers = []
61
+ @context = Utils::AccountContext.new
62
+ end
63
+
64
+ # Add an instruction composer to the transaction
65
+ #
66
+ # @param composer [Composers::Base] The instruction composer
67
+ # @return [TransactionComposer] Self for chaining
68
+ def add_instruction(composer)
69
+ merge_accounts(composer.account_context)
70
+ instruction_composers << composer
71
+ self
72
+ end
73
+
74
+ # Set the fee payer for the transaction
75
+ #
76
+ # @param pubkey [String, Solace::PublicKey, Solace::Keypair] The fee payer pubkey
77
+ # @return [TransactionComposer] Self for chaining
78
+ def set_fee_payer(pubkey)
79
+ context.set_fee_payer(pubkey)
80
+ self
81
+ end
82
+
83
+ # Compose the final transaction
84
+ #
85
+ # @return [Solace::Transaction] The composed transaction (unsigned)
86
+ def compose_transaction
87
+ context.compile
88
+
89
+ message = Solace::Message.new(
90
+ header: context.header,
91
+ accounts: context.accounts,
92
+ instructions: build_instructions,
93
+ recent_blockhash: connection.get_latest_blockhash
94
+ )
95
+
96
+ Solace::Transaction.new(message: message)
97
+ end
98
+
99
+ private
100
+
101
+ # Build all instructions with resolved indices
102
+ #
103
+ # @return [Array<Solace::Instruction>] The built instructions
104
+ def build_instructions
105
+ instruction_composers.map { _1.build_instruction(context) }
106
+ end
107
+
108
+ # Merge all accounts from another AccountContext into this one
109
+ #
110
+ # @param account_context [AccountContext] The other context to merge from
111
+ def merge_accounts(account_context)
112
+ context.merge_from(account_context)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Utils
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
32
+ class AccountContext
33
+ # @!attribute DEFAULT_ACCOUNT
34
+ # The default account data
35
+ #
36
+ # @return [Hash] The default account data with lowest level of permissions
37
+ DEFAULT_ACCOUNT = {
38
+ signer: false,
39
+ writable: false,
40
+ fee_payer: false
41
+ }.freeze
42
+
43
+ # @!attribute header
44
+ # The header for the transaction
45
+ #
46
+ # @return [Array<Integer>] The header for the transaction
47
+ attr_accessor :header
48
+
49
+ # @!attribute accounts
50
+ # The accounts in the transaction
51
+ #
52
+ # @return [Array<String>] The accounts
53
+ attr_accessor :accounts
54
+
55
+ # @!attribute pubkey_account_map
56
+ # The map of accounts
57
+ #
58
+ # @return [Hash] The map of accounts
59
+ attr_accessor :pubkey_account_map
60
+
61
+ # Initialize the account context
62
+ def initialize
63
+ @header = []
64
+ @accounts = []
65
+ @pubkey_account_map = {}
66
+ end
67
+
68
+ # Set the fee payer account
69
+ #
70
+ # @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the fee payer account
71
+ def set_fee_payer(pubkey)
72
+ merge_account(pubkey, signer: true, writable: true, fee_payer: true)
73
+ end
74
+
75
+ # Add a signer account
76
+ #
77
+ # @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the signer account
78
+ def add_writable_signer(pubkey)
79
+ merge_account(pubkey, signer: true, writable: true)
80
+ end
81
+
82
+ # Add a writable account
83
+ #
84
+ # @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the writable account
85
+ def add_writable_nonsigner(pubkey)
86
+ merge_account(pubkey, signer: false, writable: true)
87
+ end
88
+
89
+ # Add a readonly signer account
90
+ #
91
+ # @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the readonly signer account
92
+ def add_readonly_signer(pubkey)
93
+ merge_account(pubkey, signer: true, writable: false)
94
+ end
95
+
96
+ # Add a readonly account
97
+ #
98
+ # @param pubkey [Solace::Keypair, Solace::PublicKey, String] The pubkey of the readonly account
99
+ def add_readonly_nonsigner(pubkey)
100
+ merge_account(pubkey, signer: false, writable: false)
101
+ end
102
+
103
+ # Predicate to check if an account is a fee payer
104
+ #
105
+ # @param pubkey [String] The pubkey of the account
106
+ # @return [Boolean] Whether the account is a fee payer
107
+ def fee_payer?(pubkey)
108
+ @pubkey_account_map[pubkey].try { |acc| acc[:fee_payer] }
109
+ end
110
+
111
+ # Predicate to check if an account is a signer
112
+ #
113
+ # @param pubkey [String] The pubkey of the account
114
+ # @return [Boolean] Whether the account is a signer
115
+ def signer?(pubkey)
116
+ @pubkey_account_map[pubkey].try { |acc| acc[:signer] }
117
+ end
118
+
119
+ # Predicate to check if an account is writable
120
+ #
121
+ # @param pubkey [String] The pubkey of the account
122
+ # @return [Boolean] Whether the account is writable
123
+ def writable?(pubkey)
124
+ @pubkey_account_map[pubkey].try { |acc| acc[:writable] }
125
+ end
126
+
127
+ # Predicate to check if an account is a writable signer
128
+ #
129
+ # @param pubkey [String] The pubkey of the account
130
+ # @return [Boolean] Whether the account is a writable signer
131
+ def writable_signer?(pubkey)
132
+ @pubkey_account_map[pubkey].try { |acc| acc[:signer] && acc[:writable] }
133
+ end
134
+
135
+ # Predicate to check if an account is writable and not a signer
136
+ #
137
+ # @param pubkey [String] The pubkey of the account
138
+ # @return [Boolean] Whether the account is writable and not a signer
139
+ def writable_nonsigner?(pubkey)
140
+ @pubkey_account_map[pubkey].try { |acc| !acc[:signer] && acc[:writable] }
141
+ end
142
+
143
+ # Predicate to check if an account is a readonly signer
144
+ #
145
+ # @param pubkey [String] The pubkey of the account
146
+ # @return [Boolean] Whether the account is a readonly signer
147
+ def readonly_signer?(pubkey)
148
+ @pubkey_account_map[pubkey].try { |acc| acc[:signer] && !acc[:writable] }
149
+ end
150
+
151
+ # Predicate to check if an account is readonly and not a signer
152
+ #
153
+ # @param pubkey [String] The pubkey of the account
154
+ # @return [Boolean] Whether the account is readonly and not a signer
155
+ def readonly_nonsigner?(pubkey)
156
+ @pubkey_account_map[pubkey].try { |acc| !acc[:signer] && !acc[:writable] }
157
+ end
158
+
159
+ # Merge all accounts from another AccountContext into this one
160
+ #
161
+ # @param other_context [AccountContext] The other context to merge from
162
+ def merge_from(other_context)
163
+ other_context.pubkey_account_map.each do |pubkey, data|
164
+ signer, writable, fee_payer = data.values_at(:signer, :writable, :fee_payer)
165
+ merge_account(pubkey, signer: signer, writable: writable, fee_payer: fee_payer)
166
+ end
167
+ end
168
+
169
+ # Compile accounts into final format
170
+ #
171
+ # Gets unique accounts and sorts them in the following order:
172
+ # - Signers first (Solana requirement)
173
+ # - Then writable accounts
174
+ # - Then readonly accounts
175
+ #
176
+ # @return [Hash] The compiled accounts and header
177
+ def compile
178
+ self.header = calculate_header
179
+ self.accounts = order_accounts
180
+ self
181
+ end
182
+
183
+ # Index of a pubkey in the accounts array
184
+ #
185
+ # @param pubkey_str [String] The public key of the account
186
+ # @return [Integer] The index of the pubkey in the accounts array or -1 if not found
187
+ def index_of(pubkey_str)
188
+ indices[pubkey_str] || -1
189
+ end
190
+
191
+ # Get map of indicies for pubkeys in accounts array
192
+ #
193
+ # @return [Hash{String => Integer}] The indices of the pubkeys in the accounts array
194
+ def indices
195
+ accounts.each_with_index.to_h
196
+ end
197
+
198
+ private
199
+
200
+ # Add or merge an account into the context
201
+ #
202
+ # @param pubkey [String, Solace::PublicKey, Solace::Keypair] The public key of the account
203
+ # @param signer [Boolean] Whether the account is a signer
204
+ # @param writable [Boolean] Whether the account is writable
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
208
+
209
+ @pubkey_account_map[pubkey_str] ||= DEFAULT_ACCOUNT.dup
210
+ @pubkey_account_map[pubkey_str][:signer] ||= signer
211
+ @pubkey_account_map[pubkey_str][:writable] ||= writable
212
+ @pubkey_account_map[pubkey_str][:fee_payer] ||= fee_payer
213
+
214
+ self
215
+ end
216
+
217
+ # Order accounts by signer, writable, readonly signer, readonly
218
+ #
219
+ # @return [Array<String>] The ordered accounts
220
+ def order_accounts
221
+ @pubkey_account_map.keys.sort_by do |pubkey|
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
227
+ else
228
+ raise ArgumentError, "Unknown account type for pubkey: #{pubkey}"
229
+ end
230
+ end
231
+ end
232
+
233
+ # Calculate the header for the transaction
234
+ #
235
+ # @note The header is an array of three integers:
236
+ # - The number of signers (writable + readonly)
237
+ # - The number of readonly signers
238
+ # - The number of readonly unsigned accounts
239
+ #
240
+ # @return [Array] The header for the transaction
241
+ def calculate_header
242
+ @pubkey_account_map.keys.each_with_object([0, 0, 0]) do |pubkey, acc|
243
+ acc[0] += 1 if signer?(pubkey)
244
+
245
+ if readonly_signer?(pubkey) then acc[1] += 1
246
+ elsif readonly_nonsigner?(pubkey) then acc[2] += 1
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
@@ -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
- # Args:
18
- # base64 (String): The base64 string to decode
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
- # Returns:
37
- # String: The compactly encoded u16 value
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 (n >> 7).zero?
47
- out << n
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
- # Returns:
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
- def self.decode_compact_u16(io)
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 = io.read(1)
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 (byte & 0x80).zero?
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
- # Args:
109
- # n (Integer): The u64 value to encode
110
- #
111
- # Returns:
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
- # Args:
125
- # io (IO or StringIO): The input to read bytes from.
126
- #
127
- # Returns:
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
- # Args:
141
- # bytes (String): The bytes to encode
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
- # Args:
157
- # string (String): The Base58 encoded string
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
- # Args:
173
- # bytes (String): The bytes to encode
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
- # Args:
189
- # string (String): The Base58 encoded string
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
- # Args:
205
- # string (String): The string to check
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
- # The path to the native library
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