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
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "base64"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "bigdecimal"
|
|
7
|
+
|
|
8
|
+
module Honeymaker
|
|
9
|
+
class Client
|
|
10
|
+
OPTIONS = {
|
|
11
|
+
request: {
|
|
12
|
+
open_timeout: 5,
|
|
13
|
+
read_timeout: 30,
|
|
14
|
+
write_timeout: 10
|
|
15
|
+
}
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
RATE_LIMITS = {
|
|
19
|
+
default: 100,
|
|
20
|
+
orders: 100
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
attr_reader :api_key, :api_secret
|
|
24
|
+
|
|
25
|
+
def initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil)
|
|
26
|
+
@api_key = api_key
|
|
27
|
+
@api_secret = api_secret
|
|
28
|
+
@proxy = proxy
|
|
29
|
+
@logger = logger
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.rate_limits
|
|
33
|
+
self::RATE_LIMITS
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def get_balances
|
|
37
|
+
raise NotImplementedError, "#{self.class} must implement #get_balances"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate(type = :trading)
|
|
41
|
+
return Result::Failure.new("No credentials provided") unless authenticated?
|
|
42
|
+
|
|
43
|
+
case type
|
|
44
|
+
when :trading then validate_trading_credentials
|
|
45
|
+
when :read then validate_read_credentials
|
|
46
|
+
else raise Error, "Unknown validation type: #{type}. Use :trading or :read"
|
|
47
|
+
end
|
|
48
|
+
rescue Error
|
|
49
|
+
raise
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
Result::Failure.new(e.message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def validate_trading_credentials
|
|
57
|
+
raise NotImplementedError, "#{self.class} must implement #validate_trading_credentials"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_read_credentials
|
|
61
|
+
raise NotImplementedError, "#{self.class} must implement #validate_read_credentials"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# No HTTP answer means the transport failed, and its text rarely says how ("end of file
|
|
65
|
+
# reached"). The chain of exception classes does — whether the request never left or may have
|
|
66
|
+
# landed — so it travels with the failure. Facts only: what they mean is the caller's call.
|
|
67
|
+
def transport_data(error, status)
|
|
68
|
+
status.nil? ? { status: nil, error_chain: Utils.error_chain(error) } : { status: status }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def with_rescue
|
|
72
|
+
Result::Success.new(yield)
|
|
73
|
+
rescue Faraday::Error => e
|
|
74
|
+
body = e.respond_to?(:response_body) ? e.response_body : nil
|
|
75
|
+
error_message = (body && !body.empty?) ? body : e.message.to_s
|
|
76
|
+
error_message = "Unknown API error" if error_message.nil? || error_message.empty?
|
|
77
|
+
status = e.respond_to?(:response_status) ? e.response_status : nil
|
|
78
|
+
Result::Failure.new(error_message, data: transport_data(e, status))
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
# NOT an exchange error — a bug here, in a vendor gem, or in the caller, and it arrives in the
|
|
81
|
+
# same Result::Failure as a genuine venue rejection. Callers classify that text to decide "out
|
|
82
|
+
# of funds" / "bad key" / "safe to retry", so an unlabelled TypeError gets attributed to the
|
|
83
|
+
# exchange: a hyperliquid-rb signing crash sat in production order history for three months
|
|
84
|
+
# reading exactly like a venue rejection. Name the class so the true source is unmistakable,
|
|
85
|
+
# and flag it so a caller can refuse to classify it as the venue's at all.
|
|
86
|
+
#
|
|
87
|
+
# The class is a PREFIX, not a replacement: the network failures that also land here
|
|
88
|
+
# (Net::ReadTimeout, execution expired, connection refused) are matched by substring for
|
|
89
|
+
# transient retry, and those matches must keep working.
|
|
90
|
+
msg = e.message
|
|
91
|
+
labelled = (msg && !msg.empty?) ? "#{e.class}: #{msg}" : e.class.to_s
|
|
92
|
+
Result::Failure.new(labelled, data: { client_error: true })
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A business error the exchange returned inside an HTTP-200 envelope (KuCoin's non-"200000"
|
|
96
|
+
# code, Bitget's non-"00000"). Keep the exchange's own code and message: callers classify on
|
|
97
|
+
# this text to tell insufficient funds from a bad key from a stale timestamp, so collapsing it
|
|
98
|
+
# to a bare constant silently disables every one of those checks and leaves the operator with
|
|
99
|
+
# an unactionable log line. Falls back to the constant only when the body carries no detail.
|
|
100
|
+
def api_error(exchange, body)
|
|
101
|
+
detail = body.is_a?(Hash) ? [body["code"], body["msg"] || body["message"]] : []
|
|
102
|
+
detail = detail.compact.map(&:to_s).reject(&:empty?).join(": ")
|
|
103
|
+
Result::Failure.new(detail.empty? ? "#{exchange} API error" : "#{exchange} API error #{detail}")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def connection
|
|
107
|
+
@connection ||= build_client_connection(self.class::URL)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def build_client_connection(url, content_type_match: nil)
|
|
111
|
+
Faraday.new(url: url, **OPTIONS) do |config|
|
|
112
|
+
config.proxy = @proxy if @proxy
|
|
113
|
+
config.request :json
|
|
114
|
+
if content_type_match
|
|
115
|
+
config.response :json, content_type: content_type_match
|
|
116
|
+
else
|
|
117
|
+
config.response :json
|
|
118
|
+
end
|
|
119
|
+
config.response :raise_error
|
|
120
|
+
config.response :logger, @logger, headers: false, bodies: false, log_level: :debug if @logger
|
|
121
|
+
config.adapter :net_http_persistent do |http|
|
|
122
|
+
http.idle_timeout = 100
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def authenticated?
|
|
128
|
+
@api_key && !@api_key.empty? && @api_secret && !@api_secret.empty?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def timestamp_ms
|
|
132
|
+
(Time.now.utc.to_f * 1_000).to_i
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def hmac_sha256(secret, data)
|
|
136
|
+
OpenSSL::HMAC.hexdigest("sha256", secret, data)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|