solrengine-rpc 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6153fbe152baa967ab457bd1cb90c16b978c9e270bdff09e087f2f85298fde6d
4
+ data.tar.gz: b7a4c693fae301f5c2faf511507a1864e3bdc7e280f1331c426739fd03428efa
5
+ SHA512:
6
+ metadata.gz: 9e9192a9993b141904b5af5d539c0dd4a220d969ebdd075778c006cfab8826e9bbdba5e3b61a9b83ce89892d7bd5090c38356e4b60a0fcd8df47e54f3f40ccf0
7
+ data.tar.gz: ebac91864b9821fc2a1b3283b4a5ec60dea5cea930b9a4a676ec04312081ebd127cf48283a63097a89139ef1a588ec55bdb4199f59beb418cbc01e6715f69706
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # SolRengine RPC
2
+
3
+ Solana JSON-RPC client for Ruby. Handles balance queries, token accounts, signatures, transactions, and blockhash — with SSL CRL tolerance and multi-network support.
4
+
5
+ Part of the [SolRengine](https://github.com/solrengine) framework.
6
+
7
+ ## Install
8
+
9
+ ```ruby
10
+ gem "solrengine-rpc"
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ruby
16
+ client = Solrengine::Rpc.client
17
+
18
+ client.get_balance("Abc...xyz") # => 2.5 (SOL)
19
+ client.get_token_accounts("Abc...xyz") # => [{ mint:, ui_amount:, ... }]
20
+ client.get_recent_signatures("Abc...xyz") # => [{ signature:, block_time:, ... }]
21
+ client.get_latest_blockhash # => "abc123..."
22
+ client.get_signature_status("sig...") # => { "confirmationStatus" => "finalized" }
23
+ client.get_transaction("sig...") # => { full tx details }
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ ```ruby
29
+ Solrengine::Rpc.configure do |config|
30
+ config.network = "mainnet" # or "devnet", "testnet"
31
+ config.rpc_url = "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY"
32
+ config.ws_url = "wss://mainnet.helius-rpc.com/?api-key=YOUR_KEY"
33
+ end
34
+ ```
35
+
36
+ Or via environment variables:
37
+
38
+ ```
39
+ SOLANA_NETWORK=devnet
40
+ SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=xxx
41
+ SOLANA_WS_URL=wss://mainnet.helius-rpc.com/?api-key=xxx
42
+ SOLANA_RPC_DEVNET_URL=https://devnet.helius-rpc.com/?api-key=xxx
43
+ ```
44
+
45
+ In a Rails app, you can also use `config/solana.yml` — the engine loads it automatically.
46
+
47
+ ## License
48
+
49
+ MIT
@@ -0,0 +1,122 @@
1
+ require "json"
2
+
3
+ module Solrengine
4
+ module Rpc
5
+ class Client
6
+ include SslHttp
7
+
8
+ def initialize(rpc_url: nil)
9
+ @rpc_url = rpc_url || Solrengine::Rpc.configuration.rpc_url
10
+ end
11
+
12
+ def get_balance(wallet_address, commitment: "confirmed")
13
+ result = rpc_request("getBalance", [ wallet_address, { "commitment" => commitment } ])
14
+ lamports = result.dig("result", "value")
15
+ return nil unless lamports
16
+
17
+ lamports.to_f / 1_000_000_000
18
+ end
19
+
20
+ def get_token_accounts(wallet_address, program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
21
+ result = rpc_request("getTokenAccountsByOwner", [
22
+ wallet_address,
23
+ { "programId" => program_id },
24
+ { "encoding" => "jsonParsed" }
25
+ ])
26
+
27
+ accounts = result.dig("result", "value") || []
28
+
29
+ accounts.filter_map do |account|
30
+ info = account.dig("account", "data", "parsed", "info")
31
+ next unless info
32
+
33
+ token_amount = info["tokenAmount"]
34
+ amount = token_amount["uiAmount"].to_f
35
+ next if amount.zero?
36
+
37
+ {
38
+ mint: info["mint"],
39
+ balance: token_amount["amount"],
40
+ decimals: token_amount["decimals"],
41
+ ui_amount: amount,
42
+ ui_amount_string: token_amount["uiAmountString"]
43
+ }
44
+ end
45
+ end
46
+
47
+ def get_recent_signatures(wallet_address, limit: 10)
48
+ result = rpc_request("getSignaturesForAddress", [
49
+ wallet_address,
50
+ { "limit" => limit }
51
+ ])
52
+
53
+ signatures = result.dig("result") || []
54
+
55
+ signatures.map do |sig|
56
+ {
57
+ signature: sig["signature"],
58
+ slot: sig["slot"],
59
+ block_time: sig["blockTime"] ? Time.at(sig["blockTime"]) : nil,
60
+ error: sig["err"],
61
+ memo: sig["memo"],
62
+ confirmation_status: sig["confirmationStatus"]
63
+ }
64
+ end
65
+ end
66
+
67
+ def get_latest_blockhash(commitment: "finalized")
68
+ result = rpc_request("getLatestBlockhash", [ { "commitment" => commitment } ])
69
+ value = result.dig("result", "value")
70
+ return nil unless value
71
+
72
+ {
73
+ blockhash: value["blockhash"],
74
+ last_valid_block_height: value["lastValidBlockHeight"]
75
+ }
76
+ end
77
+
78
+ def get_signature_status(signature)
79
+ result = rpc_request("getSignatureStatuses", [ [ signature ] ])
80
+ result.dig("result", "value", 0)
81
+ end
82
+
83
+ def get_transaction(signature)
84
+ result = rpc_request("getTransaction", [
85
+ signature,
86
+ { "encoding" => "jsonParsed", "maxSupportedTransactionVersion" => 0 }
87
+ ])
88
+
89
+ result["result"]
90
+ end
91
+
92
+ # Generic RPC call for methods not covered above
93
+ def request(method, params = [])
94
+ rpc_request(method, params)
95
+ end
96
+
97
+ private
98
+
99
+ def rpc_request(method, params = [])
100
+ uri = URI.parse(@rpc_url)
101
+ http = ssl_http(uri)
102
+
103
+ request = Net::HTTP::Post.new(uri.request_uri.empty? ? "/" : uri.request_uri)
104
+ request["Content-Type"] = "application/json"
105
+ request.body = {
106
+ jsonrpc: "2.0",
107
+ id: 1,
108
+ method: method,
109
+ params: params
110
+ }.to_json
111
+
112
+ response = http.request(request)
113
+ JSON.parse(response.body)
114
+ rescue => e
115
+ if defined?(Rails)
116
+ Rails.logger.error("Solrengine RPC error: #{e.class} - #{e.message}")
117
+ end
118
+ {}
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,63 @@
1
+ module Solrengine
2
+ module Rpc
3
+ class Configuration
4
+ NETWORKS = %w[mainnet devnet testnet].freeze
5
+
6
+ DEFAULT_RPC_URLS = {
7
+ "mainnet" => "https://api.mainnet-beta.solana.com",
8
+ "devnet" => "https://api.devnet.solana.com",
9
+ "testnet" => "https://api.testnet.solana.com"
10
+ }.freeze
11
+
12
+ DEFAULT_WS_URLS = {
13
+ "mainnet" => "wss://api.mainnet-beta.solana.com",
14
+ "devnet" => "wss://api.devnet.solana.com",
15
+ "testnet" => "wss://api.testnet.solana.com"
16
+ }.freeze
17
+
18
+ attr_writer :network, :rpc_url, :ws_url
19
+
20
+ def network
21
+ @network ||= ENV.fetch("SOLANA_NETWORK", "mainnet")
22
+ end
23
+
24
+ def rpc_url
25
+ @rpc_url ||= env_rpc_url || DEFAULT_RPC_URLS[network]
26
+ end
27
+
28
+ def ws_url
29
+ @ws_url ||= env_ws_url || DEFAULT_WS_URLS[network]
30
+ end
31
+
32
+ def mainnet?
33
+ network == "mainnet"
34
+ end
35
+
36
+ def explorer_base
37
+ mainnet? ? "https://solscan.io" : "https://explorer.solana.com"
38
+ end
39
+
40
+ def explorer_cluster
41
+ mainnet? ? "" : "?cluster=#{network}"
42
+ end
43
+
44
+ private
45
+
46
+ def env_rpc_url
47
+ case network
48
+ when "mainnet" then ENV["SOLANA_RPC_URL"]
49
+ when "devnet" then ENV["SOLANA_RPC_DEVNET_URL"]
50
+ when "testnet" then ENV["SOLANA_RPC_TESTNET_URL"]
51
+ end
52
+ end
53
+
54
+ def env_ws_url
55
+ case network
56
+ when "mainnet" then ENV["SOLANA_WS_URL"]
57
+ when "devnet" then ENV["SOLANA_WS_DEVNET_URL"]
58
+ when "testnet" then ENV["SOLANA_WS_TESTNET_URL"]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ module Solrengine
2
+ module Rpc
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Solrengine::Rpc
5
+
6
+ initializer "solrengine-rpc.configure" do
7
+ # Load config from solana.yml if it exists and no explicit config was set
8
+ config_path = Rails.root.join("config", "solana.yml")
9
+ if config_path.exist?
10
+ solana_config = Rails.application.config_for(:solana).deep_stringify_keys
11
+ cfg = Solrengine::Rpc.configuration
12
+ cfg.network = solana_config["network"] if solana_config["network"]
13
+
14
+ network_config = solana_config.dig("networks", cfg.network)
15
+ if network_config
16
+ cfg.rpc_url = network_config["rpc_url"] if network_config["rpc_url"]
17
+ cfg.ws_url = network_config["ws_url"] if network_config["ws_url"]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ require "net/http"
2
+
3
+ module Solrengine
4
+ module Rpc
5
+ # HTTP helper that tolerates SSL certificates with unavailable CRLs.
6
+ # Some Solana and Jupiter endpoints have certs that Ruby's OpenSSL
7
+ # rejects due to missing Certificate Revocation Lists.
8
+ module SslHttp
9
+ private
10
+
11
+ def ssl_http(uri)
12
+ http = Net::HTTP.new(uri.host, uri.port)
13
+ http.use_ssl = (uri.scheme == "https")
14
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
15
+ http.verify_callback = ->(_preverify_ok, store_ctx) {
16
+ return true if store_ctx.error == 0
17
+ return true if store_ctx.error == OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL
18
+ false
19
+ }
20
+ http.open_timeout = 10
21
+ http.read_timeout = 10
22
+ http
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solrengine
4
+ module Rpc
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "rpc/version"
2
+ require_relative "rpc/configuration"
3
+ require_relative "rpc/ssl_http"
4
+ require_relative "rpc/client"
5
+ require_relative "rpc/engine" if defined?(Rails::Engine)
6
+
7
+ module Solrengine
8
+ module Rpc
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+ def configuration
13
+ @configuration ||= Configuration.new
14
+ end
15
+
16
+ def configure
17
+ yield(configuration)
18
+ end
19
+
20
+ def client(rpc_url: nil)
21
+ Client.new(rpc_url: rpc_url || configuration.rpc_url)
22
+ end
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solrengine-rpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jose Ferrer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ description: Ruby client for Solana's JSON-RPC API. Handles balance, token accounts,
28
+ signatures, transactions, and blockhash queries. Includes SSL CRL workaround and
29
+ multi-network configuration.
30
+ email:
31
+ - estoy@moviendo.me
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - README.md
37
+ - lib/solrengine/rpc.rb
38
+ - lib/solrengine/rpc/client.rb
39
+ - lib/solrengine/rpc/configuration.rb
40
+ - lib/solrengine/rpc/engine.rb
41
+ - lib/solrengine/rpc/ssl_http.rb
42
+ - lib/solrengine/rpc/version.rb
43
+ homepage: https://github.com/solrengine/rpc
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://github.com/solrengine/rpc
48
+ source_code_uri: https://github.com/solrengine/rpc
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 3.2.0
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.5.22
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Solana JSON-RPC client for Ruby with SSL CRL fix and network config
68
+ test_files: []