mixin_bot 0.12.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +31 -53
- data/lib/mixin_bot/api/blaze.rb +4 -3
- data/lib/mixin_bot/api/collectible.rb +60 -58
- data/lib/mixin_bot/api/conversation.rb +29 -49
- data/lib/mixin_bot/api/encrypted_message.rb +17 -17
- 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 +16 -221
- data/lib/mixin_bot/api/output.rb +46 -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 +9 -11
- 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 +184 -60
- data/lib/mixin_bot/api/transfer.rb +64 -32
- data/lib/mixin_bot/api/user.rb +83 -53
- data/lib/mixin_bot/api/withdraw.rb +52 -53
- data/lib/mixin_bot/api.rb +78 -45
- data/lib/mixin_bot/cli/api.rb +151 -8
- data/lib/mixin_bot/cli/utils.rb +14 -4
- data/lib/mixin_bot/cli.rb +13 -10
- data/lib/mixin_bot/client.rb +76 -127
- data/lib/mixin_bot/configuration.rb +98 -0
- data/lib/mixin_bot/nfo.rb +174 -0
- data/lib/mixin_bot/transaction.rb +505 -0
- data/lib/mixin_bot/utils/address.rb +108 -0
- data/lib/mixin_bot/utils/crypto.rb +182 -0
- data/lib/mixin_bot/utils/decoder.rb +58 -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 +101 -44
- 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/me.rb
CHANGED
@@ -3,37 +3,33 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Me
|
6
|
-
# https://developers.mixin.one/api/beta-mixin-message/read-profile/
|
7
6
|
def me(access_token: nil)
|
8
7
|
path = '/me'
|
9
|
-
|
10
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
11
|
-
client.get(path, headers: { 'Authorization': authorization })
|
8
|
+
client.get path, access_token:
|
12
9
|
end
|
13
|
-
alias read_me me
|
14
10
|
|
15
|
-
# https://developers.mixin.one/api/beta-mixin-message/update-profile/
|
16
11
|
# avatar_base64:
|
17
|
-
#
|
18
|
-
def update_me(
|
12
|
+
# String: Base64 of image, supports format png, jpeg and gif, base64 image size > 1024.
|
13
|
+
def update_me(**kwargs)
|
19
14
|
path = '/me'
|
20
15
|
payload = {
|
21
|
-
full_name: full_name,
|
22
|
-
avatar_base64: avatar_base64
|
16
|
+
full_name: kwargs[:full_name],
|
17
|
+
avatar_base64: kwargs[:avatar_base64],
|
18
|
+
access_token: kwargs[:access_token]
|
23
19
|
}
|
24
|
-
|
25
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
26
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
20
|
+
client.post path, **payload
|
27
21
|
end
|
28
22
|
|
29
23
|
# https://developers.mixin.one/api/beta-mixin-message/friends/
|
30
24
|
def friends(access_token: nil)
|
31
25
|
path = '/friends'
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
client.get path, access_token:
|
27
|
+
end
|
28
|
+
|
29
|
+
def safe_me(access_token: nil)
|
30
|
+
path = '/safe/me'
|
31
|
+
client.get path, access_token:
|
35
32
|
end
|
36
|
-
alias read_friends friends
|
37
33
|
end
|
38
34
|
end
|
39
35
|
end
|
@@ -10,10 +10,10 @@ module MixinBot
|
|
10
10
|
|
11
11
|
def acknowledge_message_receipt(message_id)
|
12
12
|
params = {
|
13
|
-
message_id
|
13
|
+
message_id:,
|
14
14
|
status: 'READ'
|
15
15
|
}
|
16
|
-
write_ws_message(action: 'ACKNOWLEDGE_MESSAGE_RECEIPT', params:
|
16
|
+
write_ws_message(action: 'ACKNOWLEDGE_MESSAGE_RECEIPT', params:)
|
17
17
|
end
|
18
18
|
|
19
19
|
def plain_text(options)
|
@@ -72,7 +72,7 @@ module MixinBot
|
|
72
72
|
options.merge!(
|
73
73
|
category: 'MESSAGE_RECALL',
|
74
74
|
data: {
|
75
|
-
message_id:
|
75
|
+
message_id:
|
76
76
|
}
|
77
77
|
)
|
78
78
|
base_message_params(options)
|
@@ -90,11 +90,11 @@ module MixinBot
|
|
90
90
|
quote_message_id: options[:quote_message_id],
|
91
91
|
message_id: options[:message_id] || SecureRandom.uuid,
|
92
92
|
data: Base64.encode64(data)
|
93
|
-
}
|
93
|
+
}.compact
|
94
94
|
end
|
95
95
|
|
96
96
|
# read the gzipped message form websocket
|
97
|
-
def
|
97
|
+
def ws_message(data)
|
98
98
|
io = StringIO.new(data.pack('c*'), 'rb')
|
99
99
|
gzip = Zlib::GzipReader.new io
|
100
100
|
msg = gzip.read
|
@@ -107,8 +107,8 @@ module MixinBot
|
|
107
107
|
def write_ws_message(params:, action: 'CREATE_MESSAGE')
|
108
108
|
msg = {
|
109
109
|
id: SecureRandom.uuid,
|
110
|
-
action
|
111
|
-
params:
|
110
|
+
action:,
|
111
|
+
params:
|
112
112
|
}.to_json
|
113
113
|
|
114
114
|
io = StringIO.new 'wb'
|
@@ -158,9 +158,12 @@ module MixinBot
|
|
158
158
|
# http post request
|
159
159
|
def send_message(payload)
|
160
160
|
path = '/messages'
|
161
|
-
|
162
|
-
|
163
|
-
|
161
|
+
|
162
|
+
if payload.is_a? Hash
|
163
|
+
client.post path, **payload
|
164
|
+
elsif payload.is_a? Array
|
165
|
+
client.post path, *payload
|
166
|
+
end
|
164
167
|
end
|
165
168
|
end
|
166
169
|
end
|
@@ -3,241 +3,36 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Multisig
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
members = kwargs[:members] || []
|
11
|
-
threshold = kwargs[:threshold] || ''
|
12
|
-
access_token = kwargs[:access_token]
|
13
|
-
members = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
|
14
|
-
|
15
|
-
path = format(
|
16
|
-
'/multisigs/outputs?limit=%<limit>s&offset=%<offset>s&state=%<state>s&members=%<members>s&threshold=%<threshold>s',
|
17
|
-
limit: limit,
|
18
|
-
offset: offset,
|
19
|
-
state: state,
|
20
|
-
members: members,
|
21
|
-
threshold: threshold
|
22
|
-
)
|
23
|
-
access_token ||= access_token('GET', path, '')
|
24
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
25
|
-
client.get(path, headers: { 'Authorization': authorization })
|
26
|
-
end
|
27
|
-
alias multisigs outputs
|
28
|
-
alias multisig_outputs outputs
|
29
|
-
|
30
|
-
def create_output(receivers:, index:, hint: nil, access_token: nil)
|
31
|
-
path = '/outputs'
|
32
|
-
payload = {
|
33
|
-
receivers: receivers,
|
34
|
-
index: index,
|
35
|
-
hint: hint
|
36
|
-
}
|
37
|
-
access_token ||= access_token('POST', path, payload.to_json)
|
38
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
39
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
40
|
-
end
|
41
|
-
|
42
|
-
MULTISIG_REQUEST_ACTIONS = %i[sign unlock].freeze
|
43
|
-
def create_multisig_request(action, raw, access_token: nil)
|
44
|
-
raise ArgumentError, "request action is limited in #{MULTISIG_REQUEST_ACTIONS.join(', ')}" unless MULTISIG_REQUEST_ACTIONS.include? action.to_sym
|
45
|
-
|
46
|
-
path = '/multisigs/requests'
|
47
|
-
payload = {
|
48
|
-
action: action,
|
6
|
+
def create_safe_multisig_request(request_id, raw, access_token: nil)
|
7
|
+
path = '/safe/multisigs'
|
8
|
+
payload = [{
|
9
|
+
request_id: request_id,
|
49
10
|
raw: raw
|
50
|
-
}
|
51
|
-
access_token ||= access_token('POST', path, payload.to_json)
|
52
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
53
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
54
|
-
end
|
11
|
+
}]
|
55
12
|
|
56
|
-
|
57
|
-
def create_sign_multisig_request(raw, access_token: nil)
|
58
|
-
create_multisig_request 'sign', raw, access_token: access_token
|
59
|
-
end
|
60
|
-
|
61
|
-
# transfer from the multisig address
|
62
|
-
# create a request for unlock a multi-sign
|
63
|
-
def create_unlock_multisig_request(raw, access_token: nil)
|
64
|
-
create_multisig_request 'unlock', raw, access_token: access_token
|
65
|
-
end
|
66
|
-
|
67
|
-
def sign_multisig_request(request_id, pin)
|
68
|
-
path = format('/multisigs/requests/%<request_id>s/sign', request_id: request_id)
|
69
|
-
payload = {
|
70
|
-
pin: encrypt_pin(pin)
|
71
|
-
}
|
72
|
-
access_token ||= access_token('POST', path, payload.to_json)
|
73
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
74
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
75
|
-
end
|
76
|
-
|
77
|
-
def unlock_multisig_request(request_id, pin)
|
78
|
-
path = format('/multisigs/requests/%<request_id>s/unlock', request_id: request_id)
|
79
|
-
payload = {
|
80
|
-
pin: encrypt_pin(pin)
|
81
|
-
}
|
82
|
-
access_token ||= access_token('POST', path, payload.to_json)
|
83
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
84
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
13
|
+
client.post path, *payload
|
85
14
|
end
|
86
15
|
|
87
|
-
def
|
88
|
-
path = format('/multisigs
|
89
|
-
payload = {
|
90
|
-
pin: encrypt_pin(pin)
|
91
|
-
}
|
92
|
-
access_token ||= access_token('POST', path, payload.to_json)
|
93
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
94
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
95
|
-
end
|
16
|
+
def sign_safe_multisig_request(request_id, raw, access_token: nil)
|
17
|
+
path = format('/safe/multisigs/%<request_id>s/sign', request_id:)
|
96
18
|
|
97
|
-
# pay to the multisig address
|
98
|
-
# used for create multisig payment code_id
|
99
|
-
def create_payment(**kwargs)
|
100
|
-
path = '/payments'
|
101
19
|
payload = {
|
102
|
-
|
103
|
-
amount: format('%.8f', kwargs[:amount].to_d.to_r),
|
104
|
-
trace_id: kwargs[:trace_id] || SecureRandom.uuid,
|
105
|
-
memo: kwargs[:memo],
|
106
|
-
opponent_multisig: {
|
107
|
-
receivers: kwargs[:receivers],
|
108
|
-
threshold: kwargs[:threshold]
|
109
|
-
}
|
20
|
+
raw:
|
110
21
|
}
|
111
|
-
access_token = kwargs[:access_token]
|
112
|
-
access_token ||= access_token('POST', path, payload.to_json)
|
113
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
114
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
115
|
-
end
|
116
|
-
alias create_multisig_payment create_payment
|
117
22
|
|
118
|
-
|
119
|
-
path = format('/codes/%<code_id>s', code_id: code_id)
|
120
|
-
access_token ||= access_token('GET', path, '')
|
121
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
122
|
-
client.get(path, headers: { 'Authorization': authorization })
|
23
|
+
client.post path, **payload
|
123
24
|
end
|
124
25
|
|
125
|
-
def
|
126
|
-
|
127
|
-
s = s.length == 1 ? "0#{s}" : s
|
128
|
-
raise 'NVALID THRESHOLD' if s.length > 2
|
26
|
+
def unlock_safe_multisig_request(request_id, access_token: nil)
|
27
|
+
path = format('/safe/multisigs/%<request_id>s/unlock', request_id:)
|
129
28
|
|
130
|
-
|
29
|
+
client.post path, access_token: access_token
|
131
30
|
end
|
132
31
|
|
133
|
-
|
134
|
-
|
135
|
-
# senders: [ uuid ],
|
136
|
-
# senders_threshold: integer,
|
137
|
-
# receivers: [ uuid ],
|
138
|
-
# receivers_threshold: integer,
|
139
|
-
# asset_id: uuid,
|
140
|
-
# amount: string / float,
|
141
|
-
# memo: string,
|
142
|
-
# }
|
143
|
-
RAW_TRANSACTION_ARGUMENTS = %i[utxos senders senders_threshold receivers receivers_threshold amount].freeze
|
144
|
-
def build_raw_transaction(**kwargs)
|
145
|
-
raise ArgumentError, "#{RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build raw transaction" unless RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
146
|
-
|
147
|
-
senders = kwargs[:senders]
|
148
|
-
senders_threshold = kwargs[:senders_threshold]
|
149
|
-
receivers = kwargs[:receivers]
|
150
|
-
receivers_threshold = kwargs[:receivers_threshold]
|
151
|
-
amount = kwargs[:amount]
|
152
|
-
asset_id = kwargs[:asset_id]
|
153
|
-
asset_mixin_id = kwargs[:asset_mixin_id]
|
154
|
-
utxos = kwargs[:utxos]
|
155
|
-
memo = kwargs[:memo]
|
156
|
-
extra = kwargs[:extra]
|
157
|
-
access_token = kwargs[:access_token]
|
158
|
-
outputs = kwargs[:outputs] || []
|
159
|
-
hint = kwargs[:hint]
|
160
|
-
|
161
|
-
raise 'access_token required!' if access_token.nil? && !senders.include?(client_id)
|
162
|
-
|
163
|
-
amount = amount.to_d.round(8)
|
164
|
-
input_amount = utxos.map(
|
165
|
-
&lambda { |utxo|
|
166
|
-
utxo['amount'].to_d
|
167
|
-
}
|
168
|
-
).sum
|
169
|
-
|
170
|
-
if input_amount < amount
|
171
|
-
raise format(
|
172
|
-
'not enough amount! %<input_amount>s < %<amount>s',
|
173
|
-
input_amount: input_amount,
|
174
|
-
amount: amount
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
inputs = utxos.map(
|
179
|
-
&lambda { |utx|
|
180
|
-
{
|
181
|
-
'hash' => utx['transaction_hash'],
|
182
|
-
'index' => utx['output_index']
|
183
|
-
}
|
184
|
-
}
|
185
|
-
)
|
186
|
-
|
187
|
-
if outputs.empty?
|
188
|
-
receivers_threshold = 1 if receivers.size == 1
|
189
|
-
output0 = build_output(
|
190
|
-
receivers: receivers,
|
191
|
-
index: 0,
|
192
|
-
amount: amount,
|
193
|
-
threshold: receivers_threshold,
|
194
|
-
hint: hint
|
195
|
-
)
|
196
|
-
outputs.push output0
|
197
|
-
|
198
|
-
if input_amount > amount
|
199
|
-
output1 = build_output(
|
200
|
-
receivers: senders,
|
201
|
-
index: 1,
|
202
|
-
amount: input_amount - amount,
|
203
|
-
threshold: senders_threshold,
|
204
|
-
hint: hint
|
205
|
-
)
|
206
|
-
outputs.push output1
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
extra ||= Digest.hexencode(memo.to_s.slice(0, 140))
|
211
|
-
asset = asset_mixin_id || SHA3::Digest::SHA256.hexdigest(asset_id)
|
212
|
-
tx = {
|
213
|
-
version: 2,
|
214
|
-
asset: asset,
|
215
|
-
inputs: inputs,
|
216
|
-
outputs: outputs,
|
217
|
-
extra: extra
|
218
|
-
}
|
219
|
-
|
220
|
-
tx.to_json
|
221
|
-
end
|
222
|
-
|
223
|
-
def build_output(receivers:, index:, amount:, threshold:, hint: nil)
|
224
|
-
_output = create_output receivers: receivers, index: index, hint: hint
|
225
|
-
{
|
226
|
-
amount: format('%.8f', amount.to_d.to_r),
|
227
|
-
script: build_threshold_script(threshold),
|
228
|
-
mask: _output['mask'],
|
229
|
-
keys: _output['keys']
|
230
|
-
}
|
231
|
-
end
|
232
|
-
|
233
|
-
def str_to_bin(str)
|
234
|
-
return if str.nil?
|
235
|
-
|
236
|
-
str.scan(/../).map(&:hex).pack('c*')
|
237
|
-
end
|
32
|
+
def safe_multisig_request(request_id, access_token: nil)
|
33
|
+
path = format('/safe/multisigs/%<request_id>s', request_id:)
|
238
34
|
|
239
|
-
|
240
|
-
MixinBot::Utils.generate_trace_from_hash hash, output_index
|
35
|
+
client.get path, access_token:
|
241
36
|
end
|
242
37
|
end
|
243
38
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Output
|
6
|
+
def build_threshold_script(threshold)
|
7
|
+
s = threshold.to_s(16)
|
8
|
+
s = "0#{s}" if s.length == 1
|
9
|
+
raise 'NVALID THRESHOLD' if s.length > 2
|
10
|
+
|
11
|
+
"fffe#{s}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def safe_outputs(**kwargs)
|
15
|
+
asset = kwargs[:asset] || kwargs[:asset_id]
|
16
|
+
limit = kwargs[:limit] || 500
|
17
|
+
offset = kwargs[:offset] || ''
|
18
|
+
state = kwargs[:state] || ''
|
19
|
+
access_token = kwargs[:access_token]
|
20
|
+
order = kwargs[:order] || 'ASC'
|
21
|
+
members = kwargs[:members] || [config.app_id]
|
22
|
+
threshold = kwargs[:threshold] || members.length
|
23
|
+
|
24
|
+
members_hash = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
|
25
|
+
|
26
|
+
path = '/safe/outputs'
|
27
|
+
params = {
|
28
|
+
asset:,
|
29
|
+
limit:,
|
30
|
+
offset:,
|
31
|
+
state:,
|
32
|
+
members: members_hash,
|
33
|
+
threshold:,
|
34
|
+
order:
|
35
|
+
}.compact
|
36
|
+
|
37
|
+
client.get path, **params, access_token:
|
38
|
+
end
|
39
|
+
|
40
|
+
def safe_output(id, access_token: nil)
|
41
|
+
path = format('/safe/outputs/%<id>s', id:)
|
42
|
+
client.get path, access_token:
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -3,28 +3,17 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Payment
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
memo: options[:memo]
|
14
|
-
)
|
15
|
-
end
|
6
|
+
def safe_pay_url(**kwargs)
|
7
|
+
members = kwargs[:members]
|
8
|
+
threshold = kwargs[:threshold]
|
9
|
+
asset_id = kwargs[:asset_id]
|
10
|
+
amount = kwargs[:amount]
|
11
|
+
memo = kwargs[:memo] || ''
|
12
|
+
trace_id = kwargs[:trace_id] || SecureRandom.uuid
|
16
13
|
|
17
|
-
|
18
|
-
def verify_payment(options)
|
19
|
-
path = 'payments'
|
20
|
-
payload = {
|
21
|
-
asset_id: options[:asset_id],
|
22
|
-
opponent_id: options[:opponent_id],
|
23
|
-
amount: options[:amount].to_s,
|
24
|
-
trace_id: options[:trace]
|
25
|
-
}
|
14
|
+
mix_address = MixinBot.utils.build_mix_address(members, threshold)
|
26
15
|
|
27
|
-
|
16
|
+
"https://mixin.one/pay/#{mix_address}?amount=#{amount}&asset=#{asset_id}&memo=#{memo}&trace=#{trace_id}"
|
28
17
|
end
|
29
18
|
end
|
30
19
|
end
|
data/lib/mixin_bot/api/pin.rb
CHANGED
@@ -4,89 +4,81 @@ module MixinBot
|
|
4
4
|
class API
|
5
5
|
module Pin
|
6
6
|
# https://developers.mixin.one/api/alpha-mixin-network/verify-pin/
|
7
|
-
def verify_pin(
|
7
|
+
def verify_pin(pin = nil)
|
8
|
+
pin ||= MixinBot.config.pin
|
9
|
+
raise ArgumentError, 'invalid pin' if pin.blank?
|
10
|
+
|
8
11
|
path = '/pin/verify'
|
9
|
-
payload = {
|
10
|
-
pin: encrypt_pin(pin_code)
|
11
|
-
}
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
payload =
|
14
|
+
if pin.length > 6
|
15
|
+
timestamp = (Time.now.utc.to_f * 1e9).to_i
|
16
|
+
pin_base64 = encrypt_tip_pin pin, 'TIP:VERIFY:', timestamp.to_s.rjust(32, '0')
|
17
|
+
|
18
|
+
{
|
19
|
+
pin_base64:,
|
20
|
+
timestamp:
|
21
|
+
}
|
22
|
+
else
|
23
|
+
{
|
24
|
+
pin: MixinBot.utils.encrypt_pin(pin)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
client.post path, **payload
|
16
29
|
end
|
17
30
|
|
18
31
|
# https://developers.mixin.one/api/alpha-mixin-network/create-pin/
|
19
|
-
def update_pin(
|
32
|
+
def update_pin(pin:, old_pin: nil)
|
33
|
+
old_pin ||= MixinBot.config.pin
|
34
|
+
raise ArgumentError, 'invalid old pin' if old_pin.present? && old_pin.length != 6
|
35
|
+
|
20
36
|
path = '/pin/update'
|
21
37
|
encrypted_old_pin = old_pin.nil? ? '' : encrypt_pin(old_pin, iterator: Time.now.utc.to_i)
|
38
|
+
|
22
39
|
encrypted_pin = encrypt_pin(pin, iterator: Time.now.utc.to_i + 1)
|
23
40
|
payload = {
|
24
|
-
|
25
|
-
|
41
|
+
old_pin_base64: encrypted_old_pin,
|
42
|
+
pin_base64: encrypted_pin
|
26
43
|
}
|
27
44
|
|
28
|
-
|
29
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
30
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
45
|
+
client.post path, **payload
|
31
46
|
end
|
32
47
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
decoded = decode_cipher.update(cipher)
|
44
|
-
decoded[0..5]
|
48
|
+
def prepare_tip_key(counter = 0)
|
49
|
+
ed25519_key = JOSE::JWA::Ed25519.keypair
|
50
|
+
|
51
|
+
private_key = ed25519_key[1].unpack1('H*')
|
52
|
+
public_key = (ed25519_key[0].bytes + MixinBot::Utils.encode_uint_64(counter + 1)).pack('c*').unpack1('H*')
|
53
|
+
|
54
|
+
{
|
55
|
+
private_key:,
|
56
|
+
public_key:
|
57
|
+
}
|
45
58
|
end
|
46
59
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
iterator ||= Time.now.utc.to_i
|
51
|
-
tszero = iterator % 0x100
|
52
|
-
tsone = (iterator % 0x10000) >> 8
|
53
|
-
tstwo = (iterator % 0x1000000) >> 16
|
54
|
-
tsthree = (iterator % 0x100000000) >> 24
|
55
|
-
tsstring = "#{tszero.chr}#{tsone.chr}#{tstwo.chr}#{tsthree.chr}\u0000\u0000\u0000\u0000"
|
56
|
-
encrypt_content = pin_code + tsstring + tsstring
|
57
|
-
pad_count = 16 - encrypt_content.length % 16
|
58
|
-
padded_content =
|
59
|
-
if pad_count.positive?
|
60
|
-
encrypt_content + pad_count.chr * pad_count
|
61
|
-
else
|
62
|
-
encrypt_content
|
63
|
-
end
|
60
|
+
def encrypt_pin(pin, iterator: nil)
|
61
|
+
MixinBot.utils.encrypt_pin(pin, iterator:, shared_key: generate_shared_key_with_server)
|
62
|
+
end
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
iv = OpenSSL::Cipher.new(alg).random_iv
|
68
|
-
aes.encrypt
|
69
|
-
aes.key = _generate_aes_key
|
70
|
-
aes.iv = iv
|
71
|
-
cipher = aes.update(padded_content)
|
72
|
-
msg = iv + cipher
|
73
|
-
Base64.strict_encode64 msg
|
64
|
+
def decrypt_pin(msg)
|
65
|
+
MixinBot.utils.decrypt_pin msg, shared_key: generate_shared_key_with_server
|
74
66
|
end
|
75
|
-
end
|
76
67
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
68
|
+
def generate_shared_key_with_server
|
69
|
+
if config.server_public_key.size == 32
|
70
|
+
JOSE::JWA::X25519.x25519(
|
71
|
+
config.session_private_key_curve25519,
|
72
|
+
config.server_public_key_curve25519
|
73
|
+
)
|
74
|
+
else
|
75
|
+
JOSE::JWA::PKCS1.rsaes_oaep_decrypt(
|
76
|
+
'SHA256',
|
77
|
+
config.server_public_key,
|
78
|
+
OpenSSL::PKey::RSA.new(config.session_private_key),
|
79
|
+
session_id
|
80
|
+
)
|
81
|
+
end
|
90
82
|
end
|
91
83
|
end
|
92
84
|
end
|
data/lib/mixin_bot/api/rpc.rb
CHANGED
@@ -6,18 +6,16 @@ module MixinBot
|
|
6
6
|
def rpc_proxy(method, params = [], access_token: nil)
|
7
7
|
path = '/external/proxy'
|
8
8
|
payload = {
|
9
|
-
method
|
10
|
-
params:
|
9
|
+
method:,
|
10
|
+
params:
|
11
11
|
}
|
12
12
|
|
13
|
-
|
14
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
15
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
13
|
+
client.post path, **payload, access_token:
|
16
14
|
end
|
17
15
|
|
18
16
|
# send a signed transaction to main net
|
19
17
|
def send_raw_transaction(raw, access_token: nil)
|
20
|
-
rpc_proxy('sendrawtransaction', [raw], access_token:
|
18
|
+
rpc_proxy('sendrawtransaction', [raw], access_token:)
|
21
19
|
end
|
22
20
|
|
23
21
|
def get_transaction(hash, access_token: nil)
|
@@ -25,23 +23,23 @@ module MixinBot
|
|
25
23
|
end
|
26
24
|
|
27
25
|
def get_utxo(hash, index = 0, access_token: nil)
|
28
|
-
rpc_proxy 'getutxo', [hash, index], access_token:
|
26
|
+
rpc_proxy 'getutxo', [hash, index], access_token:
|
29
27
|
end
|
30
28
|
|
31
29
|
def get_snapshot(hash, access_token: nil)
|
32
|
-
rpc_proxy 'getsnapshot', [hash], access_token:
|
30
|
+
rpc_proxy 'getsnapshot', [hash], access_token:
|
33
31
|
end
|
34
32
|
|
35
33
|
def list_snapshots(offset = 0, count = 10, sig = false, tx = false, access_token: nil)
|
36
|
-
rpc_proxy 'listsnapshots', [offset, count, sig, tx], access_token:
|
34
|
+
rpc_proxy 'listsnapshots', [offset, count, sig, tx], access_token:
|
37
35
|
end
|
38
36
|
|
39
37
|
def list_mint_works(offset = 0, access_token: nil)
|
40
|
-
rpc_proxy 'listmintworks', [offset], access_token:
|
38
|
+
rpc_proxy 'listmintworks', [offset], access_token:
|
41
39
|
end
|
42
40
|
|
43
41
|
def list_mint_distributions(offset = 0, count = 10, tx = false, access_token: nil)
|
44
|
-
rpc_proxy 'listmintdistributions', [offset, count, tx], access_token:
|
42
|
+
rpc_proxy 'listmintdistributions', [offset, count, tx], access_token:
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|