sequence-sdk 0.0.1
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 +7 -0
- data/LICENSE +191 -0
- data/README.md +41 -0
- data/lib/chain/account.rb +80 -0
- data/lib/chain/asset.rb +85 -0
- data/lib/chain/balance.rb +40 -0
- data/lib/chain/batch_response.rb +21 -0
- data/lib/chain/client.rb +71 -0
- data/lib/chain/client_module.rb +11 -0
- data/lib/chain/connection.rb +206 -0
- data/lib/chain/constants.rb +3 -0
- data/lib/chain/dev_utils.rb +14 -0
- data/lib/chain/errors.rb +80 -0
- data/lib/chain/key.rb +46 -0
- data/lib/chain/page.rb +25 -0
- data/lib/chain/query.rb +79 -0
- data/lib/chain/response_object.rb +116 -0
- data/lib/chain/stats.rb +31 -0
- data/lib/chain/transaction.rb +256 -0
- data/lib/chain/unspent_output.rb +72 -0
- data/lib/chain/version.rb +3 -0
- data/lib/chain.rb +3 -0
- metadata +202 -0
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'openssl'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
require_relative './batch_response'
|
8
|
+
require_relative './errors'
|
9
|
+
require_relative './version'
|
10
|
+
|
11
|
+
module Chain
|
12
|
+
class Connection
|
13
|
+
|
14
|
+
# Parameters to the retry exponential backoff function.
|
15
|
+
MAX_RETRIES = 10
|
16
|
+
RETRY_BASE_DELAY_MS = 40
|
17
|
+
RETRY_MAX_DELAY_MS = 4000
|
18
|
+
|
19
|
+
NETWORK_ERRORS = [
|
20
|
+
InvalidRequestIDError,
|
21
|
+
SocketError,
|
22
|
+
EOFError,
|
23
|
+
IOError,
|
24
|
+
Timeout::Error,
|
25
|
+
Errno::ECONNABORTED,
|
26
|
+
Errno::ECONNRESET,
|
27
|
+
Errno::ETIMEDOUT,
|
28
|
+
Errno::EHOSTUNREACH,
|
29
|
+
Errno::ECONNREFUSED,
|
30
|
+
]
|
31
|
+
|
32
|
+
def initialize(opts)
|
33
|
+
@opts = opts
|
34
|
+
@url = URI(@opts[:url])
|
35
|
+
@access_token = @opts[:access_token] || @url.userinfo
|
36
|
+
@http_mutex = Mutex.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a copy of the configuration options
|
40
|
+
def opts
|
41
|
+
@opts.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(path, body = {})
|
45
|
+
_request_with_retries(path, body)[:body]
|
46
|
+
end
|
47
|
+
|
48
|
+
def batch_request(path, body = {}, &translate)
|
49
|
+
res = _request_with_retries(path, body)
|
50
|
+
body = res[:body]
|
51
|
+
response = res[:response]
|
52
|
+
|
53
|
+
successes = {}
|
54
|
+
errors = {}
|
55
|
+
|
56
|
+
body.each_with_index do |item, i|
|
57
|
+
if !!item['code']
|
58
|
+
errors[i] = APIError.new(item, response)
|
59
|
+
else
|
60
|
+
if translate
|
61
|
+
successes[i] = translate.call(item)
|
62
|
+
else
|
63
|
+
successes[i] = item
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
BatchResponse.new(
|
69
|
+
successes: successes,
|
70
|
+
errors: errors,
|
71
|
+
response: response,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def singleton_batch_request(path, body = {}, &translate)
|
76
|
+
batch = batch_request(path, body, &translate)
|
77
|
+
|
78
|
+
if batch.size != 1
|
79
|
+
raise "Invalid response, expected a single response object but got #{batch.items.size}"
|
80
|
+
end
|
81
|
+
|
82
|
+
raise batch.errors.values.first if batch.errors.size == 1
|
83
|
+
|
84
|
+
batch.successes.values.first
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def _request_with_retries(path, body)
|
90
|
+
attempts = 0
|
91
|
+
|
92
|
+
begin
|
93
|
+
attempts += 1
|
94
|
+
|
95
|
+
# If this is a retry and not the first attempt, sleep before making the
|
96
|
+
# retry request.
|
97
|
+
sleep(backoff_delay(attempts)) if attempts > 1
|
98
|
+
|
99
|
+
_single_request(path, body)
|
100
|
+
rescue *NETWORK_ERRORS => e
|
101
|
+
raise e if attempts > MAX_RETRIES
|
102
|
+
retry
|
103
|
+
rescue APIError => e
|
104
|
+
raise e if attempts > MAX_RETRIES
|
105
|
+
retry if e.retriable?
|
106
|
+
raise e
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def _single_request(path, body)
|
111
|
+
@http_mutex.synchronize do
|
112
|
+
# Timeout configuration
|
113
|
+
[:open_timeout, :read_timeout, :ssl_timeout].each do |k|
|
114
|
+
next unless @opts.key?(k)
|
115
|
+
http.send "#{k}=", @opts[k]
|
116
|
+
end
|
117
|
+
|
118
|
+
full_path = @url.request_uri.chomp('/')
|
119
|
+
full_path += (path[0] == '/') ? path : '/' + path
|
120
|
+
|
121
|
+
req = Net::HTTP::Post.new(full_path)
|
122
|
+
req['Accept'] = 'application/json'
|
123
|
+
req['Content-Type'] = 'application/json'
|
124
|
+
req['User-Agent'] = 'chain-sdk-ruby/' + Chain::VERSION
|
125
|
+
req.body = JSON.dump(body)
|
126
|
+
|
127
|
+
if @access_token
|
128
|
+
user, pass = @access_token.split(':')
|
129
|
+
req.basic_auth(user, pass)
|
130
|
+
end
|
131
|
+
|
132
|
+
response = http.request(req)
|
133
|
+
|
134
|
+
req_id = response['Chain-Request-ID']
|
135
|
+
unless req_id.is_a?(String) && req_id.size > 0
|
136
|
+
raise InvalidRequestIDError.new(response)
|
137
|
+
end
|
138
|
+
|
139
|
+
status = Integer(response.code)
|
140
|
+
parsed_body = nil
|
141
|
+
|
142
|
+
if status != 204 # No Content
|
143
|
+
begin
|
144
|
+
parsed_body = JSON.parse(response.body)
|
145
|
+
rescue JSON::JSONError
|
146
|
+
raise JSONError.new(req_id, response)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
if status / 100 != 2
|
151
|
+
klass = status == 401 ? UnauthorizedError : APIError
|
152
|
+
raise klass.new(parsed_body, response)
|
153
|
+
end
|
154
|
+
|
155
|
+
{body: parsed_body, response: response}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
MILLIS_TO_SEC = 0.001
|
162
|
+
|
163
|
+
def backoff_delay(attempt)
|
164
|
+
max = RETRY_BASE_DELAY_MS * 2**(attempt-1)
|
165
|
+
max = [max, RETRY_MAX_DELAY_MS].min
|
166
|
+
millis = rand(max) + 1
|
167
|
+
millis * MILLIS_TO_SEC
|
168
|
+
end
|
169
|
+
|
170
|
+
def http
|
171
|
+
return @http if @http
|
172
|
+
|
173
|
+
args = [@url.host, @url.port]
|
174
|
+
|
175
|
+
# Proxy configuration
|
176
|
+
if @opts.key?(:proxy_addr)
|
177
|
+
args += [@opts[:proxy_addr], @opts[:proxy_port]]
|
178
|
+
if @opts.key?(:proxy_user)
|
179
|
+
args += [@opts[:proxy_user], @opts[:proxy_pass]]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
@http = Net::HTTP.new(*args)
|
184
|
+
|
185
|
+
@http.set_debug_output($stdout) if ENV['DEBUG']
|
186
|
+
if @url.scheme == 'https'
|
187
|
+
@http.use_ssl = true
|
188
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
189
|
+
if @opts.key?(:ssl_params)
|
190
|
+
ssl_params = @opts[:ssl_params]
|
191
|
+
if ssl_params.key?(:ca_file)
|
192
|
+
@http.ca_file = ssl_params[:ca_file]
|
193
|
+
end
|
194
|
+
if ssl_params.key?(:cert)
|
195
|
+
@http.cert = ssl_params[:cert]
|
196
|
+
end
|
197
|
+
if ssl_params.key?(:key)
|
198
|
+
@http.key = ssl_params[:key]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
@http
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
require_relative './client_module'
|
4
|
+
|
5
|
+
module Chain
|
6
|
+
class DevUtils
|
7
|
+
class ClientModule < Chain::ClientModule
|
8
|
+
# Deletes all data in the ledger. (development ledgers only)
|
9
|
+
def reset
|
10
|
+
client.conn.request('/reset', client_token: SecureRandom.uuid)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/chain/errors.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Chain
|
2
|
+
|
3
|
+
# Base class for all errors raised by the Chain SDK.
|
4
|
+
class BaseError < StandardError; end
|
5
|
+
|
6
|
+
# InvalidRequestIDError arises when an HTTP response is received, but it does
|
7
|
+
# not contain headers that are included in all Chain API responses. This
|
8
|
+
# could arise due to a badly-configured proxy, or other upstream network
|
9
|
+
# issues.
|
10
|
+
class InvalidRequestIDError < BaseError
|
11
|
+
attr_accessor :response
|
12
|
+
|
13
|
+
def initialize(response)
|
14
|
+
super "Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings."
|
15
|
+
self.response = response
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# JSONError should be very rare, and will only arise if there is a bug in the
|
20
|
+
# Chain API, or if the upstream server is spoofing common Chain API response
|
21
|
+
# headers.
|
22
|
+
class JSONError < BaseError
|
23
|
+
attr_accessor :request_id
|
24
|
+
attr_accessor :response
|
25
|
+
|
26
|
+
def initialize(request_id, response)
|
27
|
+
super "Error decoding JSON response. Request-ID: #{request_id}"
|
28
|
+
self.request_id = request_id
|
29
|
+
self.response = response
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# APIError describes errors that are codified by the Chain API. They have
|
34
|
+
# an error code, a message, and an optional detail field that provides
|
35
|
+
# additional context for the error.
|
36
|
+
class APIError < BaseError
|
37
|
+
RETRIABLE_STATUS_CODES = [
|
38
|
+
408, # Request Timeout
|
39
|
+
429, # Too Many Requests
|
40
|
+
500, # Internal Server Error
|
41
|
+
502, # Bad Gateway
|
42
|
+
503, # Service Unavailable
|
43
|
+
504, # Gateway Timeout
|
44
|
+
509, # Bandwidth Limit Exceeded
|
45
|
+
]
|
46
|
+
|
47
|
+
attr_accessor :code, :chain_message, :detail, :data, :temporary, :request_id, :response
|
48
|
+
|
49
|
+
def initialize(body, response)
|
50
|
+
self.code = body['code']
|
51
|
+
self.chain_message = body['message']
|
52
|
+
self.detail = body['detail']
|
53
|
+
self.temporary = body['temporary']
|
54
|
+
|
55
|
+
self.response = response
|
56
|
+
self.request_id = response['Chain-Request-ID'] if response
|
57
|
+
|
58
|
+
super self.class.format_error_message(code, chain_message, detail, request_id)
|
59
|
+
end
|
60
|
+
|
61
|
+
def retriable?
|
62
|
+
temporary || (response && RETRIABLE_STATUS_CODES.include?(Integer(response.code)))
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.format_error_message(code, message, detail, request_id)
|
66
|
+
tokens = []
|
67
|
+
tokens << "Code: #{code}" if code.is_a?(String) && code.size > 0
|
68
|
+
tokens << "Message: #{message}"
|
69
|
+
tokens << "Detail: #{detail}" if detail.is_a?(String) && detail.size > 0
|
70
|
+
tokens << "Request-ID: #{request_id}"
|
71
|
+
tokens.join(' ')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# UnauthorizedError is a special case of APIError, and is raised when the
|
76
|
+
# response status code is 401. This is a common error case, so a discrete
|
77
|
+
# exception type is provided for convenience.
|
78
|
+
class UnauthorizedError < APIError; end
|
79
|
+
|
80
|
+
end
|
data/lib/chain/key.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative './client_module'
|
2
|
+
require_relative './connection'
|
3
|
+
require_relative './query'
|
4
|
+
require_relative './response_object'
|
5
|
+
|
6
|
+
module Chain
|
7
|
+
class Key < ResponseObject
|
8
|
+
# @!attribute [r] alias
|
9
|
+
# User specified, unique identifier of the key.
|
10
|
+
# @return [String]
|
11
|
+
attrib :alias
|
12
|
+
|
13
|
+
# @!attribute [r] id
|
14
|
+
# Unique identifier of the key, based on the public key material itself.
|
15
|
+
# @return [String]
|
16
|
+
attrib :id
|
17
|
+
|
18
|
+
class ClientModule < Chain::ClientModule
|
19
|
+
|
20
|
+
# Creates a key object.
|
21
|
+
# @param [Hash] opts Parameters for MockHSM key creation.
|
22
|
+
# @option opts [String] alias User specified, unique identifier.
|
23
|
+
# @return [Key]
|
24
|
+
def create(opts = {})
|
25
|
+
Key.new(client.conn.request('create-key', opts))
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [Hash] opts Filtering information
|
29
|
+
# @option opts [Array<String>] aliases Optional list of requested aliases, max 200.
|
30
|
+
# @return [Query]
|
31
|
+
def query(opts = {})
|
32
|
+
Query.new(client, opts)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Query < Chain::Query
|
37
|
+
def fetch(query)
|
38
|
+
client.conn.request('list-keys', query)
|
39
|
+
end
|
40
|
+
|
41
|
+
def translate(obj)
|
42
|
+
Key.new(obj)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/chain/page.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative './response_object'
|
2
|
+
|
3
|
+
module Chain
|
4
|
+
class Page < ResponseObject
|
5
|
+
# @!attribute [r] items
|
6
|
+
# List of items.
|
7
|
+
# @return [Array]
|
8
|
+
attrib :items
|
9
|
+
|
10
|
+
# @!attribute [r] next
|
11
|
+
# Query object to request next page of items
|
12
|
+
# @return [Hash]
|
13
|
+
attrib :next
|
14
|
+
|
15
|
+
# @!attribute [r] last_page
|
16
|
+
# Indicator of whether there are more pages to load
|
17
|
+
# @return [Boolean]
|
18
|
+
attrib :last_page
|
19
|
+
|
20
|
+
def initialize(raw_attribs, translate)
|
21
|
+
super(raw_attribs)
|
22
|
+
@items = @items.map { |i| translate.call(i) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/chain/query.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative './page'
|
2
|
+
|
3
|
+
module Chain
|
4
|
+
class Query
|
5
|
+
include ::Enumerable
|
6
|
+
|
7
|
+
# @return [Client]
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
# @return [Hash]
|
11
|
+
attr_reader :query
|
12
|
+
|
13
|
+
def initialize(client, query = {})
|
14
|
+
@client = client
|
15
|
+
@query = query
|
16
|
+
end
|
17
|
+
|
18
|
+
# Iterate through objects in response, fetching the next page of results
|
19
|
+
# from the API as needed.
|
20
|
+
#
|
21
|
+
# Implements required method
|
22
|
+
# {https://ruby-doc.org/core/Enumerable.html Enumerable#each}.
|
23
|
+
# @return [void]
|
24
|
+
def each
|
25
|
+
pages.each do |page|
|
26
|
+
page.items.each do |item, index|
|
27
|
+
yield item
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def pages
|
33
|
+
PageQuery.new(client, query, method(:fetch), method(:translate))
|
34
|
+
end
|
35
|
+
|
36
|
+
# @abstract
|
37
|
+
def fetch(query)
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
# Overwrite to translate API response data to a different Ruby object.
|
42
|
+
# @abstract
|
43
|
+
def translate(response_object)
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :all, :to_a
|
48
|
+
|
49
|
+
class PageQuery
|
50
|
+
include ::Enumerable
|
51
|
+
|
52
|
+
def initialize(client, query, fetch, translate)
|
53
|
+
@client = client
|
54
|
+
@query = query
|
55
|
+
@fetch = fetch
|
56
|
+
@translate = translate
|
57
|
+
end
|
58
|
+
|
59
|
+
def each
|
60
|
+
page = nil
|
61
|
+
|
62
|
+
loop do
|
63
|
+
page = Page.new(@fetch.call(@query), @translate)
|
64
|
+
@query = page.next
|
65
|
+
|
66
|
+
yield page
|
67
|
+
|
68
|
+
break if page.last_page
|
69
|
+
|
70
|
+
# The second predicate (empty?) *should* be redundant, but we check it
|
71
|
+
# anyway as a defensive measure.
|
72
|
+
break if page.items.empty?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
alias_method :all, :to_a
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Chain
|
5
|
+
class ResponseObject
|
6
|
+
def initialize(raw_attribs)
|
7
|
+
raw_attribs.each do |k, v|
|
8
|
+
next unless self.class.has_attrib?(k)
|
9
|
+
self[k] = self.class.translate(k, v) unless v.nil?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
self.class.attrib_opts.keys.reduce({}) do |memo, name|
|
15
|
+
memo[name] = instance_variable_get("@#{name}")
|
16
|
+
memo
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_json(opts = nil)
|
21
|
+
h = to_h.reduce({}) do |memo, (k, v)|
|
22
|
+
memo[k] = self.class.detranslate(k, v)
|
23
|
+
memo
|
24
|
+
end
|
25
|
+
|
26
|
+
h.to_json
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](attrib_name)
|
30
|
+
attrib_name = attrib_name.to_sym
|
31
|
+
raise KeyError.new("key not found: #{attrib_name}") unless self.class.attrib_opts.key?(attrib_name)
|
32
|
+
|
33
|
+
instance_variable_get "@#{attrib_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(attrib_name, value)
|
37
|
+
attrib_name = attrib_name.to_sym
|
38
|
+
raise KeyError.new("key not found: #{attrib_name}") unless self.class.attrib_opts.key?(attrib_name)
|
39
|
+
|
40
|
+
instance_variable_set "@#{attrib_name}", value
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!visibility private
|
44
|
+
def self.attrib_opts
|
45
|
+
@attrib_opts ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!visibility private
|
49
|
+
def self.attrib(attrib_name, opts = {}, &translate)
|
50
|
+
opts[:translate] = translate
|
51
|
+
attrib_opts[attrib_name.to_sym] = opts
|
52
|
+
attr_accessor attrib_name
|
53
|
+
end
|
54
|
+
|
55
|
+
# @!visibility private
|
56
|
+
def self.has_attrib?(attrib_name)
|
57
|
+
attrib_opts.key?(attrib_name.to_sym)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @!visibility private
|
61
|
+
def self.translate(attrib_name, raw_value)
|
62
|
+
attrib_name = attrib_name.to_sym
|
63
|
+
opts = attrib_opts[attrib_name]
|
64
|
+
|
65
|
+
return Time.parse(raw_value) if opts[:rfc3339_time]
|
66
|
+
return raw_value if opts[:translate].nil?
|
67
|
+
|
68
|
+
begin
|
69
|
+
opts[:translate].call raw_value
|
70
|
+
rescue => e
|
71
|
+
raise TranslateError.new(attrib_name, raw_value, e)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
def self.detranslate(attrib_name, raw_value)
|
77
|
+
opts = attrib_opts.fetch(attrib_name, {})
|
78
|
+
|
79
|
+
if opts[:rfc3339_time]
|
80
|
+
begin
|
81
|
+
return raw_value.to_datetime.rfc3339
|
82
|
+
rescue => e
|
83
|
+
raise DetranslateError.new(attrib_name, raw_value, e)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
raw_value
|
88
|
+
end
|
89
|
+
|
90
|
+
class TranslateError < StandardError
|
91
|
+
attr_reader :attrib_name
|
92
|
+
attr_reader :raw_value
|
93
|
+
attr_reader :source
|
94
|
+
|
95
|
+
def initialize(attrib_name, raw_value, source)
|
96
|
+
super "Error translating attrib #{attrib_name}: #{source}"
|
97
|
+
@attrib_name = attrib_name
|
98
|
+
@raw_value = raw_value
|
99
|
+
@source = source
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class DetranslateError < StandardError
|
104
|
+
attr_reader :attrib_name
|
105
|
+
attr_reader :raw_value
|
106
|
+
attr_reader :source
|
107
|
+
|
108
|
+
def initialize(attrib_name, raw_value, source)
|
109
|
+
super "Error de-translating attrib #{attrib_name}: #{source}"
|
110
|
+
@attrib_name = attrib_name
|
111
|
+
@raw_value = raw_value
|
112
|
+
@source = source
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/chain/stats.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative './client_module'
|
2
|
+
require_relative './response_object'
|
3
|
+
require_relative './query'
|
4
|
+
|
5
|
+
module Chain
|
6
|
+
class Stats < ResponseObject
|
7
|
+
|
8
|
+
# @!attribute [r] asset_count
|
9
|
+
# Total number of assets in the ledger.
|
10
|
+
# @return [Integer]
|
11
|
+
attrib :asset_count
|
12
|
+
|
13
|
+
# @!attribute [r] account_count
|
14
|
+
# Total number of accounts in the ledger.
|
15
|
+
# @return [Integer]
|
16
|
+
attrib :account_count
|
17
|
+
|
18
|
+
# @!attribute [r] tx_count
|
19
|
+
# Total number of transactions in the ledger.
|
20
|
+
# @return [Integer]
|
21
|
+
attrib :tx_count
|
22
|
+
|
23
|
+
class ClientModule < Chain::ClientModule
|
24
|
+
# @return [Stats]
|
25
|
+
def get
|
26
|
+
Stats.new(client.conn.request('stats'))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|