sibit 0.32.1 → 0.32.3

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: c756b3d04936258f2bfa46b7a385fbb3f5f9ca696728dc2895504b1f5691e65c
4
- data.tar.gz: 3d88e0ab75c369c989dfaf360a4b332ac0d5e7479f8e9657734d710e58b712ee
3
+ metadata.gz: 196fa93e65ec890ca33a83d3d56358f311d57933e20432e61b5e3df32ae8515d
4
+ data.tar.gz: 0d02c5a2f773d84dbd13c614c6e52533e88a88dff5c07e4ff304650311ce5a22
5
5
  SHA512:
6
- metadata.gz: 4c385350b69515c9a92a32901afd99685dca40cfc38f85be613e7527421b29481a838eceb6cc21ec1bdce70deee2b8d295806118c8039f7bb78a3fd80547ac70
7
- data.tar.gz: acba7a669a53cbb92918751723e9d09a690b1f4e927f275d97633d9e538317bd4c00a355326ee0aa0fbd11a873a41fdf721570f32d73d56ef40fdc489d132b10
6
+ metadata.gz: b62032df000f92c6e58147224c5055c3db2ac1798308022899b0e5a5706b691deb9b948733514bcbbac80e70b2ea9f31e085a1da9557a37fd60076240face315
7
+ data.tar.gz: 21601d77cf209b9980caef8567321f018efe4442eec5caa96f2e791865519c7499d3f7c64111c01823e90ffeb2ed808608fc7e3387c3fda47d3056612a13449c
data/bin/sibit CHANGED
@@ -7,11 +7,9 @@
7
7
  $stdout.sync = true
8
8
 
9
9
  require 'backtrace'
10
- require 'ellipsized'
11
10
  require 'loog'
12
- require 'retriable_proxy'
13
- require 'thor'
14
11
  require_relative '../lib/sibit'
12
+ require_relative '../lib/sibit/bin'
15
13
  require_relative '../lib/sibit/http'
16
14
  require_relative '../lib/sibit/httpproxy'
17
15
  require_relative '../lib/sibit/bitcoinchain'
@@ -24,140 +22,16 @@ require_relative '../lib/sibit/fake'
24
22
  require_relative '../lib/sibit/firstof'
25
23
  require_relative '../lib/sibit/version'
26
24
 
27
- VERBOSE = !ARGV.include?('--quiet') &&
28
- (ARGV.include?('--verbose') || ENV.fetch('SIBIT_VERBOSE', nil))
29
- LOG = VERBOSE ? Loog::VERBOSE : Loog::REGULAR
30
-
31
- # Command-line interface for Sibit.
32
- # Provides commands to interact with the Bitcoin network.
33
- class Bin < Thor
34
- class_option :proxy, type: :string, desc: 'HTTPS proxy for all requests, e.g. "localhost:3128"'
35
- class_option :attempts, type: :numeric, default: 1,
36
- desc: 'How many times should we try before failing'
37
- class_option :dry, type: :boolean, default: false,
38
- desc: "Don't send a real payment, run in a read-only mode"
39
- class_option :verbose, type: :boolean, default: false, desc: 'Print all possible debug messages'
40
- class_option :quiet, type: :boolean, default: false, desc: 'Print only informative messages'
41
- class_option :api, type: :array, default: %w[blockchain btc bitcoinchain blockchair cex],
42
- desc: 'Ordered List of APIs to use, e.g. "blockchain,btc,bitcoinchain"'
43
-
44
- def self.exit_on_failure?
45
- true
46
- end
47
-
48
- def self.handle_argument_error(command, error, args, _arity)
49
- raise error unless args.include?('--help') || args.include?('-h')
50
- new.help(command.name)
51
- end
52
-
53
- desc 'price', 'Get current price of BTC in USD'
54
- def price
55
- LOG.info(client.price)
56
- end
57
-
58
- desc 'fees', 'Get currently recommended transaction fees'
59
- def fees
60
- sibit = client
61
- fees = sibit.fees
62
- text = %i[S M L XL].map do |m|
63
- sat = fees[m] * 250
64
- usd = sat * sibit.price / 100_000_000
65
- "#{m}: #{sat}sat / $#{format('%<usd>.02f', usd: usd)}"
66
- end.join("\n")
67
- LOG.info(text)
68
- end
69
-
70
- desc 'latest', 'Get hash of the latest block'
71
- def latest
72
- LOG.info(client.latest)
73
- end
74
-
75
- desc 'generate', 'Generate a new private key'
76
- def generate
77
- LOG.info(client.generate)
78
- end
79
-
80
- desc 'create KEY', 'Create a public Bitcoin address from the private key'
81
- def create(key)
82
- LOG.debug("Private key provided: #{key.ellipsized(8).inspect}")
83
- LOG.info(client.create(key))
84
- end
85
-
86
- desc 'balance ADDRESS', 'Check the balance of the Bitcoin address'
87
- def balance(address)
88
- LOG.info(client.balance(address))
89
- end
90
-
91
- desc 'pay AMOUNT FEE SOURCES TARGET CHANGE',
92
- 'Send a new Bitcoin transaction (AMOUNT can be "MAX" to use full balance)'
93
- option :skip_utxo, type: :array, default: [],
94
- desc: 'List of UTXO that must be skipped while paying'
95
- option :yes, type: :boolean, default: false,
96
- desc: 'Skip confirmation prompt and send the payment immediately'
97
- def pay(amount, fee, sources, target, change)
98
- keys = sources.split(',')
99
- if amount.upcase == 'MAX'
100
- addrs = keys.map { |k| Sibit::Key.new(k).bech32 }
101
- amount = addrs.sum { |a| client.balance(a) }
102
- end
103
- amount = amount.to_i if amount.is_a?(String) && /^[0-9]+$/.match?(amount)
104
- fee = fee.to_i if /^[0-9]+$/.match?(fee)
105
- args = [amount, fee, keys, target, change]
106
- kwargs = { skip_utxo: options[:skip_utxo] }
107
- unless options[:yes] || options[:dry]
108
- client(dry: true).pay(*args, **kwargs)
109
- print 'Do you confirm this payment? (yes/no): '
110
- answer = $stdin.gets&.strip&.downcase
111
- raise Sibit::Error, 'Payment cancelled by user' unless answer == 'yes'
112
- end
113
- LOG.info(client.pay(*args, **kwargs))
114
- end
115
-
116
- desc 'version', 'Print program version'
117
- def version
118
- LOG.info(Sibit::VERSION)
119
- end
120
-
121
- private
122
-
123
- def client(dry: false)
124
- proxy = options[:proxy] || ENV.fetch('SIBIT_PROXY', nil)
125
- http = proxy ? Sibit::HttpProxy.new(proxy) : Sibit::Http.new
126
- LOG.debug("Using proxy at #{http.host}") if proxy
127
- apis = options[:api].flat_map { |a| a.split(',') }.map(&:downcase).map do |a|
128
- case a
129
- when 'blockchain'
130
- Sibit::Blockchain.new(http: http, log: LOG)
131
- when 'btc'
132
- Sibit::Btc.new(http: http, log: LOG)
133
- when 'bitcoinchain'
134
- Sibit::Bitcoinchain.new(http: http, log: LOG)
135
- when 'blockchair'
136
- Sibit::Blockchair.new(http: http, log: LOG)
137
- when 'cex'
138
- Sibit::Cex.new(http: http, log: LOG)
139
- when 'fake'
140
- Sibit::Fake.new
141
- else
142
- raise Sibit::Error, "Unknown API \"#{a}\""
143
- end
144
- end
145
- api = Sibit::FirstOf.new(apis, log: LOG, verbose: true)
146
- api = Sibit::Dry.new(api, log: LOG) if options[:dry] || dry
147
- api = RetriableProxy.for_object(api, on: Sibit::Error) if options[:attempts] > 1
148
- Sibit.new(log: LOG, api: api)
149
- end
150
- end
151
-
152
- if __FILE__ == $PROGRAM_NAME
153
- begin
154
- Bin.start(ARGV)
155
- rescue StandardError => e
156
- if VERBOSE
157
- LOG.error(Backtrace.new(e))
158
- else
159
- LOG.error(e.message)
160
- end
161
- exit(255)
162
- end
25
+ begin
26
+ Sibit::Bin.start(ARGV)
27
+ rescue StandardError => e
28
+ verbose = !ARGV.include?('--quiet') &&
29
+ (ARGV.include?('--verbose') || ENV.fetch('SIBIT_VERBOSE', nil))
30
+ log = verbose ? Loog::VERBOSE : Loog::REGULAR
31
+ if verbose
32
+ log.error(Backtrace.new(e))
33
+ else
34
+ log.error(e.message)
35
+ end
36
+ exit(255)
163
37
  end
data/lib/sibit/bin.rb ADDED
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'ellipsized'
7
+ require 'retriable_proxy'
8
+ require 'thor'
9
+
10
+ # Sibit main class.
11
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
12
+ # Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
13
+ # License:: MIT
14
+ class Sibit
15
+ # Command-line interface for Sibit.
16
+ #
17
+ # Provides commands to interact with the Bitcoin network.
18
+ #
19
+ # Example:
20
+ # Sibit::Bin.start(['price'])
21
+ # Sibit::Bin.start(['balance', '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'])
22
+ class Bin < Thor
23
+ class_option :proxy, type: :string, desc: 'HTTPS proxy for all requests, e.g. "localhost:3128"'
24
+ class_option :attempts, type: :numeric, default: 1,
25
+ desc: 'How many times should we try before failing'
26
+ class_option :dry, type: :boolean, default: false,
27
+ desc: "Don't send a real payment, run in a read-only mode"
28
+ class_option :verbose, type: :boolean, default: false, desc: 'Print all possible debug messages'
29
+ class_option :quiet, type: :boolean, default: false, desc: 'Print only informative messages'
30
+ class_option :api, type: :array, default: %w[blockchain btc bitcoinchain blockchair cex],
31
+ desc: 'Ordered List of APIs to use, e.g. "blockchain,btc,bitcoinchain"'
32
+
33
+ def self.exit_on_failure?
34
+ true
35
+ end
36
+
37
+ def self.handle_argument_error(command, error, args, _arity)
38
+ raise error unless args.include?('--help') || args.include?('-h')
39
+ new.help(command.name)
40
+ end
41
+
42
+ desc 'price', 'Get current price of BTC in USD'
43
+ def price
44
+ log.info(client.price)
45
+ end
46
+
47
+ desc 'fees', 'Get currently recommended transaction fees'
48
+ def fees
49
+ sibit = client
50
+ fees = sibit.fees
51
+ text = %i[S M L XL].map do |m|
52
+ sat = fees[m] * 250
53
+ usd = sat * sibit.price / 100_000_000
54
+ "#{m}: #{sat}sat / $#{format('%<usd>.02f', usd: usd)}"
55
+ end.join("\n")
56
+ log.info(text)
57
+ end
58
+
59
+ desc 'latest', 'Get hash of the latest block'
60
+ def latest
61
+ log.info(client.latest)
62
+ end
63
+
64
+ desc 'generate', 'Generate a new private key'
65
+ def generate
66
+ log.info(client.generate)
67
+ end
68
+
69
+ desc 'create KEY', 'Create a public Bitcoin address from the private key'
70
+ def create(key)
71
+ log.debug("Private key provided: #{key.ellipsized(8).inspect}")
72
+ log.info(client.create(key))
73
+ end
74
+
75
+ desc 'balance ADDRESS', 'Check the balance of the Bitcoin address'
76
+ def balance(address)
77
+ log.info(client.balance(address))
78
+ end
79
+
80
+ desc \
81
+ 'pay AMOUNT FEE SOURCES TARGET CHANGE',
82
+ 'Send a new Bitcoin transaction (AMOUNT can be "MAX" to use full balance)'
83
+ option :skip_utxo, type: :array, default: [],
84
+ desc: 'List of UTXO that must be skipped while paying'
85
+ option :base58, type: :boolean, default: false,
86
+ desc: 'Convert private addresses to public in base58'
87
+ option :yes, type: :boolean, default: false,
88
+ desc: 'Skip confirmation prompt and send the payment immediately'
89
+ def pay(amount, fee, sources, target, change)
90
+ keys = sources.split(',')
91
+ if amount.upcase == 'MAX'
92
+ addrs = keys.map do |k|
93
+ kk = Sibit::Key.new(k)
94
+ options[:base58] ? kk.base58 : kk.bech32
95
+ end
96
+ amount = addrs.sum { |a| client.balance(a) }
97
+ end
98
+ amount = amount.to_i if amount.is_a?(String) && /^[0-9]+$/.match?(amount)
99
+ fee = fee.to_i if /^[0-9]+$/.match?(fee)
100
+ args = [amount, fee, keys, target, change]
101
+ kwargs = { skip_utxo: options[:skip_utxo], base58: options[:base58] }
102
+ unless options[:yes] || options[:dry]
103
+ client(dry: true).pay(*args, **kwargs)
104
+ print 'Do you confirm this payment? (yes/no): '
105
+ answer = $stdin.gets&.strip&.downcase
106
+ raise Sibit::Error, 'Payment cancelled by user' unless answer == 'yes'
107
+ end
108
+ log.info(client.pay(*args, **kwargs))
109
+ end
110
+
111
+ desc 'version', 'Print program version'
112
+ def version
113
+ log.info(Sibit::VERSION)
114
+ end
115
+
116
+ private
117
+
118
+ def log
119
+ @log ||= begin
120
+ verbose = !ARGV.include?('--quiet') &&
121
+ (ARGV.include?('--verbose') || ENV.fetch('SIBIT_VERBOSE', nil))
122
+ verbose ? Loog::VERBOSE : Loog::REGULAR
123
+ end
124
+ end
125
+
126
+ def client(dry: false)
127
+ proxy = options[:proxy] || ENV.fetch('SIBIT_PROXY', nil)
128
+ http = proxy ? Sibit::HttpProxy.new(proxy) : Sibit::Http.new
129
+ log.debug("Using proxy at #{http.host}") if proxy
130
+ apis = options[:api].flat_map { |a| a.split(',') }.map(&:downcase).map do |a|
131
+ case a
132
+ when 'blockchain'
133
+ Sibit::Blockchain.new(http: http, log: log)
134
+ when 'btc'
135
+ Sibit::Btc.new(http: http, log: log)
136
+ when 'bitcoinchain'
137
+ Sibit::Bitcoinchain.new(http: http, log: log)
138
+ when 'blockchair'
139
+ Sibit::Blockchair.new(http: http, log: log)
140
+ when 'cex'
141
+ Sibit::Cex.new(http: http, log: log)
142
+ when 'fake'
143
+ Sibit::Fake.new
144
+ else
145
+ raise Sibit::Error, "Unknown API \"#{a}\""
146
+ end
147
+ end
148
+ api = Sibit::FirstOf.new(apis, log: log, verbose: true)
149
+ api = Sibit::Dry.new(api, log: log) if options[:dry] || dry
150
+ api = RetriableProxy.for_object(api, on: Sibit::Error) if options[:attempts] > 1
151
+ Sibit.new(log: log, api: api)
152
+ end
153
+ end
154
+ end
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.32.1' unless defined?(VERSION)
12
+ VERSION = '0.32.3' unless defined?(VERSION)
13
13
  end
data/lib/sibit.rb CHANGED
@@ -103,18 +103,26 @@ class Sibit
103
103
  # +target+: the target address to send to
104
104
  # +change+: the address where the change has to be sent to
105
105
  # +network+: optional network override (:mainnet, :testnet, :regtest)
106
- def pay(amount, fee, sources, target, change, skip_utxo: [], network: nil)
106
+ def pay(amount, fee, sources, target, change, skip_utxo: [], network: nil, base58: false)
107
107
  p = price('USD')
108
108
  keys = sources.map { |k| Key.new(k, network: network) }
109
109
  network = keys.first&.network || :mainnet
110
- sources = keys.to_h { |k| [k.bech32, k.priv] }
110
+ sources = keys.to_h do |k|
111
+ pub =
112
+ if base58
113
+ k.base58
114
+ else
115
+ k.bech32
116
+ end
117
+ @log.debug("Private key #{k.priv.ellipsized(8).inspect} is public as #{pub}:")
118
+ [pub, k.priv]
119
+ end
111
120
  satoshi = satoshi(amount)
112
121
  builder = TxBuilder.new
113
122
  unspent = 0
114
123
  size = 100
115
124
  utxos = @api.utxos(sources.keys)
116
- @log.debug("#{utxos.count} UTXOs found, these will be used \
117
- (value/confirmations at tx_hash):")
125
+ @log.debug("#{utxos.count} UTXOs found, these will be used (value/confirmations at tx_hash):")
118
126
  utxos.each do |utxo|
119
127
  if skip_utxo.include?(utxo[:hash])
120
128
  @log.debug("UTXO skipped: #{utxo[:hash]}")
@@ -224,8 +232,10 @@ class Sibit
224
232
  checked += 1
225
233
  end
226
234
  count += 1
227
- @log.debug("We checked #{checked} txns and #{checked_outputs} outputs \
228
- in block #{block} (by #{json[:provider]})")
235
+ @log.debug(
236
+ "Checked #{checked} txns and #{checked_outputs} outputs " \
237
+ "in block #{block} (by #{json[:provider]})"
238
+ )
229
239
  block = json[:next]
230
240
  begin
231
241
  if block.nil?
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.32.1
4
+ version: 0.32.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -178,6 +178,7 @@ files:
178
178
  - lib/sibit/base58.rb
179
179
  - lib/sibit/bech32.rb
180
180
  - lib/sibit/bestof.rb
181
+ - lib/sibit/bin.rb
181
182
  - lib/sibit/bitcoinchain.rb
182
183
  - lib/sibit/blockchain.rb
183
184
  - lib/sibit/blockchair.rb