pump-trader 1.0.1 → 1.0.2

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.
@@ -0,0 +1,177 @@
1
+ import { Connection, PublicKey, Transaction, TransactionInstruction, Keypair } from "@solana/web3.js";
2
+ interface TradeOptions {
3
+ maxSolPerTx: bigint;
4
+ slippage: {
5
+ base: number;
6
+ max?: number;
7
+ min?: number;
8
+ impactFactor?: number;
9
+ };
10
+ priority: {
11
+ base: number;
12
+ enableRandom?: boolean;
13
+ randomRange?: number;
14
+ };
15
+ }
16
+ interface PendingTransaction {
17
+ signature: string;
18
+ lastValidBlockHeight: number;
19
+ index: number;
20
+ }
21
+ interface FailedTransaction {
22
+ index: number;
23
+ error: string;
24
+ }
25
+ interface TradeResult {
26
+ pendingTransactions: PendingTransaction[];
27
+ failedTransactions: FailedTransaction[];
28
+ }
29
+ interface BondingCurveState {
30
+ virtualTokenReserves: bigint;
31
+ virtualSolReserves: bigint;
32
+ realTokenReserves: bigint;
33
+ realSolReserves: bigint;
34
+ tokenTotalSupply: bigint;
35
+ complete: boolean;
36
+ }
37
+ interface BondingInfo {
38
+ bonding: PublicKey;
39
+ state: BondingCurveState;
40
+ creator: PublicKey;
41
+ }
42
+ interface PoolReserves {
43
+ baseAmount: bigint;
44
+ quoteAmount: bigint;
45
+ baseDecimals: number;
46
+ quoteDecimals: number;
47
+ }
48
+ interface TradeEvent {
49
+ mint: string;
50
+ solAmount: bigint;
51
+ tokenAmount: bigint;
52
+ isBuy: boolean;
53
+ user: string;
54
+ timestamp: number;
55
+ signature: string;
56
+ }
57
+ interface GlobalState {
58
+ initialized: boolean;
59
+ authority: PublicKey;
60
+ feeRecipient: PublicKey;
61
+ withdrawAuthority: PublicKey;
62
+ initialVirtualTokenReserves: bigint;
63
+ initialVirtualSolReserves: bigint;
64
+ initialRealTokenReserves: bigint;
65
+ tokenTotalSupply: bigint;
66
+ feeBasisPoints: bigint;
67
+ }
68
+ interface TokenProgramType {
69
+ type: "TOKEN_PROGRAM_ID" | "TOKEN_2022_PROGRAM_ID";
70
+ programId: PublicKey;
71
+ }
72
+ interface PoolInfo {
73
+ pool: PublicKey;
74
+ poolAuthority: PublicKey;
75
+ poolKeys: any;
76
+ globalConfig: any;
77
+ }
78
+ interface MetadataInfo {
79
+ name: string;
80
+ symbol: string;
81
+ uri: string;
82
+ }
83
+ export declare class PumpTrader {
84
+ private connection;
85
+ private wallet;
86
+ private global;
87
+ private globalState;
88
+ private tokenProgramCache;
89
+ constructor(rpc: string, privateKey: string);
90
+ /**
91
+ * 自动检测代币使用的 token program
92
+ */
93
+ detectTokenProgram(tokenAddr: string): Promise<TokenProgramType>;
94
+ /**
95
+ * 检测代币是否在外盘 (AMM)
96
+ */
97
+ isAmmCompleted(tokenAddr: string): Promise<boolean>;
98
+ /**
99
+ * 自动判断应该使用内盘还是外盘
100
+ */
101
+ getTradeMode(tokenAddr: string): Promise<"bonding" | "amm">;
102
+ loadGlobal(): Promise<GlobalState>;
103
+ getBondingPda(mint: PublicKey): PublicKey;
104
+ loadBonding(mint: PublicKey): Promise<BondingInfo>;
105
+ calcBuy(solIn: bigint, state: BondingCurveState): bigint;
106
+ calcSell(tokenIn: bigint, state: BondingCurveState): bigint;
107
+ calculateAmmBuyOutput(quoteIn: bigint, reserves: PoolReserves): bigint;
108
+ calculateAmmSellOutput(baseIn: bigint, reserves: PoolReserves): bigint;
109
+ getPriceAndStatus(tokenAddr: string): Promise<{
110
+ price: number;
111
+ completed: boolean;
112
+ }>;
113
+ getAmmPrice(mint: PublicKey): Promise<number>;
114
+ /**
115
+ * 查询代币余额
116
+ * @param tokenAddr - 代币地址(可选),如果不传则返回所有代币
117
+ * @returns 如果传入地址则返回该代币的余额数字,否则返回所有代币的详细信息
118
+ */
119
+ tokenBalance(tokenAddr?: string): Promise<number | Array<{
120
+ mint: string;
121
+ amount: number;
122
+ decimals: number;
123
+ uiAmount: number;
124
+ }>>;
125
+ /**
126
+ * 获取账户所有代币余额(仅显示余额 > 0 的)
127
+ * @returns 代币信息数组,包含mint地址、余额等信息
128
+ */
129
+ getAllTokenBalances(): Promise<Array<{
130
+ mint: string;
131
+ amount: number;
132
+ decimals: number;
133
+ uiAmount: number;
134
+ }>>;
135
+ solBalance(): Promise<number>;
136
+ ensureAta(tx: Transaction, mint: PublicKey, tokenProgram?: PublicKey): Promise<PublicKey>;
137
+ ensureWSOLAta(tx: Transaction, owner: PublicKey, mode: "buy" | "sell", lamports?: bigint): Promise<PublicKey>;
138
+ genPriority(priorityOpt: any): number;
139
+ calcSlippage({ tradeSize, reserve, slippageOpt }: any): number;
140
+ splitByMax(total: bigint, max: bigint): bigint[];
141
+ splitIntoN(total: bigint, n: number): bigint[];
142
+ /**
143
+ * 自动判断内盘/外盘并执行买入
144
+ */
145
+ autoBuy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult>;
146
+ /**
147
+ * 自动判断内盘/外盘并执行卖出
148
+ */
149
+ autoSell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult>;
150
+ buy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult>;
151
+ sell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult>;
152
+ ammBuy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult>;
153
+ ammSell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult>;
154
+ getAmmPoolInfo(mint: PublicKey): Promise<PoolInfo>;
155
+ parseAmmGlobalConfig(data: Buffer, address: PublicKey): {
156
+ address: PublicKey;
157
+ admin: PublicKey;
158
+ protocolFeeRecipients: PublicKey[];
159
+ };
160
+ getAmmPoolReserves(poolKeys: any): Promise<PoolReserves>;
161
+ createAmmBuyInstruction(poolInfo: PoolInfo, userBaseAta: PublicKey, userQuoteAta: PublicKey, baseAmountOut: bigint, maxQuoteAmountIn: bigint): TransactionInstruction;
162
+ createAmmSellInstruction(poolInfo: PoolInfo, userBaseAta: PublicKey, userQuoteAta: PublicKey, baseAmountIn: bigint, minQuoteAmountOut: bigint): TransactionInstruction;
163
+ confirmTransactionWithPolling(signature: string, lastValidBlockHeight: number, maxAttempts?: number, delayMs?: number): Promise<string>;
164
+ listenTrades(callback: (event: TradeEvent) => void, mintFilter?: PublicKey | null): number;
165
+ fetchMeta(tokenAddr: string): Promise<MetadataInfo | null>;
166
+ getWallet(): Keypair;
167
+ getConnection(): Connection;
168
+ /**
169
+ * 清除token program缓存
170
+ */
171
+ clearTokenProgramCache(tokenAddr: string): void;
172
+ /**
173
+ * 获取缓存的token program信息
174
+ */
175
+ getCachedTokenProgram(tokenAddr: string): TokenProgramType | undefined;
176
+ }
177
+ export type { TradeOptions, PendingTransaction, FailedTransaction, TradeResult, BondingCurveState, BondingInfo, PoolReserves, TradeEvent, GlobalState, TokenProgramType, PoolInfo, MetadataInfo };
package/dist/index.js ADDED
@@ -0,0 +1,954 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PumpTrader = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const spl_token_1 = require("@solana/spl-token");
9
+ const bn_js_1 = __importDefault(require("bn.js"));
10
+ const bs58_1 = __importDefault(require("bs58"));
11
+ /* ================= 常量定义 ================= */
12
+ const PROGRAM_IDS = {
13
+ PUMP: new web3_js_1.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"),
14
+ PUMP_AMM: new web3_js_1.PublicKey("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"),
15
+ METADATA: new web3_js_1.PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
16
+ FEE: new web3_js_1.PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
17
+ EVENT_AUTHORITY: new web3_js_1.PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
18
+ };
19
+ const SOL_MINT = new web3_js_1.PublicKey("So11111111111111111111111111111111111111112");
20
+ const SEEDS = {
21
+ FEE_CONFIG: new Uint8Array([
22
+ 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
23
+ 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176
24
+ ]),
25
+ AMM_FEE_CONFIG: Buffer.from([
26
+ 12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101,
27
+ 244, 41, 141, 49, 86, 213, 113, 180, 212, 248, 9, 12, 24, 233, 168, 99
28
+ ]),
29
+ GLOBAL: Buffer.from("global"),
30
+ BONDING: Buffer.from("bonding-curve")
31
+ };
32
+ const DISCRIMINATORS = {
33
+ BUY: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
34
+ SELL: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
35
+ TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238])
36
+ };
37
+ const AMM_FEE_BPS = 100n;
38
+ const BPS_DENOMINATOR = 10000n;
39
+ /* ================= 工具函数 ================= */
40
+ const u64 = (v) => {
41
+ const bn = typeof v === 'bigint' ? new bn_js_1.default(v.toString()) : new bn_js_1.default(v.toString());
42
+ return bn.toArrayLike(Buffer, "le", 8);
43
+ };
44
+ const readU64 = (buf, offset) => {
45
+ const value = buf.readBigUInt64LE(offset);
46
+ return [value, offset + 8];
47
+ };
48
+ const readU32 = (buf, offsetObj) => {
49
+ const value = buf.readUInt32LE(offsetObj.offset);
50
+ offsetObj.offset += 4;
51
+ return value;
52
+ };
53
+ const readString = (buf, offsetObj) => {
54
+ const len = readU32(buf, offsetObj);
55
+ const str = buf.slice(offsetObj.offset, offsetObj.offset + len).toString("utf8");
56
+ offsetObj.offset += len;
57
+ return str;
58
+ };
59
+ /* ================= 解析函数 ================= */
60
+ function parseMetadataAccount(data) {
61
+ const offsetObj = { offset: 1 };
62
+ const updateAuthority = new web3_js_1.PublicKey(data.slice(offsetObj.offset, offsetObj.offset + 32));
63
+ offsetObj.offset += 32;
64
+ const mint = new web3_js_1.PublicKey(data.slice(offsetObj.offset, offsetObj.offset + 32));
65
+ offsetObj.offset += 32;
66
+ const name = readString(data, offsetObj);
67
+ const symbol = readString(data, offsetObj);
68
+ const uri = readString(data, offsetObj);
69
+ return {
70
+ updateAuthority: updateAuthority.toBase58(),
71
+ mint: mint.toBase58(),
72
+ name,
73
+ symbol,
74
+ uri
75
+ };
76
+ }
77
+ function parsePoolKeys(data) {
78
+ if (!data || data.length < 280) {
79
+ throw new Error('Invalid pool account data');
80
+ }
81
+ let offset = 8;
82
+ const poolBump = data.readUInt8(offset);
83
+ offset += 1;
84
+ const index = data.readUInt16LE(offset);
85
+ offset += 2;
86
+ const creator = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
87
+ offset += 32;
88
+ const baseMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
89
+ offset += 32;
90
+ const quoteMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
91
+ offset += 32;
92
+ const lpMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
93
+ offset += 32;
94
+ const poolBaseTokenAccount = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
95
+ offset += 32;
96
+ const poolQuoteTokenAccount = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
97
+ offset += 32;
98
+ const lpSupply = data.readBigUInt64LE(offset);
99
+ offset += 8;
100
+ const coinCreator = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
101
+ offset += 32;
102
+ const isMayhemMode = data.readUInt8(offset) === 1;
103
+ return {
104
+ creator,
105
+ baseMint,
106
+ quoteMint,
107
+ lpMint,
108
+ poolBaseTokenAccount,
109
+ poolQuoteTokenAccount,
110
+ coinCreator,
111
+ isMayhemMode
112
+ };
113
+ }
114
+ /* ================= PumpTrader 类 ================= */
115
+ class PumpTrader {
116
+ constructor(rpc, privateKey) {
117
+ this.connection = new web3_js_1.Connection(rpc, "confirmed");
118
+ this.wallet = web3_js_1.Keypair.fromSecretKey(bs58_1.default.decode(privateKey));
119
+ this.global = web3_js_1.PublicKey.findProgramAddressSync([SEEDS.GLOBAL], PROGRAM_IDS.PUMP)[0];
120
+ this.globalState = null;
121
+ this.tokenProgramCache = new Map();
122
+ }
123
+ /* ---------- Token Program 检测 ---------- */
124
+ /**
125
+ * 自动检测代币使用的 token program
126
+ */
127
+ async detectTokenProgram(tokenAddr) {
128
+ // 检查缓存
129
+ if (this.tokenProgramCache.has(tokenAddr)) {
130
+ return this.tokenProgramCache.get(tokenAddr);
131
+ }
132
+ const mint = new web3_js_1.PublicKey(tokenAddr);
133
+ try {
134
+ // 首先尝试获取 TOKEN_2022 的代币信息
135
+ const mintData = await (0, spl_token_1.getMint)(this.connection, mint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID);
136
+ const result = {
137
+ type: "TOKEN_2022_PROGRAM_ID",
138
+ programId: spl_token_1.TOKEN_2022_PROGRAM_ID
139
+ };
140
+ this.tokenProgramCache.set(tokenAddr, result);
141
+ return result;
142
+ }
143
+ catch (e) {
144
+ try {
145
+ // 如果失败,尝试标准 TOKEN_PROGRAM_ID
146
+ const mintData = await (0, spl_token_1.getMint)(this.connection, mint, "confirmed", spl_token_1.TOKEN_PROGRAM_ID);
147
+ const result = {
148
+ type: "TOKEN_PROGRAM_ID",
149
+ programId: spl_token_1.TOKEN_PROGRAM_ID
150
+ };
151
+ this.tokenProgramCache.set(tokenAddr, result);
152
+ return result;
153
+ }
154
+ catch (error) {
155
+ throw new Error(`Failed to detect token program for ${tokenAddr}: ${error}`);
156
+ }
157
+ }
158
+ }
159
+ /* ---------- 内盘/外盘检测 ---------- */
160
+ /**
161
+ * 检测代币是否在外盘 (AMM)
162
+ */
163
+ async isAmmCompleted(tokenAddr) {
164
+ try {
165
+ const mint = new web3_js_1.PublicKey(tokenAddr);
166
+ const { state } = await this.loadBonding(mint);
167
+ return state.complete;
168
+ }
169
+ catch (error) {
170
+ // 如果无法加载内盘,说明可能已经在外盘
171
+ return true;
172
+ }
173
+ }
174
+ /**
175
+ * 自动判断应该使用内盘还是外盘
176
+ */
177
+ async getTradeMode(tokenAddr) {
178
+ const isAmmMode = await this.isAmmCompleted(tokenAddr);
179
+ return isAmmMode ? "amm" : "bonding";
180
+ }
181
+ /* ---------- Global State ---------- */
182
+ async loadGlobal() {
183
+ const acc = await this.connection.getAccountInfo(this.global);
184
+ if (!acc)
185
+ throw new Error("Global account not found");
186
+ const data = acc.data;
187
+ let offset = 8;
188
+ const readBool = () => data[offset++] === 1;
189
+ const readPk = () => {
190
+ const pk = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
191
+ offset += 32;
192
+ return pk;
193
+ };
194
+ const readU64 = () => {
195
+ const v = data.readBigUInt64LE(offset);
196
+ offset += 8;
197
+ return v;
198
+ };
199
+ this.globalState = {
200
+ initialized: readBool(),
201
+ authority: readPk(),
202
+ feeRecipient: readPk(),
203
+ withdrawAuthority: readPk(),
204
+ initialVirtualTokenReserves: readU64(),
205
+ initialVirtualSolReserves: readU64(),
206
+ initialRealTokenReserves: readU64(),
207
+ tokenTotalSupply: readU64(),
208
+ feeBasisPoints: readU64()
209
+ };
210
+ return this.globalState;
211
+ }
212
+ /* ---------- Bonding Curve ---------- */
213
+ getBondingPda(mint) {
214
+ return web3_js_1.PublicKey.findProgramAddressSync([SEEDS.BONDING, mint.toBuffer()], PROGRAM_IDS.PUMP)[0];
215
+ }
216
+ async loadBonding(mint) {
217
+ const bonding = this.getBondingPda(mint);
218
+ const acc = await this.connection.getAccountInfo(bonding);
219
+ if (!acc)
220
+ throw new Error("Bonding curve not found");
221
+ let offset = 8;
222
+ const data = acc.data;
223
+ const state = {};
224
+ [state.virtualTokenReserves, offset] = readU64(data, offset);
225
+ [state.virtualSolReserves, offset] = readU64(data, offset);
226
+ [state.realTokenReserves, offset] = readU64(data, offset);
227
+ [state.realSolReserves, offset] = readU64(data, offset);
228
+ [state.tokenTotalSupply, offset] = readU64(data, offset);
229
+ state.complete = data[offset] === 1;
230
+ offset += 1;
231
+ const creator = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
232
+ return { bonding, state, creator };
233
+ }
234
+ /* ---------- 价格计算 ---------- */
235
+ calcBuy(solIn, state) {
236
+ const newVirtualSol = state.virtualSolReserves + solIn;
237
+ const newVirtualToken = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
238
+ return state.virtualTokenReserves - newVirtualToken;
239
+ }
240
+ calcSell(tokenIn, state) {
241
+ const newVirtualToken = state.virtualTokenReserves + tokenIn;
242
+ const newVirtualSol = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
243
+ return state.virtualSolReserves - newVirtualSol;
244
+ }
245
+ calculateAmmBuyOutput(quoteIn, reserves) {
246
+ const quoteInAfterFee = (quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
247
+ const numerator = reserves.baseAmount * quoteInAfterFee;
248
+ const denominator = reserves.quoteAmount + quoteInAfterFee;
249
+ return numerator / denominator;
250
+ }
251
+ calculateAmmSellOutput(baseIn, reserves) {
252
+ const baseInAfterFee = (baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
253
+ const numerator = reserves.quoteAmount * baseInAfterFee;
254
+ const denominator = reserves.baseAmount + baseInAfterFee;
255
+ return numerator / denominator;
256
+ }
257
+ /* ---------- 价格查询 ---------- */
258
+ async getPriceAndStatus(tokenAddr) {
259
+ const mint = new web3_js_1.PublicKey(tokenAddr);
260
+ const { state } = await this.loadBonding(mint);
261
+ if (state.complete) {
262
+ const price = await this.getAmmPrice(mint);
263
+ return { price, completed: true };
264
+ }
265
+ const oneToken = BigInt(1_000_000);
266
+ const solOut = this.calcSell(oneToken, state);
267
+ const price = Number(solOut) / 1e9;
268
+ return { price, completed: false };
269
+ }
270
+ async getAmmPrice(mint) {
271
+ const [poolCreator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool-authority"), mint.toBuffer()], PROGRAM_IDS.PUMP);
272
+ const indexBuffer = new bn_js_1.default(0).toArrayLike(Buffer, "le", 2);
273
+ const [pool] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), SOL_MINT.toBuffer()], PROGRAM_IDS.PUMP_AMM);
274
+ const acc = await this.connection.getAccountInfo(pool);
275
+ if (!acc)
276
+ throw new Error("Pool not found");
277
+ const poolKeys = parsePoolKeys(acc.data);
278
+ const [baseInfo, quoteInfo] = await Promise.all([
279
+ this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
280
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
281
+ ]);
282
+ return quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
283
+ }
284
+ /* ---------- 余额查询 ---------- */
285
+ /**
286
+ * 查询代币余额
287
+ * @param tokenAddr - 代币地址(可选),如果不传则返回所有代币
288
+ * @returns 如果传入地址则返回该代币的余额数字,否则返回所有代币的详细信息
289
+ */
290
+ async tokenBalance(tokenAddr) {
291
+ if (tokenAddr) {
292
+ // 查询单个代币
293
+ const mint = new web3_js_1.PublicKey(tokenAddr);
294
+ const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(this.wallet.publicKey, { mint });
295
+ return tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount || 0;
296
+ }
297
+ else {
298
+ // 查询所有代币
299
+ return this.getAllTokenBalances();
300
+ }
301
+ }
302
+ /**
303
+ * 获取账户所有代币余额(仅显示余额 > 0 的)
304
+ * @returns 代币信息数组,包含mint地址、余额等信息
305
+ */
306
+ async getAllTokenBalances() {
307
+ const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(this.wallet.publicKey, { programId: spl_token_1.TOKEN_PROGRAM_ID });
308
+ const balances = tokenAccounts.value
309
+ .map((account) => {
310
+ const parsed = account.account.data.parsed;
311
+ if (parsed.type !== 'account')
312
+ return null;
313
+ const tokenAmount = parsed.info.tokenAmount;
314
+ if (Number(tokenAmount.amount) === 0)
315
+ return null; // 跳过余额为0的
316
+ return {
317
+ mint: parsed.info.mint,
318
+ amount: BigInt(tokenAmount.amount),
319
+ decimals: tokenAmount.decimals,
320
+ uiAmount: tokenAmount.uiAmount || 0
321
+ };
322
+ })
323
+ .filter((item) => item !== null);
324
+ // 同时查询 TOKEN_2022_PROGRAM_ID
325
+ const token2022Accounts = await this.connection.getParsedTokenAccountsByOwner(this.wallet.publicKey, { programId: spl_token_1.TOKEN_2022_PROGRAM_ID });
326
+ const token2022Balances = token2022Accounts.value
327
+ .map((account) => {
328
+ const parsed = account.account.data.parsed;
329
+ if (parsed.type !== 'account')
330
+ return null;
331
+ const tokenAmount = parsed.info.tokenAmount;
332
+ if (Number(tokenAmount.amount) === 0)
333
+ return null; // 跳过余额为0的
334
+ return {
335
+ mint: parsed.info.mint,
336
+ amount: BigInt(tokenAmount.amount),
337
+ decimals: tokenAmount.decimals,
338
+ uiAmount: tokenAmount.uiAmount || 0
339
+ };
340
+ })
341
+ .filter((item) => item !== null);
342
+ // 合并并去重
343
+ const allBalances = [...balances, ...token2022Balances];
344
+ const seen = new Set();
345
+ const uniqueBalances = allBalances
346
+ .filter((b) => {
347
+ if (seen.has(b.mint))
348
+ return false;
349
+ seen.add(b.mint);
350
+ return true;
351
+ })
352
+ .map((b) => ({
353
+ mint: b.mint,
354
+ amount: Number(b.amount),
355
+ decimals: b.decimals,
356
+ uiAmount: b.uiAmount
357
+ }));
358
+ return uniqueBalances;
359
+ }
360
+ async solBalance() {
361
+ const balance = await this.connection.getBalance(this.wallet.publicKey);
362
+ return balance / 1e9;
363
+ }
364
+ /* ---------- ATA 管理 ---------- */
365
+ async ensureAta(tx, mint, tokenProgram) {
366
+ const program = tokenProgram || spl_token_1.TOKEN_2022_PROGRAM_ID;
367
+ const ata = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.wallet.publicKey, false, program, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
368
+ const acc = await this.connection.getAccountInfo(ata);
369
+ if (!acc) {
370
+ tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.wallet.publicKey, ata, this.wallet.publicKey, mint, program, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
371
+ }
372
+ return ata;
373
+ }
374
+ async ensureWSOLAta(tx, owner, mode, lamports) {
375
+ const wsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, owner, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
376
+ const acc = await this.connection.getAccountInfo(wsolAta);
377
+ if (!acc) {
378
+ tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(owner, wsolAta, owner, SOL_MINT, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
379
+ }
380
+ if (mode === 'buy' && lamports) {
381
+ tx.add(web3_js_1.SystemProgram.transfer({
382
+ fromPubkey: owner,
383
+ toPubkey: wsolAta,
384
+ lamports: Number(lamports)
385
+ }));
386
+ tx.add((0, spl_token_1.createSyncNativeInstruction)(wsolAta));
387
+ }
388
+ return wsolAta;
389
+ }
390
+ /* ---------- 交易参数处理 ---------- */
391
+ genPriority(priorityOpt) {
392
+ if (!priorityOpt?.enableRandom || !priorityOpt.randomRange) {
393
+ return priorityOpt.base;
394
+ }
395
+ return priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange);
396
+ }
397
+ calcSlippage({ tradeSize, reserve, slippageOpt }) {
398
+ const impact = Number(tradeSize) / Math.max(Number(reserve), 1);
399
+ const factor = slippageOpt.impactFactor ?? 1;
400
+ let slip = slippageOpt.base + Math.floor(impact * 10_000 * factor);
401
+ if (slippageOpt.max !== undefined) {
402
+ slip = Math.min(slip, slippageOpt.max);
403
+ }
404
+ if (slippageOpt.min !== undefined) {
405
+ slip = Math.max(slip, slippageOpt.min);
406
+ }
407
+ return slip;
408
+ }
409
+ splitByMax(total, max) {
410
+ const chunks = [];
411
+ let remaining = total;
412
+ while (remaining > 0n) {
413
+ const chunk = remaining > max ? max : remaining;
414
+ chunks.push(chunk);
415
+ remaining -= chunk;
416
+ }
417
+ return chunks;
418
+ }
419
+ splitIntoN(total, n) {
420
+ const chunks = [];
421
+ const part = total / BigInt(n);
422
+ let remaining = total;
423
+ for (let i = 0; i < n; i++) {
424
+ const chunk = i === n - 1 ? remaining : part;
425
+ chunks.push(chunk);
426
+ remaining -= chunk;
427
+ }
428
+ return chunks;
429
+ }
430
+ /* ---------- 统一交易接口 ---------- */
431
+ /**
432
+ * 自动判断内盘/外盘并执行买入
433
+ */
434
+ async autoBuy(tokenAddr, totalSolIn, tradeOpt) {
435
+ const mode = await this.getTradeMode(tokenAddr);
436
+ if (mode === "bonding") {
437
+ return this.buy(tokenAddr, totalSolIn, tradeOpt);
438
+ }
439
+ else {
440
+ return this.ammBuy(tokenAddr, totalSolIn, tradeOpt);
441
+ }
442
+ }
443
+ /**
444
+ * 自动判断内盘/外盘并执行卖出
445
+ */
446
+ async autoSell(tokenAddr, totalTokenIn, tradeOpt) {
447
+ const mode = await this.getTradeMode(tokenAddr);
448
+ if (mode === "bonding") {
449
+ return this.sell(tokenAddr, totalTokenIn, tradeOpt);
450
+ }
451
+ else {
452
+ return this.ammSell(tokenAddr, totalTokenIn, tradeOpt);
453
+ }
454
+ }
455
+ /* ---------- 内盘交易 ---------- */
456
+ async buy(tokenAddr, totalSolIn, tradeOpt) {
457
+ const mint = new web3_js_1.PublicKey(tokenAddr);
458
+ const tokenProgram = await this.detectTokenProgram(tokenAddr);
459
+ if (!this.globalState)
460
+ await this.loadGlobal();
461
+ const { bonding, state, creator } = await this.loadBonding(mint);
462
+ if (state.complete)
463
+ throw new Error("Bonding curve already completed");
464
+ const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
465
+ const pendingTransactions = [];
466
+ const failedTransactions = [];
467
+ const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bonding, true, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
468
+ const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
469
+ const [globalVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_volume_accumulator")], PROGRAM_IDS.PUMP);
470
+ const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()], PROGRAM_IDS.PUMP);
471
+ const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
472
+ for (let i = 0; i < solChunks.length; i++) {
473
+ try {
474
+ const solIn = solChunks[i];
475
+ const tokenOut = this.calcBuy(solIn, state);
476
+ const slippageBps = this.calcSlippage({
477
+ tradeSize: solIn,
478
+ reserve: state.virtualSolReserves,
479
+ slippageOpt: tradeOpt.slippage
480
+ });
481
+ const maxSol = (solIn * BigInt(10_000 + slippageBps)) / 10000n;
482
+ const priority = this.genPriority(tradeOpt.priority);
483
+ const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
484
+ const userAta = await this.ensureAta(tx, mint, tokenProgram.programId);
485
+ tx.add(new web3_js_1.TransactionInstruction({
486
+ programId: PROGRAM_IDS.PUMP,
487
+ keys: [
488
+ { pubkey: this.global, isSigner: false, isWritable: false },
489
+ { pubkey: this.globalState.feeRecipient, isSigner: false, isWritable: true },
490
+ { pubkey: mint, isSigner: false, isWritable: false },
491
+ { pubkey: bonding, isSigner: false, isWritable: true },
492
+ { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
493
+ { pubkey: userAta, isSigner: false, isWritable: true },
494
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
495
+ { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
496
+ { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
497
+ { pubkey: creatorVault, isSigner: false, isWritable: true },
498
+ { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
499
+ { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
500
+ { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
501
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
502
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
503
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
504
+ ],
505
+ data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
506
+ }));
507
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('finalized');
508
+ tx.recentBlockhash = blockhash;
509
+ tx.feePayer = this.wallet.publicKey;
510
+ tx.sign(this.wallet);
511
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
512
+ skipPreflight: false,
513
+ maxRetries: 2
514
+ });
515
+ pendingTransactions.push({
516
+ signature,
517
+ lastValidBlockHeight,
518
+ index: i
519
+ });
520
+ }
521
+ catch (e) {
522
+ failedTransactions.push({
523
+ index: i,
524
+ error: e.message
525
+ });
526
+ }
527
+ }
528
+ return { pendingTransactions, failedTransactions };
529
+ }
530
+ async sell(tokenAddr, totalTokenIn, tradeOpt) {
531
+ const mint = new web3_js_1.PublicKey(tokenAddr);
532
+ const tokenProgram = await this.detectTokenProgram(tokenAddr);
533
+ if (!this.globalState)
534
+ await this.loadGlobal();
535
+ const { bonding, state, creator } = await this.loadBonding(mint);
536
+ if (state.complete)
537
+ throw new Error("Bonding curve already completed");
538
+ const totalSolOut = this.calcSell(totalTokenIn, state);
539
+ const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
540
+ ? [totalTokenIn]
541
+ : this.splitIntoN(totalTokenIn, Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx));
542
+ const pendingTransactions = [];
543
+ const failedTransactions = [];
544
+ const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bonding, true, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
545
+ const userAta = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.wallet.publicKey, false, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
546
+ const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
547
+ const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
548
+ for (let i = 0; i < tokenChunks.length; i++) {
549
+ try {
550
+ const tokenIn = tokenChunks[i];
551
+ const solOut = this.calcSell(tokenIn, state);
552
+ const slippageBps = this.calcSlippage({
553
+ tradeSize: tokenIn,
554
+ reserve: state.virtualTokenReserves,
555
+ slippageOpt: tradeOpt.slippage
556
+ });
557
+ const minSol = (solOut * BigInt(10_000 - slippageBps)) / 10000n;
558
+ const priority = this.genPriority(tradeOpt.priority);
559
+ const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
560
+ tx.add(new web3_js_1.TransactionInstruction({
561
+ programId: PROGRAM_IDS.PUMP,
562
+ keys: [
563
+ { pubkey: this.global, isSigner: false, isWritable: false },
564
+ { pubkey: this.globalState.feeRecipient, isSigner: false, isWritable: true },
565
+ { pubkey: mint, isSigner: false, isWritable: false },
566
+ { pubkey: bonding, isSigner: false, isWritable: true },
567
+ { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
568
+ { pubkey: userAta, isSigner: false, isWritable: true },
569
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
570
+ { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
571
+ { pubkey: creatorVault, isSigner: false, isWritable: true },
572
+ { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
573
+ { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
574
+ { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
575
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
576
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
577
+ ],
578
+ data: Buffer.concat([
579
+ DISCRIMINATORS.SELL,
580
+ u64(tokenIn),
581
+ u64(minSol > 0n ? minSol : 1n)
582
+ ])
583
+ }));
584
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
585
+ tx.recentBlockhash = blockhash;
586
+ tx.feePayer = this.wallet.publicKey;
587
+ tx.sign(this.wallet);
588
+ const signature = await this.connection.sendRawTransaction(tx.serialize());
589
+ pendingTransactions.push({
590
+ signature,
591
+ lastValidBlockHeight,
592
+ index: i
593
+ });
594
+ }
595
+ catch (e) {
596
+ failedTransactions.push({
597
+ index: i,
598
+ error: e.message
599
+ });
600
+ }
601
+ }
602
+ return { pendingTransactions, failedTransactions };
603
+ }
604
+ /* ---------- 外盘交易 ---------- */
605
+ async ammBuy(tokenAddr, totalSolIn, tradeOpt) {
606
+ const mint = new web3_js_1.PublicKey(tokenAddr);
607
+ const poolInfo = await this.getAmmPoolInfo(mint);
608
+ const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
609
+ const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
610
+ const pendingTransactions = [];
611
+ const failedTransactions = [];
612
+ for (let i = 0; i < solChunks.length; i++) {
613
+ try {
614
+ const solIn = solChunks[i];
615
+ const baseAmountOut = this.calculateAmmBuyOutput(solIn, reserves);
616
+ const slippageBps = this.calcSlippage({
617
+ tradeSize: solIn,
618
+ reserve: reserves.quoteAmount,
619
+ slippageOpt: tradeOpt.slippage
620
+ });
621
+ const maxQuoteIn = (solIn * BigInt(10_000 + slippageBps)) / 10000n;
622
+ const priority = this.genPriority(tradeOpt.priority);
623
+ const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
624
+ const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint);
625
+ const userQuoteAta = await this.ensureWSOLAta(tx, this.wallet.publicKey, "buy", maxQuoteIn);
626
+ const buyIx = this.createAmmBuyInstruction(poolInfo, userBaseAta, userQuoteAta, baseAmountOut, maxQuoteIn);
627
+ tx.add(buyIx);
628
+ tx.add((0, spl_token_1.createCloseAccountInstruction)(userQuoteAta, this.wallet.publicKey, this.wallet.publicKey));
629
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('finalized');
630
+ tx.recentBlockhash = blockhash;
631
+ tx.feePayer = this.wallet.publicKey;
632
+ tx.sign(this.wallet);
633
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
634
+ skipPreflight: false,
635
+ maxRetries: 2
636
+ });
637
+ pendingTransactions.push({
638
+ signature,
639
+ lastValidBlockHeight,
640
+ index: i
641
+ });
642
+ }
643
+ catch (e) {
644
+ failedTransactions.push({
645
+ index: i,
646
+ error: e.message
647
+ });
648
+ }
649
+ }
650
+ return { pendingTransactions, failedTransactions };
651
+ }
652
+ async ammSell(tokenAddr, totalTokenIn, tradeOpt) {
653
+ const mint = new web3_js_1.PublicKey(tokenAddr);
654
+ const poolInfo = await this.getAmmPoolInfo(mint);
655
+ const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
656
+ const totalSolOut = this.calculateAmmSellOutput(totalTokenIn, reserves);
657
+ const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
658
+ ? [totalTokenIn]
659
+ : this.splitIntoN(totalTokenIn, Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx));
660
+ const pendingTransactions = [];
661
+ const failedTransactions = [];
662
+ for (let i = 0; i < tokenChunks.length; i++) {
663
+ try {
664
+ const tokenIn = tokenChunks[i];
665
+ const solOut = this.calculateAmmSellOutput(tokenIn, reserves);
666
+ const slippageBps = this.calcSlippage({
667
+ tradeSize: tokenIn,
668
+ reserve: reserves.baseAmount,
669
+ slippageOpt: tradeOpt.slippage
670
+ });
671
+ const minQuoteOut = (solOut * BigInt(10_000 - slippageBps)) / 10000n;
672
+ const priority = this.genPriority(tradeOpt.priority);
673
+ const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
674
+ const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint);
675
+ const userQuoteAta = await this.ensureWSOLAta(tx, this.wallet.publicKey, "sell");
676
+ const sellIx = this.createAmmSellInstruction(poolInfo, userBaseAta, userQuoteAta, tokenIn, minQuoteOut);
677
+ tx.add(sellIx);
678
+ tx.add((0, spl_token_1.createCloseAccountInstruction)(userQuoteAta, this.wallet.publicKey, this.wallet.publicKey));
679
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('finalized');
680
+ tx.recentBlockhash = blockhash;
681
+ tx.feePayer = this.wallet.publicKey;
682
+ tx.sign(this.wallet);
683
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
684
+ skipPreflight: false,
685
+ maxRetries: 2
686
+ });
687
+ pendingTransactions.push({
688
+ signature,
689
+ lastValidBlockHeight,
690
+ index: i
691
+ });
692
+ }
693
+ catch (e) {
694
+ failedTransactions.push({
695
+ index: i,
696
+ error: e.message
697
+ });
698
+ }
699
+ }
700
+ return { pendingTransactions, failedTransactions };
701
+ }
702
+ /* ---------- AMM 池信息 ---------- */
703
+ async getAmmPoolInfo(mint) {
704
+ const [poolAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool-authority"), mint.toBuffer()], PROGRAM_IDS.PUMP);
705
+ const [pool] = web3_js_1.PublicKey.findProgramAddressSync([
706
+ Buffer.from("pool"),
707
+ new bn_js_1.default(0).toArrayLike(Buffer, "le", 2),
708
+ poolAuthority.toBuffer(),
709
+ mint.toBuffer(),
710
+ SOL_MINT.toBuffer()
711
+ ], PROGRAM_IDS.PUMP_AMM);
712
+ const acc = await this.connection.getAccountInfo(pool);
713
+ if (!acc)
714
+ throw new Error("AMM pool not found");
715
+ const poolKeys = parsePoolKeys(acc.data);
716
+ const [globalConfigPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_config")], PROGRAM_IDS.PUMP_AMM);
717
+ const globalConfigAcc = await this.connection.getAccountInfo(globalConfigPda);
718
+ if (!globalConfigAcc)
719
+ throw new Error("Global config not found");
720
+ const globalConfig = this.parseAmmGlobalConfig(globalConfigAcc.data, globalConfigPda);
721
+ return { pool, poolAuthority, poolKeys, globalConfig };
722
+ }
723
+ parseAmmGlobalConfig(data, address) {
724
+ let offset = 8;
725
+ const admin = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
726
+ offset += 32;
727
+ offset += 8;
728
+ offset += 8;
729
+ offset += 1;
730
+ const protocolFeeRecipients = [];
731
+ for (let i = 0; i < 8; i++) {
732
+ protocolFeeRecipients.push(new web3_js_1.PublicKey(data.slice(offset, offset + 32)));
733
+ offset += 32;
734
+ }
735
+ return { address, admin, protocolFeeRecipients };
736
+ }
737
+ async getAmmPoolReserves(poolKeys) {
738
+ const [baseInfo, quoteInfo] = await Promise.all([
739
+ this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
740
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
741
+ ]);
742
+ return {
743
+ baseAmount: BigInt(baseInfo.value.amount),
744
+ quoteAmount: BigInt(quoteInfo.value.amount),
745
+ baseDecimals: baseInfo.value.decimals,
746
+ quoteDecimals: quoteInfo.value.decimals
747
+ };
748
+ }
749
+ /* ---------- AMM 指令构建 ---------- */
750
+ createAmmBuyInstruction(poolInfo, userBaseAta, userQuoteAta, baseAmountOut, maxQuoteAmountIn) {
751
+ const { pool, poolKeys, globalConfig } = poolInfo;
752
+ const [eventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], PROGRAM_IDS.PUMP_AMM);
753
+ const [coinCreatorVaultAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()], PROGRAM_IDS.PUMP_AMM);
754
+ const coinCreatorVaultAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, coinCreatorVaultAuthority, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
755
+ const [globalVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_volume_accumulator")], PROGRAM_IDS.PUMP_AMM);
756
+ const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()], PROGRAM_IDS.PUMP_AMM);
757
+ const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
758
+ const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
759
+ const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
760
+ return new web3_js_1.TransactionInstruction({
761
+ programId: PROGRAM_IDS.PUMP_AMM,
762
+ keys: [
763
+ { pubkey: pool, isSigner: false, isWritable: true },
764
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
765
+ { pubkey: globalConfig.address, isSigner: false, isWritable: false },
766
+ { pubkey: poolKeys.baseMint, isSigner: false, isWritable: false },
767
+ { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
768
+ { pubkey: userBaseAta, isSigner: false, isWritable: true },
769
+ { pubkey: userQuoteAta, isSigner: false, isWritable: true },
770
+ { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
771
+ { pubkey: poolKeys.poolQuoteTokenAccount, isSigner: false, isWritable: true },
772
+ { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
773
+ { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
774
+ { pubkey: spl_token_1.TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
775
+ { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
776
+ { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
777
+ { pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
778
+ { pubkey: eventAuthority, isSigner: false, isWritable: false },
779
+ { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
780
+ { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
781
+ { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
782
+ { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
783
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
784
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
785
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
786
+ ],
787
+ data: Buffer.concat([
788
+ DISCRIMINATORS.BUY,
789
+ u64(baseAmountOut),
790
+ u64(maxQuoteAmountIn),
791
+ Buffer.from([1, 1])
792
+ ])
793
+ });
794
+ }
795
+ createAmmSellInstruction(poolInfo, userBaseAta, userQuoteAta, baseAmountIn, minQuoteAmountOut) {
796
+ const { pool, poolKeys, globalConfig } = poolInfo;
797
+ const [eventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], PROGRAM_IDS.PUMP_AMM);
798
+ const [coinCreatorVaultAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()], PROGRAM_IDS.PUMP_AMM);
799
+ const coinCreatorVaultAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, coinCreatorVaultAuthority, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
800
+ const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
801
+ const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
802
+ const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
803
+ return new web3_js_1.TransactionInstruction({
804
+ programId: PROGRAM_IDS.PUMP_AMM,
805
+ keys: [
806
+ { pubkey: pool, isSigner: false, isWritable: true },
807
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
808
+ { pubkey: globalConfig.address, isSigner: false, isWritable: false },
809
+ { pubkey: poolKeys.baseMint, isSigner: false, isWritable: false },
810
+ { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
811
+ { pubkey: userBaseAta, isSigner: false, isWritable: true },
812
+ { pubkey: userQuoteAta, isSigner: false, isWritable: true },
813
+ { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
814
+ { pubkey: poolKeys.poolQuoteTokenAccount, isSigner: false, isWritable: true },
815
+ { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
816
+ { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
817
+ { pubkey: spl_token_1.TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
818
+ { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
819
+ { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
820
+ { pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
821
+ { pubkey: eventAuthority, isSigner: false, isWritable: false },
822
+ { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
823
+ { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
824
+ { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
825
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
826
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
827
+ ],
828
+ data: Buffer.concat([
829
+ DISCRIMINATORS.SELL,
830
+ u64(baseAmountIn),
831
+ u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n)
832
+ ])
833
+ });
834
+ }
835
+ /* ---------- 交易确认 ---------- */
836
+ async confirmTransactionWithPolling(signature, lastValidBlockHeight, maxAttempts = 5, delayMs = 2000) {
837
+ console.log('✅ 交易已发送:', signature);
838
+ console.log('🔗 查看交易: https://solscan.io/tx/' + signature);
839
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
840
+ await new Promise(resolve => setTimeout(resolve, delayMs));
841
+ try {
842
+ console.log(`🔍 检查交易状态 (${attempt}/${maxAttempts})...`);
843
+ const txInfo = await this.connection.getTransaction(signature, {
844
+ commitment: 'confirmed',
845
+ maxSupportedTransactionVersion: 0
846
+ });
847
+ if (txInfo) {
848
+ if (txInfo.meta?.err) {
849
+ console.error('❌ 交易失败:', txInfo.meta.err);
850
+ throw new Error('交易失败: ' + JSON.stringify(txInfo.meta.err));
851
+ }
852
+ console.log('✅ 交易已确认!');
853
+ return signature;
854
+ }
855
+ const currentBlockHeight = await this.connection.getBlockHeight('finalized');
856
+ if (currentBlockHeight > lastValidBlockHeight) {
857
+ console.log('⚠️ 交易已过期(超过有效区块高度)');
858
+ throw new Error('交易过期:未在有效区块高度内确认');
859
+ }
860
+ }
861
+ catch (error) {
862
+ const err = error;
863
+ if (err.message?.includes('交易失败') || err.message?.includes('交易过期')) {
864
+ throw error;
865
+ }
866
+ console.log(`⚠️ 查询出错,继续重试: ${err.message}`);
867
+ }
868
+ }
869
+ throw new Error(`交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`);
870
+ }
871
+ /* ---------- 事件监听 ---------- */
872
+ listenTrades(callback, mintFilter) {
873
+ return this.connection.onLogs(PROGRAM_IDS.PUMP, (log) => {
874
+ for (const logLine of log.logs) {
875
+ if (!logLine.startsWith("Program data: "))
876
+ continue;
877
+ const buf = Buffer.from(logLine.replace("Program data: ", ""), "base64");
878
+ if (!buf.subarray(0, 8).equals(DISCRIMINATORS.TRADE_EVENT))
879
+ continue;
880
+ let offset = 8;
881
+ const mint = new web3_js_1.PublicKey(buf.slice(offset, offset + 32));
882
+ offset += 32;
883
+ if (mintFilter && !mint.equals(mintFilter))
884
+ return;
885
+ const solAmount = buf.readBigUInt64LE(offset);
886
+ offset += 8;
887
+ const tokenAmount = buf.readBigUInt64LE(offset);
888
+ offset += 8;
889
+ const isBuy = buf[offset++] === 1;
890
+ const user = new web3_js_1.PublicKey(buf.slice(offset, offset + 32));
891
+ offset += 32;
892
+ const timestamp = Number(buf.readBigInt64LE(offset));
893
+ callback({
894
+ mint: mint.toBase58(),
895
+ solAmount,
896
+ tokenAmount,
897
+ isBuy,
898
+ user: user.toBase58(),
899
+ timestamp,
900
+ signature: log.signature
901
+ });
902
+ }
903
+ }, "confirmed");
904
+ }
905
+ /* ---------- 元数据查询 ---------- */
906
+ async fetchMeta(tokenAddr) {
907
+ const mint = new web3_js_1.PublicKey(tokenAddr);
908
+ try {
909
+ const metadata = await (0, spl_token_1.getTokenMetadata)(this.connection, mint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID);
910
+ return {
911
+ name: metadata?.name || "",
912
+ symbol: metadata?.symbol || "",
913
+ uri: metadata?.uri || ""
914
+ };
915
+ }
916
+ catch (e) {
917
+ const metadataPda = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("metadata"), PROGRAM_IDS.METADATA.toBuffer(), mint.toBuffer()], PROGRAM_IDS.METADATA)[0];
918
+ const acc = await this.connection.getAccountInfo(metadataPda);
919
+ if (!acc)
920
+ return null;
921
+ const meta = parseMetadataAccount(acc.data);
922
+ return {
923
+ name: meta?.name?.replace(/\u0000/g, '') || "",
924
+ symbol: meta?.symbol?.replace(/\u0000/g, '') || "",
925
+ uri: meta?.uri?.replace(/\u0000/g, '') || ""
926
+ };
927
+ }
928
+ }
929
+ // 公开wallet方法
930
+ getWallet() {
931
+ return this.wallet;
932
+ }
933
+ getConnection() {
934
+ return this.connection;
935
+ }
936
+ /**
937
+ * 清除token program缓存
938
+ */
939
+ clearTokenProgramCache(tokenAddr) {
940
+ if (tokenAddr) {
941
+ this.tokenProgramCache.delete(tokenAddr);
942
+ }
943
+ else {
944
+ this.tokenProgramCache.clear();
945
+ }
946
+ }
947
+ /**
948
+ * 获取缓存的token program信息
949
+ */
950
+ getCachedTokenProgram(tokenAddr) {
951
+ return this.tokenProgramCache.get(tokenAddr);
952
+ }
953
+ }
954
+ exports.PumpTrader = PumpTrader;
package/package.json CHANGED
@@ -1,31 +1,17 @@
1
1
  {
2
2
  "name": "pump-trader",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "PumpFun 交易库 - 自动判断 Token Program 和内盘/外盘",
5
- "type": "module",
6
- "main": "./index.js",
7
- "types": "./index.ts",
8
- "exports": {
9
- ".": {
10
- "import": "./index.js",
11
- "require": "./index.js",
12
- "types": "./index.ts"
13
- }
14
- },
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
15
7
  "scripts": {
16
- "build": "tsc --noEmit",
8
+ "build": "tsc",
17
9
  "format": "prettier --write ."
18
10
  },
19
11
  "keywords": [
20
12
  "solana",
21
- "pump",
22
13
  "pumpfun",
23
- "trading",
24
- "token",
25
- "bonding-curve",
26
- "amm",
27
- "token-program",
28
- "token-2022"
14
+ "trading"
29
15
  ],
30
16
  "author": "Sylor",
31
17
  "license": "MIT",
package/tsconfig.json CHANGED
@@ -1,20 +1,14 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "lib": ["ES2020"],
6
- "declaration": true,
7
- "declarationMap": true,
8
- "sourceMap": true,
9
- "outDir": "./dist",
10
- "strict": true,
3
+ "target": "ES2021",
4
+ "module": "CommonJS",
11
5
  "esModuleInterop": true,
12
- "skipLibCheck": true,
13
6
  "forceConsistentCasingInFileNames": true,
14
- "resolveJsonModule": true,
15
- "moduleResolution": "node",
16
- "allowSyntheticDefaultImports": true,
17
- "types": ["node"]
7
+ "strict": true,
8
+ "outDir": "./dist",
9
+ "declaration": true,
10
+ "skipLibCheck": true,
11
+ "resolveJsonModule": true
18
12
  },
19
13
  "include": [
20
14
  "index.ts"
@@ -22,4 +16,4 @@
22
16
  "exclude": [
23
17
  "node_modules"
24
18
  ]
25
- }
19
+ }