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,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'base64'
5
+
6
+ module Solana::Ruby::Kit
7
+ module Rpc
8
+ module Api
9
+ # Fetch multiple account infos in a single RPC call.
10
+ # Mirrors TypeScript's GetMultipleAccountsApi.getMultipleAccounts.
11
+ module GetMultipleAccounts
12
+ extend T::Sig
13
+
14
+ sig do
15
+ params(
16
+ addresses: T::Array[String],
17
+ encoding: String,
18
+ commitment: T.nilable(Symbol),
19
+ min_context_slot: T.nilable(Integer),
20
+ data_slice: T.nilable(T::Hash[String, Integer])
21
+ ).returns(RpcTypes::RpcContextualValue)
22
+ end
23
+ def get_multiple_accounts(
24
+ addresses,
25
+ encoding: 'base64',
26
+ commitment: nil,
27
+ min_context_slot: nil,
28
+ data_slice: nil
29
+ )
30
+ config = { 'encoding' => encoding }
31
+ config['commitment'] = commitment.to_s if commitment
32
+ config['minContextSlot'] = min_context_slot if min_context_slot
33
+ config['dataSlice'] = data_slice if data_slice
34
+
35
+ result = transport.request('getMultipleAccounts', [addresses, config])
36
+
37
+ slot = Kernel.Integer(result['context']['slot'])
38
+ values = Kernel.Array(result['value']).map do |raw|
39
+ next nil if raw.nil?
40
+
41
+ RpcTypes::AccountInfoWithBase64Data.new(
42
+ executable: raw['executable'],
43
+ lamports: Kernel.Integer(raw['lamports']),
44
+ owner: raw['owner'],
45
+ space: Kernel.Integer(raw.fetch('space', 0)),
46
+ rent_epoch: Kernel.Integer(raw.fetch('rentEpoch', 0)),
47
+ data: Kernel.Array(raw['data'])
48
+ )
49
+ end
50
+
51
+ RpcTypes::RpcContextualValue.new(slot: slot, value: values)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Fetch all accounts owned by a program.
8
+ # Mirrors TypeScript's GetProgramAccountsApi.getProgramAccounts.
9
+ #
10
+ # Returns an Array of { pubkey:, account: AccountInfoWithBase64Data }.
11
+ module GetProgramAccounts
12
+ extend T::Sig
13
+
14
+ sig do
15
+ params(
16
+ program_id: String,
17
+ encoding: String,
18
+ filters: T::Array[T::Hash[String, T.untyped]],
19
+ commitment: T.nilable(Symbol),
20
+ min_context_slot: T.nilable(Integer),
21
+ with_context: T::Boolean
22
+ ).returns(T.untyped)
23
+ end
24
+ def get_program_accounts(
25
+ program_id,
26
+ encoding: 'base64',
27
+ filters: [],
28
+ commitment: nil,
29
+ min_context_slot: nil,
30
+ with_context: false
31
+ )
32
+ config = { 'encoding' => encoding, 'withContext' => with_context }
33
+ config['filters'] = filters unless filters.empty?
34
+ config['commitment'] = commitment.to_s if commitment
35
+ config['minContextSlot'] = min_context_slot if min_context_slot
36
+
37
+ result = transport.request('getProgramAccounts', [program_id, config])
38
+
39
+ # withContext wraps result in {context:, value:}
40
+ accounts = with_context ? result['value'] : result
41
+
42
+ Kernel.Array(accounts).map do |item|
43
+ account_raw = item['account']
44
+ {
45
+ pubkey: item['pubkey'],
46
+ account: RpcTypes::AccountInfoWithBase64Data.new(
47
+ executable: account_raw['executable'],
48
+ lamports: Kernel.Integer(account_raw['lamports']),
49
+ owner: account_raw['owner'],
50
+ space: Kernel.Integer(account_raw.fetch('space', 0)),
51
+ rent_epoch: Kernel.Integer(account_raw.fetch('rentEpoch', 0)),
52
+ data: Kernel.Array(account_raw['data'])
53
+ )
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
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
+ # Status of a confirmed transaction signature.
10
+ # Mirrors TypeScript's signature-status response shape.
11
+ class SignatureStatus < T::Struct
12
+ const :slot, Integer
13
+ const :confirmations, T.nilable(Integer) # nil when finalized
14
+ const :err, T.untyped # nil or error object
15
+ const :confirmation_status, T.nilable(Symbol) # :processed | :confirmed | :finalized
16
+ end
17
+
18
+ # Returns the confirmation status of one or more transaction signatures.
19
+ # Mirrors TypeScript's `GetSignatureStatusesApi.getSignatureStatuses(sigs, config?)`.
20
+ #
21
+ # Returns a `RpcContextualValue` with:
22
+ # .slot — context slot
23
+ # .value — Array of SignatureStatus | nil (nil for unknown signatures)
24
+ module GetSignatureStatuses
25
+ extend T::Sig
26
+
27
+ sig do
28
+ params(
29
+ signatures: T::Array[String],
30
+ search_transaction_history: T::Boolean
31
+ ).returns(RpcTypes::RpcContextualValue)
32
+ end
33
+ def get_signature_statuses(signatures, search_transaction_history: false)
34
+ config = { 'searchTransactionHistory' => search_transaction_history }
35
+ result = transport.request('getSignatureStatuses', [signatures, config])
36
+
37
+ statuses = result['value'].map do |raw|
38
+ next nil if raw.nil?
39
+
40
+ SignatureStatus.new(
41
+ slot: Kernel.Integer(raw['slot']),
42
+ confirmations: raw['confirmations'] ? Kernel.Integer(raw['confirmations']) : nil,
43
+ err: raw['err'],
44
+ confirmation_status: raw['confirmationStatus']&.to_sym
45
+ )
46
+ end
47
+
48
+ RpcTypes::RpcContextualValue.new(
49
+ slot: Kernel.Integer(result['context']['slot']),
50
+ value: statuses
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Returns the current slot number at the given commitment level.
8
+ # Mirrors TypeScript's `GetSlotApi.getSlot()`.
9
+ module GetSlot
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_slot(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
+ result = transport.request('getSlot', params)
25
+ Kernel.Integer(result)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Fetch the token balance of an SPL Token account.
8
+ # Mirrors TypeScript's GetTokenAccountBalanceApi.getTokenAccountBalance.
9
+ module GetTokenAccountBalance
10
+ extend T::Sig
11
+
12
+ sig do
13
+ params(
14
+ token_account: String,
15
+ commitment: T.nilable(Symbol)
16
+ ).returns(RpcTypes::RpcContextualValue)
17
+ end
18
+ def get_token_account_balance(token_account, commitment: nil)
19
+ config = {}
20
+ config['commitment'] = commitment.to_s if commitment
21
+
22
+ result = transport.request('getTokenAccountBalance', [token_account, config].tap { |a| a.pop if a.last.empty? })
23
+ slot = Kernel.Integer(result['context']['slot'])
24
+ raw = result['value']
25
+
26
+ value = {
27
+ amount: raw['amount'],
28
+ decimals: Kernel.Integer(raw['decimals']),
29
+ ui_amount: raw['uiAmount'],
30
+ ui_amount_string: raw['uiAmountString']
31
+ }
32
+
33
+ RpcTypes::RpcContextualValue.new(slot: slot, value: value)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Fetch all SPL Token accounts by owner.
8
+ # +filter+ must be one of:
9
+ # { 'mint' => mint_address }
10
+ # { 'programId' => program_id }
11
+ module GetTokenAccountsByOwner
12
+ extend T::Sig
13
+
14
+ sig do
15
+ params(
16
+ owner: String,
17
+ filter: T::Hash[String, String],
18
+ encoding: String,
19
+ commitment: T.nilable(Symbol)
20
+ ).returns(RpcTypes::RpcContextualValue)
21
+ end
22
+ def get_token_accounts_by_owner(owner, filter, encoding: 'base64', commitment: nil)
23
+ config = { 'encoding' => encoding }
24
+ config['commitment'] = commitment.to_s if commitment
25
+
26
+ result = transport.request('getTokenAccountsByOwner', [owner, filter, config])
27
+ slot = Kernel.Integer(result['context']['slot'])
28
+ value = Kernel.Array(result['value']).map do |item|
29
+ raw = item['account']
30
+ {
31
+ pubkey: item['pubkey'],
32
+ account: RpcTypes::AccountInfoWithBase64Data.new(
33
+ executable: raw['executable'],
34
+ lamports: Kernel.Integer(raw['lamports']),
35
+ owner: raw['owner'],
36
+ space: Kernel.Integer(raw.fetch('space', 0)),
37
+ rent_epoch: Kernel.Integer(raw.fetch('rentEpoch', 0)),
38
+ data: Kernel.Array(raw['data'])
39
+ )
40
+ }
41
+ end
42
+
43
+ RpcTypes::RpcContextualValue.new(slot: slot, value: value)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Fetch a confirmed transaction.
8
+ # Mirrors TypeScript's GetTransactionApi.getTransaction.
9
+ # Returns the raw JSON hash (nil if not found / not yet confirmed).
10
+ module GetTransaction
11
+ extend T::Sig
12
+
13
+ sig do
14
+ params(
15
+ signature: String,
16
+ encoding: String,
17
+ commitment: T.nilable(Symbol),
18
+ max_supported_transaction_version: T.nilable(Integer)
19
+ ).returns(T.nilable(T::Hash[String, T.untyped]))
20
+ end
21
+ def get_transaction(
22
+ signature,
23
+ encoding: 'json',
24
+ commitment: nil,
25
+ max_supported_transaction_version: nil
26
+ )
27
+ config = { 'encoding' => encoding }
28
+ config['commitment'] = commitment.to_s if commitment
29
+ config['maxSupportedTransactionVersion'] = max_supported_transaction_version if max_supported_transaction_version
30
+
31
+ transport.request('getTransaction', [signature, config])
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Solana::Ruby::Kit
5
+ module Rpc
6
+ module Api
7
+ # Vote account info struct.
8
+ VoteAccountInfo = T.let(
9
+ Struct.new(
10
+ :vote_pubkey,
11
+ :node_pubkey,
12
+ :activated_stake,
13
+ :epoch_vote_account,
14
+ :commission,
15
+ :last_vote,
16
+ :epoch_credits,
17
+ :root_slot,
18
+ keyword_init: true
19
+ ),
20
+ T.untyped
21
+ )
22
+
23
+ # Fetch current and delinquent vote accounts.
24
+ # Returns { current: [], delinquent: [] }.
25
+ module GetVoteAccounts
26
+ extend T::Sig
27
+
28
+ sig do
29
+ params(
30
+ commitment: T.nilable(Symbol),
31
+ vote_pubkey: T.nilable(String)
32
+ ).returns(T::Hash[Symbol, T::Array[T.untyped]])
33
+ end
34
+ def get_vote_accounts(commitment: nil, vote_pubkey: nil)
35
+ config = {}
36
+ config['commitment'] = commitment.to_s if commitment
37
+ config['votePubkey'] = vote_pubkey if vote_pubkey
38
+
39
+ raw = transport.request('getVoteAccounts', config.empty? ? [] : [config])
40
+
41
+ parse_vote = Kernel.lambda do |v|
42
+ VoteAccountInfo.new(
43
+ vote_pubkey: v['votePubkey'],
44
+ node_pubkey: v['nodePubkey'],
45
+ activated_stake: Kernel.Integer(v['activatedStake']),
46
+ epoch_vote_account: v['epochVoteAccount'],
47
+ commission: Kernel.Integer(v['commission']),
48
+ last_vote: Kernel.Integer(v['lastVote']),
49
+ epoch_credits: v['epochCredits'],
50
+ root_slot: v['rootSlot'] ? Kernel.Integer(v['rootSlot']) : nil
51
+ )
52
+ end
53
+
54
+ {
55
+ current: Kernel.Array(raw['current']).map(&parse_vote),
56
+ delinquent: Kernel.Array(raw['delinquent']).map(&parse_vote)
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ 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 whether a blockhash is still valid (not yet expired).
10
+ # Mirrors TypeScript's `IsBlockhashValidApi.isBlockhashValid(blockhash, config?)`.
11
+ #
12
+ # Returns a `RpcContextualValue` with:
13
+ # .slot — context slot
14
+ # .value — Boolean
15
+ module IsBlockhashValid
16
+ extend T::Sig
17
+
18
+ sig do
19
+ params(
20
+ blockhash: String,
21
+ commitment: T.nilable(Symbol),
22
+ min_context_slot: T.nilable(Integer)
23
+ ).returns(RpcTypes::RpcContextualValue)
24
+ end
25
+ def is_blockhash_valid(blockhash, 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? ? [blockhash] : [blockhash, config]
31
+ result = transport.request('isBlockhashValid', params)
32
+
33
+ RpcTypes::RpcContextualValue.new(
34
+ slot: Kernel.Integer(result['context']['slot']),
35
+ value: result['value'] == true
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../../keys/signatures'
5
+
6
+ module Solana::Ruby::Kit
7
+ module Rpc
8
+ module Api
9
+ # Requests an airdrop of lamports to the given address.
10
+ # Only available on devnet and testnet.
11
+ # Mirrors TypeScript's `RequestAirdropApi.requestAirdrop(address, lamports, config?)`.
12
+ #
13
+ # Returns the transaction `Signature` of the airdrop.
14
+ module RequestAirdrop
15
+ extend T::Sig
16
+
17
+ sig do
18
+ params(
19
+ address: String,
20
+ lamports: Integer,
21
+ commitment: T.nilable(Symbol)
22
+ ).returns(Keys::Signature)
23
+ end
24
+ def request_airdrop(address, lamports, commitment: nil)
25
+ config = {}
26
+ config['commitment'] = commitment.to_s if commitment
27
+
28
+ params = config.empty? ? [address, lamports] : [address, lamports, config]
29
+ sig_str = transport.request('requestAirdrop', params)
30
+ Keys::Signature.new(sig_str)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'base64'
5
+ require_relative '../../keys/signatures'
6
+
7
+ module Solana::Ruby::Kit
8
+ module Rpc
9
+ module Api
10
+ # Submits a signed transaction to the cluster for processing.
11
+ # Mirrors TypeScript's `SendTransactionApi.sendTransaction(transaction, config?)`.
12
+ #
13
+ # Accepts the transaction in two forms:
14
+ # - A `Solana::Ruby::Kit::Transactions::Transaction` struct (wire bytes are base64-encoded internally).
15
+ # - A raw base64 String (the already-encoded wire transaction).
16
+ #
17
+ # Returns a `Solana::Ruby::Kit::Keys::Signature` (base58-encoded transaction signature).
18
+ #
19
+ # Note: This method returns as soon as the node receives the transaction; it does
20
+ # NOT wait for confirmation. Use `get_signature_statuses` to poll for commitment.
21
+ module SendTransaction
22
+ extend T::Sig
23
+
24
+ sig do
25
+ params(
26
+ transaction: T.untyped, # Transactions::Transaction or String (base64)
27
+ skip_preflight: T::Boolean,
28
+ preflight_commitment: T.nilable(Symbol),
29
+ max_retries: T.nilable(Integer),
30
+ min_context_slot: T.nilable(Integer)
31
+ ).returns(Keys::Signature)
32
+ end
33
+ def send_transaction(
34
+ transaction,
35
+ skip_preflight: false,
36
+ preflight_commitment: nil,
37
+ max_retries: nil,
38
+ min_context_slot: nil
39
+ )
40
+ wire_base64 =
41
+ case transaction
42
+ when String
43
+ transaction
44
+ else
45
+ # Assume it responds to .message_bytes (Transactions::Transaction)
46
+ Base64.strict_encode64(transaction.message_bytes)
47
+ end
48
+
49
+ config = { 'encoding' => 'base64' }
50
+ config['skipPreflight'] = skip_preflight if skip_preflight
51
+ config['preflightCommitment'] = preflight_commitment.to_s if preflight_commitment
52
+ config['maxRetries'] = max_retries if max_retries
53
+ config['minContextSlot'] = min_context_slot if min_context_slot
54
+
55
+ sig_str = transport.request('sendTransaction', [wire_base64, config])
56
+ Keys::Signature.new(sig_str)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ 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
+ # Simulate a transaction without broadcasting it.
8
+ # Mirrors TypeScript's SimulateTransactionApi.simulateTransaction.
9
+ # Returns an RpcContextualValue whose value is the simulation result hash.
10
+ module SimulateTransaction
11
+ extend T::Sig
12
+
13
+ sig do
14
+ params(
15
+ encoded_transaction: String,
16
+ encoding: String,
17
+ commitment: T.nilable(Symbol),
18
+ sig_verify: T::Boolean,
19
+ replace_recent_blockhash: T::Boolean,
20
+ accounts: T.nilable(T::Hash[String, T.untyped])
21
+ ).returns(RpcTypes::RpcContextualValue)
22
+ end
23
+ def simulate_transaction(
24
+ encoded_transaction,
25
+ encoding: 'base64',
26
+ commitment: nil,
27
+ sig_verify: false,
28
+ replace_recent_blockhash: false,
29
+ accounts: nil
30
+ )
31
+ config = {
32
+ 'encoding' => encoding,
33
+ 'sigVerify' => sig_verify,
34
+ 'replaceRecentBlockhash' => replace_recent_blockhash
35
+ }
36
+ config['commitment'] = commitment.to_s if commitment
37
+ config['accounts'] = accounts if accounts
38
+
39
+ result = transport.request('simulateTransaction', [encoded_transaction, config])
40
+ slot = Kernel.Integer(result['context']['slot'])
41
+
42
+ RpcTypes::RpcContextualValue.new(slot: slot, value: result['value'])
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'transport'
5
+ require_relative 'api/get_slot'
6
+ require_relative 'api/get_balance'
7
+ require_relative 'api/get_latest_blockhash'
8
+ require_relative 'api/get_account_info'
9
+ require_relative 'api/get_block_height'
10
+ require_relative 'api/get_signature_statuses'
11
+ require_relative 'api/send_transaction'
12
+ require_relative 'api/request_airdrop'
13
+ require_relative 'api/is_blockhash_valid'
14
+ require_relative 'api/get_minimum_balance_for_rent_exemption'
15
+ require_relative 'api/get_multiple_accounts'
16
+ require_relative 'api/get_program_accounts'
17
+ require_relative 'api/get_transaction'
18
+ require_relative 'api/get_token_account_balance'
19
+ require_relative 'api/get_token_accounts_by_owner'
20
+ require_relative 'api/get_epoch_info'
21
+ require_relative 'api/get_vote_accounts'
22
+ require_relative 'api/simulate_transaction'
23
+
24
+ module Solana::Ruby::Kit
25
+ module Rpc
26
+ # Solana JSON-RPC client.
27
+ # Mirrors TypeScript's `Rpc` object created by `createSolanaRpc(url)`.
28
+ #
29
+ # Includes every API method as a mixin so the class surface exactly matches
30
+ # the TypeScript package's method set. The `transport` accessor provides
31
+ # sub-modules with the HTTP connection.
32
+ #
33
+ # @example
34
+ # rpc = Solana::Ruby::Kit::Rpc::Client.new(Solana::Ruby::Kit::RpcTypes.devnet)
35
+ # slot = rpc.get_slot
36
+ # res = rpc.get_balance(address)
37
+ # puts "#{res.value} lamports at slot #{res.slot}"
38
+ class Client
39
+ extend T::Sig
40
+
41
+ include Api::GetSlot
42
+ include Api::GetBalance
43
+ include Api::GetLatestBlockhash
44
+ include Api::GetAccountInfo
45
+ include Api::GetBlockHeight
46
+ include Api::GetSignatureStatuses
47
+ include Api::SendTransaction
48
+ include Api::RequestAirdrop
49
+ include Api::IsBlockhashValid
50
+ include Api::GetMinimumBalanceForRentExemption
51
+ include Api::GetMultipleAccounts
52
+ include Api::GetProgramAccounts
53
+ include Api::GetTransaction
54
+ include Api::GetTokenAccountBalance
55
+ include Api::GetTokenAccountsByOwner
56
+ include Api::GetEpochInfo
57
+ include Api::GetVoteAccounts
58
+ include Api::SimulateTransaction
59
+
60
+ sig { returns(Transport) }
61
+ attr_reader :transport
62
+
63
+ # @param cluster_url [RpcTypes::ClusterUrl, String] endpoint to connect to.
64
+ # @param headers [Hash<String, String>] additional HTTP headers.
65
+ # @param timeout [Integer] read timeout in seconds.
66
+ sig do
67
+ params(
68
+ cluster_url: T.untyped, # RpcTypes::ClusterUrl or String
69
+ headers: T::Hash[String, String],
70
+ timeout: Integer,
71
+ open_timeout: Integer
72
+ ).void
73
+ end
74
+ def initialize(cluster_url, headers: {}, timeout: Transport::DEFAULT_TIMEOUT, open_timeout: Transport::DEFAULT_OPEN_TIMEOUT)
75
+ url = cluster_url.respond_to?(:url) ? cluster_url.url : cluster_url.to_s
76
+ @transport = T.let(
77
+ Transport.new(url: url, headers: headers, timeout: timeout, open_timeout: open_timeout),
78
+ Transport
79
+ )
80
+ end
81
+ end
82
+ end
83
+ end