web3-tools-mcp 1.5.0 → 2.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/CHANGELOG.md +95 -0
- package/README.md +346 -164
- package/dist/anvil.d.ts +1 -1
- package/dist/anvil.d.ts.map +1 -1
- package/dist/anvil.js +11 -9
- package/dist/anvil.js.map +1 -1
- package/dist/chain-meta.d.ts.map +1 -1
- package/dist/chain-meta.js +7 -3
- package/dist/chain-meta.js.map +1 -1
- package/dist/chains.d.ts +960 -0
- package/dist/chains.d.ts.map +1 -0
- package/dist/chains.js +108 -0
- package/dist/chains.js.map +1 -0
- package/dist/client.d.ts +10 -1717
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +33 -216
- package/dist/client.js.map +1 -1
- package/dist/http-server.d.ts +18 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +89 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.js +77 -32
- package/dist/index.js.map +1 -1
- package/dist/kv-storage.d.ts +23 -0
- package/dist/kv-storage.d.ts.map +1 -0
- package/dist/kv-storage.js +69 -0
- package/dist/kv-storage.js.map +1 -0
- package/dist/oauth.d.ts +31 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +227 -0
- package/dist/oauth.js.map +1 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +14 -4
- package/dist/preview.js.map +1 -1
- package/dist/tools/advanced.d.ts +4 -4
- package/dist/tools/advanced.d.ts.map +1 -1
- package/dist/tools/advanced.js +228 -247
- package/dist/tools/advanced.js.map +1 -1
- package/dist/tools/balance.d.ts +1 -1
- package/dist/tools/balance.d.ts.map +1 -1
- package/dist/tools/balance.js +97 -103
- package/dist/tools/balance.js.map +1 -1
- package/dist/tools/contract-info.d.ts +3 -3
- package/dist/tools/contract-info.d.ts.map +1 -1
- package/dist/tools/contract-info.js +150 -161
- package/dist/tools/contract-info.js.map +1 -1
- package/dist/tools/contract.d.ts +4 -4
- package/dist/tools/contract.d.ts.map +1 -1
- package/dist/tools/contract.js +91 -110
- package/dist/tools/contract.js.map +1 -1
- package/dist/tools/ens.d.ts +6 -6
- package/dist/tools/ens.d.ts.map +1 -1
- package/dist/tools/ens.js +239 -297
- package/dist/tools/ens.js.map +1 -1
- package/dist/tools/gas.d.ts +4 -4
- package/dist/tools/gas.d.ts.map +1 -1
- package/dist/tools/gas.js +90 -138
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/logs.d.ts +1 -1
- package/dist/tools/logs.d.ts.map +1 -1
- package/dist/tools/logs.js +36 -37
- package/dist/tools/logs.js.map +1 -1
- package/dist/tools/signatures.d.ts.map +1 -1
- package/dist/tools/signatures.js +67 -89
- package/dist/tools/signatures.js.map +1 -1
- package/dist/tools/transactions.d.ts +48 -4
- package/dist/tools/transactions.d.ts.map +1 -1
- package/dist/tools/transactions.js +228 -44
- package/dist/tools/transactions.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +3 -8
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +36 -98
- package/dist/utils.js.map +1 -1
- package/dist/wallet-client.d.ts +11 -1
- package/dist/wallet-client.d.ts.map +1 -1
- package/dist/wallet-client.js +27 -9
- package/dist/wallet-client.js.map +1 -1
- package/dist/walletconnect.d.ts +52 -0
- package/dist/walletconnect.d.ts.map +1 -0
- package/dist/walletconnect.js +221 -0
- package/dist/walletconnect.js.map +1 -0
- package/package.json +21 -9
- package/src/anvil.ts +15 -18
- package/src/chain-meta.ts +8 -4
- package/src/chains.ts +145 -0
- package/src/client.ts +39 -252
- package/src/http-server.ts +117 -0
- package/src/index.ts +98 -49
- package/src/kv-storage.ts +83 -0
- package/src/oauth.ts +270 -0
- package/src/preview.ts +15 -14
- package/src/tools/advanced.ts +195 -213
- package/src/tools/balance.ts +103 -108
- package/src/tools/contract-info.ts +163 -184
- package/src/tools/contract.ts +123 -144
- package/src/tools/ens.ts +290 -345
- package/src/tools/gas.ts +122 -170
- package/src/tools/index.ts +2 -2
- package/src/tools/logs.ts +45 -43
- package/src/tools/signatures.ts +68 -86
- package/src/tools/transactions.ts +283 -50
- package/src/types.ts +2 -15
- package/src/utils.ts +56 -127
- package/src/wallet-client.ts +31 -9
- package/src/walletconnect.ts +269 -0
package/src/tools/gas.ts
CHANGED
|
@@ -1,59 +1,44 @@
|
|
|
1
|
-
import { type AbiFunction, type Address,
|
|
2
|
-
import { z } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { convertArgumentsToTypes, createTool, formatResponse } from
|
|
1
|
+
import { type AbiFunction, type Address, decodeFunctionResult, encodeFunctionData, isAddress, parseAbiItem } from 'viem'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getClientManager, SUPPORTED_CHAINS } from '../client.js'
|
|
4
|
+
import type { ChainName } from '../types.js'
|
|
5
|
+
import { convertArgumentsToTypes, createTool, formatResponse } from '../utils.js'
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
simulate_contract: createTool(
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
'Simulate Contract Call',
|
|
10
|
+
'Simulate a contract call (including state-changing functions) without broadcasting. Returns simulation result and estimated gas.',
|
|
11
11
|
z.object({
|
|
12
|
-
chain: z
|
|
13
|
-
|
|
14
|
-
.describe("Blockchain network to simulate on"),
|
|
15
|
-
contractAddress: z
|
|
16
|
-
.string()
|
|
17
|
-
.describe("Contract address to call"),
|
|
12
|
+
chain: z.enum(SUPPORTED_CHAINS).describe('Blockchain network to simulate on'),
|
|
13
|
+
contractAddress: z.string().describe('Contract address to call'),
|
|
18
14
|
functionAbi: z
|
|
19
15
|
.string()
|
|
20
|
-
.describe(
|
|
21
|
-
'Function ABI signature (e.g., "function transfer(address to, uint256 amount)"). Can be any function type.'
|
|
22
|
-
),
|
|
16
|
+
.describe('Function ABI signature (e.g., "function transfer(address to, uint256 amount)"). Can be any function type.'),
|
|
23
17
|
args: z
|
|
24
18
|
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
25
19
|
.optional()
|
|
26
|
-
.describe(
|
|
27
|
-
from: z
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.describe("Sender address (defaults to zero address)"),
|
|
31
|
-
value: z
|
|
32
|
-
.string()
|
|
33
|
-
.optional()
|
|
34
|
-
.describe("ETH value to send with the transaction (in wei as string)"),
|
|
35
|
-
blockNumber: z
|
|
36
|
-
.string()
|
|
37
|
-
.optional()
|
|
38
|
-
.describe("Block number for simulation (defaults to latest)"),
|
|
20
|
+
.describe('Function arguments in order matching the ABI signature'),
|
|
21
|
+
from: z.string().optional().describe('Sender address (defaults to zero address)'),
|
|
22
|
+
value: z.string().optional().describe('ETH value to send with the transaction (in wei as string)'),
|
|
23
|
+
blockNumber: z.string().optional().describe('Block number for simulation (defaults to latest)')
|
|
39
24
|
}),
|
|
40
25
|
async (args) => {
|
|
41
26
|
if (!isAddress(args.contractAddress)) {
|
|
42
|
-
throw new Error(`Invalid contract address: ${args.contractAddress}`)
|
|
27
|
+
throw new Error(`Invalid contract address: ${args.contractAddress}`)
|
|
43
28
|
}
|
|
44
29
|
|
|
45
30
|
if (args.from && !isAddress(args.from)) {
|
|
46
|
-
throw new Error(`Invalid from address: ${args.from}`)
|
|
31
|
+
throw new Error(`Invalid from address: ${args.from}`)
|
|
47
32
|
}
|
|
48
33
|
|
|
49
|
-
const clientManager = getClientManager()
|
|
50
|
-
const client = clientManager.getClient(args.chain as ChainName)
|
|
34
|
+
const clientManager = getClientManager()
|
|
35
|
+
const client = clientManager.getClient(args.chain as ChainName)
|
|
51
36
|
|
|
52
37
|
try {
|
|
53
|
-
const abiItem = parseAbiItem(args.functionAbi) as AbiFunction
|
|
54
|
-
const convertedArgs = convertArgumentsToTypes(args.args || [], abiItem.inputs)
|
|
38
|
+
const abiItem = parseAbiItem(args.functionAbi) as AbiFunction
|
|
39
|
+
const convertedArgs = convertArgumentsToTypes(args.args || [], abiItem.inputs)
|
|
55
40
|
|
|
56
|
-
const blockTag = args.blockNumber ? BigInt(args.blockNumber) : undefined
|
|
41
|
+
const blockTag = args.blockNumber ? BigInt(args.blockNumber) : undefined
|
|
57
42
|
|
|
58
43
|
// Simulate the call
|
|
59
44
|
const result = await client.call({
|
|
@@ -61,12 +46,12 @@ export default {
|
|
|
61
46
|
data: encodeFunctionData({
|
|
62
47
|
abi: [abiItem],
|
|
63
48
|
functionName: abiItem.name,
|
|
64
|
-
args: convertedArgs
|
|
49
|
+
args: convertedArgs
|
|
65
50
|
}),
|
|
66
51
|
account: args.from ? (args.from as Address) : undefined,
|
|
67
52
|
value: args.value ? BigInt(args.value) : undefined,
|
|
68
|
-
blockNumber: blockTag
|
|
69
|
-
})
|
|
53
|
+
blockNumber: blockTag
|
|
54
|
+
})
|
|
70
55
|
|
|
71
56
|
// Also estimate gas
|
|
72
57
|
const gasEstimate = await client.estimateGas({
|
|
@@ -74,21 +59,21 @@ export default {
|
|
|
74
59
|
data: encodeFunctionData({
|
|
75
60
|
abi: [abiItem],
|
|
76
61
|
functionName: abiItem.name,
|
|
77
|
-
args: convertedArgs
|
|
62
|
+
args: convertedArgs
|
|
78
63
|
}),
|
|
79
64
|
account: args.from ? (args.from as Address) : undefined,
|
|
80
65
|
value: args.value ? BigInt(args.value) : undefined,
|
|
81
|
-
blockNumber: blockTag
|
|
82
|
-
})
|
|
66
|
+
blockNumber: blockTag
|
|
67
|
+
})
|
|
83
68
|
|
|
84
69
|
// Decode the result if the function has outputs
|
|
85
|
-
let decodedResult: unknown = result.data
|
|
70
|
+
let decodedResult: unknown = result.data
|
|
86
71
|
if (abiItem.outputs && abiItem.outputs.length > 0 && result.data) {
|
|
87
72
|
decodedResult = decodeFunctionResult({
|
|
88
73
|
abi: [abiItem],
|
|
89
74
|
functionName: abiItem.name,
|
|
90
|
-
data: result.data
|
|
91
|
-
})
|
|
75
|
+
data: result.data
|
|
76
|
+
})
|
|
92
77
|
}
|
|
93
78
|
|
|
94
79
|
return formatResponse({
|
|
@@ -99,169 +84,136 @@ export default {
|
|
|
99
84
|
result: decodedResult,
|
|
100
85
|
rawData: result.data,
|
|
101
86
|
gasEstimate: gasEstimate.toString(),
|
|
102
|
-
blockNumber: args.blockNumber ||
|
|
103
|
-
})
|
|
87
|
+
blockNumber: args.blockNumber || 'latest'
|
|
88
|
+
})
|
|
104
89
|
} catch (error) {
|
|
105
90
|
// Check if it's a revert error
|
|
106
|
-
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
91
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
107
92
|
|
|
108
93
|
return formatResponse({
|
|
109
94
|
success: false,
|
|
110
95
|
chain: args.chain,
|
|
111
96
|
contractAddress: args.contractAddress,
|
|
112
97
|
error: errorMessage,
|
|
113
|
-
reverted: errorMessage.includes(
|
|
114
|
-
})
|
|
98
|
+
reverted: errorMessage.includes('revert') || errorMessage.includes('execution reverted')
|
|
99
|
+
})
|
|
115
100
|
}
|
|
116
101
|
}
|
|
117
102
|
),
|
|
118
103
|
|
|
119
104
|
estimate_gas: createTool(
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
'Estimate Gas',
|
|
106
|
+
'Estimate gas required for a transaction. Supports contract calls, transfers, and deployments.',
|
|
122
107
|
z.object({
|
|
123
|
-
chain: z
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.describe("Recipient address (omit for contract deployment)"),
|
|
130
|
-
from: z
|
|
131
|
-
.string()
|
|
132
|
-
.optional()
|
|
133
|
-
.describe("Sender address (optional)"),
|
|
134
|
-
value: z
|
|
135
|
-
.string()
|
|
136
|
-
.optional()
|
|
137
|
-
.describe("ETH value to send (in wei as string)"),
|
|
138
|
-
data: z
|
|
139
|
-
.string()
|
|
140
|
-
.optional()
|
|
141
|
-
.describe("Transaction data (hex string for contract calls or deployment bytecode)"),
|
|
142
|
-
functionAbi: z
|
|
143
|
-
.string()
|
|
144
|
-
.optional()
|
|
145
|
-
.describe("Optional: Function ABI signature to encode call data automatically"),
|
|
108
|
+
chain: z.enum(SUPPORTED_CHAINS).describe('Blockchain network'),
|
|
109
|
+
to: z.string().optional().describe('Recipient address (omit for contract deployment)'),
|
|
110
|
+
from: z.string().optional().describe('Sender address (optional)'),
|
|
111
|
+
value: z.string().optional().describe('ETH value to send (in wei as string)'),
|
|
112
|
+
data: z.string().optional().describe('Transaction data (hex string for contract calls or deployment bytecode)'),
|
|
113
|
+
functionAbi: z.string().optional().describe('Optional: Function ABI signature to encode call data automatically'),
|
|
146
114
|
args: z
|
|
147
115
|
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
148
116
|
.optional()
|
|
149
|
-
.describe(
|
|
117
|
+
.describe('Optional: Function arguments (only used with functionAbi)')
|
|
150
118
|
}),
|
|
151
119
|
async (args) => {
|
|
152
120
|
if (args.to && !isAddress(args.to)) {
|
|
153
|
-
throw new Error(`Invalid to address: ${args.to}`)
|
|
121
|
+
throw new Error(`Invalid to address: ${args.to}`)
|
|
154
122
|
}
|
|
155
123
|
|
|
156
124
|
if (args.from && !isAddress(args.from)) {
|
|
157
|
-
throw new Error(`Invalid from address: ${args.from}`)
|
|
125
|
+
throw new Error(`Invalid from address: ${args.from}`)
|
|
158
126
|
}
|
|
159
127
|
|
|
160
|
-
const clientManager = getClientManager()
|
|
161
|
-
const client = clientManager.getClient(args.chain as ChainName)
|
|
128
|
+
const clientManager = getClientManager()
|
|
129
|
+
const client = clientManager.getClient(args.chain as ChainName)
|
|
162
130
|
|
|
163
|
-
|
|
164
|
-
let callData = args.data;
|
|
165
|
-
|
|
166
|
-
// If functionAbi is provided, encode the call data
|
|
167
|
-
if (args.functionAbi) {
|
|
168
|
-
const abiItem = parseAbiItem(args.functionAbi) as AbiFunction;
|
|
169
|
-
const convertedArgs = convertArgumentsToTypes(args.args || [], abiItem.inputs);
|
|
170
|
-
callData = encodeFunctionData({
|
|
171
|
-
abi: [abiItem],
|
|
172
|
-
functionName: abiItem.name,
|
|
173
|
-
args: convertedArgs,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
131
|
+
let callData = args.data
|
|
176
132
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
chain: args.chain,
|
|
187
|
-
gasEstimate: gasEstimate.toString(),
|
|
188
|
-
to: args.to,
|
|
189
|
-
from: args.from,
|
|
190
|
-
value: args.value,
|
|
191
|
-
});
|
|
192
|
-
} catch (error) {
|
|
193
|
-
throw new Error(`Gas estimation failed: ${error}`);
|
|
133
|
+
// If functionAbi is provided, encode the call data
|
|
134
|
+
if (args.functionAbi) {
|
|
135
|
+
const abiItem = parseAbiItem(args.functionAbi) as AbiFunction
|
|
136
|
+
const convertedArgs = convertArgumentsToTypes(args.args || [], abiItem.inputs)
|
|
137
|
+
callData = encodeFunctionData({
|
|
138
|
+
abi: [abiItem],
|
|
139
|
+
functionName: abiItem.name,
|
|
140
|
+
args: convertedArgs
|
|
141
|
+
})
|
|
194
142
|
}
|
|
143
|
+
|
|
144
|
+
const gasEstimate = await client.estimateGas({
|
|
145
|
+
to: args.to ? (args.to as Address) : undefined,
|
|
146
|
+
account: args.from ? (args.from as Address) : undefined,
|
|
147
|
+
value: args.value ? BigInt(args.value) : undefined,
|
|
148
|
+
data: callData as `0x${string}` | undefined
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return formatResponse({
|
|
152
|
+
success: true,
|
|
153
|
+
chain: args.chain,
|
|
154
|
+
gasEstimate: gasEstimate.toString(),
|
|
155
|
+
to: args.to,
|
|
156
|
+
from: args.from,
|
|
157
|
+
value: args.value
|
|
158
|
+
})
|
|
195
159
|
}
|
|
196
160
|
),
|
|
197
161
|
|
|
198
162
|
get_gas_price: createTool(
|
|
199
|
-
|
|
200
|
-
|
|
163
|
+
'Get Gas Price',
|
|
164
|
+
'Get current gas prices for a chain. Returns both legacy gasPrice and EIP-1559 fees (maxFeePerGas, maxPriorityFeePerGas).',
|
|
201
165
|
z.object({
|
|
202
|
-
chain: z
|
|
203
|
-
|
|
204
|
-
.describe("Blockchain network"),
|
|
205
|
-
formatted: z
|
|
206
|
-
.boolean()
|
|
207
|
-
.optional()
|
|
208
|
-
.default(true)
|
|
209
|
-
.describe("Return prices in Gwei (default: true). If false, returns wei."),
|
|
166
|
+
chain: z.enum(SUPPORTED_CHAINS).describe('Blockchain network'),
|
|
167
|
+
formatted: z.boolean().optional().default(true).describe('Return prices in Gwei (default: true). If false, returns wei.')
|
|
210
168
|
}),
|
|
211
169
|
async (args) => {
|
|
212
|
-
const clientManager = getClientManager()
|
|
213
|
-
const client = clientManager.getClient(args.chain as ChainName)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const response: any = {
|
|
232
|
-
chain: args.chain,
|
|
233
|
-
timestamp: new Date().toISOString(),
|
|
234
|
-
legacy: {
|
|
235
|
-
gasPrice: args.formatted ? formatPrice(gasPrice) : gasPrice.toString(),
|
|
236
|
-
gasPriceWei: gasPrice.toString(),
|
|
237
|
-
},
|
|
238
|
-
};
|
|
170
|
+
const clientManager = getClientManager()
|
|
171
|
+
const client = clientManager.getClient(args.chain as ChainName)
|
|
172
|
+
|
|
173
|
+
// Get both legacy and EIP-1559 gas prices
|
|
174
|
+
const [gasPrice, feeData] = await Promise.all([
|
|
175
|
+
client.getGasPrice(),
|
|
176
|
+
client.estimateFeesPerGas().catch(() => null) // Some chains don't support EIP-1559
|
|
177
|
+
])
|
|
178
|
+
|
|
179
|
+
const formatPrice = (wei: bigint): string => {
|
|
180
|
+
if (args.formatted) {
|
|
181
|
+
// Convert to Gwei (1 Gwei = 1e9 wei)
|
|
182
|
+
const gwei = Number(wei) / 1e9
|
|
183
|
+
return `${gwei.toFixed(2)} Gwei`
|
|
184
|
+
}
|
|
185
|
+
return wei.toString()
|
|
186
|
+
}
|
|
239
187
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
: feeData.maxPriorityFeePerGas.toString(),
|
|
249
|
-
maxFeePerGasWei: feeData.maxFeePerGas.toString(),
|
|
250
|
-
maxPriorityFeePerGasWei: feeData.maxPriorityFeePerGas.toString(),
|
|
251
|
-
};
|
|
188
|
+
const response: any = {
|
|
189
|
+
chain: args.chain,
|
|
190
|
+
timestamp: new Date().toISOString(),
|
|
191
|
+
legacy: {
|
|
192
|
+
gasPrice: args.formatted ? formatPrice(gasPrice) : gasPrice.toString(),
|
|
193
|
+
gasPriceWei: gasPrice.toString()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
252
196
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
197
|
+
// Add EIP-1559 data if available
|
|
198
|
+
if (feeData) {
|
|
199
|
+
response.eip1559 = {
|
|
200
|
+
maxFeePerGas: args.formatted ? formatPrice(feeData.maxFeePerGas) : feeData.maxFeePerGas.toString(),
|
|
201
|
+
maxPriorityFeePerGas: args.formatted
|
|
202
|
+
? formatPrice(feeData.maxPriorityFeePerGas)
|
|
203
|
+
: feeData.maxPriorityFeePerGas.toString(),
|
|
204
|
+
maxFeePerGasWei: feeData.maxFeePerGas.toString(),
|
|
205
|
+
maxPriorityFeePerGasWei: feeData.maxPriorityFeePerGas.toString()
|
|
259
206
|
}
|
|
260
207
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
208
|
+
// Calculate estimated total cost for a standard 21000 gas transaction
|
|
209
|
+
const standardGasLimit = 21000n
|
|
210
|
+
const estimatedCost = feeData.maxFeePerGas * standardGasLimit
|
|
211
|
+
response.eip1559.estimatedCostFor21kGas = args.formatted
|
|
212
|
+
? `${(Number(estimatedCost) / 1e18).toFixed(6)} ETH`
|
|
213
|
+
: estimatedCost.toString()
|
|
264
214
|
}
|
|
215
|
+
|
|
216
|
+
return formatResponse(response)
|
|
265
217
|
}
|
|
266
|
-
)
|
|
267
|
-
}
|
|
218
|
+
)
|
|
219
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
-
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'
|
|
3
3
|
import type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'
|
|
4
4
|
import advancedTools from './advanced.js'
|
|
5
5
|
import balanceTools from './balance.js'
|
package/src/tools/logs.ts
CHANGED
|
@@ -2,17 +2,38 @@ import { HypersyncClient, type Log, LogField } from '@envio-dev/hypersync-client
|
|
|
2
2
|
import {
|
|
3
3
|
type AbiEvent,
|
|
4
4
|
type Address,
|
|
5
|
+
decodeEventLog,
|
|
6
|
+
type Hex,
|
|
5
7
|
isAddress,
|
|
6
8
|
keccak256,
|
|
9
|
+
numberToHex,
|
|
10
|
+
padHex,
|
|
7
11
|
parseAbiItem,
|
|
8
12
|
toBytes,
|
|
9
13
|
toEventSignature
|
|
10
14
|
} from 'viem'
|
|
11
15
|
import { z } from 'zod'
|
|
16
|
+
import { getClientManager, SUPPORTED_CHAINS } from '../client.js'
|
|
12
17
|
import type { ChainName } from '../types.js'
|
|
13
|
-
import { getClientManager, HYPERSYNC_URLS, SUPPORTED_CHAINS } from '../client.js'
|
|
14
18
|
import { convertEventArgsToTypes, createTool, formatResponse } from '../utils.js'
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* A topic is always 32 bytes, so an address, a number or a short hex value has to be
|
|
22
|
+
* left-padded to fit. Passing one through unpadded is rejected outright by Hypersync.
|
|
23
|
+
*/
|
|
24
|
+
function toTopic(value: unknown): Hex {
|
|
25
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
26
|
+
return padHex(numberToHex(value), { size: 32 })
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === 'boolean') {
|
|
29
|
+
return padHex(value ? '0x01' : '0x00', { size: 32 })
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'string' && (isAddress(value) || value.startsWith('0x'))) {
|
|
32
|
+
return padHex(value.toLowerCase() as Hex, { size: 32 })
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Cannot filter on indexed value ${JSON.stringify(value)} — expected an address, number or hex string`)
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
async function getLogsWithHypersync(
|
|
17
38
|
chainName: ChainName,
|
|
18
39
|
eventAbi: string,
|
|
@@ -22,7 +43,7 @@ async function getLogsWithHypersync(
|
|
|
22
43
|
toBlock?: string,
|
|
23
44
|
eventArgs?: Record<string, unknown>
|
|
24
45
|
) {
|
|
25
|
-
const hypersyncUrl =
|
|
46
|
+
const hypersyncUrl = getClientManager().getHypersyncUrl(chainName)
|
|
26
47
|
if (!hypersyncUrl) {
|
|
27
48
|
throw new Error(`Hypersync not supported for chain: ${chainName}`)
|
|
28
49
|
}
|
|
@@ -41,27 +62,13 @@ async function getLogsWithHypersync(
|
|
|
41
62
|
|
|
42
63
|
// Add indexed parameter filtering if eventArgs provided
|
|
43
64
|
if (eventArgs && Object.keys(eventArgs).length > 0) {
|
|
44
|
-
const indexedInputs = abiItem.inputs.filter(input => input.indexed)
|
|
65
|
+
const indexedInputs = abiItem.inputs.filter((input) => input.indexed)
|
|
45
66
|
|
|
46
67
|
for (let i = 0; i < indexedInputs.length && i < 3; i++) {
|
|
47
68
|
const input = indexedInputs[i]
|
|
48
69
|
const argValue = input?.name ? eventArgs[input.name] : undefined
|
|
49
70
|
|
|
50
|
-
|
|
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
|
-
}
|
|
71
|
+
topics.push(argValue === undefined ? null : toTopic(argValue))
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -81,12 +88,14 @@ async function getLogsWithHypersync(
|
|
|
81
88
|
LogField.LogIndex
|
|
82
89
|
]
|
|
83
90
|
},
|
|
84
|
-
fromBlock: fromBlock ? Number.parseInt(fromBlock) : 0,
|
|
85
|
-
toBlock: toBlock ? Number.parseInt(toBlock) : undefined,
|
|
91
|
+
fromBlock: fromBlock ? Number.parseInt(fromBlock, 10) : 0,
|
|
92
|
+
toBlock: toBlock ? Number.parseInt(toBlock, 10) : undefined,
|
|
86
93
|
logs: [
|
|
87
94
|
{
|
|
88
95
|
address: address ? [address] : undefined,
|
|
89
|
-
|
|
96
|
+
// An unfiltered position is an empty list, not a dropped entry: removing it would
|
|
97
|
+
// slide every later filter up a slot and match on the wrong parameter.
|
|
98
|
+
topics: topics.map((t) => (t === null ? [] : Array.isArray(t) ? t : [t]))
|
|
90
99
|
}
|
|
91
100
|
]
|
|
92
101
|
}
|
|
@@ -96,18 +105,22 @@ async function getLogsWithHypersync(
|
|
|
96
105
|
// Convert hypersync logs to viem-compatible format
|
|
97
106
|
const decodedLogs = res.data.logs
|
|
98
107
|
.map((log: Log) => {
|
|
108
|
+
const topics = log.topics.filter((topic): topic is string => Boolean(topic)) as [Hex, ...Hex[]]
|
|
109
|
+
const data = (log.data ?? '0x') as Hex
|
|
110
|
+
|
|
99
111
|
try {
|
|
112
|
+
const { args } = decodeEventLog({ abi: [abiItem], data, topics })
|
|
100
113
|
return {
|
|
101
114
|
address: log.address as Address,
|
|
102
|
-
blockHash: log.blockHash as
|
|
115
|
+
blockHash: log.blockHash as Hex,
|
|
103
116
|
blockNumber: log.blockNumber ? BigInt(log.blockNumber) : 0n,
|
|
104
|
-
data
|
|
117
|
+
data,
|
|
105
118
|
logIndex: log.logIndex,
|
|
106
|
-
topics
|
|
107
|
-
transactionHash: log.transactionHash as
|
|
119
|
+
topics,
|
|
120
|
+
transactionHash: log.transactionHash as Hex,
|
|
108
121
|
transactionIndex: log.transactionIndex,
|
|
109
122
|
eventName: abiItem.name,
|
|
110
|
-
args: {}
|
|
123
|
+
args: args ?? {}
|
|
111
124
|
}
|
|
112
125
|
} catch (error) {
|
|
113
126
|
console.warn('Failed to decode log:', error)
|
|
@@ -127,21 +140,12 @@ export default {
|
|
|
127
140
|
chain: z.enum(SUPPORTED_CHAINS).describe('The blockchain network to use'),
|
|
128
141
|
eventAbi: z
|
|
129
142
|
.string()
|
|
130
|
-
.describe(
|
|
131
|
-
'Event ABI definition (e.g., "event Transfer(address indexed from, address indexed to, uint256 value)")'
|
|
132
|
-
),
|
|
143
|
+
.describe('Event ABI definition (e.g., "event Transfer(address indexed from, address indexed to, uint256 value)")'),
|
|
133
144
|
address: z.string().optional().describe('Contract address (RECOMMENDED for performance)'),
|
|
134
145
|
fromBlock: z.string().optional().describe('Start block (defaults to latest-1000)'),
|
|
135
146
|
toBlock: z.string().optional().describe('End block (defaults to latest)'),
|
|
136
147
|
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
|
-
)
|
|
148
|
+
.record(z.union([z.string(), z.number(), z.boolean(), z.array(z.union([z.string(), z.number(), z.boolean()]))]))
|
|
145
149
|
.optional()
|
|
146
150
|
.describe('Filter by indexed parameters: {"from": "0x123...", "to": "0x456..."}')
|
|
147
151
|
}),
|
|
@@ -187,7 +191,7 @@ export default {
|
|
|
187
191
|
const logs = await client.getLogs(getLogsParams)
|
|
188
192
|
|
|
189
193
|
// Convert logs to serializable format
|
|
190
|
-
const serializedLogs = logs.map(log => {
|
|
194
|
+
const serializedLogs = logs.map((log) => {
|
|
191
195
|
const baseLogData = {
|
|
192
196
|
address: log.address,
|
|
193
197
|
blockHash: log.blockHash,
|
|
@@ -223,7 +227,7 @@ export default {
|
|
|
223
227
|
})
|
|
224
228
|
} catch (viemError) {
|
|
225
229
|
// If viem fails and we have hypersync support for this chain, try hypersync as fallback
|
|
226
|
-
if (
|
|
230
|
+
if (clientManager.getHypersyncUrl(args.chain as ChainName)) {
|
|
227
231
|
console.warn(`Viem getLogs failed for ${args.chain}, trying hypersync fallback:`, viemError)
|
|
228
232
|
|
|
229
233
|
try {
|
|
@@ -242,7 +246,7 @@ export default {
|
|
|
242
246
|
const toBlockNum = args.toBlock ? BigInt(args.toBlock) : undefined
|
|
243
247
|
|
|
244
248
|
// Convert hypersync logs to the same format as viem logs
|
|
245
|
-
const serializedLogs = hypersyncLogs.map(log => {
|
|
249
|
+
const serializedLogs = hypersyncLogs.map((log) => {
|
|
246
250
|
const baseLogData = {
|
|
247
251
|
address: log.address,
|
|
248
252
|
blockHash: log.blockHash,
|
|
@@ -277,9 +281,7 @@ export default {
|
|
|
277
281
|
}
|
|
278
282
|
})
|
|
279
283
|
} catch (hypersyncError) {
|
|
280
|
-
throw new Error(
|
|
281
|
-
`Both viem and hypersync failed. Viem error: ${viemError}. Hypersync error: ${hypersyncError}`
|
|
282
|
-
)
|
|
284
|
+
throw new Error(`Both viem and hypersync failed. Viem error: ${viemError}. Hypersync error: ${hypersyncError}`)
|
|
283
285
|
}
|
|
284
286
|
} else {
|
|
285
287
|
// No hypersync fallback available for this chain
|