mixin_bot 0.1.4 → 0.3.4
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/mixinbot +6 -0
- data/lib/mixin_bot.rb +9 -3
- data/lib/mixin_bot/api.rb +43 -5
- data/lib/mixin_bot/api/app.rb +31 -0
- data/lib/mixin_bot/api/auth.rb +13 -3
- data/lib/mixin_bot/api/blaze.rb +2 -2
- data/lib/mixin_bot/api/conversation.rb +2 -2
- data/lib/mixin_bot/api/me.rb +4 -4
- data/lib/mixin_bot/api/message.rb +29 -17
- data/lib/mixin_bot/api/multisig.rb +335 -0
- data/lib/mixin_bot/api/pin.rb +24 -9
- data/lib/mixin_bot/api/snapshot.rb +10 -4
- data/lib/mixin_bot/api/transfer.rb +4 -3
- data/lib/mixin_bot/api/user.rb +20 -6
- data/lib/mixin_bot/api/withdraw.rb +8 -8
- data/lib/mixin_bot/cli.rb +128 -0
- data/lib/mixin_bot/cli/me.rb +40 -0
- data/lib/mixin_bot/cli/multisig.rb +11 -0
- data/lib/mixin_bot/cli/node.rb +107 -0
- data/lib/mixin_bot/client.rb +10 -10
- data/lib/mixin_bot/version.rb +1 -1
- metadata +91 -13
data/lib/mixin_bot/api/pin.rb
CHANGED
@@ -15,13 +15,14 @@ module MixinBot
|
|
15
15
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
16
16
|
end
|
17
17
|
|
18
|
-
# TODO:
|
19
18
|
# https://developers.mixin.one/api/alpha-mixin-network/create-pin/
|
20
|
-
def update_pin(old_pin:,
|
19
|
+
def update_pin(old_pin:, pin:)
|
21
20
|
path = '/pin/update'
|
21
|
+
encrypted_old_pin = old_pin.nil? ? '' : encrypt_pin(old_pin, iterator: Time.now.utc.to_i)
|
22
|
+
encrypted_pin = encrypt_pin(pin, iterator: Time.now.utc.to_i + 1)
|
22
23
|
payload = {
|
23
|
-
old_pin:
|
24
|
-
pin:
|
24
|
+
old_pin: encrypted_old_pin,
|
25
|
+
pin: encrypted_pin
|
25
26
|
}
|
26
27
|
|
27
28
|
access_token = access_token('POST', path, payload.to_json)
|
@@ -34,12 +35,11 @@ module MixinBot
|
|
34
35
|
msg = Base64.strict_decode64 msg
|
35
36
|
iv = msg[0..15]
|
36
37
|
cipher = msg[16..47]
|
37
|
-
aes_key = JOSE::JWA::PKCS1.rsaes_oaep_decrypt('SHA256', pin_token, private_key, session_id)
|
38
38
|
alg = 'AES-256-CBC'
|
39
39
|
decode_cipher = OpenSSL::Cipher.new(alg)
|
40
40
|
decode_cipher.decrypt
|
41
41
|
decode_cipher.iv = iv
|
42
|
-
decode_cipher.key =
|
42
|
+
decode_cipher.key = _generate_aes_key
|
43
43
|
decoded = decode_cipher.update(cipher)
|
44
44
|
decoded[0..5]
|
45
45
|
end
|
@@ -47,13 +47,12 @@ module MixinBot
|
|
47
47
|
# https://developers.mixin.one/api/alpha-mixin-network/encrypted-pin/
|
48
48
|
# use timestamp(timestamp) for iterator as default: must be bigger than the previous, the first time must be greater than 0. After a new session created, it will be reset to 0.
|
49
49
|
def encrypt_pin(pin_code, iterator: nil)
|
50
|
-
aes_key = JOSE::JWA::PKCS1.rsaes_oaep_decrypt('SHA256', pin_token, private_key, session_id)
|
51
50
|
iterator ||= Time.now.utc.to_i
|
52
51
|
tszero = iterator % 0x100
|
53
52
|
tsone = (iterator % 0x10000) >> 8
|
54
53
|
tstwo = (iterator % 0x1000000) >> 16
|
55
54
|
tsthree = (iterator % 0x100000000) >> 24
|
56
|
-
tsstring = tszero.chr
|
55
|
+
tsstring = "#{tszero.chr}#{tsone.chr}#{tstwo.chr}#{tsthree.chr}\u0000\u0000\u0000\u0000"
|
57
56
|
encrypt_content = pin_code + tsstring + tsstring
|
58
57
|
pad_count = 16 - encrypt_content.length % 16
|
59
58
|
padded_content =
|
@@ -67,12 +66,28 @@ module MixinBot
|
|
67
66
|
aes = OpenSSL::Cipher.new(alg)
|
68
67
|
iv = OpenSSL::Cipher.new(alg).random_iv
|
69
68
|
aes.encrypt
|
70
|
-
aes.key =
|
69
|
+
aes.key = _generate_aes_key
|
71
70
|
aes.iv = iv
|
72
71
|
cipher = aes.update(padded_content)
|
73
72
|
msg = iv + cipher
|
74
73
|
Base64.strict_encode64 msg
|
75
74
|
end
|
76
75
|
end
|
76
|
+
|
77
|
+
def _generate_aes_key
|
78
|
+
if pin_token.size == 32
|
79
|
+
JOSE::JWA::X25519.x25519(
|
80
|
+
JOSE::JWA::Ed25519.secret_to_curve25519(private_key[0..31]),
|
81
|
+
pin_token
|
82
|
+
)
|
83
|
+
else
|
84
|
+
JOSE::JWA::PKCS1.rsaes_oaep_decrypt(
|
85
|
+
'SHA256',
|
86
|
+
pin_token,
|
87
|
+
OpenSSL::PKey::RSA.new(private_key),
|
88
|
+
session_id
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
77
92
|
end
|
78
93
|
end
|
@@ -11,7 +11,10 @@ module MixinBot
|
|
11
11
|
asset: options[:asset],
|
12
12
|
order: options[:order]
|
13
13
|
)
|
14
|
-
|
14
|
+
|
15
|
+
access_token = options[:access_token] || access_token('GET', path)
|
16
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
17
|
+
client.get(path, headers: { 'Authorization': authorization })
|
15
18
|
end
|
16
19
|
|
17
20
|
def read_snapshots(options = {})
|
@@ -22,14 +25,17 @@ module MixinBot
|
|
22
25
|
asset: options[:asset]
|
23
26
|
)
|
24
27
|
|
25
|
-
access_token = access_token('GET', path)
|
28
|
+
access_token = options[:access_token] || access_token('GET', path)
|
26
29
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
27
30
|
client.get(path, headers: { 'Authorization': authorization })
|
28
31
|
end
|
29
32
|
|
30
|
-
def read_network_snapshot(snapshot_id)
|
33
|
+
def read_network_snapshot(snapshot_id, options = {})
|
31
34
|
path = format('/network/snapshots/%<snapshot_id>s', snapshot_id: snapshot_id)
|
32
|
-
|
35
|
+
|
36
|
+
access_token = options[:access_token] || access_token('GET', path)
|
37
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
38
|
+
client.get(path, headers: { 'Authorization': authorization })
|
33
39
|
end
|
34
40
|
end
|
35
41
|
end
|
@@ -3,18 +3,19 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Transfer
|
6
|
-
def create_transfer(pin, options)
|
6
|
+
def create_transfer(pin, options, access_token: nil)
|
7
7
|
asset_id = options[:asset_id]
|
8
8
|
opponent_id = options[:opponent_id]
|
9
9
|
amount = options[:amount]
|
10
10
|
memo = options[:memo]
|
11
11
|
trace_id = options[:trace_id] || SecureRandom.uuid
|
12
|
+
encrypted_pin = options[:encrypted_pin] || encrypt_pin(pin)
|
12
13
|
|
13
14
|
path = '/transfers'
|
14
15
|
payload = {
|
15
16
|
asset_id: asset_id,
|
16
17
|
opponent_id: opponent_id,
|
17
|
-
pin:
|
18
|
+
pin: encrypted_pin,
|
18
19
|
amount: amount.to_s,
|
19
20
|
trace_id: trace_id,
|
20
21
|
memo: memo
|
@@ -25,7 +26,7 @@ module MixinBot
|
|
25
26
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
26
27
|
end
|
27
28
|
|
28
|
-
def read_transfer(trace_id)
|
29
|
+
def read_transfer(trace_id, access_token: nil)
|
29
30
|
path = format('/transfers/trace/%<trace_id>s', trace_id: trace_id)
|
30
31
|
access_token ||= access_token('GET', path, '')
|
31
32
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
data/lib/mixin_bot/api/user.rb
CHANGED
@@ -14,21 +14,27 @@ module MixinBot
|
|
14
14
|
|
15
15
|
# https://developers.mixin.one/api/alpha-mixin-network/app-user/
|
16
16
|
# Create a new Mixin Network user (like a normal Mixin Messenger user). You should keep PrivateKey which is used to sign an AuthenticationToken and encrypted PIN for the user.
|
17
|
-
def create_user(full_name, rsa_key
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def create_user(full_name, key_type: 'RSA', rsa_key: nil, ed25519_key: nil)
|
18
|
+
case key_type
|
19
|
+
when 'RSA'
|
20
|
+
rsa_key ||= generate_rsa_key
|
21
|
+
session_secret = rsa_key[:public_key].gsub(/^-----.*PUBLIC KEY-----$/, '').strip
|
22
|
+
when 'Ed25519'
|
23
|
+
ed25519_key ||= generate_ed25519_key
|
24
|
+
session_secret = ed25519_key[:public_key]
|
25
|
+
else
|
26
|
+
raise 'Only RSA and Ed25519 are supported'
|
27
|
+
end
|
21
28
|
|
22
29
|
payload = {
|
23
30
|
full_name: full_name,
|
24
31
|
session_secret: session_secret
|
25
32
|
}
|
26
|
-
|
27
33
|
access_token = access_token('POST', '/users', payload.to_json)
|
28
34
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
29
35
|
res = client.post('/users', headers: { 'Authorization': authorization }, json: payload)
|
30
36
|
|
31
|
-
res.merge(rsa_key: rsa_key)
|
37
|
+
res.merge(rsa_key: rsa_key, ed25519_key: ed25519_key)
|
32
38
|
end
|
33
39
|
|
34
40
|
def generate_rsa_key
|
@@ -39,6 +45,14 @@ module MixinBot
|
|
39
45
|
}
|
40
46
|
end
|
41
47
|
|
48
|
+
def generate_ed25519_key
|
49
|
+
ed25519_key = JOSE::JWA::Ed25519.keypair
|
50
|
+
{
|
51
|
+
private_key: Base64.strict_encode64(ed25519_key[1]),
|
52
|
+
public_key: Base64.strict_encode64(ed25519_key[0])
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
42
56
|
# https://developers.mixin.one/api/beta-mixin-message/search-user/
|
43
57
|
# search by Mixin Id or Phone Number
|
44
58
|
def search_user(query)
|
@@ -4,7 +4,7 @@ module MixinBot
|
|
4
4
|
class API
|
5
5
|
module Withdraw
|
6
6
|
# https://developers.mixin.one/api/alpha-mixin-network/create-address/
|
7
|
-
def create_withdraw_address(options)
|
7
|
+
def create_withdraw_address(options, access_token: nil)
|
8
8
|
path = '/addresses'
|
9
9
|
encrypted_pin = encrypt_pin(options[:pin])
|
10
10
|
payload =
|
@@ -27,33 +27,33 @@ module MixinBot
|
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
-
access_token
|
30
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
31
31
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
32
32
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
33
33
|
end
|
34
34
|
|
35
35
|
# https://developers.mixin.one/api/alpha-mixin-network/read-address/
|
36
|
-
def get_withdraw_address(address)
|
36
|
+
def get_withdraw_address(address, access_token: nil)
|
37
37
|
path = format('/addresses/%<address>s', address: address)
|
38
|
-
access_token
|
38
|
+
access_token ||= access_token('GET', path, '')
|
39
39
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
40
40
|
client.get(path, headers: { 'Authorization': authorization })
|
41
41
|
end
|
42
42
|
|
43
43
|
# https://developers.mixin.one/api/alpha-mixin-network/delete-address/
|
44
|
-
def delete_withdraw_address(address, pin)
|
44
|
+
def delete_withdraw_address(address, pin, access_token: nil)
|
45
45
|
path = format('/addresses/%<address>s/delete', address: address)
|
46
46
|
payload = {
|
47
47
|
pin: encrypt_pin(pin)
|
48
48
|
}
|
49
49
|
|
50
|
-
access_token
|
50
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
51
51
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
52
52
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
53
53
|
end
|
54
54
|
|
55
55
|
# https://developers.mixin.one/api/alpha-mixin-network/withdrawal-addresses/
|
56
|
-
def withdrawals(options)
|
56
|
+
def withdrawals(options, access_token: nil)
|
57
57
|
address_id = options[:address_id]
|
58
58
|
pin = options[:pin]
|
59
59
|
amount = options[:amount]
|
@@ -69,7 +69,7 @@ module MixinBot
|
|
69
69
|
pin: encrypt_pin(pin)
|
70
70
|
}
|
71
71
|
|
72
|
-
access_token
|
72
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
73
73
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
74
74
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
75
75
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'awesome_print'
|
4
|
+
require 'cli/ui'
|
5
|
+
require 'thor'
|
6
|
+
require 'yaml'
|
7
|
+
require 'json'
|
8
|
+
require_relative './cli/node'
|
9
|
+
require_relative './cli/me'
|
10
|
+
require_relative './cli/multisig'
|
11
|
+
|
12
|
+
module MixinBot
|
13
|
+
class CLI < Thor
|
14
|
+
# https://github.com/Shopify/cli-ui
|
15
|
+
UI = ::CLI::UI
|
16
|
+
|
17
|
+
class_option :apihost, type: :string, aliases: '-a', desc: 'Specify mixin api host, default as api.mixin.one'
|
18
|
+
class_option :pretty, type: :boolean, aliases: '-p', desc: 'Print output in pretty'
|
19
|
+
|
20
|
+
attr_reader :config, :api
|
21
|
+
|
22
|
+
def initialize(*args)
|
23
|
+
super
|
24
|
+
if File.exist? options[:config].to_s
|
25
|
+
@config =
|
26
|
+
begin
|
27
|
+
YAML.load_file options[:config]
|
28
|
+
rescue StandardError => e
|
29
|
+
log UI.fmt(
|
30
|
+
format(
|
31
|
+
'{{x}} %<file>s is not a valid .yml file',
|
32
|
+
file: options[:config]
|
33
|
+
)
|
34
|
+
)
|
35
|
+
UI::Frame.open('{{x}}', color: :red) do
|
36
|
+
log e
|
37
|
+
end
|
38
|
+
end
|
39
|
+
elsif options[:config]
|
40
|
+
@confg =
|
41
|
+
begin
|
42
|
+
JSON.parse options[:config]
|
43
|
+
rescue StandardError => e
|
44
|
+
log UI.fmt(
|
45
|
+
format(
|
46
|
+
'{{x}} Failed to parse %<config>s',
|
47
|
+
config: options[:config]
|
48
|
+
)
|
49
|
+
)
|
50
|
+
UI::Frame.open('{{x}}', color: :red) do
|
51
|
+
log e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return unless @config
|
57
|
+
|
58
|
+
MixinBot.api_host = options[:apihost]
|
59
|
+
@api ||=
|
60
|
+
begin
|
61
|
+
MixinBot::API.new(
|
62
|
+
client_id: @config['client_id'],
|
63
|
+
client_secret: @config['client_secret'],
|
64
|
+
session_id: @config['session_id'],
|
65
|
+
pin_token: @config['pin_token'],
|
66
|
+
private_key: @config['private_key'],
|
67
|
+
pin_code: @config['pin_code']
|
68
|
+
)
|
69
|
+
rescue StandardError => e
|
70
|
+
log UI.fmt '{{x}}: Failed to initialize api, maybe your config is incorrect.'
|
71
|
+
UI.Frame.open('{{x}}', color: :red) do
|
72
|
+
log e
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
desc 'node', 'mixin node commands helper'
|
78
|
+
subcommand 'node', MixinBot::NodeCLI
|
79
|
+
|
80
|
+
desc 'version', 'Distay MixinBot version'
|
81
|
+
def version
|
82
|
+
log MixinBot::VERSION
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.exit_on_failure?
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def api_method(method, *args, **params)
|
92
|
+
if api.nil?
|
93
|
+
log UI.fmt '{{x}} MixinBot api not initialized!'
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
97
|
+
res = if args.empty? && params.empty?
|
98
|
+
api&.public_send method
|
99
|
+
elsif args.empty? && !params.empty?
|
100
|
+
api&.public_send method params
|
101
|
+
elsif !args.empty? && params.empty?
|
102
|
+
api&.public_send method, args
|
103
|
+
else
|
104
|
+
args.push params
|
105
|
+
api&.public_send method, args
|
106
|
+
end
|
107
|
+
log res
|
108
|
+
|
109
|
+
[res, res && res['error'].nil?]
|
110
|
+
rescue MixinBot::Errors => e
|
111
|
+
UI::Frame.open('{{x}}', color: :red) do
|
112
|
+
log e
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def log(obj)
|
117
|
+
if options[:pretty]
|
118
|
+
if obj.is_a? String
|
119
|
+
puts obj
|
120
|
+
else
|
121
|
+
ap obj
|
122
|
+
end
|
123
|
+
else
|
124
|
+
puts obj.inspect
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class CLI < Thor
|
5
|
+
desc 'read_me', 'fetch mixin bot profile'
|
6
|
+
option :config, required: true, aliases: '-c'
|
7
|
+
def read_me
|
8
|
+
api_method(:read_me)
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'read_assets', 'fetch mixin bot assets'
|
12
|
+
option :config, required: true, aliases: '-c'
|
13
|
+
def read_assets
|
14
|
+
api_method(:read_assets)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'cal_assets_as_usd', 'fetch mixin bot assets'
|
18
|
+
option :config, required: true, aliases: '-c'
|
19
|
+
def cal_assets_as_usd
|
20
|
+
assets, success = read_assets
|
21
|
+
return unless success
|
22
|
+
|
23
|
+
sum = assets['data'].map(
|
24
|
+
&lambda { |asset|
|
25
|
+
asset['balance'].to_f * asset['price_usd'].to_f
|
26
|
+
}
|
27
|
+
).sum
|
28
|
+
UI::Frame.open('USD') do
|
29
|
+
log sum
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'read_asset', 'fetch specific asset of mixin bot'
|
34
|
+
option :config, required: true, aliases: '-c'
|
35
|
+
option :assetid, required: true, aliases: '-s'
|
36
|
+
def read_asset
|
37
|
+
api_method(:read_asset, options[:assetid])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module MixinBot
|
6
|
+
class NodeCLI < Thor
|
7
|
+
# https://github.com/Shopify/cli-ui
|
8
|
+
UI = ::CLI::UI
|
9
|
+
UI::StdoutRouter.enable
|
10
|
+
|
11
|
+
desc 'listallnodes', 'List all mixin nodes'
|
12
|
+
def listallnodes
|
13
|
+
return unless ensure_mixin_command_exist
|
14
|
+
|
15
|
+
o, e, _s = Open3.capture3('mixin -n 35.188.235.212:8239 listallnodes')
|
16
|
+
log e unless e.empty?
|
17
|
+
log o
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'mint', 'Mint from mint distributions'
|
21
|
+
option :node, required: true, aliases: '-n', desc: 'node RPC address'
|
22
|
+
option :batch, type: :numeric, required: true, aliases: '-b', desc: 'mint batch'
|
23
|
+
option :view, type: :string, required: true, aliases: '-v', desc: 'view key'
|
24
|
+
option :address, type: :string, required: true, aliases: '-d', desc: 'address'
|
25
|
+
def mint
|
26
|
+
c = (Date.today - Date.new(2019, 2, 28)).to_i + 1
|
27
|
+
distributions = []
|
28
|
+
UI::Spinner.spin('Listing mint distributions') do |spinner|
|
29
|
+
o, _e, _s = Open3.capture3(
|
30
|
+
'mixin',
|
31
|
+
'-n',
|
32
|
+
options[:node],
|
33
|
+
'listmintdistributions',
|
34
|
+
'-c',
|
35
|
+
c.to_s
|
36
|
+
)
|
37
|
+
distributions = eval o
|
38
|
+
spinner.update_title "#{distributions.size} mint distributions listed"
|
39
|
+
end
|
40
|
+
|
41
|
+
tx = ''
|
42
|
+
UI::Spinner.spin('Finding transaction') do |spinner|
|
43
|
+
index = distributions.index(&->(d) { d[:batch] == options[:batch] })
|
44
|
+
tx = distributions[index][:transaction]
|
45
|
+
spinner.update_title "Transaction hash found: #{tx}"
|
46
|
+
end
|
47
|
+
|
48
|
+
UI::Spinner.spin('Fetching transaction') do |spinner|
|
49
|
+
o, _e, _s = Open3.capture3(
|
50
|
+
'mixin',
|
51
|
+
'-n',
|
52
|
+
options[:node],
|
53
|
+
'gettransaction',
|
54
|
+
'-x',
|
55
|
+
tx
|
56
|
+
)
|
57
|
+
tx = eval o
|
58
|
+
spinner.update_title "#{tx[:outputs].size} transaction outputs found"
|
59
|
+
end
|
60
|
+
|
61
|
+
tx[:outputs].each_with_index do |output, index|
|
62
|
+
address = ''
|
63
|
+
UI::Spinner.spin("Checking output index: #{index}") do |spinner|
|
64
|
+
o, _e, _s = Open3.capture3(
|
65
|
+
'mixin',
|
66
|
+
'decryptghostkey',
|
67
|
+
'--key',
|
68
|
+
output[:keys].first,
|
69
|
+
'--mask',
|
70
|
+
output[:mask],
|
71
|
+
'--view',
|
72
|
+
options[:view]
|
73
|
+
)
|
74
|
+
address = o.chomp
|
75
|
+
spinner.update_title "Index #{index} Address: #{address}"
|
76
|
+
end
|
77
|
+
log "Found Utxo: #{index}" if address == options[:address]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def ensure_mixin_command_exist
|
84
|
+
return true if command?('mixin')
|
85
|
+
|
86
|
+
log UI.fmt '{{x}} `mixin` command is not valid!'
|
87
|
+
log UI.fmt 'Please install mixin software and provide a executable `mixin` command'
|
88
|
+
end
|
89
|
+
|
90
|
+
def command?(name)
|
91
|
+
`which #{name}`
|
92
|
+
$CHILD_STATUS.success?
|
93
|
+
end
|
94
|
+
|
95
|
+
def log(obj)
|
96
|
+
if options[:pretty]
|
97
|
+
if obj.is_a? String
|
98
|
+
puts obj
|
99
|
+
else
|
100
|
+
ap obj
|
101
|
+
end
|
102
|
+
else
|
103
|
+
puts obj.inspect
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|