sol-trade-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/dist/chunk-MMQAMIKR.mjs +3735 -0
- package/dist/chunk-NEZDFAYA.mjs +7744 -0
- package/dist/clients-VITWK7B6.mjs +1370 -0
- package/dist/index-1BK_FXsW.d.mts +2327 -0
- package/dist/index-1BK_FXsW.d.ts +2327 -0
- package/dist/index.d.mts +2659 -0
- package/dist/index.d.ts +2659 -0
- package/dist/index.js +13265 -0
- package/dist/index.mjs +562 -0
- package/dist/perf/index.d.mts +2 -0
- package/dist/perf/index.d.ts +2 -0
- package/dist/perf/index.js +3742 -0
- package/dist/perf/index.mjs +214 -0
- package/package.json +101 -0
- package/src/__tests__/complete_sdk.test.ts +354 -0
- package/src/__tests__/hotpath.test.ts +486 -0
- package/src/__tests__/nonce.test.ts +45 -0
- package/src/__tests__/sdk.test.ts +425 -0
- package/src/address-lookup/index.ts +197 -0
- package/src/cache/cache.ts +308 -0
- package/src/calc/index.ts +1058 -0
- package/src/calc/pumpfun.ts +124 -0
- package/src/common/bonding_curve.ts +272 -0
- package/src/common/compute-budget.ts +148 -0
- package/src/common/confirm-any-signature.ts +184 -0
- package/src/common/fast-timing.ts +481 -0
- package/src/common/fast_fn.ts +150 -0
- package/src/common/gas-fee-strategy.ts +253 -0
- package/src/common/map-pool.ts +23 -0
- package/src/common/nonce.ts +40 -0
- package/src/common/sdk-log.ts +460 -0
- package/src/common/seed.ts +381 -0
- package/src/common/spl-token.ts +578 -0
- package/src/common/subscription-handle.ts +644 -0
- package/src/common/trading-utils.ts +239 -0
- package/src/common/wsol-manager.ts +325 -0
- package/src/compute/compute_budget_manager.ts +187 -0
- package/src/compute/index.ts +21 -0
- package/src/constants/index.ts +96 -0
- package/src/execution/execution.ts +532 -0
- package/src/execution/index.ts +42 -0
- package/src/hotpath/executor.ts +464 -0
- package/src/hotpath/index.ts +64 -0
- package/src/hotpath/state.ts +435 -0
- package/src/index.ts +2117 -0
- package/src/instruction/bonk_builder.ts +730 -0
- package/src/instruction/index.ts +24 -0
- package/src/instruction/meteora_damm_v2_builder.ts +509 -0
- package/src/instruction/pumpfun_builder.ts +1183 -0
- package/src/instruction/pumpswap.ts +1123 -0
- package/src/instruction/raydium_amm_v4_builder.ts +692 -0
- package/src/instruction/raydium_cpmm_builder.ts +795 -0
- package/src/middleware/traits.ts +407 -0
- package/src/params/index.ts +483 -0
- package/src/perf/compiler-optimization.ts +529 -0
- package/src/perf/hardware.ts +631 -0
- package/src/perf/index.ts +9 -0
- package/src/perf/kernel-bypass.ts +656 -0
- package/src/perf/protocol.ts +682 -0
- package/src/perf/realtime.ts +592 -0
- package/src/perf/simd.ts +668 -0
- package/src/perf/syscall-bypass.ts +331 -0
- package/src/perf/ultra-low-latency.ts +505 -0
- package/src/perf/zero-copy.ts +589 -0
- package/src/pool/pool.ts +294 -0
- package/src/rpc/client.ts +345 -0
- package/src/sdk-errors.ts +13 -0
- package/src/security/index.ts +26 -0
- package/src/security/secure-key.ts +303 -0
- package/src/security/validators.ts +281 -0
- package/src/seed/pda.ts +262 -0
- package/src/serialization/index.ts +28 -0
- package/src/serialization/serialization.ts +288 -0
- package/src/swqos/clients.ts +1754 -0
- package/src/swqos/index.ts +50 -0
- package/src/swqos/providers.ts +1707 -0
- package/src/trading/core/async-executor.ts +702 -0
- package/src/trading/core/confirmation-monitor.ts +711 -0
- package/src/trading/core/index.ts +82 -0
- package/src/trading/core/retry-handler.ts +683 -0
- package/src/trading/core/transaction-pool.ts +780 -0
- package/src/trading/executor.ts +385 -0
- package/src/trading/factory.ts +282 -0
- package/src/trading/index.ts +30 -0
- package/src/types.ts +8 -0
- package/src/utils/index.ts +155 -0
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';
|