@k256/sdk 0.1.4 → 0.1.7

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.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Shared type definitions for K256 SDK
3
- *
3
+ *
4
4
  * These types are used across WebSocket and REST API modules.
5
- *
5
+ *
6
6
  * @module @k256/sdk/types
7
7
  */
8
8
 
@@ -20,8 +20,6 @@ export interface Pool {
20
20
  tokenBalances: string[];
21
21
  /** Token decimals */
22
22
  tokenDecimals: number[];
23
- /** Whether pool is valid for trading */
24
- isValid: boolean;
25
23
  }
26
24
 
27
25
  /**
@@ -109,24 +107,59 @@ export interface RoutePlanStep {
109
107
  }
110
108
 
111
109
  /**
112
- * Priority fee estimates
110
+ * Per-writable-account fee data
111
+ *
112
+ * Solana's scheduler limits each writable account to 12M CU per block.
113
+ * Fee pricing is per-account: the fee you pay should be
114
+ * max(p75(account) for account in your writable accounts).
113
115
  */
114
- export interface PriorityFees {
116
+ export interface AccountFee {
117
+ /** Account public key (Base58) */
118
+ pubkey: string;
119
+ /** Total transactions touching this account in the window */
120
+ totalTxs: number;
121
+ /** Number of slots where this account was active */
122
+ activeSlots: number;
123
+ /** Total CU consumed by transactions touching this account */
124
+ cuConsumed: number;
125
+ /** Account utilization percentage (0-100) of 12M CU limit */
126
+ utilizationPct: number;
127
+ /** 25th percentile fee in microlamports/CU */
128
+ p25: number;
129
+ /** 50th percentile fee in microlamports/CU */
130
+ p50: number;
131
+ /** 75th percentile fee in microlamports/CU */
132
+ p75: number;
133
+ /** 90th percentile fee in microlamports/CU */
134
+ p90: number;
135
+ /** Minimum non-zero fee observed */
136
+ minNonzeroPrice: number;
137
+ }
138
+
139
+ /**
140
+ * Fee market update (per-writable-account model)
141
+ *
142
+ * Replaces the old flat PriorityFees struct. Now provides per-account
143
+ * fee data so clients can price transactions based on the specific
144
+ * writable accounts they touch.
145
+ */
146
+ export interface FeeMarket {
115
147
  /** Current slot */
116
148
  slot: number;
117
149
  /** Timestamp in milliseconds */
118
150
  timestampMs: number;
119
- /** Recommended fee in microlamports */
151
+ /** Recommended fee in microlamports/CU (max p75 across hottest accounts) */
120
152
  recommended: number;
121
- /** Network state (0=low, 1=normal, 2=high, 3=congested) */
153
+ /** Network state (0=low, 1=normal, 2=high, 3=extreme) */
122
154
  state: NetworkState;
123
155
  /** Whether data is stale */
124
156
  isStale: boolean;
125
- /** Swap fee percentiles */
126
- swapP50: number;
127
- swapP75: number;
128
- swapP90: number;
129
- swapP99: number;
157
+ /** Block utilization percentage (0-100) */
158
+ blockUtilizationPct: number;
159
+ /** Number of blocks in the observation window */
160
+ blocksInWindow: number;
161
+ /** Per-account fee data */
162
+ accounts: AccountFee[];
130
163
  }
131
164
 
132
165
  /**
@@ -58,9 +58,19 @@ export function base58Encode(bytes: Uint8Array): string {
58
58
  * ```
59
59
  */
60
60
  export function base58Decode(str: string): Uint8Array {
61
+ if (str.length === 0) {
62
+ return new Uint8Array(0);
63
+ }
64
+
65
+ // Count leading '1's (they represent leading zero bytes)
66
+ let leadingZeros = 0;
67
+ for (let i = 0; i < str.length && str[i] === '1'; i++) {
68
+ leadingZeros++;
69
+ }
70
+
71
+ // Process remaining characters through base conversion
61
72
  const bytes = [0];
62
-
63
- for (let i = 0; i < str.length; i++) {
73
+ for (let i = leadingZeros; i < str.length; i++) {
64
74
  const char = str[i];
65
75
  const value = BASE58_ALPHABET.indexOf(char);
66
76
 
@@ -80,12 +90,30 @@ export function base58Decode(str: string): Uint8Array {
80
90
  }
81
91
  }
82
92
 
83
- // Leading '1's are leading zeros
84
- for (let i = 0; i < str.length && str[i] === '1'; i++) {
85
- bytes.push(0);
93
+ // Build result: leading zeros + converted bytes (reversed)
94
+ bytes.reverse();
95
+
96
+ // Handle case where input was all '1's (all leading zeros, no data to convert)
97
+ // In this case, bytes is just [0] from initialization, which we should ignore
98
+ if (leadingZeros > 0 && bytes.length === 1 && bytes[0] === 0) {
99
+ // Input was all '1's, return that many zero bytes
100
+ return new Uint8Array(leadingZeros);
86
101
  }
87
-
88
- return new Uint8Array(bytes.reverse());
102
+
103
+ // Remove leading zero from conversion if present (artifact of starting with [0])
104
+ const startIdx = bytes.length > 1 && bytes[0] === 0 ? 1 : 0;
105
+
106
+ const result = new Uint8Array(leadingZeros + bytes.length - startIdx);
107
+ // Fill leading zeros
108
+ for (let i = 0; i < leadingZeros; i++) {
109
+ result[i] = 0;
110
+ }
111
+ // Copy converted bytes
112
+ for (let i = startIdx; i < bytes.length; i++) {
113
+ result[leadingZeros + i - startIdx] = bytes[i];
114
+ }
115
+
116
+ return result;
89
117
  }
90
118
 
91
119
  /**
package/src/ws/client.ts CHANGED
@@ -83,7 +83,7 @@ export interface SubscribeOptions {
83
83
  channels: string[];
84
84
  /** Pool address filters (optional) */
85
85
  pools?: string[];
86
- /** Protocol filters (optional): 'Raydium AMM', 'Orca Whirlpool', etc. */
86
+ /** Protocol filters (optional): 'RaydiumAmm', 'Whirlpool', 'MeteoraDlmm', etc. */
87
87
  protocols?: string[];
88
88
  /** Token pair filters (optional): [['mint1', 'mint2'], ...] */
89
89
  tokenPairs?: string[][];
@@ -153,8 +153,8 @@ export interface K256WebSocketClientConfig {
153
153
  onPoolUpdate?: (update: PoolUpdateMessage) => void;
154
154
  /** Called on batched pool updates (for efficiency) */
155
155
  onPoolUpdateBatch?: (updates: PoolUpdateMessage[]) => void;
156
- /** Called on priority fees update */
157
- onPriorityFees?: (data: DecodedMessage & { type: 'priority_fees' }) => void;
156
+ /** Called on fee market update (per-writable-account fees) */
157
+ onFeeMarket?: (data: DecodedMessage & { type: 'fee_market' }) => void;
158
158
  /** Called on blockhash update */
159
159
  onBlockhash?: (data: DecodedMessage & { type: 'blockhash' }) => void;
160
160
  /** Called on quote update */
@@ -225,7 +225,7 @@ export class K256WebSocketClient {
225
225
  private ws: WebSocket | null = null;
226
226
  private config: Required<Omit<K256WebSocketClientConfig,
227
227
  'onStateChange' | 'onConnect' | 'onDisconnect' | 'onReconnecting' | 'onError' |
228
- 'onSubscribed' | 'onPoolUpdate' | 'onPoolUpdateBatch' | 'onPriorityFees' |
228
+ 'onSubscribed' | 'onPoolUpdate' | 'onPoolUpdateBatch' | 'onFeeMarket' |
229
229
  'onBlockhash' | 'onQuote' | 'onQuoteSubscribed' | 'onHeartbeat' | 'onPong' |
230
230
  'onMessage' | 'onRawMessage'
231
231
  >> & K256WebSocketClientConfig;
@@ -536,8 +536,8 @@ export class K256WebSocketClient {
536
536
  case 'pool_update':
537
537
  this.config.onPoolUpdate?.(decoded as PoolUpdateMessage);
538
538
  break;
539
- case 'priority_fees':
540
- this.config.onPriorityFees?.(decoded as DecodedMessage & { type: 'priority_fees' });
539
+ case 'fee_market':
540
+ this.config.onFeeMarket?.(decoded as DecodedMessage & { type: 'fee_market' });
541
541
  break;
542
542
  case 'blockhash':
543
543
  this.config.onBlockhash?.(decoded as DecodedMessage & { type: 'blockhash' });
package/src/ws/decoder.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { base58Encode } from '../utils/base58';
11
- import { MessageType, type DecodedMessage, type PoolUpdateMessage } from './types';
11
+ import { MessageType, type DecodedMessage, type PoolUpdateMessage, type FeeMarketMessage } from './types';
12
12
 
13
13
  /**
14
14
  * Decode a binary WebSocket message from K2
@@ -69,84 +69,60 @@ export function decodeMessage(data: ArrayBuffer): DecodedMessage | null {
69
69
  }
70
70
 
71
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;
72
+ // FeeMarketWire bincode layout (per-writable-account model):
73
+ // Header (42 bytes):
74
+ // Offset 0: slot: u64 (8 bytes)
75
+ // Offset 8: timestamp_ms: u64 (8 bytes)
76
+ // Offset 16: recommended: u64 (8 bytes)
77
+ // Offset 24: state: u8 (1 byte)
78
+ // Offset 25: is_stale: bool (1 byte)
79
+ // Offset 26: block_utilization_pct: f32 (4 bytes)
80
+ // Offset 30: blocks_in_window: u32 (4 bytes)
81
+ // Offset 34: account_count: u64 (8 bytes) [bincode Vec length]
82
+ // Per account (92 bytes each):
83
+ // pubkey: [u8; 32], total_txs: u32, active_slots: u32,
84
+ // cu_consumed: u64, utilization_pct: f32,
85
+ // p25: u64, p50: u64, p75: u64, p90: u64, min_nonzero_price: u64
86
+ if (payload.byteLength < 42) return null;
93
87
 
94
88
  const slot = Number(payloadView.getBigUint64(0, true));
95
89
  const timestampMs = Number(payloadView.getBigUint64(8, true));
96
90
  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));
91
+ const state = payloadView.getUint8(24);
92
+ const isStale = payloadView.getUint8(25) !== 0;
93
+ const blockUtilizationPct = payloadView.getFloat32(26, true);
94
+ const blocksInWindow = payloadView.getUint32(30, true);
95
+ const accountCount = Number(payloadView.getBigUint64(34, true));
96
+
97
+ const accounts: FeeMarketMessage['data']['accounts'] = [];
98
+ let offset = 42;
99
+ for (let i = 0; i < accountCount && offset + 92 <= payload.byteLength; i++) {
100
+ const pubkeyBytes = new Uint8Array(payload, offset, 32);
101
+ const pubkey = base58Encode(pubkeyBytes);
102
+ const totalTxs = payloadView.getUint32(offset + 32, true);
103
+ const activeSlots = payloadView.getUint32(offset + 36, true);
104
+ const cuConsumed = Number(payloadView.getBigUint64(offset + 40, true));
105
+ const utilizationPct = payloadView.getFloat32(offset + 48, true);
106
+ const p25 = Number(payloadView.getBigUint64(offset + 52, true));
107
+ const p50 = Number(payloadView.getBigUint64(offset + 60, true));
108
+ const p75 = Number(payloadView.getBigUint64(offset + 68, true));
109
+ const p90 = Number(payloadView.getBigUint64(offset + 76, true));
110
+ const minNonzeroPrice = Number(payloadView.getBigUint64(offset + 84, true));
111
+
112
+ accounts.push({
113
+ pubkey, totalTxs, activeSlots, cuConsumed, utilizationPct,
114
+ p25, p50, p75, p90, minNonzeroPrice,
115
+ });
116
+ offset += 92;
125
117
  }
126
118
 
127
119
  return {
128
- type: 'priority_fees',
120
+ type: 'fee_market',
129
121
  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,
122
+ slot, timestampMs, recommended, state, isStale,
123
+ blockUtilizationPct, blocksInWindow, accounts,
148
124
  },
149
- };
125
+ } as FeeMarketMessage;
150
126
  }
151
127
 
152
128
  case MessageType.Blockhash: {
@@ -324,10 +300,6 @@ function decodePoolUpdate(payload: ArrayBuffer, payloadView: DataView): PoolUpda
324
300
  offset += 4;
325
301
  }
326
302
 
327
- // is_valid (bool)
328
- const isValid = offset < payload.byteLength ? payloadView.getUint8(offset) !== 0 : true;
329
- offset += 1;
330
-
331
303
  // best_bid and best_ask (Option<OrderLevel>) - skip for now
332
304
  // They use bincode Option encoding: 0 = None, 1 + data = Some
333
305
 
@@ -342,24 +314,10 @@ function decodePoolUpdate(payload: ArrayBuffer, payloadView: DataView): PoolUpda
342
314
  tokenMints,
343
315
  tokenBalances,
344
316
  tokenDecimals,
345
- isValid,
346
317
  },
347
318
  };
348
319
  } 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
- };
320
+ return null;
363
321
  }
364
322
  }
365
323
 
@@ -467,24 +425,6 @@ function decodeQuote(payload: ArrayBuffer, payloadView: DataView): DecodedMessag
467
425
  },
468
426
  };
469
427
  } 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
- };
428
+ return null;
489
429
  }
490
430
  }
package/src/ws/index.ts CHANGED
@@ -44,7 +44,7 @@ export type {
44
44
  MessageTypeValue,
45
45
  DecodedMessage,
46
46
  PoolUpdateMessage,
47
- PriorityFeesMessage,
47
+ FeeMarketMessage,
48
48
  BlockhashMessage,
49
49
  QuoteMessage,
50
50
  HeartbeatMessage,
package/src/ws/types.ts CHANGED
@@ -60,46 +60,51 @@ export interface PoolUpdateMessage {
60
60
  tokenMints: string[];
61
61
  tokenBalances: string[];
62
62
  tokenDecimals: number[];
63
- isValid: boolean;
64
63
  bestBid?: { price: string; size: string };
65
64
  bestAsk?: { price: string; size: string };
66
65
  };
67
66
  }
68
67
 
69
68
  /**
70
- * Decoded priority fees from binary message
71
- *
72
- * All fields from K2 PriorityFeesWire
69
+ * Decoded fee market from binary message (per-writable-account model)
70
+ *
71
+ * All fields from K2 FeeMarketWire
73
72
  */
74
- export interface PriorityFeesMessage {
75
- type: 'priority_fees';
73
+ export interface FeeMarketMessage {
74
+ type: 'fee_market';
76
75
  data: {
77
76
  slot: number;
78
77
  timestampMs: number;
79
- /** Recommended priority fee in micro-lamports */
78
+ /** Recommended priority fee in microlamports/CU (max p75 across hottest accounts) */
80
79
  recommended: number;
81
80
  /** Fee state: 0=low, 1=normal, 2=high, 3=extreme */
82
81
  state: number;
83
82
  /** True if data is stale (no recent samples) */
84
83
  isStale: boolean;
85
- // Swap fee percentiles (for ≥50K CU transactions)
86
- swapP50: number;
87
- swapP75: number;
88
- swapP90: number;
89
- swapP99: number;
90
- /** Number of samples used for swap percentiles */
91
- swapSamples: number;
92
- // Landing probability fees (fee to land with X% probability)
93
- landingP50Fee: number;
94
- landingP75Fee: number;
95
- landingP90Fee: number;
96
- landingP99Fee: number;
97
- // Top fee tiers
98
- top10Fee: number;
99
- top25Fee: number;
100
- // Spike detection
101
- spikeDetected: boolean;
102
- spikeFee: number;
84
+ /** Block utilization percentage (0-100) */
85
+ blockUtilizationPct: number;
86
+ /** Number of blocks in the observation window */
87
+ blocksInWindow: number;
88
+ /** Per-writable-account fee data */
89
+ accounts: {
90
+ /** Account public key (Base58) */
91
+ pubkey: string;
92
+ /** Total transactions touching this account */
93
+ totalTxs: number;
94
+ /** Active slots for this account */
95
+ activeSlots: number;
96
+ /** Total CU consumed */
97
+ cuConsumed: number;
98
+ /** Utilization percentage (0-100) of 12M CU limit */
99
+ utilizationPct: number;
100
+ /** Fee percentiles in microlamports/CU */
101
+ p25: number;
102
+ p50: number;
103
+ p75: number;
104
+ p90: number;
105
+ /** Minimum non-zero fee observed */
106
+ minNonzeroPrice: number;
107
+ }[];
103
108
  };
104
109
  }
105
110
 
@@ -217,7 +222,7 @@ export interface PongMessage {
217
222
  */
218
223
  export type DecodedMessage =
219
224
  | PoolUpdateMessage
220
- | PriorityFeesMessage
225
+ | FeeMarketMessage
221
226
  | BlockhashMessage
222
227
  | QuoteMessage
223
228
  | HeartbeatMessage