mixin_bot 0.6.0 → 0.6.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: 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