bitget.rb 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +17 -2
- data/bitget.rb.gemspec +4 -2
- data/lib/Bitget/V2/Client.rb +39 -28
- data/lib/Bitget/V2/Client2.rb +500 -0
- data/lib/Bitget/VERSION.rb +6 -0
- data/lib/String/url_encode.rb +3 -20
- data/lib/Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb +26 -0
- data/lib/Thoran/String/UrlEncode/url_encode.rb +26 -0
- data/lib/bitget.rb +0 -4
- data/test/V2/client_test.rb +59 -3
- data/test/client_test.rb +59 -3
- data/test/helper.rb +3 -24
- data/test/test_all.rb +4 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9a76dd0384385ec874d2689053e2a9af7ed9cc4167e9980691bcca9b4855005
|
4
|
+
data.tar.gz: 5d1f16940c82a9f072360fbecb5941be0df52d3161dab7db217814b5a5a2b00e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 975b7a27c4ab2834dbcdd907b65d0fe66b17bb7d8a8c2fddee5d4cc6d3f4f2431afd670beee84312d5ff828e7d88a1dcdabb20fa1cb1a32a72a55f8da809c2b5
|
7
|
+
data.tar.gz: 3f030a15eaf44fd3ccba97ea216a11da4f377135c9c49127b5dbe713d4227357512950ec6ffa4e521bd2d73fed4df903b9a574cf7ea92054110eb2a366f08857
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
### Setup
|
25
25
|
```ruby
|
26
|
-
bitget_client
|
26
|
+
bitget_client = Bitget::Client.new(
|
27
27
|
api_key: 'api_key0',
|
28
28
|
api_secret: 'api_secret0',
|
29
29
|
api_passphrase: 'api_passphrase0'
|
@@ -97,9 +97,24 @@ bitget_client.spot_public_coins(coin: 'BTC')
|
|
97
97
|
# }
|
98
98
|
```
|
99
99
|
|
100
|
+
### Get Account Information
|
101
|
+
```ruby
|
102
|
+
bitget_client.spot_account_info
|
103
|
+
```
|
104
|
+
|
105
|
+
### Get Account Assets
|
106
|
+
```ruby
|
107
|
+
bitget_client.spot_account_assets
|
108
|
+
```
|
109
|
+
```ruby
|
110
|
+
bitget_client.spot_account_assets(coin: 'BTC')
|
111
|
+
```
|
112
|
+
|
113
|
+
See https://www.bitget.com/api-doc/spot/intro for further information on endpoint arguments
|
114
|
+
|
100
115
|
## Contributing
|
101
116
|
|
102
|
-
1. Fork it (
|
117
|
+
1. Fork it (https://github.com/thoran/bitget.rb/fork)
|
103
118
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
104
119
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
105
120
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/bitget.rb.gemspec
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require_relative './lib/Bitget/VERSION'
|
2
|
+
|
1
3
|
Gem::Specification.new do |spec|
|
2
4
|
spec.name = 'bitget.rb'
|
3
5
|
|
4
|
-
spec.version =
|
5
|
-
spec.date = '2025-
|
6
|
+
spec.version = Bitget::VERSION
|
7
|
+
spec.date = '2025-05-16'
|
6
8
|
|
7
9
|
spec.summary = "Access the Bitget API with Ruby."
|
8
10
|
spec.description = "Access the Bitget API with Ruby."
|
data/lib/Bitget/V2/Client.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# Bitget/V2/Client.rb
|
2
2
|
# Bitget::V2::Client
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'fileutils'
|
5
5
|
gem 'http.rb'; require 'http.rb'
|
6
6
|
require 'json'
|
7
7
|
require 'logger'
|
8
8
|
require 'openssl'
|
9
9
|
|
10
10
|
require_relative '../Error'
|
11
|
+
require_relative '../../Hash/to_parameter_string'
|
11
12
|
require_relative '../../Hash/x_www_form_urlencode'
|
12
13
|
|
13
14
|
module Bitget
|
@@ -41,22 +42,33 @@ module Bitget
|
|
41
42
|
end
|
42
43
|
end # class << self
|
43
44
|
|
45
|
+
# Get Coin Info
|
46
|
+
# GET /api/v2/spot
|
44
47
|
def spot_public_coins(coin: nil)
|
45
|
-
response = get(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
response = get(path: '/spot/public/coins', args: {coin: coin})
|
49
|
+
handle_response(response)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get Account Information
|
53
|
+
# GET /api/v2/spot/account/info
|
54
|
+
def spot_account_info
|
55
|
+
response = get(path: '/spot/account/info')
|
56
|
+
handle_response(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get Account Assets
|
60
|
+
# GET /api/v2/spot/account/assets
|
61
|
+
def spot_account_assets(**args)
|
62
|
+
response = get(path: '/spot/account/assets', args: args)
|
51
63
|
handle_response(response)
|
52
64
|
end
|
53
65
|
|
54
66
|
private
|
55
67
|
|
56
68
|
def initialize(api_key:, api_secret:, api_passphrase:)
|
57
|
-
@api_key = api_key
|
58
|
-
@api_secret = api_secret
|
59
|
-
@api_passphrase = api_passphrase
|
69
|
+
@api_key = api_key
|
70
|
+
@api_secret = api_secret
|
71
|
+
@api_passphrase = api_passphrase
|
60
72
|
end
|
61
73
|
|
62
74
|
def full_path(path)
|
@@ -69,30 +81,28 @@ module Bitget
|
|
69
81
|
end
|
70
82
|
|
71
83
|
def timestamp
|
72
|
-
@timestamp ||= Time.now.to_i.to_s
|
84
|
+
@timestamp ||= (Time.now.to_f * 1000).to_i.to_s
|
73
85
|
end
|
74
86
|
|
75
87
|
def message(verb:, path:, args:)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
when 'POST'
|
81
|
-
nil
|
88
|
+
case verb
|
89
|
+
when 'GET'
|
90
|
+
if args.empty?
|
91
|
+
[timestamp, verb, full_path(path)].join
|
82
92
|
else
|
83
|
-
|
93
|
+
query_string = args.to_parameter_string
|
94
|
+
[timestamp, verb, full_path(path), '?', query_string].join
|
84
95
|
end
|
85
|
-
|
86
|
-
|
87
|
-
if query_string.nil?
|
96
|
+
when 'POST'
|
97
|
+
body = args.to_json
|
88
98
|
[timestamp, verb, full_path(path), body].join
|
89
|
-
else
|
90
|
-
[timestamp, verb, full_path(path), '?', query_string, body].join
|
91
99
|
end
|
92
100
|
end
|
93
101
|
|
94
102
|
def signature(message)
|
95
|
-
OpenSSL::
|
103
|
+
digest = OpenSSL::Digest.new('SHA256')
|
104
|
+
hmac = OpenSSL::HMAC.digest(digest, @api_secret, message)
|
105
|
+
Base64.strict_encode64(hmac)
|
96
106
|
end
|
97
107
|
|
98
108
|
def request_string(path)
|
@@ -105,9 +115,8 @@ module Bitget
|
|
105
115
|
'ACCESS-SIGN' => signature,
|
106
116
|
'ACCESS-TIMESTAMP' => timestamp,
|
107
117
|
'ACCESS-PASSPHRASE' => @api_passphrase,
|
108
|
-
'Content-
|
109
|
-
'
|
110
|
-
'Accept' => 'application/json',
|
118
|
+
'Content-Type' => 'application/json',
|
119
|
+
'X-CHANNEL-API-CODE' => 'spot',
|
111
120
|
}
|
112
121
|
end
|
113
122
|
|
@@ -132,7 +141,9 @@ module Bitget
|
|
132
141
|
log_request(verb: verb, request_string: request_string(path), args: args)
|
133
142
|
message = message(verb: verb, path: path, args: args)
|
134
143
|
signature = signature(message)
|
135
|
-
|
144
|
+
headers = headers(signature)
|
145
|
+
@timestamp = nil
|
146
|
+
HTTP.send(verb.to_s.downcase, request_string(path), args, headers)
|
136
147
|
end
|
137
148
|
|
138
149
|
def get(path:, args: {})
|
@@ -0,0 +1,500 @@
|
|
1
|
+
# Bitget/V2/Client.rb
|
2
|
+
# Bitget::V2::Client
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
gem 'http.rb'; require 'http.rb'
|
6
|
+
require 'json'
|
7
|
+
require 'logger'
|
8
|
+
require 'openssl'
|
9
|
+
|
10
|
+
require_relative '../Error'
|
11
|
+
require_relative '../../Hash/to_parameter_string'
|
12
|
+
require_relative '../../Hash/x_www_form_urlencode'
|
13
|
+
|
14
|
+
module Bitget
|
15
|
+
module V2
|
16
|
+
class Client
|
17
|
+
|
18
|
+
API_HOST = 'api.bitget.com'
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_writer :log_file_path
|
22
|
+
|
23
|
+
def path_prefix
|
24
|
+
'/api/v2'
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_log_file_path
|
28
|
+
File.join(%w{~ log bitget log.txt})
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_file_path
|
32
|
+
File.expand_path(@log_file_path || default_log_file_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_file
|
36
|
+
FileUtils.mkdir_p(File.dirname(log_file_path))
|
37
|
+
File.open(log_file_path, File::WRONLY | File::APPEND | File::CREAT)
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger
|
41
|
+
@logger ||= Logger.new(log_file, 'daily')
|
42
|
+
end
|
43
|
+
end # class << self
|
44
|
+
|
45
|
+
# Market
|
46
|
+
|
47
|
+
def spot_public_coins(coin: nil)
|
48
|
+
response = get(path: '/spot/public/coins', args: {coin: coin})
|
49
|
+
handle_response(response)
|
50
|
+
end
|
51
|
+
|
52
|
+
def spot_public_symbols(symbol: nil)
|
53
|
+
response = get(path: '/spot/public/symbols', args: {symbol: symbol})
|
54
|
+
handle_response(response)
|
55
|
+
end
|
56
|
+
|
57
|
+
def spot_market_vip_free_rate
|
58
|
+
response = get(path: '/spot/market/vip-fee-rate')
|
59
|
+
handle_response(response)
|
60
|
+
end
|
61
|
+
|
62
|
+
def spot_market_tickers(symbol: nil)
|
63
|
+
response = get(path: '/spot/market/tickers', args: {symbol: symbol})
|
64
|
+
handle_response(response)
|
65
|
+
end
|
66
|
+
|
67
|
+
def spot_market_merge_depth(symbol:, precision: nil, limit: nil)
|
68
|
+
response = get(path: '/spot/market/merge-depth', args: {symbol: symbol, precision: precision, limit: limit})
|
69
|
+
handle_response(response)
|
70
|
+
end
|
71
|
+
|
72
|
+
def spot_market_orderbook(symbol:, type: nil, limit: nil)
|
73
|
+
response = get(path: '/spot/market/orderbook', args: {symbol: symbol, type: type, limit: limit})
|
74
|
+
handle_response(response)
|
75
|
+
end
|
76
|
+
|
77
|
+
def spot_market_candles(symbol:, granularity:, start_time: nil, end_time: nil, limit: nil)
|
78
|
+
response = get(
|
79
|
+
path: '/spot/market/candles',
|
80
|
+
args: {symbol: symbol, granularity: granularity, startTime: start_time, endTime: end_time, limit: limit}
|
81
|
+
)
|
82
|
+
handle_response(response)
|
83
|
+
end
|
84
|
+
|
85
|
+
def spot_market_history_candles(symbol:, granularity:, end_time: nil, limit: nil)
|
86
|
+
response = get(
|
87
|
+
path: '/spot/market/history-candles',
|
88
|
+
args: {symbol: symbol, granularity: granularity, endTime: end_time, limit: limit}
|
89
|
+
)
|
90
|
+
handle_response(response)
|
91
|
+
end
|
92
|
+
|
93
|
+
def spot_market_fills(symbol:, limit: nil)
|
94
|
+
response = get(path: '/spot/market/fills', args: {symbol: symbol, limit: limit})
|
95
|
+
handle_response(response)
|
96
|
+
end
|
97
|
+
|
98
|
+
def spot_market_fills_history(symbol:, limit: nil, id_less_than: nil, start_time: nil, end_time: nil)
|
99
|
+
response = get(
|
100
|
+
path: '/spot/market/fills-history',
|
101
|
+
args: {symbol: symbol, limit: limit, idLessThan: id_less_than, startTime: start_time, endTime: end_time}
|
102
|
+
)
|
103
|
+
handle_response(response)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Trade
|
107
|
+
|
108
|
+
def trade_place_order(
|
109
|
+
symbol:,
|
110
|
+
side:,
|
111
|
+
order_type:,
|
112
|
+
force:,
|
113
|
+
price: nil,
|
114
|
+
size:,
|
115
|
+
client_order_id: nil,
|
116
|
+
trigger_price: nil,
|
117
|
+
tpsl_type: nil,
|
118
|
+
request_time: nil,
|
119
|
+
receive_window: nil,
|
120
|
+
stp_mode: nil,
|
121
|
+
preset_take_profit_price: nil,
|
122
|
+
execute_take_profit_price: nil,
|
123
|
+
preset_stop_loss_price: nil,
|
124
|
+
execute_stop_loss_price: nil
|
125
|
+
)
|
126
|
+
response = post(
|
127
|
+
path: '/trade/place-order',
|
128
|
+
args: {
|
129
|
+
symbol: symbol,
|
130
|
+
side: side,
|
131
|
+
orderType: order_type,
|
132
|
+
force: force,
|
133
|
+
price: price,
|
134
|
+
size: size,
|
135
|
+
client_order_id: clientOid,
|
136
|
+
trigger_price: triggerPrice,
|
137
|
+
tpsl_type: tpslType,
|
138
|
+
request_time: requestTime,
|
139
|
+
receive_window: receiveWindow,
|
140
|
+
stp_mode: stpMode,
|
141
|
+
preset_take_profit_price: presetTakeProfitPrice,
|
142
|
+
execute_take_profit_price: executeTakeProfitPrice,
|
143
|
+
preset_stop_loss_price: presetStopLossPrice,
|
144
|
+
execute_stop_loss_price: executeStopLossPrice,
|
145
|
+
}
|
146
|
+
)
|
147
|
+
handle_response(response)
|
148
|
+
end
|
149
|
+
|
150
|
+
def trade_cancel_replace_order(
|
151
|
+
symbol:,
|
152
|
+
price:,
|
153
|
+
size:,
|
154
|
+
client_order_id: nil,
|
155
|
+
order_id: nil,
|
156
|
+
new_client_order_id: nil,
|
157
|
+
preset_take_profit_price: nil,
|
158
|
+
execute_take_profit_price: nil,
|
159
|
+
preset_stop_loss_price: nil,
|
160
|
+
execute_stop_loss_price: nil
|
161
|
+
)
|
162
|
+
response = post(
|
163
|
+
path: '/trade/cancel-replace-order',
|
164
|
+
args: {
|
165
|
+
symbol: symbol,
|
166
|
+
price: price,
|
167
|
+
size: size,
|
168
|
+
client_order_id: clientOid,
|
169
|
+
order_id: orderId,
|
170
|
+
newClientOid: new_client_order_id,
|
171
|
+
preset_take_profit_price: presetTakeProfitPrice,
|
172
|
+
execute_take_profit_price: executeTakeProfitPrice,
|
173
|
+
preset_stop_loss_price: presetStopLossPrice,
|
174
|
+
execute_stop_loss_price: executeStopLossPrice,
|
175
|
+
}
|
176
|
+
)
|
177
|
+
handle_response(response)
|
178
|
+
end
|
179
|
+
|
180
|
+
# I got lazy... (with specifying the args)
|
181
|
+
|
182
|
+
def trade_batch_cancel_replace_order(**args)
|
183
|
+
response = post(path: '/trade/batch-cancel-replace-order', args: args)
|
184
|
+
handle_response(response)
|
185
|
+
end
|
186
|
+
|
187
|
+
def trade_cancel_order(**args)
|
188
|
+
response = post(path: '/trade/cancel-order', args: args)
|
189
|
+
handle_response(response)
|
190
|
+
end
|
191
|
+
|
192
|
+
def trade_batch_orders(**args)
|
193
|
+
response = post(path: '/trade/batch-orders', args: args)
|
194
|
+
handle_response(response)
|
195
|
+
end
|
196
|
+
|
197
|
+
def trade_batch_cancel_order
|
198
|
+
response = post(path: '/trade/batch-cancel-order', args: args)
|
199
|
+
handle_response(response)
|
200
|
+
end
|
201
|
+
|
202
|
+
def trade_cancel_symbol_order(symbol:)
|
203
|
+
response = post(path: '/trade/cancel-symbol-order', args: {symbol: symbol})
|
204
|
+
handle_response(response)
|
205
|
+
end
|
206
|
+
|
207
|
+
def trade_order_info(**args)
|
208
|
+
response = get(path: '/trade/orderInfo', args: args)
|
209
|
+
handle_response(response)
|
210
|
+
end
|
211
|
+
|
212
|
+
def trade_unfilled_orders(**args)
|
213
|
+
response = get(path: '/trade/unfilled-orders', args: args)
|
214
|
+
handle_response(response)
|
215
|
+
end
|
216
|
+
|
217
|
+
def trade_history_orders(**args)
|
218
|
+
response = get(path: '/trade/history-orders', args: args)
|
219
|
+
handle_response(response)
|
220
|
+
end
|
221
|
+
|
222
|
+
def trade_fills(**args)
|
223
|
+
response = get(path: '/trade/fills', args: args)
|
224
|
+
handle_response(response)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Trigger
|
228
|
+
|
229
|
+
# Place Plan Order
|
230
|
+
# POST /api/v2/spot/trade/place-plan-order
|
231
|
+
|
232
|
+
# Modify Plan Order
|
233
|
+
# POST /api/v2/spot/trade/modify-plan-order
|
234
|
+
|
235
|
+
# Cancel Plan Order
|
236
|
+
# POST /api/v2/spot/trade/cancel-plan-order
|
237
|
+
|
238
|
+
# Get Current Plan Orders
|
239
|
+
# GET /api/v2/spot/trade/current-plan-order
|
240
|
+
|
241
|
+
# Get Plan Sub Order
|
242
|
+
# GET /api/v2/spot/trade/plan-sub-order
|
243
|
+
|
244
|
+
# Get History Plan Orders
|
245
|
+
# GET /api/v2/spot/trade/history-plan-order
|
246
|
+
|
247
|
+
# Cancel Plan Orders in Batch
|
248
|
+
# POST /api/v2/spot/trade/batch-cancel-plan-order
|
249
|
+
|
250
|
+
# Account
|
251
|
+
|
252
|
+
# Get Account Information
|
253
|
+
# GET /api/v2/spot/account/info
|
254
|
+
def spot_account_info
|
255
|
+
response = get(path: '/spot/account/info')
|
256
|
+
handle_response(response)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Get Account Assets
|
260
|
+
# GET /api/v2/spot/account/assets
|
261
|
+
def spot_account_assets(**args)
|
262
|
+
response = get(path: '/spot/account/assets', args: args)
|
263
|
+
handle_response(response)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Get Sub-accounts Assets
|
267
|
+
# GET /api/v2/spot/account/subaccount-assets
|
268
|
+
def spot_account_subaccount_assets(**args)
|
269
|
+
response = get(path: '/spot/account/subaccount-assets', args: args)
|
270
|
+
handle_response(response)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Modify Deposit Account
|
274
|
+
# POST /api/v2/spot/wallet/modify-deposit-account
|
275
|
+
def spot_wallet_modify_deposit_account(**args)
|
276
|
+
response = post(path: '/spot/wallet/modify-deposit-account', args: args)
|
277
|
+
handle_response(response)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Get Account Bills
|
281
|
+
# GET /api/v2/spot/account/bills
|
282
|
+
def spot_account_bills(**args)
|
283
|
+
response = get(path: '/spot/account/bills', args: args)
|
284
|
+
handle_response(response)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Transfer
|
288
|
+
# POST /api/v2/spot/wallet/transfer
|
289
|
+
def spot_wallet_transfer(**args)
|
290
|
+
response = post(path: '/spot/wallet/transfer', args: args)
|
291
|
+
handle_response(response)
|
292
|
+
end
|
293
|
+
|
294
|
+
# GET Transferable Coin List
|
295
|
+
# GET /api/v2/spot/wallet/transfer-coin-info
|
296
|
+
def spot_wallet_transfer_coin_info(**args)
|
297
|
+
response = get(path: '/spot/wallet/transfer-coin-info', args: args)
|
298
|
+
handle_response(response)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Sub Transfer
|
302
|
+
# POST /api/v2/spot/wallet/subaccount-transfer
|
303
|
+
def spot_wallet_subaccount_transer(**args)
|
304
|
+
response = post(path: '/spot/wallet/subaccount-transfer', args: args)
|
305
|
+
handle_response(response)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Withdraw
|
309
|
+
# POST /api/v2/spot/wallet/withdrawal
|
310
|
+
def spot_wallet_withdrawal(**args)
|
311
|
+
response = post(path: '/spot/wallet/withdrawal', args: args)
|
312
|
+
handle_response(response)
|
313
|
+
end
|
314
|
+
|
315
|
+
# Get MainSub Transfer Record
|
316
|
+
# GET /api/v2/spot/account/sub-main-trans-record
|
317
|
+
def spot_account_sub_main_trans_record(**args)
|
318
|
+
response = get(path: '/spot/account/sub-main-trans-record', args: args)
|
319
|
+
handle_response(response)
|
320
|
+
end
|
321
|
+
|
322
|
+
# Get Transfer Record
|
323
|
+
# GET /api/v2/spot/account/transferRecords
|
324
|
+
def spot_account_transfer_records(**args)
|
325
|
+
response = get(path: '/spot/account/transferRecords', args: args)
|
326
|
+
handle_response(response)
|
327
|
+
end
|
328
|
+
|
329
|
+
# Switch BGB Deduct
|
330
|
+
# POST /api/v2/spot/account/switch-deduct
|
331
|
+
def spot_account_switch_deduct(deduct:)
|
332
|
+
response = post(path: '/spot/account/switch-deduct', args: {deduct: deduct})
|
333
|
+
handle_response(response)
|
334
|
+
end
|
335
|
+
|
336
|
+
# Get Deposit Address
|
337
|
+
# GET /api/v2/spot/wallet/deposit-address
|
338
|
+
def spot_wallet_deposit_address(**args)
|
339
|
+
response = get(path: '/spot/wallet/deposit-address', args: args)
|
340
|
+
handle_response(response)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Get SubAccount Deposit Address
|
344
|
+
# GET /api/v2/spot/wallet/subaccount-deposit-address
|
345
|
+
def spot_wallet_subaccount_deposit_address(**args)
|
346
|
+
response = get(path: '/spot/wallet/subaccount-deposit-address', args: args)
|
347
|
+
handle_response(response)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Get BGB Deduct Info
|
351
|
+
# GET /api/v2/spot/account/deduct-info
|
352
|
+
def spot_account_deduct_info
|
353
|
+
response = get(path: '/spot/account/deduct-info')
|
354
|
+
handle_response(response)
|
355
|
+
end
|
356
|
+
|
357
|
+
# Cancel Withdrawal
|
358
|
+
# POST /api/v2/spot/wallet/cancel-withdrawal
|
359
|
+
def spot_wallet_cancel_withdrawal(order_id:)
|
360
|
+
response = post(path: '/spot/wallet/cancel-withdrawal', args: {order_id: order_id})
|
361
|
+
handle_response(response)
|
362
|
+
end
|
363
|
+
|
364
|
+
# Get SubAccount Deposit Records
|
365
|
+
# GET /api/v2/spot/wallet/subaccount-deposit-records
|
366
|
+
def spot_wallet_subaccount_deposit_records(**args)
|
367
|
+
response = get(path: '/spot/wallet/subaccount-deposit-records', args: args)
|
368
|
+
handle_response(response)
|
369
|
+
end
|
370
|
+
|
371
|
+
# Get Withdrawal Records
|
372
|
+
# GET /api/v2/spot/wallet/withdrawal-records
|
373
|
+
def spot_wallet_withdrawal_records(**args)
|
374
|
+
response = get(path: '/spot/wallet/withdrawal-records', args: args)
|
375
|
+
handle_response(response)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Get Deposit Records
|
379
|
+
# GET /api/v2/spot/wallet/deposit-records
|
380
|
+
def spot_wallet_deposit_records(**args)
|
381
|
+
response = get(path: '/spot/wallet/deposit-records', args: args)
|
382
|
+
handle_response(response)
|
383
|
+
end
|
384
|
+
|
385
|
+
private
|
386
|
+
|
387
|
+
def initialize(api_key:, api_secret:, api_passphrase:)
|
388
|
+
@api_key = api_key
|
389
|
+
@api_secret = api_secret
|
390
|
+
@api_passphrase = api_passphrase
|
391
|
+
end
|
392
|
+
|
393
|
+
def initialize(api_key:, api_secret:, api_passphrase:)
|
394
|
+
@api_key = api_key.encode('UTF-8')
|
395
|
+
@api_secret = api_secret.encode('UTF-8')
|
396
|
+
@api_passphrase = api_passphrase.encode('UTF-8')
|
397
|
+
end
|
398
|
+
|
399
|
+
def full_path(path)
|
400
|
+
self.class.path_prefix + path
|
401
|
+
end
|
402
|
+
|
403
|
+
def encoded_payload(args)
|
404
|
+
args.reject!{|k,v| v.nil?}
|
405
|
+
OpenSSL::Digest::SHA512.hexdigest(JSON.dump(args))
|
406
|
+
end
|
407
|
+
|
408
|
+
def timestamp
|
409
|
+
@timestamp ||= (Time.now.to_f * 1000).to_i.to_s
|
410
|
+
end
|
411
|
+
|
412
|
+
def message(verb:, path:, args:)
|
413
|
+
case verb
|
414
|
+
when 'GET'
|
415
|
+
if args.empty?
|
416
|
+
[timestamp, verb, full_path(path)].join
|
417
|
+
else
|
418
|
+
query_string = args.to_parameter_string
|
419
|
+
[timestamp, verb, full_path(path), '?', query_string].join
|
420
|
+
end
|
421
|
+
when 'POST'
|
422
|
+
body = args.to_json
|
423
|
+
[timestamp, verb, full_path(path), body].join
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def signature(message)
|
428
|
+
digest = OpenSSL::Digest.new('SHA256')
|
429
|
+
hmac = OpenSSL::HMAC.digest(digest, @api_secret, message)
|
430
|
+
Base64.strict_encode64(hmac)
|
431
|
+
end
|
432
|
+
|
433
|
+
def request_string(path)
|
434
|
+
"https://#{API_HOST}#{self.class.path_prefix}#{path}"
|
435
|
+
end
|
436
|
+
|
437
|
+
def headers(signature)
|
438
|
+
{
|
439
|
+
'ACCESS-KEY' => @api_key,
|
440
|
+
'ACCESS-SIGN' => signature,
|
441
|
+
'ACCESS-TIMESTAMP' => timestamp,
|
442
|
+
'ACCESS-PASSPHRASE' => @api_passphrase,
|
443
|
+
'Content-Type' => 'application/json',
|
444
|
+
'X-CHANNEL-API-CODE' => 'spot',
|
445
|
+
}
|
446
|
+
end
|
447
|
+
|
448
|
+
def log_args?(args)
|
449
|
+
!args.values.all?(&:nil?)
|
450
|
+
end
|
451
|
+
|
452
|
+
def log_request(verb:, request_string:, args:)
|
453
|
+
log_string = "#{verb} #{request_string}"
|
454
|
+
if log_args?(args)
|
455
|
+
log_string << "?#{args.x_www_form_urlencode}"
|
456
|
+
end
|
457
|
+
self.class.logger.info(log_string)
|
458
|
+
end
|
459
|
+
|
460
|
+
def log_error(code:, message:, body:)
|
461
|
+
log_string = "#{code}\n#{message}\n#{body}"
|
462
|
+
self.class.logger.error(log_string)
|
463
|
+
end
|
464
|
+
|
465
|
+
def do_request(verb:, path:, args: {})
|
466
|
+
log_request(verb: verb, request_string: request_string(path), args: args)
|
467
|
+
message = message(verb: verb, path: path, args: args)
|
468
|
+
signature = signature(message)
|
469
|
+
headers = headers(signature)
|
470
|
+
@timestamp = nil
|
471
|
+
HTTP.send(verb.to_s.downcase, request_string(path), args, headers)
|
472
|
+
end
|
473
|
+
|
474
|
+
def get(path:, args: {})
|
475
|
+
do_request(verb: 'GET', path: path, args: args)
|
476
|
+
end
|
477
|
+
|
478
|
+
def post(path:, args: {})
|
479
|
+
do_request(verb: 'POST', path: path, args: args)
|
480
|
+
end
|
481
|
+
|
482
|
+
def handle_response(response)
|
483
|
+
if response.success?
|
484
|
+
JSON.parse(response.body)
|
485
|
+
else
|
486
|
+
log_error(
|
487
|
+
code: response.code,
|
488
|
+
message: response.message,
|
489
|
+
body: response.body
|
490
|
+
)
|
491
|
+
raise Bitget::Error.new(
|
492
|
+
code: response.code,
|
493
|
+
message: response.message,
|
494
|
+
body: response.body
|
495
|
+
)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
data/lib/String/url_encode.rb
CHANGED
@@ -1,24 +1,7 @@
|
|
1
1
|
# String/url_encode.rb
|
2
2
|
# String#url_encode
|
3
3
|
|
4
|
-
#
|
5
|
-
# 0.
|
4
|
+
# 20250402
|
5
|
+
# 0.3.0
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
# Acknowledgements: I've simply ripped off and refashioned the code from the CGI module!...
|
10
|
-
|
11
|
-
# Changes since 0.1:
|
12
|
-
# 1. The url_encode and url_decode methods now are strictly demarcated.
|
13
|
-
# 0/1
|
14
|
-
# 2. A small reformat.
|
15
|
-
|
16
|
-
class String
|
17
|
-
|
18
|
-
def url_encode
|
19
|
-
self.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
20
|
-
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
21
|
-
end.tr(' ', '+')
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
7
|
+
require 'Thoran/String/UrlEncode/url_encode'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Thoran/Hash/XWwwFormUrlEncode/x_www_form_urlencode.rb
|
2
|
+
# Thoran::Hash::XWwwFormUrlEncode#x_www_form_urlencode
|
3
|
+
|
4
|
+
# 20241009
|
5
|
+
# 0.2.0
|
6
|
+
|
7
|
+
# Changes since 0.1:
|
8
|
+
# -/0: (The class name and the snake case name are consistent now.)
|
9
|
+
# 1. /XWWWFormUrlEncode/XWwwFormUrlEncode/
|
10
|
+
|
11
|
+
require 'Thoran/String/UrlEncode/url_encode'
|
12
|
+
|
13
|
+
module Thoran
|
14
|
+
module Hash
|
15
|
+
module XWwwFormUrlEncode
|
16
|
+
|
17
|
+
def x_www_form_url_encode(joiner = '&')
|
18
|
+
inject([]){|a,e| a << "#{e.first.to_s.url_encode}=#{e.last.to_s.url_encode}" unless e.last.nil?; a}.join(joiner)
|
19
|
+
end
|
20
|
+
alias_method :x_www_form_urlencode, :x_www_form_url_encode
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Hash.send(:include, Thoran::Hash::XWwwFormUrlEncode)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Thoran/String/UrlEncode/url_encode.rb
|
2
|
+
# Thoran::String::UrlEncode#url_encode
|
3
|
+
|
4
|
+
# 20160505
|
5
|
+
# 0.3.0
|
6
|
+
|
7
|
+
# Acknowledgements: I've simply ripped off and refashioned the code from the CGI module!...
|
8
|
+
|
9
|
+
# Changes since 0.2:
|
10
|
+
# 1. + Thoran namespace.
|
11
|
+
|
12
|
+
module Thoran
|
13
|
+
module String
|
14
|
+
module UrlEncode
|
15
|
+
|
16
|
+
def url_encode
|
17
|
+
self.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
18
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
19
|
+
end.tr(' ', '+')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
String.send(:include, Thoran::String::UrlEncode)
|
data/lib/bitget.rb
CHANGED
data/test/V2/client_test.rb
CHANGED
@@ -4,9 +4,9 @@ require_relative '../../lib/Bitget/V2/Client'
|
|
4
4
|
describe Bitget::V2::Client do
|
5
5
|
let(:client) do
|
6
6
|
Bitget::V2::Client.new(
|
7
|
-
api_key:
|
8
|
-
api_secret:
|
9
|
-
api_passphrase:
|
7
|
+
api_key: ENV.fetch('BITGET_API_KEY', '<API_KEY>'),
|
8
|
+
api_secret: ENV.fetch('BITGET_API_SECRET', '<API_SECRET>'),
|
9
|
+
api_passphrase: ENV.fetch('BITGET_API_PASSPHRASE', '<API_PASSPHRASE>')
|
10
10
|
)
|
11
11
|
end
|
12
12
|
|
@@ -50,4 +50,60 @@ describe Bitget::V2::Client do
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
describe "#spot_account_info" do
|
55
|
+
it "retrieves account information" do
|
56
|
+
VCR.use_cassette('v2/spot/account/info') do
|
57
|
+
response = client.spot_account_info
|
58
|
+
_(response).must_include('data')
|
59
|
+
_(response['data']).must_include('userId')
|
60
|
+
_(response['data']).must_include('channelCode')
|
61
|
+
_(response['data']).must_include('authorities')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when an error occurs" do
|
66
|
+
it "logs then raises an error" do
|
67
|
+
VCR.use_cassette('v2/spot/account/info-when_an_error_occurs') do
|
68
|
+
assert_raises(Bitget::Error) do
|
69
|
+
mocked_method = Minitest::Mock.new
|
70
|
+
mocked_method.expect(:call, nil, [], code: '418', message: "I'm a teapot", body: '')
|
71
|
+
client.stub(:log_error, mocked_method) do
|
72
|
+
client.spot_account_info
|
73
|
+
end
|
74
|
+
mocked_method.verify
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#spot_account_assets" do
|
82
|
+
it "retrieves account assets" do
|
83
|
+
VCR.use_cassette('v2/spot/account/assets') do
|
84
|
+
response = client.spot_account_assets
|
85
|
+
_(response).must_include('data')
|
86
|
+
_(response['data'].first).must_include('coin')
|
87
|
+
_(response['data'].first).must_include('available')
|
88
|
+
_(response['data'].first).must_include('frozen')
|
89
|
+
_(response['data'].first).must_include('locked')
|
90
|
+
_(response['data'].first).must_include('uTime')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when an error occurs" do
|
95
|
+
it "logs then raises an error" do
|
96
|
+
VCR.use_cassette('v2/spot/account/assets-when_an_error_occurs') do
|
97
|
+
assert_raises(Bitget::Error) do
|
98
|
+
mocked_method = Minitest::Mock.new
|
99
|
+
mocked_method.expect(:call, nil, [], code: '418', message: "I'm a teapot", body: '')
|
100
|
+
client.stub(:log_error, mocked_method) do
|
101
|
+
client.spot_account_assets
|
102
|
+
end
|
103
|
+
mocked_method.verify
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
53
109
|
end
|
data/test/client_test.rb
CHANGED
@@ -4,9 +4,9 @@ require_relative '../lib/Bitget/Client'
|
|
4
4
|
describe Bitget::Client do
|
5
5
|
let(:client) do
|
6
6
|
Bitget::Client.new(
|
7
|
-
api_key:
|
8
|
-
api_secret:
|
9
|
-
api_passphrase:
|
7
|
+
api_key: ENV.fetch('BITGET_API_KEY', '<API_KEY>'),
|
8
|
+
api_secret: ENV.fetch('BITGET_API_SECRET', '<API_SECRET>'),
|
9
|
+
api_passphrase: ENV.fetch('BITGET_API_PASSPHRASE', '<API_PASSPHRASE>')
|
10
10
|
)
|
11
11
|
end
|
12
12
|
|
@@ -50,4 +50,60 @@ describe Bitget::Client do
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
describe "#spot_account_info" do
|
55
|
+
it "retrieves account information" do
|
56
|
+
VCR.use_cassette('v2/spot/account/info') do
|
57
|
+
response = client.spot_account_info
|
58
|
+
_(response).must_include('data')
|
59
|
+
_(response['data']).must_include('userId')
|
60
|
+
_(response['data']).must_include('channelCode')
|
61
|
+
_(response['data']).must_include('authorities')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when an error occurs" do
|
66
|
+
it "logs then raises an error" do
|
67
|
+
VCR.use_cassette('v2/spot/account/info-when_an_error_occurs') do
|
68
|
+
assert_raises(Bitget::Error) do
|
69
|
+
mocked_method = Minitest::Mock.new
|
70
|
+
mocked_method.expect(:call, nil, [], code: '418', message: "I'm a teapot", body: '')
|
71
|
+
client.stub(:log_error, mocked_method) do
|
72
|
+
client.spot_account_info
|
73
|
+
end
|
74
|
+
mocked_method.verify
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#spot_account_assets" do
|
82
|
+
it "retrieves account assets" do
|
83
|
+
VCR.use_cassette('v2/spot/account/assets') do
|
84
|
+
response = client.spot_account_assets
|
85
|
+
_(response).must_include('data')
|
86
|
+
_(response['data'].first).must_include('coin')
|
87
|
+
_(response['data'].first).must_include('available')
|
88
|
+
_(response['data'].first).must_include('frozen')
|
89
|
+
_(response['data'].first).must_include('locked')
|
90
|
+
_(response['data'].first).must_include('uTime')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when an error occurs" do
|
95
|
+
it "logs then raises an error" do
|
96
|
+
VCR.use_cassette('v2/spot/account/assets-when_an_error_occurs') do
|
97
|
+
assert_raises(Bitget::Error) do
|
98
|
+
mocked_method = Minitest::Mock.new
|
99
|
+
mocked_method.expect(:call, nil, [], code: '418', message: "I'm a teapot", body: '')
|
100
|
+
client.stub(:log_error, mocked_method) do
|
101
|
+
client.spot_account_assets
|
102
|
+
end
|
103
|
+
mocked_method.verify
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
53
109
|
end
|
data/test/helper.rb
CHANGED
@@ -1,34 +1,13 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require 'minitest-spec-context'
|
3
3
|
require 'minitest/spec'
|
4
|
-
require 'ostruct'
|
5
4
|
require 'vcr'
|
6
5
|
|
7
6
|
VCR.configure do |config|
|
8
7
|
config.cassette_library_dir = File.expand_path('../fixtures/vcr_cassettes', __FILE__)
|
9
8
|
config.hook_into :webmock
|
10
|
-
end
|
11
|
-
|
12
|
-
def api_key
|
13
|
-
if ENV['BITGET_API_KEY']
|
14
|
-
ENV['BITGET_API_KEY']
|
15
|
-
else
|
16
|
-
'api_key0'
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def api_secret
|
21
|
-
if ENV['BITGET_API_SECRET']
|
22
|
-
ENV['BITGET_API_SECRET']
|
23
|
-
else
|
24
|
-
'api_secret0'
|
25
|
-
end
|
26
|
-
end
|
27
9
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
'api_passphrase0'
|
33
|
-
end
|
10
|
+
config.filter_sensitive_data('<API_KEY>'){ENV['BITGET_API_KEY']}
|
11
|
+
config.filter_sensitive_data('<API_SECRET>'){ENV['BITGET_API_SECRET'] }
|
12
|
+
config.filter_sensitive_data('<API_PASSPHRASE>'){ENV['BITGET_API_PASSPHRASE']}
|
34
13
|
end
|
data/test/test_all.rb
ADDED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitget.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- thoran
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-05-16 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: http.rb
|
@@ -35,13 +35,18 @@ files:
|
|
35
35
|
- lib/Bitget/Client.rb
|
36
36
|
- lib/Bitget/Error.rb
|
37
37
|
- lib/Bitget/V2/Client.rb
|
38
|
+
- lib/Bitget/V2/Client2.rb
|
39
|
+
- lib/Bitget/VERSION.rb
|
38
40
|
- lib/Hash/to_parameter_string.rb
|
39
41
|
- lib/Hash/x_www_form_urlencode.rb
|
40
42
|
- lib/String/url_encode.rb
|
43
|
+
- lib/Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb
|
44
|
+
- lib/Thoran/String/UrlEncode/url_encode.rb
|
41
45
|
- lib/bitget.rb
|
42
46
|
- test/V2/client_test.rb
|
43
47
|
- test/client_test.rb
|
44
48
|
- test/helper.rb
|
49
|
+
- test/test_all.rb
|
45
50
|
homepage: http://github.com/thoran/bitget.rb
|
46
51
|
licenses:
|
47
52
|
- Ruby
|
@@ -60,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
65
|
- !ruby/object:Gem::Version
|
61
66
|
version: '0'
|
62
67
|
requirements: []
|
63
|
-
rubygems_version: 3.6.
|
68
|
+
rubygems_version: 3.6.8
|
64
69
|
specification_version: 4
|
65
70
|
summary: Access the Bitget API with Ruby.
|
66
71
|
test_files: []
|