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.
Files changed (87) hide show
  1. package/README.md +390 -0
  2. package/dist/chunk-MMQAMIKR.mjs +3735 -0
  3. package/dist/chunk-NEZDFAYA.mjs +7744 -0
  4. package/dist/clients-VITWK7B6.mjs +1370 -0
  5. package/dist/index-1BK_FXsW.d.mts +2327 -0
  6. package/dist/index-1BK_FXsW.d.ts +2327 -0
  7. package/dist/index.d.mts +2659 -0
  8. package/dist/index.d.ts +2659 -0
  9. package/dist/index.js +13265 -0
  10. package/dist/index.mjs +562 -0
  11. package/dist/perf/index.d.mts +2 -0
  12. package/dist/perf/index.d.ts +2 -0
  13. package/dist/perf/index.js +3742 -0
  14. package/dist/perf/index.mjs +214 -0
  15. package/package.json +101 -0
  16. package/src/__tests__/complete_sdk.test.ts +354 -0
  17. package/src/__tests__/hotpath.test.ts +486 -0
  18. package/src/__tests__/nonce.test.ts +45 -0
  19. package/src/__tests__/sdk.test.ts +425 -0
  20. package/src/address-lookup/index.ts +197 -0
  21. package/src/cache/cache.ts +308 -0
  22. package/src/calc/index.ts +1058 -0
  23. package/src/calc/pumpfun.ts +124 -0
  24. package/src/common/bonding_curve.ts +272 -0
  25. package/src/common/compute-budget.ts +148 -0
  26. package/src/common/confirm-any-signature.ts +184 -0
  27. package/src/common/fast-timing.ts +481 -0
  28. package/src/common/fast_fn.ts +150 -0
  29. package/src/common/gas-fee-strategy.ts +253 -0
  30. package/src/common/map-pool.ts +23 -0
  31. package/src/common/nonce.ts +40 -0
  32. package/src/common/sdk-log.ts +460 -0
  33. package/src/common/seed.ts +381 -0
  34. package/src/common/spl-token.ts +578 -0
  35. package/src/common/subscription-handle.ts +644 -0
  36. package/src/common/trading-utils.ts +239 -0
  37. package/src/common/wsol-manager.ts +325 -0
  38. package/src/compute/compute_budget_manager.ts +187 -0
  39. package/src/compute/index.ts +21 -0
  40. package/src/constants/index.ts +96 -0
  41. package/src/execution/execution.ts +532 -0
  42. package/src/execution/index.ts +42 -0
  43. package/src/hotpath/executor.ts +464 -0
  44. package/src/hotpath/index.ts +64 -0
  45. package/src/hotpath/state.ts +435 -0
  46. package/src/index.ts +2117 -0
  47. package/src/instruction/bonk_builder.ts +730 -0
  48. package/src/instruction/index.ts +24 -0
  49. package/src/instruction/meteora_damm_v2_builder.ts +509 -0
  50. package/src/instruction/pumpfun_builder.ts +1183 -0
  51. package/src/instruction/pumpswap.ts +1123 -0
  52. package/src/instruction/raydium_amm_v4_builder.ts +692 -0
  53. package/src/instruction/raydium_cpmm_builder.ts +795 -0
  54. package/src/middleware/traits.ts +407 -0
  55. package/src/params/index.ts +483 -0
  56. package/src/perf/compiler-optimization.ts +529 -0
  57. package/src/perf/hardware.ts +631 -0
  58. package/src/perf/index.ts +9 -0
  59. package/src/perf/kernel-bypass.ts +656 -0
  60. package/src/perf/protocol.ts +682 -0
  61. package/src/perf/realtime.ts +592 -0
  62. package/src/perf/simd.ts +668 -0
  63. package/src/perf/syscall-bypass.ts +331 -0
  64. package/src/perf/ultra-low-latency.ts +505 -0
  65. package/src/perf/zero-copy.ts +589 -0
  66. package/src/pool/pool.ts +294 -0
  67. package/src/rpc/client.ts +345 -0
  68. package/src/sdk-errors.ts +13 -0
  69. package/src/security/index.ts +26 -0
  70. package/src/security/secure-key.ts +303 -0
  71. package/src/security/validators.ts +281 -0
  72. package/src/seed/pda.ts +262 -0
  73. package/src/serialization/index.ts +28 -0
  74. package/src/serialization/serialization.ts +288 -0
  75. package/src/swqos/clients.ts +1754 -0
  76. package/src/swqos/index.ts +50 -0
  77. package/src/swqos/providers.ts +1707 -0
  78. package/src/trading/core/async-executor.ts +702 -0
  79. package/src/trading/core/confirmation-monitor.ts +711 -0
  80. package/src/trading/core/index.ts +82 -0
  81. package/src/trading/core/retry-handler.ts +683 -0
  82. package/src/trading/core/transaction-pool.ts +780 -0
  83. package/src/trading/executor.ts +385 -0
  84. package/src/trading/factory.ts +282 -0
  85. package/src/trading/index.ts +30 -0
  86. package/src/types.ts +8 -0
  87. 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
+ }