mixin_bot 0.7.12 → 0.8.1

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