solace 0.0.10 → 0.1.1

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +124 -24
  3. data/README.md +10 -8
  4. data/lib/solace/composers/base.rb +35 -0
  5. data/lib/solace/composers/spl_token_program_initialize_mint_composer.rb +95 -0
  6. data/lib/solace/composers/spl_token_program_mint_to_composer.rb +86 -0
  7. data/lib/solace/composers/spl_token_program_transfer_composer.rb +90 -0
  8. data/lib/solace/composers/system_program_create_account_composer.rb +98 -0
  9. data/lib/solace/connection.rb +92 -15
  10. data/lib/solace/errors/confirmation_timeout.rb +18 -4
  11. data/lib/solace/errors/http_error.rb +16 -1
  12. data/lib/solace/errors/parse_error.rb +15 -1
  13. data/lib/solace/errors/rpc_error.rb +17 -1
  14. data/lib/solace/errors.rb +8 -3
  15. data/lib/solace/instructions/associated_token_account/create_associated_token_account_instruction.rb +9 -2
  16. data/lib/solace/instructions/spl_token/initialize_mint_instruction.rb +0 -1
  17. data/lib/solace/instructions/spl_token/transfer_instruction.rb +21 -0
  18. data/lib/solace/instructions/system_program/create_account_instruction.rb +30 -0
  19. data/lib/solace/instructions/system_program/transfer_instruction.rb +11 -0
  20. data/lib/solace/programs/associated_token_account.rb +57 -30
  21. data/lib/solace/programs/base.rb +23 -0
  22. data/lib/solace/programs/spl_token.rb +197 -125
  23. data/lib/solace/serializers/base_serializer.rb +29 -1
  24. data/lib/solace/tokens/token.rb +53 -0
  25. data/lib/solace/tokens.rb +86 -0
  26. data/lib/solace/transaction.rb +24 -21
  27. data/lib/solace/transaction_composer.rb +77 -3
  28. data/lib/solace/utils/account_context.rb +1 -1
  29. data/lib/solace/utils/codecs.rb +17 -0
  30. data/lib/solace/utils/pda.rb +13 -5
  31. data/lib/solace/version.rb +3 -2
  32. data/lib/solace.rb +38 -11
  33. metadata +21 -1
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Solace
6
+ # Represents a Solana token with its metadata.
7
+ #
8
+ # A tokens object encapsulates the symbol and associated metadata for a Solana token. It provides
9
+ # dynamic access to metadata attributes via method calls. This class is used within the Solace::Tokens
10
+ # module to represent individual tokens loaded from a YAML configuration file.
11
+ #
12
+ # @example Load tokens for the 'mainnet' network
13
+ # Solace::Tokens.load(path: 'path/to/tokens.yml', network: 'mainnet')
14
+ #
15
+ # @example Access a specific token
16
+ # usdc = Solace::Tokens.fetch('USDC')
17
+ # puts usdc.address
18
+ #
19
+ # @example Query tokens by criteria
20
+ # stablecoins = Solace::Tokens.where(type: 'stablecoin')
21
+ #
22
+ # @since 0.1.0
23
+ module Tokens
24
+ autoload :Token, File.expand_path('tokens/token', __dir__)
25
+
26
+ # Load tokens from a YAML file for a specific network
27
+ #
28
+ # @param path [String] Path to the YAML file
29
+ # @param network [String, Symbol] Network name (e.g., 'mainnet', 'testnet')
30
+ # @return [void]
31
+ def self.load(path:, network:)
32
+ data = YAML.load_file(path)
33
+ tokens = data.fetch(network.to_s) do
34
+ raise ArgumentError, "Network '#{network}' not found in config"
35
+ end
36
+
37
+ # Clear previous constants and registry
38
+ clear!
39
+
40
+ @registry = {}
41
+
42
+ tokens.each do |symbol, attrs|
43
+ token = Solace::Tokens::Token.new(symbol, attrs)
44
+ const_set(symbol, token)
45
+ @registry[symbol.to_s] = token
46
+ end
47
+ end
48
+
49
+ # Clear loaded tokens
50
+ #
51
+ # @return [void]
52
+ def self.clear!
53
+ (constants - [:Token]).each { |c| remove_const(c) }
54
+ @registry = {}
55
+ end
56
+
57
+ # Get all loaded tokens
58
+ #
59
+ # @return [Array<Solace::Tokens::Token>]
60
+ def self.all
61
+ @registry.values
62
+ end
63
+
64
+ # Fetch a token by its symbol
65
+ #
66
+ # @param symbol [String] The symbol of the token
67
+ # @return [Solace::Tokens::Token, nil] The token object or nil if not found
68
+ def self.fetch(symbol)
69
+ @registry[symbol.to_s]
70
+ end
71
+
72
+ # Query tokens based on criteria
73
+ #
74
+ # @param criteria [Hash{Symbol => Object}] Key-value pairs to filter tokens
75
+ # @return [Array<Solace::Tokens::Token>] Array of tokens matching the criteria
76
+ def self.where(criteria = {})
77
+ return all if criteria.empty?
78
+
79
+ normalized = criteria.transform_keys(&:to_sym)
80
+
81
+ all.select do |token|
82
+ normalized.all? { |k, v| token.metadata[k] == v }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -6,17 +6,17 @@ module Solace
6
6
  #
7
7
  # Transactions are the basic building blocks of Solana. They contain a message and an array of signatures. The
8
8
  # message contains the instructions to be executed and the accounts that are used by the instructions. The signatures
9
- # are the signatures of the accounts that are used by the instructions. This class provides methods for signing,
10
- # serializing, and deserializing transactions.
9
+ # are the required signatures of the accounts that are used by the instructions. This class provides methods for
10
+ # signing, serializing, and deserializing transactions.
11
11
  #
12
12
  # The BufferLayout is:
13
- # - [Signatures (variable length)]
14
- # - [Version (1 byte)] (if versioned)
15
- # - [Message header (3 bytes)]
16
- # - [Account keys (variable length)]
17
- # - [Recent blockhash (32 bytes)]
18
- # - [Instructions (variable length)]
19
- # - [Address lookup table (variable length)] (if versioned)
13
+ # - Signatures (variable length)
14
+ # - Version (1 byte if versioned)
15
+ # - Message header (3 bytes)
16
+ # - Account keys (variable length)
17
+ # - Recent blockhash (32 bytes)
18
+ # - Instructions (variable length)
19
+ # - Address lookup table (variable length if versioned)
20
20
  #
21
21
  # @example
22
22
  # # Create a new transaction
@@ -32,31 +32,26 @@ module Solace
32
32
  class Transaction
33
33
  include Solace::Concerns::BinarySerializable
34
34
 
35
- # @!attribute SERIALIZER
36
- # @return [Solace::Serializers::TransactionSerializer] The serializer for the transaction
35
+ # The serializer for the transaction
37
36
  SERIALIZER = Solace::Serializers::TransactionSerializer
38
37
 
39
- # @!attribute DESERIALIZER
40
- # @return [Solace::Serializers::TransactionDeserializer] The deserializer for the transaction
38
+ # The deserializer for the transaction
41
39
  DESERIALIZER = Solace::Serializers::TransactionDeserializer
42
40
 
43
- # @!attribute SIGNATURE_PLACEHOLDER
44
- # @return [String] Placeholder for a signature in the transaction
41
+ # Placeholder for a signature in the transaction
45
42
  SIGNATURE_PLACEHOLDER = Solace::Utils::Codecs.base58_to_binary('1' * 64)
46
43
 
47
- # @!attribute [rw] signatures
48
- # @return [Array<String>] Signatures of the transaction (binary)
44
+ # @return [Array<String>] Signatures of the transaction (binary)
49
45
  attr_accessor :signatures
50
46
 
51
- # @!attribute [rw] message
52
- # @return [Solace::Message] Message of the transaction
47
+ # @return [Solace::Message] Message of the transaction
53
48
  attr_accessor :message
54
49
 
55
50
  class << self
56
51
  # Deserialize a base64 encoded transaction into a Solace::Transaction object
57
52
  #
58
53
  # @param base64_tx [String] The base64 encoded transaction
59
- # @return [Solace::Transaction] The deserialized transaction
54
+ # @return [Transaction] The deserialized transaction
60
55
  def from(base64_tx)
61
56
  DESERIALIZER.new(Solace::Utils::Codecs.base64_to_bytestream(base64_tx)).call
62
57
  end
@@ -64,7 +59,7 @@ module Solace
64
59
 
65
60
  # Initialize a new transaction
66
61
  #
67
- # @return [Solace::Transaction] The new transaction object
62
+ # @return [Transaction] The new transaction object
68
63
  def initialize(
69
64
  signatures: [],
70
65
  message: Solace::Message.new
@@ -74,6 +69,14 @@ module Solace
74
69
  @message = message
75
70
  end
76
71
 
72
+ # Returns the first signature of the transaction (signature of the transaction fee payer)
73
+ #
74
+ # @return [String, nil] The first signature of the transaction or nil if there are no signatures
75
+ # @since 0.1.0
76
+ def signature
77
+ Utils::Codecs.binary_to_base58(signatures.first) unless signatures.empty?
78
+ end
79
+
77
80
  # Sign the transaction
78
81
  #
79
82
  # Calls sign_and_update_signatures for each keypair passed in.
@@ -2,7 +2,16 @@
2
2
 
3
3
  # lib/solace/transaction_composer.rb
4
4
  module Solace
5
- # Composes multi-instruction transactions with automatic account management
5
+ # Composes transactions with automatic account management and instruction building.
6
+ #
7
+ # This class allows you to add multiple instruction composers, manage account contexts,
8
+ # and build a complete transaction in a flexible way. It is a high-level abstraction over
9
+ # the process of creating Solana transactions, making it easier to work with complex
10
+ # transaction scenarios.
11
+ #
12
+ # For most use cases, you will create an instance of this class, add instruction composers,
13
+ # and then call the `compose_transaction` method to build the final transaction. That said,
14
+ # all of the individual pieces are also accessible for more advanced use cases.
6
15
  #
7
16
  # @example
8
17
  # # Initialize a transaction composer
@@ -38,6 +47,18 @@ module Solace
38
47
  # # Sign the transaction with all required signers
39
48
  # tx.sign(*required_signers)
40
49
  #
50
+ # @example
51
+ # # Chaining methods will return the composer itself for further modifications. The add, prepend,
52
+ # # and insert methods allow for dynamic insertion of instruction composers at required positions.
53
+ # transaction_composer = Solace::TransactionComposer
54
+ # .new(connection: connection)
55
+ # .add_instruction(instruction_composer_1)
56
+ # .prepend_instruction(instruction_composer_2)
57
+ # .insert_instruction(1, instruction_composer_3)
58
+ # .set_fee_payer(fee_payer_pubkey)
59
+ # .compose_transaction
60
+ #
61
+ # @see Solace::Composers::Base
41
62
  # @since 0.0.6
42
63
  class TransactionComposer
43
64
  # @!attribute connection
@@ -71,9 +92,62 @@ module Solace
71
92
  self
72
93
  end
73
94
 
95
+ # Prepend an instruction composer to the transaction
96
+ #
97
+ # @param composer [Composers::Base] The instruction composer
98
+ # @return [TransactionComposer] Self for chaining
99
+ #
100
+ # @since 0.1.0
101
+ def prepend_instruction(composer)
102
+ merge_accounts(composer.account_context)
103
+ instruction_composers.unshift(composer)
104
+ self
105
+ end
106
+
107
+ # Insert an instruction composer at a specific index
108
+ #
109
+ # @param index [Integer] The index to insert at
110
+ # @param composer [Composers::Base] The instruction composer
111
+ # @return [TransactionComposer] Self for chaining
112
+ #
113
+ # @since 0.1.0
114
+ def insert_instruction(index, composer)
115
+ merge_accounts(composer.account_context)
116
+ instruction_composers.insert(index, composer)
117
+ self
118
+ end
119
+
120
+ # Merge another TransactionComposer into this one
121
+ #
122
+ # @param other [TransactionComposer] The other composer to merge
123
+ # @param placement [Symbol] :add to append, :prepend to prepend
124
+ # @param index [Integer, nil] The index to insert at if placement is :insert
125
+ # @return [TransactionComposer] Self for chaining
126
+ #
127
+ # @since 0.1.0
128
+ def merge(other, placement: :add, index: nil)
129
+ merge_accounts(other.context)
130
+
131
+ case placement
132
+ when :add
133
+ # Appends the other's instruction composers to this one's list
134
+ instruction_composers.concat(other.instruction_composers)
135
+ when :insert
136
+ # Inserts the other's instruction composers at the specified index
137
+ instruction_composers.insert(index, *other.instruction_composers)
138
+ when :prepend
139
+ # Prepends the other's instruction composers to this one's list
140
+ instruction_composers.unshift(*other.instruction_composers)
141
+ else
142
+ raise ArgumentError, "Invalid placement option: #{placement}"
143
+ end
144
+
145
+ self
146
+ end
147
+
74
148
  # Set the fee payer for the transaction
75
149
  #
76
- # @param pubkey [String, Solace::PublicKey, Solace::Keypair] The fee payer pubkey
150
+ # @param pubkey [#to_s, PublicKey] The fee payer pubkey
77
151
  # @return [TransactionComposer] Self for chaining
78
152
  def set_fee_payer(pubkey)
79
153
  context.set_fee_payer(pubkey.to_s)
@@ -82,7 +156,7 @@ module Solace
82
156
 
83
157
  # Compose the final transaction
84
158
  #
85
- # @return [Solace::Transaction] The composed transaction (unsigned)
159
+ # @return [Transaction] The composed transaction (unsigned)
86
160
  def compose_transaction
87
161
  context.compile
88
162
 
@@ -199,7 +199,7 @@ module Solace
199
199
 
200
200
  # Add or merge an account into the context
201
201
  #
202
- # @param pubkey [String, Solace::PublicKey, Solace::Keypair] The public key of the account
202
+ # @param pubkey [#to_s, PublicKey] The public key of the account
203
203
  # @param signer [Boolean] Whether the account is a signer
204
204
  # @param writable [Boolean] Whether the account is writable
205
205
  # @param [Boolean] fee_payer
@@ -6,6 +6,23 @@ require 'base58'
6
6
  require 'stringio'
7
7
 
8
8
  module Solace
9
+ # The Utils module contains utility classes and helper methods used throughout
10
+ # the Solace gem.
11
+ #
12
+ # This module provides foundational utilities that support the core functionality
13
+ # of the gem, including:
14
+ # - {Solace::Utils::AccountContext} - Account management for transactions
15
+ # - {Solace::Utils::Codecs} - Encoding and decoding utilities
16
+ # - {Solace::Utils::Curve25519Dalek} - Cryptographic operations via FFI
17
+ # - {Solace::Utils::PDA} - Program Derived Address generation
18
+ # - {Solace::Utils::RPCClient} - Low-level RPC communication
19
+ #
20
+ # These utilities are primarily used internally by other parts of the gem, but
21
+ # can also be used directly for advanced use cases.
22
+ #
23
+ # @see Solace::Connection
24
+ # @see Solace::Keypair
25
+ # @since 0.0.1
9
26
  module Utils
10
27
  # Module for encoding and decoding data
11
28
  #
@@ -5,13 +5,21 @@ require 'digest'
5
5
 
6
6
  module Solace
7
7
  module Utils
8
- # Module for generating program addresses
8
+ # Raised when Program Derived Address (PDA) generation fails.
9
9
  #
10
- # This module provides methods for generating program addresses from seeds and program IDs. It interfaces
11
- # with the Curve25519 Dalek library to check if a point is on the curve. It also provides a method for
12
- # converting seeds to bytes and a method for checking if a string looks like a base58 address.
10
+ # This error is raised when attempting to derive a PDA that is invalid, typically
11
+ # because the seeds and program ID combination results in a point that lies on
12
+ # the Ed25519 curve (PDAs must be off-curve). This is a rare occurrence but can
13
+ # happen with certain seed combinations.
13
14
  #
14
- # @see Solace::Utils::Curve25519Dalek
15
+ # @example Handling invalid PDA
16
+ # begin
17
+ # pda = Solace::Utils::PDA.find_program_address(seeds, program_id)
18
+ # rescue Solace::Utils::PDA::InvalidPDAError => e
19
+ # puts "Failed to derive PDA: #{e.message}"
20
+ # end
21
+ #
22
+ # @see Solace::Utils::PDA
15
23
  # @since 0.0.1
16
24
  module PDA
17
25
  # InvalidPDAError is an error raised when an invalid PDA is generated
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Solace::VERSION is the version of the Solace gem
4
3
  module Solace
5
- VERSION = '0.0.10'
4
+ # Latest version of the Solace gem.
5
+ # @since 0.1.1
6
+ VERSION = '0.1.1'
6
7
  end
data/lib/solace.rb CHANGED
@@ -1,9 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # 🏷️ Version
3
+ # Solace is a Ruby gem for interacting with the Solana blockchain.
4
+ #
5
+ # This gem provides a comprehensive toolkit for building, signing, and sending
6
+ # transactions to Solana RPC nodes. It includes utilities for managing keypairs,
7
+ # composing complex instructions, serializing data, and interacting with
8
+ # on-chain programs like the System Program, SPL Token Program, and Associated
9
+ # Token Account Program.
10
+ #
11
+ # The gem is designed to be modular and extensible, with clear separation between
12
+ # low-level primitives (instructions, serializers) and high-level abstractions
13
+ # (composers, programs).
14
+ #
15
+ # @example Basic usage
16
+ # # Connect to a Solana RPC node
17
+ # connection = Solace::Connection.new('https://api.mainnet-beta.solana.com')
18
+ #
19
+ # # Generate a keypair
20
+ # keypair = Solace::Keypair.generate
21
+ #
22
+ # # Check balance
23
+ # balance = connection.get_balance(keypair.public_key)
24
+ #
25
+ # @see https://docs.solana.com/
26
+ # @see https://github.com/zarpay/solace
27
+ # @author Sebastian Scholl
28
+ # @since 0.0.1
29
+ module Solace; end
30
+
31
+ # Version
4
32
  require_relative 'solace/version'
5
33
 
6
- # 🛠️ Helpers
34
+ # Helpers
7
35
  require_relative 'solace/errors'
8
36
  require_relative 'solace/constants'
9
37
  require_relative 'solace/connection'
@@ -13,11 +41,14 @@ require_relative 'solace/utils/account_context'
13
41
  require_relative 'solace/utils/curve25519_dalek'
14
42
  require_relative 'solace/concerns/binary_serializable'
15
43
 
16
- # ✨ Serializers
44
+ # Tokens
45
+ require_relative 'solace/tokens'
46
+
47
+ # Serializers
17
48
  require_relative 'solace/serializers/base_serializer'
18
49
  require_relative 'solace/serializers/base_deserializer'
19
50
 
20
- # 🧬 Primitives
51
+ # Primitives
21
52
  require_relative 'solace/keypair'
22
53
  require_relative 'solace/public_key'
23
54
  require_relative 'solace/transaction'
@@ -30,16 +61,12 @@ require_relative 'solace/transaction_composer'
30
61
  require_relative 'solace/programs/base'
31
62
  require_relative 'solace/composers/base'
32
63
 
33
- # 📦 Composers
34
- #
35
- # Glob require all composers
64
+ # Composers
36
65
  Dir[File.join(__dir__, 'solace/composers', '**', '*.rb')].each { |file| require file }
37
66
 
38
- # 📦 Instructions (Builders)
39
- #
40
- # Glob require all instructions
67
+ # Instructions (Builders)
41
68
  Dir[File.join(__dir__, 'solace/instructions', '**', '*.rb')].each { |file| require file }
42
69
 
43
- # 📦 Programs
70
+ # Programs
44
71
  require_relative 'solace/programs/spl_token'
45
72
  require_relative 'solace/programs/associated_token_account'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Scholl
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '13.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: tty-spinner
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
82
96
  description: A Ruby library for working with Solana blockchain. Provides both low-level
83
97
  instruction builders and high-level program clients for interacting with Solana
84
98
  programs.
@@ -95,7 +109,11 @@ files:
95
109
  - lib/solace/address_lookup_table.rb
96
110
  - lib/solace/composers/associated_token_account_program_create_account_composer.rb
97
111
  - lib/solace/composers/base.rb
112
+ - lib/solace/composers/spl_token_program_initialize_mint_composer.rb
113
+ - lib/solace/composers/spl_token_program_mint_to_composer.rb
98
114
  - lib/solace/composers/spl_token_program_transfer_checked_composer.rb
115
+ - lib/solace/composers/spl_token_program_transfer_composer.rb
116
+ - lib/solace/composers/system_program_create_account_composer.rb
99
117
  - lib/solace/composers/system_program_transfer_composer.rb
100
118
  - lib/solace/concerns/binary_serializable.rb
101
119
  - lib/solace/connection.rb
@@ -130,6 +148,8 @@ files:
130
148
  - lib/solace/serializers/message_serializer.rb
131
149
  - lib/solace/serializers/transaction_deserializer.rb
132
150
  - lib/solace/serializers/transaction_serializer.rb
151
+ - lib/solace/tokens.rb
152
+ - lib/solace/tokens/token.rb
133
153
  - lib/solace/transaction.rb
134
154
  - lib/solace/transaction_composer.rb
135
155
  - lib/solace/utils/account_context.rb