onchain 0.4.2 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e90d6f43622f2013449f0c645899416c69cda45
4
- data.tar.gz: 0d2e9ee4ab58da945e8f8cb47c70659174a765bc
3
+ metadata.gz: 434e9d7b76fb77c75cd6ee42ee305c88ff1e654f
4
+ data.tar.gz: 3414885379272cc9c61a53a1310de8a30652c73b
5
5
  SHA512:
6
- metadata.gz: a86aac2c975f70495a05579fed758f0f8555cc05d40edbe093168e0a451ee19b82f47564c45e4d2d655004494eaa6b44f19a2dcdcfc743405bc21ca19caaa3bd
7
- data.tar.gz: d6935006cc73b8f8997d47a200d4dfe943125472f076c1774969f6e46b6cc1961461c79328c4c7860586adf7a1d3bbc579258f7a8ce04a8fcebf7374becb9572
6
+ metadata.gz: 15e32c96f9ad075a6d390ed51c82fede89aadc6d964414b224c92887bb81e027e0730ff2ff3a7fd22e73535470966d0e1e4a2ea442f3bab26c5d99725e025437
7
+ data.tar.gz: f38144aac4022320524fc41fb273b6f88afac0795f6b08598c4620d948f4faa035dedb287f9cd509205fae25b20eeae4f3023bb0d4615a858a29577b090955d3
@@ -0,0 +1,289 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ class OnChain
5
+ class << self
6
+
7
+ BALANCE_CACHE_FOR = 120
8
+ API_CACHE_FOR = 60
9
+
10
+ @@cache = {}
11
+
12
+ def cache_write(key, data, max_age=0)
13
+ @@cache[key] = [Time.now, data, max_age]
14
+ end
15
+
16
+ def cache_read(key)
17
+ # if the API URL exists as a key in cache, we just return it
18
+ # we also make sure the data is fresh
19
+ if @@cache.has_key? key
20
+ return @@cache[key][1] if Time.now-@@cache[key][0] < @@cache[key][2]
21
+ end
22
+ end
23
+
24
+
25
+ def get_all_balances(addresses)
26
+
27
+ if ! blockr_down?
28
+ bal = blockr_get_all_balances(addresses)
29
+ return bal
30
+ end
31
+
32
+ if ! blockchain_down?
33
+ bal = blockinfo_get_all_balances(addresses)
34
+ return bal
35
+ end
36
+ end
37
+
38
+ def get_balance(address)
39
+
40
+ # These guys make 300k per month so hammer them first.
41
+ if ! blockchain_down?
42
+ bal = blockinfo_balance(address)
43
+ if ! bal.instance_of? String
44
+ return bal
45
+ end
46
+ end
47
+
48
+ # Looks like blockchain is down, let's try blockr
49
+ if ! blockr_down?
50
+ bal = blockr_balance(address)
51
+ if ! bal.instance_of? String
52
+ return bal
53
+ end
54
+ end
55
+
56
+ # OK I give up.
57
+ 'Balance could not be retrieved'
58
+ end
59
+
60
+ def send_tx(tx_hex)
61
+ #payload = { :tx => tx_hex }
62
+ #HTTParty.post('http://blockchain.info/pushtx', {:body => payload})
63
+
64
+ # Blockchain doesn't support multi sig, so only use blockr
65
+ uri = URI.parse("http://btc.blockr.io/api/v1/tx/push")
66
+ http = Net::HTTP.new(uri.host, uri.port)
67
+
68
+ request = Net::HTTP::Post.new(uri.request_uri)
69
+ request.body = '{"hex":"' + tx_hex + '"}'
70
+ response = http.request(request)
71
+ return response
72
+ end
73
+
74
+ def get_history
75
+ return []
76
+ end
77
+
78
+ def get_unspent_outs(address)
79
+
80
+
81
+ # Looks like blockchain is down, let's try blockr
82
+ if ! blockr_down?
83
+ return blockr_unspent(address)
84
+ end
85
+
86
+ if ! blockchain_down?
87
+ return blockinfo_unspent(address)
88
+ end
89
+
90
+ # OK I give up.
91
+ 'Unspent outputs could not be retrieved'
92
+
93
+ end
94
+
95
+ def blockchain_is_down
96
+ cache_write(:blockchainapi, 'it is down', 60)
97
+ end
98
+
99
+ def blockchain_down?
100
+ if cache_read(:blockchainapi) != nil
101
+ return true
102
+ end
103
+ return false
104
+ end
105
+
106
+ def blockr_is_down
107
+ cache_write(:blockrapi, 'it is down', 60)
108
+ end
109
+
110
+ def blockr_down?
111
+ if cache_read(:blockrapi) != nil
112
+ return true
113
+ end
114
+ return false
115
+ end
116
+
117
+ def blockinfo_get_all_balances(addresses)
118
+ begin
119
+ base = "https://blockchain.info/multiaddr?&simple=true&active="
120
+
121
+ addresses.each do |address|
122
+ base = base + address + '|'
123
+ end
124
+
125
+ json = fetch_response(URI::encode(base))
126
+
127
+ addresses.each do |address|
128
+ bal = json[address]['final_balance'] / 100000000.0
129
+ cache_write(address, bal, BALANCE_CACHE_FOR)
130
+ end
131
+
132
+ rescue
133
+ 'Balance could not be retrieved'
134
+ end
135
+ end
136
+
137
+ def blockinfo_unspent(address)
138
+ begin
139
+ base_url = "http://blockchain.info/unspent?active=#{address}"
140
+ json = fetch_response(base_url, true)
141
+
142
+ unspent = []
143
+
144
+ json['unspent_outputs'].each do |data|
145
+ line = []
146
+ line << reverse_blockchain_tx(data['tx_hash'])
147
+ line << data['tx_output_n']
148
+ line << data['script']
149
+ line << data['value']
150
+ unspent << line
151
+ end
152
+
153
+ return unspent
154
+ rescue Exception => e
155
+ puts e.to_s
156
+ 'Unspent outputs could not be retrieved'
157
+ end
158
+ end
159
+
160
+ def blockinfo_balance(address)
161
+ begin
162
+ if cache_read(address) == nil
163
+ puts "cache is empty"
164
+ json = block_chain('address', address, "&limit=0")
165
+ if json.key?('final_balance')
166
+ bal = json['final_balance'] / 100000000.0
167
+ cache_write(address, bal, BALANCE_CACHE_FOR)
168
+ else
169
+ cache_write(address, 'Error', BALANCE_CACHE_FOR)
170
+ end
171
+ end
172
+ bal = cache_read(address)
173
+ if bal.class == Fixnum
174
+ bal = bal.to_f
175
+ end
176
+ return bal
177
+ rescue
178
+ 'Balance could not be retrieved'
179
+ end
180
+ end
181
+
182
+ def blockr_unspent(address)
183
+ begin
184
+ base_url = "http://btc.blockr.io/api/v1/address/unspent/#{address}"
185
+ json = fetch_response(base_url, true)
186
+
187
+ unspent = []
188
+
189
+ json['data']['unspent'].each do |data|
190
+ line = []
191
+ line << data['tx']
192
+ line << data['n']
193
+ line << data['script']
194
+ line << (data['amount'].to_f * 100000000).to_i
195
+ unspent << line
196
+ end
197
+
198
+ return unspent
199
+ rescue Exception => e
200
+ puts e.to_s
201
+ 'Unspent outputs could not be retrieved'
202
+ end
203
+ end
204
+
205
+ def blockr_get_all_balances(addresses)
206
+ begin
207
+ base = "https://blockr.io/api/v1/address/balance/"
208
+
209
+ addresses.each do |address|
210
+ base = base + address + ','
211
+ end
212
+
213
+ json = fetch_response(URI::encode(base))
214
+
215
+ json['data'].each do |data|
216
+ bal = data['balance']
217
+ addr = data['address']
218
+ cache_write(addr, bal, BALANCE_CACHE_FOR)
219
+ end
220
+
221
+ rescue
222
+ 'Balance could not be retrieved'
223
+ end
224
+ end
225
+
226
+ def blockr_balance(address)
227
+ begin
228
+ if cache_read(address) == nil
229
+ json = blockr('address/balance', address)
230
+ if json.key?('data')
231
+ bal = json['data']['balance'].to_f
232
+ cache_write(address, bal, BALANCE_CACHE_FOR)
233
+ else
234
+ cache_write(address, 'Error', BALANCE_CACHE_FOR)
235
+ end
236
+ end
237
+ return cache_read(address)
238
+ rescue Exception => e
239
+ puts e
240
+ 'Balance could not be retrieved'
241
+ end
242
+ end
243
+
244
+ def blockr(cmd, address, params = "")
245
+ if ! blockr_down?
246
+ begin
247
+ base_url = "http://blockr.io/api/v1/#{cmd}/#{address}" + params
248
+ fetch_response(base_url, true)
249
+ rescue
250
+ blockr_is_down
251
+ end
252
+ end
253
+ end
254
+
255
+ def block_chain(cmd, address, params = "")
256
+ if ! blockchain_down?
257
+ begin
258
+ base_url = "http://blockchain.info/#{cmd}/#{address}?format=json" + params
259
+
260
+ puts base_url
261
+ fetch_response(base_url, true)
262
+ rescue
263
+ blockchain_is_down
264
+ end
265
+ end
266
+ end
267
+
268
+ def fetch_response(url, do_json=true)
269
+ resp = Net::HTTP.get_response(URI.parse(url))
270
+ data = resp.body
271
+
272
+ if do_json
273
+ result = JSON.parse(data)
274
+ else
275
+ data
276
+ end
277
+ end
278
+
279
+ def reverse_blockchain_tx(hash)
280
+ bytes = hash.scan(/../).map { |x| x.hex.chr }.join
281
+
282
+ bytes = bytes.reverse
283
+
284
+ return hash.scan(/../).reverse.join
285
+ end
286
+
287
+
288
+ end
289
+ end
@@ -0,0 +1,41 @@
1
+ class OnChain
2
+ class << self
3
+
4
+
5
+ # With a bunch of HD wallet paths, build a transaction
6
+ # That pays all the coins to a certain address
7
+ def sweep(paths, mpk, destination_address)
8
+
9
+ master = MoneyTree::Node.from_serialized_address(mpk)
10
+
11
+ tx = Bitcoin::Protocol::Tx.new
12
+
13
+ amount = 0
14
+
15
+ paths.each do |path|
16
+
17
+ address = master.node_for_path(path).to_address
18
+
19
+ unspent = get_unspent_outs(address)
20
+
21
+ unspent.each do |spent|
22
+ txin = Bitcoin::Protocol::TxIn.new
23
+
24
+ txin.prev_out = spent[0]
25
+ txin.prev_out_index = spent[1]
26
+ amount += spent[3].to_i
27
+
28
+ tx.add_in(txin)
29
+ end
30
+ end
31
+
32
+ txout = Bitcoin::Protocol::TxOut.new(amount, Bitcoin::Script.from_string(
33
+ "OP_DUP OP_HASH160 #{destination_address} " +
34
+ "OP_EQUALVERIFY OP_CHECKSIG").to_payload)
35
+
36
+ tx.add_out(txout)
37
+ puts tx.to_json
38
+ end
39
+
40
+ end
41
+ end
data/lib/onchain.rb CHANGED
@@ -1,289 +1,4 @@
1
- require 'net/http'
2
- require 'json'
3
-
4
- class OnChain
5
- class << self
6
-
7
- BALANCE_CACHE_FOR = 120
8
- API_CACHE_FOR = 60
9
-
10
- @@cache = {}
11
-
12
- def cache_write(key, data, max_age=0)
13
- @@cache[key] = [Time.now, data, max_age]
14
- end
15
-
16
- def cache_read(key)
17
- # if the API URL exists as a key in cache, we just return it
18
- # we also make sure the data is fresh
19
- if @@cache.has_key? key
20
- return @@cache[key][1] if Time.now-@@cache[key][0] < @@cache[key][2]
21
- end
22
- end
23
-
24
-
25
- def get_all_balances(addresses)
26
-
27
- if ! blockr_down?
28
- bal = blockr_get_all_balances(addresses)
29
- return bal
30
- end
31
-
32
- if ! blockchain_down?
33
- bal = blockinfo_get_all_balances(addresses)
34
- return bal
35
- end
36
- end
37
-
38
- def get_balance(address)
39
-
40
- # These guys make 300k per month so hammer them first.
41
- if ! blockchain_down?
42
- bal = blockinfo_balance(address)
43
- if ! bal.instance_of? String
44
- return bal
45
- end
46
- end
47
-
48
- # Looks like blockchain is down, let's try blockr
49
- if ! blockr_down?
50
- bal = blockr_balance(address)
51
- if ! bal.instance_of? String
52
- return bal
53
- end
54
- end
55
-
56
- # OK I give up.
57
- 'Balance could not be retrieved'
58
- end
59
-
60
- def send_tx(tx_hex)
61
- #payload = { :tx => tx_hex }
62
- #HTTParty.post('http://blockchain.info/pushtx', {:body => payload})
63
-
64
- # Blockchain doesn't support multi sig, so only use blockr
65
- uri = URI.parse("http://btc.blockr.io/api/v1/tx/push")
66
- http = Net::HTTP.new(uri.host, uri.port)
67
-
68
- request = Net::HTTP::Post.new(uri.request_uri)
69
- request.body = '{"hex":"' + tx_hex + '"}'
70
- response = http.request(request)
71
- return response
72
- end
73
-
74
- def get_history
75
- return []
76
- end
77
-
78
- def get_unspent_outs(address)
79
-
80
-
81
- # Looks like blockchain is down, let's try blockr
82
- if ! blockr_down?
83
- return blockr_unspent(address)
84
- end
85
-
86
- if ! blockchain_down?
87
- return blockinfo_unspent(address)
88
- end
89
-
90
- # OK I give up.
91
- 'Unspent outputs could not be retrieved'
92
-
93
- end
94
-
95
- def blockchain_is_down
96
- cache_write(:blockchainapi, 'it is down', 60)
97
- end
98
-
99
- def blockchain_down?
100
- if cache_read(:blockchainapi) != nil
101
- return true
102
- end
103
- return false
104
- end
105
-
106
- def blockr_is_down
107
- cache_write(:blockrapi, 'it is down', 60)
108
- end
109
-
110
- def blockr_down?
111
- if cache_read(:blockrapi) != nil
112
- return true
113
- end
114
- return false
115
- end
116
-
117
- def blockinfo_get_all_balances(addresses)
118
- begin
119
- base = "https://blockchain.info/multiaddr?&simple=true&active="
120
-
121
- addresses.each do |address|
122
- base = base + address + '|'
123
- end
124
-
125
- json = fetch_response(URI::encode(base))
126
-
127
- addresses.each do |address|
128
- bal = json[address]['final_balance'] / 100000000.0
129
- cache_write(address, bal, BALANCE_CACHE_FOR)
130
- end
131
-
132
- rescue
133
- 'Balance could not be retrieved'
134
- end
135
- end
136
-
137
- def blockinfo_unspent(address)
138
- begin
139
- base_url = "http://blockchain.info/unspent?active=#{address}"
140
- json = fetch_response(base_url, true)
141
-
142
- unspent = []
143
-
144
- json['unspent_outputs'].each do |data|
145
- line = []
146
- line << reverse_blockchain_tx(data['tx_hash'])
147
- line << data['tx_output_n']
148
- line << data['script']
149
- line << data['value']
150
- unspent << line
151
- end
152
-
153
- return unspent
154
- rescue Exception => e
155
- puts e.to_s
156
- 'Unspent outputs could not be retrieved'
157
- end
158
- end
159
-
160
- def blockinfo_balance(address)
161
- begin
162
- if cache_read(address) == nil
163
- puts "cache is empty"
164
- json = block_chain('address', address, "&limit=0")
165
- if json.key?('final_balance')
166
- bal = json['final_balance'] / 100000000.0
167
- cache_write(address, bal, BALANCE_CACHE_FOR)
168
- else
169
- cache_write(address, 'Error', BALANCE_CACHE_FOR)
170
- end
171
- end
172
- bal = cache_read(address)
173
- if bal.class == Fixnum
174
- bal = bal.to_f
175
- end
176
- return bal
177
- rescue
178
- 'Balance could not be retrieved'
179
- end
180
- end
181
-
182
- def blockr_unspent(address)
183
- begin
184
- base_url = "http://btc.blockr.io/api/v1/address/unspent/#{address}"
185
- json = fetch_response(base_url, true)
186
-
187
- unspent = []
188
-
189
- json['data']['unspent'].each do |data|
190
- line = []
191
- line << data['tx']
192
- line << data['n']
193
- line << data['script']
194
- line << (data['amount'].to_f * 100000000).to_i
195
- unspent << line
196
- end
197
-
198
- return unspent
199
- rescue Exception => e
200
- puts e.to_s
201
- 'Unspent outputs could not be retrieved'
202
- end
203
- end
204
-
205
- def blockr_get_all_balances(addresses)
206
- begin
207
- base = "https://blockr.io/api/v1/address/balance/"
208
-
209
- addresses.each do |address|
210
- base = base + address + ','
211
- end
212
-
213
- json = fetch_response(URI::encode(base))
214
-
215
- json['data'].each do |data|
216
- bal = data['balance']
217
- addr = data['address']
218
- cache_write(addr, bal, BALANCE_CACHE_FOR)
219
- end
220
-
221
- rescue
222
- 'Balance could not be retrieved'
223
- end
224
- end
225
-
226
- def blockr_balance(address)
227
- begin
228
- if cache_read(address) == nil
229
- json = blockr('address/balance', address)
230
- if json.key?('data')
231
- bal = json['data']['balance'].to_f
232
- cache_write(address, bal, BALANCE_CACHE_FOR)
233
- else
234
- cache_write(address, 'Error', BALANCE_CACHE_FOR)
235
- end
236
- end
237
- return cache_read(address)
238
- rescue Exception => e
239
- puts e
240
- 'Balance could not be retrieved'
241
- end
242
- end
243
-
244
- def blockr(cmd, address, params = "")
245
- if ! blockr_down?
246
- begin
247
- base_url = "http://blockr.io/api/v1/#{cmd}/#{address}" + params
248
- fetch_response(base_url, true)
249
- rescue
250
- blockr_is_down
251
- end
252
- end
253
- end
254
-
255
- def block_chain(cmd, address, params = "")
256
- if ! blockchain_down?
257
- begin
258
- base_url = "http://blockchain.info/#{cmd}/#{address}?format=json" + params
259
-
260
- puts base_url
261
- fetch_response(base_url, true)
262
- rescue
263
- blockchain_is_down
264
- end
265
- end
266
- end
267
-
268
- def fetch_response(url, do_json=true)
269
- resp = Net::HTTP.get_response(URI.parse(url))
270
- data = resp.body
271
-
272
- if do_json
273
- result = JSON.parse(data)
274
- else
275
- data
276
- end
277
- end
278
-
279
- def reverse_blockchain_tx(hash)
280
- bytes = hash.scan(/../).map { |x| x.hex.chr }.join
281
-
282
- bytes = bytes.reverse
283
-
284
- return hash.scan(/../).reverse.join
285
- end
286
-
287
-
288
- end
289
- end
1
+ require 'onchain/block_chain.rb'
2
+ require 'onchain/sweeper.rb'
3
+ require 'money-tree'
4
+ require 'bitcoin'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onchain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Purton
@@ -11,7 +11,7 @@ cert_chain: []
11
11
  date: 2014-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
14
+ name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,21 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: vcr
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: ir_b
28
+ name: rspec
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
@@ -53,13 +39,13 @@ dependencies:
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
- name: guard-rspec
42
+ name: money-tree
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - ">="
60
46
  - !ruby/object:Gem::Version
61
47
  version: '0'
62
- type: :development
48
+ type: :runtime
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
@@ -67,13 +53,13 @@ dependencies:
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
- name: webmock
56
+ name: bitcoin-ruby
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - ">="
74
60
  - !ruby/object:Gem::Version
75
61
  version: '0'
76
- type: :development
62
+ type: :runtime
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
@@ -82,16 +68,14 @@ dependencies:
82
68
  version: '0'
83
69
  description: Call 3rd party API's but also switch API's if a 3rd party is down
84
70
  email:
85
- - ian@onchain.com
71
+ - ian@onchain.io
86
72
  executables: []
87
73
  extensions: []
88
74
  extra_rdoc_files: []
89
75
  files:
90
- - ".gitignore"
91
- - README.md
92
76
  - lib/onchain.rb
93
- - lib/onchain/version.rb
94
- - onchain.gemspec
77
+ - lib/onchain/block_chain.rb
78
+ - lib/onchain/sweeper.rb
95
79
  homepage: https://github.com/onchain/onchain-gem
96
80
  licenses: []
97
81
  metadata: {}
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .DS_Store
4
- .bundle
5
- .rvmrc
6
- .ruby-version
7
- .yardoc
8
- .rake_tasks~
9
- Gemfile.lock
10
- coverage/*
11
- doc/*
12
- log/*
13
- pkg/*
14
- .idea/*
data/README.md DELETED
@@ -1,4 +0,0 @@
1
- onchain-gem
2
- ===========
3
-
4
- Ruby gem to access 3rd party block chain API and unchain services.
@@ -1,3 +0,0 @@
1
- module Onchain
2
- VERSION = "0.4.2"
3
- end
data/onchain.gemspec DELETED
@@ -1,21 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/onchain/version', __FILE__)
3
-
4
- Gem::Specification.new do |gem|
5
- gem.authors = ["Ian Purton"]
6
- gem.email = ["ian@onchain.com"]
7
- gem.summary = %q{Ruby wrapper for various 3rd-party bitcoin APIs}
8
- gem.description = %q{Call 3rd party API's but also switch API's if a 3rd party is down}
9
- gem.homepage = "https://github.com/onchain/onchain-gem"
10
-
11
- gem.files = `git ls-files`.split($\)
12
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
- gem.name = "onchain"
15
- gem.require_paths = ["lib"]
16
- gem.version = Onchain::VERSION
17
-
18
- %w{rspec vcr ir_b guard-rspec webmock}.each do |gem_library|
19
- gem.add_development_dependency gem_library
20
- end
21
- end