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 +4 -4
- data/lib/mixin_bot/api/attachment.rb +19 -0
- data/lib/mixin_bot/api/collectible.rb +92 -0
- data/lib/mixin_bot/api/conversation.rb +1 -18
- data/lib/mixin_bot/api/message.rb +8 -0
- data/lib/mixin_bot/api/multisig.rb +17 -60
- data/lib/mixin_bot/api.rb +7 -1
- data/lib/mixin_bot/utils.rb +408 -0
- data/lib/mixin_bot/version.rb +1 -1
- data/lib/mixin_bot.rb +2 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4534f4c2ea6ed1928f5c2a32b5221737ba94ff70cffdaa26a95ae03c710e5186
|
4
|
+
data.tar.gz: dd4a3901a081d50f2c0d3ab751516956e4065c21acaadb980e836b25b099b90a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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:
|
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
|
-
|
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
|
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:
|
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
|
287
|
-
|
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
|
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
|
data/lib/mixin_bot/version.rb
CHANGED
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.
|
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-
|
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:
|