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/cryptoapis.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
|
|
|
@@ -17,134 +17,131 @@ require_relative 'version'
|
|
|
17
17
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
18
18
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
19
19
|
# License:: MIT
|
|
20
|
-
class Sibit
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dry = dry
|
|
29
|
-
end
|
|
20
|
+
class Sibit::Cryptoapis
|
|
21
|
+
# Constructor.
|
|
22
|
+
def initialize(key, log: Loog::NULL, http: Sibit::Http.new, dry: false)
|
|
23
|
+
@key = key
|
|
24
|
+
@http = http
|
|
25
|
+
@log = log
|
|
26
|
+
@dry = dry
|
|
27
|
+
end
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
# Current price of BTC in USD (float returned).
|
|
30
|
+
def price(_currency = 'USD')
|
|
31
|
+
raise Sibit::NotSupportedError, 'Cryptoapis doesn\'t provide BTC price'
|
|
32
|
+
end
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
# Get hash of the block after this one.
|
|
35
|
+
def next_of(hash)
|
|
36
|
+
nxt = Sibit::Json.new(http: @http, log: @log).get(
|
|
37
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks').append(hash),
|
|
38
|
+
headers: headers
|
|
39
|
+
)['payload']['hash']
|
|
40
|
+
@log.info("The block #{hash} is the latest, there is no next block") if nxt.nil?
|
|
41
|
+
@log.info("The next block of #{hash} is #{nxt}") unless nxt.nil?
|
|
42
|
+
nxt
|
|
43
|
+
end
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
# The height of the block.
|
|
46
|
+
def height(hash)
|
|
47
|
+
json = Sibit::Json.new(http: @http, log: @log).get(
|
|
48
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks').append(hash),
|
|
49
|
+
headers: headers
|
|
50
|
+
)['payload']
|
|
51
|
+
h = json['height']
|
|
52
|
+
@log.info("The height of #{hash} is #{h}")
|
|
53
|
+
h
|
|
54
|
+
end
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
# Gets the balance of the address, in satoshi.
|
|
57
|
+
def balance(address)
|
|
58
|
+
json = Sibit::Json.new(http: @http, log: @log).get(
|
|
59
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/address').append(address),
|
|
60
|
+
headers: headers
|
|
61
|
+
)['payload']
|
|
62
|
+
b = (json['balance'].to_f * 100_000_000).to_i
|
|
63
|
+
@log.info("The balance of #{address} is #{b} satoshi")
|
|
64
|
+
b
|
|
65
|
+
end
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
# Get recommended fees, in satoshi per byte.
|
|
68
|
+
def fees
|
|
69
|
+
raise Sibit::NotSupportedError, 'Cryptoapis doesn\'t provide recommended fees'
|
|
70
|
+
end
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
# Gets the hash of the latest block.
|
|
73
|
+
def latest
|
|
74
|
+
hash = Sibit::Json.new(http: @http, log: @log).get(
|
|
75
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks/latest'),
|
|
76
|
+
headers: headers
|
|
77
|
+
)['payload']['hash']
|
|
78
|
+
@log.info("The latest block hash is #{hash}")
|
|
79
|
+
hash
|
|
80
|
+
end
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
# Fetch all unspent outputs per address.
|
|
83
|
+
def utxos(_sources)
|
|
84
|
+
raise Sibit::NotSupportedError, 'Not implemented yet'
|
|
85
|
+
end
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
# Push this transaction (in hex format) to the network.
|
|
88
|
+
def push(hex)
|
|
89
|
+
Sibit::Json.new(http: @http, log: @log).post(
|
|
90
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/testnet/txs/send'),
|
|
91
|
+
JSON.pretty_generate(hex: hex),
|
|
92
|
+
headers: headers
|
|
93
|
+
)
|
|
94
|
+
end
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
96
|
+
# This method should fetch a Blockchain block and return as a hash.
|
|
97
|
+
def block(hash)
|
|
98
|
+
head = Sibit::Json.new(http: @http, log: @log).get(
|
|
99
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks').append(hash),
|
|
100
|
+
headers: headers
|
|
101
|
+
)['payload']
|
|
102
|
+
{
|
|
103
|
+
provider: self.class.name,
|
|
104
|
+
hash: head['hash'],
|
|
105
|
+
orphan: false,
|
|
106
|
+
next: head['nextblockhash'],
|
|
107
|
+
previous: head['previousblockhash'],
|
|
108
|
+
txns: txns(hash)
|
|
109
|
+
}
|
|
110
|
+
end
|
|
113
111
|
|
|
114
|
-
|
|
112
|
+
private
|
|
115
113
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
def headers
|
|
115
|
+
return {} if @key.nil? || @key.empty?
|
|
116
|
+
{
|
|
117
|
+
'X-API-Key': @key
|
|
118
|
+
}
|
|
119
|
+
end
|
|
122
120
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
end
|
|
143
|
-
all += txns
|
|
144
|
-
index += txns.length
|
|
145
|
-
break if txns.length < limit
|
|
121
|
+
def txns(hash)
|
|
122
|
+
index = 0
|
|
123
|
+
limit = 200
|
|
124
|
+
all = []
|
|
125
|
+
loop do
|
|
126
|
+
txns = Sibit::Json.new(http: @http, log: @log).get(
|
|
127
|
+
Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/txs/block/')
|
|
128
|
+
.append(hash).add(index: index, limit: limit),
|
|
129
|
+
headers: headers
|
|
130
|
+
)['payload'].map do |t|
|
|
131
|
+
{
|
|
132
|
+
hash: t['hash'],
|
|
133
|
+
outputs: t['txouts'].map do |o|
|
|
134
|
+
{
|
|
135
|
+
address: o['addresses'][0],
|
|
136
|
+
value: o['amount'].to_f * 100_000_000
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
}
|
|
146
140
|
end
|
|
147
|
-
all
|
|
141
|
+
all += txns
|
|
142
|
+
index += txns.length
|
|
143
|
+
break if txns.length < limit
|
|
148
144
|
end
|
|
145
|
+
all
|
|
149
146
|
end
|
|
150
147
|
end
|
data/lib/sibit/fake.rb
CHANGED
|
@@ -10,60 +10,55 @@ require_relative 'version'
|
|
|
10
10
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
11
11
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
12
12
|
# License:: MIT
|
|
13
|
-
class Sibit
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
4_000
|
|
18
|
-
end
|
|
13
|
+
class Sibit::Fake
|
|
14
|
+
def price(_cur = 'USD')
|
|
15
|
+
4_000
|
|
16
|
+
end
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
def next_of(_hash)
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
def height(_hash)
|
|
23
|
+
1
|
|
24
|
+
end
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
def fees
|
|
27
|
+
{ S: 12, M: 45, L: 100, XL: 200 }
|
|
28
|
+
end
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
def balance(_address)
|
|
31
|
+
100_000_000
|
|
32
|
+
end
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
def utxos(_sources)
|
|
35
|
+
[]
|
|
36
|
+
end
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
# Nothing to do here
|
|
42
|
-
end
|
|
38
|
+
def push(_hex); end
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
def latest
|
|
41
|
+
'00000000000000000008df8a6e1b61d1136803ac9791b8725235c9f780b4ed71'
|
|
42
|
+
end
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
44
|
+
def block(hash)
|
|
45
|
+
{
|
|
46
|
+
provider: self.class.name,
|
|
47
|
+
hash: hash,
|
|
48
|
+
orphan: false,
|
|
49
|
+
next: hash,
|
|
50
|
+
previous: hash,
|
|
51
|
+
txns: [
|
|
52
|
+
{
|
|
53
|
+
hash: hash,
|
|
54
|
+
outputs: [
|
|
55
|
+
{
|
|
56
|
+
address: '1HqhZx8U18TYS5paraTM1MzUQWb7ZbcG9u',
|
|
57
|
+
value: 1000
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
68
63
|
end
|
|
69
64
|
end
|
data/lib/sibit/firstof.rb
CHANGED
|
@@ -12,102 +12,99 @@ require_relative 'error'
|
|
|
12
12
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
13
13
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
14
14
|
# License:: MIT
|
|
15
|
-
class Sibit
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@verbose = verbose
|
|
23
|
-
end
|
|
15
|
+
class Sibit::FirstOf
|
|
16
|
+
# Constructor.
|
|
17
|
+
def initialize(list, log: Loog::NULL, verbose: false)
|
|
18
|
+
@list = list
|
|
19
|
+
@log = log
|
|
20
|
+
@verbose = verbose
|
|
21
|
+
end
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
23
|
+
# Current price of BTC in USD (float returned).
|
|
24
|
+
def price(currency = 'USD')
|
|
25
|
+
first_of('price') do |api|
|
|
26
|
+
api.price(currency)
|
|
30
27
|
end
|
|
28
|
+
end
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
30
|
+
# Gets the balance of the address, in satoshi.
|
|
31
|
+
def balance(address)
|
|
32
|
+
first_of('balance') do |api|
|
|
33
|
+
api.balance(address)
|
|
37
34
|
end
|
|
35
|
+
end
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end
|
|
37
|
+
# Get the height of the block.
|
|
38
|
+
def height(hash)
|
|
39
|
+
first_of('height') do |api|
|
|
40
|
+
api.height(hash)
|
|
44
41
|
end
|
|
42
|
+
end
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
44
|
+
# Get the hash of the next block.
|
|
45
|
+
def next_of(hash)
|
|
46
|
+
first_of('next_of') do |api|
|
|
47
|
+
api.next_of(hash)
|
|
51
48
|
end
|
|
49
|
+
end
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
# Get recommended fees, in satoshi per byte. The method returns
|
|
52
|
+
# a hash: { S: 12, M: 45, L: 100, XL: 200 }
|
|
53
|
+
def fees
|
|
54
|
+
first_of('fees', &:fees)
|
|
55
|
+
end
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
57
|
+
# Fetch all unspent outputs per address.
|
|
58
|
+
def utxos(keys)
|
|
59
|
+
first_of('utxos') do |api|
|
|
60
|
+
api.utxos(keys)
|
|
64
61
|
end
|
|
62
|
+
end
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
# Latest block hash.
|
|
65
|
+
def latest
|
|
66
|
+
first_of('latest', &:latest)
|
|
67
|
+
end
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
end
|
|
69
|
+
# Push this transaction (in hex format) to the network.
|
|
70
|
+
def push(hex)
|
|
71
|
+
first_of('push') do |api|
|
|
72
|
+
api.push(hex)
|
|
76
73
|
end
|
|
74
|
+
end
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
end
|
|
76
|
+
# This method should fetch a block and return as a hash.
|
|
77
|
+
def block(hash)
|
|
78
|
+
first_of('block') do |api|
|
|
79
|
+
api.block(hash)
|
|
83
80
|
end
|
|
81
|
+
end
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
private
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
end
|
|
85
|
+
def first_of(method)
|
|
86
|
+
return yield @list unless @list.is_a?(Array)
|
|
87
|
+
errors = []
|
|
88
|
+
done = false
|
|
89
|
+
result = nil
|
|
90
|
+
@list.each do |api|
|
|
91
|
+
@log.info("Calling #{api.class.name}##{method}()...")
|
|
92
|
+
begin
|
|
93
|
+
result = yield api
|
|
94
|
+
done = true
|
|
95
|
+
break
|
|
96
|
+
rescue Sibit::NotSupportedError
|
|
97
|
+
# Just ignore it
|
|
98
|
+
rescue Sibit::Error => e
|
|
99
|
+
errors << e
|
|
100
|
+
@log.info("The API #{api.class.name} failed at #{method}(): #{e.message}") if @verbose
|
|
104
101
|
end
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
end
|
|
103
|
+
unless done
|
|
104
|
+
errors.each { |e| @log.info(Backtrace.new(e).to_s) }
|
|
105
|
+
raise Sibit::Error, "No APIs out of #{@list.length} managed to succeed at #{method}(): \
|
|
108
106
|
#{@list.map { |a| a.class.name }.join(', ')}"
|
|
109
|
-
end
|
|
110
|
-
result
|
|
111
107
|
end
|
|
108
|
+
result
|
|
112
109
|
end
|
|
113
110
|
end
|
data/lib/sibit/http.rb
CHANGED
|
@@ -10,28 +10,25 @@ require 'net/http'
|
|
|
10
10
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
11
11
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
12
12
|
# License:: MIT
|
|
13
|
-
class Sibit
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
http.read_timeout = 240
|
|
20
|
-
http
|
|
21
|
-
end
|
|
13
|
+
class Sibit::Http
|
|
14
|
+
def client(uri)
|
|
15
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
16
|
+
http.use_ssl = true
|
|
17
|
+
http.read_timeout = 240
|
|
18
|
+
http
|
|
22
19
|
end
|
|
20
|
+
end
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
# This HTTP client with proxy.
|
|
23
|
+
class HttpProxy
|
|
24
|
+
def initialize(addr)
|
|
25
|
+
@host, @port = addr.split(':')
|
|
26
|
+
end
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
28
|
+
def client(uri)
|
|
29
|
+
http = Net::HTTP.new(uri.host, uri.port, @host, @port.to_i)
|
|
30
|
+
http.use_ssl = true
|
|
31
|
+
http.read_timeout = 240
|
|
32
|
+
http
|
|
36
33
|
end
|
|
37
34
|
end
|