substrate_client.rb 0.1.5 → 0.1.6
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/Gemfile.lock +1 -1
- data/README.md +65 -44
- data/exe/metadata +5 -6
- data/lib/helper.rb +129 -0
- data/lib/substrate_client.rb +128 -235
- data/lib/substrate_client/version.rb +1 -1
- data/lib/substrate_client_sync.rb +190 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a1478123f2f6a7365eab00d5bf908d93902c2b058d058207b9ed12531dc6f7a
|
4
|
+
data.tar.gz: d2df429bec159846b09646e91daa6f1050c2e1d206ed3f2c6347ad4240da8095
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 882b6345105fa751c1c42f3b5ffcc86872ba47e261a6598a1c094bc5332f7632b8238829c7d8ca261244ba4c3b0e797b82f937878a3172c2f104cfe1ba545721
|
7
|
+
data.tar.gz: 271d1ebb55ac8686eb9d047eb6b38f00b08f465fed392ec5d79f56b492b8bf303647913ef53b25b1c167f119b37b439cea6eb0fb1bd5b6aa62eb3e59f363de2f
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -28,61 +28,77 @@ Or install it yourself as:
|
|
28
28
|
require "substrate_client"
|
29
29
|
|
30
30
|
client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
|
31
|
-
|
31
|
+
client.method_list do |methods|
|
32
|
+
p methods
|
33
|
+
end
|
32
34
|
```
|
33
35
|
The rpc methods can be dynamically called by its name, so the methods returned by this method can all be used.
|
34
36
|
|
35
|
-
|
37
|
+
|
38
|
+
|
39
|
+
#### rpc methods
|
36
40
|
|
37
41
|
But, in order to show the parameters more clearly, some important or frequently used methods are hard-coded:
|
38
42
|
|
39
|
-
- chain_get_finalised_head
|
43
|
+
- chain_get_finalised_head(&callback)
|
40
44
|
|
41
45
|
Get hash of the last finalized block in the canon chain
|
42
46
|
|
47
|
+
```ruby
|
48
|
+
client.chain_get_finalised_head do |head|
|
49
|
+
p head
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
43
53
|
|
44
54
|
|
45
|
-
- chain_get_head
|
55
|
+
- chain_get_head(&callback)
|
56
|
+
|
57
|
+
Retrieves the header
|
46
58
|
|
47
|
-
|
59
|
+
|
60
|
+
|
61
|
+
- chain_get_header(block_hash = nil, &callback)
|
48
62
|
|
49
63
|
Retrieves the header for a specific block
|
50
64
|
|
51
65
|
|
52
66
|
|
53
|
-
- chain_get_block(block_hash = nil)
|
67
|
+
- chain_get_block(block_hash = nil, &callback)
|
54
68
|
|
55
69
|
Get header and body of a relay chain block
|
56
70
|
|
57
71
|
|
58
72
|
|
59
|
-
- chain_get_block_hash(block_id)
|
73
|
+
- chain_get_block_hash(block_id, &callback)
|
60
74
|
|
61
75
|
Get the block hash for a specific block
|
62
76
|
|
63
77
|
|
64
78
|
|
65
|
-
- chain_get_runtime_version(block_hash = nil)
|
79
|
+
- chain_get_runtime_version(block_hash = nil, &callback)
|
66
80
|
|
67
81
|
Get the runtime version for a specific block
|
68
82
|
|
69
83
|
|
70
84
|
|
71
|
-
- state_get_metadata(block_hash = nil)
|
85
|
+
- state_get_metadata(block_hash = nil, &callback)
|
72
86
|
|
73
87
|
Returns the runtime metadata by block
|
74
88
|
|
75
89
|
|
76
90
|
|
77
|
-
- state_get_storage(storage_key, block_hash = nil)
|
91
|
+
- state_get_storage(storage_key, block_hash = nil, &callback)
|
78
92
|
|
79
93
|
Retrieves the storage for a key
|
80
94
|
|
81
95
|
|
82
96
|
|
83
|
-
- system_name
|
97
|
+
- system_name(&callback)
|
98
|
+
|
99
|
+
|
84
100
|
|
85
|
-
- system_version
|
101
|
+
- system_version(&callback)
|
86
102
|
|
87
103
|
|
88
104
|
|
@@ -110,19 +126,10 @@ But, in order to show the parameters more clearly, some important or frequently
|
|
110
126
|
|
111
127
|
Retrieves the best header via subscription. This will return data continuously until you unsubscribe the subscription.
|
112
128
|
|
113
|
-
```ruby
|
114
|
-
subscription = client.chain_subscribe_new_heads do |data|
|
115
|
-
p data
|
116
|
-
end
|
117
|
-
```
|
118
|
-
|
119
129
|
- chain_unsubscribe_new_heads(subscription)
|
120
130
|
|
121
131
|
Unsubscribe the best header subscription.
|
122
132
|
|
123
|
-
```ruby
|
124
|
-
client.chain_unsubscribe_new_heads(subscription)
|
125
|
-
```
|
126
133
|
|
127
134
|
|
128
135
|
|
@@ -130,22 +137,12 @@ But, in order to show the parameters more clearly, some important or frequently
|
|
130
137
|
|
131
138
|
Retrieves the best finalized header via subscription. This will return data continuously until you unsubscribe the subscription.
|
132
139
|
|
133
|
-
```ruby
|
134
|
-
subscription = client.chain_subscribe_finalized_heads do |data|
|
135
|
-
p data
|
136
|
-
end
|
137
|
-
```
|
138
|
-
|
139
140
|
- chain_unsubscribe_finalized_heads(subscription)
|
140
141
|
|
141
142
|
Unsubscribe the best finalized header subscription.
|
142
143
|
|
143
|
-
```ruby
|
144
|
-
client.chain_unsubscribe_finalized_heads(subscription)
|
145
|
-
```
|
146
|
-
|
147
144
|
|
148
|
-
|
145
|
+
|
149
146
|
- state_subscribe_runtime_version(&callback)
|
150
147
|
|
151
148
|
Retrieves the runtime version via subscription.
|
@@ -175,33 +172,57 @@ But, in order to show the parameters more clearly, some important or frequently
|
|
175
172
|
|
176
173
|
These methods will encode the parameters and decode the returned data
|
177
174
|
|
178
|
-
- get_block_number(block_hash)
|
175
|
+
- get_block_number(block_hash, &callback)
|
179
176
|
|
180
|
-
- get_metadata(block_hash)
|
177
|
+
- get_metadata(block_hash, &callback)
|
181
178
|
|
182
|
-
- get_block(block_hash=nil)
|
179
|
+
- get_block(block_hash=nil, &callback)
|
183
180
|
|
184
|
-
- get_block_events(block_hash)
|
181
|
+
- get_block_events(block_hash, &callback)
|
185
182
|
|
186
|
-
- subscribe_block_events(&callback)
|
183
|
+
- subscribe_block_events(&callback, &callback)
|
187
184
|
|
188
|
-
- get_storage(module_name, storage_name, params = nil, block_hash = nil)
|
185
|
+
- get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
|
189
186
|
|
190
187
|
```ruby
|
191
|
-
client.get_storage("
|
192
|
-
|
193
|
-
|
194
|
-
|
188
|
+
client.get_storage("Balances", "TotalIssuance", nil, nil) do |storage|
|
189
|
+
p storage
|
190
|
+
end
|
191
|
+
|
192
|
+
client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil) do |storage|
|
193
|
+
p storage
|
194
|
+
end
|
195
|
+
|
196
|
+
client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil) do |storage|
|
197
|
+
p storage
|
198
|
+
end
|
195
199
|
```
|
196
200
|
|
197
|
-
- compose_call(module_name, call_name, params, block_hash=nil)
|
201
|
+
- compose_call(module_name, call_name, params, block_hash=nil, &callback)
|
198
202
|
|
199
203
|
```ruby
|
200
|
-
compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
|
204
|
+
compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil do |hex|
|
205
|
+
p hex
|
206
|
+
end
|
201
207
|
```
|
202
208
|
|
203
209
|
|
204
210
|
|
211
|
+
## Synchronized client
|
212
|
+
|
213
|
+
There is also a synchronized version of the client, which is disconnected every time the data is retrieved. It is very convenient in some situations. But this version cannot use the subscription interface.
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
client = SubstrateClientSync.new "wss://kusama-rpc.polkadot.io/"
|
217
|
+
p client.method_list
|
218
|
+
p client.chain_get_head
|
219
|
+
p client.chain_get_finalised_head
|
220
|
+
p client.chain_get_header(block_hash = nil)
|
221
|
+
...
|
222
|
+
```
|
223
|
+
|
224
|
+
|
225
|
+
|
205
226
|
## Development
|
206
227
|
|
207
228
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/exe/metadata
CHANGED
@@ -3,11 +3,10 @@
|
|
3
3
|
require "substrate_client"
|
4
4
|
require "json"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
url = ARGV[0] || "wss://kusama-rpc.polkadot.io/"
|
7
|
+
client = SubstrateClientSync.new(url)
|
8
|
+
|
9
|
+
block_hash = ARGV[1] || client.chain_get_finalised_head
|
9
10
|
|
10
|
-
client = SubstrateClient.new(url)
|
11
|
-
client.init(block_hash)
|
12
11
|
metadata = client.get_metadata(block_hash)
|
13
|
-
puts JSON.pretty_generate(metadata.value)
|
12
|
+
puts JSON.pretty_generate(metadata.value.to_human)
|
data/lib/helper.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
|
2
|
+
class SubstrateClient::Helper
|
3
|
+
class << self
|
4
|
+
def generate_storage_hash_from_metadata(metadata, module_name, storage_name, params = nil)
|
5
|
+
# find the storage item from metadata
|
6
|
+
metadata_modules = metadata.value.value[:metadata][:modules]
|
7
|
+
metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
|
8
|
+
raise "Module '#{module_name}' not exist" unless metadata_module
|
9
|
+
storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
|
10
|
+
raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
|
11
|
+
|
12
|
+
if storage_item[:type][:Plain]
|
13
|
+
return_type = storage_item[:type][:Plain]
|
14
|
+
elsif map = storage_item[:type][:Map]
|
15
|
+
raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
|
16
|
+
|
17
|
+
hasher = map[:hasher]
|
18
|
+
return_type = map[:value]
|
19
|
+
# TODO: decode to account id if param is address
|
20
|
+
# params[0] = decode(params[0]) if map[:key] == "AccountId"
|
21
|
+
params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
|
22
|
+
elsif map = storage_item[:type][:DoubleMap]
|
23
|
+
raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
|
24
|
+
|
25
|
+
hasher = map[:hasher]
|
26
|
+
hasher2 = map[:key2Hasher]
|
27
|
+
return_type = map[:value]
|
28
|
+
params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
|
29
|
+
params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
|
30
|
+
else
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
storage_hash = generate_storage_hash(
|
35
|
+
module_name,
|
36
|
+
storage_name,
|
37
|
+
params,
|
38
|
+
hasher,
|
39
|
+
hasher2,
|
40
|
+
metadata.value.value[:metadata][:version]
|
41
|
+
)
|
42
|
+
[storage_hash, return_type]
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
|
46
|
+
if metadata_version and metadata_version >= 9
|
47
|
+
storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
|
48
|
+
|
49
|
+
params&.each_with_index do |param, index|
|
50
|
+
if index == 0
|
51
|
+
param_hasher = hasher
|
52
|
+
elsif index == 1
|
53
|
+
param_hasher = hasher2
|
54
|
+
else
|
55
|
+
raise "Unexpected third parameter for storage call"
|
56
|
+
end
|
57
|
+
|
58
|
+
param_key = param.hex_to_bytes
|
59
|
+
param_hasher = "Twox128" if param_hasher.nil?
|
60
|
+
storage_hash += Crypto.send param_hasher.underscore, param_key
|
61
|
+
end
|
62
|
+
|
63
|
+
"0x#{storage_hash}"
|
64
|
+
else
|
65
|
+
# TODO: add test
|
66
|
+
storage_hash = module_name + " " + storage_name
|
67
|
+
|
68
|
+
unless params.nil?
|
69
|
+
params = [params] if params.class != ::Array
|
70
|
+
params_key = params.join("")
|
71
|
+
hasher = "Twox128" if hasher.nil?
|
72
|
+
storage_hash += params_key.hex_to_bytes.bytes_to_utf8
|
73
|
+
end
|
74
|
+
|
75
|
+
"0x#{Crypto.send( hasher.underscore, storage_hash )}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def compose_call_from_metadata(metadata, module_name, call_name, params)
|
80
|
+
call = metadata.get_module_call(module_name, call_name)
|
81
|
+
|
82
|
+
value = {
|
83
|
+
call_index: call[:lookup],
|
84
|
+
module_name: module_name,
|
85
|
+
call_name: call_name,
|
86
|
+
params: []
|
87
|
+
}
|
88
|
+
|
89
|
+
params.keys.each_with_index do |call_param_name, i|
|
90
|
+
param_value = params[call_param_name]
|
91
|
+
value[:params] << {
|
92
|
+
name: call_param_name.to_s,
|
93
|
+
type: call[:args][i][:type],
|
94
|
+
value: param_value
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
Scale::Types::Extrinsic.new(value).encode
|
99
|
+
end
|
100
|
+
|
101
|
+
def decode_block(block)
|
102
|
+
block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
|
103
|
+
|
104
|
+
block["block"]["extrinsics"].each_with_index do |hex, i|
|
105
|
+
scale_bytes = Scale::Bytes.new(hex)
|
106
|
+
block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
|
107
|
+
end
|
108
|
+
|
109
|
+
block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
|
110
|
+
scale_bytes = Scale::Bytes.new(hex)
|
111
|
+
block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
|
112
|
+
end
|
113
|
+
|
114
|
+
block
|
115
|
+
end
|
116
|
+
|
117
|
+
# chain_unsubscribe_runtime_version
|
118
|
+
# =>
|
119
|
+
# chain_unsubscribeRuntimeVersion
|
120
|
+
def real_method_name(method_name)
|
121
|
+
segments = method_name.to_s.split("_")
|
122
|
+
if segments.length == 1
|
123
|
+
segments[0]
|
124
|
+
else
|
125
|
+
segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/substrate_client.rb
CHANGED
@@ -6,7 +6,9 @@ require "json"
|
|
6
6
|
require "active_support"
|
7
7
|
require "active_support/core_ext/string"
|
8
8
|
require "websocket"
|
9
|
+
require "helper"
|
9
10
|
require "timeout_queue"
|
11
|
+
require "substrate_client_sync"
|
10
12
|
|
11
13
|
class SubstrateClient
|
12
14
|
class RpcError < StandardError; end
|
@@ -28,15 +30,15 @@ class SubstrateClient
|
|
28
30
|
Scale::TypeRegistry.instance.load(spec_name)
|
29
31
|
|
30
32
|
init_ws
|
33
|
+
|
34
|
+
at_exit { self.close }
|
31
35
|
end
|
32
36
|
|
33
37
|
def close
|
34
38
|
@ws.close
|
35
39
|
end
|
36
40
|
|
37
|
-
def request(method, params, subscription_callback
|
38
|
-
queue = TimeoutQueue.new
|
39
|
-
|
41
|
+
def request(method, params, callback: nil, subscription_callback: nil)
|
40
42
|
payload = {
|
41
43
|
"jsonrpc" => "2.0",
|
42
44
|
"method" => method,
|
@@ -44,193 +46,188 @@ class SubstrateClient
|
|
44
46
|
"id" => @request_id
|
45
47
|
}
|
46
48
|
|
47
|
-
@callbacks[@request_id] = proc
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
if not subscription_callback.nil? && data["result"]
|
53
|
-
@subscription_callbacks[data["result"]] = subscription_callback
|
54
|
-
end
|
49
|
+
@callbacks[@request_id] = proc do |data|
|
50
|
+
if not subscription_callback.nil? && data["result"]
|
51
|
+
@subscription_callbacks[data["result"]] = subscription_callback
|
52
|
+
end
|
55
53
|
|
56
|
-
|
57
|
-
raise RpcError, data["error"]
|
58
|
-
else
|
59
|
-
data["result"]
|
54
|
+
callback.call data if callback
|
60
55
|
end
|
61
|
-
|
62
|
-
|
56
|
+
@ws.send(payload.to_json)
|
57
|
+
@request_id += 1
|
63
58
|
end
|
64
59
|
|
65
|
-
def init_runtime(block_hash
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
60
|
+
def init_runtime(block_hash=nil, &callback)
|
61
|
+
# set current runtime spec version
|
62
|
+
self.state_get_runtime_version(block_hash) do |runtime_version|
|
63
|
+
@spec_version = runtime_version["specVersion"]
|
64
|
+
Scale::TypeRegistry.instance.spec_version = @spec_version
|
65
|
+
|
66
|
+
# set current metadata
|
67
|
+
self.get_metadata(block_hash) do |metadata|
|
68
|
+
@metadata = metadata
|
69
|
+
Scale::TypeRegistry.instance.metadata = @metadata.value
|
70
|
+
callback.call
|
71
71
|
end
|
72
72
|
end
|
73
|
+
end
|
73
74
|
|
74
|
-
|
75
|
-
runtime_version = self.state_get_runtime_version(block_hash)
|
76
|
-
@spec_version = runtime_version["specVersion"]
|
77
|
-
Scale::TypeRegistry.instance.spec_version = @spec_version
|
78
|
-
|
79
|
-
# set current metadata
|
80
|
-
@metadata = self.get_metadata(block_hash)
|
81
|
-
Scale::TypeRegistry.instance.metadata = @metadata.value
|
82
|
-
true
|
75
|
+
def do_init_runtime(block_hash, &callback)
|
83
76
|
end
|
84
77
|
|
85
|
-
def invoke(method,
|
86
|
-
|
87
|
-
|
78
|
+
def invoke(method, params, callback)
|
79
|
+
request method, params, callback: proc { |data|
|
80
|
+
if data["error"]
|
81
|
+
Thread.main.raise RpcError, data["error"]
|
82
|
+
else
|
83
|
+
callback.call data["result"]
|
84
|
+
end
|
85
|
+
}
|
88
86
|
end
|
89
87
|
|
90
88
|
def rpc_method(method_name)
|
91
|
-
|
89
|
+
Helper.real_method_name(method_name.to_s)
|
92
90
|
end
|
93
91
|
|
94
92
|
# ################################################
|
95
93
|
# origin rpc methods
|
96
94
|
# ################################################
|
97
|
-
def method_missing(method,
|
98
|
-
rpc_method =
|
99
|
-
invoke rpc_method,
|
95
|
+
def method_missing(method, args, &callback)
|
96
|
+
rpc_method = Helper.real_method_name(method)
|
97
|
+
invoke rpc_method, args, callback
|
98
|
+
end
|
99
|
+
|
100
|
+
def state_get_runtime_version(block_hash=nil, &callback)
|
101
|
+
invoke rpc_method(__method__), [ block_hash ], callback
|
100
102
|
end
|
101
103
|
|
102
|
-
def rpc_methods
|
103
|
-
invoke rpc_method(__method__)
|
104
|
+
def rpc_methods(&callback)
|
105
|
+
invoke rpc_method(__method__), [], callback
|
104
106
|
end
|
105
107
|
|
106
|
-
def chain_get_head
|
107
|
-
invoke rpc_method(__method__)
|
108
|
+
def chain_get_head(&callback)
|
109
|
+
invoke rpc_method(__method__), [], callback
|
108
110
|
end
|
109
111
|
|
110
112
|
def chain_get_finalised_head
|
111
|
-
invoke rpc_method(__method__)
|
113
|
+
invoke rpc_method(__method__), [], callback
|
112
114
|
end
|
113
115
|
|
114
|
-
def chain_get_header(block_hash = nil)
|
115
|
-
invoke rpc_method(__method__), block_hash
|
116
|
+
def chain_get_header(block_hash = nil, &callback)
|
117
|
+
invoke rpc_method(__method__), [ block_hash ], callback
|
116
118
|
end
|
117
119
|
|
118
|
-
def chain_get_block(block_hash = nil)
|
119
|
-
invoke rpc_method(__method__), block_hash
|
120
|
+
def chain_get_block(block_hash = nil, &callback)
|
121
|
+
invoke rpc_method(__method__), [ block_hash ], callback
|
120
122
|
end
|
121
123
|
|
122
|
-
def chain_get_block_hash(block_id)
|
123
|
-
invoke rpc_method(__method__), block_id
|
124
|
+
def chain_get_block_hash(block_id, &callback)
|
125
|
+
invoke rpc_method(__method__), [ block_id ], callback
|
124
126
|
end
|
125
127
|
|
126
|
-
def chain_get_runtime_version(block_hash = nil)
|
127
|
-
invoke rpc_method(__method__), block_hash
|
128
|
+
def chain_get_runtime_version(block_hash = nil, &callback)
|
129
|
+
invoke rpc_method(__method__), [ block_hash ], callback
|
128
130
|
end
|
129
131
|
|
130
|
-
def state_get_metadata(block_hash = nil)
|
131
|
-
invoke rpc_method(__method__), block_hash
|
132
|
+
def state_get_metadata(block_hash = nil, &callback)
|
133
|
+
invoke rpc_method(__method__), [ block_hash ], callback
|
132
134
|
end
|
133
135
|
|
134
|
-
def state_get_storage(storage_key, block_hash = nil)
|
135
|
-
invoke rpc_method(__method__), storage_key, block_hash
|
136
|
+
def state_get_storage(storage_key, block_hash = nil, &callback)
|
137
|
+
invoke rpc_method(__method__), [ storage_key, block_hash ], callback
|
136
138
|
end
|
137
139
|
|
138
|
-
def system_name
|
139
|
-
invoke rpc_method(__method__)
|
140
|
+
def system_name(&callback)
|
141
|
+
invoke rpc_method(__method__), [], callback
|
140
142
|
end
|
141
143
|
|
142
|
-
def system_version
|
143
|
-
invoke rpc_method(__method__)
|
144
|
+
def system_version(&callback)
|
145
|
+
invoke rpc_method(__method__), [], callback
|
144
146
|
end
|
145
147
|
|
146
148
|
def chain_subscribe_all_heads(&callback)
|
147
|
-
request rpc_method(__method__), [], callback
|
149
|
+
request rpc_method(__method__), [], subscription_callback: callback
|
148
150
|
end
|
149
151
|
|
150
|
-
def chain_unsubscribe_all_heads(subscription)
|
151
|
-
invoke rpc_method(__method__), subscription
|
152
|
+
def chain_unsubscribe_all_heads(subscription, &callback)
|
153
|
+
invoke rpc_method(__method__), [ subscription ], callback
|
152
154
|
end
|
153
155
|
|
154
156
|
def chain_subscribe_new_heads(&callback)
|
155
|
-
request rpc_method(__method__), [], callback
|
157
|
+
request rpc_method(__method__), [], subscription_callback: callback
|
156
158
|
end
|
157
159
|
|
158
|
-
def chain_unsubscribe_new_heads(subscription)
|
159
|
-
invoke rpc_method(__method__), subscription
|
160
|
+
def chain_unsubscribe_new_heads(subscription, &callback)
|
161
|
+
invoke rpc_method(__method__), [ subscription ], callback
|
160
162
|
end
|
161
163
|
|
162
164
|
def chain_subscribe_finalized_heads(&callback)
|
163
|
-
request rpc_method(__method__), [], callback
|
165
|
+
request rpc_method(__method__), [], subscription_callback: callback
|
164
166
|
end
|
165
167
|
|
166
|
-
def chain_unsubscribe_finalized_heads(subscription)
|
167
|
-
invoke rpc_method(__method__), subscription
|
168
|
+
def chain_unsubscribe_finalized_heads(subscription, &callback)
|
169
|
+
invoke rpc_method(__method__), [ subscription ], callback
|
168
170
|
end
|
169
171
|
|
170
172
|
def state_subscribe_runtime_version(&callback)
|
171
|
-
request rpc_method(__method__), [], callback
|
173
|
+
request rpc_method(__method__), [], subscription_callback: callback
|
172
174
|
end
|
173
175
|
|
174
|
-
def state_unsubscribe_runtime_version(subscription)
|
175
|
-
invoke rpc_method(__method__), subscription
|
176
|
+
def state_unsubscribe_runtime_version(subscription, &callback)
|
177
|
+
invoke rpc_method(__method__), [ subscription ], callback
|
176
178
|
end
|
177
179
|
|
178
180
|
def state_subscribe_storage(keys, &callback)
|
179
|
-
request rpc_method(__method__), [keys], callback
|
181
|
+
request rpc_method(__method__), [keys], subscription_callback: callback
|
180
182
|
end
|
181
183
|
|
182
|
-
def state_unsubscribe_storage(subscription)
|
183
|
-
invoke rpc_method(__method__), subscription
|
184
|
+
def state_unsubscribe_storage(subscription, &callback)
|
185
|
+
invoke rpc_method(__method__), [ subscription ], callback
|
184
186
|
end
|
185
187
|
|
186
188
|
# ################################################
|
187
189
|
# custom methods based on origin rpc methods
|
188
190
|
# ################################################
|
189
|
-
def method_list
|
190
|
-
self.rpc_methods
|
191
|
-
|
192
|
-
|
193
|
-
def get_block_number(block_hash)
|
194
|
-
header = self.chain_get_header(block_hash)
|
195
|
-
header["number"].to_i(16)
|
191
|
+
def method_list(&callback)
|
192
|
+
self.rpc_methods do |result|
|
193
|
+
callback.call result["methods"].map(&:underscore)
|
194
|
+
end
|
196
195
|
end
|
197
196
|
|
198
|
-
def
|
199
|
-
|
200
|
-
|
197
|
+
def get_block_number(block_hash, &callback)
|
198
|
+
self.chain_get_header(block_hash) do |header|
|
199
|
+
callback.call header["number"].to_i(16)
|
200
|
+
end
|
201
201
|
end
|
202
202
|
|
203
|
-
def
|
204
|
-
self.
|
205
|
-
|
206
|
-
|
207
|
-
block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
|
208
|
-
|
209
|
-
block["block"]["extrinsics"].each_with_index do |hex, i|
|
210
|
-
scale_bytes = Scale::Bytes.new(hex)
|
211
|
-
block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
|
203
|
+
def get_metadata(block_hash=nil, &callback)
|
204
|
+
self.state_get_metadata(block_hash) do |hex|
|
205
|
+
callback.call Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
|
212
206
|
end
|
207
|
+
end
|
213
208
|
|
214
|
-
|
215
|
-
|
216
|
-
|
209
|
+
def get_block(block_hash=nil, &callback)
|
210
|
+
self.init_runtime block_hash do
|
211
|
+
self.chain_get_block(block_hash) do |block|
|
212
|
+
block = Helper.decode_block block
|
213
|
+
callback.call block
|
214
|
+
end
|
217
215
|
end
|
218
|
-
|
219
|
-
block
|
220
216
|
end
|
221
217
|
|
222
|
-
def get_block_events(block_hash)
|
223
|
-
self.init_runtime(block_hash
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
218
|
+
def get_block_events(block_hash=nil, &callback)
|
219
|
+
self.init_runtime(block_hash) do
|
220
|
+
storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
|
221
|
+
self.state_get_storage storage_key, block_hash do |events_data|
|
222
|
+
scale_bytes = Scale::Bytes.new(events_data)
|
223
|
+
events = Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
|
224
|
+
callback.call events
|
225
|
+
end
|
226
|
+
end
|
230
227
|
end
|
231
228
|
|
232
229
|
def subscribe_block_events(&callback)
|
233
|
-
self.
|
230
|
+
self.chain_subscribe_finalized_heads do |data|
|
234
231
|
|
235
232
|
block_number = data["params"]["result"]["number"].to_i(16) - 1
|
236
233
|
block_hash = data["params"]["result"]["parentHash"]
|
@@ -238,17 +235,18 @@ class SubstrateClient
|
|
238
235
|
EM.defer(
|
239
236
|
|
240
237
|
proc {
|
241
|
-
|
242
|
-
|
238
|
+
self.get_block_events block_hash do |events|
|
239
|
+
begin
|
240
|
+
result = { block_number: block_number, events: events }
|
241
|
+
callback.call result
|
242
|
+
rescue => ex
|
243
|
+
SubstrateClient.logger.error ex.message
|
244
|
+
SubstrateClient.logger.error ex.backtrace.join("\n")
|
245
|
+
end
|
246
|
+
end
|
243
247
|
},
|
244
248
|
|
245
249
|
proc { |result|
|
246
|
-
begin
|
247
|
-
callback.call result
|
248
|
-
rescue => ex
|
249
|
-
SubstrateClient.logger.error ex.message
|
250
|
-
SubstrateClient.logger.error ex.backtrace.join("\n")
|
251
|
-
end
|
252
250
|
},
|
253
251
|
|
254
252
|
proc { |e|
|
@@ -259,130 +257,25 @@ class SubstrateClient
|
|
259
257
|
end
|
260
258
|
end
|
261
259
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
|
272
|
-
raise "Module '#{module_name}' not exist" unless metadata_module
|
273
|
-
storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
|
274
|
-
raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
|
275
|
-
|
276
|
-
if storage_item[:type][:Plain]
|
277
|
-
return_type = storage_item[:type][:Plain]
|
278
|
-
elsif map = storage_item[:type][:Map]
|
279
|
-
raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
|
280
|
-
|
281
|
-
hasher = map[:hasher]
|
282
|
-
return_type = map[:value]
|
283
|
-
# TODO: decode to account id if param is address
|
284
|
-
# params[0] = decode(params[0]) if map[:key] == "AccountId"
|
285
|
-
params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
|
286
|
-
elsif map = storage_item[:type][:DoubleMap]
|
287
|
-
raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
|
288
|
-
|
289
|
-
hasher = map[:hasher]
|
290
|
-
hasher2 = map[:key2Hasher]
|
291
|
-
return_type = map[:value]
|
292
|
-
params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
|
293
|
-
params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
|
294
|
-
else
|
295
|
-
raise NotImplementedError
|
296
|
-
end
|
297
|
-
|
298
|
-
storage_hash = SubstrateClient.generate_storage_hash(
|
299
|
-
module_name,
|
300
|
-
storage_name,
|
301
|
-
params,
|
302
|
-
hasher,
|
303
|
-
hasher2,
|
304
|
-
metadata.value.value[:metadata][:version]
|
305
|
-
)
|
306
|
-
|
307
|
-
p module_name
|
308
|
-
p storage_name
|
309
|
-
p params
|
310
|
-
p hasher
|
311
|
-
p hasher2
|
312
|
-
p metadata.value.value[:metadata][:version]
|
313
|
-
result = self.state_get_storage(storage_hash, block_hash)
|
314
|
-
return unless result
|
315
|
-
Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
|
316
|
-
end
|
317
|
-
|
318
|
-
# compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
|
319
|
-
def compose_call(module_name, call_name, params, block_hash=nil)
|
320
|
-
self.init_runtime(block_hash: block_hash)
|
321
|
-
|
322
|
-
call = metadata.get_module_call(module_name, call_name)
|
323
|
-
|
324
|
-
value = {
|
325
|
-
call_index: call[:lookup],
|
326
|
-
module_name: module_name,
|
327
|
-
call_name: call_name,
|
328
|
-
params: []
|
329
|
-
}
|
330
|
-
|
331
|
-
params.keys.each_with_index do |call_param_name, i|
|
332
|
-
param_value = params[call_param_name]
|
333
|
-
value[:params] << {
|
334
|
-
name: call_param_name.to_s,
|
335
|
-
type: call[:args][i][:type],
|
336
|
-
value: param_value
|
337
|
-
}
|
338
|
-
end
|
339
|
-
|
340
|
-
Scale::Types::Extrinsic.new(value).encode
|
341
|
-
end
|
342
|
-
|
343
|
-
class << self
|
344
|
-
def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
|
345
|
-
if metadata_version and metadata_version >= 9
|
346
|
-
storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
|
347
|
-
|
348
|
-
params&.each_with_index do |param, index|
|
349
|
-
if index == 0
|
350
|
-
param_hasher = hasher
|
351
|
-
elsif index == 1
|
352
|
-
param_hasher = hasher2
|
353
|
-
else
|
354
|
-
raise "Unexpected third parameter for storage call"
|
355
|
-
end
|
356
|
-
|
357
|
-
param_key = param.hex_to_bytes
|
358
|
-
param_hasher = "Twox128" if param_hasher.nil?
|
359
|
-
storage_hash += Crypto.send param_hasher.underscore, param_key
|
360
|
-
end
|
361
|
-
|
362
|
-
"0x#{storage_hash}"
|
363
|
-
else
|
364
|
-
# TODO: add test
|
365
|
-
storage_hash = module_name + " " + storage_name
|
366
|
-
|
367
|
-
unless params.nil?
|
368
|
-
params = [params] if params.class != ::Array
|
369
|
-
params_key = params.join("")
|
370
|
-
hasher = "Twox128" if hasher.nil?
|
371
|
-
storage_hash += params_key.hex_to_bytes.bytes_to_utf8
|
260
|
+
def get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
|
261
|
+
self.init_runtime(block_hash) do
|
262
|
+
storage_hash, return_type = Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
|
263
|
+
self.state_get_storage(storage_hash, block_hash) do |result|
|
264
|
+
if result
|
265
|
+
storage = Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
|
266
|
+
callback.call storage
|
267
|
+
else
|
268
|
+
callback.call nil
|
372
269
|
end
|
373
|
-
|
374
|
-
"0x#{Crypto.send( hasher.underscore, storage_hash )}"
|
375
270
|
end
|
376
271
|
end
|
272
|
+
end
|
377
273
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
segments = method_name.to_s.split("_")
|
383
|
-
segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
|
274
|
+
def compose_call(module_name, call_name, params, block_hash=nil, &callback)
|
275
|
+
self.init_runtime(block_hash) do
|
276
|
+
hex = Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
|
277
|
+
callback.call hex
|
384
278
|
end
|
385
|
-
|
386
279
|
end
|
387
280
|
|
388
281
|
private
|
@@ -0,0 +1,190 @@
|
|
1
|
+
def ws_request(url, payload)
|
2
|
+
queue = TimeoutQueue.new
|
3
|
+
|
4
|
+
thread = Thread.new do
|
5
|
+
EM.run do
|
6
|
+
ws = Faye::WebSocket::Client.new(url)
|
7
|
+
|
8
|
+
ws.on :open do |event|
|
9
|
+
ws.send(payload.to_json)
|
10
|
+
end
|
11
|
+
|
12
|
+
ws.on :message do |event|
|
13
|
+
if event.data.include?("jsonrpc")
|
14
|
+
queue << JSON.parse(event.data)
|
15
|
+
ws.close(3001, "data received")
|
16
|
+
Thread.kill thread
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ws.on :close do |event|
|
21
|
+
ws = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
queue.pop true, 10
|
27
|
+
rescue ThreadError => ex
|
28
|
+
raise SubstrateClientSync::RpcTimeout
|
29
|
+
end
|
30
|
+
|
31
|
+
class SubstrateClientSync
|
32
|
+
class RpcError < StandardError; end
|
33
|
+
class RpcTimeout < StandardError; end
|
34
|
+
|
35
|
+
attr_accessor :spec_name, :spec_version, :metadata
|
36
|
+
|
37
|
+
def initialize(url, spec_name: nil)
|
38
|
+
@url = url
|
39
|
+
@request_id = 1
|
40
|
+
@spec_name = spec_name
|
41
|
+
Scale::TypeRegistry.instance.load(spec_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(method, params)
|
45
|
+
payload = {
|
46
|
+
"jsonrpc" => "2.0",
|
47
|
+
"method" => method,
|
48
|
+
"params" => params,
|
49
|
+
"id" => @request_id
|
50
|
+
}
|
51
|
+
|
52
|
+
data = ws_request(@url, payload)
|
53
|
+
if data["error"]
|
54
|
+
raise RpcError, data["error"]
|
55
|
+
else
|
56
|
+
data["result"]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def init_runtime(block_hash=nil)
|
61
|
+
# set current runtime spec version
|
62
|
+
runtime_version = self.state_get_runtime_version(block_hash)
|
63
|
+
@spec_version = runtime_version["specVersion"]
|
64
|
+
Scale::TypeRegistry.instance.spec_version = @spec_version
|
65
|
+
|
66
|
+
# set current metadata
|
67
|
+
@metadata = self.get_metadata(block_hash)
|
68
|
+
Scale::TypeRegistry.instance.metadata = @metadata.value
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def invoke(method, *params)
|
73
|
+
# params.reject! { |param| param.nil? }
|
74
|
+
request(method, params)
|
75
|
+
end
|
76
|
+
|
77
|
+
def rpc_method(method_name)
|
78
|
+
SubstrateClient::Helper.real_method_name(method_name.to_s)
|
79
|
+
end
|
80
|
+
|
81
|
+
# ################################################
|
82
|
+
# origin rpc methods
|
83
|
+
# ################################################
|
84
|
+
def method_missing(method, *args)
|
85
|
+
rpc_method = SubstrateClient::Helper.real_method_name(method)
|
86
|
+
invoke rpc_method, *args
|
87
|
+
end
|
88
|
+
|
89
|
+
def state_get_runtime_version(block_hash=nil)
|
90
|
+
invoke rpc_method(__method__), block_hash
|
91
|
+
end
|
92
|
+
|
93
|
+
def rpc_methods
|
94
|
+
invoke rpc_method(__method__)
|
95
|
+
end
|
96
|
+
|
97
|
+
def chain_get_head
|
98
|
+
invoke rpc_method(__method__)
|
99
|
+
end
|
100
|
+
|
101
|
+
def chain_get_finalised_head
|
102
|
+
invoke rpc_method(__method__)
|
103
|
+
end
|
104
|
+
|
105
|
+
def chain_get_header(block_hash = nil)
|
106
|
+
invoke rpc_method(__method__), block_hash
|
107
|
+
end
|
108
|
+
|
109
|
+
def chain_get_block(block_hash = nil)
|
110
|
+
invoke rpc_method(__method__), block_hash
|
111
|
+
end
|
112
|
+
|
113
|
+
def chain_get_block_hash(block_id)
|
114
|
+
invoke rpc_method(__method__), block_id
|
115
|
+
end
|
116
|
+
|
117
|
+
def chain_get_runtime_version(block_hash = nil)
|
118
|
+
invoke rpc_method(__method__), block_hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def state_get_metadata(block_hash = nil)
|
122
|
+
invoke rpc_method(__method__), block_hash
|
123
|
+
end
|
124
|
+
|
125
|
+
def state_get_storage(storage_key, block_hash = nil)
|
126
|
+
invoke rpc_method(__method__), storage_key, block_hash
|
127
|
+
end
|
128
|
+
|
129
|
+
def system_name
|
130
|
+
invoke rpc_method(__method__)
|
131
|
+
end
|
132
|
+
|
133
|
+
def system_version
|
134
|
+
invoke rpc_method(__method__)
|
135
|
+
end
|
136
|
+
|
137
|
+
# ################################################
|
138
|
+
# custom methods based on origin rpc methods
|
139
|
+
# ################################################
|
140
|
+
def method_list
|
141
|
+
self.rpc_methods["methods"].map(&:underscore)
|
142
|
+
end
|
143
|
+
|
144
|
+
def get_block_number(block_hash)
|
145
|
+
header = self.chain_get_header(block_hash)
|
146
|
+
header["number"].to_i(16)
|
147
|
+
end
|
148
|
+
|
149
|
+
def get_metadata(block_hash=nil)
|
150
|
+
hex = self.state_get_metadata(block_hash)
|
151
|
+
Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_block(block_hash=nil)
|
155
|
+
self.init_runtime(block_hash)
|
156
|
+
block = self.chain_get_block(block_hash)
|
157
|
+
SubstrateClient::Helper.decode_block(block)
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_block_events(block_hash=nil)
|
161
|
+
self.init_runtime(block_hash)
|
162
|
+
|
163
|
+
storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
|
164
|
+
events_data = state_get_storage storage_key, block_hash
|
165
|
+
|
166
|
+
scale_bytes = Scale::Bytes.new(events_data)
|
167
|
+
Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
|
168
|
+
end
|
169
|
+
|
170
|
+
# Plain: client.get_storage("Sudo", "Key")
|
171
|
+
# Plain: client.get_storage("Balances", "TotalIssuance")
|
172
|
+
# Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
|
173
|
+
# DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
|
174
|
+
def get_storage(module_name, storage_name, params = nil, block_hash = nil)
|
175
|
+
self.init_runtime(block_hash)
|
176
|
+
|
177
|
+
storage_hash, return_type = SubstrateClient::Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
|
178
|
+
|
179
|
+
result = self.state_get_storage(storage_hash, block_hash)
|
180
|
+
return unless result
|
181
|
+
Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
|
182
|
+
end
|
183
|
+
|
184
|
+
# compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
|
185
|
+
def compose_call(module_name, call_name, params, block_hash=nil)
|
186
|
+
self.init_runtime(block_hash)
|
187
|
+
SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: substrate_client.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wu Minzhe
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faye-websocket
|
@@ -142,8 +142,10 @@ files:
|
|
142
142
|
- bin/console
|
143
143
|
- bin/setup
|
144
144
|
- exe/metadata
|
145
|
+
- lib/helper.rb
|
145
146
|
- lib/substrate_client.rb
|
146
147
|
- lib/substrate_client/version.rb
|
148
|
+
- lib/substrate_client_sync.rb
|
147
149
|
- lib/timeout_queue.rb
|
148
150
|
- lib/websocket.rb
|
149
151
|
- substrate_client.gemspec
|