sibit 0.30.7 → 0.32.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 +6 -7
- data/REUSE.toml +1 -0
- data/Rakefile +1 -1
- data/bin/sibit +125 -111
- data/features/cli.feature +6 -1
- data/features/step_definitions/steps.rb +0 -5
- data/lib/sibit/httpproxy.rb +2 -0
- data/lib/sibit/version.rb +1 -1
- data/sibit.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ccc8435725361e992237efffbe4f53e4f6698e8217cbac39ce0f08f8b407cb68
|
|
4
|
+
data.tar.gz: 8a787c829a4539a6b69be2764ef90b09e61b13797375b382e6a4df69826165cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 253013c5f6b978ea0db8ea3c2800aad241e2d277fbd743f21130dff952610b37fc27366ec8e67728dba64a97efbe1750c1dd7455faf991e856e8af5d06f52e88
|
|
7
|
+
data.tar.gz: ce524cedb5ae7420113a0d9f6526792f9c47dee863724e64adf7909b94aca0ede25cee286f203063ff65166e00c58512b6310f6ced1ee52a28bd7516ab563e9c
|
data/Gemfile.lock
CHANGED
|
@@ -11,7 +11,7 @@ PATH
|
|
|
11
11
|
loog (~> 0.6)
|
|
12
12
|
openssl (~> 3.0)
|
|
13
13
|
retriable_proxy (~> 1.0)
|
|
14
|
-
|
|
14
|
+
thor (~> 1.3)
|
|
15
15
|
|
|
16
16
|
GEM
|
|
17
17
|
remote: https://rubygems.org/
|
|
@@ -63,7 +63,7 @@ GEM
|
|
|
63
63
|
decoor (0.1.0)
|
|
64
64
|
diff-lcs (1.6.2)
|
|
65
65
|
docile (1.4.1)
|
|
66
|
-
donce (0.
|
|
66
|
+
donce (0.4.0)
|
|
67
67
|
backtrace (~> 0.3)
|
|
68
68
|
os (~> 1.1)
|
|
69
69
|
qbash (~> 0.3)
|
|
@@ -75,7 +75,7 @@ GEM
|
|
|
75
75
|
ffi (1.17.3-arm64-darwin)
|
|
76
76
|
ffi (1.17.3-x86_64-linux-gnu)
|
|
77
77
|
hashdiff (1.2.1)
|
|
78
|
-
iri (0.11.
|
|
78
|
+
iri (0.11.6)
|
|
79
79
|
json (2.18.0)
|
|
80
80
|
language_server-protocol (3.17.0.5)
|
|
81
81
|
lint_roller (1.1.0)
|
|
@@ -106,8 +106,8 @@ GEM
|
|
|
106
106
|
psych (5.3.1)
|
|
107
107
|
date
|
|
108
108
|
stringio
|
|
109
|
-
public_suffix (7.0.
|
|
110
|
-
qbash (0.
|
|
109
|
+
public_suffix (7.0.2)
|
|
110
|
+
qbash (0.5.0)
|
|
111
111
|
backtrace (> 0)
|
|
112
112
|
elapsed (> 0)
|
|
113
113
|
loog (> 0)
|
|
@@ -163,13 +163,12 @@ GEM
|
|
|
163
163
|
simplecov (~> 0.19)
|
|
164
164
|
simplecov-html (0.13.2)
|
|
165
165
|
simplecov_json_formatter (0.1.4)
|
|
166
|
-
slop (4.10.1)
|
|
167
166
|
stringio (3.2.0)
|
|
168
167
|
sys-uname (1.4.1)
|
|
169
168
|
ffi (~> 1.1)
|
|
170
169
|
memoist3 (~> 1.0.0)
|
|
171
170
|
tago (0.6.0)
|
|
172
|
-
thor (1.
|
|
171
|
+
thor (1.5.0)
|
|
173
172
|
tsort (0.2.0)
|
|
174
173
|
unicode-display_width (3.2.0)
|
|
175
174
|
unicode-emoji (~> 4.1)
|
data/REUSE.toml
CHANGED
data/Rakefile
CHANGED
|
@@ -33,7 +33,7 @@ end
|
|
|
33
33
|
desc 'Run them via Ruby, one by one'
|
|
34
34
|
task :picks do
|
|
35
35
|
next if OS.windows?
|
|
36
|
-
%w[
|
|
36
|
+
%w[lib].each do |d|
|
|
37
37
|
Dir["#{d}/**/*.rb"].each do |f|
|
|
38
38
|
qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout, env: { 'PICKS' => 'yes' })
|
|
39
39
|
end
|
data/bin/sibit
CHANGED
|
@@ -10,7 +10,7 @@ require 'backtrace'
|
|
|
10
10
|
require 'ellipsized'
|
|
11
11
|
require 'loog'
|
|
12
12
|
require 'retriable_proxy'
|
|
13
|
-
require '
|
|
13
|
+
require 'thor'
|
|
14
14
|
require_relative '../lib/sibit'
|
|
15
15
|
require_relative '../lib/sibit/http'
|
|
16
16
|
require_relative '../lib/sibit/httpproxy'
|
|
@@ -24,79 +24,38 @@ require_relative '../lib/sibit/fake'
|
|
|
24
24
|
require_relative '../lib/sibit/firstof'
|
|
25
25
|
require_relative '../lib/sibit/version'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
VERBOSE = ARGV.include?('--verbose') || ENV.fetch('SIBIT_VERBOSE', nil)
|
|
28
|
+
LOG = VERBOSE ? Loog::VERBOSE : Loog::REGULAR
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
o.integer(
|
|
44
|
-
'--attempts',
|
|
45
|
-
'How many times should we try before failing',
|
|
46
|
-
default: 1
|
|
47
|
-
)
|
|
48
|
-
o.bool '--dry', 'Don\'t send a real payment, run in a read-only mode'
|
|
49
|
-
o.bool '--help', 'Read this: https://github.com/yegor256/sibit' do
|
|
50
|
-
log.info(o)
|
|
51
|
-
exit
|
|
52
|
-
end
|
|
53
|
-
o.bool '--verbose', 'Print all possible debug messages'
|
|
54
|
-
o.array(
|
|
55
|
-
'--api',
|
|
56
|
-
'Ordered List of APIs to use, e.g. "eblockchain,btc,bitcoinchain"',
|
|
57
|
-
default: %w[blockchain btc bitcoinchain blockchair cex]
|
|
58
|
-
)
|
|
59
|
-
o.array(
|
|
60
|
-
'--skip-utxo',
|
|
61
|
-
'List of UTXTO that must be skipped while paying',
|
|
62
|
-
default: []
|
|
63
|
-
)
|
|
64
|
-
end
|
|
65
|
-
rescue Slop::Error => e
|
|
66
|
-
raise e.message
|
|
30
|
+
# Command-line interface for Sibit.
|
|
31
|
+
# Provides commands to interact with the Bitcoin network.
|
|
32
|
+
class Bin < Thor
|
|
33
|
+
class_option :proxy, type: :string, desc: 'HTTPS proxy for all requests, e.g. "localhost:3128"'
|
|
34
|
+
class_option :attempts, type: :numeric, default: 1,
|
|
35
|
+
desc: 'How many times should we try before failing'
|
|
36
|
+
class_option :dry, type: :boolean, default: false,
|
|
37
|
+
desc: "Don't send a real payment, run in a read-only mode"
|
|
38
|
+
class_option :verbose, type: :boolean, default: false, desc: 'Print all possible debug messages'
|
|
39
|
+
class_option :api, type: :array, default: %w[blockchain btc bitcoinchain blockchair cex],
|
|
40
|
+
desc: 'Ordered List of APIs to use, e.g. "blockchain,btc,bitcoinchain"'
|
|
41
|
+
|
|
42
|
+
def self.exit_on_failure?
|
|
43
|
+
true
|
|
67
44
|
end
|
|
68
45
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Sibit::Blockchain.new(http: http, log: log)
|
|
78
|
-
when 'btc'
|
|
79
|
-
Sibit::Btc.new(http: http, log: log)
|
|
80
|
-
when 'bitcoinchain'
|
|
81
|
-
Sibit::Bitcoinchain.new(http: http, log: log)
|
|
82
|
-
when 'blockchair'
|
|
83
|
-
Sibit::Blockchair.new(http: http, log: log)
|
|
84
|
-
when 'cex'
|
|
85
|
-
Sibit::Cex.new(http: http, log: log)
|
|
86
|
-
when 'fake'
|
|
87
|
-
Sibit::Fake.new
|
|
88
|
-
else
|
|
89
|
-
raise Sibit::Error, "Unknown API \"#{a}\""
|
|
90
|
-
end
|
|
46
|
+
def self.handle_argument_error(command, error, args, _arity)
|
|
47
|
+
raise error unless args.include?('--help') || args.include?('-h')
|
|
48
|
+
new.help(command.name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
desc 'price', 'Get current price of BTC in USD'
|
|
52
|
+
def price
|
|
53
|
+
log.info(client.price)
|
|
91
54
|
end
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
case opts.arguments[0]
|
|
97
|
-
when 'price'
|
|
98
|
-
log.info(sibit.price)
|
|
99
|
-
when 'fees'
|
|
55
|
+
|
|
56
|
+
desc 'fees', 'Get currently recommended transaction fees'
|
|
57
|
+
def fees
|
|
58
|
+
sibit = client
|
|
100
59
|
fees = sibit.fees
|
|
101
60
|
text = %i[S M L XL].map do |m|
|
|
102
61
|
sat = fees[m] * 250
|
|
@@ -104,48 +63,103 @@ begin
|
|
|
104
63
|
"#{m}: #{sat}sat / $#{format('%<usd>.02f', usd: usd)}"
|
|
105
64
|
end.join("\n")
|
|
106
65
|
log.info(text)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
desc 'latest', 'Get hash of the latest block'
|
|
69
|
+
def latest
|
|
70
|
+
log.info(client.latest)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc 'generate', 'Generate a new private key'
|
|
74
|
+
def generate
|
|
75
|
+
log.info(client.generate)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc 'create KEY', 'Create a public Bitcoin address from the private key'
|
|
79
|
+
def create(key)
|
|
80
|
+
log.debug("Private key provided: #{key.ellipsized(8).inspect}")
|
|
81
|
+
log.info(client.create(key))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
desc 'balance ADDRESS', 'Check the balance of the Bitcoin address'
|
|
85
|
+
def balance(address)
|
|
86
|
+
log.info(client.balance(address))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
desc 'pay AMOUNT FEE SOURCES TARGET CHANGE',
|
|
90
|
+
'Send a new Bitcoin transaction (AMOUNT can be "MAX" to use full balance)'
|
|
91
|
+
option :skip_utxo, type: :array, default: [],
|
|
92
|
+
desc: 'List of UTXO that must be skipped while paying'
|
|
93
|
+
option :yes, type: :boolean, default: false,
|
|
94
|
+
desc: 'Skip confirmation prompt and send the payment immediately'
|
|
95
|
+
def pay(amount, fee, sources, target, change)
|
|
96
|
+
keys = sources.split(',')
|
|
97
|
+
if amount.upcase == 'MAX'
|
|
98
|
+
addrs = keys.map { |k| Sibit::Key.new(k).bech32 }
|
|
99
|
+
amount = addrs.sum { |a| client.balance(a) }
|
|
100
|
+
end
|
|
101
|
+
amount = amount.to_i if amount.is_a?(String) && /^[0-9]+$/.match?(amount)
|
|
126
102
|
fee = fee.to_i if /^[0-9]+$/.match?(fee)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
else
|
|
142
|
-
raise "Command #{opts.arguments[0]} is not supported"
|
|
103
|
+
args = [amount, fee, keys, target, change]
|
|
104
|
+
kwargs = { skip_utxo: options[:skip_utxo] }
|
|
105
|
+
unless options[:yes] || options[:dry]
|
|
106
|
+
client(dry: true).pay(*args, **kwargs)
|
|
107
|
+
print 'Do you confirm this payment? (yes/no): '
|
|
108
|
+
answer = $stdin.gets&.strip&.downcase
|
|
109
|
+
raise Sibit::Error, 'Payment cancelled by user' unless answer == 'yes'
|
|
110
|
+
end
|
|
111
|
+
log.info(client.pay(*args, **kwargs))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
desc 'version', 'Print program version'
|
|
115
|
+
def version
|
|
116
|
+
log.info(Sibit::VERSION)
|
|
143
117
|
end
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def log
|
|
122
|
+
LOG
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def client(dry: false)
|
|
126
|
+
proxy = options[:proxy] || ENV.fetch('SIBIT_PROXY', nil)
|
|
127
|
+
http = proxy ? Sibit::HttpProxy.new(proxy) : Sibit::Http.new
|
|
128
|
+
log.debug("Using proxy at #{http.host}") if proxy
|
|
129
|
+
apis = options[:api].flat_map { |a| a.split(',') }.map(&:downcase).map do |a|
|
|
130
|
+
case a
|
|
131
|
+
when 'blockchain'
|
|
132
|
+
Sibit::Blockchain.new(http: http, log: log)
|
|
133
|
+
when 'btc'
|
|
134
|
+
Sibit::Btc.new(http: http, log: log)
|
|
135
|
+
when 'bitcoinchain'
|
|
136
|
+
Sibit::Bitcoinchain.new(http: http, log: log)
|
|
137
|
+
when 'blockchair'
|
|
138
|
+
Sibit::Blockchair.new(http: http, log: log)
|
|
139
|
+
when 'cex'
|
|
140
|
+
Sibit::Cex.new(http: http, log: log)
|
|
141
|
+
when 'fake'
|
|
142
|
+
Sibit::Fake.new
|
|
143
|
+
else
|
|
144
|
+
raise Sibit::Error, "Unknown API \"#{a}\""
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
api = Sibit::FirstOf.new(apis, log: log, verbose: true)
|
|
148
|
+
api = Sibit::Dry.new(api, log: log) if options[:dry] || dry
|
|
149
|
+
api = RetriableProxy.for_object(api, on: Sibit::Error) if options[:attempts] > 1
|
|
150
|
+
Sibit.new(log: log, api: api)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if __FILE__ == $PROGRAM_NAME
|
|
155
|
+
begin
|
|
156
|
+
Bin.start(ARGV)
|
|
157
|
+
rescue StandardError => e
|
|
158
|
+
if VERBOSE
|
|
159
|
+
LOG.error(Backtrace.new(e))
|
|
160
|
+
else
|
|
161
|
+
LOG.error(e.message)
|
|
162
|
+
end
|
|
163
|
+
exit(255)
|
|
149
164
|
end
|
|
150
|
-
exit(255)
|
|
151
165
|
end
|
data/features/cli.feature
CHANGED
|
@@ -6,7 +6,7 @@ Feature: Command Line Processing
|
|
|
6
6
|
Scenario: Help can be printed
|
|
7
7
|
When I run bin/sibit with "--help"
|
|
8
8
|
Then Exit code is zero
|
|
9
|
-
And Stdout contains "
|
|
9
|
+
And Stdout contains "Commands:"
|
|
10
10
|
|
|
11
11
|
Scenario: Bitcoin price can be retrieved
|
|
12
12
|
When I run bin/sibit with "price --attempts=4"
|
|
@@ -31,3 +31,8 @@ Feature: Command Line Processing
|
|
|
31
31
|
Scenario: Bitcoin fees can be printed
|
|
32
32
|
When I run bin/sibit with "fees --verbose --api=fake"
|
|
33
33
|
Then Exit code is zero
|
|
34
|
+
|
|
35
|
+
Scenario: Pay help shows all options
|
|
36
|
+
When I run bin/sibit with "pay --help"
|
|
37
|
+
Then Exit code is zero
|
|
38
|
+
And Stdout contains "--skip-utxo"
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
require 'nokogiri'
|
|
7
7
|
require 'tmpdir'
|
|
8
|
-
require 'slop'
|
|
9
8
|
require 'English'
|
|
10
9
|
require_relative '../../lib/sibit'
|
|
11
10
|
|
|
@@ -14,10 +13,6 @@ Before do
|
|
|
14
13
|
@dir = Dir.mktmpdir('test')
|
|
15
14
|
FileUtils.mkdir_p(@dir)
|
|
16
15
|
Dir.chdir(@dir)
|
|
17
|
-
@opts = Slop.parse ['-v', '-s', @dir] do |o|
|
|
18
|
-
o.bool '-v', '--verbose'
|
|
19
|
-
o.string '-s', '--source'
|
|
20
|
-
end
|
|
21
16
|
end
|
|
22
17
|
|
|
23
18
|
After do
|
data/lib/sibit/httpproxy.rb
CHANGED
data/lib/sibit/version.rb
CHANGED
data/sibit.gemspec
CHANGED
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.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -136,19 +136,19 @@ dependencies:
|
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '1.0'
|
|
138
138
|
- !ruby/object:Gem::Dependency
|
|
139
|
-
name:
|
|
139
|
+
name: thor
|
|
140
140
|
requirement: !ruby/object:Gem::Requirement
|
|
141
141
|
requirements:
|
|
142
142
|
- - "~>"
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '
|
|
144
|
+
version: '1.3'
|
|
145
145
|
type: :runtime
|
|
146
146
|
prerelease: false
|
|
147
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
149
|
- - "~>"
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '
|
|
151
|
+
version: '1.3'
|
|
152
152
|
description: This is a simple Bitcoin client, to use from command line or from your
|
|
153
153
|
Ruby app. You don't need to run any Bitcoin software, no need to install anything,
|
|
154
154
|
etc. All you need is just a command line and Ruby 2.5+.
|