honeymaker 0.12.4-x86_64-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.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/native-gems.yml +100 -0
  3. data/.github/workflows/release.yml +36 -0
  4. data/.github/workflows/test.yml +33 -0
  5. data/LICENSE +21 -0
  6. data/README.md +219 -0
  7. data/Rakefile +78 -0
  8. data/crates/honeymaker/tests/vectors/decimal.json +146 -0
  9. data/crates/honeymaker/tests/vectors/kraken_add_order_wire.json +74 -0
  10. data/crates/honeymaker/tests/vectors/kraken_signing.json +3314 -0
  11. data/crates/honeymaker/tests/vectors/kraken_trades_pager.json +1583 -0
  12. data/lib/honeymaker/3.4/honeymaker_native.so +0 -0
  13. data/lib/honeymaker/4.0/honeymaker_native.so +0 -0
  14. data/lib/honeymaker/client.rb +139 -0
  15. data/lib/honeymaker/clients/binance.rb +687 -0
  16. data/lib/honeymaker/clients/binance_us.rb +42 -0
  17. data/lib/honeymaker/clients/bingx.rb +242 -0
  18. data/lib/honeymaker/clients/bitget.rb +318 -0
  19. data/lib/honeymaker/clients/bitrue.rb +245 -0
  20. data/lib/honeymaker/clients/bitvavo.rb +244 -0
  21. data/lib/honeymaker/clients/bybit.rb +308 -0
  22. data/lib/honeymaker/clients/coinbase.rb +326 -0
  23. data/lib/honeymaker/clients/gemini.rb +189 -0
  24. data/lib/honeymaker/clients/hyperliquid.rb +232 -0
  25. data/lib/honeymaker/clients/kraken.rb +379 -0
  26. data/lib/honeymaker/clients/kraken_futures.rb +78 -0
  27. data/lib/honeymaker/clients/kucoin.rb +310 -0
  28. data/lib/honeymaker/clients/mexc.rb +249 -0
  29. data/lib/honeymaker/exchange.rb +129 -0
  30. data/lib/honeymaker/exchanges/binance.rb +55 -0
  31. data/lib/honeymaker/exchanges/binance_us.rb +9 -0
  32. data/lib/honeymaker/exchanges/bingx.rb +56 -0
  33. data/lib/honeymaker/exchanges/bitget.rb +52 -0
  34. data/lib/honeymaker/exchanges/bitrue.rb +53 -0
  35. data/lib/honeymaker/exchanges/bitvavo.rb +65 -0
  36. data/lib/honeymaker/exchanges/bybit.rb +57 -0
  37. data/lib/honeymaker/exchanges/coinbase.rb +69 -0
  38. data/lib/honeymaker/exchanges/gemini.rb +134 -0
  39. data/lib/honeymaker/exchanges/hyperliquid.rb +67 -0
  40. data/lib/honeymaker/exchanges/kraken.rb +122 -0
  41. data/lib/honeymaker/exchanges/kucoin.rb +52 -0
  42. data/lib/honeymaker/exchanges/mexc.rb +56 -0
  43. data/lib/honeymaker/native/kraken.rb +263 -0
  44. data/lib/honeymaker/native.rb +42 -0
  45. data/lib/honeymaker/result.rb +46 -0
  46. data/lib/honeymaker/utils.rb +39 -0
  47. data/lib/honeymaker/version.rb +5 -0
  48. data/lib/honeymaker.rb +97 -0
  49. data/script/parity/kraken_client_live.rb +191 -0
  50. data/script/parity/kraken_live.rb +70 -0
  51. data/script/parity/kraken_transport.rb +604 -0
  52. data/script/parity/resolve.c +49 -0
  53. data/script/release/push_gems.sh +28 -0
  54. data/script/vectors/kraken.rb +50 -0
  55. data/script/vectors/kraken_stage2.rb +146 -0
  56. data/sig/honeymaker.rbs +4 -0
  57. data/test/fixtures/binance_book_ticker.json +7 -0
  58. data/test/fixtures/binance_exchange_info.json +30 -0
  59. data/test/fixtures/bingx_symbols.json +26 -0
  60. data/test/fixtures/bingx_ticker.json +12 -0
  61. data/test/fixtures/bitget_symbols.json +28 -0
  62. data/test/fixtures/bitget_tickers.json +14 -0
  63. data/test/fixtures/bitmart_symbols.json +26 -0
  64. data/test/fixtures/bitmart_ticker.json +12 -0
  65. data/test/fixtures/bitrue_book_ticker.json +7 -0
  66. data/test/fixtures/bitrue_exchange_info.json +34 -0
  67. data/test/fixtures/bitvavo_book_ticker.json +7 -0
  68. data/test/fixtures/bitvavo_markets.json +30 -0
  69. data/test/fixtures/bybit_instruments.json +23 -0
  70. data/test/fixtures/bybit_tickers.json +19 -0
  71. data/test/fixtures/coinbase_product.json +7 -0
  72. data/test/fixtures/coinbase_products.json +24 -0
  73. data/test/fixtures/gemini_pubticker.json +10 -0
  74. data/test/fixtures/gemini_symbol_detail.json +10 -0
  75. data/test/fixtures/gemini_symbols.json +1 -0
  76. data/test/fixtures/hyperliquid_all_mids.json +5 -0
  77. data/test/fixtures/hyperliquid_spot_meta.json +12 -0
  78. data/test/fixtures/kraken_asset_pairs.json +43 -0
  79. data/test/fixtures/kraken_ticker.json +16 -0
  80. data/test/fixtures/kucoin_orderbook_level1.json +10 -0
  81. data/test/fixtures/kucoin_symbols.json +17 -0
  82. data/test/fixtures/mexc_book_ticker.json +7 -0
  83. data/test/fixtures/mexc_exchange_info.json +64 -0
  84. data/test/honeymaker/client_test.rb +90 -0
  85. data/test/honeymaker/clients/binance_client_test.rb +151 -0
  86. data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
  87. data/test/honeymaker/clients/bingx_client_test.rb +106 -0
  88. data/test/honeymaker/clients/bitget_client_test.rb +146 -0
  89. data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
  90. data/test/honeymaker/clients/bitvavo_client_test.rb +156 -0
  91. data/test/honeymaker/clients/bybit_client_test.rb +137 -0
  92. data/test/honeymaker/clients/coinbase_client_test.rb +153 -0
  93. data/test/honeymaker/clients/gemini_client_test.rb +100 -0
  94. data/test/honeymaker/clients/honeymaker_client_registry_test.rb +39 -0
  95. data/test/honeymaker/clients/hyperliquid_client_test.rb +292 -0
  96. data/test/honeymaker/clients/kraken_client_test.rb +255 -0
  97. data/test/honeymaker/clients/kraken_futures_client_test.rb +54 -0
  98. data/test/honeymaker/clients/kucoin_client_test.rb +149 -0
  99. data/test/honeymaker/clients/mexc_client_test.rb +98 -0
  100. data/test/honeymaker/clients/validation_test.rb +167 -0
  101. data/test/honeymaker/error_chain_test.rb +133 -0
  102. data/test/honeymaker/exchange_test.rb +138 -0
  103. data/test/honeymaker/exchanges/binance_test.rb +83 -0
  104. data/test/honeymaker/exchanges/binance_us_test.rb +53 -0
  105. data/test/honeymaker/exchanges/bingx_test.rb +65 -0
  106. data/test/honeymaker/exchanges/bitget_test.rb +65 -0
  107. data/test/honeymaker/exchanges/bitrue_test.rb +65 -0
  108. data/test/honeymaker/exchanges/bitvavo_test.rb +65 -0
  109. data/test/honeymaker/exchanges/bybit_test.rb +55 -0
  110. data/test/honeymaker/exchanges/coinbase_test.rb +106 -0
  111. data/test/honeymaker/exchanges/gemini_catalogue_test.rb +118 -0
  112. data/test/honeymaker/exchanges/gemini_test.rb +184 -0
  113. data/test/honeymaker/exchanges/hyperliquid_test.rb +64 -0
  114. data/test/honeymaker/exchanges/kraken_test.rb +191 -0
  115. data/test/honeymaker/exchanges/kucoin_test.rb +55 -0
  116. data/test/honeymaker/exchanges/mexc_test.rb +97 -0
  117. data/test/honeymaker/honeymaker_test.rb +27 -0
  118. data/test/honeymaker/result_test.rb +42 -0
  119. data/test/honeymaker/utils_test.rb +64 -0
  120. data/test/native/backend_switch_test.rb +53 -0
  121. data/test/native/convert_test.rb +137 -0
  122. data/test/native/gem_isolation_test.rb +17 -0
  123. data/test/native/loader_test.rb +29 -0
  124. data/test/native/lockstep_test.rb +23 -0
  125. data/test/native/signing_smoke_test.rb +28 -0
  126. data/test/parity/kraken_client_parity_test.rb +398 -0
  127. data/test/parity/kraken_exchange_parity_test.rb +172 -0
  128. data/test/release/branch_hardening_test.rb +34 -0
  129. data/test/release/push_gems_test.rb +79 -0
  130. data/test/smoke.rb +24 -0
  131. data/test/support/fixture_helper.rb +13 -0
  132. data/test/support/kraken_signing_smoke.rb +21 -0
  133. data/test/support/parity_helper.rb +62 -0
  134. data/test/support/venue_double.rb +100 -0
  135. data/test/test_helper.rb +9 -0
  136. metadata +254 -0
@@ -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