mixin_bot 1.4.0 → 2.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/AGENTS.md +75 -0
- data/API_COVERAGE.md +143 -0
- data/CHANGELOG.md +112 -0
- data/README.md +375 -0
- data/docs/agent/cli.md +149 -0
- data/docs/agent/cookbook.md +152 -0
- data/examples/blaze.rb +43 -0
- data/examples/config.yml.example +21 -0
- data/lib/mixin_bot/address.rb +43 -3
- data/lib/mixin_bot/api/app.rb +7 -0
- data/lib/mixin_bot/api/asset.rb +114 -3
- data/lib/mixin_bot/api/auth.rb +19 -10
- data/lib/mixin_bot/api/blaze.rb +81 -0
- data/lib/mixin_bot/api/chain.rb +94 -0
- data/lib/mixin_bot/api/code.rb +16 -0
- data/lib/mixin_bot/api/computer_api.rb +60 -0
- data/lib/mixin_bot/api/conversation.rb +7 -1
- data/lib/mixin_bot/api/deposit.rb +12 -0
- data/lib/mixin_bot/api/encrypted_message.rb +1 -1
- data/lib/mixin_bot/api/fiat.rb +12 -0
- data/lib/mixin_bot/api/inscription.rb +2 -2
- data/lib/mixin_bot/api/legacy_collectible.rb +26 -27
- data/lib/mixin_bot/api/legacy_multisig.rb +20 -21
- data/lib/mixin_bot/api/legacy_output.rb +10 -3
- data/lib/mixin_bot/api/legacy_payment.rb +2 -0
- data/lib/mixin_bot/api/legacy_snapshot.rb +16 -0
- data/lib/mixin_bot/api/legacy_transaction.rb +28 -13
- data/lib/mixin_bot/api/legacy_transfer.rb +11 -8
- data/lib/mixin_bot/api/legacy_user.rb +51 -0
- data/lib/mixin_bot/api/me.rb +99 -3
- data/lib/mixin_bot/api/message.rb +18 -27
- data/lib/mixin_bot/api/multisig.rb +19 -0
- data/lib/mixin_bot/api/network.rb +17 -0
- data/lib/mixin_bot/api/network_asset.rb +27 -0
- data/lib/mixin_bot/api/output.rb +1 -1
- data/lib/mixin_bot/api/pin.rb +16 -3
- data/lib/mixin_bot/api/pin_payload.rb +26 -0
- data/lib/mixin_bot/api/session.rb +14 -0
- data/lib/mixin_bot/api/snapshot.rb +6 -0
- data/lib/mixin_bot/api/tip.rb +74 -1
- data/lib/mixin_bot/api/transaction.rb +106 -17
- data/lib/mixin_bot/api/transfer.rb +141 -14
- data/lib/mixin_bot/api/turn.rb +12 -0
- data/lib/mixin_bot/api/user.rb +148 -45
- data/lib/mixin_bot/api/withdraw.rb +24 -23
- data/lib/mixin_bot/api.rb +248 -3
- data/lib/mixin_bot/bot_auth.rb +71 -0
- data/lib/mixin_bot/cli/api.rb +224 -143
- data/lib/mixin_bot/cli/base.rb +77 -0
- data/lib/mixin_bot/cli/call.rb +71 -0
- data/lib/mixin_bot/cli/errors.rb +56 -0
- data/lib/mixin_bot/cli/node.rb +9 -2
- data/lib/mixin_bot/cli/output.rb +196 -0
- data/lib/mixin_bot/cli/schema.rb +274 -0
- data/lib/mixin_bot/cli/schema_command.rb +21 -0
- data/lib/mixin_bot/cli/utils.rb +114 -18
- data/lib/mixin_bot/cli.rb +124 -48
- data/lib/mixin_bot/client/error_mapper.rb +40 -0
- data/lib/mixin_bot/client.rb +94 -64
- data/lib/mixin_bot/computer.rb +132 -0
- data/lib/mixin_bot/configuration.rb +108 -1
- data/lib/mixin_bot/errors.rb +102 -0
- data/lib/mixin_bot/models/address.rb +11 -0
- data/lib/mixin_bot/models/api_envelope.rb +67 -0
- data/lib/mixin_bot/models/asset.rb +11 -0
- data/lib/mixin_bot/models/ghost_keys.rb +14 -0
- data/lib/mixin_bot/models/output.rb +11 -0
- data/lib/mixin_bot/models/safe_multisig_request.rb +11 -0
- data/lib/mixin_bot/models/sequencer_transaction_request.rb +11 -0
- data/lib/mixin_bot/models/user.rb +11 -0
- data/lib/mixin_bot/models.rb +10 -0
- data/lib/mixin_bot/monitor.rb +77 -0
- data/lib/mixin_bot/transaction/buffer.rb +34 -0
- data/lib/mixin_bot/transaction/decoder.rb +227 -0
- data/lib/mixin_bot/transaction/encoder.rb +255 -0
- data/lib/mixin_bot/transaction.rb +6 -475
- data/lib/mixin_bot/url_scheme.rb +63 -0
- data/lib/mixin_bot/utils/address.rb +17 -80
- data/lib/mixin_bot/utils/crypto.rb +173 -1
- data/lib/mixin_bot/utils/decoder.rb +1 -1
- data/lib/mixin_bot/utils/encoder.rb +13 -0
- data/lib/mixin_bot/utils.rb +45 -0
- data/lib/mixin_bot/uuid.rb +78 -1
- data/lib/mixin_bot/version.rb +11 -1
- data/lib/mixin_bot.rb +172 -18
- data/lib/mvm/bridge.rb +46 -0
- data/lib/mvm/client.rb +60 -0
- data/lib/mvm/nft.rb +4 -2
- data/lib/mvm/registry.rb +2 -1
- data/lib/mvm.rb +93 -0
- data/lib/tasks/api_coverage.rake +20 -0
- data/llms.txt +29 -0
- metadata +77 -9
|
@@ -29,127 +29,11 @@ module MixinBot
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def encode
|
|
32
|
-
|
|
33
|
-
raise InvalidTransactionFormatError, 'inputs is required' if inputs.blank?
|
|
34
|
-
raise InvalidTransactionFormatError, 'outputs is required' if outputs.blank?
|
|
35
|
-
|
|
36
|
-
bytes = []
|
|
37
|
-
|
|
38
|
-
# magic number
|
|
39
|
-
bytes += MAGIC
|
|
40
|
-
|
|
41
|
-
# version
|
|
42
|
-
bytes += [0, version]
|
|
43
|
-
|
|
44
|
-
# asset
|
|
45
|
-
bytes += [asset].pack('H*').bytes
|
|
46
|
-
|
|
47
|
-
# inputs
|
|
48
|
-
bytes += encode_inputs
|
|
49
|
-
|
|
50
|
-
# output
|
|
51
|
-
bytes += encode_outputs
|
|
52
|
-
|
|
53
|
-
# placeholder for `references`
|
|
54
|
-
bytes += encode_references if version >= REFERENCES_TX_VERSION
|
|
55
|
-
|
|
56
|
-
# extra
|
|
57
|
-
extra_bytes = extra.bytes
|
|
58
|
-
raise InvalidTransactionFormatError, 'extra is too long' if extra_bytes.size > MAX_EXTRA_SIZE
|
|
59
|
-
|
|
60
|
-
bytes += MixinBot.utils.encode_uint32 extra_bytes.size
|
|
61
|
-
bytes += extra_bytes
|
|
62
|
-
|
|
63
|
-
# aggregated
|
|
64
|
-
bytes += if aggregated.nil?
|
|
65
|
-
# signatures
|
|
66
|
-
encode_signatures
|
|
67
|
-
else
|
|
68
|
-
encode_aggregated_signature
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
@hash = SHA3::Digest::SHA256.hexdigest bytes.pack('C*')
|
|
72
|
-
@hex = bytes.pack('C*').unpack1('H*')
|
|
73
|
-
|
|
74
|
-
self
|
|
32
|
+
Encoder.new(self).encode
|
|
75
33
|
end
|
|
76
34
|
|
|
77
35
|
def decode
|
|
78
|
-
|
|
79
|
-
@hash = SHA3::Digest::SHA256.hexdigest @bytes.pack('C*')
|
|
80
|
-
|
|
81
|
-
magic = @bytes.shift(2)
|
|
82
|
-
raise ArgumentError, 'Not valid raw' unless magic == MAGIC
|
|
83
|
-
|
|
84
|
-
_version = @bytes.shift(2)
|
|
85
|
-
@version = MixinBot.utils.decode_int _version
|
|
86
|
-
|
|
87
|
-
asset = @bytes.shift(32)
|
|
88
|
-
@asset = asset.pack('C*').unpack1('H*')
|
|
89
|
-
|
|
90
|
-
# read inputs
|
|
91
|
-
decode_inputs
|
|
92
|
-
|
|
93
|
-
# read outputs
|
|
94
|
-
decode_outputs
|
|
95
|
-
|
|
96
|
-
# read references
|
|
97
|
-
decode_references if version >= REFERENCES_TX_VERSION
|
|
98
|
-
|
|
99
|
-
# read extra
|
|
100
|
-
# unsigned 32 endian for extra size
|
|
101
|
-
extra_size = MixinBot.utils.decode_uint32 @bytes.shift(4)
|
|
102
|
-
@extra = @bytes.shift(extra_size).pack('C*')
|
|
103
|
-
|
|
104
|
-
num = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
105
|
-
if num == MAX_ENCODE_INT
|
|
106
|
-
# aggregated
|
|
107
|
-
@aggregated = {}
|
|
108
|
-
|
|
109
|
-
raise ArgumentError, 'invalid aggregated' unless MixinBot.utils.decode_uint16(@bytes.shift(2)) == AGGREGATED_SIGNATURE_PREFIX
|
|
110
|
-
|
|
111
|
-
@aggregated['signature'] = @bytes.shift(64).pack('C*').unpack1('H*')
|
|
112
|
-
|
|
113
|
-
byte = @bytes.shift
|
|
114
|
-
case byte
|
|
115
|
-
when AGGREGATED_SIGNATURE_ORDINAY_MASK.first
|
|
116
|
-
@aggregated['signers'] = []
|
|
117
|
-
masks_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
118
|
-
masks = @bytes.shift(masks_size)
|
|
119
|
-
masks = Array(masks)
|
|
120
|
-
|
|
121
|
-
masks.each_with_index do |mask, i|
|
|
122
|
-
8.times do |j|
|
|
123
|
-
k = 1 << j
|
|
124
|
-
aggregated['signers'].push((i * 8) + j) if mask & k == k
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
when AGGREGATED_SIGNATURE_SPARSE_MASK.first
|
|
128
|
-
signers_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
129
|
-
return if signers_size.zero?
|
|
130
|
-
|
|
131
|
-
aggregated['signers'] = []
|
|
132
|
-
signers_size.times do
|
|
133
|
-
aggregated['signers'].push MixinBot.utils.decode_uint16(@bytes.shift(2))
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
elsif num.present? && num.positive? && @bytes.size.positive?
|
|
137
|
-
@signatures = []
|
|
138
|
-
num.times do
|
|
139
|
-
signature = {}
|
|
140
|
-
|
|
141
|
-
keys_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
142
|
-
|
|
143
|
-
keys_size.times do
|
|
144
|
-
index = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
145
|
-
signature[index] = @bytes.shift(64).pack('C*').unpack1('H*')
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
@signatures << signature
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
self
|
|
36
|
+
Decoder.new(self).decode
|
|
153
37
|
end
|
|
154
38
|
|
|
155
39
|
def to_h
|
|
@@ -165,362 +49,9 @@ module MixinBot
|
|
|
165
49
|
references:
|
|
166
50
|
}.compact
|
|
167
51
|
end
|
|
168
|
-
|
|
169
|
-
private
|
|
170
|
-
|
|
171
|
-
def encode_inputs
|
|
172
|
-
bytes = []
|
|
173
|
-
|
|
174
|
-
bytes += MixinBot.utils.encode_uint16(inputs.size)
|
|
175
|
-
|
|
176
|
-
inputs.each do |input|
|
|
177
|
-
bytes += [input['hash']].pack('H*').bytes
|
|
178
|
-
bytes += MixinBot.utils.encode_uint16(input['index'])
|
|
179
|
-
|
|
180
|
-
# genesis
|
|
181
|
-
genesis = input['genesis'] || ''
|
|
182
|
-
if genesis.empty?
|
|
183
|
-
bytes += NULL_BYTES
|
|
184
|
-
else
|
|
185
|
-
genesis_bytes = [genesis].pack('H*').bytes
|
|
186
|
-
bytes += MixinBot.utils.encode_uint16 genesis_bytes.size
|
|
187
|
-
bytes += genesis_bytes
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# deposit
|
|
191
|
-
deposit = input['deposit']
|
|
192
|
-
if deposit.nil?
|
|
193
|
-
bytes += NULL_BYTES
|
|
194
|
-
else
|
|
195
|
-
bytes += MAGIC
|
|
196
|
-
bytes += [deposit['chain']].pack('H*').bytes
|
|
197
|
-
|
|
198
|
-
asset_bytes = [deposit['asset']].pack('H*')
|
|
199
|
-
bytes += MixinBot.utils.encode_uint16 asset_bytes.size
|
|
200
|
-
bytes += asset_bytes
|
|
201
|
-
|
|
202
|
-
transaction_bytes = [deposit['transaction']].pack('H*')
|
|
203
|
-
bytes += MixinBot.utils.encode_uint16 transaction_bytes.size
|
|
204
|
-
bytes += transaction_bytes
|
|
205
|
-
|
|
206
|
-
bytes += MixinBot.utils.encode_uint64 deposit['index']
|
|
207
|
-
|
|
208
|
-
amount_bytes = MixinBot.utils.bytes_of deposit['amount']
|
|
209
|
-
bytes += MixinBot.utils.encode_uint16 amount_bytes.size
|
|
210
|
-
bytes += amount_bytes
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# mint
|
|
214
|
-
mint = input['mint']
|
|
215
|
-
if mint.nil?
|
|
216
|
-
bytes += NULL_BYTES
|
|
217
|
-
else
|
|
218
|
-
bytes += MAGIC
|
|
219
|
-
|
|
220
|
-
# group
|
|
221
|
-
group = mint['group'] || ''
|
|
222
|
-
if group.empty?
|
|
223
|
-
bytes += MixinBot.utils.encode_uint16 NULL_BYTES
|
|
224
|
-
else
|
|
225
|
-
group_bytes = [group].pack('H*')
|
|
226
|
-
bytes += MixinBot.utils.encode_uint16 group_bytes.size
|
|
227
|
-
bytes += group_bytes
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
bytes += MixinBot.utils.encode_uint64 mint['batch']
|
|
231
|
-
|
|
232
|
-
amount_bytes = MixinBot.utils.encode_int mint['amount']
|
|
233
|
-
bytes += MixinBot.utils.encode_uint16 amount_bytes.size
|
|
234
|
-
bytes += amount_bytes
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
bytes
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def encode_outputs
|
|
242
|
-
bytes = []
|
|
243
|
-
|
|
244
|
-
bytes += MixinBot.utils.encode_uint16 outputs.size
|
|
245
|
-
|
|
246
|
-
outputs.each do |output|
|
|
247
|
-
type = output['type'] || 0
|
|
248
|
-
bytes += [0x00, type]
|
|
249
|
-
|
|
250
|
-
# amount
|
|
251
|
-
amount_bytes = MixinBot.utils.encode_int (output['amount'].to_d * 1e8).round
|
|
252
|
-
bytes += MixinBot.utils.encode_uint16 amount_bytes.size
|
|
253
|
-
bytes += amount_bytes
|
|
254
|
-
|
|
255
|
-
# keys
|
|
256
|
-
bytes += MixinBot.utils.encode_uint16 output['keys'].size
|
|
257
|
-
output['keys'].each do |key|
|
|
258
|
-
bytes += [key].pack('H*').bytes
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# mask
|
|
262
|
-
bytes += [output['mask']].pack('H*').bytes
|
|
263
|
-
|
|
264
|
-
# script
|
|
265
|
-
script_bytes = [output['script']].pack('H*').bytes
|
|
266
|
-
bytes += MixinBot.utils.encode_uint16 script_bytes.size
|
|
267
|
-
bytes += script_bytes
|
|
268
|
-
|
|
269
|
-
# withdrawal
|
|
270
|
-
withdrawal = output['withdrawal']
|
|
271
|
-
if withdrawal.nil?
|
|
272
|
-
bytes += NULL_BYTES
|
|
273
|
-
else
|
|
274
|
-
bytes += MAGIC
|
|
275
|
-
|
|
276
|
-
# chain
|
|
277
|
-
bytes += [withdrawal['chain']].pack('H*').bytes
|
|
278
|
-
|
|
279
|
-
# asset
|
|
280
|
-
@asset_bytes = [withdrawal['asset']].pack('H*')
|
|
281
|
-
bytes += MixinBot.utils.encode_uint16 asset_bytes.size
|
|
282
|
-
bytes += asset_bytes
|
|
283
|
-
|
|
284
|
-
# address
|
|
285
|
-
address = withdrawal['address'] || ''
|
|
286
|
-
if address.empty?
|
|
287
|
-
bytes += NULL_BYTES
|
|
288
|
-
else
|
|
289
|
-
address_bytes = [address].pack('H*').bytes
|
|
290
|
-
bytes += MixinBot.utils.encode_uint16 address.size
|
|
291
|
-
bytes += address_bytes
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
# tag
|
|
295
|
-
tag = withdrawal['tag'] || ''
|
|
296
|
-
if tag.empty?
|
|
297
|
-
bytes += NULL_BYTES
|
|
298
|
-
else
|
|
299
|
-
address_bytes = [tag].pack('H*').bytes
|
|
300
|
-
bytes += MixinBot.utils.encode_uint16 tag.size
|
|
301
|
-
bytes += address_bytes
|
|
302
|
-
end
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
bytes
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
def encode_references
|
|
310
|
-
bytes = []
|
|
311
|
-
|
|
312
|
-
bytes += MixinBot.utils.encode_uint16 references.size
|
|
313
|
-
|
|
314
|
-
references.each do |reference|
|
|
315
|
-
bytes += [reference].pack('H*').bytes
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
bytes
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
def encode_aggregated_signature
|
|
322
|
-
bytes = []
|
|
323
|
-
|
|
324
|
-
bytes += MixinBot.utils.encode_uint16 MAX_ENCODE_INT
|
|
325
|
-
bytes += MixinBot.utils.encode_uint16 AGGREGATED_SIGNATURE_PREFIX
|
|
326
|
-
bytes += [aggregated['signature']].pack('H*').bytes
|
|
327
|
-
|
|
328
|
-
signers = aggregated['signers']
|
|
329
|
-
if signers.empty?
|
|
330
|
-
bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
|
|
331
|
-
bytes += NULL_BYTES
|
|
332
|
-
else
|
|
333
|
-
signers.each do |sig, i|
|
|
334
|
-
raise ArgumentError, 'signers not sorted' if i.positive? && sig <= signers[i - 1]
|
|
335
|
-
raise ArgumentError, 'signers not sorted' if sig > MAX_ENCODE_INT
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
max = signers.last
|
|
339
|
-
if ((((max / 8) | 0) + 1) | 0) > aggregated['signature'].size * 2
|
|
340
|
-
bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
|
|
341
|
-
bytes += MixinBot.utils.encode_uint16 aggregated['signers'].size
|
|
342
|
-
signers.map(&->(signer) { bytes += MixinBot.utils.encode_uint16(signer) })
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
masks_bytes = Array.new((max / 8) + 1, 0)
|
|
346
|
-
signers.each do |signer|
|
|
347
|
-
masks[signer / 8] = masks[signer / 8] ^ (1 << (signer % 8))
|
|
348
|
-
end
|
|
349
|
-
bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
|
|
350
|
-
bytes += MixinBot.utils.encode_uint16 masks_bytes.size
|
|
351
|
-
bytes += masks_bytes
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
bytes
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
def encode_signatures
|
|
358
|
-
bytes = []
|
|
359
|
-
|
|
360
|
-
sl =
|
|
361
|
-
if signatures.is_a? Array
|
|
362
|
-
signatures.size
|
|
363
|
-
else
|
|
364
|
-
0
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
raise ArgumentError, 'signatures overflow' if sl == MAX_ENCODE_INT
|
|
368
|
-
|
|
369
|
-
bytes += MixinBot.utils.encode_uint16 sl
|
|
370
|
-
|
|
371
|
-
if sl.positive?
|
|
372
|
-
signatures.each do |signature|
|
|
373
|
-
bytes += MixinBot.utils.encode_uint16 signature.keys.size
|
|
374
|
-
|
|
375
|
-
signature.keys.sort.each do |key|
|
|
376
|
-
signature_bytes = [signature[key]].pack('H*').bytes
|
|
377
|
-
raise ArgumentError, 'Signature should be 64 bytes' if signature_bytes.size != 64
|
|
378
|
-
|
|
379
|
-
bytes += MixinBot.utils.encode_uint16 key
|
|
380
|
-
bytes += signature_bytes
|
|
381
|
-
end
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
bytes
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def decode_inputs
|
|
389
|
-
inputs_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
390
|
-
@inputs = []
|
|
391
|
-
inputs_size.times do
|
|
392
|
-
input = {}
|
|
393
|
-
hash = @bytes.shift(32)
|
|
394
|
-
input['hash'] = hash.pack('C*').unpack1('H*')
|
|
395
|
-
|
|
396
|
-
index = @bytes.shift(2)
|
|
397
|
-
input['index'] = MixinBot.utils.decode_uint16 index
|
|
398
|
-
|
|
399
|
-
if @bytes[...2] == NULL_BYTES
|
|
400
|
-
@bytes.shift 2
|
|
401
|
-
else
|
|
402
|
-
genesis_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
403
|
-
genesis = @bytes.shift genesis_size
|
|
404
|
-
input['genesis'] = genesis.pack('C*').unpack1('H*')
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
if @bytes[...2] == NULL_BYTES
|
|
408
|
-
@bytes.shift 2
|
|
409
|
-
else
|
|
410
|
-
magic = @bytes.shift(2)
|
|
411
|
-
raise ArgumentError, 'Not valid input' unless magic == MAGIC
|
|
412
|
-
|
|
413
|
-
deposit = {}
|
|
414
|
-
deposit['chain'] = @bytes.shift(32).pack('C*').unpack1('H*')
|
|
415
|
-
|
|
416
|
-
asset_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
417
|
-
deposit['asset'] = @bytes.shift(asset_size).unpack1('H*')
|
|
418
|
-
|
|
419
|
-
transaction_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
420
|
-
deposit['transaction'] = @bytes.shift(transaction_size).unpack1('H*')
|
|
421
|
-
|
|
422
|
-
deposit['index'] = MixinBot.utils.decode_uint64 @bytes.shift(8)
|
|
423
|
-
|
|
424
|
-
amount_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
425
|
-
deposit['amount'] = MixinBot.utils.decode_int @bytes.shift(amount_size)
|
|
426
|
-
|
|
427
|
-
input['deposit'] = deposit
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
if @bytes[...2] == NULL_BYTES
|
|
431
|
-
@bytes.shift 2
|
|
432
|
-
else
|
|
433
|
-
magic = @bytes.shift(2)
|
|
434
|
-
raise ArgumentError, 'Not valid input' unless magic == MAGIC
|
|
435
|
-
|
|
436
|
-
mint = {}
|
|
437
|
-
if bytes[...2] == NULL_BYTES
|
|
438
|
-
@bytes.shift 2
|
|
439
|
-
else
|
|
440
|
-
group_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
441
|
-
mint['group'] = @bytes.shift(group_size).unpack1('H*')
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
mint['batch'] = MixinBot.utils.decode_uint64 @bytes.shift(8)
|
|
445
|
-
_amount_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
446
|
-
mint['amount'] = MixinBot.utils.decode_int bytes.shift(_amount_size)
|
|
447
|
-
|
|
448
|
-
input['mint'] = mint
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
@inputs.push input
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
self
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
def decode_outputs
|
|
458
|
-
outputs_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
459
|
-
@outputs = []
|
|
460
|
-
outputs_size.times do
|
|
461
|
-
output = {}
|
|
462
|
-
|
|
463
|
-
@bytes.shift
|
|
464
|
-
type = @bytes.shift
|
|
465
|
-
output['type'] = type
|
|
466
|
-
|
|
467
|
-
amount_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
468
|
-
output['amount'] = format('%.8f', MixinBot.utils.decode_int(@bytes.shift(amount_size)).to_f / 1e8).gsub(/\.?0+$/, '')
|
|
469
|
-
|
|
470
|
-
output['keys'] = []
|
|
471
|
-
keys_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
472
|
-
keys_size.times do
|
|
473
|
-
output['keys'].push @bytes.shift(32).pack('C*').unpack1('H*')
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
output['mask'] = @bytes.shift(32).pack('C*').unpack1('H*')
|
|
477
|
-
|
|
478
|
-
script_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
479
|
-
output['script'] = @bytes.shift(script_size).pack('C*').unpack1('H*')
|
|
480
|
-
|
|
481
|
-
if @bytes[...2] == NULL_BYTES
|
|
482
|
-
@bytes.shift 2
|
|
483
|
-
else
|
|
484
|
-
magic = @bytes.shift(2)
|
|
485
|
-
raise ArgumentError, 'Not valid output' unless magic == MAGIC
|
|
486
|
-
|
|
487
|
-
output['chain'] = @bytes.shift(32).pack('C*').unpack1('H*')
|
|
488
|
-
|
|
489
|
-
asset_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
490
|
-
output['asset'] = @bytes.shift(asset_size).unpack1('H*')
|
|
491
|
-
|
|
492
|
-
if @bytes[...2] == NULL_BYTES
|
|
493
|
-
@bytes.shift 2
|
|
494
|
-
else
|
|
495
|
-
|
|
496
|
-
adderss_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
497
|
-
output['adderss'] = @bytes.shift(adderss_size).pack('C*').unpack1('H*')
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
if @bytes[...2] == NULL_BYTES
|
|
501
|
-
@bytes.shift 2
|
|
502
|
-
else
|
|
503
|
-
|
|
504
|
-
tag_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
505
|
-
output['tag'] = @bytes.shift(tag_size).pack('C*').unpack1('H*')
|
|
506
|
-
end
|
|
507
|
-
end
|
|
508
|
-
|
|
509
|
-
@outputs.push output
|
|
510
|
-
end
|
|
511
|
-
|
|
512
|
-
self
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
def decode_references
|
|
516
|
-
references_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
|
|
517
|
-
@references = []
|
|
518
|
-
|
|
519
|
-
references_size.times do
|
|
520
|
-
@references.push @bytes.shift(32).pack('C*').unpack1('H*')
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
self
|
|
524
|
-
end
|
|
525
52
|
end
|
|
526
53
|
end
|
|
54
|
+
|
|
55
|
+
require_relative 'transaction/buffer'
|
|
56
|
+
require_relative 'transaction/encoder'
|
|
57
|
+
require_relative 'transaction/decoder'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module MixinBot
|
|
6
|
+
# Deep link URL schemes (parity with Go url_scheme.go).
|
|
7
|
+
module UrlScheme
|
|
8
|
+
SCHEME = 'mixin'
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def scheme_users(user_id)
|
|
13
|
+
URI("#{SCHEME}://users/#{user_id}").to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def scheme_transfer(user_id)
|
|
17
|
+
URI("#{SCHEME}://transfer/#{user_id}").to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def scheme_pay(asset_id:, trace_id:, recipient_id:, memo:, amount:)
|
|
21
|
+
q = URI.encode_www_form(
|
|
22
|
+
asset: asset_id,
|
|
23
|
+
trace: trace_id,
|
|
24
|
+
amount: amount.to_s,
|
|
25
|
+
recipient: recipient_id,
|
|
26
|
+
memo: memo.to_s
|
|
27
|
+
)
|
|
28
|
+
"#{SCHEME}://pay?#{q}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def scheme_codes(code_id)
|
|
32
|
+
URI("#{SCHEME}://codes/#{code_id}").to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def scheme_snapshots(snapshot_id: nil, trace_id: nil)
|
|
36
|
+
u = URI("#{SCHEME}://snapshots")
|
|
37
|
+
u.path = "/#{snapshot_id}" if snapshot_id.present?
|
|
38
|
+
u.query = URI.encode_www_form(trace: trace_id) if trace_id.present?
|
|
39
|
+
u.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def scheme_conversations(conversation_id: nil, user_id: nil)
|
|
43
|
+
u = URI("#{SCHEME}://conversations")
|
|
44
|
+
u.path = "/#{conversation_id}" if conversation_id.present?
|
|
45
|
+
u.query = URI.encode_www_form(user: user_id) if user_id.present?
|
|
46
|
+
u.to_s
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def scheme_apps(app_id:, action: nil, params: {})
|
|
50
|
+
u = URI("#{SCHEME}://apps/#{app_id}")
|
|
51
|
+
q = { action: action.presence || 'open' }.merge(params || {})
|
|
52
|
+
u.query = URI.encode_www_form(q)
|
|
53
|
+
u.to_s
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def scheme_send(category:, data: nil, conversation_id: nil)
|
|
57
|
+
q = { category: category.to_s }
|
|
58
|
+
q[:data] = URI.encode_www_form_component(Base64.strict_encode64(data)) if data.present?
|
|
59
|
+
q[:conversation] = conversation_id if conversation_id.present?
|
|
60
|
+
"#{SCHEME}://send?#{URI.encode_www_form(q)}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -3,89 +3,27 @@
|
|
|
3
3
|
module MixinBot
|
|
4
4
|
module Utils
|
|
5
5
|
module Address
|
|
6
|
-
MAIN_ADDRESS_PREFIX =
|
|
7
|
-
MIX_ADDRESS_PREFIX =
|
|
8
|
-
MIX_ADDRESS_VERSION =
|
|
6
|
+
MAIN_ADDRESS_PREFIX = MixinBot::MAIN_ADDRESS_PREFIX
|
|
7
|
+
MIX_ADDRESS_PREFIX = MixinBot::MIX_ADDRESS_PREFIX
|
|
8
|
+
MIX_ADDRESS_VERSION = MixinBot::MIX_ADDRESS_VERSION
|
|
9
9
|
|
|
10
10
|
def build_main_address(public_key)
|
|
11
|
-
|
|
12
|
-
checksum = SHA3::Digest::SHA256.digest msg
|
|
13
|
-
data = public_key + checksum[0...4]
|
|
14
|
-
base58 = Base58.binary_to_base58 data, :bitcoin
|
|
15
|
-
"#{MAIN_ADDRESS_PREFIX}#{base58}"
|
|
11
|
+
MainAddress.new(public_key:).address
|
|
16
12
|
end
|
|
17
13
|
|
|
18
14
|
def parse_main_address(address)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
data = address[MAIN_ADDRESS_PREFIX.length..]
|
|
22
|
-
data = Base58.base58_to_binary data, :bitcoin
|
|
23
|
-
|
|
24
|
-
payload = data[...-4]
|
|
25
|
-
|
|
26
|
-
msg = MAIN_ADDRESS_PREFIX + payload
|
|
27
|
-
checksum = SHA3::Digest::SHA256.digest msg
|
|
28
|
-
|
|
29
|
-
raise ArgumentError, 'invalid address' unless checksum[0...4] == data[-4..]
|
|
30
|
-
|
|
31
|
-
payload
|
|
15
|
+
MainAddress.new(address:).public_key
|
|
32
16
|
end
|
|
33
17
|
|
|
34
18
|
def build_mix_address(members:, threshold:)
|
|
35
|
-
|
|
36
|
-
raise ArgumentError, 'members should not be empty' if members.empty?
|
|
37
|
-
raise ArgumentError, 'members length should less than 256' if members.length > 255
|
|
38
|
-
|
|
39
|
-
# raise ArgumentError, "invalid threshold: #{threshold}" if threshold > members.length
|
|
40
|
-
|
|
41
|
-
prefix = [MIX_ADDRESS_VERSION].pack('C*') + [threshold].pack('C*') + [members.length].pack('C*')
|
|
42
|
-
|
|
43
|
-
members = members.sort
|
|
44
|
-
msg =
|
|
45
|
-
if members.all?(&->(member) { member.start_with? MAIN_ADDRESS_PREFIX })
|
|
46
|
-
members.map(&->(member) { parse_main_address(member) }).join
|
|
47
|
-
elsif members.none?(&->(member) { member.start_with? MAIN_ADDRESS_PREFIX })
|
|
48
|
-
members.map(&->(member) { MixinBot::UUID.new(hex: member).packed }).join
|
|
49
|
-
else
|
|
50
|
-
raise ArgumentError, 'invalid members'
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
checksum = SHA3::Digest::SHA256.digest(MIX_ADDRESS_PREFIX + prefix + msg)
|
|
54
|
-
|
|
55
|
-
data = prefix + msg + checksum[0...4]
|
|
56
|
-
data = Base58.binary_to_base58 data, :bitcoin
|
|
57
|
-
"#{MIX_ADDRESS_PREFIX}#{data}"
|
|
19
|
+
MixAddress.from_members(members:, threshold:).address
|
|
58
20
|
end
|
|
59
21
|
|
|
60
22
|
def parse_mix_address(address)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
data = address[MIX_ADDRESS_PREFIX.length..]
|
|
64
|
-
data = Base58.base58_to_binary data, :bitcoin
|
|
65
|
-
raise ArgumentError, 'invalid address, length invalid' if data.length < 3 + 16 + 4
|
|
66
|
-
|
|
67
|
-
msg = data[...-4]
|
|
68
|
-
checksum = SHA3::Digest::SHA256.digest((MIX_ADDRESS_PREFIX + msg))[0...4]
|
|
69
|
-
|
|
70
|
-
raise ArgumentError, 'invalid address, checksum invalid' unless checksum[0...4] == data[-4..]
|
|
71
|
-
|
|
72
|
-
version = data[0].ord
|
|
73
|
-
raise ArgumentError, 'invalid address, version invalid' unless version == MIX_ADDRESS_VERSION
|
|
74
|
-
|
|
75
|
-
threshold = data[1].ord
|
|
76
|
-
members_count = data[2].ord
|
|
77
|
-
|
|
78
|
-
if data[3...-4].length == members_count * 16
|
|
79
|
-
members = data[3...-4].chars.each_slice(16).map(&:join)
|
|
80
|
-
members = members.map(&->(member) { MixinBot::UUID.new(raw: member).unpacked })
|
|
81
|
-
else
|
|
82
|
-
members = data[3...-4].chars.each_slice(64).map(&:join)
|
|
83
|
-
members = members.map(&->(member) { build_main_address(member) })
|
|
84
|
-
end
|
|
85
|
-
|
|
23
|
+
ma = MixAddress.parse(address)
|
|
86
24
|
{
|
|
87
|
-
members
|
|
88
|
-
threshold:
|
|
25
|
+
members: ma.uuid_members + ma.xin_members,
|
|
26
|
+
threshold: ma.threshold
|
|
89
27
|
}
|
|
90
28
|
end
|
|
91
29
|
|
|
@@ -106,16 +44,15 @@ module MixinBot
|
|
|
106
44
|
end
|
|
107
45
|
|
|
108
46
|
def burning_address
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
digest1 = SHA3::Digest::SHA256.digest seed
|
|
112
|
-
digest2 = SHA3::Digest::SHA256.digest digest1
|
|
113
|
-
src = digest1 + digest2
|
|
114
|
-
|
|
115
|
-
spend_key = MixinBot::Utils.shared_public_key(seed)
|
|
116
|
-
view_key = MixinBot::Utils.shared_public_key(src)
|
|
47
|
+
MainAddress.burning_address.address
|
|
48
|
+
end
|
|
117
49
|
|
|
118
|
-
|
|
50
|
+
##
|
|
51
|
+
# Sorted-member hash used by Safe outputs and legacy collectible listing (Go +HashMembers+).
|
|
52
|
+
#
|
|
53
|
+
def hash_members(ids)
|
|
54
|
+
list = Array(ids).flatten.compact.map(&:to_s).sort
|
|
55
|
+
SHA3::Digest::SHA256.hexdigest(list.join)
|
|
119
56
|
end
|
|
120
57
|
end
|
|
121
58
|
end
|