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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +0 -0
  3. data/LICENSE +0 -0
  4. data/README.md +661 -0
  5. data/lib/solace/address_lookup_table.rb +50 -0
  6. data/lib/solace/concerns/binary_serializable.rb +30 -0
  7. data/lib/solace/connection.rb +187 -0
  8. data/lib/solace/constants.rb +52 -0
  9. data/lib/solace/instruction.rb +38 -0
  10. data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +68 -0
  11. data/lib/solace/instructions/spl_token/initialize_account_instruction.rb +46 -0
  12. data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +68 -0
  13. data/lib/solace/instructions/spl_token/mint_to_instruction.rb +48 -0
  14. data/lib/solace/instructions/spl_token/transfer_instruction.rb +48 -0
  15. data/lib/solace/instructions/system_program/create_account_instruction.rb +58 -0
  16. data/lib/solace/instructions/transfer_checked_instruction.rb +58 -0
  17. data/lib/solace/instructions/transfer_instruction.rb +48 -0
  18. data/lib/solace/keypair.rb +121 -0
  19. data/lib/solace/message.rb +95 -0
  20. data/lib/solace/programs/associated_token_account.rb +96 -0
  21. data/lib/solace/programs/base.rb +22 -0
  22. data/lib/solace/programs/spl_token.rb +187 -0
  23. data/lib/solace/public_key.rb +74 -0
  24. data/lib/solace/serializable_record.rb +26 -0
  25. data/lib/solace/serializers/address_lookup_table_deserializer.rb +62 -0
  26. data/lib/solace/serializers/address_lookup_table_serializer.rb +54 -0
  27. data/lib/solace/serializers/base.rb +31 -0
  28. data/lib/solace/serializers/base_deserializer.rb +56 -0
  29. data/lib/solace/serializers/base_serializer.rb +52 -0
  30. data/lib/solace/serializers/instruction_deserializer.rb +62 -0
  31. data/lib/solace/serializers/instruction_serializer.rb +54 -0
  32. data/lib/solace/serializers/message_deserializer.rb +116 -0
  33. data/lib/solace/serializers/message_serializer.rb +95 -0
  34. data/lib/solace/serializers/transaction_deserializer.rb +49 -0
  35. data/lib/solace/serializers/transaction_serializer.rb +60 -0
  36. data/lib/solace/transaction.rb +98 -0
  37. data/lib/solace/utils/codecs.rb +220 -0
  38. data/lib/solace/utils/curve25519_dalek.rb +59 -0
  39. data/lib/solace/utils/libcurve25519_dalek-linux/libcurve25519_dalek.so +0 -0
  40. data/lib/solace/utils/libcurve25519_dalek-macos/libcurve25519_dalek.dylib +0 -0
  41. data/lib/solace/utils/libcurve25519_dalek-windows/curve25519_dalek.dll +0 -0
  42. data/lib/solace/utils/pda.rb +100 -0
  43. data/lib/solace/version.rb +5 -0
  44. data/lib/solace.rb +39 -0
  45. 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