@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.
- package/README.md +27 -0
- package/dist/chart/cache/index.d.ts +1 -1
- package/dist/chart/cache/index.js +1 -1
- package/dist/chart/chart.d.ts +1 -1
- package/dist/chart/collector/binance.d.ts +1 -1
- package/dist/chart/collector/bybit.d.ts +1 -1
- package/dist/chart/collector/helpers.d.ts +2 -2
- package/dist/chart/collector/helpers.js +2 -2
- package/dist/chart/collector/hyperliquid.d.ts +1 -1
- package/dist/chart/collector/index.d.ts +1 -1
- package/dist/chart/collector/index.js +2 -1
- package/dist/chart/collector/lighter.d.ts +2 -2
- package/dist/chart/collector/lighter.js +27 -3
- package/dist/chart/collector/okx.d.ts +1 -1
- package/dist/chart/compute/asset.d.ts +1 -1
- package/dist/chart/compute/index.d.ts +1 -1
- package/dist/chart/compute/performance.d.ts +1 -1
- package/dist/chart/compute/price-ratio.d.ts +1 -1
- package/dist/chart/compute/weighted-ratio.d.ts +1 -1
- package/dist/chart/types.d.ts +1 -1
- package/dist/chart/utils.d.ts +1 -1
- package/dist/chart/weekly.d.ts +20 -0
- package/dist/chart/weekly.js +46 -0
- package/dist/chart/ws/lighter.d.ts +19 -1
- package/dist/chart/ws/lighter.js +64 -3
- package/dist/funding-rate/fetcher/binance.d.ts +1 -1
- package/dist/funding-rate/fetcher/bybit.d.ts +1 -1
- package/dist/funding-rate/fetcher/http-client.d.ts +1 -1
- package/dist/funding-rate/fetcher/http-client.js +4 -2
- package/dist/funding-rate/fetcher/hyperliquid.d.ts +1 -1
- package/dist/funding-rate/fetcher/index.d.ts +2 -2
- package/dist/funding-rate/fetcher/index.js +2 -2
- package/dist/funding-rate/fetcher/lighter.d.ts +2 -2
- package/dist/funding-rate/fetcher/lighter.js +4 -2
- package/dist/funding-rate/fetcher/okx.d.ts +1 -1
- package/dist/funding-rate/funding-rate.d.ts +1 -1
- package/dist/funding-rate/funding-rate.js +2 -1
- package/dist/funding-rate/types.d.ts +1 -1
- package/dist/funding-rate/utils.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/orderbook/book/aggregate.d.ts +1 -1
- package/dist/orderbook/book/local-book.d.ts +1 -1
- package/dist/orderbook/orderbook.d.ts +1 -1
- package/dist/orderbook/types.d.ts +1 -1
- package/dist/orderbook/utils.d.ts +1 -1
- package/dist/orderbook/ws/lighter.js +9 -1
- package/dist/shared/types.d.ts +6 -1
- package/dist/transport/base-transport.d.ts +4 -0
- package/dist/transport/base-transport.js +8 -0
- package/dist/transport/index.d.ts +11 -3
- package/dist/transport/index.js +2 -2
- package/dist/transport/lighter.d.ts +31 -1
- package/dist/transport/lighter.js +78 -0
- 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.
|
|
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;
|
package/dist/chart/chart.d.ts
CHANGED
|
@@ -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(
|
|
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;
|
package/dist/chart/types.d.ts
CHANGED
package/dist/chart/utils.d.ts
CHANGED
|
@@ -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 };
|
package/dist/chart/ws/lighter.js
CHANGED
|
@@ -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}/${
|
|
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}/${
|
|
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}`,
|
|
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,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 {
|
|
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) {
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/transport/index.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
}
|