erpc-sdk 0.9.0 → 0.11.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 +4 -4
- data/LICENSES/Apache-2.0.txt +201 -0
- data/LICENSES/BSD-3-Clause-Google-Protobuf.txt +29 -0
- data/README.md +50 -7
- data/THIRD_PARTY_NOTICES.md +41 -0
- data/lib/erpc_sdk/arbitrum.rb +62 -0
- data/lib/erpc_sdk/arc.rb +33 -0
- data/lib/erpc_sdk/base.rb +333 -0
- data/lib/erpc_sdk/bitcoin.rb +126 -0
- data/lib/erpc_sdk/bitcoin_codec.rb +574 -0
- data/lib/erpc_sdk/bitcoin_transport.rb +369 -0
- data/lib/erpc_sdk/bsc.rb +33 -0
- data/lib/erpc_sdk/client.rb +109 -1
- data/lib/erpc_sdk/config.rb +168 -5
- data/lib/erpc_sdk/errors.rb +31 -1
- data/lib/erpc_sdk/generated/rpc_network_contract.rb +3196 -0
- data/lib/erpc_sdk/hyperliquid.rb +33 -0
- data/lib/erpc_sdk/kaia.rb +33 -0
- data/lib/erpc_sdk/monad.rb +33 -0
- data/lib/erpc_sdk/near.rb +276 -0
- data/lib/erpc_sdk/near_transport.rb +345 -0
- data/lib/erpc_sdk/optimism.rb +33 -0
- data/lib/erpc_sdk/polygon.rb +33 -0
- data/lib/erpc_sdk/robinhood.rb +33 -0
- data/lib/erpc_sdk/soneium.rb +33 -0
- data/lib/erpc_sdk/sui.rb +5 -0
- data/lib/erpc_sdk/sui_grpc_web.rb +226 -0
- data/lib/erpc_sdk/sui_proto.rb +370 -0
- data/lib/erpc_sdk/transport.rb +28 -0
- data/lib/erpc_sdk/tron.rb +33 -0
- data/lib/erpc_sdk/version.rb +1 -1
- data/lib/erpc_sdk.rb +21 -0
- metadata +57 -7
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "bigdecimal"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "uri"
|
|
7
|
+
|
|
8
|
+
require_relative "config"
|
|
9
|
+
require_relative "errors"
|
|
10
|
+
require_relative "transport"
|
|
11
|
+
|
|
12
|
+
module ERPC
|
|
13
|
+
SealedEvmTransportFacts = Struct.new(
|
|
14
|
+
:hosted_target,
|
|
15
|
+
:method_names,
|
|
16
|
+
:public_methods,
|
|
17
|
+
keyword_init: true
|
|
18
|
+
) do
|
|
19
|
+
def initialize(hosted_target:, method_names:, public_methods:)
|
|
20
|
+
super(
|
|
21
|
+
hosted_target: hosted_target.to_s.freeze,
|
|
22
|
+
method_names: method_names.map { |name| name.to_s.dup.freeze }.freeze,
|
|
23
|
+
public_methods: public_methods.to_h.each_with_object({}) do |(name, wire), result|
|
|
24
|
+
result[name.to_s.dup.freeze] = wire.to_s.dup.freeze
|
|
25
|
+
end.freeze
|
|
26
|
+
)
|
|
27
|
+
freeze
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def wire_method(public_name)
|
|
31
|
+
public_methods.fetch(public_name.to_s) do
|
|
32
|
+
raise ConfigError, "sealed EVM public method is absent from the accepted contract", cause: nil
|
|
33
|
+
end
|
|
34
|
+
rescue KeyError
|
|
35
|
+
raise ConfigError, "sealed EVM public method is absent from the accepted contract", cause: nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module SealedEvmContract
|
|
40
|
+
module_function
|
|
41
|
+
|
|
42
|
+
def facts_from_contract(contract, client_contract_id)
|
|
43
|
+
clients = contract.fetch("clientContracts")
|
|
44
|
+
method_sets = contract.fetch("methodSets")
|
|
45
|
+
client = clients.find do |value|
|
|
46
|
+
value.is_a?(Hash) && value["clientContractId"] == client_contract_id
|
|
47
|
+
end
|
|
48
|
+
raise ConfigError, "sealed EVM contract facts are unavailable", cause: nil unless client
|
|
49
|
+
|
|
50
|
+
method_set_id = client.fetch("methodSetId")
|
|
51
|
+
hosted = client.fetch("endpointModes").find { |value| value.is_a?(Hash) && value["mode"] == "hosted" }
|
|
52
|
+
method_set = method_sets.find { |value| value.is_a?(Hash) && value["methodSetId"] == method_set_id }
|
|
53
|
+
capabilities = method_set&.fetch("capabilities")
|
|
54
|
+
target = hosted&.fetch("defaultTarget")
|
|
55
|
+
unless target.is_a?(String) && capabilities.is_a?(Array) && !capabilities.empty?
|
|
56
|
+
raise ConfigError, "sealed EVM contract facts are unavailable", cause: nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
public_methods = {}
|
|
60
|
+
method_names = []
|
|
61
|
+
capabilities.each do |capability|
|
|
62
|
+
wire = capability.fetch("wireOperation")
|
|
63
|
+
names = capability.fetch("publicNames")
|
|
64
|
+
public_name = names.fetch("ruby")
|
|
65
|
+
unless wire.is_a?(String) && public_name.is_a?(String) && !wire.empty? && !public_name.empty?
|
|
66
|
+
raise ConfigError, "sealed EVM contract facts are unavailable", cause: nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
method_names << wire
|
|
70
|
+
public_methods[public_name] = wire
|
|
71
|
+
end
|
|
72
|
+
SealedEvmTransportFacts.new(
|
|
73
|
+
hosted_target: target,
|
|
74
|
+
method_names: method_names,
|
|
75
|
+
public_methods: public_methods
|
|
76
|
+
)
|
|
77
|
+
rescue KeyError, NoMethodError, TypeError
|
|
78
|
+
raise ConfigError, "sealed EVM contract facts are unavailable", cause: nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if defined?(RPC_NETWORK_CONTRACT)
|
|
83
|
+
BASE_TRANSPORT_FACTS = SealedEvmContract.facts_from_contract(RPC_NETWORK_CONTRACT, "client-base-rpc")
|
|
84
|
+
BASE_RPC_METHODS = BASE_TRANSPORT_FACTS.method_names
|
|
85
|
+
private_constant :BASE_TRANSPORT_FACTS
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class SealedEvmHttpTransport
|
|
89
|
+
attr_reader :method_names
|
|
90
|
+
|
|
91
|
+
def initialize(facts:, api_key: nil, endpoint: nil, direct_endpoint: nil, headers: {}, timeout: DEFAULT_TIMEOUT,
|
|
92
|
+
adapter: NetHttpAdapter.new, namespace:)
|
|
93
|
+
unless facts.is_a?(SealedEvmTransportFacts)
|
|
94
|
+
raise ConfigError, "sealed EVM transport facts are unavailable", cause: nil
|
|
95
|
+
end
|
|
96
|
+
raise ConfigError, "timeout must be positive", cause: nil unless timeout.is_a?(Numeric) && timeout.finite? && timeout.positive?
|
|
97
|
+
|
|
98
|
+
@facts = facts
|
|
99
|
+
@method_names = facts.method_names
|
|
100
|
+
@api_key = api_key.is_a?(String) && !api_key.strip.empty? ? api_key.strip : nil
|
|
101
|
+
@timeout = timeout.to_f
|
|
102
|
+
@adapter = adapter
|
|
103
|
+
@namespace = namespace.to_s.freeze
|
|
104
|
+
@id_mutex = Mutex.new
|
|
105
|
+
@next_id = 0
|
|
106
|
+
@direct = !direct_endpoint.nil?
|
|
107
|
+
|
|
108
|
+
if direct_endpoint
|
|
109
|
+
unless direct_endpoint.is_a?(RpcEndpointConfig)
|
|
110
|
+
raise ConfigError, "#{@namespace} must use RpcEndpointConfig", cause: nil
|
|
111
|
+
end
|
|
112
|
+
if direct_endpoint.websocket_url
|
|
113
|
+
raise ConfigError, "#{@namespace} does not support WebSocket URLs", cause: nil
|
|
114
|
+
end
|
|
115
|
+
@target = validate_http_url(direct_endpoint.http_url)
|
|
116
|
+
@headers = direct_endpoint.headers.dup
|
|
117
|
+
@redactions = Redaction.send(:direct_variants_with_path,
|
|
118
|
+
http_url: direct_endpoint.http_url,
|
|
119
|
+
websocket_url: direct_endpoint.websocket_url,
|
|
120
|
+
headers: direct_endpoint.headers
|
|
121
|
+
)
|
|
122
|
+
@available = true
|
|
123
|
+
elsif @api_key
|
|
124
|
+
@target = hosted_target(endpoint || facts.hosted_target, @api_key)
|
|
125
|
+
@headers = headers.to_h.dup
|
|
126
|
+
@redactions = [@api_key]
|
|
127
|
+
@available = true
|
|
128
|
+
else
|
|
129
|
+
@target = nil
|
|
130
|
+
@headers = {}
|
|
131
|
+
@redactions = []
|
|
132
|
+
@available = false
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def endpoint
|
|
137
|
+
return "" unless @available
|
|
138
|
+
|
|
139
|
+
uri = URI.parse(@target)
|
|
140
|
+
uri.path = "/"
|
|
141
|
+
uri.query = nil
|
|
142
|
+
uri.fragment = nil
|
|
143
|
+
uri.user = nil if uri.respond_to?(:user=)
|
|
144
|
+
uri.to_s
|
|
145
|
+
rescue URI::InvalidURIError, ArgumentError
|
|
146
|
+
""
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def inspect
|
|
150
|
+
"#<#{self.class} endpoint=#{endpoint.inspect} header_names=#{safe_header_names.inspect}>"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def wire_method(public_name)
|
|
154
|
+
@facts.wire_method(public_name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def request(method, params = nil)
|
|
158
|
+
unless @method_names.include?(method.to_s)
|
|
159
|
+
raise ConfigError, "sealed EVM method is not in the accepted named-read contract", cause: nil
|
|
160
|
+
end
|
|
161
|
+
raise NotConfiguredError, @namespace unless @available
|
|
162
|
+
|
|
163
|
+
request_id = next_id
|
|
164
|
+
body = {
|
|
165
|
+
"jsonrpc" => "2.0",
|
|
166
|
+
"id" => request_id,
|
|
167
|
+
"method" => method.to_s,
|
|
168
|
+
"params" => params.nil? ? [] : params
|
|
169
|
+
}
|
|
170
|
+
raw_body = JSON.generate(body)
|
|
171
|
+
response = nil
|
|
172
|
+
Timeout.timeout(@timeout) do
|
|
173
|
+
response = @adapter.request(
|
|
174
|
+
method: :post,
|
|
175
|
+
url: @target,
|
|
176
|
+
headers: request_headers,
|
|
177
|
+
body: raw_body,
|
|
178
|
+
timeout: @timeout
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
raise HttpError, response.status unless response.respond_to?(:status) && response.status.to_i.between?(200, 299)
|
|
182
|
+
|
|
183
|
+
unwrap(response.body, request_id)
|
|
184
|
+
rescue ::Timeout::Error
|
|
185
|
+
raise TimeoutError, @timeout, cause: nil
|
|
186
|
+
rescue InvalidResponseError, JsonRpcError, HttpError, NotConfiguredError, ConfigError, TimeoutError, TransportError
|
|
187
|
+
raise
|
|
188
|
+
rescue JSON::ParserError, JSON::GeneratorError, TypeError, ArgumentError
|
|
189
|
+
raise InvalidResponseError.new("ERPC returned an invalid response"), cause: nil
|
|
190
|
+
rescue IOError, EOFError, SocketError, SystemCallError, Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout
|
|
191
|
+
raise TransportError, "Unable to reach ERPC", cause: nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def validate_http_url(value)
|
|
197
|
+
input = value.to_s
|
|
198
|
+
uri = URI.parse(input)
|
|
199
|
+
valid = uri.absolute? && uri.host && %w[http https].include?(uri.scheme) && !input.include?("#")
|
|
200
|
+
valid &&= !input.match?(%r{://[^/?#]*@})
|
|
201
|
+
raise ConfigError, "HTTP endpoint must be an absolute HTTP(S) URL", cause: nil unless valid
|
|
202
|
+
|
|
203
|
+
input.freeze
|
|
204
|
+
rescue URI::InvalidURIError, ArgumentError
|
|
205
|
+
raise ConfigError, "HTTP endpoint must be an absolute HTTP(S) URL", cause: nil
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def hosted_target(value, api_key)
|
|
209
|
+
uri = URI.parse(validate_http_url(value))
|
|
210
|
+
pairs = URI.decode_www_form(uri.query.to_s).reject { |name, _| name.casecmp?("api-key") }
|
|
211
|
+
pairs << ["api-key", api_key]
|
|
212
|
+
uri.query = URI.encode_www_form(pairs)
|
|
213
|
+
uri.fragment = nil
|
|
214
|
+
uri.to_s
|
|
215
|
+
rescue URI::InvalidURIError, ArgumentError
|
|
216
|
+
raise ConfigError, "HTTP endpoint must be an absolute HTTP(S) URL", cause: nil
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def request_headers
|
|
220
|
+
values = @headers.dup
|
|
221
|
+
values.delete_if { |name, _| %w[accept content-type].include?(name.to_s.downcase) }
|
|
222
|
+
values["accept"] = "application/json"
|
|
223
|
+
values["content-type"] = "application/json"
|
|
224
|
+
values
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def next_id
|
|
228
|
+
@id_mutex.synchronize do
|
|
229
|
+
@next_id += 1
|
|
230
|
+
@next_id = 1 if @next_id > 2_147_483_647
|
|
231
|
+
@next_id
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def safe_header_names
|
|
236
|
+
@headers.keys.map(&:to_s).sort
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def unwrap(raw_body, expected_id)
|
|
240
|
+
parsed = JSON.parse(raw_body.to_s, create_additions: false, allow_nan: false, decimal_class: BigDecimal)
|
|
241
|
+
unless parsed.is_a?(Hash) && parsed["id"] == expected_id
|
|
242
|
+
raise InvalidResponseError.new("ERPC returned an unexpected response id"), cause: nil
|
|
243
|
+
end
|
|
244
|
+
if parsed.key?("error") && !parsed["error"].nil?
|
|
245
|
+
error = parsed["error"]
|
|
246
|
+
unless error.is_a?(Hash) && error["code"].is_a?(Integer) &&
|
|
247
|
+
!error["code"].is_a?(TrueClass) && !error["code"].is_a?(FalseClass) &&
|
|
248
|
+
error["message"].is_a?(String)
|
|
249
|
+
raise InvalidResponseError.new("ERPC returned an invalid RPC error"), cause: nil
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
data = error.key?("data") ? Redaction.value(error["data"], @redactions) : nil
|
|
253
|
+
raise JsonRpcError.new(error["code"], Redaction.text(error["message"], @redactions), data), cause: nil
|
|
254
|
+
end
|
|
255
|
+
result = parsed["result"]
|
|
256
|
+
raise InvalidResponseError.new("ERPC returned an invalid response"), cause: nil unless result.is_a?(String)
|
|
257
|
+
|
|
258
|
+
result
|
|
259
|
+
rescue InvalidResponseError, JsonRpcError
|
|
260
|
+
raise
|
|
261
|
+
rescue JSON::ParserError, JSON::NestingError, ArgumentError, TypeError, SystemStackError
|
|
262
|
+
raise InvalidResponseError.new("ERPC returned an invalid response"), cause: nil
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
class SealedEvmPendingRequest
|
|
267
|
+
def initialize(&block)
|
|
268
|
+
@block = block
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def send
|
|
272
|
+
@block.call
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
class BaseRpc
|
|
277
|
+
attr_reader :endpoint
|
|
278
|
+
|
|
279
|
+
def initialize(transport)
|
|
280
|
+
@transport = transport
|
|
281
|
+
@endpoint = transport.endpoint
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def inspect
|
|
285
|
+
"#<#{self.class} endpoint=#{endpoint.inspect}>"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def eth_chain_id
|
|
289
|
+
pending(@transport.wire_method("eth_chain_id"), [])
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def eth_get_balance(address, block = nil)
|
|
293
|
+
params = [address]
|
|
294
|
+
params << block unless block.nil?
|
|
295
|
+
pending(@transport.wire_method("eth_get_balance"), params)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def eth_call(transaction, block = nil, state_override = nil)
|
|
299
|
+
params = [transaction]
|
|
300
|
+
params << block unless block.nil?
|
|
301
|
+
params << state_override unless state_override.nil?
|
|
302
|
+
pending(@transport.wire_method("eth_call"), params)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
private
|
|
306
|
+
|
|
307
|
+
def pending(method, params)
|
|
308
|
+
SealedEvmPendingRequest.new { @transport.request(method, params) }
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
class BaseClient
|
|
313
|
+
attr_reader :rpc
|
|
314
|
+
|
|
315
|
+
def initialize(transport)
|
|
316
|
+
@rpc = BaseRpc.new(transport)
|
|
317
|
+
freeze
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
BaseRpcClient = BaseRpc
|
|
322
|
+
|
|
323
|
+
module Base
|
|
324
|
+
module_function
|
|
325
|
+
|
|
326
|
+
def create_client(transport)
|
|
327
|
+
BaseClient.new(transport)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
private_constant :SealedEvmTransportFacts, :SealedEvmContract, :SealedEvmHttpTransport,
|
|
332
|
+
:SealedEvmPendingRequest
|
|
333
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "bitcoin_transport"
|
|
4
|
+
|
|
5
|
+
module ERPC
|
|
6
|
+
if defined?(RPC_NETWORK_CONTRACT)
|
|
7
|
+
BITCOIN_TRANSPORT_FACTS = BitcoinTransportContract.facts_from_contract(RPC_NETWORK_CONTRACT)
|
|
8
|
+
BITCOIN_RPC_METHODS = BITCOIN_TRANSPORT_FACTS.method_names
|
|
9
|
+
private_constant :BITCOIN_TRANSPORT_FACTS
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class BitcoinPendingRequest
|
|
13
|
+
def initialize(&block)
|
|
14
|
+
@block = block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def send
|
|
18
|
+
@block.call
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class BitcoinRpc
|
|
23
|
+
attr_reader :endpoint
|
|
24
|
+
|
|
25
|
+
def initialize(transport)
|
|
26
|
+
@transport = transport
|
|
27
|
+
@endpoint = transport.endpoint
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def inspect
|
|
31
|
+
"#<#{self.class} endpoint=#{endpoint.inspect}>"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def estimate_smart_fee(conf_target = nil, estimate_mode = nil)
|
|
35
|
+
if estimate_mode && conf_target.nil?
|
|
36
|
+
raise ConfigError, "estimate_mode requires conf_target", cause: nil
|
|
37
|
+
end
|
|
38
|
+
params = conf_target.nil? ? [] : [conf_target]
|
|
39
|
+
params << estimate_mode unless estimate_mode.nil?
|
|
40
|
+
pending("estimate_smart_fee", params)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_best_block_hash
|
|
44
|
+
pending("get_best_block_hash", [], projector: BitcoinCodec.method(:as_string))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_block(block_hash, verbosity = nil)
|
|
48
|
+
params = [block_hash]
|
|
49
|
+
params << verbosity unless verbosity.nil?
|
|
50
|
+
pending("get_block", params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def get_blockchain_info
|
|
54
|
+
pending("get_blockchain_info", [])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get_block_count
|
|
58
|
+
pending("get_block_count", [], projector: BitcoinCodec.method(:as_block_count))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get_block_hash(height)
|
|
62
|
+
pending("get_block_hash", [height], projector: BitcoinCodec.method(:as_string))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_block_header(block_hash, verbose = nil)
|
|
66
|
+
params = [block_hash]
|
|
67
|
+
params << verbose unless verbose.nil?
|
|
68
|
+
pending("get_block_header", params)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def get_mempool_info
|
|
72
|
+
pending("get_mempool_info", [])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_network_info
|
|
76
|
+
pending("get_network_info", [])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def get_raw_mempool(verbose = nil)
|
|
80
|
+
pending("get_raw_mempool", verbose.nil? ? [] : [verbose])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def get_raw_transaction(transaction_id, verbose = nil, block_hash = nil)
|
|
84
|
+
params = [transaction_id]
|
|
85
|
+
params << verbose unless verbose.nil?
|
|
86
|
+
params << block_hash unless block_hash.nil?
|
|
87
|
+
pending("get_raw_transaction", params)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def get_tx_out(transaction_id, output_index, include_mempool = nil)
|
|
91
|
+
params = [transaction_id, output_index]
|
|
92
|
+
params << include_mempool unless include_mempool.nil?
|
|
93
|
+
pending("get_tx_out", params, projector: BitcoinCodec.method(:as_nullable_object))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def pending(public_name, params, projector: nil)
|
|
99
|
+
method = @transport.wire_method(public_name)
|
|
100
|
+
BitcoinPendingRequest.new do
|
|
101
|
+
@transport.request(method, params, projector: projector)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class BitcoinClient
|
|
107
|
+
attr_reader :rpc
|
|
108
|
+
|
|
109
|
+
def initialize(transport)
|
|
110
|
+
@rpc = BitcoinRpc.new(transport)
|
|
111
|
+
freeze
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
BitcoinRpcClient = BitcoinRpc
|
|
116
|
+
|
|
117
|
+
module Bitcoin
|
|
118
|
+
module_function
|
|
119
|
+
|
|
120
|
+
def create_client(transport)
|
|
121
|
+
BitcoinClient.new(transport)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private_constant :BitcoinPendingRequest
|
|
126
|
+
end
|