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,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Trade Executor for Sol Trade SDK
|
|
3
|
+
* Implements asynchronous trade execution with configurable submission modes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Connection, Transaction, Commitment } from '@solana/web3.js';
|
|
7
|
+
import { TradeError, SwqosType, TradeType } from '../../index';
|
|
8
|
+
import { SwqosClient } from '../../swqos/clients';
|
|
9
|
+
|
|
10
|
+
// ===== Types =====
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Submission mode for transaction execution
|
|
14
|
+
*/
|
|
15
|
+
export enum SubmitMode {
|
|
16
|
+
/** Submit to single fastest provider */
|
|
17
|
+
Single = 'Single',
|
|
18
|
+
/** Submit to multiple providers in parallel */
|
|
19
|
+
Parallel = 'Parallel',
|
|
20
|
+
/** Submit with fallback providers */
|
|
21
|
+
Fallback = 'Fallback',
|
|
22
|
+
/** Submit with redundancy for high availability */
|
|
23
|
+
Redundant = 'Redundant',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execution status for tracking transaction state
|
|
28
|
+
*/
|
|
29
|
+
export enum ExecutionStatus {
|
|
30
|
+
/** Initial pending state */
|
|
31
|
+
Pending = 'Pending',
|
|
32
|
+
/** Transaction submitted to provider */
|
|
33
|
+
Submitted = 'Submitted',
|
|
34
|
+
/** Transaction confirmed on chain */
|
|
35
|
+
Confirmed = 'Confirmed',
|
|
36
|
+
/** Transaction finalized */
|
|
37
|
+
Finalized = 'Finalized',
|
|
38
|
+
/** Transaction failed */
|
|
39
|
+
Failed = 'Failed',
|
|
40
|
+
/** Transaction timed out */
|
|
41
|
+
TimedOut = 'TimedOut',
|
|
42
|
+
/** Transaction cancelled */
|
|
43
|
+
Cancelled = 'Cancelled',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Configuration for async trade execution
|
|
48
|
+
*/
|
|
49
|
+
export interface ExecutionConfig {
|
|
50
|
+
/** Submission mode */
|
|
51
|
+
submitMode: SubmitMode;
|
|
52
|
+
/** Whether to wait for confirmation */
|
|
53
|
+
waitConfirmation: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Commitment level for confirmation polling.
|
|
56
|
+
* `finalized` waits for finalized only; any other value follows Rust
|
|
57
|
+
* `poll_any_transaction_confirmation` (confirmed or finalized, not processed-only).
|
|
58
|
+
*/
|
|
59
|
+
commitment: Commitment;
|
|
60
|
+
/** Maximum number of retries */
|
|
61
|
+
maxRetries: number;
|
|
62
|
+
/** Delay between retries in milliseconds */
|
|
63
|
+
retryDelayMs: number;
|
|
64
|
+
/** Timeout for execution in milliseconds */
|
|
65
|
+
timeoutMs: number;
|
|
66
|
+
/** Whether to abort on first success */
|
|
67
|
+
abortOnSuccess: boolean;
|
|
68
|
+
/** Priority providers to use (empty = all) */
|
|
69
|
+
priorityProviders: SwqosType[];
|
|
70
|
+
/** Callback for status updates */
|
|
71
|
+
onStatusUpdate?: (status: ExecutionStatus, result?: ExecutionResult) => void;
|
|
72
|
+
/** Callback for progress updates */
|
|
73
|
+
onProgress?: (progress: ExecutionProgress) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Execution progress information
|
|
78
|
+
*/
|
|
79
|
+
export interface ExecutionProgress {
|
|
80
|
+
/** Current attempt number */
|
|
81
|
+
attempt: number;
|
|
82
|
+
/** Total attempts allowed */
|
|
83
|
+
totalAttempts: number;
|
|
84
|
+
/** Current provider being used */
|
|
85
|
+
currentProvider?: SwqosType;
|
|
86
|
+
/** Number of providers tried */
|
|
87
|
+
providersTried: number;
|
|
88
|
+
/** Elapsed time in milliseconds */
|
|
89
|
+
elapsedMs: number;
|
|
90
|
+
/** Estimated time remaining in milliseconds */
|
|
91
|
+
estimatedRemainingMs?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Result of trade execution
|
|
96
|
+
*/
|
|
97
|
+
export interface ExecutionResult {
|
|
98
|
+
/** Transaction signature */
|
|
99
|
+
signature: string;
|
|
100
|
+
/** Execution success status */
|
|
101
|
+
success: boolean;
|
|
102
|
+
/** Final execution status */
|
|
103
|
+
status: ExecutionStatus;
|
|
104
|
+
/** Error message if failed */
|
|
105
|
+
error?: string;
|
|
106
|
+
/** Provider that succeeded (if any) */
|
|
107
|
+
provider?: SwqosType;
|
|
108
|
+
/** Number of attempts made */
|
|
109
|
+
attempts: number;
|
|
110
|
+
/** Total execution time in milliseconds */
|
|
111
|
+
executionTimeMs: number;
|
|
112
|
+
/** Time to confirmation in milliseconds */
|
|
113
|
+
confirmationTimeMs?: number;
|
|
114
|
+
/** Slot when transaction was confirmed */
|
|
115
|
+
slot?: number;
|
|
116
|
+
/** Blockhash used for transaction */
|
|
117
|
+
blockhash?: string;
|
|
118
|
+
/** Additional metadata */
|
|
119
|
+
metadata?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Internal execution state
|
|
124
|
+
*/
|
|
125
|
+
interface ExecutionState {
|
|
126
|
+
id: string;
|
|
127
|
+
startTime: number;
|
|
128
|
+
attempts: number;
|
|
129
|
+
providersTried: Set<SwqosType>;
|
|
130
|
+
currentStatus: ExecutionStatus;
|
|
131
|
+
abortController: AbortController;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ===== Default Configurations =====
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get default execution configuration
|
|
138
|
+
*/
|
|
139
|
+
export function defaultExecutionConfig(): ExecutionConfig {
|
|
140
|
+
return {
|
|
141
|
+
submitMode: SubmitMode.Parallel,
|
|
142
|
+
waitConfirmation: true,
|
|
143
|
+
commitment: 'confirmed',
|
|
144
|
+
maxRetries: 3,
|
|
145
|
+
retryDelayMs: 100,
|
|
146
|
+
timeoutMs: 60000,
|
|
147
|
+
abortOnSuccess: true,
|
|
148
|
+
priorityProviders: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get execution config for high-frequency trading
|
|
154
|
+
*/
|
|
155
|
+
export function hftExecutionConfig(): ExecutionConfig {
|
|
156
|
+
return {
|
|
157
|
+
submitMode: SubmitMode.Parallel,
|
|
158
|
+
waitConfirmation: false,
|
|
159
|
+
commitment: 'processed',
|
|
160
|
+
maxRetries: 1,
|
|
161
|
+
retryDelayMs: 50,
|
|
162
|
+
timeoutMs: 10000,
|
|
163
|
+
abortOnSuccess: true,
|
|
164
|
+
priorityProviders: [SwqosType.Jito, SwqosType.Bloxroute],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get execution config for reliable execution
|
|
170
|
+
*/
|
|
171
|
+
export function reliableExecutionConfig(): ExecutionConfig {
|
|
172
|
+
return {
|
|
173
|
+
submitMode: SubmitMode.Fallback,
|
|
174
|
+
waitConfirmation: true,
|
|
175
|
+
commitment: 'finalized',
|
|
176
|
+
maxRetries: 5,
|
|
177
|
+
retryDelayMs: 500,
|
|
178
|
+
timeoutMs: 120000,
|
|
179
|
+
abortOnSuccess: true,
|
|
180
|
+
priorityProviders: [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ===== Async Trade Executor =====
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Async trade executor with multiple submission modes
|
|
188
|
+
*/
|
|
189
|
+
export class AsyncTradeExecutor {
|
|
190
|
+
private clients: Map<SwqosType, SwqosClient> = new Map();
|
|
191
|
+
private connection: Connection;
|
|
192
|
+
private activeExecutions: Map<string, ExecutionState> = new Map();
|
|
193
|
+
|
|
194
|
+
constructor(
|
|
195
|
+
private rpcUrl: string,
|
|
196
|
+
clients: SwqosClient[] = []
|
|
197
|
+
) {
|
|
198
|
+
this.connection = new Connection(rpcUrl, 'confirmed');
|
|
199
|
+
for (const client of clients) {
|
|
200
|
+
this.clients.set(client.getSwqosType(), client);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Add a SWQOS client
|
|
206
|
+
*/
|
|
207
|
+
addClient(client: SwqosClient): void {
|
|
208
|
+
this.clients.set(client.getSwqosType(), client);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Remove a SWQOS client
|
|
213
|
+
*/
|
|
214
|
+
removeClient(type: SwqosType): void {
|
|
215
|
+
this.clients.delete(type);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get all registered clients
|
|
220
|
+
*/
|
|
221
|
+
getClients(): Map<SwqosType, SwqosClient> {
|
|
222
|
+
return new Map(this.clients);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Execute a trade asynchronously
|
|
227
|
+
*/
|
|
228
|
+
async execute(
|
|
229
|
+
tradeType: TradeType,
|
|
230
|
+
transaction: Buffer,
|
|
231
|
+
config: Partial<ExecutionConfig> = {}
|
|
232
|
+
): Promise<ExecutionResult> {
|
|
233
|
+
const fullConfig = { ...defaultExecutionConfig(), ...config };
|
|
234
|
+
const executionId = this.generateExecutionId();
|
|
235
|
+
|
|
236
|
+
const state: ExecutionState = {
|
|
237
|
+
id: executionId,
|
|
238
|
+
startTime: Date.now(),
|
|
239
|
+
attempts: 0,
|
|
240
|
+
providersTried: new Set(),
|
|
241
|
+
currentStatus: ExecutionStatus.Pending,
|
|
242
|
+
abortController: new AbortController(),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
this.activeExecutions.set(executionId, state);
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
this.updateStatus(state, ExecutionStatus.Pending, fullConfig);
|
|
249
|
+
|
|
250
|
+
const result = await this.executeWithTimeout(
|
|
251
|
+
tradeType,
|
|
252
|
+
transaction,
|
|
253
|
+
fullConfig,
|
|
254
|
+
state
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
} finally {
|
|
259
|
+
this.activeExecutions.delete(executionId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Cancel an active execution
|
|
265
|
+
*/
|
|
266
|
+
cancel(executionId: string): boolean {
|
|
267
|
+
const state = this.activeExecutions.get(executionId);
|
|
268
|
+
if (state) {
|
|
269
|
+
state.abortController.abort();
|
|
270
|
+
this.updateStatus(state, ExecutionStatus.Cancelled);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Cancel all active executions
|
|
278
|
+
*/
|
|
279
|
+
cancelAll(): number {
|
|
280
|
+
let count = 0;
|
|
281
|
+
for (const [id, state] of this.activeExecutions) {
|
|
282
|
+
state.abortController.abort();
|
|
283
|
+
this.updateStatus(state, ExecutionStatus.Cancelled);
|
|
284
|
+
count++;
|
|
285
|
+
}
|
|
286
|
+
return count;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get active execution count
|
|
291
|
+
*/
|
|
292
|
+
getActiveExecutionCount(): number {
|
|
293
|
+
return this.activeExecutions.size;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private async executeWithTimeout(
|
|
297
|
+
tradeType: TradeType,
|
|
298
|
+
transaction: Buffer,
|
|
299
|
+
config: ExecutionConfig,
|
|
300
|
+
state: ExecutionState
|
|
301
|
+
): Promise<ExecutionResult> {
|
|
302
|
+
return new Promise((resolve, reject) => {
|
|
303
|
+
const timeoutId = setTimeout(() => {
|
|
304
|
+
this.updateStatus(state, ExecutionStatus.TimedOut, config);
|
|
305
|
+
resolve(this.createTimeoutResult(state));
|
|
306
|
+
}, config.timeoutMs);
|
|
307
|
+
|
|
308
|
+
this.executeInternal(tradeType, transaction, config, state)
|
|
309
|
+
.then(result => {
|
|
310
|
+
clearTimeout(timeoutId);
|
|
311
|
+
resolve(result);
|
|
312
|
+
})
|
|
313
|
+
.catch(error => {
|
|
314
|
+
clearTimeout(timeoutId);
|
|
315
|
+
reject(error);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private async executeInternal(
|
|
321
|
+
tradeType: TradeType,
|
|
322
|
+
transaction: Buffer,
|
|
323
|
+
config: ExecutionConfig,
|
|
324
|
+
state: ExecutionState
|
|
325
|
+
): Promise<ExecutionResult> {
|
|
326
|
+
switch (config.submitMode) {
|
|
327
|
+
case SubmitMode.Single:
|
|
328
|
+
return this.executeSingle(tradeType, transaction, config, state);
|
|
329
|
+
case SubmitMode.Parallel:
|
|
330
|
+
return this.executeParallel(tradeType, transaction, config, state);
|
|
331
|
+
case SubmitMode.Fallback:
|
|
332
|
+
return this.executeFallback(tradeType, transaction, config, state);
|
|
333
|
+
case SubmitMode.Redundant:
|
|
334
|
+
return this.executeRedundant(tradeType, transaction, config, state);
|
|
335
|
+
default:
|
|
336
|
+
throw new TradeError(400, `Unknown submit mode: ${config.submitMode}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async executeSingle(
|
|
341
|
+
tradeType: TradeType,
|
|
342
|
+
transaction: Buffer,
|
|
343
|
+
config: ExecutionConfig,
|
|
344
|
+
state: ExecutionState
|
|
345
|
+
): Promise<ExecutionResult> {
|
|
346
|
+
const providers = this.getOrderedProviders(config.priorityProviders);
|
|
347
|
+
const provider = providers[0];
|
|
348
|
+
|
|
349
|
+
if (!provider) {
|
|
350
|
+
return this.createErrorResult(state, 'No providers available');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return this.executeWithRetry(tradeType, transaction, config, state, provider);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private async executeParallel(
|
|
357
|
+
tradeType: TradeType,
|
|
358
|
+
transaction: Buffer,
|
|
359
|
+
config: ExecutionConfig,
|
|
360
|
+
state: ExecutionState
|
|
361
|
+
): Promise<ExecutionResult> {
|
|
362
|
+
const providers = this.getOrderedProviders(config.priorityProviders);
|
|
363
|
+
|
|
364
|
+
if (providers.length === 0) {
|
|
365
|
+
return this.createErrorResult(state, 'No providers available');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const promises = providers.map(provider =>
|
|
369
|
+
this.executeWithProvider(tradeType, transaction, config, state, provider)
|
|
370
|
+
.catch(error => ({ success: false, error, provider: provider.getSwqosType() } as ExecutionResult))
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const results = await Promise.allSettled(promises);
|
|
374
|
+
|
|
375
|
+
for (const result of results) {
|
|
376
|
+
if (result.status === 'fulfilled' && result.value.success) {
|
|
377
|
+
return result.value;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// All failed, return first error
|
|
382
|
+
const firstError = results.find(r => r.status === 'fulfilled');
|
|
383
|
+
if (firstError && firstError.status === 'fulfilled') {
|
|
384
|
+
return firstError.value;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return this.createErrorResult(state, 'All parallel submissions failed');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private async executeFallback(
|
|
391
|
+
tradeType: TradeType,
|
|
392
|
+
transaction: Buffer,
|
|
393
|
+
config: ExecutionConfig,
|
|
394
|
+
state: ExecutionState
|
|
395
|
+
): Promise<ExecutionResult> {
|
|
396
|
+
const providers = this.getOrderedProviders(config.priorityProviders);
|
|
397
|
+
|
|
398
|
+
for (const provider of providers) {
|
|
399
|
+
const result = await this.executeWithProvider(
|
|
400
|
+
tradeType,
|
|
401
|
+
transaction,
|
|
402
|
+
config,
|
|
403
|
+
state,
|
|
404
|
+
provider
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
if (result.success) {
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (config.retryDelayMs > 0) {
|
|
412
|
+
await this.sleep(config.retryDelayMs);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return this.createErrorResult(state, 'All fallback providers failed');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private async executeRedundant(
|
|
420
|
+
tradeType: TradeType,
|
|
421
|
+
transaction: Buffer,
|
|
422
|
+
config: ExecutionConfig,
|
|
423
|
+
state: ExecutionState
|
|
424
|
+
): Promise<ExecutionResult> {
|
|
425
|
+
// Similar to parallel but continues even after first success for redundancy
|
|
426
|
+
const providers = this.getOrderedProviders(config.priorityProviders);
|
|
427
|
+
const minSuccesses = Math.min(2, providers.length);
|
|
428
|
+
|
|
429
|
+
const promises = providers.map(provider =>
|
|
430
|
+
this.executeWithProvider(tradeType, transaction, config, state, provider)
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const results = await Promise.allSettled(promises);
|
|
434
|
+
const successes = results.filter(
|
|
435
|
+
(r): r is PromiseFulfilledResult<ExecutionResult> => r.status === 'fulfilled' && r.value.success
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (successes.length >= minSuccesses) {
|
|
439
|
+
// Return the fastest successful result
|
|
440
|
+
const firstSuccess = successes[0]!;
|
|
441
|
+
if (firstSuccess.status === 'fulfilled') {
|
|
442
|
+
return {
|
|
443
|
+
...firstSuccess.value,
|
|
444
|
+
metadata: {
|
|
445
|
+
...firstSuccess.value.metadata,
|
|
446
|
+
redundantSubmissions: successes.length,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return this.createErrorResult(
|
|
453
|
+
state,
|
|
454
|
+
`Redundant execution failed: ${successes.length}/${minSuccesses} successes`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private async executeWithRetry(
|
|
459
|
+
tradeType: TradeType,
|
|
460
|
+
transaction: Buffer,
|
|
461
|
+
config: ExecutionConfig,
|
|
462
|
+
state: ExecutionState,
|
|
463
|
+
provider: SwqosClient
|
|
464
|
+
): Promise<ExecutionResult> {
|
|
465
|
+
for (let attempt = 0; attempt < config.maxRetries; attempt++) {
|
|
466
|
+
state.attempts = attempt + 1;
|
|
467
|
+
|
|
468
|
+
this.reportProgress(state, config, provider.getSwqosType());
|
|
469
|
+
|
|
470
|
+
const result = await this.executeWithProvider(
|
|
471
|
+
tradeType,
|
|
472
|
+
transaction,
|
|
473
|
+
config,
|
|
474
|
+
state,
|
|
475
|
+
provider
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
if (result.success) {
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (attempt < config.maxRetries - 1 && config.retryDelayMs > 0) {
|
|
483
|
+
await this.sleep(config.retryDelayMs * Math.pow(2, attempt)); // Exponential backoff
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return this.createErrorResult(
|
|
488
|
+
state,
|
|
489
|
+
`Failed after ${config.maxRetries} retries`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private async executeWithProvider(
|
|
494
|
+
tradeType: TradeType,
|
|
495
|
+
transaction: Buffer,
|
|
496
|
+
config: ExecutionConfig,
|
|
497
|
+
state: ExecutionState,
|
|
498
|
+
provider: SwqosClient
|
|
499
|
+
): Promise<ExecutionResult> {
|
|
500
|
+
const providerType = provider.getSwqosType();
|
|
501
|
+
state.providersTried.add(providerType);
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
this.updateStatus(state, ExecutionStatus.Submitted, config);
|
|
505
|
+
|
|
506
|
+
const signature = await provider.sendTransaction(
|
|
507
|
+
tradeType,
|
|
508
|
+
transaction,
|
|
509
|
+
false
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
let confirmationTimeMs: number | undefined;
|
|
513
|
+
|
|
514
|
+
if (config.waitConfirmation) {
|
|
515
|
+
this.updateStatus(state, ExecutionStatus.Confirmed, config);
|
|
516
|
+
const confirmed = await this.waitForConfirmation(
|
|
517
|
+
signature,
|
|
518
|
+
config.commitment
|
|
519
|
+
);
|
|
520
|
+
confirmationTimeMs = Date.now() - state.startTime;
|
|
521
|
+
|
|
522
|
+
if (!confirmed) {
|
|
523
|
+
return this.createErrorResult(state, 'Transaction failed to confirm', providerType);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
this.updateStatus(state, ExecutionStatus.Finalized, config);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
signature,
|
|
531
|
+
success: true,
|
|
532
|
+
status: config.waitConfirmation ? ExecutionStatus.Finalized : ExecutionStatus.Submitted,
|
|
533
|
+
provider: providerType,
|
|
534
|
+
attempts: state.attempts,
|
|
535
|
+
executionTimeMs: Date.now() - state.startTime,
|
|
536
|
+
confirmationTimeMs,
|
|
537
|
+
};
|
|
538
|
+
} catch (error) {
|
|
539
|
+
return this.createErrorResult(
|
|
540
|
+
state,
|
|
541
|
+
error instanceof Error ? error.message : 'Unknown error',
|
|
542
|
+
providerType
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private async waitForConfirmation(
|
|
548
|
+
signature: string,
|
|
549
|
+
commitment: Commitment
|
|
550
|
+
): Promise<boolean> {
|
|
551
|
+
const timeoutMs = 30000;
|
|
552
|
+
const startTime = Date.now();
|
|
553
|
+
|
|
554
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
555
|
+
try {
|
|
556
|
+
const status = await this.connection.getSignatureStatus(signature);
|
|
557
|
+
if (status.value) {
|
|
558
|
+
if (status.value.err) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const cs = status.value.confirmationStatus;
|
|
562
|
+
if (commitment === 'finalized') {
|
|
563
|
+
if (cs === 'finalized') return true;
|
|
564
|
+
} else {
|
|
565
|
+
// Rust `poll_any_transaction_confirmation`: Confirmed | Finalized only
|
|
566
|
+
if (cs === 'confirmed' || cs === 'finalized') return true;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} catch {
|
|
570
|
+
// Continue polling
|
|
571
|
+
}
|
|
572
|
+
await this.sleep(500);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private getOrderedProviders(priorityProviders: SwqosType[]): SwqosClient[] {
|
|
579
|
+
const providers: SwqosClient[] = [];
|
|
580
|
+
|
|
581
|
+
// Add priority providers first
|
|
582
|
+
for (const type of priorityProviders) {
|
|
583
|
+
const client = this.clients.get(type);
|
|
584
|
+
if (client) {
|
|
585
|
+
providers.push(client);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Add remaining providers
|
|
590
|
+
for (const [type, client] of this.clients) {
|
|
591
|
+
if (!priorityProviders.includes(type)) {
|
|
592
|
+
providers.push(client);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return providers;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private updateStatus(
|
|
600
|
+
state: ExecutionState,
|
|
601
|
+
status: ExecutionStatus,
|
|
602
|
+
config?: ExecutionConfig,
|
|
603
|
+
result?: ExecutionResult
|
|
604
|
+
): void {
|
|
605
|
+
state.currentStatus = status;
|
|
606
|
+
if (config?.onStatusUpdate) {
|
|
607
|
+
config.onStatusUpdate(status, result);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private reportProgress(
|
|
612
|
+
state: ExecutionState,
|
|
613
|
+
config: ExecutionConfig,
|
|
614
|
+
currentProvider?: SwqosType
|
|
615
|
+
): void {
|
|
616
|
+
if (config.onProgress) {
|
|
617
|
+
const elapsedMs = Date.now() - state.startTime;
|
|
618
|
+
const progress: ExecutionProgress = {
|
|
619
|
+
attempt: state.attempts,
|
|
620
|
+
totalAttempts: config.maxRetries,
|
|
621
|
+
currentProvider,
|
|
622
|
+
providersTried: state.providersTried.size,
|
|
623
|
+
elapsedMs,
|
|
624
|
+
estimatedRemainingMs: this.estimateRemainingTime(state, config),
|
|
625
|
+
};
|
|
626
|
+
config.onProgress(progress);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private estimateRemainingTime(state: ExecutionState, config: ExecutionConfig): number | undefined {
|
|
631
|
+
if (state.attempts === 0) {
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const elapsedMs = Date.now() - state.startTime;
|
|
636
|
+
const avgTimePerAttempt = elapsedMs / state.attempts;
|
|
637
|
+
const remainingAttempts = config.maxRetries - state.attempts;
|
|
638
|
+
|
|
639
|
+
return Math.ceil(avgTimePerAttempt * remainingAttempts);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private createErrorResult(
|
|
643
|
+
state: ExecutionState,
|
|
644
|
+
error: string,
|
|
645
|
+
provider?: SwqosType
|
|
646
|
+
): ExecutionResult {
|
|
647
|
+
return {
|
|
648
|
+
signature: '',
|
|
649
|
+
success: false,
|
|
650
|
+
status: ExecutionStatus.Failed,
|
|
651
|
+
error,
|
|
652
|
+
provider,
|
|
653
|
+
attempts: state.attempts,
|
|
654
|
+
executionTimeMs: Date.now() - state.startTime,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
private createTimeoutResult(state: ExecutionState): ExecutionResult {
|
|
659
|
+
return {
|
|
660
|
+
signature: '',
|
|
661
|
+
success: false,
|
|
662
|
+
status: ExecutionStatus.TimedOut,
|
|
663
|
+
error: 'Execution timed out',
|
|
664
|
+
attempts: state.attempts,
|
|
665
|
+
executionTimeMs: Date.now() - state.startTime,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private generateExecutionId(): string {
|
|
670
|
+
return `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private sleep(ms: number): Promise<void> {
|
|
674
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ===== Convenience Functions =====
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Create an async trade executor with default configuration
|
|
682
|
+
*/
|
|
683
|
+
export function createAsyncExecutor(
|
|
684
|
+
rpcUrl: string,
|
|
685
|
+
clients: SwqosClient[] = []
|
|
686
|
+
): AsyncTradeExecutor {
|
|
687
|
+
return new AsyncTradeExecutor(rpcUrl, clients);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Execute a single trade with minimal configuration
|
|
692
|
+
*/
|
|
693
|
+
export async function executeTrade(
|
|
694
|
+
rpcUrl: string,
|
|
695
|
+
tradeType: TradeType,
|
|
696
|
+
transaction: Buffer,
|
|
697
|
+
clients: SwqosClient[],
|
|
698
|
+
config?: Partial<ExecutionConfig>
|
|
699
|
+
): Promise<ExecutionResult> {
|
|
700
|
+
const executor = new AsyncTradeExecutor(rpcUrl, clients);
|
|
701
|
+
return executor.execute(tradeType, transaction, config);
|
|
702
|
+
}
|