@k256/sdk 0.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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/index.cjs +949 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +938 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.cjs +14 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +202 -0
- package/dist/types/index.d.ts +202 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +70 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +48 -0
- package/dist/utils/index.d.ts +48 -0
- package/dist/utils/index.js +66 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/ws/index.cjs +896 -0
- package/dist/ws/index.cjs.map +1 -0
- package/dist/ws/index.d.cts +516 -0
- package/dist/ws/index.d.ts +516 -0
- package/dist/ws/index.js +889 -0
- package/dist/ws/index.js.map +1 -0
- package/package.json +92 -0
- package/src/index.ts +34 -0
- package/src/types/index.ts +212 -0
- package/src/utils/base58.ts +123 -0
- package/src/utils/index.ts +7 -0
- package/src/ws/client.ts +786 -0
- package/src/ws/decoder.ts +490 -0
- package/src/ws/index.ts +55 -0
- package/src/ws/types.ts +227 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary message decoder for K256 WebSocket protocol
|
|
3
|
+
*
|
|
4
|
+
* Decodes binary messages from K2 server into typed JavaScript objects.
|
|
5
|
+
* Supports both single messages and batched pool updates.
|
|
6
|
+
*
|
|
7
|
+
* Wire format: [1 byte MessageType][N bytes Payload]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { base58Encode } from '../utils/base58';
|
|
11
|
+
import { MessageType, type DecodedMessage, type PoolUpdateMessage } from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Decode a binary WebSocket message from K2
|
|
15
|
+
*
|
|
16
|
+
* @param data - Raw binary data from WebSocket
|
|
17
|
+
* @returns Decoded message or null if unrecognized type
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* ws.onmessage = (event) => {
|
|
22
|
+
* if (event.data instanceof ArrayBuffer) {
|
|
23
|
+
* const message = decodeMessage(event.data);
|
|
24
|
+
* if (message?.type === 'pool_update') {
|
|
25
|
+
* console.log(message.data.poolAddress);
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* };
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function decodeMessage(data: ArrayBuffer): DecodedMessage | null {
|
|
32
|
+
const view = new DataView(data);
|
|
33
|
+
if (data.byteLength < 1) return null;
|
|
34
|
+
|
|
35
|
+
const msgType = view.getUint8(0);
|
|
36
|
+
const payload = data.slice(1);
|
|
37
|
+
const payloadView = new DataView(payload);
|
|
38
|
+
|
|
39
|
+
switch (msgType) {
|
|
40
|
+
case MessageType.Subscribed:
|
|
41
|
+
case MessageType.QuoteSubscribed:
|
|
42
|
+
case MessageType.Heartbeat: {
|
|
43
|
+
// JSON payload
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
const text = decoder.decode(payload);
|
|
46
|
+
try {
|
|
47
|
+
let type: string;
|
|
48
|
+
if (msgType === MessageType.QuoteSubscribed) {
|
|
49
|
+
type = 'quote_subscribed';
|
|
50
|
+
} else if (msgType === MessageType.Heartbeat) {
|
|
51
|
+
type = 'heartbeat';
|
|
52
|
+
} else {
|
|
53
|
+
type = 'subscribed';
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
type,
|
|
57
|
+
data: JSON.parse(text),
|
|
58
|
+
} as DecodedMessage;
|
|
59
|
+
} catch {
|
|
60
|
+
return { type: 'error', data: { message: text } };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case MessageType.Error: {
|
|
65
|
+
// UTF-8 string payload
|
|
66
|
+
const decoder = new TextDecoder();
|
|
67
|
+
const text = decoder.decode(payload);
|
|
68
|
+
return { type: 'error', data: { message: text } };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case MessageType.PriorityFees: {
|
|
72
|
+
// PriorityFeesWire bincode layout:
|
|
73
|
+
// Offset 0: slot: u64
|
|
74
|
+
// Offset 8: timestamp_ms: u64
|
|
75
|
+
// Offset 16: recommended: u64
|
|
76
|
+
// Offset 24: state: u8 (0=low, 1=normal, 2=high, 3=extreme)
|
|
77
|
+
// Offset 25: is_stale: bool
|
|
78
|
+
// Offset 26: swap_p50: u64
|
|
79
|
+
// Offset 34: swap_p75: u64
|
|
80
|
+
// Offset 42: swap_p90: u64
|
|
81
|
+
// Offset 50: swap_p99: u64
|
|
82
|
+
// Offset 58: swap_samples: u32
|
|
83
|
+
// Offset 62: landing_p50_fee: u64
|
|
84
|
+
// Offset 70: landing_p75_fee: u64
|
|
85
|
+
// Offset 78: landing_p90_fee: u64
|
|
86
|
+
// Offset 86: landing_p99_fee: u64
|
|
87
|
+
// Offset 94: top_10_fee: u64
|
|
88
|
+
// Offset 102: top_25_fee: u64
|
|
89
|
+
// Offset 110: spike_detected: bool
|
|
90
|
+
// Offset 111: spike_fee: u64
|
|
91
|
+
// Total: 119 bytes
|
|
92
|
+
if (payload.byteLength < 24) return null;
|
|
93
|
+
|
|
94
|
+
const slot = Number(payloadView.getBigUint64(0, true));
|
|
95
|
+
const timestampMs = Number(payloadView.getBigUint64(8, true));
|
|
96
|
+
const recommended = Number(payloadView.getBigUint64(16, true));
|
|
97
|
+
const state = payload.byteLength > 24 ? payloadView.getUint8(24) : 1;
|
|
98
|
+
const isStale = payload.byteLength > 25 ? payloadView.getUint8(25) !== 0 : false;
|
|
99
|
+
|
|
100
|
+
// Swap percentiles (offset 26-57)
|
|
101
|
+
let swapP50 = 0, swapP75 = 0, swapP90 = 0, swapP99 = 0;
|
|
102
|
+
if (payload.byteLength >= 58) {
|
|
103
|
+
swapP50 = Number(payloadView.getBigUint64(26, true));
|
|
104
|
+
swapP75 = Number(payloadView.getBigUint64(34, true));
|
|
105
|
+
swapP90 = Number(payloadView.getBigUint64(42, true));
|
|
106
|
+
swapP99 = Number(payloadView.getBigUint64(50, true));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Extended fields (offset 58+)
|
|
110
|
+
let swapSamples = 0;
|
|
111
|
+
let landingP50Fee = 0, landingP75Fee = 0, landingP90Fee = 0, landingP99Fee = 0;
|
|
112
|
+
let top10Fee = 0, top25Fee = 0;
|
|
113
|
+
let spikeDetected = false, spikeFee = 0;
|
|
114
|
+
|
|
115
|
+
if (payload.byteLength >= 119) {
|
|
116
|
+
swapSamples = payloadView.getUint32(58, true);
|
|
117
|
+
landingP50Fee = Number(payloadView.getBigUint64(62, true));
|
|
118
|
+
landingP75Fee = Number(payloadView.getBigUint64(70, true));
|
|
119
|
+
landingP90Fee = Number(payloadView.getBigUint64(78, true));
|
|
120
|
+
landingP99Fee = Number(payloadView.getBigUint64(86, true));
|
|
121
|
+
top10Fee = Number(payloadView.getBigUint64(94, true));
|
|
122
|
+
top25Fee = Number(payloadView.getBigUint64(102, true));
|
|
123
|
+
spikeDetected = payloadView.getUint8(110) !== 0;
|
|
124
|
+
spikeFee = Number(payloadView.getBigUint64(111, true));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
type: 'priority_fees',
|
|
129
|
+
data: {
|
|
130
|
+
slot,
|
|
131
|
+
timestampMs,
|
|
132
|
+
recommended,
|
|
133
|
+
state,
|
|
134
|
+
isStale,
|
|
135
|
+
swapP50,
|
|
136
|
+
swapP75,
|
|
137
|
+
swapP90,
|
|
138
|
+
swapP99,
|
|
139
|
+
swapSamples,
|
|
140
|
+
landingP50Fee,
|
|
141
|
+
landingP75Fee,
|
|
142
|
+
landingP90Fee,
|
|
143
|
+
landingP99Fee,
|
|
144
|
+
top10Fee,
|
|
145
|
+
top25Fee,
|
|
146
|
+
spikeDetected,
|
|
147
|
+
spikeFee,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case MessageType.Blockhash: {
|
|
153
|
+
// BlockhashWire bincode layout
|
|
154
|
+
if (payload.byteLength < 65) return null;
|
|
155
|
+
|
|
156
|
+
const slot = Number(payloadView.getBigUint64(0, true));
|
|
157
|
+
const timestampMs = Number(payloadView.getBigUint64(8, true));
|
|
158
|
+
const blockhashBytes = new Uint8Array(payload, 16, 32);
|
|
159
|
+
const blockHeight = Number(payloadView.getBigUint64(48, true));
|
|
160
|
+
const lastValidBlockHeight = Number(payloadView.getBigUint64(56, true));
|
|
161
|
+
const isStale = payloadView.getUint8(64) !== 0;
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
type: 'blockhash',
|
|
165
|
+
data: {
|
|
166
|
+
slot,
|
|
167
|
+
timestampMs,
|
|
168
|
+
blockhash: base58Encode(blockhashBytes),
|
|
169
|
+
blockHeight,
|
|
170
|
+
lastValidBlockHeight,
|
|
171
|
+
isStale,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case MessageType.PoolUpdate: {
|
|
177
|
+
return decodePoolUpdate(payload, payloadView);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case MessageType.PoolUpdateBatch: {
|
|
181
|
+
// Batched pool updates: [u16 count][u32 len][payload]...
|
|
182
|
+
// Returns array of individual updates
|
|
183
|
+
if (payload.byteLength < 2) return null;
|
|
184
|
+
|
|
185
|
+
const updates = decodePoolUpdateBatch(payload);
|
|
186
|
+
if (updates.length === 0) return null;
|
|
187
|
+
|
|
188
|
+
// Return first update for single-message interface
|
|
189
|
+
// Use decodePoolUpdateBatch() directly for batch handling
|
|
190
|
+
return updates[0];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case MessageType.Quote: {
|
|
194
|
+
return decodeQuote(payload, payloadView);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case MessageType.Pong: {
|
|
198
|
+
if (payload.byteLength < 8) return null;
|
|
199
|
+
return {
|
|
200
|
+
type: 'pong',
|
|
201
|
+
data: {
|
|
202
|
+
timestampMs: Number(payloadView.getBigUint64(0, true)),
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
default:
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Decode a batch of pool updates
|
|
214
|
+
*
|
|
215
|
+
* Use this when you need to process all updates in a batch.
|
|
216
|
+
* For high-throughput scenarios, batches can contain 50-200 updates.
|
|
217
|
+
*
|
|
218
|
+
* @param data - Raw payload (without the 0x0E type prefix)
|
|
219
|
+
* @returns Array of decoded pool updates
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* if (msgType === MessageType.PoolUpdateBatch) {
|
|
224
|
+
* const updates = decodePoolUpdateBatch(payload);
|
|
225
|
+
* for (const update of updates) {
|
|
226
|
+
* console.log(update.data.poolAddress);
|
|
227
|
+
* }
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function decodePoolUpdateBatch(payload: ArrayBuffer): PoolUpdateMessage[] {
|
|
232
|
+
const view = new DataView(payload);
|
|
233
|
+
if (payload.byteLength < 2) return [];
|
|
234
|
+
|
|
235
|
+
const count = view.getUint16(0, true);
|
|
236
|
+
const updates: PoolUpdateMessage[] = [];
|
|
237
|
+
let offset = 2;
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < count && offset + 4 <= payload.byteLength; i++) {
|
|
240
|
+
const payloadLen = view.getUint32(offset, true);
|
|
241
|
+
offset += 4;
|
|
242
|
+
|
|
243
|
+
if (offset + payloadLen > payload.byteLength) break;
|
|
244
|
+
|
|
245
|
+
// Decode individual pool update (payload is WITHOUT the 0x01 type prefix)
|
|
246
|
+
const updatePayload = payload.slice(offset, offset + payloadLen);
|
|
247
|
+
const updateView = new DataView(updatePayload);
|
|
248
|
+
const decoded = decodePoolUpdate(updatePayload, updateView);
|
|
249
|
+
|
|
250
|
+
if (decoded) {
|
|
251
|
+
updates.push(decoded);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
offset += payloadLen;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return updates;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Decode a single pool update payload
|
|
262
|
+
*/
|
|
263
|
+
function decodePoolUpdate(payload: ArrayBuffer, payloadView: DataView): PoolUpdateMessage | null {
|
|
264
|
+
if (payload.byteLength < 50) return null;
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
let offset = 0;
|
|
268
|
+
const decoder = new TextDecoder();
|
|
269
|
+
|
|
270
|
+
// Skip serialized_state (Bytes: u64 len + bytes)
|
|
271
|
+
const serializedStateLen = Number(payloadView.getBigUint64(offset, true));
|
|
272
|
+
offset += 8 + serializedStateLen;
|
|
273
|
+
|
|
274
|
+
if (payload.byteLength < offset + 24) return null;
|
|
275
|
+
|
|
276
|
+
// sequence (u64)
|
|
277
|
+
const sequence = Number(payloadView.getBigUint64(offset, true));
|
|
278
|
+
offset += 8;
|
|
279
|
+
|
|
280
|
+
// slot (u64)
|
|
281
|
+
const slot = Number(payloadView.getBigUint64(offset, true));
|
|
282
|
+
offset += 8;
|
|
283
|
+
|
|
284
|
+
// write_version (u64)
|
|
285
|
+
const writeVersion = Number(payloadView.getBigUint64(offset, true));
|
|
286
|
+
offset += 8;
|
|
287
|
+
|
|
288
|
+
// protocol_name (String: u64 len + utf8 bytes)
|
|
289
|
+
const protocolLen = Number(payloadView.getBigUint64(offset, true));
|
|
290
|
+
offset += 8;
|
|
291
|
+
const protocolBytes = new Uint8Array(payload, offset, protocolLen);
|
|
292
|
+
const protocol = decoder.decode(protocolBytes);
|
|
293
|
+
offset += protocolLen;
|
|
294
|
+
|
|
295
|
+
// pool_address ([u8; 32])
|
|
296
|
+
const poolAddr = new Uint8Array(payload, offset, 32);
|
|
297
|
+
offset += 32;
|
|
298
|
+
|
|
299
|
+
// all_token_mints (Vec<[u8; 32]>)
|
|
300
|
+
const tokenMintCount = Number(payloadView.getBigUint64(offset, true));
|
|
301
|
+
offset += 8;
|
|
302
|
+
const tokenMints: string[] = [];
|
|
303
|
+
for (let i = 0; i < tokenMintCount && offset + 32 <= payload.byteLength; i++) {
|
|
304
|
+
const mint = new Uint8Array(payload, offset, 32);
|
|
305
|
+
tokenMints.push(base58Encode(mint));
|
|
306
|
+
offset += 32;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// all_token_balances (Vec<u64>)
|
|
310
|
+
const balanceCount = Number(payloadView.getBigUint64(offset, true));
|
|
311
|
+
offset += 8;
|
|
312
|
+
const tokenBalances: string[] = [];
|
|
313
|
+
for (let i = 0; i < balanceCount && offset + 8 <= payload.byteLength; i++) {
|
|
314
|
+
tokenBalances.push(payloadView.getBigUint64(offset, true).toString());
|
|
315
|
+
offset += 8;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// all_token_decimals (Vec<i32>)
|
|
319
|
+
const decimalsCount = Number(payloadView.getBigUint64(offset, true));
|
|
320
|
+
offset += 8;
|
|
321
|
+
const tokenDecimals: number[] = [];
|
|
322
|
+
for (let i = 0; i < decimalsCount && offset + 4 <= payload.byteLength; i++) {
|
|
323
|
+
tokenDecimals.push(payloadView.getInt32(offset, true));
|
|
324
|
+
offset += 4;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// is_valid (bool)
|
|
328
|
+
const isValid = offset < payload.byteLength ? payloadView.getUint8(offset) !== 0 : true;
|
|
329
|
+
offset += 1;
|
|
330
|
+
|
|
331
|
+
// best_bid and best_ask (Option<OrderLevel>) - skip for now
|
|
332
|
+
// They use bincode Option encoding: 0 = None, 1 + data = Some
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
type: 'pool_update',
|
|
336
|
+
data: {
|
|
337
|
+
sequence,
|
|
338
|
+
slot,
|
|
339
|
+
writeVersion,
|
|
340
|
+
protocol,
|
|
341
|
+
poolAddress: base58Encode(poolAddr),
|
|
342
|
+
tokenMints,
|
|
343
|
+
tokenBalances,
|
|
344
|
+
tokenDecimals,
|
|
345
|
+
isValid,
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
} catch {
|
|
349
|
+
return {
|
|
350
|
+
type: 'pool_update',
|
|
351
|
+
data: {
|
|
352
|
+
sequence: 0,
|
|
353
|
+
slot: 0,
|
|
354
|
+
writeVersion: 0,
|
|
355
|
+
protocol: 'unknown',
|
|
356
|
+
poolAddress: '',
|
|
357
|
+
tokenMints: [],
|
|
358
|
+
tokenBalances: [],
|
|
359
|
+
tokenDecimals: [],
|
|
360
|
+
isValid: false,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Decode a quote message payload
|
|
368
|
+
*/
|
|
369
|
+
function decodeQuote(payload: ArrayBuffer, payloadView: DataView): DecodedMessage | null {
|
|
370
|
+
if (payload.byteLength < 8) return null;
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
let offset = 0;
|
|
374
|
+
const decoder = new TextDecoder();
|
|
375
|
+
|
|
376
|
+
// Helper to read bincode String (u64 len + UTF-8 bytes)
|
|
377
|
+
const readString = (): string => {
|
|
378
|
+
const len = Number(payloadView.getBigUint64(offset, true));
|
|
379
|
+
offset += 8;
|
|
380
|
+
const bytes = new Uint8Array(payload, offset, len);
|
|
381
|
+
offset += len;
|
|
382
|
+
return decoder.decode(bytes);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// topic_id (String)
|
|
386
|
+
const topicId = readString();
|
|
387
|
+
|
|
388
|
+
// timestamp_ms (u64)
|
|
389
|
+
const timestampMs = Number(payloadView.getBigUint64(offset, true));
|
|
390
|
+
offset += 8;
|
|
391
|
+
|
|
392
|
+
// sequence (u64)
|
|
393
|
+
const sequence = Number(payloadView.getBigUint64(offset, true));
|
|
394
|
+
offset += 8;
|
|
395
|
+
|
|
396
|
+
// input_mint ([u8; 32])
|
|
397
|
+
const inputMintBytes = new Uint8Array(payload, offset, 32);
|
|
398
|
+
offset += 32;
|
|
399
|
+
|
|
400
|
+
// output_mint ([u8; 32])
|
|
401
|
+
const outputMintBytes = new Uint8Array(payload, offset, 32);
|
|
402
|
+
offset += 32;
|
|
403
|
+
|
|
404
|
+
// in_amount (u64)
|
|
405
|
+
const inAmount = payloadView.getBigUint64(offset, true).toString();
|
|
406
|
+
offset += 8;
|
|
407
|
+
|
|
408
|
+
// out_amount (u64)
|
|
409
|
+
const outAmount = payloadView.getBigUint64(offset, true).toString();
|
|
410
|
+
offset += 8;
|
|
411
|
+
|
|
412
|
+
// price_impact_bps (i32)
|
|
413
|
+
const priceImpactBps = payloadView.getInt32(offset, true);
|
|
414
|
+
offset += 4;
|
|
415
|
+
|
|
416
|
+
// context_slot (u64)
|
|
417
|
+
const contextSlot = Number(payloadView.getBigUint64(offset, true));
|
|
418
|
+
offset += 8;
|
|
419
|
+
|
|
420
|
+
// algorithm (String)
|
|
421
|
+
const algorithm = readString();
|
|
422
|
+
|
|
423
|
+
// is_improvement (bool)
|
|
424
|
+
const isImprovement = payloadView.getUint8(offset) !== 0;
|
|
425
|
+
offset += 1;
|
|
426
|
+
|
|
427
|
+
// is_cached (bool)
|
|
428
|
+
const isCached = payloadView.getUint8(offset) !== 0;
|
|
429
|
+
offset += 1;
|
|
430
|
+
|
|
431
|
+
// is_stale (bool)
|
|
432
|
+
const isStale = payloadView.getUint8(offset) !== 0;
|
|
433
|
+
offset += 1;
|
|
434
|
+
|
|
435
|
+
// route_plan_json (Vec<u8> - bincode: u64 len + bytes)
|
|
436
|
+
let routePlan = null;
|
|
437
|
+
if (offset + 8 <= payload.byteLength) {
|
|
438
|
+
const routePlanLen = Number(payloadView.getBigUint64(offset, true));
|
|
439
|
+
offset += 8;
|
|
440
|
+
if (routePlanLen > 0 && offset + routePlanLen <= payload.byteLength) {
|
|
441
|
+
const routePlanBytes = new Uint8Array(payload, offset, routePlanLen);
|
|
442
|
+
try {
|
|
443
|
+
routePlan = JSON.parse(decoder.decode(routePlanBytes));
|
|
444
|
+
} catch {
|
|
445
|
+
// Route plan JSON parsing failed, leave as null
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
type: 'quote',
|
|
452
|
+
data: {
|
|
453
|
+
topicId,
|
|
454
|
+
timestampMs,
|
|
455
|
+
sequence,
|
|
456
|
+
inputMint: base58Encode(inputMintBytes),
|
|
457
|
+
outputMint: base58Encode(outputMintBytes),
|
|
458
|
+
inAmount,
|
|
459
|
+
outAmount,
|
|
460
|
+
priceImpactBps,
|
|
461
|
+
contextSlot,
|
|
462
|
+
algorithm,
|
|
463
|
+
isImprovement,
|
|
464
|
+
isCached,
|
|
465
|
+
isStale,
|
|
466
|
+
routePlan,
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
} catch {
|
|
470
|
+
return {
|
|
471
|
+
type: 'quote',
|
|
472
|
+
data: {
|
|
473
|
+
topicId: '',
|
|
474
|
+
timestampMs: 0,
|
|
475
|
+
sequence: 0,
|
|
476
|
+
inputMint: '',
|
|
477
|
+
outputMint: '',
|
|
478
|
+
inAmount: '0',
|
|
479
|
+
outAmount: '0',
|
|
480
|
+
priceImpactBps: 0,
|
|
481
|
+
contextSlot: 0,
|
|
482
|
+
algorithm: '',
|
|
483
|
+
isImprovement: false,
|
|
484
|
+
isCached: false,
|
|
485
|
+
isStale: false,
|
|
486
|
+
routePlan: null,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
package/src/ws/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket module for K256 SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides production-grade WebSocket client with:
|
|
5
|
+
* - Binary and JSON mode support
|
|
6
|
+
* - Automatic reconnection with exponential backoff
|
|
7
|
+
* - Ping/pong keepalive
|
|
8
|
+
* - Full error handling with RFC 6455 close codes
|
|
9
|
+
*
|
|
10
|
+
* @module @k256/sdk/ws
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { K256WebSocketClient } from '@k256/sdk/ws';
|
|
15
|
+
*
|
|
16
|
+
* const client = new K256WebSocketClient({
|
|
17
|
+
* apiKey: 'your-api-key',
|
|
18
|
+
* mode: 'binary',
|
|
19
|
+
* onPoolUpdate: (update) => console.log(update),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await client.connect();
|
|
23
|
+
* client.subscribe({ channels: ['pools'] });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// Client
|
|
28
|
+
export { K256WebSocketClient, K256WebSocketError, CloseCode } from './client';
|
|
29
|
+
export type {
|
|
30
|
+
K256WebSocketClientConfig,
|
|
31
|
+
K256ErrorCode,
|
|
32
|
+
CloseCodeValue,
|
|
33
|
+
ConnectionState,
|
|
34
|
+
SubscribeOptions,
|
|
35
|
+
SubscribeQuoteOptions,
|
|
36
|
+
} from './client';
|
|
37
|
+
|
|
38
|
+
// Decoder (for advanced usage)
|
|
39
|
+
export { decodeMessage, decodePoolUpdateBatch } from './decoder';
|
|
40
|
+
|
|
41
|
+
// Types
|
|
42
|
+
export { MessageType } from './types';
|
|
43
|
+
export type {
|
|
44
|
+
MessageTypeValue,
|
|
45
|
+
DecodedMessage,
|
|
46
|
+
PoolUpdateMessage,
|
|
47
|
+
PriorityFeesMessage,
|
|
48
|
+
BlockhashMessage,
|
|
49
|
+
QuoteMessage,
|
|
50
|
+
HeartbeatMessage,
|
|
51
|
+
ErrorMessage,
|
|
52
|
+
SubscribedMessage,
|
|
53
|
+
QuoteSubscribedMessage,
|
|
54
|
+
PongMessage,
|
|
55
|
+
} from './types';
|