solana-ruby-web3js 1.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
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