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.
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,28 @@
1
+ #!/usr/bin/env bash
2
+ # Push built gems: platform gems first, the source gem last. Idempotent: a gem already on
3
+ # rubygems.org is accepted only if byte-identical (a rerun after a partial publish).
4
+ set -euo pipefail
5
+ dir="${1:?gem dir}"
6
+ version="${2:?version}"
7
+ api="${RUBYGEMS_API:-https://rubygems.org}"
8
+ # Exactly the release set, or nothing is pushed: 5 platform gems + the source gem.
9
+ expected=(x86_64-linux aarch64-linux x86_64-darwin arm64-darwin x64-mingw-ucrt)
10
+ for plat in "${expected[@]}"; do
11
+ [ -f "$dir/honeymaker-$version-$plat.gem" ] || { echo "::error::missing honeymaker-$version-$plat.gem" >&2; exit 1; }
12
+ done
13
+ [ -f "$dir/honeymaker-$version.gem" ] || { echo "::error::missing honeymaker-$version.gem (source)" >&2; exit 1; }
14
+ count=$(find "$dir" -maxdepth 1 -name '*.gem' | wc -l | tr -d ' ')
15
+ [ "$count" = 6 ] || { echo "::error::expected 6 gems in $dir, found $count" >&2; exit 1; }
16
+ push() {
17
+ local g="$1" ver plat sha remote
18
+ if gem push "$g"; then return 0; fi
19
+ ver=$(ruby -rrubygems/package -e 'print Gem::Package.new(ARGV[0]).spec.version' "$g")
20
+ plat=$(ruby -rrubygems/package -e 'print Gem::Package.new(ARGV[0]).spec.platform' "$g")
21
+ sha=$(ruby -rdigest -e 'print Digest::SHA256.file(ARGV[0]).hexdigest' "$g")
22
+ remote=$(curl -fsS "$api/api/v2/rubygems/honeymaker/versions/${ver}.json?platform=${plat}" \
23
+ | ruby -rjson -e 'print JSON.parse($stdin.read)["sha"]')
24
+ if [ "$sha" != "$remote" ]; then echo "::error::$g differs from the published gem" >&2; return 1; fi
25
+ echo "$g already published with the same checksum"
26
+ }
27
+ for plat in "${expected[@]}"; do push "$dir/honeymaker-$version-$plat.gem"; done
28
+ push "$dir/honeymaker-$version.gem"
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regenerates crates/honeymaker/tests/vectors/*.json from the LEGACY Ruby implementation.
4
+ # bundle exec ruby -Ilib script/vectors/kraken.rb
5
+ require "honeymaker"
6
+ require "json"
7
+ require "bigdecimal"
8
+
9
+ out_dir = File.expand_path("../../crates/honeymaker/tests/vectors", __dir__)
10
+
11
+ secrets = [
12
+ Base64.strict_encode64("test_secret_key_1234567890123456"),
13
+ "dGVzd A==\n!!-_x", "abc", "=abc", "", " ", "a", "ab==cd", "not base64 at all ~~~",
14
+ Base64.strict_encode64((0..255).to_a.pack("C*"))
15
+ ]
16
+ bodies = [
17
+ [["nonce", "1727000000000001"]],
18
+ [["nonce", "1727000000000002"], ["ordertype", "market"], ["type", "buy"], ["volume", "0.001"], ["pair", "XBTUSDT"],
19
+ ["oflags", "viqc"], ["close[ordertype]", "limit"]],
20
+ [["nonce", "1727000000000003"], ["txid", "O1,O2"], ["consolidate_taker", "true"]],
21
+ [["nonce", "1727000000000004"], ["asset", "a b~*-._/,:[]\u00e9+&="]]
22
+ ]
23
+ paths = %w[/0/private/BalanceEx /0/private/AddOrder /0/private/QueryOrders /0/private/Earn/Allocations]
24
+
25
+ signing = []
26
+ secrets.each do |secret|
27
+ [["key", secret], ["", secret], ["key", ""]].each do |key, sec|
28
+ bodies.each_with_index do |pairs, i|
29
+ body = URI.encode_www_form(pairs)
30
+ c = Honeymaker::Clients::Kraken.new(api_key: key, api_secret: sec)
31
+ signing << { api_key: key, api_secret: sec, path: paths[i], pairs: pairs, body: body,
32
+ decoded_secret_hex: Base64.decode64(sec).unpack1("H*"),
33
+ headers: c.send(:private_headers, paths[i], body).transform_keys(&:to_s) }
34
+ end
35
+ end
36
+ end
37
+
38
+ decimal_inputs = ["0.5", "1", "-0", "-0.0000", "0.0000000001", "123456789012345678901234567890.123456789",
39
+ "1e-9", "1E5", " 1.5 ", "1_000.5", ".5", "5.", "+2", "", "abc", "1,5", "0x10", "NaN",
40
+ "Infinity", "-Infinity", "1d2", "true"]
41
+ decimals = decimal_inputs.map do |s|
42
+ v = BigDecimal(s)
43
+ { input: s, ok: true, finite: v.finite?, plain: v.finite? ? v.to_s("F") : v.to_s, sign: v.sign }
44
+ rescue ArgumentError => e
45
+ { input: s, ok: false, error: e.message }
46
+ end
47
+
48
+ File.write(File.join(out_dir, "kraken_signing.json"), JSON.pretty_generate(signing))
49
+ File.write(File.join(out_dir, "decimal.json"), JSON.pretty_generate(decimals))
50
+ puts "wrote #{signing.size} signing vectors, #{decimals.size} decimals"
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regenerates the stage-2 vectors from the LEGACY Ruby implementation.
4
+ # bundle exec ruby -Ilib script/vectors/kraken_stage2.rb
5
+ require "honeymaker"
6
+ require "json"
7
+ require "faraday"
8
+
9
+ OUT = File.expand_path("../../crates/honeymaker/tests/vectors", __dir__)
10
+ SECRET = Base64.strict_encode64("test_secret_key_1234567890123456")
11
+ NONCE = 1_727_000_000_000_001
12
+
13
+ def legacy
14
+ c = Honeymaker::Clients::Kraken.new(api_key: "key", api_secret: SECRET)
15
+ c.define_singleton_method(:nonce) { NONCE }
16
+ c
17
+ end
18
+
19
+ # --- A: AddOrder with cl_ord_id and deadline, as legacy signs and sends it ---
20
+ orders = [
21
+ { ordertype: "market", type: "buy", volume: "0.0012", pair: "XBTEUR", oflags: ["viqc"],
22
+ cl_ord_id: "6f1c1a52-7c8e-4d0e-9a57-0b6f0f1d2e3a", deadline: "2026-09-30T12:00:10.000Z" },
23
+ { ordertype: "limit", type: "buy", volume: "0.00119", pair: "XBTEUR", price: "49870.1", oflags: [],
24
+ cl_ord_id: "0d9e2c1b-2f4a-4b8c-8d1e-5a6b7c8d9e0f", deadline: "2026-09-30T12:00:10.123Z" },
25
+ { ordertype: "market", type: "buy", volume: "60", pair: "XBTEUR", oflags: ["viqc"],
26
+ cl_ord_id: "6f1c1a52-7c8e-4d0e-9a57-0b6f0f1d2e3a", deadline: "2026-09-30T12:00:10.000Z", validate: true }
27
+ ]
28
+ wire = orders.map do |args|
29
+ body = nil
30
+ stubs = Faraday::Adapter::Test::Stubs.new do |s|
31
+ s.post("/0/private/AddOrder") do |env|
32
+ body = env.body
33
+ [200, { "Content-Type" => "application/json" }, '{"error":[],"result":{"txid":["OTX-1"]}}']
34
+ end
35
+ end
36
+ c = legacy
37
+ c.instance_variable_set(:@connection, Faraday.new(url: Honeymaker::Clients::Kraken::URL) { |f| f.response :json; f.adapter :test, stubs })
38
+ raise "legacy did not answer" unless c.add_order(**args).success?
39
+
40
+ { args: args, nonce: NONCE, api_key: "key", api_secret: SECRET, body: body,
41
+ headers: c.send(:private_headers, "/0/private/AddOrder", body).transform_keys(&:to_s) }
42
+ end
43
+ File.write(File.join(OUT, "kraken_add_order_wire.json"), JSON.pretty_generate(wire))
44
+ puts "wrote #{wire.size} AddOrder wire vectors"
45
+
46
+ # --- B: closed_orders_from_trades, the verbatim Ruby loop (with Task 8's dedupe), over scripted
47
+ # pages. Each case says what the STRICT Rust scan must do: "same" as Ruby (a completed scan, a
48
+ # refusal, an unreadable page or a Ruby exception), "incomplete" where Ruby stopped early or
49
+ # trusted a malformed container and returned what it had, or "error" where Ruby skipped a malformed
50
+ # record that Rust must refuse. The label is what Rust must do, not what Ruby did: some
51
+ # "incomplete" cases are Ruby raises (NoMethodError on trades as a non-empty array, a non-empty
52
+ # string or a number, and on count true or an array), not partial answers. Their "outcome" says so.
53
+ def page(trades, count = :none)
54
+ result = { "trades" => trades }
55
+ result["count"] = count unless count == :none
56
+ { "error" => [], "result" => result }
57
+ end
58
+
59
+ def trade(o, vol, cost, type: "buy", ordertype: "market", fee: "0.1")
60
+ { "ordertxid" => o, "vol" => vol, "cost" => cost, "fee" => fee, "type" => type, "ordertype" => ordertype, "time" => 1.5 }
61
+ end
62
+
63
+ T = ->(i, o = "O1") { { "T#{i}" => trade(o, "1", "2") } }
64
+ cases = [
65
+ ["one page", :same, %w[O1], 1_700_000_000, 20, [page({ "T1" => trade("O1", "0.0006", "30"), "T2" => trade("O1", "0.0006", "30") }, 2)]],
66
+ ["partial fill across pages, unwanted skipped", :same, %w[O1 O2], 1_700_000_000, 20,
67
+ [page({ "T1" => trade("O1", "1", "2"), "T2" => trade("O9", "1", "2") }, 3),
68
+ page({ "T3" => trade("O1", "1", "2"), "T4" => trade("O2", "2", "8", ordertype: "limit", type: "sell") }, 3)]],
69
+ ["no wanted ids: no request", :same, [], nil, 20, []],
70
+ ["a trade on two pages: rows reach count, distinct trades do not", :incomplete, %w[O1], nil, 20,
71
+ [page({ "T1" => trade("O1", "1", "2"), "T2" => trade("O1", "3", "9") }, 3), page({ "T2" => trade("O1", "3", "9") }, 3)]],
72
+ ["overlapping pages that cover every trade", :same, %w[O1], nil, 20,
73
+ [page({ "T1" => trade("O1", "1", "2"), "T2" => trade("O1", "3", "9") }, 3),
74
+ page({ "T2" => trade("O1", "3", "9"), "T3" => trade("O1", "1", "1") }, 3)]],
75
+ ["complete exactly at the page cap", :same, %w[O1], nil, 2, [page(T[1], 2), page(T[2], 2)]],
76
+ ["an empty page after the last trade", :same, %w[O1], nil, 20, [page({}, 0)]],
77
+ ["a fill beyond the page cap", :incomplete, %w[O1], nil, 2, [page(T[1, "O9"], 3), page(T[2, "O9"], 3), page(T[3], 3)]],
78
+ ["count as a string", :incomplete, %w[O1], nil, 20, [page(T[1], "2"), page(T[2], "2")]],
79
+ ["count 1_000 pages to max_pages", :incomplete, %w[O1], nil, 2, [page(T[1], "1_000"), page(T[2], "1_000"), page(T[3], "1_000")]],
80
+ ["bignum string count", :incomplete, %w[O1], nil, 3, [page(T[1], "99999999999999999999999"), page(T[2], "1"), page(T[3], "1")]],
81
+ ["bignum integer count", :incomplete, %w[O1], nil, 3, [page(T[1], 10**25), page(T[2], 10**25), page(T[3], 10**25), page(T[4], 10**25)]],
82
+ ["float count", :incomplete, %w[O1], nil, 20, [page(T[1], 2.9), page(T[2], 2.9), page(T[3], 2.9)]],
83
+ ["missing count", :incomplete, %w[O1], nil, 20, [page(T[1]), page(T[2])]],
84
+ ["nil count", :incomplete, %w[O1], nil, 20, [page(T[1], nil), page(T[2])]],
85
+ ["negative count", :incomplete, %w[O1], nil, 20, [page(T[1], -1), page(T[2])]],
86
+ *[" 3", "3abc", "0x10", "_3", "3__0", "+3", "-3", "", "abc"].map do |c|
87
+ ["count #{c.inspect}", :incomplete, %w[O1], nil, 4, (1..4).map { |i| page(T[i], c) }]
88
+ end,
89
+ ["empty trades before count", :incomplete, %w[O1], nil, 20, [page({}, 5), page(T[1], 5)]],
90
+ ["missing trades", :incomplete, %w[O1], nil, 20, [{ "error" => [], "result" => { "count" => 3 } }, page(T[1])]],
91
+ ["trades false", :incomplete, %w[O1], nil, 20, [page(false, 3), page(T[1])]],
92
+ ["trades []", :incomplete, %w[O1], nil, 20, [page([], 3), page(T[1])]],
93
+ ["trades \"\"", :incomplete, %w[O1], nil, 20, [page("", 3), page(T[1])]],
94
+ ["nil result", :incomplete, %w[O1], nil, 20, [{ "error" => [] }, page(T[1])]],
95
+ ["result as a string raises", :same, %w[O1], nil, 20, [{ "error" => [], "result" => "x" }]],
96
+ ["result as an array raises", :same, %w[O1], nil, 20, [{ "error" => [], "result" => [1] }]],
97
+ ["result false raises", :same, %w[O1], nil, 20, [{ "error" => [], "result" => false }]],
98
+ ["trades as a non-empty array", :incomplete, %w[O1], nil, 20, [page([1], 1)]],
99
+ ["trades as a non-empty string", :incomplete, %w[O1], nil, 20, [page("x", 1)]],
100
+ ["trades as a number", :incomplete, %w[O1], nil, 20, [page(5, 1)]],
101
+ *[[false, "false"], [[], "[]"], ["", "\"\""], [nil, "null"]].map do |trades, label|
102
+ ["trades #{label} with count 0", :incomplete, %w[O1], nil, 20, [page(trades, 0)]]
103
+ end,
104
+ ["trades missing with count 0", :incomplete, %w[O1], nil, 20, [{ "error" => [], "result" => { "count" => 0 } }]],
105
+ ["a trade that is a string", :error, %w[O1], nil, 20, [page({ "T1" => "no id here", "T2" => trade("O1", "1", "2") }, 2)]],
106
+ ["a record without ordertxid", :error, %w[O1], nil, 20, [page({ "T1" => { "vol" => "1", "cost" => "2" } }, 1)]],
107
+ ["a record with a numeric ordertxid", :error, %w[O1], nil, 20, [page({ "T1" => trade(5, "1", "2") }, 1)]],
108
+ ["a trade that is a number raises", :same, %w[O1], nil, 20, [page({ "T1" => 5 }, 1)]],
109
+ ["a trade that is null raises", :same, %w[O1], nil, 20, [page({ "T1" => nil }, 1)]],
110
+ ["count true", :incomplete, %w[O1], nil, 20, [page(T[1], true)]],
111
+ ["count as an array", :incomplete, %w[O1], nil, 20, [page(T[1], [1])]],
112
+ ["refusal on page 2", :same, %w[O1], nil, 20, [page(T[1], 5), { "error" => ["EAPI:Rate limit exceeded"] }]],
113
+ ["falsy errors are not a refusal", :same, %w[O1], nil, 20, [{ "error" => [nil, false], "result" => { "trades" => T[1], "count" => 1 } }]],
114
+ ["non-object page 2 is unreadable", :same, %w[O1], nil, 20, [page(T[1], 5), "oops"]],
115
+ ["pages run out: legacy sees null", :same, %w[O1], nil, 20, [page(T[1], 5)]],
116
+ ["zero volume has no price", :same, %w[O1], nil, 20, [page({ "T1" => trade("O1", "0", "0") }, 1)]]
117
+ ]
118
+
119
+ pager = cases.map do |name, strict, ids, start, max_pages, pages|
120
+ texts = pages.map { |p| JSON.generate(p) }
121
+ queue = texts.dup
122
+ requests = []
123
+ c = legacy
124
+ c.define_singleton_method(:get_trades_history) do |start: nil, ofs: nil, **|
125
+ requests << [start, ofs]
126
+ Honeymaker::Result::Success.new(JSON.parse(queue.shift || "null"))
127
+ end
128
+ outcome = begin
129
+ r = c.closed_orders_from_trades(order_ids: ids, start: start, max_pages: max_pages)
130
+ if r.success?
131
+ { "ok" => r.data.map do |txid, a|
132
+ [txid, { "vol" => a[:amount_exec].to_s("F"), "cost" => a[:quote_amount_exec].to_s("F"), "fee" => a[:fee].to_s("F"),
133
+ "price" => a[:price]&.to_s("F"), "side" => a[:side]&.to_s, "order_type" => a[:order_type].to_s }]
134
+ end }
135
+ elsif r.data.is_a?(Hash) && r.data[:unreadable]
136
+ { "unreadable" => true }
137
+ else
138
+ { "venue" => r.errors }
139
+ end
140
+ rescue StandardError => e
141
+ { "raise" => e.class.name }
142
+ end
143
+ { name: name, strict: strict, order_ids: ids, start: start, max_pages: max_pages, pages: texts, requests: requests, outcome: outcome }
144
+ end
145
+ File.write(File.join(OUT, "kraken_trades_pager.json"), JSON.pretty_generate(pager))
146
+ puts "wrote #{pager.size} trades-pager vectors"
@@ -0,0 +1,4 @@
1
+ module Honeymaker
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
@@ -0,0 +1,7 @@
1
+ {
2
+ "symbol": "BTCUSDT",
3
+ "bidPrice": "67123.45000000",
4
+ "bidQty": "1.23456700",
5
+ "askPrice": "67125.67000000",
6
+ "askQty": "0.98765400"
7
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "symbols": [
3
+ {
4
+ "symbol": "BTCUSDT",
5
+ "status": "TRADING",
6
+ "baseAsset": "BTC",
7
+ "quoteAsset": "USDT",
8
+ "quoteAssetPrecision": 8,
9
+ "filters": [
10
+ {
11
+ "filterType": "PRICE_FILTER",
12
+ "minPrice": "0.01",
13
+ "maxPrice": "1000000.00",
14
+ "tickSize": "0.01"
15
+ },
16
+ {
17
+ "filterType": "LOT_SIZE",
18
+ "minQty": "0.00000100",
19
+ "maxQty": "9000.00000000",
20
+ "stepSize": "0.000001"
21
+ },
22
+ {
23
+ "filterType": "NOTIONAL",
24
+ "minNotional": "5.00000000",
25
+ "maxNotional": "9000000.00000000"
26
+ }
27
+ ]
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "data": {
3
+ "symbols": [
4
+ {
5
+ "symbol": "BTC-USDT",
6
+ "minQty": "0.00001",
7
+ "maxQty": "500",
8
+ "minNotional": "1",
9
+ "maxNotional": "5000000",
10
+ "stepSize": "0.00001",
11
+ "tickSize": "0.01",
12
+ "status": 1
13
+ },
14
+ {
15
+ "symbol": "INVALID",
16
+ "minQty": 1,
17
+ "maxQty": 1000,
18
+ "minNotional": 1,
19
+ "maxNotional": 10000,
20
+ "stepSize": "1",
21
+ "tickSize": "0.01",
22
+ "status": 1
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "code": 0,
3
+ "data": [
4
+ {
5
+ "symbol": "BTC-USDT",
6
+ "bestBidPrice": "67123.45",
7
+ "bestBidQty": "1.234567",
8
+ "bestAskPrice": "67125.67",
9
+ "bestAskQty": "0.987654"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "symbol": "BTCUSDT",
5
+ "baseCoin": "BTC",
6
+ "quoteCoin": "USDT",
7
+ "minTradeAmount": "0.00001",
8
+ "minTradeUSDT": "5",
9
+ "maxTradeAmount": "9000",
10
+ "quantityPrecision": "6",
11
+ "quotePrecision": "8",
12
+ "pricePrecision": "2",
13
+ "status": "online"
14
+ },
15
+ {
16
+ "symbol": "ETHUSDT",
17
+ "baseCoin": "ETH",
18
+ "quoteCoin": "USDT",
19
+ "minTradeAmount": "0.001",
20
+ "minTradeUSDT": "5",
21
+ "maxTradeAmount": "5000",
22
+ "quantityPrecision": "4",
23
+ "quotePrecision": "8",
24
+ "pricePrecision": "2",
25
+ "status": "offline"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "code": "00000",
3
+ "msg": "success",
4
+ "data": [
5
+ {
6
+ "symbol": "BTCUSDT",
7
+ "bestBid": "67123.45",
8
+ "bestAsk": "67125.67",
9
+ "lastPr": "67124.56",
10
+ "high24h": "67500.00",
11
+ "low24h": "66800.00"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "data": {
3
+ "symbols": [
4
+ {
5
+ "symbol": "BTC_USDT",
6
+ "base_currency": "BTC",
7
+ "quote_currency": "USDT",
8
+ "base_min_size": "0.00001",
9
+ "min_buy_amount": "5",
10
+ "quote_increment": "0.01",
11
+ "price_max_precision": 2,
12
+ "trade_status": "trading"
13
+ },
14
+ {
15
+ "symbol": "ETH_USDT",
16
+ "base_currency": "ETH",
17
+ "quote_currency": "USDT",
18
+ "base_min_size": "0.001",
19
+ "min_buy_amount": "5",
20
+ "quote_increment": "0.0001",
21
+ "price_max_precision": 4,
22
+ "trade_status": "pre-trade"
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "code": 1000,
3
+ "message": "OK",
4
+ "data": {
5
+ "symbol": "BTC_USDT",
6
+ "last": "67124.56",
7
+ "best_bid": "67123.45",
8
+ "best_ask": "67125.67",
9
+ "high_24h": "67500.00",
10
+ "low_24h": "66800.00"
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "symbol": "BTCUSDT",
3
+ "bidPrice": "67123.45",
4
+ "bidQty": "1.234567",
5
+ "askPrice": "67125.67",
6
+ "askQty": "0.987654"
7
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "symbols": [
3
+ {
4
+ "symbol": "BTCUSDT",
5
+ "baseAsset": "btc",
6
+ "quoteAsset": "usdt",
7
+ "baseAssetPrecision": 8,
8
+ "quotePrecision": 2,
9
+ "status": "TRADING",
10
+ "filters": [
11
+ {
12
+ "filterType": "PRICE_FILTER",
13
+ "tickSize": "0.01"
14
+ },
15
+ {
16
+ "filterType": "LOT_SIZE",
17
+ "minQty": "0.00001",
18
+ "maxQty": "9000",
19
+ "stepSize": "0.00001",
20
+ "minVal": "5"
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "symbol": "ETHUSDT",
26
+ "baseAsset": "eth",
27
+ "quoteAsset": "usdt",
28
+ "baseAssetPrecision": 6,
29
+ "quotePrecision": 2,
30
+ "status": "TRADING",
31
+ "filters": []
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "market": "BTC-EUR",
3
+ "bid": "67123.45",
4
+ "bidSize": "1.234567",
5
+ "ask": "67125.67",
6
+ "askSize": "0.987654"
7
+ }
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "market": "BTC-EUR",
4
+ "status": "trading",
5
+ "base": "BTC",
6
+ "quote": "EUR",
7
+ "minOrderInBaseAsset": "0.00001",
8
+ "minOrderInQuoteAsset": "5",
9
+ "maxOrderInBaseAsset": "1000",
10
+ "maxOrderInQuoteAsset": "1000000",
11
+ "pricePrecision": null,
12
+ "tickSize": "1.00",
13
+ "quantityDecimals": 8,
14
+ "notionalDecimals": 2,
15
+ "orderTypes": ["market", "limit"]
16
+ },
17
+ {
18
+ "market": "ETH-EUR",
19
+ "status": "halted",
20
+ "base": "ETH",
21
+ "quote": "EUR",
22
+ "minOrderInBaseAsset": "0.0001",
23
+ "minOrderInQuoteAsset": "5",
24
+ "pricePrecision": null,
25
+ "tickSize": "0.01",
26
+ "quantityDecimals": 6,
27
+ "notionalDecimals": 2,
28
+ "orderTypes": ["market", "limit"]
29
+ }
30
+ ]
@@ -0,0 +1,23 @@
1
+ {
2
+ "result": {
3
+ "list": [
4
+ {
5
+ "symbol": "BTCUSDT",
6
+ "baseCoin": "BTC",
7
+ "quoteCoin": "USDT",
8
+ "status": "Trading",
9
+ "lotSizeFilter": {
10
+ "minOrderQty": "0.000048",
11
+ "maxOrderQty": "71.73956243",
12
+ "minOrderAmt": "1",
13
+ "maxOrderAmt": "4000000",
14
+ "basePrecision": "0.000001",
15
+ "quotePrecision": "0.00000001"
16
+ },
17
+ "priceFilter": {
18
+ "tickSize": "0.01"
19
+ }
20
+ }
21
+ ]
22
+ }
23
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "retCode": 0,
3
+ "retMsg": "OK",
4
+ "result": {
5
+ "category": "spot",
6
+ "list": [
7
+ {
8
+ "symbol": "BTCUSDT",
9
+ "bid1Price": "67123.45",
10
+ "bid1Size": "1.234567",
11
+ "ask1Price": "67125.67",
12
+ "ask1Size": "0.987654",
13
+ "lastPrice": "67124.56",
14
+ "highPrice24h": "67500.00",
15
+ "lowPrice24h": "66800.00"
16
+ }
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "product_id": "BTC-USD",
3
+ "price": "67124.56",
4
+ "bid": "67123.45",
5
+ "ask": "67125.67",
6
+ "volume_24h": "12345.678"
7
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "products": [
3
+ {
4
+ "product_id": "BTC-USD",
5
+ "base_increment": "0.00000001",
6
+ "quote_increment": "0.01",
7
+ "price_increment": "0.01",
8
+ "base_min_size": "0.00000001",
9
+ "quote_min_size": "1",
10
+ "base_max_size": "3400",
11
+ "quote_max_size": "250000000"
12
+ },
13
+ {
14
+ "product_id": "RNDR-USD",
15
+ "base_increment": "0.01",
16
+ "quote_increment": "0.01",
17
+ "price_increment": "0.01",
18
+ "base_min_size": "0.01",
19
+ "quote_min_size": "1",
20
+ "base_max_size": "10000",
21
+ "quote_max_size": "250000000"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "bid": "67123.45",
3
+ "ask": "67125.67",
4
+ "last": "67124.56",
5
+ "volume": {
6
+ "BTC": "1234.5678",
7
+ "USD": "82888888.88",
8
+ "timestamp": 1700000000000
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "symbol": "BTCUSD",
3
+ "base_currency": "btc",
4
+ "quote_currency": "usd",
5
+ "tick_size": "0.00000001",
6
+ "quote_increment": "0.01",
7
+ "min_order_size": "0.00001",
8
+ "status": "open",
9
+ "product_type": "spot"
10
+ }
@@ -0,0 +1 @@
1
+ ["btcusd", "ethusd"]
@@ -0,0 +1,5 @@
1
+ {
2
+ "PURR/USDC": "0.001234",
3
+ "BTC/USDC": "67124.56",
4
+ "ETH/USDC": "3456.78"
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "tokens": [
3
+ { "name": "PURR", "index": 0, "szDecimals": 2 },
4
+ { "name": "USDC", "index": 1, "szDecimals": 6 }
5
+ ],
6
+ "universe": [
7
+ {
8
+ "name": "PURR/USDC",
9
+ "tokens": [0, 1]
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "error": [],
3
+ "result": {
4
+ "XBTUSDT": {
5
+ "altname": "XBTUSDT",
6
+ "wsname": "XBT/USDT",
7
+ "base": "XXBT",
8
+ "quote": "USDT",
9
+ "lot_decimals": 8,
10
+ "cost_decimals": 5,
11
+ "pair_decimals": 1,
12
+ "ordermin": "0.00010000",
13
+ "costmin": "0.50000",
14
+ "aclass_base": "currency"
15
+ },
16
+ "NVDAxUSD": {
17
+ "altname": "NVDAxUSD",
18
+ "wsname": "NVDAx/USD",
19
+ "aclass_base": "tokenized_asset",
20
+ "base": "NVDAx",
21
+ "quote": "ZUSD",
22
+ "lot_decimals": 8,
23
+ "cost_decimals": 5,
24
+ "pair_decimals": 2,
25
+ "ordermin": "0.00000001",
26
+ "costmin": "0.5",
27
+ "status": "online"
28
+ },
29
+ "NVDASPVUSD": {
30
+ "altname": "NVDAxUSD",
31
+ "wsname": "NVDAx/USD",
32
+ "aclass_base": "tokenized_asset",
33
+ "base": "NVDAx",
34
+ "quote": "ZUSD",
35
+ "lot_decimals": 8,
36
+ "cost_decimals": 5,
37
+ "pair_decimals": 2,
38
+ "ordermin": "0.00000001",
39
+ "costmin": "0.5",
40
+ "status": "online"
41
+ }
42
+ }
43
+ }