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
@@ -3,241 +3,36 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Multisig
6
- def outputs(**kwargs)
7
- limit = kwargs[:limit] || 100
8
- offset = kwargs[:offset] || ''
9
- state = kwargs[:state] || ''
10
- members = kwargs[:members] || []
11
- threshold = kwargs[:threshold] || ''
12
- access_token = kwargs[:access_token]
13
- members = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
14
-
15
- path = format(
16
- '/multisigs/outputs?limit=%<limit>s&offset=%<offset>s&state=%<state>s&members=%<members>s&threshold=%<threshold>s',
17
- limit: limit,
18
- offset: offset,
19
- state: state,
20
- members: members,
21
- threshold: threshold
22
- )
23
- access_token ||= access_token('GET', path, '')
24
- authorization = format('Bearer %<access_token>s', access_token: access_token)
25
- client.get(path, headers: { 'Authorization': authorization })
26
- end
27
- alias multisigs outputs
28
- alias multisig_outputs outputs
29
-
30
- def create_output(receivers:, index:, hint: nil, access_token: nil)
31
- path = '/outputs'
32
- payload = {
33
- receivers: receivers,
34
- index: index,
35
- hint: hint
36
- }
37
- access_token ||= access_token('POST', path, payload.to_json)
38
- authorization = format('Bearer %<access_token>s', access_token: access_token)
39
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
40
- end
41
-
42
- MULTISIG_REQUEST_ACTIONS = %i[sign unlock].freeze
43
- def create_multisig_request(action, raw, access_token: nil)
44
- raise ArgumentError, "request action is limited in #{MULTISIG_REQUEST_ACTIONS.join(', ')}" unless MULTISIG_REQUEST_ACTIONS.include? action.to_sym
45
-
46
- path = '/multisigs/requests'
47
- payload = {
48
- action: action,
6
+ def create_safe_multisig_request(request_id, raw, access_token: nil)
7
+ path = '/safe/multisigs'
8
+ payload = [{
9
+ request_id: request_id,
49
10
  raw: raw
50
- }
51
- access_token ||= access_token('POST', path, payload.to_json)
52
- authorization = format('Bearer %<access_token>s', access_token: access_token)
53
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
54
- end
11
+ }]
55
12
 
56
- # transfer from the multisig address
57
- def create_sign_multisig_request(raw, access_token: nil)
58
- create_multisig_request 'sign', raw, access_token: access_token
59
- end
60
-
61
- # transfer from the multisig address
62
- # create a request for unlock a multi-sign
63
- def create_unlock_multisig_request(raw, access_token: nil)
64
- create_multisig_request 'unlock', raw, access_token: access_token
65
- end
66
-
67
- def sign_multisig_request(request_id, pin)
68
- path = format('/multisigs/requests/%<request_id>s/sign', request_id: request_id)
69
- payload = {
70
- pin: encrypt_pin(pin)
71
- }
72
- access_token ||= access_token('POST', path, payload.to_json)
73
- authorization = format('Bearer %<access_token>s', access_token: access_token)
74
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
75
- end
76
-
77
- def unlock_multisig_request(request_id, pin)
78
- path = format('/multisigs/requests/%<request_id>s/unlock', request_id: request_id)
79
- payload = {
80
- pin: encrypt_pin(pin)
81
- }
82
- access_token ||= access_token('POST', path, payload.to_json)
83
- authorization = format('Bearer %<access_token>s', access_token: access_token)
84
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
13
+ client.post path, *payload
85
14
  end
86
15
 
87
- def cancel_multisig_request(request_id, pin)
88
- path = format('/multisigs/requests/%<request_id>s/cancel', request_id: request_id)
89
- payload = {
90
- pin: encrypt_pin(pin)
91
- }
92
- access_token ||= access_token('POST', path, payload.to_json)
93
- authorization = format('Bearer %<access_token>s', access_token: access_token)
94
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
95
- end
16
+ def sign_safe_multisig_request(request_id, raw, access_token: nil)
17
+ path = format('/safe/multisigs/%<request_id>s/sign', request_id:)
96
18
 
97
- # pay to the multisig address
98
- # used for create multisig payment code_id
99
- def create_payment(**kwargs)
100
- path = '/payments'
101
19
  payload = {
102
- asset_id: kwargs[:asset_id],
103
- amount: format('%.8f', kwargs[:amount].to_d.to_r),
104
- trace_id: kwargs[:trace_id] || SecureRandom.uuid,
105
- memo: kwargs[:memo],
106
- opponent_multisig: {
107
- receivers: kwargs[:receivers],
108
- threshold: kwargs[:threshold]
109
- }
20
+ raw:
110
21
  }
111
- access_token = kwargs[:access_token]
112
- access_token ||= access_token('POST', path, payload.to_json)
113
- authorization = format('Bearer %<access_token>s', access_token: access_token)
114
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
115
- end
116
- alias create_multisig_payment create_payment
117
22
 
118
- def verify_multisig(code_id, access_token: nil)
119
- path = format('/codes/%<code_id>s', code_id: code_id)
120
- access_token ||= access_token('GET', path, '')
121
- authorization = format('Bearer %<access_token>s', access_token: access_token)
122
- client.get(path, headers: { 'Authorization': authorization })
23
+ client.post path, **payload
123
24
  end
124
25
 
125
- def build_threshold_script(threshold)
126
- s = threshold.to_s(16)
127
- s = s.length == 1 ? "0#{s}" : s
128
- raise 'NVALID THRESHOLD' if s.length > 2
26
+ def unlock_safe_multisig_request(request_id, access_token: nil)
27
+ path = format('/safe/multisigs/%<request_id>s/unlock', request_id:)
129
28
 
130
- "fffe#{s}"
29
+ client.post path, access_token: access_token
131
30
  end
132
31
 
133
- # kwargs:
134
- # {
135
- # senders: [ uuid ],
136
- # senders_threshold: integer,
137
- # receivers: [ uuid ],
138
- # receivers_threshold: integer,
139
- # asset_id: uuid,
140
- # amount: string / float,
141
- # memo: string,
142
- # }
143
- RAW_TRANSACTION_ARGUMENTS = %i[utxos senders senders_threshold receivers receivers_threshold amount].freeze
144
- def build_raw_transaction(**kwargs)
145
- raise ArgumentError, "#{RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build raw transaction" unless RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
146
-
147
- senders = kwargs[:senders]
148
- senders_threshold = kwargs[:senders_threshold]
149
- receivers = kwargs[:receivers]
150
- receivers_threshold = kwargs[:receivers_threshold]
151
- amount = kwargs[:amount]
152
- asset_id = kwargs[:asset_id]
153
- asset_mixin_id = kwargs[:asset_mixin_id]
154
- utxos = kwargs[:utxos]
155
- memo = kwargs[:memo]
156
- extra = kwargs[:extra]
157
- access_token = kwargs[:access_token]
158
- outputs = kwargs[:outputs] || []
159
- hint = kwargs[:hint]
160
-
161
- raise 'access_token required!' if access_token.nil? && !senders.include?(client_id)
162
-
163
- amount = amount.to_d.round(8)
164
- input_amount = utxos.map(
165
- &lambda { |utxo|
166
- utxo['amount'].to_d
167
- }
168
- ).sum
169
-
170
- if input_amount < amount
171
- raise format(
172
- 'not enough amount! %<input_amount>s < %<amount>s',
173
- input_amount: input_amount,
174
- amount: amount
175
- )
176
- end
177
-
178
- inputs = utxos.map(
179
- &lambda { |utx|
180
- {
181
- 'hash' => utx['transaction_hash'],
182
- 'index' => utx['output_index']
183
- }
184
- }
185
- )
186
-
187
- if outputs.empty?
188
- receivers_threshold = 1 if receivers.size == 1
189
- output0 = build_output(
190
- receivers: receivers,
191
- index: 0,
192
- amount: amount,
193
- threshold: receivers_threshold,
194
- hint: hint
195
- )
196
- outputs.push output0
197
-
198
- if input_amount > amount
199
- output1 = build_output(
200
- receivers: senders,
201
- index: 1,
202
- amount: input_amount - amount,
203
- threshold: senders_threshold,
204
- hint: hint
205
- )
206
- outputs.push output1
207
- end
208
- end
209
-
210
- extra ||= Digest.hexencode(memo.to_s.slice(0, 140))
211
- asset = asset_mixin_id || SHA3::Digest::SHA256.hexdigest(asset_id)
212
- tx = {
213
- version: 2,
214
- asset: asset,
215
- inputs: inputs,
216
- outputs: outputs,
217
- extra: extra
218
- }
219
-
220
- tx.to_json
221
- end
222
-
223
- def build_output(receivers:, index:, amount:, threshold:, hint: nil)
224
- _output = create_output receivers: receivers, index: index, hint: hint
225
- {
226
- amount: format('%.8f', amount.to_d.to_r),
227
- script: build_threshold_script(threshold),
228
- mask: _output['mask'],
229
- keys: _output['keys']
230
- }
231
- end
232
-
233
- def str_to_bin(str)
234
- return if str.nil?
235
-
236
- str.scan(/../).map(&:hex).pack('c*')
237
- end
32
+ def safe_multisig_request(request_id, access_token: nil)
33
+ path = format('/safe/multisigs/%<request_id>s', request_id:)
238
34
 
239
- def generate_trace_from_hash(hash, output_index = 0)
240
- MixinBot::Utils.generate_trace_from_hash hash, output_index
35
+ client.get path, access_token:
241
36
  end
242
37
  end
243
38
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class API
5
+ module Output
6
+ def build_threshold_script(threshold)
7
+ s = threshold.to_s(16)
8
+ s = "0#{s}" if s.length == 1
9
+ raise 'NVALID THRESHOLD' if s.length > 2
10
+
11
+ "fffe#{s}"
12
+ end
13
+
14
+ def safe_outputs(**kwargs)
15
+ asset = kwargs[:asset] || kwargs[:asset_id]
16
+ limit = kwargs[:limit] || 500
17
+ offset = kwargs[:offset] || ''
18
+ state = kwargs[:state] || ''
19
+ access_token = kwargs[:access_token]
20
+ order = kwargs[:order] || 'ASC'
21
+ members = kwargs[:members] || [config.app_id]
22
+ threshold = kwargs[:threshold] || members.length
23
+
24
+ members_hash = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
25
+
26
+ path = '/safe/outputs'
27
+ params = {
28
+ asset:,
29
+ limit:,
30
+ offset:,
31
+ state:,
32
+ members: members_hash,
33
+ threshold:,
34
+ order:
35
+ }.compact
36
+
37
+ client.get path, **params, access_token:
38
+ end
39
+
40
+ def safe_output(id, access_token: nil)
41
+ path = format('/safe/outputs/%<id>s', id:)
42
+ client.get path, access_token:
43
+ end
44
+ end
45
+ end
46
+ end
@@ -3,28 +3,17 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Payment
6
- def pay_url(options)
7
- format(
8
- 'https://mixin.one/pay?recipient=%<recipient_id>s&asset=%<asset>s&amount=%<amount>s&trace=%<trace>s&memo=%<memo>s',
9
- recipient_id: options[:recipient_id],
10
- asset: options[:asset_id],
11
- amount: options[:amount].to_s,
12
- trace: options[:trace],
13
- memo: options[:memo]
14
- )
15
- end
6
+ def safe_pay_url(**kwargs)
7
+ members = kwargs[:members]
8
+ threshold = kwargs[:threshold]
9
+ asset_id = kwargs[:asset_id]
10
+ amount = kwargs[:amount]
11
+ memo = kwargs[:memo] || ''
12
+ trace_id = kwargs[:trace_id] || SecureRandom.uuid
16
13
 
17
- # https://developers.mixin.one/api/alpha-mixin-network/verify-payment/
18
- def verify_payment(options)
19
- path = 'payments'
20
- payload = {
21
- asset_id: options[:asset_id],
22
- opponent_id: options[:opponent_id],
23
- amount: options[:amount].to_s,
24
- trace_id: options[:trace]
25
- }
14
+ mix_address = MixinBot.utils.build_mix_address(members, threshold)
26
15
 
27
- client.post(path, json: payload)
16
+ "https://mixin.one/pay/#{mix_address}?amount=#{amount}&asset=#{asset_id}&memo=#{memo}&trace=#{trace_id}"
28
17
  end
29
18
  end
30
19
  end
@@ -4,89 +4,81 @@ module MixinBot
4
4
  class API
5
5
  module Pin
6
6
  # https://developers.mixin.one/api/alpha-mixin-network/verify-pin/
7
- def verify_pin(pin_code)
7
+ def verify_pin(pin = nil)
8
+ pin ||= MixinBot.config.pin
9
+ raise ArgumentError, 'invalid pin' if pin.blank?
10
+
8
11
  path = '/pin/verify'
9
- payload = {
10
- pin: encrypt_pin(pin_code)
11
- }
12
12
 
13
- access_token = access_token('POST', path, payload.to_json)
14
- authorization = format('Bearer %<access_token>s', access_token: access_token)
15
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
13
+ payload =
14
+ if pin.length > 6
15
+ timestamp = (Time.now.utc.to_f * 1e9).to_i
16
+ pin_base64 = encrypt_tip_pin pin, 'TIP:VERIFY:', timestamp.to_s.rjust(32, '0')
17
+
18
+ {
19
+ pin_base64:,
20
+ timestamp:
21
+ }
22
+ else
23
+ {
24
+ pin: MixinBot.utils.encrypt_pin(pin)
25
+ }
26
+ end
27
+
28
+ client.post path, **payload
16
29
  end
17
30
 
18
31
  # https://developers.mixin.one/api/alpha-mixin-network/create-pin/
19
- def update_pin(old_pin:, pin:)
32
+ def update_pin(pin:, old_pin: nil)
33
+ old_pin ||= MixinBot.config.pin
34
+ raise ArgumentError, 'invalid old pin' if old_pin.present? && old_pin.length != 6
35
+
20
36
  path = '/pin/update'
21
37
  encrypted_old_pin = old_pin.nil? ? '' : encrypt_pin(old_pin, iterator: Time.now.utc.to_i)
38
+
22
39
  encrypted_pin = encrypt_pin(pin, iterator: Time.now.utc.to_i + 1)
23
40
  payload = {
24
- old_pin: encrypted_old_pin,
25
- pin: encrypted_pin
41
+ old_pin_base64: encrypted_old_pin,
42
+ pin_base64: encrypted_pin
26
43
  }
27
44
 
28
- access_token = access_token('POST', path, payload.to_json)
29
- authorization = format('Bearer %<access_token>s', access_token: access_token)
30
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
45
+ client.post path, **payload
31
46
  end
32
47
 
33
- # decrypt the encrpted pin, just for test
34
- def decrypt_pin(msg)
35
- msg = Base64.strict_decode64 msg
36
- iv = msg[0..15]
37
- cipher = msg[16..47]
38
- alg = 'AES-256-CBC'
39
- decode_cipher = OpenSSL::Cipher.new(alg)
40
- decode_cipher.decrypt
41
- decode_cipher.iv = iv
42
- decode_cipher.key = _generate_aes_key
43
- decoded = decode_cipher.update(cipher)
44
- decoded[0..5]
48
+ def prepare_tip_key(counter = 0)
49
+ ed25519_key = JOSE::JWA::Ed25519.keypair
50
+
51
+ private_key = ed25519_key[1].unpack1('H*')
52
+ public_key = (ed25519_key[0].bytes + MixinBot::Utils.encode_uint_64(counter + 1)).pack('c*').unpack1('H*')
53
+
54
+ {
55
+ private_key:,
56
+ public_key:
57
+ }
45
58
  end
46
59
 
47
- # https://developers.mixin.one/api/alpha-mixin-network/encrypted-pin/
48
- # 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.
49
- def encrypt_pin(pin_code, iterator: nil)
50
- iterator ||= Time.now.utc.to_i
51
- tszero = iterator % 0x100
52
- tsone = (iterator % 0x10000) >> 8
53
- tstwo = (iterator % 0x1000000) >> 16
54
- tsthree = (iterator % 0x100000000) >> 24
55
- tsstring = "#{tszero.chr}#{tsone.chr}#{tstwo.chr}#{tsthree.chr}\u0000\u0000\u0000\u0000"
56
- encrypt_content = pin_code + tsstring + tsstring
57
- pad_count = 16 - encrypt_content.length % 16
58
- padded_content =
59
- if pad_count.positive?
60
- encrypt_content + pad_count.chr * pad_count
61
- else
62
- encrypt_content
63
- end
60
+ def encrypt_pin(pin, iterator: nil)
61
+ MixinBot.utils.encrypt_pin(pin, iterator:, shared_key: generate_shared_key_with_server)
62
+ end
64
63
 
65
- alg = 'AES-256-CBC'
66
- aes = OpenSSL::Cipher.new(alg)
67
- iv = OpenSSL::Cipher.new(alg).random_iv
68
- aes.encrypt
69
- aes.key = _generate_aes_key
70
- aes.iv = iv
71
- cipher = aes.update(padded_content)
72
- msg = iv + cipher
73
- Base64.strict_encode64 msg
64
+ def decrypt_pin(msg)
65
+ MixinBot.utils.decrypt_pin msg, shared_key: generate_shared_key_with_server
74
66
  end
75
- end
76
67
 
77
- def _generate_aes_key
78
- if pin_token.size == 32
79
- JOSE::JWA::X25519.x25519(
80
- JOSE::JWA::Ed25519.secret_to_curve25519(private_key[0..31]),
81
- pin_token
82
- )
83
- else
84
- JOSE::JWA::PKCS1.rsaes_oaep_decrypt(
85
- 'SHA256',
86
- pin_token,
87
- OpenSSL::PKey::RSA.new(private_key),
88
- session_id
89
- )
68
+ def generate_shared_key_with_server
69
+ if config.server_public_key.size == 32
70
+ JOSE::JWA::X25519.x25519(
71
+ config.session_private_key_curve25519,
72
+ config.server_public_key_curve25519
73
+ )
74
+ else
75
+ JOSE::JWA::PKCS1.rsaes_oaep_decrypt(
76
+ 'SHA256',
77
+ config.server_public_key,
78
+ OpenSSL::PKey::RSA.new(config.session_private_key),
79
+ session_id
80
+ )
81
+ end
90
82
  end
91
83
  end
92
84
  end
@@ -6,18 +6,16 @@ module MixinBot
6
6
  def rpc_proxy(method, params = [], access_token: nil)
7
7
  path = '/external/proxy'
8
8
  payload = {
9
- method: method,
10
- params: params
9
+ method:,
10
+ params:
11
11
  }
12
12
 
13
- access_token ||= access_token('POST', path, payload.to_json)
14
- authorization = format('Bearer %<access_token>s', access_token: access_token)
15
- client.post(path, headers: { 'Authorization': authorization }, json: payload)
13
+ client.post path, **payload, access_token:
16
14
  end
17
15
 
18
16
  # send a signed transaction to main net
19
17
  def send_raw_transaction(raw, access_token: nil)
20
- rpc_proxy('sendrawtransaction', [raw], access_token: access_token)
18
+ rpc_proxy('sendrawtransaction', [raw], access_token:)
21
19
  end
22
20
 
23
21
  def get_transaction(hash, access_token: nil)
@@ -25,23 +23,23 @@ module MixinBot
25
23
  end
26
24
 
27
25
  def get_utxo(hash, index = 0, access_token: nil)
28
- rpc_proxy 'getutxo', [hash, index], access_token: access_token
26
+ rpc_proxy 'getutxo', [hash, index], access_token:
29
27
  end
30
28
 
31
29
  def get_snapshot(hash, access_token: nil)
32
- rpc_proxy 'getsnapshot', [hash], access_token: access_token
30
+ rpc_proxy 'getsnapshot', [hash], access_token:
33
31
  end
34
32
 
35
33
  def list_snapshots(offset = 0, count = 10, sig = false, tx = false, access_token: nil)
36
- rpc_proxy 'listsnapshots', [offset, count, sig, tx], access_token: access_token
34
+ rpc_proxy 'listsnapshots', [offset, count, sig, tx], access_token:
37
35
  end
38
36
 
39
37
  def list_mint_works(offset = 0, access_token: nil)
40
- rpc_proxy 'listmintworks', [offset], access_token: access_token
38
+ rpc_proxy 'listmintworks', [offset], access_token:
41
39
  end
42
40
 
43
41
  def list_mint_distributions(offset = 0, count = 10, tx = false, access_token: nil)
44
- rpc_proxy 'listmintdistributions', [offset, count, tx], access_token: access_token
42
+ rpc_proxy 'listmintdistributions', [offset, count, tx], access_token:
45
43
  end
46
44
  end
47
45
  end
@@ -3,45 +3,31 @@
3
3
  module MixinBot
4
4
  class API
5
5
  module Snapshot
6
- def network_snapshots(**options)
7
- path = format(
8
- '/network/snapshots?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&order=%<order>s',
9
- limit: options[:limit],
10
- offset: options[:offset],
11
- asset: options[:asset],
12
- order: options[:order]
13
- )
14
-
15
- access_token = options[:access_token] || access_token('GET', path)
16
- authorization = format('Bearer %<access_token>s', access_token: access_token)
17
- client.get(path, headers: { 'Authorization': authorization })
18
- end
19
- alias read_network_snapshots network_snapshots
20
-
21
- def snapshots(**options)
22
- path = format(
23
- '/snapshots?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&opponent=%<opponent>s&order=%<order>s',
6
+ def safe_snapshots(**options)
7
+ path = '/safe/snapshots'
8
+ params = {
24
9
  limit: options[:limit],
25
10
  offset: options[:offset],
26
11
  asset: options[:asset],
27
12
  opponent: options[:opponent],
13
+ app: options[:app_id],
28
14
  order: options[:order]
29
- )
15
+ }
30
16
 
31
- access_token = options[:access_token] || access_token('GET', path)
32
- authorization = format('Bearer %<access_token>s', access_token: access_token)
33
- client.get(path, headers: { 'Authorization': authorization })
17
+ client.get path, **params
34
18
  end
35
- alias read_snapshots snapshots
36
19
 
37
- def network_snapshot(snapshot_id, **options)
38
- path = format('/network/snapshots/%<snapshot_id>s', snapshot_id: snapshot_id)
20
+ def create_safe_snapshot_notification(**kwargs)
21
+ path = '/safe/snapshots/notifications'
22
+
23
+ payload = {
24
+ transaction_hash: kwargs[:transaction_hash],
25
+ output_index: kwargs[:output_index],
26
+ receiver_id: kwargs[:receiver_id]
27
+ }
39
28
 
40
- access_token = options[:access_token] || access_token('GET', path)
41
- authorization = format('Bearer %<access_token>s', access_token: access_token)
42
- client.get(path, headers: { 'Authorization': authorization })
29
+ client.post path, **payload, access_token: kwargs[:access_token]
43
30
  end
44
- alias read_network_snapshot network_snapshot
45
31
  end
46
32
  end
47
33
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class API
5
+ module Tip
6
+ TIP_ACTIONS = %w[
7
+ TIP:VERIFY:
8
+ TIP:ADDRESS:ADD:
9
+ TIP:ADDRESS:REMOVE:
10
+ TIP:USER:DEACTIVATE:
11
+ TIP:EMERGENCY:CONTACT:CREATE:
12
+ TIP:EMERGENCY:CONTACT:READ:
13
+ TIP:EMERGENCY:CONTACT:REMOVE:
14
+ TIP:PHONE:NUMBER:UPDATE:
15
+ TIP:MULTISIG:REQUEST:SIGN:
16
+ TIP:MULTISIG:REQUEST:UNLOCK:
17
+ TIP:COLLECTIBLE:REQUEST:SIGN:
18
+ TIP:COLLECTIBLE:REQUEST:UNLOCK:
19
+ TIP:TRANSFER:CREATE:
20
+ TIP:WITHDRAWAL:CREATE:
21
+ TIP:TRANSACTION:CREATE:
22
+ TIP:OAUTH:APPROVE:
23
+ TIP:PROVISIONING:UPDATE:
24
+ TIP:APP:OWNERSHIP:TRANSFER:
25
+ SEQUENCER:REGISTER:
26
+ ].freeze
27
+
28
+ def encrypt_tip_pin(pin, action, *params)
29
+ raise ArgumentError, 'invalid action' unless TIP_ACTIONS.include? action
30
+
31
+ pin_key = MixinBot.utils.decode_key pin
32
+
33
+ msg = action + params.flatten.map(&:to_s).join
34
+
35
+ msg = Digest::SHA256.digest(msg) unless action == 'TIP:VERIFY:'
36
+
37
+ signature = JOSE::JWA::Ed25519.sign msg, pin_key
38
+
39
+ encrypt_pin signature
40
+ end
41
+ end
42
+ end
43
+ end