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,1754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SWQOS Clients for Sol Trade SDK
|
|
3
|
+
* Implements various SWQOS (Solana Write Queue Operating System) providers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AstralaneTransport, SwqosTransport, SwqosType, SwqosRegion, TradeType } from '../index';
|
|
7
|
+
import { TradeError } from '../sdk-errors';
|
|
8
|
+
|
|
9
|
+
// ===== Utility =====
|
|
10
|
+
|
|
11
|
+
export function randomChoice<T>(arr: T[]): T {
|
|
12
|
+
const item = arr[Math.floor(Math.random() * arr.length)];
|
|
13
|
+
if (item === undefined) {
|
|
14
|
+
throw new Error('randomChoice called with empty array');
|
|
15
|
+
}
|
|
16
|
+
return item;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ===== Constants =====
|
|
20
|
+
|
|
21
|
+
export const MIN_TIP_JITO = 0.00001;
|
|
22
|
+
export const MIN_TIP_BLOXROUTE = 0.0001;
|
|
23
|
+
export const MIN_TIP_ZERO_SLOT = 0.0001;
|
|
24
|
+
export const MIN_TIP_TEMPORAL = 0.0001;
|
|
25
|
+
export const MIN_TIP_FLASH_BLOCK = 0.0001;
|
|
26
|
+
export const MIN_TIP_BLOCK_RAZOR = 0.0001;
|
|
27
|
+
export const MIN_TIP_NODE1 = 0.0001;
|
|
28
|
+
export const MIN_TIP_ASTRALANE = 0.00001;
|
|
29
|
+
export const MIN_TIP_HELIUS = 0.000005; // swqos_only
|
|
30
|
+
export const MIN_TIP_HELIUS_NORMAL = 0.0002; // 普通模式
|
|
31
|
+
export const MIN_TIP_STELLIUM = 0.0001;
|
|
32
|
+
export const MIN_TIP_LIGHTSPEED = 0.0001;
|
|
33
|
+
export const MIN_TIP_NEXT_BLOCK = 0.001;
|
|
34
|
+
export const MIN_TIP_SOYAS = 0.001;
|
|
35
|
+
export const MIN_TIP_SPEEDLANDING = 0.001;
|
|
36
|
+
export const MIN_TIP_DEFAULT = 0.0;
|
|
37
|
+
|
|
38
|
+
// ===== Tip Accounts =====
|
|
39
|
+
|
|
40
|
+
const JITO_TIP_ACCOUNTS = [
|
|
41
|
+
'96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5',
|
|
42
|
+
'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe',
|
|
43
|
+
'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY',
|
|
44
|
+
'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49',
|
|
45
|
+
'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh',
|
|
46
|
+
'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt',
|
|
47
|
+
'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL',
|
|
48
|
+
'3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const ZERO_SLOT_TIP_ACCOUNTS = [
|
|
52
|
+
'Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3',
|
|
53
|
+
'FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe',
|
|
54
|
+
'ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13',
|
|
55
|
+
'6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK',
|
|
56
|
+
'Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const TEMPORAL_TIP_ACCOUNTS = [
|
|
60
|
+
'TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq',
|
|
61
|
+
'noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4',
|
|
62
|
+
'noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE',
|
|
63
|
+
'noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo',
|
|
64
|
+
'noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ',
|
|
65
|
+
'nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L',
|
|
66
|
+
'nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z',
|
|
67
|
+
'nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu',
|
|
68
|
+
'noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7',
|
|
69
|
+
'nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP',
|
|
70
|
+
'nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P',
|
|
71
|
+
'nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge',
|
|
72
|
+
'nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3',
|
|
73
|
+
'nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ',
|
|
74
|
+
'nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk',
|
|
75
|
+
'nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne',
|
|
76
|
+
'nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const FLASH_BLOCK_TIP_ACCOUNTS = [
|
|
80
|
+
'FLaShB3iXXTWE1vu9wQsChUKq3HFtpMAhb8kAh1pf1wi',
|
|
81
|
+
'FLashhsorBmM9dLpuq6qATawcpqk1Y2aqaZfkd48iT3W',
|
|
82
|
+
'FLaSHJNm5dWYzEgnHJWWJP5ccu128Mu61NJLxUf7mUXU',
|
|
83
|
+
'FLaSHR4Vv7sttd6TyDF4yR1bJyAxRwWKbohDytEMu3wL',
|
|
84
|
+
'FLASHRzANfcAKDuQ3RXv9hbkBy4WVEKDzoAgxJ56DiE4',
|
|
85
|
+
'FLasHstqx11M8W56zrSEqkCyhMCCpr6ze6Mjdvqope5s',
|
|
86
|
+
'FLAShWTjcweNT4NSotpjpxAkwxUr2we3eXQGhpTVzRwy',
|
|
87
|
+
'FLasHXTqrbNvpWFB6grN47HGZfK6pze9HLNTgbukfPSk',
|
|
88
|
+
'FLAShyAyBcKb39KPxSzXcepiS8iDYUhDGwJcJDPX4g2B',
|
|
89
|
+
'FLAsHZTRcf3Dy1APaz6j74ebdMC6Xx4g6i9YxjyrDybR',
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const HELIUS_TIP_ACCOUNTS = [
|
|
93
|
+
'4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE',
|
|
94
|
+
'D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ',
|
|
95
|
+
'9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta',
|
|
96
|
+
'5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn',
|
|
97
|
+
'2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD',
|
|
98
|
+
'2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ',
|
|
99
|
+
'wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF',
|
|
100
|
+
'3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT',
|
|
101
|
+
'4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey',
|
|
102
|
+
'4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or',
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const NODE1_TIP_ACCOUNTS = [
|
|
106
|
+
'node1PqAa3BWWzUnTHVbw8NJHC874zn9ngAkXjgWEej',
|
|
107
|
+
'node1UzzTxAAeBTpfZkQPJXBAqixsbdth11ba1NXLBG',
|
|
108
|
+
'node1Qm1bV4fwYnCurP8otJ9s5yrkPq7SPZ5uhj3Tsv',
|
|
109
|
+
'node1PUber6SFmSQgvf2ECmXsHP5o3boRSGhvJyPMX1',
|
|
110
|
+
'node1AyMbeqiVN6eoQzEAwCA6Pk826hrdqdAHR7cdJ3',
|
|
111
|
+
'node1YtWCoTwwVYTFLfS19zquRQzYX332hs1HEuRBjC',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const BLOCK_RAZOR_TIP_ACCOUNTS = [
|
|
115
|
+
'FjmZZrFvhnqqb9ThCuMVnENaM3JGVuGWNyCAxRJcFpg9',
|
|
116
|
+
'6No2i3aawzHsjtThw81iq1EXPJN6rh8eSJCLaYZfKDTG',
|
|
117
|
+
'A9cWowVAiHe9pJfKAj3TJiN9VpbzMUq6E4kEvf5mUT22',
|
|
118
|
+
'Gywj98ophM7GmkDdaWs4isqZnDdFCW7B46TXmKfvyqSm',
|
|
119
|
+
'68Pwb4jS7eZATjDfhmTXgRJjCiZmw1L7Huy4HNpnxJ3o',
|
|
120
|
+
'4ABhJh5rZPjv63RBJBuyWzBK3g9gWMUQdTZP2kiW31V9',
|
|
121
|
+
'B2M4NG5eyZp5SBQrSdtemzk5TqVuaWGQnowGaCBt8GyM',
|
|
122
|
+
'5jA59cXMKQqZAVdtopv8q3yyw9SYfiE3vUCbt7p8MfVf',
|
|
123
|
+
'5YktoWygr1Bp9wiS1xtMtUki1PeYuuzuCF98tqwYxf61',
|
|
124
|
+
'295Avbam4qGShBYK7E9H5Ldew4B3WyJGmgmXfiWdeeyV',
|
|
125
|
+
'EDi4rSy2LZgKJX74mbLTFk4mxoTgT6F7HxxzG2HBAFyK',
|
|
126
|
+
'BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6',
|
|
127
|
+
'Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq',
|
|
128
|
+
'AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S',
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const ASTRALANE_TIP_ACCOUNTS = [
|
|
132
|
+
'astrazznxsGUhWShqgNtAdfrzP2G83DzcWVJDxwV9bF',
|
|
133
|
+
'astra4uejePWneqNaJKuFFA8oonqCE1sqF6b45kDMZm',
|
|
134
|
+
'astra9xWY93QyfG6yM8zwsKsRodscjQ2uU2HKNL5prk',
|
|
135
|
+
'astraRVUuTHjpwEVvNBeQEgwYx9w9CFyfxjYoobCZhL',
|
|
136
|
+
'astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK',
|
|
137
|
+
'astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC',
|
|
138
|
+
'astraZW5GLFefxNPAatceHhYjfA1ciq9gvfEg2S47xk',
|
|
139
|
+
'astrawVNP4xDBKT7rAdxrLYiTSTdqtUr63fSMduivXK',
|
|
140
|
+
'AstrA1ejL4UeXC2SBP4cpeEmtcFPZVLxx3XGKXyCW6to',
|
|
141
|
+
'AsTra79FET4aCKWspPqeSFvjJNyp96SvAnrmyAxqg5b7',
|
|
142
|
+
'AstrABAu8CBTyuPXpV4eSCJ5fePEPnxN8NqBaPKQ9fHR',
|
|
143
|
+
'AsTRADtvb6tTmrsqULQ9Wji9PigDMjhfEMza6zkynEvV',
|
|
144
|
+
'AsTRAEoyMofR3vUPpf9k68Gsfb6ymTZttEtsAbv8Bk4d',
|
|
145
|
+
'AStrAJv2RN2hKCHxwUMtqmSxgdcNZbihCwc1mCSnG83W',
|
|
146
|
+
'Astran35aiQUF57XZsmkWMtNCtXGLzs8upfiqXxth2bz',
|
|
147
|
+
'AStRAnpi6kFrKypragExgeRoJ1QnKH7pbSjLAKQVWUum',
|
|
148
|
+
'ASTRaoF93eYt73TYvwtsv6fMWHWbGmMUZfVZPo3CRU9C',
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const BLOXROUTE_TIP_ACCOUNTS = [
|
|
152
|
+
'HWEoBxYs7ssKuudEjzjmpfJVX7Dvi7wescFsVx2L5yoY',
|
|
153
|
+
'95cfoy472fcQHaw4tPGBTKpn6ZQnfEPfBgDQx6gcRmRg',
|
|
154
|
+
'3UQUKjhMKaY2S6bjcQD6yHB7utcZt5bfarRCmctpRtUd',
|
|
155
|
+
'FogxVNs6Mm2w9rnGL1vkARSwJxvLE8mujTv3LK8RnUhF',
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const STELLIUM_TIP_ACCOUNTS = [
|
|
159
|
+
'ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH',
|
|
160
|
+
'ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt',
|
|
161
|
+
'ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb',
|
|
162
|
+
'ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN',
|
|
163
|
+
'ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX',
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const NEXT_BLOCK_TIP_ACCOUNTS = [
|
|
167
|
+
'NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE',
|
|
168
|
+
'NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2',
|
|
169
|
+
'NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X',
|
|
170
|
+
'NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb',
|
|
171
|
+
'neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At',
|
|
172
|
+
'nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG',
|
|
173
|
+
'NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid',
|
|
174
|
+
'nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc',
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const SOYAS_TIP_ACCOUNTS = [
|
|
178
|
+
'soyas4s6L8KWZ8rsSk1mF3d1mQScoTGGAgjk98bF8nP',
|
|
179
|
+
'soyascXFW5wEEYiwfEmHy2pNwomqzvggJosGVD6TJdY',
|
|
180
|
+
'soyasDBdKjADwPz3xk82U3TNPRDKEWJj7wWLajNHZ1L',
|
|
181
|
+
'soyasE2abjBAynmHbGWgEwk4ctBy7JMTUCNrMbjcnyH',
|
|
182
|
+
'soyasi59njacMUPvo3TM5paHjeK8pYSdovXgFi32gRt',
|
|
183
|
+
'soyasQYhJxv8uZgWDxhg72td6piAf7XTkoyWHtSATEz',
|
|
184
|
+
'soyastP66xyYC8XADXZjdMM5BAVGD2YRvz8dwtLsqb8',
|
|
185
|
+
'soyasvdgUJWYcUCzDxpmjUnNjH7KamXLXTzLwFvdVPE',
|
|
186
|
+
'soyasvxAunisNxaoRxkKGjNir7KmbwYnr37JmefkX9G',
|
|
187
|
+
'soyas5doVFUwH8s5zK8gEvCL5KR5ogDmf52LsrJEZ9h',
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const SPEEDLANDING_TIP_ACCOUNTS = [
|
|
191
|
+
'SpEEdz8S1KorkMZqjMUxfxrmWwofmp6ReNP2Nx6CUmq',
|
|
192
|
+
'SpeeDy3GJM4wcrQmk1itRFWgidvxX4rwjTLMv78wwjE',
|
|
193
|
+
'SPeEdva37vW8vRtqgYjprQs1g3965icfVN5Rt7SMAyh',
|
|
194
|
+
'speEdrSEpox5GUfHWcBc7tQjRuSfUin2yvB7qoYvvJh',
|
|
195
|
+
'SPeEDmkHkN3A2roSZf6aZyEMsmrGqTHKqwP51y2Y4rV',
|
|
196
|
+
'SpeedLdTJXh2RKpXEaP8JCxkWoUVXhtdPQ1EnxBJMxc',
|
|
197
|
+
'SpEediGKLbbXndSYTzwmz6Z3NDgHQLDcTDEvGFkSMH9',
|
|
198
|
+
'speede8xCcUq2Tiv1efXeTuE3k9TDNq8TnGKaKSc6J4',
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// ===== Region Endpoint Maps =====
|
|
202
|
+
|
|
203
|
+
export const JITO_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
204
|
+
[SwqosRegion.NewYork]: 'https://ny.mainnet.block-engine.jito.wtf',
|
|
205
|
+
[SwqosRegion.Frankfurt]: 'https://frankfurt.mainnet.block-engine.jito.wtf',
|
|
206
|
+
[SwqosRegion.Amsterdam]: 'https://amsterdam.mainnet.block-engine.jito.wtf',
|
|
207
|
+
[SwqosRegion.Dublin]: 'https://dublin.mainnet.block-engine.jito.wtf',
|
|
208
|
+
[SwqosRegion.SLC]: 'https://slc.mainnet.block-engine.jito.wtf',
|
|
209
|
+
[SwqosRegion.Tokyo]: 'https://tokyo.mainnet.block-engine.jito.wtf',
|
|
210
|
+
[SwqosRegion.London]: 'https://london.mainnet.block-engine.jito.wtf',
|
|
211
|
+
[SwqosRegion.LosAngeles]: 'https://slc.mainnet.block-engine.jito.wtf',
|
|
212
|
+
[SwqosRegion.Singapore]: 'https://singapore.mainnet.block-engine.jito.wtf',
|
|
213
|
+
[SwqosRegion.Default]: 'https://mainnet.block-engine.jito.wtf',
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const BLOXROUTE_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
217
|
+
[SwqosRegion.NewYork]: 'https://ny.solana.dex.blxrbdn.com',
|
|
218
|
+
[SwqosRegion.Frankfurt]: 'https://germany.solana.dex.blxrbdn.com',
|
|
219
|
+
[SwqosRegion.Amsterdam]: 'https://amsterdam.solana.dex.blxrbdn.com',
|
|
220
|
+
[SwqosRegion.Dublin]: 'https://uk.solana.dex.blxrbdn.com',
|
|
221
|
+
[SwqosRegion.SLC]: 'https://ny.solana.dex.blxrbdn.com',
|
|
222
|
+
[SwqosRegion.Tokyo]: 'https://tokyo.solana.dex.blxrbdn.com',
|
|
223
|
+
[SwqosRegion.London]: 'https://uk.solana.dex.blxrbdn.com',
|
|
224
|
+
[SwqosRegion.LosAngeles]: 'https://la.solana.dex.blxrbdn.com',
|
|
225
|
+
[SwqosRegion.Singapore]: 'https://global.solana.dex.blxrbdn.com',
|
|
226
|
+
[SwqosRegion.Default]: 'https://global.solana.dex.blxrbdn.com',
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const ZERO_SLOT_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
230
|
+
[SwqosRegion.NewYork]: 'http://ny.0slot.trade',
|
|
231
|
+
[SwqosRegion.Frankfurt]: 'http://de2.0slot.trade',
|
|
232
|
+
[SwqosRegion.Amsterdam]: 'http://ams.0slot.trade',
|
|
233
|
+
[SwqosRegion.Dublin]: 'http://ams.0slot.trade',
|
|
234
|
+
[SwqosRegion.SLC]: 'http://la.0slot.trade',
|
|
235
|
+
[SwqosRegion.Tokyo]: 'http://jp.0slot.trade',
|
|
236
|
+
[SwqosRegion.London]: 'http://ams.0slot.trade',
|
|
237
|
+
[SwqosRegion.LosAngeles]: 'http://la.0slot.trade',
|
|
238
|
+
[SwqosRegion.Singapore]: 'http://jp.0slot.trade',
|
|
239
|
+
[SwqosRegion.Default]: 'http://de2.0slot.trade',
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const TEMPORAL_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
243
|
+
[SwqosRegion.NewYork]: 'http://ewr1.nozomi.temporal.xyz',
|
|
244
|
+
[SwqosRegion.Frankfurt]: 'http://fra2.nozomi.temporal.xyz',
|
|
245
|
+
[SwqosRegion.Amsterdam]: 'http://ams1.nozomi.temporal.xyz',
|
|
246
|
+
[SwqosRegion.Dublin]: 'http://lon1.nozomi.temporal.xyz',
|
|
247
|
+
[SwqosRegion.SLC]: 'http://lax1.nozomi.temporal.xyz',
|
|
248
|
+
[SwqosRegion.Tokyo]: 'http://tyo1.nozomi.temporal.xyz',
|
|
249
|
+
[SwqosRegion.London]: 'http://lon1.nozomi.temporal.xyz',
|
|
250
|
+
[SwqosRegion.LosAngeles]: 'http://lax1.nozomi.temporal.xyz',
|
|
251
|
+
[SwqosRegion.Singapore]: 'http://sgp1.nozomi.temporal.xyz',
|
|
252
|
+
[SwqosRegion.Default]: 'http://fra2.nozomi.temporal.xyz',
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export const FLASH_BLOCK_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
256
|
+
[SwqosRegion.NewYork]: 'http://ny.flashblock.trade',
|
|
257
|
+
[SwqosRegion.Frankfurt]: 'http://fra.flashblock.trade',
|
|
258
|
+
[SwqosRegion.Amsterdam]: 'http://ams.flashblock.trade',
|
|
259
|
+
[SwqosRegion.Dublin]: 'http://london.flashblock.trade',
|
|
260
|
+
[SwqosRegion.SLC]: 'http://slc.flashblock.trade',
|
|
261
|
+
[SwqosRegion.Tokyo]: 'http://tokyo.flashblock.trade',
|
|
262
|
+
[SwqosRegion.London]: 'http://london.flashblock.trade',
|
|
263
|
+
[SwqosRegion.LosAngeles]: 'http://slc.flashblock.trade',
|
|
264
|
+
[SwqosRegion.Singapore]: 'http://singapore.flashblock.trade',
|
|
265
|
+
[SwqosRegion.Default]: 'http://fra.flashblock.trade',
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const HELIUS_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
269
|
+
[SwqosRegion.NewYork]: 'http://ewr-sender.helius-rpc.com/fast',
|
|
270
|
+
[SwqosRegion.Frankfurt]: 'http://fra-sender.helius-rpc.com/fast',
|
|
271
|
+
[SwqosRegion.Amsterdam]: 'http://ams-sender.helius-rpc.com/fast',
|
|
272
|
+
[SwqosRegion.Dublin]: 'http://lon-sender.helius-rpc.com/fast',
|
|
273
|
+
[SwqosRegion.SLC]: 'http://slc-sender.helius-rpc.com/fast',
|
|
274
|
+
[SwqosRegion.Tokyo]: 'http://tyo-sender.helius-rpc.com/fast',
|
|
275
|
+
[SwqosRegion.London]: 'http://lon-sender.helius-rpc.com/fast',
|
|
276
|
+
[SwqosRegion.LosAngeles]: 'http://slc-sender.helius-rpc.com/fast',
|
|
277
|
+
[SwqosRegion.Singapore]: 'http://sg-sender.helius-rpc.com/fast',
|
|
278
|
+
[SwqosRegion.Default]: 'https://sender.helius-rpc.com/fast',
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const NODE1_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
282
|
+
[SwqosRegion.NewYork]: 'http://ny.node1.me',
|
|
283
|
+
[SwqosRegion.Frankfurt]: 'http://fra.node1.me',
|
|
284
|
+
[SwqosRegion.Amsterdam]: 'http://ams.node1.me',
|
|
285
|
+
[SwqosRegion.Dublin]: 'http://lon.node1.me',
|
|
286
|
+
[SwqosRegion.SLC]: 'http://ny.node1.me',
|
|
287
|
+
[SwqosRegion.Tokyo]: 'http://tk.node1.me',
|
|
288
|
+
[SwqosRegion.London]: 'http://lon.node1.me',
|
|
289
|
+
[SwqosRegion.LosAngeles]: 'http://ny.node1.me',
|
|
290
|
+
[SwqosRegion.Singapore]: 'http://fra.node1.me',
|
|
291
|
+
[SwqosRegion.Default]: 'http://fra.node1.me',
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export const BLOCK_RAZOR_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
295
|
+
[SwqosRegion.NewYork]: 'http://newyork.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
296
|
+
[SwqosRegion.Frankfurt]: 'http://frankfurt.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
297
|
+
[SwqosRegion.Amsterdam]: 'http://amsterdam.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
298
|
+
[SwqosRegion.Dublin]: 'http://london.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
299
|
+
[SwqosRegion.SLC]: 'http://newyork.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
300
|
+
[SwqosRegion.Tokyo]: 'http://tokyo.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
301
|
+
[SwqosRegion.London]: 'http://london.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
302
|
+
[SwqosRegion.LosAngeles]: 'http://newyork.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
303
|
+
[SwqosRegion.Singapore]: 'http://frankfurt.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
304
|
+
[SwqosRegion.Default]: 'http://frankfurt.solana.blockrazor.xyz:443/v2/sendTransaction',
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export const ASTRALANE_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
308
|
+
[SwqosRegion.NewYork]: 'http://ny.gateway.astralane.io/irisb',
|
|
309
|
+
[SwqosRegion.Frankfurt]: 'http://fr.gateway.astralane.io/irisb',
|
|
310
|
+
[SwqosRegion.Amsterdam]: 'http://ams.gateway.astralane.io/irisb',
|
|
311
|
+
[SwqosRegion.Dublin]: 'http://ams.gateway.astralane.io/irisb',
|
|
312
|
+
[SwqosRegion.SLC]: 'http://ny.gateway.astralane.io/irisb',
|
|
313
|
+
[SwqosRegion.Tokyo]: 'http://jp.gateway.astralane.io/irisb',
|
|
314
|
+
[SwqosRegion.London]: 'http://ams.gateway.astralane.io/irisb',
|
|
315
|
+
[SwqosRegion.LosAngeles]: 'http://lax.gateway.astralane.io/irisb',
|
|
316
|
+
[SwqosRegion.Singapore]: 'http://lim.gateway.astralane.io/irisb',
|
|
317
|
+
[SwqosRegion.Default]: 'http://lim.gateway.astralane.io/irisb',
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
export const ASTRALANE_QUIC_HOSTS: Record<SwqosRegion, string> = {
|
|
321
|
+
[SwqosRegion.NewYork]: 'ny.gateway.astralane.io',
|
|
322
|
+
[SwqosRegion.Frankfurt]: 'fr.gateway.astralane.io',
|
|
323
|
+
[SwqosRegion.Amsterdam]: 'ams.gateway.astralane.io',
|
|
324
|
+
[SwqosRegion.Dublin]: 'ams.gateway.astralane.io',
|
|
325
|
+
[SwqosRegion.SLC]: 'ny.gateway.astralane.io',
|
|
326
|
+
[SwqosRegion.Tokyo]: 'jp.gateway.astralane.io',
|
|
327
|
+
[SwqosRegion.London]: 'ams.gateway.astralane.io',
|
|
328
|
+
[SwqosRegion.LosAngeles]: 'lax.gateway.astralane.io',
|
|
329
|
+
[SwqosRegion.Singapore]: 'lim.gateway.astralane.io',
|
|
330
|
+
[SwqosRegion.Default]: 'lim.gateway.astralane.io',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const STELLIUM_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
334
|
+
[SwqosRegion.NewYork]: 'http://ewr1.flashrpc.com',
|
|
335
|
+
[SwqosRegion.Frankfurt]: 'http://fra1.flashrpc.com',
|
|
336
|
+
[SwqosRegion.Amsterdam]: 'http://ams1.flashrpc.com',
|
|
337
|
+
[SwqosRegion.Dublin]: 'http://lhr1.flashrpc.com',
|
|
338
|
+
[SwqosRegion.SLC]: 'http://ewr1.flashrpc.com',
|
|
339
|
+
[SwqosRegion.Tokyo]: 'http://tyo1.flashrpc.com',
|
|
340
|
+
[SwqosRegion.London]: 'http://lhr1.flashrpc.com',
|
|
341
|
+
[SwqosRegion.LosAngeles]: 'http://ewr1.flashrpc.com',
|
|
342
|
+
[SwqosRegion.Singapore]: 'http://fra1.flashrpc.com',
|
|
343
|
+
[SwqosRegion.Default]: 'http://fra1.flashrpc.com',
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export const NEXT_BLOCK_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
347
|
+
[SwqosRegion.NewYork]: 'http://ny.nextblock.io',
|
|
348
|
+
[SwqosRegion.Frankfurt]: 'http://fra.nextblock.io',
|
|
349
|
+
[SwqosRegion.Amsterdam]: 'http://ams.nextblock.io',
|
|
350
|
+
[SwqosRegion.Dublin]: 'http://dublin.nextblock.io',
|
|
351
|
+
[SwqosRegion.SLC]: 'http://slc.nextblock.io',
|
|
352
|
+
[SwqosRegion.Tokyo]: 'http://tokyo.nextblock.io',
|
|
353
|
+
[SwqosRegion.London]: 'http://london.nextblock.io',
|
|
354
|
+
[SwqosRegion.LosAngeles]: 'http://slc.nextblock.io',
|
|
355
|
+
[SwqosRegion.Singapore]: 'http://sgp.nextblock.io',
|
|
356
|
+
[SwqosRegion.Default]: 'http://fra.nextblock.io',
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export const SOYAS_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
360
|
+
[SwqosRegion.NewYork]: 'nyc.landing.soyas.xyz:9000',
|
|
361
|
+
[SwqosRegion.Frankfurt]: 'fra.landing.soyas.xyz:9000',
|
|
362
|
+
[SwqosRegion.Amsterdam]: 'ams.landing.soyas.xyz:9000',
|
|
363
|
+
[SwqosRegion.Dublin]: 'lon.landing.soyas.xyz:9000',
|
|
364
|
+
[SwqosRegion.SLC]: 'nyc.landing.soyas.xyz:9000',
|
|
365
|
+
[SwqosRegion.Tokyo]: 'tyo.landing.soyas.xyz:9000',
|
|
366
|
+
[SwqosRegion.London]: 'lon.landing.soyas.xyz:9000',
|
|
367
|
+
[SwqosRegion.LosAngeles]: 'nyc.landing.soyas.xyz:9000',
|
|
368
|
+
[SwqosRegion.Singapore]: 'fra.landing.soyas.xyz:9000',
|
|
369
|
+
[SwqosRegion.Default]: 'fra.landing.soyas.xyz:9000',
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export const SPEEDLANDING_ENDPOINTS: Record<SwqosRegion, string> = {
|
|
373
|
+
[SwqosRegion.NewYork]: 'nyc.speedlanding.trade:17778',
|
|
374
|
+
[SwqosRegion.Frankfurt]: 'fra.speedlanding.trade:17778',
|
|
375
|
+
[SwqosRegion.Amsterdam]: 'ams.speedlanding.trade:17778',
|
|
376
|
+
[SwqosRegion.Dublin]: 'ams.speedlanding.trade:17778',
|
|
377
|
+
[SwqosRegion.SLC]: 'nyc.speedlanding.trade:17778',
|
|
378
|
+
[SwqosRegion.Tokyo]: 'tyo.speedlanding.trade:17778',
|
|
379
|
+
[SwqosRegion.London]: 'fra.speedlanding.trade:17778',
|
|
380
|
+
[SwqosRegion.LosAngeles]: 'nyc.speedlanding.trade:17778',
|
|
381
|
+
[SwqosRegion.Singapore]: 'fra.speedlanding.trade:17778',
|
|
382
|
+
[SwqosRegion.Default]: 'fra.speedlanding.trade:17778',
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// ===== SWQOS Client Interface =====
|
|
386
|
+
|
|
387
|
+
export interface SwqosClient {
|
|
388
|
+
sendTransaction(
|
|
389
|
+
tradeType: TradeType,
|
|
390
|
+
transaction: Buffer,
|
|
391
|
+
waitConfirmation: boolean
|
|
392
|
+
): Promise<string>;
|
|
393
|
+
|
|
394
|
+
sendTransactions(
|
|
395
|
+
tradeType: TradeType,
|
|
396
|
+
transactions: Buffer[],
|
|
397
|
+
waitConfirmation: boolean
|
|
398
|
+
): Promise<string[]>;
|
|
399
|
+
|
|
400
|
+
getTipAccount(): string;
|
|
401
|
+
getSwqosType(): SwqosType;
|
|
402
|
+
minTipSol(): number;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ===== HTTP Client Base =====
|
|
406
|
+
|
|
407
|
+
abstract class BaseClient implements SwqosClient {
|
|
408
|
+
abstract getTipAccount(): string;
|
|
409
|
+
abstract getSwqosType(): SwqosType;
|
|
410
|
+
abstract minTipSol(): number;
|
|
411
|
+
abstract sendTransaction(
|
|
412
|
+
tradeType: TradeType,
|
|
413
|
+
transaction: Buffer,
|
|
414
|
+
waitConfirmation: boolean
|
|
415
|
+
): Promise<string>;
|
|
416
|
+
|
|
417
|
+
async sendTransactions(
|
|
418
|
+
tradeType: TradeType,
|
|
419
|
+
transactions: Buffer[],
|
|
420
|
+
waitConfirmation: boolean
|
|
421
|
+
): Promise<string[]> {
|
|
422
|
+
const signatures: string[] = [];
|
|
423
|
+
for (const tx of transactions) {
|
|
424
|
+
const sig = await this.sendTransaction(tradeType, tx, waitConfirmation);
|
|
425
|
+
signatures.push(sig);
|
|
426
|
+
}
|
|
427
|
+
return signatures;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
protected async post(url: string, payload: unknown, headers: Record<string, string> = {}): Promise<unknown> {
|
|
431
|
+
const response = await fetch(url, {
|
|
432
|
+
method: 'POST',
|
|
433
|
+
headers: {
|
|
434
|
+
'Content-Type': 'application/json',
|
|
435
|
+
...headers,
|
|
436
|
+
},
|
|
437
|
+
body: JSON.stringify(payload),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
if (!response.ok) {
|
|
441
|
+
throw new TradeError(response.status, `HTTP error: ${response.statusText}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return response.json();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
protected async postRaw(url: string, body: string, headers: Record<string, string> = {}): Promise<unknown> {
|
|
448
|
+
const response = await fetch(url, {
|
|
449
|
+
method: 'POST',
|
|
450
|
+
headers: {
|
|
451
|
+
'Content-Type': 'text/plain',
|
|
452
|
+
...headers,
|
|
453
|
+
},
|
|
454
|
+
body,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (!response.ok) {
|
|
458
|
+
throw new TradeError(response.status, `HTTP error: ${response.statusText}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return response.json();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ===== Jito Client =====
|
|
466
|
+
|
|
467
|
+
export class JitoClient extends BaseClient {
|
|
468
|
+
private tipAccounts = JITO_TIP_ACCOUNTS;
|
|
469
|
+
|
|
470
|
+
constructor(
|
|
471
|
+
private rpcUrl: string,
|
|
472
|
+
private endpoint: string,
|
|
473
|
+
private authToken?: string
|
|
474
|
+
) {
|
|
475
|
+
super();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async sendTransaction(
|
|
479
|
+
tradeType: TradeType,
|
|
480
|
+
transaction: Buffer,
|
|
481
|
+
waitConfirmation: boolean
|
|
482
|
+
): Promise<string> {
|
|
483
|
+
const encoded = transaction.toString('base64');
|
|
484
|
+
|
|
485
|
+
const payload = {
|
|
486
|
+
jsonrpc: '2.0',
|
|
487
|
+
id: 1,
|
|
488
|
+
method: 'sendTransaction',
|
|
489
|
+
params: [
|
|
490
|
+
encoded,
|
|
491
|
+
{ encoding: 'base64' },
|
|
492
|
+
],
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const headers: Record<string, string> = {};
|
|
496
|
+
let url = `${this.endpoint}/api/v1/transactions`;
|
|
497
|
+
if (this.authToken) {
|
|
498
|
+
headers['x-jito-auth'] = this.authToken;
|
|
499
|
+
url = `${this.endpoint}/api/v1/transactions?uuid=${this.authToken}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const result = (await this.post(url, payload, headers)) as any;
|
|
503
|
+
|
|
504
|
+
if (result.error) {
|
|
505
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return result.result;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async sendTransactions(
|
|
512
|
+
tradeType: TradeType,
|
|
513
|
+
transactions: Buffer[],
|
|
514
|
+
waitConfirmation: boolean
|
|
515
|
+
): Promise<string[]> {
|
|
516
|
+
if (transactions.length === 0) return [];
|
|
517
|
+
if (transactions.length === 1) {
|
|
518
|
+
const tx = transactions[0]!;
|
|
519
|
+
return [await this.sendTransaction(tradeType, tx, waitConfirmation)];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const encodedTxs = transactions.map(tx => tx.toString('base64'));
|
|
523
|
+
|
|
524
|
+
const payload = {
|
|
525
|
+
jsonrpc: '2.0',
|
|
526
|
+
method: 'sendBundle',
|
|
527
|
+
params: [encodedTxs, { encoding: 'base64' }],
|
|
528
|
+
id: 1,
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const headers: Record<string, string> = {};
|
|
532
|
+
let url = `${this.endpoint}/api/v1/bundles`;
|
|
533
|
+
if (this.authToken) {
|
|
534
|
+
headers['x-jito-auth'] = this.authToken;
|
|
535
|
+
url = `${this.endpoint}/api/v1/bundles?uuid=${this.authToken}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const result = (await this.post(url, payload, headers)) as any;
|
|
539
|
+
|
|
540
|
+
if (result.error) {
|
|
541
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Bundle returns a single bundle ID, wrap in array for interface compatibility
|
|
545
|
+
return [result.result];
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
getTipAccount(): string {
|
|
549
|
+
return randomChoice(this.tipAccounts);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
getSwqosType(): SwqosType {
|
|
553
|
+
return SwqosType.Jito;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
minTipSol(): number {
|
|
557
|
+
return MIN_TIP_JITO;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ===== Bloxroute Client =====
|
|
562
|
+
|
|
563
|
+
export class BloxrouteClient extends BaseClient {
|
|
564
|
+
private tipAccounts = BLOXROUTE_TIP_ACCOUNTS;
|
|
565
|
+
|
|
566
|
+
constructor(
|
|
567
|
+
private rpcUrl: string,
|
|
568
|
+
private endpoint: string,
|
|
569
|
+
private authToken?: string
|
|
570
|
+
) {
|
|
571
|
+
super();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async sendTransaction(
|
|
575
|
+
tradeType: TradeType,
|
|
576
|
+
transaction: Buffer,
|
|
577
|
+
waitConfirmation: boolean
|
|
578
|
+
): Promise<string> {
|
|
579
|
+
const encoded = transaction.toString('base64');
|
|
580
|
+
|
|
581
|
+
const payload = {
|
|
582
|
+
transaction: { content: encoded },
|
|
583
|
+
frontRunningProtection: false,
|
|
584
|
+
useStakedRPCs: true,
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const headers: Record<string, string> = {};
|
|
588
|
+
if (this.authToken) {
|
|
589
|
+
headers['Authorization'] = this.authToken;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const url = `${this.endpoint}/api/v2/submit`;
|
|
593
|
+
const result = (await this.post(url, payload, headers)) as any;
|
|
594
|
+
|
|
595
|
+
if (result.error) {
|
|
596
|
+
throw new TradeError(result.error.code || 500, result.error.message || result.error);
|
|
597
|
+
}
|
|
598
|
+
if (result.reason) {
|
|
599
|
+
throw new TradeError(500, result.reason);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return result.signature || result.result || '';
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
getTipAccount(): string {
|
|
606
|
+
return randomChoice(this.tipAccounts);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
getSwqosType(): SwqosType {
|
|
610
|
+
return SwqosType.Bloxroute;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
minTipSol(): number {
|
|
614
|
+
return MIN_TIP_BLOXROUTE;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ===== ZeroSlot Client =====
|
|
619
|
+
|
|
620
|
+
export class ZeroSlotClient extends BaseClient {
|
|
621
|
+
private tipAccounts = ZERO_SLOT_TIP_ACCOUNTS;
|
|
622
|
+
|
|
623
|
+
constructor(
|
|
624
|
+
private rpcUrl: string,
|
|
625
|
+
private endpoint: string,
|
|
626
|
+
private authToken?: string
|
|
627
|
+
) {
|
|
628
|
+
super();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async sendTransaction(
|
|
632
|
+
tradeType: TradeType,
|
|
633
|
+
transaction: Buffer,
|
|
634
|
+
waitConfirmation: boolean
|
|
635
|
+
): Promise<string> {
|
|
636
|
+
const encoded = transaction.toString('base64');
|
|
637
|
+
|
|
638
|
+
const payload = {
|
|
639
|
+
jsonrpc: '2.0',
|
|
640
|
+
id: 1,
|
|
641
|
+
method: 'sendTransaction',
|
|
642
|
+
params: [encoded, { encoding: 'base64' }],
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// Auth in URL param, no Authorization header
|
|
646
|
+
let url = this.endpoint;
|
|
647
|
+
if (this.authToken) {
|
|
648
|
+
url = `${this.endpoint}?api-key=${this.authToken}`;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const result = (await this.post(url, payload)) as any;
|
|
652
|
+
|
|
653
|
+
if (result.error) {
|
|
654
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return result.result;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
getTipAccount(): string {
|
|
661
|
+
return randomChoice(this.tipAccounts);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
getSwqosType(): SwqosType {
|
|
665
|
+
return SwqosType.ZeroSlot;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
minTipSol(): number {
|
|
669
|
+
return MIN_TIP_ZERO_SLOT;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ===== Temporal Client =====
|
|
674
|
+
|
|
675
|
+
export class TemporalClient extends BaseClient {
|
|
676
|
+
private tipAccounts = TEMPORAL_TIP_ACCOUNTS;
|
|
677
|
+
|
|
678
|
+
constructor(
|
|
679
|
+
private rpcUrl: string,
|
|
680
|
+
private endpoint: string,
|
|
681
|
+
private authToken?: string
|
|
682
|
+
) {
|
|
683
|
+
super();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async sendTransaction(
|
|
687
|
+
tradeType: TradeType,
|
|
688
|
+
transaction: Buffer,
|
|
689
|
+
waitConfirmation: boolean
|
|
690
|
+
): Promise<string> {
|
|
691
|
+
const encoded = transaction.toString('base64');
|
|
692
|
+
|
|
693
|
+
const payload = {
|
|
694
|
+
jsonrpc: '2.0',
|
|
695
|
+
id: 1,
|
|
696
|
+
method: 'sendTransaction',
|
|
697
|
+
params: [encoded, { encoding: 'base64' }],
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// Auth in URL param ?c=token, no Authorization header
|
|
701
|
+
let url = this.endpoint;
|
|
702
|
+
if (this.authToken) {
|
|
703
|
+
url = `${this.endpoint}/?c=${this.authToken}`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const result = (await this.post(url, payload)) as any;
|
|
707
|
+
|
|
708
|
+
if (result.error) {
|
|
709
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return result.result;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
getTipAccount(): string {
|
|
716
|
+
return randomChoice(this.tipAccounts);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
getSwqosType(): SwqosType {
|
|
720
|
+
return SwqosType.Temporal;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
minTipSol(): number {
|
|
724
|
+
return MIN_TIP_TEMPORAL;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// ===== FlashBlock Client =====
|
|
729
|
+
|
|
730
|
+
export class FlashBlockClient extends BaseClient {
|
|
731
|
+
private tipAccounts = FLASH_BLOCK_TIP_ACCOUNTS;
|
|
732
|
+
|
|
733
|
+
constructor(
|
|
734
|
+
private rpcUrl: string,
|
|
735
|
+
private endpoint: string,
|
|
736
|
+
private authToken?: string
|
|
737
|
+
) {
|
|
738
|
+
super();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async sendTransaction(
|
|
742
|
+
tradeType: TradeType,
|
|
743
|
+
transaction: Buffer,
|
|
744
|
+
waitConfirmation: boolean
|
|
745
|
+
): Promise<string> {
|
|
746
|
+
const encoded = transaction.toString('base64');
|
|
747
|
+
|
|
748
|
+
const payload = {
|
|
749
|
+
transactions: [encoded],
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
const headers: Record<string, string> = {};
|
|
753
|
+
if (this.authToken) {
|
|
754
|
+
headers['Authorization'] = this.authToken;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const url = `${this.endpoint}/api/v2/submit-batch`;
|
|
758
|
+
const result = (await this.post(url, payload, headers)) as any;
|
|
759
|
+
|
|
760
|
+
if (result.error) {
|
|
761
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Batch submit may return array of results
|
|
765
|
+
if (Array.isArray(result) && result.length > 0) {
|
|
766
|
+
return result[0].signature || result[0].result || '';
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return result.signature || result.result || '';
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
getTipAccount(): string {
|
|
773
|
+
return randomChoice(this.tipAccounts);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
getSwqosType(): SwqosType {
|
|
777
|
+
return SwqosType.FlashBlock;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
minTipSol(): number {
|
|
781
|
+
return MIN_TIP_FLASH_BLOCK;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ===== Helius Client =====
|
|
786
|
+
|
|
787
|
+
export class HeliusClient extends BaseClient {
|
|
788
|
+
private tipAccounts = HELIUS_TIP_ACCOUNTS;
|
|
789
|
+
|
|
790
|
+
constructor(
|
|
791
|
+
private rpcUrl: string,
|
|
792
|
+
private endpoint: string,
|
|
793
|
+
private apiKey?: string,
|
|
794
|
+
private swqosOnly: boolean = false
|
|
795
|
+
) {
|
|
796
|
+
super();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async sendTransaction(
|
|
800
|
+
tradeType: TradeType,
|
|
801
|
+
transaction: Buffer,
|
|
802
|
+
waitConfirmation: boolean
|
|
803
|
+
): Promise<string> {
|
|
804
|
+
const encoded = transaction.toString('base64');
|
|
805
|
+
|
|
806
|
+
const payload = {
|
|
807
|
+
jsonrpc: '2.0',
|
|
808
|
+
id: '1', // string "1" per Helius API spec
|
|
809
|
+
method: 'sendTransaction',
|
|
810
|
+
params: [
|
|
811
|
+
encoded,
|
|
812
|
+
{
|
|
813
|
+
encoding: 'base64',
|
|
814
|
+
skipPreflight: true,
|
|
815
|
+
maxRetries: 0,
|
|
816
|
+
},
|
|
817
|
+
],
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// Auth in URL param ?api-key=...
|
|
821
|
+
let url = this.endpoint;
|
|
822
|
+
if (this.apiKey) {
|
|
823
|
+
url = `${this.endpoint}?api-key=${this.apiKey}`;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const result = (await this.post(url, payload)) as any;
|
|
827
|
+
|
|
828
|
+
if (result.error) {
|
|
829
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return result.result;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
getTipAccount(): string {
|
|
836
|
+
return randomChoice(this.tipAccounts);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
getSwqosType(): SwqosType {
|
|
840
|
+
return SwqosType.Helius;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
minTipSol(): number {
|
|
844
|
+
return this.swqosOnly ? MIN_TIP_HELIUS : MIN_TIP_HELIUS_NORMAL;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// ===== Node1 Client =====
|
|
849
|
+
|
|
850
|
+
export class Node1Client extends BaseClient {
|
|
851
|
+
private tipAccounts = NODE1_TIP_ACCOUNTS;
|
|
852
|
+
|
|
853
|
+
constructor(
|
|
854
|
+
private rpcUrl: string,
|
|
855
|
+
private endpoint: string,
|
|
856
|
+
private authToken?: string
|
|
857
|
+
) {
|
|
858
|
+
super();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
async sendTransaction(
|
|
862
|
+
tradeType: TradeType,
|
|
863
|
+
transaction: Buffer,
|
|
864
|
+
waitConfirmation: boolean
|
|
865
|
+
): Promise<string> {
|
|
866
|
+
const encoded = transaction.toString('base64');
|
|
867
|
+
|
|
868
|
+
const payload = {
|
|
869
|
+
jsonrpc: '2.0',
|
|
870
|
+
id: 1,
|
|
871
|
+
method: 'sendTransaction',
|
|
872
|
+
params: [encoded, { encoding: 'base64', skipPreflight: true }],
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
const headers: Record<string, string> = {};
|
|
876
|
+
if (this.authToken) {
|
|
877
|
+
// Header name is 'api-key', not 'Authorization: Bearer'
|
|
878
|
+
headers['api-key'] = this.authToken;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// endpoint is the full URL (e.g., http://ny.node1.me)
|
|
882
|
+
const result = (await this.post(this.endpoint, payload, headers)) as any;
|
|
883
|
+
|
|
884
|
+
if (result.error) {
|
|
885
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return result.result;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
getTipAccount(): string {
|
|
892
|
+
return randomChoice(this.tipAccounts);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
getSwqosType(): SwqosType {
|
|
896
|
+
return SwqosType.Node1;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
minTipSol(): number {
|
|
900
|
+
return MIN_TIP_NODE1;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
export class Node1QuicClient implements SwqosClient {
|
|
905
|
+
private readonly tipAccounts = NODE1_TIP_ACCOUNTS;
|
|
906
|
+
private readonly host: string;
|
|
907
|
+
private readonly port: number;
|
|
908
|
+
|
|
909
|
+
constructor(
|
|
910
|
+
private readonly rpcUrl: string,
|
|
911
|
+
private readonly endpoint: string,
|
|
912
|
+
private readonly authToken: string,
|
|
913
|
+
) {
|
|
914
|
+
const lastColon = endpoint.lastIndexOf(':');
|
|
915
|
+
this.host = lastColon >= 0 ? endpoint.slice(0, lastColon) : endpoint;
|
|
916
|
+
this.port = lastColon >= 0 ? parseInt(endpoint.slice(lastColon + 1), 10) : 16666;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
async sendTransaction(
|
|
920
|
+
tradeType: TradeType,
|
|
921
|
+
transaction: Buffer,
|
|
922
|
+
waitConfirmation: boolean
|
|
923
|
+
): Promise<string> {
|
|
924
|
+
await sendNode1ViaQUIC(this.host, this.port, this.authToken, new Uint8Array(transaction));
|
|
925
|
+
return '';
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async sendTransactions(
|
|
929
|
+
tradeType: TradeType,
|
|
930
|
+
transactions: Buffer[],
|
|
931
|
+
waitConfirmation: boolean
|
|
932
|
+
): Promise<string[]> {
|
|
933
|
+
const signatures: string[] = [];
|
|
934
|
+
for (const tx of transactions) {
|
|
935
|
+
signatures.push(await this.sendTransaction(tradeType, tx, waitConfirmation));
|
|
936
|
+
}
|
|
937
|
+
return signatures;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
getTipAccount(): string {
|
|
941
|
+
return randomChoice(this.tipAccounts);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
getSwqosType(): SwqosType {
|
|
945
|
+
return SwqosType.Node1;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
minTipSol(): number {
|
|
949
|
+
return MIN_TIP_NODE1;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ===== BlockRazor Client =====
|
|
954
|
+
|
|
955
|
+
export class BlockRazorClient extends BaseClient {
|
|
956
|
+
private tipAccounts = BLOCK_RAZOR_TIP_ACCOUNTS;
|
|
957
|
+
|
|
958
|
+
constructor(
|
|
959
|
+
private rpcUrl: string,
|
|
960
|
+
private endpoint: string,
|
|
961
|
+
private authToken?: string,
|
|
962
|
+
private mevProtection: boolean = false
|
|
963
|
+
) {
|
|
964
|
+
super();
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async sendTransaction(
|
|
968
|
+
tradeType: TradeType,
|
|
969
|
+
transaction: Buffer,
|
|
970
|
+
waitConfirmation: boolean
|
|
971
|
+
): Promise<string> {
|
|
972
|
+
const encoded = transaction.toString('base64');
|
|
973
|
+
|
|
974
|
+
const mode = this.mevProtection ? 'sandwichMitigation' : 'fast';
|
|
975
|
+
// Auth in URL param ?auth=token&mode=...
|
|
976
|
+
let url = `${this.endpoint}?mode=${mode}`;
|
|
977
|
+
if (this.authToken) {
|
|
978
|
+
url = `${this.endpoint}?auth=${this.authToken}&mode=${mode}`;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Body is raw base64 string, Content-Type: text/plain
|
|
982
|
+
const result = (await this.postRaw(url, encoded)) as any;
|
|
983
|
+
|
|
984
|
+
if (result.error) {
|
|
985
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return result.result || result.signature || '';
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
getTipAccount(): string {
|
|
992
|
+
return randomChoice(this.tipAccounts);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
getSwqosType(): SwqosType {
|
|
996
|
+
return SwqosType.BlockRazor;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
minTipSol(): number {
|
|
1000
|
+
return MIN_TIP_BLOCK_RAZOR;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// ===== Astralane Client =====
|
|
1005
|
+
|
|
1006
|
+
export class AstralaneClient extends BaseClient {
|
|
1007
|
+
private tipAccounts = ASTRALANE_TIP_ACCOUNTS;
|
|
1008
|
+
|
|
1009
|
+
constructor(
|
|
1010
|
+
private rpcUrl: string,
|
|
1011
|
+
private endpoint: string,
|
|
1012
|
+
private authToken?: string
|
|
1013
|
+
) {
|
|
1014
|
+
super();
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async sendTransaction(
|
|
1018
|
+
tradeType: TradeType,
|
|
1019
|
+
transaction: Buffer,
|
|
1020
|
+
waitConfirmation: boolean
|
|
1021
|
+
): Promise<string> {
|
|
1022
|
+
const encoded = transaction.toString('base64');
|
|
1023
|
+
|
|
1024
|
+
const payload = {
|
|
1025
|
+
jsonrpc: '2.0',
|
|
1026
|
+
id: 1,
|
|
1027
|
+
method: 'sendTransaction',
|
|
1028
|
+
params: [encoded, { encoding: 'base64' }],
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// Auth in URL params ?api-key=...&method=sendTransaction
|
|
1032
|
+
let url = `${this.endpoint}?method=sendTransaction`;
|
|
1033
|
+
if (this.authToken) {
|
|
1034
|
+
url = `${this.endpoint}?api-key=${this.authToken}&method=sendTransaction`;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const result = (await this.post(url, payload)) as any;
|
|
1038
|
+
|
|
1039
|
+
if (result.error) {
|
|
1040
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
return result.result || result.signature || '';
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
getTipAccount(): string {
|
|
1047
|
+
return randomChoice(this.tipAccounts);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
getSwqosType(): SwqosType {
|
|
1051
|
+
return SwqosType.Astralane;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
minTipSol(): number {
|
|
1055
|
+
return MIN_TIP_ASTRALANE;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
export class AstralaneQuicClient implements SwqosClient {
|
|
1060
|
+
private readonly tipAccounts = ASTRALANE_TIP_ACCOUNTS;
|
|
1061
|
+
private readonly host: string;
|
|
1062
|
+
private readonly port: number;
|
|
1063
|
+
|
|
1064
|
+
constructor(
|
|
1065
|
+
private readonly rpcUrl: string,
|
|
1066
|
+
private readonly endpoint: string,
|
|
1067
|
+
private readonly authToken: string,
|
|
1068
|
+
) {
|
|
1069
|
+
const lastColon = endpoint.lastIndexOf(':');
|
|
1070
|
+
this.host = lastColon >= 0 ? endpoint.slice(0, lastColon) : endpoint;
|
|
1071
|
+
this.port = lastColon >= 0 ? parseInt(endpoint.slice(lastColon + 1), 10) : 7000;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
async sendTransaction(
|
|
1075
|
+
tradeType: TradeType,
|
|
1076
|
+
transaction: Buffer,
|
|
1077
|
+
waitConfirmation: boolean
|
|
1078
|
+
): Promise<string> {
|
|
1079
|
+
if (transaction.length > 1232) {
|
|
1080
|
+
throw new TradeError(400, `Astralane QUIC transaction too large: ${transaction.length} > 1232`);
|
|
1081
|
+
}
|
|
1082
|
+
await sendViaQUIC(this.host, this.port, 'astralane', new Uint8Array(transaction), {
|
|
1083
|
+
alpn: 'astralane-tpu',
|
|
1084
|
+
commonName: this.authToken,
|
|
1085
|
+
algorithm: 'ecdsa',
|
|
1086
|
+
});
|
|
1087
|
+
return '';
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
async sendTransactions(
|
|
1091
|
+
tradeType: TradeType,
|
|
1092
|
+
transactions: Buffer[],
|
|
1093
|
+
waitConfirmation: boolean
|
|
1094
|
+
): Promise<string[]> {
|
|
1095
|
+
const signatures: string[] = [];
|
|
1096
|
+
for (const tx of transactions) {
|
|
1097
|
+
signatures.push(await this.sendTransaction(tradeType, tx, waitConfirmation));
|
|
1098
|
+
}
|
|
1099
|
+
return signatures;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
getTipAccount(): string {
|
|
1103
|
+
return randomChoice(this.tipAccounts);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
getSwqosType(): SwqosType {
|
|
1107
|
+
return SwqosType.Astralane;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
minTipSol(): number {
|
|
1111
|
+
return MIN_TIP_ASTRALANE;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// ===== Stellium Client =====
|
|
1116
|
+
|
|
1117
|
+
export class StelliumClient extends BaseClient {
|
|
1118
|
+
private tipAccounts = STELLIUM_TIP_ACCOUNTS;
|
|
1119
|
+
|
|
1120
|
+
constructor(
|
|
1121
|
+
private rpcUrl: string,
|
|
1122
|
+
private endpoint: string,
|
|
1123
|
+
private authToken?: string
|
|
1124
|
+
) {
|
|
1125
|
+
super();
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async sendTransaction(
|
|
1129
|
+
tradeType: TradeType,
|
|
1130
|
+
transaction: Buffer,
|
|
1131
|
+
waitConfirmation: boolean
|
|
1132
|
+
): Promise<string> {
|
|
1133
|
+
const encoded = transaction.toString('base64');
|
|
1134
|
+
|
|
1135
|
+
const payload = {
|
|
1136
|
+
jsonrpc: '2.0',
|
|
1137
|
+
id: 1,
|
|
1138
|
+
method: 'sendTransaction',
|
|
1139
|
+
params: [encoded, { encoding: 'base64' }],
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// Token is appended directly to path: {endpoint}/{token}
|
|
1143
|
+
let url = this.endpoint;
|
|
1144
|
+
if (this.authToken) {
|
|
1145
|
+
url = `${this.endpoint}/${this.authToken}`;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const result = (await this.post(url, payload)) as any;
|
|
1149
|
+
|
|
1150
|
+
if (result.error) {
|
|
1151
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return result.result || result.signature || '';
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
getTipAccount(): string {
|
|
1158
|
+
return randomChoice(this.tipAccounts);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
getSwqosType(): SwqosType {
|
|
1162
|
+
return SwqosType.Stellium;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
minTipSol(): number {
|
|
1166
|
+
return MIN_TIP_STELLIUM;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// ===== Lightspeed Client =====
|
|
1171
|
+
|
|
1172
|
+
export class LightspeedClient extends BaseClient {
|
|
1173
|
+
constructor(
|
|
1174
|
+
private rpcUrl: string,
|
|
1175
|
+
private customUrl: string // must be provided, format already contains api_key
|
|
1176
|
+
) {
|
|
1177
|
+
super();
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
async sendTransaction(
|
|
1181
|
+
tradeType: TradeType,
|
|
1182
|
+
transaction: Buffer,
|
|
1183
|
+
waitConfirmation: boolean
|
|
1184
|
+
): Promise<string> {
|
|
1185
|
+
const encoded = transaction.toString('base64');
|
|
1186
|
+
|
|
1187
|
+
const payload = {
|
|
1188
|
+
jsonrpc: '2.0',
|
|
1189
|
+
id: 1,
|
|
1190
|
+
method: 'sendTransaction',
|
|
1191
|
+
params: [
|
|
1192
|
+
encoded,
|
|
1193
|
+
{
|
|
1194
|
+
encoding: 'base64',
|
|
1195
|
+
skipPreflight: true,
|
|
1196
|
+
preflightCommitment: 'processed',
|
|
1197
|
+
maxRetries: 0,
|
|
1198
|
+
},
|
|
1199
|
+
],
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
// customUrl already contains api_key in its format
|
|
1203
|
+
const result = (await this.post(this.customUrl, payload)) as any;
|
|
1204
|
+
|
|
1205
|
+
if (result.error) {
|
|
1206
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return result.result || result.signature || '';
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
getTipAccount(): string {
|
|
1213
|
+
// Lightspeed has 2 tip accounts
|
|
1214
|
+
const accounts = [
|
|
1215
|
+
'53PhM3UTdMQWu5t81wcd35AHGc5xpmHoRjem7GQPvXjA',
|
|
1216
|
+
'9tYF5yPDC1NP8s6diiB3kAX6ZZnva9DM3iDwJkBRarBB',
|
|
1217
|
+
];
|
|
1218
|
+
return randomChoice(accounts);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
getSwqosType(): SwqosType {
|
|
1222
|
+
return SwqosType.Lightspeed;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
minTipSol(): number {
|
|
1226
|
+
return MIN_TIP_LIGHTSPEED;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// ===== NextBlock Client =====
|
|
1231
|
+
|
|
1232
|
+
export class NextBlockClient extends BaseClient {
|
|
1233
|
+
private tipAccounts = NEXT_BLOCK_TIP_ACCOUNTS;
|
|
1234
|
+
|
|
1235
|
+
constructor(
|
|
1236
|
+
private rpcUrl: string,
|
|
1237
|
+
private endpoint: string,
|
|
1238
|
+
private authToken?: string
|
|
1239
|
+
) {
|
|
1240
|
+
super();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
async sendTransaction(
|
|
1244
|
+
tradeType: TradeType,
|
|
1245
|
+
transaction: Buffer,
|
|
1246
|
+
waitConfirmation: boolean
|
|
1247
|
+
): Promise<string> {
|
|
1248
|
+
const encoded = transaction.toString('base64');
|
|
1249
|
+
|
|
1250
|
+
const payload = {
|
|
1251
|
+
transaction: { content: encoded },
|
|
1252
|
+
frontRunningProtection: false,
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
const headers: Record<string, string> = {};
|
|
1256
|
+
if (this.authToken) {
|
|
1257
|
+
headers['Authorization'] = this.authToken;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const url = `${this.endpoint}/api/v2/submit`;
|
|
1261
|
+
const result = (await this.post(url, payload, headers)) as any;
|
|
1262
|
+
|
|
1263
|
+
if (result.error) {
|
|
1264
|
+
throw new TradeError(result.error.code || 500, result.error.message || String(result.error));
|
|
1265
|
+
}
|
|
1266
|
+
if (result.reason) {
|
|
1267
|
+
throw new TradeError(500, result.reason);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return result.signature || result.result || '';
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
getTipAccount(): string {
|
|
1274
|
+
return randomChoice(this.tipAccounts);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
getSwqosType(): SwqosType {
|
|
1278
|
+
return SwqosType.NextBlock;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
minTipSol(): number {
|
|
1282
|
+
return MIN_TIP_NEXT_BLOCK;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ===== Default RPC Client =====
|
|
1287
|
+
|
|
1288
|
+
export class DefaultClient extends BaseClient {
|
|
1289
|
+
constructor(private rpcUrl: string) {
|
|
1290
|
+
super();
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
async sendTransaction(
|
|
1294
|
+
tradeType: TradeType,
|
|
1295
|
+
transaction: Buffer,
|
|
1296
|
+
waitConfirmation: boolean
|
|
1297
|
+
): Promise<string> {
|
|
1298
|
+
const encoded = transaction.toString('base64');
|
|
1299
|
+
|
|
1300
|
+
const payload = {
|
|
1301
|
+
jsonrpc: '2.0',
|
|
1302
|
+
id: 1,
|
|
1303
|
+
method: 'sendTransaction',
|
|
1304
|
+
params: [
|
|
1305
|
+
encoded,
|
|
1306
|
+
{ encoding: 'base64' },
|
|
1307
|
+
],
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const result = (await this.post(this.rpcUrl, payload)) as any;
|
|
1311
|
+
|
|
1312
|
+
if (result.error) {
|
|
1313
|
+
throw new TradeError(result.error.code || 500, result.error.message);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
return result.result;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
getTipAccount(): string {
|
|
1320
|
+
return '';
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
getSwqosType(): SwqosType {
|
|
1324
|
+
return SwqosType.Default;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
minTipSol(): number {
|
|
1328
|
+
return MIN_TIP_DEFAULT;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// ===== QUIC helper (Soyas / Speedlanding) =====
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Send raw transaction bytes via QUIC to a Solana TPU endpoint.
|
|
1336
|
+
*
|
|
1337
|
+
* Uses @matrixai/quic (ESM-only) via dynamic import so this file stays CJS-
|
|
1338
|
+
* compatible. A self-signed Ed25519 certificate is generated via `selfsigned`
|
|
1339
|
+
* with ALPN "solana-tpu", matching the pattern of go-solana-tpu and the Rust
|
|
1340
|
+
* solana-tls-utils crate.
|
|
1341
|
+
*
|
|
1342
|
+
* Install optional deps: npm install @matrixai/quic selfsigned
|
|
1343
|
+
*/
|
|
1344
|
+
async function sendViaQUIC(
|
|
1345
|
+
host: string,
|
|
1346
|
+
port: number,
|
|
1347
|
+
serverName: string,
|
|
1348
|
+
txBytes: Uint8Array,
|
|
1349
|
+
options: {
|
|
1350
|
+
alpn?: string;
|
|
1351
|
+
commonName?: string;
|
|
1352
|
+
algorithm?: string;
|
|
1353
|
+
} = {},
|
|
1354
|
+
): Promise<void> {
|
|
1355
|
+
// Dynamic imports so the file compiles/runs even without the optional packages
|
|
1356
|
+
let QUICClient: any;
|
|
1357
|
+
let selfsigned: any;
|
|
1358
|
+
try {
|
|
1359
|
+
({ QUICClient } = await import('@matrixai/quic'));
|
|
1360
|
+
selfsigned = (await import('selfsigned')).default ?? (await import('selfsigned'));
|
|
1361
|
+
} catch {
|
|
1362
|
+
throw new TradeError(
|
|
1363
|
+
501,
|
|
1364
|
+
'QUIC not available: run "npm install @matrixai/quic selfsigned" to enable Soyas/Speedlanding.',
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Generate ephemeral self-signed Ed25519 certificate (no server verification)
|
|
1369
|
+
const pems = selfsigned.generate(
|
|
1370
|
+
[{ name: 'commonName', value: options.commonName ?? 'Solana node' }],
|
|
1371
|
+
{ days: 36500, algorithm: options.algorithm ?? 'ed25519' },
|
|
1372
|
+
);
|
|
1373
|
+
|
|
1374
|
+
const client = await QUICClient.createQUICClient({
|
|
1375
|
+
host,
|
|
1376
|
+
port,
|
|
1377
|
+
config: {
|
|
1378
|
+
key: pems.private,
|
|
1379
|
+
cert: pems.cert,
|
|
1380
|
+
verifyPeer: false,
|
|
1381
|
+
applicationProtos: [options.alpn ?? 'solana-tpu'],
|
|
1382
|
+
tlsVersion: 'tlsv13',
|
|
1383
|
+
},
|
|
1384
|
+
logger: undefined,
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
try {
|
|
1388
|
+
const stream = client.connection.newStream('uni');
|
|
1389
|
+
const writer = stream.writable.getWriter();
|
|
1390
|
+
await writer.write(txBytes);
|
|
1391
|
+
await writer.close();
|
|
1392
|
+
// Brief delay to allow the stack to flush before closing
|
|
1393
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1394
|
+
} finally {
|
|
1395
|
+
await client.destroy();
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
function hostPortFromHttp(endpoint: string, port: number): { host: string; port: number } {
|
|
1400
|
+
try {
|
|
1401
|
+
const url = new URL(endpoint);
|
|
1402
|
+
return { host: url.hostname, port };
|
|
1403
|
+
} catch {
|
|
1404
|
+
const withoutScheme = endpoint.replace(/^https?:\/\//, '').split('/')[0]!;
|
|
1405
|
+
const lastColon = withoutScheme.lastIndexOf(':');
|
|
1406
|
+
const host = lastColon >= 0 ? withoutScheme.slice(0, lastColon) : withoutScheme;
|
|
1407
|
+
return { host, port };
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function uuidToBytes(apiKey: string): Uint8Array {
|
|
1412
|
+
const hex = apiKey.replace(/-/g, '');
|
|
1413
|
+
if (!/^[0-9a-fA-F]{32}$/.test(hex)) {
|
|
1414
|
+
throw new TradeError(400, 'Node1 QUIC API key must be a UUID');
|
|
1415
|
+
}
|
|
1416
|
+
return new Uint8Array(Buffer.from(hex, 'hex'));
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
async function readQuicStream(stream: any): Promise<Uint8Array> {
|
|
1420
|
+
const reader = stream.readable.getReader();
|
|
1421
|
+
const chunks: Uint8Array[] = [];
|
|
1422
|
+
let total = 0;
|
|
1423
|
+
try {
|
|
1424
|
+
for (;;) {
|
|
1425
|
+
const { value, done } = await reader.read();
|
|
1426
|
+
if (done) break;
|
|
1427
|
+
if (value) {
|
|
1428
|
+
const chunk = new Uint8Array(value);
|
|
1429
|
+
chunks.push(chunk);
|
|
1430
|
+
total += chunk.length;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
} finally {
|
|
1434
|
+
reader.releaseLock?.();
|
|
1435
|
+
}
|
|
1436
|
+
const out = new Uint8Array(total);
|
|
1437
|
+
let offset = 0;
|
|
1438
|
+
for (const chunk of chunks) {
|
|
1439
|
+
out.set(chunk, offset);
|
|
1440
|
+
offset += chunk.length;
|
|
1441
|
+
}
|
|
1442
|
+
return out;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
async function sendNode1ViaQUIC(
|
|
1446
|
+
host: string,
|
|
1447
|
+
port: number,
|
|
1448
|
+
apiKey: string,
|
|
1449
|
+
txBytes: Uint8Array,
|
|
1450
|
+
): Promise<void> {
|
|
1451
|
+
let QUICClient: any;
|
|
1452
|
+
try {
|
|
1453
|
+
({ QUICClient } = await import('@matrixai/quic'));
|
|
1454
|
+
} catch {
|
|
1455
|
+
throw new TradeError(501, 'QUIC not available: run "npm install @matrixai/quic" to enable Node1 QUIC.');
|
|
1456
|
+
}
|
|
1457
|
+
if (txBytes.length > 1232) {
|
|
1458
|
+
throw new TradeError(400, `Node1 QUIC transaction too large: ${txBytes.length} > 1232`);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
const client = await QUICClient.createQUICClient({
|
|
1462
|
+
host,
|
|
1463
|
+
port,
|
|
1464
|
+
config: {
|
|
1465
|
+
verifyPeer: false,
|
|
1466
|
+
applicationProtos: ['h3'],
|
|
1467
|
+
tlsVersion: 'tlsv13',
|
|
1468
|
+
serverName: host,
|
|
1469
|
+
},
|
|
1470
|
+
logger: undefined,
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
try {
|
|
1474
|
+
const authStream = client.connection.newStream('bi');
|
|
1475
|
+
const authWriter = authStream.writable.getWriter();
|
|
1476
|
+
await authWriter.write(uuidToBytes(apiKey));
|
|
1477
|
+
await authWriter.close();
|
|
1478
|
+
const authReply = await readQuicStream(authStream);
|
|
1479
|
+
if (authReply[0] !== 0) {
|
|
1480
|
+
throw new TradeError(401, `Node1 QUIC auth rejected: ${authReply[0] ?? -1}`);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
const txStream = client.connection.newStream('bi');
|
|
1484
|
+
const txWriter = txStream.writable.getWriter();
|
|
1485
|
+
await txWriter.write(txBytes);
|
|
1486
|
+
await txWriter.close();
|
|
1487
|
+
const response = await readQuicStream(txStream);
|
|
1488
|
+
if (response.length < 6) {
|
|
1489
|
+
throw new TradeError(500, 'Node1 QUIC response too short');
|
|
1490
|
+
}
|
|
1491
|
+
const status = (response[0]! << 8) | response[1]!;
|
|
1492
|
+
const msgLen = (response[2]! << 24) | (response[3]! << 16) | (response[4]! << 8) | response[5]!;
|
|
1493
|
+
const msg = Buffer.from(response.slice(6, 6 + msgLen)).toString('utf8');
|
|
1494
|
+
if (status !== 200) {
|
|
1495
|
+
throw new TradeError(status, `Node1 QUIC submit failed: ${msg}`);
|
|
1496
|
+
}
|
|
1497
|
+
} finally {
|
|
1498
|
+
await client.destroy();
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// ===== Soyas Client =====
|
|
1503
|
+
|
|
1504
|
+
/**
|
|
1505
|
+
* Soyas SWQOS client.
|
|
1506
|
+
*
|
|
1507
|
+
* Transport: QUIC with self-signed Ed25519 cert, ALPN "solana-tpu".
|
|
1508
|
+
* Endpoint: host:port (e.g. nyc.landing.soyas.xyz:9000)
|
|
1509
|
+
* SNI: "soyas-landing" (matches Rust SDK SOYAS_SERVER constant)
|
|
1510
|
+
* Requires: npm install @matrixai/quic selfsigned
|
|
1511
|
+
*/
|
|
1512
|
+
export class SoyasClient implements SwqosClient {
|
|
1513
|
+
private readonly tipAccount: string;
|
|
1514
|
+
private readonly host: string;
|
|
1515
|
+
private readonly port: number;
|
|
1516
|
+
|
|
1517
|
+
constructor(
|
|
1518
|
+
private readonly rpcUrl: string,
|
|
1519
|
+
private readonly endpoint: string,
|
|
1520
|
+
private readonly apiKey?: string,
|
|
1521
|
+
) {
|
|
1522
|
+
this.tipAccount = randomChoice(SOYAS_TIP_ACCOUNTS);
|
|
1523
|
+
const parts = endpoint.split(':');
|
|
1524
|
+
this.host = parts.slice(0, -1).join(':') || endpoint;
|
|
1525
|
+
this.port = parts.length > 1 ? parseInt(parts[parts.length - 1]!, 10) : 9000;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
async sendTransaction(
|
|
1529
|
+
_tradeType: TradeType,
|
|
1530
|
+
transaction: Buffer,
|
|
1531
|
+
_waitConfirmation: boolean,
|
|
1532
|
+
): Promise<string> {
|
|
1533
|
+
await sendViaQUIC(this.host, this.port, 'soyas-landing', new Uint8Array(transaction));
|
|
1534
|
+
return '';
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
async sendTransactions(
|
|
1538
|
+
tradeType: TradeType,
|
|
1539
|
+
transactions: Buffer[],
|
|
1540
|
+
waitConfirmation: boolean,
|
|
1541
|
+
): Promise<string[]> {
|
|
1542
|
+
for (const tx of transactions) {
|
|
1543
|
+
await this.sendTransaction(tradeType, tx, waitConfirmation);
|
|
1544
|
+
}
|
|
1545
|
+
return transactions.map(() => '');
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
getTipAccount(): string { return this.tipAccount; }
|
|
1549
|
+
getSwqosType(): SwqosType { return SwqosType.Soyas; }
|
|
1550
|
+
minTipSol(): number { return MIN_TIP_SOYAS; }
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// ===== Speedlanding Client =====
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Speedlanding SWQOS client.
|
|
1557
|
+
*
|
|
1558
|
+
* Transport: QUIC with self-signed Ed25519 cert, ALPN "solana-tpu".
|
|
1559
|
+
* Endpoint: host:port (e.g. nyc.speedlanding.trade:17778)
|
|
1560
|
+
* SNI: derived from hostname; falls back to "speed-landing" for bare IPs
|
|
1561
|
+
* (matches Rust SDK behavior).
|
|
1562
|
+
* Requires: npm install @matrixai/quic selfsigned
|
|
1563
|
+
*/
|
|
1564
|
+
export class SpeedlandingClient implements SwqosClient {
|
|
1565
|
+
private readonly tipAccount: string;
|
|
1566
|
+
private readonly host: string;
|
|
1567
|
+
private readonly port: number;
|
|
1568
|
+
private readonly serverName: string;
|
|
1569
|
+
|
|
1570
|
+
constructor(
|
|
1571
|
+
private readonly rpcUrl: string,
|
|
1572
|
+
private readonly endpoint: string,
|
|
1573
|
+
private readonly apiKey?: string,
|
|
1574
|
+
) {
|
|
1575
|
+
this.tipAccount = randomChoice(SPEEDLANDING_TIP_ACCOUNTS);
|
|
1576
|
+
const lastColon = endpoint.lastIndexOf(':');
|
|
1577
|
+
this.host = lastColon >= 0 ? endpoint.slice(0, lastColon) : endpoint;
|
|
1578
|
+
this.port = lastColon >= 0 ? parseInt(endpoint.slice(lastColon + 1), 10) : 17778;
|
|
1579
|
+
// Use hostname as SNI; fall back to "speed-landing" for bare IP addresses
|
|
1580
|
+
this.serverName = /^\d+\.\d+\.\d+\.\d+$/.test(this.host) ? 'speed-landing' : this.host;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
async sendTransaction(
|
|
1584
|
+
_tradeType: TradeType,
|
|
1585
|
+
transaction: Buffer,
|
|
1586
|
+
_waitConfirmation: boolean,
|
|
1587
|
+
): Promise<string> {
|
|
1588
|
+
await sendViaQUIC(this.host, this.port, this.serverName, new Uint8Array(transaction));
|
|
1589
|
+
return '';
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
async sendTransactions(
|
|
1593
|
+
tradeType: TradeType,
|
|
1594
|
+
transactions: Buffer[],
|
|
1595
|
+
waitConfirmation: boolean,
|
|
1596
|
+
): Promise<string[]> {
|
|
1597
|
+
for (const tx of transactions) {
|
|
1598
|
+
await this.sendTransaction(tradeType, tx, waitConfirmation);
|
|
1599
|
+
}
|
|
1600
|
+
return transactions.map(() => '');
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
getTipAccount(): string { return this.tipAccount; }
|
|
1604
|
+
getSwqosType(): SwqosType { return SwqosType.Speedlanding; }
|
|
1605
|
+
minTipSol(): number { return MIN_TIP_SPEEDLANDING; }
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// ===== Client Factory =====
|
|
1609
|
+
|
|
1610
|
+
export interface SwqosClientConfig {
|
|
1611
|
+
type: SwqosType;
|
|
1612
|
+
region?: SwqosRegion;
|
|
1613
|
+
customUrl?: string;
|
|
1614
|
+
apiKey?: string;
|
|
1615
|
+
mevProtection?: boolean;
|
|
1616
|
+
transport?: SwqosTransport;
|
|
1617
|
+
astralaneTransport?: AstralaneTransport;
|
|
1618
|
+
swqosOnly?: boolean;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
export class ClientFactory {
|
|
1622
|
+
static createClient(config: SwqosClientConfig, rpcUrl: string): SwqosClient {
|
|
1623
|
+
const region = config.region ?? SwqosRegion.Default;
|
|
1624
|
+
|
|
1625
|
+
switch (config.type) {
|
|
1626
|
+
case SwqosType.Jito: {
|
|
1627
|
+
const endpoint = config.customUrl || JITO_ENDPOINTS[region];
|
|
1628
|
+
return new JitoClient(rpcUrl, endpoint, config.apiKey);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
case SwqosType.Bloxroute: {
|
|
1632
|
+
const endpoint = config.customUrl || BLOXROUTE_ENDPOINTS[region];
|
|
1633
|
+
return new BloxrouteClient(rpcUrl, endpoint, config.apiKey);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
case SwqosType.ZeroSlot: {
|
|
1637
|
+
const endpoint = config.customUrl || ZERO_SLOT_ENDPOINTS[region];
|
|
1638
|
+
return new ZeroSlotClient(rpcUrl, endpoint, config.apiKey);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
case SwqosType.Temporal: {
|
|
1642
|
+
const endpoint = config.customUrl || TEMPORAL_ENDPOINTS[region];
|
|
1643
|
+
return new TemporalClient(rpcUrl, endpoint, config.apiKey);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
case SwqosType.FlashBlock: {
|
|
1647
|
+
const endpoint = config.customUrl || FLASH_BLOCK_ENDPOINTS[region];
|
|
1648
|
+
return new FlashBlockClient(rpcUrl, endpoint, config.apiKey);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
case SwqosType.Helius: {
|
|
1652
|
+
const endpoint = config.customUrl || HELIUS_ENDPOINTS[region];
|
|
1653
|
+
return new HeliusClient(rpcUrl, endpoint, config.apiKey, config.swqosOnly ?? false);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
case SwqosType.Node1: {
|
|
1657
|
+
const endpoint = config.customUrl || NODE1_ENDPOINTS[region];
|
|
1658
|
+
if (config.transport === SwqosTransport.Quic) {
|
|
1659
|
+
const parsed = /^https?:\/\//.test(endpoint)
|
|
1660
|
+
? hostPortFromHttp(endpoint, 16666)
|
|
1661
|
+
: (() => {
|
|
1662
|
+
const lastColon = endpoint.lastIndexOf(':');
|
|
1663
|
+
return {
|
|
1664
|
+
host: lastColon >= 0 ? endpoint.slice(0, lastColon) : endpoint,
|
|
1665
|
+
port: lastColon >= 0 ? parseInt(endpoint.slice(lastColon + 1), 10) : 16666,
|
|
1666
|
+
};
|
|
1667
|
+
})();
|
|
1668
|
+
return new Node1QuicClient(rpcUrl, `${parsed.host}:${parsed.port}`, config.apiKey || '');
|
|
1669
|
+
}
|
|
1670
|
+
return new Node1Client(rpcUrl, endpoint, config.apiKey);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
case SwqosType.BlockRazor: {
|
|
1674
|
+
const endpoint = config.customUrl || BLOCK_RAZOR_ENDPOINTS[region];
|
|
1675
|
+
return new BlockRazorClient(rpcUrl, endpoint, config.apiKey, config.mevProtection ?? false);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
case SwqosType.Astralane: {
|
|
1679
|
+
const baseEndpoint = config.customUrl || ASTRALANE_ENDPOINTS[region];
|
|
1680
|
+
if (config.astralaneTransport === AstralaneTransport.Quic) {
|
|
1681
|
+
let endpoint: string;
|
|
1682
|
+
const port = config.mevProtection ? 9000 : 7000;
|
|
1683
|
+
if (config.customUrl) {
|
|
1684
|
+
if (/^https?:\/\//.test(config.customUrl)) {
|
|
1685
|
+
const parsed = hostPortFromHttp(config.customUrl, port);
|
|
1686
|
+
endpoint = `${parsed.host}:${parsed.port}`;
|
|
1687
|
+
} else {
|
|
1688
|
+
endpoint = config.customUrl;
|
|
1689
|
+
}
|
|
1690
|
+
} else {
|
|
1691
|
+
endpoint = `${ASTRALANE_QUIC_HOSTS[region]}:${port}`;
|
|
1692
|
+
}
|
|
1693
|
+
return new AstralaneQuicClient(rpcUrl, endpoint, config.apiKey || '');
|
|
1694
|
+
}
|
|
1695
|
+
const endpoint =
|
|
1696
|
+
config.astralaneTransport === AstralaneTransport.Plain
|
|
1697
|
+
? baseEndpoint.replace('/irisb', '/iris')
|
|
1698
|
+
: baseEndpoint;
|
|
1699
|
+
return new AstralaneClient(rpcUrl, endpoint, config.apiKey);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
case SwqosType.Stellium: {
|
|
1703
|
+
const endpoint = config.customUrl || STELLIUM_ENDPOINTS[region];
|
|
1704
|
+
return new StelliumClient(rpcUrl, endpoint, config.apiKey);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
case SwqosType.Lightspeed: {
|
|
1708
|
+
if (!config.customUrl) {
|
|
1709
|
+
throw new TradeError(400, 'LightspeedClient requires customUrl (format already contains api_key)');
|
|
1710
|
+
}
|
|
1711
|
+
return new LightspeedClient(rpcUrl, config.customUrl);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
case SwqosType.NextBlock: {
|
|
1715
|
+
const endpoint = config.customUrl || NEXT_BLOCK_ENDPOINTS[region];
|
|
1716
|
+
return new NextBlockClient(rpcUrl, endpoint, config.apiKey);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
case SwqosType.Soyas: {
|
|
1720
|
+
const endpoint = config.customUrl || SOYAS_ENDPOINTS[region];
|
|
1721
|
+
return new SoyasClient(rpcUrl, endpoint, config.apiKey);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
case SwqosType.Speedlanding: {
|
|
1725
|
+
const endpoint = config.customUrl || SPEEDLANDING_ENDPOINTS[region];
|
|
1726
|
+
return new SpeedlandingClient(rpcUrl, endpoint, config.apiKey);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
case SwqosType.Default:
|
|
1730
|
+
default:
|
|
1731
|
+
return new DefaultClient(rpcUrl);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// ===== Convenience Function =====
|
|
1737
|
+
|
|
1738
|
+
export function createSwqosClient(
|
|
1739
|
+
swqosType: SwqosType,
|
|
1740
|
+
rpcUrl: string,
|
|
1741
|
+
authToken?: string,
|
|
1742
|
+
region?: SwqosRegion,
|
|
1743
|
+
customUrl?: string,
|
|
1744
|
+
mevProtection: boolean = false
|
|
1745
|
+
): SwqosClient {
|
|
1746
|
+
const config: SwqosClientConfig = {
|
|
1747
|
+
type: swqosType,
|
|
1748
|
+
region,
|
|
1749
|
+
customUrl,
|
|
1750
|
+
apiKey: authToken,
|
|
1751
|
+
mevProtection,
|
|
1752
|
+
};
|
|
1753
|
+
return ClientFactory.createClient(config, rpcUrl);
|
|
1754
|
+
}
|