perp-cli 0.12.17 → 0.14.1

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.md +247 -27
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +303 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +70 -92
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +248 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +925 -245
  103. package/dist/commands/history.js +102 -54
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +357 -459
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +240 -6
  166. package/dist/exchanges/aster.js +1139 -195
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +152 -0
  200. package/dist/exchanges/ondo/adapter.js +811 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +95 -0
  206. package/dist/exchanges/ondo/funds.js +198 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +293 -4
  218. package/dist/exchanges/pacifica.js +1044 -90
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1386 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +29 -0
  270. package/dist/exchanges/venues/ondo.js +136 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +39 -0
  274. package/dist/exchanges/venues/qfex.js +140 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +821 -403
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/onchain-balances.d.ts +40 -0
  293. package/dist/onchain-balances.js +173 -0
  294. package/dist/pacifica/client.d.ts +41 -10
  295. package/dist/pacifica/client.js +167 -63
  296. package/dist/pacifica/constants.d.ts +0 -1
  297. package/dist/pacifica/constants.js +0 -5
  298. package/dist/pacifica/deposit.d.ts +30 -3
  299. package/dist/pacifica/deposit.js +119 -2
  300. package/dist/pacifica/faucet.d.ts +29 -0
  301. package/dist/pacifica/faucet.js +100 -0
  302. package/dist/pacifica/sign-tx.d.ts +15 -0
  303. package/dist/pacifica/sign-tx.js +41 -0
  304. package/dist/pacifica/types/account.d.ts +28 -4
  305. package/dist/pacifica/types/lake.d.ts +20 -5
  306. package/dist/pacifica/types/lake.js +4 -0
  307. package/dist/pacifica/types/market.d.ts +5 -0
  308. package/dist/pacifica/types/order.d.ts +4 -1
  309. package/dist/pacifica/types/position.d.ts +9 -1
  310. package/dist/plan-executor.d.ts +10 -5
  311. package/dist/plan-executor.js +165 -54
  312. package/dist/position-history.js +13 -0
  313. package/dist/rebalance.d.ts +26 -2
  314. package/dist/rebalance.js +86 -7
  315. package/dist/relay-swap.d.ts +232 -0
  316. package/dist/relay-swap.js +630 -0
  317. package/dist/risk.js +5 -1
  318. package/dist/secret-env.d.ts +15 -0
  319. package/dist/secret-env.js +36 -0
  320. package/dist/settings.d.ts +169 -6
  321. package/dist/settings.js +219 -38
  322. package/dist/signer/ows-evm.d.ts +13 -0
  323. package/dist/signer/ows-evm.js +22 -0
  324. package/dist/smart-order.d.ts +9 -0
  325. package/dist/smart-order.js +53 -8
  326. package/dist/strategies/dca.d.ts +6 -2
  327. package/dist/strategies/dca.js +66 -26
  328. package/dist/strategies/funding-arb.d.ts +11 -1
  329. package/dist/strategies/funding-arb.js +202 -89
  330. package/dist/strategies/grid.d.ts +31 -0
  331. package/dist/strategies/grid.js +89 -11
  332. package/dist/strategies/pending-stop.d.ts +8 -0
  333. package/dist/strategies/pending-stop.js +12 -0
  334. package/dist/strategies/split-order.d.ts +45 -17
  335. package/dist/strategies/split-order.js +157 -91
  336. package/dist/strategies/trailing-stop.d.ts +2 -1
  337. package/dist/strategies/trailing-stop.js +52 -6
  338. package/dist/strategies/twap.d.ts +6 -1
  339. package/dist/strategies/twap.js +39 -11
  340. package/dist/trade-validator.d.ts +22 -0
  341. package/dist/trade-validator.js +119 -15
  342. package/dist/utils/numeric.d.ts +46 -0
  343. package/dist/utils/numeric.js +79 -0
  344. package/dist/utils.d.ts +19 -0
  345. package/dist/utils.js +96 -19
  346. package/dist/withdraw-gate.d.ts +49 -0
  347. package/dist/withdraw-gate.js +131 -0
  348. package/package.json +15 -6
  349. package/skills/perp-cli/SKILL.md +159 -55
  350. package/skills/perp-cli/references/agent-operations.md +112 -58
  351. package/skills/perp-cli/references/commands.md +269 -73
  352. package/skills/perp-cli/references/strategies.md +34 -31
  353. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  354. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  355. package/skills/perp-cli/scripts/preflight.sh +21 -14
  356. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  357. package/skills/perp-cli/scripts/validate-arb.sh +31 -15
  358. package/vendor/lighter-signer/SHA256SUMS +2 -0
  359. package/vendor/lighter-signer/VERSION +8 -0
  360. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  361. package/vendor/lighter-signer/wasm_exec.js +575 -0
  362. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  363. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -0,0 +1,39 @@
1
+ /**
2
+ * QFEX venue definition (design §14.1 as amended by §14.12).
3
+ *
4
+ * TradFi perpetuals (equities, indices, commodities; no spot) on a custodial, off-chain
5
+ * matching engine. All markets are cross-margin, margin asset USDC. Orders go over the Trade
6
+ * WebSocket only; account reads, leverage and transfers over REST; deposits / withdrawals over
7
+ * the separate "banker" host. Hot-path key = HMAC API key created on the QFEX website (2FA),
8
+ * stored in ~/.perp/qfex-agents/<publicKey>.json (SSOT Rule #3). There is no wallet signature.
9
+ * Not registered here; the registry module owns registerVenue().
10
+ */
11
+ import type { VenueDefinition } from "../venue.js";
12
+ import type { Network } from "../venue-types.js";
13
+ /**
14
+ * Hosts per network. VenueNetworkConfig carries only two (rest + ws), so the MDS and banker
15
+ * hosts live here. Testnet = QFEX "UAT": "identical API and behaviour, separate exchange
16
+ * instance with no real funds" (official CLI README "Environments").
17
+ */
18
+ export declare const QFEX_ENDPOINTS: Readonly<Record<Network, {
19
+ rest: string;
20
+ tradeWs: string;
21
+ mdsWs: string;
22
+ banker: string;
23
+ }>>;
24
+ /** Chain QFEX USDC deposits / withdrawals use in perp-cli (banker network ARBITRUM_ONE). */
25
+ export declare const QFEX_WITHDRAW_NETWORK = "arbitrum";
26
+ /**
27
+ * API withdrawals are switched off (owner decision 2026-10-13): `funds withdraw qfex` and
28
+ * QfexAdapter.withdraw refuse with CAPABILITY_UNSUPPORTED. The implementation (banker POST
29
+ * /withdraw behind settings.withdraw.qfex) is kept; flip this to re-enable it.
30
+ */
31
+ export declare const QFEX_WITHDRAW_ENABLED: boolean;
32
+ /** The uniform refusal while QFEX_WITHDRAW_ENABLED is false. */
33
+ export declare const QFEX_WITHDRAW_DISABLED: {
34
+ readonly reason: "QFEX withdrawals are disabled in perp-cli for now";
35
+ readonly remediation: "Withdraw from the QFEX web app";
36
+ };
37
+ /** Banker `network` value for QFEX_WITHDRAW_NETWORK. */
38
+ export declare const QFEX_BANKER_NETWORK = "ARBITRUM_ONE";
39
+ export declare const qfexVenue: VenueDefinition;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * QFEX venue definition (design §14.1 as amended by §14.12).
3
+ *
4
+ * TradFi perpetuals (equities, indices, commodities; no spot) on a custodial, off-chain
5
+ * matching engine. All markets are cross-margin, margin asset USDC. Orders go over the Trade
6
+ * WebSocket only; account reads, leverage and transfers over REST; deposits / withdrawals over
7
+ * the separate "banker" host. Hot-path key = HMAC API key created on the QFEX website (2FA),
8
+ * stored in ~/.perp/qfex-agents/<publicKey>.json (SSOT Rule #3). There is no wallet signature.
9
+ * Not registered here; the registry module owns registerVenue().
10
+ */
11
+ import { BUILDER_FEE_CAPABILITIES } from "../builder-fee.js";
12
+ import { QFEX_MODIFY_KEEPS_CLIENT_ID, QFEX_RESTING_REDUCE_ONLY_VERIFIED } from "../qfex/signing.js";
13
+ /**
14
+ * Hosts per network. VenueNetworkConfig carries only two (rest + ws), so the MDS and banker
15
+ * hosts live here. Testnet = QFEX "UAT": "identical API and behaviour, separate exchange
16
+ * instance with no real funds" (official CLI README "Environments").
17
+ */
18
+ export const QFEX_ENDPOINTS = {
19
+ mainnet: { rest: "https://api.qfex.com", tradeWs: "wss://trade.qfex.com", mdsWs: "wss://mds.qfex.com", banker: "https://banker.qfex.com" },
20
+ testnet: { rest: "https://api.qfex.io", tradeWs: "wss://trade.qfex.io", mdsWs: "wss://mds.qfex.io", banker: "https://banker.qfex.io" },
21
+ };
22
+ /** Chain QFEX USDC deposits / withdrawals use in perp-cli (banker network ARBITRUM_ONE). */
23
+ export const QFEX_WITHDRAW_NETWORK = "arbitrum";
24
+ /**
25
+ * API withdrawals are switched off (owner decision 2026-10-13): `funds withdraw qfex` and
26
+ * QfexAdapter.withdraw refuse with CAPABILITY_UNSUPPORTED. The implementation (banker POST
27
+ * /withdraw behind settings.withdraw.qfex) is kept; flip this to re-enable it.
28
+ */
29
+ export const QFEX_WITHDRAW_ENABLED = false;
30
+ /** The uniform refusal while QFEX_WITHDRAW_ENABLED is false. */
31
+ export const QFEX_WITHDRAW_DISABLED = { reason: "QFEX withdrawals are disabled in perp-cli for now", remediation: "Withdraw from the QFEX web app" };
32
+ /** Banker `network` value for QFEX_WITHDRAW_NETWORK. */
33
+ export const QFEX_BANKER_NETWORK = "ARBITRUM_ONE";
34
+ /** Lazy: public.ts (and `ws`) is only loaded when market data is actually requested. */
35
+ const publicData = {
36
+ async fetchFundingRates(net) {
37
+ return (await import("../qfex/public.js")).qfexPublicData.fetchFundingRates(net);
38
+ },
39
+ async fetchMarkets(net) {
40
+ return (await import("../qfex/public.js")).qfexPublicData.fetchMarkets(net);
41
+ },
42
+ };
43
+ function network(net) {
44
+ return {
45
+ restUrl: QFEX_ENDPOINTS[net].rest,
46
+ wsUrl: QFEX_ENDPOINTS[net].tradeWs,
47
+ explorerUrl: null,
48
+ signingChainId: null,
49
+ settlementChain: "offchain",
50
+ collateral: ["USDC"],
51
+ collateralAssetIndex: null,
52
+ rpcUrl: null,
53
+ depositPins: null,
54
+ };
55
+ }
56
+ export const qfexVenue = {
57
+ id: "qfex",
58
+ aliases: ["qf"],
59
+ displayName: "QFEX",
60
+ family: "qfex",
61
+ // Custodial: there is no owner-wallet signing path at all.
62
+ masterKeyKind: "none",
63
+ agentKeyKind: "hmac",
64
+ secretStorage: { kind: "perp-keystore", dir: "qfex-agents" },
65
+ networks: { mainnet: network("mainnet"), testnet: network("testnet") },
66
+ // Docs /qfex/funding: settled every 60 minutes.
67
+ fundingRateBasis: { kind: "perHours", hours: 1 },
68
+ settlementIntervalHours: 1,
69
+ // Q4: the unit of `funding_rate` (and funding_rate vs next_funding_rate) is not verified
70
+ // against settled payments, and equity funding accrues only ~120 of 168 hours a week, so
71
+ // hourly × 8760 overstates it. Until both are settled QFEX is not a funding-arb venue.
72
+ fundingVerified: false,
73
+ roles: ["marketData", "account", "trading"],
74
+ capabilities: {
75
+ // The venue offers a native MARKET order (bounded only by the price band — docs /qfex/risk-limits
76
+ // "Dynamic price bands": 1.65%–10% by product preset and session; "integrations should use
77
+ // that live value rather than hardcoding a percentage"); the
78
+ // adapter sends every market order as an IOC LIMIT at the tick-aligned protection price
79
+ // (mark ± resolved slippage, design §13.2) — the same declaration as Ondo.
80
+ marketOrder: "native",
81
+ // ALO = order_type "ALO" + GTC.
82
+ timeInForce: ["GTC", "IOC", "FOK", "ALO"],
83
+ // Reduce-only (Q2, production 2026-10-05: `reduce_only` is a JSON boolean; rejected with no
84
+ // position, but an order larger than the position is accepted and RESTS at full size). The
85
+ // adapter enforces side and size against the live position before each send, which covers
86
+ // an immediate order only — a resting one is refused until match-time capping is proven
87
+ // (QFEX_RESTING_REDUCE_ONLY_VERIFIED, qfex/signing.ts).
88
+ reduceOnlyTif: QFEX_RESTING_REDUCE_ONLY_VERIFIED === true ? null : ["IOC"],
89
+ clientOrderId: true,
90
+ // The ACK arrives first; fills / rejections follow as separate events. Client order ids
91
+ // are GENERATED only (venue ids are not unique) — a user --client-id is refused.
92
+ orderAck: "async",
93
+ // Attached take_profit / stop_loss exists on add_order, but the stop quantity on a partial
94
+ // or zero-fill entry and the id of the resulting stop are unverified (Q7) → separate
95
+ // reduce-only TAKE_PROFIT / STOP_LOSS stop orders sized to the verified fill (Q21 passed on
96
+ // production 2026-10-12: the stop ACK echoes the client id — qfex/signing.ts QFEX_STOP_CLIENT_ID_ECHO).
97
+ tpsl: "separateOrders",
98
+ // modify_order returns a NEW order id; the MODIFIED frame keeps the client id (Q5, production
99
+ // 2026-10-12 — QFEX_MODIFY_KEEPS_CLIENT_ID) → native amend correlated by client id.
100
+ amend: QFEX_MODIFY_KEEPS_CLIENT_ID === true ? "native" : null,
101
+ batchPlaceMax: null,
102
+ marginModes: ["cross"],
103
+ // TRON USDT and USD bank rails are out of scope. API withdrawals are off (QFEX_WITHDRAW_ENABLED).
104
+ withdrawAssets: QFEX_WITHDRAW_ENABLED ? ["USDC"] : null,
105
+ },
106
+ // Venue budget: 12,000 weight / 60 s per USER across all connections and keys (general and
107
+ // cancel buckets separate). This process takes 25% (50/s, burst 100); the rest is left to the
108
+ // user's other processes, the QFEX web app and the official CLI. Weights: docs /websocket/rate.
109
+ rateLimit: {
110
+ capacity: 100,
111
+ refillPerSec: 50,
112
+ weights: {
113
+ add_order: 1, cancel_order: 1, modify_order: 1, get_order: 2, get_user_orders: 5, cancel_all_orders: 2,
114
+ get_user_trades: 0.5, subscribe: 0.1, unsubscribe: 0.1, set_user_leverage: 0.1, cancel_stop_order: 1,
115
+ modify_stop_order: 1,
116
+ },
117
+ },
118
+ // Docs /qfex/faq + prohibited-jurisdictions ("including but not limited to"). Warn-only.
119
+ restrictedRegions: ["US", "GB", "ES", "UA", "SY", "IR", "RU", "KP", "MM", "BY", "VE", "SD"],
120
+ symbolIdentity: "explicit",
121
+ publicData,
122
+ spot: null,
123
+ // Widest documented price band (equities at the open: 10%). The order's own band is checked before sending.
124
+ maxSlippageBps: 1000,
125
+ builderFee: BUILDER_FEE_CAPABILITIES["qfex"],
126
+ create: async (ctx) => {
127
+ const { QfexAdapter } = await import("../qfex/adapter.js");
128
+ const adapter = new QfexAdapter(qfexVenue, ctx);
129
+ // Builder code: only settings builder.qfex (off by default); no approval step. It rides on
130
+ // the connection's auth, so --no-builder / testnet use a session without it.
131
+ const { loadSettings } = await import("../../settings.js");
132
+ const { resolveBuilderGate } = await import("../builder-fee.js");
133
+ const settings = loadSettings();
134
+ adapter.builderFee = resolveBuilderGate("qfex", ctx, settings.builder, null, () => "");
135
+ // Withdraw gate (settings.withdraw.qfex): absent = withdraw() refuses.
136
+ const gate = settings.withdraw.qfex;
137
+ adapter.withdrawGate = gate === undefined ? null : gate;
138
+ return adapter;
139
+ },
140
+ };
@@ -11,13 +11,21 @@ function genId() {
11
11
  }
12
12
  /** Append an execution record to the log */
13
13
  export function logExecution(record) {
14
- ensureDir();
15
14
  const full = {
16
15
  id: genId(),
17
16
  timestamp: new Date().toISOString(),
18
17
  ...record,
19
18
  };
20
- appendFileSync(LOG_FILE, JSON.stringify(full) + "\n", { mode: 0o600 });
19
+ // The log is a record OF an outcome, never part of it: a write failure (disk full, read-only
20
+ // home, permissions) must not turn a filled order into a thrown "failed" one. It is reported
21
+ // on stderr and the caller continues with the real result.
22
+ try {
23
+ ensureDir();
24
+ appendFileSync(LOG_FILE, JSON.stringify(full) + "\n", { mode: 0o600 });
25
+ }
26
+ catch (e) {
27
+ process.stderr.write(`[perp] Warning: could not write the execution log (${LOG_FILE}): ${e instanceof Error ? e.message : String(e)} — the ${record.type} on ${record.exchange} was recorded as "${record.status}" in memory only\n`);
28
+ }
21
29
  return full;
22
30
  }
23
31
  /** Read execution log with optional filters */
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Public market data for the legacy venues without a native `getVenueMarkets`
3
+ * (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
4
+ * exchanges/lighter-public.ts.
5
+ *
6
+ * Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
7
+ * never from the mutable `*_API_URL` globals. A network without a known public
8
+ * API throws NOT_IMPLEMENTED.
9
+ *
10
+ * FUNDING BASIS / SETTLEMENT
11
+ * pacifica, hyperliquid: the venue definition (fundingBasisOf / settlementIntervalOf).
12
+ * aster: per symbol from GET /fapi/v3/fundingInfo `fundingIntervalHours`
13
+ * (live 2026-09-30: 1h/2h/4h/8h mixed). One quoted rate covers one interval,
14
+ * so basis hours == settlement hours. A symbol missing from fundingInfo is
15
+ * excluded with a warning (its period would be a guess).
16
+ *
17
+ * ASSET CLASS
18
+ * hyperliquid core: crypto (HIP-3 dexes are not served here).
19
+ * pacifica: crypto-centric; PACIFICA_NON_CRYPTO lists every non-crypto perp
20
+ * listed on 2026-09-30.
21
+ * aster: exchangeInfo `underlyingSubType` (STOCK / ETF / Commodities / pre-launch).
22
+ * AMBIGUOUS tickers are excluded with a warning so they cannot produce a false
23
+ * cross-venue match (SPX = the SPX6900 token, whose ticker equals the S&P 500
24
+ * canonical key).
25
+ *
26
+ * Fields a venue does not publish on this path are null (HL has no fixed tick
27
+ * size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
28
+ */
29
+ import type { VenuePublicData } from "../exchanges/venue.js";
30
+ export type LegacyPublicVenue = "pacifica" | "hyperliquid" | "aster";
31
+ export declare function legacyPublicData(venue: LegacyPublicVenue): VenuePublicData;
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Public market data for the legacy venues without a native `getVenueMarkets`
3
+ * (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
4
+ * exchanges/lighter-public.ts.
5
+ *
6
+ * Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
7
+ * never from the mutable `*_API_URL` globals. A network without a known public
8
+ * API throws NOT_IMPLEMENTED.
9
+ *
10
+ * FUNDING BASIS / SETTLEMENT
11
+ * pacifica, hyperliquid: the venue definition (fundingBasisOf / settlementIntervalOf).
12
+ * aster: per symbol from GET /fapi/v3/fundingInfo `fundingIntervalHours`
13
+ * (live 2026-09-30: 1h/2h/4h/8h mixed). One quoted rate covers one interval,
14
+ * so basis hours == settlement hours. A symbol missing from fundingInfo is
15
+ * excluded with a warning (its period would be a guess).
16
+ *
17
+ * ASSET CLASS
18
+ * hyperliquid core: crypto (HIP-3 dexes are not served here).
19
+ * pacifica: crypto-centric; PACIFICA_NON_CRYPTO lists every non-crypto perp
20
+ * listed on 2026-09-30.
21
+ * aster: exchangeInfo `underlyingSubType` (STOCK / ETF / Commodities / pre-launch).
22
+ * AMBIGUOUS tickers are excluded with a warning so they cannot produce a false
23
+ * cross-venue match (SPX = the SPX6900 token, whose ticker equals the S&P 500
24
+ * canonical key).
25
+ *
26
+ * Fields a venue does not publish on this path are null (HL has no fixed tick
27
+ * size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
28
+ */
29
+ import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
30
+ import { PerpError } from "../errors.js";
31
+ import { venueRateString } from "../utils/numeric.js";
32
+ import { assertOk } from "../api/public/_http.js";
33
+ import { legacyPublicBaseUrl } from "../api/public/urls.js";
34
+ import { canonicalFor } from "../exchanges/canonical-symbols.js";
35
+ import { fundingBasisOf, settlementIntervalOf } from "../exchanges/venue.js";
36
+ // ── Classification ──
37
+ const AMBIGUOUS = new Set(["SPX"]);
38
+ /** Pacifica non-crypto perps (live /info 2026-09-30). BP / CHIP: ticker collides with a listed equity/ETF. */
39
+ const PACIFICA_NON_CRYPTO = {
40
+ NVDA: "equity", TSLA: "equity", GOOGL: "equity", PLTR: "equity", HOOD: "equity", CRCL: "equity",
41
+ SPCX: "equity", SKHYNIX: "equity", SAMSUNG: "equity", MSTR: "equity", MU: "equity", SNDK: "equity",
42
+ META: "equity",
43
+ DRAM: "etf", URNM: "etf",
44
+ XAU: "commodity", XAG: "commodity", CL: "commodity", COPPER: "commodity", NATGAS: "commodity",
45
+ PLATINUM: "commodity",
46
+ USDJPY: "fx", EURUSD: "fx",
47
+ SP500: "index",
48
+ };
49
+ const PACIFICA_AMBIGUOUS = new Set(["BP", "CHIP"]);
50
+ function classifyPacifica(symbol) {
51
+ const up = symbol.toUpperCase();
52
+ if (AMBIGUOUS.has(up) || PACIFICA_AMBIGUOUS.has(up))
53
+ return null;
54
+ return PACIFICA_NON_CRYPTO[up] ?? "crypto"; // crypto-centric venue: non-crypto is listed explicitly
55
+ }
56
+ function classifyHyperliquid(symbol) {
57
+ return AMBIGUOUS.has(symbol.toUpperCase()) ? null : "crypto";
58
+ }
59
+ function classifyAster(symbol, subTypes) {
60
+ if (AMBIGUOUS.has(symbol.toUpperCase()))
61
+ return null;
62
+ if (subTypes.includes("pre-launch"))
63
+ return "preIpo";
64
+ if (subTypes.includes("ETF"))
65
+ return "etf";
66
+ if (subTypes.includes("STOCK"))
67
+ return "equity";
68
+ if (subTypes.includes("Commodities"))
69
+ return "commodity";
70
+ return "crypto";
71
+ }
72
+ function warnExcluded(venue, what, symbols) {
73
+ if (symbols.length > 0) {
74
+ console.warn(`[${venue}] excluded ${symbols.length} ${what}: ${symbols.join(", ")}`);
75
+ }
76
+ }
77
+ // ── HTTP ──
78
+ async function getJson(venue, url, label, init) {
79
+ const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15_000) });
80
+ await assertOk(res, `${venue} ${label}`);
81
+ const text = await res.text();
82
+ try {
83
+ return JSON.parse(text);
84
+ }
85
+ catch {
86
+ throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: invalid JSON`, { body: text.slice(0, 500) });
87
+ }
88
+ }
89
+ function arrayField(venue, label, v) {
90
+ if (!Array.isArray(v))
91
+ throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: expected an array`);
92
+ return v;
93
+ }
94
+ function numStr(v) {
95
+ if (typeof v === "number" && Number.isFinite(v))
96
+ return String(v);
97
+ if (typeof v === "string" && v.length > 0 && Number.isFinite(Number(v)))
98
+ return v;
99
+ return null;
100
+ }
101
+ /** A funding rate field: a JSON number without float noise (venueRateString), a numeric string verbatim; else null. */
102
+ function rateStr(v) {
103
+ if (typeof v === "number" && Number.isFinite(v))
104
+ return venueRateString(v);
105
+ return numStr(v);
106
+ }
107
+ function positiveNumStr(v) {
108
+ const s = numStr(v);
109
+ return s !== null && Number(s) > 0 ? s : null;
110
+ }
111
+ function stepFromDecimals(d) {
112
+ return d === 0 ? "1" : `0.${"0".repeat(d - 1)}1`;
113
+ }
114
+ // ── Pacifica ──
115
+ async function pacificaRows(net) {
116
+ const base = legacyPublicBaseUrl("pacifica", net);
117
+ const [infoJson, pricesJson] = await Promise.all([
118
+ getJson("pacifica", `${base}/info`, "info"),
119
+ getJson("pacifica", `${base}/info/prices`, "info/prices"),
120
+ ]);
121
+ const info = arrayField("pacifica", "info", infoJson.data);
122
+ const prices = arrayField("pacifica", "info/prices", pricesJson.data);
123
+ const priceBySymbol = new Map(prices.map(p => [String(p.symbol), p]));
124
+ const basis = fundingBasisOf("pacifica");
125
+ const settlement = settlementIntervalOf("pacifica");
126
+ const out = [];
127
+ const unclassified = [];
128
+ const unpriced = [];
129
+ for (const m of info) {
130
+ // /info lists spot markets too (e.g. SOL-USDC). A row without the field cannot be classified.
131
+ if (typeof m.instrument_type !== "string") {
132
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica /info row ${String(m.symbol)} has no instrument_type`, { exchange: "pacifica" });
133
+ }
134
+ if (m.instrument_type === "spot")
135
+ continue;
136
+ const symbol = String(m.symbol ?? "");
137
+ const assetClass = classifyPacifica(symbol);
138
+ if (assetClass === null) {
139
+ unclassified.push(symbol);
140
+ continue;
141
+ }
142
+ const p = priceBySymbol.get(symbol);
143
+ const mark = p ? positiveNumStr(p.mark) : null;
144
+ if (!p || mark === null) {
145
+ unpriced.push(symbol);
146
+ continue;
147
+ }
148
+ const maxLeverage = Number(m.max_leverage);
149
+ out.push({
150
+ market: {
151
+ venue: "pacifica",
152
+ venueSymbol: symbol,
153
+ symbol,
154
+ canonical: canonicalFor("pacifica", symbol, assetClass),
155
+ assetClass,
156
+ settle: "USDC",
157
+ quoteAsset: null,
158
+ markPrice: mark,
159
+ indexPrice: positiveNumStr(p.oracle),
160
+ fundingRate: rateStr(p.next_funding),
161
+ fundingRateBasis: basis,
162
+ settlementIntervalHours: settlement,
163
+ openInterest: numStr(p.open_interest),
164
+ volume24h: numStr(p.volume_24h),
165
+ tickSize: positiveNumStr(m.tick_size),
166
+ stepSize: positiveNumStr(m.lot_size),
167
+ minSize: null,
168
+ minNotional: positiveNumStr(m.min_order_size),
169
+ maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
170
+ marginTiers: null,
171
+ session: null,
172
+ tradable: true,
173
+ },
174
+ nextFundingTime: null, // Pacifica publishes no next-funding timestamp
175
+ });
176
+ }
177
+ warnExcluded("pacifica", "unclassified/ambiguous market(s)", unclassified);
178
+ warnExcluded("pacifica", "market(s) without a mark price", unpriced);
179
+ return out;
180
+ }
181
+ // ── Hyperliquid core ──
182
+ async function hyperliquidRows(net) {
183
+ const base = legacyPublicBaseUrl("hyperliquid", net);
184
+ const json = await getJson("hyperliquid", `${base}/info`, "info metaAndAssetCtxs", {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ body: JSON.stringify({ type: "metaAndAssetCtxs" }),
188
+ });
189
+ if (!Array.isArray(json) || json.length < 2) {
190
+ throw new PerpError("EXCHANGE_ERROR", "hyperliquid metaAndAssetCtxs: expected [meta, assetCtxs]");
191
+ }
192
+ const universe = arrayField("hyperliquid", "meta.universe", json[0].universe);
193
+ const ctxs = arrayField("hyperliquid", "assetCtxs", json[1]);
194
+ if (ctxs.length !== universe.length) {
195
+ throw new PerpError("EXCHANGE_ERROR", `hyperliquid metaAndAssetCtxs: ${universe.length} assets vs ${ctxs.length} contexts`);
196
+ }
197
+ const basis = fundingBasisOf("hyperliquid");
198
+ const settlement = settlementIntervalOf("hyperliquid");
199
+ const out = [];
200
+ const unclassified = [];
201
+ const unpriced = [];
202
+ universe.forEach((a, i) => {
203
+ const ctx = ctxs[i];
204
+ const symbol = String(a.name ?? "");
205
+ const assetClass = classifyHyperliquid(symbol);
206
+ if (assetClass === null) {
207
+ unclassified.push(symbol);
208
+ return;
209
+ }
210
+ const mark = positiveNumStr(ctx.markPx);
211
+ if (mark === null) {
212
+ unpriced.push(symbol);
213
+ return;
214
+ }
215
+ const szDecimals = Number(a.szDecimals);
216
+ const maxLeverage = Number(a.maxLeverage);
217
+ out.push({
218
+ market: {
219
+ venue: "hyperliquid",
220
+ venueSymbol: symbol,
221
+ symbol,
222
+ canonical: canonicalFor("hyperliquid", symbol, assetClass),
223
+ assetClass,
224
+ settle: "USDC",
225
+ quoteAsset: null,
226
+ markPrice: mark,
227
+ indexPrice: positiveNumStr(ctx.oraclePx),
228
+ fundingRate: rateStr(ctx.funding),
229
+ fundingRateBasis: basis,
230
+ settlementIntervalHours: settlement,
231
+ openInterest: numStr(ctx.openInterest),
232
+ volume24h: numStr(ctx.dayNtlVlm),
233
+ tickSize: null, // HL prices use a significant-figure rule, not a fixed tick
234
+ stepSize: Number.isInteger(szDecimals) && szDecimals >= 0 ? stepFromDecimals(szDecimals) : null,
235
+ minSize: null,
236
+ minNotional: HL_MIN_ORDER_VALUE_USD,
237
+ maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
238
+ marginTiers: null,
239
+ session: null,
240
+ tradable: a.isDelisted !== true,
241
+ },
242
+ nextFundingTime: null, // not published in metaAndAssetCtxs
243
+ });
244
+ });
245
+ warnExcluded("hyperliquid", "unclassified/ambiguous market(s)", unclassified);
246
+ warnExcluded("hyperliquid", "market(s) without a mark price", unpriced);
247
+ return out;
248
+ }
249
+ // ── Aster ──
250
+ function asterFilter(filters, type, field) {
251
+ if (!Array.isArray(filters))
252
+ return null;
253
+ const f = filters.find(x => x.filterType === type);
254
+ return f ? positiveNumStr(f[field]) : null;
255
+ }
256
+ async function asterRows(net) {
257
+ const base = legacyPublicBaseUrl("aster", net);
258
+ const [infoJson, premiumJson, tickerJson, fundingInfoJson] = await Promise.all([
259
+ getJson("aster", `${base}/fapi/v1/exchangeInfo`, "exchangeInfo"),
260
+ getJson("aster", `${base}/fapi/v1/premiumIndex`, "premiumIndex"),
261
+ getJson("aster", `${base}/fapi/v1/ticker/24hr`, "ticker/24hr"),
262
+ getJson("aster", `${base}/fapi/v3/fundingInfo`, "fundingInfo"),
263
+ ]);
264
+ const symbols = arrayField("aster", "exchangeInfo.symbols", infoJson.symbols);
265
+ const premiumBySymbol = new Map(arrayField("aster", "premiumIndex", premiumJson).map(p => [String(p.symbol), p]));
266
+ const tickerBySymbol = new Map(arrayField("aster", "ticker/24hr", tickerJson).map(t => [String(t.symbol), t]));
267
+ const intervalBySymbol = new Map();
268
+ for (const f of arrayField("aster", "fundingInfo", fundingInfoJson)) {
269
+ const h = Number(f.fundingIntervalHours);
270
+ if (Number.isFinite(h) && h > 0)
271
+ intervalBySymbol.set(String(f.symbol), h);
272
+ }
273
+ const out = [];
274
+ const unclassified = [];
275
+ const unpriced = [];
276
+ const noInterval = [];
277
+ for (const s of symbols) {
278
+ if (s.contractType !== "PERPETUAL" || s.quoteAsset !== "USDT")
279
+ continue;
280
+ const venueSymbol = String(s.symbol);
281
+ const symbol = String(s.baseAsset);
282
+ const subTypes = Array.isArray(s.underlyingSubType) ? s.underlyingSubType.map(String) : [];
283
+ const assetClass = classifyAster(symbol, subTypes);
284
+ if (assetClass === null) {
285
+ unclassified.push(venueSymbol);
286
+ continue;
287
+ }
288
+ const p = premiumBySymbol.get(venueSymbol);
289
+ const mark = p ? positiveNumStr(p.markPrice) : null;
290
+ if (!p || mark === null) {
291
+ unpriced.push(venueSymbol);
292
+ continue;
293
+ }
294
+ const hours = intervalBySymbol.get(venueSymbol);
295
+ if (hours === undefined) {
296
+ noInterval.push(venueSymbol);
297
+ continue;
298
+ }
299
+ if (typeof s.marginAsset !== "string" || s.marginAsset.length === 0) {
300
+ throw new PerpError("EXCHANGE_ERROR", `aster exchangeInfo: ${venueSymbol} has no marginAsset`);
301
+ }
302
+ const next = Number(p.nextFundingTime);
303
+ const t = tickerBySymbol.get(venueSymbol);
304
+ out.push({
305
+ market: {
306
+ venue: "aster",
307
+ venueSymbol,
308
+ symbol,
309
+ canonical: canonicalFor("aster", symbol, assetClass),
310
+ assetClass,
311
+ settle: s.marginAsset,
312
+ quoteAsset: s.quoteAsset,
313
+ markPrice: mark,
314
+ indexPrice: positiveNumStr(p.indexPrice),
315
+ fundingRate: rateStr(p.lastFundingRate),
316
+ fundingRateBasis: { kind: "perHours", hours },
317
+ settlementIntervalHours: hours,
318
+ openInterest: null, // per-symbol endpoint only
319
+ volume24h: t ? numStr(t.quoteVolume) : null,
320
+ tickSize: asterFilter(s.filters, "PRICE_FILTER", "tickSize"),
321
+ stepSize: asterFilter(s.filters, "LOT_SIZE", "stepSize"),
322
+ minSize: asterFilter(s.filters, "LOT_SIZE", "minQty"),
323
+ minNotional: asterFilter(s.filters, "MIN_NOTIONAL", "notional"),
324
+ maxLeverage: null, // leverage brackets require a signed call
325
+ marginTiers: null,
326
+ session: null,
327
+ tradable: s.status === "TRADING",
328
+ },
329
+ nextFundingTime: Number.isFinite(next) && next > 0 ? next : null,
330
+ });
331
+ }
332
+ warnExcluded("aster", "unclassified/ambiguous market(s)", unclassified);
333
+ warnExcluded("aster", "market(s) without a mark price", unpriced);
334
+ warnExcluded("aster", "market(s) missing from fundingInfo", noInterval);
335
+ return out;
336
+ }
337
+ // ── VenuePublicData ──
338
+ function rowsFor(venue, net) {
339
+ switch (venue) {
340
+ case "pacifica": return pacificaRows(net);
341
+ case "hyperliquid": return hyperliquidRows(net);
342
+ case "aster": return asterRows(net);
343
+ }
344
+ }
345
+ export function legacyPublicData(venue) {
346
+ return {
347
+ async fetchMarkets(net) {
348
+ return (await rowsFor(venue, net)).map(r => r.market);
349
+ },
350
+ async fetchFundingRates(net) {
351
+ const out = [];
352
+ for (const { market: m, nextFundingTime } of await rowsFor(venue, net)) {
353
+ if (!m.tradable || m.fundingRate === null)
354
+ continue;
355
+ out.push({
356
+ venue: m.venue,
357
+ venueSymbol: m.venueSymbol,
358
+ symbol: m.symbol,
359
+ canonical: m.canonical,
360
+ rate: m.fundingRate,
361
+ basis: m.fundingRateBasis,
362
+ markPrice: m.markPrice,
363
+ nextFundingTime,
364
+ });
365
+ }
366
+ return out;
367
+ },
368
+ };
369
+ }