solana-ruby-kit 7.1.1.1 → 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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -0
  3. data/lib/solana/ruby/kit/errors.rb +8 -0
  4. data/lib/solana/ruby/kit/functional.rb +1 -1
  5. data/lib/solana/ruby/kit/instruction_plans/instruction_plan.rb +13 -1
  6. data/lib/solana/ruby/kit/instruction_plans/transaction_plan.rb +9 -0
  7. data/lib/solana/ruby/kit/instruction_plans/transaction_plan_executor.rb +117 -2
  8. data/lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb +18 -0
  9. data/lib/solana/ruby/kit/instruction_plans/transaction_planner.rb +24 -2
  10. data/lib/solana/ruby/kit/rpc/api/get_account_info.rb +5 -0
  11. data/lib/solana/ruby/kit/rpc/api/get_ag_genesis_cert.rb +72 -0
  12. data/lib/solana/ruby/kit/rpc/api/get_balance.rb +4 -0
  13. data/lib/solana/ruby/kit/rpc/api/get_block_height.rb +5 -0
  14. data/lib/solana/ruby/kit/rpc/api/get_block_time.rb +5 -0
  15. data/lib/solana/ruby/kit/rpc/api/get_epoch_info.rb +5 -0
  16. data/lib/solana/ruby/kit/rpc/api/get_epoch_schedule.rb +5 -0
  17. data/lib/solana/ruby/kit/rpc/api/get_inflation_reward.rb +6 -1
  18. data/lib/solana/ruby/kit/rpc/api/get_latest_blockhash.rb +4 -0
  19. data/lib/solana/ruby/kit/rpc/api/get_minimum_balance_for_rent_exemption.rb +5 -0
  20. data/lib/solana/ruby/kit/rpc/api/get_multiple_accounts.rb +5 -0
  21. data/lib/solana/ruby/kit/rpc/api/get_program_accounts.rb +5 -0
  22. data/lib/solana/ruby/kit/rpc/api/get_signature_statuses.rb +4 -0
  23. data/lib/solana/ruby/kit/rpc/api/get_signatures_for_address.rb +5 -0
  24. data/lib/solana/ruby/kit/rpc/api/get_slot.rb +5 -0
  25. data/lib/solana/ruby/kit/rpc/api/get_token_account_balance.rb +5 -0
  26. data/lib/solana/ruby/kit/rpc/api/get_token_accounts_by_owner.rb +5 -0
  27. data/lib/solana/ruby/kit/rpc/api/get_token_supply.rb +43 -0
  28. data/lib/solana/ruby/kit/rpc/api/get_transaction.rb +5 -0
  29. data/lib/solana/ruby/kit/rpc/api/get_transactions_for_address.rb +5 -0
  30. data/lib/solana/ruby/kit/rpc/api/get_vote_accounts.rb +5 -0
  31. data/lib/solana/ruby/kit/rpc/api/has_transport.rb +25 -0
  32. data/lib/solana/ruby/kit/rpc/api/is_blockhash_valid.rb +4 -0
  33. data/lib/solana/ruby/kit/rpc/api/request_airdrop.rb +4 -0
  34. data/lib/solana/ruby/kit/rpc/api/send_transaction.rb +5 -0
  35. data/lib/solana/ruby/kit/rpc/api/simulate_transaction.rb +5 -0
  36. data/lib/solana/ruby/kit/rpc/client.rb +8 -1
  37. data/lib/solana/ruby/kit/rpc_subscriptions/api/account_notifications.rb +5 -0
  38. data/lib/solana/ruby/kit/rpc_subscriptions/api/has_transport.rb +35 -0
  39. data/lib/solana/ruby/kit/rpc_subscriptions/api/logs_notifications.rb +5 -0
  40. data/lib/solana/ruby/kit/rpc_subscriptions/api/program_notifications.rb +5 -0
  41. data/lib/solana/ruby/kit/rpc_subscriptions/api/root_notifications.rb +5 -0
  42. data/lib/solana/ruby/kit/rpc_subscriptions/api/signature_notifications.rb +5 -0
  43. data/lib/solana/ruby/kit/rpc_subscriptions/api/slot_notifications.rb +5 -0
  44. data/lib/solana/ruby/kit/rpc_subscriptions/client.rb +5 -2
  45. data/lib/solana/ruby/kit/rpc_types/sol.rb +8 -6
  46. data/lib/solana/ruby/kit/subscribable/reactive_action_store.rb +6 -6
  47. data/lib/solana/ruby/kit/subscribable/reactive_stream_store.rb +4 -4
  48. data/lib/solana/ruby/kit/transaction_messages/compute_budget.rb +6 -4
  49. data/lib/solana/ruby/kit/transaction_messages/resource_limit_validation.rb +70 -0
  50. data/lib/solana/ruby/kit/transaction_messages.rb +1 -0
  51. data/lib/solana/ruby/kit/version.rb +1 -1
  52. data/lib/solana/ruby/kit/wallet_standard.rb +4 -4
  53. metadata +6 -3
  54. data/lib/core_extensions/tapioca/name_patch.rb +0 -32
  55. data/lib/core_extensions/tapioca/required_ancestors.rb +0 -13
@@ -40,7 +40,9 @@ module Solana::Ruby::Kit
40
40
 
41
41
  sig { params(other: T.untyped).returns(T::Boolean) }
42
42
  def ==(other)
43
- other.is_a?(Sol) && @raw == other.raw
43
+ return false unless other.is_a?(Sol)
44
+
45
+ @raw == other.raw
44
46
  end
45
47
 
46
48
  sig { returns(Integer) }
@@ -50,8 +52,8 @@ module Solana::Ruby::Kit
50
52
  end
51
53
 
52
54
  SOL_DECIMALS = T.let(9, Integer)
53
- SOL_SCALE = T.let(10**9, Integer)
54
- SOL_RAW_MAX = T.let(2**64 - 1, Integer)
55
+ SOL_SCALE = T.let(1_000_000_000, Integer)
56
+ SOL_RAW_MAX = T.let(Kernel.Integer(2**64 - 1), Integer)
55
57
 
56
58
  module_function
57
59
 
@@ -66,17 +68,17 @@ module Solana::Ruby::Kit
66
68
  # Mirrors TypeScript's sol(value, rounding?).
67
69
  sig { params(value: String).returns(Sol) }
68
70
  def sol(value)
69
- bd = BigDecimal(value)
71
+ bd = Kernel.BigDecimal(value)
70
72
  Kernel.raise SolanaError.new(SolanaError::FIXED_POINTS__STRICT_MODE_PRECISION_LOSS) if bd < 0
71
73
 
72
74
  scaled = bd * SOL_SCALE
73
75
  raw = scaled.to_i
74
76
 
75
- unless scaled == BigDecimal(raw.to_s)
77
+ unless scaled == Kernel.BigDecimal(raw.to_s)
76
78
  Kernel.raise SolanaError.new(SolanaError::FIXED_POINTS__STRICT_MODE_PRECISION_LOSS)
77
79
  end
78
80
 
79
- Kernel.raise SolanaError.new(SolanaError::SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE) if raw > SOL_RAW_MAX
81
+ Kernel.raise SolanaError.new(SolanaError::LAMPORTS_OUT_OF_RANGE) if raw > SOL_RAW_MAX
80
82
 
81
83
  Sol.new(raw)
82
84
  end
@@ -82,8 +82,8 @@ module Solana::Ruby::Kit
82
82
  # most recent call's result updates state; superseded threads are ignored.
83
83
  sig { params(args: T.untyped).void }
84
84
  def dispatch(*args)
85
- gen = nil
86
- prev_data = nil
85
+ gen = T.let(nil, T.nilable(Integer))
86
+ prev_data = T.let(nil, T.untyped)
87
87
  @mutex.synchronize do
88
88
  @current_gen += 1
89
89
  gen = @current_gen
@@ -94,7 +94,7 @@ module Solana::Ruby::Kit
94
94
 
95
95
  Thread.new do
96
96
  begin
97
- result = @fn.call(*args)
97
+ result = @fn.call(*T.unsafe(args))
98
98
  active = @mutex.synchronize do
99
99
  if @current_gen == gen
100
100
  @state = ReactiveActionState.new(status: 'success', data: result)
@@ -125,8 +125,8 @@ module Solana::Ruby::Kit
125
125
  # by a concurrent dispatch or reset().
126
126
  sig { params(args: T.untyped).returns(T.untyped) }
127
127
  def dispatch_async(*args)
128
- gen = nil
129
- prev_data = nil
128
+ gen = T.let(nil, T.nilable(Integer))
129
+ prev_data = T.let(nil, T.untyped)
130
130
  @mutex.synchronize do
131
131
  @current_gen += 1
132
132
  gen = @current_gen
@@ -135,7 +135,7 @@ module Solana::Ruby::Kit
135
135
  end
136
136
  _notify
137
137
 
138
- result = @fn.call(*args)
138
+ result = @fn.call(*T.unsafe(args))
139
139
 
140
140
  active = @mutex.synchronize do
141
141
  if @current_gen == gen
@@ -108,7 +108,7 @@ module Solana::Ruby::Kit
108
108
  # Internal: transition state and notify all subscribers.
109
109
  sig { params(new_state: ReactiveState).void }
110
110
  def _set_state(new_state)
111
- subs = nil
111
+ subs = T.let(nil, T.nilable(T::Array[T.proc.void]))
112
112
  @mutex.synchronize do
113
113
  return if @state.equal?(new_state)
114
114
  @state = new_state
@@ -167,7 +167,7 @@ module Solana::Ruby::Kit
167
167
  return if @mutex.synchronize { @stopped }
168
168
 
169
169
  # Fresh active flag for this connection window
170
- active = [true]
170
+ active = T.let([true], T::Array[T::Boolean])
171
171
  @mutex.synchronize { @conn_active = active }
172
172
 
173
173
  # inner_signal fires (raises) once this connection is superseded or errors out,
@@ -193,8 +193,8 @@ module Solana::Ruby::Kit
193
193
  publisher.on(@error_channel, signal: inner_signal) do |err|
194
194
  next unless active[0] && get_unified_state.status != 'error'
195
195
  active[0] = false
196
- stale_data = get_unified_state.data
197
- _set_state(ReactiveState.new(status: 'error', data: stale_data, error: err))
196
+ last_data = get_unified_state.data
197
+ _set_state(ReactiveState.new(status: 'error', data: last_data, error: err))
198
198
  end
199
199
  end
200
200
  end
@@ -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,16 +64,17 @@ 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
 
69
71
  if existing_idx.nil?
70
72
  append_instructions(message, [new_ix])
71
- elsif compute_unit_limit_from_instruction_data(T.must(message.instructions[existing_idx].data)) == limit
73
+ elsif compute_unit_limit_from_instruction_data(T.must(T.must(message.instructions[existing_idx]).data)) == limit
72
74
  message
73
75
  else
74
76
  new_instructions = message.instructions.dup
75
- new_instructions[T.must(existing_idx)] = new_ix
77
+ new_instructions[existing_idx] = new_ix
76
78
  TransactionMessage.new(
77
79
  version: message.version,
78
80
  instructions: new_instructions,
@@ -127,11 +129,11 @@ module Solana::Ruby::Kit
127
129
 
128
130
  if existing_idx.nil?
129
131
  append_instructions(message, [new_ix])
130
- elsif loaded_accounts_data_size_limit_from_instruction_data(T.must(message.instructions[existing_idx].data)) == limit
132
+ elsif loaded_accounts_data_size_limit_from_instruction_data(T.must(T.must(message.instructions[existing_idx]).data)) == limit
131
133
  message
132
134
  else
133
135
  new_instructions = message.instructions.dup
134
- new_instructions[T.must(existing_idx)] = new_ix
136
+ new_instructions[existing_idx] = new_ix
135
137
  TransactionMessage.new(
136
138
  version: message.version,
137
139
  instructions: new_instructions,
@@ -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.1'
7
+ VERSION = '8.2.0'
8
8
  end
9
9
  end
10
10
  end
@@ -115,7 +115,7 @@ module Solana::Ruby::Kit
115
115
  reason: 'message section is missing'
116
116
  )
117
117
  end
118
- message_bytes = T.must(msg_slice).b
118
+ message_bytes = msg_slice.b
119
119
 
120
120
  # 4. Determine version and locate the message header.
121
121
  # Legacy transactions: first byte = num_required_signatures (0..127)
@@ -139,14 +139,14 @@ module Solana::Ruby::Kit
139
139
 
140
140
  account_addrs = T.let([], T::Array[String])
141
141
  num_accounts.times do
142
- account_addrs << Addresses.encode_address(message_bytes[msg_pos, 32].b)
142
+ account_addrs << Addresses.encode_address(T.must(message_bytes[msg_pos, 32]).b)
143
143
  msg_pos += 32
144
144
  end
145
145
 
146
146
  # 6. Build the signatures map.
147
147
  # The first +num_required_sigs+ accounts in the account table are the
148
148
  # signers; their raw_sigs slots are positionally aligned.
149
- signer_addrs = account_addrs[0, num_required_sigs]
149
+ signer_addrs = T.must(account_addrs[0, num_required_sigs])
150
150
  signatures = T.let({}, T::Hash[String, T.nilable(String)])
151
151
  signer_addrs.each_with_index { |addr, i| signatures[addr] = raw_sigs[i] }
152
152
 
@@ -234,7 +234,7 @@ module Solana::Ruby::Kit
234
234
  def decode_compact_u16(bytes, offset)
235
235
  value = 0
236
236
  shift = 0
237
- loop do
237
+ Kernel.loop do
238
238
  byte = bytes.getbyte(offset)
239
239
  if byte.nil?
240
240
  Kernel.raise SolanaError.new(
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.1
4
+ version: 8.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Zupan, Idhra Inc.
@@ -205,8 +205,6 @@ extra_rdoc_files:
205
205
  files:
206
206
  - LICENSE
207
207
  - README.md
208
- - lib/core_extensions/tapioca/name_patch.rb
209
- - lib/core_extensions/tapioca/required_ancestors.rb
210
208
  - lib/generators/solana/ruby/kit/install/install_generator.rb
211
209
  - lib/generators/solana/ruby/kit/install/templates/solana_ruby_kit.rb.tt
212
210
  - lib/solana/ruby/kit.rb
@@ -264,6 +262,7 @@ files:
264
262
  - lib/solana/ruby/kit/resource_limit_estimation.rb
265
263
  - lib/solana/ruby/kit/rpc.rb
266
264
  - lib/solana/ruby/kit/rpc/api/get_account_info.rb
265
+ - lib/solana/ruby/kit/rpc/api/get_ag_genesis_cert.rb
267
266
  - lib/solana/ruby/kit/rpc/api/get_balance.rb
268
267
  - lib/solana/ruby/kit/rpc/api/get_block_height.rb
269
268
  - lib/solana/ruby/kit/rpc/api/get_block_time.rb
@@ -279,9 +278,11 @@ files:
279
278
  - lib/solana/ruby/kit/rpc/api/get_slot.rb
280
279
  - lib/solana/ruby/kit/rpc/api/get_token_account_balance.rb
281
280
  - lib/solana/ruby/kit/rpc/api/get_token_accounts_by_owner.rb
281
+ - lib/solana/ruby/kit/rpc/api/get_token_supply.rb
282
282
  - lib/solana/ruby/kit/rpc/api/get_transaction.rb
283
283
  - lib/solana/ruby/kit/rpc/api/get_transactions_for_address.rb
284
284
  - lib/solana/ruby/kit/rpc/api/get_vote_accounts.rb
285
+ - lib/solana/ruby/kit/rpc/api/has_transport.rb
285
286
  - lib/solana/ruby/kit/rpc/api/is_blockhash_valid.rb
286
287
  - lib/solana/ruby/kit/rpc/api/request_airdrop.rb
287
288
  - lib/solana/ruby/kit/rpc/api/send_transaction.rb
@@ -296,6 +297,7 @@ files:
296
297
  - lib/solana/ruby/kit/rpc_parsed_types/vote_account.rb
297
298
  - lib/solana/ruby/kit/rpc_subscriptions.rb
298
299
  - lib/solana/ruby/kit/rpc_subscriptions/api/account_notifications.rb
300
+ - lib/solana/ruby/kit/rpc_subscriptions/api/has_transport.rb
299
301
  - lib/solana/ruby/kit/rpc_subscriptions/api/logs_notifications.rb
300
302
  - lib/solana/ruby/kit/rpc_subscriptions/api/program_notifications.rb
301
303
  - lib/solana/ruby/kit/rpc_subscriptions/api/root_notifications.rb
@@ -336,6 +338,7 @@ files:
336
338
  - lib/solana/ruby/kit/transaction_introspection/walk_instructions.rb
337
339
  - lib/solana/ruby/kit/transaction_messages.rb
338
340
  - lib/solana/ruby/kit/transaction_messages/compute_budget.rb
341
+ - lib/solana/ruby/kit/transaction_messages/resource_limit_validation.rb
339
342
  - lib/solana/ruby/kit/transaction_messages/transaction_message.rb
340
343
  - lib/solana/ruby/kit/transactions.rb
341
344
  - lib/solana/ruby/kit/transactions/compiler.rb
@@ -1,32 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- module T
5
- module Types
6
- class Simple
7
- module NamePatch
8
- NAME_METHOD = T.let(::Module.instance_method(:name), UnboundMethod)
9
-
10
- def name
11
- # Sorbet memoizes this method into the `@name` instance variable but
12
- # doing so means that types get memoized before this patch is applied
13
- qualified_name_of(@raw_type)
14
- end
15
-
16
- def qualified_name_of(constant)
17
- name = NAME_METHOD.bind_call(constant)
18
- name = nil if name&.start_with?("#<")
19
- return if name.nil?
20
-
21
- if name.start_with?("::")
22
- name
23
- else
24
- "::#{name}"
25
- end
26
- end
27
- end
28
-
29
- prepend NamePatch
30
- end
31
- end
32
- end
@@ -1,13 +0,0 @@
1
- module T
2
- module Helpers
3
- prepend(::Module.new do
4
- def requires_ancestor(&block)
5
- # We can't directly call the block since the ancestor might not be loaded yet.
6
- # We save the block in the map and will resolve it later.
7
- Tapioca::Runtime::Trackers::RequiredAncestor.register(self, block)
8
-
9
- super
10
- end
11
- end)
12
- end
13
- end