mixin_bot 0.12.1 → 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 +29 -51
- 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 +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 +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
@@ -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
|
@@ -3,45 +3,31 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Snapshot
|
6
|
-
def
|
7
|
-
path =
|
8
|
-
|
9
|
-
limit: options[:limit],
|
10
|
-
offset: options[:offset],
|
11
|
-
asset: options[:asset],
|
12
|
-
order: options[:order]
|
13
|
-
)
|
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 })
|
18
|
-
end
|
19
|
-
alias read_network_snapshots network_snapshots
|
20
|
-
|
21
|
-
def snapshots(**options)
|
22
|
-
path = format(
|
23
|
-
'/snapshots?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&opponent=%<opponent>s&order=%<order>s',
|
6
|
+
def safe_snapshots(**options)
|
7
|
+
path = '/safe/snapshots'
|
8
|
+
params = {
|
24
9
|
limit: options[:limit],
|
25
10
|
offset: options[:offset],
|
26
11
|
asset: options[:asset],
|
27
12
|
opponent: options[:opponent],
|
13
|
+
app: options[:app_id],
|
28
14
|
order: options[:order]
|
29
|
-
|
15
|
+
}
|
30
16
|
|
31
|
-
|
32
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
33
|
-
client.get(path, headers: { 'Authorization': authorization })
|
17
|
+
client.get path, **params
|
34
18
|
end
|
35
|
-
alias read_snapshots snapshots
|
36
19
|
|
37
|
-
def
|
38
|
-
path =
|
20
|
+
def create_safe_snapshot_notification(**kwargs)
|
21
|
+
path = '/safe/snapshots/notifications'
|
22
|
+
|
23
|
+
payload = {
|
24
|
+
transaction_hash: kwargs[:transaction_hash],
|
25
|
+
output_index: kwargs[:output_index],
|
26
|
+
receiver_id: kwargs[:receiver_id]
|
27
|
+
}
|
39
28
|
|
40
|
-
access_token
|
41
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
42
|
-
client.get(path, headers: { 'Authorization': authorization })
|
29
|
+
client.post path, **payload, access_token: kwargs[:access_token]
|
43
30
|
end
|
44
|
-
alias read_network_snapshot network_snapshot
|
45
31
|
end
|
46
32
|
end
|
47
33
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Tip
|
6
|
+
TIP_ACTIONS = %w[
|
7
|
+
TIP:VERIFY:
|
8
|
+
TIP:ADDRESS:ADD:
|
9
|
+
TIP:ADDRESS:REMOVE:
|
10
|
+
TIP:USER:DEACTIVATE:
|
11
|
+
TIP:EMERGENCY:CONTACT:CREATE:
|
12
|
+
TIP:EMERGENCY:CONTACT:READ:
|
13
|
+
TIP:EMERGENCY:CONTACT:REMOVE:
|
14
|
+
TIP:PHONE:NUMBER:UPDATE:
|
15
|
+
TIP:MULTISIG:REQUEST:SIGN:
|
16
|
+
TIP:MULTISIG:REQUEST:UNLOCK:
|
17
|
+
TIP:COLLECTIBLE:REQUEST:SIGN:
|
18
|
+
TIP:COLLECTIBLE:REQUEST:UNLOCK:
|
19
|
+
TIP:TRANSFER:CREATE:
|
20
|
+
TIP:WITHDRAWAL:CREATE:
|
21
|
+
TIP:TRANSACTION:CREATE:
|
22
|
+
TIP:OAUTH:APPROVE:
|
23
|
+
TIP:PROVISIONING:UPDATE:
|
24
|
+
TIP:APP:OWNERSHIP:TRANSFER:
|
25
|
+
SEQUENCER:REGISTER:
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
def encrypt_tip_pin(pin, action, *params)
|
29
|
+
raise ArgumentError, 'invalid action' unless TIP_ACTIONS.include? action
|
30
|
+
|
31
|
+
pin_key = MixinBot.utils.decode_key pin
|
32
|
+
|
33
|
+
msg = action + params.flatten.map(&:to_s).join
|
34
|
+
|
35
|
+
msg = Digest::SHA256.digest(msg) unless action == 'TIP:VERIFY:'
|
36
|
+
|
37
|
+
signature = JOSE::JWA::Ed25519.sign msg, pin_key
|
38
|
+
|
39
|
+
encrypt_pin signature
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|