sol-trade-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +390 -0
  2. package/dist/chunk-MMQAMIKR.mjs +3735 -0
  3. package/dist/chunk-NEZDFAYA.mjs +7744 -0
  4. package/dist/clients-VITWK7B6.mjs +1370 -0
  5. package/dist/index-1BK_FXsW.d.mts +2327 -0
  6. package/dist/index-1BK_FXsW.d.ts +2327 -0
  7. package/dist/index.d.mts +2659 -0
  8. package/dist/index.d.ts +2659 -0
  9. package/dist/index.js +13265 -0
  10. package/dist/index.mjs +562 -0
  11. package/dist/perf/index.d.mts +2 -0
  12. package/dist/perf/index.d.ts +2 -0
  13. package/dist/perf/index.js +3742 -0
  14. package/dist/perf/index.mjs +214 -0
  15. package/package.json +101 -0
  16. package/src/__tests__/complete_sdk.test.ts +354 -0
  17. package/src/__tests__/hotpath.test.ts +486 -0
  18. package/src/__tests__/nonce.test.ts +45 -0
  19. package/src/__tests__/sdk.test.ts +425 -0
  20. package/src/address-lookup/index.ts +197 -0
  21. package/src/cache/cache.ts +308 -0
  22. package/src/calc/index.ts +1058 -0
  23. package/src/calc/pumpfun.ts +124 -0
  24. package/src/common/bonding_curve.ts +272 -0
  25. package/src/common/compute-budget.ts +148 -0
  26. package/src/common/confirm-any-signature.ts +184 -0
  27. package/src/common/fast-timing.ts +481 -0
  28. package/src/common/fast_fn.ts +150 -0
  29. package/src/common/gas-fee-strategy.ts +253 -0
  30. package/src/common/map-pool.ts +23 -0
  31. package/src/common/nonce.ts +40 -0
  32. package/src/common/sdk-log.ts +460 -0
  33. package/src/common/seed.ts +381 -0
  34. package/src/common/spl-token.ts +578 -0
  35. package/src/common/subscription-handle.ts +644 -0
  36. package/src/common/trading-utils.ts +239 -0
  37. package/src/common/wsol-manager.ts +325 -0
  38. package/src/compute/compute_budget_manager.ts +187 -0
  39. package/src/compute/index.ts +21 -0
  40. package/src/constants/index.ts +96 -0
  41. package/src/execution/execution.ts +532 -0
  42. package/src/execution/index.ts +42 -0
  43. package/src/hotpath/executor.ts +464 -0
  44. package/src/hotpath/index.ts +64 -0
  45. package/src/hotpath/state.ts +435 -0
  46. package/src/index.ts +2117 -0
  47. package/src/instruction/bonk_builder.ts +730 -0
  48. package/src/instruction/index.ts +24 -0
  49. package/src/instruction/meteora_damm_v2_builder.ts +509 -0
  50. package/src/instruction/pumpfun_builder.ts +1183 -0
  51. package/src/instruction/pumpswap.ts +1123 -0
  52. package/src/instruction/raydium_amm_v4_builder.ts +692 -0
  53. package/src/instruction/raydium_cpmm_builder.ts +795 -0
  54. package/src/middleware/traits.ts +407 -0
  55. package/src/params/index.ts +483 -0
  56. package/src/perf/compiler-optimization.ts +529 -0
  57. package/src/perf/hardware.ts +631 -0
  58. package/src/perf/index.ts +9 -0
  59. package/src/perf/kernel-bypass.ts +656 -0
  60. package/src/perf/protocol.ts +682 -0
  61. package/src/perf/realtime.ts +592 -0
  62. package/src/perf/simd.ts +668 -0
  63. package/src/perf/syscall-bypass.ts +331 -0
  64. package/src/perf/ultra-low-latency.ts +505 -0
  65. package/src/perf/zero-copy.ts +589 -0
  66. package/src/pool/pool.ts +294 -0
  67. package/src/rpc/client.ts +345 -0
  68. package/src/sdk-errors.ts +13 -0
  69. package/src/security/index.ts +26 -0
  70. package/src/security/secure-key.ts +303 -0
  71. package/src/security/validators.ts +281 -0
  72. package/src/seed/pda.ts +262 -0
  73. package/src/serialization/index.ts +28 -0
  74. package/src/serialization/serialization.ts +288 -0
  75. package/src/swqos/clients.ts +1754 -0
  76. package/src/swqos/index.ts +50 -0
  77. package/src/swqos/providers.ts +1707 -0
  78. package/src/trading/core/async-executor.ts +702 -0
  79. package/src/trading/core/confirmation-monitor.ts +711 -0
  80. package/src/trading/core/index.ts +82 -0
  81. package/src/trading/core/retry-handler.ts +683 -0
  82. package/src/trading/core/transaction-pool.ts +780 -0
  83. package/src/trading/executor.ts +385 -0
  84. package/src/trading/factory.ts +282 -0
  85. package/src/trading/index.ts +30 -0
  86. package/src/types.ts +8 -0
  87. package/src/utils/index.ts +155 -0
@@ -0,0 +1,1183 @@
1
+ /**
2
+ * PumpFun Protocol Instruction Builder
3
+ *
4
+ * Production-grade instruction builder for PumpFun bonding curve protocol.
5
+ * Supports buy, sell, and cashback claim operations.
6
+ * 100% port from Rust: src/instruction/pumpfun.rs
7
+ */
8
+
9
+ import {
10
+ PublicKey,
11
+ Keypair,
12
+ AccountMeta,
13
+ TransactionInstruction,
14
+ SystemProgram,
15
+ } from "@solana/web3.js";
16
+ import {
17
+ getAssociatedTokenAddressSync,
18
+ createAssociatedTokenAccountInstruction,
19
+ createAssociatedTokenAccountIdempotentInstruction,
20
+ TOKEN_PROGRAM_ID,
21
+ TOKEN_2022_PROGRAM_ID,
22
+ ASSOCIATED_TOKEN_PROGRAM_ID as SPL_ASSOCIATED_TOKEN_PROGRAM_ID,
23
+ createCloseAccountInstruction,
24
+ createSyncNativeInstruction,
25
+ NATIVE_MINT,
26
+ } from "@solana/spl-token";
27
+
28
+ // ============================================
29
+ // Program IDs and Constants
30
+ // ============================================
31
+
32
+ /** PumpFun program ID */
33
+ export const PUMPFUN_PROGRAM_ID = new PublicKey(
34
+ "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
35
+ );
36
+
37
+ /** Event Authority for PumpFun */
38
+ export const PUMPFUN_EVENT_AUTHORITY = new PublicKey(
39
+ "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"
40
+ );
41
+
42
+ /** Fee Program */
43
+ export const PUMPFUN_FEE_PROGRAM = new PublicKey(
44
+ "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
45
+ );
46
+
47
+ /** Global Volume Accumulator */
48
+ export const PUMPFUN_GLOBAL_VOLUME_ACCUMULATOR = new PublicKey(
49
+ "Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y"
50
+ );
51
+
52
+ /** Fee Config */
53
+ export const PUMPFUN_FEE_CONFIG = new PublicKey(
54
+ "8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"
55
+ );
56
+
57
+ /** Global Account */
58
+ export const PUMPFUN_GLOBAL_ACCOUNT = new PublicKey(
59
+ "4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf"
60
+ );
61
+
62
+ /** Fee Recipient */
63
+ export const PUMPFUN_FEE_RECIPIENT = new PublicKey(
64
+ "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"
65
+ );
66
+
67
+ /** Non-mayhem: random among primary + Pump.fun AMM protocol fee recipients (Rust `get_standard_fee_recipient_meta_random`). */
68
+ export const PUMPFUN_STANDARD_FEE_RECIPIENTS: PublicKey[] = [
69
+ PUMPFUN_FEE_RECIPIENT,
70
+ new PublicKey("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"),
71
+ new PublicKey("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"),
72
+ new PublicKey("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"),
73
+ new PublicKey("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"),
74
+ new PublicKey("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"),
75
+ new PublicKey("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"),
76
+ new PublicKey("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"),
77
+ ];
78
+
79
+ /**
80
+ * Protocol extra fee recipients (Apr 2026 breaking upgrade).
81
+ * One pubkey is appended after bonding-curve-v2 on buy/sell; account must be writable.
82
+ * @see https://github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
83
+ */
84
+ export const PUMPFUN_PROTOCOL_EXTRA_FEE_RECIPIENTS: PublicKey[] = [
85
+ new PublicKey("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
86
+ new PublicKey("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
87
+ new PublicKey("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
88
+ new PublicKey("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
89
+ new PublicKey("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
90
+ new PublicKey("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
91
+ new PublicKey("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
92
+ new PublicKey("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
93
+ ];
94
+
95
+ /** V2 buyback fee recipients (same static pool as Rust `get_buyback_fee_recipient_random`). */
96
+ export const PUMPFUN_BUYBACK_FEE_RECIPIENTS: PublicKey[] = PUMPFUN_PROTOCOL_EXTRA_FEE_RECIPIENTS;
97
+
98
+ /** Mayhem Fee Recipients */
99
+ export const PUMPFUN_MAYHEM_FEE_RECIPIENTS: PublicKey[] = [
100
+ new PublicKey("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
101
+ new PublicKey("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
102
+ new PublicKey("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
103
+ new PublicKey("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
104
+ new PublicKey("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
105
+ new PublicKey("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
106
+ new PublicKey("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
107
+ new PublicKey("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
108
+ ];
109
+
110
+ // ============================================
111
+ // Discriminators - from Rust src/instruction/utils/pumpfun.rs
112
+ // ============================================
113
+
114
+ /** Buy instruction discriminator */
115
+ export const PUMPFUN_BUY_DISCRIMINATOR: Buffer = Buffer.from([
116
+ 102, 6, 61, 18, 1, 218, 235, 234,
117
+ ]);
118
+
119
+ /** Buy exact SOL in discriminator */
120
+ export const PUMPFUN_BUY_EXACT_SOL_IN_DISCRIMINATOR: Buffer = Buffer.from([
121
+ 56, 252, 116, 8, 158, 223, 205, 95,
122
+ ]);
123
+
124
+ /** Sell instruction discriminator */
125
+ export const PUMPFUN_SELL_DISCRIMINATOR: Buffer = Buffer.from([
126
+ 51, 230, 133, 164, 1, 127, 131, 173,
127
+ ]);
128
+
129
+ /** PumpFun V2 buy instruction discriminator */
130
+ export const PUMPFUN_BUY_V2_DISCRIMINATOR: Buffer = Buffer.from([
131
+ 184, 23, 238, 97, 103, 197, 211, 61,
132
+ ]);
133
+
134
+ /** PumpFun V2 sell instruction discriminator */
135
+ export const PUMPFUN_SELL_V2_DISCRIMINATOR: Buffer = Buffer.from([
136
+ 93, 246, 130, 60, 231, 233, 64, 178,
137
+ ]);
138
+
139
+ /** PumpFun V2 exact quote-in buy discriminator */
140
+ export const PUMPFUN_BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR: Buffer = Buffer.from([
141
+ 194, 171, 28, 70, 104, 77, 91, 47,
142
+ ]);
143
+
144
+ /** Claim cashback discriminator */
145
+ export const PUMPFUN_CLAIM_CASHBACK_DISCRIMINATOR: Buffer = Buffer.from([
146
+ 37, 58, 35, 126, 190, 53, 228, 197,
147
+ ]);
148
+
149
+ // ============================================
150
+ // Seeds
151
+ // ============================================
152
+
153
+ export const PUMPFUN_BONDING_CURVE_SEED = Buffer.from("bonding-curve");
154
+ export const PUMPFUN_BONDING_CURVE_V2_SEED = Buffer.from("bonding-curve-v2");
155
+ export const PUMPFUN_CREATOR_VAULT_SEED = Buffer.from("creator-vault");
156
+ export const PUMPFUN_USER_VOLUME_ACCUMULATOR_SEED = Buffer.from("user_volume_accumulator");
157
+ export const PUMPFUN_SHARING_CONFIG_SEED = Buffer.from("sharing-config");
158
+
159
+ // ============================================
160
+ // PDA Derivation Functions
161
+ // ============================================
162
+
163
+ /**
164
+ * Derive the bonding curve PDA for a given mint
165
+ */
166
+ export function getBondingCurvePda(mint: PublicKey): PublicKey {
167
+ const [pda] = PublicKey.findProgramAddressSync(
168
+ [PUMPFUN_BONDING_CURVE_SEED, mint.toBuffer()],
169
+ PUMPFUN_PROGRAM_ID
170
+ );
171
+ return pda;
172
+ }
173
+
174
+ /**
175
+ * Derive the bonding curve v2 PDA for a given mint
176
+ */
177
+ export function getBondingCurveV2Pda(mint: PublicKey): PublicKey {
178
+ const [pda] = PublicKey.findProgramAddressSync(
179
+ [PUMPFUN_BONDING_CURVE_V2_SEED, mint.toBuffer()],
180
+ PUMPFUN_PROGRAM_ID
181
+ );
182
+ return pda;
183
+ }
184
+
185
+ /**
186
+ * Derive the creator vault PDA for a given creator
187
+ */
188
+ export function getCreatorVaultPda(creator: PublicKey): PublicKey {
189
+ const [pda] = PublicKey.findProgramAddressSync(
190
+ [PUMPFUN_CREATOR_VAULT_SEED, creator.toBuffer()],
191
+ PUMPFUN_PROGRAM_ID
192
+ );
193
+ return pda;
194
+ }
195
+
196
+ /**
197
+ * Derive the user volume accumulator PDA for a given user
198
+ */
199
+ export function getPumpFunUserVolumeAccumulatorPda(user: PublicKey): PublicKey {
200
+ const [pda] = PublicKey.findProgramAddressSync(
201
+ [PUMPFUN_USER_VOLUME_ACCUMULATOR_SEED, user.toBuffer()],
202
+ PUMPFUN_PROGRAM_ID
203
+ );
204
+ return pda;
205
+ }
206
+
207
+ /**
208
+ * Derive the fee sharing config PDA for a PumpFun mint.
209
+ */
210
+ export function getPumpFunFeeSharingConfigPda(mint: PublicKey): PublicKey {
211
+ const [pda] = PublicKey.findProgramAddressSync(
212
+ [PUMPFUN_SHARING_CONFIG_SEED, mint.toBuffer()],
213
+ PUMPFUN_FEE_PROGRAM
214
+ );
215
+ return pda;
216
+ }
217
+
218
+ /**
219
+ * Get a random Mayhem fee recipient
220
+ */
221
+ export function getRandomMayhemFeeRecipient(): PublicKey {
222
+ const index = Math.floor(Math.random() * PUMPFUN_MAYHEM_FEE_RECIPIENTS.length);
223
+ const recipient = PUMPFUN_MAYHEM_FEE_RECIPIENTS[index];
224
+ if (!recipient) {
225
+ return PUMPFUN_MAYHEM_FEE_RECIPIENTS[0]!;
226
+ }
227
+ return recipient;
228
+ }
229
+
230
+ export function getStandardFeeRecipientRandom(): PublicKey {
231
+ const index = Math.floor(Math.random() * PUMPFUN_STANDARD_FEE_RECIPIENTS.length);
232
+ return PUMPFUN_STANDARD_FEE_RECIPIENTS[index] ?? PUMPFUN_FEE_RECIPIENT;
233
+ }
234
+
235
+ /** Random protocol extra fee recipient (after bonding-curve-v2, mutable). */
236
+ export function getPumpFunProtocolExtraFeeRecipientRandom(): PublicKey {
237
+ const index = Math.floor(Math.random() * PUMPFUN_PROTOCOL_EXTRA_FEE_RECIPIENTS.length);
238
+ return PUMPFUN_PROTOCOL_EXTRA_FEE_RECIPIENTS[index] ?? PUMPFUN_PROTOCOL_EXTRA_FEE_RECIPIENTS[0]!;
239
+ }
240
+
241
+ /** Random PumpFun V2 buyback fee recipient. */
242
+ export function getPumpFunBuybackFeeRecipientRandom(): PublicKey {
243
+ const index = Math.floor(Math.random() * PUMPFUN_BUYBACK_FEE_RECIPIENTS.length);
244
+ return PUMPFUN_BUYBACK_FEE_RECIPIENTS[index] ?? PUMPFUN_BUYBACK_FEE_RECIPIENTS[0]!;
245
+ }
246
+
247
+ /**
248
+ * Account #2 fee recipient: prefer gRPC/event `feeRecipient`; if `default` pubkey, random from mayhem or standard pool (Rust `pump_fun_fee_recipient_meta`).
249
+ */
250
+ export function pumpFunFeeRecipientMeta(
251
+ fromStream: PublicKey | undefined,
252
+ isMayhemMode: boolean
253
+ ): PublicKey {
254
+ if (fromStream && !fromStream.equals(PublicKey.default)) {
255
+ return fromStream;
256
+ }
257
+ return isMayhemMode ? getRandomMayhemFeeRecipient() : getStandardFeeRecipientRandom();
258
+ }
259
+
260
+ // ============================================
261
+ // Types
262
+ // ============================================
263
+
264
+ export interface PumpFunBondingCurve {
265
+ account: PublicKey;
266
+ virtualTokenReserves: bigint;
267
+ virtualSolReserves: bigint;
268
+ realTokenReserves: bigint;
269
+ creator?: PublicKey;
270
+ isMayhemMode: boolean;
271
+ isCashbackCoin: boolean;
272
+ }
273
+
274
+ export interface PumpFunParams {
275
+ bondingCurve: PumpFunBondingCurve;
276
+ creatorVault: PublicKey;
277
+ tokenProgram: PublicKey;
278
+ associatedBondingCurve?: PublicKey;
279
+ observedTradeCreator?: PublicKey;
280
+ feeSharingCreatorVaultIfActive?: PublicKey;
281
+ closeTokenAccountWhenSell?: boolean;
282
+ /** From parser/gRPC (`tradeEvent.feeRecipient`); default pubkey → random pool */
283
+ feeRecipient?: PublicKey;
284
+ /** Quote mint for V2 instructions; default means WSOL. */
285
+ quoteMint?: PublicKey;
286
+ /** Per-params V2 toggle; global `TradeConfig.usePumpfunV2` also maps here. */
287
+ useV2Ix?: boolean;
288
+ }
289
+
290
+ export interface PumpFunBuildBuyParams {
291
+ payer: Keypair | PublicKey;
292
+ outputMint: PublicKey;
293
+ inputAmount: bigint;
294
+ slippageBasisPoints?: bigint;
295
+ fixedOutputAmount?: bigint;
296
+ createOutputMintAta?: boolean;
297
+ createInputMintAta?: boolean;
298
+ protocolParams: PumpFunParams;
299
+ useExactSolAmount?: boolean;
300
+ usePumpFunV2?: boolean;
301
+ }
302
+
303
+ export interface PumpFunBuildSellParams {
304
+ payer: Keypair | PublicKey;
305
+ inputMint: PublicKey;
306
+ inputAmount: bigint;
307
+ slippageBasisPoints?: bigint;
308
+ fixedOutputAmount?: bigint;
309
+ createOutputMintAta?: boolean;
310
+ closeInputMintAta?: boolean;
311
+ protocolParams: PumpFunParams;
312
+ usePumpFunV2?: boolean;
313
+ }
314
+
315
+ // ============================================
316
+ // Helper Functions
317
+ // ============================================
318
+
319
+ const MAX_SLIPPAGE_BPS = BigInt(9999);
320
+ const PUMPFUN_FEE_BASIS_POINTS = 95n;
321
+ const PUMPFUN_CREATOR_FEE_BASIS_POINTS = 30n;
322
+ const PHANTOM_DEFAULT_CREATOR_VAULT = new PublicKey(
323
+ "2DR3iqRPVThyRLVJnwjPW1qiGWrp8RUFfHVjMbZyhdNc"
324
+ );
325
+
326
+ function calculateWithSlippageBuy(amount: bigint, basisPoints: bigint): bigint {
327
+ const bps = basisPoints > MAX_SLIPPAGE_BPS ? MAX_SLIPPAGE_BPS : basisPoints;
328
+ return amount + (amount * bps) / BigInt(10000);
329
+ }
330
+
331
+ function calculateWithSlippageSell(amount: bigint, basisPoints: bigint): bigint {
332
+ const bps = basisPoints > MAX_SLIPPAGE_BPS ? MAX_SLIPPAGE_BPS : basisPoints;
333
+ const result = amount - (amount * bps) / BigInt(10000);
334
+ return result > BigInt(0) ? result : BigInt(1);
335
+ }
336
+
337
+ function isUsablePubkey(value: PublicKey | undefined): value is PublicKey {
338
+ return (
339
+ value !== undefined &&
340
+ !value.equals(PublicKey.default) &&
341
+ !value.equals(PHANTOM_DEFAULT_CREATOR_VAULT)
342
+ );
343
+ }
344
+
345
+ function effectiveCreatorForTrade(protocolParams: PumpFunParams): PublicKey {
346
+ if (isUsablePubkey(protocolParams.observedTradeCreator)) {
347
+ return protocolParams.observedTradeCreator;
348
+ }
349
+ if (isUsablePubkey(protocolParams.bondingCurve.creator)) {
350
+ return protocolParams.bondingCurve.creator;
351
+ }
352
+ return PublicKey.default;
353
+ }
354
+
355
+ function resolveCreatorVaultForIx(protocolParams: PumpFunParams, mint: PublicKey): PublicKey {
356
+ if (isUsablePubkey(protocolParams.creatorVault)) {
357
+ return protocolParams.creatorVault;
358
+ }
359
+ if (isUsablePubkey(protocolParams.feeSharingCreatorVaultIfActive)) {
360
+ return protocolParams.feeSharingCreatorVaultIfActive;
361
+ }
362
+ const creator = effectiveCreatorForTrade(protocolParams);
363
+ if (isUsablePubkey(creator)) {
364
+ return getCreatorVaultPda(creator);
365
+ }
366
+ throw new Error(`creator_vault PDA derivation failed for mint ${mint.toBase58()}`);
367
+ }
368
+
369
+ function resolveCreatorVaultForSellV2(protocolParams: PumpFunParams, mint: PublicKey): PublicKey {
370
+ if (isUsablePubkey(protocolParams.creatorVault)) {
371
+ return protocolParams.creatorVault;
372
+ }
373
+ if (isUsablePubkey(protocolParams.feeSharingCreatorVaultIfActive)) {
374
+ return protocolParams.feeSharingCreatorVaultIfActive;
375
+ }
376
+ const curveCreator = protocolParams.bondingCurve.creator;
377
+ if (isUsablePubkey(curveCreator)) {
378
+ return getCreatorVaultPda(curveCreator);
379
+ }
380
+ throw new Error(`creator_vault PDA derivation failed (curve_creator=${String(curveCreator)}, mint=${mint.toBase58()})`);
381
+ }
382
+
383
+ function effectivePumpMintTokenProgram(mint: PublicKey, protocolParams: PumpFunParams): PublicKey {
384
+ if (mint.toBase58().endsWith("pump")) {
385
+ return TOKEN_2022_PROGRAM_ID;
386
+ }
387
+ if (isUsablePubkey(protocolParams.tokenProgram)) {
388
+ return protocolParams.tokenProgram;
389
+ }
390
+ return TOKEN_2022_PROGRAM_ID;
391
+ }
392
+
393
+ function effectiveQuoteMint(protocolParams: PumpFunParams): PublicKey {
394
+ return isUsablePubkey(protocolParams.quoteMint) ? protocolParams.quoteMint : NATIVE_MINT;
395
+ }
396
+
397
+ function associatedTokenAddress(mint: PublicKey, owner: PublicKey, tokenProgram: PublicKey): PublicKey {
398
+ return getAssociatedTokenAddressSync(
399
+ mint,
400
+ owner,
401
+ true,
402
+ tokenProgram,
403
+ SPL_ASSOCIATED_TOKEN_PROGRAM_ID
404
+ );
405
+ }
406
+
407
+ function getBuyTokenAmountFromSolAmount(
408
+ amount: bigint,
409
+ bondingCurve: PumpFunBondingCurve,
410
+ creator: PublicKey
411
+ ): bigint {
412
+ if (amount === 0n || bondingCurve.virtualTokenReserves === 0n) {
413
+ return 0n;
414
+ }
415
+ const totalFeeBps =
416
+ PUMPFUN_FEE_BASIS_POINTS + (isUsablePubkey(creator) ? PUMPFUN_CREATOR_FEE_BASIS_POINTS : 0n);
417
+ const inputAmount = (amount * 10_000n) / (totalFeeBps + 10_000n);
418
+ const denominator = bondingCurve.virtualSolReserves + inputAmount;
419
+ if (denominator === 0n) {
420
+ return 0n;
421
+ }
422
+ let tokensReceived = (inputAmount * bondingCurve.virtualTokenReserves) / denominator;
423
+ tokensReceived =
424
+ tokensReceived < bondingCurve.realTokenReserves ? tokensReceived : bondingCurve.realTokenReserves;
425
+ if (tokensReceived <= 100n * 1_000_000n) {
426
+ tokensReceived = amount > 10_000_000n ? 25_547_619n * 1_000_000n : 255_476n * 1_000_000n;
427
+ }
428
+ return tokensReceived;
429
+ }
430
+
431
+ function getSellSolAmountFromTokenAmount(
432
+ amount: bigint,
433
+ bondingCurve: PumpFunBondingCurve,
434
+ creator: PublicKey
435
+ ): bigint {
436
+ if (amount === 0n || bondingCurve.virtualTokenReserves === 0n) {
437
+ return 0n;
438
+ }
439
+ const solCost =
440
+ (amount * bondingCurve.virtualSolReserves) / (bondingCurve.virtualTokenReserves + amount);
441
+ const totalFeeBps =
442
+ PUMPFUN_FEE_BASIS_POINTS + (isUsablePubkey(creator) ? PUMPFUN_CREATOR_FEE_BASIS_POINTS : 0n);
443
+ const fee = (solCost * totalFeeBps + 9_999n) / 10_000n;
444
+ return solCost > fee ? solCost - fee : 0n;
445
+ }
446
+
447
+ // ============================================
448
+ // Instruction Builders
449
+ // ============================================
450
+
451
+ /**
452
+ * Build buy instructions for PumpFun protocol
453
+ * 100% port from Rust: src/instruction/pumpfun.rs build_buy_instructions
454
+ */
455
+ export function buildPumpFunBuyInstructions(
456
+ params: PumpFunBuildBuyParams
457
+ ): TransactionInstruction[] {
458
+ const {
459
+ payer,
460
+ outputMint,
461
+ inputAmount,
462
+ slippageBasisPoints = BigInt(1000),
463
+ fixedOutputAmount,
464
+ createOutputMintAta = true,
465
+ createInputMintAta = false,
466
+ protocolParams,
467
+ useExactSolAmount = true,
468
+ usePumpFunV2 = false,
469
+ } = params;
470
+
471
+ if (usePumpFunV2 || protocolParams.useV2Ix || isUsablePubkey(protocolParams.quoteMint)) {
472
+ return buildPumpFunBuyV2Instructions({
473
+ ...params,
474
+ createInputMintAta,
475
+ usePumpFunV2: true,
476
+ });
477
+ }
478
+
479
+ if (inputAmount === BigInt(0)) {
480
+ throw new Error("Amount cannot be zero");
481
+ }
482
+
483
+ const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
484
+ const instructions: TransactionInstruction[] = [];
485
+
486
+ const { bondingCurve, creatorVault, associatedBondingCurve, feeRecipient } =
487
+ protocolParams;
488
+ const creator = effectiveCreatorForTrade(protocolParams);
489
+ const creatorVaultAccount = (() => {
490
+ try {
491
+ return resolveCreatorVaultForIx(protocolParams, outputMint);
492
+ } catch {
493
+ return creatorVault;
494
+ }
495
+ })();
496
+
497
+ // Derive bonding curve address
498
+ const bondingCurveAddr =
499
+ bondingCurve.account.equals(PublicKey.default) || !bondingCurve.account
500
+ ? getBondingCurvePda(outputMint)
501
+ : bondingCurve.account;
502
+
503
+ // Get token program
504
+ const tokenProgramId = effectivePumpMintTokenProgram(outputMint, protocolParams);
505
+
506
+ // Derive associated bonding curve
507
+ const associatedBondingCurveAddr =
508
+ associatedBondingCurve && !associatedBondingCurve.equals(PublicKey.default)
509
+ ? associatedBondingCurve
510
+ : associatedTokenAddress(outputMint, bondingCurveAddr, tokenProgramId);
511
+
512
+ // Derive user token account
513
+ const userTokenAccount = associatedTokenAddress(outputMint, payerPubkey, tokenProgramId);
514
+
515
+ // Derive user volume accumulator
516
+ const userVolumeAccumulator = getPumpFunUserVolumeAccumulatorPda(payerPubkey);
517
+
518
+ // Create ATA if needed
519
+ if (createOutputMintAta) {
520
+ instructions.push(
521
+ createAssociatedTokenAccountInstruction(
522
+ payerPubkey,
523
+ userTokenAccount,
524
+ payerPubkey,
525
+ outputMint,
526
+ tokenProgramId
527
+ )
528
+ );
529
+ }
530
+
531
+ const feeRecipientPk = pumpFunFeeRecipientMeta(feeRecipient, bondingCurve.isMayhemMode);
532
+
533
+ // Derive bonding curve v2
534
+ const bondingCurveV2 = getBondingCurveV2Pda(outputMint);
535
+
536
+ // Track volume for cashback coins
537
+ const trackVolume = bondingCurve.isCashbackCoin
538
+ ? Buffer.from([1, 1])
539
+ : Buffer.from([1, 0]);
540
+
541
+ const buyTokenAmount = fixedOutputAmount
542
+ ? fixedOutputAmount
543
+ : getBuyTokenAmountFromSolAmount(inputAmount, bondingCurve, creator);
544
+ const maxSolCost = calculateWithSlippageBuy(inputAmount, slippageBasisPoints);
545
+
546
+ // Build instruction data
547
+ let data: Buffer;
548
+ if (useExactSolAmount) {
549
+ // buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64, track_volume)
550
+ const minTokensOut = fixedOutputAmount
551
+ ? fixedOutputAmount
552
+ : calculateWithSlippageSell(buyTokenAmount, slippageBasisPoints);
553
+ data = Buffer.alloc(26);
554
+ PUMPFUN_BUY_EXACT_SOL_IN_DISCRIMINATOR.copy(data, 0);
555
+ data.writeBigUInt64LE(inputAmount, 8);
556
+ data.writeBigUInt64LE(minTokensOut, 16);
557
+ trackVolume.copy(data, 24);
558
+ } else {
559
+ // buy(token_amount: u64, max_sol_cost: u64, track_volume)
560
+ data = Buffer.alloc(26);
561
+ PUMPFUN_BUY_DISCRIMINATOR.copy(data, 0);
562
+ data.writeBigUInt64LE(buyTokenAmount, 8);
563
+ data.writeBigUInt64LE(maxSolCost, 16);
564
+ trackVolume.copy(data, 24);
565
+ }
566
+
567
+ // Build accounts
568
+ const keys: AccountMeta[] = [
569
+ { pubkey: PUMPFUN_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
570
+ { pubkey: feeRecipientPk, isSigner: false, isWritable: true },
571
+ { pubkey: outputMint, isSigner: false, isWritable: false },
572
+ { pubkey: bondingCurveAddr, isSigner: false, isWritable: true },
573
+ { pubkey: associatedBondingCurveAddr, isSigner: false, isWritable: true },
574
+ { pubkey: userTokenAccount, isSigner: false, isWritable: true },
575
+ { pubkey: payerPubkey, isSigner: true, isWritable: true },
576
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
577
+ { pubkey: tokenProgramId, isSigner: false, isWritable: false },
578
+ { pubkey: creatorVaultAccount, isSigner: false, isWritable: true },
579
+ { pubkey: PUMPFUN_EVENT_AUTHORITY, isSigner: false, isWritable: false },
580
+ { pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
581
+ { pubkey: PUMPFUN_GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true },
582
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
583
+ { pubkey: PUMPFUN_FEE_CONFIG, isSigner: false, isWritable: false },
584
+ { pubkey: PUMPFUN_FEE_PROGRAM, isSigner: false, isWritable: false },
585
+ { pubkey: bondingCurveV2, isSigner: false, isWritable: false },
586
+ { pubkey: getPumpFunProtocolExtraFeeRecipientRandom(), isSigner: false, isWritable: true },
587
+ ];
588
+
589
+ instructions.push(
590
+ new TransactionInstruction({
591
+ keys,
592
+ programId: PUMPFUN_PROGRAM_ID,
593
+ data,
594
+ })
595
+ );
596
+
597
+ return instructions;
598
+ }
599
+
600
+ /**
601
+ * Build sell instructions for PumpFun protocol
602
+ * 100% port from Rust: src/instruction/pumpfun.rs build_sell_instructions
603
+ */
604
+ export function buildPumpFunSellInstructions(
605
+ params: PumpFunBuildSellParams
606
+ ): TransactionInstruction[] {
607
+ const {
608
+ payer,
609
+ inputMint,
610
+ inputAmount,
611
+ slippageBasisPoints = BigInt(1000),
612
+ fixedOutputAmount,
613
+ createOutputMintAta = false,
614
+ closeInputMintAta = false,
615
+ protocolParams,
616
+ usePumpFunV2 = false,
617
+ } = params;
618
+
619
+ if (usePumpFunV2 || protocolParams.useV2Ix || isUsablePubkey(protocolParams.quoteMint)) {
620
+ return buildPumpFunSellV2Instructions({
621
+ ...params,
622
+ createOutputMintAta,
623
+ usePumpFunV2: true,
624
+ });
625
+ }
626
+
627
+ if (inputAmount === BigInt(0)) {
628
+ throw new Error("Amount cannot be zero");
629
+ }
630
+
631
+ const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
632
+ const instructions: TransactionInstruction[] = [];
633
+
634
+ const {
635
+ bondingCurve,
636
+ creatorVault,
637
+ associatedBondingCurve,
638
+ closeTokenAccountWhenSell,
639
+ feeRecipient,
640
+ } = protocolParams;
641
+ const creator = effectiveCreatorForTrade(protocolParams);
642
+ const creatorVaultAccount = (() => {
643
+ try {
644
+ return resolveCreatorVaultForIx(protocolParams, inputMint);
645
+ } catch {
646
+ return creatorVault;
647
+ }
648
+ })();
649
+
650
+ // Derive bonding curve address
651
+ const bondingCurveAddr =
652
+ bondingCurve.account.equals(PublicKey.default) || !bondingCurve.account
653
+ ? getBondingCurvePda(inputMint)
654
+ : bondingCurve.account;
655
+
656
+ // Get token program
657
+ const tokenProgramId = effectivePumpMintTokenProgram(inputMint, protocolParams);
658
+
659
+ // Derive associated bonding curve
660
+ const associatedBondingCurveAddr =
661
+ associatedBondingCurve && !associatedBondingCurve.equals(PublicKey.default)
662
+ ? associatedBondingCurve
663
+ : associatedTokenAddress(inputMint, bondingCurveAddr, tokenProgramId);
664
+
665
+ // Derive user token account
666
+ const userTokenAccount = associatedTokenAddress(inputMint, payerPubkey, tokenProgramId);
667
+
668
+ const feeRecipientPk = pumpFunFeeRecipientMeta(feeRecipient, bondingCurve.isMayhemMode);
669
+
670
+ // Derive bonding curve v2
671
+ const bondingCurveV2 = getBondingCurveV2Pda(inputMint);
672
+
673
+ // Build instruction data (sell: token_amount, min_sol_output)
674
+ const expectedSolOutput = getSellSolAmountFromTokenAmount(inputAmount, bondingCurve, creator);
675
+ const minSolOutput = fixedOutputAmount
676
+ ? fixedOutputAmount
677
+ : calculateWithSlippageSell(expectedSolOutput, slippageBasisPoints);
678
+ const data = Buffer.alloc(24);
679
+ PUMPFUN_SELL_DISCRIMINATOR.copy(data, 0);
680
+ data.writeBigUInt64LE(inputAmount, 8);
681
+ data.writeBigUInt64LE(minSolOutput, 16);
682
+
683
+ // Build accounts
684
+ const keys: AccountMeta[] = [
685
+ { pubkey: PUMPFUN_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
686
+ { pubkey: feeRecipientPk, isSigner: false, isWritable: true },
687
+ { pubkey: inputMint, isSigner: false, isWritable: false },
688
+ { pubkey: bondingCurveAddr, isSigner: false, isWritable: true },
689
+ { pubkey: associatedBondingCurveAddr, isSigner: false, isWritable: true },
690
+ { pubkey: userTokenAccount, isSigner: false, isWritable: true },
691
+ { pubkey: payerPubkey, isSigner: true, isWritable: true },
692
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
693
+ { pubkey: creatorVaultAccount, isSigner: false, isWritable: true },
694
+ { pubkey: tokenProgramId, isSigner: false, isWritable: false },
695
+ { pubkey: PUMPFUN_EVENT_AUTHORITY, isSigner: false, isWritable: false },
696
+ { pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
697
+ { pubkey: PUMPFUN_FEE_CONFIG, isSigner: false, isWritable: false },
698
+ { pubkey: PUMPFUN_FEE_PROGRAM, isSigner: false, isWritable: false },
699
+ ];
700
+
701
+ // Add user volume accumulator for cashback coins
702
+ if (bondingCurve.isCashbackCoin) {
703
+ const userVolumeAccumulator = getPumpFunUserVolumeAccumulatorPda(payerPubkey);
704
+ keys.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
705
+ }
706
+
707
+ // Add bonding curve v2
708
+ keys.push({ pubkey: bondingCurveV2, isSigner: false, isWritable: false });
709
+ keys.push({ pubkey: getPumpFunProtocolExtraFeeRecipientRandom(), isSigner: false, isWritable: true });
710
+
711
+ instructions.push(
712
+ new TransactionInstruction({
713
+ keys,
714
+ programId: PUMPFUN_PROGRAM_ID,
715
+ data,
716
+ })
717
+ );
718
+
719
+ // Close token account if requested
720
+ if (closeInputMintAta || closeTokenAccountWhenSell) {
721
+ instructions.push(
722
+ createCloseAccountInstruction(
723
+ userTokenAccount,
724
+ payerPubkey,
725
+ payerPubkey,
726
+ [],
727
+ tokenProgramId
728
+ )
729
+ );
730
+ }
731
+
732
+ return instructions;
733
+ }
734
+
735
+ /**
736
+ * Build PumpFun V2 buy instructions (`buy_v2` / `buy_exact_quote_in_v2`).
737
+ */
738
+ export function buildPumpFunBuyV2Instructions(
739
+ params: PumpFunBuildBuyParams
740
+ ): TransactionInstruction[] {
741
+ const {
742
+ payer,
743
+ outputMint,
744
+ inputAmount,
745
+ slippageBasisPoints = BigInt(1000),
746
+ fixedOutputAmount,
747
+ createOutputMintAta = true,
748
+ createInputMintAta = false,
749
+ protocolParams,
750
+ useExactSolAmount = true,
751
+ } = params;
752
+
753
+ if (inputAmount === 0n) {
754
+ throw new Error("Amount cannot be zero");
755
+ }
756
+
757
+ const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
758
+ const instructions: TransactionInstruction[] = [];
759
+ const bondingCurve = protocolParams.bondingCurve;
760
+ const creator = effectiveCreatorForTrade(protocolParams);
761
+ const creatorVaultAccount = resolveCreatorVaultForIx(protocolParams, outputMint);
762
+
763
+ const bondingCurveAddr =
764
+ bondingCurve.account.equals(PublicKey.default) || !bondingCurve.account
765
+ ? getBondingCurvePda(outputMint)
766
+ : bondingCurve.account;
767
+
768
+ const baseTokenProgram = effectivePumpMintTokenProgram(outputMint, protocolParams);
769
+ const quoteMint = effectiveQuoteMint(protocolParams);
770
+ const quoteTokenProgram = TOKEN_PROGRAM_ID;
771
+
772
+ const associatedBaseBondingCurve = associatedTokenAddress(
773
+ outputMint,
774
+ bondingCurveAddr,
775
+ baseTokenProgram
776
+ );
777
+ const associatedBaseUser = associatedTokenAddress(outputMint, payerPubkey, baseTokenProgram);
778
+
779
+ const feeRecipientPk = pumpFunFeeRecipientMeta(
780
+ protocolParams.feeRecipient,
781
+ bondingCurve.isMayhemMode
782
+ );
783
+ const buybackFeeRecipient = getPumpFunBuybackFeeRecipientRandom();
784
+
785
+ const associatedQuoteFeeRecipient = associatedTokenAddress(
786
+ quoteMint,
787
+ feeRecipientPk,
788
+ quoteTokenProgram
789
+ );
790
+ const associatedQuoteBuybackFeeRecipient = associatedTokenAddress(
791
+ quoteMint,
792
+ buybackFeeRecipient,
793
+ quoteTokenProgram
794
+ );
795
+ const associatedQuoteBondingCurve = associatedTokenAddress(
796
+ quoteMint,
797
+ bondingCurveAddr,
798
+ quoteTokenProgram
799
+ );
800
+ const associatedQuoteUser = associatedTokenAddress(quoteMint, payerPubkey, quoteTokenProgram);
801
+ const associatedCreatorVault = associatedTokenAddress(
802
+ quoteMint,
803
+ creatorVaultAccount,
804
+ quoteTokenProgram
805
+ );
806
+ const sharingConfig = getPumpFunFeeSharingConfigPda(outputMint);
807
+ const userVolumeAccumulator = getPumpFunUserVolumeAccumulatorPda(payerPubkey);
808
+ const associatedUserVolumeAccumulator = associatedTokenAddress(
809
+ quoteMint,
810
+ userVolumeAccumulator,
811
+ quoteTokenProgram
812
+ );
813
+
814
+ if (createOutputMintAta) {
815
+ instructions.push(
816
+ createAssociatedTokenAccountIdempotentInstruction(
817
+ payerPubkey,
818
+ associatedBaseUser,
819
+ payerPubkey,
820
+ outputMint,
821
+ baseTokenProgram,
822
+ SPL_ASSOCIATED_TOKEN_PROGRAM_ID
823
+ )
824
+ );
825
+ }
826
+
827
+ if (createInputMintAta) {
828
+ instructions.push(
829
+ createAssociatedTokenAccountIdempotentInstruction(
830
+ payerPubkey,
831
+ associatedQuoteUser,
832
+ payerPubkey,
833
+ quoteMint,
834
+ quoteTokenProgram,
835
+ SPL_ASSOCIATED_TOKEN_PROGRAM_ID
836
+ )
837
+ );
838
+ }
839
+
840
+ const buyTokenAmount = fixedOutputAmount
841
+ ? fixedOutputAmount
842
+ : getBuyTokenAmountFromSolAmount(inputAmount, bondingCurve, creator);
843
+ const maxSolCost = calculateWithSlippageBuy(inputAmount, slippageBasisPoints);
844
+ let data: Buffer;
845
+ if (useExactSolAmount) {
846
+ const minTokensOut = fixedOutputAmount
847
+ ? fixedOutputAmount
848
+ : calculateWithSlippageSell(buyTokenAmount, slippageBasisPoints);
849
+ data = Buffer.alloc(24);
850
+ PUMPFUN_BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR.copy(data, 0);
851
+ data.writeBigUInt64LE(inputAmount, 8);
852
+ data.writeBigUInt64LE(minTokensOut, 16);
853
+ } else {
854
+ data = Buffer.alloc(24);
855
+ PUMPFUN_BUY_V2_DISCRIMINATOR.copy(data, 0);
856
+ data.writeBigUInt64LE(buyTokenAmount, 8);
857
+ data.writeBigUInt64LE(maxSolCost, 16);
858
+ }
859
+
860
+ const keys: AccountMeta[] = [
861
+ { pubkey: PUMPFUN_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
862
+ { pubkey: outputMint, isSigner: false, isWritable: false },
863
+ { pubkey: quoteMint, isSigner: false, isWritable: false },
864
+ { pubkey: baseTokenProgram, isSigner: false, isWritable: false },
865
+ { pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
866
+ { pubkey: SPL_ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
867
+ { pubkey: feeRecipientPk, isSigner: false, isWritable: true },
868
+ { pubkey: associatedQuoteFeeRecipient, isSigner: false, isWritable: true },
869
+ { pubkey: buybackFeeRecipient, isSigner: false, isWritable: false },
870
+ { pubkey: associatedQuoteBuybackFeeRecipient, isSigner: false, isWritable: true },
871
+ { pubkey: bondingCurveAddr, isSigner: false, isWritable: true },
872
+ { pubkey: associatedBaseBondingCurve, isSigner: false, isWritable: true },
873
+ { pubkey: associatedQuoteBondingCurve, isSigner: false, isWritable: true },
874
+ { pubkey: payerPubkey, isSigner: true, isWritable: true },
875
+ { pubkey: associatedBaseUser, isSigner: false, isWritable: true },
876
+ { pubkey: associatedQuoteUser, isSigner: false, isWritable: true },
877
+ { pubkey: creatorVaultAccount, isSigner: false, isWritable: true },
878
+ { pubkey: associatedCreatorVault, isSigner: false, isWritable: true },
879
+ { pubkey: sharingConfig, isSigner: false, isWritable: false },
880
+ { pubkey: PUMPFUN_GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true },
881
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
882
+ { pubkey: associatedUserVolumeAccumulator, isSigner: false, isWritable: true },
883
+ { pubkey: PUMPFUN_FEE_CONFIG, isSigner: false, isWritable: false },
884
+ { pubkey: PUMPFUN_FEE_PROGRAM, isSigner: false, isWritable: false },
885
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
886
+ { pubkey: PUMPFUN_EVENT_AUTHORITY, isSigner: false, isWritable: false },
887
+ { pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
888
+ ];
889
+
890
+ instructions.push(
891
+ new TransactionInstruction({
892
+ keys,
893
+ programId: PUMPFUN_PROGRAM_ID,
894
+ data,
895
+ })
896
+ );
897
+
898
+ return instructions;
899
+ }
900
+
901
+ /**
902
+ * Build PumpFun V2 sell instructions (`sell_v2`).
903
+ */
904
+ export function buildPumpFunSellV2Instructions(
905
+ params: PumpFunBuildSellParams
906
+ ): TransactionInstruction[] {
907
+ const {
908
+ payer,
909
+ inputMint,
910
+ inputAmount,
911
+ slippageBasisPoints = BigInt(1000),
912
+ fixedOutputAmount,
913
+ createOutputMintAta = false,
914
+ closeInputMintAta = false,
915
+ protocolParams,
916
+ } = params;
917
+
918
+ if (inputAmount === 0n) {
919
+ throw new Error("Amount cannot be zero");
920
+ }
921
+
922
+ const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
923
+ const instructions: TransactionInstruction[] = [];
924
+ const bondingCurve = protocolParams.bondingCurve;
925
+ const creator = effectiveCreatorForTrade(protocolParams);
926
+ const creatorVaultAccount = resolveCreatorVaultForSellV2(protocolParams, inputMint);
927
+
928
+ const bondingCurveAddr =
929
+ bondingCurve.account.equals(PublicKey.default) || !bondingCurve.account
930
+ ? getBondingCurvePda(inputMint)
931
+ : bondingCurve.account;
932
+
933
+ const baseTokenProgram = effectivePumpMintTokenProgram(inputMint, protocolParams);
934
+ const quoteMint = effectiveQuoteMint(protocolParams);
935
+ const quoteTokenProgram = TOKEN_PROGRAM_ID;
936
+
937
+ const associatedBaseBondingCurve = associatedTokenAddress(
938
+ inputMint,
939
+ bondingCurveAddr,
940
+ baseTokenProgram
941
+ );
942
+ const associatedBaseUser = associatedTokenAddress(inputMint, payerPubkey, baseTokenProgram);
943
+
944
+ const feeRecipientPk = pumpFunFeeRecipientMeta(
945
+ protocolParams.feeRecipient,
946
+ bondingCurve.isMayhemMode
947
+ );
948
+ const buybackFeeRecipient = getPumpFunBuybackFeeRecipientRandom();
949
+
950
+ const associatedQuoteFeeRecipient = associatedTokenAddress(
951
+ quoteMint,
952
+ feeRecipientPk,
953
+ quoteTokenProgram
954
+ );
955
+ const associatedQuoteBuybackFeeRecipient = associatedTokenAddress(
956
+ quoteMint,
957
+ buybackFeeRecipient,
958
+ quoteTokenProgram
959
+ );
960
+ const associatedQuoteBondingCurve = associatedTokenAddress(
961
+ quoteMint,
962
+ bondingCurveAddr,
963
+ quoteTokenProgram
964
+ );
965
+ const associatedQuoteUser = associatedTokenAddress(quoteMint, payerPubkey, quoteTokenProgram);
966
+ const associatedCreatorVault = associatedTokenAddress(
967
+ quoteMint,
968
+ creatorVaultAccount,
969
+ quoteTokenProgram
970
+ );
971
+ const sharingConfig = getPumpFunFeeSharingConfigPda(inputMint);
972
+ const userVolumeAccumulator = getPumpFunUserVolumeAccumulatorPda(payerPubkey);
973
+ const associatedUserVolumeAccumulator = associatedTokenAddress(
974
+ quoteMint,
975
+ userVolumeAccumulator,
976
+ quoteTokenProgram
977
+ );
978
+
979
+ if (createOutputMintAta) {
980
+ instructions.push(
981
+ createAssociatedTokenAccountIdempotentInstruction(
982
+ payerPubkey,
983
+ associatedQuoteUser,
984
+ payerPubkey,
985
+ quoteMint,
986
+ quoteTokenProgram,
987
+ SPL_ASSOCIATED_TOKEN_PROGRAM_ID
988
+ )
989
+ );
990
+ }
991
+
992
+ const expectedSolOutput = getSellSolAmountFromTokenAmount(inputAmount, bondingCurve, creator);
993
+ const minSolOutput = fixedOutputAmount
994
+ ? fixedOutputAmount
995
+ : calculateWithSlippageSell(expectedSolOutput, slippageBasisPoints);
996
+ const data = Buffer.alloc(24);
997
+ PUMPFUN_SELL_V2_DISCRIMINATOR.copy(data, 0);
998
+ data.writeBigUInt64LE(inputAmount, 8);
999
+ data.writeBigUInt64LE(minSolOutput, 16);
1000
+
1001
+ const keys: AccountMeta[] = [
1002
+ { pubkey: PUMPFUN_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
1003
+ { pubkey: inputMint, isSigner: false, isWritable: false },
1004
+ { pubkey: quoteMint, isSigner: false, isWritable: false },
1005
+ { pubkey: baseTokenProgram, isSigner: false, isWritable: false },
1006
+ { pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
1007
+ { pubkey: SPL_ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1008
+ { pubkey: feeRecipientPk, isSigner: false, isWritable: true },
1009
+ { pubkey: associatedQuoteFeeRecipient, isSigner: false, isWritable: true },
1010
+ { pubkey: buybackFeeRecipient, isSigner: false, isWritable: false },
1011
+ { pubkey: associatedQuoteBuybackFeeRecipient, isSigner: false, isWritable: true },
1012
+ { pubkey: bondingCurveAddr, isSigner: false, isWritable: true },
1013
+ { pubkey: associatedBaseBondingCurve, isSigner: false, isWritable: true },
1014
+ { pubkey: associatedQuoteBondingCurve, isSigner: false, isWritable: true },
1015
+ { pubkey: payerPubkey, isSigner: true, isWritable: true },
1016
+ { pubkey: associatedBaseUser, isSigner: false, isWritable: true },
1017
+ { pubkey: associatedQuoteUser, isSigner: false, isWritable: true },
1018
+ { pubkey: creatorVaultAccount, isSigner: false, isWritable: true },
1019
+ { pubkey: associatedCreatorVault, isSigner: false, isWritable: true },
1020
+ { pubkey: sharingConfig, isSigner: false, isWritable: false },
1021
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1022
+ { pubkey: associatedUserVolumeAccumulator, isSigner: false, isWritable: true },
1023
+ { pubkey: PUMPFUN_FEE_CONFIG, isSigner: false, isWritable: false },
1024
+ { pubkey: PUMPFUN_FEE_PROGRAM, isSigner: false, isWritable: false },
1025
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1026
+ { pubkey: PUMPFUN_EVENT_AUTHORITY, isSigner: false, isWritable: false },
1027
+ { pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
1028
+ ];
1029
+
1030
+ instructions.push(
1031
+ new TransactionInstruction({
1032
+ keys,
1033
+ programId: PUMPFUN_PROGRAM_ID,
1034
+ data,
1035
+ })
1036
+ );
1037
+
1038
+ if (closeInputMintAta || protocolParams.closeTokenAccountWhenSell) {
1039
+ instructions.push(
1040
+ createCloseAccountInstruction(
1041
+ associatedBaseUser,
1042
+ payerPubkey,
1043
+ payerPubkey,
1044
+ [],
1045
+ baseTokenProgram
1046
+ )
1047
+ );
1048
+ }
1049
+
1050
+ return instructions;
1051
+ }
1052
+
1053
+ /**
1054
+ * Build claim cashback instruction for PumpFun
1055
+ */
1056
+ export function buildPumpFunClaimCashbackInstruction(payer: PublicKey): TransactionInstruction {
1057
+ const userVolumeAccumulator = getPumpFunUserVolumeAccumulatorPda(payer);
1058
+
1059
+ const keys: AccountMeta[] = [
1060
+ { pubkey: payer, isSigner: true, isWritable: true },
1061
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1062
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1063
+ { pubkey: PUMPFUN_EVENT_AUTHORITY, isSigner: false, isWritable: false },
1064
+ { pubkey: PUMPFUN_PROGRAM_ID, isSigner: false, isWritable: false },
1065
+ ];
1066
+
1067
+ return new TransactionInstruction({
1068
+ keys,
1069
+ programId: PUMPFUN_PROGRAM_ID,
1070
+ data: PUMPFUN_CLAIM_CASHBACK_DISCRIMINATOR,
1071
+ });
1072
+ }
1073
+
1074
+ // ===== Async Fetch Functions - from Rust: src/instruction/utils/pumpfun.rs =====
1075
+
1076
+ /**
1077
+ * Fetch bonding curve account from RPC.
1078
+ * 100% from Rust: src/instruction/utils/pumpfun.rs fetch_bonding_curve_account
1079
+ */
1080
+ export async function fetchBondingCurveAccount(
1081
+ connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }> },
1082
+ mint: PublicKey
1083
+ ): Promise<{ bondingCurve: PumpFunBondingCurve; bondingCurvePda: PublicKey } | null> {
1084
+ const bondingCurvePda = getBondingCurvePda(mint);
1085
+ const account = await connection.getAccountInfo(bondingCurvePda);
1086
+
1087
+ if (!account?.value?.data || account.value.data.length === 0) {
1088
+ return null;
1089
+ }
1090
+
1091
+ const data = account.value.data;
1092
+ // Bonding curve data starts after 8-byte discriminator
1093
+ let offset = 8;
1094
+
1095
+ // virtual_token_reserves: u64
1096
+ const virtualTokenReserves = data.readBigUInt64LE(offset);
1097
+ offset += 8;
1098
+
1099
+ // virtual_sol_reserves: u64
1100
+ const virtualSolReserves = data.readBigUInt64LE(offset);
1101
+ offset += 8;
1102
+
1103
+ // real_token_reserves: u64
1104
+ const realTokenReserves = data.readBigUInt64LE(offset);
1105
+ offset += 8;
1106
+
1107
+ // real_sol_reserves: u64
1108
+ const realSolReserves = data.readBigUInt64LE(offset);
1109
+ offset += 8;
1110
+
1111
+ // token_total_supply: u64
1112
+ offset += 8; // skip
1113
+
1114
+ // complete: bool
1115
+ const complete = data.readUInt8(offset) === 1;
1116
+ offset += 1;
1117
+
1118
+ // creator: Pubkey (32 bytes)
1119
+ const creator = new PublicKey(data.subarray(offset, offset + 32));
1120
+ offset += 32;
1121
+
1122
+ // is_mayhem_mode: bool
1123
+ const isMayhemMode = data.readUInt8(offset) === 1;
1124
+ offset += 1;
1125
+
1126
+ // is_cashback_coin: bool
1127
+ const isCashbackCoin = data.readUInt8(offset) === 1;
1128
+
1129
+ return {
1130
+ bondingCurve: {
1131
+ account: bondingCurvePda,
1132
+ virtualTokenReserves,
1133
+ virtualSolReserves,
1134
+ realTokenReserves,
1135
+ creator,
1136
+ isMayhemMode,
1137
+ isCashbackCoin,
1138
+ },
1139
+ bondingCurvePda,
1140
+ };
1141
+ }
1142
+
1143
+ /**
1144
+ * Get creator from creator vault PDA.
1145
+ * 100% from Rust: src/instruction/utils/pumpfun.rs get_creator
1146
+ */
1147
+ export function getCreator(creatorVaultPda: PublicKey): PublicKey {
1148
+ // Check if creator_vault_pda is default
1149
+ const defaultBytes = Buffer.alloc(32);
1150
+ if (creatorVaultPda.equals(new PublicKey(defaultBytes))) {
1151
+ return new PublicKey(defaultBytes);
1152
+ }
1153
+
1154
+ // Check against default creator vault
1155
+ const defaultCreatorVault = getCreatorVaultPda(new PublicKey(defaultBytes));
1156
+ if (creatorVaultPda.equals(defaultCreatorVault)) {
1157
+ return new PublicKey(defaultBytes);
1158
+ }
1159
+
1160
+ return creatorVaultPda;
1161
+ }
1162
+
1163
+ /**
1164
+ * Get buy price (tokens received for SOL).
1165
+ * 100% from Rust: src/instruction/utils/pumpfun.rs get_buy_price
1166
+ */
1167
+ export function getBuyPrice(
1168
+ amount: bigint,
1169
+ virtualSolReserves: bigint,
1170
+ virtualTokenReserves: bigint,
1171
+ realTokenReserves: bigint
1172
+ ): bigint {
1173
+ if (amount === 0n) {
1174
+ return 0n;
1175
+ }
1176
+
1177
+ const n = virtualSolReserves * virtualTokenReserves;
1178
+ const i = virtualSolReserves + amount;
1179
+ const r = n / i + 1n;
1180
+ const s = virtualTokenReserves - r;
1181
+
1182
+ return s < realTokenReserves ? s : realTokenReserves;
1183
+ }