perp-cli 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +152 -13
  2. package/dist/agent-wallet/store.d.ts +3 -2
  3. package/dist/agent-wallet/store.js +2 -2
  4. package/dist/api/public/hyperliquid.js +4 -0
  5. package/dist/api/public/pacifica.js +83 -13
  6. package/dist/api/public/urls.d.ts +2 -0
  7. package/dist/api/public/urls.js +3 -0
  8. package/dist/arb/utils.d.ts +1 -2
  9. package/dist/arb/utils.js +1 -2
  10. package/dist/bot/apex/pulse.js +3 -0
  11. package/dist/bot/apex/radar.js +2 -1
  12. package/dist/bot/engine.d.ts +10 -1
  13. package/dist/bot/engine.js +14 -6
  14. package/dist/bot/indicators.d.ts +7 -0
  15. package/dist/bot/indicators.js +16 -0
  16. package/dist/bot/perp-order.d.ts +4 -4
  17. package/dist/bot/perp-order.js +5 -5
  18. package/dist/bot/quarantine.d.ts +1 -1
  19. package/dist/bot/quarantine.js +1 -1
  20. package/dist/bot/strategies/apex-strategy.js +3 -2
  21. package/dist/bot/strategies/engine-mm.js +6 -5
  22. package/dist/bot/strategies/funding-arb-strategy.js +9 -9
  23. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
  24. package/dist/bot/strategies/funding-arb-utils.js +43 -24
  25. package/dist/bot/strategies/funding-auto.js +2 -2
  26. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  27. package/dist/bot/strategies/liquidation-mm.js +8 -2
  28. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  29. package/dist/bot/strategies/momentum-breakout.js +11 -3
  30. package/dist/bot/strategies/spot-perp-arb-strategy.js +23 -12
  31. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  32. package/dist/bot/strategies/spot-perp-executor.js +1 -1
  33. package/dist/bot/strategy-types.d.ts +2 -1
  34. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  35. package/dist/bot/tui/BotDashboard.js +1 -1
  36. package/dist/cache.js +12 -6
  37. package/dist/cli-program.d.ts +15 -0
  38. package/dist/cli-program.js +125 -5
  39. package/dist/commands/agent-import.d.ts +7 -0
  40. package/dist/commands/agent-import.js +30 -1
  41. package/dist/commands/agent.d.ts +6 -3
  42. package/dist/commands/agent.js +160 -12
  43. package/dist/commands/arb/index.js +4 -4
  44. package/dist/commands/arb-auto.js +21 -17
  45. package/dist/commands/arb.d.ts +12 -0
  46. package/dist/commands/arb.js +61 -143
  47. package/dist/commands/bot.js +1 -1
  48. package/dist/commands/builder.js +1 -1
  49. package/dist/commands/funds.js +113 -3
  50. package/dist/commands/init.d.ts +1 -1
  51. package/dist/commands/init.js +27 -7
  52. package/dist/commands/manage.js +23 -21
  53. package/dist/commands/market.js +168 -36
  54. package/dist/commands/options.d.ts +9 -0
  55. package/dist/commands/options.js +134 -0
  56. package/dist/commands/pacifica-earn.d.ts +5 -0
  57. package/dist/commands/pacifica-earn.js +468 -0
  58. package/dist/commands/pacifica-orders.d.ts +14 -0
  59. package/dist/commands/pacifica-orders.js +142 -0
  60. package/dist/commands/portfolio.js +32 -47
  61. package/dist/commands/rebalance.d.ts +1 -0
  62. package/dist/commands/rebalance.js +15 -0
  63. package/dist/commands/risk.d.ts +11 -2
  64. package/dist/commands/risk.js +39 -44
  65. package/dist/commands/trade.d.ts +1 -0
  66. package/dist/commands/trade.js +63 -58
  67. package/dist/commands/wallet.js +1 -1
  68. package/dist/config.d.ts +2 -2
  69. package/dist/config.js +15 -4
  70. package/dist/constants.js +6 -0
  71. package/dist/dashboard/server.js +1 -1
  72. package/dist/dashboard/ui.js +1 -1
  73. package/dist/errors.d.ts +24 -1
  74. package/dist/errors.js +25 -3
  75. package/dist/exchanges/arcus/adapter.d.ts +7 -7
  76. package/dist/exchanges/arcus/adapter.js +12 -12
  77. package/dist/exchanges/arcus/agent.d.ts +1 -1
  78. package/dist/exchanges/arcus/agent.js +1 -1
  79. package/dist/exchanges/arcus/client.js +1 -1
  80. package/dist/exchanges/arcus/signing.d.ts +2 -3
  81. package/dist/exchanges/arcus/signing.js +1 -1
  82. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  83. package/dist/exchanges/arcus/spot-chain.js +1 -1
  84. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  85. package/dist/exchanges/arcus/spot-router.js +2 -2
  86. package/dist/exchanges/arcus/spot.d.ts +2 -3
  87. package/dist/exchanges/arcus/spot.js +2 -3
  88. package/dist/exchanges/aster-spot.d.ts +3 -3
  89. package/dist/exchanges/aster-spot.js +6 -9
  90. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  91. package/dist/exchanges/aster-typed-data.js +1 -1
  92. package/dist/exchanges/aster.d.ts +9 -4
  93. package/dist/exchanges/aster.js +125 -55
  94. package/dist/exchanges/builder-fee.d.ts +1 -1
  95. package/dist/exchanges/builder-fee.js +13 -2
  96. package/dist/exchanges/canonical-symbols.d.ts +1 -2
  97. package/dist/exchanges/canonical-symbols.js +26 -2
  98. package/dist/exchanges/derive/client.d.ts +37 -0
  99. package/dist/exchanges/derive/client.js +135 -0
  100. package/dist/exchanges/derive/options.d.ts +68 -0
  101. package/dist/exchanges/derive/options.js +360 -0
  102. package/dist/exchanges/extended/adapter.d.ts +82 -0
  103. package/dist/exchanges/extended/adapter.js +179 -0
  104. package/dist/exchanges/extended/client.d.ts +38 -0
  105. package/dist/exchanges/extended/client.js +88 -0
  106. package/dist/exchanges/extended/public.d.ts +130 -0
  107. package/dist/exchanges/extended/public.js +429 -0
  108. package/dist/exchanges/grvt/adapter.d.ts +86 -0
  109. package/dist/exchanges/grvt/adapter.js +206 -0
  110. package/dist/exchanges/grvt/client.d.ts +25 -0
  111. package/dist/exchanges/grvt/client.js +86 -0
  112. package/dist/exchanges/grvt/public.d.ts +151 -0
  113. package/dist/exchanges/grvt/public.js +438 -0
  114. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  115. package/dist/exchanges/hyperliquid-spot.js +5 -5
  116. package/dist/exchanges/hyperliquid.d.ts +22 -16
  117. package/dist/exchanges/hyperliquid.js +80 -55
  118. package/dist/exchanges/index.d.ts +1 -1
  119. package/dist/exchanges/index.js +1 -1
  120. package/dist/exchanges/interface.d.ts +97 -8
  121. package/dist/exchanges/interface.js +19 -2
  122. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  123. package/dist/exchanges/ioc-outcome.js +1 -1
  124. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  125. package/dist/exchanges/legacy-trading-v2.js +7 -2
  126. package/dist/exchanges/lighter-public.d.ts +2 -2
  127. package/dist/exchanges/lighter-public.js +2 -2
  128. package/dist/exchanges/lighter-signer.d.ts +1 -1
  129. package/dist/exchanges/lighter-signer.js +1 -1
  130. package/dist/exchanges/lighter-spot.d.ts +3 -4
  131. package/dist/exchanges/lighter-spot.js +6 -7
  132. package/dist/exchanges/lighter.d.ts +11 -11
  133. package/dist/exchanges/lighter.js +28 -14
  134. package/dist/exchanges/ondo/adapter.d.ts +5 -6
  135. package/dist/exchanges/ondo/adapter.js +16 -14
  136. package/dist/exchanges/ondo/agent.d.ts +1 -1
  137. package/dist/exchanges/ondo/agent.js +1 -1
  138. package/dist/exchanges/ondo/client.d.ts +1 -1
  139. package/dist/exchanges/ondo/client.js +2 -2
  140. package/dist/exchanges/ondo/public.d.ts +8 -1
  141. package/dist/exchanges/ondo/public.js +13 -2
  142. package/dist/exchanges/ondo/spot.d.ts +2 -2
  143. package/dist/exchanges/ondo/spot.js +3 -3
  144. package/dist/exchanges/options-interface.d.ts +158 -0
  145. package/dist/exchanges/options-interface.js +17 -0
  146. package/dist/exchanges/options-venues.d.ts +12 -0
  147. package/dist/exchanges/options-venues.js +35 -0
  148. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  149. package/dist/exchanges/pacifica-atomic.js +440 -0
  150. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  151. package/dist/exchanges/pacifica-decimal.js +51 -0
  152. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  153. package/dist/exchanges/pacifica-presigned.js +141 -0
  154. package/dist/exchanges/pacifica-print.d.ts +200 -0
  155. package/dist/exchanges/pacifica-print.js +417 -0
  156. package/dist/exchanges/pacifica-spot.d.ts +2 -2
  157. package/dist/exchanges/pacifica-spot.js +3 -3
  158. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  159. package/dist/exchanges/pacifica-typed-data.js +1 -1
  160. package/dist/exchanges/pacifica-vaults.d.ts +121 -0
  161. package/dist/exchanges/pacifica-vaults.js +264 -0
  162. package/dist/exchanges/pacifica.d.ts +32 -5
  163. package/dist/exchanges/pacifica.js +114 -48
  164. package/dist/exchanges/precision.d.ts +3 -1
  165. package/dist/exchanges/precision.js +9 -2
  166. package/dist/exchanges/qfex/adapter.d.ts +18 -5
  167. package/dist/exchanges/qfex/adapter.js +32 -12
  168. package/dist/exchanges/qfex/agent.d.ts +1 -1
  169. package/dist/exchanges/qfex/agent.js +1 -1
  170. package/dist/exchanges/qfex/client.d.ts +1 -1
  171. package/dist/exchanges/qfex/client.js +1 -1
  172. package/dist/exchanges/qfex/errors.d.ts +1 -1
  173. package/dist/exchanges/qfex/errors.js +2 -2
  174. package/dist/exchanges/qfex/public.d.ts +3 -3
  175. package/dist/exchanges/qfex/public.js +3 -3
  176. package/dist/exchanges/qfex/signing.d.ts +2 -2
  177. package/dist/exchanges/qfex/signing.js +4 -5
  178. package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
  179. package/dist/exchanges/qfex/ws-transport.js +3 -3
  180. package/dist/exchanges/rate-limiter.d.ts +1 -1
  181. package/dist/exchanges/rate-limiter.js +5 -2
  182. package/dist/exchanges/registry.d.ts +1 -1
  183. package/dist/exchanges/registry.js +1 -1
  184. package/dist/exchanges/risex/adapter.d.ts +148 -0
  185. package/dist/exchanges/risex/adapter.js +796 -0
  186. package/dist/exchanges/risex/agent.d.ts +154 -0
  187. package/dist/exchanges/risex/agent.js +384 -0
  188. package/dist/exchanges/risex/client.d.ts +47 -0
  189. package/dist/exchanges/risex/client.js +174 -0
  190. package/dist/exchanges/risex/nonce.d.ts +43 -0
  191. package/dist/exchanges/risex/nonce.js +212 -0
  192. package/dist/exchanges/risex/public.d.ts +64 -0
  193. package/dist/exchanges/risex/public.js +150 -0
  194. package/dist/exchanges/risex/session.d.ts +71 -0
  195. package/dist/exchanges/risex/session.js +171 -0
  196. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  197. package/dist/exchanges/risex/typed-data.js +240 -0
  198. package/dist/exchanges/risex/units.d.ts +29 -0
  199. package/dist/exchanges/risex/units.js +104 -0
  200. package/dist/exchanges/slippage.d.ts +1 -1
  201. package/dist/exchanges/slippage.js +1 -1
  202. package/dist/exchanges/spot-factory.d.ts +3 -3
  203. package/dist/exchanges/spot-factory.js +7 -7
  204. package/dist/exchanges/spot-interface.d.ts +5 -5
  205. package/dist/exchanges/spot-interface.js +2 -2
  206. package/dist/exchanges/spot-result.d.ts +3 -3
  207. package/dist/exchanges/spot-result.js +3 -3
  208. package/dist/exchanges/trading-v2.d.ts +3 -3
  209. package/dist/exchanges/trading-v2.js +9 -6
  210. package/dist/exchanges/txflow/adapter.d.ts +54 -0
  211. package/dist/exchanges/txflow/adapter.js +118 -0
  212. package/dist/exchanges/txflow/client.d.ts +47 -0
  213. package/dist/exchanges/txflow/client.js +153 -0
  214. package/dist/exchanges/txflow/public.d.ts +76 -0
  215. package/dist/exchanges/txflow/public.js +284 -0
  216. package/dist/exchanges/unknown-symbol.d.ts +29 -0
  217. package/dist/exchanges/unknown-symbol.js +51 -0
  218. package/dist/exchanges/variational/adapter.d.ts +71 -0
  219. package/dist/exchanges/variational/adapter.js +137 -0
  220. package/dist/exchanges/variational/client.d.ts +21 -0
  221. package/dist/exchanges/variational/client.js +66 -0
  222. package/dist/exchanges/variational/public.d.ts +126 -0
  223. package/dist/exchanges/variational/public.js +430 -0
  224. package/dist/exchanges/venue-types.d.ts +6 -7
  225. package/dist/exchanges/venue-types.js +2 -2
  226. package/dist/exchanges/venue.d.ts +51 -8
  227. package/dist/exchanges/venue.js +54 -4
  228. package/dist/exchanges/venues/arcus.d.ts +1 -2
  229. package/dist/exchanges/venues/arcus.js +5 -4
  230. package/dist/exchanges/venues/aster.d.ts +2 -2
  231. package/dist/exchanges/venues/aster.js +6 -4
  232. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  233. package/dist/exchanges/venues/create-helpers.js +3 -3
  234. package/dist/exchanges/venues/extended.d.ts +21 -0
  235. package/dist/exchanges/venues/extended.js +96 -0
  236. package/dist/exchanges/venues/grvt.d.ts +30 -0
  237. package/dist/exchanges/venues/grvt.js +98 -0
  238. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  239. package/dist/exchanges/venues/hyperliquid.js +10 -8
  240. package/dist/exchanges/venues/index.d.ts +7 -2
  241. package/dist/exchanges/venues/index.js +8 -2
  242. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  243. package/dist/exchanges/venues/lighter-family.js +9 -11
  244. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  245. package/dist/exchanges/venues/lighter-rh.js +7 -5
  246. package/dist/exchanges/venues/lighter.d.ts +1 -1
  247. package/dist/exchanges/venues/lighter.js +5 -3
  248. package/dist/exchanges/venues/ondo.d.ts +1 -1
  249. package/dist/exchanges/venues/ondo.js +5 -3
  250. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  251. package/dist/exchanges/venues/pacifica.js +6 -4
  252. package/dist/exchanges/venues/qfex.d.ts +1 -1
  253. package/dist/exchanges/venues/qfex.js +4 -2
  254. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  255. package/dist/exchanges/venues/risex-release.js +14 -0
  256. package/dist/exchanges/venues/risex.d.ts +31 -0
  257. package/dist/exchanges/venues/risex.js +116 -0
  258. package/dist/exchanges/venues/txflow.d.ts +18 -0
  259. package/dist/exchanges/venues/txflow.js +77 -0
  260. package/dist/exchanges/venues/variational.d.ts +21 -0
  261. package/dist/exchanges/venues/variational.js +85 -0
  262. package/dist/execution-log.d.ts +1 -1
  263. package/dist/funding/legacy-public.d.ts +1 -1
  264. package/dist/funding/legacy-public.js +9 -4
  265. package/dist/funding/normalize.d.ts +1 -1
  266. package/dist/funding/normalize.js +1 -1
  267. package/dist/funding/rates.d.ts +44 -4
  268. package/dist/funding/rates.js +56 -12
  269. package/dist/index.js +7 -6
  270. package/dist/jobs.d.ts +1 -0
  271. package/dist/jobs.js +25 -1
  272. package/dist/landing.d.ts +2 -0
  273. package/dist/landing.js +1 -1
  274. package/dist/mcp-pacifica-earn.d.ts +9 -0
  275. package/dist/mcp-pacifica-earn.js +58 -0
  276. package/dist/mcp-server.js +113 -59
  277. package/dist/pacifica/client.d.ts +67 -4
  278. package/dist/pacifica/client.js +170 -8
  279. package/dist/pacifica/rate-limit.d.ts +144 -0
  280. package/dist/pacifica/rate-limit.js +267 -0
  281. package/dist/pacifica/types/order.d.ts +2 -1
  282. package/dist/plan-executor.js +7 -2
  283. package/dist/rebalance.js +1 -1
  284. package/dist/risk.d.ts +2 -1
  285. package/dist/risk.js +2 -4
  286. package/dist/settings.d.ts +32 -3
  287. package/dist/signer/ows-evm.d.ts +1 -1
  288. package/dist/signer/ows-evm.js +1 -1
  289. package/dist/smart-order.d.ts +1 -1
  290. package/dist/smart-order.js +1 -1
  291. package/dist/strategies/funding-arb.d.ts +1 -1
  292. package/dist/strategies/funding-arb.js +1 -1
  293. package/dist/trade-validator.d.ts +1 -1
  294. package/dist/trade-validator.js +9 -4
  295. package/dist/utils.d.ts +12 -3
  296. package/dist/utils.js +11 -3
  297. package/dist/venue-health.d.ts +40 -0
  298. package/dist/venue-health.js +75 -0
  299. package/dist/withdraw-gate.d.ts +1 -1
  300. package/dist/withdraw-gate.js +1 -1
  301. package/dist/ws/index.d.ts +959 -0
  302. package/dist/ws/index.js +4295 -0
  303. package/dist/ws/node.d.ts +975 -0
  304. package/dist/ws/node.js +4366 -0
  305. package/dist/ws/pacifica-instrument.d.ts +8 -0
  306. package/dist/ws/pacifica-instrument.js +8 -0
  307. package/dist/ws-cli/credentials.d.ts +62 -0
  308. package/dist/ws-cli/credentials.js +126 -0
  309. package/package.json +58 -10
  310. package/skills/perp-cli/SKILL.md +62 -9
  311. package/skills/perp-cli/references/agent-operations.md +7 -0
  312. package/skills/perp-cli/references/commands.md +76 -5
  313. package/skills/perp-cli/references/ws-library.md +492 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Extended public-only adapter — official API, MARKET DATA ONLY in this version (venues/extended.ts).
3
+ *
4
+ * Market data (markets, orderbook, trades, funding history, candles) is implemented with the
5
+ * documented public endpoints (https://api.docs.extended.exchange/ "Public REST-API"). Every
6
+ * account / trading method refuses with CAPABILITY_UNSUPPORTED before any request — there is no
7
+ * Extended account, order or signing code.
8
+ *
9
+ * A symbol is resolved first (resolveExtendedMarketRemote: `?market=<name>` reads, the full listing
10
+ * only when no wire-name candidate matches), so an unlisted symbol is SYMBOL_NOT_FOUND before any
11
+ * per-market request (the orderbook of an unknown market answers `{"status":"OK"}` without data,
12
+ * its trades `[]` — live 2026-10-19).
13
+ */
14
+ import { PerpError } from "../../errors.js";
15
+ import { annualizeHourlyRate } from "../../funding/normalize.js";
16
+ import { roleUnsupported } from "../venue.js";
17
+ import { toLegacyMarketInfo } from "../venue-types.js";
18
+ import { extendedShapeError } from "./client.js";
19
+ import { extendedClient, fetchExtendedMarketRows, resolveExtendedMarketRemote } from "./public.js";
20
+ /** CLI candle interval → Extended ISO-8601 duration (docs "Get candles history" / candles stream). */
21
+ export const EXTENDED_KLINE_INTERVALS = {
22
+ "1m": { wire: "PT1M", ms: 60_000 },
23
+ "5m": { wire: "PT5M", ms: 300_000 },
24
+ "15m": { wire: "PT15M", ms: 900_000 },
25
+ "30m": { wire: "PT30M", ms: 1_800_000 },
26
+ "1h": { wire: "PT1H", ms: 3_600_000 },
27
+ "2h": { wire: "PT2H", ms: 7_200_000 },
28
+ "4h": { wire: "PT4H", ms: 14_400_000 },
29
+ "8h": { wire: "PT8H", ms: 28_800_000 },
30
+ "12h": { wire: "PT12H", ms: 43_200_000 },
31
+ "1d": { wire: "PT24H", ms: 86_400_000 },
32
+ "1w": { wire: "P7D", ms: 604_800_000 },
33
+ };
34
+ /** Documented per-request maximum of the candles and funding-history endpoints. */
35
+ export const EXTENDED_MAX_RECORDS = 10_000;
36
+ const DEC = /^-?\d+(\.\d+)?$/;
37
+ function dec(v, what) {
38
+ if (typeof v !== "string" || !DEC.test(v))
39
+ throw extendedShapeError(`${what} is not a decimal string`);
40
+ return v;
41
+ }
42
+ function ms(v, what) {
43
+ if (typeof v !== "number" || !Number.isInteger(v) || v <= 0)
44
+ throw extendedShapeError(`${what} is not a ms timestamp`);
45
+ return v;
46
+ }
47
+ export class ExtendedPublicAdapter {
48
+ venue;
49
+ network;
50
+ name = "extended";
51
+ aliases = ["ext", "x10"];
52
+ client;
53
+ constructor(venue, network) {
54
+ this.venue = venue;
55
+ this.network = network;
56
+ this.client = extendedClient(network);
57
+ }
58
+ /** Per-market reads resolve the symbol with the small `?market=` read (public.ts), not the full listing. */
59
+ async market(symbol) {
60
+ return resolveExtendedMarketRemote(this.client, symbol);
61
+ }
62
+ // ── Market data ──
63
+ async getMarkets() {
64
+ const rows = await fetchExtendedMarketRows(this.client);
65
+ return rows.map(({ meta, market, nextFundingTime }) => ({
66
+ ...toLegacyMarketInfo(market),
67
+ venueSymbol: market.venueSymbol,
68
+ canonical: market.canonical,
69
+ assetClass: market.assetClass,
70
+ tickSize: market.tickSize,
71
+ minSize: market.minSize,
72
+ tradable: market.tradable,
73
+ status: meta.status,
74
+ isRfq: meta.isRfq,
75
+ tradingHours: meta.tradingHours,
76
+ isOffHours: meta.isOffHours,
77
+ fundingRateKind: "accruing",
78
+ nextFundingTime,
79
+ }));
80
+ }
81
+ async getOrderbook(symbol) {
82
+ const m = await this.market(symbol);
83
+ const body = await this.client.get(`/info/markets/${encodeURIComponent(m.name)}/orderbook`);
84
+ const data = body.data;
85
+ if (data === undefined || data === null || !Array.isArray(data.bid) || !Array.isArray(data.ask)) {
86
+ throw extendedShapeError(`orderbook of ${m.name} has no bid / ask lists`);
87
+ }
88
+ const side = (rows, what) => rows.map(r => {
89
+ const x = r;
90
+ return [dec(x.price, `${what} price`), dec(x.qty, `${what} qty`)];
91
+ });
92
+ return { bids: side(data.bid, "orderbook bid"), asks: side(data.ask, "orderbook ask") };
93
+ }
94
+ async getRecentTrades(symbol, limit = 50) {
95
+ const m = await this.market(symbol);
96
+ const body = await this.client.get(`/info/markets/${encodeURIComponent(m.name)}/trades`);
97
+ if (!Array.isArray(body.data))
98
+ throw extendedShapeError(`trades of ${m.name} is not a list`);
99
+ const trades = body.data.map(t => {
100
+ const x = t;
101
+ if (x.S !== "BUY" && x.S !== "SELL")
102
+ throw extendedShapeError(`trade side ${String(x.S)}`);
103
+ // Public tape carries no fee.
104
+ return { time: ms(x.T, "trade T"), symbol: m.symbol, side: x.S === "BUY" ? "buy" : "sell", price: dec(x.p, "trade p"), size: dec(x.q, "trade q"), fee: null };
105
+ });
106
+ // Newest first, like every other venue's tape.
107
+ return trades.sort((a, b) => b.time - a.time).slice(0, limit);
108
+ }
109
+ /** Applied (settled) hourly rates, newest first — fraction per hour; no oracle price published. */
110
+ async getFundingHistory(symbol, limit = 10) {
111
+ if (!Number.isInteger(limit) || limit < 1 || limit > EXTENDED_MAX_RECORDS) {
112
+ throw new PerpError("INVALID_PARAMS", `Extended: funding history limit must be 1..${EXTENDED_MAX_RECORDS} (got ${limit})`);
113
+ }
114
+ const m = await this.market(symbol);
115
+ const end = Date.now();
116
+ // One record per hour; two spare hours cover the in-progress hour and clock skew.
117
+ const start = end - (limit + 2) * 3_600_000;
118
+ const body = await this.client.get(`/info/${encodeURIComponent(m.name)}/funding`, [
119
+ ["startTime", String(start)], ["endTime", String(end)], ["limit", String(limit)],
120
+ ]);
121
+ if (!Array.isArray(body.data))
122
+ throw extendedShapeError(`funding history of ${m.name} is not a list`);
123
+ return body.data
124
+ .map(r => {
125
+ const x = r;
126
+ const rate = dec(x.f, "funding f");
127
+ return { time: ms(x.T, "funding T"), rate, price: null, rateKind: "lastSettled", intervalHours: 1, annualizedPct: annualizeHourlyRate(Number(rate)) };
128
+ })
129
+ .sort((a, b) => b.time - a.time)
130
+ .slice(0, limit);
131
+ }
132
+ async getKlines(symbol, interval, startTime, endTime) {
133
+ const iv = Object.prototype.hasOwnProperty.call(EXTENDED_KLINE_INTERVALS, interval) ? EXTENDED_KLINE_INTERVALS[interval] : undefined;
134
+ if (iv === undefined) {
135
+ throw new PerpError("INVALID_PARAMS", `Extended: unsupported candle interval "${interval}"`, {
136
+ remediation: `Use one of: ${Object.keys(EXTENDED_KLINE_INTERVALS).join(", ")}`,
137
+ });
138
+ }
139
+ if (!(endTime > startTime))
140
+ throw new PerpError("INVALID_PARAMS", "Extended: candle end time must be after the start time");
141
+ const m = await this.market(symbol);
142
+ const limit = Math.min(EXTENDED_MAX_RECORDS, Math.ceil((endTime - startTime) / iv.ms) + 1);
143
+ const body = await this.client.get(`/info/candles/${encodeURIComponent(m.name)}/trades`, [
144
+ ["interval", iv.wire], ["limit", String(limit)], ["endTime", String(endTime)],
145
+ ]);
146
+ if (!Array.isArray(body.data))
147
+ throw extendedShapeError(`candles of ${m.name} is not a list`);
148
+ return body.data
149
+ .map(c => {
150
+ const x = c;
151
+ return {
152
+ time: ms(x.T, "candle T"),
153
+ open: dec(x.o, "candle o"), high: dec(x.h, "candle h"), low: dec(x.l, "candle l"), close: dec(x.c, "candle c"),
154
+ volume: dec(x.v, "candle v"),
155
+ quoteVolume: null,
156
+ trades: null,
157
+ };
158
+ })
159
+ .filter(k => k.time >= startTime && k.time <= endTime)
160
+ .sort((a, b) => a.time - b.time);
161
+ }
162
+ // ── Account / trading: refused (market data only in this version) ──
163
+ refuse(feature) {
164
+ throw roleUnsupported(this.venue, feature);
165
+ }
166
+ async getBalance() { return this.refuse("account"); }
167
+ async getPositions() { return this.refuse("account"); }
168
+ async getOpenOrders() { return this.refuse("account"); }
169
+ async getOrderHistory() { return this.refuse("account"); }
170
+ async getTradeHistory() { return this.refuse("account"); }
171
+ async getFundingPayments() { return this.refuse("account"); }
172
+ async marketOrder() { return this.refuse("trading"); }
173
+ async limitOrder() { return this.refuse("trading"); }
174
+ async editOrder() { return this.refuse("trading"); }
175
+ async cancelOrder() { return this.refuse("trading"); }
176
+ async cancelAllOrders() { return this.refuse("trading"); }
177
+ async setLeverage() { return this.refuse("trading"); }
178
+ async stopOrder() { return this.refuse("trading"); }
179
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Extended public REST transport (official API, https://api.docs.extended.exchange/).
3
+ *
4
+ * Every response is `{status:"OK", data, pagination?}` or `{status:"ERROR", error:{code, message}}`
5
+ * (live 2026-10-19: an unknown market on /info/markets?market= or /info/<m>/funding is HTTP 400
6
+ * `{"code":1001,"message":"Market not found"}`). A User-Agent header is mandatory (docs
7
+ * "Mandatory headers"; without one the API answers 403). GET only — there is no write path.
8
+ */
9
+ import { PerpError } from "../../errors.js";
10
+ import type { RateLimitPolicy } from "../venue.js";
11
+ export declare const EXTENDED_HTTP_TIMEOUT_MS = 15000;
12
+ export declare const EXTENDED_USER_AGENT = "perp-cli";
13
+ /** Extended's error code for an unknown market (live 2026-10-19). */
14
+ export declare const EXTENDED_MARKET_NOT_FOUND = 1001;
15
+ /** A body that does not match the documented shape: an explicit EXCHANGE_ERROR, never a guess. */
16
+ export declare function extendedShapeError(what: string): PerpError;
17
+ export interface ExtendedErrorBody {
18
+ code: number | string | null;
19
+ message: string;
20
+ }
21
+ export declare class ExtendedClient {
22
+ private readonly restUrl;
23
+ private readonly rateLimit;
24
+ /** Limiter bucket: one per network (the documented limit is per IP, per host). */
25
+ private readonly bucket;
26
+ constructor(restUrl: string, rateLimit: RateLimitPolicy,
27
+ /** Limiter bucket: one per network (the documented limit is per IP, per host). */
28
+ bucket: string);
29
+ /**
30
+ * GET <rest><path>; returns the parsed body (`status: "OK"`). Every failure is a PerpError:
31
+ * transport → EXCHANGE_UNREACHABLE, 429 → RATE_LIMITED, `{status:"ERROR"}` / non-2xx /
32
+ * non-JSON → EXCHANGE_ERROR (an unknown market keeps the venue's 1001 code in details).
33
+ */
34
+ get<T = unknown>(path: string, query?: ReadonlyArray<[string, string]>): Promise<{
35
+ status: "OK";
36
+ data?: T;
37
+ } & Record<string, unknown>>;
38
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Extended public REST transport (official API, https://api.docs.extended.exchange/).
3
+ *
4
+ * Every response is `{status:"OK", data, pagination?}` or `{status:"ERROR", error:{code, message}}`
5
+ * (live 2026-10-19: an unknown market on /info/markets?market= or /info/<m>/funding is HTTP 400
6
+ * `{"code":1001,"message":"Market not found"}`). A User-Agent header is mandatory (docs
7
+ * "Mandatory headers"; without one the API answers 403). GET only — there is no write path.
8
+ */
9
+ import { PerpError, extractErrorMessage } from "../../errors.js";
10
+ import { acquireRate, rateLimitedError } from "../rate-limiter.js";
11
+ export const EXTENDED_HTTP_TIMEOUT_MS = 15_000;
12
+ export const EXTENDED_USER_AGENT = "perp-cli";
13
+ /** Extended's error code for an unknown market (live 2026-10-19). */
14
+ export const EXTENDED_MARKET_NOT_FOUND = 1001;
15
+ /** A body that does not match the documented shape: an explicit EXCHANGE_ERROR, never a guess. */
16
+ export function extendedShapeError(what) {
17
+ return new PerpError("EXCHANGE_ERROR", `Extended API response did not match the documented shape: ${what}`, {
18
+ exchange: "extended",
19
+ remediation: "Extended changed its public API; perp-cli needs an update to read it",
20
+ });
21
+ }
22
+ function errorBodyOf(parsed) {
23
+ if (parsed === null || typeof parsed !== "object")
24
+ return null;
25
+ const p = parsed;
26
+ if (p.status !== "ERROR")
27
+ return null;
28
+ const e = p.error;
29
+ const code = typeof e?.code === "number" || typeof e?.code === "string" ? e.code : null;
30
+ return { code, message: typeof e?.message === "string" ? e.message : JSON.stringify(p.error === undefined ? null : p.error) };
31
+ }
32
+ export class ExtendedClient {
33
+ restUrl;
34
+ rateLimit;
35
+ bucket;
36
+ constructor(restUrl, rateLimit,
37
+ /** Limiter bucket: one per network (the documented limit is per IP, per host). */
38
+ bucket) {
39
+ this.restUrl = restUrl;
40
+ this.rateLimit = rateLimit;
41
+ this.bucket = bucket;
42
+ }
43
+ /**
44
+ * GET <rest><path>; returns the parsed body (`status: "OK"`). Every failure is a PerpError:
45
+ * transport → EXCHANGE_UNREACHABLE, 429 → RATE_LIMITED, `{status:"ERROR"}` / non-2xx /
46
+ * non-JSON → EXCHANGE_ERROR (an unknown market keeps the venue's 1001 code in details).
47
+ */
48
+ async get(path, query = []) {
49
+ await acquireRate(this.bucket, this.rateLimit, path);
50
+ const qs = query.length > 0 ? `?${query.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&")}` : "";
51
+ let res;
52
+ try {
53
+ res = await fetch(`${this.restUrl}${path}${qs}`, {
54
+ headers: { accept: "application/json", "user-agent": EXTENDED_USER_AGENT },
55
+ signal: AbortSignal.timeout(EXTENDED_HTTP_TIMEOUT_MS),
56
+ });
57
+ }
58
+ catch (e) {
59
+ throw new PerpError("EXCHANGE_UNREACHABLE", `Extended ${path}: ${extractErrorMessage(e)}`, { exchange: "extended" });
60
+ }
61
+ const text = await res.text().catch((e) => {
62
+ throw new PerpError("EXCHANGE_UNREACHABLE", `Extended ${path}: response body unreadable (${extractErrorMessage(e)})`, { exchange: "extended" });
63
+ });
64
+ if (res.status === 429)
65
+ throw rateLimitedError("extended", res.headers.get("retry-after"), text);
66
+ let parsed;
67
+ try {
68
+ parsed = JSON.parse(text);
69
+ }
70
+ catch {
71
+ parsed = undefined;
72
+ }
73
+ const err = errorBodyOf(parsed);
74
+ if (err !== null || !res.ok) {
75
+ const msg = err !== null ? `${err.code === null ? "" : `${err.code} `}${err.message}` : `HTTP ${res.status} ${text.slice(0, 200)}`;
76
+ throw new PerpError("EXCHANGE_ERROR", `Extended ${path}: ${msg}`, {
77
+ exchange: "extended", status: res.status,
78
+ ...(err !== null && err.code !== null ? { venueCode: err.code } : {}),
79
+ });
80
+ }
81
+ if (parsed === undefined)
82
+ throw extendedShapeError(`${path} returned a non-JSON body`);
83
+ if (parsed === null || typeof parsed !== "object" || parsed.status !== "OK") {
84
+ throw extendedShapeError(`${path} has no status "OK"`);
85
+ }
86
+ return parsed;
87
+ }
88
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Extended public market data — GET /api/v1/info/markets (one call serves markets and funding).
3
+ * Docs: https://api.docs.extended.exchange/ ("Get markets").
4
+ *
5
+ * Mapping:
6
+ * - Only `type: "PERPETUAL"` rows with `status` ACTIVE or REDUCE_ONLY are markets here. The
7
+ * `active` flag is NOT a listing state (live 2026-10-19: 54 DELISTED and 12 PRELISTED rows have
8
+ * `active: true`); SPOT rows carry perp fields as 0 (docs) and are excluded.
9
+ * - venueSymbol = `name` ("BTC-USD", "TSLA_24_5-USD", "SPX500m-USD"), the wire id of every
10
+ * per-market endpoint. symbol = `assetName` upper-cased with the `_24_5` naming suffix removed
11
+ * ("TSLA_24_5" → "TSLA", "SPX500m" → "SPX500M"). Two listed markets with one symbol: the single
12
+ * ACTIVE one is kept, else both are skipped (warned).
13
+ * - tradable = status ACTIVE && !isOffHours (docs "Trading hours": while isOffHours, order
14
+ * placement and execution are unavailable). REDUCE_ONLY rows are listed, not tradable.
15
+ * - tick = tradingConfig.minPriceChange, step = minOrderSizeChange, min size = minOrderSize,
16
+ * maxLeverage = tradingConfig.maxLeverage; marginTiers from riskFactorConfig (rows available to
17
+ * users; leverage = 1 / riskFactor — the first tier equals maxLeverage on every live market).
18
+ * - open interest = marketStats.openInterestBase (base units); 24h volume = dailyVolume (USD).
19
+ *
20
+ * Asset class (`category` / `subCategory`): Crypto → crypto; RWA → Equity: equity, Commodity:
21
+ * commodity, FX: fx, Pre-market: preIpo, ETF/Index: etf for the ETFs in EXTENDED_ETFS, else index.
22
+ * Any other category / subCategory is skipped with a warning (never guessed).
23
+ *
24
+ * Canonical (cross-venue key, canonical-symbols.ts): crypto and equity / ETF by ticker identity;
25
+ * index / commodity / fx / preIpo only through EXPLICIT_CANONICAL. An RWA ticker that is also a
26
+ * crypto ticker is NEVER matched (canonical null): EXTENDED_RWA_CRYPTO_COLLISIONS (PURR =
27
+ * Hyperliquid Strategies Inc. vs the PURR token; QNT = Quantinuum vs Quant; BB = BlackBerry vs
28
+ * BounceBit; ON = ON Semiconductor vs the ON token; ADI = Analog Devices vs the ADI token). An RWA
29
+ * ticker equal to one of Extended's own crypto symbols is never matched either: symbols are unique per
30
+ * listing (dedupeSymbols). The crypto SPX market (SPX6900) is ambiguous with the S&P 500 index
31
+ * canonical "SPX" and is never matched (EXTENDED_AMBIGUOUS_CRYPTO, as Hyperliquid's SPX). XIAOMI is
32
+ * not matched (EXTENDED_UNMATCHED_EQUITIES): ~7.8× the Lighter / Aster XIAOMI price on 2026-10-19,
33
+ * i.e. a different unit (HKD-per-share vs a USD-quoted product), not the same contract.
34
+ *
35
+ * Funding (docs "Get funding rates history" + docs.extended.exchange funding-payments):
36
+ * - applied once per hour ("While the funding rate is calculated every minute, it is only applied
37
+ * once per hour"); payment = position size × mark price × (−rate).
38
+ * - marketStats.fundingRate is a FRACTION per hour (crypto interest component 0.01 % / 8 h =
39
+ * 0.0000125 / h, published as "0.000013"; RWA 0.0035 % / 8 h → "0.000004").
40
+ * - it is the CURRENT hour's rate, recomputed every minute (accruing): it can differ from the last
41
+ * applied print in /info/<market>/funding mid-hour, and becomes that print at the hour.
42
+ * - marketStats.nextFundingRate is (despite its name) the timestamp (ms) of the next application.
43
+ * - rates are published ROUNDED TO 6 DECIMALS (resolution 0.000001 / h ≈ 0.876 % APR): the
44
+ * 0.0000125 baseline reads "0.000013", and a rate below 0.0000005 / h reads "0.000000".
45
+ * - `_24_5` RWA equities print 0 while their underlying is closed (TSLA_24_5-USD: 34 of 43
46
+ * weekend prints were 0), so an hourly rate × 8760 overstates them — the venue is
47
+ * fundingVerified false (venues/extended.ts).
48
+ */
49
+ import { type VenuePublicData } from "../venue.js";
50
+ import type { AssetClass, FundingRateBasis, Network, VenueFundingRate, VenueMarket } from "../venue-types.js";
51
+ import { ExtendedClient } from "./client.js";
52
+ export declare const EXTENDED_FUNDING_BASIS: FundingRateBasis;
53
+ /** RWA "ETF/Index" markets that are ETFs (the rest of that subCategory are indices). */
54
+ export declare const EXTENDED_ETFS: ReadonlySet<string>;
55
+ /**
56
+ * RWA tickers that are also crypto tickers on other venues (live 2026-10-19) — never matched.
57
+ * ADI: Analog Devices vs Lighter's ADI token. BOT: RoboStrategy (PRELISTED on 2026-10-19) vs the BOT
58
+ * token (Lighter LIGHTER_AMBIGUOUS). WEN (also Lighter-ambiguous) is not listed on Extended.
59
+ */
60
+ export declare const EXTENDED_RWA_CRYPTO_COLLISIONS: ReadonlySet<string>;
61
+ /** RWA markets whose product differs from the same ticker elsewhere (XIAOMI: ~7.8× Lighter / Aster). */
62
+ export declare const EXTENDED_UNMATCHED_EQUITIES: ReadonlySet<string>;
63
+ /** Crypto tickers that name an index elsewhere — never matched (SPX-USD = SPX6900, not the S&P 500). */
64
+ export declare const EXTENDED_AMBIGUOUS_CRYPTO: ReadonlySet<string>;
65
+ export interface ExtendedMarketMeta {
66
+ /** Wire id ("BTC-USD"). */
67
+ name: string;
68
+ symbol: string;
69
+ assetClass: AssetClass;
70
+ canonical: string | null;
71
+ status: "ACTIVE" | "REDUCE_ONLY";
72
+ isRfq: boolean;
73
+ isOffHours: boolean;
74
+ tradingHours: string;
75
+ }
76
+ export interface ExtendedMarketRow {
77
+ meta: ExtendedMarketMeta;
78
+ market: VenueMarket;
79
+ nextFundingTime: number | null;
80
+ }
81
+ /** "TSLA_24_5" → "TSLA"; "SPX500m" → "SPX500M". */
82
+ export declare function extendedSymbolOf(assetName: string): string;
83
+ /** Asset class from category / subCategory; null = unknown (the row is skipped with a warning). */
84
+ export declare function extendedAssetClass(category: string, subCategory: string | null, symbol: string): AssetClass | null;
85
+ /** Canonical key, or null when the market must not be matched across venues. */
86
+ export declare function extendedCanonical(symbol: string, assetClass: AssetClass): string | null;
87
+ /**
88
+ * Parse /info/markets `data` into listed perpetual markets. A row that does not match the
89
+ * documented shape is skipped with a warning naming it (one drifted row never hides the others);
90
+ * rows of an unknown category, without a mark price, or sharing a symbol (dedupeSymbols) are
91
+ * skipped with a warning too. EXCHANGE_ERROR only for envelope drift (`data` not a list) or when
92
+ * malformed rows leave no market at all.
93
+ */
94
+ export declare function parseExtendedMarkets(data: unknown, warn: (msg: string) => void): ExtendedMarketRow[];
95
+ export declare function extendedClient(net: Network): ExtendedClient;
96
+ export interface ExtendedPublicDeps {
97
+ warn: (msg: string) => void;
98
+ }
99
+ export declare function fetchExtendedMarketRows(client: ExtendedClient, deps?: ExtendedPublicDeps): Promise<ExtendedMarketRow[]>;
100
+ export declare function fetchExtendedMarkets(net: Network, deps?: ExtendedPublicDeps): Promise<VenueMarket[]>;
101
+ /** Funding rows: the current hour's accruing rate (fraction per hour); a market without one has no row. */
102
+ export declare function toExtendedFundingRates(rows: readonly ExtendedMarketRow[]): VenueFundingRate[];
103
+ export declare function fetchExtendedFundingRates(net: Network, deps?: ExtendedPublicDeps): Promise<VenueFundingRate[]>;
104
+ export declare const extendedPublicData: VenuePublicData;
105
+ /**
106
+ * Resolve a CLI symbol against the listed markets: the wire name ("BTC-USD", "TSLA_24_5-USD")
107
+ * first, then the display symbol ("tsla", "BTC-PERP"), then "<symbol>-USD" ("TSLA-USD").
108
+ * Unlisted → SYMBOL_NOT_FOUND.
109
+ */
110
+ export declare function resolveExtendedMarket(markets: readonly ExtendedMarketMeta[], input: string): ExtendedMarketMeta;
111
+ /** Wire names a bare CLI symbol may be ("btc" → BTC-USD, BTC_24_5-USD): every name whose row maps to that symbol. */
112
+ export declare function extendedWireCandidates(symbol: string): string[];
113
+ /**
114
+ * Resolve a CLI symbol for a per-market read without the ~1 MB full listing, by the SAME rules as
115
+ * the listing (parseExtendedMarkets → dedupeSymbols, with its warning):
116
+ * 1. The symbol's wire names are read with `GET /info/markets?market=<name>` (2–3 KB each;
117
+ * case-sensitive; unknown = HTTP 400 code 1001): for a bare symbol `<S>-USD` and
118
+ * `<S>_24_5-USD`; for a wire name ("NOW-USD") that name first, then the other names of the
119
+ * symbol its row maps to — so a ticker migration (NOW-USD + NOW_24_5-USD) is deduplicated here
120
+ * exactly as in `market list`.
121
+ * 2. Every returned row goes through parseExtendedMarkets: not a listed perpetual, unpriced, or
122
+ * losing the duplicate-symbol rule → not a market. resolveExtendedMarket then decides;
123
+ * nothing left → SYMBOL_NOT_FOUND.
124
+ * 3. Only when EVERY name answered 1001 (no row at all — e.g. "spx500m" for SPX500m-USD) does the
125
+ * full listing decide.
126
+ * A malformed row in a `?market=` reply is EXCHANGE_ERROR when it leaves no row to parse (the
127
+ * market asked for is unreadable — no fallback to the listing); next to a valid row it is skipped
128
+ * with a warning, as in the listing. Any error other than 1001 is thrown as is.
129
+ */
130
+ export declare function resolveExtendedMarketRemote(client: ExtendedClient, input: string, deps?: ExtendedPublicDeps): Promise<ExtendedMarketMeta>;