@perkos/util-tokens 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @perkos/util-tokens
2
+
3
+ ERC20 token detection utilities for reading on-chain token metadata. Essential for EIP-712 domain construction in payment signatures.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @perkos/util-tokens
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { detectTokenInfo, getChainId, getRpcUrl } from "@perkos/util-tokens";
15
+
16
+ // Detect token information from contract
17
+ const tokenInfo = await detectTokenInfo(
18
+ "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
19
+ "avalanche"
20
+ );
21
+
22
+ console.log(tokenInfo);
23
+ // {
24
+ // address: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
25
+ // name: "USD Coin",
26
+ // symbol: "USDC",
27
+ // decimals: 6,
28
+ // chainId: 43114
29
+ // }
30
+
31
+ // Get chain ID for a network
32
+ const chainId = getChainId("base-sepolia"); // 84532
33
+
34
+ // Get RPC URL
35
+ const rpcUrl = getRpcUrl("avalanche");
36
+ ```
37
+
38
+ ## Features
39
+
40
+ - **Automatic Caching**: Token info is cached to avoid repeated RPC calls
41
+ - **Multi-Network Support**: Avalanche, Base, Celo (mainnet and testnets)
42
+ - **CAIP-2 Support**: Accepts both legacy and CAIP-2 network identifiers
43
+ - **TypeScript**: Full type definitions included
44
+
45
+ ## API
46
+
47
+ ### `detectTokenInfo(tokenAddress, network, config?)`
48
+
49
+ Detect token information from a contract address.
50
+
51
+ - `tokenAddress`: ERC20 token contract address
52
+ - `network`: Network name or CAIP-2 identifier
53
+ - `config`: Optional configuration with custom RPC URL
54
+
55
+ Returns `TokenInfo | null`
56
+
57
+ ### `getChainId(network)`
58
+
59
+ Get chain ID for a network name.
60
+
61
+ ### `getRpcUrl(network, customUrl?)`
62
+
63
+ Get RPC URL for a network.
64
+
65
+ ### `clearTokenCache()`
66
+
67
+ Clear the token info cache.
68
+
69
+ ## Supported Networks
70
+
71
+ - Avalanche (43114)
72
+ - Avalanche Fuji (43113)
73
+ - Base (8453)
74
+ - Base Sepolia (84532)
75
+ - Celo (42220)
76
+ - Celo Sepolia (11142220)
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,81 @@
1
+ import { Address, PublicClient } from 'viem';
2
+
3
+ /**
4
+ * @perkos/token-detection
5
+ * ERC20 token detection utilities for reading on-chain token metadata
6
+ */
7
+
8
+ /**
9
+ * Token information returned from detection
10
+ */
11
+ interface TokenInfo {
12
+ address: Address;
13
+ name: string;
14
+ symbol: string;
15
+ decimals: number;
16
+ chainId: number;
17
+ }
18
+ /**
19
+ * Supported network names
20
+ */
21
+ type NetworkName = "avalanche" | "avalanche-fuji" | "base" | "base-sepolia" | "celo" | "celo-sepolia";
22
+ /**
23
+ * Network configuration options
24
+ */
25
+ interface NetworkConfig {
26
+ rpcUrl?: string;
27
+ }
28
+ /**
29
+ * Convert CAIP-2 network format to legacy format
30
+ * e.g., "eip155:43114" → "avalanche"
31
+ */
32
+ declare function toLegacyNetwork(network: string): NetworkName;
33
+ /**
34
+ * Get chain ID for a network
35
+ */
36
+ declare function getChainId(network: string): number;
37
+ /**
38
+ * Get RPC URL for a network
39
+ */
40
+ declare function getRpcUrl(network: string, customUrl?: string): string;
41
+ /**
42
+ * Get public client for a network
43
+ */
44
+ declare function getPublicClient(network: string, config?: NetworkConfig): PublicClient;
45
+ /**
46
+ * Detect token information from contract address
47
+ * Results are cached to avoid repeated RPC calls
48
+ *
49
+ * @param tokenAddress - The ERC20 token contract address
50
+ * @param network - Network name or CAIP-2 identifier
51
+ * @param config - Optional network configuration
52
+ * @returns Token information or null if detection fails
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const tokenInfo = await detectTokenInfo(
57
+ * "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
58
+ * "avalanche"
59
+ * );
60
+ * // => { name: "USD Coin", symbol: "USDC", decimals: 6, ... }
61
+ * ```
62
+ */
63
+ declare function detectTokenInfo(tokenAddress: Address, network: string, config?: NetworkConfig): Promise<TokenInfo | null>;
64
+ /**
65
+ * Clear the token info cache
66
+ */
67
+ declare function clearTokenCache(): void;
68
+ /**
69
+ * Get cached token info without RPC call
70
+ */
71
+ declare function getCachedTokenInfo(tokenAddress: Address, network: string): TokenInfo | undefined;
72
+ /**
73
+ * Check if a network is valid
74
+ */
75
+ declare function isValidNetwork(network: string): boolean;
76
+ /**
77
+ * Get all supported networks
78
+ */
79
+ declare function getSupportedNetworks(): NetworkName[];
80
+
81
+ export { type NetworkConfig, type NetworkName, type TokenInfo, clearTokenCache, detectTokenInfo, getCachedTokenInfo, getChainId, getPublicClient, getRpcUrl, getSupportedNetworks, isValidNetwork, toLegacyNetwork };
@@ -0,0 +1,81 @@
1
+ import { Address, PublicClient } from 'viem';
2
+
3
+ /**
4
+ * @perkos/token-detection
5
+ * ERC20 token detection utilities for reading on-chain token metadata
6
+ */
7
+
8
+ /**
9
+ * Token information returned from detection
10
+ */
11
+ interface TokenInfo {
12
+ address: Address;
13
+ name: string;
14
+ symbol: string;
15
+ decimals: number;
16
+ chainId: number;
17
+ }
18
+ /**
19
+ * Supported network names
20
+ */
21
+ type NetworkName = "avalanche" | "avalanche-fuji" | "base" | "base-sepolia" | "celo" | "celo-sepolia";
22
+ /**
23
+ * Network configuration options
24
+ */
25
+ interface NetworkConfig {
26
+ rpcUrl?: string;
27
+ }
28
+ /**
29
+ * Convert CAIP-2 network format to legacy format
30
+ * e.g., "eip155:43114" → "avalanche"
31
+ */
32
+ declare function toLegacyNetwork(network: string): NetworkName;
33
+ /**
34
+ * Get chain ID for a network
35
+ */
36
+ declare function getChainId(network: string): number;
37
+ /**
38
+ * Get RPC URL for a network
39
+ */
40
+ declare function getRpcUrl(network: string, customUrl?: string): string;
41
+ /**
42
+ * Get public client for a network
43
+ */
44
+ declare function getPublicClient(network: string, config?: NetworkConfig): PublicClient;
45
+ /**
46
+ * Detect token information from contract address
47
+ * Results are cached to avoid repeated RPC calls
48
+ *
49
+ * @param tokenAddress - The ERC20 token contract address
50
+ * @param network - Network name or CAIP-2 identifier
51
+ * @param config - Optional network configuration
52
+ * @returns Token information or null if detection fails
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const tokenInfo = await detectTokenInfo(
57
+ * "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
58
+ * "avalanche"
59
+ * );
60
+ * // => { name: "USD Coin", symbol: "USDC", decimals: 6, ... }
61
+ * ```
62
+ */
63
+ declare function detectTokenInfo(tokenAddress: Address, network: string, config?: NetworkConfig): Promise<TokenInfo | null>;
64
+ /**
65
+ * Clear the token info cache
66
+ */
67
+ declare function clearTokenCache(): void;
68
+ /**
69
+ * Get cached token info without RPC call
70
+ */
71
+ declare function getCachedTokenInfo(tokenAddress: Address, network: string): TokenInfo | undefined;
72
+ /**
73
+ * Check if a network is valid
74
+ */
75
+ declare function isValidNetwork(network: string): boolean;
76
+ /**
77
+ * Get all supported networks
78
+ */
79
+ declare function getSupportedNetworks(): NetworkName[];
80
+
81
+ export { type NetworkConfig, type NetworkName, type TokenInfo, clearTokenCache, detectTokenInfo, getCachedTokenInfo, getChainId, getPublicClient, getRpcUrl, getSupportedNetworks, isValidNetwork, toLegacyNetwork };
package/dist/index.js ADDED
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ clearTokenCache: () => clearTokenCache,
24
+ detectTokenInfo: () => detectTokenInfo,
25
+ getCachedTokenInfo: () => getCachedTokenInfo,
26
+ getChainId: () => getChainId,
27
+ getPublicClient: () => getPublicClient,
28
+ getRpcUrl: () => getRpcUrl,
29
+ getSupportedNetworks: () => getSupportedNetworks,
30
+ isValidNetwork: () => isValidNetwork,
31
+ toLegacyNetwork: () => toLegacyNetwork
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+ var import_viem = require("viem");
35
+ var import_chains = require("viem/chains");
36
+ var ERC20_ABI = [
37
+ {
38
+ constant: true,
39
+ inputs: [],
40
+ name: "name",
41
+ outputs: [{ name: "", type: "string" }],
42
+ type: "function"
43
+ },
44
+ {
45
+ constant: true,
46
+ inputs: [],
47
+ name: "symbol",
48
+ outputs: [{ name: "", type: "string" }],
49
+ type: "function"
50
+ },
51
+ {
52
+ constant: true,
53
+ inputs: [],
54
+ name: "decimals",
55
+ outputs: [{ name: "", type: "uint8" }],
56
+ type: "function"
57
+ }
58
+ ];
59
+ var CHAIN_IDS = {
60
+ avalanche: 43114,
61
+ "avalanche-fuji": 43113,
62
+ base: 8453,
63
+ "base-sepolia": 84532,
64
+ celo: 42220,
65
+ "celo-sepolia": 11142220,
66
+ // CAIP-2 format support
67
+ "eip155:43114": 43114,
68
+ "eip155:43113": 43113,
69
+ "eip155:8453": 8453,
70
+ "eip155:84532": 84532,
71
+ "eip155:42220": 42220,
72
+ "eip155:11142220": 11142220
73
+ };
74
+ var DEFAULT_RPC_URLS = {
75
+ avalanche: "https://api.avax.network/ext/bc/C/rpc",
76
+ "avalanche-fuji": "https://api.avax-test.network/ext/bc/C/rpc",
77
+ base: "https://mainnet.base.org",
78
+ "base-sepolia": "https://sepolia.base.org",
79
+ celo: "https://forno.celo.org",
80
+ "celo-sepolia": "https://forno.celo-sepolia.celo-testnet.org"
81
+ };
82
+ var CHAINS = {
83
+ avalanche: import_chains.avalanche,
84
+ "avalanche-fuji": import_chains.avalancheFuji,
85
+ base: import_chains.base,
86
+ "base-sepolia": import_chains.baseSepolia,
87
+ celo: import_chains.celo,
88
+ "celo-sepolia": import_chains.celoAlfajores
89
+ };
90
+ var CAIP2_TO_LEGACY = {
91
+ "eip155:43114": "avalanche",
92
+ "eip155:43113": "avalanche-fuji",
93
+ "eip155:8453": "base",
94
+ "eip155:84532": "base-sepolia",
95
+ "eip155:42220": "celo",
96
+ "eip155:11142220": "celo-sepolia"
97
+ };
98
+ var tokenInfoCache = /* @__PURE__ */ new Map();
99
+ function toLegacyNetwork(network) {
100
+ if (!network.includes(":")) {
101
+ return network;
102
+ }
103
+ return CAIP2_TO_LEGACY[network] || "avalanche";
104
+ }
105
+ function getChainId(network) {
106
+ return CHAIN_IDS[network] || CHAIN_IDS.avalanche;
107
+ }
108
+ function getRpcUrl(network, customUrl) {
109
+ if (customUrl) return customUrl;
110
+ const legacyNetwork = toLegacyNetwork(network);
111
+ return DEFAULT_RPC_URLS[legacyNetwork] || DEFAULT_RPC_URLS.avalanche;
112
+ }
113
+ function getPublicClient(network, config) {
114
+ const legacyNetwork = toLegacyNetwork(network);
115
+ const rpcUrl = getRpcUrl(network, config?.rpcUrl);
116
+ const chain = CHAINS[legacyNetwork] || {
117
+ id: getChainId(legacyNetwork),
118
+ name: legacyNetwork,
119
+ network: legacyNetwork,
120
+ nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
121
+ rpcUrls: { default: { http: [rpcUrl] } }
122
+ };
123
+ return (0, import_viem.createPublicClient)({
124
+ chain,
125
+ transport: (0, import_viem.http)(rpcUrl)
126
+ });
127
+ }
128
+ async function detectTokenInfo(tokenAddress, network, config) {
129
+ const legacyNetwork = toLegacyNetwork(network);
130
+ const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;
131
+ if (tokenInfoCache.has(cacheKey)) {
132
+ return tokenInfoCache.get(cacheKey);
133
+ }
134
+ try {
135
+ const client = getPublicClient(legacyNetwork, config);
136
+ const chainId = getChainId(legacyNetwork);
137
+ const [name, symbol, decimals] = await Promise.all([
138
+ client.readContract({
139
+ address: tokenAddress,
140
+ abi: ERC20_ABI,
141
+ functionName: "name"
142
+ }),
143
+ client.readContract({
144
+ address: tokenAddress,
145
+ abi: ERC20_ABI,
146
+ functionName: "symbol"
147
+ }),
148
+ client.readContract({
149
+ address: tokenAddress,
150
+ abi: ERC20_ABI,
151
+ functionName: "decimals"
152
+ })
153
+ ]);
154
+ const tokenInfo = {
155
+ address: tokenAddress,
156
+ name: name || "Unknown Token",
157
+ symbol: symbol || "UNKNOWN",
158
+ decimals: decimals || 18,
159
+ chainId
160
+ };
161
+ tokenInfoCache.set(cacheKey, tokenInfo);
162
+ return tokenInfo;
163
+ } catch (error) {
164
+ console.error("Failed to detect token info:", error);
165
+ return null;
166
+ }
167
+ }
168
+ function clearTokenCache() {
169
+ tokenInfoCache.clear();
170
+ }
171
+ function getCachedTokenInfo(tokenAddress, network) {
172
+ const legacyNetwork = toLegacyNetwork(network);
173
+ const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;
174
+ return tokenInfoCache.get(cacheKey);
175
+ }
176
+ function isValidNetwork(network) {
177
+ const legacyNetwork = toLegacyNetwork(network);
178
+ return legacyNetwork in CHAINS;
179
+ }
180
+ function getSupportedNetworks() {
181
+ return Object.keys(CHAINS);
182
+ }
183
+ // Annotate the CommonJS export names for ESM import in node:
184
+ 0 && (module.exports = {
185
+ clearTokenCache,
186
+ detectTokenInfo,
187
+ getCachedTokenInfo,
188
+ getChainId,
189
+ getPublicClient,
190
+ getRpcUrl,
191
+ getSupportedNetworks,
192
+ isValidNetwork,
193
+ toLegacyNetwork
194
+ });
195
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @perkos/token-detection\n * ERC20 token detection utilities for reading on-chain token metadata\n */\n\nimport { createPublicClient, http, type Address, type PublicClient } from \"viem\";\nimport {\n avalanche,\n avalancheFuji,\n base,\n baseSepolia,\n celo,\n celoAlfajores,\n} from \"viem/chains\";\n\n/**\n * Token information returned from detection\n */\nexport interface TokenInfo {\n address: Address;\n name: string;\n symbol: string;\n decimals: number;\n chainId: number;\n}\n\n/**\n * Supported network names\n */\nexport type NetworkName =\n | \"avalanche\"\n | \"avalanche-fuji\"\n | \"base\"\n | \"base-sepolia\"\n | \"celo\"\n | \"celo-sepolia\";\n\n/**\n * Network configuration options\n */\nexport interface NetworkConfig {\n rpcUrl?: string;\n}\n\n/**\n * Standard ERC20 ABI (minimal - just what we need)\n */\nconst ERC20_ABI = [\n {\n constant: true,\n inputs: [],\n name: \"name\",\n outputs: [{ name: \"\", type: \"string\" }],\n type: \"function\",\n },\n {\n constant: true,\n inputs: [],\n name: \"symbol\",\n outputs: [{ name: \"\", type: \"string\" }],\n type: \"function\",\n },\n {\n constant: true,\n inputs: [],\n name: \"decimals\",\n outputs: [{ name: \"\", type: \"uint8\" }],\n type: \"function\",\n },\n] as const;\n\n/**\n * Chain ID mappings\n */\nconst CHAIN_IDS: Record<string, number> = {\n avalanche: 43114,\n \"avalanche-fuji\": 43113,\n base: 8453,\n \"base-sepolia\": 84532,\n celo: 42220,\n \"celo-sepolia\": 11142220,\n // CAIP-2 format support\n \"eip155:43114\": 43114,\n \"eip155:43113\": 43113,\n \"eip155:8453\": 8453,\n \"eip155:84532\": 84532,\n \"eip155:42220\": 42220,\n \"eip155:11142220\": 11142220,\n};\n\n/**\n * Default RPC URLs\n */\nconst DEFAULT_RPC_URLS: Record<NetworkName, string> = {\n avalanche: \"https://api.avax.network/ext/bc/C/rpc\",\n \"avalanche-fuji\": \"https://api.avax-test.network/ext/bc/C/rpc\",\n base: \"https://mainnet.base.org\",\n \"base-sepolia\": \"https://sepolia.base.org\",\n celo: \"https://forno.celo.org\",\n \"celo-sepolia\": \"https://forno.celo-sepolia.celo-testnet.org\",\n};\n\n/**\n * Chain definitions\n */\nconst CHAINS: Record<NetworkName, any> = {\n avalanche,\n \"avalanche-fuji\": avalancheFuji,\n base,\n \"base-sepolia\": baseSepolia,\n celo,\n \"celo-sepolia\": celoAlfajores,\n};\n\n/**\n * CAIP-2 to legacy network mapping\n */\nconst CAIP2_TO_LEGACY: Record<string, NetworkName> = {\n \"eip155:43114\": \"avalanche\",\n \"eip155:43113\": \"avalanche-fuji\",\n \"eip155:8453\": \"base\",\n \"eip155:84532\": \"base-sepolia\",\n \"eip155:42220\": \"celo\",\n \"eip155:11142220\": \"celo-sepolia\",\n};\n\n// Token info cache to avoid repeated RPC calls\nconst tokenInfoCache = new Map<string, TokenInfo>();\n\n/**\n * Convert CAIP-2 network format to legacy format\n * e.g., \"eip155:43114\" → \"avalanche\"\n */\nexport function toLegacyNetwork(network: string): NetworkName {\n if (!network.includes(\":\")) {\n return network as NetworkName;\n }\n return CAIP2_TO_LEGACY[network] || \"avalanche\";\n}\n\n/**\n * Get chain ID for a network\n */\nexport function getChainId(network: string): number {\n return CHAIN_IDS[network] || CHAIN_IDS.avalanche;\n}\n\n/**\n * Get RPC URL for a network\n */\nexport function getRpcUrl(network: string, customUrl?: string): string {\n if (customUrl) return customUrl;\n const legacyNetwork = toLegacyNetwork(network);\n return DEFAULT_RPC_URLS[legacyNetwork] || DEFAULT_RPC_URLS.avalanche;\n}\n\n/**\n * Get public client for a network\n */\nexport function getPublicClient(\n network: string,\n config?: NetworkConfig\n): PublicClient {\n const legacyNetwork = toLegacyNetwork(network);\n const rpcUrl = getRpcUrl(network, config?.rpcUrl);\n const chain = CHAINS[legacyNetwork] || {\n id: getChainId(legacyNetwork),\n name: legacyNetwork,\n network: legacyNetwork,\n nativeCurrency: { name: \"ETH\", symbol: \"ETH\", decimals: 18 },\n rpcUrls: { default: { http: [rpcUrl] } },\n };\n\n return createPublicClient({\n chain,\n transport: http(rpcUrl),\n }) as PublicClient;\n}\n\n/**\n * Detect token information from contract address\n * Results are cached to avoid repeated RPC calls\n *\n * @param tokenAddress - The ERC20 token contract address\n * @param network - Network name or CAIP-2 identifier\n * @param config - Optional network configuration\n * @returns Token information or null if detection fails\n *\n * @example\n * ```typescript\n * const tokenInfo = await detectTokenInfo(\n * \"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E\",\n * \"avalanche\"\n * );\n * // => { name: \"USD Coin\", symbol: \"USDC\", decimals: 6, ... }\n * ```\n */\nexport async function detectTokenInfo(\n tokenAddress: Address,\n network: string,\n config?: NetworkConfig\n): Promise<TokenInfo | null> {\n const legacyNetwork = toLegacyNetwork(network);\n const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;\n\n // Check cache first\n if (tokenInfoCache.has(cacheKey)) {\n return tokenInfoCache.get(cacheKey)!;\n }\n\n try {\n const client = getPublicClient(legacyNetwork, config);\n const chainId = getChainId(legacyNetwork);\n\n // Read token info in parallel\n const [name, symbol, decimals] = await Promise.all([\n client.readContract({\n address: tokenAddress,\n abi: ERC20_ABI,\n functionName: \"name\",\n }) as Promise<string>,\n client.readContract({\n address: tokenAddress,\n abi: ERC20_ABI,\n functionName: \"symbol\",\n }) as Promise<string>,\n client.readContract({\n address: tokenAddress,\n abi: ERC20_ABI,\n functionName: \"decimals\",\n }) as Promise<number>,\n ]);\n\n const tokenInfo: TokenInfo = {\n address: tokenAddress,\n name: name || \"Unknown Token\",\n symbol: symbol || \"UNKNOWN\",\n decimals: decimals || 18,\n chainId,\n };\n\n // Cache the result\n tokenInfoCache.set(cacheKey, tokenInfo);\n\n return tokenInfo;\n } catch (error) {\n console.error(\"Failed to detect token info:\", error);\n return null;\n }\n}\n\n/**\n * Clear the token info cache\n */\nexport function clearTokenCache(): void {\n tokenInfoCache.clear();\n}\n\n/**\n * Get cached token info without RPC call\n */\nexport function getCachedTokenInfo(\n tokenAddress: Address,\n network: string\n): TokenInfo | undefined {\n const legacyNetwork = toLegacyNetwork(network);\n const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;\n return tokenInfoCache.get(cacheKey);\n}\n\n/**\n * Check if a network is valid\n */\nexport function isValidNetwork(network: string): boolean {\n const legacyNetwork = toLegacyNetwork(network);\n return legacyNetwork in CHAINS;\n}\n\n/**\n * Get all supported networks\n */\nexport function getSupportedNetworks(): NetworkName[] {\n return Object.keys(CHAINS) as NetworkName[];\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,169 @@
1
+ // src/index.ts
2
+ import { createPublicClient, http } from "viem";
3
+ import {
4
+ avalanche,
5
+ avalancheFuji,
6
+ base,
7
+ baseSepolia,
8
+ celo,
9
+ celoAlfajores
10
+ } from "viem/chains";
11
+ var ERC20_ABI = [
12
+ {
13
+ constant: true,
14
+ inputs: [],
15
+ name: "name",
16
+ outputs: [{ name: "", type: "string" }],
17
+ type: "function"
18
+ },
19
+ {
20
+ constant: true,
21
+ inputs: [],
22
+ name: "symbol",
23
+ outputs: [{ name: "", type: "string" }],
24
+ type: "function"
25
+ },
26
+ {
27
+ constant: true,
28
+ inputs: [],
29
+ name: "decimals",
30
+ outputs: [{ name: "", type: "uint8" }],
31
+ type: "function"
32
+ }
33
+ ];
34
+ var CHAIN_IDS = {
35
+ avalanche: 43114,
36
+ "avalanche-fuji": 43113,
37
+ base: 8453,
38
+ "base-sepolia": 84532,
39
+ celo: 42220,
40
+ "celo-sepolia": 11142220,
41
+ // CAIP-2 format support
42
+ "eip155:43114": 43114,
43
+ "eip155:43113": 43113,
44
+ "eip155:8453": 8453,
45
+ "eip155:84532": 84532,
46
+ "eip155:42220": 42220,
47
+ "eip155:11142220": 11142220
48
+ };
49
+ var DEFAULT_RPC_URLS = {
50
+ avalanche: "https://api.avax.network/ext/bc/C/rpc",
51
+ "avalanche-fuji": "https://api.avax-test.network/ext/bc/C/rpc",
52
+ base: "https://mainnet.base.org",
53
+ "base-sepolia": "https://sepolia.base.org",
54
+ celo: "https://forno.celo.org",
55
+ "celo-sepolia": "https://forno.celo-sepolia.celo-testnet.org"
56
+ };
57
+ var CHAINS = {
58
+ avalanche,
59
+ "avalanche-fuji": avalancheFuji,
60
+ base,
61
+ "base-sepolia": baseSepolia,
62
+ celo,
63
+ "celo-sepolia": celoAlfajores
64
+ };
65
+ var CAIP2_TO_LEGACY = {
66
+ "eip155:43114": "avalanche",
67
+ "eip155:43113": "avalanche-fuji",
68
+ "eip155:8453": "base",
69
+ "eip155:84532": "base-sepolia",
70
+ "eip155:42220": "celo",
71
+ "eip155:11142220": "celo-sepolia"
72
+ };
73
+ var tokenInfoCache = /* @__PURE__ */ new Map();
74
+ function toLegacyNetwork(network) {
75
+ if (!network.includes(":")) {
76
+ return network;
77
+ }
78
+ return CAIP2_TO_LEGACY[network] || "avalanche";
79
+ }
80
+ function getChainId(network) {
81
+ return CHAIN_IDS[network] || CHAIN_IDS.avalanche;
82
+ }
83
+ function getRpcUrl(network, customUrl) {
84
+ if (customUrl) return customUrl;
85
+ const legacyNetwork = toLegacyNetwork(network);
86
+ return DEFAULT_RPC_URLS[legacyNetwork] || DEFAULT_RPC_URLS.avalanche;
87
+ }
88
+ function getPublicClient(network, config) {
89
+ const legacyNetwork = toLegacyNetwork(network);
90
+ const rpcUrl = getRpcUrl(network, config?.rpcUrl);
91
+ const chain = CHAINS[legacyNetwork] || {
92
+ id: getChainId(legacyNetwork),
93
+ name: legacyNetwork,
94
+ network: legacyNetwork,
95
+ nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
96
+ rpcUrls: { default: { http: [rpcUrl] } }
97
+ };
98
+ return createPublicClient({
99
+ chain,
100
+ transport: http(rpcUrl)
101
+ });
102
+ }
103
+ async function detectTokenInfo(tokenAddress, network, config) {
104
+ const legacyNetwork = toLegacyNetwork(network);
105
+ const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;
106
+ if (tokenInfoCache.has(cacheKey)) {
107
+ return tokenInfoCache.get(cacheKey);
108
+ }
109
+ try {
110
+ const client = getPublicClient(legacyNetwork, config);
111
+ const chainId = getChainId(legacyNetwork);
112
+ const [name, symbol, decimals] = await Promise.all([
113
+ client.readContract({
114
+ address: tokenAddress,
115
+ abi: ERC20_ABI,
116
+ functionName: "name"
117
+ }),
118
+ client.readContract({
119
+ address: tokenAddress,
120
+ abi: ERC20_ABI,
121
+ functionName: "symbol"
122
+ }),
123
+ client.readContract({
124
+ address: tokenAddress,
125
+ abi: ERC20_ABI,
126
+ functionName: "decimals"
127
+ })
128
+ ]);
129
+ const tokenInfo = {
130
+ address: tokenAddress,
131
+ name: name || "Unknown Token",
132
+ symbol: symbol || "UNKNOWN",
133
+ decimals: decimals || 18,
134
+ chainId
135
+ };
136
+ tokenInfoCache.set(cacheKey, tokenInfo);
137
+ return tokenInfo;
138
+ } catch (error) {
139
+ console.error("Failed to detect token info:", error);
140
+ return null;
141
+ }
142
+ }
143
+ function clearTokenCache() {
144
+ tokenInfoCache.clear();
145
+ }
146
+ function getCachedTokenInfo(tokenAddress, network) {
147
+ const legacyNetwork = toLegacyNetwork(network);
148
+ const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;
149
+ return tokenInfoCache.get(cacheKey);
150
+ }
151
+ function isValidNetwork(network) {
152
+ const legacyNetwork = toLegacyNetwork(network);
153
+ return legacyNetwork in CHAINS;
154
+ }
155
+ function getSupportedNetworks() {
156
+ return Object.keys(CHAINS);
157
+ }
158
+ export {
159
+ clearTokenCache,
160
+ detectTokenInfo,
161
+ getCachedTokenInfo,
162
+ getChainId,
163
+ getPublicClient,
164
+ getRpcUrl,
165
+ getSupportedNetworks,
166
+ isValidNetwork,
167
+ toLegacyNetwork
168
+ };
169
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @perkos/token-detection\n * ERC20 token detection utilities for reading on-chain token metadata\n */\n\nimport { createPublicClient, http, type Address, type PublicClient } from \"viem\";\nimport {\n avalanche,\n avalancheFuji,\n base,\n baseSepolia,\n celo,\n celoAlfajores,\n} from \"viem/chains\";\n\n/**\n * Token information returned from detection\n */\nexport interface TokenInfo {\n address: Address;\n name: string;\n symbol: string;\n decimals: number;\n chainId: number;\n}\n\n/**\n * Supported network names\n */\nexport type NetworkName =\n | \"avalanche\"\n | \"avalanche-fuji\"\n | \"base\"\n | \"base-sepolia\"\n | \"celo\"\n | \"celo-sepolia\";\n\n/**\n * Network configuration options\n */\nexport interface NetworkConfig {\n rpcUrl?: string;\n}\n\n/**\n * Standard ERC20 ABI (minimal - just what we need)\n */\nconst ERC20_ABI = [\n {\n constant: true,\n inputs: [],\n name: \"name\",\n outputs: [{ name: \"\", type: \"string\" }],\n type: \"function\",\n },\n {\n constant: true,\n inputs: [],\n name: \"symbol\",\n outputs: [{ name: \"\", type: \"string\" }],\n type: \"function\",\n },\n {\n constant: true,\n inputs: [],\n name: \"decimals\",\n outputs: [{ name: \"\", type: \"uint8\" }],\n type: \"function\",\n },\n] as const;\n\n/**\n * Chain ID mappings\n */\nconst CHAIN_IDS: Record<string, number> = {\n avalanche: 43114,\n \"avalanche-fuji\": 43113,\n base: 8453,\n \"base-sepolia\": 84532,\n celo: 42220,\n \"celo-sepolia\": 11142220,\n // CAIP-2 format support\n \"eip155:43114\": 43114,\n \"eip155:43113\": 43113,\n \"eip155:8453\": 8453,\n \"eip155:84532\": 84532,\n \"eip155:42220\": 42220,\n \"eip155:11142220\": 11142220,\n};\n\n/**\n * Default RPC URLs\n */\nconst DEFAULT_RPC_URLS: Record<NetworkName, string> = {\n avalanche: \"https://api.avax.network/ext/bc/C/rpc\",\n \"avalanche-fuji\": \"https://api.avax-test.network/ext/bc/C/rpc\",\n base: \"https://mainnet.base.org\",\n \"base-sepolia\": \"https://sepolia.base.org\",\n celo: \"https://forno.celo.org\",\n \"celo-sepolia\": \"https://forno.celo-sepolia.celo-testnet.org\",\n};\n\n/**\n * Chain definitions\n */\nconst CHAINS: Record<NetworkName, any> = {\n avalanche,\n \"avalanche-fuji\": avalancheFuji,\n base,\n \"base-sepolia\": baseSepolia,\n celo,\n \"celo-sepolia\": celoAlfajores,\n};\n\n/**\n * CAIP-2 to legacy network mapping\n */\nconst CAIP2_TO_LEGACY: Record<string, NetworkName> = {\n \"eip155:43114\": \"avalanche\",\n \"eip155:43113\": \"avalanche-fuji\",\n \"eip155:8453\": \"base\",\n \"eip155:84532\": \"base-sepolia\",\n \"eip155:42220\": \"celo\",\n \"eip155:11142220\": \"celo-sepolia\",\n};\n\n// Token info cache to avoid repeated RPC calls\nconst tokenInfoCache = new Map<string, TokenInfo>();\n\n/**\n * Convert CAIP-2 network format to legacy format\n * e.g., \"eip155:43114\" → \"avalanche\"\n */\nexport function toLegacyNetwork(network: string): NetworkName {\n if (!network.includes(\":\")) {\n return network as NetworkName;\n }\n return CAIP2_TO_LEGACY[network] || \"avalanche\";\n}\n\n/**\n * Get chain ID for a network\n */\nexport function getChainId(network: string): number {\n return CHAIN_IDS[network] || CHAIN_IDS.avalanche;\n}\n\n/**\n * Get RPC URL for a network\n */\nexport function getRpcUrl(network: string, customUrl?: string): string {\n if (customUrl) return customUrl;\n const legacyNetwork = toLegacyNetwork(network);\n return DEFAULT_RPC_URLS[legacyNetwork] || DEFAULT_RPC_URLS.avalanche;\n}\n\n/**\n * Get public client for a network\n */\nexport function getPublicClient(\n network: string,\n config?: NetworkConfig\n): PublicClient {\n const legacyNetwork = toLegacyNetwork(network);\n const rpcUrl = getRpcUrl(network, config?.rpcUrl);\n const chain = CHAINS[legacyNetwork] || {\n id: getChainId(legacyNetwork),\n name: legacyNetwork,\n network: legacyNetwork,\n nativeCurrency: { name: \"ETH\", symbol: \"ETH\", decimals: 18 },\n rpcUrls: { default: { http: [rpcUrl] } },\n };\n\n return createPublicClient({\n chain,\n transport: http(rpcUrl),\n }) as PublicClient;\n}\n\n/**\n * Detect token information from contract address\n * Results are cached to avoid repeated RPC calls\n *\n * @param tokenAddress - The ERC20 token contract address\n * @param network - Network name or CAIP-2 identifier\n * @param config - Optional network configuration\n * @returns Token information or null if detection fails\n *\n * @example\n * ```typescript\n * const tokenInfo = await detectTokenInfo(\n * \"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E\",\n * \"avalanche\"\n * );\n * // => { name: \"USD Coin\", symbol: \"USDC\", decimals: 6, ... }\n * ```\n */\nexport async function detectTokenInfo(\n tokenAddress: Address,\n network: string,\n config?: NetworkConfig\n): Promise<TokenInfo | null> {\n const legacyNetwork = toLegacyNetwork(network);\n const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;\n\n // Check cache first\n if (tokenInfoCache.has(cacheKey)) {\n return tokenInfoCache.get(cacheKey)!;\n }\n\n try {\n const client = getPublicClient(legacyNetwork, config);\n const chainId = getChainId(legacyNetwork);\n\n // Read token info in parallel\n const [name, symbol, decimals] = await Promise.all([\n client.readContract({\n address: tokenAddress,\n abi: ERC20_ABI,\n functionName: \"name\",\n }) as Promise<string>,\n client.readContract({\n address: tokenAddress,\n abi: ERC20_ABI,\n functionName: \"symbol\",\n }) as Promise<string>,\n client.readContract({\n address: tokenAddress,\n abi: ERC20_ABI,\n functionName: \"decimals\",\n }) as Promise<number>,\n ]);\n\n const tokenInfo: TokenInfo = {\n address: tokenAddress,\n name: name || \"Unknown Token\",\n symbol: symbol || \"UNKNOWN\",\n decimals: decimals || 18,\n chainId,\n };\n\n // Cache the result\n tokenInfoCache.set(cacheKey, tokenInfo);\n\n return tokenInfo;\n } catch (error) {\n console.error(\"Failed to detect token info:\", error);\n return null;\n }\n}\n\n/**\n * Clear the token info cache\n */\nexport function clearTokenCache(): void {\n tokenInfoCache.clear();\n}\n\n/**\n * Get cached token info without RPC call\n */\nexport function getCachedTokenInfo(\n tokenAddress: Address,\n network: string\n): TokenInfo | undefined {\n const legacyNetwork = toLegacyNetwork(network);\n const cacheKey = `${tokenAddress.toLowerCase()}-${legacyNetwork}`;\n return tokenInfoCache.get(cacheKey);\n}\n\n/**\n * Check if a network is valid\n */\nexport function isValidNetwork(network: string): boolean {\n const legacyNetwork = toLegacyNetwork(network);\n return legacyNetwork in CHAINS;\n}\n\n/**\n * Get all supported networks\n */\nexport function getSupportedNetworks(): NetworkName[] {\n return Object.keys(CHAINS) as NetworkName[];\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@perkos/util-tokens",
3
+ "version": "1.0.0",
4
+ "description": "ERC20 token detection utilities for reading on-chain token metadata",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "lint": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "clean": "rm -rf dist",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "erc20",
28
+ "token",
29
+ "detection",
30
+ "blockchain",
31
+ "eip-712",
32
+ "perkos"
33
+ ],
34
+ "author": "PerkOS",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/PerkOS-xyz/pkg-util-tokens.git"
39
+ },
40
+ "dependencies": {
41
+ "viem": "^2.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.10.0",
45
+ "tsup": "^8.0.1",
46
+ "typescript": "^5.3.3",
47
+ "vitest": "^1.2.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ }
52
+ }