mixin_bot 0.7.13 → 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: 5cdd410480755e106ac96a3e492d2d4c30936dab79592442fc8ac939c3c4f184
4
- data.tar.gz: fe3e012d4fe40573d4843b552d8f6e8dcb4322cb28f7b5aad5ec73bc115ebe1f
3
+ metadata.gz: a76c8b7d2c5d8cebc963a56d350e3fd5e4bbc1e18ad3e037b65cfb6e1d77b0d1
4
+ data.tar.gz: 8c9813a57cb33f256c695a88e42f53feffb33a45979f5fbf788916f332a52d26
5
5
  SHA512:
6
- metadata.gz: 738d63fe68c680d5aef9a38ecc2d1235f8b7adb9e0acf4fec3996ac161af779bfaf0b8a4042850e3da5bd74099b99c9197fd84a3ca558df176aa8a63e001a3c9
7
- data.tar.gz: 51be6edd791ca609c3e40a7758ec132f29f86903cbfaf639a382cd95c9ebccee641d2d4497c113a12a8021f37d79055a58358ea225a16c7557c2b5c0dd16e062
6
+ metadata.gz: 999325d7c1b73c54b80a6a7a2e432e83aaff341997db01211fb43cddffa32448451aaf5c1d92852dfcda872d92b4724d938bacde83621650c20594aaa034ff32
7
+ data.tar.gz: 7b49bda1bb1026823ca51301a7f61380db7714f9d9441c466ff0a0c2bb50be52e199900e13b0d7c5e8e6782f938c57f38562e9d56439346c62b7abb364d928e5
@@ -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,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.13'
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.13
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-11 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: