mixin_bot 0.6.0 → 0.6.1

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: 4534f4c2ea6ed1928f5c2a32b5221737ba94ff70cffdaa26a95ae03c710e5186
4
- data.tar.gz: dd4a3901a081d50f2c0d3ab751516956e4065c21acaadb980e836b25b099b90a
3
+ metadata.gz: fea4c2a8e738f32bcadb126f775042eb9c0a57d9fcfd168948127e8332e58a9a
4
+ data.tar.gz: 9f0699d3a105c499530230b565b6c22a8cd74dcda857a220fb6de73a899f1ffe
5
5
  SHA512:
6
- metadata.gz: 1a729cad7c8086ebfaae941b6ea4ba93318376e3effb469e42979317f048fb89c34b1b53425389c7da7138dddf6ebed80a643360c9a377c9c4c22d4834c02027
7
- data.tar.gz: 44d76715c4721f2db56a7fee621e3454f18556f94b41d225821bc352cb1fd8ddaa61c38f2c5fbc4380099ddd995e94bca841e743eedb1675b555bf5a79a8bf58
6
+ metadata.gz: 774bf51ca38c7e507e4e6da343e139500715d7c150fb04083f4418e9a3a8fe6627383230953807f7463cc041391e8649c01290e634783b6e04b47f150eb01fe6
7
+ data.tar.gz: 69732a0f23244b8c62aa4fd553af66239df418427c9f7b0afd1437cd8821a9d05df3ac8c1ad4d60c66559b613fce5dc4268e67b5122bae7d7aeff20273a301ce
@@ -3,6 +3,8 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Collectible
6
+ NFT_ASSET_MIXIN_ID = '1700941284a95f31b25ec8c546008f208f88eee4419ccdcdbe6e3195e60128ca'
7
+
6
8
  def collectible(id, access_token: nil)
7
9
  path = "/collectibles/tokens/#{id}"
8
10
  access_token ||= access_token('GET', path, '')
@@ -35,7 +37,7 @@ module MixinBot
35
37
 
36
38
  COLLECTABLE_REQUEST_ACTIONS = %i[sign unlock].freeze
37
39
  def create_collectible_request(action, raw, access_token: nil)
38
- raise ArgumentError, "request action is limited in #{COLLECTABLE_REQUEST_ACTIONS.join(', ')}" unless action.to_sym.in? COLLECTABLE_REQUEST_ACTIONS
40
+ raise ArgumentError, "request action is limited in #{COLLECTABLE_REQUEST_ACTIONS.join(', ')}" unless COLLECTABLE_REQUEST_ACTIONS.include? action.to_sym
39
41
  path = '/collectibles/requests'
40
42
  payload = {
41
43
  action: action,
@@ -47,11 +49,11 @@ module MixinBot
47
49
  end
48
50
 
49
51
  def create_sign_collectible_request(raw, access_token: nil)
50
- create_collectible_output 'sign', raw, access_token
52
+ create_collectible_request 'sign', raw, access_token: access_token
51
53
  end
52
54
 
53
55
  def create_unlock_collectible_request(raw, access_token: nil)
54
- create_collectible_output 'unlock', raw, access_token
56
+ create_collectible_request 'unlock', raw, access_token: access_token
55
57
  end
56
58
 
57
59
  def sign_collectible_request(request_id, pin)
@@ -85,6 +87,39 @@ module MixinBot
85
87
  end
86
88
  end
87
89
 
90
+ # collectible = {
91
+ # type: 'non_fungible_output',
92
+ # user_id: '',
93
+ # output_id: '',
94
+ # token_id: '',
95
+ # transaction_hash: '',
96
+ # output_index: '',
97
+ # amount: 1,
98
+ # senders: [],
99
+ # sender_threshold: 1,
100
+ # receivers: [],
101
+ # receivers_threshold: 1,
102
+ # state: 'unspent'
103
+ # }
104
+ COLLECTIBLE_TRANSACTION_ARGUMENTS = %i[collectible nfo receivers threshold].freeze
105
+ def build_collectible_transaction(**kwargs)
106
+ raise ArgumentError, "#{COLLECTIBLE_TRANSACTION_ARGUMENTS.join(', ')} are needed for build collectible transaction" unless COLLECTIBLE_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
107
+
108
+ kwargs = kwargs.with_indifferent_access
109
+ collectible = kwargs['collectible']
110
+ raise "collectible is #{collectible['state']}" unless collectible['state'] == 'unspent'
111
+
112
+ build_raw_transaction(
113
+ utxos: [collectible],
114
+ senders: collectible['receivers'],
115
+ receivers: kwargs['receivers'],
116
+ threshold: kwargs['threshold'],
117
+ extra: kwargs["nfo"],
118
+ amount: 1,
119
+ asset_mixin_id: NFT_ASSET_MIXIN_ID
120
+ )
121
+ end
122
+
88
123
  def nft_memo(collection, token_id, meta)
89
124
  MixinBot::Utils.nft_memo collection, token_id, meta
90
125
  end
@@ -40,7 +40,7 @@ module MixinBot
40
40
 
41
41
  MULTISIG_REQUEST_ACTIONS = %i[sign unlock].freeze
42
42
  def create_multisig_request(action, raw, access_token: nil)
43
- raise ArgumentError, "request action is limited in #{MULTISIG_REQUEST_ACTIONS.join(', ')}" unless action.to_sym.in? MULTISIG_REQUEST_ACTIONS
43
+ raise ArgumentError, "request action is limited in #{MULTISIG_REQUEST_ACTIONS.join(', ')}" unless MULTISIG_REQUEST_ACTIONS.include? action.to_sym
44
44
 
45
45
  path = '/multisigs/requests'
46
46
  payload = {
@@ -54,13 +54,13 @@ module MixinBot
54
54
 
55
55
  # transfer from the multisig address
56
56
  def create_sign_multisig_request(raw, access_token: nil)
57
- create_multisig_request 'sign', raw, access_token
57
+ create_multisig_request 'sign', raw, access_token: access_token
58
58
  end
59
59
 
60
60
  # transfer from the multisig address
61
61
  # create a request for unlock a multi-sign
62
62
  def create_unlock_multisig_request(raw, access_token: nil)
63
- create_multisig_request 'unlock', raw, access_token
63
+ create_multisig_request 'unlock', raw, access_token: access_token
64
64
  end
65
65
 
66
66
  def sign_multisig_request(request_id, pin)
@@ -151,7 +151,7 @@ module MixinBot
151
151
  # amount: string / float,
152
152
  # memo: string,
153
153
  # }
154
- RAW_TRANSACTION_ARGUMENTS = %i[senders receivers amount threshold asset_id].freeze
154
+ RAW_TRANSACTION_ARGUMENTS = %i[utxos senders receivers amount threshold].freeze
155
155
  def build_raw_transaction(**kwargs)
156
156
  raise ArgumentError, "#{RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build raw transaction" unless RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
157
157
 
@@ -160,24 +160,14 @@ module MixinBot
160
160
  amount = kwargs[:amount]
161
161
  threshold = kwargs[:threshold]
162
162
  asset_id = kwargs[:asset_id]
163
+ asset_mixin_id = kwargs[:asset_mixin_id]
163
164
  utxos = kwargs[:utxos]
164
165
  memo = kwargs[:memo]
166
+ extra = kwargs[:extra]
165
167
  access_token = kwargs[:access_token]
166
168
 
167
169
  raise 'access_token required!' if access_token.nil? && !senders.include?(client_id)
168
170
 
169
- # default to use all(first 100) unspent utxo
170
- utxos ||= multisigs(
171
- members: senders,
172
- threshold: threshold,
173
- state: 'unspent',
174
- access_token: access_token
175
- )['data'].filter(
176
- &lambda { |utxo|
177
- utxo['asset_id'] == kwargs[:asset_id]
178
- }
179
- )
180
-
181
171
  amount = amount.to_f.round(8)
182
172
  input_amount = utxos.map(
183
173
  &lambda { |utxo|
@@ -221,10 +211,11 @@ module MixinBot
221
211
  }
222
212
  end
223
213
 
224
- extra = Digest.hexencode memo.to_s.slice(0, 140)
214
+ extra = extra || Digest.hexencode(memo.to_s.slice(0, 140))
215
+ asset = asset_mixin_id || SHA3::Digest::SHA256.hexdigest(asset_id)
225
216
  tx = {
226
217
  version: 2,
227
- asset: SHA3::Digest::SHA256.hexdigest(asset_id),
218
+ asset: asset,
228
219
  inputs: inputs,
229
220
  outputs: outputs,
230
221
  extra: extra
data/lib/mixin_bot/api.rb CHANGED
@@ -48,6 +48,10 @@ module MixinBot
48
48
  MixinBot::Utils.sign_raw_transaction tx
49
49
  end
50
50
 
51
+ def decode_raw_transaction(raw)
52
+ MixinBot::Utils.decode_raw_transaction raw
53
+ end
54
+
51
55
  # Use a mixin software to implement transaction build
52
56
  def sign_raw_transaction_native(json)
53
57
  ensure_mixin_command_exist
@@ -59,6 +63,17 @@ module MixinBot
59
63
  output.chomp
60
64
  end
61
65
 
66
+ # Use a mixin software to implement transaction build
67
+ def decode_raw_transaction_native(raw)
68
+ ensure_mixin_command_exist
69
+ command = format("mixin decoderawtransaction --raw '%<arg>s'", arg: raw)
70
+
71
+ output, error = Open3.capture3(command)
72
+ raise error unless error.empty?
73
+
74
+ JSON.parse output.chomp
75
+ end
76
+
62
77
  include MixinBot::API::App
63
78
  include MixinBot::API::Asset
64
79
  include MixinBot::API::Attachment
@@ -58,6 +58,7 @@ module MixinBot
58
58
  if tx.is_a? String
59
59
  tx = JSON.parse tx
60
60
  end
61
+ raise "#{tx} is not a valid json" unless tx.is_a? Hash
61
62
 
62
63
  tx = tx.with_indifferent_access
63
64
  bytes = []
@@ -67,6 +68,8 @@ module MixinBot
67
68
 
68
69
  # version
69
70
  bytes += [0, tx['version']]
71
+
72
+ # asset
70
73
  bytes += [tx['asset']].pack('H*').bytes
71
74
 
72
75
  # inputs
@@ -92,6 +95,72 @@ module MixinBot
92
95
  bytes.pack('C*').unpack1('H*')
93
96
  end
94
97
 
98
+ def decode_raw_transaction(raw)
99
+ bytes = [raw].pack('H*').bytes
100
+ tx = {}
101
+
102
+ magic = bytes.shift(2)
103
+ raise 'Not valid raw' unless magic == MAGIC
104
+
105
+ version = bytes.shift(2)
106
+ tx['version'] = bytes_to_int version
107
+
108
+ asset = bytes.shift(32)
109
+ tx['asset'] = asset.pack('C*').unpack1('H*')
110
+
111
+ # read inputs
112
+ bytes, tx = decode_inputs bytes, tx
113
+
114
+ # read outputs
115
+ bytes, tx = decode_outputs bytes, tx
116
+
117
+ extra_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
118
+ tx['extra'] = bytes.shift(extra_size).pack('C*').unpack1('H*')
119
+
120
+ num = bytes.shift(2).reverse.pack('C*').unpack1('S*')
121
+ if num == MAX_ENCODE_INT
122
+ # aggregated
123
+ aggregated = {}
124
+
125
+ raise 'invalid aggregated' unless bytes.shift(2).reverse.pack('C*').unpack1('S*') == AGGREGATED_SIGNATURE_PREFIX
126
+
127
+ aggregated['signature'] = bytes.shift(64).pack('C*').unpack1('H*')
128
+
129
+ byte = bytes.shift
130
+ case byte
131
+ when AGGREGATED_SIGNATURE_ORDINAY_MASK.first
132
+ aggregated['signers'] = []
133
+ masks_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
134
+ masks = bytes.shift(masks_size)
135
+ masks = [masks] unless masks.is_a? Array
136
+
137
+ masks.each_with_index do |mask, i|
138
+ 8.times do |j|
139
+ k = 1 << j
140
+ aggregated['signers'].push(i * 8 + j) if mask & k == k
141
+ end
142
+ end
143
+ when AGGREGATED_SIGNATURE_SPARSE_MASK.first
144
+ signers_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
145
+ return if signers_size == 0
146
+
147
+ aggregated['signers'] = []
148
+ signers_size.times do
149
+ aggregated['signers'].push bytes.shift(2).reverse.pack('C*').unpack1('S*')
150
+ end
151
+ end
152
+
153
+ tx['aggregated'] = aggregated
154
+ else
155
+ if !bytes.empty? && bytes[...2] != NULL_BYTES
156
+ signatures_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
157
+ tx['signatures'] = bytes.shift(signatures_size).pack('C*').unpack1('H*')
158
+ end
159
+ end
160
+
161
+ tx
162
+ end
163
+
95
164
  def nft_memo_hash(collection, token_id, meta)
96
165
  collection = NULL_UUID if collection.empty?
97
166
  meta = meta.to_json if meta.is_a?(Hash)
@@ -225,9 +294,9 @@ module MixinBot
225
294
  # genesis
226
295
  genesis = input['genesis'] || ''
227
296
  if genesis.empty?
228
- bytes += encode_int 0
297
+ bytes += NULL_BYTES
229
298
  else
230
- genesis_bytes = [genesis].pack('H*')
299
+ genesis_bytes = [genesis].pack('H*').bytes
231
300
  bytes += encode_int genesis_bytes.size
232
301
  bytes += genesis_bytes
233
302
  end
@@ -265,7 +334,7 @@ module MixinBot
265
334
  # group
266
335
  group = mint['group'] || ''
267
336
  if group.empty?
268
- bytes += encode_int 0
337
+ bytes += encode_int NULL_BYTES
269
338
  else
270
339
  group_bytes = [group].pack('H*')
271
340
  bytes += encode_int group_bytes.size
@@ -349,14 +418,14 @@ module MixinBot
349
418
  end
350
419
 
351
420
  def encode_aggregated_signature(aggregated, bytes = [])
352
- bytes += MAX_ENCODE_INT
421
+ bytes += encode_int MAX_ENCODE_INT
353
422
  bytes += encode_int AGGREGATED_SIGNATURE_PREFIX
354
423
  bytes += [aggregated['signature']].pack('H*').bytes
355
424
 
356
425
  signers = aggregated['signers']
357
426
  if signers.size == 0
358
427
  bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
359
- bytes += encode_int 0
428
+ bytes += NULL_BYTES
360
429
  else
361
430
  signers.each do |sig, i|
362
431
  raise 'signers not sorted' if i > 0 && sig <= signers[i - 1]
@@ -366,7 +435,7 @@ module MixinBot
366
435
  max = signers.last
367
436
  if (((max / 8 | 0) + 1 | 0) > aggregated['signature'].size * 2)
368
437
  bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
369
- bytes += encode_int aggregated['signature'].size
438
+ bytes += encode_int aggregated['signers'].size
370
439
  signers.map(&->(signer) { bytes += encode_int(signer) })
371
440
  end
372
441
 
@@ -403,6 +472,137 @@ module MixinBot
403
472
 
404
473
  bytes
405
474
  end
475
+
476
+ def decode_inputs(bytes, tx)
477
+ inputs_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
478
+ tx['inputs'] = []
479
+ inputs_size.times do
480
+ input = {}
481
+ hash = bytes.shift(32)
482
+ input['hash'] = hash.pack('C*').unpack1('H*')
483
+
484
+ index = bytes.shift(2)
485
+ input['index'] = index.reverse.pack('C*').unpack1('S*')
486
+
487
+ if bytes[...2] != NULL_BYTES
488
+ genesis_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
489
+ genesis = bytes.shift(genesis_size)
490
+ input['genesis'] = genesis.pack('C*').unpack1('H*')
491
+ else
492
+ bytes.shift 2
493
+ end
494
+
495
+ if bytes[...2] != NULL_BYTES
496
+ magic = bytes.shift(2)
497
+ raise 'Not valid input' unless magic == MAGIC
498
+
499
+ deposit = {}
500
+ deposit['chain'] = bytes.shift(32).pack('C*').unpack1('H*')
501
+
502
+ asset_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
503
+ deposit['asset'] = bytes.shift(asset_size).unpack1('H*')
504
+
505
+ transaction_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
506
+ deposit['transaction'] = bytes.shift(transaction_size).unpack1('H*')
507
+
508
+ deposit['index'] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
509
+
510
+ amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
511
+ deposit['amount'] = bytes_to_int bytes.shift(amount_size)
512
+
513
+ input['deposit'] = deposit
514
+ else
515
+ bytes.shift 2
516
+ end
517
+
518
+ if bytes[...2] != NULL_BYTES
519
+ magic = bytes.shift(2)
520
+ raise 'Not valid input' unless magic == MAGIC
521
+
522
+ mint = {}
523
+ if bytes[...2] != NULL_BYTES
524
+ group_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
525
+ mint['group'] = bytes.shift(group_size).unpack1('H*')
526
+ else
527
+ bytes.shift 2
528
+ end
529
+
530
+ mint['batch'] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
531
+ _amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
532
+ mint['amount'] = bytes_to_int bytes.shift(_amount_size)
533
+
534
+ input['mint'] = mint
535
+ else
536
+ bytes.shift 2
537
+ end
538
+
539
+ tx['inputs'].push input
540
+ end
541
+
542
+ [bytes, tx]
543
+ end
544
+
545
+ def decode_outputs(bytes, tx)
546
+ outputs_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
547
+ tx['outputs'] = []
548
+ outputs_size.times do
549
+ output = {}
550
+
551
+ bytes.shift
552
+ type = bytes.shift
553
+ output['type'] = type
554
+
555
+ amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
556
+ output['amount'] = format('%.8f', bytes_to_int(bytes.shift(amount_size)).to_f / 1e8)
557
+
558
+ output['keys'] = []
559
+ keys_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
560
+ keys_size.times do
561
+ output['keys'].push bytes.shift(32).pack('C*').unpack1('H*')
562
+ end
563
+
564
+ output['mask'] = bytes.shift(32).pack('C*').unpack1('H*')
565
+
566
+ script_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
567
+ output['script'] = bytes.shift(script_size).pack('C*').unpack1('H*')
568
+
569
+ if bytes[...2] != NULL_BYTES
570
+ magic = bytes.shift(2)
571
+ raise 'Not valid output' unless magic == MAGIC
572
+
573
+ withdraw = {}
574
+
575
+ output['chain'] = bytes.shift(32).pack('C*').unpack1('H*')
576
+
577
+ asset_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
578
+ output['asset'] = bytes.shift(asset_size).unpack1('H*')
579
+
580
+ if bytes[...2] != NULL_BYTES
581
+ address = {}
582
+
583
+ adderss_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
584
+ output['adderss'] = bytes.shift(adderss_size).pack('C*').unpack1('H*')
585
+ else
586
+ bytes.shift 2
587
+ end
588
+
589
+ if bytes[...2] != NULL_BYTES
590
+ tag = {}
591
+
592
+ tag_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
593
+ output['tag'] = bytes.shift(tag_size).pack('C*').unpack1('H*')
594
+ else
595
+ bytes.shift 2
596
+ end
597
+ else
598
+ bytes.shift 2
599
+ end
600
+
601
+ tx['outputs'].push output
602
+ end
603
+
604
+ [bytes, tx]
605
+ end
406
606
  end
407
607
  end
408
608
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MixinBot
4
- VERSION = '0.6.0'
4
+ VERSION = '0.6.1'
5
5
  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.6.0
4
+ version: 0.6.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: 2021-10-27 00:00:00.000000000 Z
11
+ date: 2021-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport