four-flap-meme-sdk 1.3.39 → 1.3.40

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.
@@ -303,17 +303,25 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
303
303
  const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
304
304
  const finalGasLimit = getGasLimit(config);
305
305
  const nonceManager = new NonceManager(provider);
306
- // ✅ 优化:并行获取 gasPrice、tokenDecimalsnonces(JSON-RPC 批量请求)
307
- const [gasPrice, tokenDecimals, nonces] = await Promise.all([
306
+ // ✅ 优化:并行获取 gasPrice 和 tokenDecimals
307
+ const [gasPrice, tokenDecimals] = await Promise.all([
308
308
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
309
- getTokenDecimals(tokenAddress, provider),
310
- nonceManager.getNextNoncesForWallets(buyers) // ✅ 批量获取 nonce
309
+ getTokenDecimals(tokenAddress, provider)
311
310
  ]);
312
- // ✅ 如果需要利润转账,预先获取 nonce
313
- let profitNonce;
314
- if (extractProfit && totalProfit > 0n) {
315
- profitNonce = await nonceManager.getNextNonce(buyers[0]);
316
- }
311
+ // ✅ 修复:先计算需要的 nonce 数量,再统一获取
312
+ // buyers[0] 可能需要 2 个 nonce(买入 + 利润转账)
313
+ const needProfitTx = extractProfit && totalProfit > 0n;
314
+ const buyer0NeedCount = needProfitTx ? 2 : 1;
315
+ // 获取 buyers[0] 的连续 nonces
316
+ const buyer0Nonces = await nonceManager.getNextNonceBatch(buyers[0], buyer0NeedCount);
317
+ // 获取其他 buyers 的 nonces(如果有)
318
+ let otherNonces = [];
319
+ if (buyers.length > 1) {
320
+ otherNonces = await nonceManager.getNextNoncesForWallets(buyers.slice(1));
321
+ }
322
+ // 组装最终的 nonces 数组
323
+ const nonces = [buyer0Nonces[0], ...otherNonces];
324
+ const profitNonce = needProfitTx ? buyer0Nonces[1] : undefined;
317
325
  // 计算 minOutputAmounts
318
326
  let minOuts;
319
327
  if (params.minOutputAmounts && params.minOutputAmounts.length === buyers.length) {
@@ -401,12 +409,11 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
401
409
  const finalGasLimit = getGasLimit(config);
402
410
  const nonceManager = new NonceManager(provider);
403
411
  const extractProfit = shouldExtractProfit(config);
404
- // ✅ 优化:并行获取 gasPrice、tokenDecimals、allowancesnonces(JSON-RPC 批量请求)
405
- const [gasPrice, tokenDecimals, allowances, nonces] = await Promise.all([
412
+ // ✅ 优化:并行获取 gasPrice、tokenDecimals 和 allowances
413
+ const [gasPrice, tokenDecimals, allowances] = await Promise.all([
406
414
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
407
415
  getTokenDecimals(tokenAddress, provider),
408
- batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress),
409
- nonceManager.getNextNoncesForWallets(sellers) // ✅ 批量获取 nonce
416
+ batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress)
410
417
  ]);
411
418
  const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, tokenDecimals));
412
419
  // 找出需要授权的钱包索引
@@ -464,7 +471,7 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
464
471
  // ✅ minOuts = 0,不设置滑点限制(大额交易更稳定)
465
472
  minOuts = quotedOutputs.map(() => 0n);
466
473
  }
467
- // ✅ 计算利润并找出收益最多的钱包(提前计算,预先获取 nonce)
474
+ // ✅ 计算利润并找出收益最多的钱包
468
475
  let totalProfit = 0n;
469
476
  let maxRevenueIndex = 0;
470
477
  let maxRevenue = 0n;
@@ -480,10 +487,35 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
480
487
  }
481
488
  }
482
489
  }
483
- // ✅ 如果需要利润转账,预先获取 nonce
490
+ // ✅ 修复:先计算需要的 nonce 数量,再统一获取
491
+ const needProfitTx = extractProfit && totalProfit > 0n && maxRevenue > 0n;
492
+ // 分配 nonces:收益最多的钱包可能需要 2 个 nonce(卖出 + 利润转账)
493
+ let nonces;
484
494
  let profitNonce;
485
- if (extractProfit && totalProfit > 0n && maxRevenue > 0n) {
486
- profitNonce = await nonceManager.getNextNonce(sellers[maxRevenueIndex]);
495
+ if (needProfitTx) {
496
+ // 收益最多的钱包需要 2 个连续 nonce
497
+ const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
498
+ // 其他钱包各需要 1 个 nonce
499
+ const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
500
+ const otherNonces = otherSellers.length > 0
501
+ ? await nonceManager.getNextNoncesForWallets(otherSellers)
502
+ : [];
503
+ // 组装最终的 nonces 数组(保持原顺序)
504
+ nonces = [];
505
+ let otherIdx = 0;
506
+ for (let i = 0; i < sellers.length; i++) {
507
+ if (i === maxRevenueIndex) {
508
+ nonces.push(maxRevenueNonces[0]); // 卖出交易用第一个 nonce
509
+ }
510
+ else {
511
+ nonces.push(otherNonces[otherIdx++]);
512
+ }
513
+ }
514
+ profitNonce = maxRevenueNonces[1]; // 利润交易用第二个 nonce
515
+ }
516
+ else {
517
+ // 不需要利润交易,所有钱包各 1 个 nonce
518
+ nonces = await nonceManager.getNextNoncesForWallets(sellers);
487
519
  }
488
520
  // 卖出不需要发送 BNB,只需要 flatFee
489
521
  const needBNB = false;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ECDH + AES-GCM 加密工具(浏览器兼容)
3
+ * 用于将签名交易用服务器公钥加密
4
+ */
5
+ /**
6
+ * 用服务器公钥加密签名交易(ECDH + AES-GCM)
7
+ *
8
+ * @param signedTransactions 签名后的交易数组
9
+ * @param publicKeyBase64 服务器提供的公钥(Base64 格式)
10
+ * @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
11
+ */
12
+ export declare function encryptWithPublicKey(signedTransactions: string[], publicKeyBase64: string): Promise<string>;
13
+ /**
14
+ * 验证公钥格式(Base64)
15
+ */
16
+ export declare function validatePublicKey(publicKeyBase64: string): boolean;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * ECDH + AES-GCM 加密工具(浏览器兼容)
3
+ * 用于将签名交易用服务器公钥加密
4
+ */
5
+ /**
6
+ * 获取全局 crypto 对象(最简单直接的方式)
7
+ */
8
+ function getCryptoAPI() {
9
+ // 尝试所有可能的全局对象,优先浏览器环境
10
+ const cryptoObj = (typeof window !== 'undefined' && window.crypto) ||
11
+ (typeof self !== 'undefined' && self.crypto) ||
12
+ (typeof global !== 'undefined' && global.crypto) ||
13
+ (typeof globalThis !== 'undefined' && globalThis.crypto);
14
+ if (!cryptoObj) {
15
+ const env = typeof window !== 'undefined' ? 'Browser' : 'Node.js';
16
+ const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
17
+ throw new Error(`❌ Crypto API 不可用。环境: ${env}, 协议: ${protocol}. ` +
18
+ '请确保在 HTTPS 或 localhost 下运行');
19
+ }
20
+ return cryptoObj;
21
+ }
22
+ /**
23
+ * 获取 SubtleCrypto(用于加密操作)
24
+ */
25
+ function getSubtleCrypto() {
26
+ const crypto = getCryptoAPI();
27
+ if (!crypto.subtle) {
28
+ const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
29
+ const hostname = typeof location !== 'undefined' ? location.hostname : 'unknown';
30
+ throw new Error(`❌ SubtleCrypto API 不可用。协议: ${protocol}, 主机: ${hostname}. ` +
31
+ '请确保:1) 使用 HTTPS (或 localhost);2) 浏览器支持 Web Crypto API;' +
32
+ '3) 不在无痕/隐私浏览模式下');
33
+ }
34
+ return crypto.subtle;
35
+ }
36
+ /**
37
+ * Base64 转 ArrayBuffer(优先使用浏览器 API)
38
+ */
39
+ function base64ToArrayBuffer(base64) {
40
+ // 浏览器环境(优先)
41
+ if (typeof atob !== 'undefined') {
42
+ const binaryString = atob(base64);
43
+ const bytes = new Uint8Array(binaryString.length);
44
+ for (let i = 0; i < binaryString.length; i++) {
45
+ bytes[i] = binaryString.charCodeAt(i);
46
+ }
47
+ return bytes.buffer;
48
+ }
49
+ // Node.js 环境(fallback)
50
+ if (typeof Buffer !== 'undefined') {
51
+ return Buffer.from(base64, 'base64').buffer;
52
+ }
53
+ throw new Error('❌ Base64 解码不可用');
54
+ }
55
+ /**
56
+ * ArrayBuffer 转 Base64(优先使用浏览器 API)
57
+ */
58
+ function arrayBufferToBase64(buffer) {
59
+ // 浏览器环境(优先)
60
+ if (typeof btoa !== 'undefined') {
61
+ const bytes = new Uint8Array(buffer);
62
+ let binary = '';
63
+ for (let i = 0; i < bytes.length; i++) {
64
+ binary += String.fromCharCode(bytes[i]);
65
+ }
66
+ return btoa(binary);
67
+ }
68
+ // Node.js 环境(fallback)
69
+ if (typeof Buffer !== 'undefined') {
70
+ return Buffer.from(buffer).toString('base64');
71
+ }
72
+ throw new Error('❌ Base64 编码不可用');
73
+ }
74
+ /**
75
+ * 生成随机 Hex 字符串
76
+ */
77
+ function randomHex(length) {
78
+ const crypto = getCryptoAPI();
79
+ const array = new Uint8Array(length);
80
+ crypto.getRandomValues(array);
81
+ return Array.from(array)
82
+ .map(b => b.toString(16).padStart(2, '0'))
83
+ .join('');
84
+ }
85
+ /**
86
+ * 用服务器公钥加密签名交易(ECDH + AES-GCM)
87
+ *
88
+ * @param signedTransactions 签名后的交易数组
89
+ * @param publicKeyBase64 服务器提供的公钥(Base64 格式)
90
+ * @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
91
+ */
92
+ export async function encryptWithPublicKey(signedTransactions, publicKeyBase64) {
93
+ try {
94
+ // 0. 获取 SubtleCrypto 和 Crypto API
95
+ const subtle = getSubtleCrypto();
96
+ const crypto = getCryptoAPI();
97
+ // 1. 准备数据
98
+ const payload = {
99
+ signedTransactions,
100
+ timestamp: Date.now(),
101
+ nonce: randomHex(8)
102
+ };
103
+ const plaintext = JSON.stringify(payload);
104
+ // 2. 生成临时 ECDH 密钥对
105
+ const ephemeralKeyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
106
+ // 3. 导入服务器公钥
107
+ const publicKeyBuffer = base64ToArrayBuffer(publicKeyBase64);
108
+ const publicKey = await subtle.importKey('raw', publicKeyBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
109
+ // 4. 派生共享密钥(AES-256)
110
+ const sharedKey = await subtle.deriveKey({ name: 'ECDH', public: publicKey }, ephemeralKeyPair.privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
111
+ // 5. AES-GCM 加密
112
+ const iv = crypto.getRandomValues(new Uint8Array(12));
113
+ const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, new TextEncoder().encode(plaintext));
114
+ // 6. 导出临时公钥
115
+ const ephemeralPublicKeyRaw = await subtle.exportKey('raw', ephemeralKeyPair.publicKey);
116
+ // 7. 返回加密包(JSON 格式)
117
+ return JSON.stringify({
118
+ e: arrayBufferToBase64(ephemeralPublicKeyRaw), // 临时公钥
119
+ i: arrayBufferToBase64(iv.buffer), // IV
120
+ d: arrayBufferToBase64(encrypted) // 密文
121
+ });
122
+ }
123
+ catch (error) {
124
+ throw new Error(`加密失败: ${error?.message || String(error)}`);
125
+ }
126
+ }
127
+ /**
128
+ * 验证公钥格式(Base64)
129
+ */
130
+ export function validatePublicKey(publicKeyBase64) {
131
+ try {
132
+ if (!publicKeyBase64)
133
+ return false;
134
+ // Base64 字符集验证
135
+ if (!/^[A-Za-z0-9+/=]+$/.test(publicKeyBase64))
136
+ return false;
137
+ // ECDH P-256 公钥固定长度 65 字节(未压缩)
138
+ // Base64 编码后约 88 字符
139
+ if (publicKeyBase64.length < 80 || publicKeyBase64.length > 100)
140
+ return false;
141
+ return true;
142
+ }
143
+ catch {
144
+ return false;
145
+ }
146
+ }
@@ -354,8 +354,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
354
354
  const finalGasLimit = getGasLimit(config);
355
355
  const extractProfit = shouldExtractProfit(config);
356
356
  const nonceManager = new NonceManager(provider);
357
- // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances、nonces(JSON-RPC 批量请求)
358
- const [gasPrice, tokenDecimals, , nonces] = await Promise.all([
357
+ // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
358
+ const [gasPrice, tokenDecimals] = await Promise.all([
359
359
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
360
360
  getTokenDecimals(tokenAddress, provider),
361
361
  ensureAllowances({
@@ -363,8 +363,7 @@ export async function pancakeProxyBatchSellMerkle(params) {
363
363
  tokenAddress,
364
364
  owners: sellers.map(w => w.address),
365
365
  spender: ADDRESSES.BSC.PancakeProxy
366
- }),
367
- nonceManager.getNextNoncesForWallets(sellers) // ✅ 批量获取 nonce
366
+ })
368
367
  ]);
369
368
  const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
370
369
  // ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
@@ -376,6 +375,52 @@ export async function pancakeProxyBatchSellMerkle(params) {
376
375
  routeType,
377
376
  amountsWei
378
377
  });
378
+ // ✅ 修复:先计算利润和 maxRevenueIndex,再统一分配 nonces
379
+ let totalProfit = 0n;
380
+ let maxRevenueIndex = -1;
381
+ let maxRevenue = 0n;
382
+ if (extractProfit && quotedOutputs.length > 0) {
383
+ for (let i = 0; i < sellers.length; i++) {
384
+ const quoted = quotedOutputs[i];
385
+ if (quoted > 0n) {
386
+ const { profit } = calculateProfit(quoted, config);
387
+ totalProfit += profit;
388
+ if (quoted > maxRevenue) {
389
+ maxRevenue = quoted;
390
+ maxRevenueIndex = i;
391
+ }
392
+ }
393
+ }
394
+ }
395
+ // ✅ 修复:根据是否需要利润交易,统一分配 nonces
396
+ const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
397
+ let nonces;
398
+ let profitNonce;
399
+ if (needProfitTx) {
400
+ // maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
401
+ const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
402
+ // 其他钱包各需要 1 个 nonce
403
+ const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
404
+ const otherNonces = otherSellers.length > 0
405
+ ? await nonceManager.getNextNoncesForWallets(otherSellers)
406
+ : [];
407
+ // 组装最终的 nonces 数组(保持原顺序)
408
+ nonces = [];
409
+ let otherIdx = 0;
410
+ for (let i = 0; i < sellers.length; i++) {
411
+ if (i === maxRevenueIndex) {
412
+ nonces.push(maxRevenueNonces[0]); // 卖出交易用第一个 nonce
413
+ }
414
+ else {
415
+ nonces.push(otherNonces[otherIdx++]);
416
+ }
417
+ }
418
+ profitNonce = maxRevenueNonces[1]; // 利润交易用第二个 nonce
419
+ }
420
+ else {
421
+ // 不需要利润交易,所有钱包各 1 个 nonce
422
+ nonces = await nonceManager.getNextNoncesForWallets(sellers);
423
+ }
379
424
  const unsignedSells = await buildSellTransactions({
380
425
  routeType,
381
426
  params,
@@ -399,16 +444,19 @@ export async function pancakeProxyBatchSellMerkle(params) {
399
444
  value: txValue
400
445
  });
401
446
  }));
402
- await appendSellProfitTransfer({
403
- extractProfit,
404
- quotedOutputs,
405
- wallets: sellers,
406
- nonceManager,
407
- signedTxs,
408
- gasPrice,
409
- chainId,
410
- config
411
- });
447
+ // ✅ 添加利润交易(使用预先分配的 profitNonce)
448
+ if (needProfitTx && profitNonce !== undefined) {
449
+ const profitTx = await sellers[maxRevenueIndex].signTransaction({
450
+ to: getProfitRecipient(),
451
+ value: totalProfit,
452
+ nonce: profitNonce,
453
+ gasPrice,
454
+ gasLimit: 21000n,
455
+ chainId,
456
+ type: getTxType(config)
457
+ });
458
+ signedTxs.push(profitTx);
459
+ }
412
460
  nonceManager.clearTemp();
413
461
  return {
414
462
  signedTransactions: signedTxs
@@ -479,15 +527,31 @@ async function buildBuyTransactions({ routeType, params, proxies, wallets, token
479
527
  throw new Error(`Unsupported routeType: ${routeType}`);
480
528
  }
481
529
  /**
482
- * ✅ 优化:批量获取所有钱包的 nonce(JSON-RPC 批量请求)
530
+ * ✅ 修复:明确分配 nonces,避免隐式状态依赖
483
531
  */
484
532
  async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, totalProfit, nonceManager) {
485
- // 批量获取所有钱包的初始 nonce(单次网络往返)
486
- const nonces = await nonceManager.getNextNoncesForWallets(wallets);
487
- // 如果需要利润转账,maxIndex 钱包需要额外一个 nonce
488
- if (extractProfit && totalProfit > 0n && maxIndex >= 0 && maxIndex < wallets.length) {
489
- // 预留一个 nonce 给利润交易(从缓存获取,不触发 RPC)
490
- await nonceManager.getNextNonce(wallets[maxIndex]);
533
+ const needProfitTx = extractProfit && totalProfit > 0n && maxIndex >= 0 && maxIndex < wallets.length;
534
+ if (!needProfitTx) {
535
+ // 不需要利润交易,所有钱包各 1 nonce
536
+ return await nonceManager.getNextNoncesForWallets(wallets);
537
+ }
538
+ // 需要利润交易:maxIndex 钱包需要 2 个连续 nonce
539
+ const maxIndexNonces = await nonceManager.getNextNonceBatch(wallets[maxIndex], 2);
540
+ // 其他钱包各需要 1 个 nonce
541
+ const otherWallets = wallets.filter((_, i) => i !== maxIndex);
542
+ const otherNonces = otherWallets.length > 0
543
+ ? await nonceManager.getNextNoncesForWallets(otherWallets)
544
+ : [];
545
+ // 组装最终的 nonces 数组(保持原顺序)
546
+ const nonces = [];
547
+ let otherIdx = 0;
548
+ for (let i = 0; i < wallets.length; i++) {
549
+ if (i === maxIndex) {
550
+ nonces.push(maxIndexNonces[0]); // 买入交易用第一个 nonce
551
+ }
552
+ else {
553
+ nonces.push(otherNonces[otherIdx++]);
554
+ }
491
555
  }
492
556
  return nonces;
493
557
  }
@@ -641,36 +705,4 @@ async function buildSellTransactions({ routeType, params, proxies, wallets, toke
641
705
  }
642
706
  throw new Error(`Unsupported routeType: ${routeType}`);
643
707
  }
644
- async function appendSellProfitTransfer({ extractProfit, quotedOutputs, wallets, nonceManager, signedTxs, gasPrice, chainId, config }) {
645
- if (!extractProfit || quotedOutputs.length === 0) {
646
- return;
647
- }
648
- let totalProfit = 0n;
649
- let maxRevenueIndex = -1;
650
- let maxRevenue = 0n;
651
- for (let i = 0; i < wallets.length; i++) {
652
- const quoted = quotedOutputs[i];
653
- if (quoted > 0n) {
654
- const { profit } = calculateProfit(quoted, config);
655
- totalProfit += profit;
656
- if (quoted > maxRevenue) {
657
- maxRevenue = quoted;
658
- maxRevenueIndex = i;
659
- }
660
- }
661
- }
662
- if (totalProfit === 0n || maxRevenueIndex < 0) {
663
- return;
664
- }
665
- const profitNonce = await nonceManager.getNextNonce(wallets[maxRevenueIndex]);
666
- const profitTx = await wallets[maxRevenueIndex].signTransaction({
667
- to: getProfitRecipient(),
668
- value: totalProfit,
669
- nonce: profitNonce,
670
- gasPrice,
671
- gasLimit: 21000n,
672
- chainId,
673
- type: getTxType(config)
674
- });
675
- signedTxs.push(profitTx);
676
- }
708
+ // appendSellProfitTransfer 已内联到 pancakeProxyBatchSellMerkle 中,避免 nonce 竞争问题
@@ -147,8 +147,8 @@ export async function flapBundleBuyFirstMerkle(params) {
147
147
  });
148
148
  const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
149
149
  const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
150
- // ✅ 优化:第三批并行 - buyUnsigned、sellUnsigned、approvalTx、estimatedSellFunds、noncePlan
151
- const [buyUnsigned, sellUnsigned, approvalTx, estimatedSellFunds, noncePlan] = await Promise.all([
150
+ // ✅ 第三批并行 - buyUnsigned、sellUnsigned、estimatedSellFunds(不含 nonce 相关操作)
151
+ const [buyUnsigned, sellUnsigned, estimatedSellFunds] = await Promise.all([
152
152
  portalBuyer.swapExactInput.populateTransaction({
153
153
  inputToken, // ✅ 使用动态输入代币
154
154
  outputToken: tokenAddress,
@@ -164,29 +164,30 @@ export async function flapBundleBuyFirstMerkle(params) {
164
164
  minOutputAmount: 0,
165
165
  permitData: '0x'
166
166
  }),
167
- buildApprovalTransaction({
168
- tokenAddress,
169
- provider: chainContext.provider,
170
- seller,
171
- decimals: sellerInfo.decimals,
172
- portalAddress: chainContext.portalAddress,
173
- chainId: chainContext.chainId,
174
- config,
175
- nonceManager,
176
- gasPrice,
177
- txType
178
- }),
179
- estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken),
180
- // 预先规划 nonces(假设可能需要利润转账)
181
- planNonces({
182
- buyer,
183
- seller,
184
- approvalExists: true, // 保守估计,假设需要授权
185
- extractProfit: true, // 保守估计,假设需要利润转账
186
- sameAddress,
187
- nonceManager
188
- })
167
+ estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken)
189
168
  ]);
169
+ // ✅ 修复:先构建授权交易(会消耗 nonce),再规划其他 nonce
170
+ const approvalTx = await buildApprovalTransaction({
171
+ tokenAddress,
172
+ provider: chainContext.provider,
173
+ seller,
174
+ decimals: sellerInfo.decimals,
175
+ portalAddress: chainContext.portalAddress,
176
+ chainId: chainContext.chainId,
177
+ config,
178
+ nonceManager,
179
+ gasPrice,
180
+ txType
181
+ });
182
+ // ✅ 修复:根据实际的授权情况规划 nonce
183
+ const noncePlan = await planNonces({
184
+ buyer,
185
+ seller,
186
+ approvalExists: approvalTx !== null, // ✅ 使用实际值
187
+ extractProfit: true,
188
+ sameAddress,
189
+ nonceManager
190
+ });
190
191
  // ✅ 修复:基于卖出收益估算利润
191
192
  const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : buyerFundsWei;
192
193
  const tokenProfitAmount = calculateProfitAmount(profitBase);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.39",
3
+ "version": "1.3.40",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",