perp-cli 0.12.17 → 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 +247 -27
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +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 +70 -92
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +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 +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +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 +142 -55
  348. package/skills/perp-cli/references/agent-operations.md +110 -56
  349. package/skills/perp-cli/references/commands.md +266 -71
  350. package/skills/perp-cli/references/strategies.md +34 -31
  351. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  352. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  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 +31 -15
  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
@@ -0,0 +1,301 @@
1
+ /**
2
+ * TradingV2 — normalized order entry for venues added under the venue
3
+ * abstraction (docs/DESIGN-venue-abstraction.md §3 L3, §10.7, §10.10).
4
+ *
5
+ * Method names deliberately do not overlap the legacy ExchangeAdapter trading
6
+ * methods, so an adapter can implement both without overloads.
7
+ */
8
+ import { randomBytes, randomUUID } from "node:crypto";
9
+ import { PerpError } from "../errors.js";
10
+ import { orderRefOf } from "./venue-types.js";
11
+ import { requirePositive } from "./precision.js";
12
+ export function isTradingV2(a) {
13
+ const r = a;
14
+ return !!r
15
+ && typeof r.placeOrder === "function"
16
+ && typeof r.cancelByRef === "function"
17
+ && typeof r.getOrderStatus === "function"
18
+ && typeof r.getVenueMarkets === "function";
19
+ }
20
+ /**
21
+ * Uniform "not supported" response (design §13.6, §13.10 #13): CAPABILITY_UNSUPPORTED
22
+ * with details { venue, feature, reason } and an alternative in `remediation`.
23
+ * `venue` is a venue id or display name; `feature` a short capability name
24
+ * ("limitOrder", "fok", "postOnly", "amend", "trading", "orderbook", ...).
25
+ */
26
+ export function unsupported(venue, feature, reason, remediation) {
27
+ return new PerpError("CAPABILITY_UNSUPPORTED", `${venue} does not support ${feature} — ${reason}`, {
28
+ venue, feature, reason, remediation,
29
+ });
30
+ }
31
+ /**
32
+ * An adapter whose reduce-only orders are currently refused (a venue whose reduce-only wire
33
+ * format is not set; none today) reports why. Callers that will need a reduce-only order
34
+ * LATER in a flow ask first — an entry carrying --tp/--sl is refused BEFORE the entry is sent
35
+ * (never "entry filled, protection failed"), and a close is refused before any read. Adapters
36
+ * without the method have no such gate.
37
+ */
38
+ export function reduceOnlyGateOf(adapter) {
39
+ const fn = adapter?.reduceOnlyGate;
40
+ if (typeof fn !== "function")
41
+ return null;
42
+ const gate = fn.call(adapter);
43
+ return gate instanceof PerpError ? gate : null;
44
+ }
45
+ /**
46
+ * An adapter whose STOP orders are currently refused (QFEX until stop acknowledgements can be
47
+ * correlated) reports why. `--tp/--sl` entries and `trade tpsl` ask first, so an entry is never
48
+ * sent when its protective legs would be refused. Adapters without the method have no such gate.
49
+ */
50
+ export function stopOrderGateOf(adapter) {
51
+ const fn = adapter?.stopOrderGate;
52
+ if (typeof fn !== "function")
53
+ return null;
54
+ const gate = fn.call(adapter);
55
+ return gate instanceof PerpError ? gate : null;
56
+ }
57
+ /**
58
+ * An adapter on which UNATTENDED automation is currently refused (QFEX while its stop orders are
59
+ * refused) reports why. Bots, plans, TWAP, DCA, grid, funding-arb, trailing stop and background
60
+ * jobs ask before they start; manual commands never ask. Adapters without the method have no
61
+ * such gate.
62
+ */
63
+ export function automationGateOf(adapter) {
64
+ const fn = adapter?.automationGate;
65
+ if (typeof fn !== "function")
66
+ return null;
67
+ const gate = fn.call(adapter);
68
+ return gate instanceof PerpError ? gate : null;
69
+ }
70
+ /**
71
+ * Refuse to START anything that trades unattended on a venue that cannot close or protect its
72
+ * positions. Bots, plans, TWAP, DCA, grid, funding-arb, trailing stop and background jobs all
73
+ * need a reduce-only order later (close / stop / drawdown exit): on a venue whose reduce-only
74
+ * orders are gated (reduceOnlyGateOf) they would open a position they cannot close, and on a
75
+ * venue that refuses automation (automationGateOf) they would run without a protective exit.
76
+ * Throws the gate before the first order.
77
+ */
78
+ export function assertCanClosePositions(adapter, what) {
79
+ const reduceOnly = reduceOnlyGateOf(adapter);
80
+ if (reduceOnly !== null)
81
+ throw startRefusal(what, "it could open a position it cannot close", reduceOnly);
82
+ const automation = automationGateOf(adapter);
83
+ if (automation !== null)
84
+ throw startRefusal(what, "it would trade unattended", automation);
85
+ }
86
+ /** The gate's own code / details / remediation, with what was refused and why in front. */
87
+ export function startRefusal(what, why, gate) {
88
+ const remediation = gate.structured.remediation;
89
+ return new PerpError(gate.structured.code, `${what} refused: ${why} — ${gate.message}`, {
90
+ ...(gate.structured.details === undefined ? {} : gate.structured.details),
91
+ ...(remediation === undefined ? {} : { remediation }),
92
+ });
93
+ }
94
+ /**
95
+ * Validate an order request against the venue's declared capabilities BEFORE
96
+ * anything is sent. Throws; never adjusts the request.
97
+ */
98
+ export function validateOrderRequest(venue, caps, req) {
99
+ const isStop = req.type === "stopMarket" || req.type === "stopLimit";
100
+ const needsPrice = req.type === "limit" || req.type === "stopLimit";
101
+ const isMarketLike = req.type === "market" || req.type === "stopMarket";
102
+ if (needsPrice && req.price === null) {
103
+ throw new PerpError("INVALID_PARAMS", `${req.type} order requires a price`);
104
+ }
105
+ if (isStop && req.triggerPrice === null) {
106
+ throw new PerpError("INVALID_PARAMS", `${req.type} order requires a trigger price`);
107
+ }
108
+ if (!isStop && req.triggerPrice !== null) {
109
+ throw new PerpError("INVALID_PARAMS", `triggerPrice is only valid for stop orders`);
110
+ }
111
+ // A zero / negative / non-decimal price is never sent to the venue.
112
+ if (req.price !== null)
113
+ requirePositive(req.price, "price");
114
+ if (req.triggerPrice !== null)
115
+ requirePositive(req.triggerPrice, "triggerPrice");
116
+ // Callers resolve the common default (GTC, design §13.3) before building the request;
117
+ // a missing TIF here is a programming error, never a user-facing TIF_REQUIRED.
118
+ if (needsPrice && req.tif === null) {
119
+ throw new PerpError("INVALID_PARAMS", `${venue.displayName} ${req.type} order is missing its resolved time-in-force`);
120
+ }
121
+ if (req.tif !== null) {
122
+ if (isMarketLike) {
123
+ throw new PerpError("INVALID_PARAMS", `time-in-force is not valid for ${req.type} orders`);
124
+ }
125
+ if (!caps.timeInForce.includes(req.tif)) {
126
+ throw unsupported(venue.displayName, "timeInForce", `time-in-force ${req.tif} is not offered`, `Use one of: ${caps.timeInForce.join(", ")}`);
127
+ }
128
+ }
129
+ // A reduce-only limit order may only carry the TIFs the venue allows for it (Ondo: IOC only —
130
+ // a resting reduce-only order is refused there); refused before anything is sent.
131
+ if (req.reduceOnly && req.type === "limit" && req.tif !== null && caps.reduceOnlyTif !== null && !caps.reduceOnlyTif.includes(req.tif)) {
132
+ const alternative = `set a take-profit / stop-loss on the position instead: perp trade tpsl ${req.symbol} <long|short> --tp <price>${caps.tpsl !== null && caps.tpsl !== "positionLevel" ? " [--size <qty>]" : ""} -e ${venue.id}`;
133
+ throw unsupported(venue.displayName, "restingReduceOnly", `a reduce-only limit order can only be ${caps.reduceOnlyTif.join(" / ")} (got ${req.tif})`, `Use --tif ${caps.reduceOnlyTif.join(" | ")}, or ${alternative}`);
134
+ }
135
+ // Callers always pass resolveSlippage() output for market orders (§13.10 #1); a
136
+ // missing bound here is a programming error, never "send unbounded".
137
+ if (req.type === "market" && req.slippage === null) {
138
+ throw new PerpError("INVALID_PARAMS", `${venue.displayName} market order is missing its resolved slippage bound`);
139
+ }
140
+ if (req.slippage !== null && (!Number.isFinite(req.slippage.bps) || req.slippage.bps < 1 || req.slippage.bps > venue.maxSlippageBps)) {
141
+ throw new PerpError("INVALID_PARAMS", `slippage ${req.slippage.bps} bps is outside 1..${venue.maxSlippageBps} for ${venue.displayName}`);
142
+ }
143
+ if (caps.orderAck === "async" && req.clientOrderId === null) {
144
+ throw new PerpError("INVALID_PARAMS", `${venue.displayName} acknowledges orders asynchronously; a clientOrderId is required`);
145
+ }
146
+ if (req.clientOrderId !== null && !caps.clientOrderId) {
147
+ throw unsupported(venue.displayName, "clientOrderId", "the venue has no client order ids", "Omit the client order id");
148
+ }
149
+ if (req.tpsl !== null) {
150
+ if (req.tpsl.tp === null && req.tpsl.sl === null) {
151
+ throw new PerpError("INVALID_PARAMS", "tpsl given but neither tp nor sl is set");
152
+ }
153
+ if (caps.tpsl !== "attached" && caps.tpsl !== "batchGroup") {
154
+ throw unsupported(venue.displayName, "tpslAttached", "TP/SL cannot be attached to an entry order", `Place the entry, then run: perp trade tpsl -e ${venue.id}`);
155
+ }
156
+ }
157
+ if (isStop && caps.tpsl === "batchGroup" && !req.reduceOnly) {
158
+ throw unsupported(venue.displayName, "openingStop", "stop orders cannot open a position", "Stop orders on this venue must be reduce-only");
159
+ }
160
+ }
161
+ /**
162
+ * Common TIF aliases (design §13.3): a common name the venue lists under its own
163
+ * spelling, and venue spellings accepted for the common name. Only same-meaning
164
+ * pairs: GTC ↔ GTT (resting until cancelled / venue expiry), ALO ↔ GTX (post-only).
165
+ */
166
+ const TIF_ALIASES = {
167
+ GTC: ["GTT"],
168
+ GTT: ["GTC"],
169
+ ALO: ["GTX"],
170
+ GTX: ["ALO"],
171
+ };
172
+ /**
173
+ * Resolve a CLI TIF input (common GTC/IOC/FOK/ALO or a venue alias GTT/GTX/TOB,
174
+ * case-insensitive) to a value the venue declares in its capabilities. Never
175
+ * substitutes a different meaning: FOK is never emulated (§13.3) and an
176
+ * unknown / unoffered value is the uniform CAPABILITY_UNSUPPORTED response.
177
+ */
178
+ export function validateLegacyTif(venue, caps, tif) {
179
+ const upper = tif.trim().toUpperCase();
180
+ if (caps.timeInForce.includes(upper))
181
+ return upper;
182
+ for (const alias of TIF_ALIASES[upper] ?? []) {
183
+ if (caps.timeInForce.includes(alias))
184
+ return alias;
185
+ }
186
+ if (upper === "FOK") {
187
+ throw unsupported(venue.displayName, "fok", "fill-or-kill is not offered and is never emulated (a partial IOC fill cannot be undone)", `Use --tif IOC (partial fills possible) or one of: ${caps.timeInForce.join(", ")}`);
188
+ }
189
+ const feature = upper === "ALO" || upper === "GTX" ? "postOnly" : "timeInForce";
190
+ throw unsupported(venue.displayName, feature, `time-in-force ${tif} is not offered`, `Use one of: ${caps.timeInForce.join(", ")}`);
191
+ }
192
+ const FINAL_STATUSES = new Set(["filled", "rejected", "cancelled"]);
193
+ /**
194
+ * Poll getOrderStatus until the order reaches a final state, is resting ("open"),
195
+ * or the timeout elapses. On timeout returns the last observation with
196
+ * finality "pending" — never guesses a fill.
197
+ *
198
+ * `untilResting`: treat "open"/"partiallyFilled" as a stopping point (limit orders).
199
+ * `ack`: the placement acknowledgement of an async venue. When set, an
200
+ * ORDER_NOT_FOUND from getOrderStatus inside this loop means "acknowledged but
201
+ * not yet visible on the read path" (Arcus forwards to the engine before the
202
+ * order store has it) and the ack itself (status "accepted") stands as the
203
+ * observation. Outside this loop ORDER_NOT_FOUND is never reinterpreted.
204
+ */
205
+ export async function awaitOrderFinal(adapter, symbol, ref, opts) {
206
+ if (!(opts.timeoutMs > 0) || !(opts.pollMs > 0)) {
207
+ throw new PerpError("INVALID_PARAMS", "awaitOrderFinal: timeoutMs and pollMs must be positive");
208
+ }
209
+ if (opts.ack !== null && opts.ack.status !== "accepted") {
210
+ throw new PerpError("INVALID_PARAMS", `awaitOrderFinal: ack must have status "accepted" (got ${opts.ack.status})`);
211
+ }
212
+ const deadline = Date.now() + opts.timeoutMs;
213
+ let last = null;
214
+ for (;;) {
215
+ try {
216
+ last = await adapter.getOrderStatus(symbol, ref);
217
+ }
218
+ catch (e) {
219
+ if (opts.ack === null || !(e instanceof PerpError) || e.structured.code !== "ORDER_NOT_FOUND")
220
+ throw e;
221
+ last = opts.ack;
222
+ }
223
+ if (FINAL_STATUSES.has(last.status))
224
+ return { ...last, finality: "final" };
225
+ if (opts.untilResting && (last.status === "open" || last.status === "partiallyFilled")) {
226
+ return { ...last, finality: "final" };
227
+ }
228
+ if (Date.now() + opts.pollMs > deadline)
229
+ return { ...last, finality: "pending" };
230
+ await new Promise(r => setTimeout(r, opts.pollMs));
231
+ }
232
+ }
233
+ /** Generate a client order id: time-ordered, 20 chars max, [0-9a-z]. */
234
+ export function newClientOrderId(prefix = "p") {
235
+ const t = Date.now().toString(36);
236
+ const r = Math.floor(Math.random() * 36 ** 6).toString(36).padStart(6, "0");
237
+ return `${prefix}${t}${r}`.slice(0, 20);
238
+ }
239
+ /**
240
+ * Client order id in the venue's format: Lighter-family `clientOrderIndex` is a
241
+ * decimal integer ≤ 2^48 − 1 (ms timestamp × 100 + 2 random digits); QFEX and Pacifica take a
242
+ * UUID (QFEX: the format the venue itself generates; Pacifica: "Full UUID string"); Hyperliquid a
243
+ * 128-bit cloid ("0x" + 32 hex); every other venue (Aster: [.A-Z:/a-z0-9_-]{1,36}) takes
244
+ * newClientOrderId().
245
+ */
246
+ export function newVenueClientOrderId(venue) {
247
+ if (venue.family === "lighter")
248
+ return String(Date.now() * 100 + Math.floor(Math.random() * 100));
249
+ if (venue.family === "qfex" || venue.family === "pacifica")
250
+ return randomUUID();
251
+ if (venue.family === "hyperliquid")
252
+ return `0x${randomBytes(16).toString("hex")}`;
253
+ return newClientOrderId();
254
+ }
255
+ /** Finality wait after an async (orderAck "async") placement ack. */
256
+ const ASYNC_ACK_TIMEOUT_MS = 15_000;
257
+ const ASYNC_ACK_POLL_MS = 1_000;
258
+ /**
259
+ * Place through TradingV2 and settle the outcome as far as the venue can prove it:
260
+ * async-ack venues are polled with awaitOrderFinal until final / resting (non-market)
261
+ * or ASYNC_ACK_TIMEOUT_MS. A non-final result carries a "do NOT resubmit" remediation;
262
+ * a failed status poll after the ack is never reported as a failed order.
263
+ */
264
+ export async function placeAndConfirm(adapter, caps, req) {
265
+ const placed = await adapter.placeOrder(req);
266
+ const def = adapter.venue;
267
+ const listHint = `perp account orders -e ${def.id} / perp account positions -e ${def.id}`;
268
+ if (placed.finality === "final")
269
+ return { ...placed, remediation: null };
270
+ // The venue's own acknowledgement already reports a resting order (Arcus answers OPEN with the
271
+ // full remaining size; its order read can 404 for a moment after that): for a resting TIF that
272
+ // is the order's settled state — the same stopping point awaitOrderFinal uses (untilResting).
273
+ if (req.type !== "market" && req.tif !== "IOC" && req.tif !== "FOK" && (placed.status === "open" || placed.status === "partiallyFilled")) {
274
+ return { ...placed, finality: "final", remediation: null };
275
+ }
276
+ if (caps.orderAck !== "async" || (placed.orderId === null && placed.clientOrderId === null)) {
277
+ return { ...placed, remediation: `${def.displayName} acknowledged the order without a final status — do NOT resubmit; verify with: ${listHint}` };
278
+ }
279
+ const ref = placed.clientOrderId !== null ? `c:${placed.clientOrderId}` : placed.orderId;
280
+ const statusHint = `perp trade status ${ref} --symbol ${req.symbol} -e ${def.id}`;
281
+ let result;
282
+ try {
283
+ result = await awaitOrderFinal(adapter, req.symbol, orderRefOf(placed), {
284
+ timeoutMs: ASYNC_ACK_TIMEOUT_MS,
285
+ pollMs: ASYNC_ACK_POLL_MS,
286
+ untilResting: req.type !== "market",
287
+ ack: placed.status === "accepted" ? placed : null,
288
+ });
289
+ }
290
+ catch (e) {
291
+ const msg = e instanceof Error ? e.message : String(e);
292
+ return {
293
+ ...placed,
294
+ remediation: `Order was accepted by ${def.displayName} but its status could not be confirmed (${msg}). Do NOT resubmit — check: ${statusHint}`,
295
+ };
296
+ }
297
+ if (result.finality === "pending") {
298
+ return { ...result, remediation: `Order not final after ${ASYNC_ACK_TIMEOUT_MS / 1000}s — do NOT resubmit; check: ${statusHint}` };
299
+ }
300
+ return { ...result, remediation: null };
301
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Venue-level data contracts (docs/DESIGN-venue-abstraction.md §10).
3
+ *
4
+ * SSOT Rule #2: every "unknown" value is `T | null`, never optional, never a
5
+ * silently substituted default.
6
+ */
7
+ import type { ExchangeMarketInfo } from "./interface.js";
8
+ import type { ResolvedSlippage, SlippageSource } from "./slippage.js";
9
+ import type { AppliedBuilderFee } from "./builder-fee.js";
10
+ export type VenueId = "pacifica" | "hyperliquid" | "lighter" | "aster" | "lighter-rh" | "ondo" | "arcus" | "qfex";
11
+ export type LegacyVenueId = "pacifica" | "hyperliquid" | "lighter" | "aster";
12
+ export declare const VENUE_IDS: readonly VenueId[];
13
+ export declare const LEGACY_VENUE_IDS: readonly LegacyVenueId[];
14
+ export declare function isVenueId(s: string): s is VenueId;
15
+ export declare function isLegacyVenueId(s: string): s is LegacyVenueId;
16
+ export type Network = "mainnet" | "testnet";
17
+ /** How many hours one quoted funding rate covers (used for normalization). */
18
+ export type FundingRateBasis = {
19
+ kind: "perHours";
20
+ hours: number;
21
+ } | {
22
+ kind: "annualized";
23
+ };
24
+ /** Convert a raw quoted rate to a per-hour rate. */
25
+ export declare function rateToHourly(rate: number, basis: FundingRateBasis): number;
26
+ export type AssetClass = "crypto" | "equity" | "index" | "etf" | "commodity" | "fx" | "preIpo";
27
+ export interface MarketSession {
28
+ /** Whether the underlying (e.g. US equity) market is open right now. */
29
+ underlyingOpen: boolean;
30
+ /** Venue-enforced price band while the underlying is closed (Ondo weekend band, Arcus). */
31
+ priceBand: {
32
+ lower: string;
33
+ upper: string;
34
+ } | null;
35
+ /** Initial-margin multiplier while the underlying is closed (Arcus 1.5x). */
36
+ marginMultiplier: number | null;
37
+ }
38
+ export interface VenueMarket {
39
+ venue: VenueId;
40
+ /** Venue-native identifier used on the wire ("TSLA-USD.P", "16", "BTC"). */
41
+ venueSymbol: string;
42
+ /** Display symbol used by the CLI (e.g. "TSLA", "BTC"). */
43
+ symbol: string;
44
+ /** Cross-venue matching key from canonical-symbols.ts; null = not matchable. */
45
+ canonical: string | null;
46
+ assetClass: AssetClass;
47
+ settle: string;
48
+ /**
49
+ * Currency prices are quoted in ("USD", "KRW", ...); null = this venue's data path does not
50
+ * record it. A non-USD quote is never traded through perp-cli (sizing, risk limits and
51
+ * notionals assume USD) — see QFEX, design §14.12 #7.
52
+ */
53
+ quoteAsset: string | null;
54
+ markPrice: string;
55
+ indexPrice: string | null;
56
+ fundingRate: string | null;
57
+ fundingRateBasis: FundingRateBasis;
58
+ settlementIntervalHours: number;
59
+ openInterest: string | null;
60
+ volume24h: string | null;
61
+ /** null = venue publishes no tick (read-only venues). Trading venues must set it. */
62
+ tickSize: string | null;
63
+ /** null = venue publishes no lot step (read-only venues). Trading venues must set it. */
64
+ stepSize: string | null;
65
+ minSize: string | null;
66
+ minNotional: string | null;
67
+ /** null = venue publishes no leverage cap (read-only venues). */
68
+ maxLeverage: number | null;
69
+ marginTiers: Array<{
70
+ lowerBound: number;
71
+ maxLeverage: number;
72
+ }> | null;
73
+ session: MarketSession | null;
74
+ /** false = listed but not tradable right now (offline / paused / corporate action). */
75
+ tradable: boolean;
76
+ }
77
+ /** Pure mapping used by new-venue adapters to serve the legacy getMarkets(). */
78
+ /** Tick/step/leverage required for order entry. Throws when the venue did not publish them. */
79
+ export declare function requireTradingPrecision(m: VenueMarket): {
80
+ tickSize: string;
81
+ stepSize: string;
82
+ maxLeverage: number;
83
+ };
84
+ export declare function toLegacyMarketInfo(m: VenueMarket): ExchangeMarketInfo;
85
+ export interface VenueFundingRate {
86
+ venue: VenueId;
87
+ venueSymbol: string;
88
+ symbol: string;
89
+ canonical: string | null;
90
+ rate: string;
91
+ basis: FundingRateBasis;
92
+ markPrice: string | null;
93
+ nextFundingTime: number | null;
94
+ }
95
+ /** CLI-level TIF inputs. "TOB" = Pacifica top-of-book, "GTX" = Aster post-only. */
96
+ export type TimeInForce = "GTC" | "GTT" | "IOC" | "FOK" | "ALO" | "TOB" | "GTX";
97
+ export type OrderType = "market" | "limit" | "stopMarket" | "stopLimit";
98
+ export interface OrderRequest {
99
+ symbol: string;
100
+ side: "buy" | "sell";
101
+ type: OrderType;
102
+ size: string;
103
+ /** Required for limit/stopLimit. */
104
+ price: string | null;
105
+ /** Required for stopMarket/stopLimit. */
106
+ triggerPrice: string | null;
107
+ triggerSource: "mark" | "last" | "mid" | null;
108
+ /** Required for limit/stopLimit; only null is allowed for market/stopMarket. */
109
+ tif: TimeInForce | null;
110
+ reduceOnly: boolean;
111
+ /** Required when the venue's orderAck is "async". */
112
+ clientOrderId: string | null;
113
+ /**
114
+ * Resolved slippage bound (resolveSlippage(), design §13.10 #1). Required for
115
+ * type "market"; adapters whose stopMarket / TP-SL legs are protection-priced
116
+ * (Arcus) require it there too. null only for orders that carry no market leg.
117
+ */
118
+ slippage: ResolvedSlippage | null;
119
+ tpsl: {
120
+ tp: string | null;
121
+ sl: string | null;
122
+ } | null;
123
+ }
124
+ export type OrderStatus = "accepted" | "open" | "partiallyFilled" | "filled" | "rejected" | "cancelled";
125
+ export interface OrderResult {
126
+ venue: VenueId;
127
+ symbol: string;
128
+ status: OrderStatus;
129
+ /** "pending" = not final yet (async venue ack, or awaitOrderFinal timed out). */
130
+ finality: "final" | "pending";
131
+ /**
132
+ * Invariant: orderId !== null || clientOrderId !== null — except for an order the
133
+ * venue acknowledged without any id (e.g. a legacy-venue trigger order); that
134
+ * result says so in execution.notes and orderRefOf() refuses it.
135
+ */
136
+ orderId: string | null;
137
+ clientOrderId: string | null;
138
+ /** Filled base size; null = the venue response does not report it (never "0" as a stand-in). */
139
+ filledSize: string | null;
140
+ avgPrice: string | null;
141
+ /** How the venue adapter actually executed the request (design §13.1 #3, §13.10 #14). */
142
+ execution: OrderExecution;
143
+ venueRaw: unknown;
144
+ }
145
+ /**
146
+ * Transparent record of the venue-specific handling behind a uniform order
147
+ * (design §13.1 #3, §13.2, §13.10 #14). Every field is required; a value the
148
+ * venue / adapter did not report is null (SSOT Rule #2), never a guess.
149
+ */
150
+ export interface OrderExecution {
151
+ /**
152
+ * Execution method, e.g. "protectedIoc" (IOC limit at a protection price),
153
+ * "venueSlippage" (venue applies a slippage percent), "limit", "trigger",
154
+ * "triggerLimit", "nativeAmend", "cancelReplace".
155
+ */
156
+ method: string;
157
+ /** Slippage bound actually applied (after price-grid rounding when the adapter reports it). */
158
+ slippageBps: number | null;
159
+ slippageSource: SlippageSource | null;
160
+ /** Limit price sent with a market-type order; null = not echoed / computed by the venue. */
161
+ protectionPrice: string | null;
162
+ /** Reference price the protection price was derived from (mark / mid / trigger). */
163
+ referencePrice: string | null;
164
+ /** TIF as requested by the caller (common or venue alias). */
165
+ tifRequested: string | null;
166
+ /** Venue-native TIF actually sent (e.g. "Gtc", "GTT", "GTX"). */
167
+ tifSent: string | null;
168
+ /** Unix ms when a resting order expires (GTC mapped to GTT); null = no venue expiry. */
169
+ expiresAt: number | null;
170
+ /**
171
+ * Builder fee this order carried (settings builder.<venue> + confirmed approval); null = none.
172
+ * A configured fee that was NOT attached says why in `notes`.
173
+ */
174
+ builderFee: AppliedBuilderFee | null;
175
+ notes: string[];
176
+ }
177
+ /**
178
+ * Attach an execution record to a result built without one. Fields of `partial`
179
+ * that are absent are recorded as null (unknown), never defaulted to a value.
180
+ */
181
+ export declare function withExecution(result: Omit<OrderResult, "execution">, method: string, partial: Partial<Omit<OrderExecution, "method">>): OrderResult;
182
+ export type OrderRef = {
183
+ kind: "orderId";
184
+ value: string;
185
+ } | {
186
+ kind: "clientOrderId";
187
+ value: string;
188
+ };
189
+ export interface AmendRequest {
190
+ symbol: string;
191
+ ref: OrderRef;
192
+ price: string;
193
+ size: string;
194
+ /**
195
+ * Time-in-force of the replacement. null = keep the resting order's own TIF
196
+ * (venues that can read it back, e.g. Arcus); cancel→replace venues that cannot
197
+ * read post-only back (Ondo) refuse null.
198
+ */
199
+ tif: TimeInForce | null;
200
+ /**
201
+ * Allow an explicit `tif` that flips the post-only flag encoded in the order's generated
202
+ * client id (Ondo / Pacifica): without it such a conflict is INVALID_PARAMS (CLI --force-tif).
203
+ */
204
+ forceTif?: boolean;
205
+ }
206
+ export interface CancelResult {
207
+ venue: VenueId;
208
+ /** Only orders whose cancel the venue result proves (per order). */
209
+ cancelled: OrderRef[];
210
+ failed: Array<{
211
+ ref: OrderRef | null;
212
+ error: string;
213
+ }>;
214
+ /** Cancels sent but not proven by the venue result — status pending; verify before acting again. */
215
+ pending?: Array<{
216
+ ref: OrderRef | null;
217
+ reason: string;
218
+ remediation: string;
219
+ }>;
220
+ venueRaw: unknown;
221
+ }
222
+ export interface LeverageResult {
223
+ venue: VenueId;
224
+ symbol: string;
225
+ leverage: number;
226
+ marginMode: "cross" | "isolated";
227
+ venueRaw: unknown;
228
+ }
229
+ export declare function orderRefOf(r: Pick<OrderResult, "orderId" | "clientOrderId">): OrderRef;
230
+ /** Parse a CLI order reference: "c:<id>" → clientOrderId, otherwise orderId. */
231
+ export declare function parseOrderRef(s: string): OrderRef;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Venue-level data contracts (docs/DESIGN-venue-abstraction.md §10).
3
+ *
4
+ * SSOT Rule #2: every "unknown" value is `T | null`, never optional, never a
5
+ * silently substituted default.
6
+ */
7
+ export const VENUE_IDS = [
8
+ "pacifica", "hyperliquid", "lighter", "aster",
9
+ "lighter-rh", "ondo", "arcus", "qfex",
10
+ ];
11
+ export const LEGACY_VENUE_IDS = ["pacifica", "hyperliquid", "lighter", "aster"];
12
+ export function isVenueId(s) {
13
+ return VENUE_IDS.includes(s);
14
+ }
15
+ export function isLegacyVenueId(s) {
16
+ return LEGACY_VENUE_IDS.includes(s);
17
+ }
18
+ /** Convert a raw quoted rate to a per-hour rate. */
19
+ export function rateToHourly(rate, basis) {
20
+ if (basis.kind === "perHours")
21
+ return rate / basis.hours;
22
+ return rate / (24 * 365);
23
+ }
24
+ function decimalsOf(step) {
25
+ const s = step.includes("e") || step.includes("E") ? Number(step).toFixed(20).replace(/0+$/, "") : step;
26
+ const dot = s.indexOf(".");
27
+ return dot === -1 ? 0 : s.length - dot - 1;
28
+ }
29
+ /** Pure mapping used by new-venue adapters to serve the legacy getMarkets(). */
30
+ /** Tick/step/leverage required for order entry. Throws when the venue did not publish them. */
31
+ export function requireTradingPrecision(m) {
32
+ if (m.tickSize === null || m.stepSize === null || m.maxLeverage === null) {
33
+ throw new Error(`${m.venue} ${m.symbol}: tick size / step size / max leverage not published — market is not tradable via API`);
34
+ }
35
+ return { tickSize: m.tickSize, stepSize: m.stepSize, maxLeverage: m.maxLeverage };
36
+ }
37
+ export function toLegacyMarketInfo(m) {
38
+ const p = requireTradingPrecision(m);
39
+ const hours = m.fundingRateBasis.kind === "perHours" ? m.fundingRateBasis.hours : null;
40
+ return {
41
+ symbol: m.symbol,
42
+ markPrice: m.markPrice,
43
+ indexPrice: m.indexPrice,
44
+ fundingRate: m.fundingRate,
45
+ volume24h: m.volume24h,
46
+ openInterest: m.openInterest,
47
+ maxLeverage: p.maxLeverage,
48
+ sizeDecimals: decimalsOf(p.stepSize),
49
+ stepSize: p.stepSize,
50
+ fundingHours: hours,
51
+ marginTiers: m.marginTiers,
52
+ };
53
+ }
54
+ /**
55
+ * Attach an execution record to a result built without one. Fields of `partial`
56
+ * that are absent are recorded as null (unknown), never defaulted to a value.
57
+ */
58
+ export function withExecution(result, method, partial) {
59
+ return {
60
+ ...result,
61
+ execution: {
62
+ method,
63
+ slippageBps: partial.slippageBps ?? null,
64
+ slippageSource: partial.slippageSource ?? null,
65
+ protectionPrice: partial.protectionPrice ?? null,
66
+ referencePrice: partial.referencePrice ?? null,
67
+ tifRequested: partial.tifRequested ?? null,
68
+ tifSent: partial.tifSent ?? null,
69
+ expiresAt: partial.expiresAt ?? null,
70
+ builderFee: partial.builderFee ?? null,
71
+ notes: partial.notes ?? [],
72
+ },
73
+ };
74
+ }
75
+ export function orderRefOf(r) {
76
+ if (r.orderId !== null)
77
+ return { kind: "orderId", value: r.orderId };
78
+ if (r.clientOrderId !== null)
79
+ return { kind: "clientOrderId", value: r.clientOrderId };
80
+ throw new Error("OrderResult invariant violated: neither orderId nor clientOrderId is set");
81
+ }
82
+ /** Parse a CLI order reference: "c:<id>" → clientOrderId, otherwise orderId. */
83
+ export function parseOrderRef(s) {
84
+ if (s.startsWith("c:"))
85
+ return { kind: "clientOrderId", value: s.slice(2) };
86
+ return { kind: "orderId", value: s };
87
+ }