sibit 0.14.2 → 0.14.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/sibit +8 -1
- data/lib/sibit/bitcoinchain.rb +10 -5
- data/lib/sibit/blockchain.rb +22 -141
- data/lib/sibit/btc.rb +10 -5
- data/lib/sibit/earn.rb +91 -0
- data/lib/sibit/fake.rb +6 -2
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +98 -2
- data/test/test_fake.rb +1 -4
- data/test/test_sibit.rb +5 -2
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3746b345c4e7b36083c7a2377e4a5449532116978e38f964ac558046670fb5d
|
4
|
+
data.tar.gz: 480071555f2cc5e4b15a81274b526fb74a75c223d7b98a98226a134dd87e0e03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '097f3d44a66b8ef449e26429ec068bc8395258d80a2e0d5f520f9af24f5e0323d1f83f2f41e691d9daee570fb9527288c4b721ffdfa43762eafd1ef8c62d8095'
|
7
|
+
data.tar.gz: 8e9d7c89c01e6d537d159e16af0facdffbee6ea1ed4c16104a1c1fd33e4342b0a0a44bc7b3b5b2b5b45e11fa0fd222b4ac8e54aa4de6a75e416e4837540bc796
|
data/bin/sibit
CHANGED
@@ -31,6 +31,7 @@ require_relative '../lib/sibit/version'
|
|
31
31
|
require_relative '../lib/sibit/blockchain'
|
32
32
|
require_relative '../lib/sibit/btc'
|
33
33
|
require_relative '../lib/sibit/bitcoinchain'
|
34
|
+
require_relative '../lib/sibit/earn'
|
34
35
|
|
35
36
|
begin
|
36
37
|
begin
|
@@ -57,7 +58,11 @@ Options are:"
|
|
57
58
|
exit
|
58
59
|
end
|
59
60
|
o.bool '--verbose', 'Print all possible debug messages'
|
60
|
-
o.array
|
61
|
+
o.array(
|
62
|
+
'--api',
|
63
|
+
'Ordered List of APIs to use, e.g. "earn,blockchain,btc,bitcoinchain"',
|
64
|
+
default: %w[earn blockchain btc bitcoinchain]
|
65
|
+
)
|
61
66
|
end
|
62
67
|
rescue Slop::Error => ex
|
63
68
|
raise ex.message
|
@@ -73,6 +78,8 @@ Options are:"
|
|
73
78
|
api = Sibit::Btc.new(http: http, log: log, dry: opts[:dry])
|
74
79
|
elsif a == 'bitcoinchain'
|
75
80
|
api = Sibit::Bitcoinchain.new(http: http, log: log, dry: opts[:dry])
|
81
|
+
elsif a == 'earn'
|
82
|
+
api = Sibit::Earn.new(http: http, log: log, dry: opts[:dry])
|
76
83
|
else
|
77
84
|
raise Sibit::Error, "Unknown API \"#{a}\""
|
78
85
|
end
|
data/lib/sibit/bitcoinchain.rb
CHANGED
@@ -60,11 +60,6 @@ class Sibit
|
|
60
60
|
raise Sibit::Error, 'Not implemented yet'
|
61
61
|
end
|
62
62
|
|
63
|
-
# Sends a payment and returns the transaction hash.
|
64
|
-
def pay(_amount, _fee, _sources, _target, _change)
|
65
|
-
raise Sibit::Error, 'Not implemented yet'
|
66
|
-
end
|
67
|
-
|
68
63
|
# Gets the hash of the latest block.
|
69
64
|
def latest
|
70
65
|
Sibit::Json.new(http: @http, log: @log).get(
|
@@ -72,6 +67,16 @@ class Sibit
|
|
72
67
|
)['hash']
|
73
68
|
end
|
74
69
|
|
70
|
+
# Fetch all unspent outputs per address.
|
71
|
+
def utxos(_sources)
|
72
|
+
raise Sibit::Error, 'Not implemented yet'
|
73
|
+
end
|
74
|
+
|
75
|
+
# Push this transaction (in hex format) to the network.
|
76
|
+
def push(_hex)
|
77
|
+
raise Sibit::Error, 'Not implemented yet'
|
78
|
+
end
|
79
|
+
|
75
80
|
# This method should fetch a Blockchain block and return as a hash. Raises
|
76
81
|
# an exception if the block is not found.
|
77
82
|
def block(hash)
|
data/lib/sibit/blockchain.rb
CHANGED
@@ -67,85 +67,33 @@ class Sibit
|
|
67
67
|
json['final_balance']
|
68
68
|
end
|
69
69
|
|
70
|
-
# Get recommended fees
|
71
|
-
# a hash: { S: 12, M: 45, L: 100, XL: 200 }
|
70
|
+
# Get recommended fees.
|
72
71
|
def fees
|
73
|
-
|
74
|
-
URI('https://bitcoinfees.earn.com/api/v1/fees/recommended')
|
75
|
-
)
|
76
|
-
@log.info("Current recommended Bitcoin fees: \
|
77
|
-
#{json['hourFee']}/#{json['halfHourFee']}/#{json['fastestFee']} sat/byte")
|
78
|
-
{
|
79
|
-
S: json['hourFee'] / 3,
|
80
|
-
M: json['hourFee'],
|
81
|
-
L: json['halfHourFee'],
|
82
|
-
XL: json['fastestFee']
|
83
|
-
}
|
72
|
+
raise Sibit::Error, 'fees() not implemented yet'
|
84
73
|
end
|
85
74
|
|
86
|
-
#
|
87
|
-
def
|
88
|
-
|
89
|
-
satoshi = satoshi(amount)
|
90
|
-
f = mfee(fee, size_of(amount, sources))
|
91
|
-
satoshi += f if f.negative?
|
92
|
-
raise Error, "The fee #{f.abs} covers the entire amount" if satoshi.zero?
|
93
|
-
raise Error, "The fee #{f.abs} is bigger than the amount #{satoshi}" if satoshi.negative?
|
94
|
-
builder = Bitcoin::Builder::TxBuilder.new
|
95
|
-
unspent = 0
|
96
|
-
size = 100
|
97
|
-
utxos = Sibit::Json.new(http: @http, log: @log).get(
|
75
|
+
# Fetch all unspent outputs per address.
|
76
|
+
def utxos(sources)
|
77
|
+
Sibit::Json.new(http: @http, log: @log).get(
|
98
78
|
URI("https://blockchain.info/unspent?active=#{sources.keys.join('|')}&limit=1000")
|
99
|
-
)['unspent_outputs']
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
i.prev_out_script = [utxo['script']].pack('H*')
|
108
|
-
address = Bitcoin::Script.new([utxo['script']].pack('H*')).get_address
|
109
|
-
i.signature_key(key(sources[address]))
|
110
|
-
end
|
111
|
-
size += 180
|
112
|
-
@log.info(
|
113
|
-
" #{num(utxo['value'], p)}/#{utxo['confirmations']} at #{utxo['tx_hash_big_endian']}"
|
114
|
-
)
|
115
|
-
break if unspent > satoshi
|
79
|
+
)['unspent_outputs'].map do |u|
|
80
|
+
{
|
81
|
+
value: u['value'],
|
82
|
+
hash: u['tx_hash_big_endian'],
|
83
|
+
index: u['tx_output_n'],
|
84
|
+
confirmations: u['confirmations'],
|
85
|
+
script: [u['script']].pack('H*')
|
86
|
+
}
|
116
87
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
extra_fee: [f, Bitcoin.network[:min_tx_fee]].max,
|
126
|
-
change_address: change
|
88
|
+
end
|
89
|
+
|
90
|
+
# Push this transaction (in hex format) to the network.
|
91
|
+
def push(hex)
|
92
|
+
return if @dry
|
93
|
+
Sibit::Json.new(http: @http, log: @log).post(
|
94
|
+
URI('https://blockchain.info/pushtx'),
|
95
|
+
hex
|
127
96
|
)
|
128
|
-
left = unspent - tx.outputs.map(&:value).inject(&:+)
|
129
|
-
@log.info("A new Bitcoin transaction #{tx.hash} prepared:
|
130
|
-
#{tx.in.count} input#{tx.in.count > 1 ? 's' : ''}:
|
131
|
-
#{tx.inputs.map { |i| " in: #{i.prev_out.bth}:#{i.prev_out_index}" }.join("\n ")}
|
132
|
-
#{tx.out.count} output#{tx.out.count > 1 ? 's' : ''}:
|
133
|
-
#{tx.outputs.map { |o| "out: #{o.script.bth} / #{num(o.value, p)}" }.join("\n ")}
|
134
|
-
Min tx fee: #{num(Bitcoin.network[:min_tx_fee], p)}
|
135
|
-
Fee requested: #{num(f, p)} as \"#{fee}\"
|
136
|
-
Fee actually paid: #{num(left, p)}
|
137
|
-
Tx size: #{size} bytes
|
138
|
-
Unspent: #{num(unspent, p)}
|
139
|
-
Amount: #{num(satoshi, p)}
|
140
|
-
Target address: #{target}
|
141
|
-
Change address is #{change}")
|
142
|
-
unless @dry
|
143
|
-
Sibit::Json.new(http: @http, log: @log).post(
|
144
|
-
URI('https://blockchain.info/pushtx'),
|
145
|
-
tx.to_payload.bth
|
146
|
-
)
|
147
|
-
end
|
148
|
-
tx.hash
|
149
97
|
end
|
150
98
|
|
151
99
|
# Gets the hash of the latest block.
|
@@ -157,74 +105,7 @@ class Sibit
|
|
157
105
|
|
158
106
|
# This method should fetch a Blockchain block and return as a hash.
|
159
107
|
def block(_hash)
|
160
|
-
raise Sibit::Error, '
|
161
|
-
end
|
162
|
-
|
163
|
-
private
|
164
|
-
|
165
|
-
def num(satoshi, usd)
|
166
|
-
format(
|
167
|
-
'%<satoshi>ss/$%<dollars>0.2f',
|
168
|
-
satoshi: satoshi.to_s.gsub(/\d(?=(...)+$)/, '\0,'),
|
169
|
-
dollars: satoshi * usd / 100_000_000
|
170
|
-
)
|
171
|
-
end
|
172
|
-
|
173
|
-
# Convert text to amount.
|
174
|
-
def satoshi(amount)
|
175
|
-
return amount if amount.is_a?(Integer)
|
176
|
-
raise Error, 'Amount should either be a String or Integer' unless amount.is_a?(String)
|
177
|
-
return (amount.gsub(/BTC$/, '').to_f * 100_000_000).to_i if amount.end_with?('BTC')
|
178
|
-
raise Error, "Can't understand the amount #{amount.inspect}"
|
179
|
-
end
|
180
|
-
|
181
|
-
# Calculates a fee in satoshi for the transaction of the specified size.
|
182
|
-
# The +fee+ argument could be a number in satoshi, in which case it will
|
183
|
-
# be returned as is, or a string like "XL" or "S", in which case the
|
184
|
-
# fee will be calculated using the +size+ argument (which is the size
|
185
|
-
# of the transaction in bytes).
|
186
|
-
def mfee(fee, size)
|
187
|
-
return fee.to_i if fee.is_a?(Integer)
|
188
|
-
raise Error, 'Fee should either be a String or Integer' unless fee.is_a?(String)
|
189
|
-
mul = 1
|
190
|
-
if fee.start_with?('+', '-')
|
191
|
-
mul = -1 if fee.start_with?('-')
|
192
|
-
fee = fee[1..-1]
|
193
|
-
end
|
194
|
-
sat = fees[fee.to_sym]
|
195
|
-
raise Error, "Can't understand the fee: #{fee.inspect}" if sat.nil?
|
196
|
-
mul * sat * size
|
197
|
-
end
|
198
|
-
|
199
|
-
# Make key from private key string in Hash160.
|
200
|
-
def key(hash160)
|
201
|
-
key = Bitcoin::Key.new
|
202
|
-
key.priv = hash160
|
203
|
-
key
|
204
|
-
end
|
205
|
-
|
206
|
-
# Calculate an approximate size of the transaction.
|
207
|
-
def size_of(amount, sources)
|
208
|
-
satoshi = satoshi(amount)
|
209
|
-
builder = Bitcoin::Builder::TxBuilder.new
|
210
|
-
unspent = 0
|
211
|
-
size = 100
|
212
|
-
utxos = Sibit::Json.new(http: @http, log: @log).get(
|
213
|
-
URI("https://blockchain.info/unspent?active=#{sources.keys.join('|')}&limit=1000")
|
214
|
-
)['unspent_outputs']
|
215
|
-
utxos.each do |utxo|
|
216
|
-
unspent += utxo['value']
|
217
|
-
builder.input do |i|
|
218
|
-
i.prev_out(utxo['tx_hash_big_endian'])
|
219
|
-
i.prev_out_index(utxo['tx_output_n'])
|
220
|
-
i.prev_out_script = [utxo['script']].pack('H*')
|
221
|
-
address = Bitcoin::Script.new([utxo['script']].pack('H*')).get_address
|
222
|
-
i.signature_key(key(sources[address]))
|
223
|
-
end
|
224
|
-
size += 180
|
225
|
-
break if unspent > satoshi
|
226
|
-
end
|
227
|
-
size
|
108
|
+
raise Sibit::Error, 'block() not implemented yet'
|
228
109
|
end
|
229
110
|
end
|
230
111
|
end
|
data/lib/sibit/btc.rb
CHANGED
@@ -68,11 +68,6 @@ class Sibit
|
|
68
68
|
raise Sibit::Error, 'Not implemented yet'
|
69
69
|
end
|
70
70
|
|
71
|
-
# Sends a payment and returns the transaction hash.
|
72
|
-
def pay(_amount, _fee, _sources, _target, _change)
|
73
|
-
raise Sibit::Error, 'Not implemented yet'
|
74
|
-
end
|
75
|
-
|
76
71
|
# Gets the hash of the latest block.
|
77
72
|
def latest
|
78
73
|
uri = URI('https://chain.api.btc.com/v3/block/latest')
|
@@ -82,6 +77,16 @@ class Sibit
|
|
82
77
|
hash
|
83
78
|
end
|
84
79
|
|
80
|
+
# Fetch all unspent outputs per address.
|
81
|
+
def utxos(_sources)
|
82
|
+
raise Sibit::Error, 'Not implemented yet'
|
83
|
+
end
|
84
|
+
|
85
|
+
# Push this transaction (in hex format) to the network.
|
86
|
+
def push(_hex)
|
87
|
+
raise Sibit::Error, 'Not implemented yet'
|
88
|
+
end
|
89
|
+
|
85
90
|
# This method should fetch a Blockchain block and return as a hash.
|
86
91
|
def block(hash)
|
87
92
|
head = Sibit::Json.new(http: @http, log: @log).get(
|
data/lib/sibit/earn.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2019-2020 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
|
+
require_relative 'json'
|
29
|
+
|
30
|
+
# Earn.com API.
|
31
|
+
#
|
32
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
33
|
+
# Copyright:: Copyright (c) 2019-2020 Yegor Bugayenko
|
34
|
+
# License:: MIT
|
35
|
+
class Sibit
|
36
|
+
# Blockchain.info API.
|
37
|
+
class Earn
|
38
|
+
# Constructor.
|
39
|
+
def initialize(log: Sibit::Log.new, http: Sibit::Http.new, dry: false)
|
40
|
+
@http = http
|
41
|
+
@log = log
|
42
|
+
@dry = dry
|
43
|
+
end
|
44
|
+
|
45
|
+
# Current price of BTC in USD (float returned).
|
46
|
+
def price(_currency)
|
47
|
+
raise Sibit::Error, 'price() doesn\'t work here'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Gets the balance of the address, in satoshi.
|
51
|
+
def balance(_address)
|
52
|
+
raise Sibit::Error, 'balance() doesn\'t work here'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get recommended fees, in satoshi per byte. The method returns
|
56
|
+
# a hash: { S: 12, M: 45, L: 100, XL: 200 }
|
57
|
+
def fees
|
58
|
+
json = Sibit::Json.new(http: @http, log: @log).get(
|
59
|
+
URI('https://bitcoinfees.earn.com/api/v1/fees/recommended')
|
60
|
+
)
|
61
|
+
@log.info("Current recommended Bitcoin fees: \
|
62
|
+
#{json['hourFee']}/#{json['halfHourFee']}/#{json['fastestFee']} sat/byte")
|
63
|
+
{
|
64
|
+
S: json['hourFee'] / 3,
|
65
|
+
M: json['hourFee'],
|
66
|
+
L: json['halfHourFee'],
|
67
|
+
XL: json['fastestFee']
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fetch all unspent outputs per address.
|
72
|
+
def utxos(_sources)
|
73
|
+
raise Sibit::Error, 'Not implemented yet'
|
74
|
+
end
|
75
|
+
|
76
|
+
# Push this transaction (in hex format) to the network.
|
77
|
+
def push(_hex)
|
78
|
+
raise Sibit::Error, 'Not implemented yet'
|
79
|
+
end
|
80
|
+
|
81
|
+
# Gets the hash of the latest block.
|
82
|
+
def latest
|
83
|
+
raise Sibit::Error, 'latest() doesn\'t work here'
|
84
|
+
end
|
85
|
+
|
86
|
+
# This method should fetch a Blockchain block and return as a hash.
|
87
|
+
def block(_hash)
|
88
|
+
raise Sibit::Error, 'block() doesn\'t work here'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/sibit/fake.rb
CHANGED
@@ -42,8 +42,12 @@ class Sibit
|
|
42
42
|
100_000_000
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
45
|
+
def utxos(_sources)
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
def push(hex)
|
50
|
+
# Nothing to do here
|
47
51
|
end
|
48
52
|
|
49
53
|
def latest
|
data/lib/sibit/version.rb
CHANGED
data/lib/sibit.rb
CHANGED
@@ -105,9 +105,61 @@ class Sibit
|
|
105
105
|
# +target+: the target address to send to
|
106
106
|
# +change+: the address where the change has to be sent to
|
107
107
|
def pay(amount, fee, sources, target, change)
|
108
|
+
p = price('USD')
|
109
|
+
satoshi = satoshi(amount)
|
110
|
+
builder = Bitcoin::Builder::TxBuilder.new
|
111
|
+
unspent = 0
|
112
|
+
size = 100
|
113
|
+
utxos = first_one { |api| api.utxos(sources) }
|
114
|
+
@log.info("#{utxos.count} UTXOs found, these will be used \
|
115
|
+
(value/confirmations at tx_hash):")
|
116
|
+
utxos.each do |utxo|
|
117
|
+
unspent += utxo[:value]
|
118
|
+
builder.input do |i|
|
119
|
+
i.prev_out(utxo[:hash])
|
120
|
+
i.prev_out_index(utxo[:index])
|
121
|
+
i.prev_out_script = utxo[:script]
|
122
|
+
address = Bitcoin::Script.new(utxo[:script]).get_address
|
123
|
+
i.signature_key(key(sources[address]))
|
124
|
+
end
|
125
|
+
size += 180
|
126
|
+
@log.info(
|
127
|
+
" #{num(utxo[:value], p)}/#{utxo[:confirmations]} at #{utxo[:hash]}"
|
128
|
+
)
|
129
|
+
break if unspent > satoshi
|
130
|
+
end
|
131
|
+
if unspent < satoshi
|
132
|
+
raise Error, "Not enough funds to send #{num(satoshi, p)}, only #{num(unspent, p)} left"
|
133
|
+
end
|
134
|
+
builder.output(satoshi, target)
|
135
|
+
f = mfee(fee, size)
|
136
|
+
satoshi += f if f.negative?
|
137
|
+
raise Error, "The fee #{f.abs} covers the entire amount" if satoshi.zero?
|
138
|
+
raise Error, "The fee #{f.abs} is bigger than the amount #{satoshi}" if satoshi.negative?
|
139
|
+
tx = builder.tx(
|
140
|
+
input_value: unspent,
|
141
|
+
leave_fee: true,
|
142
|
+
extra_fee: [f, Bitcoin.network[:min_tx_fee]].max,
|
143
|
+
change_address: change
|
144
|
+
)
|
145
|
+
left = unspent - tx.outputs.map(&:value).inject(&:+)
|
146
|
+
@log.info("A new Bitcoin transaction #{tx.hash} prepared:
|
147
|
+
#{tx.in.count} input#{tx.in.count > 1 ? 's' : ''}:
|
148
|
+
#{tx.inputs.map { |i| " in: #{i.prev_out.bth}:#{i.prev_out_index}" }.join("\n ")}
|
149
|
+
#{tx.out.count} output#{tx.out.count > 1 ? 's' : ''}:
|
150
|
+
#{tx.outputs.map { |o| "out: #{o.script.bth} / #{num(o.value, p)}" }.join("\n ")}
|
151
|
+
Min tx fee: #{num(Bitcoin.network[:min_tx_fee], p)}
|
152
|
+
Fee requested: #{num(f, p)} as \"#{fee}\"
|
153
|
+
Fee actually paid: #{num(left, p)}
|
154
|
+
Tx size: #{size} bytes
|
155
|
+
Unspent: #{num(unspent, p)}
|
156
|
+
Amount: #{num(satoshi, p)}
|
157
|
+
Target address: #{target}
|
158
|
+
Change address is #{change}")
|
108
159
|
first_one do |api|
|
109
|
-
api.
|
160
|
+
api.push(tx.to_payload.bth)
|
110
161
|
end
|
162
|
+
tx.hash
|
111
163
|
end
|
112
164
|
|
113
165
|
# Gets the hash of the latest block.
|
@@ -186,7 +238,51 @@ class Sibit
|
|
186
238
|
@log.info("The API #{api.class.name} failed: #{e.message}")
|
187
239
|
end
|
188
240
|
end
|
189
|
-
|
241
|
+
unless done
|
242
|
+
raise Sibit::Error, "No APIs out of #{@api.length} managed to succeed: \
|
243
|
+
#{@api.map { |a| a.class.name }.join(', ')}"
|
244
|
+
end
|
190
245
|
result
|
191
246
|
end
|
247
|
+
|
248
|
+
def num(satoshi, usd)
|
249
|
+
format(
|
250
|
+
'%<satoshi>ss/$%<dollars>0.2f',
|
251
|
+
satoshi: satoshi.to_s.gsub(/\d(?=(...)+$)/, '\0,'),
|
252
|
+
dollars: satoshi * usd / 100_000_000
|
253
|
+
)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Convert text to amount.
|
257
|
+
def satoshi(amount)
|
258
|
+
return amount if amount.is_a?(Integer)
|
259
|
+
raise Error, 'Amount should either be a String or Integer' unless amount.is_a?(String)
|
260
|
+
return (amount.gsub(/BTC$/, '').to_f * 100_000_000).to_i if amount.end_with?('BTC')
|
261
|
+
raise Error, "Can't understand the amount #{amount.inspect}"
|
262
|
+
end
|
263
|
+
|
264
|
+
# Calculates a fee in satoshi for the transaction of the specified size.
|
265
|
+
# The +fee+ argument could be a number in satoshi, in which case it will
|
266
|
+
# be returned as is, or a string like "XL" or "S", in which case the
|
267
|
+
# fee will be calculated using the +size+ argument (which is the size
|
268
|
+
# of the transaction in bytes).
|
269
|
+
def mfee(fee, size)
|
270
|
+
return fee.to_i if fee.is_a?(Integer)
|
271
|
+
raise Error, 'Fee should either be a String or Integer' unless fee.is_a?(String)
|
272
|
+
mul = 1
|
273
|
+
if fee.start_with?('+', '-')
|
274
|
+
mul = -1 if fee.start_with?('-')
|
275
|
+
fee = fee[1..-1]
|
276
|
+
end
|
277
|
+
sat = fees[fee.to_sym]
|
278
|
+
raise Error, "Can't understand the fee: #{fee.inspect}" if sat.nil?
|
279
|
+
mul * sat * size
|
280
|
+
end
|
281
|
+
|
282
|
+
# Make key from private key string in Hash160.
|
283
|
+
def key(hash160)
|
284
|
+
key = Bitcoin::Key.new
|
285
|
+
key.priv = hash160
|
286
|
+
key
|
287
|
+
end
|
192
288
|
end
|
data/test/test_fake.rb
CHANGED
@@ -35,10 +35,7 @@ class TestFake < Minitest::Test
|
|
35
35
|
assert_equal(4_000, sibit.price)
|
36
36
|
assert_equal(12, sibit.fees[:S])
|
37
37
|
assert_equal(100_000_000, sibit.balance(''))
|
38
|
-
assert_equal(
|
39
|
-
'9dfe55a30b5ee732005158c589179a398117117a68d21531fb6c78b85b544c54',
|
40
|
-
sibit.pay(0, 'M', {}, '', '')
|
41
|
-
)
|
38
|
+
assert_equal([], sibit.utxos(nil))
|
42
39
|
assert_equal('00000000000000000008df8a6e1b61d1136803ac9791b8725235c9f780b4ed71', sibit.latest)
|
43
40
|
end
|
44
41
|
|
data/test/test_sibit.rb
CHANGED
@@ -24,6 +24,9 @@ require 'minitest/autorun'
|
|
24
24
|
require 'webmock/minitest'
|
25
25
|
require 'json'
|
26
26
|
require_relative '../lib/sibit'
|
27
|
+
require_relative '../lib/sibit/earn'
|
28
|
+
require_relative '../lib/sibit/fake'
|
29
|
+
require_relative '../lib/sibit/blockchain'
|
27
30
|
|
28
31
|
# Sibit.
|
29
32
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
@@ -34,7 +37,7 @@ class TestSibit < Minitest::Test
|
|
34
37
|
stub_request(
|
35
38
|
:get, 'https://bitcoinfees.earn.com/api/v1/fees/recommended'
|
36
39
|
).to_return(body: '{"fastestFee":300,"halfHourFee":200,"hourFee":180}')
|
37
|
-
sibit = Sibit.new
|
40
|
+
sibit = Sibit.new(api: Sibit::Earn.new)
|
38
41
|
fees = sibit.fees
|
39
42
|
assert_equal(60, fees[:S])
|
40
43
|
assert_equal(180, fees[:M])
|
@@ -124,7 +127,7 @@ class TestSibit < Minitest::Test
|
|
124
127
|
'https://blockchain.info/unspent?active=1JvCsJtLmCxEk7ddZFnVkGXpr9uhxZPmJi&limit=1000'
|
125
128
|
).to_return(body: JSON.pretty_generate(json))
|
126
129
|
stub_request(:post, 'https://blockchain.info/pushtx').to_return(status: 200)
|
127
|
-
sibit = Sibit.new
|
130
|
+
sibit = Sibit.new(api: [Sibit::Earn.new, Sibit::Blockchain.new])
|
128
131
|
target = sibit.create(sibit.generate)
|
129
132
|
change = sibit.create(sibit.generate)
|
130
133
|
tx = sibit.pay(
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sibit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.14.
|
4
|
+
version: 0.14.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
@@ -284,6 +284,7 @@ files:
|
|
284
284
|
- lib/sibit/bitcoinchain.rb
|
285
285
|
- lib/sibit/blockchain.rb
|
286
286
|
- lib/sibit/btc.rb
|
287
|
+
- lib/sibit/earn.rb
|
287
288
|
- lib/sibit/error.rb
|
288
289
|
- lib/sibit/fake.rb
|
289
290
|
- lib/sibit/http.rb
|