Skip to content

Commit 16e7e4e

Browse files
use insight client + fixes + tests
1 parent 6986315 commit 16e7e4e

File tree

7 files changed

+217
-109
lines changed

7 files changed

+217
-109
lines changed

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
9393
const events = await getContractEvents({
9494
contract: USDT_CONTRACT,
9595
fromBlock: FORK_BLOCK_NUMBER - 10n,
96+
toBlock: FORK_BLOCK_NUMBER,
9697
useIndexer: false,
9798
events: [
9899
prepareEvent({
@@ -134,6 +135,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
134135
const events = await getContractEvents({
135136
contract: DOODLES_CONTRACT,
136137
fromBlock: FORK_BLOCK_NUMBER - 1000n,
138+
toBlock: FORK_BLOCK_NUMBER,
137139
useIndexer: false,
138140
events: [transferEvent()],
139141
});
@@ -144,6 +146,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
144146
const events = await getContractEvents({
145147
contract: DOODLES_CONTRACT,
146148
fromBlock: FORK_BLOCK_NUMBER - 1000n,
149+
toBlock: FORK_BLOCK_NUMBER,
147150
useIndexer: false,
148151
events: [
149152
transferEvent({
@@ -154,6 +157,8 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
154157
expect(events.length).toBe(2);
155158
});
156159

160+
// insight tests
161+
157162
it("should get events for blockHash using indexer", async () => {
158163
const BLOCK_HASH =
159164
"0xb0ad5ee7b4912b50e5a2d7993796944653a4c0632c57740fe4a7a1c61e426324";
@@ -165,4 +170,73 @@ describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => {
165170

166171
expect(events.length).toBe(14);
167172
});
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+
});
168242
});

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

Lines changed: 42 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
1-
/**
2-
* 1. blockTime + contract (with abi) + no events -> logs with types and parsing *if* contract has abi defined
3-
* 2. blockTime + contract (no abi) + no events -> logs with NO types but *with* parsing
4-
* 3. blockTime + no contract + events -> logs with types and parsing (across all "addresses") (no contract filter)
5-
* 4. blockTime + contract + events -> logs with types and parsing (filtered by contract address + event topics)
6-
*/
7-
81
import type {
92
Abi,
103
AbiEvent,
114
ExtractAbiEvent,
125
ExtractAbiEventNames,
136
} from "abitype";
147
import { type Log, formatLog } from "viem";
15-
import type { Chain } from "../../chains/types.js";
168
import { getChainServices } from "../../chains/utils.js";
17-
import type { ThirdwebClient } from "../../client/client.js";
189
import { resolveContractAbi } from "../../contract/actions/resolve-abi.js";
1910
import type { ThirdwebContract } from "../../contract/contract.js";
11+
import { getContractEvents as getContractEventsInsight } from "../../insight/get-events.js";
2012
import { eth_blockNumber } from "../../rpc/actions/eth_blockNumber.js";
2113
import {
2214
type GetLogsBlockParams,
@@ -25,9 +17,7 @@ import {
2517
} from "../../rpc/actions/eth_getLogs.js";
2618
import { getRpcClient } from "../../rpc/rpc.js";
2719
import { getAddress } from "../../utils/address.js";
28-
import { getThirdwebDomains } from "../../utils/domains.js";
2920
import { type Hex, numberToHex } from "../../utils/encoding/hex.js";
30-
import { getClientFetch } from "../../utils/fetch.js";
3121
import type { Prettify } from "../../utils/type-utils.js";
3222
import { type PreparedEvent, prepareEvent } from "../prepare-event.js";
3323
import { isAbiEvent } from "../utils.js";
@@ -189,7 +179,6 @@ export async function getContractEvents<
189179
...restParams,
190180
address: getAddress(contract.address),
191181
topics: e.topics,
192-
signature: `${e?.abiEvent.name}(${e?.abiEvent.inputs.map((i) => i.type).join(",")})`,
193182
}))
194183
: // otherwise we want "all" events (aka not pass any topics at all)
195184
[{ ...restParams, address: getAddress(contract.address) }];
@@ -203,12 +192,12 @@ export async function getContractEvents<
203192
logsParams.map((p) =>
204193
getLogsFromInsight({
205194
params: p,
206-
chain: contract.chain,
207-
client: contract.client,
195+
contract,
208196
}),
209197
),
210198
);
211-
} catch {
199+
} catch (e) {
200+
console.warn("Error fetching from insight", e);
212201
// fetch from rpc
213202
logs = await Promise.all(
214203
logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)),
@@ -232,95 +221,55 @@ export async function getContractEvents<
232221

233222
async function getLogsFromInsight(options: {
234223
params: GetLogsParamsExtra;
235-
chain: Chain;
236-
client: ThirdwebClient;
237-
signature?: string;
224+
contract: ThirdwebContract<Abi>;
238225
}): Promise<Log[]> {
239-
const { params, chain, client, signature } = options;
226+
const { params, contract } = options;
240227

241-
const chainServices = await getChainServices(chain);
228+
const chainServices = await getChainServices(contract.chain);
242229
const insightEnabled = chainServices.some(
243230
(c) => c.service === "insight" && c.enabled,
244231
);
245232

246233
if (!insightEnabled) {
247-
throw new Error(`Insight is not available for chainId ${chain.id}`);
234+
throw new Error(
235+
`Insight is not available for chainId ${contract.chain.id}`,
236+
);
248237
}
249238

250-
try {
251-
let baseUrl = `https://${getThirdwebDomains().insight}/v1/events`;
252-
if (params.address) {
253-
baseUrl += `/${params.address}`;
254-
if (signature) {
255-
baseUrl += `/${signature}`;
256-
}
257-
}
258-
const url = new URL(baseUrl);
259-
260-
url.searchParams.set("chain", chain.id.toString());
261-
url.searchParams.set("limit", "500"); // this is max limit on insight
239+
const fromBlock =
240+
typeof params.fromBlock === "bigint" ? Number(params.fromBlock) : undefined;
262241

263-
if (params.blockHash) {
264-
url.searchParams.set("filter_block_hash", params.blockHash);
265-
} else {
266-
if (params.fromBlock) {
267-
const fromBlock =
268-
typeof params.fromBlock === "bigint"
269-
? numberToHex(params.fromBlock)
270-
: params.fromBlock;
271-
272-
url.searchParams.set("filter_block_number_gte", fromBlock);
273-
}
274-
if (params.toBlock) {
275-
const toBlock =
276-
typeof params.toBlock === "bigint"
277-
? numberToHex(params.toBlock)
278-
: params.toBlock;
279-
280-
url.searchParams.set("filter_block_number_lte", toBlock);
281-
}
282-
}
242+
const toBlock =
243+
typeof params.toBlock === "bigint" ? Number(params.toBlock) : undefined;
283244

284-
if (params.topics) {
285-
const args = params.topics.slice(1);
286-
for (const [i, a] of args.entries()) {
287-
if (a) {
288-
url.searchParams.set(`filter_topic_${i + 1}`, a as Hex);
289-
}
290-
}
291-
}
245+
const r = await getContractEventsInsight({
246+
client: contract.client,
247+
chains: [contract.chain],
248+
contractAddress: contract.address,
249+
queryOptions: {
250+
limit: 500,
251+
filter_block_hash: params.blockHash,
252+
filter_block_number_gte: fromBlock,
253+
filter_block_number_lte: toBlock,
254+
filter_topic_0: params.topics?.[0] as Hex | undefined,
255+
filter_topic_1: params.topics?.[1] as Hex | undefined,
256+
filter_topic_2: params.topics?.[2] as Hex | undefined,
257+
filter_topic_3: params.topics?.[3] as Hex | undefined,
258+
},
259+
});
292260

293-
const clientFetch = getClientFetch(client);
294-
const result = await clientFetch(url.toString());
295-
const fetchedEventData = (await result.json()) as {
296-
data: {
297-
chain_id: number;
298-
block_number: number;
299-
block_hash: string;
300-
block_timestamp: string;
301-
transaction_hash: string;
302-
transaction_index: number;
303-
log_index: number;
304-
address: string;
305-
data: string;
306-
topics: string[];
307-
}[];
308-
};
309-
const cleanedEventData = fetchedEventData.data.map((tx) => ({
310-
chainId: tx.chain_id,
311-
blockNumber: numberToHex(tx.block_number),
312-
blockHash: tx.block_hash as Hex,
313-
blockTimestamp: tx.block_timestamp,
314-
transactionHash: tx.transaction_hash as Hex,
315-
transactionIndex: numberToHex(tx.transaction_index),
316-
logIndex: numberToHex(tx.log_index),
317-
address: tx.address,
318-
data: tx.data as Hex,
319-
topics: tx.topics as [`0x${string}`, ...`0x${string}`[]] | [] | undefined,
320-
}));
261+
const cleanedEventData = r.map((tx) => ({
262+
chainId: tx.chain_id,
263+
blockNumber: numberToHex(Number(tx.block_number)),
264+
blockHash: tx.block_hash as Hex,
265+
blockTimestamp: tx.block_timestamp,
266+
transactionHash: tx.transaction_hash as Hex,
267+
transactionIndex: numberToHex(tx.transaction_index),
268+
logIndex: numberToHex(tx.log_index),
269+
address: tx.address,
270+
data: tx.data as Hex,
271+
topics: tx.topics as [`0x${string}`, ...`0x${string}`[]] | [] | undefined,
272+
}));
321273

322-
return cleanedEventData.map((e) => formatLog(e));
323-
} catch {
324-
throw new Error("Error fetching events from insight");
325-
}
274+
return cleanedEventData.map((e) => formatLog(e));
326275
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {
2+
type GetV1EventsByContractAddressData,
3+
type GetV1EventsByContractAddressResponse,
4+
getV1EventsByContractAddress,
5+
} from "@thirdweb-dev/insight";
6+
import type { AbiEvent } from "ox/AbiEvent";
7+
import { stringify } from "viem";
8+
import type { Chain } from "../chains/types.js";
9+
import type { ThirdwebClient } from "../client/client.js";
10+
import type { PreparedEvent } from "../event/prepare-event.js";
11+
import { getThirdwebDomains } from "../utils/domains.js";
12+
import { getClientFetch } from "../utils/fetch.js";
13+
14+
export type ContractEvent = NonNullable<
15+
GetV1EventsByContractAddressResponse["data"]
16+
>[number];
17+
18+
/**
19+
* Get contract events
20+
* @example
21+
* ```ts
22+
* import { Insight } from "thirdweb";
23+
*
24+
* const events = await Insight.getContractEvents({
25+
* client,
26+
* chains: [sepolia],
27+
* contractAddress: "0x1234567890123456789012345678901234567890",
28+
* event: transferEvent(),
29+
* decodeLogs: true,
30+
* });
31+
* ```
32+
* @insight
33+
*/
34+
export async function getContractEvents(options: {
35+
client: ThirdwebClient;
36+
chains: Chain[];
37+
contractAddress: string;
38+
event?: PreparedEvent<AbiEvent>;
39+
decodeLogs?: boolean;
40+
queryOptions?: GetV1EventsByContractAddressData["query"];
41+
}): Promise<ContractEvent[]> {
42+
const { client, chains, contractAddress, event, queryOptions, decodeLogs } =
43+
options;
44+
45+
const defaultQueryOptions: GetV1EventsByContractAddressData["query"] = {
46+
chain: chains.map((chain) => chain.id),
47+
limit: 100,
48+
decode: decodeLogs,
49+
};
50+
51+
if (event) {
52+
defaultQueryOptions.filter_topic_0 = event.topics[0];
53+
defaultQueryOptions.filter_topic_1 = event.topics[1];
54+
defaultQueryOptions.filter_topic_2 = event.topics[2];
55+
defaultQueryOptions.filter_topic_3 = event.topics[3];
56+
}
57+
58+
const result = await getV1EventsByContractAddress({
59+
baseUrl: `https://${getThirdwebDomains().insight}`,
60+
fetch: getClientFetch(client),
61+
path: {
62+
contractAddress,
63+
},
64+
query: {
65+
chain: chains.map((chain) => chain.id),
66+
...defaultQueryOptions,
67+
...queryOptions,
68+
},
69+
});
70+
71+
if (result.error) {
72+
throw new Error(
73+
`${result.response.status} ${result.response.statusText} - ${result.error ? stringify(result.error) : "Unknown error"}`,
74+
);
75+
}
76+
77+
return result.data?.data ?? [];
78+
}

0 commit comments

Comments
 (0)