four-flap-meme-sdk 2.0.0 → 2.1.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 +34 -0
- package/dist/chains/index.d.ts +13 -0
- package/dist/chains/index.js +13 -0
- package/dist/chains/xlayer/eip7702/index.d.ts +2 -0
- package/dist/flap/index.d.ts +10 -0
- package/dist/flap/index.js +8 -0
- package/dist/shared/constants/index.d.ts +2 -0
- package/dist/shared/index.d.ts +2 -0
- package/package.json +66 -1
- 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,645 +0,0 @@
|
|
|
1
|
-
import { Contract, Wallet, JsonRpcProvider, Interface, parseUnits } from 'ethers';
|
|
2
|
-
import { ADDRESSES, ZERO_ADDRESS, getMulticall3Address } from '../constants.js';
|
|
3
|
-
import { NonceManager } from '../bundle-helpers.js';
|
|
4
|
-
import { ERC20_ABI, MULTICALL3_ABI } from '../../shared/abis/common.js';
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// ✅ Max approval(与 BSC 策略一致:阈值判断,避免频繁重复授权)
|
|
7
|
-
// ============================================================================
|
|
8
|
-
const MAX_UINT256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
|
9
|
-
const MAX_APPROVAL_THRESHOLD = MAX_UINT256 / 2n;
|
|
10
|
-
/**
|
|
11
|
-
* 验证合约地址是否有效(是否部署了代码)
|
|
12
|
-
*/
|
|
13
|
-
async function validateContractAddress(provider, address, label) {
|
|
14
|
-
try {
|
|
15
|
-
// ✅ 先规范化地址(转为 checksum 格式),避免 ethers v6 的严格校验报错
|
|
16
|
-
const normalizedAddress = address.toLowerCase().startsWith('0x')
|
|
17
|
-
? address.toLowerCase()
|
|
18
|
-
: `0x${address.toLowerCase()}`;
|
|
19
|
-
const code = await provider.getCode(normalizedAddress);
|
|
20
|
-
if (code === '0x' || code.length <= 2) {
|
|
21
|
-
throw new Error(`❌ ${label} 地址无效或未部署合约: ${address}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (error.message.includes('无效或未部署')) {
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
// ✅ 如果是 checksum 错误,提供更友好的提示
|
|
29
|
-
if (error.message.includes('bad address checksum') || error.code === 'INVALID_ARGUMENT') {
|
|
30
|
-
throw new Error(`❌ ${label} 地址格式错误,请使用正确的 checksum 格式: ${address}`);
|
|
31
|
-
}
|
|
32
|
-
throw new Error(`❌ 无法验证 ${label} 地址 ${address}: ${error.message}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* 通用 ERC20 授权方法(内部辅助函数)
|
|
37
|
-
* 自动检查当前授权额度,只在不足时才发送交易
|
|
38
|
-
*/
|
|
39
|
-
async function ensureAllowance(rpcUrl, privateKey, token, owner, spender, required) {
|
|
40
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
41
|
-
const signer = new Wallet(privateKey, provider);
|
|
42
|
-
// ✅ 验证 token 和 spender 地址
|
|
43
|
-
await validateContractAddress(provider, token, 'Token');
|
|
44
|
-
await validateContractAddress(provider, spender, 'Spender');
|
|
45
|
-
const erc20 = new Contract(token, ERC20_ABI, signer);
|
|
46
|
-
// ✅ 自动检查当前授权额度,只在不足时才发送交易
|
|
47
|
-
let current;
|
|
48
|
-
try {
|
|
49
|
-
current = await erc20.allowance(owner, spender);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`);
|
|
53
|
-
}
|
|
54
|
-
if (current >= required) {
|
|
55
|
-
return {
|
|
56
|
-
alreadyApproved: true,
|
|
57
|
-
currentAllowance: current,
|
|
58
|
-
requiredAllowance: required,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const tx = await erc20.approve(spender, required);
|
|
62
|
-
const receipt = await tx.wait();
|
|
63
|
-
return {
|
|
64
|
-
alreadyApproved: false,
|
|
65
|
-
currentAllowance: current,
|
|
66
|
-
requiredAllowance: required,
|
|
67
|
-
txReceipt: receipt,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* 检查代币授权状态 - Four.meme V1(只读,不发送交易)
|
|
72
|
-
* @returns 是否已授权足够的额度
|
|
73
|
-
*/
|
|
74
|
-
export async function checkSellApprovalV1(chain, rpcUrl, token, owner, amount) {
|
|
75
|
-
const proxyAddresses = {
|
|
76
|
-
BSC: ADDRESSES.BSC.TokenManagerV1, // FourMeme 代理合约 V1 (BSC)
|
|
77
|
-
BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE)
|
|
78
|
-
ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM)
|
|
79
|
-
};
|
|
80
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
81
|
-
// ✅ 验证 token 和代理合约地址
|
|
82
|
-
await validateContractAddress(provider, token, 'Token');
|
|
83
|
-
await validateContractAddress(provider, proxyAddresses[chain], `Four.meme V1 Proxy (${chain})`);
|
|
84
|
-
const erc20 = new Contract(token, ERC20_ABI, provider);
|
|
85
|
-
let current;
|
|
86
|
-
try {
|
|
87
|
-
current = await erc20.allowance(owner, proxyAddresses[chain]);
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`);
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
isApproved: current >= amount,
|
|
94
|
-
currentAllowance: current,
|
|
95
|
-
requiredAllowance: amount,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* 确保代币已授权给 Four.meme V1 代理合约
|
|
100
|
-
* 用于 Four.meme V1 代币的卖出操作
|
|
101
|
-
* @returns 授权结果,包含是否已授权、当前额度、交易回执等信息
|
|
102
|
-
*/
|
|
103
|
-
export async function ensureSellApprovalV1(chain, rpcUrl, privateKey, token, owner, amount) {
|
|
104
|
-
// ✅ 授权给 FourMeme 代理合约 V1(收费版)
|
|
105
|
-
const proxyAddresses = {
|
|
106
|
-
BSC: ADDRESSES.BSC.TokenManagerV1, // FourMeme 代理合约 V1 (BSC)
|
|
107
|
-
BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE)
|
|
108
|
-
ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM)
|
|
109
|
-
};
|
|
110
|
-
return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount);
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* 检查代币授权状态 - Four.meme V2(只读,不发送交易)
|
|
114
|
-
* @returns 是否已授权足够的额度
|
|
115
|
-
*/
|
|
116
|
-
export async function checkSellApprovalV2(chain, rpcUrl, token, owner, amount) {
|
|
117
|
-
const proxyAddresses = {
|
|
118
|
-
BSC: ADDRESSES.BSC.TokenManagerV2, // FourMeme 代理合约 V2 (BSC)
|
|
119
|
-
BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE)
|
|
120
|
-
ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM)
|
|
121
|
-
};
|
|
122
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
123
|
-
// ✅ 验证 token 和代理合约地址
|
|
124
|
-
await validateContractAddress(provider, token, 'Token');
|
|
125
|
-
await validateContractAddress(provider, proxyAddresses[chain], `Four.meme V2 Proxy (${chain})`);
|
|
126
|
-
const erc20 = new Contract(token, ERC20_ABI, provider);
|
|
127
|
-
let current;
|
|
128
|
-
try {
|
|
129
|
-
current = await erc20.allowance(owner, proxyAddresses[chain]);
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`);
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
isApproved: current >= amount,
|
|
136
|
-
currentAllowance: current,
|
|
137
|
-
requiredAllowance: amount,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 确保代币已授权给 Four.meme V2 代理合约
|
|
142
|
-
* 用于 Four.meme V2 代币的卖出操作
|
|
143
|
-
* @returns 授权结果,包含是否已授权、当前额度、交易回执等信息
|
|
144
|
-
*/
|
|
145
|
-
export async function ensureSellApprovalV2(chain, rpcUrl, privateKey, token, owner, amount) {
|
|
146
|
-
// ✅ 授权给 FourMeme 代理合约 V2(收费版)
|
|
147
|
-
const proxyAddresses = {
|
|
148
|
-
BSC: ADDRESSES.BSC.TokenManagerV2, // FourMeme 代理合约 V2 (BSC)
|
|
149
|
-
BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE)
|
|
150
|
-
ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM)
|
|
151
|
-
};
|
|
152
|
-
return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount);
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* 检查代币授权状态 - Four.meme(通用,默认使用 V2)
|
|
156
|
-
* @deprecated 建议使用明确版本的方法:checkSellApprovalV1 或 checkSellApprovalV2
|
|
157
|
-
*/
|
|
158
|
-
export async function checkSellApproval(chain, rpcUrl, token, owner, amount) {
|
|
159
|
-
return checkSellApprovalV2(chain, rpcUrl, token, owner, amount);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* 确保代币已授权给 Four.meme 代理合约(通用,默认使用 V2)
|
|
163
|
-
* @deprecated 建议使用明确版本的方法:ensureSellApprovalV1 或 ensureSellApprovalV2
|
|
164
|
-
*/
|
|
165
|
-
export async function ensureSellApproval(chain, rpcUrl, privateKey, token, owner, amount) {
|
|
166
|
-
return ensureSellApprovalV2(chain, rpcUrl, privateKey, token, owner, amount);
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* 检查代币授权状态 - Flap Protocol(只读,不发送交易)
|
|
170
|
-
* @returns 是否已授权足够的额度
|
|
171
|
-
*/
|
|
172
|
-
export async function checkFlapSellApproval(chain, rpcUrl, token, owner, amount) {
|
|
173
|
-
const proxyAddresses = {
|
|
174
|
-
BSC: ADDRESSES.BSC.FlapPortal, // Flap Portal 代理合约 (BSC)
|
|
175
|
-
BASE: ADDRESSES.BASE.FlapPortal, // Flap Portal 代理合约 (BASE)
|
|
176
|
-
XLAYER: ADDRESSES.XLAYER.FlapPortal, // Flap Portal 代理合约 (XLAYER)
|
|
177
|
-
MORPH: ADDRESSES.MORPH.FlapPortal, // Flap Portal 代理合约 (MORPH)
|
|
178
|
-
MONAD: ADDRESSES.MONAD.FlapPortal, // Flap Portal 代理合约 (MONAD)
|
|
179
|
-
};
|
|
180
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
181
|
-
// ✅ 验证 token 和代理合约地址
|
|
182
|
-
await validateContractAddress(provider, token, 'Token');
|
|
183
|
-
await validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`);
|
|
184
|
-
const erc20 = new Contract(token, ERC20_ABI, provider);
|
|
185
|
-
let current;
|
|
186
|
-
try {
|
|
187
|
-
current = await erc20.allowance(owner, proxyAddresses[chain]);
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`);
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
isApproved: current >= amount,
|
|
194
|
-
currentAllowance: current,
|
|
195
|
-
requiredAllowance: amount,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* 确保代币已授权给 Flap Protocol 代理合约
|
|
200
|
-
* 用于 Flap Protocol 代币的卖出操作
|
|
201
|
-
* @returns 授权结果,包含是否已授权、当前额度、交易回执等信息
|
|
202
|
-
*/
|
|
203
|
-
export async function ensureFlapSellApproval(chain, rpcUrl, privateKey, token, owner, amount) {
|
|
204
|
-
// ✅ 授权给 Flap Portal 代理合约(收费版)
|
|
205
|
-
const proxyAddresses = {
|
|
206
|
-
BSC: ADDRESSES.BSC.FlapPortal, // Flap Portal 代理合约 (BSC)
|
|
207
|
-
BASE: ADDRESSES.BASE.FlapPortal, // Flap Portal 代理合约 (BASE)
|
|
208
|
-
XLAYER: ADDRESSES.XLAYER.FlapPortal, // Flap Portal 代理合约 (XLAYER)
|
|
209
|
-
MORPH: ADDRESSES.MORPH.FlapPortal, // Flap Portal 代理合约 (MORPH)
|
|
210
|
-
MONAD: ADDRESSES.MONAD.FlapPortal, // Flap Portal 代理合约 (MONAD)
|
|
211
|
-
};
|
|
212
|
-
return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount);
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* 批量确保代币已授权给 Flap Protocol 代理合约(默认授权上限 2^256-1)
|
|
216
|
-
* @param privateKeys 拥有者私钥数组(每个地址需自行签名)
|
|
217
|
-
* @returns 每个地址的授权结果(包含 owner 与交易回执等信息)
|
|
218
|
-
*/
|
|
219
|
-
export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, token, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
|
|
220
|
-
if (!privateKeys || privateKeys.length === 0)
|
|
221
|
-
return [];
|
|
222
|
-
// ✅ 并行处理所有授权
|
|
223
|
-
const results = await Promise.all(privateKeys.map(async (pk) => {
|
|
224
|
-
const owner = new Wallet(pk).address;
|
|
225
|
-
const r = await ensureFlapSellApproval(chain, rpcUrl, pk, token, owner, required);
|
|
226
|
-
return { owner, ...r };
|
|
227
|
-
}));
|
|
228
|
-
return results;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
|
|
232
|
-
* ✅ 使用 Multicall3 批量查询,减少 RPC 调用次数
|
|
233
|
-
*/
|
|
234
|
-
export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
|
|
235
|
-
if (!owners || owners.length === 0)
|
|
236
|
-
return [];
|
|
237
|
-
const proxyAddresses = {
|
|
238
|
-
BSC: ADDRESSES.BSC.FlapPortal,
|
|
239
|
-
BASE: ADDRESSES.BASE.FlapPortal,
|
|
240
|
-
XLAYER: ADDRESSES.XLAYER.FlapPortal,
|
|
241
|
-
MORPH: ADDRESSES.MORPH.FlapPortal,
|
|
242
|
-
MONAD: ADDRESSES.MONAD.FlapPortal,
|
|
243
|
-
};
|
|
244
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
245
|
-
// ✅ 并行验证 token 和代理合约地址
|
|
246
|
-
await Promise.all([
|
|
247
|
-
validateContractAddress(provider, token, 'Token'),
|
|
248
|
-
validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`)
|
|
249
|
-
]);
|
|
250
|
-
// ✅ 使用 batchCheckAllowances 批量查询(Multicall3)
|
|
251
|
-
const allowances = await batchCheckAllowances(provider, token, owners, proxyAddresses[chain]);
|
|
252
|
-
return owners.map((owner, i) => ({
|
|
253
|
-
owner,
|
|
254
|
-
isApproved: allowances[i] >= required,
|
|
255
|
-
currentAllowance: allowances[i],
|
|
256
|
-
requiredAllowance: required
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* 使用 Multicall3 批量查询 ERC20 授权额度
|
|
261
|
-
* @param provider - Provider 实例
|
|
262
|
-
* @param tokenAddress - ERC20 代币地址
|
|
263
|
-
* @param owners - 所有者地址数组
|
|
264
|
-
* @param spender - 被授权的 spender 地址
|
|
265
|
-
* @returns 每个地址的授权额度数组
|
|
266
|
-
*/
|
|
267
|
-
export async function batchCheckAllowances(provider, tokenAddress, owners, spender) {
|
|
268
|
-
const network = await provider.getNetwork();
|
|
269
|
-
const multicall3Address = getMulticall3Address(Number(network.chainId));
|
|
270
|
-
const multicall3 = new Contract(multicall3Address, MULTICALL3_ABI, provider);
|
|
271
|
-
// 编码 allowance(owner, spender) 调用数据
|
|
272
|
-
const erc20Interface = new Contract(tokenAddress, ERC20_ABI, provider).interface;
|
|
273
|
-
const calls = owners.map(owner => ({
|
|
274
|
-
target: tokenAddress,
|
|
275
|
-
allowFailure: true,
|
|
276
|
-
callData: erc20Interface.encodeFunctionData('allowance', [owner, spender])
|
|
277
|
-
}));
|
|
278
|
-
// 批量调用
|
|
279
|
-
const results = await multicall3.aggregate3(calls);
|
|
280
|
-
// 解析结果
|
|
281
|
-
return results.map((result, index) => {
|
|
282
|
-
if (!result.success) {
|
|
283
|
-
return 0n;
|
|
284
|
-
}
|
|
285
|
-
try {
|
|
286
|
-
const decoded = erc20Interface.decodeFunctionResult('allowance', result.returnData);
|
|
287
|
-
return decoded[0];
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
return 0n;
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* 🔧 内部辅助函数:根据链和平台自动解析 spender 地址
|
|
296
|
-
*/
|
|
297
|
-
function resolveSpenderAddress(chain, platform) {
|
|
298
|
-
const spenderMap = {
|
|
299
|
-
flap: {
|
|
300
|
-
BSC: ADDRESSES.BSC.FlapPortal,
|
|
301
|
-
BASE: ADDRESSES.BASE.FlapPortal,
|
|
302
|
-
XLAYER: ADDRESSES.XLAYER.FlapPortal,
|
|
303
|
-
MORPH: ADDRESSES.MORPH.FlapPortal,
|
|
304
|
-
MONAD: ADDRESSES.MONAD.FlapPortal,
|
|
305
|
-
ENI: ZERO_ADDRESS,
|
|
306
|
-
},
|
|
307
|
-
four: {
|
|
308
|
-
BSC: ADDRESSES.BSC.TokenManagerOriginal,
|
|
309
|
-
BASE: ADDRESSES.BASE.TokenManagerHelper3,
|
|
310
|
-
XLAYER: ZERO_ADDRESS,
|
|
311
|
-
MORPH: ZERO_ADDRESS,
|
|
312
|
-
MONAD: ZERO_ADDRESS,
|
|
313
|
-
ENI: ZERO_ADDRESS,
|
|
314
|
-
},
|
|
315
|
-
'pancake-v2': {
|
|
316
|
-
BSC: ADDRESSES.BSC.PancakeV2Router,
|
|
317
|
-
BASE: '0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb',
|
|
318
|
-
XLAYER: ZERO_ADDRESS,
|
|
319
|
-
MORPH: ZERO_ADDRESS,
|
|
320
|
-
MONAD: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9',
|
|
321
|
-
ENI: ZERO_ADDRESS,
|
|
322
|
-
},
|
|
323
|
-
'pancake-v3': {
|
|
324
|
-
BSC: ADDRESSES.BSC.PancakeV3Router,
|
|
325
|
-
BASE: '0x678Aa4bF4E210cf2166753e054d5b7c31cc7fa86',
|
|
326
|
-
XLAYER: ZERO_ADDRESS,
|
|
327
|
-
MORPH: ZERO_ADDRESS,
|
|
328
|
-
MONAD: '0x1b81d678ffb9c0263b24a97847620c99d213eb14',
|
|
329
|
-
ENI: ZERO_ADDRESS,
|
|
330
|
-
},
|
|
331
|
-
daoaas: {
|
|
332
|
-
BSC: ZERO_ADDRESS,
|
|
333
|
-
BASE: ZERO_ADDRESS,
|
|
334
|
-
XLAYER: ZERO_ADDRESS,
|
|
335
|
-
MORPH: ZERO_ADDRESS,
|
|
336
|
-
MONAD: ZERO_ADDRESS,
|
|
337
|
-
ENI: ADDRESSES.ENI.DaoaasPortal,
|
|
338
|
-
},
|
|
339
|
-
'dswap-v2': {
|
|
340
|
-
BSC: ZERO_ADDRESS,
|
|
341
|
-
BASE: ZERO_ADDRESS,
|
|
342
|
-
XLAYER: ZERO_ADDRESS,
|
|
343
|
-
MORPH: ZERO_ADDRESS,
|
|
344
|
-
MONAD: ZERO_ADDRESS,
|
|
345
|
-
ENI: ADDRESSES.ENI.DswapV2Router,
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
|
-
const spender = spenderMap[platform]?.[chain];
|
|
349
|
-
if (!spender || spender === ZERO_ADDRESS) {
|
|
350
|
-
throw new Error(`❌ 不支持的链或平台: ${chain} / ${platform}`);
|
|
351
|
-
}
|
|
352
|
-
return spender;
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* ✅ 智能路由:检查单个 ERC20 授权额度(自动选择 spender)
|
|
356
|
-
*
|
|
357
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
358
|
-
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
359
|
-
* @param rpcUrl - RPC 节点地址
|
|
360
|
-
* @param tokenAddress - 代币合约地址
|
|
361
|
-
* @param ownerAddress - 代币持有者地址
|
|
362
|
-
* @returns 当前授权额度(bigint)
|
|
363
|
-
*/
|
|
364
|
-
export async function checkAllowance(chain, platform, rpcUrl, tokenAddress, ownerAddress) {
|
|
365
|
-
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
366
|
-
return checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spenderAddress);
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* ✅ 底层方法:检查 ERC20 代币授权额度(手动指定 spender)
|
|
370
|
-
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
371
|
-
*
|
|
372
|
-
* @param rpcUrl - RPC 节点地址
|
|
373
|
-
* @param tokenAddress - 代币合约地址
|
|
374
|
-
* @param ownerAddress - 代币持有者地址
|
|
375
|
-
* @param spenderAddress - 被授权的合约地址(如 Router、Portal、TokenManager 等)
|
|
376
|
-
* @returns 当前授权额度(bigint)
|
|
377
|
-
*/
|
|
378
|
-
export async function checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spenderAddress) {
|
|
379
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
380
|
-
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
381
|
-
const normalizedToken = tokenAddress.toLowerCase();
|
|
382
|
-
const normalizedOwner = ownerAddress.toLowerCase();
|
|
383
|
-
const normalizedSpender = spenderAddress.toLowerCase();
|
|
384
|
-
// 验证地址
|
|
385
|
-
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
386
|
-
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
387
|
-
const erc20 = new Contract(normalizedToken, ERC20_ABI, provider);
|
|
388
|
-
try {
|
|
389
|
-
const allowance = await erc20.allowance(normalizedOwner, normalizedSpender);
|
|
390
|
-
return allowance;
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
throw new Error(`❌ 查询授权额度失败: ${error.message}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* ✅ 智能路由:授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
398
|
-
*
|
|
399
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
400
|
-
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
401
|
-
* @param rpcUrl - RPC 节点地址
|
|
402
|
-
* @param privateKey - 私钥
|
|
403
|
-
* @param tokenAddress - 代币合约地址
|
|
404
|
-
* @param amount - 授权数量(bigint),传 'max' 表示最大授权
|
|
405
|
-
* @returns 授权结果
|
|
406
|
-
*/
|
|
407
|
-
export async function approveToken(chain, platform, rpcUrl, privateKey, tokenAddress, amount) {
|
|
408
|
-
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
409
|
-
return approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderAddress, amount);
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* ✅ 底层方法:授权 ERC20 代币给指定合约(手动指定 spender)
|
|
413
|
-
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
414
|
-
*
|
|
415
|
-
* @param rpcUrl - RPC 节点地址
|
|
416
|
-
* @param privateKey - 私钥
|
|
417
|
-
* @param tokenAddress - 代币合约地址
|
|
418
|
-
* @param spenderAddress - 被授权的合约地址(如 Router、Portal、TokenManager 等)
|
|
419
|
-
* @param amount - 授权数量(bigint),传 'max' 或 ethers.MaxUint256 表示最大授权
|
|
420
|
-
* @returns 授权结果
|
|
421
|
-
*/
|
|
422
|
-
export async function approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderAddress, amount) {
|
|
423
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
424
|
-
const signer = new Wallet(privateKey, provider);
|
|
425
|
-
const ownerAddress = signer.address;
|
|
426
|
-
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
427
|
-
const normalizedToken = tokenAddress.toLowerCase();
|
|
428
|
-
const normalizedSpender = spenderAddress.toLowerCase();
|
|
429
|
-
// 验证地址
|
|
430
|
-
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
431
|
-
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
432
|
-
const erc20 = new Contract(normalizedToken, ERC20_ABI, signer);
|
|
433
|
-
const isMax = amount === 'max';
|
|
434
|
-
const requiredAmount = isMax ? MAX_UINT256 : amount;
|
|
435
|
-
// 检查当前授权额度
|
|
436
|
-
let currentAllowance;
|
|
437
|
-
try {
|
|
438
|
-
currentAllowance = await erc20.allowance(ownerAddress, normalizedSpender);
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
throw new Error(`❌ 查询授权额度失败: ${error.message}`);
|
|
442
|
-
}
|
|
443
|
-
// 如果已经授权足够,直接返回
|
|
444
|
-
// ✅ max 授权:使用阈值判断,避免每次消耗一点 allowance 都重新授权
|
|
445
|
-
if (isMax ? (currentAllowance >= MAX_APPROVAL_THRESHOLD) : (currentAllowance >= requiredAmount)) {
|
|
446
|
-
return {
|
|
447
|
-
alreadyApproved: true,
|
|
448
|
-
currentAllowance,
|
|
449
|
-
requiredAllowance: requiredAmount
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
// 发送授权交易
|
|
453
|
-
try {
|
|
454
|
-
const tx = await erc20.approve(normalizedSpender, requiredAmount);
|
|
455
|
-
const receipt = await tx.wait();
|
|
456
|
-
return {
|
|
457
|
-
alreadyApproved: false,
|
|
458
|
-
currentAllowance,
|
|
459
|
-
requiredAllowance: requiredAmount,
|
|
460
|
-
txReceipt: receipt
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
catch (error) {
|
|
464
|
-
throw new Error(`❌ 授权交易失败: ${error.message}`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* ✅ 智能路由:批量检查多个钱包的授权额度(自动选择 spender)
|
|
469
|
-
*
|
|
470
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
471
|
-
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
472
|
-
* @param rpcUrl - RPC 节点地址
|
|
473
|
-
* @param tokenAddress - 代币合约地址
|
|
474
|
-
* @param ownerAddresses - 代币持有者地址数组
|
|
475
|
-
* @returns 每个地址的授权额度数组
|
|
476
|
-
*/
|
|
477
|
-
export async function checkAllowanceBatch(chain, platform, rpcUrl, tokenAddress, ownerAddresses) {
|
|
478
|
-
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
479
|
-
return checkAllowanceBatchRaw(rpcUrl, tokenAddress, ownerAddresses, spenderAddress);
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* ✅ 底层方法:批量检查多个钱包的授权额度(手动指定 spender)
|
|
483
|
-
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
484
|
-
*
|
|
485
|
-
* @param rpcUrl - RPC 节点地址
|
|
486
|
-
* @param tokenAddress - 代币合约地址
|
|
487
|
-
* @param ownerAddresses - 代币持有者地址数组
|
|
488
|
-
* @param spenderAddress - 被授权的合约地址
|
|
489
|
-
* @returns 每个地址的授权额度数组
|
|
490
|
-
*/
|
|
491
|
-
export async function checkAllowanceBatchRaw(rpcUrl, tokenAddress, ownerAddresses, spenderAddress) {
|
|
492
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
493
|
-
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
494
|
-
const normalizedToken = tokenAddress.toLowerCase();
|
|
495
|
-
const normalizedOwners = ownerAddresses.map(addr => addr.toLowerCase());
|
|
496
|
-
const normalizedSpender = spenderAddress.toLowerCase();
|
|
497
|
-
// 验证地址
|
|
498
|
-
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
499
|
-
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
500
|
-
return batchCheckAllowances(provider, normalizedToken, normalizedOwners, normalizedSpender);
|
|
501
|
-
}
|
|
502
|
-
export async function approveTokenBatch(params) {
|
|
503
|
-
const { chain, platform, rpcUrl, privateKeys, tokenAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId, skipValidation } = params;
|
|
504
|
-
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
505
|
-
return approveTokenBatchRaw({ rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId, skipValidation });
|
|
506
|
-
}
|
|
507
|
-
export async function approveTokenBatchRaw(params) {
|
|
508
|
-
const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId = 56, nonceManager: externalNonceManager, skipValidation } = params;
|
|
509
|
-
if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
|
|
510
|
-
throw new Error('❌ 私钥数量和授权数量必须匹配');
|
|
511
|
-
}
|
|
512
|
-
const provider = new JsonRpcProvider(rpcUrl);
|
|
513
|
-
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
514
|
-
const normalizedToken = tokenAddress.toLowerCase();
|
|
515
|
-
const normalizedSpender = spenderAddress.toLowerCase();
|
|
516
|
-
// 验证地址(如果 skipValidation=true 则跳过)
|
|
517
|
-
if (!skipValidation) {
|
|
518
|
-
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
519
|
-
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
520
|
-
}
|
|
521
|
-
// ✅ 优化:批量创建钱包和合约实例
|
|
522
|
-
const wallets = privateKeys.map(key => new Wallet(key, provider));
|
|
523
|
-
const ownerAddresses = wallets.map(w => w.address);
|
|
524
|
-
const isMaxApprovals = amounts.map(a => a === 'max');
|
|
525
|
-
const requiredAmounts = amounts.map(amount => amount === 'max'
|
|
526
|
-
? MAX_UINT256
|
|
527
|
-
: amount);
|
|
528
|
-
// ==================== signOnly=true:只签名不提交 ====================
|
|
529
|
-
if (signOnly) {
|
|
530
|
-
// ✅ 使用 NonceManager 管理 nonce(和买卖交易一样)
|
|
531
|
-
const nonceManager = externalNonceManager || new NonceManager(provider);
|
|
532
|
-
// ✅ 并行获取:当前授权额度 + nonces + gasPrice
|
|
533
|
-
const [currentAllowances, nonces, fetchedGasPrice] = await Promise.all([
|
|
534
|
-
batchCheckAllowances(provider, normalizedToken, ownerAddresses, normalizedSpender),
|
|
535
|
-
nonceManager.getNextNoncesForWallets(wallets), // ✅ 使用 NonceManager 批量获取 nonce
|
|
536
|
-
gasPriceGwei ? Promise.resolve(parseUnits(gasPriceGwei.toString(), 'gwei')) : provider.getFeeData().then(fee => fee.gasPrice || parseUnits('3', 'gwei'))
|
|
537
|
-
]);
|
|
538
|
-
const finalGasPrice = fetchedGasPrice;
|
|
539
|
-
const finalGasLimit = BigInt(gasLimit || 100000);
|
|
540
|
-
// ✅ ERC20 approve 函数的 ABI 编码
|
|
541
|
-
const erc20Interface = new Interface(ERC20_ABI);
|
|
542
|
-
// ✅ 并行签名所有需要授权的交易
|
|
543
|
-
const signPromises = wallets.map(async (wallet, i) => {
|
|
544
|
-
const ownerAddress = ownerAddresses[i];
|
|
545
|
-
const currentAllowance = currentAllowances[i];
|
|
546
|
-
const requiredAmount = requiredAmounts[i];
|
|
547
|
-
const isMax = isMaxApprovals[i];
|
|
548
|
-
// 如果已经授权足够,跳过
|
|
549
|
-
// ✅ max 授权:使用阈值判断(与 BSC 一致)
|
|
550
|
-
if (isMax ? (currentAllowance >= MAX_APPROVAL_THRESHOLD) : (currentAllowance >= requiredAmount)) {
|
|
551
|
-
return {
|
|
552
|
-
owner: ownerAddress,
|
|
553
|
-
alreadyApproved: true,
|
|
554
|
-
currentAllowance,
|
|
555
|
-
requiredAllowance: requiredAmount,
|
|
556
|
-
signedTx: undefined
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
// 构建并签名交易
|
|
560
|
-
const txData = erc20Interface.encodeFunctionData('approve', [normalizedSpender, requiredAmount]);
|
|
561
|
-
const signedTx = await wallet.signTransaction({
|
|
562
|
-
to: normalizedToken,
|
|
563
|
-
data: txData,
|
|
564
|
-
nonce: nonces[i], // ✅ NonceManager 已经处理好递增
|
|
565
|
-
gasLimit: finalGasLimit,
|
|
566
|
-
gasPrice: finalGasPrice,
|
|
567
|
-
chainId,
|
|
568
|
-
type: 0 // Legacy 交易
|
|
569
|
-
});
|
|
570
|
-
return {
|
|
571
|
-
owner: ownerAddress,
|
|
572
|
-
alreadyApproved: false,
|
|
573
|
-
currentAllowance,
|
|
574
|
-
requiredAllowance: requiredAmount,
|
|
575
|
-
signedTx
|
|
576
|
-
};
|
|
577
|
-
});
|
|
578
|
-
const results = await Promise.all(signPromises);
|
|
579
|
-
// ✅ 提取所有签名交易(过滤掉已授权的)
|
|
580
|
-
const signedTransactions = results
|
|
581
|
-
.filter(r => !r.alreadyApproved && r.signedTx)
|
|
582
|
-
.map(r => r.signedTx);
|
|
583
|
-
const alreadyApprovedCount = results.filter(r => r.alreadyApproved).length;
|
|
584
|
-
const needApproveCount = results.filter(r => !r.alreadyApproved).length;
|
|
585
|
-
return {
|
|
586
|
-
signedTransactions,
|
|
587
|
-
results,
|
|
588
|
-
needApproveCount,
|
|
589
|
-
alreadyApprovedCount
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
// ==================== signOnly=false(默认):直接发送交易 ====================
|
|
593
|
-
// ✅ 优化:并行检查所有钱包的授权额度
|
|
594
|
-
const currentAllowances = await batchCheckAllowances(provider, normalizedToken, ownerAddresses, normalizedSpender);
|
|
595
|
-
// ✅ 优化:并行发送所有需要授权的交易
|
|
596
|
-
const approvalPromises = wallets.map(async (wallet, i) => {
|
|
597
|
-
const ownerAddress = ownerAddresses[i];
|
|
598
|
-
const currentAllowance = currentAllowances[i];
|
|
599
|
-
const requiredAmount = requiredAmounts[i];
|
|
600
|
-
const isMax = isMaxApprovals[i];
|
|
601
|
-
try {
|
|
602
|
-
// 如果已经授权足够,跳过
|
|
603
|
-
// ✅ max 授权:使用阈值判断(与 BSC 一致)
|
|
604
|
-
if (isMax ? (currentAllowance >= MAX_APPROVAL_THRESHOLD) : (currentAllowance >= requiredAmount)) {
|
|
605
|
-
return {
|
|
606
|
-
owner: ownerAddress,
|
|
607
|
-
alreadyApproved: true,
|
|
608
|
-
currentAllowance,
|
|
609
|
-
requiredAllowance: requiredAmount
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
// 发送授权交易
|
|
613
|
-
const erc20 = new Contract(normalizedToken, ERC20_ABI, wallet);
|
|
614
|
-
const tx = await erc20.approve(normalizedSpender, requiredAmount);
|
|
615
|
-
const receipt = await tx.wait();
|
|
616
|
-
return {
|
|
617
|
-
owner: ownerAddress,
|
|
618
|
-
alreadyApproved: false,
|
|
619
|
-
currentAllowance,
|
|
620
|
-
requiredAllowance: requiredAmount,
|
|
621
|
-
txHash: receipt.hash
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
catch (error) {
|
|
625
|
-
return {
|
|
626
|
-
owner: ownerAddress,
|
|
627
|
-
alreadyApproved: false,
|
|
628
|
-
currentAllowance,
|
|
629
|
-
requiredAllowance: requiredAmount,
|
|
630
|
-
error: error.message
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
// ✅ 优化:并行等待所有授权交易完成
|
|
635
|
-
const results = await Promise.all(approvalPromises);
|
|
636
|
-
// 统计结果
|
|
637
|
-
const approvedCount = results.filter(r => !r.alreadyApproved && !r.error).length;
|
|
638
|
-
const errorCount = results.filter(r => r.error).length;
|
|
639
|
-
// ✅ 只要没有错误,就算成功(包括所有钱包都已授权的情况)
|
|
640
|
-
return {
|
|
641
|
-
success: errorCount === 0,
|
|
642
|
-
approvedCount,
|
|
643
|
-
results
|
|
644
|
-
};
|
|
645
|
-
}
|