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
package/src/index.ts ADDED
@@ -0,0 +1,2117 @@
1
+ /**
2
+ * Sol Trade SDK - TypeScript SDK for Solana DEX trading
3
+ *
4
+ * A comprehensive SDK for seamless Solana DEX trading with support for
5
+ * PumpFun, PumpSwap, Bonk, Raydium CPMM, Raydium AMM V4, and Meteora DAMM V2.
6
+ */
7
+
8
+ import {
9
+ Connection,
10
+ Keypair,
11
+ PublicKey,
12
+ Transaction,
13
+ TransactionInstruction,
14
+ TransactionMessage,
15
+ VersionedTransaction,
16
+ AddressLookupTableAccount,
17
+ Commitment,
18
+ BlockhashWithExpiryBlockHeight,
19
+ SystemProgram,
20
+ type SimulateTransactionConfig,
21
+ } from '@solana/web3.js';
22
+ import { cpus } from 'node:os';
23
+
24
+ // Import GasFeeStrategy class for createGasFeeStrategy / TradeConfig.gasStrategy
25
+ import {
26
+ GasFeeStrategy as GasFeeStrategyClass,
27
+ GasFeeStrategyType,
28
+ TradeType as GfsTradeType,
29
+ SwqosType as GfsSwqosType,
30
+ type GasFeeStrategyValue,
31
+ } from './common/gas-fee-strategy';
32
+ import { CONSTANTS as SDK_CONSTANTS } from './constants';
33
+ import {
34
+ buildPumpFunBuyInstructions,
35
+ buildPumpFunSellInstructions,
36
+ buildPumpFunClaimCashbackInstruction,
37
+ type PumpFunParams as PumpFunBuilderParams,
38
+ } from './instruction/pumpfun_builder';
39
+ import {
40
+ buildBuyInstructions as buildPumpSwapBuyInstructions,
41
+ buildSellInstructions as buildPumpSwapSellInstructions,
42
+ buildClaimCashbackInstruction as buildPumpSwapClaimCashbackInstruction,
43
+ createAssociatedTokenAccountIdempotent as createAtaIdempotentPumpSwap,
44
+ findPoolByMint as findPumpSwapPoolByMint,
45
+ type PumpSwapParams as PumpSwapBuilderParams,
46
+ } from './instruction/pumpswap';
47
+ import {
48
+ buildBonkBuyInstructions,
49
+ buildBonkSellInstructions,
50
+ type BonkParams as BonkBuilderParams,
51
+ } from './instruction/bonk_builder';
52
+ import {
53
+ buildRaydiumCpmmBuyInstructions,
54
+ buildRaydiumCpmmSellInstructions,
55
+ type RaydiumCpmmParams as RaydiumCpmmBuilderParams,
56
+ } from './instruction/raydium_cpmm_builder';
57
+ import {
58
+ buildRaydiumAmmV4BuyInstructions,
59
+ buildRaydiumAmmV4SellInstructions,
60
+ type RaydiumAmmV4Params as RaydiumAmmV4BuilderParams,
61
+ } from './instruction/raydium_amm_v4_builder';
62
+ import {
63
+ buildMeteoraDammV2BuyInstructions,
64
+ buildMeteoraDammV2SellInstructions,
65
+ } from './instruction/meteora_damm_v2_builder';
66
+ import { handleWsol, closeWsol as wsolCloseIx } from './common/wsol-manager';
67
+ import { computeBudgetInstructions } from './common/compute-budget';
68
+ import { confirmAnyTransactionSignature } from './common/confirm-any-signature';
69
+ import { mapWithConcurrencyLimit } from './common/map-pool';
70
+ import { InstructionProcessor, Prefetch } from './execution/execution';
71
+
72
+ // ============== Enums ==============
73
+
74
+ /**
75
+ * Supported DEX protocols
76
+ */
77
+ export enum DexType {
78
+ PumpFun = 'PumpFun',
79
+ PumpSwap = 'PumpSwap',
80
+ Bonk = 'Bonk',
81
+ RaydiumCpmm = 'RaydiumCpmm',
82
+ RaydiumAmmV4 = 'RaydiumAmmV4',
83
+ MeteoraDammV2 = 'MeteoraDammV2',
84
+ }
85
+
86
+ /**
87
+ * Type of token to trade
88
+ */
89
+ export enum TradeTokenType {
90
+ SOL = 'SOL',
91
+ WSOL = 'WSOL',
92
+ USD1 = 'USD1',
93
+ USDC = 'USDC',
94
+ }
95
+
96
+ /**
97
+ * Trade operation type
98
+ */
99
+ export enum TradeType {
100
+ Buy = 'Buy',
101
+ Sell = 'Sell',
102
+ }
103
+
104
+ /**
105
+ * SWQOS service regions
106
+ */
107
+ export enum SwqosRegion {
108
+ Frankfurt = 'Frankfurt',
109
+ NewYork = 'NewYork',
110
+ Amsterdam = 'Amsterdam',
111
+ Dublin = 'Dublin',
112
+ Tokyo = 'Tokyo',
113
+ Singapore = 'Singapore',
114
+ SLC = 'SLC',
115
+ London = 'London',
116
+ LosAngeles = 'LosAngeles',
117
+ Default = 'Default',
118
+ }
119
+
120
+ /**
121
+ * SWQOS service types
122
+ */
123
+ export enum SwqosType {
124
+ Default = 'Default',
125
+ Jito = 'Jito',
126
+ Bloxroute = 'Bloxroute',
127
+ ZeroSlot = 'ZeroSlot',
128
+ Temporal = 'Temporal',
129
+ FlashBlock = 'FlashBlock',
130
+ BlockRazor = 'BlockRazor',
131
+ Node1 = 'Node1',
132
+ Astralane = 'Astralane',
133
+ NextBlock = 'NextBlock',
134
+ Helius = 'Helius',
135
+ Stellium = 'Stellium',
136
+ Lightspeed = 'Lightspeed',
137
+ Soyas = 'Soyas',
138
+ Speedlanding = 'Speedlanding',
139
+ Triton = 'Triton',
140
+ QuickNode = 'QuickNode',
141
+ Syndica = 'Syndica',
142
+ Figment = 'Figment',
143
+ Alchemy = 'Alchemy',
144
+ }
145
+
146
+ export enum SwqosTransport {
147
+ Http = 'Http',
148
+ Grpc = 'Grpc',
149
+ Quic = 'Quic',
150
+ }
151
+
152
+ export enum AstralaneTransport {
153
+ Binary = 'Binary',
154
+ Plain = 'Plain',
155
+ Quic = 'Quic',
156
+ }
157
+
158
+ // ============== Interfaces ==============
159
+
160
+ /**
161
+ * SWQOS service configuration
162
+ */
163
+ export interface SwqosConfig {
164
+ type: SwqosType;
165
+ region: SwqosRegion;
166
+ apiKey: string;
167
+ customUrl?: string;
168
+ mevProtection?: boolean;
169
+ transport?: SwqosTransport;
170
+ astralaneTransport?: AstralaneTransport;
171
+ swqosOnly?: boolean;
172
+ }
173
+
174
+ /**
175
+ * Gas fee strategy configuration
176
+ */
177
+ export interface GasFeeStrategyConfig {
178
+ buyPriorityFee: number;
179
+ sellPriorityFee: number;
180
+ buyComputeUnits: number;
181
+ sellComputeUnits: number;
182
+ buyTipLamports: number;
183
+ sellTipLamports: number;
184
+ }
185
+
186
+ /**
187
+ * Durable nonce information
188
+ *
189
+ * Populate via `fetchDurableNonceInfo` (Rust `fetch_nonce_info` parity) or manually.
190
+ */
191
+ export interface DurableNonceInfo {
192
+ nonceAccount: PublicKey;
193
+ authority: PublicKey;
194
+ nonceHash: string;
195
+ recentBlockhash: string;
196
+ }
197
+
198
+ /**
199
+ * Buy trade parameters
200
+ */
201
+ export interface TradeBuyParams {
202
+ dexType: DexType;
203
+ inputTokenType: TradeTokenType;
204
+ mint: PublicKey;
205
+ inputTokenAmount: number;
206
+ slippageBasisPoints?: number;
207
+ recentBlockhash?: string;
208
+ extensionParams: DexParamEnum;
209
+ addressLookupTableAccount?: AddressLookupTableAccount;
210
+ waitTxConfirmed?: boolean;
211
+ createInputTokenAta?: boolean;
212
+ closeInputTokenAta?: boolean;
213
+ createMintAta?: boolean;
214
+ durableNonce?: DurableNonceInfo;
215
+ fixedOutputTokenAmount?: number;
216
+ gasFeeStrategy?: GasFeeStrategyConfig;
217
+ simulate?: boolean;
218
+ useExactSolAmount?: boolean;
219
+ grpcRecvUs?: number;
220
+ }
221
+
222
+ /**
223
+ * Sell trade parameters
224
+ */
225
+ export interface TradeSellParams {
226
+ dexType: DexType;
227
+ outputTokenType: TradeTokenType;
228
+ mint: PublicKey;
229
+ inputTokenAmount: number;
230
+ slippageBasisPoints?: number;
231
+ recentBlockhash?: string;
232
+ withTip?: boolean;
233
+ extensionParams: DexParamEnum;
234
+ addressLookupTableAccount?: AddressLookupTableAccount;
235
+ waitTxConfirmed?: boolean;
236
+ createOutputTokenAta?: boolean;
237
+ closeOutputTokenAta?: boolean;
238
+ closeMintTokenAta?: boolean;
239
+ durableNonce?: DurableNonceInfo;
240
+ fixedOutputTokenAmount?: number;
241
+ gasFeeStrategy?: GasFeeStrategyConfig;
242
+ simulate?: boolean;
243
+ grpcRecvUs?: number;
244
+ }
245
+
246
+ /**
247
+ * Trade execution result
248
+ */
249
+ export interface TradeResult {
250
+ success: boolean;
251
+ signatures: string[];
252
+ error?: TradeError;
253
+ timings: SwqosTiming[];
254
+ /**
255
+ * Set when `simulate: true` on buy/sell — Rust `simulate_transaction` (`units_consumed`, `logs`).
256
+ */
257
+ simulation?: {
258
+ unitsConsumed?: number;
259
+ logs?: string[] | null;
260
+ };
261
+ }
262
+
263
+ /**
264
+ * SWQOS timing information
265
+ */
266
+ export interface SwqosTiming {
267
+ swqosType: SwqosType;
268
+ duration: number; // microseconds
269
+ /** Present when `TradeConfig.gasStrategy` expands multiple Rust `GasFeeStrategyType` rows. */
270
+ gasFeeStrategyType?: GasFeeStrategyType;
271
+ }
272
+
273
+ export { TradeError } from './sdk-errors';
274
+ import { TradeError } from './sdk-errors';
275
+ import type { MiddlewareManager } from './middleware/traits';
276
+
277
+ // ============== DEX Parameters ==============
278
+
279
+ /**
280
+ * Bonding curve account state
281
+ */
282
+ export interface BondingCurveAccount {
283
+ discriminator: number;
284
+ account: PublicKey;
285
+ virtualTokenReserves: number;
286
+ virtualSolReserves: number;
287
+ realTokenReserves: number;
288
+ realSolReserves: number;
289
+ tokenTotalSupply: number;
290
+ complete: boolean;
291
+ creator: PublicKey;
292
+ isMayhemMode: boolean;
293
+ isCashbackCoin: boolean;
294
+ }
295
+
296
+ /**
297
+ * PumpFun protocol parameters
298
+ */
299
+ export interface PumpFunParams {
300
+ bondingCurve: BondingCurveAccount;
301
+ associatedBondingCurve: PublicKey;
302
+ creatorVault: PublicKey;
303
+ tokenProgram: PublicKey;
304
+ observedTradeCreator?: PublicKey;
305
+ feeSharingCreatorVaultIfActive?: PublicKey;
306
+ closeTokenAccountWhenSell?: boolean;
307
+ /** Parser/gRPC fee recipient; omit or `PublicKey.default` for SDK random pool (Rust parity). */
308
+ feeRecipient?: PublicKey;
309
+ /** PumpFun V2 quote mint; omit or default pubkey for WSOL. */
310
+ quoteMint?: PublicKey;
311
+ /** Per-token V2 ix toggle; global `TradeConfig.usePumpfunV2` also enables it. */
312
+ useV2Ix?: boolean;
313
+ }
314
+
315
+ /**
316
+ * PumpSwap protocol parameters
317
+ */
318
+ export interface PumpSwapParams {
319
+ pool: PublicKey;
320
+ baseMint: PublicKey;
321
+ quoteMint: PublicKey;
322
+ poolBaseTokenAccount: PublicKey;
323
+ poolQuoteTokenAccount: PublicKey;
324
+ poolBaseTokenReserves: number;
325
+ poolQuoteTokenReserves: number;
326
+ coinCreatorVaultAta: PublicKey;
327
+ coinCreatorVaultAuthority: PublicKey;
328
+ baseTokenProgram: PublicKey;
329
+ quoteTokenProgram: PublicKey;
330
+ isMayhemMode: boolean;
331
+ isCashbackCoin: boolean;
332
+ }
333
+
334
+ /**
335
+ * Bonk protocol parameters
336
+ */
337
+ export interface BonkParams {
338
+ virtualBase: bigint;
339
+ virtualQuote: bigint;
340
+ realBase: bigint;
341
+ realQuote: bigint;
342
+ poolState: PublicKey;
343
+ baseVault: PublicKey;
344
+ quoteVault: PublicKey;
345
+ mintTokenProgram: PublicKey;
346
+ platformConfig: PublicKey;
347
+ platformAssociatedAccount: PublicKey;
348
+ creatorAssociatedAccount: PublicKey;
349
+ globalConfig: PublicKey;
350
+ }
351
+
352
+ /**
353
+ * Raydium CPMM protocol parameters
354
+ */
355
+ export interface RaydiumCpmmParams {
356
+ poolState: PublicKey;
357
+ ammConfig: PublicKey;
358
+ baseMint: PublicKey;
359
+ quoteMint: PublicKey;
360
+ baseReserve: number;
361
+ quoteReserve: number;
362
+ baseVault: PublicKey;
363
+ quoteVault: PublicKey;
364
+ baseTokenProgram: PublicKey;
365
+ quoteTokenProgram: PublicKey;
366
+ observationState: PublicKey;
367
+ }
368
+
369
+ /**
370
+ * Raydium AMM V4 protocol parameters
371
+ */
372
+ export interface RaydiumAmmV4Params {
373
+ amm: PublicKey;
374
+ coinMint: PublicKey;
375
+ pcMint: PublicKey;
376
+ tokenCoin: PublicKey;
377
+ tokenPc: PublicKey;
378
+ coinReserve: number;
379
+ pcReserve: number;
380
+ }
381
+
382
+ /**
383
+ * Meteora DAMM V2 protocol parameters
384
+ */
385
+ export interface MeteoraDammV2Params {
386
+ pool: PublicKey;
387
+ tokenAVault: PublicKey;
388
+ tokenBVault: PublicKey;
389
+ tokenAMint: PublicKey;
390
+ tokenBMint: PublicKey;
391
+ tokenAProgram: PublicKey;
392
+ tokenBProgram: PublicKey;
393
+ }
394
+
395
+ /**
396
+ * Union type for DEX parameters
397
+ */
398
+ export type DexParamEnum =
399
+ | { type: 'PumpFun'; params: PumpFunParams }
400
+ | { type: 'PumpSwap'; params: PumpSwapParams }
401
+ | { type: 'Bonk'; params: BonkParams }
402
+ | { type: 'RaydiumCpmm'; params: RaydiumCpmmParams }
403
+ | { type: 'RaydiumAmmV4'; params: RaydiumAmmV4Params }
404
+ | { type: 'MeteoraDammV2'; params: MeteoraDammV2Params };
405
+
406
+ // ============== Main Client ==============
407
+
408
+ /**
409
+ * Trading configuration
410
+ */
411
+ export interface TradeConfig {
412
+ rpcUrl: string;
413
+ swqosConfigs: SwqosConfig[];
414
+ commitment?: Commitment;
415
+ logEnabled?: boolean;
416
+ checkMinTip?: boolean;
417
+ mevProtection?: boolean;
418
+ /** Default gas/CU settings when trade params omit `gasFeeStrategy`. */
419
+ gasFeeStrategy?: GasFeeStrategyConfig;
420
+ /**
421
+ * Per-SWQOS gas table (Rust `GasFeeStrategy`). Used when neither trade nor `gasFeeStrategy` flat config is set.
422
+ * Lookup uses the first SWQOS entry after `withTip` / `checkMinTip` filtering.
423
+ */
424
+ gasStrategy?: GasFeeStrategyClass;
425
+ /** Reserved for Rust parity (seed-optimized ATA); instruction builders may ignore if not wired. */
426
+ useSeedOptimize?: boolean;
427
+ /** Use PumpFun V2 ix layout by default (`buy_v2` / `sell_v2` / quote mint support). */
428
+ usePumpfunV2?: boolean;
429
+ /** Prefer assigning SWQOS submit threads from the end of the CPU core list. */
430
+ swqosCoresFromEnd?: boolean;
431
+ /** If true, best-effort background WSOL ATA creation after connect (Rust `create_wsol_ata_on_startup`). */
432
+ createWsolAtaOnStartup?: boolean;
433
+ /**
434
+ * Rust `MiddlewareManager`: `process_protocol_instructions` before gas/tip wiring;
435
+ * `process_full_instructions` after nonce + tip + compute budget + protocol (see `transaction_builder.rs`).
436
+ */
437
+ middlewareManager?: MiddlewareManager;
438
+ /**
439
+ * Cap concurrent SWQOS `sendTransaction` calls when multiple gas/SWQOS tasks run.
440
+ * Rust uses a bounded worker pool (`max_sender_concurrency`); set this to approximate that (e.g. 8–18).
441
+ * Omit or set ≥ task count to keep previous behavior (all tasks parallel).
442
+ */
443
+ maxSwqosSubmitConcurrency?: number;
444
+ }
445
+
446
+ /**
447
+ * Builder for TradeConfig - makes all options discoverable via IDE autocomplete.
448
+ *
449
+ * @example
450
+ * const config = TradeConfigBuilder.create(rpcUrl)
451
+ * .swqosConfigs([{ type: SwqosType.Jito, apiKey: 'your-key' }])
452
+ * // .mevProtection(true) // Enable MEV protection (BlockRazor: sandwichMitigation, Astralane: port 9000)
453
+ * .build();
454
+ */
455
+ export class TradeConfigBuilder {
456
+ private _rpcUrl: string;
457
+ private _swqosConfigs: SwqosConfig[] = [];
458
+ private _commitment?: Commitment;
459
+ private _logEnabled: boolean = true;
460
+ private _checkMinTip: boolean = false;
461
+ private _mevProtection: boolean = false;
462
+ private _useSeedOptimize: boolean = true;
463
+ private _usePumpfunV2: boolean = false;
464
+ private _swqosCoresFromEnd: boolean = true;
465
+ private _createWsolAtaOnStartup: boolean = false;
466
+ private _gasFeeStrategy?: GasFeeStrategyConfig;
467
+ private _gasStrategy?: GasFeeStrategyClass;
468
+ private _middlewareManager?: MiddlewareManager;
469
+ private _maxSwqosSubmitConcurrency?: number;
470
+
471
+ private constructor(rpcUrl: string) {
472
+ this._rpcUrl = rpcUrl;
473
+ }
474
+
475
+ static create(rpcUrl: string): TradeConfigBuilder {
476
+ return new TradeConfigBuilder(rpcUrl);
477
+ }
478
+
479
+ swqosConfigs(configs: SwqosConfig[]): this {
480
+ this._swqosConfigs = configs;
481
+ return this;
482
+ }
483
+
484
+ commitment(commitment: Commitment): this {
485
+ this._commitment = commitment;
486
+ return this;
487
+ }
488
+
489
+ logEnabled(enabled: boolean): this {
490
+ this._logEnabled = enabled;
491
+ return this;
492
+ }
493
+
494
+ checkMinTip(check: boolean): this {
495
+ this._checkMinTip = check;
496
+ return this;
497
+ }
498
+
499
+ /**
500
+ * Enable MEV protection (default: false).
501
+ * When enabled:
502
+ * - BlockRazor uses mode=sandwichMitigation
503
+ * - Astralane uses port 9000 MEV-protected QUIC endpoint
504
+ */
505
+ mevProtection(enabled: boolean): this {
506
+ this._mevProtection = enabled;
507
+ return this;
508
+ }
509
+
510
+ useSeedOptimize(enabled: boolean): this {
511
+ this._useSeedOptimize = enabled;
512
+ return this;
513
+ }
514
+
515
+ usePumpfunV2(enabled: boolean): this {
516
+ this._usePumpfunV2 = enabled;
517
+ return this;
518
+ }
519
+
520
+ swqosCoresFromEnd(enabled: boolean): this {
521
+ this._swqosCoresFromEnd = enabled;
522
+ return this;
523
+ }
524
+
525
+ createWsolAtaOnStartup(enabled: boolean): this {
526
+ this._createWsolAtaOnStartup = enabled;
527
+ return this;
528
+ }
529
+
530
+ gasFeeStrategy(config: GasFeeStrategyConfig): this {
531
+ this._gasFeeStrategy = config;
532
+ return this;
533
+ }
534
+
535
+ gasStrategy(strategy: GasFeeStrategyClass): this {
536
+ this._gasStrategy = strategy;
537
+ return this;
538
+ }
539
+
540
+ /** Rust `SolanaTrade::with_middleware_manager` parity. */
541
+ middlewareManager(manager: MiddlewareManager): this {
542
+ this._middlewareManager = manager;
543
+ return this;
544
+ }
545
+
546
+ /**
547
+ * Limit parallel SWQOS submits (Rust `max_sender_concurrency` / worker pool).
548
+ */
549
+ maxSwqosSubmitConcurrency(limit: number | undefined): this {
550
+ this._maxSwqosSubmitConcurrency = limit;
551
+ return this;
552
+ }
553
+
554
+ build(): TradeConfig {
555
+ return {
556
+ rpcUrl: this._rpcUrl,
557
+ swqosConfigs: this._swqosConfigs,
558
+ commitment: this._commitment,
559
+ logEnabled: this._logEnabled,
560
+ checkMinTip: this._checkMinTip,
561
+ mevProtection: this._mevProtection,
562
+ useSeedOptimize: this._useSeedOptimize,
563
+ usePumpfunV2: this._usePumpfunV2,
564
+ swqosCoresFromEnd: this._swqosCoresFromEnd,
565
+ createWsolAtaOnStartup: this._createWsolAtaOnStartup,
566
+ gasFeeStrategy: this._gasFeeStrategy,
567
+ gasStrategy: this._gasStrategy,
568
+ middlewareManager: this._middlewareManager,
569
+ maxSwqosSubmitConcurrency: this._maxSwqosSubmitConcurrency,
570
+ };
571
+ }
572
+ }
573
+
574
+ export function recommendedSenderThreadCoreIndices(
575
+ swqosCount: number,
576
+ availableCores: number = cpus().length,
577
+ fromEnd: boolean = true
578
+ ): number[] {
579
+ if (swqosCount <= 0 || availableCores <= 0) {
580
+ return [];
581
+ }
582
+ const count = Math.min(swqosCount, availableCores);
583
+ if (fromEnd) {
584
+ return Array.from({ length: count }, (_, i) => availableCores - count + i);
585
+ }
586
+ return Array.from({ length: count }, (_, i) => i);
587
+ }
588
+
589
+ /**
590
+ * Middleware context
591
+ */
592
+ export interface MiddlewareContext {
593
+ tradeType: TradeType;
594
+ inputMint: PublicKey;
595
+ outputMint: PublicKey;
596
+ inputAmount: number;
597
+ payer: PublicKey;
598
+ additionalData?: Record<string, unknown>;
599
+ }
600
+
601
+ /**
602
+ * Middleware interface
603
+ */
604
+ export interface Middleware {
605
+ process(
606
+ instructions: TransactionInstruction[],
607
+ context: MiddlewareContext
608
+ ): Promise<TransactionInstruction[]>;
609
+ name: string;
610
+ }
611
+
612
+ function validateDexParamEnum(dexType: DexType, ext: DexParamEnum): void {
613
+ if (ext.type !== dexType) {
614
+ throw new TradeError(
615
+ 5,
616
+ `extensionParams.type (${ext.type}) must match dexType (${String(dexType)})`
617
+ );
618
+ }
619
+ }
620
+
621
+ function mapPumpFunParams(p: PumpFunParams): PumpFunBuilderParams {
622
+ const bc = p.bondingCurve;
623
+ return {
624
+ bondingCurve: {
625
+ account: bc.account,
626
+ virtualTokenReserves: BigInt(bc.virtualTokenReserves),
627
+ virtualSolReserves: BigInt(bc.virtualSolReserves),
628
+ realTokenReserves: BigInt(bc.realTokenReserves),
629
+ creator: bc.creator,
630
+ isMayhemMode: bc.isMayhemMode,
631
+ isCashbackCoin: bc.isCashbackCoin,
632
+ },
633
+ creatorVault: p.creatorVault,
634
+ tokenProgram: p.tokenProgram,
635
+ associatedBondingCurve: p.associatedBondingCurve,
636
+ observedTradeCreator: p.observedTradeCreator,
637
+ feeSharingCreatorVaultIfActive: p.feeSharingCreatorVaultIfActive,
638
+ closeTokenAccountWhenSell: p.closeTokenAccountWhenSell,
639
+ feeRecipient: p.feeRecipient,
640
+ quoteMint: p.quoteMint,
641
+ useV2Ix: p.useV2Ix,
642
+ };
643
+ }
644
+
645
+ /**
646
+ * 按 mint 查找池地址(与 Rust `find_pool_by_mint` 一致:当前仅 PumpSwap)。
647
+ */
648
+ export async function findPoolByMint(
649
+ connection: Connection,
650
+ mint: PublicKey,
651
+ dexType: DexType
652
+ ): Promise<PublicKey> {
653
+ if (dexType !== DexType.PumpSwap) {
654
+ throw new TradeError(
655
+ 6,
656
+ `findPoolByMint is only implemented for PumpSwap`
657
+ );
658
+ }
659
+ const found = await findPumpSwapPoolByMint(
660
+ {
661
+ getAccountInfo: async (pk: PublicKey) => {
662
+ const a = await connection.getAccountInfo(pk);
663
+ return { value: a ? { data: a.data as Buffer } : null };
664
+ },
665
+ },
666
+ mint
667
+ );
668
+ if (!found) {
669
+ throw new TradeError(7, 'No PumpSwap pool found for mint');
670
+ }
671
+ return found.poolAddress;
672
+ }
673
+
674
+ /** @internal */
675
+ interface TxExecContext {
676
+ tradeType: TradeType;
677
+ /** For Rust `InstructionMiddleware` protocol / full passes (`String(dex_type)`). */
678
+ dexType: DexType;
679
+ gasFeeStrategy?: GasFeeStrategyConfig;
680
+ withTip?: boolean;
681
+ grpcRecvUs?: number;
682
+ /** Rust `execute_parallel`: multi-SWQOS buy requires durable nonce; also drives nonce advance + blockhash. */
683
+ durableNonce?: DurableNonceInfo;
684
+ }
685
+
686
+ /** Rust `async_executor`: `SUBMIT_TIMEOUT_SECS` when `wait_transaction_confirmed` is false. */
687
+ const SWQOS_SUBMIT_TIMEOUT_MS_WHEN_NO_CONFIRM = 2000;
688
+
689
+ /** Rust `executor::simulate_transaction` / `RpcSimulateTransactionConfig` (processed, inner ix, no sig verify). */
690
+ export const RUST_PARITY_SIMULATE_CONFIG: SimulateTransactionConfig = {
691
+ sigVerify: false,
692
+ replaceRecentBlockhash: false,
693
+ commitment: 'processed',
694
+ innerInstructions: true,
695
+ };
696
+
697
+ /** Instruction order matches Rust `trading/common/transaction_builder.rs` `build_transaction`: tip → compute budget → business. */
698
+ function buildInstructionListWithGasAndTip(
699
+ coreInstructions: TransactionInstruction[],
700
+ payer: PublicKey,
701
+ tradeType: TradeType,
702
+ gas: GasFeeStrategyConfig | undefined,
703
+ tipRecipient: PublicKey | null,
704
+ addTip: boolean
705
+ ): TransactionInstruction[] {
706
+ const isBuy = tradeType === TradeType.Buy;
707
+ const cuLimit = gas
708
+ ? isBuy
709
+ ? gas.buyComputeUnits
710
+ : gas.sellComputeUnits
711
+ : SDK_CONSTANTS.DEFAULT_COMPUTE_UNITS;
712
+ const cuPrice = BigInt(
713
+ gas
714
+ ? isBuy
715
+ ? gas.buyPriorityFee
716
+ : gas.sellPriorityFee
717
+ : SDK_CONSTANTS.DEFAULT_PRIORITY_FEE
718
+ );
719
+ const tipLamports = gas
720
+ ? isBuy
721
+ ? gas.buyTipLamports
722
+ : gas.sellTipLamports
723
+ : 0;
724
+
725
+ const out: TransactionInstruction[] = [];
726
+ if (addTip && tipRecipient && tipLamports > 0) {
727
+ out.push(
728
+ SystemProgram.transfer({
729
+ fromPubkey: payer,
730
+ toPubkey: tipRecipient,
731
+ lamports: tipLamports,
732
+ })
733
+ );
734
+ }
735
+ out.push(...computeBudgetInstructions(cuPrice, cuLimit));
736
+ return [...out, ...coreInstructions];
737
+ }
738
+
739
+ /**
740
+ * Rust `trading/common/transaction_builder.rs` `build_versioned_transaction`:
741
+ * always produce a v0 {@link VersionedTransaction} with optional LUT.
742
+ */
743
+ function buildSignedVersionedTransaction(
744
+ payer: Keypair,
745
+ instructions: TransactionInstruction[],
746
+ recentBlockhash: string,
747
+ addressLookupTableAccount?: AddressLookupTableAccount
748
+ ): VersionedTransaction {
749
+ const messageV0 = new TransactionMessage({
750
+ payerKey: payer.publicKey,
751
+ recentBlockhash,
752
+ instructions,
753
+ }).compileToV0Message(
754
+ addressLookupTableAccount != null ? [addressLookupTableAccount] : []
755
+ );
756
+ const tx = new VersionedTransaction(messageV0);
757
+ tx.sign([payer]);
758
+ return tx;
759
+ }
760
+
761
+ function mapSwqosToClientConfig(
762
+ c: SwqosConfig,
763
+ globalMev?: boolean
764
+ ): {
765
+ type: SwqosType;
766
+ region?: SwqosRegion;
767
+ apiKey?: string;
768
+ customUrl?: string;
769
+ mevProtection?: boolean;
770
+ transport?: SwqosTransport;
771
+ astralaneTransport?: AstralaneTransport;
772
+ swqosOnly?: boolean;
773
+ } {
774
+ return {
775
+ type: c.type,
776
+ region: c.region,
777
+ apiKey: c.apiKey,
778
+ customUrl: c.customUrl,
779
+ mevProtection: c.mevProtection ?? globalMev ?? false,
780
+ transport: c.transport,
781
+ astralaneTransport: c.astralaneTransport,
782
+ swqosOnly: c.swqosOnly,
783
+ };
784
+ }
785
+
786
+ function gasConfigFromStrategyClass(
787
+ gs: GasFeeStrategyClass,
788
+ swqosType: SwqosType
789
+ ): GasFeeStrategyConfig | undefined {
790
+ const st = swqosType as unknown as GfsSwqosType;
791
+ const buyV = gs.get(st, GfsTradeType.Buy, GasFeeStrategyType.Normal);
792
+ const sellV = gs.get(st, GfsTradeType.Sell, GasFeeStrategyType.Normal);
793
+ if (!buyV && !sellV) return undefined;
794
+ return {
795
+ buyComputeUnits: buyV?.cuLimit ?? SDK_CONSTANTS.DEFAULT_COMPUTE_UNITS,
796
+ sellComputeUnits: sellV?.cuLimit ?? SDK_CONSTANTS.DEFAULT_COMPUTE_UNITS,
797
+ buyPriorityFee: buyV?.cuPrice ?? SDK_CONSTANTS.DEFAULT_PRIORITY_FEE,
798
+ sellPriorityFee: sellV?.cuPrice ?? SDK_CONSTANTS.DEFAULT_PRIORITY_FEE,
799
+ buyTipLamports: buyV ? Math.floor(buyV.tip * 1e9) : 0,
800
+ sellTipLamports: sellV ? Math.floor(sellV.tip * 1e9) : 0,
801
+ };
802
+ }
803
+
804
+ /** Single strategy row → flat gas config for the active trade direction (Rust `GasFeeStrategyValue` job). */
805
+ function gasConfigFromStrategyValue(
806
+ value: GasFeeStrategyValue,
807
+ tradeType: TradeType
808
+ ): GasFeeStrategyConfig {
809
+ const isBuy = tradeType === TradeType.Buy;
810
+ const tipLam = Math.floor(value.tip * 1e9);
811
+ return {
812
+ buyComputeUnits: isBuy ? value.cuLimit : SDK_CONSTANTS.DEFAULT_COMPUTE_UNITS,
813
+ sellComputeUnits: !isBuy ? value.cuLimit : SDK_CONSTANTS.DEFAULT_COMPUTE_UNITS,
814
+ buyPriorityFee: isBuy ? value.cuPrice : SDK_CONSTANTS.DEFAULT_PRIORITY_FEE,
815
+ sellPriorityFee: !isBuy ? value.cuPrice : SDK_CONSTANTS.DEFAULT_PRIORITY_FEE,
816
+ buyTipLamports: isBuy ? tipLam : 0,
817
+ sellTipLamports: !isBuy ? tipLam : 0,
818
+ };
819
+ }
820
+
821
+ /** One parallel send: SWQOS config + gas row (Rust `task_configs` entry). */
822
+ interface SwqosGasTask {
823
+ cfg: SwqosConfig;
824
+ gas: GasFeeStrategyConfig | undefined;
825
+ strategyType?: GasFeeStrategyType;
826
+ }
827
+
828
+ /**
829
+ * Rust `async_executor::execute_parallel`: `gas_fee_strategy.get_strategies(trade_type)` × each SWQOS client,
830
+ * with `check_min_tip` applied per (client, strategy row).
831
+ */
832
+ function expandSwqosGasTasks(
833
+ effectiveSwqos: SwqosConfig[],
834
+ tradeType: TradeType,
835
+ execGas: GasFeeStrategyConfig | undefined,
836
+ configGas: GasFeeStrategyConfig | undefined,
837
+ gasStrategy: GasFeeStrategyClass | undefined,
838
+ useStrategyRowMinTip: boolean,
839
+ checkMinTip: boolean,
840
+ withTip: boolean,
841
+ swqosMod: typeof import('./swqos/clients'),
842
+ mevProtection: boolean | undefined,
843
+ rpcUrl: string,
844
+ logEnabled: boolean
845
+ ): SwqosGasTask[] {
846
+ const out: SwqosGasTask[] = [];
847
+
848
+ if (execGas || configGas) {
849
+ for (const cfg of effectiveSwqos) {
850
+ const gas = resolveMergedGas(execGas, configGas, undefined, cfg.type);
851
+ out.push({ cfg, gas });
852
+ }
853
+ return out;
854
+ }
855
+
856
+ if (gasStrategy && useStrategyRowMinTip) {
857
+ const gfsTT = tradeType as unknown as GfsTradeType;
858
+ const rows = gasStrategy.getStrategies(gfsTT);
859
+ for (const cfg of effectiveSwqos) {
860
+ const st = cfg.type as unknown as GfsSwqosType;
861
+ const matching = rows.filter((r) => r.swqosType === st);
862
+ if (matching.length === 0) {
863
+ const g = gasConfigFromStrategyClass(gasStrategy, cfg.type);
864
+ if (g) {
865
+ out.push({ cfg, gas: g, strategyType: GasFeeStrategyType.Normal });
866
+ }
867
+ continue;
868
+ }
869
+ for (const row of matching) {
870
+ const tipLamports = Math.floor(row.value.tip * 1e9);
871
+ if (checkMinTip && withTip && cfg.type !== SwqosType.Default) {
872
+ const client = swqosMod.ClientFactory.createClient(
873
+ mapSwqosToClientConfig(cfg, mevProtection),
874
+ rpcUrl
875
+ );
876
+ const minLamports = Math.ceil(client.minTipSol() * 1_000_000_000);
877
+ if (tipLamports < minLamports) {
878
+ if (logEnabled) {
879
+ console.info(
880
+ `[sol-trade-sdk] SWQOS ${cfg.type} (${row.strategyType}) skipped (checkMinTip): tip ${tipLamports} lamports < minimum ${minLamports}`
881
+ );
882
+ }
883
+ continue;
884
+ }
885
+ }
886
+ out.push({
887
+ cfg,
888
+ gas: gasConfigFromStrategyValue(row.value, tradeType),
889
+ strategyType: row.strategyType,
890
+ });
891
+ }
892
+ }
893
+ return out;
894
+ }
895
+
896
+ for (const cfg of effectiveSwqos) {
897
+ const gas = resolveMergedGas(undefined, undefined, gasStrategy, cfg.type);
898
+ out.push({ cfg, gas });
899
+ }
900
+ return out;
901
+ }
902
+
903
+ function resolveMergedGas(
904
+ execGas: GasFeeStrategyConfig | undefined,
905
+ configGas: GasFeeStrategyConfig | undefined,
906
+ gasStrategy: GasFeeStrategyClass | undefined,
907
+ firstSwqosType: SwqosType | undefined
908
+ ): GasFeeStrategyConfig | undefined {
909
+ if (execGas) return execGas;
910
+ if (configGas) return configGas;
911
+ if (gasStrategy && firstSwqosType !== undefined) {
912
+ return gasConfigFromStrategyClass(gasStrategy, firstSwqosType);
913
+ }
914
+ return undefined;
915
+ }
916
+
917
+ /** Rust `execute_parallel`: when `with_tip` is false, only `Default` RPC SWQOS participates. */
918
+ function filterSwqosConfigsForWithTip(
919
+ configs: SwqosConfig[],
920
+ withTip: boolean
921
+ ): SwqosConfig[] {
922
+ if (withTip) return configs;
923
+ return configs.filter((c) => c.type === SwqosType.Default);
924
+ }
925
+
926
+ function filterSwqosConfigsByMinTip(
927
+ swqosMod: typeof import('./swqos/clients'),
928
+ configs: SwqosConfig[],
929
+ tradeType: TradeType,
930
+ gas: GasFeeStrategyConfig | undefined,
931
+ mevProtection: boolean | undefined,
932
+ rpcUrl: string,
933
+ logEnabled: boolean
934
+ ): SwqosConfig[] {
935
+ const tipLamports = gas
936
+ ? tradeType === TradeType.Buy
937
+ ? gas.buyTipLamports
938
+ : gas.sellTipLamports
939
+ : 0;
940
+ const out: SwqosConfig[] = [];
941
+ for (const cfg of configs) {
942
+ if (cfg.type === SwqosType.Default) {
943
+ out.push(cfg);
944
+ continue;
945
+ }
946
+ const client = swqosMod.ClientFactory.createClient(
947
+ mapSwqosToClientConfig(cfg, mevProtection),
948
+ rpcUrl
949
+ );
950
+ const minLamports = Math.ceil(client.minTipSol() * 1_000_000_000);
951
+ if (tipLamports < minLamports) {
952
+ if (logEnabled) {
953
+ console.info(
954
+ `[sol-trade-sdk] SWQOS ${cfg.type} skipped (checkMinTip): tip ${tipLamports} lamports < minimum ${minLamports}`
955
+ );
956
+ }
957
+ continue;
958
+ }
959
+ out.push(cfg);
960
+ }
961
+ return out;
962
+ }
963
+
964
+ function resolveTipRecipientPubkey(
965
+ swqosMod: typeof import('./swqos/clients'),
966
+ configs: SwqosConfig[],
967
+ mevProtection: boolean | undefined,
968
+ rpcUrl: string
969
+ ): PublicKey | null {
970
+ const preferred =
971
+ configs.find((c) => c.type !== SwqosType.Default) ?? configs[0];
972
+ if (!preferred) return null;
973
+ const client = swqosMod.ClientFactory.createClient(
974
+ mapSwqosToClientConfig(preferred, mevProtection),
975
+ rpcUrl
976
+ );
977
+ const acc = client.getTipAccount();
978
+ if (!acc) return null;
979
+ try {
980
+ return new PublicKey(acc);
981
+ } catch {
982
+ return null;
983
+ }
984
+ }
985
+
986
+ /**
987
+ * Main trading client for Solana DEX operations(指令构建与 Rust SDK 对齐,经 `instruction/*` 实现)
988
+ */
989
+ export class TradingClient {
990
+ private payer: Keypair;
991
+ private connection: Connection;
992
+ private _config: TradeConfig;
993
+ private middlewares: Middleware[] = [];
994
+ private _logEnabled: boolean;
995
+
996
+ constructor(payer: Keypair, config: TradeConfig) {
997
+ this.payer = payer;
998
+ this._config = config;
999
+ this.connection = new Connection(config.rpcUrl, {
1000
+ commitment: config.commitment ?? 'confirmed',
1001
+ });
1002
+ this._logEnabled = config.logEnabled ?? true;
1003
+ }
1004
+
1005
+ /** Get the current configuration */
1006
+ get config(): TradeConfig {
1007
+ return this._config;
1008
+ }
1009
+
1010
+ /** Check if logging is enabled */
1011
+ get isLogEnabled(): boolean {
1012
+ return this._logEnabled;
1013
+ }
1014
+
1015
+ /**
1016
+ * Get the underlying connection
1017
+ */
1018
+ getConnection(): Connection {
1019
+ return this.connection;
1020
+ }
1021
+
1022
+ /**
1023
+ * Get the payer public key
1024
+ */
1025
+ getPayer(): PublicKey {
1026
+ return this.payer.publicKey;
1027
+ }
1028
+
1029
+ private rpcCommitment(): Commitment {
1030
+ return this._config.commitment ?? 'confirmed';
1031
+ }
1032
+
1033
+ /**
1034
+ * Add middleware to the chain
1035
+ */
1036
+ addMiddleware(middleware: Middleware): this {
1037
+ this.middlewares.push(middleware);
1038
+ return this;
1039
+ }
1040
+
1041
+ /**
1042
+ * Execute a buy order
1043
+ */
1044
+ async buy(params: TradeBuyParams): Promise<TradeResult> {
1045
+ const blockhash =
1046
+ params.recentBlockhash ?? params.durableNonce?.nonceHash;
1047
+ if (!blockhash) {
1048
+ throw new TradeError(
1049
+ 1,
1050
+ 'Must provide recentBlockhash or durableNonce.nonceHash (current nonce blockhash)'
1051
+ );
1052
+ }
1053
+ if (
1054
+ params.inputTokenType === TradeTokenType.USD1 &&
1055
+ params.dexType !== DexType.Bonk
1056
+ ) {
1057
+ throw new TradeError(
1058
+ 1,
1059
+ 'USD1 as input is only supported on Bonk (Rust SDK parity)'
1060
+ );
1061
+ }
1062
+ validateDexParamEnum(params.dexType, params.extensionParams);
1063
+
1064
+ Prefetch.keypair(this.payer);
1065
+ let instructions = this.buildBuyInstructions(params);
1066
+ try {
1067
+ InstructionProcessor.preprocess(instructions);
1068
+ } catch (e) {
1069
+ if (e instanceof Error && e.message === 'Instructions empty') {
1070
+ throw new TradeError(105, 'Instructions empty');
1071
+ }
1072
+ throw e;
1073
+ }
1074
+ if (this._config.middlewareManager) {
1075
+ instructions =
1076
+ this._config.middlewareManager.applyMiddlewaresProcessProtocolInstructions(
1077
+ instructions,
1078
+ String(params.dexType),
1079
+ true
1080
+ );
1081
+ }
1082
+
1083
+ const processedInstructions = await this.processMiddlewares(
1084
+ instructions,
1085
+ TradeType.Buy,
1086
+ params
1087
+ );
1088
+
1089
+ return this.executeTransaction(
1090
+ processedInstructions,
1091
+ blockhash,
1092
+ params.addressLookupTableAccount,
1093
+ params.waitTxConfirmed ?? true,
1094
+ params.simulate,
1095
+ {
1096
+ tradeType: TradeType.Buy,
1097
+ dexType: params.dexType,
1098
+ gasFeeStrategy: params.gasFeeStrategy,
1099
+ withTip: true,
1100
+ grpcRecvUs: params.grpcRecvUs,
1101
+ durableNonce: params.durableNonce,
1102
+ }
1103
+ );
1104
+ }
1105
+
1106
+ /**
1107
+ * Execute a sell order
1108
+ */
1109
+ async sell(params: TradeSellParams): Promise<TradeResult> {
1110
+ const blockhash =
1111
+ params.recentBlockhash ?? params.durableNonce?.nonceHash;
1112
+ if (!blockhash) {
1113
+ throw new TradeError(
1114
+ 1,
1115
+ 'Must provide recentBlockhash or durableNonce.nonceHash (current nonce blockhash)'
1116
+ );
1117
+ }
1118
+ if (
1119
+ params.outputTokenType === TradeTokenType.USD1 &&
1120
+ params.dexType !== DexType.Bonk
1121
+ ) {
1122
+ throw new TradeError(
1123
+ 1,
1124
+ 'USD1 as output is only supported on Bonk (Rust SDK parity)'
1125
+ );
1126
+ }
1127
+ validateDexParamEnum(params.dexType, params.extensionParams);
1128
+
1129
+ Prefetch.keypair(this.payer);
1130
+ let instructions = this.buildSellInstructions(params);
1131
+ try {
1132
+ InstructionProcessor.preprocess(instructions);
1133
+ } catch (e) {
1134
+ if (e instanceof Error && e.message === 'Instructions empty') {
1135
+ throw new TradeError(105, 'Instructions empty');
1136
+ }
1137
+ throw e;
1138
+ }
1139
+ if (this._config.middlewareManager) {
1140
+ instructions =
1141
+ this._config.middlewareManager.applyMiddlewaresProcessProtocolInstructions(
1142
+ instructions,
1143
+ String(params.dexType),
1144
+ false
1145
+ );
1146
+ }
1147
+
1148
+ const processedInstructions = await this.processMiddlewares(
1149
+ instructions,
1150
+ TradeType.Sell,
1151
+ params
1152
+ );
1153
+
1154
+ return this.executeTransaction(
1155
+ processedInstructions,
1156
+ blockhash,
1157
+ params.addressLookupTableAccount,
1158
+ params.waitTxConfirmed ?? true,
1159
+ params.simulate,
1160
+ {
1161
+ tradeType: TradeType.Sell,
1162
+ dexType: params.dexType,
1163
+ gasFeeStrategy: params.gasFeeStrategy,
1164
+ withTip: params.withTip ?? true,
1165
+ grpcRecvUs: params.grpcRecvUs,
1166
+ durableNonce: params.durableNonce,
1167
+ }
1168
+ );
1169
+ }
1170
+
1171
+ /**
1172
+ * Execute a sell order for a percentage of tokens
1173
+ */
1174
+ async sellByPercent(
1175
+ params: TradeSellParams,
1176
+ totalAmount: number,
1177
+ percent: number
1178
+ ): Promise<TradeResult> {
1179
+ if (percent <= 0 || percent > 100) {
1180
+ throw new TradeError(2, 'Percentage must be between 1 and 100');
1181
+ }
1182
+ const amount = Math.floor((totalAmount * percent) / 100);
1183
+ return this.sell({ ...params, inputTokenAmount: amount });
1184
+ }
1185
+
1186
+ /**
1187
+ * Get latest blockhash
1188
+ */
1189
+ async getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight> {
1190
+ return this.connection.getLatestBlockhash(this.rpcCommitment());
1191
+ }
1192
+
1193
+ /**
1194
+ * Wrap SOL to WSOL
1195
+ */
1196
+ async wrapSolToWsol(amount: number): Promise<string> {
1197
+ if (amount <= 0) throw new TradeError(2, 'Amount must be greater than zero');
1198
+ return this.sendInstructions(handleWsol(this.payer.publicKey, BigInt(amount)));
1199
+ }
1200
+
1201
+ /**
1202
+ * Close WSOL account and unwrap to SOL
1203
+ */
1204
+ async closeWsol(): Promise<string> {
1205
+ return this.sendSingleInstruction(wsolCloseIx(this.payer.publicKey));
1206
+ }
1207
+
1208
+ /**
1209
+ * Claim PumpFun bonding-curve cashback (SOL → wallet).
1210
+ */
1211
+ async claimCashbackPumpfun(): Promise<string> {
1212
+ const ix = buildPumpFunClaimCashbackInstruction(this.payer.publicKey);
1213
+ return this.sendSingleInstruction(ix);
1214
+ }
1215
+
1216
+ /**
1217
+ * Claim PumpSwap AMM cashback(会先走 WSOL ATA idempotent 创建,与 Rust 一致)。
1218
+ */
1219
+ async claimCashbackPumpswap(): Promise<string> {
1220
+ const { TOKEN_PROGRAM, WSOL_TOKEN_ACCOUNT } = SDK_CONSTANTS;
1221
+ const ixs = [
1222
+ createAtaIdempotentPumpSwap(
1223
+ this.payer.publicKey,
1224
+ this.payer.publicKey,
1225
+ WSOL_TOKEN_ACCOUNT,
1226
+ TOKEN_PROGRAM
1227
+ ),
1228
+ buildPumpSwapClaimCashbackInstruction(
1229
+ this.payer.publicKey,
1230
+ WSOL_TOKEN_ACCOUNT,
1231
+ TOKEN_PROGRAM
1232
+ ),
1233
+ ];
1234
+ return this.sendInstructions(ixs);
1235
+ }
1236
+
1237
+ private async sendSingleInstruction(ix: TransactionInstruction): Promise<string> {
1238
+ return this.sendInstructions([ix]);
1239
+ }
1240
+
1241
+ private async sendInstructions(ixs: TransactionInstruction[]): Promise<string> {
1242
+ const { blockhash, lastValidBlockHeight } =
1243
+ await this.connection.getLatestBlockhash(this.rpcCommitment());
1244
+ const tx = new Transaction({ blockhash, lastValidBlockHeight });
1245
+ tx.add(...ixs);
1246
+ tx.sign(this.payer);
1247
+ const signature = await this.connection.sendRawTransaction(tx.serialize());
1248
+ await this.connection.confirmTransaction(
1249
+ {
1250
+ signature,
1251
+ blockhash,
1252
+ lastValidBlockHeight,
1253
+ },
1254
+ this.rpcCommitment()
1255
+ );
1256
+ return signature;
1257
+ }
1258
+
1259
+ private buildBuyInstructions(params: TradeBuyParams): TransactionInstruction[] {
1260
+ const slippage = BigInt(
1261
+ params.slippageBasisPoints ?? SDK_CONSTANTS.DEFAULT_SLIPPAGE
1262
+ );
1263
+ const inputAmt = BigInt(params.inputTokenAmount);
1264
+ const ext = params.extensionParams;
1265
+
1266
+ switch (params.dexType) {
1267
+ case DexType.PumpFun: {
1268
+ if (ext.type !== 'PumpFun') throw new TradeError(5, 'Invalid PumpFun params');
1269
+ return buildPumpFunBuyInstructions({
1270
+ payer: this.payer.publicKey,
1271
+ outputMint: params.mint,
1272
+ inputAmount: inputAmt,
1273
+ slippageBasisPoints: slippage,
1274
+ fixedOutputAmount:
1275
+ params.fixedOutputTokenAmount !== undefined
1276
+ ? BigInt(params.fixedOutputTokenAmount)
1277
+ : undefined,
1278
+ createOutputMintAta: params.createMintAta ?? true,
1279
+ createInputMintAta: params.createInputTokenAta ?? false,
1280
+ protocolParams: mapPumpFunParams(ext.params),
1281
+ useExactSolAmount: params.useExactSolAmount ?? true,
1282
+ usePumpFunV2: this._config.usePumpfunV2 ?? ext.params.useV2Ix ?? false,
1283
+ });
1284
+ }
1285
+ case DexType.PumpSwap: {
1286
+ if (ext.type !== 'PumpSwap') throw new TradeError(5, 'Invalid PumpSwap params');
1287
+ const p = ext.params;
1288
+ const protocolParams: PumpSwapBuilderParams = {
1289
+ pool: p.pool,
1290
+ baseMint: p.baseMint,
1291
+ quoteMint: p.quoteMint,
1292
+ poolBaseTokenAccount: p.poolBaseTokenAccount,
1293
+ poolQuoteTokenAccount: p.poolQuoteTokenAccount,
1294
+ poolBaseTokenReserves: BigInt(p.poolBaseTokenReserves),
1295
+ poolQuoteTokenReserves: BigInt(p.poolQuoteTokenReserves),
1296
+ coinCreatorVaultAta: p.coinCreatorVaultAta,
1297
+ coinCreatorVaultAuthority: p.coinCreatorVaultAuthority,
1298
+ baseTokenProgram: p.baseTokenProgram,
1299
+ quoteTokenProgram: p.quoteTokenProgram,
1300
+ isMayhemMode: p.isMayhemMode,
1301
+ isCashbackCoin: p.isCashbackCoin,
1302
+ };
1303
+ return buildPumpSwapBuyInstructions({
1304
+ payer: this.payer.publicKey,
1305
+ inputAmount: inputAmt,
1306
+ slippageBasisPoints: slippage,
1307
+ protocolParams,
1308
+ createInputMintAta: params.createInputTokenAta ?? true,
1309
+ createOutputMintAta: params.createMintAta ?? true,
1310
+ closeInputMintAta: params.closeInputTokenAta ?? false,
1311
+ useExactQuoteAmount: params.useExactSolAmount ?? true,
1312
+ fixedOutputAmount:
1313
+ params.fixedOutputTokenAmount !== undefined
1314
+ ? BigInt(params.fixedOutputTokenAmount)
1315
+ : undefined,
1316
+ });
1317
+ }
1318
+ case DexType.Bonk: {
1319
+ if (ext.type !== 'Bonk') throw new TradeError(5, 'Invalid Bonk params');
1320
+ const p = ext.params;
1321
+ const protocolParams: BonkBuilderParams = {
1322
+ poolState: p.poolState,
1323
+ baseVault: p.baseVault,
1324
+ quoteVault: p.quoteVault,
1325
+ virtualBase: BigInt(p.virtualBase),
1326
+ virtualQuote: BigInt(p.virtualQuote),
1327
+ realBase: BigInt(p.realBase),
1328
+ realQuote: BigInt(p.realQuote),
1329
+ mintTokenProgram: p.mintTokenProgram,
1330
+ platformConfig: p.platformConfig,
1331
+ platformAssociatedAccount: p.platformAssociatedAccount,
1332
+ creatorAssociatedAccount: p.creatorAssociatedAccount,
1333
+ globalConfig: p.globalConfig,
1334
+ };
1335
+ return buildBonkBuyInstructions({
1336
+ payer: this.payer.publicKey,
1337
+ outputMint: params.mint,
1338
+ inputAmount: inputAmt,
1339
+ slippageBasisPoints: slippage,
1340
+ fixedOutputAmount:
1341
+ params.fixedOutputTokenAmount !== undefined
1342
+ ? BigInt(params.fixedOutputTokenAmount)
1343
+ : undefined,
1344
+ createInputMintAta: params.createInputTokenAta ?? true,
1345
+ createOutputMintAta: params.createMintAta ?? true,
1346
+ closeInputMintAta: params.closeInputTokenAta ?? false,
1347
+ protocolParams,
1348
+ });
1349
+ }
1350
+ case DexType.RaydiumCpmm: {
1351
+ if (ext.type !== 'RaydiumCpmm') throw new TradeError(5, 'Invalid Raydium CPMM params');
1352
+ const p = ext.params;
1353
+ const protocolParams: RaydiumCpmmBuilderParams = {
1354
+ poolState: p.poolState,
1355
+ ammConfig: p.ammConfig,
1356
+ baseMint: p.baseMint,
1357
+ quoteMint: p.quoteMint,
1358
+ baseTokenProgram: p.baseTokenProgram,
1359
+ quoteTokenProgram: p.quoteTokenProgram,
1360
+ baseVault: p.baseVault,
1361
+ quoteVault: p.quoteVault,
1362
+ baseReserve: BigInt(p.baseReserve),
1363
+ quoteReserve: BigInt(p.quoteReserve),
1364
+ observationState: p.observationState,
1365
+ };
1366
+ return buildRaydiumCpmmBuyInstructions({
1367
+ payer: this.payer.publicKey,
1368
+ outputMint: params.mint,
1369
+ inputAmount: inputAmt,
1370
+ slippageBasisPoints: slippage,
1371
+ fixedOutputAmount:
1372
+ params.fixedOutputTokenAmount !== undefined
1373
+ ? BigInt(params.fixedOutputTokenAmount)
1374
+ : undefined,
1375
+ createInputMintAta: params.createInputTokenAta ?? true,
1376
+ createOutputMintAta: params.createMintAta ?? true,
1377
+ closeInputMintAta: params.closeInputTokenAta ?? false,
1378
+ protocolParams,
1379
+ });
1380
+ }
1381
+ case DexType.RaydiumAmmV4: {
1382
+ if (ext.type !== 'RaydiumAmmV4') throw new TradeError(5, 'Invalid Raydium AMM V4 params');
1383
+ const p = ext.params;
1384
+ const protocolParams: RaydiumAmmV4BuilderParams = {
1385
+ amm: p.amm,
1386
+ coinMint: p.coinMint,
1387
+ pcMint: p.pcMint,
1388
+ tokenCoin: p.tokenCoin,
1389
+ tokenPc: p.tokenPc,
1390
+ coinReserve: BigInt(p.coinReserve),
1391
+ pcReserve: BigInt(p.pcReserve),
1392
+ };
1393
+ return buildRaydiumAmmV4BuyInstructions({
1394
+ payer: this.payer.publicKey,
1395
+ outputMint: params.mint,
1396
+ inputAmount: inputAmt,
1397
+ slippageBasisPoints: slippage,
1398
+ fixedOutputAmount:
1399
+ params.fixedOutputTokenAmount !== undefined
1400
+ ? BigInt(params.fixedOutputTokenAmount)
1401
+ : undefined,
1402
+ createInputMintAta: params.createInputTokenAta ?? true,
1403
+ createOutputMintAta: params.createMintAta ?? true,
1404
+ closeInputMintAta: params.closeInputTokenAta ?? false,
1405
+ protocolParams,
1406
+ });
1407
+ }
1408
+ case DexType.MeteoraDammV2: {
1409
+ if (ext.type !== 'MeteoraDammV2') throw new TradeError(5, 'Invalid Meteora params');
1410
+ if (params.fixedOutputTokenAmount === undefined) {
1411
+ throw new TradeError(
1412
+ 8,
1413
+ 'Meteora DAMM V2 requires fixedOutputTokenAmount (builder parity)'
1414
+ );
1415
+ }
1416
+ const p = ext.params;
1417
+ return buildMeteoraDammV2BuyInstructions({
1418
+ payer: this.payer.publicKey,
1419
+ inputMint: this.getInputMint(params.inputTokenType),
1420
+ outputMint: params.mint,
1421
+ inputAmount: inputAmt,
1422
+ slippageBasisPoints: slippage,
1423
+ fixedOutputAmount: BigInt(params.fixedOutputTokenAmount),
1424
+ createInputMintAta: params.createInputTokenAta ?? true,
1425
+ createOutputMintAta: params.createMintAta ?? true,
1426
+ closeInputMintAta: params.closeInputTokenAta ?? false,
1427
+ protocolParams: {
1428
+ pool: p.pool,
1429
+ tokenAVault: p.tokenAVault,
1430
+ tokenBVault: p.tokenBVault,
1431
+ tokenAMint: p.tokenAMint,
1432
+ tokenBMint: p.tokenBMint,
1433
+ tokenAProgram: p.tokenAProgram,
1434
+ tokenBProgram: p.tokenBProgram,
1435
+ },
1436
+ });
1437
+ }
1438
+ default:
1439
+ throw new TradeError(4, `Unsupported DEX type: ${String(params.dexType)}`);
1440
+ }
1441
+ }
1442
+
1443
+ private buildSellInstructions(params: TradeSellParams): TransactionInstruction[] {
1444
+ const slippage = BigInt(
1445
+ params.slippageBasisPoints ?? SDK_CONSTANTS.DEFAULT_SLIPPAGE
1446
+ );
1447
+ const inputAmt = BigInt(params.inputTokenAmount);
1448
+ const ext = params.extensionParams;
1449
+
1450
+ switch (params.dexType) {
1451
+ case DexType.PumpFun: {
1452
+ if (ext.type !== 'PumpFun') throw new TradeError(5, 'Invalid PumpFun params');
1453
+ return buildPumpFunSellInstructions({
1454
+ payer: this.payer.publicKey,
1455
+ inputMint: params.mint,
1456
+ inputAmount: inputAmt,
1457
+ slippageBasisPoints: slippage,
1458
+ fixedOutputAmount:
1459
+ params.fixedOutputTokenAmount !== undefined
1460
+ ? BigInt(params.fixedOutputTokenAmount)
1461
+ : undefined,
1462
+ closeInputMintAta: params.closeMintTokenAta ?? false,
1463
+ createOutputMintAta: params.createOutputTokenAta ?? false,
1464
+ protocolParams: mapPumpFunParams(ext.params),
1465
+ usePumpFunV2: this._config.usePumpfunV2 ?? ext.params.useV2Ix ?? false,
1466
+ });
1467
+ }
1468
+ case DexType.PumpSwap: {
1469
+ if (ext.type !== 'PumpSwap') throw new TradeError(5, 'Invalid PumpSwap params');
1470
+ const p = ext.params;
1471
+ const protocolParams: PumpSwapBuilderParams = {
1472
+ pool: p.pool,
1473
+ baseMint: p.baseMint,
1474
+ quoteMint: p.quoteMint,
1475
+ poolBaseTokenAccount: p.poolBaseTokenAccount,
1476
+ poolQuoteTokenAccount: p.poolQuoteTokenAccount,
1477
+ poolBaseTokenReserves: BigInt(p.poolBaseTokenReserves),
1478
+ poolQuoteTokenReserves: BigInt(p.poolQuoteTokenReserves),
1479
+ coinCreatorVaultAta: p.coinCreatorVaultAta,
1480
+ coinCreatorVaultAuthority: p.coinCreatorVaultAuthority,
1481
+ baseTokenProgram: p.baseTokenProgram,
1482
+ quoteTokenProgram: p.quoteTokenProgram,
1483
+ isMayhemMode: p.isMayhemMode,
1484
+ isCashbackCoin: p.isCashbackCoin,
1485
+ };
1486
+ return buildPumpSwapSellInstructions({
1487
+ payer: this.payer.publicKey,
1488
+ inputAmount: inputAmt,
1489
+ slippageBasisPoints: slippage,
1490
+ protocolParams,
1491
+ createOutputMintAta: params.createOutputTokenAta ?? false,
1492
+ closeOutputMintAta: params.closeOutputTokenAta ?? false,
1493
+ closeInputMintAta: params.closeMintTokenAta ?? false,
1494
+ fixedOutputAmount:
1495
+ params.fixedOutputTokenAmount !== undefined
1496
+ ? BigInt(params.fixedOutputTokenAmount)
1497
+ : undefined,
1498
+ });
1499
+ }
1500
+ case DexType.Bonk: {
1501
+ if (ext.type !== 'Bonk') throw new TradeError(5, 'Invalid Bonk params');
1502
+ const p = ext.params;
1503
+ const protocolParams: BonkBuilderParams = {
1504
+ poolState: p.poolState,
1505
+ baseVault: p.baseVault,
1506
+ quoteVault: p.quoteVault,
1507
+ virtualBase: BigInt(p.virtualBase),
1508
+ virtualQuote: BigInt(p.virtualQuote),
1509
+ realBase: BigInt(p.realBase),
1510
+ realQuote: BigInt(p.realQuote),
1511
+ mintTokenProgram: p.mintTokenProgram,
1512
+ platformConfig: p.platformConfig,
1513
+ platformAssociatedAccount: p.platformAssociatedAccount,
1514
+ creatorAssociatedAccount: p.creatorAssociatedAccount,
1515
+ globalConfig: p.globalConfig,
1516
+ };
1517
+ return buildBonkSellInstructions({
1518
+ payer: this.payer.publicKey,
1519
+ inputMint: params.mint,
1520
+ inputAmount: inputAmt,
1521
+ slippageBasisPoints: slippage,
1522
+ fixedOutputAmount:
1523
+ params.fixedOutputTokenAmount !== undefined
1524
+ ? BigInt(params.fixedOutputTokenAmount)
1525
+ : undefined,
1526
+ createOutputMintAta: params.createOutputTokenAta ?? false,
1527
+ closeOutputMintAta: params.closeOutputTokenAta ?? false,
1528
+ closeInputMintAta: params.closeMintTokenAta ?? false,
1529
+ protocolParams,
1530
+ });
1531
+ }
1532
+ case DexType.RaydiumCpmm: {
1533
+ if (ext.type !== 'RaydiumCpmm') throw new TradeError(5, 'Invalid Raydium CPMM params');
1534
+ const p = ext.params;
1535
+ const protocolParams: RaydiumCpmmBuilderParams = {
1536
+ poolState: p.poolState,
1537
+ ammConfig: p.ammConfig,
1538
+ baseMint: p.baseMint,
1539
+ quoteMint: p.quoteMint,
1540
+ baseTokenProgram: p.baseTokenProgram,
1541
+ quoteTokenProgram: p.quoteTokenProgram,
1542
+ baseVault: p.baseVault,
1543
+ quoteVault: p.quoteVault,
1544
+ baseReserve: BigInt(p.baseReserve),
1545
+ quoteReserve: BigInt(p.quoteReserve),
1546
+ observationState: p.observationState,
1547
+ };
1548
+ return buildRaydiumCpmmSellInstructions({
1549
+ payer: this.payer.publicKey,
1550
+ inputMint: params.mint,
1551
+ inputAmount: inputAmt,
1552
+ slippageBasisPoints: slippage,
1553
+ fixedOutputAmount:
1554
+ params.fixedOutputTokenAmount !== undefined
1555
+ ? BigInt(params.fixedOutputTokenAmount)
1556
+ : undefined,
1557
+ createOutputMintAta: params.createOutputTokenAta ?? false,
1558
+ closeOutputMintAta: params.closeOutputTokenAta ?? false,
1559
+ closeInputMintAta: params.closeMintTokenAta ?? false,
1560
+ protocolParams,
1561
+ });
1562
+ }
1563
+ case DexType.RaydiumAmmV4: {
1564
+ if (ext.type !== 'RaydiumAmmV4') throw new TradeError(5, 'Invalid Raydium AMM V4 params');
1565
+ const p = ext.params;
1566
+ const protocolParams: RaydiumAmmV4BuilderParams = {
1567
+ amm: p.amm,
1568
+ coinMint: p.coinMint,
1569
+ pcMint: p.pcMint,
1570
+ tokenCoin: p.tokenCoin,
1571
+ tokenPc: p.tokenPc,
1572
+ coinReserve: BigInt(p.coinReserve),
1573
+ pcReserve: BigInt(p.pcReserve),
1574
+ };
1575
+ return buildRaydiumAmmV4SellInstructions({
1576
+ payer: this.payer.publicKey,
1577
+ inputMint: params.mint,
1578
+ inputAmount: inputAmt,
1579
+ slippageBasisPoints: slippage,
1580
+ fixedOutputAmount:
1581
+ params.fixedOutputTokenAmount !== undefined
1582
+ ? BigInt(params.fixedOutputTokenAmount)
1583
+ : undefined,
1584
+ createOutputMintAta: params.createOutputTokenAta ?? false,
1585
+ closeOutputMintAta: params.closeOutputTokenAta ?? false,
1586
+ closeInputMintAta: params.closeMintTokenAta ?? false,
1587
+ protocolParams,
1588
+ });
1589
+ }
1590
+ case DexType.MeteoraDammV2: {
1591
+ if (ext.type !== 'MeteoraDammV2') throw new TradeError(5, 'Invalid Meteora params');
1592
+ if (params.fixedOutputTokenAmount === undefined) {
1593
+ throw new TradeError(
1594
+ 8,
1595
+ 'Meteora DAMM V2 requires fixedOutputTokenAmount (builder parity)'
1596
+ );
1597
+ }
1598
+ const p = ext.params;
1599
+ return buildMeteoraDammV2SellInstructions({
1600
+ payer: this.payer.publicKey,
1601
+ inputMint: params.mint,
1602
+ outputMint: this.getOutputMint(params.outputTokenType),
1603
+ inputAmount: inputAmt,
1604
+ fixedOutputAmount: BigInt(params.fixedOutputTokenAmount),
1605
+ createOutputMintAta: params.createOutputTokenAta ?? false,
1606
+ closeOutputMintAta: params.closeOutputTokenAta ?? false,
1607
+ closeInputMintAta: params.closeMintTokenAta ?? false,
1608
+ protocolParams: {
1609
+ pool: p.pool,
1610
+ tokenAVault: p.tokenAVault,
1611
+ tokenBVault: p.tokenBVault,
1612
+ tokenAMint: p.tokenAMint,
1613
+ tokenBMint: p.tokenBMint,
1614
+ tokenAProgram: p.tokenAProgram,
1615
+ tokenBProgram: p.tokenBProgram,
1616
+ },
1617
+ });
1618
+ }
1619
+ default:
1620
+ throw new TradeError(4, `Unsupported DEX type: ${String(params.dexType)}`);
1621
+ }
1622
+ }
1623
+
1624
+ private getInputMint(tokenType: TradeTokenType): PublicKey {
1625
+ switch (tokenType) {
1626
+ case TradeTokenType.SOL:
1627
+ return SDK_CONSTANTS.SOL_TOKEN_ACCOUNT;
1628
+ case TradeTokenType.WSOL:
1629
+ return SDK_CONSTANTS.WSOL_TOKEN_ACCOUNT;
1630
+ case TradeTokenType.USDC:
1631
+ return SDK_CONSTANTS.USDC_TOKEN_ACCOUNT;
1632
+ case TradeTokenType.USD1:
1633
+ return SDK_CONSTANTS.USD1_TOKEN_ACCOUNT;
1634
+ default:
1635
+ throw new TradeError(3, `Unsupported token type: ${tokenType}`);
1636
+ }
1637
+ }
1638
+
1639
+ private getOutputMint(tokenType: TradeTokenType): PublicKey {
1640
+ return this.getInputMint(tokenType);
1641
+ }
1642
+
1643
+ private async processMiddlewares(
1644
+ instructions: TransactionInstruction[],
1645
+ tradeType: TradeType,
1646
+ params: TradeBuyParams | TradeSellParams
1647
+ ): Promise<TransactionInstruction[]> {
1648
+ let result = instructions;
1649
+
1650
+ const inputMint =
1651
+ 'inputTokenType' in params
1652
+ ? this.getInputMint(params.inputTokenType)
1653
+ : params.mint;
1654
+ const outputMint =
1655
+ 'inputTokenType' in params
1656
+ ? params.mint
1657
+ : this.getOutputMint(params.outputTokenType);
1658
+
1659
+ for (const middleware of this.middlewares) {
1660
+ result = await middleware.process(result, {
1661
+ tradeType,
1662
+ inputMint,
1663
+ outputMint,
1664
+ inputAmount: params.inputTokenAmount,
1665
+ payer: this.payer.publicKey,
1666
+ });
1667
+ }
1668
+
1669
+ return result;
1670
+ }
1671
+
1672
+ private async executeTransaction(
1673
+ instructions: TransactionInstruction[],
1674
+ recentBlockhash?: string,
1675
+ lookupTableAccount?: AddressLookupTableAccount,
1676
+ waitConfirmed?: boolean,
1677
+ simulate?: boolean,
1678
+ execCtx?: TxExecContext
1679
+ ): Promise<TradeResult> {
1680
+ const blockhash =
1681
+ recentBlockhash ??
1682
+ execCtx?.durableNonce?.nonceHash ??
1683
+ (await this.connection.getLatestBlockhash(this.rpcCommitment())).blockhash;
1684
+
1685
+ const swqosList = this._config.swqosConfigs ?? [];
1686
+ const tradeType = execCtx?.tradeType ?? TradeType.Buy;
1687
+ const withTip = execCtx?.withTip ?? true;
1688
+
1689
+ const swqosMod =
1690
+ swqosList.length > 0 ? await import('./swqos/clients') : null;
1691
+
1692
+ let effectiveSwqos = swqosList;
1693
+ if (swqosMod && !withTip) {
1694
+ effectiveSwqos = filterSwqosConfigsForWithTip(swqosList, withTip);
1695
+ if (effectiveSwqos.length === 0) {
1696
+ return {
1697
+ success: false,
1698
+ signatures: [],
1699
+ error: new TradeError(
1700
+ 102,
1701
+ 'No Rpc Default Swqos configured when withTip is false'
1702
+ ),
1703
+ timings: [],
1704
+ };
1705
+ }
1706
+ }
1707
+
1708
+ const flatGas = !!(
1709
+ execCtx?.gasFeeStrategy ?? this._config.gasFeeStrategy
1710
+ );
1711
+ /** Rust: per-row `check_min_tip` when using `GasFeeStrategy` table (not flat override). */
1712
+ const useStrategyRowMinTip = !!(
1713
+ this._config.gasStrategy && !flatGas
1714
+ );
1715
+
1716
+ let gasMerged = resolveMergedGas(
1717
+ execCtx?.gasFeeStrategy,
1718
+ this._config.gasFeeStrategy,
1719
+ this._config.gasStrategy,
1720
+ effectiveSwqos[0]?.type
1721
+ );
1722
+
1723
+ if (
1724
+ swqosMod &&
1725
+ (this._config.checkMinTip ?? false) &&
1726
+ withTip &&
1727
+ !useStrategyRowMinTip
1728
+ ) {
1729
+ effectiveSwqos = filterSwqosConfigsByMinTip(
1730
+ swqosMod,
1731
+ effectiveSwqos,
1732
+ tradeType,
1733
+ gasMerged,
1734
+ this._config.mevProtection,
1735
+ this._config.rpcUrl,
1736
+ this._logEnabled
1737
+ );
1738
+ if (effectiveSwqos.length === 0) {
1739
+ return {
1740
+ success: false,
1741
+ signatures: [],
1742
+ error: new TradeError(
1743
+ 103,
1744
+ 'All SWQOS providers filtered out by checkMinTip (tip below minimum)'
1745
+ ),
1746
+ timings: [],
1747
+ };
1748
+ }
1749
+ gasMerged = resolveMergedGas(
1750
+ execCtx?.gasFeeStrategy,
1751
+ this._config.gasFeeStrategy,
1752
+ this._config.gasStrategy,
1753
+ effectiveSwqos[0]?.type
1754
+ );
1755
+ }
1756
+
1757
+ let swqosTasks: SwqosGasTask[] = [];
1758
+ if (swqosMod) {
1759
+ swqosTasks = expandSwqosGasTasks(
1760
+ effectiveSwqos,
1761
+ tradeType,
1762
+ execCtx?.gasFeeStrategy,
1763
+ this._config.gasFeeStrategy,
1764
+ this._config.gasStrategy,
1765
+ useStrategyRowMinTip,
1766
+ this._config.checkMinTip ?? false,
1767
+ withTip,
1768
+ swqosMod,
1769
+ this._config.mevProtection,
1770
+ this._config.rpcUrl,
1771
+ this._logEnabled
1772
+ );
1773
+ if (swqosTasks.length === 0) {
1774
+ return {
1775
+ success: false,
1776
+ signatures: [],
1777
+ error: new TradeError(
1778
+ 103,
1779
+ 'All SWQOS providers filtered out by checkMinTip (tip below minimum)'
1780
+ ),
1781
+ timings: [],
1782
+ };
1783
+ }
1784
+ }
1785
+
1786
+ if (
1787
+ swqosList.length > 0 &&
1788
+ tradeType === TradeType.Buy &&
1789
+ swqosTasks.length > 1 &&
1790
+ !execCtx?.durableNonce
1791
+ ) {
1792
+ return {
1793
+ success: false,
1794
+ signatures: [],
1795
+ error: new TradeError(
1796
+ 104,
1797
+ 'Multiple SWQOS transactions require durable_nonce to be set (Rust SDK parity)'
1798
+ ),
1799
+ timings: [],
1800
+ };
1801
+ }
1802
+
1803
+ const nonceAdvanceIx =
1804
+ execCtx?.durableNonce?.nonceAccount
1805
+ ? SystemProgram.nonceAdvance({
1806
+ noncePubkey: execCtx.durableNonce.nonceAccount,
1807
+ authorizedPubkey: execCtx.durableNonce.authority,
1808
+ })
1809
+ : null;
1810
+
1811
+ const buildWired = (
1812
+ gas: GasFeeStrategyConfig | undefined,
1813
+ tipRecipient: PublicKey | null,
1814
+ addTip: boolean
1815
+ ): TransactionInstruction[] => [
1816
+ ...(nonceAdvanceIx ? [nonceAdvanceIx] : []),
1817
+ ...buildInstructionListWithGasAndTip(
1818
+ instructions,
1819
+ this.payer.publicKey,
1820
+ tradeType,
1821
+ gas,
1822
+ tipRecipient,
1823
+ addTip
1824
+ ),
1825
+ ];
1826
+
1827
+ /** Rust `transaction_builder::build_versioned_transaction`: after full ix assembly, before sign. */
1828
+ const finalizeWiredForSign = (
1829
+ wired: TransactionInstruction[]
1830
+ ): TransactionInstruction[] => {
1831
+ const mgr = this._config.middlewareManager;
1832
+ if (!mgr || execCtx?.dexType === undefined) return wired;
1833
+ return mgr.applyMiddlewaresProcessFullInstructions(
1834
+ wired,
1835
+ String(execCtx.dexType),
1836
+ execCtx.tradeType === TradeType.Buy
1837
+ );
1838
+ };
1839
+
1840
+ if (this._logEnabled && execCtx?.grpcRecvUs != null && typeof performance !== 'undefined') {
1841
+ const nowUs = Math.round(performance.now() * 1000);
1842
+ const deltaUs = nowUs - execCtx.grpcRecvUs;
1843
+ console.info(`[sol-trade-sdk] grpc_recv_us → build tx: ~${deltaUs}µs (approx)`);
1844
+ }
1845
+
1846
+ try {
1847
+ if (simulate) {
1848
+ if (swqosMod && swqosTasks.length > 0) {
1849
+ const t = swqosTasks[0]!;
1850
+ const tipSim = withTip
1851
+ ? resolveTipRecipientPubkey(
1852
+ swqosMod,
1853
+ [t.cfg],
1854
+ this._config.mevProtection,
1855
+ this._config.rpcUrl
1856
+ )
1857
+ : null;
1858
+ const txSim = buildSignedVersionedTransaction(
1859
+ this.payer,
1860
+ finalizeWiredForSign(buildWired(t.gas, tipSim, withTip)),
1861
+ blockhash,
1862
+ lookupTableAccount
1863
+ );
1864
+ const sim = await this.connection.simulateTransaction(
1865
+ txSim,
1866
+ RUST_PARITY_SIMULATE_CONFIG
1867
+ );
1868
+ const err = sim.value.err;
1869
+ return {
1870
+ success: err === null,
1871
+ signatures: [],
1872
+ error: err
1873
+ ? new TradeError(101, `Simulation failed: ${JSON.stringify(err)}`)
1874
+ : undefined,
1875
+ timings: [],
1876
+ simulation: {
1877
+ unitsConsumed: sim.value.unitsConsumed,
1878
+ logs: sim.value.logs ?? null,
1879
+ },
1880
+ };
1881
+ }
1882
+ const gSim = resolveMergedGas(
1883
+ execCtx?.gasFeeStrategy,
1884
+ this._config.gasFeeStrategy,
1885
+ this._config.gasStrategy,
1886
+ undefined
1887
+ );
1888
+ const txSim = buildSignedVersionedTransaction(
1889
+ this.payer,
1890
+ finalizeWiredForSign(buildWired(gSim, null, false)),
1891
+ blockhash,
1892
+ lookupTableAccount
1893
+ );
1894
+ const sim = await this.connection.simulateTransaction(
1895
+ txSim,
1896
+ RUST_PARITY_SIMULATE_CONFIG
1897
+ );
1898
+ const err = sim.value.err;
1899
+ return {
1900
+ success: err === null,
1901
+ signatures: [],
1902
+ error: err
1903
+ ? new TradeError(101, `Simulation failed: ${JSON.stringify(err)}`)
1904
+ : undefined,
1905
+ timings: [],
1906
+ simulation: {
1907
+ unitsConsumed: sim.value.unitsConsumed,
1908
+ logs: sim.value.logs ?? null,
1909
+ },
1910
+ };
1911
+ }
1912
+
1913
+ if (swqosMod) {
1914
+ const timings: SwqosTiming[] = [];
1915
+ const signatures: string[] = [];
1916
+ const submitConcurrency =
1917
+ this._config.maxSwqosSubmitConcurrency ?? swqosTasks.length;
1918
+ const results = await mapWithConcurrencyLimit(
1919
+ swqosTasks,
1920
+ submitConcurrency,
1921
+ async (task) => {
1922
+ const t0 = performance.now();
1923
+ const tipPk = withTip
1924
+ ? resolveTipRecipientPubkey(
1925
+ swqosMod,
1926
+ [task.cfg],
1927
+ this._config.mevProtection,
1928
+ this._config.rpcUrl
1929
+ )
1930
+ : null;
1931
+ const tx = buildSignedVersionedTransaction(
1932
+ this.payer,
1933
+ finalizeWiredForSign(buildWired(task.gas, tipPk, withTip)),
1934
+ blockhash,
1935
+ lookupTableAccount
1936
+ );
1937
+ const raw = Buffer.from(tx.serialize());
1938
+ const client = swqosMod.ClientFactory.createClient(
1939
+ mapSwqosToClientConfig(task.cfg, this._config.mevProtection),
1940
+ this._config.rpcUrl
1941
+ );
1942
+ try {
1943
+ const pending = client.sendTransaction(tradeType, raw, false);
1944
+ const sig = await (!waitConfirmed
1945
+ ? Promise.race([
1946
+ pending,
1947
+ new Promise<never>((_, reject) =>
1948
+ setTimeout(
1949
+ () =>
1950
+ reject(
1951
+ new Error(
1952
+ `SWQOS submit timed out after ${SWQOS_SUBMIT_TIMEOUT_MS_WHEN_NO_CONFIRM}ms`
1953
+ )
1954
+ ),
1955
+ SWQOS_SUBMIT_TIMEOUT_MS_WHEN_NO_CONFIRM
1956
+ )
1957
+ ),
1958
+ ])
1959
+ : pending);
1960
+ const duration = Math.round((performance.now() - t0) * 1000);
1961
+ return { ok: true as const, sig, task, duration };
1962
+ } catch (e) {
1963
+ const duration = Math.round((performance.now() - t0) * 1000);
1964
+ return { ok: false as const, err: e, task, duration };
1965
+ }
1966
+ }
1967
+ );
1968
+
1969
+ for (const v of results) {
1970
+ timings.push({
1971
+ swqosType: v.task.cfg.type,
1972
+ duration: v.duration,
1973
+ gasFeeStrategyType: v.task.strategyType,
1974
+ });
1975
+ if (v.ok) {
1976
+ signatures.push(v.sig);
1977
+ }
1978
+ }
1979
+
1980
+ const success = signatures.length > 0;
1981
+ if (success && waitConfirmed && signatures.length > 0) {
1982
+ await confirmAnyTransactionSignature(this.connection, signatures, {
1983
+ commitment: this._config.commitment ?? 'confirmed',
1984
+ });
1985
+ }
1986
+
1987
+ return {
1988
+ success,
1989
+ signatures: success ? signatures : [],
1990
+ error: success
1991
+ ? undefined
1992
+ : new TradeError(100, 'All SWQOS submissions failed'),
1993
+ timings,
1994
+ };
1995
+ }
1996
+
1997
+ const gRpc = resolveMergedGas(
1998
+ execCtx?.gasFeeStrategy,
1999
+ this._config.gasFeeStrategy,
2000
+ this._config.gasStrategy,
2001
+ undefined
2002
+ );
2003
+ const txRpc = buildSignedVersionedTransaction(
2004
+ this.payer,
2005
+ finalizeWiredForSign(buildWired(gRpc, null, false)),
2006
+ blockhash,
2007
+ lookupTableAccount
2008
+ );
2009
+ const signature = await this.connection.sendRawTransaction(
2010
+ Buffer.from(txRpc.serialize())
2011
+ );
2012
+
2013
+ if (waitConfirmed) {
2014
+ await this.connection.confirmTransaction(
2015
+ signature,
2016
+ this._config.commitment ?? 'confirmed'
2017
+ );
2018
+ }
2019
+
2020
+ return {
2021
+ success: true,
2022
+ signatures: [signature],
2023
+ timings: [],
2024
+ };
2025
+ } catch (error) {
2026
+ if (error instanceof TradeError) {
2027
+ return {
2028
+ success: false,
2029
+ signatures: [],
2030
+ error,
2031
+ timings: [],
2032
+ };
2033
+ }
2034
+ return {
2035
+ success: false,
2036
+ signatures: [],
2037
+ error: new TradeError(100, 'Transaction failed', error as Error),
2038
+ timings: [],
2039
+ };
2040
+ }
2041
+ }
2042
+ }
2043
+
2044
+ /**
2045
+ * Create a new gas fee strategy with defaults
2046
+ */
2047
+ export function createGasFeeStrategy(): GasFeeStrategyClass {
2048
+ return new GasFeeStrategyClass();
2049
+ }
2050
+
2051
+ /**
2052
+ * Create a new trade config (shorthand for {@link TradeConfig} / {@link TradeConfigBuilder}).
2053
+ */
2054
+ export function createTradeConfig(
2055
+ rpcUrl: string,
2056
+ swqosConfigs: SwqosConfig[] = [],
2057
+ options?: Partial<Omit<TradeConfig, 'rpcUrl' | 'swqosConfigs'>>
2058
+ ): TradeConfig {
2059
+ const { commitment, logEnabled, ...rest } = options ?? {};
2060
+ return {
2061
+ rpcUrl,
2062
+ swqosConfigs,
2063
+ ...rest,
2064
+ commitment: commitment ?? 'confirmed',
2065
+ logEnabled: logEnabled ?? true,
2066
+ };
2067
+ }
2068
+
2069
+ // Re-export utilities
2070
+ export * from './constants';
2071
+ export * from './instruction';
2072
+ export * from './params';
2073
+ export * from './utils';
2074
+
2075
+ // Re-export hotpath module
2076
+ export * from './hotpath';
2077
+
2078
+ // Re-export ultra-low-latency/perf helpers without colliding with hotpath symbols.
2079
+ export * as perf from './perf';
2080
+
2081
+ // Re-export gas fee strategy class
2082
+ export { GasFeeStrategy, GasFeeStrategyType } from './common/gas-fee-strategy';
2083
+ export type { GasFeeStrategyValue } from './common/gas-fee-strategy';
2084
+
2085
+ // Rust `trading/core/execution` parity (preprocess, prefetch, size estimate)
2086
+ export {
2087
+ InstructionProcessor,
2088
+ Prefetch,
2089
+ ExecutionPath,
2090
+ MAX_INSTRUCTIONS_WARN,
2091
+ BYTES_PER_ACCOUNT,
2092
+ } from './execution/execution';
2093
+
2094
+ export {
2095
+ confirmAnyTransactionSignature,
2096
+ commitmentToGetTxFinality,
2097
+ extractHintsFromLogs,
2098
+ instructionErrorCodeFromMetaErr,
2099
+ type ConfirmAnySignatureOptions,
2100
+ } from './common/confirm-any-signature';
2101
+ export { mapWithConcurrencyLimit } from './common/map-pool';
2102
+
2103
+ // Durable nonce (Rust `nonce_cache`)
2104
+ export { fetchDurableNonceInfo } from './common/nonce';
2105
+ export type { FetchedDurableNonce } from './common/nonce';
2106
+
2107
+ // Re-export security module
2108
+ export * from './security';
2109
+
2110
+ // Re-export address lookup module
2111
+ export * from './address-lookup';
2112
+
2113
+ // Re-export trading factory
2114
+ export * from './trading/factory';
2115
+
2116
+ // Re-export middleware module
2117
+ export * from './middleware/traits';