honeymaker 0.1.0 → 0.2.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/honeymaker-0.1.0.gem +0 -0
- data/lib/honeymaker/client.rb +74 -0
- data/lib/honeymaker/clients/binance.rb +240 -0
- data/lib/honeymaker/clients/binance_us.rb +9 -0
- data/lib/honeymaker/clients/bingx.rb +105 -0
- data/lib/honeymaker/clients/bitget.rb +128 -0
- data/lib/honeymaker/clients/bitmart.rb +117 -0
- data/lib/honeymaker/clients/bitrue.rb +114 -0
- data/lib/honeymaker/clients/bitvavo.rb +136 -0
- data/lib/honeymaker/clients/bybit.rb +136 -0
- data/lib/honeymaker/clients/coinbase.rb +184 -0
- data/lib/honeymaker/clients/gemini.rb +81 -0
- data/lib/honeymaker/clients/hyperliquid.rb +42 -0
- data/lib/honeymaker/clients/kraken.rb +132 -0
- data/lib/honeymaker/clients/kucoin.rb +147 -0
- data/lib/honeymaker/clients/mexc.rb +136 -0
- data/lib/honeymaker/exchanges/binance.rb +11 -17
- data/lib/honeymaker/exchanges/bitget.rb +2 -2
- data/lib/honeymaker/exchanges/bitrue.rb +6 -16
- data/lib/honeymaker/exchanges/bitvavo.rb +2 -2
- data/lib/honeymaker/exchanges/bybit.rb +2 -2
- data/lib/honeymaker/exchanges/coinbase.rb +5 -9
- data/lib/honeymaker/exchanges/gemini.rb +4 -6
- data/lib/honeymaker/exchanges/kraken.rb +5 -9
- data/lib/honeymaker/exchanges/kucoin.rb +2 -2
- data/lib/honeymaker/exchanges/mexc.rb +11 -17
- data/lib/honeymaker/utils.rb +9 -0
- data/lib/honeymaker/version.rb +1 -1
- data/lib/honeymaker.rb +38 -0
- data/test/fixtures/bingx_symbols.json +26 -0
- data/test/fixtures/bitget_symbols.json +28 -0
- data/test/fixtures/bitmart_symbols.json +26 -0
- data/test/fixtures/bitrue_exchange_info.json +34 -0
- data/test/fixtures/bitvavo_markets.json +16 -0
- data/test/fixtures/bybit_instruments.json +23 -0
- data/test/fixtures/coinbase_products.json +24 -0
- data/test/fixtures/gemini_symbol_detail.json +9 -0
- data/test/fixtures/gemini_symbols.json +1 -0
- data/test/fixtures/hyperliquid_spot_meta.json +12 -0
- data/test/fixtures/kucoin_symbols.json +17 -0
- data/test/fixtures/mexc_exchange_info.json +40 -0
- data/test/honeymaker/client_test.rb +53 -0
- data/test/honeymaker/clients/binance_client_test.rb +80 -0
- data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
- data/test/honeymaker/clients/bingx_client_test.rb +64 -0
- data/test/honeymaker/clients/bitget_client_test.rb +85 -0
- data/test/honeymaker/clients/bitmart_client_test.rb +78 -0
- data/test/honeymaker/clients/bitrue_client_test.rb +63 -0
- data/test/honeymaker/clients/bitvavo_client_test.rb +87 -0
- data/test/honeymaker/clients/bybit_client_test.rb +84 -0
- data/test/honeymaker/clients/coinbase_client_test.rb +118 -0
- data/test/honeymaker/clients/gemini_client_test.rb +71 -0
- data/test/honeymaker/clients/honeymaker_client_registry_test.rb +44 -0
- data/test/honeymaker/clients/hyperliquid_client_test.rb +53 -0
- data/test/honeymaker/clients/kraken_client_test.rb +70 -0
- data/test/honeymaker/clients/kucoin_client_test.rb +88 -0
- data/test/honeymaker/clients/mexc_client_test.rb +75 -0
- data/test/honeymaker/exchanges/binance_us_test.rb +40 -0
- data/test/honeymaker/exchanges/bingx_test.rb +53 -0
- data/test/honeymaker/exchanges/bitget_test.rb +52 -0
- data/test/honeymaker/exchanges/bitmart_test.rb +52 -0
- data/test/honeymaker/exchanges/bitrue_test.rb +53 -0
- data/test/honeymaker/exchanges/bitvavo_test.rb +52 -0
- data/test/honeymaker/exchanges/bybit_test.rb +43 -0
- data/test/honeymaker/exchanges/coinbase_test.rb +52 -0
- data/test/honeymaker/exchanges/gemini_test.rb +48 -0
- data/test/honeymaker/exchanges/hyperliquid_test.rb +52 -0
- data/test/honeymaker/exchanges/kucoin_test.rb +43 -0
- data/test/honeymaker/exchanges/mexc_test.rb +64 -0
- data/test/honeymaker/utils_test.rb +38 -0
- data/test/test_helper.rb +1 -0
- metadata +74 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class Bitrue < Client
|
|
6
|
+
URL = "https://openapi.bitrue.com"
|
|
7
|
+
|
|
8
|
+
def exchange_information
|
|
9
|
+
get_public("/api/v1/exchangeInfo")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def symbol_price_ticker(symbol: nil)
|
|
13
|
+
get_public("/api/v1/ticker/price", { symbol: symbol })
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def symbol_order_book_ticker(symbol: nil)
|
|
17
|
+
get_public("/api/v1/ticker/bookTicker", { symbol: symbol })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def candlestick_data(symbol:, interval:, start_time: nil, end_time: nil, limit: 500)
|
|
21
|
+
get_public("/api/v1/market/kline", {
|
|
22
|
+
symbol: symbol, scale: interval, startTime: start_time,
|
|
23
|
+
endTime: end_time, limit: limit
|
|
24
|
+
})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def account_information(recv_window: 5000)
|
|
28
|
+
get_signed("/api/v1/account", { recvWindow: recv_window })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def query_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
|
|
32
|
+
get_signed("/api/v1/order", {
|
|
33
|
+
symbol: symbol, orderId: order_id,
|
|
34
|
+
origClientOrderId: orig_client_order_id, recvWindow: recv_window
|
|
35
|
+
})
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def new_order(symbol:, side:, type:, time_in_force: nil, quantity: nil, quote_order_qty: nil,
|
|
39
|
+
price: nil, new_client_order_id: nil, recv_window: 5000)
|
|
40
|
+
post_signed("/api/v1/order", {
|
|
41
|
+
symbol: symbol, side: side, type: type,
|
|
42
|
+
timeInForce: time_in_force, quantity: quantity,
|
|
43
|
+
quoteOrderQty: quote_order_qty, price: price,
|
|
44
|
+
newClientOrderId: new_client_order_id, recvWindow: recv_window
|
|
45
|
+
})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cancel_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
|
|
49
|
+
delete_signed("/api/v1/order", {
|
|
50
|
+
symbol: symbol, orderId: order_id,
|
|
51
|
+
origClientOrderId: orig_client_order_id, recvWindow: recv_window
|
|
52
|
+
})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def get_public(path, params = {})
|
|
58
|
+
with_rescue do
|
|
59
|
+
response = connection.get do |req|
|
|
60
|
+
req.url path
|
|
61
|
+
req.params = params.compact
|
|
62
|
+
end
|
|
63
|
+
response.body
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_signed(path, params = {})
|
|
68
|
+
with_rescue do
|
|
69
|
+
response = connection.get do |req|
|
|
70
|
+
req.url path
|
|
71
|
+
req.headers = auth_headers
|
|
72
|
+
req.params = params.compact.merge(timestamp: timestamp_ms)
|
|
73
|
+
req.params[:signature] = sign_params(req.params)
|
|
74
|
+
end
|
|
75
|
+
response.body
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def post_signed(path, params = {})
|
|
80
|
+
with_rescue do
|
|
81
|
+
response = connection.post do |req|
|
|
82
|
+
req.url path
|
|
83
|
+
req.headers = auth_headers
|
|
84
|
+
req.params = params.compact.merge(timestamp: timestamp_ms)
|
|
85
|
+
req.params[:signature] = sign_params(req.params)
|
|
86
|
+
end
|
|
87
|
+
response.body
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def delete_signed(path, params = {})
|
|
92
|
+
with_rescue do
|
|
93
|
+
response = connection.delete do |req|
|
|
94
|
+
req.url path
|
|
95
|
+
req.headers = auth_headers
|
|
96
|
+
req.params = params.compact.merge(timestamp: timestamp_ms)
|
|
97
|
+
req.params[:signature] = sign_params(req.params)
|
|
98
|
+
end
|
|
99
|
+
response.body
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def auth_headers
|
|
104
|
+
{ "X-MBX-APIKEY": @api_key, Accept: "application/json", "Content-Type": "application/json" }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def sign_params(params)
|
|
108
|
+
return unless @api_secret
|
|
109
|
+
query = Faraday::Utils.build_query(params)
|
|
110
|
+
hmac_sha256(@api_secret, query)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class Bitvavo < Client
|
|
6
|
+
URL = "https://api.bitvavo.com"
|
|
7
|
+
ACCESS_WINDOW = "10000"
|
|
8
|
+
|
|
9
|
+
def get_assets
|
|
10
|
+
get_public("/v2/assets")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get_markets(market: nil)
|
|
14
|
+
get_public("/v2/markets", { market: market })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_ticker_price(market: nil)
|
|
18
|
+
get_public("/v2/ticker/price", { market: market })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_ticker_book(market: nil)
|
|
22
|
+
get_public("/v2/ticker/book", { market: market })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get_candles(market:, interval:, start_time: nil, end_time: nil, limit: nil)
|
|
26
|
+
get_public("/v2/#{market}/candles", {
|
|
27
|
+
interval: interval, start: start_time, end: end_time, limit: limit
|
|
28
|
+
})
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_balance(symbol: nil)
|
|
32
|
+
get_signed("/v2/balance", { symbol: symbol })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def place_order(market:, side:, order_type:, amount: nil, amount_quote: nil, price: nil,
|
|
36
|
+
time_in_force: nil, client_order_id: nil)
|
|
37
|
+
post_signed("/v2/order", {
|
|
38
|
+
market: market, side: side, orderType: order_type,
|
|
39
|
+
amount: amount, amountQuote: amount_quote, price: price,
|
|
40
|
+
timeInForce: time_in_force, clientOrderId: client_order_id
|
|
41
|
+
})
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_order(market:, order_id:)
|
|
45
|
+
get_signed("/v2/order", { market: market, orderId: order_id })
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cancel_order(market:, order_id:)
|
|
49
|
+
delete_signed("/v2/order", { market: market, orderId: order_id })
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def withdraw(symbol:, amount:, address:, payment_id: nil)
|
|
53
|
+
post_signed("/v2/withdrawal", {
|
|
54
|
+
symbol: symbol, amount: amount, address: address, paymentId: payment_id
|
|
55
|
+
})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def get_public(path, params = {})
|
|
61
|
+
with_rescue do
|
|
62
|
+
response = connection.get do |req|
|
|
63
|
+
req.url path
|
|
64
|
+
req.headers = unauthenticated_headers
|
|
65
|
+
req.params = params.compact
|
|
66
|
+
end
|
|
67
|
+
response.body
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def get_signed(path, params = {})
|
|
72
|
+
with_rescue do
|
|
73
|
+
params = params.compact
|
|
74
|
+
query_string = params.any? ? "?#{Faraday::Utils.build_query(params)}" : ""
|
|
75
|
+
ts = timestamp_ms.to_s
|
|
76
|
+
payload = "#{ts}GET#{path}#{query_string}"
|
|
77
|
+
|
|
78
|
+
response = connection.get do |req|
|
|
79
|
+
req.url path
|
|
80
|
+
req.headers = signed_headers(ts, payload)
|
|
81
|
+
req.params = params
|
|
82
|
+
end
|
|
83
|
+
response.body
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def post_signed(path, body = {})
|
|
88
|
+
with_rescue do
|
|
89
|
+
body = body.compact
|
|
90
|
+
body_string = body.to_json
|
|
91
|
+
ts = timestamp_ms.to_s
|
|
92
|
+
payload = "#{ts}POST#{path}#{body_string}"
|
|
93
|
+
|
|
94
|
+
response = connection.post do |req|
|
|
95
|
+
req.url path
|
|
96
|
+
req.headers = signed_headers(ts, payload)
|
|
97
|
+
req.body = body
|
|
98
|
+
end
|
|
99
|
+
response.body
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def delete_signed(path, params = {})
|
|
104
|
+
with_rescue do
|
|
105
|
+
params = params.compact
|
|
106
|
+
query_string = params.any? ? "?#{Faraday::Utils.build_query(params)}" : ""
|
|
107
|
+
ts = timestamp_ms.to_s
|
|
108
|
+
payload = "#{ts}DELETE#{path}#{query_string}"
|
|
109
|
+
|
|
110
|
+
response = connection.delete do |req|
|
|
111
|
+
req.url path
|
|
112
|
+
req.headers = signed_headers(ts, payload)
|
|
113
|
+
req.params = params
|
|
114
|
+
end
|
|
115
|
+
response.body
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def unauthenticated_headers
|
|
120
|
+
{ Accept: "application/json", "Content-Type": "application/json" }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def signed_headers(timestamp, payload)
|
|
124
|
+
signature = hmac_sha256(@api_secret, payload)
|
|
125
|
+
{
|
|
126
|
+
"BITVAVO-ACCESS-KEY": @api_key,
|
|
127
|
+
"BITVAVO-ACCESS-SIGNATURE": signature,
|
|
128
|
+
"BITVAVO-ACCESS-TIMESTAMP": timestamp,
|
|
129
|
+
"BITVAVO-ACCESS-WINDOW": ACCESS_WINDOW,
|
|
130
|
+
Accept: "application/json",
|
|
131
|
+
"Content-Type": "application/json"
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class Bybit < Client
|
|
6
|
+
URL = "https://api.bybit.com"
|
|
7
|
+
RECV_WINDOW = "5000"
|
|
8
|
+
|
|
9
|
+
def get_coin_query_info
|
|
10
|
+
get_authenticated("/v5/asset/coin/query-info")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def instruments_info(category:, symbol: nil, status: nil, base_coin: nil, limit: nil, cursor: nil)
|
|
14
|
+
get_public("/v5/market/instruments-info", {
|
|
15
|
+
category: category, symbol: symbol, status: status,
|
|
16
|
+
baseCoin: base_coin, limit: limit, cursor: cursor
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tickers(category:, symbol: nil, base_coin: nil, exp_date: nil)
|
|
21
|
+
get_public("/v5/market/tickers", {
|
|
22
|
+
category: category, symbol: symbol, baseCoin: base_coin, expDate: exp_date
|
|
23
|
+
})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def orderbook(category:, symbol:, limit: nil)
|
|
27
|
+
get_public("/v5/market/orderbook", { category: category, symbol: symbol, limit: limit })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def kline(category:, symbol:, interval:, start: nil, end_time: nil, limit: nil)
|
|
31
|
+
get_public("/v5/market/kline", {
|
|
32
|
+
category: category, symbol: symbol, interval: interval,
|
|
33
|
+
start: start, end: end_time, limit: limit
|
|
34
|
+
})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def wallet_balance(account_type:, coin: nil)
|
|
38
|
+
get_authenticated("/v5/account/wallet-balance", { accountType: account_type, coin: coin })
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_order(category:, order_id: nil, symbol: nil, order_link_id: nil)
|
|
42
|
+
get_authenticated("/v5/order/realtime", {
|
|
43
|
+
category: category, orderId: order_id, symbol: symbol, orderLinkId: order_link_id
|
|
44
|
+
})
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_order(category:, symbol:, side:, order_type:, qty:, price: nil,
|
|
48
|
+
time_in_force: nil, market_unit: nil, order_link_id: nil)
|
|
49
|
+
post_authenticated("/v5/order/create", {
|
|
50
|
+
category: category, symbol: symbol, side: side,
|
|
51
|
+
orderType: order_type, qty: qty, price: price,
|
|
52
|
+
timeInForce: time_in_force, marketUnit: market_unit,
|
|
53
|
+
orderLinkId: order_link_id
|
|
54
|
+
})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def cancel_order(category:, symbol:, order_id: nil, order_link_id: nil)
|
|
58
|
+
post_authenticated("/v5/order/cancel", {
|
|
59
|
+
category: category, symbol: symbol,
|
|
60
|
+
orderId: order_id, orderLinkId: order_link_id
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def withdraw(coin:, chain:, address:, amount:, tag: nil, force_chain: nil)
|
|
65
|
+
post_authenticated("/v5/asset/withdraw/create", {
|
|
66
|
+
coin: coin, chain: chain, address: address, amount: amount,
|
|
67
|
+
tag: tag, forceChain: force_chain, timestamp: timestamp_ms
|
|
68
|
+
})
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def get_public(path, params = {})
|
|
74
|
+
with_rescue do
|
|
75
|
+
response = connection.get do |req|
|
|
76
|
+
req.url path
|
|
77
|
+
req.headers = unauthenticated_headers
|
|
78
|
+
req.params = params.compact
|
|
79
|
+
end
|
|
80
|
+
response.body
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def get_authenticated(path, params = {})
|
|
85
|
+
with_rescue do
|
|
86
|
+
params = params.compact
|
|
87
|
+
response = connection.get do |req|
|
|
88
|
+
req.url path
|
|
89
|
+
req.headers = signed_headers("GET", params)
|
|
90
|
+
req.params = params
|
|
91
|
+
end
|
|
92
|
+
response.body
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def post_authenticated(path, body = {})
|
|
97
|
+
with_rescue do
|
|
98
|
+
body = body.compact
|
|
99
|
+
response = connection.post do |req|
|
|
100
|
+
req.url path
|
|
101
|
+
req.headers = signed_headers("POST", body)
|
|
102
|
+
req.body = body
|
|
103
|
+
end
|
|
104
|
+
response.body
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def unauthenticated_headers
|
|
109
|
+
{ Accept: "application/json", "Content-Type": "application/json" }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def signed_headers(method, params_or_body)
|
|
113
|
+
return unauthenticated_headers unless authenticated?
|
|
114
|
+
|
|
115
|
+
ts = timestamp_ms
|
|
116
|
+
payload = if method == "GET"
|
|
117
|
+
query_string = Faraday::Utils.build_query(params_or_body)
|
|
118
|
+
"#{ts}#{@api_key}#{RECV_WINDOW}#{query_string}"
|
|
119
|
+
else
|
|
120
|
+
"#{ts}#{@api_key}#{RECV_WINDOW}#{params_or_body.to_json}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
signature = hmac_sha256(@api_secret, payload)
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
"X-BAPI-API-KEY": @api_key,
|
|
127
|
+
"X-BAPI-SIGN": signature,
|
|
128
|
+
"X-BAPI-TIMESTAMP": ts.to_s,
|
|
129
|
+
"X-BAPI-RECV-WINDOW": RECV_WINDOW,
|
|
130
|
+
Accept: "application/json",
|
|
131
|
+
"Content-Type": "application/json"
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jwt"
|
|
4
|
+
|
|
5
|
+
module Honeymaker
|
|
6
|
+
module Clients
|
|
7
|
+
class Coinbase < Client
|
|
8
|
+
URL = "https://api.coinbase.com"
|
|
9
|
+
|
|
10
|
+
def initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil)
|
|
11
|
+
super
|
|
12
|
+
@api_secret = @api_secret&.gsub('\n', "\n")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_order(order_id:)
|
|
16
|
+
get("/api/v3/brokerage/orders/historical/#{order_id}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def list_orders(order_ids: nil, product_ids: nil, product_type: nil, order_status: nil,
|
|
20
|
+
time_in_forces: nil, order_types: nil, order_side: nil, start_date: nil,
|
|
21
|
+
end_date: nil, order_placement_source: nil, contract_expiry_type: nil,
|
|
22
|
+
asset_filters: nil, limit: nil, cursor: nil, sort_by: nil)
|
|
23
|
+
with_rescue do
|
|
24
|
+
response = connection.get do |req|
|
|
25
|
+
req.url "/api/v3/brokerage/orders/historical/batch"
|
|
26
|
+
req.headers = auth_headers(req)
|
|
27
|
+
req.params = {
|
|
28
|
+
order_ids: order_ids, product_ids: product_ids, product_type: product_type,
|
|
29
|
+
order_status: order_status, time_in_forces: time_in_forces,
|
|
30
|
+
order_types: order_types, order_side: order_side,
|
|
31
|
+
start_date: start_date, end_date: end_date,
|
|
32
|
+
order_placement_source: order_placement_source,
|
|
33
|
+
contract_expiry_type: contract_expiry_type,
|
|
34
|
+
asset_filters: asset_filters, limit: limit, cursor: cursor, sort_by: sort_by
|
|
35
|
+
}.compact
|
|
36
|
+
req.options.params_encoder = Faraday::FlatParamsEncoder
|
|
37
|
+
end
|
|
38
|
+
response.body
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def create_order(client_order_id:, product_id:, side:, order_configuration:)
|
|
43
|
+
post("/api/v3/brokerage/orders", {
|
|
44
|
+
client_order_id: client_order_id, product_id: product_id,
|
|
45
|
+
side: side, order_configuration: order_configuration
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def cancel_orders(order_ids:)
|
|
50
|
+
post("/api/v3/brokerage/orders/batch_cancel", { order_ids: order_ids })
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def list_public_products(limit: nil, offset: nil, product_type: nil, product_ids: nil,
|
|
54
|
+
contract_expiry_type: nil, expiring_contract_status: nil,
|
|
55
|
+
get_tradability_status: nil, get_all_products: nil)
|
|
56
|
+
get("/api/v3/brokerage/market/products", {
|
|
57
|
+
limit: limit, offset: offset, product_type: product_type, product_ids: product_ids,
|
|
58
|
+
contract_expiry_type: contract_expiry_type,
|
|
59
|
+
expiring_contract_status: expiring_contract_status,
|
|
60
|
+
get_tradability_status: get_tradability_status, get_all_products: get_all_products
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_public_product(product_id:, get_tradability_status: nil)
|
|
65
|
+
get("/api/v3/brokerage/market/products/#{product_id}", { get_tradability_status: get_tradability_status })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_public_product_book(product_id:, limit: nil, aggregation_price_increment: nil)
|
|
69
|
+
with_rescue do
|
|
70
|
+
response = connection.get do |req|
|
|
71
|
+
req.url "/api/v3/brokerage/market/product_book"
|
|
72
|
+
req.headers = auth_headers(req)
|
|
73
|
+
req.params = {
|
|
74
|
+
product_id: product_id, limit: limit,
|
|
75
|
+
aggregation_price_increment: aggregation_price_increment
|
|
76
|
+
}.compact
|
|
77
|
+
req.options.params_encoder = Faraday::FlatParamsEncoder
|
|
78
|
+
end
|
|
79
|
+
response.body
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def get_public_product_candles(product_id:, start_time:, end_time:, granularity:, limit: nil)
|
|
84
|
+
get("/api/v3/brokerage/market/products/#{product_id}/candles", {
|
|
85
|
+
start: start_time, end: end_time, granularity: granularity, limit: limit
|
|
86
|
+
})
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def get_api_key_permissions
|
|
90
|
+
get("/api/v3/brokerage/key_permissions")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def list_accounts
|
|
94
|
+
get("/api/v3/brokerage/accounts")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def list_portfolios
|
|
98
|
+
get("/api/v3/brokerage/portfolios")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def get_portfolio_breakdown(portfolio_uuid:, currency: nil)
|
|
102
|
+
get("/api/v3/brokerage/portfolios/#{portfolio_uuid}", { currency: currency })
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def send_money(account_id:, to:, amount:, currency:, idem: nil)
|
|
106
|
+
post("/v2/accounts/#{account_id}/transactions", {
|
|
107
|
+
type: "send", to: to, amount: amount, currency: currency, idem: idem
|
|
108
|
+
})
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def get(path, params = {})
|
|
114
|
+
with_rescue do
|
|
115
|
+
response = connection.get do |req|
|
|
116
|
+
req.url path
|
|
117
|
+
req.headers = auth_headers(req)
|
|
118
|
+
req.params = params.compact
|
|
119
|
+
end
|
|
120
|
+
response.body
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def post(path, body = {})
|
|
125
|
+
with_rescue do
|
|
126
|
+
response = connection.post do |req|
|
|
127
|
+
req.url path
|
|
128
|
+
req.headers = auth_headers(req)
|
|
129
|
+
req.body = body.compact
|
|
130
|
+
end
|
|
131
|
+
response.body
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def auth_headers(req)
|
|
136
|
+
return unauthenticated_headers unless authenticated?
|
|
137
|
+
|
|
138
|
+
timestamp = Time.now.utc.to_i
|
|
139
|
+
method = req.http_method.to_s.upcase
|
|
140
|
+
request_host = URI(URL).host
|
|
141
|
+
request_path = req.path
|
|
142
|
+
|
|
143
|
+
jwt_payload = {
|
|
144
|
+
sub: @api_key,
|
|
145
|
+
iss: "coinbase-cloud",
|
|
146
|
+
nbf: timestamp,
|
|
147
|
+
exp: timestamp + 120,
|
|
148
|
+
uri: "#{method} #{request_host}#{request_path}"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
signing_key = ecdsa_key? ? ecdsa_signing_key : ed25519_signing_key
|
|
152
|
+
return unauthenticated_headers if signing_key.nil?
|
|
153
|
+
|
|
154
|
+
algorithm = ecdsa_key? ? "ES256" : "EdDSA"
|
|
155
|
+
jwt = JWT.encode(jwt_payload, signing_key, algorithm, { kid: @api_key, nonce: SecureRandom.hex })
|
|
156
|
+
|
|
157
|
+
{ Authorization: "Bearer #{jwt}", Accept: "application/json", "Content-Type": "application/json" }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def unauthenticated_headers
|
|
161
|
+
{ Accept: "application/json", "Content-Type": "application/json" }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def ecdsa_key?
|
|
165
|
+
@api_secret&.start_with?("-----BEGIN EC PRIVATE KEY-----")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def ecdsa_signing_key
|
|
169
|
+
OpenSSL::PKey::EC.new(@api_secret)
|
|
170
|
+
rescue OpenSSL::PKey::ECError
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def ed25519_signing_key
|
|
175
|
+
require "rbnacl"
|
|
176
|
+
decoded_key = Base64.decode64(@api_secret)
|
|
177
|
+
seed = decoded_key[0...32]
|
|
178
|
+
RbNaCl::Signatures::Ed25519::SigningKey.new(seed)
|
|
179
|
+
rescue LoadError, RbNaCl::LengthError
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class Gemini < Client
|
|
6
|
+
URL = "https://api.gemini.com"
|
|
7
|
+
|
|
8
|
+
def get_symbols
|
|
9
|
+
get_public("/v1/symbols")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_symbol_details(symbol:)
|
|
13
|
+
get_public("/v1/symbols/details/#{symbol}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_ticker(symbol:)
|
|
17
|
+
get_public("/v1/pubticker/#{symbol}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_candles(symbol:, time_frame:)
|
|
21
|
+
get_public("/v2/candles/#{symbol}/#{time_frame}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get_balances
|
|
25
|
+
post_signed("/v1/balances")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def new_order(symbol:, amount:, price:, side:, type:, client_order_id: nil, options: [])
|
|
29
|
+
post_signed("/v1/order/new", {
|
|
30
|
+
symbol: symbol, amount: amount, price: price,
|
|
31
|
+
side: side, type: type, client_order_id: client_order_id,
|
|
32
|
+
options: options
|
|
33
|
+
})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def order_status(order_id:)
|
|
37
|
+
post_signed("/v1/order/status", { order_id: order_id })
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cancel_order(order_id:)
|
|
41
|
+
post_signed("/v1/order/cancel", { order_id: order_id })
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def withdraw(currency:, address:, amount:)
|
|
45
|
+
post_signed("/v1/withdraw/#{currency}", { address: address, amount: amount })
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def get_public(path, params = {})
|
|
51
|
+
with_rescue do
|
|
52
|
+
response = connection.get do |req|
|
|
53
|
+
req.url path
|
|
54
|
+
req.params = params.compact
|
|
55
|
+
end
|
|
56
|
+
response.body
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def post_signed(path, body = {})
|
|
61
|
+
with_rescue do
|
|
62
|
+
payload = body.compact.merge(request: path, nonce: timestamp_ms.to_s)
|
|
63
|
+
encoded_payload = Base64.strict_encode64(payload.to_json)
|
|
64
|
+
signature = hmac_sha256(@api_secret, encoded_payload)
|
|
65
|
+
|
|
66
|
+
response = connection.post do |req|
|
|
67
|
+
req.url path
|
|
68
|
+
req.headers = {
|
|
69
|
+
"X-GEMINI-APIKEY": @api_key,
|
|
70
|
+
"X-GEMINI-PAYLOAD": encoded_payload,
|
|
71
|
+
"X-GEMINI-SIGNATURE": signature,
|
|
72
|
+
Accept: "application/json",
|
|
73
|
+
"Content-Type": "text/plain"
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
response.body
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class Hyperliquid < Client
|
|
6
|
+
URL = "https://api.hyperliquid.xyz"
|
|
7
|
+
|
|
8
|
+
def spot_meta
|
|
9
|
+
post_info({ type: "spotMeta" })
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def spot_meta_and_asset_ctxs
|
|
13
|
+
post_info({ type: "spotMetaAndAssetCtxs" })
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def spot_clearinghouse_state(user:)
|
|
17
|
+
post_info({ type: "spotClearinghouseState", user: user })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def order_status(user:, oid:)
|
|
21
|
+
post_info({ type: "orderStatus", user: user, oid: oid })
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def open_orders(user:)
|
|
25
|
+
post_info({ type: "openOrders", user: user })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def post_info(body)
|
|
31
|
+
with_rescue do
|
|
32
|
+
response = connection.post do |req|
|
|
33
|
+
req.url "/info"
|
|
34
|
+
req.headers = { Accept: "application/json", "Content-Type": "application/json" }
|
|
35
|
+
req.body = body.to_json
|
|
36
|
+
end
|
|
37
|
+
response.body
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|