blockchain 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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