solana-ruby-web3js 1.0.1.beta1

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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +13 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +17 -0
  9. data/Gemfile.lock +184 -0
  10. data/README.md +273 -0
  11. data/Rakefile +8 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/lib/.DS_Store +0 -0
  15. data/lib/solana_ruby/.DS_Store +0 -0
  16. data/lib/solana_ruby/base_client.rb +46 -0
  17. data/lib/solana_ruby/http_client.rb +42 -0
  18. data/lib/solana_ruby/http_methods/account_methods.rb +98 -0
  19. data/lib/solana_ruby/http_methods/basic_methods.rb +108 -0
  20. data/lib/solana_ruby/http_methods/block_methods.rb +89 -0
  21. data/lib/solana_ruby/http_methods/blockhash_methods.rb +30 -0
  22. data/lib/solana_ruby/http_methods/lookup_table_methods.rb +55 -0
  23. data/lib/solana_ruby/http_methods/signature_methods.rb +30 -0
  24. data/lib/solana_ruby/http_methods/slot_methods.rb +44 -0
  25. data/lib/solana_ruby/http_methods/token_methods.rb +32 -0
  26. data/lib/solana_ruby/http_methods/transaction_methods.rb +85 -0
  27. data/lib/solana_ruby/version.rb +5 -0
  28. data/lib/solana_ruby/web_socket_client.rb +93 -0
  29. data/lib/solana_ruby/web_socket_handlers.rb +34 -0
  30. data/lib/solana_ruby/web_socket_methods/account_methods.rb +37 -0
  31. data/lib/solana_ruby/web_socket_methods/log_methods.rb +27 -0
  32. data/lib/solana_ruby/web_socket_methods/root_methods.rb +16 -0
  33. data/lib/solana_ruby/web_socket_methods/signature_methods.rb +25 -0
  34. data/lib/solana_ruby/web_socket_methods/slot_methods.rb +20 -0
  35. data/lib/solana_ruby.rb +10 -0
  36. metadata +136 -0
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+ require 'base64'
7
+ require 'base58'
8
+ require_relative 'base_client'
9
+ Dir[File.join(__dir__, 'http_methods', '*.rb')].each { |file| require file }
10
+
11
+ module SolanaRuby
12
+ class HttpClient < BaseClient
13
+ [HttpMethods::BasicMethods, HttpMethods::LookupTableMethods, HttpMethods::TransactionMethods,
14
+ HttpMethods::SignatureMethods, HttpMethods::BlockhashMethods, HttpMethods::BlockMethods,
15
+ HttpMethods::AccountMethods, HttpMethods::TokenMethods, HttpMethods::SlotMethods].each do |mod|
16
+ include mod
17
+ end
18
+ BASE_URL = 'https://api.mainnet-beta.solana.com'
19
+
20
+ def initialize(endpoint = BASE_URL)
21
+ @uri = URI.parse(endpoint)
22
+ end
23
+
24
+ def request(method, params = [])
25
+ http = Net::HTTP.new(@uri.host, @uri.port)
26
+ http.use_ssl = true
27
+
28
+ request = Net::HTTP::Post.new(@uri.request_uri, {'Content-Type' => 'application/json'})
29
+ request.body = {
30
+ jsonrpc: '2.0',
31
+ id: 1,
32
+ method: method,
33
+ params: params
34
+ }.to_json
35
+
36
+ response = http.request(request)
37
+ handle_http_response(response)
38
+ rescue StandardError => e
39
+ handle_error(e)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Account Related HTTP Methods
6
+ module AccountMethods
7
+ ENCODING_JSON_OPTIONS = { encoding: "jsonParsed", commitment: "finalized" }.freeze
8
+ FINALIZED_OPTIONS = { commitment: "finalized" }.freeze
9
+ ENCODING_BASE58_OPTIONS = { encoding: "base58" }.freeze
10
+
11
+ def get_account_info(pubkey)
12
+ account_info = get_account_info_and_context(pubkey)
13
+ account_info["value"]
14
+ end
15
+
16
+ def get_parsed_account_info(pubkey, options = ENCODING_JSON_OPTIONS)
17
+ get_account_info_and_context(pubkey, options)
18
+ end
19
+
20
+ def get_account_info_and_context(pubkey, options = {})
21
+ account_info = request("getAccountInfo", [pubkey, options])
22
+ account_info["result"]
23
+ end
24
+
25
+ def get_multiple_account_info(pubkeys, options = ENCODING_BASE58_OPTIONS)
26
+ accounts_info = get_multiple_account_info_and_context(pubkeys, options)
27
+ accounts_info["value"]
28
+ end
29
+
30
+ def get_multiple_account_info_and_context(pubkeys, options = ENCODING_BASE58_OPTIONS)
31
+ params = [pubkeys, options]
32
+ accounts_info = request("getMultipleAccounts", params)
33
+ accounts_info["result"]
34
+ end
35
+
36
+ def get_multiple_parsed_accounts(pubkeys, options = ENCODING_JSON_OPTIONS)
37
+ get_multiple_account_info_and_context(pubkeys, options)
38
+ end
39
+
40
+ def get_largest_accounts(options = ENCODING_BASE58_OPTIONS.merge(FINALIZED_OPTIONS))
41
+ account_info = request("getLargestAccounts", [options])
42
+ account_info["result"]
43
+ end
44
+
45
+ def get_program_accounts(program_id, options = FINALIZED_OPTIONS)
46
+ params = [program_id, options]
47
+ account_info = request("getProgramAccounts", params)
48
+ account_info["result"]
49
+ end
50
+
51
+ def get_parsed_program_accounts(program_id, options = ENCODING_JSON_OPTIONS)
52
+ get_program_accounts(program_id, options)
53
+ end
54
+
55
+ def get_vote_accounts(options = FINALIZED_OPTIONS)
56
+ account_info = request("getVoteAccounts", [options])
57
+ account_info["result"]
58
+ end
59
+
60
+ def get_parsed_token_accounts_by_owner(owner_pubkey, filters = {}, options = ENCODING_JSON_OPTIONS)
61
+ params = [owner_pubkey, filters, options]
62
+ parsed_token_accounts = request("getTokenAccountsByOwner", params)
63
+ parsed_token_accounts["result"]
64
+ end
65
+
66
+ def get_nonce_and_context(pubkey)
67
+ account_info_and_context = get_account_info_and_context(pubkey)
68
+ unless account_info_and_context["value"]["owner"] == "11111111111111111111111111111111"
69
+ raise "Provided account is not a nonce account"
70
+ end
71
+
72
+ data = account_info_and_context["value"]["data"][0]
73
+ raise "Nonce account data is empty" if data.nil? || data.empty?
74
+
75
+ decoded_data = Base64.decode64(data)
76
+ nonce_info = parse_nonce_account(decoded_data)
77
+ {
78
+ context: account_info_and_context["context"],
79
+ value: nonce_info
80
+ }
81
+ end
82
+
83
+ def get_nonce(pubkey)
84
+ nonce_info = get_nonce_and_context(pubkey)
85
+ nonce_info[:value]
86
+ end
87
+
88
+ private
89
+
90
+ def parse_nonce_account(data)
91
+ {
92
+ blockhash: data[4, 32].unpack1("H*"),
93
+ fee_calculator: { lamports_per_signature: data[36, 8].unpack1("Q<") }
94
+ }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Basic Methods
6
+ module BasicMethods
7
+ FINALIZED_OPTIONS = { commitment: "finalized" }.freeze
8
+
9
+ def get_balance(pubkey)
10
+ balance_info = request("getBalance", [pubkey])
11
+ balance_info["result"]["value"]
12
+ end
13
+
14
+ def get_balance_and_context(pubkey)
15
+ balance_info = request("getBalance", [pubkey])
16
+ balance_info["result"]
17
+ end
18
+
19
+ def get_epoch_info(options = FINALIZED_OPTIONS)
20
+ epoch_info = request("getEpochInfo", [options])
21
+ epoch_info["result"]
22
+ end
23
+
24
+ def get_epoch_schedule
25
+ epoch_schedule = request("getEpochSchedule")
26
+ epoch_schedule["result"]
27
+ end
28
+
29
+ def get_genesis_hash
30
+ genesis_hash = request("getGenesisHash")
31
+ genesis_hash["result"]
32
+ end
33
+
34
+ def get_inflation_governor
35
+ inflation_governor = request("getInflationGovernor")
36
+ inflation_governor["result"]
37
+ end
38
+
39
+ def get_inflation_rate
40
+ inflation_rate = request("getInflationRate")
41
+ inflation_rate["result"]
42
+ end
43
+
44
+ def get_inflation_reward(addresses, options = {})
45
+ params = [addresses, options]
46
+ request("getInflationReward", params)
47
+ end
48
+
49
+ def get_leader_schedule(options = { epoch: nil })
50
+ leader_schedule = request("getLeaderSchedule", [options])
51
+ leader_schedule["result"]
52
+ end
53
+
54
+ def get_minimum_balance_for_rent_exemption(account_data_size, options = FINALIZED_OPTIONS)
55
+ params = [account_data_size, options]
56
+ minimum_balance_for_rent_exemption = request("getMinimumBalanceForRentExemption", params)
57
+ minimum_balance_for_rent_exemption["result"]
58
+ end
59
+
60
+ def get_stake_activation(account_pubkey, options = FINALIZED_OPTIONS.merge(epoch: nil))
61
+ params = [account_pubkey, options]
62
+ stake_activation = request("getStakeActivation", params)
63
+ stake_activation["result"]
64
+ end
65
+
66
+ def get_stake_minimum_delegation(options = FINALIZED_OPTIONS)
67
+ stake_minimum_delagation = request("getStakeMinimumDelegation", [FINALIZED_OPTIONS])
68
+ stake_minimum_delagation["result"]
69
+ end
70
+
71
+ def get_supply(options = FINALIZED_OPTIONS)
72
+ supply_info = request("getSupply", [options])
73
+ supply_info["result"]
74
+ end
75
+
76
+ def get_version
77
+ version_info = request("getVersion")
78
+ version_info["result"]
79
+ end
80
+
81
+ def get_total_supply(options = FINALIZED_OPTIONS)
82
+ supply_info = get_supply(options)
83
+ supply_info["value"]["total"]
84
+ end
85
+
86
+ def get_health
87
+ health_info = request("getHealth")
88
+ health_info["result"]
89
+ end
90
+
91
+ def get_identity
92
+ health_info = request("getIdentity")
93
+ health_info["result"]
94
+ end
95
+
96
+ def get_recent_performance_samples(limit = 10)
97
+ performance_samples = request("getRecentPerformanceSamples", [limit])
98
+ performance_samples["result"]
99
+ end
100
+
101
+ def get_recent_prioritization_fees(addresses)
102
+ params = [addresses]
103
+ prioritization_fees = request("getRecentPrioritizationFees", params)
104
+ prioritization_fees["result"]
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Block Related HTTP Methods
6
+ module BlockMethods
7
+ DEFAULT_OPTIONS = { maxSupportedTransactionVersion: 0 }.freeze
8
+
9
+ def get_blocks(start_slot, end_slot)
10
+ params = [start_slot, end_slot]
11
+ block_info = request("getBlocks", params)
12
+ block_info["result"]
13
+ end
14
+
15
+ def get_block(slot, options = DEFAULT_OPTIONS)
16
+ params = [slot, options]
17
+ block_info = request("getBlock", params)
18
+ block_info["result"]
19
+ end
20
+
21
+ def get_block_production
22
+ request("getBlockProduction")
23
+ end
24
+
25
+ def get_block_time(slot)
26
+ block_info = request("getBlockTime", [slot])
27
+ block_info["result"]
28
+ end
29
+
30
+ def get_block_signatures(slot, options = DEFAULT_OPTIONS)
31
+ block_info = get_block(slot, options)
32
+ block_signatures(block_info)
33
+ end
34
+
35
+ def get_cluster_nodes
36
+ cluster_nodes_info = request("getClusterNodes")
37
+ cluster_nodes_info["result"]
38
+ end
39
+
40
+ def get_confirmed_block(slot, options = DEFAULT_OPTIONS)
41
+ block_info = get_block(slot, options)
42
+ block_info["result"]
43
+ end
44
+
45
+ def get_confirmed_block_signatures(slot)
46
+ block_info = get_confirmed_block(slot)
47
+ block_signatures(block_info)
48
+ end
49
+
50
+ def get_parsed_block(slot, options = {})
51
+ params = [slot, { encoding: "jsonParsed", transactionDetails: "full" }.merge(options)]
52
+ result = request("getBlock", params)
53
+ result["result"]
54
+ end
55
+
56
+ def get_first_available_block
57
+ result = request("getFirstAvailableBlock")
58
+ result["result"]
59
+ end
60
+
61
+ def get_blocks_with_limit(start_slot, limit)
62
+ params = [start_slot, limit]
63
+ response = request("getBlocksWithLimit", params)
64
+ response["result"]
65
+ end
66
+
67
+ def get_block_height
68
+ block_height = request("getBlockHeight")
69
+ block_height["result"]
70
+ end
71
+
72
+ def get_block_commitment(block_slot)
73
+ block_commitment = request("getBlockCommitment", [block_slot])
74
+ block_commitment["result"]
75
+ end
76
+
77
+ private
78
+
79
+ def block_signatures(block_info)
80
+ signatures = block_info["transactions"][0]["transaction"]["signatures"]
81
+ block_info.delete("transactions")
82
+ block_info.delete("rewards")
83
+ block_info.merge({
84
+ signatures: signatures
85
+ })
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Blockhash Related HTTP Methods
6
+ module BlockhashMethods
7
+ def get_latest_blockhash
8
+ recent_blockhash_info = get_latest_blockhash_and_context
9
+ recent_blockhash_info["value"]
10
+ end
11
+
12
+ def get_latest_blockhash_and_context
13
+ recent_blockhash_info = request("getLatestBlockhash")
14
+ recent_blockhash_info["result"]
15
+ end
16
+
17
+ def get_fee_for_message(blockhash, options = { commitment: "processed" })
18
+ params = [blockhash, options]
19
+ fee_for_blockhash_info = request("getFeeForMessage", params)
20
+ fee_for_blockhash_info["result"]
21
+ end
22
+
23
+ def is_blockhash_valid?(blockhash, options = { commitment: "processed" })
24
+ params = [blockhash, options]
25
+ blockhash_info = request("isBlockhashValid", params)
26
+ blockhash_info["result"]["value"]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Lookup Table Related HTTP Methods
6
+ module LookupTableMethods
7
+ def get_address_lookup_table(pubkey)
8
+ response = get_account_info_and_context(pubkey)
9
+
10
+ # Handle the response to ensure the account is a valid Address Lookup Table
11
+ unless response && response["value"]
12
+ raise SolanaError.new("Address Lookup Table not found or invalid account data.")
13
+ end
14
+
15
+ account_data = response["value"]["data"]
16
+
17
+ # Decode the account data
18
+ decode_lookup_table_data(Base64.decode64(account_data))
19
+
20
+ # Return the parsed lookup table details
21
+ end
22
+
23
+ private
24
+
25
+ def decode_lookup_table_data(data)
26
+ lookup_table_state = {}
27
+
28
+ lookup_table_state[:last_extended_slot],
29
+ lookup_table_state[:last_extended_block_height],
30
+ deactivation_slot = data[0, 24].unpack("Q<Q<Q<")
31
+
32
+ lookup_table_state[:deactivation_slot] = deactivation_slot == 0xFFFFFFFFFFFFFFFF ? nil : deactivation_slot
33
+
34
+ authority_offset = 24
35
+ addresses_offset = authority_offset + 32
36
+
37
+ authority_key = data[authority_offset, 32]
38
+ lookup_table_state[:authority] = if authority_key == ("\x00" * 32)
39
+ nil
40
+ else
41
+ Base58.binary_to_base58(authority_key, :bitcoin)
42
+ end
43
+
44
+ addresses_data = data[addresses_offset..-1]
45
+ address_count = addresses_data.size / 32
46
+ lookup_table_state[:addresses] = address_count.times.map do |i|
47
+ address_data = addresses_data[i * 32, 32]
48
+ Base58.binary_to_base58(address_data, :bitcoin)
49
+ end
50
+
51
+ lookup_table_state
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Signature Related Web Socket Methods
6
+ module SignatureMethods
7
+ def get_signature_statuses(signatures, options = {})
8
+ params = [signatures, options]
9
+ signature_request("getSignatureStatuses", params)
10
+ end
11
+
12
+ def get_signature_status(signature, options = {})
13
+ signature_status = get_signature_statuses([signature], options)
14
+ signature_status["value"].first
15
+ end
16
+
17
+ def get_signatures_for_address(address, options = {})
18
+ params = [address, options]
19
+ signature_request("getSignaturesForAddress", params)
20
+ end
21
+
22
+ private
23
+
24
+ def signature_request(method, params)
25
+ signatures_info = request(method, params)
26
+ signatures_info["result"]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Slot Related HTTP Methods
6
+ module SlotMethods
7
+ def get_slot
8
+ slot_info = request("getSlot")
9
+ slot_info["result"]
10
+ end
11
+
12
+ def get_slot_leader(options = {})
13
+ slot_leader = request("getSlotLeader", [options])
14
+ slot_leader["result"]
15
+ end
16
+
17
+ def get_slot_leaders(start_slot, limit)
18
+ params = [start_slot, limit]
19
+ slot_leaders = request("getSlotLeaders", params)
20
+ slot_leaders["result"]
21
+ end
22
+
23
+ def get_highest_snapshot_slot
24
+ slot_leaders = request("getHighestSnapshotSlot")
25
+ slot_leaders["result"]
26
+ end
27
+
28
+ def get_minimum_ledger_slot
29
+ minimum_ladger_slot = request("minimumLedgerSlot")
30
+ minimum_ladger_slot["result"]
31
+ end
32
+
33
+ def get_max_retransmit_slot
34
+ max_retransmit_slot = request("getMaxRetransmitSlot")
35
+ max_retransmit_slot["result"]
36
+ end
37
+
38
+ def get_max_shred_insert_slot
39
+ max_shred_insert_slot = request("getMaxShredInsertSlot")
40
+ max_shred_insert_slot["result"]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Token Related HTTP Methods
6
+ module TokenMethods
7
+ FINALIZED_OPTIONS = { commitment: "finalized" }.freeze
8
+
9
+ def get_token_balance(pubkey, options = FINALIZED_OPTIONS)
10
+ balance_info = request("getTokenAccountBalance", [pubkey, options])
11
+ balance_info["result"]["value"]
12
+ end
13
+
14
+ def get_token_supply(pubkey)
15
+ balance_info = request("getTokenSupply", [pubkey])
16
+ balance_info["result"]["value"]
17
+ end
18
+
19
+ def get_token_accounts_by_owner(owner_pubkey, filters = {}, options = {})
20
+ params = [owner_pubkey, filters, options]
21
+ response = request("getTokenAccountsByOwner", params)
22
+ response["result"]
23
+ end
24
+
25
+ def get_token_largest_accounts(mint_pubkey, options = {})
26
+ params = [mint_pubkey, options]
27
+ response = request("getTokenLargestAccounts", params)
28
+ response["result"]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ module HttpMethods
5
+ # Transaction Related HTTP Methods
6
+ module TransactionMethods
7
+ DEFAULT_COMMITMENT = "finalized"
8
+ TIMEOUT = 60 # seconds
9
+ RETRY_INTERVAL = 2 # seconds
10
+ ENCODED_TRANSACTION_OPTIONS = { skipPreflight: false }.freeze
11
+ FINALIZED_OPTIONS = { commitment: "finalized" }.freeze
12
+
13
+ def send_transaction(signed_transaction, options = {})
14
+ params = [signed_transaction, options]
15
+ result = request("sendTransaction", params)
16
+ result["result"]
17
+ end
18
+
19
+ def confirm_transaction(signature, commitment = DEFAULT_COMMITMENT, timeout = TIMEOUT)
20
+ start_time = Time.now
21
+
22
+ loop do
23
+ # Fetch transaction status
24
+ options = { searchTransactionHistory: true }
25
+ status_info = get_signature_status(signature, options)
26
+
27
+ # Check if the transaction is confirmed based on the commitment level
28
+ if status_info && (status_info["confirmationStatus"] == commitment || status_info["confirmationStatus"] == "confirmed")
29
+ return true
30
+ end
31
+
32
+ # Break the loop if timeout is reached
33
+ if Time.now - start_time > timeout
34
+ raise "Transaction #{signature} was not confirmed within #{timeout} seconds."
35
+ end
36
+
37
+ sleep(RETRY_INTERVAL)
38
+ end
39
+ end
40
+
41
+ def get_transaction(signature, options = {})
42
+ params = [signature, options]
43
+ response = request("getTransaction", params)
44
+ response["result"]
45
+ end
46
+
47
+ def get_transaction_count(options = FINALIZED_OPTIONS)
48
+ result = request("getTransactionCount", [options])
49
+ result["result"]
50
+ end
51
+
52
+ def get_transactions(signatures, options = FINALIZED_OPTIONS)
53
+ transactions = []
54
+ signatures.each do |signature|
55
+ transaction = get_transaction(signature, options)
56
+ transactions << transaction
57
+ end
58
+ transactions
59
+ end
60
+
61
+ def request_airdrop(pubkey, lamports, options = FINALIZED_OPTIONS)
62
+ params = [pubkey, lamports, options]
63
+ response = request("requestAirdrop", params)
64
+ response["result"]
65
+ end
66
+
67
+ def simulate_transaction(transaction, options = { encoding: "base64" })
68
+ params = [transaction, options]
69
+ response = request("simulateTransaction", params)
70
+ response["result"]
71
+ end
72
+
73
+ def send_encoded_transaction(encoded_transaction, options = ENCODED_TRANSACTION_OPTIONS.merge(FINALIZED_OPTIONS))
74
+ send_transaction(encoded_transaction, options)
75
+ end
76
+
77
+ def send_raw_transaction(raw_transaction, options = ENCODED_TRANSACTION_OPTIONS.merge(FINALIZED_OPTIONS))
78
+ # Convert the raw hexadecimal transaction to base64 encoding
79
+ base64_encoded_transaction = Base64.encode64(raw_transaction)
80
+
81
+ send_transaction(base64_encoded_transaction, options)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolanaRuby
4
+ VERSION = "1.0.1.beta1"
5
+ end