@k256/sdk 0.1.6 → 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.
@@ -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
@@ -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: {
@@ -341,19 +317,7 @@ function decodePoolUpdate(payload: ArrayBuffer, payloadView: DataView): PoolUpda
341
317
  },
342
318
  };
343
319
  } catch {
344
- return {
345
- type: 'pool_update',
346
- data: {
347
- sequence: 0,
348
- slot: 0,
349
- writeVersion: 0,
350
- protocol: 'unknown',
351
- poolAddress: '',
352
- tokenMints: [],
353
- tokenBalances: [],
354
- tokenDecimals: [],
355
- },
356
- };
320
+ return null;
357
321
  }
358
322
  }
359
323
 
@@ -461,24 +425,6 @@ function decodeQuote(payload: ArrayBuffer, payloadView: DataView): DecodedMessag
461
425
  },
462
426
  };
463
427
  } catch {
464
- return {
465
- type: 'quote',
466
- data: {
467
- topicId: '',
468
- timestampMs: 0,
469
- sequence: 0,
470
- inputMint: '',
471
- outputMint: '',
472
- inAmount: '0',
473
- outAmount: '0',
474
- priceImpactBps: 0,
475
- contextSlot: 0,
476
- algorithm: '',
477
- isImprovement: false,
478
- isCached: false,
479
- isStale: false,
480
- routePlan: null,
481
- },
482
- };
428
+ return null;
483
429
  }
484
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
@@ -66,39 +66,45 @@ export interface PoolUpdateMessage {
66
66
  }
67
67
 
68
68
  /**
69
- * Decoded priority fees from binary message
70
- *
71
- * All fields from K2 PriorityFeesWire
69
+ * Decoded fee market from binary message (per-writable-account model)
70
+ *
71
+ * All fields from K2 FeeMarketWire
72
72
  */
73
- export interface PriorityFeesMessage {
74
- type: 'priority_fees';
73
+ export interface FeeMarketMessage {
74
+ type: 'fee_market';
75
75
  data: {
76
76
  slot: number;
77
77
  timestampMs: number;
78
- /** Recommended priority fee in micro-lamports */
78
+ /** Recommended priority fee in microlamports/CU (max p75 across hottest accounts) */
79
79
  recommended: number;
80
80
  /** Fee state: 0=low, 1=normal, 2=high, 3=extreme */
81
81
  state: number;
82
82
  /** True if data is stale (no recent samples) */
83
83
  isStale: boolean;
84
- // Swap fee percentiles (for ≥50K CU transactions)
85
- swapP50: number;
86
- swapP75: number;
87
- swapP90: number;
88
- swapP99: number;
89
- /** Number of samples used for swap percentiles */
90
- swapSamples: number;
91
- // Landing probability fees (fee to land with X% probability)
92
- landingP50Fee: number;
93
- landingP75Fee: number;
94
- landingP90Fee: number;
95
- landingP99Fee: number;
96
- // Top fee tiers
97
- top10Fee: number;
98
- top25Fee: number;
99
- // Spike detection
100
- spikeDetected: boolean;
101
- 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
+ }[];
102
108
  };
103
109
  }
104
110
 
@@ -216,7 +222,7 @@ export interface PongMessage {
216
222
  */
217
223
  export type DecodedMessage =
218
224
  | PoolUpdateMessage
219
- | PriorityFeesMessage
225
+ | FeeMarketMessage
220
226
  | BlockhashMessage
221
227
  | QuoteMessage
222
228
  | HeartbeatMessage