sibit 0.29.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 +12 -0
- data/Rakefile +16 -3
- data/bin/sibit +1 -0
- data/features/cli.feature +1 -1
- data/features/dry.feature +25 -0
- data/lib/sibit/bitcoin/base58.rb +17 -16
- data/lib/sibit/bitcoin/key.rb +4 -3
- data/lib/sibit/bitcoin/script.rb +4 -3
- data/lib/sibit/bitcoin/tx.rb +60 -49
- data/lib/sibit/bitcoin/txbuilder.rb +64 -53
- data/lib/sibit/fake.rb +9 -1
- data/lib/sibit/http.rb +29 -22
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +11 -9
- 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: 16d749128c92e0a715c1545d8cb6aa6a41a4afcbd7fc39db0e708fa87ea7383d
|
|
4
|
+
data.tar.gz: c522bc105501aedd9f3ef6140ac99bb1b18cec23fc060ff8dd6ceed4d4e69dff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 854ede7b60a6cd9e1334341b14f1b712e002b23499a5a192d61b53380b68d3203f4d7c3d0aaa87499d59a885190345e776410e1eb133f26f85b8aaac260772ed
|
|
7
|
+
data.tar.gz: f99cee0f4d8615d0a39ee7609c697b4238e0e867193934564d5f2d29cfffe454b630b8da0f4e7f1bda9620d57d395bdf4b365614cf318c6079b808dab330cc2f
|
data/Gemfile
CHANGED
|
@@ -14,6 +14,8 @@ gem 'logger', '~>1.7', require: false
|
|
|
14
14
|
gem 'minitest', '~>6.0', require: false
|
|
15
15
|
gem 'minitest-reporters', '~>1.7', require: false
|
|
16
16
|
gem 'nokogiri', '~>1.18', require: false
|
|
17
|
+
gem 'os', '~>1.1', require: false
|
|
18
|
+
gem 'qbash', '~>0.0', require: false
|
|
17
19
|
gem 'rake', '~>13.2', require: false
|
|
18
20
|
gem 'rdoc', '~>7.0', require: false
|
|
19
21
|
gem 'rubocop', '~>1.62', require: false
|
data/Gemfile.lock
CHANGED
|
@@ -59,6 +59,9 @@ GEM
|
|
|
59
59
|
date (3.5.1)
|
|
60
60
|
diff-lcs (1.6.2)
|
|
61
61
|
docile (1.4.1)
|
|
62
|
+
elapsed (0.2.1)
|
|
63
|
+
loog (~> 0.6)
|
|
64
|
+
tago (~> 0.1)
|
|
62
65
|
erb (6.0.1)
|
|
63
66
|
ffi (1.17.2-arm64-darwin)
|
|
64
67
|
ffi (1.17.2-x86_64-linux-gnu)
|
|
@@ -85,6 +88,7 @@ GEM
|
|
|
85
88
|
mini_portile2 (~> 2.8.2)
|
|
86
89
|
racc (~> 1.4)
|
|
87
90
|
openssl (4.0.0)
|
|
91
|
+
os (1.1.4)
|
|
88
92
|
parallel (1.27.0)
|
|
89
93
|
parser (3.3.10.0)
|
|
90
94
|
ast (~> 2.4.1)
|
|
@@ -94,6 +98,11 @@ GEM
|
|
|
94
98
|
date
|
|
95
99
|
stringio
|
|
96
100
|
public_suffix (7.0.0)
|
|
101
|
+
qbash (0.4.8)
|
|
102
|
+
backtrace (> 0)
|
|
103
|
+
elapsed (> 0)
|
|
104
|
+
loog (> 0)
|
|
105
|
+
tago (> 0)
|
|
97
106
|
racc (1.8.1)
|
|
98
107
|
rainbow (3.1.1)
|
|
99
108
|
rake (13.3.1)
|
|
@@ -150,6 +159,7 @@ GEM
|
|
|
150
159
|
sys-uname (1.4.1)
|
|
151
160
|
ffi (~> 1.1)
|
|
152
161
|
memoist3 (~> 1.0.0)
|
|
162
|
+
tago (0.6.0)
|
|
153
163
|
thor (1.4.0)
|
|
154
164
|
tsort (0.2.0)
|
|
155
165
|
unicode-display_width (3.2.0)
|
|
@@ -173,6 +183,8 @@ DEPENDENCIES
|
|
|
173
183
|
minitest (~> 6.0)
|
|
174
184
|
minitest-reporters (~> 1.7)
|
|
175
185
|
nokogiri (~> 1.18)
|
|
186
|
+
os (~> 1.1)
|
|
187
|
+
qbash (~> 0.0)
|
|
176
188
|
rake (~> 13.2)
|
|
177
189
|
rdoc (~> 7.0)
|
|
178
190
|
rubocop (~> 1.62)
|
data/Rakefile
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require '
|
|
6
|
+
require 'os'
|
|
7
|
+
require 'qbash'
|
|
7
8
|
require 'rake'
|
|
8
|
-
require 'rdoc'
|
|
9
9
|
require 'rake/clean'
|
|
10
|
+
require 'rdoc'
|
|
11
|
+
require 'rubygems'
|
|
12
|
+
require 'shellwords'
|
|
10
13
|
|
|
11
14
|
def name
|
|
12
15
|
@name ||= File.basename(Dir['*.gemspec'].first, '.*')
|
|
@@ -16,7 +19,7 @@ def version
|
|
|
16
19
|
Gem::Specification.load(Dir['*.gemspec'].first).version
|
|
17
20
|
end
|
|
18
21
|
|
|
19
|
-
task default: %i[clean test features rubocop]
|
|
22
|
+
task default: %i[clean test picks features rubocop]
|
|
20
23
|
|
|
21
24
|
require 'rake/testtask'
|
|
22
25
|
Rake::TestTask.new(:test) do |test|
|
|
@@ -27,6 +30,16 @@ Rake::TestTask.new(:test) do |test|
|
|
|
27
30
|
test.verbose = false
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
desc 'Run them via Ruby, one by one'
|
|
34
|
+
task :picks do
|
|
35
|
+
next if OS.windows?
|
|
36
|
+
%w[test lib].each do |d|
|
|
37
|
+
Dir["#{d}/**/*.rb"].each do |f|
|
|
38
|
+
qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout, env: { 'PICKS' => 'yes' })
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
30
43
|
require 'rdoc/task'
|
|
31
44
|
Rake::RDocTask.new do |rdoc|
|
|
32
45
|
rdoc.rdoc_dir = 'rdoc'
|
data/bin/sibit
CHANGED
|
@@ -11,6 +11,7 @@ require 'loog'
|
|
|
11
11
|
require 'retriable_proxy'
|
|
12
12
|
require 'slop'
|
|
13
13
|
require_relative '../lib/sibit'
|
|
14
|
+
require_relative '../lib/sibit/http'
|
|
14
15
|
require_relative '../lib/sibit/bitcoinchain'
|
|
15
16
|
require_relative '../lib/sibit/blockchain'
|
|
16
17
|
require_relative '../lib/sibit/blockchair'
|
data/features/cli.feature
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
Feature: Command Line Processing
|
|
4
|
-
As a
|
|
4
|
+
As a holder of BTC I want to be able to use sibit
|
|
5
5
|
|
|
6
6
|
Scenario: Help can be printed
|
|
7
7
|
When I run bin/sibit with "--help"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
Feature: Command Line Processing
|
|
4
|
+
As a holder of BTC I want to use sibit in dry mode
|
|
5
|
+
|
|
6
|
+
Scenario: Bitcoin price can be retrieved
|
|
7
|
+
When I run bin/sibit with "price --dry --attempts=4"
|
|
8
|
+
Then Exit code is zero
|
|
9
|
+
|
|
10
|
+
Scenario: Bitcoin latest block hash can be retrieved
|
|
11
|
+
When I run bin/sibit with "latest --dry --api=blockchain"
|
|
12
|
+
Then Exit code is zero
|
|
13
|
+
|
|
14
|
+
Scenario: Bitcoin balance can be checked
|
|
15
|
+
When I run bin/sibit with "balance --dry 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose --api=blockchain,btc"
|
|
16
|
+
Then Exit code is zero
|
|
17
|
+
|
|
18
|
+
Scenario: Bitcoin fees can be printed
|
|
19
|
+
When I run bin/sibit with "fees --dry --verbose --api=fake"
|
|
20
|
+
Then Exit code is zero
|
|
21
|
+
|
|
22
|
+
Scenario: Bitcoin payment can be sent
|
|
23
|
+
When I run bin/sibit with "pay --dry --verbose --api=fake --proxy=localhost:3128 999999 XL- 46feba063e9b59a8ae0dba68abd39a3cb8f52089e776576d6eb1bb5bfec123d1 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f 1Fsyq5YGe8zbSjLS8YsDnZWM8U6AYMR6ZD"
|
|
24
|
+
Then Exit code is not zero
|
|
25
|
+
Then Stdout contains "UTXO arrived to 1JvCsJtLmCxEk7ddZFnVkGXpr9uhxZPmJi is incorrect"
|
data/lib/sibit/bitcoin/base58.rb
CHANGED
|
@@ -5,25 +5,26 @@
|
|
|
5
5
|
|
|
6
6
|
require 'digest'
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
# Pure Ruby implementation of Bitcoin functionality using OpenSSL 3.0+.
|
|
11
|
-
# Replaces the bitcoin-ruby dependency which is incompatible with OpenSSL 3.0.
|
|
12
|
-
module Sibit::Bitcoin
|
|
13
|
-
MIN_TX_FEE = 10_000
|
|
14
|
-
|
|
8
|
+
# Sibit main class.
|
|
9
|
+
class Sibit
|
|
15
10
|
# Base58 encoding for Bitcoin addresses.
|
|
16
11
|
#
|
|
12
|
+
# Encapsulates hex data and provides encoding/decoding functionality.
|
|
13
|
+
#
|
|
17
14
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
18
15
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
19
16
|
# License:: MIT
|
|
20
|
-
|
|
17
|
+
class Base58
|
|
21
18
|
ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
22
19
|
|
|
23
|
-
def
|
|
24
|
-
|
|
20
|
+
def initialize(data)
|
|
21
|
+
@data = data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def encode
|
|
25
|
+
bytes = [@data].pack('H*')
|
|
25
26
|
leading = bytes.match(/^\x00*/)[0].length
|
|
26
|
-
num =
|
|
27
|
+
num = @data.to_i(16)
|
|
27
28
|
result = ''
|
|
28
29
|
while num.positive?
|
|
29
30
|
num, remainder = num.divmod(58)
|
|
@@ -32,17 +33,17 @@ module Sibit::Bitcoin
|
|
|
32
33
|
('1' * leading) + result
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
def
|
|
36
|
-
leading =
|
|
36
|
+
def decode
|
|
37
|
+
leading = @data.match(/^1*/)[0].length
|
|
37
38
|
num = 0
|
|
38
|
-
|
|
39
|
+
@data.each_char { |c| num = (num * 58) + ALPHABET.index(c) }
|
|
39
40
|
hex = num.zero? ? '' : num.to_s(16)
|
|
40
41
|
hex = "0#{hex}" if hex.length.odd?
|
|
41
42
|
('00' * leading) + hex
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
def
|
|
45
|
-
Digest::SHA256.hexdigest(Digest::SHA256.digest([
|
|
45
|
+
def check
|
|
46
|
+
Digest::SHA256.hexdigest(Digest::SHA256.digest([@data].pack('H*')))[0...8]
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
end
|
data/lib/sibit/bitcoin/key.rb
CHANGED
|
@@ -7,7 +7,8 @@ require 'digest'
|
|
|
7
7
|
require 'openssl'
|
|
8
8
|
require_relative 'base58'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# Sibit main class.
|
|
11
|
+
class Sibit
|
|
11
12
|
# Bitcoin ECDSA key using secp256k1 curve.
|
|
12
13
|
#
|
|
13
14
|
# Supports OpenSSL 3.0+ by constructing keys via DER encoding instead
|
|
@@ -44,8 +45,8 @@ module Sibit::Bitcoin
|
|
|
44
45
|
def addr
|
|
45
46
|
hash = hash160(pub)
|
|
46
47
|
versioned = "00#{hash}"
|
|
47
|
-
checksum = Base58.
|
|
48
|
-
Base58.
|
|
48
|
+
checksum = Base58.new(versioned).check
|
|
49
|
+
Base58.new(versioned + checksum).encode
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def sign(data)
|
data/lib/sibit/bitcoin/script.rb
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
require 'digest'
|
|
7
7
|
require_relative 'base58'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# Sibit main class.
|
|
10
|
+
class Sibit
|
|
10
11
|
# Bitcoin Script parser.
|
|
11
12
|
#
|
|
12
13
|
# Parses standard P2PKH scripts to extract addresses.
|
|
@@ -49,8 +50,8 @@ module Sibit::Bitcoin
|
|
|
49
50
|
h = hash160
|
|
50
51
|
return nil unless h
|
|
51
52
|
versioned = "00#{h}"
|
|
52
|
-
checksum = Base58.
|
|
53
|
-
Base58.
|
|
53
|
+
checksum = Base58.new(versioned).check
|
|
54
|
+
Base58.new(versioned + checksum).encode
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
end
|
data/lib/sibit/bitcoin/tx.rb
CHANGED
|
@@ -8,7 +8,10 @@ require_relative 'base58'
|
|
|
8
8
|
require_relative 'key'
|
|
9
9
|
require_relative 'script'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Sibit main class.
|
|
12
|
+
class Sibit
|
|
13
|
+
MIN_TX_FEE = 10_000
|
|
14
|
+
|
|
12
15
|
# Bitcoin Transaction structure.
|
|
13
16
|
#
|
|
14
17
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
@@ -55,6 +58,62 @@ module Sibit::Bitcoin
|
|
|
55
58
|
@outputs
|
|
56
59
|
end
|
|
57
60
|
|
|
61
|
+
# Transaction input.
|
|
62
|
+
#
|
|
63
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
64
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
65
|
+
# License:: MIT
|
|
66
|
+
class Input
|
|
67
|
+
attr_reader :hash, :index, :prev_script, :key
|
|
68
|
+
attr_accessor :script_sig
|
|
69
|
+
|
|
70
|
+
def initialize(hash, index, script, key)
|
|
71
|
+
@hash = hash
|
|
72
|
+
@index = index
|
|
73
|
+
@prev_script = script
|
|
74
|
+
@key = key
|
|
75
|
+
@script_sig = ''
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def prev_out
|
|
79
|
+
[@hash].pack('H*')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def prev_out_index
|
|
83
|
+
@index
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Transaction output.
|
|
88
|
+
#
|
|
89
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
90
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
91
|
+
# License:: MIT
|
|
92
|
+
class Output
|
|
93
|
+
attr_reader :value
|
|
94
|
+
|
|
95
|
+
def initialize(value, address)
|
|
96
|
+
@value = value
|
|
97
|
+
@address = address
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def script
|
|
101
|
+
hash160 = address_to_hash160(@address)
|
|
102
|
+
[0x76, 0xa9, 0x14].pack('C*') + [hash160].pack('H*') + [0x88, 0xac].pack('C*')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def script_hex
|
|
106
|
+
script.unpack1('H*')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def address_to_hash160(addr)
|
|
112
|
+
decoded = Base58.new(addr).decode
|
|
113
|
+
decoded[2..41]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
58
117
|
private
|
|
59
118
|
|
|
60
119
|
def sign_inputs
|
|
@@ -159,52 +218,4 @@ module Sibit::Bitcoin
|
|
|
159
218
|
[0xff, num].pack('CQ<')
|
|
160
219
|
end
|
|
161
220
|
end
|
|
162
|
-
|
|
163
|
-
# Transaction input.
|
|
164
|
-
class Input
|
|
165
|
-
attr_reader :hash, :index, :prev_script, :key
|
|
166
|
-
attr_accessor :script_sig
|
|
167
|
-
|
|
168
|
-
def initialize(hash, index, script, key)
|
|
169
|
-
@hash = hash
|
|
170
|
-
@index = index
|
|
171
|
-
@prev_script = script
|
|
172
|
-
@key = key
|
|
173
|
-
@script_sig = ''
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def prev_out
|
|
177
|
-
[@hash].pack('H*')
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def prev_out_index
|
|
181
|
-
@index
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Transaction output.
|
|
186
|
-
class Output
|
|
187
|
-
attr_reader :value
|
|
188
|
-
|
|
189
|
-
def initialize(value, address)
|
|
190
|
-
@value = value
|
|
191
|
-
@address = address
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def script
|
|
195
|
-
hash160 = address_to_hash160(@address)
|
|
196
|
-
[0x76, 0xa9, 0x14].pack('C*') + [hash160].pack('H*') + [0x88, 0xac].pack('C*')
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def script_hex
|
|
200
|
-
script.unpack1('H*')
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
private
|
|
204
|
-
|
|
205
|
-
def address_to_hash160(addr)
|
|
206
|
-
decoded = Base58.decode(addr)
|
|
207
|
-
decoded[2..41]
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
221
|
end
|
|
@@ -6,54 +6,57 @@
|
|
|
6
6
|
require_relative 'key'
|
|
7
7
|
require_relative 'tx'
|
|
8
8
|
|
|
9
|
+
# Sibit main class.
|
|
9
10
|
class Sibit
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
11
|
+
# Bitcoin Transaction Builder.
|
|
12
|
+
#
|
|
13
|
+
# Provides a similar interface to Bitcoin::Builder::TxBuilder for
|
|
14
|
+
# building and signing Bitcoin transactions.
|
|
15
|
+
#
|
|
16
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
17
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
18
|
+
# License:: MIT
|
|
19
|
+
class TxBuilder
|
|
20
|
+
def initialize
|
|
21
|
+
@inputs = []
|
|
22
|
+
@outputs = []
|
|
23
|
+
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
def input
|
|
26
|
+
inp = Input.new
|
|
27
|
+
yield inp
|
|
28
|
+
@inputs << inp
|
|
29
|
+
end
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
def output(value, address)
|
|
32
|
+
@outputs << { value: value, address: address }
|
|
33
|
+
end
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
35
|
+
def tx(input_value:, leave_fee:, extra_fee:, change_address:)
|
|
36
|
+
txn = Tx.new
|
|
37
|
+
@inputs.each do |inp|
|
|
38
|
+
txn.add_input(
|
|
39
|
+
hash: inp.prev_out_hash,
|
|
40
|
+
index: inp.prev_out_idx,
|
|
41
|
+
script: inp.script,
|
|
42
|
+
key: inp.key
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
total_out = @outputs.sum { |o| o[:value] }
|
|
46
|
+
@outputs.each { |o| txn.add_output(o[:value], o[:address]) }
|
|
47
|
+
if leave_fee
|
|
48
|
+
change = input_value - total_out - extra_fee
|
|
49
|
+
txn.add_output(change, change_address) if change.positive?
|
|
50
|
+
end
|
|
51
|
+
Built.new(txn, @inputs, @outputs)
|
|
53
52
|
end
|
|
54
53
|
|
|
55
54
|
# Input builder for collecting input parameters.
|
|
56
|
-
|
|
55
|
+
#
|
|
56
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
57
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
58
|
+
# License:: MIT
|
|
59
|
+
class Input
|
|
57
60
|
attr_reader :prev_out_hash, :prev_out_idx, :script, :key
|
|
58
61
|
|
|
59
62
|
def prev_out(hash)
|
|
@@ -74,11 +77,15 @@ class Sibit
|
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
# Wrapper for built transaction with convenience methods.
|
|
77
|
-
|
|
80
|
+
#
|
|
81
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
82
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
83
|
+
# License:: MIT
|
|
84
|
+
class Built
|
|
78
85
|
def initialize(txn, inputs, outputs)
|
|
79
86
|
@tx = txn
|
|
80
|
-
@
|
|
81
|
-
@
|
|
87
|
+
@inputs = inputs
|
|
88
|
+
@outputs = outputs
|
|
82
89
|
end
|
|
83
90
|
|
|
84
91
|
def hash
|
|
@@ -102,18 +109,22 @@ class Sibit
|
|
|
102
109
|
end
|
|
103
110
|
|
|
104
111
|
def to_payload
|
|
105
|
-
|
|
112
|
+
Payload.new(@tx.payload)
|
|
106
113
|
end
|
|
107
|
-
end
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
# Wrapper for payload with hex conversion.
|
|
116
|
+
#
|
|
117
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
118
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
119
|
+
# License:: MIT
|
|
120
|
+
class Payload
|
|
121
|
+
def initialize(bytes)
|
|
122
|
+
@bytes = bytes
|
|
123
|
+
end
|
|
114
124
|
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
def bth
|
|
126
|
+
@bytes.unpack1('H*')
|
|
127
|
+
end
|
|
117
128
|
end
|
|
118
129
|
end
|
|
119
130
|
end
|
data/lib/sibit/fake.rb
CHANGED
|
@@ -32,7 +32,15 @@ class Sibit::Fake
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def utxos(_sources)
|
|
35
|
-
[
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
hash: '5de641d3867eb8fec3eb1a5ef2b44df39b54e0b3bb664ab520f2ae26a5b18ffc',
|
|
38
|
+
index: 0,
|
|
39
|
+
value: 100_000_000,
|
|
40
|
+
confirmations: 6,
|
|
41
|
+
script: '76a914c48a1737b35a9f9d9e3b624a910f1e22f7e80bbc88ac'
|
|
42
|
+
}
|
|
43
|
+
]
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
def push(_hex); end
|
data/lib/sibit/http.rb
CHANGED
|
@@ -5,30 +5,37 @@
|
|
|
5
5
|
|
|
6
6
|
require 'net/http'
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
# Sibit main class.
|
|
9
|
+
class Sibit
|
|
10
|
+
# HTTP interface.
|
|
11
|
+
#
|
|
12
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
13
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
14
|
+
# License:: MIT
|
|
15
|
+
class Http
|
|
16
|
+
def client(uri)
|
|
17
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
18
|
+
http.use_ssl = true
|
|
19
|
+
http.read_timeout = 240
|
|
20
|
+
http
|
|
21
|
+
end
|
|
19
22
|
end
|
|
20
|
-
end
|
|
21
23
|
|
|
22
|
-
# This HTTP client with proxy.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
# This HTTP client with proxy.
|
|
25
|
+
#
|
|
26
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
27
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
28
|
+
# License:: MIT
|
|
29
|
+
class HttpProxy
|
|
30
|
+
def initialize(addr)
|
|
31
|
+
@host, @port = addr.split(':')
|
|
32
|
+
end
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
def client(uri)
|
|
35
|
+
http = Net::HTTP.new(uri.host, uri.port, @host, @port.to_i)
|
|
36
|
+
http.use_ssl = true
|
|
37
|
+
http.read_timeout = 240
|
|
38
|
+
http
|
|
39
|
+
end
|
|
33
40
|
end
|
|
34
41
|
end
|
data/lib/sibit/version.rb
CHANGED
data/lib/sibit.rb
CHANGED
|
@@ -48,14 +48,14 @@ class Sibit
|
|
|
48
48
|
|
|
49
49
|
# Generates new Bitcoin private key and returns in Hash160 format.
|
|
50
50
|
def generate
|
|
51
|
-
key =
|
|
51
|
+
key = Key.generate.priv
|
|
52
52
|
@log.info("Bitcoin private key generated: #{key[0..8]}...")
|
|
53
53
|
key
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Creates Bitcoin address using the private key in Hash160 format.
|
|
57
57
|
def create(pvt)
|
|
58
|
-
|
|
58
|
+
Key.new(pvt).addr
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Gets the balance of the address, in satoshi.
|
|
@@ -100,9 +100,9 @@ class Sibit
|
|
|
100
100
|
# +change+: the address where the change has to be sent to
|
|
101
101
|
def pay(amount, fee, sources, target, change, skip_utxo: [])
|
|
102
102
|
p = price('USD')
|
|
103
|
-
sources = sources.map { |k| [
|
|
103
|
+
sources = sources.map { |k| [Key.new(k).addr, k] }.to_h
|
|
104
104
|
satoshi = satoshi(amount)
|
|
105
|
-
builder =
|
|
105
|
+
builder = TxBuilder.new
|
|
106
106
|
unspent = 0
|
|
107
107
|
size = 100
|
|
108
108
|
utxos = @api.utxos(sources.keys)
|
|
@@ -118,8 +118,10 @@ class Sibit
|
|
|
118
118
|
i.prev_out(utxo[:hash])
|
|
119
119
|
i.prev_out_index(utxo[:index])
|
|
120
120
|
i.prev_out_script = script_hex(utxo[:script])
|
|
121
|
-
address =
|
|
122
|
-
|
|
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))
|
|
123
125
|
end
|
|
124
126
|
size += 180
|
|
125
127
|
@log.info(
|
|
@@ -138,7 +140,7 @@ class Sibit
|
|
|
138
140
|
tx = builder.tx(
|
|
139
141
|
input_value: unspent,
|
|
140
142
|
leave_fee: true,
|
|
141
|
-
extra_fee: [f,
|
|
143
|
+
extra_fee: [f, MIN_TX_FEE].max,
|
|
142
144
|
change_address: change
|
|
143
145
|
)
|
|
144
146
|
left = unspent - tx.outputs.sum(&:value)
|
|
@@ -147,7 +149,7 @@ class Sibit
|
|
|
147
149
|
#{tx.inputs.map { |i| " in: #{i.prev_out.unpack1('H*')}:#{i.prev_out_index}" }.join("\n ")}
|
|
148
150
|
#{tx.out.count} output#{'s' if tx.out.count > 1}:
|
|
149
151
|
#{tx.outputs.map { |o| "out: #{o.script_hex} / #{num(o.value, p)}" }.join("\n ")}
|
|
150
|
-
Min tx fee: #{num(
|
|
152
|
+
Min tx fee: #{num(MIN_TX_FEE, p)}
|
|
151
153
|
Fee requested: #{num(f, p)} as \"#{fee}\"
|
|
152
154
|
Fee actually paid: #{num(left, p)}
|
|
153
155
|
Tx size: #{size} bytes
|
|
@@ -272,7 +274,7 @@ in block #{block} (by #{json[:provider]})")
|
|
|
272
274
|
|
|
273
275
|
# Make key from private key string in Hash160.
|
|
274
276
|
def key(hash160)
|
|
275
|
-
|
|
277
|
+
Key.new(hash160)
|
|
276
278
|
end
|
|
277
279
|
|
|
278
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.29.
|
|
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
|