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,857 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EIP-7702 转账功能
|
|
3
|
-
*
|
|
4
|
-
* 支持三种模式:
|
|
5
|
-
* 1. 分散 (disperse): 一对多分发资金
|
|
6
|
-
* 2. 归集 (sweep): 多对一收集资金
|
|
7
|
-
* 3. 多对多 (pairwise): sender[i] → receiver[i] 一一对应转账
|
|
8
|
-
*
|
|
9
|
-
* 所有模式都支持:
|
|
10
|
-
* - 原生 OKB 转账
|
|
11
|
-
* - ERC20 代币转账
|
|
12
|
-
* - 可选的多跳混淆
|
|
13
|
-
*
|
|
14
|
-
* 只生成签名,由调用方决定如何提交
|
|
15
|
-
*/
|
|
16
|
-
import { ethers, Contract } from 'ethers';
|
|
17
|
-
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync, generateRandomWallets, getProfitRecipient, } from '../utils.js';
|
|
18
|
-
import { UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, ERC20_ABI, XLAYER_CHAIN_ID, } from '../constants.js';
|
|
19
|
-
import { calculateTransferFee } from '../../../../shared/constants/index.js';
|
|
20
|
-
// ========================================
|
|
21
|
-
// 通用类型定义
|
|
22
|
-
// ========================================
|
|
23
|
-
// ========================================
|
|
24
|
-
// 分散 (Disperse) - 一对多
|
|
25
|
-
// ========================================
|
|
26
|
-
/**
|
|
27
|
-
* 分散资金 - 一对多分发
|
|
28
|
-
*
|
|
29
|
-
* 支持:原生 OKB / ERC20 代币 + 可选多跳
|
|
30
|
-
*
|
|
31
|
-
* 流程(无多跳):主钱包 → 多个接收者
|
|
32
|
-
* 流程(有多跳):主钱包 → hop1 → hop2 → ... → 分发给接收者
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* // 原生 OKB 直接分发
|
|
36
|
-
* const result = await disperse({
|
|
37
|
-
* mainPrivateKey: '0x...',
|
|
38
|
-
* recipients: ['0xAAA', '0xBBB', '0xCCC'],
|
|
39
|
-
* amounts: ['0.1', '0.2', '0.3'],
|
|
40
|
-
* });
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* // 原生 OKB 多跳分发
|
|
44
|
-
* const result = await disperse({
|
|
45
|
-
* mainPrivateKey: '0x...',
|
|
46
|
-
* recipients: ['0xAAA', '0xBBB'],
|
|
47
|
-
* amounts: ['0.5', '0.5'],
|
|
48
|
-
* hopCount: 2,
|
|
49
|
-
* });
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* // ERC20 代币分发
|
|
53
|
-
* const result = await disperse({
|
|
54
|
-
* mainPrivateKey: '0x...',
|
|
55
|
-
* recipients: ['0xAAA', '0xBBB'],
|
|
56
|
-
* amounts: ['100', '200'],
|
|
57
|
-
* tokenAddress: '0xTOKEN...',
|
|
58
|
-
* tokenDecimals: 18,
|
|
59
|
-
* hopCount: 2,
|
|
60
|
-
* });
|
|
61
|
-
*/
|
|
62
|
-
export async function disperse(params) {
|
|
63
|
-
const { mainPrivateKey, payerPrivateKey, recipients, amounts, tokenAddress, tokenDecimals = 18, hopCount = 0, userType, config, } = params;
|
|
64
|
-
if (recipients.length !== amounts.length) {
|
|
65
|
-
throw new Error('recipients 和 amounts 长度必须相同');
|
|
66
|
-
}
|
|
67
|
-
const provider = getCachedProvider(config?.rpcUrl);
|
|
68
|
-
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
69
|
-
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
70
|
-
// ✅ mainWallet: 资金来源(Token/OKB 从这里转出)
|
|
71
|
-
const mainWallet = createWallet(mainPrivateKey, provider);
|
|
72
|
-
const isNative = !tokenAddress;
|
|
73
|
-
const profitRecipient = getProfitRecipient(config);
|
|
74
|
-
// ✅ 支持单独的 Payer 钱包(用于支付 gas)
|
|
75
|
-
const hasSeparatePayer = payerPrivateKey && payerPrivateKey !== mainPrivateKey;
|
|
76
|
-
const payerWallet = hasSeparatePayer ? createWallet(payerPrivateKey, provider) : null;
|
|
77
|
-
const txSender = payerWallet ?? mainWallet; // 交易发起者
|
|
78
|
-
console.log(`[disperse] 资金钱包: ${mainWallet.address}`);
|
|
79
|
-
console.log(`[disperse] Gas Payer: ${txSender.address}${hasSeparatePayer ? ' (独立 Payer)' : ''}`);
|
|
80
|
-
// ✅ 为每个接收者生成独立的中间钱包链
|
|
81
|
-
// 每个接收者有自己的 hopCount 个中间钱包
|
|
82
|
-
const allHopWalletInfos = [];
|
|
83
|
-
const allHopWallets = [];
|
|
84
|
-
for (let i = 0; i < recipients.length; i++) {
|
|
85
|
-
if (hopCount > 0) {
|
|
86
|
-
const hopInfos = generateRandomWallets(hopCount);
|
|
87
|
-
allHopWalletInfos.push(hopInfos);
|
|
88
|
-
allHopWallets.push(hopInfos.map(info => createWallet(info.privateKey, provider)));
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
allHopWalletInfos.push([]);
|
|
92
|
-
allHopWallets.push([]);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// 解析金额
|
|
96
|
-
const amountsWei = amounts.map(amt => isNative ? ethers.parseEther(amt) : ethers.parseUnits(amt, tokenDecimals));
|
|
97
|
-
const totalAmount = amountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
98
|
-
// ✅ 按地址数量收取固定费用(原生 OKB)
|
|
99
|
-
const profitAmountOkb = calculateTransferFee(XLAYER_CHAIN_ID, recipients.length);
|
|
100
|
-
console.log(`[disperse] 按地址收费: ${recipients.length} 个地址 × ${ethers.formatEther(calculateTransferFee(XLAYER_CHAIN_ID, 1))} = ${ethers.formatEther(profitAmountOkb)} OKB`);
|
|
101
|
-
// ========================================
|
|
102
|
-
// 并行获取数据
|
|
103
|
-
// ========================================
|
|
104
|
-
const addressesToGetNonces = payerWallet
|
|
105
|
-
? [payerWallet.address, mainWallet.address]
|
|
106
|
-
: [mainWallet.address];
|
|
107
|
-
const [allNoncesResult, feeData] = await Promise.all([
|
|
108
|
-
batchGetNonces(addressesToGetNonces, provider),
|
|
109
|
-
provider.getFeeData(),
|
|
110
|
-
]);
|
|
111
|
-
const payerNonce = payerWallet ? allNoncesResult[0] : allNoncesResult[0];
|
|
112
|
-
const mainNonce = payerWallet ? allNoncesResult[1] : allNoncesResult[0];
|
|
113
|
-
// ========================================
|
|
114
|
-
// 同步签署授权(已有 nonce)
|
|
115
|
-
// ========================================
|
|
116
|
-
// ✅ 如果有 Payer,需要为 Payer 和 mainWallet 都生成授权
|
|
117
|
-
const allWallets = payerWallet ? [payerWallet, mainWallet] : [mainWallet];
|
|
118
|
-
const allNonces = payerWallet ? [payerNonce, mainNonce] : [mainNonce];
|
|
119
|
-
for (const hopWallets of allHopWallets) {
|
|
120
|
-
for (const hopWallet of hopWallets) {
|
|
121
|
-
allWallets.push(hopWallet);
|
|
122
|
-
allNonces.push(0);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // txSender(Payer 或 mainWallet)是交易发起者
|
|
126
|
-
);
|
|
127
|
-
// ========================================
|
|
128
|
-
// 同步构建调用
|
|
129
|
-
// ========================================
|
|
130
|
-
const calls = [];
|
|
131
|
-
for (let i = 0; i < recipients.length; i++) {
|
|
132
|
-
const recipient = recipients[i];
|
|
133
|
-
const amtWei = amountsWei[i];
|
|
134
|
-
const hopWallets = allHopWallets[i];
|
|
135
|
-
if (hopCount === 0 || hopWallets.length === 0) {
|
|
136
|
-
// ========================================
|
|
137
|
-
// 直接分发:主钱包 → 接收者
|
|
138
|
-
// ========================================
|
|
139
|
-
if (isNative) {
|
|
140
|
-
// ✅ Payer 模式:使用 transferAmount(从 mainWallet 余额转出)
|
|
141
|
-
// ✅ 非 Payer 模式:使用 transferTo(从 msg.value 转出)
|
|
142
|
-
if (payerWallet) {
|
|
143
|
-
calls.push({
|
|
144
|
-
target: mainWallet.address,
|
|
145
|
-
allowFailure: false,
|
|
146
|
-
value: 0n,
|
|
147
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [recipient, amtWei]),
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
calls.push({
|
|
152
|
-
target: mainWallet.address,
|
|
153
|
-
allowFailure: false,
|
|
154
|
-
value: amtWei,
|
|
155
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [recipient]),
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
calls.push({
|
|
161
|
-
target: mainWallet.address,
|
|
162
|
-
allowFailure: false,
|
|
163
|
-
value: 0n,
|
|
164
|
-
callData: delegateInterface.encodeFunctionData('executeTransfer', [
|
|
165
|
-
tokenAddress,
|
|
166
|
-
recipient,
|
|
167
|
-
amtWei,
|
|
168
|
-
]),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
// ========================================
|
|
174
|
-
// 多跳分发:主钱包 → hop1 → hop2 → ... → 接收者
|
|
175
|
-
// ========================================
|
|
176
|
-
const firstHop = hopWallets[0];
|
|
177
|
-
const lastHop = hopWallets[hopWallets.length - 1];
|
|
178
|
-
if (isNative) {
|
|
179
|
-
// 主钱包 → 第一个中间钱包
|
|
180
|
-
// ✅ Payer 模式:使用 transferAmount(从 mainWallet 余额转出)
|
|
181
|
-
if (payerWallet) {
|
|
182
|
-
calls.push({
|
|
183
|
-
target: mainWallet.address,
|
|
184
|
-
allowFailure: false,
|
|
185
|
-
value: 0n,
|
|
186
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [firstHop.address, amtWei]),
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
calls.push({
|
|
191
|
-
target: mainWallet.address,
|
|
192
|
-
allowFailure: false,
|
|
193
|
-
value: amtWei,
|
|
194
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [firstHop.address]),
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
// 中间钱包之间转账(使用全部余额)
|
|
198
|
-
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
199
|
-
calls.push({
|
|
200
|
-
target: hopWallets[j].address,
|
|
201
|
-
allowFailure: false,
|
|
202
|
-
value: 0n,
|
|
203
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [hopWallets[j + 1].address]),
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
// 最后一个中间钱包 → 接收者(使用全部余额)
|
|
207
|
-
calls.push({
|
|
208
|
-
target: lastHop.address,
|
|
209
|
-
allowFailure: false,
|
|
210
|
-
value: 0n,
|
|
211
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [recipient]),
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
// ERC20 多跳
|
|
216
|
-
// 主钱包 → 第一个中间钱包
|
|
217
|
-
calls.push({
|
|
218
|
-
target: mainWallet.address,
|
|
219
|
-
allowFailure: false,
|
|
220
|
-
value: 0n,
|
|
221
|
-
callData: delegateInterface.encodeFunctionData('executeTransfer', [
|
|
222
|
-
tokenAddress,
|
|
223
|
-
firstHop.address,
|
|
224
|
-
amtWei,
|
|
225
|
-
]),
|
|
226
|
-
});
|
|
227
|
-
// 中间钱包之间转账
|
|
228
|
-
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
229
|
-
calls.push({
|
|
230
|
-
target: hopWallets[j].address,
|
|
231
|
-
allowFailure: false,
|
|
232
|
-
value: 0n,
|
|
233
|
-
callData: delegateInterface.encodeFunctionData('executeTransferAll', [
|
|
234
|
-
tokenAddress,
|
|
235
|
-
hopWallets[j + 1].address,
|
|
236
|
-
]),
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
// 最后一个中间钱包 → 接收者
|
|
240
|
-
calls.push({
|
|
241
|
-
target: lastHop.address,
|
|
242
|
-
allowFailure: false,
|
|
243
|
-
value: 0n,
|
|
244
|
-
callData: delegateInterface.encodeFunctionData('executeTransferAll', [
|
|
245
|
-
tokenAddress,
|
|
246
|
-
recipient,
|
|
247
|
-
]),
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// ========================================
|
|
253
|
-
// 添加利润转账调用(固定费用,OKB)
|
|
254
|
-
// ========================================
|
|
255
|
-
if (profitAmountOkb > 0n) {
|
|
256
|
-
calls.push({
|
|
257
|
-
target: mainWallet.address,
|
|
258
|
-
allowFailure: false,
|
|
259
|
-
value: 0n,
|
|
260
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
261
|
-
});
|
|
262
|
-
console.log(`[disperse] 已添加利润转账调用: ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
263
|
-
}
|
|
264
|
-
// ========================================
|
|
265
|
-
// 构建交易
|
|
266
|
-
// ========================================
|
|
267
|
-
// ✅ Payer 模式下 value 为 0(资金从 mainWallet 转出,不是 Payer)
|
|
268
|
-
// 无 Payer 时,原生币分发需要传递 value(资金从 txSender 转出)
|
|
269
|
-
const totalValue = (isNative && !payerWallet) ? totalAmount : 0n;
|
|
270
|
-
const signedTransaction = buildEIP7702TransactionSync(txSender, // ✅ 使用 txSender(Payer 或 mainWallet)发起交易
|
|
271
|
-
authorizations, calls, totalValue, payerNonce, // ✅ 使用 txSender 的 nonce
|
|
272
|
-
feeData, config);
|
|
273
|
-
// ✅ 返回所有中间钱包(扁平化)
|
|
274
|
-
const flatHopWallets = allHopWalletInfos.flat();
|
|
275
|
-
return {
|
|
276
|
-
signedTransaction,
|
|
277
|
-
hopWallets: flatHopWallets.length > 0 ? flatHopWallets : undefined,
|
|
278
|
-
metadata: {
|
|
279
|
-
senderAddress: mainWallet.address, // 资金来源
|
|
280
|
-
payerAddress: txSender.address, // Gas 支付者
|
|
281
|
-
recipientCount: recipients.length,
|
|
282
|
-
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
283
|
-
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|
|
284
|
-
isNative,
|
|
285
|
-
tokenAddress: tokenAddress ?? null,
|
|
286
|
-
hopCount,
|
|
287
|
-
userType,
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
// ========================================
|
|
292
|
-
// 归集 (Sweep) - 多对一
|
|
293
|
-
// ========================================
|
|
294
|
-
/**
|
|
295
|
-
* 归集资金 - 多对一收集
|
|
296
|
-
*
|
|
297
|
-
* 支持:原生 OKB / ERC20 代币 + 可选多跳
|
|
298
|
-
*
|
|
299
|
-
* 流程(无多跳):多个源钱包 → 目标地址
|
|
300
|
-
* 流程(有多跳):多个源钱包 → hop1 → hop2 → ... → 目标地址
|
|
301
|
-
*
|
|
302
|
-
* @example
|
|
303
|
-
* // 原生 OKB 直接归集
|
|
304
|
-
* const result = await sweep({
|
|
305
|
-
* targetAddress: '0xTARGET',
|
|
306
|
-
* sourcePrivateKeys: ['0x...', '0x...'],
|
|
307
|
-
* });
|
|
308
|
-
*
|
|
309
|
-
* @example
|
|
310
|
-
* // 原生 OKB 多跳归集
|
|
311
|
-
* const result = await sweep({
|
|
312
|
-
* targetAddress: '0xTARGET',
|
|
313
|
-
* sourcePrivateKeys: ['0x...', '0x...'],
|
|
314
|
-
* hopCount: 2,
|
|
315
|
-
* });
|
|
316
|
-
*
|
|
317
|
-
* @example
|
|
318
|
-
* // ERC20 代币归集
|
|
319
|
-
* const result = await sweep({
|
|
320
|
-
* targetAddress: '0xTARGET',
|
|
321
|
-
* sourcePrivateKeys: ['0x...', '0x...'],
|
|
322
|
-
* tokenAddress: '0xTOKEN...',
|
|
323
|
-
* tokenDecimals: 18,
|
|
324
|
-
* hopCount: 2,
|
|
325
|
-
* });
|
|
326
|
-
*/
|
|
327
|
-
export async function sweep(params) {
|
|
328
|
-
const { targetAddress, sourcePrivateKeys, payerPrivateKey, tokenAddress, tokenDecimals = 18, hopCount = 0, reserveGas = true, percent = 100, userType, config, } = params;
|
|
329
|
-
const provider = getCachedProvider(config?.rpcUrl);
|
|
330
|
-
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
331
|
-
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
332
|
-
const sourceWallets = sourcePrivateKeys.map(pk => createWallet(pk, provider));
|
|
333
|
-
const isNative = !tokenAddress;
|
|
334
|
-
const profitRecipient = getProfitRecipient(config);
|
|
335
|
-
// ✅ 为每个源钱包生成独立的中间钱包链
|
|
336
|
-
const allHopWalletInfos = [];
|
|
337
|
-
const allHopWallets = [];
|
|
338
|
-
for (let i = 0; i < sourceWallets.length; i++) {
|
|
339
|
-
if (hopCount > 0) {
|
|
340
|
-
const hopInfos = generateRandomWallets(hopCount);
|
|
341
|
-
allHopWalletInfos.push(hopInfos);
|
|
342
|
-
allHopWallets.push(hopInfos.map(info => createWallet(info.privateKey, provider)));
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
allHopWalletInfos.push([]);
|
|
346
|
-
allHopWallets.push([]);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// ✅ 支持单独的 Payer 钱包(用于支付 gas)
|
|
350
|
-
const hasSeparatePayer = payerPrivateKey && !sourcePrivateKeys.includes(payerPrivateKey);
|
|
351
|
-
const payerWallet = hasSeparatePayer ? createWallet(payerPrivateKey, provider) : null;
|
|
352
|
-
const mainWallet = payerWallet ?? sourceWallets[0];
|
|
353
|
-
// ========================================
|
|
354
|
-
// 并行获取数据
|
|
355
|
-
// ========================================
|
|
356
|
-
const sourceAddresses = sourceWallets.map(w => w.address);
|
|
357
|
-
const addressesToGetNonces = payerWallet
|
|
358
|
-
? [payerWallet.address, ...sourceAddresses]
|
|
359
|
-
: sourceAddresses;
|
|
360
|
-
const parallelPromises = [
|
|
361
|
-
batchGetNonces(addressesToGetNonces, provider),
|
|
362
|
-
provider.getFeeData(),
|
|
363
|
-
];
|
|
364
|
-
if (isNative) {
|
|
365
|
-
parallelPromises.push(Promise.all(sourceWallets.map(w => provider.getBalance(w.address))));
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
369
|
-
parallelPromises.push(Promise.all(sourceWallets.map(w => tokenContract.balanceOf(w.address))));
|
|
370
|
-
}
|
|
371
|
-
const results = await Promise.all(parallelPromises);
|
|
372
|
-
const allNoncesResult = results[0];
|
|
373
|
-
const payerNonce = payerWallet ? allNoncesResult[0] : allNoncesResult[0];
|
|
374
|
-
const nonces = payerWallet ? allNoncesResult.slice(1) : allNoncesResult;
|
|
375
|
-
const feeData = results[1];
|
|
376
|
-
const balances = results[2].map(b => typeof b === 'bigint' ? b : BigInt(b.toString()));
|
|
377
|
-
// 计算转账金额
|
|
378
|
-
// ✅ 修复:当 percent=100 时,不预留 gas,归集全部余额
|
|
379
|
-
const isFullSweep = percent >= 100;
|
|
380
|
-
const gasReserve = (reserveGas && isNative && !isFullSweep) ? ethers.parseEther('0.001') : 0n;
|
|
381
|
-
const percentBigInt = BigInt(Math.min(100, Math.max(1, percent)));
|
|
382
|
-
const transferAmounts = balances.map(b => {
|
|
383
|
-
if (isFullSweep) {
|
|
384
|
-
// 100% 归集:使用全部余额
|
|
385
|
-
return b;
|
|
386
|
-
}
|
|
387
|
-
const afterReserve = b > gasReserve ? b - gasReserve : 0n;
|
|
388
|
-
return (afterReserve * percentBigInt) / 100n;
|
|
389
|
-
});
|
|
390
|
-
const totalAmount = transferAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
391
|
-
// ✅ 按地址数量收取固定费用(原生 OKB)
|
|
392
|
-
const profitAmountOkb = calculateTransferFee(XLAYER_CHAIN_ID, sourceWallets.length);
|
|
393
|
-
console.log(`[sweep] 按地址收费: ${sourceWallets.length} 个地址 × ${ethers.formatEther(calculateTransferFee(XLAYER_CHAIN_ID, 1))} = ${ethers.formatEther(profitAmountOkb)} OKB`);
|
|
394
|
-
// ✅ 找出归集金额最大的钱包(用于支付 OKB 利润)
|
|
395
|
-
let maxSweepIndex = -1;
|
|
396
|
-
let maxSweepAmount = 0n;
|
|
397
|
-
for (let i = 0; i < transferAmounts.length; i++) {
|
|
398
|
-
if (transferAmounts[i] > maxSweepAmount) {
|
|
399
|
-
maxSweepAmount = transferAmounts[i];
|
|
400
|
-
maxSweepIndex = i;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
// ========================================
|
|
404
|
-
// 构建调用
|
|
405
|
-
// ========================================
|
|
406
|
-
const calls = [];
|
|
407
|
-
for (let i = 0; i < sourceWallets.length; i++) {
|
|
408
|
-
const sourceWallet = sourceWallets[i];
|
|
409
|
-
const amount = transferAmounts[i];
|
|
410
|
-
const hopWallets = allHopWallets[i];
|
|
411
|
-
if (amount <= 0n)
|
|
412
|
-
continue;
|
|
413
|
-
// ✅ 使用 Payer 时所有源钱包都用自己的余额(value: 0n)
|
|
414
|
-
const callValue = (payerWallet || i > 0) ? 0n : amount;
|
|
415
|
-
if (hopCount === 0 || hopWallets.length === 0) {
|
|
416
|
-
// ========================================
|
|
417
|
-
// 直接归集:源钱包 → 目标地址
|
|
418
|
-
// ========================================
|
|
419
|
-
// ✅ 最大归集钱包负责转 OKB 固定费用
|
|
420
|
-
if (profitAmountOkb > 0n && i === maxSweepIndex) {
|
|
421
|
-
calls.push({
|
|
422
|
-
target: sourceWallet.address,
|
|
423
|
-
allowFailure: false,
|
|
424
|
-
value: 0n,
|
|
425
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
426
|
-
});
|
|
427
|
-
console.log(`[sweep] 钱包 ${i} 转利润: ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
428
|
-
}
|
|
429
|
-
if (isNative) {
|
|
430
|
-
// ✅ 原生 OKB 归集
|
|
431
|
-
calls.push({
|
|
432
|
-
target: sourceWallet.address,
|
|
433
|
-
allowFailure: false,
|
|
434
|
-
value: callValue,
|
|
435
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [targetAddress, amount]),
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
// ✅ ERC20 归集
|
|
440
|
-
calls.push({
|
|
441
|
-
target: sourceWallet.address,
|
|
442
|
-
allowFailure: false,
|
|
443
|
-
value: 0n,
|
|
444
|
-
callData: delegateInterface.encodeFunctionData('executeTransfer', [
|
|
445
|
-
tokenAddress,
|
|
446
|
-
targetAddress,
|
|
447
|
-
amount,
|
|
448
|
-
]),
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
// ========================================
|
|
454
|
-
// 多跳归集:源钱包 → hop1 → hop2 → ... → 目标地址
|
|
455
|
-
// ========================================
|
|
456
|
-
const firstHop = hopWallets[0];
|
|
457
|
-
const lastHop = hopWallets[hopWallets.length - 1];
|
|
458
|
-
// ✅ 最大归集钱包负责转 OKB 固定费用(在归集前)
|
|
459
|
-
if (profitAmountOkb > 0n && i === maxSweepIndex) {
|
|
460
|
-
calls.push({
|
|
461
|
-
target: sourceWallet.address,
|
|
462
|
-
allowFailure: false,
|
|
463
|
-
value: 0n,
|
|
464
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
465
|
-
});
|
|
466
|
-
console.log(`[sweep] 钱包 ${i} 转利润(多跳): ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
467
|
-
}
|
|
468
|
-
if (isNative) {
|
|
469
|
-
// 源钱包 → 第一个中间钱包
|
|
470
|
-
// ✅ 修复:使用 transferAmount 替代 transferTo,支持百分比归集
|
|
471
|
-
calls.push({
|
|
472
|
-
target: sourceWallet.address,
|
|
473
|
-
allowFailure: false,
|
|
474
|
-
value: callValue,
|
|
475
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [firstHop.address, amount]),
|
|
476
|
-
});
|
|
477
|
-
// 中间钱包之间转账
|
|
478
|
-
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
479
|
-
calls.push({
|
|
480
|
-
target: hopWallets[j].address,
|
|
481
|
-
allowFailure: false,
|
|
482
|
-
value: 0n,
|
|
483
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [hopWallets[j + 1].address]),
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
// 最后一个中间钱包 → 目标地址
|
|
487
|
-
calls.push({
|
|
488
|
-
target: lastHop.address,
|
|
489
|
-
allowFailure: false,
|
|
490
|
-
value: 0n,
|
|
491
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [targetAddress]),
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
// ERC20 多跳
|
|
496
|
-
// 源钱包 → 第一个中间钱包(ERC20 代币)
|
|
497
|
-
calls.push({
|
|
498
|
-
target: sourceWallet.address,
|
|
499
|
-
allowFailure: false,
|
|
500
|
-
value: 0n,
|
|
501
|
-
callData: delegateInterface.encodeFunctionData('executeTransfer', [
|
|
502
|
-
tokenAddress,
|
|
503
|
-
firstHop.address,
|
|
504
|
-
amount,
|
|
505
|
-
]),
|
|
506
|
-
});
|
|
507
|
-
// 中间钱包之间转账
|
|
508
|
-
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
509
|
-
calls.push({
|
|
510
|
-
target: hopWallets[j].address,
|
|
511
|
-
allowFailure: false,
|
|
512
|
-
value: 0n,
|
|
513
|
-
callData: delegateInterface.encodeFunctionData('executeTransferAll', [
|
|
514
|
-
tokenAddress,
|
|
515
|
-
hopWallets[j + 1].address,
|
|
516
|
-
]),
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
// 最后一个中间钱包 → 目标地址
|
|
520
|
-
calls.push({
|
|
521
|
-
target: lastHop.address,
|
|
522
|
-
allowFailure: false,
|
|
523
|
-
value: 0n,
|
|
524
|
-
callData: delegateInterface.encodeFunctionData('executeTransferAll', [
|
|
525
|
-
tokenAddress,
|
|
526
|
-
targetAddress,
|
|
527
|
-
]),
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// ========================================
|
|
533
|
-
// 同步签署授权(已有 nonce)
|
|
534
|
-
// ========================================
|
|
535
|
-
// ✅ 收集所有需要授权的钱包
|
|
536
|
-
const allWallets = payerWallet ? [payerWallet] : [];
|
|
537
|
-
const allNonces = payerWallet ? [payerNonce] : [];
|
|
538
|
-
// 添加源钱包
|
|
539
|
-
for (let i = 0; i < sourceWallets.length; i++) {
|
|
540
|
-
allWallets.push(sourceWallets[i]);
|
|
541
|
-
allNonces.push(nonces[i]);
|
|
542
|
-
}
|
|
543
|
-
// 添加所有中间钱包
|
|
544
|
-
for (const hopWallets of allHopWallets) {
|
|
545
|
-
for (const hopWallet of hopWallets) {
|
|
546
|
-
allWallets.push(hopWallet);
|
|
547
|
-
allNonces.push(0);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // mainWallet 是交易发起者
|
|
551
|
-
);
|
|
552
|
-
// ========================================
|
|
553
|
-
// 同步构建交易
|
|
554
|
-
// ========================================
|
|
555
|
-
// ✅ Payer 模式下 value 为 0(资金来自源钱包)
|
|
556
|
-
const totalValue = (isNative && !payerWallet) ? transferAmounts[0] : 0n;
|
|
557
|
-
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, payerWallet ? payerNonce : nonces[0], feeData, config);
|
|
558
|
-
// ✅ 返回所有中间钱包(扁平化)
|
|
559
|
-
const flatHopWallets = allHopWalletInfos.flat();
|
|
560
|
-
return {
|
|
561
|
-
signedTransaction,
|
|
562
|
-
hopWallets: flatHopWallets.length > 0 ? flatHopWallets : undefined,
|
|
563
|
-
metadata: {
|
|
564
|
-
targetAddress,
|
|
565
|
-
sourceCount: sourceWallets.length,
|
|
566
|
-
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
567
|
-
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|
|
568
|
-
isNative,
|
|
569
|
-
tokenAddress: tokenAddress ?? null,
|
|
570
|
-
hopCount,
|
|
571
|
-
percent,
|
|
572
|
-
userType,
|
|
573
|
-
},
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
// ========================================
|
|
577
|
-
// 多对多 (Pairwise) - sender[i] → receiver[i]
|
|
578
|
-
// ========================================
|
|
579
|
-
/**
|
|
580
|
-
* 多对多转账 - sender[i] → receiver[i] 一一对应
|
|
581
|
-
*
|
|
582
|
-
* 支持:原生 OKB / ERC20 代币 + 可选多跳
|
|
583
|
-
*
|
|
584
|
-
* 每对都有独立的多跳链:sender → hop1 → hop2 → ... → receiver
|
|
585
|
-
*
|
|
586
|
-
* @example
|
|
587
|
-
* // 原生 OKB 多对多(直接转账)
|
|
588
|
-
* const result = await pairwiseTransfer({
|
|
589
|
-
* senderPrivateKeys: ['0xA...', '0xB...', '0xC...'],
|
|
590
|
-
* receiverAddresses: ['0x111', '0x222', '0x333'],
|
|
591
|
-
* amount: '0.1',
|
|
592
|
-
* });
|
|
593
|
-
*
|
|
594
|
-
* @example
|
|
595
|
-
* // 原生 OKB 多对多(带多跳)
|
|
596
|
-
* const result = await pairwiseTransfer({
|
|
597
|
-
* senderPrivateKeys: ['0xA...', '0xB...'],
|
|
598
|
-
* receiverAddresses: ['0x111', '0x222'],
|
|
599
|
-
* amounts: ['0.1', '0.2'],
|
|
600
|
-
* hopCount: 2, // A → hop1 → hop2 → 111, B → hop3 → hop4 → 222
|
|
601
|
-
* });
|
|
602
|
-
*
|
|
603
|
-
* @example
|
|
604
|
-
* // ERC20 多对多(带多跳)
|
|
605
|
-
* const result = await pairwiseTransfer({
|
|
606
|
-
* senderPrivateKeys: ['0xA...', '0xB...'],
|
|
607
|
-
* receiverAddresses: ['0x111', '0x222'],
|
|
608
|
-
* amount: '100',
|
|
609
|
-
* tokenAddress: '0xTOKEN...',
|
|
610
|
-
* tokenDecimals: 18,
|
|
611
|
-
* hopCount: 2,
|
|
612
|
-
* });
|
|
613
|
-
*/
|
|
614
|
-
export async function pairwiseTransfer(params) {
|
|
615
|
-
const { senderPrivateKeys, receiverAddresses, payerPrivateKey, amount, amounts, tokenAddress, tokenDecimals = 18, hopCount = 0, userType, config, } = params;
|
|
616
|
-
// 验证参数
|
|
617
|
-
if (senderPrivateKeys.length !== receiverAddresses.length) {
|
|
618
|
-
throw new Error(`senderPrivateKeys 长度 (${senderPrivateKeys.length}) 必须与 receiverAddresses 长度 (${receiverAddresses.length}) 相同`);
|
|
619
|
-
}
|
|
620
|
-
const pairCount = senderPrivateKeys.length;
|
|
621
|
-
if (pairCount === 0) {
|
|
622
|
-
throw new Error('至少需要一对发送者/接收者');
|
|
623
|
-
}
|
|
624
|
-
// 解析金额
|
|
625
|
-
const normalizedAmounts = (() => {
|
|
626
|
-
if (amounts && amounts.length > 0) {
|
|
627
|
-
if (amounts.length !== pairCount) {
|
|
628
|
-
throw new Error(`amounts 长度 (${amounts.length}) 必须与对数 (${pairCount}) 相同`);
|
|
629
|
-
}
|
|
630
|
-
return amounts;
|
|
631
|
-
}
|
|
632
|
-
if (amount !== undefined && amount.trim().length > 0) {
|
|
633
|
-
return new Array(pairCount).fill(amount);
|
|
634
|
-
}
|
|
635
|
-
throw new Error('必须提供 amount 或 amounts');
|
|
636
|
-
})();
|
|
637
|
-
const provider = getCachedProvider(config?.rpcUrl);
|
|
638
|
-
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
639
|
-
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
640
|
-
const isNative = !tokenAddress;
|
|
641
|
-
const profitRecipient = getProfitRecipient(config);
|
|
642
|
-
// 创建钱包
|
|
643
|
-
const senderWallets = senderPrivateKeys.map(pk => createWallet(pk, provider));
|
|
644
|
-
// ✅ 支持单独的 Payer 钱包(用于支付 gas)
|
|
645
|
-
const hasSeparatePayer = payerPrivateKey && !senderPrivateKeys.includes(payerPrivateKey);
|
|
646
|
-
const payerWallet = hasSeparatePayer ? createWallet(payerPrivateKey, provider) : null;
|
|
647
|
-
const mainWallet = payerWallet ?? senderWallets[0];
|
|
648
|
-
// 为每对生成独立的中间钱包链
|
|
649
|
-
const allHopWalletInfos = [];
|
|
650
|
-
const allHopWallets = [];
|
|
651
|
-
for (let i = 0; i < pairCount; i++) {
|
|
652
|
-
if (hopCount > 0) {
|
|
653
|
-
const hopInfos = generateRandomWallets(hopCount);
|
|
654
|
-
allHopWalletInfos.push(hopInfos);
|
|
655
|
-
allHopWallets.push(hopInfos.map(info => createWallet(info.privateKey, provider)));
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
allHopWalletInfos.push([]);
|
|
659
|
-
allHopWallets.push([]);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
// 解析金额为 Wei
|
|
663
|
-
const amountsWei = normalizedAmounts.map(amt => isNative ? ethers.parseEther(amt) : ethers.parseUnits(amt, tokenDecimals));
|
|
664
|
-
const totalAmount = amountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
665
|
-
// ✅ 按地址数量收取固定费用(原生 OKB)
|
|
666
|
-
const profitAmountOkb = calculateTransferFee(XLAYER_CHAIN_ID, pairCount);
|
|
667
|
-
console.log(`[pairwiseTransfer] 按地址收费: ${pairCount} 个地址 × ${ethers.formatEther(calculateTransferFee(XLAYER_CHAIN_ID, 1))} = ${ethers.formatEther(profitAmountOkb)} OKB`);
|
|
668
|
-
// ✅ 使用第一个发送者支付 OKB 利润
|
|
669
|
-
const maxTransferIndex = 0;
|
|
670
|
-
// ========================================
|
|
671
|
-
// 并行获取数据
|
|
672
|
-
// ========================================
|
|
673
|
-
const senderAddresses = senderWallets.map(w => w.address);
|
|
674
|
-
const addressesToGetNonces = payerWallet
|
|
675
|
-
? [payerWallet.address, ...senderAddresses]
|
|
676
|
-
: senderAddresses;
|
|
677
|
-
const [allNoncesResult, feeData] = await Promise.all([
|
|
678
|
-
batchGetNonces(addressesToGetNonces, provider),
|
|
679
|
-
provider.getFeeData(),
|
|
680
|
-
]);
|
|
681
|
-
// 如果有单独的 Payer,第一个是 Payer 的 nonce,其余是发送者的
|
|
682
|
-
const payerNonce = payerWallet ? allNoncesResult[0] : allNoncesResult[0];
|
|
683
|
-
const nonces = payerWallet ? allNoncesResult.slice(1) : allNoncesResult;
|
|
684
|
-
// ========================================
|
|
685
|
-
// 构建调用
|
|
686
|
-
// ========================================
|
|
687
|
-
const calls = [];
|
|
688
|
-
for (let i = 0; i < pairCount; i++) {
|
|
689
|
-
const senderWallet = senderWallets[i];
|
|
690
|
-
const receiver = receiverAddresses[i];
|
|
691
|
-
const amtWei = amountsWei[i];
|
|
692
|
-
const hopWallets = allHopWallets[i];
|
|
693
|
-
// ✅ 如果有单独的 Payer,发送者使用自己的余额(value: 0n, 由 transferTo 使用 address(this).balance)
|
|
694
|
-
// ✅ 如果没有 Payer 且是第一个发送者(发起交易),需要传递 value
|
|
695
|
-
const shouldPassValue = !payerWallet && i === 0;
|
|
696
|
-
if (hopCount === 0 || hopWallets.length === 0) {
|
|
697
|
-
// ========================================
|
|
698
|
-
// 直接转账:sender → receiver
|
|
699
|
-
// ========================================
|
|
700
|
-
// ✅ 第一个发送者负责转 OKB 固定费用
|
|
701
|
-
if (profitAmountOkb > 0n && i === maxTransferIndex) {
|
|
702
|
-
calls.push({
|
|
703
|
-
target: senderWallet.address,
|
|
704
|
-
allowFailure: false,
|
|
705
|
-
value: 0n,
|
|
706
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
707
|
-
});
|
|
708
|
-
console.log(`[pairwiseTransfer] 发送者 ${i} 转利润: ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
709
|
-
}
|
|
710
|
-
if (isNative) {
|
|
711
|
-
// ✅ 使用 transferAmount 转移指定金额(而不是 transferTo 全额)
|
|
712
|
-
calls.push({
|
|
713
|
-
target: senderWallet.address,
|
|
714
|
-
allowFailure: false,
|
|
715
|
-
value: shouldPassValue ? amtWei : 0n,
|
|
716
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [receiver, amtWei]),
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
else {
|
|
720
|
-
// ERC20 代币转账
|
|
721
|
-
calls.push({
|
|
722
|
-
target: senderWallet.address,
|
|
723
|
-
allowFailure: false,
|
|
724
|
-
value: 0n,
|
|
725
|
-
callData: delegateInterface.encodeFunctionData('executeTransfer', [
|
|
726
|
-
tokenAddress,
|
|
727
|
-
receiver,
|
|
728
|
-
amtWei,
|
|
729
|
-
]),
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
// ========================================
|
|
735
|
-
// 多跳转账:sender → hop1 → hop2 → ... → receiver
|
|
736
|
-
// ========================================
|
|
737
|
-
const firstHop = hopWallets[0];
|
|
738
|
-
const lastHop = hopWallets[hopWallets.length - 1];
|
|
739
|
-
// ✅ 第一个发送者负责转 OKB 固定费用(在转账前)
|
|
740
|
-
if (profitAmountOkb > 0n && i === maxTransferIndex) {
|
|
741
|
-
calls.push({
|
|
742
|
-
target: senderWallet.address,
|
|
743
|
-
allowFailure: false,
|
|
744
|
-
value: 0n,
|
|
745
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
746
|
-
});
|
|
747
|
-
console.log(`[pairwiseTransfer] 发送者 ${i} 转利润(多跳): ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
748
|
-
}
|
|
749
|
-
if (isNative) {
|
|
750
|
-
// 原生 OKB 多跳
|
|
751
|
-
// ✅ sender → 第一个中间钱包(使用 transferAmount 转移指定金额)
|
|
752
|
-
calls.push({
|
|
753
|
-
target: senderWallet.address,
|
|
754
|
-
allowFailure: false,
|
|
755
|
-
value: shouldPassValue ? amtWei : 0n,
|
|
756
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [firstHop.address, amtWei]),
|
|
757
|
-
});
|
|
758
|
-
// 中间钱包之间转账(使用 transferTo,因为中间钱包只有这笔钱)
|
|
759
|
-
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
760
|
-
calls.push({
|
|
761
|
-
target: hopWallets[j].address,
|
|
762
|
-
allowFailure: false,
|
|
763
|
-
value: 0n,
|
|
764
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [hopWallets[j + 1].address]),
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
// 最后一个中间钱包 → receiver(使用 transferTo 转移全部)
|
|
768
|
-
calls.push({
|
|
769
|
-
target: lastHop.address,
|
|
770
|
-
allowFailure: false,
|
|
771
|
-
value: 0n,
|
|
772
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [receiver]),
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
else {
|
|
776
|
-
// ERC20 多跳
|
|
777
|
-
// sender → 第一个中间钱包(ERC20 代币)
|
|
778
|
-
calls.push({
|
|
779
|
-
target: senderWallet.address,
|
|
780
|
-
allowFailure: false,
|
|
781
|
-
value: 0n,
|
|
782
|
-
callData: delegateInterface.encodeFunctionData('executeTransfer', [
|
|
783
|
-
tokenAddress,
|
|
784
|
-
firstHop.address,
|
|
785
|
-
amtWei,
|
|
786
|
-
]),
|
|
787
|
-
});
|
|
788
|
-
// 中间钱包之间转账
|
|
789
|
-
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
790
|
-
calls.push({
|
|
791
|
-
target: hopWallets[j].address,
|
|
792
|
-
allowFailure: false,
|
|
793
|
-
value: 0n,
|
|
794
|
-
callData: delegateInterface.encodeFunctionData('executeTransferAll', [
|
|
795
|
-
tokenAddress,
|
|
796
|
-
hopWallets[j + 1].address,
|
|
797
|
-
]),
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
// 最后一个中间钱包 → receiver
|
|
801
|
-
calls.push({
|
|
802
|
-
target: lastHop.address,
|
|
803
|
-
allowFailure: false,
|
|
804
|
-
value: 0n,
|
|
805
|
-
callData: delegateInterface.encodeFunctionData('executeTransferAll', [
|
|
806
|
-
tokenAddress,
|
|
807
|
-
receiver,
|
|
808
|
-
]),
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
// ========================================
|
|
814
|
-
// 同步签署授权(已有 nonce)
|
|
815
|
-
// ========================================
|
|
816
|
-
// ✅ 如果有单独的 Payer,需要为 Payer 也生成授权
|
|
817
|
-
const allWallets = payerWallet
|
|
818
|
-
? [payerWallet, ...senderWallets]
|
|
819
|
-
: [...senderWallets];
|
|
820
|
-
const allNonces = payerWallet
|
|
821
|
-
? [payerNonce, ...nonces]
|
|
822
|
-
: [...nonces];
|
|
823
|
-
for (const hopWallets of allHopWallets) {
|
|
824
|
-
for (const hopWallet of hopWallets) {
|
|
825
|
-
allWallets.push(hopWallet);
|
|
826
|
-
allNonces.push(0);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // mainWallet(可能是 Payer 或第一个发送者钱包)是交易发起者
|
|
830
|
-
);
|
|
831
|
-
// ========================================
|
|
832
|
-
// 同步构建交易
|
|
833
|
-
// ========================================
|
|
834
|
-
// ✅ Payer 模式下 value 为 0(资金来自发送者钱包)
|
|
835
|
-
const totalValue = (isNative && !payerWallet) ? totalAmount : 0n;
|
|
836
|
-
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, payerWallet ? payerNonce : nonces[0], feeData, config);
|
|
837
|
-
return {
|
|
838
|
-
signedTransaction,
|
|
839
|
-
hopWallets: hopCount > 0 ? allHopWalletInfos : undefined,
|
|
840
|
-
metadata: {
|
|
841
|
-
pairCount,
|
|
842
|
-
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
843
|
-
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|
|
844
|
-
isNative,
|
|
845
|
-
tokenAddress: tokenAddress ?? null,
|
|
846
|
-
hopCount,
|
|
847
|
-
userType,
|
|
848
|
-
},
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
// ========================================
|
|
852
|
-
// 保留旧函数名的别名(向后兼容)
|
|
853
|
-
// ========================================
|
|
854
|
-
/** @deprecated 使用 disperse 替代 */
|
|
855
|
-
export const disperseWithHops = disperse;
|
|
856
|
-
/** @deprecated 使用 sweep 替代 */
|
|
857
|
-
export const sweepWithHops = sweep;
|