mixin_bot 0.5.3 → 0.6.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: 0bd270e4f04a3a379ecf9d90c4cf66f4c6929bcfb7bd5be1891e164ed3d100a5
4
- data.tar.gz: 74c16671b88f9217814c86f4aba53f8a044248a2a039831f0c37b3bfdc9cb0cf
3
+ metadata.gz: 4534f4c2ea6ed1928f5c2a32b5221737ba94ff70cffdaa26a95ae03c710e5186
4
+ data.tar.gz: dd4a3901a081d50f2c0d3ab751516956e4065c21acaadb980e836b25b099b90a
5
5
  SHA512:
6
- metadata.gz: 4fbed881680c09010f23c7056ebf3a1effadf4ce5736bde757f1bef0323186169a951567d98420b1b2c2851696f8e2407053761e2fc1e9321c5e3e76340351c9
7
- data.tar.gz: c72f873676da408ee725a0906fcdbe2567a91df65077aecb9dda501e03d6ea5c322d01f821993ecb8828b9efc30d3b38253bd0ceac80c0aa0e593cb6938c0ae1
6
+ metadata.gz: 1a729cad7c8086ebfaae941b6ea4ba93318376e3effb469e42979317f048fb89c34b1b53425389c7da7138dddf6ebed80a643360c9a377c9c4c22d4834c02027
7
+ data.tar.gz: 44d76715c4721f2db56a7fee621e3454f18556f94b41d225821bc352cb1fd8ddaa61c38f2c5fbc4380099ddd995e94bca841e743eedb1675b555bf5a79a8bf58
@@ -21,6 +21,25 @@ module MixinBot
21
21
  client.post(path, headers: { 'Authorization': authorization }, json: {})
22
22
  end
23
23
 
24
+ def upload_attachment(file)
25
+ attachment = create_attachment['data']
26
+
27
+ HTTP
28
+ .timeout(connect: 5, write: 5, read: 5)
29
+ .request(
30
+ :put,
31
+ attachment.delete('upload_url'),
32
+ {
33
+ body: file,
34
+ headers: {
35
+ 'x-amz-acl': 'public-read',
36
+ 'Content-Type': 'application/octet-stream',
37
+ }
38
+ })
39
+
40
+ attachment
41
+ end
42
+
24
43
  def read_attachment(attachment_id)
25
44
  path = format('/attachments/%<id>s', id: attachment_id)
26
45
  access_token ||= access_token('GET', path, '')
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class API
5
+ module Collectible
6
+ def collectible(id, access_token: nil)
7
+ path = "/collectibles/tokens/#{id}"
8
+ access_token ||= access_token('GET', path, '')
9
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
10
+ client.get(path, headers: { 'Authorization': authorization })
11
+ end
12
+
13
+ def collectible_outputs(**kwargs)
14
+ limit = kwargs[:limit] || 100
15
+ offset = kwargs[:offset] || ''
16
+ state = kwargs[:state] || ''
17
+ members = kwargs[:members] || [client_id]
18
+ threshold = kwargs[:threshold] || 1
19
+ access_token = kwargs[:access_token]
20
+ members = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
21
+
22
+ path = format(
23
+ '/collectibles/outputs?limit=%<limit>s&offset=%<offset>s&state=%<state>s&members=%<members>s&threshold=%<threshold>s',
24
+ limit: limit,
25
+ offset: offset,
26
+ state: state,
27
+ members: members,
28
+ threshold: threshold
29
+ )
30
+ access_token ||= access_token('GET', path, '')
31
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
32
+ client.get(path, headers: { 'Authorization': authorization })
33
+ end
34
+ alias collectibles collectible_outputs
35
+
36
+ COLLECTABLE_REQUEST_ACTIONS = %i[sign unlock].freeze
37
+ 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
39
+ path = '/collectibles/requests'
40
+ payload = {
41
+ action: action,
42
+ raw: raw
43
+ }
44
+ access_token ||= access_token('POST', path, payload.to_json)
45
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
46
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
47
+ end
48
+
49
+ def create_sign_collectible_request(raw, access_token: nil)
50
+ create_collectible_output 'sign', raw, access_token
51
+ end
52
+
53
+ def create_unlock_collectible_request(raw, access_token: nil)
54
+ create_collectible_output 'unlock', raw, access_token
55
+ end
56
+
57
+ def sign_collectible_request(request_id, pin)
58
+ path = format('/collectibles/requests/%<request_id>s/sign', request_id: request_id)
59
+ payload = {
60
+ pin: encrypt_pin(pin)
61
+ }
62
+ access_token ||= access_token('POST', path, payload.to_json)
63
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
64
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
65
+ end
66
+
67
+ def unlock_collectible_request(request_id, pin)
68
+ path = format('/collectibles/requests/%<request_id>s/unlock', request_id: request_id)
69
+ payload = {
70
+ pin: encrypt_pin(pin)
71
+ }
72
+ access_token ||= access_token('POST', path, payload.to_json)
73
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
74
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
75
+ end
76
+
77
+ def cancel_collectible_request(request_id, pin)
78
+ path = format('/collectibles/requests/%<request_id>s/cancel', request_id: request_id)
79
+ payload = {
80
+ pin: encrypt_pin(pin)
81
+ }
82
+ access_token ||= access_token('POST', path, payload.to_json)
83
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
84
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
85
+ end
86
+ end
87
+
88
+ def nft_memo(collection, token_id, meta)
89
+ MixinBot::Utils.nft_memo collection, token_id, meta
90
+ end
91
+ end
92
+ end
@@ -123,24 +123,7 @@ module MixinBot
123
123
  end
124
124
 
125
125
  def unique_uuid(user_id, opponent_id = nil)
126
- opponent_id ||= client_id
127
- md5 = Digest::MD5.new
128
- md5 << [user_id, opponent_id].min
129
- md5 << [user_id, opponent_id].max
130
- digest = md5.digest
131
- digest6 = (digest[6].ord & 0x0f | 0x30).chr
132
- digest8 = (digest[8].ord & 0x3f | 0x80).chr
133
- cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
134
- hex = cipher.unpack1('H*')
135
-
136
- format(
137
- '%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
138
- first: hex[0..7],
139
- second: hex[8..11],
140
- third: hex[12..15],
141
- forth: hex[16..19],
142
- fifth: hex[20..]
143
- )
126
+ MixinBot::Utils.unique_uuid user_id, opponent_id
144
127
  end
145
128
  alias unique_conversation_id unique_uuid
146
129
  end
@@ -276,6 +276,14 @@ module MixinBot
276
276
  send_message plain_text(options)
277
277
  end
278
278
 
279
+ def send_image_message(options)
280
+ send_message plain_image(options)
281
+ end
282
+
283
+ def send_file_message(options)
284
+ send_message plain_data(options)
285
+ end
286
+
279
287
  def send_post_message(options)
280
288
  send_message plain_post(options)
281
289
  end
@@ -3,31 +3,6 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Multisig
6
- # https://w3c.group/c/1574309272319630
7
-
8
- # {"data":[
9
- # {
10
- # "type":"multisig_utxo",
11
- # "user_id":"514ae2ff-c24e-4379-a482-e2c0f798ebb1",
12
- # "utxo_id":"94711ac9-5981-4fe3-8c0e-19622219ea72",
13
- # "asset_id":"965e5c6e-434c-3fa9-b780-c50f43cd955c",
14
- # "transaction_hash":"2e67f3e36ee4b3c13effcc8a9aaafeb8122cad98f72d9ccc04d65a5ada2aa39d",
15
- # "output_index":0,
16
- # "amount":"0.123456",
17
- # "threshold":2,
18
- # "members":[
19
- # "514ae2ff-c24e-4379-a482-e2c0f798ebb1",
20
- # "13ce6c86-307a-5187-98b0-76424cbc0fbf",
21
- # "2b9df368-8e3e-46ce-ac57-e6111e8ff50e",
22
- # "3cb87491-4fa0-4c2f-b387-262b63cbc412"
23
- # ],
24
- # "memo":"难道你是女生",
25
- # "state":"unspent",
26
- # "created_at":"2019-11-03T13:30:43.922655Z",
27
- # "signed_by":"",
28
- # "signed_tx":""
29
- # }
30
- # ]}
31
6
  def outputs(**kwargs)
32
7
  limit = kwargs[:limit] || 100
33
8
  offset = kwargs[:offset] || ''
@@ -50,6 +25,7 @@ module MixinBot
50
25
  client.get(path, headers: { 'Authorization': authorization })
51
26
  end
52
27
  alias multisigs outputs
28
+ alias multisig_outputs outputs
53
29
 
54
30
  def create_output(receivers:, index:, access_token: nil)
55
31
  path = '/outputs'
@@ -62,13 +38,13 @@ module MixinBot
62
38
  client.post(path, headers: { 'Authorization': authorization }, json: payload)
63
39
  end
64
40
 
65
- # transfer from the multisig address
66
- # create a request for multi sign
67
- # for now, raw(RAW-TRANSACTION-HEX) can only be generated by Mixin SDK of Golang or Javascript
68
- def create_sign_multisig_request(raw, access_token: nil)
41
+ MULTISIG_REQUEST_ACTIONS = %i[sign unlock].freeze
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
44
+
69
45
  path = '/multisigs/requests'
70
46
  payload = {
71
- action: 'sign',
47
+ action: action,
72
48
  raw: raw
73
49
  }
74
50
  access_token ||= access_token('POST', path, payload.to_json)
@@ -76,17 +52,15 @@ module MixinBot
76
52
  client.post(path, headers: { 'Authorization': authorization }, json: payload)
77
53
  end
78
54
 
55
+ # transfer from the multisig address
56
+ def create_sign_multisig_request(raw, access_token: nil)
57
+ create_multisig_request 'sign', raw, access_token
58
+ end
59
+
79
60
  # transfer from the multisig address
80
61
  # create a request for unlock a multi-sign
81
62
  def create_unlock_multisig_request(raw, access_token: nil)
82
- path = '/multisigs/requests'
83
- payload = {
84
- action: 'unlock',
85
- raw: raw
86
- }
87
- access_token ||= access_token('POST', path, payload.to_json)
88
- authorization = format('Bearer %<access_token>s', access_token: access_token)
89
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
63
+ create_multisig_request 'unlock', raw, access_token
90
64
  end
91
65
 
92
66
  def sign_multisig_request(request_id, pin)
@@ -121,7 +95,7 @@ module MixinBot
121
95
 
122
96
  # pay to the multisig address
123
97
  # used for create multisig payment code_id
124
- def create_multisig_payment(**kwargs)
98
+ def create_payment(**kwargs)
125
99
  path = '/payments'
126
100
  payload = {
127
101
  asset_id: kwargs[:asset_id],
@@ -138,6 +112,7 @@ module MixinBot
138
112
  authorization = format('Bearer %<access_token>s', access_token: access_token)
139
113
  client.post(path, headers: { 'Authorization': authorization }, json: payload)
140
114
  end
115
+ alias create_multisig_payment create_payment
141
116
 
142
117
  def verify_multisig(code_id, access_token: nil)
143
118
  path = format('/codes/%<code_id>s', code_id: code_id)
@@ -248,7 +223,7 @@ module MixinBot
248
223
 
249
224
  extra = Digest.hexencode memo.to_s.slice(0, 140)
250
225
  tx = {
251
- version: 1,
226
+ version: 2,
252
227
  asset: SHA3::Digest::SHA256.hexdigest(asset_id),
253
228
  inputs: inputs,
254
229
  outputs: outputs,
@@ -283,26 +258,8 @@ module MixinBot
283
258
  res
284
259
  end
285
260
 
286
- def build_outputs(outputs)
287
- res = []
288
- prototype = {
289
- 'Type' => 0,
290
- 'Amount' => nil,
291
- 'Keys' => nil,
292
- 'Script' => nil,
293
- 'Mask' => nil
294
- }
295
- outputs.each do |output|
296
- struc = prototype.dup
297
- struc['Type'] = str_to_bin output['type']
298
- struc['Amount'] = str_to_bin output['amount']
299
- struc['Keys'] = output['keys'].map(&->(key) { str_to_bin(key) })
300
- struc['Script'] = str_to_bin output['script']
301
- struc['Mask'] = str_to_bin output['mask']
302
- res << struc
303
- end
304
-
305
- res
261
+ def generate_trace_from_hash(hash, output_index = 0)
262
+ MixinBot::Utils.generate_trace_from_hash hash, output_index
306
263
  end
307
264
  end
308
265
  end
data/lib/mixin_bot/api.rb CHANGED
@@ -6,6 +6,7 @@ require_relative './api/asset'
6
6
  require_relative './api/attachment'
7
7
  require_relative './api/auth'
8
8
  require_relative './api/blaze'
9
+ require_relative './api/collectible'
9
10
  require_relative './api/conversation'
10
11
  require_relative './api/me'
11
12
  require_relative './api/message'
@@ -43,8 +44,12 @@ module MixinBot
43
44
  end
44
45
  end
45
46
 
47
+ def sign_raw_transaction(tx)
48
+ MixinBot::Utils.sign_raw_transaction tx
49
+ end
50
+
46
51
  # Use a mixin software to implement transaction build
47
- def sign_raw_transaction(json)
52
+ def sign_raw_transaction_native(json)
48
53
  ensure_mixin_command_exist
49
54
  command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
50
55
 
@@ -59,6 +64,7 @@ module MixinBot
59
64
  include MixinBot::API::Attachment
60
65
  include MixinBot::API::Auth
61
66
  include MixinBot::API::Blaze
67
+ include MixinBot::API::Collectible
62
68
  include MixinBot::API::Conversation
63
69
  include MixinBot::API::Me
64
70
  include MixinBot::API::Message
@@ -0,0 +1,408 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ module Utils
5
+ class << self
6
+ MAGIC = [0x77, 0x77]
7
+ TX_VERSION = 2
8
+ MAX_ENCODE_INT = 0xFFFF
9
+ NULL_BYTES = [0x00, 0x00]
10
+ AGGREGATED_SIGNATURE_PREFIX = 0xFF01
11
+ AGGREGATED_SIGNATURE_ORDINAY_MASK = [0x00]
12
+ 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
+
20
+ def unique_uuid(user_id, opponent_id = nil)
21
+ opponent_id ||= client_id
22
+ md5 = Digest::MD5.new
23
+ md5 << [user_id, opponent_id].min
24
+ md5 << [user_id, opponent_id].max
25
+ digest = md5.digest
26
+ digest6 = (digest[6].ord & 0x0f | 0x30).chr
27
+ digest8 = (digest[8].ord & 0x3f | 0x80).chr
28
+ cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
29
+ hex = cipher.unpack1('H*')
30
+
31
+ format(
32
+ '%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
33
+ first: hex[0..7],
34
+ second: hex[8..11],
35
+ third: hex[12..15],
36
+ forth: hex[16..19],
37
+ fifth: hex[20..]
38
+ )
39
+ end
40
+
41
+ def generate_trace_from_hash(hash, output_index = 0)
42
+ md5 = Digest::MD5.new
43
+ md5 << hash
44
+ md5 << [output_index].pack('c*') if output_index.positive? && output_index < 256
45
+ digest = md5.digest
46
+ digest[6] = ((digest[6].ord & 0x0f) | 0x30).chr
47
+ digest[8] = ((digest[8].ord & 0x3f) | 0x80).chr
48
+ hex = digest.unpack1('H*')
49
+
50
+ hex_to_uuid hex
51
+ end
52
+
53
+ def hex_to_uuid(hex)
54
+ [hex[0..7], hex[8..11], hex[12..15], hex[16..19], hex[20..]].join('-')
55
+ end
56
+
57
+ def sign_raw_transaction(tx)
58
+ if tx.is_a? String
59
+ tx = JSON.parse tx
60
+ end
61
+
62
+ tx = tx.with_indifferent_access
63
+ bytes = []
64
+
65
+ # magic number
66
+ bytes += MAGIC
67
+
68
+ # version
69
+ bytes += [0, tx['version']]
70
+ bytes += [tx['asset']].pack('H*').bytes
71
+
72
+ # inputs
73
+ bytes += encode_inputs tx['inputs']
74
+
75
+ # output
76
+ bytes += encode_outputs tx['outputs']
77
+
78
+ # extra
79
+ extra_bytes = [tx['extra']].pack('H*').bytes
80
+ bytes += encode_int extra_bytes.size
81
+ bytes += extra_bytes
82
+
83
+ # aggregated
84
+ aggregated = tx['aggregated']
85
+ if aggregated.nil?
86
+ # signatures
87
+ bytes += encode_signatures tx['signatures']
88
+ else
89
+ bytes += encode_aggregated_signature aggregated
90
+ end
91
+
92
+ bytes.pack('C*').unpack1('H*')
93
+ end
94
+
95
+ def nft_memo_hash(collection, token_id, meta)
96
+ collection = NULL_UUID if collection.empty?
97
+ meta = meta.to_json if meta.is_a?(Hash)
98
+
99
+ memo = {
100
+ prefix: NFT_MEMO_PREFIX,
101
+ version: NFT_MEMO_VERSION,
102
+ mask: 0,
103
+ chain: NFT_MEMO_DEFAULT_CHAIN,
104
+ class: NFT_MEMO_DEFAULT_CLASS,
105
+ collection: collection,
106
+ token: token_id,
107
+ extra: SHA3::Digest::SHA256.hexdigest(meta)
108
+ }
109
+
110
+ mark = [0]
111
+ mark.map do |index|
112
+ if index >= 64
113
+ raise "invalid NFO memo index #{index}"
114
+ end
115
+ memo[:mask] = memo[:mask] ^ (1 << index)
116
+ end
117
+
118
+ memo
119
+ end
120
+
121
+ def nft_memo(collection, token_id, meta)
122
+ encode_nft_memo nft_memo_hash(collection, token_id, meta)
123
+ end
124
+
125
+ def encode_nft_memo(memo)
126
+ bytes = []
127
+
128
+ bytes += NFT_MEMO_PREFIX.bytes
129
+ bytes += [NFT_MEMO_VERSION]
130
+
131
+ if memo[:mask] != 0
132
+ bytes += [1]
133
+ bytes += encode_unit_64 memo[:mask]
134
+ bytes += memo[:chain].split('-').pack('H* H* H* H* H*').bytes
135
+
136
+ class_bytes = [memo[:class]].pack('H*').bytes
137
+ bytes += bytes_of class_bytes.size
138
+ bytes += class_bytes
139
+
140
+ collection_bytes = memo[:collection].split('-').pack('H* H* H* H* H*').bytes
141
+ bytes += bytes_of collection_bytes.size
142
+ bytes += collection_bytes
143
+
144
+ # token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
145
+ token_bytes = bytes_of memo[:token]
146
+ bytes += bytes_of token_bytes.size
147
+ bytes += token_bytes
148
+ end
149
+
150
+ extra_bytes = [memo[:extra]].pack('H*').bytes
151
+ bytes += bytes_of extra_bytes.size
152
+ bytes += extra_bytes
153
+
154
+ Base64.urlsafe_encode64 bytes.pack('C*'), padding: false
155
+ end
156
+
157
+ def decode_nft_memo(encoded)
158
+ bytes = Base64.urlsafe_decode64(encoded).bytes
159
+ memo = {}
160
+ memo[:prefix] = bytes.shift(3).pack('C*')
161
+ memo[:version] = bytes.shift
162
+
163
+ hint = bytes.shift
164
+ if hint == 1
165
+ memo[:mask] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
166
+ memo[:chain] = hex_to_uuid bytes.shift(16).pack('C*').unpack1('H*')
167
+
168
+ class_length = bytes.shift
169
+ memo[:class] = bytes.shift(class_length).pack('C*').unpack1('H*')
170
+
171
+ collection_length = bytes.shift
172
+ memo[:collection] = hex_to_uuid bytes.shift(collection_length).pack('C*').unpack1('H*')
173
+
174
+ token_length = bytes.shift
175
+ memo[:token] = bytes_to_int bytes.shift(token_length)
176
+ end
177
+
178
+ extra_length = bytes.shift
179
+ memo[:extra] = bytes.shift(extra_length).pack('C*').unpack1('H*')
180
+
181
+ memo
182
+ end
183
+
184
+ private
185
+
186
+ def encode_int(int)
187
+ raise "only support int #{int}" unless int.is_a?(Integer)
188
+ raise "int #{int} is larger than MAX_ENCODE_INT #{MAX_ENCODE_INT}" if int > MAX_ENCODE_INT
189
+
190
+ [int].pack('S*').bytes.reverse
191
+ end
192
+
193
+ def encode_unit_64(int)
194
+ [int].pack('Q*').bytes.reverse
195
+ end
196
+
197
+ def bytes_of(int)
198
+ raise 'not integer' unless int.is_a?(Integer)
199
+
200
+ bytes = []
201
+ loop do
202
+ break if int === 0
203
+ bytes.push int & 255
204
+ int = int / (2 ** 8) | 0
205
+ end
206
+
207
+ bytes.reverse
208
+ end
209
+
210
+ def bytes_to_int(bytes)
211
+ int = 0
212
+ bytes.each do |byte|
213
+ int = int * (2 ** 8) + byte
214
+ end
215
+
216
+ int
217
+ end
218
+
219
+ def encode_inputs(inputs, bytes = [])
220
+ bytes += encode_int(inputs.size)
221
+ inputs.each do |input|
222
+ bytes += [input['hash']].pack('H*').bytes
223
+ bytes += encode_int(input['index'])
224
+
225
+ # genesis
226
+ genesis = input['genesis'] || ''
227
+ if genesis.empty?
228
+ bytes += encode_int 0
229
+ else
230
+ genesis_bytes = [genesis].pack('H*')
231
+ bytes += encode_int genesis_bytes.size
232
+ bytes += genesis_bytes
233
+ end
234
+
235
+ # deposit
236
+ deposit = input['deposit']
237
+ if deposit.nil?
238
+ bytes += NULL_BYTES
239
+ else
240
+ bytes += MAGIC
241
+ bytes += [deposit['chain']].pack('H*').bytes
242
+
243
+ asset_bytes = [deposit['asset']].pack('H*')
244
+ bytes += encode_int asset_bytes.size
245
+ bytes += asset_bytes
246
+
247
+ transaction_bytes = [deposit['transaction']].pack('H*')
248
+ bytes += encode_int transaction_bytes.size
249
+ bytes += transaction_bytes
250
+
251
+ bytes += encode_unit_64 deposit['index']
252
+
253
+ amount_bytes = bytes_of deposit['amount']
254
+ bytes += encode_int amount_bytes.size
255
+ bytes += amount_bytes
256
+ end
257
+
258
+ # mint
259
+ mint = input['mint']
260
+ if mint.nil?
261
+ bytes += NULL_BYTES
262
+ else
263
+ bytes += MAGIC
264
+
265
+ # group
266
+ group = mint['group'] || ''
267
+ if group.empty?
268
+ bytes += encode_int 0
269
+ else
270
+ group_bytes = [group].pack('H*')
271
+ bytes += encode_int group_bytes.size
272
+ bytes += group_bytes
273
+ end
274
+
275
+ bytes += encode_unit_64 mint['batch']
276
+
277
+ amount_bytes = bytes_of mint['amount']
278
+ bytes += encode_int amount_bytes.size
279
+ bytes += amount_bytes
280
+ end
281
+ end
282
+
283
+ bytes
284
+ end
285
+
286
+ def encode_outputs(outputs, bytes = [])
287
+ bytes += encode_int(outputs.size)
288
+ outputs.each do |output|
289
+ type = output['type'] || 0
290
+ bytes += [0x00, type]
291
+
292
+ # amount
293
+ amount_bytes = bytes_of (output['amount'].to_f * 1e8).to_i
294
+ bytes += encode_int amount_bytes.size
295
+ bytes += amount_bytes
296
+
297
+ # keys
298
+ bytes += encode_int output['keys'].size
299
+ output['keys'].each do |key|
300
+ bytes += [key].pack('H*').bytes
301
+ end
302
+
303
+ # mask
304
+ bytes += [output['mask']].pack('H*').bytes
305
+
306
+ # script
307
+ script_bytes = [output['script']].pack('H*').bytes
308
+ bytes += encode_int script_bytes.size
309
+ bytes += script_bytes
310
+
311
+ # withdrawal
312
+ withdrawal = output['withdrawal']
313
+ if withdrawal.nil?
314
+ bytes += NULL_BYTES
315
+ else
316
+ bytes += MAGIC
317
+
318
+ # chain
319
+ bytes += [withdrawal['chain']].pack('H*').bytes
320
+
321
+ # asset
322
+ asset_bytes = [withdrawal['asset']].pack('H*')
323
+ bytes += encode_int asset_bytes.size
324
+ bytes += asset_bytes
325
+
326
+ # address
327
+ address = withdrawal['address'] || ''
328
+ if address.empty?
329
+ bytes += NULL_BYTES
330
+ else
331
+ address_bytes = [address].pack('H*').bytes
332
+ bytes += encode_int address.size
333
+ bytes += address_bytes
334
+ end
335
+
336
+ # tag
337
+ tag = withdrawal['tag'] || ''
338
+ if tag.empty?
339
+ bytes += NULL_BYTES
340
+ else
341
+ address_bytes = [tag].pack('H*').bytes
342
+ bytes += encode_int tag.size
343
+ bytes += address_bytes
344
+ end
345
+ end
346
+ end
347
+
348
+ bytes
349
+ end
350
+
351
+ def encode_aggregated_signature(aggregated, bytes = [])
352
+ bytes += MAX_ENCODE_INT
353
+ bytes += encode_int AGGREGATED_SIGNATURE_PREFIX
354
+ bytes += [aggregated['signature']].pack('H*').bytes
355
+
356
+ signers = aggregated['signers']
357
+ if signers.size == 0
358
+ bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
359
+ bytes += encode_int 0
360
+ else
361
+ signers.each do |sig, i|
362
+ raise 'signers not sorted' if i > 0 && sig <= signers[i - 1]
363
+ raise 'signers not sorted' if sig > MAX_ENCODE_INT
364
+ end
365
+
366
+ max = signers.last
367
+ if (((max / 8 | 0) + 1 | 0) > aggregated['signature'].size * 2)
368
+ bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
369
+ bytes += encode_int aggregated['signature'].size
370
+ signers.map(&->(signer) { bytes += encode_int(signer) })
371
+ end
372
+
373
+ masks_bytes = Array.new(max / 8 + 1, 0)
374
+ signers.each do |signer|
375
+ masks[signer/8] = masks[signer/8] ^ (1 << (signer % 8))
376
+ end
377
+ bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
378
+ bytes += encode_int masks_bytes.size
379
+ bytes += masks_bytes
380
+ end
381
+
382
+ bytes
383
+ end
384
+
385
+ def encode_signatures(signatures, bytes = [])
386
+ sl =
387
+ if signatures.is_a? Hash
388
+ signatures.keys.size
389
+ else
390
+ 0
391
+ end
392
+
393
+ raise 'signatures overflow' if sl == MAX_ENCODE_INT
394
+ bytes += encode_int sl
395
+
396
+ if sl > 0
397
+ bytes += encode_int signatures.keys.size
398
+ signatures.keys.sort.each do |key|
399
+ bytes += encode_int key
400
+ bytes += [signatures[key]].pack('H*').bytes
401
+ end
402
+ end
403
+
404
+ bytes
405
+ end
406
+ end
407
+ end
408
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MixinBot
4
- VERSION = '0.5.3'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/mixin_bot.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  # third-party dependencies
4
4
  require 'English'
5
+ require 'active_support/core_ext/hash'
5
6
  require 'base64'
6
7
  require 'digest'
7
8
  require 'faye/websocket'
@@ -15,6 +16,7 @@ require 'sha3'
15
16
 
16
17
  require_relative './mixin_bot/api'
17
18
  require_relative './mixin_bot/cli'
19
+ require_relative './mixin_bot/utils'
18
20
  require_relative './mixin_bot/version'
19
21
 
20
22
  module MixinBot
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixin_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.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: 2021-08-31 00:00:00.000000000 Z
11
+ date: 2021-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: awesome_print
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
166
  version: '1.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: fastimage
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 2.2.5
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 2.2.5
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: pry
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -251,6 +279,7 @@ files:
251
279
  - lib/mixin_bot/api/attachment.rb
252
280
  - lib/mixin_bot/api/auth.rb
253
281
  - lib/mixin_bot/api/blaze.rb
282
+ - lib/mixin_bot/api/collectible.rb
254
283
  - lib/mixin_bot/api/conversation.rb
255
284
  - lib/mixin_bot/api/me.rb
256
285
  - lib/mixin_bot/api/message.rb
@@ -267,6 +296,7 @@ files:
267
296
  - lib/mixin_bot/cli/multisig.rb
268
297
  - lib/mixin_bot/cli/node.rb
269
298
  - lib/mixin_bot/client.rb
299
+ - lib/mixin_bot/utils.rb
270
300
  - lib/mixin_bot/version.rb
271
301
  homepage: https://github.com/an-lee/mixin_bot
272
302
  licenses: