web3-tools-mcp 1.1.0 → 1.2.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.
Files changed (68) hide show
  1. package/README.md +28 -4
  2. package/dist/package.json +5 -2
  3. package/dist/src/tools/ens.d.ts +119 -0
  4. package/dist/src/tools/ens.d.ts.map +1 -0
  5. package/dist/src/tools/ens.js +425 -0
  6. package/dist/src/tools/ens.js.map +1 -0
  7. package/dist/src/tools/index.d.ts.map +1 -1
  8. package/dist/src/tools/index.js +3 -1
  9. package/dist/src/tools/index.js.map +1 -1
  10. package/dist/test/tools/ens.test.d.ts +2 -0
  11. package/dist/test/tools/ens.test.d.ts.map +1 -0
  12. package/dist/test/tools/ens.test.js +236 -0
  13. package/dist/test/tools/ens.test.js.map +1 -0
  14. package/package.json +5 -2
  15. package/src/tools/ens.ts +493 -0
  16. package/src/tools/index.ts +3 -1
  17. package/dist/client.d.ts +0 -1653
  18. package/dist/client.d.ts.map +0 -1
  19. package/dist/client.js +0 -142
  20. package/dist/client.js.map +0 -1
  21. package/dist/index.d.ts +0 -3
  22. package/dist/index.d.ts.map +0 -1
  23. package/dist/index.js +0 -88
  24. package/dist/index.js.map +0 -1
  25. package/dist/test/mcp-client.test.d.ts +0 -2
  26. package/dist/test/mcp-client.test.d.ts.map +0 -1
  27. package/dist/test/mcp-client.test.js +0 -376
  28. package/dist/test/mcp-client.test.js.map +0 -1
  29. package/dist/tools/advanced.d.ts +0 -75
  30. package/dist/tools/advanced.d.ts.map +0 -1
  31. package/dist/tools/advanced.js +0 -234
  32. package/dist/tools/advanced.js.map +0 -1
  33. package/dist/tools/balance.d.ts +0 -55
  34. package/dist/tools/balance.d.ts.map +0 -1
  35. package/dist/tools/balance.js +0 -146
  36. package/dist/tools/balance.js.map +0 -1
  37. package/dist/tools/batch.d.ts +0 -122
  38. package/dist/tools/batch.d.ts.map +0 -1
  39. package/dist/tools/batch.js +0 -243
  40. package/dist/tools/batch.js.map +0 -1
  41. package/dist/tools/contract-info.d.ts +0 -79
  42. package/dist/tools/contract-info.d.ts.map +0 -1
  43. package/dist/tools/contract-info.js +0 -450
  44. package/dist/tools/contract-info.js.map +0 -1
  45. package/dist/tools/contract.d.ts +0 -79
  46. package/dist/tools/contract.d.ts.map +0 -1
  47. package/dist/tools/contract.js +0 -140
  48. package/dist/tools/contract.js.map +0 -1
  49. package/dist/tools/index.d.ts +0 -3
  50. package/dist/tools/index.d.ts.map +0 -1
  51. package/dist/tools/index.js +0 -27
  52. package/dist/tools/index.js.map +0 -1
  53. package/dist/tools/logs.d.ts +0 -39
  54. package/dist/tools/logs.d.ts.map +0 -1
  55. package/dist/tools/logs.js +0 -231
  56. package/dist/tools/logs.js.map +0 -1
  57. package/dist/tools/signatures.d.ts +0 -83
  58. package/dist/tools/signatures.d.ts.map +0 -1
  59. package/dist/tools/signatures.js +0 -93
  60. package/dist/tools/signatures.js.map +0 -1
  61. package/dist/types.d.ts +0 -22
  62. package/dist/types.d.ts.map +0 -1
  63. package/dist/types.js +0 -2
  64. package/dist/types.js.map +0 -1
  65. package/dist/utils.d.ts +0 -22
  66. package/dist/utils.d.ts.map +0 -1
  67. package/dist/utils.js +0 -183
  68. package/dist/utils.js.map +0 -1
@@ -0,0 +1,493 @@
1
+ import { type Address, isAddress, namehash } from "viem";
2
+ import { normalize } from "viem/ens";
3
+ import { z } from "zod";
4
+ import type { ChainName } from "../types.js";
5
+ import { getClientManager, SUPPORTED_CHAINS } from "../client.js";
6
+ import { createTool, formatResponse } from "../utils.js";
7
+
8
+ // Base ENS Contract Addresses
9
+ const BASE_ENS_CONTRACTS = {
10
+ registry: "0xb94704422c2a1e396835a571837aa5ae53285a95" as Address,
11
+ reverseRegistrar: "0x79ea96012eea67a83431f1701b3dff7e37f9e282" as Address,
12
+ l2Resolver: "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD" as Address,
13
+ };
14
+
15
+ // Helper function for Base ENS reverse resolution
16
+ async function reverseResolveBase(address: Address): Promise<string | null> {
17
+ const clientManager = getClientManager();
18
+ const client = clientManager.getClient("base");
19
+
20
+ try {
21
+ // Get the reverse node from ReverseRegistrar
22
+ const reverseNode = await client.readContract({
23
+ address: BASE_ENS_CONTRACTS.reverseRegistrar,
24
+ abi: [
25
+ {
26
+ name: "node",
27
+ type: "function",
28
+ stateMutability: "pure",
29
+ inputs: [{ name: "addr", type: "address" }],
30
+ outputs: [{ name: "", type: "bytes32" }],
31
+ },
32
+ ],
33
+ functionName: "node",
34
+ args: [address],
35
+ });
36
+
37
+ // Query the L2Resolver for the name
38
+ const name = await client.readContract({
39
+ address: BASE_ENS_CONTRACTS.l2Resolver,
40
+ abi: [
41
+ {
42
+ name: "name",
43
+ type: "function",
44
+ stateMutability: "view",
45
+ inputs: [{ name: "node", type: "bytes32" }],
46
+ outputs: [{ name: "", type: "string" }],
47
+ },
48
+ ],
49
+ functionName: "name",
50
+ args: [reverseNode as `0x${string}`],
51
+ });
52
+
53
+ return name || null;
54
+ } catch (error) {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ // Helper function for Base ENS forward resolution
60
+ async function resolveNameBase(name: string): Promise<Address | null> {
61
+ const clientManager = getClientManager();
62
+ const client = clientManager.getClient("base");
63
+
64
+ try {
65
+ const node = namehash(name);
66
+
67
+ // Get resolver from registry
68
+ const resolver = await client.readContract({
69
+ address: BASE_ENS_CONTRACTS.registry,
70
+ abi: [
71
+ {
72
+ name: "resolver",
73
+ type: "function",
74
+ stateMutability: "view",
75
+ inputs: [{ name: "node", type: "bytes32" }],
76
+ outputs: [{ name: "", type: "address" }],
77
+ },
78
+ ],
79
+ functionName: "resolver",
80
+ args: [node],
81
+ });
82
+
83
+ if (!resolver || resolver === "0x0000000000000000000000000000000000000000") {
84
+ return null;
85
+ }
86
+
87
+ // Get address from resolver
88
+ const address = await client.readContract({
89
+ address: resolver as Address,
90
+ abi: [
91
+ {
92
+ name: "addr",
93
+ type: "function",
94
+ stateMutability: "view",
95
+ inputs: [{ name: "node", type: "bytes32" }],
96
+ outputs: [{ name: "", type: "address" }],
97
+ },
98
+ ],
99
+ functionName: "addr",
100
+ args: [node],
101
+ });
102
+
103
+ return address as Address | null;
104
+ } catch (error) {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ // Helper function for Base ENS text records
110
+ async function getTextRecordBase(name: string, key: string): Promise<string | null> {
111
+ const clientManager = getClientManager();
112
+ const client = clientManager.getClient("base");
113
+
114
+ try {
115
+ const node = namehash(name);
116
+
117
+ // Get resolver from registry
118
+ const resolver = await client.readContract({
119
+ address: BASE_ENS_CONTRACTS.registry,
120
+ abi: [
121
+ {
122
+ name: "resolver",
123
+ type: "function",
124
+ stateMutability: "view",
125
+ inputs: [{ name: "node", type: "bytes32" }],
126
+ outputs: [{ name: "", type: "address" }],
127
+ },
128
+ ],
129
+ functionName: "resolver",
130
+ args: [node],
131
+ });
132
+
133
+ if (!resolver || resolver === "0x0000000000000000000000000000000000000000") {
134
+ return null;
135
+ }
136
+
137
+ // Get text record from resolver
138
+ const textRecord = await client.readContract({
139
+ address: resolver as Address,
140
+ abi: [
141
+ {
142
+ name: "text",
143
+ type: "function",
144
+ stateMutability: "view",
145
+ inputs: [
146
+ { name: "node", type: "bytes32" },
147
+ { name: "key", type: "string" },
148
+ ],
149
+ outputs: [{ name: "", type: "string" }],
150
+ },
151
+ ],
152
+ functionName: "text",
153
+ args: [node, key],
154
+ });
155
+
156
+ return textRecord || null;
157
+ } catch (error) {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ const EnsResolveSchema = z.object({
163
+ chain: z
164
+ .enum(SUPPORTED_CHAINS)
165
+ .default("mainnet")
166
+ .describe("Blockchain network (defaults to mainnet, as ENS is primarily on Ethereum)"),
167
+ name: z
168
+ .string()
169
+ .describe("ENS name to resolve to an address (e.g., 'vitalik.eth', 'example.eth')"),
170
+ universalResolver: z
171
+ .boolean()
172
+ .optional()
173
+ .describe("Use universal resolver for cross-chain resolution (defaults to true)"),
174
+ });
175
+
176
+ const EnsReverseResolveSchema = z.object({
177
+ chain: z
178
+ .enum(SUPPORTED_CHAINS)
179
+ .default("mainnet")
180
+ .describe("Blockchain network (defaults to mainnet)"),
181
+ address: z.string().describe("Ethereum address to resolve to an ENS name"),
182
+ universalResolver: z
183
+ .boolean()
184
+ .optional()
185
+ .describe("Use universal resolver for cross-chain resolution (defaults to true)"),
186
+ });
187
+
188
+ const EnsTextRecordSchema = z.object({
189
+ chain: z
190
+ .enum(SUPPORTED_CHAINS)
191
+ .default("mainnet")
192
+ .describe("Blockchain network (defaults to mainnet)"),
193
+ name: z.string().describe("ENS name to query text records from"),
194
+ key: z
195
+ .string()
196
+ .describe(
197
+ "Text record key (e.g., 'avatar', 'description', 'email', 'url', 'com.twitter', 'com.github')"
198
+ ),
199
+ universalResolver: z
200
+ .boolean()
201
+ .optional()
202
+ .describe("Use universal resolver for cross-chain resolution (defaults to true)"),
203
+ });
204
+
205
+ const EnsBatchResolveSchema = z.object({
206
+ chain: z
207
+ .enum(SUPPORTED_CHAINS)
208
+ .default("mainnet")
209
+ .describe("Blockchain network (defaults to mainnet)"),
210
+ names: z
211
+ .array(z.string())
212
+ .describe("Array of ENS names to resolve to addresses in a single batch"),
213
+ universalResolver: z
214
+ .boolean()
215
+ .optional()
216
+ .describe("Use universal resolver for cross-chain resolution (defaults to true)"),
217
+ });
218
+
219
+ export default {
220
+ resolve_ens_name: createTool(
221
+ "Resolve ENS Name to Address",
222
+ "Resolve an ENS name to its Ethereum address. ENS names are human-readable identifiers (like vitalik.eth) that resolve to Ethereum addresses.",
223
+ EnsResolveSchema,
224
+ async (args) => {
225
+ try {
226
+ // Normalize the ENS name
227
+ const normalizedName = normalize(args.name);
228
+
229
+ let address: Address | null = null;
230
+
231
+ // Use Base-specific logic for Base chain
232
+ if (args.chain === "base") {
233
+ address = await resolveNameBase(normalizedName);
234
+ } else {
235
+ // Use viem's built-in ENS resolution for other chains
236
+ const clientManager = getClientManager();
237
+ const client = clientManager.getClient(args.chain as ChainName);
238
+
239
+ address = await client.getEnsAddress({
240
+ name: normalizedName,
241
+ universalResolverAddress: args.universalResolver !== false ? undefined : undefined,
242
+ });
243
+ }
244
+
245
+ if (!address) {
246
+ return formatResponse({
247
+ success: false,
248
+ name: args.name,
249
+ normalizedName,
250
+ address: null,
251
+ chain: args.chain,
252
+ message: "ENS name does not resolve to an address",
253
+ });
254
+ }
255
+
256
+ return formatResponse({
257
+ success: true,
258
+ name: args.name,
259
+ normalizedName,
260
+ address,
261
+ chain: args.chain,
262
+ });
263
+ } catch (error) {
264
+ throw new Error(`Failed to resolve ENS name: ${error}`);
265
+ }
266
+ }
267
+ ),
268
+
269
+ reverse_resolve_ens: createTool(
270
+ "Reverse Resolve Address to ENS Name",
271
+ "Reverse resolve an Ethereum address to its primary ENS name. Returns the ENS name if the address has set a reverse record.",
272
+ EnsReverseResolveSchema,
273
+ async (args) => {
274
+ if (!isAddress(args.address)) {
275
+ throw new Error(`Invalid Ethereum address: ${args.address}`);
276
+ }
277
+
278
+ try {
279
+ let ensName: string | null = null;
280
+
281
+ // Use Base-specific logic for Base chain
282
+ if (args.chain === "base") {
283
+ ensName = await reverseResolveBase(args.address as Address);
284
+ } else {
285
+ // Use viem's built-in ENS resolution for other chains
286
+ const clientManager = getClientManager();
287
+ const client = clientManager.getClient(args.chain as ChainName);
288
+
289
+ ensName = await client.getEnsName({
290
+ address: args.address as Address,
291
+ universalResolverAddress: args.universalResolver !== false ? undefined : undefined,
292
+ });
293
+ }
294
+
295
+ if (!ensName) {
296
+ return formatResponse({
297
+ success: false,
298
+ address: args.address,
299
+ name: null,
300
+ chain: args.chain,
301
+ message: "Address does not have a primary ENS name set",
302
+ });
303
+ }
304
+
305
+ return formatResponse({
306
+ success: true,
307
+ address: args.address,
308
+ name: ensName,
309
+ chain: args.chain,
310
+ });
311
+ } catch (error) {
312
+ throw new Error(`Failed to reverse resolve address: ${error}`);
313
+ }
314
+ }
315
+ ),
316
+
317
+ get_ens_text_record: createTool(
318
+ "Get ENS Text Record",
319
+ "Retrieve a text record from an ENS name. Common keys include 'avatar', 'description', 'email', 'url', 'com.twitter', 'com.github'.",
320
+ EnsTextRecordSchema,
321
+ async (args) => {
322
+ try {
323
+ // Normalize the ENS name
324
+ const normalizedName = normalize(args.name);
325
+
326
+ let textRecord: string | null = null;
327
+
328
+ // Use Base-specific logic for Base chain
329
+ if (args.chain === "base") {
330
+ textRecord = await getTextRecordBase(normalizedName, args.key);
331
+ } else {
332
+ // Use viem's built-in ENS resolution for other chains
333
+ const clientManager = getClientManager();
334
+ const client = clientManager.getClient(args.chain as ChainName);
335
+
336
+ textRecord = await client.getEnsText({
337
+ name: normalizedName,
338
+ key: args.key,
339
+ universalResolverAddress: args.universalResolver !== false ? undefined : undefined,
340
+ });
341
+ }
342
+
343
+ if (!textRecord) {
344
+ return formatResponse({
345
+ success: false,
346
+ name: args.name,
347
+ normalizedName,
348
+ key: args.key,
349
+ value: null,
350
+ chain: args.chain,
351
+ message: `Text record '${args.key}' not found for ${args.name}`,
352
+ });
353
+ }
354
+
355
+ return formatResponse({
356
+ success: true,
357
+ name: args.name,
358
+ normalizedName,
359
+ key: args.key,
360
+ value: textRecord,
361
+ chain: args.chain,
362
+ });
363
+ } catch (error) {
364
+ throw new Error(`Failed to get ENS text record: ${error}`);
365
+ }
366
+ }
367
+ ),
368
+
369
+ get_ens_avatar: createTool(
370
+ "Get ENS Avatar",
371
+ "Get the avatar URI for an ENS name. Returns the avatar URL if set.",
372
+ z.object({
373
+ chain: z
374
+ .enum(SUPPORTED_CHAINS)
375
+ .default("mainnet")
376
+ .describe("Blockchain network (defaults to mainnet)"),
377
+ name: z.string().describe("ENS name to get avatar from"),
378
+ universalResolver: z
379
+ .boolean()
380
+ .optional()
381
+ .describe("Use universal resolver for cross-chain resolution (defaults to true)"),
382
+ }),
383
+ async (args) => {
384
+ try {
385
+ // Normalize the ENS name
386
+ const normalizedName = normalize(args.name);
387
+
388
+ let avatar: string | null = null;
389
+
390
+ // Use Base-specific logic for Base chain
391
+ if (args.chain === "base") {
392
+ avatar = await getTextRecordBase(normalizedName, "avatar");
393
+ } else {
394
+ // Use viem's built-in ENS resolution for other chains
395
+ const clientManager = getClientManager();
396
+ const client = clientManager.getClient(args.chain as ChainName);
397
+
398
+ avatar = await client.getEnsAvatar({
399
+ name: normalizedName,
400
+ universalResolverAddress: args.universalResolver !== false ? undefined : undefined,
401
+ });
402
+ }
403
+
404
+ if (!avatar) {
405
+ return formatResponse({
406
+ success: false,
407
+ name: args.name,
408
+ normalizedName,
409
+ avatar: null,
410
+ chain: args.chain,
411
+ message: `No avatar set for ${args.name}`,
412
+ });
413
+ }
414
+
415
+ return formatResponse({
416
+ success: true,
417
+ name: args.name,
418
+ normalizedName,
419
+ avatar,
420
+ chain: args.chain,
421
+ });
422
+ } catch (error) {
423
+ throw new Error(`Failed to get ENS avatar: ${error}`);
424
+ }
425
+ }
426
+ ),
427
+
428
+ batch_resolve_ens_names: createTool(
429
+ "Batch Resolve ENS Names",
430
+ "Resolve multiple ENS names to addresses in a single batch for efficiency.",
431
+ EnsBatchResolveSchema,
432
+ async (args) => {
433
+ try {
434
+ // Resolve all ENS names in parallel
435
+ const results = await Promise.allSettled(
436
+ args.names.map(async (name) => {
437
+ const normalizedName = normalize(name);
438
+ let address: Address | null = null;
439
+
440
+ // Use Base-specific logic for Base chain
441
+ if (args.chain === "base") {
442
+ address = await resolveNameBase(normalizedName);
443
+ } else {
444
+ // Use viem's built-in ENS resolution for other chains
445
+ const clientManager = getClientManager();
446
+ const client = clientManager.getClient(args.chain as ChainName);
447
+
448
+ address = await client.getEnsAddress({
449
+ name: normalizedName,
450
+ universalResolverAddress: args.universalResolver !== false ? undefined : undefined,
451
+ });
452
+ }
453
+
454
+ return {
455
+ name,
456
+ normalizedName,
457
+ address,
458
+ success: !!address,
459
+ };
460
+ })
461
+ );
462
+
463
+ // Process results
464
+ const resolved = results.map((result, index) => {
465
+ if (result.status === "fulfilled") {
466
+ return result.value;
467
+ }
468
+ return {
469
+ name: args.names[index],
470
+ normalizedName: args.names[index],
471
+ address: null,
472
+ success: false,
473
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
474
+ };
475
+ });
476
+
477
+ const successCount = resolved.filter((r) => r.success).length;
478
+ const failCount = resolved.filter((r) => !r.success).length;
479
+
480
+ return formatResponse({
481
+ success: true,
482
+ chain: args.chain,
483
+ totalNames: args.names.length,
484
+ successfulResolves: successCount,
485
+ failedResolves: failCount,
486
+ results: resolved,
487
+ });
488
+ } catch (error) {
489
+ throw new Error(`Failed to batch resolve ENS names: ${error}`);
490
+ }
491
+ }
492
+ ),
493
+ };
@@ -5,6 +5,7 @@ import advancedTools from './advanced.js'
5
5
  import balanceTools from './balance.js'
6
6
  import contractTools from './contract.js'
7
7
  import contractInfoTools from './contract-info.js'
8
+ import ensTools from './ens.js'
8
9
  import logTools from './logs.js'
9
10
  import signatureTools from './signatures.js'
10
11
 
@@ -14,7 +15,8 @@ const allToolDefinitions = {
14
15
  ...contractInfoTools,
15
16
  ...balanceTools,
16
17
  ...logTools,
17
- ...advancedTools
18
+ ...advancedTools,
19
+ ...ensTools
18
20
  } as const
19
21
 
20
22
  // Register all tools with the MCP server