sol-trade-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/dist/chunk-MMQAMIKR.mjs +3735 -0
- package/dist/chunk-NEZDFAYA.mjs +7744 -0
- package/dist/clients-VITWK7B6.mjs +1370 -0
- package/dist/index-1BK_FXsW.d.mts +2327 -0
- package/dist/index-1BK_FXsW.d.ts +2327 -0
- package/dist/index.d.mts +2659 -0
- package/dist/index.d.ts +2659 -0
- package/dist/index.js +13265 -0
- package/dist/index.mjs +562 -0
- package/dist/perf/index.d.mts +2 -0
- package/dist/perf/index.d.ts +2 -0
- package/dist/perf/index.js +3742 -0
- package/dist/perf/index.mjs +214 -0
- package/package.json +101 -0
- package/src/__tests__/complete_sdk.test.ts +354 -0
- package/src/__tests__/hotpath.test.ts +486 -0
- package/src/__tests__/nonce.test.ts +45 -0
- package/src/__tests__/sdk.test.ts +425 -0
- package/src/address-lookup/index.ts +197 -0
- package/src/cache/cache.ts +308 -0
- package/src/calc/index.ts +1058 -0
- package/src/calc/pumpfun.ts +124 -0
- package/src/common/bonding_curve.ts +272 -0
- package/src/common/compute-budget.ts +148 -0
- package/src/common/confirm-any-signature.ts +184 -0
- package/src/common/fast-timing.ts +481 -0
- package/src/common/fast_fn.ts +150 -0
- package/src/common/gas-fee-strategy.ts +253 -0
- package/src/common/map-pool.ts +23 -0
- package/src/common/nonce.ts +40 -0
- package/src/common/sdk-log.ts +460 -0
- package/src/common/seed.ts +381 -0
- package/src/common/spl-token.ts +578 -0
- package/src/common/subscription-handle.ts +644 -0
- package/src/common/trading-utils.ts +239 -0
- package/src/common/wsol-manager.ts +325 -0
- package/src/compute/compute_budget_manager.ts +187 -0
- package/src/compute/index.ts +21 -0
- package/src/constants/index.ts +96 -0
- package/src/execution/execution.ts +532 -0
- package/src/execution/index.ts +42 -0
- package/src/hotpath/executor.ts +464 -0
- package/src/hotpath/index.ts +64 -0
- package/src/hotpath/state.ts +435 -0
- package/src/index.ts +2117 -0
- package/src/instruction/bonk_builder.ts +730 -0
- package/src/instruction/index.ts +24 -0
- package/src/instruction/meteora_damm_v2_builder.ts +509 -0
- package/src/instruction/pumpfun_builder.ts +1183 -0
- package/src/instruction/pumpswap.ts +1123 -0
- package/src/instruction/raydium_amm_v4_builder.ts +692 -0
- package/src/instruction/raydium_cpmm_builder.ts +795 -0
- package/src/middleware/traits.ts +407 -0
- package/src/params/index.ts +483 -0
- package/src/perf/compiler-optimization.ts +529 -0
- package/src/perf/hardware.ts +631 -0
- package/src/perf/index.ts +9 -0
- package/src/perf/kernel-bypass.ts +656 -0
- package/src/perf/protocol.ts +682 -0
- package/src/perf/realtime.ts +592 -0
- package/src/perf/simd.ts +668 -0
- package/src/perf/syscall-bypass.ts +331 -0
- package/src/perf/ultra-low-latency.ts +505 -0
- package/src/perf/zero-copy.ts +589 -0
- package/src/pool/pool.ts +294 -0
- package/src/rpc/client.ts +345 -0
- package/src/sdk-errors.ts +13 -0
- package/src/security/index.ts +26 -0
- package/src/security/secure-key.ts +303 -0
- package/src/security/validators.ts +281 -0
- package/src/seed/pda.ts +262 -0
- package/src/serialization/index.ts +28 -0
- package/src/serialization/serialization.ts +288 -0
- package/src/swqos/clients.ts +1754 -0
- package/src/swqos/index.ts +50 -0
- package/src/swqos/providers.ts +1707 -0
- package/src/trading/core/async-executor.ts +702 -0
- package/src/trading/core/confirmation-monitor.ts +711 -0
- package/src/trading/core/index.ts +82 -0
- package/src/trading/core/retry-handler.ts +683 -0
- package/src/trading/core/transaction-pool.ts +780 -0
- package/src/trading/executor.ts +385 -0
- package/src/trading/factory.ts +282 -0
- package/src/trading/index.ts +30 -0
- package/src/types.ts +8 -0
- package/src/utils/index.ts +155 -0
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PumpSwap instruction builder - Production-grade implementation
|
|
3
|
+
* 100% port from Rust sol-trade-sdk
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
PublicKey,
|
|
8
|
+
TransactionInstruction,
|
|
9
|
+
SystemProgram,
|
|
10
|
+
SYSVAR_RENT_PUBKEY,
|
|
11
|
+
} from '@solana/web3.js';
|
|
12
|
+
import {
|
|
13
|
+
TOKEN_PROGRAM,
|
|
14
|
+
TOKEN_PROGRAM_2022,
|
|
15
|
+
ASSOCIATED_TOKEN_PROGRAM,
|
|
16
|
+
WSOL_TOKEN_ACCOUNT,
|
|
17
|
+
USDC_TOKEN_ACCOUNT,
|
|
18
|
+
} from '../constants';
|
|
19
|
+
import {
|
|
20
|
+
calculateWithSlippageBuy,
|
|
21
|
+
calculateWithSlippageSell,
|
|
22
|
+
ceilDiv,
|
|
23
|
+
computeFee,
|
|
24
|
+
buyQuoteInputInternal,
|
|
25
|
+
sellBaseInputInternal,
|
|
26
|
+
PUMPSWAP_CONSTANTS,
|
|
27
|
+
} from '../calc';
|
|
28
|
+
|
|
29
|
+
// ===== Constants from Rust: src/instruction/utils/pumpswap.rs =====
|
|
30
|
+
|
|
31
|
+
export const PUMPSWAP_PROGRAM = new PublicKey('pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA');
|
|
32
|
+
export const PUMPSWAP_PUMP_PROGRAM_ID = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P');
|
|
33
|
+
export const PUMPSWAP_FEE_PROGRAM = new PublicKey('pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ');
|
|
34
|
+
|
|
35
|
+
// Accounts
|
|
36
|
+
export const PUMPSWAP_FEE_RECIPIENT = new PublicKey('62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV');
|
|
37
|
+
export const PUMPSWAP_GLOBAL_ACCOUNT = new PublicKey('ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw');
|
|
38
|
+
export const PUMPSWAP_EVENT_AUTHORITY = new PublicKey('GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR');
|
|
39
|
+
export const PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR = new PublicKey('C2aFPdENg4A2HQsmrd5rTw5TaYBX5Ku887cWjbFKtZpw');
|
|
40
|
+
export const PUMPSWAP_FEE_CONFIG = new PublicKey('5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx');
|
|
41
|
+
export const PUMPSWAP_DEFAULT_COIN_CREATOR_VAULT_AUTHORITY = new PublicKey('8N3GDaZ2iwN65oxVatKTLPNooAVUJTbfiVJ1ahyqwjSk');
|
|
42
|
+
|
|
43
|
+
// Mayhem fee recipients (use any one randomly)
|
|
44
|
+
export const PUMPSWAP_MAYHEM_FEE_RECIPIENTS: PublicKey[] = [
|
|
45
|
+
new PublicKey('GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS'),
|
|
46
|
+
new PublicKey('4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6'),
|
|
47
|
+
new PublicKey('8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR'),
|
|
48
|
+
new PublicKey('4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH'),
|
|
49
|
+
new PublicKey('8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6'),
|
|
50
|
+
new PublicKey('Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk'),
|
|
51
|
+
new PublicKey('463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq'),
|
|
52
|
+
new PublicKey('6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA'),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** Protocol extra fee recipients (Apr 2026); after pool-v2: readonly, then quote ATA (mutable). */
|
|
56
|
+
export const PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS: PublicKey[] = [
|
|
57
|
+
new PublicKey('5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD'),
|
|
58
|
+
new PublicKey('9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7'),
|
|
59
|
+
new PublicKey('GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL'),
|
|
60
|
+
new PublicKey('3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR'),
|
|
61
|
+
new PublicKey('5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6'),
|
|
62
|
+
new PublicKey('EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL'),
|
|
63
|
+
new PublicKey('5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD'),
|
|
64
|
+
new PublicKey('A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW'),
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Discriminators
|
|
68
|
+
export const PUMPSWAP_BUY_DISCRIMINATOR = Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]);
|
|
69
|
+
export const PUMPSWAP_BUY_EXACT_QUOTE_IN_DISCRIMINATOR = Buffer.from([198, 46, 21, 82, 180, 217, 232, 112]);
|
|
70
|
+
export const PUMPSWAP_SELL_DISCRIMINATOR = Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]);
|
|
71
|
+
export const PUMPSWAP_CLAIM_CASHBACK_DISCRIMINATOR = Buffer.from([37, 58, 35, 126, 190, 53, 228, 197]);
|
|
72
|
+
|
|
73
|
+
// Seeds
|
|
74
|
+
const POOL_V2_SEED = Buffer.from('pool-v2');
|
|
75
|
+
const POOL_SEED = Buffer.from('pool');
|
|
76
|
+
const POOL_AUTHORITY_SEED = Buffer.from('pool-authority');
|
|
77
|
+
const USER_VOLUME_ACCUMULATOR_SEED = Buffer.from('user_volume_accumulator');
|
|
78
|
+
const CREATOR_VAULT_SEED = Buffer.from('creator_vault');
|
|
79
|
+
const FEE_CONFIG_SEED = Buffer.from('fee_config');
|
|
80
|
+
const GLOBAL_VOLUME_ACCUMULATOR_SEED = Buffer.from('global_volume_accumulator');
|
|
81
|
+
|
|
82
|
+
// ===== PDA Derivation Functions =====
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get a random Mayhem fee recipient
|
|
86
|
+
*/
|
|
87
|
+
export function getMayhemFeeRecipientRandom(): PublicKey {
|
|
88
|
+
const index = Math.floor(Math.random() * PUMPSWAP_MAYHEM_FEE_RECIPIENTS.length);
|
|
89
|
+
const recipient = PUMPSWAP_MAYHEM_FEE_RECIPIENTS[index];
|
|
90
|
+
if (!recipient) {
|
|
91
|
+
return PUMPSWAP_MAYHEM_FEE_RECIPIENTS[0]!;
|
|
92
|
+
}
|
|
93
|
+
return recipient;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getPumpSwapProtocolExtraFeeRecipientRandom(): PublicKey {
|
|
97
|
+
const index = Math.floor(Math.random() * PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS.length);
|
|
98
|
+
return PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS[index] ?? PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS[0]!;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Pool v2 PDA (seeds: ["pool-v2", base_mint])
|
|
103
|
+
*/
|
|
104
|
+
export function getPoolV2PDA(baseMint: PublicKey): PublicKey {
|
|
105
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
106
|
+
[POOL_V2_SEED, baseMint.toBuffer()],
|
|
107
|
+
PUMPSWAP_PROGRAM
|
|
108
|
+
);
|
|
109
|
+
return pda;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Pump program pool-authority PDA (for canonical pool)
|
|
114
|
+
*/
|
|
115
|
+
export function getPumpPoolAuthorityPDA(mint: PublicKey): PublicKey {
|
|
116
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
117
|
+
[POOL_AUTHORITY_SEED, mint.toBuffer()],
|
|
118
|
+
PUMPSWAP_PUMP_PROGRAM_ID
|
|
119
|
+
);
|
|
120
|
+
return pda;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Canonical Pump pool PDA
|
|
125
|
+
*/
|
|
126
|
+
export function getCanonicalPoolPDA(mint: PublicKey): PublicKey {
|
|
127
|
+
const authority = getPumpPoolAuthorityPDA(mint);
|
|
128
|
+
const index = Buffer.alloc(2);
|
|
129
|
+
index.writeUInt16LE(0);
|
|
130
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
131
|
+
[POOL_SEED, index, authority.toBuffer(), mint.toBuffer(), WSOL_TOKEN_ACCOUNT.toBuffer()],
|
|
132
|
+
PUMPSWAP_PROGRAM
|
|
133
|
+
);
|
|
134
|
+
return pda;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Coin creator vault authority PDA
|
|
139
|
+
*/
|
|
140
|
+
export function getCoinCreatorVaultAuthority(coinCreator: PublicKey): PublicKey {
|
|
141
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
142
|
+
[CREATOR_VAULT_SEED, coinCreator.toBuffer()],
|
|
143
|
+
PUMPSWAP_PROGRAM
|
|
144
|
+
);
|
|
145
|
+
return pda;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Coin creator vault ATA
|
|
150
|
+
*/
|
|
151
|
+
export function getCoinCreatorVaultAta(coinCreator: PublicKey, quoteMint: PublicKey): PublicKey {
|
|
152
|
+
const authority = getCoinCreatorVaultAuthority(coinCreator);
|
|
153
|
+
return getAssociatedTokenAddress(authority, quoteMint, TOKEN_PROGRAM);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Fee recipient ATA
|
|
158
|
+
*/
|
|
159
|
+
export function getFeeRecipientAta(feeRecipient: PublicKey, quoteMint: PublicKey): PublicKey {
|
|
160
|
+
return getAssociatedTokenAddress(feeRecipient, quoteMint, TOKEN_PROGRAM);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* User volume accumulator PDA
|
|
165
|
+
*/
|
|
166
|
+
export function getUserVolumeAccumulatorPDA(user: PublicKey): PublicKey {
|
|
167
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
168
|
+
[USER_VOLUME_ACCUMULATOR_SEED, user.toBuffer()],
|
|
169
|
+
PUMPSWAP_PROGRAM
|
|
170
|
+
);
|
|
171
|
+
return pda;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* WSOL ATA of UserVolumeAccumulator (for buy cashback)
|
|
176
|
+
*/
|
|
177
|
+
export function getUserVolumeAccumulatorWsolAta(user: PublicKey): PublicKey {
|
|
178
|
+
const accumulator = getUserVolumeAccumulatorPDA(user);
|
|
179
|
+
return getAssociatedTokenAddress(accumulator, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Quote-mint ATA of UserVolumeAccumulator (for sell cashback)
|
|
184
|
+
*/
|
|
185
|
+
export function getUserVolumeAccumulatorQuoteAta(
|
|
186
|
+
user: PublicKey,
|
|
187
|
+
quoteMint: PublicKey,
|
|
188
|
+
quoteTokenProgram: PublicKey
|
|
189
|
+
): PublicKey {
|
|
190
|
+
const accumulator = getUserVolumeAccumulatorPDA(user);
|
|
191
|
+
return getAssociatedTokenAddress(accumulator, quoteMint, quoteTokenProgram);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Global volume accumulator PDA
|
|
196
|
+
* Seeds: ["global_volume_accumulator"], owner: PUMPSWAP_PROGRAM
|
|
197
|
+
*/
|
|
198
|
+
export function getGlobalVolumeAccumulatorPDA(): PublicKey {
|
|
199
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
200
|
+
[GLOBAL_VOLUME_ACCUMULATOR_SEED],
|
|
201
|
+
PUMPSWAP_PROGRAM
|
|
202
|
+
);
|
|
203
|
+
return pda;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get associated token address
|
|
208
|
+
*/
|
|
209
|
+
export function getAssociatedTokenAddress(
|
|
210
|
+
owner: PublicKey,
|
|
211
|
+
mint: PublicKey,
|
|
212
|
+
tokenProgram: PublicKey = TOKEN_PROGRAM
|
|
213
|
+
): PublicKey {
|
|
214
|
+
const [ata] = PublicKey.findProgramAddressSync(
|
|
215
|
+
[owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()],
|
|
216
|
+
ASSOCIATED_TOKEN_PROGRAM
|
|
217
|
+
);
|
|
218
|
+
return ata;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ===== WSOL Manager =====
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create WSOL ATA and wrap SOL
|
|
225
|
+
* Returns instructions for: create ATA (idempotent), transfer SOL, sync_native
|
|
226
|
+
*/
|
|
227
|
+
export function handleWsol(owner: PublicKey, amount: bigint): TransactionInstruction[] {
|
|
228
|
+
const wsolAta = getAssociatedTokenAddress(owner, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM);
|
|
229
|
+
const instructions: TransactionInstruction[] = [];
|
|
230
|
+
|
|
231
|
+
// Create ATA (idempotent)
|
|
232
|
+
instructions.push(
|
|
233
|
+
createAssociatedTokenAccountIdempotent(owner, owner, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// Transfer SOL to WSOL ATA
|
|
237
|
+
instructions.push(
|
|
238
|
+
SystemProgram.transfer({
|
|
239
|
+
fromPubkey: owner,
|
|
240
|
+
toPubkey: wsolAta,
|
|
241
|
+
lamports: Number(amount),
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Sync native
|
|
246
|
+
instructions.push(
|
|
247
|
+
new TransactionInstruction({
|
|
248
|
+
keys: [{ pubkey: wsolAta, isSigner: false, isWritable: true }],
|
|
249
|
+
programId: TOKEN_PROGRAM,
|
|
250
|
+
data: Buffer.from([17]), // sync_native discriminator
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return instructions;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Close WSOL ATA and reclaim rent
|
|
259
|
+
*/
|
|
260
|
+
export function closeWsol(owner: PublicKey): TransactionInstruction {
|
|
261
|
+
const wsolAta = getAssociatedTokenAddress(owner, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM);
|
|
262
|
+
return new TransactionInstruction({
|
|
263
|
+
keys: [
|
|
264
|
+
{ pubkey: wsolAta, isSigner: false, isWritable: true },
|
|
265
|
+
{ pubkey: owner, isSigner: false, isWritable: true },
|
|
266
|
+
{ pubkey: owner, isSigner: true, isWritable: false },
|
|
267
|
+
],
|
|
268
|
+
programId: TOKEN_PROGRAM,
|
|
269
|
+
data: Buffer.from([9, 0, 0, 0, 0, 0, 0, 0]), // close_account discriminator
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Create associated token account idempotent
|
|
275
|
+
*/
|
|
276
|
+
export function createAssociatedTokenAccountIdempotent(
|
|
277
|
+
payer: PublicKey,
|
|
278
|
+
owner: PublicKey,
|
|
279
|
+
mint: PublicKey,
|
|
280
|
+
tokenProgram: PublicKey = TOKEN_PROGRAM
|
|
281
|
+
): TransactionInstruction {
|
|
282
|
+
const ata = getAssociatedTokenAddress(owner, mint, tokenProgram);
|
|
283
|
+
|
|
284
|
+
return new TransactionInstruction({
|
|
285
|
+
keys: [
|
|
286
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
287
|
+
{ pubkey: ata, isSigner: false, isWritable: true },
|
|
288
|
+
{ pubkey: owner, isSigner: false, isWritable: false },
|
|
289
|
+
{ pubkey: mint, isSigner: false, isWritable: false },
|
|
290
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
291
|
+
{ pubkey: tokenProgram, isSigner: false, isWritable: false },
|
|
292
|
+
{ pubkey: ASSOCIATED_TOKEN_PROGRAM, isSigner: false, isWritable: false },
|
|
293
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
294
|
+
],
|
|
295
|
+
programId: ASSOCIATED_TOKEN_PROGRAM,
|
|
296
|
+
data: Buffer.from([1]), // Idempotent discriminator
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ===== Params Interface =====
|
|
301
|
+
|
|
302
|
+
export interface PumpSwapParams {
|
|
303
|
+
pool: PublicKey;
|
|
304
|
+
baseMint: PublicKey;
|
|
305
|
+
quoteMint: PublicKey;
|
|
306
|
+
poolBaseTokenAccount: PublicKey;
|
|
307
|
+
poolQuoteTokenAccount: PublicKey;
|
|
308
|
+
poolBaseTokenReserves: bigint;
|
|
309
|
+
poolQuoteTokenReserves: bigint;
|
|
310
|
+
coinCreatorVaultAta: PublicKey;
|
|
311
|
+
coinCreatorVaultAuthority: PublicKey;
|
|
312
|
+
baseTokenProgram: PublicKey;
|
|
313
|
+
quoteTokenProgram: PublicKey;
|
|
314
|
+
isMayhemMode: boolean;
|
|
315
|
+
isCashbackCoin: boolean;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export interface BuildBuyParams {
|
|
319
|
+
payer: PublicKey;
|
|
320
|
+
inputAmount: bigint;
|
|
321
|
+
slippageBasisPoints: bigint;
|
|
322
|
+
protocolParams: PumpSwapParams;
|
|
323
|
+
createInputMintAta?: boolean;
|
|
324
|
+
closeInputMintAta?: boolean;
|
|
325
|
+
createOutputMintAta?: boolean;
|
|
326
|
+
useExactQuoteAmount?: boolean;
|
|
327
|
+
fixedOutputAmount?: bigint;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface BuildSellParams {
|
|
331
|
+
payer: PublicKey;
|
|
332
|
+
inputAmount: bigint;
|
|
333
|
+
slippageBasisPoints: bigint;
|
|
334
|
+
protocolParams: PumpSwapParams;
|
|
335
|
+
createOutputMintAta?: boolean;
|
|
336
|
+
closeOutputMintAta?: boolean;
|
|
337
|
+
closeInputMintAta?: boolean;
|
|
338
|
+
fixedOutputAmount?: bigint;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ===== Instruction Builders =====
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Build buy instructions for PumpSwap
|
|
345
|
+
* 100% port from Rust: src/instruction/pumpswap.rs build_buy_instructions
|
|
346
|
+
*/
|
|
347
|
+
export function buildBuyInstructions(params: BuildBuyParams): TransactionInstruction[] {
|
|
348
|
+
const {
|
|
349
|
+
payer,
|
|
350
|
+
inputAmount,
|
|
351
|
+
slippageBasisPoints,
|
|
352
|
+
protocolParams,
|
|
353
|
+
createInputMintAta = false,
|
|
354
|
+
closeInputMintAta = false,
|
|
355
|
+
createOutputMintAta = true,
|
|
356
|
+
useExactQuoteAmount = true,
|
|
357
|
+
fixedOutputAmount,
|
|
358
|
+
} = params;
|
|
359
|
+
|
|
360
|
+
if (inputAmount === 0n) {
|
|
361
|
+
throw new Error('Amount cannot be zero');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const {
|
|
365
|
+
pool,
|
|
366
|
+
baseMint,
|
|
367
|
+
quoteMint,
|
|
368
|
+
poolBaseTokenAccount,
|
|
369
|
+
poolQuoteTokenAccount,
|
|
370
|
+
poolBaseTokenReserves,
|
|
371
|
+
poolQuoteTokenReserves,
|
|
372
|
+
coinCreatorVaultAta,
|
|
373
|
+
coinCreatorVaultAuthority,
|
|
374
|
+
baseTokenProgram,
|
|
375
|
+
quoteTokenProgram,
|
|
376
|
+
isMayhemMode,
|
|
377
|
+
isCashbackCoin,
|
|
378
|
+
} = protocolParams;
|
|
379
|
+
|
|
380
|
+
// Check if pool contains WSOL or USDC
|
|
381
|
+
const isWsol = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || baseMint.equals(WSOL_TOKEN_ACCOUNT);
|
|
382
|
+
const isUsdc = quoteMint.equals(USDC_TOKEN_ACCOUNT) || baseMint.equals(USDC_TOKEN_ACCOUNT);
|
|
383
|
+
|
|
384
|
+
if (!isWsol && !isUsdc) {
|
|
385
|
+
throw new Error('Pool must contain WSOL or USDC');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const quoteIsWsolOrUsdc = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
|
|
389
|
+
|
|
390
|
+
// Determine creator for fee calculation
|
|
391
|
+
let creator = PublicKey.default;
|
|
392
|
+
if (!coinCreatorVaultAuthority.equals(PUMPSWAP_DEFAULT_COIN_CREATOR_VAULT_AUTHORITY)) {
|
|
393
|
+
creator = coinCreatorVaultAuthority;
|
|
394
|
+
}
|
|
395
|
+
const hasCoinCreator = !creator.equals(PublicKey.default);
|
|
396
|
+
|
|
397
|
+
// Calculate trade amounts
|
|
398
|
+
let tokenAmount: bigint;
|
|
399
|
+
let solAmount: bigint;
|
|
400
|
+
|
|
401
|
+
if (quoteIsWsolOrUsdc) {
|
|
402
|
+
// Buying base with quote (WSOL/USDC)
|
|
403
|
+
const result = buyQuoteInputInternal(
|
|
404
|
+
inputAmount,
|
|
405
|
+
slippageBasisPoints,
|
|
406
|
+
poolBaseTokenReserves,
|
|
407
|
+
poolQuoteTokenReserves,
|
|
408
|
+
hasCoinCreator
|
|
409
|
+
);
|
|
410
|
+
tokenAmount = result.base;
|
|
411
|
+
solAmount = result.maxQuote;
|
|
412
|
+
} else {
|
|
413
|
+
// This would be selling base for quote - shouldn't happen in buy
|
|
414
|
+
throw new Error('Invalid configuration for buy');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Override token amount if fixed output is specified
|
|
418
|
+
if (fixedOutputAmount !== undefined) {
|
|
419
|
+
tokenAmount = fixedOutputAmount;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Get user token accounts
|
|
423
|
+
const userBaseTokenAccount = getAssociatedTokenAddress(payer, baseMint, baseTokenProgram);
|
|
424
|
+
const userQuoteTokenAccount = getAssociatedTokenAddress(payer, quoteMint, quoteTokenProgram);
|
|
425
|
+
|
|
426
|
+
// Determine fee recipient
|
|
427
|
+
const feeRecipient = isMayhemMode ? getMayhemFeeRecipientRandom() : PUMPSWAP_FEE_RECIPIENT;
|
|
428
|
+
const feeRecipientAta = getFeeRecipientAta(feeRecipient, quoteMint);
|
|
429
|
+
|
|
430
|
+
// Build instructions
|
|
431
|
+
const instructions: TransactionInstruction[] = [];
|
|
432
|
+
|
|
433
|
+
// Handle WSOL wrapping if needed
|
|
434
|
+
if (createInputMintAta && quoteIsWsolOrUsdc) {
|
|
435
|
+
// Determine wrap amount based on instruction type:
|
|
436
|
+
// - buy_exact_quote_in: program spends exactly input_amount, wrap input_amount
|
|
437
|
+
// - buy: program may spend up to max_quote, wrap max_quote
|
|
438
|
+
const wrapAmount = useExactQuoteAmount ? inputAmount : solAmount;
|
|
439
|
+
instructions.push(...handleWsol(payer, wrapAmount));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Create output token ATA if needed
|
|
443
|
+
if (createOutputMintAta) {
|
|
444
|
+
instructions.push(
|
|
445
|
+
createAssociatedTokenAccountIdempotent(payer, payer, baseMint, baseTokenProgram)
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Build accounts array
|
|
450
|
+
const accounts = [
|
|
451
|
+
{ pubkey: pool, isSigner: false, isWritable: true },
|
|
452
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
453
|
+
{ pubkey: PUMPSWAP_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
|
|
454
|
+
{ pubkey: baseMint, isSigner: false, isWritable: false },
|
|
455
|
+
{ pubkey: quoteMint, isSigner: false, isWritable: false },
|
|
456
|
+
{ pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
|
|
457
|
+
{ pubkey: userQuoteTokenAccount, isSigner: false, isWritable: true },
|
|
458
|
+
{ pubkey: poolBaseTokenAccount, isSigner: false, isWritable: true },
|
|
459
|
+
{ pubkey: poolQuoteTokenAccount, isSigner: false, isWritable: true },
|
|
460
|
+
{ pubkey: feeRecipient, isSigner: false, isWritable: false },
|
|
461
|
+
{ pubkey: feeRecipientAta, isSigner: false, isWritable: true },
|
|
462
|
+
{ pubkey: baseTokenProgram, isSigner: false, isWritable: false },
|
|
463
|
+
{ pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
|
|
464
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
465
|
+
{ pubkey: ASSOCIATED_TOKEN_PROGRAM, isSigner: false, isWritable: false },
|
|
466
|
+
{ pubkey: PUMPSWAP_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
467
|
+
{ pubkey: PUMPSWAP_PROGRAM, isSigner: false, isWritable: false },
|
|
468
|
+
{ pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
|
|
469
|
+
{ pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
|
|
470
|
+
];
|
|
471
|
+
|
|
472
|
+
// Add volume accumulator accounts for quote (WSOL/USDC) buy
|
|
473
|
+
if (quoteIsWsolOrUsdc) {
|
|
474
|
+
accounts.push(
|
|
475
|
+
{ pubkey: PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true }
|
|
476
|
+
);
|
|
477
|
+
const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
|
|
478
|
+
accounts.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Add fee config and program
|
|
482
|
+
accounts.push(
|
|
483
|
+
{ pubkey: PUMPSWAP_FEE_CONFIG, isSigner: false, isWritable: false },
|
|
484
|
+
{ pubkey: PUMPSWAP_FEE_PROGRAM, isSigner: false, isWritable: false }
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Add cashback WSOL ATA if needed
|
|
488
|
+
if (isCashbackCoin) {
|
|
489
|
+
const wsolAta = getUserVolumeAccumulatorWsolAta(payer);
|
|
490
|
+
accounts.push({ pubkey: wsolAta, isSigner: false, isWritable: true });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Add pool v2 PDA
|
|
494
|
+
const poolV2 = getPoolV2PDA(baseMint);
|
|
495
|
+
accounts.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
496
|
+
const protocolExtraFee = getPumpSwapProtocolExtraFeeRecipientRandom();
|
|
497
|
+
accounts.push({ pubkey: protocolExtraFee, isSigner: false, isWritable: false });
|
|
498
|
+
accounts.push({
|
|
499
|
+
pubkey: getAssociatedTokenAddress(protocolExtraFee, quoteMint, TOKEN_PROGRAM),
|
|
500
|
+
isSigner: false,
|
|
501
|
+
isWritable: true,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Build instruction data
|
|
505
|
+
const trackVolume = isCashbackCoin ? Buffer.from([1, 1]) : Buffer.from([1, 0]);
|
|
506
|
+
let data: Buffer;
|
|
507
|
+
|
|
508
|
+
if (useExactQuoteAmount) {
|
|
509
|
+
// buy_exact_quote_in(spendable_quote_in, min_base_amount_out, track_volume)
|
|
510
|
+
const minBaseAmountOut = calculateWithSlippageSell(tokenAmount, slippageBasisPoints);
|
|
511
|
+
data = Buffer.alloc(26);
|
|
512
|
+
PUMPSWAP_BUY_EXACT_QUOTE_IN_DISCRIMINATOR.copy(data, 0);
|
|
513
|
+
data.writeBigUInt64LE(inputAmount, 8);
|
|
514
|
+
data.writeBigUInt64LE(minBaseAmountOut, 16);
|
|
515
|
+
trackVolume.copy(data, 24);
|
|
516
|
+
} else {
|
|
517
|
+
// buy(token_amount, max_quote, track_volume)
|
|
518
|
+
data = Buffer.alloc(26);
|
|
519
|
+
PUMPSWAP_BUY_DISCRIMINATOR.copy(data, 0);
|
|
520
|
+
data.writeBigUInt64LE(tokenAmount, 8);
|
|
521
|
+
data.writeBigUInt64LE(solAmount, 16);
|
|
522
|
+
trackVolume.copy(data, 24);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
instructions.push(
|
|
526
|
+
new TransactionInstruction({
|
|
527
|
+
keys: accounts,
|
|
528
|
+
programId: PUMPSWAP_PROGRAM,
|
|
529
|
+
data,
|
|
530
|
+
})
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
// Close WSOL ATA if requested
|
|
534
|
+
if (closeInputMintAta) {
|
|
535
|
+
instructions.push(closeWsol(payer));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return instructions;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Build sell instructions for PumpSwap
|
|
543
|
+
* 100% port from Rust: src/instruction/pumpswap.rs build_sell_instructions
|
|
544
|
+
*/
|
|
545
|
+
export function buildSellInstructions(params: BuildSellParams): TransactionInstruction[] {
|
|
546
|
+
const {
|
|
547
|
+
payer,
|
|
548
|
+
inputAmount,
|
|
549
|
+
slippageBasisPoints,
|
|
550
|
+
protocolParams,
|
|
551
|
+
createOutputMintAta = false,
|
|
552
|
+
closeOutputMintAta = false,
|
|
553
|
+
closeInputMintAta = false,
|
|
554
|
+
fixedOutputAmount,
|
|
555
|
+
} = params;
|
|
556
|
+
|
|
557
|
+
if (inputAmount === 0n) {
|
|
558
|
+
throw new Error('Amount cannot be zero');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const {
|
|
562
|
+
pool,
|
|
563
|
+
baseMint,
|
|
564
|
+
quoteMint,
|
|
565
|
+
poolBaseTokenAccount,
|
|
566
|
+
poolQuoteTokenAccount,
|
|
567
|
+
poolBaseTokenReserves,
|
|
568
|
+
poolQuoteTokenReserves,
|
|
569
|
+
coinCreatorVaultAta,
|
|
570
|
+
coinCreatorVaultAuthority,
|
|
571
|
+
baseTokenProgram,
|
|
572
|
+
quoteTokenProgram,
|
|
573
|
+
isMayhemMode,
|
|
574
|
+
isCashbackCoin,
|
|
575
|
+
} = protocolParams;
|
|
576
|
+
|
|
577
|
+
// Check if pool contains WSOL or USDC
|
|
578
|
+
const isWsol = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || baseMint.equals(WSOL_TOKEN_ACCOUNT);
|
|
579
|
+
const isUsdc = quoteMint.equals(USDC_TOKEN_ACCOUNT) || baseMint.equals(USDC_TOKEN_ACCOUNT);
|
|
580
|
+
|
|
581
|
+
if (!isWsol && !isUsdc) {
|
|
582
|
+
throw new Error('Pool must contain WSOL or USDC');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const quoteIsWsolOrUsdc = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
|
|
586
|
+
|
|
587
|
+
// Determine creator for fee calculation
|
|
588
|
+
let creator = PublicKey.default;
|
|
589
|
+
if (!coinCreatorVaultAuthority.equals(PUMPSWAP_DEFAULT_COIN_CREATOR_VAULT_AUTHORITY)) {
|
|
590
|
+
creator = coinCreatorVaultAuthority;
|
|
591
|
+
}
|
|
592
|
+
const hasCoinCreator = !creator.equals(PublicKey.default);
|
|
593
|
+
|
|
594
|
+
// Calculate trade amounts
|
|
595
|
+
let tokenAmount: bigint;
|
|
596
|
+
let solAmount: bigint;
|
|
597
|
+
|
|
598
|
+
if (quoteIsWsolOrUsdc) {
|
|
599
|
+
// Selling base for quote (WSOL/USDC)
|
|
600
|
+
tokenAmount = inputAmount;
|
|
601
|
+
const result = sellBaseInputInternal(
|
|
602
|
+
inputAmount,
|
|
603
|
+
slippageBasisPoints,
|
|
604
|
+
poolBaseTokenReserves,
|
|
605
|
+
poolQuoteTokenReserves,
|
|
606
|
+
hasCoinCreator
|
|
607
|
+
);
|
|
608
|
+
solAmount = result.minQuote;
|
|
609
|
+
} else {
|
|
610
|
+
// Selling quote for base - unusual case
|
|
611
|
+
tokenAmount = inputAmount;
|
|
612
|
+
solAmount = 0n; // Would need different calculation
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Override sol amount if fixed output is specified
|
|
616
|
+
if (fixedOutputAmount !== undefined) {
|
|
617
|
+
solAmount = fixedOutputAmount;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Get user token accounts
|
|
621
|
+
const userBaseTokenAccount = getAssociatedTokenAddress(payer, baseMint, baseTokenProgram);
|
|
622
|
+
const userQuoteTokenAccount = getAssociatedTokenAddress(payer, quoteMint, quoteTokenProgram);
|
|
623
|
+
|
|
624
|
+
// Determine fee recipient
|
|
625
|
+
const feeRecipient = isMayhemMode ? getMayhemFeeRecipientRandom() : PUMPSWAP_FEE_RECIPIENT;
|
|
626
|
+
const feeRecipientAta = getFeeRecipientAta(feeRecipient, quoteMint);
|
|
627
|
+
|
|
628
|
+
// Build instructions
|
|
629
|
+
const instructions: TransactionInstruction[] = [];
|
|
630
|
+
|
|
631
|
+
// Create WSOL/USDC ATA if needed for receiving
|
|
632
|
+
if (createOutputMintAta && quoteIsWsolOrUsdc) {
|
|
633
|
+
instructions.push(
|
|
634
|
+
createAssociatedTokenAccountIdempotent(payer, payer, quoteMint, quoteTokenProgram)
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Build accounts array
|
|
639
|
+
const accounts = [
|
|
640
|
+
{ pubkey: pool, isSigner: false, isWritable: true },
|
|
641
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
642
|
+
{ pubkey: PUMPSWAP_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
|
|
643
|
+
{ pubkey: baseMint, isSigner: false, isWritable: false },
|
|
644
|
+
{ pubkey: quoteMint, isSigner: false, isWritable: false },
|
|
645
|
+
{ pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
|
|
646
|
+
{ pubkey: userQuoteTokenAccount, isSigner: false, isWritable: true },
|
|
647
|
+
{ pubkey: poolBaseTokenAccount, isSigner: false, isWritable: true },
|
|
648
|
+
{ pubkey: poolQuoteTokenAccount, isSigner: false, isWritable: true },
|
|
649
|
+
{ pubkey: feeRecipient, isSigner: false, isWritable: false },
|
|
650
|
+
{ pubkey: feeRecipientAta, isSigner: false, isWritable: true },
|
|
651
|
+
{ pubkey: baseTokenProgram, isSigner: false, isWritable: false },
|
|
652
|
+
{ pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
|
|
653
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
654
|
+
{ pubkey: ASSOCIATED_TOKEN_PROGRAM, isSigner: false, isWritable: false },
|
|
655
|
+
{ pubkey: PUMPSWAP_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
656
|
+
{ pubkey: PUMPSWAP_PROGRAM, isSigner: false, isWritable: false },
|
|
657
|
+
{ pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
|
|
658
|
+
{ pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
|
|
659
|
+
];
|
|
660
|
+
|
|
661
|
+
// Add volume accumulator accounts for non-quote sell
|
|
662
|
+
if (!quoteIsWsolOrUsdc) {
|
|
663
|
+
accounts.push(
|
|
664
|
+
{ pubkey: PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true }
|
|
665
|
+
);
|
|
666
|
+
const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
|
|
667
|
+
accounts.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Add fee config and program
|
|
671
|
+
accounts.push(
|
|
672
|
+
{ pubkey: PUMPSWAP_FEE_CONFIG, isSigner: false, isWritable: false },
|
|
673
|
+
{ pubkey: PUMPSWAP_FEE_PROGRAM, isSigner: false, isWritable: false }
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
// Add cashback accounts if needed (sell uses quote ATA)
|
|
677
|
+
if (isCashbackCoin) {
|
|
678
|
+
const quoteAta = getUserVolumeAccumulatorQuoteAta(payer, quoteMint, quoteTokenProgram);
|
|
679
|
+
const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
|
|
680
|
+
accounts.push(
|
|
681
|
+
{ pubkey: quoteAta, isSigner: false, isWritable: true },
|
|
682
|
+
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true }
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Add pool v2 PDA
|
|
687
|
+
const poolV2 = getPoolV2PDA(baseMint);
|
|
688
|
+
accounts.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
689
|
+
const protocolExtraFee = getPumpSwapProtocolExtraFeeRecipientRandom();
|
|
690
|
+
accounts.push({ pubkey: protocolExtraFee, isSigner: false, isWritable: false });
|
|
691
|
+
accounts.push({
|
|
692
|
+
pubkey: getAssociatedTokenAddress(protocolExtraFee, quoteMint, TOKEN_PROGRAM),
|
|
693
|
+
isSigner: false,
|
|
694
|
+
isWritable: true,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Build instruction data
|
|
698
|
+
const data = Buffer.alloc(24);
|
|
699
|
+
if (quoteIsWsolOrUsdc) {
|
|
700
|
+
PUMPSWAP_SELL_DISCRIMINATOR.copy(data, 0);
|
|
701
|
+
data.writeBigUInt64LE(tokenAmount, 8);
|
|
702
|
+
data.writeBigUInt64LE(solAmount, 16);
|
|
703
|
+
} else {
|
|
704
|
+
PUMPSWAP_SELL_DISCRIMINATOR.copy(data, 0);
|
|
705
|
+
data.writeBigUInt64LE(solAmount, 8);
|
|
706
|
+
data.writeBigUInt64LE(tokenAmount, 16);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
instructions.push(
|
|
710
|
+
new TransactionInstruction({
|
|
711
|
+
keys: accounts,
|
|
712
|
+
programId: PUMPSWAP_PROGRAM,
|
|
713
|
+
data,
|
|
714
|
+
})
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// Close WSOL ATA if requested
|
|
718
|
+
if (closeOutputMintAta && quoteIsWsolOrUsdc) {
|
|
719
|
+
instructions.push(closeWsol(payer));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Close base token account if requested
|
|
723
|
+
if (closeInputMintAta) {
|
|
724
|
+
const closeIx = new TransactionInstruction({
|
|
725
|
+
keys: [
|
|
726
|
+
{ pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
|
|
727
|
+
{ pubkey: payer, isSigner: false, isWritable: true },
|
|
728
|
+
{ pubkey: payer, isSigner: true, isWritable: false },
|
|
729
|
+
],
|
|
730
|
+
programId: baseTokenProgram,
|
|
731
|
+
data: Buffer.from([9, 0, 0, 0, 0, 0, 0, 0]),
|
|
732
|
+
});
|
|
733
|
+
instructions.push(closeIx);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return instructions;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Build claim cashback instruction for PumpSwap
|
|
741
|
+
*/
|
|
742
|
+
export function buildClaimCashbackInstruction(
|
|
743
|
+
payer: PublicKey,
|
|
744
|
+
quoteMint: PublicKey,
|
|
745
|
+
quoteTokenProgram: PublicKey
|
|
746
|
+
): TransactionInstruction {
|
|
747
|
+
const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
|
|
748
|
+
const userVolumeAccumulatorWsolAta = getUserVolumeAccumulatorWsolAta(payer);
|
|
749
|
+
const userWsolAta = getAssociatedTokenAddress(payer, quoteMint, quoteTokenProgram);
|
|
750
|
+
|
|
751
|
+
const accounts = [
|
|
752
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
753
|
+
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
754
|
+
{ pubkey: quoteMint, isSigner: false, isWritable: false },
|
|
755
|
+
{ pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
|
|
756
|
+
{ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true },
|
|
757
|
+
{ pubkey: userWsolAta, isSigner: false, isWritable: true },
|
|
758
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
759
|
+
{ pubkey: PUMPSWAP_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
760
|
+
{ pubkey: PUMPSWAP_PROGRAM, isSigner: false, isWritable: false },
|
|
761
|
+
];
|
|
762
|
+
|
|
763
|
+
return new TransactionInstruction({
|
|
764
|
+
keys: accounts,
|
|
765
|
+
programId: PUMPSWAP_PROGRAM,
|
|
766
|
+
data: PUMPSWAP_CLAIM_CASHBACK_DISCRIMINATOR,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// ===== Pool Types and Decoding - from Rust: src/instruction/utils/pumpswap_types.rs =====
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Pool size in bytes (244 bytes as per pump-public-docs)
|
|
774
|
+
*/
|
|
775
|
+
export const POOL_SIZE = 244;
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* PumpSwap Pool structure
|
|
779
|
+
* Matches Rust: src/instruction/utils/pumpswap_types.rs Pool struct
|
|
780
|
+
*/
|
|
781
|
+
export interface PumpSwapPool {
|
|
782
|
+
poolBump: number;
|
|
783
|
+
index: number;
|
|
784
|
+
creator: PublicKey;
|
|
785
|
+
baseMint: PublicKey;
|
|
786
|
+
quoteMint: PublicKey;
|
|
787
|
+
lpMint: PublicKey;
|
|
788
|
+
poolBaseTokenAccount: PublicKey;
|
|
789
|
+
poolQuoteTokenAccount: PublicKey;
|
|
790
|
+
lpSupply: bigint;
|
|
791
|
+
coinCreator: PublicKey;
|
|
792
|
+
isMayhemMode: boolean;
|
|
793
|
+
isCashbackCoin: boolean;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Decode a PumpSwap pool from account data
|
|
798
|
+
* Uses Borsh deserialization
|
|
799
|
+
*/
|
|
800
|
+
export function decodePool(data: Buffer): PumpSwapPool | null {
|
|
801
|
+
if (data.length < POOL_SIZE) {
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
try {
|
|
806
|
+
let offset = 0;
|
|
807
|
+
|
|
808
|
+
// pool_bump: u8
|
|
809
|
+
const poolBump = data.readUInt8(offset);
|
|
810
|
+
offset += 1;
|
|
811
|
+
|
|
812
|
+
// index: u16
|
|
813
|
+
const index = data.readUInt16LE(offset);
|
|
814
|
+
offset += 2;
|
|
815
|
+
|
|
816
|
+
// creator: Pubkey (32 bytes)
|
|
817
|
+
const creator = new PublicKey(data.subarray(offset, offset + 32));
|
|
818
|
+
offset += 32;
|
|
819
|
+
|
|
820
|
+
// base_mint: Pubkey
|
|
821
|
+
const baseMint = new PublicKey(data.subarray(offset, offset + 32));
|
|
822
|
+
offset += 32;
|
|
823
|
+
|
|
824
|
+
// quote_mint: Pubkey
|
|
825
|
+
const quoteMint = new PublicKey(data.subarray(offset, offset + 32));
|
|
826
|
+
offset += 32;
|
|
827
|
+
|
|
828
|
+
// lp_mint: Pubkey
|
|
829
|
+
const lpMint = new PublicKey(data.subarray(offset, offset + 32));
|
|
830
|
+
offset += 32;
|
|
831
|
+
|
|
832
|
+
// pool_base_token_account: Pubkey
|
|
833
|
+
const poolBaseTokenAccount = new PublicKey(data.subarray(offset, offset + 32));
|
|
834
|
+
offset += 32;
|
|
835
|
+
|
|
836
|
+
// pool_quote_token_account: Pubkey
|
|
837
|
+
const poolQuoteTokenAccount = new PublicKey(data.subarray(offset, offset + 32));
|
|
838
|
+
offset += 32;
|
|
839
|
+
|
|
840
|
+
// lp_supply: u64
|
|
841
|
+
const lpSupply = data.readBigUInt64LE(offset);
|
|
842
|
+
offset += 8;
|
|
843
|
+
|
|
844
|
+
// coin_creator: Pubkey
|
|
845
|
+
const coinCreator = new PublicKey(data.subarray(offset, offset + 32));
|
|
846
|
+
offset += 32;
|
|
847
|
+
|
|
848
|
+
// is_mayhem_mode: bool
|
|
849
|
+
const isMayhemMode = data.readUInt8(offset) === 1;
|
|
850
|
+
offset += 1;
|
|
851
|
+
|
|
852
|
+
// is_cashback_coin: bool
|
|
853
|
+
const isCashbackCoin = data.readUInt8(offset) === 1;
|
|
854
|
+
offset += 1;
|
|
855
|
+
|
|
856
|
+
return {
|
|
857
|
+
poolBump,
|
|
858
|
+
index,
|
|
859
|
+
creator,
|
|
860
|
+
baseMint,
|
|
861
|
+
quoteMint,
|
|
862
|
+
lpMint,
|
|
863
|
+
poolBaseTokenAccount,
|
|
864
|
+
poolQuoteTokenAccount,
|
|
865
|
+
lpSupply,
|
|
866
|
+
coinCreator,
|
|
867
|
+
isMayhemMode,
|
|
868
|
+
isCashbackCoin,
|
|
869
|
+
};
|
|
870
|
+
} catch {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// ===== Pool Finder Functions - from Rust: src/instruction/utils/pumpswap.rs =====
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Find a PumpSwap pool by mint
|
|
879
|
+
*
|
|
880
|
+
* Search order (matches @pump-fun/pump-swap-sdk):
|
|
881
|
+
* 1. Pool v2 PDA ["pool-v2", base_mint]
|
|
882
|
+
* 2. Canonical pool PDA ["pool", 0, pumpPoolAuthority(mint), mint, WSOL]
|
|
883
|
+
* 3. getProgramAccounts by base_mint / quote_mint
|
|
884
|
+
*/
|
|
885
|
+
export async function findPoolByMint(
|
|
886
|
+
connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value: { data: Buffer } | null }> },
|
|
887
|
+
mint: PublicKey
|
|
888
|
+
): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
|
|
889
|
+
// 1. Try Pool v2 PDA
|
|
890
|
+
const poolV2 = getPoolV2PDA(mint);
|
|
891
|
+
const poolV2Account = await connection.getAccountInfo(poolV2);
|
|
892
|
+
if (poolV2Account?.value?.data) {
|
|
893
|
+
const pool = decodePool(poolV2Account.value.data);
|
|
894
|
+
if (pool && pool.baseMint.equals(mint)) {
|
|
895
|
+
return { poolAddress: poolV2, pool };
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// 2. Try canonical pool PDA
|
|
900
|
+
const canonicalAddress = getCanonicalPoolPDA(mint);
|
|
901
|
+
const canonicalAccount = await connection.getAccountInfo(canonicalAddress);
|
|
902
|
+
if (canonicalAccount?.value?.data) {
|
|
903
|
+
const pool = decodePool(canonicalAccount.value.data);
|
|
904
|
+
if (pool && pool.baseMint.equals(mint)) {
|
|
905
|
+
return { poolAddress: canonicalAddress, pool };
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Get fee config PDA
|
|
914
|
+
*/
|
|
915
|
+
export function getFeeConfigPDA(): PublicKey {
|
|
916
|
+
const [pda] = PublicKey.findProgramAddressSync(
|
|
917
|
+
[FEE_CONFIG_SEED, PUMPSWAP_PROGRAM.toBuffer()],
|
|
918
|
+
new PublicKey('pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ')
|
|
919
|
+
);
|
|
920
|
+
return pda;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// ===== Async Fetch Functions - from Rust: src/instruction/utils/pumpswap.rs =====
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Fetch a PumpSwap pool from RPC.
|
|
927
|
+
* 100% from Rust: src/instruction/utils/pumpswap.rs fetch_pool
|
|
928
|
+
*/
|
|
929
|
+
export async function fetchPool(
|
|
930
|
+
connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }> },
|
|
931
|
+
poolAddress: PublicKey
|
|
932
|
+
): Promise<PumpSwapPool | null> {
|
|
933
|
+
const account = await connection.getAccountInfo(poolAddress);
|
|
934
|
+
if (!account?.value?.data) {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
const pool = decodePool(account.value.data);
|
|
938
|
+
return pool;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Get token balances for a pool's token accounts.
|
|
943
|
+
* 100% from Rust: src/instruction/utils/pumpswap.rs get_token_balances
|
|
944
|
+
*/
|
|
945
|
+
export async function getTokenBalances(
|
|
946
|
+
connection: {
|
|
947
|
+
getTokenAccountBalance: (pubkey: PublicKey) => Promise<{ value?: { amount: string } }>
|
|
948
|
+
},
|
|
949
|
+
pool: PumpSwapPool
|
|
950
|
+
): Promise<{ baseBalance: bigint; quoteBalance: bigint } | null> {
|
|
951
|
+
try {
|
|
952
|
+
const baseBalanceResult = await connection.getTokenAccountBalance(pool.poolBaseTokenAccount);
|
|
953
|
+
const quoteBalanceResult = await connection.getTokenAccountBalance(pool.poolQuoteTokenAccount);
|
|
954
|
+
|
|
955
|
+
const baseBalance = BigInt(baseBalanceResult?.value?.amount ?? '0');
|
|
956
|
+
const quoteBalance = BigInt(quoteBalanceResult?.value?.amount ?? '0');
|
|
957
|
+
|
|
958
|
+
return { baseBalance, quoteBalance };
|
|
959
|
+
} catch {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Find a PumpSwap pool by mint with full RPC lookup.
|
|
966
|
+
* 100% from Rust: src/instruction/utils/pumpswap.rs find_by_mint
|
|
967
|
+
* Search order:
|
|
968
|
+
* 1. Pool v2 PDA ["pool-v2", base_mint]
|
|
969
|
+
* 2. Canonical pool PDA
|
|
970
|
+
* 3. getProgramAccounts by base_mint / quote_mint (optional fallback)
|
|
971
|
+
*/
|
|
972
|
+
export async function findByMint(
|
|
973
|
+
connection: {
|
|
974
|
+
getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }>;
|
|
975
|
+
getProgramAccounts?: (programId: PublicKey, config?: unknown) => Promise<Array<{ pubkey: PublicKey; account: { data: Buffer } }>>;
|
|
976
|
+
},
|
|
977
|
+
mint: PublicKey
|
|
978
|
+
): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
|
|
979
|
+
// 1. Try v2 PDA
|
|
980
|
+
const poolV2 = getPoolV2PDA(mint);
|
|
981
|
+
const poolV2Account = await connection.getAccountInfo(poolV2);
|
|
982
|
+
if (poolV2Account?.value?.data) {
|
|
983
|
+
const pool = decodePool(poolV2Account.value.data);
|
|
984
|
+
if (pool && pool.baseMint.equals(mint)) {
|
|
985
|
+
return { poolAddress: poolV2, pool };
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// 2. Try canonical pool PDA
|
|
990
|
+
const canonicalAddress = getCanonicalPoolPDA(mint);
|
|
991
|
+
const canonicalAccount = await connection.getAccountInfo(canonicalAddress);
|
|
992
|
+
if (canonicalAccount?.value?.data) {
|
|
993
|
+
const pool = decodePool(canonicalAccount.value.data);
|
|
994
|
+
if (pool && pool.baseMint.equals(mint)) {
|
|
995
|
+
return { poolAddress: canonicalAddress, pool };
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// 3. Optional: getProgramAccounts fallback (if available)
|
|
1000
|
+
// This would require more complex implementation with memcmp filters
|
|
1001
|
+
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// ===== Pool Size Constants - from Rust: src/instruction/utils/pumpswap.rs =====
|
|
1006
|
+
|
|
1007
|
+
/** Pool data size for SPL Token (8 discriminator + 244 data) */
|
|
1008
|
+
const POOL_DATA_LEN_SPL = 8 + 244;
|
|
1009
|
+
/** Pool data size for Token2022 */
|
|
1010
|
+
const POOL_DATA_LEN_T22 = 643;
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Find a PumpSwap pool by base mint using getProgramAccounts.
|
|
1014
|
+
* 100% from Rust: src/instruction/utils/pumpswap.rs find_by_base_mint
|
|
1015
|
+
* base_mint offset: 8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43
|
|
1016
|
+
*/
|
|
1017
|
+
export async function findByBaseMint(
|
|
1018
|
+
connection: {
|
|
1019
|
+
getProgramAccounts: (
|
|
1020
|
+
programId: PublicKey,
|
|
1021
|
+
config?: {
|
|
1022
|
+
filters?: Array<{ dataSize?: number; memcmp?: { offset: number; bytes: string } }>;
|
|
1023
|
+
encoding?: string;
|
|
1024
|
+
}
|
|
1025
|
+
) => Promise<Array<{ pubkey: PublicKey; account: { data: Buffer } }>>;
|
|
1026
|
+
},
|
|
1027
|
+
baseMint: PublicKey
|
|
1028
|
+
): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
|
|
1029
|
+
// base_mint offset: 8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43
|
|
1030
|
+
const memcmpOffset = 43;
|
|
1031
|
+
|
|
1032
|
+
// Query both pool sizes in parallel (SPL Token and Token2022)
|
|
1033
|
+
const filters = [
|
|
1034
|
+
{ memcmp: { offset: memcmpOffset, bytes: baseMint.toBase58() } }
|
|
1035
|
+
];
|
|
1036
|
+
|
|
1037
|
+
try {
|
|
1038
|
+
const results = await connection.getProgramAccounts(PUMPSWAP_PROGRAM, {
|
|
1039
|
+
filters,
|
|
1040
|
+
encoding: 'base64'
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
if (!results || results.length === 0) {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Decode and sort by lp_supply (highest first)
|
|
1048
|
+
const pools: { poolAddress: PublicKey; pool: PumpSwapPool }[] = [];
|
|
1049
|
+
for (const { pubkey, account } of results) {
|
|
1050
|
+
const pool = decodePool(account.data);
|
|
1051
|
+
if (pool) {
|
|
1052
|
+
pools.push({ poolAddress: pubkey, pool });
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
if (pools.length === 0) {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Sort by lp_supply descending
|
|
1061
|
+
pools.sort((a, b) => Number(b.pool.lpSupply - a.pool.lpSupply));
|
|
1062
|
+
|
|
1063
|
+
return pools[0] ?? null;
|
|
1064
|
+
} catch {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Find a PumpSwap pool by quote mint using getProgramAccounts.
|
|
1071
|
+
* 100% from Rust: src/instruction/utils/pumpswap.rs find_by_quote_mint
|
|
1072
|
+
* quote_mint offset: 8 + 1 + 2 + 32 + 32 = 75
|
|
1073
|
+
*/
|
|
1074
|
+
export async function findByQuoteMint(
|
|
1075
|
+
connection: {
|
|
1076
|
+
getProgramAccounts: (
|
|
1077
|
+
programId: PublicKey,
|
|
1078
|
+
config?: {
|
|
1079
|
+
filters?: Array<{ dataSize?: number; memcmp?: { offset: number; bytes: string } }>;
|
|
1080
|
+
encoding?: string;
|
|
1081
|
+
}
|
|
1082
|
+
) => Promise<Array<{ pubkey: PublicKey; account: { data: Buffer } }>>;
|
|
1083
|
+
},
|
|
1084
|
+
quoteMint: PublicKey
|
|
1085
|
+
): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
|
|
1086
|
+
// quote_mint offset: 8 + 1 + 2 + 32 + 32 = 75
|
|
1087
|
+
const memcmpOffset = 75;
|
|
1088
|
+
|
|
1089
|
+
const filters = [
|
|
1090
|
+
{ memcmp: { offset: memcmpOffset, bytes: quoteMint.toBase58() } }
|
|
1091
|
+
];
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
const results = await connection.getProgramAccounts(PUMPSWAP_PROGRAM, {
|
|
1095
|
+
filters,
|
|
1096
|
+
encoding: 'base64'
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
if (!results || results.length === 0) {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Decode and sort by lp_supply (highest first)
|
|
1104
|
+
const pools: { poolAddress: PublicKey; pool: PumpSwapPool }[] = [];
|
|
1105
|
+
for (const { pubkey, account } of results) {
|
|
1106
|
+
const pool = decodePool(account.data);
|
|
1107
|
+
if (pool) {
|
|
1108
|
+
pools.push({ poolAddress: pubkey, pool });
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
if (pools.length === 0) {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Sort by lp_supply descending
|
|
1117
|
+
pools.sort((a, b) => Number(b.pool.lpSupply - a.pool.lpSupply));
|
|
1118
|
+
|
|
1119
|
+
return pools[0] ?? null;
|
|
1120
|
+
} catch {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
}
|