solace 0.1.7 → 0.1.9

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.
@@ -62,6 +62,7 @@ module Solace
62
62
  def initialize
63
63
  @header = []
64
64
  @accounts = []
65
+ @loaded_accounts = []
65
66
  @pubkey_account_map = {}
66
67
  end
67
68
 
@@ -100,6 +101,27 @@ module Solace
100
101
  merge_account(pubkey, signer: false, writable: false)
101
102
  end
102
103
 
104
+ # Add or merge an account into the context
105
+ #
106
+ # The primitive the +add_*+ methods above name a role for; call it
107
+ # directly when the flags are data in hand. Permissions only ever widen:
108
+ # an account added twice keeps the union of what it was given.
109
+ #
110
+ # @param pubkey [#to_s, PublicKey] The public key of the account
111
+ # @param signer [Boolean] Whether the account is a signer
112
+ # @param writable [Boolean] Whether the account is writable
113
+ # @param fee_payer [Boolean] Whether the account pays the fee (see {#set_fee_payer})
114
+ def merge_account(pubkey, signer:, writable:, fee_payer: false)
115
+ pubkey_str = pubkey.to_s
116
+
117
+ @pubkey_account_map[pubkey_str] ||= DEFAULT_ACCOUNT.dup
118
+ @pubkey_account_map[pubkey_str][:signer] ||= signer
119
+ @pubkey_account_map[pubkey_str][:writable] ||= writable
120
+ @pubkey_account_map[pubkey_str][:fee_payer] ||= fee_payer
121
+
122
+ self
123
+ end
124
+
103
125
  # Predicate to check if an account is a fee payer
104
126
  #
105
127
  # @param pubkey [String] The pubkey of the account
@@ -173,60 +195,69 @@ module Solace
173
195
  # - Then writable accounts
174
196
  # - Then readonly accounts
175
197
  #
176
- # @return [Hash] The compiled accounts and header
177
- def compile
178
- self.header = calculate_header
179
- self.accounts = order_accounts
198
+ # For a v0 (versioned) transaction, accounts resolved through lookup tables
199
+ # leave the static account list entirely: the message carries only the
200
+ # static keys, and the runtime rebuilds the combined space
201
+ # [static..., loaded...] at execution time. Passing them keeps {#index_of}
202
+ # resolving against that combined space (so instructions index correctly)
203
+ # while dropping them from the static accounts and the header. They must be
204
+ # ordered writable-first to match the runtime's combined space. With no
205
+ # loaded accounts (the default) this is the legacy compilation, unchanged.
206
+ #
207
+ # @param loaded_accounts [Array<String>] Pubkeys resolved through lookup tables
208
+ # @return [AccountContext] Self
209
+ def compile(loaded_accounts: [])
210
+ @loaded_accounts = loaded_accounts
211
+
212
+ self.header = calculate_header(loaded_accounts)
213
+ self.accounts = order_accounts(loaded_accounts)
180
214
  self
181
215
  end
182
216
 
183
- # Index of a pubkey in the accounts array
217
+ # Index of a pubkey in the combined account space
184
218
  #
185
219
  # @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
220
+ # @return [Integer] The index in the combined space, or -1 if not found
187
221
  def index_of(pubkey_str)
188
222
  indices[pubkey_str] || -1
189
223
  end
190
224
 
191
- # Get map of indicies for pubkeys in accounts array
225
+ # Map of pubkey => index across the combined space (static keys followed by
226
+ # any loaded accounts), so instruction indices resolve the same way the
227
+ # runtime does once lookup tables are expanded.
192
228
  #
193
- # @return [Hash{String => Integer}] The indices of the pubkeys in the accounts array
229
+ # @return [Hash{String => Integer}] The indices of the pubkeys
194
230
  def indices
195
- accounts.each_with_index.to_h
231
+ (accounts + @loaded_accounts).each_with_index.to_h
196
232
  end
197
233
 
198
234
  private
199
235
 
200
- # Add or merge an account into the context
236
+ # Order the static accounts by signer, writable, readonly signer, readonly
201
237
  #
202
- # @param pubkey [#to_s, PublicKey] 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
238
+ # Loaded accounts are excluded — they leave the static account list and are
239
+ # resolved through lookup tables at runtime.
240
+ #
241
+ # @param loaded_accounts [Array<String>] Pubkeys resolved through lookup tables
242
+ # @return [Array<String>] The ordered static accounts
243
+ def order_accounts(loaded_accounts)
244
+ # Keyed on the position too: sort_by is not stable, and accounts of the
245
+ # same rank must keep the order they were added in
246
+ (@pubkey_account_map.keys - loaded_accounts).sort_by.with_index { |pubkey, index| [rank_of(pubkey), index] }
215
247
  end
216
248
 
217
- # Order accounts by signer, writable, readonly signer, readonly
249
+ # The position of an account's segment in the static account list
218
250
  #
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
251
+ # @param pubkey [String] The pubkey of the account
252
+ # @return [Integer] The segment rank
253
+ def rank_of(pubkey)
254
+ if fee_payer?(pubkey) then 0
255
+ elsif writable_signer?(pubkey) then 1
256
+ elsif readonly_signer?(pubkey) then 2
257
+ elsif writable_nonsigner?(pubkey) then 3
258
+ elsif readonly_nonsigner?(pubkey) then 4
259
+ else
260
+ raise ArgumentError, "Unknown account type for pubkey: #{pubkey}"
230
261
  end
231
262
  end
232
263
 
@@ -237,9 +268,14 @@ module Solace
237
268
  # - The number of readonly signers
238
269
  # - The number of readonly unsigned accounts
239
270
  #
271
+ # Loaded accounts no longer occupy the static account list, so they drop
272
+ # out of the header. They are never signers, so only the readonly-unsigned
273
+ # count changes.
274
+ #
275
+ # @param loaded_accounts [Array<String>] Pubkeys resolved through lookup tables
240
276
  # @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|
277
+ def calculate_header(loaded_accounts)
278
+ (@pubkey_account_map.keys - loaded_accounts).each_with_object([0, 0, 0]) do |pubkey, acc|
243
279
  acc[0] += 1 if signer?(pubkey)
244
280
 
245
281
  if readonly_signer?(pubkey) then acc[1] += 1
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../composers/base'
4
+ require_relative '../composers/compute_budget_program_set_compute_unit_limit_composer'
5
+ require_relative '../composers/compute_budget_program_set_compute_unit_price_composer'
6
+
7
+ module Solace
8
+ module Utils
9
+ # The compute budget a transaction composer carries as a setting
10
+ #
11
+ # Holds the compute unit limit and price, answers the ComputeBudget composers
12
+ # they translate to (limit then price), and decides which directly added
13
+ # ComputeBudget composers the setting supersedes so the budget is never
14
+ # declared twice. Instances are immutable; {#merge} answers a new one.
15
+ #
16
+ # @example
17
+ # budget = Solace::Utils::ComputeBudget.new(units: 200_000, micro_lamports: 50_000)
18
+ # budget.composers # => [SetComputeUnitLimit composer, SetComputeUnitPrice composer]
19
+ #
20
+ # @see Solace::TransactionComposer#set_compute_budget
21
+ # @since 0.1.9
22
+ class ComputeBudget
23
+ # The composer a compute unit limit translates to
24
+ LIMIT_COMPOSER = Composers::ComputeBudgetProgramSetComputeUnitLimitComposer
25
+ private_constant :LIMIT_COMPOSER
26
+
27
+ # The composer a compute unit price translates to
28
+ PRICE_COMPOSER = Composers::ComputeBudgetProgramSetComputeUnitPriceComposer
29
+ private_constant :PRICE_COMPOSER
30
+
31
+ # @!attribute units
32
+ # The compute unit limit, or nil when none is set
33
+ attr_reader :units
34
+
35
+ # @!attribute micro_lamports
36
+ # The compute unit price in micro-lamports, or nil when none is set
37
+ attr_reader :micro_lamports
38
+
39
+ # Initialize the compute budget
40
+ #
41
+ # @param units [Integer, nil] The compute unit limit
42
+ # @param micro_lamports [Integer, nil] The compute unit price in micro-lamports
43
+ def initialize(units: nil, micro_lamports: nil)
44
+ @units = units
45
+ @micro_lamports = micro_lamports
46
+ end
47
+
48
+ # Whether any part of the budget is set
49
+ #
50
+ # @return [Boolean]
51
+ def set?
52
+ !(units.nil? && micro_lamports.nil?)
53
+ end
54
+
55
+ # The ComputeBudget composers the budget translates to, limit then price
56
+ #
57
+ # @return [Array<Composers::Base>] The composers, empty when nothing is set
58
+ def composers
59
+ [
60
+ (LIMIT_COMPOSER.new(units: units) if units),
61
+ (PRICE_COMPOSER.new(micro_lamports: micro_lamports) if micro_lamports)
62
+ ].compact
63
+ end
64
+
65
+ # Whether a directly added composer is replaced by a setting of the same kind
66
+ #
67
+ # @param composer [Composers::Base] The added composer
68
+ # @return [Boolean]
69
+ def supersedes?(composer)
70
+ (!units.nil? && composer.is_a?(LIMIT_COMPOSER)) || (!micro_lamports.nil? && composer.is_a?(PRICE_COMPOSER))
71
+ end
72
+
73
+ # A budget with the other's settings where it has them, and this one's otherwise
74
+ #
75
+ # @param other [ComputeBudget] The budget to fold in
76
+ # @return [ComputeBudget] The merged budget
77
+ def merge(other)
78
+ self.class.new(
79
+ units: other.units || units,
80
+ micro_lamports: other.micro_lamports || micro_lamports
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Solace
4
4
  # Latest version of the Solace gem.
5
- VERSION = '0.1.7'
5
+ VERSION = '0.1.9'
6
6
  end
data/lib/solace.rb CHANGED
@@ -35,11 +35,14 @@ require_relative 'solace/version'
35
35
  require_relative 'solace/errors'
36
36
  require_relative 'solace/constants'
37
37
  require_relative 'solace/connection'
38
+ require_relative 'solace/concerns/binary_serializable'
39
+
40
+ # Utils
38
41
  require_relative 'solace/utils/codecs'
39
42
  require_relative 'solace/utils/pda'
40
43
  require_relative 'solace/utils/account_context'
44
+ require_relative 'solace/utils/compute_budget'
41
45
  require_relative 'solace/utils/curve25519_dalek'
42
- require_relative 'solace/concerns/binary_serializable'
43
46
 
44
47
  # Tokens
45
48
  require_relative 'solace/tokens'
@@ -55,7 +58,12 @@ require_relative 'solace/transaction'
55
58
  require_relative 'solace/message'
56
59
  require_relative 'solace/instruction'
57
60
  require_relative 'solace/address_lookup_table'
61
+
62
+ # Accounts (on-chain account models)
63
+ require_relative 'solace/accounts/address_lookup_table'
64
+
58
65
  require_relative 'solace/transaction_composer'
66
+ require_relative 'solace/transaction_decomposer'
59
67
 
60
68
  # Base Classes (Abstract classes)
61
69
  require_relative 'solace/programs/base'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Scholl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base58
@@ -201,13 +201,18 @@ executables: []
201
201
  extensions: []
202
202
  extra_rdoc_files: []
203
203
  files:
204
+ - lib/rubocop/cop/solace/exploded_hash.rb
204
205
  - lib/solace.rb
206
+ - lib/solace/accounts/address_lookup_table.rb
205
207
  - lib/solace/address_lookup_table.rb
208
+ - lib/solace/composers/address_lookup_table_program_create_composer.rb
209
+ - lib/solace/composers/address_lookup_table_program_extend_composer.rb
206
210
  - lib/solace/composers/associated_token_account_program_create_account_composer.rb
207
211
  - lib/solace/composers/associated_token_account_program_create_idempotent_account_composer.rb
208
212
  - lib/solace/composers/base.rb
209
213
  - lib/solace/composers/compute_budget_program_set_compute_unit_limit_composer.rb
210
214
  - lib/solace/composers/compute_budget_program_set_compute_unit_price_composer.rb
215
+ - lib/solace/composers/opaque_instruction_composer.rb
211
216
  - lib/solace/composers/spl_token_program_close_account_composer.rb
212
217
  - lib/solace/composers/spl_token_program_initialize_mint_composer.rb
213
218
  - lib/solace/composers/spl_token_program_mint_to_composer.rb
@@ -224,6 +229,7 @@ files:
224
229
  - lib/solace/connection.rb
225
230
  - lib/solace/constants.rb
226
231
  - lib/solace/errors.rb
232
+ - lib/solace/errors/address_lookup_table_not_found.rb
227
233
  - lib/solace/errors/confirmation_timeout.rb
228
234
  - lib/solace/errors/connection_error.rb
229
235
  - lib/solace/errors/error.rb
@@ -231,6 +237,8 @@ files:
231
237
  - lib/solace/errors/parse_error.rb
232
238
  - lib/solace/errors/rpc_error.rb
233
239
  - lib/solace/instruction.rb
240
+ - lib/solace/instructions/address_lookup_table_program/create_lookup_table_instruction.rb
241
+ - lib/solace/instructions/address_lookup_table_program/extend_lookup_table_instruction.rb
234
242
  - lib/solace/instructions/associated_token_account/create_account_instruction.rb
235
243
  - lib/solace/instructions/associated_token_account/create_idempotent_account_instruction.rb
236
244
  - lib/solace/instructions/compute_budget/set_compute_unit_limit_instruction.rb
@@ -271,8 +279,10 @@ files:
271
279
  - lib/solace/tokens/token.rb
272
280
  - lib/solace/transaction.rb
273
281
  - lib/solace/transaction_composer.rb
282
+ - lib/solace/transaction_decomposer.rb
274
283
  - lib/solace/utils/account_context.rb
275
284
  - lib/solace/utils/codecs.rb
285
+ - lib/solace/utils/compute_budget.rb
276
286
  - lib/solace/utils/curve25519_dalek.rb
277
287
  - lib/solace/utils/libcurve25519_dalek-linux-arm64/libcurve25519_dalek.so
278
288
  - lib/solace/utils/libcurve25519_dalek-linux/libcurve25519_dalek.so