mixin_bot 0.7.11 → 0.8.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 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: