solana-ruby-kit 0.1.9 → 7.0.0.1
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/lib/solana/ruby/kit/errors.rb +26 -1
- data/lib/solana/ruby/kit/instruction_plans/instruction_plan.rb +34 -7
- data/lib/solana/ruby/kit/instruction_plans/max_instructions.rb +59 -0
- data/lib/solana/ruby/kit/instruction_plans/transaction_planner.rb +49 -9
- data/lib/solana/ruby/kit/instruction_plans.rb +1 -0
- data/lib/solana/ruby/kit/rpc/api/get_inflation_reward.rb +4 -2
- data/lib/solana/ruby/kit/rpc/api/get_transactions_for_address.rb +208 -0
- data/lib/solana/ruby/kit/rpc/api/get_vote_accounts.rb +2 -0
- data/lib/solana/ruby/kit/rpc/client.rb +2 -0
- data/lib/solana/ruby/kit/subscribable/bridge_store_to_async_iterable.rb +172 -0
- data/lib/solana/ruby/kit/subscribable.rb +1 -0
- data/lib/solana/ruby/kit/transaction_introspection/compiled_transaction_message.rb +168 -0
- data/lib/solana/ruby/kit/transaction_introspection/decode_rpc_transaction.rb +176 -0
- data/lib/solana/ruby/kit/transaction_introspection/get_inner_instructions.rb +88 -0
- data/lib/solana/ruby/kit/transaction_introspection/get_instructions.rb +134 -0
- data/lib/solana/ruby/kit/transaction_introspection/loaded_addresses.rb +19 -0
- data/lib/solana/ruby/kit/transaction_introspection/types.rb +29 -0
- data/lib/solana/ruby/kit/transaction_introspection/walk_instructions.rb +60 -0
- data/lib/solana/ruby/kit/transaction_introspection.rb +13 -0
- data/lib/solana/ruby/kit/version.rb +1 -1
- data/lib/solana/ruby/kit.rb +2 -0
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 950ad695ca0d9971314172207d974e747cf35c5dfec1210fb32882f0517306f8
|
|
4
|
+
data.tar.gz: ce0c4c171c78ca202a35bbacdd74630bb24afd3a438c7ebe7669b5cb56ee9a71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d356ad614cfe6990a0df08121f6db68dcdf4e72b19979bd01a56233a1fccc699a16ee716c2e12d33b4de5c34792cc627fd0e273bfefce73a7144f70fff37ddf
|
|
7
|
+
data.tar.gz: b866ff957a09ba7dc4ab26708ea1311b2c8a283e992f972351c173187a47d905b6ba62483beefd0f5b2c9d46f9ecdd8574bba1f4491e96771abdc9c75417f7ee
|
|
@@ -56,6 +56,10 @@ module Solana::Ruby::Kit
|
|
|
56
56
|
TRANSACTIONS__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS = :SOLANA_ERROR__TRANSACTIONS__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS
|
|
57
57
|
TRANSACTIONS__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING = :SOLANA_ERROR__TRANSACTIONS__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING
|
|
58
58
|
TRANSACTIONS__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND = :SOLANA_ERROR__TRANSACTIONS__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND
|
|
59
|
+
# context: { index: }
|
|
60
|
+
TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE = :SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE
|
|
61
|
+
# context: { unsupported_version: }
|
|
62
|
+
TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED = :SOLANA_ERROR__TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED
|
|
59
63
|
TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE = :SOLANA_ERROR__TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE
|
|
60
64
|
TRANSACTIONS__BLOCKHASH_NOT_FOUND = :SOLANA_ERROR__TRANSACTIONS__BLOCKHASH_NOT_FOUND
|
|
61
65
|
TRANSACTIONS__FAILED_TRANSACTION_PLAN = :SOLANA_ERROR__TRANSACTIONS__FAILED_TRANSACTION_PLAN
|
|
@@ -118,6 +122,16 @@ module Solana::Ruby::Kit
|
|
|
118
122
|
INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE = :SOLANA_ERROR__INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE
|
|
119
123
|
INSTRUCTION_PLANS__EMPTY_INSTRUCTION_PLAN = :SOLANA_ERROR__INSTRUCTION_PLANS__EMPTY_INSTRUCTION_PLAN
|
|
120
124
|
INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN = :SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN
|
|
125
|
+
# context: { max_instructions:, transaction_instruction_limit: }
|
|
126
|
+
INSTRUCTION_PLANS__INVALID_MAX_INSTRUCTIONS_PER_TRANSACTION = :SOLANA_ERROR__INSTRUCTION_PLANS__INVALID_MAX_INSTRUCTIONS_PER_TRANSACTION
|
|
127
|
+
# context: { max_instructions:, num_instructions: }
|
|
128
|
+
INSTRUCTION_PLANS__MAX_INSTRUCTIONS_PER_TRANSACTION_EXCEEDED = :SOLANA_ERROR__INSTRUCTION_PLANS__MAX_INSTRUCTIONS_PER_TRANSACTION_EXCEEDED
|
|
129
|
+
|
|
130
|
+
# ── Transaction introspection ────────────────────────────────────────────
|
|
131
|
+
TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION = :SOLANA_ERROR__TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION
|
|
132
|
+
TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE = :SOLANA_ERROR__TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE
|
|
133
|
+
# context: { needed_bytes: }
|
|
134
|
+
TRANSACTION_INTROSPECTION__MALFORMED_COMPILED_MESSAGE = :SOLANA_ERROR__TRANSACTION_INTROSPECTION__MALFORMED_COMPILED_MESSAGE
|
|
121
135
|
|
|
122
136
|
# ── Wallet Standard ───────────────────────────────────────────────────────
|
|
123
137
|
WALLET_STANDARD__INVALID_WIRE_FORMAT = :SOLANA_ERROR__WALLET_STANDARD__INVALID_WIRE_FORMAT
|
|
@@ -125,6 +139,7 @@ module Solana::Ruby::Kit
|
|
|
125
139
|
|
|
126
140
|
# ── Subscribable ──────────────────────────────────────────────────────────
|
|
127
141
|
SUBSCRIBABLE__RETRY_NOT_SUPPORTED = :SOLANA_ERROR__SUBSCRIBABLE__RETRY_NOT_SUPPORTED
|
|
142
|
+
SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR = :SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR
|
|
128
143
|
|
|
129
144
|
# ── Fixed-points ──────────────────────────────────────────────────────────
|
|
130
145
|
FIXED_POINTS__STRICT_MODE_PRECISION_LOSS = :SOLANA_ERROR__FIXED_POINTS__STRICT_MODE_PRECISION_LOSS
|
|
@@ -184,7 +199,9 @@ module Solana::Ruby::Kit
|
|
|
184
199
|
TRANSACTIONS__VERSION_NUMBER_OUT_OF_RANGE => 'Transaction version %{version} is out of range',
|
|
185
200
|
TRANSACTIONS__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS => 'Failed to decompile address lookup table contents',
|
|
186
201
|
TRANSACTIONS__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING => 'Failed to decompile transaction: fee payer is missing',
|
|
187
|
-
TRANSACTIONS__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND => 'Failed to decompile instruction: program address not found',
|
|
202
|
+
TRANSACTIONS__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND => 'Failed to decompile instruction: program address not found at index %{index}',
|
|
203
|
+
TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE => 'Failed to decompile instruction: account index %{index} is out of range',
|
|
204
|
+
TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED => 'Transaction version %{unsupported_version} is not supported',
|
|
188
205
|
TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE => 'Transaction simulation failed: %{message}',
|
|
189
206
|
TRANSACTIONS__BLOCKHASH_NOT_FOUND => 'Blockhash not found',
|
|
190
207
|
TRANSACTIONS__FAILED_TRANSACTION_PLAN => 'Failed to execute transaction plan',
|
|
@@ -245,9 +262,17 @@ module Solana::Ruby::Kit
|
|
|
245
262
|
INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE => 'Message packer is already complete; no more instructions to pack',
|
|
246
263
|
INSTRUCTION_PLANS__EMPTY_INSTRUCTION_PLAN => 'Instruction plan is empty and produced no transaction messages',
|
|
247
264
|
INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN => 'Failed to execute transaction plan',
|
|
265
|
+
INSTRUCTION_PLANS__INVALID_MAX_INSTRUCTIONS_PER_TRANSACTION => 'The configured maximum of %{max_instructions} instructions per transaction is invalid. It must be a positive integer no greater than the transaction format limit of %{transaction_instruction_limit} instructions per transaction.',
|
|
266
|
+
INSTRUCTION_PLANS__MAX_INSTRUCTIONS_PER_TRANSACTION_EXCEEDED => 'Planning this transaction message would require %{num_instructions} instructions, which exceeds the configured maximum of %{max_instructions} instructions per transaction.',
|
|
267
|
+
|
|
268
|
+
# Transaction introspection
|
|
269
|
+
TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION => "Cannot decode a 'jsonParsed'-encoded getTransaction response; its instructions are pre-parsed and lack raw bytes. Fetch with encoding: 'json' or 'base64' instead.",
|
|
270
|
+
TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE => 'Unrecognized getTransaction response shape; expected a base64, base58, or json-encoded transaction.',
|
|
271
|
+
TRANSACTION_INTROSPECTION__MALFORMED_COMPILED_MESSAGE => 'Malformed compiled transaction message: expected at least %{needed_bytes} byte(s)',
|
|
248
272
|
|
|
249
273
|
# Subscribable
|
|
250
274
|
SUBSCRIBABLE__RETRY_NOT_SUPPORTED => 'This reactive store does not support retry(); use create_reactive_store_from_data_publisher_factory for a retryable store',
|
|
275
|
+
SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR => 'The stream store closed in an error state but did not report an error.',
|
|
251
276
|
|
|
252
277
|
# Fixed-points
|
|
253
278
|
FIXED_POINTS__STRICT_MODE_PRECISION_LOSS => 'Value has more than 9 fractional digits and cannot be represented exactly as a Sol amount',
|
|
@@ -5,6 +5,7 @@ require_relative '../errors'
|
|
|
5
5
|
require_relative '../instructions/instruction'
|
|
6
6
|
require_relative '../transaction_messages/transaction_message'
|
|
7
7
|
require_relative '../transactions/compiler'
|
|
8
|
+
require_relative 'max_instructions'
|
|
8
9
|
|
|
9
10
|
module Solana::Ruby::Kit
|
|
10
11
|
module InstructionPlans
|
|
@@ -68,9 +69,17 @@ module Solana::Ruby::Kit
|
|
|
68
69
|
# Packs as many instructions as possible into +message+ and returns the
|
|
69
70
|
# updated message. Raises SolanaError if the message is too small or the
|
|
70
71
|
# packer is already done.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
#
|
|
73
|
+
# +max_instructions+ caps the number of top-level instructions allowed in the
|
|
74
|
+
# returned message (defaults to 16; must be a positive integer no greater than 64).
|
|
75
|
+
sig do
|
|
76
|
+
params(
|
|
77
|
+
message: TransactionMessages::TransactionMessage,
|
|
78
|
+
max_instructions: T.nilable(Integer)
|
|
79
|
+
).returns(TransactionMessages::TransactionMessage)
|
|
80
|
+
end
|
|
81
|
+
def pack_message_to_capacity(message, max_instructions: nil)
|
|
82
|
+
@pack_proc.call(message, max_instructions)
|
|
74
83
|
end
|
|
75
84
|
end
|
|
76
85
|
|
|
@@ -122,11 +131,15 @@ module Solana::Ruby::Kit
|
|
|
122
131
|
offset = T.let(0, Integer)
|
|
123
132
|
MessagePacker.new(
|
|
124
133
|
done_proc: -> { offset >= total_length },
|
|
125
|
-
pack_proc: ->(message) {
|
|
134
|
+
pack_proc: ->(message, max_instructions) {
|
|
126
135
|
if offset >= total_length
|
|
127
136
|
Kernel.raise SolanaError.new(SolanaError::INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE)
|
|
128
137
|
end
|
|
129
138
|
|
|
139
|
+
InstructionPlans.assert_valid_max_instructions_per_transaction(max_instructions)
|
|
140
|
+
resolved_max = InstructionPlans.resolve_max_instructions(max_instructions)
|
|
141
|
+
InstructionPlans.assert_max_instructions_per_transaction(message.instructions.length + 1, resolved_max)
|
|
142
|
+
|
|
130
143
|
base_ix = get_instruction.call(offset, 0)
|
|
131
144
|
with_base = TransactionMessages.append_instructions(message, [base_ix])
|
|
132
145
|
base_size = Transactions.get_transaction_message_size(with_base)
|
|
@@ -164,18 +177,30 @@ module Solana::Ruby::Kit
|
|
|
164
177
|
idx = T.let(0, Integer)
|
|
165
178
|
MessagePacker.new(
|
|
166
179
|
done_proc: -> { idx >= instructions.length },
|
|
167
|
-
pack_proc: ->(message) {
|
|
180
|
+
pack_proc: ->(message, max_instructions) {
|
|
168
181
|
if idx >= instructions.length
|
|
169
182
|
Kernel.raise SolanaError.new(SolanaError::INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE)
|
|
170
183
|
end
|
|
171
184
|
|
|
185
|
+
InstructionPlans.assert_valid_max_instructions_per_transaction(max_instructions)
|
|
186
|
+
resolved_max = InstructionPlans.resolve_max_instructions(max_instructions)
|
|
187
|
+
InstructionPlans.assert_max_instructions_per_transaction(message.instructions.length + 1, resolved_max)
|
|
188
|
+
|
|
172
189
|
original_size = Transactions.get_transaction_message_size(message)
|
|
173
190
|
packed = T.let(message, TransactionMessages::TransactionMessage)
|
|
174
191
|
start_idx = idx
|
|
175
192
|
|
|
176
193
|
(idx...instructions.length).each do |i|
|
|
177
|
-
|
|
178
|
-
|
|
194
|
+
# Stop once the message is full, before compiling a message that would exceed
|
|
195
|
+
# the instruction limit (which would raise). The assertion above guarantees at
|
|
196
|
+
# least the first instruction fits, so reaching the limit here is a graceful stop.
|
|
197
|
+
if packed.instructions.length >= resolved_max
|
|
198
|
+
idx = i
|
|
199
|
+
return packed
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
next_packed = TransactionMessages.append_instructions(packed, [instructions[i]])
|
|
203
|
+
size = Transactions.get_transaction_message_size(next_packed)
|
|
179
204
|
|
|
180
205
|
if size > Transactions::TRANSACTION_SIZE_LIMIT
|
|
181
206
|
if i == start_idx
|
|
@@ -190,6 +215,8 @@ module Solana::Ruby::Kit
|
|
|
190
215
|
idx = i
|
|
191
216
|
return packed
|
|
192
217
|
end
|
|
218
|
+
|
|
219
|
+
packed = next_packed
|
|
193
220
|
end
|
|
194
221
|
|
|
195
222
|
idx = instructions.length
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../errors'
|
|
5
|
+
|
|
6
|
+
module Solana::Ruby::Kit
|
|
7
|
+
module InstructionPlans
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
# The default maximum number of top-level instructions per planned transaction message.
|
|
11
|
+
#
|
|
12
|
+
# Intentionally lower than the transaction format's instruction limit to leave headroom
|
|
13
|
+
# for inner instructions (CPIs), which are not visible at planning time.
|
|
14
|
+
DEFAULT_MAX_INSTRUCTIONS_PER_TRANSACTION = T.let(16, Integer)
|
|
15
|
+
|
|
16
|
+
# The hard maximum number of top-level instructions the transaction format can encode.
|
|
17
|
+
TRANSACTION_INSTRUCTION_LIMIT = T.let(64, Integer)
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# Resolves the effective maximum number of instructions allowed in a transaction message,
|
|
22
|
+
# falling back to the default when no value is provided.
|
|
23
|
+
# Mirrors `resolveMaxInstructions(maxInstructions)`.
|
|
24
|
+
sig { params(max_instructions: T.nilable(Integer)).returns(Integer) }
|
|
25
|
+
def resolve_max_instructions(max_instructions)
|
|
26
|
+
max_instructions || DEFAULT_MAX_INSTRUCTIONS_PER_TRANSACTION
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Asserts that a configured maximum number of instructions per transaction is valid.
|
|
30
|
+
# +nil+ is allowed and falls back to the default.
|
|
31
|
+
# Mirrors `assertValidMaxInstructionsPerTransaction(maxInstructions)`.
|
|
32
|
+
sig { params(max_instructions: T.nilable(Integer)).void }
|
|
33
|
+
def assert_valid_max_instructions_per_transaction(max_instructions)
|
|
34
|
+
return if max_instructions.nil?
|
|
35
|
+
|
|
36
|
+
if max_instructions <= 0 || max_instructions > TRANSACTION_INSTRUCTION_LIMIT
|
|
37
|
+
Kernel.raise SolanaError.new(
|
|
38
|
+
SolanaError::INSTRUCTION_PLANS__INVALID_MAX_INSTRUCTIONS_PER_TRANSACTION,
|
|
39
|
+
{
|
|
40
|
+
max_instructions: max_instructions,
|
|
41
|
+
transaction_instruction_limit: TRANSACTION_INSTRUCTION_LIMIT
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Raises if +num_instructions+ exceeds +max_instructions+.
|
|
48
|
+
# Mirrors `assertMaxInstructionsPerTransaction(numInstructions, maxInstructions)`.
|
|
49
|
+
sig { params(num_instructions: Integer, max_instructions: Integer).void }
|
|
50
|
+
def assert_max_instructions_per_transaction(num_instructions, max_instructions)
|
|
51
|
+
return if num_instructions <= max_instructions
|
|
52
|
+
|
|
53
|
+
Kernel.raise SolanaError.new(
|
|
54
|
+
SolanaError::INSTRUCTION_PLANS__MAX_INSTRUCTIONS_PER_TRANSACTION_EXCEEDED,
|
|
55
|
+
{ max_instructions: max_instructions, num_instructions: num_instructions }
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -23,27 +23,45 @@ module Solana::Ruby::Kit
|
|
|
23
23
|
# Called after instructions are appended to a message. Must return the
|
|
24
24
|
# (possibly further updated) message. Defaults to identity.
|
|
25
25
|
#
|
|
26
|
-
#
|
|
26
|
+
# max_instructions_per_transaction: Integer (optional)
|
|
27
|
+
# Maximum number of top-level instructions allowed in each planned transaction.
|
|
28
|
+
# Must be a positive integer no greater than 64. Defaults to 16. Can be overridden
|
|
29
|
+
# per-call via the returned planner's `max_instructions_per_transaction:` kwarg.
|
|
30
|
+
#
|
|
31
|
+
# The returned planner is a lambda:
|
|
32
|
+
# planner.call(instruction_plan, max_instructions_per_transaction: nil) -> TransactionPlan
|
|
27
33
|
#
|
|
28
34
|
# Mirrors `createTransactionPlanner(config)` from @solana/instruction-plans.
|
|
29
35
|
# NOTE: Ruby translation is synchronous; abort-signal support is omitted.
|
|
30
36
|
sig do
|
|
31
37
|
params(
|
|
32
|
-
create_transaction_message:
|
|
33
|
-
on_transaction_message_updated:
|
|
38
|
+
create_transaction_message: T.untyped,
|
|
39
|
+
on_transaction_message_updated: T.untyped,
|
|
40
|
+
max_instructions_per_transaction: T.nilable(Integer)
|
|
34
41
|
).returns(T.untyped)
|
|
35
42
|
end
|
|
36
43
|
def create_transaction_planner(
|
|
37
44
|
create_transaction_message:,
|
|
38
|
-
on_transaction_message_updated: ->(msg) { msg }
|
|
45
|
+
on_transaction_message_updated: ->(msg) { msg },
|
|
46
|
+
max_instructions_per_transaction: nil
|
|
39
47
|
)
|
|
40
48
|
ctx = {
|
|
41
49
|
create_transaction_message: create_transaction_message,
|
|
42
50
|
on_transaction_message_updated: on_transaction_message_updated
|
|
43
51
|
}
|
|
52
|
+
config_max_instructions_per_transaction = max_instructions_per_transaction
|
|
53
|
+
|
|
54
|
+
->(instruction_plan, max_instructions_per_transaction: nil) {
|
|
55
|
+
resolved_max = max_instructions_per_transaction || config_max_instructions_per_transaction
|
|
56
|
+
|
|
57
|
+
# Reject up front any configured maximum the transaction format could never satisfy,
|
|
58
|
+
# rather than discovering it mid-plan when a message fails to compile.
|
|
59
|
+
InstructionPlans.assert_valid_max_instructions_per_transaction(resolved_max)
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
mutable = planner_traverse(
|
|
62
|
+
instruction_plan,
|
|
63
|
+
ctx.merge(parent: nil, parent_candidates: [], max_instructions_per_transaction: resolved_max)
|
|
64
|
+
)
|
|
47
65
|
Kernel.raise SolanaError.new(SolanaError::INSTRUCTION_PLANS__EMPTY_INSTRUCTION_PLAN) unless mutable
|
|
48
66
|
planner_freeze(mutable)
|
|
49
67
|
}
|
|
@@ -136,7 +154,7 @@ module Solana::Ruby::Kit
|
|
|
136
154
|
candidates = ctx[:parent_candidates].dup
|
|
137
155
|
|
|
138
156
|
until packer.done?
|
|
139
|
-
predicate = ->(msg) { packer.pack_message_to_capacity(msg) }
|
|
157
|
+
predicate = ->(msg) { packer.pack_message_to_capacity(msg, max_instructions: ctx[:max_instructions_per_transaction]) }
|
|
140
158
|
candidate = planner_select_and_mutate_candidate(ctx, candidates, &predicate)
|
|
141
159
|
unless candidate
|
|
142
160
|
msg = planner_create_new_message(ctx, &predicate)
|
|
@@ -162,11 +180,18 @@ module Solana::Ruby::Kit
|
|
|
162
180
|
begin
|
|
163
181
|
updated = ctx[:on_transaction_message_updated].call(predicate.call(candidate[:message]))
|
|
164
182
|
if Transactions.get_transaction_message_size(updated) <= Transactions::TRANSACTION_SIZE_LIMIT
|
|
183
|
+
InstructionPlans.assert_max_instructions_per_transaction(
|
|
184
|
+
updated.instructions.length,
|
|
185
|
+
InstructionPlans.resolve_max_instructions(ctx[:max_instructions_per_transaction])
|
|
186
|
+
)
|
|
165
187
|
candidate[:message] = updated
|
|
166
188
|
return candidate
|
|
167
189
|
end
|
|
168
190
|
rescue SolanaError => e
|
|
169
|
-
next if
|
|
191
|
+
next if [
|
|
192
|
+
SolanaError::INSTRUCTION_PLANS__MESSAGE_CANNOT_ACCOMMODATE_PLAN,
|
|
193
|
+
SolanaError::INSTRUCTION_PLANS__MAX_INSTRUCTIONS_PER_TRANSACTION_EXCEEDED
|
|
194
|
+
].include?(e.code)
|
|
170
195
|
Kernel.raise
|
|
171
196
|
end
|
|
172
197
|
end
|
|
@@ -187,6 +212,11 @@ module Solana::Ruby::Kit
|
|
|
187
212
|
)
|
|
188
213
|
end
|
|
189
214
|
|
|
215
|
+
InstructionPlans.assert_max_instructions_per_transaction(
|
|
216
|
+
updated_msg.instructions.length,
|
|
217
|
+
InstructionPlans.resolve_max_instructions(ctx[:max_instructions_per_transaction])
|
|
218
|
+
)
|
|
219
|
+
|
|
190
220
|
updated_msg
|
|
191
221
|
end
|
|
192
222
|
|
|
@@ -205,11 +235,21 @@ module Solana::Ruby::Kit
|
|
|
205
235
|
num_free_bytes: Transactions::TRANSACTION_SIZE_LIMIT - base_size }
|
|
206
236
|
)
|
|
207
237
|
end
|
|
238
|
+
InstructionPlans.assert_max_instructions_per_transaction(
|
|
239
|
+
updated.instructions.length,
|
|
240
|
+
InstructionPlans.resolve_max_instructions(ctx[:max_instructions_per_transaction])
|
|
241
|
+
)
|
|
208
242
|
updated
|
|
209
243
|
when :message_packer
|
|
210
244
|
packer = plan.get_message_packer.call
|
|
211
245
|
msg = message
|
|
212
|
-
|
|
246
|
+
until packer.done?
|
|
247
|
+
msg = packer.pack_message_to_capacity(msg, max_instructions: ctx[:max_instructions_per_transaction])
|
|
248
|
+
InstructionPlans.assert_max_instructions_per_transaction(
|
|
249
|
+
msg.instructions.length,
|
|
250
|
+
InstructionPlans.resolve_max_instructions(ctx[:max_instructions_per_transaction])
|
|
251
|
+
)
|
|
252
|
+
end
|
|
213
253
|
msg
|
|
214
254
|
else
|
|
215
255
|
Kernel.raise SolanaError.new(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# Mirrors @solana/instruction-plans.
|
|
5
5
|
# An InstructionPlan describes operations that may span multiple transactions.
|
|
6
|
+
require_relative 'instruction_plans/max_instructions'
|
|
6
7
|
require_relative 'instruction_plans/instruction_plan'
|
|
7
8
|
require_relative 'instruction_plans/transaction_plan'
|
|
8
9
|
require_relative 'instruction_plans/transaction_plan_result'
|
|
@@ -9,7 +9,9 @@ module Solana::Ruby::Kit
|
|
|
9
9
|
InflationReward = T.let(
|
|
10
10
|
Struct.new(
|
|
11
11
|
:amount, # Integer (Lamports) — reward credited
|
|
12
|
-
:commission, # Integer — vote account commission at reward time
|
|
12
|
+
:commission, # Integer, nilable — vote account commission at reward time;
|
|
13
|
+
# null once the vote account reports commission through
|
|
14
|
+
# getVoteAccounts' inflationRewardsCommissionBps instead
|
|
13
15
|
:effective_slot, # Integer — slot in which rewards were delivered
|
|
14
16
|
:epoch, # Integer — epoch for which reward occurred
|
|
15
17
|
:post_balance, # Integer (Lamports) — post-reward account balance
|
|
@@ -46,7 +48,7 @@ module Solana::Ruby::Kit
|
|
|
46
48
|
|
|
47
49
|
InflationReward.new(
|
|
48
50
|
amount: Kernel.Integer(entry['amount']),
|
|
49
|
-
commission: Kernel.Integer(entry['commission']),
|
|
51
|
+
commission: entry['commission'] ? Kernel.Integer(entry['commission']) : nil,
|
|
50
52
|
effective_slot: Kernel.Integer(entry['effectiveSlot']),
|
|
51
53
|
epoch: Kernel.Integer(entry['epoch']),
|
|
52
54
|
post_balance: Kernel.Integer(entry['postBalance'])
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Solana::Ruby::Kit
|
|
5
|
+
module Rpc
|
|
6
|
+
module Api
|
|
7
|
+
# One signature-level entry returned by getTransactionsForAddress when
|
|
8
|
+
# `transaction_details` is `:signatures`.
|
|
9
|
+
# Mirrors TypeScript's GetTransactionsForAddressSignature.
|
|
10
|
+
TransactionsForAddressSignature = T.let(
|
|
11
|
+
Struct.new(
|
|
12
|
+
:block_time, # Integer | nil — Unix timestamp of block production, nil if unavailable
|
|
13
|
+
:confirmation_status, # Symbol | nil — :processed, :confirmed, or :finalized
|
|
14
|
+
:err, # Hash | nil — transaction error, nil if succeeded
|
|
15
|
+
:memo, # String | nil — memo associated with the transaction
|
|
16
|
+
:signature, # String — base-58 encoded transaction signature
|
|
17
|
+
:slot, # Integer — slot containing the transaction
|
|
18
|
+
:transaction_index, # Integer — 0-based index within the block
|
|
19
|
+
keyword_init: true
|
|
20
|
+
),
|
|
21
|
+
T.untyped
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# The result envelope shared by every mode of getTransactionsForAddress.
|
|
25
|
+
#
|
|
26
|
+
# `data` holds the matching transactions in the requested sort order. Its
|
|
27
|
+
# element type depends on `transaction_details`: TransactionsForAddressSignature
|
|
28
|
+
# structs for `:signatures`, and raw (String-keyed) response hashes for
|
|
29
|
+
# `:full` — the same shape `Rpc::Api::GetTransaction#get_transaction`
|
|
30
|
+
# returns, so each element can be handed straight to
|
|
31
|
+
# `TransactionIntrospection.decode_transaction_from_rpc_response`.
|
|
32
|
+
#
|
|
33
|
+
# `pagination_token` is the cursor to pass back as `pagination_token` on a
|
|
34
|
+
# subsequent call, or nil when there are no more results.
|
|
35
|
+
#
|
|
36
|
+
# Mirrors TypeScript's GetTransactionsForAddressApiResponse.
|
|
37
|
+
TransactionsForAddressPage = T.let(
|
|
38
|
+
Struct.new(
|
|
39
|
+
:data, # Array<TransactionsForAddressSignature> | Array<Hash>
|
|
40
|
+
:pagination_token, # String | nil — cursor for the next page
|
|
41
|
+
keyword_init: true
|
|
42
|
+
),
|
|
43
|
+
T.untyped
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Returns confirmed transactions that load the given address, with
|
|
47
|
+
# server-side filtering, sorting, and cursor-based pagination.
|
|
48
|
+
#
|
|
49
|
+
# This combines the discovery step of getSignaturesForAddress with
|
|
50
|
+
# filtering and pagination the server applies itself. Note: check your RPC
|
|
51
|
+
# provider for support for this method.
|
|
52
|
+
#
|
|
53
|
+
# Mirrors TypeScript's GetTransactionsForAddressApi.getTransactionsForAddress.
|
|
54
|
+
# See https://solana.com/docs/rpc/http/gettransactionsforaddress
|
|
55
|
+
module GetTransactionsForAddress
|
|
56
|
+
extend T::Sig
|
|
57
|
+
|
|
58
|
+
# Maps the snake_case keys of a Ruby +filters+ hash onto the wire names.
|
|
59
|
+
# Comparison operators (:eq, :gt, :gte, :lt, :lte) pass through as-is.
|
|
60
|
+
FILTER_KEYS = T.let(
|
|
61
|
+
{
|
|
62
|
+
block_time: 'blockTime',
|
|
63
|
+
signature: 'signature',
|
|
64
|
+
slot: 'slot',
|
|
65
|
+
status: 'status',
|
|
66
|
+
token_accounts: 'tokenAccounts'
|
|
67
|
+
}.freeze,
|
|
68
|
+
T::Hash[Symbol, String]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
COMPARISON_KEYS = T.let(%i[eq gt gte lt lte].freeze, T::Array[Symbol])
|
|
72
|
+
|
|
73
|
+
# @param address [String] base-58 address to search for.
|
|
74
|
+
# @param transaction_details [Symbol] :signatures (default) or :full.
|
|
75
|
+
# @param encoding [String, nil] 'base58', 'base64', 'json' (server default),
|
|
76
|
+
# or 'jsonParsed'. Only valid with `transaction_details: :full`.
|
|
77
|
+
# @param max_supported_transaction_version [Integer, nil] newest transaction
|
|
78
|
+
# version to accept. Omit to receive only legacy transactions (in which
|
|
79
|
+
# case no `version` is returned). Only valid with `transaction_details: :full`.
|
|
80
|
+
# @param commitment [Symbol, nil] :confirmed or :finalized. This method does
|
|
81
|
+
# not support :processed.
|
|
82
|
+
# @param filters [Hash, nil] server-side filters, combined with AND. Supports
|
|
83
|
+
# `block_time:` ({eq/gt/gte/lt/lte}), `signature:` ({gt/gte/lt/lte}),
|
|
84
|
+
# `slot:` ({gt/gte/lt/lte}), `status:` (:any, :succeeded, :failed), and
|
|
85
|
+
# `token_accounts:` (:none, :balance_changed, :all).
|
|
86
|
+
# @param limit [Integer, nil] max results. The server caps this at 1000 for
|
|
87
|
+
# `:signatures` and 100 for `:full`.
|
|
88
|
+
# @param min_context_slot [Integer, nil] reject data older than this slot.
|
|
89
|
+
# @param pagination_token [String, nil] cursor from a previous response's
|
|
90
|
+
# `pagination_token`, formatted "<slot>:<position>".
|
|
91
|
+
# @param sort_order [Symbol, nil] :desc (newest first, server default) or :asc.
|
|
92
|
+
# @return [TransactionsForAddressPage]
|
|
93
|
+
sig do
|
|
94
|
+
params(
|
|
95
|
+
address: String,
|
|
96
|
+
transaction_details: Symbol,
|
|
97
|
+
encoding: T.nilable(String),
|
|
98
|
+
max_supported_transaction_version: T.nilable(Integer),
|
|
99
|
+
commitment: T.nilable(Symbol),
|
|
100
|
+
filters: T.nilable(T::Hash[Symbol, T.untyped]),
|
|
101
|
+
limit: T.nilable(Integer),
|
|
102
|
+
min_context_slot: T.nilable(Integer),
|
|
103
|
+
pagination_token: T.nilable(String),
|
|
104
|
+
sort_order: T.nilable(Symbol)
|
|
105
|
+
).returns(T.untyped)
|
|
106
|
+
end
|
|
107
|
+
def get_transactions_for_address(
|
|
108
|
+
address,
|
|
109
|
+
transaction_details: :signatures,
|
|
110
|
+
encoding: nil,
|
|
111
|
+
max_supported_transaction_version: nil,
|
|
112
|
+
commitment: nil,
|
|
113
|
+
filters: nil,
|
|
114
|
+
limit: nil,
|
|
115
|
+
min_context_slot: nil,
|
|
116
|
+
pagination_token: nil,
|
|
117
|
+
sort_order: nil
|
|
118
|
+
)
|
|
119
|
+
unless %i[signatures full].include?(transaction_details)
|
|
120
|
+
Kernel.raise ArgumentError, "transaction_details must be :signatures or :full, got #{transaction_details.inspect}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The RPC rejects `processed` for this method; TypeScript encodes that
|
|
124
|
+
# as `Exclude<Commitment, 'processed'>`, so fail fast here too.
|
|
125
|
+
if commitment == :processed
|
|
126
|
+
Kernel.raise ArgumentError, 'getTransactionsForAddress does not support the :processed commitment'
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if transaction_details == :signatures
|
|
130
|
+
if encoding
|
|
131
|
+
Kernel.raise ArgumentError, 'encoding is only valid with transaction_details: :full'
|
|
132
|
+
end
|
|
133
|
+
if max_supported_transaction_version
|
|
134
|
+
Kernel.raise ArgumentError, 'max_supported_transaction_version is only valid with transaction_details: :full'
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
config = { 'transactionDetails' => transaction_details.to_s }
|
|
139
|
+
config['encoding'] = encoding if encoding
|
|
140
|
+
config['maxSupportedTransactionVersion'] = max_supported_transaction_version if max_supported_transaction_version
|
|
141
|
+
config['commitment'] = commitment.to_s if commitment
|
|
142
|
+
config['limit'] = limit if limit
|
|
143
|
+
config['minContextSlot'] = min_context_slot if min_context_slot
|
|
144
|
+
config['paginationToken'] = pagination_token if pagination_token
|
|
145
|
+
config['sortOrder'] = sort_order.to_s if sort_order
|
|
146
|
+
config['filters'] = build_filters(filters) if filters && !filters.empty?
|
|
147
|
+
|
|
148
|
+
raw = transport.request('getTransactionsForAddress', [address, config])
|
|
149
|
+
rows = raw['data'] || []
|
|
150
|
+
|
|
151
|
+
data = if transaction_details == :full
|
|
152
|
+
# Returned verbatim so each element can be fed to
|
|
153
|
+
# TransactionIntrospection.decode_transaction_from_rpc_response.
|
|
154
|
+
rows
|
|
155
|
+
else
|
|
156
|
+
rows.map do |tx|
|
|
157
|
+
TransactionsForAddressSignature.new(
|
|
158
|
+
block_time: tx['blockTime'],
|
|
159
|
+
confirmation_status: tx['confirmationStatus']&.to_sym,
|
|
160
|
+
err: tx['err'],
|
|
161
|
+
memo: tx['memo'],
|
|
162
|
+
signature: tx['signature'],
|
|
163
|
+
slot: Kernel.Integer(tx['slot']),
|
|
164
|
+
transaction_index: tx['transactionIndex'] && Kernel.Integer(tx['transactionIndex'])
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
TransactionsForAddressPage.new(data: data, pagination_token: raw['paginationToken'])
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# ── Private helpers ────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
# Converts the snake_case +filters+ hash into its wire form. Symbol values
|
|
175
|
+
# (`status`, `token_accounts`) are lowerCamelCased so `:balance_changed`
|
|
176
|
+
# reaches the server as `"balanceChanged"`.
|
|
177
|
+
sig { params(filters: T::Hash[Symbol, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
|
178
|
+
def build_filters(filters)
|
|
179
|
+
filters.each_with_object({}) do |(key, value), out|
|
|
180
|
+
wire_key = FILTER_KEYS[key]
|
|
181
|
+
Kernel.raise ArgumentError, "unknown filter #{key.inspect}" if wire_key.nil?
|
|
182
|
+
|
|
183
|
+
out[wire_key] = if value.is_a?(Hash)
|
|
184
|
+
value.each_with_object({}) do |(op, operand), cmp|
|
|
185
|
+
unless COMPARISON_KEYS.include?(op)
|
|
186
|
+
Kernel.raise ArgumentError, "unknown comparison #{op.inspect} in filter #{key.inspect}"
|
|
187
|
+
end
|
|
188
|
+
cmp[op.to_s] = operand
|
|
189
|
+
end
|
|
190
|
+
elsif value.is_a?(Symbol)
|
|
191
|
+
camelize(value)
|
|
192
|
+
else
|
|
193
|
+
value
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
private :build_filters
|
|
198
|
+
|
|
199
|
+
sig { params(value: Symbol).returns(String) }
|
|
200
|
+
def camelize(value)
|
|
201
|
+
head, *rest = value.to_s.split('_')
|
|
202
|
+
([head] + rest.map(&:capitalize)).join
|
|
203
|
+
end
|
|
204
|
+
private :camelize
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -12,6 +12,7 @@ module Solana::Ruby::Kit
|
|
|
12
12
|
:activated_stake,
|
|
13
13
|
:epoch_vote_account,
|
|
14
14
|
:commission,
|
|
15
|
+
:inflation_rewards_commission_bps,
|
|
15
16
|
:last_vote,
|
|
16
17
|
:epoch_credits,
|
|
17
18
|
:root_slot,
|
|
@@ -45,6 +46,7 @@ module Solana::Ruby::Kit
|
|
|
45
46
|
activated_stake: Kernel.Integer(v['activatedStake']),
|
|
46
47
|
epoch_vote_account: v['epochVoteAccount'],
|
|
47
48
|
commission: Kernel.Integer(v['commission']),
|
|
49
|
+
inflation_rewards_commission_bps: v['inflationRewardsCommissionBps'] ? Kernel.Integer(v['inflationRewardsCommissionBps']) : nil,
|
|
48
50
|
last_vote: Kernel.Integer(v['lastVote']),
|
|
49
51
|
epoch_credits: v['epochCredits'],
|
|
50
52
|
root_slot: v['rootSlot'] ? Kernel.Integer(v['rootSlot']) : nil
|
|
@@ -24,6 +24,7 @@ require_relative 'api/get_block_time'
|
|
|
24
24
|
require_relative 'api/get_epoch_schedule'
|
|
25
25
|
require_relative 'api/get_inflation_reward'
|
|
26
26
|
require_relative 'api/get_signatures_for_address'
|
|
27
|
+
require_relative 'api/get_transactions_for_address'
|
|
27
28
|
|
|
28
29
|
module Solana::Ruby::Kit
|
|
29
30
|
module Rpc
|
|
@@ -64,6 +65,7 @@ module Solana::Ruby::Kit
|
|
|
64
65
|
include Api::GetEpochSchedule
|
|
65
66
|
include Api::GetInflationReward
|
|
66
67
|
include Api::GetSignaturesForAddress
|
|
68
|
+
include Api::GetTransactionsForAddress
|
|
67
69
|
|
|
68
70
|
sig { returns(Transport) }
|
|
69
71
|
attr_reader :transport
|