perp-cli 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +152 -13
  2. package/dist/agent-wallet/store.d.ts +3 -2
  3. package/dist/agent-wallet/store.js +2 -2
  4. package/dist/api/public/hyperliquid.js +4 -0
  5. package/dist/api/public/pacifica.js +83 -13
  6. package/dist/api/public/urls.d.ts +2 -0
  7. package/dist/api/public/urls.js +3 -0
  8. package/dist/arb/utils.d.ts +1 -2
  9. package/dist/arb/utils.js +1 -2
  10. package/dist/bot/apex/pulse.js +3 -0
  11. package/dist/bot/apex/radar.js +2 -1
  12. package/dist/bot/engine.d.ts +10 -1
  13. package/dist/bot/engine.js +14 -6
  14. package/dist/bot/indicators.d.ts +7 -0
  15. package/dist/bot/indicators.js +16 -0
  16. package/dist/bot/perp-order.d.ts +4 -4
  17. package/dist/bot/perp-order.js +5 -5
  18. package/dist/bot/quarantine.d.ts +1 -1
  19. package/dist/bot/quarantine.js +1 -1
  20. package/dist/bot/strategies/apex-strategy.js +3 -2
  21. package/dist/bot/strategies/engine-mm.js +6 -5
  22. package/dist/bot/strategies/funding-arb-strategy.js +9 -9
  23. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
  24. package/dist/bot/strategies/funding-arb-utils.js +43 -24
  25. package/dist/bot/strategies/funding-auto.js +2 -2
  26. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  27. package/dist/bot/strategies/liquidation-mm.js +8 -2
  28. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  29. package/dist/bot/strategies/momentum-breakout.js +11 -3
  30. package/dist/bot/strategies/spot-perp-arb-strategy.js +23 -12
  31. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  32. package/dist/bot/strategies/spot-perp-executor.js +1 -1
  33. package/dist/bot/strategy-types.d.ts +2 -1
  34. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  35. package/dist/bot/tui/BotDashboard.js +1 -1
  36. package/dist/cache.js +12 -6
  37. package/dist/cli-program.d.ts +15 -0
  38. package/dist/cli-program.js +125 -5
  39. package/dist/commands/agent-import.d.ts +7 -0
  40. package/dist/commands/agent-import.js +30 -1
  41. package/dist/commands/agent.d.ts +6 -3
  42. package/dist/commands/agent.js +160 -12
  43. package/dist/commands/arb/index.js +4 -4
  44. package/dist/commands/arb-auto.js +21 -17
  45. package/dist/commands/arb.d.ts +12 -0
  46. package/dist/commands/arb.js +61 -143
  47. package/dist/commands/bot.js +1 -1
  48. package/dist/commands/builder.js +1 -1
  49. package/dist/commands/funds.js +113 -3
  50. package/dist/commands/init.d.ts +1 -1
  51. package/dist/commands/init.js +27 -7
  52. package/dist/commands/manage.js +23 -21
  53. package/dist/commands/market.js +168 -36
  54. package/dist/commands/options.d.ts +9 -0
  55. package/dist/commands/options.js +134 -0
  56. package/dist/commands/pacifica-earn.d.ts +5 -0
  57. package/dist/commands/pacifica-earn.js +468 -0
  58. package/dist/commands/pacifica-orders.d.ts +14 -0
  59. package/dist/commands/pacifica-orders.js +142 -0
  60. package/dist/commands/portfolio.js +32 -47
  61. package/dist/commands/rebalance.d.ts +1 -0
  62. package/dist/commands/rebalance.js +15 -0
  63. package/dist/commands/risk.d.ts +11 -2
  64. package/dist/commands/risk.js +39 -44
  65. package/dist/commands/trade.d.ts +1 -0
  66. package/dist/commands/trade.js +63 -58
  67. package/dist/commands/wallet.js +1 -1
  68. package/dist/config.d.ts +2 -2
  69. package/dist/config.js +15 -4
  70. package/dist/constants.js +6 -0
  71. package/dist/dashboard/server.js +1 -1
  72. package/dist/dashboard/ui.js +1 -1
  73. package/dist/errors.d.ts +24 -1
  74. package/dist/errors.js +25 -3
  75. package/dist/exchanges/arcus/adapter.d.ts +7 -7
  76. package/dist/exchanges/arcus/adapter.js +12 -12
  77. package/dist/exchanges/arcus/agent.d.ts +1 -1
  78. package/dist/exchanges/arcus/agent.js +1 -1
  79. package/dist/exchanges/arcus/client.js +1 -1
  80. package/dist/exchanges/arcus/signing.d.ts +2 -3
  81. package/dist/exchanges/arcus/signing.js +1 -1
  82. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  83. package/dist/exchanges/arcus/spot-chain.js +1 -1
  84. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  85. package/dist/exchanges/arcus/spot-router.js +2 -2
  86. package/dist/exchanges/arcus/spot.d.ts +2 -3
  87. package/dist/exchanges/arcus/spot.js +2 -3
  88. package/dist/exchanges/aster-spot.d.ts +3 -3
  89. package/dist/exchanges/aster-spot.js +6 -9
  90. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  91. package/dist/exchanges/aster-typed-data.js +1 -1
  92. package/dist/exchanges/aster.d.ts +9 -4
  93. package/dist/exchanges/aster.js +125 -55
  94. package/dist/exchanges/builder-fee.d.ts +1 -1
  95. package/dist/exchanges/builder-fee.js +13 -2
  96. package/dist/exchanges/canonical-symbols.d.ts +1 -2
  97. package/dist/exchanges/canonical-symbols.js +26 -2
  98. package/dist/exchanges/derive/client.d.ts +37 -0
  99. package/dist/exchanges/derive/client.js +135 -0
  100. package/dist/exchanges/derive/options.d.ts +68 -0
  101. package/dist/exchanges/derive/options.js +360 -0
  102. package/dist/exchanges/extended/adapter.d.ts +82 -0
  103. package/dist/exchanges/extended/adapter.js +179 -0
  104. package/dist/exchanges/extended/client.d.ts +38 -0
  105. package/dist/exchanges/extended/client.js +88 -0
  106. package/dist/exchanges/extended/public.d.ts +130 -0
  107. package/dist/exchanges/extended/public.js +429 -0
  108. package/dist/exchanges/grvt/adapter.d.ts +86 -0
  109. package/dist/exchanges/grvt/adapter.js +206 -0
  110. package/dist/exchanges/grvt/client.d.ts +25 -0
  111. package/dist/exchanges/grvt/client.js +86 -0
  112. package/dist/exchanges/grvt/public.d.ts +151 -0
  113. package/dist/exchanges/grvt/public.js +438 -0
  114. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  115. package/dist/exchanges/hyperliquid-spot.js +5 -5
  116. package/dist/exchanges/hyperliquid.d.ts +22 -16
  117. package/dist/exchanges/hyperliquid.js +80 -55
  118. package/dist/exchanges/index.d.ts +1 -1
  119. package/dist/exchanges/index.js +1 -1
  120. package/dist/exchanges/interface.d.ts +97 -8
  121. package/dist/exchanges/interface.js +19 -2
  122. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  123. package/dist/exchanges/ioc-outcome.js +1 -1
  124. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  125. package/dist/exchanges/legacy-trading-v2.js +7 -2
  126. package/dist/exchanges/lighter-public.d.ts +2 -2
  127. package/dist/exchanges/lighter-public.js +2 -2
  128. package/dist/exchanges/lighter-signer.d.ts +1 -1
  129. package/dist/exchanges/lighter-signer.js +1 -1
  130. package/dist/exchanges/lighter-spot.d.ts +3 -4
  131. package/dist/exchanges/lighter-spot.js +6 -7
  132. package/dist/exchanges/lighter.d.ts +11 -11
  133. package/dist/exchanges/lighter.js +28 -14
  134. package/dist/exchanges/ondo/adapter.d.ts +5 -6
  135. package/dist/exchanges/ondo/adapter.js +16 -14
  136. package/dist/exchanges/ondo/agent.d.ts +1 -1
  137. package/dist/exchanges/ondo/agent.js +1 -1
  138. package/dist/exchanges/ondo/client.d.ts +1 -1
  139. package/dist/exchanges/ondo/client.js +2 -2
  140. package/dist/exchanges/ondo/public.d.ts +8 -1
  141. package/dist/exchanges/ondo/public.js +13 -2
  142. package/dist/exchanges/ondo/spot.d.ts +2 -2
  143. package/dist/exchanges/ondo/spot.js +3 -3
  144. package/dist/exchanges/options-interface.d.ts +158 -0
  145. package/dist/exchanges/options-interface.js +17 -0
  146. package/dist/exchanges/options-venues.d.ts +12 -0
  147. package/dist/exchanges/options-venues.js +35 -0
  148. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  149. package/dist/exchanges/pacifica-atomic.js +440 -0
  150. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  151. package/dist/exchanges/pacifica-decimal.js +51 -0
  152. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  153. package/dist/exchanges/pacifica-presigned.js +141 -0
  154. package/dist/exchanges/pacifica-print.d.ts +200 -0
  155. package/dist/exchanges/pacifica-print.js +417 -0
  156. package/dist/exchanges/pacifica-spot.d.ts +2 -2
  157. package/dist/exchanges/pacifica-spot.js +3 -3
  158. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  159. package/dist/exchanges/pacifica-typed-data.js +1 -1
  160. package/dist/exchanges/pacifica-vaults.d.ts +121 -0
  161. package/dist/exchanges/pacifica-vaults.js +264 -0
  162. package/dist/exchanges/pacifica.d.ts +32 -5
  163. package/dist/exchanges/pacifica.js +114 -48
  164. package/dist/exchanges/precision.d.ts +3 -1
  165. package/dist/exchanges/precision.js +9 -2
  166. package/dist/exchanges/qfex/adapter.d.ts +18 -5
  167. package/dist/exchanges/qfex/adapter.js +32 -12
  168. package/dist/exchanges/qfex/agent.d.ts +1 -1
  169. package/dist/exchanges/qfex/agent.js +1 -1
  170. package/dist/exchanges/qfex/client.d.ts +1 -1
  171. package/dist/exchanges/qfex/client.js +1 -1
  172. package/dist/exchanges/qfex/errors.d.ts +1 -1
  173. package/dist/exchanges/qfex/errors.js +2 -2
  174. package/dist/exchanges/qfex/public.d.ts +3 -3
  175. package/dist/exchanges/qfex/public.js +3 -3
  176. package/dist/exchanges/qfex/signing.d.ts +2 -2
  177. package/dist/exchanges/qfex/signing.js +4 -5
  178. package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
  179. package/dist/exchanges/qfex/ws-transport.js +3 -3
  180. package/dist/exchanges/rate-limiter.d.ts +1 -1
  181. package/dist/exchanges/rate-limiter.js +5 -2
  182. package/dist/exchanges/registry.d.ts +1 -1
  183. package/dist/exchanges/registry.js +1 -1
  184. package/dist/exchanges/risex/adapter.d.ts +148 -0
  185. package/dist/exchanges/risex/adapter.js +796 -0
  186. package/dist/exchanges/risex/agent.d.ts +154 -0
  187. package/dist/exchanges/risex/agent.js +384 -0
  188. package/dist/exchanges/risex/client.d.ts +47 -0
  189. package/dist/exchanges/risex/client.js +174 -0
  190. package/dist/exchanges/risex/nonce.d.ts +43 -0
  191. package/dist/exchanges/risex/nonce.js +212 -0
  192. package/dist/exchanges/risex/public.d.ts +64 -0
  193. package/dist/exchanges/risex/public.js +150 -0
  194. package/dist/exchanges/risex/session.d.ts +71 -0
  195. package/dist/exchanges/risex/session.js +171 -0
  196. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  197. package/dist/exchanges/risex/typed-data.js +240 -0
  198. package/dist/exchanges/risex/units.d.ts +29 -0
  199. package/dist/exchanges/risex/units.js +104 -0
  200. package/dist/exchanges/slippage.d.ts +1 -1
  201. package/dist/exchanges/slippage.js +1 -1
  202. package/dist/exchanges/spot-factory.d.ts +3 -3
  203. package/dist/exchanges/spot-factory.js +7 -7
  204. package/dist/exchanges/spot-interface.d.ts +5 -5
  205. package/dist/exchanges/spot-interface.js +2 -2
  206. package/dist/exchanges/spot-result.d.ts +3 -3
  207. package/dist/exchanges/spot-result.js +3 -3
  208. package/dist/exchanges/trading-v2.d.ts +3 -3
  209. package/dist/exchanges/trading-v2.js +9 -6
  210. package/dist/exchanges/txflow/adapter.d.ts +54 -0
  211. package/dist/exchanges/txflow/adapter.js +118 -0
  212. package/dist/exchanges/txflow/client.d.ts +47 -0
  213. package/dist/exchanges/txflow/client.js +153 -0
  214. package/dist/exchanges/txflow/public.d.ts +76 -0
  215. package/dist/exchanges/txflow/public.js +284 -0
  216. package/dist/exchanges/unknown-symbol.d.ts +29 -0
  217. package/dist/exchanges/unknown-symbol.js +51 -0
  218. package/dist/exchanges/variational/adapter.d.ts +71 -0
  219. package/dist/exchanges/variational/adapter.js +137 -0
  220. package/dist/exchanges/variational/client.d.ts +21 -0
  221. package/dist/exchanges/variational/client.js +66 -0
  222. package/dist/exchanges/variational/public.d.ts +126 -0
  223. package/dist/exchanges/variational/public.js +430 -0
  224. package/dist/exchanges/venue-types.d.ts +6 -7
  225. package/dist/exchanges/venue-types.js +2 -2
  226. package/dist/exchanges/venue.d.ts +51 -8
  227. package/dist/exchanges/venue.js +54 -4
  228. package/dist/exchanges/venues/arcus.d.ts +1 -2
  229. package/dist/exchanges/venues/arcus.js +5 -4
  230. package/dist/exchanges/venues/aster.d.ts +2 -2
  231. package/dist/exchanges/venues/aster.js +6 -4
  232. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  233. package/dist/exchanges/venues/create-helpers.js +3 -3
  234. package/dist/exchanges/venues/extended.d.ts +21 -0
  235. package/dist/exchanges/venues/extended.js +96 -0
  236. package/dist/exchanges/venues/grvt.d.ts +30 -0
  237. package/dist/exchanges/venues/grvt.js +98 -0
  238. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  239. package/dist/exchanges/venues/hyperliquid.js +10 -8
  240. package/dist/exchanges/venues/index.d.ts +7 -2
  241. package/dist/exchanges/venues/index.js +8 -2
  242. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  243. package/dist/exchanges/venues/lighter-family.js +9 -11
  244. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  245. package/dist/exchanges/venues/lighter-rh.js +7 -5
  246. package/dist/exchanges/venues/lighter.d.ts +1 -1
  247. package/dist/exchanges/venues/lighter.js +5 -3
  248. package/dist/exchanges/venues/ondo.d.ts +1 -1
  249. package/dist/exchanges/venues/ondo.js +5 -3
  250. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  251. package/dist/exchanges/venues/pacifica.js +6 -4
  252. package/dist/exchanges/venues/qfex.d.ts +1 -1
  253. package/dist/exchanges/venues/qfex.js +4 -2
  254. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  255. package/dist/exchanges/venues/risex-release.js +14 -0
  256. package/dist/exchanges/venues/risex.d.ts +31 -0
  257. package/dist/exchanges/venues/risex.js +116 -0
  258. package/dist/exchanges/venues/txflow.d.ts +18 -0
  259. package/dist/exchanges/venues/txflow.js +77 -0
  260. package/dist/exchanges/venues/variational.d.ts +21 -0
  261. package/dist/exchanges/venues/variational.js +85 -0
  262. package/dist/execution-log.d.ts +1 -1
  263. package/dist/funding/legacy-public.d.ts +1 -1
  264. package/dist/funding/legacy-public.js +9 -4
  265. package/dist/funding/normalize.d.ts +1 -1
  266. package/dist/funding/normalize.js +1 -1
  267. package/dist/funding/rates.d.ts +44 -4
  268. package/dist/funding/rates.js +56 -12
  269. package/dist/index.js +7 -6
  270. package/dist/jobs.d.ts +1 -0
  271. package/dist/jobs.js +25 -1
  272. package/dist/landing.d.ts +2 -0
  273. package/dist/landing.js +1 -1
  274. package/dist/mcp-pacifica-earn.d.ts +9 -0
  275. package/dist/mcp-pacifica-earn.js +58 -0
  276. package/dist/mcp-server.js +113 -59
  277. package/dist/pacifica/client.d.ts +67 -4
  278. package/dist/pacifica/client.js +170 -8
  279. package/dist/pacifica/rate-limit.d.ts +144 -0
  280. package/dist/pacifica/rate-limit.js +267 -0
  281. package/dist/pacifica/types/order.d.ts +2 -1
  282. package/dist/plan-executor.js +7 -2
  283. package/dist/rebalance.js +1 -1
  284. package/dist/risk.d.ts +2 -1
  285. package/dist/risk.js +2 -4
  286. package/dist/settings.d.ts +32 -3
  287. package/dist/signer/ows-evm.d.ts +1 -1
  288. package/dist/signer/ows-evm.js +1 -1
  289. package/dist/smart-order.d.ts +1 -1
  290. package/dist/smart-order.js +1 -1
  291. package/dist/strategies/funding-arb.d.ts +1 -1
  292. package/dist/strategies/funding-arb.js +1 -1
  293. package/dist/trade-validator.d.ts +1 -1
  294. package/dist/trade-validator.js +9 -4
  295. package/dist/utils.d.ts +12 -3
  296. package/dist/utils.js +11 -3
  297. package/dist/venue-health.d.ts +40 -0
  298. package/dist/venue-health.js +75 -0
  299. package/dist/withdraw-gate.d.ts +1 -1
  300. package/dist/withdraw-gate.js +1 -1
  301. package/dist/ws/index.d.ts +959 -0
  302. package/dist/ws/index.js +4295 -0
  303. package/dist/ws/node.d.ts +975 -0
  304. package/dist/ws/node.js +4366 -0
  305. package/dist/ws/pacifica-instrument.d.ts +8 -0
  306. package/dist/ws/pacifica-instrument.js +8 -0
  307. package/dist/ws-cli/credentials.d.ts +62 -0
  308. package/dist/ws-cli/credentials.js +126 -0
  309. package/package.json +58 -10
  310. package/skills/perp-cli/SKILL.md +62 -9
  311. package/skills/perp-cli/references/agent-operations.md +7 -0
  312. package/skills/perp-cli/references/commands.md +76 -5
  313. package/skills/perp-cli/references/ws-library.md +492 -0
@@ -99,9 +99,17 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
99
99
  .description("Create a new vault (lake)")
100
100
  .option("--manager <address>", "Manager address (omit to create a fresh vault with no manager)")
101
101
  .option("--nickname <name>", "Display name for the vault")
102
- .option("--initial-deposit <amount>", "Seed deposit in USDC from the creator")
102
+ .option("--initial-deposit <amount>", "Seed deposit in USDC from the creator (master-signed only: refused with an agent key)")
103
103
  .action(async (opts) => {
104
104
  const a = await pac();
105
+ // A seed deposit moves funds into a vault like `vault deposit`: master-signed only (an agent
106
+ // key must not be able to fund a vault it names the manager of).
107
+ if (opts.initialDeposit !== undefined && a.activeSignerTier !== "master" && a.activeSignerTier !== "pk") {
108
+ throw new PerpError("NO_SIGNER_AVAILABLE", "A vault seed deposit (--initial-deposit) must be signed by the master wallet, not the agent key", {
109
+ exchange: "pacifica",
110
+ remediation: "Re-run with --no-agent (master wallet), or create the vault without --initial-deposit and deposit with: perp -e pacifica vault deposit <vault> <amount>",
111
+ });
112
+ }
105
113
  const result = await a.sdk.createLake({
106
114
  manager: opts.manager === undefined ? null : opts.manager,
107
115
  nickname: opts.nickname === undefined ? null : opts.nickname,
@@ -112,26 +120,20 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
112
120
  console.log(chalk.green(`\n Vault created.\n`));
113
121
  console.log(JSON.stringify(result, null, 2));
114
122
  });
115
- lake
116
- .command("deposit <lake> <amount>")
117
- .description("Deposit USDC into a vault (<lake> = vault address)")
118
- .action(async (lakeAddress, amount) => {
119
- const a = await pac();
120
- const result = await a.sdk.depositToLake({ lake: lakeAddress, amount: parsePositiveDecimal(amount, "deposit amount") }, a.publicKey, a.signer);
121
- if (isJson())
122
- return printJson(jsonOk(result));
123
- console.log(chalk.green(`\n Deposited $${amount} to vault ${lakeAddress}.\n`));
124
- });
125
- lake
126
- .command("withdraw <lake> <shares>")
127
- .description("Redeem vault shares (<lake> = vault address, <shares> = number of shares, not USDC)")
128
- .action(async (lakeAddress, shares) => {
129
- const a = await pac();
130
- const result = await a.sdk.withdrawFromLake({ lake: lakeAddress, shares: parsePositiveDecimal(shares, "shares") }, a.publicKey, a.signer);
131
- if (isJson())
132
- return printJson(jsonOk(result));
133
- console.log(chalk.green(`\n Redeemed ${shares} shares from vault ${lakeAddress}.\n`));
134
- });
123
+ // deposit / withdraw moved to the validated path (`perp -e pacifica vault deposit|withdraw`):
124
+ // nickname / address resolution, documented limits, --dry-run, unknown-outcome handling, and a
125
+ // master-only vault deposit. These stubs refuse before any key is loaded or anything is signed.
126
+ for (const [sub, args, target] of [["deposit", "<lake> <amount>", "vault deposit <vault> <amount>"], ["withdraw", "<lake> <shares>", "vault withdraw <vault> <shares>|--all"]]) {
127
+ lake
128
+ .command(`${sub} ${args}`)
129
+ .description(`Moved: use perp -e pacifica ${target} (validated, supports --dry-run)`)
130
+ .action(() => {
131
+ throw new PerpError("INVALID_PARAMS", `wallet manage lake ${sub} was replaced by: perp -e pacifica ${target}`, {
132
+ exchange: "pacifica",
133
+ remediation: `Run: perp -e pacifica --dry-run ${target} — then without --dry-run (nothing was signed or sent)`,
134
+ });
135
+ });
136
+ }
135
137
  // Pacifica builder-OWNER tools. A user's builder approval / revoke / status is
136
138
  // `perp wallet builder approve|revoke|status <venue>` (settings builder.<venue>).
137
139
  const builder = manage.command("builder").description("Pacifica builder-owner tools (overview, trades, leaderboard, update-fee). Approve / revoke / status: perp wallet builder");
@@ -1,33 +1,122 @@
1
- import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
1
+ import { tapeSkippedOf } from "../exchanges/interface.js";
2
+ import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors, normalizeSymbolArg } from "../utils.js";
2
3
  import chalk from "chalk";
3
4
  import { PerpError } from "../errors.js";
4
5
  import { midDecimal } from "../exchanges/precision.js";
5
6
  import { hasPacificaSdk, isDexCapable, klinesNeedAuth } from "../exchanges/capabilities.js";
6
- import { listVenuesWithRole } from "../exchanges/venue.js";
7
+ import { getVenue, listVenuesWithRole, tryResolveVenueId, unofficialApiNote } from "../exchanges/venue.js";
7
8
  import "../exchanges/venues/index.js";
8
9
  /** Venues with an adapter (market-data-only venues have none — see `perp arb scan`). */
9
10
  const EXCHANGES = listVenuesWithRole("account").map(v => v.id);
10
11
  /** null = venue does not publish the value → "-" (never rendered as $0). */
11
12
  const usdOrDash = (v) => (v === null ? chalk.gray("-") : `$${formatUsd(v)}`);
13
+ /**
14
+ * Funding cell. On a venue whose funding interval is per market (VenueDefinition.fundingRateBasis
15
+ * "perMarket": Variational, Aster, GRVT) the raw rate is labelled with its interval ("+0.0079%/8h"), since
16
+ * the rows of one table cover different periods.
17
+ */
18
+ function fundingCell(m, perMarket) {
19
+ if (m.fundingRate == null)
20
+ return chalk.gray("N/A");
21
+ const hours = perMarket ? m.fundingHours ?? null : null;
22
+ return hours === null ? formatPercent(m.fundingRate) : `${formatPercent(m.fundingRate)}${chalk.gray(`/${hours}h`)}`;
23
+ }
24
+ /** True when `venueName` is a registered venue whose funding interval is per market. */
25
+ function perMarketFunding(venueName) {
26
+ const id = tryResolveVenueId(venueName);
27
+ return id !== null && getVenue(id).fundingRateBasis === "perMarket";
28
+ }
29
+ /** Base asset of a market symbol: "BTC-PERP" → "BTC", "xyz:XYZ100" → "XYZ100". */
30
+ const baseAssetOf = (symbol) => symbol.split(":").pop().replace(/-PERP$/i, "");
31
+ /**
32
+ * Open interest is published in BASE-asset units by every venue (ExchangeMarketInfo.openInterest),
33
+ * so it is shown as "<n> <BASE>" — never with a "$". `withUsd` adds the notional at the mark
34
+ * price, labelled as such; null (not published) → "-".
35
+ */
36
+ function formatOpenInterest(m, withUsd) {
37
+ if (m.openInterest === null)
38
+ return chalk.gray("-");
39
+ const oi = Number(m.openInterest);
40
+ const base = `${Number.isFinite(oi) ? oi.toLocaleString("en-US", { maximumFractionDigits: 4 }) : m.openInterest} ${baseAssetOf(m.symbol)}`;
41
+ const mark = Number(m.markPrice);
42
+ if (!withUsd || !Number.isFinite(oi) || !(mark > 0))
43
+ return base;
44
+ return `${base} ${chalk.gray(`(≈ $${formatUsd(oi * mark)} at mark)`)}`;
45
+ }
46
+ function printRfqQuotes(q, json) {
47
+ if (json)
48
+ return printJson(jsonOk(q));
49
+ console.log(chalk.cyan.bold(`\n RFQ quotes: ${q.ticker} (${q.venue}) — not an order book\n`));
50
+ console.log(` Mark: $${formatUsd(q.markPrice)} Base spread: ${q.baseSpreadBps === null ? chalk.gray("-") : `${Number(q.baseSpreadBps).toFixed(2)} bps`} Quoted at: ${q.updatedAt ?? chalk.gray("-")}`);
51
+ if (q.quotes.length === 0) {
52
+ console.log(chalk.gray("\n No quotes published right now (the market may be closed).\n"));
53
+ }
54
+ else {
55
+ console.log(makeTable(["Size", "Bid", "Ask", "Spread"], q.quotes.map(t => {
56
+ const bid = Number(t.bid);
57
+ const ask = Number(t.ask);
58
+ const mid = (bid + ask) / 2;
59
+ return [
60
+ t.notionalUsd === null ? "min order" : `$${formatUsd(t.notionalUsd)}`,
61
+ chalk.green(`$${formatUsd(t.bid)}`),
62
+ chalk.red(`$${formatUsd(t.ask)}`),
63
+ mid > 0 ? `${((ask - bid) / mid * 10_000).toFixed(2)} bps` : chalk.gray("-"),
64
+ ];
65
+ })));
66
+ }
67
+ console.log(chalk.gray(` ${q.note}\n`));
68
+ }
69
+ function printFundingTerms(t, json) {
70
+ if (json)
71
+ return printJson(jsonOk(t));
72
+ console.log(chalk.cyan.bold(`\n Funding: ${t.ticker} (${t.venue}) — current terms, no history published\n`));
73
+ if (t.swap !== null) {
74
+ const row = (label, r) => r === null
75
+ ? [label, chalk.gray("-"), chalk.gray("-"), chalk.gray("-"), chalk.gray("-")]
76
+ : [label, formatPercent(r.intervalLong), formatPercent(r.intervalShort), formatPercent(r.annualizedLong), formatPercent(r.annualizedShort)];
77
+ console.log(makeTable(["Payment", "Long / day", "Short / day", "Long APR", "Short APR"], [row("last applied", t.swap.last), row("next", t.swap.next)]));
78
+ }
79
+ else {
80
+ console.log(` Rate kind: ${t.rateKind ?? chalk.gray("-")}`);
81
+ console.log(` Interval: ${t.intervalHours === null ? chalk.gray("-") : `${t.intervalHours}h`}`);
82
+ console.log(` Per interval: ${t.intervalRate === null ? chalk.gray("N/A") : formatPercent(t.intervalRate)}`);
83
+ console.log(` Hourly: ${t.hourlyRate === null ? chalk.gray("N/A") : formatPercent(t.hourlyRate)}`);
84
+ console.log(` APR: ${t.annualizedPct === null ? chalk.gray("N/A") : `${t.annualizedPct.toFixed(2)}%`}`);
85
+ }
86
+ console.log(` Next funding: ${t.nextFundingTime === null ? chalk.gray("-") : new Date(t.nextFundingTime).toLocaleString()}`);
87
+ console.log(chalk.gray(` ${t.note}\n`));
88
+ }
12
89
  export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange,
13
90
  /** Credentialed adapter of the selected venue — for market data the venue serves only to an authenticated account. */
14
91
  getAccountAdapter) {
15
92
  const market = program.command("market").description("Market data commands");
93
+ // Human output for a venue read through an UNOFFICIAL API carries a footnote (JSON rows carry venueApi).
94
+ market.hook("postAction", () => {
95
+ if (isJson())
96
+ return;
97
+ const raw = program.opts().exchange;
98
+ const id = raw === undefined ? null : tryResolveVenueId(raw);
99
+ if (id === null)
100
+ return;
101
+ const note = unofficialApiNote(getVenue(id));
102
+ if (note !== null)
103
+ console.log(chalk.yellow(` * ${note}\n`));
104
+ });
16
105
  market
17
106
  .command("list")
18
107
  .description("List all available markets")
19
108
  .option("--hip3", "Include HIP-3 dex markets (Hyperliquid)")
20
109
  .action(async (opts) => {
21
110
  const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
22
- const HEADERS = ["Symbol", "Mark", "Index", "Funding", "24h Vol", "OI", "Max Lev"];
23
- const marketRow = (m) => [
111
+ const HEADERS = ["Symbol", "Mark", "Index", "Funding", "24h Vol", "OI (base)", "Max Lev"];
112
+ const marketRow = (m, perMarket = false) => [
24
113
  chalk.white.bold(m.symbol),
25
114
  `$${formatUsd(m.markPrice)}`,
26
115
  usdOrDash(m.indexPrice),
27
- m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A"),
116
+ fundingCell(m, perMarket),
28
117
  usdOrDash(m.volume24h),
29
- usdOrDash(m.openInterest),
30
- String(m.maxLeverage) + "x",
118
+ formatOpenInterest(m, false),
119
+ m.maxLeverage === null ? chalk.gray("-") : String(m.maxLeverage) + "x",
31
120
  ];
32
121
  // Multi-exchange mode
33
122
  if (!explicitExchange && getAdapterForExchange) {
@@ -63,7 +152,7 @@ getAccountAdapter) {
63
152
  const isHip3 = ex.startsWith("hip3:");
64
153
  const label = isHip3 ? chalk.magenta.bold(` HIP-3: ${ex.slice(5)}`) : chalk.cyan.bold(` ${ex.toUpperCase()}`);
65
154
  console.log(`\n${label} ${chalk.gray(`(${markets.length} markets)`)}`);
66
- console.log(makeTable(HEADERS, markets.map(marketRow)));
155
+ console.log(makeTable(HEADERS, markets.map(m => marketRow(m))));
67
156
  }
68
157
  for (const [ex, msg] of Object.entries(errors)) {
69
158
  console.log(chalk.gray(` ${ex}: ${msg}`));
@@ -91,10 +180,21 @@ getAccountAdapter) {
91
180
  }
92
181
  return printJson(jsonOk(markets));
93
182
  }
94
- console.log(makeTable(HEADERS, markets.map(marketRow)));
183
+ const perMarket = perMarketFunding(adapter.name);
184
+ // Rows that carry an asset class (Extended: crypto / equity / etf / index / commodity / fx / preIpo) show it.
185
+ const classOf = (m) => {
186
+ const c = m.assetClass;
187
+ return typeof c === "string" ? c : null;
188
+ };
189
+ if (markets.length > 0 && markets.every(m => classOf(m) !== null)) {
190
+ console.log(makeTable([...HEADERS, "Class"], markets.map(m => [...marketRow(m, perMarket), classOf(m)])));
191
+ }
192
+ else {
193
+ console.log(makeTable(HEADERS, markets.map(m => marketRow(m, perMarket))));
194
+ }
95
195
  for (const [dex, m] of Object.entries(hip3)) {
96
196
  console.log(chalk.magenta.bold(`\n HIP-3: ${dex}`) + chalk.gray(` (${m.length} markets)`));
97
- console.log(makeTable(HEADERS, m.map(marketRow)));
197
+ console.log(makeTable(HEADERS, m.map(x => marketRow(x))));
98
198
  }
99
199
  });
100
200
  market
@@ -103,11 +203,11 @@ getAccountAdapter) {
103
203
  .action(async () => {
104
204
  const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
105
205
  const PRICE_HEADERS = ["Symbol", "Mark", "Index", "Funding"];
106
- const priceRow = (m) => [
206
+ const priceRow = (m, perMarket = false) => [
107
207
  chalk.white.bold(m.symbol),
108
208
  `$${formatUsd(m.markPrice)}`,
109
209
  usdOrDash(m.indexPrice),
110
- m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A"),
210
+ fundingCell(m, perMarket),
111
211
  ];
112
212
  if (!explicitExchange && getAdapterForExchange) {
113
213
  const grouped = {};
@@ -128,7 +228,7 @@ getAccountAdapter) {
128
228
  if (markets.length === 0)
129
229
  continue;
130
230
  console.log(chalk.cyan.bold(`\n ${ex.toUpperCase()}`) + chalk.gray(` (${markets.length} markets)`));
131
- console.log(makeTable(PRICE_HEADERS, markets.map(priceRow)));
231
+ console.log(makeTable(PRICE_HEADERS, markets.map(m => priceRow(m))));
132
232
  }
133
233
  for (const [ex, msg] of Object.entries(errors))
134
234
  console.log(chalk.gray(` ${ex}: ${msg}`));
@@ -153,7 +253,7 @@ getAccountAdapter) {
153
253
  const markets = await adapter.getMarkets();
154
254
  if (isJson())
155
255
  return printJson(jsonOk(markets));
156
- console.log(makeTable(PRICE_HEADERS, markets.map(priceRow)));
256
+ console.log(makeTable(PRICE_HEADERS, markets.map(m => priceRow(m, perMarketFunding(adapter.name)))));
157
257
  }
158
258
  });
159
259
  market
@@ -161,6 +261,17 @@ getAccountAdapter) {
161
261
  .description("Detailed info for a single market")
162
262
  .action(async (symbol) => {
163
263
  const adapter = await getAdapter();
264
+ // A venue that serves one market directly (GRVT: one ticker + one margin-rules read instead
265
+ // of the whole fan-out) answers here; SYMBOL_NOT_FOUND comes from its own directory.
266
+ const single = adapter.getMarketInfo;
267
+ if (typeof single === "function") {
268
+ return void await withJsonErrors(isJson(), async () => {
269
+ const one = await single.call(adapter, symbol);
270
+ if (isJson())
271
+ return printJson(jsonOk(one));
272
+ printMarketInfo(adapter.name, one);
273
+ });
274
+ }
164
275
  const markets = await adapter.getMarkets();
165
276
  const sym = symbol.toUpperCase();
166
277
  const m = markets.find((mk) => {
@@ -169,21 +280,20 @@ getAccountAdapter) {
169
280
  || s.split(":").pop() === sym; // HIP-3: "km:US500" matches "US500"
170
281
  });
171
282
  if (!m) {
172
- if (isJson())
173
- return printJson(jsonError("SYMBOL_NOT_FOUND", `Market ${sym} not found`));
174
- console.log(chalk.red(`\n Market "${sym}" not found.\n`));
175
- return process.exit(1);
283
+ // Printed (envelope / Error + Remediation) and exit 1 via withJsonErrors.
284
+ return void await withJsonErrors(isJson(), async () => {
285
+ const delisted = typeof adapter.delistedMessage === "function"
286
+ ? adapter.delistedMessage(symbol)
287
+ : null;
288
+ throw new PerpError("SYMBOL_NOT_FOUND", delisted ?? `Market ${normalizeSymbolArg(symbol)} not found on ${adapter.name}`, {
289
+ exchange: adapter.name,
290
+ remediation: `List markets: perp -e ${adapter.name} market list`,
291
+ });
292
+ });
176
293
  }
177
294
  if (isJson())
178
295
  return printJson(jsonOk(m));
179
- console.log(chalk.cyan.bold(`\n ${m.symbol} on ${adapter.name}\n`));
180
- console.log(` Mark Price: $${formatUsd(m.markPrice)}`);
181
- console.log(` Index Price: ${usdOrDash(m.indexPrice)}`);
182
- console.log(` Funding Rate: ${m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A")}`);
183
- console.log(` 24h Volume: ${usdOrDash(m.volume24h)}`);
184
- console.log(` Open Interest: ${usdOrDash(m.openInterest)}`);
185
- console.log(` Max Leverage: ${m.maxLeverage}x`);
186
- console.log();
296
+ printMarketInfo(adapter.name, m);
187
297
  });
188
298
  market
189
299
  .command("book <symbol>")
@@ -191,7 +301,11 @@ getAccountAdapter) {
191
301
  .option("-d, --depth <n>", "Number of levels", "10")
192
302
  .action(async (symbol, opts) => {
193
303
  const adapter = await getAdapter();
194
- const book = await adapter.getOrderbook(symbol.toUpperCase());
304
+ const sym = normalizeSymbolArg(symbol);
305
+ // RFQ venue (Variational): no order book — the maker's indicative quotes, labelled as such.
306
+ if (adapter.getRfqQuotes !== undefined)
307
+ return printRfqQuotes(await adapter.getRfqQuotes(sym), isJson());
308
+ const book = await adapter.getOrderbook(sym);
195
309
  if (isJson())
196
310
  return printJson(jsonOk(book));
197
311
  const depth = parseInt(opts.depth);
@@ -214,7 +328,7 @@ getAccountAdapter) {
214
328
  const len = maxCum > 0 ? Math.round((cum / maxCum) * BAR_WIDTH) : 0;
215
329
  return "█".repeat(len);
216
330
  };
217
- console.log(chalk.cyan.bold(`\n Orderbook: ${symbol.toUpperCase()} (${adapter.name})\n`));
331
+ console.log(chalk.cyan.bold(`\n Orderbook: ${sym} (${adapter.name})\n`));
218
332
  console.log(chalk.gray(" Price Size Cum$ Depth"));
219
333
  console.log(chalk.gray(" " + "─".repeat(60)));
220
334
  asks.forEach(([p, a], i) => {
@@ -240,9 +354,12 @@ getAccountAdapter) {
240
354
  .option("--limit <n>", "Number of recent trades to show", "20")
241
355
  .action(async (symbol, opts) => {
242
356
  const adapter = await getAdapter();
243
- const trades = await adapter.getRecentTrades(symbol.toUpperCase(), parseInt(opts.limit));
357
+ const trades = await adapter.getRecentTrades(normalizeSymbolArg(symbol), parseInt(opts.limit));
358
+ const skipped = tapeSkippedOf(trades);
244
359
  if (isJson())
245
- return printJson(jsonOk(trades));
360
+ return printJson(jsonOk(trades, skipped !== null ? { skippedRows: skipped } : undefined));
361
+ if (skipped !== null)
362
+ console.log(chalk.yellow(` ${skipped.count} tape row(s) skipped: ${skipped.reason}`));
246
363
  if (trades.length === 0) {
247
364
  console.log(chalk.gray("\n No recent trades.\n"));
248
365
  return;
@@ -261,7 +378,10 @@ getAccountAdapter) {
261
378
  .option("-l, --limit <n>", "Number of records", "10")
262
379
  .action(async (symbol, opts) => {
263
380
  const adapter = await getAdapter();
264
- const history = await adapter.getFundingHistory(symbol.toUpperCase(), parseInt(opts.limit));
381
+ // A venue without a funding-history API (Variational): its current funding terms, labelled as such.
382
+ if (adapter.getFundingTerms !== undefined)
383
+ return printFundingTerms(await adapter.getFundingTerms(normalizeSymbolArg(symbol)), isJson());
384
+ const history = await adapter.getFundingHistory(normalizeSymbolArg(symbol), parseInt(opts.limit));
265
385
  if (isJson())
266
386
  return printJson(jsonOk(history));
267
387
  if (history.length === 0) {
@@ -287,7 +407,7 @@ getAccountAdapter) {
287
407
  .command("mid <symbol>")
288
408
  .description("Get mid price for a single symbol (fast)")
289
409
  .action(async (symbol) => {
290
- const sym = symbol.toUpperCase();
410
+ const sym = normalizeSymbolArg(symbol);
291
411
  // withJsonErrors: every failure (incl. an empty book) is an error envelope AND exit code 1.
292
412
  await withJsonErrors(isJson(), async () => {
293
413
  const adapter = await getAdapter();
@@ -337,21 +457,22 @@ getAccountAdapter) {
337
457
  const now = Date.now();
338
458
  const startTime = opts.start ? parseInt(opts.start) : now - 24 * 60 * 60 * 1000;
339
459
  const endTime = opts.end ? parseInt(opts.end) : now;
340
- const klines = await adapter.getKlines(symbol.toUpperCase(), interval, startTime, endTime);
460
+ const klines = await adapter.getKlines(normalizeSymbolArg(symbol), interval, startTime, endTime);
341
461
  if (isJson())
342
462
  return printJson(jsonOk(klines));
343
463
  if (klines.length === 0) {
344
464
  console.log(chalk.gray("\n No kline data.\n"));
345
465
  return;
346
466
  }
467
+ // ISO (UTC) candle open times — one format for every venue, independent of the host locale.
347
468
  const rows = klines.slice(-20).map((k) => [
348
- new Date(k.time).toLocaleString(),
469
+ new Date(k.time).toISOString(),
349
470
  `$${formatUsd(k.open)}`,
350
471
  `$${formatUsd(k.high)}`,
351
472
  `$${formatUsd(k.low)}`,
352
473
  `$${formatUsd(k.close)}`,
353
- k.volume,
354
- String(k.trades),
474
+ k.volume === null ? chalk.gray("-") : k.volume,
475
+ k.trades === null ? chalk.gray("-") : String(k.trades),
355
476
  ]);
356
477
  console.log(makeTable(["Time", "Open", "High", "Low", "Close", "Volume", "Trades"], rows));
357
478
  });
@@ -389,3 +510,14 @@ getAccountAdapter) {
389
510
  });
390
511
  });
391
512
  }
513
+ function printMarketInfo(venue, m) {
514
+ console.log(chalk.cyan.bold(`\n ${m.symbol} on ${venue}\n`));
515
+ console.log(` Mark Price: $${formatUsd(m.markPrice)}`);
516
+ console.log(` Index Price: ${usdOrDash(m.indexPrice)}`);
517
+ // Per-market funding interval (GRVT 4h / 8h, Variational, Aster): the rate carries its interval.
518
+ console.log(` Funding Rate: ${fundingCell(m, perMarketFunding(venue))}`);
519
+ console.log(` 24h Volume: ${usdOrDash(m.volume24h)}`);
520
+ console.log(` Open Interest: ${formatOpenInterest(m, true)}`);
521
+ console.log(` Max Leverage: ${m.maxLeverage === null ? chalk.gray("-") : `${m.maxLeverage}x`}`);
522
+ console.log();
523
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `perp options` — read-only options market data (`-e derive`).
3
+ *
4
+ * Public, keyless reads through the options venue registry (src/exchanges/options-venues.ts);
5
+ * no orders, no account. JSON shapes: src/exchanges/options-interface.ts (`schema: 1`).
6
+ * The venue is the root `-e`; a perp venue there (or no `-e`) is CAPABILITY_UNSUPPORTED.
7
+ */
8
+ import { Command } from "commander";
9
+ export declare function registerOptionsCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * `perp options` — read-only options market data (`-e derive`).
3
+ *
4
+ * Public, keyless reads through the options venue registry (src/exchanges/options-venues.ts);
5
+ * no orders, no account. JSON shapes: src/exchanges/options-interface.ts (`schema: 1`).
6
+ * The venue is the root `-e`; a perp venue there (or no `-e`) is CAPABILITY_UNSUPPORTED.
7
+ */
8
+ import chalk from "chalk";
9
+ import { networkOf } from "../cli-flags.js";
10
+ import { PerpError } from "../errors.js";
11
+ import { getOptionsVenue, listOptionsVenueIds } from "../exchanges/options-venues.js";
12
+ import { makeTable, printJson, jsonOk } from "../utils.js";
13
+ export function registerOptionsCommands(program, isJson) {
14
+ const options = program
15
+ .command("options")
16
+ .description(`Options market data, read-only (-e ${listOptionsVenueIds().join(" | -e ")}): expiries, chain, greeks, IV`);
17
+ async function venue() {
18
+ if (program.getOptionValueSource("exchange") !== "cli") {
19
+ throw new PerpError("CAPABILITY_UNSUPPORTED", "perp options needs an options venue in -e", {
20
+ remediation: `Use -e ${listOptionsVenueIds()[0]} (e.g. perp -e ${listOptionsVenueIds()[0]} options expiries ETH)`,
21
+ });
22
+ }
23
+ return getOptionsVenue(String(program.opts().exchange), networkOf(program));
24
+ }
25
+ // ── options underlyings ──────────────────────────────────────────────────
26
+ options
27
+ .command("underlyings")
28
+ .description("List underlyings that have options on the venue")
29
+ .action(async () => {
30
+ const data = await (await venue()).listUnderlyings();
31
+ if (isJson())
32
+ return printJson(jsonOk(data));
33
+ const rows = data.underlyings.map(u => [chalk.white.bold(u.underlying), u.spotPrice === null ? chalk.gray("—") : fmtPx(u.spotPrice)]);
34
+ console.log(`\n ${chalk.white.bold(`${data.venue} options underlyings`)}\n` + makeTable(["underlying", "spot"], rows) + "\n");
35
+ });
36
+ // ── options expiries <underlying> ────────────────────────────────────────
37
+ options
38
+ .command("expiries <underlying>")
39
+ .description("List active option expiries of an underlying (e.g. ETH)")
40
+ .action(async (underlying) => {
41
+ const data = await (await venue()).listExpiries(underlying);
42
+ if (isJson())
43
+ return printJson(jsonOk(data));
44
+ const rows = data.expiries.map(e => [
45
+ chalk.white.bold(e.expiry),
46
+ new Date(e.expiryMs).toISOString().slice(11, 16) + " UTC",
47
+ String(e.strikes),
48
+ String(e.instruments),
49
+ ]);
50
+ console.log(`\n ${chalk.white.bold(`${data.underlying} options (${data.venue})`)}\n` + makeTable(["expiry", "time", "strikes", "instruments"], rows) + "\n");
51
+ });
52
+ // ── options chain <underlying> ───────────────────────────────────────────
53
+ options
54
+ .command("chain <underlying>")
55
+ .description("Option chain for one expiry: calls / puts per strike with bid / ask, mark, IV, greeks, OI")
56
+ .option("--expiry <date>", "Expiry (YYYY-MM-DD, UTC); default: the nearest active expiry")
57
+ .option("--strikes <n>", "Only the N strikes closest to the ATM reference (forward, else index)")
58
+ .action(async (underlying, opts) => {
59
+ const strikes = opts.strikes === undefined ? null : parseStrikes(opts.strikes);
60
+ const chain = await (await venue()).getChain({ underlying, expiry: opts.expiry ?? null, strikesAroundAtm: strikes });
61
+ if (isJson())
62
+ return printJson(jsonOk(chain));
63
+ printChain(chain);
64
+ });
65
+ // ── options quote <instrument> ───────────────────────────────────────────
66
+ options
67
+ .command("quote <instrument>")
68
+ .description("One option: bid / ask (price + size), mark, IV, greeks, forward, index, OI, 24h volume (e.g. ETH-20261225-3500-P)")
69
+ .action(async (instrument) => {
70
+ const q = await (await venue()).getQuote(instrument);
71
+ if (isJson())
72
+ return printJson(jsonOk(q));
73
+ printQuote(q);
74
+ });
75
+ }
76
+ function parseStrikes(raw) {
77
+ const n = Number(raw);
78
+ if (!/^\d+$/.test(raw.trim()) || !Number.isInteger(n) || n < 1) {
79
+ throw new PerpError("INVALID_PARAMS", `--strikes must be a positive integer (got "${raw}")`, {
80
+ remediation: "Pass a positive integer, e.g. --strikes 10",
81
+ });
82
+ }
83
+ return n;
84
+ }
85
+ // ── human output ──
86
+ const dash = chalk.gray("—");
87
+ function fmtPx(v) {
88
+ return v === null ? dash : String(Number(Number(v).toPrecision(8)));
89
+ }
90
+ function fmtPct(v) {
91
+ return v === null ? dash : `${(Number(v) * 100).toFixed(1)}%`;
92
+ }
93
+ function fmtGreek(v) {
94
+ return v === null || v === undefined ? dash : Number(v).toFixed(3);
95
+ }
96
+ function sideCells(q) {
97
+ if (q === null)
98
+ return [dash, dash, dash, dash, dash];
99
+ return [
100
+ q.bid === null ? dash : fmtPx(q.bid.price),
101
+ q.ask === null ? dash : fmtPx(q.ask.price),
102
+ fmtPx(q.mark),
103
+ fmtPct(q.iv.mark),
104
+ fmtGreek(q.greeks?.delta),
105
+ ];
106
+ }
107
+ function printChain(c) {
108
+ const ref = c.atm === null ? "ATM unknown" : `ATM ${c.atm.strike} (${c.atm.reference} ${fmtPx(c.atm.price)})`;
109
+ console.log(chalk.white.bold(`\n ${c.underlying} options — ${c.expiry} (${c.venue})`));
110
+ console.log(chalk.gray(` index ${fmtPx(c.index)} · forward ${fmtPx(c.forward)} · ${ref} · ${c.rows.length}/${c.strikesListed} strikes\n`));
111
+ const rows = c.rows.map(r => {
112
+ const strike = c.atm !== null && r.strike === c.atm.strike ? chalk.yellow.bold(r.strike) : chalk.white.bold(r.strike);
113
+ return [...sideCells(r.call), strike, ...sideCells(r.put).reverse()];
114
+ });
115
+ console.log(makeTable(["C bid", "C ask", "C mark", "C IV", "C Δ", "strike", "P Δ", "P IV", "P mark", "P ask", "P bid"], rows));
116
+ if (c.missingTickers.length > 0)
117
+ console.log(chalk.yellow(` no ticker: ${c.missingTickers.join(", ")}`));
118
+ console.log("");
119
+ }
120
+ function printQuote(q) {
121
+ const side = (s) => (s === null ? dash : `${fmtPx(s.price)} × ${s.size}`);
122
+ console.log(chalk.white.bold(`\n ${q.instrument}`) + chalk.gray(` (${q.venue} · ${q.kind} · strike ${q.strike} · expiry ${new Date(q.expiryMs).toISOString().replace("T", " ").slice(0, 16)} UTC)`));
123
+ const rows = [
124
+ ["bid", side(q.bid), "IV", fmtPct(q.iv.bid)],
125
+ ["ask", side(q.ask), "IV", fmtPct(q.iv.ask)],
126
+ ["mark", fmtPx(q.mark), "IV", fmtPct(q.iv.mark)],
127
+ ["index", fmtPx(q.index), "forward", fmtPx(q.forward)],
128
+ ["delta", fmtGreek(q.greeks?.delta), "gamma", q.greeks === null ? dash : q.greeks.gamma],
129
+ ["theta", fmtGreek(q.greeks?.theta), "vega", fmtGreek(q.greeks?.vega)],
130
+ ["rho", fmtGreek(q.greeks?.rho), "OI", q.openInterest],
131
+ ["24h contracts", q.stats24h.contracts, "24h trades", String(q.stats24h.trades)],
132
+ ];
133
+ console.log(makeTable(["field", "value", "field", "value"], rows) + "\n");
134
+ }
@@ -0,0 +1,5 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ type Getter = (exchange: string) => Promise<ExchangeAdapter>;
4
+ export declare function registerPacificaEarnCommands(program: Command, getAdapterForExchange: Getter, isJson: () => boolean): void;
5
+ export {};