perp-cli 0.15.0 → 0.16.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 (154) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +129 -535
  4. package/README.zh.md +243 -0
  5. package/dist/api/public/lighter.d.ts +5 -0
  6. package/dist/api/public/lighter.js +5 -0
  7. package/dist/api/public/pacifica.js +2 -2
  8. package/dist/arb/sizing.js +4 -2
  9. package/dist/arb/state.d.ts +6 -0
  10. package/dist/balance-by-address.d.ts +39 -0
  11. package/dist/balance-by-address.js +44 -0
  12. package/dist/bot/quote-rules.js +1 -1
  13. package/dist/bot/strategies/funding-arb-strategy.js +10 -3
  14. package/dist/bot/strategies/funding-arb-utils.d.ts +14 -2
  15. package/dist/bot/strategies/funding-arb-utils.js +48 -15
  16. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  17. package/dist/bot/strategies/spot-perp-arb-strategy.js +4 -2
  18. package/dist/bot/strategies/spot-perp-executor.js +2 -2
  19. package/dist/cli-program.js +1 -1
  20. package/dist/commands/account.js +168 -24
  21. package/dist/commands/arb/index.js +19 -77
  22. package/dist/commands/arb-auto.d.ts +24 -0
  23. package/dist/commands/arb-auto.js +155 -81
  24. package/dist/commands/arb.d.ts +8 -0
  25. package/dist/commands/arb.js +49 -23
  26. package/dist/commands/backtest.js +23 -21
  27. package/dist/commands/market.d.ts +48 -0
  28. package/dist/commands/market.js +241 -78
  29. package/dist/commands/options.d.ts +5 -0
  30. package/dist/commands/options.js +20 -3
  31. package/dist/commands/pacifica-earn.js +8 -21
  32. package/dist/commands/portfolio.d.ts +16 -0
  33. package/dist/commands/portfolio.js +51 -8
  34. package/dist/commands/trade.js +4 -4
  35. package/dist/cross-chain-margin.d.ts +3 -1
  36. package/dist/cross-chain-margin.js +4 -2
  37. package/dist/dashboard/server.js +2 -2
  38. package/dist/dashboard/ui.js +9 -6
  39. package/dist/errors.d.ts +5 -0
  40. package/dist/errors.js +10 -0
  41. package/dist/exchanges/address.d.ts +8 -0
  42. package/dist/exchanges/address.js +47 -0
  43. package/dist/exchanges/arcus/adapter.d.ts +10 -6
  44. package/dist/exchanges/arcus/adapter.js +33 -6
  45. package/dist/exchanges/arcus/public.js +3 -2
  46. package/dist/exchanges/aster.d.ts +27 -7
  47. package/dist/exchanges/aster.js +124 -42
  48. package/dist/exchanges/canonical-symbols.d.ts +7 -0
  49. package/dist/exchanges/canonical-symbols.js +20 -0
  50. package/dist/exchanges/capabilities.d.ts +2 -0
  51. package/dist/exchanges/capabilities.js +2 -1
  52. package/dist/exchanges/derive/options.d.ts +5 -0
  53. package/dist/exchanges/derive/options.js +20 -1
  54. package/dist/exchanges/extended/adapter.d.ts +16 -2
  55. package/dist/exchanges/extended/adapter.js +96 -34
  56. package/dist/exchanges/extended/public.d.ts +3 -0
  57. package/dist/exchanges/extended/public.js +9 -2
  58. package/dist/exchanges/grvt/adapter.d.ts +16 -2
  59. package/dist/exchanges/grvt/adapter.js +98 -21
  60. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  61. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  62. package/dist/exchanges/hyperliquid-spot.d.ts +8 -0
  63. package/dist/exchanges/hyperliquid-spot.js +20 -12
  64. package/dist/exchanges/hyperliquid.d.ts +180 -13
  65. package/dist/exchanges/hyperliquid.js +487 -185
  66. package/dist/exchanges/interface.d.ts +64 -8
  67. package/dist/exchanges/interface.js +31 -0
  68. package/dist/exchanges/lighter-public.d.ts +66 -11
  69. package/dist/exchanges/lighter-public.js +249 -81
  70. package/dist/exchanges/lighter-spot.d.ts +7 -1
  71. package/dist/exchanges/lighter-spot.js +99 -90
  72. package/dist/exchanges/lighter.d.ts +70 -11
  73. package/dist/exchanges/lighter.js +285 -92
  74. package/dist/exchanges/ondo/adapter.d.ts +34 -8
  75. package/dist/exchanges/ondo/adapter.js +73 -16
  76. package/dist/exchanges/ondo/public.d.ts +27 -2
  77. package/dist/exchanges/ondo/public.js +63 -31
  78. package/dist/exchanges/outcome-interface.d.ts +4 -0
  79. package/dist/exchanges/pacifica-spot.d.ts +6 -0
  80. package/dist/exchanges/pacifica-spot.js +23 -15
  81. package/dist/exchanges/pacifica-vaults.d.ts +9 -0
  82. package/dist/exchanges/pacifica-vaults.js +14 -1
  83. package/dist/exchanges/pacifica.d.ts +77 -6
  84. package/dist/exchanges/pacifica.js +321 -130
  85. package/dist/exchanges/public-balances.d.ts +25 -0
  86. package/dist/exchanges/public-balances.js +158 -0
  87. package/dist/exchanges/public-positions.d.ts +43 -0
  88. package/dist/exchanges/public-positions.js +154 -0
  89. package/dist/exchanges/qfex/adapter.d.ts +52 -9
  90. package/dist/exchanges/qfex/adapter.js +74 -9
  91. package/dist/exchanges/qfex/public.d.ts +62 -9
  92. package/dist/exchanges/qfex/public.js +128 -22
  93. package/dist/exchanges/qfex/ws-transport.d.ts +1 -1
  94. package/dist/exchanges/risex/adapter.d.ts +19 -7
  95. package/dist/exchanges/risex/adapter.js +58 -12
  96. package/dist/exchanges/spot-factory.js +1 -1
  97. package/dist/exchanges/txflow/adapter.d.ts +14 -7
  98. package/dist/exchanges/txflow/adapter.js +51 -19
  99. package/dist/exchanges/txflow/client.js +3 -1
  100. package/dist/exchanges/txflow/public.d.ts +12 -1
  101. package/dist/exchanges/txflow/public.js +82 -11
  102. package/dist/exchanges/unknown-symbol.d.ts +6 -1
  103. package/dist/exchanges/unknown-symbol.js +7 -2
  104. package/dist/exchanges/variational/adapter.d.ts +2 -6
  105. package/dist/exchanges/venue-types.d.ts +46 -3
  106. package/dist/exchanges/venue-types.js +80 -9
  107. package/dist/exchanges/venue.d.ts +99 -3
  108. package/dist/exchanges/venue.js +72 -0
  109. package/dist/exchanges/venues/hyperliquid.js +9 -0
  110. package/dist/exchanges/venues/lighter-rh.js +13 -3
  111. package/dist/exchanges/venues/lighter.js +13 -3
  112. package/dist/exchanges/venues/pacifica.js +9 -0
  113. package/dist/exchanges/venues/risex.js +15 -0
  114. package/dist/funding/legacy-public.d.ts +5 -3
  115. package/dist/funding/legacy-public.js +33 -37
  116. package/dist/funding/rates.d.ts +54 -5
  117. package/dist/funding/rates.js +49 -13
  118. package/dist/liquidity.d.ts +3 -1
  119. package/dist/liquidity.js +8 -4
  120. package/dist/mcp-pacifica-earn.js +11 -5
  121. package/dist/mcp-server.js +104 -15
  122. package/dist/pacifica/client.d.ts +2 -1
  123. package/dist/pacifica/client.js +11 -0
  124. package/dist/positions-by-address.d.ts +34 -0
  125. package/dist/positions-by-address.js +46 -0
  126. package/dist/strategies/funding-arb.js +35 -62
  127. package/dist/utils.d.ts +11 -1
  128. package/dist/venue-health.js +1 -1
  129. package/dist/ws/arcus-market.d.ts +9 -0
  130. package/dist/ws/arcus-market.js +9 -0
  131. package/dist/ws/aster-market.d.ts +33 -0
  132. package/dist/ws/aster-market.js +30 -0
  133. package/dist/ws/index.d.ts +30 -9
  134. package/dist/ws/index.js +180 -15
  135. package/dist/ws/lighter-market.d.ts +14 -0
  136. package/dist/ws/lighter-market.js +13 -0
  137. package/dist/ws/node.d.ts +30 -9
  138. package/dist/ws/node.js +204 -16
  139. package/dist/ws/unclassified.d.ts +16 -0
  140. package/dist/ws/unclassified.js +43 -0
  141. package/package.json +15 -2
  142. package/skills/perp-cli/SKILL.md +97 -33
  143. package/skills/perp-cli/package.json +15 -2
  144. package/skills/perp-cli/references/agent-operations.md +15 -12
  145. package/skills/perp-cli/references/commands.md +67 -12
  146. package/skills/perp-cli/references/strategies.md +38 -3
  147. package/skills/perp-cli/references/ws-library.md +55 -24
  148. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  149. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  150. package/skills/perp-cli/scripts/preflight.sh +27 -19
  151. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  152. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  153. package/dist/cli-spec.d.ts +0 -46
  154. package/dist/cli-spec.js +0 -81
@@ -34,13 +34,14 @@
34
34
  * Limit prices and TP/SL triggers must be on tick (INVALID_TICK); sizes are
35
35
  * floored to the step (SIZE_TOO_SMALL when that reaches zero).
36
36
  */
37
- import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
37
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
38
38
  import type { ResolvedSlippage } from "../slippage.js";
39
39
  import type { TradingV2 } from "../trading-v2.js";
40
40
  import type { VenueCreateContext, VenueDefinition } from "../venue.js";
41
41
  import type { AmendRequest, CancelResult, LeverageResult, Network, OrderRef, OrderRequest, OrderResult, VenueMarket } from "../venue-types.js";
42
42
  import { type BuilderFeeGate } from "../builder-fee.js";
43
43
  import { OndoClient } from "./client.js";
44
+ import { type OndoListedStatus } from "./public.js";
44
45
  export declare class OndoAdapter implements ExchangeAdapter, TradingV2 {
45
46
  readonly name: "ondo";
46
47
  readonly aliases: readonly ["ondo-perps"];
@@ -49,6 +50,7 @@ export declare class OndoAdapter implements ExchangeAdapter, TradingV2 {
49
50
  private readonly caps;
50
51
  private readonly client;
51
52
  private pairsCache;
53
+ private contractsCache;
52
54
  /**
53
55
  * Builder-fee gate (settings builder.ondo; Ondo has no approval step); null = not
54
56
  * configured. Set by the venue definition's create — the adapter never reads settings.
@@ -65,6 +67,8 @@ export declare class OndoAdapter implements ExchangeAdapter, TradingV2 {
65
67
  get restClient(): OndoClient;
66
68
  /** Account id from GET /v1/account — the `from.id`/`to.id` of withdrawals and spot↔perp transfers. */
67
69
  accountId(): Promise<string>;
70
+ private pairsByMarket;
71
+ private contractsByMarket;
68
72
  private pairOf;
69
73
  /**
70
74
  * An order the venue returned as "pending" (not yet open / filled) is read back by id
@@ -99,18 +103,40 @@ export declare class OndoAdapter implements ExchangeAdapter, TradingV2 {
99
103
  cancelAllV2(symbol: string | null): Promise<CancelResult>;
100
104
  applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
101
105
  getOrderStatus(_symbol: string, ref: OrderRef): Promise<OrderResult>;
102
- getMarkets(): Promise<ExchangeMarketInfo[]>;
106
+ /** Legacy rows of LISTED markets plus `tradable` (status "active") and `status` (active / closed / reduceOnly). */
107
+ getMarkets(): Promise<Array<ExchangeMarketInfo & {
108
+ tradable: boolean;
109
+ status: OndoListedStatus;
110
+ }>>;
111
+ /**
112
+ * `market info` for one market, resolved with the Ondo normalizer (ondoVenueSymbol: "AAPL",
113
+ * "AAPL-USD", "AAPL-USD.P", "AAPL-PERP"). A disabled market is refused like every per-symbol read.
114
+ */
115
+ getMarketInfo(symbol: string): Promise<ExchangeMarketInfo & {
116
+ tradable: boolean;
117
+ status: OndoListedStatus;
118
+ }>;
119
+ /**
120
+ * Venue symbol of a per-symbol read (book / trades / funding / kline), via the one normalizer
121
+ * (ondoVenueSymbol). A disabled (delisted / paused) listing → SYMBOL_NOT_FOUND with status
122
+ * "disabled": its prices are zeroed or frozen and its funding rate is stale, so nothing it returns
123
+ * is live market data. An unlisted symbol is left to each read's own SYMBOL_NOT_FOUND
124
+ * (nonEmptyOrListed / the venue's answer).
125
+ */
126
+ private readableMarket;
103
127
  getOrderbook(symbol: string): Promise<{
104
128
  bids: [string, string][];
105
129
  asks: [string, string][];
106
130
  }>;
107
131
  getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
108
- getFundingHistory(symbol: string, limit?: number): Promise<{
109
- time: number;
110
- rate: string;
111
- price: string | null;
112
- }[]>;
113
- /** GET /v1/perps/candles — authenticated (see klinesRequireAuth). */
132
+ getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
133
+ /**
134
+ * GET /v1/perps/candles — authenticated (see klinesRequireAuth).
135
+ * Minute-based resolutions are bucketed from `from` (live 2026-10-19: from=10:01:07 → candles at
136
+ * :01), so `from` is floored to the interval boundary first; D / W / M are venue-aligned already.
137
+ * The venue also returns a zero-volume bucket that opens after `to`; candles opening after the
138
+ * requested end are not part of the range and are left out.
139
+ */
114
140
  getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
115
141
  getBalance(): Promise<ExchangeBalance>;
116
142
  /**
@@ -44,7 +44,7 @@ import { parseOrderRef, toLegacyMarketInfo } from "../venue-types.js";
44
44
  import { builderGateOf, builderRejectionOf, NO_BUILDER, requireFeeBps } from "../builder-fee.js";
45
45
  import { loadOndoCredentials, ONDO_APPROVE_REMEDIATION } from "./agent.js";
46
46
  import { OndoClient } from "./client.js";
47
- import { fetchOndoTradingPairs, ondoPlainSymbol, ondoPublicData, ondoVenueSymbol, parseOndoTime, strOrNull, } from "./public.js";
47
+ import { fetchOndoMarketsWithStatus, fetchOndoTradingPairs, ondoDisabled, ondoPlainSymbol, ondoPublicData, ondoVenueSymbol, parseOndoTime, strOrNull, } from "./public.js";
48
48
  const ORDERS_PAGE_LIMIT = 100;
49
49
  const DEPTH_LEVELS = 50;
50
50
  const PAIRS_TTL_MS = 5 * 60_000;
@@ -164,6 +164,7 @@ export class OndoAdapter {
164
164
  caps;
165
165
  client;
166
166
  pairsCache = null;
167
+ contractsCache = null;
167
168
  /**
168
169
  * Builder-fee gate (settings builder.ondo; Ondo has no approval step); null = not
169
170
  * configured. Set by the venue definition's create — the adapter never reads settings.
@@ -217,13 +218,25 @@ export class OndoAdapter {
217
218
  return account.accountID;
218
219
  }
219
220
  // ── Helpers ──
220
- async pairOf(symbol) {
221
- const market = ondoVenueSymbol(symbol);
221
+ async pairsByMarket() {
222
222
  if (this.pairsCache === null || Date.now() - this.pairsCache.at > PAIRS_TTL_MS) {
223
223
  const pairs = await fetchOndoTradingPairs(this.client);
224
224
  this.pairsCache = { at: Date.now(), byMarket: new Map(pairs.map(p => [p.market, p])) };
225
225
  }
226
- const pair = this.pairsCache.byMarket.get(market);
226
+ return this.pairsCache.byMarket;
227
+ }
228
+ async contractsByMarket() {
229
+ if (this.contractsCache === null || Date.now() - this.contractsCache.at > PAIRS_TTL_MS) {
230
+ const rows = await this.client.publicGet("/v1/perps/contracts", null);
231
+ if (!Array.isArray(rows))
232
+ throw new PerpError("EXCHANGE_ERROR", "Ondo Perps: /v1/perps/contracts is not a list");
233
+ this.contractsCache = { at: Date.now(), byMarket: new Map(rows.map(c => [c.market, c])) };
234
+ }
235
+ return this.contractsCache.byMarket;
236
+ }
237
+ async pairOf(symbol) {
238
+ const market = ondoVenueSymbol(symbol);
239
+ const pair = (await this.pairsByMarket()).get(market);
227
240
  if (pair === undefined) {
228
241
  throw new PerpError("SYMBOL_NOT_FOUND", `Ondo Perps has no market ${market}`, {
229
242
  remediation: "List markets with: perp market list -e ondo",
@@ -657,17 +670,50 @@ export class OndoAdapter {
657
670
  return this.toOrderResult(order, order, readExecution(order));
658
671
  }
659
672
  // ── Legacy ExchangeAdapter: market data ──
673
+ /** Legacy rows of LISTED markets plus `tradable` (status "active") and `status` (active / closed / reduceOnly). */
660
674
  async getMarkets() {
661
- return (await this.getVenueMarkets()).map(toLegacyMarketInfo);
675
+ return (await fetchOndoMarketsWithStatus(this.network)).map(r => ({ ...toLegacyMarketInfo(r.market), tradable: r.market.tradable, status: r.status }));
676
+ }
677
+ /**
678
+ * `market info` for one market, resolved with the Ondo normalizer (ondoVenueSymbol: "AAPL",
679
+ * "AAPL-USD", "AAPL-USD.P", "AAPL-PERP"). A disabled market is refused like every per-symbol read.
680
+ */
681
+ async getMarketInfo(symbol) {
682
+ const market = (await this.pairOf(symbol)).market; // unlisted → SYMBOL_NOT_FOUND
683
+ await this.readableMarket(market);
684
+ const row = (await this.getMarkets()).find(m => m.symbol === ondoPlainSymbol(market));
685
+ if (row === undefined) {
686
+ throw new PerpError("SYMBOL_NOT_FOUND", `Ondo Perps ${market} has no mark price (no price feed)`, { exchange: "ondo", remediation: "List markets with: perp market list -e ondo" });
687
+ }
688
+ return row;
689
+ }
690
+ /**
691
+ * Venue symbol of a per-symbol read (book / trades / funding / kline), via the one normalizer
692
+ * (ondoVenueSymbol). A disabled (delisted / paused) listing → SYMBOL_NOT_FOUND with status
693
+ * "disabled": its prices are zeroed or frozen and its funding rate is stale, so nothing it returns
694
+ * is live market data. An unlisted symbol is left to each read's own SYMBOL_NOT_FOUND
695
+ * (nonEmptyOrListed / the venue's answer).
696
+ */
697
+ async readableMarket(symbol) {
698
+ const market = ondoVenueSymbol(symbol);
699
+ const pair = (await this.pairsByMarket()).get(market);
700
+ // Same predicate as the listing (ondoDisabled): the /v1/markets pair OR the contracts row.
701
+ if (pair !== undefined && ondoDisabled(pair, (await this.contractsByMarket()).get(market) ?? null)) {
702
+ throw new PerpError("SYMBOL_NOT_FOUND", `Ondo Perps ${market} is disabled (delisted / paused) — no live market data`, {
703
+ exchange: "ondo", status: "disabled", remediation: "List tradable markets with: perp market list -e ondo",
704
+ });
705
+ }
706
+ return market;
662
707
  }
663
708
  async getOrderbook(symbol) {
664
- const book = await this.client.publicGet("/v1/perps/depth", { market: ondoVenueSymbol(symbol), depth: DEPTH_LEVELS });
709
+ const book = await this.client.publicGet("/v1/perps/depth", { market: await this.readableMarket(symbol), depth: DEPTH_LEVELS });
665
710
  const lvl = (l) => [l[0], l[1]];
666
711
  return { bids: book.bids.map(lvl), asks: book.asks.map(lvl) };
667
712
  }
668
713
  async getRecentTrades(symbol, limit) {
714
+ const market = await this.readableMarket(symbol);
669
715
  const trades = await this.client.publicGet("/v1/perps/trades", {
670
- market: ondoVenueSymbol(symbol), limit: limit === undefined ? null : limit,
716
+ market, limit: limit === undefined ? null : limit,
671
717
  });
672
718
  // `200 []` for an unlisted market (live 2026-10-14): checked against the market directory.
673
719
  return nonEmptyOrListed(this, symbol, listOf(trades).map(t => ({
@@ -677,23 +723,34 @@ export class OndoAdapter {
677
723
  price: t.price,
678
724
  size: t.size,
679
725
  fee: null, // public tape: no fee applies to the viewer
680
- })), ondoPlainSymbol(ondoVenueSymbol(symbol)));
726
+ })), ondoPlainSymbol(market));
681
727
  }
682
728
  async getFundingHistory(symbol, limit) {
683
729
  const rows = await this.client.publicGet("/v1/perps/funding_rate_history", {
684
- market: ondoVenueSymbol(symbol), limit: limit === undefined ? null : limit,
730
+ market: await this.readableMarket(symbol), limit: limit === undefined ? null : limit,
685
731
  });
686
- return listOf(rows).map(r => ({ time: parseOndoTime(r.time, "funding"), rate: r.fundingRate, price: null }));
732
+ return listOf(rows).map(r => ({ time: parseOndoTime(r.time, "funding"), rate: r.fundingRate, price: null, intervalHours: 1, rateKind: "lastSettled" }));
687
733
  }
688
- /** GET /v1/perps/candles — authenticated (see klinesRequireAuth). */
734
+ /**
735
+ * GET /v1/perps/candles — authenticated (see klinesRequireAuth).
736
+ * Minute-based resolutions are bucketed from `from` (live 2026-10-19: from=10:01:07 → candles at
737
+ * :01), so `from` is floored to the interval boundary first; D / W / M are venue-aligned already.
738
+ * The venue also returns a zero-volume bucket that opens after `to`; candles opening after the
739
+ * requested end are not part of the range and are left out.
740
+ */
689
741
  async getKlines(symbol, interval, startTime, endTime) {
742
+ const resolution = toResolution(interval);
743
+ const minuteMs = /^\d+$/.test(resolution) ? Number(resolution) * 60_000 : null;
744
+ const from = minuteMs === null ? startTime : Math.floor(startTime / minuteMs) * minuteMs;
745
+ const market = await this.readableMarket(symbol);
690
746
  const candles = await this.client.get("/v1/perps/candles", {
691
- market: ondoVenueSymbol(symbol),
692
- resolution: toResolution(interval),
693
- from: Math.floor(startTime / 1000),
747
+ market,
748
+ resolution,
749
+ from: Math.floor(from / 1000),
694
750
  to: Math.floor(endTime / 1000),
695
751
  });
696
- return nonEmptyOrListed(this, symbol, listOf(candles).map(c => ({
752
+ const inRange = listOf(candles).filter(c => parseOndoTime(c.startTime, "candle") <= endTime);
753
+ return nonEmptyOrListed(this, symbol, inRange.map(c => ({
697
754
  time: parseOndoTime(c.startTime, "candle"),
698
755
  open: c.open,
699
756
  high: c.high,
@@ -702,7 +759,7 @@ export class OndoAdapter {
702
759
  volume: c.volume,
703
760
  quoteVolume: null,
704
761
  trades: null, // Ondo publishes no per-candle trade count
705
- })), ondoPlainSymbol(ondoVenueSymbol(symbol)));
762
+ })), ondoPlainSymbol(market));
706
763
  }
707
764
  // ── Legacy ExchangeAdapter: account ──
708
765
  async getBalance() {
@@ -18,7 +18,7 @@
18
18
  * fetchOndoSpotTradingPairs.
19
19
  */
20
20
  import type { VenuePublicData } from "../venue.js";
21
- import type { AssetClass, FundingRateBasis, Network } from "../venue-types.js";
21
+ import type { AssetClass, FundingRateBasis, Network, VenueMarket } from "../venue-types.js";
22
22
  import { OndoClient } from "./client.js";
23
23
  export declare const ONDO_PERP_SUFFIX = "-USD.P";
24
24
  export declare const ONDO_FUNDING_BASIS: FundingRateBasis;
@@ -84,8 +84,24 @@ export interface OndoMarkPrice {
84
84
  export declare function ondoAssetClass(market: string, tags: readonly string[] | null | undefined): AssetClass;
85
85
  /** "TSLA-USD.P" → "TSLA". Throws for non-perp symbols. */
86
86
  export declare function ondoPlainSymbol(venueSymbol: string): string;
87
- /** CLI symbol ("tsla", "TSLA", "TSLA-USD.P") → venue symbol "TSLA-USD.P". */
87
+ /**
88
+ * The one Ondo symbol normalizer, used by every command: "tsla", "TSLA", "TSLA-USD", "TSLA-USD.P",
89
+ * "TSLA-PERP" → venue symbol "TSLA-USD.P". 0.15.0 appended the suffix to whatever it was given
90
+ * ("AAPL-USD" → "AAPL-USD-USD.P", "AAPL-PERP" → "AAPL-PERP-USD.P").
91
+ */
88
92
  export declare function ondoVenueSymbol(symbol: string): string;
93
+ /**
94
+ * Listing state of an Ondo perp. "disabled" = delisted / paused (`disabled: true` on the
95
+ * /v1/markets pair or the /v1/perps/contracts row; its price fields are zeroed or frozen and its
96
+ * fundingRate is stale); "closed" = `isClosed` (no new trading); "reduceOnly" = closes only.
97
+ * Only "active" is tradable.
98
+ */
99
+ export type OndoMarketStatus = "active" | "disabled" | "closed" | "reduceOnly";
100
+ /** State of a LISTED market: a disabled one is not listed (listing policy: delisted / disabled / inactive → not listed). */
101
+ export type OndoListedStatus = Exclude<OndoMarketStatus, "disabled">;
102
+ /** The one "disabled" predicate (listing and every per-symbol read): either source flags it. */
103
+ export declare function ondoDisabled(pair: Pick<OndoTradingPair, "disabled">, contract: Pick<OndoContract, "disabled"> | null): boolean;
104
+ export declare function ondoMarketStatus(pair: Pick<OndoTradingPair, "disabled">, contract: Pick<OndoContract, "disabled" | "isClosed" | "reduceOnly"> | null): OndoMarketStatus;
89
105
  /** Empty string / absent → null. Ondo omits or blanks fields it does not publish. */
90
106
  export declare function strOrNull(v: string | undefined | null): string | null;
91
107
  /**
@@ -99,4 +115,13 @@ export declare function parseOndoTime(iso: string, what: string): number;
99
115
  export declare function ondoPublicClient(net: Network): OndoClient;
100
116
  export declare function fetchOndoTradingPairs(client: OndoClient): Promise<OndoTradingPair[]>;
101
117
  export declare function fetchOndoSpotTradingPairs(client: OndoClient): Promise<OndoSpotTradingPair[]>;
118
+ /**
119
+ * Every listed Ondo perp with its state (getMarkets rows carry `status`; VenueMarket has no field
120
+ * for it). Disabled (delisted / paused) listings are NOT listed — per-symbol reads refuse them —
121
+ * while closed / reduce-only markets stay listed with tradable false.
122
+ */
123
+ export declare function fetchOndoMarketsWithStatus(net: Network): Promise<Array<{
124
+ market: VenueMarket;
125
+ status: OndoListedStatus;
126
+ }>>;
102
127
  export declare const ondoPublicData: VenuePublicData;
@@ -54,12 +54,29 @@ export function ondoPlainSymbol(venueSymbol) {
54
54
  }
55
55
  return venueSymbol.slice(0, -ONDO_PERP_SUFFIX.length);
56
56
  }
57
- /** CLI symbol ("tsla", "TSLA", "TSLA-USD.P") → venue symbol "TSLA-USD.P". */
57
+ /**
58
+ * The one Ondo symbol normalizer, used by every command: "tsla", "TSLA", "TSLA-USD", "TSLA-USD.P",
59
+ * "TSLA-PERP" → venue symbol "TSLA-USD.P". 0.15.0 appended the suffix to whatever it was given
60
+ * ("AAPL-USD" → "AAPL-USD-USD.P", "AAPL-PERP" → "AAPL-PERP-USD.P").
61
+ */
58
62
  export function ondoVenueSymbol(symbol) {
59
- const up = symbol.trim().toUpperCase();
60
- if (up.length === 0)
63
+ const base = symbol.trim().toUpperCase().replace(/(-USD\.P|-PERP|-USD)$/, "");
64
+ if (base.length === 0)
61
65
  throw new PerpError("INVALID_PARAMS", "symbol is empty");
62
- return up.endsWith(ONDO_PERP_SUFFIX) ? up : `${up}${ONDO_PERP_SUFFIX}`;
66
+ return `${base}${ONDO_PERP_SUFFIX}`;
67
+ }
68
+ /** The one "disabled" predicate (listing and every per-symbol read): either source flags it. */
69
+ export function ondoDisabled(pair, contract) {
70
+ return pair.disabled === true || contract?.disabled === true;
71
+ }
72
+ export function ondoMarketStatus(pair, contract) {
73
+ if (ondoDisabled(pair, contract))
74
+ return "disabled";
75
+ if (contract?.isClosed === true)
76
+ return "closed";
77
+ if (contract?.reduceOnly === true)
78
+ return "reduceOnly";
79
+ return "active";
63
80
  }
64
81
  /** Empty string / absent → null. Ondo omits or blanks fields it does not publish. */
65
82
  export function strOrNull(v) {
@@ -150,7 +167,12 @@ function requireRow(m, market, source) {
150
167
  }
151
168
  return row;
152
169
  }
153
- async function fetchMarkets(net) {
170
+ /**
171
+ * Every listed Ondo perp with its state (getMarkets rows carry `status`; VenueMarket has no field
172
+ * for it). Disabled (delisted / paused) listings are NOT listed — per-symbol reads refuse them —
173
+ * while closed / reduce-only markets stay listed with tradable false.
174
+ */
175
+ export async function fetchOndoMarketsWithStatus(net) {
154
176
  const client = ondoPublicClient(net);
155
177
  const pairs = await fetchOndoTradingPairs(client);
156
178
  const contracts = byMarket(await client.publicGet("/v1/perps/contracts", null));
@@ -162,37 +184,47 @@ async function fetchMarkets(net) {
162
184
  if (noMark.length > 0) {
163
185
  console.warn(`[ondo] excluded ${noMark.length} market(s) without a mark price: ${noMark.join(", ")}`);
164
186
  }
165
- return pairs.filter(p => marks[p.market] !== undefined).map((pair) => {
187
+ const out = [];
188
+ for (const pair of pairs.filter(p => marks[p.market] !== undefined)) {
166
189
  const contract = requireRow(contracts, pair.market, "/v1/perps/contracts");
167
190
  const mark = marks[pair.market];
168
191
  const symbol = ondoPlainSymbol(pair.market);
169
192
  const assetClass = ondoAssetClass(pair.market, pair.tags);
170
193
  const { maxLeverage, tiers } = marginTiersOf(pair);
171
- return {
172
- venue: "ondo",
173
- venueSymbol: pair.market,
174
- symbol,
175
- canonical: canonicalFor("ondo", symbol, assetClass),
176
- assetClass,
177
- settle: "USDC",
178
- quoteAsset: pair.pair.quote,
179
- markPrice: mark.markPrice,
180
- indexPrice: priceOrNull(contract.indexPrice),
181
- fundingRate: strOrNull(contract.fundingRate),
182
- fundingRateBasis: ONDO_FUNDING_BASIS,
183
- settlementIntervalHours: ONDO_SETTLEMENT_HOURS,
184
- openInterest: strOrNull(contract.openInterest),
185
- volume24h: strOrNull(contract.usdVolume),
186
- tickSize: pair.quoteIncrement,
187
- stepSize: pair.baseIncrement,
188
- minSize: null,
189
- minNotional: null,
190
- maxLeverage,
191
- marginTiers: tiers,
192
- session: sessionOf(pair, mark),
193
- tradable: pair.disabled !== true && !contract.disabled && contract.isClosed !== true && contract.reduceOnly !== true,
194
- };
195
- });
194
+ const status = ondoMarketStatus(pair, contract);
195
+ if (status === "disabled")
196
+ continue;
197
+ out.push({ status, market: {
198
+ venue: "ondo",
199
+ venueSymbol: pair.market,
200
+ symbol,
201
+ canonical: canonicalFor("ondo", symbol, assetClass),
202
+ assetClass,
203
+ settle: "USDC",
204
+ quoteAsset: pair.pair.quote,
205
+ markPrice: mark.markPrice,
206
+ // /v1/perps/contracts `indexPrice` is frozen (live 2026-10-19: BTC 82702.30 unchanged for 17 min
207
+ // while the mark_prices oracle moved 0.5%); the oracle of /v1/perps/mark_prices is the live index.
208
+ indexPrice: priceOrNull(mark.oraclePrice),
209
+ fundingRate: strOrNull(contract.fundingRate),
210
+ fundingRateBasis: ONDO_FUNDING_BASIS,
211
+ settlementIntervalHours: ONDO_SETTLEMENT_HOURS,
212
+ openInterest: strOrNull(contract.openInterest),
213
+ volume24h: strOrNull(contract.usdVolume),
214
+ tickSize: pair.quoteIncrement,
215
+ stepSize: pair.baseIncrement,
216
+ minSize: null,
217
+ minNotional: null,
218
+ maxLeverage,
219
+ marginTiers: tiers,
220
+ session: sessionOf(pair, mark),
221
+ tradable: status === "active",
222
+ } });
223
+ }
224
+ return out;
225
+ }
226
+ async function fetchMarkets(net) {
227
+ return (await fetchOndoMarketsWithStatus(net)).map(r => ({ ...r.market, status: r.status }));
196
228
  }
197
229
  async function fetchFundingRates(net) {
198
230
  const client = ondoPublicClient(net);
@@ -80,6 +80,10 @@ export interface OutcomeViewSide extends OutcomeSideInfo {
80
80
  bestAsk?: string;
81
81
  /** Implied probability of THIS side winning, derived from mid */
82
82
  impliedProb?: number;
83
+ /** Side name without a "template:" prefix ("template:Yes" → "Yes"). */
84
+ label?: string;
85
+ /** true = bid–ask wider than OUTCOME_WIDE_SPREAD: the mid is a weak probability estimate; null = one side empty. */
86
+ wideSpread?: boolean | null;
83
87
  }
84
88
  export interface OutcomeViewUnderlying {
85
89
  /** Underlying symbol from description (e.g. "BTC") */
@@ -18,6 +18,12 @@ import type { SpotAdapter, SpotMarketInfo, SpotBalance, SpotOrderResult, SpotOpe
18
18
  import type { ResolvedSlippage } from "./slippage.js";
19
19
  import { type PacificaAdapter } from "./pacifica.js";
20
20
  import type { IocOutcome } from "./ioc-outcome.js";
21
+ /**
22
+ * Spot balances from Pacifica GET /account (USDC = the shared perp collateral, then
23
+ * `spot_balances`) — the one mapper behind the keyed spot adapter and the keyless
24
+ * `account balance --address`.
25
+ */
26
+ export declare function pacificaSpotBalanceRows(info: Record<string, unknown>): SpotBalance[];
21
27
  export declare class PacificaSpotAdapter implements SpotAdapter, SpotOpenOrdersCapable, SpotCancelAllCapable {
22
28
  readonly name = "pacifica";
23
29
  private _perp;
@@ -36,6 +36,28 @@ function decimalsOf(step) {
36
36
  const frac = step.split(".")[1];
37
37
  return frac ? frac.replace(/0+$/, "").length : 0;
38
38
  }
39
+ /**
40
+ * Spot balances from Pacifica GET /account (USDC = the shared perp collateral, then
41
+ * `spot_balances`) — the one mapper behind the keyed spot adapter and the keyless
42
+ * `account balance --address`.
43
+ */
44
+ export function pacificaSpotBalanceRows(info) {
45
+ const rows = info.spot_balances;
46
+ if (!Array.isArray(rows)) {
47
+ throw new PerpError("INVALID_PARAMS", "Pacifica /account response has no spot_balances array", { exchange: "pacifica" });
48
+ }
49
+ const out = [];
50
+ // USDC = the shared perp collateral (same money as perp equity).
51
+ const total = String(info.balance);
52
+ const available = String(info.available_to_spend);
53
+ out.push({ token: QUOTE, total, available, held: subDecimal(total, available), shared: true });
54
+ for (const r of rows) {
55
+ const t = String(r.amount);
56
+ const a = String(r.available_to_withdraw);
57
+ out.push({ token: String(r.symbol), total: t, available: a, held: subDecimal(t, a), shared: false });
58
+ }
59
+ return out;
60
+ }
39
61
  export class PacificaSpotAdapter {
40
62
  name = "pacifica";
41
63
  _perp;
@@ -105,21 +127,7 @@ export class PacificaSpotAdapter {
105
127
  }
106
128
  async getSpotBalances() {
107
129
  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;
130
+ return pacificaSpotBalanceRows(info);
123
131
  }
124
132
  /** Mark price for min-notional / slippage checks. */
125
133
  async _mark(wire) {
@@ -53,6 +53,15 @@ export declare function findVault(vaults: PacificaVault[], ref: string): Pacific
53
53
  /** UNOFFICIAL endpoint (not in the API docs; used by app.pacifica.fi). Advisory only — the venue's own refusal is final. */
54
54
  export declare const DEPOSIT_WHITELIST_API_STATUS: "unofficial";
55
55
  export declare function fetchDepositWhitelist(client: Reader, vault: string): Promise<string[]>;
56
+ /**
57
+ * The advisory whitelist read for display: `addresses` null = unreadable, with the error CODE that
58
+ * made it so (never a bare null that looks like "no whitelist data"). The venue's refusal of a
59
+ * deposit stays final either way.
60
+ */
61
+ export declare function readDepositWhitelist(client: Reader, vault: string): Promise<{
62
+ addresses: string[] | null;
63
+ error: string | null;
64
+ }>;
56
65
  export interface WithdrawWindow {
57
66
  /** No window configured: always open (subject to the other rules). */
58
67
  restricted: boolean;
@@ -23,7 +23,7 @@
23
23
  * Live 2026-10-19: absent config fields are omitted (→ null here), and `user_share` was null for a
24
24
  * vault's own manager (it counts LP shares; the manager's are `manager_shares`).
25
25
  */
26
- import { PerpError } from "../errors.js";
26
+ import { PerpError, classifyError } from "../errors.js";
27
27
  import { mulDecimal, subDecimal } from "./precision.js";
28
28
  import { cmpDecimal, divDecimal, isPlainDecimal, trimDecimal } from "./pacifica-decimal.js";
29
29
  import { envelopeData, requireUsdcAmount, PLAIN_POSITIVE } from "./pacifica-print.js";
@@ -142,6 +142,19 @@ export async function fetchDepositWhitelist(client, vault) {
142
142
  throw bad("/lake/get_deposit_whitelist without an addresses list", data);
143
143
  return a;
144
144
  }
145
+ /**
146
+ * The advisory whitelist read for display: `addresses` null = unreadable, with the error CODE that
147
+ * made it so (never a bare null that looks like "no whitelist data"). The venue's refusal of a
148
+ * deposit stays final either way.
149
+ */
150
+ export async function readDepositWhitelist(client, vault) {
151
+ try {
152
+ return { addresses: await fetchDepositWhitelist(client, vault), error: null };
153
+ }
154
+ catch (e) {
155
+ return { addresses: null, error: classifyError(e, "pacifica").code };
156
+ }
157
+ }
145
158
  /** docs: open while current_unix_time_s mod withdraw_window_s < withdraw_duration_s (epoch-anchored). */
146
159
  export function withdrawWindowAt(c, nowMs) {
147
160
  if (c.withdrawWindowS === null || c.withdrawDurationS === null || c.withdrawWindowS <= 0)