sibit 0.33.0 → 0.34.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 +4 -4
- data/Gemfile +4 -5
- data/Gemfile.lock +76 -56
- data/README.md +16 -2
- data/Rakefile +1 -1
- data/bin/sibit +6 -4
- data/cross/alpine.df +6 -0
- data/cross/centos.df +13 -0
- data/cross/debian.df +9 -0
- data/cross/fedora.df +7 -0
- data/cross/opensuse.df +9 -0
- data/cross/rocky.df +13 -0
- data/cross/ubuntu.df +9 -0
- data/features/step_definitions/steps.rb +5 -5
- data/features/support/env.rb +1 -1
- data/lib/sibit/base58.rb +3 -6
- data/lib/sibit/bech32.rb +9 -14
- data/lib/sibit/bestof.rb +7 -4
- data/lib/sibit/bin.rb +65 -53
- data/lib/sibit/bitcoinchain.rb +8 -8
- data/lib/sibit/blockchain.rb +5 -19
- data/lib/sibit/blockchair.rb +14 -24
- data/lib/sibit/btc.rb +39 -45
- data/lib/sibit/cex.rb +12 -11
- data/lib/sibit/cryptoapis.rb +30 -27
- data/lib/sibit/firstof.rb +8 -5
- data/lib/sibit/httpproxy.rb +1 -1
- data/lib/sibit/json.rb +7 -7
- data/lib/sibit/key.rb +39 -41
- data/lib/sibit/script.rb +3 -6
- data/lib/sibit/sochain.rb +177 -0
- data/lib/sibit/tx.rb +48 -32
- data/lib/sibit/txbuilder.rb +3 -3
- data/lib/sibit/version.rb +1 -2
- data/lib/sibit.rb +70 -57
- data/sibit.gemspec +11 -11
- metadata +9 -1
data/lib/sibit/bin.rb
CHANGED
|
@@ -22,16 +22,13 @@ class Sibit
|
|
|
22
22
|
class Bin < Thor
|
|
23
23
|
stop_on_unknown_option!
|
|
24
24
|
|
|
25
|
-
class_option :proxy, type: :string,
|
|
26
|
-
desc: 'HTTPS proxy for all requests, e.g. "localhost:3128"'
|
|
25
|
+
class_option :proxy, type: :string, desc: 'HTTPS proxy for all requests, e.g. "localhost:3128"'
|
|
27
26
|
class_option :attempts, type: :numeric, default: 1,
|
|
28
27
|
desc: 'How many times should we try before failing'
|
|
29
28
|
class_option :dry, type: :boolean, default: false,
|
|
30
29
|
desc: "Don't send a real payment, run in a read-only mode"
|
|
31
|
-
class_option :verbose, type: :boolean, default: false,
|
|
32
|
-
|
|
33
|
-
class_option :quiet, type: :boolean, default: false,
|
|
34
|
-
desc: 'Print only informative messages'
|
|
30
|
+
class_option :verbose, type: :boolean, default: false, desc: 'Print all possible debug messages'
|
|
31
|
+
class_option :quiet, type: :boolean, default: false, desc: 'Print only informative messages'
|
|
35
32
|
class_option :api, type: :array, default: %w[blockchain btc bitcoinchain blockchair cex],
|
|
36
33
|
desc: 'Ordered List of APIs to use, e.g. "blockchain,btc,bitcoinchain"'
|
|
37
34
|
class_option :base58, type: :boolean, default: false,
|
|
@@ -45,10 +42,10 @@ class Sibit
|
|
|
45
42
|
return new.help(command.name) if args.include?('--help') || args.include?('-h')
|
|
46
43
|
unknown = args.find { |a| a.start_with?('-') }
|
|
47
44
|
if unknown
|
|
48
|
-
warn
|
|
49
|
-
exit
|
|
45
|
+
warn("Unknown option: #{unknown}")
|
|
46
|
+
exit(1)
|
|
50
47
|
end
|
|
51
|
-
raise
|
|
48
|
+
raise(error)
|
|
52
49
|
end
|
|
53
50
|
|
|
54
51
|
desc 'price', 'Get current price of BTC in USD'
|
|
@@ -60,12 +57,12 @@ class Sibit
|
|
|
60
57
|
def fees
|
|
61
58
|
sibit = client
|
|
62
59
|
fees = sibit.fees
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
log.info(
|
|
61
|
+
%i[S M L XL].map do |m|
|
|
62
|
+
sat = fees[m] * 250
|
|
63
|
+
"#{m}: #{sat}sat / $#{format('%<usd>.02f', usd: (sat * sibit.price / 100_000_000))}"
|
|
64
|
+
end.join("\n")
|
|
65
|
+
)
|
|
69
66
|
end
|
|
70
67
|
|
|
71
68
|
desc 'latest', 'Get hash of the latest block'
|
|
@@ -86,8 +83,10 @@ class Sibit
|
|
|
86
83
|
end
|
|
87
84
|
|
|
88
85
|
desc 'balance ADDRESS', 'Check the balance of the Bitcoin address'
|
|
86
|
+
option :trust, type: :numeric, default: 0,
|
|
87
|
+
desc: 'Minimum number of confirmations required for a UTXO to count toward the balance'
|
|
89
88
|
def balance(address)
|
|
90
|
-
log.info(client.balance(address))
|
|
89
|
+
log.info(client.balance(address, trust: options[:trust]))
|
|
91
90
|
end
|
|
92
91
|
|
|
93
92
|
desc \
|
|
@@ -97,26 +96,32 @@ class Sibit
|
|
|
97
96
|
desc: 'List of UTXO that must be skipped while paying'
|
|
98
97
|
option :yes, type: :boolean, default: false,
|
|
99
98
|
desc: 'Skip confirmation prompt and send the payment immediately'
|
|
100
|
-
option :price, type: :numeric,
|
|
101
|
-
|
|
99
|
+
option :price, type: :numeric, desc: 'BTC price in USD (skips API price fetch if provided)'
|
|
100
|
+
option :trust, type: :numeric, default: 0,
|
|
101
|
+
desc: 'Minimum number of confirmations required on a UTXO before it can be spent'
|
|
102
102
|
def pay(amount, fee, sources, target, change)
|
|
103
103
|
keys = sources.split(',')
|
|
104
104
|
if amount.upcase == 'MAX'
|
|
105
|
-
addrs =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
addrs =
|
|
106
|
+
keys.map do |k|
|
|
107
|
+
kk = Sibit::Key.new(k)
|
|
108
|
+
options[:base58] ? kk.base58 : kk.bech32
|
|
109
|
+
end
|
|
110
|
+
amount = addrs.sum { |a| client.balance(a, trust: options[:trust]) }
|
|
110
111
|
end
|
|
111
|
-
amount = amount
|
|
112
|
-
fee = fee
|
|
112
|
+
amount = Integer(amount, 10) if amount.is_a?(String) && /^[0-9]+$/.match?(amount)
|
|
113
|
+
fee = Integer(fee, 10) if /^[0-9]+$/.match?(fee)
|
|
113
114
|
args = [amount, fee, keys, target, change]
|
|
114
|
-
kwargs = {
|
|
115
|
+
kwargs = {
|
|
116
|
+
skip_utxo: options[:skip_utxo], base58: options[:base58],
|
|
117
|
+
price: options[:price], trust: options[:trust]
|
|
118
|
+
}
|
|
115
119
|
unless options[:yes] || options[:dry]
|
|
116
120
|
client(dry: true).pay(*args, **kwargs)
|
|
117
|
-
print
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
print('Do you confirm this payment? (yes/no): ')
|
|
122
|
+
unless $stdin.gets&.strip&.downcase == 'yes'
|
|
123
|
+
raise(Sibit::Error, 'Payment cancelled by user')
|
|
124
|
+
end
|
|
120
125
|
end
|
|
121
126
|
log.info(client.pay(*args, **kwargs))
|
|
122
127
|
end
|
|
@@ -129,36 +134,43 @@ class Sibit
|
|
|
129
134
|
private
|
|
130
135
|
|
|
131
136
|
def log
|
|
132
|
-
@log
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
return @log if @log
|
|
138
|
+
@log =
|
|
139
|
+
if !options[:quiet] && (options[:verbose] || ENV.fetch('SIBIT_VERBOSE', nil))
|
|
140
|
+
Loog::VERBOSE
|
|
141
|
+
else
|
|
142
|
+
Loog::REGULAR
|
|
143
|
+
end
|
|
137
144
|
end
|
|
138
145
|
|
|
139
146
|
def client(dry: false)
|
|
140
147
|
proxy = options[:proxy] || ENV.fetch('SIBIT_PROXY', nil)
|
|
141
148
|
http = proxy ? Sibit::HttpProxy.new(proxy) : Sibit::Http.new
|
|
142
149
|
log.debug("Using proxy at #{http.host}") if proxy
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
150
|
+
api = Sibit::FirstOf.new(
|
|
151
|
+
options[:api].flat_map { |a| a.split(',') }.map(&:downcase).map do |a|
|
|
152
|
+
case a
|
|
153
|
+
when 'blockchain'
|
|
154
|
+
Sibit::Blockchain.new(http: http, log: log)
|
|
155
|
+
when 'btc'
|
|
156
|
+
Sibit::Btc.new(http: http, log: log)
|
|
157
|
+
when 'bitcoinchain'
|
|
158
|
+
Sibit::Bitcoinchain.new(http: http, log: log)
|
|
159
|
+
when 'blockchair'
|
|
160
|
+
Sibit::Blockchair.new(http: http, log: log)
|
|
161
|
+
when 'cex'
|
|
162
|
+
Sibit::Cex.new(http: http, log: log)
|
|
163
|
+
when 'cryptoapis'
|
|
164
|
+
Sibit::Cryptoapis.new(ENV.fetch('SIBIT_CRYPTOAPIS_KEY', nil), http: http, log: log)
|
|
165
|
+
when 'sochain'
|
|
166
|
+
Sibit::Sochain.new(http: http, log: log)
|
|
167
|
+
when 'fake'
|
|
168
|
+
Sibit::Fake.new
|
|
169
|
+
else
|
|
170
|
+
raise(Sibit::Error, "Unknown API \"#{a}\"")
|
|
171
|
+
end
|
|
172
|
+
end, log: log, verbose: true
|
|
173
|
+
)
|
|
162
174
|
api = Sibit::Dry.new(api, log: log) if options[:dry] || dry
|
|
163
175
|
api = RetriableProxy.for_object(api, on: Sibit::Error) if options[:attempts] > 1
|
|
164
176
|
Sibit.new(log: log, api: api)
|
data/lib/sibit/bitcoinchain.rb
CHANGED
|
@@ -26,12 +26,12 @@ class Sibit::Bitcoinchain
|
|
|
26
26
|
|
|
27
27
|
# Current price of BTC in USD (float returned).
|
|
28
28
|
def price(_currency = 'USD')
|
|
29
|
-
raise
|
|
29
|
+
raise(Sibit::NotSupportedError, 'Bitcoinchain API doesn\'t provide BTC price')
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# The height of the block.
|
|
33
33
|
def height(_hash)
|
|
34
|
-
raise
|
|
34
|
+
raise(Sibit::NotSupportedError, 'Bitcoinchain API doesn\'t provide height()')
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Get hash of the block after this one.
|
|
@@ -39,7 +39,7 @@ class Sibit::Bitcoinchain
|
|
|
39
39
|
block = Sibit::Json.new(http: @http, log: @log).get(
|
|
40
40
|
Iri.new('https://api-r.bitcoinchain.com/v1/block').append(hash)
|
|
41
41
|
)[0]
|
|
42
|
-
raise
|
|
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
45
|
@log.debug("The block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
@@ -59,14 +59,14 @@ class Sibit::Bitcoinchain
|
|
|
59
59
|
return 0
|
|
60
60
|
end
|
|
61
61
|
b *= 100_000_000
|
|
62
|
-
b = b
|
|
62
|
+
b = Integer(b)
|
|
63
63
|
@log.debug("The balance of #{address} is #{b} satoshi (#{json['transactions']} txns)")
|
|
64
64
|
b
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Get recommended fees, in satoshi per byte.
|
|
68
68
|
def fees
|
|
69
|
-
raise
|
|
69
|
+
raise(Sibit::NotSupportedError, 'Not implemented yet')
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# Gets the hash of the latest block.
|
|
@@ -80,12 +80,12 @@ class Sibit::Bitcoinchain
|
|
|
80
80
|
|
|
81
81
|
# Fetch all unspent outputs per address.
|
|
82
82
|
def utxos(_sources)
|
|
83
|
-
raise
|
|
83
|
+
raise(Sibit::NotSupportedError, 'Not implemented yet')
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Push this transaction (in hex format) to the network.
|
|
87
87
|
def push(_hex)
|
|
88
|
-
raise
|
|
88
|
+
raise(Sibit::NotSupportedError, 'Not implemented yet')
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
# This method should fetch a Blockchain block and return as a hash. Raises
|
|
@@ -94,7 +94,7 @@ class Sibit::Bitcoinchain
|
|
|
94
94
|
head = Sibit::Json.new(http: @http, log: @log).get(
|
|
95
95
|
Iri.new('https://api-r.bitcoinchain.com/v1/block').append(hash)
|
|
96
96
|
)[0]
|
|
97
|
-
raise
|
|
97
|
+
raise(Sibit::Error, "The block #{hash} is not found") if head.nil?
|
|
98
98
|
txs = Sibit::Json.new(http: @http, log: @log).get(
|
|
99
99
|
Iri.new('https://api-r.bitcoinchain.com/v1/block/txs').append(hash)
|
|
100
100
|
)
|
data/lib/sibit/blockchain.rb
CHANGED
|
@@ -32,7 +32,7 @@ class Sibit::Blockchain
|
|
|
32
32
|
h = Sibit::Json.new(http: @http, log: @log).get(
|
|
33
33
|
Iri.new('https://blockchain.info/ticker')
|
|
34
34
|
)[currency]
|
|
35
|
-
raise
|
|
35
|
+
raise(Error, "Unrecognized currency #{currency}") if h.nil?
|
|
36
36
|
price = h['15m']
|
|
37
37
|
@log.debug("The price of BTC is #{price} USD")
|
|
38
38
|
price
|
|
@@ -40,25 +40,14 @@ class Sibit::Blockchain
|
|
|
40
40
|
|
|
41
41
|
# Get hash of the block after this one.
|
|
42
42
|
def next_of(_hash)
|
|
43
|
-
raise
|
|
44
|
-
# json = Sibit::Json.new(http: @http, log: @log).get(
|
|
45
|
-
# Iri.new('https://blockchain.info/rawblock').append(hash)
|
|
46
|
-
# )
|
|
47
|
-
# nxt = json['next_block'][0]
|
|
48
|
-
# if nxt.nil?
|
|
49
|
-
# @log.debug("There is no block after #{hash}")
|
|
50
|
-
# else
|
|
51
|
-
# @log.debug("The next block of #{hash} is #{nxt}")
|
|
52
|
-
# end
|
|
53
|
-
# nxt
|
|
43
|
+
raise(Sibit::NotSupportedError, 'next_of() in Blockchain API is broken, always returns NULL')
|
|
54
44
|
end
|
|
55
45
|
|
|
56
46
|
# The height of the block.
|
|
57
47
|
def height(hash)
|
|
58
|
-
|
|
48
|
+
h = Sibit::Json.new(http: @http, log: @log).get(
|
|
59
49
|
Iri.new('https://blockchain.info/rawblock').append(hash)
|
|
60
|
-
)
|
|
61
|
-
h = json['height']
|
|
50
|
+
)['height']
|
|
62
51
|
@log.debug("The height of #{hash} is #{h}")
|
|
63
52
|
h
|
|
64
53
|
end
|
|
@@ -111,10 +100,7 @@ class Sibit::Blockchain
|
|
|
111
100
|
|
|
112
101
|
# Push this transaction (in hex format) to the network.
|
|
113
102
|
def push(hex)
|
|
114
|
-
Sibit::Json.new(http: @http, log: @log).post(
|
|
115
|
-
Iri.new('https://blockchain.info/pushtx'),
|
|
116
|
-
hex
|
|
117
|
-
)
|
|
103
|
+
Sibit::Json.new(http: @http, log: @log).post(Iri.new('https://blockchain.info/pushtx'), hex)
|
|
118
104
|
end
|
|
119
105
|
|
|
120
106
|
# Gets the hash of the latest block.
|
data/lib/sibit/blockchair.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require 'cgi'
|
|
7
6
|
require 'iri'
|
|
8
7
|
require 'json'
|
|
9
8
|
require 'loog'
|
|
@@ -28,67 +27,58 @@ class Sibit::Blockchair
|
|
|
28
27
|
|
|
29
28
|
# Current price of BTC in USD (float returned).
|
|
30
29
|
def price(_currency = 'USD')
|
|
31
|
-
raise
|
|
30
|
+
raise(Sibit::NotSupportedError, 'Blockchair doesn\'t provide BTC price')
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
# The height of the block.
|
|
35
34
|
def height(_hash)
|
|
36
|
-
raise
|
|
35
|
+
raise(Sibit::NotSupportedError, 'Blockchair API doesn\'t provide height()')
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
# Get hash of the block after this one.
|
|
40
39
|
def next_of(_hash)
|
|
41
|
-
|
|
42
|
-
raise Sibit::NotSupportedError, 'Blockchair API doesn\'t provide next_of()'
|
|
40
|
+
raise(Sibit::NotSupportedError, 'Blockchair API doesn\'t provide next_of()')
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
# Gets the balance of the address, in satoshi.
|
|
46
44
|
def balance(address)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)['data'][address]
|
|
45
|
+
uri = Iri.new('https://api.blockchair.com/bitcoin/dashboards/address').append(address)
|
|
46
|
+
uri = uri.add(key: @key) unless @key.nil?
|
|
47
|
+
json = Sibit::Json.new(http: @http, log: @log).get(uri)['data'][address]
|
|
50
48
|
if json.nil?
|
|
51
49
|
@log.debug("Address #{address} not found")
|
|
52
50
|
return 0
|
|
53
51
|
end
|
|
54
|
-
|
|
55
|
-
b = a['balance']
|
|
52
|
+
b = json['address']['balance']
|
|
56
53
|
@log.debug("The balance of #{address} is #{b} satoshi")
|
|
57
54
|
b
|
|
58
55
|
end
|
|
59
56
|
|
|
60
57
|
# Get recommended fees, in satoshi per byte.
|
|
61
58
|
def fees
|
|
62
|
-
raise
|
|
59
|
+
raise(Sibit::NotSupportedError, 'Blockchair doesn\'t implement fees()')
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
# Gets the hash of the latest block.
|
|
66
63
|
def latest
|
|
67
|
-
raise
|
|
64
|
+
raise(Sibit::NotSupportedError, 'Blockchair doesn\'t implement latest()')
|
|
68
65
|
end
|
|
69
66
|
|
|
70
67
|
# Fetch all unspent outputs per address.
|
|
71
68
|
def utxos(_sources)
|
|
72
|
-
raise
|
|
69
|
+
raise(Sibit::NotSupportedError, 'Blockchair doesn\'t implement utxos()')
|
|
73
70
|
end
|
|
74
71
|
|
|
75
72
|
# Push this transaction (in hex format) to the network.
|
|
76
73
|
def push(hex)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
74
|
+
uri = Iri.new('https://api.blockchair.com/bitcoin/push/transaction')
|
|
75
|
+
uri = uri.add(key: @key) unless @key.nil?
|
|
76
|
+
Sibit::Json.new(http: @http, log: @log).post(uri, "data=#{hex}")
|
|
81
77
|
@log.debug("Transaction (#{hex.length} in hex) has been pushed to Blockchair")
|
|
82
78
|
end
|
|
83
79
|
|
|
84
80
|
# This method should fetch a Blockchain block and return as a hash.
|
|
85
81
|
def block(_hash)
|
|
86
|
-
raise
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
private
|
|
90
|
-
|
|
91
|
-
def the_key
|
|
92
|
-
@key.nil? ? '' : "key=#{CGI.escape(@key)}"
|
|
82
|
+
raise(Sibit::NotSupportedError, 'Blockchair doesn\'t implement block()')
|
|
93
83
|
end
|
|
94
84
|
end
|
data/lib/sibit/btc.rb
CHANGED
|
@@ -28,13 +28,12 @@ class Sibit::Btc
|
|
|
28
28
|
|
|
29
29
|
# Current price of BTC in USD (float returned).
|
|
30
30
|
def price(_currency = 'USD')
|
|
31
|
-
raise
|
|
31
|
+
raise(Sibit::NotSupportedError, 'Btc.com API doesn\'t provide prices')
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Gets the balance of the address, in satoshi.
|
|
35
35
|
def balance(address)
|
|
36
|
-
|
|
37
|
-
json = Sibit::Json.new(http: @http, log: @log).get(uri)
|
|
36
|
+
json = Sibit::Json.new(http: @http, log: @log).get(Iri.new('https://chain.api.btc.com/v3/address').append(address).append('unspent'))
|
|
38
37
|
if json['err_no'] == 1
|
|
39
38
|
@log.debug("The balance of #{address} is zero (not found)")
|
|
40
39
|
return 0
|
|
@@ -56,11 +55,10 @@ class Sibit::Btc
|
|
|
56
55
|
|
|
57
56
|
# Get hash of the block after this one, or NIL if it's the last one in Blockchain.
|
|
58
57
|
def next_of(hash)
|
|
59
|
-
|
|
58
|
+
data = Sibit::Json.new(http: @http, log: @log).get(
|
|
60
59
|
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
60
|
+
)['data']
|
|
61
|
+
raise(Sibit::Error, "The block #{hash} not found") if data.nil?
|
|
64
62
|
nxt = data['next_block_hash']
|
|
65
63
|
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
66
64
|
@log.debug("In BTC.com the block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
@@ -70,28 +68,25 @@ class Sibit::Btc
|
|
|
70
68
|
|
|
71
69
|
# The height of the block.
|
|
72
70
|
def height(hash)
|
|
73
|
-
|
|
71
|
+
data = Sibit::Json.new(http: @http, log: @log).get(
|
|
74
72
|
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
73
|
+
)['data']
|
|
74
|
+
raise(Sibit::Error, "The block #{hash} not found") if data.nil?
|
|
78
75
|
h = data['height']
|
|
79
|
-
raise
|
|
76
|
+
raise(Sibit::Error, "The block #{hash} found but the height is absent") if h.nil?
|
|
80
77
|
@log.debug("The height of #{hash} is #{h}")
|
|
81
78
|
h
|
|
82
79
|
end
|
|
83
80
|
|
|
84
81
|
# Get recommended fees, in satoshi per byte.
|
|
85
82
|
def fees
|
|
86
|
-
raise
|
|
83
|
+
raise(Sibit::NotSupportedError, 'Btc.com doesn\'t provide recommended fees')
|
|
87
84
|
end
|
|
88
85
|
|
|
89
86
|
# Gets the hash of the latest block.
|
|
90
87
|
def latest
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
data = json['data']
|
|
94
|
-
raise Sibit::Error, 'The latest block not found' if data.nil?
|
|
88
|
+
data = Sibit::Json.new(http: @http, log: @log).get(Iri.new('https://chain.api.btc.com/v3/block/latest'))['data']
|
|
89
|
+
raise(Sibit::Error, 'The latest block not found') if data.nil?
|
|
95
90
|
hash = data['hash']
|
|
96
91
|
@log.debug("The hash of the latest block is #{hash}")
|
|
97
92
|
hash
|
|
@@ -99,22 +94,21 @@ class Sibit::Btc
|
|
|
99
94
|
|
|
100
95
|
# Fetch all unspent outputs per address.
|
|
101
96
|
def utxos(sources)
|
|
102
|
-
|
|
97
|
+
results = []
|
|
103
98
|
sources.each do |hash|
|
|
104
|
-
|
|
99
|
+
data = Sibit::Json.new(http: @http, log: @log).get(
|
|
105
100
|
Iri.new('https://chain.api.btc.com/v3/address').append(hash).append('unspent')
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
txns.each do |u|
|
|
101
|
+
)['data']
|
|
102
|
+
raise(Sibit::Error, "The address #{hash} not found") if data.nil?
|
|
103
|
+
list = data['list']
|
|
104
|
+
next if list.nil?
|
|
105
|
+
list.each do |u|
|
|
112
106
|
outs = Sibit::Json.new(http: @http, log: @log).get(
|
|
113
107
|
Iri.new('https://chain.api.btc.com/v3/tx').append(u['tx_hash']).add(verbose: 3)
|
|
114
108
|
)['data']['outputs']
|
|
115
109
|
outs.each_with_index do |o, i|
|
|
116
110
|
next unless o['addresses'].include?(hash)
|
|
117
|
-
|
|
111
|
+
results << {
|
|
118
112
|
value: o['value'],
|
|
119
113
|
hash: u['tx_hash'],
|
|
120
114
|
index: i,
|
|
@@ -124,21 +118,20 @@ class Sibit::Btc
|
|
|
124
118
|
end
|
|
125
119
|
end
|
|
126
120
|
end
|
|
127
|
-
|
|
121
|
+
results
|
|
128
122
|
end
|
|
129
123
|
|
|
130
124
|
# Push this transaction (in hex format) to the network.
|
|
131
125
|
def push(_hex)
|
|
132
|
-
raise
|
|
126
|
+
raise(Sibit::NotSupportedError, 'Btc.com doesn\'t provide payment gateway')
|
|
133
127
|
end
|
|
134
128
|
|
|
135
129
|
# This method should fetch a Blockchain block and return as a hash.
|
|
136
130
|
def block(hash)
|
|
137
|
-
|
|
131
|
+
data = Sibit::Json.new(http: @http, log: @log).get(
|
|
138
132
|
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
133
|
+
)['data']
|
|
134
|
+
raise(Sibit::Error, "The block #{hash} not found") if data.nil?
|
|
142
135
|
nxt = data['next_block_hash']
|
|
143
136
|
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
144
137
|
{
|
|
@@ -162,20 +155,21 @@ class Sibit::Btc
|
|
|
162
155
|
Iri.new('https://chain.api.btc.com/v3/block')
|
|
163
156
|
.append(hash).append('tx').add(page: page, pagesize: psize)
|
|
164
157
|
)['data']
|
|
165
|
-
raise
|
|
158
|
+
raise(Sibit::Error, "The block #{hash} has no data at page #{page}") if data.nil?
|
|
166
159
|
list = data['list']
|
|
167
|
-
raise
|
|
168
|
-
txns =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
160
|
+
raise(Sibit::Error, "The list is empty for block #{hash} at page #{page}") if list.nil?
|
|
161
|
+
txns =
|
|
162
|
+
list.map do |t|
|
|
163
|
+
{
|
|
164
|
+
hash: t['hash'],
|
|
165
|
+
outputs: t['outputs'].reject { |o| o['spent_by_tx'] }.map do |o|
|
|
166
|
+
{
|
|
167
|
+
address: o['addresses'][0],
|
|
168
|
+
value: o['value']
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
}
|
|
172
|
+
end
|
|
179
173
|
all += txns
|
|
180
174
|
page += 1
|
|
181
175
|
break if txns.length < psize
|
data/lib/sibit/cex.rb
CHANGED
|
@@ -25,51 +25,52 @@ class Sibit::Cex
|
|
|
25
25
|
|
|
26
26
|
# Current price of BTC in USD (float returned).
|
|
27
27
|
def price(currency = 'USD')
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
p = Float(
|
|
29
|
+
Sibit::Json.new(http: @http, log: @log).get(
|
|
30
|
+
Iri.new('https://cex.io/api/last_price/BTC').append(currency)
|
|
31
|
+
)['lprice']
|
|
30
32
|
)
|
|
31
|
-
p = json['lprice'].to_f
|
|
32
33
|
@log.debug("The price of BTC is #{p} #{currency}")
|
|
33
34
|
p
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# Get hash of the block after this one.
|
|
37
38
|
def next_of(_hash)
|
|
38
|
-
raise
|
|
39
|
+
raise(Sibit::NotSupportedError, 'Cex.io API doesn\'t provide next_of()')
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
# Gets the balance of the address, in satoshi.
|
|
42
43
|
def balance(_address)
|
|
43
|
-
raise
|
|
44
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement balance()')
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
# The height of the block.
|
|
47
48
|
def height(_hash)
|
|
48
|
-
raise
|
|
49
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement height()')
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
# Get recommended fees, in satoshi per byte.
|
|
52
53
|
def fees
|
|
53
|
-
raise
|
|
54
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement fees()')
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
# Gets the hash of the latest block.
|
|
57
58
|
def latest
|
|
58
|
-
raise
|
|
59
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement latest()')
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
# Fetch all unspent outputs per address.
|
|
62
63
|
def utxos(_sources)
|
|
63
|
-
raise
|
|
64
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement utxos()')
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# Push this transaction (in hex format) to the network.
|
|
67
68
|
def push(_hex)
|
|
68
|
-
raise
|
|
69
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement push()')
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
# This method should fetch a Blockchain block and return as a hash.
|
|
72
73
|
def block(_hash)
|
|
73
|
-
raise
|
|
74
|
+
raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement block()')
|
|
74
75
|
end
|
|
75
76
|
end
|