four-flap-meme-sdk 1.4.4 → 1.4.5

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.
@@ -846,13 +846,26 @@ export async function directV2BatchSell(params) {
846
846
  sellAmountsWei.push(balances[i]);
847
847
  }
848
848
  }
849
- // 方案 B:提前计算利润并并行获取 ERC20 报价
850
- const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
851
- const baseProfitWei = calculateProfitAmount(totalOutputEstimate);
852
- // ERC20 报价(如果需要)- 可以在签名同时进行
853
- const nativeProfitPromise = (!useNativeOutput && baseProfitWei > 0n && quoteToken)
854
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
855
- : Promise.resolve(baseProfitWei);
849
+ const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
850
+ // 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
851
+ // 需要先获取卖出报价,然后基于预估得到的原生代币数量计算利润
852
+ const nativeProfitPromise = (async () => {
853
+ if (useNativeOutput) {
854
+ // 卖出代币 得到 BNB:先获取报价,再计算利润
855
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
856
+ if (estimatedBNBOut <= 0n)
857
+ return 0n;
858
+ return calculateProfitAmount(estimatedBNBOut); // 0.3% × 得到的 BNB
859
+ }
860
+ else if (quoteToken) {
861
+ // 卖出代币 → 得到 ERC20:先计算 ERC20 利润,再转换为 BNB
862
+ const baseProfitWei = calculateProfitAmount(totalSellAmount);
863
+ if (baseProfitWei <= 0n)
864
+ return 0n;
865
+ return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
866
+ }
867
+ return 0n;
868
+ })();
856
869
  // 构建路径和 Router
857
870
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
858
871
  const path = [tokenAddress, outputToken];
@@ -939,7 +952,7 @@ export async function directV2BatchSell(params) {
939
952
  metadata: {
940
953
  profitAmount: ethers.formatEther(profitWei),
941
954
  profitRecipient: PROFIT_CONFIG.RECIPIENT,
942
- totalFlow: ethers.formatEther(totalOutputEstimate),
955
+ totalFlow: ethers.formatEther(totalSellAmount),
943
956
  },
944
957
  };
945
958
  }
@@ -1084,12 +1097,26 @@ export async function directV3BatchSell(params) {
1084
1097
  sellAmountsWei.push(balances[i]);
1085
1098
  }
1086
1099
  }
1087
- // 方案 B:提前计算利润并并行获取 ERC20 报价
1088
- const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
1089
- const baseProfitWei = calculateProfitAmount(totalOutputEstimate);
1090
- const nativeProfitPromise = (!useNativeOutput && baseProfitWei > 0n && quoteToken)
1091
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
1092
- : Promise.resolve(baseProfitWei);
1100
+ const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
1101
+ // 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
1102
+ // 需要先获取卖出报价,然后基于预估得到的原生代币数量计算利润
1103
+ const nativeProfitPromise = (async () => {
1104
+ if (useNativeOutput) {
1105
+ // 卖出代币 → 得到 BNB:先获取报价,再计算利润
1106
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
1107
+ if (estimatedBNBOut <= 0n)
1108
+ return 0n;
1109
+ return calculateProfitAmount(estimatedBNBOut); // 0.3% × 得到的 BNB
1110
+ }
1111
+ else if (quoteToken) {
1112
+ // 卖出代币 → 得到 ERC20:先计算 ERC20 利润,再转换为 BNB
1113
+ const baseProfitWei = calculateProfitAmount(totalSellAmount);
1114
+ if (baseProfitWei <= 0n)
1115
+ return 0n;
1116
+ return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
1117
+ }
1118
+ return 0n;
1119
+ })();
1093
1120
  const routerIface = new ethers.Interface(routerAbi);
1094
1121
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
1095
1122
  const deadline = getDeadline();
@@ -1157,7 +1184,7 @@ export async function directV3BatchSell(params) {
1157
1184
  metadata: {
1158
1185
  profitAmount: ethers.formatEther(profitWei),
1159
1186
  profitRecipient: PROFIT_CONFIG.RECIPIENT,
1160
- totalFlow: ethers.formatEther(totalOutputEstimate),
1187
+ totalFlow: ethers.formatEther(totalSellAmount),
1161
1188
  },
1162
1189
  };
1163
1190
  }
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export { inspectTokenLP, getFactoryFromRouter, registerDYORSwap, registerDex, ge
38
38
  export { disperseWithBundle, sweepWithBundle, type DisperseSignParams, type SweepSignParams, type SignedTransactionsResult, type DisperseParams, type SweepParams, type BundleSubmitResult } from './utils/airdrop-sweep.js';
39
39
  export { fourBundleSwapMerkle, fourBatchSwapMerkle, type FourSwapSignConfig, type FourSwapConfig, type FourBundleSwapSignParams, type FourBundleSwapParams, type FourSwapResult, type FourBatchSwapSignParams, type FourBatchSwapResult } from './contracts/tm-bundle-merkle/swap.js';
40
40
  export { flapBundleSwapMerkle, flapBatchSwapMerkle, type FlapSwapSignConfig, type FlapSwapConfig, type FlapBundleSwapSignParams, type FlapBundleSwapParams, type FlapSwapResult, type FlapBatchSwapSignParams, type FlapBatchSwapResult } from './flap/portal-bundle-merkle/swap.js';
41
- export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
41
+ export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type PancakeQuickBatchSwapParams, type PancakeQuickBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
42
42
  export { fourBundleBuyFirstMerkle, type FourBuyFirstConfig, type FourBundleBuyFirstParams, type FourBuyFirstSignConfig, type FourBundleBuyFirstSignParams, type FourBuyFirstResult } from './contracts/tm-bundle-merkle/swap-buy-first.js';
43
43
  export { flapBundleBuyFirstMerkle, type FlapBuyFirstSignConfig, type FlapBuyFirstConfig, type FlapBundleBuyFirstSignParams, type FlapBundleBuyFirstParams, type FlapBuyFirstResult } from './flap/portal-bundle-merkle/swap-buy-first.js';
44
44
  export { pancakeBundleBuyFirstMerkle, type PancakeBuyFirstSignConfig, type PancakeBuyFirstConfig, type PancakeBundleBuyFirstSignParams, type PancakeBundleBuyFirstParams, type PancakeBuyFirstResult } from './pancake/bundle-buy-first.js';
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ export { fourBundleSwapMerkle, fourBatchSwapMerkle } from './contracts/tm-bundle
56
56
  // Flap内盘换手
57
57
  export { flapBundleSwapMerkle, flapBatchSwapMerkle } from './flap/portal-bundle-merkle/swap.js';
58
58
  // PancakeSwap V2/V3通用换手
59
- export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle } from './pancake/bundle-swap.js';
59
+ export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle } from './pancake/bundle-swap.js';
60
60
  // 先买后卖(Buy-First)入口
61
61
  export { fourBundleBuyFirstMerkle } from './contracts/tm-bundle-merkle/swap-buy-first.js';
62
62
  export { flapBundleBuyFirstMerkle } from './flap/portal-bundle-merkle/swap-buy-first.js';
@@ -115,3 +115,45 @@ export interface PancakeBatchSwapResult {
115
115
  * 交易顺序:[授权(可选)] → [卖出] → [买入1, 买入2, ..., 买入N] → [利润]
116
116
  */
117
117
  export declare function pancakeBatchSwapMerkle(params: PancakeBatchSwapSignParams): Promise<PancakeBatchSwapResult>;
118
+ /**
119
+ * 快捷批量换手参数
120
+ * 特点:子钱包不需要预先有余额,资金来自主钱包卖出所得
121
+ */
122
+ export interface PancakeQuickBatchSwapParams {
123
+ sellerPrivateKey: string;
124
+ sellAmount?: string;
125
+ sellPercentage?: number;
126
+ buyerPrivateKeys: string[];
127
+ buyerRatios?: number[];
128
+ buyerAmounts?: string[];
129
+ tokenAddress: string;
130
+ routeParams: RouteParams;
131
+ config: PancakeSwapSignConfig;
132
+ }
133
+ /**
134
+ * 快捷批量换手结果
135
+ */
136
+ export interface PancakeQuickBatchSwapResult {
137
+ signedTransactions: string[];
138
+ metadata?: {
139
+ sellerAddress: string;
140
+ buyerAddresses: string[];
141
+ sellAmount: string;
142
+ estimatedBNBOut: string;
143
+ transferAmounts: string[];
144
+ profitAmount?: string;
145
+ };
146
+ }
147
+ /**
148
+ * PancakeSwap 快捷批量换手(资金自动流转)
149
+ *
150
+ * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
151
+ *
152
+ * 特点:
153
+ * - 子钱包不需要预先有 BNB 余额
154
+ * - 资金来自主钱包卖出代币所得
155
+ * - 提升资金利用率
156
+ *
157
+ * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
158
+ */
159
+ export declare function pancakeQuickBatchSwapMerkle(params: PancakeQuickBatchSwapParams): Promise<PancakeQuickBatchSwapResult>;
@@ -667,3 +667,208 @@ export async function pancakeBatchSwapMerkle(params) {
667
667
  }
668
668
  };
669
669
  }
670
+ /**
671
+ * PancakeSwap 快捷批量换手(资金自动流转)
672
+ *
673
+ * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
674
+ *
675
+ * 特点:
676
+ * - 子钱包不需要预先有 BNB 余额
677
+ * - 资金来自主钱包卖出代币所得
678
+ * - 提升资金利用率
679
+ *
680
+ * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
681
+ */
682
+ export async function pancakeQuickBatchSwapMerkle(params) {
683
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
684
+ // ✅ 校验买方数量(最多 12 个,因为每个买方需要 2 笔交易:转账 + 买入)
685
+ const MAX_BUYERS = 12;
686
+ if (buyerPrivateKeys.length === 0) {
687
+ throw new Error('至少需要一个买方钱包');
688
+ }
689
+ if (buyerPrivateKeys.length > MAX_BUYERS) {
690
+ throw new Error(`快捷模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
691
+ }
692
+ // ✅ 校验分配模式
693
+ if (!buyerRatios && !buyerAmounts) {
694
+ throw new Error('必须提供 buyerRatios 或 buyerAmounts');
695
+ }
696
+ if (buyerRatios && buyerRatios.length !== buyerPrivateKeys.length) {
697
+ throw new Error(`buyerRatios 长度 (${buyerRatios.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
698
+ }
699
+ if (buyerAmounts && buyerAmounts.length !== buyerPrivateKeys.length) {
700
+ throw new Error(`buyerAmounts 长度 (${buyerAmounts.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
701
+ }
702
+ const context = createPancakeContext(config);
703
+ const seller = new Wallet(sellerPrivateKey, context.provider);
704
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
705
+ // ✅ 创建共享资源
706
+ const nonceManager = new NonceManager(context.provider);
707
+ const finalGasLimit = getGasLimit(config);
708
+ const gasPrice = await getGasPrice(context.provider, config);
709
+ const txType = config.txType ?? 0;
710
+ // ✅ 计算卖出数量
711
+ const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
712
+ // ✅ 获取卖出报价
713
+ const quoteResult = await quoteSellOutput({
714
+ routeParams,
715
+ sellAmountWei,
716
+ provider: context.provider
717
+ });
718
+ const estimatedBNBOut = quoteResult.estimatedBNBOut;
719
+ // ✅ 计算利润(从卖出所得中预扣)
720
+ const profitAmount = calculateProfitAmount(estimatedBNBOut);
721
+ const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
722
+ // ✅ 计算每个子钱包分到的金额
723
+ let transferAmountsWei;
724
+ if (buyerAmounts && buyerAmounts.length === buyers.length) {
725
+ // 数量模式:使用指定的金额
726
+ transferAmountsWei = buyerAmounts.map(amt => ethers.parseEther(amt));
727
+ // 校验总金额不超过可分配金额
728
+ const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
729
+ if (totalTransfer > distributableBNB) {
730
+ throw new Error(`指定的买入总金额 (${ethers.formatEther(totalTransfer)} BNB) 超过可分配金额 (${ethers.formatEther(distributableBNB)} BNB)`);
731
+ }
732
+ }
733
+ else if (buyerRatios && buyerRatios.length === buyers.length) {
734
+ // 比例模式:按比例分配可分配金额
735
+ transferAmountsWei = buyerRatios.map(ratio => {
736
+ const amount = (distributableBNB * BigInt(Math.round(ratio * 10000))) / 10000n;
737
+ return amount;
738
+ });
739
+ }
740
+ else {
741
+ throw new Error('必须提供 buyerRatios 或 buyerAmounts');
742
+ }
743
+ // ✅ 获取贿赂金额
744
+ const bribeAmount = config.bribeAmount && config.bribeAmount > 0
745
+ ? ethers.parseEther(String(config.bribeAmount))
746
+ : 0n;
747
+ // ✅ 验证主钱包余额(需要支付 Gas 费用)
748
+ const sellerBalance = await seller.provider.getBalance(seller.address);
749
+ const estimatedGasCost = gasPrice * finalGasLimit * BigInt(3 + buyers.length * 2); // 贿赂 + 卖出 + 转账 + 利润
750
+ if (sellerBalance < bribeAmount + estimatedGasCost) {
751
+ throw new Error(`主钱包 BNB 余额不足 (用于支付 Gas): 需要约 ${ethers.formatEther(bribeAmount + estimatedGasCost)} BNB, 实际 ${ethers.formatEther(sellerBalance)} BNB`);
752
+ }
753
+ // ==================== 规划 Nonce ====================
754
+ // 卖方: [贿赂(可选)] → [卖出] → [转账1, 转账2, ...] → [利润(可选)]
755
+ // 买方: [买入]
756
+ let sellerNonce = await nonceManager.getNextNonce(seller);
757
+ // ✅ 贿赂交易(首位)
758
+ let bribeTx = null;
759
+ if (bribeAmount > 0n) {
760
+ bribeTx = await seller.signTransaction({
761
+ to: BLOCKRAZOR_BUILDER_EOA,
762
+ value: bribeAmount,
763
+ nonce: sellerNonce++,
764
+ gasPrice,
765
+ gasLimit: 21000n,
766
+ chainId: context.chainId,
767
+ type: txType
768
+ });
769
+ }
770
+ // ✅ 卖出交易
771
+ const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
772
+ const deadline = Math.floor(Date.now() / 1000) + 600;
773
+ let sellUnsigned;
774
+ if (routeParams.routeType === 'v2') {
775
+ const { v2Path } = routeParams;
776
+ sellUnsigned = await proxySeller.swapV2.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline, { value: FLAT_FEE });
777
+ }
778
+ else if (routeParams.routeType === 'v3-single') {
779
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
780
+ sellUnsigned = await proxySeller.swapV3Single.populateTransaction(v3TokenIn, v3TokenOut, v3Fee, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
781
+ }
782
+ else {
783
+ const { v3LpAddresses, v3ExactTokenIn } = routeParams;
784
+ sellUnsigned = await proxySeller.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
785
+ }
786
+ const signedSell = await seller.signTransaction({
787
+ ...sellUnsigned,
788
+ from: seller.address,
789
+ nonce: sellerNonce++,
790
+ gasLimit: finalGasLimit,
791
+ gasPrice,
792
+ chainId: context.chainId,
793
+ type: txType
794
+ });
795
+ // ✅ 转账交易(主钱包 → 各子钱包)
796
+ const transferTxs = [];
797
+ for (let i = 0; i < buyers.length; i++) {
798
+ const transferTx = await seller.signTransaction({
799
+ to: buyers[i].address,
800
+ value: transferAmountsWei[i],
801
+ nonce: sellerNonce++,
802
+ gasPrice,
803
+ gasLimit: 21000n,
804
+ chainId: context.chainId,
805
+ type: txType
806
+ });
807
+ transferTxs.push(transferTx);
808
+ }
809
+ // ✅ 利润交易
810
+ let profitTx = null;
811
+ if (profitAmount > 0n) {
812
+ profitTx = await seller.signTransaction({
813
+ to: PROFIT_CONFIG.RECIPIENT,
814
+ value: profitAmount,
815
+ nonce: sellerNonce++,
816
+ gasPrice,
817
+ gasLimit: 21000n,
818
+ chainId: context.chainId,
819
+ type: txType
820
+ });
821
+ }
822
+ // ✅ 并行获取所有买方的 nonce
823
+ const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
824
+ // ✅ 并行构建买入交易
825
+ const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
826
+ const buyAmount = transferAmountsWei[i];
827
+ const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
828
+ const buyValue = buyAmount + FLAT_FEE;
829
+ let buyUnsigned;
830
+ if (routeParams.routeType === 'v2') {
831
+ const { v2Path } = routeParams;
832
+ const reversePath = [...v2Path].reverse();
833
+ buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline, { value: buyValue });
834
+ }
835
+ else if (routeParams.routeType === 'v3-single') {
836
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
837
+ buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenOut, v3TokenIn, v3Fee, buyAmount, 0n, buyer.address, { value: buyValue });
838
+ }
839
+ else {
840
+ const { v3LpAddresses } = routeParams;
841
+ buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, tokenAddress, buyAmount, 0n, buyer.address, { value: buyValue });
842
+ }
843
+ return buyer.signTransaction({
844
+ ...buyUnsigned,
845
+ from: buyer.address,
846
+ nonce: buyerNonces[i],
847
+ gasLimit: finalGasLimit,
848
+ gasPrice,
849
+ chainId: context.chainId,
850
+ type: txType
851
+ });
852
+ }));
853
+ nonceManager.clearTemp();
854
+ // ✅ 按顺序组装交易数组:贿赂 → 卖出 → 转账 → 买入 → 利润
855
+ const signedTransactions = [];
856
+ if (bribeTx)
857
+ signedTransactions.push(bribeTx); // 贿赂(首位)
858
+ signedTransactions.push(signedSell); // 卖出
859
+ signedTransactions.push(...transferTxs); // 转账到各子钱包
860
+ signedTransactions.push(...signedBuys); // 各子钱包买入
861
+ if (profitTx)
862
+ signedTransactions.push(profitTx); // 利润(末尾)
863
+ return {
864
+ signedTransactions,
865
+ metadata: {
866
+ sellerAddress: seller.address,
867
+ buyerAddresses: buyers.map(b => b.address),
868
+ sellAmount: ethers.formatUnits(sellAmountWei, decimals),
869
+ estimatedBNBOut: ethers.formatEther(estimatedBNBOut),
870
+ transferAmounts: transferAmountsWei.map(amt => ethers.formatEther(amt)),
871
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
872
+ }
873
+ };
874
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",