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.
- package/dist/index.d.ts +177 -0
- package/dist/index.js +954 -0
- package/package.json +5 -19
- package/tsconfig.json +8 -14
package/dist/index.d.ts
ADDED
|
@@ -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.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "PumpFun 交易库 - 自动判断 Token Program 和内盘/外盘",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
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
|
|
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": "
|
|
4
|
-
"module": "
|
|
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
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
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
|
+
}
|