sol-trade-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/dist/chunk-MMQAMIKR.mjs +3735 -0
- package/dist/chunk-NEZDFAYA.mjs +7744 -0
- package/dist/clients-VITWK7B6.mjs +1370 -0
- package/dist/index-1BK_FXsW.d.mts +2327 -0
- package/dist/index-1BK_FXsW.d.ts +2327 -0
- package/dist/index.d.mts +2659 -0
- package/dist/index.d.ts +2659 -0
- package/dist/index.js +13265 -0
- package/dist/index.mjs +562 -0
- package/dist/perf/index.d.mts +2 -0
- package/dist/perf/index.d.ts +2 -0
- package/dist/perf/index.js +3742 -0
- package/dist/perf/index.mjs +214 -0
- package/package.json +101 -0
- package/src/__tests__/complete_sdk.test.ts +354 -0
- package/src/__tests__/hotpath.test.ts +486 -0
- package/src/__tests__/nonce.test.ts +45 -0
- package/src/__tests__/sdk.test.ts +425 -0
- package/src/address-lookup/index.ts +197 -0
- package/src/cache/cache.ts +308 -0
- package/src/calc/index.ts +1058 -0
- package/src/calc/pumpfun.ts +124 -0
- package/src/common/bonding_curve.ts +272 -0
- package/src/common/compute-budget.ts +148 -0
- package/src/common/confirm-any-signature.ts +184 -0
- package/src/common/fast-timing.ts +481 -0
- package/src/common/fast_fn.ts +150 -0
- package/src/common/gas-fee-strategy.ts +253 -0
- package/src/common/map-pool.ts +23 -0
- package/src/common/nonce.ts +40 -0
- package/src/common/sdk-log.ts +460 -0
- package/src/common/seed.ts +381 -0
- package/src/common/spl-token.ts +578 -0
- package/src/common/subscription-handle.ts +644 -0
- package/src/common/trading-utils.ts +239 -0
- package/src/common/wsol-manager.ts +325 -0
- package/src/compute/compute_budget_manager.ts +187 -0
- package/src/compute/index.ts +21 -0
- package/src/constants/index.ts +96 -0
- package/src/execution/execution.ts +532 -0
- package/src/execution/index.ts +42 -0
- package/src/hotpath/executor.ts +464 -0
- package/src/hotpath/index.ts +64 -0
- package/src/hotpath/state.ts +435 -0
- package/src/index.ts +2117 -0
- package/src/instruction/bonk_builder.ts +730 -0
- package/src/instruction/index.ts +24 -0
- package/src/instruction/meteora_damm_v2_builder.ts +509 -0
- package/src/instruction/pumpfun_builder.ts +1183 -0
- package/src/instruction/pumpswap.ts +1123 -0
- package/src/instruction/raydium_amm_v4_builder.ts +692 -0
- package/src/instruction/raydium_cpmm_builder.ts +795 -0
- package/src/middleware/traits.ts +407 -0
- package/src/params/index.ts +483 -0
- package/src/perf/compiler-optimization.ts +529 -0
- package/src/perf/hardware.ts +631 -0
- package/src/perf/index.ts +9 -0
- package/src/perf/kernel-bypass.ts +656 -0
- package/src/perf/protocol.ts +682 -0
- package/src/perf/realtime.ts +592 -0
- package/src/perf/simd.ts +668 -0
- package/src/perf/syscall-bypass.ts +331 -0
- package/src/perf/ultra-low-latency.ts +505 -0
- package/src/perf/zero-copy.ts +589 -0
- package/src/pool/pool.ts +294 -0
- package/src/rpc/client.ts +345 -0
- package/src/sdk-errors.ts +13 -0
- package/src/security/index.ts +26 -0
- package/src/security/secure-key.ts +303 -0
- package/src/security/validators.ts +281 -0
- package/src/seed/pda.ts +262 -0
- package/src/serialization/index.ts +28 -0
- package/src/serialization/serialization.ts +288 -0
- package/src/swqos/clients.ts +1754 -0
- package/src/swqos/index.ts +50 -0
- package/src/swqos/providers.ts +1707 -0
- package/src/trading/core/async-executor.ts +702 -0
- package/src/trading/core/confirmation-monitor.ts +711 -0
- package/src/trading/core/index.ts +82 -0
- package/src/trading/core/retry-handler.ts +683 -0
- package/src/trading/core/transaction-pool.ts +780 -0
- package/src/trading/executor.ts +385 -0
- package/src/trading/factory.ts +282 -0
- package/src/trading/index.ts +30 -0
- package/src/types.ts +8 -0
- package/src/utils/index.ts +155 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirmation Monitor for Sol Trade SDK
|
|
3
|
+
* Monitors transaction confirmations with configurable strategies.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Connection, Commitment, PublicKey } from '@solana/web3.js';
|
|
7
|
+
import { TradeError } from '../../index';
|
|
8
|
+
|
|
9
|
+
// ===== Types =====
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Confirmation status levels
|
|
13
|
+
*/
|
|
14
|
+
export enum ConfirmationStatus {
|
|
15
|
+
/** Transaction not found */
|
|
16
|
+
NotFound = 'NotFound',
|
|
17
|
+
/** Transaction processed but not confirmed */
|
|
18
|
+
Processed = 'Processed',
|
|
19
|
+
/** Transaction confirmed by cluster */
|
|
20
|
+
Confirmed = 'Confirmed',
|
|
21
|
+
/** Transaction finalized (rooted) */
|
|
22
|
+
Finalized = 'Finalized',
|
|
23
|
+
/** Transaction failed with error */
|
|
24
|
+
Failed = 'Failed',
|
|
25
|
+
/** Confirmation timed out */
|
|
26
|
+
TimedOut = 'TimedOut',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Configuration for confirmation monitoring
|
|
31
|
+
*/
|
|
32
|
+
export interface ConfirmationConfig {
|
|
33
|
+
/** Target commitment level */
|
|
34
|
+
commitment: Commitment;
|
|
35
|
+
/** Polling interval in milliseconds */
|
|
36
|
+
pollIntervalMs: number;
|
|
37
|
+
/** Maximum time to wait for confirmation */
|
|
38
|
+
timeoutMs: number;
|
|
39
|
+
/** Maximum retry attempts */
|
|
40
|
+
maxRetries: number;
|
|
41
|
+
/** Whether to use WebSocket subscription */
|
|
42
|
+
useWebSocket: boolean;
|
|
43
|
+
/** Whether to enable signature caching */
|
|
44
|
+
enableCache: boolean;
|
|
45
|
+
/** Cache TTL in milliseconds */
|
|
46
|
+
cacheTtlMs: number;
|
|
47
|
+
/** Callback for status updates */
|
|
48
|
+
onStatusUpdate?: (signature: string, status: ConfirmationStatus, result?: ConfirmationResult) => void;
|
|
49
|
+
/** Callback for progress updates */
|
|
50
|
+
onProgress?: (signature: string, progress: ConfirmationProgress) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Confirmation progress information
|
|
55
|
+
*/
|
|
56
|
+
export interface ConfirmationProgress {
|
|
57
|
+
/** Current status */
|
|
58
|
+
status: ConfirmationStatus;
|
|
59
|
+
/** Number of confirmations received */
|
|
60
|
+
confirmations: number;
|
|
61
|
+
/** Slot when transaction was processed */
|
|
62
|
+
slot?: number;
|
|
63
|
+
/** Current slot */
|
|
64
|
+
currentSlot?: number;
|
|
65
|
+
/** Elapsed time in milliseconds */
|
|
66
|
+
elapsedMs: number;
|
|
67
|
+
/** Estimated time remaining in milliseconds */
|
|
68
|
+
estimatedRemainingMs?: number;
|
|
69
|
+
/** Retry attempt number */
|
|
70
|
+
retryAttempt: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Result of confirmation monitoring
|
|
75
|
+
*/
|
|
76
|
+
export interface ConfirmationResult {
|
|
77
|
+
/** Transaction signature */
|
|
78
|
+
signature: string;
|
|
79
|
+
/** Final confirmation status */
|
|
80
|
+
status: ConfirmationStatus;
|
|
81
|
+
/** Slot when transaction was processed */
|
|
82
|
+
slot?: number;
|
|
83
|
+
/** Blockhash of the block containing the transaction */
|
|
84
|
+
blockhash?: string;
|
|
85
|
+
/** Error information if transaction failed */
|
|
86
|
+
error?: TransactionError;
|
|
87
|
+
/** Total time to confirm in milliseconds */
|
|
88
|
+
confirmationTimeMs: number;
|
|
89
|
+
/** Number of retry attempts made */
|
|
90
|
+
retryAttempts: number;
|
|
91
|
+
/** Timestamp when monitoring started */
|
|
92
|
+
startedAt: number;
|
|
93
|
+
/** Timestamp when monitoring completed */
|
|
94
|
+
completedAt: number;
|
|
95
|
+
/** Additional metadata */
|
|
96
|
+
metadata?: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Transaction error information
|
|
101
|
+
*/
|
|
102
|
+
export interface TransactionError {
|
|
103
|
+
/** Error code */
|
|
104
|
+
code: number;
|
|
105
|
+
/** Error message */
|
|
106
|
+
message: string;
|
|
107
|
+
/** Program that caused the error (if applicable) */
|
|
108
|
+
programId?: string;
|
|
109
|
+
/** Error logs */
|
|
110
|
+
logs?: string[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Monitored signature information
|
|
115
|
+
*/
|
|
116
|
+
interface MonitoredSignature {
|
|
117
|
+
signature: string;
|
|
118
|
+
startTime: number;
|
|
119
|
+
config: ConfirmationConfig;
|
|
120
|
+
status: ConfirmationStatus;
|
|
121
|
+
slot?: number;
|
|
122
|
+
blockhash?: string;
|
|
123
|
+
error?: TransactionError;
|
|
124
|
+
retryAttempts: number;
|
|
125
|
+
lastPollTime: number;
|
|
126
|
+
abortController: AbortController;
|
|
127
|
+
callbacks: Set<(result: ConfirmationResult) => void>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ===== Default Configurations =====
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get default confirmation configuration
|
|
134
|
+
*/
|
|
135
|
+
export function defaultConfirmationConfig(): ConfirmationConfig {
|
|
136
|
+
return {
|
|
137
|
+
commitment: 'confirmed',
|
|
138
|
+
pollIntervalMs: 500,
|
|
139
|
+
timeoutMs: 60000,
|
|
140
|
+
maxRetries: 3,
|
|
141
|
+
useWebSocket: false,
|
|
142
|
+
enableCache: true,
|
|
143
|
+
cacheTtlMs: 300000, // 5 minutes
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get fast confirmation configuration
|
|
149
|
+
*/
|
|
150
|
+
export function fastConfirmationConfig(): ConfirmationConfig {
|
|
151
|
+
return {
|
|
152
|
+
commitment: 'processed',
|
|
153
|
+
pollIntervalMs: 200,
|
|
154
|
+
timeoutMs: 10000,
|
|
155
|
+
maxRetries: 1,
|
|
156
|
+
useWebSocket: true,
|
|
157
|
+
enableCache: false,
|
|
158
|
+
cacheTtlMs: 60000,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get reliable confirmation configuration
|
|
164
|
+
*/
|
|
165
|
+
export function reliableConfirmationConfig(): ConfirmationConfig {
|
|
166
|
+
return {
|
|
167
|
+
commitment: 'finalized',
|
|
168
|
+
pollIntervalMs: 1000,
|
|
169
|
+
timeoutMs: 120000,
|
|
170
|
+
maxRetries: 5,
|
|
171
|
+
useWebSocket: true,
|
|
172
|
+
enableCache: true,
|
|
173
|
+
cacheTtlMs: 600000, // 10 minutes
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ===== Confirmation Monitor =====
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Monitors transaction confirmations with configurable strategies
|
|
181
|
+
*/
|
|
182
|
+
export class ConfirmationMonitor {
|
|
183
|
+
private connection: Connection;
|
|
184
|
+
private monitored: Map<string, MonitoredSignature> = new Map();
|
|
185
|
+
private cache: Map<string, ConfirmationResult> = new Map();
|
|
186
|
+
private pollInterval?: NodeJS.Timeout;
|
|
187
|
+
private isRunning: boolean = false;
|
|
188
|
+
private wsSubscription?: number;
|
|
189
|
+
|
|
190
|
+
constructor(
|
|
191
|
+
rpcUrl: string,
|
|
192
|
+
private defaultConfig: ConfirmationConfig = defaultConfirmationConfig()
|
|
193
|
+
) {
|
|
194
|
+
this.connection = new Connection(rpcUrl, defaultConfig.commitment);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Start the confirmation monitor
|
|
199
|
+
*/
|
|
200
|
+
start(): void {
|
|
201
|
+
if (this.isRunning) return;
|
|
202
|
+
|
|
203
|
+
this.isRunning = true;
|
|
204
|
+
this.pollInterval = setInterval(() => {
|
|
205
|
+
this.pollAll();
|
|
206
|
+
}, this.defaultConfig.pollIntervalMs);
|
|
207
|
+
|
|
208
|
+
if (this.defaultConfig.useWebSocket) {
|
|
209
|
+
this.setupWebSocket();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Stop the confirmation monitor
|
|
215
|
+
*/
|
|
216
|
+
stop(): void {
|
|
217
|
+
this.isRunning = false;
|
|
218
|
+
|
|
219
|
+
if (this.pollInterval) {
|
|
220
|
+
clearInterval(this.pollInterval);
|
|
221
|
+
this.pollInterval = undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.wsSubscription !== undefined) {
|
|
225
|
+
this.connection.removeSignatureListener(this.wsSubscription);
|
|
226
|
+
this.wsSubscription = undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Abort all monitored signatures
|
|
230
|
+
for (const monitored of this.monitored.values()) {
|
|
231
|
+
monitored.abortController.abort();
|
|
232
|
+
}
|
|
233
|
+
this.monitored.clear();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Monitor a transaction signature for confirmation
|
|
238
|
+
*/
|
|
239
|
+
async monitor(
|
|
240
|
+
signature: string,
|
|
241
|
+
config?: Partial<ConfirmationConfig>
|
|
242
|
+
): Promise<ConfirmationResult> {
|
|
243
|
+
const fullConfig = { ...this.defaultConfig, ...config };
|
|
244
|
+
|
|
245
|
+
// Check cache first
|
|
246
|
+
if (fullConfig.enableCache) {
|
|
247
|
+
const cached = this.getCachedResult(signature);
|
|
248
|
+
if (cached) {
|
|
249
|
+
return cached;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check if already being monitored
|
|
254
|
+
const existing = this.monitored.get(signature);
|
|
255
|
+
if (existing) {
|
|
256
|
+
return this.waitForResult(existing);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Start monitoring
|
|
260
|
+
const monitored: MonitoredSignature = {
|
|
261
|
+
signature,
|
|
262
|
+
startTime: Date.now(),
|
|
263
|
+
config: fullConfig,
|
|
264
|
+
status: ConfirmationStatus.NotFound,
|
|
265
|
+
retryAttempts: 0,
|
|
266
|
+
lastPollTime: 0,
|
|
267
|
+
abortController: new AbortController(),
|
|
268
|
+
callbacks: new Set(),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
this.monitored.set(signature, monitored);
|
|
272
|
+
|
|
273
|
+
if (!this.isRunning) {
|
|
274
|
+
this.start();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Set up timeout
|
|
278
|
+
this.setupTimeout(monitored);
|
|
279
|
+
|
|
280
|
+
return this.waitForResult(monitored);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Monitor multiple signatures
|
|
285
|
+
*/
|
|
286
|
+
async monitorMultiple(
|
|
287
|
+
signatures: string[],
|
|
288
|
+
config?: Partial<ConfirmationConfig>
|
|
289
|
+
): Promise<ConfirmationResult[]> {
|
|
290
|
+
return Promise.all(
|
|
291
|
+
signatures.map(sig => this.monitor(sig, config))
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Cancel monitoring for a signature
|
|
297
|
+
*/
|
|
298
|
+
cancel(signature: string): boolean {
|
|
299
|
+
const monitored = this.monitored.get(signature);
|
|
300
|
+
if (monitored) {
|
|
301
|
+
monitored.abortController.abort();
|
|
302
|
+
this.monitored.delete(signature);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get the current status of a monitored signature
|
|
310
|
+
*/
|
|
311
|
+
getStatus(signature: string): ConfirmationStatus | undefined {
|
|
312
|
+
const monitored = this.monitored.get(signature);
|
|
313
|
+
if (monitored) {
|
|
314
|
+
return monitored.status;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const cached = this.cache.get(signature);
|
|
318
|
+
if (cached) {
|
|
319
|
+
return cached.status;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get cached result if available and not expired
|
|
327
|
+
*/
|
|
328
|
+
getCachedResult(signature: string): ConfirmationResult | undefined {
|
|
329
|
+
const cached = this.cache.get(signature);
|
|
330
|
+
if (!cached) return undefined;
|
|
331
|
+
|
|
332
|
+
const age = Date.now() - cached.completedAt;
|
|
333
|
+
if (age > this.defaultConfig.cacheTtlMs) {
|
|
334
|
+
this.cache.delete(signature);
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return cached;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Clear expired cache entries
|
|
343
|
+
*/
|
|
344
|
+
clearExpiredCache(): number {
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
let cleared = 0;
|
|
347
|
+
|
|
348
|
+
for (const [signature, result] of this.cache) {
|
|
349
|
+
if (now - result.completedAt > this.defaultConfig.cacheTtlMs) {
|
|
350
|
+
this.cache.delete(signature);
|
|
351
|
+
cleared++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return cleared;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get count of actively monitored signatures
|
|
360
|
+
*/
|
|
361
|
+
getActiveCount(): number {
|
|
362
|
+
return this.monitored.size;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get all actively monitored signatures
|
|
367
|
+
*/
|
|
368
|
+
getActiveSignatures(): string[] {
|
|
369
|
+
return Array.from(this.monitored.keys());
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private async waitForResult(monitored: MonitoredSignature): Promise<ConfirmationResult> {
|
|
373
|
+
return new Promise((resolve, reject) => {
|
|
374
|
+
// Add callback to be called when monitoring completes
|
|
375
|
+
const callback = (result: ConfirmationResult) => {
|
|
376
|
+
if (result.status === ConfirmationStatus.Failed ||
|
|
377
|
+
result.status === ConfirmationStatus.TimedOut) {
|
|
378
|
+
reject(new TradeError(500, result.error?.message || 'Confirmation failed'));
|
|
379
|
+
} else {
|
|
380
|
+
resolve(result);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
monitored.callbacks.add(callback);
|
|
385
|
+
|
|
386
|
+
// Check if already completed
|
|
387
|
+
if (this.isTerminalStatus(monitored.status)) {
|
|
388
|
+
const result = this.createResult(monitored);
|
|
389
|
+
this.completeMonitoring(monitored, result);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private async pollAll(): Promise<void> {
|
|
395
|
+
const promises: Promise<void>[] = [];
|
|
396
|
+
|
|
397
|
+
for (const monitored of this.monitored.values()) {
|
|
398
|
+
// Skip if recently polled
|
|
399
|
+
const timeSinceLastPoll = Date.now() - monitored.lastPollTime;
|
|
400
|
+
if (timeSinceLastPoll < monitored.config.pollIntervalMs) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
promises.push(this.pollSignature(monitored));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await Promise.all(promises);
|
|
408
|
+
|
|
409
|
+
// Clean up completed monitors
|
|
410
|
+
this.cleanupCompleted();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private async pollSignature(monitored: MonitoredSignature): Promise<void> {
|
|
414
|
+
if (monitored.abortController.signal.aborted) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
monitored.lastPollTime = Date.now();
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
const status = await this.connection.getSignatureStatus(monitored.signature);
|
|
422
|
+
|
|
423
|
+
if (!status.value) {
|
|
424
|
+
// Transaction not found yet
|
|
425
|
+
this.updateStatus(monitored, ConfirmationStatus.NotFound);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Update slot information
|
|
430
|
+
if (status.value.slot) {
|
|
431
|
+
monitored.slot = status.value.slot;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check for errors
|
|
435
|
+
if (status.value.err) {
|
|
436
|
+
const error: TransactionError = {
|
|
437
|
+
code: -32002,
|
|
438
|
+
message: typeof status.value.err === 'string'
|
|
439
|
+
? status.value.err
|
|
440
|
+
: JSON.stringify(status.value.err),
|
|
441
|
+
};
|
|
442
|
+
monitored.error = error;
|
|
443
|
+
this.updateStatus(monitored, ConfirmationStatus.Failed);
|
|
444
|
+
|
|
445
|
+
const result = this.createResult(monitored);
|
|
446
|
+
this.completeMonitoring(monitored, result);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Determine confirmation status
|
|
451
|
+
const confirmationStatus = status.value.confirmationStatus;
|
|
452
|
+
let newStatus = ConfirmationStatus.Processed;
|
|
453
|
+
|
|
454
|
+
if (confirmationStatus === 'finalized') {
|
|
455
|
+
newStatus = ConfirmationStatus.Finalized;
|
|
456
|
+
} else if (confirmationStatus === 'confirmed') {
|
|
457
|
+
newStatus = ConfirmationStatus.Confirmed;
|
|
458
|
+
} else if (confirmationStatus === 'processed') {
|
|
459
|
+
newStatus = ConfirmationStatus.Processed;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
this.updateStatus(monitored, newStatus);
|
|
463
|
+
|
|
464
|
+
// Check if target commitment reached
|
|
465
|
+
if (this.isTargetReached(newStatus, monitored.config.commitment)) {
|
|
466
|
+
const result = this.createResult(monitored);
|
|
467
|
+
this.completeMonitoring(monitored, result);
|
|
468
|
+
}
|
|
469
|
+
} catch (error) {
|
|
470
|
+
// Retry on error
|
|
471
|
+
monitored.retryAttempts++;
|
|
472
|
+
|
|
473
|
+
if (monitored.retryAttempts >= monitored.config.maxRetries) {
|
|
474
|
+
monitored.error = {
|
|
475
|
+
code: -32003,
|
|
476
|
+
message: error instanceof Error ? error.message : 'Polling error',
|
|
477
|
+
};
|
|
478
|
+
this.updateStatus(monitored, ConfirmationStatus.Failed);
|
|
479
|
+
|
|
480
|
+
const result = this.createResult(monitored);
|
|
481
|
+
this.completeMonitoring(monitored, result);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.reportProgress(monitored);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private setupWebSocket(): void {
|
|
489
|
+
// Note: Full WebSocket implementation would require signatureSubscribe
|
|
490
|
+
// This is a simplified version
|
|
491
|
+
try {
|
|
492
|
+
// WebSocket setup would go here
|
|
493
|
+
// this.wsSubscription = this.connection.onSignature(...);
|
|
494
|
+
} catch {
|
|
495
|
+
// Fall back to polling
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private setupTimeout(monitored: MonitoredSignature): void {
|
|
500
|
+
setTimeout(() => {
|
|
501
|
+
if (this.monitored.has(monitored.signature) &&
|
|
502
|
+
!this.isTerminalStatus(monitored.status)) {
|
|
503
|
+
this.updateStatus(monitored, ConfirmationStatus.TimedOut);
|
|
504
|
+
|
|
505
|
+
const result = this.createResult(monitored);
|
|
506
|
+
this.completeMonitoring(monitored, result);
|
|
507
|
+
}
|
|
508
|
+
}, monitored.config.timeoutMs);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private updateStatus(monitored: MonitoredSignature, status: ConfirmationStatus): void {
|
|
512
|
+
monitored.status = status;
|
|
513
|
+
|
|
514
|
+
if (monitored.config.onStatusUpdate) {
|
|
515
|
+
const result = this.isTerminalStatus(status) ? this.createResult(monitored) : undefined;
|
|
516
|
+
monitored.config.onStatusUpdate(monitored.signature, status, result);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private reportProgress(monitored: MonitoredSignature): void {
|
|
521
|
+
if (monitored.config.onProgress) {
|
|
522
|
+
const elapsedMs = Date.now() - monitored.startTime;
|
|
523
|
+
const progress: ConfirmationProgress = {
|
|
524
|
+
status: monitored.status,
|
|
525
|
+
confirmations: this.getConfirmationsCount(monitored.status),
|
|
526
|
+
slot: monitored.slot,
|
|
527
|
+
elapsedMs,
|
|
528
|
+
estimatedRemainingMs: this.estimateRemainingTime(monitored),
|
|
529
|
+
retryAttempt: monitored.retryAttempts,
|
|
530
|
+
};
|
|
531
|
+
monitored.config.onProgress(monitored.signature, progress);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private getConfirmationsCount(status: ConfirmationStatus): number {
|
|
536
|
+
switch (status) {
|
|
537
|
+
case ConfirmationStatus.NotFound:
|
|
538
|
+
return 0;
|
|
539
|
+
case ConfirmationStatus.Processed:
|
|
540
|
+
return 1;
|
|
541
|
+
case ConfirmationStatus.Confirmed:
|
|
542
|
+
return 2;
|
|
543
|
+
case ConfirmationStatus.Finalized:
|
|
544
|
+
return 3;
|
|
545
|
+
default:
|
|
546
|
+
return 0;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private estimateRemainingTime(monitored: MonitoredSignature): number | undefined {
|
|
551
|
+
const targetConfirmations = this.getConfirmationsCount(
|
|
552
|
+
this.commitmentToStatus(monitored.config.commitment)
|
|
553
|
+
);
|
|
554
|
+
const currentConfirmations = this.getConfirmationsCount(monitored.status);
|
|
555
|
+
|
|
556
|
+
if (currentConfirmations >= targetConfirmations) {
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const elapsedMs = Date.now() - monitored.startTime;
|
|
561
|
+
const avgTimePerConfirmation = elapsedMs / Math.max(currentConfirmations, 1);
|
|
562
|
+
const remainingConfirmations = targetConfirmations - currentConfirmations;
|
|
563
|
+
|
|
564
|
+
return Math.round(avgTimePerConfirmation * remainingConfirmations);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private commitmentToStatus(commitment: Commitment): ConfirmationStatus {
|
|
568
|
+
switch (commitment) {
|
|
569
|
+
case 'processed':
|
|
570
|
+
return ConfirmationStatus.Processed;
|
|
571
|
+
case 'confirmed':
|
|
572
|
+
return ConfirmationStatus.Confirmed;
|
|
573
|
+
case 'finalized':
|
|
574
|
+
return ConfirmationStatus.Finalized;
|
|
575
|
+
default:
|
|
576
|
+
return ConfirmationStatus.Confirmed;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private isTargetReached(current: ConfirmationStatus, target: Commitment): boolean {
|
|
581
|
+
const levels = {
|
|
582
|
+
[ConfirmationStatus.NotFound]: 0,
|
|
583
|
+
[ConfirmationStatus.Processed]: 1,
|
|
584
|
+
[ConfirmationStatus.Confirmed]: 2,
|
|
585
|
+
[ConfirmationStatus.Finalized]: 3,
|
|
586
|
+
[ConfirmationStatus.Failed]: 4,
|
|
587
|
+
[ConfirmationStatus.TimedOut]: 4,
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const currentLevel = levels[current];
|
|
591
|
+
const targetLevel = this.getConfirmationsCount(this.commitmentToStatus(target));
|
|
592
|
+
|
|
593
|
+
return currentLevel >= targetLevel;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private isTerminalStatus(status: ConfirmationStatus): boolean {
|
|
597
|
+
return [
|
|
598
|
+
ConfirmationStatus.Finalized,
|
|
599
|
+
ConfirmationStatus.Failed,
|
|
600
|
+
ConfirmationStatus.TimedOut,
|
|
601
|
+
].includes(status);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private createResult(monitored: MonitoredSignature): ConfirmationResult {
|
|
605
|
+
const completedAt = Date.now();
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
signature: monitored.signature,
|
|
609
|
+
status: monitored.status,
|
|
610
|
+
slot: monitored.slot,
|
|
611
|
+
blockhash: monitored.blockhash,
|
|
612
|
+
error: monitored.error,
|
|
613
|
+
confirmationTimeMs: completedAt - monitored.startTime,
|
|
614
|
+
retryAttempts: monitored.retryAttempts,
|
|
615
|
+
startedAt: monitored.startTime,
|
|
616
|
+
completedAt,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private completeMonitoring(monitored: MonitoredSignature, result: ConfirmationResult): void {
|
|
621
|
+
// Cache the result
|
|
622
|
+
if (monitored.config.enableCache) {
|
|
623
|
+
this.cache.set(monitored.signature, result);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Notify callbacks
|
|
627
|
+
for (const callback of monitored.callbacks) {
|
|
628
|
+
try {
|
|
629
|
+
callback(result);
|
|
630
|
+
} catch {
|
|
631
|
+
// Ignore callback errors
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Clean up
|
|
636
|
+
monitored.callbacks.clear();
|
|
637
|
+
this.monitored.delete(monitored.signature);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private cleanupCompleted(): void {
|
|
641
|
+
const now = Date.now();
|
|
642
|
+
const maxAge = 300000; // 5 minutes
|
|
643
|
+
|
|
644
|
+
for (const [signature, monitored] of this.monitored) {
|
|
645
|
+
if (this.isTerminalStatus(monitored.status)) {
|
|
646
|
+
const age = now - monitored.startTime;
|
|
647
|
+
if (age > maxAge) {
|
|
648
|
+
this.monitored.delete(signature);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ===== Convenience Functions =====
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Create a new confirmation monitor
|
|
659
|
+
*/
|
|
660
|
+
export function createConfirmationMonitor(
|
|
661
|
+
rpcUrl: string,
|
|
662
|
+
config?: Partial<ConfirmationConfig>
|
|
663
|
+
): ConfirmationMonitor {
|
|
664
|
+
return new ConfirmationMonitor(rpcUrl, { ...defaultConfirmationConfig(), ...config });
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Wait for a single transaction confirmation
|
|
669
|
+
*/
|
|
670
|
+
export async function waitForConfirmation(
|
|
671
|
+
connection: Connection,
|
|
672
|
+
signature: string,
|
|
673
|
+
commitment: Commitment = 'confirmed',
|
|
674
|
+
timeoutMs: number = 60000
|
|
675
|
+
): Promise<ConfirmationResult> {
|
|
676
|
+
const monitor = new ConfirmationMonitor(connection.rpcEndpoint, {
|
|
677
|
+
...defaultConfirmationConfig(),
|
|
678
|
+
commitment,
|
|
679
|
+
timeoutMs,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
monitor.start();
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
return await monitor.monitor(signature);
|
|
686
|
+
} finally {
|
|
687
|
+
monitor.stop();
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Check if a transaction is confirmed
|
|
693
|
+
*/
|
|
694
|
+
export async function isConfirmed(
|
|
695
|
+
connection: Connection,
|
|
696
|
+
signature: string,
|
|
697
|
+
commitment: Commitment = 'confirmed'
|
|
698
|
+
): Promise<boolean> {
|
|
699
|
+
try {
|
|
700
|
+
const status = await connection.getSignatureStatus(signature);
|
|
701
|
+
if (!status.value) return false;
|
|
702
|
+
|
|
703
|
+
const levels = ['processed', 'confirmed', 'finalized'];
|
|
704
|
+
const targetIndex = levels.indexOf(commitment);
|
|
705
|
+
const currentIndex = levels.indexOf(status.value.confirmationStatus || 'processed');
|
|
706
|
+
|
|
707
|
+
return currentIndex >= targetIndex && !status.value.err;
|
|
708
|
+
} catch {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|