sibit 0.30.1 → 0.30.2
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 +4 -4
- data/Gemfile.lock +4 -3
- data/bin/sibit +19 -15
- data/lib/sibit/bestof.rb +2 -2
- data/lib/sibit/bitcoinchain.rb +5 -5
- data/lib/sibit/blockchain.rb +12 -8
- data/lib/sibit/blockchair.rb +3 -3
- data/lib/sibit/btc.rb +8 -8
- data/lib/sibit/cex.rb +1 -1
- data/lib/sibit/cryptoapis.rb +5 -5
- data/lib/sibit/dry.rb +1 -1
- data/lib/sibit/firstof.rb +3 -3
- data/lib/sibit/json.rb +40 -36
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +23 -18
- data/sibit.gemspec +3 -2
- metadata +21 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7463ec26890a5bd8515ce96c92190a50edaf46f5b94aed0a1b54261f0d63ca65
|
|
4
|
+
data.tar.gz: 71be0a78c7bfcfffdebc6daa2c63a570d561b5e6e923f0d600e2791c2856cac5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae0606a033a0506be0d3f397a0ac81bf51db53e7c35671256642764e2dcca358d95cbb9517c67c0a49f0621110926760154ea1c1c885d4aa5b6a2a32792078ff
|
|
7
|
+
data.tar.gz: 21a0bf5f5e27f3c22ceb82f2432d3b3fdd8a4ae1181cbdad383d34f081024c4802b6c83c2a7f51c1205728ccede22c8e45c8498f2d74ffd04282f828b29b4c54
|
data/Gemfile.lock
CHANGED
|
@@ -4,10 +4,11 @@ PATH
|
|
|
4
4
|
sibit (0.0.0)
|
|
5
5
|
backtrace (~> 0.3)
|
|
6
6
|
decoor (~> 0.1)
|
|
7
|
+
elapsed (~> 0.2)
|
|
7
8
|
iri (~> 0.5)
|
|
8
|
-
json (~> 2)
|
|
9
|
+
json (~> 2.18)
|
|
9
10
|
loog (~> 0.6)
|
|
10
|
-
openssl (
|
|
11
|
+
openssl (~> 3.0)
|
|
11
12
|
retriable_proxy (~> 1.0)
|
|
12
13
|
slop (~> 4.6)
|
|
13
14
|
|
|
@@ -89,7 +90,7 @@ GEM
|
|
|
89
90
|
racc (~> 1.4)
|
|
90
91
|
nokogiri (1.18.10-x86_64-linux-gnu)
|
|
91
92
|
racc (~> 1.4)
|
|
92
|
-
openssl (
|
|
93
|
+
openssl (3.3.2)
|
|
93
94
|
os (1.1.4)
|
|
94
95
|
parallel (1.27.0)
|
|
95
96
|
parser (3.3.10.0)
|
data/bin/sibit
CHANGED
|
@@ -23,6 +23,8 @@ require_relative '../lib/sibit/fake'
|
|
|
23
23
|
require_relative '../lib/sibit/firstof'
|
|
24
24
|
require_relative '../lib/sibit/version'
|
|
25
25
|
|
|
26
|
+
log = Loog::REGULAR
|
|
27
|
+
|
|
26
28
|
opts =
|
|
27
29
|
begin
|
|
28
30
|
Slop.parse(ARGV, strict: true, help: true) do |o|
|
|
@@ -44,7 +46,7 @@ Options are:"
|
|
|
44
46
|
)
|
|
45
47
|
o.bool '--dry', 'Don\'t send a real payment, run in a read-only mode'
|
|
46
48
|
o.bool '--help', 'Read this: https://github.com/yegor256/sibit' do
|
|
47
|
-
|
|
49
|
+
log.info(o)
|
|
48
50
|
exit
|
|
49
51
|
end
|
|
50
52
|
o.bool '--verbose', 'Print all possible debug messages'
|
|
@@ -66,7 +68,7 @@ Options are:"
|
|
|
66
68
|
raise 'Try --help' if opts.arguments.empty?
|
|
67
69
|
|
|
68
70
|
begin
|
|
69
|
-
log = opts[:verbose]
|
|
71
|
+
log = Loog::VERBOSE if opts[:verbose]
|
|
70
72
|
http = opts[:proxy] ? Sibit::HttpProxy.new(opts[:proxy]) : Sibit::Http.new
|
|
71
73
|
apis = opts[:api].map(&:downcase).map do |a|
|
|
72
74
|
case a
|
|
@@ -92,7 +94,7 @@ begin
|
|
|
92
94
|
sibit = Sibit.new(log: log, api: api)
|
|
93
95
|
case opts.arguments[0]
|
|
94
96
|
when 'price'
|
|
95
|
-
|
|
97
|
+
log.info(sibit.price)
|
|
96
98
|
when 'fees'
|
|
97
99
|
fees = sibit.fees
|
|
98
100
|
text = %i[S M L XL].map do |m|
|
|
@@ -100,19 +102,19 @@ begin
|
|
|
100
102
|
usd = sat * sibit.price / 100_000_000
|
|
101
103
|
"#{m}: #{sat}sat / $#{format('%<usd>.02f', usd: usd)}"
|
|
102
104
|
end.join("\n")
|
|
103
|
-
|
|
105
|
+
log.info(text)
|
|
104
106
|
when 'latest'
|
|
105
|
-
|
|
107
|
+
log.info(sibit.latest)
|
|
106
108
|
when 'generate'
|
|
107
|
-
|
|
109
|
+
log.info(sibit.generate)
|
|
108
110
|
when 'create'
|
|
109
111
|
pvt = opts.arguments[1]
|
|
110
112
|
raise 'Private key argument is required' if pvt.nil?
|
|
111
|
-
|
|
113
|
+
log.info(sibit.create(pvt))
|
|
112
114
|
when 'balance'
|
|
113
115
|
address = opts.arguments[1]
|
|
114
116
|
raise 'Address argument is required' if address.nil?
|
|
115
|
-
|
|
117
|
+
log.info(sibit.balance(address))
|
|
116
118
|
when 'pay'
|
|
117
119
|
amount = opts.arguments[1]
|
|
118
120
|
raise 'Amount argument is required' if amount.nil?
|
|
@@ -126,20 +128,22 @@ begin
|
|
|
126
128
|
raise 'Target argument is required' if target.nil?
|
|
127
129
|
change = opts.arguments[5]
|
|
128
130
|
raise 'Change argument is required' if change.nil?
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
log.info(
|
|
132
|
+
sibit.pay(
|
|
133
|
+
amount, fee,
|
|
134
|
+
sources.split(','),
|
|
135
|
+
target, change,
|
|
136
|
+
skip_utxo: opts['skip-utxo']
|
|
137
|
+
)
|
|
134
138
|
)
|
|
135
139
|
else
|
|
136
140
|
raise "Command #{opts.arguments[0]} is not supported"
|
|
137
141
|
end
|
|
138
142
|
rescue StandardError => e
|
|
139
143
|
if opts[:verbose]
|
|
140
|
-
|
|
144
|
+
log.error(Backtrace.new(e))
|
|
141
145
|
else
|
|
142
|
-
|
|
146
|
+
log.error(e.message)
|
|
143
147
|
end
|
|
144
148
|
exit(255)
|
|
145
149
|
end
|
data/lib/sibit/bestof.rb
CHANGED
|
@@ -92,10 +92,10 @@ class Sibit::BestOf
|
|
|
92
92
|
# Just ignore it
|
|
93
93
|
rescue Sibit::Error => e
|
|
94
94
|
errors << e
|
|
95
|
-
@log.
|
|
95
|
+
@log.debug("The API #{api.class.name} failed at #{method}(): #{e.message}") if @verbose
|
|
96
96
|
end
|
|
97
97
|
if results.empty?
|
|
98
|
-
errors.each { |e| @log.
|
|
98
|
+
errors.each { |e| @log.debug(Backtrace.new(e).to_s) }
|
|
99
99
|
raise Sibit::Error, "No APIs out of #{@list.length} managed to succeed at #{method}(): \
|
|
100
100
|
#{@list.map { |a| a.class.name }.join(', ')}"
|
|
101
101
|
end
|
data/lib/sibit/bitcoinchain.rb
CHANGED
|
@@ -42,8 +42,8 @@ class Sibit::Bitcoinchain
|
|
|
42
42
|
raise Sibit::Error, "Block #{hash} not found" if block.nil?
|
|
43
43
|
nxt = block['next_block']
|
|
44
44
|
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
45
|
-
@log.
|
|
46
|
-
@log.
|
|
45
|
+
@log.debug("The block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
46
|
+
@log.debug("The next block of #{hash} is #{nxt}") unless nxt.nil?
|
|
47
47
|
nxt
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -55,12 +55,12 @@ class Sibit::Bitcoinchain
|
|
|
55
55
|
)[0]
|
|
56
56
|
b = json['balance']
|
|
57
57
|
if b.nil?
|
|
58
|
-
@log.
|
|
58
|
+
@log.debug("The balance of #{address} is not visible")
|
|
59
59
|
return 0
|
|
60
60
|
end
|
|
61
61
|
b *= 100_000_000
|
|
62
62
|
b = b.to_i
|
|
63
|
-
@log.
|
|
63
|
+
@log.debug("The balance of #{address} is #{b} satoshi (#{json['transactions']} txns)")
|
|
64
64
|
b
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -74,7 +74,7 @@ class Sibit::Bitcoinchain
|
|
|
74
74
|
hash = Sibit::Json.new(http: @http, log: @log).get(
|
|
75
75
|
Iri.new('https://api-r.bitcoinchain.com/v1/status')
|
|
76
76
|
)['hash']
|
|
77
|
-
@log.
|
|
77
|
+
@log.debug("The latest block hash is #{hash}")
|
|
78
78
|
hash
|
|
79
79
|
end
|
|
80
80
|
|
data/lib/sibit/blockchain.rb
CHANGED
|
@@ -34,7 +34,7 @@ class Sibit::Blockchain
|
|
|
34
34
|
)[currency]
|
|
35
35
|
raise Error, "Unrecognized currency #{currency}" if h.nil?
|
|
36
36
|
price = h['15m']
|
|
37
|
-
@log.
|
|
37
|
+
@log.debug("The price of BTC is #{price} USD")
|
|
38
38
|
price
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -46,9 +46,9 @@ class Sibit::Blockchain
|
|
|
46
46
|
# )
|
|
47
47
|
# nxt = json['next_block'][0]
|
|
48
48
|
# if nxt.nil?
|
|
49
|
-
# @log.
|
|
49
|
+
# @log.debug("There is no block after #{hash}")
|
|
50
50
|
# else
|
|
51
|
-
# @log.
|
|
51
|
+
# @log.debug("The next block of #{hash} is #{nxt}")
|
|
52
52
|
# end
|
|
53
53
|
# nxt
|
|
54
54
|
end
|
|
@@ -59,7 +59,7 @@ class Sibit::Blockchain
|
|
|
59
59
|
Iri.new('https://blockchain.info/rawblock').append(hash)
|
|
60
60
|
)
|
|
61
61
|
h = json['height']
|
|
62
|
-
@log.
|
|
62
|
+
@log.debug("The height of #{hash} is #{h}")
|
|
63
63
|
h
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -70,7 +70,7 @@ class Sibit::Blockchain
|
|
|
70
70
|
accept: [200, 500]
|
|
71
71
|
)
|
|
72
72
|
b = json['final_balance']
|
|
73
|
-
@log.
|
|
73
|
+
@log.debug("The balance of #{address} is #{b} satoshi (#{json['n_tx']} txns)")
|
|
74
74
|
b
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -79,8 +79,12 @@ class Sibit::Blockchain
|
|
|
79
79
|
json = Sibit::Json.new(http: @http, log: @log).get(
|
|
80
80
|
Iri.new('https://api.blockchain.info/mempool/fees')
|
|
81
81
|
)
|
|
82
|
-
@log.
|
|
83
|
-
|
|
82
|
+
@log.debug(
|
|
83
|
+
[
|
|
84
|
+
'Currently recommended Bitcoin fees: ',
|
|
85
|
+
"#{json['regular']}/#{json['priority']}/#{json['limits']['max']} sat/byte"
|
|
86
|
+
].join
|
|
87
|
+
)
|
|
84
88
|
{
|
|
85
89
|
S: json['regular'] / 3,
|
|
86
90
|
M: json['regular'],
|
|
@@ -118,7 +122,7 @@ class Sibit::Blockchain
|
|
|
118
122
|
hash = Sibit::Json.new(http: @http, log: @log).get(
|
|
119
123
|
Iri.new('https://blockchain.info/latestblock')
|
|
120
124
|
)['hash']
|
|
121
|
-
@log.
|
|
125
|
+
@log.debug("The latest block hash is #{hash}")
|
|
122
126
|
hash
|
|
123
127
|
end
|
|
124
128
|
|
data/lib/sibit/blockchair.rb
CHANGED
|
@@ -48,12 +48,12 @@ class Sibit::Blockchair
|
|
|
48
48
|
Iri.new('https://api.blockchair.com/bitcoin/dashboards/address').append(address).fragment(the_key)
|
|
49
49
|
)['data'][address]
|
|
50
50
|
if json.nil?
|
|
51
|
-
@log.
|
|
51
|
+
@log.debug("Address #{address} not found")
|
|
52
52
|
return 0
|
|
53
53
|
end
|
|
54
54
|
a = json['address']
|
|
55
55
|
b = a['balance']
|
|
56
|
-
@log.
|
|
56
|
+
@log.debug("The balance of #{address} is #{b} satoshi")
|
|
57
57
|
b
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -78,7 +78,7 @@ class Sibit::Blockchair
|
|
|
78
78
|
Iri.new('https://api.blockchair.com/bitcoin/push/transaction').fragment(the_key),
|
|
79
79
|
"data=#{hex}"
|
|
80
80
|
)
|
|
81
|
-
@log.
|
|
81
|
+
@log.debug("Transaction (#{hex.length} in hex) has been pushed to Blockchair")
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# This method should fetch a Blockchain block and return as a hash.
|
data/lib/sibit/btc.rb
CHANGED
|
@@ -36,21 +36,21 @@ class Sibit::Btc
|
|
|
36
36
|
uri = Iri.new('https://chain.api.btc.com/v3/address').append(address).append('unspent')
|
|
37
37
|
json = Sibit::Json.new(http: @http, log: @log).get(uri)
|
|
38
38
|
if json['err_no'] == 1
|
|
39
|
-
@log.
|
|
39
|
+
@log.debug("The balance of #{address} is zero (not found)")
|
|
40
40
|
return 0
|
|
41
41
|
end
|
|
42
42
|
data = json['data']
|
|
43
43
|
if data.nil?
|
|
44
|
-
@log.
|
|
44
|
+
@log.debug("The balance of #{address} is probably zero (not found)")
|
|
45
45
|
return 0
|
|
46
46
|
end
|
|
47
47
|
txns = data['list']
|
|
48
48
|
if txns.nil?
|
|
49
|
-
@log.
|
|
49
|
+
@log.debug("The balance of #{address} is probably zero (not found)")
|
|
50
50
|
return 0
|
|
51
51
|
end
|
|
52
52
|
balance = txns.sum { |tx| tx['value'] } || 0
|
|
53
|
-
@log.
|
|
53
|
+
@log.debug("The balance of #{address} is #{balance}, total txns: #{txns.count}")
|
|
54
54
|
balance
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -63,8 +63,8 @@ class Sibit::Btc
|
|
|
63
63
|
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
64
64
|
nxt = data['next_block_hash']
|
|
65
65
|
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
66
|
-
@log.
|
|
67
|
-
@log.
|
|
66
|
+
@log.debug("In BTC.com the block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
67
|
+
@log.debug("The next block of #{hash} is #{nxt}") unless nxt.nil?
|
|
68
68
|
nxt
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -77,7 +77,7 @@ class Sibit::Btc
|
|
|
77
77
|
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
78
78
|
h = data['height']
|
|
79
79
|
raise Sibit::Error, "The block #{hash} found but the height is absent" if h.nil?
|
|
80
|
-
@log.
|
|
80
|
+
@log.debug("The height of #{hash} is #{h}")
|
|
81
81
|
h
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -93,7 +93,7 @@ class Sibit::Btc
|
|
|
93
93
|
data = json['data']
|
|
94
94
|
raise Sibit::Error, 'The latest block not found' if data.nil?
|
|
95
95
|
hash = data['hash']
|
|
96
|
-
@log.
|
|
96
|
+
@log.debug("The hash of the latest block is #{hash}")
|
|
97
97
|
hash
|
|
98
98
|
end
|
|
99
99
|
|
data/lib/sibit/cex.rb
CHANGED
data/lib/sibit/cryptoapis.rb
CHANGED
|
@@ -36,8 +36,8 @@ class Sibit::Cryptoapis
|
|
|
36
36
|
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks').append(hash),
|
|
37
37
|
headers: headers
|
|
38
38
|
)['payload']['hash']
|
|
39
|
-
@log.
|
|
40
|
-
@log.
|
|
39
|
+
@log.debug("The block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
40
|
+
@log.debug("The next block of #{hash} is #{nxt}") unless nxt.nil?
|
|
41
41
|
nxt
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -48,7 +48,7 @@ class Sibit::Cryptoapis
|
|
|
48
48
|
headers: headers
|
|
49
49
|
)['payload']
|
|
50
50
|
h = json['height']
|
|
51
|
-
@log.
|
|
51
|
+
@log.debug("The height of #{hash} is #{h}")
|
|
52
52
|
h
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -59,7 +59,7 @@ class Sibit::Cryptoapis
|
|
|
59
59
|
headers: headers
|
|
60
60
|
)['payload']
|
|
61
61
|
b = (json['balance'].to_f * 100_000_000).to_i
|
|
62
|
-
@log.
|
|
62
|
+
@log.debug("The balance of #{address} is #{b} satoshi")
|
|
63
63
|
b
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -74,7 +74,7 @@ class Sibit::Cryptoapis
|
|
|
74
74
|
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks/latest'),
|
|
75
75
|
headers: headers
|
|
76
76
|
)['payload']['hash']
|
|
77
|
-
@log.
|
|
77
|
+
@log.debug("The latest block hash is #{hash}")
|
|
78
78
|
hash
|
|
79
79
|
end
|
|
80
80
|
|
data/lib/sibit/dry.rb
CHANGED
data/lib/sibit/firstof.rb
CHANGED
|
@@ -88,7 +88,7 @@ class Sibit::FirstOf
|
|
|
88
88
|
done = false
|
|
89
89
|
result = nil
|
|
90
90
|
@list.each do |api|
|
|
91
|
-
@log.
|
|
91
|
+
@log.debug("Calling #{api.class.name}##{method}()...")
|
|
92
92
|
begin
|
|
93
93
|
result = yield api
|
|
94
94
|
done = true
|
|
@@ -97,11 +97,11 @@ class Sibit::FirstOf
|
|
|
97
97
|
# Just ignore it
|
|
98
98
|
rescue Sibit::Error => e
|
|
99
99
|
errors << e
|
|
100
|
-
@log.
|
|
100
|
+
@log.debug("The API #{api.class.name} failed at #{method}(): #{e.message}") if @verbose
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
unless done
|
|
104
|
-
errors.each { |e| @log.
|
|
104
|
+
errors.each { |e| @log.debug(Backtrace.new(e).to_s) }
|
|
105
105
|
raise Sibit::Error, "No APIs out of #{@list.length} managed to succeed at #{method}(): \
|
|
106
106
|
#{@list.map { |a| a.class.name }.join(', ')}"
|
|
107
107
|
end
|
data/lib/sibit/json.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
6
|
require 'cgi'
|
|
7
|
+
require 'elapsed'
|
|
7
8
|
require 'json'
|
|
8
9
|
require 'loog'
|
|
9
10
|
require 'uri'
|
|
@@ -30,52 +31,55 @@ class Sibit::Json
|
|
|
30
31
|
# This method will also log the process and will validate the
|
|
31
32
|
# response for correctness.
|
|
32
33
|
def get(address, headers: {}, accept: [200])
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
ret = nil
|
|
35
|
+
elapsed(@log) do
|
|
36
|
+
uri = URI(address.to_s)
|
|
37
|
+
res = @http.client(uri).get(
|
|
38
|
+
"#{uri.path.empty? ? '/' : uri.path}#{"?#{uri.query}" if uri.query}",
|
|
39
|
+
{
|
|
40
|
+
'Accept' => 'application/json',
|
|
41
|
+
'User-Agent' => user_agent,
|
|
42
|
+
'Accept-Charset' => 'UTF-8',
|
|
43
|
+
'Accept-Encoding' => ''
|
|
44
|
+
}.merge(headers)
|
|
45
|
+
)
|
|
46
|
+
unless accept.include?(res.code.to_i)
|
|
47
|
+
raise Sibit::Error, "Failed to retrieve #{uri} (#{res.code}): #{res.body}"
|
|
48
|
+
end
|
|
49
|
+
ret =
|
|
50
|
+
begin
|
|
51
|
+
JSON.parse(res.body)
|
|
52
|
+
rescue JSON::ParserError => e
|
|
53
|
+
raise Sibit::Error, "Can't parse JSON: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
throw :"GET #{uri}: #{res.code}/#{length(res.body.length)}"
|
|
46
56
|
end
|
|
47
|
-
|
|
48
|
-
JSON.parse(res.body)
|
|
49
|
-
rescue JSON::ParserError => e
|
|
50
|
-
raise Sibit::Error, "Can't parse JSON: #{e.message}"
|
|
57
|
+
ret
|
|
51
58
|
end
|
|
52
59
|
|
|
53
60
|
def post(address, body, headers: {})
|
|
54
|
-
start = Time.now
|
|
55
61
|
uri = URI(address.to_s)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
elapsed(@log) do
|
|
63
|
+
res = @http.client(uri).post(
|
|
64
|
+
"#{uri.path}?#{uri.query}",
|
|
65
|
+
"tx=#{CGI.escape(body)}",
|
|
66
|
+
{
|
|
67
|
+
'Accept' => 'text/plain',
|
|
68
|
+
'User-Agent' => user_agent,
|
|
69
|
+
'Accept-Charset' => 'UTF-8',
|
|
70
|
+
'Accept-Encoding' => '',
|
|
71
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
72
|
+
}.merge(headers)
|
|
73
|
+
)
|
|
74
|
+
unless res.code == '200'
|
|
75
|
+
raise Sibit::Error, "Failed to post tx to #{uri}: #{res.code}\n#{res.body}"
|
|
76
|
+
end
|
|
77
|
+
throw :"POST #{uri}: #{res.code}"
|
|
69
78
|
end
|
|
70
|
-
@log.info("POST #{uri}: #{res.code} in #{age(start)}")
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
private
|
|
74
82
|
|
|
75
|
-
def age(start)
|
|
76
|
-
"#{((Time.now - start) * 1000).round}ms"
|
|
77
|
-
end
|
|
78
|
-
|
|
79
83
|
def length(bytes)
|
|
80
84
|
if bytes > 1024 * 1024
|
|
81
85
|
"#{bytes / (1024 * 1024)}mb"
|
data/lib/sibit/version.rb
CHANGED
data/lib/sibit.rb
CHANGED
|
@@ -49,7 +49,7 @@ class Sibit
|
|
|
49
49
|
# Generates new Bitcoin private key and returns in Hash160 format.
|
|
50
50
|
def generate
|
|
51
51
|
key = Key.generate.priv
|
|
52
|
-
@log.
|
|
52
|
+
@log.debug("Bitcoin private key generated: #{key[0..8]}...")
|
|
53
53
|
key
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -106,11 +106,11 @@ class Sibit
|
|
|
106
106
|
unspent = 0
|
|
107
107
|
size = 100
|
|
108
108
|
utxos = @api.utxos(sources.keys)
|
|
109
|
-
@log.
|
|
109
|
+
@log.debug("#{utxos.count} UTXOs found, these will be used \
|
|
110
110
|
(value/confirmations at tx_hash):")
|
|
111
111
|
utxos.each do |utxo|
|
|
112
112
|
if skip_utxo.include?(utxo[:hash])
|
|
113
|
-
@log.
|
|
113
|
+
@log.debug("UTXO skipped: #{utxo[:hash]}")
|
|
114
114
|
next
|
|
115
115
|
end
|
|
116
116
|
unspent += utxo[:value]
|
|
@@ -124,7 +124,7 @@ class Sibit
|
|
|
124
124
|
i.signature_key(key(k))
|
|
125
125
|
end
|
|
126
126
|
size += 180
|
|
127
|
-
@log.
|
|
127
|
+
@log.debug(
|
|
128
128
|
" #{num(utxo[:value], p)}/#{utxo[:confirmations]} at #{utxo[:hash]}"
|
|
129
129
|
)
|
|
130
130
|
break if unspent > satoshi
|
|
@@ -132,11 +132,14 @@ class Sibit
|
|
|
132
132
|
if unspent < satoshi
|
|
133
133
|
raise Error, "Not enough funds to send #{num(satoshi, p)}, only #{num(unspent, p)} left"
|
|
134
134
|
end
|
|
135
|
-
builder.output(satoshi, target)
|
|
136
135
|
f = mfee(fee, size)
|
|
137
|
-
|
|
136
|
+
if f.negative?
|
|
137
|
+
satoshi += f
|
|
138
|
+
f = -f
|
|
139
|
+
end
|
|
138
140
|
raise Error, "The fee #{f.abs} covers the entire amount" if satoshi.zero?
|
|
139
141
|
raise Error, "The fee #{f.abs} is bigger than the amount #{satoshi}" if satoshi.negative?
|
|
142
|
+
builder.output(satoshi, target)
|
|
140
143
|
tx = builder.tx(
|
|
141
144
|
input_value: unspent,
|
|
142
145
|
leave_fee: true,
|
|
@@ -144,7 +147,7 @@ class Sibit
|
|
|
144
147
|
change_address: change
|
|
145
148
|
)
|
|
146
149
|
left = unspent - tx.outputs.sum(&:value)
|
|
147
|
-
@log.
|
|
150
|
+
@log.debug("A new Bitcoin transaction #{tx.hash} prepared:
|
|
148
151
|
#{tx.in.count} input#{'s' if tx.in.count > 1}:
|
|
149
152
|
#{tx.inputs.map { |i| " in: #{i.prev_out.unpack1('H*')}:#{i.prev_out_index}" }.join("\n ")}
|
|
150
153
|
#{tx.out.count} output#{'s' if tx.out.count > 1}:
|
|
@@ -186,10 +189,10 @@ class Sibit
|
|
|
186
189
|
json = @api.block(block)
|
|
187
190
|
if json[:orphan]
|
|
188
191
|
steps = 4
|
|
189
|
-
@log.
|
|
192
|
+
@log.debug("Orphan block found at #{block}, moving #{steps} steps back...")
|
|
190
193
|
steps.times do
|
|
191
194
|
block = json[:previous]
|
|
192
|
-
@log.
|
|
195
|
+
@log.debug("Moved back to #{block}")
|
|
193
196
|
json = @api.block(block)
|
|
194
197
|
end
|
|
195
198
|
next
|
|
@@ -203,34 +206,34 @@ class Sibit
|
|
|
203
206
|
hash = "#{t[:hash]}:#{i}"
|
|
204
207
|
satoshi = o[:value]
|
|
205
208
|
if yield(address, hash, satoshi)
|
|
206
|
-
@log.
|
|
209
|
+
@log.debug("Bitcoin tx found at #{hash} for #{satoshi} sent to #{address}")
|
|
207
210
|
end
|
|
208
211
|
end
|
|
209
212
|
checked += 1
|
|
210
213
|
end
|
|
211
214
|
count += 1
|
|
212
|
-
@log.
|
|
215
|
+
@log.debug("We checked #{checked} txns and #{checked_outputs} outputs \
|
|
213
216
|
in block #{block} (by #{json[:provider]})")
|
|
214
217
|
block = json[:next]
|
|
215
218
|
begin
|
|
216
219
|
if block.nil?
|
|
217
|
-
@log.
|
|
220
|
+
@log.debug("The next_block is empty in #{json[:hash]}, this may be the end...")
|
|
218
221
|
block = @api.next_of(json[:hash])
|
|
219
222
|
end
|
|
220
223
|
rescue Sibit::Error => e
|
|
221
|
-
@log.
|
|
224
|
+
@log.debug("Failed to get the next_of(#{json[:hash]}), quitting: #{e.message}")
|
|
222
225
|
break
|
|
223
226
|
end
|
|
224
227
|
if block.nil?
|
|
225
|
-
@log.
|
|
228
|
+
@log.debug("The block #{json[:hash]} is definitely the end of Blockchain, we stop.")
|
|
226
229
|
break
|
|
227
230
|
end
|
|
228
231
|
if count >= max
|
|
229
|
-
@log.
|
|
232
|
+
@log.debug("Too many blocks (#{count}) in one go, let's get back to it next time")
|
|
230
233
|
break
|
|
231
234
|
end
|
|
232
235
|
end
|
|
233
|
-
@log.
|
|
236
|
+
@log.debug("Scanned from #{start} to #{json[:hash]} (#{count} blocks)")
|
|
234
237
|
json[:hash]
|
|
235
238
|
end
|
|
236
239
|
|
|
@@ -256,7 +259,7 @@ in block #{block} (by #{json[:provider]})")
|
|
|
256
259
|
|
|
257
260
|
# Calculates a fee in satoshi for the transaction of the specified size.
|
|
258
261
|
# The +fee+ argument could be a number in satoshi, in which case it will
|
|
259
|
-
# be returned as is, or a string like "XL" or "S", in which case the
|
|
262
|
+
# be returned as is, or a string like "XL" or "S-", in which case the
|
|
260
263
|
# fee will be calculated using the +size+ argument (which is the size
|
|
261
264
|
# of the transaction in bytes).
|
|
262
265
|
def mfee(fee, size)
|
|
@@ -269,7 +272,9 @@ in block #{block} (by #{json[:provider]})")
|
|
|
269
272
|
end
|
|
270
273
|
sat = fees[fee.to_sym]
|
|
271
274
|
raise Error, "Can't understand the fee: #{fee.inspect}" if sat.nil?
|
|
272
|
-
mul * sat * size
|
|
275
|
+
f = mul * sat * size
|
|
276
|
+
@log.debug("Fee calculated as #{mul} * #{sat} * #{size} = #{f}")
|
|
277
|
+
f
|
|
273
278
|
end
|
|
274
279
|
|
|
275
280
|
# Make key from private key string in Hash160.
|
data/sibit.gemspec
CHANGED
|
@@ -31,10 +31,11 @@ Gem::Specification.new do |s|
|
|
|
31
31
|
s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
|
32
32
|
s.add_dependency 'backtrace', '~> 0.3'
|
|
33
33
|
s.add_dependency 'decoor', '~> 0.1'
|
|
34
|
+
s.add_dependency 'elapsed', '~> 0.2'
|
|
34
35
|
s.add_dependency 'iri', '~> 0.5'
|
|
35
|
-
s.add_dependency 'json', '~> 2'
|
|
36
|
+
s.add_dependency 'json', '~> 2.18'
|
|
36
37
|
s.add_dependency 'loog', '~> 0.6'
|
|
37
|
-
s.add_dependency 'openssl', '
|
|
38
|
+
s.add_dependency 'openssl', '~> 3.0'
|
|
38
39
|
s.add_dependency 'retriable_proxy', '~> 1.0'
|
|
39
40
|
s.add_dependency 'slop', '~> 4.6'
|
|
40
41
|
end
|
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.30.
|
|
4
|
+
version: 0.30.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: elapsed
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.2'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: iri
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,14 +71,14 @@ dependencies:
|
|
|
57
71
|
requirements:
|
|
58
72
|
- - "~>"
|
|
59
73
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '2'
|
|
74
|
+
version: '2.18'
|
|
61
75
|
type: :runtime
|
|
62
76
|
prerelease: false
|
|
63
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
78
|
requirements:
|
|
65
79
|
- - "~>"
|
|
66
80
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '2'
|
|
81
|
+
version: '2.18'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: loog
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -83,16 +97,16 @@ dependencies:
|
|
|
83
97
|
name: openssl
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
85
99
|
requirements:
|
|
86
|
-
- - "
|
|
100
|
+
- - "~>"
|
|
87
101
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '
|
|
102
|
+
version: '3.0'
|
|
89
103
|
type: :runtime
|
|
90
104
|
prerelease: false
|
|
91
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
106
|
requirements:
|
|
93
|
-
- - "
|
|
107
|
+
- - "~>"
|
|
94
108
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '
|
|
109
|
+
version: '3.0'
|
|
96
110
|
- !ruby/object:Gem::Dependency
|
|
97
111
|
name: retriable_proxy
|
|
98
112
|
requirement: !ruby/object:Gem::Requirement
|