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
data/lib/mixin_bot/api.rb CHANGED
@@ -1,62 +1,86 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './client'
4
- require_relative './api/app'
5
- require_relative './api/asset'
6
- require_relative './api/attachment'
7
- require_relative './api/auth'
8
- require_relative './api/blaze'
9
- require_relative './api/collectible'
10
- require_relative './api/conversation'
11
- require_relative './api/encrypted_message'
12
- require_relative './api/me'
13
- require_relative './api/message'
14
- require_relative './api/multisig'
15
- require_relative './api/payment'
16
- require_relative './api/pin'
17
- require_relative './api/rpc'
18
- require_relative './api/snapshot'
19
- require_relative './api/transaction'
20
- require_relative './api/transfer'
21
- require_relative './api/user'
22
- require_relative './api/withdraw'
3
+ require_relative 'client'
4
+ require_relative 'configuration'
5
+ require_relative 'api/address'
6
+ require_relative 'api/app'
7
+ require_relative 'api/asset'
8
+ require_relative 'api/attachment'
9
+ require_relative 'api/auth'
10
+ require_relative 'api/blaze'
11
+ require_relative 'api/collectible'
12
+ require_relative 'api/conversation'
13
+ require_relative 'api/encrypted_message'
14
+ require_relative 'api/legacy_multisig'
15
+ require_relative 'api/legacy_output'
16
+ require_relative 'api/legacy_payment'
17
+ require_relative 'api/legacy_snapshot'
18
+ require_relative 'api/legacy_transaction'
19
+ require_relative 'api/legacy_transfer'
20
+ require_relative 'api/me'
21
+ require_relative 'api/message'
22
+ require_relative 'api/multisig'
23
+ require_relative 'api/output'
24
+ require_relative 'api/payment'
25
+ require_relative 'api/pin'
26
+ require_relative 'api/rpc'
27
+ require_relative 'api/snapshot'
28
+ require_relative 'api/tip'
29
+ require_relative 'api/transaction'
30
+ require_relative 'api/transfer'
31
+ require_relative 'api/user'
32
+ require_relative 'api/withdraw'
23
33
 
24
34
  module MixinBot
25
35
  class API
26
- attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key, :client, :blaze_host, :key_type
27
-
28
- def initialize(options = {})
29
- @client_id = options[:client_id] || MixinBot.client_id
30
- @client_secret = options[:client_secret] || MixinBot.client_secret
31
- @session_id = options[:session_id] || MixinBot.session_id
32
- @client = Client.new(MixinBot.api_host || 'api.mixin.one')
33
- @blaze_host = MixinBot.blaze_host || 'blaze.mixin.one'
34
- @pin_token =
35
- begin
36
- Base64.urlsafe_decode64 options[:pin_token] || MixinBot.pin_token
37
- rescue StandardError
38
- ''
36
+ attr_reader :config, :client
37
+
38
+ def initialize(**kwargs)
39
+ @config =
40
+ if kwargs.present?
41
+ MixinBot::Configuration.new(**kwargs)
42
+ else
43
+ MixinBot.config
39
44
  end
40
- _private_key = options[:private_key] || MixinBot.private_key
41
- if /^-----BEGIN RSA PRIVATE KEY-----/.match? _private_key
42
- @private_key = _private_key.gsub('\\r\\n', "\n").gsub("\r\n", "\n")
43
- @key_type = :rsa
44
- else
45
- @private_key = Base64.urlsafe_decode64 _private_key
46
- @key_type = :ed25519
47
- end
45
+
46
+ @client = Client.new(@config)
47
+ end
48
+
49
+ def utils
50
+ MixinBot::Utils
51
+ end
52
+
53
+ def client_id
54
+ config.app_id
48
55
  end
49
56
 
50
- def sign_raw_transaction(tx)
51
- MixinBot::Utils.sign_raw_transaction tx
57
+ def access_token(method, uri, body, **kwargs)
58
+ utils.access_token(
59
+ method,
60
+ uri,
61
+ body,
62
+ exp_in: kwargs.delete(:exp_in) || 600,
63
+ scp: kwargs.delete(:scp) || 'FULL',
64
+ app_id: config.app_id,
65
+ session_id: config.session_id,
66
+ private_key: config.session_private_key
67
+ )
68
+ end
69
+
70
+ def encode_raw_transaction(tx)
71
+ utils.encode_raw_transaction tx
52
72
  end
53
73
 
54
74
  def decode_raw_transaction(raw)
55
- MixinBot::Utils.decode_raw_transaction raw
75
+ utils.decode_raw_transaction raw
76
+ end
77
+
78
+ def generate_trace_from_hash(hash, output_index = 0)
79
+ utils.generate_trace_from_hash hash, output_index
56
80
  end
57
81
 
58
82
  # Use a mixin software to implement transaction build
59
- def sign_raw_transaction_native(json)
83
+ def encode_raw_transaction_native(json)
60
84
  ensure_mixin_command_exist
61
85
  command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
62
86
 
@@ -77,6 +101,7 @@ module MixinBot
77
101
  JSON.parse output.chomp
78
102
  end
79
103
 
104
+ include MixinBot::API::Address
80
105
  include MixinBot::API::App
81
106
  include MixinBot::API::Asset
82
107
  include MixinBot::API::Attachment
@@ -85,13 +110,21 @@ module MixinBot
85
110
  include MixinBot::API::Collectible
86
111
  include MixinBot::API::Conversation
87
112
  include MixinBot::API::EncryptedMessage
113
+ include MixinBot::API::LegacyMultisig
114
+ include MixinBot::API::LegacyOutput
115
+ include MixinBot::API::LegacyPayment
116
+ include MixinBot::API::LegacySnapshot
117
+ include MixinBot::API::LegacyTransaction
118
+ include MixinBot::API::LegacyTransfer
88
119
  include MixinBot::API::Me
89
120
  include MixinBot::API::Message
90
121
  include MixinBot::API::Multisig
122
+ include MixinBot::API::Output
91
123
  include MixinBot::API::Payment
92
124
  include MixinBot::API::Pin
93
125
  include MixinBot::API::Rpc
94
126
  include MixinBot::API::Snapshot
127
+ include MixinBot::API::Tip
95
128
  include MixinBot::API::Transaction
96
129
  include MixinBot::API::Transfer
97
130
  include MixinBot::API::User
@@ -34,16 +34,16 @@ module MixinBot
34
34
  end
35
35
 
36
36
  access_token = options[:accesstoken] || api_instance.access_token(options[:method].upcase, path, payload.blank? ? '' : payload.to_json)
37
- authorization = format('Bearer %<access_token>s', access_token: access_token)
37
+ authorization = format('Bearer %<access_token>s', access_token:)
38
38
  res = {}
39
39
 
40
40
  CLI::UI::Spinner.spin("#{options[:method]} #{path}, payload: #{payload}") do |_spinner|
41
41
  res =
42
42
  case options[:method].downcase.to_sym
43
43
  when :post
44
- api_instance.client.post(path, headers: { 'Authorization': authorization }, json: payload)
44
+ api_instance.client.post(path, headers: { Authorization: authorization }, json: payload)
45
45
  when :get
46
- api_instance.client.get(path, headers: { 'Authorization': authorization })
46
+ api_instance.client.get(path, headers: { Authorization: authorization })
47
47
  end
48
48
  end
49
49
 
@@ -52,19 +52,163 @@ module MixinBot
52
52
 
53
53
  desc 'authcode', 'code to authorize other mixin account'
54
54
  option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
55
- option :client_id, type: :string, required: true, aliases: '-c', desc: 'client_id of bot to authorize'
55
+ option :app_id, type: :string, required: true, aliases: '-c', desc: 'app_id of bot to authorize'
56
56
  option :scope, type: :array, default: ['PROFILE:READ'], aliases: '-s', desc: 'scope to authorize'
57
57
  def authcode
58
58
  res = {}
59
59
  CLI::UI::Spinner.spin('POST /oauth/authorize') do |_spinner|
60
60
  res =
61
61
  api_instance.authorize_code(
62
- user_id: options[:client_id],
62
+ user_id: options[:app_id],
63
63
  scope: options[:scope],
64
64
  pin: keystore['pin']
65
65
  )
66
66
  end
67
67
  log res['data']
68
68
  end
69
+
70
+ desc 'updatetip PIN', 'update TIP pin'
71
+ option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
72
+ def updatetip(pin)
73
+ profile = api_instance.me
74
+ log UI.fmt "{{v}} #{profile['full_name']}, TIP counter: #{profile['tip_counter']}"
75
+
76
+ counter = profile['tip_counter']
77
+ key = api_instance.prepare_tip_key counter
78
+ log UI.fmt "{{v}} Generated key: #{key[:private_key]}"
79
+
80
+ res = api_instance.update_pin old_pin: pin.to_s, pin: key[:public_key]
81
+
82
+ log({
83
+ pin: key[:private_key],
84
+ tip_key_base64: res['tip_key_base64']
85
+ })
86
+ rescue StandardError => e
87
+ log UI.fmt "{{x}} #{e.inspect}"
88
+ end
89
+
90
+ desc 'verifypin PIN', 'verify pin'
91
+ option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
92
+ def verifypin(pin)
93
+ res = api_instance.verify_pin pin.to_s
94
+
95
+ log res
96
+ rescue StandardError => e
97
+ log UI.fmt "{{x}} #{e.inspect}"
98
+ end
99
+
100
+ desc 'transfer USER_ID', 'transfer asset to USER_ID'
101
+ option :asset, type: :string, required: true, desc: 'Asset ID'
102
+ option :amount, type: :numeric, required: true, desc: 'Amount'
103
+ option :memo, type: :string, required: false, desc: 'memo'
104
+ option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
105
+ def transfer(user_id)
106
+ res = {}
107
+
108
+ CLI::UI::Spinner.spin "Try to transfer #{options[:amount]} #{options[:asset]} to #{user_id}" do |_spinner|
109
+ res = api_instance.create_transfer(
110
+ keystore['pin'],
111
+ {
112
+ asset_id: options[:asset],
113
+ opponent_id: user_id,
114
+ amount: options[:amount],
115
+ memo: options[:memo]
116
+ }
117
+ )
118
+ end
119
+
120
+ return unless res['snapshot_id'].present?
121
+
122
+ log UI.fmt "{{v}} Finished: https://mixin.one/snapshots/#{res['snapshot_id']}"
123
+ end
124
+
125
+ desc 'saferegister', 'register SAFE network'
126
+ option :spend_key, type: :string, required: true, desc: 'spend_key'
127
+ option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
128
+ def saferegister
129
+ res = api_instance.safe_register options[:spend_key]
130
+ log res
131
+ end
132
+
133
+ desc 'pay', 'generate payment url'
134
+ option :members, type: :array, required: true, desc: 'Reveivers, maybe multisig'
135
+ option :threshold, type: :numeric, required: false, default: 1, desc: 'Threshold of multisig'
136
+ option :asset, type: :string, required: true, desc: 'Asset ID'
137
+ option :amount, type: :numeric, required: true, desc: 'Amount'
138
+ option :trace, type: :string, required: false, desc: 'Trace ID'
139
+ option :memo, type: :string, required: false, desc: 'memo'
140
+ def pay
141
+ url = api_instance.safe_pay_url(
142
+ members: options[:members],
143
+ threshold: options[:threshold],
144
+ asset_id: options[:asset],
145
+ amount: options[:amount],
146
+ trace_id: options[:trace],
147
+ memo: options[:memo]
148
+ )
149
+
150
+ log UI.fmt "{{v}} #{url}"
151
+ end
152
+
153
+ desc 'safetransfer USER_ID', 'transfer asset to USER_ID with SAFE network'
154
+ option :asset, type: :string, required: true, desc: 'Asset ID'
155
+ option :amount, type: :numeric, required: true, desc: 'Amount'
156
+ option :trace, type: :string, required: false, desc: 'Trace ID'
157
+ option :memo, type: :string, required: false, desc: 'memo'
158
+ option :keystore, type: :string, aliases: '-k', required: true, desc: 'keystore or keystore.json file path'
159
+ def safetransfer(user_id)
160
+ amount = options[:amount].to_d
161
+ asset = options[:asset]
162
+ memo = options[:memo] || ''
163
+
164
+ # step 1: select inputs
165
+ outputs = api_instance.safe_outputs(state: 'unspent', asset_id: asset, limit: 500)['data'].sort_by { |o| o['amount'].to_d }
166
+ balance = outputs.sum(&->(output) { output['amount'].to_d })
167
+
168
+ utxos = []
169
+ outputs.each do |output|
170
+ break if utxos.sum { |o| o['amount'].to_d } >= amount
171
+
172
+ utxos.shift if utxos.size >= 255
173
+ utxos << output
174
+ end
175
+
176
+ log UI.fmt "Step 1/7: {{v}} Found #{outputs.count} unspent outputs, balance: #{balance}, selected #{utxos.count} outputs"
177
+
178
+ # step 2: build transaction
179
+ tx = api_instance.build_safe_transaction(
180
+ utxos:,
181
+ receivers: [
182
+ members: [user_id],
183
+ threshold: 1,
184
+ amount:
185
+ ],
186
+ extra: memo
187
+ )
188
+ raw = MixinBot::Utils.encode_raw_transaction tx
189
+ log UI.fmt "Step 2/5: {{v}} Built raw: #{raw}"
190
+
191
+ # step 3: verify transaction
192
+ request_id = SecureRandom.uuid
193
+ request = api_instance.create_safe_transaction_request(request_id, raw)['data']
194
+ log UI.fmt "Step 3/5: {{v}} Verified transaction, request_id: #{request[0]['request_id']}"
195
+
196
+ # step 4: sign transaction
197
+ signed_raw = api_instance.sign_safe_transaction(
198
+ raw:,
199
+ utxos:,
200
+ request: request[0]
201
+ )
202
+ log UI.fmt "Step 4/5: {{v}} Signed transaction: #{signed_raw}"
203
+
204
+ # step 5: submit transaction
205
+ r = api_instance.send_safe_transaction(
206
+ request_id,
207
+ signed_raw
208
+ )
209
+ log UI.fmt "Step 5/5: {{v}} Submit transaction, hash: #{r['data'].first['transaction_hash']}"
210
+ rescue StandardError => e
211
+ log UI.fmt "{{x}} #{e.inspect}"
212
+ end
69
213
  end
70
214
  end
@@ -11,17 +11,17 @@ module MixinBot
11
11
 
12
12
  desc 'unique UUIDS', 'generate unique UUID for two or more UUIDs'
13
13
  def unique(*uuids)
14
- log MixinBot::Utils.unique_uuid(*uuids)
14
+ log MixinBot.utils.unique_uuid(*uuids)
15
15
  end
16
16
 
17
17
  desc 'generatetrace HASH', 'generate trace ID from Tx hash'
18
18
  def generatetrace(hash)
19
- log MixinBot::Utils.generate_trace_from_hash(hash)
19
+ log MixinBot.utils.generate_trace_from_hash(hash)
20
20
  end
21
21
 
22
22
  desc 'decodetx TRANSACTION', 'decode raw transaction'
23
23
  def decodetx(transaction)
24
- log MixinBot::Utils.decode_raw_transaction(transaction)
24
+ log MixinBot.utils.decode_raw_transaction(transaction)
25
25
  end
26
26
 
27
27
  desc 'nftmemo', 'memo for mint NFT'
@@ -29,7 +29,17 @@ module MixinBot
29
29
  option :token, type: :numeric, required: true, aliases: '-t', desc: 'Token ID, Integer'
30
30
  option :hash, type: :string, required: true, aliases: '-h', desc: 'Hash of NFT metadata, 256-bit string'
31
31
  def nftmemo
32
- log MixinBot::Utils.nft(options[:collection], options[:token], options[:hash])
32
+ log MixinBot.utils.nft(options[:collection], options[:token], options[:hash])
33
+ end
34
+
35
+ desc 'rsa', 'generate RSA key'
36
+ def rsa
37
+ log MixinBot.utils.generate_rsa_key
38
+ end
39
+
40
+ desc 'ed25519', 'generate Ed25519 key'
41
+ def ed25519
42
+ log MixinBot.utils.generate_ed25519_key
33
43
  end
34
44
  end
35
45
  end
data/lib/mixin_bot/cli.rb CHANGED
@@ -5,9 +5,9 @@ require 'cli/ui'
5
5
  require 'thor'
6
6
  require 'yaml'
7
7
  require 'json'
8
- require_relative './cli/api'
9
- require_relative './cli/node'
10
- require_relative './cli/utils'
8
+ require_relative 'cli/api'
9
+ require_relative 'cli/node'
10
+ require_relative 'cli/utils'
11
11
 
12
12
  module MixinBot
13
13
  class CLI < Thor
@@ -21,7 +21,10 @@ module MixinBot
21
21
 
22
22
  def initialize(*args)
23
23
  super
24
- return if options[:keystore].blank?
24
+ if options[:keystore].blank?
25
+ @api_instance = MixinBot::API.new
26
+ return
27
+ end
25
28
 
26
29
  keystore =
27
30
  if File.file? options[:keystore]
@@ -33,7 +36,7 @@ module MixinBot
33
36
  @keystore =
34
37
  begin
35
38
  JSON.parse keystore
36
- rescue JSON::ParserError => e
39
+ rescue JSON::ParserError
37
40
  log UI.fmt(
38
41
  format(
39
42
  '{{x}} falied to parse keystore.json: %<keystore>s',
@@ -44,17 +47,17 @@ module MixinBot
44
47
 
45
48
  return unless @keystore
46
49
 
47
- MixinBot.api_host = options[:apihost]
50
+ MixinBot.config.api_host = options[:apihost]
48
51
  @api_instance ||=
49
52
  begin
50
53
  MixinBot::API.new(
51
- client_id: @keystore['client_id'],
54
+ app_id: @keystore['app_id'] || @keystore['client_id'],
52
55
  session_id: @keystore['session_id'],
53
- pin_token: @keystore['pin_token'],
54
- private_key: @keystore['private_key']
56
+ server_public_key: @keystore['server_public_key'] || @keystore['pin_token'],
57
+ session_private_key: @keystore['session_private_key'] || @keystore['private_key']
55
58
  )
56
59
  rescue StandardError => e
57
- log UI.fmt '{{x}}: Failed to initialize api, maybe your keystore is incorrect.'
60
+ log UI.fmt '{{x}}: Failed to initialize api, maybe your keystore is incorrect: %<error>s', error: e.message
58
61
  end
59
62
  end
60
63
 
@@ -4,153 +4,102 @@ module MixinBot
4
4
  class Client
5
5
  SERVER_SCHEME = 'https'
6
6
 
7
- attr_reader :host
8
-
9
- def initialize(host = 'api.mixin.one')
10
- @host = host
7
+ attr_reader :config, :conn
8
+
9
+ def initialize(config)
10
+ @config = config || MixinBot.config
11
+ @conn = Faraday.new(
12
+ url: "#{SERVER_SCHEME}://#{config.api_host}",
13
+ headers: {
14
+ 'Content-Type' => 'application/json',
15
+ 'User-Agent' => "mixin_bot/#{MixinBot::VERSION}"
16
+ }
17
+ ) do |f|
18
+ f.request :json
19
+ f.request :retry
20
+ f.response :json
21
+ f.response :logger if config.debug
22
+ end
11
23
  end
12
24
 
13
- def get(path, options = {})
14
- request(:get, path, options)
25
+ def get(path, *, **)
26
+ request(:get, path, *, **)
15
27
  end
16
28
 
17
- def post(path, options = {})
18
- request(:post, path, options)
29
+ def post(path, *, **)
30
+ request(:post, path, *, **)
19
31
  end
20
32
 
21
33
  private
22
34
 
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
33
- end
34
-
35
- raise RequestError, response.to_s unless response.status.success?
36
-
37
- parse_response(response) do |parse_as, result|
38
- case parse_as
39
- when :json
40
- if result['error'].nil?
41
- result.merge! result['data'] if result['data'].is_a? Hash
42
- break result
43
- end
35
+ def request(verb, path, *args, **kwargs)
36
+ access_token = kwargs.delete :access_token
37
+ exp_in = kwargs.delete(:exp_in) || 600
38
+ scp = kwargs.delete(:scp) || 'FULL'
44
39
 
45
- errmsg = "errcode: #{result['error']['code']}, errmsg: #{result['error']['description']}, request_id: #{response&.[]('X-Request-Id')}, server_time: #{response&.[]('X-Server-Time')}'"
46
-
47
- # status code description
48
- # 202 400 The request body can’t be pasred as valid data.
49
- # 202 401 Unauthorized.
50
- # 202 403 Forbidden.
51
- # 202 404 The endpoint is not found.
52
- # 202 429 Too Many Requests.
53
- # 202 10006 App update required.
54
- # 202 20116 The group chat is full.
55
- # 500 500 Internal Server Error.
56
- # 500 7000 Blaze server error.
57
- # 500 7001 The blaze operation timeout.
58
- # 202 10002 Illegal request paramters.
59
- # 202 20117 Insufficient balance。
60
- # 202 20118 PIN format error.
61
- # 202 20119 PIN error.
62
- # 202 20120 Transfer amount is too small.
63
- # 202 20121 Authorization code has expired.
64
- # 202 20124 Insufficient withdrawal fee.
65
- # 202 20125 The transfer has been paid by someone else.
66
- # 202 20127 The withdrawal amount is too small.
67
- # 202 20131 Withdrawal Memo format error.
68
- # 500 30100 The current asset's public chain synchronization error.
69
- # 500 30101 Wrong private key.
70
- # 500 30102 Wrong withdrawal address.
71
- # 500 30103 Insufficient pool.
72
- # 500 7000 WebSocket server error.
73
- # 500 7001 WebSocket operation timeout.
74
- case result['error']['code']
75
- when 401, 20121
76
- raise UnauthorizedError, errmsg
77
- when 403, 20116, 10002, 429
78
- raise ForbiddenError, errmsg
79
- when 404
80
- raise NotFoundError, errmsg
81
- when 400, 10006, 20133, 500, 7000, 7001
82
- raise ResponseError, errmsg
83
- when 20117
84
- raise InsufficientBalanceError, errmsg
85
- when 20118, 20119
86
- raise PinError, errmsg
87
- when 30103
88
- raise InsufficientPoolError, errmsg
40
+ kwargs.compact!
41
+ body =
42
+ if verb == :post
43
+ if args.present?
44
+ args.to_json
89
45
  else
90
- raise ResponseError, errmsg
46
+ kwargs.to_json
91
47
  end
92
48
  else
93
- result
49
+ ''
94
50
  end
95
- end
96
- end
97
-
98
- def uri_for(path)
99
- uri_options = {
100
- scheme: SERVER_SCHEME,
101
- host: host,
102
- path: path
103
- }
104
- Addressable::URI.new(uri_options)
105
- end
106
51
 
107
- def parse_response(response)
108
- content_type = response.headers[:content_type]
109
- parse_as = {
110
- %r{^application/json} => :json,
111
- %r{^image/.*} => :file,
112
- %r{^text/html} => :xml,
113
- %r{^text/plain} => :plain
114
- }.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || :plain
52
+ path = "#{path}?#{URI.encode_www_form(kwargs.sort_by { |k, _v| k })}" if verb == :get && kwargs.present?
53
+ access_token ||=
54
+ MixinBot.utils.access_token(
55
+ verb.to_s.upcase,
56
+ path,
57
+ body,
58
+ exp_in:,
59
+ scp:,
60
+ app_id: config.app_id,
61
+ session_id: config.session_id,
62
+ private_key: config.session_private_key
63
+ )
64
+ authorization = format('Bearer %<access_token>s', access_token:)
65
+
66
+ response =
67
+ case verb
68
+ when :get
69
+ @conn.get path, nil, { Authorization: authorization }
70
+ when :post
71
+ @conn.post path, body, { Authorization: authorization }
72
+ end
115
73
 
116
- if parse_as == :plain
117
- result = JSON.parse(response&.body&.to_s)
118
- result && yield(:json, result)
74
+ result = response.body
119
75
 
120
- yield(:plain, response.body)
76
+ if result['error'].blank?
77
+ result.merge! result['data'] if result['data'].is_a? Hash
78
+ return result
121
79
  end
122
80
 
123
- case parse_as
124
- when :json
125
- result = JSON.parse(response.body.to_s)
126
- when :file
127
- extension =
128
- if response.headers[:content_type] =~ %r{^image/.*}
129
- {
130
- 'image/gif': '.gif',
131
- 'image/jpeg': '.jpg',
132
- 'image/png': '.png'
133
- }[response.headers['content-type']]
134
- else
135
- ''
136
- end
137
-
138
- begin
139
- file = Tempfile.new(['mixin-file-', extension])
140
- file.binmode
141
- file.write(response.body)
142
- ensure
143
- file&.close
144
- end
145
-
146
- result = file
147
- when :xml
148
- result = Hash.from_xml(response.body.to_s)
81
+ errmsg = "#{verb.upcase}|#{path}|#{body}, errcode: #{result['error']['code']}, errmsg: #{result['error']['description']}, request_id: #{response&.[]('X-Request-Id')}, server_time: #{response&.[]('X-Server-Time')}'"
82
+
83
+ case result['error']['code']
84
+ when 401, 20121
85
+ raise UnauthorizedError, errmsg
86
+ when 403, 20116, 10002, 429
87
+ raise ForbiddenError, errmsg
88
+ when 404
89
+ raise NotFoundError, errmsg
90
+ when 400, 10006, 20133, 500, 7000, 7001
91
+ raise ResponseError, errmsg
92
+ when 20117
93
+ raise InsufficientBalanceError, errmsg
94
+ when 20118, 20119
95
+ raise PinError, errmsg
96
+ when 30103
97
+ raise InsufficientPoolError, errmsg
98
+ when 10404
99
+ raise UserNotFoundError, errmsg
149
100
  else
150
- result = response.body
101
+ raise ResponseError, errmsg
151
102
  end
152
-
153
- yield(parse_as, result)
154
103
  end
155
104
  end
156
105
  end