@apibara/evm-rpc 2.1.0-beta.48 → 2.1.0-beta.50
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/dist/index.cjs +134 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +135 -67
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -5
- package/src/log-fetcher.ts +173 -72
- package/src/stream-config.ts +10 -1
package/src/log-fetcher.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { LogFilter } from "@apibara/evm";
|
|
2
2
|
import type { Bytes } from "@apibara/protocol";
|
|
3
3
|
import type { RpcLog } from "viem";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
hexToNumber,
|
|
6
|
+
isAddressEqual,
|
|
7
|
+
isHex,
|
|
8
|
+
numberToHex,
|
|
9
|
+
pad,
|
|
10
|
+
trim,
|
|
11
|
+
} from "viem";
|
|
5
12
|
import type { Log } from "./block";
|
|
6
13
|
import type { Filter } from "./filter";
|
|
7
14
|
import type { ViemRpcClient } from "./stream-config";
|
|
@@ -11,56 +18,53 @@ export async function fetchLogsByBlockHash({
|
|
|
11
18
|
client,
|
|
12
19
|
blockHash,
|
|
13
20
|
filter,
|
|
21
|
+
mergeGetLogs,
|
|
14
22
|
}: {
|
|
15
23
|
client: ViemRpcClient;
|
|
16
24
|
blockHash: Bytes;
|
|
17
25
|
filter: Filter;
|
|
26
|
+
mergeGetLogs: boolean;
|
|
18
27
|
}): Promise<{ logs: Log[] }> {
|
|
19
28
|
if (!filter.logs || filter.logs.length === 0) {
|
|
20
29
|
return { logs: [] };
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
const responses =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
],
|
|
32
|
+
const responses = mergeGetLogs
|
|
33
|
+
? await mergedGetLogsCalls({
|
|
34
|
+
client,
|
|
35
|
+
filter,
|
|
36
|
+
blockHash,
|
|
37
|
+
})
|
|
38
|
+
: await standardGetLogsCalls({
|
|
39
|
+
client,
|
|
40
|
+
filter,
|
|
41
|
+
blockHash,
|
|
34
42
|
});
|
|
35
|
-
return { logs, logFilter };
|
|
36
|
-
}),
|
|
37
|
-
);
|
|
38
43
|
|
|
39
|
-
// Multiple calls may have produced the same log.
|
|
40
|
-
// We track all the logs (by their logIndex, which is unique within a block).
|
|
41
|
-
// logIndex -> position
|
|
42
44
|
const allLogs: Log[] = [];
|
|
43
45
|
const seenLogsByIndex: Record<number, number> = {};
|
|
44
46
|
|
|
45
|
-
for (const {
|
|
47
|
+
for (const { logFilters, logs } of responses) {
|
|
46
48
|
for (const log of logs) {
|
|
47
49
|
if (log.blockNumber === null) {
|
|
48
50
|
throw new Error("Log block number is null");
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
const
|
|
53
|
+
for (const logFilter of logFilters) {
|
|
54
|
+
const refinedLog = refineLog(log, logFilter);
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
if (refinedLog) {
|
|
57
|
+
const existingPosition = seenLogsByIndex[refinedLog.logIndex];
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
if (existingPosition !== undefined) {
|
|
60
|
+
const existingLog = allLogs[existingPosition];
|
|
61
|
+
(existingLog.filterIds as number[]).push(logFilter.id ?? 0);
|
|
62
|
+
} else {
|
|
63
|
+
(refinedLog.filterIds as number[]).push(logFilter.id ?? 0);
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
allLogs.push(refinedLog);
|
|
66
|
+
seenLogsByIndex[refinedLog.logIndex] = allLogs.length - 1;
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -74,11 +78,13 @@ export async function fetchLogsForRange({
|
|
|
74
78
|
fromBlock,
|
|
75
79
|
toBlock,
|
|
76
80
|
filter,
|
|
81
|
+
mergeGetLogs,
|
|
77
82
|
}: {
|
|
78
83
|
client: ViemRpcClient;
|
|
79
84
|
fromBlock: bigint;
|
|
80
85
|
toBlock: bigint;
|
|
81
86
|
filter: Filter;
|
|
87
|
+
mergeGetLogs: boolean;
|
|
82
88
|
}): Promise<{ logs: Record<number, Log[]>; blockNumbers: bigint[] }> {
|
|
83
89
|
const logsByBlock: Record<number, Log[]> = {};
|
|
84
90
|
|
|
@@ -86,68 +92,61 @@ export async function fetchLogsForRange({
|
|
|
86
92
|
return { logs: logsByBlock, blockNumbers: [] };
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
const responses =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
: undefined,
|
|
102
|
-
},
|
|
103
|
-
],
|
|
95
|
+
const responses = mergeGetLogs
|
|
96
|
+
? await mergedGetLogsCalls({
|
|
97
|
+
client,
|
|
98
|
+
filter,
|
|
99
|
+
fromBlock: numberToHex(fromBlock),
|
|
100
|
+
toBlock: numberToHex(toBlock),
|
|
101
|
+
})
|
|
102
|
+
: await standardGetLogsCalls({
|
|
103
|
+
client,
|
|
104
|
+
filter,
|
|
105
|
+
fromBlock: numberToHex(fromBlock),
|
|
106
|
+
toBlock: numberToHex(toBlock),
|
|
104
107
|
});
|
|
105
|
-
return { logs, logFilter };
|
|
106
|
-
}),
|
|
107
|
-
);
|
|
108
108
|
|
|
109
109
|
const blockNumbers = new Set<bigint>();
|
|
110
110
|
|
|
111
|
-
// Multiple calls may have produced the same log.
|
|
112
|
-
// We track all the logs (by their logIndex, which is unique within a block).
|
|
113
|
-
// blockNumber -> logIndex -> position
|
|
114
111
|
const seenLogsByBlockNumberAndIndex: Record<
|
|
115
112
|
number,
|
|
116
113
|
Record<number, number>
|
|
117
114
|
> = {};
|
|
118
115
|
|
|
119
|
-
for (const {
|
|
116
|
+
for (const { logFilters, logs } of responses) {
|
|
120
117
|
for (const log of logs) {
|
|
121
118
|
if (log.blockNumber === null) {
|
|
122
119
|
throw new Error("Log block number is null");
|
|
123
120
|
}
|
|
124
121
|
|
|
125
|
-
const
|
|
122
|
+
for (const logFilter of logFilters) {
|
|
123
|
+
const refinedLog = refineLog(log, logFilter);
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
if (refinedLog) {
|
|
126
|
+
const blockNumber = hexToNumber(log.blockNumber);
|
|
127
|
+
blockNumbers.add(BigInt(blockNumber));
|
|
130
128
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
if (!logsByBlock[blockNumber]) {
|
|
130
|
+
logsByBlock[blockNumber] = [];
|
|
131
|
+
}
|
|
134
132
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
if (!seenLogsByBlockNumberAndIndex[blockNumber]) {
|
|
134
|
+
seenLogsByBlockNumberAndIndex[blockNumber] = {};
|
|
135
|
+
}
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const existingPosition =
|
|
138
|
+
seenLogsByBlockNumberAndIndex[blockNumber][refinedLog.logIndex];
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
if (existingPosition !== undefined) {
|
|
141
|
+
const existingLog = logsByBlock[blockNumber][existingPosition];
|
|
142
|
+
(existingLog.filterIds as number[]).push(logFilter.id ?? 0);
|
|
143
|
+
} else {
|
|
144
|
+
(refinedLog.filterIds as number[]).push(logFilter.id ?? 0);
|
|
147
145
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
logsByBlock[blockNumber].push(refinedLog);
|
|
147
|
+
seenLogsByBlockNumberAndIndex[blockNumber][refinedLog.logIndex] =
|
|
148
|
+
logsByBlock[blockNumber].length - 1;
|
|
149
|
+
}
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
}
|
|
@@ -165,8 +164,11 @@ function refineLog(log: RpcLog, filter: LogFilter): Log | null {
|
|
|
165
164
|
return null;
|
|
166
165
|
}
|
|
167
166
|
|
|
167
|
+
if (filter.address && !isAddressEqual(log.address, filter.address)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
168
171
|
const filterTopics = filter.topics ?? [];
|
|
169
|
-
// Strict mode
|
|
170
172
|
if (filter.strict && log.topics.length !== filterTopics.length) {
|
|
171
173
|
return null;
|
|
172
174
|
}
|
|
@@ -201,3 +203,102 @@ function refineLog(log: RpcLog, filter: LogFilter): Log | null {
|
|
|
201
203
|
|
|
202
204
|
return viemRpcLogToDna(log);
|
|
203
205
|
}
|
|
206
|
+
|
|
207
|
+
async function mergedGetLogsCalls({
|
|
208
|
+
client,
|
|
209
|
+
filter,
|
|
210
|
+
blockHash,
|
|
211
|
+
fromBlock,
|
|
212
|
+
toBlock,
|
|
213
|
+
}: {
|
|
214
|
+
client: ViemRpcClient;
|
|
215
|
+
filter: Filter;
|
|
216
|
+
blockHash?: Bytes;
|
|
217
|
+
fromBlock?: `0x${string}`;
|
|
218
|
+
toBlock?: `0x${string}`;
|
|
219
|
+
}) {
|
|
220
|
+
const blockParams = blockHash ? { blockHash } : { fromBlock, toBlock };
|
|
221
|
+
|
|
222
|
+
const filtersWithAddress = filter.logs.filter((f) => f.address !== undefined);
|
|
223
|
+
const filtersWithoutAddress = filter.logs.filter(
|
|
224
|
+
(f) => f.address === undefined,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const promises: Promise<{
|
|
228
|
+
logs: RpcLog[];
|
|
229
|
+
logFilters: typeof filter.logs;
|
|
230
|
+
}>[] = [];
|
|
231
|
+
|
|
232
|
+
if (filtersWithAddress.length > 0) {
|
|
233
|
+
const addresses = filtersWithAddress.map((f) => f.address!);
|
|
234
|
+
|
|
235
|
+
promises.push(
|
|
236
|
+
client
|
|
237
|
+
.request({
|
|
238
|
+
method: "eth_getLogs",
|
|
239
|
+
params: [
|
|
240
|
+
{
|
|
241
|
+
address: addresses,
|
|
242
|
+
...blockParams,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
})
|
|
246
|
+
.then((logs: RpcLog[]) => ({ logs, logFilters: filtersWithAddress })),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (filtersWithoutAddress.length > 0) {
|
|
251
|
+
promises.push(
|
|
252
|
+
client
|
|
253
|
+
.request({
|
|
254
|
+
method: "eth_getLogs",
|
|
255
|
+
params: [
|
|
256
|
+
{
|
|
257
|
+
...blockParams,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
})
|
|
261
|
+
.then((logs: RpcLog[]) => ({
|
|
262
|
+
logs,
|
|
263
|
+
logFilters: filtersWithoutAddress,
|
|
264
|
+
})),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return await Promise.all(promises);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function standardGetLogsCalls({
|
|
272
|
+
client,
|
|
273
|
+
filter,
|
|
274
|
+
blockHash,
|
|
275
|
+
fromBlock,
|
|
276
|
+
toBlock,
|
|
277
|
+
}: {
|
|
278
|
+
client: ViemRpcClient;
|
|
279
|
+
filter: Filter;
|
|
280
|
+
blockHash?: Bytes;
|
|
281
|
+
fromBlock?: `0x${string}`;
|
|
282
|
+
toBlock?: `0x${string}`;
|
|
283
|
+
}) {
|
|
284
|
+
const blockParams = blockHash ? { blockHash } : { fromBlock, toBlock };
|
|
285
|
+
return await Promise.all(
|
|
286
|
+
filter.logs.map(async (logFilter) => {
|
|
287
|
+
const logs = await client.request({
|
|
288
|
+
method: "eth_getLogs",
|
|
289
|
+
params: [
|
|
290
|
+
{
|
|
291
|
+
address: logFilter.address,
|
|
292
|
+
topics:
|
|
293
|
+
logFilter.topics !== undefined
|
|
294
|
+
? [...logFilter.topics]
|
|
295
|
+
: undefined,
|
|
296
|
+
...blockParams,
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return { logs, logFilters: [logFilter] };
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
}
|
package/src/stream-config.ts
CHANGED
|
@@ -43,6 +43,10 @@ export type EvmRpcStreamOptions = {
|
|
|
43
43
|
headRefreshIntervalMs?: number;
|
|
44
44
|
/** How often to refresh the finalized block. */
|
|
45
45
|
finalizedRefreshIntervalMs?: number;
|
|
46
|
+
/** Force sending accepted headers even if no data matched. */
|
|
47
|
+
alwaysSendAcceptedHeaders?: boolean;
|
|
48
|
+
/** Merge multiple `eth_getLogs` calls into a single one, filtering data on the client. */
|
|
49
|
+
mergeGetLogsFilter?: "always" | "accepted" | false;
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
export class EvmRpcStream extends RpcStreamConfig<Filter, Block> {
|
|
@@ -237,7 +241,8 @@ export class EvmRpcStream extends RpcStreamConfig<Filter, Block> {
|
|
|
237
241
|
const shouldSendBlock =
|
|
238
242
|
filter.header === "always" ||
|
|
239
243
|
logs.length > 0 ||
|
|
240
|
-
(filter.header === "on_data_or_on_new_block" && isAtHead)
|
|
244
|
+
(filter.header === "on_data_or_on_new_block" && isAtHead) ||
|
|
245
|
+
this.options.alwaysSendAcceptedHeaders;
|
|
241
246
|
|
|
242
247
|
if (shouldSendBlock) {
|
|
243
248
|
block = {
|
|
@@ -273,6 +278,7 @@ export class EvmRpcStream extends RpcStreamConfig<Filter, Block> {
|
|
|
273
278
|
fromBlock,
|
|
274
279
|
toBlock,
|
|
275
280
|
filter,
|
|
281
|
+
mergeGetLogs: this.options.mergeGetLogsFilter === "always",
|
|
276
282
|
});
|
|
277
283
|
} catch (error) {
|
|
278
284
|
this.blockRangeOracle.handleError(error);
|
|
@@ -292,6 +298,9 @@ export class EvmRpcStream extends RpcStreamConfig<Filter, Block> {
|
|
|
292
298
|
client: this.client,
|
|
293
299
|
blockHash,
|
|
294
300
|
filter,
|
|
301
|
+
mergeGetLogs:
|
|
302
|
+
this.options.mergeGetLogsFilter === "always" ||
|
|
303
|
+
this.options.mergeGetLogsFilter === "accepted",
|
|
295
304
|
});
|
|
296
305
|
}
|
|
297
306
|
|