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
@@ -18,6 +18,9 @@
18
18
  * Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
19
19
  * Base: https://fapi.asterdex.com
20
20
  */
21
+ import { nonEmptyOrListed } from "./unknown-symbol.js";
22
+ import { asterCliSymbol, isAsterListedPerpetual } from "../ws/aster-market.js";
23
+ import { decimalsOf } from "./venue-types.js";
21
24
  import { accountCacheEpoch } from "../cache.js";
22
25
  import { isExpired } from "../agent-wallet/expiry.js";
23
26
  import { classifyError, DEFINITE_ORDER_FAILURE_CODES, isTransportFailure, orderNotSent, orderOutcomeUnknown, PerpError, sendFailure } from "../errors.js";
@@ -30,7 +33,7 @@ import { approvalFromMax, bpsToRate, builderGateOf, NO_BUILDER, rateToBps, requi
30
33
  import { ASTER_CLIENT_ID_RE, ASTER_FUNDS_CHAINS, asterVenue } from "./venues/aster.js";
31
34
  import { assertAsterWithdrawAmount, assertAsterWithdrawSignature, parseAsterWithdrawLimits, requireAsterFundsNetwork, } from "./aster-funds.js";
32
35
  import { nextAsterNonce } from "./aster-nonce.js";
33
- /** Common TIF (§13.3) → Aster perp timeInForce. Live exchangeInfo lists GTC/IOC/GTX/HIDDEN — no FOK. */
36
+ /** Common TIF → Aster perp timeInForce. Live exchangeInfo lists GTC/IOC/GTX/HIDDEN — no FOK. */
34
37
  function asterTif(raw) {
35
38
  const t = raw.toUpperCase();
36
39
  if (t === "GTC" || t === "IOC" || t === "GTX")
@@ -72,6 +75,16 @@ export function asterIocOutcome(raw) {
72
75
  const filledSize = plainQty(r.executedQty);
73
76
  return filledSize === null ? null : { filledSize, final: true };
74
77
  }
78
+ /** The numeric `code` of an Aster error body ({"code":-1121,"msg":"Invalid symbol."}); null when absent. */
79
+ function asterErrorCode(body) {
80
+ try {
81
+ const j = JSON.parse(body);
82
+ return typeof j.code === "number" ? j.code : null;
83
+ }
84
+ catch {
85
+ return null; // not JSON (e.g. an HTML error page): no venue code
86
+ }
87
+ }
75
88
  /**
76
89
  * Order-placement refusals by documented venue code (asterdex/api-docs V3 futures "Error
77
90
  * Codes"; "Codes are universal,but messages can vary"). [PerpError code, docs name, remediation].
@@ -163,6 +176,11 @@ export const ASTER_INCOME_MAX_PAGES = 60;
163
176
  const ASTER_KLINE_PAGE = 1500;
164
177
  /** Page cap for one range (150 000 bars). */
165
178
  const ASTER_KLINE_MAX_PAGES = 100;
179
+ /**
180
+ * Kline intervals verified live 2026-10-19 (/fapi/v3/klines BTCUSDT: each answers bars of that
181
+ * length; "7m" / "2d" → -1120 "Invalid interval."). Anything else is refused before a request.
182
+ */
183
+ export const ASTER_KLINE_INTERVALS = new Set(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"]);
166
184
  export class AsterAdapter {
167
185
  name = "aster";
168
186
  chain = "bnb";
@@ -186,6 +204,8 @@ export class AsterAdapter {
186
204
  /** API symbol → price filters; null = listed but PRICE_FILTER / PERCENT_PRICE unreadable. */
187
205
  _priceFilters = new Map();
188
206
  _priceFiltersLoadedAt = 0;
207
+ /** The in-flight exchangeInfo load shared by concurrent first uses (_ensureDirectory). */
208
+ _directoryLoad = null;
189
209
  _accountCache = null;
190
210
  _positionsCache = null;
191
211
  _ordersCache = null;
@@ -283,7 +303,7 @@ export class AsterAdapter {
283
303
  return null;
284
304
  }
285
305
  }
286
- // ── Spot host access (design §12, used by AsterSpotAdapter) ──
306
+ // ── Spot host access (used by AsterSpotAdapter) ──
287
307
  /**
288
308
  * Aster spot REST host. Spot shares the perp EIP-712 Domain B signing
289
309
  * (chainId 1666 mainnet / 714 testnet) and the per-agent nonce space.
@@ -316,12 +336,23 @@ export class AsterAdapter {
316
336
  return this._agentMeta;
317
337
  }
318
338
  // ── Symbol helpers ──
319
- /** CLI symbol → Aster API symbol (ETH → ETHUSDT) */
339
+ /**
340
+ * CLI symbol → Aster API symbol through the LOADED market directory (exchangeInfo): the exact
341
+ * venue symbol first (BTCUSDT, BTCUSD1, BTCU — a non-USDT market's CLI symbol is its venue
342
+ * symbol, asterCliSymbol), then the USDT market of a base (ETH → ETHUSDT, USD1 → USD1USDT),
343
+ * else SYMBOL_NOT_FOUND — never a guessed suffix. Callers load the directory first (_apiSymbol).
344
+ */
320
345
  _toApi(symbol) {
321
346
  const s = symbol.toUpperCase().replace(/-PERP$/, "");
322
- if (s.endsWith("USDT") || s.endsWith("BUSD"))
347
+ // A bare base names its USDT market first (X → XUSDT), even if a non-USDT venue symbol is
348
+ // spelled X too; an exact venue symbol otherwise (BTCUSD1, BTCU, XUSDT).
349
+ if (this._priceFilters.has(`${s}USDT`))
350
+ return `${s}USDT`;
351
+ if (this._priceFilters.has(s))
323
352
  return s;
324
- return `${s}USDT`;
353
+ throw new PerpError("SYMBOL_NOT_FOUND", `Aster has no market ${s} (neither the venue symbol ${s} nor ${s}USDT is listed)`, {
354
+ exchange: "aster", remediation: "List markets: perp -e aster market list",
355
+ });
325
356
  }
326
357
  /** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
327
358
  _fromApi(symbol) {
@@ -397,10 +428,9 @@ export class AsterAdapter {
397
428
  }
398
429
  const premiumMap = new Map(premiums.map(p => [String(p.symbol), p]));
399
430
  this._ingestPriceFilters(info.symbols);
400
- // USDT-quoted perps only: the CLI symbol mapping (ETH ↔ ETHUSDT) cannot address the USD1 / U
401
- // quoted markets (live 2026-10-13: 22 USD1 + 2 U), so listing them would offer untradeable rows.
402
- const tradingSymbols = info.symbols
403
- .filter((s) => String(s.contractType) === "PERPETUAL" && String(s.status) === "TRADING" && String(s.quoteAsset) === "USDT");
431
+ // Every listed perpetual, any quote (the WS directory's set): USDT-quoted ones by base (ETH),
432
+ // USD1 / U quoted ones by venue symbol (BTCUSD1, BTCU — asterCliSymbol), with quoteAsset.
433
+ const tradingSymbols = info.symbols.filter((s) => isAsterListedPerpetual(s));
404
434
  const result = tradingSymbols.flatMap((s) => {
405
435
  const sym = String(s.symbol);
406
436
  const ticker = tickerMap.get(sym);
@@ -412,17 +442,24 @@ export class AsterAdapter {
412
442
  const maxLev = null;
413
443
  const lotFilter = s.filters
414
444
  ?.find(f => f.filterType === "LOT_SIZE");
445
+ const cliSymbol = asterCliSymbol({ symbol: sym, baseAsset: String(s.baseAsset), quoteAsset: s.quoteAsset });
415
446
  const fundingHours = this._fundingHoursCache.get(this._fromApi(sym));
416
447
  return [{
417
- symbol: this._fromApi(sym),
448
+ symbol: cliSymbol,
449
+ venueSymbol: sym,
450
+ quoteAsset: typeof s.quoteAsset === "string" ? s.quoteAsset : null,
418
451
  markPrice: String(premium.markPrice),
419
452
  indexPrice: premium?.indexPrice != null ? String(premium.indexPrice) : null,
420
453
  fundingRate: premium?.lastFundingRate != null ? String(premium.lastFundingRate) : null,
454
+ // premiumIndex lastFundingRate moves during the period (same value as WS markPrice@arr `r`: accruing).
455
+ fundingRateKind: premium?.lastFundingRate != null ? "accruing" : null,
421
456
  volume24h: ticker?.quoteVolume != null ? String(ticker.quoteVolume) : null,
422
457
  // /fapi/v3/exchangeInfo and ticker/24hr carry no open interest.
423
458
  openInterest: null,
424
459
  maxLeverage: maxLev,
425
- sizeDecimals: s.quantityPrecision != null ? Number(s.quantityPrecision) : null,
460
+ // The LOT_SIZE step is the authoritative grid (quantityPrecision can disagree with it: CFX
461
+ // step "1", precision 1); its decimals, the public directory's rule (venueMarketRow).
462
+ sizeDecimals: lotFilter?.stepSize != null ? decimalsOf(String(lotFilter.stepSize)) : s.quantityPrecision != null ? Number(s.quantityPrecision) : null,
426
463
  stepSize: lotFilter?.stepSize != null ? String(lotFilter.stepSize) : null,
427
464
  fundingHours: fundingHours ?? null,
428
465
  marginTiers: null,
@@ -457,16 +494,38 @@ export class AsterAdapter {
457
494
  this._priceFiltersLoadedAt = Date.now();
458
495
  }
459
496
  /** PRICE_FILTER / PERCENT_PRICE of an API symbol (exchangeInfo, cached CACHE_TTL). */
460
- async _symbolPriceFilters(apiSymbol) {
461
- if (!this._priceFiltersLoadedAt || Date.now() - this._priceFiltersLoadedAt >= AsterAdapter.CACHE_TTL) {
462
- const info = await this._publicGet("/fapi/v3/exchangeInfo");
463
- if (!Array.isArray(info?.symbols)) {
464
- throw new PerpError("EXCHANGE_ERROR", "Aster /fapi/v3/exchangeInfo returned no symbols", { exchange: "aster" });
465
- }
466
- this._ingestPriceFilters(info.symbols);
497
+ /** exchangeInfo symbols (the market directory) loaded / refreshed per CACHE_TTL. */
498
+ /**
499
+ * exchangeInfo symbols (the market directory), refreshed per CACHE_TTL. Concurrent first uses
500
+ * share ONE in-flight request; a failed load is not cached (the next call retries).
501
+ */
502
+ async _ensureDirectory() {
503
+ if (this._priceFiltersLoadedAt && Date.now() - this._priceFiltersLoadedAt < AsterAdapter.CACHE_TTL)
504
+ return;
505
+ if (this._directoryLoad === null) {
506
+ this._directoryLoad = (async () => {
507
+ const info = await this._publicGet("/fapi/v3/exchangeInfo");
508
+ if (!Array.isArray(info?.symbols)) {
509
+ throw new PerpError("EXCHANGE_ERROR", "Aster /fapi/v3/exchangeInfo returned no symbols", { exchange: "aster" });
510
+ }
511
+ this._ingestPriceFilters(info.symbols);
512
+ })().finally(() => { this._directoryLoad = null; });
467
513
  }
514
+ return this._directoryLoad;
515
+ }
516
+ /**
517
+ * CLI symbol → Aster API symbol, resolved through the market directory (_toApi), loaded on first
518
+ * use and cached CACHE_TTL — so the answer never depends on what was read before (a cold
519
+ * adapter resolves BTCU, USD1 → USD1USDT, an unknown symbol is SYMBOL_NOT_FOUND).
520
+ */
521
+ async _apiSymbol(symbol) {
522
+ await this._ensureDirectory();
523
+ return this._toApi(symbol);
524
+ }
525
+ async _symbolPriceFilters(apiSymbol) {
526
+ await this._ensureDirectory();
468
527
  if (!this._priceFilters.has(apiSymbol)) {
469
- throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster" });
528
+ throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
470
529
  }
471
530
  const f = this._priceFilters.get(apiSymbol);
472
531
  if (!f) {
@@ -475,12 +534,12 @@ export class AsterAdapter {
475
534
  return f;
476
535
  }
477
536
  /**
478
- * Declared protection-price reference (§13.10 #5): the venue mark price from
537
+ * Declared protection-price reference: the venue mark price from
479
538
  * GET /fapi/v3/premiumIndex — the same price PERCENT_PRICE is evaluated against.
480
539
  * No runtime substitute (no mid / last fallback): unavailable → PRICE_STALE.
481
540
  */
482
541
  async referencePrice(symbol) {
483
- const apiSymbol = this._toApi(symbol);
542
+ const apiSymbol = await this._apiSymbol(symbol);
484
543
  let mark;
485
544
  try {
486
545
  const res = await this._publicGet("/fapi/v3/premiumIndex", { symbol: apiSymbol });
@@ -525,7 +584,7 @@ export class AsterAdapter {
525
584
  * against PRICE_FILTER / PERCENT_PRICE. appliedBps = the bound after tick alignment.
526
585
  */
527
586
  async computeProtectionPrice(symbol, side, slippage) {
528
- const apiSymbol = this._toApi(symbol);
587
+ const apiSymbol = await this._apiSymbol(symbol);
529
588
  const f = await this._symbolPriceFilters(apiSymbol);
530
589
  const ref = await this.referencePrice(symbol);
531
590
  const protectionPrice = alignProtectionPrice(slippagePrice(ref, side, slippage.bps), f.tickSize, side);
@@ -538,7 +597,7 @@ export class AsterAdapter {
538
597
  return asterIocOutcome(raw);
539
598
  }
540
599
  async getOrderbook(symbol) {
541
- const res = await this._publicGet("/fapi/v3/depth", { symbol: this._toApi(symbol), limit: "50" });
600
+ const res = await this._publicGet("/fapi/v3/depth", { symbol: await this._apiSymbol(symbol), limit: "50" });
542
601
  return {
543
602
  bids: res?.bids ?? [],
544
603
  asks: res?.asks ?? [],
@@ -546,27 +605,33 @@ export class AsterAdapter {
546
605
  }
547
606
  async getRecentTrades(symbol, limit = 20) {
548
607
  const trades = await this._publicGet("/fapi/v3/trades", {
549
- symbol: this._toApi(symbol),
608
+ symbol: await this._apiSymbol(symbol),
550
609
  limit: String(limit),
551
610
  });
552
- return (trades ?? []).map((t) => ({
553
- time: Number(t.time ?? 0),
554
- symbol: this._fromApi(String(t.symbol ?? symbol)),
555
- side: t.isBuyerMaker ? "sell" : "buy",
556
- price: String(t.price ?? "0"),
557
- size: String(t.qty ?? "0"),
558
- fee: "0",
559
- }));
611
+ if (!Array.isArray(trades))
612
+ throw new PerpError("EXCHANGE_ERROR", `Aster trades is not a list (${JSON.stringify(trades).slice(0, 120)})`, { exchange: "aster" });
613
+ // The tape rows carry no symbol: the normalized venue symbol (was the raw argument, "BTC-PERP").
614
+ const sym = this._fromApi(await this._apiSymbol(symbol));
615
+ return trades.map((t) => {
616
+ if (typeof t.time !== "number" || typeof t.price !== "string" || typeof t.qty !== "string" || typeof t.isBuyerMaker !== "boolean") {
617
+ throw new PerpError("EXCHANGE_ERROR", `Aster trade row without time / price / qty / isBuyerMaker: ${JSON.stringify(t).slice(0, 160)}`, { exchange: "aster" });
618
+ }
619
+ return { time: t.time, symbol: sym, side: t.isBuyerMaker ? "sell" : "buy", price: t.price, size: t.qty, fee: null }; // the public tape carries no fee
620
+ });
560
621
  }
561
622
  async getFundingHistory(symbol, limit = 10) {
562
623
  const data = await this._publicGet("/fapi/v3/fundingRate", {
563
- symbol: this._toApi(symbol),
624
+ symbol: await this._apiSymbol(symbol),
564
625
  limit: String(limit),
565
626
  });
566
627
  if (!Array.isArray(data)) {
567
628
  throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate history is not a list (${JSON.stringify(data).slice(0, 120)})`, { exchange: "aster" });
568
629
  }
569
- return data.map((d) => {
630
+ // Per-market interval (/fapi/v3/fundingInfo); null when the venue lists none for the symbol.
631
+ const hours = await this.getFundingHours(symbol);
632
+ const intervalHours = hours === undefined ? null : hours;
633
+ // `200 []` for an unlisted symbol (live 2026-10-14): checked against the market directory.
634
+ return nonEmptyOrListed(this, symbol, data.map((d) => {
570
635
  const time = Number(d.fundingTime);
571
636
  if (d.fundingTime === undefined || d.fundingTime === null || !Number.isFinite(time) || time <= 0) {
572
637
  throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate row without a numeric fundingTime: ${JSON.stringify(d).slice(0, 160)}`, { exchange: "aster" });
@@ -575,8 +640,10 @@ export class AsterAdapter {
575
640
  time,
576
641
  rate: venueRateField(d.fundingRate, "fundingRate fundingRate", "aster"),
577
642
  price: d.markPrice ? String(d.markPrice) : null,
643
+ intervalHours,
644
+ rateKind: "lastSettled",
578
645
  };
579
- });
646
+ }), this._fromApi(await this._apiSymbol(symbol)));
580
647
  }
581
648
  /**
582
649
  * Candles over [startTime, endTime]. GET /fapi/v3/klines returns at most 1500 bars per request
@@ -584,6 +651,11 @@ export class AsterAdapter {
584
651
  * range is never cut to the first page. More than ASTER_KLINE_MAX_PAGES pages → INVALID_PARAMS.
585
652
  */
586
653
  async getKlines(symbol, interval, startTime, endTime) {
654
+ if (!ASTER_KLINE_INTERVALS.has(interval)) {
655
+ throw new PerpError("INVALID_PARAMS", `Aster: unsupported candle interval "${interval}"`, {
656
+ remediation: `Use one of: ${[...ASTER_KLINE_INTERVALS].join(", ")}`,
657
+ });
658
+ }
587
659
  if (!(startTime <= endTime)) {
588
660
  throw new PerpError("INVALID_PARAMS", `Aster klines: start ${startTime} is after end ${endTime}`, {
589
661
  remediation: "Pass a --start that is before --end",
@@ -598,7 +670,7 @@ export class AsterAdapter {
598
670
  });
599
671
  }
600
672
  const data = await this._publicGet("/fapi/v3/klines", {
601
- symbol: this._toApi(symbol), interval,
673
+ symbol: await this._apiSymbol(symbol), interval,
602
674
  startTime: String(from), endTime: String(endTime), limit: String(ASTER_KLINE_PAGE),
603
675
  });
604
676
  if (!Array.isArray(data)) {
@@ -607,10 +679,11 @@ export class AsterAdapter {
607
679
  for (const k of data) {
608
680
  const time = Number(k[0]);
609
681
  const trades = Number(k[8]);
610
- if (!Number.isFinite(time) || !Number.isFinite(trades)) {
611
- throw new PerpError("EXCHANGE_ERROR", `Aster kline row without open time / trade count: ${JSON.stringify(k).slice(0, 120)}`, { exchange: "aster" });
682
+ const numeric = (v) => (typeof v === "string" || typeof v === "number") && String(v).trim() !== "" && Number.isFinite(Number(v));
683
+ if (!Number.isFinite(time) || !Number.isFinite(trades) || !numeric(k[5]) || !numeric(k[7])) {
684
+ throw new PerpError("EXCHANGE_ERROR", `Aster kline row without open time / volume / quote volume / trade count: ${JSON.stringify(k).slice(0, 120)}`, { exchange: "aster" });
612
685
  }
613
- out.push({ time, open: String(k[1]), high: String(k[2]), low: String(k[3]), close: String(k[4]), volume: String(k[5]), trades });
686
+ out.push({ time, open: String(k[1]), high: String(k[2]), low: String(k[3]), close: String(k[4]), volume: String(k[5]), quoteVolume: String(k[7]), trades });
614
687
  }
615
688
  if (data.length < ASTER_KLINE_PAGE)
616
689
  break;
@@ -975,12 +1048,12 @@ export class AsterAdapter {
975
1048
  }
976
1049
  // ── Trading ──
977
1050
  async marketOrder(symbol, side, size, opts) {
978
- // Design §13.2 / §13.10 #6: never native MARKET (no price cap) — an IOC LIMIT at the
1051
+ // Never native MARKET (no price cap) — an IOC LIMIT at the
979
1052
  // protection price (mark ± opts.slippage.bps, tick-aligned toward crossing, inside
980
1053
  // PRICE_FILTER / PERCENT_PRICE). newOrderRespType=RESULT makes the response carry the
981
1054
  // final IOC status (FILLED / EXPIRED) and executedQty.
982
1055
  const r = this._resolveSigner();
983
- const apiSymbol = this._toApi(symbol);
1056
+ const apiSymbol = await this._apiSymbol(symbol);
984
1057
  const clientOrderId = opts.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
985
1058
  let protectionPrice;
986
1059
  let protection;
@@ -1068,11 +1141,11 @@ export class AsterAdapter {
1068
1141
  }
1069
1142
  async limitOrder(symbol, side, price, size, opts) {
1070
1143
  const r = this._resolveSigner();
1071
- const apiSymbol = this._toApi(symbol);
1144
+ const apiSymbol = await this._apiSymbol(symbol);
1072
1145
  const clientOrderId = opts?.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
1073
1146
  let params;
1074
1147
  try {
1075
- // Common TIF names (§13.3): GTC / IOC / ALO→GTX (GTX accepted as alias); FOK unsupported.
1148
+ // Common TIF names: GTC / IOC / ALO→GTX (GTX accepted as alias); FOK unsupported.
1076
1149
  const tif = asterTif(opts?.tif ?? "GTC");
1077
1150
  // Price must already be on PRICE_FILTER.tickSize — validated, never rounded (INVALID_TICK).
1078
1151
  const px = requireOnTick(price, (await this._symbolPriceFilters(apiSymbol)).tickSize);
@@ -1104,14 +1177,14 @@ export class AsterAdapter {
1104
1177
  /**
1105
1178
  * Native modify (PUT /fapi/v3/order, V3 futures docs "Modify Order"): LIMIT orders
1106
1179
  * only; TIF (incl. GTX) and reduceOnly stay on the order. `size` is the NEW UNFILLED
1107
- * quantity (§13.10 #10); the venue's `quantity` is the order total, so
1180
+ * quantity; the venue's `quantity` is the order total, so
1108
1181
  * quantity = size + executedQty (read just before). A fill landing between the read
1109
1182
  * and the PUT can only shrink the remainder, never over-size it. A venue rejection
1110
1183
  * leaves the original order untouched (docs).
1111
1184
  */
1112
1185
  async editOrder(symbol, orderId, price, size) {
1113
1186
  const r = this._resolveSigner();
1114
- const apiSymbol = this._toApi(symbol);
1187
+ const apiSymbol = await this._apiSymbol(symbol);
1115
1188
  const rawOrders = await this._signedGetEip712("/fapi/v3/openOrders", {}, r);
1116
1189
  const existing = (rawOrders ?? []).find(o => String(o.orderId ?? "") === orderId);
1117
1190
  if (!existing) {
@@ -1158,7 +1231,7 @@ export class AsterAdapter {
1158
1231
  const label = ref.kind === "orderId" ? ref.value : `c:${ref.value}`;
1159
1232
  let row;
1160
1233
  try {
1161
- row = await this._signedGetEip712("/fapi/v3/order", { symbol: this._toApi(symbol), ...key }, this._resolveSigner());
1234
+ row = await this._signedGetEip712("/fapi/v3/order", { symbol: await this._apiSymbol(symbol), ...key }, this._resolveSigner());
1162
1235
  }
1163
1236
  catch (e) {
1164
1237
  if (e instanceof PerpError && e.structured.details?.venueCode === -2013) {
@@ -1177,7 +1250,7 @@ export class AsterAdapter {
1177
1250
  const r = this._resolveSigner();
1178
1251
  try {
1179
1252
  return await this._signedDeleteEip712("/fapi/v3/order", {
1180
- symbol: this._toApi(symbol),
1253
+ symbol: await this._apiSymbol(symbol),
1181
1254
  ...key,
1182
1255
  }, r);
1183
1256
  }
@@ -1198,6 +1271,7 @@ export class AsterAdapter {
1198
1271
  const r = this._resolveSigner();
1199
1272
  if (!symbol) {
1200
1273
  const orders = await this.getOpenOrders();
1274
+ await this._ensureDirectory();
1201
1275
  const apiSymbols = new Set(orders.map(o => this._toApi(o.symbol)));
1202
1276
  const results = [];
1203
1277
  for (const sym of apiSymbols) {
@@ -1205,7 +1279,7 @@ export class AsterAdapter {
1205
1279
  }
1206
1280
  return results;
1207
1281
  }
1208
- return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: this._toApi(symbol) }, r);
1282
+ return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: await this._apiSymbol(symbol) }, r);
1209
1283
  }
1210
1284
  // ── Risk ──
1211
1285
  async setLeverage(symbol, leverage, marginMode) {
@@ -1213,7 +1287,7 @@ export class AsterAdapter {
1213
1287
  if (marginMode) {
1214
1288
  try {
1215
1289
  await this._signedPostEip712("/fapi/v3/marginType", {
1216
- symbol: this._toApi(symbol),
1290
+ symbol: await this._apiSymbol(symbol),
1217
1291
  marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
1218
1292
  }, r);
1219
1293
  }
@@ -1252,7 +1326,7 @@ export class AsterAdapter {
1252
1326
  }
1253
1327
  }
1254
1328
  return this._signedPostEip712("/fapi/v3/leverage", {
1255
- symbol: this._toApi(symbol),
1329
+ symbol: await this._apiSymbol(symbol),
1256
1330
  leverage: String(leverage),
1257
1331
  }, r);
1258
1332
  }
@@ -1271,7 +1345,7 @@ export class AsterAdapter {
1271
1345
  */
1272
1346
  async stopOrder(symbol, side, size, triggerPrice, opts) {
1273
1347
  const r = this._resolveSigner();
1274
- const apiSymbol = this._toApi(symbol);
1348
+ const apiSymbol = await this._apiSymbol(symbol);
1275
1349
  const what = `stop ${side} ${size} ${symbol} @ ${triggerPrice}`;
1276
1350
  let params;
1277
1351
  try {
@@ -1757,6 +1831,11 @@ export class AsterAdapter {
1757
1831
  if (!res.ok) {
1758
1832
  const text = await res.text();
1759
1833
  const clean = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120);
1834
+ // -1121 BAD_SYMBOL ("Invalid symbol.", live on /fapi/v3/depth for an unlisted symbol): the
1835
+ // symbol does not exist — SYMBOL_NOT_FOUND, not an UNKNOWN 500.
1836
+ if (asterErrorCode(text) === -1121) {
1837
+ throw new PerpError("SYMBOL_NOT_FOUND", `Aster: ${clean} [${method} ${path}]`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
1838
+ }
1760
1839
  throw new Error(`${method} ${path} failed (${res.status}): ${clean}`);
1761
1840
  }
1762
1841
  return res.json();
@@ -1768,10 +1847,31 @@ export class AsterAdapter {
1768
1847
  const url = `${this._baseUrl}${path}${qs ? `?${qs}` : ""}`;
1769
1848
  for (let attempt = 0; attempt < 3; attempt++) {
1770
1849
  const res = await fetch(url);
1771
- const result = await this._handleResponse(res, "GET", path, attempt);
1850
+ let result;
1851
+ try {
1852
+ result = await this._handleResponse(res, "GET", path, attempt);
1853
+ }
1854
+ catch (e) {
1855
+ if (e instanceof PerpError)
1856
+ throw e;
1857
+ // A public read never surfaces as UNKNOWN: rate limit / server-side / other HTTP failure.
1858
+ const msg = e instanceof Error ? e.message : String(e);
1859
+ throw new PerpError(res.status === 429 ? "RATE_LIMITED" : res.status >= 500 ? "EXCHANGE_UNREACHABLE" : "EXCHANGE_ERROR", `Aster ${msg}`, {
1860
+ exchange: "aster", status: res.status, remediation: res.status === 429 || res.status >= 500 ? "Retry later" : "Check the request (symbol / parameters)",
1861
+ });
1862
+ }
1863
+ // A parameter rejection arrives as HTTP 200 with an error body (live 2026-10-19,
1864
+ // fundingRate limit=1001: {"status":"ERROR","type":"VALID","code":"000002","errorData":{"limit":"…"}}).
1865
+ if (result !== null && typeof result === "object" && !Array.isArray(result) && result.status === "ERROR") {
1866
+ const r = result;
1867
+ const detail = JSON.stringify(r.errorData ?? r).slice(0, 200);
1868
+ throw new PerpError(r.type === "VALID" ? "INVALID_PARAMS" : "EXCHANGE_ERROR", `Aster ${path} rejected the request: ${detail}`, {
1869
+ exchange: "aster", venueCode: r.code, remediation: "Check the request parameters (e.g. --limit 1..1000)",
1870
+ });
1871
+ }
1772
1872
  if (result !== null)
1773
1873
  return result;
1774
1874
  }
1775
- throw new Error(`GET ${path} failed: max retries exceeded`);
1875
+ throw new PerpError("RATE_LIMITED", `Aster GET ${path}: rate limited after 3 attempts`, { exchange: "aster", remediation: "Retry later" });
1776
1876
  }
1777
1877
  }
@@ -21,7 +21,7 @@
21
21
  * `builder: { b: <address>, f: <tenths of a bp> }`; max 0.1% perps / 1% spot; status
22
22
  * info `{ type: "maxBuilderFee", user, builder }`. HIP-3 dexes are not covered by the
23
23
  * docs → never attached there.
24
- * - pacifica https://pacifica.gitbook.io/docs/programs/builder-program.md
24
+ * - pacifica https://docs.pacifica.fi/programs/builder-program.md
25
25
  * approve_builder_code { builder_code, max_fee_rate } signed by the account; per-order
26
26
  * `builder_code` on create_market / create / stop/create / positions/tpsl. TWAP
27
27
  * (create_twap_order) does not document builder_code → never sent there.
@@ -21,7 +21,7 @@
21
21
  * `builder: { b: <address>, f: <tenths of a bp> }`; max 0.1% perps / 1% spot; status
22
22
  * info `{ type: "maxBuilderFee", user, builder }`. HIP-3 dexes are not covered by the
23
23
  * docs → never attached there.
24
- * - pacifica https://pacifica.gitbook.io/docs/programs/builder-program.md
24
+ * - pacifica https://docs.pacifica.fi/programs/builder-program.md
25
25
  * approve_builder_code { builder_code, max_fee_rate } signed by the account; per-order
26
26
  * `builder_code` on create_market / create / stop/create / positions/tpsl. TWAP
27
27
  * (create_twap_order) does not document builder_code → never sent there.
@@ -49,6 +49,7 @@
49
49
  */
50
50
  import { PerpError, classifyError, isDefiniteOrderFailure, isTransportFailure, isUnknownOutcomeServerError } from "../errors.js";
51
51
  import { isVenueId } from "./venue-types.js";
52
+ import { VARIATIONAL_READ_ONLY_REMEDIATION } from "./venues/variational.js";
52
53
  export const BUILDER_FEE_CAPABILITIES = {
53
54
  hyperliquid: {
54
55
  approval: "master", identity: "address", perpCapBps: 10, spotCapBps: 100, hip3: false, testnet: true,
@@ -56,7 +57,7 @@ export const BUILDER_FEE_CAPABILITIES = {
56
57
  },
57
58
  pacifica: {
58
59
  approval: "master", identity: "code", perpCapBps: 10, spotCapBps: null, hip3: null, testnet: true,
59
- feeStepBps: 0.01, feeKind: "max", docs: "https://pacifica.gitbook.io/docs/programs/builder-program.md",
60
+ feeStepBps: 0.01, feeKind: "max", docs: "https://docs.pacifica.fi/programs/builder-program.md",
60
61
  },
61
62
  aster: {
62
63
  approval: "master", identity: "address", perpCapBps: 10, spotCapBps: null, hip3: null, testnet: false,
@@ -73,11 +74,21 @@ export const BUILDER_FEE_CAPABILITIES = {
73
74
  lighter: null,
74
75
  "lighter-rh": null,
75
76
  arcus: null,
77
+ risex: null,
78
+ txflow: null,
79
+ variational: null,
80
+ grvt: null,
81
+ extended: null,
76
82
  };
77
83
  /** Why a venue has no builder-fee support (shown in CAPABILITY_UNSUPPORTED). */
78
84
  export const BUILDER_FEE_UNSUPPORTED = {
79
85
  lighter: "not yet implemented in perp-cli — the official lighter-go signer can sign ApproveIntegrator and the per-order integrator fields, but perp-cli sends them as 0 (https://apidocs.lighter.xyz/docs/partner-integration)",
80
86
  "lighter-rh": "not yet implemented in perp-cli — Lighter RH uses the same official lighter-go signer; partner-attribution fields are sent as 0 (https://apidocs.rh.lighter.xyz/docs/partner-attribution.md)",
87
+ risex: "not implemented yet in perp-cli (RISEx builder codes are planned; note the venue's \"bps\" field is parts-per-million)",
88
+ txflow: "TxFlow is integrated through an unofficial read-only API (market data only)",
89
+ variational: VARIATIONAL_READ_ONLY_REMEDIATION,
90
+ grvt: "GRVT is read-only (market data) in this version; trading is planned for a later release",
91
+ extended: "Extended is available for market data only in this version (trading is planned for a later release)",
81
92
  arcus: "Arcus has no perps builder fee; its integrator fee applies to the spot router only and needs a partner key from Arcus (https://docs.arcus.xyz/concepts/spot-router)",
82
93
  };
83
94
  export function builderFeeUnsupported(venue) {
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Cross-venue canonical symbols — the single mapping source used to decide
3
- * that two markets on different venues are the same underlying
4
- * (design §4 L4, §10.9).
3
+ * that two markets on different venues are the same underlying.
5
4
  *
6
5
  * Rules by asset class:
7
6
  * - crypto: ticker identity after the adapter strips its quote suffix, plus
@@ -30,3 +29,10 @@ export declare function isNeverMatch(a: string, b: string): boolean;
30
29
  * @param symbol plain symbol as reported by the adapter (no quote suffix, no dex prefix)
31
30
  */
32
31
  export declare function canonicalFor(venueKey: VenueId | `hl:${string}`, symbol: string, assetClass: AssetClass): string | null;
32
+ /**
33
+ * Canonical key for a symbol a USER typed (no venue / asset class known — `arb scan --compare`):
34
+ * a "-PERP" suffix is dropped, a denomination alias maps to its canonical ticker (kPEPE →
35
+ * 1000PEPE, UBTC → BTC), and a venue ticker of the explicit table maps to its canonical key when
36
+ * every venue that lists it agrees (XAU → GOLD, CL → CRUDE_OIL_WTI). Anything else: upper-cased.
37
+ */
38
+ export declare function canonicalQuerySymbol(symbol: string): string;
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Cross-venue canonical symbols — the single mapping source used to decide
3
- * that two markets on different venues are the same underlying
4
- * (design §4 L4, §10.9).
3
+ * that two markets on different venues are the same underlying.
5
4
  *
6
5
  * Rules by asset class:
7
6
  * - crypto: ticker identity after the adapter strips its quote suffix, plus
@@ -47,19 +46,44 @@ export const EXPLICIT_CANONICAL = {
47
46
  "hl:xyz|SILVER": "SILVER",
48
47
  "qfex|GOLD": "GOLD",
49
48
  "qfex|SILVER": "SILVER",
49
+ "risex|XAU": "GOLD",
50
+ "risex|XAG": "SILVER",
51
+ "txflow|XAU": "GOLD",
52
+ "txflow|XAG": "SILVER",
53
+ "variational|XAU": "GOLD",
54
+ "variational|XAG": "SILVER",
55
+ "grvt|XAU": "GOLD",
56
+ "grvt|XAG": "SILVER",
57
+ "extended|XAU": "GOLD",
58
+ "extended|XAG": "SILVER",
50
59
  // Crude oil (WTI)
51
60
  "ondo|WTI": "CRUDE_OIL_WTI",
52
61
  "hl:xyz|CL": "CRUDE_OIL_WTI",
53
62
  "hl:flx|OIL": "CRUDE_OIL_WTI",
54
63
  // QFEX CL blends expiring futures; roll-week gaps are caught by the cross-venue price filter.
55
64
  "qfex|CL": "CRUDE_OIL_WTI",
65
+ "risex|CL": "CRUDE_OIL_WTI",
66
+ // Brent crude
67
+ "risex|BZ": "CRUDE_OIL_BRENT",
68
+ "txflow|BZ": "CRUDE_OIL_BRENT",
69
+ "txflow|CL": "CRUDE_OIL_WTI",
70
+ "variational|BZ": "CRUDE_OIL_BRENT",
71
+ "variational|CL": "CRUDE_OIL_WTI",
72
+ "grvt|BZ": "CRUDE_OIL_BRENT",
73
+ "grvt|CL": "CRUDE_OIL_WTI",
74
+ "extended|WTI": "CRUDE_OIL_WTI",
75
+ "extended|XBR": "CRUDE_OIL_BRENT",
56
76
  // US equity indices
57
77
  "ondo|US500": "SPX",
58
78
  "ondo|US100": "NDX",
59
79
  "qfex|US500": "SPX",
60
80
  "qfex|US100": "NDX",
81
+ // Extended SPX500m / TECH100m: S&P 500 / Nasdaq-100 index perps (uiName SPX-USD / NDX-USD).
82
+ "extended|SPX500M": "SPX",
83
+ "extended|TECH100M": "NDX",
61
84
  // FX
62
85
  "ondo|USDJPY": "USDJPY",
86
+ "extended|USDJPY": "USDJPY",
63
87
  };
64
88
  /** Pairs verified to be DIFFERENT products despite similar names. Never match. */
65
89
  export const NEVER_MATCH = new Set([
@@ -92,5 +116,25 @@ export function canonicalFor(venueKey, symbol, assetClass) {
92
116
  case "fx":
93
117
  case "preIpo":
94
118
  return EXPLICIT_CANONICAL[`${venueKey}|${up}`] ?? null;
119
+ case "unclassified":
120
+ return null; // ambiguous ticker: listed, never matched across venues
95
121
  }
96
122
  }
123
+ /**
124
+ * Canonical key for a symbol a USER typed (no venue / asset class known — `arb scan --compare`):
125
+ * a "-PERP" suffix is dropped, a denomination alias maps to its canonical ticker (kPEPE →
126
+ * 1000PEPE, UBTC → BTC), and a venue ticker of the explicit table maps to its canonical key when
127
+ * every venue that lists it agrees (XAU → GOLD, CL → CRUDE_OIL_WTI). Anything else: upper-cased.
128
+ */
129
+ export function canonicalQuerySymbol(symbol) {
130
+ const up = symbol.trim().toUpperCase().replace(/-PERP$/, "");
131
+ const alias = DENOMINATION_ALIASES[up];
132
+ if (alias !== undefined)
133
+ return alias;
134
+ const targets = new Set();
135
+ for (const [key, canonical] of Object.entries(EXPLICIT_CANONICAL)) {
136
+ if (key.slice(key.indexOf("|") + 1) === up)
137
+ targets.add(canonical);
138
+ }
139
+ return targets.size === 1 ? [...targets][0] : up;
140
+ }
@@ -29,6 +29,8 @@ export interface TriggerOrderCapable {
29
29
  export interface DexCapable {
30
30
  readonly dex: string;
31
31
  setDex(dex: string): void;
32
+ /** An independent adapter for `dex` with this account configuration and its own market state (never mutates this one). */
33
+ forDex(dex: string): ExchangeAdapter & DexCapable;
32
34
  listDeployedDexes(): Promise<{
33
35
  name: string;
34
36
  deployer: string;
@@ -18,7 +18,8 @@ export function isTriggerOrderCapable(adapter) {
18
18
  }
19
19
  export function isDexCapable(adapter) {
20
20
  return typeof adapter.setDex === "function"
21
- && typeof adapter.listDeployedDexes === "function";
21
+ && typeof adapter.listDeployedDexes === "function"
22
+ && typeof adapter.forDex === "function";
22
23
  }
23
24
  export function isSubAccountCapable(adapter) {
24
25
  return typeof adapter.createSubAccount === "function";