erpc-sdk 0.4.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0b83f0a7e0881001a42033b8c7116ea6407f1e024cd258e9cf7df546eb0d875
4
- data.tar.gz: 48a8098b1739371b431ba2e534644002647ffff6fa4c57889cf0723ffeecfdc1
3
+ metadata.gz: 4152582a8b2cdb14a5b98a90761f07abf0ab2313540e5f46cef71d0111a6c531
4
+ data.tar.gz: 6f8d52e59d11fe4aa9d7d81af2941c65e59adfb98b5bade3a478f25c91d2bc52
5
5
  SHA512:
6
- metadata.gz: 6badad47dbad416e9cea863d849de82beefd7fbf9448acce21b9a146ebf4f13e1260aad6591b94bd33a71f21cb832640a66bb3767c8a40f3785ab2629d9f1a1c
7
- data.tar.gz: d9c6f52fbfc4ca654e8540640a50dc6fdda24773d6bb3ed54752cd8d6ba99fdf33db19a92522acdef95c5761aa9d4e702ff024f773fd2fff06196c523504eab3
6
+ metadata.gz: 8f14e31048992941c30e753cf0ad84e066063ec667b66d5718c4d442acf2e5e1202b9bb7d70c391f720f32554cb518cde25de19c0166da4c2722cbe2ac66b0bf
7
+ data.tar.gz: '094c74f0c2939dab9312171da51889a121b8a1f6baed947bc11942ebde119211373f785abf5d8d81b8b1f9cf4818e3b2b8d6bedfe9ace572a7ce6beab765b73a'
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # ERPC SDK for Ruby
2
2
 
3
- Synchronous Ruby client for ERPC. It covers standard Solana and Ethereum
4
- JSON-RPC, indexed data, analytics, WebSocket subscriptions, price REST and
5
- server-sent events, account usage, and scoped Cloud reads.
3
+ Synchronous Ruby client for ERPC. It covers standard Solana, Ethereum, and
4
+ Avalanche C/P/X-chain JSON-RPC, indexed data, analytics, WebSocket subscriptions,
5
+ price REST and server-sent events, account usage, and scoped Cloud reads.
6
6
 
7
7
  ```bash
8
8
  gem install erpc-sdk
@@ -21,7 +21,8 @@ begin
21
21
 
22
22
  slot = erpc.solana.rpc.get_slot.send
23
23
  chain_id = erpc.ethereum.rpc.eth_chain_id.send
24
- puts({ slot: slot, chain_id: chain_id })
24
+ avalanche_chain_id = erpc.avalanche.rpc.eth_chain_id.send
25
+ puts({ slot: slot, chain_id: chain_id, avalanche_chain_id: avalanche_chain_id })
25
26
  ensure
26
27
  erpc&.close
27
28
  end
@@ -44,10 +45,31 @@ only when `send` is called. `request` restricts calls to the namespace catalog;
44
45
  | `erpc.solana.subscriptions` | Enhanced WebSocket subscriptions |
45
46
  | `erpc.ethereum.rpc` | Standard Ethereum JSON-RPC |
46
47
  | `erpc.ethereum.subscriptions` | Ethereum WebSocket subscriptions |
48
+ | `erpc.avalanche.rpc` | Avalanche C-Chain EVM-compatible JSON-RPC |
49
+ | `erpc.avalanche.subscriptions` | Avalanche C-Chain WebSocket subscriptions |
50
+ | `erpc.avalanche.avax` | C-Chain AVAX atomic transaction API |
51
+ | `erpc.avalanche.x_chain` | X-Chain API |
52
+ | `erpc.avalanche.p_chain` | P-Chain API |
53
+ | `erpc.avalanche.proposer_vm` | P-Chain proposer VM API |
54
+ | `erpc.avalanche.info` | Network upgrade information |
55
+ | `erpc.avalanche.index` | C/P/X block and X transaction indexes |
47
56
  | `erpc.price` | Price metadata, updates, and SSE streams |
48
57
  | `erpc.account` | Token balance |
49
58
  | `erpc.usage` | Masked monthly API-key usage |
50
59
 
60
+ `avalanche_endpoint` defaults to `https://ava-rpc.erpc.global` and can be
61
+ overridden independently. The SDK uses its `/ava` HTTP route and `/ava-ws`
62
+ WebSocket route. Native methods use named Hash parameters and Index calls use
63
+ explicit chain/container paths. Native and Index batches are rejected locally;
64
+ use `raw` with an exact wire method name for forward compatibility.
65
+
66
+ ```ruby
67
+ p_height = erpc.avalanche.p_chain.get_height.send
68
+ indexed_tx = erpc.avalanche.index.x_chain_transactions
69
+ .get_container_by_id(id: transaction_id)
70
+ .send
71
+ ```
72
+
51
73
  ## Intact batches
52
74
 
53
75
  ```ruby
@@ -74,7 +96,7 @@ end
74
96
  ```
75
97
 
76
98
  Subscriptions use a lazy persistent WebSocket connection. `unsubscribe` is
77
- idempotent, and `close` closes both network subscription transports.
99
+ idempotent, and `close` closes all network subscription transports.
78
100
 
79
101
  ## Cloud reads
80
102
 
@@ -3,9 +3,27 @@
3
3
  module ERPC
4
4
  SolanaClient = Struct.new(:rpc, :das, :history, :leaders, :analytics, :subscriptions, keyword_init: true)
5
5
  EthereumClient = Struct.new(:rpc, :subscriptions, keyword_init: true)
6
+ AvalancheIndexClient = Struct.new(
7
+ :c_chain_blocks,
8
+ :p_chain_blocks,
9
+ :x_chain_blocks,
10
+ :x_chain_transactions,
11
+ keyword_init: true
12
+ )
13
+ AvalancheClient = Struct.new(
14
+ :rpc,
15
+ :avax,
16
+ :x_chain,
17
+ :p_chain,
18
+ :proposer_vm,
19
+ :info,
20
+ :index,
21
+ :subscriptions,
22
+ keyword_init: true
23
+ )
6
24
 
7
25
  class Client
8
- attr_reader :solana, :ethereum, :price, :account, :usage
26
+ attr_reader :solana, :ethereum, :avalanche, :price, :account, :usage
9
27
 
10
28
  def initialize(config, http_adapter: nil, websocket_factory: nil)
11
29
  adapter = http_adapter || NetHttpAdapter.new
@@ -23,6 +41,22 @@ module ERPC
23
41
  timeout: config.timeout,
24
42
  adapter: adapter
25
43
  )
44
+ avalanche_transport = HttpJsonRpcTransport.new(
45
+ api_key: config.api_key,
46
+ endpoint: URLs.with_path(config.avalanche_endpoint, "/ava"),
47
+ headers: config.headers,
48
+ timeout: config.timeout,
49
+ adapter: adapter
50
+ )
51
+ avalanche_index_transport = lambda do |path|
52
+ HttpJsonRpcTransport.new(
53
+ api_key: config.api_key,
54
+ endpoint: URLs.with_path(config.avalanche_endpoint, path),
55
+ headers: config.headers,
56
+ timeout: config.timeout,
57
+ adapter: adapter
58
+ )
59
+ end
26
60
  solana_ws = WebSocketJsonRpcTransport.new(
27
61
  URLs.websocket(config.endpoint, config.api_key),
28
62
  config.api_key,
@@ -35,6 +69,12 @@ module ERPC
35
69
  config.timeout,
36
70
  connection_factory: websocket_factory
37
71
  )
72
+ avalanche_ws = WebSocketJsonRpcTransport.new(
73
+ URLs.websocket(config.avalanche_endpoint, config.api_key, "/ava-ws"),
74
+ config.api_key,
75
+ config.timeout,
76
+ connection_factory: websocket_factory
77
+ )
38
78
 
39
79
  @solana = SolanaClient.new(
40
80
  rpc: RpcNamespace.new(
@@ -62,6 +102,41 @@ module ERPC
62
102
  rpc: RpcNamespace.new(ethereum_transport, ETHEREUM_RPC_METHODS, parameter_mode: :positional),
63
103
  subscriptions: EthereumSubscriptions.new(ethereum_ws)
64
104
  )
105
+ @avalanche = AvalancheClient.new(
106
+ rpc: RpcNamespace.new(avalanche_transport, ETHEREUM_RPC_METHODS, parameter_mode: :positional),
107
+ avax: avalanche_namespace(avalanche_transport, AVALANCHE_AVAX_METHODS, "avax"),
108
+ x_chain: avalanche_namespace(avalanche_transport, AVALANCHE_X_CHAIN_METHODS, "avm"),
109
+ p_chain: avalanche_namespace(avalanche_transport, AVALANCHE_P_CHAIN_METHODS, "platform"),
110
+ proposer_vm: avalanche_namespace(
111
+ avalanche_transport,
112
+ AVALANCHE_PROPOSER_VM_METHODS,
113
+ "proposervm"
114
+ ),
115
+ info: avalanche_namespace(avalanche_transport, AVALANCHE_INFO_METHODS, "info"),
116
+ index: AvalancheIndexClient.new(
117
+ c_chain_blocks: avalanche_namespace(
118
+ avalanche_index_transport.call("/ava/ext/index/C/block"),
119
+ AVALANCHE_INDEX_METHODS,
120
+ "index"
121
+ ),
122
+ p_chain_blocks: avalanche_namespace(
123
+ avalanche_index_transport.call("/ava/ext/index/P/block"),
124
+ AVALANCHE_INDEX_METHODS,
125
+ "index"
126
+ ),
127
+ x_chain_blocks: avalanche_namespace(
128
+ avalanche_index_transport.call("/ava/ext/index/X/block"),
129
+ AVALANCHE_INDEX_METHODS,
130
+ "index"
131
+ ),
132
+ x_chain_transactions: avalanche_namespace(
133
+ avalanche_index_transport.call("/ava/ext/index/X/tx"),
134
+ AVALANCHE_INDEX_METHODS,
135
+ "index"
136
+ )
137
+ ),
138
+ subscriptions: EthereumSubscriptions.new(avalanche_ws)
139
+ )
65
140
  @price = PriceClient.new(
66
141
  RestTransport.new(
67
142
  credential: config.api_key,
@@ -98,8 +173,21 @@ module ERPC
98
173
  @closed = true
99
174
  solana.subscriptions.close
100
175
  ethereum.subscriptions.close
176
+ avalanche.subscriptions.close
101
177
  nil
102
178
  end
179
+
180
+ private
181
+
182
+ def avalanche_namespace(transport, methods, prefix)
183
+ RpcNamespace.new(
184
+ transport,
185
+ methods,
186
+ parameter_mode: :named,
187
+ batch_policy: :unsupported,
188
+ method_prefix: prefix
189
+ )
190
+ end
103
191
  end
104
192
 
105
193
  class CloudClient
@@ -4,6 +4,7 @@ require "uri"
4
4
 
5
5
  module ERPC
6
6
  DEFAULT_ENDPOINT = "https://edge.erpc.global"
7
+ DEFAULT_AVALANCHE_ENDPOINT = "https://ava-rpc.erpc.global"
7
8
  DEFAULT_ACCOUNT_ENDPOINT = "https://solana-rpc.erpc.global"
8
9
  DEFAULT_USER_ENDPOINT = "https://user-api.erpc.global"
9
10
  DEFAULT_TIMEOUT = 30.0
@@ -56,15 +57,17 @@ module ERPC
56
57
  end
57
58
 
58
59
  class ClientConfig
59
- attr_reader :api_key, :endpoint, :account_endpoint, :user_endpoint, :headers, :timeout
60
+ attr_reader :api_key, :endpoint, :account_endpoint, :user_endpoint, :headers, :timeout, :avalanche_endpoint
60
61
 
61
62
  def initialize(api_key:, endpoint: DEFAULT_ENDPOINT, account_endpoint: DEFAULT_ACCOUNT_ENDPOINT,
62
- user_endpoint: DEFAULT_USER_ENDPOINT, headers: {}, timeout: DEFAULT_TIMEOUT)
63
+ user_endpoint: DEFAULT_USER_ENDPOINT, headers: {}, timeout: DEFAULT_TIMEOUT,
64
+ avalanche_endpoint: DEFAULT_AVALANCHE_ENDPOINT)
63
65
  @api_key = api_key.to_s.strip
64
66
  raise ConfigError, "api_key must not be empty" if @api_key.empty?
65
67
  raise ConfigError, "timeout must be positive" unless timeout.is_a?(Numeric) && timeout.finite? && timeout.positive?
66
68
 
67
69
  @endpoint = URLs.normalize_endpoint(endpoint)
70
+ @avalanche_endpoint = URLs.normalize_endpoint(avalanche_endpoint)
68
71
  @account_endpoint = URLs.normalize_endpoint(account_endpoint)
69
72
  @user_endpoint = URLs.normalize_endpoint(user_endpoint)
70
73
  @headers = headers.to_h.transform_keys(&:to_s).transform_values(&:to_s).freeze
@@ -73,6 +76,7 @@ module ERPC
73
76
 
74
77
  def inspect
75
78
  "#<#{self.class} api_key=[REDACTED] endpoint=#{endpoint.inspect} " \
79
+ "avalanche_endpoint=#{avalanche_endpoint.inspect} " \
76
80
  "account_endpoint=#{account_endpoint.inspect} user_endpoint=#{user_endpoint.inspect} " \
77
81
  "header_names=#{headers.keys.inspect} timeout=#{timeout.inspect}>"
78
82
  end
data/lib/erpc_sdk/rpc.rb CHANGED
@@ -162,6 +162,72 @@ module ERPC
162
162
  "eth_unsubscribe"
163
163
  ].freeze
164
164
 
165
+ AVALANCHE_AVAX_METHODS = [
166
+ "avax.getAtomicTx",
167
+ "avax.getAtomicTxStatus",
168
+ "avax.getUTXOs",
169
+ "avax.issueTx"
170
+ ].freeze
171
+
172
+ AVALANCHE_X_CHAIN_METHODS = [
173
+ "avm.buildGenesis",
174
+ "avm.getAllBalances",
175
+ "avm.getAssetDescription",
176
+ "avm.getBalance",
177
+ "avm.getBlockByHeight",
178
+ "avm.getHeight",
179
+ "avm.getTx",
180
+ "avm.getTxFee",
181
+ "avm.getTxStatus",
182
+ "avm.getUTXOs",
183
+ "avm.issueTx"
184
+ ].freeze
185
+
186
+ AVALANCHE_P_CHAIN_METHODS = [
187
+ "platform.getAllValidatorsAt",
188
+ "platform.getBalance",
189
+ "platform.getBlockchainStatus",
190
+ "platform.getBlockchains",
191
+ "platform.getCurrentSupply",
192
+ "platform.getCurrentValidators",
193
+ "platform.getFeeConfig",
194
+ "platform.getFeeState",
195
+ "platform.getHeight",
196
+ "platform.getMinStake",
197
+ "platform.getRewardUTXOs",
198
+ "platform.getStake",
199
+ "platform.getStakingAssetID",
200
+ "platform.getSubnets",
201
+ "platform.getTimestamp",
202
+ "platform.getTotalStake",
203
+ "platform.getTx",
204
+ "platform.getTxStatus",
205
+ "platform.getUTXOs",
206
+ "platform.getValidatorFeeConfig",
207
+ "platform.getValidatorFeeState",
208
+ "platform.getValidatorsAt",
209
+ "platform.issueTx",
210
+ "platform.sampleValidators",
211
+ "platform.validatedBy",
212
+ "platform.validates"
213
+ ].freeze
214
+
215
+ AVALANCHE_PROPOSER_VM_METHODS = [
216
+ "proposervm.getCurrentEpoch",
217
+ "proposervm.getProposedHeight"
218
+ ].freeze
219
+
220
+ AVALANCHE_INFO_METHODS = ["info.upgrades"].freeze
221
+
222
+ AVALANCHE_INDEX_METHODS = [
223
+ "index.getContainerByID",
224
+ "index.getContainerByIndex",
225
+ "index.getContainerRange",
226
+ "index.getIndex",
227
+ "index.getLastAccepted",
228
+ "index.isAccepted"
229
+ ].freeze
230
+
165
231
  HEAVY_SOLANA_METHODS = %w[
166
232
  getPriorityFeeEstimate
167
233
  getProgramAccounts
@@ -197,11 +263,16 @@ module ERPC
197
263
  class RpcNamespace
198
264
  attr_reader :endpoint
199
265
 
200
- def initialize(transport, methods, parameter_mode:, batch_policy: :any)
266
+ def initialize(transport, methods, parameter_mode:, batch_policy: :any, method_prefix: nil)
201
267
  @transport = transport
202
268
  @endpoint = transport.endpoint
203
- @methods = methods.to_h { |method| [method, true] }.freeze
204
- @aliases = methods.to_h { |method| [snake_case(method), method] }.freeze
269
+ prefix = method_prefix.nil? ? "" : "#{method_prefix}."
270
+ @wire_methods = methods.to_h do |method|
271
+ public_method = method.start_with?(prefix) ? method.delete_prefix(prefix) : method
272
+ [public_method, method]
273
+ end.freeze
274
+ @methods = @wire_methods.transform_values { true }.freeze
275
+ @aliases = @methods.to_h { |method, _| [snake_case(method), method] }.freeze
205
276
  @parameter_mode = parameter_mode
206
277
  @batch_policy = batch_policy
207
278
  end
@@ -211,7 +282,7 @@ module ERPC
211
282
  raise ConfigError, "#{method.inspect} is not in this namespace; use raw for forward-compatible methods"
212
283
  end
213
284
 
214
- PendingRpcRequest.new(@transport, method, params, decoder || ->(value) { value })
285
+ PendingRpcRequest.new(@transport, @wire_methods.fetch(method), params, decoder || ->(value) { value })
215
286
  end
216
287
 
217
288
  def raw(method, params = nil, decoder: nil)
@@ -222,7 +293,7 @@ module ERPC
222
293
 
223
294
  def batch(calls)
224
295
  if @batch_policy == :unsupported && !calls.empty?
225
- raise BatchPolicyError, "Leader RPC methods do not support batching"
296
+ raise BatchPolicyError, "This RPC namespace does not support batching"
226
297
  end
227
298
  if @batch_policy == :solana_standard
228
299
  methods = calls.map { |call| call[:method] || call["method"] }
@@ -233,7 +304,12 @@ module ERPC
233
304
  end
234
305
  end
235
306
 
236
- PendingRpcBatchRequest.new(@transport, calls)
307
+ wire_calls = if @wire_methods.any? { |method, wire| method != wire }
308
+ calls.map { |call| wire_call(call) }
309
+ else
310
+ calls
311
+ end
312
+ PendingRpcBatchRequest.new(@transport, wire_calls)
237
313
  end
238
314
 
239
315
  def method_missing(name, *arguments, &block)
@@ -262,6 +338,18 @@ module ERPC
262
338
 
263
339
  private
264
340
 
341
+ def wire_call(call)
342
+ method = call[:method] || call["method"]
343
+ unless @wire_methods.key?(method)
344
+ raise ConfigError, "#{method.inspect} is not in this namespace; use raw for forward-compatible methods"
345
+ end
346
+
347
+ mapped = { method: @wire_methods.fetch(method) }
348
+ mapped[:params] = call[:params] if call.key?(:params)
349
+ mapped[:params] = call["params"] if call.key?("params")
350
+ mapped
351
+ end
352
+
265
353
  def snake_case(value)
266
354
  value.gsub(/(?<=[a-z0-9])(?=[A-Z])/, "_").downcase
267
355
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ERPC
4
- VERSION = "0.4.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erpc-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ELSOUL LABO B.V.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-29 00:00:00.000000000 Z
11
+ date: 2026-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -50,8 +50,8 @@ dependencies:
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '14'
53
- description: Ruby client for ERPC JSON-RPC, REST, streams, subscriptions, and Cloud
54
- reads.
53
+ description: Ruby client for ERPC Solana, Ethereum, and Avalanche RPC, REST, streams,
54
+ subscriptions, and Cloud reads.
55
55
  email:
56
56
  executables: []
57
57
  extensions: []
@@ -96,5 +96,5 @@ requirements: []
96
96
  rubygems_version: 3.3.27
97
97
  signing_key:
98
98
  specification_version: 4
99
- summary: Ruby SDK for ERPC
99
+ summary: Multi-network Ruby SDK for ERPC
100
100
  test_files: []