mixin_bot 0.12.1 → 1.0.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/address.rb +21 -0
- data/lib/mixin_bot/api/app.rb +5 -11
- data/lib/mixin_bot/api/asset.rb +9 -16
- data/lib/mixin_bot/api/attachment.rb +27 -22
- data/lib/mixin_bot/api/auth.rb +29 -51
- data/lib/mixin_bot/api/blaze.rb +4 -3
- data/lib/mixin_bot/api/collectible.rb +60 -58
- data/lib/mixin_bot/api/conversation.rb +29 -49
- data/lib/mixin_bot/api/encrypted_message.rb +17 -17
- data/lib/mixin_bot/api/legacy_multisig.rb +87 -0
- data/lib/mixin_bot/api/legacy_output.rb +50 -0
- data/lib/mixin_bot/api/legacy_payment.rb +31 -0
- data/lib/mixin_bot/api/legacy_snapshot.rb +39 -0
- data/lib/mixin_bot/api/legacy_transaction.rb +173 -0
- data/lib/mixin_bot/api/legacy_transfer.rb +42 -0
- data/lib/mixin_bot/api/me.rb +13 -17
- data/lib/mixin_bot/api/message.rb +13 -10
- data/lib/mixin_bot/api/multisig.rb +16 -221
- data/lib/mixin_bot/api/output.rb +46 -0
- data/lib/mixin_bot/api/payment.rb +9 -20
- data/lib/mixin_bot/api/pin.rb +57 -65
- data/lib/mixin_bot/api/rpc.rb +9 -11
- data/lib/mixin_bot/api/snapshot.rb +15 -29
- data/lib/mixin_bot/api/tip.rb +43 -0
- data/lib/mixin_bot/api/transaction.rb +184 -60
- data/lib/mixin_bot/api/transfer.rb +64 -32
- data/lib/mixin_bot/api/user.rb +83 -53
- data/lib/mixin_bot/api/withdraw.rb +52 -53
- data/lib/mixin_bot/api.rb +78 -45
- data/lib/mixin_bot/cli/api.rb +149 -5
- data/lib/mixin_bot/cli/utils.rb +14 -4
- data/lib/mixin_bot/cli.rb +13 -10
- data/lib/mixin_bot/client.rb +76 -127
- data/lib/mixin_bot/configuration.rb +98 -0
- data/lib/mixin_bot/nfo.rb +174 -0
- data/lib/mixin_bot/transaction.rb +505 -0
- data/lib/mixin_bot/utils/address.rb +108 -0
- data/lib/mixin_bot/utils/crypto.rb +182 -0
- data/lib/mixin_bot/utils/decoder.rb +58 -0
- data/lib/mixin_bot/utils/encoder.rb +63 -0
- data/lib/mixin_bot/utils.rb +8 -109
- data/lib/mixin_bot/uuid.rb +41 -0
- data/lib/mixin_bot/version.rb +1 -1
- data/lib/mixin_bot.rb +39 -14
- data/lib/mvm/bridge.rb +2 -19
- data/lib/mvm/client.rb +11 -33
- data/lib/mvm/nft.rb +4 -4
- data/lib/mvm/registry.rb +9 -9
- data/lib/mvm/scan.rb +3 -5
- data/lib/mvm.rb +5 -6
- metadata +101 -44
- data/lib/mixin_bot/utils/nfo.rb +0 -176
- data/lib/mixin_bot/utils/transaction.rb +0 -478
- data/lib/mixin_bot/utils/uuid.rb +0 -43
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
module Utils
|
5
|
+
module Crypto
|
6
|
+
def access_token(method, uri, body = '', **kwargs)
|
7
|
+
sig = Digest::SHA256.hexdigest(method + uri + body.to_s)
|
8
|
+
iat = Time.now.utc.to_i
|
9
|
+
exp = (Time.now.utc + (kwargs[:exp_in] || 600)).to_i
|
10
|
+
scp = kwargs[:scp] || 'FULL'
|
11
|
+
jti = SecureRandom.uuid
|
12
|
+
uid = kwargs[:app_id] || MixinBot.config.app_id
|
13
|
+
sid = kwargs[:session_id] || MixinBot.config.session_id
|
14
|
+
private_key = kwargs[:private_key] || MixinBot.config.session_private_key
|
15
|
+
|
16
|
+
payload = {
|
17
|
+
uid:,
|
18
|
+
sid:,
|
19
|
+
iat:,
|
20
|
+
exp:,
|
21
|
+
jti:,
|
22
|
+
sig:,
|
23
|
+
scp:
|
24
|
+
}
|
25
|
+
|
26
|
+
if private_key.blank?
|
27
|
+
raise ConfigurationNotValidError, 'private_key is required'
|
28
|
+
elsif private_key.size == 64
|
29
|
+
jwk = JOSE::JWK.from_okp [:Ed25519, private_key]
|
30
|
+
jws = JOSE::JWS.from({ 'alg' => 'EdDSA' })
|
31
|
+
else
|
32
|
+
jwk = JOSE::JWK.from_pem private_key
|
33
|
+
jws = JOSE::JWS.from({ 'alg' => 'RS512' })
|
34
|
+
end
|
35
|
+
|
36
|
+
jwt = JOSE::JWT.from payload
|
37
|
+
JOSE::JWT.sign(jwk, jws, jwt).compact
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_ed25519_key
|
41
|
+
ed25519_key = JOSE::JWA::Ed25519.keypair
|
42
|
+
{
|
43
|
+
private_key: Base64.urlsafe_encode64(ed25519_key[1], padding: false),
|
44
|
+
public_key: Base64.urlsafe_encode64(ed25519_key[0], padding: false)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_rsa_key
|
49
|
+
rsa_key = OpenSSL::PKey::RSA.new 1024
|
50
|
+
{
|
51
|
+
private_key: rsa_key.to_pem,
|
52
|
+
public_key: rsa_key.public_key.to_pem
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate_public_key(key)
|
57
|
+
point = JOSE::JWA::FieldElement.new(
|
58
|
+
OpenSSL::BN.new(key.reverse, 2),
|
59
|
+
JOSE::JWA::Edwards25519Point::L
|
60
|
+
)
|
61
|
+
|
62
|
+
(JOSE::JWA::Edwards25519Point.stdbase * point.x.to_i).encode
|
63
|
+
end
|
64
|
+
|
65
|
+
def sign(msg, key:)
|
66
|
+
msg = Digest::Blake3.digest msg
|
67
|
+
|
68
|
+
pub = generate_public_key key
|
69
|
+
|
70
|
+
y_point = JOSE::JWA::FieldElement.new(
|
71
|
+
OpenSSL::BN.new(key.reverse, 2),
|
72
|
+
JOSE::JWA::Edwards25519Point::L
|
73
|
+
)
|
74
|
+
|
75
|
+
key_digest = Digest::SHA512.digest key
|
76
|
+
msg_digest = Digest::SHA512.digest(key_digest[-32...] + msg)
|
77
|
+
|
78
|
+
z_point = JOSE::JWA::FieldElement.new(
|
79
|
+
OpenSSL::BN.new(msg_digest[...64].reverse, 2),
|
80
|
+
JOSE::JWA::Edwards25519Point::L
|
81
|
+
)
|
82
|
+
|
83
|
+
r_point = JOSE::JWA::Edwards25519Point.stdbase * z_point.x.to_i
|
84
|
+
hram_digest = Digest::SHA512.digest(r_point.encode + pub + msg)
|
85
|
+
|
86
|
+
x_point = JOSE::JWA::FieldElement.new(
|
87
|
+
OpenSSL::BN.new(hram_digest[...64].reverse, 2),
|
88
|
+
JOSE::JWA::Edwards25519Point::L
|
89
|
+
)
|
90
|
+
s_point = (x_point * y_point) + z_point
|
91
|
+
|
92
|
+
r_point.encode + s_point.to_bytes(36)
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_unique_uuid(uuid_1, uuid_2)
|
96
|
+
md5 = Digest::MD5.new
|
97
|
+
md5 << [uuid_1, uuid_2].min
|
98
|
+
md5 << [uuid_1, uuid_2].max
|
99
|
+
digest = md5.digest
|
100
|
+
digest6 = ((digest[6].ord & 0x0f) | 0x30).chr
|
101
|
+
digest8 = ((digest[8].ord & 0x3f) | 0x80).chr
|
102
|
+
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
|
103
|
+
|
104
|
+
MixinBot::UUID.new(raw: cipher).unpacked
|
105
|
+
end
|
106
|
+
|
107
|
+
def unique_uuid(*uuids)
|
108
|
+
uuids = uuids.flatten.compact
|
109
|
+
uuids.sort
|
110
|
+
r = uuids.first
|
111
|
+
uuids.each_with_index do |uuid, i|
|
112
|
+
r = generate_unique_uuid(r, uuid) if i.positive?
|
113
|
+
end
|
114
|
+
|
115
|
+
r
|
116
|
+
end
|
117
|
+
|
118
|
+
def generate_trace_from_hash(hash, output_index = 0)
|
119
|
+
md5 = Digest::MD5.new
|
120
|
+
md5 << hash
|
121
|
+
md5 << [output_index].pack('c*') if output_index.positive? && output_index < 256
|
122
|
+
digest = md5.digest
|
123
|
+
digest[6] = ((digest[6].ord & 0x0f) | 0x30).chr
|
124
|
+
digest[8] = ((digest[8].ord & 0x3f) | 0x80).chr
|
125
|
+
|
126
|
+
MixinBot::UUID.new(raw: digest).unpacked
|
127
|
+
end
|
128
|
+
|
129
|
+
# decrypt the encrpted pin, just for test
|
130
|
+
def decrypt_pin(msg, shared_key:)
|
131
|
+
msg = Base64.urlsafe_decode64 msg
|
132
|
+
iv = msg[0..15]
|
133
|
+
cipher = msg[16..47]
|
134
|
+
alg = 'AES-256-CBC'
|
135
|
+
decode_cipher = OpenSSL::Cipher.new(alg)
|
136
|
+
decode_cipher.decrypt
|
137
|
+
decode_cipher.iv = iv
|
138
|
+
decode_cipher.key = shared_key
|
139
|
+
decode_cipher.update(cipher)
|
140
|
+
end
|
141
|
+
|
142
|
+
# use timestamp(timestamp) for iterator as default: must be bigger than the previous, the first time must be greater than 0. After a new session created, it will be reset to 0.
|
143
|
+
def encrypt_pin(pin, **kwargs)
|
144
|
+
pin = MixinBot.utils.decode_key pin
|
145
|
+
|
146
|
+
shared_key = kwargs[:shared_key]
|
147
|
+
raise ArgumentError, 'shared_key is required' if shared_key.blank?
|
148
|
+
|
149
|
+
iterator ||= kwargs[:iterator] || Time.now.utc.to_i
|
150
|
+
tszero = iterator % 0x100
|
151
|
+
tsone = (iterator % 0x10000) >> 8
|
152
|
+
tstwo = (iterator % 0x1000000) >> 16
|
153
|
+
tsthree = (iterator % 0x100000000) >> 24
|
154
|
+
tsstring = "#{tszero.chr}#{tsone.chr}#{tstwo.chr}#{tsthree.chr}\u0000\u0000\u0000\u0000"
|
155
|
+
encrypt_content = pin + tsstring + tsstring
|
156
|
+
pad_count = 16 - (encrypt_content.length % 16)
|
157
|
+
padded_content =
|
158
|
+
if pad_count.positive?
|
159
|
+
encrypt_content + (pad_count.chr * pad_count)
|
160
|
+
else
|
161
|
+
encrypt_content
|
162
|
+
end
|
163
|
+
|
164
|
+
alg = 'AES-256-CBC'
|
165
|
+
aes = OpenSSL::Cipher.new(alg)
|
166
|
+
iv = OpenSSL::Cipher.new(alg).random_iv
|
167
|
+
aes.encrypt
|
168
|
+
aes.key = shared_key
|
169
|
+
aes.iv = iv
|
170
|
+
cipher = aes.update(padded_content)
|
171
|
+
msg = iv + cipher
|
172
|
+
Base64.urlsafe_encode64 msg, padding: false
|
173
|
+
end
|
174
|
+
|
175
|
+
def tip_public_key(key, counter: 0)
|
176
|
+
raise ArgumentError, 'invalid key' if key.size < 32
|
177
|
+
|
178
|
+
(key[0...32].bytes + MixinBot::Utils.encode_uint_64(counter + 1)).pack('c*').unpack1('H*')
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
module Utils
|
5
|
+
module Decoder
|
6
|
+
def decode_key(key)
|
7
|
+
return if key.blank?
|
8
|
+
|
9
|
+
if key.match?(/\A[\h]{64,}\z/i)
|
10
|
+
[key].pack('H*')
|
11
|
+
elsif key.match?(/\A[a-zA-Z0-9\-\_=]{43,}\z/)
|
12
|
+
Base64.urlsafe_decode64 key
|
13
|
+
elsif key.match?(/^-----BEGIN RSA PRIVATE KEY-----/)
|
14
|
+
key.gsub('\\r\\n', "\n").gsub("\r\n", "\n")
|
15
|
+
elsif key.match?(/\d{6}/) || (key.size % 32).zero?
|
16
|
+
key
|
17
|
+
else
|
18
|
+
raise ArgumentError, "Invalid key #{key}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode_raw_transaction(hex)
|
23
|
+
MixinBot::Transaction.new(hex:).decode.to_h
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode_uint_16(bytes)
|
27
|
+
raise ArgumentError, "only support bytes #{bytes}" unless bytes.is_a?(Array)
|
28
|
+
|
29
|
+
bytes.reverse.pack('C*').unpack1('S*')
|
30
|
+
end
|
31
|
+
|
32
|
+
def decode_uint_32(bytes)
|
33
|
+
raise ArgumentError, "only support bytes #{bytes}" unless bytes.is_a?(Array)
|
34
|
+
|
35
|
+
bytes.reverse.pack('C*').unpack1('L*')
|
36
|
+
end
|
37
|
+
|
38
|
+
def decode_uint_64(bytes)
|
39
|
+
raise ArgumentError, "only support bytes #{bytes}" unless bytes.is_a?(Array)
|
40
|
+
|
41
|
+
bytes.reverse.pack('C*').unpack1('Q*')
|
42
|
+
end
|
43
|
+
|
44
|
+
def decode_int(bytes)
|
45
|
+
int = 0
|
46
|
+
bytes.each do |byte|
|
47
|
+
int = (int * (2**8)) + byte
|
48
|
+
end
|
49
|
+
|
50
|
+
int
|
51
|
+
end
|
52
|
+
|
53
|
+
def hex_to_uuid(hex)
|
54
|
+
MixinBot::UUID.new(hex:).unpacked
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
module Utils
|
5
|
+
module Encoder
|
6
|
+
def encode_raw_transaction(tx)
|
7
|
+
if tx.is_a? String
|
8
|
+
begin
|
9
|
+
tx = JSON.parse tx
|
10
|
+
rescue JSON::ParserError
|
11
|
+
tx
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
raise ArgumentError, "#{tx} is not a valid json" unless tx.is_a? Hash
|
16
|
+
|
17
|
+
tx = tx.with_indifferent_access
|
18
|
+
|
19
|
+
MixinBot::Transaction.new(**tx).encode.hex
|
20
|
+
end
|
21
|
+
|
22
|
+
def encode_uint_16(int)
|
23
|
+
raise ArgumentError, "only support int #{int}" unless int.is_a?(Integer)
|
24
|
+
|
25
|
+
[int].pack('S*').bytes.reverse
|
26
|
+
end
|
27
|
+
|
28
|
+
def encode_uint_32(int)
|
29
|
+
raise ArgumentError, "only support int #{int}" unless int.is_a?(Integer)
|
30
|
+
|
31
|
+
[int].pack('L*').bytes.reverse
|
32
|
+
end
|
33
|
+
|
34
|
+
def encode_uint_64(int)
|
35
|
+
raise ArgumentError, "only support int #{int}" unless int.is_a?(Integer)
|
36
|
+
|
37
|
+
[int].pack('Q*').bytes.reverse
|
38
|
+
end
|
39
|
+
|
40
|
+
def encode_int(int)
|
41
|
+
raise ArgumentError, 'not integer' unless int.is_a?(Integer)
|
42
|
+
|
43
|
+
bytes = []
|
44
|
+
loop do
|
45
|
+
break if int === 0
|
46
|
+
|
47
|
+
bytes.push int & 255
|
48
|
+
int = (int / (2**8)) | 0
|
49
|
+
end
|
50
|
+
|
51
|
+
bytes.reverse
|
52
|
+
end
|
53
|
+
|
54
|
+
def nft_memo(collection, token, extra)
|
55
|
+
MixinBot::Nfo.new(
|
56
|
+
collection:,
|
57
|
+
token:,
|
58
|
+
extra:
|
59
|
+
).mint_memo
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/mixin_bot/utils.rb
CHANGED
@@ -1,116 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
5
|
-
require_relative '
|
3
|
+
require_relative 'utils/address'
|
4
|
+
require_relative 'utils/crypto'
|
5
|
+
require_relative 'utils/decoder'
|
6
|
+
require_relative 'utils/encoder'
|
6
7
|
|
7
8
|
module MixinBot
|
8
9
|
module Utils
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
NULL_BYTES = [0x00, 0x00].freeze
|
14
|
-
AGGREGATED_SIGNATURE_PREFIX = 0xFF01
|
15
|
-
AGGREGATED_SIGNATURE_ORDINAY_MASK = [0x00].freeze
|
16
|
-
AGGREGATED_SIGNATURE_SPARSE_MASK = [0x01].freeze
|
17
|
-
|
18
|
-
def generate_unique_uuid(uuid_1, uuid_2)
|
19
|
-
md5 = Digest::MD5.new
|
20
|
-
md5 << [uuid_1, uuid_2].min
|
21
|
-
md5 << [uuid_1, uuid_2].max
|
22
|
-
digest = md5.digest
|
23
|
-
digest6 = (digest[6].ord & 0x0f | 0x30).chr
|
24
|
-
digest8 = (digest[8].ord & 0x3f | 0x80).chr
|
25
|
-
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
|
26
|
-
|
27
|
-
UUID.new(raw: cipher).unpacked
|
28
|
-
end
|
29
|
-
|
30
|
-
def unique_uuid(*uuids)
|
31
|
-
uuids.sort
|
32
|
-
r = uuids.first
|
33
|
-
uuids.each_with_index do |uuid, i|
|
34
|
-
r = MixinBot::Utils.generate_unique_uuid(r, uuid) if i.positive?
|
35
|
-
end
|
36
|
-
|
37
|
-
r
|
38
|
-
end
|
39
|
-
|
40
|
-
def generate_trace_from_hash(hash, output_index = 0)
|
41
|
-
md5 = Digest::MD5.new
|
42
|
-
md5 << hash
|
43
|
-
md5 << [output_index].pack('c*') if output_index.positive? && output_index < 256
|
44
|
-
digest = md5.digest
|
45
|
-
digest[6] = ((digest[6].ord & 0x0f) | 0x30).chr
|
46
|
-
digest[8] = ((digest[8].ord & 0x3f) | 0x80).chr
|
47
|
-
|
48
|
-
UUID.new(raw: digest).unpacked
|
49
|
-
end
|
50
|
-
|
51
|
-
def hex_to_uuid(hex)
|
52
|
-
UUID.new(hex: hex).unpacked
|
53
|
-
end
|
54
|
-
|
55
|
-
def sign_raw_transaction(tx)
|
56
|
-
tx = JSON.parse tx if tx.is_a? String
|
57
|
-
raise ArgumentError, "#{tx} is not a valid json" unless tx.is_a? Hash
|
58
|
-
|
59
|
-
tx = tx.with_indifferent_access
|
60
|
-
|
61
|
-
Transaction.new(
|
62
|
-
asset: tx[:asset],
|
63
|
-
inputs: tx[:inputs],
|
64
|
-
outputs: tx[:outputs],
|
65
|
-
extra: tx[:extra]
|
66
|
-
).encode.hex
|
67
|
-
end
|
68
|
-
|
69
|
-
def decode_raw_transaction(hex)
|
70
|
-
Transaction.new(hex: hex).decode.to_h
|
71
|
-
end
|
72
|
-
|
73
|
-
def nft_memo(collection, token, extra)
|
74
|
-
MixinBot::Utils::Nfo.new(
|
75
|
-
collection: collection,
|
76
|
-
token: token,
|
77
|
-
extra: extra
|
78
|
-
).mint_memo
|
79
|
-
end
|
80
|
-
|
81
|
-
def encode_int(int)
|
82
|
-
raise ArgumentError, "only support int #{int}" unless int.is_a?(Integer)
|
83
|
-
raise ArgumentError, "int #{int} is larger than MAX_ENCODE_INT #{MAX_ENCODE_INT}" if int > MAX_ENCODE_INT
|
84
|
-
|
85
|
-
[int].pack('S*').bytes.reverse
|
86
|
-
end
|
87
|
-
|
88
|
-
def encode_unit_64(int)
|
89
|
-
[int].pack('Q*').bytes.reverse
|
90
|
-
end
|
91
|
-
|
92
|
-
def bytes_of(int)
|
93
|
-
raise ArgumentError, 'not integer' unless int.is_a?(Integer)
|
94
|
-
|
95
|
-
bytes = []
|
96
|
-
loop do
|
97
|
-
break if int === 0
|
98
|
-
|
99
|
-
bytes.push int & 255
|
100
|
-
int = int / (2**8) | 0
|
101
|
-
end
|
102
|
-
|
103
|
-
bytes.reverse
|
104
|
-
end
|
105
|
-
|
106
|
-
def bytes_to_int(bytes)
|
107
|
-
int = 0
|
108
|
-
bytes.each do |byte|
|
109
|
-
int = int * (2**8) + byte
|
110
|
-
end
|
111
|
-
|
112
|
-
int
|
113
|
-
end
|
114
|
-
end
|
10
|
+
extend MixinBot::Utils::Address
|
11
|
+
extend MixinBot::Utils::Crypto
|
12
|
+
extend MixinBot::Utils::Decoder
|
13
|
+
extend MixinBot::Utils::Encoder
|
115
14
|
end
|
116
15
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class UUID
|
5
|
+
attr_accessor :hex, :raw
|
6
|
+
|
7
|
+
def initialize(**args)
|
8
|
+
@hex = args[:hex]
|
9
|
+
@raw = args[:raw]
|
10
|
+
|
11
|
+
raise MixinBot::InvalidUuidFormatError if raw.present? && raw.size != 16
|
12
|
+
raise MixinBot::InvalidUuidFormatError if hex.present? && hex.gsub('-', '').size != 32
|
13
|
+
end
|
14
|
+
|
15
|
+
def packed
|
16
|
+
if raw.present?
|
17
|
+
raw
|
18
|
+
elsif hex.present?
|
19
|
+
[hex.gsub('-', '')].pack('H*')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def unpacked
|
24
|
+
_hex =
|
25
|
+
if hex.present?
|
26
|
+
hex.gsub('-', '')
|
27
|
+
elsif raw.present?
|
28
|
+
_hex = raw.unpack1('H*')
|
29
|
+
end
|
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
|
+
end
|
41
|
+
end
|
data/lib/mixin_bot/version.rb
CHANGED
data/lib/mixin_bot.rb
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
# third-party dependencies
|
4
4
|
require 'English'
|
5
5
|
require 'active_support/all'
|
6
|
+
require 'base58'
|
6
7
|
require 'base64'
|
7
8
|
require 'bigdecimal'
|
8
9
|
require 'bigdecimal/util'
|
9
10
|
require 'digest'
|
11
|
+
require 'digest/blake3'
|
10
12
|
require 'faye/websocket'
|
11
|
-
require '
|
13
|
+
require 'faraday'
|
14
|
+
require 'faraday/multipart'
|
15
|
+
require 'faraday/retry'
|
12
16
|
require 'jose'
|
13
17
|
require 'msgpack'
|
14
18
|
require 'open3'
|
@@ -16,19 +20,38 @@ require 'openssl'
|
|
16
20
|
require 'rbnacl'
|
17
21
|
require 'sha3'
|
18
22
|
|
19
|
-
require_relative '
|
20
|
-
require_relative '
|
21
|
-
require_relative '
|
22
|
-
require_relative '
|
23
|
-
require_relative '
|
23
|
+
require_relative 'mixin_bot/api'
|
24
|
+
require_relative 'mixin_bot/cli'
|
25
|
+
require_relative 'mixin_bot/utils'
|
26
|
+
require_relative 'mixin_bot/nfo'
|
27
|
+
require_relative 'mixin_bot/uuid'
|
28
|
+
require_relative 'mixin_bot/transaction'
|
29
|
+
require_relative 'mixin_bot/version'
|
30
|
+
require_relative 'mvm'
|
24
31
|
|
25
32
|
module MixinBot
|
26
|
-
class<< self
|
27
|
-
|
28
|
-
|
33
|
+
class << self
|
34
|
+
def api
|
35
|
+
return @api if defined?(@api)
|
36
|
+
|
37
|
+
@api = MixinBot::API.new
|
38
|
+
@api
|
39
|
+
end
|
40
|
+
|
41
|
+
def config
|
42
|
+
return @config if defined?(@config)
|
43
|
+
|
44
|
+
@config = MixinBot::Configuration.new
|
45
|
+
@config
|
46
|
+
end
|
47
|
+
|
48
|
+
def configure(&)
|
49
|
+
config.instance_exec(&)
|
50
|
+
end
|
29
51
|
|
30
|
-
|
31
|
-
|
52
|
+
def utils
|
53
|
+
MixinBot::Utils
|
54
|
+
end
|
32
55
|
end
|
33
56
|
|
34
57
|
class Error < StandardError; end
|
@@ -37,12 +60,14 @@ module MixinBot
|
|
37
60
|
class RequestError < Error; end
|
38
61
|
class ResponseError < Error; end
|
39
62
|
class NotFoundError < Error; end
|
63
|
+
class UserNotFoundError < Error; end
|
40
64
|
class UnauthorizedError < Error; end
|
41
65
|
class ForbiddenError < Error; end
|
42
66
|
class InsufficientBalanceError < Error; end
|
43
67
|
class InsufficientPoolError < Error; end
|
44
68
|
class PinError < Error; end
|
45
|
-
class InvalidNfoFormatError <
|
46
|
-
class InvalidUuidFormatError <
|
47
|
-
class InvalidTransactionFormatError <
|
69
|
+
class InvalidNfoFormatError < Error; end
|
70
|
+
class InvalidUuidFormatError < Error; end
|
71
|
+
class InvalidTransactionFormatError < Error; end
|
72
|
+
class ConfigurationNotValidError < Error; end
|
48
73
|
end
|
data/lib/mvm/bridge.rb
CHANGED
@@ -16,27 +16,10 @@ module MVM
|
|
16
16
|
path = '/users'
|
17
17
|
|
18
18
|
payload = {
|
19
|
-
public_key:
|
19
|
+
public_key:
|
20
20
|
}
|
21
21
|
|
22
|
-
client.post path,
|
23
|
-
end
|
24
|
-
|
25
|
-
def extra(receivers: [], threshold: 1, extra: '')
|
26
|
-
return if receivers.blank?
|
27
|
-
|
28
|
-
path = '/extra'
|
29
|
-
|
30
|
-
payload = {
|
31
|
-
receivers: receivers,
|
32
|
-
threshold: threshold,
|
33
|
-
extra: extra
|
34
|
-
}
|
35
|
-
|
36
|
-
client.post path, json: payload
|
37
|
-
end
|
38
|
-
|
39
|
-
def mirror
|
22
|
+
client.post path, **payload
|
40
23
|
end
|
41
24
|
end
|
42
25
|
end
|
data/lib/mvm/client.rb
CHANGED
@@ -8,43 +8,21 @@ module MVM
|
|
8
8
|
|
9
9
|
def initialize(host)
|
10
10
|
@host = host
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def post(path, options = {})
|
18
|
-
request(:post, path, options)
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def request(verb, path, options = {})
|
24
|
-
uri = uri_for path
|
25
|
-
|
26
|
-
options[:headers] ||= {}
|
27
|
-
options[:headers]['Content-Type'] ||= 'application/json'
|
28
|
-
|
29
|
-
begin
|
30
|
-
response = HTTP.timeout(connect: 5, write: 5, read: 5).request(verb, uri, options)
|
31
|
-
rescue HTTP::Error => e
|
32
|
-
raise HttpError, e.message
|
11
|
+
@conn = Faraday.new(url: "#{SERVER_SCHEME}://#{host}") do |f|
|
12
|
+
f.request :json
|
13
|
+
f.request :retry
|
14
|
+
f.response :raise_error
|
15
|
+
f.response :logger
|
16
|
+
f.response :json
|
33
17
|
end
|
18
|
+
end
|
34
19
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
JSON.parse(response.body.to_s)
|
20
|
+
def get(path, **options)
|
21
|
+
@conn.get(path, **options).body
|
39
22
|
end
|
40
23
|
|
41
|
-
def
|
42
|
-
|
43
|
-
scheme: SERVER_SCHEME,
|
44
|
-
host: host,
|
45
|
-
path: path
|
46
|
-
}
|
47
|
-
Addressable::URI.new(uri_options)
|
24
|
+
def post(path, **options)
|
25
|
+
@conn.post(path, **options).body
|
48
26
|
end
|
49
27
|
end
|
50
28
|
end
|
data/lib/mvm/nft.rb
CHANGED
@@ -6,14 +6,14 @@ module MVM
|
|
6
6
|
|
7
7
|
def initialize(rpc_url: MVM::RPC_URL, mirror_address: MVM::MIRROR_ADDRESS)
|
8
8
|
@rpc = Eth::Client.create rpc_url
|
9
|
-
@mirror = Eth::Contract.from_abi name: 'Mirror', address: mirror_address, abi: File.
|
9
|
+
@mirror = Eth::Contract.from_abi name: 'Mirror', address: mirror_address, abi: File.read(File.expand_path('./abis/mirror.json', __dir__))
|
10
10
|
end
|
11
11
|
|
12
12
|
def collection_from_contract(address)
|
13
13
|
collection = @rpc.call @mirror, 'collections', address
|
14
14
|
return if collection.zero?
|
15
15
|
|
16
|
-
MixinBot::
|
16
|
+
MixinBot::UUID.new(hex: collection.to_fs(16)).unpacked
|
17
17
|
end
|
18
18
|
|
19
19
|
def contract_from_collection(uuid)
|
@@ -29,7 +29,7 @@ module MVM
|
|
29
29
|
address = contract_from_collection collection_id
|
30
30
|
return if address.blank? || address.to_i(16).zero?
|
31
31
|
|
32
|
-
contract = Eth::Contract.from_abi name: 'Collectible', address
|
32
|
+
contract = Eth::Contract.from_abi name: 'Collectible', address:, abi: File.read(File.expand_path('./abis/erc721.json', __dir__))
|
33
33
|
owner = @rpc.call contract, 'ownerOf', token_id.to_i
|
34
34
|
address = Eth::Address.new owner
|
35
35
|
return unless address.valid?
|
@@ -40,7 +40,7 @@ module MVM
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def token_of_owner_by_index(contract, owner, index)
|
43
|
-
contract = Eth::Contract.from_abi name: 'Collectible', address: contract, abi: File.
|
43
|
+
contract = Eth::Contract.from_abi name: 'Collectible', address: contract, abi: File.read(File.expand_path('./abis/erc721.json', __dir__))
|
44
44
|
|
45
45
|
@rpc.call contract, 'tokenOfOwnerByIndex', owner, index
|
46
46
|
rescue IOError
|