mixin_bot 0.12.1 → 1.1.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mixin_bot/api/address.rb +21 -0
  3. data/lib/mixin_bot/api/app.rb +5 -11
  4. data/lib/mixin_bot/api/asset.rb +9 -16
  5. data/lib/mixin_bot/api/attachment.rb +27 -22
  6. data/lib/mixin_bot/api/auth.rb +34 -56
  7. data/lib/mixin_bot/api/blaze.rb +4 -3
  8. data/lib/mixin_bot/api/conversation.rb +29 -49
  9. data/lib/mixin_bot/api/encrypted_message.rb +19 -19
  10. data/lib/mixin_bot/api/inscription.rb +71 -0
  11. data/lib/mixin_bot/api/legacy_collectible.rb +140 -0
  12. data/lib/mixin_bot/api/legacy_multisig.rb +87 -0
  13. data/lib/mixin_bot/api/legacy_output.rb +50 -0
  14. data/lib/mixin_bot/api/legacy_payment.rb +31 -0
  15. data/lib/mixin_bot/api/legacy_snapshot.rb +39 -0
  16. data/lib/mixin_bot/api/legacy_transaction.rb +173 -0
  17. data/lib/mixin_bot/api/legacy_transfer.rb +42 -0
  18. data/lib/mixin_bot/api/me.rb +13 -17
  19. data/lib/mixin_bot/api/message.rb +13 -10
  20. data/lib/mixin_bot/api/multisig.rb +17 -222
  21. data/lib/mixin_bot/api/output.rb +48 -0
  22. data/lib/mixin_bot/api/payment.rb +9 -20
  23. data/lib/mixin_bot/api/pin.rb +57 -65
  24. data/lib/mixin_bot/api/rpc.rb +12 -14
  25. data/lib/mixin_bot/api/snapshot.rb +15 -29
  26. data/lib/mixin_bot/api/tip.rb +43 -0
  27. data/lib/mixin_bot/api/transaction.rb +295 -60
  28. data/lib/mixin_bot/api/transfer.rb +69 -31
  29. data/lib/mixin_bot/api/user.rb +88 -53
  30. data/lib/mixin_bot/api/withdraw.rb +52 -53
  31. data/lib/mixin_bot/api.rb +81 -46
  32. data/lib/mixin_bot/cli/api.rb +149 -5
  33. data/lib/mixin_bot/cli/utils.rb +14 -4
  34. data/lib/mixin_bot/cli.rb +13 -10
  35. data/lib/mixin_bot/client.rb +74 -127
  36. data/lib/mixin_bot/configuration.rb +98 -0
  37. data/lib/mixin_bot/nfo.rb +174 -0
  38. data/lib/mixin_bot/transaction.rb +524 -0
  39. data/lib/mixin_bot/utils/address.rb +121 -0
  40. data/lib/mixin_bot/utils/crypto.rb +218 -0
  41. data/lib/mixin_bot/utils/decoder.rb +56 -0
  42. data/lib/mixin_bot/utils/encoder.rb +63 -0
  43. data/lib/mixin_bot/utils.rb +8 -109
  44. data/lib/mixin_bot/uuid.rb +41 -0
  45. data/lib/mixin_bot/version.rb +1 -1
  46. data/lib/mixin_bot.rb +39 -14
  47. data/lib/mvm/bridge.rb +2 -19
  48. data/lib/mvm/client.rb +11 -33
  49. data/lib/mvm/nft.rb +4 -4
  50. data/lib/mvm/registry.rb +9 -9
  51. data/lib/mvm/scan.rb +3 -5
  52. data/lib/mvm.rb +5 -6
  53. metadata +77 -103
  54. data/lib/mixin_bot/api/collectible.rb +0 -138
  55. data/lib/mixin_bot/utils/nfo.rb +0 -176
  56. data/lib/mixin_bot/utils/transaction.rb +0 -478
  57. data/lib/mixin_bot/utils/uuid.rb +0 -43
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class API
5
+ module Tip
6
+ TIP_ACTIONS = %w[
7
+ TIP:VERIFY:
8
+ TIP:ADDRESS:ADD:
9
+ TIP:ADDRESS:REMOVE:
10
+ TIP:USER:DEACTIVATE:
11
+ TIP:EMERGENCY:CONTACT:CREATE:
12
+ TIP:EMERGENCY:CONTACT:READ:
13
+ TIP:EMERGENCY:CONTACT:REMOVE:
14
+ TIP:PHONE:NUMBER:UPDATE:
15
+ TIP:MULTISIG:REQUEST:SIGN:
16
+ TIP:MULTISIG:REQUEST:UNLOCK:
17
+ TIP:COLLECTIBLE:REQUEST:SIGN:
18
+ TIP:COLLECTIBLE:REQUEST:UNLOCK:
19
+ TIP:TRANSFER:CREATE:
20
+ TIP:WITHDRAWAL:CREATE:
21
+ TIP:TRANSACTION:CREATE:
22
+ TIP:OAUTH:APPROVE:
23
+ TIP:PROVISIONING:UPDATE:
24
+ TIP:APP:OWNERSHIP:TRANSFER:
25
+ SEQUENCER:REGISTER:
26
+ ].freeze
27
+
28
+ def encrypt_tip_pin(pin, action, *params)
29
+ raise ArgumentError, 'invalid action' unless TIP_ACTIONS.include? action
30
+
31
+ pin_key = MixinBot.utils.decode_key pin
32
+
33
+ msg = action + params.flatten.map(&:to_s).join
34
+
35
+ msg = Digest::SHA256.digest(msg) unless action == 'TIP:VERIFY:'
36
+
37
+ signature = JOSE::JWA::Ed25519.sign msg, pin_key
38
+
39
+ encrypt_pin signature
40
+ end
41
+ end
42
+ end
43
+ end
@@ -3,75 +3,310 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Transaction
6
- MULTISIG_TRANSACTION_ARGUMENTS = %i[asset_id receivers threshold amount].freeze
7
- def create_multisig_transaction(pin, options = {})
8
- raise ArgumentError, "#{MULTISIG_TRANSACTION_ARGUMENTS.join(', ')} are needed for create multisig transaction" unless MULTISIG_TRANSACTION_ARGUMENTS.all? { |param| options.keys.include? param }
9
-
10
- asset_id = options[:asset_id]
11
- receivers = options[:receivers]
12
- threshold = options[:threshold]
13
- amount = options[:amount].to_d
14
- memo = options[:memo]
15
- trace_id = options[:trace_id] || SecureRandom.uuid
16
- encrypted_pin = options[:encrypted_pin] || encrypt_pin(pin)
17
-
18
- path = '/transactions'
19
- payload = {
20
- asset_id: asset_id,
21
- opponent_multisig: {
22
- receivers: receivers,
23
- threshold: threshold
24
- },
25
- pin: encrypted_pin,
26
- amount: format('%.8f', amount.to_r),
27
- trace_id: trace_id,
28
- memo: memo
29
- }
6
+ SAFE_TX_VERSION = 0x05
7
+ OUTPUT_TYPE_SCRIPT = 0x00
8
+ OUTPUT_TYPE_WITHDRAW_SUBMIT = 0xa1
9
+ XIN_ASSET_ID = 'c94ac88f-4671-3976-b60a-09064f1811e8'
10
+ EXTRA_SIZE_STORAGE_CAPACITY = 1024 * 1024 * 4
11
+ EXTRA_STORAGE_PRICE_STEP = 0.0001
12
+
13
+ def create_safe_keys(*payload, access_token: nil)
14
+ raise ArgumentError, 'payload should be an array' unless payload.is_a? Array
15
+ raise ArgumentError, 'payload should not be empty' unless payload.size.positive?
16
+ raise ArgumentError, 'invalid payload' unless payload.all?(&lambda { |param|
17
+ param.key?(:receivers) && param.key?(:index)
18
+ })
19
+
20
+ payload.each do |param|
21
+ param[:hint] ||= SecureRandom.uuid
22
+ end
23
+
24
+ path = '/safe/keys'
25
+
26
+ client.post path, *payload, access_token:
27
+ end
28
+ alias create_ghost_keys create_safe_keys
29
+
30
+ def generate_safe_keys(recipients)
31
+ raise ArgumentError, 'recipients should be an array' unless recipients.is_a? Array
32
+
33
+ ghost_keys = []
34
+ uuid_recipients = []
30
35
 
31
- access_token = options[:access_token]
32
- access_token ||= access_token('POST', path, payload.to_json)
33
- authorization = format('Bearer %<access_token>s', access_token: access_token)
34
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
36
+ recipients.each_with_index do |recipient, index|
37
+ next if recipient[:mix_address].blank?
38
+
39
+ if recipient[:members].all?(&->(m) { m.start_with? MixinBot::Utils::Address::MAIN_ADDRESS_PREFIX })
40
+ key = JOSE::JWA::Ed25519.keypair
41
+ gk = {
42
+ mask: key[0].unpack1('H*'),
43
+ keys: []
44
+ }
45
+ recipient[:members].each do |member|
46
+ payload = MixinBot.utils.parse_main_address member
47
+ spend_key = payload[0...32]
48
+ view_key = payload[-32..]
49
+
50
+ ghost_public_key = MixinBot.utils.derive_ghost_public_key(key[1], view_key, spend_key, index)
51
+
52
+ gk[:keys] << ghost_public_key.unpack1('H*')
53
+ end
54
+
55
+ ghost_keys[index] = gk.with_indifferent_access
56
+
57
+ elsif recipient[:members].none?(&->(m) { m.start_with? MixinBot::Utils::Address::MAIN_ADDRESS_PREFIX })
58
+ uuid_recipients.push(
59
+ {
60
+ receivers: recipient[:members],
61
+ index:,
62
+ hint: SecureRandom.uuid
63
+ }.with_indifferent_access
64
+ )
65
+ end
66
+ end
67
+
68
+ if uuid_recipients.present?
69
+ keys = create_safe_keys(*uuid_recipients)['data']
70
+ keys.each_with_index do |key, index|
71
+ ghost_keys[uuid_recipients[index][:index]] = key
72
+ end
73
+ end
74
+
75
+ ghost_keys
35
76
  end
36
77
 
37
- MAINNET_TRANSACTION_ARGUMENTS = %i[asset_id opponent_key amount].freeze
38
- def create_mainnet_transaction(pin, options = {})
39
- raise ArgumentError, "#{MAINNET_TRANSACTION_ARGUMENTS.join(', ')} are needed for create main net transactions" unless MAINNET_TRANSACTION_ARGUMENTS.all? { |param| options.keys.include? param }
40
-
41
- asset_id = options[:asset_id]
42
- opponent_key = options[:opponent_key]
43
- amount = options[:amount].to_d
44
- memo = options[:memo]
45
- trace_id = options[:trace_id] || SecureRandom.uuid
46
- encrypted_pin = options[:encrypted_pin] || encrypt_pin(pin)
47
-
48
- path = '/transactions'
49
- payload = {
50
- asset_id: asset_id,
51
- opponent_key: opponent_key,
52
- pin: encrypted_pin,
53
- amount: format('%.8f', amount.to_r),
54
- trace_id: trace_id,
55
- memo: memo
78
+ # kwargs:
79
+ # {
80
+ # utxos: [ utxo ],
81
+ # receivers: [ {
82
+ # members: [ uuid ],
83
+ # threshold: integer,
84
+ # amount: string,
85
+ # } ],
86
+ # ghosts: [ ghost ],
87
+ # extra: string,
88
+ # }
89
+ SAFE_RAW_TRANSACTION_ARGUMENTS = %i[utxos receivers].freeze
90
+ def build_safe_transaction(**kwargs)
91
+ raise ArgumentError, "#{SAFE_RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build safe transaction" unless SAFE_RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
92
+ raise ArgumentError, 'receivers should be an array' unless kwargs[:receivers].is_a? Array
93
+ raise ArgumentError, 'utxos should be an array' unless kwargs[:utxos].is_a? Array
94
+
95
+ utxos = kwargs[:utxos].map(&:with_indifferent_access)
96
+ receivers = kwargs[:receivers].map(&:with_indifferent_access)
97
+
98
+ senders = utxos.map { |utxo| utxo['receivers'] }.uniq
99
+ raise ArgumentError, 'utxos should have same senders' if senders.size > 1
100
+
101
+ senders_threshold = utxos.map { |utxo| utxo['receivers_threshold'] }.uniq
102
+ raise ArgumentError, 'utxos should have same senders_threshold' if senders_threshold.size > 1
103
+
104
+ raise ArgumentError, 'utxos should not be empty' if utxos.empty?
105
+ raise ArgumentError, 'utxos too many' if utxos.size > 256
106
+
107
+ recipients = receivers.map do |receiver|
108
+ MixinBot.utils.build_safe_recipient(
109
+ members: receiver[:members],
110
+ threshold: receiver[:threshold],
111
+ amount: receiver[:amount]
112
+ ).with_indifferent_access
113
+ end
114
+
115
+ inputs_sum = utxos.sum(&->(utxo) { utxo['amount'].to_d })
116
+ outputs_sum = recipients.sum(&->(recipient) { recipient['amount'].to_d })
117
+ change = inputs_sum - outputs_sum
118
+ raise InsufficientBalanceError, "inputs sum #{inputs_sum} < outputs sum #{outputs_sum}" if change.negative?
119
+
120
+ if change.positive?
121
+ recipients << MixinBot.utils.build_safe_recipient(
122
+ members: utxos.first['receivers'],
123
+ threshold: utxos.first['receivers_threshold'],
124
+ amount: change
125
+ ).with_indifferent_access
126
+ end
127
+ raise ArgumentError, 'recipients too many' if recipients.size > 256
128
+
129
+ asset = utxos[0]['asset']
130
+ inputs = []
131
+ utxos.each do |utxo|
132
+ raise ArgumentError, 'utxo asset not match' unless utxo['asset'] == asset
133
+
134
+ inputs << {
135
+ hash: utxo['transaction_hash'],
136
+ index: utxo['output_index']
137
+ }
138
+ end
139
+
140
+ ghosts = generate_safe_keys(recipients)
141
+
142
+ outputs = []
143
+ recipients.each_with_index do |recipient, index|
144
+ outputs << if recipient['destination']
145
+ {
146
+ type: OUTPUT_TYPE_WITHDRAW_SUBMIT,
147
+ amount: recipient['amount'],
148
+ withdrawal: {
149
+ address: recipient['destination'],
150
+ tag: recipient['tag'] || ''
151
+ }
152
+ }
153
+ else
154
+ {
155
+ type: OUTPUT_TYPE_SCRIPT,
156
+ amount: recipient['amount'],
157
+ keys: ghosts[index]['keys'],
158
+ mask: ghosts[index]['mask'],
159
+ script: build_threshold_script(recipient['threshold'])
160
+ }
161
+ end
162
+ end
163
+
164
+ {
165
+ version: SAFE_TX_VERSION,
166
+ asset:,
167
+ inputs:,
168
+ outputs:,
169
+ extra: kwargs[:extra] || '',
170
+ references: kwargs[:references] || []
56
171
  }
172
+ end
173
+
174
+ def create_safe_transaction_request(request_id, raw)
175
+ path = '/safe/transaction/requests'
176
+ payload = [{
177
+ request_id:,
178
+ raw:
179
+ }]
180
+
181
+ client.post path, *payload
182
+ end
57
183
 
58
- access_token = options[:access_token]
59
- access_token ||= access_token('POST', path, payload.to_json)
60
- authorization = format('Bearer %<access_token>s', access_token: access_token)
61
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
184
+ def send_safe_transaction(request_id, raw)
185
+ path = '/safe/transactions'
186
+ payload = [{
187
+ request_id:,
188
+ raw:
189
+ }]
190
+
191
+ client.post path, *payload
192
+ end
193
+
194
+ def safe_transaction(request_id, access_token: nil)
195
+ path = format('/safe/transactions/%<request_id>s', request_id:)
196
+
197
+ client.get path, access_token:
62
198
  end
63
199
 
64
- def transactions(**options)
65
- path = format(
66
- '/external/transactions?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&destination=%<destination>s&tag=%<tag>s',
67
- limit: options[:limit],
68
- offset: options[:offset],
69
- asset: options[:asset],
70
- destination: options[:destination],
71
- tag: options[:tag]
200
+ SIGN_SAFE_TRANSACTION_ARGUMENTS = %i[raw utxos request spend_key].freeze
201
+ def sign_safe_transaction(**kwargs)
202
+ raise ArgumentError, "#{SIGN_SAFE_TRANSACTION_ARGUMENTS.join(', ')} are needed for sign safe transaction" unless SIGN_SAFE_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
203
+
204
+ raw = kwargs[:raw]
205
+ tx = MixinBot.utils.decode_raw_transaction raw
206
+ utxos = kwargs[:utxos]
207
+ request = kwargs[:request]
208
+ spend_key = MixinBot.utils.decode_key(kwargs[:spend_key]) || config.spend_key
209
+ spend_key = Digest::SHA512.digest spend_key[...32]
210
+
211
+ msg = [raw].pack('H*')
212
+
213
+ y_scalar = JOSE::JWA::FieldElement.new(
214
+ JOSE::JWA::X25519.clamp_scalar(spend_key[...32]).x,
215
+ JOSE::JWA::Edwards25519Point::L
72
216
  )
73
217
 
74
- client.get path
218
+ tx[:signatures] = []
219
+ tx[:inputs].each_with_index do |input, index|
220
+ utxo = utxos[index]
221
+ raise ArgumentError, 'utxo not match' unless input['hash'] == utxo['transaction_hash'] && input['index'] == utxo['output_index']
222
+
223
+ view = [request['views'][index]].pack('H*')
224
+ x_scalar = MixinBot.utils.scalar_from_bytes(view)
225
+
226
+ t_scalar = x_scalar + y_scalar
227
+ key = t_scalar.to_bytes(JOSE::JWA::Edwards25519Point::B)
228
+
229
+ pub = MixinBot.utils.shared_public_key key
230
+ key_index = utxo['keys'].index pub.unpack1('H*')
231
+ raise ArgumentError, 'cannot find valid key' unless key_index.is_a? Integer
232
+
233
+ signature = MixinBot.utils.sign(msg, key:)
234
+ signature = signature.unpack1('H*')
235
+ sig = {}
236
+ sig[key_index] = signature
237
+ tx[:signatures] << sig
238
+ end
239
+
240
+ MixinBot.utils.encode_raw_transaction tx
241
+ end
242
+
243
+ def build_object_transaction(extra, **)
244
+ extra = extra.to_s
245
+ raise ArgumentError, 'Extra too large' if extra.bytesize > EXTRA_SIZE_STORAGE_CAPACITY
246
+
247
+ # calculate fee base on extra length
248
+ amount = EXTRA_STORAGE_PRICE_STEP * ((extra.bytesize / 1024) + 1)
249
+
250
+ # burning address
251
+ receivers = [
252
+ {
253
+ members: [MixinBot.utils.burning_address],
254
+ threshold: 64,
255
+ amount:
256
+ }
257
+ ]
258
+
259
+ # find XIN utxos
260
+ utxos = build_utxos(asset_id: XIN_ASSET_ID, amount:)
261
+
262
+ # build transaction
263
+ build_safe_transaction utxos:, receivers:, extra:, **
264
+ end
265
+
266
+ INSCRIBE_TRANSACTION_ARGUMENTS = %i[content collection_hash].freeze
267
+ def build_inscribe_transaction(**kwargs)
268
+ raise ArgumentError, "#{INSCRIBE_TRANSACTION_ARGUMENTS.join(', ')} are needed for inscribe transaction" unless INSCRIBE_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
269
+
270
+ receivers = kwargs[:receivers].presence || [config.app_id]
271
+ receivers_threshold = kwargs[:receivers_threshold] || receivers.length
272
+ recipient = MixinBot.utils.build_mix_address(receivers, receivers_threshold)
273
+
274
+ content = kwargs[:content]
275
+ collection_hash = kwargs[:collection_hash]
276
+
277
+ data = {
278
+ operation: 'inscribe',
279
+ recipient:,
280
+ content:
281
+ }
282
+
283
+ MixinBot.api.build_object_transaction data.to_json, references: [collection_hash]
284
+ end
285
+
286
+ OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS = %i[amount inscription_hash utxos].freeze
287
+ def build_occupy_transaction(**kwargs)
288
+ raise ArgumentError, "#{OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS.join(', ')} are needed for occupy NFT transaction" unless OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
289
+
290
+ members = kwargs[:members].presence || [config.app_id]
291
+ threshold = kwargs[:threshold] || members.length
292
+ amount = kwargs[:amount]
293
+ inscription_hash = kwargs[:inscription_hash]
294
+
295
+ receivers = [
296
+ {
297
+ members:,
298
+ threshold:,
299
+ amount:
300
+ }
301
+ ]
302
+
303
+ extra = {
304
+ operation: 'occupy',
305
+ recipient:,
306
+ content:
307
+ }.to_json
308
+
309
+ MixinBot.api.build_safe_transaction(utxos:, receivers:, extra:, references: [inscription_hash])
75
310
  end
76
311
  end
77
312
  end
@@ -3,40 +3,78 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Transfer
6
- TRANSFER_ARGUMENTS = %i[asset_id opponent_id amount].freeze
7
- def create_transfer(pin, options = {})
8
- raise ArgumentError, "#{TRANSFER_ARGUMENTS.join(', ')} are needed for create transfer" unless TRANSFER_ARGUMENTS.all? { |param| options.keys.include? param }
9
-
10
- asset_id = options[:asset_id]
11
- opponent_id = options[:opponent_id]
12
- amount = options[:amount].to_d
13
- memo = options[:memo]
14
- trace_id = options[:trace_id] || SecureRandom.uuid
15
- encrypted_pin = options[:encrypted_pin] || encrypt_pin(pin)
16
-
17
- path = '/transfers'
18
- payload = {
19
- asset_id: asset_id,
20
- opponent_id: opponent_id,
21
- pin: encrypted_pin,
22
- amount: format('%.8f', amount.to_r),
23
- trace_id: trace_id,
24
- memo: memo
25
- }
26
-
27
- access_token = options[:access_token]
28
- access_token ||= access_token('POST', path, payload.to_json)
29
- authorization = format('Bearer %<access_token>s', access_token: access_token)
30
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
6
+ # kwargs:
7
+ # {
8
+ # members: uuid | [ uuid ],
9
+ # threshold: integer / nil,
10
+ # asset_id: uuid,
11
+ # amount: string / float,
12
+ # trace_id: uuid / nil,
13
+ # request_id: uuid / nil,
14
+ # memo: string,
15
+ # spend_key: string / nil,
16
+ # }
17
+ def create_safe_transfer(**kwargs)
18
+ asset_id = kwargs[:asset_id]
19
+ raise ArgumentError, 'asset_id required' if asset_id.blank?
20
+
21
+ amount = kwargs[:amount]&.to_d
22
+ raise ArgumentError, 'amount required' if amount.blank?
23
+
24
+ members = [kwargs[:members]].flatten.compact
25
+ raise ArgumentError, 'members required' if members.blank?
26
+
27
+ threshold = kwargs[:threshold] || members.length
28
+ request_id = kwargs[:request_id] || kwargs[:trace_id] || SecureRandom.uuid
29
+ memo = kwargs[:memo] || ''
30
+
31
+ # step 1: select inputs
32
+ utxos = build_utxos(asset_id:, amount:)
33
+
34
+ # step 2: build transaction
35
+ tx = build_safe_transaction(
36
+ utxos:,
37
+ receivers: [{
38
+ members:,
39
+ threshold:,
40
+ amount:
41
+ }],
42
+ extra: memo
43
+ )
44
+ raw = MixinBot.utils.encode_raw_transaction tx
45
+
46
+ # step 3: verify transaction
47
+ request = create_safe_transaction_request(request_id, raw)['data']
48
+
49
+ # step 4: sign transaction
50
+ spend_key = MixinBot.utils.decode_key(kwargs[:spend_key]) || config.spend_key
51
+ signed_raw = MixinBot.api.sign_safe_transaction(
52
+ raw:,
53
+ utxos:,
54
+ request: request[0],
55
+ spend_key:
56
+ )
57
+
58
+ # step 5: submit transaction
59
+ send_safe_transaction(
60
+ request_id,
61
+ signed_raw
62
+ )
31
63
  end
32
64
 
33
- def transfer(trace_id, access_token: nil)
34
- path = format('/transfers/trace/%<trace_id>s', trace_id: trace_id)
35
- access_token ||= access_token('GET', path, '')
36
- authorization = format('Bearer %<access_token>s', access_token: access_token)
37
- client.get(path, headers: { 'Authorization': authorization })
65
+ def build_utxos(asset_id:, amount:)
66
+ outputs = safe_outputs(state: 'unspent', asset: asset_id, limit: 500)['data'].sort_by { |o| o['amount'].to_d }
67
+
68
+ utxos = []
69
+ outputs.each do |output|
70
+ break if utxos.sum { |o| o['amount'].to_d } >= amount
71
+
72
+ utxos.shift if utxos.size >= 256
73
+ utxos << output
74
+ end
75
+
76
+ utxos
38
77
  end
39
- alias read_transfer transfer
40
78
  end
41
79
  end
42
80
  end