four-flap-meme-sdk 2.0.0 → 2.2.0
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/__tests__/subpath-exports.test.js +64 -0
- package/dist/chains/bsc/iro.d.ts +5 -0
- package/dist/chains/bsc/iro.js +4 -0
- package/dist/chains/eni/flat-aliases.d.ts +10 -0
- package/dist/chains/eni/flat-aliases.js +8 -0
- package/dist/chains/eni/index.d.ts +1 -0
- package/dist/chains/eni/index.js +1 -0
- package/dist/chains/index.d.ts +13 -0
- package/dist/chains/index.js +13 -0
- package/dist/chains/xlayer/eip7702/flat-aliases.d.ts +13 -0
- package/dist/chains/xlayer/eip7702/flat-aliases.js +10 -0
- package/dist/chains/xlayer/eip7702/index.d.ts +1 -0
- package/dist/chains/xlayer/eip7702/index.js +1 -0
- package/dist/chains/xlayer/index.d.ts +3 -2
- package/dist/chains/xlayer/index.js +4 -7
- package/dist/flap/index.d.ts +10 -0
- package/dist/flap/index.js +8 -0
- package/dist/merkle/index.d.ts +12 -0
- package/dist/merkle/index.js +11 -0
- package/dist/shared/constants/index.d.ts +2 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/vanity/index.d.ts +5 -0
- package/dist/vanity/index.js +5 -0
- package/package.json +93 -2
- package/dist/chains/bsc/four/disperse.d.ts +0 -12
- package/dist/chains/bsc/four/disperse.js +0 -470
- package/dist/chains/bsc/four/pairwise.d.ts +0 -7
- package/dist/chains/bsc/four/pairwise.js +0 -308
- package/dist/chains/bsc/four/submit/blockrazor.d.ts +0 -18
- package/dist/chains/bsc/four/submit/blockrazor.js +0 -86
- package/dist/chains/bsc/four/submit/direct.d.ts +0 -66
- package/dist/chains/bsc/four/submit/direct.js +0 -452
- package/dist/chains/bsc/four/submit/helpers.d.ts +0 -18
- package/dist/chains/bsc/four/submit/helpers.js +0 -57
- package/dist/chains/bsc/four/submit/index.d.ts +0 -12
- package/dist/chains/bsc/four/submit/index.js +0 -11
- package/dist/chains/bsc/four/submit/merkle.d.ts +0 -18
- package/dist/chains/bsc/four/submit/merkle.js +0 -74
- package/dist/chains/bsc/four/submit/types.d.ts +0 -143
- package/dist/chains/bsc/four/swap/index.d.ts +0 -32
- package/dist/chains/bsc/four/swap/index.js +0 -829
- package/dist/chains/bsc/four/swap/types.d.ts +0 -70
- package/dist/chains/bsc/four/swap/types.js +0 -1
- package/dist/chains/bsc/four/sweep.d.ts +0 -13
- package/dist/chains/bsc/four/sweep.js +0 -788
- package/dist/chains/bsc/four/utils/index.d.ts +0 -20
- package/dist/chains/bsc/four/utils/index.js +0 -1558
- package/dist/chains/bsc/four/utils/types.d.ts +0 -1
- package/dist/chains/bsc/four/utils/types.js +0 -1
- package/dist/chains/bsc/pancake/bundle-buy-first/index.d.ts +0 -8
- package/dist/chains/bsc/pancake/bundle-buy-first/index.js +0 -907
- package/dist/chains/bsc/pancake/bundle-buy-first/types.d.ts +0 -73
- package/dist/chains/bsc/pancake/bundle-buy-first/types.js +0 -1
- package/dist/chains/bsc/pancake/bundle-swap/helpers.d.ts +0 -102
- package/dist/chains/bsc/pancake/bundle-swap/helpers.js +0 -572
- package/dist/chains/bsc/pancake/bundle-swap/index.d.ts +0 -50
- package/dist/chains/bsc/pancake/bundle-swap/index.js +0 -1066
- package/dist/chains/bsc/pancake/bundle-swap/types.d.ts +0 -202
- package/dist/chains/bsc/pancake/bundle-swap/types.js +0 -3
- package/dist/chains/xlayer/eip7702/bundle-swap/index.d.ts +0 -72
- package/dist/chains/xlayer/eip7702/bundle-swap/index.js +0 -921
- package/dist/chains/xlayer/eip7702/bundle-swap/types.d.ts +0 -65
- package/dist/chains/xlayer/eip7702/bundle-swap/types.js +0 -1
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.d.ts +0 -128
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.js +0 -857
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.d.ts +0 -85
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.js +0 -1
- package/dist/chains/xlayer/eip7702/volume/index.d.ts +0 -96
- package/dist/chains/xlayer/eip7702/volume/index.js +0 -793
- package/dist/chains/xlayer/eip7702/volume/types.d.ts +0 -124
- package/dist/chains/xlayer/eip7702/volume/types.js +0 -1
- package/dist/chains/xlayer/eoa/types-core.d.ts +0 -363
- package/dist/chains/xlayer/eoa/types-core.js +0 -53
- package/dist/chains/xlayer/eoa/types-create.d.ts +0 -413
- package/dist/chains/xlayer/eoa/types-create.js +0 -9
- package/dist/chains/xlayer/eoa/types-volume.d.ts +0 -209
- package/dist/chains/xlayer/eoa/types-volume.js +0 -13
- package/dist/dex/direct-router/index.d.ts +0 -70
- package/dist/dex/direct-router/index.js +0 -1410
- package/dist/dex/direct-router/types.d.ts +0 -81
- package/dist/dex/direct-router/types.js +0 -1
- package/dist/shared/abis/TaxToken.json +0 -969
- package/dist/shared/abis/TokenManager.json +0 -836
- package/dist/shared/abis/TokenManager2.json +0 -136
- package/dist/shared/abis/TokenManagerHelper3.json +0 -993
- package/dist/shared/abis 2/TaxToken.json +0 -105
- package/dist/shared/abis 2/TokenManager.json +0 -836
- package/dist/shared/abis 2/TokenManager2.json +0 -60
- package/dist/shared/abis 2/TokenManagerHelper3.json +0 -993
- package/dist/shared/abis 2/common.d.ts +0 -85
- package/dist/shared/abis 2/common.js +0 -254
- package/dist/shared/abis 2/index.d.ts +0 -8
- package/dist/shared/abis 2/index.js +0 -8
- package/dist/shared/clients 2/blockrazor.d.ts +0 -314
- package/dist/shared/clients 2/blockrazor.js +0 -596
- package/dist/shared/clients 2/club48.d.ts +0 -154
- package/dist/shared/clients 2/club48.js +0 -331
- package/dist/shared/clients 2/emitservice.d.ts +0 -47
- package/dist/shared/clients 2/emitservice.js +0 -44
- package/dist/shared/clients 2/four.d.ts +0 -132
- package/dist/shared/clients 2/four.js +0 -281
- package/dist/shared/clients 2/merkle.d.ts +0 -210
- package/dist/shared/clients 2/merkle.js +0 -400
- package/dist/shared/flap/__tests__/curve.test.d.ts +0 -1
- package/dist/shared/flap/__tests__/curve.test.js +0 -85
- package/dist/shared/flap/portal/index.d.ts +0 -12
- package/dist/shared/flap/portal/index.js +0 -11
- package/dist/shared/flap/portal/portal.d.ts +0 -47
- package/dist/shared/flap/portal/portal.js +0 -218
- package/dist/shared/flap/portal/types.d.ts +0 -227
- package/dist/shared/flap/portal/types.js +0 -80
- package/dist/shared/flap/portal/writer.d.ts +0 -121
- package/dist/shared/flap/portal/writer.js +0 -265
- package/dist/shared/flap/portal-bundle-merkle/core/index.d.ts +0 -18
- package/dist/shared/flap/portal-bundle-merkle/core/index.js +0 -938
- package/dist/shared/flap/portal-bundle-merkle/core/types.d.ts +0 -1
- package/dist/shared/flap/portal-bundle-merkle/core/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/swap/index.d.ts +0 -42
- package/dist/shared/flap/portal-bundle-merkle/swap/index.js +0 -1448
- package/dist/shared/flap/portal-bundle-merkle/swap/types.d.ts +0 -84
- package/dist/shared/flap/portal-bundle-merkle/swap/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/utils/index.d.ts +0 -17
- package/dist/shared/flap/portal-bundle-merkle/utils/index.js +0 -1024
- package/dist/shared/flap/portal-bundle-merkle/utils/types.d.ts +0 -16
- package/dist/shared/flap/portal-bundle-merkle/utils/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/utils-helpers.d.ts +0 -100
- package/dist/shared/flap/portal-bundle-merkle/utils-helpers.js +0 -133
- package/dist/shared/flap 2/__tests__/curve.test.d.ts +0 -1
- package/dist/shared/flap 2/__tests__/curve.test.js +0 -85
- package/dist/shared/flap 2/abi.d.ts +0 -4
- package/dist/shared/flap 2/abi.js +0 -4
- package/dist/shared/flap 2/constants.d.ts +0 -128
- package/dist/shared/flap 2/constants.js +0 -143
- package/dist/shared/flap 2/curve.d.ts +0 -33
- package/dist/shared/flap 2/curve.js +0 -84
- package/dist/shared/flap 2/errors.d.ts +0 -37
- package/dist/shared/flap 2/errors.js +0 -114
- package/dist/shared/flap 2/index.d.ts +0 -22
- package/dist/shared/flap 2/index.js +0 -33
- package/dist/shared/flap 2/ipfs.d.ts +0 -21
- package/dist/shared/flap 2/ipfs.js +0 -38
- package/dist/shared/flap 2/meta.d.ts +0 -30
- package/dist/shared/flap 2/meta.js +0 -195
- package/dist/shared/flap 2/permit.d.ts +0 -16
- package/dist/shared/flap 2/permit.js +0 -67
- package/dist/shared/flap 2/pinata.d.ts +0 -40
- package/dist/shared/flap 2/pinata.js +0 -106
- package/dist/shared/flap 2/portal-bundle-merkle/config.d.ts +0 -79
- package/dist/shared/flap 2/portal-bundle-merkle/config.js +0 -133
- package/dist/shared/flap 2/portal-bundle-merkle/core.d.ts +0 -18
- package/dist/shared/flap 2/portal-bundle-merkle/core.js +0 -938
- package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.d.ts +0 -125
- package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.js +0 -665
- package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.d.ts +0 -88
- package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.js +0 -446
- package/dist/shared/flap 2/portal-bundle-merkle/index.d.ts +0 -14
- package/dist/shared/flap 2/portal-bundle-merkle/index.js +0 -26
- package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.d.ts +0 -28
- package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.js +0 -701
- package/dist/shared/flap 2/portal-bundle-merkle/private.d.ts +0 -17
- package/dist/shared/flap 2/portal-bundle-merkle/private.js +0 -549
- package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.d.ts +0 -65
- package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.js +0 -831
- package/dist/shared/flap 2/portal-bundle-merkle/swap.d.ts +0 -201
- package/dist/shared/flap 2/portal-bundle-merkle/swap.js +0 -1359
- package/dist/shared/flap 2/portal-bundle-merkle/types.d.ts +0 -358
- package/dist/shared/flap 2/portal-bundle-merkle/types.js +0 -1
- package/dist/shared/flap 2/portal-bundle-merkle/utils.d.ts +0 -89
- package/dist/shared/flap 2/portal-bundle-merkle/utils.js +0 -963
- package/dist/shared/flap 2/portal-bundle.d.ts +0 -119
- package/dist/shared/flap 2/portal-bundle.js +0 -584
- package/dist/shared/flap 2/portal.d.ts +0 -392
- package/dist/shared/flap 2/portal.js +0 -559
- package/dist/shared/flap 2/vanity.d.ts +0 -48
- package/dist/shared/flap 2/vanity.js +0 -110
- package/dist/shared/flap 2/vault.d.ts +0 -240
- package/dist/shared/flap 2/vault.js +0 -366
- package/dist/shared/four 2/index.d.ts +0 -7
- package/dist/shared/four 2/index.js +0 -22
- package/dist/shared/four 2/tax-token.d.ts +0 -176
- package/dist/shared/four 2/tax-token.js +0 -302
- package/dist/shared/index 2.js +0 -10
- package/dist/shared/index.d 2.ts +0 -10
- package/dist/types/distribute.d.ts +0 -72
- package/dist/types/distribute.js +0 -1
- package/dist/types 2/errors.d.ts +0 -27
- package/dist/types 2/errors.js +0 -34
- package/dist/utils/__tests__/errors.test.d.ts +0 -1
- package/dist/utils/__tests__/errors.test.js +0 -76
- package/dist/utils/airdrop-sweep-types.d.ts +0 -1
- package/dist/utils/airdrop-sweep-types.js +0 -1
- package/dist/utils/erc20/index.d.ts +0 -242
- package/dist/utils/erc20/index.js +0 -645
- package/dist/utils/erc20/types.d.ts +0 -77
- package/dist/utils/erc20/types.js +0 -1
- package/dist/utils/erc20-types.d.ts +0 -1
- package/dist/utils/erc20-types.js +0 -1
- package/dist/utils/holders-maker/helpers.d.ts +0 -43
- package/dist/utils/holders-maker/helpers.js +0 -371
- package/dist/utils/holders-maker/index.d.ts +0 -26
- package/dist/utils/holders-maker/index.js +0 -218
- package/dist/utils/holders-maker/types.d.ts +0 -72
- package/dist/utils/holders-maker/types.js +0 -4
- package/dist/utils/holders-maker-types.d.ts +0 -1
- package/dist/utils/holders-maker-types.js +0 -1
- package/dist/utils/lp-inspect/index.d.ts +0 -44
- package/dist/utils/lp-inspect/index.js +0 -937
- package/dist/utils/lp-inspect/types.d.ts +0 -100
- package/dist/utils/lp-inspect/types.js +0 -1
- package/dist/utils/lp-inspect-types.d.ts +0 -1
- package/dist/utils/lp-inspect-types.js +0 -1
- package/dist/utils/private-sale-types.d.ts +0 -1
- package/dist/utils/private-sale-types.js +0 -1
- package/dist/utils/quote-helpers/index.d.ts +0 -107
- package/dist/utils/quote-helpers/index.js +0 -346
- package/dist/utils/quote-helpers/types.d.ts +0 -16
- package/dist/utils/quote-helpers/types.js +0 -1
- package/dist/utils/quote-helpers-types.d.ts +0 -1
- package/dist/utils/quote-helpers-types.js +0 -1
- /package/dist/{chains/bsc/four/submit/types.js → __tests__/subpath-exports.test.d.ts} +0 -0
|
@@ -1,793 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EIP-7702 XLayer 刷量功能
|
|
3
|
-
*
|
|
4
|
-
* 支持两种模式:
|
|
5
|
-
* 1. 无币模式 (Wash): 单钱包买入+卖出,不需要预先持有代币
|
|
6
|
-
* 2. 捆绑模式 (Bundle): 多钱包批量买入 + 批量卖出
|
|
7
|
-
*
|
|
8
|
-
* 支持池子类型:
|
|
9
|
-
* - FLAP: 内盘 (Flap Portal)
|
|
10
|
-
* - V2: PotatoSwap V2 外盘
|
|
11
|
-
* - V3: PotatoSwap V3 外盘
|
|
12
|
-
*
|
|
13
|
-
* 只生成签名,由调用方决定如何提交
|
|
14
|
-
*/
|
|
15
|
-
import { ethers, Contract } from 'ethers';
|
|
16
|
-
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync, calculateProfitAmountByTxCount, getProfitRecipient, } from '../utils.js';
|
|
17
|
-
import { UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, FLAP_PORTAL_ADDRESS, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, WOKB_ADDRESS, V3_FEE_TIERS, ERC20_ABI, V2_ROUTER_ABI, } from '../constants.js';
|
|
18
|
-
import { bundleBuy } from '../bundle-buy.js';
|
|
19
|
-
import { bundleSell } from '../bundle-sell.js';
|
|
20
|
-
import { bundleSwap } from '../bundle-swap.js';
|
|
21
|
-
// ========================================
|
|
22
|
-
// 类型定义
|
|
23
|
-
// ========================================
|
|
24
|
-
/**
|
|
25
|
-
* 截断小数位数,避免 parseEther/parseUnits 报错
|
|
26
|
-
* @param value 数值字符串
|
|
27
|
-
* @param decimals 保留的小数位数(默认 18)
|
|
28
|
-
*/
|
|
29
|
-
function truncateDecimals(value, decimals = 18) {
|
|
30
|
-
if (!value || value === '0')
|
|
31
|
-
return value;
|
|
32
|
-
const parts = String(value).split('.');
|
|
33
|
-
if (parts.length === 1)
|
|
34
|
-
return value;
|
|
35
|
-
const intPart = parts[0] || '0';
|
|
36
|
-
const decPart = (parts[1] || '').slice(0, decimals);
|
|
37
|
-
return decPart ? `${intPart}.${decPart}` : intPart;
|
|
38
|
-
}
|
|
39
|
-
// ========================================
|
|
40
|
-
// 无币刷量 (Wash Mode)
|
|
41
|
-
// ========================================
|
|
42
|
-
/**
|
|
43
|
-
* 无币刷量 - 每个钱包执行买入+卖出
|
|
44
|
-
*
|
|
45
|
-
* 核心逻辑:[利润刮取] + [钱包1买入] + [钱包1卖出] + [钱包2买入] + [钱包2卖出] + ...
|
|
46
|
-
*
|
|
47
|
-
* 特点:
|
|
48
|
-
* - 钱包不需要预先持有代币
|
|
49
|
-
* - 每个钱包在同一笔交易中完成买入和卖出
|
|
50
|
-
* - 利润从主钱包统一支付
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* const result = await washVolume({
|
|
54
|
-
* privateKeys: ['0x...', '0x...'],
|
|
55
|
-
* tokenAddress: '0x...',
|
|
56
|
-
* buyAmountPerWallet: '0.01',
|
|
57
|
-
* sellPercent: 100,
|
|
58
|
-
* poolType: 'V3',
|
|
59
|
-
* });
|
|
60
|
-
*/
|
|
61
|
-
export async function washVolume(params) {
|
|
62
|
-
const { mainPrivateKey, privateKeys, tokenAddress, tokenDecimals = 18, buyAmountPerWallet, sellPercent = 100, poolType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
63
|
-
config, } = params;
|
|
64
|
-
if (!mainPrivateKey) {
|
|
65
|
-
throw new Error('必须提供 mainPrivateKey(Payer 钱包私钥)');
|
|
66
|
-
}
|
|
67
|
-
if (privateKeys.length === 0) {
|
|
68
|
-
throw new Error('至少需要一个钱包');
|
|
69
|
-
}
|
|
70
|
-
// ✅ 验证私钥格式
|
|
71
|
-
const isValidPrivateKey = (pk) => {
|
|
72
|
-
const trimmed = (pk || '').trim();
|
|
73
|
-
return trimmed.length >= 64 && (trimmed.startsWith('0x') || /^[0-9a-fA-F]{64}$/.test(trimmed));
|
|
74
|
-
};
|
|
75
|
-
if (!isValidPrivateKey(mainPrivateKey)) {
|
|
76
|
-
throw new Error('mainPrivateKey 格式无效');
|
|
77
|
-
}
|
|
78
|
-
const validPrivateKeys = privateKeys.filter(pk => isValidPrivateKey(pk));
|
|
79
|
-
if (validPrivateKeys.length === 0) {
|
|
80
|
-
throw new Error('没有有效的私钥(私钥必须是 0x 开头的 66 位或 64 位十六进制字符串)');
|
|
81
|
-
}
|
|
82
|
-
if (validPrivateKeys.length !== privateKeys.length) {
|
|
83
|
-
console.warn(`[washVolume] 跳过 ${privateKeys.length - validPrivateKeys.length} 个无效私钥`);
|
|
84
|
-
}
|
|
85
|
-
// 使用验证后的私钥
|
|
86
|
-
const actualPrivateKeys = validPrivateKeys;
|
|
87
|
-
const provider = getCachedProvider(config?.rpcUrl);
|
|
88
|
-
// ✅ 空字符串也使用默认值
|
|
89
|
-
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
90
|
-
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
91
|
-
const portalInterface = new ethers.Interface(FLAP_PORTAL_ABI);
|
|
92
|
-
// 获取实际路由地址
|
|
93
|
-
const actualRouter = routerAddress ?? getDefaultRouter(poolType);
|
|
94
|
-
// ✅ 创建 Payer 钱包(支付 Gas 费)
|
|
95
|
-
const mainWallet = createWallet(mainPrivateKey, provider);
|
|
96
|
-
// 创建交易钱包(使用验证后的私钥)
|
|
97
|
-
const wallets = actualPrivateKeys.map(pk => createWallet(pk, provider));
|
|
98
|
-
// 计算金额(无币刷量:每个钱包 1买 + 1卖 = 2笔交易)
|
|
99
|
-
const buyAmountWei = ethers.parseEther(truncateDecimals(buyAmountPerWallet));
|
|
100
|
-
const totalBuyAmount = buyAmountWei * BigInt(wallets.length);
|
|
101
|
-
const totalTxCount = wallets.length * 2; // 每个钱包 1买 + 1卖
|
|
102
|
-
const profitAmount = calculateProfitAmountByTxCount(totalBuyAmount, totalTxCount, userType);
|
|
103
|
-
const profitRecipient = getProfitRecipient(config);
|
|
104
|
-
// ========================================
|
|
105
|
-
// 合并所有钱包(去重)
|
|
106
|
-
// mainWallet 需要参与授权签名(因为它执行利润刮取的 delegate 调用)
|
|
107
|
-
// ========================================
|
|
108
|
-
const allWalletsMap = new Map();
|
|
109
|
-
allWalletsMap.set(mainWallet.address.toLowerCase(), mainWallet);
|
|
110
|
-
wallets.forEach(w => allWalletsMap.set(w.address.toLowerCase(), w));
|
|
111
|
-
const allWallets = Array.from(allWalletsMap.values());
|
|
112
|
-
// 找出 mainWallet 在 allWallets 中的索引
|
|
113
|
-
const mainWalletIndex = allWallets.findIndex(w => w.address.toLowerCase() === mainWallet.address.toLowerCase());
|
|
114
|
-
// ========================================
|
|
115
|
-
// 并行获取数据
|
|
116
|
-
// ========================================
|
|
117
|
-
const allAddresses = allWallets.map(w => w.address);
|
|
118
|
-
// ✅ 对所有模式,使用【总金额报价 + 按比例分配】
|
|
119
|
-
// 因为所有钱包在同一笔交易中执行,会累积影响价格
|
|
120
|
-
// 这确保了「买多少就卖多少」,避免清空钱包预存代币
|
|
121
|
-
let estimatedTokenAmounts = [];
|
|
122
|
-
const totalBuyWei = buyAmountWei * BigInt(wallets.length);
|
|
123
|
-
if (poolType === 'FLAP') {
|
|
124
|
-
console.log(`[washVolume] FLAP 总金额报价: ${wallets.length} 个钱包, 每个 ${buyAmountWei} wei, 总计 ${totalBuyWei} wei`);
|
|
125
|
-
try {
|
|
126
|
-
const portalContract = new Contract(FLAP_PORTAL_ADDRESS, FLAP_PORTAL_ABI, provider);
|
|
127
|
-
// ✅ 使用 previewBuy(与 AA 模式一致),降级到 quoteExactInput
|
|
128
|
-
// 某些代币的 previewBuy 会 revert (0xac5f6092),降级到 quoteExactInput
|
|
129
|
-
let totalTokenAmount = 0n;
|
|
130
|
-
try {
|
|
131
|
-
totalTokenAmount = await portalContract.previewBuy(tokenAddress, totalBuyWei);
|
|
132
|
-
console.log(`[washVolume] FLAP previewBuy 总预估: ${totalBuyWei} wei OKB -> ${totalTokenAmount} wei 代币`);
|
|
133
|
-
}
|
|
134
|
-
catch (previewErr) {
|
|
135
|
-
console.warn(`[washVolume] previewBuy 失败,尝试 quoteExactInput:`, previewErr);
|
|
136
|
-
try {
|
|
137
|
-
// ✅ 降级到 quoteExactInput(与 AA 模式相同的降级策略)
|
|
138
|
-
totalTokenAmount = await portalContract.quoteExactInput.staticCall({
|
|
139
|
-
inputToken: ethers.ZeroAddress, // OKB
|
|
140
|
-
outputToken: tokenAddress, // Token
|
|
141
|
-
inputAmount: totalBuyWei,
|
|
142
|
-
});
|
|
143
|
-
console.log(`[washVolume] FLAP quoteExactInput 总预估: ${totalBuyWei} wei OKB -> ${totalTokenAmount} wei 代币`);
|
|
144
|
-
}
|
|
145
|
-
catch (quoteErr) {
|
|
146
|
-
console.warn(`[washVolume] quoteExactInput 也失败:`, quoteErr);
|
|
147
|
-
totalTokenAmount = 0n;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
// 按每个钱包的买入金额比例分配(由于每个钱包金额相同,均分即可)
|
|
151
|
-
if (totalBuyWei > 0n && totalTokenAmount > 0n) {
|
|
152
|
-
const sharePerWallet = totalTokenAmount / BigInt(wallets.length);
|
|
153
|
-
let allocated = 0n;
|
|
154
|
-
estimatedTokenAmounts = wallets.map((_, i) => {
|
|
155
|
-
if (i === wallets.length - 1) {
|
|
156
|
-
// 最后一个钱包分配剩余的全部(避免精度损失)
|
|
157
|
-
return totalTokenAmount - allocated;
|
|
158
|
-
}
|
|
159
|
-
allocated += sharePerWallet;
|
|
160
|
-
return sharePerWallet;
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
console.log(`[washVolume] FLAP 代币分配:`, estimatedTokenAmounts.map(a => a.toString()));
|
|
164
|
-
}
|
|
165
|
-
catch (e) {
|
|
166
|
-
console.warn(`[washVolume] FLAP 报价失败:`, e);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else if (poolType === 'V2' || poolType === 'V3') {
|
|
170
|
-
// ✅ V2/V3 外盘模式:通过报价获取预估代币数量,确保买卖对等
|
|
171
|
-
console.log(`[washVolume] ${poolType} 总金额报价: ${wallets.length} 个钱包, 每个 ${buyAmountWei} wei, 总计 ${totalBuyWei} wei`);
|
|
172
|
-
try {
|
|
173
|
-
const { quoteV2, quoteV3 } = await import('../../../../utils/quote-helpers.js');
|
|
174
|
-
let totalTokenAmount = 0n;
|
|
175
|
-
if (poolType === 'V3') {
|
|
176
|
-
const result = await quoteV3(provider, WOKB_ADDRESS, tokenAddress, totalBuyWei, 'XLAYER', fee);
|
|
177
|
-
totalTokenAmount = result.amountOut;
|
|
178
|
-
console.log(`[washVolume] V3 报价成功: ${totalBuyWei} wei OKB -> ${totalTokenAmount} wei 代币 (fee: ${fee})`);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
const result = await quoteV2(provider, WOKB_ADDRESS, tokenAddress, totalBuyWei, 'XLAYER');
|
|
182
|
-
totalTokenAmount = result.amountOut;
|
|
183
|
-
console.log(`[washVolume] V2 报价成功: ${totalBuyWei} wei OKB -> ${totalTokenAmount} wei 代币`);
|
|
184
|
-
}
|
|
185
|
-
// 均分到每个钱包
|
|
186
|
-
if (totalBuyWei > 0n && totalTokenAmount > 0n) {
|
|
187
|
-
const sharePerWallet = totalTokenAmount / BigInt(wallets.length);
|
|
188
|
-
let allocated = 0n;
|
|
189
|
-
estimatedTokenAmounts = wallets.map((_, i) => {
|
|
190
|
-
if (i === wallets.length - 1) {
|
|
191
|
-
return totalTokenAmount - allocated;
|
|
192
|
-
}
|
|
193
|
-
allocated += sharePerWallet;
|
|
194
|
-
return sharePerWallet;
|
|
195
|
-
});
|
|
196
|
-
console.log(`[washVolume] ${poolType} 代币分配:`, estimatedTokenAmounts.map(a => a.toString()));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
catch (e) {
|
|
200
|
-
console.warn(`[washVolume] ${poolType} 报价失败,将使用 sellPercent 模式:`, e);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
const [nonces, feeData] = await Promise.all([
|
|
204
|
-
batchGetNonces(allAddresses, provider),
|
|
205
|
-
provider.getFeeData(),
|
|
206
|
-
]);
|
|
207
|
-
// ========================================
|
|
208
|
-
// 同步签署授权
|
|
209
|
-
// ========================================
|
|
210
|
-
const authorizations = signAuthorizationsWithNonces(allWallets, nonces, delegateAddress, mainWalletIndex // mainWallet 是交易发起者
|
|
211
|
-
);
|
|
212
|
-
// ========================================
|
|
213
|
-
// 构建调用
|
|
214
|
-
// ========================================
|
|
215
|
-
const calls = [];
|
|
216
|
-
// ✅ 修复:先执行买卖,最后执行利润转账(与 bundleBuy 调用顺序一致)
|
|
217
|
-
// 每个钱包执行买入 + 卖出
|
|
218
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
219
|
-
const wallet = wallets[i];
|
|
220
|
-
// 买入调用
|
|
221
|
-
const buyCall = buildBuyCall(wallet, buyAmountWei, tokenAddress, poolType, actualRouter, fee, delegateInterface, portalInterface);
|
|
222
|
-
calls.push(buyCall);
|
|
223
|
-
// 卖出调用(使用预估的代币数量)
|
|
224
|
-
// ✅ 统一所有模式:当有预估数量时使用精确卖出,确保买卖对等
|
|
225
|
-
const estimatedAmount = estimatedTokenAmounts[i] || 0n;
|
|
226
|
-
const sellCall = estimatedAmount > 0n
|
|
227
|
-
? buildSellCallWithAmount(wallet, estimatedAmount, tokenAddress, poolType, actualRouter, fee, delegateInterface, portalInterface)
|
|
228
|
-
: buildSellCall(wallet, 0n, tokenAddress, sellPercent, tokenDecimals, poolType, actualRouter, fee, delegateInterface, portalInterface);
|
|
229
|
-
calls.push(sellCall);
|
|
230
|
-
}
|
|
231
|
-
// ✅ 修复:利润刮取从第一个参与刷量的钱包扣取(卖出后有OKB)
|
|
232
|
-
// 使用 transferAmount 从钱包余额转账,不需要通过 msg.value
|
|
233
|
-
if (profitAmount > 0n && wallets.length > 0) {
|
|
234
|
-
calls.push({
|
|
235
|
-
target: wallets[0].address, // 从第一个刷量钱包扣利润
|
|
236
|
-
allowFailure: false,
|
|
237
|
-
value: 0n,
|
|
238
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
// ========================================
|
|
242
|
-
// 构建交易
|
|
243
|
-
// ========================================
|
|
244
|
-
// ✅ 修复:利润使用 transferAmount 从钱包余额转账,不需要通过 msg.value
|
|
245
|
-
const totalValue = 0n;
|
|
246
|
-
// ✅ 使用 mainWallet 的 nonce(mainWallet 是交易发起者和 Gas 支付者)
|
|
247
|
-
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, nonces[mainWalletIndex], feeData, config);
|
|
248
|
-
return {
|
|
249
|
-
signedTransaction,
|
|
250
|
-
metadata: {
|
|
251
|
-
walletCount: wallets.length,
|
|
252
|
-
buyAmountPerWallet,
|
|
253
|
-
totalBuyAmount: ethers.formatEther(totalBuyAmount),
|
|
254
|
-
sellPercent,
|
|
255
|
-
poolType,
|
|
256
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
257
|
-
},
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
// ========================================
|
|
261
|
-
// 捆绑刷量 (Bundle Mode)
|
|
262
|
-
// ========================================
|
|
263
|
-
/**
|
|
264
|
-
* 捆绑刷量 - 多钱包批量买入 + 批量卖出
|
|
265
|
-
*
|
|
266
|
-
* 核心逻辑:[利润刮取] + [买入Ops...] + [卖出Ops...]
|
|
267
|
-
*
|
|
268
|
-
* 特点:
|
|
269
|
-
* - 买入和卖出可以使用不同的钱包
|
|
270
|
-
* - 所有操作在一笔交易中原子执行
|
|
271
|
-
* - 卖出钱包需要预先持有代币
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* const result = await bundleVolume({
|
|
275
|
-
* buyerPrivateKeys: ['0x...', '0x...'],
|
|
276
|
-
* buyAmounts: ['0.01', '0.02'],
|
|
277
|
-
* sellerPrivateKeys: ['0x...'],
|
|
278
|
-
* sellPercent: 100,
|
|
279
|
-
* tokenAddress: '0x...',
|
|
280
|
-
* poolType: 'FLAP',
|
|
281
|
-
* });
|
|
282
|
-
*/
|
|
283
|
-
export async function bundleVolume(params) {
|
|
284
|
-
const { mainPrivateKey, buyerPrivateKeys, buyAmounts, sellerPrivateKeys, sellAmounts, sellPercent = 100, tokenAddress, tokenDecimals = 18, poolType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
285
|
-
config, } = params;
|
|
286
|
-
// ✅ 验证私钥格式的辅助函数
|
|
287
|
-
const isValidPrivateKey = (pk) => {
|
|
288
|
-
const trimmed = (pk || '').trim();
|
|
289
|
-
return trimmed.length >= 64 && (trimmed.startsWith('0x') || /^[0-9a-fA-F]{64}$/.test(trimmed));
|
|
290
|
-
};
|
|
291
|
-
if (!mainPrivateKey || !isValidPrivateKey(mainPrivateKey)) {
|
|
292
|
-
throw new Error('必须提供有效的 mainPrivateKey(Payer 钱包私钥)');
|
|
293
|
-
}
|
|
294
|
-
// ✅ 验证买家私钥
|
|
295
|
-
const validBuyerIndices = [];
|
|
296
|
-
const validBuyerPrivateKeys = [];
|
|
297
|
-
const validBuyAmounts = [];
|
|
298
|
-
buyerPrivateKeys.forEach((pk, i) => {
|
|
299
|
-
if (isValidPrivateKey(pk)) {
|
|
300
|
-
validBuyerIndices.push(i);
|
|
301
|
-
validBuyerPrivateKeys.push(pk);
|
|
302
|
-
validBuyAmounts.push(buyAmounts[i] || '0');
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
if (validBuyerPrivateKeys.length === 0) {
|
|
306
|
-
throw new Error('没有有效的买家私钥');
|
|
307
|
-
}
|
|
308
|
-
// ✅ 验证卖家私钥
|
|
309
|
-
const validSellerPrivateKeys = sellerPrivateKeys.filter(pk => isValidPrivateKey(pk));
|
|
310
|
-
if (validSellerPrivateKeys.length === 0) {
|
|
311
|
-
throw new Error('没有有效的卖家私钥');
|
|
312
|
-
}
|
|
313
|
-
if (validBuyerPrivateKeys.length !== buyerPrivateKeys.length || validSellerPrivateKeys.length !== sellerPrivateKeys.length) {
|
|
314
|
-
console.warn(`[bundleVolume] 跳过无效私钥: 买家 ${buyerPrivateKeys.length - validBuyerPrivateKeys.length} 个, 卖家 ${sellerPrivateKeys.length - validSellerPrivateKeys.length} 个`);
|
|
315
|
-
}
|
|
316
|
-
const provider = getCachedProvider(config?.rpcUrl);
|
|
317
|
-
// ✅ 空字符串也使用默认值
|
|
318
|
-
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
319
|
-
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
320
|
-
const portalInterface = new ethers.Interface(FLAP_PORTAL_ABI);
|
|
321
|
-
const actualRouter = routerAddress ?? getDefaultRouter(poolType);
|
|
322
|
-
// ✅ 创建 Payer 钱包(支付 Gas 费)
|
|
323
|
-
const mainWallet = createWallet(mainPrivateKey, provider);
|
|
324
|
-
// 创建钱包(使用验证后的私钥)
|
|
325
|
-
const buyerWallets = validBuyerPrivateKeys.map(pk => createWallet(pk, provider));
|
|
326
|
-
const sellerWallets = validSellerPrivateKeys.map(pk => createWallet(pk, provider));
|
|
327
|
-
// 合并所有钱包(去重,包括 mainWallet)
|
|
328
|
-
const allWalletsMap = new Map();
|
|
329
|
-
allWalletsMap.set(mainWallet.address.toLowerCase(), mainWallet);
|
|
330
|
-
buyerWallets.forEach(w => allWalletsMap.set(w.address.toLowerCase(), w));
|
|
331
|
-
sellerWallets.forEach(w => allWalletsMap.set(w.address.toLowerCase(), w));
|
|
332
|
-
const allWallets = Array.from(allWalletsMap.values());
|
|
333
|
-
// 找出 mainWallet 在 allWallets 中的索引
|
|
334
|
-
const mainWalletIndex = allWallets.findIndex(w => w.address.toLowerCase() === mainWallet.address.toLowerCase());
|
|
335
|
-
// 计算买入金额(使用验证后的金额数组)
|
|
336
|
-
const buyAmountsWei = validBuyAmounts.map(amt => ethers.parseEther(truncateDecimals(amt)));
|
|
337
|
-
const totalBuyAmount = buyAmountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
338
|
-
// ✅ 计算利润(捆绑刷量:买家数 + 卖家数 = 总交易数)
|
|
339
|
-
const totalTxCount = buyerWallets.length + sellerWallets.length;
|
|
340
|
-
const profitAmount = calculateProfitAmountByTxCount(totalBuyAmount, totalTxCount, userType);
|
|
341
|
-
const profitRecipient = getProfitRecipient(config);
|
|
342
|
-
// ========================================
|
|
343
|
-
// 并行获取数据
|
|
344
|
-
// ========================================
|
|
345
|
-
const allAddresses = allWallets.map(w => w.address);
|
|
346
|
-
// 获取卖出钱包的代币余额(如果需要)
|
|
347
|
-
const needBalanceQuery = !sellAmounts || sellAmounts.length !== sellerWallets.length;
|
|
348
|
-
const parallelPromises = [
|
|
349
|
-
batchGetNonces(allAddresses, provider),
|
|
350
|
-
provider.getFeeData(),
|
|
351
|
-
];
|
|
352
|
-
if (needBalanceQuery && sellerWallets.length > 0) {
|
|
353
|
-
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
|
|
354
|
-
parallelPromises.push(Promise.all(sellerWallets.map(w => tokenContract.balanceOf(w.address))));
|
|
355
|
-
}
|
|
356
|
-
const results = await Promise.all(parallelPromises);
|
|
357
|
-
const nonces = results[0];
|
|
358
|
-
const feeData = results[1];
|
|
359
|
-
// 计算卖出金额
|
|
360
|
-
let sellAmountsWei;
|
|
361
|
-
if (sellAmounts && sellAmounts.length === sellerWallets.length) {
|
|
362
|
-
// ✅ 如果有指定卖出金额,直接使用
|
|
363
|
-
sellAmountsWei = sellAmounts.map(amt => ethers.parseUnits(truncateDecimals(amt, tokenDecimals), tokenDecimals));
|
|
364
|
-
}
|
|
365
|
-
else if (needBalanceQuery && results[2]) {
|
|
366
|
-
// ✅ 没有指定卖出金额,需要根据买入金额预估应卖出的代币数量
|
|
367
|
-
const balances = results[2].map(b => typeof b === 'bigint' ? b : BigInt(b.toString()));
|
|
368
|
-
const totalSellerBalance = balances.reduce((sum, b) => sum + b, 0n);
|
|
369
|
-
// ✅ 根据 poolType 预估买入能获得的代币数量
|
|
370
|
-
let estimatedTokenAmount = 0n;
|
|
371
|
-
try {
|
|
372
|
-
if (poolType === 'FLAP') {
|
|
373
|
-
// FLAP 内盘:使用 previewBuy,降级到 quoteExactInput
|
|
374
|
-
const portalContract = new ethers.Contract(FLAP_PORTAL_ADDRESS, FLAP_PORTAL_ABI, provider);
|
|
375
|
-
try {
|
|
376
|
-
estimatedTokenAmount = await portalContract.previewBuy(tokenAddress, totalBuyAmount);
|
|
377
|
-
console.log(`[bundleVolume] FLAP previewBuy: ${totalBuyAmount} wei OKB -> ${estimatedTokenAmount} wei 代币`);
|
|
378
|
-
}
|
|
379
|
-
catch (previewErr) {
|
|
380
|
-
console.warn(`[bundleVolume] previewBuy 失败,尝试 quoteBuy:`, previewErr);
|
|
381
|
-
try {
|
|
382
|
-
estimatedTokenAmount = await portalContract.quoteBuy(tokenAddress, totalBuyAmount);
|
|
383
|
-
console.log(`[bundleVolume] FLAP quoteBuy: ${totalBuyAmount} wei OKB -> ${estimatedTokenAmount} wei 代币`);
|
|
384
|
-
}
|
|
385
|
-
catch (quoteBuyErr) {
|
|
386
|
-
console.warn(`[bundleVolume] quoteBuy 失败,尝试 quoteExactInput:`, quoteBuyErr);
|
|
387
|
-
try {
|
|
388
|
-
estimatedTokenAmount = await portalContract.quoteExactInput.staticCall({
|
|
389
|
-
inputToken: ethers.ZeroAddress,
|
|
390
|
-
outputToken: tokenAddress,
|
|
391
|
-
inputAmount: totalBuyAmount,
|
|
392
|
-
});
|
|
393
|
-
console.log(`[bundleVolume] FLAP quoteExactInput: ${totalBuyAmount} wei OKB -> ${estimatedTokenAmount} wei 代币`);
|
|
394
|
-
}
|
|
395
|
-
catch (quoteErr) {
|
|
396
|
-
console.warn(`[bundleVolume] quoteExactInput 也失败,尝试 V2 路由作为终极兜底:`, quoteErr);
|
|
397
|
-
try {
|
|
398
|
-
// 终极兜底:当内盘接口全部失效时,尝试使用 V2 代码进行报价
|
|
399
|
-
const v2Router = new ethers.Contract(POTATOSWAP_V2_ROUTER, V2_ROUTER_ABI, provider);
|
|
400
|
-
const path = [WOKB_ADDRESS, tokenAddress];
|
|
401
|
-
const amounts = await v2Router.getAmountsOut(totalBuyAmount, path);
|
|
402
|
-
estimatedTokenAmount = amounts[amounts.length - 1];
|
|
403
|
-
console.log(`[bundleVolume] FLAP 终极回退 V2 quote: ${totalBuyAmount} wei OKB -> ${estimatedTokenAmount} wei 代币`);
|
|
404
|
-
}
|
|
405
|
-
catch (v2Err) {
|
|
406
|
-
console.warn(`[bundleVolume] 终极回退 V2 quote 也失败:`, v2Err);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
else if (poolType === 'V2' || poolType === 'V3') {
|
|
413
|
-
// V2/V3 外盘:使用 Router 报价
|
|
414
|
-
const v2Router = new ethers.Contract(POTATOSWAP_V2_ROUTER, V2_ROUTER_ABI, provider);
|
|
415
|
-
const path = [WOKB_ADDRESS, tokenAddress];
|
|
416
|
-
try {
|
|
417
|
-
if (poolType === 'V2') {
|
|
418
|
-
const amounts = await v2Router.getAmountsOut(totalBuyAmount, path);
|
|
419
|
-
estimatedTokenAmount = amounts[amounts.length - 1];
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
// V3: 使用 V3 Quoter
|
|
423
|
-
const v3Quoter = new ethers.Contract('0x27d52F6D9Ed2eBb3E6a4c0Af6b1f5dc85A4d5b2C', [
|
|
424
|
-
'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut)'
|
|
425
|
-
], provider);
|
|
426
|
-
estimatedTokenAmount = await v3Quoter.quoteExactInputSingle.staticCall({
|
|
427
|
-
tokenIn: WOKB_ADDRESS,
|
|
428
|
-
tokenOut: tokenAddress,
|
|
429
|
-
amountIn: totalBuyAmount,
|
|
430
|
-
fee: fee,
|
|
431
|
-
sqrtPriceLimitX96: 0n,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
console.log(`[bundleVolume] ${poolType} quote: ${totalBuyAmount} wei OKB -> ${estimatedTokenAmount} wei 代币`);
|
|
435
|
-
}
|
|
436
|
-
catch (quoteErr) {
|
|
437
|
-
console.warn(`[bundleVolume] ${poolType} 报价失败:`, quoteErr);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
catch (e) {
|
|
442
|
-
console.warn(`[bundleVolume] 报价预估失败:`, e);
|
|
443
|
-
}
|
|
444
|
-
// ✅ 修正卖出数量计算逻辑:卖出数额仅基于本次交易买入的代币预估值,忽略原有余额
|
|
445
|
-
const percentBigInt = BigInt(Math.min(100, Math.max(0, sellPercent)));
|
|
446
|
-
// 1. 初始化每个钱包的预期卖出数量为 0n
|
|
447
|
-
const walletSellAmounts = sellerWallets.map(() => 0n);
|
|
448
|
-
// 2. 将本次预估买入的代币分配给卖家
|
|
449
|
-
if (estimatedTokenAmount > 0n && totalBuyAmount > 0n && sellerWallets.length > 0) {
|
|
450
|
-
// ✅ 修正逻辑:将本次买入预估产生的总代币量随机分配给所有指定的卖家
|
|
451
|
-
const totalToSell = (estimatedTokenAmount * percentBigInt) / 100n;
|
|
452
|
-
if (sellerWallets.length === 1) {
|
|
453
|
-
walletSellAmounts[0] = totalToSell;
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
// ✅ 多卖家场景:随机权重分配 (50% - 150%),差异更明显
|
|
457
|
-
const weights = sellerWallets.map(() => 50 + Math.random() * 100);
|
|
458
|
-
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
|
|
459
|
-
let allocated = 0n;
|
|
460
|
-
sellerWallets.forEach((_, i) => {
|
|
461
|
-
if (i === sellerWallets.length - 1) {
|
|
462
|
-
// 最后一个钱包分配剩余的全部(避免精度损失)
|
|
463
|
-
walletSellAmounts[i] = totalToSell - allocated;
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
const share = (totalToSell * BigInt(Math.round(weights[i] * 1000))) / BigInt(Math.round(totalWeight * 1000));
|
|
467
|
-
walletSellAmounts[i] = share;
|
|
468
|
-
allocated += share;
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
console.log(`[bundleVolume] 每个卖家随机分配的卖出量:`, walletSellAmounts.map(a => a.toString()));
|
|
473
|
-
}
|
|
474
|
-
// ✅ 修复:不再重复应用 sellPercent,因为 totalToSell 已经按 sellPercent 计算过了
|
|
475
|
-
sellAmountsWei = walletSellAmounts;
|
|
476
|
-
console.log(`[bundleVolume] 最终卖出数量 (sellPercent=${sellPercent}%):`, sellAmountsWei.map(a => a.toString()));
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
// ✅ 即使没有余额查询结果,也初始化数组
|
|
480
|
-
sellAmountsWei = sellerWallets.map(() => 0n);
|
|
481
|
-
}
|
|
482
|
-
const totalSellAmount = sellAmountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
483
|
-
// ========================================
|
|
484
|
-
// 同步签署授权
|
|
485
|
-
// ========================================
|
|
486
|
-
const authorizations = signAuthorizationsWithNonces(allWallets, nonces, delegateAddress, mainWalletIndex);
|
|
487
|
-
// ========================================
|
|
488
|
-
// 构建调用
|
|
489
|
-
// ========================================
|
|
490
|
-
const calls = [];
|
|
491
|
-
// ✅ 修复:先执行买卖,最后执行利润转账(与 bundleBuy 调用顺序一致)
|
|
492
|
-
// 买入调用
|
|
493
|
-
for (let i = 0; i < buyerWallets.length; i++) {
|
|
494
|
-
const buyCall = buildBuyCall(buyerWallets[i], buyAmountsWei[i], tokenAddress, poolType, actualRouter, fee, delegateInterface, portalInterface);
|
|
495
|
-
calls.push(buyCall);
|
|
496
|
-
}
|
|
497
|
-
// 卖出调用
|
|
498
|
-
for (let i = 0; i < sellerWallets.length; i++) {
|
|
499
|
-
const amount = sellAmountsWei[i];
|
|
500
|
-
// ✅ 修复:即使 amount 为 0,只要 sellPercent 为 100,也应该尝试卖出全部,防止刷量缺失 Sell
|
|
501
|
-
if (amount <= 0n && sellPercent !== 100)
|
|
502
|
-
continue;
|
|
503
|
-
const sellCall = amount > 0n
|
|
504
|
-
? buildSellCallWithAmount(sellerWallets[i], amount, tokenAddress, poolType, actualRouter, fee, delegateInterface, portalInterface)
|
|
505
|
-
: buildSellCall(sellerWallets[i], 0n, // amount = 0n 配合 sellPercent = 100 会执行卖出全部
|
|
506
|
-
tokenAddress, sellPercent, tokenDecimals, poolType, actualRouter, fee, delegateInterface, portalInterface);
|
|
507
|
-
calls.push(sellCall);
|
|
508
|
-
}
|
|
509
|
-
// ✅ 修复:利润刮取从第一个卖方钱包扣取(卖出后有OKB)
|
|
510
|
-
// 使用 transferAmount 从钱包余额转账,不需要通过 msg.value
|
|
511
|
-
if (profitAmount > 0n && sellerWallets.length > 0) {
|
|
512
|
-
calls.push({
|
|
513
|
-
target: sellerWallets[0].address, // 从第一个卖方钱包扣利润
|
|
514
|
-
allowFailure: false,
|
|
515
|
-
value: 0n,
|
|
516
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
// ========================================
|
|
520
|
-
// 构建交易
|
|
521
|
-
// ========================================
|
|
522
|
-
// ✅ 修复:利润使用 transferAmount 从钱包余额转账,不需要通过 msg.value
|
|
523
|
-
const totalValue = 0n;
|
|
524
|
-
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, nonces[mainWalletIndex], feeData, config);
|
|
525
|
-
return {
|
|
526
|
-
signedTransaction,
|
|
527
|
-
metadata: {
|
|
528
|
-
buyerCount: buyerWallets.length,
|
|
529
|
-
sellerCount: sellerWallets.filter((_, i) => sellAmountsWei[i] > 0n).length,
|
|
530
|
-
totalBuyAmount: ethers.formatEther(totalBuyAmount),
|
|
531
|
-
totalSellAmount: ethers.formatUnits(totalSellAmount, tokenDecimals),
|
|
532
|
-
poolType,
|
|
533
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
534
|
-
},
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
// ========================================
|
|
538
|
-
// 单轮刷量(买入+卖出分开签名)
|
|
539
|
-
// ========================================
|
|
540
|
-
/**
|
|
541
|
-
* 生成单轮刷量签名(买入 + 卖出分开)
|
|
542
|
-
*
|
|
543
|
-
* 注意:买入和卖出是两笔独立交易
|
|
544
|
-
*/
|
|
545
|
-
export async function singleRoundVolume(params) {
|
|
546
|
-
const { privateKeys, tokenAddress, tokenDecimals = 18, buyAmountPerWallet, sellPercent = 100, tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, config, } = params;
|
|
547
|
-
const mainPrivateKey = privateKeys[0];
|
|
548
|
-
// 并行生成买入和卖出签名
|
|
549
|
-
const [buyResult, sellResult] = await Promise.all([
|
|
550
|
-
bundleBuy({
|
|
551
|
-
mainPrivateKey,
|
|
552
|
-
privateKeys,
|
|
553
|
-
buyAmounts: privateKeys.map(() => buyAmountPerWallet),
|
|
554
|
-
tokenAddress,
|
|
555
|
-
tradeType,
|
|
556
|
-
routerAddress,
|
|
557
|
-
fee,
|
|
558
|
-
config,
|
|
559
|
-
}),
|
|
560
|
-
bundleSell({
|
|
561
|
-
mainPrivateKey,
|
|
562
|
-
privateKeys,
|
|
563
|
-
sellPercent,
|
|
564
|
-
tokenAddress,
|
|
565
|
-
tokenDecimals,
|
|
566
|
-
tradeType,
|
|
567
|
-
routerAddress,
|
|
568
|
-
fee,
|
|
569
|
-
config,
|
|
570
|
-
}),
|
|
571
|
-
]);
|
|
572
|
-
return {
|
|
573
|
-
buySignedTransaction: buyResult.signedTransaction,
|
|
574
|
-
sellSignedTransaction: sellResult.signedTransaction,
|
|
575
|
-
metadata: {
|
|
576
|
-
buyAmount: buyAmountPerWallet,
|
|
577
|
-
sellPercent,
|
|
578
|
-
walletCount: privateKeys.length,
|
|
579
|
-
},
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
// ========================================
|
|
583
|
-
// 多轮刷量
|
|
584
|
-
// ========================================
|
|
585
|
-
/**
|
|
586
|
-
* 生成多轮刷量签名
|
|
587
|
-
*
|
|
588
|
-
* 注意:由于 nonce 问题,只能预生成第一轮
|
|
589
|
-
* 后续轮次需要在前一轮交易确认后才能生成
|
|
590
|
-
*/
|
|
591
|
-
export async function makeVolume(params) {
|
|
592
|
-
const { privateKeys, tokenAddress, tokenDecimals = 18, buyAmountPerRound, rounds, tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, onProgress, config, } = params;
|
|
593
|
-
const buyAmountPerWallet = (parseFloat(buyAmountPerRound) / privateKeys.length).toString();
|
|
594
|
-
const roundResults = [];
|
|
595
|
-
// 只生成第一轮签名
|
|
596
|
-
const firstRound = await singleRoundVolume({
|
|
597
|
-
privateKeys,
|
|
598
|
-
tokenAddress,
|
|
599
|
-
tokenDecimals,
|
|
600
|
-
buyAmountPerWallet,
|
|
601
|
-
tradeType,
|
|
602
|
-
routerAddress,
|
|
603
|
-
fee,
|
|
604
|
-
config,
|
|
605
|
-
});
|
|
606
|
-
roundResults.push(firstRound);
|
|
607
|
-
onProgress?.({
|
|
608
|
-
currentRound: 1,
|
|
609
|
-
totalRounds: rounds,
|
|
610
|
-
status: 'completed',
|
|
611
|
-
});
|
|
612
|
-
return {
|
|
613
|
-
rounds: roundResults,
|
|
614
|
-
metadata: {
|
|
615
|
-
totalRounds: rounds,
|
|
616
|
-
totalBuyAmount: (parseFloat(buyAmountPerRound) * rounds).toString(),
|
|
617
|
-
walletCount: privateKeys.length,
|
|
618
|
-
},
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
// ========================================
|
|
622
|
-
// 换手刷量
|
|
623
|
-
// ========================================
|
|
624
|
-
/**
|
|
625
|
-
* 生成换手刷量签名
|
|
626
|
-
*/
|
|
627
|
-
export async function makeVolumeWithSwap(params) {
|
|
628
|
-
const { sellerPrivateKey, buyerPrivateKey, tokenAddress, tokenDecimals = 18, sellAmount, hopCount = 2, tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, config, } = params;
|
|
629
|
-
const result = await bundleSwap({
|
|
630
|
-
sellerPrivateKey,
|
|
631
|
-
buyerPrivateKey,
|
|
632
|
-
tokenAddress,
|
|
633
|
-
tokenDecimals,
|
|
634
|
-
sellAmount,
|
|
635
|
-
hopCount,
|
|
636
|
-
tradeType,
|
|
637
|
-
routerAddress,
|
|
638
|
-
fee,
|
|
639
|
-
config,
|
|
640
|
-
});
|
|
641
|
-
return {
|
|
642
|
-
signedTransaction: result.signedTransaction,
|
|
643
|
-
hopWallets: result.hopWallets || [],
|
|
644
|
-
metadata: {
|
|
645
|
-
sellerAddress: result.metadata.sellerAddress,
|
|
646
|
-
buyerAddress: result.metadata.buyerAddresses[0],
|
|
647
|
-
sellAmount: result.metadata.sellAmount,
|
|
648
|
-
},
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
// ========================================
|
|
652
|
-
// 工具函数
|
|
653
|
-
// ========================================
|
|
654
|
-
function getDefaultRouter(poolType) {
|
|
655
|
-
switch (poolType) {
|
|
656
|
-
case 'FLAP':
|
|
657
|
-
return FLAP_PORTAL_ADDRESS;
|
|
658
|
-
case 'V2':
|
|
659
|
-
return POTATOSWAP_V2_ROUTER;
|
|
660
|
-
case 'V3':
|
|
661
|
-
return POTATOSWAP_V3_ROUTER;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
function buildBuyCall(wallet, amount, tokenAddress, poolType, routerAddress, fee, delegateInterface, portalInterface) {
|
|
665
|
-
switch (poolType) {
|
|
666
|
-
case 'FLAP': {
|
|
667
|
-
// ✅ 修复:使用 swapExactInput 代替 buy 函数,明确指定 inputAmount
|
|
668
|
-
// buy(token) 依赖 msg.value,当 value=0n 时会失败
|
|
669
|
-
const portalCallData = portalInterface.encodeFunctionData('swapExactInput', [{
|
|
670
|
-
inputToken: ethers.ZeroAddress, // 0x0 = OKB (原生代币)
|
|
671
|
-
outputToken: tokenAddress,
|
|
672
|
-
inputAmount: amount,
|
|
673
|
-
minOutputAmount: 0n,
|
|
674
|
-
permitData: '0x',
|
|
675
|
-
}]);
|
|
676
|
-
const batchCalls = [{ target: FLAP_PORTAL_ADDRESS, value: amount, data: portalCallData }];
|
|
677
|
-
return {
|
|
678
|
-
target: wallet.address,
|
|
679
|
-
allowFailure: false,
|
|
680
|
-
value: 0n,
|
|
681
|
-
callData: delegateInterface.encodeFunctionData('executeBatch', [batchCalls]),
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
case 'V2': {
|
|
685
|
-
const path = [WOKB_ADDRESS, tokenAddress];
|
|
686
|
-
// ✅ 修复:value 设为 0n,让钱包使用自己余额买入;传入正确的 amount
|
|
687
|
-
return {
|
|
688
|
-
target: wallet.address,
|
|
689
|
-
allowFailure: false,
|
|
690
|
-
value: 0n,
|
|
691
|
-
callData: delegateInterface.encodeFunctionData('executeBuyV2', [routerAddress, path, amount]),
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
case 'V3': {
|
|
695
|
-
// ✅ 修复:value 设为 0n,让钱包使用自己余额买入;传入正确的 amount
|
|
696
|
-
return {
|
|
697
|
-
target: wallet.address,
|
|
698
|
-
allowFailure: false,
|
|
699
|
-
value: 0n,
|
|
700
|
-
callData: delegateInterface.encodeFunctionData('executeBuy', [
|
|
701
|
-
routerAddress, WOKB_ADDRESS, tokenAddress, fee, amount
|
|
702
|
-
]),
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
function buildSellCall(wallet, amount, // ✅ 传入具体数量,用于非 100% 卖出时的占位或精确值
|
|
708
|
-
tokenAddress, sellPercent, tokenDecimals, poolType, routerAddress, fee, delegateInterface, portalInterface) {
|
|
709
|
-
// 使用 executeTransferAll 卖出全部(sellPercent=100)
|
|
710
|
-
// 或者使用 executeSell 指定百分比
|
|
711
|
-
switch (poolType) {
|
|
712
|
-
case 'FLAP': {
|
|
713
|
-
// ✅ 使用 swapExactInput 卖出(与 AA 模式保持一致)
|
|
714
|
-
// 注意:inputAmount=0 会失败,FLAP 需要先获取余额再调用
|
|
715
|
-
// 这里需要传入实际的代币余额,外层调用者应该提前查询
|
|
716
|
-
const portalCallData = portalInterface.encodeFunctionData('swapExactInput', [{
|
|
717
|
-
inputToken: tokenAddress,
|
|
718
|
-
outputToken: ethers.ZeroAddress, // 0x0 = OKB
|
|
719
|
-
inputAmount: 1n, // 占位符,如果调用 buildSellCall 时不指定 amount,通常意味逻辑上有问题
|
|
720
|
-
minOutputAmount: 0n,
|
|
721
|
-
permitData: '0x',
|
|
722
|
-
}]);
|
|
723
|
-
const batchCalls = [{ target: FLAP_PORTAL_ADDRESS, value: 0n, data: portalCallData }];
|
|
724
|
-
return {
|
|
725
|
-
target: wallet.address,
|
|
726
|
-
allowFailure: false,
|
|
727
|
-
value: 0n,
|
|
728
|
-
callData: delegateInterface.encodeFunctionData('executeBatch', [batchCalls]),
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
case 'V2': {
|
|
732
|
-
const path = [tokenAddress, WOKB_ADDRESS];
|
|
733
|
-
// ✅ 修复:传入实际数量,避免卖出全部
|
|
734
|
-
return {
|
|
735
|
-
target: wallet.address,
|
|
736
|
-
allowFailure: false,
|
|
737
|
-
value: 0n,
|
|
738
|
-
callData: delegateInterface.encodeFunctionData('executeSellV2', [routerAddress, path, sellPercent === 100 ? 0 : amount]),
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
case 'V3': {
|
|
742
|
-
// ✅ 修复:传入实际数量,避免卖出全部
|
|
743
|
-
return {
|
|
744
|
-
target: wallet.address,
|
|
745
|
-
allowFailure: false,
|
|
746
|
-
value: 0n,
|
|
747
|
-
callData: delegateInterface.encodeFunctionData('executeSell', [
|
|
748
|
-
routerAddress, tokenAddress, WOKB_ADDRESS, fee, sellPercent === 100 ? 0 : amount
|
|
749
|
-
]),
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
function buildSellCallWithAmount(wallet, amount, tokenAddress, poolType, routerAddress, fee, delegateInterface, portalInterface) {
|
|
755
|
-
switch (poolType) {
|
|
756
|
-
case 'FLAP': {
|
|
757
|
-
// ✅ 使用 swapExactInput 卖出(与 AA 模式保持一致)
|
|
758
|
-
const portalCallData = portalInterface.encodeFunctionData('swapExactInput', [{
|
|
759
|
-
inputToken: tokenAddress,
|
|
760
|
-
outputToken: ethers.ZeroAddress, // 0x0 = OKB
|
|
761
|
-
inputAmount: amount,
|
|
762
|
-
minOutputAmount: 0n,
|
|
763
|
-
permitData: '0x',
|
|
764
|
-
}]);
|
|
765
|
-
const batchCalls = [{ target: FLAP_PORTAL_ADDRESS, value: 0n, data: portalCallData }];
|
|
766
|
-
return {
|
|
767
|
-
target: wallet.address,
|
|
768
|
-
allowFailure: false,
|
|
769
|
-
value: 0n,
|
|
770
|
-
callData: delegateInterface.encodeFunctionData('executeBatch', [batchCalls]),
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
case 'V2': {
|
|
774
|
-
const path = [tokenAddress, WOKB_ADDRESS];
|
|
775
|
-
return {
|
|
776
|
-
target: wallet.address,
|
|
777
|
-
allowFailure: false,
|
|
778
|
-
value: 0n,
|
|
779
|
-
callData: delegateInterface.encodeFunctionData('executeSellV2', [routerAddress, path, amount]),
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
case 'V3': {
|
|
783
|
-
return {
|
|
784
|
-
target: wallet.address,
|
|
785
|
-
allowFailure: false,
|
|
786
|
-
value: 0n,
|
|
787
|
-
callData: delegateInterface.encodeFunctionData('executeSell', [
|
|
788
|
-
routerAddress, tokenAddress, WOKB_ADDRESS, fee, amount
|
|
789
|
-
]),
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|