web3-tools-mcp 1.0.1 → 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 (93) hide show
  1. package/README.md +46 -230
  2. package/dist/package.json +63 -0
  3. package/dist/src/client.d.ts +1653 -0
  4. package/dist/src/client.d.ts.map +1 -0
  5. package/dist/src/client.js +142 -0
  6. package/dist/src/client.js.map +1 -0
  7. package/dist/src/index.d.ts +3 -0
  8. package/dist/src/index.d.ts.map +1 -0
  9. package/dist/src/index.js +88 -0
  10. package/dist/src/index.js.map +1 -0
  11. package/dist/src/tools/advanced.d.ts +75 -0
  12. package/dist/src/tools/advanced.d.ts.map +1 -0
  13. package/dist/src/tools/advanced.js +234 -0
  14. package/dist/src/tools/advanced.js.map +1 -0
  15. package/dist/src/tools/balance.d.ts +55 -0
  16. package/dist/src/tools/balance.d.ts.map +1 -0
  17. package/dist/src/tools/balance.js +146 -0
  18. package/dist/src/tools/balance.js.map +1 -0
  19. package/dist/src/tools/contract-info.d.ts +79 -0
  20. package/dist/src/tools/contract-info.d.ts.map +1 -0
  21. package/dist/src/tools/contract-info.js +450 -0
  22. package/dist/src/tools/contract-info.js.map +1 -0
  23. package/dist/src/tools/contract.d.ts +79 -0
  24. package/dist/src/tools/contract.d.ts.map +1 -0
  25. package/dist/src/tools/contract.js +140 -0
  26. package/dist/src/tools/contract.js.map +1 -0
  27. package/dist/src/tools/ens.d.ts +119 -0
  28. package/dist/src/tools/ens.d.ts.map +1 -0
  29. package/dist/src/tools/ens.js +425 -0
  30. package/dist/src/tools/ens.js.map +1 -0
  31. package/dist/src/tools/index.d.ts +3 -0
  32. package/dist/src/tools/index.d.ts.map +1 -0
  33. package/dist/src/tools/index.js +29 -0
  34. package/dist/src/tools/index.js.map +1 -0
  35. package/dist/src/tools/logs.d.ts +39 -0
  36. package/dist/src/tools/logs.d.ts.map +1 -0
  37. package/dist/src/tools/logs.js +231 -0
  38. package/dist/src/tools/logs.js.map +1 -0
  39. package/dist/src/tools/signatures.d.ts +83 -0
  40. package/dist/src/tools/signatures.d.ts.map +1 -0
  41. package/dist/src/tools/signatures.js +93 -0
  42. package/dist/src/tools/signatures.js.map +1 -0
  43. package/dist/src/types.d.ts +22 -0
  44. package/dist/src/types.d.ts.map +1 -0
  45. package/dist/src/types.js +2 -0
  46. package/dist/src/types.js.map +1 -0
  47. package/dist/src/utils.d.ts +22 -0
  48. package/dist/src/utils.d.ts.map +1 -0
  49. package/dist/src/utils.js +183 -0
  50. package/dist/src/utils.js.map +1 -0
  51. package/dist/test/tools/advanced.test.d.ts +2 -0
  52. package/dist/test/tools/advanced.test.d.ts.map +1 -0
  53. package/dist/test/tools/advanced.test.js +235 -0
  54. package/dist/test/tools/advanced.test.js.map +1 -0
  55. package/dist/test/tools/balance.test.d.ts +2 -0
  56. package/dist/test/tools/balance.test.d.ts.map +1 -0
  57. package/dist/test/tools/balance.test.js +167 -0
  58. package/dist/test/tools/balance.test.js.map +1 -0
  59. package/dist/test/tools/contract-info.test.d.ts +2 -0
  60. package/dist/test/tools/contract-info.test.d.ts.map +1 -0
  61. package/dist/test/tools/contract-info.test.js +286 -0
  62. package/dist/test/tools/contract-info.test.js.map +1 -0
  63. package/dist/test/tools/contract.test.d.ts +2 -0
  64. package/dist/test/tools/contract.test.d.ts.map +1 -0
  65. package/dist/test/tools/contract.test.js +234 -0
  66. package/dist/test/tools/contract.test.js.map +1 -0
  67. package/dist/test/tools/ens.test.d.ts +2 -0
  68. package/dist/test/tools/ens.test.d.ts.map +1 -0
  69. package/dist/test/tools/ens.test.js +236 -0
  70. package/dist/test/tools/ens.test.js.map +1 -0
  71. package/dist/test/tools/logs.test.d.ts +2 -0
  72. package/dist/test/tools/logs.test.d.ts.map +1 -0
  73. package/dist/test/tools/logs.test.js +196 -0
  74. package/dist/test/tools/logs.test.js.map +1 -0
  75. package/dist/test/tools/signatures.test.d.ts +2 -0
  76. package/dist/test/tools/signatures.test.d.ts.map +1 -0
  77. package/dist/test/tools/signatures.test.js +112 -0
  78. package/dist/test/tools/signatures.test.js.map +1 -0
  79. package/package.json +37 -22
  80. package/src/client.ts +160 -0
  81. package/src/index.ts +96 -0
  82. package/src/tools/advanced.ts +262 -0
  83. package/src/tools/balance.ts +176 -0
  84. package/src/tools/contract-info.ts +582 -0
  85. package/src/tools/contract.ts +180 -0
  86. package/src/tools/ens.ts +493 -0
  87. package/src/tools/index.ts +37 -0
  88. package/src/tools/logs.ts +291 -0
  89. package/src/tools/signatures.ts +126 -0
  90. package/src/types.ts +29 -0
  91. package/src/utils.ts +222 -0
  92. package/dist/hypersync-client.darwin-arm64-7fegfwn5.node +0 -0
  93. package/dist/index.js +0 -28282
@@ -0,0 +1,291 @@
1
+ import { HypersyncClient, type Log, LogField } from '@envio-dev/hypersync-client'
2
+ import {
3
+ type AbiEvent,
4
+ type Address,
5
+ isAddress,
6
+ keccak256,
7
+ parseAbiItem,
8
+ toBytes,
9
+ toEventSignature
10
+ } from 'viem'
11
+ import { z } from 'zod'
12
+ import type { ChainName } from '../types.js'
13
+ import { getClientManager, HYPERSYNC_URLS, SUPPORTED_CHAINS } from '../client.js'
14
+ import { convertEventArgsToTypes, createTool, formatResponse } from '../utils.js'
15
+
16
+ async function getLogsWithHypersync(
17
+ chainName: ChainName,
18
+ eventAbi: string,
19
+ hypersyncApiKey?: string,
20
+ address?: string,
21
+ fromBlock?: string,
22
+ toBlock?: string,
23
+ eventArgs?: Record<string, unknown>
24
+ ) {
25
+ const hypersyncUrl = HYPERSYNC_URLS[chainName]
26
+ if (!hypersyncUrl) {
27
+ throw new Error(`Hypersync not supported for chain: ${chainName}`)
28
+ }
29
+
30
+ const client = HypersyncClient.new({
31
+ url: hypersyncUrl,
32
+ bearerToken: hypersyncApiKey
33
+ })
34
+
35
+ const abiItem = parseAbiItem(eventAbi) as AbiEvent
36
+ const eventSignature = toEventSignature(abiItem)
37
+ const topic0 = keccak256(toBytes(eventSignature))
38
+
39
+ // Build topics array for filtering
40
+ const topics: (string | string[] | null)[] = [topic0]
41
+
42
+ // Add indexed parameter filtering if eventArgs provided
43
+ if (eventArgs && Object.keys(eventArgs).length > 0) {
44
+ const indexedInputs = abiItem.inputs.filter(input => input.indexed)
45
+
46
+ for (let i = 0; i < indexedInputs.length && i < 3; i++) {
47
+ const input = indexedInputs[i]
48
+ const argValue = input?.name ? eventArgs[input.name] : undefined
49
+
50
+ if (argValue !== undefined) {
51
+ // Convert the argument to the proper format for topic filtering
52
+ let topicValue: string
53
+ if (typeof argValue === 'string' && argValue.startsWith('0x')) {
54
+ topicValue = argValue
55
+ } else if (typeof argValue === 'string' && isAddress(argValue)) {
56
+ topicValue = argValue.toLowerCase().padStart(66, '0x000000000000000000000000')
57
+ } else {
58
+ // For other types, we'd need more sophisticated encoding
59
+ topicValue = String(argValue)
60
+ }
61
+ topics.push(topicValue)
62
+ } else {
63
+ topics.push(null)
64
+ }
65
+ }
66
+ }
67
+
68
+ const query = {
69
+ fieldSelection: {
70
+ log: [
71
+ LogField.Address,
72
+ LogField.Topic0,
73
+ LogField.Topic1,
74
+ LogField.Topic2,
75
+ LogField.Topic3,
76
+ LogField.Data,
77
+ LogField.BlockNumber,
78
+ LogField.BlockHash,
79
+ LogField.TransactionHash,
80
+ LogField.TransactionIndex,
81
+ LogField.LogIndex
82
+ ]
83
+ },
84
+ fromBlock: fromBlock ? Number.parseInt(fromBlock) : 0,
85
+ toBlock: toBlock ? Number.parseInt(toBlock) : undefined,
86
+ logs: [
87
+ {
88
+ address: address ? [address] : undefined,
89
+ topics: topics.filter(t => t !== null).map(t => (Array.isArray(t) ? t : [t]))
90
+ }
91
+ ]
92
+ }
93
+
94
+ const res = await client.get(query)
95
+
96
+ // Convert hypersync logs to viem-compatible format
97
+ const decodedLogs = res.data.logs
98
+ .map((log: Log) => {
99
+ try {
100
+ return {
101
+ address: log.address as Address,
102
+ blockHash: log.blockHash as `0x${string}`,
103
+ blockNumber: log.blockNumber ? BigInt(log.blockNumber) : 0n,
104
+ data: log.data as `0x${string}`,
105
+ logIndex: log.logIndex,
106
+ topics: log.topics as `0x${string}`[],
107
+ transactionHash: log.transactionHash as `0x${string}`,
108
+ transactionIndex: log.transactionIndex,
109
+ eventName: abiItem.name,
110
+ args: {}
111
+ }
112
+ } catch (error) {
113
+ console.warn('Failed to decode log:', error)
114
+ return null
115
+ }
116
+ })
117
+ .filter((log): log is NonNullable<typeof log> => log !== null)
118
+
119
+ return decodedLogs
120
+ }
121
+
122
+ export default {
123
+ get_logs: createTool(
124
+ 'Query Contract Events',
125
+ 'Search and decode contract events with filtering. Uses viem with hypersync fallback. Specify address and block ranges for best performance.',
126
+ z.object({
127
+ chain: z.enum(SUPPORTED_CHAINS).describe('The blockchain network to use'),
128
+ eventAbi: z
129
+ .string()
130
+ .describe(
131
+ 'Event ABI definition (e.g., "event Transfer(address indexed from, address indexed to, uint256 value)")'
132
+ ),
133
+ address: z.string().optional().describe('Contract address (RECOMMENDED for performance)'),
134
+ fromBlock: z.string().optional().describe('Start block (defaults to latest-1000)'),
135
+ toBlock: z.string().optional().describe('End block (defaults to latest)'),
136
+ eventArgs: z
137
+ .record(
138
+ z.union([
139
+ z.string(),
140
+ z.number(),
141
+ z.boolean(),
142
+ z.array(z.union([z.string(), z.number(), z.boolean()]))
143
+ ])
144
+ )
145
+ .optional()
146
+ .describe('Filter by indexed parameters: {"from": "0x123...", "to": "0x456..."}')
147
+ }),
148
+ async (args) => {
149
+ const clientManager = getClientManager()
150
+ const config = (clientManager as any).config
151
+
152
+ // First try with regular viem client
153
+ try {
154
+ const client = clientManager.getClient(args.chain as ChainName)
155
+ const abiItem = parseAbiItem(args.eventAbi) as AbiEvent
156
+
157
+ // Determine block range
158
+ const fromBlockNum = args.fromBlock ? BigInt(args.fromBlock) : undefined
159
+ const toBlockNum = args.toBlock ? BigInt(args.toBlock) : undefined
160
+
161
+ // Prepare getLogs parameters
162
+ const getLogsParams: {
163
+ address?: Address
164
+ event: AbiEvent
165
+ fromBlock?: bigint
166
+ toBlock?: bigint
167
+ args?: Record<string, unknown>
168
+ } = {
169
+ event: abiItem,
170
+ fromBlock: fromBlockNum,
171
+ toBlock: toBlockNum
172
+ }
173
+
174
+ // Add address filter if provided
175
+ if (args.address && isAddress(args.address)) {
176
+ getLogsParams.address = args.address as Address
177
+ }
178
+
179
+ // Add event argument filters if provided
180
+ if (args.eventArgs && Object.keys(args.eventArgs).length > 0) {
181
+ // Convert event arguments to proper types based on ABI
182
+ const convertedEventArgs = convertEventArgsToTypes(args.eventArgs, abiItem.inputs)
183
+ getLogsParams.args = convertedEventArgs
184
+ }
185
+
186
+ // Use client.getLogs with proper typing
187
+ const logs = await client.getLogs(getLogsParams)
188
+
189
+ // Convert logs to serializable format
190
+ const serializedLogs = logs.map(log => {
191
+ const baseLogData = {
192
+ address: log.address,
193
+ blockHash: log.blockHash,
194
+ blockNumber: log.blockNumber?.toString(),
195
+ data: log.data,
196
+ logIndex: log.logIndex,
197
+ topics: log.topics,
198
+ transactionHash: log.transactionHash,
199
+ transactionIndex: log.transactionIndex
200
+ }
201
+
202
+ return {
203
+ ...baseLogData,
204
+ decoded: {
205
+ eventName: log.eventName,
206
+ args: log.args
207
+ }
208
+ }
209
+ })
210
+
211
+ return formatResponse({
212
+ logs: serializedLogs,
213
+ eventSignature: toEventSignature(abiItem),
214
+ chain: args.chain,
215
+ fromBlock: fromBlockNum?.toString() || 'latest',
216
+ toBlock: toBlockNum?.toString() || 'latest',
217
+ count: logs.length,
218
+ dataSource: 'viem',
219
+ filters: {
220
+ address: args.address || null,
221
+ eventArgs: args.eventArgs || null
222
+ }
223
+ })
224
+ } catch (viemError) {
225
+ // If viem fails and we have hypersync support for this chain, try hypersync as fallback
226
+ if (HYPERSYNC_URLS[args.chain as ChainName] && args.chain !== 'localhost') {
227
+ console.warn(`Viem getLogs failed for ${args.chain}, trying hypersync fallback:`, viemError)
228
+
229
+ try {
230
+ const hypersyncLogs = await getLogsWithHypersync(
231
+ args.chain as ChainName,
232
+ args.eventAbi,
233
+ config.hypersyncApiKey,
234
+ args.address,
235
+ args.fromBlock,
236
+ args.toBlock,
237
+ args.eventArgs
238
+ )
239
+
240
+ const abiItem = parseAbiItem(args.eventAbi) as AbiEvent
241
+ const fromBlockNum = args.fromBlock ? BigInt(args.fromBlock) : undefined
242
+ const toBlockNum = args.toBlock ? BigInt(args.toBlock) : undefined
243
+
244
+ // Convert hypersync logs to the same format as viem logs
245
+ const serializedLogs = hypersyncLogs.map(log => {
246
+ const baseLogData = {
247
+ address: log.address,
248
+ blockHash: log.blockHash,
249
+ blockNumber: log.blockNumber?.toString(),
250
+ data: log.data,
251
+ logIndex: log.logIndex,
252
+ topics: log.topics,
253
+ transactionHash: log.transactionHash,
254
+ transactionIndex: log.transactionIndex
255
+ }
256
+
257
+ return {
258
+ ...baseLogData,
259
+ decoded: {
260
+ eventName: log.eventName,
261
+ args: log.args
262
+ }
263
+ }
264
+ })
265
+
266
+ return formatResponse({
267
+ logs: serializedLogs,
268
+ eventSignature: toEventSignature(abiItem),
269
+ chain: args.chain,
270
+ fromBlock: fromBlockNum?.toString() || 'latest',
271
+ toBlock: toBlockNum?.toString() || 'latest',
272
+ count: hypersyncLogs.length,
273
+ dataSource: 'hypersync',
274
+ filters: {
275
+ address: args.address || null,
276
+ eventArgs: args.eventArgs || null
277
+ }
278
+ })
279
+ } catch (hypersyncError) {
280
+ throw new Error(
281
+ `Both viem and hypersync failed. Viem error: ${viemError}. Hypersync error: ${hypersyncError}`
282
+ )
283
+ }
284
+ } else {
285
+ // No hypersync fallback available for this chain
286
+ throw new Error(`Failed to get logs with viem (hypersync not available for ${args.chain}): ${viemError}`)
287
+ }
288
+ }
289
+ }
290
+ )
291
+ }
@@ -0,0 +1,126 @@
1
+ import {
2
+ type AbiEvent,
3
+ type AbiFunction,
4
+ type AbiParameter,
5
+ keccak256,
6
+ parseAbiItem,
7
+ toBytes,
8
+ toEventSignature,
9
+ toFunctionSignature
10
+ } from 'viem'
11
+ import { z } from 'zod'
12
+ import type { AbiError } from '../types.js'
13
+ import { createTool, formatResponse } from '../utils.js'
14
+
15
+ const FunctionAbiSchema = z.object({
16
+ functionAbi: z.string().describe('Function ABI definition (e.g., "function transfer(address to, uint256 amount)")')
17
+ })
18
+
19
+ const EventAbiSchema = z.object({
20
+ eventAbi: z
21
+ .string()
22
+ .describe('Event ABI definition (e.g., "event Transfer(address indexed from, address indexed to, uint256 value)")')
23
+ })
24
+
25
+ const ErrorAbiSchema = z.object({
26
+ errorAbi: z
27
+ .string()
28
+ .describe('Error ABI definition (e.g., "error InsufficientBalance(uint256 available, uint256 required)")')
29
+ })
30
+
31
+ export default {
32
+ get_function_signature: createTool(
33
+ 'Calculate Function Signatures',
34
+ 'Generate 4-byte function selectors from ABI definitions. BATCH OPTIMIZED - use for transaction decoding.',
35
+ z.object({
36
+ items: z
37
+ .array(FunctionAbiSchema)
38
+ .describe('Array of function ABI definitions. Batch multiple functions for efficiency.')
39
+ }),
40
+ async (args) => {
41
+ try {
42
+ const results = args.items.map((item) => {
43
+ const abiItem = parseAbiItem(item.functionAbi) as AbiFunction
44
+ const signature = toFunctionSignature(abiItem)
45
+ const hash = keccak256(toBytes(signature))
46
+ const selector = hash.slice(0, 10)
47
+
48
+ return {
49
+ signature: selector,
50
+ fullSignature: signature,
51
+ functionName: abiItem.name,
52
+ inputs: abiItem.inputs,
53
+ outputs: abiItem.outputs || [],
54
+ stateMutability: abiItem.stateMutability,
55
+ originalAbi: item.functionAbi
56
+ }
57
+ })
58
+
59
+ return formatResponse(results)
60
+ } catch (error) {
61
+ throw new Error(`Failed to parse function ABI: ${error}`)
62
+ }
63
+ }
64
+ ),
65
+
66
+ get_event_signature: createTool(
67
+ 'Calculate Event Signatures',
68
+ 'Generate event topic0 hashes from ABI definitions. BATCH OPTIMIZED - use for log filtering setup.',
69
+ z.object({
70
+ items: z.array(EventAbiSchema).describe('Array of event ABI definitions. Batch multiple events for optimal performance.')
71
+ }),
72
+ async (args) => {
73
+ try {
74
+ const results = args.items.map((item) => {
75
+ const abiItem = parseAbiItem(item.eventAbi) as AbiEvent
76
+ const signature = toEventSignature(abiItem)
77
+ const hash = keccak256(toBytes(signature))
78
+
79
+ return {
80
+ signature: hash,
81
+ topic0: hash,
82
+ fullSignature: signature,
83
+ eventName: abiItem.name,
84
+ inputs: abiItem.inputs,
85
+ anonymous: abiItem.anonymous || false,
86
+ originalAbi: item.eventAbi
87
+ }
88
+ })
89
+
90
+ return formatResponse(results)
91
+ } catch (error) {
92
+ throw new Error(`Failed to parse event ABI: ${error}`)
93
+ }
94
+ }
95
+ ),
96
+
97
+ get_error_signature: createTool(
98
+ 'Calculate Error Signatures',
99
+ 'Generate 4-byte error selectors from ABI definitions. BATCH OPTIMIZED - use for revert reason decoding.',
100
+ z.object({
101
+ items: z.array(ErrorAbiSchema).describe('Array of error ABI definitions. Batch related errors for efficiency.')
102
+ }),
103
+ async (args) => {
104
+ try {
105
+ const results = args.items.map((item) => {
106
+ const abiItem = parseAbiItem(item.errorAbi) as AbiError
107
+ const signature = `${abiItem.name}(${abiItem.inputs.map((input: AbiParameter) => input.type).join(',')})`
108
+ const hash = keccak256(toBytes(signature))
109
+ const selector = hash.slice(0, 10)
110
+
111
+ return {
112
+ signature: selector,
113
+ fullSignature: signature,
114
+ errorName: abiItem.name,
115
+ inputs: abiItem.inputs,
116
+ originalAbi: item.errorAbi
117
+ }
118
+ })
119
+
120
+ return formatResponse(results)
121
+ } catch (error) {
122
+ throw new Error(`Failed to parse error ABI: ${error}`)
123
+ }
124
+ }
125
+ )
126
+ }
package/src/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { AbiParameter } from 'viem'
2
+
3
+ // Define AbiError type since it's not exported by viem
4
+ export interface AbiError {
5
+ type: 'error'
6
+ name: string
7
+ inputs: readonly AbiParameter[]
8
+ }
9
+
10
+ // Configuration interface
11
+ export interface Config {
12
+ etherscanApiKey?: string
13
+ alchemyApiKey?: string
14
+ infuraApiKey?: string
15
+ customRpcUrls?: Record<string, string>
16
+ hypersyncApiKey?: string
17
+ }
18
+
19
+ // Chain names
20
+ export type ChainName = 'mainnet' | 'base' | 'arbitrum' | 'polygon' | 'optimism' | 'celo' | 'localhost'
21
+
22
+ // Tool handler result type
23
+ export interface ToolResult {
24
+ [x: string]: unknown
25
+ content: Array<{
26
+ type: 'text'
27
+ text: string
28
+ }>
29
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,222 @@
1
+ import type { AbiParameter } from 'viem'
2
+ import type { ToolResult } from './types.js'
3
+ import { z } from 'zod'
4
+
5
+ // Create tool helper
6
+ export function createTool<T extends z.ZodType>(
7
+ title: string,
8
+ description: string,
9
+ schema: T,
10
+ handler: (args: z.infer<T>) => Promise<ToolResult>
11
+ ) {
12
+ return {
13
+ title,
14
+ description,
15
+ schema,
16
+ handler
17
+ }
18
+ }
19
+
20
+ // Convert BigInt values to strings for JSON serialization
21
+ export function convertBigIntToString(obj: unknown): unknown {
22
+ if (typeof obj === 'bigint') {
23
+ return obj.toString()
24
+ }
25
+ if (Array.isArray(obj)) {
26
+ return obj.map(item => convertBigIntToString(item))
27
+ }
28
+ if (obj && typeof obj === 'object') {
29
+ const converted: Record<string, unknown> = {}
30
+ for (const [key, value] of Object.entries(obj)) {
31
+ converted[key] = convertBigIntToString(value)
32
+ }
33
+ return converted
34
+ }
35
+ return obj
36
+ }
37
+
38
+ // Convert arguments to appropriate types based on ABI
39
+ export function convertArgumentsToTypes(
40
+ args: (string | number | boolean | null)[],
41
+ abiInputs: readonly AbiParameter[]
42
+ ): unknown[] {
43
+ return args.map((arg, index) => {
44
+ if (index >= abiInputs.length) {
45
+ throw new Error(`Too many arguments provided. Expected ${abiInputs.length}, got ${args.length}`)
46
+ }
47
+
48
+ const param = abiInputs[index]
49
+ if (!param?.type) {
50
+ throw new Error(`Missing type information for parameter at index ${index}`)
51
+ }
52
+
53
+ const paramType = param.type
54
+
55
+ // Handle null values
56
+ if (arg === null) {
57
+ return null
58
+ }
59
+
60
+ // Handle different parameter types
61
+ if (paramType === 'address') {
62
+ return String(arg)
63
+ }
64
+
65
+ if (paramType === 'bool') {
66
+ if (typeof arg === 'boolean') return arg
67
+ if (typeof arg === 'string') return arg.toLowerCase() === 'true'
68
+ return Boolean(arg)
69
+ }
70
+
71
+ if (paramType.startsWith('uint') || paramType.startsWith('int')) {
72
+ if (typeof arg === 'number') return BigInt(arg)
73
+ if (typeof arg === 'string') return BigInt(arg)
74
+ throw new Error(`Invalid type for ${paramType}: ${typeof arg}`)
75
+ }
76
+
77
+ if (paramType === 'string') {
78
+ return String(arg)
79
+ }
80
+
81
+ if (paramType === 'bytes' || paramType.startsWith('bytes')) {
82
+ return String(arg)
83
+ }
84
+
85
+ // For other types, try to convert appropriately
86
+ if (typeof arg === 'string' && arg.startsWith('0x')) {
87
+ return arg // Assume it's already properly formatted
88
+ }
89
+ return arg
90
+ })
91
+ }
92
+
93
+ // Convert event arguments to appropriate types
94
+ export function convertEventArgsToTypes(
95
+ eventArgs: Record<string, unknown>,
96
+ abiInputs: readonly AbiParameter[]
97
+ ): Record<string, unknown> {
98
+ const converted: Record<string, unknown> = {}
99
+
100
+ for (const [argName, argValue] of Object.entries(eventArgs)) {
101
+ // Find the parameter definition for this argument name
102
+ const param = abiInputs.find(input => input.name === argName)
103
+
104
+ if (!param) {
105
+ throw new Error(`Parameter '${argName}' not found in event ABI`)
106
+ }
107
+
108
+ if (!param.type) {
109
+ throw new Error(`Missing type information for parameter '${argName}'`)
110
+ }
111
+
112
+ const paramType = param.type
113
+
114
+ // Handle null/undefined values
115
+ if (argValue === null || argValue === undefined) {
116
+ converted[argName] = null
117
+ continue
118
+ }
119
+
120
+ // Handle different parameter types
121
+ if (paramType === 'address') {
122
+ converted[argName] = String(argValue)
123
+ } else if (paramType === 'bool') {
124
+ if (typeof argValue === 'boolean') {
125
+ converted[argName] = argValue
126
+ } else if (typeof argValue === 'string') {
127
+ converted[argName] = argValue.toLowerCase() === 'true'
128
+ } else {
129
+ converted[argName] = Boolean(argValue)
130
+ }
131
+ } else if (paramType.startsWith('uint') || paramType.startsWith('int')) {
132
+ if (typeof argValue === 'number') {
133
+ converted[argName] = BigInt(argValue)
134
+ } else if (typeof argValue === 'string') {
135
+ converted[argName] = BigInt(argValue)
136
+ } else {
137
+ throw new Error(`Invalid type for ${paramType}: ${typeof argValue}`)
138
+ }
139
+ } else if (paramType === 'string') {
140
+ converted[argName] = String(argValue)
141
+ } else if (paramType === 'bytes' || paramType.startsWith('bytes')) {
142
+ converted[argName] = String(argValue)
143
+ } else {
144
+ // For other types, handle arrays and try to convert appropriately
145
+ if (Array.isArray(argValue)) {
146
+ converted[argName] = argValue
147
+ } else if (typeof argValue === 'string' && argValue.startsWith('0x')) {
148
+ converted[argName] = argValue // Assume it's already properly formatted
149
+ } else {
150
+ converted[argName] = argValue
151
+ }
152
+ }
153
+ }
154
+
155
+ return converted
156
+ }
157
+
158
+ // Format response as tool result
159
+ export function formatResponse(data: unknown): ToolResult {
160
+ const serializable = convertBigIntToString(data)
161
+ return {
162
+ content: [
163
+ {
164
+ type: 'text',
165
+ text: JSON.stringify(serializable, null, 2)
166
+ }
167
+ ]
168
+ }
169
+ }
170
+
171
+ // Parse command line arguments
172
+ export function parseCommandLineArgs(): {
173
+ etherscanApiKey?: string
174
+ alchemyApiKey?: string
175
+ infuraApiKey?: string
176
+ customRpcUrls?: Record<string, string>
177
+ hypersyncApiKey?: string
178
+ showHelp?: boolean
179
+ } {
180
+ const config: {
181
+ etherscanApiKey?: string
182
+ alchemyApiKey?: string
183
+ infuraApiKey?: string
184
+ customRpcUrls?: Record<string, string>
185
+ hypersyncApiKey?: string
186
+ showHelp?: boolean
187
+ } = {}
188
+
189
+ function getArgValue(argName: string): string | undefined {
190
+ const args = process.argv
191
+ const index = args.findIndex(arg => arg === argName)
192
+ return index !== -1 && index + 1 < args.length ? args[index + 1] : undefined
193
+ }
194
+
195
+ function hasArg(argName: string): boolean {
196
+ return process.argv.includes(argName)
197
+ }
198
+
199
+ // Check for help flag
200
+ if (hasArg('--help') || hasArg('-h')) {
201
+ config.showHelp = true
202
+ return config
203
+ }
204
+
205
+ // Parse environment variables and command line arguments
206
+ config.etherscanApiKey = process.env.ETHERSCAN_API_KEY || getArgValue('--etherscan-api-key')
207
+ config.alchemyApiKey = process.env.ALCHEMY_API_KEY || getArgValue('--alchemy-api-key')
208
+ config.infuraApiKey = process.env.INFURA_API_KEY || getArgValue('--infura-api-key')
209
+ config.hypersyncApiKey = process.env.HYPERSYNC_API_KEY || getArgValue('--hypersync-api-key')
210
+
211
+ // Parse custom RPC URLs
212
+ const customRpcs = getArgValue('--custom-rpc')
213
+ if (customRpcs) {
214
+ try {
215
+ config.customRpcUrls = JSON.parse(customRpcs)
216
+ } catch {
217
+ console.error('Invalid JSON for --custom-rpc:', customRpcs)
218
+ }
219
+ }
220
+
221
+ return config
222
+ }