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.
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