solace 0.0.2
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 +7 -0
- data/CHANGELOG +0 -0
- data/LICENSE +0 -0
- data/README.md +661 -0
- data/lib/solace/address_lookup_table.rb +50 -0
- data/lib/solace/concerns/binary_serializable.rb +30 -0
- data/lib/solace/connection.rb +187 -0
- data/lib/solace/constants.rb +52 -0
- data/lib/solace/instruction.rb +38 -0
- data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +68 -0
- data/lib/solace/instructions/spl_token/initialize_account_instruction.rb +46 -0
- data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +68 -0
- data/lib/solace/instructions/spl_token/mint_to_instruction.rb +48 -0
- data/lib/solace/instructions/spl_token/transfer_instruction.rb +48 -0
- data/lib/solace/instructions/system_program/create_account_instruction.rb +58 -0
- data/lib/solace/instructions/transfer_checked_instruction.rb +58 -0
- data/lib/solace/instructions/transfer_instruction.rb +48 -0
- data/lib/solace/keypair.rb +121 -0
- data/lib/solace/message.rb +95 -0
- data/lib/solace/programs/associated_token_account.rb +96 -0
- data/lib/solace/programs/base.rb +22 -0
- data/lib/solace/programs/spl_token.rb +187 -0
- data/lib/solace/public_key.rb +74 -0
- data/lib/solace/serializable_record.rb +26 -0
- data/lib/solace/serializers/address_lookup_table_deserializer.rb +62 -0
- data/lib/solace/serializers/address_lookup_table_serializer.rb +54 -0
- data/lib/solace/serializers/base.rb +31 -0
- data/lib/solace/serializers/base_deserializer.rb +56 -0
- data/lib/solace/serializers/base_serializer.rb +52 -0
- data/lib/solace/serializers/instruction_deserializer.rb +62 -0
- data/lib/solace/serializers/instruction_serializer.rb +54 -0
- data/lib/solace/serializers/message_deserializer.rb +116 -0
- data/lib/solace/serializers/message_serializer.rb +95 -0
- data/lib/solace/serializers/transaction_deserializer.rb +49 -0
- data/lib/solace/serializers/transaction_serializer.rb +60 -0
- data/lib/solace/transaction.rb +98 -0
- data/lib/solace/utils/codecs.rb +220 -0
- data/lib/solace/utils/curve25519_dalek.rb +59 -0
- data/lib/solace/utils/libcurve25519_dalek-linux/libcurve25519_dalek.so +0 -0
- data/lib/solace/utils/libcurve25519_dalek-macos/libcurve25519_dalek.dylib +0 -0
- data/lib/solace/utils/libcurve25519_dalek-windows/curve25519_dalek.dll +0 -0
- data/lib/solace/utils/pda.rb +100 -0
- data/lib/solace/version.rb +5 -0
- data/lib/solace.rb +39 -0
- metadata +165 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Instructions
|
5
|
+
module SystemProgram
|
6
|
+
class CreateAccountInstruction
|
7
|
+
# !@const INSTRUCTION_INDEX
|
8
|
+
# Instruction index for SystemProgram::CreateAccount
|
9
|
+
# This is the same across all Solana clusters
|
10
|
+
# @return [Array<Integer>]
|
11
|
+
INSTRUCTION_INDEX = [0, 0, 0, 0].freeze
|
12
|
+
|
13
|
+
# Builds a SystemProgram::CreateAccount instruction
|
14
|
+
#
|
15
|
+
# @param space [Integer] Number of bytes to allocate for the new account
|
16
|
+
# @param lamports [Integer] Amount of lamports to fund the new account
|
17
|
+
# @param owner [String] The program_id of the owner of the new account
|
18
|
+
# @param from_index [Integer] Index of the funding account (payer) in the transaction's accounts
|
19
|
+
# @param new_account_index [Integer] Index of the new account to create in the transaction's accounts
|
20
|
+
# @param system_program_index [Integer] Index of the system program in the transaction's accounts (default: 2)
|
21
|
+
# @return [Solace::Instruction]
|
22
|
+
def self.build(
|
23
|
+
space:,
|
24
|
+
lamports:,
|
25
|
+
owner: Solace::Constants::SYSTEM_PROGRAM_ID,
|
26
|
+
from_index:,
|
27
|
+
new_account_index:,
|
28
|
+
system_program_index: 2
|
29
|
+
)
|
30
|
+
Solace::Instruction.new.tap do |ix|
|
31
|
+
ix.program_index = system_program_index
|
32
|
+
ix.accounts = [from_index, new_account_index]
|
33
|
+
ix.data = data(lamports, space, owner)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Builds the data for a SystemProgram::CreateAccount instruction
|
38
|
+
#
|
39
|
+
# The BufferLayout is:
|
40
|
+
# - [Instruction Index (4 bytes)]
|
41
|
+
# - [Lamports (8 bytes)]
|
42
|
+
# - [Space (8 bytes)]
|
43
|
+
# - [Owner (32 bytes)]
|
44
|
+
#
|
45
|
+
# @param lamports [Integer] Amount of lamports to fund the new account
|
46
|
+
# @param space [Integer] Number of bytes to allocate for the new account
|
47
|
+
# @param owner [String] The program_id of the owner of the new account
|
48
|
+
# @return [Array] 4-byte instruction index + 8-byte lamports + 8-byte space + 32-byte owner
|
49
|
+
def self.data(lamports, space, owner)
|
50
|
+
INSTRUCTION_INDEX +
|
51
|
+
Solace::Utils::Codecs.encode_le_u64(lamports).bytes +
|
52
|
+
Solace::Utils::Codecs.encode_le_u64(space).bytes +
|
53
|
+
Solace::Utils::Codecs.base58_to_bytes(owner)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
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
|
@@ -0,0 +1,48 @@
|
|
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
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'base58'
|
5
|
+
|
6
|
+
# =============================
|
7
|
+
# Keypair
|
8
|
+
# =============================
|
9
|
+
#
|
10
|
+
# Represents a Solana Ed25519 Keypair
|
11
|
+
module Solace
|
12
|
+
class Keypair
|
13
|
+
# !@const SECRET_LENGTH
|
14
|
+
# The length of a Solana secret key in bytes
|
15
|
+
#
|
16
|
+
# @return [Integer] The length of a secret key
|
17
|
+
SECRET_LENGTH = 64
|
18
|
+
|
19
|
+
# !@const SEED_LENGTH
|
20
|
+
# The length of a Solana seed in bytes (borrowed from RbNaCl)
|
21
|
+
#
|
22
|
+
# @return [Integer] The length of a seed
|
23
|
+
SEED_LENGTH = RbNaCl::Signatures::Ed25519::SEEDBYTES
|
24
|
+
|
25
|
+
# !@const SigningKey
|
26
|
+
# The RbNaCl signing key
|
27
|
+
#
|
28
|
+
# @return [RbNaCl::Signatures::Ed25519::SigningKey]
|
29
|
+
SigningKey = RbNaCl::Signatures::Ed25519::SigningKey
|
30
|
+
|
31
|
+
# !@attribute [r] keypair_bytes
|
32
|
+
# @return [Array<Integer>] The keypair bytes
|
33
|
+
attr_reader :keypair_bytes
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# Generate a new random keypair
|
37
|
+
#
|
38
|
+
# @return [Keypair]
|
39
|
+
def generate
|
40
|
+
from_seed(RbNaCl::Random.random_bytes(SEED_LENGTH))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a keypair from a 32-byte seed
|
44
|
+
#
|
45
|
+
# @param seed [String] 32-byte array
|
46
|
+
# @return [Keypair]
|
47
|
+
def from_seed(seed)
|
48
|
+
raise ArgumentError, 'Seed must be 32 bytes' unless seed.length == SEED_LENGTH
|
49
|
+
|
50
|
+
new(SigningKey.new(seed).keypair_bytes.bytes)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create a keypair from a 64-byte secret key
|
54
|
+
#
|
55
|
+
# @param secret_key [String] 64-byte array
|
56
|
+
# @return [Keypair]
|
57
|
+
def from_secret_key(secret_key)
|
58
|
+
raise ArgumentError, 'Secret key must be 64 bytes' unless secret_key.length == SECRET_LENGTH
|
59
|
+
|
60
|
+
new(SigningKey.new(secret_key[0..31]).keypair_bytes.bytes)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Initialize a new keypair
|
65
|
+
#
|
66
|
+
# @param keypair_bytes [Array<Integer>] The keypair bytes
|
67
|
+
# @return [Keypair] The new keypair object
|
68
|
+
def initialize(keypair_bytes)
|
69
|
+
raise ArgumentError, 'Keypair must be 64 bytes' unless keypair_bytes.length == SECRET_LENGTH
|
70
|
+
|
71
|
+
@keypair_bytes = keypair_bytes
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the public key
|
75
|
+
#
|
76
|
+
# @return [PublicKey]
|
77
|
+
def public_key
|
78
|
+
@public_key ||= Solace::PublicKey.new(public_key_bytes)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the signing key
|
82
|
+
#
|
83
|
+
# @return [RbNaCl::Signatures::Ed25519::SigningKey]
|
84
|
+
def signing_key
|
85
|
+
@signing_key ||= SigningKey.new(private_key)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the public key bytes
|
89
|
+
#
|
90
|
+
# The public key bytes are the last 32 bytes of the keypair
|
91
|
+
#
|
92
|
+
# @return [String] 32 bytes
|
93
|
+
def public_key_bytes
|
94
|
+
keypair_bytes[32..63]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns the private key
|
98
|
+
#
|
99
|
+
# The private key is the first 32 bytes of the keypair
|
100
|
+
#
|
101
|
+
# @return [String] 32 characters
|
102
|
+
def private_key
|
103
|
+
keypair_bytes[0..31].pack('C*')
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the public key as a Base58 string
|
107
|
+
#
|
108
|
+
# @return [String] Base58 encoded public key
|
109
|
+
def address
|
110
|
+
public_key.to_base58
|
111
|
+
end
|
112
|
+
|
113
|
+
# Signs a message (string or binary)
|
114
|
+
#
|
115
|
+
# @param message [String | Binary]
|
116
|
+
# @return [Binary] signature (binary)
|
117
|
+
def sign(message)
|
118
|
+
signing_key.sign(message)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# =============================
|
4
|
+
# Message
|
5
|
+
# =============================
|
6
|
+
#
|
7
|
+
# Represents the message portion of a Solana transaction (legacy or versioned).
|
8
|
+
# Handles serialization and deserialization of message fields.
|
9
|
+
module Solace
|
10
|
+
class Message < Solace::SerializableRecord
|
11
|
+
# @!const SERIALIZER
|
12
|
+
# @return [Solace::Serializers::MessageSerializer] The serializer for the message
|
13
|
+
SERIALIZER = Solace::Serializers::MessageSerializer
|
14
|
+
|
15
|
+
# @!const DESERIALIZER
|
16
|
+
# @return [Solace::Serializers::MessageDeserializer] The deserializer for the message
|
17
|
+
DESERIALIZER = Solace::Serializers::MessageDeserializer
|
18
|
+
|
19
|
+
# @!attribute [rw] version
|
20
|
+
# @return [Integer, nil] Message version (nil for legacy)
|
21
|
+
attr_accessor :version
|
22
|
+
|
23
|
+
# @!attribute [rw] header
|
24
|
+
# @return [Array<Integer>] Message header [num_required_signatures, num_readonly_signed, num_readonly_unsigned]
|
25
|
+
attr_accessor :header
|
26
|
+
|
27
|
+
# @!attribute [rw] accounts
|
28
|
+
# @return [Array<String>] Account public keys (base58)
|
29
|
+
attr_accessor :accounts
|
30
|
+
|
31
|
+
# @!attribute [rw] recent_blockhash
|
32
|
+
# @return [String] Recent blockhash (base58)
|
33
|
+
attr_accessor :recent_blockhash
|
34
|
+
|
35
|
+
# @!attribute [rw] instructions
|
36
|
+
# @return [Array<Solace::Instruction>] Instructions in the message
|
37
|
+
attr_accessor :instructions
|
38
|
+
|
39
|
+
# @!attribute [rw] address_lookup_tables
|
40
|
+
# @return [Array<Solace::AddressLookupTable>] Address lookup tables (for versioned messages)
|
41
|
+
attr_accessor :address_lookup_tables
|
42
|
+
|
43
|
+
# Initialize a new Message
|
44
|
+
#
|
45
|
+
# @param version [Integer, nil] Message version (nil for legacy)
|
46
|
+
# @param accounts [Array<String>] Account public keys (base58)
|
47
|
+
# @param instructions [Array<Solace::Instruction>] Instructions in the message
|
48
|
+
# @param recent_blockhash [String] Recent blockhash (base58)
|
49
|
+
# @param header [Array<Integer>] Message header [num_required_signatures, num_readonly_signed, num_readonly_unsigned]
|
50
|
+
# @param address_lookup_tables [Array<Solace::AddressLookupTable>]
|
51
|
+
def initialize(
|
52
|
+
version: nil,
|
53
|
+
accounts: [],
|
54
|
+
instructions: [],
|
55
|
+
recent_blockhash: nil,
|
56
|
+
header: [0, 0, 0],
|
57
|
+
address_lookup_tables: []
|
58
|
+
)
|
59
|
+
@version = version
|
60
|
+
@header = header
|
61
|
+
@accounts = accounts
|
62
|
+
@recent_blockhash = recent_blockhash
|
63
|
+
@instructions = instructions
|
64
|
+
@address_lookup_tables = address_lookup_tables
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if the message is versioned
|
68
|
+
#
|
69
|
+
# @return [Boolean] True if the message is versioned, false otherwise
|
70
|
+
def versioned?
|
71
|
+
!version.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the number of required signatures
|
75
|
+
#
|
76
|
+
# @return [Integer] The number of required signatures
|
77
|
+
def num_required_signatures
|
78
|
+
header[0]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the number of readonly signed accounts
|
82
|
+
#
|
83
|
+
# @return [Integer] The number of readonly signed accounts
|
84
|
+
def num_readonly_signed_accounts
|
85
|
+
header[1]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the number of readonly unsigned accounts
|
89
|
+
#
|
90
|
+
# @return [Integer] The number of readonly unsigned accounts
|
91
|
+
def num_readonly_unsigned_accounts
|
92
|
+
header[2]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Programs
|
5
|
+
# A client for interacting with the SPL Token Program.
|
6
|
+
class AssociatedTokenAccount < Base
|
7
|
+
class << self
|
8
|
+
# Gets the address of an associated token account.
|
9
|
+
#
|
10
|
+
# @param owner [Solace::Keypair || Solace::PublicKey] The keypair of the owner.
|
11
|
+
# @param mint [Solace::Keypair || Solace::PublicKey] The keypair of the mint.
|
12
|
+
# @return [String] The address of the associated token account.
|
13
|
+
def get_address(owner:, mint:)
|
14
|
+
Solace::Utils::PDA.find_program_address(
|
15
|
+
[
|
16
|
+
owner.address,
|
17
|
+
Solace::Constants::TOKEN_PROGRAM_ID,
|
18
|
+
mint.address
|
19
|
+
],
|
20
|
+
Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initializes a new Associated Token Account client.
|
26
|
+
#
|
27
|
+
# @param connection [Solace::Connection] The connection to the Solana cluster.
|
28
|
+
def initialize(connection:)
|
29
|
+
super(connection:, program_id: Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Alias method for get_address
|
33
|
+
#
|
34
|
+
# @param oprtions [Hash] A hash of options for the get_address class method
|
35
|
+
def get_address(**options)
|
36
|
+
self.class.get_address(**options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a new associated token account.
|
40
|
+
#
|
41
|
+
# @param options [Hash] Options for calling the prepare_create_associated_token_account method.
|
42
|
+
# @return [String] The signature of the transaction.
|
43
|
+
def create_associated_token_account(**options)
|
44
|
+
tx = prepare_create_associated_token_account(**options)
|
45
|
+
|
46
|
+
@connection.send_transaction(tx.serialize)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Prepares a new associated token account and returns the signed transaction.
|
50
|
+
#
|
51
|
+
# @param owner [Solace::Keypair || Solace::PublicKey] The keypair of the owner.
|
52
|
+
# @param mint [Solace::Keypair || Solace::PublicKey] The keypair of the mint.
|
53
|
+
# @param payer [Solace::Keypair] The keypair that will pay for fees and rent.
|
54
|
+
# @return [Solace::Transaction] The signed transaction.
|
55
|
+
def prepare_create_associated_token_account(
|
56
|
+
owner:,
|
57
|
+
mint:,
|
58
|
+
payer:
|
59
|
+
)
|
60
|
+
ata_address, _ = get_address(owner:, mint:)
|
61
|
+
|
62
|
+
accounts = [
|
63
|
+
payer.address,
|
64
|
+
ata_address,
|
65
|
+
owner.address,
|
66
|
+
mint.address,
|
67
|
+
Solace::Constants::SYSTEM_PROGRAM_ID,
|
68
|
+
Solace::Constants::TOKEN_PROGRAM_ID,
|
69
|
+
Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
|
70
|
+
]
|
71
|
+
|
72
|
+
instruction = Solace::Instructions::AssociatedTokenAccount::CreateAssociatedTokenAccountInstruction.build(
|
73
|
+
funder_index: 0,
|
74
|
+
associated_token_account_index: 1,
|
75
|
+
owner_index: 2,
|
76
|
+
mint_index: 3,
|
77
|
+
system_program_index: 4,
|
78
|
+
token_program_index: 5,
|
79
|
+
program_index: 6
|
80
|
+
)
|
81
|
+
|
82
|
+
message = Solace::Message.new(
|
83
|
+
header: [1, 0, 4],
|
84
|
+
accounts: accounts,
|
85
|
+
recent_blockhash: @connection.get_latest_blockhash,
|
86
|
+
instructions: [instruction]
|
87
|
+
)
|
88
|
+
|
89
|
+
tx = Solace::Transaction.new(message: message)
|
90
|
+
tx.sign(payer)
|
91
|
+
|
92
|
+
tx
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/solace/programs/base.rb
|
4
|
+
|
5
|
+
module Solace
|
6
|
+
module Programs
|
7
|
+
# Base class for program-specific clients.
|
8
|
+
# Provides a consistent interface for interacting with on-chain programs.
|
9
|
+
class Base
|
10
|
+
attr_reader :connection, :program_id
|
11
|
+
|
12
|
+
# Initializes a new program client.
|
13
|
+
#
|
14
|
+
# @param connection [Solace::Connection] The connection to the Solana cluster.
|
15
|
+
# @param program_id [String] The base58 public key of the on-chain program.
|
16
|
+
def initialize(connection:, program_id:)
|
17
|
+
@connection = connection
|
18
|
+
@program_id = program_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solace
|
4
|
+
module Programs
|
5
|
+
# A client for interacting with the SPL Token Program.
|
6
|
+
class SplToken < Base
|
7
|
+
# Initializes a new SPL Token client.
|
8
|
+
#
|
9
|
+
# @param connection [Solace::Connection] The connection to the Solana cluster.
|
10
|
+
def initialize(connection:)
|
11
|
+
super(connection: connection, program_id: Solace::Constants::TOKEN_PROGRAM_ID)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a new SPL Token mint.
|
15
|
+
#
|
16
|
+
# @param options [Hash] Options for calling the prepare_create_mint method.
|
17
|
+
# @return [String] The signature of the transaction.
|
18
|
+
def create_mint(**options)
|
19
|
+
tx = prepare_create_mint(**options)
|
20
|
+
|
21
|
+
@connection.send_transaction(tx.serialize)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Prepares a new SPL Token mint and returns the signed transaction.
|
25
|
+
#
|
26
|
+
# @param payer [Solace::Keypair] The keypair that will pay for fees and rent.
|
27
|
+
# @param decimals [Integer] The number of decimal places for the token.
|
28
|
+
# @param mint_authority [String] The base58 public key for the mint authority.
|
29
|
+
# @param freeze_authority [String] (Optional) The base58 public key for the freeze authority.
|
30
|
+
# @param mint_keypair [Solace::Keypair] (Optional) The keypair for the new mint.
|
31
|
+
# @return [Solace::Transaction] The signed transaction.
|
32
|
+
def prepare_create_mint(
|
33
|
+
payer:,
|
34
|
+
decimals:,
|
35
|
+
mint_authority:,
|
36
|
+
freeze_authority:,
|
37
|
+
mint_keypair: Solace::Keypair.generate
|
38
|
+
)
|
39
|
+
accounts = [
|
40
|
+
payer.address,
|
41
|
+
mint_keypair.address,
|
42
|
+
Solace::Constants::SYSVAR_RENT_PROGRAM_ID,
|
43
|
+
Solace::Constants::TOKEN_PROGRAM_ID,
|
44
|
+
Solace::Constants::SYSTEM_PROGRAM_ID
|
45
|
+
]
|
46
|
+
|
47
|
+
rent_lamports = @connection.get_minimum_lamports_for_rent_exemption(82)
|
48
|
+
|
49
|
+
create_account_ix = Solace::Instructions::SystemProgram::CreateAccountInstruction.build(
|
50
|
+
from_index: 0,
|
51
|
+
new_account_index: 1,
|
52
|
+
system_program_index: 4,
|
53
|
+
lamports: rent_lamports,
|
54
|
+
space: 82,
|
55
|
+
owner: program_id
|
56
|
+
)
|
57
|
+
|
58
|
+
freeze_authority_address = freeze_authority.respond_to?(:address) ? freeze_authority.address : nil
|
59
|
+
|
60
|
+
initialize_mint_ix = Solace::Instructions::SplToken::InitializeMintInstruction.build(
|
61
|
+
mint_account_index: 1,
|
62
|
+
rent_sysvar_index: 2,
|
63
|
+
program_index: 3,
|
64
|
+
decimals: decimals,
|
65
|
+
mint_authority: mint_authority.address,
|
66
|
+
freeze_authority: freeze_authority_address
|
67
|
+
)
|
68
|
+
|
69
|
+
message = Message.new(
|
70
|
+
header: [2, 0, 3],
|
71
|
+
accounts: accounts,
|
72
|
+
recent_blockhash: @connection.get_latest_blockhash,
|
73
|
+
instructions: [create_account_ix, initialize_mint_ix]
|
74
|
+
)
|
75
|
+
|
76
|
+
tx = Transaction.new(message: message)
|
77
|
+
tx.sign(payer, mint_keypair)
|
78
|
+
|
79
|
+
tx
|
80
|
+
end
|
81
|
+
|
82
|
+
# Mint tokens to a token account
|
83
|
+
#
|
84
|
+
# @param options [Hash] Options for calling the prepare_mint_to method.
|
85
|
+
# @return [String] The signature of the transaction.
|
86
|
+
def mint_to(**options)
|
87
|
+
tx = prepare_mint_to(**options)
|
88
|
+
|
89
|
+
@connection.send_transaction(tx.serialize)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Prepares a mint to instruction and returns the signed transaction.
|
93
|
+
#
|
94
|
+
# @param options [Hash] Options for calling the prepare_mint_to method.
|
95
|
+
# @return [Solace::Transaction] The signed transaction.
|
96
|
+
def prepare_mint_to(
|
97
|
+
payer:,
|
98
|
+
mint:,
|
99
|
+
destination:,
|
100
|
+
amount:,
|
101
|
+
mint_authority:
|
102
|
+
)
|
103
|
+
accounts = [
|
104
|
+
payer.address,
|
105
|
+
mint_authority.address,
|
106
|
+
mint.address,
|
107
|
+
destination,
|
108
|
+
Solace::Constants::TOKEN_PROGRAM_ID,
|
109
|
+
]
|
110
|
+
|
111
|
+
ix = Solace::Instructions::SplToken::MintToInstruction.build(
|
112
|
+
amount:,
|
113
|
+
mint_authority_index: 1,
|
114
|
+
mint_index: 2,
|
115
|
+
destination_index: 3,
|
116
|
+
program_index: 4
|
117
|
+
)
|
118
|
+
|
119
|
+
message = Solace::Message.new(
|
120
|
+
header: [2, 0, 1],
|
121
|
+
accounts:,
|
122
|
+
instructions: [ix],
|
123
|
+
recent_blockhash: connection.get_latest_blockhash,
|
124
|
+
)
|
125
|
+
|
126
|
+
tx = Solace::Transaction.new(message: message)
|
127
|
+
tx.sign(payer, mint_authority)
|
128
|
+
|
129
|
+
tx
|
130
|
+
end
|
131
|
+
|
132
|
+
# Transfers tokens from one account to another
|
133
|
+
#
|
134
|
+
# @param options [Hash] Options for calling the prepare_transfer method.
|
135
|
+
# @return [String] The signature of the transaction.
|
136
|
+
def transfer(**options)
|
137
|
+
tx = prepare_transfer(**options)
|
138
|
+
|
139
|
+
@connection.send_transaction(tx.serialize)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Prepares a transfer instruction and returns the signed transaction.
|
143
|
+
#
|
144
|
+
# @param payer [Solace::Keypair] The keypair that will pay for fees and rent.
|
145
|
+
# @param source [String] The source token account address.
|
146
|
+
# @param destination [String] The destination token account address.
|
147
|
+
# @param amount [Integer] The number of tokens to transfer.
|
148
|
+
# @param owner [Solace::Keypair] The keypair of the owner of the source account.
|
149
|
+
# @return [Solace::Transaction] The signed transaction.
|
150
|
+
def prepare_transfer(
|
151
|
+
amount:,
|
152
|
+
payer:,
|
153
|
+
source:,
|
154
|
+
destination:,
|
155
|
+
owner:
|
156
|
+
)
|
157
|
+
accounts = [
|
158
|
+
payer.address,
|
159
|
+
owner.address,
|
160
|
+
source,
|
161
|
+
destination,
|
162
|
+
Solace::Constants::TOKEN_PROGRAM_ID
|
163
|
+
]
|
164
|
+
|
165
|
+
ix = Solace::Instructions::SplToken::TransferInstruction.build(
|
166
|
+
amount:,
|
167
|
+
owner_index: 1,
|
168
|
+
source_index: 2,
|
169
|
+
destination_index: 3,
|
170
|
+
program_index: 4
|
171
|
+
)
|
172
|
+
|
173
|
+
message = Solace::Message.new(
|
174
|
+
header: [2, 0, 1],
|
175
|
+
accounts:,
|
176
|
+
instructions: [ix],
|
177
|
+
recent_blockhash: connection.get_latest_blockhash
|
178
|
+
)
|
179
|
+
|
180
|
+
tx = Solace::Transaction.new(message:)
|
181
|
+
tx.sign(payer, owner)
|
182
|
+
|
183
|
+
tx
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|