honeymaker 0.12.4-aarch64-linux
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 +7 -0
- data/.github/workflows/native-gems.yml +100 -0
- data/.github/workflows/release.yml +36 -0
- data/.github/workflows/test.yml +33 -0
- data/LICENSE +21 -0
- data/README.md +219 -0
- data/Rakefile +78 -0
- data/crates/honeymaker/tests/vectors/decimal.json +146 -0
- data/crates/honeymaker/tests/vectors/kraken_add_order_wire.json +74 -0
- data/crates/honeymaker/tests/vectors/kraken_signing.json +3314 -0
- data/crates/honeymaker/tests/vectors/kraken_trades_pager.json +1583 -0
- data/lib/honeymaker/3.4/honeymaker_native.so +0 -0
- data/lib/honeymaker/4.0/honeymaker_native.so +0 -0
- data/lib/honeymaker/client.rb +139 -0
- data/lib/honeymaker/clients/binance.rb +687 -0
- data/lib/honeymaker/clients/binance_us.rb +42 -0
- data/lib/honeymaker/clients/bingx.rb +242 -0
- data/lib/honeymaker/clients/bitget.rb +318 -0
- data/lib/honeymaker/clients/bitrue.rb +245 -0
- data/lib/honeymaker/clients/bitvavo.rb +244 -0
- data/lib/honeymaker/clients/bybit.rb +308 -0
- data/lib/honeymaker/clients/coinbase.rb +326 -0
- data/lib/honeymaker/clients/gemini.rb +189 -0
- data/lib/honeymaker/clients/hyperliquid.rb +232 -0
- data/lib/honeymaker/clients/kraken.rb +379 -0
- data/lib/honeymaker/clients/kraken_futures.rb +78 -0
- data/lib/honeymaker/clients/kucoin.rb +310 -0
- data/lib/honeymaker/clients/mexc.rb +249 -0
- data/lib/honeymaker/exchange.rb +129 -0
- data/lib/honeymaker/exchanges/binance.rb +55 -0
- data/lib/honeymaker/exchanges/binance_us.rb +9 -0
- data/lib/honeymaker/exchanges/bingx.rb +56 -0
- data/lib/honeymaker/exchanges/bitget.rb +52 -0
- data/lib/honeymaker/exchanges/bitrue.rb +53 -0
- data/lib/honeymaker/exchanges/bitvavo.rb +65 -0
- data/lib/honeymaker/exchanges/bybit.rb +57 -0
- data/lib/honeymaker/exchanges/coinbase.rb +69 -0
- data/lib/honeymaker/exchanges/gemini.rb +134 -0
- data/lib/honeymaker/exchanges/hyperliquid.rb +67 -0
- data/lib/honeymaker/exchanges/kraken.rb +122 -0
- data/lib/honeymaker/exchanges/kucoin.rb +52 -0
- data/lib/honeymaker/exchanges/mexc.rb +56 -0
- data/lib/honeymaker/native/kraken.rb +263 -0
- data/lib/honeymaker/native.rb +42 -0
- data/lib/honeymaker/result.rb +46 -0
- data/lib/honeymaker/utils.rb +39 -0
- data/lib/honeymaker/version.rb +5 -0
- data/lib/honeymaker.rb +97 -0
- data/script/parity/kraken_client_live.rb +191 -0
- data/script/parity/kraken_live.rb +70 -0
- data/script/parity/kraken_transport.rb +604 -0
- data/script/parity/resolve.c +49 -0
- data/script/release/push_gems.sh +28 -0
- data/script/vectors/kraken.rb +50 -0
- data/script/vectors/kraken_stage2.rb +146 -0
- data/sig/honeymaker.rbs +4 -0
- data/test/fixtures/binance_book_ticker.json +7 -0
- data/test/fixtures/binance_exchange_info.json +30 -0
- data/test/fixtures/bingx_symbols.json +26 -0
- data/test/fixtures/bingx_ticker.json +12 -0
- data/test/fixtures/bitget_symbols.json +28 -0
- data/test/fixtures/bitget_tickers.json +14 -0
- data/test/fixtures/bitmart_symbols.json +26 -0
- data/test/fixtures/bitmart_ticker.json +12 -0
- data/test/fixtures/bitrue_book_ticker.json +7 -0
- data/test/fixtures/bitrue_exchange_info.json +34 -0
- data/test/fixtures/bitvavo_book_ticker.json +7 -0
- data/test/fixtures/bitvavo_markets.json +30 -0
- data/test/fixtures/bybit_instruments.json +23 -0
- data/test/fixtures/bybit_tickers.json +19 -0
- data/test/fixtures/coinbase_product.json +7 -0
- data/test/fixtures/coinbase_products.json +24 -0
- data/test/fixtures/gemini_pubticker.json +10 -0
- data/test/fixtures/gemini_symbol_detail.json +10 -0
- data/test/fixtures/gemini_symbols.json +1 -0
- data/test/fixtures/hyperliquid_all_mids.json +5 -0
- data/test/fixtures/hyperliquid_spot_meta.json +12 -0
- data/test/fixtures/kraken_asset_pairs.json +43 -0
- data/test/fixtures/kraken_ticker.json +16 -0
- data/test/fixtures/kucoin_orderbook_level1.json +10 -0
- data/test/fixtures/kucoin_symbols.json +17 -0
- data/test/fixtures/mexc_book_ticker.json +7 -0
- data/test/fixtures/mexc_exchange_info.json +64 -0
- data/test/honeymaker/client_test.rb +90 -0
- data/test/honeymaker/clients/binance_client_test.rb +151 -0
- data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
- data/test/honeymaker/clients/bingx_client_test.rb +106 -0
- data/test/honeymaker/clients/bitget_client_test.rb +146 -0
- data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
- data/test/honeymaker/clients/bitvavo_client_test.rb +156 -0
- data/test/honeymaker/clients/bybit_client_test.rb +137 -0
- data/test/honeymaker/clients/coinbase_client_test.rb +153 -0
- data/test/honeymaker/clients/gemini_client_test.rb +100 -0
- data/test/honeymaker/clients/honeymaker_client_registry_test.rb +39 -0
- data/test/honeymaker/clients/hyperliquid_client_test.rb +292 -0
- data/test/honeymaker/clients/kraken_client_test.rb +255 -0
- data/test/honeymaker/clients/kraken_futures_client_test.rb +54 -0
- data/test/honeymaker/clients/kucoin_client_test.rb +149 -0
- data/test/honeymaker/clients/mexc_client_test.rb +98 -0
- data/test/honeymaker/clients/validation_test.rb +167 -0
- data/test/honeymaker/error_chain_test.rb +133 -0
- data/test/honeymaker/exchange_test.rb +138 -0
- data/test/honeymaker/exchanges/binance_test.rb +83 -0
- data/test/honeymaker/exchanges/binance_us_test.rb +53 -0
- data/test/honeymaker/exchanges/bingx_test.rb +65 -0
- data/test/honeymaker/exchanges/bitget_test.rb +65 -0
- data/test/honeymaker/exchanges/bitrue_test.rb +65 -0
- data/test/honeymaker/exchanges/bitvavo_test.rb +65 -0
- data/test/honeymaker/exchanges/bybit_test.rb +55 -0
- data/test/honeymaker/exchanges/coinbase_test.rb +106 -0
- data/test/honeymaker/exchanges/gemini_catalogue_test.rb +118 -0
- data/test/honeymaker/exchanges/gemini_test.rb +184 -0
- data/test/honeymaker/exchanges/hyperliquid_test.rb +64 -0
- data/test/honeymaker/exchanges/kraken_test.rb +191 -0
- data/test/honeymaker/exchanges/kucoin_test.rb +55 -0
- data/test/honeymaker/exchanges/mexc_test.rb +97 -0
- data/test/honeymaker/honeymaker_test.rb +27 -0
- data/test/honeymaker/result_test.rb +42 -0
- data/test/honeymaker/utils_test.rb +64 -0
- data/test/native/backend_switch_test.rb +53 -0
- data/test/native/convert_test.rb +137 -0
- data/test/native/gem_isolation_test.rb +17 -0
- data/test/native/loader_test.rb +29 -0
- data/test/native/lockstep_test.rb +23 -0
- data/test/native/signing_smoke_test.rb +28 -0
- data/test/parity/kraken_client_parity_test.rb +398 -0
- data/test/parity/kraken_exchange_parity_test.rb +172 -0
- data/test/release/branch_hardening_test.rb +34 -0
- data/test/release/push_gems_test.rb +79 -0
- data/test/smoke.rb +24 -0
- data/test/support/fixture_helper.rb +13 -0
- data/test/support/kraken_signing_smoke.rb +21 -0
- data/test/support/parity_helper.rb +62 -0
- data/test/support/venue_double.rb +100 -0
- data/test/test_helper.rb +9 -0
- metadata +254 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class Honeymaker::Clients::BitrueTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@client = Honeymaker::Clients::Bitrue.new(api_key: "test_key", api_secret: "test_secret")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_url
|
|
11
|
+
assert_equal "https://openapi.bitrue.com", Honeymaker::Clients::Bitrue::URL
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_exchange_information
|
|
15
|
+
stub_connection(:get, { "symbols" => [{ "symbol" => "BTCUSDT" }] })
|
|
16
|
+
result = @client.exchange_information
|
|
17
|
+
assert result.success?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_symbol_price_ticker
|
|
21
|
+
stub_connection(:get, { "symbol" => "BTCUSDT", "price" => "50000" })
|
|
22
|
+
result = @client.symbol_price_ticker(symbol: "BTCUSDT")
|
|
23
|
+
assert result.success?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_account_information
|
|
27
|
+
stub_connection(:get, { "balances" => [{ "asset" => "BTC" }] })
|
|
28
|
+
result = @client.account_information
|
|
29
|
+
assert result.success?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_new_order
|
|
33
|
+
stub_connection(:post, { "orderId" => "123" })
|
|
34
|
+
result = @client.new_order(symbol: "BTCUSDT", side: "BUY", type: "MARKET", quantity: "0.001")
|
|
35
|
+
assert result.success?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_query_order
|
|
39
|
+
stub_connection(:get, { "orderId" => "123", "status" => "FILLED" })
|
|
40
|
+
result = @client.query_order(symbol: "BTCUSDT", order_id: "123")
|
|
41
|
+
assert result.success?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_cancel_order
|
|
45
|
+
stub_connection(:delete, { "orderId" => "123" })
|
|
46
|
+
result = @client.cancel_order(symbol: "BTCUSDT", order_id: "123")
|
|
47
|
+
assert result.success?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_account_trade_list
|
|
51
|
+
stub_connection(:get, [{ "symbol" => "BTCUSDT", "id" => 1 }])
|
|
52
|
+
result = @client.account_trade_list(symbol: "BTCUSDT")
|
|
53
|
+
assert result.success?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_headers_include_api_key
|
|
57
|
+
headers = @client.send(:auth_headers)
|
|
58
|
+
assert_equal "test_key", headers[:"X-MBX-APIKEY"]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def stub_connection(method, body)
|
|
64
|
+
response = stub(body: body)
|
|
65
|
+
connection = stub
|
|
66
|
+
connection.stubs(method).returns(response)
|
|
67
|
+
@client.instance_variable_set(:@connection, connection)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class Honeymaker::Clients::BitvavoTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@client = Honeymaker::Clients::Bitvavo.new(api_key: "test_key", api_secret: "test_secret")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_url
|
|
11
|
+
assert_equal "https://api.bitvavo.com", Honeymaker::Clients::Bitvavo::URL
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_get_markets
|
|
15
|
+
stub_connection(:get, [{ "market" => "BTC-EUR" }])
|
|
16
|
+
result = @client.get_markets
|
|
17
|
+
assert result.success?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_get_ticker_price
|
|
21
|
+
stub_connection(:get, [{ "market" => "BTC-EUR", "price" => "50000" }])
|
|
22
|
+
result = @client.get_ticker_price
|
|
23
|
+
assert result.success?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_get_raw_balance
|
|
27
|
+
stub_connection(:get, [{ "symbol" => "BTC", "available" => "0.5" }])
|
|
28
|
+
result = @client.get_raw_balance
|
|
29
|
+
assert result.success?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_place_order
|
|
33
|
+
stub_connection(:post, { "orderId" => "123" })
|
|
34
|
+
result = @client.place_order(market: "BTC-EUR", side: "buy", order_type: "market", amount_quote: "100")
|
|
35
|
+
assert result.success?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_get_order
|
|
39
|
+
stub_connection(:get, { "orderId" => "123", "status" => "filled" })
|
|
40
|
+
result = @client.get_order(market: "BTC-EUR", order_id: "123")
|
|
41
|
+
assert result.success?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_cancel_order
|
|
45
|
+
stub_connection(:delete, { "orderId" => "123" })
|
|
46
|
+
result = @client.cancel_order(market: "BTC-EUR", order_id: "123")
|
|
47
|
+
assert result.success?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Bitvavo requires a mandatory integer operatorId on every order create/cancel
|
|
51
|
+
# (errorCode 203). These tests yield the real Faraday request block so we assert
|
|
52
|
+
# what the client actually sends, not just the stubbed response.
|
|
53
|
+
|
|
54
|
+
def test_place_order_sends_default_operator_id
|
|
55
|
+
req = capture_request(:post, { "orderId" => "123" })
|
|
56
|
+
@client.place_order(market: "BTC-EUR", side: "buy", order_type: "market", amount_quote: "100")
|
|
57
|
+
assert_equal @client.class::DEFAULT_OPERATOR_ID, req.body[:operatorId]
|
|
58
|
+
assert_kind_of Integer, req.body[:operatorId]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_place_order_operator_id_override
|
|
62
|
+
req = capture_request(:post, { "orderId" => "123" })
|
|
63
|
+
@client.place_order(market: "BTC-EUR", side: "buy", order_type: "market", amount_quote: "100", operator_id: 42)
|
|
64
|
+
assert_equal 42, req.body[:operatorId]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_cancel_order_sends_default_operator_id
|
|
68
|
+
req = capture_request(:delete, { "orderId" => "123" })
|
|
69
|
+
@client.cancel_order(market: "BTC-EUR", order_id: "123")
|
|
70
|
+
assert_equal @client.class::DEFAULT_OPERATOR_ID, req.params[:operatorId]
|
|
71
|
+
assert_kind_of Integer, req.params[:operatorId]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_cancel_order_operator_id_override
|
|
75
|
+
req = capture_request(:delete, { "orderId" => "123" })
|
|
76
|
+
@client.cancel_order(market: "BTC-EUR", order_id: "123", operator_id: 42)
|
|
77
|
+
assert_equal 42, req.params[:operatorId]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_withdraw
|
|
81
|
+
stub_connection(:post, { "success" => true })
|
|
82
|
+
result = @client.withdraw(symbol: "BTC", amount: "0.1", address: "addr")
|
|
83
|
+
assert result.success?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_get_assets
|
|
87
|
+
stub_connection(:get, [{ "symbol" => "BTC" }])
|
|
88
|
+
result = @client.get_assets
|
|
89
|
+
assert result.success?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_get_trades
|
|
93
|
+
stub_connection(:get, [{ "id" => "t1", "side" => "buy" }])
|
|
94
|
+
result = @client.get_trades(market: "BTC-EUR")
|
|
95
|
+
assert result.success?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_get_deposit_history
|
|
99
|
+
stub_connection(:get, [{ "symbol" => "BTC", "amount" => "1.0" }])
|
|
100
|
+
result = @client.get_deposit_history
|
|
101
|
+
assert result.success?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_get_withdrawal_history
|
|
105
|
+
stub_connection(:get, [{ "symbol" => "ETH", "amount" => "2.0" }])
|
|
106
|
+
result = @client.get_withdrawal_history
|
|
107
|
+
assert result.success?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_signed_headers_include_access_window
|
|
111
|
+
ts = "1234567890"
|
|
112
|
+
headers = @client.send(:signed_headers, ts, "payload")
|
|
113
|
+
assert_equal "test_key", headers[:"BITVAVO-ACCESS-KEY"]
|
|
114
|
+
assert_equal "10000", headers[:"BITVAVO-ACCESS-WINDOW"]
|
|
115
|
+
assert headers.key?(:"BITVAVO-ACCESS-SIGNATURE")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_signing_payload_format
|
|
119
|
+
# Verify the signing payload matches Bitvavo's expected format
|
|
120
|
+
sig1 = @client.send(:hmac_sha256, "test_secret", "1234567890GET/v2/balance")
|
|
121
|
+
sig2 = @client.send(:hmac_sha256, "test_secret", "1234567890POST/v2/order{\"market\":\"BTC-EUR\"}")
|
|
122
|
+
assert sig1.is_a?(String)
|
|
123
|
+
assert sig2.is_a?(String)
|
|
124
|
+
refute_equal sig1, sig2
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def stub_connection(method, body)
|
|
130
|
+
response = stub(body: body)
|
|
131
|
+
connection = stub
|
|
132
|
+
connection.stubs(method).returns(response)
|
|
133
|
+
@client.instance_variable_set(:@connection, connection)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Records what the client sets on the Faraday request. Unlike stub_connection,
|
|
137
|
+
# this yields the request block (connection.post { |req| ... }) so the real
|
|
138
|
+
# body/params-building code in post_signed/delete_signed actually executes.
|
|
139
|
+
class RequestRecorder
|
|
140
|
+
attr_accessor :headers, :body, :params
|
|
141
|
+
attr_reader :requested_url
|
|
142
|
+
|
|
143
|
+
def url(path)
|
|
144
|
+
@requested_url = path
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def capture_request(method, body)
|
|
149
|
+
response = stub(body: body)
|
|
150
|
+
recorder = RequestRecorder.new
|
|
151
|
+
connection = stub
|
|
152
|
+
connection.stubs(method).yields(recorder).returns(response)
|
|
153
|
+
@client.instance_variable_set(:@connection, connection)
|
|
154
|
+
recorder
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class Honeymaker::Clients::BybitTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@client = Honeymaker::Clients::Bybit.new(api_key: "test_key", api_secret: "test_secret")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_url
|
|
11
|
+
assert_equal "https://api.bybit.com", Honeymaker::Clients::Bybit::URL
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_instruments_info
|
|
15
|
+
stub_connection(:get, { "result" => { "list" => [{ "symbol" => "BTCUSDT" }] } })
|
|
16
|
+
result = @client.instruments_info(category: "spot")
|
|
17
|
+
assert result.success?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_tickers
|
|
21
|
+
stub_connection(:get, { "result" => { "list" => [{ "lastPrice" => "50000" }] } })
|
|
22
|
+
result = @client.tickers(category: "spot", symbol: "BTCUSDT")
|
|
23
|
+
assert result.success?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_wallet_balance
|
|
27
|
+
stub_connection(:get, { "result" => { "list" => [{ "coin" => [{ "coin" => "BTC" }] }] } })
|
|
28
|
+
result = @client.wallet_balance(account_type: "UNIFIED")
|
|
29
|
+
assert result.success?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_create_order
|
|
33
|
+
stub_connection(:post, { "retCode" => 0, "retMsg" => "OK", "result" => { "orderId" => "123" } })
|
|
34
|
+
result = @client.create_order(category: "spot", symbol: "BTCUSDT", side: "Buy", order_type: "Market", qty: "0.001")
|
|
35
|
+
assert result.success?
|
|
36
|
+
assert_equal "123", result.data[:order_id]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_get_order
|
|
40
|
+
stub_connection(:get, { "retCode" => 0, "retMsg" => "OK", "result" => { "list" => [{
|
|
41
|
+
"orderId" => "123", "orderType" => "Market", "side" => "Buy", "orderStatus" => "Filled",
|
|
42
|
+
"avgPrice" => "50000", "qty" => "0.001", "cumExecQty" => "0.001", "cumExecValue" => "50"
|
|
43
|
+
}] } })
|
|
44
|
+
result = @client.get_order(category: "spot", order_id: "123")
|
|
45
|
+
assert result.success?
|
|
46
|
+
assert_equal :closed, result.data[:status]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_get_order_partially_filled_canceled_is_cancelled
|
|
50
|
+
stub_connection(:get, { "retCode" => 0, "retMsg" => "OK", "result" => { "list" => [{
|
|
51
|
+
"orderId" => "123", "orderType" => "Limit", "side" => "Buy", "orderStatus" => "PartiallyFilledCanceled",
|
|
52
|
+
"avgPrice" => "50000", "qty" => "0.002", "cumExecQty" => "0.001", "cumExecValue" => "50"
|
|
53
|
+
}] } })
|
|
54
|
+
result = @client.get_order(category: "spot", order_id: "123")
|
|
55
|
+
assert_equal :cancelled, result.data[:status]
|
|
56
|
+
assert_equal BigDecimal("0.001"), result.data[:amount_exec]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_order_history
|
|
60
|
+
@client.expects(:get_authenticated).with("/v5/order/history", {
|
|
61
|
+
category: "spot", symbol: nil, orderId: "123", orderLinkId: nil, orderStatus: nil,
|
|
62
|
+
startTime: 1, endTime: 2, limit: 50, cursor: "c"
|
|
63
|
+
}).returns(Honeymaker::Result::Success.new({ "retCode" => 0 }))
|
|
64
|
+
assert @client.order_history(category: "spot", order_id: "123", start_time: 1, end_time: 2, limit: 50, cursor: "c").success?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_internal_deposit_records
|
|
68
|
+
@client.expects(:get_authenticated).with("/v5/asset/deposit/query-internal-record", {
|
|
69
|
+
txID: nil, coin: "USDT", startTime: 1, endTime: 2, limit: 50, cursor: "c"
|
|
70
|
+
}).returns(Honeymaker::Result::Success.new({ "retCode" => 0 }))
|
|
71
|
+
assert @client.internal_deposit_records(coin: "USDT", start_time: 1, end_time: 2, limit: 50, cursor: "c").success?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_cancel_order
|
|
75
|
+
stub_connection(:post, { "result" => { "orderId" => "123" } })
|
|
76
|
+
result = @client.cancel_order(category: "spot", symbol: "BTCUSDT", order_id: "123")
|
|
77
|
+
assert result.success?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_kline
|
|
81
|
+
stub_connection(:get, { "result" => { "list" => [] } })
|
|
82
|
+
result = @client.kline(category: "spot", symbol: "BTCUSDT", interval: "60")
|
|
83
|
+
assert result.success?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_withdraw
|
|
87
|
+
stub_connection(:post, { "result" => { "id" => "w1" } })
|
|
88
|
+
result = @client.withdraw(coin: "BTC", chain: "BTC", address: "addr", amount: "0.1")
|
|
89
|
+
assert result.success?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_transaction_log
|
|
93
|
+
stub_connection(:get, { "result" => { "list" => [{ "transactionTime" => "1710936000000" }] } })
|
|
94
|
+
result = @client.transaction_log
|
|
95
|
+
assert result.success?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_execution_list
|
|
99
|
+
stub_connection(:get, { "result" => { "list" => [{ "execId" => "e1" }] } })
|
|
100
|
+
result = @client.execution_list(category: "spot")
|
|
101
|
+
assert result.success?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_deposit_records
|
|
105
|
+
stub_connection(:get, { "result" => { "rows" => [{ "coin" => "BTC" }] } })
|
|
106
|
+
result = @client.deposit_records
|
|
107
|
+
assert result.success?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_withdraw_records
|
|
111
|
+
stub_connection(:get, { "result" => { "rows" => [{ "coin" => "ETH" }] } })
|
|
112
|
+
result = @client.withdraw_records
|
|
113
|
+
assert result.success?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_signed_headers_include_api_key
|
|
117
|
+
headers = @client.send(:signed_headers, "GET", { foo: "bar" })
|
|
118
|
+
assert_equal "test_key", headers[:"X-BAPI-API-KEY"]
|
|
119
|
+
assert headers.key?(:"X-BAPI-SIGN")
|
|
120
|
+
assert headers.key?(:"X-BAPI-TIMESTAMP")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_unauthenticated_client
|
|
124
|
+
client = Honeymaker::Clients::Bybit.new
|
|
125
|
+
headers = client.send(:signed_headers, "GET", {})
|
|
126
|
+
refute headers.key?(:"X-BAPI-SIGN")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def stub_connection(method, body)
|
|
132
|
+
response = stub(body: body)
|
|
133
|
+
connection = stub
|
|
134
|
+
connection.stubs(method).returns(response)
|
|
135
|
+
@client.instance_variable_set(:@connection, connection)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class Honeymaker::Clients::CoinbaseTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
# Use ECDSA key for testing
|
|
8
|
+
@key = OpenSSL::PKey::EC.generate("prime256v1")
|
|
9
|
+
@client = Honeymaker::Clients::Coinbase.new(
|
|
10
|
+
api_key: "test_key",
|
|
11
|
+
api_secret: @key.to_pem
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_url
|
|
16
|
+
assert_equal "https://api.coinbase.com", Honeymaker::Clients::Coinbase::URL
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_get_order
|
|
20
|
+
stub_connection(:get, { "order" => {
|
|
21
|
+
"order_id" => "abc", "status" => "FILLED", "order_type" => "MARKET", "side" => "BUY",
|
|
22
|
+
"average_filled_price" => "50000", "filled_size" => "0.001",
|
|
23
|
+
"total_value_after_fees" => "50", "outstanding_hold_amount" => "0",
|
|
24
|
+
"order_configuration" => { "market_market_ioc" => { "quote_size" => "50" } }
|
|
25
|
+
} })
|
|
26
|
+
result = @client.get_order(order_id: "abc")
|
|
27
|
+
assert result.success?
|
|
28
|
+
assert_equal "abc", result.data[:order_id]
|
|
29
|
+
assert_equal :closed, result.data[:status]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_create_order
|
|
33
|
+
stub_connection(:post, { "success" => true, "success_response" => { "order_id" => "xyz" } })
|
|
34
|
+
result = @client.create_order(
|
|
35
|
+
client_order_id: "c1", product_id: "BTC-USD",
|
|
36
|
+
side: "BUY", order_configuration: { market_market_ioc: { quote_size: "100" } }
|
|
37
|
+
)
|
|
38
|
+
assert result.success?
|
|
39
|
+
assert_equal "xyz", result.data[:order_id]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_cancel_orders
|
|
43
|
+
stub_connection(:post, { "results" => [{ "success" => true }] })
|
|
44
|
+
result = @client.cancel_orders(order_ids: ["abc"])
|
|
45
|
+
assert result.success?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_list_public_products
|
|
49
|
+
stub_connection(:get, { "products" => [{ "product_id" => "BTC-USD" }] })
|
|
50
|
+
result = @client.list_public_products
|
|
51
|
+
assert result.success?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_get_public_product
|
|
55
|
+
stub_connection(:get, { "product_id" => "BTC-USD" })
|
|
56
|
+
result = @client.get_public_product(product_id: "BTC-USD")
|
|
57
|
+
assert result.success?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_get_api_key_permissions
|
|
61
|
+
stub_connection(:get, { "can_trade" => true })
|
|
62
|
+
result = @client.get_api_key_permissions
|
|
63
|
+
assert result.success?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_list_accounts
|
|
67
|
+
stub_connection(:get, { "accounts" => [] })
|
|
68
|
+
result = @client.list_accounts
|
|
69
|
+
assert result.success?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_list_accounts_passes_cursor_and_limit
|
|
73
|
+
response = stub(body: { "accounts" => [], "has_next" => false, "cursor" => "" })
|
|
74
|
+
request = stub(url: nil)
|
|
75
|
+
request.stubs(:headers=)
|
|
76
|
+
request.stubs(:http_method).returns(:get)
|
|
77
|
+
request.stubs(:path).returns("/api/v3/brokerage/accounts")
|
|
78
|
+
request.expects(:params=).with({ limit: 250, cursor: "c2" })
|
|
79
|
+
connection = stub
|
|
80
|
+
connection.stubs(:get).yields(request).returns(response)
|
|
81
|
+
@client.instance_variable_set(:@connection, connection)
|
|
82
|
+
|
|
83
|
+
assert @client.list_accounts(limit: 250, cursor: "c2").success?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_send_money
|
|
87
|
+
stub_connection(:post, { "data" => { "id" => "tx1" } })
|
|
88
|
+
result = @client.send_money(account_id: "acc1", to: "addr", amount: "0.1", currency: "BTC")
|
|
89
|
+
assert result.success?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_list_fills
|
|
93
|
+
stub_connection(:get, { "fills" => [{ "trade_id" => "f1", "product_id" => "BTC-USD" }] })
|
|
94
|
+
result = @client.list_fills(product_id: "BTC-USD")
|
|
95
|
+
assert result.success?
|
|
96
|
+
assert_equal "f1", result.data["fills"].first["trade_id"]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_list_transactions
|
|
100
|
+
stub_connection(:get, { "data" => [{ "id" => "tx1", "type" => "send" }] })
|
|
101
|
+
result = @client.list_transactions(account_id: "acc1")
|
|
102
|
+
assert result.success?
|
|
103
|
+
assert_equal "tx1", result.data["data"].first["id"]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_handles_api_error
|
|
107
|
+
connection = stub
|
|
108
|
+
connection.stubs(:get).raises(Faraday::ClientError.new("401", { status: 401, body: "Unauthorized" }))
|
|
109
|
+
@client.instance_variable_set(:@connection, connection)
|
|
110
|
+
result = @client.list_accounts
|
|
111
|
+
assert result.failure?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_ecdsa_key_detected
|
|
115
|
+
assert @client.send(:ecdsa_key?)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_non_ecdsa_key_not_detected
|
|
119
|
+
client = Honeymaker::Clients::Coinbase.new(api_key: "k", api_secret: "not-a-pem-key")
|
|
120
|
+
refute client.send(:ecdsa_key?)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_ed25519_returns_nil_without_rbnacl
|
|
124
|
+
client = Honeymaker::Clients::Coinbase.new(api_key: "k", api_secret: Base64.encode64("short"))
|
|
125
|
+
# rbnacl may or may not be installed; either way should not raise
|
|
126
|
+
result = client.send(:ed25519_signing_key)
|
|
127
|
+
# Returns nil if rbnacl not installed or key is invalid
|
|
128
|
+
assert_nil result
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_unauthenticated_headers
|
|
132
|
+
client = Honeymaker::Clients::Coinbase.new
|
|
133
|
+
stub_connection_on(client, :get, { "products" => [] })
|
|
134
|
+
result = client.list_public_products
|
|
135
|
+
assert result.success?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
def stub_connection(method, body)
|
|
141
|
+
response = stub(body: body)
|
|
142
|
+
connection = stub
|
|
143
|
+
connection.stubs(method).returns(response)
|
|
144
|
+
@client.instance_variable_set(:@connection, connection)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def stub_connection_on(client, method, body)
|
|
148
|
+
response = stub(body: body)
|
|
149
|
+
connection = stub
|
|
150
|
+
connection.stubs(method).returns(response)
|
|
151
|
+
client.instance_variable_set(:@connection, connection)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class Honeymaker::Clients::GeminiTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@client = Honeymaker::Clients::Gemini.new(api_key: "test_key", api_secret: "test_secret")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_url
|
|
11
|
+
assert_equal "https://api.gemini.com", Honeymaker::Clients::Gemini::URL
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_get_symbols
|
|
15
|
+
stub_connection(:get, ["btcusd", "ethusd"])
|
|
16
|
+
result = @client.get_symbols
|
|
17
|
+
assert result.success?
|
|
18
|
+
assert_includes result.data, "btcusd"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_get_symbol_details
|
|
22
|
+
stub_connection(:get, { "symbol" => "BTCUSD", "base_currency" => "BTC" })
|
|
23
|
+
result = @client.get_symbol_details(symbol: "btcusd")
|
|
24
|
+
assert result.success?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_get_ticker
|
|
28
|
+
stub_connection(:get, { "last" => "50000" })
|
|
29
|
+
result = @client.get_ticker(symbol: "btcusd")
|
|
30
|
+
assert result.success?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_get_balances
|
|
34
|
+
stub_connection(:post, [{ "currency" => "BTC", "amount" => "0.5" }])
|
|
35
|
+
result = @client.get_balances
|
|
36
|
+
assert result.success?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_new_order
|
|
40
|
+
stub_connection(:post, { "order_id" => "123" })
|
|
41
|
+
result = @client.new_order(symbol: "btcusd", amount: "0.001", price: "50000", side: "buy", type: "exchange limit")
|
|
42
|
+
assert result.success?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_order_status
|
|
46
|
+
stub_connection(:post, { "order_id" => "123", "is_live" => false })
|
|
47
|
+
result = @client.order_status(order_id: "123")
|
|
48
|
+
assert result.success?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_cancel_order
|
|
52
|
+
stub_connection(:post, { "order_id" => "123", "is_cancelled" => true })
|
|
53
|
+
result = @client.cancel_order(order_id: "123")
|
|
54
|
+
assert result.success?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_withdraw
|
|
58
|
+
stub_connection(:post, { "destination" => "addr", "amount" => "0.1" })
|
|
59
|
+
result = @client.withdraw(currency: "btc", address: "addr", amount: "0.1")
|
|
60
|
+
assert result.success?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_get_my_trades
|
|
64
|
+
stub_connection(:post, [{ "tid" => 123, "price" => "50000" }])
|
|
65
|
+
result = @client.get_my_trades(symbol: "btcusd")
|
|
66
|
+
assert result.success?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_get_transfers
|
|
70
|
+
stub_connection(:post, [{ "type" => "Deposit", "currency" => "BTC" }])
|
|
71
|
+
result = @client.get_transfers
|
|
72
|
+
assert result.success?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Gemini signs with HMAC-SHA384 of the base64 payload
|
|
76
|
+
# (https://developer.gemini.com/authentication/api-key); any other digest is rejected.
|
|
77
|
+
def test_private_requests_are_signed_with_hmac_sha384
|
|
78
|
+
request = Struct.new(:headers) { def url(_path) = nil }.new
|
|
79
|
+
connection = Object.new
|
|
80
|
+
connection.define_singleton_method(:post) do |&block|
|
|
81
|
+
block.call(request)
|
|
82
|
+
Struct.new(:body).new({})
|
|
83
|
+
end
|
|
84
|
+
@client.instance_variable_set(:@connection, connection)
|
|
85
|
+
|
|
86
|
+
@client.get_balances
|
|
87
|
+
|
|
88
|
+
payload = request.headers[:"X-GEMINI-PAYLOAD"]
|
|
89
|
+
assert_equal OpenSSL::HMAC.hexdigest("sha384", "test_secret", payload), request.headers[:"X-GEMINI-SIGNATURE"]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def stub_connection(method, body)
|
|
95
|
+
response = stub(body: body)
|
|
96
|
+
connection = stub
|
|
97
|
+
connection.stubs(method).returns(response)
|
|
98
|
+
@client.instance_variable_set(:@connection, connection)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HoneymakerClientRegistryTest < Minitest::Test
|
|
6
|
+
def test_client_returns_correct_class
|
|
7
|
+
client = Honeymaker.client("binance", api_key: "k", api_secret: "s")
|
|
8
|
+
assert_instance_of Honeymaker::Clients::Binance, client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_client_with_symbol
|
|
12
|
+
client = Honeymaker.client(:kraken, api_key: "k", api_secret: "s")
|
|
13
|
+
assert_instance_of Honeymaker::Clients::Kraken, client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_client_raises_for_unknown
|
|
17
|
+
assert_raises(Honeymaker::Error) { Honeymaker.client("unknown") }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_all_clients_registered
|
|
21
|
+
assert_equal 14, Honeymaker::CLIENTS.size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_client_passes_credentials
|
|
25
|
+
client = Honeymaker.client("binance", api_key: "my_key", api_secret: "my_secret")
|
|
26
|
+
assert_equal "my_key", client.api_key
|
|
27
|
+
assert_equal "my_secret", client.api_secret
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_bitget_accepts_passphrase
|
|
31
|
+
client = Honeymaker.client("bitget", api_key: "k", api_secret: "s", passphrase: "p")
|
|
32
|
+
assert_equal "p", client.passphrase
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_kucoin_accepts_passphrase
|
|
36
|
+
client = Honeymaker.client("kucoin", api_key: "k", api_secret: "s", passphrase: "p")
|
|
37
|
+
assert_equal "p", client.passphrase
|
|
38
|
+
end
|
|
39
|
+
end
|