perp-cli 0.14.3 → 0.15.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.
Files changed (311) hide show
  1. package/README.md +152 -13
  2. package/dist/agent-wallet/store.d.ts +3 -2
  3. package/dist/agent-wallet/store.js +2 -2
  4. package/dist/api/public/hyperliquid.js +4 -0
  5. package/dist/api/public/pacifica.js +83 -13
  6. package/dist/api/public/urls.d.ts +2 -0
  7. package/dist/api/public/urls.js +3 -0
  8. package/dist/arb/utils.d.ts +1 -2
  9. package/dist/arb/utils.js +1 -2
  10. package/dist/bot/apex/pulse.js +3 -0
  11. package/dist/bot/apex/radar.js +2 -1
  12. package/dist/bot/engine.d.ts +10 -1
  13. package/dist/bot/engine.js +14 -6
  14. package/dist/bot/indicators.d.ts +7 -0
  15. package/dist/bot/indicators.js +16 -0
  16. package/dist/bot/perp-order.d.ts +4 -4
  17. package/dist/bot/perp-order.js +5 -5
  18. package/dist/bot/quarantine.d.ts +1 -1
  19. package/dist/bot/quarantine.js +1 -1
  20. package/dist/bot/strategies/apex-strategy.js +3 -2
  21. package/dist/bot/strategies/engine-mm.js +6 -5
  22. package/dist/bot/strategies/funding-arb-strategy.js +2 -2
  23. package/dist/bot/strategies/funding-arb-utils.d.ts +1 -1
  24. package/dist/bot/strategies/funding-arb-utils.js +5 -5
  25. package/dist/bot/strategies/funding-auto.js +2 -2
  26. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  27. package/dist/bot/strategies/liquidation-mm.js +8 -2
  28. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  29. package/dist/bot/strategies/momentum-breakout.js +11 -3
  30. package/dist/bot/strategies/spot-perp-arb-strategy.js +2 -2
  31. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  32. package/dist/bot/strategies/spot-perp-executor.js +1 -1
  33. package/dist/bot/strategy-types.d.ts +2 -1
  34. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  35. package/dist/bot/tui/BotDashboard.js +1 -1
  36. package/dist/cache.js +12 -6
  37. package/dist/cli-program.d.ts +15 -0
  38. package/dist/cli-program.js +125 -5
  39. package/dist/commands/agent-import.d.ts +7 -0
  40. package/dist/commands/agent-import.js +30 -1
  41. package/dist/commands/agent.d.ts +6 -3
  42. package/dist/commands/agent.js +160 -12
  43. package/dist/commands/arb/index.js +4 -4
  44. package/dist/commands/arb-auto.js +21 -17
  45. package/dist/commands/arb.d.ts +12 -0
  46. package/dist/commands/arb.js +61 -143
  47. package/dist/commands/bot.js +1 -1
  48. package/dist/commands/builder.js +1 -1
  49. package/dist/commands/funds.js +113 -3
  50. package/dist/commands/init.d.ts +1 -1
  51. package/dist/commands/init.js +27 -7
  52. package/dist/commands/manage.js +23 -21
  53. package/dist/commands/market.js +167 -35
  54. package/dist/commands/options.d.ts +9 -0
  55. package/dist/commands/options.js +134 -0
  56. package/dist/commands/pacifica-earn.d.ts +5 -0
  57. package/dist/commands/pacifica-earn.js +468 -0
  58. package/dist/commands/pacifica-orders.d.ts +14 -0
  59. package/dist/commands/pacifica-orders.js +142 -0
  60. package/dist/commands/portfolio.js +32 -47
  61. package/dist/commands/rebalance.d.ts +1 -0
  62. package/dist/commands/rebalance.js +15 -0
  63. package/dist/commands/risk.d.ts +11 -2
  64. package/dist/commands/risk.js +39 -44
  65. package/dist/commands/trade.d.ts +1 -0
  66. package/dist/commands/trade.js +63 -58
  67. package/dist/commands/wallet.js +1 -1
  68. package/dist/config.d.ts +2 -2
  69. package/dist/config.js +15 -4
  70. package/dist/constants.js +6 -0
  71. package/dist/dashboard/server.js +1 -1
  72. package/dist/errors.d.ts +24 -1
  73. package/dist/errors.js +25 -3
  74. package/dist/exchanges/arcus/adapter.d.ts +7 -7
  75. package/dist/exchanges/arcus/adapter.js +12 -12
  76. package/dist/exchanges/arcus/agent.d.ts +1 -1
  77. package/dist/exchanges/arcus/agent.js +1 -1
  78. package/dist/exchanges/arcus/client.js +1 -1
  79. package/dist/exchanges/arcus/signing.d.ts +2 -3
  80. package/dist/exchanges/arcus/signing.js +1 -1
  81. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  82. package/dist/exchanges/arcus/spot-chain.js +1 -1
  83. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  84. package/dist/exchanges/arcus/spot-router.js +2 -2
  85. package/dist/exchanges/arcus/spot.d.ts +2 -3
  86. package/dist/exchanges/arcus/spot.js +2 -3
  87. package/dist/exchanges/aster-spot.d.ts +3 -3
  88. package/dist/exchanges/aster-spot.js +6 -9
  89. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  90. package/dist/exchanges/aster-typed-data.js +1 -1
  91. package/dist/exchanges/aster.d.ts +4 -4
  92. package/dist/exchanges/aster.js +31 -13
  93. package/dist/exchanges/builder-fee.d.ts +1 -1
  94. package/dist/exchanges/builder-fee.js +13 -2
  95. package/dist/exchanges/canonical-symbols.d.ts +1 -2
  96. package/dist/exchanges/canonical-symbols.js +26 -2
  97. package/dist/exchanges/derive/client.d.ts +37 -0
  98. package/dist/exchanges/derive/client.js +135 -0
  99. package/dist/exchanges/derive/options.d.ts +68 -0
  100. package/dist/exchanges/derive/options.js +360 -0
  101. package/dist/exchanges/extended/adapter.d.ts +82 -0
  102. package/dist/exchanges/extended/adapter.js +179 -0
  103. package/dist/exchanges/extended/client.d.ts +38 -0
  104. package/dist/exchanges/extended/client.js +88 -0
  105. package/dist/exchanges/extended/public.d.ts +130 -0
  106. package/dist/exchanges/extended/public.js +429 -0
  107. package/dist/exchanges/grvt/adapter.d.ts +86 -0
  108. package/dist/exchanges/grvt/adapter.js +206 -0
  109. package/dist/exchanges/grvt/client.d.ts +25 -0
  110. package/dist/exchanges/grvt/client.js +86 -0
  111. package/dist/exchanges/grvt/public.d.ts +151 -0
  112. package/dist/exchanges/grvt/public.js +438 -0
  113. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  114. package/dist/exchanges/hyperliquid-spot.js +5 -5
  115. package/dist/exchanges/hyperliquid.d.ts +22 -12
  116. package/dist/exchanges/hyperliquid.js +80 -34
  117. package/dist/exchanges/index.d.ts +1 -1
  118. package/dist/exchanges/index.js +1 -1
  119. package/dist/exchanges/interface.d.ts +94 -6
  120. package/dist/exchanges/interface.js +16 -0
  121. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  122. package/dist/exchanges/ioc-outcome.js +1 -1
  123. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  124. package/dist/exchanges/legacy-trading-v2.js +7 -2
  125. package/dist/exchanges/lighter-public.d.ts +2 -2
  126. package/dist/exchanges/lighter-public.js +2 -2
  127. package/dist/exchanges/lighter-signer.d.ts +1 -1
  128. package/dist/exchanges/lighter-signer.js +1 -1
  129. package/dist/exchanges/lighter-spot.d.ts +3 -4
  130. package/dist/exchanges/lighter-spot.js +6 -7
  131. package/dist/exchanges/lighter.d.ts +11 -11
  132. package/dist/exchanges/lighter.js +28 -14
  133. package/dist/exchanges/ondo/adapter.d.ts +5 -6
  134. package/dist/exchanges/ondo/adapter.js +16 -14
  135. package/dist/exchanges/ondo/agent.d.ts +1 -1
  136. package/dist/exchanges/ondo/agent.js +1 -1
  137. package/dist/exchanges/ondo/client.d.ts +1 -1
  138. package/dist/exchanges/ondo/client.js +2 -2
  139. package/dist/exchanges/ondo/public.d.ts +8 -1
  140. package/dist/exchanges/ondo/public.js +13 -2
  141. package/dist/exchanges/ondo/spot.d.ts +2 -2
  142. package/dist/exchanges/ondo/spot.js +3 -3
  143. package/dist/exchanges/options-interface.d.ts +158 -0
  144. package/dist/exchanges/options-interface.js +17 -0
  145. package/dist/exchanges/options-venues.d.ts +12 -0
  146. package/dist/exchanges/options-venues.js +35 -0
  147. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  148. package/dist/exchanges/pacifica-atomic.js +440 -0
  149. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  150. package/dist/exchanges/pacifica-decimal.js +51 -0
  151. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  152. package/dist/exchanges/pacifica-presigned.js +141 -0
  153. package/dist/exchanges/pacifica-print.d.ts +200 -0
  154. package/dist/exchanges/pacifica-print.js +417 -0
  155. package/dist/exchanges/pacifica-spot.d.ts +2 -2
  156. package/dist/exchanges/pacifica-spot.js +3 -3
  157. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  158. package/dist/exchanges/pacifica-typed-data.js +1 -1
  159. package/dist/exchanges/pacifica-vaults.d.ts +121 -0
  160. package/dist/exchanges/pacifica-vaults.js +264 -0
  161. package/dist/exchanges/pacifica.d.ts +32 -5
  162. package/dist/exchanges/pacifica.js +114 -48
  163. package/dist/exchanges/precision.d.ts +3 -1
  164. package/dist/exchanges/precision.js +9 -2
  165. package/dist/exchanges/qfex/adapter.d.ts +18 -5
  166. package/dist/exchanges/qfex/adapter.js +32 -12
  167. package/dist/exchanges/qfex/agent.d.ts +1 -1
  168. package/dist/exchanges/qfex/agent.js +1 -1
  169. package/dist/exchanges/qfex/client.d.ts +1 -1
  170. package/dist/exchanges/qfex/client.js +1 -1
  171. package/dist/exchanges/qfex/errors.d.ts +1 -1
  172. package/dist/exchanges/qfex/errors.js +2 -2
  173. package/dist/exchanges/qfex/public.d.ts +3 -3
  174. package/dist/exchanges/qfex/public.js +3 -3
  175. package/dist/exchanges/qfex/signing.d.ts +2 -2
  176. package/dist/exchanges/qfex/signing.js +4 -5
  177. package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
  178. package/dist/exchanges/qfex/ws-transport.js +3 -3
  179. package/dist/exchanges/rate-limiter.d.ts +1 -1
  180. package/dist/exchanges/rate-limiter.js +5 -2
  181. package/dist/exchanges/registry.d.ts +1 -1
  182. package/dist/exchanges/registry.js +1 -1
  183. package/dist/exchanges/risex/adapter.d.ts +148 -0
  184. package/dist/exchanges/risex/adapter.js +796 -0
  185. package/dist/exchanges/risex/agent.d.ts +154 -0
  186. package/dist/exchanges/risex/agent.js +384 -0
  187. package/dist/exchanges/risex/client.d.ts +47 -0
  188. package/dist/exchanges/risex/client.js +174 -0
  189. package/dist/exchanges/risex/nonce.d.ts +43 -0
  190. package/dist/exchanges/risex/nonce.js +212 -0
  191. package/dist/exchanges/risex/public.d.ts +64 -0
  192. package/dist/exchanges/risex/public.js +150 -0
  193. package/dist/exchanges/risex/session.d.ts +71 -0
  194. package/dist/exchanges/risex/session.js +171 -0
  195. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  196. package/dist/exchanges/risex/typed-data.js +240 -0
  197. package/dist/exchanges/risex/units.d.ts +29 -0
  198. package/dist/exchanges/risex/units.js +104 -0
  199. package/dist/exchanges/slippage.d.ts +1 -1
  200. package/dist/exchanges/slippage.js +1 -1
  201. package/dist/exchanges/spot-factory.d.ts +3 -3
  202. package/dist/exchanges/spot-factory.js +7 -7
  203. package/dist/exchanges/spot-interface.d.ts +5 -5
  204. package/dist/exchanges/spot-interface.js +2 -2
  205. package/dist/exchanges/spot-result.d.ts +3 -3
  206. package/dist/exchanges/spot-result.js +3 -3
  207. package/dist/exchanges/trading-v2.d.ts +3 -3
  208. package/dist/exchanges/trading-v2.js +9 -6
  209. package/dist/exchanges/txflow/adapter.d.ts +54 -0
  210. package/dist/exchanges/txflow/adapter.js +118 -0
  211. package/dist/exchanges/txflow/client.d.ts +47 -0
  212. package/dist/exchanges/txflow/client.js +153 -0
  213. package/dist/exchanges/txflow/public.d.ts +76 -0
  214. package/dist/exchanges/txflow/public.js +284 -0
  215. package/dist/exchanges/unknown-symbol.d.ts +29 -0
  216. package/dist/exchanges/unknown-symbol.js +51 -0
  217. package/dist/exchanges/variational/adapter.d.ts +71 -0
  218. package/dist/exchanges/variational/adapter.js +137 -0
  219. package/dist/exchanges/variational/client.d.ts +21 -0
  220. package/dist/exchanges/variational/client.js +66 -0
  221. package/dist/exchanges/variational/public.d.ts +126 -0
  222. package/dist/exchanges/variational/public.js +430 -0
  223. package/dist/exchanges/venue-types.d.ts +6 -7
  224. package/dist/exchanges/venue-types.js +2 -2
  225. package/dist/exchanges/venue.d.ts +51 -8
  226. package/dist/exchanges/venue.js +54 -4
  227. package/dist/exchanges/venues/arcus.d.ts +1 -2
  228. package/dist/exchanges/venues/arcus.js +5 -4
  229. package/dist/exchanges/venues/aster.d.ts +2 -2
  230. package/dist/exchanges/venues/aster.js +6 -4
  231. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  232. package/dist/exchanges/venues/create-helpers.js +3 -3
  233. package/dist/exchanges/venues/extended.d.ts +21 -0
  234. package/dist/exchanges/venues/extended.js +96 -0
  235. package/dist/exchanges/venues/grvt.d.ts +30 -0
  236. package/dist/exchanges/venues/grvt.js +98 -0
  237. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  238. package/dist/exchanges/venues/hyperliquid.js +10 -8
  239. package/dist/exchanges/venues/index.d.ts +7 -2
  240. package/dist/exchanges/venues/index.js +8 -2
  241. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  242. package/dist/exchanges/venues/lighter-family.js +9 -11
  243. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  244. package/dist/exchanges/venues/lighter-rh.js +7 -5
  245. package/dist/exchanges/venues/lighter.d.ts +1 -1
  246. package/dist/exchanges/venues/lighter.js +5 -3
  247. package/dist/exchanges/venues/ondo.d.ts +1 -1
  248. package/dist/exchanges/venues/ondo.js +5 -3
  249. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  250. package/dist/exchanges/venues/pacifica.js +6 -4
  251. package/dist/exchanges/venues/qfex.d.ts +1 -1
  252. package/dist/exchanges/venues/qfex.js +4 -2
  253. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  254. package/dist/exchanges/venues/risex-release.js +14 -0
  255. package/dist/exchanges/venues/risex.d.ts +31 -0
  256. package/dist/exchanges/venues/risex.js +116 -0
  257. package/dist/exchanges/venues/txflow.d.ts +18 -0
  258. package/dist/exchanges/venues/txflow.js +77 -0
  259. package/dist/exchanges/venues/variational.d.ts +21 -0
  260. package/dist/exchanges/venues/variational.js +85 -0
  261. package/dist/execution-log.d.ts +1 -1
  262. package/dist/funding/legacy-public.d.ts +1 -1
  263. package/dist/funding/legacy-public.js +9 -4
  264. package/dist/funding/normalize.d.ts +1 -1
  265. package/dist/funding/normalize.js +1 -1
  266. package/dist/funding/rates.d.ts +44 -4
  267. package/dist/funding/rates.js +56 -12
  268. package/dist/index.js +7 -6
  269. package/dist/jobs.d.ts +1 -0
  270. package/dist/jobs.js +25 -1
  271. package/dist/landing.d.ts +2 -0
  272. package/dist/landing.js +1 -1
  273. package/dist/mcp-pacifica-earn.d.ts +9 -0
  274. package/dist/mcp-pacifica-earn.js +58 -0
  275. package/dist/mcp-server.js +113 -59
  276. package/dist/pacifica/client.d.ts +67 -4
  277. package/dist/pacifica/client.js +170 -8
  278. package/dist/pacifica/rate-limit.d.ts +144 -0
  279. package/dist/pacifica/rate-limit.js +267 -0
  280. package/dist/pacifica/types/order.d.ts +2 -1
  281. package/dist/plan-executor.js +7 -2
  282. package/dist/rebalance.js +1 -1
  283. package/dist/risk.d.ts +2 -1
  284. package/dist/risk.js +2 -4
  285. package/dist/settings.d.ts +32 -3
  286. package/dist/signer/ows-evm.d.ts +1 -1
  287. package/dist/signer/ows-evm.js +1 -1
  288. package/dist/smart-order.d.ts +1 -1
  289. package/dist/smart-order.js +1 -1
  290. package/dist/strategies/funding-arb.d.ts +1 -1
  291. package/dist/strategies/funding-arb.js +1 -1
  292. package/dist/trade-validator.js +2 -2
  293. package/dist/utils.d.ts +12 -3
  294. package/dist/utils.js +11 -3
  295. package/dist/venue-health.d.ts +40 -0
  296. package/dist/venue-health.js +75 -0
  297. package/dist/withdraw-gate.d.ts +1 -1
  298. package/dist/withdraw-gate.js +1 -1
  299. package/dist/ws/index.d.ts +959 -0
  300. package/dist/ws/index.js +4295 -0
  301. package/dist/ws/node.d.ts +975 -0
  302. package/dist/ws/node.js +4366 -0
  303. package/dist/ws/pacifica-instrument.d.ts +8 -0
  304. package/dist/ws/pacifica-instrument.js +8 -0
  305. package/dist/ws-cli/credentials.d.ts +62 -0
  306. package/dist/ws-cli/credentials.js +126 -0
  307. package/package.json +58 -10
  308. package/skills/perp-cli/SKILL.md +62 -9
  309. package/skills/perp-cli/references/agent-operations.md +7 -0
  310. package/skills/perp-cli/references/commands.md +76 -5
  311. package/skills/perp-cli/references/ws-library.md +492 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Decimal-string precision helpers for order entry (design §10.10).
2
+ * Decimal-string precision helpers for order entry.
3
3
  *
4
4
  * All math is done on scaled BigInt to avoid float drift. Rules:
5
5
  * - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
@@ -24,6 +24,8 @@ export declare function requirePositive(value: string, what: string): void;
24
24
  export declare function requireOnTick(price: string, tickSize: string): string;
25
25
  /** Exact (a + b) / 2 of two plain decimal strings (no float arithmetic: 0.1 and 0.2 → "0.15"). */
26
26
  export declare function midDecimal(a: string, b: string): string;
27
+ /** Exact decimal a + b (plain decimal strings). */
28
+ export declare function addDecimal(a: string, b: string): string;
27
29
  /** Exact decimal a − b (plain decimal strings). */
28
30
  export declare function subDecimal(a: string, b: string): string;
29
31
  /** Exact decimal a × b (plain decimal strings). */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Decimal-string precision helpers for order entry (design §10.10).
2
+ * Decimal-string precision helpers for order entry.
3
3
  *
4
4
  * All math is done on scaled BigInt to avoid float drift. Rules:
5
5
  * - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
@@ -119,6 +119,13 @@ export function midDecimal(a, b) {
119
119
  const scale = Math.max(x.scale, y.scale) + 1; // one more digit holds the possible ".5"
120
120
  return format((rescale(x, scale) + rescale(y, scale)) / 2n, scale);
121
121
  }
122
+ /** Exact decimal a + b (plain decimal strings). */
123
+ export function addDecimal(a, b) {
124
+ const x = parseDecimal(a);
125
+ const y = parseDecimal(b);
126
+ const scale = Math.max(x.scale, y.scale);
127
+ return format(rescale(x, scale) + rescale(y, scale), scale);
128
+ }
122
129
  /** Exact decimal a − b (plain decimal strings). */
123
130
  export function subDecimal(a, b) {
124
131
  const x = parseDecimal(a);
@@ -195,7 +202,7 @@ export async function lotStepOf(adapter, symbol) {
195
202
  m = bare[0];
196
203
  }
197
204
  if (m === undefined) {
198
- throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name });
205
+ throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name, remediation: `List markets: perp -e ${adapter.name} market list` });
199
206
  }
200
207
  if (m.stepSize !== null)
201
208
  return m.stepSize;
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
3
- * (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
4
3
  *
5
4
  * Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
6
5
  * Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
@@ -42,7 +41,7 @@
42
41
  import { PerpError } from "../../errors.js";
43
42
  import type { WithdrawGate } from "../../settings.js";
44
43
  import { type BuilderFeeGate } from "../builder-fee.js";
45
- import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade } from "../interface.js";
44
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
46
45
  import type { IocOutcome } from "../ioc-outcome.js";
47
46
  import type { ResolvedSlippage } from "../slippage.js";
48
47
  import type { TradingV2 } from "../trading-v2.js";
@@ -52,6 +51,20 @@ import { type QfexReduceOnlyWire } from "./signing.js";
52
51
  import { qfexPendingRemediation, type QfexOrderResponse } from "./ws-transport.js";
53
52
  /** A mark price entry older than this (its own venue `time`) is not used as an order reference. */
54
53
  export declare const QFEX_MARK_MAX_AGE_MS = 10000;
54
+ /**
55
+ * Candle volumes. GET /candles returns `usdVolume` (populated) and `baseTokenVolume`, which the
56
+ * venue leaves "0" on traded candles (live 2026-10-14, CL-USD 1HOUR: baseTokenVolume "0" with
57
+ * usdVolume "52534.48506" on every row; startingOpenInterest / orderbookMidPrice* are "0" too).
58
+ * A base "0" next to a non-zero USD volume is a placeholder → null (unknown), never 0; when
59
+ * both are zero the candle really had no volume.
60
+ */
61
+ export declare function qfexCandleVolumes(c: {
62
+ baseTokenVolume: unknown;
63
+ usdVolume: unknown;
64
+ }): {
65
+ volume: string | null;
66
+ quoteVolume: string;
67
+ };
55
68
  /** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
56
69
  export declare function qfexClientOrderId(): string;
57
70
  /**
@@ -215,7 +228,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
215
228
  /**
216
229
  * A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
217
230
  * on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
218
- * unknown (ORDER_PENDING, design §14.12 #4e: never re-sent). It is NOT cancelled here: a cancel
231
+ * unknown (ORDER_PENDING, never re-sent). It is NOT cancelled here: a cancel
219
232
  * is a second mutating command with its own unknown outcome and cannot undo a fill.
220
233
  */
221
234
  private reduceOnlyEchoError;
@@ -225,7 +238,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
225
238
  private orderResultOf;
226
239
  /**
227
240
  * Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
228
- * terminal frame was received (design §14.12 #3). A pending result is never an outcome.
241
+ * terminal frame was received. A pending result is never an outcome.
229
242
  */
230
243
  iocOutcomeOf(raw: unknown): IocOutcome | null;
231
244
  amendOrder(req: AmendRequest): Promise<OrderResult>;
@@ -253,7 +266,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
253
266
  bids: [string, string][];
254
267
  asks: [string, string][];
255
268
  }>;
256
- getRecentTrades(): Promise<ExchangeTrade[]>;
269
+ getRecentTrades(): Promise<ExchangePublicTrade[]>;
257
270
  /** Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24). */
258
271
  getFundingHistory(symbol: string, limit?: number): Promise<{
259
272
  time: number;
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
3
- * (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
4
3
  *
5
4
  * Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
6
5
  * Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
@@ -78,6 +77,24 @@ const STOP_TYPES = new Set(["TAKE_PROFIT", "STOP_LOSS"]);
78
77
  const KLINE_RESOLUTION = {
79
78
  "1m": "1MIN", "5m": "5MINS", "15m": "15MINS", "30m": "30MINS", "1h": "1HOUR", "4h": "4HOURS", "1d": "1DAY",
80
79
  };
80
+ /**
81
+ * Candle volumes. GET /candles returns `usdVolume` (populated) and `baseTokenVolume`, which the
82
+ * venue leaves "0" on traded candles (live 2026-10-14, CL-USD 1HOUR: baseTokenVolume "0" with
83
+ * usdVolume "52534.48506" on every row; startingOpenInterest / orderbookMidPrice* are "0" too).
84
+ * A base "0" next to a non-zero USD volume is a placeholder → null (unknown), never 0; when
85
+ * both are zero the candle really had no volume.
86
+ */
87
+ export function qfexCandleVolumes(c) {
88
+ const decimal = (v, what) => {
89
+ if (typeof v !== "string" || v.trim() === "" || !Number.isFinite(Number(v))) {
90
+ throw new PerpError("EXCHANGE_ERROR", `QFEX candles: ${what} is not a decimal string (${JSON.stringify(v)})`, { exchange: "qfex" });
91
+ }
92
+ return v;
93
+ };
94
+ const quoteVolume = decimal(c.usdVolume, "usdVolume");
95
+ const base = decimal(c.baseTokenVolume, "baseTokenVolume");
96
+ return { volume: Number(base) === 0 && Number(quoteVolume) !== 0 ? null : base, quoteVolume };
97
+ }
81
98
  /** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
82
99
  export function qfexClientOrderId() {
83
100
  return randomUUID();
@@ -262,7 +279,7 @@ export class QfexAdapter {
262
279
  const venueSymbol = qfexVenueSymbol(symbol);
263
280
  const i = (await this.instrumentMap()).get(venueSymbol);
264
281
  if (i === undefined) {
265
- throw new PerpError("SYMBOL_NOT_FOUND", `QFEX has no market ${venueSymbol}`, { exchange: "qfex", remediation: "List markets with: perp market list -e qfex" });
282
+ throw new PerpError("SYMBOL_NOT_FOUND", `QFEX has no market ${venueSymbol}`, { exchange: "qfex", remediation: "List markets: perp -e qfex market list" });
266
283
  }
267
284
  return i;
268
285
  }
@@ -339,7 +356,7 @@ export class QfexAdapter {
339
356
  stopOrderGate() {
340
357
  if (QFEX_STOP_CLIENT_ID_ECHO === true)
341
358
  return null;
342
- return unsupported("qfex", "stopOrder", "the stop order acknowledgement is not documented to echo a client order id, so a stop cannot be correlated safely (Q21)", "Set stops / TP / SL on the QFEX web app until docs/TEST-PLAN.md QFEX item Q21 passes");
359
+ return unsupported("qfex", "stopOrder", "the stop order acknowledgement is not documented to echo a client order id, so a stop cannot be correlated safely (Q21)", "Set stops / TP / SL on the QFEX web app until QFEX item Q21 passes");
343
360
  }
344
361
  /**
345
362
  * One reduce-only stop-market order (add_order with order_type TAKE_PROFIT / STOP_LOSS).
@@ -571,7 +588,7 @@ export class QfexAdapter {
571
588
  /**
572
589
  * A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
573
590
  * on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
574
- * unknown (ORDER_PENDING, design §14.12 #4e: never re-sent). It is NOT cancelled here: a cancel
591
+ * unknown (ORDER_PENDING, never re-sent). It is NOT cancelled here: a cancel
575
592
  * is a second mutating command with its own unknown outcome and cannot undo a fill.
576
593
  */
577
594
  reduceOnlyEchoError(i, orderId, clientOrderId, echo, venueRaw) {
@@ -746,7 +763,7 @@ export class QfexAdapter {
746
763
  }
747
764
  if (isZeroDecimal(filled)) {
748
765
  if (ctx.immediate) {
749
- // A terminal frame with nothing filled — IOC_CANCELLED or CANCELLED alike (§13.10 #4).
766
+ // A terminal frame with nothing filled — IOC_CANCELLED or CANCELLED alike.
750
767
  throw new PerpError("ORDER_REJECTED", `QFEX ${i.venueSymbol}: the ${t.time_in_force} order filled nothing and was cancelled (${t.status})`, {
751
768
  exchange: "qfex", reason: "iocNoFill", venueStatus: t.status, orderId: add.orderId, clientOrderId: add.clientOrderId, venueRaw,
752
769
  remediation: "Nothing was filled. Widen --slippage-bps (within the price band), reduce the size, or place a resting limit order",
@@ -781,7 +798,7 @@ export class QfexAdapter {
781
798
  }
782
799
  /**
783
800
  * Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
784
- * terminal frame was received (design §14.12 #3). A pending result is never an outcome.
801
+ * terminal frame was received. A pending result is never an outcome.
785
802
  */
786
803
  iocOutcomeOf(raw) {
787
804
  const r = raw;
@@ -1098,7 +1115,9 @@ export class QfexAdapter {
1098
1115
  return (await this.getVenueMarkets()).map(toLegacyMarketInfo);
1099
1116
  }
1100
1117
  async getOrderbook(symbol) {
1101
- const venueSymbol = qfexVenueSymbol(symbol);
1118
+ // Resolved against refdata first: an unknown symbol is SYMBOL_NOT_FOUND, not the venue's
1119
+ // HTTP 404 surfacing as a retryable EXCHANGE_ERROR.
1120
+ const venueSymbol = (await this.instrumentOf(symbol)).venueSymbol;
1102
1121
  const book = await this.client.publicGet(`/md/orderbook/${encodeURIComponent(venueSymbol)}`, null);
1103
1122
  // The venue lists empty price levels (quantity "0"); they are not part of the book.
1104
1123
  const side = (v, what) => listOf(v, what)
@@ -1111,7 +1130,7 @@ export class QfexAdapter {
1111
1130
  }
1112
1131
  /** Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24). */
1113
1132
  async getFundingHistory(symbol, limit) {
1114
- const venueSymbol = qfexVenueSymbol(symbol);
1133
+ const venueSymbol = (await this.instrumentOf(symbol)).venueSymbol;
1115
1134
  const hours = limit === undefined ? 24 : limit;
1116
1135
  if (!Number.isInteger(hours) || hours < 1)
1117
1136
  throw new PerpError("INVALID_PARAMS", `funding history limit must be a positive integer of hours (got ${hours})`);
@@ -1135,14 +1154,15 @@ export class QfexAdapter {
1135
1154
  if (resolution === undefined) {
1136
1155
  throw new PerpError("INVALID_PARAMS", `Unsupported kline interval "${interval}" on QFEX`, { remediation: `Use one of: ${Object.keys(KLINE_RESOLUTION).join(", ")}` });
1137
1156
  }
1138
- const body = await this.client.publicGet(`/candles/${encodeURIComponent(qfexVenueSymbol(symbol))}`, {
1157
+ const venueSymbol = (await this.instrumentOf(symbol)).venueSymbol;
1158
+ const body = await this.client.publicGet(`/candles/${encodeURIComponent(venueSymbol)}`, {
1139
1159
  resolution, fromISO: new Date(startTime).toISOString(), toISO: new Date(endTime).toISOString(),
1140
1160
  });
1141
1161
  return listOf(body.candles, "candles").map(c => {
1142
1162
  const time = Date.parse(c.startedAt);
1143
1163
  if (!Number.isFinite(time))
1144
1164
  throw new PerpError("EXCHANGE_ERROR", `QFEX candles: invalid startedAt "${c.startedAt}"`);
1145
- return { time, open: c.open, high: c.high, low: c.low, close: c.close, volume: c.baseTokenVolume, trades: null };
1165
+ return { time, open: c.open, high: c.high, low: c.low, close: c.close, ...qfexCandleVolumes(c), trades: null };
1146
1166
  }).sort((a, b) => a.time - b.time);
1147
1167
  }
1148
1168
  // ── Legacy ExchangeAdapter: account ──
@@ -1258,7 +1278,7 @@ export class QfexAdapter {
1258
1278
  async getFundingPayments() {
1259
1279
  throw unsupported("qfex", "fundingPayments", "funding payments are GET /executions rows, but the execution_type that marks them is not documented (Q10)", "See funding in the QFEX web app; net funding per position is in: perp account positions -e qfex --json");
1260
1280
  }
1261
- // ── Legacy ExchangeAdapter: trading (shim → TradingV2, design §10.7). Client ids are generated here. ──
1281
+ // ── Legacy ExchangeAdapter: trading (shim → TradingV2). Client ids are generated here. ──
1262
1282
  async marketOrder(symbol, side, size, opts) {
1263
1283
  return this.placeOrder({
1264
1284
  symbol, side, type: "market", size, price: null, triggerPrice: null, triggerSource: null, tif: null,
@@ -1294,7 +1314,7 @@ export class QfexAdapter {
1294
1314
  reduceOnly: opts?.reduceOnly === true, clientOrderId: qfexClientOrderId(), slippage: null, tpsl: null,
1295
1315
  });
1296
1316
  }
1297
- // ── Funds (design §14.6, §14.12 #8) ──
1317
+ // ── Funds ──
1298
1318
  /**
1299
1319
  * Deposit address for USDC on Arbitrum One (banker GET /address). The address is returned
1300
1320
  * only when the venue confirms exactly that network and asset — anything else is refused,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX API key import (design §14.5 as amended by §14.12 #9).
2
+ * QFEX API key import.
3
3
  *
4
4
  * Keys are created on the QFEX website (Developer Settings, 2FA) — there is no programmatic
5
5
  * approval. The public key + secret are entered via hidden prompt / stdin only (never flags,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX API key import (design §14.5 as amended by §14.12 #9).
2
+ * QFEX API key import.
3
3
  *
4
4
  * Keys are created on the QFEX website (Developer Settings, 2FA) — there is no programmatic
5
5
  * approval. The public key + secret are entered via hidden prompt / stdin only (never flags,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX raw REST client (design §14.1, §14.4, §14.6): raw fetch + node:crypto HMAC, no SDK.
2
+ * QFEX raw REST client: raw fetch + node:crypto HMAC, no SDK.
3
3
  *
4
4
  * Two hosts: the API (`api.qfex.*` — market data, account reads, leverage, subaccount
5
5
  * transfer) and the banker (`banker.qfex.*` — deposit address, withdraw). Both take the same
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX raw REST client (design §14.1, §14.4, §14.6): raw fetch + node:crypto HMAC, no SDK.
2
+ * QFEX raw REST client: raw fetch + node:crypto HMAC, no SDK.
3
3
  *
4
4
  * Two hosts: the API (`api.qfex.*` — market data, account reads, leverage, subaccount
5
5
  * transfer) and the banker (`banker.qfex.*` — deposit address, withdraw). Both take the same
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX venue status / error → PerpError (design §14.3 error table, §14.12 #3, #8, #11).
2
+ * QFEX venue status / error → PerpError.
3
3
  *
4
4
  * Three sources:
5
5
  * - order_response.status engine verdict on one order (definite once correlated);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX venue status / error → PerpError (design §14.3 error table, §14.12 #3, #8, #11).
2
+ * QFEX venue status / error → PerpError.
3
3
  *
4
4
  * Three sources:
5
5
  * - order_response.status engine verdict on one order (definite once correlated);
@@ -90,7 +90,7 @@ const STATUS_REMEDIATION = {
90
90
  CANNOT_MODIFY_PARTIAL_FILL: "A partially filled order cannot be modified — cancel it and place a new order",
91
91
  CANNOT_MODIFY_ALO_WOULD_CROSS: "The new price would cross the book — choose a passive price, or cancel and place a LIMIT order",
92
92
  REJECTED_MARKET_CLOSED: "The market is closed for this order right now — check the session: perp market info <symbol> -e qfex",
93
- INVALID_CLIENT_ORDER_ID: "The venue refused the generated client order id — report it (docs/TEST-PLAN.md QFEX Q18)",
93
+ INVALID_CLIENT_ORDER_ID: "The venue refused the generated client order id — report it (QFEX item Q18)",
94
94
  };
95
95
  /** "… retry after 200ms" / "… retry after 2 seconds" → ms; null when the message carries no hint. */
96
96
  export function parseRetryAfterMs(message) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX public market data (design §14.8 as amended by §14.12 #6, #7, #10).
2
+ * QFEX public market data.
3
3
  *
4
4
  * Sources (all public):
5
5
  * GET /refdata instruments: tick, lot, min/max quantity, price band, status,
@@ -8,7 +8,7 @@
8
8
  * 24h notional volume
9
9
  * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
10
10
  *
11
- * Coupling (§14.12 #10): order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
11
+ * Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
12
12
  * Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
13
13
  * fails fetchFundingRates, never market listing or order entry.
14
14
  *
@@ -89,7 +89,7 @@ export declare function qfexPlainSymbol(venueSymbol: string): string;
89
89
  export declare function qfexVenueSymbol(symbol: string): string;
90
90
  export declare function qfexAssetClass(row: Pick<QfexRefdataRow, "symbol" | "product_category" | "base_asset">): AssetClass;
91
91
  export declare function toQfexInstrument(raw: unknown): QfexInstrument;
92
- /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE (§14.12 #7). */
92
+ /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
93
93
  export declare function qfexTradable(i: Pick<QfexInstrument, "status" | "quoteAsset">): boolean;
94
94
  /**
95
95
  * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX public market data (design §14.8 as amended by §14.12 #6, #7, #10).
2
+ * QFEX public market data.
3
3
  *
4
4
  * Sources (all public):
5
5
  * GET /refdata instruments: tick, lot, min/max quantity, price band, status,
@@ -8,7 +8,7 @@
8
8
  * 24h notional volume
9
9
  * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
10
10
  *
11
- * Coupling (§14.12 #10): order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
11
+ * Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
12
12
  * Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
13
13
  * fails fetchFundingRates, never market listing or order entry.
14
14
  *
@@ -103,7 +103,7 @@ export function toQfexInstrument(raw) {
103
103
  marketHours: hours !== null && typeof hours === "object" && !Array.isArray(hours) ? hours : null,
104
104
  };
105
105
  }
106
- /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE (§14.12 #7). */
106
+ /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
107
107
  export function qfexTradable(i) {
108
108
  return i.status === "ACTIVE" && i.quoteAsset === "USD";
109
109
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX request signing + wire-number conversion (design §14.2, §14.3).
2
+ * QFEX request signing + wire-number conversion.
3
3
  *
4
4
  * Auth (docs /websocket/channels/trade/authenticate, OpenAPI securitySchemes):
5
5
  * signature = hex(HMAC-SHA256(secret, `${nonce}:${unix_ts}`))
@@ -12,7 +12,7 @@
12
12
  * fromWireNumber are the only places a decimal string becomes a float or the reverse.
13
13
  *
14
14
  * Wire facts that needed a live check are single constants here; while one is unset every code
15
- * path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response (design §14.12).
15
+ * path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response.
16
16
  */
17
17
  import { PerpError } from "../../errors.js";
18
18
  export interface QfexCredentials {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX request signing + wire-number conversion (design §14.2, §14.3).
2
+ * QFEX request signing + wire-number conversion.
3
3
  *
4
4
  * Auth (docs /websocket/channels/trade/authenticate, OpenAPI securitySchemes):
5
5
  * signature = hex(HMAC-SHA256(secret, `${nonce}:${unix_ts}`))
@@ -12,7 +12,7 @@
12
12
  * fromWireNumber are the only places a decimal string becomes a float or the reverse.
13
13
  *
14
14
  * Wire facts that needed a live check are single constants here; while one is unset every code
15
- * path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response (design §14.12).
15
+ * path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response.
16
16
  */
17
17
  import { createHmac, randomBytes } from "node:crypto";
18
18
  import { PerpError } from "../../errors.js";
@@ -108,8 +108,7 @@ export const QFEX_MODIFY_KEEPS_CLIENT_ID = true;
108
108
  * (it may be the user id). true → a mismatch is ORDER_PENDING.
109
109
  */
110
110
  export const QFEX_USER_ID_IS_ACCOUNT = null;
111
- const Q2_REMEDIATION = "Reduce / close the position on the QFEX web app. perp-cli sends reduce-only orders on QFEX only with the wire format of item Q2 "
112
- + "(docs/TEST-PLAN.md) set";
111
+ const Q2_REMEDIATION = "Reduce / close the position on the QFEX web app. perp-cli sends reduce-only orders on QFEX only with the wire format of item Q2 set";
113
112
  /** The uniform refusal of every reduce-only path when the wire format is unset; null when it is set. */
114
113
  export function qfexReduceOnlyGate(wire) {
115
114
  if (wire !== null)
@@ -121,7 +120,7 @@ export function qfexAutomationGate(verified) {
121
120
  if (verified === true)
122
121
  return null;
123
122
  return unsupported("qfex", "automation", "funding is unverified (Q4) and a resting reduce-only order the market reaches while flat is not proven harmless (Q2), and no automated flow places its own protective stop yet", "Trade QFEX with the manual commands (perp trade market / limit / stop / tpsl / close / reduce / close-all / flatten -e qfex) and watch the position yourself; "
124
- + "automation opens once docs/TEST-PLAN.md QFEX items Q2 / Q4 are settled");
123
+ + "automation opens once QFEX items Q2 / Q4 are settled");
125
124
  }
126
125
  /** `reduce_only` value for the wire in the verified format. */
127
126
  export function reduceOnlyWireValue(wire, reduceOnly) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX Trade WebSocket transport (design §14.2 as amended by §14.12 #2, #3, #4, #11, #13).
2
+ * QFEX Trade WebSocket transport.
3
3
  *
4
4
  * The venue has no request id. Correlation therefore rests on four rules:
5
5
  * 1. ONE command at a time per connection (FIFO) — reads share the queue.
@@ -94,7 +94,7 @@ export interface QfexFillResponse {
94
94
  export declare function orderResponseOf(frame: QfexFrame): QfexOrderResponse | null;
95
95
  export declare function fillResponseOf(frame: QfexFrame): QfexFillResponse | null;
96
96
  /**
97
- * Error frame in any documented shape (design §14.12 #11):
97
+ * Error frame in any documented shape:
98
98
  * { "err": { error_code, message, incoming_message } } /websocket/errors + AsyncAPI
99
99
  * { "type": "Err", error_code, message, incoming_message } /websocket/rate
100
100
  * { "type": "error", error_code, message } the docs' own client samples
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QFEX Trade WebSocket transport (design §14.2 as amended by §14.12 #2, #3, #4, #11, #13).
2
+ * QFEX Trade WebSocket transport.
3
3
  *
4
4
  * The venue has no request id. Correlation therefore rests on four rules:
5
5
  * 1. ONE command at a time per connection (FIFO) — reads share the queue.
@@ -66,7 +66,7 @@ export function fillResponseOf(frame) {
66
66
  return r;
67
67
  }
68
68
  /**
69
- * Error frame in any documented shape (design §14.12 #11):
69
+ * Error frame in any documented shape:
70
70
  * { "err": { error_code, message, incoming_message } } /websocket/errors + AsyncAPI
71
71
  * { "type": "Err", error_code, message, incoming_message } /websocket/rate
72
72
  * { "type": "error", error_code, message } the docs' own client samples
@@ -86,7 +86,7 @@ export function errFrameOf(frame) {
86
86
  : null;
87
87
  return { errorCode: body.error_code, message: typeof body.message === "string" ? body.message : null, echo };
88
88
  }
89
- /** Auth outcome frame: both documented success shapes are accepted (§14.12 #11). */
89
+ /** Auth outcome frame: both documented success shapes are accepted. */
90
90
  function authVerdictOf(frame) {
91
91
  if (typeof frame.authenticated === "boolean")
92
92
  return frame.authenticated;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Per-process token-bucket rate limiter keyed by venue (design §3 L4, §10.9).
2
+ * Per-process token-bucket rate limiter keyed by venue.
3
3
  *
4
4
  * Limitation (documented): this only coordinates requests inside one process.
5
5
  * CLI + MCP server + bots running at once can still exceed a venue's limit.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Per-process token-bucket rate limiter keyed by venue (design §3 L4, §10.9).
2
+ * Per-process token-bucket rate limiter keyed by venue.
3
3
  *
4
4
  * Limitation (documented): this only coordinates requests inside one process.
5
5
  * CLI + MCP server + bots running at once can still exceed a venue's limit.
@@ -19,7 +19,10 @@ class TokenBucket {
19
19
  }
20
20
  refill() {
21
21
  const now = Date.now();
22
- this.tokens = Math.min(this.policy.capacity, this.tokens + ((now - this.last) / 1000) * this.policy.refillPerSec);
22
+ // Clamped: a wall clock stepping backwards (NTP, VM resume, fake timers) must not drive the bucket
23
+ // negative and stall every request until the clock catches up.
24
+ const elapsedMs = Math.max(0, now - this.last);
25
+ this.tokens = Math.min(this.policy.capacity, this.tokens + (elapsedMs / 1000) * this.policy.refillPerSec);
23
26
  this.last = now;
24
27
  }
25
28
  acquire(weight) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Adapter registry — legacy name/alias API, now derived from the venue
3
- * definitions (src/exchanges/venues/, design §10.10). New venues are added by
3
+ * definitions (src/exchanges/venues/). New venues are added by
4
4
  * registering a VenueDefinition, not here.
5
5
  */
6
6
  import "./venues/index.js";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Adapter registry — legacy name/alias API, now derived from the venue
3
- * definitions (src/exchanges/venues/, design §10.10). New venues are added by
3
+ * definitions (src/exchanges/venues/). New venues are added by
4
4
  * registering a VenueDefinition, not here.
5
5
  */
6
6
  import { getVenue, listVenues, tryResolveVenueId } from "./venue.js";
@@ -0,0 +1,148 @@
1
+ /**
2
+ * RISEx adapter — market data (public), account reads (public by address) and trading through the
3
+ * registered, trading-only session signer (EIP-712 VerifyWitness permits; the server submits the
4
+ * on-chain transaction and pays gas).
5
+ *
6
+ * Account address: the signer's account (agent meta), else the master wallet's address (reads
7
+ * only, nothing is signed with it here). Writes need the signer.
8
+ *
9
+ * Order safety:
10
+ * - every refusal (market flags, maintenance, precision, pins) happens before a nonce is
11
+ * allocated or anything is signed;
12
+ * - every permit consumes a fresh (anchor, bit) pair that is persisted BEFORE the send, so a
13
+ * pair is never reused, even when the outcome is unknown;
14
+ * - a client order id is always attached (generated uint64 when the caller gives none);
15
+ * - an unknown send outcome → one read-only reconciliation (permit nonce consumed on-chain? the
16
+ * client order id in open orders / order history?) → a proven result carries notes
17
+ * ["reconciled"]; otherwise ORDER_PENDING ("do NOT resubmit") and the symbol is quarantined.
18
+ * The permit deadline is short (RISEX_PERMIT_TTL_S), so an unproven permit expires quickly.
19
+ */
20
+ import { PerpError } from "../../errors.js";
21
+ import type { RisexAgentMeta } from "../../settings.js";
22
+ import type { EvmSigner } from "../../signer/interface.js";
23
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
24
+ import type { ResolvedSlippage } from "../slippage.js";
25
+ import { type TradingV2 } from "../trading-v2.js";
26
+ import type { VenueCreateContext, VenueDefinition } from "../venue.js";
27
+ import { type AmendRequest, type CancelResult, type LeverageResult, type Network, type OrderRef, type OrderRequest, type OrderResult, type VenueMarket } from "../venue-types.js";
28
+ import { type RisexChainReader } from "./session.js";
29
+ /** Permit lifetime: long enough for the server to submit, short enough that an unproven permit expires soon. */
30
+ export declare const RISEX_PERMIT_TTL_S = 120;
31
+ /** How long a successful on-chain trading-only proof is trusted before the next permit re-checks it. */
32
+ export declare const RISEX_TRADING_ONLY_TTL_MS = 60000;
33
+ export interface RisexAdapterDeps {
34
+ /** Account the reads and permits are for; null = public market data only. */
35
+ account: string | null;
36
+ /** Trading-only session signer; null = no trading. */
37
+ signer: EvmSigner | null;
38
+ /** Agent meta the signer came from (for messages); null when none. */
39
+ meta: RisexAgentMeta | null;
40
+ /** Why trading is unavailable when `signer` is null (the uniform refusal). */
41
+ noSignerReason: PerpError | null;
42
+ nonceDir?: string;
43
+ chain?: RisexChainReader;
44
+ sleep?: (ms: number) => Promise<void>;
45
+ /** Quarantine hook for unknown outcomes (default: bot/quarantine.ts). */
46
+ quarantine?: (symbol: string, reason: string, remediation: string) => void;
47
+ }
48
+ export declare class RisexAdapter implements ExchangeAdapter, TradingV2 {
49
+ readonly name: "risex";
50
+ readonly aliases: readonly ["rise"];
51
+ readonly venue: VenueDefinition;
52
+ readonly network: Network;
53
+ private readonly client;
54
+ private readonly deps;
55
+ private markets;
56
+ constructor(def: VenueDefinition, network: Network, deps: RisexAdapterDeps);
57
+ /** Resolve the account and the trading-only signer from the create context (OWS only). */
58
+ static create(def: VenueDefinition, ctx: VenueCreateContext): Promise<RisexAdapter>;
59
+ /**
60
+ * Trading capabilities — also the adapter-level release gate: while the trading role is gated
61
+ * (risex-release.ts) this throws CAPABILITY_UNSUPPORTED before any validation, nonce or
62
+ * signature. Read paths never touch it, so the adapter still serves market data / account reads.
63
+ */
64
+ private get caps();
65
+ private requireAccount;
66
+ private requireSigner;
67
+ private sleep;
68
+ private rawMarkets;
69
+ /** Active market by symbol ("BTC", "BTC/USDC", "BTC-PERP") or market id. */
70
+ private market;
71
+ private symbolOfId;
72
+ /**
73
+ * The on-chain trading-only proof, re-checked at most every RISEX_TRADING_ONLY_TTL_MS before a
74
+ * permit (long-lived adapters — perp-mcp, bots — must notice a contract upgrade). A failed proof
75
+ * (refusal or RPC error) is never cached.
76
+ */
77
+ private tradingOnlyProof;
78
+ private permit;
79
+ private chainRead;
80
+ private quarantine;
81
+ /** The account reads / permits are for (signer's account, else the master address). */
82
+ accountAddress(): string;
83
+ /** Testnet faucet: 1000 test USDC, once per wallet (also registers the account). Refused on mainnet. */
84
+ testnetFaucet(): Promise<{
85
+ txHash: string | null;
86
+ }>;
87
+ getVenueMarkets(): Promise<VenueMarket[]>;
88
+ /** Legacy rows plus the venue's market-level order restrictions. */
89
+ getMarkets(): Promise<Array<ExchangeMarketInfo & {
90
+ marketId: string;
91
+ reduceOnlyMarket: boolean;
92
+ postOnlyMarket: boolean;
93
+ tradable: boolean;
94
+ }>>;
95
+ getOrderbook(symbol: string): Promise<{
96
+ bids: [string, string][];
97
+ asks: [string, string][];
98
+ }>;
99
+ getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
100
+ getFundingHistory(symbol: string, limit?: number): Promise<{
101
+ time: number;
102
+ rate: string;
103
+ price: string | null;
104
+ }[]>;
105
+ getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
106
+ private portfolio;
107
+ getBalance(): Promise<ExchangeBalance>;
108
+ getPositions(): Promise<ExchangePosition[]>;
109
+ private openOrdersRaw;
110
+ private historyOrders;
111
+ private toExchangeOrder;
112
+ /** On-chain open orders, enriched with the indexer's decimals / fills (one retry for indexer lag). */
113
+ getOpenOrders(): Promise<ExchangeOrder[]>;
114
+ getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
115
+ getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
116
+ /** `payment` is positive when RECEIVED (RISEx `funding_payment` is positive when paid — negated here). */
117
+ getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
118
+ placeOrder(req: OrderRequest): Promise<OrderResult>;
119
+ /** One read-only reconciliation of an unknown place outcome. */
120
+ private reconcilePlace;
121
+ amendOrder(_req: AmendRequest): Promise<OrderResult>;
122
+ cancelByRef(symbol: string, ref: OrderRef): Promise<CancelResult>;
123
+ cancelAllV2(symbol: string | null): Promise<CancelResult>;
124
+ applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
125
+ private sendAccountAction;
126
+ getOrderStatus(symbol: string, ref: OrderRef): Promise<OrderResult>;
127
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
128
+ reduceOnly: boolean;
129
+ slippage: ResolvedSlippage;
130
+ clientOrderId?: string;
131
+ }): Promise<unknown>;
132
+ limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
133
+ reduceOnly?: boolean;
134
+ tif?: string;
135
+ clientOrderId?: string;
136
+ }): Promise<unknown>;
137
+ editOrder(): Promise<unknown>;
138
+ cancelOrder(symbol: string, orderId: string): Promise<unknown>;
139
+ cancelAllOrders(symbol?: string): Promise<unknown>;
140
+ setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
141
+ stopOrder(): Promise<unknown>;
142
+ /** TP/SL entries are refused before the entry is sent (not implemented yet). */
143
+ stopOrderGate(): PerpError;
144
+ /** Bots / TWAP / grid / funding-arb legs stay off until RISEx trading is verified on mainnet. */
145
+ automationGate(): PerpError;
146
+ }
147
+ /** Candle intervals → ms (the venue takes the interval in ns). */
148
+ export declare const KLINE_INTERVAL_MS: Readonly<Record<string, number>>;