solana-ruby-kit 0.1.0

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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/lib/core_extensions/tapioca/name_patch.rb +32 -0
  3. data/lib/core_extensions/tapioca/required_ancestors.rb +13 -0
  4. data/lib/generators/solana/ruby/kit/install/install_generator.rb +17 -0
  5. data/lib/generators/solana/ruby/kit/install/templates/solana_ruby_kit.rb.tt +8 -0
  6. data/lib/solana/ruby/kit/accounts/account.rb +47 -0
  7. data/lib/solana/ruby/kit/accounts/maybe_account.rb +86 -0
  8. data/lib/solana/ruby/kit/accounts.rb +6 -0
  9. data/lib/solana/ruby/kit/addresses/address.rb +133 -0
  10. data/lib/solana/ruby/kit/addresses/curve.rb +112 -0
  11. data/lib/solana/ruby/kit/addresses/program_derived_address.rb +155 -0
  12. data/lib/solana/ruby/kit/addresses/public_key.rb +39 -0
  13. data/lib/solana/ruby/kit/addresses.rb +11 -0
  14. data/lib/solana/ruby/kit/codecs/bytes.rb +58 -0
  15. data/lib/solana/ruby/kit/codecs/codec.rb +135 -0
  16. data/lib/solana/ruby/kit/codecs/data_structures.rb +177 -0
  17. data/lib/solana/ruby/kit/codecs/decoder.rb +43 -0
  18. data/lib/solana/ruby/kit/codecs/encoder.rb +52 -0
  19. data/lib/solana/ruby/kit/codecs/numbers.rb +217 -0
  20. data/lib/solana/ruby/kit/codecs/strings.rb +116 -0
  21. data/lib/solana/ruby/kit/codecs.rb +25 -0
  22. data/lib/solana/ruby/kit/configuration.rb +48 -0
  23. data/lib/solana/ruby/kit/encoding/base58.rb +62 -0
  24. data/lib/solana/ruby/kit/errors.rb +226 -0
  25. data/lib/solana/ruby/kit/fast_stable_stringify.rb +62 -0
  26. data/lib/solana/ruby/kit/functional.rb +29 -0
  27. data/lib/solana/ruby/kit/instruction_plans/plans.rb +27 -0
  28. data/lib/solana/ruby/kit/instruction_plans.rb +47 -0
  29. data/lib/solana/ruby/kit/instructions/accounts.rb +80 -0
  30. data/lib/solana/ruby/kit/instructions/instruction.rb +71 -0
  31. data/lib/solana/ruby/kit/instructions/roles.rb +84 -0
  32. data/lib/solana/ruby/kit/instructions.rb +7 -0
  33. data/lib/solana/ruby/kit/keys/key_pair.rb +84 -0
  34. data/lib/solana/ruby/kit/keys/private_key.rb +39 -0
  35. data/lib/solana/ruby/kit/keys/public_key.rb +31 -0
  36. data/lib/solana/ruby/kit/keys/signatures.rb +171 -0
  37. data/lib/solana/ruby/kit/keys.rb +11 -0
  38. data/lib/solana/ruby/kit/offchain_messages/codec.rb +107 -0
  39. data/lib/solana/ruby/kit/offchain_messages/message.rb +22 -0
  40. data/lib/solana/ruby/kit/offchain_messages.rb +16 -0
  41. data/lib/solana/ruby/kit/options/option.rb +132 -0
  42. data/lib/solana/ruby/kit/options.rb +5 -0
  43. data/lib/solana/ruby/kit/plugin_core.rb +58 -0
  44. data/lib/solana/ruby/kit/programs.rb +42 -0
  45. data/lib/solana/ruby/kit/promises.rb +85 -0
  46. data/lib/solana/ruby/kit/railtie.rb +18 -0
  47. data/lib/solana/ruby/kit/rpc/api/get_account_info.rb +76 -0
  48. data/lib/solana/ruby/kit/rpc/api/get_balance.rb +41 -0
  49. data/lib/solana/ruby/kit/rpc/api/get_block_height.rb +29 -0
  50. data/lib/solana/ruby/kit/rpc/api/get_epoch_info.rb +47 -0
  51. data/lib/solana/ruby/kit/rpc/api/get_latest_blockhash.rb +52 -0
  52. data/lib/solana/ruby/kit/rpc/api/get_minimum_balance_for_rent_exemption.rb +29 -0
  53. data/lib/solana/ruby/kit/rpc/api/get_multiple_accounts.rb +56 -0
  54. data/lib/solana/ruby/kit/rpc/api/get_program_accounts.rb +60 -0
  55. data/lib/solana/ruby/kit/rpc/api/get_signature_statuses.rb +56 -0
  56. data/lib/solana/ruby/kit/rpc/api/get_slot.rb +30 -0
  57. data/lib/solana/ruby/kit/rpc/api/get_token_account_balance.rb +38 -0
  58. data/lib/solana/ruby/kit/rpc/api/get_token_accounts_by_owner.rb +48 -0
  59. data/lib/solana/ruby/kit/rpc/api/get_transaction.rb +36 -0
  60. data/lib/solana/ruby/kit/rpc/api/get_vote_accounts.rb +62 -0
  61. data/lib/solana/ruby/kit/rpc/api/is_blockhash_valid.rb +41 -0
  62. data/lib/solana/ruby/kit/rpc/api/request_airdrop.rb +35 -0
  63. data/lib/solana/ruby/kit/rpc/api/send_transaction.rb +61 -0
  64. data/lib/solana/ruby/kit/rpc/api/simulate_transaction.rb +47 -0
  65. data/lib/solana/ruby/kit/rpc/client.rb +83 -0
  66. data/lib/solana/ruby/kit/rpc/transport.rb +137 -0
  67. data/lib/solana/ruby/kit/rpc.rb +13 -0
  68. data/lib/solana/ruby/kit/rpc_parsed_types/address_lookup_table.rb +33 -0
  69. data/lib/solana/ruby/kit/rpc_parsed_types/nonce_account.rb +33 -0
  70. data/lib/solana/ruby/kit/rpc_parsed_types/stake_account.rb +51 -0
  71. data/lib/solana/ruby/kit/rpc_parsed_types/token_account.rb +52 -0
  72. data/lib/solana/ruby/kit/rpc_parsed_types/vote_account.rb +38 -0
  73. data/lib/solana/ruby/kit/rpc_parsed_types.rb +16 -0
  74. data/lib/solana/ruby/kit/rpc_subscriptions/api/account_notifications.rb +29 -0
  75. data/lib/solana/ruby/kit/rpc_subscriptions/api/logs_notifications.rb +28 -0
  76. data/lib/solana/ruby/kit/rpc_subscriptions/api/program_notifications.rb +30 -0
  77. data/lib/solana/ruby/kit/rpc_subscriptions/api/root_notifications.rb +19 -0
  78. data/lib/solana/ruby/kit/rpc_subscriptions/api/signature_notifications.rb +28 -0
  79. data/lib/solana/ruby/kit/rpc_subscriptions/api/slot_notifications.rb +19 -0
  80. data/lib/solana/ruby/kit/rpc_subscriptions/autopinger.rb +42 -0
  81. data/lib/solana/ruby/kit/rpc_subscriptions/client.rb +80 -0
  82. data/lib/solana/ruby/kit/rpc_subscriptions/subscription.rb +58 -0
  83. data/lib/solana/ruby/kit/rpc_subscriptions/transport.rb +163 -0
  84. data/lib/solana/ruby/kit/rpc_subscriptions.rb +12 -0
  85. data/lib/solana/ruby/kit/rpc_types/account_info.rb +53 -0
  86. data/lib/solana/ruby/kit/rpc_types/cluster_url.rb +56 -0
  87. data/lib/solana/ruby/kit/rpc_types/commitment.rb +52 -0
  88. data/lib/solana/ruby/kit/rpc_types/lamports.rb +43 -0
  89. data/lib/solana/ruby/kit/rpc_types.rb +8 -0
  90. data/lib/solana/ruby/kit/signers/keypair_signer.rb +126 -0
  91. data/lib/solana/ruby/kit/signers.rb +5 -0
  92. data/lib/solana/ruby/kit/subscribable/async_iterable.rb +80 -0
  93. data/lib/solana/ruby/kit/subscribable/data_publisher.rb +90 -0
  94. data/lib/solana/ruby/kit/subscribable.rb +13 -0
  95. data/lib/solana/ruby/kit/sysvars/addresses.rb +19 -0
  96. data/lib/solana/ruby/kit/sysvars/clock.rb +37 -0
  97. data/lib/solana/ruby/kit/sysvars/epoch_schedule.rb +34 -0
  98. data/lib/solana/ruby/kit/sysvars/last_restart_slot.rb +22 -0
  99. data/lib/solana/ruby/kit/sysvars/rent.rb +29 -0
  100. data/lib/solana/ruby/kit/sysvars.rb +33 -0
  101. data/lib/solana/ruby/kit/transaction_confirmation.rb +159 -0
  102. data/lib/solana/ruby/kit/transaction_messages/transaction_message.rb +168 -0
  103. data/lib/solana/ruby/kit/transaction_messages.rb +5 -0
  104. data/lib/solana/ruby/kit/transactions/transaction.rb +135 -0
  105. data/lib/solana/ruby/kit/transactions.rb +5 -0
  106. data/lib/solana/ruby/kit/version.rb +10 -0
  107. data/lib/solana/ruby/kit.rb +100 -0
  108. data/solana-ruby-kit.gemspec +29 -0
  109. metadata +311 -0
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module OffchainMessages
6
+ # An off-chain message ready for signing.
7
+ # Mirrors the OffchainMessage type from @solana/signers.
8
+ class Message < T::Struct
9
+ # Header version: 0 = legacy ASCII, 1 = extended UTF-8.
10
+ const :version, Integer
11
+
12
+ # Application domain (up to 255 bytes).
13
+ const :domain, String
14
+
15
+ # UTF-8 message body.
16
+ const :message, String
17
+
18
+ # Optional application-specific domain (version ≥ 1 only).
19
+ const :application_domain, T.nilable(String), default: nil
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'rbnacl'
5
+
6
+ # Mirrors @solana/signers off-chain message signing.
7
+ require_relative 'offchain_messages/message'
8
+ require_relative 'offchain_messages/codec'
9
+
10
+ module Solana::Ruby::Kit
11
+ module OffchainMessages
12
+ # Re-export codec helpers at module level for convenience.
13
+ extend T::Sig
14
+ extend Codec
15
+ end
16
+ end
@@ -0,0 +1,132 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Options
6
+ extend T::Sig
7
+ # Mirrors Rust's `Option<T>` pattern from @solana/options.
8
+ #
9
+ # TypeScript represents absence as `T | null`, but that collapses nested
10
+ # options: `Option<Option<T>>` becomes indistinguishable from `Option<T>`.
11
+ # This explicit discriminated union preserves that distinction.
12
+
13
+ # Represents the presence of a value — mirrors TypeScript's `Some<T>`.
14
+ class Some
15
+ extend T::Sig
16
+ extend T::Generic
17
+
18
+ Elem = type_member
19
+
20
+ sig { returns(Elem) }
21
+ attr_reader :value
22
+
23
+ sig { params(value: Elem).void }
24
+ def initialize(value)
25
+ @value = T.let(value, Elem)
26
+ end
27
+
28
+ sig { returns(String) }
29
+ def inspect = "Some(#{T.unsafe(@value).inspect})"
30
+
31
+ sig { params(other: T.untyped).returns(T::Boolean) }
32
+ def ==(other)
33
+ !!(other.is_a?(Some) && T.unsafe(value) == T.unsafe(other.value))
34
+ end
35
+ end
36
+
37
+ # Represents the absence of a value — mirrors TypeScript's `None`.
38
+ class None
39
+ extend T::Sig
40
+
41
+ INSTANCE = T.let(new, None)
42
+
43
+ sig { returns(String) }
44
+ def inspect = 'None'
45
+
46
+ sig { params(other: T.untyped).returns(T::Boolean) }
47
+ def ==(other) = other.is_a?(None)
48
+ end
49
+
50
+ # Sorbet type alias: Option<T> is either Some or None.
51
+ # Because Sorbet generics on non-class types are limited, we use T.untyped
52
+ # for the contained value at the type-alias level; callers rely on Some's
53
+ # generic parameter for per-use type safety.
54
+ Option = T.type_alias { T.any(Solana::Ruby::Kit::Options::Some[T.untyped], None) }
55
+
56
+ # OptionOrNullable mirrors TypeScript's `OptionOrNullable<T>`:
57
+ # accepts Option<T>, T, or nil — useful for codec input.
58
+ OptionOrNullable = T.type_alias { T.untyped }
59
+
60
+ module_function
61
+
62
+ # Wraps a value in Some. Mirrors `some<T>(value)`.
63
+ sig { params(value: T.untyped).returns(Solana::Ruby::Kit::Options::Some[T.untyped]) }
64
+ def some(value)
65
+ Some.new(value)
66
+ end
67
+
68
+ # Returns the singleton None instance. Mirrors `none<T>()`.
69
+ sig { returns(None) }
70
+ def none
71
+ None::INSTANCE
72
+ end
73
+
74
+ # Returns true if the value is an Option (Some or None).
75
+ # Mirrors `isOption()`.
76
+ sig { params(input: T.untyped).returns(T::Boolean) }
77
+ def option?(input)
78
+ input.is_a?(Some) || input.is_a?(None)
79
+ end
80
+
81
+ # Returns true if the option contains a value.
82
+ # Mirrors `isSome()`.
83
+ sig { params(opt: T.untyped).returns(T::Boolean) }
84
+ def some?(opt)
85
+ opt.is_a?(Some)
86
+ end
87
+
88
+ # Returns true if the option is empty.
89
+ # Mirrors `isNone()`.
90
+ sig { params(opt: T.untyped).returns(T::Boolean) }
91
+ def none?(opt)
92
+ opt.is_a?(None)
93
+ end
94
+
95
+ # Extracts the contained value, or returns a fallback.
96
+ # Mirrors `unwrapOption(option, fallback?)`.
97
+ #
98
+ # @param opt [Some, None]
99
+ # @param fallback [Proc, nil] called when opt is None; returns nil if omitted
100
+ sig { params(opt: T.untyped, fallback: T.nilable(T.proc.returns(T.untyped))).returns(T.untyped) }
101
+ def unwrap_option(opt, fallback = nil)
102
+ return opt.value if opt.is_a?(Some)
103
+
104
+ fallback ? fallback.call : nil
105
+ end
106
+
107
+ # Wraps a nullable (nil-able) value into an Option.
108
+ # Mirrors `wrapNullable()`.
109
+ sig { params(nullable: T.untyped).returns(T.any(Solana::Ruby::Kit::Options::Some[T.untyped], None)) }
110
+ def wrap_nullable(nullable)
111
+ nullable.nil? ? none : some(nullable)
112
+ end
113
+
114
+ # Recursively unwraps nested Options within objects, arrays, and hashes.
115
+ # Mirrors `unwrapOptionRecursively()`.
116
+ #
117
+ # Primitives (Integer, Float, String, Symbol, true, false) and
118
+ # binary/typed-array equivalents are returned as-is.
119
+ sig { params(input: T.untyped, fallback: T.nilable(T.proc.returns(T.untyped))).returns(T.untyped) }
120
+ def unwrap_option_recursively(input, fallback = nil)
121
+ nxt = ->(x) { unwrap_option_recursively(x, fallback) }
122
+
123
+ case input
124
+ when Some then nxt.call(input.value)
125
+ when None then fallback ? fallback.call : nil
126
+ when Array then input.map { |el| nxt.call(el) }
127
+ when Hash then input.transform_values { |v| nxt.call(v) }
128
+ else input # primitives and opaque objects pass through
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,5 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # Option<T> type utilities — mirrors @solana/options.
5
+ require_relative 'options/option'
@@ -0,0 +1,58 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # Mirrors @solana/rpc-types plugin-core pattern (createEmptyClient / use).
5
+ #
6
+ # A PluginClient starts empty and is extended incrementally via #use.
7
+ # Each plugin is a callable that receives the current client and returns a
8
+ # Hash of { method_name => callable } which is merged into the client.
9
+ #
10
+ # client = Solana::Ruby::Kit::PluginCore.create_client
11
+ # .use(Solana::Ruby::Kit::Rpc::Api::ALL_METHODS)
12
+ #
13
+ module Solana::Ruby::Kit
14
+ class PluginClient
15
+ extend T::Sig
16
+
17
+ sig { void }
18
+ def initialize
19
+ @methods = T.let({}, T::Hash[Symbol, T.proc.params(args: T.untyped).returns(T.untyped)])
20
+ end
21
+
22
+ # Apply +plugin+ and return a new PluginClient with the additional methods.
23
+ # +plugin+ may be:
24
+ # - a Hash of { symbol => callable }
25
+ # - a callable that receives self and returns such a Hash
26
+ sig { params(plugin: T.untyped).returns(PluginClient) }
27
+ def use(plugin)
28
+ new_methods = plugin.respond_to?(:call) ? plugin.call(self) : plugin
29
+ extended = PluginClient.new
30
+ extended.instance_variable_set(:@methods, @methods.merge(new_methods.transform_keys(&:to_sym)))
31
+ extended
32
+ end
33
+
34
+ sig { params(name: Symbol, args: T.untyped, block: T.untyped).returns(T.untyped) }
35
+ def method_missing(name, *args, &block)
36
+ m = @methods[name]
37
+ return super unless m
38
+
39
+ T.unsafe(m).call(*args, &block)
40
+ end
41
+
42
+ sig { params(name: Symbol, include_private: T::Boolean).returns(T::Boolean) }
43
+ def respond_to_missing?(name, include_private = false)
44
+ @methods.key?(name) || super
45
+ end
46
+ end
47
+
48
+ module PluginCore
49
+ extend T::Sig
50
+
51
+ module_function
52
+
53
+ sig { returns(PluginClient) }
54
+ def create_client
55
+ PluginClient.new
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # Mirrors @solana/programs — helpers for inspecting custom program errors
5
+ # returned in Solana transaction failures.
6
+ #
7
+ # A program error in a transaction result looks like:
8
+ # { "InstructionError" => [0, { "Custom" => 1234 }] }
9
+ module Solana::Ruby::Kit
10
+ module Programs
11
+ extend T::Sig
12
+
13
+ module_function
14
+
15
+ # Returns true when +err+ is a custom program error, optionally matching
16
+ # a specific error code.
17
+ sig { params(err: T.untyped, expected_code: T.nilable(Integer)).returns(T::Boolean) }
18
+ def program_error?(err, expected_code: nil)
19
+ code = get_program_error_code(err)
20
+ return false if code.nil?
21
+
22
+ expected_code ? code == expected_code : true
23
+ end
24
+
25
+ # Extract the custom program error code from a transaction error hash.
26
+ # Returns nil when the error is not a custom program error.
27
+ sig { params(err: T.untyped).returns(T.nilable(Integer)) }
28
+ def get_program_error_code(err)
29
+ return nil unless err.is_a?(Hash)
30
+
31
+ instruction_error = err['InstructionError']
32
+ return nil unless instruction_error.is_a?(Array) && instruction_error.length == 2
33
+
34
+ inner = instruction_error[1]
35
+ return nil unless inner.is_a?(Hash) && inner.key?('Custom')
36
+
37
+ Kernel.Integer(inner['Custom'])
38
+ rescue TypeError, ArgumentError
39
+ nil
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,85 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'timeout'
5
+
6
+ # Mirrors @solana/promises.
7
+ # Provides thread-safe race and timeout helpers used by the RPC subscription
8
+ # transport layer. Ruby threads replace JavaScript Promises throughout.
9
+ module Solana::Ruby::Kit
10
+ module Promises
11
+ extend T::Sig
12
+
13
+ module_function
14
+
15
+ # Run each callable in a dedicated thread and return the first result.
16
+ # All losing threads are killed to avoid memory leaks — analogous to the
17
+ # JS safeRace() that cancels losing promises via AbortSignal.
18
+ #
19
+ # @param callables [Array<#call>] lambdas / procs to race
20
+ # @param timeout_secs [Float, nil] optional wall-clock limit
21
+ # @return the return value of whichever callable finishes first
22
+ # @raise [Timeout::Error] if timeout_secs is reached before any finishes
23
+ # @raise propagates any exception thrown by the winning callable
24
+ sig do
25
+ params(
26
+ callables: T::Array[T.proc.returns(T.untyped)],
27
+ timeout_secs: T.nilable(Float)
28
+ ).returns(T.untyped)
29
+ end
30
+ def safe_race(callables, timeout_secs: nil)
31
+ result_q = Queue.new
32
+ threads = callables.map do |callable|
33
+ Thread.new do
34
+ value = callable.call
35
+ result_q.push([:ok, value])
36
+ rescue StandardError => e
37
+ result_q.push([:err, e])
38
+ end
39
+ end
40
+
41
+ begin
42
+ kind, payload = if timeout_secs
43
+ Timeout.timeout(timeout_secs) { result_q.pop }
44
+ else
45
+ result_q.pop
46
+ end
47
+ ensure
48
+ threads.each(&:kill)
49
+ end
50
+
51
+ Kernel.raise payload if kind == :err
52
+
53
+ payload
54
+ end
55
+
56
+ # Execute +block+ with an optional wall-clock deadline.
57
+ # When +secs+ is nil the block runs without any deadline.
58
+ #
59
+ # @param secs [Float, nil]
60
+ # @raise [Timeout::Error] on deadline exceeded
61
+ sig do
62
+ type_parameters(:R)
63
+ .params(secs: T.nilable(Float), block: T.proc.returns(T.type_parameter(:R)))
64
+ .returns(T.type_parameter(:R))
65
+ end
66
+ def with_timeout(secs, &block)
67
+ if secs
68
+ Timeout.timeout(secs) { block.call }
69
+ else
70
+ yield
71
+ end
72
+ end
73
+
74
+ # Returns a lambda that, when called, raises Timeout::Error.
75
+ # Useful for constructing an "abort signal" from a deadline.
76
+ sig { params(secs: Float).returns(T.proc.void) }
77
+ def make_abort_signal(secs)
78
+ start = T.let(Time.now, Time)
79
+ Kernel.lambda do
80
+ elapsed = Time.now - start
81
+ Kernel.raise Timeout::Error, "Aborted after #{elapsed.round(2)}s" if elapsed >= secs
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,18 @@
1
+ # typed: ignore
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ class Railtie < Rails::Railtie
6
+ config.solana_ruby_kit = ActiveSupport::OrderedOptions.new
7
+
8
+ initializer 'solana_ruby_kit.configure' do |app|
9
+ opts = app.config.solana_ruby_kit
10
+ Solana::Ruby::Kit.configure do |c|
11
+ c.rpc_url = opts.rpc_url if opts.rpc_url
12
+ c.ws_url = opts.ws_url if opts.ws_url
13
+ c.commitment = opts.commitment if opts.commitment
14
+ c.timeout = opts.timeout if opts.timeout
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'base64'
5
+ require_relative '../../rpc_types/account_info'
6
+
7
+ module Solana::Ruby::Kit
8
+ module Rpc
9
+ module Api
10
+ # Fetches all stored information for an account at the given address.
11
+ # Mirrors TypeScript's `GetAccountInfoApi.getAccountInfo(address, config?)`.
12
+ #
13
+ # Returns a `RpcContextualValue` with:
14
+ # .slot — context slot
15
+ # .value — AccountInfoWithBase64Data | AccountInfoWithJsonData | nil
16
+ # (nil when the account does not exist)
17
+ module GetAccountInfo
18
+ extend T::Sig
19
+
20
+ SUPPORTED_ENCODINGS = T.let(%w[base64 jsonParsed base64+zstd].freeze, T::Array[String])
21
+
22
+ sig do
23
+ params(
24
+ address: String,
25
+ encoding: String,
26
+ commitment: T.nilable(Symbol),
27
+ min_context_slot: T.nilable(Integer),
28
+ data_slice: T.nilable(T::Hash[String, Integer])
29
+ ).returns(RpcTypes::RpcContextualValue)
30
+ end
31
+ def get_account_info(
32
+ address,
33
+ encoding: 'base64',
34
+ commitment: nil,
35
+ min_context_slot: nil,
36
+ data_slice: nil
37
+ )
38
+ config = { 'encoding' => encoding }
39
+ config['commitment'] = commitment.to_s if commitment
40
+ config['minContextSlot'] = min_context_slot if min_context_slot
41
+ config['dataSlice'] = data_slice if data_slice
42
+
43
+ result = transport.request('getAccountInfo', [address, config])
44
+
45
+ slot = Kernel.Integer(result['context']['slot'])
46
+ raw = result['value']
47
+
48
+ value =
49
+ if raw.nil?
50
+ nil
51
+ elsif encoding == 'jsonParsed'
52
+ RpcTypes::AccountInfoWithJsonData.new(
53
+ executable: raw['executable'],
54
+ lamports: Kernel.Integer(raw['lamports']),
55
+ owner: raw['owner'],
56
+ space: Kernel.Integer(raw.fetch('space', 0)),
57
+ rent_epoch: Kernel.Integer(raw.fetch('rentEpoch', 0)),
58
+ data: raw['data']
59
+ )
60
+ else
61
+ RpcTypes::AccountInfoWithBase64Data.new(
62
+ executable: raw['executable'],
63
+ lamports: Kernel.Integer(raw['lamports']),
64
+ owner: raw['owner'],
65
+ space: Kernel.Integer(raw.fetch('space', 0)),
66
+ rent_epoch: Kernel.Integer(raw.fetch('rentEpoch', 0)),
67
+ data: Kernel.Array(raw['data'])
68
+ )
69
+ end
70
+
71
+ RpcTypes::RpcContextualValue.new(slot: slot, value: value)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../../rpc_types/account_info'
5
+
6
+ module Solana::Ruby::Kit
7
+ module Rpc
8
+ module Api
9
+ # Returns the lamport balance of an account.
10
+ # Mirrors TypeScript's `GetBalanceApi.getBalance(address, config?)`.
11
+ #
12
+ # Returns a `RpcContextualValue` with:
13
+ # .slot — the slot at which the balance was read
14
+ # .value — Integer (lamports)
15
+ module GetBalance
16
+ extend T::Sig
17
+
18
+ sig do
19
+ params(
20
+ address: String,
21
+ commitment: T.nilable(Symbol),
22
+ min_context_slot: T.nilable(Integer)
23
+ ).returns(RpcTypes::RpcContextualValue)
24
+ end
25
+ def get_balance(address, commitment: nil, min_context_slot: nil)
26
+ config = {}
27
+ config['commitment'] = commitment.to_s if commitment
28
+ config['minContextSlot'] = min_context_slot if min_context_slot
29
+
30
+ params = config.empty? ? [address] : [address, config]
31
+ result = transport.request('getBalance', params)
32
+
33
+ RpcTypes::RpcContextualValue.new(
34
+ slot: Kernel.Integer(result['context']['slot']),
35
+ value: Kernel.Integer(result['value'])
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Returns the current block height of the node.
8
+ # Mirrors TypeScript's `GetBlockHeightApi.getBlockHeight(config?)`.
9
+ module GetBlockHeight
10
+ extend T::Sig
11
+
12
+ sig do
13
+ params(
14
+ commitment: T.nilable(Symbol),
15
+ min_context_slot: T.nilable(Integer)
16
+ ).returns(Integer)
17
+ end
18
+ def get_block_height(commitment: nil, min_context_slot: nil)
19
+ config = {}
20
+ config['commitment'] = commitment.to_s if commitment
21
+ config['minContextSlot'] = min_context_slot if min_context_slot
22
+
23
+ params = config.empty? ? [] : [config]
24
+ Kernel.Integer(transport.request('getBlockHeight', params))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Struct returned by get_epoch_info.
8
+ EpochInfo = T.let(
9
+ Struct.new(
10
+ :absolute_slot, # Integer — current slot
11
+ :block_height, # Integer
12
+ :epoch, # Integer — current epoch
13
+ :slot_index, # Integer — slot within current epoch
14
+ :slots_in_epoch, # Integer — total slots in epoch
15
+ :transaction_count, # Integer | nil
16
+ keyword_init: true
17
+ ),
18
+ T.untyped
19
+ )
20
+
21
+ # Fetch information about the current epoch.
22
+ # Mirrors TypeScript's GetEpochInfoApi.getEpochInfo.
23
+ module GetEpochInfo
24
+ extend T::Sig
25
+
26
+ sig do
27
+ params(commitment: T.nilable(Symbol)).returns(T.untyped)
28
+ end
29
+ def get_epoch_info(commitment: nil)
30
+ config = {}
31
+ config['commitment'] = commitment.to_s if commitment
32
+
33
+ raw = transport.request('getEpochInfo', config.empty? ? [] : [config])
34
+
35
+ EpochInfo.new(
36
+ absolute_slot: Kernel.Integer(raw['absoluteSlot']),
37
+ block_height: Kernel.Integer(raw['blockHeight']),
38
+ epoch: Kernel.Integer(raw['epoch']),
39
+ slot_index: Kernel.Integer(raw['slotIndex']),
40
+ slots_in_epoch: Kernel.Integer(raw['slotsInEpoch']),
41
+ transaction_count: raw['transactionCount'] ? Kernel.Integer(raw['transactionCount']) : nil
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../../rpc_types/account_info'
5
+
6
+ module Solana::Ruby::Kit
7
+ module Rpc
8
+ module Api
9
+ # The payload returned by getLatestBlockhash.
10
+ # Mirrors TypeScript's `GetLatestBlockhashApiResponse`.
11
+ class LatestBlockhash < T::Struct
12
+ const :blockhash, String # base58-encoded 32 bytes
13
+ const :last_valid_block_height, Integer # TypeScript bigint → Ruby Integer
14
+ end
15
+
16
+ # Returns the latest blockhash and its expiry block height.
17
+ # Mirrors TypeScript's `GetLatestBlockhashApi.getLatestBlockhash(config?)`.
18
+ #
19
+ # Returns a `RpcContextualValue` with:
20
+ # .slot — context slot
21
+ # .value — LatestBlockhash
22
+ module GetLatestBlockhash
23
+ extend T::Sig
24
+
25
+ sig do
26
+ params(
27
+ commitment: T.nilable(Symbol),
28
+ min_context_slot: T.nilable(Integer)
29
+ ).returns(RpcTypes::RpcContextualValue)
30
+ end
31
+ def get_latest_blockhash(commitment: nil, min_context_slot: nil)
32
+ config = {}
33
+ config['commitment'] = commitment.to_s if commitment
34
+ config['minContextSlot'] = min_context_slot if min_context_slot
35
+
36
+ params = config.empty? ? [] : [config]
37
+ result = transport.request('getLatestBlockhash', params)
38
+
39
+ value = LatestBlockhash.new(
40
+ blockhash: result['value']['blockhash'],
41
+ last_valid_block_height: Kernel.Integer(result['value']['lastValidBlockHeight'])
42
+ )
43
+
44
+ RpcTypes::RpcContextualValue.new(
45
+ slot: Kernel.Integer(result['context']['slot']),
46
+ value: value
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Returns the minimum balance required to keep an account rent-exempt
8
+ # given its data size in bytes.
9
+ # Mirrors `GetMinimumBalanceForRentExemptionApi.getMinimumBalanceForRentExemption()`.
10
+ module GetMinimumBalanceForRentExemption
11
+ extend T::Sig
12
+
13
+ sig do
14
+ params(
15
+ data_size: Integer,
16
+ commitment: T.nilable(Symbol)
17
+ ).returns(Integer)
18
+ end
19
+ def get_minimum_balance_for_rent_exemption(data_size, commitment: nil)
20
+ config = {}
21
+ config['commitment'] = commitment.to_s if commitment
22
+
23
+ params = config.empty? ? [data_size] : [data_size, config]
24
+ Kernel.Integer(transport.request('getMinimumBalanceForRentExemption', params))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end