sibit 0.28.0 → 0.29.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.lock +3 -3
- data/README.md +14 -17
- data/bin/sibit +0 -4
- data/lib/sibit/bestof.rb +68 -71
- data/lib/sibit/bitcoin/base58.rb +33 -35
- data/lib/sibit/bitcoin/key.rb +64 -66
- data/lib/sibit/bitcoin/script.rb +45 -47
- data/lib/sibit/bitcoin/tx.rb +162 -164
- data/lib/sibit/bitcoin/txbuilder.rb +1 -1
- data/lib/sibit/bitcoinchain.rb +93 -96
- data/lib/sibit/blockchain.rb +115 -118
- data/lib/sibit/blockchair.rb +62 -65
- data/lib/sibit/btc.rb +147 -150
- data/lib/sibit/cex.rb +49 -50
- data/lib/sibit/cryptoapis.rb +113 -116
- data/lib/sibit/fake.rb +42 -47
- data/lib/sibit/firstof.rb +73 -76
- data/lib/sibit/http.rb +17 -20
- data/lib/sibit/json.rb +63 -66
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +7 -9
- metadata +1 -1
data/lib/sibit/btc.rb
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
require 'iri'
|
|
7
7
|
require 'json'
|
|
8
|
+
require 'loog'
|
|
8
9
|
require 'uri'
|
|
9
10
|
require_relative 'error'
|
|
10
11
|
require_relative 'http'
|
|
11
|
-
require 'loog'
|
|
12
12
|
require_relative 'json'
|
|
13
13
|
require_relative 'version'
|
|
14
14
|
|
|
@@ -19,171 +19,168 @@ require_relative 'version'
|
|
|
19
19
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
20
20
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
21
21
|
# License:: MIT
|
|
22
|
-
class Sibit
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@dry = dry
|
|
30
|
-
end
|
|
22
|
+
class Sibit::Btc
|
|
23
|
+
# Constructor.
|
|
24
|
+
def initialize(log: Loog::NULL, http: Sibit::Http.new, dry: false)
|
|
25
|
+
@http = http
|
|
26
|
+
@log = log
|
|
27
|
+
@dry = dry
|
|
28
|
+
end
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
# Current price of BTC in USD (float returned).
|
|
31
|
+
def price(_currency = 'USD')
|
|
32
|
+
raise Sibit::NotSupportedError, 'Btc.com API doesn\'t provide prices'
|
|
33
|
+
end
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
45
|
-
data = json['data']
|
|
46
|
-
if data.nil?
|
|
47
|
-
@log.info("The balance of #{address} is probably zero (not found)")
|
|
48
|
-
return 0
|
|
49
|
-
end
|
|
50
|
-
txns = data['list']
|
|
51
|
-
if txns.nil?
|
|
52
|
-
@log.info("The balance of #{address} is probably zero (not found)")
|
|
53
|
-
return 0
|
|
54
|
-
end
|
|
55
|
-
balance = txns.sum { |tx| tx['value'] } || 0
|
|
56
|
-
@log.info("The balance of #{address} is #{balance}, total txns: #{txns.count}")
|
|
57
|
-
balance
|
|
35
|
+
# Gets the balance of the address, in satoshi.
|
|
36
|
+
def balance(address)
|
|
37
|
+
uri = Iri.new('https://chain.api.btc.com/v3/address').append(address).append('unspent')
|
|
38
|
+
json = Sibit::Json.new(http: @http, log: @log).get(uri)
|
|
39
|
+
if json['err_no'] == 1
|
|
40
|
+
@log.info("The balance of #{address} is zero (not found)")
|
|
41
|
+
return 0
|
|
58
42
|
end
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
64
|
-
)
|
|
65
|
-
data = head['data']
|
|
66
|
-
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
67
|
-
nxt = data['next_block_hash']
|
|
68
|
-
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
69
|
-
@log.info("In BTC.com the block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
70
|
-
@log.info("The next block of #{hash} is #{nxt}") unless nxt.nil?
|
|
71
|
-
nxt
|
|
43
|
+
data = json['data']
|
|
44
|
+
if data.nil?
|
|
45
|
+
@log.info("The balance of #{address} is probably zero (not found)")
|
|
46
|
+
return 0
|
|
72
47
|
end
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
78
|
-
)
|
|
79
|
-
data = json['data']
|
|
80
|
-
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
81
|
-
h = data['height']
|
|
82
|
-
raise Sibit::Error, "The block #{hash} found but the height is absent" if h.nil?
|
|
83
|
-
@log.info("The height of #{hash} is #{h}")
|
|
84
|
-
h
|
|
48
|
+
txns = data['list']
|
|
49
|
+
if txns.nil?
|
|
50
|
+
@log.info("The balance of #{address} is probably zero (not found)")
|
|
51
|
+
return 0
|
|
85
52
|
end
|
|
53
|
+
balance = txns.sum { |tx| tx['value'] } || 0
|
|
54
|
+
@log.info("The balance of #{address} is #{balance}, total txns: #{txns.count}")
|
|
55
|
+
balance
|
|
56
|
+
end
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
58
|
+
# Get hash of the block after this one, or NIL if it's the last one in Blockchain.
|
|
59
|
+
def next_of(hash)
|
|
60
|
+
head = Sibit::Json.new(http: @http, log: @log).get(
|
|
61
|
+
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
62
|
+
)
|
|
63
|
+
data = head['data']
|
|
64
|
+
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
65
|
+
nxt = data['next_block_hash']
|
|
66
|
+
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
67
|
+
@log.info("In BTC.com the block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
68
|
+
@log.info("The next block of #{hash} is #{nxt}") unless nxt.nil?
|
|
69
|
+
nxt
|
|
70
|
+
end
|
|
91
71
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
72
|
+
# The height of the block.
|
|
73
|
+
def height(hash)
|
|
74
|
+
json = Sibit::Json.new(http: @http, log: @log).get(
|
|
75
|
+
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
76
|
+
)
|
|
77
|
+
data = json['data']
|
|
78
|
+
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
79
|
+
h = data['height']
|
|
80
|
+
raise Sibit::Error, "The block #{hash} found but the height is absent" if h.nil?
|
|
81
|
+
@log.info("The height of #{hash} is #{h}")
|
|
82
|
+
h
|
|
83
|
+
end
|
|
102
84
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
85
|
+
# Get recommended fees, in satoshi per byte.
|
|
86
|
+
def fees
|
|
87
|
+
raise Sibit::NotSupportedError, 'Btc.com doesn\'t provide recommended fees'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Gets the hash of the latest block.
|
|
91
|
+
def latest
|
|
92
|
+
uri = Iri.new('https://chain.api.btc.com/v3/block/latest')
|
|
93
|
+
json = Sibit::Json.new(http: @http, log: @log).get(uri)
|
|
94
|
+
data = json['data']
|
|
95
|
+
raise Sibit::Error, 'The latest block not found' if data.nil?
|
|
96
|
+
hash = data['hash']
|
|
97
|
+
@log.info("The hash of the latest block is #{hash}")
|
|
98
|
+
hash
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Fetch all unspent outputs per address.
|
|
102
|
+
def utxos(sources)
|
|
103
|
+
txns = []
|
|
104
|
+
sources.each do |hash|
|
|
105
|
+
json = Sibit::Json.new(http: @http, log: @log).get(
|
|
106
|
+
Iri.new('https://chain.api.btc.com/v3/address').append(hash).append('unspent')
|
|
107
|
+
)
|
|
108
|
+
data = json['data']
|
|
109
|
+
raise Sibit::Error, "The address #{hash} not found" if data.nil?
|
|
110
|
+
txns = data['list']
|
|
111
|
+
next if txns.nil?
|
|
112
|
+
txns.each do |u|
|
|
113
|
+
outs = Sibit::Json.new(http: @http, log: @log).get(
|
|
114
|
+
Iri.new('https://chain.api.btc.com/v3/tx').append(u['tx_hash']).add(verbose: 3)
|
|
115
|
+
)['data']['outputs']
|
|
116
|
+
outs.each_with_index do |o, i|
|
|
117
|
+
next unless o['addresses'].include?(hash)
|
|
118
|
+
txns << {
|
|
119
|
+
value: o['value'],
|
|
120
|
+
hash: u['tx_hash'],
|
|
121
|
+
index: i,
|
|
122
|
+
confirmations: u['confirmations'],
|
|
123
|
+
script: [o['script_hex']].pack('H*')
|
|
124
|
+
}
|
|
128
125
|
end
|
|
129
126
|
end
|
|
130
|
-
txns
|
|
131
127
|
end
|
|
128
|
+
txns
|
|
129
|
+
end
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
# Push this transaction (in hex format) to the network.
|
|
132
|
+
def push(_hex)
|
|
133
|
+
raise Sibit::NotSupportedError, 'Btc.com doesn\'t provide payment gateway'
|
|
134
|
+
end
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
136
|
+
# This method should fetch a Blockchain block and return as a hash.
|
|
137
|
+
def block(hash)
|
|
138
|
+
head = Sibit::Json.new(http: @http, log: @log).get(
|
|
139
|
+
Iri.new('https://chain.api.btc.com/v3/block').append(hash)
|
|
140
|
+
)
|
|
141
|
+
data = head['data']
|
|
142
|
+
raise Sibit::Error, "The block #{hash} not found" if data.nil?
|
|
143
|
+
nxt = data['next_block_hash']
|
|
144
|
+
nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
|
|
145
|
+
{
|
|
146
|
+
provider: self.class.name,
|
|
147
|
+
hash: data['hash'],
|
|
148
|
+
orphan: data['is_orphan'],
|
|
149
|
+
next: nxt,
|
|
150
|
+
previous: data['prev_block_hash'],
|
|
151
|
+
txns: txns(hash)
|
|
152
|
+
}
|
|
153
|
+
end
|
|
156
154
|
|
|
157
|
-
|
|
155
|
+
private
|
|
158
156
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
end
|
|
182
|
-
all += txns
|
|
183
|
-
page += 1
|
|
184
|
-
break if txns.length < psize
|
|
157
|
+
def txns(hash)
|
|
158
|
+
page = 1
|
|
159
|
+
psize = 50
|
|
160
|
+
all = []
|
|
161
|
+
loop do
|
|
162
|
+
data = Sibit::Json.new(http: @http, log: @log).get(
|
|
163
|
+
Iri.new('https://chain.api.btc.com/v3/block')
|
|
164
|
+
.append(hash).append('tx').add(page: page, pagesize: psize)
|
|
165
|
+
)['data']
|
|
166
|
+
raise Sibit::Error, "The block #{hash} has no data at page #{page}" if data.nil?
|
|
167
|
+
list = data['list']
|
|
168
|
+
raise Sibit::Error, "The list is empty for block #{hash} at page #{page}" if list.nil?
|
|
169
|
+
txns = list.map do |t|
|
|
170
|
+
{
|
|
171
|
+
hash: t['hash'],
|
|
172
|
+
outputs: t['outputs'].reject { |o| o['spent_by_tx'] }.map do |o|
|
|
173
|
+
{
|
|
174
|
+
address: o['addresses'][0],
|
|
175
|
+
value: o['value']
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
}
|
|
185
179
|
end
|
|
186
|
-
all
|
|
180
|
+
all += txns
|
|
181
|
+
page += 1
|
|
182
|
+
break if txns.length < psize
|
|
187
183
|
end
|
|
184
|
+
all
|
|
188
185
|
end
|
|
189
186
|
end
|
data/lib/sibit/cex.rb
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require '
|
|
6
|
+
require 'iri'
|
|
7
7
|
require 'json'
|
|
8
8
|
require 'loog'
|
|
9
|
+
require 'uri'
|
|
9
10
|
require_relative 'error'
|
|
10
11
|
require_relative 'http'
|
|
11
12
|
require_relative 'json'
|
|
@@ -15,63 +16,61 @@ require_relative 'json'
|
|
|
15
16
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
16
17
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
17
18
|
# License:: MIT
|
|
18
|
-
class Sibit
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dry = dry
|
|
26
|
-
end
|
|
19
|
+
class Sibit::Cex
|
|
20
|
+
# Constructor.
|
|
21
|
+
def initialize(log: Loog::NULL, http: Sibit::Http.new, dry: false)
|
|
22
|
+
@http = http
|
|
23
|
+
@log = log
|
|
24
|
+
@dry = dry
|
|
25
|
+
end
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
# Current price of BTC in USD (float returned).
|
|
28
|
+
def price(currency = 'USD')
|
|
29
|
+
json = Sibit::Json.new(http: @http, log: @log).get(
|
|
30
|
+
Iri.new('https://cex.io/api/last_price/BTC').append(currency)
|
|
31
|
+
)
|
|
32
|
+
p = json['lprice'].to_f
|
|
33
|
+
@log.info("The price of BTC is #{p} #{currency}")
|
|
34
|
+
p
|
|
35
|
+
end
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
# Get hash of the block after this one.
|
|
38
|
+
def next_of(_hash)
|
|
39
|
+
raise Sibit::NotSupportedError, 'Cex.io API doesn\'t provide next_of()'
|
|
40
|
+
end
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
# Gets the balance of the address, in satoshi.
|
|
43
|
+
def balance(_address)
|
|
44
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement balance()'
|
|
45
|
+
end
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
# The height of the block.
|
|
48
|
+
def height(_hash)
|
|
49
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement height()'
|
|
50
|
+
end
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
# Get recommended fees, in satoshi per byte.
|
|
53
|
+
def fees
|
|
54
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement fees()'
|
|
55
|
+
end
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
# Gets the hash of the latest block.
|
|
58
|
+
def latest
|
|
59
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement latest()'
|
|
60
|
+
end
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
# Fetch all unspent outputs per address.
|
|
63
|
+
def utxos(_sources)
|
|
64
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement utxos()'
|
|
65
|
+
end
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
# Push this transaction (in hex format) to the network.
|
|
68
|
+
def push(_hex)
|
|
69
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement push()'
|
|
70
|
+
end
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
# This method should fetch a Blockchain block and return as a hash.
|
|
73
|
+
def block(_hash)
|
|
74
|
+
raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement block()'
|
|
76
75
|
end
|
|
77
76
|
end
|