@pear-protocol/market-sdk 0.0.14 → 0.2.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 (54) hide show
  1. package/README.md +27 -0
  2. package/dist/chart/cache/index.d.ts +1 -1
  3. package/dist/chart/cache/index.js +1 -1
  4. package/dist/chart/chart.d.ts +1 -1
  5. package/dist/chart/collector/binance.d.ts +1 -1
  6. package/dist/chart/collector/bybit.d.ts +1 -1
  7. package/dist/chart/collector/helpers.d.ts +2 -2
  8. package/dist/chart/collector/helpers.js +2 -2
  9. package/dist/chart/collector/hyperliquid.d.ts +1 -1
  10. package/dist/chart/collector/index.d.ts +1 -1
  11. package/dist/chart/collector/index.js +2 -1
  12. package/dist/chart/collector/lighter.d.ts +2 -2
  13. package/dist/chart/collector/lighter.js +27 -3
  14. package/dist/chart/collector/okx.d.ts +1 -1
  15. package/dist/chart/compute/asset.d.ts +1 -1
  16. package/dist/chart/compute/index.d.ts +1 -1
  17. package/dist/chart/compute/performance.d.ts +1 -1
  18. package/dist/chart/compute/price-ratio.d.ts +1 -1
  19. package/dist/chart/compute/weighted-ratio.d.ts +1 -1
  20. package/dist/chart/types.d.ts +1 -1
  21. package/dist/chart/utils.d.ts +1 -1
  22. package/dist/chart/weekly.d.ts +20 -0
  23. package/dist/chart/weekly.js +46 -0
  24. package/dist/chart/ws/lighter.d.ts +19 -1
  25. package/dist/chart/ws/lighter.js +64 -3
  26. package/dist/funding-rate/fetcher/binance.d.ts +1 -1
  27. package/dist/funding-rate/fetcher/bybit.d.ts +1 -1
  28. package/dist/funding-rate/fetcher/http-client.d.ts +1 -1
  29. package/dist/funding-rate/fetcher/http-client.js +4 -2
  30. package/dist/funding-rate/fetcher/hyperliquid.d.ts +1 -1
  31. package/dist/funding-rate/fetcher/index.d.ts +2 -2
  32. package/dist/funding-rate/fetcher/index.js +2 -2
  33. package/dist/funding-rate/fetcher/lighter.d.ts +2 -2
  34. package/dist/funding-rate/fetcher/lighter.js +4 -2
  35. package/dist/funding-rate/fetcher/okx.d.ts +1 -1
  36. package/dist/funding-rate/funding-rate.d.ts +1 -1
  37. package/dist/funding-rate/funding-rate.js +2 -1
  38. package/dist/funding-rate/types.d.ts +1 -1
  39. package/dist/funding-rate/utils.d.ts +1 -1
  40. package/dist/index.d.ts +2 -2
  41. package/dist/orderbook/book/aggregate.d.ts +1 -1
  42. package/dist/orderbook/book/local-book.d.ts +1 -1
  43. package/dist/orderbook/orderbook.d.ts +1 -1
  44. package/dist/orderbook/types.d.ts +1 -1
  45. package/dist/orderbook/utils.d.ts +1 -1
  46. package/dist/orderbook/ws/lighter.js +9 -1
  47. package/dist/shared/types.d.ts +6 -1
  48. package/dist/transport/base-transport.d.ts +4 -0
  49. package/dist/transport/base-transport.js +8 -0
  50. package/dist/transport/index.d.ts +11 -3
  51. package/dist/transport/index.js +2 -2
  52. package/dist/transport/lighter.d.ts +31 -1
  53. package/dist/transport/lighter.js +78 -0
  54. package/package.json +5 -3
package/README.md CHANGED
@@ -70,6 +70,33 @@ orderbook.destroy();
70
70
  transport.destroy();
71
71
  ```
72
72
 
73
+ ## Transport
74
+
75
+ `CreateTransport(connector)` builds the connection every feature shares. Pass it to `Chart`,
76
+ `Orderbook`, and `FundingRate`, then `destroy()` it once when you are done.
77
+
78
+ ### Lighter rate limits
79
+
80
+ Lighter grants a wider rate limit to requests it can attribute to an account, on public market data
81
+ as well as private endpoints. Supply a token provider and this SDK sends it on Lighter's candle and
82
+ funding reads (`authorization` header) and on its candle and order-book subscriptions (`auth` field):
83
+
84
+ ```typescript
85
+ import { CreateTransport } from '@pear-protocol/market-sdk';
86
+ import { retrieveLighterAuthToken } from '@pear-protocol/exchanges-sdk';
87
+
88
+ const transport = CreateTransport('lighter', {
89
+ authToken: () => retrieveLighterAuthToken(sdk, credentials, false),
90
+ });
91
+ ```
92
+
93
+ This SDK never holds Lighter credentials — the caller mints the token and owns its cache.
94
+ `retrieveLighterAuthToken` is the same cache the account trackers use, so both SDKs share one token.
95
+
96
+ Everything is optional. Omit `authToken`, or resolve `undefined` for a visitor with no connected
97
+ account, and every request goes out anonymously. A failed mint logs a warning and falls back to
98
+ anonymous rather than breaking the market data.
99
+
73
100
  ## Chart
74
101
 
75
102
  ### Initialization
@@ -1,9 +1,9 @@
1
1
  import { CandleInterval, CandleData, HistoricalRange } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare class CandleCache {
9
9
  private historicalPriceData;
@@ -81,7 +81,7 @@ class CandleCache {
81
81
  const key = createKey(symbol, interval);
82
82
  const tokenData = this.historicalPriceData[key];
83
83
  if (!tokenData) return [];
84
- return tokenData.candles.filter((candle) => candle.t >= startTime && candle.t < endTime);
84
+ return tokenData.candles.filter((candle) => candle.T >= startTime && candle.t < endTime);
85
85
  }
86
86
  getEffectiveBoundary(symbols, interval) {
87
87
  if (symbols.length === 0) return null;
@@ -1,9 +1,9 @@
1
1
  import { ChartConfig, TokenSelection, CandleInterval, ChartType, Bar, RealtimeBarCallback } from './types.js';
2
2
  import '../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../shared/types.js';
4
5
  import '../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../shared/types.js';
7
7
 
8
8
  declare class Chart {
9
9
  private collector;
@@ -1,9 +1,9 @@
1
1
  import { CandleInterval, CandleData } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
9
9
 
@@ -1,9 +1,9 @@
1
1
  import { CandleInterval, CandleData } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
9
9
 
@@ -1,10 +1,10 @@
1
1
  import { Connector } from '@pear-protocol/types';
2
2
  import { CandleInterval, CandleData } from '../types.js';
3
3
  import '../../transport/index.js';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
- declare function fetchHistoricalCandles(connector: Connector, symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
8
+ declare function fetchHistoricalCandles(connector: Connector, symbol: string, startTime: number, endTime: number, interval: CandleInterval, authToken?: string): Promise<CandleData[]>;
9
9
 
10
10
  export { fetchHistoricalCandles };
@@ -6,7 +6,7 @@ import { fetchHistoricalCandles as fetchHistoricalCandles$2 } from './okx';
6
6
 
7
7
  var __defProp = Object.defineProperty;
8
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
- async function fetchHistoricalCandles(connector, symbol, startTime, endTime, interval) {
9
+ async function fetchHistoricalCandles(connector, symbol, startTime, endTime, interval, authToken) {
10
10
  switch (connector) {
11
11
  case "hyperliquid":
12
12
  return fetchHistoricalCandles$5(symbol, startTime, endTime, interval);
@@ -17,7 +17,7 @@ async function fetchHistoricalCandles(connector, symbol, startTime, endTime, int
17
17
  case "okx":
18
18
  return fetchHistoricalCandles$2(symbol, startTime, endTime, interval);
19
19
  case "lighter":
20
- return fetchHistoricalCandles$1(symbol, startTime, endTime, interval);
20
+ return fetchHistoricalCandles$1(symbol, startTime, endTime, interval, authToken);
21
21
  default:
22
22
  throw new Error(`Unsupported connector: ${connector}`);
23
23
  }
@@ -1,9 +1,9 @@
1
1
  import { CandleInterval, CandleData } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
9
9
 
@@ -1,9 +1,9 @@
1
1
  import { ChartConfig, TokenSelection, CandleInterval, RealtimeCandleCallback, CandleData } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare class DataCollector {
9
9
  private transport;
@@ -127,10 +127,11 @@ class DataCollector {
127
127
  for (const token of tokensToFetch) {
128
128
  this.cache.addLoadingToken(token.symbol);
129
129
  }
130
+ const authToken = await this.transport.retrieveAuthToken();
130
131
  try {
131
132
  const fetchPromises = tokensToFetch.map(async (token) => {
132
133
  try {
133
- const candles = await fetchHistoricalCandles(this.transport.connector, token.symbol, startTime, endTime, interval);
134
+ const candles = await fetchHistoricalCandles(this.transport.connector, token.symbol, startTime, endTime, interval, authToken);
134
135
  this.cache.addHistoricalPriceData(token.symbol, interval, candles, {
135
136
  start: startTime,
136
137
  end: endTime
@@ -1,10 +1,10 @@
1
1
  import { CandleInterval, CandleData } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
- declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
8
+ declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval, authToken?: string): Promise<CandleData[]>;
9
9
 
10
10
  export { fetchHistoricalCandles };
@@ -1,9 +1,11 @@
1
1
  import { getIntervalSeconds } from '../utils';
2
+ import { getWeekStart, aggregateDailyCandlesToWeekly } from '../weekly';
2
3
 
3
4
  var __defProp = Object.defineProperty;
4
5
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
6
  const BASE_URL = "https://mainnet.zklighter.elliot.ai";
6
7
  const MAX_LIMIT = 500;
8
+ const DAY_MS = getIntervalSeconds("1d") * 1e3;
7
9
  const RESOLUTION_MAP = {
8
10
  "1m": "1m",
9
11
  "5m": "5m",
@@ -14,7 +16,25 @@ const RESOLUTION_MAP = {
14
16
  "12h": "12h",
15
17
  "1d": "1d"
16
18
  };
17
- async function fetchHistoricalCandles(symbol, startTime, endTime, interval) {
19
+ async function fetchHistoricalCandles(symbol, startTime, endTime, interval, authToken) {
20
+ if (interval === "1w") {
21
+ const dailyCandles = await fetchDailyCandlesChunked(symbol, getWeekStart(startTime), endTime, authToken);
22
+ return aggregateDailyCandlesToWeekly(dailyCandles);
23
+ }
24
+ return fetchResolutionCandles(symbol, startTime, endTime, interval, authToken);
25
+ }
26
+ __name(fetchHistoricalCandles, "fetchHistoricalCandles");
27
+ async function fetchDailyCandlesChunked(symbol, startTime, endTime, authToken) {
28
+ const chunkMs = MAX_LIMIT * DAY_MS;
29
+ const chunkStarts = [];
30
+ for (let chunkStart = startTime; chunkStart < endTime; chunkStart += chunkMs) {
31
+ chunkStarts.push(chunkStart);
32
+ }
33
+ const chunks = await Promise.all(chunkStarts.map((chunkStart) => fetchResolutionCandles(symbol, chunkStart, Math.min(chunkStart + chunkMs, endTime), "1d", authToken)));
34
+ return chunks.flat();
35
+ }
36
+ __name(fetchDailyCandlesChunked, "fetchDailyCandlesChunked");
37
+ async function fetchResolutionCandles(symbol, startTime, endTime, interval, authToken) {
18
38
  const resolution = RESOLUTION_MAP[interval];
19
39
  if (!resolution) throw new Error(`Lighter does not support the '${interval}' interval`);
20
40
  const intervalMs = getIntervalSeconds(interval) * 1e3;
@@ -26,7 +46,11 @@ async function fetchHistoricalCandles(symbol, startTime, endTime, interval) {
26
46
  end_timestamp: String(endTime),
27
47
  count_back: String(countBack)
28
48
  });
29
- const response = await fetch(`${BASE_URL}/api/v1/candles?${params}`);
49
+ const response = await fetch(`${BASE_URL}/api/v1/candles?${params}`, authToken ? {
50
+ headers: {
51
+ authorization: authToken
52
+ }
53
+ } : void 0);
30
54
  if (!response.ok) throw new Error(`Lighter API error: ${response.status} ${response.statusText}`);
31
55
  const data = await response.json();
32
56
  if (data.code !== 200) throw new Error(`Lighter API error: ${data.message ?? String(data.code)}`);
@@ -39,6 +63,6 @@ async function fetchHistoricalCandles(symbol, startTime, endTime, interval) {
39
63
  c: candle.c ?? 0
40
64
  }));
41
65
  }
42
- __name(fetchHistoricalCandles, "fetchHistoricalCandles");
66
+ __name(fetchResolutionCandles, "fetchResolutionCandles");
43
67
 
44
68
  export { fetchHistoricalCandles };
@@ -1,9 +1,9 @@
1
1
  import { CandleInterval, CandleData } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalCandles(instId: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
9
9
 
@@ -1,9 +1,9 @@
1
1
  import { CandleData, Bar } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function computeAssetBars(tokenCandles: Record<string, CandleData[]>, symbol: string): Bar[];
9
9
  declare function computeRealtimeAssetBar(snapshot: Record<string, CandleData>, symbol: string): Bar | null;
@@ -5,6 +5,6 @@ export { computeRealtimeWeightedRatioBar, computeWeightedRatioCandles } from './
5
5
  import '../types.js';
6
6
  import '../../transport/index.js';
7
7
  import '@pear-protocol/types';
8
+ import '../../shared/types.js';
8
9
  import '../../transport/base-transport.js';
9
10
  import 'partysocket';
10
- import '../../shared/types.js';
@@ -1,9 +1,9 @@
1
1
  import { TokenSelection, CandleData, PerformanceResult, Bar } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function computePerformanceCandles(longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>): PerformanceResult;
9
9
  declare function computeRealtimePerformanceBar(longTokens: TokenSelection[], shortTokens: TokenSelection[], snapshot: Record<string, CandleData>, baselinePrices: Record<string, number>): Bar | null;
@@ -1,9 +1,9 @@
1
1
  import { TokenSelection, CandleData, Bar } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function computePriceRatioCandles(longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>): Bar[];
9
9
  declare function computeRealtimePriceRatioBar(longTokens: TokenSelection[], shortTokens: TokenSelection[], snapshot: Record<string, CandleData>): Bar | null;
@@ -1,9 +1,9 @@
1
1
  import { TokenSelection, CandleData, Bar } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function computeWeightedRatioCandles(longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>): Bar[];
9
9
  declare function computeRealtimeWeightedRatioBar(longTokens: TokenSelection[], shortTokens: TokenSelection[], snapshot: Record<string, CandleData>): Bar | null;
@@ -1,8 +1,8 @@
1
1
  import { Transport } from '../transport/index.js';
2
2
  import '@pear-protocol/types';
3
+ import '../shared/types.js';
3
4
  import '../transport/base-transport.js';
4
5
  import 'partysocket';
5
- import '../shared/types.js';
6
6
 
7
7
  interface TokenSelection {
8
8
  symbol: string;
@@ -1,9 +1,9 @@
1
1
  import { CandleData, CandleInterval, HistoricalRange } from './types.js';
2
2
  import '../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../shared/types.js';
4
5
  import '../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../shared/types.js';
7
7
 
8
8
  declare const createKey: (symbol: string, interval: CandleInterval) => string;
9
9
  declare const getIntervalSeconds: (interval: CandleInterval) => number;
@@ -0,0 +1,20 @@
1
+ import { CandleData } from './types.js';
2
+ import '../transport/index.js';
3
+ import '@pear-protocol/types';
4
+ import '../shared/types.js';
5
+ import '../transport/base-transport.js';
6
+ import 'partysocket';
7
+
8
+ /** Start (ms epoch, UTC) of the Monday-aligned week containing `timestamp`. */
9
+ declare function getWeekStart(timestamp: number): number;
10
+ /**
11
+ * Folds one daily candle into its week's running OHLC bar. Extends `existing` when it already
12
+ * covers the same week (open stays, high/low/close roll forward); otherwise starts a fresh bar.
13
+ * Shared by the REST batch aggregator below and LighterCandleWs's live incremental merge, so
14
+ * historical and realtime weekly candles are always built the same way.
15
+ */
16
+ declare function foldDayIntoWeek(existing: CandleData | undefined, day: CandleData): CandleData;
17
+ /** Reduces a batch of daily candles into one OHLC candle per UTC week, oldest first. */
18
+ declare function aggregateDailyCandlesToWeekly(dailyCandles: CandleData[]): CandleData[];
19
+
20
+ export { aggregateDailyCandlesToWeekly, foldDayIntoWeek, getWeekStart };
@@ -0,0 +1,46 @@
1
+ import { getIntervalSeconds } from './utils';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ const WEEK_MS = getIntervalSeconds("1w") * 1e3;
6
+ function getWeekStart(timestamp) {
7
+ const date = new Date(timestamp);
8
+ const mondayOffset = (date.getUTCDay() + 6) % 7;
9
+ const monday = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
10
+ monday.setUTCDate(monday.getUTCDate() - mondayOffset);
11
+ return monday.getTime();
12
+ }
13
+ __name(getWeekStart, "getWeekStart");
14
+ function foldDayIntoWeek(existing, day) {
15
+ const weekStart = getWeekStart(day.t);
16
+ if (existing && existing.t === weekStart) {
17
+ return {
18
+ ...existing,
19
+ h: Math.max(existing.h, day.h),
20
+ l: Math.min(existing.l, day.l),
21
+ c: day.c
22
+ };
23
+ }
24
+ return {
25
+ t: weekStart,
26
+ T: weekStart + WEEK_MS - 1,
27
+ o: day.o,
28
+ h: day.h,
29
+ l: day.l,
30
+ c: day.c
31
+ };
32
+ }
33
+ __name(foldDayIntoWeek, "foldDayIntoWeek");
34
+ function aggregateDailyCandlesToWeekly(dailyCandles) {
35
+ const weeklyByStart = /* @__PURE__ */ new Map();
36
+ for (const day of [
37
+ ...dailyCandles
38
+ ].sort((a, b) => a.t - b.t)) {
39
+ const folded = foldDayIntoWeek(weeklyByStart.get(getWeekStart(day.t)), day);
40
+ weeklyByStart.set(folded.t, folded);
41
+ }
42
+ return Array.from(weeklyByStart.values()).sort((a, b) => a.t - b.t);
43
+ }
44
+ __name(aggregateDailyCandlesToWeekly, "aggregateDailyCandlesToWeekly");
45
+
46
+ export { aggregateDailyCandlesToWeekly, foldDayIntoWeek, getWeekStart };
@@ -1,5 +1,5 @@
1
1
  import { WsMessage } from '../../shared/types.js';
2
- import { CandleInterval } from '../types.js';
2
+ import { CandleInterval, CandleData } from '../types.js';
3
3
  import { BaseCandleWs, ParsedCandle } from './base-candle.js';
4
4
  import '../../transport/index.js';
5
5
  import '@pear-protocol/types';
@@ -11,13 +11,29 @@ import 'partysocket';
11
11
  * them onto CandleData — it never aggregates the trade tape and never seeds itself over REST, because
12
12
  * the subscribe acknowledgement already carries the current live candle.
13
13
  * Docs: https://apidocs.lighter.xyz/docs/websocket-reference#candlesticks
14
+ *
15
+ * Exception: Lighter has no weekly channel. A '1w' subscription rides the native daily channel
16
+ * instead and this class folds each incoming daily candle into a running weekly bar client-side
17
+ * (see foldDayIntoWeek in chart/weekly.ts) — still no REST call of its own; the weekly seed comes
18
+ * from onHistoricalDataFetched, fed by DataCollector's already-synthesized weekly history.
14
19
  */
15
20
 
16
21
  declare class LighterCandleWs extends BaseCandleWs {
22
+ private weeklyCandles;
23
+ private weeklyTrackedSymbols;
17
24
  /** Subscribes to a market's candle stream; `symbol` is Lighter's numeric market id. */
18
25
  protected buildSubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
19
26
  /** Mirrors the subscribe channel — Lighter keys unsubscribes by the same slash-delimited name. */
20
27
  protected buildUnsubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
28
+ /**
29
+ * Seeds the running weekly bar from the REST-aggregated weekly history, so the first live
30
+ * daily tick extends the correct week's open/high/low instead of starting a bar from today.
31
+ *
32
+ * DataCollector calls this for every historical fetch, including a backfill for an older
33
+ * range while a live subscription is already running (e.g. scrolling a chart back in time) —
34
+ * so a batch's last candle is not necessarily the current live week.
35
+ */
36
+ onHistoricalDataFetched(symbol: string, interval: CandleInterval, candles: CandleData[]): void;
21
37
  /**
22
38
  * Translates one candle message into CandleData, oldest first.
23
39
  *
@@ -25,6 +41,8 @@ declare class LighterCandleWs extends BaseCandleWs {
25
41
  * followed by the newly opened one — both are emitted so the closed bar keeps its final trades.
26
42
  */
27
43
  protected parseMessages(data: string): ParsedCandle[];
44
+ /** Folds one live daily candle into the symbol's running weekly bar and records the result. */
45
+ private mergeIntoWeeklyCandle;
28
46
  }
29
47
 
30
48
  export { LighterCandleWs };
@@ -1,4 +1,5 @@
1
1
  import { getIntervalSeconds } from '../utils';
2
+ import { foldDayIntoWeek } from '../weekly';
2
3
  import { BaseCandleWs } from './base-candle';
3
4
 
4
5
  var __defProp = Object.defineProperty;
@@ -17,21 +18,74 @@ class LighterCandleWs extends BaseCandleWs {
17
18
  static {
18
19
  __name(this, "LighterCandleWs");
19
20
  }
21
+ // Per-symbol running weekly bar, kept only while that symbol is subscribed at the synthesized '1w'.
22
+ weeklyCandles = /* @__PURE__ */ new Map();
23
+ weeklyTrackedSymbols = /* @__PURE__ */ new Set();
20
24
  /** Subscribes to a market's candle stream; `symbol` is Lighter's numeric market id. */
21
25
  buildSubscribeMessage(symbol, interval) {
26
+ const resolution = interval === "1w" ? "1d" : interval;
27
+ if (interval === "1w") {
28
+ this.weeklyTrackedSymbols.add(symbol);
29
+ }
30
+ const authToken = this.transport.authToken;
31
+ if (!authToken) {
32
+ return {
33
+ type: "subscribe",
34
+ channel: `candle/${symbol}/${resolution}`
35
+ };
36
+ }
22
37
  return {
23
38
  type: "subscribe",
24
- channel: `candle/${symbol}/${interval}`
39
+ channel: `candle/${symbol}/${resolution}`,
40
+ auth: authToken
25
41
  };
26
42
  }
27
43
  /** Mirrors the subscribe channel — Lighter keys unsubscribes by the same slash-delimited name. */
28
44
  buildUnsubscribeMessage(symbol, interval) {
45
+ const resolution = interval === "1w" ? "1d" : interval;
46
+ if (interval === "1w") {
47
+ this.weeklyTrackedSymbols.delete(symbol);
48
+ this.weeklyCandles.delete(symbol);
49
+ }
29
50
  return {
30
51
  type: "unsubscribe",
31
- channel: `candle/${symbol}/${interval}`
52
+ channel: `candle/${symbol}/${resolution}`
32
53
  };
33
54
  }
34
55
  /**
56
+ * Seeds the running weekly bar from the REST-aggregated weekly history, so the first live
57
+ * daily tick extends the correct week's open/high/low instead of starting a bar from today.
58
+ *
59
+ * DataCollector calls this for every historical fetch, including a backfill for an older
60
+ * range while a live subscription is already running (e.g. scrolling a chart back in time) —
61
+ * so a batch's last candle is not necessarily the current live week.
62
+ */
63
+ onHistoricalDataFetched(symbol, interval, candles) {
64
+ if (interval !== "1w") {
65
+ return;
66
+ }
67
+ const latest = candles[candles.length - 1];
68
+ if (!latest) {
69
+ return;
70
+ }
71
+ const tracked = this.weeklyCandles.get(symbol);
72
+ if (tracked && latest.t < tracked.t) {
73
+ return;
74
+ }
75
+ if (tracked && latest.t === tracked.t) {
76
+ this.weeklyCandles.set(symbol, {
77
+ t: tracked.t,
78
+ T: tracked.T,
79
+ o: tracked.o,
80
+ h: Math.max(tracked.h, latest.h),
81
+ l: Math.min(tracked.l, latest.l),
82
+ c: tracked.c
83
+ });
84
+ return;
85
+ }
86
+ this.weeklyCandles.set(symbol, latest);
87
+ }
88
+ /**
35
89
  * Translates one candle message into CandleData, oldest first.
36
90
  *
37
91
  * Lighter batches updates every 500ms and, when a period rolls over, sends the just-closed candle
@@ -55,11 +109,18 @@ class LighterCandleWs extends BaseCandleWs {
55
109
  return [];
56
110
  }
57
111
  const intervalMs = getIntervalSeconds(route.interval) * 1e3;
112
+ const isWeeklyTracked = route.interval === "1d" && this.weeklyTrackedSymbols.has(route.symbol);
58
113
  return message.candles.map((candle) => ({
59
114
  symbol: route.symbol,
60
- candle: toCandleData(candle, intervalMs)
115
+ candle: isWeeklyTracked ? this.mergeIntoWeeklyCandle(route.symbol, toCandleData(candle, intervalMs)) : toCandleData(candle, intervalMs)
61
116
  }));
62
117
  }
118
+ /** Folds one live daily candle into the symbol's running weekly bar and records the result. */
119
+ mergeIntoWeeklyCandle(symbol, day) {
120
+ const weekly = foldDayIntoWeek(this.weeklyCandles.get(symbol), day);
121
+ this.weeklyCandles.set(symbol, weekly);
122
+ return weekly;
123
+ }
63
124
  }
64
125
  function parseChannel(channel) {
65
126
  if (!channel) {
@@ -1,9 +1,9 @@
1
1
  import { FundingRateEntry } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalFundingRates(symbol: string, startTime: number, endTime: number): Promise<FundingRateEntry[]>;
9
9
 
@@ -1,9 +1,9 @@
1
1
  import { FundingRateEntry } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalFundingRates(symbol: string, startTime: number, endTime: number): Promise<FundingRateEntry[]>;
9
9
 
@@ -1,5 +1,5 @@
1
1
  type ResponseValidator<T> = (data: T) => string | undefined;
2
- declare function httpGet<T>(url: string, params: Record<string, string>, validate?: ResponseValidator<T>): Promise<T>;
2
+ declare function httpGet<T>(url: string, params: Record<string, string>, validate?: ResponseValidator<T>, headers?: Record<string, string>): Promise<T>;
3
3
  declare function httpPost<T>(url: string, body: unknown, validate?: ResponseValidator<T>): Promise<T>;
4
4
 
5
5
  export { type ResponseValidator, httpGet, httpPost };
@@ -2,9 +2,11 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
  const BASE_DELAY_MS = 1e3;
4
4
  const MAX_DELAY_MS = 6e4;
5
- async function httpGet(url, params, validate) {
5
+ async function httpGet(url, params, validate, headers) {
6
6
  const qs = new URLSearchParams(params).toString();
7
- return httpFetch(`${url}?${qs}`, void 0, validate);
7
+ return httpFetch(`${url}?${qs}`, headers ? {
8
+ headers
9
+ } : void 0, validate);
8
10
  }
9
11
  __name(httpGet, "httpGet");
10
12
  async function httpPost(url, body, validate) {
@@ -1,9 +1,9 @@
1
1
  import { FundingRateEntry } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalFundingRates(symbol: string, startTime: number, endTime: number): Promise<FundingRateEntry[]>;
9
9
 
@@ -1,10 +1,10 @@
1
1
  import { Connector } from '@pear-protocol/types';
2
2
  import { FundingRateDuration, FundingRateEntry } from '../types.js';
3
3
  import '../../transport/index.js';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
- declare function fetchHistoricalFundingRates(connector: Connector, symbol: string, startTime: number, endTime: number, duration: FundingRateDuration): Promise<FundingRateEntry[]>;
8
+ declare function fetchHistoricalFundingRates(connector: Connector, symbol: string, startTime: number, endTime: number, duration: FundingRateDuration, authToken?: string): Promise<FundingRateEntry[]>;
9
9
 
10
10
  export { fetchHistoricalFundingRates };
@@ -6,7 +6,7 @@ import { fetchHistoricalFundingRates as fetchHistoricalFundingRates$2 } from './
6
6
 
7
7
  var __defProp = Object.defineProperty;
8
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
- async function fetchHistoricalFundingRates(connector, symbol, startTime, endTime, duration) {
9
+ async function fetchHistoricalFundingRates(connector, symbol, startTime, endTime, duration, authToken) {
10
10
  switch (connector) {
11
11
  case "hyperliquid":
12
12
  return fetchHistoricalFundingRates$5(symbol, startTime, endTime);
@@ -17,7 +17,7 @@ async function fetchHistoricalFundingRates(connector, symbol, startTime, endTime
17
17
  case "okx":
18
18
  return fetchHistoricalFundingRates$2(symbol, startTime, endTime);
19
19
  case "lighter":
20
- return fetchHistoricalFundingRates$1(symbol, startTime, endTime, duration);
20
+ return fetchHistoricalFundingRates$1(symbol, startTime, endTime, duration, authToken);
21
21
  default:
22
22
  throw new Error(`Unsupported connector: ${connector}`);
23
23
  }
@@ -1,10 +1,10 @@
1
1
  import { FundingRateDuration, FundingRateEntry } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
- declare function fetchHistoricalFundingRates(symbol: string, startTime: number, endTime: number, _duration: FundingRateDuration): Promise<FundingRateEntry[]>;
8
+ declare function fetchHistoricalFundingRates(symbol: string, startTime: number, endTime: number, _duration: FundingRateDuration, authToken?: string): Promise<FundingRateEntry[]>;
9
9
 
10
10
  export { fetchHistoricalFundingRates };
@@ -5,7 +5,7 @@ var __defProp = Object.defineProperty;
5
5
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
6
  const BASE_URL = "https://mainnet.zklighter.elliot.ai";
7
7
  const MAX_LIMIT = 500;
8
- async function fetchHistoricalFundingRates(symbol, startTime, endTime, _duration) {
8
+ async function fetchHistoricalFundingRates(symbol, startTime, endTime, _duration, authToken) {
9
9
  const intervalMs = 60 * 60 * 1e3;
10
10
  const allRates = [];
11
11
  let cursor = startTime;
@@ -17,7 +17,9 @@ async function fetchHistoricalFundingRates(symbol, startTime, endTime, _duration
17
17
  start_timestamp: String(cursor),
18
18
  end_timestamp: String(endTime),
19
19
  count_back: String(countBack)
20
- });
20
+ }, void 0, authToken ? {
21
+ authorization: authToken
22
+ } : void 0);
21
23
  const entries = data.fundings ?? [];
22
24
  if (entries.length === 0) break;
23
25
  for (const entry of entries) {
@@ -1,9 +1,9 @@
1
1
  import { FundingRateEntry } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  declare function fetchHistoricalFundingRates(instId: string, startTime: number, endTime: number): Promise<FundingRateEntry[]>;
9
9
 
@@ -1,9 +1,9 @@
1
1
  import { FundingRateConfig, TokenSelection, FundingRateAggregation, FundingRateDuration, FundingRateEntry } from './types.js';
2
2
  import '../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../shared/types.js';
4
5
  import '../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../shared/types.js';
7
7
 
8
8
  declare class FundingRate {
9
9
  private transport;
@@ -88,12 +88,13 @@ class FundingRate {
88
88
  }
89
89
  async refetch(tokens, start, end, duration) {
90
90
  if (tokens.length === 0) return;
91
+ const authToken = await this.transport.retrieveAuthToken();
91
92
  await Promise.all(tokens.map(async (token) => {
92
93
  const existing = this.cache[token.symbol];
93
94
  const fetchStart = existing ? Math.min(start, existing.start) : start;
94
95
  const fetchEnd = existing ? Math.max(end, existing.end) : end;
95
96
  try {
96
- const rates = await fetchHistoricalFundingRates(this.transport.connector, token.symbol, fetchStart, fetchEnd, duration);
97
+ const rates = await fetchHistoricalFundingRates(this.transport.connector, token.symbol, fetchStart, fetchEnd, duration, authToken);
97
98
  this.cache[token.symbol] = {
98
99
  entries: rates,
99
100
  start: fetchStart,
@@ -1,8 +1,8 @@
1
1
  import { Transport } from '../transport/index.js';
2
2
  import '@pear-protocol/types';
3
+ import '../shared/types.js';
3
4
  import '../transport/base-transport.js';
4
5
  import 'partysocket';
5
- import '../shared/types.js';
6
6
 
7
7
  interface TokenSelection {
8
8
  symbol: string;
@@ -1,9 +1,9 @@
1
1
  import { FundingRateEntry, FundingRateAggregation, TokenSelection, FundingRateDuration } from './types.js';
2
2
  import '../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../shared/types.js';
4
5
  import '../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../shared/types.js';
7
7
 
8
8
  declare function computeStartTime(endTime: number, duration: FundingRateDuration): number;
9
9
  declare function aggregateRates(rawRates: FundingRateEntry[], aggregation: FundingRateAggregation): FundingRateEntry[];
package/dist/index.d.ts CHANGED
@@ -5,8 +5,8 @@ export { FundingRateAggregation, FundingRateConfig, FundingRateDuration, Funding
5
5
  export { Orderbook } from './orderbook/orderbook.js';
6
6
  export { AggregationConfig, BBO, CexAggregationConfig, HyperliquidAggregationConfig, OrderbookCallback, OrderbookConfig, OrderbookLevel, OrderbookSnapshot } from './orderbook/types.js';
7
7
  export { getAvailableAggregations } from './orderbook/utils.js';
8
- export { CreateTransport, Transport } from './transport/index.js';
8
+ export { AuthTokenProvider } from './shared/types.js';
9
+ export { CreateTransport, Transport, TransportOptions } from './transport/index.js';
9
10
  export { BaseTransport } from './transport/base-transport.js';
10
11
  import '@pear-protocol/types';
11
12
  import 'partysocket';
12
- import './shared/types.js';
@@ -2,9 +2,9 @@ import { OrderbookLevel } from '../types.js';
2
2
  import { LocalBook } from './local-book.js';
3
3
  import '../../transport/index.js';
4
4
  import '@pear-protocol/types';
5
+ import '../../shared/types.js';
5
6
  import '../../transport/base-transport.js';
6
7
  import 'partysocket';
7
- import '../../shared/types.js';
8
8
 
9
9
  /**
10
10
  * Pure aggregation of a LocalBook into top-N bid/ask buckets at the given
@@ -1,9 +1,9 @@
1
1
  import { BBO } from '../types.js';
2
2
  import '../../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../../shared/types.js';
4
5
  import '../../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../../shared/types.js';
7
7
 
8
8
  type Level = readonly [price: string, size: number];
9
9
  declare class LocalBook {
@@ -1,9 +1,9 @@
1
1
  import { OrderbookConfig, OrderbookCallback, OrderbookSnapshot, BBO } from './types.js';
2
2
  import '../transport/index.js';
3
3
  import '@pear-protocol/types';
4
+ import '../shared/types.js';
4
5
  import '../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../shared/types.js';
7
7
 
8
8
  /**
9
9
  * Orderbook exposes an aggregated top-of-book view for a single symbol.
@@ -1,8 +1,8 @@
1
1
  import { Transport } from '../transport/index.js';
2
2
  import '@pear-protocol/types';
3
+ import '../shared/types.js';
3
4
  import '../transport/base-transport.js';
4
5
  import 'partysocket';
5
- import '../shared/types.js';
6
6
 
7
7
  declare const DEFAULT_DEPTH = 10;
8
8
  declare const DEFAULT_MULTIPLIERS: number[];
@@ -1,9 +1,9 @@
1
1
  import { Connector } from '@pear-protocol/types';
2
2
  import { AggregationConfig } from './types.js';
3
3
  import '../transport/index.js';
4
+ import '../shared/types.js';
4
5
  import '../transport/base-transport.js';
5
6
  import 'partysocket';
6
- import '../shared/types.js';
7
7
 
8
8
  declare function getCexAvailableAggregations(tickSize: number, midPrice: number, multipliers?: number[]): number[];
9
9
  declare function getHyperliquidAvailableAggregations(maxDecimals: number, midPrice: number, multipliers?: number[]): number[];
@@ -10,9 +10,17 @@ class LighterDepthWs extends BaseDepthWs {
10
10
  lastNonce = /* @__PURE__ */ new Map();
11
11
  resetListenerId = null;
12
12
  buildSubscribeMessage(symbol) {
13
+ const authToken = this.transport.authToken;
14
+ if (!authToken) {
15
+ return {
16
+ type: "subscribe",
17
+ channel: `order_book@tier2/${symbol}`
18
+ };
19
+ }
13
20
  return {
14
21
  type: "subscribe",
15
- channel: `order_book@tier2/${symbol}`
22
+ channel: `order_book@tier2/${symbol}`,
23
+ auth: authToken
16
24
  };
17
25
  }
18
26
  buildUnsubscribeMessage(symbol) {
@@ -2,5 +2,10 @@ interface WsMessage {
2
2
  [key: string]: unknown;
3
3
  }
4
4
  type MessageListener = (data: string) => void;
5
+ /**
6
+ * Mints the identity a venue rate-limits by. Supplied by the caller, which owns the credentials and
7
+ * the token cache; resolving `undefined` keeps the traffic anonymous.
8
+ */
9
+ type AuthTokenProvider = () => Promise<string | undefined>;
5
10
 
6
- export type { MessageListener, WsMessage };
11
+ export type { AuthTokenProvider, MessageListener, WsMessage };
@@ -15,6 +15,10 @@ declare abstract class BaseTransport {
15
15
  connect(): void;
16
16
  disconnect(): void;
17
17
  get connected(): boolean;
18
+ /** The identity already in hand, for venues that widen rate limits for a known account. */
19
+ get authToken(): string | undefined;
20
+ /** Resolves a live identity for the next request; a venue without one stays anonymous. */
21
+ retrieveAuthToken(): Promise<string | undefined>;
18
22
  send(msg: WsMessage | string): void;
19
23
  addMessageListener(listener: MessageListener): string;
20
24
  removeMessageListener(id: string): void;
@@ -55,6 +55,14 @@ class BaseTransport {
55
55
  get connected() {
56
56
  return this.ws?.readyState === WebSocket.OPEN;
57
57
  }
58
+ /** The identity already in hand, for venues that widen rate limits for a known account. */
59
+ get authToken() {
60
+ return void 0;
61
+ }
62
+ /** Resolves a live identity for the next request; a venue without one stays anonymous. */
63
+ retrieveAuthToken() {
64
+ return Promise.resolve(void 0);
65
+ }
58
66
  send(msg) {
59
67
  if (this.ws?.readyState === WebSocket.OPEN) {
60
68
  this.ws.send(typeof msg === "string" ? msg : JSON.stringify(msg));
@@ -1,10 +1,18 @@
1
1
  import { Connector } from '@pear-protocol/types';
2
+ import { AuthTokenProvider } from '../shared/types.js';
2
3
  import { BaseTransport } from './base-transport.js';
3
4
  import 'partysocket';
4
- import '../shared/types.js';
5
5
 
6
6
  type Transport = BaseTransport;
7
7
 
8
- declare function CreateTransport(connector: Connector): Transport;
8
+ interface TransportOptions {
9
+ /**
10
+ * Mints the token that attributes this transport's traffic to a connected account. Only Lighter
11
+ * reads it today, where an authenticated request earns a wider rate limit on public market data.
12
+ * Omit it — or resolve `undefined` — to stay anonymous.
13
+ */
14
+ authToken?: AuthTokenProvider;
15
+ }
16
+ declare function CreateTransport(connector: Connector, options?: TransportOptions): Transport;
9
17
 
10
- export { BaseTransport, CreateTransport, type Transport };
18
+ export { BaseTransport, CreateTransport, type Transport, type TransportOptions };
@@ -7,7 +7,7 @@ import { OkxTransport } from './okx';
7
7
 
8
8
  var __defProp = Object.defineProperty;
9
9
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
- function CreateTransport(connector) {
10
+ function CreateTransport(connector, options) {
11
11
  switch (connector) {
12
12
  case "binance":
13
13
  return new BinanceTransport();
@@ -18,7 +18,7 @@ function CreateTransport(connector) {
18
18
  case "hyperliquid":
19
19
  return new HyperliquidTransport();
20
20
  case "lighter":
21
- return new LighterTransport();
21
+ return new LighterTransport(options?.authToken);
22
22
  default: {
23
23
  const _exhaustive = connector;
24
24
  throw new Error(`Unsupported connector: ${String(_exhaustive)}`);
@@ -1,13 +1,43 @@
1
1
  import { Connector } from '@pear-protocol/types';
2
- import { WsMessage } from '../shared/types.js';
2
+ import { AuthTokenProvider, WsMessage } from '../shared/types.js';
3
3
  import { BaseTransport } from './base-transport.js';
4
4
  import 'partysocket';
5
5
 
6
+ /**
7
+ * Lighter's transport carries the connected account's identity alongside the socket, because the
8
+ * venue widens rate limits for authenticated traffic on otherwise public market data. The token is
9
+ * supplied by the caller — this package never holds Lighter credentials.
10
+ */
11
+
6
12
  declare class LighterTransport extends BaseTransport {
7
13
  readonly connector: Connector;
14
+ private readonly authTokenProvider;
15
+ private cachedAuthToken;
16
+ private authRefreshTimer;
17
+ private mintInFlight;
18
+ private wantsConnection;
19
+ /** Takes the caller's minter; without one this transport stays anonymous for its whole life. */
20
+ constructor(authTokenProvider?: AuthTokenProvider);
21
+ /** Holds the socket until a first token lands, so the opening subscriptions carry it. */
22
+ connect(): void;
23
+ /** Tears the socket down and drops the identity that belonged to it. */
24
+ disconnect(): void;
25
+ /** Exposes the cached token to the subscription frames, which are built synchronously. */
26
+ get authToken(): string | undefined;
27
+ /** Refreshes the cached token; the caller's provider owns expiry and request coalescing. */
28
+ retrieveAuthToken(): Promise<string | undefined>;
29
+ /** The venue's public market-data stream, which serves both authenticated and anonymous traffic. */
8
30
  protected getWsUrl(): string;
31
+ /** How often the socket must speak to stay open. */
9
32
  protected getPingIntervalMs(): number;
33
+ /** The keep-alive frame the venue expects. */
10
34
  protected buildPingMessage(): WsMessage;
35
+ /** Opens the socket and keeps the token fresh for as long as it stays open. */
36
+ private openSocket;
37
+ /** Re-mints the cached token on a timer, because reconnects resubscribe without warning. */
38
+ private startAuthRefresh;
39
+ /** Stops the re-minting timer, so a closed socket costs nothing. */
40
+ private stopAuthRefresh;
11
41
  }
12
42
 
13
43
  export { LighterTransport };
@@ -2,22 +2,100 @@ import { BaseTransport } from './base-transport';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
4
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ const AUTH_REFRESH_INTERVAL_MS = 6e4;
5
6
  class LighterTransport extends BaseTransport {
6
7
  static {
7
8
  __name(this, "LighterTransport");
8
9
  }
9
10
  connector = "lighter";
11
+ authTokenProvider;
12
+ cachedAuthToken;
13
+ authRefreshTimer = null;
14
+ mintInFlight = false;
15
+ wantsConnection = false;
16
+ /** Takes the caller's minter; without one this transport stays anonymous for its whole life. */
17
+ constructor(authTokenProvider) {
18
+ super();
19
+ this.authTokenProvider = authTokenProvider;
20
+ }
21
+ /** Holds the socket until a first token lands, so the opening subscriptions carry it. */
22
+ connect() {
23
+ this.wantsConnection = true;
24
+ if (!this.authTokenProvider || this.cachedAuthToken) {
25
+ this.openSocket();
26
+ return;
27
+ }
28
+ if (this.mintInFlight) {
29
+ return;
30
+ }
31
+ this.mintInFlight = true;
32
+ void this.retrieveAuthToken().then(() => {
33
+ this.mintInFlight = false;
34
+ if (!this.wantsConnection) {
35
+ this.cachedAuthToken = void 0;
36
+ return;
37
+ }
38
+ this.openSocket();
39
+ });
40
+ }
41
+ /** Tears the socket down and drops the identity that belonged to it. */
42
+ disconnect() {
43
+ this.wantsConnection = false;
44
+ this.stopAuthRefresh();
45
+ this.cachedAuthToken = void 0;
46
+ super.disconnect();
47
+ }
48
+ /** Exposes the cached token to the subscription frames, which are built synchronously. */
49
+ get authToken() {
50
+ return this.cachedAuthToken;
51
+ }
52
+ /** Refreshes the cached token; the caller's provider owns expiry and request coalescing. */
53
+ async retrieveAuthToken() {
54
+ if (!this.authTokenProvider) {
55
+ return void 0;
56
+ }
57
+ try {
58
+ this.cachedAuthToken = await this.authTokenProvider();
59
+ } catch (error) {
60
+ console.warn("Failed to mint Lighter auth token, keeping the identity already in hand", error);
61
+ }
62
+ return this.cachedAuthToken;
63
+ }
64
+ /** The venue's public market-data stream, which serves both authenticated and anonymous traffic. */
10
65
  getWsUrl() {
11
66
  return "wss://mainnet.zklighter.elliot.ai/stream";
12
67
  }
68
+ /** How often the socket must speak to stay open. */
13
69
  getPingIntervalMs() {
14
70
  return 6e4;
15
71
  }
72
+ /** The keep-alive frame the venue expects. */
16
73
  buildPingMessage() {
17
74
  return {
18
75
  type: "ping"
19
76
  };
20
77
  }
78
+ /** Opens the socket and keeps the token fresh for as long as it stays open. */
79
+ openSocket() {
80
+ super.connect();
81
+ this.startAuthRefresh();
82
+ }
83
+ /** Re-mints the cached token on a timer, because reconnects resubscribe without warning. */
84
+ startAuthRefresh() {
85
+ if (!this.authTokenProvider || this.authRefreshTimer) {
86
+ return;
87
+ }
88
+ this.authRefreshTimer = setInterval(() => {
89
+ void this.retrieveAuthToken();
90
+ }, AUTH_REFRESH_INTERVAL_MS);
91
+ }
92
+ /** Stops the re-minting timer, so a closed socket costs nothing. */
93
+ stopAuthRefresh() {
94
+ if (this.authRefreshTimer) {
95
+ clearInterval(this.authRefreshTimer);
96
+ this.authRefreshTimer = null;
97
+ }
98
+ }
21
99
  }
22
100
 
23
101
  export { LighterTransport };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/market-sdk",
3
- "version": "0.0.14",
3
+ "version": "0.2.0",
4
4
  "description": "Pear Protocol Market SDK",
5
5
  "private": false,
6
6
  "type": "module",
@@ -22,16 +22,18 @@
22
22
  "scripts": {
23
23
  "build": "tsup",
24
24
  "clean": "rm -rf dist .turbo",
25
+ "test": "vitest run",
25
26
  "typecheck": "tsc --noEmit"
26
27
  },
27
28
  "dependencies": {
28
- "@pear-protocol/types": "^1.26.0",
29
+ "@pear-protocol/types": "^1.30.0",
29
30
  "bignumber.js": "^9.1.2",
30
31
  "partysocket": "^1.0.3"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@backend/typescript-config": "workspace:*",
34
35
  "tsup": "8.5.1",
35
- "typescript": "5.9.3"
36
+ "typescript": "5.9.3",
37
+ "vitest": "4.1.2"
36
38
  }
37
39
  }