Skip to content

Commit 44dfacf

Browse files
committed
internal function to fetch events from insight
1 parent eb17f21 commit 44dfacf

File tree

3 files changed

+186
-36
lines changed

3 files changed

+186
-36
lines changed

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.ts

Lines changed: 117 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import type {
1111
ExtractAbiEvent,
1212
ExtractAbiEventNames,
1313
} from "abitype";
14-
import { formatLog } from "viem";
14+
import { type Log, formatLog } from "viem";
15+
import type { Chain } from "../../chains/types.js";
16+
import { getChainServices } from "../../chains/utils.js";
17+
import type { ThirdwebClient } from "../../client/client.js";
1518
import { resolveContractAbi } from "../../contract/actions/resolve-abi.js";
1619
import type { ThirdwebContract } from "../../contract/contract.js";
1720
import { eth_blockNumber } from "../../rpc/actions/eth_blockNumber.js";
@@ -22,7 +25,7 @@ import {
2225
} from "../../rpc/actions/eth_getLogs.js";
2326
import { getRpcClient } from "../../rpc/rpc.js";
2427
import { getAddress } from "../../utils/address.js";
25-
import { getThirdwebDomains } from "../../utils/domains.js";
28+
import { type Hex, numberToHex } from "../../utils/encoding/hex.js";
2629
import { getClientFetch } from "../../utils/fetch.js";
2730
import type { Prettify } from "../../utils/type-utils.js";
2831
import { type PreparedEvent, prepareEvent } from "../prepare-event.js";
@@ -52,13 +55,9 @@ export type GetContractEventsResult<
5255
TStrict extends boolean,
5356
> = ParseEventLogsResult<abiEvents, TStrict>;
5457

55-
type InsightEvent = {
56-
data: {
57-
address: string;
58-
data: `0x${string}`;
59-
topics: [`0x${string}`, ...`0x${string}`[]] | [];
60-
}[];
61-
};
58+
export type GetLogsParamsExtra = {
59+
signature?: string;
60+
} & GetLogsParams;
6261

6362
/**
6463
* Retrieves events from a contract based on the provided options.
@@ -121,29 +120,6 @@ export async function getContractEvents<
121120

122121
const rpcRequest = getRpcClient(contract);
123122

124-
try {
125-
if (events) {
126-
const sig = `${events[0]?.abiEvent.name}(${events[0]?.abiEvent.inputs.map((i) => i.type).join(",")})`;
127-
const url = new URL(
128-
`https://${getThirdwebDomains().insight}/v1/events/${contract.address}/${sig}`,
129-
);
130-
url.searchParams.set("limit", "10");
131-
url.searchParams.set("chain", contract.chain.id.toString());
132-
const clientFetch = getClientFetch(contract.client);
133-
const result = await clientFetch(url.toString());
134-
const eventInfo = (await result.json()) as InsightEvent;
135-
const cleanedEventInfo = eventInfo.data.map((e) => formatLog(e));
136-
137-
return parseEventLogs({
138-
logs: cleanedEventInfo,
139-
events,
140-
});
141-
}
142-
} catch (error) {
143-
//biome-ignore lint/suspicious/noConsole: Todo
144-
console.debug(error);
145-
}
146-
147123
if (
148124
restParams.blockHash &&
149125
(blockRange || restParams.fromBlock || restParams.toBlock)
@@ -198,20 +174,38 @@ export async function getContractEvents<
198174
}
199175
}
200176

201-
const logsParams: GetLogsParams[] =
177+
const logsParams: GetLogsParamsExtra[] =
202178
events && events.length > 0
203179
? // if we have events passed in then we use those
204180
events.map((e) => ({
205181
...restParams,
206182
address: getAddress(contract.address),
207183
topics: e.topics,
184+
signature: `${e?.abiEvent.name}(${e?.abiEvent.inputs.map((i) => i.type).join(",")})`,
208185
}))
209186
: // otherwise we want "all" events (aka not pass any topics at all)
210187
[{ ...restParams, address: getAddress(contract.address) }];
211188

212-
const logs = await Promise.all(
213-
logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)),
214-
);
189+
let logs: Log[][] = [];
190+
191+
// try fetching from insight if available
192+
try {
193+
logs = await Promise.all(
194+
logsParams.map((p) =>
195+
getLogsFromInsight({
196+
params: p,
197+
chain: contract.chain,
198+
client: contract.client,
199+
}),
200+
),
201+
);
202+
} catch {
203+
// fetch from rpc
204+
logs = await Promise.all(
205+
logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)),
206+
);
207+
}
208+
215209
const flattenLogs = logs
216210
.flat()
217211
.sort((a, b) => Number((a.blockNumber ?? 0n) - (b.blockNumber ?? 0n)));
@@ -220,3 +214,90 @@ export async function getContractEvents<
220214
events: resolvedEvents,
221215
});
222216
}
217+
218+
async function getLogsFromInsight(options: {
219+
params: GetLogsParamsExtra;
220+
chain: Chain;
221+
client: ThirdwebClient;
222+
signature?: string;
223+
}): Promise<Log[]> {
224+
const { params, chain, client } = options;
225+
226+
const chainServices = await getChainServices(chain);
227+
const insightEnabled = chainServices.some(
228+
(c) => c.service === "insight" && c.enabled,
229+
);
230+
231+
if (!insightEnabled) {
232+
throw new Error(`Insight is not available for chainId ${chain.id}`);
233+
}
234+
235+
try {
236+
const baseUrl = new URL("https://insight.thirdweb-dev.com/v1/events"); // TODO: change to prod
237+
let path = "";
238+
if (params.address) {
239+
path += `/${params.address}`;
240+
if (params.signature) {
241+
path += `/${params.signature}`;
242+
}
243+
}
244+
const url = new URL(path, baseUrl);
245+
246+
url.searchParams.set("chain", chain.id.toString());
247+
url.searchParams.set("limit", "500"); // this is max limit on insight
248+
249+
if (params.blockHash) {
250+
url.searchParams.set("filter_block_hash", params.blockHash);
251+
} else {
252+
if (params.fromBlock) {
253+
const fromBlock =
254+
typeof params.fromBlock === "bigint"
255+
? numberToHex(params.fromBlock)
256+
: params.fromBlock;
257+
258+
url.searchParams.set("filter_block_number_gte", fromBlock);
259+
}
260+
if (params.toBlock) {
261+
const toBlock =
262+
typeof params.toBlock === "bigint"
263+
? numberToHex(params.toBlock)
264+
: params.toBlock;
265+
266+
url.searchParams.set("filter_block_number_lte", toBlock);
267+
}
268+
}
269+
270+
const clientFetch = getClientFetch(client);
271+
const result = await clientFetch(url.toString());
272+
const fetchedEventData = (await result.json()) as {
273+
data: {
274+
chain_id: number;
275+
block_number: number;
276+
block_hash: string;
277+
block_timestamp: string;
278+
transaction_hash: string;
279+
transaction_index: number;
280+
log_index: number;
281+
address: string;
282+
data: string;
283+
topics: string[];
284+
}[];
285+
};
286+
const cleanedEventData = fetchedEventData.data.map((tx) => ({
287+
chainId: tx.chain_id,
288+
blockNumber: numberToHex(tx.block_number),
289+
blockHash: tx.block_hash as Hex,
290+
blockTimestamp: tx.block_timestamp,
291+
transactionHash: tx.transaction_hash as Hex,
292+
transactionIndex: numberToHex(tx.transaction_index),
293+
logIndex: numberToHex(tx.log_index),
294+
address: tx.address,
295+
data: tx.data as Hex,
296+
topics: tx.topics as [`0x${string}`, ...`0x${string}`[]] | [] | undefined,
297+
}));
298+
299+
return cleanedEventData.map((e) => formatLog(e));
300+
} catch {
301+
throw new Error("Error fetching events from insight");
302+
}
303+
}

0 commit comments

Comments
 (0)