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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mixin_bot/api/address.rb +21 -0
  3. data/lib/mixin_bot/api/app.rb +5 -11
  4. data/lib/mixin_bot/api/asset.rb +9 -16
  5. data/lib/mixin_bot/api/attachment.rb +27 -22
  6. data/lib/mixin_bot/api/auth.rb +29 -51
  7. data/lib/mixin_bot/api/blaze.rb +4 -3
  8. data/lib/mixin_bot/api/collectible.rb +60 -58
  9. data/lib/mixin_bot/api/conversation.rb +29 -49
  10. data/lib/mixin_bot/api/encrypted_message.rb +17 -17
  11. data/lib/mixin_bot/api/legacy_multisig.rb +87 -0
  12. data/lib/mixin_bot/api/legacy_output.rb +50 -0
  13. data/lib/mixin_bot/api/legacy_payment.rb +31 -0
  14. data/lib/mixin_bot/api/legacy_snapshot.rb +39 -0
  15. data/lib/mixin_bot/api/legacy_transaction.rb +173 -0
  16. data/lib/mixin_bot/api/legacy_transfer.rb +42 -0
  17. data/lib/mixin_bot/api/me.rb +13 -17
  18. data/lib/mixin_bot/api/message.rb +13 -10
  19. data/lib/mixin_bot/api/multisig.rb +16 -221
  20. data/lib/mixin_bot/api/output.rb +46 -0
  21. data/lib/mixin_bot/api/payment.rb +9 -20
  22. data/lib/mixin_bot/api/pin.rb +57 -65
  23. data/lib/mixin_bot/api/rpc.rb +9 -11
  24. data/lib/mixin_bot/api/snapshot.rb +15 -29
  25. data/lib/mixin_bot/api/tip.rb +43 -0
  26. data/lib/mixin_bot/api/transaction.rb +184 -60
  27. data/lib/mixin_bot/api/transfer.rb +64 -32
  28. data/lib/mixin_bot/api/user.rb +83 -53
  29. data/lib/mixin_bot/api/withdraw.rb +52 -53
  30. data/lib/mixin_bot/api.rb +78 -45
  31. data/lib/mixin_bot/cli/api.rb +149 -5
  32. data/lib/mixin_bot/cli/utils.rb +14 -4
  33. data/lib/mixin_bot/cli.rb +13 -10
  34. data/lib/mixin_bot/client.rb +76 -127
  35. data/lib/mixin_bot/configuration.rb +98 -0
  36. data/lib/mixin_bot/nfo.rb +174 -0
  37. data/lib/mixin_bot/transaction.rb +505 -0
  38. data/lib/mixin_bot/utils/address.rb +108 -0
  39. data/lib/mixin_bot/utils/crypto.rb +182 -0
  40. data/lib/mixin_bot/utils/decoder.rb +58 -0
  41. data/lib/mixin_bot/utils/encoder.rb +63 -0
  42. data/lib/mixin_bot/utils.rb +8 -109
  43. data/lib/mixin_bot/uuid.rb +41 -0
  44. data/lib/mixin_bot/version.rb +1 -1
  45. data/lib/mixin_bot.rb +39 -14
  46. data/lib/mvm/bridge.rb +2 -19
  47. data/lib/mvm/client.rb +11 -33
  48. data/lib/mvm/nft.rb +4 -4
  49. data/lib/mvm/registry.rb +9 -9
  50. data/lib/mvm/scan.rb +3 -5
  51. data/lib/mvm.rb +5 -6
  52. metadata +101 -44
  53. data/lib/mixin_bot/utils/nfo.rb +0 -176
  54. data/lib/mixin_bot/utils/transaction.rb +0 -478
  55. 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