Skip to content

Commit a3e7300

Browse files
Get events from insight (#6683)
Co-authored-by: Joaquim Verges <[email protected]>
1 parent 4c1f384 commit a3e7300

File tree

12 files changed

+404
-41
lines changed

12 files changed

+404
-41
lines changed

.changeset/ready-tigers-pump.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Get indexed events from `getContractEvents`
6+
7+
You can now automatically query indexed events on supported chains when calling getContractEvents
8+
9+
```ts
10+
import { getContractEvents } from "thirdweb";
11+
12+
const events = await getContractEvents({
13+
contract: DOODLES_CONTRACT,
14+
events: [transferEvent()],
15+
});
16+
```
17+
18+
This method falls back to RPC eth_getLogs if the indexer is not available.
19+
20+
You can also use the dedicated indexer function via the Insight export
21+
22+
```ts
23+
import { Insight } from "thirdweb";
24+
25+
const events = await Insight.getContractEvents({
26+
client,
27+
chains: [sepolia],
28+
contractAddress: "0x1234567890123456789012345678901234567890",
29+
event: transferEvent(),
30+
decodeLogs: true,
31+
});
32+
```

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/cross-chain/page.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { fetchPublishedContractsFromDeploy } from "components/contract-component
22
import { notFound } from "next/navigation";
33
import {
44
type ContractOptions,
5-
eth_blockNumber,
65
eth_getTransactionByHash,
76
eth_getTransactionReceipt,
87
getContractEvents,
@@ -39,14 +38,17 @@ export default async function Page(props: {
3938
chain_id: string;
4039
}>;
4140
}) {
41+
const params = await props.params;
42+
const info = await getContractPageParamsInfo(params);
43+
4244
const ProxyDeployedEvent = prepareEvent({
4345
signature:
4446
"event ProxyDeployedV2(address indexed implementation, address indexed proxy, address indexed deployer, bytes32 inputSalt, bytes data, bytes extraData)",
47+
filters: {
48+
proxy: params.contractAddress.toLowerCase(),
49+
},
4550
});
4651

47-
const params = await props.params;
48-
const info = await getContractPageParamsInfo(params);
49-
5052
if (!info) {
5153
notFound();
5254
}
@@ -111,16 +113,9 @@ export default async function Page(props: {
111113
let creationBlockNumber: bigint | undefined;
112114

113115
if (twCloneFactoryContract) {
114-
const latestBlockNumber = await eth_blockNumber(
115-
getRpcClient({
116-
client: contract.client,
117-
chain: contract.chain,
118-
}),
119-
);
120116
const events = await getContractEvents({
121117
contract: twCloneFactoryContract,
122118
events: [ProxyDeployedEvent],
123-
blockRange: latestBlockNumber < 100000n ? latestBlockNumber : 100000n,
124119
});
125120
const event = events.find(
126121
(e) =>
@@ -208,7 +203,6 @@ export default async function Page(props: {
208203
const events = await getContractEvents({
209204
contract: contract,
210205
events: [moduleEvent],
211-
blockRange: 123456n,
212206
});
213207

214208
const filteredEvents = events.filter(

packages/thirdweb/.size-limit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{
99
"name": "thirdweb (cjs)",
1010
"path": "./dist/cjs/exports/thirdweb.js",
11-
"limit": "140 kB"
11+
"limit": "150 kB"
1212
},
1313
{
1414
"name": "thirdweb (minimal + tree-shaking)",

packages/thirdweb/src/chains/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ export type ChainMetadata = {
8888
stackType: string;
8989
};
9090

91+
/**
92+
* @chain
93+
*/
94+
export type ChainService = {
95+
service: string;
96+
enabled: boolean;
97+
};
98+
9199
/**
92100
* @chain
93101
*/

packages/thirdweb/src/chains/utils.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
Chain,
88
ChainMetadata,
99
ChainOptions,
10+
ChainService,
1011
LegacyChain,
1112
} from "./types.js";
1213

@@ -321,6 +322,66 @@ export function getChainMetadata(chain: Chain): Promise<ChainMetadata> {
321322
);
322323
}
323324

325+
type FetchChainServiceResponse =
326+
| {
327+
data: {
328+
services: ChainService[];
329+
};
330+
error?: never;
331+
}
332+
| {
333+
data?: never;
334+
error: unknown;
335+
};
336+
337+
/**
338+
* Retrieves a list of services available on a given chain
339+
* @param chain - The chain object containing the chain ID.
340+
* @returns A Promise that resolves to chain services.
341+
* @throws If there is an error fetching the chain services.
342+
* @example
343+
* ```ts
344+
* const chain = defineChain({ id: 1 });
345+
* const chainServices = await getChainServices(chain);
346+
* console.log(chainServices);
347+
* ```
348+
* @chain
349+
*/
350+
export function getChainServices(chain: Chain): Promise<ChainService[]> {
351+
const chainId = chain.id;
352+
return withCache(
353+
async () => {
354+
try {
355+
const res = await fetch(
356+
`https://api.thirdweb.com/v1/chains/${chainId}/services`,
357+
);
358+
if (!res.ok) {
359+
res.body?.cancel();
360+
throw new Error(`Failed to fetch services for chainId ${chainId}`);
361+
}
362+
363+
const response = (await res.json()) as FetchChainServiceResponse;
364+
if (response.error) {
365+
throw new Error(`Failed to fetch services for chainId ${chainId}`);
366+
}
367+
if (!response.data) {
368+
throw new Error(`Failed to fetch services for chainId ${chainId}`);
369+
}
370+
371+
const services = response.data.services;
372+
373+
return services;
374+
} catch {
375+
throw new Error(`Failed to fetch services for chainId ${chainId}`);
376+
}
377+
},
378+
{
379+
cacheKey: `chain:${chainId}:services`,
380+
cacheTime: 24 * 60 * 60 * 1000, // 1 day
381+
},
382+
);
383+
}
384+
324385
/**
325386
* Convert `ApiChain` to `Chain` object
326387
* @internal

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

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
1515
const events = await getContractEvents({
1616
contract: USDT_CONTRACT,
1717
fromBlock: FORK_BLOCK_NUMBER - 10n,
18+
useIndexer: false,
1819
});
1920
expect(events.length).toBe(261);
2021
});
@@ -35,13 +36,15 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
3536
const events = await getContractEvents({
3637
contract: USDT_CONTRACT,
3738
blockRange: 10n,
39+
useIndexer: false,
3840
});
3941
expect(events.length).toBe(241);
4042

4143
const explicitEvents = await getContractEvents({
4244
contract: USDT_CONTRACT,
4345
fromBlock: FORK_BLOCK_NUMBER - 9n, // 1 less than range as fromBlock and toBlock are inclusive
4446
toBlock: FORK_BLOCK_NUMBER,
47+
useIndexer: false,
4548
});
4649
expect(explicitEvents.length).toEqual(events.length);
4750
});
@@ -51,20 +54,23 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
5154
contract: USDT_CONTRACT,
5255
fromBlock: FORK_BLOCK_NUMBER - 49n,
5356
blockRange: 20n,
57+
useIndexer: false,
5458
});
5559
expect(eventsFromBlock.length).toBe(412);
5660

5761
const eventsToBlock = await getContractEvents({
5862
contract: USDT_CONTRACT,
5963
toBlock: FORK_BLOCK_NUMBER - 30n,
6064
blockRange: 20n,
65+
useIndexer: false,
6166
});
6267
expect(eventsToBlock.length).toBe(eventsFromBlock.length);
6368

6469
const explicitEvents = await getContractEvents({
6570
contract: USDT_CONTRACT,
6671
fromBlock: FORK_BLOCK_NUMBER - 49n,
6772
toBlock: FORK_BLOCK_NUMBER - 30n,
73+
useIndexer: false,
6874
});
6975
expect(explicitEvents.length).toEqual(eventsFromBlock.length);
7076
});
@@ -73,6 +79,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
7379
const events = await getContractEvents({
7480
contract: USDT_CONTRACT,
7581
fromBlock: FORK_BLOCK_NUMBER - 100n,
82+
useIndexer: false,
7683
events: [
7784
prepareEvent({
7885
signature: "event Burn(address indexed burner, uint256 amount)",
@@ -86,6 +93,8 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
8693
const events = await getContractEvents({
8794
contract: USDT_CONTRACT,
8895
fromBlock: FORK_BLOCK_NUMBER - 10n,
96+
toBlock: FORK_BLOCK_NUMBER,
97+
useIndexer: false,
8998
events: [
9099
prepareEvent({
91100
signature: "event Burn(address indexed burner, uint256 amount)",
@@ -126,6 +135,8 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
126135
const events = await getContractEvents({
127136
contract: DOODLES_CONTRACT,
128137
fromBlock: FORK_BLOCK_NUMBER - 1000n,
138+
toBlock: FORK_BLOCK_NUMBER,
139+
useIndexer: false,
129140
events: [transferEvent()],
130141
});
131142
expect(events.length).toBe(38);
@@ -135,6 +146,8 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
135146
const events = await getContractEvents({
136147
contract: DOODLES_CONTRACT,
137148
fromBlock: FORK_BLOCK_NUMBER - 1000n,
149+
toBlock: FORK_BLOCK_NUMBER,
150+
useIndexer: false,
138151
events: [
139152
transferEvent({
140153
from: "0xB81965DdFdDA3923f292a47A1be83ba3A36B5133",
@@ -143,4 +156,87 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
143156
});
144157
expect(events.length).toBe(2);
145158
});
159+
160+
// insight tests
161+
162+
it("should get events for blockHash using indexer", async () => {
163+
const BLOCK_HASH =
164+
"0xb0ad5ee7b4912b50e5a2d7993796944653a4c0632c57740fe4a7a1c61e426324";
165+
const events = await getContractEvents({
166+
contract: USDT_CONTRACT,
167+
blockHash: BLOCK_HASH,
168+
useIndexer: true,
169+
});
170+
171+
expect(events.length).toBe(14);
172+
});
173+
174+
it("should get individual events with extension no filter using indexer", async () => {
175+
const events = await getContractEvents({
176+
contract: DOODLES_CONTRACT,
177+
fromBlock: FORK_BLOCK_NUMBER - 1000n,
178+
toBlock: FORK_BLOCK_NUMBER,
179+
events: [transferEvent()],
180+
useIndexer: true,
181+
});
182+
expect(events.length).toBe(38);
183+
});
184+
185+
it("should get events for signature using indexer", async () => {
186+
const events = await getContractEvents({
187+
contract: DOODLES_CONTRACT,
188+
fromBlock: FORK_BLOCK_NUMBER - 1000n,
189+
toBlock: FORK_BLOCK_NUMBER,
190+
events: [
191+
transferEvent({
192+
from: "0xB81965DdFdDA3923f292a47A1be83ba3A36B5133",
193+
}),
194+
],
195+
useIndexer: true,
196+
});
197+
198+
expect(events.length).toBe(2);
199+
});
200+
201+
it("should get specified events using indexer", async () => {
202+
const events = await getContractEvents({
203+
contract: USDT_CONTRACT,
204+
fromBlock: FORK_BLOCK_NUMBER - 10n,
205+
toBlock: FORK_BLOCK_NUMBER,
206+
useIndexer: true,
207+
events: [
208+
prepareEvent({
209+
signature: "event Burn(address indexed burner, uint256 amount)",
210+
}),
211+
prepareEvent({
212+
signature: {
213+
anonymous: false,
214+
inputs: [
215+
{
216+
indexed: true,
217+
internalType: "address",
218+
name: "owner",
219+
type: "address",
220+
},
221+
{
222+
indexed: true,
223+
internalType: "address",
224+
name: "spender",
225+
type: "address",
226+
},
227+
{
228+
indexed: false,
229+
internalType: "uint256",
230+
name: "value",
231+
type: "uint256",
232+
},
233+
],
234+
name: "Approval",
235+
type: "event",
236+
},
237+
}),
238+
],
239+
});
240+
expect(events.length).toMatchInlineSnapshot("9");
241+
});
146242
});

0 commit comments

Comments
 (0)