perp-cli 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +152 -13
  2. package/dist/agent-wallet/store.d.ts +3 -2
  3. package/dist/agent-wallet/store.js +2 -2
  4. package/dist/api/public/hyperliquid.js +4 -0
  5. package/dist/api/public/pacifica.js +83 -13
  6. package/dist/api/public/urls.d.ts +2 -0
  7. package/dist/api/public/urls.js +3 -0
  8. package/dist/arb/utils.d.ts +1 -2
  9. package/dist/arb/utils.js +1 -2
  10. package/dist/bot/apex/pulse.js +3 -0
  11. package/dist/bot/apex/radar.js +2 -1
  12. package/dist/bot/engine.d.ts +10 -1
  13. package/dist/bot/engine.js +14 -6
  14. package/dist/bot/indicators.d.ts +7 -0
  15. package/dist/bot/indicators.js +16 -0
  16. package/dist/bot/perp-order.d.ts +4 -4
  17. package/dist/bot/perp-order.js +5 -5
  18. package/dist/bot/quarantine.d.ts +1 -1
  19. package/dist/bot/quarantine.js +1 -1
  20. package/dist/bot/strategies/apex-strategy.js +3 -2
  21. package/dist/bot/strategies/engine-mm.js +6 -5
  22. package/dist/bot/strategies/funding-arb-strategy.js +9 -9
  23. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
  24. package/dist/bot/strategies/funding-arb-utils.js +43 -24
  25. package/dist/bot/strategies/funding-auto.js +2 -2
  26. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  27. package/dist/bot/strategies/liquidation-mm.js +8 -2
  28. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  29. package/dist/bot/strategies/momentum-breakout.js +11 -3
  30. package/dist/bot/strategies/spot-perp-arb-strategy.js +23 -12
  31. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  32. package/dist/bot/strategies/spot-perp-executor.js +1 -1
  33. package/dist/bot/strategy-types.d.ts +2 -1
  34. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  35. package/dist/bot/tui/BotDashboard.js +1 -1
  36. package/dist/cache.js +12 -6
  37. package/dist/cli-program.d.ts +15 -0
  38. package/dist/cli-program.js +125 -5
  39. package/dist/commands/agent-import.d.ts +7 -0
  40. package/dist/commands/agent-import.js +30 -1
  41. package/dist/commands/agent.d.ts +6 -3
  42. package/dist/commands/agent.js +160 -12
  43. package/dist/commands/arb/index.js +4 -4
  44. package/dist/commands/arb-auto.js +21 -17
  45. package/dist/commands/arb.d.ts +12 -0
  46. package/dist/commands/arb.js +61 -143
  47. package/dist/commands/bot.js +1 -1
  48. package/dist/commands/builder.js +1 -1
  49. package/dist/commands/funds.js +113 -3
  50. package/dist/commands/init.d.ts +1 -1
  51. package/dist/commands/init.js +27 -7
  52. package/dist/commands/manage.js +23 -21
  53. package/dist/commands/market.js +168 -36
  54. package/dist/commands/options.d.ts +9 -0
  55. package/dist/commands/options.js +134 -0
  56. package/dist/commands/pacifica-earn.d.ts +5 -0
  57. package/dist/commands/pacifica-earn.js +468 -0
  58. package/dist/commands/pacifica-orders.d.ts +14 -0
  59. package/dist/commands/pacifica-orders.js +142 -0
  60. package/dist/commands/portfolio.js +32 -47
  61. package/dist/commands/rebalance.d.ts +1 -0
  62. package/dist/commands/rebalance.js +15 -0
  63. package/dist/commands/risk.d.ts +11 -2
  64. package/dist/commands/risk.js +39 -44
  65. package/dist/commands/trade.d.ts +1 -0
  66. package/dist/commands/trade.js +63 -58
  67. package/dist/commands/wallet.js +1 -1
  68. package/dist/config.d.ts +2 -2
  69. package/dist/config.js +15 -4
  70. package/dist/constants.js +6 -0
  71. package/dist/dashboard/server.js +1 -1
  72. package/dist/dashboard/ui.js +1 -1
  73. package/dist/errors.d.ts +24 -1
  74. package/dist/errors.js +25 -3
  75. package/dist/exchanges/arcus/adapter.d.ts +7 -7
  76. package/dist/exchanges/arcus/adapter.js +12 -12
  77. package/dist/exchanges/arcus/agent.d.ts +1 -1
  78. package/dist/exchanges/arcus/agent.js +1 -1
  79. package/dist/exchanges/arcus/client.js +1 -1
  80. package/dist/exchanges/arcus/signing.d.ts +2 -3
  81. package/dist/exchanges/arcus/signing.js +1 -1
  82. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  83. package/dist/exchanges/arcus/spot-chain.js +1 -1
  84. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  85. package/dist/exchanges/arcus/spot-router.js +2 -2
  86. package/dist/exchanges/arcus/spot.d.ts +2 -3
  87. package/dist/exchanges/arcus/spot.js +2 -3
  88. package/dist/exchanges/aster-spot.d.ts +3 -3
  89. package/dist/exchanges/aster-spot.js +6 -9
  90. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  91. package/dist/exchanges/aster-typed-data.js +1 -1
  92. package/dist/exchanges/aster.d.ts +9 -4
  93. package/dist/exchanges/aster.js +125 -55
  94. package/dist/exchanges/builder-fee.d.ts +1 -1
  95. package/dist/exchanges/builder-fee.js +13 -2
  96. package/dist/exchanges/canonical-symbols.d.ts +1 -2
  97. package/dist/exchanges/canonical-symbols.js +26 -2
  98. package/dist/exchanges/derive/client.d.ts +37 -0
  99. package/dist/exchanges/derive/client.js +135 -0
  100. package/dist/exchanges/derive/options.d.ts +68 -0
  101. package/dist/exchanges/derive/options.js +360 -0
  102. package/dist/exchanges/extended/adapter.d.ts +82 -0
  103. package/dist/exchanges/extended/adapter.js +179 -0
  104. package/dist/exchanges/extended/client.d.ts +38 -0
  105. package/dist/exchanges/extended/client.js +88 -0
  106. package/dist/exchanges/extended/public.d.ts +130 -0
  107. package/dist/exchanges/extended/public.js +429 -0
  108. package/dist/exchanges/grvt/adapter.d.ts +86 -0
  109. package/dist/exchanges/grvt/adapter.js +206 -0
  110. package/dist/exchanges/grvt/client.d.ts +25 -0
  111. package/dist/exchanges/grvt/client.js +86 -0
  112. package/dist/exchanges/grvt/public.d.ts +151 -0
  113. package/dist/exchanges/grvt/public.js +438 -0
  114. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  115. package/dist/exchanges/hyperliquid-spot.js +5 -5
  116. package/dist/exchanges/hyperliquid.d.ts +22 -16
  117. package/dist/exchanges/hyperliquid.js +80 -55
  118. package/dist/exchanges/index.d.ts +1 -1
  119. package/dist/exchanges/index.js +1 -1
  120. package/dist/exchanges/interface.d.ts +97 -8
  121. package/dist/exchanges/interface.js +19 -2
  122. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  123. package/dist/exchanges/ioc-outcome.js +1 -1
  124. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  125. package/dist/exchanges/legacy-trading-v2.js +7 -2
  126. package/dist/exchanges/lighter-public.d.ts +2 -2
  127. package/dist/exchanges/lighter-public.js +2 -2
  128. package/dist/exchanges/lighter-signer.d.ts +1 -1
  129. package/dist/exchanges/lighter-signer.js +1 -1
  130. package/dist/exchanges/lighter-spot.d.ts +3 -4
  131. package/dist/exchanges/lighter-spot.js +6 -7
  132. package/dist/exchanges/lighter.d.ts +11 -11
  133. package/dist/exchanges/lighter.js +28 -14
  134. package/dist/exchanges/ondo/adapter.d.ts +5 -6
  135. package/dist/exchanges/ondo/adapter.js +16 -14
  136. package/dist/exchanges/ondo/agent.d.ts +1 -1
  137. package/dist/exchanges/ondo/agent.js +1 -1
  138. package/dist/exchanges/ondo/client.d.ts +1 -1
  139. package/dist/exchanges/ondo/client.js +2 -2
  140. package/dist/exchanges/ondo/public.d.ts +8 -1
  141. package/dist/exchanges/ondo/public.js +13 -2
  142. package/dist/exchanges/ondo/spot.d.ts +2 -2
  143. package/dist/exchanges/ondo/spot.js +3 -3
  144. package/dist/exchanges/options-interface.d.ts +158 -0
  145. package/dist/exchanges/options-interface.js +17 -0
  146. package/dist/exchanges/options-venues.d.ts +12 -0
  147. package/dist/exchanges/options-venues.js +35 -0
  148. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  149. package/dist/exchanges/pacifica-atomic.js +440 -0
  150. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  151. package/dist/exchanges/pacifica-decimal.js +51 -0
  152. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  153. package/dist/exchanges/pacifica-presigned.js +141 -0
  154. package/dist/exchanges/pacifica-print.d.ts +200 -0
  155. package/dist/exchanges/pacifica-print.js +417 -0
  156. package/dist/exchanges/pacifica-spot.d.ts +2 -2
  157. package/dist/exchanges/pacifica-spot.js +3 -3
  158. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  159. package/dist/exchanges/pacifica-typed-data.js +1 -1
  160. package/dist/exchanges/pacifica-vaults.d.ts +121 -0
  161. package/dist/exchanges/pacifica-vaults.js +264 -0
  162. package/dist/exchanges/pacifica.d.ts +32 -5
  163. package/dist/exchanges/pacifica.js +114 -48
  164. package/dist/exchanges/precision.d.ts +3 -1
  165. package/dist/exchanges/precision.js +9 -2
  166. package/dist/exchanges/qfex/adapter.d.ts +18 -5
  167. package/dist/exchanges/qfex/adapter.js +32 -12
  168. package/dist/exchanges/qfex/agent.d.ts +1 -1
  169. package/dist/exchanges/qfex/agent.js +1 -1
  170. package/dist/exchanges/qfex/client.d.ts +1 -1
  171. package/dist/exchanges/qfex/client.js +1 -1
  172. package/dist/exchanges/qfex/errors.d.ts +1 -1
  173. package/dist/exchanges/qfex/errors.js +2 -2
  174. package/dist/exchanges/qfex/public.d.ts +3 -3
  175. package/dist/exchanges/qfex/public.js +3 -3
  176. package/dist/exchanges/qfex/signing.d.ts +2 -2
  177. package/dist/exchanges/qfex/signing.js +4 -5
  178. package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
  179. package/dist/exchanges/qfex/ws-transport.js +3 -3
  180. package/dist/exchanges/rate-limiter.d.ts +1 -1
  181. package/dist/exchanges/rate-limiter.js +5 -2
  182. package/dist/exchanges/registry.d.ts +1 -1
  183. package/dist/exchanges/registry.js +1 -1
  184. package/dist/exchanges/risex/adapter.d.ts +148 -0
  185. package/dist/exchanges/risex/adapter.js +796 -0
  186. package/dist/exchanges/risex/agent.d.ts +154 -0
  187. package/dist/exchanges/risex/agent.js +384 -0
  188. package/dist/exchanges/risex/client.d.ts +47 -0
  189. package/dist/exchanges/risex/client.js +174 -0
  190. package/dist/exchanges/risex/nonce.d.ts +43 -0
  191. package/dist/exchanges/risex/nonce.js +212 -0
  192. package/dist/exchanges/risex/public.d.ts +64 -0
  193. package/dist/exchanges/risex/public.js +150 -0
  194. package/dist/exchanges/risex/session.d.ts +71 -0
  195. package/dist/exchanges/risex/session.js +171 -0
  196. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  197. package/dist/exchanges/risex/typed-data.js +240 -0
  198. package/dist/exchanges/risex/units.d.ts +29 -0
  199. package/dist/exchanges/risex/units.js +104 -0
  200. package/dist/exchanges/slippage.d.ts +1 -1
  201. package/dist/exchanges/slippage.js +1 -1
  202. package/dist/exchanges/spot-factory.d.ts +3 -3
  203. package/dist/exchanges/spot-factory.js +7 -7
  204. package/dist/exchanges/spot-interface.d.ts +5 -5
  205. package/dist/exchanges/spot-interface.js +2 -2
  206. package/dist/exchanges/spot-result.d.ts +3 -3
  207. package/dist/exchanges/spot-result.js +3 -3
  208. package/dist/exchanges/trading-v2.d.ts +3 -3
  209. package/dist/exchanges/trading-v2.js +9 -6
  210. package/dist/exchanges/txflow/adapter.d.ts +54 -0
  211. package/dist/exchanges/txflow/adapter.js +118 -0
  212. package/dist/exchanges/txflow/client.d.ts +47 -0
  213. package/dist/exchanges/txflow/client.js +153 -0
  214. package/dist/exchanges/txflow/public.d.ts +76 -0
  215. package/dist/exchanges/txflow/public.js +284 -0
  216. package/dist/exchanges/unknown-symbol.d.ts +29 -0
  217. package/dist/exchanges/unknown-symbol.js +51 -0
  218. package/dist/exchanges/variational/adapter.d.ts +71 -0
  219. package/dist/exchanges/variational/adapter.js +137 -0
  220. package/dist/exchanges/variational/client.d.ts +21 -0
  221. package/dist/exchanges/variational/client.js +66 -0
  222. package/dist/exchanges/variational/public.d.ts +126 -0
  223. package/dist/exchanges/variational/public.js +430 -0
  224. package/dist/exchanges/venue-types.d.ts +6 -7
  225. package/dist/exchanges/venue-types.js +2 -2
  226. package/dist/exchanges/venue.d.ts +51 -8
  227. package/dist/exchanges/venue.js +54 -4
  228. package/dist/exchanges/venues/arcus.d.ts +1 -2
  229. package/dist/exchanges/venues/arcus.js +5 -4
  230. package/dist/exchanges/venues/aster.d.ts +2 -2
  231. package/dist/exchanges/venues/aster.js +6 -4
  232. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  233. package/dist/exchanges/venues/create-helpers.js +3 -3
  234. package/dist/exchanges/venues/extended.d.ts +21 -0
  235. package/dist/exchanges/venues/extended.js +96 -0
  236. package/dist/exchanges/venues/grvt.d.ts +30 -0
  237. package/dist/exchanges/venues/grvt.js +98 -0
  238. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  239. package/dist/exchanges/venues/hyperliquid.js +10 -8
  240. package/dist/exchanges/venues/index.d.ts +7 -2
  241. package/dist/exchanges/venues/index.js +8 -2
  242. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  243. package/dist/exchanges/venues/lighter-family.js +9 -11
  244. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  245. package/dist/exchanges/venues/lighter-rh.js +7 -5
  246. package/dist/exchanges/venues/lighter.d.ts +1 -1
  247. package/dist/exchanges/venues/lighter.js +5 -3
  248. package/dist/exchanges/venues/ondo.d.ts +1 -1
  249. package/dist/exchanges/venues/ondo.js +5 -3
  250. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  251. package/dist/exchanges/venues/pacifica.js +6 -4
  252. package/dist/exchanges/venues/qfex.d.ts +1 -1
  253. package/dist/exchanges/venues/qfex.js +4 -2
  254. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  255. package/dist/exchanges/venues/risex-release.js +14 -0
  256. package/dist/exchanges/venues/risex.d.ts +31 -0
  257. package/dist/exchanges/venues/risex.js +116 -0
  258. package/dist/exchanges/venues/txflow.d.ts +18 -0
  259. package/dist/exchanges/venues/txflow.js +77 -0
  260. package/dist/exchanges/venues/variational.d.ts +21 -0
  261. package/dist/exchanges/venues/variational.js +85 -0
  262. package/dist/execution-log.d.ts +1 -1
  263. package/dist/funding/legacy-public.d.ts +1 -1
  264. package/dist/funding/legacy-public.js +9 -4
  265. package/dist/funding/normalize.d.ts +1 -1
  266. package/dist/funding/normalize.js +1 -1
  267. package/dist/funding/rates.d.ts +44 -4
  268. package/dist/funding/rates.js +56 -12
  269. package/dist/index.js +7 -6
  270. package/dist/jobs.d.ts +1 -0
  271. package/dist/jobs.js +25 -1
  272. package/dist/landing.d.ts +2 -0
  273. package/dist/landing.js +1 -1
  274. package/dist/mcp-pacifica-earn.d.ts +9 -0
  275. package/dist/mcp-pacifica-earn.js +58 -0
  276. package/dist/mcp-server.js +113 -59
  277. package/dist/pacifica/client.d.ts +67 -4
  278. package/dist/pacifica/client.js +170 -8
  279. package/dist/pacifica/rate-limit.d.ts +144 -0
  280. package/dist/pacifica/rate-limit.js +267 -0
  281. package/dist/pacifica/types/order.d.ts +2 -1
  282. package/dist/plan-executor.js +7 -2
  283. package/dist/rebalance.js +1 -1
  284. package/dist/risk.d.ts +2 -1
  285. package/dist/risk.js +2 -4
  286. package/dist/settings.d.ts +32 -3
  287. package/dist/signer/ows-evm.d.ts +1 -1
  288. package/dist/signer/ows-evm.js +1 -1
  289. package/dist/smart-order.d.ts +1 -1
  290. package/dist/smart-order.js +1 -1
  291. package/dist/strategies/funding-arb.d.ts +1 -1
  292. package/dist/strategies/funding-arb.js +1 -1
  293. package/dist/trade-validator.d.ts +1 -1
  294. package/dist/trade-validator.js +9 -4
  295. package/dist/utils.d.ts +12 -3
  296. package/dist/utils.js +11 -3
  297. package/dist/venue-health.d.ts +40 -0
  298. package/dist/venue-health.js +75 -0
  299. package/dist/withdraw-gate.d.ts +1 -1
  300. package/dist/withdraw-gate.js +1 -1
  301. package/dist/ws/index.d.ts +959 -0
  302. package/dist/ws/index.js +4295 -0
  303. package/dist/ws/node.d.ts +975 -0
  304. package/dist/ws/node.js +4366 -0
  305. package/dist/ws/pacifica-instrument.d.ts +8 -0
  306. package/dist/ws/pacifica-instrument.js +8 -0
  307. package/dist/ws-cli/credentials.d.ts +62 -0
  308. package/dist/ws-cli/credentials.js +126 -0
  309. package/package.json +58 -10
  310. package/skills/perp-cli/SKILL.md +62 -9
  311. package/skills/perp-cli/references/agent-operations.md +7 -0
  312. package/skills/perp-cli/references/commands.md +76 -5
  313. package/skills/perp-cli/references/ws-library.md +492 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Pacifica vaults ("lakes" in the API) — docs.pacifica.fi/vaults.md and pages under it,
3
+ * api-documentation/api/rest-api/vaults/*.
4
+ *
5
+ * Product: a vault is a real Pacifica account traded by its manager; depositors (LPs) get LP
6
+ * shares at NAV (lp_balance ÷ lp_shares, 1:1 when the LP class is empty). PnL is attributed to the
7
+ * LP / manager classes before every deposit or withdrawal; the manager takes `manager_profit_share`
8
+ * of profit above the high-water mark. Minimum deposit 10 USDC; `deposit_cap` caps the LP balance
9
+ * (manager deposits are not counted). A withdrawal redeems shares (amount = shares × NAV) straight
10
+ * into the depositor's own trading balance — the API has no destination field — and is allowed
11
+ * only when: enough shares; `deposit_min_duration_ms` elapsed since the depositor's LAST deposit
12
+ * (each deposit resets the lock for the whole balance); inside the withdrawal window when
13
+ * `withdraw_window_s` / `withdraw_duration_s` are set (open while now_s mod window < duration,
14
+ * epoch-anchored); and the vault has free balance (a fully deployed vault may pay partially).
15
+ * The venue publishes no APY: `apy` is null.
16
+ *
17
+ * Endpoints (GET unauthenticated; writes signed):
18
+ * GET /lake/list[?account=] vaults (+ the account's `user_share`)
19
+ * GET /lake/get_deposit_whitelist?lake= deposit-address whitelist (app.pacifica.fi; not in the API docs)
20
+ * GET /account?account=<vault> · /positions the vault account's live equity / positions
21
+ * POST /lake/deposit type deposit_to_lake {lake, amount, idempotency_key?}
22
+ * POST /lake/withdraw type withdraw_from_lake {lake, shares, idempotency_key?}
23
+ * Live 2026-10-19: absent config fields are omitted (→ null here), and `user_share` was null for a
24
+ * vault's own manager (it counts LP shares; the manager's are `manager_shares`).
25
+ */
26
+ import { PerpError } from "../errors.js";
27
+ import { mulDecimal, subDecimal } from "./precision.js";
28
+ import { cmpDecimal, divDecimal, isPlainDecimal, trimDecimal } from "./pacifica-decimal.js";
29
+ import { envelopeData, requireUsdcAmount, PLAIN_POSITIVE } from "./pacifica-print.js";
30
+ import { buildPresigned } from "./pacifica-presigned.js";
31
+ /** docs vaults/depositing: "Minimum deposit — Constant 10 USDC." */
32
+ export const VAULT_MIN_DEPOSIT_USDC = "10";
33
+ function bad(what, row) {
34
+ return new PerpError("EXCHANGE_ERROR", `Pacifica ${what}: ${JSON.stringify(row).slice(0, 200)}`, { exchange: "pacifica" });
35
+ }
36
+ function str(r, k) {
37
+ const v = r[k];
38
+ return typeof v === "string" && v !== "" ? v : null;
39
+ }
40
+ function dec(r, k) {
41
+ const v = r[k];
42
+ return isPlainDecimal(v) ? trimDecimal(v) : null;
43
+ }
44
+ function reqDec(r, k) {
45
+ const v = dec(r, k);
46
+ if (v === null)
47
+ throw bad(`/lake/list row without a decimal ${k}`, r);
48
+ return v;
49
+ }
50
+ function int(r, k) {
51
+ const v = r[k];
52
+ return typeof v === "number" && Number.isInteger(v) ? v : null;
53
+ }
54
+ function add(a, b) {
55
+ return subDecimal(a, `-${b}`.replace(/^--/, ""));
56
+ }
57
+ function parseConfig(x) {
58
+ const c = x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
59
+ return {
60
+ depositCap: dec(c, "deposit_cap"),
61
+ managerProfitShare: dec(c, "manager_profit_share"),
62
+ managerLossShare: dec(c, "manager_loss_share"),
63
+ depositMinDurationMs: int(c, "deposit_min_duration_ms"),
64
+ managerMinBalancePortion: dec(c, "manager_min_balance_portion"),
65
+ managerLiquidationBalancePortion: dec(c, "manager_liquidation_balance_portion"),
66
+ withdrawWindowS: int(c, "withdraw_window_s"),
67
+ withdrawDurationS: int(c, "withdraw_duration_s"),
68
+ };
69
+ }
70
+ export function parseVault(x) {
71
+ if (x === null || typeof x !== "object" || Array.isArray(x))
72
+ throw bad("/lake/list row is not an object", x);
73
+ const r = x;
74
+ const address = str(r, "address");
75
+ if (address === null)
76
+ throw bad("/lake/list row without an address", r);
77
+ const lpShares = reqDec(r, "lp_shares");
78
+ const managerShares = reqDec(r, "manager_shares");
79
+ const lpBalance = reqDec(r, "lp_balance");
80
+ const managerBalance = reqDec(r, "manager_balance");
81
+ const created = r.created_at;
82
+ return {
83
+ schema: 1,
84
+ address,
85
+ nickname: str(r, "nickname"),
86
+ creator: str(r, "creator"),
87
+ manager: str(r, "manager"),
88
+ referrer: str(r, "referrer"),
89
+ createdAt: typeof created === "number" && created > 0 ? created : null,
90
+ lpShares, managerShares, lpBalance, managerBalance,
91
+ tvl: add(lpBalance, managerBalance),
92
+ equitySnapshot: dec(r, "last_checked_equity"),
93
+ highWatermark: dec(r, "high_watermark"),
94
+ navPerLpShare: divDecimal(lpBalance, lpShares, 12),
95
+ navPerManagerShare: divDecimal(managerBalance, managerShares, 12),
96
+ apy: null,
97
+ config: parseConfig(r.config),
98
+ userShares: dec(r, "user_share"),
99
+ };
100
+ }
101
+ /** GET /lake/list `data` (`{lakes: [...]}`). */
102
+ export function parseVaultList(data) {
103
+ const rows = data !== null && typeof data === "object" && !Array.isArray(data) ? data.lakes : undefined;
104
+ if (!Array.isArray(rows))
105
+ throw bad("/lake/list without a lakes list", data);
106
+ return rows.map(parseVault);
107
+ }
108
+ export async function fetchVaults(client, account) {
109
+ const env = await client.getEnvelope("/lake/list", account !== null ? { account } : {});
110
+ return parseVaultList(envelopeData(env, "/lake/list"));
111
+ }
112
+ /**
113
+ * Vault by address, or by a nickname (case-insensitive) that matches EXACTLY ONE vault. Nicknames
114
+ * are not unique in practice (live 2026-10-19: "Pacifica Vault" / "Pacifica vault", "Shark vault" /
115
+ * "shark vault"), so two or more matches are INVALID_PARAMS listing every candidate — never the
116
+ * first / newest one (a copycat vault must not receive a deposit).
117
+ */
118
+ export function findVault(vaults, ref) {
119
+ const r = ref.trim();
120
+ const byAddress = vaults.find((x) => x.address === r);
121
+ if (byAddress !== undefined)
122
+ return byAddress;
123
+ const matches = vaults.filter((x) => x.nickname !== null && x.nickname.toLowerCase() === r.toLowerCase());
124
+ if (matches.length === 1)
125
+ return matches[0];
126
+ if (matches.length === 0) {
127
+ throw new PerpError("SYMBOL_NOT_FOUND", `No Pacifica vault "${ref}" (address or nickname)`, {
128
+ exchange: "pacifica", remediation: "List them: perp -e pacifica vault list",
129
+ });
130
+ }
131
+ const candidates = matches.map((x) => ({ address: x.address, nickname: x.nickname, manager: x.manager, tvl: x.tvl }));
132
+ throw new PerpError("INVALID_PARAMS", `Vault nickname "${ref}" is ambiguous — ${matches.length} vaults match: ${candidates.map((c) => `${c.address} (${c.nickname}, manager ${c.manager ?? "none"}, TVL ${c.tvl})`).join("; ")}`, {
133
+ exchange: "pacifica", candidates, remediation: "Pass the vault ADDRESS (check its manager first: perp -e pacifica vault info <address>)",
134
+ });
135
+ }
136
+ /** UNOFFICIAL endpoint (not in the API docs; used by app.pacifica.fi). Advisory only — the venue's own refusal is final. */
137
+ export const DEPOSIT_WHITELIST_API_STATUS = "unofficial";
138
+ export async function fetchDepositWhitelist(client, vault) {
139
+ const data = envelopeData(await client.getEnvelope("/lake/get_deposit_whitelist", { lake: vault }), "/lake/get_deposit_whitelist");
140
+ const a = data !== null && typeof data === "object" ? data.addresses : undefined;
141
+ if (!Array.isArray(a) || !a.every((s) => typeof s === "string"))
142
+ throw bad("/lake/get_deposit_whitelist without an addresses list", data);
143
+ return a;
144
+ }
145
+ /** docs: open while current_unix_time_s mod withdraw_window_s < withdraw_duration_s (epoch-anchored). */
146
+ export function withdrawWindowAt(c, nowMs) {
147
+ if (c.withdrawWindowS === null || c.withdrawDurationS === null || c.withdrawWindowS <= 0)
148
+ return { restricted: false, open: true, nextChangeAt: null };
149
+ const now = Math.floor(nowMs / 1000);
150
+ const start = now - (now % c.withdrawWindowS);
151
+ const open = now % c.withdrawWindowS < c.withdrawDurationS;
152
+ return { restricted: true, open, nextChangeAt: (open ? start + c.withdrawDurationS : start + c.withdrawWindowS) * 1000 };
153
+ }
154
+ /** Shares this account holds in the vault: manager shares for the manager, else LP `user_share`. */
155
+ export function heldShares(v, account) {
156
+ if (v.manager === account)
157
+ return { role: "manager", shares: v.managerShares, nav: v.navPerManagerShare };
158
+ return { role: "lp", shares: v.userShares, nav: v.navPerLpShare };
159
+ }
160
+ /**
161
+ * Validate a deposit (docs vaults/depositing: min 10 USDC, deposit cap on the LP balance,
162
+ * available_to_withdraw, deposit whitelist). Throws INVALID_PARAMS / INSUFFICIENT_BALANCE / POLICY_DENIED.
163
+ */
164
+ export function planVaultDeposit(v, account, amountRaw, ctx) {
165
+ const amount = requireUsdcAmount(amountRaw, "vault deposit");
166
+ const where = `vault ${v.nickname ?? v.address}`;
167
+ if (cmpDecimal(amount, VAULT_MIN_DEPOSIT_USDC) < 0) {
168
+ throw new PerpError("INVALID_PARAMS", `${where}: deposit ${amount} USDC is below the ${VAULT_MIN_DEPOSIT_USDC} USDC minimum`, { exchange: "pacifica", remediation: `Deposit at least ${VAULT_MIN_DEPOSIT_USDC} USDC` });
169
+ }
170
+ const { role } = heldShares(v, account);
171
+ // NAV of the class receiving the deposit must be defined and positive (docs: shares = amount ×
172
+ // shares ÷ balance; 1:1 only when the class is EMPTY). Live: XAU_Stable_Growth had lp_shares 10,
173
+ // lp_balance 0 — a deposit there would be priced at NAV 0.
174
+ const [clsShares, clsBalance] = role === "lp" ? [v.lpShares, v.lpBalance] : [v.managerShares, v.managerBalance];
175
+ const sharesZero = cmpDecimal(clsShares, "0") === 0;
176
+ if (sharesZero ? cmpDecimal(clsBalance, "0") !== 0 : cmpDecimal(clsBalance, "0") <= 0) {
177
+ throw new PerpError("INVALID_PARAMS", `${where}: ${role} NAV is ${sharesZero ? "undefined" : "zero"} (${role} shares ${clsShares}, balance ${clsBalance}) — a deposit cannot be priced`, {
178
+ exchange: "pacifica", remediation: "Pick another vault (perp -e pacifica vault list)",
179
+ });
180
+ }
181
+ if (role === "lp" && v.config.depositCap !== null && cmpDecimal(add(v.lpBalance, amount), v.config.depositCap) > 0) {
182
+ throw new PerpError("INVALID_PARAMS", `${where}: LP balance ${v.lpBalance} + ${amount} exceeds the deposit cap ${v.config.depositCap} USDC`, {
183
+ exchange: "pacifica", remediation: `Deposit at most ${subDecimal(v.config.depositCap, v.lpBalance)} USDC`,
184
+ });
185
+ }
186
+ if (cmpDecimal(ctx.availableToWithdraw, amount) < 0) {
187
+ throw new PerpError("INSUFFICIENT_BALANCE", `${where}: deposit ${amount} USDC exceeds available_to_withdraw ${ctx.availableToWithdraw}`, { exchange: "pacifica", remediation: "Deposit less, or free margin first (perp -e pacifica portfolio)" });
188
+ }
189
+ const warnings = [
190
+ "A vault is a third-party trading pool: you can lose up to the whole deposit (Pacifica does not vet or insure managers).",
191
+ `Manager: ${v.manager ?? "none"} — verify this address is the manager you intend to fund.`,
192
+ ];
193
+ // Unofficial whitelist endpoint: advisory only; the venue's refusal is final.
194
+ if (ctx.whitelist === null)
195
+ warnings.push("Deposit whitelist unknown (unofficial endpoint unreadable); the venue refuses if the vault restricts depositors.");
196
+ else if (ctx.whitelist.length > 0 && !ctx.whitelist.includes(account))
197
+ warnings.push(`The vault's deposit whitelist (unofficial endpoint) lists ${ctx.whitelist.length} address(es), not ${account}: the venue will likely refuse.`);
198
+ const lock = v.config.depositMinDurationMs;
199
+ if (lock !== null && lock > 0)
200
+ warnings.push(`Lock-up: no withdrawal for ${(lock / 3_600_000).toFixed(2)} h after this deposit — and the timer restarts for your WHOLE balance in this vault.`);
201
+ if (v.config.withdrawWindowS !== null && v.config.withdrawDurationS !== null) {
202
+ warnings.push(`Withdrawals open only ${v.config.withdrawDurationS} s out of every ${v.config.withdrawWindowS} s (epoch-anchored window).`);
203
+ }
204
+ if (v.manager === null)
205
+ warnings.push("The vault has no manager yet (fresh vault): nobody trades it until a manager claims the seat.");
206
+ if (v.config.managerProfitShare !== null)
207
+ warnings.push(`Manager performance fee: ${v.config.managerProfitShare} of profit above the high-water mark.`);
208
+ return {
209
+ schema: 1, vault: v.address, nickname: v.nickname, manager: v.manager, role, amount,
210
+ // docs: shares_minted = amount × shares ÷ balance of the class (1:1 when the class is empty)
211
+ estimatedShares: role === "lp"
212
+ ? (cmpDecimal(v.lpShares, "0") === 0 ? amount : divDecimal(mulDecimal(amount, v.lpShares), v.lpBalance, 6))
213
+ : (cmpDecimal(v.managerShares, "0") === 0 ? amount : divDecimal(mulDecimal(amount, v.managerShares), v.managerBalance, 6)),
214
+ lockMs: lock, warnings,
215
+ };
216
+ }
217
+ /** Validate a withdrawal: shares held, withdrawal window. Throws POSITION_NOT_FOUND / INVALID_PARAMS / POLICY_DENIED. */
218
+ export function planVaultWithdraw(v, account, sharesRaw, ctx) {
219
+ const where = `vault ${v.nickname ?? v.address}`;
220
+ const held = heldShares(v, account);
221
+ if (held.shares === null || cmpDecimal(held.shares, "0") <= 0) {
222
+ throw new PerpError("POSITION_NOT_FOUND", `${account} holds no ${held.role} shares in ${where}`, { exchange: "pacifica", remediation: "See your vault positions: perp -e pacifica vault positions" });
223
+ }
224
+ let shares;
225
+ if (sharesRaw === "all")
226
+ shares = held.shares;
227
+ else {
228
+ const s = String(sharesRaw).trim();
229
+ if (!PLAIN_POSITIVE.test(s) || !(Number(s) > 0))
230
+ throw new PerpError("INVALID_PARAMS", `shares must be a positive decimal number without leading zeros (got "${sharesRaw}")`, { exchange: "pacifica", remediation: "Pass the shares to redeem, or --all" });
231
+ shares = trimDecimal(s);
232
+ }
233
+ if (cmpDecimal(shares, held.shares) > 0) {
234
+ throw new PerpError("SIZE_TOO_LARGE", `${where}: redeeming ${shares} shares exceeds the ${held.shares} held`, { exchange: "pacifica", remediation: `Redeem at most ${held.shares} (or --all)` });
235
+ }
236
+ const window = withdrawWindowAt(v.config, ctx.nowMs);
237
+ if (!window.open) {
238
+ throw new PerpError("POLICY_DENIED", `${where}: the withdrawal window is closed; it next opens at ${new Date(window.nextChangeAt).toISOString()}`, {
239
+ exchange: "pacifica", remediation: "Withdraw while the window is open (perp -e pacifica vault info <vault> shows it)",
240
+ });
241
+ }
242
+ const estimatedAmount = held.nav === null ? null : trimDecimal(mulDecimal(shares, held.nav));
243
+ const warnings = [];
244
+ const lock = v.config.depositMinDurationMs;
245
+ if (lock !== null && lock > 0)
246
+ warnings.push(`The venue refuses a withdrawal within ${(lock / 3_600_000).toFixed(2)} h of your most recent deposit into this vault (not checkable here).`);
247
+ if (estimatedAmount !== null && ctx.vaultAvailableToWithdraw !== null && cmpDecimal(estimatedAmount, ctx.vaultAvailableToWithdraw) > 0) {
248
+ warnings.push(`The vault's free balance ${ctx.vaultAvailableToWithdraw} USDC is below the ≈${estimatedAmount} USDC redeemed: the venue may pay only part.`);
249
+ }
250
+ return { schema: 1, vault: v.address, nickname: v.nickname, manager: v.manager, role: held.role, shares, heldShares: held.shares, estimatedAmount, destination: account, window, warnings };
251
+ }
252
+ // ── Write requests (pure) ───────────────────────────────────────────────
253
+ export function buildVaultDepositRequest(vault, amount, identity, opts) {
254
+ return buildPresigned({
255
+ type: "deposit_to_lake", path: "/lake/deposit", identity, timestamp: opts.timestamp, expiryWindow: opts.expiryWindow,
256
+ payload: { lake: vault, amount, idempotency_key: opts.idempotencyKey },
257
+ });
258
+ }
259
+ export function buildVaultWithdrawRequest(vault, shares, identity, opts) {
260
+ return buildPresigned({
261
+ type: "withdraw_from_lake", path: "/lake/withdraw", identity, timestamp: opts.timestamp, expiryWindow: opts.expiryWindow,
262
+ payload: { lake: vault, shares, idempotency_key: opts.idempotencyKey },
263
+ });
264
+ }
@@ -1,6 +1,6 @@
1
1
  import { Keypair } from "@solana/web3.js";
2
2
  import { PacificaClient, type Network } from "../pacifica/index.js";
3
- import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
3
+ import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangePublicTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
4
4
  import type { ResolvedSlippage } from "./slippage.js";
5
5
  import type { SolanaSigner } from "../signer/index.js";
6
6
  import type { AgentMeta } from "../settings.js";
@@ -8,7 +8,7 @@ import type { TimeInForce as PacificaTif } from "../pacifica/types/order.js";
8
8
  import type { IocOutcome } from "./ioc-outcome.js";
9
9
  import { type BuilderApproval, type BuilderDecision, type BuilderFeeGate, type BuilderTarget } from "./builder-fee.js";
10
10
  /**
11
- * CLI TIF → Pacifica TIF (design §13.3). GTC / IOC / ALO / TOB are native; venue
11
+ * CLI TIF → Pacifica TIF. GTC / IOC / ALO / TOB are native; venue
12
12
  * aliases GTT → GTC and GTX → ALO; no TIF → GTC (uniform default). FOK (never
13
13
  * emulated) and unknown values → uniform CAPABILITY_UNSUPPORTED.
14
14
  */
@@ -114,6 +114,7 @@ export declare const PACIFICA_WITHDRAW_MIN_USDC = "1";
114
114
  * else (other status, unparseable body, already a PerpError) → `e` unchanged (outcome unknown).
115
115
  */
116
116
  export declare function pacificaWithdrawRejection(e: unknown, amount: string): unknown;
117
+ /** A POST the venue refused with HTTP 4xx — incl. RATE_LIMITED (429 / pre-send throttle, nothing sent). */
117
118
  export declare function pacificaRefused(e: unknown): boolean;
118
119
  /** A cancel the venue refused (HTTP 4xx), settled from the order's own history_by_id rows. */
119
120
  export type PacificaCancelRefusal = {
@@ -156,6 +157,12 @@ export interface PacificaAmendResult {
156
157
  }
157
158
  /** Pacifica order_type of a stop / TP / SL order (stop_market, stop_limit, take_profit_*, stop_loss_*). */
158
159
  export declare function isPacificaStopType(type: string): boolean;
160
+ /**
161
+ * Trade direction of a Pacifica fill `side` (position effect): open_long / close_short buy,
162
+ * open_short / close_long sell; bid / ask for rows that carry an order side. Anything else →
163
+ * EXCHANGE_ERROR (never a guessed direction). Used by the public tape and the account fills.
164
+ */
165
+ export declare function pacificaFillSide(side: string, endpoint: string): "buy" | "sell";
159
166
  export declare class PacificaAdapter implements ExchangeAdapter {
160
167
  readonly name = "pacifica";
161
168
  readonly chain = "solana";
@@ -213,6 +220,17 @@ export declare class PacificaAdapter implements ExchangeAdapter {
213
220
  * mint) — never the agent, which cannot move funds.
214
221
  */
215
222
  masterSolanaSigner(): SolanaSigner;
223
+ /**
224
+ * The active signer for an account request whose body the caller builds itself (Print / vault
225
+ * writes): `account` (master address) and `agentWallet` (agent pubkey, or null when the master
226
+ * signs). Resolving it signs nothing. NO_SIGNER_AVAILABLE / AGENT_EXPIRED as `_resolveSigner`.
227
+ */
228
+ signingIdentity(): {
229
+ tier: "agent" | "master" | "pk";
230
+ account: string;
231
+ agentWallet: string | null;
232
+ signMessage: (msg: Uint8Array) => Promise<Uint8Array>;
233
+ };
216
234
  /** Access the underlying Keypair (only available with LocalSolanaSigner). */
217
235
  get keypair(): Keypair;
218
236
  private _getPrices;
@@ -251,7 +269,7 @@ export declare class PacificaAdapter implements ExchangeAdapter {
251
269
  clientOrderId?: string;
252
270
  }): Promise<unknown>;
253
271
  /**
254
- * Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable, design §13.10 #4),
272
+ * Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable),
255
273
  * parsed from the result's content (so copies of it work): its `fillReport` — the terminal
256
274
  * history_by_id row (filled / cancelled / rejected) with a reported filled_amount — for the
257
275
  * same order id as the create response. null otherwise — reduce-only orders (not polled),
@@ -321,7 +339,7 @@ export declare class PacificaAdapter implements ExchangeAdapter {
321
339
  */
322
340
  editOrder(symbol: string, orderId: string, price: string, size: string): Promise<unknown>;
323
341
  /**
324
- * Amend that keeps the order's TIF (design §13.4, §13.10 #10). `size` = new open size.
342
+ * Amend that keeps the order's TIF. `size` = new open size.
325
343
  * TIF = `tif` when the caller gives one, else the TIF encoded in the order's generated
326
344
  * client_order_id; unknown → uniform CAPABILITY_UNSUPPORTED (never guessed).
327
345
  * - ALO: native edit (the venue re-creates the order as ALO, keeping its client_order_id).
@@ -368,7 +386,16 @@ export declare class PacificaAdapter implements ExchangeAdapter {
368
386
  assetId?: number;
369
387
  routeType?: number;
370
388
  }): Promise<unknown>;
371
- getRecentTrades(symbol: string, _limit?: number): Promise<ExchangeTrade[]>;
389
+ /**
390
+ * Public tape (GET /trades). Every fill is listed TWICE — a `fulfill_taker` row and a
391
+ * `fulfill_maker` row, each with that party's position effect as `side` (live 2026-10-14:
392
+ * taker `open_short` paired with maker `close_short` at the same time / price / amount). The
393
+ * tape is the taker rows, with the taker's direction: open_long / close_short buy, open_short /
394
+ * close_long sell (the same mapping as the account fills, PACIFICA_FILL_*_SIDES). Was every row
395
+ * of both parties mapped through `side === "bid"` → all "sell". No de-duplication: two taker
396
+ * rows with equal time / price / amount are two fills (one order filling two makers).
397
+ */
398
+ getRecentTrades(symbol: string, _limit?: number): Promise<ExchangePublicTrade[]>;
372
399
  getFundingHistory(symbol: string, limit?: number): Promise<{
373
400
  time: number;
374
401
  rate: string;
@@ -1,6 +1,10 @@
1
+ import { nonEmptyOrListed, symbolNotFound } from "./unknown-symbol.js";
2
+ import { withTapeSkipped } from "./interface.js";
1
3
  import { PacificaClient } from "../pacifica/index.js";
4
+ import { isPacificaRateLimited } from "../pacifica/rate-limit.js";
2
5
  import { LocalSolanaSigner } from "../signer/index.js";
3
6
  import { isExpired } from "../agent-wallet/expiry.js";
7
+ import { isPacificaPerpetual } from "../ws/pacifica-instrument.js";
4
8
  import { randomUUID } from "crypto";
5
9
  import { PerpError, extractErrorMessage, isDefiniteOrderFailure, orderNotSent, orderOutcomeUnknown } from "../errors.js";
6
10
  import { parseFiniteVenueNumber, venueRateField } from "../utils/numeric.js";
@@ -10,7 +14,7 @@ import { requireCapabilities } from "./venue.js";
10
14
  import { PACIFICA_UUID_RE, pacificaVenue } from "./venues/pacifica.js";
11
15
  import { approvalFromCeiling, builderGateOf, rateToBps, requireFeeBps, NO_BUILDER } from "./builder-fee.js";
12
16
  /**
13
- * CLI TIF → Pacifica TIF (design §13.3). GTC / IOC / ALO / TOB are native; venue
17
+ * CLI TIF → Pacifica TIF. GTC / IOC / ALO / TOB are native; venue
14
18
  * aliases GTT → GTC and GTX → ALO; no TIF → GTC (uniform default). FOK (never
15
19
  * emulated) and unknown values → uniform CAPABILITY_UNSUPPORTED.
16
20
  */
@@ -20,7 +24,7 @@ export function pacificaTif(tif) {
20
24
  return validateLegacyTif(pacificaVenue, requireCapabilities(pacificaVenue), tif);
21
25
  }
22
26
  /**
23
- * Generated client_order_id carrying the order's TIF (design §13.4, §13.10 #10, like Ondo's
27
+ * Generated client_order_id carrying the order's TIF (like Ondo's
24
28
  * `pcli-<p|n>-…`): Pacifica takes a full UUID and never returns the TIF (GET /orders has no
25
29
  * tif field), so amend recovers it from the id. Layout: a UUID v4 whose first 6 hex digits are
26
30
  * `5ac1f` + TIF code (1 GTC, 2 IOC, 3 ALO, 4 TOB). The native edit keeps the client_order_id.
@@ -274,7 +278,10 @@ export function pacificaWithdrawRejection(e, amount) {
274
278
  }
275
279
  return new PerpError("ACTION_REJECTED", `Pacifica withdraw ${amount} USDC refused (nothing was withdrawn): ${body.error}`, details);
276
280
  }
281
+ /** A POST the venue refused with HTTP 4xx — incl. RATE_LIMITED (429 / pre-send throttle, nothing sent). */
277
282
  export function pacificaRefused(e) {
283
+ if (isPacificaRateLimited(e))
284
+ return true;
278
285
  return !(e instanceof PerpError) && /^POST \S+ failed \(4\d\d\)/.test(extractErrorMessage(e));
279
286
  }
280
287
  /**
@@ -320,13 +327,13 @@ function requireInstrumentType(rows) {
320
327
  }
321
328
  return rows;
322
329
  }
323
- /** Perp markets of /info (spot rows such as SOL-USDC excluded, design §12). */
330
+ /** Perp markets of /info: instrument_type "perpetual" only (spot rows such as SOL-USDC, or any other type, excluded). */
324
331
  function perpInfoRows(rows) {
325
- return requireInstrumentType(rows).filter((m) => m.instrument_type !== "spot");
332
+ return requireInstrumentType(rows).filter(isPacificaPerpetual);
326
333
  }
327
- /** Wire symbols of /info spot markets. */
328
- function spotInfoSymbols(rows) {
329
- return new Set(requireInstrumentType(rows).filter((m) => m.instrument_type === "spot").map((m) => m.symbol));
334
+ /** Wire symbols of /info markets that are not perps (spot, any other instrument_type). */
335
+ function nonPerpInfoSymbols(rows) {
336
+ return new Set(requireInstrumentType(rows).filter((m) => !isPacificaPerpetual(m)).map((m) => m.symbol));
330
337
  }
331
338
  /** Pacifica order_type of a stop / TP / SL order (stop_market, stop_limit, take_profit_*, stop_loss_*). */
332
339
  export function isPacificaStopType(type) {
@@ -335,6 +342,18 @@ export function isPacificaStopType(type) {
335
342
  /** Fill sides of GET /trades/history (position effect) plus the order sides bid / ask. */
336
343
  const PACIFICA_FILL_BUY_SIDES = new Set(["open_long", "close_short", "bid"]);
337
344
  const PACIFICA_FILL_SELL_SIDES = new Set(["open_short", "close_long", "ask"]);
345
+ /**
346
+ * Trade direction of a Pacifica fill `side` (position effect): open_long / close_short buy,
347
+ * open_short / close_long sell; bid / ask for rows that carry an order side. Anything else →
348
+ * EXCHANGE_ERROR (never a guessed direction). Used by the public tape and the account fills.
349
+ */
350
+ export function pacificaFillSide(side, endpoint) {
351
+ if (PACIFICA_FILL_BUY_SIDES.has(side))
352
+ return "buy";
353
+ if (PACIFICA_FILL_SELL_SIDES.has(side))
354
+ return "sell";
355
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica ${endpoint} row with unknown side "${side}"`, { exchange: "pacifica" });
356
+ }
338
357
  /** `created_at` (ms) of a Pacifica history row; non-numeric → EXCHANGE_ERROR (never NaN). */
339
358
  function pacificaTime(row, endpoint) {
340
359
  const t = Number(pacificaField(row, "created_at", endpoint));
@@ -491,6 +510,15 @@ export class PacificaAdapter {
491
510
  remediation: "Select the master wallet (--ows <wallet> or perp wallet use <wallet>), or do it on the Pacifica website",
492
511
  });
493
512
  }
513
+ /**
514
+ * The active signer for an account request whose body the caller builds itself (Print / vault
515
+ * writes): `account` (master address) and `agentWallet` (agent pubkey, or null when the master
516
+ * signs). Resolving it signs nothing. NO_SIGNER_AVAILABLE / AGENT_EXPIRED as `_resolveSigner`.
517
+ */
518
+ signingIdentity() {
519
+ const r = this._resolveSigner();
520
+ return { tier: r.tier, account: this.account, agentWallet: r.tier === "agent" ? r.address : null, signMessage: (msg) => r.signer.signMessage(msg) };
521
+ }
494
522
  /** Access the underlying Keypair (only available with LocalSolanaSigner). */
495
523
  get keypair() {
496
524
  if (this._solanaSigner instanceof LocalSolanaSigner) {
@@ -552,7 +580,7 @@ export class PacificaAdapter {
552
580
  this._getPrices(),
553
581
  ]);
554
582
  const priceMap = new Map(prices.map((p) => [p.symbol, p]));
555
- // /info also lists spot markets (e.g. SOL-USDC); those are not perps (§12).
583
+ // /info also lists spot markets (e.g. SOL-USDC); those are not perps.
556
584
  const result = perpInfoRows(markets).map((m) => {
557
585
  const p = priceMap.get(m.symbol);
558
586
  // Derive sizeDecimals from lot_size (e.g. "0.001" → 3, "1" → 0)
@@ -579,7 +607,12 @@ export class PacificaAdapter {
579
607
  return result;
580
608
  }
581
609
  async getOrderbook(symbol) {
582
- const book = await this.client.getBook(symbol);
610
+ const book = await this.client.getBook(symbol).catch((e) => {
611
+ // GET /book for an unlisted symbol: HTTP 400 {"error":"Book not found: NOPE"} (live 2026-10-14).
612
+ if (e instanceof Error && /^GET \/book failed \(400\):.*Book not found/.test(e.message))
613
+ throw symbolNotFound("pacifica", symbol);
614
+ throw e;
615
+ });
583
616
  return {
584
617
  bids: book.l[0].map((e) => [e.p, e.a]),
585
618
  asks: book.l[1].map((e) => [e.p, e.a]),
@@ -736,8 +769,8 @@ export class PacificaAdapter {
736
769
  throw new Error("No private key configured — account data unavailable. Run: perp setup");
737
770
  // GET /orders covers every market; spot orders (SOL-USDC) belong to the spot adapter.
738
771
  const [orders, info] = await Promise.all([this.client.getOrders(this.account), this.client.getInfo()]);
739
- const spotSymbols = spotInfoSymbols(info);
740
- return orders.filter((o) => !spotSymbols.has(String(o.symbol))).map((o) => {
772
+ const nonPerp = nonPerpInfoSymbols(info);
773
+ return orders.filter((o) => !nonPerp.has(String(o.symbol))).map((o) => {
741
774
  const raw = o;
742
775
  return {
743
776
  orderId: String(raw.order_id),
@@ -786,7 +819,7 @@ export class PacificaAdapter {
786
819
  const filled = parseFloat(String(latest.filled_amount ?? "0"));
787
820
  if (status === "filled" || filled > 0) {
788
821
  // Terminal row (filled / cancelled remainder) with a reported filled_amount = the
789
- // order's final fill (design §13.10 #4). A non-terminal partial row is not final.
822
+ // order's final fill. A non-terminal partial row is not final.
790
823
  const reported = latest.filled_amount;
791
824
  const terminal = status === "filled" || status === "cancelled" || status === "rejected";
792
825
  if (terminal && reported !== undefined && reported !== null && String(reported).trim() !== ""
@@ -830,7 +863,7 @@ export class PacificaAdapter {
830
863
  return result;
831
864
  }
832
865
  /**
833
- * Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable, design §13.10 #4),
866
+ * Venue-final outcome of a raw marketOrder() result (IocOutcomeCapable),
834
867
  * parsed from the result's content (so copies of it work): its `fillReport` — the terminal
835
868
  * history_by_id row (filled / cancelled / rejected) with a reported filled_amount — for the
836
869
  * same order id as the create response. null otherwise — reduce-only orders (not polled),
@@ -891,7 +924,7 @@ export class PacificaAdapter {
891
924
  */
892
925
  async limitOrder(symbol, side, price, size, opts) {
893
926
  this.ensureSigner();
894
- const tif = pacificaTif(opts?.tif); // §13.3 common mapping; FOK → CAPABILITY_UNSUPPORTED
927
+ const tif = pacificaTif(opts?.tif); // common mapping; FOK → CAPABILITY_UNSUPPORTED
895
928
  // The caller's client order id (validated UUID) is sent as given; without one a generated
896
929
  // client_order_id records the TIF for a later TIF-preserving amend. Either way the id sent
897
930
  // rides on the result (`clientOrderId`).
@@ -1034,7 +1067,7 @@ export class PacificaAdapter {
1034
1067
  return this.client.editOrder({ symbol, order_id: Number(orderId), price, amount: size }, this.account, this.signMessage);
1035
1068
  }
1036
1069
  /**
1037
- * Amend that keeps the order's TIF (design §13.4, §13.10 #10). `size` = new open size.
1070
+ * Amend that keeps the order's TIF. `size` = new open size.
1038
1071
  * TIF = `tif` when the caller gives one, else the TIF encoded in the order's generated
1039
1072
  * client_order_id; unknown → uniform CAPABILITY_UNSUPPORTED (never guessed).
1040
1073
  * - ALO: native edit (the venue re-creates the order as ALO, keeping its client_order_id).
@@ -1314,48 +1347,85 @@ export class PacificaAdapter {
1314
1347
  return this.client.withdraw({ amount }, this.account, (msg) => master.signMessage(msg))
1315
1348
  .catch((e) => { throw pacificaWithdrawRejection(e, amount); });
1316
1349
  }
1350
+ /**
1351
+ * Public tape (GET /trades). Every fill is listed TWICE — a `fulfill_taker` row and a
1352
+ * `fulfill_maker` row, each with that party's position effect as `side` (live 2026-10-14:
1353
+ * taker `open_short` paired with maker `close_short` at the same time / price / amount). The
1354
+ * tape is the taker rows, with the taker's direction: open_long / close_short buy, open_short /
1355
+ * close_long sell (the same mapping as the account fills, PACIFICA_FILL_*_SIDES). Was every row
1356
+ * of both parties mapped through `side === "bid"` → all "sell". No de-duplication: two taker
1357
+ * rows with equal time / price / amount are two fills (one order filling two makers).
1358
+ */
1317
1359
  async getRecentTrades(symbol, _limit = 20) {
1318
1360
  const trades = await this.client.getTrades(symbol);
1319
- // Deduplicate by time+price+size+side (API sometimes returns duplicate entries)
1320
- const seen = new Set();
1321
- const unique = trades.filter((t) => {
1322
- const key = `${t.created_at}:${t.price}:${t.amount}:${t.side}`;
1323
- if (seen.has(key))
1324
- return false;
1325
- seen.add(key);
1326
- return true;
1327
- });
1328
- return unique.slice(0, _limit).map((t) => ({
1329
- time: Number(t.created_at ?? 0),
1330
- symbol,
1331
- side: t.side === "bid" ? "buy" : "sell",
1332
- price: t.price,
1333
- size: t.amount,
1334
- fee: String(t.fee ?? "0"),
1335
- }));
1361
+ if (!Array.isArray(trades)) {
1362
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica /trades returned no trade list (${JSON.stringify(trades).slice(0, 120)})`, { exchange: "pacifica" });
1363
+ }
1364
+ // The docs list event_type fulfill_taker / fulfill_maker and side open_long / open_short /
1365
+ // close_long / close_short without declaring the sets closed: a row with any other value is
1366
+ // skipped (stderr warning + meta.skippedRows), never given a guessed direction, and does not
1367
+ // fail the rest of the tape.
1368
+ const skippedWhy = [];
1369
+ const rows = [];
1370
+ for (const t of trades) {
1371
+ const ev = pacificaField(t, "event_type", "/trades");
1372
+ if (ev === "fulfill_maker")
1373
+ continue;
1374
+ if (ev !== "fulfill_taker") {
1375
+ skippedWhy.push(`undocumented event_type "${ev}"`);
1376
+ continue;
1377
+ }
1378
+ const side = pacificaField(t, "side", "/trades");
1379
+ if (!PACIFICA_FILL_BUY_SIDES.has(side) && !PACIFICA_FILL_SELL_SIDES.has(side)) {
1380
+ skippedWhy.push(`undocumented side "${side}"`);
1381
+ continue;
1382
+ }
1383
+ if (rows.length >= _limit)
1384
+ continue;
1385
+ rows.push({
1386
+ time: pacificaTime(t, "/trades"),
1387
+ symbol,
1388
+ side: pacificaFillSide(side, "/trades"),
1389
+ price: pacificaField(t, "price", "/trades"),
1390
+ size: pacificaField(t, "amount", "/trades"),
1391
+ fee: null, // the public tape carries no fee
1392
+ });
1393
+ }
1394
+ const skipped = skippedWhy.length === 0 ? null : { count: skippedWhy.length, reason: [...new Set(skippedWhy)].join(", ") };
1395
+ if (skipped !== null)
1396
+ process.stderr.write(`[pacifica] /trades: skipped ${skipped.count} row(s) — ${skipped.reason}\n`);
1397
+ return withTapeSkipped(await nonEmptyOrListed(this, symbol, rows), skipped);
1336
1398
  }
1337
1399
  async getFundingHistory(symbol, limit = 10) {
1338
- const result = await this.client.getFundingHistory(symbol, { limit });
1339
- const data = (result.data ?? result);
1340
- if (!Array.isArray(data))
1341
- return [];
1342
- return data.map((h) => ({
1343
- time: Number(h.created_at ?? 0),
1344
- rate: venueRateField(h.funding_rate, "funding history funding_rate", "pacifica"),
1345
- price: String(h.oracle_price ?? "0"),
1400
+ // The client unwraps the {success, data} envelope: the answer is the row list itself.
1401
+ const data = await this.client.getFundingHistory(symbol, { limit });
1402
+ if (!Array.isArray(data)) {
1403
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica /funding_rate/history returned no row list (${JSON.stringify(data).slice(0, 120)})`, { exchange: "pacifica" });
1404
+ }
1405
+ // `200 []` for an unlisted symbol (live): checked against the market directory.
1406
+ return nonEmptyOrListed(this, symbol, data.map((h) => {
1407
+ const oracle = h.oracle_price;
1408
+ return {
1409
+ time: pacificaTime(h, "/funding_rate/history"),
1410
+ rate: venueRateField(h.funding_rate, "funding history funding_rate", "pacifica"),
1411
+ // absent → null (unknown), never "0"
1412
+ price: (typeof oracle === "string" && oracle !== "") || (typeof oracle === "number" && Number.isFinite(oracle)) ? String(oracle) : null,
1413
+ };
1346
1414
  }));
1347
1415
  }
1348
1416
  async getKlines(symbol, interval, startTime, endTime) {
1349
1417
  const klines = await this.client.getKline(symbol, interval, startTime, endTime);
1350
- return klines.map((k) => ({
1418
+ // `200 []` for an unlisted symbol (live): checked against the market directory.
1419
+ return nonEmptyOrListed(this, symbol, klines.map((k) => ({
1351
1420
  time: k.t,
1352
1421
  open: k.o,
1353
1422
  high: k.h,
1354
1423
  low: k.l,
1355
1424
  close: k.c,
1356
1425
  volume: k.v,
1426
+ quoteVolume: null, // /kline carries base volume only
1357
1427
  trades: k.n,
1358
- }));
1428
+ })));
1359
1429
  }
1360
1430
  /**
1361
1431
  * Recent orders, newest first (GET /orders/history; PacificaClient.get already unwraps the
@@ -1402,15 +1472,11 @@ export class PacificaAdapter {
1402
1472
  throw new PerpError("EXCHANGE_ERROR", `Pacifica /trades/history returned no list (${JSON.stringify(rows).slice(0, 120)})`, { exchange: "pacifica" });
1403
1473
  }
1404
1474
  return rows.slice(0, limit).map((t) => {
1405
- const side = pacificaField(t, "side", "/trades/history");
1406
- const buy = PACIFICA_FILL_BUY_SIDES.has(side);
1407
- if (!buy && !PACIFICA_FILL_SELL_SIDES.has(side)) {
1408
- throw new PerpError("EXCHANGE_ERROR", `Pacifica /trades/history row with unknown side "${side}"`, { exchange: "pacifica" });
1409
- }
1475
+ const side = pacificaFillSide(pacificaField(t, "side", "/trades/history"), "/trades/history");
1410
1476
  return {
1411
1477
  time: pacificaTime(t, "/trades/history"),
1412
1478
  symbol: pacificaField(t, "symbol", "/trades/history"),
1413
- side: buy ? "buy" : "sell",
1479
+ side,
1414
1480
  price: pacificaField(t, "price", "/trades/history"),
1415
1481
  size: pacificaField(t, "amount", "/trades/history"),
1416
1482
  fee: pacificaField(t, "fee", "/trades/history"),