solace 0.0.2 → 0.0.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +57 -0
  3. data/LICENSE +21 -0
  4. data/README.md +142 -287
  5. data/lib/solace/address_lookup_table.rb +34 -18
  6. data/lib/solace/composers/base.rb +45 -0
  7. data/lib/solace/composers/spl_token_program_transfer_checked_composer.rb +113 -0
  8. data/lib/solace/composers/system_program_transfer_composer.rb +80 -0
  9. data/lib/solace/concerns/binary_serializable.rb +39 -0
  10. data/lib/solace/connection.rb +101 -44
  11. data/lib/solace/constants.rb +7 -14
  12. data/lib/solace/instruction.rb +30 -19
  13. data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +18 -3
  14. data/lib/solace/instructions/spl_token/initialize_account_instruction.rb +24 -3
  15. data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +18 -1
  16. data/lib/solace/instructions/spl_token/mint_to_instruction.rb +16 -3
  17. data/lib/solace/instructions/spl_token/transfer_checked_instruction.rb +76 -0
  18. data/lib/solace/instructions/spl_token/transfer_instruction.rb +15 -2
  19. data/lib/solace/instructions/system_program/create_account_instruction.rb +18 -3
  20. data/lib/solace/instructions/system_program/transfer_instruction.rb +58 -0
  21. data/lib/solace/keypair.rb +64 -31
  22. data/lib/solace/message.rb +22 -10
  23. data/lib/solace/programs/associated_token_account.rb +58 -11
  24. data/lib/solace/programs/base.rb +6 -0
  25. data/lib/solace/programs/spl_token.rb +52 -14
  26. data/lib/solace/public_key.rb +45 -20
  27. data/lib/solace/serializers/address_lookup_table_deserializer.rb +3 -5
  28. data/lib/solace/serializers/address_lookup_table_serializer.rb +7 -7
  29. data/lib/solace/serializers/base_deserializer.rb +29 -19
  30. data/lib/solace/serializers/base_serializer.rb +18 -9
  31. data/lib/solace/serializers/instruction_deserializer.rb +5 -7
  32. data/lib/solace/serializers/instruction_serializer.rb +4 -6
  33. data/lib/solace/serializers/message_deserializer.rb +3 -5
  34. data/lib/solace/serializers/message_serializer.rb +3 -5
  35. data/lib/solace/serializers/transaction_deserializer.rb +5 -7
  36. data/lib/solace/serializers/transaction_serializer.rb +5 -7
  37. data/lib/solace/transaction.rb +38 -23
  38. data/lib/solace/transaction_composer.rb +115 -0
  39. data/lib/solace/utils/account_context.rb +252 -0
  40. data/lib/solace/utils/codecs.rb +56 -128
  41. data/lib/solace/utils/curve25519_dalek.rb +9 -4
  42. data/lib/solace/utils/pda.rb +22 -24
  43. data/lib/solace/version.rb +2 -1
  44. data/lib/solace.rb +9 -7
  45. metadata +15 -12
  46. data/lib/solace/instructions/transfer_checked_instruction.rb +0 -58
  47. data/lib/solace/instructions/transfer_instruction.rb +0 -48
  48. data/lib/solace/serializable_record.rb +0 -26
  49. data/lib/solace/serializers/base.rb +0 -31
@@ -1,11 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/solace/instructions/spl_token/initialize_account_instruction.rb
2
4
 
3
5
  module Solace
4
6
  module Instructions
5
7
  module SplToken
6
- # A class to build the InitializeAccount instruction for the SPL Token Program.
8
+ # Instruction for initializing a new token account.
9
+ #
10
+ # This instruction is used to initialize a new token account for a given mint and owner. It
11
+ # is used in conjunction with the CreateAccount instruction to create and initialize a new
12
+ # token account. Note that the AssociatedTokenAccount::CreateAssociatedTokenAccountInstruction
13
+ # is a special "all-in-one" instruction that creates and initializes the account in a single
14
+ # instruction.
15
+ #
16
+ # @example Build an InitializeAccount instruction
17
+ # instruction = Solace::Instructions::SplToken::InitializeAccountInstruction.build(
18
+ # account_index: 0,
19
+ # mint_index: 1,
20
+ # owner_index: 2,
21
+ # rent_sysvar_index: 3,
22
+ # program_index: 4
23
+ # )
24
+ #
25
+ # @see Solace::Instructions::AssociatedTokenAccount::CreateAssociatedTokenAccountInstruction
26
+ # @see Solace::Instructions::SystemProgram::CreateAccountInstruction
27
+ # @since 0.0.2
7
28
  class InitializeAccountInstruction
8
- # @!const [Array<Integer>] INSTRUCTION_INDEX
29
+ # @!attribute [Array<Integer>] INSTRUCTION_INDEX
9
30
  # Instruction index for SPL Token Program's InitializeAccount instruction.
10
31
  INSTRUCTION_INDEX = [1].freeze
11
32
 
@@ -43,4 +64,4 @@ module Solace
43
64
  end
44
65
  end
45
66
  end
46
- end
67
+ end
@@ -3,6 +3,23 @@
3
3
  module Solace
4
4
  module Instructions
5
5
  module SplToken
6
+ # Instruction for initializing a new mint.
7
+ #
8
+ # This instruction is used to initialize a new mint for a given token. It is used in conjunction with the SystemProgram::CreateAccount
9
+ # instruction to create and initialize a new mint account.
10
+ #
11
+ # @example Build an InitializeMint instruction
12
+ # instruction = Solace::Instructions::SplToken::InitializeMintInstruction.build(
13
+ # decimals: 6,
14
+ # mint_authority: mint_authority.address,
15
+ # freeze_authority: freeze_authority.address,
16
+ # rent_sysvar_index: 2,
17
+ # mint_account_index: 1,
18
+ # program_index: 3
19
+ # )
20
+ #
21
+ # @see Solace::Instructions::SystemProgram::CreateAccountInstruction
22
+ # @since 0.0.2
6
23
  class InitializeMintInstruction
7
24
  # Instruction index for Initialize Mint
8
25
  INSTRUCTION_INDEX = [0].freeze
@@ -49,7 +66,7 @@ module Solace
49
66
  # @param decimals [Integer] Number of decimals for the token
50
67
  # @param mint_authority [String] Public key of the mint authority
51
68
  # @param freeze_authority [String, nil] Public key of the freeze authority
52
- # @return [Array] 1-byte instruction index + 1-byte decimals + 32-byte mint authority + 1-byte freeze authority option + 32-byte freeze authority
69
+ # @return [Array<u8>] The instruction data
53
70
  def self.data(decimals, mint_authority, freeze_authority)
54
71
  INSTRUCTION_INDEX +
55
72
  [decimals] +
@@ -3,9 +3,22 @@
3
3
  module Solace
4
4
  module Instructions
5
5
  module SplToken
6
- # A class to build the MintTo instruction for the SPL Token Program.
6
+ # Instruction for minting tokens to a token account.
7
+ #
8
+ # This instruction is used to mint tokens to a token account for a given mint and owner.
9
+ #
10
+ # @example Build a MintTo instruction
11
+ # instruction = Solace::Instructions::SplToken::MintToInstruction.build(
12
+ # amount: 100,
13
+ # mint_index: 1,
14
+ # mint_authority_index: 2,
15
+ # destination_index: 3,
16
+ # program_index: 4
17
+ # )
18
+ #
19
+ # @since 0.0.2
7
20
  class MintToInstruction
8
- # @!const [Array<Integer>] INSTRUCTION_INDEX
21
+ # @!attribute [Array<Integer>] INSTRUCTION_INDEX
9
22
  # Instruction index for SPL Token Program's MintTo instruction.
10
23
  INSTRUCTION_INDEX = [7].freeze
11
24
 
@@ -45,4 +58,4 @@ module Solace
45
58
  end
46
59
  end
47
60
  end
48
- end
61
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Instructions
5
+ module SplToken
6
+ # Instruction for transferring SPL tokens.
7
+ #
8
+ # This instruction is used to transfer SPL tokens from one token account to another while checking the decimals
9
+ # of the token to ensure the transfer amount is correct.
10
+ #
11
+ # @example Build a TransferChecked instruction
12
+ # instruction = Solace::Instructions::SplToken::TransferCheckedInstruction.build(
13
+ # amount: 100,
14
+ # decimals: 6,
15
+ # to_index: 1,
16
+ # from_index: 2,
17
+ # mint_index: 3,
18
+ # authority_index: 4,
19
+ # program_index: 5
20
+ # )
21
+ #
22
+ # @since 0.0.2
23
+ class TransferCheckedInstruction
24
+ # SPL Token Program instruction index for Transfer Checked
25
+ INSTRUCTION_INDEX = [12].freeze
26
+
27
+ # Builds a Solace::Instruction for transferring SPL tokens
28
+ #
29
+ # SPL Token Program transfer instruction layout:
30
+ # - 1 byte: instruction index (12 for transfer checked)
31
+ # - 8 bytes: amount (u64, little-endian)
32
+ # - 8 bytes: decimals (u64, little-endian)
33
+ #
34
+ # @param amount [Integer] Amount to transfer (in tokens, according to mint's decimals)
35
+ # @param decimals [Integer] Number of decimals for the token
36
+ # @param to_index [Integer] Index of the destination token account in the transaction's accounts
37
+ # @param from_index [Integer] Index of the source token account in the transaction's accounts
38
+ # @param mint_index [Integer] Index of the mint in the transaction's accounts
39
+ # @param authority_index [Integer] Index of the authority (owner) in the transaction's accounts
40
+ # @param program_index [Integer] Index of the SPL Token Program in the transaction's accounts (default: 3)
41
+ # @return [Solace::Instruction]
42
+ def self.build(
43
+ amount:,
44
+ decimals:,
45
+ to_index:,
46
+ from_index:,
47
+ mint_index:,
48
+ authority_index:,
49
+ program_index: 3
50
+ )
51
+ Solace::Instruction.new.tap do |ix|
52
+ ix.program_index = program_index
53
+ ix.accounts = [from_index, mint_index, to_index, authority_index]
54
+ ix.data = data(amount, decimals)
55
+ end
56
+ end
57
+
58
+ # Instruction data for a token transfer instruction
59
+ #
60
+ # The BufferLayout is:
61
+ # - [Instruction Index (1 byte)]
62
+ # - [Amount (8 bytes little-endian u64)]
63
+ # - [Decimals (8 bytes little-endian u64)]
64
+ #
65
+ # @param amount [Integer] Amount to transfer
66
+ # @param decimals [Integer] Number of decimals for the token
67
+ # @return [Array] 1-byte instruction index + 8-byte amount + decimals
68
+ def self.data(amount, decimals)
69
+ INSTRUCTION_INDEX +
70
+ Solace::Utils::Codecs.encode_le_u64(amount).bytes +
71
+ [decimals]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -3,9 +3,22 @@
3
3
  module Solace
4
4
  module Instructions
5
5
  module SplToken
6
- # A class to build the Transfer instruction for the SPL Token Program.
6
+ # Instruction for transferring SPL tokens.
7
+ #
8
+ # This instruction is used to transfer SPL tokens from one token account to another.
9
+ #
10
+ # @example Build a Transfer instruction
11
+ # instruction = Solace::Instructions::SplToken::TransferInstruction.build(
12
+ # amount: 100,
13
+ # owner_index: 1,
14
+ # source_index: 2,
15
+ # destination_index: 3,
16
+ # program_index: 4
17
+ # )
18
+ #
19
+ # @since 0.0.2
7
20
  class TransferInstruction
8
- # @!const [Array<Integer>] INSTRUCTION_INDEX
21
+ # @!attribute [Array<Integer>] INSTRUCTION_INDEX
9
22
  # Instruction index for SPL Token Program's Transfer instruction.
10
23
  INSTRUCTION_INDEX = [3].freeze
11
24
 
@@ -3,11 +3,25 @@
3
3
  module Solace
4
4
  module Instructions
5
5
  module SystemProgram
6
+ # Instruction for creating a new account.
7
+ #
8
+ # This instruction is used to create a new account for a given program.
9
+ #
10
+ # @example Build a CreateAccount instruction
11
+ # instruction = Solace::Instructions::SystemProgram::CreateAccountInstruction.build(
12
+ # space: 1024,
13
+ # lamports: 1000,
14
+ # from_index: 0,
15
+ # new_account_index: 1,
16
+ # owner: owner.address,
17
+ # system_program_index: 2
18
+ # )
19
+ #
20
+ # @since 0.0.2
6
21
  class CreateAccountInstruction
7
- # !@const INSTRUCTION_INDEX
22
+ # @!attribute [Array<Integer>] INSTRUCTION_INDEX
8
23
  # Instruction index for SystemProgram::CreateAccount
9
24
  # This is the same across all Solana clusters
10
- # @return [Array<Integer>]
11
25
  INSTRUCTION_INDEX = [0, 0, 0, 0].freeze
12
26
 
13
27
  # Builds a SystemProgram::CreateAccount instruction
@@ -22,9 +36,9 @@ module Solace
22
36
  def self.build(
23
37
  space:,
24
38
  lamports:,
25
- owner: Solace::Constants::SYSTEM_PROGRAM_ID,
26
39
  from_index:,
27
40
  new_account_index:,
41
+ owner: Solace::Constants::SYSTEM_PROGRAM_ID,
28
42
  system_program_index: 2
29
43
  )
30
44
  Solace::Instruction.new.tap do |ix|
@@ -33,6 +47,7 @@ module Solace
33
47
  ix.data = data(lamports, space, owner)
34
48
  end
35
49
  end
50
+ # rubocop:enable Metrics/ParameterLists
36
51
 
37
52
  # Builds the data for a SystemProgram::CreateAccount instruction
38
53
  #
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solace
4
+ module Instructions
5
+ module SystemProgram
6
+ # Instruction for transferring SOL.
7
+ #
8
+ # This instruction is used to transfer SOL from one account to another.
9
+ #
10
+ # @example Build a Transfer instruction
11
+ # instruction = Solace::Instructions::SystemProgram::TransferInstruction.build(
12
+ # lamports: 100,
13
+ # to_index: 1,
14
+ # from_index: 2,
15
+ # program_index: 3
16
+ # )
17
+ #
18
+ # @since 0.0.2
19
+ class TransferInstruction
20
+ # Instruction ID for System Transfer
21
+ INSTRUCTION_ID = [2, 0, 0, 0].freeze
22
+
23
+ # Builds a Solace::Instruction for transferring SOL
24
+ #
25
+ # @param lamports [Integer] Amount to transfer (in lamports)
26
+ # @param to_index [Integer] Index of the recipient in the transaction's accounts
27
+ # @param from_index [Integer] Index of the sender in the transaction's accounts
28
+ # @param program_index [Integer] Index of the program in the transaction's accounts (default: 2)
29
+ # @return [Solace::Instruction]
30
+ def self.build(
31
+ lamports:,
32
+ to_index:,
33
+ from_index:,
34
+ program_index: 2
35
+ )
36
+ Instruction.new.tap do |ix|
37
+ ix.program_index = program_index
38
+ ix.accounts = [from_index, to_index]
39
+ ix.data = data(lamports)
40
+ end
41
+ end
42
+
43
+ # Instruction data for a transfer instruction
44
+ #
45
+ # The BufferLayout is:
46
+ # - [Instruction ID (4 bytes)]
47
+ # - [Amount (8 bytes little-endian u64)]
48
+ #
49
+ # @param lamports [Integer] Amount to transfer (in lamports)
50
+ # @return [Array] 4-byte instruction ID + 8-byte amount
51
+ def self.data(lamports)
52
+ INSTRUCTION_ID +
53
+ Utils::Codecs.encode_le_u64(lamports).bytes
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -3,38 +3,40 @@
3
3
  require 'rbnacl'
4
4
  require 'base58'
5
5
 
6
- # =============================
7
- # Keypair
8
- # =============================
9
- #
10
- # Represents a Solana Ed25519 Keypair
11
6
  module Solace
7
+ # Class representing a Solana Ed25519 Keypair
8
+ #
9
+ # This class provides utility methods for encoding, decoding, signing, and validating keypairs.
10
+ #
11
+ # @example
12
+ # # Generate a new keypair
13
+ # keypair = Solace::Keypair.generate
14
+ #
15
+ # # Get the address of the pubkey
16
+ # keypair.address
17
+ #
18
+ # # Sign a message using the keypair
19
+ # keypair.sign("<any-message>")
20
+ #
21
+ # @since 0.0.1
12
22
  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
23
+ # The length of a Solana secret key in bytes.
17
24
  SECRET_LENGTH = 64
18
25
 
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
26
+ # The length of a Solana seed in bytes.
27
+ SEED_LENGTH = 32
24
28
 
25
- # !@const SigningKey
26
- # The RbNaCl signing key
29
+ # The full keypair bytes array
27
30
  #
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
31
+ # @return [Array<u8>] The 64 bytes of the keypair
33
32
  attr_reader :keypair_bytes
34
33
 
35
34
  class << self
36
35
  # Generate a new random keypair
37
36
  #
37
+ # @example
38
+ # keypair = Solace::Keypair.generate
39
+ #
38
40
  # @return [Keypair]
39
41
  def generate
40
42
  from_seed(RbNaCl::Random.random_bytes(SEED_LENGTH))
@@ -42,28 +44,40 @@ module Solace
42
44
 
43
45
  # Create a keypair from a 32-byte seed
44
46
  #
47
+ # @example
48
+ # keypair = Solace::Keypair.from_seed(seed)
49
+ #
45
50
  # @param seed [String] 32-byte array
51
+ # @raise [ArgumentError] If the length of the seed isn't 32 bytes
46
52
  # @return [Keypair]
47
53
  def from_seed(seed)
48
54
  raise ArgumentError, 'Seed must be 32 bytes' unless seed.length == SEED_LENGTH
49
55
 
50
- new(SigningKey.new(seed).keypair_bytes.bytes)
56
+ new(RbNaCl::Signatures::Ed25519::SigningKey.new(seed).keypair_bytes.bytes)
51
57
  end
52
58
 
53
59
  # Create a keypair from a 64-byte secret key
54
60
  #
61
+ # @example
62
+ # keypair = Solace::Keypair.from_secret_key(secret_key)
63
+ #
55
64
  # @param secret_key [String] 64-byte array
65
+ # @raise [ArgumentError] If the length of the secret_key isn't 64 bytes
56
66
  # @return [Keypair]
57
67
  def from_secret_key(secret_key)
58
68
  raise ArgumentError, 'Secret key must be 64 bytes' unless secret_key.length == SECRET_LENGTH
59
69
 
60
- new(SigningKey.new(secret_key[0..31]).keypair_bytes.bytes)
70
+ new(RbNaCl::Signatures::Ed25519::SigningKey.new(secret_key[0..31]).keypair_bytes.bytes)
61
71
  end
62
72
  end
63
73
 
64
74
  # Initialize a new keypair
65
75
  #
76
+ # @example
77
+ # keypair = Solace::Keypair.new(bytes)
78
+ #
66
79
  # @param keypair_bytes [Array<Integer>] The keypair bytes
80
+ # @raise [ArgumentError] If the length of the keypair_bytes isn't 64 bytes
67
81
  # @return [Keypair] The new keypair object
68
82
  def initialize(keypair_bytes)
69
83
  raise ArgumentError, 'Keypair must be 64 bytes' unless keypair_bytes.length == SECRET_LENGTH
@@ -73,6 +87,9 @@ module Solace
73
87
 
74
88
  # Returns the public key
75
89
  #
90
+ # @example
91
+ # pubkey = keypair.public_key
92
+ #
76
93
  # @return [PublicKey]
77
94
  def public_key
78
95
  @public_key ||= Solace::PublicKey.new(public_key_bytes)
@@ -80,16 +97,22 @@ module Solace
80
97
 
81
98
  # Returns the signing key
82
99
  #
100
+ # @example
101
+ # signing_key = instance.signing_key
102
+ #
83
103
  # @return [RbNaCl::Signatures::Ed25519::SigningKey]
84
104
  def signing_key
85
- @signing_key ||= SigningKey.new(private_key)
105
+ @signing_key ||= RbNaCl::Signatures::Ed25519::SigningKey.new(private_key_bytes.pack('C*'))
86
106
  end
87
107
 
88
108
  # Returns the public key bytes
89
109
  #
90
110
  # The public key bytes are the last 32 bytes of the keypair
91
111
  #
92
- # @return [String] 32 bytes
112
+ # @example
113
+ # public_key_bytes = instance.public_key_bytes
114
+ #
115
+ # @return [Array<u8>] 32 bytes
93
116
  def public_key_bytes
94
117
  keypair_bytes[32..63]
95
118
  end
@@ -98,12 +121,18 @@ module Solace
98
121
  #
99
122
  # The private key is the first 32 bytes of the keypair
100
123
  #
101
- # @return [String] 32 characters
102
- def private_key
103
- keypair_bytes[0..31].pack('C*')
124
+ # @example
125
+ # private_key_bytes = instance.private_key_bytes
126
+ #
127
+ # @return [Array<u8>] 32 characters
128
+ def private_key_bytes
129
+ keypair_bytes[0..31]
104
130
  end
105
131
 
106
- # Returns the public key as a Base58 string
132
+ # Returns the public key address as a Base58 string
133
+ #
134
+ # @example
135
+ # pubkey_str = instance.address
107
136
  #
108
137
  # @return [String] Base58 encoded public key
109
138
  def address
@@ -112,8 +141,12 @@ module Solace
112
141
 
113
142
  # Signs a message (string or binary)
114
143
  #
115
- # @param message [String | Binary]
116
- # @return [Binary] signature (binary)
144
+ # @example
145
+ # message = "An important message to be signed,"
146
+ # signature = instance.sign(message)
147
+ #
148
+ # @param message [String, Binary]
149
+ # @return [String] signature (binary string)
117
150
  def sign(message)
118
151
  signing_key.sign(message)
119
152
  end
@@ -1,18 +1,28 @@
1
1
  # frozen_string_literal: true
2
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
3
  module Solace
10
- class Message < Solace::SerializableRecord
11
- # @!const SERIALIZER
4
+ # Solace::Message represents the message portion of a Solana transaction (legacy or versioned). It handles
5
+ # serialization and deserialization of message fields.
6
+ #
7
+ # @example
8
+ # message = Solace::Message.new(
9
+ # version: 0,
10
+ # header: [0, 0, 0],
11
+ # accounts: ['11111111111111111111111111111111'],
12
+ # recent_blockhash: '11111111111111111111111111111111',
13
+ # instructions: [],
14
+ # address_lookup_tables: []
15
+ # )
16
+ #
17
+ # @since 0.0.1
18
+ class Message
19
+ include Solace::Concerns::BinarySerializable
20
+
21
+ # @!attribute SERIALIZER
12
22
  # @return [Solace::Serializers::MessageSerializer] The serializer for the message
13
23
  SERIALIZER = Solace::Serializers::MessageSerializer
14
24
 
15
- # @!const DESERIALIZER
25
+ # @!attribute DESERIALIZER
16
26
  # @return [Solace::Serializers::MessageDeserializer] The deserializer for the message
17
27
  DESERIALIZER = Solace::Serializers::MessageDeserializer
18
28
 
@@ -46,7 +56,7 @@ module Solace
46
56
  # @param accounts [Array<String>] Account public keys (base58)
47
57
  # @param instructions [Array<Solace::Instruction>] Instructions in the message
48
58
  # @param recent_blockhash [String] Recent blockhash (base58)
49
- # @param header [Array<Integer>] Message header [num_required_signatures, num_readonly_signed, num_readonly_unsigned]
59
+ # @param header [Array<Integer>] Message header
50
60
  # @param address_lookup_tables [Array<Solace::AddressLookupTable>]
51
61
  def initialize(
52
62
  version: nil,
@@ -56,6 +66,8 @@ module Solace
56
66
  header: [0, 0, 0],
57
67
  address_lookup_tables: []
58
68
  )
69
+ super()
70
+
59
71
  @version = version
60
72
  @header = header
61
73
  @accounts = accounts
@@ -2,13 +2,33 @@
2
2
 
3
3
  module Solace
4
4
  module Programs
5
- # A client for interacting with the SPL Token Program.
5
+ # Client for interacting with the Associated Token Account Program.
6
+ #
7
+ # This client provides methods for interacting with the Associated Token Account Program. It is a
8
+ # wrapper around the SPL Token Program and provides a more convenient interface for creating and
9
+ # managing associated token accounts.
10
+ #
11
+ # @example Create an associated token account
12
+ # # Initialize the program with a connection
13
+ # program = Solace::Programs::AssociatedTokenAccount.new(connection: connection)
14
+ #
15
+ # # Create an associated token account
16
+ # result = program.create_associated_token_account(
17
+ # payer: payer,
18
+ # owner: owner,
19
+ # mint: mint
20
+ # )
21
+ #
22
+ # # Wait for the transaction to be finalized
23
+ # @connection.wait_for_confirmed_signature('finalized') { result['result'] }
24
+ #
25
+ # @since 0.0.2
6
26
  class AssociatedTokenAccount < Base
7
27
  class << self
8
28
  # Gets the address of an associated token account.
9
29
  #
10
- # @param owner [Solace::Keypair || Solace::PublicKey] The keypair of the owner.
11
- # @param mint [Solace::Keypair || Solace::PublicKey] The keypair of the mint.
30
+ # @param owner [Solace::Keypair, Solace::PublicKey] The keypair of the owner.
31
+ # @param mint [Solace::Keypair, Solace::PublicKey] The keypair of the mint.
12
32
  # @return [String] The address of the associated token account.
13
33
  def get_address(owner:, mint:)
14
34
  Solace::Utils::PDA.find_program_address(
@@ -26,16 +46,40 @@ module Solace
26
46
  #
27
47
  # @param connection [Solace::Connection] The connection to the Solana cluster.
28
48
  def initialize(connection:)
29
- super(connection:, program_id: Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID)
49
+ super(connection: connection, program_id: Solace::Constants::ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID)
30
50
  end
31
51
 
32
52
  # Alias method for get_address
33
- #
34
- # @param oprtions [Hash] A hash of options for the get_address class method
53
+ #
54
+ # @option options [Hash] A hash of options for the get_address class method
55
+ # @return [Array<String, Integer>] The address of the associated token account and the bump seed
35
56
  def get_address(**options)
36
57
  self.class.get_address(**options)
37
58
  end
38
59
 
60
+ # Gets the address of an associated token account, creating it if it doesn't exist.
61
+ #
62
+ # @param payer [Solace::Keypair] The keypair that will pay for fees and rent.
63
+ # @param owner [Solace::Keypair, Solace::PublicKey] The keypair of the owner.
64
+ # @param mint [Solace::Keypair, Solace::PublicKey] The keypair of the mint.
65
+ # @param commitment [String] The commitment level for the get_account_info call.
66
+ # @return [String] The address of the associated token account
67
+ def get_or_create_address(payer:, owner:, mint:, commitment: 'confirmed')
68
+ ata_address, _bump = get_address(owner: owner, mint: mint)
69
+
70
+ account_info = @connection.get_account_info(ata_address)
71
+
72
+ return ata_address if account_info
73
+
74
+ response = create_associated_token_account(payer: payer, owner: owner, mint: mint)
75
+
76
+ raise 'Failed to create associated token account' unless response['result']
77
+
78
+ @connection.wait_for_confirmed_signature(commitment) { response['result'] }
79
+
80
+ ata_address
81
+ end
82
+
39
83
  # Creates a new associated token account.
40
84
  #
41
85
  # @param options [Hash] Options for calling the prepare_create_associated_token_account method.
@@ -48,16 +92,18 @@ module Solace
48
92
 
49
93
  # Prepares a new associated token account and returns the signed transaction.
50
94
  #
51
- # @param owner [Solace::Keypair || Solace::PublicKey] The keypair of the owner.
52
- # @param mint [Solace::Keypair || Solace::PublicKey] The keypair of the mint.
95
+ # @param owner [Solace::Keypair, Solace::PublicKey] The keypair of the owner.
96
+ # @param mint [Solace::Keypair, Solace::PublicKey] The keypair of the mint.
53
97
  # @param payer [Solace::Keypair] The keypair that will pay for fees and rent.
54
98
  # @return [Solace::Transaction] The signed transaction.
99
+ #
100
+ # rubocop:disable Metrics/MethodLength
55
101
  def prepare_create_associated_token_account(
102
+ payer:,
56
103
  owner:,
57
- mint:,
58
- payer:
104
+ mint:
59
105
  )
60
- ata_address, _ = get_address(owner:, mint:)
106
+ ata_address, = get_address(owner: owner, mint: mint)
61
107
 
62
108
  accounts = [
63
109
  payer.address,
@@ -91,6 +137,7 @@ module Solace
91
137
 
92
138
  tx
93
139
  end
140
+ # rubocop:enable Metrics/MethodLength
94
141
  end
95
142
  end
96
143
  end
@@ -5,7 +5,13 @@
5
5
  module Solace
6
6
  module Programs
7
7
  # Base class for program-specific clients.
8
+ #
8
9
  # Provides a consistent interface for interacting with on-chain programs.
10
+ #
11
+ # @abstract
12
+ # @see Solace::Programs::SplToken
13
+ # @see Solace::Programs::AssociatedTokenAccount
14
+ # @since 0.0.2
9
15
  class Base
10
16
  attr_reader :connection, :program_id
11
17