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
|
@@ -0,0 +1,281 @@
|
|
|
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, rpcReason } 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
|
+
/** More than this is cut short, with the block to resume from. */
|
|
38
|
+
export const MAX_LOGS = 1000
|
|
39
|
+
|
|
40
|
+
interface DecodedLog {
|
|
41
|
+
address: Address
|
|
42
|
+
blockHash: Hex | null
|
|
43
|
+
blockNumber: bigint | null
|
|
44
|
+
data: Hex
|
|
45
|
+
logIndex: number | null | undefined
|
|
46
|
+
topics: readonly Hex[]
|
|
47
|
+
transactionHash: Hex | null
|
|
48
|
+
transactionIndex: number | null | undefined
|
|
49
|
+
eventName: string
|
|
50
|
+
args: unknown
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function serializeLog(log: DecodedLog) {
|
|
54
|
+
return {
|
|
55
|
+
address: log.address,
|
|
56
|
+
blockHash: log.blockHash,
|
|
57
|
+
blockNumber: log.blockNumber?.toString(),
|
|
58
|
+
data: log.data,
|
|
59
|
+
logIndex: log.logIndex,
|
|
60
|
+
topics: log.topics,
|
|
61
|
+
transactionHash: log.transactionHash,
|
|
62
|
+
transactionIndex: log.transactionIndex,
|
|
63
|
+
decoded: { eventName: log.eventName, args: log.args }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Resuming from `nextBlock` may repeat logs of that block already returned. */
|
|
68
|
+
export function capLogs(logs: DecodedLog[]) {
|
|
69
|
+
if (logs.length <= MAX_LOGS) return { logs: logs.map(serializeLog) }
|
|
70
|
+
return {
|
|
71
|
+
logs: logs.slice(0, MAX_LOGS).map(serializeLog),
|
|
72
|
+
truncated: true,
|
|
73
|
+
nextBlock: logs[MAX_LOGS]?.blockNumber?.toString()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The same range eth_getLogs would scan — each end defaults to the latest block — in
|
|
79
|
+
* Hypersync's terms, where `toBlock` is exclusive.
|
|
80
|
+
*/
|
|
81
|
+
export function hypersyncRange(fromBlock: string | undefined, toBlock: string | undefined, latest: number) {
|
|
82
|
+
return {
|
|
83
|
+
fromBlock: fromBlock ? Number(BigInt(fromBlock)) : latest,
|
|
84
|
+
toBlock: (toBlock ? Number(BigInt(toBlock)) : latest) + 1
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** One topic, or a list of alternatives for an OR filter. */
|
|
89
|
+
export function toTopics(value: unknown): Hex | Hex[] {
|
|
90
|
+
return Array.isArray(value) ? value.map(toTopic) : toTopic(value)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function getLogsWithHypersync(
|
|
94
|
+
chainName: ChainName,
|
|
95
|
+
abiItem: AbiEvent,
|
|
96
|
+
hypersyncApiKey?: string,
|
|
97
|
+
address?: string,
|
|
98
|
+
fromBlock?: string,
|
|
99
|
+
toBlock?: string,
|
|
100
|
+
eventArgs?: Record<string, unknown>
|
|
101
|
+
): Promise<DecodedLog[]> {
|
|
102
|
+
const hypersyncUrl = getClientManager().getHypersyncUrl(chainName)
|
|
103
|
+
if (!hypersyncUrl) {
|
|
104
|
+
throw new Error(`Hypersync not supported for chain: ${chainName}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const client = HypersyncClient.new({
|
|
108
|
+
url: hypersyncUrl,
|
|
109
|
+
bearerToken: hypersyncApiKey
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const topic0 = keccak256(toBytes(toEventSignature(abiItem)))
|
|
113
|
+
|
|
114
|
+
// Build topics array for filtering
|
|
115
|
+
const topics: (Hex | Hex[] | null)[] = [topic0]
|
|
116
|
+
|
|
117
|
+
// Add indexed parameter filtering if eventArgs provided
|
|
118
|
+
if (eventArgs) {
|
|
119
|
+
const indexedInputs = abiItem.inputs.filter((input) => input.indexed)
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < indexedInputs.length && i < 3; i++) {
|
|
122
|
+
const input = indexedInputs[i]
|
|
123
|
+
const argValue = input?.name ? eventArgs[input.name] : undefined
|
|
124
|
+
|
|
125
|
+
topics.push(argValue === undefined ? null : toTopics(argValue))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const latest = fromBlock && toBlock ? 0 : await client.getHeight()
|
|
130
|
+
const range = hypersyncRange(fromBlock, toBlock, latest)
|
|
131
|
+
|
|
132
|
+
const query = {
|
|
133
|
+
fieldSelection: {
|
|
134
|
+
log: [
|
|
135
|
+
LogField.Address,
|
|
136
|
+
LogField.Topic0,
|
|
137
|
+
LogField.Topic1,
|
|
138
|
+
LogField.Topic2,
|
|
139
|
+
LogField.Topic3,
|
|
140
|
+
LogField.Data,
|
|
141
|
+
LogField.BlockNumber,
|
|
142
|
+
LogField.BlockHash,
|
|
143
|
+
LogField.TransactionHash,
|
|
144
|
+
LogField.TransactionIndex,
|
|
145
|
+
LogField.LogIndex
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
...range,
|
|
149
|
+
logs: [
|
|
150
|
+
{
|
|
151
|
+
address: address ? [address] : undefined,
|
|
152
|
+
// An unfiltered position is an empty list, not a dropped entry: removing it would
|
|
153
|
+
// slide every later filter up a slot and match on the wrong parameter.
|
|
154
|
+
topics: topics.map((t) => (t === null ? [] : Array.isArray(t) ? t : [t]))
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// One response covers only part of a long range; `nextBlock` says where it stopped.
|
|
160
|
+
const logs: Log[] = []
|
|
161
|
+
for (let from = range.fromBlock; from < range.toBlock && logs.length <= MAX_LOGS; ) {
|
|
162
|
+
const res = await client.get({ ...query, fromBlock: from })
|
|
163
|
+
logs.push(...res.data.logs)
|
|
164
|
+
if (res.nextBlock <= from) break
|
|
165
|
+
from = res.nextBlock
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return logs
|
|
169
|
+
.map((log: Log): DecodedLog | null => {
|
|
170
|
+
const topics = log.topics.filter((topic): topic is string => Boolean(topic)) as [Hex, ...Hex[]]
|
|
171
|
+
const data = (log.data ?? '0x') as Hex
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const { args } = decodeEventLog({ abi: [abiItem], data, topics })
|
|
175
|
+
return {
|
|
176
|
+
address: log.address as Address,
|
|
177
|
+
blockHash: log.blockHash as Hex,
|
|
178
|
+
blockNumber: log.blockNumber !== undefined ? BigInt(log.blockNumber) : null,
|
|
179
|
+
data,
|
|
180
|
+
logIndex: log.logIndex,
|
|
181
|
+
topics,
|
|
182
|
+
transactionHash: log.transactionHash as Hex,
|
|
183
|
+
transactionIndex: log.transactionIndex,
|
|
184
|
+
eventName: abiItem.name,
|
|
185
|
+
args: args ?? {}
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.warn('Failed to decode log:', error)
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
.filter((log): log is DecodedLog => log !== null)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export default {
|
|
196
|
+
get_logs: createTool(
|
|
197
|
+
'Query Contract Events',
|
|
198
|
+
`Search and decode contract events with filtering. Uses viem with hypersync fallback. Specify address and block ranges for best performance. Returns at most ${MAX_LOGS} logs; past that, truncated is true and nextBlock says where to resume.`,
|
|
199
|
+
z.object({
|
|
200
|
+
chain: z.enum(SUPPORTED_CHAINS).describe('The blockchain network to use'),
|
|
201
|
+
eventAbi: z
|
|
202
|
+
.string()
|
|
203
|
+
.describe('Event ABI definition (e.g., "event Transfer(address indexed from, address indexed to, uint256 value)")'),
|
|
204
|
+
address: z.string().optional().describe('Contract address (RECOMMENDED for performance)'),
|
|
205
|
+
fromBlock: z.string().optional().describe('Start block (defaults to the latest block)'),
|
|
206
|
+
toBlock: z.string().optional().describe('End block, inclusive (defaults to the latest block)'),
|
|
207
|
+
eventArgs: z
|
|
208
|
+
.record(z.union([z.string(), z.number(), z.boolean(), z.array(z.union([z.string(), z.number(), z.boolean()]))]))
|
|
209
|
+
.optional()
|
|
210
|
+
.describe(
|
|
211
|
+
'Filter by indexed parameters: {"from": "0x123...", "to": "0x456..."}. A list matches any of its values: {"to": ["0x1...", "0x2..."]}'
|
|
212
|
+
)
|
|
213
|
+
}),
|
|
214
|
+
async (args) => {
|
|
215
|
+
const clientManager = getClientManager()
|
|
216
|
+
const chain = args.chain as ChainName
|
|
217
|
+
const abiItem = parseAbiItem(args.eventAbi) as AbiEvent
|
|
218
|
+
|
|
219
|
+
if (args.address && !isAddress(args.address)) {
|
|
220
|
+
throw new Error(`Invalid contract address: ${args.address}`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const eventArgs =
|
|
224
|
+
args.eventArgs && Object.keys(args.eventArgs).length > 0
|
|
225
|
+
? convertEventArgsToTypes(args.eventArgs, abiItem.inputs)
|
|
226
|
+
: undefined
|
|
227
|
+
const fromBlockNum = args.fromBlock ? BigInt(args.fromBlock) : undefined
|
|
228
|
+
const toBlockNum = args.toBlock ? BigInt(args.toBlock) : undefined
|
|
229
|
+
|
|
230
|
+
const respond = (logs: DecodedLog[], dataSource: 'viem' | 'hypersync') => {
|
|
231
|
+
const capped = capLogs(logs)
|
|
232
|
+
return formatResponse({
|
|
233
|
+
...capped,
|
|
234
|
+
eventSignature: toEventSignature(abiItem),
|
|
235
|
+
chain: args.chain,
|
|
236
|
+
fromBlock: fromBlockNum?.toString() || 'latest',
|
|
237
|
+
toBlock: toBlockNum?.toString() || 'latest',
|
|
238
|
+
count: capped.logs.length,
|
|
239
|
+
dataSource,
|
|
240
|
+
filters: {
|
|
241
|
+
address: args.address || null,
|
|
242
|
+
eventArgs: args.eventArgs || null
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const logs = await clientManager.getClient(chain).getLogs({
|
|
249
|
+
address: args.address as Address | undefined,
|
|
250
|
+
event: abiItem,
|
|
251
|
+
args: eventArgs,
|
|
252
|
+
fromBlock: fromBlockNum,
|
|
253
|
+
toBlock: toBlockNum
|
|
254
|
+
})
|
|
255
|
+
return respond(logs as DecodedLog[], 'viem')
|
|
256
|
+
} catch (viemError) {
|
|
257
|
+
if (!clientManager.getHypersyncUrl(chain)) {
|
|
258
|
+
throw new Error(`Failed to get logs with viem (hypersync not available for ${args.chain}): ${rpcReason(viemError)}`)
|
|
259
|
+
}
|
|
260
|
+
console.warn(`Viem getLogs failed for ${args.chain}, trying hypersync fallback: ${rpcReason(viemError)}`)
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const logs = await getLogsWithHypersync(
|
|
264
|
+
chain,
|
|
265
|
+
abiItem,
|
|
266
|
+
clientManager.getConfig().hypersyncApiKey,
|
|
267
|
+
args.address,
|
|
268
|
+
args.fromBlock,
|
|
269
|
+
args.toBlock,
|
|
270
|
+
eventArgs
|
|
271
|
+
)
|
|
272
|
+
return respond(logs, 'hypersync')
|
|
273
|
+
} catch (hypersyncError) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Both viem and hypersync failed. Viem error: ${rpcReason(viemError)}. Hypersync error: ${rpcReason(hypersyncError)}`
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AbiEvent,
|
|
3
3
|
type AbiFunction,
|
|
4
|
-
type AbiParameter,
|
|
5
4
|
encodeFunctionData,
|
|
6
5
|
keccak256,
|
|
7
6
|
parseAbiItem,
|
|
@@ -10,8 +9,8 @@ import {
|
|
|
10
9
|
toFunctionSignature
|
|
11
10
|
} from 'viem'
|
|
12
11
|
import { z } from 'zod'
|
|
13
|
-
import type { AbiError } from '
|
|
14
|
-
import { createTool, formatResponse } from '
|
|
12
|
+
import type { AbiError } from '../../types.js'
|
|
13
|
+
import { convertArgumentsToTypes, createTool, formatResponse } from '../../utils.js'
|
|
15
14
|
|
|
16
15
|
const FunctionAbiSchema = z.object({
|
|
17
16
|
functionAbi: z.string().describe('Function ABI definition (e.g., "function transfer(address to, uint256 amount)")')
|
|
@@ -92,7 +91,8 @@ export default {
|
|
|
92
91
|
async (args) => {
|
|
93
92
|
const results = args.items.map((item) => {
|
|
94
93
|
const abiItem = parseAbiItem(item.errorAbi) as AbiError
|
|
95
|
-
|
|
94
|
+
// Formatted as a function, which an error's signature is; viem keeps an "error " prefix otherwise.
|
|
95
|
+
const signature = toFunctionSignature({ ...abiItem, type: 'function', outputs: [], stateMutability: 'nonpayable' })
|
|
96
96
|
const hash = keccak256(toBytes(signature))
|
|
97
97
|
const selector = hash.slice(0, 10)
|
|
98
98
|
|
|
@@ -125,7 +125,7 @@ export default {
|
|
|
125
125
|
const data = encodeFunctionData({
|
|
126
126
|
abi: [abiItem],
|
|
127
127
|
functionName: abiItem.name,
|
|
128
|
-
args: args.args
|
|
128
|
+
args: convertArgumentsToTypes(args.args, abiItem.inputs)
|
|
129
129
|
})
|
|
130
130
|
|
|
131
131
|
return formatResponse({
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { collectSigningRequest } from '../../signing-requests.js'
|
|
3
|
+
import { createTool, formatResponse } from '../../utils.js'
|
|
4
|
+
import { failure } from '../sign/errors.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Collecting a signature that was not given within the tool call that asked for it.
|
|
8
|
+
*
|
|
9
|
+
* A read rather than a signing tool on purpose: the signature was requested — and charged
|
|
10
|
+
* for — once already, and a host metering signing should not bill an agent again for asking
|
|
11
|
+
* whether the human has got round to it yet.
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
14
|
+
check_signing_request: createTool(
|
|
15
|
+
'Check Signing Request',
|
|
16
|
+
'Collect the result of a signing request that was still waiting for approval. Waits a while for the user to approve, then reports back. Call this instead of asking for the signature again — repeating the original tool would put a second request in front of the wallet.',
|
|
17
|
+
z.object({
|
|
18
|
+
requestId: z.string().describe('The requestId returned when the signing tool said it was awaiting approval')
|
|
19
|
+
}),
|
|
20
|
+
async (args, identity) => {
|
|
21
|
+
try {
|
|
22
|
+
const request = await collectSigningRequest(args.requestId, identity)
|
|
23
|
+
|
|
24
|
+
const waitedFor = `${Math.round((Date.now() - request.startedAt) / 1000)}s`
|
|
25
|
+
|
|
26
|
+
if (request.state === 'pending') {
|
|
27
|
+
// Approved already and waiting on the chain is a different thing to report: there
|
|
28
|
+
// is nothing left for the user to do, and nothing to chase them about.
|
|
29
|
+
if (request.stage === 'mining') {
|
|
30
|
+
return formatResponse({
|
|
31
|
+
success: true,
|
|
32
|
+
status: 'mining',
|
|
33
|
+
requestId: request.id,
|
|
34
|
+
what: request.summary,
|
|
35
|
+
transactionHash: request.txHash,
|
|
36
|
+
waitingFor: waitedFor,
|
|
37
|
+
nextStep: 'Approved and broadcast. Call this again with the same requestId to get the mined result.',
|
|
38
|
+
message: 'Signed and sent. Waiting for it to be mined.'
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return formatResponse({
|
|
43
|
+
success: true,
|
|
44
|
+
status: 'awaiting_approval',
|
|
45
|
+
requestId: request.id,
|
|
46
|
+
what: request.summary,
|
|
47
|
+
waitingFor: waitedFor,
|
|
48
|
+
nextStep:
|
|
49
|
+
'Still not approved. Ask the user to check their wallet and approve or reject it, then call this again with the same requestId.',
|
|
50
|
+
message: 'Nothing has been signed yet.'
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (request.state === 'failed') {
|
|
55
|
+
// Only an explicit rejection proves nothing was signed. A timeout or a dropped
|
|
56
|
+
// connection can land after the wallet signed and broadcast.
|
|
57
|
+
const rejected = request.stage === 'approval' && /rejected|denied/i.test(request.error ?? '')
|
|
58
|
+
return formatResponse({
|
|
59
|
+
success: false,
|
|
60
|
+
status: rejected ? 'rejected' : 'outcome_unknown',
|
|
61
|
+
what: request.summary,
|
|
62
|
+
error: request.error,
|
|
63
|
+
...(request.txHash && { transactionHash: request.txHash }),
|
|
64
|
+
message: rejected
|
|
65
|
+
? 'The wallet rejected it. Nothing was signed.'
|
|
66
|
+
: 'It did not complete here, but it may still have been signed or broadcast. Do not retry until the wallet or a block explorer shows it did not go through.'
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A transaction carries how it ended on the chain; a message signature is just the
|
|
71
|
+
// signature, with nothing to mine.
|
|
72
|
+
const settled = request.result as { receipt?: { status: string; blockNumber?: string; gasUsed?: string } } | undefined
|
|
73
|
+
const reverted = settled?.receipt?.status === 'reverted'
|
|
74
|
+
|
|
75
|
+
return formatResponse({
|
|
76
|
+
success: !reverted,
|
|
77
|
+
status: settled?.receipt?.status ?? 'signed',
|
|
78
|
+
what: request.summary,
|
|
79
|
+
signedWith: request.signer,
|
|
80
|
+
result: request.result,
|
|
81
|
+
message: reverted
|
|
82
|
+
? 'Mined, but it reverted. The gas was spent and nothing else changed.'
|
|
83
|
+
: settled?.receipt?.status === 'broadcast'
|
|
84
|
+
? 'Approved and broadcast, but not mined yet.'
|
|
85
|
+
: 'Approved and done.'
|
|
86
|
+
})
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return failure(error, 'Could not check that signing request')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { log } from '../../log.js'
|
|
2
|
+
import { formatResponse } from '../../utils.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* WalletConnect rejects with a plain `{ code, message }` rather than an Error, which
|
|
6
|
+
* String() renders as "[object Object]" — the reason a wallet gave for refusing is the
|
|
7
|
+
* whole point of the message, so dig it out.
|
|
8
|
+
*/
|
|
9
|
+
export function describeError(error: unknown): string {
|
|
10
|
+
if (error instanceof Error) return error.message
|
|
11
|
+
if (error && typeof error === 'object') {
|
|
12
|
+
const { message, code, reason } = error as { message?: string; code?: number; reason?: string }
|
|
13
|
+
const text = message ?? reason
|
|
14
|
+
if (text) return code === undefined ? text : `${text} (code ${code})`
|
|
15
|
+
return JSON.stringify(error)
|
|
16
|
+
}
|
|
17
|
+
return String(error)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A refused or failed signing attempt, reported to the caller and to the log alike. */
|
|
21
|
+
export function failure(error: unknown, message: string) {
|
|
22
|
+
log('warning', 'Transaction', `${message} — ${describeError(error)}`)
|
|
23
|
+
return formatResponse({
|
|
24
|
+
success: false,
|
|
25
|
+
error: describeError(error),
|
|
26
|
+
message
|
|
27
|
+
})
|
|
28
|
+
}
|