@k256/sdk 0.2.1 → 0.3.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/index.cjs +500 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +495 -1
- package/dist/index.js.map +1 -1
- package/dist/leader-ws/index.cjs +527 -0
- package/dist/leader-ws/index.cjs.map +1 -0
- package/dist/leader-ws/index.d.cts +337 -0
- package/dist/leader-ws/index.d.ts +337 -0
- package/dist/leader-ws/index.js +520 -0
- package/dist/leader-ws/index.js.map +1 -0
- package/package.json +11 -1
- package/src/index.ts +32 -1
- package/src/leader-ws/client.ts +377 -0
- package/src/leader-ws/decoder.ts +314 -0
- package/src/leader-ws/index.ts +58 -0
- package/src/leader-ws/types.ts +212 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leader Schedule WebSocket message types and interfaces
|
|
3
|
+
*
|
|
4
|
+
* The leader-schedule WS uses wincode binary protocol (matching K2 pattern).
|
|
5
|
+
* Wire format: [1-byte tag][wincode payload]
|
|
6
|
+
* Every decoded message has:
|
|
7
|
+
* - type: message type name
|
|
8
|
+
* - kind: "snapshot" (full state) | "diff" (merge into snapshot) | "event" (append-only)
|
|
9
|
+
* - key: primary key field for merging (on diff/event types)
|
|
10
|
+
* - data: typed payload
|
|
11
|
+
*
|
|
12
|
+
* @see https://k256.xyz/docs/leader-schedule
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Leader Schedule WebSocket channels
|
|
16
|
+
*/
|
|
17
|
+
declare const LeaderChannel: {
|
|
18
|
+
/** Full epoch leader schedule (on connect + epoch change) */
|
|
19
|
+
readonly LeaderSchedule: "leader_schedule";
|
|
20
|
+
/** Gossip peers (snapshot on connect, then diffs) */
|
|
21
|
+
readonly Gossip: "gossip";
|
|
22
|
+
/** Real-time slot updates with current leader */
|
|
23
|
+
readonly Slots: "slots";
|
|
24
|
+
/** Skip events, IP changes, routing health */
|
|
25
|
+
readonly Alerts: "alerts";
|
|
26
|
+
};
|
|
27
|
+
type LeaderChannelValue = typeof LeaderChannel[keyof typeof LeaderChannel];
|
|
28
|
+
/** All available channels */
|
|
29
|
+
declare const ALL_LEADER_CHANNELS: LeaderChannelValue[];
|
|
30
|
+
/**
|
|
31
|
+
* Message kind — tells you how to consume the message
|
|
32
|
+
*/
|
|
33
|
+
type MessageKind = 'snapshot' | 'diff' | 'event';
|
|
34
|
+
/** Protocol schema entry (included in subscribed handshake) */
|
|
35
|
+
interface MessageSchemaEntry {
|
|
36
|
+
type: string;
|
|
37
|
+
tag: string;
|
|
38
|
+
kind: MessageKind;
|
|
39
|
+
key?: string;
|
|
40
|
+
description: string;
|
|
41
|
+
}
|
|
42
|
+
/** Subscribed response — connection handshake */
|
|
43
|
+
interface LeaderSubscribedMessage {
|
|
44
|
+
type: 'subscribed';
|
|
45
|
+
data: {
|
|
46
|
+
channels: string[];
|
|
47
|
+
currentSlot: number;
|
|
48
|
+
epoch: number;
|
|
49
|
+
schema: MessageSchemaEntry[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Full epoch leader schedule (snapshot — replaces previous) */
|
|
53
|
+
interface LeaderScheduleMessage {
|
|
54
|
+
type: 'leader_schedule';
|
|
55
|
+
kind: 'snapshot';
|
|
56
|
+
data: {
|
|
57
|
+
epoch: number;
|
|
58
|
+
slotsInEpoch: number;
|
|
59
|
+
validators: number;
|
|
60
|
+
schedule: Array<{
|
|
61
|
+
identity: string;
|
|
62
|
+
slots: number;
|
|
63
|
+
slotIndices: number[];
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Gossip peer data */
|
|
68
|
+
interface GossipPeer {
|
|
69
|
+
identity: string;
|
|
70
|
+
tpuQuic: string | null;
|
|
71
|
+
tpuUdp: string | null;
|
|
72
|
+
tpuForwardsQuic: string | null;
|
|
73
|
+
tpuForwardsUdp: string | null;
|
|
74
|
+
tpuVote: string | null;
|
|
75
|
+
tpuVoteQuic: string | null;
|
|
76
|
+
gossipAddr: string | null;
|
|
77
|
+
shredVersion: number;
|
|
78
|
+
version: string;
|
|
79
|
+
activatedStake: number;
|
|
80
|
+
commission: number;
|
|
81
|
+
isDelinquent: boolean;
|
|
82
|
+
votePubkey: string;
|
|
83
|
+
lastVote: number;
|
|
84
|
+
rootSlot: number;
|
|
85
|
+
wallclock: number;
|
|
86
|
+
}
|
|
87
|
+
/** Full gossip peer list (snapshot — apply gossip_diff to keep current) */
|
|
88
|
+
interface GossipSnapshotMessage {
|
|
89
|
+
type: 'gossip_snapshot';
|
|
90
|
+
kind: 'snapshot';
|
|
91
|
+
key: 'identity';
|
|
92
|
+
data: {
|
|
93
|
+
timestampMs: number;
|
|
94
|
+
count: number;
|
|
95
|
+
peers: GossipPeer[];
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Incremental gossip changes (diff — merge into snapshot using identity) */
|
|
99
|
+
interface GossipDiffMessage {
|
|
100
|
+
type: 'gossip_diff';
|
|
101
|
+
kind: 'diff';
|
|
102
|
+
key: 'identity';
|
|
103
|
+
data: {
|
|
104
|
+
timestampMs: number;
|
|
105
|
+
added: GossipPeer[];
|
|
106
|
+
removed: string[];
|
|
107
|
+
updated: GossipPeer[];
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Current slot with leader identity (snapshot — each replaces previous) */
|
|
111
|
+
interface SlotUpdateMessage {
|
|
112
|
+
type: 'slot_update';
|
|
113
|
+
kind: 'snapshot';
|
|
114
|
+
data: {
|
|
115
|
+
slot: number;
|
|
116
|
+
leader: string;
|
|
117
|
+
blockHeight: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Routing health summary (snapshot — each replaces previous) */
|
|
121
|
+
interface RoutingHealthMessage {
|
|
122
|
+
type: 'routing_health';
|
|
123
|
+
kind: 'snapshot';
|
|
124
|
+
data: {
|
|
125
|
+
leadersTotal: number;
|
|
126
|
+
leadersInGossip: number;
|
|
127
|
+
leadersMissingGossip: string[];
|
|
128
|
+
leadersWithoutTpuQuic: string[];
|
|
129
|
+
leadersDelinquent: string[];
|
|
130
|
+
coverage: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Block production stats per validator (event — cumulative) */
|
|
134
|
+
interface SkipEventMessage {
|
|
135
|
+
type: 'skip_event';
|
|
136
|
+
kind: 'event';
|
|
137
|
+
key: 'leader';
|
|
138
|
+
data: {
|
|
139
|
+
slot: number;
|
|
140
|
+
leader: string;
|
|
141
|
+
assigned: number;
|
|
142
|
+
produced: number;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Validator IP address change (event) */
|
|
146
|
+
interface IpChangeMessage {
|
|
147
|
+
type: 'ip_change';
|
|
148
|
+
kind: 'event';
|
|
149
|
+
key: 'identity';
|
|
150
|
+
data: {
|
|
151
|
+
identity: string;
|
|
152
|
+
oldIp: string;
|
|
153
|
+
newIp: string;
|
|
154
|
+
timestampMs: number;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Server heartbeat (snapshot — each replaces previous) */
|
|
158
|
+
interface LeaderHeartbeatMessage {
|
|
159
|
+
type: 'heartbeat';
|
|
160
|
+
kind: 'snapshot';
|
|
161
|
+
data: {
|
|
162
|
+
timestampMs: number;
|
|
163
|
+
currentSlot: number;
|
|
164
|
+
connectedClients: number;
|
|
165
|
+
gossipPeers: number;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Error from server */
|
|
169
|
+
interface LeaderErrorMessage {
|
|
170
|
+
type: 'error';
|
|
171
|
+
data: {
|
|
172
|
+
message: string;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Union of all leader-schedule message types */
|
|
176
|
+
type LeaderDecodedMessage = LeaderSubscribedMessage | LeaderScheduleMessage | GossipSnapshotMessage | GossipDiffMessage | SlotUpdateMessage | RoutingHealthMessage | SkipEventMessage | IpChangeMessage | LeaderHeartbeatMessage | LeaderErrorMessage;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Leader Schedule WebSocket Client
|
|
180
|
+
*
|
|
181
|
+
* Connects to the K256 leader-schedule service via the Gateway.
|
|
182
|
+
* Binary mode by default (wincode protocol, matching K2 pattern).
|
|
183
|
+
* JSON mode opt-in via mode: 'json' (gateway decodes wincode to JSON).
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* import { LeaderWebSocketClient } from '@k256/sdk/leader-ws';
|
|
188
|
+
*
|
|
189
|
+
* const client = new LeaderWebSocketClient({
|
|
190
|
+
* apiKey: 'your-api-key',
|
|
191
|
+
* onSlotUpdate: (msg) => console.log('Slot:', msg.data.slot, 'Leader:', msg.data.leader),
|
|
192
|
+
* onRoutingHealth: (msg) => console.log('Coverage:', msg.data.coverage),
|
|
193
|
+
* onGossipDiff: (msg) => console.log('Peers changed:', msg.data.added.length, 'added'),
|
|
194
|
+
* });
|
|
195
|
+
*
|
|
196
|
+
* await client.connect();
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Connection state
|
|
202
|
+
*/
|
|
203
|
+
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
|
|
204
|
+
/**
|
|
205
|
+
* Leader WebSocket client configuration
|
|
206
|
+
*/
|
|
207
|
+
interface LeaderWebSocketClientConfig {
|
|
208
|
+
/** API key for authentication */
|
|
209
|
+
apiKey: string;
|
|
210
|
+
/** Gateway URL (default: wss://gateway.k256.xyz/v1/leader-ws) */
|
|
211
|
+
url?: string;
|
|
212
|
+
/** Message format: 'binary' (default, efficient) or 'json' (debugging via gateway) */
|
|
213
|
+
mode?: 'binary' | 'json';
|
|
214
|
+
/** Channels to subscribe to (default: all channels) */
|
|
215
|
+
channels?: LeaderChannelValue[];
|
|
216
|
+
/** Enable automatic reconnection (default: true) */
|
|
217
|
+
autoReconnect?: boolean;
|
|
218
|
+
/** Initial reconnect delay in ms (default: 1000) */
|
|
219
|
+
reconnectDelayMs?: number;
|
|
220
|
+
/** Max reconnect delay in ms (default: 30000) */
|
|
221
|
+
maxReconnectDelayMs?: number;
|
|
222
|
+
/** Max reconnect attempts (default: Infinity) */
|
|
223
|
+
maxReconnectAttempts?: number;
|
|
224
|
+
/** Called when connection state changes */
|
|
225
|
+
onStateChange?: (state: ConnectionState, prevState: ConnectionState) => void;
|
|
226
|
+
/** Called on successful connection */
|
|
227
|
+
onConnect?: () => void;
|
|
228
|
+
/** Called on disconnection */
|
|
229
|
+
onDisconnect?: (code: number, reason: string, wasClean: boolean) => void;
|
|
230
|
+
/** Called on reconnection attempt */
|
|
231
|
+
onReconnecting?: (attempt: number, delayMs: number) => void;
|
|
232
|
+
/** Called on any error */
|
|
233
|
+
onError?: (error: LeaderWebSocketError) => void;
|
|
234
|
+
/** Called on subscription confirmed (includes protocol schema) */
|
|
235
|
+
onSubscribed?: (msg: LeaderSubscribedMessage) => void;
|
|
236
|
+
/** Called on full leader schedule (snapshot — replaces previous) */
|
|
237
|
+
onLeaderSchedule?: (msg: LeaderScheduleMessage) => void;
|
|
238
|
+
/** Called on full gossip peer snapshot (snapshot — key: identity) */
|
|
239
|
+
onGossipSnapshot?: (msg: GossipSnapshotMessage) => void;
|
|
240
|
+
/** Called on gossip diff (diff — merge into snapshot using identity) */
|
|
241
|
+
onGossipDiff?: (msg: GossipDiffMessage) => void;
|
|
242
|
+
/** Called on slot update (snapshot — each replaces previous) */
|
|
243
|
+
onSlotUpdate?: (msg: SlotUpdateMessage) => void;
|
|
244
|
+
/** Called on routing health (snapshot — each replaces previous) */
|
|
245
|
+
onRoutingHealth?: (msg: RoutingHealthMessage) => void;
|
|
246
|
+
/** Called on skip event (event — block production stats) */
|
|
247
|
+
onSkipEvent?: (msg: SkipEventMessage) => void;
|
|
248
|
+
/** Called on IP change event */
|
|
249
|
+
onIpChange?: (msg: IpChangeMessage) => void;
|
|
250
|
+
/** Called on heartbeat (every 10s) */
|
|
251
|
+
onHeartbeat?: (msg: LeaderHeartbeatMessage) => void;
|
|
252
|
+
/** Called on any message (raw) */
|
|
253
|
+
onMessage?: (msg: LeaderDecodedMessage) => void;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Error codes for leader WebSocket
|
|
257
|
+
*/
|
|
258
|
+
type LeaderErrorCode = 'CONNECTION_FAILED' | 'CONNECTION_LOST' | 'AUTH_FAILED' | 'SERVER_ERROR' | 'INVALID_MESSAGE' | 'RECONNECT_FAILED';
|
|
259
|
+
/**
|
|
260
|
+
* WebSocket error with context
|
|
261
|
+
*/
|
|
262
|
+
declare class LeaderWebSocketError extends Error {
|
|
263
|
+
readonly code: LeaderErrorCode;
|
|
264
|
+
readonly closeCode?: number | undefined;
|
|
265
|
+
readonly closeReason?: string | undefined;
|
|
266
|
+
constructor(code: LeaderErrorCode, message: string, closeCode?: number | undefined, closeReason?: string | undefined);
|
|
267
|
+
get isRecoverable(): boolean;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Leader Schedule WebSocket Client
|
|
271
|
+
*
|
|
272
|
+
* Connects to the leader-schedule service via the Gateway.
|
|
273
|
+
* Binary mode by default (wincode protocol). JSON mode opt-in via gateway.
|
|
274
|
+
* Automatically subscribes to configured channels on connect/reconnect.
|
|
275
|
+
*/
|
|
276
|
+
declare class LeaderWebSocketClient {
|
|
277
|
+
private ws;
|
|
278
|
+
private readonly config;
|
|
279
|
+
private _state;
|
|
280
|
+
private reconnectAttempts;
|
|
281
|
+
private reconnectTimer;
|
|
282
|
+
private isIntentionallyClosed;
|
|
283
|
+
/** Current connection state */
|
|
284
|
+
get state(): ConnectionState;
|
|
285
|
+
/** Whether currently connected */
|
|
286
|
+
get isConnected(): boolean;
|
|
287
|
+
constructor(config: LeaderWebSocketClientConfig);
|
|
288
|
+
/**
|
|
289
|
+
* Connect to the leader-schedule WebSocket
|
|
290
|
+
*/
|
|
291
|
+
connect(): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Disconnect from the WebSocket
|
|
294
|
+
*/
|
|
295
|
+
disconnect(): void;
|
|
296
|
+
/** Handle JSON text frame (from gateway JSON mode) */
|
|
297
|
+
private handleJsonMessage;
|
|
298
|
+
/** Dispatch a decoded message to typed callbacks */
|
|
299
|
+
private dispatchMessage;
|
|
300
|
+
private setState;
|
|
301
|
+
private scheduleReconnect;
|
|
302
|
+
private clearTimers;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Binary message decoder for Leader Schedule WebSocket protocol
|
|
307
|
+
*
|
|
308
|
+
* Decodes wincode messages from the leader-schedule server into typed objects.
|
|
309
|
+
* Matches K2 decoder pattern: manual DataView offset walking, little-endian.
|
|
310
|
+
*
|
|
311
|
+
* Wire format: [1 byte MessageType][N bytes wincode payload]
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
/** Message type tag constants (must match leader-schedule server protocol.rs) */
|
|
315
|
+
declare const LeaderMessageTag: {
|
|
316
|
+
readonly Subscribe: 1;
|
|
317
|
+
readonly Subscribed: 2;
|
|
318
|
+
readonly LeaderSchedule: 16;
|
|
319
|
+
readonly GossipSnapshot: 17;
|
|
320
|
+
readonly GossipDiff: 18;
|
|
321
|
+
readonly SlotUpdate: 19;
|
|
322
|
+
readonly RoutingHealth: 20;
|
|
323
|
+
readonly SkipEvent: 21;
|
|
324
|
+
readonly IpChange: 22;
|
|
325
|
+
readonly Heartbeat: 253;
|
|
326
|
+
readonly Ping: 254;
|
|
327
|
+
readonly Error: 255;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Decode a binary WebSocket message from the leader-schedule server.
|
|
331
|
+
*
|
|
332
|
+
* @param data - Raw binary data from WebSocket
|
|
333
|
+
* @returns Decoded message or null if unrecognized type
|
|
334
|
+
*/
|
|
335
|
+
declare function decodeLeaderMessage(data: ArrayBuffer): LeaderDecodedMessage | null;
|
|
336
|
+
|
|
337
|
+
export { ALL_LEADER_CHANNELS, type ConnectionState, type GossipDiffMessage, type GossipPeer, type GossipSnapshotMessage, type IpChangeMessage, LeaderChannel, type LeaderChannelValue, type LeaderDecodedMessage, type LeaderErrorCode, type LeaderErrorMessage, type LeaderHeartbeatMessage, LeaderMessageTag, type LeaderScheduleMessage, type LeaderSubscribedMessage, LeaderWebSocketClient, type LeaderWebSocketClientConfig, LeaderWebSocketError, type MessageKind, type MessageSchemaEntry, type RoutingHealthMessage, type SkipEventMessage, type SlotUpdateMessage, decodeLeaderMessage };
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leader Schedule WebSocket message types and interfaces
|
|
3
|
+
*
|
|
4
|
+
* The leader-schedule WS uses wincode binary protocol (matching K2 pattern).
|
|
5
|
+
* Wire format: [1-byte tag][wincode payload]
|
|
6
|
+
* Every decoded message has:
|
|
7
|
+
* - type: message type name
|
|
8
|
+
* - kind: "snapshot" (full state) | "diff" (merge into snapshot) | "event" (append-only)
|
|
9
|
+
* - key: primary key field for merging (on diff/event types)
|
|
10
|
+
* - data: typed payload
|
|
11
|
+
*
|
|
12
|
+
* @see https://k256.xyz/docs/leader-schedule
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Leader Schedule WebSocket channels
|
|
16
|
+
*/
|
|
17
|
+
declare const LeaderChannel: {
|
|
18
|
+
/** Full epoch leader schedule (on connect + epoch change) */
|
|
19
|
+
readonly LeaderSchedule: "leader_schedule";
|
|
20
|
+
/** Gossip peers (snapshot on connect, then diffs) */
|
|
21
|
+
readonly Gossip: "gossip";
|
|
22
|
+
/** Real-time slot updates with current leader */
|
|
23
|
+
readonly Slots: "slots";
|
|
24
|
+
/** Skip events, IP changes, routing health */
|
|
25
|
+
readonly Alerts: "alerts";
|
|
26
|
+
};
|
|
27
|
+
type LeaderChannelValue = typeof LeaderChannel[keyof typeof LeaderChannel];
|
|
28
|
+
/** All available channels */
|
|
29
|
+
declare const ALL_LEADER_CHANNELS: LeaderChannelValue[];
|
|
30
|
+
/**
|
|
31
|
+
* Message kind — tells you how to consume the message
|
|
32
|
+
*/
|
|
33
|
+
type MessageKind = 'snapshot' | 'diff' | 'event';
|
|
34
|
+
/** Protocol schema entry (included in subscribed handshake) */
|
|
35
|
+
interface MessageSchemaEntry {
|
|
36
|
+
type: string;
|
|
37
|
+
tag: string;
|
|
38
|
+
kind: MessageKind;
|
|
39
|
+
key?: string;
|
|
40
|
+
description: string;
|
|
41
|
+
}
|
|
42
|
+
/** Subscribed response — connection handshake */
|
|
43
|
+
interface LeaderSubscribedMessage {
|
|
44
|
+
type: 'subscribed';
|
|
45
|
+
data: {
|
|
46
|
+
channels: string[];
|
|
47
|
+
currentSlot: number;
|
|
48
|
+
epoch: number;
|
|
49
|
+
schema: MessageSchemaEntry[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Full epoch leader schedule (snapshot — replaces previous) */
|
|
53
|
+
interface LeaderScheduleMessage {
|
|
54
|
+
type: 'leader_schedule';
|
|
55
|
+
kind: 'snapshot';
|
|
56
|
+
data: {
|
|
57
|
+
epoch: number;
|
|
58
|
+
slotsInEpoch: number;
|
|
59
|
+
validators: number;
|
|
60
|
+
schedule: Array<{
|
|
61
|
+
identity: string;
|
|
62
|
+
slots: number;
|
|
63
|
+
slotIndices: number[];
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Gossip peer data */
|
|
68
|
+
interface GossipPeer {
|
|
69
|
+
identity: string;
|
|
70
|
+
tpuQuic: string | null;
|
|
71
|
+
tpuUdp: string | null;
|
|
72
|
+
tpuForwardsQuic: string | null;
|
|
73
|
+
tpuForwardsUdp: string | null;
|
|
74
|
+
tpuVote: string | null;
|
|
75
|
+
tpuVoteQuic: string | null;
|
|
76
|
+
gossipAddr: string | null;
|
|
77
|
+
shredVersion: number;
|
|
78
|
+
version: string;
|
|
79
|
+
activatedStake: number;
|
|
80
|
+
commission: number;
|
|
81
|
+
isDelinquent: boolean;
|
|
82
|
+
votePubkey: string;
|
|
83
|
+
lastVote: number;
|
|
84
|
+
rootSlot: number;
|
|
85
|
+
wallclock: number;
|
|
86
|
+
}
|
|
87
|
+
/** Full gossip peer list (snapshot — apply gossip_diff to keep current) */
|
|
88
|
+
interface GossipSnapshotMessage {
|
|
89
|
+
type: 'gossip_snapshot';
|
|
90
|
+
kind: 'snapshot';
|
|
91
|
+
key: 'identity';
|
|
92
|
+
data: {
|
|
93
|
+
timestampMs: number;
|
|
94
|
+
count: number;
|
|
95
|
+
peers: GossipPeer[];
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Incremental gossip changes (diff — merge into snapshot using identity) */
|
|
99
|
+
interface GossipDiffMessage {
|
|
100
|
+
type: 'gossip_diff';
|
|
101
|
+
kind: 'diff';
|
|
102
|
+
key: 'identity';
|
|
103
|
+
data: {
|
|
104
|
+
timestampMs: number;
|
|
105
|
+
added: GossipPeer[];
|
|
106
|
+
removed: string[];
|
|
107
|
+
updated: GossipPeer[];
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Current slot with leader identity (snapshot — each replaces previous) */
|
|
111
|
+
interface SlotUpdateMessage {
|
|
112
|
+
type: 'slot_update';
|
|
113
|
+
kind: 'snapshot';
|
|
114
|
+
data: {
|
|
115
|
+
slot: number;
|
|
116
|
+
leader: string;
|
|
117
|
+
blockHeight: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Routing health summary (snapshot — each replaces previous) */
|
|
121
|
+
interface RoutingHealthMessage {
|
|
122
|
+
type: 'routing_health';
|
|
123
|
+
kind: 'snapshot';
|
|
124
|
+
data: {
|
|
125
|
+
leadersTotal: number;
|
|
126
|
+
leadersInGossip: number;
|
|
127
|
+
leadersMissingGossip: string[];
|
|
128
|
+
leadersWithoutTpuQuic: string[];
|
|
129
|
+
leadersDelinquent: string[];
|
|
130
|
+
coverage: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Block production stats per validator (event — cumulative) */
|
|
134
|
+
interface SkipEventMessage {
|
|
135
|
+
type: 'skip_event';
|
|
136
|
+
kind: 'event';
|
|
137
|
+
key: 'leader';
|
|
138
|
+
data: {
|
|
139
|
+
slot: number;
|
|
140
|
+
leader: string;
|
|
141
|
+
assigned: number;
|
|
142
|
+
produced: number;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Validator IP address change (event) */
|
|
146
|
+
interface IpChangeMessage {
|
|
147
|
+
type: 'ip_change';
|
|
148
|
+
kind: 'event';
|
|
149
|
+
key: 'identity';
|
|
150
|
+
data: {
|
|
151
|
+
identity: string;
|
|
152
|
+
oldIp: string;
|
|
153
|
+
newIp: string;
|
|
154
|
+
timestampMs: number;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Server heartbeat (snapshot — each replaces previous) */
|
|
158
|
+
interface LeaderHeartbeatMessage {
|
|
159
|
+
type: 'heartbeat';
|
|
160
|
+
kind: 'snapshot';
|
|
161
|
+
data: {
|
|
162
|
+
timestampMs: number;
|
|
163
|
+
currentSlot: number;
|
|
164
|
+
connectedClients: number;
|
|
165
|
+
gossipPeers: number;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Error from server */
|
|
169
|
+
interface LeaderErrorMessage {
|
|
170
|
+
type: 'error';
|
|
171
|
+
data: {
|
|
172
|
+
message: string;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Union of all leader-schedule message types */
|
|
176
|
+
type LeaderDecodedMessage = LeaderSubscribedMessage | LeaderScheduleMessage | GossipSnapshotMessage | GossipDiffMessage | SlotUpdateMessage | RoutingHealthMessage | SkipEventMessage | IpChangeMessage | LeaderHeartbeatMessage | LeaderErrorMessage;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Leader Schedule WebSocket Client
|
|
180
|
+
*
|
|
181
|
+
* Connects to the K256 leader-schedule service via the Gateway.
|
|
182
|
+
* Binary mode by default (wincode protocol, matching K2 pattern).
|
|
183
|
+
* JSON mode opt-in via mode: 'json' (gateway decodes wincode to JSON).
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* import { LeaderWebSocketClient } from '@k256/sdk/leader-ws';
|
|
188
|
+
*
|
|
189
|
+
* const client = new LeaderWebSocketClient({
|
|
190
|
+
* apiKey: 'your-api-key',
|
|
191
|
+
* onSlotUpdate: (msg) => console.log('Slot:', msg.data.slot, 'Leader:', msg.data.leader),
|
|
192
|
+
* onRoutingHealth: (msg) => console.log('Coverage:', msg.data.coverage),
|
|
193
|
+
* onGossipDiff: (msg) => console.log('Peers changed:', msg.data.added.length, 'added'),
|
|
194
|
+
* });
|
|
195
|
+
*
|
|
196
|
+
* await client.connect();
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Connection state
|
|
202
|
+
*/
|
|
203
|
+
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
|
|
204
|
+
/**
|
|
205
|
+
* Leader WebSocket client configuration
|
|
206
|
+
*/
|
|
207
|
+
interface LeaderWebSocketClientConfig {
|
|
208
|
+
/** API key for authentication */
|
|
209
|
+
apiKey: string;
|
|
210
|
+
/** Gateway URL (default: wss://gateway.k256.xyz/v1/leader-ws) */
|
|
211
|
+
url?: string;
|
|
212
|
+
/** Message format: 'binary' (default, efficient) or 'json' (debugging via gateway) */
|
|
213
|
+
mode?: 'binary' | 'json';
|
|
214
|
+
/** Channels to subscribe to (default: all channels) */
|
|
215
|
+
channels?: LeaderChannelValue[];
|
|
216
|
+
/** Enable automatic reconnection (default: true) */
|
|
217
|
+
autoReconnect?: boolean;
|
|
218
|
+
/** Initial reconnect delay in ms (default: 1000) */
|
|
219
|
+
reconnectDelayMs?: number;
|
|
220
|
+
/** Max reconnect delay in ms (default: 30000) */
|
|
221
|
+
maxReconnectDelayMs?: number;
|
|
222
|
+
/** Max reconnect attempts (default: Infinity) */
|
|
223
|
+
maxReconnectAttempts?: number;
|
|
224
|
+
/** Called when connection state changes */
|
|
225
|
+
onStateChange?: (state: ConnectionState, prevState: ConnectionState) => void;
|
|
226
|
+
/** Called on successful connection */
|
|
227
|
+
onConnect?: () => void;
|
|
228
|
+
/** Called on disconnection */
|
|
229
|
+
onDisconnect?: (code: number, reason: string, wasClean: boolean) => void;
|
|
230
|
+
/** Called on reconnection attempt */
|
|
231
|
+
onReconnecting?: (attempt: number, delayMs: number) => void;
|
|
232
|
+
/** Called on any error */
|
|
233
|
+
onError?: (error: LeaderWebSocketError) => void;
|
|
234
|
+
/** Called on subscription confirmed (includes protocol schema) */
|
|
235
|
+
onSubscribed?: (msg: LeaderSubscribedMessage) => void;
|
|
236
|
+
/** Called on full leader schedule (snapshot — replaces previous) */
|
|
237
|
+
onLeaderSchedule?: (msg: LeaderScheduleMessage) => void;
|
|
238
|
+
/** Called on full gossip peer snapshot (snapshot — key: identity) */
|
|
239
|
+
onGossipSnapshot?: (msg: GossipSnapshotMessage) => void;
|
|
240
|
+
/** Called on gossip diff (diff — merge into snapshot using identity) */
|
|
241
|
+
onGossipDiff?: (msg: GossipDiffMessage) => void;
|
|
242
|
+
/** Called on slot update (snapshot — each replaces previous) */
|
|
243
|
+
onSlotUpdate?: (msg: SlotUpdateMessage) => void;
|
|
244
|
+
/** Called on routing health (snapshot — each replaces previous) */
|
|
245
|
+
onRoutingHealth?: (msg: RoutingHealthMessage) => void;
|
|
246
|
+
/** Called on skip event (event — block production stats) */
|
|
247
|
+
onSkipEvent?: (msg: SkipEventMessage) => void;
|
|
248
|
+
/** Called on IP change event */
|
|
249
|
+
onIpChange?: (msg: IpChangeMessage) => void;
|
|
250
|
+
/** Called on heartbeat (every 10s) */
|
|
251
|
+
onHeartbeat?: (msg: LeaderHeartbeatMessage) => void;
|
|
252
|
+
/** Called on any message (raw) */
|
|
253
|
+
onMessage?: (msg: LeaderDecodedMessage) => void;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Error codes for leader WebSocket
|
|
257
|
+
*/
|
|
258
|
+
type LeaderErrorCode = 'CONNECTION_FAILED' | 'CONNECTION_LOST' | 'AUTH_FAILED' | 'SERVER_ERROR' | 'INVALID_MESSAGE' | 'RECONNECT_FAILED';
|
|
259
|
+
/**
|
|
260
|
+
* WebSocket error with context
|
|
261
|
+
*/
|
|
262
|
+
declare class LeaderWebSocketError extends Error {
|
|
263
|
+
readonly code: LeaderErrorCode;
|
|
264
|
+
readonly closeCode?: number | undefined;
|
|
265
|
+
readonly closeReason?: string | undefined;
|
|
266
|
+
constructor(code: LeaderErrorCode, message: string, closeCode?: number | undefined, closeReason?: string | undefined);
|
|
267
|
+
get isRecoverable(): boolean;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Leader Schedule WebSocket Client
|
|
271
|
+
*
|
|
272
|
+
* Connects to the leader-schedule service via the Gateway.
|
|
273
|
+
* Binary mode by default (wincode protocol). JSON mode opt-in via gateway.
|
|
274
|
+
* Automatically subscribes to configured channels on connect/reconnect.
|
|
275
|
+
*/
|
|
276
|
+
declare class LeaderWebSocketClient {
|
|
277
|
+
private ws;
|
|
278
|
+
private readonly config;
|
|
279
|
+
private _state;
|
|
280
|
+
private reconnectAttempts;
|
|
281
|
+
private reconnectTimer;
|
|
282
|
+
private isIntentionallyClosed;
|
|
283
|
+
/** Current connection state */
|
|
284
|
+
get state(): ConnectionState;
|
|
285
|
+
/** Whether currently connected */
|
|
286
|
+
get isConnected(): boolean;
|
|
287
|
+
constructor(config: LeaderWebSocketClientConfig);
|
|
288
|
+
/**
|
|
289
|
+
* Connect to the leader-schedule WebSocket
|
|
290
|
+
*/
|
|
291
|
+
connect(): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Disconnect from the WebSocket
|
|
294
|
+
*/
|
|
295
|
+
disconnect(): void;
|
|
296
|
+
/** Handle JSON text frame (from gateway JSON mode) */
|
|
297
|
+
private handleJsonMessage;
|
|
298
|
+
/** Dispatch a decoded message to typed callbacks */
|
|
299
|
+
private dispatchMessage;
|
|
300
|
+
private setState;
|
|
301
|
+
private scheduleReconnect;
|
|
302
|
+
private clearTimers;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Binary message decoder for Leader Schedule WebSocket protocol
|
|
307
|
+
*
|
|
308
|
+
* Decodes wincode messages from the leader-schedule server into typed objects.
|
|
309
|
+
* Matches K2 decoder pattern: manual DataView offset walking, little-endian.
|
|
310
|
+
*
|
|
311
|
+
* Wire format: [1 byte MessageType][N bytes wincode payload]
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
/** Message type tag constants (must match leader-schedule server protocol.rs) */
|
|
315
|
+
declare const LeaderMessageTag: {
|
|
316
|
+
readonly Subscribe: 1;
|
|
317
|
+
readonly Subscribed: 2;
|
|
318
|
+
readonly LeaderSchedule: 16;
|
|
319
|
+
readonly GossipSnapshot: 17;
|
|
320
|
+
readonly GossipDiff: 18;
|
|
321
|
+
readonly SlotUpdate: 19;
|
|
322
|
+
readonly RoutingHealth: 20;
|
|
323
|
+
readonly SkipEvent: 21;
|
|
324
|
+
readonly IpChange: 22;
|
|
325
|
+
readonly Heartbeat: 253;
|
|
326
|
+
readonly Ping: 254;
|
|
327
|
+
readonly Error: 255;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Decode a binary WebSocket message from the leader-schedule server.
|
|
331
|
+
*
|
|
332
|
+
* @param data - Raw binary data from WebSocket
|
|
333
|
+
* @returns Decoded message or null if unrecognized type
|
|
334
|
+
*/
|
|
335
|
+
declare function decodeLeaderMessage(data: ArrayBuffer): LeaderDecodedMessage | null;
|
|
336
|
+
|
|
337
|
+
export { ALL_LEADER_CHANNELS, type ConnectionState, type GossipDiffMessage, type GossipPeer, type GossipSnapshotMessage, type IpChangeMessage, LeaderChannel, type LeaderChannelValue, type LeaderDecodedMessage, type LeaderErrorCode, type LeaderErrorMessage, type LeaderHeartbeatMessage, LeaderMessageTag, type LeaderScheduleMessage, type LeaderSubscribedMessage, LeaderWebSocketClient, type LeaderWebSocketClientConfig, LeaderWebSocketError, type MessageKind, type MessageSchemaEntry, type RoutingHealthMessage, type SkipEventMessage, type SlotUpdateMessage, decodeLeaderMessage };
|