four-flap-meme-sdk 1.2.53 → 1.2.55
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/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/utils/erc20.d.ts +57 -8
- package/dist/utils/erc20.js +143 -28
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * as Abis from './abis/index.js';
|
|
2
2
|
export { ADDRESSES, CHAIN } from './utils/constants.js';
|
|
3
3
|
export { isExclusiveOnChain, isExclusiveOffChain } from './utils/mpcExclusive.js';
|
|
4
|
-
export { ensureSellApprovalV1, checkSellApprovalV1, ensureSellApprovalV2, checkSellApprovalV2, ensureSellApproval, checkSellApproval, ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch, checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch, type EnsureAllowanceBatchItemResult, type ApproveTokenBatchParams, type ApproveTokenBatchResult } from './utils/erc20.js';
|
|
4
|
+
export { ensureSellApprovalV1, checkSellApprovalV1, ensureSellApprovalV2, checkSellApprovalV2, ensureSellApproval, checkSellApproval, ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch, checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch, checkAllowanceRaw, approveTokenRaw, checkAllowanceBatchRaw, approveTokenBatchRaw, type EnsureAllowanceBatchItemResult, type ApproveTokenBatchParams, type ApproveTokenBatchRawParams, type ApproveTokenBatchResult } from './utils/erc20.js';
|
|
5
5
|
export { parseFourError, type FourErrorCode } from './utils/errors.js';
|
|
6
6
|
export { getTokenManagerV1, getTokenManagerV2, getTokenManagerHelper3, getTokenManagerV1Writer, getTokenManagerV2Writer, getTokenManagerHelper3Writer, getTokenManagerAddress, type ChainName } from './utils/contract-factory.js';
|
|
7
7
|
export { FourClient, buildLoginMessage, type FourConfig, type GenerateNonceReq, type LoginReq, type CreateTokenReq, type CreateTokenResp } from './clients/four.js';
|
package/dist/index.js
CHANGED
|
@@ -10,8 +10,10 @@ ensureSellApprovalV2, checkSellApprovalV2,
|
|
|
10
10
|
ensureSellApproval, checkSellApproval,
|
|
11
11
|
// Flap Protocol 授权
|
|
12
12
|
ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch,
|
|
13
|
-
// ✅
|
|
14
|
-
checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch
|
|
13
|
+
// ✅ 智能路由授权方法(自动选择 spender,适用于 Flap、Four、V2、V3)
|
|
14
|
+
checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch,
|
|
15
|
+
// ✅ 底层授权方法(手动指定 spender,适用于任意合约)
|
|
16
|
+
checkAllowanceRaw, approveTokenRaw, checkAllowanceBatchRaw, approveTokenBatchRaw } from './utils/erc20.js';
|
|
15
17
|
export { parseFourError } from './utils/errors.js';
|
|
16
18
|
export { getTokenManagerV1, getTokenManagerV2, getTokenManagerHelper3, getTokenManagerV1Writer, getTokenManagerV2Writer, getTokenManagerHelper3Writer, getTokenManagerAddress } from './utils/contract-factory.js';
|
|
17
19
|
export { FourClient, buildLoginMessage } from './clients/four.js';
|
package/dist/utils/erc20.d.ts
CHANGED
|
@@ -92,7 +92,18 @@ export declare function checkFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAY
|
|
|
92
92
|
*/
|
|
93
93
|
export declare function batchCheckAllowances(provider: JsonRpcProvider, tokenAddress: string, owners: string[], spender: string): Promise<bigint[]>;
|
|
94
94
|
/**
|
|
95
|
-
* ✅
|
|
95
|
+
* ✅ 智能路由:检查单个 ERC20 授权额度(自动选择 spender)
|
|
96
|
+
*
|
|
97
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
98
|
+
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
99
|
+
* @param rpcUrl - RPC 节点地址
|
|
100
|
+
* @param tokenAddress - 代币合约地址
|
|
101
|
+
* @param ownerAddress - 代币持有者地址
|
|
102
|
+
* @returns 当前授权额度(bigint)
|
|
103
|
+
*/
|
|
104
|
+
export declare function checkAllowance(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, tokenAddress: string, ownerAddress: string): Promise<bigint>;
|
|
105
|
+
/**
|
|
106
|
+
* ✅ 底层方法:检查 ERC20 代币授权额度(手动指定 spender)
|
|
96
107
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
97
108
|
*
|
|
98
109
|
* @param rpcUrl - RPC 节点地址
|
|
@@ -101,9 +112,21 @@ export declare function batchCheckAllowances(provider: JsonRpcProvider, tokenAdd
|
|
|
101
112
|
* @param spenderAddress - 被授权的合约地址(如 Router、Portal、TokenManager 等)
|
|
102
113
|
* @returns 当前授权额度(bigint)
|
|
103
114
|
*/
|
|
104
|
-
export declare function
|
|
115
|
+
export declare function checkAllowanceRaw(rpcUrl: string, tokenAddress: string, ownerAddress: string, spenderAddress: string): Promise<bigint>;
|
|
105
116
|
/**
|
|
106
|
-
* ✅
|
|
117
|
+
* ✅ 智能路由:授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
118
|
+
*
|
|
119
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
120
|
+
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
121
|
+
* @param rpcUrl - RPC 节点地址
|
|
122
|
+
* @param privateKey - 私钥
|
|
123
|
+
* @param tokenAddress - 代币合约地址
|
|
124
|
+
* @param amount - 授权数量(bigint),传 'max' 表示最大授权
|
|
125
|
+
* @returns 授权结果
|
|
126
|
+
*/
|
|
127
|
+
export declare function approveToken(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, privateKey: string, tokenAddress: string, amount: bigint | 'max'): Promise<EnsureAllowanceResult>;
|
|
128
|
+
/**
|
|
129
|
+
* ✅ 底层方法:授权 ERC20 代币给指定合约(手动指定 spender)
|
|
107
130
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
108
131
|
*
|
|
109
132
|
* @param rpcUrl - RPC 节点地址
|
|
@@ -113,9 +136,20 @@ export declare function checkAllowance(rpcUrl: string, tokenAddress: string, own
|
|
|
113
136
|
* @param amount - 授权数量(bigint),传 'max' 或 ethers.MaxUint256 表示最大授权
|
|
114
137
|
* @returns 授权结果
|
|
115
138
|
*/
|
|
116
|
-
export declare function
|
|
139
|
+
export declare function approveTokenRaw(rpcUrl: string, privateKey: string, tokenAddress: string, spenderAddress: string, amount: bigint | 'max'): Promise<EnsureAllowanceResult>;
|
|
117
140
|
/**
|
|
118
|
-
* ✅
|
|
141
|
+
* ✅ 智能路由:批量检查多个钱包的授权额度(自动选择 spender)
|
|
142
|
+
*
|
|
143
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
144
|
+
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
145
|
+
* @param rpcUrl - RPC 节点地址
|
|
146
|
+
* @param tokenAddress - 代币合约地址
|
|
147
|
+
* @param ownerAddresses - 代币持有者地址数组
|
|
148
|
+
* @returns 每个地址的授权额度数组
|
|
149
|
+
*/
|
|
150
|
+
export declare function checkAllowanceBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, tokenAddress: string, ownerAddresses: string[]): Promise<bigint[]>;
|
|
151
|
+
/**
|
|
152
|
+
* ✅ 底层方法:批量检查多个钱包的授权额度(手动指定 spender)
|
|
119
153
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
120
154
|
*
|
|
121
155
|
* @param rpcUrl - RPC 节点地址
|
|
@@ -124,8 +158,16 @@ export declare function approveToken(rpcUrl: string, privateKey: string, tokenAd
|
|
|
124
158
|
* @param spenderAddress - 被授权的合约地址
|
|
125
159
|
* @returns 每个地址的授权额度数组
|
|
126
160
|
*/
|
|
127
|
-
export declare function
|
|
161
|
+
export declare function checkAllowanceBatchRaw(rpcUrl: string, tokenAddress: string, ownerAddresses: string[], spenderAddress: string): Promise<bigint[]>;
|
|
128
162
|
export type ApproveTokenBatchParams = {
|
|
163
|
+
chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH';
|
|
164
|
+
platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3';
|
|
165
|
+
rpcUrl: string;
|
|
166
|
+
privateKeys: string[];
|
|
167
|
+
tokenAddress: string;
|
|
168
|
+
amounts: (bigint | 'max')[];
|
|
169
|
+
};
|
|
170
|
+
export type ApproveTokenBatchRawParams = {
|
|
129
171
|
rpcUrl: string;
|
|
130
172
|
privateKeys: string[];
|
|
131
173
|
tokenAddress: string;
|
|
@@ -145,10 +187,17 @@ export type ApproveTokenBatchResult = {
|
|
|
145
187
|
}>;
|
|
146
188
|
};
|
|
147
189
|
/**
|
|
148
|
-
* ✅
|
|
149
|
-
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
190
|
+
* ✅ 智能路由:批量授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
150
191
|
*
|
|
151
192
|
* @param params - 批量授权参数
|
|
152
193
|
* @returns 批量授权结果
|
|
153
194
|
*/
|
|
154
195
|
export declare function approveTokenBatch(params: ApproveTokenBatchParams): Promise<ApproveTokenBatchResult>;
|
|
196
|
+
/**
|
|
197
|
+
* ✅ 底层方法:批量授权 ERC20 代币(手动指定 spender)
|
|
198
|
+
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
199
|
+
*
|
|
200
|
+
* @param params - 批量授权参数
|
|
201
|
+
* @returns 批量授权结果
|
|
202
|
+
*/
|
|
203
|
+
export declare function approveTokenBatchRaw(params: ApproveTokenBatchRawParams): Promise<ApproveTokenBatchResult>;
|
package/dist/utils/erc20.js
CHANGED
|
@@ -11,7 +11,11 @@ const ERC20_ABI = [
|
|
|
11
11
|
*/
|
|
12
12
|
async function validateContractAddress(provider, address, label) {
|
|
13
13
|
try {
|
|
14
|
-
|
|
14
|
+
// ✅ 先规范化地址(转为 checksum 格式),避免 ethers v6 的严格校验报错
|
|
15
|
+
const normalizedAddress = address.toLowerCase().startsWith('0x')
|
|
16
|
+
? address.toLowerCase()
|
|
17
|
+
: `0x${address.toLowerCase()}`;
|
|
18
|
+
const code = await provider.getCode(normalizedAddress);
|
|
15
19
|
if (code === '0x' || code.length <= 2) {
|
|
16
20
|
throw new Error(`❌ ${label} 地址无效或未部署合约: ${address}`);
|
|
17
21
|
}
|
|
@@ -20,6 +24,10 @@ async function validateContractAddress(provider, address, label) {
|
|
|
20
24
|
if (error.message.includes('无效或未部署')) {
|
|
21
25
|
throw error;
|
|
22
26
|
}
|
|
27
|
+
// ✅ 如果是 checksum 错误,提供更友好的提示
|
|
28
|
+
if (error.message.includes('bad address checksum') || error.code === 'INVALID_ARGUMENT') {
|
|
29
|
+
throw new Error(`❌ ${label} 地址格式错误,请使用正确的 checksum 格式: ${address}`);
|
|
30
|
+
}
|
|
23
31
|
throw new Error(`❌ 无法验证 ${label} 地址 ${address}: ${error.message}`);
|
|
24
32
|
}
|
|
25
33
|
}
|
|
@@ -205,7 +213,7 @@ export async function ensureFlapSellApproval(chain, rpcUrl, privateKey, token, o
|
|
|
205
213
|
* @param privateKeys 拥有者私钥数组(每个地址需自行签名)
|
|
206
214
|
* @returns 每个地址的授权结果(包含 owner 与交易回执等信息)
|
|
207
215
|
*/
|
|
208
|
-
export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, token, required = (
|
|
216
|
+
export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, token, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
|
|
209
217
|
const results = [];
|
|
210
218
|
for (const pk of privateKeys || []) {
|
|
211
219
|
const owner = new Wallet(pk).address;
|
|
@@ -217,7 +225,7 @@ export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, to
|
|
|
217
225
|
/**
|
|
218
226
|
* 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
|
|
219
227
|
*/
|
|
220
|
-
export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, required = (
|
|
228
|
+
export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
|
|
221
229
|
const proxyAddresses = {
|
|
222
230
|
BSC: ADDRESSES.BSC.FlapPortal,
|
|
223
231
|
BASE: ADDRESSES.BASE.FlapPortal,
|
|
@@ -280,7 +288,60 @@ export async function batchCheckAllowances(provider, tokenAddress, owners, spend
|
|
|
280
288
|
});
|
|
281
289
|
}
|
|
282
290
|
/**
|
|
283
|
-
*
|
|
291
|
+
* 🔧 内部辅助函数:根据链和平台自动解析 spender 地址
|
|
292
|
+
*/
|
|
293
|
+
function resolveSpenderAddress(chain, platform) {
|
|
294
|
+
const spenderMap = {
|
|
295
|
+
flap: {
|
|
296
|
+
BSC: ADDRESSES.BSC.FlapPortal,
|
|
297
|
+
BASE: ADDRESSES.BASE.FlapPortal,
|
|
298
|
+
XLAYER: ADDRESSES.XLAYER.FlapPortal,
|
|
299
|
+
MORPH: ADDRESSES.MORPH.FlapPortal,
|
|
300
|
+
},
|
|
301
|
+
four: {
|
|
302
|
+
// Four.meme 使用 TokenManagerV2Proxy(默认)
|
|
303
|
+
BSC: ADDRESSES.BSC.TokenManagerV2Proxy,
|
|
304
|
+
BASE: ADDRESSES.BASE.TokenManagerHelper3,
|
|
305
|
+
XLAYER: '0x0000000000000000000000000000000000000000', // XLAYER 暂不支持 Four.meme
|
|
306
|
+
MORPH: '0x0000000000000000000000000000000000000000', // MORPH 暂不支持 Four.meme
|
|
307
|
+
},
|
|
308
|
+
'pancake-v2': {
|
|
309
|
+
// PancakeSwap V2 Router 地址(需要在 constants.ts 中添加)
|
|
310
|
+
BSC: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // BSC PancakeSwap V2 Router
|
|
311
|
+
BASE: '0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb', // BASE PancakeSwap V2 Router
|
|
312
|
+
XLAYER: '0x0000000000000000000000000000000000000000', // XLAYER 暂不支持
|
|
313
|
+
MORPH: '0x0000000000000000000000000000000000000000', // MORPH 暂不支持
|
|
314
|
+
},
|
|
315
|
+
'pancake-v3': {
|
|
316
|
+
// PancakeSwap V3 SmartRouter 地址(需要在 constants.ts 中添加)
|
|
317
|
+
BSC: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4', // BSC PancakeSwap V3 SmartRouter
|
|
318
|
+
BASE: '0x678Aa4bF4E210cf2166753e054d5b7c31cc7fa86', // BASE PancakeSwap V3 SmartRouter
|
|
319
|
+
XLAYER: '0x0000000000000000000000000000000000000000', // XLAYER 暂不支持
|
|
320
|
+
MORPH: '0x0000000000000000000000000000000000000000', // MORPH 暂不支持
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
const spender = spenderMap[platform]?.[chain];
|
|
324
|
+
if (!spender || spender === '0x0000000000000000000000000000000000000000') {
|
|
325
|
+
throw new Error(`❌ 不支持的链或平台: ${chain} / ${platform}`);
|
|
326
|
+
}
|
|
327
|
+
return spender;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* ✅ 智能路由:检查单个 ERC20 授权额度(自动选择 spender)
|
|
331
|
+
*
|
|
332
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
333
|
+
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
334
|
+
* @param rpcUrl - RPC 节点地址
|
|
335
|
+
* @param tokenAddress - 代币合约地址
|
|
336
|
+
* @param ownerAddress - 代币持有者地址
|
|
337
|
+
* @returns 当前授权额度(bigint)
|
|
338
|
+
*/
|
|
339
|
+
export async function checkAllowance(chain, platform, rpcUrl, tokenAddress, ownerAddress) {
|
|
340
|
+
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
341
|
+
return checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spenderAddress);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* ✅ 底层方法:检查 ERC20 代币授权额度(手动指定 spender)
|
|
284
345
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
285
346
|
*
|
|
286
347
|
* @param rpcUrl - RPC 节点地址
|
|
@@ -289,14 +350,18 @@ export async function batchCheckAllowances(provider, tokenAddress, owners, spend
|
|
|
289
350
|
* @param spenderAddress - 被授权的合约地址(如 Router、Portal、TokenManager 等)
|
|
290
351
|
* @returns 当前授权额度(bigint)
|
|
291
352
|
*/
|
|
292
|
-
export async function
|
|
353
|
+
export async function checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spenderAddress) {
|
|
293
354
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
355
|
+
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
356
|
+
const normalizedToken = tokenAddress.toLowerCase();
|
|
357
|
+
const normalizedOwner = ownerAddress.toLowerCase();
|
|
358
|
+
const normalizedSpender = spenderAddress.toLowerCase();
|
|
294
359
|
// 验证地址
|
|
295
|
-
await validateContractAddress(provider,
|
|
296
|
-
await validateContractAddress(provider,
|
|
297
|
-
const erc20 = new Contract(
|
|
360
|
+
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
361
|
+
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
362
|
+
const erc20 = new Contract(normalizedToken, ERC20_ABI, provider);
|
|
298
363
|
try {
|
|
299
|
-
const allowance = await erc20.allowance(
|
|
364
|
+
const allowance = await erc20.allowance(normalizedOwner, normalizedSpender);
|
|
300
365
|
return allowance;
|
|
301
366
|
}
|
|
302
367
|
catch (error) {
|
|
@@ -304,7 +369,22 @@ export async function checkAllowance(rpcUrl, tokenAddress, ownerAddress, spender
|
|
|
304
369
|
}
|
|
305
370
|
}
|
|
306
371
|
/**
|
|
307
|
-
* ✅
|
|
372
|
+
* ✅ 智能路由:授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
373
|
+
*
|
|
374
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
375
|
+
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
376
|
+
* @param rpcUrl - RPC 节点地址
|
|
377
|
+
* @param privateKey - 私钥
|
|
378
|
+
* @param tokenAddress - 代币合约地址
|
|
379
|
+
* @param amount - 授权数量(bigint),传 'max' 表示最大授权
|
|
380
|
+
* @returns 授权结果
|
|
381
|
+
*/
|
|
382
|
+
export async function approveToken(chain, platform, rpcUrl, privateKey, tokenAddress, amount) {
|
|
383
|
+
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
384
|
+
return approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderAddress, amount);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* ✅ 底层方法:授权 ERC20 代币给指定合约(手动指定 spender)
|
|
308
388
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
309
389
|
*
|
|
310
390
|
* @param rpcUrl - RPC 节点地址
|
|
@@ -314,19 +394,22 @@ export async function checkAllowance(rpcUrl, tokenAddress, ownerAddress, spender
|
|
|
314
394
|
* @param amount - 授权数量(bigint),传 'max' 或 ethers.MaxUint256 表示最大授权
|
|
315
395
|
* @returns 授权结果
|
|
316
396
|
*/
|
|
317
|
-
export async function
|
|
397
|
+
export async function approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderAddress, amount) {
|
|
318
398
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
319
399
|
const signer = new Wallet(privateKey, provider);
|
|
320
400
|
const ownerAddress = signer.address;
|
|
401
|
+
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
402
|
+
const normalizedToken = tokenAddress.toLowerCase();
|
|
403
|
+
const normalizedSpender = spenderAddress.toLowerCase();
|
|
321
404
|
// 验证地址
|
|
322
|
-
await validateContractAddress(provider,
|
|
323
|
-
await validateContractAddress(provider,
|
|
324
|
-
const erc20 = new Contract(
|
|
405
|
+
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
406
|
+
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
407
|
+
const erc20 = new Contract(normalizedToken, ERC20_ABI, signer);
|
|
325
408
|
const requiredAmount = amount === 'max' ? BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') : amount;
|
|
326
409
|
// 检查当前授权额度
|
|
327
410
|
let currentAllowance;
|
|
328
411
|
try {
|
|
329
|
-
currentAllowance = await erc20.allowance(ownerAddress,
|
|
412
|
+
currentAllowance = await erc20.allowance(ownerAddress, normalizedSpender);
|
|
330
413
|
}
|
|
331
414
|
catch (error) {
|
|
332
415
|
throw new Error(`❌ 查询授权额度失败: ${error.message}`);
|
|
@@ -341,7 +424,7 @@ export async function approveToken(rpcUrl, privateKey, tokenAddress, spenderAddr
|
|
|
341
424
|
}
|
|
342
425
|
// 发送授权交易
|
|
343
426
|
try {
|
|
344
|
-
const tx = await erc20.approve(
|
|
427
|
+
const tx = await erc20.approve(normalizedSpender, requiredAmount);
|
|
345
428
|
const receipt = await tx.wait();
|
|
346
429
|
return {
|
|
347
430
|
alreadyApproved: false,
|
|
@@ -355,7 +438,21 @@ export async function approveToken(rpcUrl, privateKey, tokenAddress, spenderAddr
|
|
|
355
438
|
}
|
|
356
439
|
}
|
|
357
440
|
/**
|
|
358
|
-
* ✅
|
|
441
|
+
* ✅ 智能路由:批量检查多个钱包的授权额度(自动选择 spender)
|
|
442
|
+
*
|
|
443
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
444
|
+
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
445
|
+
* @param rpcUrl - RPC 节点地址
|
|
446
|
+
* @param tokenAddress - 代币合约地址
|
|
447
|
+
* @param ownerAddresses - 代币持有者地址数组
|
|
448
|
+
* @returns 每个地址的授权额度数组
|
|
449
|
+
*/
|
|
450
|
+
export async function checkAllowanceBatch(chain, platform, rpcUrl, tokenAddress, ownerAddresses) {
|
|
451
|
+
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
452
|
+
return checkAllowanceBatchRaw(rpcUrl, tokenAddress, ownerAddresses, spenderAddress);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* ✅ 底层方法:批量检查多个钱包的授权额度(手动指定 spender)
|
|
359
456
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
360
457
|
*
|
|
361
458
|
* @param rpcUrl - RPC 节点地址
|
|
@@ -364,29 +461,47 @@ export async function approveToken(rpcUrl, privateKey, tokenAddress, spenderAddr
|
|
|
364
461
|
* @param spenderAddress - 被授权的合约地址
|
|
365
462
|
* @returns 每个地址的授权额度数组
|
|
366
463
|
*/
|
|
367
|
-
export async function
|
|
464
|
+
export async function checkAllowanceBatchRaw(rpcUrl, tokenAddress, ownerAddresses, spenderAddress) {
|
|
368
465
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
466
|
+
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
467
|
+
const normalizedToken = tokenAddress.toLowerCase();
|
|
468
|
+
const normalizedOwners = ownerAddresses.map(addr => addr.toLowerCase());
|
|
469
|
+
const normalizedSpender = spenderAddress.toLowerCase();
|
|
369
470
|
// 验证地址
|
|
370
|
-
await validateContractAddress(provider,
|
|
371
|
-
await validateContractAddress(provider,
|
|
372
|
-
return batchCheckAllowances(provider,
|
|
471
|
+
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
472
|
+
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
473
|
+
return batchCheckAllowances(provider, normalizedToken, normalizedOwners, normalizedSpender);
|
|
373
474
|
}
|
|
374
475
|
/**
|
|
375
|
-
* ✅
|
|
376
|
-
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
476
|
+
* ✅ 智能路由:批量授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
377
477
|
*
|
|
378
478
|
* @param params - 批量授权参数
|
|
379
479
|
* @returns 批量授权结果
|
|
380
480
|
*/
|
|
381
481
|
export async function approveTokenBatch(params) {
|
|
482
|
+
const { chain, platform, rpcUrl, privateKeys, tokenAddress, amounts } = params;
|
|
483
|
+
const spenderAddress = resolveSpenderAddress(chain, platform);
|
|
484
|
+
return approveTokenBatchRaw({ rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts });
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* ✅ 底层方法:批量授权 ERC20 代币(手动指定 spender)
|
|
488
|
+
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
489
|
+
*
|
|
490
|
+
* @param params - 批量授权参数
|
|
491
|
+
* @returns 批量授权结果
|
|
492
|
+
*/
|
|
493
|
+
export async function approveTokenBatchRaw(params) {
|
|
382
494
|
const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts } = params;
|
|
383
495
|
if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
|
|
384
496
|
throw new Error('❌ 私钥数量和授权数量必须匹配');
|
|
385
497
|
}
|
|
386
498
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
499
|
+
// ✅ 规范化地址(转为小写,避免 checksum 错误)
|
|
500
|
+
const normalizedToken = tokenAddress.toLowerCase();
|
|
501
|
+
const normalizedSpender = spenderAddress.toLowerCase();
|
|
387
502
|
// 验证地址
|
|
388
|
-
await validateContractAddress(provider,
|
|
389
|
-
await validateContractAddress(provider,
|
|
503
|
+
await validateContractAddress(provider, normalizedToken, 'Token');
|
|
504
|
+
await validateContractAddress(provider, normalizedSpender, 'Spender');
|
|
390
505
|
const results = [];
|
|
391
506
|
let approvedCount = 0;
|
|
392
507
|
for (let i = 0; i < privateKeys.length; i++) {
|
|
@@ -396,9 +511,9 @@ export async function approveTokenBatch(params) {
|
|
|
396
511
|
? BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
|
|
397
512
|
: amounts[i];
|
|
398
513
|
try {
|
|
399
|
-
const erc20 = new Contract(
|
|
514
|
+
const erc20 = new Contract(normalizedToken, ERC20_ABI, signer);
|
|
400
515
|
// 检查当前授权
|
|
401
|
-
const currentAllowance = await erc20.allowance(ownerAddress,
|
|
516
|
+
const currentAllowance = await erc20.allowance(ownerAddress, normalizedSpender);
|
|
402
517
|
if (currentAllowance >= requiredAmount) {
|
|
403
518
|
results.push({
|
|
404
519
|
owner: ownerAddress,
|
|
@@ -409,7 +524,7 @@ export async function approveTokenBatch(params) {
|
|
|
409
524
|
continue;
|
|
410
525
|
}
|
|
411
526
|
// 发送授权交易
|
|
412
|
-
const tx = await erc20.approve(
|
|
527
|
+
const tx = await erc20.approve(normalizedSpender, requiredAmount);
|
|
413
528
|
const receipt = await tx.wait();
|
|
414
529
|
results.push({
|
|
415
530
|
owner: ownerAddress,
|