mixin_bot 0.7.11 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b4b77d94221e1120da8c18fadd70bcf5898d083e2bd95c469cee8c96084c76f
4
- data.tar.gz: '039f44131a984c10b1f527b6c45766a3660af6efc40e2e66e132b46e1cbc8428'
3
+ metadata.gz: a76c8b7d2c5d8cebc963a56d350e3fd5e4bbc1e18ad3e037b65cfb6e1d77b0d1
4
+ data.tar.gz: 8c9813a57cb33f256c695a88e42f53feffb33a45979f5fbf788916f332a52d26
5
5
  SHA512:
6
- metadata.gz: 22470467c1b5955e2e2352788fecbf14bf307abdba0283796a2b86525660a8f479670135bd9d594b59462a2058956214036025f9881833b57b21f59f28644876
7
- data.tar.gz: e88226bd6042e3ff224ff5805d1982df1ee4a6b17186440d3edeae820c9f804e1952b3da83dc2b29258a97f5be7685b6d3e163bbab242d83752e468a0d1a4908
6
+ metadata.gz: 999325d7c1b73c54b80a6a7a2e432e83aaff341997db01211fb43cddffa32448451aaf5c1d92852dfcda872d92b4724d938bacde83621650c20594aaa034ff32
7
+ data.tar.gz: 7b49bda1bb1026823ca51301a7f61380db7714f9d9441c466ff0a0c2bb50be52e199900e13b0d7c5e8e6782f938c57f38562e9d56439346c62b7abb364d928e5
@@ -20,11 +20,12 @@ module MixinBot
20
20
 
21
21
  def snapshots(**options)
22
22
  path = format(
23
- '/snapshots?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&opponent=%<opponent>s',
23
+ '/snapshots?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&opponent=%<opponent>s&order=%<order>s',
24
24
  limit: options[:limit],
25
25
  offset: options[:offset],
26
26
  asset: options[:asset],
27
- opponent: options[:opponent]
27
+ opponent: options[:opponent],
28
+ order: options[:order],
28
29
  )
29
30
 
30
31
  access_token = options[:access_token] || access_token('GET', path)
@@ -11,13 +11,7 @@ module MixinBot
11
11
 
12
12
  desc 'unique UUIDS', 'generate unique UUID for two or more UUIDs'
13
13
  def unique(*uuids)
14
- uuids.sort
15
- r = uuids.first
16
- uuids.each_with_index do |uuid, i|
17
- r = MixinBot::Utils.unique_uuid(r, uuid) if i.positive?
18
- end
19
-
20
- log r
14
+ log MixinBot::Utils.unique_uuid *uuids
21
15
  end
22
16
 
23
17
  desc 'generatetrace HASH', 'generate trace ID from Tx hash'
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ module Utils
5
+ class Nfo
6
+ NFT_MEMO_PREFIX = 'NFO'
7
+ NFT_MEMO_VERSION = 0x00
8
+ NFT_MEMO_DEFAULT_CHAIN = '43d61dcd-e413-450d-80b8-101d5e903357'
9
+ NFT_MEMO_DEFAULT_CLASS = '3c8c161a18ae2c8b14fda1216fff7da88c419b5d'
10
+ NULL_UUID = '00000000-0000-0000-0000-000000000000'
11
+
12
+ attr_reader :prefix, :version, :raw
13
+ attr_accessor :mask, :chain, :nm_class, :collection, :token, :extra, :memo, :hex
14
+
15
+ def initialize(**kwargs)
16
+ @prefix = NFT_MEMO_PREFIX
17
+ @version = NFT_MEMO_VERSION
18
+ @mask = kwargs[:mask] || 0
19
+ @chain = kwargs[:chain]
20
+ @nm_class = kwargs[:nm_class]
21
+ @collection = kwargs[:collection]
22
+ @token = kwargs[:token]
23
+ @extra = kwargs[:extra]
24
+ @memo = kwargs[:memo]
25
+ @hex = kwargs[:hex]
26
+ end
27
+
28
+ def mint_memo
29
+ raise MixinBot::InvalidNfoFormatError, 'token is required' if token.blank?
30
+ raise MixinBot::InvalidNfoFormatError, 'extra must be 256-bit string' if extra.blank? || extra.size != 64
31
+
32
+ @collection = NULL_UUID if collection.blank?
33
+ @chain = NFT_MEMO_DEFAULT_CHAIN
34
+ @nm_class= NFT_MEMO_DEFAULT_CLASS
35
+ mark 0
36
+ encode
37
+
38
+ memo
39
+ end
40
+
41
+ def mark(*indexes)
42
+ indexes.map do |index|
43
+ if index >= 64 || index < 0
44
+ raise ArgumentError, "invalid NFO memo index #{index}"
45
+ end
46
+ @mask = mask ^ (1 << index)
47
+ end
48
+ end
49
+
50
+ def encode
51
+ bytes = []
52
+
53
+ bytes += prefix.bytes
54
+ bytes += [version]
55
+
56
+ if mask != 0
57
+ bytes += [1]
58
+ bytes += MixinBot::Utils.encode_unit_64 mask
59
+ bytes += MixinBot::Utils::UUID.new(hex: chain).packed.bytes
60
+
61
+ class_bytes = [nm_class].pack('H*').bytes
62
+ bytes += MixinBot::Utils.bytes_of class_bytes.size
63
+ bytes += class_bytes
64
+
65
+ collection_bytes = collection.split('-').pack('H* H* H* H* H*').bytes
66
+ bytes += MixinBot::Utils.bytes_of collection_bytes.size
67
+ bytes += collection_bytes
68
+
69
+ # token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
70
+ token_bytes = MixinBot::Utils.bytes_of token
71
+ bytes += MixinBot::Utils.bytes_of token_bytes.size
72
+ bytes += token_bytes
73
+ end
74
+
75
+ extra_bytes = [extra].pack('H*').bytes
76
+ bytes += MixinBot::Utils.bytes_of extra_bytes.size
77
+ bytes += extra_bytes
78
+
79
+ @raw = bytes.pack('C*')
80
+ @hex = raw.unpack1('H*')
81
+ @memo = Base64.urlsafe_encode64 raw, padding: false
82
+
83
+ self
84
+ end
85
+
86
+ def decode
87
+ @raw =
88
+ if memo.present?
89
+ Base64.urlsafe_decode64 memo
90
+ elsif hex.present?
91
+ [hex].pack('H*')
92
+ else
93
+ raise InvalidNfoFormatError, 'memo or hex is required'
94
+ end
95
+
96
+ @hex = raw.unpack1('H*') if hex.blank?
97
+ @memo = Base64.urlsafe_encode64 raw, padding: false if memo.blank?
98
+
99
+ decode_bytes
100
+ self
101
+ end
102
+
103
+ def decode_bytes
104
+ bytes = raw.bytes
105
+
106
+ _prefix = bytes.shift(3).pack('C*')
107
+ raise MixinBot::InvalidNfoFormatError, "NFO prefix #{_prefix}" if _prefix != prefix
108
+
109
+ _version = bytes.shift
110
+ raise MixinBot::InvalidNfoFormatError, "NFO version #{prefix}" if _version != version
111
+
112
+ hint = bytes.shift
113
+ if hint == 1
114
+ @mask = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
115
+
116
+ @chain = MixinBot::Utils::UUID.new(hex: bytes.shift(16).pack('C*').unpack1('H*')).unpacked
117
+
118
+ class_length = bytes.shift
119
+ @nm_class = bytes.shift(class_length).pack('C*').unpack1('H*')
120
+
121
+ collection_length = bytes.shift
122
+ @collection = MixinBot::Utils::UUID.new(hex: bytes.shift(collection_length).pack('C*').unpack1('H*')).unpacked
123
+
124
+ token_length = bytes.shift
125
+ @token = MixinBot::Utils.bytes_to_int bytes.shift(token_length)
126
+ end
127
+
128
+ extra_length = bytes.shift
129
+ @extra = bytes.shift(extra_length).pack('C*').unpack1('H*')
130
+ end
131
+
132
+ def to_h
133
+ hash = {
134
+ prefix: prefix,
135
+ version: version,
136
+ mask: mask,
137
+ chain: chain,
138
+ class: nm_class,
139
+ collection: collection,
140
+ token: token,
141
+ extra: extra,
142
+ memo: memo,
143
+ hex: hex
144
+ }
145
+
146
+ hash.each do |key, value|
147
+ hash.delete key if value.blank?
148
+ end
149
+
150
+ hash
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,478 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ module Utils
5
+ class Transaction
6
+ DEAULT_VERSION = 2
7
+ MAGIC = [0x77, 0x77]
8
+ TX_VERSION = 2
9
+ MAX_ENCODE_INT = 0xFFFF
10
+ NULL_BYTES = [0x00, 0x00]
11
+ AGGREGATED_SIGNATURE_PREFIX = 0xFF01
12
+ AGGREGATED_SIGNATURE_ORDINAY_MASK = [0x00]
13
+ AGGREGATED_SIGNATURE_SPARSE_MASK = [0x01]
14
+
15
+ attr_accessor :version, :asset, :inputs, :outputs, :extra, :signatures, :aggregated, :hex, :hash
16
+
17
+ def initialize(**kwargs)
18
+ @version = kwargs[:version] || DEAULT_VERSION
19
+ @asset = kwargs[:asset]
20
+ @inputs = kwargs[:inputs]
21
+ @outputs = kwargs[:outputs]
22
+ @extra = kwargs[:extra]
23
+ @hex = kwargs[:hex]
24
+ @signatures = kwargs[:signatures]
25
+ @aggregated = kwargs[:aggregated]
26
+ end
27
+
28
+ def encode
29
+ raise InvalidTransactionFormatError, 'asset is required' if asset.blank?
30
+ raise InvalidTransactionFormatError, 'inputs is required' if inputs.blank?
31
+ raise InvalidTransactionFormatError, 'outputs is required' if outputs.blank?
32
+
33
+ bytes = []
34
+
35
+ # magic number
36
+ bytes += MAGIC
37
+
38
+ # version
39
+ bytes += [0, version]
40
+
41
+ # asset
42
+ bytes += [asset].pack('H*').bytes
43
+
44
+ # inputs
45
+ bytes += encode_inputs
46
+
47
+ # output
48
+ bytes += encode_outputs
49
+
50
+ # extra
51
+ extra_bytes = [extra].pack('H*').bytes
52
+ bytes += MixinBot::Utils.encode_int extra_bytes.size
53
+ bytes += extra_bytes
54
+
55
+ # aggregated
56
+ if aggregated.nil?
57
+ # signatures
58
+ bytes += encode_signatures
59
+ else
60
+ bytes += encode_aggregated_signature
61
+ end
62
+
63
+ @hash = SHA3::Digest::SHA256.hexdigest bytes.pack('C*')
64
+ @hex = bytes.pack('C*').unpack1('H*')
65
+
66
+ self
67
+ end
68
+
69
+ def decode
70
+ @bytes = [hex].pack('H*').bytes
71
+ @hash = SHA3::Digest::SHA256.hexdigest @bytes.pack('C*')
72
+
73
+ magic = @bytes.shift(2)
74
+ raise ArgumentError, 'Not valid raw' unless magic == MAGIC
75
+
76
+ version = @bytes.shift(2)
77
+ @version = MixinBot::Utils.bytes_to_int version
78
+
79
+ asset = @bytes.shift(32)
80
+ @asset = asset.pack('C*').unpack1('H*')
81
+
82
+ # read inputs
83
+ decode_inputs
84
+
85
+ # read outputs
86
+ decode_outputs
87
+
88
+ extra_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
89
+ @extra = @bytes.shift(extra_size).pack('C*').unpack1('H*')
90
+
91
+ num = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
92
+ if num == MAX_ENCODE_INT
93
+ # aggregated
94
+ @aggregated = {}
95
+
96
+ raise ArgumentError, 'invalid aggregated' unless @bytes.shift(2).reverse.pack('C*').unpack1('S*') == AGGREGATED_SIGNATURE_PREFIX
97
+
98
+ @aggregated['signature'] = @bytes.shift(64).pack('C*').unpack1('H*')
99
+
100
+ byte = @bytes.shift
101
+ case byte
102
+ when AGGREGATED_SIGNATURE_ORDINAY_MASK.first
103
+ @aggregated['signers'] = []
104
+ masks_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
105
+ masks = @bytes.shift(masks_size)
106
+ masks = [masks] unless masks.is_a? Array
107
+
108
+ masks.each_with_index do |mask, i|
109
+ 8.times do |j|
110
+ k = 1 << j
111
+ aggregated['signers'].push(i * 8 + j) if mask & k == k
112
+ end
113
+ end
114
+ when AGGREGATED_SIGNATURE_SPARSE_MASK.first
115
+ signers_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
116
+ return if signers_size == 0
117
+
118
+ aggregated['signers'] = []
119
+ signers_size.times do
120
+ aggregated['signers'].push @bytes.shift(2).reverse.pack('C*').unpack1('S*')
121
+ end
122
+ end
123
+ else
124
+ if !@bytes.empty? && @bytes[...2] != NULL_BYTES
125
+ signatures_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
126
+ @signatures = @bytes.shift(signatures_size).pack('C*').unpack1('H*')
127
+ end
128
+ end
129
+
130
+ self
131
+ end
132
+
133
+ def to_h
134
+ {
135
+ version: version,
136
+ asset: asset,
137
+ inputs: inputs,
138
+ outputs: outputs,
139
+ extra: extra,
140
+ signatures: signatures,
141
+ aggregated: aggregated,
142
+ hash: hash
143
+ }.compact
144
+ end
145
+
146
+ private
147
+
148
+ def encode_inputs
149
+ bytes = []
150
+
151
+ bytes += MixinBot::Utils.encode_int(inputs.size)
152
+
153
+ inputs.each do |input|
154
+ bytes += [input['hash']].pack('H*').bytes
155
+ bytes += MixinBot::Utils.encode_int(input['index'])
156
+
157
+ # genesis
158
+ genesis = input['genesis'] || ''
159
+ if genesis.empty?
160
+ bytes += NULL_BYTES
161
+ else
162
+ genesis_bytes = [genesis].pack('H*').bytes
163
+ bytes += MixinBot::Utils.encode_int genesis_bytes.size
164
+ bytes += genesis_bytes
165
+ end
166
+
167
+ # deposit
168
+ deposit = input['deposit']
169
+ if deposit.nil?
170
+ bytes += NULL_BYTES
171
+ else
172
+ bytes += MAGIC
173
+ bytes += [deposit['chain']].pack('H*').bytes
174
+
175
+ asset_bytes = [deposit['asset']].pack('H*')
176
+ bytes += MixinBot::Utils.encode_int asset_bytes.size
177
+ bytes += asset_bytes
178
+
179
+ transaction_bytes = [deposit['transaction']].pack('H*')
180
+ bytes += MixinBot::Utils.encode_int transaction_bytes.size
181
+ bytes += transaction_bytes
182
+
183
+ bytes += MixinBot::Utils.encode_unit_64 deposit['index']
184
+
185
+ amount_bytes = MixinBot::Utils.bytes_of deposit['amount']
186
+ bytes += MixinBot::Utils.encode_int amount_bytes.size
187
+ bytes += amount_bytes
188
+ end
189
+
190
+ # mint
191
+ mint = input['mint']
192
+ if mint.nil?
193
+ bytes += NULL_BYTES
194
+ else
195
+ bytes += MAGIC
196
+
197
+ # group
198
+ group = mint['group'] || ''
199
+ if group.empty?
200
+ bytes += MixinBot::Utils.encode_int NULL_BYTES
201
+ else
202
+ group_bytes = [group].pack('H*')
203
+ bytes += MixinBot::Utils.encode_int group_bytes.size
204
+ bytes += group_bytes
205
+ end
206
+
207
+ bytes += MixinBot::Utils.encode_unit_64 mint['batch']
208
+
209
+ amount_bytes = MixinBot::Utils.bytes_of mint['amount']
210
+ bytes += MixinBot::Utils.encode_int amount_bytes.size
211
+ bytes += amount_bytes
212
+ end
213
+ end
214
+
215
+ bytes
216
+ end
217
+
218
+ def encode_outputs
219
+ bytes = []
220
+
221
+ bytes += MixinBot::Utils.encode_int outputs.size
222
+
223
+ outputs.each do |output|
224
+ type = output['type'] || 0
225
+ bytes += [0x00, type]
226
+
227
+ # amount
228
+ amount_bytes = MixinBot::Utils.bytes_of (output['amount'].to_d * 1e8).round
229
+ bytes += MixinBot::Utils.encode_int amount_bytes.size
230
+ bytes += amount_bytes
231
+
232
+ # keys
233
+ bytes += MixinBot::Utils.encode_int output['keys'].size
234
+ output['keys'].each do |key|
235
+ bytes += [key].pack('H*').bytes
236
+ end
237
+
238
+ # mask
239
+ bytes += [output['mask']].pack('H*').bytes
240
+
241
+ # script
242
+ script_bytes = [output['script']].pack('H*').bytes
243
+ bytes += MixinBot::Utils.encode_int script_bytes.size
244
+ bytes += script_bytes
245
+
246
+ # withdrawal
247
+ withdrawal = output['withdrawal']
248
+ if withdrawal.nil?
249
+ bytes += NULL_BYTES
250
+ else
251
+ bytes += MAGIC
252
+
253
+ # chain
254
+ bytes += [withdrawal['chain']].pack('H*').bytes
255
+
256
+ # asset
257
+ @asset_bytes = [withdrawal['asset']].pack('H*')
258
+ bytes += MixinBot::Utils.encode_int asset_bytes.size
259
+ bytes += asset_bytes
260
+
261
+ # address
262
+ address = withdrawal['address'] || ''
263
+ if address.empty?
264
+ bytes += NULL_BYTES
265
+ else
266
+ address_bytes = [address].pack('H*').bytes
267
+ bytes += MixinBot::Utils.encode_int address.size
268
+ bytes += address_bytes
269
+ end
270
+
271
+ # tag
272
+ tag = withdrawal['tag'] || ''
273
+ if tag.empty?
274
+ bytes += NULL_BYTES
275
+ else
276
+ address_bytes = [tag].pack('H*').bytes
277
+ bytes += MixinBot::Utils.encode_int tag.size
278
+ bytes += address_bytes
279
+ end
280
+ end
281
+ end
282
+
283
+ bytes
284
+ end
285
+
286
+ def encode_aggregated_signature
287
+ bytes = []
288
+
289
+ bytes += MixinBot::Utils.encode_int MAX_ENCODE_INT
290
+ bytes += MixinBot::Utils.encode_int AGGREGATED_SIGNATURE_PREFIX
291
+ bytes += [aggregated['signature']].pack('H*').bytes
292
+
293
+ signers = aggregated['signers']
294
+ if signers.size == 0
295
+ bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
296
+ bytes += NULL_BYTES
297
+ else
298
+ signers.each do |sig, i|
299
+ raise ArgumentError, 'signers not sorted' if i > 0 && sig <= signers[i - 1]
300
+ raise ArgumentError, 'signers not sorted' if sig > MAX_ENCODE_INT
301
+ end
302
+
303
+ max = signers.last
304
+ if (((max / 8 | 0) + 1 | 0) > aggregated['signature'].size * 2)
305
+ bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
306
+ bytes += MixinBot::Utils.encode_int aggregated['signers'].size
307
+ signers.map(&->(signer) { bytes += MixinBot::Utils.encode_int(signer) })
308
+ end
309
+
310
+ masks_bytes = Array.new(max / 8 + 1, 0)
311
+ signers.each do |signer|
312
+ masks[signer/8] = masks[signer/8] ^ (1 << (signer % 8))
313
+ end
314
+ bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
315
+ bytes += MixinBot::Utils.encode_int masks_bytes.size
316
+ bytes += masks_bytes
317
+ end
318
+
319
+ bytes
320
+ end
321
+
322
+ def encode_signatures
323
+ bytes = []
324
+
325
+ sl =
326
+ if signatures.is_a? Hash
327
+ signatures.keys.size
328
+ else
329
+ 0
330
+ end
331
+
332
+ raise ArgumentError, 'signatures overflow' if sl == MAX_ENCODE_INT
333
+ bytes += MixinBot::Utils.encode_int sl
334
+
335
+ if sl > 0
336
+ bytes += MixinBot::Utils.encode_int signatures.keys.size
337
+ signatures.keys.sort.each do |key|
338
+ bytes += MixinBot::Utils.encode_int key
339
+ bytes += [signatures[key]].pack('H*').bytes
340
+ end
341
+ end
342
+
343
+ bytes
344
+ end
345
+
346
+ def decode_inputs
347
+ inputs_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
348
+ @inputs = []
349
+ inputs_size.times do
350
+ input = {}
351
+ hash = @bytes.shift(32)
352
+ input['hash'] = hash.pack('C*').unpack1('H*')
353
+
354
+ index = @bytes.shift(2)
355
+ input['index'] = index.reverse.pack('C*').unpack1('S*')
356
+
357
+ if @bytes[...2] != NULL_BYTES
358
+ genesis_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
359
+ genesis = @bytes.shift(genesis_size)
360
+ input['genesis'] = genesis.pack('C*').unpack1('H*')
361
+ else
362
+ @bytes.shift 2
363
+ end
364
+
365
+ if @bytes[...2] != NULL_BYTES
366
+ magic = @bytes.shift(2)
367
+ raise ArgumentError, 'Not valid input' unless magic == MAGIC
368
+
369
+ deposit = {}
370
+ deposit['chain'] = @bytes.shift(32).pack('C*').unpack1('H*')
371
+
372
+ asset_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
373
+ deposit['asset'] = @bytes.shift(asset_size).unpack1('H*')
374
+
375
+ transaction_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
376
+ deposit['transaction'] = @bytes.shift(transaction_size).unpack1('H*')
377
+
378
+ deposit['index'] = @bytes.shift(8).reverse.pack('C*').unpack1('Q*')
379
+
380
+ amount_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
381
+ deposit['amount'] = MixinBot::Utils.bytes_to_int @bytes.shift(amount_size)
382
+
383
+ input['deposit'] = deposit
384
+ else
385
+ @bytes.shift 2
386
+ end
387
+
388
+ if @bytes[...2] != NULL_BYTES
389
+ magic = @bytes.shift(2)
390
+ raise ArgumentError, 'Not valid input' unless magic == MAGIC
391
+
392
+ mint = {}
393
+ if bytes[...2] != NULL_BYTES
394
+ group_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
395
+ mint['group'] = @bytes.shift(group_size).unpack1('H*')
396
+ else
397
+ @bytes.shift 2
398
+ end
399
+
400
+ mint['batch'] = @bytes.shift(8).reverse.pack('C*').unpack1('Q*')
401
+ _amount_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
402
+ mint['amount'] = MixinBot::Utils.bytes_to_int bytes.shift(_amount_size)
403
+
404
+ input['mint'] = mint
405
+ else
406
+ @bytes.shift 2
407
+ end
408
+
409
+ @inputs.push input
410
+ end
411
+
412
+ self
413
+ end
414
+
415
+ def decode_outputs
416
+ outputs_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
417
+ @outputs = []
418
+ outputs_size.times do
419
+ output = {}
420
+
421
+ @bytes.shift
422
+ type = @bytes.shift
423
+ output['type'] = type
424
+
425
+ amount_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
426
+ output['amount'] = format('%.8f', MixinBot::Utils.bytes_to_int(@bytes.shift(amount_size)).to_f / 1e8)
427
+
428
+ output['keys'] = []
429
+ keys_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
430
+ keys_size.times do
431
+ output['keys'].push @bytes.shift(32).pack('C*').unpack1('H*')
432
+ end
433
+
434
+ output['mask'] = @bytes.shift(32).pack('C*').unpack1('H*')
435
+
436
+ script_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
437
+ output['script'] = @bytes.shift(script_size).pack('C*').unpack1('H*')
438
+
439
+ if @bytes[...2] != NULL_BYTES
440
+ magic = @bytes.shift(2)
441
+ raise ArgumentError, 'Not valid output' unless magic == MAGIC
442
+
443
+ withdraw = {}
444
+
445
+ output['chain'] = @bytes.shift(32).pack('C*').unpack1('H*')
446
+
447
+ asset_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
448
+ output['asset'] = @bytes.shift(asset_size).unpack1('H*')
449
+
450
+ if @bytes[...2] != NULL_BYTES
451
+ address = {}
452
+
453
+ adderss_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
454
+ output['adderss'] = @bytes.shift(adderss_size).pack('C*').unpack1('H*')
455
+ else
456
+ @bytes.shift 2
457
+ end
458
+
459
+ if @bytes[...2] != NULL_BYTES
460
+ tag = {}
461
+
462
+ tag_size = @bytes.shift(2).reverse.pack('C*').unpack1('S*')
463
+ output['tag'] = @bytes.shift(tag_size).pack('C*').unpack1('H*')
464
+ else
465
+ @bytes.shift 2
466
+ end
467
+ else
468
+ @bytes.shift 2
469
+ end
470
+
471
+ @outputs.push output
472
+ end
473
+
474
+ self
475
+ end
476
+ end
477
+ end
478
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ module Utils
5
+ class UUID
6
+ attr_accessor :hex, :raw
7
+
8
+ def initialize(**args)
9
+ @hex = args[:hex]
10
+ @raw = args[:raw]
11
+
12
+ raise MixinBot::InvalidUuidFormatError if raw.present? && raw.size != 16
13
+ raise MixinBot::InvalidUuidFormatError if hex.present? && hex.gsub('-', '').size != 32
14
+ end
15
+
16
+ def packed
17
+ if raw.present?
18
+ raw
19
+ elsif hex.present?
20
+ [hex.gsub('-', '')].pack('H*')
21
+ end
22
+ end
23
+
24
+ def unpacked
25
+ _hex =
26
+ if hex.present?
27
+ hex.gsub('-', '')
28
+ elsif raw.present?
29
+ _hex = raw.unpack1('H*')
30
+ end
31
+
32
+ format(
33
+ '%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
34
+ first: _hex[0..7],
35
+ second: _hex[8..11],
36
+ third: _hex[12..15],
37
+ forth: _hex[16..19],
38
+ fifth: _hex[20..]
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './utils/nfo'
4
+ require_relative './utils/uuid'
5
+ require_relative './utils/transaction'
6
+
3
7
  module MixinBot
4
8
  module Utils
5
9
  class << self
@@ -10,14 +14,8 @@ module MixinBot
10
14
  AGGREGATED_SIGNATURE_PREFIX = 0xFF01
11
15
  AGGREGATED_SIGNATURE_ORDINAY_MASK = [0x00]
12
16
  AGGREGATED_SIGNATURE_SPARSE_MASK = [0x01]
13
- NFT_MEMO_PREFIX = 'NFO'
14
- NFT_MEMO_VERSION = 0x00
15
- NFT_MEMO_DEFAULT_CHAIN = '43d61dcd-e413-450d-80b8-101d5e903357'
16
- NFT_MEMO_DEFAULT_CLASS = '3c8c161a18ae2c8b14fda1216fff7da88c419b5d'
17
- NFT_MASK = 0x00
18
- NULL_UUID = '00000000-0000-0000-0000-000000000000'
19
17
 
20
- def unique_uuid(uuid_1, uuid_2)
18
+ def generate_unique_uuid(uuid_1, uuid_2)
21
19
  md5 = Digest::MD5.new
22
20
  md5 << [uuid_1, uuid_2].min
23
21
  md5 << [uuid_1, uuid_2].max
@@ -25,16 +23,18 @@ module MixinBot
25
23
  digest6 = (digest[6].ord & 0x0f | 0x30).chr
26
24
  digest8 = (digest[8].ord & 0x3f | 0x80).chr
27
25
  cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
28
- hex = cipher.unpack1('H*')
29
26
 
30
- format(
31
- '%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
32
- first: hex[0..7],
33
- second: hex[8..11],
34
- third: hex[12..15],
35
- forth: hex[16..19],
36
- fifth: hex[20..]
37
- )
27
+ UUID.new(raw: cipher).unpacked
28
+ end
29
+
30
+ def unique_uuid(*uuids)
31
+ uuids.sort
32
+ r = uuids.first
33
+ uuids.each_with_index do |uuid, i|
34
+ r = MixinBot::Utils.generate_unique_uuid(r, uuid) if i.positive?
35
+ end
36
+
37
+ r
38
38
  end
39
39
 
40
40
  def generate_trace_from_hash(hash, output_index = 0)
@@ -44,13 +44,12 @@ module MixinBot
44
44
  digest = md5.digest
45
45
  digest[6] = ((digest[6].ord & 0x0f) | 0x30).chr
46
46
  digest[8] = ((digest[8].ord & 0x3f) | 0x80).chr
47
- hex = digest.unpack1('H*')
48
47
 
49
- hex_to_uuid hex
48
+ UUID.new(raw: digest).unpacked
50
49
  end
51
50
 
52
51
  def hex_to_uuid(hex)
53
- [hex[0..7], hex[8..11], hex[12..15], hex[16..19], hex[20..]].join('-')
52
+ UUID.new(hex: hex).unpacked
54
53
  end
55
54
 
56
55
  def sign_raw_transaction(tx)
@@ -60,197 +59,27 @@ module MixinBot
60
59
  raise ArgumentError, "#{tx} is not a valid json" unless tx.is_a? Hash
61
60
 
62
61
  tx = tx.with_indifferent_access
63
- bytes = []
64
-
65
- # magic number
66
- bytes += MAGIC
67
-
68
- # version
69
- bytes += [0, tx['version']]
70
-
71
- # asset
72
- bytes += [tx['asset']].pack('H*').bytes
73
-
74
- # inputs
75
- bytes += encode_inputs tx['inputs']
76
-
77
- # output
78
- bytes += encode_outputs tx['outputs']
79
-
80
- # extra
81
- extra_bytes = [tx['extra']].pack('H*').bytes
82
- bytes += encode_int extra_bytes.size
83
- bytes += extra_bytes
84
-
85
- # aggregated
86
- aggregated = tx['aggregated']
87
- if aggregated.nil?
88
- # signatures
89
- bytes += encode_signatures tx['signatures']
90
- else
91
- bytes += encode_aggregated_signature aggregated
92
- end
93
-
94
- bytes.pack('C*').unpack1('H*')
95
- end
96
-
97
- def decode_raw_transaction(raw)
98
- bytes = [raw].pack('H*').bytes
99
- tx = {}
100
-
101
- magic = bytes.shift(2)
102
- raise ArgumentError, 'Not valid raw' unless magic == MAGIC
103
-
104
- version = bytes.shift(2)
105
- tx['version'] = bytes_to_int version
106
-
107
- asset = bytes.shift(32)
108
- tx['asset'] = asset.pack('C*').unpack1('H*')
109
-
110
- # read inputs
111
- bytes, tx = decode_inputs bytes, tx
112
-
113
- # read outputs
114
- bytes, tx = decode_outputs bytes, tx
115
-
116
- extra_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
117
- tx['extra'] = bytes.shift(extra_size).pack('C*').unpack1('H*')
118
-
119
- num = bytes.shift(2).reverse.pack('C*').unpack1('S*')
120
- if num == MAX_ENCODE_INT
121
- # aggregated
122
- aggregated = {}
123
-
124
- raise ArgumentError, 'invalid aggregated' unless bytes.shift(2).reverse.pack('C*').unpack1('S*') == AGGREGATED_SIGNATURE_PREFIX
125
-
126
- aggregated['signature'] = bytes.shift(64).pack('C*').unpack1('H*')
127
-
128
- byte = bytes.shift
129
- case byte
130
- when AGGREGATED_SIGNATURE_ORDINAY_MASK.first
131
- aggregated['signers'] = []
132
- masks_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
133
- masks = bytes.shift(masks_size)
134
- masks = [masks] unless masks.is_a? Array
135
-
136
- masks.each_with_index do |mask, i|
137
- 8.times do |j|
138
- k = 1 << j
139
- aggregated['signers'].push(i * 8 + j) if mask & k == k
140
- end
141
- end
142
- when AGGREGATED_SIGNATURE_SPARSE_MASK.first
143
- signers_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
144
- return if signers_size == 0
145
-
146
- aggregated['signers'] = []
147
- signers_size.times do
148
- aggregated['signers'].push bytes.shift(2).reverse.pack('C*').unpack1('S*')
149
- end
150
- end
151
-
152
- tx['aggregated'] = aggregated
153
- else
154
- if !bytes.empty? && bytes[...2] != NULL_BYTES
155
- signatures_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
156
- tx['signatures'] = bytes.shift(signatures_size).pack('C*').unpack1('H*')
157
- end
158
- end
159
-
160
- tx
161
- end
162
-
163
- def nft_memo_hash(collection, token_id, hash)
164
- collection = NULL_UUID if collection.empty?
165
- raise ArgumentError, 'hash must be 256-bit string' unless hash.is_a?(String) && hash.size == 64
166
62
 
167
- memo = {
168
- prefix: NFT_MEMO_PREFIX,
169
- version: NFT_MEMO_VERSION,
170
- mask: 0,
171
- chain: NFT_MEMO_DEFAULT_CHAIN,
172
- class: NFT_MEMO_DEFAULT_CLASS,
173
- collection: collection,
174
- token: token_id.to_i,
175
- extra: hash
176
- }
177
-
178
- mark = [0]
179
- mark.map do |index|
180
- if index >= 64
181
- raise ArgumentError, "invalid NFO memo index #{index}"
182
- end
183
- memo[:mask] = memo[:mask] ^ (1 << index)
184
- end
185
-
186
- memo
63
+ Transaction.new(
64
+ asset: tx[:asset],
65
+ inputs: tx[:inputs],
66
+ outputs: tx[:outputs],
67
+ extra: tx[:extra],
68
+ ).encode.hex
187
69
  end
188
70
 
189
- def nft_memo(collection, token_id, hash)
190
- encode_nft_memo nft_memo_hash(collection, token_id, hash)
71
+ def decode_raw_transaction(hex)
72
+ Transaction.new(hex: hex).decode.to_h
191
73
  end
192
74
 
193
- def encode_nft_memo(memo)
194
- bytes = []
195
-
196
- bytes += NFT_MEMO_PREFIX.bytes
197
- bytes += [NFT_MEMO_VERSION]
198
-
199
- if memo[:mask] != 0
200
- bytes += [1]
201
- bytes += encode_unit_64 memo[:mask]
202
- bytes += memo[:chain].split('-').pack('H* H* H* H* H*').bytes
203
-
204
- class_bytes = [memo[:class]].pack('H*').bytes
205
- bytes += bytes_of class_bytes.size
206
- bytes += class_bytes
207
-
208
- collection_bytes = memo[:collection].split('-').pack('H* H* H* H* H*').bytes
209
- bytes += bytes_of collection_bytes.size
210
- bytes += collection_bytes
211
-
212
- # token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
213
- token_bytes = bytes_of memo[:token]
214
- bytes += bytes_of token_bytes.size
215
- bytes += token_bytes
216
- end
217
-
218
- extra_bytes = [memo[:extra]].pack('H*').bytes
219
- bytes += bytes_of extra_bytes.size
220
- bytes += extra_bytes
221
-
222
- Base64.urlsafe_encode64 bytes.pack('C*'), padding: false
75
+ def nft_memo(collection, token, extra)
76
+ MixinBot::Utils::Nfo.new(
77
+ collection: collection,
78
+ token: token,
79
+ extra: extra
80
+ ).mint_memo
223
81
  end
224
82
 
225
- def decode_nft_memo(encoded)
226
- bytes = Base64.urlsafe_decode64(encoded).bytes
227
- memo = {}
228
- memo[:prefix] = bytes.shift(3).pack('C*')
229
- memo[:version] = bytes.shift
230
-
231
- hint = bytes.shift
232
- if hint == 1
233
- memo[:mask] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
234
- memo[:chain] = hex_to_uuid bytes.shift(16).pack('C*').unpack1('H*')
235
-
236
- class_length = bytes.shift
237
- memo[:class] = bytes.shift(class_length).pack('C*').unpack1('H*')
238
-
239
- collection_length = bytes.shift
240
- memo[:collection] = hex_to_uuid bytes.shift(collection_length).pack('C*').unpack1('H*')
241
-
242
- token_length = bytes.shift
243
- memo[:token] = bytes_to_int bytes.shift(token_length)
244
- end
245
-
246
- extra_length = bytes.shift
247
- memo[:extra] = bytes.shift(extra_length).pack('C*').unpack1('H*')
248
-
249
- memo
250
- end
251
-
252
- private
253
-
254
83
  def encode_int(int)
255
84
  raise ArgumentError, "only support int #{int}" unless int.is_a?(Integer)
256
85
  raise ArgumentError,"int #{int} is larger than MAX_ENCODE_INT #{MAX_ENCODE_INT}" if int > MAX_ENCODE_INT
@@ -283,325 +112,6 @@ module MixinBot
283
112
 
284
113
  int
285
114
  end
286
-
287
- def encode_inputs(inputs, bytes = [])
288
- bytes += encode_int(inputs.size)
289
- inputs.each do |input|
290
- bytes += [input['hash']].pack('H*').bytes
291
- bytes += encode_int(input['index'])
292
-
293
- # genesis
294
- genesis = input['genesis'] || ''
295
- if genesis.empty?
296
- bytes += NULL_BYTES
297
- else
298
- genesis_bytes = [genesis].pack('H*').bytes
299
- bytes += encode_int genesis_bytes.size
300
- bytes += genesis_bytes
301
- end
302
-
303
- # deposit
304
- deposit = input['deposit']
305
- if deposit.nil?
306
- bytes += NULL_BYTES
307
- else
308
- bytes += MAGIC
309
- bytes += [deposit['chain']].pack('H*').bytes
310
-
311
- asset_bytes = [deposit['asset']].pack('H*')
312
- bytes += encode_int asset_bytes.size
313
- bytes += asset_bytes
314
-
315
- transaction_bytes = [deposit['transaction']].pack('H*')
316
- bytes += encode_int transaction_bytes.size
317
- bytes += transaction_bytes
318
-
319
- bytes += encode_unit_64 deposit['index']
320
-
321
- amount_bytes = bytes_of deposit['amount']
322
- bytes += encode_int amount_bytes.size
323
- bytes += amount_bytes
324
- end
325
-
326
- # mint
327
- mint = input['mint']
328
- if mint.nil?
329
- bytes += NULL_BYTES
330
- else
331
- bytes += MAGIC
332
-
333
- # group
334
- group = mint['group'] || ''
335
- if group.empty?
336
- bytes += encode_int NULL_BYTES
337
- else
338
- group_bytes = [group].pack('H*')
339
- bytes += encode_int group_bytes.size
340
- bytes += group_bytes
341
- end
342
-
343
- bytes += encode_unit_64 mint['batch']
344
-
345
- amount_bytes = bytes_of mint['amount']
346
- bytes += encode_int amount_bytes.size
347
- bytes += amount_bytes
348
- end
349
- end
350
-
351
- bytes
352
- end
353
-
354
- def encode_outputs(outputs, bytes = [])
355
- bytes += encode_int(outputs.size)
356
- outputs.each do |output|
357
- type = output['type'] || 0
358
- bytes += [0x00, type]
359
-
360
- # amount
361
- amount_bytes = bytes_of (output['amount'].to_f * 1e8).round
362
- bytes += encode_int amount_bytes.size
363
- bytes += amount_bytes
364
-
365
- # keys
366
- bytes += encode_int output['keys'].size
367
- output['keys'].each do |key|
368
- bytes += [key].pack('H*').bytes
369
- end
370
-
371
- # mask
372
- bytes += [output['mask']].pack('H*').bytes
373
-
374
- # script
375
- script_bytes = [output['script']].pack('H*').bytes
376
- bytes += encode_int script_bytes.size
377
- bytes += script_bytes
378
-
379
- # withdrawal
380
- withdrawal = output['withdrawal']
381
- if withdrawal.nil?
382
- bytes += NULL_BYTES
383
- else
384
- bytes += MAGIC
385
-
386
- # chain
387
- bytes += [withdrawal['chain']].pack('H*').bytes
388
-
389
- # asset
390
- asset_bytes = [withdrawal['asset']].pack('H*')
391
- bytes += encode_int asset_bytes.size
392
- bytes += asset_bytes
393
-
394
- # address
395
- address = withdrawal['address'] || ''
396
- if address.empty?
397
- bytes += NULL_BYTES
398
- else
399
- address_bytes = [address].pack('H*').bytes
400
- bytes += encode_int address.size
401
- bytes += address_bytes
402
- end
403
-
404
- # tag
405
- tag = withdrawal['tag'] || ''
406
- if tag.empty?
407
- bytes += NULL_BYTES
408
- else
409
- address_bytes = [tag].pack('H*').bytes
410
- bytes += encode_int tag.size
411
- bytes += address_bytes
412
- end
413
- end
414
- end
415
-
416
- bytes
417
- end
418
-
419
- def encode_aggregated_signature(aggregated, bytes = [])
420
- bytes += encode_int MAX_ENCODE_INT
421
- bytes += encode_int AGGREGATED_SIGNATURE_PREFIX
422
- bytes += [aggregated['signature']].pack('H*').bytes
423
-
424
- signers = aggregated['signers']
425
- if signers.size == 0
426
- bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
427
- bytes += NULL_BYTES
428
- else
429
- signers.each do |sig, i|
430
- raise ArgumentError, 'signers not sorted' if i > 0 && sig <= signers[i - 1]
431
- raise ArgumentError, 'signers not sorted' if sig > MAX_ENCODE_INT
432
- end
433
-
434
- max = signers.last
435
- if (((max / 8 | 0) + 1 | 0) > aggregated['signature'].size * 2)
436
- bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
437
- bytes += encode_int aggregated['signers'].size
438
- signers.map(&->(signer) { bytes += encode_int(signer) })
439
- end
440
-
441
- masks_bytes = Array.new(max / 8 + 1, 0)
442
- signers.each do |signer|
443
- masks[signer/8] = masks[signer/8] ^ (1 << (signer % 8))
444
- end
445
- bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
446
- bytes += encode_int masks_bytes.size
447
- bytes += masks_bytes
448
- end
449
-
450
- bytes
451
- end
452
-
453
- def encode_signatures(signatures, bytes = [])
454
- sl =
455
- if signatures.is_a? Hash
456
- signatures.keys.size
457
- else
458
- 0
459
- end
460
-
461
- raise ArgumentError, 'signatures overflow' if sl == MAX_ENCODE_INT
462
- bytes += encode_int sl
463
-
464
- if sl > 0
465
- bytes += encode_int signatures.keys.size
466
- signatures.keys.sort.each do |key|
467
- bytes += encode_int key
468
- bytes += [signatures[key]].pack('H*').bytes
469
- end
470
- end
471
-
472
- bytes
473
- end
474
-
475
- def decode_inputs(bytes, tx)
476
- inputs_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
477
- tx['inputs'] = []
478
- inputs_size.times do
479
- input = {}
480
- hash = bytes.shift(32)
481
- input['hash'] = hash.pack('C*').unpack1('H*')
482
-
483
- index = bytes.shift(2)
484
- input['index'] = index.reverse.pack('C*').unpack1('S*')
485
-
486
- if bytes[...2] != NULL_BYTES
487
- genesis_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
488
- genesis = bytes.shift(genesis_size)
489
- input['genesis'] = genesis.pack('C*').unpack1('H*')
490
- else
491
- bytes.shift 2
492
- end
493
-
494
- if bytes[...2] != NULL_BYTES
495
- magic = bytes.shift(2)
496
- raise ArgumentError, 'Not valid input' unless magic == MAGIC
497
-
498
- deposit = {}
499
- deposit['chain'] = bytes.shift(32).pack('C*').unpack1('H*')
500
-
501
- asset_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
502
- deposit['asset'] = bytes.shift(asset_size).unpack1('H*')
503
-
504
- transaction_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
505
- deposit['transaction'] = bytes.shift(transaction_size).unpack1('H*')
506
-
507
- deposit['index'] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
508
-
509
- amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
510
- deposit['amount'] = bytes_to_int bytes.shift(amount_size)
511
-
512
- input['deposit'] = deposit
513
- else
514
- bytes.shift 2
515
- end
516
-
517
- if bytes[...2] != NULL_BYTES
518
- magic = bytes.shift(2)
519
- raise ArgumentError, 'Not valid input' unless magic == MAGIC
520
-
521
- mint = {}
522
- if bytes[...2] != NULL_BYTES
523
- group_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
524
- mint['group'] = bytes.shift(group_size).unpack1('H*')
525
- else
526
- bytes.shift 2
527
- end
528
-
529
- mint['batch'] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
530
- _amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
531
- mint['amount'] = bytes_to_int bytes.shift(_amount_size)
532
-
533
- input['mint'] = mint
534
- else
535
- bytes.shift 2
536
- end
537
-
538
- tx['inputs'].push input
539
- end
540
-
541
- [bytes, tx]
542
- end
543
-
544
- def decode_outputs(bytes, tx)
545
- outputs_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
546
- tx['outputs'] = []
547
- outputs_size.times do
548
- output = {}
549
-
550
- bytes.shift
551
- type = bytes.shift
552
- output['type'] = type
553
-
554
- amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
555
- output['amount'] = format('%.8f', bytes_to_int(bytes.shift(amount_size)).to_f / 1e8)
556
-
557
- output['keys'] = []
558
- keys_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
559
- keys_size.times do
560
- output['keys'].push bytes.shift(32).pack('C*').unpack1('H*')
561
- end
562
-
563
- output['mask'] = bytes.shift(32).pack('C*').unpack1('H*')
564
-
565
- script_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
566
- output['script'] = bytes.shift(script_size).pack('C*').unpack1('H*')
567
-
568
- if bytes[...2] != NULL_BYTES
569
- magic = bytes.shift(2)
570
- raise ArgumentError, 'Not valid output' unless magic == MAGIC
571
-
572
- withdraw = {}
573
-
574
- output['chain'] = bytes.shift(32).pack('C*').unpack1('H*')
575
-
576
- asset_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
577
- output['asset'] = bytes.shift(asset_size).unpack1('H*')
578
-
579
- if bytes[...2] != NULL_BYTES
580
- address = {}
581
-
582
- adderss_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
583
- output['adderss'] = bytes.shift(adderss_size).pack('C*').unpack1('H*')
584
- else
585
- bytes.shift 2
586
- end
587
-
588
- if bytes[...2] != NULL_BYTES
589
- tag = {}
590
-
591
- tag_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
592
- output['tag'] = bytes.shift(tag_size).pack('C*').unpack1('H*')
593
- else
594
- bytes.shift 2
595
- end
596
- else
597
- bytes.shift 2
598
- end
599
-
600
- tx['outputs'].push output
601
- end
602
-
603
- [bytes, tx]
604
- end
605
115
  end
606
116
  end
607
117
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MixinBot
4
- VERSION = '0.7.11'
4
+ VERSION = '0.8.0'
5
5
  end
data/lib/mixin_bot.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # third-party dependencies
4
4
  require 'English'
5
- require 'active_support'
5
+ require 'active_support/all'
6
6
  require 'base64'
7
7
  require 'bigdecimal'
8
8
  require 'bigdecimal/util'
@@ -41,4 +41,7 @@ module MixinBot
41
41
  class InsufficientBalanceError < Error; end
42
42
  class InsufficientPoolError < Error; end
43
43
  class PinError < Error; end
44
+ class InvalidNfoFormatError < MixinBot::Error; end
45
+ class InvalidUuidFormatError < MixinBot::Error; end
46
+ class InvalidTransactionFormatError < MixinBot::Error; end
44
47
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixin_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.11
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - an-lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-07 00:00:00.000000000 Z
11
+ date: 2022-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -298,6 +298,9 @@ files:
298
298
  - lib/mixin_bot/cli/utils.rb
299
299
  - lib/mixin_bot/client.rb
300
300
  - lib/mixin_bot/utils.rb
301
+ - lib/mixin_bot/utils/nfo.rb
302
+ - lib/mixin_bot/utils/transaction.rb
303
+ - lib/mixin_bot/utils/uuid.rb
301
304
  - lib/mixin_bot/version.rb
302
305
  homepage: https://github.com/an-lee/mixin_bot
303
306
  licenses: