perp-cli 0.12.15 → 0.14.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 (361) hide show
  1. package/README.md +278 -31
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +74 -84
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +157 -682
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +14 -0
  287. package/dist/landing.js +36 -0
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +338 -134
  348. package/skills/perp-cli/references/agent-operations.md +181 -92
  349. package/skills/perp-cli/references/commands.md +475 -132
  350. package/skills/perp-cli/references/strategies.md +44 -38
  351. package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
  352. package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +33 -16
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
package/dist/config.d.ts CHANGED
@@ -1,19 +1,70 @@
1
1
  import { Keypair } from "@solana/web3.js";
2
- export type Exchange = "pacifica" | "hyperliquid" | "lighter" | "aster";
2
+ import type { Network, VenueId } from "./exchanges/venue-types.js";
3
+ import type { VenueCredentials } from "./exchanges/venue.js";
4
+ /** @deprecated alias — every venue id is an exchange id (design §10.3). */
5
+ export type Exchange = VenueId;
6
+ /** CLI (and MCP) inputs that select credentials. Absent flag = null. */
7
+ export interface CliCredentialOpts {
8
+ privateKey: string | null;
9
+ /** --wallet <name>: OWS wallet, else local wallets.json entry */
10
+ wallet: string | null;
11
+ /** --ows <name> */
12
+ ows: string | null;
13
+ /** --ows-key <token> (or OWS_API_KEY env) */
14
+ owsKey: string | null;
15
+ /** --passphrase <pp> */
16
+ passphrase: string | null;
17
+ }
18
+ export declare const NO_CLI_CREDENTIAL_OPTS: CliCredentialOpts;
19
+ /** True when the OWS vault holds a wallet with this name (no catch: a broken OWS install throws). */
20
+ export declare function owsWalletExists(walletName: string): boolean;
3
21
  /**
4
- * Try to load a private key without throwing if none is found.
5
- * Returns null if no key is configured.
6
- */
7
- export declare function tryLoadPrivateKey(exchange: Exchange, pkOverride?: string, walletName?: string): Promise<string | null>;
8
- /**
9
- * Resolve an OWS wallet name to a signer-ready marker.
10
- * Returns the OWS wallet name prefixed with "ows:" so callers can detect OWS mode.
22
+ * Resolve an OWS wallet name to a signer-ready marker ("ows:<name>"), or null
23
+ * when the vault has no such wallet.
11
24
  */
12
25
  export declare function resolveOwsWallet(walletName: string): string | null;
13
26
  /** Check if a key string represents an OWS wallet reference. */
14
27
  export declare function isOwsKey(key: string): boolean;
15
28
  /** Extract OWS wallet name from an ows: prefixed key string. */
16
29
  export declare function getOwsWalletName(key: string): string;
17
- export declare function loadPrivateKey(exchange: Exchange, pkOverride?: string, walletName?: string): Promise<string>;
30
+ /** CLI/MCP-shared credential resolution (design §10.2). */
31
+ export declare function resolveCredentials(venue: VenueId, opts: CliCredentialOpts, network: Network): Promise<VenueCredentials>;
32
+ /**
33
+ * Whether any credential source is configured for the venue (explicit checks,
34
+ * no catch). true does not mean the source is valid — creating the adapter
35
+ * surfaces that error.
36
+ */
37
+ export declare function hasCredentials(venue: VenueId, opts: CliCredentialOpts): Promise<boolean>;
38
+ /**
39
+ * Account-role venues (registry order) with a configured credential source —
40
+ * the default set for "all exchanges" aggregate views. Market-data-only venues
41
+ * never qualify.
42
+ */
43
+ export declare function listConfiguredAccountVenues(opts?: CliCredentialOpts): Promise<VenueId[]>;
44
+ /**
45
+ * Master key for a venue as a string: "ows:<name>" for OWS wallets, else the raw key.
46
+ * Throws NO_ENV_TIER for venues without an env/PK tier when nothing else is configured,
47
+ * and a setup hint when no source exists.
48
+ */
49
+ export declare function loadPrivateKey(exchange: VenueId, pkOverride?: string, walletName?: string): Promise<string>;
50
+ /** Root-program option values → credential selectors (shared by the CLI adapter factory and master-only commands). */
51
+ export declare function cliCredentialOptsFrom(rootOpts: Record<string, unknown>): CliCredentialOpts;
52
+ /**
53
+ * Commands that only the account owner can sign (deposit, withdraw, transfer, bridge, referral
54
+ * set, agent approve/revoke): refuse up front with NO_SIGNER_AVAILABLE when no master wallet is
55
+ * configured on this machine — e.g. only an agent / API key imported from the exchange website.
56
+ * Never falls back to the agent key (it cannot sign these). A configured-but-missing master
57
+ * (active wallet not in the vault) throws KEY_NOT_FOUND from resolveMasterKey.
58
+ */
59
+ export declare function requireMasterWallet(venue: VenueId, opts: CliCredentialOpts, action: string): Promise<void>;
60
+ /**
61
+ * The OWS master wallet selected by the CLI flags / active wallet, with the passphrase that opens
62
+ * it — for on-chain transactions the master signs itself (deposits, bridges). null when the
63
+ * master source is not an OWS wallet (none configured, or a raw private key).
64
+ */
65
+ export declare function resolveOwsMaster(venue: VenueId, opts: CliCredentialOpts): Promise<{
66
+ wallet: string;
67
+ passphrase: string;
68
+ } | null>;
18
69
  export declare function parseSolanaKeypair(input: string): Keypair;
19
70
  export declare function isEvmPrivateKey(input: string): boolean;
package/dist/config.js CHANGED
@@ -4,32 +4,34 @@ import chalk from "chalk";
4
4
  import { resolve } from "path";
5
5
  import { existsSync, readFileSync } from "fs";
6
6
  import { createRequire } from "node:module";
7
+ import { PerpError } from "./errors.js";
8
+ import { isLegacyVenueId } from "./exchanges/venue-types.js";
7
9
  const _require = createRequire(import.meta.url);
8
10
  /**
9
- * Try to load a private key without throwing if none is found.
10
- * Returns null if no key is configured.
11
+ * Legacy env private-key variables (design §10.3). New venues have no env tier:
12
+ * loadPrivateKey()/resolveCredentials() never read env keys for them.
13
+ * v0.12.10 restored ASTER_PRIVATE_KEY so Aster Tier 3 env-PK loading works.
11
14
  */
12
- export async function tryLoadPrivateKey(exchange, pkOverride, walletName) {
13
- try {
14
- return await loadPrivateKey(exchange, pkOverride, walletName);
15
- }
16
- catch {
17
- return null;
18
- }
15
+ const envMap = {
16
+ pacifica: ["PACIFICA_PRIVATE_KEY", "pk"],
17
+ hyperliquid: ["HYPERLIQUID_PRIVATE_KEY", "HL_PRIVATE_KEY"],
18
+ lighter: ["LIGHTER_PRIVATE_KEY"],
19
+ aster: ["ASTER_PRIVATE_KEY"],
20
+ };
21
+ export const NO_CLI_CREDENTIAL_OPTS = {
22
+ privateKey: null, wallet: null, ows: null, owsKey: null, passphrase: null,
23
+ };
24
+ /** True when the OWS vault holds a wallet with this name (no catch: a broken OWS install throws). */
25
+ export function owsWalletExists(walletName) {
26
+ const { listWallets } = _require("@open-wallet-standard/core");
27
+ return listWallets().some((w) => w.name === walletName);
19
28
  }
20
29
  /**
21
- * Resolve an OWS wallet name to a signer-ready marker.
22
- * Returns the OWS wallet name prefixed with "ows:" so callers can detect OWS mode.
30
+ * Resolve an OWS wallet name to a signer-ready marker ("ows:<name>"), or null
31
+ * when the vault has no such wallet.
23
32
  */
24
33
  export function resolveOwsWallet(walletName) {
25
- try {
26
- const { getWallet } = _require("@open-wallet-standard/core");
27
- getWallet(walletName); // throws if not found
28
- return `ows:${walletName}`;
29
- }
30
- catch {
31
- return null;
32
- }
34
+ return owsWalletExists(walletName) ? `ows:${walletName}` : null;
33
35
  }
34
36
  /** Check if a key string represents an OWS wallet reference. */
35
37
  export function isOwsKey(key) {
@@ -39,80 +41,248 @@ export function isOwsKey(key) {
39
41
  export function getOwsWalletName(key) {
40
42
  return key.slice(4);
41
43
  }
42
- export async function loadPrivateKey(exchange, pkOverride, walletName) {
43
- // 0a. OWS wallet (--ows flag sets walletName with "ows:" prefix)
44
- if (walletName?.startsWith("ows:")) {
45
- return walletName; // pass through — adapters detect the ows: prefix
44
+ function legacyKeyFile(venue) {
45
+ return resolve(process.env.HOME || "~", ".perp", `${venue}.key`);
46
+ }
47
+ function envKeyOf(venue) {
48
+ for (const envVar of envMap[venue] ?? []) {
49
+ const v = process.env[envVar];
50
+ if (v)
51
+ return v;
46
52
  }
47
- // 0b. Wallet name override (--wallet flag) — check OWS first, then local
48
- if (walletName) {
49
- // Try OWS vault
50
- const owsKey = resolveOwsWallet(walletName);
51
- if (owsKey)
52
- return owsKey;
53
- // Fall back to local wallets
53
+ // config.ts generic PRIVATE_KEY — recorded as out of scope in §10.11, legacy venues only.
54
+ const generic = process.env.PRIVATE_KEY;
55
+ return generic ? generic : null;
56
+ }
57
+ /**
58
+ * The master key source for a venue, in precedence order:
59
+ * --ows → --wallet (OWS, else local wallets.json) → --private-key →
60
+ * settings.owsActiveWallet → [legacy venues only] env → wallets.json active → ~/.perp/<venue>.key
61
+ * Returns null only when no source is configured. A configured source that
62
+ * cannot be resolved throws (SSOT Rule #2).
63
+ */
64
+ async function resolveMasterKey(venue, opts) {
65
+ if (opts.ows !== null)
66
+ return { kind: "ows", wallet: opts.ows };
67
+ if (opts.wallet !== null) {
68
+ if (owsWalletExists(opts.wallet))
69
+ return { kind: "ows", wallet: opts.wallet };
54
70
  const { getWalletKeyByName } = await import("./commands/wallet.js");
55
- const key = getWalletKeyByName(walletName);
71
+ const key = getWalletKeyByName(opts.wallet);
56
72
  if (!key)
57
- throw new Error(`Wallet "${walletName}" not found. Run: perp wallet list`);
58
- return key;
73
+ throw new PerpError("KEY_NOT_FOUND", `Wallet "${opts.wallet}" not found`, { remediation: "Run: perp wallet list" });
74
+ return { kind: "pk", key };
59
75
  }
60
- // 1. CLI flag
61
- if (pkOverride)
62
- return pkOverride;
63
- // 2. OWS active wallet (highest priority after explicit flags)
64
- try {
65
- const { loadSettings } = await import("./settings.js");
66
- const settings = loadSettings();
67
- if (settings.owsActiveWallet) {
68
- const owsKey = resolveOwsWallet(settings.owsActiveWallet);
69
- if (owsKey)
70
- return owsKey;
76
+ if (opts.privateKey !== null)
77
+ return { kind: "pk", key: opts.privateKey };
78
+ const { loadSettings } = await import("./settings.js");
79
+ const active = loadSettings().owsActiveWallet;
80
+ if (active) {
81
+ if (!owsWalletExists(active)) {
82
+ throw new PerpError("KEY_NOT_FOUND", `Active OWS wallet "${active}" is not in the OWS vault`, {
83
+ remediation: "Run: perp wallet list, then select an existing wallet (perp wallet use <name>)",
84
+ });
71
85
  }
86
+ return { kind: "ows", wallet: active };
72
87
  }
73
- catch {
74
- // settings module not available, skip
75
- }
76
- // 3. Exchange-specific env vars
77
- // v0.12.4 retired the legacy HMAC ASTER_API_KEY/SECRET path.
78
- // v0.12.6 restored Aster as a normal EVM exchange in EXCHANGE_ENV_MAP
79
- // (setup wizard / wallet show), but missed this map — leaving Tier 3
80
- // env-PK loading broken. v0.12.10 restores ASTER_PRIVATE_KEY here so
81
- // `tryLoadPrivateKey("aster")` finds the env value, mirroring HL/LT.
82
- const envMap = {
83
- pacifica: ["PACIFICA_PRIVATE_KEY", "pk"],
84
- hyperliquid: ["HYPERLIQUID_PRIVATE_KEY", "HL_PRIVATE_KEY"],
85
- lighter: ["LIGHTER_PRIVATE_KEY"],
86
- aster: ["ASTER_PRIVATE_KEY"],
87
- };
88
- for (const envVar of envMap[exchange]) {
89
- if (process.env[envVar])
90
- return process.env[envVar];
91
- }
92
- // 4. Generic fallback
93
- if (process.env.PRIVATE_KEY)
94
- return process.env.PRIVATE_KEY;
95
- // 5. Legacy: Active wallet from wallets.json
96
- try {
97
- const { getActiveWalletKey } = await import("./commands/wallet.js");
98
- const walletKey = getActiveWalletKey(exchange);
99
- if (walletKey)
100
- return walletKey;
88
+ if (!isLegacyVenueId(venue))
89
+ return null;
90
+ const envKey = envKeyOf(venue);
91
+ if (envKey !== null)
92
+ return { kind: "pk", key: envKey };
93
+ const { getActiveWalletKey } = await import("./commands/wallet.js");
94
+ const walletKey = getActiveWalletKey(venue);
95
+ if (walletKey)
96
+ return { kind: "pk", key: walletKey };
97
+ const keyFile = legacyKeyFile(venue);
98
+ if (existsSync(keyFile))
99
+ return { kind: "pk", key: readFileSync(keyFile, "utf-8").trim() };
100
+ return null;
101
+ }
102
+ /** Registered agent meta for a venue (any network), or null — presence check only. */
103
+ async function agentOf(venue) {
104
+ const store = await import("./agent-wallet/store.js");
105
+ switch (venue) {
106
+ case "ondo": return store.getVenueAgent("ondo");
107
+ case "arcus": return store.getVenueAgent("arcus");
108
+ case "qfex": return store.getVenueAgent("qfex");
109
+ case "pacifica":
110
+ case "hyperliquid":
111
+ case "lighter":
112
+ case "lighter-rh":
113
+ case "aster":
114
+ return store.getAgent(venue);
101
115
  }
102
- catch {
103
- // wallet module not available, skip
116
+ }
117
+ /**
118
+ * Agent meta to sign with on `network`. Legacy-shaped agents are selected by
119
+ * their recorded network (a mainnet agent is never handed to a testnet adapter).
120
+ * Ondo / Arcus metas are selected by their `network` field the same way.
121
+ */
122
+ async function agentForNetwork(venue, network) {
123
+ const store = await import("./agent-wallet/store.js");
124
+ switch (venue) {
125
+ case "ondo": return store.getVenueAgentForNetwork("ondo", network);
126
+ case "arcus": return store.getVenueAgentForNetwork("arcus", network);
127
+ case "qfex": return store.getVenueAgentForNetwork("qfex", network);
128
+ case "pacifica":
129
+ case "hyperliquid":
130
+ case "lighter":
131
+ case "lighter-rh":
132
+ case "aster":
133
+ return store.getAgentForNetwork(venue, network);
104
134
  }
105
- // 6. Legacy key file (~/.perp/<exchange>.key)
106
- const keyFile = resolve(process.env.HOME || "~", ".perp", `${exchange}.key`);
107
- if (existsSync(keyFile)) {
108
- return readFileSync(keyFile, "utf-8").trim();
135
+ }
136
+ /**
137
+ * Passphrase that opens an OWS master wallet. Precedence between explicit
138
+ * user inputs: --ows-key token (routes through the OWS policy engine) →
139
+ * --passphrase → OWS_PASSPHRASE env. None given → "" (the OWS empty
140
+ * passphrase; this is what the pre-venue OWS path always used).
141
+ */
142
+ function owsPassphraseOf(opts) {
143
+ if (opts.owsKey !== null)
144
+ return opts.owsKey;
145
+ if (opts.passphrase !== null)
146
+ return opts.passphrase;
147
+ if ("OWS_PASSPHRASE" in process.env)
148
+ return process.env.OWS_PASSPHRASE ?? "";
149
+ return "";
150
+ }
151
+ function owsKeyTokenOf(opts) {
152
+ if (opts.owsKey !== null)
153
+ return opts.owsKey;
154
+ // OWS_API_KEY env: recorded as out of scope in §10.11 (kept as before).
155
+ const env = process.env.OWS_API_KEY;
156
+ return env ? env : null;
157
+ }
158
+ /** CLI/MCP-shared credential resolution (design §10.2). */
159
+ export async function resolveCredentials(venue, opts, network) {
160
+ const withToken = { ...opts, owsKey: owsKeyTokenOf(opts) };
161
+ // Custodial venues (masterKeyKind "none": qfex) have no master key to resolve.
162
+ const { ensureVenuesRegistered } = await import("./exchanges/venues/index.js");
163
+ const { getVenue } = await import("./exchanges/venue.js");
164
+ ensureVenuesRegistered();
165
+ const master = getVenue(venue).masterKeyKind === "none" ? null : await resolveMasterKey(venue, withToken);
166
+ return {
167
+ pk: master?.kind === "pk" ? master.key : null,
168
+ owsWallet: master?.kind === "ows" ? master.wallet : null,
169
+ owsPassphrase: master?.kind === "ows" ? owsPassphraseOf(withToken) : null,
170
+ owsKeyToken: withToken.owsKey,
171
+ agent: await agentForNetwork(venue, network),
172
+ };
173
+ }
174
+ /**
175
+ * Whether any credential source is configured for the venue (explicit checks,
176
+ * no catch). true does not mean the source is valid — creating the adapter
177
+ * surfaces that error.
178
+ */
179
+ export async function hasCredentials(venue, opts) {
180
+ // A venue without any owner-wallet signing path (qfex: custodial, API key only) is configured
181
+ // only by an imported key — a master wallet / private key is not a credential for it.
182
+ const { ensureVenuesRegistered } = await import("./exchanges/venues/index.js");
183
+ const { getVenue } = await import("./exchanges/venue.js");
184
+ ensureVenuesRegistered();
185
+ if (getVenue(venue).masterKeyKind === "none")
186
+ return (await agentOf(venue)) !== null;
187
+ if (opts.ows !== null || opts.wallet !== null || opts.privateKey !== null)
188
+ return true;
189
+ if ((await agentOf(venue)) !== null)
190
+ return true;
191
+ const { loadSettings } = await import("./settings.js");
192
+ if (loadSettings().owsActiveWallet)
193
+ return true;
194
+ if (!isLegacyVenueId(venue))
195
+ return false;
196
+ if (envKeyOf(venue) !== null)
197
+ return true;
198
+ const { getActiveWalletKey } = await import("./commands/wallet.js");
199
+ if (getActiveWalletKey(venue))
200
+ return true;
201
+ return existsSync(legacyKeyFile(venue));
202
+ }
203
+ /**
204
+ * Account-role venues (registry order) with a configured credential source —
205
+ * the default set for "all exchanges" aggregate views. Market-data-only venues
206
+ * never qualify.
207
+ */
208
+ export async function listConfiguredAccountVenues(opts = NO_CLI_CREDENTIAL_OPTS) {
209
+ const { ensureVenuesRegistered } = await import("./exchanges/venues/index.js");
210
+ const { listVenuesWithRole } = await import("./exchanges/venue.js");
211
+ ensureVenuesRegistered();
212
+ const venues = listVenuesWithRole("account").map(v => v.id);
213
+ const configured = await Promise.all(venues.map(v => hasCredentials(v, opts)));
214
+ return venues.filter((_, i) => configured[i]);
215
+ }
216
+ /**
217
+ * Master key for a venue as a string: "ows:<name>" for OWS wallets, else the raw key.
218
+ * Throws NO_ENV_TIER for venues without an env/PK tier when nothing else is configured,
219
+ * and a setup hint when no source exists.
220
+ */
221
+ export async function loadPrivateKey(exchange, pkOverride, walletName) {
222
+ const opts = {
223
+ ...NO_CLI_CREDENTIAL_OPTS,
224
+ privateKey: pkOverride ?? null,
225
+ ows: walletName?.startsWith("ows:") ? walletName.slice(4) : null,
226
+ wallet: walletName && !walletName.startsWith("ows:") ? walletName : null,
227
+ };
228
+ const master = await resolveMasterKey(exchange, opts);
229
+ if (master?.kind === "ows")
230
+ return `ows:${master.wallet}`;
231
+ if (master?.kind === "pk")
232
+ return master.key;
233
+ if (!isLegacyVenueId(exchange)) {
234
+ throw new PerpError("NO_ENV_TIER", `${exchange} has no env/private-key tier`, {
235
+ remediation: `Use an OWS wallet (perp wallet generate, then --ows <name>) or register an agent: perp wallet agent approve ${exchange}`,
236
+ });
109
237
  }
110
- throw new Error(`No wallet configured for ${exchange}.\n\n` +
238
+ throw new PerpError("NO_SIGNER_AVAILABLE", `No wallet configured for ${exchange}.\n\n` +
111
239
  ` Quick start: ${chalk.cyan("perp wallet generate")}\n\n` +
112
240
  ` Or manually:\n` +
113
- ` perp wallet import <key> Import key to OWS vault\n` +
114
- ` perp wallet set ${exchange} <key> Legacy: set env var\n` +
115
- ` --private-key <key> Per-command override`);
241
+ ` perp wallet import Import key to OWS vault (hidden prompt / stdin)\n` +
242
+ ` perp wallet set ${exchange} Import key (hidden prompt / stdin) to OWS vault`, { exchange, remediation: masterMissingRemediation(exchange) });
243
+ }
244
+ /** Root-program option values → credential selectors (shared by the CLI adapter factory and master-only commands). */
245
+ export function cliCredentialOptsFrom(rootOpts) {
246
+ const str = (v) => (typeof v === "string" ? v : null);
247
+ return {
248
+ privateKey: str(rootOpts.privateKey),
249
+ wallet: str(rootOpts.wallet),
250
+ ows: str(rootOpts.ows),
251
+ owsKey: str(rootOpts.owsKey),
252
+ passphrase: str(rootOpts.passphrase),
253
+ };
254
+ }
255
+ function masterMissingRemediation(venue) {
256
+ const chain = venue === "pacifica" ? "solana" : "evm";
257
+ return `Do this on the exchange website, or add the master wallet to this computer: perp wallet import --name <name> --chain ${chain} (hidden prompt / stdin), then perp wallet use <name> (or pass --ows <name>). An imported agent / API key can trade but cannot move funds.`;
258
+ }
259
+ /**
260
+ * Commands that only the account owner can sign (deposit, withdraw, transfer, bridge, referral
261
+ * set, agent approve/revoke): refuse up front with NO_SIGNER_AVAILABLE when no master wallet is
262
+ * configured on this machine — e.g. only an agent / API key imported from the exchange website.
263
+ * Never falls back to the agent key (it cannot sign these). A configured-but-missing master
264
+ * (active wallet not in the vault) throws KEY_NOT_FOUND from resolveMasterKey.
265
+ */
266
+ export async function requireMasterWallet(venue, opts, action) {
267
+ const master = await resolveMasterKey(venue, { ...opts, owsKey: owsKeyTokenOf(opts) });
268
+ if (master !== null)
269
+ return;
270
+ throw new PerpError("NO_SIGNER_AVAILABLE", `${action} needs the ${venue} master wallet, which is not on this computer`, {
271
+ exchange: venue,
272
+ remediation: masterMissingRemediation(venue),
273
+ });
274
+ }
275
+ /**
276
+ * The OWS master wallet selected by the CLI flags / active wallet, with the passphrase that opens
277
+ * it — for on-chain transactions the master signs itself (deposits, bridges). null when the
278
+ * master source is not an OWS wallet (none configured, or a raw private key).
279
+ */
280
+ export async function resolveOwsMaster(venue, opts) {
281
+ const withToken = { ...opts, owsKey: owsKeyTokenOf(opts) };
282
+ const master = await resolveMasterKey(venue, withToken);
283
+ if (master === null || master.kind !== "ows")
284
+ return null;
285
+ return { wallet: master.wallet, passphrase: owsPassphraseOf(withToken) };
116
286
  }
117
287
  export function parseSolanaKeypair(input) {
118
288
  // Try base58 first
@@ -1,11 +1,36 @@
1
1
  /**
2
2
  * Shared constants for perp-cli.
3
3
  */
4
- /** Default taker fee per exchange (as fraction, e.g. 0.00035 = 0.035%) */
4
+ /**
5
+ * Published taker fee per venue (fraction, e.g. 0.00035 = 0.035%) — configuration, not a
6
+ * fallback (SSOT Rule #2 shipped defaults): used when settings.fees has no entry for the venue.
7
+ */
5
8
  export declare const TAKER_FEES: Record<string, number>;
6
- /** Get taker fee for an exchange. Uses settings if available, falls back to defaults. */
9
+ /**
10
+ * Published maker fee per venue (fraction; negative = rebate). Only venues whose base-tier
11
+ * maker rate is published and verified are listed; others resolve to 0 in getMakerFee().
12
+ */
13
+ export declare const MAKER_FEES: Record<string, number>;
14
+ /**
15
+ * Taker fee for a venue: settings.fees[venue].taker → TAKER_FEES[venue] → DEFAULT_TAKER_FEE
16
+ * (estimate for venues without a published entry). loadSettings() errors (malformed
17
+ * settings.json) propagate — never replaced by the built-in fees.
18
+ */
7
19
  export declare function getTakerFee(exchange: string): number;
8
- /** Get maker fee for an exchange. Uses settings if available. */
20
+ /**
21
+ * Maker fee for a venue: settings.fees[venue].maker → MAKER_FEES[venue] → 0 (no maker fee
22
+ * published/configured). loadSettings() errors propagate.
23
+ */
9
24
  export declare function getMakerFee(exchange: string): number;
10
- /** Default taker fee for single-exchange use (when exchange is unknown). */
25
+ /**
26
+ * Taker-fee estimate for an id with no TAKER_FEES entry. Every venue id in VENUE_IDS has
27
+ * a published entry above, so this applies only to genuinely unknown ids — e.g. arb-auto's
28
+ * getTakerFee("default") and trade-validator's venue-agnostic notional fee estimate.
29
+ */
11
30
  export declare const DEFAULT_TAKER_FEE = 0.00035;
31
+ /**
32
+ * Hyperliquid's minimum order value, venue-wide (native perps and HIP-3 dexes): the venue refuses
33
+ * smaller orders with "Order must have minimum value of $10". It is a documented constant, not a
34
+ * field of `meta` / `metaAndAssetCtxs`, so it is the one order minimum that lives in code.
35
+ */
36
+ export declare const HL_MIN_ORDER_VALUE_USD = "10";
package/dist/constants.js CHANGED
@@ -2,36 +2,78 @@
2
2
  * Shared constants for perp-cli.
3
3
  */
4
4
  import { loadSettings } from "./settings.js";
5
- /** Default taker fee per exchange (as fraction, e.g. 0.00035 = 0.035%) */
5
+ /**
6
+ * Published taker fee per venue (fraction, e.g. 0.00035 = 0.035%) — configuration, not a
7
+ * fallback (SSOT Rule #2 shipped defaults): used when settings.fees has no entry for the venue.
8
+ */
6
9
  export const TAKER_FEES = {
7
10
  hyperliquid: 0.00035,
8
11
  pacifica: 0.00035,
9
12
  lighter: 0,
10
13
  aster: 0.0004, // 0.04% taker fee
14
+ // Ondo Perps base rate (docs.ondoperps.xyz/fees, 2026-10-02): taker 5 bps. A limited-time
15
+ // 50% promotion (2.5 bps) is NOT assumed — it can end without notice; set
16
+ // settings.fees.ondo to use the promotional or a volume-tier rate.
17
+ ondo: 0.0005,
18
+ // Arcus mainnet tier 0 "Base" (GET https://api.arcus.xyz/v1/feetiers, read 2026-10-02):
19
+ // taker_fee_ppm 225. The docs publish no rates ("read fees from the API"); higher-volume
20
+ // tiers are cheaper (Bronze 190 … Max 95 ppm). Testnet Base differs (450 ppm).
21
+ arcus: 0.000225,
22
+ // Lighter RH "Standard" (default) account tier: 0% / 0% (apidocs.lighter.xyz lighter-rh).
23
+ // "Plus" is 0.005%/0.005%; "Premium" is 0.0120%/0.0350% (maker/taker) down to
24
+ // 0.0084%/0.0245% by 14-day volume — Premium accounts must set settings.fees["lighter-rh"].
25
+ "lighter-rh": 0,
26
+ // QFEX entry tier ("Tier 5", < $2m 30-day effective volume; docs.qfex.com/qfex/fees, read
27
+ // 2026-10-04): taker 0.10% on single stocks — the HIGHEST class rate, so index / commodity
28
+ // orders (0.05%) are never under-estimated. Lower tiers / the lifetime-tier promotion: set
29
+ // settings.fees.qfex.
30
+ qfex: 0.001,
11
31
  };
12
- /** Get taker fee for an exchange. Uses settings if available, falls back to defaults. */
32
+ /**
33
+ * Published maker fee per venue (fraction; negative = rebate). Only venues whose base-tier
34
+ * maker rate is published and verified are listed; others resolve to 0 in getMakerFee().
35
+ */
36
+ export const MAKER_FEES = {
37
+ ondo: 0.0002, // base 2 bps; promotional 1 bp not assumed (see TAKER_FEES.ondo)
38
+ arcus: 0, // mainnet Base tier maker_fee_ppm 0
39
+ // Aster perpetuals, Crypto — General (docs.asterdex.com trading/perpetuals/fees-and-specs/fees,
40
+ // read 2026-10-02): maker 0%, taker 0.04% (TAKER_FEES.aster). RWA markets: maker 0 bps.
41
+ aster: 0,
42
+ "lighter-rh": 0, // Standard tier (Premium: 0.012% under $1M 14-day volume)
43
+ qfex: 0.0005, // Tier 5 single stocks 0.05% (indices / commodities 0.02%); see TAKER_FEES.qfex
44
+ };
45
+ /**
46
+ * Taker fee for a venue: settings.fees[venue].taker → TAKER_FEES[venue] → DEFAULT_TAKER_FEE
47
+ * (estimate for venues without a published entry). loadSettings() errors (malformed
48
+ * settings.json) propagate — never replaced by the built-in fees.
49
+ */
13
50
  export function getTakerFee(exchange) {
14
51
  const key = exchange.toLowerCase();
15
- try {
16
- const settings = loadSettings();
17
- if (settings.fees[key]?.taker !== undefined) {
18
- return settings.fees[key].taker;
19
- }
20
- }
21
- catch { /* fallback to hardcoded */ }
22
- return TAKER_FEES[key] ?? 0.00035;
52
+ const configured = loadSettings().fees[key]?.taker;
53
+ if (configured !== undefined)
54
+ return configured;
55
+ return TAKER_FEES[key] ?? DEFAULT_TAKER_FEE;
23
56
  }
24
- /** Get maker fee for an exchange. Uses settings if available. */
57
+ /**
58
+ * Maker fee for a venue: settings.fees[venue].maker → MAKER_FEES[venue] → 0 (no maker fee
59
+ * published/configured). loadSettings() errors propagate.
60
+ */
25
61
  export function getMakerFee(exchange) {
26
62
  const key = exchange.toLowerCase();
27
- try {
28
- const settings = loadSettings();
29
- if (settings.fees[key]?.maker !== undefined) {
30
- return settings.fees[key].maker;
31
- }
32
- }
33
- catch { /* fallback */ }
34
- return 0;
63
+ const configured = loadSettings().fees[key]?.maker;
64
+ if (configured !== undefined)
65
+ return configured;
66
+ return MAKER_FEES[key] ?? 0;
35
67
  }
36
- /** Default taker fee for single-exchange use (when exchange is unknown). */
68
+ /**
69
+ * Taker-fee estimate for an id with no TAKER_FEES entry. Every venue id in VENUE_IDS has
70
+ * a published entry above, so this applies only to genuinely unknown ids — e.g. arb-auto's
71
+ * getTakerFee("default") and trade-validator's venue-agnostic notional fee estimate.
72
+ */
37
73
  export const DEFAULT_TAKER_FEE = 0.00035;
74
+ /**
75
+ * Hyperliquid's minimum order value, venue-wide (native perps and HIP-3 dexes): the venue refuses
76
+ * smaller orders with "Order must have minimum value of $10". It is a documented constant, not a
77
+ * field of `meta` / `metaAndAssetCtxs`, so it is the one order minimum that lives in code.
78
+ */
79
+ export const HL_MIN_ORDER_VALUE_USD = "10";
@@ -63,6 +63,8 @@ export interface DashboardSnapshot {
63
63
  dexAssets: DexAssetRow[];
64
64
  dexNames: string[];
65
65
  exchangeStatus: Record<string, string>;
66
+ /** Error message per venue (or "funding" when the whole fetch failed). */
67
+ exchangeErrors: Record<string, string>;
66
68
  };
67
69
  }
68
70
  export interface DashboardOpts {
@@ -9,7 +9,7 @@ import { WebSocketServer, WebSocket } from "ws";
9
9
  import { getUI } from "./ui.js";
10
10
  import { WsFeedManager } from "./ws-feeds.js";
11
11
  // Cached arb data (polled less frequently)
12
- let cachedArb = { opportunities: [], dexArb: [], dexAssets: [], dexNames: [], exchangeStatus: {} };
12
+ let cachedArb = { opportunities: [], dexArb: [], dexAssets: [], dexNames: [], exchangeStatus: {}, exchangeErrors: {} };
13
13
  // Cached market data (polled with arb cycle — market metadata rarely changes)
14
14
  const cachedMarkets = new Map();
15
15
  /**
@@ -40,10 +40,12 @@ async function pollArbData() {
40
40
  const opportunities = [];
41
41
  const dexArb = [];
42
42
  let exchangeStatus = {};
43
+ let exchangeErrors = {};
43
44
  try {
44
45
  const { fetchAllFundingRates } = await import("../funding/rates.js");
45
46
  const snapshot = await fetchAllFundingRates({ minSpread: 5 });
46
47
  exchangeStatus = snapshot.exchangeStatus;
48
+ exchangeErrors = snapshot.exchangeErrors;
47
49
  for (const sym of snapshot.symbols.slice(0, 20)) {
48
50
  opportunities.push({
49
51
  symbol: sym.symbol,
@@ -60,8 +62,9 @@ async function pollArbData() {
60
62
  });
61
63
  }
62
64
  }
63
- catch {
64
- // funding rates unavailable
65
+ catch (e) {
66
+ exchangeStatus = { funding: "error" };
67
+ exchangeErrors = { funding: e instanceof Error ? e.message : String(e) };
65
68
  }
66
69
  const dexAssets = [];
67
70
  const dexNamesSet = new Set();
@@ -95,8 +98,8 @@ async function pollArbData() {
95
98
  const row = {
96
99
  base,
97
100
  dexes: assets.map((a) => {
98
- // All dex funding rates are already per-hour, same as hyperliquid
99
- const ann = annualizeRate(a.fundingRate, "hyperliquid");
101
+ // Venue ref: "hyperliquid" for native, "hl:<dex>" for HIP-3 (same basis, §10.1)
102
+ const ann = annualizeRate(a.fundingRate, a.dex === "hl" ? "hyperliquid" : `hl:${a.dex}`);
100
103
  return { dex: a.dex, rate: a.fundingRate, annualizedPct: ann, markPrice: a.markPrice, oi: a.openInterest };
101
104
  }),
102
105
  };
@@ -113,7 +116,7 @@ async function pollArbData() {
113
116
  // dex arb unavailable
114
117
  }
115
118
  const dexNames = [...dexNamesSet].sort();
116
- return { opportunities, dexArb, dexAssets: dexAssets.slice(0, 30), dexNames, exchangeStatus };
119
+ return { opportunities, dexArb, dexAssets: dexAssets.slice(0, 30), dexNames, exchangeStatus, exchangeErrors };
117
120
  }
118
121
  /**
119
122
  * Poll all exchanges and return a unified snapshot.