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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +75 -0
  3. data/API_COVERAGE.md +143 -0
  4. data/CHANGELOG.md +112 -0
  5. data/README.md +375 -0
  6. data/docs/agent/cli.md +149 -0
  7. data/docs/agent/cookbook.md +152 -0
  8. data/examples/blaze.rb +43 -0
  9. data/examples/config.yml.example +21 -0
  10. data/lib/mixin_bot/address.rb +43 -3
  11. data/lib/mixin_bot/api/app.rb +7 -0
  12. data/lib/mixin_bot/api/asset.rb +114 -3
  13. data/lib/mixin_bot/api/auth.rb +19 -10
  14. data/lib/mixin_bot/api/blaze.rb +81 -0
  15. data/lib/mixin_bot/api/chain.rb +94 -0
  16. data/lib/mixin_bot/api/code.rb +16 -0
  17. data/lib/mixin_bot/api/computer_api.rb +60 -0
  18. data/lib/mixin_bot/api/conversation.rb +7 -1
  19. data/lib/mixin_bot/api/deposit.rb +12 -0
  20. data/lib/mixin_bot/api/encrypted_message.rb +1 -1
  21. data/lib/mixin_bot/api/fiat.rb +12 -0
  22. data/lib/mixin_bot/api/inscription.rb +2 -2
  23. data/lib/mixin_bot/api/legacy_collectible.rb +26 -27
  24. data/lib/mixin_bot/api/legacy_multisig.rb +20 -21
  25. data/lib/mixin_bot/api/legacy_output.rb +10 -3
  26. data/lib/mixin_bot/api/legacy_payment.rb +2 -0
  27. data/lib/mixin_bot/api/legacy_snapshot.rb +16 -0
  28. data/lib/mixin_bot/api/legacy_transaction.rb +28 -13
  29. data/lib/mixin_bot/api/legacy_transfer.rb +11 -8
  30. data/lib/mixin_bot/api/legacy_user.rb +51 -0
  31. data/lib/mixin_bot/api/me.rb +99 -3
  32. data/lib/mixin_bot/api/message.rb +18 -27
  33. data/lib/mixin_bot/api/multisig.rb +19 -0
  34. data/lib/mixin_bot/api/network.rb +17 -0
  35. data/lib/mixin_bot/api/network_asset.rb +27 -0
  36. data/lib/mixin_bot/api/output.rb +1 -1
  37. data/lib/mixin_bot/api/pin.rb +16 -3
  38. data/lib/mixin_bot/api/pin_payload.rb +26 -0
  39. data/lib/mixin_bot/api/session.rb +14 -0
  40. data/lib/mixin_bot/api/snapshot.rb +6 -0
  41. data/lib/mixin_bot/api/tip.rb +74 -1
  42. data/lib/mixin_bot/api/transaction.rb +106 -17
  43. data/lib/mixin_bot/api/transfer.rb +141 -14
  44. data/lib/mixin_bot/api/turn.rb +12 -0
  45. data/lib/mixin_bot/api/user.rb +148 -45
  46. data/lib/mixin_bot/api/withdraw.rb +24 -23
  47. data/lib/mixin_bot/api.rb +248 -3
  48. data/lib/mixin_bot/bot_auth.rb +71 -0
  49. data/lib/mixin_bot/cli/api.rb +224 -143
  50. data/lib/mixin_bot/cli/base.rb +77 -0
  51. data/lib/mixin_bot/cli/call.rb +71 -0
  52. data/lib/mixin_bot/cli/errors.rb +56 -0
  53. data/lib/mixin_bot/cli/node.rb +9 -2
  54. data/lib/mixin_bot/cli/output.rb +196 -0
  55. data/lib/mixin_bot/cli/schema.rb +274 -0
  56. data/lib/mixin_bot/cli/schema_command.rb +21 -0
  57. data/lib/mixin_bot/cli/utils.rb +114 -18
  58. data/lib/mixin_bot/cli.rb +124 -48
  59. data/lib/mixin_bot/client/error_mapper.rb +40 -0
  60. data/lib/mixin_bot/client.rb +94 -64
  61. data/lib/mixin_bot/computer.rb +132 -0
  62. data/lib/mixin_bot/configuration.rb +108 -1
  63. data/lib/mixin_bot/errors.rb +102 -0
  64. data/lib/mixin_bot/models/address.rb +11 -0
  65. data/lib/mixin_bot/models/api_envelope.rb +67 -0
  66. data/lib/mixin_bot/models/asset.rb +11 -0
  67. data/lib/mixin_bot/models/ghost_keys.rb +14 -0
  68. data/lib/mixin_bot/models/output.rb +11 -0
  69. data/lib/mixin_bot/models/safe_multisig_request.rb +11 -0
  70. data/lib/mixin_bot/models/sequencer_transaction_request.rb +11 -0
  71. data/lib/mixin_bot/models/user.rb +11 -0
  72. data/lib/mixin_bot/models.rb +10 -0
  73. data/lib/mixin_bot/monitor.rb +77 -0
  74. data/lib/mixin_bot/transaction/buffer.rb +34 -0
  75. data/lib/mixin_bot/transaction/decoder.rb +227 -0
  76. data/lib/mixin_bot/transaction/encoder.rb +255 -0
  77. data/lib/mixin_bot/transaction.rb +6 -475
  78. data/lib/mixin_bot/url_scheme.rb +63 -0
  79. data/lib/mixin_bot/utils/address.rb +17 -80
  80. data/lib/mixin_bot/utils/crypto.rb +173 -1
  81. data/lib/mixin_bot/utils/decoder.rb +1 -1
  82. data/lib/mixin_bot/utils/encoder.rb +13 -0
  83. data/lib/mixin_bot/utils.rb +45 -0
  84. data/lib/mixin_bot/uuid.rb +78 -1
  85. data/lib/mixin_bot/version.rb +11 -1
  86. data/lib/mixin_bot.rb +172 -18
  87. data/lib/mvm/bridge.rb +46 -0
  88. data/lib/mvm/client.rb +60 -0
  89. data/lib/mvm/nft.rb +4 -2
  90. data/lib/mvm/registry.rb +2 -1
  91. data/lib/mvm.rb +93 -0
  92. data/lib/tasks/api_coverage.rake +20 -0
  93. data/llms.txt +29 -0
  94. metadata +77 -9
@@ -29,127 +29,11 @@ module MixinBot
29
29
  end
30
30
 
31
31
  def encode
32
- raise InvalidTransactionFormatError, 'asset is required' if asset.blank?
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
- @bytes = [hex].pack('H*').bytes
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 = 'XIN'
7
- MIX_ADDRESS_PREFIX = 'MIX'
8
- MIX_ADDRESS_VERSION = 2
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
- msg = MAIN_ADDRESS_PREFIX + public_key
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
- raise ArgumentError, 'invalid address' unless address.start_with? MAIN_ADDRESS_PREFIX
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
- raise ArgumentError, 'members should be an array' unless members.is_a? Array
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
- raise ArgumentError, 'invalid address' unless address.start_with? MIX_ADDRESS_PREFIX
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
- seed = "\0" * 64
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
- MixinBot::Utils.build_main_address spend_key + view_key
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