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,780 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction Pool for Sol Trade SDK
|
|
3
|
+
* Manages a pool of pending transactions with priority-based processing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Connection, Transaction, PublicKey } from '@solana/web3.js';
|
|
7
|
+
import { TradeError, SwqosType, TradeType } from '../../index';
|
|
8
|
+
import { SwqosClient } from '../../swqos/clients';
|
|
9
|
+
import { ExecutionResult, ExecutionStatus } from './async-executor';
|
|
10
|
+
|
|
11
|
+
// ===== Types =====
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Transaction status in the pool
|
|
15
|
+
*/
|
|
16
|
+
export enum TransactionStatus {
|
|
17
|
+
/** Transaction is queued and waiting */
|
|
18
|
+
Queued = 'Queued',
|
|
19
|
+
/** Transaction is being processed */
|
|
20
|
+
Processing = 'Processing',
|
|
21
|
+
/** Transaction submitted to network */
|
|
22
|
+
Submitted = 'Submitted',
|
|
23
|
+
/** Transaction confirmed on chain */
|
|
24
|
+
Confirmed = 'Confirmed',
|
|
25
|
+
/** Transaction failed */
|
|
26
|
+
Failed = 'Failed',
|
|
27
|
+
/** Transaction was dropped */
|
|
28
|
+
Dropped = 'Dropped',
|
|
29
|
+
/** Transaction expired */
|
|
30
|
+
Expired = 'Expired',
|
|
31
|
+
/** Transaction cancelled by user */
|
|
32
|
+
Cancelled = 'Cancelled',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Priority level for transactions
|
|
37
|
+
*/
|
|
38
|
+
export enum PriorityLevel {
|
|
39
|
+
/** Critical priority - process immediately */
|
|
40
|
+
Critical = 0,
|
|
41
|
+
/** High priority - process before normal */
|
|
42
|
+
High = 1,
|
|
43
|
+
/** Normal priority */
|
|
44
|
+
Normal = 2,
|
|
45
|
+
/** Low priority - process when idle */
|
|
46
|
+
Low = 3,
|
|
47
|
+
/** Background priority - lowest */
|
|
48
|
+
Background = 4,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Configuration for the transaction pool
|
|
53
|
+
*/
|
|
54
|
+
export interface PoolConfig {
|
|
55
|
+
/** Maximum number of concurrent transactions */
|
|
56
|
+
maxConcurrent: number;
|
|
57
|
+
/** Maximum queue size */
|
|
58
|
+
maxQueueSize: number;
|
|
59
|
+
/** Default priority for new transactions */
|
|
60
|
+
defaultPriority: PriorityLevel;
|
|
61
|
+
/** Whether to enable priority boosting */
|
|
62
|
+
enablePriorityBoost: boolean;
|
|
63
|
+
/** Time before priority boost in milliseconds */
|
|
64
|
+
priorityBoostDelayMs: number;
|
|
65
|
+
/** Maximum time in queue before expiration */
|
|
66
|
+
maxQueueTimeMs: number;
|
|
67
|
+
/** Polling interval for queue processing */
|
|
68
|
+
pollIntervalMs: number;
|
|
69
|
+
/** Whether to auto-start the pool */
|
|
70
|
+
autoStart: boolean;
|
|
71
|
+
/** Callback for transaction status changes */
|
|
72
|
+
onStatusChange?: (tx: PendingTransaction) => void;
|
|
73
|
+
/** Callback for pool statistics updates */
|
|
74
|
+
onStatsUpdate?: (stats: PoolStats) => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Pending transaction in the pool
|
|
79
|
+
*/
|
|
80
|
+
export interface PendingTransaction {
|
|
81
|
+
/** Unique transaction ID */
|
|
82
|
+
id: string;
|
|
83
|
+
/** Transaction data */
|
|
84
|
+
transaction: Buffer;
|
|
85
|
+
/** Trade type */
|
|
86
|
+
tradeType: TradeType;
|
|
87
|
+
/** Associated SWQOS provider */
|
|
88
|
+
preferredProvider?: SwqosType;
|
|
89
|
+
/** Current status */
|
|
90
|
+
status: TransactionStatus;
|
|
91
|
+
/** Priority level */
|
|
92
|
+
priority: PriorityLevel;
|
|
93
|
+
/** Timestamp when added to pool */
|
|
94
|
+
queuedAt: number;
|
|
95
|
+
/** Timestamp when processing started */
|
|
96
|
+
processingAt?: number;
|
|
97
|
+
/** Timestamp when submitted */
|
|
98
|
+
submittedAt?: number;
|
|
99
|
+
/** Timestamp when confirmed */
|
|
100
|
+
confirmedAt?: number;
|
|
101
|
+
/** Transaction signature (when available) */
|
|
102
|
+
signature?: string;
|
|
103
|
+
/** Error message if failed */
|
|
104
|
+
error?: string;
|
|
105
|
+
/** Number of submission attempts */
|
|
106
|
+
attempts: number;
|
|
107
|
+
/** Maximum allowed attempts */
|
|
108
|
+
maxAttempts: number;
|
|
109
|
+
/** Associated accounts for conflict detection */
|
|
110
|
+
accounts: PublicKey[];
|
|
111
|
+
/** Custom metadata */
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Pool statistics
|
|
117
|
+
*/
|
|
118
|
+
export interface PoolStats {
|
|
119
|
+
/** Total transactions processed */
|
|
120
|
+
totalProcessed: number;
|
|
121
|
+
/** Total transactions successful */
|
|
122
|
+
totalSuccessful: number;
|
|
123
|
+
/** Total transactions failed */
|
|
124
|
+
totalFailed: number;
|
|
125
|
+
/** Current queue size */
|
|
126
|
+
queueSize: number;
|
|
127
|
+
/** Currently processing count */
|
|
128
|
+
processingCount: number;
|
|
129
|
+
/** Average processing time in milliseconds */
|
|
130
|
+
avgProcessingTimeMs: number;
|
|
131
|
+
/** Average wait time in milliseconds */
|
|
132
|
+
avgWaitTimeMs: number;
|
|
133
|
+
/** Transactions by status */
|
|
134
|
+
byStatus: Record<TransactionStatus, number>;
|
|
135
|
+
/** Current throughput (tx/sec) */
|
|
136
|
+
throughput: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Priority calculation result
|
|
141
|
+
*/
|
|
142
|
+
export interface PriorityScore {
|
|
143
|
+
/** Calculated priority score (higher = more important) */
|
|
144
|
+
score: number;
|
|
145
|
+
/** Base priority level */
|
|
146
|
+
basePriority: PriorityLevel;
|
|
147
|
+
/** Age bonus (increases with wait time) */
|
|
148
|
+
ageBonus: number;
|
|
149
|
+
/** Custom adjustments */
|
|
150
|
+
adjustments: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ===== Default Configurations =====
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get default pool configuration
|
|
157
|
+
*/
|
|
158
|
+
export function defaultPoolConfig(): PoolConfig {
|
|
159
|
+
return {
|
|
160
|
+
maxConcurrent: 5,
|
|
161
|
+
maxQueueSize: 100,
|
|
162
|
+
defaultPriority: PriorityLevel.Normal,
|
|
163
|
+
enablePriorityBoost: true,
|
|
164
|
+
priorityBoostDelayMs: 5000,
|
|
165
|
+
maxQueueTimeMs: 60000,
|
|
166
|
+
pollIntervalMs: 100,
|
|
167
|
+
autoStart: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get high-throughput pool configuration
|
|
173
|
+
*/
|
|
174
|
+
export function highThroughputPoolConfig(): PoolConfig {
|
|
175
|
+
return {
|
|
176
|
+
maxConcurrent: 10,
|
|
177
|
+
maxQueueSize: 200,
|
|
178
|
+
defaultPriority: PriorityLevel.Normal,
|
|
179
|
+
enablePriorityBoost: true,
|
|
180
|
+
priorityBoostDelayMs: 2000,
|
|
181
|
+
maxQueueTimeMs: 30000,
|
|
182
|
+
pollIntervalMs: 50,
|
|
183
|
+
autoStart: true,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get conservative pool configuration
|
|
189
|
+
*/
|
|
190
|
+
export function conservativePoolConfig(): PoolConfig {
|
|
191
|
+
return {
|
|
192
|
+
maxConcurrent: 2,
|
|
193
|
+
maxQueueSize: 50,
|
|
194
|
+
defaultPriority: PriorityLevel.Normal,
|
|
195
|
+
enablePriorityBoost: false,
|
|
196
|
+
priorityBoostDelayMs: 10000,
|
|
197
|
+
maxQueueTimeMs: 120000,
|
|
198
|
+
pollIntervalMs: 200,
|
|
199
|
+
autoStart: true,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ===== Priority Calculator =====
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Calculates priority scores for transactions
|
|
207
|
+
*/
|
|
208
|
+
export class PriorityCalculator {
|
|
209
|
+
private baseScores: Map<PriorityLevel, number> = new Map([
|
|
210
|
+
[PriorityLevel.Critical, 1000],
|
|
211
|
+
[PriorityLevel.High, 500],
|
|
212
|
+
[PriorityLevel.Normal, 100],
|
|
213
|
+
[PriorityLevel.Low, 50],
|
|
214
|
+
[PriorityLevel.Background, 10],
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
constructor(private config: PoolConfig) {}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Calculate priority score for a transaction
|
|
221
|
+
*/
|
|
222
|
+
calculate(tx: PendingTransaction): PriorityScore {
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
const ageMs = now - tx.queuedAt;
|
|
225
|
+
const baseScore = this.baseScores.get(tx.priority) || 100;
|
|
226
|
+
|
|
227
|
+
let ageBonus = 0;
|
|
228
|
+
if (this.config.enablePriorityBoost && ageMs > this.config.priorityBoostDelayMs) {
|
|
229
|
+
ageBonus = Math.min(
|
|
230
|
+
Math.floor((ageMs - this.config.priorityBoostDelayMs) / 1000) * 10,
|
|
231
|
+
200 // Max age bonus
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const adjustments = this.calculateAdjustments(tx);
|
|
236
|
+
const score = baseScore + ageBonus + adjustments;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
score,
|
|
240
|
+
basePriority: tx.priority,
|
|
241
|
+
ageBonus,
|
|
242
|
+
adjustments,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Compare two transactions by priority
|
|
248
|
+
*/
|
|
249
|
+
compare(tx1: PendingTransaction, tx2: PendingTransaction): number {
|
|
250
|
+
const score1 = this.calculate(tx1).score;
|
|
251
|
+
const score2 = this.calculate(tx2).score;
|
|
252
|
+
return score2 - score1; // Higher score first
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private calculateAdjustments(tx: PendingTransaction): number {
|
|
256
|
+
let adjustments = 0;
|
|
257
|
+
|
|
258
|
+
// Boost for retried transactions
|
|
259
|
+
if (tx.attempts > 0) {
|
|
260
|
+
adjustments += tx.attempts * 5;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Boost for transactions nearing expiration
|
|
264
|
+
const ageMs = Date.now() - tx.queuedAt;
|
|
265
|
+
const timeRemaining = this.config.maxQueueTimeMs - ageMs;
|
|
266
|
+
if (timeRemaining < 10000) {
|
|
267
|
+
adjustments += 50;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return adjustments;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ===== Transaction Pool =====
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Manages a pool of transactions with priority-based processing
|
|
278
|
+
*/
|
|
279
|
+
export class TransactionPool {
|
|
280
|
+
private queue: PendingTransaction[] = [];
|
|
281
|
+
private processing: Map<string, PendingTransaction> = new Map();
|
|
282
|
+
private completed: Map<string, PendingTransaction> = new Map();
|
|
283
|
+
private calculator: PriorityCalculator;
|
|
284
|
+
private clients: Map<SwqosType, SwqosClient> = new Map();
|
|
285
|
+
private connection: Connection;
|
|
286
|
+
private isRunning: boolean = false;
|
|
287
|
+
private pollInterval?: NodeJS.Timeout;
|
|
288
|
+
private stats: PoolStats;
|
|
289
|
+
private processingTimes: number[] = [];
|
|
290
|
+
private waitTimes: number[] = [];
|
|
291
|
+
|
|
292
|
+
constructor(
|
|
293
|
+
private rpcUrl: string,
|
|
294
|
+
private config: PoolConfig = defaultPoolConfig()
|
|
295
|
+
) {
|
|
296
|
+
this.connection = new Connection(rpcUrl, 'confirmed');
|
|
297
|
+
this.calculator = new PriorityCalculator(config);
|
|
298
|
+
this.stats = this.initializeStats();
|
|
299
|
+
|
|
300
|
+
if (config.autoStart) {
|
|
301
|
+
this.start();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Add a client to the pool
|
|
307
|
+
*/
|
|
308
|
+
addClient(client: SwqosClient): void {
|
|
309
|
+
this.clients.set(client.getSwqosType(), client);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Remove a client from the pool
|
|
314
|
+
*/
|
|
315
|
+
removeClient(type: SwqosType): void {
|
|
316
|
+
this.clients.delete(type);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Submit a transaction to the pool
|
|
321
|
+
*/
|
|
322
|
+
async submit(
|
|
323
|
+
transaction: Buffer,
|
|
324
|
+
tradeType: TradeType,
|
|
325
|
+
options: {
|
|
326
|
+
priority?: PriorityLevel;
|
|
327
|
+
preferredProvider?: SwqosType;
|
|
328
|
+
maxAttempts?: number;
|
|
329
|
+
accounts?: PublicKey[];
|
|
330
|
+
metadata?: Record<string, unknown>;
|
|
331
|
+
} = {}
|
|
332
|
+
): Promise<string> {
|
|
333
|
+
if (this.queue.length >= this.config.maxQueueSize) {
|
|
334
|
+
throw new TradeError(429, 'Transaction pool queue is full');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const id = this.generateTransactionId();
|
|
338
|
+
const pendingTx: PendingTransaction = {
|
|
339
|
+
id,
|
|
340
|
+
transaction,
|
|
341
|
+
tradeType,
|
|
342
|
+
preferredProvider: options.preferredProvider,
|
|
343
|
+
status: TransactionStatus.Queued,
|
|
344
|
+
priority: options.priority ?? this.config.defaultPriority,
|
|
345
|
+
queuedAt: Date.now(),
|
|
346
|
+
attempts: 0,
|
|
347
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
348
|
+
accounts: options.accounts ?? [],
|
|
349
|
+
metadata: options.metadata,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
this.queue.push(pendingTx);
|
|
353
|
+
this.sortQueue();
|
|
354
|
+
this.updateStats();
|
|
355
|
+
|
|
356
|
+
return id;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Get transaction by ID
|
|
361
|
+
*/
|
|
362
|
+
getTransaction(id: string): PendingTransaction | undefined {
|
|
363
|
+
// Check queue
|
|
364
|
+
const queued = this.queue.find(tx => tx.id === id);
|
|
365
|
+
if (queued) return queued;
|
|
366
|
+
|
|
367
|
+
// Check processing
|
|
368
|
+
const processing = this.processing.get(id);
|
|
369
|
+
if (processing) return processing;
|
|
370
|
+
|
|
371
|
+
// Check completed
|
|
372
|
+
return this.completed.get(id);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Cancel a pending transaction
|
|
377
|
+
*/
|
|
378
|
+
cancel(id: string): boolean {
|
|
379
|
+
// Try to remove from queue
|
|
380
|
+
const index = this.queue.findIndex(tx => tx.id === id);
|
|
381
|
+
if (index !== -1) {
|
|
382
|
+
const tx = this.queue.splice(index, 1)[0]!;
|
|
383
|
+
tx.status = TransactionStatus.Cancelled;
|
|
384
|
+
this.completed.set(id, tx);
|
|
385
|
+
this.notifyStatusChange(tx);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Cannot cancel if already processing
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Start the transaction pool processing
|
|
395
|
+
*/
|
|
396
|
+
start(): void {
|
|
397
|
+
if (this.isRunning) return;
|
|
398
|
+
|
|
399
|
+
this.isRunning = true;
|
|
400
|
+
this.pollInterval = setInterval(() => {
|
|
401
|
+
this.processQueue();
|
|
402
|
+
}, this.config.pollIntervalMs);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Stop the transaction pool processing
|
|
407
|
+
*/
|
|
408
|
+
stop(): void {
|
|
409
|
+
this.isRunning = false;
|
|
410
|
+
if (this.pollInterval) {
|
|
411
|
+
clearInterval(this.pollInterval);
|
|
412
|
+
this.pollInterval = undefined;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Check if pool is running
|
|
418
|
+
*/
|
|
419
|
+
isActive(): boolean {
|
|
420
|
+
return this.isRunning;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get current pool statistics
|
|
425
|
+
*/
|
|
426
|
+
getStats(): PoolStats {
|
|
427
|
+
return { ...this.stats };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get all queued transactions
|
|
432
|
+
*/
|
|
433
|
+
getQueue(): PendingTransaction[] {
|
|
434
|
+
return [...this.queue];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Get all processing transactions
|
|
439
|
+
*/
|
|
440
|
+
getProcessing(): PendingTransaction[] {
|
|
441
|
+
return Array.from(this.processing.values());
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Clear completed transactions older than specified age
|
|
446
|
+
*/
|
|
447
|
+
clearCompleted(maxAgeMs: number = 300000): number {
|
|
448
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
449
|
+
let cleared = 0;
|
|
450
|
+
|
|
451
|
+
for (const [id, tx] of this.completed) {
|
|
452
|
+
const completedTime = tx.confirmedAt || tx.submittedAt || tx.processingAt;
|
|
453
|
+
if (completedTime && completedTime < cutoff) {
|
|
454
|
+
this.completed.delete(id);
|
|
455
|
+
cleared++;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return cleared;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Wait for a transaction to complete
|
|
464
|
+
*/
|
|
465
|
+
async waitForTransaction(
|
|
466
|
+
id: string,
|
|
467
|
+
timeoutMs: number = 60000
|
|
468
|
+
): Promise<PendingTransaction> {
|
|
469
|
+
const startTime = Date.now();
|
|
470
|
+
|
|
471
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
472
|
+
const tx = this.getTransaction(id);
|
|
473
|
+
if (!tx) {
|
|
474
|
+
throw new TradeError(404, `Transaction ${id} not found`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (this.isTerminalStatus(tx.status)) {
|
|
478
|
+
return tx;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
await this.sleep(100);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
throw new TradeError(408, `Timeout waiting for transaction ${id}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private async processQueue(): Promise<void> {
|
|
488
|
+
if (!this.isRunning) return;
|
|
489
|
+
|
|
490
|
+
// Remove expired transactions
|
|
491
|
+
this.removeExpired();
|
|
492
|
+
|
|
493
|
+
// Process available slots
|
|
494
|
+
while (
|
|
495
|
+
this.processing.size < this.config.maxConcurrent &&
|
|
496
|
+
this.queue.length > 0
|
|
497
|
+
) {
|
|
498
|
+
const tx = this.queue.shift();
|
|
499
|
+
if (!tx) break;
|
|
500
|
+
|
|
501
|
+
this.processTransaction(tx);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
this.updateStats();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private async processTransaction(tx: PendingTransaction): Promise<void> {
|
|
508
|
+
tx.status = TransactionStatus.Processing;
|
|
509
|
+
tx.processingAt = Date.now();
|
|
510
|
+
this.processing.set(tx.id, tx);
|
|
511
|
+
this.notifyStatusChange(tx);
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
const result = await this.submitTransaction(tx);
|
|
515
|
+
|
|
516
|
+
if (result.success) {
|
|
517
|
+
tx.status = TransactionStatus.Confirmed;
|
|
518
|
+
tx.signature = result.signature;
|
|
519
|
+
tx.confirmedAt = Date.now();
|
|
520
|
+
this.stats.totalSuccessful++;
|
|
521
|
+
} else {
|
|
522
|
+
tx.attempts++;
|
|
523
|
+
if (tx.attempts >= tx.maxAttempts) {
|
|
524
|
+
tx.status = TransactionStatus.Failed;
|
|
525
|
+
tx.error = result.error;
|
|
526
|
+
this.stats.totalFailed++;
|
|
527
|
+
} else {
|
|
528
|
+
// Re-queue for retry
|
|
529
|
+
tx.status = TransactionStatus.Queued;
|
|
530
|
+
this.queue.push(tx);
|
|
531
|
+
this.sortQueue();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
} catch (error) {
|
|
535
|
+
tx.attempts++;
|
|
536
|
+
if (tx.attempts >= tx.maxAttempts) {
|
|
537
|
+
tx.status = TransactionStatus.Failed;
|
|
538
|
+
tx.error = error instanceof Error ? error.message : 'Unknown error';
|
|
539
|
+
this.stats.totalFailed++;
|
|
540
|
+
} else {
|
|
541
|
+
tx.status = TransactionStatus.Queued;
|
|
542
|
+
this.queue.push(tx);
|
|
543
|
+
this.sortQueue();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (this.isTerminalStatus(tx.status)) {
|
|
548
|
+
this.processing.delete(tx.id);
|
|
549
|
+
this.completed.set(tx.id, tx);
|
|
550
|
+
this.stats.totalProcessed++;
|
|
551
|
+
|
|
552
|
+
// Track processing time
|
|
553
|
+
if (tx.processingAt && tx.confirmedAt) {
|
|
554
|
+
this.processingTimes.push(tx.confirmedAt - tx.processingAt);
|
|
555
|
+
if (this.processingTimes.length > 100) {
|
|
556
|
+
this.processingTimes.shift();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Track wait time
|
|
561
|
+
if (tx.processingAt) {
|
|
562
|
+
this.waitTimes.push(tx.processingAt - tx.queuedAt);
|
|
563
|
+
if (this.waitTimes.length > 100) {
|
|
564
|
+
this.waitTimes.shift();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
this.notifyStatusChange(tx);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private async submitTransaction(tx: PendingTransaction): Promise<ExecutionResult> {
|
|
573
|
+
const providers = this.getProviders(tx.preferredProvider);
|
|
574
|
+
|
|
575
|
+
for (const provider of providers) {
|
|
576
|
+
try {
|
|
577
|
+
tx.status = TransactionStatus.Submitted;
|
|
578
|
+
tx.submittedAt = Date.now();
|
|
579
|
+
this.notifyStatusChange(tx);
|
|
580
|
+
|
|
581
|
+
const signature = await provider.sendTransaction(
|
|
582
|
+
tx.tradeType,
|
|
583
|
+
tx.transaction,
|
|
584
|
+
false
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
signature,
|
|
589
|
+
success: true,
|
|
590
|
+
status: ExecutionStatus.Confirmed,
|
|
591
|
+
provider: provider.getSwqosType(),
|
|
592
|
+
attempts: tx.attempts + 1,
|
|
593
|
+
executionTimeMs: Date.now() - tx.queuedAt,
|
|
594
|
+
};
|
|
595
|
+
} catch (error) {
|
|
596
|
+
// Try next provider
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
signature: '',
|
|
603
|
+
success: false,
|
|
604
|
+
status: ExecutionStatus.Failed,
|
|
605
|
+
error: 'All providers failed',
|
|
606
|
+
attempts: tx.attempts + 1,
|
|
607
|
+
executionTimeMs: Date.now() - tx.queuedAt,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private getProviders(preferred?: SwqosType): SwqosClient[] {
|
|
612
|
+
const providers: SwqosClient[] = [];
|
|
613
|
+
|
|
614
|
+
if (preferred) {
|
|
615
|
+
const preferredClient = this.clients.get(preferred);
|
|
616
|
+
if (preferredClient) {
|
|
617
|
+
providers.push(preferredClient);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Add remaining providers
|
|
622
|
+
for (const [type, client] of this.clients) {
|
|
623
|
+
if (type !== preferred) {
|
|
624
|
+
providers.push(client);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return providers;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private removeExpired(): void {
|
|
632
|
+
const now = Date.now();
|
|
633
|
+
const expired: PendingTransaction[] = [];
|
|
634
|
+
|
|
635
|
+
this.queue = this.queue.filter(tx => {
|
|
636
|
+
if (now - tx.queuedAt > this.config.maxQueueTimeMs) {
|
|
637
|
+
tx.status = TransactionStatus.Expired;
|
|
638
|
+
expired.push(tx);
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
for (const tx of expired) {
|
|
645
|
+
this.completed.set(tx.id, tx);
|
|
646
|
+
this.notifyStatusChange(tx);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
private sortQueue(): void {
|
|
651
|
+
this.queue.sort((a, b) => this.calculator.compare(a, b));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private isTerminalStatus(status: TransactionStatus): boolean {
|
|
655
|
+
return [
|
|
656
|
+
TransactionStatus.Confirmed,
|
|
657
|
+
TransactionStatus.Failed,
|
|
658
|
+
TransactionStatus.Dropped,
|
|
659
|
+
TransactionStatus.Expired,
|
|
660
|
+
TransactionStatus.Cancelled,
|
|
661
|
+
].includes(status);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private initializeStats(): PoolStats {
|
|
665
|
+
return {
|
|
666
|
+
totalProcessed: 0,
|
|
667
|
+
totalSuccessful: 0,
|
|
668
|
+
totalFailed: 0,
|
|
669
|
+
queueSize: 0,
|
|
670
|
+
processingCount: 0,
|
|
671
|
+
avgProcessingTimeMs: 0,
|
|
672
|
+
avgWaitTimeMs: 0,
|
|
673
|
+
byStatus: {
|
|
674
|
+
[TransactionStatus.Queued]: 0,
|
|
675
|
+
[TransactionStatus.Processing]: 0,
|
|
676
|
+
[TransactionStatus.Submitted]: 0,
|
|
677
|
+
[TransactionStatus.Confirmed]: 0,
|
|
678
|
+
[TransactionStatus.Failed]: 0,
|
|
679
|
+
[TransactionStatus.Dropped]: 0,
|
|
680
|
+
[TransactionStatus.Expired]: 0,
|
|
681
|
+
[TransactionStatus.Cancelled]: 0,
|
|
682
|
+
},
|
|
683
|
+
throughput: 0,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
private updateStats(): void {
|
|
688
|
+
// Calculate averages
|
|
689
|
+
const avgProcessingTime = this.processingTimes.length > 0
|
|
690
|
+
? this.processingTimes.reduce((a, b) => a + b, 0) / this.processingTimes.length
|
|
691
|
+
: 0;
|
|
692
|
+
|
|
693
|
+
const avgWaitTime = this.waitTimes.length > 0
|
|
694
|
+
? this.waitTimes.reduce((a, b) => a + b, 0) / this.waitTimes.length
|
|
695
|
+
: 0;
|
|
696
|
+
|
|
697
|
+
// Count by status
|
|
698
|
+
const byStatus: Record<TransactionStatus, number> = { ...this.stats.byStatus };
|
|
699
|
+
for (const status of Object.values(TransactionStatus)) {
|
|
700
|
+
byStatus[status] = 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
for (const tx of this.queue) {
|
|
704
|
+
byStatus[tx.status]++;
|
|
705
|
+
}
|
|
706
|
+
for (const tx of this.processing.values()) {
|
|
707
|
+
byStatus[tx.status]++;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
this.stats = {
|
|
711
|
+
...this.stats,
|
|
712
|
+
queueSize: this.queue.length,
|
|
713
|
+
processingCount: this.processing.size,
|
|
714
|
+
avgProcessingTimeMs: Math.round(avgProcessingTime),
|
|
715
|
+
avgWaitTimeMs: Math.round(avgWaitTime),
|
|
716
|
+
byStatus,
|
|
717
|
+
throughput: this.calculateThroughput(),
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
if (this.config.onStatsUpdate) {
|
|
721
|
+
this.config.onStatsUpdate(this.stats);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private calculateThroughput(): number {
|
|
726
|
+
// Simple throughput calculation based on recent completions
|
|
727
|
+
const recentTimeWindow = 60000; // 1 minute
|
|
728
|
+
const cutoff = Date.now() - recentTimeWindow;
|
|
729
|
+
|
|
730
|
+
let recentCompletions = 0;
|
|
731
|
+
for (const tx of this.completed.values()) {
|
|
732
|
+
if (tx.confirmedAt && tx.confirmedAt > cutoff) {
|
|
733
|
+
recentCompletions++;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return Math.round((recentCompletions / recentTimeWindow) * 1000) / 1000;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private notifyStatusChange(tx: PendingTransaction): void {
|
|
741
|
+
if (this.config.onStatusChange) {
|
|
742
|
+
this.config.onStatusChange(tx);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private generateTransactionId(): string {
|
|
747
|
+
return `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private sleep(ms: number): Promise<void> {
|
|
751
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// ===== Convenience Functions =====
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Create a new transaction pool
|
|
759
|
+
*/
|
|
760
|
+
export function createTransactionPool(
|
|
761
|
+
rpcUrl: string,
|
|
762
|
+
config?: Partial<PoolConfig>
|
|
763
|
+
): TransactionPool {
|
|
764
|
+
return new TransactionPool(rpcUrl, { ...defaultPoolConfig(), ...config });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Submit a single transaction to a pool
|
|
769
|
+
*/
|
|
770
|
+
export async function submitToPool(
|
|
771
|
+
pool: TransactionPool,
|
|
772
|
+
transaction: Buffer,
|
|
773
|
+
tradeType: TradeType,
|
|
774
|
+
options?: {
|
|
775
|
+
priority?: PriorityLevel;
|
|
776
|
+
preferredProvider?: SwqosType;
|
|
777
|
+
}
|
|
778
|
+
): Promise<string> {
|
|
779
|
+
return pool.submit(transaction, tradeType, options);
|
|
780
|
+
}
|