perp-cli 0.14.3 → 0.16.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 (363) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +138 -405
  4. package/README.zh.md +243 -0
  5. package/dist/agent-wallet/store.d.ts +3 -2
  6. package/dist/agent-wallet/store.js +2 -2
  7. package/dist/api/public/hyperliquid.js +4 -0
  8. package/dist/api/public/lighter.d.ts +5 -0
  9. package/dist/api/public/lighter.js +5 -0
  10. package/dist/api/public/pacifica.js +85 -15
  11. package/dist/api/public/urls.d.ts +2 -0
  12. package/dist/api/public/urls.js +3 -0
  13. package/dist/arb/sizing.js +4 -2
  14. package/dist/arb/state.d.ts +6 -0
  15. package/dist/arb/utils.d.ts +1 -2
  16. package/dist/arb/utils.js +1 -2
  17. package/dist/balance-by-address.d.ts +39 -0
  18. package/dist/balance-by-address.js +44 -0
  19. package/dist/bot/apex/pulse.js +3 -0
  20. package/dist/bot/apex/radar.js +2 -1
  21. package/dist/bot/engine.d.ts +10 -1
  22. package/dist/bot/engine.js +14 -6
  23. package/dist/bot/indicators.d.ts +7 -0
  24. package/dist/bot/indicators.js +16 -0
  25. package/dist/bot/perp-order.d.ts +4 -4
  26. package/dist/bot/perp-order.js +5 -5
  27. package/dist/bot/quarantine.d.ts +1 -1
  28. package/dist/bot/quarantine.js +1 -1
  29. package/dist/bot/quote-rules.js +1 -1
  30. package/dist/bot/strategies/apex-strategy.js +3 -2
  31. package/dist/bot/strategies/engine-mm.js +6 -5
  32. package/dist/bot/strategies/funding-arb-strategy.js +12 -5
  33. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
  34. package/dist/bot/strategies/funding-arb-utils.js +53 -20
  35. package/dist/bot/strategies/funding-auto.js +2 -2
  36. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  37. package/dist/bot/strategies/liquidation-mm.js +8 -2
  38. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  39. package/dist/bot/strategies/momentum-breakout.js +11 -3
  40. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  41. package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
  42. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  43. package/dist/bot/strategies/spot-perp-executor.js +3 -3
  44. package/dist/bot/strategy-types.d.ts +2 -1
  45. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  46. package/dist/bot/tui/BotDashboard.js +1 -1
  47. package/dist/cache.js +12 -6
  48. package/dist/cli-program.d.ts +15 -0
  49. package/dist/cli-program.js +126 -6
  50. package/dist/commands/account.js +168 -24
  51. package/dist/commands/agent-import.d.ts +7 -0
  52. package/dist/commands/agent-import.js +30 -1
  53. package/dist/commands/agent.d.ts +6 -3
  54. package/dist/commands/agent.js +160 -12
  55. package/dist/commands/arb/index.js +23 -81
  56. package/dist/commands/arb-auto.d.ts +24 -0
  57. package/dist/commands/arb-auto.js +175 -97
  58. package/dist/commands/arb.d.ts +20 -0
  59. package/dist/commands/arb.js +107 -163
  60. package/dist/commands/backtest.js +23 -21
  61. package/dist/commands/bot.js +1 -1
  62. package/dist/commands/builder.js +1 -1
  63. package/dist/commands/funds.js +113 -3
  64. package/dist/commands/init.d.ts +1 -1
  65. package/dist/commands/init.js +27 -7
  66. package/dist/commands/manage.js +23 -21
  67. package/dist/commands/market.d.ts +48 -0
  68. package/dist/commands/market.js +395 -100
  69. package/dist/commands/options.d.ts +14 -0
  70. package/dist/commands/options.js +151 -0
  71. package/dist/commands/pacifica-earn.d.ts +5 -0
  72. package/dist/commands/pacifica-earn.js +455 -0
  73. package/dist/commands/pacifica-orders.d.ts +14 -0
  74. package/dist/commands/pacifica-orders.js +142 -0
  75. package/dist/commands/portfolio.d.ts +16 -0
  76. package/dist/commands/portfolio.js +83 -55
  77. package/dist/commands/rebalance.d.ts +1 -0
  78. package/dist/commands/rebalance.js +15 -0
  79. package/dist/commands/risk.d.ts +11 -2
  80. package/dist/commands/risk.js +39 -44
  81. package/dist/commands/trade.d.ts +1 -0
  82. package/dist/commands/trade.js +67 -62
  83. package/dist/commands/wallet.js +1 -1
  84. package/dist/config.d.ts +2 -2
  85. package/dist/config.js +15 -4
  86. package/dist/constants.js +6 -0
  87. package/dist/cross-chain-margin.d.ts +3 -1
  88. package/dist/cross-chain-margin.js +4 -2
  89. package/dist/dashboard/server.js +3 -3
  90. package/dist/dashboard/ui.js +9 -6
  91. package/dist/errors.d.ts +29 -1
  92. package/dist/errors.js +35 -3
  93. package/dist/exchanges/address.d.ts +8 -0
  94. package/dist/exchanges/address.js +47 -0
  95. package/dist/exchanges/arcus/adapter.d.ts +16 -12
  96. package/dist/exchanges/arcus/adapter.js +45 -18
  97. package/dist/exchanges/arcus/agent.d.ts +1 -1
  98. package/dist/exchanges/arcus/agent.js +1 -1
  99. package/dist/exchanges/arcus/client.js +1 -1
  100. package/dist/exchanges/arcus/public.js +3 -2
  101. package/dist/exchanges/arcus/signing.d.ts +2 -3
  102. package/dist/exchanges/arcus/signing.js +1 -1
  103. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  104. package/dist/exchanges/arcus/spot-chain.js +1 -1
  105. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  106. package/dist/exchanges/arcus/spot-router.js +2 -2
  107. package/dist/exchanges/arcus/spot.d.ts +2 -3
  108. package/dist/exchanges/arcus/spot.js +2 -3
  109. package/dist/exchanges/aster-spot.d.ts +3 -3
  110. package/dist/exchanges/aster-spot.js +6 -9
  111. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  112. package/dist/exchanges/aster-typed-data.js +1 -1
  113. package/dist/exchanges/aster.d.ts +30 -10
  114. package/dist/exchanges/aster.js +153 -53
  115. package/dist/exchanges/builder-fee.d.ts +1 -1
  116. package/dist/exchanges/builder-fee.js +13 -2
  117. package/dist/exchanges/canonical-symbols.d.ts +8 -2
  118. package/dist/exchanges/canonical-symbols.js +46 -2
  119. package/dist/exchanges/capabilities.d.ts +2 -0
  120. package/dist/exchanges/capabilities.js +2 -1
  121. package/dist/exchanges/derive/client.d.ts +37 -0
  122. package/dist/exchanges/derive/client.js +135 -0
  123. package/dist/exchanges/derive/options.d.ts +73 -0
  124. package/dist/exchanges/derive/options.js +379 -0
  125. package/dist/exchanges/extended/adapter.d.ts +96 -0
  126. package/dist/exchanges/extended/adapter.js +241 -0
  127. package/dist/exchanges/extended/client.d.ts +38 -0
  128. package/dist/exchanges/extended/client.js +88 -0
  129. package/dist/exchanges/extended/public.d.ts +133 -0
  130. package/dist/exchanges/extended/public.js +436 -0
  131. package/dist/exchanges/grvt/adapter.d.ts +100 -0
  132. package/dist/exchanges/grvt/adapter.js +283 -0
  133. package/dist/exchanges/grvt/client.d.ts +25 -0
  134. package/dist/exchanges/grvt/client.js +86 -0
  135. package/dist/exchanges/grvt/public.d.ts +151 -0
  136. package/dist/exchanges/grvt/public.js +438 -0
  137. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  138. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  139. package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
  140. package/dist/exchanges/hyperliquid-spot.js +24 -16
  141. package/dist/exchanges/hyperliquid.d.ts +199 -22
  142. package/dist/exchanges/hyperliquid.js +550 -202
  143. package/dist/exchanges/index.d.ts +1 -1
  144. package/dist/exchanges/index.js +1 -1
  145. package/dist/exchanges/interface.d.ts +157 -13
  146. package/dist/exchanges/interface.js +47 -0
  147. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  148. package/dist/exchanges/ioc-outcome.js +1 -1
  149. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  150. package/dist/exchanges/legacy-trading-v2.js +7 -2
  151. package/dist/exchanges/lighter-public.d.ts +67 -12
  152. package/dist/exchanges/lighter-public.js +250 -82
  153. package/dist/exchanges/lighter-signer.d.ts +1 -1
  154. package/dist/exchanges/lighter-signer.js +1 -1
  155. package/dist/exchanges/lighter-spot.d.ts +10 -5
  156. package/dist/exchanges/lighter-spot.js +105 -97
  157. package/dist/exchanges/lighter.d.ts +80 -21
  158. package/dist/exchanges/lighter.js +312 -105
  159. package/dist/exchanges/ondo/adapter.d.ts +38 -13
  160. package/dist/exchanges/ondo/adapter.js +86 -27
  161. package/dist/exchanges/ondo/agent.d.ts +1 -1
  162. package/dist/exchanges/ondo/agent.js +1 -1
  163. package/dist/exchanges/ondo/client.d.ts +1 -1
  164. package/dist/exchanges/ondo/client.js +2 -2
  165. package/dist/exchanges/ondo/public.d.ts +35 -3
  166. package/dist/exchanges/ondo/public.js +75 -32
  167. package/dist/exchanges/ondo/spot.d.ts +2 -2
  168. package/dist/exchanges/ondo/spot.js +3 -3
  169. package/dist/exchanges/options-interface.d.ts +158 -0
  170. package/dist/exchanges/options-interface.js +17 -0
  171. package/dist/exchanges/options-venues.d.ts +12 -0
  172. package/dist/exchanges/options-venues.js +35 -0
  173. package/dist/exchanges/outcome-interface.d.ts +4 -0
  174. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  175. package/dist/exchanges/pacifica-atomic.js +440 -0
  176. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  177. package/dist/exchanges/pacifica-decimal.js +51 -0
  178. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  179. package/dist/exchanges/pacifica-presigned.js +141 -0
  180. package/dist/exchanges/pacifica-print.d.ts +200 -0
  181. package/dist/exchanges/pacifica-print.js +417 -0
  182. package/dist/exchanges/pacifica-spot.d.ts +8 -2
  183. package/dist/exchanges/pacifica-spot.js +26 -18
  184. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  185. package/dist/exchanges/pacifica-typed-data.js +1 -1
  186. package/dist/exchanges/pacifica-vaults.d.ts +130 -0
  187. package/dist/exchanges/pacifica-vaults.js +277 -0
  188. package/dist/exchanges/pacifica.d.ts +108 -10
  189. package/dist/exchanges/pacifica.js +428 -171
  190. package/dist/exchanges/precision.d.ts +3 -1
  191. package/dist/exchanges/precision.js +9 -2
  192. package/dist/exchanges/public-balances.d.ts +25 -0
  193. package/dist/exchanges/public-balances.js +158 -0
  194. package/dist/exchanges/public-positions.d.ts +43 -0
  195. package/dist/exchanges/public-positions.js +154 -0
  196. package/dist/exchanges/qfex/adapter.d.ts +69 -13
  197. package/dist/exchanges/qfex/adapter.js +102 -17
  198. package/dist/exchanges/qfex/agent.d.ts +1 -1
  199. package/dist/exchanges/qfex/agent.js +1 -1
  200. package/dist/exchanges/qfex/client.d.ts +1 -1
  201. package/dist/exchanges/qfex/client.js +1 -1
  202. package/dist/exchanges/qfex/errors.d.ts +1 -1
  203. package/dist/exchanges/qfex/errors.js +2 -2
  204. package/dist/exchanges/qfex/public.d.ts +65 -12
  205. package/dist/exchanges/qfex/public.js +131 -25
  206. package/dist/exchanges/qfex/signing.d.ts +2 -2
  207. package/dist/exchanges/qfex/signing.js +4 -5
  208. package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
  209. package/dist/exchanges/qfex/ws-transport.js +3 -3
  210. package/dist/exchanges/rate-limiter.d.ts +1 -1
  211. package/dist/exchanges/rate-limiter.js +5 -2
  212. package/dist/exchanges/registry.d.ts +1 -1
  213. package/dist/exchanges/registry.js +1 -1
  214. package/dist/exchanges/risex/adapter.d.ts +160 -0
  215. package/dist/exchanges/risex/adapter.js +842 -0
  216. package/dist/exchanges/risex/agent.d.ts +154 -0
  217. package/dist/exchanges/risex/agent.js +384 -0
  218. package/dist/exchanges/risex/client.d.ts +47 -0
  219. package/dist/exchanges/risex/client.js +174 -0
  220. package/dist/exchanges/risex/nonce.d.ts +43 -0
  221. package/dist/exchanges/risex/nonce.js +212 -0
  222. package/dist/exchanges/risex/public.d.ts +64 -0
  223. package/dist/exchanges/risex/public.js +150 -0
  224. package/dist/exchanges/risex/session.d.ts +71 -0
  225. package/dist/exchanges/risex/session.js +171 -0
  226. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  227. package/dist/exchanges/risex/typed-data.js +240 -0
  228. package/dist/exchanges/risex/units.d.ts +29 -0
  229. package/dist/exchanges/risex/units.js +104 -0
  230. package/dist/exchanges/slippage.d.ts +1 -1
  231. package/dist/exchanges/slippage.js +1 -1
  232. package/dist/exchanges/spot-factory.d.ts +3 -3
  233. package/dist/exchanges/spot-factory.js +8 -8
  234. package/dist/exchanges/spot-interface.d.ts +5 -5
  235. package/dist/exchanges/spot-interface.js +2 -2
  236. package/dist/exchanges/spot-result.d.ts +3 -3
  237. package/dist/exchanges/spot-result.js +3 -3
  238. package/dist/exchanges/trading-v2.d.ts +3 -3
  239. package/dist/exchanges/trading-v2.js +9 -6
  240. package/dist/exchanges/txflow/adapter.d.ts +61 -0
  241. package/dist/exchanges/txflow/adapter.js +150 -0
  242. package/dist/exchanges/txflow/client.d.ts +47 -0
  243. package/dist/exchanges/txflow/client.js +155 -0
  244. package/dist/exchanges/txflow/public.d.ts +87 -0
  245. package/dist/exchanges/txflow/public.js +355 -0
  246. package/dist/exchanges/unknown-symbol.d.ts +34 -0
  247. package/dist/exchanges/unknown-symbol.js +56 -0
  248. package/dist/exchanges/variational/adapter.d.ts +67 -0
  249. package/dist/exchanges/variational/adapter.js +137 -0
  250. package/dist/exchanges/variational/client.d.ts +21 -0
  251. package/dist/exchanges/variational/client.js +66 -0
  252. package/dist/exchanges/variational/public.d.ts +126 -0
  253. package/dist/exchanges/variational/public.js +430 -0
  254. package/dist/exchanges/venue-types.d.ts +52 -10
  255. package/dist/exchanges/venue-types.js +82 -11
  256. package/dist/exchanges/venue.d.ts +150 -11
  257. package/dist/exchanges/venue.js +126 -4
  258. package/dist/exchanges/venues/arcus.d.ts +1 -2
  259. package/dist/exchanges/venues/arcus.js +5 -4
  260. package/dist/exchanges/venues/aster.d.ts +2 -2
  261. package/dist/exchanges/venues/aster.js +6 -4
  262. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  263. package/dist/exchanges/venues/create-helpers.js +3 -3
  264. package/dist/exchanges/venues/extended.d.ts +21 -0
  265. package/dist/exchanges/venues/extended.js +96 -0
  266. package/dist/exchanges/venues/grvt.d.ts +30 -0
  267. package/dist/exchanges/venues/grvt.js +98 -0
  268. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  269. package/dist/exchanges/venues/hyperliquid.js +19 -8
  270. package/dist/exchanges/venues/index.d.ts +7 -2
  271. package/dist/exchanges/venues/index.js +8 -2
  272. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  273. package/dist/exchanges/venues/lighter-family.js +9 -11
  274. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  275. package/dist/exchanges/venues/lighter-rh.js +20 -8
  276. package/dist/exchanges/venues/lighter.d.ts +1 -1
  277. package/dist/exchanges/venues/lighter.js +18 -6
  278. package/dist/exchanges/venues/ondo.d.ts +1 -1
  279. package/dist/exchanges/venues/ondo.js +5 -3
  280. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  281. package/dist/exchanges/venues/pacifica.js +15 -4
  282. package/dist/exchanges/venues/qfex.d.ts +1 -1
  283. package/dist/exchanges/venues/qfex.js +4 -2
  284. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  285. package/dist/exchanges/venues/risex-release.js +14 -0
  286. package/dist/exchanges/venues/risex.d.ts +31 -0
  287. package/dist/exchanges/venues/risex.js +131 -0
  288. package/dist/exchanges/venues/txflow.d.ts +18 -0
  289. package/dist/exchanges/venues/txflow.js +77 -0
  290. package/dist/exchanges/venues/variational.d.ts +21 -0
  291. package/dist/exchanges/venues/variational.js +85 -0
  292. package/dist/execution-log.d.ts +1 -1
  293. package/dist/funding/legacy-public.d.ts +6 -4
  294. package/dist/funding/legacy-public.js +42 -41
  295. package/dist/funding/normalize.d.ts +1 -1
  296. package/dist/funding/normalize.js +1 -1
  297. package/dist/funding/rates.d.ts +95 -6
  298. package/dist/funding/rates.js +99 -19
  299. package/dist/index.js +7 -6
  300. package/dist/jobs.d.ts +1 -0
  301. package/dist/jobs.js +25 -1
  302. package/dist/landing.d.ts +2 -0
  303. package/dist/landing.js +1 -1
  304. package/dist/liquidity.d.ts +3 -1
  305. package/dist/liquidity.js +8 -4
  306. package/dist/mcp-pacifica-earn.d.ts +9 -0
  307. package/dist/mcp-pacifica-earn.js +64 -0
  308. package/dist/mcp-server.js +211 -68
  309. package/dist/pacifica/client.d.ts +69 -5
  310. package/dist/pacifica/client.js +181 -8
  311. package/dist/pacifica/rate-limit.d.ts +144 -0
  312. package/dist/pacifica/rate-limit.js +267 -0
  313. package/dist/pacifica/types/order.d.ts +2 -1
  314. package/dist/plan-executor.js +7 -2
  315. package/dist/positions-by-address.d.ts +34 -0
  316. package/dist/positions-by-address.js +46 -0
  317. package/dist/rebalance.js +1 -1
  318. package/dist/risk.d.ts +2 -1
  319. package/dist/risk.js +2 -4
  320. package/dist/settings.d.ts +32 -3
  321. package/dist/signer/ows-evm.d.ts +1 -1
  322. package/dist/signer/ows-evm.js +1 -1
  323. package/dist/smart-order.d.ts +1 -1
  324. package/dist/smart-order.js +1 -1
  325. package/dist/strategies/funding-arb.d.ts +1 -1
  326. package/dist/strategies/funding-arb.js +36 -63
  327. package/dist/trade-validator.js +2 -2
  328. package/dist/utils.d.ts +22 -3
  329. package/dist/utils.js +11 -3
  330. package/dist/venue-health.d.ts +40 -0
  331. package/dist/venue-health.js +75 -0
  332. package/dist/withdraw-gate.d.ts +1 -1
  333. package/dist/withdraw-gate.js +1 -1
  334. package/dist/ws/arcus-market.d.ts +9 -0
  335. package/dist/ws/arcus-market.js +9 -0
  336. package/dist/ws/aster-market.d.ts +33 -0
  337. package/dist/ws/aster-market.js +30 -0
  338. package/dist/ws/index.d.ts +980 -0
  339. package/dist/ws/index.js +4460 -0
  340. package/dist/ws/lighter-market.d.ts +14 -0
  341. package/dist/ws/lighter-market.js +13 -0
  342. package/dist/ws/node.d.ts +996 -0
  343. package/dist/ws/node.js +4554 -0
  344. package/dist/ws/pacifica-instrument.d.ts +8 -0
  345. package/dist/ws/pacifica-instrument.js +8 -0
  346. package/dist/ws/unclassified.d.ts +16 -0
  347. package/dist/ws/unclassified.js +43 -0
  348. package/dist/ws-cli/credentials.d.ts +62 -0
  349. package/dist/ws-cli/credentials.js +126 -0
  350. package/package.json +71 -10
  351. package/skills/perp-cli/SKILL.md +153 -36
  352. package/skills/perp-cli/package.json +15 -2
  353. package/skills/perp-cli/references/agent-operations.md +22 -12
  354. package/skills/perp-cli/references/commands.md +143 -17
  355. package/skills/perp-cli/references/strategies.md +38 -3
  356. package/skills/perp-cli/references/ws-library.md +523 -0
  357. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  358. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  359. package/skills/perp-cli/scripts/preflight.sh +27 -19
  360. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  361. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  362. package/dist/cli-spec.d.ts +0 -46
  363. package/dist/cli-spec.js +0 -81
@@ -1,21 +1,30 @@
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,
6
6
  * quote / margin asset, allowed order types + TIFs, market hours
7
7
  * MDS market_stats snapshot mark price (each entry with its own `time`), open interest,
8
8
  * 24h notional volume
9
- * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
9
+ * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads);
10
+ * index_price (market rows, best effort)
10
11
  *
11
- * Coupling (§14.12 #10): order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
12
- * Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
13
- * fails fetchFundingRates, never market listing or order entry.
12
+ * Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
13
+ * Markets need /refdata + the MDS snapshot; /md/contracts only adds indexPrice — its failure
14
+ * leaves indexPrice null (unknown) with a warning, never fails market listing or order entry.
15
+ * Funding reads need /md/contracts.
16
+ *
17
+ * indexPrice = /md/contracts `index_price` (docs contract-specifications: the Index Price is "the
18
+ * reference spot price for the asset"; the OpenAPI field has no description). Live 2026-10-19 it
19
+ * tracked the MDS `underlier` channel (AAPL 342.47 = 342.47, GOLD 4170.22 vs 4170.05, SAMSUNG-KRW
20
+ * 220092 vs 220082 KRW), moved on 122 of 222 markets within 45 s, and sat a median 0.015 % from
21
+ * the mark (max 5 %, CVNA pre-market). It is in the quote currency (`index_currency`), like the mark.
22
+ * /md/contracts lists ACTIVE markets only; a non-positive / missing value is null.
14
23
  *
15
24
  * Funding is UNVERIFIED (venue flag fundingVerified: false, Q4): the rate's unit is not
16
25
  * confirmed against settled payments and `funding_rate` vs `next_funding_rate` is undecided.
17
26
  * Markets therefore carry fundingRate null; fetchFundingRates returns the raw `funding_rate`
18
- * for the explicit, flagged reads only (`perp funding`, `arb scan --include-unverified`).
27
+ * for the explicit, flagged reads only (`arb scan --rates|--compare --include-unverified`).
19
28
  *
20
29
  * Symbols: "AAPL-USD" → CLI symbol "AAPL". Non-USD quotes keep the suffix ("SKHYNIX-KRW"),
21
30
  * have no canonical symbol and are NOT tradable through perp-cli (USD-assuming sizing / risk).
@@ -103,26 +112,24 @@ export function toQfexInstrument(raw) {
103
112
  marketHours: hours !== null && typeof hours === "object" && !Array.isArray(hours) ? hours : null,
104
113
  };
105
114
  }
106
- /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE (§14.12 #7). */
115
+ /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
107
116
  export function qfexTradable(i) {
108
117
  return i.status === "ACTIVE" && i.quoteAsset === "USD";
109
118
  }
110
119
  // ── Market hours → session ──
111
120
  const WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
112
121
  /**
113
- * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours
114
- * (weekday + local time in its `tz`). null = the venue published no usable schedule.
115
- * This is the underlying's regular session only — it is NOT used to infer whether funding
116
- * accrues (the schedule and the documented funding hours do not map 1:1, Q9).
122
+ * Session names of the instrument's market_hours that contain `ms` (weekday + local time in its
123
+ * `tz`); [] = no window listed at that time. null = the venue published no usable schedule.
117
124
  */
118
- export function qfexUnderlyingOpen(marketHours, nowMs) {
125
+ function qfexSessionsAt(marketHours, ms) {
119
126
  if (marketHours === null || typeof marketHours.tz !== "string")
120
127
  return null;
121
128
  let parts;
122
129
  try {
123
130
  parts = new Intl.DateTimeFormat("en-US", {
124
131
  timeZone: marketHours.tz, weekday: "long", hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23",
125
- }).formatToParts(new Date(nowMs));
132
+ }).formatToParts(new Date(ms));
126
133
  }
127
134
  catch {
128
135
  return null; // an unknown IANA zone name is "no usable schedule", not a closed market
@@ -135,19 +142,70 @@ export function qfexUnderlyingOpen(marketHours, nowMs) {
135
142
  const now = `${h}:${m}:${s}`;
136
143
  const day = marketHours[weekday];
137
144
  if (day === undefined)
138
- return false; // no window listed for this weekday
145
+ return []; // no window listed for this weekday
139
146
  if (!Array.isArray(day))
140
147
  return null;
148
+ const out = [];
141
149
  for (const w of day) {
142
150
  if (w === null || typeof w !== "object")
143
151
  return null;
144
152
  const win = w;
145
153
  if (typeof win.session !== "string" || typeof win.open !== "string" || typeof win.close !== "string")
146
154
  return null;
147
- if (win.session === "open" && now >= win.open && now <= win.close)
148
- return true;
155
+ if (now >= win.open && now <= win.close)
156
+ out.push(win.session);
149
157
  }
150
- return false;
158
+ return out;
159
+ }
160
+ /**
161
+ * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours.
162
+ * null = the venue published no usable schedule. This is the underlying's regular session only;
163
+ * funding hours are qfexFundingWindowClosed's question.
164
+ */
165
+ export function qfexUnderlyingOpen(marketHours, nowMs) {
166
+ const sessions = qfexSessionsAt(marketHours, nowMs);
167
+ return sessions === null ? null : sessions.includes("open");
168
+ }
169
+ /** market_hours session names during which no funding accrues. */
170
+ const QFEX_NON_FUNDING_SESSIONS = new Set(["weekend", "close"]);
171
+ /**
172
+ * Was the funding window labelled `windowStartMs` outside the market's funding hours? Docs
173
+ * (docs.qfex.com/qfex/funding): "Funding is only active during a symbol's configured
174
+ * market_hours"; outside them "funding rate is 0 and no funding payment is exchanged".
175
+ *
176
+ * The window labelled T covers the hour BEFORE T (live 2026-10-19, GET /funding, 9 symbols over
177
+ * 10 days: AAPL-USD prints its last non-zero rate at Sat 00:00Z = Fri 20:00 ET and its first at
178
+ * Mon 01:00Z = Sun 21:00 ET, i.e. funding Sun 20:00–Fri 20:00 ET as documented, only when each
179
+ * label is read as the end of its hour).
180
+ *
181
+ * market_hours alone does not say whether an unlisted gap funds (US equities fund overnight
182
+ * between post_market and pre_market; Korean equities do not fund outside "open"), so a window is
183
+ * closed when the venue printed exactly 0 AND no part of the hour was in a funding session
184
+ * (anything but "weekend" / "close"). A non-zero rate is never marked closed, and a 0 inside a
185
+ * session stays a real 0. No usable schedule → never closed.
186
+ *
187
+ * Known limits (review B2-4 / B2-5):
188
+ * - market holidays are not in market_hours: a holiday inside a configured weekday session
189
+ * (SAMSUNG-KRW on Hangul Day) prints the venue's 0 as a real-looking "0", marketClosed false;
190
+ * - US equities fund in the weekday overnight gap (post_market → pre_market, docs "Sunday 20:00 –
191
+ * Friday 20:00 ET"), so a gap is never closed by schedule alone — only an exact 0 there is, and
192
+ * a genuine 0 rate in such a gap is (rarely) reported as closed.
193
+ */
194
+ export function qfexFundingWindowClosed(marketHours, windowStartMs, rate) {
195
+ if (rate !== 0)
196
+ return false;
197
+ const from = windowStartMs - 3_600_000;
198
+ // Sampled inside the hour, never on its edges: market_hours closes are inclusive and often on
199
+ // the hour (GOLD-USD Fri close "17:00:00"), so a sample AT `from` would put the hour after the
200
+ // close inside the session (GOLD printed 0 for Fri 17:00–18:00 ET on 2026-10-09 and 10-16).
201
+ for (const t of [from + 1_000, from + 900_000, from + 1_800_000, from + 2_700_000, windowStartMs - 1_000]) {
202
+ const sessions = qfexSessionsAt(marketHours, t);
203
+ if (sessions === null)
204
+ return false;
205
+ if (sessions.some(x => !QFEX_NON_FUNDING_SESSIONS.has(x)))
206
+ return false;
207
+ }
208
+ return true;
151
209
  }
152
210
  function sessionOf(i, nowMs) {
153
211
  const open = qfexUnderlyingOpen(i.marketHours, nowMs);
@@ -173,10 +231,24 @@ function canonicalOf(i) {
173
231
  return null;
174
232
  return canonicalFor("qfex", i.symbol, i.assetClass);
175
233
  }
176
- export function buildQfexMarkets(instruments, snap, nowMs) {
234
+ /** venue symbol → /md/contracts `index_price` (positive finite only). */
235
+ export function qfexIndexPrices(contracts) {
236
+ const out = new Map();
237
+ for (const c of contracts) {
238
+ const v = c.index_price;
239
+ if (typeof c.ticker_id === "string" && typeof v === "string" && Number.isFinite(Number(v)) && Number(v) > 0)
240
+ out.set(c.ticker_id, v);
241
+ }
242
+ return out;
243
+ }
244
+ export function buildQfexMarkets(instruments, snap, nowMs, indexPrices = new Map()) {
177
245
  const markets = [];
178
246
  const activeWithoutMark = [];
179
247
  for (const i of instruments) {
248
+ // Listing policy: a DELISTED / INACTIVE market is not listed (per-symbol reads refuse it); only
249
+ // ACTIVE markets are, with tradable false when perp-cli cannot trade them (non-USD quote).
250
+ if (i.status !== "ACTIVE")
251
+ continue;
180
252
  const mark = snap.marks.get(i.venueSymbol);
181
253
  if (mark === undefined) {
182
254
  if (i.status === "ACTIVE")
@@ -194,12 +266,16 @@ export function buildQfexMarkets(instruments, snap, nowMs) {
194
266
  settle: i.marginAsset,
195
267
  quoteAsset: i.quoteAsset,
196
268
  markPrice: mark.price,
197
- indexPrice: null,
269
+ indexPrice: indexPrices.get(i.venueSymbol) ?? null,
198
270
  fundingRate: null, // unverified (Q4) — see module header
199
271
  fundingRateBasis: QFEX_FUNDING_BASIS,
200
272
  settlementIntervalHours: QFEX_SETTLEMENT_HOURS,
201
273
  openInterest: oi === undefined ? null : oi,
202
- volume24h: vol === undefined ? null : vol,
274
+ // daily_volumes_notional is in the QUOTE currency (live 2026-10-19: SAMSUNG-KRW 9,827,530 =
275
+ // /md/contracts target_volume 9,763,491 KRW ≈ base_volume 45.16 × ~216,000 KRW, and the
276
+ // 24 hourly candles' usdVolume sum to 9,821,767). VenueMarket.volume24h is USD, and the venue
277
+ // publishes no USD-KRW / JPY / CNY / SEK / HKD rate → null for a non-USD quote.
278
+ volume24h: vol === undefined || i.quoteAsset !== "USD" ? null : vol,
203
279
  tickSize: i.tickSize,
204
280
  stepSize: i.lotSize,
205
281
  minSize: i.minQuantity,
@@ -212,14 +288,40 @@ export function buildQfexMarkets(instruments, snap, nowMs) {
212
288
  }
213
289
  return { markets, activeWithoutMark };
214
290
  }
215
- async function fetchMarkets(net) {
216
- const instruments = await fetchQfexInstruments(qfexPublicClient(net));
291
+ /**
292
+ * Priced markets, the /refdata instruments they were built from (status / quote asset per row) and
293
+ * the 24h notional per venue symbol in its QUOTE currency (KRW for SAMSUNG-KRW).
294
+ */
295
+ export async function fetchQfexMarketsWithInstruments(net) {
296
+ const client = qfexPublicClient(net);
297
+ const instruments = await fetchQfexInstruments(client);
217
298
  const snap = await fetchQfexMarketStats(QFEX_ENDPOINTS[net].mdsWs, "*", QFEX_MDS_TIMEOUT_MS);
218
- const { markets, activeWithoutMark } = buildQfexMarkets(instruments, snap, Date.now());
299
+ let indexPrices = new Map();
300
+ try {
301
+ const body = await client.publicGet("/md/contracts", null);
302
+ if (!Array.isArray(body.data))
303
+ throw new Error("response has no data list");
304
+ indexPrices = qfexIndexPrices(body.data);
305
+ }
306
+ catch (e) {
307
+ console.warn(`[qfex] index prices unavailable (/md/contracts: ${e instanceof Error ? e.message : String(e)}); indexPrice left null`);
308
+ }
309
+ const { markets, activeWithoutMark } = buildQfexMarkets(instruments, snap, Date.now(), indexPrices);
219
310
  if (activeWithoutMark.length > 0) {
220
311
  console.warn(`[qfex] excluded ${activeWithoutMark.length} active market(s) without a mark price: ${activeWithoutMark.join(", ")}`);
221
312
  }
222
- return markets;
313
+ return { markets, instruments, volumeNotional: snap.volumeNotional };
314
+ }
315
+ /** "active" = ACTIVE and USD-quoted; "nonUsdQuote" = ACTIVE, quoted in KRW / JPY / … (readable, never ordered). */
316
+ export function qfexListedStatus(i) {
317
+ if (i.status !== "ACTIVE")
318
+ throw new PerpError("EXCHANGE_ERROR", `QFEX ${i.venueSymbol} is ${i.status}; it is not listed`, { exchange: "qfex" });
319
+ return qfexTradable(i) ? "active" : "nonUsdQuote";
320
+ }
321
+ async function fetchMarkets(net) {
322
+ const { markets, instruments } = await fetchQfexMarketsWithInstruments(net);
323
+ const bySymbol = new Map(instruments.map(i => [i.venueSymbol, i]));
324
+ return markets.map(m => { const i = bySymbol.get(m.venueSymbol); return i === undefined ? m : { ...m, status: qfexListedStatus(i) }; });
223
325
  }
224
326
  export function buildQfexFundingRates(instruments, contracts, marks, nowMs) {
225
327
  const byVenueSymbol = new Map(instruments.map(i => [i.venueSymbol, i]));
@@ -238,12 +340,16 @@ export function buildQfexFundingRates(instruments, contracts, marks, nowMs) {
238
340
  }
239
341
  const mark = marks.get(c.ticker_id);
240
342
  const nextMs = typeof c.next_funding_rate_timestamp === "number" ? c.next_funding_rate_timestamp * 1000 : null;
343
+ // `funding_rate` is the implied rate of the window in progress, labelled by its END (the next
344
+ // top of the hour, = next_funding_rate_timestamp live) — the same window rule as history.
345
+ const marketClosed = qfexFundingWindowClosed(i.marketHours, Math.floor(nowMs / 3_600_000) * 3_600_000 + 3_600_000, Number(c.funding_rate));
241
346
  rates.push({
242
347
  venue: "qfex",
243
348
  venueSymbol: c.ticker_id,
244
349
  symbol: i.symbol,
245
350
  canonical: canonicalOf(i),
246
- rate: c.funding_rate,
351
+ rate: marketClosed ? null : c.funding_rate,
352
+ marketClosed,
247
353
  basis: QFEX_FUNDING_BASIS,
248
354
  markPrice: mark === undefined ? null : mark.price,
249
355
  // A timestamp in the past is not a "next" funding time (seen on closed markets).
@@ -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
@@ -277,7 +277,7 @@ export interface QfexMdsSnapshot {
277
277
  }>;
278
278
  /** Venue symbol → open interest (base units). */
279
279
  openInterest: Map<string, string>;
280
- /** Venue symbol → 24h volume, quote notional. */
280
+ /** Venue symbol → 24h volume, notional in the market's QUOTE currency (KRW for SAMSUNG-KRW — not USD). */
281
281
  volumeNotional: Map<string, string>;
282
282
  frame: QfexFrame;
283
283
  }
@@ -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,160 @@
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, FundingHistoryRow } 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
+ /** `depth` levels per side (API max 250, developer.rise.trade; was always 50). */
96
+ getOrderbook(symbol: string, depth?: number): Promise<{
97
+ bids: [string, string][];
98
+ asks: [string, string][];
99
+ }>;
100
+ getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
101
+ /**
102
+ * Settled hourly prints, newest first. The API defaults `start_time` to now − 7 d (168 rows, any
103
+ * `limit`), so the window is sent explicitly (`limit` hours back, one spare) and pages are
104
+ * followed with `page` / `has_next_page` (max 1000 rows per page; developer.rise.trade
105
+ * getfundingratehistory; verified live 2026-10-19).
106
+ */
107
+ getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
108
+ getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
109
+ private portfolio;
110
+ getBalance(): Promise<ExchangeBalance>;
111
+ getPositions(): Promise<ExchangePosition[]>;
112
+ private openOrdersRaw;
113
+ private historyOrders;
114
+ private toExchangeOrder;
115
+ /** On-chain open orders, enriched with the indexer's decimals / fills (one retry for indexer lag). */
116
+ getOpenOrders(): Promise<ExchangeOrder[]>;
117
+ getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
118
+ getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
119
+ /** `payment` is positive when RECEIVED (RISEx `funding_payment` is positive when paid — negated here). */
120
+ getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
121
+ placeOrder(req: OrderRequest): Promise<OrderResult>;
122
+ /** One read-only reconciliation of an unknown place outcome. */
123
+ private reconcilePlace;
124
+ amendOrder(_req: AmendRequest): Promise<OrderResult>;
125
+ cancelByRef(symbol: string, ref: OrderRef): Promise<CancelResult>;
126
+ cancelAllV2(symbol: string | null): Promise<CancelResult>;
127
+ applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
128
+ private sendAccountAction;
129
+ getOrderStatus(symbol: string, ref: OrderRef): Promise<OrderResult>;
130
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
131
+ reduceOnly: boolean;
132
+ slippage: ResolvedSlippage;
133
+ clientOrderId?: string;
134
+ }): Promise<unknown>;
135
+ limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
136
+ reduceOnly?: boolean;
137
+ tif?: string;
138
+ clientOrderId?: string;
139
+ }): Promise<unknown>;
140
+ editOrder(): Promise<unknown>;
141
+ cancelOrder(symbol: string, orderId: string): Promise<unknown>;
142
+ cancelAllOrders(symbol?: string): Promise<unknown>;
143
+ setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
144
+ stopOrder(): Promise<unknown>;
145
+ /** TP/SL entries are refused before the entry is sent (not implemented yet). */
146
+ stopOrderGate(): PerpError;
147
+ /** Bots / TWAP / grid / funding-arb legs stay off until RISEx trading is verified on mainnet. */
148
+ automationGate(): PerpError;
149
+ }
150
+ /** Candle intervals → ms (the venue takes the interval in ns). */
151
+ /**
152
+ * Candle intervals verified live (2026-10-19, market 1): each returns rows labelled with that
153
+ * interval. 3m / 30m / 2h / 8h / 12h / 3d are answered with 1m bars (no error), so they are not
154
+ * listed — getKlines refuses them with INVALID_PARAMS instead of returning 1m candles.
155
+ */
156
+ export declare const RISEX_BOOK_MAX = 250;
157
+ export declare const RISEX_TRADES_CAP = 1000;
158
+ /** funding-rate-history: max rows per page (docs) and pages followed before giving up. */
159
+ export declare const RISEX_FUNDING_PAGE_MAX = 1000;
160
+ export declare const KLINE_INTERVAL_MS: Readonly<Record<string, number>>;