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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c1f18b76d75fe20bb741ac3c033bde8df69a6f54bb31e0b26f03e900d04f635
4
- data.tar.gz: ae570fa1bf67e683ac1c3a6f7754c225d6a6eea3e2376d2d2d96fc18a39504bf
3
+ metadata.gz: ccc8435725361e992237efffbe4f53e4f6698e8217cbac39ce0f08f8b407cb68
4
+ data.tar.gz: 8a787c829a4539a6b69be2764ef90b09e61b13797375b382e6a4df69826165cf
5
5
  SHA512:
6
- metadata.gz: 60685974e155e059ccc11607b731b32e27df7cec9b51db25701a174c3dee9ac1a22890e922d9b61727bd886b480f01819848b35869b18f2fc96e9fd045d2074e
7
- data.tar.gz: 773589a2fd128215dd2d16ddcd3ad0a89c983367dbd5c224afd90f4f1894cd37d07ee255406ce8372f0ac05327fe7d3efc44e8bf5db1c696c41781d4ea1a53df
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
- slop (~> 4.6)
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.3.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.4)
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.0)
110
- qbash (0.4.8)
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.4.0)
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
@@ -6,6 +6,7 @@ version = 1
6
6
  path = [
7
7
  ".DS_Store",
8
8
  ".gitattributes",
9
+ ".gitleaksignore",
9
10
  ".gitignore",
10
11
  ".pdd",
11
12
  "**.json",
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[test lib].each do |d|
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 'slop'
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
- log = Loog::REGULAR
27
+ VERBOSE = ARGV.include?('--verbose') || ENV.fetch('SIBIT_VERBOSE', nil)
28
+ LOG = VERBOSE ? Loog::VERBOSE : Loog::REGULAR
28
29
 
29
- opts =
30
- begin
31
- Slop.parse(ARGV, strict: true, help: true) do |o|
32
- o.banner = "Usage (#{Sibit::VERSION}): sibit [options] command [args]
33
- Commands are:
34
- price: Get current price of BTC in USD
35
- fees: Get currently recommended transaction fees
36
- latest: Get hash of the latest block
37
- generate: Generate a new private key
38
- create: Create a public Bitcoin address from the key
39
- balance: Check the balance of the Bitcoin address
40
- pay: Send a new Bitcoin transaction
41
- Options are:"
42
- o.string '--proxy', 'HTTPS proxy for all requests, e.g. "localhost:3128"'
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
- raise 'Try --help' if opts.arguments.empty?
70
-
71
- begin
72
- log = Loog::VERBOSE if opts[:verbose]
73
- http = opts[:proxy] ? Sibit::HttpProxy.new(opts[:proxy]) : Sibit::Http.new
74
- apis = opts[:api].map(&:downcase).map do |a|
75
- case a
76
- when 'blockchain'
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
- api = Sibit::FirstOf.new(apis, log: log, verbose: true)
93
- api = Sibit::Dry.new(api, log: log) if opts[:dry]
94
- api = RetriableProxy.for_object(api, on: Sibit::Error) if opts[:attempts] > 1
95
- sibit = Sibit.new(log: log, api: api)
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
- when 'latest'
108
- log.info(sibit.latest)
109
- when 'generate'
110
- log.info(sibit.generate)
111
- when 'create'
112
- pvt = opts.arguments[1]
113
- raise 'Private key argument is required' if pvt.nil?
114
- log.debug("Private key provided: #{pvt.ellipsized(8).inspect}")
115
- log.info(sibit.create(pvt))
116
- when 'balance'
117
- address = opts.arguments[1]
118
- raise 'Address argument is required' if address.nil?
119
- log.info(sibit.balance(address))
120
- when 'pay'
121
- amount = opts.arguments[1]
122
- raise 'Amount argument is required' if amount.nil?
123
- amount = amount.to_i if /^[0-9]+$/.match?(amount)
124
- fee = opts.arguments[2]
125
- raise 'Miners fee argument is required' if fee.nil?
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
- sources = opts.arguments[3]
128
- raise 'Addresses argument is required' if sources.nil?
129
- target = opts.arguments[4]
130
- raise 'Target argument is required' if target.nil?
131
- change = opts.arguments[5]
132
- raise 'Change argument is required' if change.nil?
133
- log.info(
134
- sibit.pay(
135
- amount, fee,
136
- sources.split(','),
137
- target, change,
138
- skip_utxo: opts['skip-utxo']
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
- rescue StandardError => e
145
- if opts[:verbose]
146
- log.error(Backtrace.new(e))
147
- else
148
- log.error(e.message)
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 "--help"
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
@@ -19,6 +19,8 @@ class Sibit
19
19
  @user, @password, @host, @port = parse(addr)
20
20
  end
21
21
 
22
+ attr_reader :host
23
+
22
24
  def client(uri)
23
25
  http = Net::HTTP.new(uri.host, uri.port, @host, @port.to_i, @user, @password)
24
26
  http.use_ssl = true
data/lib/sibit/version.rb CHANGED
@@ -9,5 +9,5 @@
9
9
  # License:: MIT
10
10
  class Sibit
11
11
  # Current version of the library.
12
- VERSION = '0.30.7' unless defined?(VERSION)
12
+ VERSION = '0.32.0' unless defined?(VERSION)
13
13
  end
data/sibit.gemspec CHANGED
@@ -38,5 +38,5 @@ Gem::Specification.new do |s|
38
38
  s.add_dependency 'loog', '~> 0.6'
39
39
  s.add_dependency 'openssl', '~> 3.0'
40
40
  s.add_dependency 'retriable_proxy', '~> 1.0'
41
- s.add_dependency 'slop', '~> 4.6'
41
+ s.add_dependency 'thor', '~> 1.3'
42
42
  end
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.30.7
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: slop
139
+ name: thor
140
140
  requirement: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: '4.6'
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: '4.6'
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+.