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,138 @@
1
+ /**
2
+ * Arcus public market data — GET /v1/markets (one call serves both markets
3
+ * and funding rates).
4
+ *
5
+ * Mapping notes:
6
+ * - symbol: `baseAsset` (plain ticker, e.g. "TSLA"); venueSymbol: display name
7
+ * ("TSLA-USD") which is also the `market` key for l2OrderBook/trades/candles.
8
+ * - tickSize / stepSize: the top-level fields. `tickTiers` (per-price-band
9
+ * placement grid) is NOT folded into VenueMarket — the signed integer price
10
+ * always divides by the top-level tickSize; tiers only restrict which limit
11
+ * prices are accepted. The adapter validates/aligns against the tier tick
12
+ * (see tickForPrice) using the raw market.
13
+ * - maxLeverage: floor(1 / initialMarginFraction) (regular-hours IMF).
14
+ * - assetClass from `category`: CRYPTO → crypto, EQUITIES → equity,
15
+ * FOREX → fx. COMMODITIES / INDICES listings on Arcus are ETFs (GLD, SLV,
16
+ * USO, CPER, SPY, QQQ …) → "etf" when `fullAssetName` names an ETF/Trust/
17
+ * Fund/Shares; otherwise commodity / index (canonical then comes only from the
18
+ * explicit table).
19
+ * - session: only for markets with `regularTradingHours` (24/7 crypto → null).
20
+ * underlyingOpen = !isOutsideRth; priceBand = off-hours trading bounds when
21
+ * published; marginMultiplier = offHoursIMF / IMF.
22
+ * - funding: hourly (`fundingRate` is the per-hour fraction last charged);
23
+ * `nextFundingAt` is Unix seconds.
24
+ * - tradable: status === "ONLINE".
25
+ */
26
+ import { PerpError } from "../../errors.js";
27
+ import { canonicalFor } from "../canonical-symbols.js";
28
+ import { arcusVenue } from "../venues/arcus.js";
29
+ import { ArcusClient } from "./client.js";
30
+ const FUNDING_BASIS = { kind: "perHours", hours: 1 };
31
+ export async function fetchArcusRawMarkets(client) {
32
+ const res = await client.get("/v1/markets", "markets");
33
+ if (!res || !Array.isArray(res.markets)) {
34
+ throw new PerpError("EXCHANGE_ERROR", "Arcus /v1/markets: missing markets array");
35
+ }
36
+ return res.markets;
37
+ }
38
+ /** Fund-style names seen on Arcus COMMODITIES/INDICES listings (GLD "SPDR Gold Shares", SLV "iShares Silver Trust", USO/CPER "… Fund", SPY "SPDR S&P 500 ETF Trust"). */
39
+ const ETF_NAME = /\b(ETF|Trust|Fund|Shares|iShares|SPDR)\b/i;
40
+ export function arcusAssetClass(m) {
41
+ switch (m.category) {
42
+ case "CRYPTO": return "crypto";
43
+ case "EQUITIES": return "equity";
44
+ case "FOREX": return "fx";
45
+ case "COMMODITIES": return m.fullAssetName && ETF_NAME.test(m.fullAssetName) ? "etf" : "commodity";
46
+ case "INDICES": return m.fullAssetName && ETF_NAME.test(m.fullAssetName) ? "etf" : "index";
47
+ default:
48
+ throw new PerpError("EXCHANGE_ERROR", `Arcus market ${m.marketDisplayName}: unknown category "${m.category}"`);
49
+ }
50
+ }
51
+ function numOrNull(s) {
52
+ return typeof s === "string" && s.length > 0 && Number.isFinite(Number(s)) ? s : null;
53
+ }
54
+ function maxLeverageOf(m) {
55
+ const imf = Number(m.initialMarginFraction);
56
+ if (!(imf > 0)) {
57
+ throw new PerpError("EXCHANGE_ERROR", `Arcus market ${m.marketDisplayName}: invalid initialMarginFraction "${m.initialMarginFraction}"`);
58
+ }
59
+ // 1/0.025 is exactly 40 in binary FP for current markets; epsilon guards 1/0.1-style drift.
60
+ return Math.floor(1 / imf + 1e-9);
61
+ }
62
+ function sessionOf(m) {
63
+ if (m.regularTradingHours === null || m.regularTradingHours === undefined)
64
+ return null;
65
+ const lower = numOrNull(m.lowerTradingBound);
66
+ const upper = numOrNull(m.upperTradingBound);
67
+ const imf = Number(m.initialMarginFraction);
68
+ const offImf = Number(m.offHoursInitialMarginFraction);
69
+ return {
70
+ underlyingOpen: !m.isOutsideRth,
71
+ priceBand: lower !== null && upper !== null ? { lower, upper } : null,
72
+ // Rounded to 6 dp to drop binary-FP noise (0.0375/0.025 → 1.4999999999999998).
73
+ marginMultiplier: imf > 0 && offImf > 0 ? Math.round((offImf / imf) * 1e6) / 1e6 : null,
74
+ };
75
+ }
76
+ export function toArcusVenueMarket(m) {
77
+ const assetClass = arcusAssetClass(m);
78
+ const mark = numOrNull(m.markPrice);
79
+ if (mark === null) {
80
+ throw new PerpError("EXCHANGE_ERROR", `Arcus market ${m.marketDisplayName}: missing markPrice`);
81
+ }
82
+ return {
83
+ venue: "arcus",
84
+ venueSymbol: m.marketDisplayName,
85
+ symbol: m.baseAsset,
86
+ canonical: canonicalFor("arcus", m.baseAsset, assetClass),
87
+ assetClass,
88
+ settle: arcusVenue.networks.mainnet.collateral[0],
89
+ quoteAsset: m.quoteAsset,
90
+ markPrice: mark,
91
+ indexPrice: numOrNull(m.oraclePrice),
92
+ fundingRate: numOrNull(m.fundingRate),
93
+ fundingRateBasis: FUNDING_BASIS,
94
+ settlementIntervalHours: 1,
95
+ openInterest: numOrNull(m.openInterest),
96
+ volume24h: numOrNull(m.volume24hNotional),
97
+ tickSize: m.tickSize,
98
+ stepSize: m.stepSize,
99
+ minSize: numOrNull(m.minOrderSize),
100
+ minNotional: numOrNull(m.minOrderNotional),
101
+ maxLeverage: maxLeverageOf(m),
102
+ marginTiers: null,
103
+ session: sessionOf(m),
104
+ tradable: m.status === "ONLINE",
105
+ };
106
+ }
107
+ async function fetchMarkets(net) {
108
+ const raw = await fetchArcusRawMarkets(new ArcusClient(net, null));
109
+ return raw.map(toArcusVenueMarket);
110
+ }
111
+ async function fetchFundingRates(net) {
112
+ const raw = await fetchArcusRawMarkets(new ArcusClient(net, null));
113
+ const out = [];
114
+ const missing = [];
115
+ for (const m of raw) {
116
+ const rate = numOrNull(m.fundingRate);
117
+ if (rate === null) {
118
+ missing.push(m.marketDisplayName);
119
+ continue;
120
+ }
121
+ const assetClass = arcusAssetClass(m);
122
+ out.push({
123
+ venue: "arcus",
124
+ venueSymbol: m.marketDisplayName,
125
+ symbol: m.baseAsset,
126
+ canonical: canonicalFor("arcus", m.baseAsset, assetClass),
127
+ rate,
128
+ basis: FUNDING_BASIS,
129
+ markPrice: numOrNull(m.markPrice),
130
+ nextFundingTime: typeof m.nextFundingAt === "number" ? m.nextFundingAt * 1000 : null,
131
+ });
132
+ }
133
+ if (missing.length > 0) {
134
+ console.warn(`[arcus] excluded ${missing.length} market(s) without a published funding rate: ${missing.join(", ")}`);
135
+ }
136
+ return out;
137
+ }
138
+ export const arcusPublicData = { fetchMarkets, fetchFundingRates };
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Arcus request signing (docs.arcus.xyz/api-reference/authentication).
3
+ *
4
+ * Two Ed25519 schemes, both signed by the agent API key:
5
+ * - Scheme 1 (typed payload): placeOrder / cancelOrder / modifyOrder and every
6
+ * element of batchPlaceOrders / batchCancelOrders. The signed bytes are a
7
+ * compact, key-sorted JSON object of engine-native integers:
8
+ * place (op 1, op 4 = untriggered TPSL leg):
9
+ * {"ad","ai"[,"c"],"ct","g","m","op","p","q","r","s","t","v"}
10
+ * cancel (op 2): {"ad","ai"[,"c"],"ct"[,"id"],"m","op","v"} (exactly one of id / c)
11
+ * modify (op 3): {"ad","ai"[,"c"],"ct","g"[,"id"],"m","op","p","q","r","s","t","v"}
12
+ * identity per ARCUS_MODIFY_SIGNED_IDENTITY (UNVERIFIED — see below)
13
+ * - Scheme 2 (legacy message): cancelAllOrders, setLeverage (and, by the docs'
14
+ * "everything else" rule, scheduleCancel — unverified):
15
+ * ed25519(timestampNs + action + canonicalJson(body))
16
+ *
17
+ * Time units (design §10.10): `ct` and X-Timestamp are Unix NANOseconds;
18
+ * signed `g` is NANOseconds; the request body `goodTilTime` is a MICROsecond
19
+ * decimal string (g = goodTilTime × 1000).
20
+ *
21
+ * Master-wallet operations (createApiKey / revokeApiKey / withdraw / transfer)
22
+ * are secp256k1 EIP-712 typed data; builders for those live at the bottom.
23
+ */
24
+ /** Raw Ed25519 detached signer (OWS agent wallet or tweetnacl). No prefixing. */
25
+ export type Ed25519SignFn = (message: Uint8Array) => Promise<Uint8Array>;
26
+ /**
27
+ * Compact JSON with keys sorted (recursively), no whitespace. `bigint` values
28
+ * are emitted as unquoted integers (engine-native ticks / quantums / ns).
29
+ */
30
+ export declare function canonicalJson(value: unknown): string;
31
+ export declare const ARCUS_SIDE: {
32
+ readonly buy: 0;
33
+ readonly sell: 1;
34
+ };
35
+ /** Signed TIF codes: 0 GTT, 1 FOK, 2 IOC, 3 ALO. */
36
+ export declare const ARCUS_TIF_CODE: {
37
+ readonly GTT: 0;
38
+ readonly FOK: 1;
39
+ readonly IOC: 2;
40
+ readonly ALO: 3;
41
+ };
42
+ export type ArcusTif = keyof typeof ARCUS_TIF_CODE;
43
+ export declare const ARCUS_OP: {
44
+ readonly place: 1;
45
+ readonly cancel: 2;
46
+ readonly modify: 3;
47
+ readonly placeUntriggered: 4;
48
+ readonly withdraw: 5;
49
+ };
50
+ export declare function assertArcusClientId(c: string): void;
51
+ export interface ArcusPlaceFields {
52
+ /** Master EVM address (lowercased when signing). */
53
+ address: string;
54
+ accountIndex: number;
55
+ clientId: string | null;
56
+ /** X-Timestamp, ns. */
57
+ ctNs: bigint;
58
+ /** goodTilTime, ns (= body µs × 1000). */
59
+ goodTilNs: bigint;
60
+ marketId: number;
61
+ /** price ÷ top-level market tickSize (exact). */
62
+ priceTicks: bigint;
63
+ /** size ÷ stepSize (exact). */
64
+ quantums: bigint;
65
+ reduceOnly: boolean;
66
+ side: "buy" | "sell";
67
+ tif: ArcusTif;
68
+ }
69
+ /** op 1 (plain order) or op 4 (TPSL / untriggered leg inside a batch grouping). */
70
+ export declare function placePayload(f: ArcusPlaceFields, untriggered: boolean): string;
71
+ /**
72
+ * Which order identity the signed modify payload (op 3) carries.
73
+ *
74
+ * UNVERIFIED — the Arcus docs contradict each other:
75
+ * - openapi.yml (info.description) and the modify-order page: modifyOrder payload
76
+ * `…"g":N,[,"id":"…"],"m":N… (exactly one of id / c)` — the request body also takes
77
+ * exactly one of orderId / clientId;
78
+ * - authentication page: always `id`, plus `c` "only if the resting order was placed
79
+ * with a client id … the engine rejects a mismatch".
80
+ * Default follows the openapi spec ("idOnly": sign `id`, never `c`; the body sends
81
+ * orderId only). "idAndClientId" is the authentication-page variant. Settled by the
82
+ * docs/TEST-PLAN.md step "Arcus modify signature identity" on testnet.
83
+ */
84
+ export type ArcusModifyIdentity = "idOnly" | "idAndClientId";
85
+ export declare const ARCUS_MODIFY_SIGNED_IDENTITY: ArcusModifyIdentity;
86
+ /** op 3. Signs `id`; echoes the resting order's `c` only in the "idAndClientId" variant. */
87
+ export declare function modifyPayload(f: ArcusPlaceFields, orderId: string, identity?: ArcusModifyIdentity): string;
88
+ export interface ArcusCancelFields {
89
+ address: string;
90
+ accountIndex: number;
91
+ ctNs: bigint;
92
+ marketId: number;
93
+ ref: {
94
+ kind: "orderId";
95
+ value: string;
96
+ } | {
97
+ kind: "clientOrderId";
98
+ value: string;
99
+ };
100
+ }
101
+ /** op 2 — exactly one of `id` (server order id) or `c` (client id). */
102
+ export declare function cancelPayload(f: ArcusCancelFields): string;
103
+ /** op 5 — API-key withdraw (only for keys provisioned with the `withdraw` permission). */
104
+ export declare function withdrawPayload(f: {
105
+ address: string;
106
+ accountIndex: number;
107
+ ctNs: bigint;
108
+ nonce: string;
109
+ amountQuantums: bigint;
110
+ }): string;
111
+ /** `ts + action + canonicalJson(body)`, no delimiters. The same canonical body string must be sent as the HTTP body. */
112
+ export declare function scheme2Message(tsNs: bigint, action: string, canonicalBody: string): string;
113
+ export declare function signUtf8Hex(sign: Ed25519SignFn, message: string): Promise<string>;
114
+ /**
115
+ * Unix time in nanoseconds (ms × 1e6), strictly increasing within the process
116
+ * so two requests in the same millisecond never share a `ct` replay slot.
117
+ */
118
+ export declare function nowNs(): bigint;
119
+ export type Eip712Types = Record<string, Array<{
120
+ name: string;
121
+ type: string;
122
+ }>>;
123
+ export interface ArcusTypedData {
124
+ domain: Record<string, unknown>;
125
+ /** Contains ONLY the primary type (OwsEvmSigner takes the first key as primaryType). */
126
+ types: Eip712Types;
127
+ primaryType: string;
128
+ message: Record<string, unknown>;
129
+ }
130
+ /** BridgeVault (withdraw / transfer verifyingContract) per signing chain id. */
131
+ export declare const ARCUS_BRIDGE_VAULT: Readonly<Record<number, `0x${string}`>>;
132
+ /**
133
+ * createApiKey typed data. Always uses the accountIndex-bearing type (valid for
134
+ * every index, mandatory for 1–9/255); with a nonce, the combined type.
135
+ */
136
+ export declare function createApiKeyTypedData(p: {
137
+ chainId: number;
138
+ apiWalletName: string;
139
+ publicKeyHex: string;
140
+ validUntilMs: number;
141
+ accountIndex: number;
142
+ nonce: string | null;
143
+ }): ArcusTypedData;
144
+ /** revokeApiKey typed data (no validUntil). */
145
+ export declare function revokeApiKeyTypedData(p: {
146
+ chainId: number;
147
+ apiWalletName: string;
148
+ publicKeyHex: string;
149
+ accountIndex: number;
150
+ nonce: string | null;
151
+ }): ArcusTypedData;
152
+ /** Withdraw-to-self typed data. `amountQuantums`: quote quantums (1e9 = $1), sent as a decimal string. */
153
+ export declare function withdrawTypedData(p: {
154
+ chainId: number;
155
+ ethereumAddress: string;
156
+ accountIndex: number;
157
+ amountQuantums: bigint;
158
+ nonce: string;
159
+ }): ArcusTypedData;
160
+ /** Internal subaccount transfer typed data. */
161
+ export declare function transferTypedData(p: {
162
+ chainId: number;
163
+ ethereumAddress: string;
164
+ fromAccountIndex: number;
165
+ toAccountIndex: number;
166
+ amountQuantums: bigint;
167
+ nonce: string;
168
+ }): ArcusTypedData;
169
+ /** Split a 65-byte 0x-hex signature into Arcus `{r, s, v}` with v ∈ {0x1b, 0x1c}. */
170
+ export declare function splitEvmSignature(sig: string): {
171
+ r: string;
172
+ s: string;
173
+ v: string;
174
+ };
175
+ /** 1 quote quantum = 1e-9 USDG. */
176
+ export declare const QUOTE_QUANTUM = "0.000000001";
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Arcus request signing (docs.arcus.xyz/api-reference/authentication).
3
+ *
4
+ * Two Ed25519 schemes, both signed by the agent API key:
5
+ * - Scheme 1 (typed payload): placeOrder / cancelOrder / modifyOrder and every
6
+ * element of batchPlaceOrders / batchCancelOrders. The signed bytes are a
7
+ * compact, key-sorted JSON object of engine-native integers:
8
+ * place (op 1, op 4 = untriggered TPSL leg):
9
+ * {"ad","ai"[,"c"],"ct","g","m","op","p","q","r","s","t","v"}
10
+ * cancel (op 2): {"ad","ai"[,"c"],"ct"[,"id"],"m","op","v"} (exactly one of id / c)
11
+ * modify (op 3): {"ad","ai"[,"c"],"ct","g"[,"id"],"m","op","p","q","r","s","t","v"}
12
+ * identity per ARCUS_MODIFY_SIGNED_IDENTITY (UNVERIFIED — see below)
13
+ * - Scheme 2 (legacy message): cancelAllOrders, setLeverage (and, by the docs'
14
+ * "everything else" rule, scheduleCancel — unverified):
15
+ * ed25519(timestampNs + action + canonicalJson(body))
16
+ *
17
+ * Time units (design §10.10): `ct` and X-Timestamp are Unix NANOseconds;
18
+ * signed `g` is NANOseconds; the request body `goodTilTime` is a MICROsecond
19
+ * decimal string (g = goodTilTime × 1000).
20
+ *
21
+ * Master-wallet operations (createApiKey / revokeApiKey / withdraw / transfer)
22
+ * are secp256k1 EIP-712 typed data; builders for those live at the bottom.
23
+ */
24
+ import { PerpError } from "../../errors.js";
25
+ // ── Canonical JSON ──
26
+ /**
27
+ * Compact JSON with keys sorted (recursively), no whitespace. `bigint` values
28
+ * are emitted as unquoted integers (engine-native ticks / quantums / ns).
29
+ */
30
+ export function canonicalJson(value) {
31
+ if (value === null)
32
+ return "null";
33
+ switch (typeof value) {
34
+ case "bigint":
35
+ return value.toString();
36
+ case "number":
37
+ if (!Number.isFinite(value))
38
+ throw new Error(`canonicalJson: non-finite number ${value}`);
39
+ return JSON.stringify(value);
40
+ case "string":
41
+ case "boolean":
42
+ return JSON.stringify(value);
43
+ case "object": {
44
+ if (Array.isArray(value))
45
+ return `[${value.map(canonicalJson).join(",")}]`;
46
+ const obj = value;
47
+ const keys = Object.keys(obj).filter(k => obj[k] !== undefined).sort();
48
+ return `{${keys.map(k => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
49
+ }
50
+ default:
51
+ throw new Error(`canonicalJson: unsupported type ${typeof value}`);
52
+ }
53
+ }
54
+ // ── Scheme 1 enums ──
55
+ export const ARCUS_SIDE = { buy: 0, sell: 1 };
56
+ /** Signed TIF codes: 0 GTT, 1 FOK, 2 IOC, 3 ALO. */
57
+ export const ARCUS_TIF_CODE = { GTT: 0, FOK: 1, IOC: 2, ALO: 3 };
58
+ export const ARCUS_OP = { place: 1, cancel: 2, modify: 3, placeUntriggered: 4, withdraw: 5 };
59
+ /** Payload version (`v`). */
60
+ const PAYLOAD_VERSION = 1;
61
+ /** clientId charset per docs: [A-Za-z0-9_-], 1–36 chars. Signed verbatim. */
62
+ const CLIENT_ID_RE = /^[A-Za-z0-9_-]{1,36}$/;
63
+ export function assertArcusClientId(c) {
64
+ if (!CLIENT_ID_RE.test(c)) {
65
+ throw new PerpError("INVALID_PARAMS", `Arcus clientId "${c}" must match [A-Za-z0-9_-]{1,36}`);
66
+ }
67
+ }
68
+ function lowerAddress(ad) {
69
+ if (!/^0x[0-9a-fA-F]{40}$/.test(ad))
70
+ throw new PerpError("INVALID_PARAMS", `Invalid Arcus master address "${ad}"`);
71
+ return ad.toLowerCase();
72
+ }
73
+ function placeLikeObject(op, f, orderId) {
74
+ if (f.clientId !== null)
75
+ assertArcusClientId(f.clientId);
76
+ return {
77
+ ad: lowerAddress(f.address),
78
+ ai: f.accountIndex,
79
+ c: f.clientId ?? undefined, // omitted entirely when absent
80
+ ct: f.ctNs,
81
+ g: f.goodTilNs,
82
+ id: orderId ?? undefined,
83
+ m: f.marketId,
84
+ op,
85
+ p: f.priceTicks,
86
+ q: f.quantums,
87
+ r: f.reduceOnly ? 1 : 0,
88
+ s: ARCUS_SIDE[f.side],
89
+ t: ARCUS_TIF_CODE[f.tif],
90
+ v: PAYLOAD_VERSION,
91
+ };
92
+ }
93
+ /** op 1 (plain order) or op 4 (TPSL / untriggered leg inside a batch grouping). */
94
+ export function placePayload(f, untriggered) {
95
+ return canonicalJson(placeLikeObject(untriggered ? ARCUS_OP.placeUntriggered : ARCUS_OP.place, f, null));
96
+ }
97
+ export const ARCUS_MODIFY_SIGNED_IDENTITY = "idOnly";
98
+ /** op 3. Signs `id`; echoes the resting order's `c` only in the "idAndClientId" variant. */
99
+ export function modifyPayload(f, orderId, identity = ARCUS_MODIFY_SIGNED_IDENTITY) {
100
+ if (orderId.length === 0)
101
+ throw new PerpError("INVALID_PARAMS", "Arcus modifyOrder requires the server orderId");
102
+ const fields = identity === "idOnly" ? { ...f, clientId: null } : f;
103
+ return canonicalJson(placeLikeObject(ARCUS_OP.modify, fields, orderId));
104
+ }
105
+ /** op 2 — exactly one of `id` (server order id) or `c` (client id). */
106
+ export function cancelPayload(f) {
107
+ if (f.ref.kind === "clientOrderId")
108
+ assertArcusClientId(f.ref.value);
109
+ return canonicalJson({
110
+ ad: lowerAddress(f.address),
111
+ ai: f.accountIndex,
112
+ c: f.ref.kind === "clientOrderId" ? f.ref.value : undefined,
113
+ ct: f.ctNs,
114
+ id: f.ref.kind === "orderId" ? f.ref.value : undefined,
115
+ m: f.marketId,
116
+ op: ARCUS_OP.cancel,
117
+ v: PAYLOAD_VERSION,
118
+ });
119
+ }
120
+ /** op 5 — API-key withdraw (only for keys provisioned with the `withdraw` permission). */
121
+ export function withdrawPayload(f) {
122
+ return canonicalJson({
123
+ ad: lowerAddress(f.address),
124
+ ai: f.accountIndex,
125
+ ct: f.ctNs,
126
+ n: f.nonce,
127
+ op: ARCUS_OP.withdraw,
128
+ q: f.amountQuantums,
129
+ v: PAYLOAD_VERSION,
130
+ });
131
+ }
132
+ // ── Scheme 2 ──
133
+ /** `ts + action + canonicalJson(body)`, no delimiters. The same canonical body string must be sent as the HTTP body. */
134
+ export function scheme2Message(tsNs, action, canonicalBody) {
135
+ return `${tsNs.toString()}${action}${canonicalBody}`;
136
+ }
137
+ // ── Ed25519 helpers ──
138
+ export async function signUtf8Hex(sign, message) {
139
+ const sig = await sign(new TextEncoder().encode(message));
140
+ if (sig.length !== 64) {
141
+ throw new PerpError("SIGNATURE_FAILED", `Arcus: Ed25519 signature must be 64 bytes (got ${sig.length})`);
142
+ }
143
+ return Buffer.from(sig).toString("hex");
144
+ }
145
+ let lastNs = 0n;
146
+ /**
147
+ * Unix time in nanoseconds (ms × 1e6), strictly increasing within the process
148
+ * so two requests in the same millisecond never share a `ct` replay slot.
149
+ */
150
+ export function nowNs() {
151
+ let t = BigInt(Date.now()) * 1000000n;
152
+ if (t <= lastNs)
153
+ t = lastNs + 1n;
154
+ lastNs = t;
155
+ return t;
156
+ }
157
+ /** BridgeVault (withdraw / transfer verifyingContract) per signing chain id. */
158
+ export const ARCUS_BRIDGE_VAULT = {
159
+ 4663: "0x14b107cf534239c59571b066cb6497a321da897c",
160
+ 46630: "0x9a6d3499149fea853efe775a3577701539054eaf",
161
+ };
162
+ function bridgeVault(chainId) {
163
+ const v = ARCUS_BRIDGE_VAULT[chainId];
164
+ if (!v)
165
+ throw new PerpError("INVALID_PARAMS", `No Arcus BridgeVault known for chainId ${chainId}`);
166
+ return v;
167
+ }
168
+ function apiKeyDomain(chainId) {
169
+ // No verifyingContract (docs: API keys are pure off-chain auth).
170
+ return { name: "Arcus API Key", version: "1", chainId };
171
+ }
172
+ function assertApiKeyAccountIndex(i) {
173
+ if (!(Number.isInteger(i) && ((i >= 0 && i <= 9) || i === 255))) {
174
+ throw new PerpError("INVALID_PARAMS", `Arcus API key accountIndex must be 0–9 or 255 (got ${i})`);
175
+ }
176
+ }
177
+ function assertPubHex(h) {
178
+ if (!/^[0-9a-f]{64}$/.test(h))
179
+ throw new PerpError("INVALID_PARAMS", `Arcus API key must be 64 lowercase hex chars`);
180
+ }
181
+ /**
182
+ * createApiKey typed data. Always uses the accountIndex-bearing type (valid for
183
+ * every index, mandatory for 1–9/255); with a nonce, the combined type.
184
+ */
185
+ export function createApiKeyTypedData(p) {
186
+ assertApiKeyAccountIndex(p.accountIndex);
187
+ assertPubHex(p.publicKeyHex);
188
+ const fields = [
189
+ { name: "apiWalletName", type: "string" },
190
+ { name: "apiWalletPublicKey", type: "string" },
191
+ { name: "validUntil", type: "uint256" },
192
+ ...(p.nonce !== null ? [{ name: "nonce", type: "string" }] : []),
193
+ { name: "accountIndex", type: "uint8" },
194
+ ];
195
+ return {
196
+ domain: apiKeyDomain(p.chainId),
197
+ types: { CreateApiKey: fields },
198
+ primaryType: "CreateApiKey",
199
+ message: {
200
+ apiWalletName: p.apiWalletName,
201
+ apiWalletPublicKey: p.publicKeyHex,
202
+ validUntil: p.validUntilMs,
203
+ ...(p.nonce !== null ? { nonce: p.nonce } : {}),
204
+ accountIndex: p.accountIndex,
205
+ },
206
+ };
207
+ }
208
+ /** revokeApiKey typed data (no validUntil). */
209
+ export function revokeApiKeyTypedData(p) {
210
+ assertApiKeyAccountIndex(p.accountIndex);
211
+ assertPubHex(p.publicKeyHex);
212
+ const fields = [
213
+ { name: "apiWalletName", type: "string" },
214
+ { name: "apiWalletPublicKey", type: "string" },
215
+ ...(p.nonce !== null ? [{ name: "nonce", type: "string" }] : []),
216
+ { name: "accountIndex", type: "uint8" },
217
+ ];
218
+ return {
219
+ domain: apiKeyDomain(p.chainId),
220
+ types: { RevokeApiKey: fields },
221
+ primaryType: "RevokeApiKey",
222
+ message: {
223
+ apiWalletName: p.apiWalletName,
224
+ apiWalletPublicKey: p.publicKeyHex,
225
+ ...(p.nonce !== null ? { nonce: p.nonce } : {}),
226
+ accountIndex: p.accountIndex,
227
+ },
228
+ };
229
+ }
230
+ /** Withdraw-to-self typed data. `amountQuantums`: quote quantums (1e9 = $1), sent as a decimal string. */
231
+ export function withdrawTypedData(p) {
232
+ return {
233
+ domain: { name: "Arcus Withdraw", version: "1", chainId: p.chainId, verifyingContract: bridgeVault(p.chainId) },
234
+ types: {
235
+ Withdraw: [
236
+ { name: "ethereumAddress", type: "address" },
237
+ { name: "accountIndex", type: "uint8" },
238
+ { name: "amount", type: "uint256" },
239
+ { name: "nonce", type: "string" },
240
+ ],
241
+ },
242
+ primaryType: "Withdraw",
243
+ message: {
244
+ ethereumAddress: p.ethereumAddress,
245
+ accountIndex: p.accountIndex,
246
+ amount: p.amountQuantums.toString(),
247
+ nonce: p.nonce,
248
+ },
249
+ };
250
+ }
251
+ /** Internal subaccount transfer typed data. */
252
+ export function transferTypedData(p) {
253
+ return {
254
+ domain: { name: "Arcus Transfer", version: "1", chainId: p.chainId, verifyingContract: bridgeVault(p.chainId) },
255
+ types: {
256
+ Transfer: [
257
+ { name: "ethereumAddress", type: "address" },
258
+ { name: "fromAccountIndex", type: "uint8" },
259
+ { name: "toAccountIndex", type: "uint8" },
260
+ { name: "amount", type: "uint256" },
261
+ { name: "nonce", type: "string" },
262
+ ],
263
+ },
264
+ primaryType: "Transfer",
265
+ message: {
266
+ ethereumAddress: p.ethereumAddress,
267
+ fromAccountIndex: p.fromAccountIndex,
268
+ toAccountIndex: p.toAccountIndex,
269
+ amount: p.amountQuantums.toString(),
270
+ nonce: p.nonce,
271
+ },
272
+ };
273
+ }
274
+ /** Split a 65-byte 0x-hex signature into Arcus `{r, s, v}` with v ∈ {0x1b, 0x1c}. */
275
+ export function splitEvmSignature(sig) {
276
+ const hex = sig.startsWith("0x") ? sig.slice(2) : sig;
277
+ if (hex.length !== 130) {
278
+ throw new PerpError("SIGNATURE_FAILED", `Arcus: expected a 65-byte EVM signature (got ${hex.length / 2} bytes)`);
279
+ }
280
+ let v = parseInt(hex.slice(128, 130), 16);
281
+ if (v === 0 || v === 1)
282
+ v += 27;
283
+ if (v !== 27 && v !== 28)
284
+ throw new PerpError("SIGNATURE_FAILED", `Arcus: unexpected signature v=${v}`);
285
+ return { r: `0x${hex.slice(0, 64)}`, s: `0x${hex.slice(64, 128)}`, v: `0x${v.toString(16)}` };
286
+ }
287
+ /** 1 quote quantum = 1e-9 USDG. */
288
+ export const QUOTE_QUANTUM = "0.000000001";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Robinhood Chain reads/txs for Arcus spot (design §12.4 #7), over
3
+ * `VenueNetworkConfig.rpcUrl` with ethers.
4
+ *
5
+ * Permit2 allowance logic ported from @arcus-xyz/arcus-spot-sdk@1.0.2
6
+ * (src/permits.ts) — Copyright Arcus, Apache License 2.0
7
+ * (http://www.apache.org/licenses/LICENSE-2.0). Changes: viem → ethers; the
8
+ * SDK's last-resort EIP-2612 domain guess (`version() ?? "1"` without a
9
+ * DOMAIN_SEPARATOR match) is replaced by an error (SSOT Rule #2).
10
+ */
11
+ import { JsonRpcProvider, Transaction } from "ethers";
12
+ /** Approve receipt wait. */
13
+ export declare const ARCUS_APPROVE_RECEIPT_TIMEOUT_MS = 120000;
14
+ export declare const MAX_UINT256: bigint;
15
+ export interface TokenTransfer {
16
+ token: string;
17
+ from: string;
18
+ to: string;
19
+ amount: bigint;
20
+ }
21
+ export declare class ArcusSpotChain {
22
+ readonly rpcUrl: string;
23
+ readonly chainId: number;
24
+ readonly provider: JsonRpcProvider;
25
+ private chainChecked;
26
+ constructor(rpcUrl: string, chainId: number);
27
+ /** Fails if the RPC serves a different chain than the venue's signing chain. */
28
+ ready(): Promise<void>;
29
+ private call;
30
+ nativeBalance(owner: string): Promise<bigint>;
31
+ /**
32
+ * balanceOf(owner) for each token via Multicall3. Returns null for a target
33
+ * that answered with no data (no contract deployed — e.g. a wrapped token the
34
+ * factory has not created yet); a reverted call throws.
35
+ */
36
+ erc20Balances(owner: string, tokens: string[]): Promise<Array<bigint | null>>;
37
+ permit2Allowance(token: string, owner: string): Promise<bigint>;
38
+ /** EIP-2612 nonce, or null when the token has no `nonces()` (not EIP-2612 → approve tx needed). */
39
+ eip2612Nonce(token: string, owner: string): Promise<bigint | null>;
40
+ /**
41
+ * EIP-712 domain the token verifies permits with: EIP-5267 `eip712Domain()`,
42
+ * else a `version()` / "1" / "2" candidate that reproduces DOMAIN_SEPARATOR().
43
+ * Nothing verifiable → throws (no guessing).
44
+ */
45
+ eip2612Domain(token: string): Promise<{
46
+ name: string;
47
+ version: string;
48
+ }>;
49
+ /**
50
+ * Send `approve(Permit2, MAX_UINT256)` from `owner`, signed via `signTx`
51
+ * (master key), after checking the native ETH balance covers
52
+ * gasLimit × maxFeePerGas. Waits for a successful receipt.
53
+ */
54
+ approvePermit2(token: string, owner: string, signTx: (tx: Transaction) => Promise<string>): Promise<string>;
55
+ /** ERC-20 Transfer logs in a mined tx (null when the RPC has no receipt for it). */
56
+ transfersIn(txHash: string): Promise<{
57
+ status: number | null;
58
+ transfers: TokenTransfer[];
59
+ } | null>;
60
+ }