mixin_bot 0.5.4 → 0.6.1
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 +0 -2
- data/lib/mixin_bot/api/collectible.rb +127 -0
- data/lib/mixin_bot/api/conversation.rb +1 -18
- data/lib/mixin_bot/api/multisig.rb +23 -75
- data/lib/mixin_bot/api.rb +22 -1
- data/lib/mixin_bot/utils.rb +608 -0
- data/lib/mixin_bot/version.rb +1 -1
- data/lib/mixin_bot.rb +2 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fea4c2a8e738f32bcadb126f775042eb9c0a57d9fcfd168948127e8332e58a9a
|
4
|
+
data.tar.gz: 9f0699d3a105c499530230b565b6c22a8cd74dcda857a220fb6de73a899f1ffe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 774bf51ca38c7e507e4e6da343e139500715d7c150fb04083f4418e9a3a8fe6627383230953807f7463cc041391e8649c01290e634783b6e04b47f150eb01fe6
|
7
|
+
data.tar.gz: 69732a0f23244b8c62aa4fd553af66239df418427c9f7b0afd1437cd8821a9d05df3ac8c1ad4d60c66559b613fce5dc4268e67b5122bae7d7aeff20273a301ce
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Collectible
|
6
|
+
NFT_ASSET_MIXIN_ID = '1700941284a95f31b25ec8c546008f208f88eee4419ccdcdbe6e3195e60128ca'
|
7
|
+
|
8
|
+
def collectible(id, access_token: nil)
|
9
|
+
path = "/collectibles/tokens/#{id}"
|
10
|
+
access_token ||= access_token('GET', path, '')
|
11
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
12
|
+
client.get(path, headers: { 'Authorization': authorization })
|
13
|
+
end
|
14
|
+
|
15
|
+
def collectible_outputs(**kwargs)
|
16
|
+
limit = kwargs[:limit] || 100
|
17
|
+
offset = kwargs[:offset] || ''
|
18
|
+
state = kwargs[:state] || ''
|
19
|
+
members = kwargs[:members] || [client_id]
|
20
|
+
threshold = kwargs[:threshold] || 1
|
21
|
+
access_token = kwargs[:access_token]
|
22
|
+
members = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
|
23
|
+
|
24
|
+
path = format(
|
25
|
+
'/collectibles/outputs?limit=%<limit>s&offset=%<offset>s&state=%<state>s&members=%<members>s&threshold=%<threshold>s',
|
26
|
+
limit: limit,
|
27
|
+
offset: offset,
|
28
|
+
state: state,
|
29
|
+
members: members,
|
30
|
+
threshold: threshold
|
31
|
+
)
|
32
|
+
access_token ||= access_token('GET', path, '')
|
33
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
34
|
+
client.get(path, headers: { 'Authorization': authorization })
|
35
|
+
end
|
36
|
+
alias collectibles collectible_outputs
|
37
|
+
|
38
|
+
COLLECTABLE_REQUEST_ACTIONS = %i[sign unlock].freeze
|
39
|
+
def create_collectible_request(action, raw, access_token: nil)
|
40
|
+
raise ArgumentError, "request action is limited in #{COLLECTABLE_REQUEST_ACTIONS.join(', ')}" unless COLLECTABLE_REQUEST_ACTIONS.include? action.to_sym
|
41
|
+
path = '/collectibles/requests'
|
42
|
+
payload = {
|
43
|
+
action: action,
|
44
|
+
raw: raw
|
45
|
+
}
|
46
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
47
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
48
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_sign_collectible_request(raw, access_token: nil)
|
52
|
+
create_collectible_request 'sign', raw, access_token: access_token
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_unlock_collectible_request(raw, access_token: nil)
|
56
|
+
create_collectible_request 'unlock', raw, access_token: access_token
|
57
|
+
end
|
58
|
+
|
59
|
+
def sign_collectible_request(request_id, pin)
|
60
|
+
path = format('/collectibles/requests/%<request_id>s/sign', request_id: request_id)
|
61
|
+
payload = {
|
62
|
+
pin: encrypt_pin(pin)
|
63
|
+
}
|
64
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
65
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
66
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
67
|
+
end
|
68
|
+
|
69
|
+
def unlock_collectible_request(request_id, pin)
|
70
|
+
path = format('/collectibles/requests/%<request_id>s/unlock', request_id: request_id)
|
71
|
+
payload = {
|
72
|
+
pin: encrypt_pin(pin)
|
73
|
+
}
|
74
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
75
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
76
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
77
|
+
end
|
78
|
+
|
79
|
+
def cancel_collectible_request(request_id, pin)
|
80
|
+
path = format('/collectibles/requests/%<request_id>s/cancel', request_id: request_id)
|
81
|
+
payload = {
|
82
|
+
pin: encrypt_pin(pin)
|
83
|
+
}
|
84
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
85
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
86
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# collectible = {
|
91
|
+
# type: 'non_fungible_output',
|
92
|
+
# user_id: '',
|
93
|
+
# output_id: '',
|
94
|
+
# token_id: '',
|
95
|
+
# transaction_hash: '',
|
96
|
+
# output_index: '',
|
97
|
+
# amount: 1,
|
98
|
+
# senders: [],
|
99
|
+
# sender_threshold: 1,
|
100
|
+
# receivers: [],
|
101
|
+
# receivers_threshold: 1,
|
102
|
+
# state: 'unspent'
|
103
|
+
# }
|
104
|
+
COLLECTIBLE_TRANSACTION_ARGUMENTS = %i[collectible nfo receivers threshold].freeze
|
105
|
+
def build_collectible_transaction(**kwargs)
|
106
|
+
raise ArgumentError, "#{COLLECTIBLE_TRANSACTION_ARGUMENTS.join(', ')} are needed for build collectible transaction" unless COLLECTIBLE_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
107
|
+
|
108
|
+
kwargs = kwargs.with_indifferent_access
|
109
|
+
collectible = kwargs['collectible']
|
110
|
+
raise "collectible is #{collectible['state']}" unless collectible['state'] == 'unspent'
|
111
|
+
|
112
|
+
build_raw_transaction(
|
113
|
+
utxos: [collectible],
|
114
|
+
senders: collectible['receivers'],
|
115
|
+
receivers: kwargs['receivers'],
|
116
|
+
threshold: kwargs['threshold'],
|
117
|
+
extra: kwargs["nfo"],
|
118
|
+
amount: 1,
|
119
|
+
asset_mixin_id: NFT_ASSET_MIXIN_ID
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
def nft_memo(collection, token_id, meta)
|
124
|
+
MixinBot::Utils.nft_memo collection, token_id, meta
|
125
|
+
end
|
126
|
+
end
|
127
|
+
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
|
@@ -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 MULTISIG_REQUEST_ACTIONS.include? action.to_sym
|
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: 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: 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)
|
@@ -176,7 +151,7 @@ module MixinBot
|
|
176
151
|
# amount: string / float,
|
177
152
|
# memo: string,
|
178
153
|
# }
|
179
|
-
RAW_TRANSACTION_ARGUMENTS = %i[senders receivers amount threshold
|
154
|
+
RAW_TRANSACTION_ARGUMENTS = %i[utxos senders receivers amount threshold].freeze
|
180
155
|
def build_raw_transaction(**kwargs)
|
181
156
|
raise ArgumentError, "#{RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build raw transaction" unless RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
182
157
|
|
@@ -185,24 +160,14 @@ module MixinBot
|
|
185
160
|
amount = kwargs[:amount]
|
186
161
|
threshold = kwargs[:threshold]
|
187
162
|
asset_id = kwargs[:asset_id]
|
163
|
+
asset_mixin_id = kwargs[:asset_mixin_id]
|
188
164
|
utxos = kwargs[:utxos]
|
189
165
|
memo = kwargs[:memo]
|
166
|
+
extra = kwargs[:extra]
|
190
167
|
access_token = kwargs[:access_token]
|
191
168
|
|
192
169
|
raise 'access_token required!' if access_token.nil? && !senders.include?(client_id)
|
193
170
|
|
194
|
-
# default to use all(first 100) unspent utxo
|
195
|
-
utxos ||= multisigs(
|
196
|
-
members: senders,
|
197
|
-
threshold: threshold,
|
198
|
-
state: 'unspent',
|
199
|
-
access_token: access_token
|
200
|
-
)['data'].filter(
|
201
|
-
&lambda { |utxo|
|
202
|
-
utxo['asset_id'] == kwargs[:asset_id]
|
203
|
-
}
|
204
|
-
)
|
205
|
-
|
206
171
|
amount = amount.to_f.round(8)
|
207
172
|
input_amount = utxos.map(
|
208
173
|
&lambda { |utxo|
|
@@ -246,10 +211,11 @@ module MixinBot
|
|
246
211
|
}
|
247
212
|
end
|
248
213
|
|
249
|
-
extra = Digest.hexencode
|
214
|
+
extra = extra || Digest.hexencode(memo.to_s.slice(0, 140))
|
215
|
+
asset = asset_mixin_id || SHA3::Digest::SHA256.hexdigest(asset_id)
|
250
216
|
tx = {
|
251
|
-
version:
|
252
|
-
asset:
|
217
|
+
version: 2,
|
218
|
+
asset: asset,
|
253
219
|
inputs: inputs,
|
254
220
|
outputs: outputs,
|
255
221
|
extra: extra
|
@@ -283,26 +249,8 @@ module MixinBot
|
|
283
249
|
res
|
284
250
|
end
|
285
251
|
|
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
|
252
|
+
def generate_trace_from_hash(hash, output_index = 0)
|
253
|
+
MixinBot::Utils.generate_trace_from_hash hash, output_index
|
306
254
|
end
|
307
255
|
end
|
308
256
|
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,16 @@ 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
|
+
|
51
|
+
def decode_raw_transaction(raw)
|
52
|
+
MixinBot::Utils.decode_raw_transaction raw
|
53
|
+
end
|
54
|
+
|
46
55
|
# Use a mixin software to implement transaction build
|
47
|
-
def
|
56
|
+
def sign_raw_transaction_native(json)
|
48
57
|
ensure_mixin_command_exist
|
49
58
|
command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
|
50
59
|
|
@@ -54,11 +63,23 @@ module MixinBot
|
|
54
63
|
output.chomp
|
55
64
|
end
|
56
65
|
|
66
|
+
# Use a mixin software to implement transaction build
|
67
|
+
def decode_raw_transaction_native(raw)
|
68
|
+
ensure_mixin_command_exist
|
69
|
+
command = format("mixin decoderawtransaction --raw '%<arg>s'", arg: raw)
|
70
|
+
|
71
|
+
output, error = Open3.capture3(command)
|
72
|
+
raise error unless error.empty?
|
73
|
+
|
74
|
+
JSON.parse output.chomp
|
75
|
+
end
|
76
|
+
|
57
77
|
include MixinBot::API::App
|
58
78
|
include MixinBot::API::Asset
|
59
79
|
include MixinBot::API::Attachment
|
60
80
|
include MixinBot::API::Auth
|
61
81
|
include MixinBot::API::Blaze
|
82
|
+
include MixinBot::API::Collectible
|
62
83
|
include MixinBot::API::Conversation
|
63
84
|
include MixinBot::API::Me
|
64
85
|
include MixinBot::API::Message
|
@@ -0,0 +1,608 @@
|
|
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
|
+
raise "#{tx} is not a valid json" unless tx.is_a? Hash
|
62
|
+
|
63
|
+
tx = tx.with_indifferent_access
|
64
|
+
bytes = []
|
65
|
+
|
66
|
+
# magic number
|
67
|
+
bytes += MAGIC
|
68
|
+
|
69
|
+
# version
|
70
|
+
bytes += [0, tx['version']]
|
71
|
+
|
72
|
+
# asset
|
73
|
+
bytes += [tx['asset']].pack('H*').bytes
|
74
|
+
|
75
|
+
# inputs
|
76
|
+
bytes += encode_inputs tx['inputs']
|
77
|
+
|
78
|
+
# output
|
79
|
+
bytes += encode_outputs tx['outputs']
|
80
|
+
|
81
|
+
# extra
|
82
|
+
extra_bytes = [tx['extra']].pack('H*').bytes
|
83
|
+
bytes += encode_int extra_bytes.size
|
84
|
+
bytes += extra_bytes
|
85
|
+
|
86
|
+
# aggregated
|
87
|
+
aggregated = tx['aggregated']
|
88
|
+
if aggregated.nil?
|
89
|
+
# signatures
|
90
|
+
bytes += encode_signatures tx['signatures']
|
91
|
+
else
|
92
|
+
bytes += encode_aggregated_signature aggregated
|
93
|
+
end
|
94
|
+
|
95
|
+
bytes.pack('C*').unpack1('H*')
|
96
|
+
end
|
97
|
+
|
98
|
+
def decode_raw_transaction(raw)
|
99
|
+
bytes = [raw].pack('H*').bytes
|
100
|
+
tx = {}
|
101
|
+
|
102
|
+
magic = bytes.shift(2)
|
103
|
+
raise 'Not valid raw' unless magic == MAGIC
|
104
|
+
|
105
|
+
version = bytes.shift(2)
|
106
|
+
tx['version'] = bytes_to_int version
|
107
|
+
|
108
|
+
asset = bytes.shift(32)
|
109
|
+
tx['asset'] = asset.pack('C*').unpack1('H*')
|
110
|
+
|
111
|
+
# read inputs
|
112
|
+
bytes, tx = decode_inputs bytes, tx
|
113
|
+
|
114
|
+
# read outputs
|
115
|
+
bytes, tx = decode_outputs bytes, tx
|
116
|
+
|
117
|
+
extra_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
118
|
+
tx['extra'] = bytes.shift(extra_size).pack('C*').unpack1('H*')
|
119
|
+
|
120
|
+
num = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
121
|
+
if num == MAX_ENCODE_INT
|
122
|
+
# aggregated
|
123
|
+
aggregated = {}
|
124
|
+
|
125
|
+
raise 'invalid aggregated' unless bytes.shift(2).reverse.pack('C*').unpack1('S*') == AGGREGATED_SIGNATURE_PREFIX
|
126
|
+
|
127
|
+
aggregated['signature'] = bytes.shift(64).pack('C*').unpack1('H*')
|
128
|
+
|
129
|
+
byte = bytes.shift
|
130
|
+
case byte
|
131
|
+
when AGGREGATED_SIGNATURE_ORDINAY_MASK.first
|
132
|
+
aggregated['signers'] = []
|
133
|
+
masks_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
134
|
+
masks = bytes.shift(masks_size)
|
135
|
+
masks = [masks] unless masks.is_a? Array
|
136
|
+
|
137
|
+
masks.each_with_index do |mask, i|
|
138
|
+
8.times do |j|
|
139
|
+
k = 1 << j
|
140
|
+
aggregated['signers'].push(i * 8 + j) if mask & k == k
|
141
|
+
end
|
142
|
+
end
|
143
|
+
when AGGREGATED_SIGNATURE_SPARSE_MASK.first
|
144
|
+
signers_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
145
|
+
return if signers_size == 0
|
146
|
+
|
147
|
+
aggregated['signers'] = []
|
148
|
+
signers_size.times do
|
149
|
+
aggregated['signers'].push bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
tx['aggregated'] = aggregated
|
154
|
+
else
|
155
|
+
if !bytes.empty? && bytes[...2] != NULL_BYTES
|
156
|
+
signatures_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
157
|
+
tx['signatures'] = bytes.shift(signatures_size).pack('C*').unpack1('H*')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
tx
|
162
|
+
end
|
163
|
+
|
164
|
+
def nft_memo_hash(collection, token_id, meta)
|
165
|
+
collection = NULL_UUID if collection.empty?
|
166
|
+
meta = meta.to_json if meta.is_a?(Hash)
|
167
|
+
|
168
|
+
memo = {
|
169
|
+
prefix: NFT_MEMO_PREFIX,
|
170
|
+
version: NFT_MEMO_VERSION,
|
171
|
+
mask: 0,
|
172
|
+
chain: NFT_MEMO_DEFAULT_CHAIN,
|
173
|
+
class: NFT_MEMO_DEFAULT_CLASS,
|
174
|
+
collection: collection,
|
175
|
+
token: token_id,
|
176
|
+
extra: SHA3::Digest::SHA256.hexdigest(meta)
|
177
|
+
}
|
178
|
+
|
179
|
+
mark = [0]
|
180
|
+
mark.map do |index|
|
181
|
+
if index >= 64
|
182
|
+
raise "invalid NFO memo index #{index}"
|
183
|
+
end
|
184
|
+
memo[:mask] = memo[:mask] ^ (1 << index)
|
185
|
+
end
|
186
|
+
|
187
|
+
memo
|
188
|
+
end
|
189
|
+
|
190
|
+
def nft_memo(collection, token_id, meta)
|
191
|
+
encode_nft_memo nft_memo_hash(collection, token_id, meta)
|
192
|
+
end
|
193
|
+
|
194
|
+
def encode_nft_memo(memo)
|
195
|
+
bytes = []
|
196
|
+
|
197
|
+
bytes += NFT_MEMO_PREFIX.bytes
|
198
|
+
bytes += [NFT_MEMO_VERSION]
|
199
|
+
|
200
|
+
if memo[:mask] != 0
|
201
|
+
bytes += [1]
|
202
|
+
bytes += encode_unit_64 memo[:mask]
|
203
|
+
bytes += memo[:chain].split('-').pack('H* H* H* H* H*').bytes
|
204
|
+
|
205
|
+
class_bytes = [memo[:class]].pack('H*').bytes
|
206
|
+
bytes += bytes_of class_bytes.size
|
207
|
+
bytes += class_bytes
|
208
|
+
|
209
|
+
collection_bytes = memo[:collection].split('-').pack('H* H* H* H* H*').bytes
|
210
|
+
bytes += bytes_of collection_bytes.size
|
211
|
+
bytes += collection_bytes
|
212
|
+
|
213
|
+
# token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
|
214
|
+
token_bytes = bytes_of memo[:token]
|
215
|
+
bytes += bytes_of token_bytes.size
|
216
|
+
bytes += token_bytes
|
217
|
+
end
|
218
|
+
|
219
|
+
extra_bytes = [memo[:extra]].pack('H*').bytes
|
220
|
+
bytes += bytes_of extra_bytes.size
|
221
|
+
bytes += extra_bytes
|
222
|
+
|
223
|
+
Base64.urlsafe_encode64 bytes.pack('C*'), padding: false
|
224
|
+
end
|
225
|
+
|
226
|
+
def decode_nft_memo(encoded)
|
227
|
+
bytes = Base64.urlsafe_decode64(encoded).bytes
|
228
|
+
memo = {}
|
229
|
+
memo[:prefix] = bytes.shift(3).pack('C*')
|
230
|
+
memo[:version] = bytes.shift
|
231
|
+
|
232
|
+
hint = bytes.shift
|
233
|
+
if hint == 1
|
234
|
+
memo[:mask] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
|
235
|
+
memo[:chain] = hex_to_uuid bytes.shift(16).pack('C*').unpack1('H*')
|
236
|
+
|
237
|
+
class_length = bytes.shift
|
238
|
+
memo[:class] = bytes.shift(class_length).pack('C*').unpack1('H*')
|
239
|
+
|
240
|
+
collection_length = bytes.shift
|
241
|
+
memo[:collection] = hex_to_uuid bytes.shift(collection_length).pack('C*').unpack1('H*')
|
242
|
+
|
243
|
+
token_length = bytes.shift
|
244
|
+
memo[:token] = bytes_to_int bytes.shift(token_length)
|
245
|
+
end
|
246
|
+
|
247
|
+
extra_length = bytes.shift
|
248
|
+
memo[:extra] = bytes.shift(extra_length).pack('C*').unpack1('H*')
|
249
|
+
|
250
|
+
memo
|
251
|
+
end
|
252
|
+
|
253
|
+
private
|
254
|
+
|
255
|
+
def encode_int(int)
|
256
|
+
raise "only support int #{int}" unless int.is_a?(Integer)
|
257
|
+
raise "int #{int} is larger than MAX_ENCODE_INT #{MAX_ENCODE_INT}" if int > MAX_ENCODE_INT
|
258
|
+
|
259
|
+
[int].pack('S*').bytes.reverse
|
260
|
+
end
|
261
|
+
|
262
|
+
def encode_unit_64(int)
|
263
|
+
[int].pack('Q*').bytes.reverse
|
264
|
+
end
|
265
|
+
|
266
|
+
def bytes_of(int)
|
267
|
+
raise 'not integer' unless int.is_a?(Integer)
|
268
|
+
|
269
|
+
bytes = []
|
270
|
+
loop do
|
271
|
+
break if int === 0
|
272
|
+
bytes.push int & 255
|
273
|
+
int = int / (2 ** 8) | 0
|
274
|
+
end
|
275
|
+
|
276
|
+
bytes.reverse
|
277
|
+
end
|
278
|
+
|
279
|
+
def bytes_to_int(bytes)
|
280
|
+
int = 0
|
281
|
+
bytes.each do |byte|
|
282
|
+
int = int * (2 ** 8) + byte
|
283
|
+
end
|
284
|
+
|
285
|
+
int
|
286
|
+
end
|
287
|
+
|
288
|
+
def encode_inputs(inputs, bytes = [])
|
289
|
+
bytes += encode_int(inputs.size)
|
290
|
+
inputs.each do |input|
|
291
|
+
bytes += [input['hash']].pack('H*').bytes
|
292
|
+
bytes += encode_int(input['index'])
|
293
|
+
|
294
|
+
# genesis
|
295
|
+
genesis = input['genesis'] || ''
|
296
|
+
if genesis.empty?
|
297
|
+
bytes += NULL_BYTES
|
298
|
+
else
|
299
|
+
genesis_bytes = [genesis].pack('H*').bytes
|
300
|
+
bytes += encode_int genesis_bytes.size
|
301
|
+
bytes += genesis_bytes
|
302
|
+
end
|
303
|
+
|
304
|
+
# deposit
|
305
|
+
deposit = input['deposit']
|
306
|
+
if deposit.nil?
|
307
|
+
bytes += NULL_BYTES
|
308
|
+
else
|
309
|
+
bytes += MAGIC
|
310
|
+
bytes += [deposit['chain']].pack('H*').bytes
|
311
|
+
|
312
|
+
asset_bytes = [deposit['asset']].pack('H*')
|
313
|
+
bytes += encode_int asset_bytes.size
|
314
|
+
bytes += asset_bytes
|
315
|
+
|
316
|
+
transaction_bytes = [deposit['transaction']].pack('H*')
|
317
|
+
bytes += encode_int transaction_bytes.size
|
318
|
+
bytes += transaction_bytes
|
319
|
+
|
320
|
+
bytes += encode_unit_64 deposit['index']
|
321
|
+
|
322
|
+
amount_bytes = bytes_of deposit['amount']
|
323
|
+
bytes += encode_int amount_bytes.size
|
324
|
+
bytes += amount_bytes
|
325
|
+
end
|
326
|
+
|
327
|
+
# mint
|
328
|
+
mint = input['mint']
|
329
|
+
if mint.nil?
|
330
|
+
bytes += NULL_BYTES
|
331
|
+
else
|
332
|
+
bytes += MAGIC
|
333
|
+
|
334
|
+
# group
|
335
|
+
group = mint['group'] || ''
|
336
|
+
if group.empty?
|
337
|
+
bytes += encode_int NULL_BYTES
|
338
|
+
else
|
339
|
+
group_bytes = [group].pack('H*')
|
340
|
+
bytes += encode_int group_bytes.size
|
341
|
+
bytes += group_bytes
|
342
|
+
end
|
343
|
+
|
344
|
+
bytes += encode_unit_64 mint['batch']
|
345
|
+
|
346
|
+
amount_bytes = bytes_of mint['amount']
|
347
|
+
bytes += encode_int amount_bytes.size
|
348
|
+
bytes += amount_bytes
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
bytes
|
353
|
+
end
|
354
|
+
|
355
|
+
def encode_outputs(outputs, bytes = [])
|
356
|
+
bytes += encode_int(outputs.size)
|
357
|
+
outputs.each do |output|
|
358
|
+
type = output['type'] || 0
|
359
|
+
bytes += [0x00, type]
|
360
|
+
|
361
|
+
# amount
|
362
|
+
amount_bytes = bytes_of (output['amount'].to_f * 1e8).to_i
|
363
|
+
bytes += encode_int amount_bytes.size
|
364
|
+
bytes += amount_bytes
|
365
|
+
|
366
|
+
# keys
|
367
|
+
bytes += encode_int output['keys'].size
|
368
|
+
output['keys'].each do |key|
|
369
|
+
bytes += [key].pack('H*').bytes
|
370
|
+
end
|
371
|
+
|
372
|
+
# mask
|
373
|
+
bytes += [output['mask']].pack('H*').bytes
|
374
|
+
|
375
|
+
# script
|
376
|
+
script_bytes = [output['script']].pack('H*').bytes
|
377
|
+
bytes += encode_int script_bytes.size
|
378
|
+
bytes += script_bytes
|
379
|
+
|
380
|
+
# withdrawal
|
381
|
+
withdrawal = output['withdrawal']
|
382
|
+
if withdrawal.nil?
|
383
|
+
bytes += NULL_BYTES
|
384
|
+
else
|
385
|
+
bytes += MAGIC
|
386
|
+
|
387
|
+
# chain
|
388
|
+
bytes += [withdrawal['chain']].pack('H*').bytes
|
389
|
+
|
390
|
+
# asset
|
391
|
+
asset_bytes = [withdrawal['asset']].pack('H*')
|
392
|
+
bytes += encode_int asset_bytes.size
|
393
|
+
bytes += asset_bytes
|
394
|
+
|
395
|
+
# address
|
396
|
+
address = withdrawal['address'] || ''
|
397
|
+
if address.empty?
|
398
|
+
bytes += NULL_BYTES
|
399
|
+
else
|
400
|
+
address_bytes = [address].pack('H*').bytes
|
401
|
+
bytes += encode_int address.size
|
402
|
+
bytes += address_bytes
|
403
|
+
end
|
404
|
+
|
405
|
+
# tag
|
406
|
+
tag = withdrawal['tag'] || ''
|
407
|
+
if tag.empty?
|
408
|
+
bytes += NULL_BYTES
|
409
|
+
else
|
410
|
+
address_bytes = [tag].pack('H*').bytes
|
411
|
+
bytes += encode_int tag.size
|
412
|
+
bytes += address_bytes
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
bytes
|
418
|
+
end
|
419
|
+
|
420
|
+
def encode_aggregated_signature(aggregated, bytes = [])
|
421
|
+
bytes += encode_int MAX_ENCODE_INT
|
422
|
+
bytes += encode_int AGGREGATED_SIGNATURE_PREFIX
|
423
|
+
bytes += [aggregated['signature']].pack('H*').bytes
|
424
|
+
|
425
|
+
signers = aggregated['signers']
|
426
|
+
if signers.size == 0
|
427
|
+
bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
|
428
|
+
bytes += NULL_BYTES
|
429
|
+
else
|
430
|
+
signers.each do |sig, i|
|
431
|
+
raise 'signers not sorted' if i > 0 && sig <= signers[i - 1]
|
432
|
+
raise 'signers not sorted' if sig > MAX_ENCODE_INT
|
433
|
+
end
|
434
|
+
|
435
|
+
max = signers.last
|
436
|
+
if (((max / 8 | 0) + 1 | 0) > aggregated['signature'].size * 2)
|
437
|
+
bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
|
438
|
+
bytes += encode_int aggregated['signers'].size
|
439
|
+
signers.map(&->(signer) { bytes += encode_int(signer) })
|
440
|
+
end
|
441
|
+
|
442
|
+
masks_bytes = Array.new(max / 8 + 1, 0)
|
443
|
+
signers.each do |signer|
|
444
|
+
masks[signer/8] = masks[signer/8] ^ (1 << (signer % 8))
|
445
|
+
end
|
446
|
+
bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
|
447
|
+
bytes += encode_int masks_bytes.size
|
448
|
+
bytes += masks_bytes
|
449
|
+
end
|
450
|
+
|
451
|
+
bytes
|
452
|
+
end
|
453
|
+
|
454
|
+
def encode_signatures(signatures, bytes = [])
|
455
|
+
sl =
|
456
|
+
if signatures.is_a? Hash
|
457
|
+
signatures.keys.size
|
458
|
+
else
|
459
|
+
0
|
460
|
+
end
|
461
|
+
|
462
|
+
raise 'signatures overflow' if sl == MAX_ENCODE_INT
|
463
|
+
bytes += encode_int sl
|
464
|
+
|
465
|
+
if sl > 0
|
466
|
+
bytes += encode_int signatures.keys.size
|
467
|
+
signatures.keys.sort.each do |key|
|
468
|
+
bytes += encode_int key
|
469
|
+
bytes += [signatures[key]].pack('H*').bytes
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
bytes
|
474
|
+
end
|
475
|
+
|
476
|
+
def decode_inputs(bytes, tx)
|
477
|
+
inputs_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
478
|
+
tx['inputs'] = []
|
479
|
+
inputs_size.times do
|
480
|
+
input = {}
|
481
|
+
hash = bytes.shift(32)
|
482
|
+
input['hash'] = hash.pack('C*').unpack1('H*')
|
483
|
+
|
484
|
+
index = bytes.shift(2)
|
485
|
+
input['index'] = index.reverse.pack('C*').unpack1('S*')
|
486
|
+
|
487
|
+
if bytes[...2] != NULL_BYTES
|
488
|
+
genesis_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
489
|
+
genesis = bytes.shift(genesis_size)
|
490
|
+
input['genesis'] = genesis.pack('C*').unpack1('H*')
|
491
|
+
else
|
492
|
+
bytes.shift 2
|
493
|
+
end
|
494
|
+
|
495
|
+
if bytes[...2] != NULL_BYTES
|
496
|
+
magic = bytes.shift(2)
|
497
|
+
raise 'Not valid input' unless magic == MAGIC
|
498
|
+
|
499
|
+
deposit = {}
|
500
|
+
deposit['chain'] = bytes.shift(32).pack('C*').unpack1('H*')
|
501
|
+
|
502
|
+
asset_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
503
|
+
deposit['asset'] = bytes.shift(asset_size).unpack1('H*')
|
504
|
+
|
505
|
+
transaction_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
506
|
+
deposit['transaction'] = bytes.shift(transaction_size).unpack1('H*')
|
507
|
+
|
508
|
+
deposit['index'] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
|
509
|
+
|
510
|
+
amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
511
|
+
deposit['amount'] = bytes_to_int bytes.shift(amount_size)
|
512
|
+
|
513
|
+
input['deposit'] = deposit
|
514
|
+
else
|
515
|
+
bytes.shift 2
|
516
|
+
end
|
517
|
+
|
518
|
+
if bytes[...2] != NULL_BYTES
|
519
|
+
magic = bytes.shift(2)
|
520
|
+
raise 'Not valid input' unless magic == MAGIC
|
521
|
+
|
522
|
+
mint = {}
|
523
|
+
if bytes[...2] != NULL_BYTES
|
524
|
+
group_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
525
|
+
mint['group'] = bytes.shift(group_size).unpack1('H*')
|
526
|
+
else
|
527
|
+
bytes.shift 2
|
528
|
+
end
|
529
|
+
|
530
|
+
mint['batch'] = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
|
531
|
+
_amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
532
|
+
mint['amount'] = bytes_to_int bytes.shift(_amount_size)
|
533
|
+
|
534
|
+
input['mint'] = mint
|
535
|
+
else
|
536
|
+
bytes.shift 2
|
537
|
+
end
|
538
|
+
|
539
|
+
tx['inputs'].push input
|
540
|
+
end
|
541
|
+
|
542
|
+
[bytes, tx]
|
543
|
+
end
|
544
|
+
|
545
|
+
def decode_outputs(bytes, tx)
|
546
|
+
outputs_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
547
|
+
tx['outputs'] = []
|
548
|
+
outputs_size.times do
|
549
|
+
output = {}
|
550
|
+
|
551
|
+
bytes.shift
|
552
|
+
type = bytes.shift
|
553
|
+
output['type'] = type
|
554
|
+
|
555
|
+
amount_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
556
|
+
output['amount'] = format('%.8f', bytes_to_int(bytes.shift(amount_size)).to_f / 1e8)
|
557
|
+
|
558
|
+
output['keys'] = []
|
559
|
+
keys_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
560
|
+
keys_size.times do
|
561
|
+
output['keys'].push bytes.shift(32).pack('C*').unpack1('H*')
|
562
|
+
end
|
563
|
+
|
564
|
+
output['mask'] = bytes.shift(32).pack('C*').unpack1('H*')
|
565
|
+
|
566
|
+
script_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
567
|
+
output['script'] = bytes.shift(script_size).pack('C*').unpack1('H*')
|
568
|
+
|
569
|
+
if bytes[...2] != NULL_BYTES
|
570
|
+
magic = bytes.shift(2)
|
571
|
+
raise 'Not valid output' unless magic == MAGIC
|
572
|
+
|
573
|
+
withdraw = {}
|
574
|
+
|
575
|
+
output['chain'] = bytes.shift(32).pack('C*').unpack1('H*')
|
576
|
+
|
577
|
+
asset_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
578
|
+
output['asset'] = bytes.shift(asset_size).unpack1('H*')
|
579
|
+
|
580
|
+
if bytes[...2] != NULL_BYTES
|
581
|
+
address = {}
|
582
|
+
|
583
|
+
adderss_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
584
|
+
output['adderss'] = bytes.shift(adderss_size).pack('C*').unpack1('H*')
|
585
|
+
else
|
586
|
+
bytes.shift 2
|
587
|
+
end
|
588
|
+
|
589
|
+
if bytes[...2] != NULL_BYTES
|
590
|
+
tag = {}
|
591
|
+
|
592
|
+
tag_size = bytes.shift(2).reverse.pack('C*').unpack1('S*')
|
593
|
+
output['tag'] = bytes.shift(tag_size).pack('C*').unpack1('H*')
|
594
|
+
else
|
595
|
+
bytes.shift 2
|
596
|
+
end
|
597
|
+
else
|
598
|
+
bytes.shift 2
|
599
|
+
end
|
600
|
+
|
601
|
+
tx['outputs'].push output
|
602
|
+
end
|
603
|
+
|
604
|
+
[bytes, tx]
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- an-lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-28 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
|
@@ -265,6 +279,7 @@ files:
|
|
265
279
|
- lib/mixin_bot/api/attachment.rb
|
266
280
|
- lib/mixin_bot/api/auth.rb
|
267
281
|
- lib/mixin_bot/api/blaze.rb
|
282
|
+
- lib/mixin_bot/api/collectible.rb
|
268
283
|
- lib/mixin_bot/api/conversation.rb
|
269
284
|
- lib/mixin_bot/api/me.rb
|
270
285
|
- lib/mixin_bot/api/message.rb
|
@@ -281,6 +296,7 @@ files:
|
|
281
296
|
- lib/mixin_bot/cli/multisig.rb
|
282
297
|
- lib/mixin_bot/cli/node.rb
|
283
298
|
- lib/mixin_bot/client.rb
|
299
|
+
- lib/mixin_bot/utils.rb
|
284
300
|
- lib/mixin_bot/version.rb
|
285
301
|
homepage: https://github.com/an-lee/mixin_bot
|
286
302
|
licenses:
|