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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab3415933c7875971ad410ecc1d8d7487233a7eb58e9c3701f23babdaae9fbd5
4
- data.tar.gz: a9ef2bbb03d7988487ae48bff51122dc45670e5ad6ef8c8d6154fa386d0a1281
3
+ metadata.gz: 2f6a793f450df697c4f216ab4e5a483d7a331f57a3fccb05ecd7c76e788fa4ed
4
+ data.tar.gz: 479fcb0c38b8e5ea8d82636922ab327ec68ae18455550658c79b06ddd11b2bbd
5
5
  SHA512:
6
- metadata.gz: 869108bf70f8a9143e28dae425a5e843135aedfd78642c39683fd214ab4a3d1c847125e0e2f0fc60b1766ac45970dc8f14bfc512861db95b89cdaaedc593226d
7
- data.tar.gz: 8ff9bcac1403007eb3dcc340587c76f4acb5dff266390621f16708589153644db16a16cdb0f08936f3d3a690c2943d78edd570735e9f0692eac6ffb62ee3c969
6
+ metadata.gz: ec35bdf08bf2ecdd235d25ec67f9c897a7a5c0a167c46d3f2dea8cfb21600b7c3523e236f9db644638fdb2aadd3b219a67296676d580d3ce2ef2ad1a9c8ebe00
7
+ data.tar.gz: 9647d86486a247042220f53b621591e2de5599868bb154350eb367a4d523301c60ac1735cc664c46ebb18e181f7095c9972748257d25373863d81c520ab7ba3a
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Solace
6
+ # Every pair in a braced hash starts on its own line.
7
+ #
8
+ # A hash is read as a table: a reader scanning for one key, or diffing two
9
+ # of them, works down the left edge. Collapsing pairs onto a shared line
10
+ # hides a key mid-line and makes a one-key change show up as a rewritten
11
+ # line. Length is not the test — a short hash collapses just as badly as a
12
+ # long one, which is why the built-in layout cops are not enough: they only
13
+ # act once a hash is already multi-line.
14
+ #
15
+ # Only a hash written on one line is this cop's business. Once a hash spans
16
+ # lines, Layout/FirstHashElementLineBreak and Layout/MultilineHashKeyLineBreaks
17
+ # already break every pair onto its own line, and a second cop inserting the
18
+ # same break in the same correction loop leaves a blank line behind.
19
+ #
20
+ # Two more restrictions keep it from reaching where it does not belong. A
21
+ # hash of one pair is left inline, since it is already the only thing on
22
+ # its line. And only BRACED hashes are checked: a keyword argument list is
23
+ # a hash node too, and exploding it would break up every multi-argument
24
+ # call in the codebase.
25
+ #
26
+ # @example
27
+ # # bad
28
+ # page_id: { type: 'string', format: 'uuid' }
29
+ #
30
+ # # good
31
+ # page_id: {
32
+ # type: 'string',
33
+ # format: 'uuid'
34
+ # }
35
+ #
36
+ # # good — one pair, and a keyword argument list
37
+ # { user: payload[:user_id] }
38
+ # create(:user, name: 'Ada', email: 'ada@example.com')
39
+ class ExplodedHash < Base
40
+ extend AutoCorrector
41
+
42
+ MSG = 'Each pair in a hash starts on its own line.'
43
+
44
+ def on_hash(node)
45
+ pairs = braced_pairs(node)
46
+ return unless pairs
47
+
48
+ pairs.each { |pair| flag(pair) }
49
+ end
50
+
51
+ private
52
+
53
+ # Nil for anything this cop leaves alone: a hash already spanning lines,
54
+ # a keyword argument list, which carries no braces, and a hash of a
55
+ # single pair.
56
+ def braced_pairs(node)
57
+ return unless node.loc.begin
58
+ return if node.multiline?
59
+
60
+ pairs = node.pairs
61
+ pairs if pairs.size >= 2
62
+ end
63
+
64
+ # The break replaces the space in front of the pair rather than being
65
+ # inserted before it, or the old separator is left behind as trailing
66
+ # whitespace. Indentation is left to Layout/FirstHashElementIndentation
67
+ # and Layout/HashAlignment, which run in the same correction loop.
68
+ def flag(pair)
69
+ add_offense(pair) { |corrector| corrector.replace(leading_space(pair), "\n") }
70
+ end
71
+
72
+ def leading_space(pair)
73
+ range = pair.source_range
74
+ source = range.source_buffer.source
75
+ start = range.begin_pos
76
+ start -= 1 while start.positive? && [' ', "\t"].include?(source[start - 1])
77
+ Parser::Source::Range.new(range.source_buffer, start, range.begin_pos)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ # On-chain account models — the account state living at an address, as opposed
5
+ # to the wire-level structures that reference it. Conventions for these types
6
+ # (fetching, deserializing, deriving) are expected to grow here.
7
+ module Accounts
8
+ # Models an on-chain Address Lookup Table account: its address, the metadata
9
+ # the program stores (authority, activation slots), and the full, ordered
10
+ # list of addresses it holds.
11
+ #
12
+ # This is distinct from {Solace::AddressLookupTable}, which is the *reference*
13
+ # a v0 message carries (a table account plus the writable/readonly index
14
+ # positions into it). A composer decides which of this account's addresses
15
+ # load — a v0 concern that needs the account context — and this object owns
16
+ # the table-local part: turning the addresses it contributes into that
17
+ # message reference.
18
+ #
19
+ # @example Register a table on a composer
20
+ # table = Solace::Accounts::AddressLookupTable.new(account: address, addresses: on_chain_addresses)
21
+ # table.reference(loaded_writable, loaded_readonly) # => Solace::AddressLookupTable or nil
22
+ #
23
+ # @example Read a table's on-chain state
24
+ # table = Solace::Accounts::AddressLookupTable.fetch(address, connection: connection)
25
+ # table&.addresses # => the stored addresses, or nil if the chain holds no such account
26
+ #
27
+ # @see Solace::AddressLookupTable
28
+ # @see Solace::TransactionComposer
29
+ # @since 0.1.8
30
+ class AddressLookupTable
31
+ # Byte offset at which the stored addresses begin — the program reserves a
32
+ # fixed-size metadata region ahead of them regardless of its contents.
33
+ META_SIZE = 56
34
+
35
+ # @!attribute [r] account
36
+ # @return [String, nil] The lookup table's on-chain address
37
+ attr_reader :account
38
+
39
+ # @!attribute [r] addresses
40
+ # @return [Array<String>] The full, ordered list of addresses stored in the table
41
+ attr_reader :addresses
42
+
43
+ # @!attribute [r] authority
44
+ # @return [String, nil] The authority allowed to extend/close the table
45
+ attr_reader :authority
46
+
47
+ # @!attribute [r] deactivation_slot
48
+ # @return [Integer, nil] The slot the table was deactivated in (max while active)
49
+ attr_reader :deactivation_slot
50
+
51
+ # @!attribute [r] last_extended_slot
52
+ # @return [Integer, nil] The slot the table was last extended in
53
+ attr_reader :last_extended_slot
54
+
55
+ class << self
56
+ # Fetch a lookup table account from chain
57
+ #
58
+ # Like {Solace::Connection#get_account_info}, answers nil when the chain
59
+ # holds no account at the address; the caller decides whether that is an error.
60
+ #
61
+ # @param account [#to_s, PublicKey] The lookup table's on-chain address
62
+ # @param connection [Solace::Connection] The connection to read it through
63
+ # @return [AddressLookupTable, nil] The table with its address set, or nil
64
+ def fetch(account, connection:)
65
+ info = connection.get_account_info(account.to_s)
66
+ return unless info
67
+
68
+ base64, = info['data'] # the RPC answers [data, encoding]
69
+ deserialize(Utils::Codecs.base64_to_bytestream(base64), account: account)
70
+ end
71
+
72
+ # Deserialize an on-chain lookup table account
73
+ #
74
+ # The BufferLayout is:
75
+ # - [State type (4 bytes, u32 LE)]
76
+ # - [Deactivation slot (8 bytes, u64 LE)]
77
+ # - [Last extended slot (8 bytes, u64 LE)]
78
+ # - [Last extended start index (1 byte)]
79
+ # - [Authority (Borsh Option<Pubkey>)]
80
+ # - [Padding, up to {META_SIZE}]
81
+ # - [Addresses (32 bytes each, to end of data)]
82
+ #
83
+ # The account data does not carry its own address, so pass it to have
84
+ # the table know where it lives.
85
+ #
86
+ # @param io [IO, StringIO] The account data to read from
87
+ # @param account [#to_s, PublicKey, nil] The lookup table's on-chain address
88
+ # @return [AddressLookupTable] The parsed table
89
+ def deserialize(io, account: nil)
90
+ Utils::Codecs.decode_le_u32(io) # state type (1 = lookup table); positional
91
+ deactivation_slot = Utils::Codecs.decode_le_u64(io)
92
+ last_extended_slot = Utils::Codecs.decode_le_u64(io)
93
+
94
+ Utils::Codecs.decode_u8(io) # last extended start index; positional
95
+ authority = Utils::Codecs.decode_option_pubkey(io)
96
+
97
+ io.seek(META_SIZE) # addresses begin after the fixed-size metadata region
98
+
99
+ addresses = []
100
+ addresses << Utils::Codecs.decode_pubkey(io) until io.eof?
101
+
102
+ new(
103
+ account: account,
104
+ deactivation_slot: deactivation_slot,
105
+ last_extended_slot: last_extended_slot,
106
+ authority: authority,
107
+ addresses: addresses
108
+ )
109
+ end
110
+ end
111
+
112
+ # Initialize a lookup table account
113
+ #
114
+ # @param account [#to_s, PublicKey, nil] The lookup table's on-chain address
115
+ # @param addresses [Array<#to_s>, nil] The full, ordered address list; may be
116
+ # omitted (the composer then loads nothing through this table)
117
+ # @param authority [String, nil] The table authority
118
+ # @param deactivation_slot [Integer, nil] The deactivation slot
119
+ # @param last_extended_slot [Integer, nil] The last extended slot
120
+ def initialize(account: nil, addresses: nil, authority: nil, deactivation_slot: nil, last_extended_slot: nil)
121
+ @account = account&.to_s
122
+ @addresses = Array(addresses).map(&:to_s)
123
+ @authority = authority
124
+ @deactivation_slot = deactivation_slot
125
+ @last_extended_slot = last_extended_slot
126
+ end
127
+
128
+ # Build the v0 message reference for the addresses this table contributes
129
+ #
130
+ # @param writable [Array<String>] Loaded writable pubkeys drawn from this table
131
+ # @param readonly [Array<String>] Loaded readonly pubkeys drawn from this table
132
+ # @return [Solace::AddressLookupTable, nil] The reference, or nil if it loads nothing
133
+ def reference(writable, readonly)
134
+ writable_indexes = positions_of(writable)
135
+ readonly_indexes = positions_of(readonly)
136
+ return if writable_indexes.empty? && readonly_indexes.empty?
137
+
138
+ Solace::AddressLookupTable.new.tap do |reference|
139
+ reference.account = account
140
+ reference.writable_indexes = writable_indexes
141
+ reference.readonly_indexes = readonly_indexes
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ # Positions of the given pubkeys within this table's address list
148
+ #
149
+ # @param pubkeys [Array<String>] The pubkeys to locate
150
+ # @return [Array<Integer>] Their positions in {#addresses}
151
+ def positions_of(pubkeys)
152
+ pubkeys.filter_map { |pubkey| addresses.index(pubkey) }
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Composers
5
+ # Composer for creating an address lookup table.
6
+ #
7
+ # Resolves and orders the accounts for a `CreateLookupTable` instruction and
8
+ # delegates construction to
9
+ # `Instructions::AddressLookupTableProgram::CreateLookupTableInstruction`.
10
+ #
11
+ # The table address is a program-derived address of `[authority, recent_slot]`;
12
+ # derive it (and its bump) with {Solace::Utils::PDA} and pass both in.
13
+ #
14
+ # Required accounts:
15
+ # - **Table**: the uninitialized table account (writable, non-signer)
16
+ # - **Authority**: controls the table (readonly, signer)
17
+ # - **Payer**: funds the table's rent (writable, signer)
18
+ # - **System program** (readonly, non-signer)
19
+ #
20
+ # @example
21
+ # composer = AddressLookupTableProgramCreateComposer.new(
22
+ # table: table_address,
23
+ # authority: authority,
24
+ # payer: payer,
25
+ # recent_slot: recent_slot,
26
+ # bump: bump
27
+ # )
28
+ #
29
+ # @see Instructions::AddressLookupTableProgram::CreateLookupTableInstruction
30
+ # @since 0.1.8
31
+ class AddressLookupTableProgramCreateComposer < Base
32
+ # @return [String] The table's on-chain address
33
+ def table
34
+ params[:table].to_s
35
+ end
36
+
37
+ # @return [String] The table authority
38
+ def authority
39
+ params[:authority].to_s
40
+ end
41
+
42
+ # @return [String] The rent payer
43
+ def payer
44
+ params[:payer].to_s
45
+ end
46
+
47
+ # @return [String] The system program id
48
+ def system_program
49
+ Solace::Constants::SYSTEM_PROGRAM_ID.to_s
50
+ end
51
+
52
+ # @return [String] The address lookup table program id
53
+ def lookup_table_program
54
+ Solace::Constants::ADDRESS_LOOKUP_TABLE_PROGRAM_ID.to_s
55
+ end
56
+
57
+ # @return [Integer] The slot used to derive the table address
58
+ def recent_slot
59
+ params[:recent_slot]
60
+ end
61
+
62
+ # @return [Integer] The bump seed for the table's program-derived address
63
+ def bump
64
+ params[:bump]
65
+ end
66
+
67
+ # Setup accounts required for the create lookup table instruction
68
+ #
69
+ # @return [void]
70
+ def setup_accounts
71
+ account_context.add_writable_nonsigner(table)
72
+ account_context.add_readonly_signer(authority)
73
+ account_context.add_writable_signer(payer)
74
+ account_context.add_readonly_nonsigner(system_program)
75
+ account_context.add_readonly_nonsigner(lookup_table_program)
76
+ end
77
+
78
+ # Build instruction with resolved account indices
79
+ #
80
+ # @param account_context [Utils::AccountContext] The account context
81
+ # @return [Solace::Instruction]
82
+ def build_instruction(account_context)
83
+ Solace::Instructions::AddressLookupTableProgram::CreateLookupTableInstruction.build(
84
+ recent_slot: recent_slot,
85
+ bump: bump,
86
+ program_index: account_context.index_of(lookup_table_program),
87
+ table_index: account_context.index_of(table),
88
+ authority_index: account_context.index_of(authority),
89
+ payer_index: account_context.index_of(payer),
90
+ system_program_index: account_context.index_of(system_program)
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Composers
5
+ # Composer for extending an address lookup table with new addresses.
6
+ #
7
+ # Resolves and orders the accounts for an `ExtendLookupTable` instruction and
8
+ # delegates construction to
9
+ # `Instructions::AddressLookupTableProgram::ExtendLookupTableInstruction`.
10
+ #
11
+ # Addresses appended here become usable one slot after the extend lands.
12
+ #
13
+ # Required accounts:
14
+ # - **Table**: the table account being extended (writable, non-signer)
15
+ # - **Authority**: controls the table (readonly, signer)
16
+ # - **Payer**: funds any additional rent (writable, signer)
17
+ # - **System program** (readonly, non-signer)
18
+ #
19
+ # @example
20
+ # composer = AddressLookupTableProgramExtendComposer.new(
21
+ # table: table_address,
22
+ # authority: authority,
23
+ # payer: payer,
24
+ # addresses: [recipient1, recipient2]
25
+ # )
26
+ #
27
+ # @see Instructions::AddressLookupTableProgram::ExtendLookupTableInstruction
28
+ # @since 0.1.8
29
+ class AddressLookupTableProgramExtendComposer < Base
30
+ # @return [String] The table's on-chain address
31
+ def table
32
+ params[:table].to_s
33
+ end
34
+
35
+ # @return [String] The table authority
36
+ def authority
37
+ params[:authority].to_s
38
+ end
39
+
40
+ # @return [String] The rent payer
41
+ def payer
42
+ params[:payer].to_s
43
+ end
44
+
45
+ # @return [String] The system program id
46
+ def system_program
47
+ Solace::Constants::SYSTEM_PROGRAM_ID.to_s
48
+ end
49
+
50
+ # @return [String] The address lookup table program id
51
+ def lookup_table_program
52
+ Solace::Constants::ADDRESS_LOOKUP_TABLE_PROGRAM_ID.to_s
53
+ end
54
+
55
+ # @return [Array<String>] The addresses to append to the table
56
+ def addresses
57
+ params[:addresses].map(&:to_s)
58
+ end
59
+
60
+ # Setup accounts required for the extend lookup table instruction
61
+ #
62
+ # @return [void]
63
+ def setup_accounts
64
+ account_context.add_writable_nonsigner(table)
65
+ account_context.add_readonly_signer(authority)
66
+ account_context.add_writable_signer(payer)
67
+ account_context.add_readonly_nonsigner(system_program)
68
+ account_context.add_readonly_nonsigner(lookup_table_program)
69
+ end
70
+
71
+ # Build instruction with resolved account indices
72
+ #
73
+ # @param account_context [Utils::AccountContext] The account context
74
+ # @return [Solace::Instruction]
75
+ def build_instruction(account_context)
76
+ Solace::Instructions::AddressLookupTableProgram::ExtendLookupTableInstruction.build(
77
+ addresses: addresses,
78
+ program_index: account_context.index_of(lookup_table_program),
79
+ table_index: account_context.index_of(table),
80
+ authority_index: account_context.index_of(authority),
81
+ payer_index: account_context.index_of(payer),
82
+ system_program_index: account_context.index_of(system_program)
83
+ )
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Composers
5
+ # Composer for an instruction it does not interpret.
6
+ #
7
+ # Where every other composer derives its accounts' roles from domain
8
+ # arguments, this one is handed the program id, the accounts in order, the
9
+ # data, and an {Utils::AccountContext} declaring each account's role, and
10
+ # rebuilds the instruction by index against whatever context it is
11
+ # composed into. It is what {Solace::TransactionDecomposer} answers for
12
+ # each instruction of a transaction taken apart, and what a caller reaches
13
+ # for to rebuild one of those with an account swapped out.
14
+ #
15
+ # @example Rebuild a recovered instruction with a different rent payer
16
+ # roles = Solace::Utils::AccountContext.new
17
+ # roles.merge_from(recovered.account_context)
18
+ # roles.add_writable_signer(sponsor)
19
+ #
20
+ # composer = Solace::Composers::OpaqueInstructionComposer.new(
21
+ # program_id: recovered.program_id,
22
+ # accounts: [sponsor, *recovered.accounts.drop(1)],
23
+ # data: recovered.data,
24
+ # roles: roles
25
+ # )
26
+ #
27
+ # @since 0.1.9
28
+ class OpaqueInstructionComposer < Base
29
+ # The program the instruction invokes
30
+ #
31
+ # @return [String] The program id
32
+ def program_id
33
+ params[:program_id].to_s
34
+ end
35
+
36
+ # The accounts the instruction touches, in order
37
+ #
38
+ # @return [Array<String>] The pubkeys
39
+ def accounts
40
+ @accounts ||= params[:accounts].map(&:to_s)
41
+ end
42
+
43
+ # The instruction data, untouched
44
+ #
45
+ # @return [Array<Integer>] The data bytes
46
+ def data
47
+ params[:data]
48
+ end
49
+
50
+ # The context declaring each account's role
51
+ #
52
+ # @return [Utils::AccountContext]
53
+ def roles
54
+ params[:roles]
55
+ end
56
+
57
+ # Declare every account with the role given for it, plus the program
58
+ #
59
+ # @return [void]
60
+ # @raise [ArgumentError] When an account has no role declared
61
+ def setup_accounts
62
+ accounts.each do |pubkey|
63
+ raise ArgumentError, "No role declared for account #{pubkey}" unless roles.pubkey_account_map.key?(pubkey)
64
+
65
+ account_context.merge_account(pubkey, signer: roles.signer?(pubkey), writable: roles.writable?(pubkey))
66
+ end
67
+
68
+ account_context.add_readonly_nonsigner(program_id)
69
+ end
70
+
71
+ # Build the instruction with indices resolved against the given context
72
+ #
73
+ # @param account_context [Utils::AccountContext] The account context
74
+ # @return [Solace::Instruction]
75
+ def build_instruction(account_context)
76
+ Solace::Instruction.new.tap do |instruction|
77
+ instruction.program_index = account_context.index_of(program_id)
78
+ instruction.accounts = accounts.map { |pubkey| account_context.index_of(pubkey) }
79
+ instruction.data = data
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -168,6 +168,16 @@ module Solace
168
168
  @rpc_client.rpc_request('getBlockHeight', [{ commitment: commitment }])['result']
169
169
  end
170
170
 
171
+ # Get the current slot from the Solana node
172
+ #
173
+ # @param commitment [String] The commitment level for the request
174
+ # @return [Integer] The current slot
175
+ #
176
+ # @since 0.1.8
177
+ def get_slot(commitment: default_options[:commitment])
178
+ @rpc_client.rpc_request('getSlot', [{ commitment: commitment }])['result']
179
+ end
180
+
171
181
  # Get the minimum required lamports for rent exemption
172
182
  #
173
183
  # @param space [Integer] Number of bytes to allocate for the account
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Errors
5
+ # Raised when an address lookup table a transaction references does not
6
+ # exist on chain. A message that loads addresses through a table cannot be
7
+ # taken apart without the table's address list, so a missing table is an
8
+ # error rather than an empty list.
9
+ #
10
+ # @example
11
+ # begin
12
+ # Solace::TransactionDecomposer.new(connection: connection).decompose_transaction(transaction)
13
+ # rescue Solace::Errors::AddressLookupTableNotFound => e
14
+ # puts "missing table: #{e.account}"
15
+ # end
16
+ #
17
+ # @since 0.1.9
18
+ class AddressLookupTableNotFound < Error
19
+ # @return [String] The lookup table's on-chain address
20
+ attr_reader :account
21
+
22
+ # @param account [String] The lookup table's on-chain address
23
+ def initialize(account)
24
+ super("address lookup table #{account} not found on chain")
25
+ @account = account
26
+ end
27
+ end
28
+ end
29
+ end
@@ -47,7 +47,13 @@ module Solace
47
47
  end
48
48
 
49
49
  # @return [Hash] The error as a hash
50
- def to_h = { code: rpc_code, message: rpc_message, data: rpc_data }
50
+ def to_h
51
+ {
52
+ code: rpc_code,
53
+ message: rpc_message,
54
+ data: rpc_data
55
+ }
56
+ end
51
57
  end
52
58
  end
53
59
  end
data/lib/solace/errors.rb CHANGED
@@ -21,5 +21,6 @@ module Solace
21
21
  require 'solace/errors/http_error'
22
22
  require 'solace/errors/parse_error'
23
23
  require 'solace/errors/confirmation_timeout'
24
+ require 'solace/errors/address_lookup_table_not_found'
24
25
  end
25
26
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Instructions
5
+ # The AddressLookupTableProgram module contains instruction builders for the
6
+ # Address Lookup Table program.
7
+ #
8
+ # Address lookup tables let a versioned (v0) transaction reference accounts by
9
+ # a compact table index instead of a full 32-byte key, so a single transaction
10
+ # can touch far more accounts than the legacy format allows.
11
+ #
12
+ # @see https://docs.solana.com/developing/lookup-tables
13
+ # @since 0.1.8
14
+ module AddressLookupTableProgram
15
+ # Instruction for creating a new (uninitialized) address lookup table.
16
+ #
17
+ # The table's address is a program-derived address of `[authority, recent_slot]`;
18
+ # the caller supplies the derived address and its bump seed.
19
+ #
20
+ # @example Build a CreateLookupTable instruction
21
+ # instruction = Solace::Instructions::AddressLookupTableProgram::CreateLookupTableInstruction.build(
22
+ # recent_slot: 123,
23
+ # bump: 254,
24
+ # program_index: 4,
25
+ # table_index: 1,
26
+ # authority_index: 0,
27
+ # payer_index: 0,
28
+ # system_program_index: 3
29
+ # )
30
+ #
31
+ # @since 0.1.8
32
+ class CreateLookupTableInstruction
33
+ # Instruction discriminator for CreateLookupTable (u32 LE)
34
+ INSTRUCTION_ID = [0, 0, 0, 0].freeze
35
+
36
+ # Builds a Solace::Instruction for creating a lookup table
37
+ #
38
+ # @param recent_slot [Integer] The slot used to derive the table address
39
+ # @param bump [Integer] The bump seed for the table's program-derived address
40
+ # @param program_index [Integer] Index of the lookup table program
41
+ # @param table_index [Integer] Index of the (uninitialized) table account
42
+ # @param authority_index [Integer] Index of the table authority (signer)
43
+ # @param payer_index [Integer] Index of the rent payer (writable signer)
44
+ # @param system_program_index [Integer] Index of the system program
45
+ # @return [Solace::Instruction]
46
+ def self.build(
47
+ recent_slot:,
48
+ bump:,
49
+ program_index:,
50
+ table_index:,
51
+ authority_index:,
52
+ payer_index:,
53
+ system_program_index:
54
+ )
55
+ Instruction.new.tap do |ix|
56
+ ix.program_index = program_index
57
+ ix.accounts = [table_index, authority_index, payer_index, system_program_index]
58
+ ix.data = data(recent_slot, bump)
59
+ end
60
+ end
61
+
62
+ # Instruction data for a create lookup table instruction
63
+ #
64
+ # The BufferLayout is:
65
+ # - [Instruction discriminator (4 bytes, u32 LE)]
66
+ # - [Recent slot (8 bytes, u64 LE)]
67
+ # - [Bump seed (1 byte)]
68
+ #
69
+ # @param recent_slot [Integer] The slot used to derive the table address
70
+ # @param bump [Integer] The bump seed
71
+ # @return [Array<Integer>]
72
+ def self.data(recent_slot, bump)
73
+ INSTRUCTION_ID +
74
+ Utils::Codecs.encode_le_u64(recent_slot).bytes +
75
+ [bump]
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end