mixin_bot 0.12.0 → 1.0.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 +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
|