four-flap-meme-sdk 1.4.12 → 1.4.13
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/pancake/bundle-swap.d.ts +6 -2
- package/dist/pancake/bundle-swap.js +162 -55
- package/package.json +1 -1
|
@@ -129,6 +129,8 @@ export interface PancakeQuickBatchSwapParams {
|
|
|
129
129
|
tokenAddress: string;
|
|
130
130
|
routeParams: RouteParams;
|
|
131
131
|
config: PancakeSwapSignConfig;
|
|
132
|
+
quoteToken?: string;
|
|
133
|
+
quoteTokenDecimals?: number;
|
|
132
134
|
}
|
|
133
135
|
/**
|
|
134
136
|
* 快捷批量换手结果
|
|
@@ -139,9 +141,10 @@ export interface PancakeQuickBatchSwapResult {
|
|
|
139
141
|
sellerAddress: string;
|
|
140
142
|
buyerAddresses: string[];
|
|
141
143
|
sellAmount: string;
|
|
142
|
-
|
|
144
|
+
estimatedOutput: string;
|
|
143
145
|
transferAmounts: string[];
|
|
144
146
|
profitAmount?: string;
|
|
147
|
+
useNativeToken: boolean;
|
|
145
148
|
};
|
|
146
149
|
}
|
|
147
150
|
/**
|
|
@@ -150,9 +153,10 @@ export interface PancakeQuickBatchSwapResult {
|
|
|
150
153
|
* 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
|
|
151
154
|
*
|
|
152
155
|
* 特点:
|
|
153
|
-
* -
|
|
156
|
+
* - 子钱包不需要预先有余额
|
|
154
157
|
* - 资金来自主钱包卖出代币所得
|
|
155
158
|
* - 提升资金利用率
|
|
159
|
+
* - 支持 BNB 和 ERC20(如 USDT)两种模式
|
|
156
160
|
*
|
|
157
161
|
* 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
|
|
158
162
|
*/
|
|
@@ -743,22 +743,30 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
743
743
|
* 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
|
|
744
744
|
*
|
|
745
745
|
* 特点:
|
|
746
|
-
* -
|
|
746
|
+
* - 子钱包不需要预先有余额
|
|
747
747
|
* - 资金来自主钱包卖出代币所得
|
|
748
748
|
* - 提升资金利用率
|
|
749
|
+
* - 支持 BNB 和 ERC20(如 USDT)两种模式
|
|
749
750
|
*
|
|
750
751
|
* 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
|
|
751
752
|
*/
|
|
752
753
|
export async function pancakeQuickBatchSwapMerkle(params) {
|
|
753
|
-
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
|
|
754
|
-
// ✅
|
|
755
|
-
|
|
756
|
-
const
|
|
754
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18 } = params;
|
|
755
|
+
// ✅ 判断是否使用原生代币
|
|
756
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
757
|
+
const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase();
|
|
758
|
+
// ✅ 校验买方数量
|
|
759
|
+
// BNB 模式:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23
|
|
760
|
+
// ERC20 模式:贿赂(1) + 卖出(1) + ERC20转账(N) + BNB Gas转账(N) + 买入(N) + 利润(1) ≤ 50 → 3N + 3 ≤ 50 → N ≤ 15
|
|
761
|
+
const MAX_BUYERS_NATIVE = 23;
|
|
762
|
+
const MAX_BUYERS_ERC20 = 15;
|
|
763
|
+
const MAX_BUYERS = useNativeToken ? MAX_BUYERS_NATIVE : MAX_BUYERS_ERC20;
|
|
757
764
|
if (buyerPrivateKeys.length === 0) {
|
|
758
765
|
throw new Error('至少需要一个买方钱包');
|
|
759
766
|
}
|
|
760
767
|
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
761
|
-
|
|
768
|
+
const mode = useNativeToken ? 'BNB' : 'ERC20';
|
|
769
|
+
throw new Error(`资金利用率模式(${mode})买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
762
770
|
}
|
|
763
771
|
// ✅ 校验分配模式
|
|
764
772
|
if (!buyerRatios && !buyerAmounts) {
|
|
@@ -772,20 +780,51 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
772
780
|
}
|
|
773
781
|
const context = createPancakeContext(config);
|
|
774
782
|
const seller = new Wallet(sellerPrivateKey, context.provider);
|
|
775
|
-
// ✅ 买方需要私钥(因为买方自己执行买入交易)
|
|
776
783
|
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
784
|
+
// ✅ 校验卖出路径输出代币是否匹配
|
|
785
|
+
let sellOutputToken;
|
|
786
|
+
if (routeParams.routeType === 'v2') {
|
|
787
|
+
const { v2Path } = routeParams;
|
|
788
|
+
sellOutputToken = v2Path[v2Path.length - 1];
|
|
789
|
+
}
|
|
790
|
+
else if (routeParams.routeType === 'v3-single') {
|
|
791
|
+
const { v3TokenOut } = routeParams;
|
|
792
|
+
sellOutputToken = v3TokenOut;
|
|
793
|
+
}
|
|
794
|
+
else if (routeParams.routeType === 'v3-multi') {
|
|
795
|
+
const { v2Path } = routeParams;
|
|
796
|
+
if (v2Path && v2Path.length > 0) {
|
|
797
|
+
sellOutputToken = v2Path[v2Path.length - 1];
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
// 校验输出代币
|
|
801
|
+
if (useNativeToken) {
|
|
802
|
+
// 原生代币模式:输出必须是 WBNB
|
|
803
|
+
if (!sellOutputToken || sellOutputToken.toLowerCase() !== WBNB) {
|
|
804
|
+
throw new Error(`原生代币模式要求卖出路径以 WBNB 结尾(当前输出: ${sellOutputToken || '未知'})。` +
|
|
805
|
+
`请切换交易对或使用 ERC20 模式。`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
// ERC20 模式:输出必须是指定的 quoteToken
|
|
810
|
+
if (!sellOutputToken || sellOutputToken.toLowerCase() !== quoteToken.toLowerCase()) {
|
|
811
|
+
throw new Error(`ERC20 模式要求卖出路径以 ${quoteToken} 结尾(当前输出: ${sellOutputToken || '未知'})。`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
777
814
|
// ✅ 创建共享资源
|
|
778
815
|
const nonceManager = new NonceManager(context.provider);
|
|
779
816
|
const finalGasLimit = getGasLimit(config);
|
|
780
817
|
const txType = config.txType ?? 0;
|
|
818
|
+
const ERC20_TRANSFER_GAS = 65000n; // ERC20 transfer 的 gas 限制
|
|
781
819
|
// ✅ 并行获取 gasPrice 和卖出数量
|
|
782
820
|
const [gasPrice, sellAmountResult] = await Promise.all([
|
|
783
821
|
getGasPrice(context.provider, config),
|
|
784
822
|
calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
|
|
785
823
|
]);
|
|
786
824
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
787
|
-
// ✅
|
|
788
|
-
console.log(`[pancakeQuickBatchSwapMerkle]
|
|
825
|
+
// ✅ 调试日志
|
|
826
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 模式: ${useNativeToken ? 'BNB' : 'ERC20'}`);
|
|
827
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)}`);
|
|
789
828
|
console.log(`[pancakeQuickBatchSwapMerkle] routeParams:`, JSON.stringify({
|
|
790
829
|
routeType: routeParams.routeType,
|
|
791
830
|
v2Path: routeParams.v2Path,
|
|
@@ -799,31 +838,44 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
799
838
|
sellAmountWei,
|
|
800
839
|
provider: context.provider
|
|
801
840
|
});
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
841
|
+
const estimatedOutput = quoteResult.estimatedBNBOut;
|
|
842
|
+
const outputFormatted = useNativeToken
|
|
843
|
+
? ethers.formatEther(estimatedOutput)
|
|
844
|
+
: ethers.formatUnits(estimatedOutput, quoteTokenDecimals);
|
|
845
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 预估卖出所得: ${outputFormatted} ${useNativeToken ? 'BNB' : 'ERC20'}`);
|
|
846
|
+
// ✅ 计算利润(基于 BNB 价值)
|
|
847
|
+
let profitAmount;
|
|
848
|
+
if (useNativeToken) {
|
|
849
|
+
profitAmount = calculateProfitAmount(estimatedOutput);
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
// ERC20 模式:需要将 ERC20 价值转换为 BNB
|
|
853
|
+
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
854
|
+
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
855
|
+
const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedOutput, version, fee);
|
|
856
|
+
profitAmount = calculateProfitAmount(estimatedBNBValue);
|
|
857
|
+
console.log(`[pancakeQuickBatchSwapMerkle] ERC20→BNB 报价: ${outputFormatted} → ${ethers.formatEther(estimatedBNBValue)} BNB`);
|
|
858
|
+
}
|
|
859
|
+
const distributableAmount = estimatedOutput - (useNativeToken ? profitAmount : 0n);
|
|
860
|
+
// ✅ 计算每个买方分到的金额
|
|
812
861
|
let transferAmountsWei;
|
|
813
862
|
if (buyerAmounts && buyerAmounts.length === buyers.length) {
|
|
814
|
-
//
|
|
815
|
-
transferAmountsWei = buyerAmounts.map(amt =>
|
|
816
|
-
|
|
863
|
+
// 数量模式
|
|
864
|
+
transferAmountsWei = buyerAmounts.map(amt => useNativeToken
|
|
865
|
+
? ethers.parseEther(amt)
|
|
866
|
+
: ethers.parseUnits(amt, quoteTokenDecimals));
|
|
817
867
|
const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
|
|
818
|
-
if (totalTransfer >
|
|
819
|
-
|
|
868
|
+
if (totalTransfer > distributableAmount) {
|
|
869
|
+
const formatted = useNativeToken
|
|
870
|
+
? ethers.formatEther(distributableAmount)
|
|
871
|
+
: ethers.formatUnits(distributableAmount, quoteTokenDecimals);
|
|
872
|
+
throw new Error(`指定的买入总金额超过可分配金额 (${formatted})`);
|
|
820
873
|
}
|
|
821
874
|
}
|
|
822
875
|
else if (buyerRatios && buyerRatios.length === buyers.length) {
|
|
823
|
-
//
|
|
876
|
+
// 比例模式
|
|
824
877
|
transferAmountsWei = buyerRatios.map(ratio => {
|
|
825
|
-
|
|
826
|
-
return amount;
|
|
878
|
+
return (distributableAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
827
879
|
});
|
|
828
880
|
}
|
|
829
881
|
else {
|
|
@@ -833,16 +885,27 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
833
885
|
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
834
886
|
? ethers.parseEther(String(config.bribeAmount))
|
|
835
887
|
: 0n;
|
|
836
|
-
// ✅
|
|
888
|
+
// ✅ 验证主钱包余额
|
|
837
889
|
const sellerBalance = await seller.provider.getBalance(seller.address);
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
890
|
+
let sellerGasCost;
|
|
891
|
+
let sellerRequired;
|
|
892
|
+
if (useNativeToken) {
|
|
893
|
+
// BNB 模式:贿赂(21000) + 卖出(gasLimit) + N个原生转账(21000 each) + 利润(21000)
|
|
894
|
+
sellerGasCost = gasPrice * (21000n + finalGasLimit + 21000n * BigInt(buyers.length) + 21000n);
|
|
895
|
+
sellerRequired = bribeAmount + sellerGasCost;
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
// ERC20 模式:
|
|
899
|
+
// - 贿赂(21000) + 卖出(gasLimit) + N个ERC20转账(65000 each) + N个BNB Gas转账(21000 each) + 利润(21000)
|
|
900
|
+
// - 还需要给买家转 BNB 用于支付 FLAT_FEE 和买入 Gas
|
|
901
|
+
sellerGasCost = gasPrice * (21000n + finalGasLimit + (ERC20_TRANSFER_GAS + 21000n) * BigInt(buyers.length) + 21000n);
|
|
902
|
+
const buyerGasNeeded = (gasPrice * finalGasLimit + FLAT_FEE) * BigInt(buyers.length);
|
|
903
|
+
sellerRequired = bribeAmount + sellerGasCost + buyerGasNeeded;
|
|
904
|
+
}
|
|
841
905
|
if (sellerBalance < sellerRequired) {
|
|
842
906
|
throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
|
|
843
907
|
}
|
|
844
908
|
// ==================== 规划 Nonce ====================
|
|
845
|
-
// 流程: [贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
|
|
846
909
|
let sellerNonce = await nonceManager.getNextNonce(seller);
|
|
847
910
|
const deadline = Math.floor(Date.now() / 1000) + 600;
|
|
848
911
|
// ==================== 1. 贿赂交易 ====================
|
|
@@ -884,32 +947,71 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
884
947
|
type: txType
|
|
885
948
|
});
|
|
886
949
|
console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`);
|
|
887
|
-
// ==================== 3.
|
|
950
|
+
// ==================== 3. 转账交易 ====================
|
|
888
951
|
const transferTxs = [];
|
|
889
|
-
|
|
890
|
-
//
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
952
|
+
if (useNativeToken) {
|
|
953
|
+
// ✅ 原生代币模式:直接 BNB 转账
|
|
954
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
955
|
+
const buyerGasCost = gasPrice * finalGasLimit;
|
|
956
|
+
const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
|
|
957
|
+
const transferTx = await seller.signTransaction({
|
|
958
|
+
to: buyers[i].address,
|
|
959
|
+
value: transferValue,
|
|
960
|
+
nonce: sellerNonce++,
|
|
961
|
+
gasPrice,
|
|
962
|
+
gasLimit: 21000n,
|
|
963
|
+
chainId: context.chainId,
|
|
964
|
+
type: txType
|
|
965
|
+
});
|
|
966
|
+
transferTxs.push(transferTx);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
// ✅ ERC20 模式:ERC20 transfer 调用
|
|
971
|
+
const erc20Interface = new ethers.Interface([
|
|
972
|
+
'function transfer(address to, uint256 amount) returns (bool)'
|
|
973
|
+
]);
|
|
974
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
975
|
+
const transferData = erc20Interface.encodeFunctionData('transfer', [
|
|
976
|
+
buyers[i].address,
|
|
977
|
+
transferAmountsWei[i]
|
|
978
|
+
]);
|
|
979
|
+
const transferTx = await seller.signTransaction({
|
|
980
|
+
to: quoteToken,
|
|
981
|
+
data: transferData,
|
|
982
|
+
value: 0n,
|
|
983
|
+
nonce: sellerNonce++,
|
|
984
|
+
gasPrice,
|
|
985
|
+
gasLimit: ERC20_TRANSFER_GAS,
|
|
986
|
+
chainId: context.chainId,
|
|
987
|
+
type: txType
|
|
988
|
+
});
|
|
989
|
+
transferTxs.push(transferTx);
|
|
990
|
+
}
|
|
991
|
+
// ERC20 模式:额外转账 Gas 费用给买家(用于支付 FLAT_FEE 和买入 Gas)
|
|
992
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
993
|
+
const buyerGasCost = gasPrice * finalGasLimit + FLAT_FEE;
|
|
994
|
+
const gasTx = await seller.signTransaction({
|
|
995
|
+
to: buyers[i].address,
|
|
996
|
+
value: buyerGasCost,
|
|
997
|
+
nonce: sellerNonce++,
|
|
998
|
+
gasPrice,
|
|
999
|
+
gasLimit: 21000n,
|
|
1000
|
+
chainId: context.chainId,
|
|
1001
|
+
type: txType
|
|
1002
|
+
});
|
|
1003
|
+
transferTxs.push(gasTx);
|
|
1004
|
+
}
|
|
903
1005
|
}
|
|
904
1006
|
console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
|
|
905
|
-
// ==================== 4.
|
|
906
|
-
// 并行获取所有买方的 nonce
|
|
1007
|
+
// ==================== 4. 买入交易 ====================
|
|
907
1008
|
const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
908
|
-
// 并行签名所有买入交易
|
|
909
1009
|
const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
|
|
910
1010
|
const buyAmount = transferAmountsWei[i];
|
|
911
1011
|
const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
|
|
912
|
-
|
|
1012
|
+
// BNB 模式:value = buyAmount + FLAT_FEE
|
|
1013
|
+
// ERC20 模式:value = FLAT_FEE(买入金额是 ERC20,通过授权支付)
|
|
1014
|
+
const buyValue = useNativeToken ? buyAmount + FLAT_FEE : FLAT_FEE;
|
|
913
1015
|
let buyUnsigned;
|
|
914
1016
|
if (routeParams.routeType === 'v2') {
|
|
915
1017
|
const { v2Path } = routeParams;
|
|
@@ -951,7 +1053,6 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
951
1053
|
}
|
|
952
1054
|
nonceManager.clearTemp();
|
|
953
1055
|
// ==================== 组装交易数组 ====================
|
|
954
|
-
// 顺序:贿赂 → 卖出 → 转账1, 转账2, ... → 买入1, 买入2, ... → 利润
|
|
955
1056
|
const signedTransactions = [];
|
|
956
1057
|
if (bribeTx)
|
|
957
1058
|
signedTransactions.push(bribeTx);
|
|
@@ -966,15 +1067,21 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
966
1067
|
console.log(` - 转账: ${transferTxs.length}`);
|
|
967
1068
|
console.log(` - 买入: ${signedBuys.length}`);
|
|
968
1069
|
console.log(` - 利润: ${profitTx ? 1 : 0}`);
|
|
1070
|
+
const outputUnit = useNativeToken ? 'BNB' : 'ERC20';
|
|
969
1071
|
return {
|
|
970
1072
|
signedTransactions,
|
|
971
1073
|
metadata: {
|
|
972
1074
|
sellerAddress: seller.address,
|
|
973
1075
|
buyerAddresses: buyers.map(b => b.address),
|
|
974
1076
|
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1077
|
+
estimatedOutput: useNativeToken
|
|
1078
|
+
? ethers.formatEther(estimatedOutput)
|
|
1079
|
+
: ethers.formatUnits(estimatedOutput, quoteTokenDecimals),
|
|
1080
|
+
transferAmounts: transferAmountsWei.map(amt => useNativeToken
|
|
1081
|
+
? ethers.formatEther(amt)
|
|
1082
|
+
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
1083
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
1084
|
+
useNativeToken
|
|
978
1085
|
}
|
|
979
1086
|
};
|
|
980
1087
|
}
|