solace 0.0.2 → 0.0.3
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/CHANGELOG +45 -0
- data/LICENSE +21 -0
- data/README.md +2 -2
- data/lib/solace/composers/base.rb +40 -0
- data/lib/solace/composers/spl_token_program_transfer_checked_composer.rb +87 -0
- data/lib/solace/composers/system_program_transfer_composer.rb +59 -0
- data/lib/solace/connection.rb +43 -17
- data/lib/solace/instructions/base.rb +21 -0
- data/lib/solace/instructions/spl_token/transfer_checked_instruction.rb +60 -0
- data/lib/solace/instructions/system_program/transfer_instruction.rb +50 -0
- data/lib/solace/programs/associated_token_account.rb +25 -3
- data/lib/solace/transaction_composer.rb +81 -0
- data/lib/solace/utils/account_context.rb +253 -0
- data/lib/solace/version.rb +1 -1
- data/lib/solace.rb +7 -0
- metadata +10 -4
- data/lib/solace/instructions/transfer_checked_instruction.rb +0 -58
- data/lib/solace/instructions/transfer_instruction.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73c15e6b362288fa1f3be0fd2793fcf43aff390deabe20f3264f22550cb67734
|
4
|
+
data.tar.gz: 1b036900c1306c1af4f4c0887a625d6a633a7cf4276c79c1a8b61754d7ffe593
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 422a09d627bbf0e9bb59f3183f4a1ad34904a14996291555ff7c83717554c0915c4ba1d93c689b852cb9d9fab024cfd0e491c0443b332a6dc6f448028c48e000
|
7
|
+
data.tar.gz: a185276e03d031c72c7a78b11db5fd3a9b1d1fa6388758b41c20fad66241aa14b44e5188caace7d0bffaf498c03834d4fe73461754834c3a33e412a13544bf1c
|
data/CHANGELOG
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
5
|
+
|
6
|
+
### Template
|
7
|
+
|
8
|
+
```markdown
|
9
|
+
## [VERSION] - yyyy-mm-dd
|
10
|
+
|
11
|
+
### Added
|
12
|
+
1.
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
```
|
18
|
+
|
19
|
+
## [0.0.3] - 2025-07-30
|
20
|
+
|
21
|
+
### Added
|
22
|
+
1. Moved `TransferCheckedInstruction` and `TransferInstruction` to `Solace::Instructions::SystemProgram` namespace.
|
23
|
+
2. Added `get_token_account_balance` to `Solace::Connection`.
|
24
|
+
3. Added `get_or_create_address` to `Solace::Programs::AssociatedTokenAccount`.
|
25
|
+
4. Added Composer system for handling accounts and instructions in a higher level abstraction.
|
26
|
+
5. Added `SystemProgramTransferComposer` to `Solace::Composers` namespace.
|
27
|
+
6. Added `SplTokenProgramTransferCheckedComposer` to `Solace::Composers` namespace.
|
28
|
+
7. Moved TransferCheckedInstruction to proper namespace `Solace::Instructions::SplToken::TransferCheckedInstruction`.
|
29
|
+
8. Updated `get_or_create_address` to wait for confirmation before returning the address in `Solace::Programs::AssociatedTokenAccount`.
|
30
|
+
9. Moved default options to instance variable in `Solace::Connection`.
|
31
|
+
10. Moved test setup to `test/bootstrap.rb` and added rake task `bootstrap` to run it.
|
32
|
+
|
33
|
+
### Changed
|
34
|
+
|
35
|
+
THESE ARE BREAKING CHANGES FROM 0.0.2 TO 0.0.3. Solice is still in alpha and the API is subject to change.
|
36
|
+
|
37
|
+
There was a bit of a refactoring of the codebase to make it more maintainable and to make it easier to add new features. Additonally, the SDK now has `Composers` for handling accounts and instructions in a higher level abstraction. These composers are used in the tests and can be used in your own code as well.
|
38
|
+
|
39
|
+
Soon, YARD documentation will be added to the SDK and published to https://solace-rb.github.io/solace/.
|
40
|
+
|
41
|
+
Stay tuned.
|
42
|
+
|
43
|
+
### Fixed
|
44
|
+
|
45
|
+
N/A
|
data/LICENSE
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Sebastian Scholl
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -174,7 +174,7 @@ Instruction builders are service objects that create specific instruction types.
|
|
174
174
|
|
175
175
|
```ruby
|
176
176
|
# Build a SOL transfer instruction
|
177
|
-
transfer_ix = Solace::Instructions::TransferInstruction.build(
|
177
|
+
transfer_ix = Solace::Instructions::SystemProgram::TransferInstruction.build(
|
178
178
|
lamports: 1_000_000, # 0.001 SOL
|
179
179
|
from_index: 0, # Sender account index
|
180
180
|
to_index: 1, # Recipient account index
|
@@ -433,7 +433,7 @@ recipient = Solace::Keypair.generate
|
|
433
433
|
connection.request_airdrop(payer.address, 1_000_000_000)
|
434
434
|
|
435
435
|
# Build transfer instruction
|
436
|
-
transfer_ix = Solace::Instructions::TransferInstruction.build(
|
436
|
+
transfer_ix = Solace::Instructions::SystemProgram::TransferInstruction.build(
|
437
437
|
lamports: 100_000_000, # 0.1 SOL
|
438
438
|
from_index: 0,
|
439
439
|
to_index: 1,
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Solace
|
2
|
+
module Composers
|
3
|
+
class Base
|
4
|
+
# @!attribute params
|
5
|
+
# The params for the composer
|
6
|
+
#
|
7
|
+
# @return [Hash] The parameters passed to the composer
|
8
|
+
attr_reader :params
|
9
|
+
|
10
|
+
# @!attribute account_context
|
11
|
+
# The account_context for the composer
|
12
|
+
#
|
13
|
+
# @return [Utils::AccountContext] The AccountContext instance for the composer
|
14
|
+
attr_reader :account_context
|
15
|
+
|
16
|
+
# Initialize the composer
|
17
|
+
#
|
18
|
+
# @param **params [Hash] Parameters to pass to the composer constructor
|
19
|
+
def initialize(**params)
|
20
|
+
@params = params
|
21
|
+
@account_context = Utils::AccountContext.new
|
22
|
+
setup_accounts
|
23
|
+
end
|
24
|
+
|
25
|
+
# Setup accounts required for this instruction
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
def setup_accounts
|
29
|
+
raise NotImplementedError, "Subclasses must implement setup_accounts method"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Build instruction with resolved account indices
|
33
|
+
#
|
34
|
+
# @return [void]
|
35
|
+
def build_instruction(indices)
|
36
|
+
raise NotImplementedError, "Subclasses must implement build_instruction method"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Composers
|
5
|
+
class SplTokenProgramTransferCheckedComposer < Base
|
6
|
+
# Extracts the to address from the params
|
7
|
+
#
|
8
|
+
# @return [String] The to address
|
9
|
+
def to
|
10
|
+
params[:to].is_a?(String) ? params[:to] : params[:to].address
|
11
|
+
end
|
12
|
+
|
13
|
+
# Extracts the from address from the params
|
14
|
+
#
|
15
|
+
# @return [String] The from address
|
16
|
+
def from
|
17
|
+
params[:from].is_a?(String) ? params[:from] : params[:from].address
|
18
|
+
end
|
19
|
+
|
20
|
+
# Extracts the authority address from the params
|
21
|
+
#
|
22
|
+
# The authority is the owner of the token account
|
23
|
+
#
|
24
|
+
# @return [String] The authority address
|
25
|
+
def authority
|
26
|
+
params[:authority].is_a?(String) ? params[:authority] : params[:authority].address
|
27
|
+
end
|
28
|
+
|
29
|
+
# Extracts the mint address from the params
|
30
|
+
#
|
31
|
+
# @return [String] The mint address
|
32
|
+
def mint
|
33
|
+
params[:mint].is_a?(String) ? params[:mint] : params[:mint].address
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the spl token program id
|
37
|
+
#
|
38
|
+
# @return [String] The spl token program id
|
39
|
+
def spl_token_program
|
40
|
+
Constants::TOKEN_PROGRAM_ID
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the lamports to transfer
|
44
|
+
#
|
45
|
+
# @return [Integer] The lamports to transfer
|
46
|
+
def amount
|
47
|
+
params[:amount]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the decimals for the mint of the token
|
51
|
+
#
|
52
|
+
# @return [Integer] The decimals for the mint
|
53
|
+
def decimals
|
54
|
+
params[:decimals]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Setup accounts required for transfer instruction
|
58
|
+
# Called automatically during initialization
|
59
|
+
#
|
60
|
+
# @return [void]
|
61
|
+
def setup_accounts
|
62
|
+
account_context.add_writable_signer(authority)
|
63
|
+
account_context.add_writable_nonsigner(to)
|
64
|
+
account_context.add_writable_nonsigner(from)
|
65
|
+
account_context.add_readonly_nonsigner(mint)
|
66
|
+
account_context.add_readonly_nonsigner(spl_token_program)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Build instruction with resolved account indices
|
70
|
+
#
|
71
|
+
# @param account_context [Utils::AccountContext] The account context
|
72
|
+
# @return [Solace::Instruction]
|
73
|
+
def build_instruction(account_context)
|
74
|
+
Instructions::SplToken::TransferCheckedInstruction.build(
|
75
|
+
amount: amount,
|
76
|
+
decimals: decimals,
|
77
|
+
to_index: account_context.index_of(to),
|
78
|
+
from_index: account_context.index_of(from),
|
79
|
+
mint_index: account_context.index_of(mint),
|
80
|
+
authority_index: account_context.index_of(authority),
|
81
|
+
program_index: account_context.index_of(spl_token_program)
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Composers
|
5
|
+
class SystemProgramTransferComposer < Base
|
6
|
+
# Extracts the to address from the params
|
7
|
+
#
|
8
|
+
# @return [String] The to address
|
9
|
+
def to
|
10
|
+
params[:to].is_a?(String) ? params[:to] : params[:to].address
|
11
|
+
end
|
12
|
+
|
13
|
+
# Extracts the from address from the params
|
14
|
+
#
|
15
|
+
# @return [String] The from address
|
16
|
+
def from
|
17
|
+
params[:from].is_a?(String) ? params[:from] : params[:from].address
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the system program id
|
21
|
+
#
|
22
|
+
# @return [String] The system program id
|
23
|
+
def system_program
|
24
|
+
Solace::Constants::SYSTEM_PROGRAM_ID
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the lamports to transfer
|
28
|
+
#
|
29
|
+
# @return [Integer] The lamports to transfer
|
30
|
+
def lamports
|
31
|
+
params[:lamports]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Setup accounts required for transfer instruction
|
35
|
+
# Called automatically during initialization
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
def setup_accounts
|
39
|
+
account_context.add_writable_signer(from)
|
40
|
+
account_context.add_writable_nonsigner(to)
|
41
|
+
account_context.add_readonly_nonsigner(system_program)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Build instruction with resolved account indices
|
45
|
+
#
|
46
|
+
# @param account_context [Utils::AccountContext] The account context
|
47
|
+
# @return [Solace::Instruction]
|
48
|
+
def build_instruction(account_context)
|
49
|
+
Instructions::SystemProgram::TransferInstruction.build(
|
50
|
+
lamports: lamports,
|
51
|
+
to_index: account_context.index_of(to),
|
52
|
+
from_index: account_context.index_of(from),
|
53
|
+
program_index: account_context.index_of(system_program)
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/lib/solace/connection.rb
CHANGED
@@ -6,21 +6,31 @@ require 'uri'
|
|
6
6
|
|
7
7
|
module Solace
|
8
8
|
class Connection
|
9
|
+
# @!attribute [r] rpc_url
|
10
|
+
# The URL of the Solana RPC node
|
11
|
+
#
|
12
|
+
# @return [String] The URL of the Solana RPC node
|
9
13
|
attr_reader :rpc_url
|
10
14
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
# @!attribute [r] default_options
|
16
|
+
# The default options for RPC requests
|
17
|
+
#
|
18
|
+
# @return [Hash] The default options for RPC requests
|
19
|
+
attr_reader :default_options
|
16
20
|
|
17
21
|
# Initialize the connection with a default or custom RPC URL
|
18
22
|
#
|
19
23
|
# @param rpc_url [String] The URL of the Solana RPC node
|
20
24
|
# @return [Solace::Connection] The connection object
|
21
|
-
def initialize(rpc_url = 'http://localhost:8899')
|
22
|
-
@rpc_url = rpc_url
|
25
|
+
def initialize(rpc_url = 'http://localhost:8899', commitment: 'confirmed')
|
23
26
|
@request_id = nil
|
27
|
+
@rpc_url = rpc_url
|
28
|
+
|
29
|
+
# Set default options
|
30
|
+
@default_options = {
|
31
|
+
commitment: commitment,
|
32
|
+
encoding: 'base64'
|
33
|
+
}
|
24
34
|
end
|
25
35
|
|
26
36
|
# Make an RPC request to the Solana node
|
@@ -68,7 +78,7 @@ module Solace
|
|
68
78
|
[
|
69
79
|
pubkey,
|
70
80
|
lamports,
|
71
|
-
|
81
|
+
default_options.merge(options)
|
72
82
|
]
|
73
83
|
)
|
74
84
|
end
|
@@ -97,7 +107,7 @@ module Solace
|
|
97
107
|
'getAccountInfo',
|
98
108
|
[
|
99
109
|
pubkey,
|
100
|
-
|
110
|
+
default_options
|
101
111
|
]
|
102
112
|
)['result']
|
103
113
|
|
@@ -115,7 +125,21 @@ module Solace
|
|
115
125
|
'getBalance',
|
116
126
|
[
|
117
127
|
pubkey,
|
118
|
-
|
128
|
+
default_options
|
129
|
+
]
|
130
|
+
)['result']['value']
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get the balance of a token account
|
134
|
+
#
|
135
|
+
# @param token_account [String] The public key of the token account
|
136
|
+
# @return [Hash] Token account balance information with amount and decimals
|
137
|
+
def get_token_account_balance(token_account)
|
138
|
+
rpc_request(
|
139
|
+
'getTokenAccountBalance',
|
140
|
+
[
|
141
|
+
token_account,
|
142
|
+
default_options
|
119
143
|
]
|
120
144
|
)['result']['value']
|
121
145
|
end
|
@@ -129,7 +153,7 @@ module Solace
|
|
129
153
|
'getTransaction',
|
130
154
|
[
|
131
155
|
signature,
|
132
|
-
|
156
|
+
default_options.merge(options)
|
133
157
|
]
|
134
158
|
)['result']
|
135
159
|
end
|
@@ -143,7 +167,7 @@ module Solace
|
|
143
167
|
'getSignatureStatuses',
|
144
168
|
[
|
145
169
|
signatures,
|
146
|
-
|
170
|
+
default_options.merge({ 'searchTransactionHistory' => true })
|
147
171
|
]
|
148
172
|
)['result']
|
149
173
|
end
|
@@ -157,7 +181,7 @@ module Solace
|
|
157
181
|
'sendTransaction',
|
158
182
|
[
|
159
183
|
transaction,
|
160
|
-
|
184
|
+
default_options.merge(options)
|
161
185
|
]
|
162
186
|
)
|
163
187
|
end
|
@@ -172,15 +196,17 @@ module Solace
|
|
172
196
|
# Get the signature from the block
|
173
197
|
signature = yield
|
174
198
|
|
199
|
+
interval = 0.1
|
200
|
+
|
175
201
|
# Wait for confirmation
|
176
202
|
loop do
|
177
203
|
status = get_signature_status([signature]).dig('value', 0)
|
178
|
-
|
204
|
+
|
179
205
|
break if status && status['confirmationStatus'] == commitment
|
180
|
-
|
181
|
-
sleep
|
206
|
+
|
207
|
+
sleep interval
|
182
208
|
end
|
183
|
-
|
209
|
+
|
184
210
|
signature
|
185
211
|
end
|
186
212
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Solace
|
2
|
+
module Instructions
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
# Must implement build method
|
6
|
+
#
|
7
|
+
# @return [Solace::Instruction] The instruction
|
8
|
+
def build
|
9
|
+
raise NotImplementedError, "Subclasses must implement build method"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Must implement data method
|
13
|
+
#
|
14
|
+
# @return [Array<Integer>] The instruction data
|
15
|
+
def data
|
16
|
+
raise NotImplementedError, "Subclasses must implement data method"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Instructions
|
5
|
+
module SplToken
|
6
|
+
# Service object for building an SPL Token Program transfer instruction
|
7
|
+
class TransferCheckedInstruction
|
8
|
+
# SPL Token Program instruction index for Transfer Checked
|
9
|
+
INSTRUCTION_INDEX = [12].freeze
|
10
|
+
|
11
|
+
# Builds a Solace::Instruction for transferring SPL tokens
|
12
|
+
#
|
13
|
+
# SPL Token Program transfer instruction layout:
|
14
|
+
# - 1 byte: instruction index (12 for transfer checked)
|
15
|
+
# - 8 bytes: amount (u64, little-endian)
|
16
|
+
# - 8 bytes: decimals (u64, little-endian)
|
17
|
+
#
|
18
|
+
# @param amount [Integer] Amount to transfer (in tokens, according to mint's decimals)
|
19
|
+
# @param decimals [Integer] Number of decimals for the token
|
20
|
+
# @param to_index [Integer] Index of the destination token account in the transaction's accounts
|
21
|
+
# @param from_index [Integer] Index of the source token account in the transaction's accounts
|
22
|
+
# @param mint_index [Integer] Index of the mint in the transaction's accounts
|
23
|
+
# @param authority_index [Integer] Index of the authority (owner) in the transaction's accounts
|
24
|
+
# @param program_index [Integer] Index of the SPL Token Program in the transaction's accounts (default: 3)
|
25
|
+
# @return [Solace::Instruction]
|
26
|
+
def self.build(
|
27
|
+
amount:,
|
28
|
+
decimals:,
|
29
|
+
to_index:,
|
30
|
+
from_index:,
|
31
|
+
mint_index:,
|
32
|
+
authority_index:,
|
33
|
+
program_index: 3
|
34
|
+
)
|
35
|
+
Solace::Instruction.new.tap do |ix|
|
36
|
+
ix.program_index = program_index
|
37
|
+
ix.accounts = [from_index, mint_index, to_index, authority_index]
|
38
|
+
ix.data = data(amount, decimals)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Instruction data for a token transfer instruction
|
43
|
+
#
|
44
|
+
# The BufferLayout is:
|
45
|
+
# - [Instruction Index (1 byte)]
|
46
|
+
# - [Amount (8 bytes little-endian u64)]
|
47
|
+
# - [Decimals (8 bytes little-endian u64)]
|
48
|
+
#
|
49
|
+
# @param amount [Integer] Amount to transfer
|
50
|
+
# @param decimals [Integer] Number of decimals for the token
|
51
|
+
# @return [Array] 1-byte instruction index + 8-byte amount + decimals
|
52
|
+
def self.data(amount, decimals)
|
53
|
+
INSTRUCTION_INDEX +
|
54
|
+
Solace::Utils::Codecs.encode_le_u64(amount).bytes +
|
55
|
+
[decimals]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Instructions
|
5
|
+
module SystemProgram
|
6
|
+
# Service object for building a System Program transfer instruction
|
7
|
+
class TransferInstruction < Base
|
8
|
+
# Instruction ID for System Transfer
|
9
|
+
INSTRUCTION_ID = [2, 0, 0, 0].freeze
|
10
|
+
|
11
|
+
# Builds a Solace::Instruction for transferring SOL
|
12
|
+
#
|
13
|
+
# System Program transfer instruction layout:
|
14
|
+
# - 4 bytes: instruction index (0 for transfer)
|
15
|
+
# - 8 bytes: amount (u64, little-endian)
|
16
|
+
#
|
17
|
+
# @param lamports [Integer] Amount to transfer (in lamports)
|
18
|
+
# @param to_index [Integer] Index of the recipient in the transaction's accounts
|
19
|
+
# @param from_index [Integer] Index of the sender in the transaction's accounts
|
20
|
+
# @param program_index [Integer] Index of the program in the transaction's accounts (default: 2)
|
21
|
+
# @return [Solace::Instruction]
|
22
|
+
def self.build(
|
23
|
+
lamports:,
|
24
|
+
to_index:,
|
25
|
+
from_index:,
|
26
|
+
program_index: 2
|
27
|
+
)
|
28
|
+
Instruction.new.tap do |ix|
|
29
|
+
ix.program_index = program_index
|
30
|
+
ix.accounts = [from_index, to_index]
|
31
|
+
ix.data = data(lamports)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Instruction data for a transfer instruction
|
36
|
+
#
|
37
|
+
# The BufferLayout is:
|
38
|
+
# - [Instruction ID (4 bytes)]
|
39
|
+
# - [Amount (8 bytes little-endian u64)]
|
40
|
+
#
|
41
|
+
# @param lamports [Integer] Amount to transfer (in lamports)
|
42
|
+
# @return [Array] 4-byte instruction ID + 8-byte amount
|
43
|
+
def self.data(lamports)
|
44
|
+
INSTRUCTION_ID +
|
45
|
+
Utils::Codecs.encode_le_u64(lamports).bytes
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -36,6 +36,28 @@ module Solace
|
|
36
36
|
self.class.get_address(**options)
|
37
37
|
end
|
38
38
|
|
39
|
+
# Gets the address of an associated token account, creating it if it doesn't exist.
|
40
|
+
#
|
41
|
+
# @param payer [Solace::Keypair] The keypair that will pay for fees and rent (required if account needs to be created).
|
42
|
+
# @param owner [Solace::Keypair || Solace::PublicKey] The keypair of the owner.
|
43
|
+
# @param mint [Solace::Keypair || Solace::PublicKey] The keypair of the mint.
|
44
|
+
# @return [String] The address of the associated token account
|
45
|
+
def get_or_create_address(payer:, owner:, mint:, commitment: 'confirmed')
|
46
|
+
ata_address, _ = get_address(owner:, mint:)
|
47
|
+
|
48
|
+
account_info = @connection.get_account_info(ata_address)
|
49
|
+
|
50
|
+
return ata_address if account_info
|
51
|
+
|
52
|
+
response = create_associated_token_account(payer:, owner:, mint:)
|
53
|
+
|
54
|
+
raise "Failed to create associated token account" unless response['result']
|
55
|
+
|
56
|
+
@connection.wait_for_confirmed_signature(commitment) { response['result'] }
|
57
|
+
|
58
|
+
ata_address
|
59
|
+
end
|
60
|
+
|
39
61
|
# Creates a new associated token account.
|
40
62
|
#
|
41
63
|
# @param options [Hash] Options for calling the prepare_create_associated_token_account method.
|
@@ -53,9 +75,9 @@ module Solace
|
|
53
75
|
# @param payer [Solace::Keypair] The keypair that will pay for fees and rent.
|
54
76
|
# @return [Solace::Transaction] The signed transaction.
|
55
77
|
def prepare_create_associated_token_account(
|
56
|
-
|
57
|
-
|
58
|
-
|
78
|
+
payer:,
|
79
|
+
owner:,
|
80
|
+
mint:
|
59
81
|
)
|
60
82
|
ata_address, _ = get_address(owner:, mint:)
|
61
83
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# lib/solace/transaction_composer.rb
|
2
|
+
module Solace
|
3
|
+
# Composes multi-instruction transactions with automatic account management
|
4
|
+
class TransactionComposer
|
5
|
+
|
6
|
+
# @!attribute connection
|
7
|
+
#
|
8
|
+
# @return [Solace::Connection] The connection to the Solana cluster
|
9
|
+
attr_reader :connection
|
10
|
+
|
11
|
+
# @!attribute context
|
12
|
+
#
|
13
|
+
# @return [Utils::AccountContext] The account registry
|
14
|
+
attr_reader :context
|
15
|
+
|
16
|
+
# @!attribute instruction_composers
|
17
|
+
#
|
18
|
+
# @return [Array<Composers::Base>] The instruction composers
|
19
|
+
attr_reader :instruction_composers
|
20
|
+
|
21
|
+
# Initialize the composer
|
22
|
+
#
|
23
|
+
# @param connection [Solace::Connection] The connection to the Solana cluster
|
24
|
+
def initialize(connection:)
|
25
|
+
@connection = connection
|
26
|
+
@instruction_composers = []
|
27
|
+
@context = Utils::AccountContext.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add an instruction composer to the transaction
|
31
|
+
#
|
32
|
+
# @param composer [Composers::Base] The instruction composer
|
33
|
+
# @return [TransactionComposer] Self for chaining
|
34
|
+
def add_instruction(composer)
|
35
|
+
merge_accounts(composer.account_context)
|
36
|
+
instruction_composers << composer
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the fee payer for the transaction
|
41
|
+
#
|
42
|
+
# @param pubkey [String | Solace::PublicKey | Solace::Keypair] The fee payer pubkey
|
43
|
+
# @return [TransactionComposer] Self for chaining
|
44
|
+
def set_fee_payer(pubkey)
|
45
|
+
context.set_fee_payer(pubkey)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Compose the final transaction
|
50
|
+
#
|
51
|
+
# @return [Solace::Transaction] The composed transaction (unsigned)
|
52
|
+
def compose_transaction
|
53
|
+
context.compile
|
54
|
+
|
55
|
+
message = Solace::Message.new(
|
56
|
+
header: context.header,
|
57
|
+
accounts: context.accounts,
|
58
|
+
instructions: build_instructions,
|
59
|
+
recent_blockhash: connection.get_latest_blockhash,
|
60
|
+
)
|
61
|
+
|
62
|
+
Solace::Transaction.new(message: message)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Build all instructions with resolved indices
|
68
|
+
#
|
69
|
+
# @return [Array<Solace::Instruction>] The built instructions
|
70
|
+
def build_instructions
|
71
|
+
instruction_composers.map { _1.build_instruction(context) }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Merge all accounts from another AccountContext into this one
|
75
|
+
#
|
76
|
+
# @param account_context [AccountContext] The other context to merge from
|
77
|
+
def merge_accounts(account_context)
|
78
|
+
context.merge_from(account_context)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Utils
|
5
|
+
# Internal utility for managing account context in transaction building
|
6
|
+
# with automatic deduplication and sorting
|
7
|
+
class AccountContext
|
8
|
+
# @!attribute DEFAULT_ACCOUNT
|
9
|
+
# The default account data
|
10
|
+
#
|
11
|
+
# @return [Hash] The default account data with lowest level of permissions
|
12
|
+
DEFAULT_ACCOUNT = {
|
13
|
+
signer: false,
|
14
|
+
writable: false,
|
15
|
+
fee_payer: false,
|
16
|
+
}
|
17
|
+
|
18
|
+
# @!attribute header
|
19
|
+
# The header for the transaction
|
20
|
+
#
|
21
|
+
# @return [Array<Integer>] The header for the transaction
|
22
|
+
attr_accessor :header
|
23
|
+
|
24
|
+
# @!attribute accounts
|
25
|
+
# The accounts in the transaction
|
26
|
+
#
|
27
|
+
# @return [Array<String>] The accounts
|
28
|
+
attr_accessor :accounts
|
29
|
+
|
30
|
+
# @!attribute pubkey_account_map
|
31
|
+
# The map of accounts
|
32
|
+
#
|
33
|
+
# @return [Hash] The map of accounts
|
34
|
+
attr_accessor :pubkey_account_map
|
35
|
+
|
36
|
+
# Initialize the account context
|
37
|
+
def initialize
|
38
|
+
@header = []
|
39
|
+
@accounts = []
|
40
|
+
@pubkey_account_map = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set the fee payer account
|
44
|
+
#
|
45
|
+
# @param pubkey [Solace::Keypair | Solace::PublicKey | String] The pubkey of the fee payer account
|
46
|
+
def set_fee_payer(pubkey)
|
47
|
+
merge_account(pubkey, signer: true, writable: true, fee_payer: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a signer account
|
51
|
+
#
|
52
|
+
# @param pubkey [Solace::Keypair | Solace::PublicKey | String] The pubkey of the signer account
|
53
|
+
def add_writable_signer(pubkey)
|
54
|
+
merge_account(pubkey, signer: true, writable: true)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Add a writable account
|
58
|
+
#
|
59
|
+
# @param pubkey [Solace::Keypair | Solace::PublicKey | String] The pubkey of the writable account
|
60
|
+
def add_writable_nonsigner(pubkey)
|
61
|
+
merge_account(pubkey, signer: false, writable: true)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add a readonly signer account
|
65
|
+
#
|
66
|
+
# @param pubkey [Solace::Keypair | Solace::PublicKey | String] The pubkey of the readonly signer account
|
67
|
+
def add_readonly_signer(pubkey)
|
68
|
+
merge_account(pubkey, signer: true, writable: false)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Add a readonly account
|
73
|
+
#
|
74
|
+
# @param pubkey [Solace::Keypair | Solace::PublicKey | String] The pubkey of the readonly account
|
75
|
+
def add_readonly_nonsigner(pubkey)
|
76
|
+
merge_account(pubkey, signer: false, writable: false)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Predicate to check if an account is a fee payer
|
80
|
+
#
|
81
|
+
# @param pubkey [String] The pubkey of the account
|
82
|
+
# @return [Boolean] Whether the account is a fee payer
|
83
|
+
def fee_payer?(pubkey)
|
84
|
+
@pubkey_account_map[pubkey].try { |acc| acc[:fee_payer] }
|
85
|
+
end
|
86
|
+
|
87
|
+
# Predicate to check if an account is a signer
|
88
|
+
#
|
89
|
+
# @param pubkey [String] The pubkey of the account
|
90
|
+
# @return [Boolean] Whether the account is a signer
|
91
|
+
def signer?(pubkey)
|
92
|
+
@pubkey_account_map[pubkey].try { |acc| acc[:signer] }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Predicate to check if an account is writable
|
96
|
+
#
|
97
|
+
# @param pubkey [String] The pubkey of the account
|
98
|
+
# @return [Boolean] Whether the account is writable
|
99
|
+
def writable?(pubkey)
|
100
|
+
@pubkey_account_map[pubkey].try { |acc| acc[:writable] }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Predicate to check if an account is a writable signer
|
104
|
+
#
|
105
|
+
# @param pubkey [String] The pubkey of the account
|
106
|
+
# @return [Boolean] Whether the account is a writable signer
|
107
|
+
def writable_signer?(pubkey)
|
108
|
+
@pubkey_account_map[pubkey].try { |acc| acc[:signer] && acc[:writable] }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Predicate to check if an account is writable and not a signer
|
112
|
+
#
|
113
|
+
# @param pubkey [String] The pubkey of the account
|
114
|
+
# @return [Boolean] Whether the account is writable and not a signer
|
115
|
+
def writable_nonsigner?(pubkey)
|
116
|
+
@pubkey_account_map[pubkey].try { |acc| !acc[:signer] && acc[:writable] }
|
117
|
+
end
|
118
|
+
|
119
|
+
# Predicate to check if an account is a readonly signer
|
120
|
+
#
|
121
|
+
# @param pubkey [String] The pubkey of the account
|
122
|
+
# @return [Boolean] Whether the account is a readonly signer
|
123
|
+
def readonly_signer?(pubkey)
|
124
|
+
@pubkey_account_map[pubkey].try { |acc| acc[:signer] && !acc[:writable] }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Predicate to check if an account is readonly and not a signer
|
128
|
+
#
|
129
|
+
# @param pubkey [String] The pubkey of the account
|
130
|
+
# @return [Boolean] Whether the account is readonly and not a signer
|
131
|
+
def readonly_nonsigner?(pubkey)
|
132
|
+
@pubkey_account_map[pubkey].try { |acc| !acc[:signer] && !acc[:writable] }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Merge all accounts from another AccountContext into this one
|
136
|
+
#
|
137
|
+
# @param other_context [AccountContext] The other context to merge from
|
138
|
+
def merge_from(other_context)
|
139
|
+
other_context.pubkey_account_map.each do |pubkey, data|
|
140
|
+
merge_account(
|
141
|
+
pubkey,
|
142
|
+
signer: data[:signer],
|
143
|
+
writable: data[:writable],
|
144
|
+
fee_payer: data[:fee_payer]
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Compile accounts into final format
|
150
|
+
#
|
151
|
+
# Gets unique accounts and sorts them in the following order:
|
152
|
+
# - Signers first (Solana requirement)
|
153
|
+
# - Then writable accounts
|
154
|
+
# - Then readonly accounts
|
155
|
+
#
|
156
|
+
# @return [Hash] The compiled accounts and header
|
157
|
+
def compile
|
158
|
+
self.header = calculate_header
|
159
|
+
self.accounts = order_accounts
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
# Index of a pubkey in the accounts array
|
164
|
+
#
|
165
|
+
# @param pubkey_str [String] The public key of the account
|
166
|
+
# @return [Integer] The index of the pubkey in the accounts array or -1 if not found
|
167
|
+
def index_of(pubkey_str)
|
168
|
+
indices[pubkey_str] || -1
|
169
|
+
end
|
170
|
+
|
171
|
+
# Get map of indicies for pubkeys in accounts array
|
172
|
+
#
|
173
|
+
# @return [Hash<String, Integer>] The indices of the pubkeys in the accounts array
|
174
|
+
def indices
|
175
|
+
accounts.each_with_index.to_h
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
# Add or merge an account into the context
|
181
|
+
#
|
182
|
+
# @param pubkey [String | Solace::PublicKey | Solace::Keypair] The public key of the account
|
183
|
+
# @param signer [Boolean] Whether the account is a signer
|
184
|
+
# @param writable [Boolean] Whether the account is writable
|
185
|
+
def merge_account(
|
186
|
+
pubkey,
|
187
|
+
signer:,
|
188
|
+
writable:,
|
189
|
+
fee_payer: false
|
190
|
+
)
|
191
|
+
pubkey_str = resolve_pubkey(pubkey)
|
192
|
+
|
193
|
+
@pubkey_account_map[pubkey_str] ||= DEFAULT_ACCOUNT.dup
|
194
|
+
@pubkey_account_map[pubkey_str][:signer] ||= signer
|
195
|
+
@pubkey_account_map[pubkey_str][:writable] ||= writable
|
196
|
+
@pubkey_account_map[pubkey_str][:fee_payer] ||= fee_payer
|
197
|
+
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
# Order accounts by signer, writable, readonly signer, readonly
|
202
|
+
#
|
203
|
+
# @return [Array<String>] The ordered accounts
|
204
|
+
def order_accounts
|
205
|
+
@pubkey_account_map.keys.sort_by do |pubkey|
|
206
|
+
if fee_payer?(pubkey)
|
207
|
+
0
|
208
|
+
elsif writable_signer?(pubkey)
|
209
|
+
1
|
210
|
+
elsif readonly_signer?(pubkey)
|
211
|
+
2
|
212
|
+
elsif writable_nonsigner?(pubkey)
|
213
|
+
2
|
214
|
+
elsif readonly_nonsigner?(pubkey)
|
215
|
+
3
|
216
|
+
else
|
217
|
+
raise ArgumentError, "Unknown account type for pubkey: #{pubkey}"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Calculate the header for the transaction
|
223
|
+
#
|
224
|
+
# @note The header is an array of three integers:
|
225
|
+
# - The number of signers (writable + readonly)
|
226
|
+
# - The number of readonly signers
|
227
|
+
# - The number of readonly unsigned accounts
|
228
|
+
#
|
229
|
+
# @return [Array] The header for the transaction
|
230
|
+
def calculate_header
|
231
|
+
@pubkey_account_map.keys.reduce([0, 0, 0]) do |acc, pubkey|
|
232
|
+
acc[0] += 1 if signer?(pubkey)
|
233
|
+
|
234
|
+
if readonly_signer?(pubkey)
|
235
|
+
acc[1] += 1
|
236
|
+
elsif readonly_nonsigner?(pubkey)
|
237
|
+
acc[2] += 1
|
238
|
+
end
|
239
|
+
|
240
|
+
acc
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Resolve the pubkey from a Solace::PublicKey or String
|
245
|
+
#
|
246
|
+
# @param pubkey [String|Solace::PublicKey] The pubkey to resolve
|
247
|
+
# @return [String] The resolved pubkey
|
248
|
+
def resolve_pubkey(pubkey)
|
249
|
+
pubkey.is_a?(String) ? pubkey : pubkey.address
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
data/lib/solace/version.rb
CHANGED
data/lib/solace.rb
CHANGED
@@ -8,6 +8,7 @@ require_relative 'solace/constants'
|
|
8
8
|
require_relative 'solace/connection'
|
9
9
|
require_relative 'solace/utils/codecs'
|
10
10
|
require_relative 'solace/utils/pda'
|
11
|
+
require_relative 'solace/utils/account_context'
|
11
12
|
require_relative 'solace/utils/curve25519_dalek'
|
12
13
|
require_relative 'solace/concerns/binary_serializable'
|
13
14
|
|
@@ -26,6 +27,12 @@ require_relative 'solace/transaction'
|
|
26
27
|
require_relative 'solace/message'
|
27
28
|
require_relative 'solace/instruction'
|
28
29
|
require_relative 'solace/address_lookup_table'
|
30
|
+
require_relative 'solace/transaction_composer'
|
31
|
+
|
32
|
+
# 📦 Composers (Builders)
|
33
|
+
#
|
34
|
+
# Glob require all instructions
|
35
|
+
Dir[File.join(__dir__, 'solace/composers', '**', '*.rb')].sort.each { |file| require file }
|
29
36
|
|
30
37
|
# 📦 Instructions (Builders)
|
31
38
|
#
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Scholl
|
@@ -83,7 +83,7 @@ description: A Ruby library for working with Solana blockchain. Provides both lo
|
|
83
83
|
instruction builders and high-level program clients for interacting with Solana
|
84
84
|
programs.
|
85
85
|
email:
|
86
|
-
-
|
86
|
+
- sebscholl@gmail.com
|
87
87
|
executables: []
|
88
88
|
extensions: []
|
89
89
|
extra_rdoc_files: []
|
@@ -93,18 +93,22 @@ files:
|
|
93
93
|
- README.md
|
94
94
|
- lib/solace.rb
|
95
95
|
- lib/solace/address_lookup_table.rb
|
96
|
+
- lib/solace/composers/base.rb
|
97
|
+
- lib/solace/composers/spl_token_program_transfer_checked_composer.rb
|
98
|
+
- lib/solace/composers/system_program_transfer_composer.rb
|
96
99
|
- lib/solace/concerns/binary_serializable.rb
|
97
100
|
- lib/solace/connection.rb
|
98
101
|
- lib/solace/constants.rb
|
99
102
|
- lib/solace/instruction.rb
|
100
103
|
- lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb
|
104
|
+
- lib/solace/instructions/base.rb
|
101
105
|
- lib/solace/instructions/spl_token/initialize_account_instruction.rb
|
102
106
|
- lib/solace/instructions/spl_token/initialize_mint_instruction.rb
|
103
107
|
- lib/solace/instructions/spl_token/mint_to_instruction.rb
|
108
|
+
- lib/solace/instructions/spl_token/transfer_checked_instruction.rb
|
104
109
|
- lib/solace/instructions/spl_token/transfer_instruction.rb
|
105
110
|
- lib/solace/instructions/system_program/create_account_instruction.rb
|
106
|
-
- lib/solace/instructions/
|
107
|
-
- lib/solace/instructions/transfer_instruction.rb
|
111
|
+
- lib/solace/instructions/system_program/transfer_instruction.rb
|
108
112
|
- lib/solace/keypair.rb
|
109
113
|
- lib/solace/message.rb
|
110
114
|
- lib/solace/programs/associated_token_account.rb
|
@@ -124,6 +128,8 @@ files:
|
|
124
128
|
- lib/solace/serializers/transaction_deserializer.rb
|
125
129
|
- lib/solace/serializers/transaction_serializer.rb
|
126
130
|
- lib/solace/transaction.rb
|
131
|
+
- lib/solace/transaction_composer.rb
|
132
|
+
- lib/solace/utils/account_context.rb
|
127
133
|
- lib/solace/utils/codecs.rb
|
128
134
|
- lib/solace/utils/curve25519_dalek.rb
|
129
135
|
- lib/solace/utils/libcurve25519_dalek-linux/libcurve25519_dalek.so
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Solace
|
4
|
-
module Instructions
|
5
|
-
# Service object for building an SPL Token Program transfer instruction
|
6
|
-
class TransferCheckedInstruction
|
7
|
-
# SPL Token Program instruction index for Transfer Checked
|
8
|
-
INSTRUCTION_INDEX = [12].freeze
|
9
|
-
|
10
|
-
# Builds a Solace::Instruction for transferring SPL tokens
|
11
|
-
#
|
12
|
-
# SPL Token Program transfer instruction layout:
|
13
|
-
# - 1 byte: instruction index (12 for transfer checked)
|
14
|
-
# - 8 bytes: amount (u64, little-endian)
|
15
|
-
# - 8 bytes: decimals (u64, little-endian)
|
16
|
-
#
|
17
|
-
# @param amount [Integer] Amount to transfer (in tokens, according to mint's decimals)
|
18
|
-
# @param decimals [Integer] Number of decimals for the token
|
19
|
-
# @param to_index [Integer] Index of the destination token account in the transaction's accounts
|
20
|
-
# @param from_index [Integer] Index of the source token account in the transaction's accounts
|
21
|
-
# @param mint_index [Integer] Index of the mint in the transaction's accounts
|
22
|
-
# @param authority_index [Integer] Index of the authority (owner) in the transaction's accounts
|
23
|
-
# @param program_index [Integer] Index of the SPL Token Program in the transaction's accounts (default: 3)
|
24
|
-
# @return [Solace::Instruction]
|
25
|
-
def self.build(
|
26
|
-
amount:,
|
27
|
-
decimals:,
|
28
|
-
to_index:,
|
29
|
-
from_index:,
|
30
|
-
mint_index:,
|
31
|
-
authority_index:,
|
32
|
-
program_index: 3
|
33
|
-
)
|
34
|
-
Solace::Instruction.new.tap do |ix|
|
35
|
-
ix.program_index = program_index
|
36
|
-
ix.accounts = [from_index, mint_index, to_index, authority_index]
|
37
|
-
ix.data = data(amount, decimals)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Instruction data for a token transfer instruction
|
42
|
-
#
|
43
|
-
# The BufferLayout is:
|
44
|
-
# - [Instruction Index (1 byte)]
|
45
|
-
# - [Amount (8 bytes little-endian u64)]
|
46
|
-
# - [Decimals (8 bytes little-endian u64)]
|
47
|
-
#
|
48
|
-
# @param amount [Integer] Amount to transfer
|
49
|
-
# @param decimals [Integer] Number of decimals for the token
|
50
|
-
# @return [Array] 1-byte instruction index + 8-byte amount + decimals
|
51
|
-
def self.data(amount, decimals)
|
52
|
-
INSTRUCTION_INDEX +
|
53
|
-
Solace::Utils::Codecs.encode_le_u64(amount).bytes +
|
54
|
-
[decimals]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Solace
|
4
|
-
module Instructions
|
5
|
-
# Service object for building a System Program transfer instruction
|
6
|
-
class TransferInstruction
|
7
|
-
# Instruction ID for System Transfer
|
8
|
-
INSTRUCTION_ID = [2, 0, 0, 0].freeze
|
9
|
-
|
10
|
-
# Builds a Solace::Instruction for transferring SOL
|
11
|
-
#
|
12
|
-
# System Program transfer instruction layout:
|
13
|
-
# - 4 bytes: instruction index (0 for transfer)
|
14
|
-
# - 8 bytes: amount (u64, little-endian)
|
15
|
-
#
|
16
|
-
# @param lamports [Integer] Amount to transfer (in lamports)
|
17
|
-
# @param to_index [Integer] Index of the recipient in the transaction's accounts
|
18
|
-
# @param from_index [Integer] Index of the sender in the transaction's accounts
|
19
|
-
# @param program_index [Integer] Index of the program in the transaction's accounts (default: 2)
|
20
|
-
# @return [Solace::Instruction]
|
21
|
-
def self.build(
|
22
|
-
lamports:,
|
23
|
-
to_index:,
|
24
|
-
from_index:,
|
25
|
-
program_index: 2
|
26
|
-
)
|
27
|
-
Solace::Instruction.new.tap do |ix|
|
28
|
-
ix.program_index = program_index
|
29
|
-
ix.accounts = [from_index, to_index]
|
30
|
-
ix.data = data(lamports)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# Instruction data for a transfer instruction
|
35
|
-
#
|
36
|
-
# The BufferLayout is:
|
37
|
-
# - [Instruction ID (4 bytes)]
|
38
|
-
# - [Amount (8 bytes little-endian u64)]
|
39
|
-
#
|
40
|
-
# @param lamports [Integer] Amount to transfer (in lamports)
|
41
|
-
# @return [Array] 4-byte instruction ID + 8-byte amount
|
42
|
-
def self.data(lamports)
|
43
|
-
INSTRUCTION_ID +
|
44
|
-
Solace::Utils::Codecs.encode_le_u64(lamports).bytes
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|