honeymaker 0.1.0 → 0.3.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/lib/honeymaker/client.rb +96 -0
  4. data/lib/honeymaker/clients/binance.rb +288 -0
  5. data/lib/honeymaker/clients/binance_us.rb +9 -0
  6. data/lib/honeymaker/clients/bingx.rb +136 -0
  7. data/lib/honeymaker/clients/bitget.rb +157 -0
  8. data/lib/honeymaker/clients/bitmart.rb +146 -0
  9. data/lib/honeymaker/clients/bitrue.rb +130 -0
  10. data/lib/honeymaker/clients/bitvavo.rb +160 -0
  11. data/lib/honeymaker/clients/bybit.rb +166 -0
  12. data/lib/honeymaker/clients/coinbase.rb +219 -0
  13. data/lib/honeymaker/clients/gemini.rb +99 -0
  14. data/lib/honeymaker/clients/hyperliquid.rb +65 -0
  15. data/lib/honeymaker/clients/kraken.rb +162 -0
  16. data/lib/honeymaker/clients/kucoin.rb +180 -0
  17. data/lib/honeymaker/clients/mexc.rb +166 -0
  18. data/lib/honeymaker/exchanges/binance.rb +11 -17
  19. data/lib/honeymaker/exchanges/bitget.rb +2 -2
  20. data/lib/honeymaker/exchanges/bitrue.rb +6 -16
  21. data/lib/honeymaker/exchanges/bitvavo.rb +2 -2
  22. data/lib/honeymaker/exchanges/bybit.rb +2 -2
  23. data/lib/honeymaker/exchanges/coinbase.rb +5 -9
  24. data/lib/honeymaker/exchanges/gemini.rb +4 -6
  25. data/lib/honeymaker/exchanges/kraken.rb +5 -9
  26. data/lib/honeymaker/exchanges/kucoin.rb +2 -2
  27. data/lib/honeymaker/exchanges/mexc.rb +11 -17
  28. data/lib/honeymaker/utils.rb +9 -0
  29. data/lib/honeymaker/version.rb +1 -1
  30. data/lib/honeymaker.rb +38 -0
  31. data/test/fixtures/bingx_symbols.json +26 -0
  32. data/test/fixtures/bitget_symbols.json +28 -0
  33. data/test/fixtures/bitmart_symbols.json +26 -0
  34. data/test/fixtures/bitrue_exchange_info.json +34 -0
  35. data/test/fixtures/bitvavo_markets.json +16 -0
  36. data/test/fixtures/bybit_instruments.json +23 -0
  37. data/test/fixtures/coinbase_products.json +24 -0
  38. data/test/fixtures/gemini_symbol_detail.json +9 -0
  39. data/test/fixtures/gemini_symbols.json +1 -0
  40. data/test/fixtures/hyperliquid_spot_meta.json +12 -0
  41. data/test/fixtures/kucoin_symbols.json +17 -0
  42. data/test/fixtures/mexc_exchange_info.json +40 -0
  43. data/test/honeymaker/client_test.rb +53 -0
  44. data/test/honeymaker/clients/binance_client_test.rb +101 -0
  45. data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
  46. data/test/honeymaker/clients/bingx_client_test.rb +82 -0
  47. data/test/honeymaker/clients/bitget_client_test.rb +109 -0
  48. data/test/honeymaker/clients/bitmart_client_test.rb +96 -0
  49. data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
  50. data/test/honeymaker/clients/bitvavo_client_test.rb +105 -0
  51. data/test/honeymaker/clients/bybit_client_test.rb +102 -0
  52. data/test/honeymaker/clients/coinbase_client_test.rb +132 -0
  53. data/test/honeymaker/clients/gemini_client_test.rb +83 -0
  54. data/test/honeymaker/clients/honeymaker_client_registry_test.rb +44 -0
  55. data/test/honeymaker/clients/hyperliquid_client_test.rb +65 -0
  56. data/test/honeymaker/clients/kraken_client_test.rb +84 -0
  57. data/test/honeymaker/clients/kucoin_client_test.rb +106 -0
  58. data/test/honeymaker/clients/mexc_client_test.rb +93 -0
  59. data/test/honeymaker/clients/validation_test.rb +182 -0
  60. data/test/honeymaker/exchanges/binance_us_test.rb +40 -0
  61. data/test/honeymaker/exchanges/bingx_test.rb +53 -0
  62. data/test/honeymaker/exchanges/bitget_test.rb +52 -0
  63. data/test/honeymaker/exchanges/bitmart_test.rb +52 -0
  64. data/test/honeymaker/exchanges/bitrue_test.rb +53 -0
  65. data/test/honeymaker/exchanges/bitvavo_test.rb +52 -0
  66. data/test/honeymaker/exchanges/bybit_test.rb +43 -0
  67. data/test/honeymaker/exchanges/coinbase_test.rb +52 -0
  68. data/test/honeymaker/exchanges/gemini_test.rb +48 -0
  69. data/test/honeymaker/exchanges/hyperliquid_test.rb +52 -0
  70. data/test/honeymaker/exchanges/kucoin_test.rb +43 -0
  71. data/test/honeymaker/exchanges/mexc_test.rb +64 -0
  72. data/test/honeymaker/utils_test.rb +38 -0
  73. data/test/test_helper.rb +1 -0
  74. metadata +74 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07736f839ae1cb3df2941e94e0170ebcea93e080631a42ce2dbc83ea85c8ca71
4
- data.tar.gz: 3574dff97c256b910fa3941ac4968295955ce8ff11997dc2b6f50ddb146aaf45
3
+ metadata.gz: ff95afc5916845501bc9cbb7c625979f9f06662608d0e47bfbf9ec4bb7be686b
4
+ data.tar.gz: 978d54c3f108c0a037c627250bd277d6c1e5159d940c10a1579e54d0f96523d3
5
5
  SHA512:
6
- metadata.gz: 51079a9c330fb03e8bb806d30040b3774ea27244551b6a13443eb20fc3c061b41ac025802b04c2276e7c5412ed85895bb6d49dae310f05edc54a20e2595f447b
7
- data.tar.gz: 301512a5685a2f19858b7f60dc99c6f53b584acbfbcf275491f55488eeac534e856eaa3dbbc679dc28d53dbfe331c8327cd834a2f830c478949e29ee421e3741
6
+ metadata.gz: aec9b76d37ca978445002ba6243c73c857b6a3e9bfc4b7fd43f8c73091a7606503df1076da8af93856a4a6a74fb9e72c32af20237b74b3e339339cc5b168d3d3
7
+ data.tar.gz: 630b79116ad0d74be37993f412aa4b81f371cdc58fae756fd5c08fe7496a887c458aee0b9e6439575d89268a6f8f0c8b78bc7734d35ac8debe0c16185d7280a1
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Honeymaker
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/honeymaker)](https://rubygems.org/gems/honeymaker)
4
+ [![CI](https://github.com/deltabadger/honeymaker/actions/workflows/test.yml/badge.svg)](https://github.com/deltabadger/honeymaker/actions)
5
+ [![License](https://img.shields.io/github/license/deltabadger/honeymaker)](LICENSE)
6
+
3
7
  Ruby clients for cryptocurrency exchange APIs. Originally extracted from [Deltabadger](https://github.com/deltabadger/deltabadger).
4
8
 
5
9
  ## Supported Exchanges
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+ require "securerandom"
6
+
7
+ module Honeymaker
8
+ class Client
9
+ OPTIONS = {
10
+ request: {
11
+ open_timeout: 5,
12
+ read_timeout: 30,
13
+ write_timeout: 10
14
+ }
15
+ }.freeze
16
+
17
+ attr_reader :api_key, :api_secret
18
+
19
+ def initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil)
20
+ @api_key = api_key
21
+ @api_secret = api_secret
22
+ @proxy = proxy
23
+ @logger = logger
24
+ end
25
+
26
+ def validate(type = :trading)
27
+ return Result::Failure.new("No credentials provided") unless authenticated?
28
+
29
+ case type
30
+ when :trading then validate_trading_credentials
31
+ when :read then validate_read_credentials
32
+ else raise Error, "Unknown validation type: #{type}. Use :trading or :read"
33
+ end
34
+ rescue Error
35
+ raise
36
+ rescue StandardError => e
37
+ Result::Failure.new(e.message)
38
+ end
39
+
40
+ private
41
+
42
+ def validate_trading_credentials
43
+ raise NotImplementedError, "#{self.class} must implement #validate_trading_credentials"
44
+ end
45
+
46
+ def validate_read_credentials
47
+ raise NotImplementedError, "#{self.class} must implement #validate_read_credentials"
48
+ end
49
+
50
+ def with_rescue
51
+ Result::Success.new(yield)
52
+ rescue Faraday::Error => e
53
+ body = e.respond_to?(:response_body) ? e.response_body : nil
54
+ error_message = (body && !body.empty?) ? body : e.message.to_s
55
+ error_message = "Unknown API error" if error_message.nil? || error_message.empty?
56
+ status = e.respond_to?(:response_status) ? e.response_status : nil
57
+ Result::Failure.new(error_message, data: { status: status })
58
+ rescue StandardError => e
59
+ msg = e.message
60
+ Result::Failure.new((msg && !msg.empty?) ? msg : "Unknown error")
61
+ end
62
+
63
+ def connection
64
+ @connection ||= build_client_connection(self.class::URL)
65
+ end
66
+
67
+ def build_client_connection(url, content_type_match: nil)
68
+ Faraday.new(url: url, **OPTIONS) do |config|
69
+ config.proxy = @proxy if @proxy
70
+ config.request :json
71
+ if content_type_match
72
+ config.response :json, content_type: content_type_match
73
+ else
74
+ config.response :json
75
+ end
76
+ config.response :raise_error
77
+ config.response :logger, @logger, headers: false, bodies: false, log_level: :debug if @logger
78
+ config.adapter :net_http_persistent do |http|
79
+ http.idle_timeout = 100
80
+ end
81
+ end
82
+ end
83
+
84
+ def authenticated?
85
+ @api_key && !@api_key.empty? && @api_secret && !@api_secret.empty?
86
+ end
87
+
88
+ def timestamp_ms
89
+ (Time.now.utc.to_f * 1_000).to_i
90
+ end
91
+
92
+ def hmac_sha256(secret, data)
93
+ OpenSSL::HMAC.hexdigest("sha256", secret, data)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,288 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeymaker
4
+ module Clients
5
+ class Binance < Client
6
+ URL = "https://api.binance.com"
7
+
8
+ def exchange_information(symbol: nil, symbols: nil, permissions: nil, show_permission_sets: nil, symbol_status: nil)
9
+ with_rescue do
10
+ response = connection.get do |req|
11
+ req.url "/api/v3/exchangeInfo"
12
+ req.headers = headers
13
+ req.params = {
14
+ symbol: symbol,
15
+ symbols: symbols&.to_json,
16
+ permissions: permissions&.to_json,
17
+ showPermissionSets: show_permission_sets,
18
+ symbolStatus: symbol_status
19
+ }.compact
20
+ end
21
+ response.body
22
+ end
23
+ end
24
+
25
+ def symbol_price_ticker(symbol: nil, symbols: nil)
26
+ with_rescue do
27
+ response = connection.get do |req|
28
+ req.url "/api/v3/ticker/price"
29
+ req.headers = headers
30
+ req.params = { symbol: symbol, symbols: symbols&.to_json }.compact
31
+ end
32
+ response.body
33
+ end
34
+ end
35
+
36
+ def symbol_order_book_ticker(symbol: nil, symbols: nil)
37
+ with_rescue do
38
+ response = connection.get do |req|
39
+ req.url "/api/v3/ticker/bookTicker"
40
+ req.headers = headers
41
+ req.params = { symbol: symbol, symbols: symbols&.to_json }.compact
42
+ end
43
+ response.body
44
+ end
45
+ end
46
+
47
+ def candlestick_data(symbol:, interval:, start_time: nil, end_time: nil, time_zone: 0, limit: 500)
48
+ with_rescue do
49
+ response = connection.get do |req|
50
+ req.url "/api/v3/klines"
51
+ req.headers = headers
52
+ req.params = {
53
+ symbol: symbol, interval: interval,
54
+ startTime: start_time, endTime: end_time,
55
+ timeZone: time_zone, limit: limit
56
+ }.compact
57
+ end
58
+ response.body
59
+ end
60
+ end
61
+
62
+ def account_information(omit_zero_balances: false, recv_window: 5000)
63
+ with_rescue do
64
+ response = connection.get do |req|
65
+ req.url "/api/v3/account"
66
+ req.headers = headers
67
+ req.params = {
68
+ omitZeroBalances: omit_zero_balances,
69
+ recvWindow: recv_window,
70
+ timestamp: timestamp_ms
71
+ }.compact
72
+ req.params[:signature] = sign_params(req.params)
73
+ end
74
+ response.body
75
+ end
76
+ end
77
+
78
+ def account_trade_list(symbol:, order_id: nil, start_time: nil, end_time: nil, from_id: nil, limit: 500, recv_window: 5000)
79
+ with_rescue do
80
+ response = connection.get do |req|
81
+ req.url "/api/v3/myTrades"
82
+ req.headers = headers
83
+ req.params = {
84
+ symbol: symbol, orderId: order_id,
85
+ startTime: start_time, endTime: end_time,
86
+ fromId: from_id, limit: limit,
87
+ recvWindow: recv_window, timestamp: timestamp_ms
88
+ }.compact
89
+ req.params[:signature] = sign_params(req.params)
90
+ end
91
+ response.body
92
+ end
93
+ end
94
+
95
+ def query_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
96
+ with_rescue do
97
+ response = connection.get do |req|
98
+ req.url "/api/v3/order"
99
+ req.headers = headers
100
+ req.params = {
101
+ symbol: symbol, orderId: order_id,
102
+ origClientOrderId: orig_client_order_id,
103
+ recvWindow: recv_window, timestamp: timestamp_ms
104
+ }.compact
105
+ req.params[:signature] = sign_params(req.params)
106
+ end
107
+ response.body
108
+ end
109
+ end
110
+
111
+ def all_orders(symbol:, order_id: nil, start_time: nil, end_time: nil, limit: 500, recv_window: 5000)
112
+ with_rescue do
113
+ response = connection.get do |req|
114
+ req.url "/api/v3/allOrders"
115
+ req.headers = headers
116
+ req.params = {
117
+ symbol: symbol, orderId: order_id,
118
+ startTime: start_time, endTime: end_time,
119
+ limit: limit, recvWindow: recv_window, timestamp: timestamp_ms
120
+ }.compact
121
+ req.params[:signature] = sign_params(req.params)
122
+ end
123
+ response.body
124
+ end
125
+ end
126
+
127
+ def new_order(symbol:, side:, type:, time_in_force: nil, quantity: nil, quote_order_qty: nil,
128
+ price: nil, new_client_order_id: nil, strategy_id: nil, strategy_type: nil,
129
+ stop_price: nil, trailing_delta: nil, iceberg_qty: nil, new_order_resp_type: nil,
130
+ self_trade_prevention_mode: nil, recv_window: 5000)
131
+ with_rescue do
132
+ response = connection.post do |req|
133
+ req.url "/api/v3/order"
134
+ req.headers = headers
135
+ req.params = {
136
+ symbol: symbol, side: side, type: type,
137
+ timeInForce: time_in_force, quantity: quantity,
138
+ quoteOrderQty: quote_order_qty, price: price,
139
+ newClientOrderId: new_client_order_id,
140
+ strategyId: strategy_id, strategyType: strategy_type,
141
+ stopPrice: stop_price, trailingDelta: trailing_delta,
142
+ icebergQty: iceberg_qty, newOrderRespType: new_order_resp_type,
143
+ selfTradePreventionMode: self_trade_prevention_mode,
144
+ recvWindow: recv_window, timestamp: timestamp_ms
145
+ }.compact
146
+ req.params[:signature] = sign_params(req.params)
147
+ end
148
+ response.body
149
+ end
150
+ end
151
+
152
+ def cancel_order(symbol:, order_id: nil, orig_client_order_id: nil, new_client_order_id: nil,
153
+ cancel_restrictions: nil, recv_window: 5000)
154
+ with_rescue do
155
+ response = connection.delete do |req|
156
+ req.url "/api/v3/order"
157
+ req.headers = headers
158
+ req.params = {
159
+ symbol: symbol, orderId: order_id,
160
+ origClientOrderId: orig_client_order_id,
161
+ newClientOrderId: new_client_order_id,
162
+ cancelRestrictions: cancel_restrictions,
163
+ recvWindow: recv_window, timestamp: timestamp_ms
164
+ }.compact
165
+ req.params[:signature] = sign_params(req.params)
166
+ end
167
+ response.body
168
+ end
169
+ end
170
+
171
+ def deposit_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: 1000, recv_window: 5000)
172
+ with_rescue do
173
+ response = connection.get do |req|
174
+ req.url "/sapi/v1/capital/deposit/hisrec"
175
+ req.headers = headers
176
+ req.params = {
177
+ coin: coin, status: status,
178
+ startTime: start_time, endTime: end_time,
179
+ offset: offset, limit: limit,
180
+ recvWindow: recv_window, timestamp: timestamp_ms
181
+ }.compact
182
+ req.params[:signature] = sign_params(req.params)
183
+ end
184
+ response.body
185
+ end
186
+ end
187
+
188
+ def withdraw_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: 1000, recv_window: 5000)
189
+ with_rescue do
190
+ response = connection.get do |req|
191
+ req.url "/sapi/v1/capital/withdraw/history"
192
+ req.headers = headers
193
+ req.params = {
194
+ coin: coin, status: status,
195
+ startTime: start_time, endTime: end_time,
196
+ offset: offset, limit: limit,
197
+ recvWindow: recv_window, timestamp: timestamp_ms
198
+ }.compact
199
+ req.params[:signature] = sign_params(req.params)
200
+ end
201
+ response.body
202
+ end
203
+ end
204
+
205
+ def get_all_coins_information(recv_window: 5000)
206
+ with_rescue do
207
+ response = connection.get do |req|
208
+ req.url "/sapi/v1/capital/config/getall"
209
+ req.headers = headers
210
+ req.params = { recvWindow: recv_window, timestamp: timestamp_ms }
211
+ req.params[:signature] = sign_params(req.params)
212
+ end
213
+ response.body
214
+ end
215
+ end
216
+
217
+ def api_description(recv_window: 5000)
218
+ with_rescue do
219
+ response = connection.get do |req|
220
+ req.url "/sapi/v1/account/apiRestrictions"
221
+ req.headers = headers
222
+ req.params = { recvWindow: recv_window, timestamp: timestamp_ms }
223
+ req.params[:signature] = sign_params(req.params)
224
+ end
225
+ response.body
226
+ end
227
+ end
228
+
229
+ def get_withdraw_addresses(recv_window: 5000)
230
+ with_rescue do
231
+ response = connection.get do |req|
232
+ req.url "/sapi/v1/capital/withdraw/address/list"
233
+ req.headers = headers
234
+ req.params = { recvWindow: recv_window, timestamp: timestamp_ms }
235
+ req.params[:signature] = sign_params(req.params)
236
+ end
237
+ response.body
238
+ end
239
+ end
240
+
241
+ def withdraw(coin:, address:, amount:, network: nil, address_tag: nil, recv_window: 5000)
242
+ with_rescue do
243
+ response = connection.post do |req|
244
+ req.url "/sapi/v1/capital/withdraw/apply"
245
+ req.headers = headers
246
+ req.params = {
247
+ coin: coin, address: address, amount: amount,
248
+ network: network, addressTag: address_tag,
249
+ recvWindow: recv_window, timestamp: timestamp_ms
250
+ }.compact
251
+ req.params[:signature] = sign_params(req.params)
252
+ end
253
+ response.body
254
+ end
255
+ end
256
+
257
+ private
258
+
259
+ def validate_trading_credentials
260
+ # Try cancelling a non-existent order — error -2011 (ORDER_DOES_NOT_EXIST) means key is valid with trade permission
261
+ result = cancel_order(symbol: "ETHBTC", order_id: "9999999999")
262
+ return Result::Failure.new("Invalid trading credentials") if result.failure?
263
+
264
+ code = result.data.is_a?(Hash) ? result.data["code"] : nil
265
+ code == -2011 ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
266
+ end
267
+
268
+ def validate_read_credentials
269
+ result = api_description
270
+ Result.new(data: result.success?, errors: result.success? ? [] : ["Invalid read credentials"])
271
+ end
272
+
273
+ def headers
274
+ if authenticated?
275
+ { "X-MBX-APIKEY": @api_key, Accept: "application/json", "Content-Type": "application/json" }
276
+ else
277
+ { Accept: "application/json", "Content-Type": "application/json" }
278
+ end
279
+ end
280
+
281
+ def sign_params(params)
282
+ return unless @api_secret
283
+ query = Faraday::Utils.build_query(params)
284
+ hmac_sha256(@api_secret, query)
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeymaker
4
+ module Clients
5
+ class BinanceUs < Binance
6
+ URL = "https://api.binance.us"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeymaker
4
+ module Clients
5
+ class BingX < Client
6
+ URL = "https://open-api.bingx.com"
7
+
8
+ def get_symbols
9
+ get_public("/openApi/spot/v1/common/symbols")
10
+ end
11
+
12
+ def get_ticker(symbol: nil)
13
+ get_public("/openApi/spot/v2/market/ticker", { symbol: symbol })
14
+ end
15
+
16
+ def get_depth(symbol:, limit: nil)
17
+ get_public("/openApi/spot/v1/market/depth", { symbol: symbol, limit: limit })
18
+ end
19
+
20
+ def get_klines(symbol:, interval:, start_time: nil, end_time: nil, limit: nil)
21
+ get_public("/openApi/spot/v1/market/kline", {
22
+ symbol: symbol, interval: interval,
23
+ startTime: start_time, endTime: end_time, limit: limit
24
+ })
25
+ end
26
+
27
+ def get_balances
28
+ get_signed("/openApi/spot/v1/account/balance")
29
+ end
30
+
31
+ def place_order(symbol:, side:, type:, quantity: nil, quote_order_qty: nil, price: nil,
32
+ time_in_force: nil, client_order_id: nil)
33
+ post_signed("/openApi/spot/v1/trade/order", {
34
+ symbol: symbol, side: side, type: type,
35
+ quantity: quantity, quoteOrderQty: quote_order_qty,
36
+ price: price, timeInForce: time_in_force,
37
+ newClientOrderId: client_order_id
38
+ })
39
+ end
40
+
41
+ def get_order(symbol:, order_id: nil, client_order_id: nil)
42
+ get_signed("/openApi/spot/v1/trade/query", {
43
+ symbol: symbol, orderId: order_id, clientOrderID: client_order_id
44
+ })
45
+ end
46
+
47
+ def cancel_order(symbol:, order_id: nil, client_order_id: nil)
48
+ post_signed("/openApi/spot/v1/trade/cancel", {
49
+ symbol: symbol, orderId: order_id, clientOrderID: client_order_id
50
+ })
51
+ end
52
+
53
+ def get_trade_fills(symbol: nil, order_id: nil, start_time: nil, end_time: nil, from_id: nil, limit: nil)
54
+ get_signed("/openApi/spot/v1/trade/fills", {
55
+ symbol: symbol, orderId: order_id,
56
+ startTime: start_time, endTime: end_time, fromId: from_id, limit: limit
57
+ })
58
+ end
59
+
60
+ def deposit_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: nil)
61
+ get_signed("/openApi/wallets/v1/capital/deposit/hisrec", {
62
+ coin: coin, status: status,
63
+ startTime: start_time, endTime: end_time, offset: offset, limit: limit
64
+ })
65
+ end
66
+
67
+ def withdraw_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: nil, id: nil)
68
+ get_signed("/openApi/wallets/v1/capital/withdraw/history", {
69
+ coin: coin, status: status,
70
+ startTime: start_time, endTime: end_time, offset: offset, limit: limit, id: id
71
+ })
72
+ end
73
+
74
+ def withdraw(coin:, address:, amount:, network: nil, wallet_type: nil, tag: nil)
75
+ post_signed("/openApi/wallets/v1/capital/withdraw/apply", {
76
+ coin: coin, address: address, amount: amount,
77
+ network: network, walletType: wallet_type, tag: tag
78
+ })
79
+ end
80
+
81
+ private
82
+
83
+ def validate_trading_credentials
84
+ result = get_balances
85
+ return Result::Failure.new("Invalid trading credentials") if result.failure?
86
+ result.data["code"]&.to_i&.zero? ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
87
+ end
88
+
89
+ def validate_read_credentials
90
+ validate_trading_credentials
91
+ end
92
+
93
+ def get_public(path, params = {})
94
+ with_rescue do
95
+ response = connection.get do |req|
96
+ req.url path
97
+ req.params = params.compact
98
+ end
99
+ response.body
100
+ end
101
+ end
102
+
103
+ def get_signed(path, params = {})
104
+ with_rescue do
105
+ params = params.compact.merge(timestamp: timestamp_ms)
106
+ params[:signature] = hmac_sha256(@api_secret, Faraday::Utils.build_query(params))
107
+
108
+ response = connection.get do |req|
109
+ req.url path
110
+ req.headers = { "X-BX-APIKEY": @api_key }
111
+ req.params = params
112
+ end
113
+ response.body
114
+ end
115
+ end
116
+
117
+ def post_signed(path, params = {})
118
+ with_rescue do
119
+ params = params.compact.merge(timestamp: timestamp_ms)
120
+ params[:signature] = hmac_sha256(@api_secret, Faraday::Utils.build_query(params))
121
+
122
+ response = connection.post do |req|
123
+ req.url path
124
+ req.headers = { "X-BX-APIKEY": @api_key, "Content-Type": "application/json" }
125
+ req.params = params
126
+ end
127
+ response.body
128
+ end
129
+ end
130
+
131
+ def connection
132
+ @connection ||= build_client_connection(URL, content_type_match: //)
133
+ end
134
+ end
135
+ end
136
+ end