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.
@@ -1,12 +1,20 @@
1
1
  /**
2
2
  * X-Layer Bundle 批量授权
3
3
  *
4
- * 使用 Bundle 实现多钱包批量授权
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
- * 使用 Bundle 实现多钱包批量授权
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
- // 构建参与者:每个钱包授权给所有 Spender
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: SPENDERS_TO_APPROVE.map(spender => buildApprove(tokenAddress, spender, MAX_UINT256)),
107
+ calls,
29
108
  }));
30
- console.log(`[Bundle授权] 执行批量授权: ${wallets.length} 个钱包, ${SPENDERS_TO_APPROVE.length} 个 Spender`);
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 SPENDERS_TO_APPROVE;
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
- const hex = ethers.toBeHex(value);
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.v,
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.v),
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
- * 检查钱包是否已授权所有 Spender
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 checkApprovalStatusBatch(rpcUrl: string, tokenAddress: string, walletAddresses: string[]): Promise<Map<string, boolean>>;
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
- * 检查钱包是否已授权所有 Spender
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 erc20Interface = new Interface(ERC20_ABI);
22
- const contract = new Contract(tokenAddress, erc20Interface, provider);
23
- try {
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 checkApprovalStatusBatch(rpcUrl, tokenAddress, walletAddresses) {
44
- const results = new Map();
45
- await Promise.all(walletAddresses.map(async (address) => {
46
- const isApproved = await checkApprovalStatus(rpcUrl, tokenAddress, address);
47
- results.set(address.toLowerCase(), isApproved);
48
- }));
49
- return results;
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 erc20Interface = new Interface(ERC20_ABI);
83
- const contract = new Contract(tokenAddress, erc20Interface, provider);
242
+ const spenders = getSpenders(target);
243
+ const allowances = await batchCheckAllowancesMulticall(provider, tokenAddress, [walletAddress], spenders);
84
244
  const results = new Map();
85
- await Promise.all(SPENDERS_TO_APPROVE.map(async (spender) => {
86
- try {
87
- const allowance = await contract.allowance(walletAddress, spender);
88
- results.set(spender.toLowerCase(), BigInt(allowance));
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 SPENDERS_TO_APPROVE) {
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
- /** 需要授权的所有 Spender 列表 */
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
- /** 需要授权的所有 Spender 列表 */
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
- const hex = ethers.toBeHex(value);
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.v,
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
- const signedFields = [...fields, toRlpHex(signature.v), signature.r, signature.s];
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
- const hex = ethers.toBeHex(value);
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.v,
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
- const signedFields = [...fields, toRlpHex(signature.v), signature.r, signature.s];
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
- const hex = ethers.toBeHex(value);
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.v,
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
- const signedFields = [...fields, toRlpHex(signature.v), signature.r, signature.s];
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
- const hex = ethers.toBeHex(value);
31
- // 移除前导零(0x00... -> 0x...)
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.v,
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.v),
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: SPENDERS_TO_APPROVE.map(spender => buildApprove(tokenAddress, spender, MAX_UINT256)),
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
- const hex = ethers.toBeHex(value);
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.v,
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
- const signedFields = [...fields, toRlpHex(signature.v), signature.r, signature.s];
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",