mixin_bot 0.12.1 → 1.1.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 +34 -56
- data/lib/mixin_bot/api/blaze.rb +4 -3
- data/lib/mixin_bot/api/conversation.rb +29 -49
- data/lib/mixin_bot/api/encrypted_message.rb +19 -19
- data/lib/mixin_bot/api/inscription.rb +71 -0
- data/lib/mixin_bot/api/legacy_collectible.rb +140 -0
- 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 +17 -222
- data/lib/mixin_bot/api/output.rb +48 -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 +12 -14
- 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 +295 -60
- data/lib/mixin_bot/api/transfer.rb +69 -31
- data/lib/mixin_bot/api/user.rb +88 -53
- data/lib/mixin_bot/api/withdraw.rb +52 -53
- data/lib/mixin_bot/api.rb +81 -46
- 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 +74 -127
- data/lib/mixin_bot/configuration.rb +98 -0
- data/lib/mixin_bot/nfo.rb +174 -0
- data/lib/mixin_bot/transaction.rb +524 -0
- data/lib/mixin_bot/utils/address.rb +121 -0
- data/lib/mixin_bot/utils/crypto.rb +218 -0
- data/lib/mixin_bot/utils/decoder.rb +56 -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 +77 -103
- data/lib/mixin_bot/api/collectible.rb +0 -138
- 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,218 @@
|
|
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 shared_public_key(key)
|
57
|
+
(JOSE::JWA::Edwards25519Point.stdbase * scalar_from_bytes(key[...64]).x.to_i).encode
|
58
|
+
end
|
59
|
+
|
60
|
+
def scalar_from_bytes(raw)
|
61
|
+
JOSE::JWA::FieldElement.new(
|
62
|
+
# https://github.com/potatosalad/ruby-jose/blob/e1be589b889f1e59ac233a5d19a3fa13f1e4b8a0/lib/jose/jwa/x25519.rb#L122C14-L122C48
|
63
|
+
OpenSSL::BN.new(raw.reverse, 2),
|
64
|
+
JOSE::JWA::Edwards25519Point::L
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def sign(msg, key:)
|
69
|
+
msg = Digest::Blake3.digest msg
|
70
|
+
|
71
|
+
pub = shared_public_key key
|
72
|
+
|
73
|
+
y_scalar = scalar_from_bytes key
|
74
|
+
|
75
|
+
key_digest = Digest::SHA512.digest key
|
76
|
+
msg_digest = Digest::SHA512.digest(key_digest[-32...] + msg)
|
77
|
+
|
78
|
+
z_scalar = scalar_from_bytes msg_digest[...64]
|
79
|
+
|
80
|
+
r_point = JOSE::JWA::Edwards25519Point.stdbase * z_scalar.x.to_i
|
81
|
+
|
82
|
+
hram_digest = Digest::SHA512.digest(r_point.encode + pub + msg)
|
83
|
+
|
84
|
+
x_scalar = scalar_from_bytes hram_digest[...64]
|
85
|
+
|
86
|
+
s_scalar = (x_scalar * y_scalar) + z_scalar
|
87
|
+
|
88
|
+
r_point.encode + s_scalar.to_bytes(36)
|
89
|
+
end
|
90
|
+
|
91
|
+
def generate_unique_uuid(uuid1, uuid2)
|
92
|
+
md5 = Digest::MD5.new
|
93
|
+
md5 << [uuid1, uuid2].min
|
94
|
+
md5 << [uuid1, uuid2].max
|
95
|
+
digest = md5.digest
|
96
|
+
digest6 = ((digest[6].ord & 0x0f) | 0x30).chr
|
97
|
+
digest8 = ((digest[8].ord & 0x3f) | 0x80).chr
|
98
|
+
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
|
99
|
+
|
100
|
+
MixinBot::UUID.new(raw: cipher).unpacked
|
101
|
+
end
|
102
|
+
|
103
|
+
def unique_uuid(*uuids)
|
104
|
+
uuids = uuids.flatten.compact
|
105
|
+
uuids.sort
|
106
|
+
r = uuids.first
|
107
|
+
uuids.each_with_index do |uuid, i|
|
108
|
+
r = generate_unique_uuid(r, uuid) if i.positive?
|
109
|
+
end
|
110
|
+
|
111
|
+
r
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_trace_from_hash(hash, output_index = 0)
|
115
|
+
md5 = Digest::MD5.new
|
116
|
+
md5 << hash
|
117
|
+
md5 << [output_index].pack('c*') if output_index.positive? && output_index < 256
|
118
|
+
digest = md5.digest
|
119
|
+
digest[6] = ((digest[6].ord & 0x0f) | 0x30).chr
|
120
|
+
digest[8] = ((digest[8].ord & 0x3f) | 0x80).chr
|
121
|
+
|
122
|
+
MixinBot::UUID.new(raw: digest).unpacked
|
123
|
+
end
|
124
|
+
|
125
|
+
# decrypt the encrpted pin, just for test
|
126
|
+
def decrypt_pin(msg, shared_key:)
|
127
|
+
msg = Base64.urlsafe_decode64 msg
|
128
|
+
iv = msg[0..15]
|
129
|
+
cipher = msg[16..47]
|
130
|
+
alg = 'AES-256-CBC'
|
131
|
+
decode_cipher = OpenSSL::Cipher.new(alg)
|
132
|
+
decode_cipher.decrypt
|
133
|
+
decode_cipher.iv = iv
|
134
|
+
decode_cipher.key = shared_key
|
135
|
+
decode_cipher.update(cipher)
|
136
|
+
end
|
137
|
+
|
138
|
+
# 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.
|
139
|
+
def encrypt_pin(pin, **kwargs)
|
140
|
+
pin = MixinBot.utils.decode_key pin
|
141
|
+
|
142
|
+
shared_key = kwargs[:shared_key]
|
143
|
+
raise ArgumentError, 'shared_key is required' if shared_key.blank?
|
144
|
+
|
145
|
+
iterator ||= kwargs[:iterator] || Time.now.utc.to_i
|
146
|
+
tszero = iterator % 0x100
|
147
|
+
tsone = (iterator % 0x10000) >> 8
|
148
|
+
tstwo = (iterator % 0x1000000) >> 16
|
149
|
+
tsthree = (iterator % 0x100000000) >> 24
|
150
|
+
tsstring = "#{tszero.chr}#{tsone.chr}#{tstwo.chr}#{tsthree.chr}\u0000\u0000\u0000\u0000"
|
151
|
+
encrypt_content = pin + tsstring + tsstring
|
152
|
+
pad_count = 16 - (encrypt_content.length % 16)
|
153
|
+
padded_content =
|
154
|
+
if pad_count.positive?
|
155
|
+
encrypt_content + (pad_count.chr * pad_count)
|
156
|
+
else
|
157
|
+
encrypt_content
|
158
|
+
end
|
159
|
+
|
160
|
+
alg = 'AES-256-CBC'
|
161
|
+
aes = OpenSSL::Cipher.new(alg)
|
162
|
+
iv = OpenSSL::Cipher.new(alg).random_iv
|
163
|
+
aes.encrypt
|
164
|
+
aes.key = shared_key
|
165
|
+
aes.iv = iv
|
166
|
+
cipher = aes.update(padded_content)
|
167
|
+
msg = iv + cipher
|
168
|
+
Base64.urlsafe_encode64 msg, padding: false
|
169
|
+
end
|
170
|
+
|
171
|
+
def tip_public_key(key, counter: 0)
|
172
|
+
raise ArgumentError, 'invalid key' if key.size < 32
|
173
|
+
|
174
|
+
(key[0...32].bytes + MixinBot::Utils.encode_uint64(counter + 1)).pack('c*').unpack1('H*')
|
175
|
+
end
|
176
|
+
|
177
|
+
def hash_scalar(pkey, output_index)
|
178
|
+
tmp = [output_index].pack('Q<').reverse
|
179
|
+
|
180
|
+
hash1 = Digest::Blake3.digest(pkey + tmp)
|
181
|
+
hash2 = Digest::Blake3.digest hash1
|
182
|
+
|
183
|
+
hash3 = Digest::Blake3.digest(hash1 + hash2)
|
184
|
+
hash4 = Digest::Blake3.digest hash3
|
185
|
+
|
186
|
+
hash3 + hash4
|
187
|
+
end
|
188
|
+
|
189
|
+
def multiply_keys(public_key:, private_key:)
|
190
|
+
public_point = JOSE::JWA::Edwards25519Point.stdbase.decode public_key
|
191
|
+
private_scalar = scalar_from_bytes private_key
|
192
|
+
(public_point * private_scalar.x.to_i).encode
|
193
|
+
end
|
194
|
+
|
195
|
+
def derive_ghost_public_key(private_key, view_key, spend_key, index)
|
196
|
+
mult_value = multiply_keys(public_key: view_key, private_key:)
|
197
|
+
|
198
|
+
x = hash_scalar mult_value, index
|
199
|
+
|
200
|
+
p1 = JOSE::JWA::Edwards25519Point.stdbase.decode spend_key
|
201
|
+
p2 = JOSE::JWA::Edwards25519Point.stdbase * scalar_from_bytes(x).x.to_i
|
202
|
+
|
203
|
+
(p1 + p2).encode
|
204
|
+
end
|
205
|
+
|
206
|
+
def derive_ghost_private_key(public_key, view_key, spend_key, index)
|
207
|
+
mult_value = multiply_keys(public_key:, private_key: view_key)
|
208
|
+
|
209
|
+
x = hash_scalar mult_value, index
|
210
|
+
|
211
|
+
x_scalar = scalar_from_bytes x
|
212
|
+
y_scalar = scalar_from_bytes spend_key
|
213
|
+
|
214
|
+
(x_scalar + y_scalar).to_bytes(36)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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?(/^-----BEGIN RSA PRIVATE KEY-----/)
|
12
|
+
key.gsub('\\r\\n', "\n").gsub("\r\n", "\n")
|
13
|
+
elsif key.match?(/\d{6}/) || (key.size % 32).zero?
|
14
|
+
key
|
15
|
+
else
|
16
|
+
Base64.urlsafe_decode64 key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode_raw_transaction(hex)
|
21
|
+
MixinBot::Transaction.new(hex:).decode.to_h
|
22
|
+
end
|
23
|
+
|
24
|
+
def decode_uint16(bytes)
|
25
|
+
raise ArgumentError, "only support bytes #{bytes}" unless bytes.is_a?(Array)
|
26
|
+
|
27
|
+
bytes.reverse.pack('C*').unpack1('S*')
|
28
|
+
end
|
29
|
+
|
30
|
+
def decode_uint32(bytes)
|
31
|
+
raise ArgumentError, "only support bytes #{bytes}" unless bytes.is_a?(Array)
|
32
|
+
|
33
|
+
bytes.reverse.pack('C*').unpack1('L*')
|
34
|
+
end
|
35
|
+
|
36
|
+
def decode_uint64(bytes)
|
37
|
+
raise ArgumentError, "only support bytes #{bytes}" unless bytes.is_a?(Array)
|
38
|
+
|
39
|
+
bytes.reverse.pack('C*').unpack1('Q*')
|
40
|
+
end
|
41
|
+
|
42
|
+
def decode_int(bytes)
|
43
|
+
int = 0
|
44
|
+
bytes.each do |byte|
|
45
|
+
int = (int * (2**8)) + byte
|
46
|
+
end
|
47
|
+
|
48
|
+
int
|
49
|
+
end
|
50
|
+
|
51
|
+
def hex_to_uuid(hex)
|
52
|
+
MixinBot::UUID.new(hex:).unpacked
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
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(txn)
|
7
|
+
if txn.is_a? String
|
8
|
+
begin
|
9
|
+
txn = JSON.parse txn
|
10
|
+
rescue JSON::ParserError
|
11
|
+
txn
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
raise ArgumentError, "#{txn} is not a valid json" unless txn.is_a? Hash
|
16
|
+
|
17
|
+
txn = txn.with_indifferent_access
|
18
|
+
|
19
|
+
MixinBot::Transaction.new(**txn).encode.hex
|
20
|
+
end
|
21
|
+
|
22
|
+
def encode_uint16(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_uint32(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_uint64(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.zero?
|
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, **)
|
21
|
+
@conn.get(path, **).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, **)
|
25
|
+
@conn.post(path, **).body
|
48
26
|
end
|
49
27
|
end
|
50
28
|
end
|