tardis-dev 16.0.0 → 16.1.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/dist/consts.d.ts +2 -1
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +5 -2
- package/dist/consts.js.map +1 -1
- package/dist/mappers/bybit.d.ts +2 -2
- package/dist/mappers/bybitspot.d.ts +1 -1
- package/dist/mappers/cryptocom.d.ts +1 -1
- package/dist/mappers/huobi.d.ts +3 -3
- package/dist/mappers/index.d.ts +6 -0
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +9 -3
- package/dist/mappers/index.js.map +1 -1
- package/dist/mappers/lighter.d.ts +159 -0
- package/dist/mappers/lighter.d.ts.map +1 -0
- package/dist/mappers/lighter.js +173 -0
- package/dist/mappers/lighter.js.map +1 -0
- package/dist/realtimefeeds/index.d.ts.map +1 -1
- package/dist/realtimefeeds/index.js +3 -1
- package/dist/realtimefeeds/index.js.map +1 -1
- package/dist/realtimefeeds/lighter.d.ts +8 -0
- package/dist/realtimefeeds/lighter.d.ts.map +1 -0
- package/dist/realtimefeeds/lighter.js +25 -0
- package/dist/realtimefeeds/lighter.js.map +1 -0
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/consts.ts +6 -2
- package/src/mappers/index.ts +15 -3
- package/src/mappers/lighter.ts +348 -0
- package/src/realtimefeeds/index.ts +3 -1
- package/src/realtimefeeds/lighter.ts +31 -0
- package/src/worker.ts +1 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { asNumberIfValid } from '../handy.ts'
|
|
2
|
+
import { BookChange, BookTicker, DerivativeTicker, Liquidation, Trade } from '../types.ts'
|
|
3
|
+
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
|
|
4
|
+
|
|
5
|
+
function parseChannelMarketId(channel: string): string | undefined {
|
|
6
|
+
const colonIndex = channel.indexOf(':')
|
|
7
|
+
if (colonIndex < 0) {
|
|
8
|
+
return undefined
|
|
9
|
+
}
|
|
10
|
+
const suffix = channel.slice(colonIndex + 1)
|
|
11
|
+
if (suffix === 'all') {
|
|
12
|
+
return undefined
|
|
13
|
+
}
|
|
14
|
+
return suffix
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class LighterTradesMapper implements Mapper<'lighter', Trade> {
|
|
18
|
+
canHandle(message: LighterTradeMessage) {
|
|
19
|
+
return message.type === 'update/trade'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getFilters(symbols?: string[]) {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
channel: 'trade' as const,
|
|
26
|
+
symbols
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
*map(message: LighterTradeMessage, localTimestamp: Date): IterableIterator<Trade> {
|
|
32
|
+
for (const trade of message.trades) {
|
|
33
|
+
yield {
|
|
34
|
+
type: 'trade',
|
|
35
|
+
symbol: trade.market_id.toString(),
|
|
36
|
+
exchange: 'lighter',
|
|
37
|
+
id: trade.trade_id_str,
|
|
38
|
+
price: Number(trade.price),
|
|
39
|
+
amount: Number(trade.size),
|
|
40
|
+
side: trade.is_maker_ask ? 'buy' : 'sell',
|
|
41
|
+
timestamp: new Date(trade.timestamp),
|
|
42
|
+
localTimestamp
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class LighterLiquidationMapper implements Mapper<'lighter', Liquidation> {
|
|
49
|
+
canHandle(message: LighterTradeMessage) {
|
|
50
|
+
return message.type === 'update/trade'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getFilters(symbols?: string[]) {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
channel: 'trade' as const,
|
|
57
|
+
symbols
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
*map(message: LighterTradeMessage, localTimestamp: Date): IterableIterator<Liquidation> {
|
|
63
|
+
for (const trade of message.liquidation_trades) {
|
|
64
|
+
if (trade.type !== 'liquidation') {
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
yield {
|
|
69
|
+
type: 'liquidation',
|
|
70
|
+
symbol: trade.market_id.toString(),
|
|
71
|
+
exchange: 'lighter',
|
|
72
|
+
id: trade.trade_id_str,
|
|
73
|
+
price: Number(trade.price),
|
|
74
|
+
amount: Number(trade.size),
|
|
75
|
+
side: trade.is_maker_ask ? 'buy' : 'sell',
|
|
76
|
+
timestamp: new Date(trade.timestamp),
|
|
77
|
+
localTimestamp
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class LighterBookChangeMapper implements Mapper<'lighter', BookChange> {
|
|
84
|
+
canHandle(message: LighterOrderBookMessage) {
|
|
85
|
+
return message.type === 'subscribed/order_book' || message.type === 'update/order_book'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getFilters(symbols?: string[]) {
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
channel: 'order_book' as const,
|
|
92
|
+
symbols
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
*map(message: LighterOrderBookMessage, localTimestamp: Date): IterableIterator<BookChange> {
|
|
98
|
+
const symbol = parseChannelMarketId(message.channel)
|
|
99
|
+
if (symbol === undefined) return
|
|
100
|
+
|
|
101
|
+
yield {
|
|
102
|
+
type: 'book_change',
|
|
103
|
+
symbol,
|
|
104
|
+
exchange: 'lighter',
|
|
105
|
+
isSnapshot: message.type === 'subscribed/order_book',
|
|
106
|
+
bids: message.order_book.bids.map(this.mapLevel),
|
|
107
|
+
asks: message.order_book.asks.map(this.mapLevel),
|
|
108
|
+
timestamp: new Date(message.timestamp),
|
|
109
|
+
localTimestamp
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private mapLevel(level: LighterLevel) {
|
|
114
|
+
return {
|
|
115
|
+
price: Number(level.price),
|
|
116
|
+
amount: Number(level.size)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export class LighterBookTickerMapper implements Mapper<'lighter', BookTicker> {
|
|
122
|
+
canHandle(message: LighterTickerMessage) {
|
|
123
|
+
return message.type === 'update/ticker'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getFilters(symbols?: string[]) {
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
channel: 'ticker' as const,
|
|
130
|
+
symbols
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
*map(message: LighterTickerMessage, localTimestamp: Date): IterableIterator<BookTicker> {
|
|
136
|
+
const symbol = parseChannelMarketId(message.channel)
|
|
137
|
+
if (symbol === undefined) return
|
|
138
|
+
|
|
139
|
+
yield {
|
|
140
|
+
type: 'book_ticker',
|
|
141
|
+
symbol,
|
|
142
|
+
exchange: 'lighter',
|
|
143
|
+
askAmount: asNumberIfValid(message.ticker?.a?.size),
|
|
144
|
+
askPrice: asNumberIfValid(message.ticker?.a?.price),
|
|
145
|
+
bidPrice: asNumberIfValid(message.ticker?.b?.price),
|
|
146
|
+
bidAmount: asNumberIfValid(message.ticker?.b?.size),
|
|
147
|
+
timestamp: new Date(message.timestamp),
|
|
148
|
+
localTimestamp
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class LighterDerivativeTickerMapper implements Mapper<'lighter', DerivativeTicker> {
|
|
154
|
+
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
|
|
155
|
+
|
|
156
|
+
canHandle(message: LighterMarketStatsMessage) {
|
|
157
|
+
return message.type === 'update/market_stats'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getFilters(_symbols?: string[]) {
|
|
161
|
+
return [
|
|
162
|
+
{
|
|
163
|
+
channel: 'market_stats' as const,
|
|
164
|
+
symbols: []
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
*map(message: LighterMarketStatsMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
|
|
170
|
+
for (const entry of this.iterateMarketStats(message)) {
|
|
171
|
+
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(entry.market_id.toString(), 'lighter')
|
|
172
|
+
|
|
173
|
+
pendingTickerInfo.updateMarkPrice(Number(entry.mark_price))
|
|
174
|
+
pendingTickerInfo.updateIndexPrice(Number(entry.index_price))
|
|
175
|
+
pendingTickerInfo.updateFundingRate(Number(entry.current_funding_rate))
|
|
176
|
+
pendingTickerInfo.updateLastPrice(Number(entry.last_trade_price))
|
|
177
|
+
pendingTickerInfo.updateOpenInterest(Number(entry.open_interest))
|
|
178
|
+
|
|
179
|
+
if (pendingTickerInfo.hasChanged()) {
|
|
180
|
+
pendingTickerInfo.updateTimestamp(new Date(message.timestamp))
|
|
181
|
+
yield pendingTickerInfo.getSnapshot(localTimestamp)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private *iterateMarketStats(message: LighterMarketStatsMessage): IterableIterator<LighterMarketStats> {
|
|
187
|
+
if (message.channel === 'market_stats:all') {
|
|
188
|
+
for (const key of Object.keys(message.market_stats)) {
|
|
189
|
+
yield message.market_stats[key]
|
|
190
|
+
}
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
yield message.market_stats
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type LighterLevel = {
|
|
199
|
+
price: string
|
|
200
|
+
size: string
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
type LighterOrderBook = {
|
|
204
|
+
asks: LighterLevel[]
|
|
205
|
+
bids: LighterLevel[]
|
|
206
|
+
code: number
|
|
207
|
+
nonce: number
|
|
208
|
+
begin_nonce: number
|
|
209
|
+
offset: number
|
|
210
|
+
last_updated_at: number
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
type LighterOrderBookMessage = {
|
|
214
|
+
type: 'subscribed/order_book' | 'update/order_book'
|
|
215
|
+
channel: `order_book:${number}`
|
|
216
|
+
last_updated_at: number
|
|
217
|
+
offset: number
|
|
218
|
+
timestamp: number
|
|
219
|
+
order_book: LighterOrderBook
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
type LighterTicker = {
|
|
223
|
+
s: string
|
|
224
|
+
a?: Partial<LighterLevel>
|
|
225
|
+
b?: Partial<LighterLevel>
|
|
226
|
+
last_updated_at: number
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
type LighterTickerMessage = {
|
|
230
|
+
type: 'subscribed/ticker' | 'update/ticker'
|
|
231
|
+
channel: `ticker:${number}`
|
|
232
|
+
last_updated_at: number
|
|
233
|
+
nonce: number
|
|
234
|
+
ticker: LighterTicker
|
|
235
|
+
timestamp: number
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
type LighterTrade = {
|
|
239
|
+
trade_id: number
|
|
240
|
+
trade_id_str: string
|
|
241
|
+
tx_hash: string
|
|
242
|
+
type: 'trade' | 'liquidation' | 'deleverage' | 'market-settlement'
|
|
243
|
+
market_id: number
|
|
244
|
+
size: string
|
|
245
|
+
price: string
|
|
246
|
+
usd_amount: string
|
|
247
|
+
ask_id: number
|
|
248
|
+
ask_id_str: string
|
|
249
|
+
bid_id: number
|
|
250
|
+
bid_id_str: string
|
|
251
|
+
ask_client_id: number
|
|
252
|
+
ask_client_id_str: string
|
|
253
|
+
bid_client_id: number
|
|
254
|
+
bid_client_id_str: string
|
|
255
|
+
ask_account_id: number
|
|
256
|
+
bid_account_id: number
|
|
257
|
+
is_maker_ask: boolean
|
|
258
|
+
block_height: number
|
|
259
|
+
timestamp: number
|
|
260
|
+
taker_fee?: number
|
|
261
|
+
taker_position_size_before?: string
|
|
262
|
+
taker_entry_quote_before?: string
|
|
263
|
+
taker_initial_margin_fraction_before?: number
|
|
264
|
+
taker_position_sign_changed?: boolean
|
|
265
|
+
taker_allocated_margin_usdc_before?: number
|
|
266
|
+
maker_fee?: number
|
|
267
|
+
maker_position_size_before?: string
|
|
268
|
+
maker_entry_quote_before?: string
|
|
269
|
+
maker_initial_margin_fraction_before?: number
|
|
270
|
+
maker_position_sign_changed?: boolean
|
|
271
|
+
transaction_time: number
|
|
272
|
+
ask_account_pnl?: string
|
|
273
|
+
bid_account_pnl?: string
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
type LighterTradeMessage = {
|
|
277
|
+
type: 'subscribed/trade' | 'update/trade'
|
|
278
|
+
channel: `trade:${number}`
|
|
279
|
+
nonce: number
|
|
280
|
+
trades: LighterTrade[]
|
|
281
|
+
liquidation_trades: LighterTrade[]
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
type LighterMarketStats = {
|
|
285
|
+
symbol: string
|
|
286
|
+
market_id: number
|
|
287
|
+
index_price: string
|
|
288
|
+
mark_price: string
|
|
289
|
+
mid_price: string
|
|
290
|
+
open_interest: string
|
|
291
|
+
open_interest_limit: string
|
|
292
|
+
funding_clamp_small: string
|
|
293
|
+
funding_clamp_big: string
|
|
294
|
+
last_trade_price: string
|
|
295
|
+
current_funding_rate: string
|
|
296
|
+
funding_rate: string
|
|
297
|
+
funding_timestamp: number
|
|
298
|
+
daily_base_token_volume: number
|
|
299
|
+
daily_quote_token_volume: number
|
|
300
|
+
daily_price_low: number
|
|
301
|
+
daily_price_high: number
|
|
302
|
+
daily_price_change: number
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
type LighterMarketStatsAllMessage = {
|
|
306
|
+
type: 'subscribed/market_stats' | 'update/market_stats'
|
|
307
|
+
channel: 'market_stats:all'
|
|
308
|
+
timestamp: number
|
|
309
|
+
market_stats: Record<string, LighterMarketStats>
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
type LighterMarketStatsMarketIdMessage = {
|
|
313
|
+
type: 'subscribed/market_stats' | 'update/market_stats'
|
|
314
|
+
channel: `market_stats:${number}`
|
|
315
|
+
timestamp: number
|
|
316
|
+
market_stats: LighterMarketStats
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
type LighterMarketStatsMessage = LighterMarketStatsAllMessage | LighterMarketStatsMarketIdMessage
|
|
320
|
+
|
|
321
|
+
type LighterSpotMarketStats = {
|
|
322
|
+
symbol: string
|
|
323
|
+
market_id: number
|
|
324
|
+
index_price: string
|
|
325
|
+
mid_price: string
|
|
326
|
+
last_trade_price: string
|
|
327
|
+
daily_base_token_volume: number
|
|
328
|
+
daily_quote_token_volume: number
|
|
329
|
+
daily_price_low: number
|
|
330
|
+
daily_price_high: number
|
|
331
|
+
daily_price_change: number
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
type LighterSpotMarketStatsAllMessage = {
|
|
335
|
+
type: 'subscribed/spot_market_stats' | 'update/spot_market_stats'
|
|
336
|
+
channel: 'spot_market_stats:all'
|
|
337
|
+
timestamp: number
|
|
338
|
+
spot_market_stats: Record<string, LighterSpotMarketStats>
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
type LighterSpotMarketStatsMarketIdMessage = {
|
|
342
|
+
type: 'subscribed/spot_market_stats' | 'update/spot_market_stats'
|
|
343
|
+
channel: `spot_market_stats:${number}`
|
|
344
|
+
timestamp: number
|
|
345
|
+
spot_market_stats: LighterSpotMarketStats
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
type LighterSpotMarketStatsMessage = LighterSpotMarketStatsAllMessage | LighterSpotMarketStatsMarketIdMessage
|
|
@@ -52,6 +52,7 @@ import { DydxV4RealTimeFeed } from './dydx_v4.ts'
|
|
|
52
52
|
import { BitgetFuturesRealTimeFeed, BitgetRealTimeFeed } from './bitget.ts'
|
|
53
53
|
import { CoinbaseInternationalRealTimeFeed } from './coinbaseinternational.ts'
|
|
54
54
|
import { HyperliquidRealTimeFeed } from './hyperliquid.ts'
|
|
55
|
+
import { LighterRealTimeFeed } from './lighter.ts'
|
|
55
56
|
|
|
56
57
|
export * from './realtimefeed.ts'
|
|
57
58
|
|
|
@@ -114,7 +115,8 @@ const realTimeFeedsMap: {
|
|
|
114
115
|
bitget: BitgetRealTimeFeed,
|
|
115
116
|
'bitget-futures': BitgetFuturesRealTimeFeed,
|
|
116
117
|
'coinbase-international': CoinbaseInternationalRealTimeFeed,
|
|
117
|
-
hyperliquid: HyperliquidRealTimeFeed
|
|
118
|
+
hyperliquid: HyperliquidRealTimeFeed,
|
|
119
|
+
lighter: LighterRealTimeFeed
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Filter } from '../types.ts'
|
|
2
|
+
import { RealTimeFeedBase } from './realtimefeed.ts'
|
|
3
|
+
|
|
4
|
+
export class LighterRealTimeFeed extends RealTimeFeedBase {
|
|
5
|
+
protected wssURL = 'wss://mainnet.zklighter.elliot.ai/stream'
|
|
6
|
+
|
|
7
|
+
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
8
|
+
return filters.flatMap((filter) => {
|
|
9
|
+
if (filter.channel === 'market_stats') {
|
|
10
|
+
return [{ type: 'subscribe', channel: 'market_stats/all' }]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (filter.channel === 'spot_market_stats') {
|
|
14
|
+
return [{ type: 'subscribe', channel: 'spot_market_stats/all' }]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!filter.symbols || filter.symbols.length === 0) {
|
|
18
|
+
throw new Error('LighterRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return filter.symbols.map((marketId) => ({
|
|
22
|
+
type: 'subscribe',
|
|
23
|
+
channel: `${filter.channel}/${marketId}`
|
|
24
|
+
}))
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected messageIsError(message: any): boolean {
|
|
29
|
+
return message.error !== undefined
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/worker.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { Exchange, Filter } from './types.ts'
|
|
|
8
8
|
const debug = dbg('tardis-dev')
|
|
9
9
|
|
|
10
10
|
if (isMainThread) {
|
|
11
|
-
debug('
|
|
11
|
+
debug('current worker is not meant to run in main thread')
|
|
12
12
|
} else {
|
|
13
13
|
parentPort!.on('message', (signal: WorkerSignal) => {
|
|
14
14
|
if (signal === WorkerSignal.BEFORE_TERMINATE) {
|