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,83 @@
1
+ /**
2
+ * Pacifica Spot Adapter (design §12).
3
+ *
4
+ * Spot is a different `symbol` ("SOL-USDC") on the same Pacifica order
5
+ * endpoints as perps, signed by the same Solana signer (incl. agent_wallet).
6
+ * Account model is unified: spot BUYs spend the shared USDC perp margin and
7
+ * spot holdings are cross-margin collateral — there is no transfer.
8
+ *
9
+ * Venue constraints: reduce_only must be false, no TP/SL, no builder code.
10
+ *
11
+ * Unverifiable without a funded live account (documented, not guessed):
12
+ * - whether GET /orders lists spot orders (assumed: it covers all markets;
13
+ * we filter by spot symbol);
14
+ * - the exact shape of /orders/history_by_id rows (fill price field), so
15
+ * avgPrice is reported only when the row exposes `average_filled_price`.
16
+ */
17
+ import type { SpotAdapter, SpotMarketInfo, SpotBalance, SpotOrderResult, SpotOpenOrdersCapable, SpotCancelAllCapable } from "./spot-interface.js";
18
+ import type { ResolvedSlippage } from "./slippage.js";
19
+ import { type PacificaAdapter } from "./pacifica.js";
20
+ import type { IocOutcome } from "./ioc-outcome.js";
21
+ export declare class PacificaSpotAdapter implements SpotAdapter, SpotOpenOrdersCapable, SpotCancelAllCapable {
22
+ readonly name = "pacifica";
23
+ private _perp;
24
+ /** base ("SOL") → /info spot row */
25
+ private _spot;
26
+ private _initialized;
27
+ /** Venue-final IOC outcomes keyed by the SpotOrderResult returned (see iocOutcomeOf). */
28
+ private readonly _iocOutcomes;
29
+ constructor(perp: PacificaAdapter);
30
+ init(): Promise<void>;
31
+ private _loadSpotInfo;
32
+ /** bare base or "BASE/USDC" → /info spot row (wire symbol "BASE-USDC"). */
33
+ private _resolve;
34
+ getSpotMarkets(): Promise<SpotMarketInfo[]>;
35
+ getSpotOrderbook(symbol: string): Promise<{
36
+ bids: [string, string][];
37
+ asks: [string, string][];
38
+ }>;
39
+ getSpotBalances(): Promise<SpotBalance[]>;
40
+ /** Mark price for min-notional / slippage checks. */
41
+ private _mark;
42
+ private _checkNotional;
43
+ /** Throws unless the response is an accepted order; returns order_id when present. */
44
+ private _accepted;
45
+ /**
46
+ * Poll history_by_id until a terminal state (or, for resting orders, the first
47
+ * row) — otherwise finality "pending", never resubmit. The order is already
48
+ * accepted, so a poll error is reported as pending (with the order id), not thrown.
49
+ * `amount` = the placed size (a "filled" row without filled_amount filled all of it).
50
+ * `finalFill` = the reported filled_amount of a terminal row (filled / cancelled /
51
+ * rejected) when `wantTerminal` (IOC / market) — the venue-final fill for iocOutcomeOf;
52
+ * null otherwise (no report, resting order, pending), never the placed amount.
53
+ */
54
+ private _settle;
55
+ /** Assemble the result and remember its venue-final fill (when proven) for iocOutcomeOf. */
56
+ private _withOutcome;
57
+ /**
58
+ * Venue-final outcome of a SpotOrderResult returned by spotMarketOrder / IOC
59
+ * spotLimitOrder (IocOutcomeCapable, design §13.10 #4): set only when the polled
60
+ * history row was terminal and reported filled_amount. null for anything else.
61
+ */
62
+ iocOutcomeOf(raw: unknown): IocOutcome | null;
63
+ spotMarketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
64
+ slippage: ResolvedSlippage;
65
+ }): Promise<SpotOrderResult>;
66
+ spotLimitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
67
+ tif?: string;
68
+ }): Promise<SpotOrderResult>;
69
+ spotCancelOrder(symbol: string, orderId: string): Promise<SpotOrderResult>;
70
+ getSpotOpenOrders(symbol: string | null): Promise<Array<{
71
+ orderId: string;
72
+ symbol: string;
73
+ side: "buy" | "sell";
74
+ price: string;
75
+ size: string;
76
+ filled: string;
77
+ }>>;
78
+ /**
79
+ * Cancels spot orders one by one. The venue `cancel_all` with all_symbols
80
+ * would also cancel perp orders, so it is never used here.
81
+ */
82
+ spotCancelAllOrders(symbol: string | null): Promise<SpotOrderResult[]>;
83
+ }
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Pacifica Spot Adapter (design §12).
3
+ *
4
+ * Spot is a different `symbol` ("SOL-USDC") on the same Pacifica order
5
+ * endpoints as perps, signed by the same Solana signer (incl. agent_wallet).
6
+ * Account model is unified: spot BUYs spend the shared USDC perp margin and
7
+ * spot holdings are cross-margin collateral — there is no transfer.
8
+ *
9
+ * Venue constraints: reduce_only must be false, no TP/SL, no builder code.
10
+ *
11
+ * Unverifiable without a funded live account (documented, not guessed):
12
+ * - whether GET /orders lists spot orders (assumed: it covers all markets;
13
+ * we filter by spot symbol);
14
+ * - the exact shape of /orders/history_by_id rows (fill price field), so
15
+ * avgPrice is reported only when the row exposes `average_filled_price`.
16
+ */
17
+ import { pacificaCreateRejection, pacificaLatestHistoryRow, pacificaTif } from "./pacifica.js";
18
+ import { PerpError, orderNotSent } from "../errors.js";
19
+ import { floorSize, requireOnTick, requirePositive, subDecimal } from "./precision.js";
20
+ const QUOTE = "USDC";
21
+ let buyWarned = false;
22
+ function warnSharedMarginOnce() {
23
+ if (buyWarned)
24
+ return;
25
+ buyWarned = true;
26
+ console.error("[pacifica-spot] Warning: spot buys on Pacifica spend the shared USDC perp margin (unified account) and reduce perp available margin.");
27
+ }
28
+ /** Required venue field as a string (Rule #2: a missing field is an error, not "0"). */
29
+ function reqField(row, field) {
30
+ const v = row[field];
31
+ if (typeof v === "string" || (typeof v === "number" && Number.isFinite(v)))
32
+ return String(v);
33
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica order row ${String(row.order_id)} has no "${field}"`, { exchange: "pacifica" });
34
+ }
35
+ function decimalsOf(step) {
36
+ const frac = step.split(".")[1];
37
+ return frac ? frac.replace(/0+$/, "").length : 0;
38
+ }
39
+ export class PacificaSpotAdapter {
40
+ name = "pacifica";
41
+ _perp;
42
+ /** base ("SOL") → /info spot row */
43
+ _spot = new Map();
44
+ _initialized = false;
45
+ /** Venue-final IOC outcomes keyed by the SpotOrderResult returned (see iocOutcomeOf). */
46
+ _iocOutcomes = new WeakMap();
47
+ constructor(perp) {
48
+ this._perp = perp;
49
+ }
50
+ async init() {
51
+ if (this._initialized)
52
+ return;
53
+ await this._loadSpotInfo();
54
+ this._initialized = true;
55
+ }
56
+ async _loadSpotInfo() {
57
+ const rows = (await this._perp.sdk.getInfo()).filter((m) => m.instrument_type === "spot");
58
+ this._spot.clear();
59
+ for (const m of rows)
60
+ this._spot.set(m.base_asset.toUpperCase(), m);
61
+ return rows;
62
+ }
63
+ /** bare base or "BASE/USDC" → /info spot row (wire symbol "BASE-USDC"). */
64
+ _resolve(symbol) {
65
+ const [base, quote] = symbol.toUpperCase().split("/");
66
+ if (quote !== undefined && quote !== QUOTE) {
67
+ throw new PerpError("SYMBOL_NOT_FOUND", `Pacifica spot only quotes ${QUOTE} (got ${symbol})`, { exchange: "pacifica" });
68
+ }
69
+ const m = this._spot.get(base);
70
+ if (!m) {
71
+ throw new PerpError("SYMBOL_NOT_FOUND", `No Pacifica spot market for ${symbol}`, {
72
+ exchange: "pacifica",
73
+ remediation: `Available: ${[...this._spot.keys()].map((b) => `${b}/${QUOTE}`).join(", ") || "none"}`,
74
+ });
75
+ }
76
+ return m;
77
+ }
78
+ async getSpotMarkets() {
79
+ const [rows, prices] = await Promise.all([this._loadSpotInfo(), this._perp.sdk.getPrices()]);
80
+ this._initialized = true;
81
+ const priceMap = new Map(prices.map((p) => [p.symbol, p]));
82
+ return rows.map((m) => {
83
+ const p = priceMap.get(m.symbol);
84
+ if (!p)
85
+ throw new PerpError("PRICE_STALE", `No price for Pacifica spot market ${m.symbol}`, { exchange: "pacifica" });
86
+ return {
87
+ symbol: `${m.base_asset}/${QUOTE}`,
88
+ baseToken: m.base_asset,
89
+ quoteToken: QUOTE,
90
+ markPrice: p.mark,
91
+ volume24h: p.volume_24h,
92
+ sizeDecimals: decimalsOf(m.lot_size),
93
+ priceDecimals: decimalsOf(m.tick_size),
94
+ };
95
+ });
96
+ }
97
+ async getSpotOrderbook(symbol) {
98
+ await this.init();
99
+ const m = this._resolve(symbol);
100
+ const book = await this._perp.sdk.getBook(m.symbol);
101
+ return {
102
+ bids: book.l[0].map((e) => [e.p, e.a]),
103
+ asks: book.l[1].map((e) => [e.p, e.a]),
104
+ };
105
+ }
106
+ async getSpotBalances() {
107
+ const info = (await this._perp.sdk.getAccount(this._perp.publicKey));
108
+ const rows = info.spot_balances;
109
+ if (!Array.isArray(rows)) {
110
+ throw new PerpError("INVALID_PARAMS", "Pacifica /account response has no spot_balances array", { exchange: "pacifica" });
111
+ }
112
+ const out = [];
113
+ // USDC = the shared perp collateral (same money as perp equity).
114
+ const total = String(info.balance);
115
+ const available = String(info.available_to_spend);
116
+ out.push({ token: QUOTE, total, available, held: subDecimal(total, available), shared: true });
117
+ for (const r of rows) {
118
+ const t = String(r.amount);
119
+ const a = String(r.available_to_withdraw);
120
+ out.push({ token: String(r.symbol), total: t, available: a, held: subDecimal(t, a), shared: false });
121
+ }
122
+ return out;
123
+ }
124
+ /** Mark price for min-notional / slippage checks. */
125
+ async _mark(wire) {
126
+ const p = (await this._perp.sdk.getPrices()).find((x) => x.symbol === wire);
127
+ if (!p)
128
+ throw new PerpError("PRICE_STALE", `No price for ${wire}`, { exchange: "pacifica" });
129
+ return p.mark;
130
+ }
131
+ _checkNotional(m, price, size) {
132
+ if (Number(price) * Number(size) < Number(m.min_order_size)) {
133
+ throw new PerpError("SIZE_TOO_SMALL", `Notional ${(Number(price) * Number(size)).toFixed(4)} USD is below Pacifica spot minimum ${m.min_order_size} USD`, {
134
+ exchange: "pacifica",
135
+ remediation: `Increase size so price x size >= ${m.min_order_size} USD`,
136
+ });
137
+ }
138
+ }
139
+ /** Throws unless the response is an accepted order; returns order_id when present. */
140
+ _accepted(res, what) {
141
+ const r = res;
142
+ if (r.success === false || r.error) {
143
+ throw new PerpError("EXCHANGE_ERROR", `${what}: ${String(r.error ?? "order rejected")}`, { exchange: "pacifica" });
144
+ }
145
+ const data = (r.data ?? r);
146
+ return { orderId: data.order_id != null ? String(data.order_id) : null };
147
+ }
148
+ /**
149
+ * Poll history_by_id until a terminal state (or, for resting orders, the first
150
+ * row) — otherwise finality "pending", never resubmit. The order is already
151
+ * accepted, so a poll error is reported as pending (with the order id), not thrown.
152
+ * `amount` = the placed size (a "filled" row without filled_amount filled all of it).
153
+ * `finalFill` = the reported filled_amount of a terminal row (filled / cancelled /
154
+ * rejected) when `wantTerminal` (IOC / market) — the venue-final fill for iocOutcomeOf;
155
+ * null otherwise (no report, resting order, pending), never the placed amount.
156
+ */
157
+ async _settle(orderId, amount, wantTerminal) {
158
+ const pending = (why) => ({
159
+ result: {
160
+ status: "pending",
161
+ filledSize: null,
162
+ avgPrice: null,
163
+ finality: "pending",
164
+ remediation: `Order ${orderId} was accepted but ${why}; do NOT resubmit. Check: perp -e pacifica trade spot-orders / spot-balance`,
165
+ },
166
+ finalFill: null,
167
+ });
168
+ const done = (result, reported) => ({ result, finalFill: wantTerminal ? reported : null });
169
+ const attempts = wantTerminal ? 10 : 3;
170
+ try {
171
+ for (let i = 0; i < attempts; i++) {
172
+ await new Promise((r) => setTimeout(r, 500));
173
+ const hist = await this._perp.sdk.getOrderHistoryById(Number(orderId));
174
+ const latest = pacificaLatestHistoryRow(hist.data ?? hist, String(orderId));
175
+ if (latest === null)
176
+ continue;
177
+ const st = String(latest.order_status);
178
+ const filled = latest.filled_amount != null ? String(latest.filled_amount) : null;
179
+ const avg = latest.average_filled_price != null ? String(latest.average_filled_price) : null;
180
+ if (st === "filled") {
181
+ // "filled" = the whole placed amount; filled_amount (when present) is authoritative.
182
+ return done({ status: "filled", filledSize: filled !== null ? filled : amount, avgPrice: avg, finality: "final", remediation: null }, filled);
183
+ }
184
+ if (st === "rejected")
185
+ return done({ status: "rejected", filledSize: filled, avgPrice: avg, finality: "final", remediation: null }, filled);
186
+ if (st === "cancelled" || st === "canceled") {
187
+ if (filled === null)
188
+ return pending(`was cancelled without a reported filled_amount (a partial fill cannot be ruled out)`);
189
+ return done({ status: Number(filled) > 0 ? "partial" : "cancelled", filledSize: filled, avgPrice: avg, finality: "final", remediation: null }, filled);
190
+ }
191
+ if (st === "open" && !wantTerminal) {
192
+ if (filled === null)
193
+ return done({ status: "open", filledSize: null, avgPrice: avg, finality: "final", remediation: null }, null);
194
+ return done({ status: Number(filled) > 0 ? "partial" : "open", filledSize: filled, avgPrice: avg, finality: "final", remediation: null }, null);
195
+ }
196
+ }
197
+ }
198
+ catch (e) {
199
+ return pending(`its status could not be read (${e instanceof Error ? e.message : String(e)})`);
200
+ }
201
+ // No terminal (or, for resting orders, no) history row within the poll window.
202
+ return pending("its final state was not confirmed in time");
203
+ }
204
+ /** Assemble the result and remember its venue-final fill (when proven) for iocOutcomeOf. */
205
+ _withOutcome(base, settled) {
206
+ const out = { ...base, ...settled.result };
207
+ if (settled.finalFill !== null && settled.finalFill.trim() !== "" && Number.isFinite(Number(settled.finalFill))) {
208
+ this._iocOutcomes.set(out, { filledSize: settled.finalFill.trim(), final: true });
209
+ }
210
+ return out;
211
+ }
212
+ /**
213
+ * Venue-final outcome of a SpotOrderResult returned by spotMarketOrder / IOC
214
+ * spotLimitOrder (IocOutcomeCapable, design §13.10 #4): set only when the polled
215
+ * history row was terminal and reported filled_amount. null for anything else.
216
+ */
217
+ iocOutcomeOf(raw) {
218
+ if (raw === null || typeof raw !== "object")
219
+ return null;
220
+ return this._iocOutcomes.get(raw) ?? null;
221
+ }
222
+ async spotMarketOrder(symbol, side, size, opts) {
223
+ // Pre-send (market resolution, mark read, notional check, signing setup): any failure here
224
+ // is a definite "not sent" — never a retryable error that hides whether an order went out.
225
+ let m;
226
+ let amount;
227
+ const bps = opts.slippage.bps;
228
+ try {
229
+ await this.init();
230
+ m = this._resolve(symbol);
231
+ requirePositive(size, "size");
232
+ amount = floorSize(size, m.lot_size);
233
+ this._checkNotional(m, await this._mark(m.symbol), amount);
234
+ this._perp.prepareSigning();
235
+ }
236
+ catch (e) {
237
+ throw orderNotSent("pacifica", `spot market ${side} ${size} ${symbol}`, e);
238
+ }
239
+ if (side === "buy")
240
+ warnSharedMarginOnce();
241
+ const res = await this._perp.sdk.createMarketOrder({ symbol: m.symbol, amount, side: side === "buy" ? "bid" : "ask", reduce_only: false, slippage_percent: String(bps / 100) }, this._perp.publicKey, this._perp.signer, null).catch((e) => { throw pacificaCreateRejection(e, `spot market ${side} ${amount} ${m.symbol}`); });
242
+ const { orderId } = this._accepted(res, `Spot market ${side} ${m.symbol}`);
243
+ const base = { venue: "pacifica", symbol: `${m.base_asset}/${QUOTE}`, orderId, receivedToken: null, venueRaw: res };
244
+ if (!orderId) {
245
+ return { ...base, status: "pending", filledSize: null, avgPrice: null, finality: "pending", remediation: "Order accepted without order_id; do NOT resubmit. Check open orders and balances." };
246
+ }
247
+ return this._withOutcome(base, await this._settle(orderId, amount, true));
248
+ }
249
+ async spotLimitOrder(symbol, side, price, size, opts) {
250
+ let m;
251
+ let tif;
252
+ let px;
253
+ let amount;
254
+ try { // pre-send (see spotMarketOrder)
255
+ await this.init();
256
+ m = this._resolve(symbol);
257
+ tif = pacificaTif(opts?.tif); // §13.3 common mapping; FOK → CAPABILITY_UNSUPPORTED
258
+ px = requireOnTick(price, m.tick_size);
259
+ requirePositive(size, "size");
260
+ amount = floorSize(size, m.lot_size);
261
+ this._checkNotional(m, px, amount);
262
+ this._perp.prepareSigning();
263
+ }
264
+ catch (e) {
265
+ throw orderNotSent("pacifica", `spot limit ${side} ${size} ${symbol} @ ${price}`, e);
266
+ }
267
+ if (side === "buy")
268
+ warnSharedMarginOnce();
269
+ const res = await this._perp.sdk.createLimitOrder({ symbol: m.symbol, price: px, amount, side: side === "buy" ? "bid" : "ask", tif, reduce_only: false }, this._perp.publicKey, this._perp.signer, null).catch((e) => { throw pacificaCreateRejection(e, `spot limit ${tif} ${side} ${amount} ${m.symbol} @ ${px}`); });
270
+ const { orderId } = this._accepted(res, `Spot limit ${side} ${m.symbol}`);
271
+ const base = { venue: "pacifica", symbol: `${m.base_asset}/${QUOTE}`, orderId, receivedToken: null, venueRaw: res };
272
+ if (!orderId) {
273
+ return { ...base, status: "pending", filledSize: null, avgPrice: null, finality: "pending", remediation: "Order accepted without order_id; do NOT resubmit. Check open orders." };
274
+ }
275
+ // IOC resolves immediately (terminal); resting TIFs just report open/partial.
276
+ return this._withOutcome(base, await this._settle(orderId, amount, tif === "IOC"));
277
+ }
278
+ async spotCancelOrder(symbol, orderId) {
279
+ await this.init();
280
+ const m = this._resolve(symbol);
281
+ this._perp.prepareSigning();
282
+ const res = await this._perp.sdk.cancelOrder({ symbol: m.symbol, order_id: Number(orderId) }, this._perp.publicKey, this._perp.signer);
283
+ this._accepted(res, `Spot cancel ${m.symbol} ${orderId}`);
284
+ return {
285
+ venue: "pacifica", symbol: `${m.base_asset}/${QUOTE}`, orderId, status: "cancelled",
286
+ filledSize: null, avgPrice: null, finality: "final", receivedToken: null, remediation: null, venueRaw: res,
287
+ };
288
+ }
289
+ async getSpotOpenOrders(symbol) {
290
+ await this.init();
291
+ const wire = symbol === null ? null : this._resolve(symbol).symbol;
292
+ const spotWire = new Set([...this._spot.values()].map((m) => m.symbol));
293
+ const orders = await this._perp.sdk.getOrders(this._perp.publicKey);
294
+ return orders
295
+ .map((o) => o)
296
+ .filter((o) => spotWire.has(String(o.symbol)) && (wire === null || o.symbol === wire))
297
+ .map((o) => {
298
+ const w = String(o.symbol);
299
+ return {
300
+ orderId: String(o.order_id),
301
+ symbol: `${w.split("-")[0]}/${QUOTE}`,
302
+ side: o.side === "bid" ? "buy" : o.side === "ask" ? "sell" : (() => {
303
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica spot order ${String(o.order_id)} has an unrecognized side ${JSON.stringify(o.side ?? null)} (expected "bid" / "ask")`, {
304
+ exchange: "pacifica", remediation: "Retry: perp spot orders -e pacifica",
305
+ });
306
+ })(),
307
+ price: String(o.price),
308
+ size: reqField(o, "initial_amount"),
309
+ filled: reqField(o, "filled_amount"),
310
+ };
311
+ });
312
+ }
313
+ /**
314
+ * Cancels spot orders one by one. The venue `cancel_all` with all_symbols
315
+ * would also cancel perp orders, so it is never used here.
316
+ */
317
+ async spotCancelAllOrders(symbol) {
318
+ const open = await this.getSpotOpenOrders(symbol);
319
+ const results = [];
320
+ for (const o of open)
321
+ results.push(await this.spotCancelOrder(o.symbol, o.orderId));
322
+ return results;
323
+ }
324
+ }
@@ -12,10 +12,14 @@
12
12
  * where header is `{ type, timestamp, expiry_window }`. The `data` key
13
13
  * is sorted lexicographically alongside the header fields.
14
14
  *
15
- * The agent bind/unbind action types are documented in HypurrQuant_FE
16
- * `PacificaPerpAdapter.ts` (production reference for HypurrQuant_FE; see
17
- * project memory `hypurrquant_fe_reference.md`). We mirror their `bind_agent_wallet`
18
- * and `unbind_agent_wallet` operation types here.
15
+ * `bind_agent_wallet` mirrors HypurrQuant_FE `PacificaPerpAdapter.ts` (see
16
+ * project memory `hypurrquant_fe_reference.md`).
17
+ *
18
+ * Revocation follows the OFFICIAL Pacifica SDK (`rest/api_agent_keys_detailed.py`):
19
+ * `revoke_agent_wallet` → POST /agent/revoke, `revoke_all_agent_wallets` →
20
+ * POST /agent/revoke_all. An earlier `unbind_agent_wallet` → /agent/bind shape
21
+ * was carried over from the HypurrQuant reference and is NOT part of Pacifica's
22
+ * API; it was silently rejected, leaving revoked-looking agents authorized.
19
23
  *
20
24
  * Pure functions — no side effects, no fs/fetch/env reads.
21
25
  */
@@ -31,13 +35,10 @@ export interface BindAgentMessageParams {
31
35
  /** Optional expiry window (ms); defaults to DEFAULT_EXPIRY_WINDOW. */
32
36
  expiryWindow?: number;
33
37
  }
34
- export interface UnbindAgentMessageParams {
38
+ export interface RevokeAgentMessageParams {
35
39
  /** Master Solana base58 public key. */
36
40
  account: string;
37
- /**
38
- * Agent Solana base58 public key to unbind. Pass an empty string to
39
- * unbind all agent wallets per Pacifica convention.
40
- */
41
+ /** Agent Solana base58 public key to revoke. Required — must not be empty. */
41
42
  agentWallet: string;
42
43
  timestamp?: number;
43
44
  expiryWindow?: number;
@@ -67,9 +68,25 @@ export interface BuiltMessage {
67
68
  */
68
69
  export declare function buildBindAgentMessage(params: BindAgentMessageParams): BuiltMessage;
69
70
  /**
70
- * Build the canonical JSON for `unbind_agent_wallet`.
71
+ * Build the canonical JSON for `revoke_agent_wallet` (POST /agent/revoke).
71
72
  *
72
- * Pacifica convention: empty `agent_wallet` string unbinds all agents; a
73
- * specific base58 address unbinds just that agent.
73
+ * Revokes exactly one agent wallet. To revoke every agent, use
74
+ * `buildRevokeAllAgentsMessage` — Pacifica exposes that as its own operation
75
+ * type and endpoint, not as an empty-string sentinel on this one.
76
+ */
77
+ export declare function buildRevokeAgentMessage(params: RevokeAgentMessageParams): BuiltMessage;
78
+ /**
79
+ * Build the canonical JSON for `revoke_all_agent_wallets`
80
+ * (POST /agent/revoke_all). Payload is empty per the official SDK.
74
81
  */
75
- export declare function buildUnbindAgentMessage(params: UnbindAgentMessageParams): BuiltMessage;
82
+ export declare function buildRevokeAllAgentsMessage(params: Omit<RevokeAgentMessageParams, "agentWallet">): BuiltMessage;
83
+ /**
84
+ * Build the canonical JSON for `list_agent_wallets` (POST /agent/list).
85
+ * Payload is empty per the official SDK (`rest/api_agent_keys_detailed.py`
86
+ * `list_agent_wallets`), so the signed message nests `"data":{}`:
87
+ * {"data":{},"expiry_window":5000,"timestamp":...,"type":"list_agent_wallets"}
88
+ */
89
+ export declare function buildListAgentWalletsMessage(params?: {
90
+ timestamp?: number;
91
+ expiryWindow?: number;
92
+ }): BuiltMessage;
@@ -12,10 +12,14 @@
12
12
  * where header is `{ type, timestamp, expiry_window }`. The `data` key
13
13
  * is sorted lexicographically alongside the header fields.
14
14
  *
15
- * The agent bind/unbind action types are documented in HypurrQuant_FE
16
- * `PacificaPerpAdapter.ts` (production reference for HypurrQuant_FE; see
17
- * project memory `hypurrquant_fe_reference.md`). We mirror their `bind_agent_wallet`
18
- * and `unbind_agent_wallet` operation types here.
15
+ * `bind_agent_wallet` mirrors HypurrQuant_FE `PacificaPerpAdapter.ts` (see
16
+ * project memory `hypurrquant_fe_reference.md`).
17
+ *
18
+ * Revocation follows the OFFICIAL Pacifica SDK (`rest/api_agent_keys_detailed.py`):
19
+ * `revoke_agent_wallet` → POST /agent/revoke, `revoke_all_agent_wallets` →
20
+ * POST /agent/revoke_all. An earlier `unbind_agent_wallet` → /agent/bind shape
21
+ * was carried over from the HypurrQuant reference and is NOT part of Pacifica's
22
+ * API; it was silently rejected, leaving revoked-looking agents authorized.
19
23
  *
20
24
  * Pure functions — no side effects, no fs/fetch/env reads.
21
25
  */
@@ -51,15 +55,21 @@ export function buildBindAgentMessage(params) {
51
55
  };
52
56
  }
53
57
  /**
54
- * Build the canonical JSON for `unbind_agent_wallet`.
58
+ * Build the canonical JSON for `revoke_agent_wallet` (POST /agent/revoke).
55
59
  *
56
- * Pacifica convention: empty `agent_wallet` string unbinds all agents; a
57
- * specific base58 address unbinds just that agent.
60
+ * Revokes exactly one agent wallet. To revoke every agent, use
61
+ * `buildRevokeAllAgentsMessage` — Pacifica exposes that as its own operation
62
+ * type and endpoint, not as an empty-string sentinel on this one.
58
63
  */
59
- export function buildUnbindAgentMessage(params) {
64
+ export function buildRevokeAgentMessage(params) {
65
+ if (!params.agentWallet) {
66
+ // Rule #2: an empty agent address is not a "revoke all" sentinel on this
67
+ // operation. Signing one would produce a request the venue cannot act on.
68
+ throw new Error("buildRevokeAgentMessage requires a non-empty agentWallet; use buildRevokeAllAgentsMessage to revoke every agent.");
69
+ }
60
70
  const timestamp = params.timestamp ?? Date.now();
61
71
  const expiryWindow = params.expiryWindow ?? DEFAULT_EXPIRY_WINDOW;
62
- const type = "unbind_agent_wallet";
72
+ const type = "revoke_agent_wallet";
63
73
  const payload = {
64
74
  agent_wallet: params.agentWallet,
65
75
  };
@@ -72,3 +82,41 @@ export function buildUnbindAgentMessage(params) {
72
82
  payload,
73
83
  };
74
84
  }
85
+ /**
86
+ * Build the canonical JSON for `revoke_all_agent_wallets`
87
+ * (POST /agent/revoke_all). Payload is empty per the official SDK.
88
+ */
89
+ export function buildRevokeAllAgentsMessage(params) {
90
+ const timestamp = params.timestamp ?? Date.now();
91
+ const expiryWindow = params.expiryWindow ?? DEFAULT_EXPIRY_WINDOW;
92
+ const type = "revoke_all_agent_wallets";
93
+ const payload = {};
94
+ const header = { type, timestamp, expiry_window: expiryWindow };
95
+ const wrapped = { ...header, data: payload };
96
+ const sorted = sortJsonKeys(wrapped);
97
+ return {
98
+ canonicalJson: JSON.stringify(sorted),
99
+ header,
100
+ payload,
101
+ };
102
+ }
103
+ /**
104
+ * Build the canonical JSON for `list_agent_wallets` (POST /agent/list).
105
+ * Payload is empty per the official SDK (`rest/api_agent_keys_detailed.py`
106
+ * `list_agent_wallets`), so the signed message nests `"data":{}`:
107
+ * {"data":{},"expiry_window":5000,"timestamp":...,"type":"list_agent_wallets"}
108
+ */
109
+ export function buildListAgentWalletsMessage(params = {}) {
110
+ const timestamp = params.timestamp ?? Date.now();
111
+ const expiryWindow = params.expiryWindow ?? DEFAULT_EXPIRY_WINDOW;
112
+ const type = "list_agent_wallets";
113
+ const payload = {};
114
+ const header = { type, timestamp, expiry_window: expiryWindow };
115
+ const wrapped = { ...header, data: payload };
116
+ const sorted = sortJsonKeys(wrapped);
117
+ return {
118
+ canonicalJson: JSON.stringify(sorted),
119
+ header,
120
+ payload,
121
+ };
122
+ }