mixin_bot 0.12.1 → 1.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 +4 -4
- data/lib/mixin_bot/api/address.rb +21 -0
- data/lib/mixin_bot/api/app.rb +5 -11
- data/lib/mixin_bot/api/asset.rb +9 -16
- data/lib/mixin_bot/api/attachment.rb +27 -22
- data/lib/mixin_bot/api/auth.rb +34 -56
- data/lib/mixin_bot/api/blaze.rb +4 -3
- data/lib/mixin_bot/api/conversation.rb +29 -49
- data/lib/mixin_bot/api/encrypted_message.rb +19 -19
- data/lib/mixin_bot/api/inscription.rb +71 -0
- data/lib/mixin_bot/api/legacy_collectible.rb +140 -0
- data/lib/mixin_bot/api/legacy_multisig.rb +87 -0
- data/lib/mixin_bot/api/legacy_output.rb +50 -0
- data/lib/mixin_bot/api/legacy_payment.rb +31 -0
- data/lib/mixin_bot/api/legacy_snapshot.rb +39 -0
- data/lib/mixin_bot/api/legacy_transaction.rb +173 -0
- data/lib/mixin_bot/api/legacy_transfer.rb +42 -0
- data/lib/mixin_bot/api/me.rb +13 -17
- data/lib/mixin_bot/api/message.rb +13 -10
- data/lib/mixin_bot/api/multisig.rb +17 -222
- data/lib/mixin_bot/api/output.rb +48 -0
- data/lib/mixin_bot/api/payment.rb +9 -20
- data/lib/mixin_bot/api/pin.rb +57 -65
- data/lib/mixin_bot/api/rpc.rb +12 -14
- data/lib/mixin_bot/api/snapshot.rb +15 -29
- data/lib/mixin_bot/api/tip.rb +43 -0
- data/lib/mixin_bot/api/transaction.rb +295 -60
- data/lib/mixin_bot/api/transfer.rb +69 -31
- data/lib/mixin_bot/api/user.rb +88 -53
- data/lib/mixin_bot/api/withdraw.rb +52 -53
- data/lib/mixin_bot/api.rb +81 -46
- data/lib/mixin_bot/cli/api.rb +149 -5
- data/lib/mixin_bot/cli/utils.rb +14 -4
- data/lib/mixin_bot/cli.rb +13 -10
- data/lib/mixin_bot/client.rb +74 -127
- data/lib/mixin_bot/configuration.rb +98 -0
- data/lib/mixin_bot/nfo.rb +174 -0
- data/lib/mixin_bot/transaction.rb +524 -0
- data/lib/mixin_bot/utils/address.rb +121 -0
- data/lib/mixin_bot/utils/crypto.rb +218 -0
- data/lib/mixin_bot/utils/decoder.rb +56 -0
- data/lib/mixin_bot/utils/encoder.rb +63 -0
- data/lib/mixin_bot/utils.rb +8 -109
- data/lib/mixin_bot/uuid.rb +41 -0
- data/lib/mixin_bot/version.rb +1 -1
- data/lib/mixin_bot.rb +39 -14
- data/lib/mvm/bridge.rb +2 -19
- data/lib/mvm/client.rb +11 -33
- data/lib/mvm/nft.rb +4 -4
- data/lib/mvm/registry.rb +9 -9
- data/lib/mvm/scan.rb +3 -5
- data/lib/mvm.rb +5 -6
- metadata +77 -103
- data/lib/mixin_bot/api/collectible.rb +0 -138
- data/lib/mixin_bot/utils/nfo.rb +0 -176
- data/lib/mixin_bot/utils/transaction.rb +0 -478
- data/lib/mixin_bot/utils/uuid.rb +0 -43
data/lib/mixin_bot/api/user.rb
CHANGED
@@ -3,76 +3,111 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module User
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
path = format('/users/%<user_id>s', user_id: user_id)
|
10
|
-
access_token = access_token('GET', path, '')
|
11
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
12
|
-
client.get(path, headers: { 'Authorization': authorization })
|
6
|
+
def user(user_id, access_token: nil)
|
7
|
+
path = format('/users/%<user_id>s', user_id:)
|
8
|
+
client.get path, access_token:
|
13
9
|
end
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
11
|
+
def create_user(full_name, key: nil)
|
12
|
+
keypair = JOSE::JWA::Ed25519.keypair key
|
13
|
+
session_secret = Base64.urlsafe_encode64 keypair[0], padding: false
|
14
|
+
private_key = keypair[1].unpack1('H*')
|
28
15
|
|
16
|
+
path = '/users'
|
29
17
|
payload = {
|
30
|
-
full_name
|
31
|
-
session_secret:
|
18
|
+
full_name:,
|
19
|
+
session_secret:
|
32
20
|
}
|
33
|
-
access_token = access_token('POST', '/users', payload.to_json)
|
34
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
35
|
-
res = client.post('/users', headers: { 'Authorization': authorization }, json: payload)
|
36
21
|
|
37
|
-
res.
|
22
|
+
res = client.post path, **payload
|
23
|
+
res.merge(private_key:).with_indifferent_access
|
38
24
|
end
|
39
25
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
public_key: rsa_key.public_key.to_pem
|
45
|
-
}
|
26
|
+
def search_user(query, access_token: nil)
|
27
|
+
path = format('/search/%<query>s', query:)
|
28
|
+
|
29
|
+
client.get path, access_token:
|
46
30
|
end
|
47
31
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
32
|
+
def fetch_users(user_ids)
|
33
|
+
path = '/users/fetch'
|
34
|
+
user_ids = [user_ids] if user_ids.is_a? String
|
35
|
+
payload = user_ids
|
36
|
+
|
37
|
+
client.post path, *payload
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_safe_user(name, private_key: nil, spend_key: nil)
|
41
|
+
private_keypair = JOSE::JWA::Ed25519.keypair private_key
|
42
|
+
private_key = private_keypair[1].unpack1('H*')
|
43
|
+
|
44
|
+
spend_keypair = JOSE::JWA::Ed25519.keypair spend_key
|
45
|
+
spend_key = spend_keypair[1].unpack1('H*')
|
46
|
+
|
47
|
+
user = create_user name, key: private_keypair[1][...32]
|
48
|
+
|
49
|
+
keystore = {
|
50
|
+
app_id: user['data']['user_id'],
|
51
|
+
session_id: user['data']['session_id'],
|
52
|
+
private_key:,
|
53
|
+
pin_token: user['data']['pin_token_base64'],
|
54
|
+
spend_key: spend_keypair[1].unpack1('H*')
|
53
55
|
}
|
56
|
+
user_api = MixinBot::API.new(**keystore)
|
57
|
+
|
58
|
+
user_api.update_pin pin: MixinBot.utils.tip_public_key(spend_keypair[0], counter: user['data']['tip_counter'])
|
59
|
+
|
60
|
+
# wait for tip pin update in server
|
61
|
+
sleep 1
|
62
|
+
|
63
|
+
user_api.safe_register spend_key
|
64
|
+
|
65
|
+
keystore
|
54
66
|
end
|
55
67
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
68
|
+
def safe_register(pin, spend_key: nil)
|
69
|
+
path = '/safe/users'
|
70
|
+
|
71
|
+
spend_key ||= MixinBot.utils.decode_key pin
|
72
|
+
key = JOSE::JWA::Ed25519.keypair spend_key[...32]
|
73
|
+
public_key = key[0].unpack1('H*')
|
74
|
+
|
75
|
+
hex = SHA3::Digest::SHA256.hexdigest config.app_id
|
76
|
+
signature = Base64.urlsafe_encode64 JOSE::JWA::Ed25519.sign([hex].pack('H*'), key[1]), padding: false
|
60
77
|
|
61
|
-
|
62
|
-
|
63
|
-
|
78
|
+
pin_base64 = encrypt_tip_pin pin, 'SEQUENCER:REGISTER:', config.app_id, public_key
|
79
|
+
|
80
|
+
payload = {
|
81
|
+
public_key:,
|
82
|
+
signature:,
|
83
|
+
pin_base64:
|
84
|
+
}
|
85
|
+
|
86
|
+
client.post path, **payload
|
64
87
|
end
|
65
88
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
89
|
+
def migrate_to_safe(spend_key:, pin: nil)
|
90
|
+
profile = me['data']
|
91
|
+
return true if profile['has_safe']
|
92
|
+
|
93
|
+
spend_keypair = JOSE::JWA::Ed25519.keypair spend_key
|
94
|
+
spend_key = spend_keypair[1].unpack1('H*')
|
72
95
|
|
73
|
-
|
74
|
-
|
75
|
-
|
96
|
+
if profile['tip_key_base64'].blank?
|
97
|
+
new_pin = MixinBot.utils.tip_public_key(spend_keypair[0], counter: profile['tip_counter'])
|
98
|
+
update_pin(pin: new_pin, old_pin: pin)
|
99
|
+
|
100
|
+
pin = new_pin
|
101
|
+
end
|
102
|
+
|
103
|
+
# wait for tip pin update in server
|
104
|
+
sleep 1
|
105
|
+
|
106
|
+
safe_register pin, spend_key
|
107
|
+
|
108
|
+
{
|
109
|
+
spend_key:
|
110
|
+
}.with_indifferent_access
|
76
111
|
end
|
77
112
|
end
|
78
113
|
end
|
@@ -3,75 +3,74 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Withdraw
|
6
|
-
|
7
|
-
def create_withdraw_address(options, access_token: nil)
|
6
|
+
def create_withdraw_address(**kwargs)
|
8
7
|
path = '/addresses'
|
9
|
-
|
8
|
+
pin = kwargs[:pin]
|
10
9
|
payload =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
asset_id: options[:asset_id],
|
24
|
-
public_key: options[:public_key],
|
25
|
-
label: options[:label],
|
26
|
-
pin: encrypted_pin
|
27
|
-
}
|
28
|
-
end
|
10
|
+
{
|
11
|
+
asset_id: kwargs[:asset_id],
|
12
|
+
destination: kwargs[:destination],
|
13
|
+
tag: kwargs[:tag],
|
14
|
+
label: kwargs[:label]
|
15
|
+
}
|
16
|
+
|
17
|
+
if pin.length > 6
|
18
|
+
payload[:pin_base64] = encrypt_tip_pin pin, 'TIP:ADDRESS:ADD:', payload[:asset_id], payload[:destination], payload[:tag], payload[:label]
|
19
|
+
else
|
20
|
+
payload[:pin] = encrypt_pin pin
|
21
|
+
end
|
29
22
|
|
30
|
-
|
31
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
32
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
23
|
+
client.post path, **payload
|
33
24
|
end
|
34
25
|
|
35
|
-
# https://developers.mixin.one/api/alpha-mixin-network/read-address/
|
36
26
|
def get_withdraw_address(address, access_token: nil)
|
37
|
-
path = format('/addresses/%<address>s', address:
|
38
|
-
|
39
|
-
|
40
|
-
client.get(path, headers: { 'Authorization': authorization })
|
27
|
+
path = format('/addresses/%<address>s', address:)
|
28
|
+
|
29
|
+
client.get path, access_token:
|
41
30
|
end
|
42
31
|
|
43
|
-
|
44
|
-
|
45
|
-
path = format('/addresses/%<address>s/delete', address: address)
|
46
|
-
payload = {
|
47
|
-
pin: encrypt_pin(pin)
|
48
|
-
}
|
32
|
+
def delete_withdraw_address(address, **kwargs)
|
33
|
+
pin = kwargs[:pin]
|
49
34
|
|
50
|
-
|
51
|
-
|
52
|
-
|
35
|
+
path = format('/addresses/%<address>s/delete', address:)
|
36
|
+
payload =
|
37
|
+
if pin.length > 6
|
38
|
+
{
|
39
|
+
pin_base64: encrypt_tip_pin(pin, 'TIP:ADDRESS:REMOVE:', address)
|
40
|
+
}
|
41
|
+
else
|
42
|
+
{
|
43
|
+
pin: encrypt_pin(pin)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
client.post path, **payload
|
53
48
|
end
|
54
49
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
def withdrawals(**kwargs)
|
51
|
+
address_id = kwargs[:address_id]
|
52
|
+
pin = kwargs[:pin]
|
53
|
+
amount = format('%.8f', kwargs[:amount].to_d.to_r)
|
54
|
+
trace_id = kwargs[:trace_id]
|
55
|
+
memo = kwargs[:memo]
|
56
|
+
kwargs[:access_token]
|
62
57
|
|
63
58
|
path = '/withdrawals'
|
64
59
|
payload = {
|
65
|
-
address_id
|
66
|
-
amount
|
67
|
-
trace_id
|
68
|
-
memo:
|
69
|
-
pin: encrypt_pin(pin)
|
60
|
+
address_id:,
|
61
|
+
amount:,
|
62
|
+
trace_id:,
|
63
|
+
memo:
|
70
64
|
}
|
71
65
|
|
72
|
-
|
73
|
-
|
74
|
-
|
66
|
+
if pin.length > 6
|
67
|
+
fee = '0'
|
68
|
+
payload[:pin_base64] = encrypt_tip_pin pin, 'TIP:WITHDRAW:', address_id, amount, fee, trace_id, memo
|
69
|
+
else
|
70
|
+
payload[:pin] = encrypt_pin pin
|
71
|
+
end
|
72
|
+
|
73
|
+
client.post path, **payload
|
75
74
|
end
|
76
75
|
end
|
77
76
|
end
|
data/lib/mixin_bot/api.rb
CHANGED
@@ -1,62 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
5
|
-
require_relative '
|
6
|
-
require_relative '
|
7
|
-
require_relative '
|
8
|
-
require_relative '
|
9
|
-
require_relative '
|
10
|
-
require_relative '
|
11
|
-
require_relative '
|
12
|
-
require_relative '
|
13
|
-
require_relative '
|
14
|
-
require_relative '
|
15
|
-
require_relative '
|
16
|
-
require_relative '
|
17
|
-
require_relative '
|
18
|
-
require_relative '
|
19
|
-
require_relative '
|
20
|
-
require_relative '
|
21
|
-
require_relative '
|
22
|
-
require_relative '
|
3
|
+
require_relative 'client'
|
4
|
+
require_relative 'configuration'
|
5
|
+
require_relative 'api/address'
|
6
|
+
require_relative 'api/app'
|
7
|
+
require_relative 'api/asset'
|
8
|
+
require_relative 'api/attachment'
|
9
|
+
require_relative 'api/auth'
|
10
|
+
require_relative 'api/blaze'
|
11
|
+
require_relative 'api/conversation'
|
12
|
+
require_relative 'api/encrypted_message'
|
13
|
+
require_relative 'api/inscription'
|
14
|
+
require_relative 'api/legacy_collectible'
|
15
|
+
require_relative 'api/legacy_multisig'
|
16
|
+
require_relative 'api/legacy_output'
|
17
|
+
require_relative 'api/legacy_payment'
|
18
|
+
require_relative 'api/legacy_snapshot'
|
19
|
+
require_relative 'api/legacy_transaction'
|
20
|
+
require_relative 'api/legacy_transfer'
|
21
|
+
require_relative 'api/me'
|
22
|
+
require_relative 'api/message'
|
23
|
+
require_relative 'api/multisig'
|
24
|
+
require_relative 'api/output'
|
25
|
+
require_relative 'api/payment'
|
26
|
+
require_relative 'api/pin'
|
27
|
+
require_relative 'api/rpc'
|
28
|
+
require_relative 'api/snapshot'
|
29
|
+
require_relative 'api/tip'
|
30
|
+
require_relative 'api/transaction'
|
31
|
+
require_relative 'api/transfer'
|
32
|
+
require_relative 'api/user'
|
33
|
+
require_relative 'api/withdraw'
|
23
34
|
|
24
35
|
module MixinBot
|
25
36
|
class API
|
26
|
-
attr_reader :
|
27
|
-
|
28
|
-
def initialize(
|
29
|
-
@
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@pin_token =
|
35
|
-
begin
|
36
|
-
Base64.urlsafe_decode64 options[:pin_token] || MixinBot.pin_token
|
37
|
-
rescue StandardError
|
38
|
-
''
|
37
|
+
attr_reader :config, :client
|
38
|
+
|
39
|
+
def initialize(**kwargs)
|
40
|
+
@config =
|
41
|
+
if kwargs.present?
|
42
|
+
MixinBot::Configuration.new(**kwargs)
|
43
|
+
else
|
44
|
+
MixinBot.config
|
39
45
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
|
47
|
+
@client = Client.new(@config)
|
48
|
+
end
|
49
|
+
|
50
|
+
def utils
|
51
|
+
MixinBot::Utils
|
52
|
+
end
|
53
|
+
|
54
|
+
def client_id
|
55
|
+
config.app_id
|
48
56
|
end
|
49
57
|
|
50
|
-
def
|
51
|
-
|
58
|
+
def access_token(method, uri, body, **kwargs)
|
59
|
+
utils.access_token(
|
60
|
+
method,
|
61
|
+
uri,
|
62
|
+
body,
|
63
|
+
exp_in: kwargs.delete(:exp_in) || 600,
|
64
|
+
scp: kwargs.delete(:scp) || 'FULL',
|
65
|
+
app_id: config.app_id,
|
66
|
+
session_id: config.session_id,
|
67
|
+
private_key: config.session_private_key
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def encode_raw_transaction(txn)
|
72
|
+
utils.encode_raw_transaction txn
|
52
73
|
end
|
53
74
|
|
54
75
|
def decode_raw_transaction(raw)
|
55
|
-
|
76
|
+
utils.decode_raw_transaction raw
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_trace_from_hash(hash, output_index = 0)
|
80
|
+
utils.generate_trace_from_hash hash, output_index
|
56
81
|
end
|
57
82
|
|
58
83
|
# Use a mixin software to implement transaction build
|
59
|
-
def
|
84
|
+
def encode_raw_transaction_native(json)
|
60
85
|
ensure_mixin_command_exist
|
61
86
|
command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
|
62
87
|
|
@@ -77,21 +102,31 @@ module MixinBot
|
|
77
102
|
JSON.parse output.chomp
|
78
103
|
end
|
79
104
|
|
105
|
+
include MixinBot::API::Address
|
80
106
|
include MixinBot::API::App
|
81
107
|
include MixinBot::API::Asset
|
82
108
|
include MixinBot::API::Attachment
|
83
109
|
include MixinBot::API::Auth
|
84
110
|
include MixinBot::API::Blaze
|
85
|
-
include MixinBot::API::Collectible
|
86
111
|
include MixinBot::API::Conversation
|
87
112
|
include MixinBot::API::EncryptedMessage
|
113
|
+
include MixinBot::API::Inscription
|
114
|
+
include MixinBot::API::LegacyCollectible
|
115
|
+
include MixinBot::API::LegacyMultisig
|
116
|
+
include MixinBot::API::LegacyOutput
|
117
|
+
include MixinBot::API::LegacyPayment
|
118
|
+
include MixinBot::API::LegacySnapshot
|
119
|
+
include MixinBot::API::LegacyTransaction
|
120
|
+
include MixinBot::API::LegacyTransfer
|
88
121
|
include MixinBot::API::Me
|
89
122
|
include MixinBot::API::Message
|
90
123
|
include MixinBot::API::Multisig
|
124
|
+
include MixinBot::API::Output
|
91
125
|
include MixinBot::API::Payment
|
92
126
|
include MixinBot::API::Pin
|
93
127
|
include MixinBot::API::Rpc
|
94
128
|
include MixinBot::API::Snapshot
|
129
|
+
include MixinBot::API::Tip
|
95
130
|
include MixinBot::API::Transaction
|
96
131
|
include MixinBot::API::Transfer
|
97
132
|
include MixinBot::API::User
|
data/lib/mixin_bot/cli/api.rb
CHANGED
@@ -34,16 +34,16 @@ module MixinBot
|
|
34
34
|
end
|
35
35
|
|
36
36
|
access_token = options[:accesstoken] || api_instance.access_token(options[:method].upcase, path, payload.blank? ? '' : payload.to_json)
|
37
|
-
authorization = format('Bearer %<access_token>s', access_token:
|
37
|
+
authorization = format('Bearer %<access_token>s', access_token:)
|
38
38
|
res = {}
|
39
39
|
|
40
40
|
CLI::UI::Spinner.spin("#{options[:method]} #{path}, payload: #{payload}") do |_spinner|
|
41
41
|
res =
|
42
42
|
case options[:method].downcase.to_sym
|
43
43
|
when :post
|
44
|
-
api_instance.client.post(path, headers: {
|
44
|
+
api_instance.client.post(path, headers: { Authorization: authorization }, json: payload)
|
45
45
|
when :get
|
46
|
-
api_instance.client.get(path, headers: {
|
46
|
+
api_instance.client.get(path, headers: { Authorization: authorization })
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -52,19 +52,163 @@ module MixinBot
|
|
52
52
|
|
53
53
|
desc 'authcode', 'code to authorize other mixin account'
|
54
54
|
option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
|
55
|
-
option :
|
55
|
+
option :app_id, type: :string, required: true, aliases: '-c', desc: 'app_id of bot to authorize'
|
56
56
|
option :scope, type: :array, default: ['PROFILE:READ'], aliases: '-s', desc: 'scope to authorize'
|
57
57
|
def authcode
|
58
58
|
res = {}
|
59
59
|
CLI::UI::Spinner.spin('POST /oauth/authorize') do |_spinner|
|
60
60
|
res =
|
61
61
|
api_instance.authorize_code(
|
62
|
-
user_id: options[:
|
62
|
+
user_id: options[:app_id],
|
63
63
|
scope: options[:scope],
|
64
64
|
pin: keystore['pin']
|
65
65
|
)
|
66
66
|
end
|
67
67
|
log res['data']
|
68
68
|
end
|
69
|
+
|
70
|
+
desc 'updatetip PIN', 'update TIP pin'
|
71
|
+
option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
|
72
|
+
def updatetip(pin)
|
73
|
+
profile = api_instance.me
|
74
|
+
log UI.fmt "{{v}} #{profile['full_name']}, TIP counter: #{profile['tip_counter']}"
|
75
|
+
|
76
|
+
counter = profile['tip_counter']
|
77
|
+
key = api_instance.prepare_tip_key counter
|
78
|
+
log UI.fmt "{{v}} Generated key: #{key[:private_key]}"
|
79
|
+
|
80
|
+
res = api_instance.update_pin old_pin: pin.to_s, pin: key[:public_key]
|
81
|
+
|
82
|
+
log({
|
83
|
+
pin: key[:private_key],
|
84
|
+
tip_key_base64: res['tip_key_base64']
|
85
|
+
})
|
86
|
+
rescue StandardError => e
|
87
|
+
log UI.fmt "{{x}} #{e.inspect}"
|
88
|
+
end
|
89
|
+
|
90
|
+
desc 'verifypin PIN', 'verify pin'
|
91
|
+
option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
|
92
|
+
def verifypin(pin)
|
93
|
+
res = api_instance.verify_pin pin.to_s
|
94
|
+
|
95
|
+
log res
|
96
|
+
rescue StandardError => e
|
97
|
+
log UI.fmt "{{x}} #{e.inspect}"
|
98
|
+
end
|
99
|
+
|
100
|
+
desc 'transfer USER_ID', 'transfer asset to USER_ID'
|
101
|
+
option :asset, type: :string, required: true, desc: 'Asset ID'
|
102
|
+
option :amount, type: :numeric, required: true, desc: 'Amount'
|
103
|
+
option :memo, type: :string, required: false, desc: 'memo'
|
104
|
+
option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
|
105
|
+
def transfer(user_id)
|
106
|
+
res = {}
|
107
|
+
|
108
|
+
CLI::UI::Spinner.spin "Try to transfer #{options[:amount]} #{options[:asset]} to #{user_id}" do |_spinner|
|
109
|
+
res = api_instance.create_transfer(
|
110
|
+
keystore['pin'],
|
111
|
+
{
|
112
|
+
asset_id: options[:asset],
|
113
|
+
opponent_id: user_id,
|
114
|
+
amount: options[:amount],
|
115
|
+
memo: options[:memo]
|
116
|
+
}
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
return unless res['snapshot_id'].present?
|
121
|
+
|
122
|
+
log UI.fmt "{{v}} Finished: https://mixin.one/snapshots/#{res['snapshot_id']}"
|
123
|
+
end
|
124
|
+
|
125
|
+
desc 'saferegister', 'register SAFE network'
|
126
|
+
option :spend_key, type: :string, required: true, desc: 'spend_key'
|
127
|
+
option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
|
128
|
+
def saferegister
|
129
|
+
res = api_instance.safe_register options[:spend_key]
|
130
|
+
log res
|
131
|
+
end
|
132
|
+
|
133
|
+
desc 'pay', 'generate payment url'
|
134
|
+
option :members, type: :array, required: true, desc: 'Reveivers, maybe multisig'
|
135
|
+
option :threshold, type: :numeric, required: false, default: 1, desc: 'Threshold of multisig'
|
136
|
+
option :asset, type: :string, required: true, desc: 'Asset ID'
|
137
|
+
option :amount, type: :numeric, required: true, desc: 'Amount'
|
138
|
+
option :trace, type: :string, required: false, desc: 'Trace ID'
|
139
|
+
option :memo, type: :string, required: false, desc: 'memo'
|
140
|
+
def pay
|
141
|
+
url = api_instance.safe_pay_url(
|
142
|
+
members: options[:members],
|
143
|
+
threshold: options[:threshold],
|
144
|
+
asset_id: options[:asset],
|
145
|
+
amount: options[:amount],
|
146
|
+
trace_id: options[:trace],
|
147
|
+
memo: options[:memo]
|
148
|
+
)
|
149
|
+
|
150
|
+
log UI.fmt "{{v}} #{url}"
|
151
|
+
end
|
152
|
+
|
153
|
+
desc 'safetransfer USER_ID', 'transfer asset to USER_ID with SAFE network'
|
154
|
+
option :asset, type: :string, required: true, desc: 'Asset ID'
|
155
|
+
option :amount, type: :numeric, required: true, desc: 'Amount'
|
156
|
+
option :trace, type: :string, required: false, desc: 'Trace ID'
|
157
|
+
option :memo, type: :string, required: false, desc: 'memo'
|
158
|
+
option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
|
159
|
+
def safetransfer(user_id)
|
160
|
+
amount = options[:amount].to_d
|
161
|
+
asset = options[:asset]
|
162
|
+
memo = options[:memo] || ''
|
163
|
+
|
164
|
+
# step 1: select inputs
|
165
|
+
outputs = api_instance.safe_outputs(state: 'unspent', asset_id: asset, limit: 500)['data'].sort_by { |o| o['amount'].to_d }
|
166
|
+
balance = outputs.sum(&->(output) { output['amount'].to_d })
|
167
|
+
|
168
|
+
utxos = []
|
169
|
+
outputs.each do |output|
|
170
|
+
break if utxos.sum { |o| o['amount'].to_d } >= amount
|
171
|
+
|
172
|
+
utxos.shift if utxos.size >= 255
|
173
|
+
utxos << output
|
174
|
+
end
|
175
|
+
|
176
|
+
log UI.fmt "Step 1/7: {{v}} Found #{outputs.count} unspent outputs, balance: #{balance}, selected #{utxos.count} outputs"
|
177
|
+
|
178
|
+
# step 2: build transaction
|
179
|
+
tx = api_instance.build_safe_transaction(
|
180
|
+
utxos:,
|
181
|
+
receivers: [
|
182
|
+
members: [user_id],
|
183
|
+
threshold: 1,
|
184
|
+
amount:
|
185
|
+
],
|
186
|
+
extra: memo
|
187
|
+
)
|
188
|
+
raw = MixinBot::Utils.encode_raw_transaction tx
|
189
|
+
log UI.fmt "Step 2/5: {{v}} Built raw: #{raw}"
|
190
|
+
|
191
|
+
# step 3: verify transaction
|
192
|
+
request_id = SecureRandom.uuid
|
193
|
+
request = api_instance.create_safe_transaction_request(request_id, raw)['data']
|
194
|
+
log UI.fmt "Step 3/5: {{v}} Verified transaction, request_id: #{request[0]['request_id']}"
|
195
|
+
|
196
|
+
# step 4: sign transaction
|
197
|
+
signed_raw = api_instance.sign_safe_transaction(
|
198
|
+
raw:,
|
199
|
+
utxos:,
|
200
|
+
request: request[0]
|
201
|
+
)
|
202
|
+
log UI.fmt "Step 4/5: {{v}} Signed transaction: #{signed_raw}"
|
203
|
+
|
204
|
+
# step 5: submit transaction
|
205
|
+
r = api_instance.send_safe_transaction(
|
206
|
+
request_id,
|
207
|
+
signed_raw
|
208
|
+
)
|
209
|
+
log UI.fmt "Step 5/5: {{v}} Submit transaction, hash: #{r['data'].first['transaction_hash']}"
|
210
|
+
rescue StandardError => e
|
211
|
+
log UI.fmt "{{x}} #{e.inspect}"
|
212
|
+
end
|
69
213
|
end
|
70
214
|
end
|