mixin_bot 0.12.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class Configuration
|
5
|
+
CONFIGURABLE_ATTRS = %i[
|
6
|
+
app_id
|
7
|
+
client_secret
|
8
|
+
session_id
|
9
|
+
session_private_key
|
10
|
+
server_public_key
|
11
|
+
spend_key
|
12
|
+
pin
|
13
|
+
api_host
|
14
|
+
blaze_host
|
15
|
+
session_private_key_curve25519
|
16
|
+
server_public_key_curve25519
|
17
|
+
debug
|
18
|
+
].freeze
|
19
|
+
attr_accessor(*CONFIGURABLE_ATTRS)
|
20
|
+
|
21
|
+
def initialize(**kwargs)
|
22
|
+
@app_id = kwargs[:app_id] || kwargs[:client_id]
|
23
|
+
@client_secret = kwargs[:client_secret]
|
24
|
+
@session_id = kwargs[:session_id]
|
25
|
+
@api_host = kwargs[:api_host] || 'api.mixin.one'
|
26
|
+
@blaze_host = kwargs[:blaze_host] || 'blaze.mixin.one'
|
27
|
+
@debug = kwargs[:debug] || false
|
28
|
+
|
29
|
+
self.session_private_key = kwargs[:session_private_key] || kwargs[:private_key]
|
30
|
+
self.server_public_key = kwargs[:server_public_key] || kwargs[:pin_token]
|
31
|
+
self.spend_key = kwargs[:spend_key]
|
32
|
+
self.pin = kwargs[:pin] || spend_key
|
33
|
+
end
|
34
|
+
|
35
|
+
def valid?
|
36
|
+
%i[app_id session_id session_private_key server_public_key].all? do |attr|
|
37
|
+
send(attr).present?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def session_private_key=(key)
|
42
|
+
return if key.blank?
|
43
|
+
|
44
|
+
_private_key = decode_key key
|
45
|
+
@session_private_key =
|
46
|
+
if _private_key.size == 32
|
47
|
+
JOSE::JWA::Ed25519.keypair(_private_key).last
|
48
|
+
else
|
49
|
+
_private_key
|
50
|
+
end
|
51
|
+
|
52
|
+
@session_private_key_curve25519 = JOSE::JWA::Ed25519.sk_to_curve25519(@session_private_key) if @session_private_key.size == 64
|
53
|
+
end
|
54
|
+
|
55
|
+
def server_public_key=(key)
|
56
|
+
return if key.blank?
|
57
|
+
|
58
|
+
@server_public_key = decode_key key
|
59
|
+
# HEX encoded
|
60
|
+
@server_public_key_curve25519 =
|
61
|
+
if key.match?(/\A[\h]{32,}\z/i)
|
62
|
+
JOSE::JWA::Ed25519.pk_to_curve25519 @server_public_key
|
63
|
+
else
|
64
|
+
server_public_key
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def spend_key=(key)
|
69
|
+
return if key.blank?
|
70
|
+
|
71
|
+
_private_key = decode_key key
|
72
|
+
@spend_key =
|
73
|
+
if _private_key.size == 32
|
74
|
+
JOSE::JWA::Ed25519.keypair(_private_key).last
|
75
|
+
else
|
76
|
+
_private_key
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def pin=(key)
|
81
|
+
return if key.blank?
|
82
|
+
|
83
|
+
_private_key = decode_key key
|
84
|
+
@pin =
|
85
|
+
if _private_key.size == 32
|
86
|
+
JOSE::JWA::Ed25519.keypair(_private_key).last
|
87
|
+
else
|
88
|
+
_private_key
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def decode_key(key)
|
95
|
+
MixinBot.utils.decode_key key
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class Nfo
|
5
|
+
NFT_MEMO_PREFIX = 'NFO'
|
6
|
+
NFT_MEMO_VERSION = 0x00
|
7
|
+
NFT_MEMO_DEFAULT_CHAIN = '43d61dcd-e413-450d-80b8-101d5e903357'
|
8
|
+
NFT_MEMO_DEFAULT_CLASS = '3c8c161a18ae2c8b14fda1216fff7da88c419b5d'
|
9
|
+
NULL_UUID = '00000000-0000-0000-0000-000000000000'
|
10
|
+
|
11
|
+
attr_reader :prefix, :version, :raw
|
12
|
+
attr_accessor :mask, :chain, :nm_class, :collection, :token, :extra, :memo, :hex
|
13
|
+
|
14
|
+
def initialize(**kwargs)
|
15
|
+
@prefix = NFT_MEMO_PREFIX
|
16
|
+
@version = NFT_MEMO_VERSION
|
17
|
+
@mask = kwargs[:mask] || 0
|
18
|
+
@chain = kwargs[:chain] || NFT_MEMO_DEFAULT_CHAIN
|
19
|
+
@nm_class = kwargs[:nm_class] || NFT_MEMO_DEFAULT_CLASS
|
20
|
+
@collection = kwargs[:collection] || NULL_UUID
|
21
|
+
@token = kwargs[:token].presence&.to_i
|
22
|
+
@extra = kwargs[:extra]
|
23
|
+
@memo = kwargs[:memo]
|
24
|
+
@hex = kwargs[:hex]
|
25
|
+
end
|
26
|
+
|
27
|
+
def mint_memo
|
28
|
+
raise MixinBot::InvalidNfoFormatError, 'token is required' if token.blank?
|
29
|
+
raise MixinBot::InvalidNfoFormatError, 'extra must be 256-bit string' if extra.blank? || extra.size != 64
|
30
|
+
|
31
|
+
@collection = NULL_UUID if collection.blank?
|
32
|
+
@chain = NFT_MEMO_DEFAULT_CHAIN
|
33
|
+
@nm_class = NFT_MEMO_DEFAULT_CLASS
|
34
|
+
mark 0
|
35
|
+
encode
|
36
|
+
|
37
|
+
memo
|
38
|
+
end
|
39
|
+
|
40
|
+
def unique_token_id
|
41
|
+
bytes = []
|
42
|
+
bytes += MixinBot::UUID.new(hex: chain).packed.bytes
|
43
|
+
bytes += [nm_class].pack('H*').bytes
|
44
|
+
bytes += MixinBot::UUID.new(hex: collection).packed.bytes
|
45
|
+
bytes += MixinBot.utils.encode_int token
|
46
|
+
|
47
|
+
md5 = Digest::MD5.new
|
48
|
+
md5.update bytes.pack('c*')
|
49
|
+
digest = [md5.hexdigest].pack('H*').bytes
|
50
|
+
|
51
|
+
digest[6] = (digest[6] & 0x0f) | 0x30
|
52
|
+
digest[8] = (digest[8] & 0x3f) | 0x80
|
53
|
+
|
54
|
+
hex = digest.pack('c*').unpack1('H*')
|
55
|
+
|
56
|
+
MixinBot::UUID.new(hex:).unpacked
|
57
|
+
end
|
58
|
+
|
59
|
+
def mark(*indexes)
|
60
|
+
indexes.map do |index|
|
61
|
+
raise ArgumentError, "invalid NFO memo index #{index}" if index >= 64 || index.negative?
|
62
|
+
|
63
|
+
@mask = mask ^ (1 << index)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def encode
|
68
|
+
bytes = []
|
69
|
+
|
70
|
+
bytes += prefix.bytes
|
71
|
+
bytes += [version]
|
72
|
+
|
73
|
+
if mask.zero?
|
74
|
+
bytes += [0]
|
75
|
+
else
|
76
|
+
bytes += [1]
|
77
|
+
bytes += MixinBot.utils.encode_uint_64 mask
|
78
|
+
bytes += MixinBot::UUID.new(hex: chain).packed.bytes
|
79
|
+
|
80
|
+
class_bytes = [nm_class].pack('H*').bytes
|
81
|
+
bytes += MixinBot.utils.encode_int class_bytes.size
|
82
|
+
bytes += class_bytes
|
83
|
+
|
84
|
+
collection_bytes = collection.split('-').pack('H* H* H* H* H*').bytes
|
85
|
+
bytes += MixinBot.utils.encode_int collection_bytes.size
|
86
|
+
bytes += collection_bytes
|
87
|
+
|
88
|
+
# token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
|
89
|
+
token_bytes = MixinBot.utils.encode_int token
|
90
|
+
bytes += MixinBot.utils.encode_int token_bytes.size
|
91
|
+
bytes += token_bytes
|
92
|
+
end
|
93
|
+
|
94
|
+
extra_bytes = [extra].pack('H*').bytes
|
95
|
+
bytes += MixinBot.utils.encode_int extra_bytes.size
|
96
|
+
bytes += extra_bytes
|
97
|
+
|
98
|
+
@raw = bytes.pack('C*')
|
99
|
+
@hex = raw.unpack1('H*')
|
100
|
+
@memo = Base64.urlsafe_encode64 raw, padding: false
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def decode
|
106
|
+
@raw =
|
107
|
+
if memo.present?
|
108
|
+
Base64.urlsafe_decode64 memo
|
109
|
+
elsif hex.present?
|
110
|
+
[hex].pack('H*')
|
111
|
+
else
|
112
|
+
raise InvalidNfoFormatError, 'memo or hex is required'
|
113
|
+
end
|
114
|
+
|
115
|
+
@hex = raw.unpack1('H*') if hex.blank?
|
116
|
+
@memo = Base64.urlsafe_encode64 raw, padding: false if memo.blank?
|
117
|
+
|
118
|
+
decode_bytes
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def decode_bytes
|
123
|
+
bytes = raw.bytes
|
124
|
+
|
125
|
+
_prefix = bytes.shift(3).pack('C*')
|
126
|
+
raise MixinBot::InvalidNfoFormatError, "NFO prefix #{_prefix}" if _prefix != prefix
|
127
|
+
|
128
|
+
_version = bytes.shift
|
129
|
+
raise MixinBot::InvalidNfoFormatError, "NFO version #{prefix}" if _version != version
|
130
|
+
|
131
|
+
hint = bytes.shift
|
132
|
+
if hint == 1
|
133
|
+
@mask = bytes.shift(8).reverse.pack('C*').unpack1('Q*')
|
134
|
+
|
135
|
+
@chain = MixinBot::UUID.new(hex: bytes.shift(16).pack('C*').unpack1('H*')).unpacked
|
136
|
+
|
137
|
+
class_length = bytes.shift
|
138
|
+
@nm_class = bytes.shift(class_length).pack('C*').unpack1('H*')
|
139
|
+
|
140
|
+
collection_length = bytes.shift
|
141
|
+
@collection = MixinBot::UUID.new(hex: bytes.shift(collection_length).pack('C*').unpack1('H*')).unpacked
|
142
|
+
|
143
|
+
token_length = bytes.shift
|
144
|
+
@token = MixinBot.utils.decode_int bytes.shift(token_length)
|
145
|
+
end
|
146
|
+
|
147
|
+
extra_length = bytes.shift
|
148
|
+
@extra = bytes.shift(extra_length).pack('C*').unpack1('H*')
|
149
|
+
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_h
|
154
|
+
hash = {
|
155
|
+
prefix:,
|
156
|
+
version:,
|
157
|
+
mask:,
|
158
|
+
chain:,
|
159
|
+
class: nm_class,
|
160
|
+
collection:,
|
161
|
+
token:,
|
162
|
+
extra:,
|
163
|
+
memo:,
|
164
|
+
hex:
|
165
|
+
}
|
166
|
+
|
167
|
+
hash.each do |key, value|
|
168
|
+
hash.delete key if value.blank?
|
169
|
+
end
|
170
|
+
|
171
|
+
hash
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|