@wtflabs/x402-detector 0.0.1-beta.2
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/README.md +332 -0
- package/dist/cjs/index.d.ts +209 -0
- package/dist/cjs/index.js +490 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.d.mts +209 -0
- package/dist/esm/index.mjs +453 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { PublicClient, Address } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 支持的支付方式
|
|
5
|
+
*/
|
|
6
|
+
type PaymentMethod = "eip3009" | "permit" | "permit2" | "permit2-witness";
|
|
7
|
+
/**
|
|
8
|
+
* Token 信息
|
|
9
|
+
*/
|
|
10
|
+
interface TokenInfo {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Token 支付能力检测结果
|
|
16
|
+
*/
|
|
17
|
+
interface TokenPaymentCapabilities {
|
|
18
|
+
/** Token 地址 */
|
|
19
|
+
address: string;
|
|
20
|
+
/** 支持的支付方式列表 */
|
|
21
|
+
supportedMethods: PaymentMethod[];
|
|
22
|
+
/** 详细检测结果 */
|
|
23
|
+
details: {
|
|
24
|
+
/** 是否支持 EIP-3009 (transferWithAuthorization) */
|
|
25
|
+
hasEIP3009: boolean;
|
|
26
|
+
/** 是否支持 EIP-2612 (permit) */
|
|
27
|
+
hasPermit: boolean;
|
|
28
|
+
/** 是否支持 Permit2 (通用授权) */
|
|
29
|
+
hasPermit2Approval: boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 完整的 Token 检测结果(包含 name 和 version)
|
|
34
|
+
*/
|
|
35
|
+
interface TokenDetectionResult extends TokenPaymentCapabilities {
|
|
36
|
+
/** Token 名称 */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Token version(用于 EIP-712 签名) */
|
|
39
|
+
version: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 预设 Token 配置
|
|
43
|
+
*/
|
|
44
|
+
interface PresetTokenConfig {
|
|
45
|
+
/** 支持的支付方式 */
|
|
46
|
+
supportedMethods: PaymentMethod[];
|
|
47
|
+
/** 支持的网络 ID 列表 */
|
|
48
|
+
supportedNetworks: number[];
|
|
49
|
+
/** Token 描述(可选) */
|
|
50
|
+
description?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Logger 接口
|
|
54
|
+
*/
|
|
55
|
+
interface Logger {
|
|
56
|
+
log: (message: string) => void;
|
|
57
|
+
error: (message: string, error?: unknown) => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* TokenDetector 配置选项
|
|
61
|
+
*/
|
|
62
|
+
interface TokenDetectorOptions {
|
|
63
|
+
/** 自定义 logger,默认使用 console */
|
|
64
|
+
logger?: Logger | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* EIP-3009 方法签名
|
|
69
|
+
* - transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
|
|
70
|
+
*
|
|
71
|
+
* 支持多个方法签名变体,以兼容不同的实现:
|
|
72
|
+
* - 0xe3ee160e: 标准 EIP-3009 实现
|
|
73
|
+
* - 0xcf092995: 某些代币的替代实现
|
|
74
|
+
*/
|
|
75
|
+
declare const EIP3009_SIGNATURES: readonly ["0xe3ee160e", "0xcf092995"];
|
|
76
|
+
/**
|
|
77
|
+
* EIP-2612 Permit 方法签名
|
|
78
|
+
* - permit(address,address,uint256,uint256,uint8,bytes32,bytes32)
|
|
79
|
+
*/
|
|
80
|
+
declare const EIP2612_PERMIT: "0xd505accf";
|
|
81
|
+
/**
|
|
82
|
+
* Uniswap Permit2 合约地址(所有链相同)
|
|
83
|
+
*/
|
|
84
|
+
declare const PERMIT2_ADDRESS: "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
85
|
+
/**
|
|
86
|
+
* EIP-1967 标准实现槽位
|
|
87
|
+
* keccak256("eip1967.proxy.implementation") - 1
|
|
88
|
+
*/
|
|
89
|
+
declare const EIP1967_IMPLEMENTATION_SLOT: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
90
|
+
/**
|
|
91
|
+
* EIP-1822 UUPS 实现槽位
|
|
92
|
+
* keccak256("PROXIABLE")
|
|
93
|
+
*/
|
|
94
|
+
declare const EIP1822_IMPLEMENTATION_SLOT: "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3";
|
|
95
|
+
/**
|
|
96
|
+
* 预设 Token 配置
|
|
97
|
+
* 用于已知的特殊 Token,避免重复检测
|
|
98
|
+
*/
|
|
99
|
+
declare const PRESET_TOKEN_CAPABILITIES: Record<string, PresetTokenConfig>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 检测 Token 支持的支付方式
|
|
103
|
+
*
|
|
104
|
+
* @param tokenAddress - Token 地址
|
|
105
|
+
* @param client - viem PublicClient
|
|
106
|
+
* @param logger - 可选的 logger
|
|
107
|
+
* @returns 检测结果
|
|
108
|
+
*/
|
|
109
|
+
declare function detectTokenPaymentMethods(tokenAddress: string, client: PublicClient, logger?: Logger | null): Promise<TokenPaymentCapabilities>;
|
|
110
|
+
/**
|
|
111
|
+
* 获取推荐的支付方式(仅返回 schema 支持的类型)
|
|
112
|
+
* 按优先级排序:eip3009 > permit > permit2
|
|
113
|
+
* 注意:permit2-witness 会被映射为 permit2,因为它们在 schema 中是同一种支付类型
|
|
114
|
+
*
|
|
115
|
+
* @param tokenAddress - Token 地址
|
|
116
|
+
* @param client - viem PublicClient
|
|
117
|
+
* @param logger - 可选的 logger
|
|
118
|
+
* @returns 推荐的支付方式
|
|
119
|
+
*/
|
|
120
|
+
declare function getRecommendedPaymentMethod(tokenAddress: string, client: PublicClient, logger?: Logger | null): Promise<"eip3009" | "permit2" | "permit" | null>;
|
|
121
|
+
/**
|
|
122
|
+
* 获取 Token 的 name 和 version 信息(用于 EIP-712 签名)
|
|
123
|
+
* 支持代理合约(会自动从代理合约读取,因为代理合约会 delegatecall 到实现合约)
|
|
124
|
+
*
|
|
125
|
+
* @param tokenAddress - Token 地址
|
|
126
|
+
* @param client - viem PublicClient
|
|
127
|
+
* @param logger - 可选的 logger
|
|
128
|
+
* @returns Token 的 name 和 version
|
|
129
|
+
*/
|
|
130
|
+
declare function getTokenInfo(tokenAddress: string, client: PublicClient, logger?: Logger | null): Promise<TokenInfo>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 检测合约是否是代理合约,并获取实现合约地址
|
|
134
|
+
*
|
|
135
|
+
* @param client - viem PublicClient
|
|
136
|
+
* @param proxyAddress - 代理合约地址
|
|
137
|
+
* @param logger - 可选的 logger
|
|
138
|
+
* @returns 实现合约地址或 null
|
|
139
|
+
*/
|
|
140
|
+
declare function getImplementationAddress(client: PublicClient, proxyAddress: Address, logger?: Logger | null): Promise<Address | null>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Token 检测器 - 带缓存功能的 SDK
|
|
144
|
+
*
|
|
145
|
+
* 主要用于 x402-server,也可以独立使用
|
|
146
|
+
*/
|
|
147
|
+
declare class TokenDetector {
|
|
148
|
+
/** 缓存存储 (chainId:address -> TokenDetectionResult) */
|
|
149
|
+
private cache;
|
|
150
|
+
/** viem PublicClient */
|
|
151
|
+
private client;
|
|
152
|
+
/** Logger 实例 */
|
|
153
|
+
private logger;
|
|
154
|
+
/**
|
|
155
|
+
* 构造函数
|
|
156
|
+
*
|
|
157
|
+
* @param client - viem PublicClient
|
|
158
|
+
* @param options - 可选配置
|
|
159
|
+
*/
|
|
160
|
+
constructor(client: PublicClient, options?: TokenDetectorOptions);
|
|
161
|
+
/**
|
|
162
|
+
* 完整检测(同时获取支付能力和 Token 信息)
|
|
163
|
+
* 优先从缓存读取,缓存未命中时执行检测并缓存结果
|
|
164
|
+
*
|
|
165
|
+
* @param tokenAddress - Token 地址
|
|
166
|
+
* @returns 完整的检测结果
|
|
167
|
+
*/
|
|
168
|
+
detect(tokenAddress: string): Promise<TokenDetectionResult>;
|
|
169
|
+
/**
|
|
170
|
+
* 获取推荐的支付方式
|
|
171
|
+
* 优先级:eip3009 > permit > permit2
|
|
172
|
+
*
|
|
173
|
+
* @param tokenAddress - Token 地址
|
|
174
|
+
* @returns 推荐的支付方式
|
|
175
|
+
*/
|
|
176
|
+
getRecommendedMethod(tokenAddress: string): Promise<"eip3009" | "permit" | "permit2" | null>;
|
|
177
|
+
/**
|
|
178
|
+
* 批量初始化(预热缓存)
|
|
179
|
+
* 并行检测多个 Token 并缓存结果
|
|
180
|
+
*
|
|
181
|
+
* @param tokenAddresses - Token 地址列表
|
|
182
|
+
* @returns 检测结果数组
|
|
183
|
+
*/
|
|
184
|
+
initialize(tokenAddresses: string[]): Promise<TokenDetectionResult[]>;
|
|
185
|
+
/**
|
|
186
|
+
* 清除缓存
|
|
187
|
+
*
|
|
188
|
+
* @param tokenAddress - 可选,指定要清除的 Token 地址
|
|
189
|
+
*/
|
|
190
|
+
clearCache(tokenAddress?: string): Promise<void>;
|
|
191
|
+
/**
|
|
192
|
+
* 获取缓存统计
|
|
193
|
+
*
|
|
194
|
+
* @returns 缓存统计信息
|
|
195
|
+
*/
|
|
196
|
+
getCacheStats(): {
|
|
197
|
+
size: number;
|
|
198
|
+
keys: string[];
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* 生成缓存键
|
|
202
|
+
*
|
|
203
|
+
* @param tokenAddress - Token 地址
|
|
204
|
+
* @returns 缓存键
|
|
205
|
+
*/
|
|
206
|
+
private getCacheKey;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { EIP1822_IMPLEMENTATION_SLOT, EIP1967_IMPLEMENTATION_SLOT, EIP2612_PERMIT, EIP3009_SIGNATURES, type Logger, PERMIT2_ADDRESS, PRESET_TOKEN_CAPABILITIES, type PaymentMethod, type PresetTokenConfig, type TokenDetectionResult, TokenDetector, type TokenDetectorOptions, type TokenInfo, type TokenPaymentCapabilities, detectTokenPaymentMethods, getImplementationAddress, getRecommendedPaymentMethod, getTokenInfo };
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var EIP3009_SIGNATURES = ["0xe3ee160e", "0xcf092995"];
|
|
3
|
+
var EIP2612_PERMIT = "0xd505accf";
|
|
4
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
5
|
+
var EIP1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
6
|
+
var EIP1822_IMPLEMENTATION_SLOT = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3";
|
|
7
|
+
var PRESET_TOKEN_CAPABILITIES = {
|
|
8
|
+
// World Liberty Financial USD - 只支持 permit
|
|
9
|
+
"0x8d0d000ee44948fc98c9b98a4fa4921476f08b0d": {
|
|
10
|
+
supportedMethods: ["permit"],
|
|
11
|
+
supportedNetworks: [56],
|
|
12
|
+
// BSC
|
|
13
|
+
description: "World Liberty Financial USD (WLFI)"
|
|
14
|
+
}
|
|
15
|
+
// 可以继续添加更多预设代币
|
|
16
|
+
// 示例:
|
|
17
|
+
// "0x其他代币地址": {
|
|
18
|
+
// supportedMethods: ["eip3009", "permit"],
|
|
19
|
+
// supportedNetworks: [1, 56],
|
|
20
|
+
// description: "代币名称",
|
|
21
|
+
// },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/proxy.ts
|
|
25
|
+
var defaultLogger = {
|
|
26
|
+
log: (message) => console.log(message),
|
|
27
|
+
error: (message, error) => console.error(message, error)
|
|
28
|
+
};
|
|
29
|
+
async function getImplementationAddress(client, proxyAddress, logger = defaultLogger) {
|
|
30
|
+
try {
|
|
31
|
+
try {
|
|
32
|
+
const implSlotData = await client.getStorageAt({
|
|
33
|
+
address: proxyAddress,
|
|
34
|
+
slot: EIP1967_IMPLEMENTATION_SLOT
|
|
35
|
+
});
|
|
36
|
+
if (implSlotData && implSlotData !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
37
|
+
const implAddress = `0x${implSlotData.slice(-40)}`;
|
|
38
|
+
if (implAddress !== "0x0000000000000000000000000000000000000000") {
|
|
39
|
+
logger == null ? void 0 : logger.log(` \u{1F4E6} Detected EIP-1967 proxy, implementation: ${implAddress}`);
|
|
40
|
+
return implAddress;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const uupsSlotData = await client.getStorageAt({
|
|
47
|
+
address: proxyAddress,
|
|
48
|
+
slot: EIP1822_IMPLEMENTATION_SLOT
|
|
49
|
+
});
|
|
50
|
+
if (uupsSlotData && uupsSlotData !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
51
|
+
const implAddress = `0x${uupsSlotData.slice(-40)}`;
|
|
52
|
+
if (implAddress !== "0x0000000000000000000000000000000000000000") {
|
|
53
|
+
logger == null ? void 0 : logger.log(` \u{1F4E6} Detected EIP-1822 UUPS proxy, implementation: ${implAddress}`);
|
|
54
|
+
return implAddress;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const implABI = [
|
|
61
|
+
{
|
|
62
|
+
inputs: [],
|
|
63
|
+
name: "implementation",
|
|
64
|
+
outputs: [{ name: "", type: "address" }],
|
|
65
|
+
stateMutability: "view",
|
|
66
|
+
type: "function"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
const implAddress = await client.readContract({
|
|
70
|
+
address: proxyAddress,
|
|
71
|
+
abi: implABI,
|
|
72
|
+
functionName: "implementation"
|
|
73
|
+
});
|
|
74
|
+
if (implAddress && implAddress !== "0x0000000000000000000000000000000000000000") {
|
|
75
|
+
logger == null ? void 0 : logger.log(` \u{1F4E6} Detected proxy via implementation(), implementation: ${implAddress}`);
|
|
76
|
+
return implAddress;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger == null ? void 0 : logger.error("Error detecting proxy implementation:", error);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/detector.ts
|
|
88
|
+
var defaultLogger2 = {
|
|
89
|
+
log: (message) => console.log(message),
|
|
90
|
+
error: (message, error) => console.error(message, error)
|
|
91
|
+
};
|
|
92
|
+
async function hasMethod(client, tokenAddress, methodSelector, logger = defaultLogger2) {
|
|
93
|
+
try {
|
|
94
|
+
const code = await client.getCode({ address: tokenAddress });
|
|
95
|
+
if (!code) return false;
|
|
96
|
+
const hasMethodInProxy = code.toLowerCase().includes(methodSelector.slice(2).toLowerCase());
|
|
97
|
+
if (hasMethodInProxy) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const implAddress = await getImplementationAddress(client, tokenAddress, logger);
|
|
101
|
+
if (implAddress) {
|
|
102
|
+
const implCode = await client.getCode({ address: implAddress });
|
|
103
|
+
if (implCode) {
|
|
104
|
+
const hasMethodInImpl = implCode.toLowerCase().includes(methodSelector.slice(2).toLowerCase());
|
|
105
|
+
if (hasMethodInImpl) {
|
|
106
|
+
logger == null ? void 0 : logger.log(` \u2705 Method ${methodSelector} found in implementation contract`);
|
|
107
|
+
}
|
|
108
|
+
return hasMethodInImpl;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger == null ? void 0 : logger.error(`Error checking method ${methodSelector}:`, error);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function hasAnyMethod(client, tokenAddress, methodSelectors, logger = defaultLogger2) {
|
|
118
|
+
try {
|
|
119
|
+
const code = await client.getCode({ address: tokenAddress });
|
|
120
|
+
if (!code) return false;
|
|
121
|
+
const codeLower = code.toLowerCase();
|
|
122
|
+
const hasMethodInProxy = methodSelectors.some(
|
|
123
|
+
(selector) => codeLower.includes(selector.slice(2).toLowerCase())
|
|
124
|
+
);
|
|
125
|
+
if (hasMethodInProxy) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
const implAddress = await getImplementationAddress(client, tokenAddress, logger);
|
|
129
|
+
if (implAddress) {
|
|
130
|
+
const implCode = await client.getCode({ address: implAddress });
|
|
131
|
+
if (implCode) {
|
|
132
|
+
const implCodeLower = implCode.toLowerCase();
|
|
133
|
+
const hasMethodInImpl = methodSelectors.some(
|
|
134
|
+
(selector) => implCodeLower.includes(selector.slice(2).toLowerCase())
|
|
135
|
+
);
|
|
136
|
+
if (hasMethodInImpl) {
|
|
137
|
+
logger == null ? void 0 : logger.log(` \u2705 Method(s) found in implementation contract`);
|
|
138
|
+
}
|
|
139
|
+
return hasMethodInImpl;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
logger == null ? void 0 : logger.error(`Error checking methods ${methodSelectors.join(", ")}:`, error);
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function checkPermit2Support(client, logger = defaultLogger2) {
|
|
149
|
+
try {
|
|
150
|
+
const permit2Code = await client.getCode({ address: PERMIT2_ADDRESS });
|
|
151
|
+
if (!permit2Code) return false;
|
|
152
|
+
return true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger == null ? void 0 : logger.error("Error checking Permit2 support:", error);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function detectTokenPaymentMethods(tokenAddress, client, logger = defaultLogger2) {
|
|
159
|
+
const address = tokenAddress.toLowerCase();
|
|
160
|
+
const chainId = await client.getChainId();
|
|
161
|
+
const presetCapabilities = PRESET_TOKEN_CAPABILITIES[address];
|
|
162
|
+
if (presetCapabilities) {
|
|
163
|
+
if (!chainId || !presetCapabilities.supportedNetworks.includes(chainId)) {
|
|
164
|
+
return {
|
|
165
|
+
address,
|
|
166
|
+
supportedMethods: [],
|
|
167
|
+
details: {
|
|
168
|
+
hasEIP3009: false,
|
|
169
|
+
hasPermit: false,
|
|
170
|
+
hasPermit2Approval: false
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const hasEIP30092 = presetCapabilities.supportedMethods.includes("eip3009");
|
|
175
|
+
const hasPermit2 = presetCapabilities.supportedMethods.includes("permit");
|
|
176
|
+
const hasPermit2Approval2 = presetCapabilities.supportedMethods.includes("permit2") || presetCapabilities.supportedMethods.includes("permit2-witness");
|
|
177
|
+
if (hasEIP30092) {
|
|
178
|
+
logger == null ? void 0 : logger.log(" \u2705 EIP-3009 (transferWithAuthorization) - from preset");
|
|
179
|
+
}
|
|
180
|
+
if (hasPermit2) {
|
|
181
|
+
logger == null ? void 0 : logger.log(" \u2705 EIP-2612 (permit) - from preset");
|
|
182
|
+
}
|
|
183
|
+
if (hasPermit2Approval2) {
|
|
184
|
+
logger == null ? void 0 : logger.log(" \u2705 Permit2 support - from preset");
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
address,
|
|
188
|
+
supportedMethods: presetCapabilities.supportedMethods,
|
|
189
|
+
details: {
|
|
190
|
+
hasEIP3009: hasEIP30092,
|
|
191
|
+
hasPermit: hasPermit2,
|
|
192
|
+
hasPermit2Approval: hasPermit2Approval2
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
logger == null ? void 0 : logger.log(`\u{1F50D} Detecting payment methods for token ${address}...`);
|
|
197
|
+
const [hasEIP3009, hasPermit, hasPermit2Approval] = await Promise.all([
|
|
198
|
+
hasAnyMethod(client, address, EIP3009_SIGNATURES, logger),
|
|
199
|
+
hasMethod(client, address, EIP2612_PERMIT, logger),
|
|
200
|
+
checkPermit2Support(client, logger)
|
|
201
|
+
]);
|
|
202
|
+
const supportedMethods = [];
|
|
203
|
+
if (hasEIP3009) {
|
|
204
|
+
supportedMethods.push("eip3009");
|
|
205
|
+
logger == null ? void 0 : logger.log(" \u2705 EIP-3009 (transferWithAuthorization) detected");
|
|
206
|
+
}
|
|
207
|
+
if (hasPermit) {
|
|
208
|
+
supportedMethods.push("permit");
|
|
209
|
+
logger == null ? void 0 : logger.log(" \u2705 EIP-2612 (permit) detected");
|
|
210
|
+
}
|
|
211
|
+
if (hasPermit2Approval) {
|
|
212
|
+
supportedMethods.push("permit2");
|
|
213
|
+
supportedMethods.push("permit2-witness");
|
|
214
|
+
logger == null ? void 0 : logger.log(" \u2705 Permit2 support available (universal)");
|
|
215
|
+
}
|
|
216
|
+
if (supportedMethods.length === 0) {
|
|
217
|
+
logger == null ? void 0 : logger.log(" \u26A0\uFE0F No advanced payment methods detected (standard ERC-20 only)");
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
address,
|
|
221
|
+
supportedMethods,
|
|
222
|
+
details: {
|
|
223
|
+
hasEIP3009,
|
|
224
|
+
hasPermit,
|
|
225
|
+
hasPermit2Approval
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async function getRecommendedPaymentMethod(tokenAddress, client, logger = defaultLogger2) {
|
|
230
|
+
const capabilities = await detectTokenPaymentMethods(tokenAddress, client, logger);
|
|
231
|
+
const { supportedMethods } = capabilities;
|
|
232
|
+
if (supportedMethods.includes("eip3009")) return "eip3009";
|
|
233
|
+
if (supportedMethods.includes("permit")) return "permit";
|
|
234
|
+
if (supportedMethods.includes("permit2") || supportedMethods.includes("permit2-witness")) {
|
|
235
|
+
return "permit2";
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
async function getTokenInfo(tokenAddress, client, logger = defaultLogger2) {
|
|
240
|
+
const address = tokenAddress.toLowerCase();
|
|
241
|
+
const erc20ABI = [
|
|
242
|
+
{
|
|
243
|
+
inputs: [],
|
|
244
|
+
name: "name",
|
|
245
|
+
outputs: [{ name: "", type: "string" }],
|
|
246
|
+
stateMutability: "view",
|
|
247
|
+
type: "function"
|
|
248
|
+
}
|
|
249
|
+
];
|
|
250
|
+
const eip712DomainABI = [
|
|
251
|
+
{
|
|
252
|
+
inputs: [],
|
|
253
|
+
name: "eip712Domain",
|
|
254
|
+
outputs: [
|
|
255
|
+
{ name: "fields", type: "bytes1" },
|
|
256
|
+
{ name: "name", type: "string" },
|
|
257
|
+
{ name: "version", type: "string" },
|
|
258
|
+
{ name: "chainId", type: "uint256" },
|
|
259
|
+
{ name: "verifyingContract", type: "address" },
|
|
260
|
+
{ name: "salt", type: "bytes32" },
|
|
261
|
+
{ name: "extensions", type: "uint256[]" }
|
|
262
|
+
],
|
|
263
|
+
stateMutability: "view",
|
|
264
|
+
type: "function"
|
|
265
|
+
}
|
|
266
|
+
];
|
|
267
|
+
const versionABI = [
|
|
268
|
+
{
|
|
269
|
+
inputs: [],
|
|
270
|
+
name: "version",
|
|
271
|
+
outputs: [{ name: "", type: "string" }],
|
|
272
|
+
stateMutability: "view",
|
|
273
|
+
type: "function"
|
|
274
|
+
}
|
|
275
|
+
];
|
|
276
|
+
try {
|
|
277
|
+
const implAddress = await getImplementationAddress(client, address, logger);
|
|
278
|
+
if (implAddress) {
|
|
279
|
+
logger == null ? void 0 : logger.log(
|
|
280
|
+
` \u{1F4E6} Reading token info from proxy, actual calls will be delegated to implementation`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const name = await client.readContract({
|
|
284
|
+
address,
|
|
285
|
+
abi: erc20ABI,
|
|
286
|
+
functionName: "name"
|
|
287
|
+
});
|
|
288
|
+
let version = "1";
|
|
289
|
+
try {
|
|
290
|
+
const result = await client.readContract({
|
|
291
|
+
address,
|
|
292
|
+
abi: eip712DomainABI,
|
|
293
|
+
functionName: "eip712Domain"
|
|
294
|
+
});
|
|
295
|
+
version = result[2];
|
|
296
|
+
} catch {
|
|
297
|
+
try {
|
|
298
|
+
version = await client.readContract({
|
|
299
|
+
address,
|
|
300
|
+
abi: versionABI,
|
|
301
|
+
functionName: "version"
|
|
302
|
+
});
|
|
303
|
+
} catch {
|
|
304
|
+
logger == null ? void 0 : logger.log(` \u2139\uFE0F Using default version "1" for token ${address}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
name,
|
|
309
|
+
version
|
|
310
|
+
};
|
|
311
|
+
} catch (error) {
|
|
312
|
+
logger == null ? void 0 : logger.error(`Error getting token info for ${address}:`, error);
|
|
313
|
+
throw new Error(`Failed to get token info: ${error}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/cache.ts
|
|
318
|
+
var defaultLogger3 = {
|
|
319
|
+
log: (message) => console.log(message),
|
|
320
|
+
error: (message, error) => console.error(message, error)
|
|
321
|
+
};
|
|
322
|
+
var TokenDetector = class {
|
|
323
|
+
/**
|
|
324
|
+
* 构造函数
|
|
325
|
+
*
|
|
326
|
+
* @param client - viem PublicClient
|
|
327
|
+
* @param options - 可选配置
|
|
328
|
+
*/
|
|
329
|
+
constructor(client, options) {
|
|
330
|
+
/** 缓存存储 (chainId:address -> TokenDetectionResult) */
|
|
331
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
332
|
+
this.client = client;
|
|
333
|
+
this.logger = (options == null ? void 0 : options.logger) === null ? null : (options == null ? void 0 : options.logger) || defaultLogger3;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 完整检测(同时获取支付能力和 Token 信息)
|
|
337
|
+
* 优先从缓存读取,缓存未命中时执行检测并缓存结果
|
|
338
|
+
*
|
|
339
|
+
* @param tokenAddress - Token 地址
|
|
340
|
+
* @returns 完整的检测结果
|
|
341
|
+
*/
|
|
342
|
+
async detect(tokenAddress) {
|
|
343
|
+
var _a, _b;
|
|
344
|
+
const cacheKey = await this.getCacheKey(tokenAddress);
|
|
345
|
+
const cached = this.cache.get(cacheKey);
|
|
346
|
+
if (cached) {
|
|
347
|
+
(_a = this.logger) == null ? void 0 : _a.log(`\u{1F4BE} Using cached result for token ${tokenAddress}`);
|
|
348
|
+
return cached;
|
|
349
|
+
}
|
|
350
|
+
(_b = this.logger) == null ? void 0 : _b.log(`\u{1F50D} Detecting token ${tokenAddress}...`);
|
|
351
|
+
const [capabilities, info] = await Promise.all([
|
|
352
|
+
detectTokenPaymentMethods(tokenAddress, this.client, this.logger),
|
|
353
|
+
getTokenInfo(tokenAddress, this.client, this.logger)
|
|
354
|
+
]);
|
|
355
|
+
const result = {
|
|
356
|
+
...capabilities,
|
|
357
|
+
...info
|
|
358
|
+
};
|
|
359
|
+
this.cache.set(cacheKey, result);
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 获取推荐的支付方式
|
|
364
|
+
* 优先级:eip3009 > permit > permit2
|
|
365
|
+
*
|
|
366
|
+
* @param tokenAddress - Token 地址
|
|
367
|
+
* @returns 推荐的支付方式
|
|
368
|
+
*/
|
|
369
|
+
async getRecommendedMethod(tokenAddress) {
|
|
370
|
+
const result = await this.detect(tokenAddress);
|
|
371
|
+
const { supportedMethods } = result;
|
|
372
|
+
if (supportedMethods.includes("eip3009")) return "eip3009";
|
|
373
|
+
if (supportedMethods.includes("permit")) return "permit";
|
|
374
|
+
if (supportedMethods.includes("permit2") || supportedMethods.includes("permit2-witness")) {
|
|
375
|
+
return "permit2";
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 批量初始化(预热缓存)
|
|
381
|
+
* 并行检测多个 Token 并缓存结果
|
|
382
|
+
*
|
|
383
|
+
* @param tokenAddresses - Token 地址列表
|
|
384
|
+
* @returns 检测结果数组
|
|
385
|
+
*/
|
|
386
|
+
async initialize(tokenAddresses) {
|
|
387
|
+
var _a, _b;
|
|
388
|
+
(_a = this.logger) == null ? void 0 : _a.log(`\u{1F525} Warming up cache for ${tokenAddresses.length} tokens...`);
|
|
389
|
+
const results = await Promise.all(
|
|
390
|
+
tokenAddresses.map(
|
|
391
|
+
(address) => this.detect(address).catch((error) => {
|
|
392
|
+
var _a2;
|
|
393
|
+
(_a2 = this.logger) == null ? void 0 : _a2.error(`Failed to detect token ${address}:`, error);
|
|
394
|
+
return null;
|
|
395
|
+
})
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
const successCount = results.filter((r) => r !== null).length;
|
|
399
|
+
(_b = this.logger) == null ? void 0 : _b.log(`\u2705 Successfully detected ${successCount}/${tokenAddresses.length} tokens`);
|
|
400
|
+
return results.filter((r) => r !== null);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 清除缓存
|
|
404
|
+
*
|
|
405
|
+
* @param tokenAddress - 可选,指定要清除的 Token 地址
|
|
406
|
+
*/
|
|
407
|
+
async clearCache(tokenAddress) {
|
|
408
|
+
var _a, _b;
|
|
409
|
+
if (tokenAddress) {
|
|
410
|
+
const cacheKey = await this.getCacheKey(tokenAddress);
|
|
411
|
+
this.cache.delete(cacheKey);
|
|
412
|
+
(_a = this.logger) == null ? void 0 : _a.log(`\u{1F5D1}\uFE0F Cleared cache for token ${tokenAddress}`);
|
|
413
|
+
} else {
|
|
414
|
+
this.cache.clear();
|
|
415
|
+
(_b = this.logger) == null ? void 0 : _b.log(`\u{1F5D1}\uFE0F Cleared all cache`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* 获取缓存统计
|
|
420
|
+
*
|
|
421
|
+
* @returns 缓存统计信息
|
|
422
|
+
*/
|
|
423
|
+
getCacheStats() {
|
|
424
|
+
return {
|
|
425
|
+
size: this.cache.size,
|
|
426
|
+
keys: Array.from(this.cache.keys())
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 生成缓存键
|
|
431
|
+
*
|
|
432
|
+
* @param tokenAddress - Token 地址
|
|
433
|
+
* @returns 缓存键
|
|
434
|
+
*/
|
|
435
|
+
async getCacheKey(tokenAddress) {
|
|
436
|
+
const chainId = await this.client.getChainId();
|
|
437
|
+
return `${chainId}:${tokenAddress.toLowerCase()}`;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
export {
|
|
441
|
+
EIP1822_IMPLEMENTATION_SLOT,
|
|
442
|
+
EIP1967_IMPLEMENTATION_SLOT,
|
|
443
|
+
EIP2612_PERMIT,
|
|
444
|
+
EIP3009_SIGNATURES,
|
|
445
|
+
PERMIT2_ADDRESS,
|
|
446
|
+
PRESET_TOKEN_CAPABILITIES,
|
|
447
|
+
TokenDetector,
|
|
448
|
+
detectTokenPaymentMethods,
|
|
449
|
+
getImplementationAddress,
|
|
450
|
+
getRecommendedPaymentMethod,
|
|
451
|
+
getTokenInfo
|
|
452
|
+
};
|
|
453
|
+
//# sourceMappingURL=index.mjs.map
|