sibit 0.28.0 → 0.29.1
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 +2 -0
- data/Gemfile.lock +15 -3
- data/README.md +14 -17
- data/Rakefile +16 -3
- data/bin/sibit +1 -4
- data/features/cli.feature +1 -1
- data/features/dry.feature +25 -0
- data/lib/sibit/bestof.rb +68 -71
- data/lib/sibit/bitcoin/base58.rb +32 -33
- data/lib/sibit/bitcoin/key.rb +64 -65
- data/lib/sibit/bitcoin/script.rb +45 -46
- data/lib/sibit/bitcoin/tx.rb +153 -144
- data/lib/sibit/bitcoin/txbuilder.rb +65 -54
- 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 +50 -47
- data/lib/sibit/firstof.rb +73 -76
- data/lib/sibit/http.rb +10 -6
- data/lib/sibit/json.rb +63 -66
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +18 -18
- metadata +2 -1
data/lib/sibit/json.rb
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
+
require 'cgi'
|
|
6
7
|
require 'json'
|
|
8
|
+
require 'loog'
|
|
7
9
|
require 'uri'
|
|
8
|
-
require 'cgi'
|
|
9
|
-
require_relative 'version'
|
|
10
10
|
require_relative 'error'
|
|
11
|
-
require 'loog'
|
|
12
11
|
require_relative 'http'
|
|
12
|
+
require_relative 'version'
|
|
13
13
|
|
|
14
14
|
# Json SDK.
|
|
15
15
|
#
|
|
@@ -19,77 +19,74 @@ require_relative 'http'
|
|
|
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
|
-
@log = log
|
|
29
|
-
end
|
|
22
|
+
class Sibit::Json
|
|
23
|
+
# Constructor.
|
|
24
|
+
def initialize(log: Loog::NULL, http: Sibit::Http.new)
|
|
25
|
+
@http = http
|
|
26
|
+
@log = log
|
|
27
|
+
end
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
@log.info("GET #{uri}: #{res.code}/#{length(res.body.length)} in #{age(start)}")
|
|
50
|
-
JSON.parse(res.body)
|
|
51
|
-
rescue JSON::ParserError => e
|
|
52
|
-
raise Sibit::Error, "Can't parse JSON: #{e.message}"
|
|
29
|
+
# Send GET request to the HTTP and return JSON response.
|
|
30
|
+
# This method will also log the process and will validate the
|
|
31
|
+
# response for correctness.
|
|
32
|
+
def get(address, headers: {}, accept: [200])
|
|
33
|
+
start = Time.now
|
|
34
|
+
uri = URI(address.to_s)
|
|
35
|
+
res = @http.client(uri).get(
|
|
36
|
+
"#{uri.path.empty? ? '/' : uri.path}#{"?#{uri.query}" if uri.query}",
|
|
37
|
+
{
|
|
38
|
+
'Accept' => 'application/json',
|
|
39
|
+
'User-Agent' => user_agent,
|
|
40
|
+
'Accept-Charset' => 'UTF-8',
|
|
41
|
+
'Accept-Encoding' => ''
|
|
42
|
+
}.merge(headers)
|
|
43
|
+
)
|
|
44
|
+
unless accept.include?(res.code.to_i)
|
|
45
|
+
raise Sibit::Error, "Failed to retrieve #{uri} (#{res.code}): #{res.body}"
|
|
53
46
|
end
|
|
47
|
+
@log.info("GET #{uri}: #{res.code}/#{length(res.body.length)} in #{age(start)}")
|
|
48
|
+
JSON.parse(res.body)
|
|
49
|
+
rescue JSON::ParserError => e
|
|
50
|
+
raise Sibit::Error, "Can't parse JSON: #{e.message}"
|
|
51
|
+
end
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
72
|
-
@log.info("POST #{uri}: #{res.code} in #{age(start)}")
|
|
53
|
+
def post(address, body, headers: {})
|
|
54
|
+
start = Time.now
|
|
55
|
+
uri = URI(address.to_s)
|
|
56
|
+
res = @http.client(uri).post(
|
|
57
|
+
"#{uri.path}?#{uri.query}",
|
|
58
|
+
"tx=#{CGI.escape(body)}",
|
|
59
|
+
{
|
|
60
|
+
'Accept' => 'text/plain',
|
|
61
|
+
'User-Agent' => user_agent,
|
|
62
|
+
'Accept-Charset' => 'UTF-8',
|
|
63
|
+
'Accept-Encoding' => '',
|
|
64
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
65
|
+
}.merge(headers)
|
|
66
|
+
)
|
|
67
|
+
unless res.code == '200'
|
|
68
|
+
raise Sibit::Error, "Failed to post tx to #{uri}: #{res.code}\n#{res.body}"
|
|
73
69
|
end
|
|
70
|
+
@log.info("POST #{uri}: #{res.code} in #{age(start)}")
|
|
71
|
+
end
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
private
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
def age(start)
|
|
76
|
+
"#{((Time.now - start) * 1000).round}ms"
|
|
77
|
+
end
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
79
|
+
def length(bytes)
|
|
80
|
+
if bytes > 1024 * 1024
|
|
81
|
+
"#{bytes / (1024 * 1024)}mb"
|
|
82
|
+
elsif bytes > 1024
|
|
83
|
+
"#{bytes / 1024}kb"
|
|
84
|
+
else
|
|
85
|
+
"#{bytes}b"
|
|
89
86
|
end
|
|
87
|
+
end
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end
|
|
89
|
+
def user_agent
|
|
90
|
+
"Anonymous/#{Sibit::VERSION}"
|
|
94
91
|
end
|
|
95
92
|
end
|
data/lib/sibit/version.rb
CHANGED
data/lib/sibit.rb
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
6
|
require 'loog'
|
|
7
|
-
require_relative 'sibit/version'
|
|
8
|
-
require_relative 'sibit/blockchain'
|
|
9
7
|
require_relative 'sibit/bitcoin/base58'
|
|
10
8
|
require_relative 'sibit/bitcoin/key'
|
|
11
9
|
require_relative 'sibit/bitcoin/script'
|
|
12
10
|
require_relative 'sibit/bitcoin/tx'
|
|
13
11
|
require_relative 'sibit/bitcoin/txbuilder'
|
|
12
|
+
require_relative 'sibit/blockchain'
|
|
13
|
+
require_relative 'sibit/version'
|
|
14
|
+
|
|
14
15
|
# Sibit main class.
|
|
15
16
|
#
|
|
16
17
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
@@ -47,14 +48,14 @@ class Sibit
|
|
|
47
48
|
|
|
48
49
|
# Generates new Bitcoin private key and returns in Hash160 format.
|
|
49
50
|
def generate
|
|
50
|
-
key =
|
|
51
|
+
key = Key.generate.priv
|
|
51
52
|
@log.info("Bitcoin private key generated: #{key[0..8]}...")
|
|
52
53
|
key
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
# Creates Bitcoin address using the private key in Hash160 format.
|
|
56
57
|
def create(pvt)
|
|
57
|
-
|
|
58
|
+
Key.new(pvt).addr
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
# Gets the balance of the address, in satoshi.
|
|
@@ -99,9 +100,9 @@ class Sibit
|
|
|
99
100
|
# +change+: the address where the change has to be sent to
|
|
100
101
|
def pay(amount, fee, sources, target, change, skip_utxo: [])
|
|
101
102
|
p = price('USD')
|
|
102
|
-
sources = sources.map { |k| [
|
|
103
|
+
sources = sources.map { |k| [Key.new(k).addr, k] }.to_h
|
|
103
104
|
satoshi = satoshi(amount)
|
|
104
|
-
builder =
|
|
105
|
+
builder = TxBuilder.new
|
|
105
106
|
unspent = 0
|
|
106
107
|
size = 100
|
|
107
108
|
utxos = @api.utxos(sources.keys)
|
|
@@ -117,8 +118,10 @@ class Sibit
|
|
|
117
118
|
i.prev_out(utxo[:hash])
|
|
118
119
|
i.prev_out_index(utxo[:index])
|
|
119
120
|
i.prev_out_script = script_hex(utxo[:script])
|
|
120
|
-
address =
|
|
121
|
-
|
|
121
|
+
address = Script.new(script_hex(utxo[:script])).address
|
|
122
|
+
k = sources[address]
|
|
123
|
+
raise Error, "UTXO arrived to #{address} is incorrect" unless k
|
|
124
|
+
i.signature_key(key(k))
|
|
122
125
|
end
|
|
123
126
|
size += 180
|
|
124
127
|
@log.info(
|
|
@@ -137,7 +140,7 @@ class Sibit
|
|
|
137
140
|
tx = builder.tx(
|
|
138
141
|
input_value: unspent,
|
|
139
142
|
leave_fee: true,
|
|
140
|
-
extra_fee: [f,
|
|
143
|
+
extra_fee: [f, MIN_TX_FEE].max,
|
|
141
144
|
change_address: change
|
|
142
145
|
)
|
|
143
146
|
left = unspent - tx.outputs.sum(&:value)
|
|
@@ -146,7 +149,7 @@ class Sibit
|
|
|
146
149
|
#{tx.inputs.map { |i| " in: #{i.prev_out.unpack1('H*')}:#{i.prev_out_index}" }.join("\n ")}
|
|
147
150
|
#{tx.out.count} output#{'s' if tx.out.count > 1}:
|
|
148
151
|
#{tx.outputs.map { |o| "out: #{o.script_hex} / #{num(o.value, p)}" }.join("\n ")}
|
|
149
|
-
Min tx fee: #{num(
|
|
152
|
+
Min tx fee: #{num(MIN_TX_FEE, p)}
|
|
150
153
|
Fee requested: #{num(f, p)} as \"#{fee}\"
|
|
151
154
|
Fee actually paid: #{num(left, p)}
|
|
152
155
|
Tx size: #{size} bytes
|
|
@@ -178,17 +181,14 @@ class Sibit
|
|
|
178
181
|
raise Error, "The max number must be above zero: #{max}" if max < 1
|
|
179
182
|
block = start
|
|
180
183
|
count = 0
|
|
181
|
-
wrong = []
|
|
182
184
|
json = {}
|
|
183
185
|
loop do
|
|
184
186
|
json = @api.block(block)
|
|
185
187
|
if json[:orphan]
|
|
186
188
|
steps = 4
|
|
187
189
|
@log.info("Orphan block found at #{block}, moving #{steps} steps back...")
|
|
188
|
-
wrong << block
|
|
189
190
|
steps.times do
|
|
190
191
|
block = json[:previous]
|
|
191
|
-
wrong << block
|
|
192
192
|
@log.info("Moved back to #{block}")
|
|
193
193
|
json = @api.block(block)
|
|
194
194
|
end
|
|
@@ -225,7 +225,7 @@ in block #{block} (by #{json[:provider]})")
|
|
|
225
225
|
@log.info("The block #{json[:hash]} is definitely the end of Blockchain, we stop.")
|
|
226
226
|
break
|
|
227
227
|
end
|
|
228
|
-
if count
|
|
228
|
+
if count >= max
|
|
229
229
|
@log.info("Too many blocks (#{count}) in one go, let's get back to it next time")
|
|
230
230
|
break
|
|
231
231
|
end
|
|
@@ -263,9 +263,9 @@ in block #{block} (by #{json[:provider]})")
|
|
|
263
263
|
return fee.to_i if fee.is_a?(Integer)
|
|
264
264
|
raise Error, 'Fee should either be a String or Integer' unless fee.is_a?(String)
|
|
265
265
|
mul = 1
|
|
266
|
-
if fee.
|
|
267
|
-
mul = -1 if fee.
|
|
268
|
-
fee = fee[
|
|
266
|
+
if fee.end_with?('+', '-')
|
|
267
|
+
mul = -1 if fee.end_with?('-')
|
|
268
|
+
fee = fee[0..-2]
|
|
269
269
|
end
|
|
270
270
|
sat = fees[fee.to_sym]
|
|
271
271
|
raise Error, "Can't understand the fee: #{fee.inspect}" if sat.nil?
|
|
@@ -274,7 +274,7 @@ in block #{block} (by #{json[:provider]})")
|
|
|
274
274
|
|
|
275
275
|
# Make key from private key string in Hash160.
|
|
276
276
|
def key(hash160)
|
|
277
|
-
|
|
277
|
+
Key.new(hash160)
|
|
278
278
|
end
|
|
279
279
|
|
|
280
280
|
# Convert script to hex string if needed.
|
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.
|
|
4
|
+
version: 0.29.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -128,6 +128,7 @@ files:
|
|
|
128
128
|
- bin/sibit
|
|
129
129
|
- cucumber.yml
|
|
130
130
|
- features/cli.feature
|
|
131
|
+
- features/dry.feature
|
|
131
132
|
- features/gem_package.feature
|
|
132
133
|
- features/step_definitions/steps.rb
|
|
133
134
|
- features/support/env.rb
|