solana-tx-kit 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 +369 -0
- package/dist/index.cjs +1297 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +675 -0
- package/dist/index.d.ts +675 -0
- package/dist/index.js +1266 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { Transaction, VersionedTransaction, Keypair, Commitment, PublicKey, Connection, TransactionInstruction } from '@solana/web3.js';
|
|
3
|
+
|
|
4
|
+
interface Logger {
|
|
5
|
+
debug(msg: string, data?: Record<string, unknown>): void;
|
|
6
|
+
info(msg: string, data?: Record<string, unknown>): void;
|
|
7
|
+
warn(msg: string, data?: Record<string, unknown>): void;
|
|
8
|
+
error(msg: string, data?: Record<string, unknown>): void;
|
|
9
|
+
}
|
|
10
|
+
type SolanaTransaction = Transaction | VersionedTransaction;
|
|
11
|
+
type Result<T, E = Error> = {
|
|
12
|
+
ok: true;
|
|
13
|
+
value: T;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
error: E;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Transaction lifecycle events emitted during send/confirm/bundle operations */
|
|
20
|
+
declare enum TxEvent {
|
|
21
|
+
SENDING = "sending",
|
|
22
|
+
SIMULATED = "simulated",
|
|
23
|
+
SENT = "sent",
|
|
24
|
+
CONFIRMING = "confirming",
|
|
25
|
+
CONFIRMED = "confirmed",
|
|
26
|
+
RETRYING = "retrying",
|
|
27
|
+
BLOCKHASH_EXPIRED = "blockhash_expired",
|
|
28
|
+
FAILED = "failed",
|
|
29
|
+
BUNDLE_SENT = "bundle_sent",
|
|
30
|
+
BUNDLE_CONFIRMED = "bundle_confirmed",
|
|
31
|
+
BUNDLE_FAILED = "bundle_failed"
|
|
32
|
+
}
|
|
33
|
+
interface TxEventMap {
|
|
34
|
+
[TxEvent.SENDING]: {
|
|
35
|
+
transaction: SolanaTransaction;
|
|
36
|
+
attempt: number;
|
|
37
|
+
};
|
|
38
|
+
[TxEvent.SIMULATED]: {
|
|
39
|
+
signature: string;
|
|
40
|
+
unitsConsumed: number;
|
|
41
|
+
logs: string[];
|
|
42
|
+
};
|
|
43
|
+
[TxEvent.SENT]: {
|
|
44
|
+
signature: string;
|
|
45
|
+
attempt: number;
|
|
46
|
+
};
|
|
47
|
+
[TxEvent.CONFIRMING]: {
|
|
48
|
+
signature: string;
|
|
49
|
+
commitment: string;
|
|
50
|
+
};
|
|
51
|
+
[TxEvent.CONFIRMED]: {
|
|
52
|
+
signature: string;
|
|
53
|
+
slot: number;
|
|
54
|
+
commitment: string;
|
|
55
|
+
};
|
|
56
|
+
[TxEvent.RETRYING]: {
|
|
57
|
+
attempt: number;
|
|
58
|
+
maxRetries: number;
|
|
59
|
+
error: Error;
|
|
60
|
+
delayMs: number;
|
|
61
|
+
};
|
|
62
|
+
[TxEvent.BLOCKHASH_EXPIRED]: {
|
|
63
|
+
oldBlockhash: string;
|
|
64
|
+
newBlockhash: string;
|
|
65
|
+
};
|
|
66
|
+
[TxEvent.FAILED]: {
|
|
67
|
+
error: Error;
|
|
68
|
+
attempt: number;
|
|
69
|
+
};
|
|
70
|
+
[TxEvent.BUNDLE_SENT]: {
|
|
71
|
+
bundleId: string;
|
|
72
|
+
txCount: number;
|
|
73
|
+
};
|
|
74
|
+
[TxEvent.BUNDLE_CONFIRMED]: {
|
|
75
|
+
bundleId: string;
|
|
76
|
+
slot: number;
|
|
77
|
+
};
|
|
78
|
+
[TxEvent.BUNDLE_FAILED]: {
|
|
79
|
+
bundleId: string;
|
|
80
|
+
error: Error;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Type-safe event emitter for transaction lifecycle events. Subscribe via `.on(TxEvent.*, handler)`. */
|
|
84
|
+
declare class TypedEventEmitter extends EventEmitter {
|
|
85
|
+
emit<K extends TxEvent>(event: K, data: TxEventMap[K]): boolean;
|
|
86
|
+
on<K extends TxEvent>(event: K, listener: (data: TxEventMap[K]) => void): this;
|
|
87
|
+
once<K extends TxEvent>(event: K, listener: (data: TxEventMap[K]) => void): this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface JitoConfig {
|
|
91
|
+
/** Block engine URL (default: mainnet) */
|
|
92
|
+
blockEngineUrl: string;
|
|
93
|
+
/** Tip amount in lamports. If not set, uses dynamic calculation */
|
|
94
|
+
tipLamports?: number;
|
|
95
|
+
/** Maximum tip in lamports (cap for dynamic tip) */
|
|
96
|
+
maxTipLamports?: number;
|
|
97
|
+
/** Minimum tip in lamports (default: 1000 — Jito minimum) */
|
|
98
|
+
minTipLamports?: number;
|
|
99
|
+
/** Keypair for signing the tip transaction */
|
|
100
|
+
tipPayer: Keypair;
|
|
101
|
+
/** Poll interval for bundle status in ms (default: 2000) */
|
|
102
|
+
statusPollIntervalMs?: number;
|
|
103
|
+
/** Maximum time to wait for bundle confirmation in ms (default: 60000) */
|
|
104
|
+
statusTimeoutMs?: number;
|
|
105
|
+
}
|
|
106
|
+
interface BundleResult {
|
|
107
|
+
bundleId: string;
|
|
108
|
+
status: BundleStatus;
|
|
109
|
+
slot?: number;
|
|
110
|
+
/** Time from submission to confirmation in ms */
|
|
111
|
+
latencyMs?: number;
|
|
112
|
+
}
|
|
113
|
+
declare enum BundleStatus {
|
|
114
|
+
SUBMITTED = "submitted",
|
|
115
|
+
PENDING = "pending",
|
|
116
|
+
LANDED = "landed",
|
|
117
|
+
FAILED = "failed",
|
|
118
|
+
DROPPED = "dropped",
|
|
119
|
+
INVALID = "invalid"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface RpcEndpointConfig {
|
|
123
|
+
/** RPC URL */
|
|
124
|
+
url: string;
|
|
125
|
+
/** Weight for routing (higher = preferred). Default: 1 */
|
|
126
|
+
weight?: number;
|
|
127
|
+
/** Maximum requests per second for this endpoint. 0 = unlimited */
|
|
128
|
+
rateLimit?: number;
|
|
129
|
+
/** Human-readable label (e.g., "helius-primary") */
|
|
130
|
+
label?: string;
|
|
131
|
+
}
|
|
132
|
+
interface HealthMetrics {
|
|
133
|
+
/** Exponential moving average of latency in ms */
|
|
134
|
+
latencyEma: number;
|
|
135
|
+
/** Total errors in the sliding window */
|
|
136
|
+
errorCount: number;
|
|
137
|
+
/** Total successes in the sliding window */
|
|
138
|
+
successCount: number;
|
|
139
|
+
/** Error rate (0-1) */
|
|
140
|
+
errorRate: number;
|
|
141
|
+
/** Last known slot from this endpoint */
|
|
142
|
+
lastSlot: number;
|
|
143
|
+
/** Delta from the highest known slot across all endpoints */
|
|
144
|
+
slotLag: number;
|
|
145
|
+
/** Timestamp of last successful response */
|
|
146
|
+
lastSuccessAt: number;
|
|
147
|
+
/** Current circuit breaker state */
|
|
148
|
+
circuitState: CircuitState;
|
|
149
|
+
}
|
|
150
|
+
declare enum CircuitState {
|
|
151
|
+
CLOSED = "closed",
|
|
152
|
+
OPEN = "open",
|
|
153
|
+
HALF_OPEN = "half_open"
|
|
154
|
+
}
|
|
155
|
+
interface CircuitBreakerConfig {
|
|
156
|
+
/** Number of errors to trip the breaker (default: 5) */
|
|
157
|
+
failureThreshold: number;
|
|
158
|
+
/** Time in OPEN state before transitioning to HALF_OPEN (default: 30000ms) */
|
|
159
|
+
resetTimeoutMs: number;
|
|
160
|
+
/** Sliding window size in ms for counting failures (default: 60000ms) */
|
|
161
|
+
windowMs: number;
|
|
162
|
+
}
|
|
163
|
+
interface ConnectionPoolConfig {
|
|
164
|
+
endpoints: RpcEndpointConfig[];
|
|
165
|
+
/** Strategy for selecting endpoints */
|
|
166
|
+
strategy?: "weighted-round-robin" | "latency-based";
|
|
167
|
+
/** How often to run health checks in ms (default: 10000) */
|
|
168
|
+
healthCheckIntervalMs?: number;
|
|
169
|
+
/** Circuit breaker settings */
|
|
170
|
+
circuitBreaker?: Partial<CircuitBreakerConfig>;
|
|
171
|
+
/** Commitment for health check calls (default: "confirmed") */
|
|
172
|
+
healthCheckCommitment?: Commitment;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface BlockhashInfo {
|
|
176
|
+
blockhash: string;
|
|
177
|
+
lastValidBlockHeight: number;
|
|
178
|
+
fetchedAt: number;
|
|
179
|
+
}
|
|
180
|
+
interface BlockhashManagerConfig {
|
|
181
|
+
/** Time-to-live for cached blockhash in ms (default: 60_000) */
|
|
182
|
+
ttlMs: number;
|
|
183
|
+
/** Background refresh interval in ms (default: 30_000) */
|
|
184
|
+
refreshIntervalMs: number;
|
|
185
|
+
/** Commitment for fetching (default: "confirmed") */
|
|
186
|
+
commitment: Commitment;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ConfirmationConfig {
|
|
190
|
+
/** Target commitment level (default: "confirmed") */
|
|
191
|
+
commitment: Commitment;
|
|
192
|
+
/** Total timeout in ms (default: 60_000) */
|
|
193
|
+
timeoutMs: number;
|
|
194
|
+
/** Polling interval for fallback polling in ms (default: 2_000) */
|
|
195
|
+
pollIntervalMs: number;
|
|
196
|
+
/** Whether to use WebSocket subscription (default: true) */
|
|
197
|
+
useWebSocket: boolean;
|
|
198
|
+
}
|
|
199
|
+
interface ConfirmationResult {
|
|
200
|
+
status: "confirmed" | "finalized" | "expired" | "failed";
|
|
201
|
+
slot?: number;
|
|
202
|
+
error?: {
|
|
203
|
+
code: number;
|
|
204
|
+
message: string;
|
|
205
|
+
};
|
|
206
|
+
/** Time from submission to confirmation in ms */
|
|
207
|
+
latencyMs: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface FeeEstimateConfig {
|
|
211
|
+
/** Percentile to target: 50, 75, or 90 (default: 75) */
|
|
212
|
+
targetPercentile: 50 | 75 | 90;
|
|
213
|
+
/** Maximum micro-lamports per CU (default: 1_000_000) */
|
|
214
|
+
maxMicroLamports: number;
|
|
215
|
+
/** Minimum micro-lamports per CU (default: 1_000) */
|
|
216
|
+
minMicroLamports: number;
|
|
217
|
+
/** Writable accounts for account-specific estimation */
|
|
218
|
+
writableAccounts?: PublicKey[];
|
|
219
|
+
}
|
|
220
|
+
interface FeeEstimateResult {
|
|
221
|
+
/** Recommended micro-lamports per CU */
|
|
222
|
+
microLamports: number;
|
|
223
|
+
/** The percentile values observed */
|
|
224
|
+
percentiles: {
|
|
225
|
+
p50: number;
|
|
226
|
+
p75: number;
|
|
227
|
+
p90: number;
|
|
228
|
+
};
|
|
229
|
+
/** Number of fee samples used */
|
|
230
|
+
sampleCount: number;
|
|
231
|
+
}
|
|
232
|
+
interface ComputeBudgetConfig {
|
|
233
|
+
/** Compute unit limit to request (default: 200_000) */
|
|
234
|
+
computeUnits: number;
|
|
235
|
+
/** Micro-lamports per compute unit */
|
|
236
|
+
microLamports: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface RetryConfig {
|
|
240
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
241
|
+
maxRetries: number;
|
|
242
|
+
/** Initial delay in ms before first retry (default: 500) */
|
|
243
|
+
baseDelayMs: number;
|
|
244
|
+
/** Maximum delay cap in ms (default: 10000) */
|
|
245
|
+
maxDelayMs: number;
|
|
246
|
+
/** Exponential multiplier (default: 2) */
|
|
247
|
+
backoffMultiplier: number;
|
|
248
|
+
/** Custom predicate: return true to retry, false to fail immediately */
|
|
249
|
+
retryPredicate?: (error: Error, attempt: number) => boolean;
|
|
250
|
+
/** Called before each retry — hook for re-signing, logging, etc. */
|
|
251
|
+
onRetry?: (error: Error, attempt: number, delayMs: number) => void | Promise<void>;
|
|
252
|
+
}
|
|
253
|
+
interface RetryContext {
|
|
254
|
+
attempt: number;
|
|
255
|
+
totalAttempts: number;
|
|
256
|
+
elapsed: number;
|
|
257
|
+
lastError?: Error | undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface SimulationConfig {
|
|
261
|
+
/** Commitment level for simulation (default: "confirmed") */
|
|
262
|
+
commitment?: Commitment;
|
|
263
|
+
/** Whether to replace the blockhash for simulation (default: true) */
|
|
264
|
+
replaceRecentBlockhash?: boolean;
|
|
265
|
+
/** Whether to verify signatures during simulation (default: false — faster) */
|
|
266
|
+
sigVerify?: boolean;
|
|
267
|
+
}
|
|
268
|
+
interface SimulationResult {
|
|
269
|
+
success: boolean;
|
|
270
|
+
unitsConsumed: number;
|
|
271
|
+
logs: string[];
|
|
272
|
+
error?: {
|
|
273
|
+
code: number;
|
|
274
|
+
message: string;
|
|
275
|
+
instructionError?: {
|
|
276
|
+
index: number;
|
|
277
|
+
message: string;
|
|
278
|
+
} | undefined;
|
|
279
|
+
} | undefined;
|
|
280
|
+
returnData?: {
|
|
281
|
+
programId: string;
|
|
282
|
+
data: string;
|
|
283
|
+
} | undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Fluent builder for constructing a TransactionSender.
|
|
288
|
+
*
|
|
289
|
+
* Usage:
|
|
290
|
+
* const sender = TransactionSender.builder()
|
|
291
|
+
* .rpc("https://api.mainnet-beta.solana.com")
|
|
292
|
+
* .signer(keypair)
|
|
293
|
+
* .withPriorityFees({ targetPercentile: 90 })
|
|
294
|
+
* .withJito({ tipLamports: 10_000, tipPayer: keypair })
|
|
295
|
+
* .withRetry({ maxRetries: 5 })
|
|
296
|
+
* .build();
|
|
297
|
+
*/
|
|
298
|
+
declare class TransactionSenderBuilder {
|
|
299
|
+
private config;
|
|
300
|
+
/** Set a single RPC endpoint */
|
|
301
|
+
rpc(url: string): this;
|
|
302
|
+
/** Set multiple RPC endpoints with failover */
|
|
303
|
+
rpcPool(endpoints: RpcEndpointConfig[], options?: {
|
|
304
|
+
strategy?: "weighted-round-robin" | "latency-based";
|
|
305
|
+
healthCheckIntervalMs?: number;
|
|
306
|
+
}): this;
|
|
307
|
+
/** Set the transaction signer */
|
|
308
|
+
signer(keypair: Keypair): this;
|
|
309
|
+
/** Configure priority fee estimation */
|
|
310
|
+
withPriorityFees(config?: Partial<FeeEstimateConfig>): this;
|
|
311
|
+
/** Disable automatic priority fee estimation */
|
|
312
|
+
disablePriorityFees(): this;
|
|
313
|
+
/** Configure Jito bundle submission */
|
|
314
|
+
withJito(config: JitoConfig): this;
|
|
315
|
+
/** Configure retry behavior */
|
|
316
|
+
withRetry(config: Partial<RetryConfig>): this;
|
|
317
|
+
/** Configure transaction simulation */
|
|
318
|
+
withSimulation(config?: Partial<SimulationConfig>): this;
|
|
319
|
+
/** Disable pre-flight simulation */
|
|
320
|
+
disableSimulation(): this;
|
|
321
|
+
/** Configure confirmation tracking */
|
|
322
|
+
withConfirmation(config: Partial<ConfirmationConfig>): this;
|
|
323
|
+
/** Configure blockhash management */
|
|
324
|
+
withBlockhash(config: Partial<BlockhashManagerConfig>): this;
|
|
325
|
+
/** Set the logger */
|
|
326
|
+
withLogger(logger: Logger): this;
|
|
327
|
+
/** Set the default commitment */
|
|
328
|
+
commitment(level: Commitment): this;
|
|
329
|
+
/** Build and return the TransactionSender */
|
|
330
|
+
build(): TransactionSender;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
interface SenderConfig {
|
|
334
|
+
/** RPC connection(s) configuration */
|
|
335
|
+
rpc: ConnectionPoolConfig | {
|
|
336
|
+
url: string;
|
|
337
|
+
};
|
|
338
|
+
/** Transaction signer */
|
|
339
|
+
signer: Keypair;
|
|
340
|
+
/** Retry configuration */
|
|
341
|
+
retry?: Partial<RetryConfig>;
|
|
342
|
+
/** Priority fee configuration. Set to false to disable */
|
|
343
|
+
priorityFee?: Partial<FeeEstimateConfig> | false;
|
|
344
|
+
/** Jito configuration. Set to false to disable (default: disabled) */
|
|
345
|
+
jito?: JitoConfig | false;
|
|
346
|
+
/** Simulation configuration. Set to false to skip simulation */
|
|
347
|
+
simulation?: Partial<SimulationConfig> | false;
|
|
348
|
+
/** Confirmation configuration */
|
|
349
|
+
confirmation?: Partial<ConfirmationConfig>;
|
|
350
|
+
/** Blockhash management configuration */
|
|
351
|
+
blockhash?: Partial<BlockhashManagerConfig>;
|
|
352
|
+
/** Logger instance */
|
|
353
|
+
logger?: Logger;
|
|
354
|
+
/** Default commitment level for all operations */
|
|
355
|
+
commitment?: Commitment;
|
|
356
|
+
}
|
|
357
|
+
interface SendResult {
|
|
358
|
+
/** Transaction signature (base58) */
|
|
359
|
+
signature: string;
|
|
360
|
+
/** Slot at which the transaction was confirmed */
|
|
361
|
+
slot: number;
|
|
362
|
+
/** Confirmation commitment achieved */
|
|
363
|
+
commitment: string;
|
|
364
|
+
/** Number of attempts (1 = first try succeeded) */
|
|
365
|
+
attempts: number;
|
|
366
|
+
/** Total time from send to confirmation in ms */
|
|
367
|
+
totalLatencyMs: number;
|
|
368
|
+
/** Compute units consumed (from simulation if run) */
|
|
369
|
+
unitsConsumed?: number | undefined;
|
|
370
|
+
/** Priority fee paid in micro-lamports per CU */
|
|
371
|
+
priorityFee?: number | undefined;
|
|
372
|
+
}
|
|
373
|
+
interface SendOptions {
|
|
374
|
+
/** Override priority fee for this send only */
|
|
375
|
+
priorityFee?: Partial<FeeEstimateConfig> | {
|
|
376
|
+
microLamports: number;
|
|
377
|
+
};
|
|
378
|
+
/** Override compute units for this send only */
|
|
379
|
+
computeUnits?: number;
|
|
380
|
+
/** Override retry config for this send only */
|
|
381
|
+
retry?: Partial<RetryConfig>;
|
|
382
|
+
/** Skip simulation for this send (speed over safety) */
|
|
383
|
+
skipSimulation?: boolean;
|
|
384
|
+
/** Skip confirmation — return after send, do not wait */
|
|
385
|
+
skipConfirmation?: boolean;
|
|
386
|
+
/** Custom commitment for this send only */
|
|
387
|
+
commitment?: Commitment;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Main orchestrator class. Composes all modules to send transactions
|
|
392
|
+
* with retry, priority fees, simulation, confirmation, and optional Jito bundling.
|
|
393
|
+
*/
|
|
394
|
+
declare class TransactionSender {
|
|
395
|
+
readonly events: TypedEventEmitter;
|
|
396
|
+
private readonly pool;
|
|
397
|
+
private readonly blockhashManager;
|
|
398
|
+
private readonly confirmer;
|
|
399
|
+
private readonly jitoBundleSender?;
|
|
400
|
+
private readonly logger;
|
|
401
|
+
private readonly config;
|
|
402
|
+
/** @param config - Full sender configuration. Prefer using {@link TransactionSender.builder} for construction. */
|
|
403
|
+
constructor(config: SenderConfig);
|
|
404
|
+
/** Create a fluent builder for configuring and constructing a TransactionSender */
|
|
405
|
+
static builder(): TransactionSenderBuilder;
|
|
406
|
+
/**
|
|
407
|
+
* Send a single transaction with the full pipeline:
|
|
408
|
+
* 1. Estimate priority fees (if enabled)
|
|
409
|
+
* 2. Fetch fresh blockhash
|
|
410
|
+
* 3. Add compute budget instructions
|
|
411
|
+
* 4. Sign transaction
|
|
412
|
+
* 5. Simulate (if enabled)
|
|
413
|
+
* 6. Send via RPC (with retry + failover)
|
|
414
|
+
* 7. Confirm (WebSocket + polling)
|
|
415
|
+
*
|
|
416
|
+
* @param transaction - A legacy or versioned Solana transaction. Not mutated for legacy transactions.
|
|
417
|
+
* @param options - Per-send overrides for priority fee, simulation, confirmation, and retry.
|
|
418
|
+
* @returns The confirmed transaction result including signature, slot, and timing info.
|
|
419
|
+
* @throws {SolTxError} On simulation failure, non-retryable errors, or exhausted retries.
|
|
420
|
+
*/
|
|
421
|
+
send(transaction: SolanaTransaction, options?: SendOptions): Promise<SendResult>;
|
|
422
|
+
/**
|
|
423
|
+
* Send a bundle of 1-5 transactions via Jito.
|
|
424
|
+
* Automatically appends tip instruction to the last transaction.
|
|
425
|
+
*/
|
|
426
|
+
sendJitoBundle(transactions: SolanaTransaction[], options?: {
|
|
427
|
+
tipLamports?: number;
|
|
428
|
+
waitForConfirmation?: boolean;
|
|
429
|
+
}): Promise<BundleResult>;
|
|
430
|
+
/** Get current RPC health metrics */
|
|
431
|
+
getHealthReport(): Map<string, HealthMetrics>;
|
|
432
|
+
/** Clean up: stop background tasks, clear intervals */
|
|
433
|
+
destroy(): void;
|
|
434
|
+
/** Build a working copy of the transaction with compute budget instructions prepended */
|
|
435
|
+
private prepareTransaction;
|
|
436
|
+
/** Set blockhash, fee payer, and sign the transaction */
|
|
437
|
+
private signTransaction;
|
|
438
|
+
/** Run simulation if enabled; returns compute units consumed */
|
|
439
|
+
private runSimulation;
|
|
440
|
+
/** Send serialized transaction via the connection pool with fallback */
|
|
441
|
+
private sendRawTransaction;
|
|
442
|
+
/** Confirm a transaction and return its slot, or throw on failure/expiry */
|
|
443
|
+
private awaitConfirmation;
|
|
444
|
+
/** Construct a SendResult */
|
|
445
|
+
private buildResult;
|
|
446
|
+
private resolvePriorityFee;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Execute `fn` with retries. Returns the result or throws after exhausting retries.
|
|
451
|
+
* Generic so it can wrap any async operation, not just transaction sending.
|
|
452
|
+
*/
|
|
453
|
+
declare function withRetry<T>(fn: (context: RetryContext) => Promise<T>, config?: Partial<RetryConfig>): Promise<T>;
|
|
454
|
+
|
|
455
|
+
interface ErrorClassification {
|
|
456
|
+
retryable: boolean;
|
|
457
|
+
/** If true, the transaction must be re-signed with a fresh blockhash before retrying */
|
|
458
|
+
needsResign: boolean;
|
|
459
|
+
errorType: string;
|
|
460
|
+
}
|
|
461
|
+
/** Classify an error as retryable or non-retryable based on message patterns and error codes */
|
|
462
|
+
declare function classifyError(error: Error): ErrorClassification;
|
|
463
|
+
declare function isBlockhashExpired(error: Error): boolean;
|
|
464
|
+
declare function isRateLimited(error: Error): boolean;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Estimates priority fees by calling getRecentPrioritizationFees,
|
|
468
|
+
* sorting the results, and computing the target percentile.
|
|
469
|
+
*/
|
|
470
|
+
declare function estimatePriorityFee(connection: Connection, config?: Partial<FeeEstimateConfig>): Promise<FeeEstimateResult>;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Returns [SetComputeUnitLimit, SetComputeUnitPrice] instructions.
|
|
474
|
+
* Always returns both — the sender prepends them to the transaction.
|
|
475
|
+
*/
|
|
476
|
+
declare function createComputeBudgetInstructions(config: ComputeBudgetConfig): [TransactionInstruction, TransactionInstruction];
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Sends a bundle of 1-5 transactions to the Jito block engine.
|
|
480
|
+
* Uses raw fetch() — no dependency on jito-ts or jito-js-rpc.
|
|
481
|
+
*/
|
|
482
|
+
declare class JitoBundleSender {
|
|
483
|
+
private readonly config;
|
|
484
|
+
private readonly logger?;
|
|
485
|
+
private readonly events?;
|
|
486
|
+
private readonly blockEngineUrl;
|
|
487
|
+
private readonly pollIntervalMs;
|
|
488
|
+
private readonly timeoutMs;
|
|
489
|
+
constructor(config: JitoConfig, logger?: Logger | undefined, events?: TypedEventEmitter | undefined);
|
|
490
|
+
/** Submit a bundle and optionally wait for confirmation */
|
|
491
|
+
sendBundle(transactions: (Transaction | VersionedTransaction)[], options?: {
|
|
492
|
+
waitForConfirmation?: boolean;
|
|
493
|
+
}): Promise<BundleResult>;
|
|
494
|
+
/** Poll getBundleStatuses until landed, failed, or timeout */
|
|
495
|
+
waitForBundleStatus(bundleId: string): Promise<BundleResult>;
|
|
496
|
+
private static validateUrl;
|
|
497
|
+
/** Raw JSON-RPC call to the block engine */
|
|
498
|
+
private rpcCall;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** Get the next tip account using round-robin rotation */
|
|
502
|
+
declare function getNextTipAccount(): PublicKey;
|
|
503
|
+
/** Reset the tip rotation index (useful for testing) */
|
|
504
|
+
declare function resetTipRotation(): void;
|
|
505
|
+
/** Create a SOL transfer instruction to a Jito tip account */
|
|
506
|
+
declare function createTipInstruction(payer: PublicKey, lamports: number): TransactionInstruction;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Manages multiple RPC connections with health tracking, circuit breakers,
|
|
510
|
+
* and selection strategies (weighted round-robin or latency-based).
|
|
511
|
+
*/
|
|
512
|
+
declare class ConnectionPool {
|
|
513
|
+
private readonly logger?;
|
|
514
|
+
private readonly trackers;
|
|
515
|
+
private healthCheckInterval?;
|
|
516
|
+
private roundRobinIndex;
|
|
517
|
+
private readonly strategy;
|
|
518
|
+
constructor(config: ConnectionPoolConfig, logger?: Logger | undefined);
|
|
519
|
+
/** Get the best available connection based on strategy */
|
|
520
|
+
getConnection(): Connection;
|
|
521
|
+
/** Get a connection, falling back through endpoints if the first fails */
|
|
522
|
+
withFallback<T>(fn: (connection: Connection) => Promise<T>): Promise<T>;
|
|
523
|
+
/** Get health metrics for all endpoints */
|
|
524
|
+
getHealthReport(): Map<string, HealthMetrics>;
|
|
525
|
+
/** Stop background health checks */
|
|
526
|
+
destroy(): void;
|
|
527
|
+
private selectByLatency;
|
|
528
|
+
private selectByWeight;
|
|
529
|
+
private runHealthChecks;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Circuit breaker per endpoint.
|
|
534
|
+
*
|
|
535
|
+
* State transitions:
|
|
536
|
+
* CLOSED --[failureThreshold exceeded]--> OPEN
|
|
537
|
+
* OPEN --[resetTimeout elapsed]------> HALF_OPEN
|
|
538
|
+
* HALF_OPEN --[probe succeeds]----------> CLOSED
|
|
539
|
+
* HALF_OPEN --[probe fails]-------------> OPEN
|
|
540
|
+
*/
|
|
541
|
+
declare class CircuitBreaker {
|
|
542
|
+
private state;
|
|
543
|
+
private failures;
|
|
544
|
+
private lastOpenedAt;
|
|
545
|
+
private readonly config;
|
|
546
|
+
constructor(config?: Partial<CircuitBreakerConfig>);
|
|
547
|
+
get currentState(): CircuitState;
|
|
548
|
+
recordSuccess(): void;
|
|
549
|
+
recordFailure(): void;
|
|
550
|
+
canExecute(): boolean;
|
|
551
|
+
reset(): void;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Tracks health metrics (latency EMA, error rate, slot lag) for a single RPC endpoint */
|
|
555
|
+
declare class HealthTracker {
|
|
556
|
+
readonly endpoint: RpcEndpointConfig;
|
|
557
|
+
private readonly connection;
|
|
558
|
+
private readonly logger?;
|
|
559
|
+
private readonly breaker;
|
|
560
|
+
private metrics;
|
|
561
|
+
constructor(endpoint: RpcEndpointConfig, connection: Connection, logger?: Logger | undefined, circuitBreakerConfig?: Partial<CircuitBreakerConfig>);
|
|
562
|
+
healthCheck(): Promise<void>;
|
|
563
|
+
recordSuccess(latencyMs: number, slot?: number): void;
|
|
564
|
+
recordFailure(error: Error): void;
|
|
565
|
+
isAvailable(): boolean;
|
|
566
|
+
getMetrics(): Readonly<HealthMetrics>;
|
|
567
|
+
updateSlotLag(highestSlot: number): void;
|
|
568
|
+
getConnection(): Connection;
|
|
569
|
+
private updateErrorRate;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Wraps Connection.simulateTransaction with:
|
|
574
|
+
* - Structured result parsing
|
|
575
|
+
* - Log extraction
|
|
576
|
+
* - Error decoding
|
|
577
|
+
* - Compute unit extraction
|
|
578
|
+
*/
|
|
579
|
+
declare function simulateTransaction(connection: Connection, transaction: VersionedTransaction | Transaction, config?: SimulationConfig, logger?: Logger): Promise<SimulationResult>;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Confirms a transaction by signature.
|
|
583
|
+
*
|
|
584
|
+
* Strategy:
|
|
585
|
+
* 1. Subscribe via WebSocket (onSignature) — fastest notification
|
|
586
|
+
* 2. Simultaneously poll getSignatureStatuses as fallback
|
|
587
|
+
* 3. Also poll getBlockHeight to detect blockhash expiry
|
|
588
|
+
* 4. First signal wins via Promise.race; losers cleaned up in finally
|
|
589
|
+
*/
|
|
590
|
+
declare class TransactionConfirmer {
|
|
591
|
+
private readonly logger?;
|
|
592
|
+
private readonly events?;
|
|
593
|
+
constructor(logger?: Logger | undefined, events?: TypedEventEmitter | undefined);
|
|
594
|
+
confirm(connection: Connection, signature: string, lastValidBlockHeight: number, config?: Partial<ConfirmationConfig>): Promise<ConfirmationResult>;
|
|
595
|
+
private subscribeWebSocket;
|
|
596
|
+
private pollForConfirmation;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Manages blockhash caching, TTL-based staleness, and background refresh.
|
|
601
|
+
* Uses promise coalescing to avoid redundant RPC calls.
|
|
602
|
+
*/
|
|
603
|
+
declare class BlockhashManager {
|
|
604
|
+
private readonly connection;
|
|
605
|
+
private readonly logger?;
|
|
606
|
+
private cache;
|
|
607
|
+
private refreshInterval?;
|
|
608
|
+
private fetchPromise;
|
|
609
|
+
private readonly config;
|
|
610
|
+
constructor(connection: Connection, config?: Partial<BlockhashManagerConfig>, logger?: Logger | undefined);
|
|
611
|
+
/** Start background refresh loop */
|
|
612
|
+
start(): void;
|
|
613
|
+
/** Get a valid blockhash. Fetches fresh one if cache is stale or missing */
|
|
614
|
+
getBlockhash(): Promise<BlockhashInfo>;
|
|
615
|
+
/** Force a fresh fetch (used after blockhash expiry during retry) */
|
|
616
|
+
refreshBlockhash(): Promise<BlockhashInfo>;
|
|
617
|
+
/** Check if the current blockhash is still valid by comparing block heights */
|
|
618
|
+
isBlockhashValid(): Promise<boolean>;
|
|
619
|
+
/** Get cached info without fetching */
|
|
620
|
+
getCachedBlockhash(): BlockhashInfo | null;
|
|
621
|
+
/** Stop background refresh */
|
|
622
|
+
destroy(): void;
|
|
623
|
+
private isStale;
|
|
624
|
+
private fetchBlockhash;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Error codes for all solana-tx-kit error types */
|
|
628
|
+
declare enum SolTxErrorCode {
|
|
629
|
+
RETRIES_EXHAUSTED = "RETRIES_EXHAUSTED",
|
|
630
|
+
NON_RETRYABLE = "NON_RETRYABLE",
|
|
631
|
+
BLOCKHASH_EXPIRED = "BLOCKHASH_EXPIRED",
|
|
632
|
+
BLOCKHASH_FETCH_FAILED = "BLOCKHASH_FETCH_FAILED",
|
|
633
|
+
SIMULATION_FAILED = "SIMULATION_FAILED",
|
|
634
|
+
INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS",
|
|
635
|
+
CONFIRMATION_TIMEOUT = "CONFIRMATION_TIMEOUT",
|
|
636
|
+
TRANSACTION_FAILED = "TRANSACTION_FAILED",
|
|
637
|
+
ALL_ENDPOINTS_UNHEALTHY = "ALL_ENDPOINTS_UNHEALTHY",
|
|
638
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
639
|
+
BUNDLE_FAILED = "BUNDLE_FAILED",
|
|
640
|
+
BUNDLE_DROPPED = "BUNDLE_DROPPED",
|
|
641
|
+
TIP_TOO_LOW = "TIP_TOO_LOW",
|
|
642
|
+
FEE_ESTIMATION_FAILED = "FEE_ESTIMATION_FAILED"
|
|
643
|
+
}
|
|
644
|
+
/** Structured error with a machine-readable code and optional cause/context */
|
|
645
|
+
declare class SolTxError extends Error {
|
|
646
|
+
readonly code: SolTxErrorCode;
|
|
647
|
+
readonly cause?: Error | undefined;
|
|
648
|
+
readonly context?: Record<string, unknown> | undefined;
|
|
649
|
+
constructor(code: SolTxErrorCode, message: string, options?: {
|
|
650
|
+
cause?: Error | undefined;
|
|
651
|
+
context?: Record<string, unknown> | undefined;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/** A SolTxError that indicates the operation can be retried after a delay */
|
|
655
|
+
declare class RetryableError extends SolTxError {
|
|
656
|
+
readonly retryAfterMs?: number | undefined;
|
|
657
|
+
constructor(code: SolTxErrorCode, message: string, options?: {
|
|
658
|
+
cause?: Error | undefined;
|
|
659
|
+
context?: Record<string, unknown> | undefined;
|
|
660
|
+
retryAfterMs?: number | undefined;
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** Type guard: returns true if the transaction is a VersionedTransaction */
|
|
665
|
+
declare function isVersionedTransaction(tx: SolanaTransaction): tx is VersionedTransaction;
|
|
666
|
+
/** Type guard: returns true if the transaction is a legacy Transaction */
|
|
667
|
+
declare function isLegacyTransaction(tx: SolanaTransaction): tx is Transaction;
|
|
668
|
+
|
|
669
|
+
declare const JITO_TIP_ACCOUNTS: readonly PublicKey[];
|
|
670
|
+
declare const JITO_BLOCK_ENGINE_URL = "https://mainnet.block-engine.jito.wtf";
|
|
671
|
+
|
|
672
|
+
/** Create a console-based logger with `[solana-tx-kit]` prefix. Pass your own Logger to override. */
|
|
673
|
+
declare function createDefaultLogger(): Logger;
|
|
674
|
+
|
|
675
|
+
export { type BlockhashInfo, BlockhashManager, type BlockhashManagerConfig, type BundleResult, BundleStatus, CircuitBreaker, type CircuitBreakerConfig, CircuitState, type ComputeBudgetConfig, type ConfirmationConfig, type ConfirmationResult, ConnectionPool, type ConnectionPoolConfig, type ErrorClassification, type FeeEstimateConfig, type FeeEstimateResult, type HealthMetrics, HealthTracker, JITO_BLOCK_ENGINE_URL, JITO_TIP_ACCOUNTS, JitoBundleSender, type JitoConfig, type Logger, type Result, type RetryConfig, type RetryContext, RetryableError, type RpcEndpointConfig, type SendOptions, type SendResult, type SenderConfig, type SimulationConfig, type SimulationResult, SolTxError, SolTxErrorCode, type SolanaTransaction, TransactionConfirmer, TransactionSender, TransactionSenderBuilder, TxEvent, type TxEventMap, TypedEventEmitter, classifyError, createComputeBudgetInstructions, createDefaultLogger, createTipInstruction, estimatePriorityFee, getNextTipAccount, isBlockhashExpired, isLegacyTransaction, isRateLimited, isVersionedTransaction, resetTipRotation, simulateTransaction, withRetry };
|