@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.
@@ -0,0 +1,516 @@
1
+ /**
2
+ * WebSocket message types and interfaces
3
+ *
4
+ * Message type constants MUST match the K2 server values.
5
+ * See: https://github.com/k256-xyz for protocol documentation
6
+ */
7
+ /**
8
+ * WebSocket message type constants
9
+ *
10
+ * These are the byte prefixes used in the binary protocol.
11
+ * All SDKs across all languages MUST use these exact values.
12
+ */
13
+ declare const MessageType: {
14
+ /** Single pool state update (bincode) */
15
+ readonly PoolUpdate: 1;
16
+ /** Subscribe request (JSON) - Client → Server */
17
+ readonly Subscribe: 2;
18
+ /** Subscription confirmed (JSON) - Server → Client */
19
+ readonly Subscribed: 3;
20
+ /** Unsubscribe all - Client → Server */
21
+ readonly Unsubscribe: 4;
22
+ /** Priority fee update (bincode) */
23
+ readonly PriorityFees: 5;
24
+ /** Recent blockhash (bincode) */
25
+ readonly Blockhash: 6;
26
+ /** Streaming quote update (bincode) */
27
+ readonly Quote: 7;
28
+ /** Quote subscription confirmed (JSON) */
29
+ readonly QuoteSubscribed: 8;
30
+ /** Subscribe to quote stream (JSON) - Client → Server */
31
+ readonly SubscribeQuote: 9;
32
+ /** Unsubscribe from quote (JSON) - Client → Server */
33
+ readonly UnsubscribeQuote: 10;
34
+ /** Ping keepalive - Client → Server */
35
+ readonly Ping: 11;
36
+ /** Pong response (bincode u64 timestamp) */
37
+ readonly Pong: 12;
38
+ /** Connection heartbeat with stats (JSON) */
39
+ readonly Heartbeat: 13;
40
+ /** Batched pool updates for high throughput */
41
+ readonly PoolUpdateBatch: 14;
42
+ /** Error message (UTF-8 string) */
43
+ readonly Error: 255;
44
+ };
45
+ type MessageTypeValue = typeof MessageType[keyof typeof MessageType];
46
+ /**
47
+ * Decoded pool update from binary message
48
+ */
49
+ interface PoolUpdateMessage {
50
+ type: 'pool_update';
51
+ data: {
52
+ sequence: number;
53
+ slot: number;
54
+ writeVersion: number;
55
+ protocol: string;
56
+ poolAddress: string;
57
+ tokenMints: string[];
58
+ tokenBalances: string[];
59
+ tokenDecimals: number[];
60
+ isValid: boolean;
61
+ bestBid?: {
62
+ price: string;
63
+ size: string;
64
+ };
65
+ bestAsk?: {
66
+ price: string;
67
+ size: string;
68
+ };
69
+ };
70
+ }
71
+ /**
72
+ * Decoded priority fees from binary message
73
+ *
74
+ * All fields from K2 PriorityFeesWire
75
+ */
76
+ interface PriorityFeesMessage {
77
+ type: 'priority_fees';
78
+ data: {
79
+ slot: number;
80
+ timestampMs: number;
81
+ /** Recommended priority fee in micro-lamports */
82
+ recommended: number;
83
+ /** Fee state: 0=low, 1=normal, 2=high, 3=extreme */
84
+ state: number;
85
+ /** True if data is stale (no recent samples) */
86
+ isStale: boolean;
87
+ swapP50: number;
88
+ swapP75: number;
89
+ swapP90: number;
90
+ swapP99: number;
91
+ /** Number of samples used for swap percentiles */
92
+ swapSamples: number;
93
+ landingP50Fee: number;
94
+ landingP75Fee: number;
95
+ landingP90Fee: number;
96
+ landingP99Fee: number;
97
+ top10Fee: number;
98
+ top25Fee: number;
99
+ spikeDetected: boolean;
100
+ spikeFee: number;
101
+ };
102
+ }
103
+ /**
104
+ * Decoded blockhash from binary message
105
+ */
106
+ interface BlockhashMessage {
107
+ type: 'blockhash';
108
+ data: {
109
+ slot: number;
110
+ timestampMs: number;
111
+ blockhash: string;
112
+ blockHeight: number;
113
+ lastValidBlockHeight: number;
114
+ isStale: boolean;
115
+ };
116
+ }
117
+ /**
118
+ * Decoded quote from binary message
119
+ */
120
+ interface QuoteMessage {
121
+ type: 'quote';
122
+ data: {
123
+ topicId: string;
124
+ timestampMs: number;
125
+ sequence: number;
126
+ inputMint: string;
127
+ outputMint: string;
128
+ inAmount: string;
129
+ outAmount: string;
130
+ priceImpactBps: number;
131
+ contextSlot: number;
132
+ algorithm: string;
133
+ isImprovement: boolean;
134
+ isCached: boolean;
135
+ isStale: boolean;
136
+ routePlan: unknown | null;
137
+ };
138
+ }
139
+ /**
140
+ * Decoded heartbeat from JSON message
141
+ */
142
+ interface HeartbeatMessage {
143
+ type: 'heartbeat';
144
+ data: {
145
+ timestampMs: number;
146
+ uptimeSecs: number;
147
+ messagesSent: number;
148
+ poolUpdatesSent: number;
149
+ messagesDropped: number;
150
+ poolUpdatesEnabled: boolean;
151
+ subscribedChannels: string[];
152
+ serverSequence: number;
153
+ };
154
+ }
155
+ /**
156
+ * Subscription confirmed message
157
+ */
158
+ interface SubscribedMessage {
159
+ type: 'subscribed';
160
+ data: {
161
+ channelCount: number;
162
+ channels: string[];
163
+ poolCount: number;
164
+ tokenPairCount: number;
165
+ protocolCount: number;
166
+ poolUpdatesEnabled: boolean;
167
+ timestampMs: number;
168
+ summary: string;
169
+ format?: 'binary' | 'json';
170
+ };
171
+ }
172
+ /**
173
+ * Quote subscription confirmed message
174
+ */
175
+ interface QuoteSubscribedMessage {
176
+ type: 'quote_subscribed';
177
+ data: {
178
+ topicId: string;
179
+ inputMint: string;
180
+ outputMint: string;
181
+ amount: string;
182
+ slippageBps: number;
183
+ refreshIntervalMs: number;
184
+ timestampMs: number;
185
+ };
186
+ }
187
+ /**
188
+ * Error message from server
189
+ */
190
+ interface ErrorMessage {
191
+ type: 'error';
192
+ data: {
193
+ message: string;
194
+ };
195
+ }
196
+ /**
197
+ * Pong response message
198
+ */
199
+ interface PongMessage {
200
+ type: 'pong';
201
+ data: {
202
+ timestampMs: number;
203
+ };
204
+ }
205
+ /**
206
+ * Union of all decoded message types
207
+ */
208
+ type DecodedMessage = PoolUpdateMessage | PriorityFeesMessage | BlockhashMessage | QuoteMessage | HeartbeatMessage | SubscribedMessage | QuoteSubscribedMessage | ErrorMessage | PongMessage;
209
+
210
+ /**
211
+ * K256 WebSocket Client
212
+ *
213
+ * Production-grade WebSocket client with:
214
+ * - Automatic reconnection with exponential backoff
215
+ * - Binary and JSON mode support
216
+ * - Ping/pong keepalive
217
+ * - Heartbeat monitoring
218
+ * - Full error handling with RFC 6455 close codes
219
+ * - Type-safe event emitters
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * const client = new K256WebSocketClient({
224
+ * apiKey: 'your-api-key',
225
+ * mode: 'binary', // or 'json'
226
+ * onPoolUpdate: (update) => console.log(update),
227
+ * onError: (error) => console.error(error),
228
+ * });
229
+ *
230
+ * await client.connect();
231
+ * client.subscribe({ channels: ['pools', 'priority_fees'] });
232
+ * ```
233
+ */
234
+
235
+ /**
236
+ * RFC 6455 WebSocket Close Codes
237
+ * @see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
238
+ */
239
+ declare const CloseCode: {
240
+ /** 1000: Normal closure - connection completed successfully */
241
+ readonly NORMAL: 1000;
242
+ /** 1001: Going away - server/client shutting down. Client: reconnect immediately */
243
+ readonly GOING_AWAY: 1001;
244
+ /** 1002: Protocol error - invalid frame format. Client: fix client code */
245
+ readonly PROTOCOL_ERROR: 1002;
246
+ /** 1003: Unsupported data - message type not supported */
247
+ readonly UNSUPPORTED_DATA: 1003;
248
+ /** 1005: No status received (reserved, not sent over wire) */
249
+ readonly NO_STATUS: 1005;
250
+ /** 1006: Abnormal closure - connection dropped without close frame */
251
+ readonly ABNORMAL: 1006;
252
+ /** 1007: Invalid payload - malformed UTF-8 or data. Client: fix message format */
253
+ readonly INVALID_PAYLOAD: 1007;
254
+ /** 1008: Policy violation - rate limit exceeded, auth failed. Client: check credentials/limits */
255
+ readonly POLICY_VIOLATION: 1008;
256
+ /** 1009: Message too big - message exceeds size limits */
257
+ readonly MESSAGE_TOO_BIG: 1009;
258
+ /** 1010: Missing extension - required extension not negotiated */
259
+ readonly MISSING_EXTENSION: 1010;
260
+ /** 1011: Internal error - unexpected server error. Client: retry with backoff */
261
+ readonly INTERNAL_ERROR: 1011;
262
+ /** 1012: Service restart - server is restarting. Client: reconnect after brief delay */
263
+ readonly SERVICE_RESTART: 1012;
264
+ /** 1013: Try again later - server overloaded. Client: retry with backoff */
265
+ readonly TRY_AGAIN_LATER: 1013;
266
+ /** 1014: Bad gateway - upstream connection failed */
267
+ readonly BAD_GATEWAY: 1014;
268
+ /** 1015: TLS handshake failed (reserved, not sent over wire) */
269
+ readonly TLS_HANDSHAKE: 1015;
270
+ };
271
+ type CloseCodeValue = typeof CloseCode[keyof typeof CloseCode];
272
+ /**
273
+ * Connection state
274
+ */
275
+ type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
276
+ /**
277
+ * Subscribe request options
278
+ */
279
+ interface SubscribeOptions {
280
+ /** Channels to subscribe to: 'pools', 'priority_fees', 'blockhash' */
281
+ channels: string[];
282
+ /** Pool address filters (optional) */
283
+ pools?: string[];
284
+ /** Protocol filters (optional): 'Raydium AMM', 'Orca Whirlpool', etc. */
285
+ protocols?: string[];
286
+ /** Token pair filters (optional): [['mint1', 'mint2'], ...] */
287
+ tokenPairs?: string[][];
288
+ }
289
+ /**
290
+ * Quote subscription options
291
+ */
292
+ interface SubscribeQuoteOptions {
293
+ /** Input token mint address */
294
+ inputMint: string;
295
+ /** Output token mint address */
296
+ outputMint: string;
297
+ /** Amount in base units (lamports/smallest unit) */
298
+ amount: number | string;
299
+ /** Slippage tolerance in basis points */
300
+ slippageBps: number;
301
+ /** How often to refresh the quote (ms) */
302
+ refreshIntervalMs?: number;
303
+ }
304
+ /**
305
+ * WebSocket client configuration
306
+ */
307
+ interface K256WebSocketClientConfig {
308
+ /** API key for authentication */
309
+ apiKey: string;
310
+ /** Gateway URL (default: wss://gateway.k256.xyz/v1/ws) */
311
+ url?: string;
312
+ /** Message format: 'binary' (default, efficient) or 'json' (debugging) */
313
+ mode?: 'binary' | 'json';
314
+ /** Enable automatic reconnection (default: true) */
315
+ autoReconnect?: boolean;
316
+ /** Initial reconnect delay in ms (default: 1000) */
317
+ reconnectDelayMs?: number;
318
+ /** Max reconnect delay in ms (default: 30000) */
319
+ maxReconnectDelayMs?: number;
320
+ /** Max reconnect attempts (default: Infinity) */
321
+ maxReconnectAttempts?: number;
322
+ /** Ping interval in ms (default: 30000) */
323
+ pingIntervalMs?: number;
324
+ /** Pong timeout in ms - disconnect if no pong (default: 10000) */
325
+ pongTimeoutMs?: number;
326
+ /** Heartbeat timeout in ms - warn if no heartbeat (default: 15000) */
327
+ heartbeatTimeoutMs?: number;
328
+ /** Called when connection state changes */
329
+ onStateChange?: (state: ConnectionState, prevState: ConnectionState) => void;
330
+ /** Called on successful connection */
331
+ onConnect?: () => void;
332
+ /** Called on disconnection */
333
+ onDisconnect?: (code: number, reason: string, wasClean: boolean) => void;
334
+ /** Called on reconnection attempt */
335
+ onReconnecting?: (attempt: number, delayMs: number) => void;
336
+ /** Called on any error */
337
+ onError?: (error: K256WebSocketError) => void;
338
+ /** Called on subscription confirmed */
339
+ onSubscribed?: (data: DecodedMessage & {
340
+ type: 'subscribed';
341
+ }) => void;
342
+ /** Called on pool update */
343
+ onPoolUpdate?: (update: PoolUpdateMessage) => void;
344
+ /** Called on batched pool updates (for efficiency) */
345
+ onPoolUpdateBatch?: (updates: PoolUpdateMessage[]) => void;
346
+ /** Called on priority fees update */
347
+ onPriorityFees?: (data: DecodedMessage & {
348
+ type: 'priority_fees';
349
+ }) => void;
350
+ /** Called on blockhash update */
351
+ onBlockhash?: (data: DecodedMessage & {
352
+ type: 'blockhash';
353
+ }) => void;
354
+ /** Called on quote update */
355
+ onQuote?: (data: DecodedMessage & {
356
+ type: 'quote';
357
+ }) => void;
358
+ /** Called on quote subscription confirmed */
359
+ onQuoteSubscribed?: (data: DecodedMessage & {
360
+ type: 'quote_subscribed';
361
+ }) => void;
362
+ /** Called on heartbeat */
363
+ onHeartbeat?: (data: DecodedMessage & {
364
+ type: 'heartbeat';
365
+ }) => void;
366
+ /** Called on pong response (with round-trip latency) */
367
+ onPong?: (latencyMs: number) => void;
368
+ /** Called on any message (raw) */
369
+ onMessage?: (message: DecodedMessage) => void;
370
+ /** Called on raw binary message (for debugging) */
371
+ onRawMessage?: (data: ArrayBuffer | string) => void;
372
+ }
373
+ /**
374
+ * Error types for K256 WebSocket
375
+ */
376
+ type K256ErrorCode = 'CONNECTION_FAILED' | 'CONNECTION_LOST' | 'PROTOCOL_ERROR' | 'AUTH_FAILED' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'PING_TIMEOUT' | 'HEARTBEAT_TIMEOUT' | 'INVALID_MESSAGE' | 'RECONNECT_FAILED';
377
+ /**
378
+ * WebSocket error with context
379
+ */
380
+ declare class K256WebSocketError extends Error {
381
+ readonly code: K256ErrorCode;
382
+ readonly closeCode?: number | undefined;
383
+ readonly closeReason?: string | undefined;
384
+ readonly cause?: unknown | undefined;
385
+ constructor(code: K256ErrorCode, message: string, closeCode?: number | undefined, closeReason?: string | undefined, cause?: unknown | undefined);
386
+ /** Check if error is recoverable (should trigger reconnect) */
387
+ get isRecoverable(): boolean;
388
+ /** Check if error is an auth failure */
389
+ get isAuthError(): boolean;
390
+ }
391
+ /**
392
+ * Production-grade K256 WebSocket Client
393
+ */
394
+ declare class K256WebSocketClient {
395
+ private ws;
396
+ private config;
397
+ private _state;
398
+ private reconnectAttempts;
399
+ private reconnectTimer;
400
+ private pingTimer;
401
+ private pongTimer;
402
+ private heartbeatTimer;
403
+ private lastPingTime;
404
+ private lastHeartbeatTime;
405
+ private pendingSubscription;
406
+ private pendingQuoteSubscription;
407
+ private isIntentionallyClosed;
408
+ /** Current connection state */
409
+ get state(): ConnectionState;
410
+ /** Whether currently connected */
411
+ get isConnected(): boolean;
412
+ /** Time since last heartbeat (ms) or null if no heartbeat received */
413
+ get timeSinceHeartbeat(): number | null;
414
+ /** Current reconnect attempt number */
415
+ get currentReconnectAttempt(): number;
416
+ constructor(config: K256WebSocketClientConfig);
417
+ /**
418
+ * Connect to the WebSocket server
419
+ * @returns Promise that resolves when connected
420
+ */
421
+ connect(): Promise<void>;
422
+ /**
423
+ * Disconnect from the WebSocket server
424
+ * @param code - Close code (default: 1000 NORMAL)
425
+ * @param reason - Close reason
426
+ */
427
+ disconnect(code?: number, reason?: string): void;
428
+ /**
429
+ * Subscribe to channels
430
+ */
431
+ subscribe(options: SubscribeOptions): void;
432
+ /**
433
+ * Subscribe to a quote stream
434
+ */
435
+ subscribeQuote(options: SubscribeQuoteOptions): void;
436
+ /**
437
+ * Unsubscribe from a quote stream
438
+ * @param topicId - Topic ID from quote_subscribed response
439
+ */
440
+ unsubscribeQuote(topicId: string): void;
441
+ /**
442
+ * Unsubscribe from all channels
443
+ */
444
+ unsubscribe(): void;
445
+ /**
446
+ * Send a ping to measure latency
447
+ */
448
+ ping(): void;
449
+ private doConnect;
450
+ private handleMessage;
451
+ private sendSubscription;
452
+ private sendQuoteSubscription;
453
+ private setState;
454
+ private handleError;
455
+ private cleanup;
456
+ private startPingInterval;
457
+ private startPongTimeout;
458
+ private clearPongTimeout;
459
+ private startHeartbeatTimeout;
460
+ private resetHeartbeatTimeout;
461
+ private shouldReconnect;
462
+ private scheduleReconnect;
463
+ private getCloseReason;
464
+ private getErrorCodeFromClose;
465
+ }
466
+
467
+ /**
468
+ * Binary message decoder for K256 WebSocket protocol
469
+ *
470
+ * Decodes binary messages from K2 server into typed JavaScript objects.
471
+ * Supports both single messages and batched pool updates.
472
+ *
473
+ * Wire format: [1 byte MessageType][N bytes Payload]
474
+ */
475
+
476
+ /**
477
+ * Decode a binary WebSocket message from K2
478
+ *
479
+ * @param data - Raw binary data from WebSocket
480
+ * @returns Decoded message or null if unrecognized type
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * ws.onmessage = (event) => {
485
+ * if (event.data instanceof ArrayBuffer) {
486
+ * const message = decodeMessage(event.data);
487
+ * if (message?.type === 'pool_update') {
488
+ * console.log(message.data.poolAddress);
489
+ * }
490
+ * }
491
+ * };
492
+ * ```
493
+ */
494
+ declare function decodeMessage(data: ArrayBuffer): DecodedMessage | null;
495
+ /**
496
+ * Decode a batch of pool updates
497
+ *
498
+ * Use this when you need to process all updates in a batch.
499
+ * For high-throughput scenarios, batches can contain 50-200 updates.
500
+ *
501
+ * @param data - Raw payload (without the 0x0E type prefix)
502
+ * @returns Array of decoded pool updates
503
+ *
504
+ * @example
505
+ * ```typescript
506
+ * if (msgType === MessageType.PoolUpdateBatch) {
507
+ * const updates = decodePoolUpdateBatch(payload);
508
+ * for (const update of updates) {
509
+ * console.log(update.data.poolAddress);
510
+ * }
511
+ * }
512
+ * ```
513
+ */
514
+ declare function decodePoolUpdateBatch(payload: ArrayBuffer): PoolUpdateMessage[];
515
+
516
+ export { type BlockhashMessage, CloseCode, type CloseCodeValue, type ConnectionState, type DecodedMessage, type ErrorMessage, type HeartbeatMessage, type K256ErrorCode, K256WebSocketClient, type K256WebSocketClientConfig, K256WebSocketError, MessageType, type MessageTypeValue, type PongMessage, type PoolUpdateMessage, type PriorityFeesMessage, type QuoteMessage, type QuoteSubscribedMessage, type SubscribeOptions, type SubscribeQuoteOptions, type SubscribedMessage, decodeMessage, decodePoolUpdateBatch };