four-flap-meme-sdk 1.7.8 → 1.7.10
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/eip7702/xlayer/approve.d.ts +84 -3
- package/dist/eip7702/xlayer/approve.js +118 -8
- package/dist/eip7702/xlayer/bundle.js +7 -7
- package/dist/eip7702/xlayer/check.d.ts +105 -6
- package/dist/eip7702/xlayer/check.js +270 -38
- package/dist/eip7702/xlayer/constants.d.ts +12 -1
- package/dist/eip7702/xlayer/constants.js +20 -2
- package/dist/eip7702/xlayer/create-to-dex.js +5 -4
- package/dist/eip7702/xlayer/curve-to-dex.js +5 -4
- package/dist/eip7702/xlayer/disperse.js +5 -4
- package/dist/eip7702/xlayer/index.d.ts +3 -3
- package/dist/eip7702/xlayer/index.js +3 -3
- package/dist/eip7702/xlayer/sign.d.ts +50 -0
- package/dist/eip7702/xlayer/sign.js +105 -9
- package/dist/eip7702/xlayer/sweep.js +5 -4
- package/package.json +1 -1
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* X-Layer Bundle 批量授权
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* ✅ 完整授权支持:
|
|
5
|
+
* - 代币授权给内盘 Flap Portal
|
|
6
|
+
* - 代币授权给外盘 V2/V3 Router
|
|
7
|
+
* - WOKB 授权给路由(用于卖出/交换)
|
|
8
|
+
* - 稳定币授权(USDT、USDC、USDT0)
|
|
5
9
|
*
|
|
6
10
|
* 优化:第一个钱包作为 Sponsor,支付 gas
|
|
7
11
|
* 其他钱包不需要 gas
|
|
8
12
|
*/
|
|
9
13
|
import type { BatchOperationResult } from './types.js';
|
|
14
|
+
/** 授权目标类型 */
|
|
15
|
+
export type ApprovalTarget = 'all' | 'flap' | 'v2' | 'v3' | 'v3-02';
|
|
16
|
+
/** 授权模式 */
|
|
17
|
+
export type ApprovalMode = 'token' | 'base' | 'full';
|
|
10
18
|
/**
|
|
11
19
|
* 批量授权参数
|
|
12
20
|
*
|
|
@@ -16,7 +24,7 @@ import type { BatchOperationResult } from './types.js';
|
|
|
16
24
|
export interface BatchApproveParams {
|
|
17
25
|
/** RPC URL */
|
|
18
26
|
rpcUrl: string;
|
|
19
|
-
/**
|
|
27
|
+
/** 代币地址(要授权的代币) */
|
|
20
28
|
tokenAddress: string;
|
|
21
29
|
/** 要授权的钱包列表(第一个钱包作为 Sponsor,支付 gas) */
|
|
22
30
|
wallets: Array<{
|
|
@@ -24,6 +32,31 @@ export interface BatchApproveParams {
|
|
|
24
32
|
}>;
|
|
25
33
|
/** Gas Price (可选) */
|
|
26
34
|
gasPrice?: bigint;
|
|
35
|
+
/** 授权目标(默认 'all':授权给所有路由) */
|
|
36
|
+
target?: ApprovalTarget;
|
|
37
|
+
/** 授权模式(默认 'token':只授权代币) */
|
|
38
|
+
mode?: ApprovalMode;
|
|
39
|
+
/** 是否包含 WOKB 授权(默认 false) */
|
|
40
|
+
includeWokb?: boolean;
|
|
41
|
+
/** 是否包含稳定币授权(默认 false) */
|
|
42
|
+
includeStablecoins?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 完整授权参数(代币 + WOKB + 稳定币)
|
|
46
|
+
*/
|
|
47
|
+
export interface FullApproveParams {
|
|
48
|
+
/** RPC URL */
|
|
49
|
+
rpcUrl: string;
|
|
50
|
+
/** 代币地址(要授权的代币) */
|
|
51
|
+
tokenAddress: string;
|
|
52
|
+
/** 要授权的钱包列表(第一个钱包作为 Sponsor,支付 gas) */
|
|
53
|
+
wallets: Array<{
|
|
54
|
+
privateKey: string;
|
|
55
|
+
}>;
|
|
56
|
+
/** Gas Price (可选) */
|
|
57
|
+
gasPrice?: bigint;
|
|
58
|
+
/** 授权目标(默认 'all':授权给所有路由) */
|
|
59
|
+
target?: ApprovalTarget;
|
|
27
60
|
}
|
|
28
61
|
/**
|
|
29
62
|
* 单个钱包授权参数
|
|
@@ -39,14 +72,54 @@ export interface SingleApproveParams {
|
|
|
39
72
|
walletPrivateKey: string;
|
|
40
73
|
/** Gas Price (可选) */
|
|
41
74
|
gasPrice?: bigint;
|
|
75
|
+
/** 授权目标(默认 'all') */
|
|
76
|
+
target?: ApprovalTarget;
|
|
77
|
+
/** 授权模式(默认 'token') */
|
|
78
|
+
mode?: ApprovalMode;
|
|
79
|
+
/** 是否包含 WOKB 授权 */
|
|
80
|
+
includeWokb?: boolean;
|
|
81
|
+
/** 是否包含稳定币授权 */
|
|
82
|
+
includeStablecoins?: boolean;
|
|
42
83
|
}
|
|
43
84
|
/**
|
|
44
85
|
* Bundle 批量授权
|
|
45
86
|
*
|
|
46
87
|
* 将多个钱包对所有 Spender 的授权打包成一笔交易
|
|
47
88
|
* 第一个钱包作为 Sponsor,支付 gas
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* // 只授权代币给所有路由
|
|
93
|
+
* await bundleBatchApprove({
|
|
94
|
+
* rpcUrl,
|
|
95
|
+
* tokenAddress,
|
|
96
|
+
* wallets: [{ privateKey: pk1 }, { privateKey: pk2 }],
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* // 授权代币 + WOKB
|
|
100
|
+
* await bundleBatchApprove({
|
|
101
|
+
* rpcUrl,
|
|
102
|
+
* tokenAddress,
|
|
103
|
+
* wallets: [{ privateKey: pk1 }],
|
|
104
|
+
* includeWokb: true,
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* // 完整授权(代币 + WOKB + 稳定币)
|
|
108
|
+
* await bundleBatchApprove({
|
|
109
|
+
* rpcUrl,
|
|
110
|
+
* tokenAddress,
|
|
111
|
+
* wallets: [{ privateKey: pk1 }],
|
|
112
|
+
* mode: 'full',
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
48
115
|
*/
|
|
49
116
|
export declare function bundleBatchApprove(params: BatchApproveParams): Promise<BatchOperationResult>;
|
|
117
|
+
/**
|
|
118
|
+
* Bundle 完整授权(代币 + WOKB + 稳定币)
|
|
119
|
+
*
|
|
120
|
+
* 一次性完成所有授权,跟 BSC 链一样
|
|
121
|
+
*/
|
|
122
|
+
export declare function bundleFullApprove(params: FullApproveParams): Promise<BatchOperationResult>;
|
|
50
123
|
/**
|
|
51
124
|
* Bundle 单钱包授权
|
|
52
125
|
*
|
|
@@ -57,4 +130,12 @@ export declare function bundleSingleApprove(params: SingleApproveParams): Promis
|
|
|
57
130
|
/**
|
|
58
131
|
* 获取需要授权的 Spender 列表
|
|
59
132
|
*/
|
|
60
|
-
export declare function getSpendersToApprove(): readonly string[];
|
|
133
|
+
export declare function getSpendersToApprove(target?: ApprovalTarget): readonly string[];
|
|
134
|
+
/**
|
|
135
|
+
* 获取基础代币列表(WOKB + 稳定币)
|
|
136
|
+
*/
|
|
137
|
+
export declare function getBaseTokensToApprove(): readonly string[];
|
|
138
|
+
/**
|
|
139
|
+
* 获取稳定币列表
|
|
140
|
+
*/
|
|
141
|
+
export declare function getStablecoins(): readonly string[];
|
|
@@ -1,33 +1,114 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* X-Layer Bundle 批量授权
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* ✅ 完整授权支持:
|
|
5
|
+
* - 代币授权给内盘 Flap Portal
|
|
6
|
+
* - 代币授权给外盘 V2/V3 Router
|
|
7
|
+
* - WOKB 授权给路由(用于卖出/交换)
|
|
8
|
+
* - 稳定币授权(USDT、USDC、USDT0)
|
|
5
9
|
*
|
|
6
10
|
* 优化:第一个钱包作为 Sponsor,支付 gas
|
|
7
11
|
* 其他钱包不需要 gas
|
|
8
12
|
*/
|
|
9
|
-
import { SPENDERS_TO_APPROVE, MAX_UINT256 } from './constants.js';
|
|
13
|
+
import { SPENDERS_TO_APPROVE, BASE_TOKENS_TO_APPROVE, MAX_UINT256, WOKB, USDT, USDC, USDT0, FLAP_PORTAL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_SWAP_ROUTER02, } from './constants.js';
|
|
10
14
|
import { XLayerBundle } from './bundle.js';
|
|
11
15
|
import { buildApprove } from './builders.js';
|
|
16
|
+
// ==================== 辅助函数 ====================
|
|
17
|
+
/**
|
|
18
|
+
* 获取目标 Spender 列表
|
|
19
|
+
*/
|
|
20
|
+
function getSpenders(target = 'all') {
|
|
21
|
+
switch (target) {
|
|
22
|
+
case 'flap':
|
|
23
|
+
return [FLAP_PORTAL];
|
|
24
|
+
case 'v2':
|
|
25
|
+
return [POTATOSWAP_V2_ROUTER];
|
|
26
|
+
case 'v3':
|
|
27
|
+
return [POTATOSWAP_V3_ROUTER];
|
|
28
|
+
case 'v3-02':
|
|
29
|
+
return [POTATOSWAP_SWAP_ROUTER02];
|
|
30
|
+
case 'all':
|
|
31
|
+
default:
|
|
32
|
+
return SPENDERS_TO_APPROVE;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 构建授权 calls
|
|
37
|
+
*/
|
|
38
|
+
function buildApproveCalls(tokenAddress, target = 'all', mode = 'token', includeWokb = false, includeStablecoins = false) {
|
|
39
|
+
const spenders = getSpenders(target);
|
|
40
|
+
const calls = [];
|
|
41
|
+
// 1. 代币授权给路由
|
|
42
|
+
if (mode === 'token' || mode === 'full') {
|
|
43
|
+
for (const spender of spenders) {
|
|
44
|
+
calls.push(buildApprove(tokenAddress, spender, MAX_UINT256));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// 2. WOKB 授权给路由(用于 V3 卖出等)
|
|
48
|
+
if (includeWokb || mode === 'base' || mode === 'full') {
|
|
49
|
+
for (const spender of spenders) {
|
|
50
|
+
calls.push(buildApprove(WOKB, spender, MAX_UINT256));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// 3. 稳定币授权给路由
|
|
54
|
+
if (includeStablecoins || mode === 'full') {
|
|
55
|
+
const stablecoins = [USDT, USDC, USDT0];
|
|
56
|
+
for (const stablecoin of stablecoins) {
|
|
57
|
+
for (const spender of spenders) {
|
|
58
|
+
calls.push(buildApprove(stablecoin, spender, MAX_UINT256));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return calls;
|
|
63
|
+
}
|
|
12
64
|
// ==================== 批量授权 ====================
|
|
13
65
|
/**
|
|
14
66
|
* Bundle 批量授权
|
|
15
67
|
*
|
|
16
68
|
* 将多个钱包对所有 Spender 的授权打包成一笔交易
|
|
17
69
|
* 第一个钱包作为 Sponsor,支付 gas
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // 只授权代币给所有路由
|
|
74
|
+
* await bundleBatchApprove({
|
|
75
|
+
* rpcUrl,
|
|
76
|
+
* tokenAddress,
|
|
77
|
+
* wallets: [{ privateKey: pk1 }, { privateKey: pk2 }],
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* // 授权代币 + WOKB
|
|
81
|
+
* await bundleBatchApprove({
|
|
82
|
+
* rpcUrl,
|
|
83
|
+
* tokenAddress,
|
|
84
|
+
* wallets: [{ privateKey: pk1 }],
|
|
85
|
+
* includeWokb: true,
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* // 完整授权(代币 + WOKB + 稳定币)
|
|
89
|
+
* await bundleBatchApprove({
|
|
90
|
+
* rpcUrl,
|
|
91
|
+
* tokenAddress,
|
|
92
|
+
* wallets: [{ privateKey: pk1 }],
|
|
93
|
+
* mode: 'full',
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
18
96
|
*/
|
|
19
97
|
export async function bundleBatchApprove(params) {
|
|
20
|
-
const { rpcUrl, tokenAddress, wallets, gasPrice } = params;
|
|
98
|
+
const { rpcUrl, tokenAddress, wallets, gasPrice, target = 'all', mode = 'token', includeWokb = false, includeStablecoins = false, } = params;
|
|
21
99
|
if (wallets.length === 0) {
|
|
22
100
|
return { success: false, successCount: 0, totalCount: 0, error: '没有钱包' };
|
|
23
101
|
}
|
|
24
102
|
const bundle = new XLayerBundle({ rpcUrl, gasPrice });
|
|
25
|
-
|
|
103
|
+
const calls = buildApproveCalls(tokenAddress, target, mode, includeWokb, includeStablecoins);
|
|
104
|
+
// 构建参与者:每个钱包执行相同的授权 calls
|
|
26
105
|
const participants = wallets.map(w => ({
|
|
27
106
|
wallet: w.privateKey,
|
|
28
|
-
calls
|
|
107
|
+
calls,
|
|
29
108
|
}));
|
|
30
|
-
|
|
109
|
+
const spenders = getSpenders(target);
|
|
110
|
+
console.log(`[Bundle授权] 执行批量授权: ${wallets.length} 个钱包, ${spenders.length} 个 Spender, ${calls.length / wallets.length} 个授权/钱包`);
|
|
111
|
+
console.log(`[Bundle授权] 模式: ${mode}, 目标: ${target}, WOKB: ${includeWokb}, 稳定币: ${includeStablecoins}`);
|
|
31
112
|
console.log(`[Bundle授权] 第一个钱包作为 Sponsor,支付 gas`);
|
|
32
113
|
const result = await bundle.execute(participants);
|
|
33
114
|
return {
|
|
@@ -36,6 +117,19 @@ export async function bundleBatchApprove(params) {
|
|
|
36
117
|
totalCount: wallets.length,
|
|
37
118
|
};
|
|
38
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Bundle 完整授权(代币 + WOKB + 稳定币)
|
|
122
|
+
*
|
|
123
|
+
* 一次性完成所有授权,跟 BSC 链一样
|
|
124
|
+
*/
|
|
125
|
+
export async function bundleFullApprove(params) {
|
|
126
|
+
return bundleBatchApprove({
|
|
127
|
+
...params,
|
|
128
|
+
mode: 'full',
|
|
129
|
+
includeWokb: true,
|
|
130
|
+
includeStablecoins: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
39
133
|
/**
|
|
40
134
|
* Bundle 单钱包授权
|
|
41
135
|
*
|
|
@@ -48,11 +142,27 @@ export async function bundleSingleApprove(params) {
|
|
|
48
142
|
tokenAddress: params.tokenAddress,
|
|
49
143
|
wallets: [{ privateKey: params.walletPrivateKey }],
|
|
50
144
|
gasPrice: params.gasPrice,
|
|
145
|
+
target: params.target,
|
|
146
|
+
mode: params.mode,
|
|
147
|
+
includeWokb: params.includeWokb,
|
|
148
|
+
includeStablecoins: params.includeStablecoins,
|
|
51
149
|
});
|
|
52
150
|
}
|
|
53
151
|
/**
|
|
54
152
|
* 获取需要授权的 Spender 列表
|
|
55
153
|
*/
|
|
56
|
-
export function getSpendersToApprove() {
|
|
57
|
-
return
|
|
154
|
+
export function getSpendersToApprove(target = 'all') {
|
|
155
|
+
return getSpenders(target);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 获取基础代币列表(WOKB + 稳定币)
|
|
159
|
+
*/
|
|
160
|
+
export function getBaseTokensToApprove() {
|
|
161
|
+
return BASE_TOKENS_TO_APPROVE;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 获取稳定币列表
|
|
165
|
+
*/
|
|
166
|
+
export function getStablecoins() {
|
|
167
|
+
return [USDT, USDC, USDT0];
|
|
58
168
|
}
|
|
@@ -17,8 +17,7 @@ function toRlpHex(value) {
|
|
|
17
17
|
if (value === 0 || value === 0n) {
|
|
18
18
|
return '0x';
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
return hex.replace(/^0x0+/, '0x') || '0x';
|
|
20
|
+
return ethers.toBeHex(value);
|
|
22
21
|
}
|
|
23
22
|
// ==================== 工具函数 ====================
|
|
24
23
|
/** 计算授权消息哈希 */
|
|
@@ -35,11 +34,12 @@ function hashAuthorization(chainId, executorAddress, nonce) {
|
|
|
35
34
|
async function signAuthorization(wallet, nonce) {
|
|
36
35
|
const messageHash = hashAuthorization(XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, nonce);
|
|
37
36
|
const signature = wallet.signingKey.sign(messageHash);
|
|
37
|
+
// 注意:EIP-7702 授权签名使用 yParity (0 或 1),不是 v (27 或 28)
|
|
38
38
|
return {
|
|
39
39
|
chainId: XLAYER_CHAIN_ID,
|
|
40
40
|
address: EXECUTOR_ADDRESS,
|
|
41
41
|
nonce,
|
|
42
|
-
v: signature.
|
|
42
|
+
v: signature.yParity,
|
|
43
43
|
r: signature.r,
|
|
44
44
|
s: signature.s,
|
|
45
45
|
};
|
|
@@ -208,17 +208,17 @@ export class XLayerBundle {
|
|
|
208
208
|
]);
|
|
209
209
|
const txHash = ethers.keccak256(unsigned);
|
|
210
210
|
const signature = sponsor.signingKey.sign(txHash);
|
|
211
|
-
//
|
|
211
|
+
// 添加签名(注意:Type 4 交易使用 yParity (0 或 1),不是 v (27 或 28))
|
|
212
212
|
const signedFields = [
|
|
213
213
|
...fields,
|
|
214
|
-
toRlpHex(signature.
|
|
214
|
+
toRlpHex(signature.yParity),
|
|
215
215
|
signature.r,
|
|
216
216
|
signature.s,
|
|
217
217
|
];
|
|
218
|
-
return ethers.concat([
|
|
218
|
+
return ethers.hexlify(ethers.concat([
|
|
219
219
|
new Uint8Array([0x04]),
|
|
220
220
|
ethers.encodeRlp(signedFields),
|
|
221
|
-
]);
|
|
221
|
+
]));
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
// ==================== 便捷函数 ====================
|
|
@@ -1,26 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* X-Layer Bundle 授权检查
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* ✅ 完整功能:
|
|
5
|
+
* - 批量检查授权状态(使用 Multicall3 优化)
|
|
6
|
+
* - 检查所有路由(Flap + V2 + V3)
|
|
7
|
+
* - 检查基础代币(WOKB + 稳定币)
|
|
8
|
+
* - 返回详细授权信息
|
|
5
9
|
*/
|
|
10
|
+
/** 授权目标类型 */
|
|
11
|
+
export type ApprovalTarget = 'all' | 'flap' | 'v2' | 'v3' | 'v3-02';
|
|
12
|
+
/** 授权状态详情 */
|
|
13
|
+
export interface ApprovalStatusDetail {
|
|
14
|
+
/** 钱包地址 */
|
|
15
|
+
walletAddress: string;
|
|
16
|
+
/** 是否所有 Spender 都已授权 */
|
|
17
|
+
isFullyApproved: boolean;
|
|
18
|
+
/** 每个 Spender 的授权详情 */
|
|
19
|
+
spenderDetails: Array<{
|
|
20
|
+
spender: string;
|
|
21
|
+
spenderName: string;
|
|
22
|
+
isApproved: boolean;
|
|
23
|
+
currentAllowance: bigint;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/** 批量授权检查结果 */
|
|
27
|
+
export interface BatchApprovalCheckResult {
|
|
28
|
+
/** 代币地址 */
|
|
29
|
+
tokenAddress: string;
|
|
30
|
+
/** 钱包总数 */
|
|
31
|
+
totalWallets: number;
|
|
32
|
+
/** 已完全授权的钱包数 */
|
|
33
|
+
fullyApprovedCount: number;
|
|
34
|
+
/** 需要授权的钱包数 */
|
|
35
|
+
needApprovalCount: number;
|
|
36
|
+
/** 详细信息 */
|
|
37
|
+
details: ApprovalStatusDetail[];
|
|
38
|
+
}
|
|
39
|
+
/** 完整授权检查结果(包含基础代币) */
|
|
40
|
+
export interface FullApprovalCheckResult {
|
|
41
|
+
/** 代币授权状态 */
|
|
42
|
+
token: BatchApprovalCheckResult;
|
|
43
|
+
/** WOKB 授权状态 */
|
|
44
|
+
wokb: BatchApprovalCheckResult;
|
|
45
|
+
/** 稳定币授权状态 */
|
|
46
|
+
stablecoins: {
|
|
47
|
+
usdt: BatchApprovalCheckResult;
|
|
48
|
+
usdc: BatchApprovalCheckResult;
|
|
49
|
+
usdt0: BatchApprovalCheckResult;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
6
52
|
/**
|
|
7
53
|
* 检查钱包是否已授权(检查所有 Spender)
|
|
8
54
|
*
|
|
9
55
|
* @param rpcUrl RPC URL
|
|
10
56
|
* @param tokenAddress 代币地址
|
|
11
57
|
* @param walletAddress 钱包地址
|
|
58
|
+
* @param target 授权目标(默认 'all')
|
|
12
59
|
* @returns 是否所有 Spender 都已授权
|
|
13
60
|
*/
|
|
14
|
-
export declare function checkApprovalStatus(rpcUrl: string, tokenAddress: string, walletAddress: string): Promise<boolean>;
|
|
61
|
+
export declare function checkApprovalStatus(rpcUrl: string, tokenAddress: string, walletAddress: string, target?: ApprovalTarget): Promise<boolean>;
|
|
15
62
|
/**
|
|
16
|
-
*
|
|
63
|
+
* 批量检查授权状态(使用 Multicall3 优化)
|
|
64
|
+
*
|
|
65
|
+
* ✅ 跟 BSC 链一样,返回详细的授权信息
|
|
66
|
+
*
|
|
67
|
+
* @param rpcUrl RPC URL
|
|
68
|
+
* @param tokenAddress 代币地址
|
|
69
|
+
* @param walletAddresses 钱包地址列表
|
|
70
|
+
* @param target 授权目标(默认 'all')
|
|
71
|
+
* @returns 批量授权检查结果
|
|
72
|
+
*/
|
|
73
|
+
export declare function checkApprovalStatusBatch(rpcUrl: string, tokenAddress: string, walletAddresses: string[], target?: ApprovalTarget): Promise<BatchApprovalCheckResult>;
|
|
74
|
+
/**
|
|
75
|
+
* 批量检查授权状态(简化版,返回 Map)
|
|
17
76
|
*
|
|
18
77
|
* @param rpcUrl RPC URL
|
|
19
78
|
* @param tokenAddress 代币地址
|
|
20
79
|
* @param walletAddresses 钱包地址列表
|
|
80
|
+
* @param target 授权目标
|
|
21
81
|
* @returns 钱包地址 -> 是否已授权
|
|
22
82
|
*/
|
|
23
|
-
export declare function
|
|
83
|
+
export declare function checkApprovalStatusBatchSimple(rpcUrl: string, tokenAddress: string, walletAddresses: string[], target?: ApprovalTarget): Promise<Map<string, boolean>>;
|
|
24
84
|
/**
|
|
25
85
|
* 检查单个 Spender 的授权状态
|
|
26
86
|
*
|
|
@@ -37,15 +97,54 @@ export declare function checkSingleApprovalStatus(rpcUrl: string, tokenAddress:
|
|
|
37
97
|
* @param rpcUrl RPC URL
|
|
38
98
|
* @param tokenAddress 代币地址
|
|
39
99
|
* @param walletAddress 钱包地址
|
|
100
|
+
* @param target 授权目标
|
|
40
101
|
* @returns Spender 地址 -> 授权额度
|
|
41
102
|
*/
|
|
42
|
-
export declare function getApprovalDetails(rpcUrl: string, tokenAddress: string, walletAddress: string): Promise<Map<string, bigint>>;
|
|
103
|
+
export declare function getApprovalDetails(rpcUrl: string, tokenAddress: string, walletAddress: string, target?: ApprovalTarget): Promise<Map<string, bigint>>;
|
|
43
104
|
/**
|
|
44
105
|
* 获取需要授权的 Spender 列表(未授权的)
|
|
45
106
|
*
|
|
46
107
|
* @param rpcUrl RPC URL
|
|
47
108
|
* @param tokenAddress 代币地址
|
|
48
109
|
* @param walletAddress 钱包地址
|
|
110
|
+
* @param target 授权目标
|
|
49
111
|
* @returns 未授权的 Spender 地址列表
|
|
50
112
|
*/
|
|
51
|
-
export declare function getUnapprovedSpenders(rpcUrl: string, tokenAddress: string, walletAddress: string): Promise<string[]>;
|
|
113
|
+
export declare function getUnapprovedSpenders(rpcUrl: string, tokenAddress: string, walletAddress: string, target?: ApprovalTarget): Promise<string[]>;
|
|
114
|
+
/**
|
|
115
|
+
* 获取需要授权的钱包列表
|
|
116
|
+
*
|
|
117
|
+
* @param rpcUrl RPC URL
|
|
118
|
+
* @param tokenAddress 代币地址
|
|
119
|
+
* @param walletAddresses 钱包地址列表
|
|
120
|
+
* @param target 授权目标
|
|
121
|
+
* @returns 需要授权的钱包地址列表
|
|
122
|
+
*/
|
|
123
|
+
export declare function getWalletsNeedApproval(rpcUrl: string, tokenAddress: string, walletAddresses: string[], target?: ApprovalTarget): Promise<string[]>;
|
|
124
|
+
/**
|
|
125
|
+
* 完整授权检查(代币 + WOKB + 稳定币)
|
|
126
|
+
*
|
|
127
|
+
* ✅ 跟 BSC 链一样,一次性检查所有授权状态
|
|
128
|
+
*
|
|
129
|
+
* @param rpcUrl RPC URL
|
|
130
|
+
* @param tokenAddress 代币地址
|
|
131
|
+
* @param walletAddresses 钱包地址列表
|
|
132
|
+
* @param target 授权目标
|
|
133
|
+
* @returns 完整授权检查结果
|
|
134
|
+
*/
|
|
135
|
+
export declare function checkFullApprovalStatus(rpcUrl: string, tokenAddress: string, walletAddresses: string[], target?: ApprovalTarget): Promise<FullApprovalCheckResult>;
|
|
136
|
+
/**
|
|
137
|
+
* 获取授权汇总信息
|
|
138
|
+
*
|
|
139
|
+
* @param rpcUrl RPC URL
|
|
140
|
+
* @param tokenAddress 代币地址
|
|
141
|
+
* @param walletAddresses 钱包地址列表
|
|
142
|
+
* @returns 授权汇总
|
|
143
|
+
*/
|
|
144
|
+
export declare function getApprovalSummary(rpcUrl: string, tokenAddress: string, walletAddresses: string[]): Promise<{
|
|
145
|
+
totalWallets: number;
|
|
146
|
+
tokenApproved: number;
|
|
147
|
+
wokbApproved: number;
|
|
148
|
+
allApproved: number;
|
|
149
|
+
needApproval: number;
|
|
150
|
+
}>;
|
|
@@ -1,52 +1,211 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* X-Layer Bundle 授权检查
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* ✅ 完整功能:
|
|
5
|
+
* - 批量检查授权状态(使用 Multicall3 优化)
|
|
6
|
+
* - 检查所有路由(Flap + V2 + V3)
|
|
7
|
+
* - 检查基础代币(WOKB + 稳定币)
|
|
8
|
+
* - 返回详细授权信息
|
|
5
9
|
*/
|
|
6
10
|
import { JsonRpcProvider, Interface, Contract } from 'ethers';
|
|
7
|
-
import { SPENDERS_TO_APPROVE, ERC20_ABI } from './constants.js';
|
|
8
|
-
// ==================== 授权检查 ====================
|
|
11
|
+
import { SPENDERS_TO_APPROVE, ERC20_ABI, WOKB, USDT, USDC, USDT0, FLAP_PORTAL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_SWAP_ROUTER02, } from './constants.js';
|
|
9
12
|
/** 最小授权额度(2^128) */
|
|
10
13
|
const MIN_ALLOWANCE = BigInt('0x100000000000000000000000000000000');
|
|
14
|
+
/** Multicall3 地址(通用) */
|
|
15
|
+
const MULTICALL3_ADDRESS = '0xca11bde05977b3631167028862be2a173976ca11';
|
|
16
|
+
/** Multicall3 ABI */
|
|
17
|
+
const MULTICALL3_ABI = [
|
|
18
|
+
'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) public payable returns (tuple(bool success, bytes returnData)[])',
|
|
19
|
+
];
|
|
20
|
+
// ==================== 辅助函数 ====================
|
|
21
|
+
/**
|
|
22
|
+
* 获取 Spender 名称
|
|
23
|
+
*/
|
|
24
|
+
function getSpenderName(spender) {
|
|
25
|
+
const names = {
|
|
26
|
+
[FLAP_PORTAL.toLowerCase()]: 'Flap Portal (内盘)',
|
|
27
|
+
[POTATOSWAP_V2_ROUTER.toLowerCase()]: 'PotatoSwap V2 Router',
|
|
28
|
+
[POTATOSWAP_V3_ROUTER.toLowerCase()]: 'PotatoSwap V3 Router',
|
|
29
|
+
[POTATOSWAP_SWAP_ROUTER02.toLowerCase()]: 'PotatoSwap SwapRouter02',
|
|
30
|
+
};
|
|
31
|
+
return names[spender.toLowerCase()] || spender;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 获取目标 Spender 列表
|
|
35
|
+
*/
|
|
36
|
+
function getSpenders(target = 'all') {
|
|
37
|
+
switch (target) {
|
|
38
|
+
case 'flap':
|
|
39
|
+
return [FLAP_PORTAL];
|
|
40
|
+
case 'v2':
|
|
41
|
+
return [POTATOSWAP_V2_ROUTER];
|
|
42
|
+
case 'v3':
|
|
43
|
+
return [POTATOSWAP_V3_ROUTER];
|
|
44
|
+
case 'v3-02':
|
|
45
|
+
return [POTATOSWAP_SWAP_ROUTER02];
|
|
46
|
+
case 'all':
|
|
47
|
+
default:
|
|
48
|
+
return SPENDERS_TO_APPROVE;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ==================== Multicall3 批量查询 ====================
|
|
52
|
+
/**
|
|
53
|
+
* 使用 Multicall3 批量查询授权额度
|
|
54
|
+
*
|
|
55
|
+
* @param provider Provider
|
|
56
|
+
* @param tokenAddress 代币地址
|
|
57
|
+
* @param walletAddresses 钱包地址列表
|
|
58
|
+
* @param spenderAddresses Spender 地址列表
|
|
59
|
+
* @returns 二维数组 [wallet][spender] = allowance
|
|
60
|
+
*/
|
|
61
|
+
async function batchCheckAllowancesMulticall(provider, tokenAddress, walletAddresses, spenderAddresses) {
|
|
62
|
+
if (walletAddresses.length === 0 || spenderAddresses.length === 0) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const erc20Interface = new Interface(ERC20_ABI);
|
|
66
|
+
const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
67
|
+
// 构建 Multicall 调用
|
|
68
|
+
const calls = [];
|
|
69
|
+
for (const wallet of walletAddresses) {
|
|
70
|
+
for (const spender of spenderAddresses) {
|
|
71
|
+
calls.push({
|
|
72
|
+
target: tokenAddress,
|
|
73
|
+
allowFailure: true,
|
|
74
|
+
callData: erc20Interface.encodeFunctionData('allowance', [wallet, spender]),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const results = await multicall.aggregate3.staticCall(calls);
|
|
80
|
+
// 解析结果
|
|
81
|
+
const allowances = [];
|
|
82
|
+
let idx = 0;
|
|
83
|
+
for (let w = 0; w < walletAddresses.length; w++) {
|
|
84
|
+
const walletAllowances = [];
|
|
85
|
+
for (let s = 0; s < spenderAddresses.length; s++) {
|
|
86
|
+
const result = results[idx++];
|
|
87
|
+
if (result.success) {
|
|
88
|
+
const decoded = erc20Interface.decodeFunctionResult('allowance', result.returnData);
|
|
89
|
+
walletAllowances.push(BigInt(decoded[0]));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
walletAllowances.push(0n);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
allowances.push(walletAllowances);
|
|
96
|
+
}
|
|
97
|
+
return allowances;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Multicall 失败时,回退到逐个查询
|
|
101
|
+
console.warn('[Multicall] 失败,回退到逐个查询:', error);
|
|
102
|
+
return batchCheckAllowancesFallback(provider, tokenAddress, walletAddresses, spenderAddresses);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 逐个查询授权额度(Multicall 失败时的备用方案)
|
|
107
|
+
*/
|
|
108
|
+
async function batchCheckAllowancesFallback(provider, tokenAddress, walletAddresses, spenderAddresses) {
|
|
109
|
+
const erc20Interface = new Interface(ERC20_ABI);
|
|
110
|
+
const contract = new Contract(tokenAddress, erc20Interface, provider);
|
|
111
|
+
const allowances = [];
|
|
112
|
+
for (const wallet of walletAddresses) {
|
|
113
|
+
const walletAllowances = [];
|
|
114
|
+
for (const spender of spenderAddresses) {
|
|
115
|
+
try {
|
|
116
|
+
const allowance = await contract.allowance(wallet, spender);
|
|
117
|
+
walletAllowances.push(BigInt(allowance));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
walletAllowances.push(0n);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
allowances.push(walletAllowances);
|
|
124
|
+
}
|
|
125
|
+
return allowances;
|
|
126
|
+
}
|
|
127
|
+
// ==================== 授权检查函数 ====================
|
|
11
128
|
/**
|
|
12
129
|
* 检查钱包是否已授权(检查所有 Spender)
|
|
13
130
|
*
|
|
14
131
|
* @param rpcUrl RPC URL
|
|
15
132
|
* @param tokenAddress 代币地址
|
|
16
133
|
* @param walletAddress 钱包地址
|
|
134
|
+
* @param target 授权目标(默认 'all')
|
|
17
135
|
* @returns 是否所有 Spender 都已授权
|
|
18
136
|
*/
|
|
19
|
-
export async function checkApprovalStatus(rpcUrl, tokenAddress, walletAddress) {
|
|
137
|
+
export async function checkApprovalStatus(rpcUrl, tokenAddress, walletAddress, target = 'all') {
|
|
20
138
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const results = await Promise.all(SPENDERS_TO_APPROVE.map(async (spender) => {
|
|
25
|
-
const allowance = await contract.allowance(walletAddress, spender);
|
|
26
|
-
return BigInt(allowance) >= MIN_ALLOWANCE;
|
|
27
|
-
}));
|
|
28
|
-
// 所有 Spender 都必须已授权
|
|
29
|
-
return results.every(Boolean);
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
139
|
+
const spenders = getSpenders(target);
|
|
140
|
+
const allowances = await batchCheckAllowancesMulticall(provider, tokenAddress, [walletAddress], spenders);
|
|
141
|
+
if (allowances.length === 0 || allowances[0].length === 0) {
|
|
32
142
|
return false;
|
|
33
143
|
}
|
|
144
|
+
return allowances[0].every(a => a >= MIN_ALLOWANCE);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 批量检查授权状态(使用 Multicall3 优化)
|
|
148
|
+
*
|
|
149
|
+
* ✅ 跟 BSC 链一样,返回详细的授权信息
|
|
150
|
+
*
|
|
151
|
+
* @param rpcUrl RPC URL
|
|
152
|
+
* @param tokenAddress 代币地址
|
|
153
|
+
* @param walletAddresses 钱包地址列表
|
|
154
|
+
* @param target 授权目标(默认 'all')
|
|
155
|
+
* @returns 批量授权检查结果
|
|
156
|
+
*/
|
|
157
|
+
export async function checkApprovalStatusBatch(rpcUrl, tokenAddress, walletAddresses, target = 'all') {
|
|
158
|
+
if (walletAddresses.length === 0) {
|
|
159
|
+
return {
|
|
160
|
+
tokenAddress,
|
|
161
|
+
totalWallets: 0,
|
|
162
|
+
fullyApprovedCount: 0,
|
|
163
|
+
needApprovalCount: 0,
|
|
164
|
+
details: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const provider = new JsonRpcProvider(rpcUrl);
|
|
168
|
+
const spenders = getSpenders(target);
|
|
169
|
+
const allowances = await batchCheckAllowancesMulticall(provider, tokenAddress, walletAddresses, spenders);
|
|
170
|
+
const details = walletAddresses.map((wallet, wIdx) => {
|
|
171
|
+
const walletAllowances = allowances[wIdx] || [];
|
|
172
|
+
const spenderDetails = spenders.map((spender, sIdx) => ({
|
|
173
|
+
spender,
|
|
174
|
+
spenderName: getSpenderName(spender),
|
|
175
|
+
isApproved: walletAllowances[sIdx] >= MIN_ALLOWANCE,
|
|
176
|
+
currentAllowance: walletAllowances[sIdx] || 0n,
|
|
177
|
+
}));
|
|
178
|
+
return {
|
|
179
|
+
walletAddress: wallet,
|
|
180
|
+
isFullyApproved: spenderDetails.every(d => d.isApproved),
|
|
181
|
+
spenderDetails,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
const fullyApprovedCount = details.filter(d => d.isFullyApproved).length;
|
|
185
|
+
return {
|
|
186
|
+
tokenAddress,
|
|
187
|
+
totalWallets: walletAddresses.length,
|
|
188
|
+
fullyApprovedCount,
|
|
189
|
+
needApprovalCount: walletAddresses.length - fullyApprovedCount,
|
|
190
|
+
details,
|
|
191
|
+
};
|
|
34
192
|
}
|
|
35
193
|
/**
|
|
36
|
-
*
|
|
194
|
+
* 批量检查授权状态(简化版,返回 Map)
|
|
37
195
|
*
|
|
38
196
|
* @param rpcUrl RPC URL
|
|
39
197
|
* @param tokenAddress 代币地址
|
|
40
198
|
* @param walletAddresses 钱包地址列表
|
|
199
|
+
* @param target 授权目标
|
|
41
200
|
* @returns 钱包地址 -> 是否已授权
|
|
42
201
|
*/
|
|
43
|
-
export async function
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
return
|
|
202
|
+
export async function checkApprovalStatusBatchSimple(rpcUrl, tokenAddress, walletAddresses, target = 'all') {
|
|
203
|
+
const result = await checkApprovalStatusBatch(rpcUrl, tokenAddress, walletAddresses, target);
|
|
204
|
+
const map = new Map();
|
|
205
|
+
for (const detail of result.details) {
|
|
206
|
+
map.set(detail.walletAddress.toLowerCase(), detail.isFullyApproved);
|
|
207
|
+
}
|
|
208
|
+
return map;
|
|
50
209
|
}
|
|
51
210
|
/**
|
|
52
211
|
* 检查单个 Spender 的授权状态
|
|
@@ -75,22 +234,18 @@ export async function checkSingleApprovalStatus(rpcUrl, tokenAddress, walletAddr
|
|
|
75
234
|
* @param rpcUrl RPC URL
|
|
76
235
|
* @param tokenAddress 代币地址
|
|
77
236
|
* @param walletAddress 钱包地址
|
|
237
|
+
* @param target 授权目标
|
|
78
238
|
* @returns Spender 地址 -> 授权额度
|
|
79
239
|
*/
|
|
80
|
-
export async function getApprovalDetails(rpcUrl, tokenAddress, walletAddress) {
|
|
240
|
+
export async function getApprovalDetails(rpcUrl, tokenAddress, walletAddress, target = 'all') {
|
|
81
241
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
242
|
+
const spenders = getSpenders(target);
|
|
243
|
+
const allowances = await batchCheckAllowancesMulticall(provider, tokenAddress, [walletAddress], spenders);
|
|
84
244
|
const results = new Map();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
results.set(spender.toLowerCase(), 0n);
|
|
92
|
-
}
|
|
93
|
-
}));
|
|
245
|
+
const walletAllowances = allowances[0] || [];
|
|
246
|
+
spenders.forEach((spender, idx) => {
|
|
247
|
+
results.set(spender.toLowerCase(), walletAllowances[idx] || 0n);
|
|
248
|
+
});
|
|
94
249
|
return results;
|
|
95
250
|
}
|
|
96
251
|
/**
|
|
@@ -99,12 +254,14 @@ export async function getApprovalDetails(rpcUrl, tokenAddress, walletAddress) {
|
|
|
99
254
|
* @param rpcUrl RPC URL
|
|
100
255
|
* @param tokenAddress 代币地址
|
|
101
256
|
* @param walletAddress 钱包地址
|
|
257
|
+
* @param target 授权目标
|
|
102
258
|
* @returns 未授权的 Spender 地址列表
|
|
103
259
|
*/
|
|
104
|
-
export async function getUnapprovedSpenders(rpcUrl, tokenAddress, walletAddress) {
|
|
105
|
-
const details = await getApprovalDetails(rpcUrl, tokenAddress, walletAddress);
|
|
260
|
+
export async function getUnapprovedSpenders(rpcUrl, tokenAddress, walletAddress, target = 'all') {
|
|
261
|
+
const details = await getApprovalDetails(rpcUrl, tokenAddress, walletAddress, target);
|
|
262
|
+
const spenders = getSpenders(target);
|
|
106
263
|
const unapproved = [];
|
|
107
|
-
for (const spender of
|
|
264
|
+
for (const spender of spenders) {
|
|
108
265
|
const allowance = details.get(spender.toLowerCase()) || 0n;
|
|
109
266
|
if (allowance < MIN_ALLOWANCE) {
|
|
110
267
|
unapproved.push(spender);
|
|
@@ -112,3 +269,78 @@ export async function getUnapprovedSpenders(rpcUrl, tokenAddress, walletAddress)
|
|
|
112
269
|
}
|
|
113
270
|
return unapproved;
|
|
114
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* 获取需要授权的钱包列表
|
|
274
|
+
*
|
|
275
|
+
* @param rpcUrl RPC URL
|
|
276
|
+
* @param tokenAddress 代币地址
|
|
277
|
+
* @param walletAddresses 钱包地址列表
|
|
278
|
+
* @param target 授权目标
|
|
279
|
+
* @returns 需要授权的钱包地址列表
|
|
280
|
+
*/
|
|
281
|
+
export async function getWalletsNeedApproval(rpcUrl, tokenAddress, walletAddresses, target = 'all') {
|
|
282
|
+
const result = await checkApprovalStatusBatch(rpcUrl, tokenAddress, walletAddresses, target);
|
|
283
|
+
return result.details
|
|
284
|
+
.filter(d => !d.isFullyApproved)
|
|
285
|
+
.map(d => d.walletAddress);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 完整授权检查(代币 + WOKB + 稳定币)
|
|
289
|
+
*
|
|
290
|
+
* ✅ 跟 BSC 链一样,一次性检查所有授权状态
|
|
291
|
+
*
|
|
292
|
+
* @param rpcUrl RPC URL
|
|
293
|
+
* @param tokenAddress 代币地址
|
|
294
|
+
* @param walletAddresses 钱包地址列表
|
|
295
|
+
* @param target 授权目标
|
|
296
|
+
* @returns 完整授权检查结果
|
|
297
|
+
*/
|
|
298
|
+
export async function checkFullApprovalStatus(rpcUrl, tokenAddress, walletAddresses, target = 'all') {
|
|
299
|
+
// 并行检查所有代币的授权状态
|
|
300
|
+
const [token, wokb, usdt, usdc, usdt0] = await Promise.all([
|
|
301
|
+
checkApprovalStatusBatch(rpcUrl, tokenAddress, walletAddresses, target),
|
|
302
|
+
checkApprovalStatusBatch(rpcUrl, WOKB, walletAddresses, target),
|
|
303
|
+
checkApprovalStatusBatch(rpcUrl, USDT, walletAddresses, target),
|
|
304
|
+
checkApprovalStatusBatch(rpcUrl, USDC, walletAddresses, target),
|
|
305
|
+
checkApprovalStatusBatch(rpcUrl, USDT0, walletAddresses, target),
|
|
306
|
+
]);
|
|
307
|
+
return {
|
|
308
|
+
token,
|
|
309
|
+
wokb,
|
|
310
|
+
stablecoins: {
|
|
311
|
+
usdt,
|
|
312
|
+
usdc,
|
|
313
|
+
usdt0,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 获取授权汇总信息
|
|
319
|
+
*
|
|
320
|
+
* @param rpcUrl RPC URL
|
|
321
|
+
* @param tokenAddress 代币地址
|
|
322
|
+
* @param walletAddresses 钱包地址列表
|
|
323
|
+
* @returns 授权汇总
|
|
324
|
+
*/
|
|
325
|
+
export async function getApprovalSummary(rpcUrl, tokenAddress, walletAddresses) {
|
|
326
|
+
const [tokenResult, wokbResult] = await Promise.all([
|
|
327
|
+
checkApprovalStatusBatch(rpcUrl, tokenAddress, walletAddresses),
|
|
328
|
+
checkApprovalStatusBatch(rpcUrl, WOKB, walletAddresses),
|
|
329
|
+
]);
|
|
330
|
+
const tokenApprovedSet = new Set(tokenResult.details.filter(d => d.isFullyApproved).map(d => d.walletAddress.toLowerCase()));
|
|
331
|
+
const wokbApprovedSet = new Set(wokbResult.details.filter(d => d.isFullyApproved).map(d => d.walletAddress.toLowerCase()));
|
|
332
|
+
let allApproved = 0;
|
|
333
|
+
for (const wallet of walletAddresses) {
|
|
334
|
+
const lowerWallet = wallet.toLowerCase();
|
|
335
|
+
if (tokenApprovedSet.has(lowerWallet) && wokbApprovedSet.has(lowerWallet)) {
|
|
336
|
+
allApproved++;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
totalWallets: walletAddresses.length,
|
|
341
|
+
tokenApproved: tokenResult.fullyApprovedCount,
|
|
342
|
+
wokbApproved: wokbResult.fullyApprovedCount,
|
|
343
|
+
allApproved,
|
|
344
|
+
needApproval: walletAddresses.length - allApproved,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
@@ -14,8 +14,19 @@ export declare const POTATOSWAP_V3_ROUTER = "0xB45D0149249488333E3F3f9F359807F4b
|
|
|
14
14
|
export declare const POTATOSWAP_SWAP_ROUTER02 = "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
|
|
15
15
|
/** Flap Portal */
|
|
16
16
|
export declare const FLAP_PORTAL = "0xb30D8c4216E1f21F27444D2FfAee3ad577808678";
|
|
17
|
-
/**
|
|
17
|
+
/** USDT 地址 (6 位精度) */
|
|
18
|
+
export declare const USDT = "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
|
|
19
|
+
/** USDC 地址 (6 位精度) */
|
|
20
|
+
export declare const USDC = "0x74b7f16337b8972027f6196a17a631ac6de26d22";
|
|
21
|
+
/** USDT0 地址 (6 位精度) - Flap xLayer 支持的稳定币 */
|
|
22
|
+
export declare const USDT0 = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
23
|
+
/** 需要授权的所有 Spender 列表(代币授权给这些路由) */
|
|
18
24
|
export declare const SPENDERS_TO_APPROVE: readonly ["0xb30D8c4216E1f21F27444D2FfAee3ad577808678", "0x881fb2f98c13d521009464e7d1cbf16e1b394e8e", "0xB45D0149249488333E3F3f9F359807F4b810C1FC", "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41"];
|
|
25
|
+
/**
|
|
26
|
+
* 需要授权给路由的基础代币(WOKB + 稳定币)
|
|
27
|
+
* 用于:卖出代币换 WOKB、稳定币交易对等
|
|
28
|
+
*/
|
|
29
|
+
export declare const BASE_TOKENS_TO_APPROVE: readonly ["0xe538905cf8410324e03a5a23c1c177a474d59b2b", "0x1e4a5963abfd975d8c9021ce480b42188849d41d", "0x74b7f16337b8972027f6196a17a631ac6de26d22", "0x779ded0c9e1022225f8e0630b35a9b54be713736"];
|
|
19
30
|
/** MaxUint256 用于无限授权 */
|
|
20
31
|
export declare const MAX_UINT256: bigint;
|
|
21
32
|
/** X-Layer 专属利润配置 */
|
|
@@ -15,13 +15,31 @@ export const POTATOSWAP_V3_ROUTER = '0xB45D0149249488333E3F3f9F359807F4b810C1FC'
|
|
|
15
15
|
export const POTATOSWAP_SWAP_ROUTER02 = '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41';
|
|
16
16
|
/** Flap Portal */
|
|
17
17
|
export const FLAP_PORTAL = '0xb30D8c4216E1f21F27444D2FfAee3ad577808678';
|
|
18
|
-
|
|
18
|
+
// ==================== 稳定币地址 ====================
|
|
19
|
+
/** USDT 地址 (6 位精度) */
|
|
20
|
+
export const USDT = '0x1e4a5963abfd975d8c9021ce480b42188849d41d';
|
|
21
|
+
/** USDC 地址 (6 位精度) */
|
|
22
|
+
export const USDC = '0x74b7f16337b8972027f6196a17a631ac6de26d22';
|
|
23
|
+
/** USDT0 地址 (6 位精度) - Flap xLayer 支持的稳定币 */
|
|
24
|
+
export const USDT0 = '0x779ded0c9e1022225f8e0630b35a9b54be713736';
|
|
25
|
+
// ==================== 授权配置 ====================
|
|
26
|
+
/** 需要授权的所有 Spender 列表(代币授权给这些路由) */
|
|
19
27
|
export const SPENDERS_TO_APPROVE = [
|
|
20
28
|
FLAP_PORTAL, // 内盘交易
|
|
21
29
|
POTATOSWAP_V2_ROUTER, // V2 外盘交易
|
|
22
|
-
POTATOSWAP_V3_ROUTER, // V3
|
|
30
|
+
POTATOSWAP_V3_ROUTER, // V3 外盘交易(Legacy SwapRouter)
|
|
23
31
|
POTATOSWAP_SWAP_ROUTER02, // V3 SwapRouter02
|
|
24
32
|
];
|
|
33
|
+
/**
|
|
34
|
+
* 需要授权给路由的基础代币(WOKB + 稳定币)
|
|
35
|
+
* 用于:卖出代币换 WOKB、稳定币交易对等
|
|
36
|
+
*/
|
|
37
|
+
export const BASE_TOKENS_TO_APPROVE = [
|
|
38
|
+
WOKB, // 原生包装代币(用于 V3 卖出等)
|
|
39
|
+
USDT, // USDT 稳定币
|
|
40
|
+
USDC, // USDC 稳定币
|
|
41
|
+
USDT0, // USDT0 稳定币
|
|
42
|
+
];
|
|
25
43
|
/** MaxUint256 用于无限授权 */
|
|
26
44
|
export const MAX_UINT256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
|
27
45
|
// ==================== X-Layer 利润配置 ====================
|
|
@@ -22,8 +22,7 @@ function toRlpHex(value) {
|
|
|
22
22
|
if (value === 0 || value === 0n) {
|
|
23
23
|
return '0x';
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
return hex.replace(/^0x0+/, '0x') || '0x';
|
|
25
|
+
return ethers.toBeHex(value);
|
|
27
26
|
}
|
|
28
27
|
// ==================== Flap Portal ABI ====================
|
|
29
28
|
const PORTAL_ABI = [
|
|
@@ -69,11 +68,12 @@ function hashAuthorization(chainId, executorAddress, nonce) {
|
|
|
69
68
|
function signAuthorization(wallet, nonce = 0) {
|
|
70
69
|
const messageHash = hashAuthorization(XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, nonce);
|
|
71
70
|
const signature = wallet.signingKey.sign(messageHash);
|
|
71
|
+
// 注意:EIP-7702 授权签名使用 yParity (0 或 1),不是 v (27 或 28)
|
|
72
72
|
return {
|
|
73
73
|
chainId: XLAYER_CHAIN_ID,
|
|
74
74
|
address: EXECUTOR_ADDRESS,
|
|
75
75
|
nonce,
|
|
76
|
-
v: signature.
|
|
76
|
+
v: signature.yParity,
|
|
77
77
|
r: signature.r,
|
|
78
78
|
s: signature.s,
|
|
79
79
|
};
|
|
@@ -270,7 +270,8 @@ async function buildAndSignType4Transaction(rpcUrl, gasPrice, participants) {
|
|
|
270
270
|
const unsigned = ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(fields)]);
|
|
271
271
|
const txHashToSign = ethers.keccak256(unsigned);
|
|
272
272
|
const signature = sponsor.signingKey.sign(txHashToSign);
|
|
273
|
-
|
|
273
|
+
// 注意:Type 4 交易使用 yParity (0 或 1),不是 v (27 或 28)
|
|
274
|
+
const signedFields = [...fields, toRlpHex(signature.yParity), signature.r, signature.s];
|
|
274
275
|
const signedTx = ethers.hexlify(ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(signedFields)]));
|
|
275
276
|
const txHash = ethers.keccak256(signedTx);
|
|
276
277
|
return { signedTx, txHash, sponsorAddress: sponsor.address };
|
|
@@ -21,8 +21,7 @@ function toRlpHex(value) {
|
|
|
21
21
|
if (value === 0 || value === 0n) {
|
|
22
22
|
return '0x';
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
return hex.replace(/^0x0+/, '0x') || '0x';
|
|
24
|
+
return ethers.toBeHex(value);
|
|
26
25
|
}
|
|
27
26
|
// ==================== Flap Portal ABI ====================
|
|
28
27
|
const PORTAL_ABI = [
|
|
@@ -65,11 +64,12 @@ function hashAuthorization(chainId, executorAddress, nonce) {
|
|
|
65
64
|
function signAuthorization(wallet, nonce = 0) {
|
|
66
65
|
const messageHash = hashAuthorization(XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, nonce);
|
|
67
66
|
const signature = wallet.signingKey.sign(messageHash);
|
|
67
|
+
// 注意:EIP-7702 授权签名使用 yParity (0 或 1),不是 v (27 或 28)
|
|
68
68
|
return {
|
|
69
69
|
chainId: XLAYER_CHAIN_ID,
|
|
70
70
|
address: EXECUTOR_ADDRESS,
|
|
71
71
|
nonce,
|
|
72
|
-
v: signature.
|
|
72
|
+
v: signature.yParity,
|
|
73
73
|
r: signature.r,
|
|
74
74
|
s: signature.s,
|
|
75
75
|
};
|
|
@@ -205,7 +205,8 @@ async function buildAndSignType4Transaction(rpcUrl, gasPrice, participants) {
|
|
|
205
205
|
const unsigned = ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(fields)]);
|
|
206
206
|
const txHashToSign = ethers.keccak256(unsigned);
|
|
207
207
|
const signature = sponsor.signingKey.sign(txHashToSign);
|
|
208
|
-
|
|
208
|
+
// 注意:Type 4 交易使用 yParity (0 或 1),不是 v (27 或 28)
|
|
209
|
+
const signedFields = [...fields, toRlpHex(signature.yParity), signature.r, signature.s];
|
|
209
210
|
const signedTx = ethers.hexlify(ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(signedFields)]));
|
|
210
211
|
const txHash = ethers.keccak256(signedTx);
|
|
211
212
|
return { signedTx, txHash, sponsorAddress: sponsor.address };
|
|
@@ -24,8 +24,7 @@ function toRlpHex(value) {
|
|
|
24
24
|
if (value === 0 || value === 0n) {
|
|
25
25
|
return '0x';
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
return hex.replace(/^0x0+/, '0x') || '0x';
|
|
27
|
+
return ethers.toBeHex(value);
|
|
29
28
|
}
|
|
30
29
|
// ==================== 利润计算 ====================
|
|
31
30
|
function getProfitRateBps(userType = 'v0') {
|
|
@@ -55,11 +54,12 @@ function hashAuthorization(chainId, executorAddress, nonce) {
|
|
|
55
54
|
function signAuthorization(wallet, nonce = 0) {
|
|
56
55
|
const messageHash = hashAuthorization(XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, nonce);
|
|
57
56
|
const signature = wallet.signingKey.sign(messageHash);
|
|
57
|
+
// 注意:EIP-7702 授权签名使用 yParity (0 或 1),不是 v (27 或 28)
|
|
58
58
|
return {
|
|
59
59
|
chainId: XLAYER_CHAIN_ID,
|
|
60
60
|
address: EXECUTOR_ADDRESS,
|
|
61
61
|
nonce,
|
|
62
|
-
v: signature.
|
|
62
|
+
v: signature.yParity,
|
|
63
63
|
r: signature.r,
|
|
64
64
|
s: signature.s,
|
|
65
65
|
};
|
|
@@ -142,7 +142,8 @@ async function buildAndSignType4Transaction(config, participants) {
|
|
|
142
142
|
const unsigned = ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(fields)]);
|
|
143
143
|
const txHashToSign = ethers.keccak256(unsigned);
|
|
144
144
|
const signature = sponsor.signingKey.sign(txHashToSign);
|
|
145
|
-
|
|
145
|
+
// 注意:Type 4 交易使用 yParity (0 或 1),不是 v (27 或 28)
|
|
146
|
+
const signedFields = [...fields, toRlpHex(signature.yParity), signature.r, signature.s];
|
|
146
147
|
const signedTx = ethers.hexlify(ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(signedFields)]));
|
|
147
148
|
const txHash = ethers.keccak256(signedTx);
|
|
148
149
|
return { signedTx, txHash, sponsorAddress: sponsor.address };
|
|
@@ -52,11 +52,11 @@ export * from './constants.js';
|
|
|
52
52
|
export * from './types.js';
|
|
53
53
|
export { XLayerBundle, createBundle, type BundleParticipant, } from './bundle.js';
|
|
54
54
|
export { buildApprove, buildTransfer, buildNativeTransfer, buildV2SwapBuy, buildV2SwapSell, buildPortalBuy, buildPortalSell, } from './builders.js';
|
|
55
|
-
export { bundleBatchApprove, bundleSingleApprove, getSpendersToApprove, type BatchApproveParams, type SingleApproveParams, } from './approve.js';
|
|
55
|
+
export { bundleBatchApprove, bundleFullApprove, bundleSingleApprove, getSpendersToApprove, getBaseTokensToApprove, getStablecoins, type BatchApproveParams, type FullApproveParams, type SingleApproveParams, type ApprovalTarget, type ApprovalMode, } from './approve.js';
|
|
56
56
|
export { bundleBatchBuy, bundleBatchSell, bundleBuySellPair, type BatchBuyParams, type BatchSellParams, type BuySellPairParams, } from './trade.js';
|
|
57
|
-
export { checkApprovalStatus, checkApprovalStatusBatch, checkSingleApprovalStatus, getApprovalDetails, getUnapprovedSpenders, } from './check.js';
|
|
57
|
+
export { checkApprovalStatus, checkApprovalStatusBatch, checkApprovalStatusBatchSimple, checkSingleApprovalStatus, checkFullApprovalStatus, getApprovalDetails, getUnapprovedSpenders, getWalletsNeedApproval, getApprovalSummary, type ApprovalTarget as CheckApprovalTarget, type ApprovalStatusDetail, type BatchApprovalCheckResult, type FullApprovalCheckResult, } from './check.js';
|
|
58
58
|
export { collectProfit, collectProfitBatch, collectProfitFromTrade, calculateProfit, getProfitRateBps, getProfitRecipient, type UserType, type ProfitMode, type ProfitParams, type BatchProfitParams, type ProfitResult, } from './profit.js';
|
|
59
|
-
export { signBatchApprove, signBatchBuy, signBatchSell, signBuySellPair, signCustomCalls, type SignConfig, type SignedTransaction, type BatchApproveSignParams, type BatchBuySignParams, type BatchSellSignParams, type BuySellPairSignParams, type UserType as SignUserType, } from './sign.js';
|
|
59
|
+
export { signBatchApprove, signFullApprove, signBatchBuy, signBatchSell, signBuySellPair, signCustomCalls, type SignConfig, type SignedTransaction, type BatchApproveSignParams, type BatchBuySignParams, type BatchSellSignParams, type BuySellPairSignParams, type UserType as SignUserType, type ApprovalTarget as SignApprovalTarget, type ApprovalMode as SignApprovalMode, } from './sign.js';
|
|
60
60
|
export { broadcastSignedBundle, broadcastSignedBundleBatch, broadcastSignedBundleParallel, submitBundleToXLayer, type BroadcastConfig, type BroadcastResult, } from './broadcast.js';
|
|
61
61
|
export { signDisperseNative, signDisperseERC20, signDisperseNativeWithAutoHop, type DisperseConfig, type DisperseRecipient, type DisperseNativeParams, type DisperseERC20Params, type DisperseSignedResult, } from './disperse.js';
|
|
62
62
|
export { signSweepNative, signSweepERC20, signSweepNativeWithAutoHop, type SweepConfig, type SweepSource, type SweepNativeParams, type SweepERC20Params, type SweepSignedResult, } from './sweep.js';
|
|
@@ -57,15 +57,15 @@ export { XLayerBundle, createBundle, } from './bundle.js';
|
|
|
57
57
|
// ==================== 调用构建器 ====================
|
|
58
58
|
export { buildApprove, buildTransfer, buildNativeTransfer, buildV2SwapBuy, buildV2SwapSell, buildPortalBuy, buildPortalSell, } from './builders.js';
|
|
59
59
|
// ==================== 批量授权 ====================
|
|
60
|
-
export { bundleBatchApprove, bundleSingleApprove, getSpendersToApprove, } from './approve.js';
|
|
60
|
+
export { bundleBatchApprove, bundleFullApprove, bundleSingleApprove, getSpendersToApprove, getBaseTokensToApprove, getStablecoins, } from './approve.js';
|
|
61
61
|
// ==================== 批量交易 ====================
|
|
62
62
|
export { bundleBatchBuy, bundleBatchSell, bundleBuySellPair, } from './trade.js';
|
|
63
63
|
// ==================== 授权检查 ====================
|
|
64
|
-
export { checkApprovalStatus, checkApprovalStatusBatch, checkSingleApprovalStatus, getApprovalDetails, getUnapprovedSpenders, } from './check.js';
|
|
64
|
+
export { checkApprovalStatus, checkApprovalStatusBatch, checkApprovalStatusBatchSimple, checkSingleApprovalStatus, checkFullApprovalStatus, getApprovalDetails, getUnapprovedSpenders, getWalletsNeedApproval, getApprovalSummary, } from './check.js';
|
|
65
65
|
// ==================== 利润刮取 ====================
|
|
66
66
|
export { collectProfit, collectProfitBatch, collectProfitFromTrade, calculateProfit, getProfitRateBps, getProfitRecipient, } from './profit.js';
|
|
67
67
|
// ==================== 前端签名(完整签名,后端只需广播) ====================
|
|
68
|
-
export { signBatchApprove, signBatchBuy, signBatchSell, signBuySellPair, signCustomCalls, } from './sign.js';
|
|
68
|
+
export { signBatchApprove, signFullApprove, signBatchBuy, signBatchSell, signBuySellPair, signCustomCalls, } from './sign.js';
|
|
69
69
|
// ==================== 后端广播(不需要私钥) ====================
|
|
70
70
|
export { broadcastSignedBundle, broadcastSignedBundleBatch, broadcastSignedBundleParallel, submitBundleToXLayer, } from './broadcast.js';
|
|
71
71
|
// ==================== 代币分发(支持多跳) ====================
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* - 利润自动计算并包含在交易中(不需要前端传递)
|
|
11
11
|
*/
|
|
12
12
|
import type { Call } from './types.js';
|
|
13
|
+
/** 授权目标类型 */
|
|
14
|
+
export type ApprovalTarget = 'all' | 'flap' | 'v2' | 'v3' | 'v3-02';
|
|
15
|
+
/** 授权模式 */
|
|
16
|
+
export type ApprovalMode = 'token' | 'base' | 'full';
|
|
13
17
|
/** 用户类型(影响利润率) */
|
|
14
18
|
export type UserType = 'v0' | 'v1' | 'normal';
|
|
15
19
|
/** 签名配置(简化版:不需要单独的 Sponsor) */
|
|
@@ -44,6 +48,14 @@ export interface BatchApproveSignParams extends SignConfig {
|
|
|
44
48
|
tokenAddress: string;
|
|
45
49
|
/** 钱包私钥列表(第一个钱包作为 Sponsor,支付 gas) */
|
|
46
50
|
privateKeys: string[];
|
|
51
|
+
/** 授权目标(默认 'all':授权给所有路由) */
|
|
52
|
+
target?: ApprovalTarget;
|
|
53
|
+
/** 授权模式(默认 'token':只授权代币) */
|
|
54
|
+
mode?: ApprovalMode;
|
|
55
|
+
/** 是否包含 WOKB 授权(默认 false) */
|
|
56
|
+
includeWokb?: boolean;
|
|
57
|
+
/** 是否包含稳定币授权(默认 false) */
|
|
58
|
+
includeStablecoins?: boolean;
|
|
47
59
|
}
|
|
48
60
|
/** 批量买入签名参数 */
|
|
49
61
|
export interface BatchBuySignParams extends SignConfig {
|
|
@@ -97,10 +109,48 @@ export interface BuySellPairSignParams extends SignConfig {
|
|
|
97
109
|
/**
|
|
98
110
|
* 批量授权签名(前端调用)
|
|
99
111
|
*
|
|
112
|
+
* ✅ 完整授权支持:
|
|
113
|
+
* - 代币授权给内盘 Flap Portal
|
|
114
|
+
* - 代币授权给外盘 V2/V3 Router
|
|
115
|
+
* - WOKB 授权给路由(用于卖出/交换)
|
|
116
|
+
* - 稳定币授权(USDT、USDC、USDT0)
|
|
117
|
+
*
|
|
100
118
|
* 第一个钱包作为 Sponsor,支付 gas
|
|
101
119
|
* 授权不需要利润
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // 只授权代币给所有路由(默认)
|
|
124
|
+
* await signBatchApprove({
|
|
125
|
+
* rpcUrl,
|
|
126
|
+
* tokenAddress,
|
|
127
|
+
* privateKeys: [pk1, pk2],
|
|
128
|
+
* });
|
|
129
|
+
*
|
|
130
|
+
* // 完整授权(代币 + WOKB + 稳定币)
|
|
131
|
+
* await signBatchApprove({
|
|
132
|
+
* rpcUrl,
|
|
133
|
+
* tokenAddress,
|
|
134
|
+
* privateKeys: [pk1],
|
|
135
|
+
* mode: 'full',
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* // 只授权给 V2 外盘
|
|
139
|
+
* await signBatchApprove({
|
|
140
|
+
* rpcUrl,
|
|
141
|
+
* tokenAddress,
|
|
142
|
+
* privateKeys: [pk1],
|
|
143
|
+
* target: 'v2',
|
|
144
|
+
* });
|
|
145
|
+
* ```
|
|
102
146
|
*/
|
|
103
147
|
export declare function signBatchApprove(params: BatchApproveSignParams): Promise<SignedTransaction>;
|
|
148
|
+
/**
|
|
149
|
+
* 完整授权签名(代币 + WOKB + 稳定币)
|
|
150
|
+
*
|
|
151
|
+
* 一次性完成所有授权,跟 BSC 链一样
|
|
152
|
+
*/
|
|
153
|
+
export declare function signFullApprove(params: Omit<BatchApproveSignParams, 'mode' | 'includeWokb' | 'includeStablecoins'>): Promise<SignedTransaction>;
|
|
104
154
|
/**
|
|
105
155
|
* 批量买入签名(前端调用)
|
|
106
156
|
*
|
|
@@ -10,8 +10,55 @@
|
|
|
10
10
|
* - 利润自动计算并包含在交易中(不需要前端传递)
|
|
11
11
|
*/
|
|
12
12
|
import { ethers, Wallet, JsonRpcProvider, Interface } from 'ethers';
|
|
13
|
-
import { XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, EXECUTOR_ABI, SPENDERS_TO_APPROVE, MAX_UINT256, DEFAULT_GAS_PRICE, GAS_LIMIT_MULTIPLIER, XLAYER_PROFIT_CONFIG, } from './constants.js';
|
|
13
|
+
import { XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, EXECUTOR_ABI, SPENDERS_TO_APPROVE, MAX_UINT256, DEFAULT_GAS_PRICE, GAS_LIMIT_MULTIPLIER, XLAYER_PROFIT_CONFIG, WOKB, USDT, USDC, USDT0, FLAP_PORTAL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_SWAP_ROUTER02, } from './constants.js';
|
|
14
14
|
import { buildApprove, buildV2SwapBuy, buildV2SwapSell, buildPortalBuy, buildPortalSell, buildNativeTransfer } from './builders.js';
|
|
15
|
+
/**
|
|
16
|
+
* 获取目标 Spender 列表
|
|
17
|
+
*/
|
|
18
|
+
function getSpenders(target = 'all') {
|
|
19
|
+
switch (target) {
|
|
20
|
+
case 'flap':
|
|
21
|
+
return [FLAP_PORTAL];
|
|
22
|
+
case 'v2':
|
|
23
|
+
return [POTATOSWAP_V2_ROUTER];
|
|
24
|
+
case 'v3':
|
|
25
|
+
return [POTATOSWAP_V3_ROUTER];
|
|
26
|
+
case 'v3-02':
|
|
27
|
+
return [POTATOSWAP_SWAP_ROUTER02];
|
|
28
|
+
case 'all':
|
|
29
|
+
default:
|
|
30
|
+
return SPENDERS_TO_APPROVE;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 构建授权 calls
|
|
35
|
+
*/
|
|
36
|
+
function buildApproveCalls(tokenAddress, target = 'all', mode = 'token', includeWokb = false, includeStablecoins = false) {
|
|
37
|
+
const spenders = getSpenders(target);
|
|
38
|
+
const calls = [];
|
|
39
|
+
// 1. 代币授权给路由
|
|
40
|
+
if (mode === 'token' || mode === 'full') {
|
|
41
|
+
for (const spender of spenders) {
|
|
42
|
+
calls.push(buildApprove(tokenAddress, spender, MAX_UINT256));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 2. WOKB 授权给路由(用于 V3 卖出等)
|
|
46
|
+
if (includeWokb || mode === 'base' || mode === 'full') {
|
|
47
|
+
for (const spender of spenders) {
|
|
48
|
+
calls.push(buildApprove(WOKB, spender, MAX_UINT256));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 3. 稳定币授权给路由
|
|
52
|
+
if (includeStablecoins || mode === 'full') {
|
|
53
|
+
const stablecoins = [USDT, USDC, USDT0];
|
|
54
|
+
for (const stablecoin of stablecoins) {
|
|
55
|
+
for (const spender of spenders) {
|
|
56
|
+
calls.push(buildApprove(stablecoin, spender, MAX_UINT256));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return calls;
|
|
61
|
+
}
|
|
15
62
|
// ==================== RLP 编码工具 ====================
|
|
16
63
|
/**
|
|
17
64
|
* 将整数转换为符合 RLP 规范的十六进制字符串
|
|
@@ -27,10 +74,8 @@ function toRlpHex(value) {
|
|
|
27
74
|
if (value === 0 || value === 0n) {
|
|
28
75
|
return '0x'; // 零值必须是空字符串
|
|
29
76
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// ethers.toBeHex 可能会产生前导零,需要手动移除
|
|
33
|
-
return hex.replace(/^0x0+/, '0x') || '0x';
|
|
77
|
+
// ethers.toBeHex 已经正确处理非零值,不需要额外处理
|
|
78
|
+
return ethers.toBeHex(value);
|
|
34
79
|
}
|
|
35
80
|
// ==================== 利润计算 ====================
|
|
36
81
|
/**
|
|
@@ -79,11 +124,12 @@ function hashAuthorization(chainId, executorAddress, nonce) {
|
|
|
79
124
|
function signAuthorization(wallet, nonce = 0) {
|
|
80
125
|
const messageHash = hashAuthorization(XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, nonce);
|
|
81
126
|
const signature = wallet.signingKey.sign(messageHash);
|
|
127
|
+
// 注意:EIP-7702 授权签名使用 yParity (0 或 1),不是 v (27 或 28)
|
|
82
128
|
return {
|
|
83
129
|
chainId: XLAYER_CHAIN_ID,
|
|
84
130
|
address: EXECUTOR_ADDRESS,
|
|
85
131
|
nonce,
|
|
86
|
-
v: signature.
|
|
132
|
+
v: signature.yParity,
|
|
87
133
|
r: signature.r,
|
|
88
134
|
s: signature.s,
|
|
89
135
|
};
|
|
@@ -175,9 +221,10 @@ async function buildAndSignType4Transaction(config, participants) {
|
|
|
175
221
|
]);
|
|
176
222
|
const txHashToSign = ethers.keccak256(unsigned);
|
|
177
223
|
const signature = sponsor.signingKey.sign(txHashToSign);
|
|
224
|
+
// 注意:Type 4 交易使用 yParity (0 或 1),不是 v (27 或 28)
|
|
178
225
|
const signedFields = [
|
|
179
226
|
...fields,
|
|
180
|
-
toRlpHex(signature.
|
|
227
|
+
toRlpHex(signature.yParity),
|
|
181
228
|
signature.r,
|
|
182
229
|
signature.s,
|
|
183
230
|
];
|
|
@@ -201,17 +248,66 @@ async function buildAndSignType4Transaction(config, participants) {
|
|
|
201
248
|
/**
|
|
202
249
|
* 批量授权签名(前端调用)
|
|
203
250
|
*
|
|
251
|
+
* ✅ 完整授权支持:
|
|
252
|
+
* - 代币授权给内盘 Flap Portal
|
|
253
|
+
* - 代币授权给外盘 V2/V3 Router
|
|
254
|
+
* - WOKB 授权给路由(用于卖出/交换)
|
|
255
|
+
* - 稳定币授权(USDT、USDC、USDT0)
|
|
256
|
+
*
|
|
204
257
|
* 第一个钱包作为 Sponsor,支付 gas
|
|
205
258
|
* 授权不需要利润
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* // 只授权代币给所有路由(默认)
|
|
263
|
+
* await signBatchApprove({
|
|
264
|
+
* rpcUrl,
|
|
265
|
+
* tokenAddress,
|
|
266
|
+
* privateKeys: [pk1, pk2],
|
|
267
|
+
* });
|
|
268
|
+
*
|
|
269
|
+
* // 完整授权(代币 + WOKB + 稳定币)
|
|
270
|
+
* await signBatchApprove({
|
|
271
|
+
* rpcUrl,
|
|
272
|
+
* tokenAddress,
|
|
273
|
+
* privateKeys: [pk1],
|
|
274
|
+
* mode: 'full',
|
|
275
|
+
* });
|
|
276
|
+
*
|
|
277
|
+
* // 只授权给 V2 外盘
|
|
278
|
+
* await signBatchApprove({
|
|
279
|
+
* rpcUrl,
|
|
280
|
+
* tokenAddress,
|
|
281
|
+
* privateKeys: [pk1],
|
|
282
|
+
* target: 'v2',
|
|
283
|
+
* });
|
|
284
|
+
* ```
|
|
206
285
|
*/
|
|
207
286
|
export async function signBatchApprove(params) {
|
|
208
|
-
const { tokenAddress, privateKeys, ...config } = params;
|
|
287
|
+
const { tokenAddress, privateKeys, target = 'all', mode = 'token', includeWokb = false, includeStablecoins = false, ...config } = params;
|
|
288
|
+
const calls = buildApproveCalls(tokenAddress, target, mode, includeWokb, includeStablecoins);
|
|
289
|
+
const spenders = getSpenders(target);
|
|
290
|
+
console.log(`[Bundle授权签名] 钱包: ${privateKeys.length}, Spender: ${spenders.length}, 授权数: ${calls.length}`);
|
|
291
|
+
console.log(`[Bundle授权签名] 模式: ${mode}, 目标: ${target}, WOKB: ${includeWokb}, 稳定币: ${includeStablecoins}`);
|
|
209
292
|
const participants = privateKeys.map(pk => ({
|
|
210
293
|
privateKey: pk,
|
|
211
|
-
calls
|
|
294
|
+
calls,
|
|
212
295
|
}));
|
|
213
296
|
return buildAndSignType4Transaction(config, participants);
|
|
214
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* 完整授权签名(代币 + WOKB + 稳定币)
|
|
300
|
+
*
|
|
301
|
+
* 一次性完成所有授权,跟 BSC 链一样
|
|
302
|
+
*/
|
|
303
|
+
export async function signFullApprove(params) {
|
|
304
|
+
return signBatchApprove({
|
|
305
|
+
...params,
|
|
306
|
+
mode: 'full',
|
|
307
|
+
includeWokb: true,
|
|
308
|
+
includeStablecoins: true,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
215
311
|
/**
|
|
216
312
|
* 批量买入签名(前端调用)
|
|
217
313
|
*
|
|
@@ -24,8 +24,7 @@ function toRlpHex(value) {
|
|
|
24
24
|
if (value === 0 || value === 0n) {
|
|
25
25
|
return '0x';
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
return hex.replace(/^0x0+/, '0x') || '0x';
|
|
27
|
+
return ethers.toBeHex(value);
|
|
29
28
|
}
|
|
30
29
|
// ==================== 利润计算 ====================
|
|
31
30
|
function getProfitRateBps(userType = 'v0') {
|
|
@@ -55,11 +54,12 @@ function hashAuthorization(chainId, executorAddress, nonce) {
|
|
|
55
54
|
function signAuthorization(wallet, nonce = 0) {
|
|
56
55
|
const messageHash = hashAuthorization(XLAYER_CHAIN_ID, EXECUTOR_ADDRESS, nonce);
|
|
57
56
|
const signature = wallet.signingKey.sign(messageHash);
|
|
57
|
+
// 注意:EIP-7702 授权签名使用 yParity (0 或 1),不是 v (27 或 28)
|
|
58
58
|
return {
|
|
59
59
|
chainId: XLAYER_CHAIN_ID,
|
|
60
60
|
address: EXECUTOR_ADDRESS,
|
|
61
61
|
nonce,
|
|
62
|
-
v: signature.
|
|
62
|
+
v: signature.yParity,
|
|
63
63
|
r: signature.r,
|
|
64
64
|
s: signature.s,
|
|
65
65
|
};
|
|
@@ -138,7 +138,8 @@ async function buildAndSignType4Transaction(config, participants) {
|
|
|
138
138
|
const unsigned = ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(fields)]);
|
|
139
139
|
const txHashToSign = ethers.keccak256(unsigned);
|
|
140
140
|
const signature = sponsor.signingKey.sign(txHashToSign);
|
|
141
|
-
|
|
141
|
+
// 注意:Type 4 交易使用 yParity (0 或 1),不是 v (27 或 28)
|
|
142
|
+
const signedFields = [...fields, toRlpHex(signature.yParity), signature.r, signature.s];
|
|
142
143
|
const signedTx = ethers.hexlify(ethers.concat([new Uint8Array([0x04]), ethers.encodeRlp(signedFields)]));
|
|
143
144
|
const txHash = ethers.keccak256(signedTx);
|
|
144
145
|
return { signedTx, txHash, sponsorAddress: sponsor.address };
|