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 +4 -4
- data/bin/sibit +13 -139
- data/lib/sibit/bin.rb +154 -0
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +16 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 196fa93e65ec890ca33a83d3d56358f311d57933e20432e61b5e3df32ae8515d
|
|
4
|
+
data.tar.gz: 0d02c5a2f773d84dbd13c614c6e52533e88a88dff5c07e4ff304650311ce5a22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
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
|
|
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(
|
|
228
|
-
|
|
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.
|
|
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
|