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.
- package/README.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +129 -535
- package/README.zh.md +243 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +2 -2
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/funding-arb-strategy.js +10 -3
- package/dist/bot/strategies/funding-arb-utils.d.ts +14 -2
- package/dist/bot/strategies/funding-arb-utils.js +48 -15
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +4 -2
- package/dist/bot/strategies/spot-perp-executor.js +2 -2
- package/dist/cli-program.js +1 -1
- package/dist/commands/account.js +168 -24
- package/dist/commands/arb/index.js +19 -77
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +155 -81
- package/dist/commands/arb.d.ts +8 -0
- package/dist/commands/arb.js +49 -23
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +241 -78
- package/dist/commands/options.d.ts +5 -0
- package/dist/commands/options.js +20 -3
- package/dist/commands/pacifica-earn.js +8 -21
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +51 -8
- package/dist/commands/trade.js +4 -4
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +2 -2
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +10 -0
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +10 -6
- package/dist/exchanges/arcus/adapter.js +33 -6
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/aster.d.ts +27 -7
- package/dist/exchanges/aster.js +124 -42
- package/dist/exchanges/canonical-symbols.d.ts +7 -0
- package/dist/exchanges/canonical-symbols.js +20 -0
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/options.d.ts +5 -0
- package/dist/exchanges/derive/options.js +20 -1
- package/dist/exchanges/extended/adapter.d.ts +16 -2
- package/dist/exchanges/extended/adapter.js +96 -34
- package/dist/exchanges/extended/public.d.ts +3 -0
- package/dist/exchanges/extended/public.js +9 -2
- package/dist/exchanges/grvt/adapter.d.ts +16 -2
- package/dist/exchanges/grvt/adapter.js +98 -21
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +8 -0
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +180 -13
- package/dist/exchanges/hyperliquid.js +487 -185
- package/dist/exchanges/interface.d.ts +64 -8
- package/dist/exchanges/interface.js +31 -0
- package/dist/exchanges/lighter-public.d.ts +66 -11
- package/dist/exchanges/lighter-public.js +249 -81
- package/dist/exchanges/lighter-spot.d.ts +7 -1
- package/dist/exchanges/lighter-spot.js +99 -90
- package/dist/exchanges/lighter.d.ts +70 -11
- package/dist/exchanges/lighter.js +285 -92
- package/dist/exchanges/ondo/adapter.d.ts +34 -8
- package/dist/exchanges/ondo/adapter.js +73 -16
- package/dist/exchanges/ondo/public.d.ts +27 -2
- package/dist/exchanges/ondo/public.js +63 -31
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-spot.d.ts +6 -0
- package/dist/exchanges/pacifica-spot.js +23 -15
- package/dist/exchanges/pacifica-vaults.d.ts +9 -0
- package/dist/exchanges/pacifica-vaults.js +14 -1
- package/dist/exchanges/pacifica.d.ts +77 -6
- package/dist/exchanges/pacifica.js +321 -130
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +52 -9
- package/dist/exchanges/qfex/adapter.js +74 -9
- package/dist/exchanges/qfex/public.d.ts +62 -9
- package/dist/exchanges/qfex/public.js +128 -22
- package/dist/exchanges/qfex/ws-transport.d.ts +1 -1
- package/dist/exchanges/risex/adapter.d.ts +19 -7
- package/dist/exchanges/risex/adapter.js +58 -12
- package/dist/exchanges/spot-factory.js +1 -1
- package/dist/exchanges/txflow/adapter.d.ts +14 -7
- package/dist/exchanges/txflow/adapter.js +51 -19
- package/dist/exchanges/txflow/client.js +3 -1
- package/dist/exchanges/txflow/public.d.ts +12 -1
- package/dist/exchanges/txflow/public.js +82 -11
- package/dist/exchanges/unknown-symbol.d.ts +6 -1
- package/dist/exchanges/unknown-symbol.js +7 -2
- package/dist/exchanges/variational/adapter.d.ts +2 -6
- package/dist/exchanges/venue-types.d.ts +46 -3
- package/dist/exchanges/venue-types.js +80 -9
- package/dist/exchanges/venue.d.ts +99 -3
- package/dist/exchanges/venue.js +72 -0
- package/dist/exchanges/venues/hyperliquid.js +9 -0
- package/dist/exchanges/venues/lighter-rh.js +13 -3
- package/dist/exchanges/venues/lighter.js +13 -3
- package/dist/exchanges/venues/pacifica.js +9 -0
- package/dist/exchanges/venues/risex.js +15 -0
- package/dist/funding/legacy-public.d.ts +5 -3
- package/dist/funding/legacy-public.js +33 -37
- package/dist/funding/rates.d.ts +54 -5
- package/dist/funding/rates.js +49 -13
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.js +11 -5
- package/dist/mcp-server.js +104 -15
- package/dist/pacifica/client.d.ts +2 -1
- package/dist/pacifica/client.js +11 -0
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/strategies/funding-arb.js +35 -62
- package/dist/utils.d.ts +11 -1
- package/dist/venue-health.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +30 -9
- package/dist/ws/index.js +180 -15
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +30 -9
- package/dist/ws/node.js +204 -16
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/package.json +15 -2
- package/skills/perp-cli/SKILL.md +97 -33
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +15 -12
- package/skills/perp-cli/references/commands.md +67 -12
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +55 -24
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- 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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
|
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(
|
|
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:
|
|
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
|
-
/**
|
|
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
|
|
692
|
-
resolution
|
|
693
|
-
from: Math.floor(
|
|
747
|
+
market,
|
|
748
|
+
resolution,
|
|
749
|
+
from: Math.floor(from / 1000),
|
|
694
750
|
to: Math.floor(endTime / 1000),
|
|
695
751
|
});
|
|
696
|
-
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
60
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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)
|