blockchain 2.0.0 → 3.0.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.
@@ -6,6 +6,7 @@ require "blockchain/pushtx"
6
6
  require "blockchain/receive"
7
7
  require "blockchain/statistics"
8
8
  require "blockchain/wallet"
9
+ require "blockchain/client"
9
10
 
10
11
  module Blockchain
11
12
  module V2
@@ -1,80 +1,169 @@
1
1
  require 'json'
2
- require_relative 'util'
2
+ require_relative 'client'
3
3
 
4
4
  module Blockchain
5
5
 
6
- def self.get_block(block_id, api_code = nil)
7
- params = api_code.nil? ? { } : { 'api_code' => api_code }
8
- resource = 'rawblock/' + block_id
9
- response = Blockchain::call_api(resource, method: 'get', data: params)
10
- return Block.new(JSON.parse(response))
11
- end
6
+ MAX_TRANSACTIONS_PER_REQUEST = 50
7
+ MAX_TRANSACTIONS_PER_MULTI_REQUEST = 100
8
+ DEFAULT_UNSPENT_TRANSACTIONS_PER_REQUEST = 250
12
9
 
13
- def self.get_tx(tx_id, api_code = nil)
14
- params = api_code.nil? ? { } : { 'api_code' => api_code }
15
- resource = 'rawtx/' + tx_id
16
- response = Blockchain::call_api(resource, method: 'get', data: params)
17
- return Transaction.new(JSON.parse(response))
18
- end
10
+ class BlockExplorer
11
+
12
+ attr_reader :client
13
+
14
+ def initialize(base_url = nil, api_code = nil)
15
+ @client = Client.new(base_url, api_code)
16
+ end
17
+
18
+ def proxy(method_name, *args)
19
+ warn "[DEPRECATED] avoid use of static methods, use an instance of BlockExplorer class instead."
20
+ send(method_name, *args)
21
+ end
22
+
23
+ # Deprecated. Please use get_block_by_hash whenever possible.
24
+ def get_block_by_index(index)
25
+ warn "[DEPRECATED] `get_block_by_index` is deprecated. Please use `get_block_by_hash` whenever possible."
26
+ return get_block(index.to_s)
27
+ end
28
+
29
+ def get_block_by_hash(block_hash)
30
+ return get_block(block_hash)
31
+ end
32
+
33
+ # Deprecated. Please use get_tx_by_hash whenever possible.
34
+ def get_tx_by_index(tx_index)
35
+ warn "[DEPRECATED] `get_tx_by_index` is deprecated. Please use `get_tx_by_hash` whenever possible."
36
+ return get_tx(tx_index.to_s)
37
+ end
38
+
39
+ def get_tx_by_hash(tx_hash)
40
+ return get_tx(tx_hash)
41
+ end
42
+
43
+ def get_block_height(height)
44
+ params = { 'format' => 'json' }
45
+ resource = "block-height/#{height}"
46
+ response = @client.call_api(resource, method: 'get', data: params)
47
+ return JSON.parse(response)['blocks'].map{ |b| Block.new(b) }
48
+ end
49
+
50
+ def get_address_by_hash160(address, limit = MAX_TRANSACTIONS_PER_REQUEST,
51
+ offset = 0, filter = FilterType::REMOVE_UNSPENDABLE)
52
+ return get_address(address, limit, offset, filter)
53
+ end
54
+
55
+ def get_address_by_base58(address, limit = MAX_TRANSACTIONS_PER_REQUEST,
56
+ offset = 0, filter = FilterType::REMOVE_UNSPENDABLE)
57
+ return get_address(address, limit, offset, filter)
58
+ end
59
+
60
+ def get_xpub(xpub, limit = MAX_TRANSACTIONS_PER_MULTI_REQUEST,
61
+ offset = 0, filter = FilterType::REMOVE_UNSPENDABLE)
62
+ params = { 'active' => xpub, 'format' => 'json', 'limit' => limit, 'offset' => offset, 'filter' => filter }
63
+ resource = 'multiaddr'
64
+ response = @client.call_api(resource, method: 'get', data: params)
65
+ return Xpub.new(JSON.parse(response))
66
+ end
67
+
68
+ def get_multi_address(address_array, limit = MAX_TRANSACTIONS_PER_MULTI_REQUEST,
69
+ offset = 0, filter = FilterType::REMOVE_UNSPENDABLE)
70
+ params = { 'active' => address_array.join("|"), 'format' => 'json', 'limit' => limit, 'offset' => offset, 'filter' => filter }
71
+ resource = 'multiaddr'
72
+ response = @client.call_api(resource, method: 'get', data: params)
73
+ return MultiAddress.new(JSON.parse(response))
74
+ end
75
+
76
+ def get_unspent_outputs(address_array,
77
+ limit = DEFAULT_UNSPENT_TRANSACTIONS_PER_REQUEST,
78
+ confirmations = 0)
79
+ params = { 'active' => address_array.join("|"), 'limit' => limit, 'confirmations' => confirmations }
80
+ resource = 'unspent'
81
+ response = @client.call_api(resource, method: 'get', data: params)
82
+ return JSON.parse(response)['unspent_outputs'].map{ |o| UnspentOutput.new(o) }
83
+ end
84
+
85
+ def get_latest_block()
86
+ resource = 'latestblock'
87
+ response = @client.call_api(resource, method: 'get')
88
+ return LatestBlock.new(JSON.parse(response))
89
+ end
90
+
91
+ def get_unconfirmed_tx()
92
+ params = { 'format' => 'json' }
93
+ resource = 'unconfirmed-transactions'
94
+ response = @client.call_api(resource, method: 'get', data: params)
95
+ return JSON.parse(response)['txs'].map{ |t| Transaction.new(t) }
96
+ end
97
+
98
+ def get_blocks(time = nil, pool_name = nil)
99
+ params = { 'format' => 'json' }
100
+ resource = "blocks/"
101
+ if !time.nil?
102
+ resource += time.to_s
103
+ elsif !pool_name.nil?
104
+ resource += pool_name
105
+ end
106
+ response = @client.call_api(resource, method: 'get', data: params)
107
+ return JSON.parse(response)['blocks'].map{ |b| SimpleBlock.new(b) }
108
+ end
109
+
110
+ private
111
+ def get_block(hash_or_index)
112
+ resource = 'rawblock/' + hash_or_index
113
+ response = @client.call_api(resource, method: 'get')
114
+ return Block.new(JSON.parse(response))
115
+ end
116
+
117
+ private
118
+ def get_tx(hash_or_index)
119
+ resource = 'rawtx/' + hash_or_index
120
+ response = @client.call_api(resource, method: 'get')
121
+ return Transaction.new(JSON.parse(response))
122
+ end
123
+
124
+ private
125
+ def get_address(address, limit = MAX_TRANSACTIONS_PER_REQUEST,
126
+ offset = 0, filter = FilterType::REMOVE_UNSPENDABLE)
127
+ params = { 'format' => 'json', 'limit' => limit, 'offset' => offset, 'filter' => filter }
128
+ resource = 'rawaddr/' + address
129
+ response = @client.call_api(resource, method: 'get', data: params)
130
+ return Address.new(JSON.parse(response))
131
+ end
132
+ end
133
+
134
+ def self.get_block(hash_or_index, api_code = nil)
135
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__, hash_or_index)
136
+ end
137
+
138
+ def self.get_tx(hash_or_index, api_code = nil)
139
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__, hash_or_index)
140
+ end
19
141
 
20
142
  def self.get_block_height(height, api_code = nil)
21
- params = { 'format' => 'json' }
22
- if !api_code.nil? then params['api_code'] = api_code end
23
- resource = "block-height/#{height}"
24
- response = Blockchain::call_api(resource, method: 'get', data: params)
25
- return JSON.parse(response)['blocks'].map{ |b| Block.new(b) }
143
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__, height)
26
144
  end
27
145
 
28
- def self.get_address(address, api_code = nil)
29
- params = api_code.nil? ? { } : { 'api_code' => api_code }
30
- resource = 'rawaddr/' + address
31
- response = Blockchain::call_api(resource, method: 'get', data: params)
32
- return Address.new(JSON.parse(response))
33
- end
146
+ def self.get_address(address, api_code = nil,
147
+ limit = MAX_TRANSACTIONS_PER_REQUEST, offset = 0,
148
+ filter = FilterType::REMOVE_UNSPENDABLE)
149
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__, address, limit, offset, filter)
150
+ end
34
151
 
35
- def self.get_unspent_outputs(address, api_code = nil)
36
- params = { 'active' => address }
37
- if !api_code.nil? then params['api_code'] = api_code end
38
- resource = 'unspent'
39
- response = Blockchain::call_api(resource, method: 'get', data: params)
40
- return JSON.parse(response)['unspent_outputs'].map{ |o| UnspentOutput.new(o) }
152
+ def self.get_unspent_outputs(address_array, api_code = nil,
153
+ limit = DEFAULT_UNSPENT_TRANSACTIONS_PER_REQUEST, confirmations = 0)
154
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__, limit, confirmations)
41
155
  end
42
156
 
43
157
  def self.get_unconfirmed_tx(api_code = nil)
44
- params = { 'format' => 'json' }
45
- if !api_code.nil? then params['api_code'] = api_code end
46
- resource = 'unconfirmed-transactions'
47
- response = Blockchain::call_api(resource, method: 'get', data: params)
48
- return JSON.parse(response)['txs'].map{ |t| Transaction.new(t) }
158
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__)
49
159
  end
50
160
 
51
161
  def self.get_blocks(api_code = nil, time: nil, pool_name: nil)
52
- params = { 'format' => 'json' }
53
- if !api_code.nil? then params['api_code'] = api_code end
54
- resource = "blocks/"
55
- if !time.nil?
56
- resource += time.to_s
57
- elsif !pool_name.nil?
58
- resource += pool_name
59
- end
60
- response = Blockchain::call_api(resource, method: 'get', data: params)
61
- return JSON.parse(response)['blocks'].map{ |b| SimpleBlock.new(b) }
162
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__, time, pool_name)
62
163
  end
63
-
164
+
64
165
  def self.get_latest_block(api_code = nil)
65
- params = {}
66
- if !api_code.nil? then params['api_code'] = api_code end
67
- resource = 'latestblock'
68
- response = Blockchain::call_api(resource, method: 'get', data: params)
69
- return LatestBlock.new(JSON.parse(response))
70
- end
71
-
72
- def self.get_inventory_data(hash, api_code = nil)
73
- params = { 'format' => 'json' }
74
- if !api_code.nil? then params['api_code'] = api_code end
75
- resource = "inv/#{hash}"
76
- response = Blockchain::call_api(resource, method: 'get', data: params)
77
- return InventoryData.new(JSON.parse(response))
166
+ Blockchain::BlockExplorer.new(nil, api_code).proxy(__method__)
78
167
  end
79
168
 
80
169
  class SimpleBlock
@@ -82,7 +171,7 @@ module Blockchain
82
171
  attr_reader :hash
83
172
  attr_reader :time
84
173
  attr_reader :main_chain
85
-
174
+
86
175
  def initialize(b)
87
176
  @height = b['height']
88
177
  @hash = b['hash']
@@ -97,7 +186,7 @@ module Blockchain
97
186
  attr_reader :block_index
98
187
  attr_reader :height
99
188
  attr_reader :tx_indexes
100
-
189
+
101
190
  def initialize(b)
102
191
  @hash = b['hash']
103
192
  @time = b['time']
@@ -115,7 +204,7 @@ module Blockchain
115
204
  attr_reader :value
116
205
  attr_reader :value_hex
117
206
  attr_reader :confirmations
118
-
207
+
119
208
  def initialize(o)
120
209
  @tx_hash = o['tx_hash']
121
210
  @tx_index = o['tx_index']
@@ -135,18 +224,48 @@ module Blockchain
135
224
  attr_reader :total_sent
136
225
  attr_reader :final_balance
137
226
  attr_reader :transactions
138
-
227
+
139
228
  def initialize(a)
140
- @hash160 = a['hash160']
229
+ @hash160 = a['hash160'] == nil ? nil : a['hash160']
141
230
  @address = a['address']
142
231
  @n_tx = a['n_tx']
143
232
  @total_received = a['total_received']
144
233
  @total_sent = a['total_sent']
145
234
  @final_balance = a['final_balance']
146
- @transactions = a['txs'].map{ |tx| Transaction.new(tx) }
235
+ @transactions = a['txs'] == nil ? nil : a['txs'].map{ |tx| Transaction.new(tx) }
147
236
  end
148
237
  end
149
238
 
239
+ class MultiAddress
240
+ attr_reader :addresses
241
+ attr_reader :transactions
242
+
243
+ def initialize(ma)
244
+ @addresses = ma['addresses'].map{ |a| Address.new(a) }
245
+ @transactions = ma['txs'].map{ |tx| Transaction.new(tx) }
246
+ end
247
+ end
248
+
249
+ class Xpub < Address
250
+ attr_reader :change_index
251
+ attr_reader :account_index
252
+ attr_reader :gap_limit
253
+
254
+ def initialize(x)
255
+ addr = x['addresses'][0]
256
+ @change_index = addr['change_index']
257
+ @account_index = addr['account_index']
258
+ @gap_limit = addr['gap_limit']
259
+ @hash160 = addr['hash160'] == nil ? nil : addr['hash160']
260
+ @address = addr['address']
261
+ @n_tx = addr['n_tx']
262
+ @total_received = addr['total_received']
263
+ @total_sent = addr['total_sent']
264
+ @final_balance = addr['final_balance']
265
+ @transactions = addr['txs'] == nil ? nil : addr['txs'].map{ |tx| Transaction.new(tx) }
266
+ end
267
+ end
268
+
150
269
  class Input
151
270
  attr_reader :n
152
271
  attr_reader :value
@@ -206,7 +325,7 @@ module Blockchain
206
325
  attr_reader :size
207
326
  attr_reader :inputs
208
327
  attr_reader :outputs
209
-
328
+
210
329
  def initialize(t)
211
330
  @double_spend = t.fetch('double_spend', false)
212
331
  @block_height = t.fetch('block_height', false)
@@ -218,18 +337,18 @@ module Blockchain
218
337
  @size = t['size']
219
338
  @inputs = t['inputs'].map{ |i| Input.new(i) }
220
339
  @outputs = t['out'].map{ |o| Output.new(o) }
221
-
340
+
222
341
  if @block_height.nil?
223
342
  @block_height = -1
224
343
  end
225
344
  end
226
-
345
+
227
346
  def adjust_block_height(h)
228
347
  @block_height = h
229
348
  end
230
349
  end
231
350
 
232
-
351
+
233
352
  class Block
234
353
  attr_reader :hash
235
354
  attr_reader :version
@@ -247,7 +366,7 @@ module Blockchain
247
366
  attr_reader :received_time
248
367
  attr_reader :relayed_by
249
368
  attr_reader :transactions
250
-
369
+
251
370
  def initialize(b)
252
371
  @hash = b['hash']
253
372
  @version = b['ver']
@@ -268,31 +387,16 @@ module Blockchain
268
387
  @transactions.each do |tx|
269
388
  tx.adjust_block_height(@height)
270
389
  end
271
-
390
+
272
391
  if @received_time.nil?
273
392
  @received_time = @time
274
393
  end
275
394
  end
276
395
  end
277
396
 
278
- class InventoryData
279
- attr_reader :hash
280
- attr_reader :type
281
- attr_reader :initial_time
282
- attr_reader :initial_ip
283
- attr_reader :nconnected
284
- attr_reader :relayed_count
285
- attr_reader :relayed_percent
286
-
287
- def initialize(i)
288
- @hash = i['hash']
289
- @type = i['type']
290
- @initial_time = i['initial_time'].to_i
291
- @initial_ip = i['initial_ip']
292
- @nconnected = i['nconnected'].to_i
293
- @relayed_count = i['relayed_count'].to_i
294
- @relayed_percent = i['relayed_percent'].to_i
295
- end
296
- end
297
-
397
+ module FilterType
398
+ ALL = 4
399
+ CONFIRMED_ONLY = 5
400
+ REMOVE_UNSPENDABLE = 6
401
+ end
298
402
  end
@@ -0,0 +1,56 @@
1
+ require 'net/http'
2
+
3
+ module Blockchain
4
+
5
+ DEFAULT_BASE_URL = 'https://blockchain.info/'
6
+ TIMEOUT_SECONDS = 10
7
+
8
+ class Client
9
+
10
+ attr_reader :base_url
11
+ attr_reader :api_code
12
+
13
+ def initialize(base_url = nil, api_code = nil)
14
+ @base_url = base_url.nil? ? DEFAULT_BASE_URL : base_url
15
+ @api_code = api_code
16
+ end
17
+
18
+ class APIException < StandardError
19
+ end
20
+
21
+ def call_api(resource, method: 'get', data: nil)
22
+ url = URI.parse(@base_url + resource)
23
+ http = Net::HTTP.new(url.host, url.port)
24
+ http.use_ssl = @base_url.start_with? 'https://'
25
+ http.read_timeout = TIMEOUT_SECONDS
26
+
27
+ request = nil
28
+ if method == 'get'
29
+ url.query = data.nil? ? nil : URI.encode_www_form(data)
30
+ request = Net::HTTP::Get.new(url.request_uri)
31
+ elsif method == 'post'
32
+ request = Net::HTTP::Post.new(url.request_uri)
33
+ request.content_type = 'application/x-www-form-urlencoded'
34
+ if data != nil
35
+ then
36
+ if !@api_code.nil? then data['api_code'] = @api_code
37
+ end
38
+ request.set_form_data(data)
39
+ else
40
+ if
41
+ !@api_code.nil? then data = { 'api_code' => @api_code }
42
+ request.set_form_data(data)
43
+ else {}
44
+
45
+ end
46
+ end
47
+ end
48
+ response = http.request(request)
49
+ if response.code != '200'
50
+ raise APIException, response.body
51
+ end
52
+ return response.body
53
+ end
54
+ end
55
+
56
+ end
@@ -1,27 +1,42 @@
1
1
  require 'json'
2
- require_relative 'util'
2
+ require_relative 'client'
3
3
 
4
4
  module Blockchain
5
5
 
6
- def self.create_wallet(password, api_code, url, priv: nil, label: nil, email: nil)
6
+ DEFAULT_WALLET_URL = 'http://127.0.0.1:3000/'
7
7
 
8
- params = { 'password' => password, 'api_code' => api_code }
9
-
10
- if !priv.nil?
11
- params['priv'] = priv
12
- end
13
- if !label.nil?
14
- params['label'] = label
15
- end
16
- if !email.nil?
17
- params['email'] = email
18
- end
19
-
20
- response = Blockchain::call_api('api/v2/create', method: 'post', data: params, base_url: url)
21
- json_response = JSON.parse(response)
22
- return CreateWalletResponse.new(json_response['guid'],
23
- json_response['address'],
24
- json_response['label'])
8
+ class WalletCreator
9
+
10
+ attr_reader :client
11
+
12
+ def initialize(base_url = DEFAULT_WALLET_URL, api_code)
13
+ @client = Client.new(base_url, api_code)
14
+ end
15
+
16
+ def create_wallet(password, priv: nil, label: nil, email: nil)
17
+ params = { 'password' => password }
18
+
19
+ if !priv.nil?
20
+ params['priv'] = priv
21
+ end
22
+ if !label.nil?
23
+ params['label'] = label
24
+ end
25
+ if !email.nil?
26
+ params['email'] = email
27
+ end
28
+
29
+ response = @client.call_api('api/v2/create', method: 'post', data: params)
30
+ json_response = JSON.parse(response)
31
+ return CreateWalletResponse.new(json_response['guid'],
32
+ json_response['address'],
33
+ json_response['label'])
34
+ end
35
+ end
36
+
37
+ def self.create_wallet(password, api_code, url, priv: nil, label: nil, email: nil)
38
+ warn "[DEPRECATED] avoid use of static methods, use an instance of WalletCreator class instead."
39
+ Blockchain::WalletCreator.new(url, api_code).create_wallet(password, priv, label, email)
25
40
  end
26
41
 
27
42
  class CreateWalletResponse