mini-wallet 0.1.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 +7 -0
- data/README.md +127 -0
- data/bin/mini_wallet +12 -0
- data/lib/blockstream/client.rb +42 -0
- data/lib/mempool/client.rb +22 -0
- data/lib/mini_wallet/balance/fetch.rb +25 -0
- data/lib/mini_wallet/cli.rb +9 -0
- data/lib/mini_wallet/commands/balance.rb +20 -0
- data/lib/mini_wallet/commands/base.rb +32 -0
- data/lib/mini_wallet/commands/init.rb +21 -0
- data/lib/mini_wallet/commands/send.rb +39 -0
- data/lib/mini_wallet/key_manager.rb +42 -0
- data/lib/mini_wallet/tx/build.rb +147 -0
- data/lib/mini_wallet/version.rb +3 -0
- data/lib/mini_wallet.rb +6 -0
- metadata +195 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5363b3d1d2d3dd287d1ab89d3e8ac183fca8280570498f599ff78baede7b8aa5
|
4
|
+
data.tar.gz: 8b4e707db9b3448700536cb60d02ebfca86eee76ad4aa716fe0c0283958c6ca2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e0880a384d54de15753097b62d3a7929a297c3e66082615fac1345621c96974e0312073b16c2225e9a655c075bdebae62b00df82a6686920503522a4724fe4c
|
7
|
+
data.tar.gz: bd02c9181483b9f46ea7cada3bc4c036792a43508a383a39712ae4b26b1d05319c5f46eaf036e2fbcfb472a3486cfb7c2d26a14e3c083be6f3016451edf850ad
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
# Минималистичный Bitcoin-кошелёк (Signet)
|
3
|
+
|
4
|
+
---
|
5
|
+
|
6
|
+
## Общее описание
|
7
|
+
|
8
|
+
- Генерация и сохранение приватного ключа в `.wallet/signet_private_key`
|
9
|
+
- Показ баланса на адресе
|
10
|
+
- Отправка BTC на адрес Signet
|
11
|
+
- Динамические комиссии за транзакции
|
12
|
+
|
13
|
+
---
|
14
|
+
|
15
|
+
## Запуск
|
16
|
+
|
17
|
+
### 1. Сборка
|
18
|
+
|
19
|
+
**Docker:**
|
20
|
+
|
21
|
+
```bash
|
22
|
+
docker compose build
|
23
|
+
```
|
24
|
+
|
25
|
+
**Makefile:**
|
26
|
+
|
27
|
+
```bash
|
28
|
+
make build
|
29
|
+
```
|
30
|
+
|
31
|
+
---
|
32
|
+
|
33
|
+
### 2. Инициализация кошелька
|
34
|
+
|
35
|
+
**Docker:**
|
36
|
+
|
37
|
+
```bash
|
38
|
+
docker compose run --rm mini_wallet init
|
39
|
+
```
|
40
|
+
|
41
|
+
**Makefile:**
|
42
|
+
|
43
|
+
```bash
|
44
|
+
make init
|
45
|
+
```
|
46
|
+
|
47
|
+
> Выведет адрес, на который можно получaть Signet-BTC.
|
48
|
+
|
49
|
+
При тестировании кошелька тестовые суммы запрашивались с адреса:
|
50
|
+
|
51
|
+
- [https://signetfaucet.bublina.eu.org/](https://signetfaucet.bublina.eu.org/)
|
52
|
+
|
53
|
+
---
|
54
|
+
|
55
|
+
### 3. Проверка баланса
|
56
|
+
|
57
|
+
**Docker:**
|
58
|
+
|
59
|
+
```bash
|
60
|
+
docker compose run --rm mini_wallet balance
|
61
|
+
```
|
62
|
+
|
63
|
+
**Makefile:**
|
64
|
+
|
65
|
+
```bash
|
66
|
+
make balance
|
67
|
+
```
|
68
|
+
|
69
|
+
---
|
70
|
+
|
71
|
+
### 4. Отправка средств
|
72
|
+
|
73
|
+
**Docker:**
|
74
|
+
|
75
|
+
```bash
|
76
|
+
docker compose run --rm mini_wallet send <адрес> <сумма_в_BTC>
|
77
|
+
```
|
78
|
+
|
79
|
+
**Makefile:**
|
80
|
+
|
81
|
+
```bash
|
82
|
+
make send TO=<адрес> AMOUNT=<сумма>
|
83
|
+
```
|
84
|
+
|
85
|
+
Пример:
|
86
|
+
|
87
|
+
```bash
|
88
|
+
make send TO=tb1q... AMOUNT=0.00002
|
89
|
+
```
|
90
|
+
|
91
|
+
---
|
92
|
+
|
93
|
+
## Тестирование
|
94
|
+
|
95
|
+
### RSpec
|
96
|
+
|
97
|
+
**Docker:**
|
98
|
+
|
99
|
+
```bash
|
100
|
+
docker compose run --rm --entrypoint rspec mini_wallet
|
101
|
+
```
|
102
|
+
|
103
|
+
**Makefile:**
|
104
|
+
|
105
|
+
```bash
|
106
|
+
make test
|
107
|
+
```
|
108
|
+
|
109
|
+
### Проверка команд с помощью bash-скрипта (опционально)
|
110
|
+
|
111
|
+
Данный пункт требует наличия баланса на адресе созданного (командoй `make init`) кошелька.
|
112
|
+
|
113
|
+
Перед началом выполните команду:
|
114
|
+
|
115
|
+
```bash
|
116
|
+
cp .env.example .env
|
117
|
+
```
|
118
|
+
|
119
|
+
Далее укажите в файле `.env` значение вашего текущего адреса кошелька в переменной `SENDER_ADDR`.
|
120
|
+
Узнать адрес своего кошелька можно командой `make balance`.
|
121
|
+
Также нужно указать signet-адрес куда будут отправляться средства - `RECIPIENT_ADDR`.
|
122
|
+
|
123
|
+
После можно запустить скрипт проверки работоспособности команд кошелька.
|
124
|
+
|
125
|
+
```bash
|
126
|
+
make
|
127
|
+
```
|
data/bin/mini_wallet
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'dry/cli'
|
3
|
+
require 'zeitwerk'
|
4
|
+
require 'bitcoin'
|
5
|
+
require 'colorize'
|
6
|
+
require 'whirly'
|
7
|
+
|
8
|
+
loader = Zeitwerk::Loader.new
|
9
|
+
loader.push_dir(File.expand_path('../lib', __dir__))
|
10
|
+
loader.setup
|
11
|
+
|
12
|
+
Dry::CLI.new(MiniWallet::Cli).call
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Blockstream
|
4
|
+
class Client
|
5
|
+
BASE_URL = 'https://blockstream.info/signet/api/'.freeze
|
6
|
+
|
7
|
+
def fetch_address_info(address)
|
8
|
+
response = conn.get("address/#{address}")
|
9
|
+
handle_response(response) { |body| JSON.parse(body) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch_transaction_hex(txid)
|
13
|
+
response = conn.get("tx/#{txid}/hex")
|
14
|
+
handle_response(response) { |body| body }
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch_utxos(address)
|
18
|
+
response = conn.get("address/#{address}/utxo")
|
19
|
+
handle_response(response) { |body| JSON.parse(body) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_fee_rate
|
23
|
+
response = conn.get('fee-estimates')
|
24
|
+
body = handle_response(response) { |body| JSON.parse(body) }
|
25
|
+
rate = body['1']
|
26
|
+
|
27
|
+
[rate.to_i, 1].max
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def conn
|
33
|
+
@conn ||= Faraday.new(url: BASE_URL)
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle_response(response)
|
37
|
+
raise "HTTP error #{response.status}: #{response.body}" unless response.success?
|
38
|
+
|
39
|
+
yield(response.body)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Mempool
|
4
|
+
class Client
|
5
|
+
BASE_URL = 'https://mempool.space/signet/api/'.freeze
|
6
|
+
|
7
|
+
def broadcast_transaction(tx_hex)
|
8
|
+
response = conn.post('tx') do |req|
|
9
|
+
req.headers['Content-Type'] = 'text/plain'
|
10
|
+
req.body = tx_hex
|
11
|
+
end
|
12
|
+
|
13
|
+
response.body
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def conn
|
19
|
+
@conn ||= Faraday.new(url: BASE_URL)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'dry/initializer'
|
2
|
+
require 'bigdecimal'
|
3
|
+
|
4
|
+
module MiniWallet
|
5
|
+
module Balance
|
6
|
+
class Fetch
|
7
|
+
extend Dry::Initializer
|
8
|
+
|
9
|
+
option :client
|
10
|
+
option :address
|
11
|
+
|
12
|
+
def call
|
13
|
+
response = client.fetch_address_info(address)
|
14
|
+
|
15
|
+
funded = response.dig('chain_stats', 'funded_txo_sum') || 0
|
16
|
+
spent = response.dig('chain_stats', 'spent_txo_sum') || 0
|
17
|
+
|
18
|
+
(BigDecimal(funded - spent) / 100_000_000).to_f
|
19
|
+
rescue Faraday::Error, JSON::ParserError => e
|
20
|
+
puts "Error fetching balance: #{e.message}"
|
21
|
+
0.0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MiniWallet
|
2
|
+
module Commands
|
3
|
+
class Balance < Base
|
4
|
+
desc 'Show BTC balance for wallet'
|
5
|
+
|
6
|
+
def call(*)
|
7
|
+
key = ensure_wallet_exists
|
8
|
+
address = key.to_p2wpkh
|
9
|
+
|
10
|
+
with_spinner do
|
11
|
+
client = Blockstream::Client.new
|
12
|
+
balance_value = MiniWallet::Balance::Fetch.new(address:, client:).call
|
13
|
+
formatted_balance = format('%.8f', balance_value)
|
14
|
+
balance = "#{formatted_balance} BTC".green.bold
|
15
|
+
puts "Balance for #{address}: #{balance}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'whirly'
|
3
|
+
|
4
|
+
module MiniWallet
|
5
|
+
module Commands
|
6
|
+
class Base < Dry::CLI::Command
|
7
|
+
private
|
8
|
+
|
9
|
+
def ensure_wallet_exists
|
10
|
+
MiniWallet::KeyManager.load_key
|
11
|
+
rescue MiniWallet::KeyManager::WalletNotFoundError
|
12
|
+
puts 'Wallet not found.'.yellow
|
13
|
+
print 'Would you like to create a new wallet now? (y/n): '
|
14
|
+
response = STDIN.gets&.strip&.downcase
|
15
|
+
|
16
|
+
raise Dry::CLI::Error, 'Wallet not initialized. Run `mini_wallet init`.' unless %w[y
|
17
|
+
yes].include?(response)
|
18
|
+
|
19
|
+
MiniWallet::Commands::Init.new.call
|
20
|
+
MiniWallet::KeyManager.load_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_spinner(status: nil, &block)
|
24
|
+
if ENV['TEST'] == 'true'
|
25
|
+
block.call
|
26
|
+
else
|
27
|
+
Whirly.start(spinner: 'dots7', status:) { block.call }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module MiniWallet
|
2
|
+
module Commands
|
3
|
+
class Init < Base
|
4
|
+
desc 'Generates a new Signet wallet (private key + address)'
|
5
|
+
|
6
|
+
def call(*)
|
7
|
+
if MiniWallet::KeyManager.key_file_exists?
|
8
|
+
puts "Wallet already exists at: #{MiniWallet::KeyManager::KEY_FILE}".yellow
|
9
|
+
address = MiniWallet::KeyManager.load_key.to_addr
|
10
|
+
puts "Address: #{address}"
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
key = MiniWallet::KeyManager.generate_key
|
15
|
+
puts 'New wallet created.'.green.bold
|
16
|
+
puts "Address: #{key.to_addr}".bold
|
17
|
+
puts "Private key saved to: #{MiniWallet::KeyManager::KEY_FILE}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MiniWallet
|
2
|
+
module Commands
|
3
|
+
class Send < Base
|
4
|
+
desc 'Send amount (BTC) to address on Signet'
|
5
|
+
|
6
|
+
argument :to_address, required: true, desc: 'Recipient Bitcoin address'
|
7
|
+
argument :send_amount_btc, required: true, desc: 'Amount BTC to send'
|
8
|
+
|
9
|
+
def call(to_address:, send_amount_btc:)
|
10
|
+
unless Bitcoin.valid_address?(to_address)
|
11
|
+
puts 'Invalid Bitcoin address'.red
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
key = ensure_wallet_exists
|
16
|
+
|
17
|
+
with_spinner do
|
18
|
+
result = MiniWallet::Tx::Build.new(
|
19
|
+
to_address:,
|
20
|
+
send_amount_btc:,
|
21
|
+
key:,
|
22
|
+
client: Blockstream::Client.new
|
23
|
+
).call
|
24
|
+
|
25
|
+
if result.failure?
|
26
|
+
puts result.failure.red
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
tx_hex = result.value!
|
31
|
+
tx_id = Mempool::Client.new.broadcast_transaction(tx_hex)
|
32
|
+
|
33
|
+
puts 'Transaction broadcasted successfully.'
|
34
|
+
puts "Transaction id: #{tx_id.green.bold}..."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module MiniWallet
|
4
|
+
class KeyManager
|
5
|
+
KEY_FILE = ENV.fetch('WALLET_KEY_FILE', '.wallet/priv_key.wif').freeze
|
6
|
+
KEY_TYPE = Bitcoin::Key::TYPES[:p2wpkh]
|
7
|
+
|
8
|
+
class WalletNotFoundError < StandardError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def generate_key
|
12
|
+
key = Bitcoin::Key.generate(KEY_TYPE)
|
13
|
+
save_key(key.to_wif)
|
14
|
+
key
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_key
|
18
|
+
raise WalletNotFoundError, 'Init new wallet first.' unless key_file_exists?
|
19
|
+
|
20
|
+
load_key_from_file
|
21
|
+
end
|
22
|
+
|
23
|
+
def key_file_exists?
|
24
|
+
File.exist?(KEY_FILE)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load_key_from_file
|
30
|
+
file = File.read(KEY_FILE).strip
|
31
|
+
Bitcoin::Key.from_wif(file)
|
32
|
+
end
|
33
|
+
|
34
|
+
def save_key(priv_key)
|
35
|
+
dir = File.dirname(KEY_FILE)
|
36
|
+
FileUtils.mkdir_p(dir, mode: 0o700) unless Dir.exist?(dir)
|
37
|
+
File.write(KEY_FILE, priv_key)
|
38
|
+
File.chmod(0o600, KEY_FILE)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'dry/operation'
|
2
|
+
require 'dry/initializer'
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
# Builds and signs Bitcoin transactions using UTXOs from Signet.
|
6
|
+
# Handles fee estimation, change output, and SegWit signing.
|
7
|
+
module MiniWallet
|
8
|
+
module Tx
|
9
|
+
class Build < Dry::Operation
|
10
|
+
extend Dry::Initializer
|
11
|
+
|
12
|
+
MIN_FEE_SAT = 150
|
13
|
+
|
14
|
+
option :to_address
|
15
|
+
option :send_amount_btc
|
16
|
+
option :key
|
17
|
+
option :client
|
18
|
+
|
19
|
+
def call # rubocop:disable Metrics/AbcSize
|
20
|
+
send_amount_sat = step parse_amount_btc(send_amount_btc)
|
21
|
+
fee_rate = step fetch_fee_rate
|
22
|
+
utxos = step fetch_utxos(key.to_p2wpkh)
|
23
|
+
probe_tx = build_probe_tx_raw(utxos:, to_address:, send_amount_sat:, key:)
|
24
|
+
fee_satoshi = calculate_fee(fee_rate:, vsize: probe_tx.vsize)
|
25
|
+
total_value = step check_balance(utxos:, send_amount_sat:, fee_satoshi:)
|
26
|
+
tx = build_final_tx(utxos:, to_address:, send_amount_sat:, total_value:, fee_satoshi:, key:)
|
27
|
+
signed_tx = step sign_tx_inputs(tx:, utxos:, key:)
|
28
|
+
|
29
|
+
signed_tx.to_hex
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parse_amount_btc(input)
|
35
|
+
amount_sat = (BigDecimal(input) * 100_000_000).to_i
|
36
|
+
return Failure('Amount must be positive') if amount_sat <= 0
|
37
|
+
|
38
|
+
Success(amount_sat)
|
39
|
+
rescue ArgumentError
|
40
|
+
Failure('Invalid BTC amount format')
|
41
|
+
end
|
42
|
+
|
43
|
+
def fetch_fee_rate
|
44
|
+
rate = client.fetch_fee_rate
|
45
|
+
|
46
|
+
if rate.is_a?(Numeric) && rate.positive?
|
47
|
+
Success(rate.to_i)
|
48
|
+
else
|
49
|
+
Success(1)
|
50
|
+
end
|
51
|
+
rescue StandardError => e
|
52
|
+
warn "Failed to fetch fee rate: #{e.message}. Using fallback rate 1 sat/vB."
|
53
|
+
Success(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_utxos(address)
|
57
|
+
utxos = client.fetch_utxos(address)
|
58
|
+
return Failure('No UTXOs available') if utxos.empty?
|
59
|
+
|
60
|
+
Success(utxos)
|
61
|
+
rescue StandardError => e
|
62
|
+
Failure("Failed to fetch UTXOs: #{e.message}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def calculate_fee(fee_rate:, vsize:)
|
66
|
+
dynamic = fee_rate * vsize
|
67
|
+
[dynamic, MIN_FEE_SAT].max
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_probe_tx_raw(utxos:, to_address:, send_amount_sat:, key:)
|
71
|
+
tx = Bitcoin::Tx.new
|
72
|
+
tx = add_inputs_raw(tx:, utxos:)
|
73
|
+
tx = add_output_raw(tx:, to_address:, value: send_amount_sat)
|
74
|
+
|
75
|
+
change_script = Bitcoin::Script.parse_from_addr(key.to_p2wpkh)
|
76
|
+
tx.out << Bitcoin::TxOut.new(value: 1, script_pubkey: change_script)
|
77
|
+
tx
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_balance(utxos:, send_amount_sat:, fee_satoshi:)
|
81
|
+
total = utxos.sum { _1['value'] }
|
82
|
+
required = send_amount_sat + fee_satoshi
|
83
|
+
|
84
|
+
if total >= required
|
85
|
+
Success(total)
|
86
|
+
else
|
87
|
+
balance_btc = BigDecimal(total) / 100_000_000
|
88
|
+
required_btc = BigDecimal(required) / 100_000_000
|
89
|
+
Failure(
|
90
|
+
"Insufficient funds: balance #{'%.8f' % balance_btc} BTC, " \
|
91
|
+
"required #{'%.8f' % required_btc} BTC (fee: #{fee_satoshi} sat)"
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_final_tx(utxos:, to_address:, send_amount_sat:, total_value:, fee_satoshi:, key:)
|
97
|
+
tx = Bitcoin::Tx.new
|
98
|
+
add_inputs_raw(tx:, utxos: utxos)
|
99
|
+
add_output_raw(tx:, to_address:, value: send_amount_sat)
|
100
|
+
|
101
|
+
change_sat = total_value - (send_amount_sat + fee_satoshi)
|
102
|
+
add_output_raw(tx:, to_address: key.to_p2wpkh, value: change_sat) if change_sat.positive?
|
103
|
+
|
104
|
+
tx
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_inputs_raw(tx:, utxos:)
|
108
|
+
utxos.each do |utxo|
|
109
|
+
out_point = Bitcoin::OutPoint.from_txid(utxo['txid'], utxo['vout'])
|
110
|
+
tx.in << Bitcoin::TxIn.new(out_point: out_point)
|
111
|
+
end
|
112
|
+
tx
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_output_raw(tx:, to_address:, value:)
|
116
|
+
script = Bitcoin::Script.parse_from_addr(to_address)
|
117
|
+
tx.out << Bitcoin::TxOut.new(value:, script_pubkey: script)
|
118
|
+
tx
|
119
|
+
end
|
120
|
+
|
121
|
+
def fetch_prev_script_pub_key(txid:, vout:)
|
122
|
+
tx_hex = client.fetch_transaction_hex(txid)
|
123
|
+
script = Bitcoin::Tx
|
124
|
+
.parse_from_payload([tx_hex].pack('H*'))
|
125
|
+
.out[vout]
|
126
|
+
.script_pubkey
|
127
|
+
Success(script)
|
128
|
+
end
|
129
|
+
|
130
|
+
def sign_tx_inputs(tx:, utxos:, key:)
|
131
|
+
utxos.each_with_index do |utxo, index|
|
132
|
+
script_pubkey = step fetch_prev_script_pub_key(txid: utxo['txid'], vout: utxo['vout'])
|
133
|
+
|
134
|
+
sig_hash = tx.sighash_for_input(index, script_pubkey, sig_version: :witness_v0, amount: utxo['value'])
|
135
|
+
signature = key.sign(sig_hash) + [Bitcoin::SIGHASH_TYPE[:all]].pack('C')
|
136
|
+
|
137
|
+
tx.in[index].script_witness.stack.concat([signature, key.pubkey.htb])
|
138
|
+
|
139
|
+
unless tx.verify_input_sig(index, script_pubkey, amount: utxo['value'])
|
140
|
+
return Failure("ScriptPubkey verification failed for txid #{utxo['txid']}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
Success(tx)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/mini_wallet.rb
ADDED
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mini-wallet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ilmir
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: bitcoin-ruby
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: colorize
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - '='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.1.0
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: dry-cli
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.3.0
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.3.0
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: dry-initializer
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 3.2.0
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 3.2.0
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: dry-operation
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 1.0.0
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.0.0
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: faraday
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '2.0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: whirly
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.3.0
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 0.3.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: zeitwerk
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '2.0'
|
117
|
+
type: :runtime
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '2.0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: rspec
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '3.13'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '3.13'
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: webmock
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 3.25.1
|
145
|
+
type: :development
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 3.25.1
|
152
|
+
description: Generate keys, check balance, and send transactions on Bitcoin Signet.
|
153
|
+
email:
|
154
|
+
- code.for.func@gmail.com
|
155
|
+
executables:
|
156
|
+
- mini_wallet
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- README.md
|
161
|
+
- bin/mini_wallet
|
162
|
+
- lib/blockstream/client.rb
|
163
|
+
- lib/mempool/client.rb
|
164
|
+
- lib/mini_wallet.rb
|
165
|
+
- lib/mini_wallet/balance/fetch.rb
|
166
|
+
- lib/mini_wallet/cli.rb
|
167
|
+
- lib/mini_wallet/commands/balance.rb
|
168
|
+
- lib/mini_wallet/commands/base.rb
|
169
|
+
- lib/mini_wallet/commands/init.rb
|
170
|
+
- lib/mini_wallet/commands/send.rb
|
171
|
+
- lib/mini_wallet/key_manager.rb
|
172
|
+
- lib/mini_wallet/tx/build.rb
|
173
|
+
- lib/mini_wallet/version.rb
|
174
|
+
homepage: https://gitlab.com/i-karimov/mini_wallet
|
175
|
+
licenses:
|
176
|
+
- MIT
|
177
|
+
metadata: {}
|
178
|
+
rdoc_options: []
|
179
|
+
require_paths:
|
180
|
+
- lib
|
181
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: 2.7.0
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
requirements: []
|
192
|
+
rubygems_version: 3.7.2
|
193
|
+
specification_version: 4
|
194
|
+
summary: A minimal Bitcoin wallet CLI for Testnet
|
195
|
+
test_files: []
|