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,1707 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SWQOS Providers for Sol Trade SDK
|
|
3
|
+
* Implements all 19 SWQOS (Solana Write Queue Operating System) providers.
|
|
4
|
+
* Based on the Rust/Python SDK implementations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { TradeError } from '../index';
|
|
8
|
+
|
|
9
|
+
// ===== Enums =====
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SWQOS service provider types
|
|
13
|
+
*/
|
|
14
|
+
export enum SwqosType {
|
|
15
|
+
Jito = 'Jito',
|
|
16
|
+
NextBlock = 'NextBlock',
|
|
17
|
+
ZeroSlot = 'ZeroSlot',
|
|
18
|
+
Temporal = 'Temporal',
|
|
19
|
+
Bloxroute = 'Bloxroute',
|
|
20
|
+
Node1 = 'Node1',
|
|
21
|
+
FlashBlock = 'FlashBlock',
|
|
22
|
+
BlockRazor = 'BlockRazor',
|
|
23
|
+
Astralane = 'Astralane',
|
|
24
|
+
Stellium = 'Stellium',
|
|
25
|
+
Lightspeed = 'Lightspeed',
|
|
26
|
+
Soyas = 'Soyas',
|
|
27
|
+
Speedlanding = 'Speedlanding',
|
|
28
|
+
Helius = 'Helius',
|
|
29
|
+
Triton = 'Triton',
|
|
30
|
+
QuickNode = 'QuickNode',
|
|
31
|
+
Syndica = 'Syndica',
|
|
32
|
+
Figment = 'Figment',
|
|
33
|
+
Alchemy = 'Alchemy',
|
|
34
|
+
Default = 'Default',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* SWQOS service regions
|
|
39
|
+
*/
|
|
40
|
+
export enum SwqosRegion {
|
|
41
|
+
NewYork = 'NewYork',
|
|
42
|
+
Frankfurt = 'Frankfurt',
|
|
43
|
+
Amsterdam = 'Amsterdam',
|
|
44
|
+
SLC = 'SLC',
|
|
45
|
+
Tokyo = 'Tokyo',
|
|
46
|
+
London = 'London',
|
|
47
|
+
LosAngeles = 'LosAngeles',
|
|
48
|
+
Singapore = 'Singapore',
|
|
49
|
+
Default = 'Default',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* MEV protection levels
|
|
54
|
+
*/
|
|
55
|
+
export enum MevProtectionLevel {
|
|
56
|
+
None = 'none',
|
|
57
|
+
Basic = 'basic',
|
|
58
|
+
Enhanced = 'enhanced',
|
|
59
|
+
Maximum = 'maximum',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ===== Types =====
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Transaction submission result
|
|
66
|
+
*/
|
|
67
|
+
export interface TransactionResult {
|
|
68
|
+
success: boolean;
|
|
69
|
+
signature?: string;
|
|
70
|
+
provider: string;
|
|
71
|
+
latencyMs: number;
|
|
72
|
+
slot?: number;
|
|
73
|
+
error?: string;
|
|
74
|
+
bundleId?: string;
|
|
75
|
+
confirmationStatus?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* SWQOS configuration
|
|
80
|
+
*/
|
|
81
|
+
export interface SwqosConfig {
|
|
82
|
+
swqosType: SwqosType;
|
|
83
|
+
apiKey?: string;
|
|
84
|
+
region?: SwqosRegion;
|
|
85
|
+
url?: string;
|
|
86
|
+
timeoutMs?: number;
|
|
87
|
+
maxRetries?: number;
|
|
88
|
+
enabled?: boolean;
|
|
89
|
+
priorityFeeMultiplier?: number;
|
|
90
|
+
mevProtection?: MevProtectionLevel;
|
|
91
|
+
customHeaders?: Record<string, string>;
|
|
92
|
+
rateLimitRps?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Client statistics
|
|
97
|
+
*/
|
|
98
|
+
export interface ClientStats {
|
|
99
|
+
requests: number;
|
|
100
|
+
successes: number;
|
|
101
|
+
failures: number;
|
|
102
|
+
avgLatencyMs: number;
|
|
103
|
+
lastError?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ===== Base Client =====
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Base SWQOS client class
|
|
110
|
+
*/
|
|
111
|
+
export abstract class SwqosClient {
|
|
112
|
+
protected _stats: ClientStats = {
|
|
113
|
+
requests: 0,
|
|
114
|
+
successes: 0,
|
|
115
|
+
failures: 0,
|
|
116
|
+
avgLatencyMs: 0,
|
|
117
|
+
};
|
|
118
|
+
protected _lastRequestTime = 0;
|
|
119
|
+
protected _rateLimitDelay: number;
|
|
120
|
+
|
|
121
|
+
constructor(public readonly config: SwqosConfig) {
|
|
122
|
+
const rateLimitRps = config.rateLimitRps ?? 100;
|
|
123
|
+
this._rateLimitDelay = rateLimitRps > 0 ? 1000 / rateLimitRps : 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Submit transaction to SWQOS provider
|
|
128
|
+
*/
|
|
129
|
+
abstract submitTransaction(transaction: Buffer, tip?: number): Promise<TransactionResult>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Submit transaction bundle
|
|
133
|
+
*/
|
|
134
|
+
async submitBundle(transactions: Buffer[], tip?: number): Promise<TransactionResult> {
|
|
135
|
+
// Default: submit first transaction only
|
|
136
|
+
if (transactions.length > 0) {
|
|
137
|
+
return this.submitTransaction(transactions[0]!, tip);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
provider: this.config.swqosType,
|
|
142
|
+
latencyMs: 0,
|
|
143
|
+
error: 'Empty bundle',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get recommended tip amount in lamports
|
|
149
|
+
*/
|
|
150
|
+
async getTipRecommendation(): Promise<number> {
|
|
151
|
+
return 10000; // Default 0.00001 SOL
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get client statistics
|
|
156
|
+
*/
|
|
157
|
+
getStats(): ClientStats {
|
|
158
|
+
return { ...this._stats };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Update client statistics
|
|
163
|
+
*/
|
|
164
|
+
protected updateStats(success: boolean, latencyMs: number, error?: string): void {
|
|
165
|
+
this._stats.requests++;
|
|
166
|
+
if (success) {
|
|
167
|
+
this._stats.successes++;
|
|
168
|
+
} else {
|
|
169
|
+
this._stats.failures++;
|
|
170
|
+
this._stats.lastError = error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Update average latency
|
|
174
|
+
const n = this._stats.requests;
|
|
175
|
+
this._stats.avgLatencyMs = Math.floor(
|
|
176
|
+
(this._stats.avgLatencyMs * (n - 1) + latencyMs) / n
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check and enforce rate limiting
|
|
182
|
+
*/
|
|
183
|
+
protected async rateLimitCheck(): Promise<void> {
|
|
184
|
+
if (this._rateLimitDelay <= 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const elapsed = Date.now() - this._lastRequestTime;
|
|
189
|
+
if (elapsed < this._rateLimitDelay) {
|
|
190
|
+
await this.sleep(this._rateLimitDelay - elapsed);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this._lastRequestTime = Date.now();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Sleep helper
|
|
198
|
+
*/
|
|
199
|
+
protected sleep(ms: number): Promise<void> {
|
|
200
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Make HTTP POST request
|
|
205
|
+
*/
|
|
206
|
+
protected async post(url: string, payload: unknown, headers: Record<string, string> = {}): Promise<unknown> {
|
|
207
|
+
const response = await fetch(url, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: {
|
|
210
|
+
'Content-Type': 'application/json',
|
|
211
|
+
...headers,
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify(payload),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new TradeError(response.status, `HTTP error: ${response.statusText}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return response.json();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get provider type
|
|
225
|
+
*/
|
|
226
|
+
abstract getProviderType(): SwqosType;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if provider is enabled
|
|
230
|
+
*/
|
|
231
|
+
isEnabled(): boolean {
|
|
232
|
+
return this.config.enabled ?? true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ===== Provider Implementations =====
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Jito SWQOS client - MEV protection and bundle submission
|
|
240
|
+
*/
|
|
241
|
+
export class JitoClient extends SwqosClient {
|
|
242
|
+
private bundleUrl: string;
|
|
243
|
+
private authToken?: string;
|
|
244
|
+
|
|
245
|
+
private static readonly DEFAULT_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
246
|
+
[SwqosRegion.NewYork]: 'https://mainnet.block-engine.jito.wtf',
|
|
247
|
+
[SwqosRegion.Frankfurt]: 'https://frankfurt.mainnet.block-engine.jito.wtf',
|
|
248
|
+
[SwqosRegion.Amsterdam]: 'https://amsterdam.mainnet.block-engine.jito.wtf',
|
|
249
|
+
[SwqosRegion.Tokyo]: 'https://tokyo.mainnet.block-engine.jito.wtf',
|
|
250
|
+
[SwqosRegion.SLC]: 'https://slc.mainnet.block-engine.jito.wtf',
|
|
251
|
+
[SwqosRegion.London]: 'https://mainnet.block-engine.jito.wtf',
|
|
252
|
+
[SwqosRegion.LosAngeles]: 'https://mainnet.block-engine.jito.wtf',
|
|
253
|
+
[SwqosRegion.Singapore]: 'https://mainnet.block-engine.jito.wtf',
|
|
254
|
+
[SwqosRegion.Default]: 'https://mainnet.block-engine.jito.wtf',
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
constructor(config: SwqosConfig) {
|
|
258
|
+
super(config);
|
|
259
|
+
this.bundleUrl = config.url || this.getEndpointForRegion(config.region || SwqosRegion.Default);
|
|
260
|
+
this.authToken = config.apiKey;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private getEndpointForRegion(region: SwqosRegion): string {
|
|
264
|
+
return JitoClient.DEFAULT_ENDPOINTS[region] || JitoClient.DEFAULT_ENDPOINTS[SwqosRegion.Default];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
268
|
+
await this.rateLimitCheck();
|
|
269
|
+
const startTime = Date.now();
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const encoded = transaction.toString('base64');
|
|
273
|
+
const payload = {
|
|
274
|
+
jsonrpc: '2.0',
|
|
275
|
+
id: 1,
|
|
276
|
+
method: 'sendTransaction',
|
|
277
|
+
params: [encoded, { encoding: 'base64' }],
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const headers: Record<string, string> = {};
|
|
281
|
+
if (this.authToken) {
|
|
282
|
+
headers['X-Jito-Auth-Token'] = this.authToken;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const result = (await this.post(`${this.bundleUrl}/api/v1/bundles`, payload, headers)) as any;
|
|
286
|
+
|
|
287
|
+
if (result.error) {
|
|
288
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const latencyMs = Date.now() - startTime;
|
|
292
|
+
this.updateStats(true, latencyMs);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
success: true,
|
|
296
|
+
signature: result.result,
|
|
297
|
+
provider: 'Jito',
|
|
298
|
+
latencyMs,
|
|
299
|
+
bundleId: `bundle_${Date.now()}`,
|
|
300
|
+
};
|
|
301
|
+
} catch (error) {
|
|
302
|
+
const latencyMs = Date.now() - startTime;
|
|
303
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
304
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
provider: 'Jito',
|
|
309
|
+
latencyMs,
|
|
310
|
+
error: errorMsg,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async submitBundle(transactions: Buffer[], tip = 0): Promise<TransactionResult> {
|
|
316
|
+
await this.rateLimitCheck();
|
|
317
|
+
const startTime = Date.now();
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const encodedTxs = transactions.map(tx => tx.toString('base64'));
|
|
321
|
+
const payload = {
|
|
322
|
+
jsonrpc: '2.0',
|
|
323
|
+
id: 1,
|
|
324
|
+
method: 'sendBundle',
|
|
325
|
+
params: [encodedTxs],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const headers: Record<string, string> = {};
|
|
329
|
+
if (this.authToken) {
|
|
330
|
+
headers['X-Jito-Auth-Token'] = this.authToken;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const result = (await this.post(`${this.bundleUrl}/api/v1/bundles`, payload, headers)) as any;
|
|
334
|
+
|
|
335
|
+
const latencyMs = Date.now() - startTime;
|
|
336
|
+
this.updateStats(true, latencyMs);
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
success: true,
|
|
340
|
+
signature: result.result,
|
|
341
|
+
provider: 'Jito',
|
|
342
|
+
latencyMs,
|
|
343
|
+
bundleId: `bundle_${Date.now()}`,
|
|
344
|
+
};
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const latencyMs = Date.now() - startTime;
|
|
347
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
348
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
success: false,
|
|
352
|
+
provider: 'Jito',
|
|
353
|
+
latencyMs,
|
|
354
|
+
error: errorMsg,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async getTipRecommendation(): Promise<number> {
|
|
360
|
+
// Jito typically recommends 10000-100000 lamports
|
|
361
|
+
return 50000;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
getProviderType(): SwqosType {
|
|
365
|
+
return SwqosType.Jito;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Bloxroute SWQOS client - High-speed transaction relay
|
|
371
|
+
*/
|
|
372
|
+
export class BloxrouteClient extends SwqosClient {
|
|
373
|
+
private gatewayUrl: string;
|
|
374
|
+
private wsUrl: string;
|
|
375
|
+
|
|
376
|
+
private static readonly DEFAULT_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
377
|
+
[SwqosRegion.NewYork]: 'https://solana.dex.blxrbdn.com',
|
|
378
|
+
[SwqosRegion.Frankfurt]: 'https://solana.dex.blxrbdn.com',
|
|
379
|
+
[SwqosRegion.Amsterdam]: 'https://solana.dex.blxrbdn.com',
|
|
380
|
+
[SwqosRegion.Tokyo]: 'https://solana.dex.blxrbdn.com',
|
|
381
|
+
[SwqosRegion.SLC]: 'https://solana.dex.blxrbdn.com',
|
|
382
|
+
[SwqosRegion.London]: 'https://solana.dex.blxrbdn.com',
|
|
383
|
+
[SwqosRegion.LosAngeles]: 'https://solana.dex.blxrbdn.com',
|
|
384
|
+
[SwqosRegion.Singapore]: 'https://solana.dex.blxrbdn.com',
|
|
385
|
+
[SwqosRegion.Default]: 'https://solana.dex.blxrbdn.com',
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
constructor(config: SwqosConfig) {
|
|
389
|
+
super(config);
|
|
390
|
+
this.gatewayUrl = config.url || BloxrouteClient.DEFAULT_ENDPOINTS[config.region || SwqosRegion.Default];
|
|
391
|
+
this.wsUrl = this.gatewayUrl.replace('https://', 'wss://');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
395
|
+
await this.rateLimitCheck();
|
|
396
|
+
const startTime = Date.now();
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const encoded = transaction.toString('base64');
|
|
400
|
+
const payload = {
|
|
401
|
+
jsonrpc: '2.0',
|
|
402
|
+
id: 1,
|
|
403
|
+
method: 'sendTransaction',
|
|
404
|
+
params: [encoded],
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const headers: Record<string, string> = {};
|
|
408
|
+
if (this.config.apiKey) {
|
|
409
|
+
headers['Authorization'] = this.config.apiKey;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const result = (await this.post(`${this.gatewayUrl}/api/v2/submit`, payload, headers)) as any;
|
|
413
|
+
|
|
414
|
+
if (result.reason) {
|
|
415
|
+
throw new TradeError(500, result.reason);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const latencyMs = Date.now() - startTime;
|
|
419
|
+
this.updateStats(true, latencyMs);
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
success: true,
|
|
423
|
+
signature: result.signature,
|
|
424
|
+
provider: 'Bloxroute',
|
|
425
|
+
latencyMs,
|
|
426
|
+
};
|
|
427
|
+
} catch (error) {
|
|
428
|
+
const latencyMs = Date.now() - startTime;
|
|
429
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
430
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
provider: 'Bloxroute',
|
|
435
|
+
latencyMs,
|
|
436
|
+
error: errorMsg,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
getProviderType(): SwqosType {
|
|
442
|
+
return SwqosType.Bloxroute;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* ZeroSlot SWQOS client - Zero-slot latency
|
|
448
|
+
*/
|
|
449
|
+
export class ZeroSlotClient extends SwqosClient {
|
|
450
|
+
private apiUrl: string;
|
|
451
|
+
|
|
452
|
+
constructor(config: SwqosConfig) {
|
|
453
|
+
super(config);
|
|
454
|
+
this.apiUrl = config.url || 'https://api.zeroslot.io';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
458
|
+
await this.rateLimitCheck();
|
|
459
|
+
const startTime = Date.now();
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
const encoded = transaction.toString('base64');
|
|
463
|
+
const payload = { transaction: encoded, tip };
|
|
464
|
+
|
|
465
|
+
const headers: Record<string, string> = {};
|
|
466
|
+
if (this.config.apiKey) {
|
|
467
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
471
|
+
|
|
472
|
+
if (result.error) {
|
|
473
|
+
throw new TradeError(500, result.error);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const latencyMs = Date.now() - startTime;
|
|
477
|
+
this.updateStats(true, latencyMs);
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
success: true,
|
|
481
|
+
signature: result.signature,
|
|
482
|
+
provider: 'ZeroSlot',
|
|
483
|
+
latencyMs,
|
|
484
|
+
slot: result.slot,
|
|
485
|
+
};
|
|
486
|
+
} catch (error) {
|
|
487
|
+
const latencyMs = Date.now() - startTime;
|
|
488
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
489
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
success: false,
|
|
493
|
+
provider: 'ZeroSlot',
|
|
494
|
+
latencyMs,
|
|
495
|
+
error: errorMsg,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
getProviderType(): SwqosType {
|
|
501
|
+
return SwqosType.ZeroSlot;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* NextBlock SWQOS client - Next block inclusion guarantee
|
|
507
|
+
*/
|
|
508
|
+
export class NextBlockClient extends SwqosClient {
|
|
509
|
+
private apiUrl: string;
|
|
510
|
+
|
|
511
|
+
constructor(config: SwqosConfig) {
|
|
512
|
+
super(config);
|
|
513
|
+
this.apiUrl = config.url || 'https://api.nextblock.io';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
517
|
+
await this.rateLimitCheck();
|
|
518
|
+
const startTime = Date.now();
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
const encoded = transaction.toString('base64');
|
|
522
|
+
const payload = { transaction: encoded, tip };
|
|
523
|
+
|
|
524
|
+
const headers: Record<string, string> = {};
|
|
525
|
+
if (this.config.apiKey) {
|
|
526
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
530
|
+
|
|
531
|
+
const latencyMs = Date.now() - startTime;
|
|
532
|
+
this.updateStats(true, latencyMs);
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
success: true,
|
|
536
|
+
signature: result.signature,
|
|
537
|
+
provider: 'NextBlock',
|
|
538
|
+
latencyMs,
|
|
539
|
+
};
|
|
540
|
+
} catch (error) {
|
|
541
|
+
const latencyMs = Date.now() - startTime;
|
|
542
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
543
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
provider: 'NextBlock',
|
|
548
|
+
latencyMs,
|
|
549
|
+
error: errorMsg,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
getProviderType(): SwqosType {
|
|
555
|
+
return SwqosType.NextBlock;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Temporal SWQOS client - Time-based execution
|
|
561
|
+
*/
|
|
562
|
+
export class TemporalClient extends SwqosClient {
|
|
563
|
+
private apiUrl: string;
|
|
564
|
+
|
|
565
|
+
constructor(config: SwqosConfig) {
|
|
566
|
+
super(config);
|
|
567
|
+
this.apiUrl = config.url || 'https://api.temporal.trade';
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
571
|
+
await this.rateLimitCheck();
|
|
572
|
+
const startTime = Date.now();
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
const encoded = transaction.toString('base64');
|
|
576
|
+
const payload = { transaction: encoded, tip };
|
|
577
|
+
|
|
578
|
+
const headers: Record<string, string> = {};
|
|
579
|
+
if (this.config.apiKey) {
|
|
580
|
+
headers['Authorization'] = this.config.apiKey;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
584
|
+
|
|
585
|
+
if (result.error) {
|
|
586
|
+
throw new TradeError(500, result.error);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const latencyMs = Date.now() - startTime;
|
|
590
|
+
this.updateStats(true, latencyMs);
|
|
591
|
+
|
|
592
|
+
return {
|
|
593
|
+
success: true,
|
|
594
|
+
signature: result.signature,
|
|
595
|
+
provider: 'Temporal',
|
|
596
|
+
latencyMs,
|
|
597
|
+
};
|
|
598
|
+
} catch (error) {
|
|
599
|
+
const latencyMs = Date.now() - startTime;
|
|
600
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
601
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
success: false,
|
|
605
|
+
provider: 'Temporal',
|
|
606
|
+
latencyMs,
|
|
607
|
+
error: errorMsg,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
getProviderType(): SwqosType {
|
|
613
|
+
return SwqosType.Temporal;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Node1 SWQOS client - Premium node access
|
|
619
|
+
*/
|
|
620
|
+
export class Node1Client extends SwqosClient {
|
|
621
|
+
private apiUrl: string;
|
|
622
|
+
|
|
623
|
+
constructor(config: SwqosConfig) {
|
|
624
|
+
super(config);
|
|
625
|
+
this.apiUrl = config.url || 'https://api.node1.io';
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
629
|
+
await this.rateLimitCheck();
|
|
630
|
+
const startTime = Date.now();
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
const encoded = transaction.toString('base64');
|
|
634
|
+
const payload = { transaction: encoded, tip };
|
|
635
|
+
|
|
636
|
+
const headers: Record<string, string> = {};
|
|
637
|
+
if (this.config.apiKey) {
|
|
638
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
642
|
+
|
|
643
|
+
if (result.error) {
|
|
644
|
+
throw new TradeError(500, result.error);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const latencyMs = Date.now() - startTime;
|
|
648
|
+
this.updateStats(true, latencyMs);
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
success: true,
|
|
652
|
+
signature: result.signature,
|
|
653
|
+
provider: 'Node1',
|
|
654
|
+
latencyMs,
|
|
655
|
+
};
|
|
656
|
+
} catch (error) {
|
|
657
|
+
const latencyMs = Date.now() - startTime;
|
|
658
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
659
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
660
|
+
|
|
661
|
+
return {
|
|
662
|
+
success: false,
|
|
663
|
+
provider: 'Node1',
|
|
664
|
+
latencyMs,
|
|
665
|
+
error: errorMsg,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
getProviderType(): SwqosType {
|
|
671
|
+
return SwqosType.Node1;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* FlashBlock SWQOS client - Flash block inclusion
|
|
677
|
+
*/
|
|
678
|
+
export class FlashBlockClient extends SwqosClient {
|
|
679
|
+
private apiUrl: string;
|
|
680
|
+
|
|
681
|
+
constructor(config: SwqosConfig) {
|
|
682
|
+
super(config);
|
|
683
|
+
this.apiUrl = config.url || 'https://api.flashblock.io';
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
687
|
+
await this.rateLimitCheck();
|
|
688
|
+
const startTime = Date.now();
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
const encoded = transaction.toString('base64');
|
|
692
|
+
const payload = { transaction: encoded, tip };
|
|
693
|
+
|
|
694
|
+
const headers: Record<string, string> = {};
|
|
695
|
+
if (this.config.apiKey) {
|
|
696
|
+
headers['X-API-Key'] = this.config.apiKey;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
700
|
+
|
|
701
|
+
if (result.error) {
|
|
702
|
+
throw new TradeError(500, result.error);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const latencyMs = Date.now() - startTime;
|
|
706
|
+
this.updateStats(true, latencyMs);
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
success: true,
|
|
710
|
+
signature: result.signature,
|
|
711
|
+
provider: 'FlashBlock',
|
|
712
|
+
latencyMs,
|
|
713
|
+
};
|
|
714
|
+
} catch (error) {
|
|
715
|
+
const latencyMs = Date.now() - startTime;
|
|
716
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
717
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
success: false,
|
|
721
|
+
provider: 'FlashBlock',
|
|
722
|
+
latencyMs,
|
|
723
|
+
error: errorMsg,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
getProviderType(): SwqosType {
|
|
729
|
+
return SwqosType.FlashBlock;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* BlockRazor SWQOS client - Block optimization
|
|
735
|
+
*/
|
|
736
|
+
export class BlockRazorClient extends SwqosClient {
|
|
737
|
+
private apiUrl: string;
|
|
738
|
+
|
|
739
|
+
constructor(config: SwqosConfig) {
|
|
740
|
+
super(config);
|
|
741
|
+
this.apiUrl = config.url || 'https://api.blockrazor.io';
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
745
|
+
await this.rateLimitCheck();
|
|
746
|
+
const startTime = Date.now();
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
const encoded = transaction.toString('base64');
|
|
750
|
+
const payload = { transaction: encoded, tip };
|
|
751
|
+
|
|
752
|
+
const headers: Record<string, string> = {};
|
|
753
|
+
if (this.config.apiKey) {
|
|
754
|
+
headers['X-API-Key'] = this.config.apiKey;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
758
|
+
|
|
759
|
+
if (result.error) {
|
|
760
|
+
throw new TradeError(500, result.error);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const latencyMs = Date.now() - startTime;
|
|
764
|
+
this.updateStats(true, latencyMs);
|
|
765
|
+
|
|
766
|
+
return {
|
|
767
|
+
success: true,
|
|
768
|
+
signature: result.signature,
|
|
769
|
+
provider: 'BlockRazor',
|
|
770
|
+
latencyMs,
|
|
771
|
+
};
|
|
772
|
+
} catch (error) {
|
|
773
|
+
const latencyMs = Date.now() - startTime;
|
|
774
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
775
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
success: false,
|
|
779
|
+
provider: 'BlockRazor',
|
|
780
|
+
latencyMs,
|
|
781
|
+
error: errorMsg,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
getProviderType(): SwqosType {
|
|
787
|
+
return SwqosType.BlockRazor;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Astralane SWQOS client - High-speed relay
|
|
793
|
+
*/
|
|
794
|
+
export class AstralaneClient extends SwqosClient {
|
|
795
|
+
private apiUrl: string;
|
|
796
|
+
|
|
797
|
+
constructor(config: SwqosConfig) {
|
|
798
|
+
super(config);
|
|
799
|
+
this.apiUrl = config.url || 'https://api.astralane.io';
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
803
|
+
await this.rateLimitCheck();
|
|
804
|
+
const startTime = Date.now();
|
|
805
|
+
|
|
806
|
+
try {
|
|
807
|
+
const encoded = transaction.toString('base64');
|
|
808
|
+
const payload = { transaction: encoded, tip };
|
|
809
|
+
|
|
810
|
+
const headers: Record<string, string> = {};
|
|
811
|
+
if (this.config.apiKey) {
|
|
812
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
816
|
+
|
|
817
|
+
if (result.error) {
|
|
818
|
+
throw new TradeError(500, result.error);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const latencyMs = Date.now() - startTime;
|
|
822
|
+
this.updateStats(true, latencyMs);
|
|
823
|
+
|
|
824
|
+
return {
|
|
825
|
+
success: true,
|
|
826
|
+
signature: result.signature,
|
|
827
|
+
provider: 'Astralane',
|
|
828
|
+
latencyMs,
|
|
829
|
+
};
|
|
830
|
+
} catch (error) {
|
|
831
|
+
const latencyMs = Date.now() - startTime;
|
|
832
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
833
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
834
|
+
|
|
835
|
+
return {
|
|
836
|
+
success: false,
|
|
837
|
+
provider: 'Astralane',
|
|
838
|
+
latencyMs,
|
|
839
|
+
error: errorMsg,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
getProviderType(): SwqosType {
|
|
845
|
+
return SwqosType.Astralane;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Stellium SWQOS client - Premium infrastructure
|
|
851
|
+
*/
|
|
852
|
+
export class StelliumClient extends SwqosClient {
|
|
853
|
+
private apiUrl: string;
|
|
854
|
+
|
|
855
|
+
constructor(config: SwqosConfig) {
|
|
856
|
+
super(config);
|
|
857
|
+
this.apiUrl = config.url || 'https://api.stellium.io';
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
861
|
+
await this.rateLimitCheck();
|
|
862
|
+
const startTime = Date.now();
|
|
863
|
+
|
|
864
|
+
try {
|
|
865
|
+
const encoded = transaction.toString('base64');
|
|
866
|
+
const payload = { transaction: encoded, tip };
|
|
867
|
+
|
|
868
|
+
const headers: Record<string, string> = {};
|
|
869
|
+
if (this.config.apiKey) {
|
|
870
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
874
|
+
|
|
875
|
+
const latencyMs = Date.now() - startTime;
|
|
876
|
+
this.updateStats(true, latencyMs);
|
|
877
|
+
|
|
878
|
+
return {
|
|
879
|
+
success: true,
|
|
880
|
+
signature: result.signature,
|
|
881
|
+
provider: 'Stellium',
|
|
882
|
+
latencyMs,
|
|
883
|
+
};
|
|
884
|
+
} catch (error) {
|
|
885
|
+
const latencyMs = Date.now() - startTime;
|
|
886
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
887
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
success: false,
|
|
891
|
+
provider: 'Stellium',
|
|
892
|
+
latencyMs,
|
|
893
|
+
error: errorMsg,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
getProviderType(): SwqosType {
|
|
899
|
+
return SwqosType.Stellium;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Lightspeed SWQOS client - Ultra-low latency
|
|
905
|
+
*/
|
|
906
|
+
export class LightspeedClient extends SwqosClient {
|
|
907
|
+
private apiUrl: string;
|
|
908
|
+
|
|
909
|
+
constructor(config: SwqosConfig) {
|
|
910
|
+
super(config);
|
|
911
|
+
this.apiUrl = config.url || 'https://api.lightspeed.trade';
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
915
|
+
await this.rateLimitCheck();
|
|
916
|
+
const startTime = Date.now();
|
|
917
|
+
|
|
918
|
+
try {
|
|
919
|
+
const encoded = transaction.toString('base64');
|
|
920
|
+
const payload = { transaction: encoded, tip };
|
|
921
|
+
|
|
922
|
+
const headers: Record<string, string> = {};
|
|
923
|
+
if (this.config.apiKey) {
|
|
924
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
928
|
+
|
|
929
|
+
const latencyMs = Date.now() - startTime;
|
|
930
|
+
this.updateStats(true, latencyMs);
|
|
931
|
+
|
|
932
|
+
return {
|
|
933
|
+
success: true,
|
|
934
|
+
signature: result.signature,
|
|
935
|
+
provider: 'Lightspeed',
|
|
936
|
+
latencyMs,
|
|
937
|
+
};
|
|
938
|
+
} catch (error) {
|
|
939
|
+
const latencyMs = Date.now() - startTime;
|
|
940
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
941
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
942
|
+
|
|
943
|
+
return {
|
|
944
|
+
success: false,
|
|
945
|
+
provider: 'Lightspeed',
|
|
946
|
+
latencyMs,
|
|
947
|
+
error: errorMsg,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
getProviderType(): SwqosType {
|
|
953
|
+
return SwqosType.Lightspeed;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Soyas SWQOS client - MEV protection
|
|
959
|
+
*/
|
|
960
|
+
export class SoyasClient extends SwqosClient {
|
|
961
|
+
private apiUrl: string;
|
|
962
|
+
|
|
963
|
+
constructor(config: SwqosConfig) {
|
|
964
|
+
super(config);
|
|
965
|
+
this.apiUrl = config.url || 'https://api.soyas.io';
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
969
|
+
await this.rateLimitCheck();
|
|
970
|
+
const startTime = Date.now();
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
const encoded = transaction.toString('base64');
|
|
974
|
+
const payload = { transaction: encoded, tip };
|
|
975
|
+
|
|
976
|
+
const headers: Record<string, string> = {};
|
|
977
|
+
if (this.config.apiKey) {
|
|
978
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
982
|
+
|
|
983
|
+
const latencyMs = Date.now() - startTime;
|
|
984
|
+
this.updateStats(true, latencyMs);
|
|
985
|
+
|
|
986
|
+
return {
|
|
987
|
+
success: true,
|
|
988
|
+
signature: result.signature,
|
|
989
|
+
provider: 'Soyas',
|
|
990
|
+
latencyMs,
|
|
991
|
+
};
|
|
992
|
+
} catch (error) {
|
|
993
|
+
const latencyMs = Date.now() - startTime;
|
|
994
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
995
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
996
|
+
|
|
997
|
+
return {
|
|
998
|
+
success: false,
|
|
999
|
+
provider: 'Soyas',
|
|
1000
|
+
latencyMs,
|
|
1001
|
+
error: errorMsg,
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
getProviderType(): SwqosType {
|
|
1007
|
+
return SwqosType.Soyas;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Speedlanding SWQOS client - Fast inclusion
|
|
1013
|
+
*/
|
|
1014
|
+
export class SpeedlandingClient extends SwqosClient {
|
|
1015
|
+
private apiUrl: string;
|
|
1016
|
+
|
|
1017
|
+
constructor(config: SwqosConfig) {
|
|
1018
|
+
super(config);
|
|
1019
|
+
this.apiUrl = config.url || 'https://api.speedlanding.io';
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1023
|
+
await this.rateLimitCheck();
|
|
1024
|
+
const startTime = Date.now();
|
|
1025
|
+
|
|
1026
|
+
try {
|
|
1027
|
+
const encoded = transaction.toString('base64');
|
|
1028
|
+
const payload = { transaction: encoded, tip };
|
|
1029
|
+
|
|
1030
|
+
const headers: Record<string, string> = {};
|
|
1031
|
+
if (this.config.apiKey) {
|
|
1032
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
1036
|
+
|
|
1037
|
+
const latencyMs = Date.now() - startTime;
|
|
1038
|
+
this.updateStats(true, latencyMs);
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
success: true,
|
|
1042
|
+
signature: result.signature,
|
|
1043
|
+
provider: 'Speedlanding',
|
|
1044
|
+
latencyMs,
|
|
1045
|
+
};
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
const latencyMs = Date.now() - startTime;
|
|
1048
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1049
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1050
|
+
|
|
1051
|
+
return {
|
|
1052
|
+
success: false,
|
|
1053
|
+
provider: 'Speedlanding',
|
|
1054
|
+
latencyMs,
|
|
1055
|
+
error: errorMsg,
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
getProviderType(): SwqosType {
|
|
1061
|
+
return SwqosType.Speedlanding;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Helius SWQOS client - Enhanced RPC
|
|
1067
|
+
*/
|
|
1068
|
+
export class HeliusClient extends SwqosClient {
|
|
1069
|
+
private apiUrl: string;
|
|
1070
|
+
|
|
1071
|
+
private static readonly DEFAULT_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
1072
|
+
[SwqosRegion.NewYork]: 'https://api.helius-rpc.com',
|
|
1073
|
+
[SwqosRegion.Frankfurt]: 'https://api.helius-rpc.com',
|
|
1074
|
+
[SwqosRegion.Amsterdam]: 'https://api.helius-rpc.com',
|
|
1075
|
+
[SwqosRegion.Tokyo]: 'https://api.helius-rpc.com',
|
|
1076
|
+
[SwqosRegion.SLC]: 'https://api.helius-rpc.com',
|
|
1077
|
+
[SwqosRegion.London]: 'https://api.helius-rpc.com',
|
|
1078
|
+
[SwqosRegion.LosAngeles]: 'https://api.helius-rpc.com',
|
|
1079
|
+
[SwqosRegion.Singapore]: 'https://api.helius-rpc.com',
|
|
1080
|
+
[SwqosRegion.Default]: 'https://api.helius-rpc.com',
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
constructor(config: SwqosConfig) {
|
|
1084
|
+
super(config);
|
|
1085
|
+
this.apiUrl = config.url || HeliusClient.DEFAULT_ENDPOINTS[config.region || SwqosRegion.Default];
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1089
|
+
await this.rateLimitCheck();
|
|
1090
|
+
const startTime = Date.now();
|
|
1091
|
+
|
|
1092
|
+
try {
|
|
1093
|
+
const encoded = transaction.toString('base64');
|
|
1094
|
+
const payload = {
|
|
1095
|
+
jsonrpc: '2.0',
|
|
1096
|
+
id: 1,
|
|
1097
|
+
method: 'sendTransaction',
|
|
1098
|
+
params: [encoded, { encoding: 'base64' }],
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
const headers: Record<string, string> = {};
|
|
1102
|
+
if (this.config.apiKey) {
|
|
1103
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const result = (await this.post(this.apiUrl, payload, headers)) as any;
|
|
1107
|
+
|
|
1108
|
+
if (result.error) {
|
|
1109
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const latencyMs = Date.now() - startTime;
|
|
1113
|
+
this.updateStats(true, latencyMs);
|
|
1114
|
+
|
|
1115
|
+
return {
|
|
1116
|
+
success: true,
|
|
1117
|
+
signature: result.result,
|
|
1118
|
+
provider: 'Helius',
|
|
1119
|
+
latencyMs,
|
|
1120
|
+
};
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
const latencyMs = Date.now() - startTime;
|
|
1123
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1124
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1125
|
+
|
|
1126
|
+
return {
|
|
1127
|
+
success: false,
|
|
1128
|
+
provider: 'Helius',
|
|
1129
|
+
latencyMs,
|
|
1130
|
+
error: errorMsg,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
getProviderType(): SwqosType {
|
|
1136
|
+
return SwqosType.Helius;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Triton SWQOS client - High-performance RPC
|
|
1142
|
+
*/
|
|
1143
|
+
export class TritonClient extends SwqosClient {
|
|
1144
|
+
private apiUrl: string;
|
|
1145
|
+
|
|
1146
|
+
constructor(config: SwqosConfig) {
|
|
1147
|
+
super(config);
|
|
1148
|
+
this.apiUrl = config.url || 'https://api.triton.one';
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1152
|
+
await this.rateLimitCheck();
|
|
1153
|
+
const startTime = Date.now();
|
|
1154
|
+
|
|
1155
|
+
try {
|
|
1156
|
+
const encoded = transaction.toString('base64');
|
|
1157
|
+
const payload = { transaction: encoded, tip };
|
|
1158
|
+
|
|
1159
|
+
const headers: Record<string, string> = {};
|
|
1160
|
+
if (this.config.apiKey) {
|
|
1161
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
1165
|
+
|
|
1166
|
+
const latencyMs = Date.now() - startTime;
|
|
1167
|
+
this.updateStats(true, latencyMs);
|
|
1168
|
+
|
|
1169
|
+
return {
|
|
1170
|
+
success: true,
|
|
1171
|
+
signature: result.signature,
|
|
1172
|
+
provider: 'Triton',
|
|
1173
|
+
latencyMs,
|
|
1174
|
+
};
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
const latencyMs = Date.now() - startTime;
|
|
1177
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1178
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1179
|
+
|
|
1180
|
+
return {
|
|
1181
|
+
success: false,
|
|
1182
|
+
provider: 'Triton',
|
|
1183
|
+
latencyMs,
|
|
1184
|
+
error: errorMsg,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
getProviderType(): SwqosType {
|
|
1190
|
+
return SwqosType.Triton;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* QuickNode SWQOS client - Enterprise RPC
|
|
1196
|
+
*/
|
|
1197
|
+
export class QuickNodeClient extends SwqosClient {
|
|
1198
|
+
private apiUrl: string;
|
|
1199
|
+
|
|
1200
|
+
constructor(config: SwqosConfig) {
|
|
1201
|
+
super(config);
|
|
1202
|
+
this.apiUrl = config.url || 'https://api.quicknode.com';
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1206
|
+
await this.rateLimitCheck();
|
|
1207
|
+
const startTime = Date.now();
|
|
1208
|
+
|
|
1209
|
+
try {
|
|
1210
|
+
const encoded = transaction.toString('base64');
|
|
1211
|
+
const payload = { transaction: encoded, tip };
|
|
1212
|
+
|
|
1213
|
+
const headers: Record<string, string> = {};
|
|
1214
|
+
if (this.config.apiKey) {
|
|
1215
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
1219
|
+
|
|
1220
|
+
const latencyMs = Date.now() - startTime;
|
|
1221
|
+
this.updateStats(true, latencyMs);
|
|
1222
|
+
|
|
1223
|
+
return {
|
|
1224
|
+
success: true,
|
|
1225
|
+
signature: result.signature,
|
|
1226
|
+
provider: 'QuickNode',
|
|
1227
|
+
latencyMs,
|
|
1228
|
+
};
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
const latencyMs = Date.now() - startTime;
|
|
1231
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1232
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1233
|
+
|
|
1234
|
+
return {
|
|
1235
|
+
success: false,
|
|
1236
|
+
provider: 'QuickNode',
|
|
1237
|
+
latencyMs,
|
|
1238
|
+
error: errorMsg,
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
getProviderType(): SwqosType {
|
|
1244
|
+
return SwqosType.QuickNode;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Syndica SWQOS client - Premium infrastructure
|
|
1250
|
+
*/
|
|
1251
|
+
export class SyndicaClient extends SwqosClient {
|
|
1252
|
+
private apiUrl: string;
|
|
1253
|
+
|
|
1254
|
+
constructor(config: SwqosConfig) {
|
|
1255
|
+
super(config);
|
|
1256
|
+
this.apiUrl = config.url || 'https://api.syndica.io';
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1260
|
+
await this.rateLimitCheck();
|
|
1261
|
+
const startTime = Date.now();
|
|
1262
|
+
|
|
1263
|
+
try {
|
|
1264
|
+
const encoded = transaction.toString('base64');
|
|
1265
|
+
const payload = { transaction: encoded, tip };
|
|
1266
|
+
|
|
1267
|
+
const headers: Record<string, string> = {};
|
|
1268
|
+
if (this.config.apiKey) {
|
|
1269
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
1273
|
+
|
|
1274
|
+
const latencyMs = Date.now() - startTime;
|
|
1275
|
+
this.updateStats(true, latencyMs);
|
|
1276
|
+
|
|
1277
|
+
return {
|
|
1278
|
+
success: true,
|
|
1279
|
+
signature: result.signature,
|
|
1280
|
+
provider: 'Syndica',
|
|
1281
|
+
latencyMs,
|
|
1282
|
+
};
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
const latencyMs = Date.now() - startTime;
|
|
1285
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1286
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1287
|
+
|
|
1288
|
+
return {
|
|
1289
|
+
success: false,
|
|
1290
|
+
provider: 'Syndica',
|
|
1291
|
+
latencyMs,
|
|
1292
|
+
error: errorMsg,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
getProviderType(): SwqosType {
|
|
1298
|
+
return SwqosType.Syndica;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Figment SWQOS client - Enterprise staking RPC
|
|
1304
|
+
*/
|
|
1305
|
+
export class FigmentClient extends SwqosClient {
|
|
1306
|
+
private apiUrl: string;
|
|
1307
|
+
|
|
1308
|
+
constructor(config: SwqosConfig) {
|
|
1309
|
+
super(config);
|
|
1310
|
+
this.apiUrl = config.url || 'https://api.figment.io';
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1314
|
+
await this.rateLimitCheck();
|
|
1315
|
+
const startTime = Date.now();
|
|
1316
|
+
|
|
1317
|
+
try {
|
|
1318
|
+
const encoded = transaction.toString('base64');
|
|
1319
|
+
const payload = { transaction: encoded, tip };
|
|
1320
|
+
|
|
1321
|
+
const headers: Record<string, string> = {};
|
|
1322
|
+
if (this.config.apiKey) {
|
|
1323
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
1327
|
+
|
|
1328
|
+
const latencyMs = Date.now() - startTime;
|
|
1329
|
+
this.updateStats(true, latencyMs);
|
|
1330
|
+
|
|
1331
|
+
return {
|
|
1332
|
+
success: true,
|
|
1333
|
+
signature: result.signature,
|
|
1334
|
+
provider: 'Figment',
|
|
1335
|
+
latencyMs,
|
|
1336
|
+
};
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
const latencyMs = Date.now() - startTime;
|
|
1339
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1340
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1341
|
+
|
|
1342
|
+
return {
|
|
1343
|
+
success: false,
|
|
1344
|
+
provider: 'Figment',
|
|
1345
|
+
latencyMs,
|
|
1346
|
+
error: errorMsg,
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
getProviderType(): SwqosType {
|
|
1352
|
+
return SwqosType.Figment;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* Alchemy SWQOS client - Web3 infrastructure
|
|
1358
|
+
*/
|
|
1359
|
+
export class AlchemyClient extends SwqosClient {
|
|
1360
|
+
private apiUrl: string;
|
|
1361
|
+
|
|
1362
|
+
constructor(config: SwqosConfig) {
|
|
1363
|
+
super(config);
|
|
1364
|
+
this.apiUrl = config.url || 'https://api.alchemy.com';
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1368
|
+
await this.rateLimitCheck();
|
|
1369
|
+
const startTime = Date.now();
|
|
1370
|
+
|
|
1371
|
+
try {
|
|
1372
|
+
const encoded = transaction.toString('base64');
|
|
1373
|
+
const payload = { transaction: encoded, tip };
|
|
1374
|
+
|
|
1375
|
+
const headers: Record<string, string> = {};
|
|
1376
|
+
if (this.config.apiKey) {
|
|
1377
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const result = (await this.post(`${this.apiUrl}/api/v1/submit`, payload, headers)) as any;
|
|
1381
|
+
|
|
1382
|
+
const latencyMs = Date.now() - startTime;
|
|
1383
|
+
this.updateStats(true, latencyMs);
|
|
1384
|
+
|
|
1385
|
+
return {
|
|
1386
|
+
success: true,
|
|
1387
|
+
signature: result.signature,
|
|
1388
|
+
provider: 'Alchemy',
|
|
1389
|
+
latencyMs,
|
|
1390
|
+
};
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
const latencyMs = Date.now() - startTime;
|
|
1393
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1394
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1395
|
+
|
|
1396
|
+
return {
|
|
1397
|
+
success: false,
|
|
1398
|
+
provider: 'Alchemy',
|
|
1399
|
+
latencyMs,
|
|
1400
|
+
error: errorMsg,
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
getProviderType(): SwqosType {
|
|
1406
|
+
return SwqosType.Alchemy;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Default SWQOS client - Standard RPC fallback
|
|
1412
|
+
*/
|
|
1413
|
+
export class DefaultClient extends SwqosClient {
|
|
1414
|
+
private rpcUrl: string;
|
|
1415
|
+
|
|
1416
|
+
constructor(config: SwqosConfig, rpcUrl: string) {
|
|
1417
|
+
super(config);
|
|
1418
|
+
this.rpcUrl = rpcUrl;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
async submitTransaction(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1422
|
+
await this.rateLimitCheck();
|
|
1423
|
+
const startTime = Date.now();
|
|
1424
|
+
|
|
1425
|
+
try {
|
|
1426
|
+
const encoded = transaction.toString('base64');
|
|
1427
|
+
const payload = {
|
|
1428
|
+
jsonrpc: '2.0',
|
|
1429
|
+
id: 1,
|
|
1430
|
+
method: 'sendTransaction',
|
|
1431
|
+
params: [encoded, { encoding: 'base64' }],
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
const result = (await this.post(this.rpcUrl, payload)) as any;
|
|
1435
|
+
|
|
1436
|
+
if (result.error) {
|
|
1437
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const latencyMs = Date.now() - startTime;
|
|
1441
|
+
this.updateStats(true, latencyMs);
|
|
1442
|
+
|
|
1443
|
+
return {
|
|
1444
|
+
success: true,
|
|
1445
|
+
signature: result.result,
|
|
1446
|
+
provider: 'Default',
|
|
1447
|
+
latencyMs,
|
|
1448
|
+
};
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
const latencyMs = Date.now() - startTime;
|
|
1451
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1452
|
+
this.updateStats(false, latencyMs, errorMsg);
|
|
1453
|
+
|
|
1454
|
+
return {
|
|
1455
|
+
success: false,
|
|
1456
|
+
provider: 'Default',
|
|
1457
|
+
latencyMs,
|
|
1458
|
+
error: errorMsg,
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
getProviderType(): SwqosType {
|
|
1464
|
+
return SwqosType.Default;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// ===== Factory =====
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Factory for creating SWQOS clients
|
|
1472
|
+
*/
|
|
1473
|
+
export class SwqosClientFactory {
|
|
1474
|
+
private static readonly CLIENT_MAP: Record<SwqosType, new (config: SwqosConfig) => SwqosClient> = {
|
|
1475
|
+
[SwqosType.Jito]: JitoClient,
|
|
1476
|
+
[SwqosType.Bloxroute]: BloxrouteClient,
|
|
1477
|
+
[SwqosType.ZeroSlot]: ZeroSlotClient,
|
|
1478
|
+
[SwqosType.NextBlock]: NextBlockClient,
|
|
1479
|
+
[SwqosType.Temporal]: TemporalClient,
|
|
1480
|
+
[SwqosType.Node1]: Node1Client,
|
|
1481
|
+
[SwqosType.FlashBlock]: FlashBlockClient,
|
|
1482
|
+
[SwqosType.BlockRazor]: BlockRazorClient,
|
|
1483
|
+
[SwqosType.Astralane]: AstralaneClient,
|
|
1484
|
+
[SwqosType.Stellium]: StelliumClient,
|
|
1485
|
+
[SwqosType.Lightspeed]: LightspeedClient,
|
|
1486
|
+
[SwqosType.Soyas]: SoyasClient,
|
|
1487
|
+
[SwqosType.Speedlanding]: SpeedlandingClient,
|
|
1488
|
+
[SwqosType.Helius]: HeliusClient,
|
|
1489
|
+
[SwqosType.Triton]: TritonClient,
|
|
1490
|
+
[SwqosType.QuickNode]: QuickNodeClient,
|
|
1491
|
+
[SwqosType.Syndica]: SyndicaClient,
|
|
1492
|
+
[SwqosType.Figment]: FigmentClient,
|
|
1493
|
+
[SwqosType.Alchemy]: AlchemyClient,
|
|
1494
|
+
[SwqosType.Default]: DefaultClient as any,
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
/**
|
|
1498
|
+
* Create SWQOS client based on config type
|
|
1499
|
+
*/
|
|
1500
|
+
static createClient(config: SwqosConfig): SwqosClient {
|
|
1501
|
+
const ClientClass = this.CLIENT_MAP[config.swqosType];
|
|
1502
|
+
if (!ClientClass) {
|
|
1503
|
+
throw new TradeError(400, `Unknown SWQOS type: ${config.swqosType}`);
|
|
1504
|
+
}
|
|
1505
|
+
return new ClientClass(config);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* Get list of supported provider types
|
|
1510
|
+
*/
|
|
1511
|
+
static getSupportedTypes(): SwqosType[] {
|
|
1512
|
+
return Object.keys(this.CLIENT_MAP) as SwqosType[];
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// ===== Manager =====
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Manager for multiple SWQOS clients
|
|
1520
|
+
*/
|
|
1521
|
+
export class SwqosManager {
|
|
1522
|
+
private clients: Map<SwqosType, SwqosClient> = new Map();
|
|
1523
|
+
private fallbackOrder: SwqosType[] = [];
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* Add a SWQOS client
|
|
1527
|
+
*/
|
|
1528
|
+
addClient(client: SwqosClient): this {
|
|
1529
|
+
this.clients.set(client.config.swqosType, client);
|
|
1530
|
+
if (!this.fallbackOrder.includes(client.config.swqosType)) {
|
|
1531
|
+
this.fallbackOrder.push(client.config.swqosType);
|
|
1532
|
+
}
|
|
1533
|
+
return this;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Remove a SWQOS client
|
|
1538
|
+
*/
|
|
1539
|
+
removeClient(swqosType: SwqosType): this {
|
|
1540
|
+
this.clients.delete(swqosType);
|
|
1541
|
+
const index = this.fallbackOrder.indexOf(swqosType);
|
|
1542
|
+
if (index > -1) {
|
|
1543
|
+
this.fallbackOrder.splice(index, 1);
|
|
1544
|
+
}
|
|
1545
|
+
return this;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Get SWQOS client by type
|
|
1550
|
+
*/
|
|
1551
|
+
getClient(swqosType: SwqosType): SwqosClient | undefined {
|
|
1552
|
+
return this.clients.get(swqosType);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Get all enabled clients
|
|
1557
|
+
*/
|
|
1558
|
+
getAllClients(): SwqosClient[] {
|
|
1559
|
+
return Array.from(this.clients.values()).filter(c => c.isEnabled());
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* Get client with best performance stats
|
|
1564
|
+
*/
|
|
1565
|
+
getBestClient(): SwqosClient | undefined {
|
|
1566
|
+
const enabled = this.getAllClients();
|
|
1567
|
+
if (enabled.length === 0) {
|
|
1568
|
+
return undefined;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Sort by success rate and latency
|
|
1572
|
+
const score = (client: SwqosClient): number => {
|
|
1573
|
+
const stats = client.getStats();
|
|
1574
|
+
if (stats.requests === 0) {
|
|
1575
|
+
return 0;
|
|
1576
|
+
}
|
|
1577
|
+
const successRate = stats.successes / stats.requests;
|
|
1578
|
+
// Higher success rate and lower latency = better score
|
|
1579
|
+
return (successRate * 1000) / (stats.avgLatencyMs + 1);
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
return enabled.reduce((best, current) =>
|
|
1583
|
+
score(current) > score(best) ? current : best
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Set fallback order for providers
|
|
1589
|
+
*/
|
|
1590
|
+
setFallbackOrder(order: SwqosType[]): void {
|
|
1591
|
+
this.fallbackOrder = order.filter(t => this.clients.has(t));
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Submit with automatic fallback
|
|
1596
|
+
*/
|
|
1597
|
+
async submitWithFallback(transaction: Buffer, tip = 0): Promise<TransactionResult> {
|
|
1598
|
+
for (const swqosType of this.fallbackOrder) {
|
|
1599
|
+
const client = this.clients.get(swqosType);
|
|
1600
|
+
if (!client || !client.isEnabled()) {
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const result = await client.submitTransaction(transaction, tip);
|
|
1605
|
+
if (result.success) {
|
|
1606
|
+
return result;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
return {
|
|
1611
|
+
success: false,
|
|
1612
|
+
provider: 'fallback',
|
|
1613
|
+
latencyMs: 0,
|
|
1614
|
+
error: 'All providers failed',
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Submit transaction to all enabled providers
|
|
1620
|
+
*/
|
|
1621
|
+
async submitToAll(transaction: Buffer, tip = 0): Promise<{
|
|
1622
|
+
results: TransactionResult[];
|
|
1623
|
+
total: number;
|
|
1624
|
+
successful: number;
|
|
1625
|
+
}> {
|
|
1626
|
+
const clients = this.getAllClients();
|
|
1627
|
+
const promises = clients.map(client => client.submitTransaction(transaction, tip));
|
|
1628
|
+
|
|
1629
|
+
const results = await Promise.all(promises);
|
|
1630
|
+
|
|
1631
|
+
return {
|
|
1632
|
+
results,
|
|
1633
|
+
total: promises.length,
|
|
1634
|
+
successful: results.filter(r => r.success).length,
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/**
|
|
1639
|
+
* Submit bundle to all providers that support it
|
|
1640
|
+
*/
|
|
1641
|
+
async submitBundleToAll(
|
|
1642
|
+
transactions: Buffer[],
|
|
1643
|
+
tip = 0
|
|
1644
|
+
): Promise<{
|
|
1645
|
+
results: TransactionResult[];
|
|
1646
|
+
total: number;
|
|
1647
|
+
successful: number;
|
|
1648
|
+
}> {
|
|
1649
|
+
const clients = this.getAllClients();
|
|
1650
|
+
const promises = clients.map(client => client.submitBundle(transactions, tip));
|
|
1651
|
+
|
|
1652
|
+
const results = await Promise.all(promises);
|
|
1653
|
+
|
|
1654
|
+
return {
|
|
1655
|
+
results,
|
|
1656
|
+
total: promises.length,
|
|
1657
|
+
successful: results.filter(r => r.success).length,
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Get stats for all clients
|
|
1663
|
+
*/
|
|
1664
|
+
getAllStats(): Map<SwqosType, ClientStats> {
|
|
1665
|
+
const stats = new Map<SwqosType, ClientStats>();
|
|
1666
|
+
for (const [type, client] of this.clients) {
|
|
1667
|
+
stats.set(type, client.getStats());
|
|
1668
|
+
}
|
|
1669
|
+
return stats;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Get aggregated stats across all clients
|
|
1674
|
+
*/
|
|
1675
|
+
getAggregatedStats(): {
|
|
1676
|
+
totalRequests: number;
|
|
1677
|
+
totalSuccesses: number;
|
|
1678
|
+
totalFailures: number;
|
|
1679
|
+
successRate: number;
|
|
1680
|
+
avgLatencyMs: number;
|
|
1681
|
+
activeProviders: number;
|
|
1682
|
+
} {
|
|
1683
|
+
let totalRequests = 0;
|
|
1684
|
+
let totalSuccesses = 0;
|
|
1685
|
+
let totalFailures = 0;
|
|
1686
|
+
let totalLatency = 0;
|
|
1687
|
+
|
|
1688
|
+
for (const client of this.clients.values()) {
|
|
1689
|
+
const stats = client.getStats();
|
|
1690
|
+
totalRequests += stats.requests;
|
|
1691
|
+
totalSuccesses += stats.successes;
|
|
1692
|
+
totalFailures += stats.failures;
|
|
1693
|
+
totalLatency += stats.avgLatencyMs;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
const clientCount = this.clients.size;
|
|
1697
|
+
|
|
1698
|
+
return {
|
|
1699
|
+
totalRequests,
|
|
1700
|
+
totalSuccesses,
|
|
1701
|
+
totalFailures,
|
|
1702
|
+
successRate: totalRequests > 0 ? totalSuccesses / totalRequests : 0,
|
|
1703
|
+
avgLatencyMs: clientCount > 0 ? totalLatency / clientCount : 0,
|
|
1704
|
+
activeProviders: this.getAllClients().length,
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
}
|