solace 0.1.4 → 0.1.5
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 +17 -0
- data/README.md +60 -0
- data/lib/solace/composers/associated_token_account_program_create_account_composer.rb +5 -4
- data/lib/solace/composers/token_2022_program_close_account_composer.rb +75 -0
- data/lib/solace/composers/token_2022_program_initialize_mint_composer.rb +93 -0
- data/lib/solace/composers/token_2022_program_mint_to_composer.rb +84 -0
- data/lib/solace/composers/token_2022_program_transfer_checked_composer.rb +109 -0
- data/lib/solace/composers/token_2022_program_transfer_composer.rb +86 -0
- data/lib/solace/connection.rb +24 -4
- data/lib/solace/constants.rb +8 -2
- data/lib/solace/instructions/spl_token/transfer_checked_instruction.rb +1 -1
- data/lib/solace/instructions/token_2022/close_account_instruction.rb +50 -0
- data/lib/solace/instructions/token_2022/initialize_account_instruction.rb +62 -0
- data/lib/solace/instructions/token_2022/initialize_mint_instruction.rb +75 -0
- data/lib/solace/instructions/token_2022/mint_to_instruction.rb +59 -0
- data/lib/solace/instructions/token_2022/transfer_checked_instruction.rb +68 -0
- data/lib/solace/instructions/token_2022/transfer_instruction.rb +71 -0
- data/lib/solace/programs/associated_token_account.rb +27 -10
- data/lib/solace/programs/spl_token.rb +18 -254
- data/lib/solace/programs/token_2022.rb +70 -0
- data/lib/solace/programs/token_program_interface.rb +312 -0
- data/lib/solace/version.rb +1 -1
- data/lib/solace.rb +2 -0
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2d850b2d501a6bf777043a53e320a2bef57fee4ea47e9a2a08e3331e4695c8f
|
|
4
|
+
data.tar.gz: 80139c66611fb213d966db7f6ea7468a46fec9b8b6418917995c424627f2998b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 659cfcfef1750281cb209a0ef13398af0c56c77f0ebe2479380c09d1764cf83eb39c74a08a3fa6234ceb7e9fbd1e97b5abbdd281bee653c39e00c177fea4aa27
|
|
7
|
+
data.tar.gz: 9d79ebd0a3c418fca8933b533bd5588605a01338327aeb6b8ed5db831497ea5acd5597d5f82a63ed563ae2e857d84a78745a39c2e1b2750b9b5eb0d5d2e2da09
|
data/CHANGELOG
CHANGED
|
@@ -20,6 +20,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## 0.1.5 - 2026-06-13
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
1. Added Token-2022 program support
|
|
28
|
+
2. Added integration tests for the Token-2022 composers
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
1. Decoupled Token-2022 from the SPL composers via a shared mixin
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
1. Fixed `TransferChecked` instruction layout documentation
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
23
40
|
## 0.1.3 - 2025-06-11
|
|
24
41
|
|
|
25
42
|
### Added
|
data/README.md
CHANGED
|
@@ -450,6 +450,66 @@ response = connection.send_transaction(transaction.serialize)
|
|
|
450
450
|
connection.wait_for_confirmed_signature { response['result'] }
|
|
451
451
|
```
|
|
452
452
|
|
|
453
|
+
## Development
|
|
454
|
+
|
|
455
|
+
### Prerequisites
|
|
456
|
+
|
|
457
|
+
- **Ruby** (matching `.ruby-version` / `Gemfile.lock`) and **Bundler**
|
|
458
|
+
- **Solana CLI**, which provides `solana-test-validator` and `solana-keygen`. Install with:
|
|
459
|
+
```bash
|
|
460
|
+
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
|
|
461
|
+
```
|
|
462
|
+
Then add the installer's PATH line to your shell profile (the installer prints the exact `export PATH=...` to use).
|
|
463
|
+
|
|
464
|
+
### One-time setup
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
# Install Ruby dependencies
|
|
468
|
+
bundle install
|
|
469
|
+
|
|
470
|
+
# Generate the seven keypair fixtures the test suite expects.
|
|
471
|
+
# test/fixtures/ is gitignored — each developer generates their own.
|
|
472
|
+
mkdir -p test/fixtures
|
|
473
|
+
for name in bob anna payer mint mint-2022 mint-authority fee-collector; do
|
|
474
|
+
solana-keygen new --no-bip39-passphrase --silent --force -o "test/fixtures/${name}.json"
|
|
475
|
+
done
|
|
476
|
+
|
|
477
|
+
# Bootstrap the local validator: airdrops SOL to the fixture accounts,
|
|
478
|
+
# creates their ATAs, and creates the test mint. Spins up a
|
|
479
|
+
# solana-test-validator on the side; ledger state persists in ./test-ledger.
|
|
480
|
+
bundle exec rake bootstrap
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
If you ever need a clean slate, delete `test-ledger/` and re-run `rake bootstrap`.
|
|
484
|
+
|
|
485
|
+
### Running the tests
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
bundle exec rake test
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`rake test` spawns `solana-test-validator` automatically (re-using `./test-ledger`) and tears it down on exit. You can also run a single file:
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
bundle exec rake test test/solace/programs/spl_token_test.rb
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
To exercise the longer end-to-end flows in `test/usecases/`:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
bundle exec rake usecases # all usecases
|
|
501
|
+
bundle exec rake usecases token_mint # a specific one
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Building & installing the gem locally
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
bundle exec rake build # produces builds/solace-<version>.gem
|
|
508
|
+
bundle exec rake install # builds and `gem install`s it locally
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
The native curve25519 library is shipped pre-compiled in `lib/solace/utils/`. You only need `rake compile` (which invokes `cargo`) if you're modifying `ext/curve25519_dalek` — that target cross-builds for every supported platform and requires the corresponding Rust toolchains.
|
|
512
|
+
|
|
453
513
|
## Dependencies
|
|
454
514
|
|
|
455
515
|
- **base58**: Base58 encoding/decoding
|
|
@@ -63,11 +63,12 @@ module Solace
|
|
|
63
63
|
Constants::SYSTEM_PROGRAM_ID.to_s
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
66
|
+
# @return [String] The token program id baked into the ATA's create-account
|
|
67
|
+
# instruction (defaults to legacy SPL Token; pass +params[:token_program_id]+
|
|
68
|
+
# to create a Token-2022 ATA — note the derived ATA address differs too,
|
|
69
|
+
# see {Programs::AssociatedTokenAccount.get_address}).
|
|
69
70
|
def token_program_id
|
|
70
|
-
Constants::TOKEN_PROGRAM_ID.to_s
|
|
71
|
+
(params[:token_program_id] || Constants::TOKEN_PROGRAM_ID).to_s
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
# Extracts the associated token account program id from the constants
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solace
|
|
4
|
+
module Composers
|
|
5
|
+
# Composer for creating a Token-2022 Program CloseAccount instruction.
|
|
6
|
+
#
|
|
7
|
+
# The CloseAccount instruction closes a token account and transfers remaining
|
|
8
|
+
# lamports to a destination account. The account must have a balance of zero tokens.
|
|
9
|
+
#
|
|
10
|
+
# Required accounts:
|
|
11
|
+
# - **Account**: token account to close (writable, non-signer)
|
|
12
|
+
# - **Destination**: account to receive lamports (writable, non-signer)
|
|
13
|
+
# - **Authority**: account authority (non-writable, signer)
|
|
14
|
+
#
|
|
15
|
+
# @example Compose and build a close account instruction
|
|
16
|
+
# composer = Token2022ProgramCloseAccountComposer.new(
|
|
17
|
+
# account: token_account_address,
|
|
18
|
+
# destination: destination_address,
|
|
19
|
+
# authority: authority_address
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @since 0.1.5
|
|
23
|
+
class Token2022ProgramCloseAccountComposer < Base
|
|
24
|
+
# Extracts the token account address from the params
|
|
25
|
+
#
|
|
26
|
+
# @return [String] The token account address
|
|
27
|
+
def account
|
|
28
|
+
params[:account].to_s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Extracts the destination address from the params
|
|
32
|
+
#
|
|
33
|
+
# @return [String] The destination address
|
|
34
|
+
def destination
|
|
35
|
+
params[:destination].to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Extracts the authority address from the params
|
|
39
|
+
#
|
|
40
|
+
# @return [String] The authority address
|
|
41
|
+
def authority
|
|
42
|
+
params[:authority].to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [String] The Token-2022 program id.
|
|
46
|
+
def token_2022_program
|
|
47
|
+
Constants::TOKEN_2022_PROGRAM_ID.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Setup accounts required for close account instruction
|
|
51
|
+
# Called automatically during initialization
|
|
52
|
+
#
|
|
53
|
+
# @return [void]
|
|
54
|
+
def setup_accounts
|
|
55
|
+
account_context.add_writable_nonsigner(account)
|
|
56
|
+
account_context.add_writable_nonsigner(destination)
|
|
57
|
+
account_context.add_readonly_signer(authority)
|
|
58
|
+
account_context.add_readonly_nonsigner(token_2022_program)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Build instruction with resolved account indices
|
|
62
|
+
#
|
|
63
|
+
# @param account_context [Utils::AccountContext] The account context
|
|
64
|
+
# @return [Solace::Instruction]
|
|
65
|
+
def build_instruction(account_context)
|
|
66
|
+
Instructions::Token2022::CloseAccountInstruction.build(
|
|
67
|
+
account_index: account_context.index_of(account),
|
|
68
|
+
authority_index: account_context.index_of(authority),
|
|
69
|
+
destination_index: account_context.index_of(destination),
|
|
70
|
+
program_index: account_context.index_of(token_2022_program)
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solace
|
|
4
|
+
module Composers
|
|
5
|
+
# Composer for initializing a mint via the Token-2022 Program.
|
|
6
|
+
#
|
|
7
|
+
# This composer resolves and orders the required accounts for an `InitializeMint`
|
|
8
|
+
# instruction, sets up their access permissions, and delegates construction to the
|
|
9
|
+
# appropriate instruction builder (`Instructions::Token2022::InitializeMintInstruction`).
|
|
10
|
+
#
|
|
11
|
+
# Required accounts:
|
|
12
|
+
# - **Mint Account**: the mint account to initialize (writable, non-signer)
|
|
13
|
+
# - **Rent Sysvar**: the rent sysvar (readonly, non-signer)
|
|
14
|
+
# - **Program**: Token-2022 program (readonly, non-signer)
|
|
15
|
+
#
|
|
16
|
+
# @example Compose and build an initialize_mint instruction
|
|
17
|
+
# composer = Token2022ProgramInitializeMintComposer.new(
|
|
18
|
+
# decimals: 6,
|
|
19
|
+
# mint_authority: mint_authority_pubkey,
|
|
20
|
+
# freeze_authority: freeze_authority_pubkey,
|
|
21
|
+
# mint_account: mint_address,
|
|
22
|
+
# )
|
|
23
|
+
#
|
|
24
|
+
# @see Instructions::Token2022::InitializeMintInstruction
|
|
25
|
+
# @since 0.1.5
|
|
26
|
+
class Token2022ProgramInitializeMintComposer < Base
|
|
27
|
+
# Extracts the mint account address from the params
|
|
28
|
+
#
|
|
29
|
+
# @return [String] The mint account address
|
|
30
|
+
def mint_account
|
|
31
|
+
params[:mint_account].to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the rent sysvar address
|
|
35
|
+
#
|
|
36
|
+
# @return [String] The rent sysvar address
|
|
37
|
+
def rent_sysvar
|
|
38
|
+
Constants::SYSVAR_RENT_PROGRAM_ID.to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [String] The Token-2022 program id.
|
|
42
|
+
def token_2022_program
|
|
43
|
+
Constants::TOKEN_2022_PROGRAM_ID.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Extracts the mint authority address from the params
|
|
47
|
+
#
|
|
48
|
+
# @return [String] The mint authority address
|
|
49
|
+
def mint_authority
|
|
50
|
+
params[:mint_authority].to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Extracts the freeze authority address from the params
|
|
54
|
+
#
|
|
55
|
+
# @return [String] The freeze authority address
|
|
56
|
+
def freeze_authority
|
|
57
|
+
params[:freeze_authority]&.to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the decimals for the mint
|
|
61
|
+
#
|
|
62
|
+
# @return [Integer] The decimals for the mint
|
|
63
|
+
def decimals
|
|
64
|
+
params[:decimals]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Setup accounts required for the InitializeMint instruction
|
|
68
|
+
# Called automatically during initialization
|
|
69
|
+
#
|
|
70
|
+
# @return [void]
|
|
71
|
+
def setup_accounts
|
|
72
|
+
account_context.add_writable_nonsigner(mint_account)
|
|
73
|
+
account_context.add_readonly_nonsigner(rent_sysvar)
|
|
74
|
+
account_context.add_readonly_nonsigner(token_2022_program)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Build instruction with resolved account indices
|
|
78
|
+
#
|
|
79
|
+
# @param account_context [Utils::AccountContext] The account context
|
|
80
|
+
# @return [Solace::Instruction]
|
|
81
|
+
def build_instruction(account_context)
|
|
82
|
+
Instructions::Token2022::InitializeMintInstruction.build(
|
|
83
|
+
mint_account_index: account_context.index_of(mint_account),
|
|
84
|
+
rent_sysvar_index: account_context.index_of(rent_sysvar),
|
|
85
|
+
program_index: account_context.index_of(token_2022_program),
|
|
86
|
+
decimals: decimals,
|
|
87
|
+
mint_authority: mint_authority,
|
|
88
|
+
freeze_authority: freeze_authority
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solace
|
|
4
|
+
module Composers
|
|
5
|
+
# Composer for creating a MintTo instruction for the Token-2022 Program.
|
|
6
|
+
#
|
|
7
|
+
# This composer builds a MintTo instruction that can be added to a transaction to mint tokens
|
|
8
|
+
# to a specified token account.
|
|
9
|
+
#
|
|
10
|
+
# Required accounts:
|
|
11
|
+
# - **Mint**: The mint account (writable, non-signer)
|
|
12
|
+
# - **Destination**: The token account to mint to (writable, non-signer)
|
|
13
|
+
# - **Mint Authority**: The mint authority account (readonly, signer)
|
|
14
|
+
#
|
|
15
|
+
# @example Build a MintTo instruction
|
|
16
|
+
# composer = Solace::Composers::Token2022ProgramMintToComposer.new(
|
|
17
|
+
# mint: mint,
|
|
18
|
+
# destination: destination,
|
|
19
|
+
# mint_authority: mint_authority,
|
|
20
|
+
# amount: 100
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# @since 0.1.5
|
|
24
|
+
class Token2022ProgramMintToComposer < Base
|
|
25
|
+
# Extracts the mint address from the params
|
|
26
|
+
#
|
|
27
|
+
# @return [String] The mint address
|
|
28
|
+
def mint
|
|
29
|
+
params[:mint].to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Extracts the destination address from the params
|
|
33
|
+
#
|
|
34
|
+
# @return [String] The destination address
|
|
35
|
+
def destination
|
|
36
|
+
params[:destination].to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Extracts the mint authority address from the params
|
|
40
|
+
#
|
|
41
|
+
# @return [String] The mint authority address
|
|
42
|
+
def mint_authority
|
|
43
|
+
params[:mint_authority].to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [String] The Token-2022 program id.
|
|
47
|
+
def token_2022_program
|
|
48
|
+
Constants::TOKEN_2022_PROGRAM_ID.to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extracts the amount from the params
|
|
52
|
+
#
|
|
53
|
+
# @return [Integer] The amount
|
|
54
|
+
def amount
|
|
55
|
+
params[:amount].to_i
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Setup accounts required for MintTo instruction
|
|
59
|
+
# Called automatically during initialization
|
|
60
|
+
#
|
|
61
|
+
# @return [void]
|
|
62
|
+
def setup_accounts
|
|
63
|
+
account_context.add_writable_nonsigner(mint)
|
|
64
|
+
account_context.add_writable_nonsigner(destination)
|
|
65
|
+
account_context.add_readonly_signer(mint_authority)
|
|
66
|
+
account_context.add_readonly_nonsigner(token_2022_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::Token2022::MintToInstruction.build(
|
|
75
|
+
amount: amount,
|
|
76
|
+
mint_index: account_context.index_of(mint),
|
|
77
|
+
destination_index: account_context.index_of(destination),
|
|
78
|
+
mint_authority_index: account_context.index_of(mint_authority),
|
|
79
|
+
program_index: account_context.index_of(token_2022_program)
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solace
|
|
4
|
+
module Composers
|
|
5
|
+
# Composer for creating a Token-2022 Program `TransferChecked` instruction.
|
|
6
|
+
#
|
|
7
|
+
# This composer resolves and orders the required accounts for a `TransferChecked` instruction,
|
|
8
|
+
# sets up their access permissions, and delegates construction to the appropriate
|
|
9
|
+
# instruction builder (`Instructions::Token2022::TransferCheckedInstruction`).
|
|
10
|
+
#
|
|
11
|
+
# Required accounts:
|
|
12
|
+
# - **From**: source token account (writable, non-signer)
|
|
13
|
+
# - **To**: destination token account (writable, non-signer)
|
|
14
|
+
# - **Mint**: mint address (readonly, non-signer)
|
|
15
|
+
# - **Authority**: token owner (writable, signer)
|
|
16
|
+
# - **Program**: Token-2022 program (readonly, non-signer)
|
|
17
|
+
#
|
|
18
|
+
# @example Compose and build a transfer_checked instruction
|
|
19
|
+
# composer = Token2022ProgramTransferCheckedComposer.new(
|
|
20
|
+
# from: from_address,
|
|
21
|
+
# to: to_address,
|
|
22
|
+
# mint: mint_address,
|
|
23
|
+
# authority: authority_pubkey,
|
|
24
|
+
# amount: 1_000_000,
|
|
25
|
+
# decimals: 6
|
|
26
|
+
# )
|
|
27
|
+
#
|
|
28
|
+
# @see Instructions::Token2022::TransferCheckedInstruction
|
|
29
|
+
# @since 0.1.5
|
|
30
|
+
class Token2022ProgramTransferCheckedComposer < Base
|
|
31
|
+
# Extracts the to address from the params
|
|
32
|
+
#
|
|
33
|
+
# @return [String] The to address
|
|
34
|
+
def to
|
|
35
|
+
params[:to].to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Extracts the from address from the params
|
|
39
|
+
#
|
|
40
|
+
# @return [String] The from address
|
|
41
|
+
def from
|
|
42
|
+
params[:from].to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Extracts the authority address from the params
|
|
46
|
+
#
|
|
47
|
+
# The authority is the owner of the token account
|
|
48
|
+
#
|
|
49
|
+
# @return [String] The authority address
|
|
50
|
+
def authority
|
|
51
|
+
params[:authority].to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Extracts the mint address from the params
|
|
55
|
+
#
|
|
56
|
+
# @return [String] The mint address
|
|
57
|
+
def mint
|
|
58
|
+
params[:mint].to_s
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [String] The Token-2022 program id.
|
|
62
|
+
def token_2022_program
|
|
63
|
+
Constants::TOKEN_2022_PROGRAM_ID.to_s
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns the amount to transfer
|
|
67
|
+
#
|
|
68
|
+
# @return [Integer] The amount to transfer
|
|
69
|
+
def amount
|
|
70
|
+
params[:amount]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns the decimals for the mint of the token
|
|
74
|
+
#
|
|
75
|
+
# @return [Integer] The decimals for the mint
|
|
76
|
+
def decimals
|
|
77
|
+
params[:decimals]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Setup accounts required for transfer instruction
|
|
81
|
+
# Called automatically during initialization
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
84
|
+
def setup_accounts
|
|
85
|
+
account_context.add_writable_signer(authority)
|
|
86
|
+
account_context.add_writable_nonsigner(to)
|
|
87
|
+
account_context.add_writable_nonsigner(from)
|
|
88
|
+
account_context.add_readonly_nonsigner(mint)
|
|
89
|
+
account_context.add_readonly_nonsigner(token_2022_program)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Build instruction with resolved account indices
|
|
93
|
+
#
|
|
94
|
+
# @param account_context [Utils::AccountContext] The account context
|
|
95
|
+
# @return [Solace::Instruction]
|
|
96
|
+
def build_instruction(account_context)
|
|
97
|
+
Instructions::Token2022::TransferCheckedInstruction.build(
|
|
98
|
+
amount: amount,
|
|
99
|
+
decimals: decimals,
|
|
100
|
+
to_index: account_context.index_of(to),
|
|
101
|
+
from_index: account_context.index_of(from),
|
|
102
|
+
mint_index: account_context.index_of(mint),
|
|
103
|
+
authority_index: account_context.index_of(authority),
|
|
104
|
+
program_index: account_context.index_of(token_2022_program)
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solace
|
|
4
|
+
module Composers
|
|
5
|
+
# Composer for creating a Token-2022 Program Transfer instruction.
|
|
6
|
+
#
|
|
7
|
+
# This composer resolves and orders the required accounts for a `Transfer` instruction,
|
|
8
|
+
# sets up their access permissions, and delegates construction to the appropriate
|
|
9
|
+
# instruction builder (`Instructions::Token2022::TransferInstruction`).
|
|
10
|
+
#
|
|
11
|
+
# Required accounts:
|
|
12
|
+
# - **Owner**: token account owner (writable, signer)
|
|
13
|
+
# - **Source**: source token account (writable, non-signer)
|
|
14
|
+
# - **Destination**: destination token account (writable, non-signer)
|
|
15
|
+
#
|
|
16
|
+
# @example Compose and build a transfer instruction
|
|
17
|
+
# composer = Token2022ProgramTransferComposer.new(
|
|
18
|
+
# amount: 1_000_000,
|
|
19
|
+
# owner: owner_address,
|
|
20
|
+
# source: source_address,
|
|
21
|
+
# destination: destination_address,
|
|
22
|
+
# )
|
|
23
|
+
#
|
|
24
|
+
# @see Instructions::Token2022::TransferInstruction
|
|
25
|
+
# @since 0.1.5
|
|
26
|
+
class Token2022ProgramTransferComposer < Base
|
|
27
|
+
# Extracts the owner address from the params
|
|
28
|
+
#
|
|
29
|
+
# @return [String] The owner address
|
|
30
|
+
def owner
|
|
31
|
+
params[:owner].to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Extracts the source associated token address from the params
|
|
35
|
+
#
|
|
36
|
+
# @return [String] The source associated token address
|
|
37
|
+
def source
|
|
38
|
+
params[:source].to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Extracts the destination associated token address from the params
|
|
42
|
+
#
|
|
43
|
+
# @return [String] The destination associated token address
|
|
44
|
+
def destination
|
|
45
|
+
params[:destination].to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [String] The Token-2022 program id.
|
|
49
|
+
def token_2022_program
|
|
50
|
+
Constants::TOKEN_2022_PROGRAM_ID.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the lamports to transfer
|
|
54
|
+
#
|
|
55
|
+
# @return [Integer] The lamports to transfer
|
|
56
|
+
def amount
|
|
57
|
+
params[:amount]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Setup accounts required for transfer instruction
|
|
61
|
+
# Called automatically during initialization
|
|
62
|
+
#
|
|
63
|
+
# @return [void]
|
|
64
|
+
def setup_accounts
|
|
65
|
+
account_context.add_writable_signer(owner)
|
|
66
|
+
account_context.add_writable_nonsigner(source)
|
|
67
|
+
account_context.add_writable_nonsigner(destination)
|
|
68
|
+
account_context.add_readonly_nonsigner(token_2022_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
|
+
Instructions::Token2022::TransferInstruction.build(
|
|
77
|
+
amount: amount,
|
|
78
|
+
owner_index: account_context.index_of(owner),
|
|
79
|
+
source_index: account_context.index_of(source),
|
|
80
|
+
destination_index: account_context.index_of(destination),
|
|
81
|
+
program_index: account_context.index_of(token_2022_program)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/solace/connection.rb
CHANGED
|
@@ -33,6 +33,7 @@ module Solace
|
|
|
33
33
|
# Solace::Errors::ConfirmationTimeout
|
|
34
34
|
# ]
|
|
35
35
|
# @since 0.0.1
|
|
36
|
+
# rubocop:disable Metrics/ClassLength
|
|
36
37
|
class Connection
|
|
37
38
|
# @!attribute [r] rpc_url
|
|
38
39
|
# The URL of the Solana RPC node
|
|
@@ -196,15 +197,33 @@ module Solace
|
|
|
196
197
|
@rpc_client.rpc_request('getTokenAccountBalance', [token_account, default_options]).dig('result', 'value')
|
|
197
198
|
end
|
|
198
199
|
|
|
199
|
-
# Gets the token accounts by owner
|
|
200
|
+
# Gets the token accounts by owner.
|
|
201
|
+
#
|
|
202
|
+
# Defaults to the legacy SPL Token program; pass
|
|
203
|
+
# +token_program_id: Solace::Constants::TOKEN_2022_PROGRAM_ID+ for Token-2022.
|
|
200
204
|
#
|
|
201
205
|
# @param owner [String] The public key of the owner
|
|
202
|
-
# @
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
# @param token_program_id [String] The token program to filter by (defaults to legacy SPL Token)
|
|
207
|
+
# @return [Array<Hash>] The token accounts owned by the owner under the given token program
|
|
208
|
+
def get_token_accounts_by_owner(owner, token_program_id: Constants::TOKEN_PROGRAM_ID)
|
|
209
|
+
params = [owner, { programId: token_program_id }, default_options]
|
|
205
210
|
@rpc_client.rpc_request('getTokenAccountsByOwner', params).dig('result', 'value')
|
|
206
211
|
end
|
|
207
212
|
|
|
213
|
+
# Discovery-only helper: returns the program ID that owns a given mint.
|
|
214
|
+
#
|
|
215
|
+
# Use this to dispatch between {Constants::TOKEN_PROGRAM_ID} and
|
|
216
|
+
# {Constants::TOKEN_2022_PROGRAM_ID} when the mint's program is not known
|
|
217
|
+
# ahead of time. If you also need the rest of the mint's account data
|
|
218
|
+
# (decimals, supply, ...), call {#get_account_info} directly and read
|
|
219
|
+
# +'owner'+ yourself — calling both would cost two RPC roundtrips.
|
|
220
|
+
#
|
|
221
|
+
# @param mint [String] The mint address
|
|
222
|
+
# @return [String, nil] The program ID that owns the mint, or +nil+ if the account does not exist
|
|
223
|
+
def get_mint_program_id(mint)
|
|
224
|
+
get_account_info(mint)&.dig('owner')
|
|
225
|
+
end
|
|
226
|
+
|
|
208
227
|
# Get the transaction by signature
|
|
209
228
|
#
|
|
210
229
|
# @param signature [String] The signature of the transaction
|
|
@@ -335,4 +354,5 @@ module Solace
|
|
|
335
354
|
Process.clock_gettime(Process::CLOCK_MONOTONIC) + seconds
|
|
336
355
|
end
|
|
337
356
|
end
|
|
357
|
+
# rubocop:enable Metrics/ClassLength
|
|
338
358
|
end
|
data/lib/solace/constants.rb
CHANGED
|
@@ -23,10 +23,16 @@ module Solace
|
|
|
23
23
|
COMPUTE_BUDGET_PROGRAM_ID = 'ComputeBudget111111111111111111111111111111'
|
|
24
24
|
|
|
25
25
|
# @!attribute TOKEN_PROGRAM_ID
|
|
26
|
-
# The public key of the SPL Token Program
|
|
27
|
-
# This is the same across all Solana clusters
|
|
26
|
+
# The public key of the SPL Token Program (legacy).
|
|
27
|
+
# This is the same across all Solana clusters.
|
|
28
28
|
TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
|
|
29
29
|
|
|
30
|
+
# @!attribute TOKEN_2022_PROGRAM_ID
|
|
31
|
+
# The public key of the Token-2022 Program (formerly Token Extensions).
|
|
32
|
+
# This is the same across all Solana clusters. See {Programs::Token2022}
|
|
33
|
+
# for the full description of how it relates to the legacy program.
|
|
34
|
+
TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
|
|
35
|
+
|
|
30
36
|
# @!attribute ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
|
|
31
37
|
# The public key of the Associated Token Account Program
|
|
32
38
|
# This is the same across all Solana clusters
|
|
@@ -29,7 +29,7 @@ module Solace
|
|
|
29
29
|
# SPL Token Program transfer instruction layout:
|
|
30
30
|
# - 1 byte: instruction index (12 for transfer checked)
|
|
31
31
|
# - 8 bytes: amount (u64, little-endian)
|
|
32
|
-
# -
|
|
32
|
+
# - 1 byte: decimals (u8)
|
|
33
33
|
#
|
|
34
34
|
# @param amount [Integer] Amount to transfer (in tokens, according to mint's decimals)
|
|
35
35
|
# @param decimals [Integer] Number of decimals for the token
|