mixin_bot 0.0.1.4 → 0.3.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.
@@ -1,69 +1,266 @@
1
+ # frozen_string_literal: false
2
+
1
3
  module MixinBot
2
4
  class API
5
+ # https://developers.mixin.one/api/beta-mixin-message/websocket-messages/
3
6
  module Message
4
7
  def list_pending_message
5
- write_message('LIST_PENDING_MESSAGES', {})
8
+ write_ws_message(action: 'LIST_PENDING_MESSAGES', params: {})
6
9
  end
7
10
 
11
+ # ACKNOWLEDGE_MESSAGE_RECEIPT ack server received message
12
+ # {
13
+ # "id": "UUID",
14
+ # "action": "ACKNOWLEDGE_MESSAGE_RECEIPT",
15
+ # "params": {
16
+ # "message_id": "UUID // message_id is you received message's message_id",
17
+ # "status": "READ"
18
+ # }
19
+ # }
8
20
  def acknowledge_message_receipt(message_id)
9
21
  params = {
10
22
  message_id: message_id,
11
23
  status: 'READ'
12
24
  }
13
- write_message('ACKNOWLEDGE_MESSAGE_RECEIPT', params)
25
+ write_ws_message(action: 'ACKNOWLEDGE_MESSAGE_RECEIPT', params: params)
14
26
  end
15
27
 
16
- def plain_text_message(conversation_id, text)
17
- encoded_text = Base64.encode64 text
28
+ # {
29
+ # "id": "UUID // generated by client",
30
+ # "action": "CREATE_MESSAGE",
31
+ # "params": {
32
+ # "conversation_id": "UUID",
33
+ # "category": "PLAIN_TEXT",
34
+ # "status": "SENT",
35
+ # "message_id": "UUID // generated by client",
36
+ # "data": "Base64 encoded data" ,
37
+ # }
38
+ # }
39
+ def plain_text(options)
40
+ options.merge!(category: 'PLAIN_TEXT')
41
+ base_message_params(options)
42
+ end
18
43
 
19
- params = {
20
- conversation_id: conversation_id,
21
- category: 'PLAIN_TEXT',
22
- status: 'SENT',
23
- message_id: SecureRandom.uuid,
24
- data: encoded_text
25
- }
44
+ # {
45
+ # "id": "UUID // generated by client",
46
+ # "action": "CREATE_MESSAGE",
47
+ # "params": {
48
+ # "conversation_id": "UUID",
49
+ # "category": "PLAIN_POST",
50
+ # "status": "SENT",
51
+ # "message_id": "UUID // generated by client",
52
+ # "data": "Base64 encoded data content is markdown" ,
53
+ # }
54
+ # }
55
+ def plain_post(options)
56
+ options.merge!(category: 'PLAIN_POST')
57
+ base_message_params(options)
58
+ end
26
59
 
27
- write_message('CREATE_MESSAGE', params)
60
+ # {
61
+ # "id": "UUID",
62
+ # "action": "CREATE_MESSAGE",
63
+ # "params": {
64
+ # "conversation_id": "UUID"
65
+ # "category": "PLAIN_IMAGE"
66
+ # "status": "SENT",
67
+ # "message_id": "UUID",
68
+ # "data": "Base64 encoded data"
69
+ # }
70
+ # }
71
+ # data format:
72
+ # {
73
+ # "attachment_id":
74
+ # "Read From POST /attachments",
75
+ # "mime_type": "",
76
+ # "width": 1024,
77
+ # "height": 1024,
78
+ # "size": 1024,
79
+ # "thumbnail": "base64 encoded"
80
+ # }
81
+ def plain_image(options)
82
+ options.merge!(category: 'PLAIN_IMAGE')
83
+ base_message_params(options)
28
84
  end
29
85
 
30
- def app_card_message
31
- # TODO:
86
+ # {
87
+ # "id": "UUID",
88
+ # "action": "CREATE_MESSAGE",
89
+ # "params": {
90
+ # "conversation_id": "UUID",
91
+ # "category": "PLAIN_DATA",
92
+ # "status": "SENT",
93
+ # "message_id": "UUID",
94
+ # "data": "Base64 encoded data",
95
+ # }
96
+ # }
97
+ # data format:
98
+ # {
99
+ # "attachment_id": "Read From POST /attachments",
100
+ # "mime_type": "",
101
+ # "size": 1024,
102
+ # "name": "Share"
103
+ # }
104
+ def plain_data(options)
105
+ options.merge!(category: 'PLAIN_DATA')
106
+ base_message_params(options)
32
107
  end
33
108
 
34
- def app_button_group_message(conversation_id, recipient_id, options={})
35
- options = options.with_indifferent_access
36
- label = options[:label] || ''
37
- color = options[:color] || '#467fcf'
38
- action = options[:action] || ''
109
+ # {
110
+ # "id": "UUID",
111
+ # "action": "CREATE_MESSAGE",
112
+ # "params": {
113
+ # "conversation_id": "UUID",
114
+ # "category": "PLAIN_STICKER",
115
+ # "status": "SENT",
116
+ # "message_id": "UUID",
117
+ # "data": "Base64 encoded data"
118
+ # }
119
+ # }
120
+ # data format:
121
+ # {
122
+ # "name": "hello",
123
+ # "album_id": "UUID"
124
+ # }
125
+ def plain_sticker(options)
126
+ options.merge!(category: 'PLAIN_STICKER')
127
+ base_message_params(options)
128
+ end
39
129
 
40
- data = [{ label: label, color: color, action: action }]
41
- encoded_data = Base64.encode64 data.to_json
130
+ # {
131
+ # "id": "UUID",
132
+ # "action": "CREATE_MESSAGE",
133
+ # "params": {
134
+ # "conversation_id": "UUID",
135
+ # "category": "PLAIN_CONTACT"
136
+ # "status": "SENT",
137
+ # "message_id": "UUID",
138
+ # "data": "Base64 encoded data"
139
+ # }
140
+ # }
141
+ # data format:
142
+ # { "user_id": "UUID"}
143
+ def plain_contact(options)
144
+ options.merge!(category: 'PLAIN_CONTACT')
145
+ base_message_params(options)
146
+ end
42
147
 
43
- params = {
44
- conversation_id: conversation_id,
45
- recipient_id: recipient_id,
46
- category: 'APP_BUTTON_GROUP',
148
+ # {
149
+ # "id": "UUID",
150
+ # "action": "CREATE_MESSAGE",
151
+ # "params": {
152
+ # "conversation_id": "UUID",
153
+ # "category": "APP_CARD",
154
+ # "status": "SENT",
155
+ # "message_id": "UUID",
156
+ # "data": "Base64 encoded data"
157
+ # }
158
+ # }
159
+ # data format:
160
+ # {
161
+ # "icon_url": "https://mixin.one/assets/98b586edb270556d1972112bd7985e9e.png",
162
+ # "title": "Mixin",
163
+ # "description": "A free and lightning fast peer-to-peer transactional network for digital assets.",
164
+ # "action": "https://mixin.one"
165
+ # }
166
+ def app_card(options)
167
+ options.merge!(category: 'APP_CARD')
168
+ base_message_params(options)
169
+ end
170
+
171
+ # {
172
+ # "id": "UUID",
173
+ # "action": "CREATE_MESSAGE",
174
+ # "params": {
175
+ # "conversation_id": "UUID",
176
+ # "category": "APP_BUTTON_GROUP",
177
+ # "status": "SENT",
178
+ # "message_id": "UUID",
179
+ # "data": "Base64 encoded data"
180
+ # }
181
+ # }
182
+ # data format:
183
+ # [
184
+ # {
185
+ # "label": "Mixin Website",
186
+ # "color": "#ABABAB",
187
+ # "action": "https://mixin.one"
188
+ # },
189
+ # ...
190
+ # ]
191
+ def app_button_group(options)
192
+ options.merge!(category: 'APP_BUTTON_GROUP')
193
+ base_message_params(options)
194
+ end
195
+
196
+ # {
197
+ # "id": "UUID",
198
+ # "action": "CREATE_MESSAGE",
199
+ # "params": {
200
+ # "conversation_id": "UUID",
201
+ # "category": "PLAIN_VIDEO",
202
+ # "status": "SENT",
203
+ # "message_id": "UUID",
204
+ # "data": "Base64 encoded data"
205
+ # }
206
+ # }
207
+ # data format:
208
+ # {
209
+ # "attachment_id": "Read From POST /attachments",
210
+ # "mime_type": "",
211
+ # "width": 1024,
212
+ # "height": 1024,
213
+ # "size": 1024,
214
+ # "duration": 1024,
215
+ # "thumbnail": "base64 encoded"
216
+ # }
217
+ def plain_video(options)
218
+ options.merge!(category: 'PLAIN_VIDEO')
219
+ base_message_params(options)
220
+ end
221
+
222
+ def recall_message_params(message_id, options)
223
+ raise 'recipient_id is required!' if options[:recipient_id].nil?
224
+
225
+ options.merge!(
226
+ category: 'MESSAGE_RECALL',
227
+ data: {
228
+ message_id: message_id
229
+ }
230
+ )
231
+ base_message_params(options)
232
+ end
233
+
234
+ # base format of message params
235
+ def base_message_params(options)
236
+ data = options[:data].is_a?(String) ? options[:data] : options[:data].to_json
237
+ {
238
+ conversation_id: options[:conversation_id],
239
+ recipient_id: options[:recipient_id],
240
+ representative_id: options[:representative_id],
241
+ category: options[:category],
47
242
  status: 'SENT',
48
- message_id: SecureRandom.uuid,
49
- data: encoded_data
243
+ quote_message_id: options[:quote_message_id],
244
+ message_id: options[:message_id] || SecureRandom.uuid,
245
+ data: Base64.encode64(data)
50
246
  }
51
-
52
- write_message('CREATE_MESSAGE', params)
53
247
  end
54
248
 
55
- def read_message(data)
249
+ # read the gzipped message form websocket
250
+ def read_ws_message(data)
56
251
  io = StringIO.new(data.pack('c*'), 'rb')
57
252
  gzip = Zlib::GzipReader.new io
58
253
  msg = gzip.read
59
254
  gzip.close
60
- return msg
255
+
256
+ msg
61
257
  end
62
258
 
63
- def write_message(action, params)
259
+ # gzip the message for websocket
260
+ def write_ws_message(params:, action: 'CREATE_MESSAGE')
64
261
  msg = {
65
262
  id: SecureRandom.uuid,
66
- action: action,
263
+ action: action,
67
264
  params: params
68
265
  }.to_json
69
266
 
@@ -71,7 +268,62 @@ module MixinBot
71
268
  gzip = Zlib::GzipWriter.new io
72
269
  gzip.write msg
73
270
  gzip.close
74
- data = io.string.unpack('c*')
271
+ io.string.unpack('c*')
272
+ end
273
+
274
+ # use HTTP to send message
275
+ def send_text_message(options)
276
+ send_message plain_text(options)
277
+ end
278
+
279
+ def send_post_message(options)
280
+ send_message plain_post(options)
281
+ end
282
+
283
+ def send_contact_message(options)
284
+ send_message plain_contact(options)
285
+ end
286
+
287
+ def send_app_card_message(options)
288
+ send_message app_card(options)
289
+ end
290
+
291
+ def send_app_button_group_message(options)
292
+ send_message app_button_group(options)
293
+ end
294
+
295
+ def recall_message(message_id, options)
296
+ send_message [recall_message_params(message_id, options)]
297
+ end
298
+
299
+ # {
300
+ # "id": "UUID",
301
+ # "action": "CREATE_PLAIN_MESSAGES",
302
+ # "params": {
303
+ # "messages": [
304
+ # {
305
+ # "conversation_id": "UUID",
306
+ # "recipient_id": "UUID",
307
+ # "message_id": "UUID",
308
+ # "representative_id": "UUID (optional, only supported in peer to peer conversation)",
309
+ # "quote_message_id": "UUID (optional, only supported text, e.g. PLAIN_TEXT)",
310
+ # "category": "Only support plain category e.g.: PLAIN_TEXT, PLAIN_STICKER etc",
311
+ # "data": "Correspond to category."
312
+ # },
313
+ # ...
314
+ # ]
315
+ # }
316
+ # }
317
+ def send_plain_messages(messages)
318
+ send_message messages
319
+ end
320
+
321
+ # http post request
322
+ def send_message(payload)
323
+ path = '/messages'
324
+ access_token ||= access_token('POST', path, payload.to_json)
325
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
326
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
75
327
  end
76
328
  end
77
329
  end
@@ -0,0 +1,335 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class API
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
+ def multisigs(limit: 100, offset: nil, access_token: nil)
32
+ path = format('/multisigs?limit=%<limit>s&offset=%<offset>s', limit: limit, offset: offset)
33
+ access_token ||= access_token('GET', path, '')
34
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
35
+ client.get(path, headers: { 'Authorization': authorization })
36
+ end
37
+
38
+ def all_multisigs(utxos: [], offset: nil, access_token: nil)
39
+ res = multisigs(limit: 100, offset: offset, access_token: access_token)
40
+
41
+ return [] if res['data'].nil?
42
+
43
+ utxos += res['data']
44
+
45
+ if res['data'].length < 100
46
+ utxos
47
+ else
48
+ all_multisigs(utxos: utxos, offset: utxos[-1]['created_at'], access_token: access_token)
49
+ end
50
+ end
51
+
52
+ def create_output(receivers:, index:, access_token: nil)
53
+ path = '/outputs'
54
+ payload = {
55
+ receivers: receivers,
56
+ index: index
57
+ }
58
+ access_token ||= access_token('POST', path, payload.to_json)
59
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
60
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
61
+ end
62
+
63
+ # transfer from the multisig address
64
+ # create a request for multi sign
65
+ # for now, raw(RAW-TRANSACTION-HEX) can only be generated by Mixin SDK of Golang or Javascript
66
+ def create_sign_multisig_request(raw, access_token: nil)
67
+ path = '/multisigs'
68
+ payload = {
69
+ action: 'sign',
70
+ raw: raw
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
+ # transfer from the multisig address
78
+ # create a request for unlock a multi-sign
79
+ def create_unlock_multisig_request(raw, access_token: nil)
80
+ path = '/multisigs'
81
+ payload = {
82
+ action: 'unlock',
83
+ raw: raw
84
+ }
85
+ access_token ||= access_token('POST', path, payload.to_json)
86
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
87
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
88
+ end
89
+
90
+ def sign_multisig_request(request_id, pin)
91
+ path = format('/multisigs/%<request_id>s/sign', request_id: request_id)
92
+ payload = {
93
+ pin: encrypt_pin(pin)
94
+ }
95
+ access_token ||= access_token('POST', path, payload.to_json)
96
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
97
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
98
+ end
99
+
100
+ def unlock_multisig_request(request_id, pin)
101
+ path = format('/multisigs/%<request_id>s/unlock', request_id: request_id)
102
+ payload = {
103
+ pin: encrypt_pin(pin)
104
+ }
105
+ access_token ||= access_token('POST', path, payload.to_json)
106
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
107
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
108
+ end
109
+
110
+ def cancel_multisig_request(request_id, pin)
111
+ path = format('/multisigs/%<request_id>s/cancel', request_id: request_id)
112
+ payload = {
113
+ pin: encrypt_pin(pin)
114
+ }
115
+ access_token ||= access_token('POST', path, payload.to_json)
116
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
117
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
118
+ end
119
+
120
+ # pay to the multisig address
121
+ # used for create multisig payment code_id
122
+ def create_multisig_payment(params)
123
+ path = '/payments'
124
+ payload = {
125
+ asset_id: params[:asset_id],
126
+ amount: params[:amount].to_s,
127
+ trace_id: params[:trace_id] || SecureRandom.uuid,
128
+ memo: params[:memo],
129
+ opponent_multisig: {
130
+ receivers: params[:receivers],
131
+ threshold: params[:threshold]
132
+ }
133
+ }
134
+ access_token = params[:access_token]
135
+ access_token ||= access_token('POST', path, payload.to_json)
136
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
137
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
138
+ end
139
+
140
+ def verify_multisig(code_id, access_token: nil)
141
+ path = format('/codes/%<code_id>s', code_id: code_id)
142
+ access_token ||= access_token('GET', path, '')
143
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
144
+ client.get(path, headers: { 'Authorization': authorization })
145
+ end
146
+
147
+ # send a signed transaction to main net
148
+ def send_raw_transaction(raw, access_token: nil)
149
+ path = '/external/proxy'
150
+ payload = {
151
+ method: 'sendrawtransaction',
152
+ params: [raw]
153
+ }
154
+
155
+ access_token ||= access_token('POST', path, payload.to_json)
156
+ authorization = format('Bearer %<access_token>s', access_token: access_token)
157
+ client.post(path, headers: { 'Authorization': authorization }, json: payload)
158
+ end
159
+
160
+ def build_threshold_script(threshold)
161
+ s = threshold.to_s(16)
162
+ s = s.length == 1 ? "0#{s}" : s
163
+ raise 'NVALID THRESHOLD' if s.length > 2
164
+
165
+ "fffe#{s}"
166
+ end
167
+
168
+ # filter utxo by members, asset_id and threshold
169
+ def filter_utxos(params)
170
+ utxos = all_multisigs(access_token: params[:access_token])
171
+
172
+ unless params[:members].nil?
173
+ utxos = utxos.filter(
174
+ &lambda { |utxo|
175
+ utxo['members'].sort == params[:members].sort
176
+ }
177
+ )
178
+ end
179
+
180
+ unless params[:asset_id].nil?
181
+ utxos = utxos.filter(
182
+ &lambda { |utxo|
183
+ utxo['asset_id'] == params[:asset_id]
184
+ }
185
+ )
186
+ end
187
+
188
+ unless params[:threshold].nil?
189
+ utxos = utxos.filter(
190
+ &lambda { |utxo|
191
+ utxo['threshold'] == params[:threshold]
192
+ }
193
+ )
194
+ end
195
+
196
+ unless params[:state].nil?
197
+ utxos = utxos.filter(
198
+ &lambda { |utxo|
199
+ utxo['state'] == params[:state]
200
+ }
201
+ )
202
+ end
203
+
204
+ utxos
205
+ end
206
+
207
+ # params:
208
+ # {
209
+ # senders: [ uuid ],
210
+ # receivers: [ uuid ],
211
+ # threshold: integer,
212
+ # asset_id: uuid,
213
+ # asset_mixin_id: string,
214
+ # amount: string / float,
215
+ # memo: string,
216
+ # }
217
+ def build_raw_transaction(params)
218
+ senders = params[:senders]
219
+ receivers = params[:receivers]
220
+ asset_id = params[:asset_id]
221
+ asset_mixin_id = params[:asset_mixin_id]
222
+ amount = params[:amount]
223
+ memo = params[:memo]
224
+ threshold = params[:threshold]
225
+ access_token = params[:access_token]
226
+ utxos = params[:utxos]
227
+
228
+ raise 'access_token required!' if access_token.nil? && !senders.include?(client_id)
229
+
230
+ # default to use all unspent utxo
231
+ utxos ||= filter_utxos(
232
+ members: senders,
233
+ asset_id: asset_id,
234
+ threshold: threshold,
235
+ state: 'unspent',
236
+ access_token: access_token
237
+ )
238
+ amount = amount.to_f.round(8)
239
+ input_amount = utxos.map(
240
+ &lambda { |utxo|
241
+ utxo['amount'].to_f
242
+ }
243
+ ).sum.round(8)
244
+
245
+ if input_amount < amount
246
+ raise format(
247
+ 'not enough amount! %<input_amount>s < %<amount>s',
248
+ input_amount: input_amount,
249
+ amount: amount
250
+ )
251
+ end
252
+
253
+ inputs = utxos.map(
254
+ &lambda { |utx|
255
+ {
256
+ 'hash' => utx['transaction_hash'],
257
+ 'index' => utx['output_index']
258
+ }
259
+ }
260
+ )
261
+
262
+ outputs = []
263
+ output0 = create_output(receivers: receivers, index: 0)['data']
264
+ output0['amount'] = format('%<amount>.8f', amount: amount)
265
+ output0['script'] = build_threshold_script(receivers.length)
266
+ outputs << output0
267
+
268
+ if input_amount > amount
269
+ output1 = create_output(receivers: senders, index: 1)['data']
270
+ output1['amount'] = format('%<amount>.8f', amount: input_amount - amount)
271
+ output1['script'] = build_threshold_script(utxos[0]['threshold'].to_i)
272
+ outputs << output1
273
+ end
274
+
275
+ extra = Digest.hexencode memo.to_s.slice(0, 140)
276
+ tx = {
277
+ version: 1,
278
+ asset: asset_mixin_id,
279
+ inputs: inputs,
280
+ outputs: outputs,
281
+ extra: extra
282
+ }
283
+
284
+ build_transaction tx.to_json
285
+ end
286
+
287
+ def str_to_bin(str)
288
+ return if str.nil?
289
+
290
+ str.scan(/../).map(&:hex).pack('c*')
291
+ end
292
+
293
+ def build_inputs(inputs)
294
+ res = []
295
+ prototype = {
296
+ 'Hash' => nil,
297
+ 'Index' => nil,
298
+ 'Genesis' => nil,
299
+ 'Deposit' => nil,
300
+ 'Mint' => nil
301
+ }
302
+ inputs.each do |input|
303
+ struc = prototype.dup
304
+ struc['Hash'] = str_to_bin input['hash']
305
+ struc['Index'] = input['index']
306
+ res << struc
307
+ end
308
+
309
+ res
310
+ end
311
+
312
+ def build_outputs(outputs)
313
+ res = []
314
+ prototype = {
315
+ 'Type' => 0,
316
+ 'Amount' => nil,
317
+ 'Keys' => nil,
318
+ 'Script' => nil,
319
+ 'Mask' => nil
320
+ }
321
+ outputs.each do |output|
322
+ struc = prototype.dup
323
+ struc['Type'] = str_to_bin output['type']
324
+ struc['Amount'] = str_to_bin output['amount']
325
+ struc['Keys'] = output['keys'].map(&->(key) { str_to_bin(key) })
326
+ struc['Script'] = str_to_bin output['script']
327
+ struc['Mask'] = str_to_bin output['mask']
328
+ res << struc
329
+ end
330
+
331
+ res
332
+ end
333
+ end
334
+ end
335
+ end