web3-tools-mcp 2.0.2 → 3.1.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 +182 -1
- package/README.md +76 -47
- package/dist/anvil.d.ts +17 -19
- package/dist/anvil.d.ts.map +1 -1
- package/dist/anvil.js +124 -228
- package/dist/anvil.js.map +1 -1
- package/dist/chain-meta.d.ts.map +1 -1
- package/dist/chain-meta.js +19 -8
- package/dist/chain-meta.js.map +1 -1
- package/dist/chains.d.ts +9 -1
- package/dist/chains.d.ts.map +1 -1
- package/dist/chains.js +11 -2
- package/dist/chains.js.map +1 -1
- package/dist/clear-signing.d.ts.map +1 -1
- package/dist/clear-signing.js +17 -6
- package/dist/clear-signing.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +22 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +24 -0
- package/dist/context.js.map +1 -0
- package/dist/hosted.d.ts +12 -0
- package/dist/hosted.d.ts.map +1 -0
- package/dist/hosted.js +12 -0
- package/dist/hosted.js.map +1 -0
- package/dist/http-server.d.ts +13 -0
- package/dist/http-server.d.ts.map +1 -1
- package/dist/http-server.js +22 -7
- package/dist/http-server.js.map +1 -1
- package/dist/index.js +20 -12
- package/dist/index.js.map +1 -1
- package/dist/kv-storage.d.ts +2 -1
- package/dist/kv-storage.d.ts.map +1 -1
- package/dist/kv-storage.js +5 -2
- package/dist/kv-storage.js.map +1 -1
- package/dist/lib.d.ts +24 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +23 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +43 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/oauth.d.ts +57 -8
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +133 -63
- package/dist/oauth.js.map +1 -1
- package/dist/preview.d.ts +7 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +21 -7
- package/dist/preview.js.map +1 -1
- package/dist/signing-requests.d.ts +81 -0
- package/dist/signing-requests.d.ts.map +1 -0
- package/dist/signing-requests.js +138 -0
- package/dist/signing-requests.js.map +1 -0
- package/dist/tools/index.d.ts +27 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +50 -15
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/{advanced.d.ts → read/advanced.d.ts} +39 -30
- package/dist/tools/read/advanced.d.ts.map +1 -0
- package/dist/tools/{advanced.js → read/advanced.js} +53 -41
- package/dist/tools/read/advanced.js.map +1 -0
- package/dist/tools/{balance.d.ts → read/balance.d.ts} +8 -7
- package/dist/tools/read/balance.d.ts.map +1 -0
- package/dist/tools/{balance.js → read/balance.js} +18 -33
- package/dist/tools/read/balance.js.map +1 -0
- package/dist/tools/{contract-info.d.ts → read/contract-info.d.ts} +20 -15
- package/dist/tools/read/contract-info.d.ts.map +1 -0
- package/dist/tools/{contract-info.js → read/contract-info.js} +54 -123
- package/dist/tools/read/contract-info.js.map +1 -0
- package/dist/tools/{contract.d.ts → read/contract.d.ts} +14 -12
- package/dist/tools/read/contract.d.ts.map +1 -0
- package/dist/tools/{contract.js → read/contract.js} +3 -2
- package/dist/tools/read/contract.js.map +1 -0
- package/dist/tools/read/ens.d.ts +104 -0
- package/dist/tools/read/ens.d.ts.map +1 -0
- package/dist/tools/{ens.js → read/ens.js} +25 -33
- package/dist/tools/read/ens.js.map +1 -0
- package/dist/tools/read/gas.d.ts +171 -0
- package/dist/tools/read/gas.d.ts.map +1 -0
- package/dist/tools/read/gas.js +333 -0
- package/dist/tools/read/gas.js.map +1 -0
- package/dist/tools/read/logs.d.ts +101 -0
- package/dist/tools/read/logs.d.ts.map +1 -0
- package/dist/tools/read/logs.js +211 -0
- package/dist/tools/read/logs.js.map +1 -0
- package/dist/tools/{signatures.d.ts → read/signatures.d.ts} +8 -4
- package/dist/tools/read/signatures.d.ts.map +1 -0
- package/dist/tools/{signatures.js → read/signatures.js} +4 -3
- package/dist/tools/read/signatures.js.map +1 -0
- package/dist/tools/read/signing.d.ts +27 -0
- package/dist/tools/read/signing.d.ts.map +1 -0
- package/dist/tools/read/signing.js +81 -0
- package/dist/tools/read/signing.js.map +1 -0
- package/dist/tools/sign/errors.d.ts +9 -0
- package/dist/tools/sign/errors.d.ts.map +1 -0
- package/dist/tools/sign/errors.js +29 -0
- package/dist/tools/sign/errors.js.map +1 -0
- package/dist/tools/sign/transactions.d.ts +141 -0
- package/dist/tools/sign/transactions.d.ts.map +1 -0
- package/dist/tools/sign/transactions.js +361 -0
- package/dist/tools/sign/transactions.js.map +1 -0
- package/dist/tools/sign/wallet.d.ts +50 -0
- package/dist/tools/sign/wallet.d.ts.map +1 -0
- package/dist/tools/sign/wallet.js +180 -0
- package/dist/tools/sign/wallet.js.map +1 -0
- package/dist/utils.d.ts +23 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +48 -3
- package/dist/utils.js.map +1 -1
- package/dist/wallet-client.d.ts +34 -4
- package/dist/wallet-client.d.ts.map +1 -1
- package/dist/wallet-client.js +106 -27
- package/dist/wallet-client.js.map +1 -1
- package/dist/walletconnect.d.ts +100 -13
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +215 -50
- package/dist/walletconnect.js.map +1 -1
- package/package.json +12 -9
- package/src/anvil.ts +150 -271
- package/src/chain-meta.ts +16 -7
- package/src/chains.ts +14 -2
- package/src/clear-signing.ts +15 -9
- package/src/client.ts +0 -1
- package/src/context.ts +27 -0
- package/src/hosted.ts +11 -0
- package/src/http-server.ts +38 -8
- package/src/index.ts +20 -13
- package/src/kv-storage.ts +6 -3
- package/src/lib.ts +24 -0
- package/src/mcp-server.ts +47 -0
- package/src/oauth.ts +181 -69
- package/src/preview.ts +28 -7
- package/src/signing-requests.ts +198 -0
- package/src/tools/index.ts +78 -16
- package/src/tools/{advanced.ts → read/advanced.ts} +74 -43
- package/src/tools/{balance.ts → read/balance.ts} +20 -35
- package/src/tools/{contract-info.ts → read/contract-info.ts} +66 -146
- package/src/tools/{contract.ts → read/contract.ts} +4 -3
- package/src/tools/{ens.ts → read/ens.ts} +27 -34
- package/src/tools/read/gas.ts +443 -0
- package/src/tools/read/logs.ts +281 -0
- package/src/tools/{signatures.ts → read/signatures.ts} +5 -5
- package/src/tools/read/signing.ts +92 -0
- package/src/tools/sign/errors.ts +28 -0
- package/src/tools/sign/transactions.ts +479 -0
- package/src/tools/sign/wallet.ts +216 -0
- package/src/utils.ts +52 -3
- package/src/wallet-client.ts +107 -24
- package/src/walletconnect.ts +250 -48
- package/dist/tools/advanced.d.ts.map +0 -1
- package/dist/tools/advanced.js.map +0 -1
- package/dist/tools/balance.d.ts.map +0 -1
- package/dist/tools/balance.js.map +0 -1
- package/dist/tools/contract-info.d.ts.map +0 -1
- package/dist/tools/contract-info.js.map +0 -1
- package/dist/tools/contract.d.ts.map +0 -1
- package/dist/tools/contract.js.map +0 -1
- package/dist/tools/ens.d.ts +0 -119
- package/dist/tools/ens.d.ts.map +0 -1
- package/dist/tools/ens.js.map +0 -1
- package/dist/tools/gas.d.ts +0 -99
- package/dist/tools/gas.d.ts.map +0 -1
- package/dist/tools/gas.js +0 -181
- package/dist/tools/gas.js.map +0 -1
- package/dist/tools/logs.d.ts +0 -39
- package/dist/tools/logs.d.ts.map +0 -1
- package/dist/tools/logs.js +0 -230
- package/dist/tools/logs.js.map +0 -1
- package/dist/tools/signatures.d.ts.map +0 -1
- package/dist/tools/signatures.js.map +0 -1
- package/dist/tools/transactions.d.ts +0 -155
- package/dist/tools/transactions.d.ts.map +0 -1
- package/dist/tools/transactions.js +0 -365
- package/dist/tools/transactions.js.map +0 -1
- package/src/tools/gas.ts +0 -219
- package/src/tools/logs.ts +0 -293
- package/src/tools/transactions.ts +0 -456
package/src/tools/gas.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
export default {
|
|
8
|
-
simulate_contract: createTool(
|
|
9
|
-
'Simulate Contract Call',
|
|
10
|
-
'Simulate a contract call (including state-changing functions) without broadcasting. Returns simulation result and estimated gas.',
|
|
11
|
-
z.object({
|
|
12
|
-
chain: z.enum(SUPPORTED_CHAINS).describe('Blockchain network to simulate on'),
|
|
13
|
-
contractAddress: z.string().describe('Contract address to call'),
|
|
14
|
-
functionAbi: z
|
|
15
|
-
.string()
|
|
16
|
-
.describe('Function ABI signature (e.g., "function transfer(address to, uint256 amount)"). Can be any function type.'),
|
|
17
|
-
args: z
|
|
18
|
-
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
19
|
-
.optional()
|
|
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)')
|
|
24
|
-
}),
|
|
25
|
-
async (args) => {
|
|
26
|
-
if (!isAddress(args.contractAddress)) {
|
|
27
|
-
throw new Error(`Invalid contract address: ${args.contractAddress}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (args.from && !isAddress(args.from)) {
|
|
31
|
-
throw new Error(`Invalid from address: ${args.from}`)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const clientManager = getClientManager()
|
|
35
|
-
const client = clientManager.getClient(args.chain as ChainName)
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const abiItem = parseAbiItem(args.functionAbi) as AbiFunction
|
|
39
|
-
const convertedArgs = convertArgumentsToTypes(args.args || [], abiItem.inputs)
|
|
40
|
-
|
|
41
|
-
const blockTag = args.blockNumber ? BigInt(args.blockNumber) : undefined
|
|
42
|
-
|
|
43
|
-
// Simulate the call
|
|
44
|
-
const result = await client.call({
|
|
45
|
-
to: args.contractAddress as Address,
|
|
46
|
-
data: encodeFunctionData({
|
|
47
|
-
abi: [abiItem],
|
|
48
|
-
functionName: abiItem.name,
|
|
49
|
-
args: convertedArgs
|
|
50
|
-
}),
|
|
51
|
-
account: args.from ? (args.from as Address) : undefined,
|
|
52
|
-
value: args.value ? BigInt(args.value) : undefined,
|
|
53
|
-
blockNumber: blockTag
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
// Also estimate gas
|
|
57
|
-
const gasEstimate = await client.estimateGas({
|
|
58
|
-
to: args.contractAddress as Address,
|
|
59
|
-
data: encodeFunctionData({
|
|
60
|
-
abi: [abiItem],
|
|
61
|
-
functionName: abiItem.name,
|
|
62
|
-
args: convertedArgs
|
|
63
|
-
}),
|
|
64
|
-
account: args.from ? (args.from as Address) : undefined,
|
|
65
|
-
value: args.value ? BigInt(args.value) : undefined,
|
|
66
|
-
blockNumber: blockTag
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// Decode the result if the function has outputs
|
|
70
|
-
let decodedResult: unknown = result.data
|
|
71
|
-
if (abiItem.outputs && abiItem.outputs.length > 0 && result.data) {
|
|
72
|
-
decodedResult = decodeFunctionResult({
|
|
73
|
-
abi: [abiItem],
|
|
74
|
-
functionName: abiItem.name,
|
|
75
|
-
data: result.data
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return formatResponse({
|
|
80
|
-
success: true,
|
|
81
|
-
chain: args.chain,
|
|
82
|
-
contractAddress: args.contractAddress,
|
|
83
|
-
functionName: abiItem.name,
|
|
84
|
-
result: decodedResult,
|
|
85
|
-
rawData: result.data,
|
|
86
|
-
gasEstimate: gasEstimate.toString(),
|
|
87
|
-
blockNumber: args.blockNumber || 'latest'
|
|
88
|
-
})
|
|
89
|
-
} catch (error) {
|
|
90
|
-
// Check if it's a revert error
|
|
91
|
-
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
92
|
-
|
|
93
|
-
return formatResponse({
|
|
94
|
-
success: false,
|
|
95
|
-
chain: args.chain,
|
|
96
|
-
contractAddress: args.contractAddress,
|
|
97
|
-
error: errorMessage,
|
|
98
|
-
reverted: errorMessage.includes('revert') || errorMessage.includes('execution reverted')
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
),
|
|
103
|
-
|
|
104
|
-
estimate_gas: createTool(
|
|
105
|
-
'Estimate Gas',
|
|
106
|
-
'Estimate gas required for a transaction. Supports contract calls, transfers, and deployments.',
|
|
107
|
-
z.object({
|
|
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'),
|
|
114
|
-
args: z
|
|
115
|
-
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
116
|
-
.optional()
|
|
117
|
-
.describe('Optional: Function arguments (only used with functionAbi)')
|
|
118
|
-
}),
|
|
119
|
-
async (args) => {
|
|
120
|
-
if (args.to && !isAddress(args.to)) {
|
|
121
|
-
throw new Error(`Invalid to address: ${args.to}`)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (args.from && !isAddress(args.from)) {
|
|
125
|
-
throw new Error(`Invalid from address: ${args.from}`)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const clientManager = getClientManager()
|
|
129
|
-
const client = clientManager.getClient(args.chain as ChainName)
|
|
130
|
-
|
|
131
|
-
let callData = args.data
|
|
132
|
-
|
|
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
|
-
})
|
|
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
|
-
})
|
|
159
|
-
}
|
|
160
|
-
),
|
|
161
|
-
|
|
162
|
-
get_gas_price: createTool(
|
|
163
|
-
'Get Gas Price',
|
|
164
|
-
'Get current gas prices for a chain. Returns both legacy gasPrice and EIP-1559 fees (maxFeePerGas, maxPriorityFeePerGas).',
|
|
165
|
-
z.object({
|
|
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.')
|
|
168
|
-
}),
|
|
169
|
-
async (args) => {
|
|
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
|
-
}
|
|
187
|
-
|
|
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
|
-
}
|
|
196
|
-
|
|
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()
|
|
206
|
-
}
|
|
207
|
-
|
|
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()
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return formatResponse(response)
|
|
217
|
-
}
|
|
218
|
-
)
|
|
219
|
-
}
|
package/src/tools/logs.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import { HypersyncClient, type Log, LogField } from '@envio-dev/hypersync-client'
|
|
2
|
-
import {
|
|
3
|
-
type AbiEvent,
|
|
4
|
-
type Address,
|
|
5
|
-
decodeEventLog,
|
|
6
|
-
type Hex,
|
|
7
|
-
isAddress,
|
|
8
|
-
keccak256,
|
|
9
|
-
numberToHex,
|
|
10
|
-
padHex,
|
|
11
|
-
parseAbiItem,
|
|
12
|
-
toBytes,
|
|
13
|
-
toEventSignature
|
|
14
|
-
} from 'viem'
|
|
15
|
-
import { z } from 'zod'
|
|
16
|
-
import { getClientManager, SUPPORTED_CHAINS } from '../client.js'
|
|
17
|
-
import type { ChainName } from '../types.js'
|
|
18
|
-
import { convertEventArgsToTypes, createTool, formatResponse } from '../utils.js'
|
|
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
|
-
|
|
37
|
-
async function getLogsWithHypersync(
|
|
38
|
-
chainName: ChainName,
|
|
39
|
-
eventAbi: string,
|
|
40
|
-
hypersyncApiKey?: string,
|
|
41
|
-
address?: string,
|
|
42
|
-
fromBlock?: string,
|
|
43
|
-
toBlock?: string,
|
|
44
|
-
eventArgs?: Record<string, unknown>
|
|
45
|
-
) {
|
|
46
|
-
const hypersyncUrl = getClientManager().getHypersyncUrl(chainName)
|
|
47
|
-
if (!hypersyncUrl) {
|
|
48
|
-
throw new Error(`Hypersync not supported for chain: ${chainName}`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const client = HypersyncClient.new({
|
|
52
|
-
url: hypersyncUrl,
|
|
53
|
-
bearerToken: hypersyncApiKey
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const abiItem = parseAbiItem(eventAbi) as AbiEvent
|
|
57
|
-
const eventSignature = toEventSignature(abiItem)
|
|
58
|
-
const topic0 = keccak256(toBytes(eventSignature))
|
|
59
|
-
|
|
60
|
-
// Build topics array for filtering
|
|
61
|
-
const topics: (string | string[] | null)[] = [topic0]
|
|
62
|
-
|
|
63
|
-
// Add indexed parameter filtering if eventArgs provided
|
|
64
|
-
if (eventArgs && Object.keys(eventArgs).length > 0) {
|
|
65
|
-
const indexedInputs = abiItem.inputs.filter((input) => input.indexed)
|
|
66
|
-
|
|
67
|
-
for (let i = 0; i < indexedInputs.length && i < 3; i++) {
|
|
68
|
-
const input = indexedInputs[i]
|
|
69
|
-
const argValue = input?.name ? eventArgs[input.name] : undefined
|
|
70
|
-
|
|
71
|
-
topics.push(argValue === undefined ? null : toTopic(argValue))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const query = {
|
|
76
|
-
fieldSelection: {
|
|
77
|
-
log: [
|
|
78
|
-
LogField.Address,
|
|
79
|
-
LogField.Topic0,
|
|
80
|
-
LogField.Topic1,
|
|
81
|
-
LogField.Topic2,
|
|
82
|
-
LogField.Topic3,
|
|
83
|
-
LogField.Data,
|
|
84
|
-
LogField.BlockNumber,
|
|
85
|
-
LogField.BlockHash,
|
|
86
|
-
LogField.TransactionHash,
|
|
87
|
-
LogField.TransactionIndex,
|
|
88
|
-
LogField.LogIndex
|
|
89
|
-
]
|
|
90
|
-
},
|
|
91
|
-
fromBlock: fromBlock ? Number.parseInt(fromBlock, 10) : 0,
|
|
92
|
-
toBlock: toBlock ? Number.parseInt(toBlock, 10) : undefined,
|
|
93
|
-
logs: [
|
|
94
|
-
{
|
|
95
|
-
address: address ? [address] : undefined,
|
|
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]))
|
|
99
|
-
}
|
|
100
|
-
]
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const res = await client.get(query)
|
|
104
|
-
|
|
105
|
-
// Convert hypersync logs to viem-compatible format
|
|
106
|
-
const decodedLogs = res.data.logs
|
|
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
|
-
|
|
111
|
-
try {
|
|
112
|
-
const { args } = decodeEventLog({ abi: [abiItem], data, topics })
|
|
113
|
-
return {
|
|
114
|
-
address: log.address as Address,
|
|
115
|
-
blockHash: log.blockHash as Hex,
|
|
116
|
-
blockNumber: log.blockNumber ? BigInt(log.blockNumber) : 0n,
|
|
117
|
-
data,
|
|
118
|
-
logIndex: log.logIndex,
|
|
119
|
-
topics,
|
|
120
|
-
transactionHash: log.transactionHash as Hex,
|
|
121
|
-
transactionIndex: log.transactionIndex,
|
|
122
|
-
eventName: abiItem.name,
|
|
123
|
-
args: args ?? {}
|
|
124
|
-
}
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.warn('Failed to decode log:', error)
|
|
127
|
-
return null
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
.filter((log): log is NonNullable<typeof log> => log !== null)
|
|
131
|
-
|
|
132
|
-
return decodedLogs
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export default {
|
|
136
|
-
get_logs: createTool(
|
|
137
|
-
'Query Contract Events',
|
|
138
|
-
'Search and decode contract events with filtering. Uses viem with hypersync fallback. Specify address and block ranges for best performance.',
|
|
139
|
-
z.object({
|
|
140
|
-
chain: z.enum(SUPPORTED_CHAINS).describe('The blockchain network to use'),
|
|
141
|
-
eventAbi: z
|
|
142
|
-
.string()
|
|
143
|
-
.describe('Event ABI definition (e.g., "event Transfer(address indexed from, address indexed to, uint256 value)")'),
|
|
144
|
-
address: z.string().optional().describe('Contract address (RECOMMENDED for performance)'),
|
|
145
|
-
fromBlock: z.string().optional().describe('Start block (defaults to latest-1000)'),
|
|
146
|
-
toBlock: z.string().optional().describe('End block (defaults to latest)'),
|
|
147
|
-
eventArgs: z
|
|
148
|
-
.record(z.union([z.string(), z.number(), z.boolean(), z.array(z.union([z.string(), z.number(), z.boolean()]))]))
|
|
149
|
-
.optional()
|
|
150
|
-
.describe('Filter by indexed parameters: {"from": "0x123...", "to": "0x456..."}')
|
|
151
|
-
}),
|
|
152
|
-
async (args) => {
|
|
153
|
-
const clientManager = getClientManager()
|
|
154
|
-
const config = (clientManager as any).config
|
|
155
|
-
|
|
156
|
-
// First try with regular viem client
|
|
157
|
-
try {
|
|
158
|
-
const client = clientManager.getClient(args.chain as ChainName)
|
|
159
|
-
const abiItem = parseAbiItem(args.eventAbi) as AbiEvent
|
|
160
|
-
|
|
161
|
-
// Determine block range
|
|
162
|
-
const fromBlockNum = args.fromBlock ? BigInt(args.fromBlock) : undefined
|
|
163
|
-
const toBlockNum = args.toBlock ? BigInt(args.toBlock) : undefined
|
|
164
|
-
|
|
165
|
-
// Prepare getLogs parameters
|
|
166
|
-
const getLogsParams: {
|
|
167
|
-
address?: Address
|
|
168
|
-
event: AbiEvent
|
|
169
|
-
fromBlock?: bigint
|
|
170
|
-
toBlock?: bigint
|
|
171
|
-
args?: Record<string, unknown>
|
|
172
|
-
} = {
|
|
173
|
-
event: abiItem,
|
|
174
|
-
fromBlock: fromBlockNum,
|
|
175
|
-
toBlock: toBlockNum
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Add address filter if provided
|
|
179
|
-
if (args.address && isAddress(args.address)) {
|
|
180
|
-
getLogsParams.address = args.address as Address
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Add event argument filters if provided
|
|
184
|
-
if (args.eventArgs && Object.keys(args.eventArgs).length > 0) {
|
|
185
|
-
// Convert event arguments to proper types based on ABI
|
|
186
|
-
const convertedEventArgs = convertEventArgsToTypes(args.eventArgs, abiItem.inputs)
|
|
187
|
-
getLogsParams.args = convertedEventArgs
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Use client.getLogs with proper typing
|
|
191
|
-
const logs = await client.getLogs(getLogsParams)
|
|
192
|
-
|
|
193
|
-
// Convert logs to serializable format
|
|
194
|
-
const serializedLogs = logs.map((log) => {
|
|
195
|
-
const baseLogData = {
|
|
196
|
-
address: log.address,
|
|
197
|
-
blockHash: log.blockHash,
|
|
198
|
-
blockNumber: log.blockNumber?.toString(),
|
|
199
|
-
data: log.data,
|
|
200
|
-
logIndex: log.logIndex,
|
|
201
|
-
topics: log.topics,
|
|
202
|
-
transactionHash: log.transactionHash,
|
|
203
|
-
transactionIndex: log.transactionIndex
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
...baseLogData,
|
|
208
|
-
decoded: {
|
|
209
|
-
eventName: log.eventName,
|
|
210
|
-
args: log.args
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
return formatResponse({
|
|
216
|
-
logs: serializedLogs,
|
|
217
|
-
eventSignature: toEventSignature(abiItem),
|
|
218
|
-
chain: args.chain,
|
|
219
|
-
fromBlock: fromBlockNum?.toString() || 'latest',
|
|
220
|
-
toBlock: toBlockNum?.toString() || 'latest',
|
|
221
|
-
count: logs.length,
|
|
222
|
-
dataSource: 'viem',
|
|
223
|
-
filters: {
|
|
224
|
-
address: args.address || null,
|
|
225
|
-
eventArgs: args.eventArgs || null
|
|
226
|
-
}
|
|
227
|
-
})
|
|
228
|
-
} catch (viemError) {
|
|
229
|
-
// If viem fails and we have hypersync support for this chain, try hypersync as fallback
|
|
230
|
-
if (clientManager.getHypersyncUrl(args.chain as ChainName)) {
|
|
231
|
-
console.warn(`Viem getLogs failed for ${args.chain}, trying hypersync fallback:`, viemError)
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
const hypersyncLogs = await getLogsWithHypersync(
|
|
235
|
-
args.chain as ChainName,
|
|
236
|
-
args.eventAbi,
|
|
237
|
-
config.hypersyncApiKey,
|
|
238
|
-
args.address,
|
|
239
|
-
args.fromBlock,
|
|
240
|
-
args.toBlock,
|
|
241
|
-
args.eventArgs
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
const abiItem = parseAbiItem(args.eventAbi) as AbiEvent
|
|
245
|
-
const fromBlockNum = args.fromBlock ? BigInt(args.fromBlock) : undefined
|
|
246
|
-
const toBlockNum = args.toBlock ? BigInt(args.toBlock) : undefined
|
|
247
|
-
|
|
248
|
-
// Convert hypersync logs to the same format as viem logs
|
|
249
|
-
const serializedLogs = hypersyncLogs.map((log) => {
|
|
250
|
-
const baseLogData = {
|
|
251
|
-
address: log.address,
|
|
252
|
-
blockHash: log.blockHash,
|
|
253
|
-
blockNumber: log.blockNumber?.toString(),
|
|
254
|
-
data: log.data,
|
|
255
|
-
logIndex: log.logIndex,
|
|
256
|
-
topics: log.topics,
|
|
257
|
-
transactionHash: log.transactionHash,
|
|
258
|
-
transactionIndex: log.transactionIndex
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
...baseLogData,
|
|
263
|
-
decoded: {
|
|
264
|
-
eventName: log.eventName,
|
|
265
|
-
args: log.args
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
return formatResponse({
|
|
271
|
-
logs: serializedLogs,
|
|
272
|
-
eventSignature: toEventSignature(abiItem),
|
|
273
|
-
chain: args.chain,
|
|
274
|
-
fromBlock: fromBlockNum?.toString() || 'latest',
|
|
275
|
-
toBlock: toBlockNum?.toString() || 'latest',
|
|
276
|
-
count: hypersyncLogs.length,
|
|
277
|
-
dataSource: 'hypersync',
|
|
278
|
-
filters: {
|
|
279
|
-
address: args.address || null,
|
|
280
|
-
eventArgs: args.eventArgs || null
|
|
281
|
-
}
|
|
282
|
-
})
|
|
283
|
-
} catch (hypersyncError) {
|
|
284
|
-
throw new Error(`Both viem and hypersync failed. Viem error: ${viemError}. Hypersync error: ${hypersyncError}`)
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
// No hypersync fallback available for this chain
|
|
288
|
-
throw new Error(`Failed to get logs with viem (hypersync not available for ${args.chain}): ${viemError}`)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
)
|
|
293
|
-
}
|