solana-ruby-kit 7.1.1.2 → 8.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a30b73ebab9578e0345910ab3f7d019cb6cacc9840797abfb8a177a3de9b2bb
4
- data.tar.gz: e540ed2b2b3cc1bb87a5f584b587d1ea4f86cad324c04385311289501ce48cf7
3
+ metadata.gz: ba7d2659d83b01af6565c7af483364d61e61fcc9761cbca9d63d8d41f6b4d73a
4
+ data.tar.gz: bd8402f845e9d8db5845be0e358676d00e308b2a0777afc1973e8814175e1c1c
5
5
  SHA512:
6
- metadata.gz: 29728a4e25c5f9d7dabd89e75cb9fd2b583f03930b6640c48c68ff070e90e06345bbfa75d636383dda51c9dc8044f9942f4a299866f39f8b93189f832fbf03a6
7
- data.tar.gz: 04c68d1f2e6f249c4be788e1c9e6919b056f9780eebd5a26bb6dc8aae6b15ceafe7838d39ed6af4ba24269ebdc5cbdd4cc028f8947db842631aace626750df45
6
+ metadata.gz: 589ade790ebb62b97f9905e8fe98113efc93deb5a6fad37239d377ab427915838eaa3d461644d213944ee8b80796134b28d5a6da43529a3920bb5037940a20a1
7
+ data.tar.gz: 3c03649c3b4314b93e7321f74b77150cd19bd775535b8431c14b678fdf35d3e7d4703fac43f341c3480ec5031b0c78bd0c0e3b7e385bb01f1822fc57fa5afaec
data/README.md CHANGED
@@ -189,9 +189,17 @@ msg = TxMsg.append_instructions(msg, [instruction])
189
189
  msg = TxMsg.prepend_instructions(msg, [priority_fee_ix])
190
190
 
191
191
  # Compute unit limit (SetComputeUnitLimit instruction from the Compute Budget program)
192
+ # The limit is validated: it must be an integer in [0, MAX_COMPUTE_UNIT_LIMIT] (1,400,000).
193
+ # The runtime silently clamps a larger request rather than failing, so asking for more than
194
+ # the maximum would run on a budget you did not ask for — this raises instead.
192
195
  msg = TxMsg.set_transaction_message_compute_unit_limit(200_000, msg)
193
196
  TxMsg.get_transaction_message_compute_unit_limit(msg) # => 200_000
194
197
 
198
+ # The check is also available on its own, alongside one for heap frame sizes — a whole
199
+ # number of KiB in [MIN_HEAP_SIZE, MAX_HEAP_SIZE] (32 KiB to 256 KiB).
200
+ TxMsg.assert_is_valid_compute_unit_limit(200_000)
201
+ TxMsg.assert_is_valid_heap_size(64 * 1024)
202
+
195
203
  # Loaded accounts data size limit
196
204
  msg = TxMsg.set_transaction_message_loaded_accounts_data_size_limit(64_000, msg)
197
205
  TxMsg.get_transaction_message_loaded_accounts_data_size_limit(msg) # => 64_000
@@ -274,6 +282,7 @@ rpc.get_block_time(slot)
274
282
  rpc.get_inflation_reward([address])
275
283
  rpc.get_signatures_for_address(address)
276
284
  rpc.get_vote_accounts
285
+ rpc.get_ag_genesis_cert # Alpenglow genesis cert, or nil
277
286
  rpc.simulate_transaction(encoded_tx)
278
287
  rpc.send_transaction(encoded_tx)
279
288
  rpc.request_airdrop(address, lamports) # devnet / testnet only
@@ -640,6 +649,21 @@ result = executor.call(transaction_plan)
640
649
  # A one-argument lambda returning { transaction:, context: } — the shape this method
641
650
  # required before v7.1.0 — is still accepted and adapted onto the flow above.
642
651
 
652
+ # ── 3b. Executing every leaf concurrently ────────────────────────────────────
653
+ # For work that has no execution dependencies — signing or serializing, say —
654
+ # create_transaction_plan_executor_with_concurrent_leaves takes the same callback
655
+ # contract but starts every leaf immediately, each on its own thread. It preserves
656
+ # the plan's nesting, order and divisibility in the result, supports non-divisible
657
+ # sequential plans, and cancels nothing: a leaf that raises does not stop the others,
658
+ # so every leaf runs to completion before the failure is reported. Because the leaves
659
+ # run at the same time, the callback must be safe to call concurrently with itself.
660
+ concurrent = Plans.create_transaction_plan_executor_with_concurrent_leaves(
661
+ execute_transaction_message: ->(_context, message) {
662
+ { transaction: Kit::Transactions.compile_transaction_message(message) }
663
+ }
664
+ )
665
+ result = concurrent.call(transaction_plan)
666
+
643
667
  # ── 4. Plan types ─────────────────────────────────────────────────────────────
644
668
  Plans.single_instruction_plan(ix) # wrap one instruction
645
669
  Plans.sequential_instruction_plan([ix1, ix2]) # ordered, divisible
@@ -60,6 +60,10 @@ module Solana::Ruby::Kit
60
60
  TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE = :SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE
61
61
  # context: { unsupported_version: }
62
62
  TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED = :SOLANA_ERROR__TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED
63
+ # context: { compute_unit_limit:, max_compute_unit_limit: }
64
+ TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE = :SOLANA_ERROR__TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE
65
+ # context: { heap_size:, max_heap_size:, min_heap_size:, multiple_of: }
66
+ TRANSACTION__INVALID_HEAP_SIZE = :SOLANA_ERROR__TRANSACTION__INVALID_HEAP_SIZE
63
67
  TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE = :SOLANA_ERROR__TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE
64
68
  TRANSACTIONS__BLOCKHASH_NOT_FOUND = :SOLANA_ERROR__TRANSACTIONS__BLOCKHASH_NOT_FOUND
65
69
  TRANSACTIONS__FAILED_TRANSACTION_PLAN = :SOLANA_ERROR__TRANSACTIONS__FAILED_TRANSACTION_PLAN
@@ -207,6 +211,8 @@ module Solana::Ruby::Kit
207
211
  TRANSACTIONS__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND => 'Failed to decompile instruction: program address not found at index %{index}',
208
212
  TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE => 'Failed to decompile instruction: account index %{index} is out of range',
209
213
  TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED => 'Transaction version %{unsupported_version} is not supported',
214
+ TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE => 'Transaction compute unit limit must be an integer in the range [0, %{max_compute_unit_limit}]. `%{compute_unit_limit}` given',
215
+ TRANSACTION__INVALID_HEAP_SIZE => 'Transaction heap size must be an integer multiple of %{multiple_of} bytes in the range [%{min_heap_size}, %{max_heap_size}]. `%{heap_size}` given',
210
216
  TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE => 'Transaction simulation failed: %{message}',
211
217
  TRANSACTIONS__BLOCKHASH_NOT_FOUND => 'Blockhash not found',
212
218
  TRANSACTIONS__FAILED_TRANSACTION_PLAN => 'Failed to execute transaction plan',
@@ -58,6 +58,57 @@ module Solana::Ruby::Kit
58
58
  }
59
59
  end
60
60
 
61
+ # Creates a TransactionPlanExecutor that processes every leaf concurrently.
62
+ #
63
+ # It takes the same configuration as +create_transaction_plan_executor+ and its
64
+ # +execute_transaction_message+ callback follows the same contract: it receives a fresh
65
+ # mutable +context+ Hash for every leaf and returns the Hash a successful result should
66
+ # carry. On success the two are merged with the returned value winning; when the callback
67
+ # raises, the context accumulated so far is preserved on the failed result. The legacy
68
+ # one-argument callable shape is accepted here too.
69
+ #
70
+ # The difference is the traversal. This executor preserves the input plan's nesting, order
71
+ # and divisibility in the returned result, but it does not enforce the execution
72
+ # dependencies expressed by sequential plans: every leaf is started immediately, each on
73
+ # its own thread, with no concurrency limit. That makes it suitable for work that can be
74
+ # performed independently, such as signing or serializing transactions. For the same
75
+ # reason a callback that raises does not cancel the other leaves — each one runs to
76
+ # completion — and non-divisible sequential plans are supported rather than rejected.
77
+ #
78
+ # Once every leaf has settled, a result tree containing any failure raises
79
+ # INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN carrying the complete tree on its
80
+ # +transaction_plan_result+ reader.
81
+ #
82
+ # Because each leaf runs on its own thread, the callback must be safe to run concurrently
83
+ # with itself. Each leaf gets its own context Hash, so the contexts do not need guarding,
84
+ # but anything the callback shares between invocations does.
85
+ #
86
+ # Mirrors `createTransactionPlanExecutorWithConcurrentLeaves(config)` from
87
+ # @solana/instruction-plans.
88
+ # NOTE: Ruby translation has no abort signal; leaves are never canceled, only failed.
89
+ sig { params(execute_transaction_message: T.untyped).returns(T.untyped) }
90
+ def create_transaction_plan_executor_with_concurrent_leaves(execute_transaction_message:)
91
+ ->(transaction_plan) {
92
+ # Traversal spawns every leaf's thread on the way down and returns a tree of thunks;
93
+ # resolving them afterwards is what joins the threads, so all leaves are already in
94
+ # flight before the first one is waited on.
95
+ result = concurrent_executor_traverse(transaction_plan, execute_transaction_message).call
96
+
97
+ cause = executor_find_error(result)
98
+ if cause
99
+ err = SolanaError.new(
100
+ SolanaError::INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN,
101
+ { cause: cause }
102
+ )
103
+ err.instance_variable_set(:@transaction_plan_result, result)
104
+ err.define_singleton_method(:transaction_plan_result) { result }
105
+ Kernel.raise err
106
+ end
107
+
108
+ result
109
+ }
110
+ end
111
+
61
112
  # ── Private helpers ────────────────────────────────────────────────────────
62
113
 
63
114
  module_function
@@ -145,6 +196,58 @@ module Solana::Ruby::Kit
145
196
  returned
146
197
  end
147
198
 
199
+ # Returns a thunk that resolves to this plan's result. Leaf threads are started as the
200
+ # tree is walked, so the whole plan is in flight by the time the outermost thunk is called.
201
+ sig { params(plan: T.untyped, execute_fn: T.untyped).returns(T.untyped) }
202
+
203
+ def concurrent_executor_traverse(plan, execute_fn)
204
+ case plan.kind
205
+ when :single
206
+ thread = Thread.new { concurrent_executor_execute_single(plan, execute_fn) }
207
+ -> { thread.value }
208
+ when :sequential, :parallel
209
+ thunks = plan.plans.map { |sub| concurrent_executor_traverse(sub, execute_fn) }
210
+ Kernel.lambda do
211
+ results = thunks.map(&:call)
212
+ if plan.kind == :parallel
213
+ parallel_transaction_plan_result(results)
214
+ elsif plan.divisible
215
+ sequential_transaction_plan_result(results)
216
+ else
217
+ # Unlike the sequential executor, non-divisible plans are supported: nothing
218
+ # about running every leaf independently depends on the plan being divisible.
219
+ non_divisible_sequential_transaction_plan_result(results)
220
+ end
221
+ end
222
+ else
223
+ Kernel.raise SolanaError.new(
224
+ SolanaError::INVARIANT_VIOLATION__INVALID_TRANSACTION_PLAN_KIND,
225
+ { kind: plan.kind }
226
+ )
227
+ end
228
+ end
229
+
230
+ # Runs one leaf. Identical to +executor_traverse_single+ except that a failure is recorded
231
+ # and nothing else — there is no shared cancellation state for it to trip.
232
+ sig { params(plan: T.untyped, execute_fn: T.untyped).returns(T.untyped) }
233
+
234
+ def concurrent_executor_execute_single(plan, execute_fn)
235
+ context = {}
236
+
237
+ begin
238
+ returned = executor_invoke(execute_fn, context, plan.message)
239
+ successful_single_transaction_plan_result(plan.message, nil, context.merge(returned))
240
+ rescue SolanaError => e
241
+ failed_single_transaction_plan_result(plan.message, e, context)
242
+ rescue => e
243
+ wrapped = SolanaError.new(
244
+ SolanaError::INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN,
245
+ { cause: e }
246
+ )
247
+ failed_single_transaction_plan_result(plan.message, wrapped, context)
248
+ end
249
+ end
250
+
148
251
  sig { params(result: T.untyped).returns(T.untyped) }
149
252
 
150
253
  def executor_find_error(result)
@@ -164,5 +267,7 @@ module Solana::Ruby::Kit
164
267
  private_class_method :executor_traverse_single
165
268
  private_class_method :executor_invoke
166
269
  private_class_method :executor_find_error
270
+ private_class_method :concurrent_executor_traverse
271
+ private_class_method :concurrent_executor_execute_single
167
272
  end
168
273
  end
@@ -0,0 +1,72 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'has_transport'
5
+
6
+ module Solana::Ruby::Kit
7
+ module Rpc
8
+ module Api
9
+ # The block an Alpenglow genesis certificate certifies.
10
+ AgGenesisCertBlock = T.let(
11
+ Struct.new(
12
+ :block_id, # Array<Integer> — block id of the certified block, as a 32-byte array
13
+ :slot, # Integer — slot of the certified block
14
+ keyword_init: true
15
+ ),
16
+ T.untyped
17
+ )
18
+
19
+ # The aggregate signature over an Alpenglow genesis certificate.
20
+ AgGenesisCertSignature = T.let(
21
+ Struct.new(
22
+ :bitmap, # Array<Integer> — ranks of the validators in the aggregate signature
23
+ :signature, # Array<Integer> — the 192-byte aggregate BLS signature
24
+ keyword_init: true
25
+ ),
26
+ T.untyped
27
+ )
28
+
29
+ # The Alpenglow genesis certificate, as gossiped between validators.
30
+ # Mirrors the `WireBlockCertMessage` type in the Agave validator.
31
+ AgGenesisCert = T.let(
32
+ Struct.new(
33
+ :block, # AgGenesisCertBlock
34
+ :signature, # AgGenesisCertSignature
35
+ keyword_init: true
36
+ ),
37
+ T.untyped
38
+ )
39
+
40
+ # Returns the Alpenglow genesis certificate — the certificate over the block at which the
41
+ # Alpenglow consensus protocol was activated — or nil if the node does not have one.
42
+ # Mirrors TypeScript's GetAgGenesisCertApi.getAgGenesisCert.
43
+ # @see https://solana.com/docs/rpc/http/getaggenesiscert
44
+ module GetAgGenesisCert
45
+ extend T::Sig
46
+ extend T::Helpers
47
+
48
+ requires_ancestor { HasTransport }
49
+
50
+ sig { returns(T.untyped) }
51
+ def get_ag_genesis_cert
52
+ raw = transport.request('getAgGenesisCert', [])
53
+ return nil if raw.nil?
54
+
55
+ block = raw['block'] || {}
56
+ signature = raw['signature'] || {}
57
+
58
+ AgGenesisCert.new(
59
+ block: AgGenesisCertBlock.new(
60
+ block_id: Kernel.Array(block['blockId']).map { |b| Kernel.Integer(b) },
61
+ slot: Kernel.Integer(block['slot'])
62
+ ),
63
+ signature: AgGenesisCertSignature.new(
64
+ bitmap: Kernel.Array(signature['bitmap']).map { |b| Kernel.Integer(b) },
65
+ signature: Kernel.Array(signature['signature']).map { |b| Kernel.Integer(b) }
66
+ )
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -7,6 +7,7 @@ require_relative 'api/get_slot'
7
7
  require_relative 'api/get_balance'
8
8
  require_relative 'api/get_latest_blockhash'
9
9
  require_relative 'api/get_account_info'
10
+ require_relative 'api/get_ag_genesis_cert'
10
11
  require_relative 'api/get_block_height'
11
12
  require_relative 'api/get_signature_statuses'
12
13
  require_relative 'api/send_transaction'
@@ -51,6 +52,7 @@ module Solana::Ruby::Kit
51
52
  include Api::GetBalance
52
53
  include Api::GetLatestBlockhash
53
54
  include Api::GetAccountInfo
55
+ include Api::GetAgGenesisCert
54
56
  include Api::GetBlockHeight
55
57
  include Api::GetSignatureStatuses
56
58
  include Api::SendTransaction
@@ -4,6 +4,7 @@
4
4
  require_relative '../addresses/address'
5
5
  require_relative '../instructions/instruction'
6
6
  require_relative 'transaction_message'
7
+ require_relative 'resource_limit_validation'
7
8
 
8
9
  module Solana::Ruby::Kit
9
10
  module TransactionMessages
@@ -63,6 +64,7 @@ module Solana::Ruby::Kit
63
64
  # SetComputeUnitLimit instruction. Mirrors `setTransactionMessageComputeUnitLimit`.
64
65
  sig { params(limit: Integer, message: TransactionMessage).returns(TransactionMessage) }
65
66
  def set_transaction_message_compute_unit_limit(limit, message)
67
+ assert_is_valid_compute_unit_limit(limit)
66
68
  existing_idx = message.instructions.index { |i| set_compute_unit_limit_instruction?(i) }
67
69
  new_ix = get_set_compute_unit_limit_instruction(limit)
68
70
 
@@ -0,0 +1,70 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../errors'
5
+
6
+ module Solana::Ruby::Kit
7
+ # NOTE: `MAX_COMPUTE_UNIT_LIMIT` lives in `compute_budget.rb`, which requires this file, so it
8
+ # is referenced from a method body rather than at load time to keep the two files acyclic.
9
+ module TransactionMessages
10
+ # The smallest heap frame size that a transaction may request (32 KiB).
11
+ MIN_HEAP_SIZE = T.let(32 * 1024, Integer)
12
+
13
+ # The largest heap frame size that a transaction may request (256 KiB).
14
+ MAX_HEAP_SIZE = T.let(256 * 1024, Integer)
15
+
16
+ # A requested heap frame size must be a whole number of KiB.
17
+ HEAP_SIZE_MULTIPLE_OF = T.let(1024, Integer)
18
+
19
+ module_function
20
+
21
+ # Raises if the given compute unit limit is one the runtime will not honor as written.
22
+ #
23
+ # A transaction may request at most MAX_COMPUTE_UNIT_LIMIT compute units. Requesting more
24
+ # does not fail the transaction; the runtime clamps the request down to that maximum, so
25
+ # the budget the transaction runs with is silently not the one that was asked for. Values
26
+ # that are not integers are a separate hazard, since they cannot be encoded as a `u32` and
27
+ # are not clamped. Failing here surfaces both at the point the value is set.
28
+ #
29
+ # Mirrors `assertIsValidComputeUnitLimit` from @solana/transaction-messages.
30
+ sig { params(compute_unit_limit: T.untyped).void }
31
+ def assert_is_valid_compute_unit_limit(compute_unit_limit)
32
+ return if compute_unit_limit.is_a?(Integer) &&
33
+ compute_unit_limit >= 0 &&
34
+ compute_unit_limit <= MAX_COMPUTE_UNIT_LIMIT
35
+
36
+ Kernel.raise SolanaError.new(
37
+ SolanaError::TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE,
38
+ { compute_unit_limit: compute_unit_limit, max_compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT }
39
+ )
40
+ end
41
+
42
+ # Raises if the given heap frame size is one the runtime will not accept.
43
+ #
44
+ # The requested heap size must be a multiple of HEAP_SIZE_MULTIPLE_OF bytes and lie between
45
+ # MIN_HEAP_SIZE and MAX_HEAP_SIZE inclusive. This mirrors the transaction sanitization check
46
+ # performed by the runtime.
47
+ #
48
+ # Mirrors `assertIsValidHeapSize` from @solana/transaction-messages. Ruby has no heap-size
49
+ # setter of its own — the `RequestHeapFrame` instruction and the version 1 transaction config
50
+ # are both untranslated — so this is offered as a standalone check for callers building that
51
+ # instruction themselves.
52
+ sig { params(heap_size: T.untyped).void }
53
+ def assert_is_valid_heap_size(heap_size)
54
+ return if heap_size.is_a?(Integer) &&
55
+ heap_size >= MIN_HEAP_SIZE &&
56
+ heap_size <= MAX_HEAP_SIZE &&
57
+ (heap_size % HEAP_SIZE_MULTIPLE_OF).zero?
58
+
59
+ Kernel.raise SolanaError.new(
60
+ SolanaError::TRANSACTION__INVALID_HEAP_SIZE,
61
+ {
62
+ heap_size: heap_size,
63
+ max_heap_size: MAX_HEAP_SIZE,
64
+ min_heap_size: MIN_HEAP_SIZE,
65
+ multiple_of: HEAP_SIZE_MULTIPLE_OF
66
+ }
67
+ )
68
+ end
69
+ end
70
+ end
@@ -3,4 +3,5 @@
3
3
 
4
4
  # Transaction message construction utilities — mirrors @solana/transaction-messages.
5
5
  require_relative 'transaction_messages/transaction_message'
6
+ require_relative 'transaction_messages/resource_limit_validation'
6
7
  require_relative 'transaction_messages/compute_budget'
@@ -4,7 +4,7 @@
4
4
  module Solana
5
5
  module Ruby
6
6
  module Kit
7
- VERSION = '7.1.1.2'
7
+ VERSION = '8.2.0'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solana-ruby-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.1.2
4
+ version: 8.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Zupan, Idhra Inc.
@@ -262,6 +262,7 @@ files:
262
262
  - lib/solana/ruby/kit/resource_limit_estimation.rb
263
263
  - lib/solana/ruby/kit/rpc.rb
264
264
  - lib/solana/ruby/kit/rpc/api/get_account_info.rb
265
+ - lib/solana/ruby/kit/rpc/api/get_ag_genesis_cert.rb
265
266
  - lib/solana/ruby/kit/rpc/api/get_balance.rb
266
267
  - lib/solana/ruby/kit/rpc/api/get_block_height.rb
267
268
  - lib/solana/ruby/kit/rpc/api/get_block_time.rb
@@ -337,6 +338,7 @@ files:
337
338
  - lib/solana/ruby/kit/transaction_introspection/walk_instructions.rb
338
339
  - lib/solana/ruby/kit/transaction_messages.rb
339
340
  - lib/solana/ruby/kit/transaction_messages/compute_budget.rb
341
+ - lib/solana/ruby/kit/transaction_messages/resource_limit_validation.rb
340
342
  - lib/solana/ruby/kit/transaction_messages/transaction_message.rb
341
343
  - lib/solana/ruby/kit/transactions.rb
342
344
  - lib/solana/ruby/kit/transactions/compiler.rb