perp-cli 0.14.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +138 -405
  4. package/README.zh.md +243 -0
  5. package/dist/agent-wallet/store.d.ts +3 -2
  6. package/dist/agent-wallet/store.js +2 -2
  7. package/dist/api/public/hyperliquid.js +4 -0
  8. package/dist/api/public/lighter.d.ts +5 -0
  9. package/dist/api/public/lighter.js +5 -0
  10. package/dist/api/public/pacifica.js +85 -15
  11. package/dist/api/public/urls.d.ts +2 -0
  12. package/dist/api/public/urls.js +3 -0
  13. package/dist/arb/sizing.js +4 -2
  14. package/dist/arb/state.d.ts +6 -0
  15. package/dist/arb/utils.d.ts +1 -2
  16. package/dist/arb/utils.js +1 -2
  17. package/dist/balance-by-address.d.ts +39 -0
  18. package/dist/balance-by-address.js +44 -0
  19. package/dist/bot/apex/pulse.js +3 -0
  20. package/dist/bot/apex/radar.js +2 -1
  21. package/dist/bot/engine.d.ts +10 -1
  22. package/dist/bot/engine.js +14 -6
  23. package/dist/bot/indicators.d.ts +7 -0
  24. package/dist/bot/indicators.js +16 -0
  25. package/dist/bot/perp-order.d.ts +4 -4
  26. package/dist/bot/perp-order.js +5 -5
  27. package/dist/bot/quarantine.d.ts +1 -1
  28. package/dist/bot/quarantine.js +1 -1
  29. package/dist/bot/quote-rules.js +1 -1
  30. package/dist/bot/strategies/apex-strategy.js +3 -2
  31. package/dist/bot/strategies/engine-mm.js +6 -5
  32. package/dist/bot/strategies/funding-arb-strategy.js +12 -5
  33. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
  34. package/dist/bot/strategies/funding-arb-utils.js +53 -20
  35. package/dist/bot/strategies/funding-auto.js +2 -2
  36. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  37. package/dist/bot/strategies/liquidation-mm.js +8 -2
  38. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  39. package/dist/bot/strategies/momentum-breakout.js +11 -3
  40. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  41. package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
  42. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  43. package/dist/bot/strategies/spot-perp-executor.js +3 -3
  44. package/dist/bot/strategy-types.d.ts +2 -1
  45. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  46. package/dist/bot/tui/BotDashboard.js +1 -1
  47. package/dist/cache.js +12 -6
  48. package/dist/cli-program.d.ts +15 -0
  49. package/dist/cli-program.js +126 -6
  50. package/dist/commands/account.js +168 -24
  51. package/dist/commands/agent-import.d.ts +7 -0
  52. package/dist/commands/agent-import.js +30 -1
  53. package/dist/commands/agent.d.ts +6 -3
  54. package/dist/commands/agent.js +160 -12
  55. package/dist/commands/arb/index.js +23 -81
  56. package/dist/commands/arb-auto.d.ts +24 -0
  57. package/dist/commands/arb-auto.js +175 -97
  58. package/dist/commands/arb.d.ts +20 -0
  59. package/dist/commands/arb.js +107 -163
  60. package/dist/commands/backtest.js +23 -21
  61. package/dist/commands/bot.js +1 -1
  62. package/dist/commands/builder.js +1 -1
  63. package/dist/commands/funds.js +113 -3
  64. package/dist/commands/init.d.ts +1 -1
  65. package/dist/commands/init.js +27 -7
  66. package/dist/commands/manage.js +23 -21
  67. package/dist/commands/market.d.ts +48 -0
  68. package/dist/commands/market.js +395 -100
  69. package/dist/commands/options.d.ts +14 -0
  70. package/dist/commands/options.js +151 -0
  71. package/dist/commands/pacifica-earn.d.ts +5 -0
  72. package/dist/commands/pacifica-earn.js +455 -0
  73. package/dist/commands/pacifica-orders.d.ts +14 -0
  74. package/dist/commands/pacifica-orders.js +142 -0
  75. package/dist/commands/portfolio.d.ts +16 -0
  76. package/dist/commands/portfolio.js +83 -55
  77. package/dist/commands/rebalance.d.ts +1 -0
  78. package/dist/commands/rebalance.js +15 -0
  79. package/dist/commands/risk.d.ts +11 -2
  80. package/dist/commands/risk.js +39 -44
  81. package/dist/commands/trade.d.ts +1 -0
  82. package/dist/commands/trade.js +67 -62
  83. package/dist/commands/wallet.js +1 -1
  84. package/dist/config.d.ts +2 -2
  85. package/dist/config.js +15 -4
  86. package/dist/constants.js +6 -0
  87. package/dist/cross-chain-margin.d.ts +3 -1
  88. package/dist/cross-chain-margin.js +4 -2
  89. package/dist/dashboard/server.js +3 -3
  90. package/dist/dashboard/ui.js +9 -6
  91. package/dist/errors.d.ts +29 -1
  92. package/dist/errors.js +35 -3
  93. package/dist/exchanges/address.d.ts +8 -0
  94. package/dist/exchanges/address.js +47 -0
  95. package/dist/exchanges/arcus/adapter.d.ts +16 -12
  96. package/dist/exchanges/arcus/adapter.js +45 -18
  97. package/dist/exchanges/arcus/agent.d.ts +1 -1
  98. package/dist/exchanges/arcus/agent.js +1 -1
  99. package/dist/exchanges/arcus/client.js +1 -1
  100. package/dist/exchanges/arcus/public.js +3 -2
  101. package/dist/exchanges/arcus/signing.d.ts +2 -3
  102. package/dist/exchanges/arcus/signing.js +1 -1
  103. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  104. package/dist/exchanges/arcus/spot-chain.js +1 -1
  105. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  106. package/dist/exchanges/arcus/spot-router.js +2 -2
  107. package/dist/exchanges/arcus/spot.d.ts +2 -3
  108. package/dist/exchanges/arcus/spot.js +2 -3
  109. package/dist/exchanges/aster-spot.d.ts +3 -3
  110. package/dist/exchanges/aster-spot.js +6 -9
  111. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  112. package/dist/exchanges/aster-typed-data.js +1 -1
  113. package/dist/exchanges/aster.d.ts +30 -10
  114. package/dist/exchanges/aster.js +153 -53
  115. package/dist/exchanges/builder-fee.d.ts +1 -1
  116. package/dist/exchanges/builder-fee.js +13 -2
  117. package/dist/exchanges/canonical-symbols.d.ts +8 -2
  118. package/dist/exchanges/canonical-symbols.js +46 -2
  119. package/dist/exchanges/capabilities.d.ts +2 -0
  120. package/dist/exchanges/capabilities.js +2 -1
  121. package/dist/exchanges/derive/client.d.ts +37 -0
  122. package/dist/exchanges/derive/client.js +135 -0
  123. package/dist/exchanges/derive/options.d.ts +73 -0
  124. package/dist/exchanges/derive/options.js +379 -0
  125. package/dist/exchanges/extended/adapter.d.ts +96 -0
  126. package/dist/exchanges/extended/adapter.js +241 -0
  127. package/dist/exchanges/extended/client.d.ts +38 -0
  128. package/dist/exchanges/extended/client.js +88 -0
  129. package/dist/exchanges/extended/public.d.ts +133 -0
  130. package/dist/exchanges/extended/public.js +436 -0
  131. package/dist/exchanges/grvt/adapter.d.ts +100 -0
  132. package/dist/exchanges/grvt/adapter.js +283 -0
  133. package/dist/exchanges/grvt/client.d.ts +25 -0
  134. package/dist/exchanges/grvt/client.js +86 -0
  135. package/dist/exchanges/grvt/public.d.ts +151 -0
  136. package/dist/exchanges/grvt/public.js +438 -0
  137. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  138. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  139. package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
  140. package/dist/exchanges/hyperliquid-spot.js +24 -16
  141. package/dist/exchanges/hyperliquid.d.ts +199 -22
  142. package/dist/exchanges/hyperliquid.js +550 -202
  143. package/dist/exchanges/index.d.ts +1 -1
  144. package/dist/exchanges/index.js +1 -1
  145. package/dist/exchanges/interface.d.ts +157 -13
  146. package/dist/exchanges/interface.js +47 -0
  147. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  148. package/dist/exchanges/ioc-outcome.js +1 -1
  149. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  150. package/dist/exchanges/legacy-trading-v2.js +7 -2
  151. package/dist/exchanges/lighter-public.d.ts +67 -12
  152. package/dist/exchanges/lighter-public.js +250 -82
  153. package/dist/exchanges/lighter-signer.d.ts +1 -1
  154. package/dist/exchanges/lighter-signer.js +1 -1
  155. package/dist/exchanges/lighter-spot.d.ts +10 -5
  156. package/dist/exchanges/lighter-spot.js +105 -97
  157. package/dist/exchanges/lighter.d.ts +80 -21
  158. package/dist/exchanges/lighter.js +312 -105
  159. package/dist/exchanges/ondo/adapter.d.ts +38 -13
  160. package/dist/exchanges/ondo/adapter.js +86 -27
  161. package/dist/exchanges/ondo/agent.d.ts +1 -1
  162. package/dist/exchanges/ondo/agent.js +1 -1
  163. package/dist/exchanges/ondo/client.d.ts +1 -1
  164. package/dist/exchanges/ondo/client.js +2 -2
  165. package/dist/exchanges/ondo/public.d.ts +35 -3
  166. package/dist/exchanges/ondo/public.js +75 -32
  167. package/dist/exchanges/ondo/spot.d.ts +2 -2
  168. package/dist/exchanges/ondo/spot.js +3 -3
  169. package/dist/exchanges/options-interface.d.ts +158 -0
  170. package/dist/exchanges/options-interface.js +17 -0
  171. package/dist/exchanges/options-venues.d.ts +12 -0
  172. package/dist/exchanges/options-venues.js +35 -0
  173. package/dist/exchanges/outcome-interface.d.ts +4 -0
  174. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  175. package/dist/exchanges/pacifica-atomic.js +440 -0
  176. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  177. package/dist/exchanges/pacifica-decimal.js +51 -0
  178. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  179. package/dist/exchanges/pacifica-presigned.js +141 -0
  180. package/dist/exchanges/pacifica-print.d.ts +200 -0
  181. package/dist/exchanges/pacifica-print.js +417 -0
  182. package/dist/exchanges/pacifica-spot.d.ts +8 -2
  183. package/dist/exchanges/pacifica-spot.js +26 -18
  184. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  185. package/dist/exchanges/pacifica-typed-data.js +1 -1
  186. package/dist/exchanges/pacifica-vaults.d.ts +130 -0
  187. package/dist/exchanges/pacifica-vaults.js +277 -0
  188. package/dist/exchanges/pacifica.d.ts +108 -10
  189. package/dist/exchanges/pacifica.js +428 -171
  190. package/dist/exchanges/precision.d.ts +3 -1
  191. package/dist/exchanges/precision.js +9 -2
  192. package/dist/exchanges/public-balances.d.ts +25 -0
  193. package/dist/exchanges/public-balances.js +158 -0
  194. package/dist/exchanges/public-positions.d.ts +43 -0
  195. package/dist/exchanges/public-positions.js +154 -0
  196. package/dist/exchanges/qfex/adapter.d.ts +69 -13
  197. package/dist/exchanges/qfex/adapter.js +102 -17
  198. package/dist/exchanges/qfex/agent.d.ts +1 -1
  199. package/dist/exchanges/qfex/agent.js +1 -1
  200. package/dist/exchanges/qfex/client.d.ts +1 -1
  201. package/dist/exchanges/qfex/client.js +1 -1
  202. package/dist/exchanges/qfex/errors.d.ts +1 -1
  203. package/dist/exchanges/qfex/errors.js +2 -2
  204. package/dist/exchanges/qfex/public.d.ts +65 -12
  205. package/dist/exchanges/qfex/public.js +131 -25
  206. package/dist/exchanges/qfex/signing.d.ts +2 -2
  207. package/dist/exchanges/qfex/signing.js +4 -5
  208. package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
  209. package/dist/exchanges/qfex/ws-transport.js +3 -3
  210. package/dist/exchanges/rate-limiter.d.ts +1 -1
  211. package/dist/exchanges/rate-limiter.js +5 -2
  212. package/dist/exchanges/registry.d.ts +1 -1
  213. package/dist/exchanges/registry.js +1 -1
  214. package/dist/exchanges/risex/adapter.d.ts +160 -0
  215. package/dist/exchanges/risex/adapter.js +842 -0
  216. package/dist/exchanges/risex/agent.d.ts +154 -0
  217. package/dist/exchanges/risex/agent.js +384 -0
  218. package/dist/exchanges/risex/client.d.ts +47 -0
  219. package/dist/exchanges/risex/client.js +174 -0
  220. package/dist/exchanges/risex/nonce.d.ts +43 -0
  221. package/dist/exchanges/risex/nonce.js +212 -0
  222. package/dist/exchanges/risex/public.d.ts +64 -0
  223. package/dist/exchanges/risex/public.js +150 -0
  224. package/dist/exchanges/risex/session.d.ts +71 -0
  225. package/dist/exchanges/risex/session.js +171 -0
  226. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  227. package/dist/exchanges/risex/typed-data.js +240 -0
  228. package/dist/exchanges/risex/units.d.ts +29 -0
  229. package/dist/exchanges/risex/units.js +104 -0
  230. package/dist/exchanges/slippage.d.ts +1 -1
  231. package/dist/exchanges/slippage.js +1 -1
  232. package/dist/exchanges/spot-factory.d.ts +3 -3
  233. package/dist/exchanges/spot-factory.js +8 -8
  234. package/dist/exchanges/spot-interface.d.ts +5 -5
  235. package/dist/exchanges/spot-interface.js +2 -2
  236. package/dist/exchanges/spot-result.d.ts +3 -3
  237. package/dist/exchanges/spot-result.js +3 -3
  238. package/dist/exchanges/trading-v2.d.ts +3 -3
  239. package/dist/exchanges/trading-v2.js +9 -6
  240. package/dist/exchanges/txflow/adapter.d.ts +61 -0
  241. package/dist/exchanges/txflow/adapter.js +150 -0
  242. package/dist/exchanges/txflow/client.d.ts +47 -0
  243. package/dist/exchanges/txflow/client.js +155 -0
  244. package/dist/exchanges/txflow/public.d.ts +87 -0
  245. package/dist/exchanges/txflow/public.js +355 -0
  246. package/dist/exchanges/unknown-symbol.d.ts +34 -0
  247. package/dist/exchanges/unknown-symbol.js +56 -0
  248. package/dist/exchanges/variational/adapter.d.ts +67 -0
  249. package/dist/exchanges/variational/adapter.js +137 -0
  250. package/dist/exchanges/variational/client.d.ts +21 -0
  251. package/dist/exchanges/variational/client.js +66 -0
  252. package/dist/exchanges/variational/public.d.ts +126 -0
  253. package/dist/exchanges/variational/public.js +430 -0
  254. package/dist/exchanges/venue-types.d.ts +52 -10
  255. package/dist/exchanges/venue-types.js +82 -11
  256. package/dist/exchanges/venue.d.ts +150 -11
  257. package/dist/exchanges/venue.js +126 -4
  258. package/dist/exchanges/venues/arcus.d.ts +1 -2
  259. package/dist/exchanges/venues/arcus.js +5 -4
  260. package/dist/exchanges/venues/aster.d.ts +2 -2
  261. package/dist/exchanges/venues/aster.js +6 -4
  262. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  263. package/dist/exchanges/venues/create-helpers.js +3 -3
  264. package/dist/exchanges/venues/extended.d.ts +21 -0
  265. package/dist/exchanges/venues/extended.js +96 -0
  266. package/dist/exchanges/venues/grvt.d.ts +30 -0
  267. package/dist/exchanges/venues/grvt.js +98 -0
  268. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  269. package/dist/exchanges/venues/hyperliquid.js +19 -8
  270. package/dist/exchanges/venues/index.d.ts +7 -2
  271. package/dist/exchanges/venues/index.js +8 -2
  272. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  273. package/dist/exchanges/venues/lighter-family.js +9 -11
  274. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  275. package/dist/exchanges/venues/lighter-rh.js +20 -8
  276. package/dist/exchanges/venues/lighter.d.ts +1 -1
  277. package/dist/exchanges/venues/lighter.js +18 -6
  278. package/dist/exchanges/venues/ondo.d.ts +1 -1
  279. package/dist/exchanges/venues/ondo.js +5 -3
  280. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  281. package/dist/exchanges/venues/pacifica.js +15 -4
  282. package/dist/exchanges/venues/qfex.d.ts +1 -1
  283. package/dist/exchanges/venues/qfex.js +4 -2
  284. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  285. package/dist/exchanges/venues/risex-release.js +14 -0
  286. package/dist/exchanges/venues/risex.d.ts +31 -0
  287. package/dist/exchanges/venues/risex.js +131 -0
  288. package/dist/exchanges/venues/txflow.d.ts +18 -0
  289. package/dist/exchanges/venues/txflow.js +77 -0
  290. package/dist/exchanges/venues/variational.d.ts +21 -0
  291. package/dist/exchanges/venues/variational.js +85 -0
  292. package/dist/execution-log.d.ts +1 -1
  293. package/dist/funding/legacy-public.d.ts +6 -4
  294. package/dist/funding/legacy-public.js +42 -41
  295. package/dist/funding/normalize.d.ts +1 -1
  296. package/dist/funding/normalize.js +1 -1
  297. package/dist/funding/rates.d.ts +95 -6
  298. package/dist/funding/rates.js +99 -19
  299. package/dist/index.js +7 -6
  300. package/dist/jobs.d.ts +1 -0
  301. package/dist/jobs.js +25 -1
  302. package/dist/landing.d.ts +2 -0
  303. package/dist/landing.js +1 -1
  304. package/dist/liquidity.d.ts +3 -1
  305. package/dist/liquidity.js +8 -4
  306. package/dist/mcp-pacifica-earn.d.ts +9 -0
  307. package/dist/mcp-pacifica-earn.js +64 -0
  308. package/dist/mcp-server.js +211 -68
  309. package/dist/pacifica/client.d.ts +69 -5
  310. package/dist/pacifica/client.js +181 -8
  311. package/dist/pacifica/rate-limit.d.ts +144 -0
  312. package/dist/pacifica/rate-limit.js +267 -0
  313. package/dist/pacifica/types/order.d.ts +2 -1
  314. package/dist/plan-executor.js +7 -2
  315. package/dist/positions-by-address.d.ts +34 -0
  316. package/dist/positions-by-address.js +46 -0
  317. package/dist/rebalance.js +1 -1
  318. package/dist/risk.d.ts +2 -1
  319. package/dist/risk.js +2 -4
  320. package/dist/settings.d.ts +32 -3
  321. package/dist/signer/ows-evm.d.ts +1 -1
  322. package/dist/signer/ows-evm.js +1 -1
  323. package/dist/smart-order.d.ts +1 -1
  324. package/dist/smart-order.js +1 -1
  325. package/dist/strategies/funding-arb.d.ts +1 -1
  326. package/dist/strategies/funding-arb.js +36 -63
  327. package/dist/trade-validator.js +2 -2
  328. package/dist/utils.d.ts +22 -3
  329. package/dist/utils.js +11 -3
  330. package/dist/venue-health.d.ts +40 -0
  331. package/dist/venue-health.js +75 -0
  332. package/dist/withdraw-gate.d.ts +1 -1
  333. package/dist/withdraw-gate.js +1 -1
  334. package/dist/ws/arcus-market.d.ts +9 -0
  335. package/dist/ws/arcus-market.js +9 -0
  336. package/dist/ws/aster-market.d.ts +33 -0
  337. package/dist/ws/aster-market.js +30 -0
  338. package/dist/ws/index.d.ts +980 -0
  339. package/dist/ws/index.js +4460 -0
  340. package/dist/ws/lighter-market.d.ts +14 -0
  341. package/dist/ws/lighter-market.js +13 -0
  342. package/dist/ws/node.d.ts +996 -0
  343. package/dist/ws/node.js +4554 -0
  344. package/dist/ws/pacifica-instrument.d.ts +8 -0
  345. package/dist/ws/pacifica-instrument.js +8 -0
  346. package/dist/ws/unclassified.d.ts +16 -0
  347. package/dist/ws/unclassified.js +43 -0
  348. package/dist/ws-cli/credentials.d.ts +62 -0
  349. package/dist/ws-cli/credentials.js +126 -0
  350. package/package.json +71 -10
  351. package/skills/perp-cli/SKILL.md +153 -36
  352. package/skills/perp-cli/package.json +15 -2
  353. package/skills/perp-cli/references/agent-operations.md +22 -12
  354. package/skills/perp-cli/references/commands.md +143 -17
  355. package/skills/perp-cli/references/strategies.md +38 -3
  356. package/skills/perp-cli/references/ws-library.md +523 -0
  357. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  358. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  359. package/skills/perp-cli/scripts/preflight.sh +27 -19
  360. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  361. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  362. package/dist/cli-spec.d.ts +0 -46
  363. package/dist/cli-spec.js +0 -81
@@ -1,5 +1,5 @@
1
1
  import { networkOf, supportsDryRun } from "../cli-flags.js";
2
- import { printJson, errorAndExit, withJsonErrors, jsonOk, jsonError, symbolMatch, formatUsd, formatOrderResult, orderResultJson } from "../utils.js";
2
+ import { printJson, errorAndExit, withJsonErrors, jsonOk, jsonError, symbolMatch, formatUsd, formatOrderResult, orderResultJson, normalizeSymbolArg } from "../utils.js";
3
3
  import { ERROR_CODES, extractErrorMessage, isDefiniteOrderFailure, parsePositiveDecimal, PerpError } from "../errors.js";
4
4
  import { logExecution } from "../execution-log.js";
5
5
  import { validateTrade, enforceOrderRisk } from "../trade-validator.js";
@@ -24,9 +24,10 @@ import { resolveAdapterSlippage, resolveSlippage } from "../exchanges/slippage.j
24
24
  import { exactUnits, floorSize, floorToStep, lotStepOf, unitsToString } from "../exchanges/precision.js";
25
25
  import { isSpotOpenOrdersCapable } from "../exchanges/spot-interface.js";
26
26
  import { builderPlanFor } from "../exchanges/builder-fee.js";
27
+ import { registerPacificaOrderCommands } from "./pacifica-orders.js";
27
28
  import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
28
29
  import { defaultSpotQuote, readSpotQuoteAvailableFresh, requireSpotAdapter, requireSpotDefinition, transferQuoteToPerp, transferQuoteToSpot, transferShortfallError } from "../exchanges/spot-factory.js";
29
- // ── Uniform order surface (design §13) ──
30
+ // ── Uniform order surface ──
30
31
  //
31
32
  // Every venue's trade market/buy/sell/limit/stop/edit/cancel goes through TradingV2:
32
33
  // native on ondo/arcus, the legacy wrapper (exchanges/legacy-trading-v2.ts) on
@@ -34,7 +35,7 @@ import { defaultSpotQuote, readSpotQuoteAvailableFresh, requireSpotAdapter, requ
34
35
  // shape (OrderResult + execution), one printer.
35
36
  /** Families with the legacy smart-order path (--smart: IOC at best book ± 1 tick). */
36
37
  const LEGACY_ORDER_FAMILIES = new Set(["pacifica", "hyperliquid", "lighter", "aster"]);
37
- /** Common default time-in-force for every venue (design §13.3; venues map GTC internally, e.g. GTT). */
38
+ /** Common default time-in-force for every venue (venues map GTC internally, e.g. GTT). */
38
39
  const DEFAULT_TIF = "GTC";
39
40
  function tradingVenueOf(adapter) {
40
41
  const def = getVenue(adapter.name);
@@ -43,7 +44,7 @@ function tradingVenueOf(adapter) {
43
44
  function isLegacyOrderVenue(def) {
44
45
  return LEGACY_ORDER_FAMILIES.has(def.family);
45
46
  }
46
- function parseSlippageBpsFlag(raw) {
47
+ export function parseSlippageBpsFlag(raw) {
47
48
  if (raw === undefined)
48
49
  return null;
49
50
  const n = Number(raw);
@@ -68,11 +69,13 @@ const V2_CLIENT_ID_RULES = {
68
69
  pacifica: { re: PACIFICA_UUID_RE, desc: "a full UUID, e.g. f47ac10b-58cc-4372-a567-0e02b2c3d479" },
69
70
  hyperliquid: { re: HL_CLOID_RE, desc: "a 128-bit hex cloid: 0x + 32 lowercase hex chars, e.g. 0x1234567890abcdef1234567890abcdef (or --auto-id)" },
70
71
  aster: { re: ASTER_CLIENT_ID_RE, desc: "1-36 chars of A-Z a-z 0-9 . : / _ -" },
72
+ // uint64 decimal (range checked by the adapter).
73
+ risex: { re: /^[1-9][0-9]{0,19}$/, desc: "a decimal integer from 1 to 2^64 - 1" },
71
74
  };
72
75
  /**
73
76
  * Venues whose client order ids perp-cli GENERATES and never takes from the user: QFEX ids are
74
77
  * not unique on the venue, so a reused id would attribute another order's fills / cancels to
75
- * this one (design §14.12 #4c). --auto-id still works (the generated id is reported).
78
+ * this one (c). --auto-id still works (the generated id is reported).
76
79
  */
77
80
  const GENERATED_CLIENT_ID_ONLY = new Set(["qfex"]);
78
81
  function validateV2ClientId(def, id) {
@@ -111,7 +114,7 @@ function v2ClientOrderId(def, caps, userId) {
111
114
  * --auto-id: venue-format id where the venue has client ids; a venue without them refuses it
112
115
  * (v2ClientOrderId — the locally generated id is never sent).
113
116
  * Ondo / Pacifica limit orders: undefined — the adapter generates its own id, which encodes
114
- * post-only (Ondo) / the TIF (Pacifica) for a later edit (design §13.10 #10, #11); the result
117
+ * post-only (Ondo) / the TIF (Pacifica) for a later edit; the result
115
118
  * reports that id. A Pacifica market order has no generated id, so --auto-id sends a UUID.
116
119
  * Generated-only venues (qfex): undefined — v2ClientOrderId generates the venue id itself.
117
120
  */
@@ -300,7 +303,7 @@ function checkTpSlDirection(positionSide, ref, refLabel, p) {
300
303
  }
301
304
  /** The venue's current mark price for `symbol` (PRICE_STALE when it is not published / not positive). */
302
305
  async function markPriceOf(adapter, symbol) {
303
- const m = (await adapter.getMarkets()).find(x => symbolMatch(x.symbol, symbol));
306
+ const m = (await adapter.getMarkets({ funding: false })).find(x => symbolMatch(x.symbol, symbol));
304
307
  const px = m === undefined ? NaN : Number(m.markPrice);
305
308
  if (!(px > 0) || !Number.isFinite(px)) {
306
309
  throw new PerpError("PRICE_STALE", `${adapter.name} ${symbol}: no mark price to check the TP/SL direction against`, { exchange: adapter.name });
@@ -314,7 +317,7 @@ function positionTpSlAdapter(adapter) {
314
317
  }
315
318
  return adapter;
316
319
  }
317
- /** Refuse when position-level TP/SL orders already exist (never overwrite, §13.10 #12). */
320
+ /** Refuse when position-level TP/SL orders already exist (never overwrite). */
318
321
  async function refuseExistingPositionTpSl(adapter, def, symbol) {
319
322
  const existing = await adapter.positionTpSlOrders(symbol);
320
323
  if (existing.length > 0) {
@@ -439,7 +442,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
439
442
  logExecution({
440
443
  type: action.includes("split") ? "split_order" : action.includes("limit") ? "limit_order" : action.includes("stop") ? "stop_order" : action.includes("cancel") ? "cancel" : "market_order",
441
444
  exchange: details.exchange ?? "unknown",
442
- symbol: (details.symbol ?? "").toUpperCase(),
445
+ symbol: normalizeSymbolArg(details.symbol ?? ""),
443
446
  side: details.side ?? "",
444
447
  size: String(details.size ?? details.totalUsd ?? "0"),
445
448
  price: details.price,
@@ -486,7 +489,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
486
489
  }
487
490
  /** Mark price of `sym` (split sizing / risk); no usable mark → PRICE_STALE, nothing sent. */
488
491
  async function splitMarkPrice(adapter, sym) {
489
- const market = (await adapter.getMarkets()).find(m => symbolMatch(m.symbol, sym));
492
+ const market = (await adapter.getMarkets({ funding: false })).find(m => symbolMatch(m.symbol, sym));
490
493
  const markPrice = market !== undefined ? Number(market.markPrice) : NaN;
491
494
  if (!(markPrice > 0)) {
492
495
  throw new PerpError("PRICE_STALE", `${adapter.name}: no usable mark price for ${sym} — cannot size the split; nothing was sent`, { exchange: adapter.name });
@@ -606,7 +609,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
606
609
  console.log();
607
610
  }
608
611
  /**
609
- * Validate an entry's --tp/--sl before anything is sent (design §13.10 #12): the venue has
612
+ * Validate an entry's --tp/--sl before anything is sent: the venue has
610
613
  * TP/SL, the entry opens (not reduce-only), separateOrders / positionLevel venues need an
611
614
  * immediate entry (market or IOC — a resting entry has no verified fill to size the legs to),
612
615
  * and the prices sit on the right sides of `ref`.
@@ -625,7 +628,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
625
628
  }
626
629
  /**
627
630
  * Place an entry carrying --tp/--sl — one meaning on every venue: protect THIS entry's
628
- * verified fill (design §13.5, §13.10 #12).
631
+ * verified fill.
629
632
  * - attached (ondo) / batchGroup (arcus): the venue attaches the legs to the entry.
630
633
  * - separateOrders (hl, lighter, aster): entry fill VERIFIED (placePerpVerified: venue-final
631
634
  * report cross-checked with the position delta), then reduce-only TP/SL triggers sized to it.
@@ -855,7 +858,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
855
858
  const s = side.toLowerCase();
856
859
  if (s !== "buy" && s !== "sell")
857
860
  errorAndExit("Side must be buy or sell");
858
- const sym = symbol.toUpperCase();
861
+ const sym = normalizeSymbolArg(symbol);
859
862
  // ── Split execution branch ──
860
863
  if (opts.split) {
861
864
  // runSplitOrder has no reduce-only path — refuse rather than silently place opening orders.
@@ -888,7 +891,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
888
891
  .option("--tp <price>", "Take-profit trigger protecting this entry's verified fill (every venue; separate-order venues need a market / IOC entry)")
889
892
  .option("--sl <price>", "Stop-loss trigger protecting this entry's verified fill (every venue; separate-order venues need a market / IOC entry)")
890
893
  .action(async (symbol, size, opts) => {
891
- await runMarketOrder(symbol.toUpperCase(), "buy", size, opts);
894
+ await runMarketOrder(normalizeSymbolArg(symbol), "buy", size, opts);
892
895
  });
893
896
  supportsDryRun(trade.command("sell <symbol> <size>"))
894
897
  .description("Market sell (shortcut for: trade market <symbol> sell <size>)")
@@ -901,7 +904,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
901
904
  .option("--tp <price>", "Take-profit trigger protecting this entry's verified fill (every venue; separate-order venues need a market / IOC entry)")
902
905
  .option("--sl <price>", "Stop-loss trigger protecting this entry's verified fill (every venue; separate-order venues need a market / IOC entry)")
903
906
  .action(async (symbol, size, opts) => {
904
- await runMarketOrder(symbol.toUpperCase(), "sell", size, opts);
907
+ await runMarketOrder(normalizeSymbolArg(symbol), "sell", size, opts);
905
908
  });
906
909
  // ── Split Order (orderbook-aware) ──
907
910
  supportsDryRun(trade.command("split <symbol> <side> <usd>"))
@@ -912,7 +915,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
912
915
  .option("--min-slice <usd>", "Minimum slice size in USD", "100")
913
916
  .option("--force", "Bypass local risk-limit checks (maxPosition / total exposure)")
914
917
  .action(async (symbol, side, usd, opts) => {
915
- const sym = symbol.toUpperCase();
918
+ const sym = normalizeSymbolArg(symbol);
916
919
  const orderSide = side.toLowerCase();
917
920
  const totalUsd = parseFloat(usd);
918
921
  if (!["buy", "sell"].includes(orderSide))
@@ -946,7 +949,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
946
949
  const s = side.toLowerCase();
947
950
  if (s !== "buy" && s !== "sell")
948
951
  errorAndExit("Side must be buy or sell");
949
- const sym = symbol.toUpperCase();
952
+ const sym = normalizeSymbolArg(symbol);
950
953
  // One parsed size for the risk check and the order ("1,000" / "abc" refused up front).
951
954
  const size = parsePositiveDecimal(rawSize, "size");
952
955
  if (opts.clientId && isOrderDuplicate(opts.clientId)) {
@@ -1062,7 +1065,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1062
1065
  };
1063
1066
  if (orderId) {
1064
1067
  // cancel <symbol> <orderId>
1065
- await cancelOne(symbolOrOrderId.toUpperCase(), orderId);
1068
+ await cancelOne(normalizeSymbolArg(symbolOrOrderId), orderId);
1066
1069
  return;
1067
1070
  }
1068
1071
  // Single argument — an order reference (c:<clientOrderId>, numeric / UUID / 0x-hex order id)
@@ -1095,7 +1098,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1095
1098
  return;
1096
1099
  }
1097
1100
  // cancel <symbol> — cancel all open orders for this symbol
1098
- const symbol = symbolOrOrderId.toUpperCase();
1101
+ const symbol = normalizeSymbolArg(symbolOrOrderId);
1099
1102
  const orders = await adapter.getOpenOrders();
1100
1103
  const matching = orders.filter((o) => symbolMatch(o.symbol, symbol));
1101
1104
  if (matching.length === 0) {
@@ -1167,16 +1170,16 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1167
1170
  const bgAdapter = await getAdapter();
1168
1171
  const exchange = bgAdapter.name;
1169
1172
  // Risk check (and its --force bypass) runs here: the child `strategy twap` has none.
1170
- await openingOrderGate(bgAdapter, { symbol: symbol.toUpperCase(), size: Number(size), reduceOnly: opts.reduceOnly, force: opts.force });
1173
+ await openingOrderGate(bgAdapter, { symbol: normalizeSymbolArg(symbol), size: Number(size), reduceOnly: opts.reduceOnly, force: opts.force });
1171
1174
  const cliArgs = [
1172
- "strategy", "twap", symbol.toUpperCase(), s, size, duration,
1175
+ "strategy", "twap", normalizeSymbolArg(symbol), s, size, duration,
1173
1176
  ...(opts.slices ? ["--slices", opts.slices] : []),
1174
1177
  ...(opts.reduceOnly ? ["--reduce-only"] : []),
1175
1178
  ];
1176
1179
  const job = startJob({
1177
1180
  strategy: "twap",
1178
1181
  exchange,
1179
- params: { symbol: symbol.toUpperCase(), side: s, size, duration, slices: opts.slices, reduceOnly: opts.reduceOnly === true },
1182
+ params: { symbol: normalizeSymbolArg(symbol), side: s, size, duration, slices: opts.slices, reduceOnly: opts.reduceOnly === true },
1180
1183
  venue: exchange, network: networkOf(program), dryRun: isDryRun(), selectors: jobSelectorsOf(program),
1181
1184
  cliArgs,
1182
1185
  });
@@ -1190,17 +1193,17 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1190
1193
  return;
1191
1194
  }
1192
1195
  const adapter = await getAdapter();
1193
- await openingOrderGate(adapter, { symbol: symbol.toUpperCase(), size: Number(size), reduceOnly: opts.reduceOnly, force: opts.force });
1196
+ await openingOrderGate(adapter, { symbol: normalizeSymbolArg(symbol), size: Number(size), reduceOnly: opts.reduceOnly, force: opts.force });
1194
1197
  // Native TWAP: Pacifica (SDK) or Hyperliquid (twapOrder)
1195
1198
  // Fallback: client-side TWAP for any exchange without native support
1196
1199
  let result;
1197
1200
  if (hasPacificaSdk(adapter)) {
1198
- // One slippage resolver (design §13.2): bps → Pacifica's slippage_percent.
1201
+ // One slippage resolver: bps → Pacifica's slippage_percent.
1199
1202
  const slippage = resolveAdapterSlippage(adapter, parseSlippageBpsFlag(opts.slippageBps));
1200
1203
  requirePacificaTwapSigner(adapter, "TWAP order");
1201
1204
  const sdk = adapter.sdk;
1202
1205
  result = await sdk.createTWAP({
1203
- symbol: symbol.toUpperCase(),
1206
+ symbol: normalizeSymbolArg(symbol),
1204
1207
  amount: size,
1205
1208
  side: s === "buy" ? "bid" : "ask",
1206
1209
  slippage_percent: String(slippage.bps / 100),
@@ -1213,13 +1216,13 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1213
1216
  if (!/^\d+$/.test(duration.trim()))
1214
1217
  errorAndExit("HL TWAP duration must be a whole number of minutes");
1215
1218
  const minutes = Number(duration.trim());
1216
- result = await adapter.twapOrder(symbol.toUpperCase(), s, size, minutes, { reduceOnly: opts.reduceOnly });
1219
+ result = await adapter.twapOrder(normalizeSymbolArg(symbol), s, size, minutes, { reduceOnly: opts.reduceOnly });
1217
1220
  }
1218
1221
  else {
1219
1222
  // Client-side TWAP (foreground) for exchanges without native TWAP
1220
1223
  const { runTWAP } = await import("../strategies/twap.js");
1221
1224
  const twapResult = await runTWAP(adapter, {
1222
- symbol: symbol.toUpperCase(),
1225
+ symbol: normalizeSymbolArg(symbol),
1223
1226
  side: s,
1224
1227
  totalSize: parseFloat(size),
1225
1228
  durationSec: parseInt(duration),
@@ -1233,7 +1236,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1233
1236
  }
1234
1237
  if (isJson())
1235
1238
  return printJson(jsonOk(result));
1236
- console.log(chalk.green(`\n TWAP ${s.toUpperCase()} ${size} ${symbol.toUpperCase()} over ${duration} placed on ${adapter.name}.\n`));
1239
+ console.log(chalk.green(`\n TWAP ${s.toUpperCase()} ${size} ${normalizeSymbolArg(symbol)} over ${duration} placed on ${adapter.name}.\n`));
1237
1240
  });
1238
1241
  // === Stop / Trigger orders (every trading venue) ===
1239
1242
  supportsDryRun(trade.command("stop <symbol> <side> <size> <stopPrice>"))
@@ -1251,7 +1254,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1251
1254
  if (opts.triggerType && !["mark", "last", "mid"].includes(opts.triggerType)) {
1252
1255
  errorAndExit("--trigger-type must be one of: mark, last, mid");
1253
1256
  }
1254
- const sym = symbol.toUpperCase();
1257
+ const sym = normalizeSymbolArg(symbol);
1255
1258
  const adapter = await getAdapter();
1256
1259
  const { def: venueDef, caps: venueCaps } = tradingVenueOf(adapter);
1257
1260
  const isLimit = opts.limitPrice !== undefined;
@@ -1317,7 +1320,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1317
1320
  const tpLimit = positivePriceOpt(opts.tpLimit, "--tp-limit");
1318
1321
  if (tpLimit !== null && prices.tp === null)
1319
1322
  throw new PerpError("INVALID_PARAMS", "--tp-limit needs --tp");
1320
- const sym = symbol.toUpperCase();
1323
+ const sym = normalizeSymbolArg(symbol);
1321
1324
  const adapter = await getAdapter();
1322
1325
  const { def, caps } = tradingVenueOf(adapter);
1323
1326
  if (caps.tpsl === null) {
@@ -1419,7 +1422,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1419
1422
  .requiredOption("--levels <levels>", "[required] Comma-separated price:percent pairs (e.g., 72000:25,75000:25,80000:50)")
1420
1423
  .option("--size <size>", "Override total position size (default: uses current position)")
1421
1424
  .action(async (symbol, opts) => {
1422
- const sym = symbol.toUpperCase();
1425
+ const sym = normalizeSymbolArg(symbol);
1423
1426
  const adapter = await getAdapter();
1424
1427
  if (adapter.name === "ondo") {
1425
1428
  // docs.ondoperps.xyz take-profit-and-stop-loss: position TP/SL "Via Risk Management Module
@@ -1486,8 +1489,8 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1486
1489
  });
1487
1490
  // === Pacifica-only commands ===
1488
1491
  supportsDryRun(trade.command("edit <symbol> <orderId> <price> <size>"))
1489
- .description("Edit an existing order: new price and new open (unfilled) size; the order keeps its TIF / post-only (pacifica: re-created as ALO) — orderId, or c:<clientOrderId> on ondo/arcus/qfex (qfex: plain LIMIT / ALO only; the venue assigns a new order id, the client id is kept)")
1490
- .option("--tif <tif>", "Time-in-force of the replacement (cancel+replace venues only, e.g. ondo; ALO keeps post-only)")
1492
+ .description("Edit an existing order: new price and new open (unfilled) size; the order keeps its TIF / post-only (pacifica: TIF read from the order's client id — ALO via native edit, GTC / TOB via cancel+replace; a new order id either way) — use the orderId the edit returns; orderId, or c:<clientOrderId> on ondo/arcus/qfex (qfex: plain LIMIT / ALO only; the venue assigns a new order id, the client id is kept)")
1493
+ .option("--tif <tif>", "Time-in-force of the replacement (ondo / pacifica only; ALO keeps post-only)")
1491
1494
  .option("--force-tif", "Allow --tif to flip the order's post-only flag (ondo / pacifica); otherwise such a conflict is refused")
1492
1495
  .action(async (symbol, orderId, price, size, opts) => {
1493
1496
  const adapter = await getAdapter();
@@ -1496,9 +1499,9 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1496
1499
  const amendTif = opts.tif !== undefined ? validateLegacyTif(venueDef, venueCaps, opts.tif) : null;
1497
1500
  if (opts.forceTif === true && amendTif === null)
1498
1501
  throw new PerpError("INVALID_PARAMS", "--force-tif needs --tif");
1499
- if (dryRunGuard("edit_order", { exchange: adapter.name, symbol: symbol.toUpperCase(), orderId, price, size }))
1502
+ if (dryRunGuard("edit_order", { exchange: adapter.name, symbol: normalizeSymbolArg(symbol), orderId, price, size }))
1500
1503
  return;
1501
- const result = await v2Of(adapter).amendOrder({ symbol: symbol.toUpperCase(), ref: parseOrderRef(orderId), price, size, tif: amendTif, forceTif: opts.forceTif === true });
1504
+ const result = await v2Of(adapter).amendOrder({ symbol: normalizeSymbolArg(symbol), ref: parseOrderRef(orderId), price, size, tif: amendTif, forceTif: opts.forceTif === true });
1502
1505
  if (isJson())
1503
1506
  return printJson(jsonOk(orderResultJson(result)));
1504
1507
  printOrder(`Edit of ${orderId} → $${price} x ${size} on ${adapter.name}`, result);
@@ -1510,7 +1513,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1510
1513
  const adapter = await getAdapter();
1511
1514
  if (adapter.name === "qfex" && isTradingV2(adapter)) {
1512
1515
  // QFEX: cancelByRef reads the row first and routes a TAKE_PROFIT / STOP_LOSS row to cancel_stop_order.
1513
- const sym = symbol.toUpperCase();
1516
+ const sym = normalizeSymbolArg(symbol);
1514
1517
  if (dryRunGuard("cancel_stop", { exchange: adapter.name, symbol: sym, stopOrderId }))
1515
1518
  return;
1516
1519
  const r = await adapter.cancelByRef(sym, parseOrderRef(stopOrderId));
@@ -1528,7 +1531,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1528
1531
  const pac = adapter;
1529
1532
  if (typeof pac.cancelStopOrder !== "function") {
1530
1533
  const { def } = tradingVenueOf(adapter);
1531
- throw unsupported(def.displayName, "cancelStop", "stop orders are cancelled like any other order on this venue", `Use: perp trade cancel ${symbol.toUpperCase()} <orderId> -e ${def.id}`);
1534
+ throw unsupported(def.displayName, "cancelStop", "stop orders are cancelled like any other order on this venue", `Use: perp trade cancel ${normalizeSymbolArg(symbol)} <orderId> -e ${def.id}`);
1532
1535
  }
1533
1536
  const result = await pac.cancelStopOrder(symbol, stopOrderId);
1534
1537
  if (isJson())
@@ -1549,10 +1552,10 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1549
1552
  if (hasPacificaSdk(adapter)) {
1550
1553
  requirePacificaTwapSigner(adapter, "TWAP cancel");
1551
1554
  const sdk = adapter.sdk;
1552
- result = await sdk.cancelTWAP({ symbol: symbol.toUpperCase(), order_id: Number(twapOrderId) }, adapter.publicKey, adapter.signer);
1555
+ result = await sdk.cancelTWAP({ symbol: normalizeSymbolArg(symbol), order_id: Number(twapOrderId) }, adapter.publicKey, adapter.signer);
1553
1556
  }
1554
1557
  else if (isTwapCapable(adapter)) {
1555
- result = await adapter.twapCancel(symbol.toUpperCase(), Number(twapOrderId));
1558
+ result = await adapter.twapCancel(normalizeSymbolArg(symbol), Number(twapOrderId));
1556
1559
  }
1557
1560
  else {
1558
1561
  errorAndExit("Cancel TWAP requires --exchange pacifica or hyperliquid");
@@ -1561,6 +1564,8 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1561
1564
  return printJson(jsonOk(result));
1562
1565
  console.log(chalk.green(`\n TWAP order ${twapOrderId} cancelled on ${adapter.name}.\n`));
1563
1566
  });
1567
+ // === Pacifica-only order types (trade atomic) ===
1568
+ registerPacificaOrderCommands(trade, getAdapter, isJson, isDryRun);
1564
1569
  // === Hyperliquid-only commands ===
1565
1570
  supportsDryRun(trade.command("leverage <symbol> <leverage>"))
1566
1571
  .description("Set leverage for a symbol")
@@ -1574,22 +1579,22 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1574
1579
  const lev = Number(leverage.trim());
1575
1580
  const adapter = await getAdapter();
1576
1581
  const mode = opts.isolated ? "isolated" : "cross";
1577
- if (dryRunGuard("set_leverage", { exchange: adapter.name, symbol: symbol.toUpperCase(), leverage: lev, mode }))
1582
+ if (dryRunGuard("set_leverage", { exchange: adapter.name, symbol: normalizeSymbolArg(symbol), leverage: lev, mode }))
1578
1583
  return;
1579
1584
  try {
1580
- const result = await adapter.setLeverage(symbol.toUpperCase(), lev, mode);
1585
+ const result = await adapter.setLeverage(normalizeSymbolArg(symbol), lev, mode);
1581
1586
  logExecution({
1582
- type: "rebalance", exchange: adapter.name, symbol: symbol.toUpperCase(), side: mode,
1587
+ type: "rebalance", exchange: adapter.name, symbol: normalizeSymbolArg(symbol), side: mode,
1583
1588
  size: leverage, status: "success", dryRun: false,
1584
1589
  meta: { action: "set_leverage", leverage: lev, mode },
1585
1590
  });
1586
1591
  if (isJson())
1587
1592
  return printJson(jsonOk(result));
1588
- console.log(chalk.green(`\n Leverage set to ${leverage}x (${mode}) for ${symbol.toUpperCase()} on ${adapter.name}.\n`));
1593
+ console.log(chalk.green(`\n Leverage set to ${leverage}x (${mode}) for ${normalizeSymbolArg(symbol)} on ${adapter.name}.\n`));
1589
1594
  }
1590
1595
  catch (err) {
1591
1596
  logExecution({
1592
- type: "rebalance", exchange: adapter.name, symbol: symbol.toUpperCase(), side: mode,
1597
+ type: "rebalance", exchange: adapter.name, symbol: normalizeSymbolArg(symbol), side: mode,
1593
1598
  size: leverage, status: "failed", dryRun: false,
1594
1599
  error: extractErrorMessage(err),
1595
1600
  meta: { action: "set_leverage", leverage: lev, mode },
@@ -1598,7 +1603,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1598
1603
  }
1599
1604
  });
1600
1605
  /**
1601
- * One verified reduce-only close (design §12.3, §13.10 #4): closePerpVerified (placePerpVerified
1606
+ * One verified reduce-only close: closePerpVerified (placePerpVerified
1602
1607
  * + one residual market close), so the reported `closed` is a measured / venue-final fill, never
1603
1608
  * the requested size. Logged "success" only when fully verified. A residual or an unknown outcome
1604
1609
  * quarantines the symbol (CLI path "trade-close"); a definite rejection propagates unchanged.
@@ -1723,7 +1728,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1723
1728
  .option("--slippage-bps <n>", "Slippage bound in bps (default: settings defaultSlippageBps)")
1724
1729
  .action(async (symbol, opts) => {
1725
1730
  await withJsonErrors(isJson(), async () => {
1726
- const sym = symbol.toUpperCase();
1731
+ const sym = normalizeSymbolArg(symbol);
1727
1732
  const adapter = await getAdapter();
1728
1733
  refuseGatedReduceOnly(adapter);
1729
1734
  const slippage = resolveAdapterSlippage(adapter, parseSlippageBpsFlag(opts.slippageBps));
@@ -1805,7 +1810,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1805
1810
  .option("--slippage-bps <n>", "Slippage bound in bps (default: settings defaultSlippageBps)")
1806
1811
  .action(async (symbol, percent, opts) => {
1807
1812
  await withJsonErrors(isJson(), async () => {
1808
- const sym = symbol.toUpperCase();
1813
+ const sym = normalizeSymbolArg(symbol);
1809
1814
  const pct = parseFloat(percent);
1810
1815
  if (isNaN(pct) || pct <= 0 || pct > 100)
1811
1816
  errorAndExit("Percent must be between 0 and 100");
@@ -1864,7 +1869,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1864
1869
  remediation: `Pass --symbol <SYM>: perp trade status ${orderId} --symbol <SYM> -e ${adapter.venue.id}`,
1865
1870
  });
1866
1871
  }
1867
- const r = await adapter.getOrderStatus(opts.symbol.toUpperCase(), parseOrderRef(orderId));
1872
+ const r = await adapter.getOrderStatus(normalizeSymbolArg(opts.symbol), parseOrderRef(orderId));
1868
1873
  if (isJson())
1869
1874
  return printJson(jsonOk(orderResultJson(r)));
1870
1875
  printOrder(`Order ${orderId} (${r.symbol}) on ${adapter.name}`, r);
@@ -1885,7 +1890,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1885
1890
  remediation: `Pass --symbol <SYM>: perp trade status ${orderId} --symbol <SYM> -e ${def.id}`,
1886
1891
  });
1887
1892
  }
1888
- const r = await v2Of(adapter).getOrderStatus(opts.symbol !== undefined ? opts.symbol.toUpperCase() : "", ref);
1893
+ const r = await v2Of(adapter).getOrderStatus(opts.symbol !== undefined ? normalizeSymbolArg(opts.symbol) : "", ref);
1889
1894
  if (isJson())
1890
1895
  return printJson(jsonOk(orderResultJson(r)));
1891
1896
  printOrder(`Order ${orderId} (${r.symbol}) on ${adapter.name}`, r);
@@ -1947,13 +1952,13 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1947
1952
  const trades = await adapter.getTradeHistory(limit);
1948
1953
  let filtered = trades;
1949
1954
  if (symbol) {
1950
- const sym = symbol.toUpperCase();
1955
+ const sym = normalizeSymbolArg(symbol);
1951
1956
  filtered = trades.filter(t => symbolMatch(t.symbol, sym));
1952
1957
  }
1953
1958
  if (isJson())
1954
1959
  return printJson(jsonOk(filtered));
1955
1960
  if (filtered.length === 0) {
1956
- console.log(chalk.gray(`\n No fills${symbol ? ` for ${symbol.toUpperCase()}` : ""}.\n`));
1961
+ console.log(chalk.gray(`\n No fills${symbol ? ` for ${normalizeSymbolArg(symbol)}` : ""}.\n`));
1957
1962
  return;
1958
1963
  }
1959
1964
  const rows = filtered.map((t) => [
@@ -1982,7 +1987,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1982
1987
  errorAndExit("Side must be buy or sell");
1983
1988
  const adapter = await getAdapter();
1984
1989
  const validation = await validateTrade(adapter, {
1985
- symbol: symbol.toUpperCase(),
1990
+ symbol: normalizeSymbolArg(symbol),
1986
1991
  side: s,
1987
1992
  size: parseFloat(size),
1988
1993
  price: opts.price ? parseFloat(opts.price) : undefined,
@@ -1992,7 +1997,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
1992
1997
  });
1993
1998
  if (isJson())
1994
1999
  return printJson(jsonOk(validation));
1995
- console.log(chalk.cyan.bold(`\n Pre-Trade Check: ${symbol.toUpperCase()} ${s.toUpperCase()} ${size}\n`));
2000
+ console.log(chalk.cyan.bold(`\n Pre-Trade Check: ${normalizeSymbolArg(symbol)} ${s.toUpperCase()} ${size}\n`));
1996
2001
  for (const check of validation.checks) {
1997
2002
  const icon = check.passed ? chalk.green("✓") : chalk.red("✗");
1998
2003
  console.log(` ${icon} ${check.check}: ${check.message}`);
@@ -2023,7 +2028,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2023
2028
  .option("--size <base>", "Total base amount (e.g., 0.01 BTC)")
2024
2029
  .option("--force", "Bypass local risk-limit checks (maxPosition / total exposure)")
2025
2030
  .action(async (symbol, side, opts) => {
2026
- const sym = symbol.toUpperCase();
2031
+ const sym = normalizeSymbolArg(symbol);
2027
2032
  const s = side.toLowerCase();
2028
2033
  if (s !== "buy" && s !== "sell")
2029
2034
  errorAndExit("Side must be buy or sell");
@@ -2174,7 +2179,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2174
2179
  }
2175
2180
  console.log(chalk.yellow(`\n PnL tracker stopped.\n`));
2176
2181
  });
2177
- // === Spot Trading (venue definitions, design §12) ===
2182
+ // === Spot Trading (venue definitions) ===
2178
2183
  /** Spot adapter for the current venue via the factory; CAPABILITY_UNSUPPORTED when def.spot is null. */
2179
2184
  async function spotContext(confirmOnchainApprove) {
2180
2185
  const adapter = await getAdapter();
@@ -2186,7 +2191,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2186
2191
  const spot = await requireSpotAdapter(def.id, adapter, { confirmOnchainApprove });
2187
2192
  return { def, spotDef, spot };
2188
2193
  }
2189
- /** Uniform feature name per spot capability (§13.6). */
2194
+ /** Uniform feature name per spot capability. */
2190
2195
  const SPOT_FEATURE = {
2191
2196
  limitOrder: "spotLimitOrder", orderbook: "spotOrderbook", cancel: "spotCancel", transfer: "spotTransfer",
2192
2197
  };
@@ -2255,7 +2260,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2255
2260
  console.log();
2256
2261
  }
2257
2262
  /**
2258
- * Execution record of a spot market order, same fields for every venue (design §13.8):
2263
+ * Execution record of a spot market order, same fields for every venue:
2259
2264
  * the resolved bound (bps + source) and, where the adapter reports it, the protection
2260
2265
  * price actually sent and its reference (`protection` on the raw result / venueRaw).
2261
2266
  */
@@ -2332,7 +2337,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2332
2337
  const slippageFlag = parseSlippageBpsFlag(opts.slippageBps);
2333
2338
  const { def, spotDef, spot } = await spotContext(opts.yes === true);
2334
2339
  const slippage = resolveSlippage(def.id, slippageFlag);
2335
- const sym = symbol.toUpperCase();
2340
+ const sym = normalizeSymbolArg(symbol);
2336
2341
  if (dryRunGuard(`spot ${verb}`, { exchange: def.id, symbol: sym, side, size }))
2337
2342
  return;
2338
2343
  const raw = await spot.spotMarketOrder(sym, side, size, { slippage });
@@ -2359,7 +2364,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2359
2364
  throw new PerpError("INVALID_PARAMS", `Side must be buy or sell (got "${side}")`);
2360
2365
  const { def, spotDef, spot } = await spotContext(false);
2361
2366
  requireSpotCapability(def, spotDef, "limitOrder", "limit orders");
2362
- const sym = symbol.toUpperCase();
2367
+ const sym = normalizeSymbolArg(symbol);
2363
2368
  if (dryRunGuard("spot limit", { exchange: def.id, symbol: sym, side: s, price, size, tif: opts.tif ?? null }))
2364
2369
  return;
2365
2370
  const raw = await spot.spotLimitOrder(sym, s, price, size, opts.tif !== undefined ? { tif: opts.tif } : undefined);
@@ -2379,7 +2384,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2379
2384
  await withJsonErrors(isJson(), async () => {
2380
2385
  const { def, spotDef, spot } = await spotContext(false);
2381
2386
  requireSpotCapability(def, spotDef, "cancel", "order cancel");
2382
- const sym = symbol.toUpperCase();
2387
+ const sym = normalizeSymbolArg(symbol);
2383
2388
  if (dryRunGuard("spot cancel", { exchange: def.id, symbol: sym, orderId }))
2384
2389
  return;
2385
2390
  const result = spotCancelResult(def, sym, orderId, await spot.spotCancelOrder(sym, orderId));
@@ -2401,7 +2406,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
2401
2406
  if (spotDef.accountModel === "wallet" || !isSpotOpenOrdersCapable(spot)) {
2402
2407
  throw unsupported(def.displayName, "spotOpenOrders", spotDef.accountModel === "wallet" ? "spot is an on-chain swap that settles immediately — there are no resting orders" : "the spot adapter cannot list open orders", spotDef.accountModel === "wallet" ? `Check the wallet balance: perp -e ${def.id} trade spot-balance` : "Use the venue UI to inspect open spot orders");
2403
2408
  }
2404
- const orders = await spot.getSpotOpenOrders(symbol === undefined ? null : symbol.toUpperCase());
2409
+ const orders = await spot.getSpotOpenOrders(symbol === undefined ? null : normalizeSymbolArg(symbol));
2405
2410
  if (isJson())
2406
2411
  return printJson(jsonOk({ exchange: def.id, orders }));
2407
2412
  console.log(chalk.cyan(`\n Open spot orders (${def.id}):\n`));
@@ -2520,7 +2525,7 @@ function parseMultiLeg(spec) {
2520
2525
  if (isNaN(Number(size)) || Number(size) <= 0) {
2521
2526
  throw new Error(`Invalid size "${size}" — must be a positive number`);
2522
2527
  }
2523
- return { exchange: normalizedExchange, symbol: symbol.toUpperCase(), side, size };
2528
+ return { exchange: normalizedExchange, symbol: normalizeSymbolArg(symbol), side, size };
2524
2529
  }
2525
2530
  function registerMultiAction(parent, getAdapterForExchange, isJson, deprecated = false) {
2526
2531
  const cmd = parent
@@ -525,7 +525,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
525
525
  const info = EXCHANGE_ENV_MAP[resolved];
526
526
  if (!info) {
527
527
  throw new PerpError("INVALID_PARAMS", `Unknown exchange: ${exchange}`, {
528
- remediation: "Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast). Lighter RH / Ondo / Arcus use: perp wallet agent approve <lighter-rh|ondo|arcus>; QFEX: perp wallet agent import qfex.",
528
+ remediation: "Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast). Lighter RH / Ondo / Arcus use: perp wallet agent approve <lighter-rh|ondo|arcus>; QFEX: perp wallet agent import qfex. TxFlow, Variational and Extended are market data only; RISEx is read-only in this release (trading later).",
529
529
  });
530
530
  }
531
531
  const { promptHidden } = await import("./agent.js");
package/dist/config.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Keypair } from "@solana/web3.js";
2
2
  import type { Network, VenueId } from "./exchanges/venue-types.js";
3
3
  import type { VenueCredentials } from "./exchanges/venue.js";
4
- /** @deprecated alias — every venue id is an exchange id (design §10.3). */
4
+ /** @deprecated alias — every venue id is an exchange id. */
5
5
  export type Exchange = VenueId;
6
6
  /** CLI (and MCP) inputs that select credentials. Absent flag = null. */
7
7
  export interface CliCredentialOpts {
@@ -27,7 +27,7 @@ export declare function resolveOwsWallet(walletName: string): string | null;
27
27
  export declare function isOwsKey(key: string): boolean;
28
28
  /** Extract OWS wallet name from an ows: prefixed key string. */
29
29
  export declare function getOwsWalletName(key: string): string;
30
- /** CLI/MCP-shared credential resolution (design §10.2). */
30
+ /** CLI/MCP-shared credential resolution. */
31
31
  export declare function resolveCredentials(venue: VenueId, opts: CliCredentialOpts, network: Network): Promise<VenueCredentials>;
32
32
  /**
33
33
  * Whether any credential source is configured for the venue (explicit checks,
package/dist/config.js CHANGED
@@ -8,7 +8,7 @@ import { PerpError } from "./errors.js";
8
8
  import { isLegacyVenueId } from "./exchanges/venue-types.js";
9
9
  const _require = createRequire(import.meta.url);
10
10
  /**
11
- * Legacy env private-key variables (design §10.3). New venues have no env tier:
11
+ * Legacy env private-key variables. New venues have no env tier:
12
12
  * loadPrivateKey()/resolveCredentials() never read env keys for them.
13
13
  * v0.12.10 restored ASTER_PRIVATE_KEY so Aster Tier 3 env-PK loading works.
14
14
  */
@@ -50,7 +50,7 @@ function envKeyOf(venue) {
50
50
  if (v)
51
51
  return v;
52
52
  }
53
- // config.ts generic PRIVATE_KEY — recorded as out of scope in §10.11, legacy venues only.
53
+ // config.ts generic PRIVATE_KEY — out of scope (legacy venues only).
54
54
  const generic = process.env.PRIVATE_KEY;
55
55
  return generic ? generic : null;
56
56
  }
@@ -106,6 +106,12 @@ async function agentOf(venue) {
106
106
  case "ondo": return store.getVenueAgent("ondo");
107
107
  case "arcus": return store.getVenueAgent("arcus");
108
108
  case "qfex": return store.getVenueAgent("qfex");
109
+ case "risex": return store.getVenueAgent("risex");
110
+ // Market-data-only venues: no agent.
111
+ case "txflow": return null;
112
+ case "variational": return null;
113
+ case "grvt": return null;
114
+ case "extended": return null;
109
115
  case "pacifica":
110
116
  case "hyperliquid":
111
117
  case "lighter":
@@ -125,6 +131,11 @@ async function agentForNetwork(venue, network) {
125
131
  case "ondo": return store.getVenueAgentForNetwork("ondo", network);
126
132
  case "arcus": return store.getVenueAgentForNetwork("arcus", network);
127
133
  case "qfex": return store.getVenueAgentForNetwork("qfex", network);
134
+ case "risex": return store.getVenueAgentForNetwork("risex", network);
135
+ case "txflow": return null;
136
+ case "variational": return null;
137
+ case "grvt": return null;
138
+ case "extended": return null;
128
139
  case "pacifica":
129
140
  case "hyperliquid":
130
141
  case "lighter":
@@ -151,11 +162,11 @@ function owsPassphraseOf(opts) {
151
162
  function owsKeyTokenOf(opts) {
152
163
  if (opts.owsKey !== null)
153
164
  return opts.owsKey;
154
- // OWS_API_KEY env: recorded as out of scope in §10.11 (kept as before).
165
+ // OWS_API_KEY env: recorded as out of scope in (kept as before).
155
166
  const env = process.env.OWS_API_KEY;
156
167
  return env ? env : null;
157
168
  }
158
- /** CLI/MCP-shared credential resolution (design §10.2). */
169
+ /** CLI/MCP-shared credential resolution. */
159
170
  export async function resolveCredentials(venue, opts, network) {
160
171
  const withToken = { ...opts, owsKey: owsKeyTokenOf(opts) };
161
172
  // Custodial venues (masterKeyKind "none": qfex) have no master key to resolve.
package/dist/constants.js CHANGED
@@ -28,6 +28,11 @@ export const TAKER_FEES = {
28
28
  // orders (0.05%) are never under-estimated. Lower tiers / the lifetime-tier promotion: set
29
29
  // settings.fees.qfex.
30
30
  qfex: 0.001,
31
+ // RISEx Tier 1 ($0 rolling 14-day volume; docs.risechain.com/docs/risex/trading/fees, read
32
+ // 2026-10-14): taker 3.00 bps, maker 1.00 bps; higher tiers down to 1.50 / 0.00 bps — set
33
+ // settings.fees.risex for a higher tier.
34
+ risex: 0.0003,
35
+ // TxFlow: no trading in perp-cli (market data only) — no fee entry on purpose.
31
36
  };
32
37
  /**
33
38
  * Published maker fee per venue (fraction; negative = rebate). Only venues whose base-tier
@@ -41,6 +46,7 @@ export const MAKER_FEES = {
41
46
  aster: 0,
42
47
  "lighter-rh": 0, // Standard tier (Premium: 0.012% under $1M 14-day volume)
43
48
  qfex: 0.0005, // Tier 5 single stocks 0.05% (indices / commodities 0.02%); see TAKER_FEES.qfex
49
+ risex: 0.0001, // Tier 1 maker 1.00 bps; see TAKER_FEES.risex
44
50
  };
45
51
  /**
46
52
  * Taker fee for a venue: settings.fees[venue].taker → TAKER_FEES[venue] → DEFAULT_TAKER_FEE
@@ -70,4 +70,6 @@ export declare function marginEntryBlocks(check: ChainMarginCheck, minMarginPct?
70
70
  * @param maxSlippagePct - Max acceptable slippage (default: 0.3%)
71
71
  * @returns USD size per leg, or 0 if not viable
72
72
  */
73
- export declare function computeAutoSize(longAdapter: ExchangeAdapter, shortAdapter: ExchangeAdapter, symbol: string, maxSlippagePct?: number): Promise<number>;
73
+ export declare function computeAutoSize(longAdapter: ExchangeAdapter, shortAdapter: ExchangeAdapter, symbol: string, maxSlippagePct?: number,
74
+ /** The short venue's own symbol when it differs (kPEPE ↔ 1000PEPE); default `symbol`. */
75
+ shortSymbol?: string): Promise<number>;