pinpet-sdk 2.1.28 → 2.1.30
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/pinpet-sdk.cjs.js +262 -301
- package/dist/pinpet-sdk.esm.js +262 -301
- package/dist/pinpet-sdk.js +262 -301
- package/dist/pinpet-sdk.js.map +1 -1
- package/package.json +1 -1
- package/src/idl/pinpet.json +94 -99
- package/src/idl/pinpet_del.json +3730 -0
- package/src/modules/chain.js +86 -79
- package/src/modules/simulator/buy_sell_token.js +16 -14
- package/src/modules/simulator/long_shrot_stop.js +12 -12
- package/src/modules/simulator/utils.js +3 -3
- package/src/modules/simulator.js +22 -23
- package/src/modules/token.js +21 -63
- package/src/modules/trading.js +8 -8
package/src/modules/chain.js
CHANGED
|
@@ -14,6 +14,21 @@ const { Buffer } = require('buffer');
|
|
|
14
14
|
class ChainModule {
|
|
15
15
|
constructor(sdk) {
|
|
16
16
|
this.sdk = sdk;
|
|
17
|
+
// getCurveAccount 缓存:key = mint string, value = { data, timestamp }
|
|
18
|
+
this._curveAccountCache = new Map();
|
|
19
|
+
this._CACHE_TTL = 10000; // 10 秒 TTL
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 清除指定 mint 的 curveAccount 缓存(交易后调用)
|
|
24
|
+
* @param {string} [mint] - 指定 mint 地址,不传则清除全部
|
|
25
|
+
*/
|
|
26
|
+
invalidateCurveCache(mint) {
|
|
27
|
+
if (mint) {
|
|
28
|
+
this._curveAccountCache.delete(typeof mint === 'string' ? mint : mint.toString());
|
|
29
|
+
} else {
|
|
30
|
+
this._curveAccountCache.clear();
|
|
31
|
+
}
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
/**
|
|
@@ -142,10 +157,23 @@ class ChainModule {
|
|
|
142
157
|
* @version 2.0.0 - Updated to use new OrderBook structure (up_orderbook/down_orderbook instead of upHead/downHead)
|
|
143
158
|
* @author SpinPet SDK Team
|
|
144
159
|
*/
|
|
145
|
-
async getCurveAccount(mint) {
|
|
160
|
+
async getCurveAccount(mint, options = {}) {
|
|
161
|
+
const { skipBalances = false } = options;
|
|
146
162
|
try {
|
|
147
163
|
// Parameter validation and conversion
|
|
148
164
|
const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
|
|
165
|
+
const mintKey = mintPubkey.toString();
|
|
166
|
+
|
|
167
|
+
// 检查缓存
|
|
168
|
+
const cached = this._curveAccountCache.get(mintKey);
|
|
169
|
+
if (cached && (Date.now() - cached.timestamp < this._CACHE_TTL)) {
|
|
170
|
+
// 缓存命中:如果请求包含余额但缓存没有,需要重新获取余额部分
|
|
171
|
+
if (!skipBalances && cached.skipBalances) {
|
|
172
|
+
// 缓存是 skipBalances 的,但现在需要余额,需要补充查询
|
|
173
|
+
} else {
|
|
174
|
+
return cached.data;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
149
177
|
|
|
150
178
|
// Calculate curve_account PDA address
|
|
151
179
|
// Use the same seeds as in the contract: [b"borrowing_curve", mint_account.key().as_ref()]
|
|
@@ -203,18 +231,25 @@ class ChainModule {
|
|
|
203
231
|
this.sdk.programId
|
|
204
232
|
);
|
|
205
233
|
|
|
206
|
-
// Query
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
// Query balances (skip if not needed)
|
|
235
|
+
let baseFeeRecipientBalance = 0;
|
|
236
|
+
let feeRecipientBalance = 0;
|
|
237
|
+
let poolTokenBalance = { value: { amount: '0' } };
|
|
238
|
+
let poolSolBalance = 0;
|
|
239
|
+
|
|
240
|
+
if (!skipBalances) {
|
|
241
|
+
[
|
|
242
|
+
baseFeeRecipientBalance,
|
|
243
|
+
feeRecipientBalance,
|
|
244
|
+
poolTokenBalance,
|
|
245
|
+
poolSolBalance
|
|
246
|
+
] = await Promise.all([
|
|
247
|
+
this.sdk.connection.getBalance(decodedData.baseFeeRecipient),
|
|
248
|
+
this.sdk.connection.getBalance(decodedData.feeRecipient),
|
|
249
|
+
this.sdk.connection.getTokenAccountBalance(poolTokenAccountPDA).catch(() => ({ value: { amount: '0' } })),
|
|
250
|
+
this.sdk.connection.getBalance(poolSolAccountPDA)
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
218
253
|
|
|
219
254
|
// Convert data format
|
|
220
255
|
const convertedData = {
|
|
@@ -270,6 +305,13 @@ class ChainModule {
|
|
|
270
305
|
}
|
|
271
306
|
};
|
|
272
307
|
|
|
308
|
+
// 写入缓存
|
|
309
|
+
this._curveAccountCache.set(mintKey, {
|
|
310
|
+
data: convertedData,
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
skipBalances: skipBalances
|
|
313
|
+
});
|
|
314
|
+
|
|
273
315
|
// Return converted data
|
|
274
316
|
return convertedData;
|
|
275
317
|
|
|
@@ -326,58 +368,13 @@ class ChainModule {
|
|
|
326
368
|
}
|
|
327
369
|
|
|
328
370
|
try {
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
|
|
333
|
-
} catch (pubkeyError) {
|
|
334
|
-
throw new Error(`Invalid mint address: ${mint}`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Validate mintPubkey
|
|
338
|
-
if (!mintPubkey || typeof mintPubkey.toBuffer !== 'function') {
|
|
339
|
-
throw new Error(`Invalid mintPubkey`);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Calculate curve_account PDA address
|
|
343
|
-
const [curveAccountPDA] = PublicKey.findProgramAddressSync(
|
|
344
|
-
[
|
|
345
|
-
Buffer.from("borrowing_curve"),
|
|
346
|
-
mintPubkey.toBuffer()
|
|
347
|
-
],
|
|
348
|
-
this.sdk.programId
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
// Use Anchor program to fetch account data directly
|
|
352
|
-
let decodedData;
|
|
353
|
-
try {
|
|
354
|
-
decodedData = await this.sdk.program.account.borrowingBondingCurve.fetch(curveAccountPDA);
|
|
355
|
-
} catch (fetchError) {
|
|
356
|
-
// If fetch fails, use raw method
|
|
357
|
-
const accountInfo = await this.sdk.connection.getAccountInfo(curveAccountPDA);
|
|
358
|
-
if (!accountInfo) {
|
|
359
|
-
throw new Error(`curve_account does not exist`);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Manually decode with BorshAccountsCoder
|
|
363
|
-
const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
decodedData = accountsCoder.decode('BorrowingBondingCurve', accountInfo.data);
|
|
367
|
-
} catch (decodeError1) {
|
|
368
|
-
try {
|
|
369
|
-
decodedData = accountsCoder.decode('borrowingBondingCurve', accountInfo.data);
|
|
370
|
-
} catch (decodeError2) {
|
|
371
|
-
throw new Error(`Cannot decode account data: ${decodeError1.message}`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
371
|
+
// 复用 getCurveAccount 缓存,避免重复 fetch 同一个账户
|
|
372
|
+
const curveData = await this.getCurveAccount(mint, { skipBalances: true });
|
|
373
|
+
const price = curveData.price;
|
|
375
374
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return decodedData.price.toString();
|
|
375
|
+
if (price && price !== 0n) {
|
|
376
|
+
return price.toString();
|
|
379
377
|
} else {
|
|
380
|
-
// If no price data, return initial price
|
|
381
378
|
const initialPrice = CurveAMM.getInitialPrice();
|
|
382
379
|
if (initialPrice === null) {
|
|
383
380
|
throw new Error('price: Unable to calculate initial price');
|
|
@@ -473,14 +470,16 @@ class ChainModule {
|
|
|
473
470
|
// Convert API type to orderbook direction
|
|
474
471
|
// "up_orders" = short orders = upOrderbook (orderType=2)
|
|
475
472
|
// "down_orders" = long orders = downOrderbook (orderType=1)
|
|
476
|
-
const
|
|
473
|
+
const seed = orderType === 'up_orders' ? 'up_orderbook' : 'down_orderbook';
|
|
477
474
|
|
|
478
|
-
//
|
|
479
|
-
const
|
|
480
|
-
const
|
|
475
|
+
// 本地计算 orderbook PDA,避免调用 getCurveAccount(省 5 个 RPC)
|
|
476
|
+
const mintPubkey = new PublicKey(mint);
|
|
477
|
+
const [orderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
478
|
+
[Buffer.from(seed), mintPubkey.toBuffer()],
|
|
479
|
+
this.sdk.programId
|
|
480
|
+
);
|
|
481
481
|
|
|
482
482
|
// Get OrderBook account data
|
|
483
|
-
const orderbookPubkey = new PublicKey(orderbookAddress);
|
|
484
483
|
const accountInfo = await this.sdk.connection.getAccountInfo(orderbookPubkey);
|
|
485
484
|
|
|
486
485
|
if (!accountInfo) {
|
|
@@ -931,15 +930,15 @@ class ChainModule {
|
|
|
931
930
|
throw new Error('debug_orders: order type must be "up_orders" or "down_orders"');
|
|
932
931
|
}
|
|
933
932
|
|
|
934
|
-
//
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
933
|
+
// 本地计算 orderbook PDA,避免调用 getCurveAccount(省 5 个 RPC)
|
|
934
|
+
const seed = orderType === 'up_orders' ? 'up_orderbook' : 'down_orderbook';
|
|
935
|
+
const mintPubkey = new PublicKey(mint);
|
|
936
|
+
const [orderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
937
|
+
[Buffer.from(seed), mintPubkey.toBuffer()],
|
|
938
|
+
this.sdk.programId
|
|
939
|
+
);
|
|
940
940
|
|
|
941
941
|
// Get OrderBook account data
|
|
942
|
-
const orderbookPubkey = new PublicKey(orderbookAddress);
|
|
943
942
|
const accountInfo = await this.sdk.connection.getAccountInfo(orderbookPubkey);
|
|
944
943
|
|
|
945
944
|
if (!accountInfo) {
|
|
@@ -1142,10 +1141,18 @@ class ChainModule {
|
|
|
1142
1141
|
const page = 1; // Always return page 1
|
|
1143
1142
|
const orderBy = options.order_by || 'start_time_desc';
|
|
1144
1143
|
|
|
1145
|
-
//
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1144
|
+
// 本地计算 orderbook PDA,避免调用 getCurveAccount(省 5 个 RPC)
|
|
1145
|
+
const mintPubkey = new PublicKey(mint);
|
|
1146
|
+
const [upOrderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
1147
|
+
[Buffer.from('up_orderbook'), mintPubkey.toBuffer()],
|
|
1148
|
+
this.sdk.programId
|
|
1149
|
+
);
|
|
1150
|
+
const [downOrderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
1151
|
+
[Buffer.from('down_orderbook'), mintPubkey.toBuffer()],
|
|
1152
|
+
this.sdk.programId
|
|
1153
|
+
);
|
|
1154
|
+
const upOrderbookAddress = upOrderbookPubkey.toString(); // Short orders (orderType=2)
|
|
1155
|
+
const downOrderbookAddress = downOrderbookPubkey.toString(); // Long orders (orderType=1)
|
|
1149
1156
|
|
|
1150
1157
|
// Collect all user orders from both OrderBooks
|
|
1151
1158
|
const allUserOrders = [];
|
|
@@ -26,7 +26,7 @@ const { calcLiqTokenBuy, calcLiqTokenSell } = require('./calcLiq');
|
|
|
26
26
|
* - suggestedTokenAmount: {string} Recommended token amount to buy based on available liquidity
|
|
27
27
|
* - suggestedSolAmount: {string} Required SOL amount for suggested token purchase
|
|
28
28
|
*/
|
|
29
|
-
async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPrice = null, ordersData = null) {
|
|
29
|
+
async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPrice = null, ordersData = null, curveData = null) {
|
|
30
30
|
// 获取价格和订单数据
|
|
31
31
|
|
|
32
32
|
//console.log('simulateTokenBuy', mint, buyTokenAmount, passOrder, lastPrice, ordersData);
|
|
@@ -49,12 +49,13 @@ async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPric
|
|
|
49
49
|
orders = ordersData.data.orders.slice(0, this.sdk.MAX_ORDERS_COUNT + 1);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
// 获取动态流动池参数(支持外部传入,避免重复请求)
|
|
53
|
+
if (!curveData) {
|
|
54
|
+
try {
|
|
55
|
+
curveData = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(`Failed to get curve account data: ${error.message}`);
|
|
58
|
+
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// 调用 calcLiqTokenBuy 进行流动性计算(传入动态流动池参数)
|
|
@@ -182,7 +183,7 @@ async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPric
|
|
|
182
183
|
* - suggestedTokenAmount: {string} Recommended token amount to sell based on available liquidity
|
|
183
184
|
* - suggestedSolAmount: {string} Expected SOL amount from suggested token sale
|
|
184
185
|
*/
|
|
185
|
-
async function simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPrice = null, ordersData = null) {
|
|
186
|
+
async function simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPrice = null, ordersData = null, curveData = null) {
|
|
186
187
|
// 获取价格和订单数据
|
|
187
188
|
let price = lastPrice;
|
|
188
189
|
if (!price) {
|
|
@@ -204,12 +205,13 @@ async function simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPr
|
|
|
204
205
|
orders = ordersData.data.orders.slice(0, this.sdk.MAX_ORDERS_COUNT + 1);
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
208
|
+
// 获取动态流动池参数(支持外部传入,避免重复请求)
|
|
209
|
+
if (!curveData) {
|
|
210
|
+
try {
|
|
211
|
+
curveData = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
throw new Error(`Failed to get curve account data: ${error.message}`);
|
|
214
|
+
}
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
// console.log('simulateTokenSell 获取的数据:');
|
|
@@ -134,7 +134,7 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
|
|
|
134
134
|
|
|
135
135
|
// 如果没有传入 borrowFee 或池子参数,从链上一次性获取
|
|
136
136
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
137
|
-
const curveAccount = await this.sdk.chain.getCurveAccount(mint);
|
|
137
|
+
const curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
138
138
|
if (borrowFee === null) borrowFee = curveAccount.borrowFee;
|
|
139
139
|
// 链上返回的是 u64 原始单位(lamports/最小单位),需要除以 10^9 转为人类可读单位
|
|
140
140
|
// 与 calcLiq.js 中的转换方式一致
|
|
@@ -454,7 +454,7 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
454
454
|
|
|
455
455
|
// 如果没有传入 borrowFee 或池子参数,从链上一次性获取
|
|
456
456
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
457
|
-
const curveAccount = await this.sdk.chain.getCurveAccount(mint);
|
|
457
|
+
const curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
458
458
|
if (borrowFee === null) borrowFee = curveAccount.borrowFee;
|
|
459
459
|
// 链上返回的是 u64 原始单位(lamports/最小单位),需要除以 10^9 转为人类可读单位
|
|
460
460
|
// 与 calcLiq.js 中的转换方式一致
|
|
@@ -702,19 +702,19 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
702
702
|
* @since 2.0.0
|
|
703
703
|
* @version 2.0.0 - 从返回 prev_order_pda/next_order_pda 改为返回 close_insert_indices
|
|
704
704
|
*/
|
|
705
|
-
async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null) {
|
|
705
|
+
async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
|
|
706
706
|
try {
|
|
707
707
|
// Parameter validation
|
|
708
708
|
if (!mint || !buySolAmount || !stopLossPrice) {
|
|
709
709
|
throw new Error('Missing required parameters');
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
// 如果没有传入 borrowFee
|
|
712
|
+
// 如果没有传入 borrowFee 或池子参数,从链上一次性获取(支持外部传入 curveAccount 避免重复 RPC)
|
|
713
713
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
714
|
-
|
|
714
|
+
if (!curveAccount) {
|
|
715
|
+
curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
716
|
+
}
|
|
715
717
|
if (borrowFee === null) borrowFee = curveAccount.borrowFee;
|
|
716
|
-
// 链上返回的是 u64 原始单位(lamports/最小单位),需要除以 10^9 转为人类可读单位
|
|
717
|
-
// 与 calcLiq.js 中的转换方式一致
|
|
718
718
|
if (initialVirtualSol === null) initialVirtualSol = new Decimal(curveAccount.initialVirtualSol.toString()).div(CurveAMM.SOL_PRECISION_FACTOR_DECIMAL).toString();
|
|
719
719
|
if (initialVirtualToken === null) initialVirtualToken = new Decimal(curveAccount.initialVirtualToken.toString()).div(CurveAMM.TOKEN_PRECISION_FACTOR_DECIMAL).toString();
|
|
720
720
|
}
|
|
@@ -886,19 +886,19 @@ async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPr
|
|
|
886
886
|
* @since 2.0.0
|
|
887
887
|
* @version 2.0.0 - 从返回 prev_order_pda/next_order_pda 改为返回 close_insert_indices
|
|
888
888
|
*/
|
|
889
|
-
async function simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null) {
|
|
889
|
+
async function simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
|
|
890
890
|
try {
|
|
891
891
|
// Parameter validation
|
|
892
892
|
if (!mint || !sellSolAmount || !stopLossPrice) {
|
|
893
893
|
throw new Error('Missing required parameters');
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
// 如果没有传入 borrowFee
|
|
896
|
+
// 如果没有传入 borrowFee 或池子参数,从链上一次性获取(支持外部传入 curveAccount 避免重复 RPC)
|
|
897
897
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
898
|
-
|
|
898
|
+
if (!curveAccount) {
|
|
899
|
+
curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
900
|
+
}
|
|
899
901
|
if (borrowFee === null) borrowFee = curveAccount.borrowFee;
|
|
900
|
-
// 链上返回的是 u64 原始单位(lamports/最小单位),需要除以 10^9 转为人类可读单位
|
|
901
|
-
// 与 calcLiq.js 中的转换方式一致
|
|
902
902
|
if (initialVirtualSol === null) initialVirtualSol = new Decimal(curveAccount.initialVirtualSol.toString()).div(CurveAMM.SOL_PRECISION_FACTOR_DECIMAL).toString();
|
|
903
903
|
if (initialVirtualToken === null) initialVirtualToken = new Decimal(curveAccount.initialVirtualToken.toString()).div(CurveAMM.TOKEN_PRECISION_FACTOR_DECIMAL).toString();
|
|
904
904
|
}
|
|
@@ -13,9 +13,9 @@ const MIN_STOP_LOSS_PERCENT = 40; // 4.0%
|
|
|
13
13
|
|
|
14
14
|
// Maximum number of candidate indices to include in close_insert_indices
|
|
15
15
|
// This represents: 1 main position + N nodes before + N nodes after
|
|
16
|
-
// Must be an odd number >= 1 (e.g.,
|
|
17
|
-
// The contract accepts up to
|
|
18
|
-
const MAX_CANDIDATE_INDICES =
|
|
16
|
+
// Must be an odd number >= 1 (e.g., 41 = 1 main + 20 before + 20 after)
|
|
17
|
+
// The contract accepts up to 41
|
|
18
|
+
const MAX_CANDIDATE_INDICES = 19;
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
// Validate MAX_CANDIDATE_INDICES constant
|
package/src/modules/simulator.js
CHANGED
|
@@ -43,8 +43,8 @@ class SimulatorModule {
|
|
|
43
43
|
* - suggestedTokenAmount: {string} Recommended token amount to buy based on available liquidity
|
|
44
44
|
* - suggestedSolAmount: {string} Required SOL amount for suggested token purchase
|
|
45
45
|
*/
|
|
46
|
-
async simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPrice = null, ordersData = null) {
|
|
47
|
-
return simulateTokenBuy.call(this, mint, buyTokenAmount, passOrder, lastPrice, ordersData);
|
|
46
|
+
async simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPrice = null, ordersData = null, curveData = null) {
|
|
47
|
+
return simulateTokenBuy.call(this, mint, buyTokenAmount, passOrder, lastPrice, ordersData, curveData);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -70,8 +70,8 @@ class SimulatorModule {
|
|
|
70
70
|
* - suggestedTokenAmount: {string} Recommended token amount to sell based on available liquidity
|
|
71
71
|
* - suggestedSolAmount: {string} Expected SOL amount from suggested token sale
|
|
72
72
|
*/
|
|
73
|
-
async simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPrice = null, ordersData = null) {
|
|
74
|
-
return simulateTokenSell.call(this, mint, sellTokenAmount, passOrder, lastPrice, ordersData);
|
|
73
|
+
async simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPrice = null, ordersData = null, curveData = null) {
|
|
74
|
+
return simulateTokenSell.call(this, mint, sellTokenAmount, passOrder, lastPrice, ordersData, curveData);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
@@ -110,8 +110,8 @@ class SimulatorModule {
|
|
|
110
110
|
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
111
111
|
* @returns {Promise<Object>} Stop loss analysis result (same as simulateLongStopLoss)
|
|
112
112
|
*/
|
|
113
|
-
async simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null) {
|
|
114
|
-
return simulateLongSolStopLoss.call(this, mint, buySolAmount, stopLossPrice, lastPrice, ordersData, borrowFee);
|
|
113
|
+
async simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
|
|
114
|
+
return simulateLongSolStopLoss.call(this, mint, buySolAmount, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken, curveAccount);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
@@ -124,8 +124,8 @@ class SimulatorModule {
|
|
|
124
124
|
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
125
125
|
* @returns {Promise<Object>} Stop loss analysis result (same as simulateShortStopLoss)
|
|
126
126
|
*/
|
|
127
|
-
async simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null) {
|
|
128
|
-
return simulateShortSolStopLoss.call(this, mint, sellSolAmount, stopLossPrice, lastPrice, ordersData, borrowFee);
|
|
127
|
+
async simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
|
|
128
|
+
return simulateShortSolStopLoss.call(this, mint, sellSolAmount, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken, curveAccount);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
@@ -204,15 +204,13 @@ class SimulatorModule {
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// Get current price and
|
|
208
|
-
const priceResult = await
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
console.log("upOrdersResult:",upOrdersResult);
|
|
215
|
-
console.log("upOrdersResult:",upOrdersResult.data.orders);
|
|
207
|
+
// Get current price, orders data, and curve account in parallel
|
|
208
|
+
const [priceResult, ordersResult, upOrdersResult, curveAccount] = await Promise.all([
|
|
209
|
+
this.sdk.data.price(mint),
|
|
210
|
+
this.sdk.data.orders(mint, { type: 'down_orders' }),
|
|
211
|
+
this.sdk.data.orders(mint, { type: 'up_orders' }),
|
|
212
|
+
this.sdk.chain.getCurveAccount(mint, { skipBalances: true })
|
|
213
|
+
]);
|
|
216
214
|
|
|
217
215
|
if (!priceResult || !ordersResult) {
|
|
218
216
|
return {
|
|
@@ -228,7 +226,6 @@ class SimulatorModule {
|
|
|
228
226
|
const currentPrice = typeof priceResult === 'string' ? BigInt(priceResult) : BigInt(priceResult.last_price || priceResult);
|
|
229
227
|
|
|
230
228
|
// Get curve account data for initialVirtualSol and initialVirtualToken
|
|
231
|
-
const curveAccount = await this.sdk.chain.getCurveAccount(mint);
|
|
232
229
|
const initialVirtualSol = curveAccount.initialVirtualSol;
|
|
233
230
|
const initialVirtualToken = curveAccount.initialVirtualToken;
|
|
234
231
|
|
|
@@ -238,8 +235,8 @@ class SimulatorModule {
|
|
|
238
235
|
|
|
239
236
|
const estimatedTokenAmount = reSolBuy.tokenAmount;
|
|
240
237
|
|
|
241
|
-
// Call simulateTokenBuy with estimated amount
|
|
242
|
-
const tokenBuyResult = await this.simulateTokenBuy(mint, estimatedTokenAmount, null, priceResult, ordersResult);
|
|
238
|
+
// Call simulateTokenBuy with estimated amount, pass curveAccount to avoid duplicate RPC
|
|
239
|
+
const tokenBuyResult = await this.simulateTokenBuy(mint, estimatedTokenAmount, null, priceResult, ordersResult, curveAccount);
|
|
243
240
|
|
|
244
241
|
// Transform result to match simulateBuy format
|
|
245
242
|
return {
|
|
@@ -325,10 +322,12 @@ class SimulatorModule {
|
|
|
325
322
|
};
|
|
326
323
|
}
|
|
327
324
|
|
|
328
|
-
// Get current price and orders data
|
|
325
|
+
// Get current price and orders data in parallel
|
|
329
326
|
// For sell transactions, we need down_orders (long orders that provide buy liquidity)
|
|
330
|
-
const priceResult = await
|
|
331
|
-
|
|
327
|
+
const [priceResult, ordersResult] = await Promise.all([
|
|
328
|
+
this.sdk.data.price(mint),
|
|
329
|
+
this.sdk.data.orders(mint, { type: 'down_orders' })
|
|
330
|
+
]);
|
|
332
331
|
|
|
333
332
|
if (!priceResult || !ordersResult) {
|
|
334
333
|
return {
|
package/src/modules/token.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { ComputeBudgetProgram, PublicKey, Transaction, Keypair, SystemProgram, SYSVAR_RENT_PUBKEY
|
|
1
|
+
const { ComputeBudgetProgram, PublicKey, Transaction, Keypair, SystemProgram, SYSVAR_RENT_PUBKEY } = require('@solana/web3.js');
|
|
2
2
|
const { TOKEN_PROGRAM_ID, getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, ASSOCIATED_TOKEN_PROGRAM_ID } = require('@solana/spl-token');
|
|
3
3
|
const anchor = require('@coral-xyz/anchor');
|
|
4
4
|
// 统一使用 buffer 包,所有平台一致
|
|
@@ -235,13 +235,8 @@ class TokenModule {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
|
-
* Create token and buy in one transaction
|
|
239
|
-
* 将 create 和 buy
|
|
240
|
-
*
|
|
241
|
-
* 注意:返回的 transaction 是 VersionedTransaction 类型(非 Legacy Transaction)
|
|
242
|
-
* - feePayer 和 recentBlockhash 已在 SDK 端设置,网站端不需要再设置
|
|
243
|
-
* - 签名方式:先 transaction.sign([mintKeypair]),再 phantom.signTransaction(tx)
|
|
244
|
-
* - 发送方式:connection.sendRawTransaction(signedTx.serialize())
|
|
238
|
+
* Create token and buy in one transaction
|
|
239
|
+
* 将 create 和 buy 两个指令合并到一个交易中,一次签名提交
|
|
245
240
|
*
|
|
246
241
|
* @param {Object} params - Creation and buy parameters
|
|
247
242
|
* @param {Keypair} params.mint - Token mint keypair
|
|
@@ -261,20 +256,7 @@ class TokenModule {
|
|
|
261
256
|
*
|
|
262
257
|
* @param {Object} options - Optional parameters
|
|
263
258
|
* @param {number} options.computeUnits - Compute units limit, default 1800000
|
|
264
|
-
* @returns {Promise<Object>} Object containing
|
|
265
|
-
*
|
|
266
|
-
* @example
|
|
267
|
-
* // Node.js 环境
|
|
268
|
-
* const result = await sdk.token.createAndBuy({...});
|
|
269
|
-
* result.transaction.sign([wallet, ...result.signers]);
|
|
270
|
-
* const sig = await connection.sendRawTransaction(result.transaction.serialize());
|
|
271
|
-
*
|
|
272
|
-
* @example
|
|
273
|
-
* // 浏览器 Phantom 环境
|
|
274
|
-
* const result = await sdk.token.createAndBuy({...});
|
|
275
|
-
* result.transaction.sign(result.signers); // mint keypair 先签名
|
|
276
|
-
* const signed = await phantom.signTransaction(result.transaction); // Phantom 追加 payer 签名
|
|
277
|
-
* const sig = await connection.sendRawTransaction(signed.serialize());
|
|
259
|
+
* @returns {Promise<Object>} Object containing transaction, signers and account info
|
|
278
260
|
*/
|
|
279
261
|
async createAndBuy({
|
|
280
262
|
mint,
|
|
@@ -293,7 +275,7 @@ class TokenModule {
|
|
|
293
275
|
}, options = {}) {
|
|
294
276
|
const { computeUnits = 1800000 } = options;
|
|
295
277
|
|
|
296
|
-
console.log('Token Module - CreateAndBuy
|
|
278
|
+
console.log('Token Module - CreateAndBuy:', {
|
|
297
279
|
mint: mint.publicKey.toString(),
|
|
298
280
|
name,
|
|
299
281
|
symbol,
|
|
@@ -329,6 +311,7 @@ class TokenModule {
|
|
|
329
311
|
console.log('Step 2: Fetching fee recipient accounts from params...');
|
|
330
312
|
|
|
331
313
|
// 直接从 SDK 配置中获取手续费接收账户(这些在 SDK 初始化时已经设置)
|
|
314
|
+
// 避免使用 program.account.params.fetch() 因为可能有 provider 配置问题
|
|
332
315
|
const feeRecipientAccount = this.sdk.feeRecipient;
|
|
333
316
|
const baseFeeRecipientAccount = this.sdk.baseFeeRecipient;
|
|
334
317
|
|
|
@@ -419,54 +402,42 @@ class TokenModule {
|
|
|
419
402
|
})
|
|
420
403
|
.instruction();
|
|
421
404
|
|
|
422
|
-
// 7.
|
|
423
|
-
console.log('Step 6:
|
|
424
|
-
const
|
|
405
|
+
// 7. 合并交易:create + buy
|
|
406
|
+
console.log('Step 6: Merging create and buy transactions...');
|
|
407
|
+
const transaction = new Transaction();
|
|
425
408
|
|
|
426
409
|
// 设置计算单元限制
|
|
427
410
|
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
|
|
428
411
|
units: computeUnits
|
|
429
412
|
});
|
|
430
|
-
|
|
413
|
+
transaction.add(modifyComputeUnits);
|
|
431
414
|
|
|
432
415
|
// 添加 create 交易的所有指令(跳过 create 中的计算单元指令)
|
|
433
416
|
createResult.transaction.instructions.forEach(ix => {
|
|
417
|
+
// 跳过 create 交易中的计算单元指令(我们已经添加了)
|
|
434
418
|
if (ix.programId.equals(ComputeBudgetProgram.programId)) {
|
|
435
419
|
return;
|
|
436
420
|
}
|
|
437
|
-
|
|
421
|
+
transaction.add(ix);
|
|
438
422
|
});
|
|
439
423
|
|
|
440
424
|
// 添加 ATA 创建指令(如果需要)
|
|
441
425
|
if (createAtaIx) {
|
|
442
|
-
|
|
426
|
+
transaction.add(createAtaIx);
|
|
443
427
|
}
|
|
444
428
|
|
|
445
|
-
// 添加 buy 指令
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// 8. 获取最新 blockhash 并构建 VersionedTransaction
|
|
449
|
-
const blockhashResult = await this.sdk.connection.getLatestBlockhash('confirmed');
|
|
429
|
+
// 添加 buy 指令
|
|
430
|
+
transaction.add(buyIx);
|
|
450
431
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
recentBlockhash: blockhashResult.blockhash,
|
|
454
|
-
instructions: instructions,
|
|
455
|
-
}).compileToV0Message();
|
|
456
|
-
|
|
457
|
-
const transaction = new VersionedTransaction(messageV0);
|
|
458
|
-
|
|
459
|
-
// 9. 用 mint keypair 预签名(调用方只需 payer 签名)
|
|
460
|
-
transaction.sign([mint]);
|
|
461
|
-
|
|
462
|
-
console.log('CreateAndBuy VersionedTransaction built successfully:');
|
|
463
|
-
console.log(' Total instructions:', instructions.length);
|
|
432
|
+
console.log('CreateAndBuy transaction built successfully:');
|
|
433
|
+
console.log(' Total instructions:', transaction.instructions.length);
|
|
464
434
|
console.log(' Compute units:', computeUnits);
|
|
465
|
-
console.log('
|
|
435
|
+
console.log(' Signers required:', [payer.toString(), mint.publicKey.toString()]);
|
|
466
436
|
|
|
467
|
-
//
|
|
437
|
+
// 8. 返回合并后的交易
|
|
468
438
|
return {
|
|
469
|
-
transaction,
|
|
439
|
+
transaction,
|
|
440
|
+
signers: [mint], // mint keypair 需要签名
|
|
470
441
|
accounts: {
|
|
471
442
|
// create 的账户
|
|
472
443
|
...createResult.accounts,
|
|
@@ -475,23 +446,10 @@ class TokenModule {
|
|
|
475
446
|
cooldown: cooldownPDA,
|
|
476
447
|
feeRecipientAccount,
|
|
477
448
|
baseFeeRecipientAccount
|
|
478
|
-
},
|
|
479
|
-
// 返回 blockhash 信息供调用方确认交易使用
|
|
480
|
-
blockhashInfo: {
|
|
481
|
-
blockhash: blockhashResult.blockhash,
|
|
482
|
-
lastValidBlockHeight: blockhashResult.lastValidBlockHeight
|
|
483
449
|
}
|
|
484
450
|
};
|
|
485
451
|
}
|
|
486
452
|
|
|
487
|
-
/**
|
|
488
|
-
* createAndBuy 的别名(功能完全相同)
|
|
489
|
-
* 保留此方法以保持向后兼容
|
|
490
|
-
*/
|
|
491
|
-
async createAndBuySign(params, options = {}) {
|
|
492
|
-
return this.createAndBuy(params, options);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
453
|
}
|
|
496
454
|
|
|
497
455
|
module.exports = TokenModule;
|