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.
@@ -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 all balances concurrently
207
- const [
208
- baseFeeRecipientBalance,
209
- feeRecipientBalance,
210
- poolTokenBalance,
211
- poolSolBalance
212
- ] = await Promise.all([
213
- this.sdk.connection.getBalance(decodedData.baseFeeRecipient),
214
- this.sdk.connection.getBalance(decodedData.feeRecipient),
215
- this.sdk.connection.getTokenAccountBalance(poolTokenAccountPDA).catch(() => ({ value: { amount: '0' } })),
216
- this.sdk.connection.getBalance(poolSolAccountPDA)
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
- // Parameter validation and conversion
330
- let mintPubkey;
331
- try {
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
- // Check price data and return
377
- if (decodedData.price && decodedData.price.toString() !== '0') {
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 orderbookField = orderType === 'up_orders' ? 'upOrderbook' : 'downOrderbook';
473
+ const seed = orderType === 'up_orders' ? 'up_orderbook' : 'down_orderbook';
477
474
 
478
- // Get curve_account data to get orderbook address
479
- const curveData = await this.getCurveAccount(mint);
480
- const orderbookAddress = curveData[orderbookField];
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
- // Convert API type to orderbook direction
935
- const orderbookField = orderType === 'up_orders' ? 'upOrderbook' : 'downOrderbook';
936
-
937
- // Get curve_account data to get orderbook address
938
- const curveData = await this.getCurveAccount(mint);
939
- const orderbookAddress = curveData[orderbookField];
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
- // Get curve_account data to get both OrderBook addresses
1146
- const curveData = await this.getCurveAccount(mint);
1147
- const upOrderbookAddress = curveData.upOrderbook; // Short orders (orderType=2)
1148
- const downOrderbookAddress = curveData.downOrderbook; // Long orders (orderType=1)
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
- let curveData;
54
- try {
55
- curveData = await this.sdk.chain.getCurveAccount(mint);
56
- } catch (error) {
57
- throw new Error(`Failed to get curve account data: ${error.message}`);
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
- let curveData;
209
- try {
210
- curveData = await this.sdk.chain.getCurveAccount(mint);
211
- } catch (error) {
212
- throw new Error(`Failed to get curve account data: ${error.message}`);
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
- const curveAccount = await this.sdk.chain.getCurveAccount(mint);
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
- const curveAccount = await this.sdk.chain.getCurveAccount(mint);
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., 21 = 1 main + 10 before + 10 after)
17
- // The contract accepts up to 20, we use 21 to provide more flexibility
18
- const MAX_CANDIDATE_INDICES = 31;
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
@@ -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 orders data
208
- const priceResult = await this.sdk.data.price(mint);
209
- const ordersResult = await this.sdk.data.orders(mint, { type: 'down_orders' });
210
-
211
- console.log("ordersResult:",ordersResult)
212
-
213
- const upOrdersResult = await this.sdk.data.orders(mint, { type: 'up_orders' });
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 this.sdk.data.price(mint);
331
- const ordersResult = await this.sdk.data.orders(mint, { type: 'down_orders' });
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 {
@@ -1,4 +1,4 @@
1
- const { ComputeBudgetProgram, PublicKey, Transaction, Keypair, SystemProgram, SYSVAR_RENT_PUBKEY, VersionedTransaction, TransactionMessage } = require('@solana/web3.js');
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 (使用 VersionedTransaction v0)
239
- * 将 create 和 buy 两个指令合并到一个 v0 交易中,一次签名提交
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 VersionedTransaction, signers, accounts and blockhashInfo
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 (VersionedTransaction):', {
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: Building VersionedTransaction (v0)...');
424
- const instructions = [];
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
- instructions.push(modifyComputeUnits);
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
- instructions.push(ix);
421
+ transaction.add(ix);
438
422
  });
439
423
 
440
424
  // 添加 ATA 创建指令(如果需要)
441
425
  if (createAtaIx) {
442
- instructions.push(createAtaIx);
426
+ transaction.add(createAtaIx);
443
427
  }
444
428
 
445
- // 添加 buy 指令
446
- instructions.push(buyIx);
447
-
448
- // 8. 获取最新 blockhash 并构建 VersionedTransaction
449
- const blockhashResult = await this.sdk.connection.getLatestBlockhash('confirmed');
429
+ // 添加 buy 指令
430
+ transaction.add(buyIx);
450
431
 
451
- const messageV0 = new TransactionMessage({
452
- payerKey: payer,
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(' mint keypair pre-signed, caller only needs payer signature');
435
+ console.log(' Signers required:', [payer.toString(), mint.publicKey.toString()]);
466
436
 
467
- // 10. 返回 VersionedTransaction(mint 已预签名)
437
+ // 8. 返回合并后的交易
468
438
  return {
469
- transaction, // 已包含 mint 签名
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;