Skip to content

Commit 795a59e

Browse files
authored
Feature: Adds blockRange option to getContractEvents (#2941)
1 parent 82e8911 commit 795a59e

File tree

4 files changed

+184
-6
lines changed

4 files changed

+184
-6
lines changed

.changeset/dirty-lemons-clap.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Can now optionally specify `blockRange` on `getContractEvents` in combination with `toBlock` and `fromBlock`.
6+
7+
## Usage
8+
9+
Specify a `blockRange`, defaulting `toBlock` to the current block number.
10+
11+
```ts
12+
await getContractEvents({
13+
contract: myContract,
14+
blockRange: 123456n,
15+
events: [preparedEvent, preparedEvent2],
16+
});
17+
```
18+
19+
Specify a block range with `toBlock`.
20+
21+
```ts
22+
await getContractEvents({
23+
contract: myContract,
24+
toBlock: endBlock,
25+
blockRange: 123456n,
26+
events: [preparedEvent, preparedEvent2],
27+
});
28+
```
29+
30+
Specify a block range starting from `fromBlock` (great for pagination).
31+
32+
```ts
33+
await getContractEvents({
34+
contract: myContract,
35+
fromBlock: lastBlockFetched,
36+
blockRange: 123456n,
37+
events: [preparedEvent, preparedEvent2],
38+
});
39+
```

packages/thirdweb/src/event/actions/get-events.test.ts

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,74 @@ import { transferEvent } from "../../extensions/erc721/__generated__/IERC721A/ev
77
import { prepareEvent } from "../prepare-event.js";
88
import { getContractEvents } from "./get-events.js";
99

10+
const LATEST_SIMULATED_BLOCK = 19139495n;
11+
1012
// skip this test suite if there is no secret key available to test with
1113
// TODO: remove reliance on secret key during unit tests entirely
1214
describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
1315
it("should get all events", async () => {
1416
const events = await getContractEvents({
1517
contract: USDT_CONTRACT,
16-
fromBlock: 19139495n - 10n,
18+
fromBlock: LATEST_SIMULATED_BLOCK - 10n,
1719
});
1820
expect(events.length).toBe(261);
1921
});
2022

23+
it("should get events for blockHash", async () => {
24+
const BLOCK_HASH =
25+
"0xb0ad5ee7b4912b50e5a2d7993796944653a4c0632c57740fe4a7a1c61e426324";
26+
const events = await getContractEvents({
27+
contract: USDT_CONTRACT,
28+
blockHash: BLOCK_HASH,
29+
});
30+
for (const e of events) {
31+
expect(e.blockHash).toBe(BLOCK_HASH);
32+
}
33+
expect(events.length).toBe(14);
34+
});
35+
36+
it("should get events for blockRange", async () => {
37+
const events = await getContractEvents({
38+
contract: USDT_CONTRACT,
39+
blockRange: 10n,
40+
});
41+
expect(events.length).toBe(261);
42+
43+
const explicitEvents = await getContractEvents({
44+
contract: USDT_CONTRACT,
45+
fromBlock: LATEST_SIMULATED_BLOCK - 10n,
46+
toBlock: LATEST_SIMULATED_BLOCK,
47+
});
48+
expect(explicitEvents.length).toEqual(events.length);
49+
});
50+
51+
it("should get events for blockRange using fromBlock and toBlock", async () => {
52+
const eventsFromBlock = await getContractEvents({
53+
contract: USDT_CONTRACT,
54+
fromBlock: LATEST_SIMULATED_BLOCK - 50n,
55+
blockRange: 20n,
56+
});
57+
expect(eventsFromBlock.length).toBe(426);
58+
59+
const eventsToBlock = await getContractEvents({
60+
contract: USDT_CONTRACT,
61+
toBlock: LATEST_SIMULATED_BLOCK - 30n,
62+
blockRange: 20n,
63+
});
64+
expect(eventsToBlock.length).toBe(426);
65+
66+
const explicitEvents = await getContractEvents({
67+
contract: USDT_CONTRACT,
68+
fromBlock: LATEST_SIMULATED_BLOCK - 50n,
69+
toBlock: LATEST_SIMULATED_BLOCK - 30n,
70+
});
71+
expect(explicitEvents.length).toEqual(eventsFromBlock.length);
72+
});
73+
2174
it("should get individual events with signature", async () => {
2275
const events = await getContractEvents({
2376
contract: USDT_CONTRACT,
24-
fromBlock: 19139495n - 100n,
77+
fromBlock: LATEST_SIMULATED_BLOCK - 100n,
2578
events: [
2679
prepareEvent({
2780
signature: "event Burn(address indexed burner, uint256 amount)",
@@ -34,7 +87,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
3487
it("should get specified events", async () => {
3588
const events = await getContractEvents({
3689
contract: USDT_CONTRACT,
37-
fromBlock: 19139495n - 10n,
90+
fromBlock: LATEST_SIMULATED_BLOCK - 10n,
3891
events: [
3992
prepareEvent({
4093
signature: "event Burn(address indexed burner, uint256 amount)",
@@ -74,7 +127,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
74127
it("should get individual events with extension", async () => {
75128
const events = await getContractEvents({
76129
contract: DOODLES_CONTRACT,
77-
fromBlock: 19139495n - 1000n,
130+
fromBlock: LATEST_SIMULATED_BLOCK - 1000n,
78131
events: [transferEvent()],
79132
});
80133
expect(events.length).toBe(38);

packages/thirdweb/src/event/actions/get-events.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from "abitype";
1414
import { resolveContractAbi } from "../../contract/actions/resolve-abi.js";
1515
import type { ThirdwebContract } from "../../contract/contract.js";
16+
import { eth_blockNumber } from "../../rpc/actions/eth_blockNumber.js";
1617
import {
1718
type GetLogsBlockParams,
1819
type GetLogsParams,
@@ -61,6 +62,37 @@ export type GetContractEventsResult<
6162
* events: [preparedEvent, preparedEvent2],
6263
* });
6364
* ```
65+
* @example
66+
* Optionally specify a blockRange as the number of blocks to retrieve. toBlock will default to the current block number.
67+
* ```ts
68+
* import { getContractEvents } from "thirdweb";
69+
* const events = await getContractEvents({
70+
* contract: myContract,
71+
* blockRange: 123456n,
72+
* events: [preparedEvent, preparedEvent2],
73+
* });
74+
* ```
75+
* @example
76+
* Use fromBlock with blockRange for pagination.
77+
* ```ts
78+
* import { getContractEvents } from "thirdweb";
79+
* const events = await getContractEvents({
80+
* contract: myContract,
81+
* fromBlock: lastBlockFetched,
82+
* blockRange: 123456n,
83+
* events: [preparedEvent, preparedEvent2],
84+
* });
85+
* ```
86+
* @example
87+
* Retrieve events for a specific block hash.
88+
* ```ts
89+
* import { getContractEvents } from "thirdweb";
90+
* const events = await getContractEvents({
91+
* contract: myContract,
92+
* blockHash: "0x...",
93+
* events: [preparedEvent, preparedEvent2],
94+
* });
95+
* ```
6496
* @contract
6597
*/
6698
export async function getContractEvents<
@@ -72,7 +104,42 @@ export async function getContractEvents<
72104
>(
73105
options: GetContractEventsOptions<abi, abiEvents, TStrict>,
74106
): Promise<GetContractEventsResult<abiEvents, TStrict>> {
75-
const { contract, events, ...restParams } = options;
107+
const { contract, events, blockRange, ...restParams } = options;
108+
109+
const rpcRequest = getRpcClient(contract);
110+
111+
if (
112+
restParams.blockHash &&
113+
(blockRange || restParams.fromBlock || restParams.toBlock)
114+
) {
115+
throw new Error("Cannot specify blockHash and range simultaneously,");
116+
}
117+
118+
// Compute toBlock and fromBlock if blockRange was passed
119+
if (blockRange) {
120+
const { fromBlock, toBlock } = restParams;
121+
122+
// Make sure the inputs were properly defined
123+
if (
124+
fromBlock &&
125+
toBlock &&
126+
BigInt(toBlock) - BigInt(fromBlock) !== BigInt(blockRange)
127+
) {
128+
throw new Error(
129+
"Incompatible blockRange with specified fromBlock and toBlock. Please only define fromBlock or toBlock when specifying blockRange.",
130+
);
131+
}
132+
133+
if (fromBlock) {
134+
restParams.toBlock = BigInt(fromBlock) + BigInt(blockRange);
135+
} else if (toBlock) {
136+
restParams.fromBlock = BigInt(toBlock) - BigInt(blockRange);
137+
} else {
138+
// If no from or to block specified, use the latest block as the to block
139+
restParams.toBlock = await eth_blockNumber(rpcRequest);
140+
restParams.fromBlock = BigInt(restParams.toBlock) - BigInt(blockRange);
141+
}
142+
}
76143

77144
let resolvedEvents = events ?? [];
78145

@@ -104,7 +171,6 @@ export async function getContractEvents<
104171
: // otherwise we want "all" events (aka not pass any topics at all)
105172
[{ ...restParams, address: contract?.address }];
106173

107-
const rpcRequest = getRpcClient(contract);
108174
const logs = await Promise.all(
109175
logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)),
110176
);

packages/thirdweb/src/rpc/actions/eth_getLogs.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,31 @@ export type GetLogsBlockParams =
1616
fromBlock?: BlockNumber | BlockTag;
1717
toBlock?: BlockNumber | BlockTag;
1818
blockHash?: never;
19+
blockRange?: never;
1920
}
2021
| {
2122
fromBlock?: never;
2223
toBlock?: never;
2324
blockHash?: Hash;
25+
blockRange?: never;
26+
}
27+
| {
28+
fromBlock?: BlockNumber | "latest";
29+
toBlock?: never;
30+
blockRange: BlockNumber;
31+
blockHash?: never;
32+
}
33+
| {
34+
fromBlock?: never;
35+
toBlock?: BlockNumber | "latest";
36+
blockRange: BlockNumber;
37+
blockHash?: never;
38+
}
39+
| {
40+
fromBlock?: never;
41+
toBlock?: never;
42+
blockRange: BlockNumber;
43+
blockHash?: never;
2444
};
2545

2646
export type GetLogsParams = {

0 commit comments

Comments
 (0)