sibit 0.12.5 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a60738f08eff40722e87f46d4d8e16c7f59f2d2488bdb95bfee6625057bf38f3
4
- data.tar.gz: a62597ee21efd55bb466316f21960df713d7ab7fc5063eb4ad721524f8a87279
3
+ metadata.gz: 84dbc9a25777fde7ba775b8adc200bc19719c2acf153e0513795e8a8f0ffc161
4
+ data.tar.gz: 6520e17706434cd239f23e38d764ea4c0a82a8d319970ac5a7d46b5d6acaa55a
5
5
  SHA512:
6
- metadata.gz: 2e9a89b805345c0a8c07ccbb8325340e73d15cf518d11d2fb5bcc11796e8ba47652d05ebcb31210b497f830fcbcc7212c2ce301cbdcca2d78acc4de260fc292f
7
- data.tar.gz: 174ea54310174a0f8732510e1acc9e1aae389f87fa32e31e28b2df9ca687753641ceeb91d469156913dfafeb2e49c6be87cf30705e7bb5669934f6e46f99c0f5
6
+ metadata.gz: 7dd7c3cbef60a7ce2242a932d39d6812ddb704928d50f9a7343e1a349cc319d8913cbb7f221257ee2dc7d06a07053d6d7a19247ea35f538fe3b70c29b0a707ed
7
+ data.tar.gz: f927d8c28599512da7bc0d2c44af18bfbb79c4683aa916324e368b321a306f648bb17a43add9aeedd71af14052a45bbfa1cbdfc6cfa7b753d5feb92e333b4efc
data/.gitignore CHANGED
@@ -5,3 +5,4 @@ Gemfile.lock
5
5
  .bundle/
6
6
  .DS_Store
7
7
  rdoc/
8
+ tmp/
data/.travis.yml CHANGED
@@ -6,8 +6,10 @@ branches:
6
6
  only:
7
7
  - master
8
8
  install:
9
+ - gem install pdd -v 0.20.5
9
10
  - travis_retry bundle update
10
11
  script:
11
- - rake
12
+ - pdd -f /dev/null
13
+ - bundle exec rake
12
14
  after_success:
13
15
  - "bash <(curl -s https://codecov.io/bash)"
data/bin/sibit CHANGED
@@ -25,8 +25,11 @@ STDOUT.sync = true
25
25
 
26
26
  require 'slop'
27
27
  require 'backtrace'
28
+ require 'retriable_proxy'
28
29
  require_relative '../lib/sibit'
29
30
  require_relative '../lib/sibit/version'
31
+ require_relative '../lib/sibit/blockchain'
32
+ require_relative '../lib/sibit/btc'
30
33
 
31
34
  begin
32
35
  begin
@@ -53,17 +56,27 @@ Options are:"
53
56
  exit
54
57
  end
55
58
  o.bool '--verbose', 'Print all possible debug messages'
59
+ o.array '--api', 'Ordered List of APIs to use, e.g. "blockchain,btc"', default: ['blockchain']
56
60
  end
57
61
  rescue Slop::Error => ex
58
62
  raise ex.message
59
63
  end
60
64
  raise 'Try --help' if opts.arguments.empty?
61
- sibit = Sibit.new(
62
- log: opts[:verbose] ? STDOUT : nil,
63
- http: opts[:proxy] ? Sibit.proxy_http(opts[:proxy]) : Sibit.default_http,
64
- dry: opts[:dry],
65
- attempts: opts[:attempts]
66
- )
65
+ log = Sibit::Log.new(opts[:verbose] ? STDOUT : nil)
66
+ http = opts[:proxy] ? Sibit::HttpProxy.new(opts[:proxy]) : Sibit::Http.new
67
+ apis = opts[:api].map(&:downcase).map do |a|
68
+ api = nil
69
+ if a == 'blockchain'
70
+ api = Sibit::Blockchain.new(http: http, log: log, dry: opts[:dry])
71
+ elsif a == 'btc'
72
+ api = Sibit::Btc.new(http: http, log: log, dry: opts[:dry])
73
+ else
74
+ raise Sibit::Error, "Unknown API \"#{a}\""
75
+ end
76
+ api = RetriableProxy.for_object(api, on: Sibit::Error) if opts[:attempts] > 1
77
+ api
78
+ end
79
+ sibit = Sibit.new(log: log, api: apis)
67
80
  case opts.arguments[0]
68
81
  when 'price'
69
82
  puts sibit.price
data/features/cli.feature CHANGED
@@ -7,7 +7,7 @@ Feature: Command Line Processing
7
7
  And Stdout contains "--help"
8
8
 
9
9
  Scenario: Bitcoin price can be retrieved
10
- When I run bin/sibit with "price"
10
+ When I run bin/sibit with "price --attempts=4"
11
11
  Then Exit code is zero
12
12
 
13
13
  Scenario: Bitcoin latest block hash can be retrieved
@@ -23,7 +23,7 @@ Feature: Command Line Processing
23
23
  Then Exit code is zero
24
24
 
25
25
  Scenario: Bitcoin balance can be checked
26
- When I run bin/sibit with "balance 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose"
26
+ When I run bin/sibit with "balance 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose --api=blockchain,btc"
27
27
  Then Exit code is zero
28
28
 
29
29
  Scenario: Bitcoin fees can be printed
@@ -21,4 +21,5 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'simplecov'
24
+ require 'aruba/cucumber'
24
25
  require_relative '../../lib/sibit'
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'bitcoin'
24
+ require 'json'
25
+ require 'uri'
26
+ require_relative 'version'
27
+ require_relative 'error'
28
+ require_relative 'http'
29
+ require_relative 'json'
30
+
31
+ # Blockchain.info API.
32
+ #
33
+ # It works through the Blockchain API:
34
+ # https://www.blockchain.com/api/blockchain_api
35
+ #
36
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
37
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
38
+ # License:: MIT
39
+ class Sibit
40
+ # Blockchain.info API.
41
+ class Blockchain
42
+ # Constructor.
43
+ def initialize(log: Sibit::Log.new, http: Sibit::Http.new, dry: false)
44
+ @http = http
45
+ @log = log
46
+ @dry = dry
47
+ end
48
+
49
+ # Current price of BTC in USD (float returned).
50
+ def price(currency)
51
+ h = Sibit::Json.new(http: @http, log: @log).get(
52
+ URI('https://blockchain.info/ticker')
53
+ )[currency]
54
+ raise Error, "Unrecognized currency #{currency}" if h.nil?
55
+ h['15m']
56
+ end
57
+
58
+ # Gets the balance of the address, in satoshi.
59
+ def balance(address)
60
+ json = Sibit::Json.new(http: @http, log: @log).get(
61
+ URI("https://blockchain.info/rawaddr/#{address}")
62
+ )
63
+ @log.info("Total transactions: #{json['n_tx']}")
64
+ @log.info("Received/sent: #{json['total_received']}/#{json['total_sent']}")
65
+ json['final_balance']
66
+ end
67
+
68
+ # Get recommended fees, in satoshi per byte. The method returns
69
+ # a hash: { S: 12, M: 45, L: 100, XL: 200 }
70
+ def fees
71
+ json = Sibit::Json.new(http: @http, log: @log).get(
72
+ URI('https://bitcoinfees.earn.com/api/v1/fees/recommended')
73
+ )
74
+ @log.info("Current recommended Bitcoin fees: \
75
+ #{json['hourFee']}/#{json['halfHourFee']}/#{json['fastestFee']} sat/byte")
76
+ {
77
+ S: json['hourFee'] / 3,
78
+ M: json['hourFee'],
79
+ L: json['halfHourFee'],
80
+ XL: json['fastestFee']
81
+ }
82
+ end
83
+
84
+ # Sends a payment and returns the transaction hash.
85
+ def pay(amount, fee, sources, target, change)
86
+ p = price('USD')
87
+ satoshi = satoshi(amount)
88
+ f = mfee(fee, size_of(amount, sources))
89
+ satoshi += f if f.negative?
90
+ raise Error, "The fee #{f.abs} covers the entire amount" if satoshi.zero?
91
+ raise Error, "The fee #{f.abs} is bigger than the amount #{satoshi}" if satoshi.negative?
92
+ builder = Bitcoin::Builder::TxBuilder.new
93
+ unspent = 0
94
+ size = 100
95
+ utxos = Sibit::Json.new(http: @http, log: @log).get(
96
+ URI("https://blockchain.info/unspent?active=#{sources.keys.join('|')}&limit=1000")
97
+ )['unspent_outputs']
98
+ @log.info("#{utxos.count} UTXOs found, these will be used \
99
+ (value/confirmations at tx_hash):")
100
+ utxos.each do |utxo|
101
+ unspent += utxo['value']
102
+ builder.input do |i|
103
+ i.prev_out(utxo['tx_hash_big_endian'])
104
+ i.prev_out_index(utxo['tx_output_n'])
105
+ i.prev_out_script = [utxo['script']].pack('H*')
106
+ address = Bitcoin::Script.new([utxo['script']].pack('H*')).get_address
107
+ i.signature_key(key(sources[address]))
108
+ end
109
+ size += 180
110
+ @log.info(
111
+ " #{num(utxo['value'], p)}/#{utxo['confirmations']} at #{utxo['tx_hash_big_endian']}"
112
+ )
113
+ break if unspent > satoshi
114
+ end
115
+ if unspent < satoshi
116
+ raise Error, "Not enough funds to send #{num(satoshi, p)}, only #{num(unspent, p)} left"
117
+ end
118
+ builder.output(satoshi, target)
119
+ f = mfee(fee, size)
120
+ tx = builder.tx(
121
+ input_value: unspent,
122
+ leave_fee: true,
123
+ extra_fee: [f, Bitcoin.network[:min_tx_fee]].max,
124
+ change_address: change
125
+ )
126
+ left = unspent - tx.outputs.map(&:value).inject(&:+)
127
+ @log.info("A new Bitcoin transaction #{tx.hash} prepared:
128
+ #{tx.in.count} input#{tx.in.count > 1 ? 's' : ''}:
129
+ #{tx.inputs.map { |i| " in: #{i.prev_out.bth}:#{i.prev_out_index}" }.join("\n ")}
130
+ #{tx.out.count} output#{tx.out.count > 1 ? 's' : ''}:
131
+ #{tx.outputs.map { |o| "out: #{o.script.bth} / #{num(o.value, p)}" }.join("\n ")}
132
+ Min tx fee: #{num(Bitcoin.network[:min_tx_fee], p)}
133
+ Fee requested: #{num(f, p)} as \"#{fee}\"
134
+ Fee left: #{num(left, p)}
135
+ Tx size: #{size} bytes
136
+ Unspent: #{num(unspent, p)}
137
+ Amount: #{num(satoshi, p)}
138
+ Target address: #{target}
139
+ Change address is #{change}")
140
+ unless @dry
141
+ Sibit::Json.new(http: @http, log: @log).post(
142
+ URI('https://blockchain.info/pushtx'),
143
+ tx.to_payload.bth
144
+ )
145
+ end
146
+ tx.hash
147
+ end
148
+
149
+ # Gets the hash of the latest block.
150
+ def latest
151
+ Sibit::Json.new(http: @http, log: @log).get(
152
+ URI('https://blockchain.info/latestblock')
153
+ )['hash']
154
+ end
155
+
156
+ private
157
+
158
+ def num(satoshi, usd)
159
+ format(
160
+ '%<satoshi>ss/$%<dollars>0.2f',
161
+ satoshi: satoshi.to_s.gsub(/\d(?=(...)+$)/, '\0,'),
162
+ dollars: satoshi * usd / 100_000_000
163
+ )
164
+ end
165
+
166
+ # Convert text to amount.
167
+ def satoshi(amount)
168
+ return amount if amount.is_a?(Integer)
169
+ raise Error, 'Amount should either be a String or Integer' unless amount.is_a?(String)
170
+ return (amount.gsub(/BTC$/, '').to_f * 100_000_000).to_i if amount.end_with?('BTC')
171
+ raise Error, "Can't understand the amount #{amount.inspect}"
172
+ end
173
+
174
+ # Calculates a fee in satoshi for the transaction of the specified size.
175
+ # The +fee+ argument could be a number in satoshi, in which case it will
176
+ # be returned as is, or a string like "XL" or "S", in which case the
177
+ # fee will be calculated using the +size+ argument (which is the size
178
+ # of the transaction in bytes).
179
+ def mfee(fee, size)
180
+ return fee.to_i if fee.is_a?(Integer)
181
+ raise Error, 'Fee should either be a String or Integer' unless fee.is_a?(String)
182
+ mul = 1
183
+ if fee.start_with?('+', '-')
184
+ mul = -1 if fee.start_with?('-')
185
+ fee = fee[1..-1]
186
+ end
187
+ sat = fees[fee.to_sym]
188
+ raise Error, "Can't understand the fee: #{fee.inspect}" if sat.nil?
189
+ mul * sat * size
190
+ end
191
+
192
+ # Make key from private key string in Hash160.
193
+ def key(hash160)
194
+ key = Bitcoin::Key.new
195
+ key.priv = hash160
196
+ key
197
+ end
198
+
199
+ # Calculate an approximate size of the transaction.
200
+ def size_of(amount, sources)
201
+ satoshi = satoshi(amount)
202
+ builder = Bitcoin::Builder::TxBuilder.new
203
+ unspent = 0
204
+ size = 100
205
+ utxos = Sibit::Json.new(http: @http, log: @log).get(
206
+ URI("https://blockchain.info/unspent?active=#{sources.keys.join('|')}&limit=1000")
207
+ )['unspent_outputs']
208
+ utxos.each do |utxo|
209
+ unspent += utxo['value']
210
+ builder.input do |i|
211
+ i.prev_out(utxo['tx_hash_big_endian'])
212
+ i.prev_out_index(utxo['tx_output_n'])
213
+ i.prev_out_script = [utxo['script']].pack('H*')
214
+ address = Bitcoin::Script.new([utxo['script']].pack('H*')).get_address
215
+ i.signature_key(key(sources[address]))
216
+ end
217
+ size += 180
218
+ break if unspent > satoshi
219
+ end
220
+ size
221
+ end
222
+ end
223
+ end
data/lib/sibit/btc.rb ADDED
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'uri'
24
+ require 'json'
25
+ require_relative 'version'
26
+ require_relative 'error'
27
+ require_relative 'log'
28
+ require_relative 'http'
29
+ require_relative 'json'
30
+
31
+ # Btc.com API.
32
+ #
33
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
34
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
35
+ # License:: MIT
36
+ class Sibit
37
+ # Btc.com API.
38
+ class Btc
39
+ # Constructor.
40
+ def initialize(log: Sibit::Log.new, http: Sibit::Http.new, dry: false)
41
+ @http = http
42
+ @log = log
43
+ @dry = dry
44
+ end
45
+
46
+ # Current price of BTC in USD (float returned).
47
+ def price(_currency)
48
+ raise Sibit::Error, 'Not implemented yet'
49
+ end
50
+
51
+ # Gets the balance of the address, in satoshi.
52
+ def balance(address)
53
+ uri = URI("https://chain.api.btc.com/v3/address/#{address}/unspent")
54
+ json = Sibit::Json.new(http: @http, log: @log).get(uri)
55
+ txns = json['data']['list']
56
+ balance = txns.map { |tx| tx['value'] }.inject(&:+) || 0
57
+ @log.info("The balance of #{address} is #{balance}, total txns: #{txns.count}")
58
+ balance
59
+ end
60
+
61
+ # Get recommended fees, in satoshi per byte.
62
+ def fees
63
+ raise Sibit::Error, 'Not implemented yet'
64
+ end
65
+
66
+ # Sends a payment and returns the transaction hash.
67
+ def pay(_amount, _fee, _sources, _target, _change)
68
+ raise Sibit::Error, 'Not implemented yet'
69
+ end
70
+
71
+ # Gets the hash of the latest block.
72
+ def latest
73
+ raise Sibit::Error, 'Not implemented yet'
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ # The error.
24
+ #
25
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
26
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
27
+ # License:: MIT
28
+ class Sibit
29
+ # The error.
30
+ class Error < StandardError; end
31
+ end
data/lib/sibit/fake.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative 'version'
24
+
25
+ # Fake API.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
29
+ # License:: MIT
30
+ class Sibit
31
+ # Fake API
32
+ class Fake
33
+ def price(_cur = 'USD')
34
+ 4_000
35
+ end
36
+
37
+ def fees
38
+ { S: 12, M: 45, L: 100, XL: 200 }
39
+ end
40
+
41
+ def balance(_address)
42
+ 100_000_000
43
+ end
44
+
45
+ def pay(_amount, _fee, _sources, _target, _change)
46
+ '9dfe55a30b5ee732005158c589179a398117117a68d21531fb6c78b85b544c54'
47
+ end
48
+
49
+ def latest
50
+ '00000000000000000008df8a6e1b61d1136803ac9791b8725235c9f780b4ed71'
51
+ end
52
+
53
+ def get_json(_uri)
54
+ {}
55
+ end
56
+ end
57
+ end
data/lib/sibit/http.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'net/http'
24
+
25
+ # HTTP interface.
26
+ #
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
29
+ # License:: MIT
30
+ class Sibit
31
+ # This HTTP client will be used by default.
32
+ class Http
33
+ def client(uri)
34
+ http = Net::HTTP.new(uri.host, uri.port)
35
+ http.use_ssl = true
36
+ http
37
+ end
38
+ end
39
+
40
+ # This HTTP client with proxy.
41
+ class HttpProxy
42
+ def initialize(addr)
43
+ @host, @port = addr.split(':')
44
+ end
45
+
46
+ def client(uri)
47
+ http = Net::HTTP.new(uri.host, uri.port, @host, @port.to_i)
48
+ http.use_ssl = true
49
+ http
50
+ end
51
+ end
52
+ end
data/lib/sibit/json.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'json'
24
+ require 'uri'
25
+ require_relative 'version'
26
+ require_relative 'error'
27
+ require_relative 'http'
28
+
29
+ # Json SDK.
30
+ #
31
+ # It works through the Blockchain API:
32
+ # https://www.blockchain.com/api/blockchain_api
33
+ #
34
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
35
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
36
+ # License:: MIT
37
+ class Sibit
38
+ # JSON processing.
39
+ class Json
40
+ # Constructor.
41
+ def initialize(log: Sibit::Log.new, http: Sibit::Http.new)
42
+ @http = http
43
+ @log = log
44
+ end
45
+
46
+ # Send GET request to the HTTP and return JSON response.
47
+ # This method will also log the process and will validate the
48
+ # response for correctness.
49
+ def get(uri)
50
+ start = Time.now
51
+ res = @http.client(uri).get(
52
+ "#{uri.path}?#{uri.query}",
53
+ 'Accept' => 'text/plain',
54
+ 'User-Agent' => user_agent,
55
+ 'Accept-Encoding' => ''
56
+ )
57
+ unless res.code == '200'
58
+ raise Sibit::Error, "Failed to retrieve #{uri} (#{res.code}): #{res.body}"
59
+ end
60
+ @log.info("GET #{uri}: #{res.code}/#{res.body.length}b in #{age(start)}")
61
+ JSON.parse(res.body)
62
+ end
63
+
64
+ def post(uri, body)
65
+ start = Time.now
66
+ res = @http.client(uri).post(
67
+ "#{uri.path}?#{uri.query}",
68
+ "tx=#{CGI.escape(body)}",
69
+ 'Accept' => 'text/plain',
70
+ 'User-Agent' => user_agent,
71
+ 'Accept-Encoding' => '',
72
+ 'Content-Type' => 'application/x-www-form-urlencoded'
73
+ )
74
+ unless res.code == '200'
75
+ raise Sibit::Error, "Failed to post tx to #{uri}: #{res.code}\n#{res.body}"
76
+ end
77
+ @log.info("POST #{uri}: #{res.code} in #{age(start)}")
78
+ end
79
+
80
+ private
81
+
82
+ def age(start)
83
+ "#{((Time.now - start) * 1000).round}ms"
84
+ end
85
+
86
+ def user_agent
87
+ "Anonymous/#{Sibit::VERSION}"
88
+ end
89
+ end
90
+ end
data/lib/sibit/log.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ # The log.
24
+ #
25
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
26
+ # Copyright:: Copyright (c) 2019 Yegor Bugayenko
27
+ # License:: MIT
28
+ class Sibit
29
+ # Log.
30
+ class Log
31
+ # Constructor.
32
+ #
33
+ # You may provide the log you want to see the messages in. If you don't
34
+ # provide anything, the console will be used. The object you provide
35
+ # has to respond to the method +info+ or +puts+ in order to receive logging
36
+ # messages.
37
+ def initialize(log = STDOUT)
38
+ @log = log
39
+ end
40
+
41
+ def info(msg)
42
+ if @log.respond_to?(:info)
43
+ @log.info(msg)
44
+ elsif @log.respond_to?(:puts)
45
+ @log.puts(msg)
46
+ end
47
+ end
48
+ end
49
+ end