Skip to content

Commit fff75fa

Browse files
feat: get logs improvements (#85)
* feat: make get logs type more generic * fix: slightly longer delay to make sure announcements are watched
1 parent b55a867 commit fff75fa

File tree

5 files changed

+147
-131
lines changed

5 files changed

+147
-131
lines changed
Lines changed: 29 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import type { BlockType } from '../types';
2-
3-
import { type PublicClient, parseAbiItem } from 'viem';
4-
import { getBlock, getBlockNumber, getLogs } from 'viem/actions';
1+
import type { GetEventArgs } from 'viem';
2+
import { ERC5564AnnouncerAbi } from '../../abi';
3+
import { fetchLogsInChunks } from '../../helpers/logs';
54
import { handleViemPublicClient } from '../../stealthClient/createStealthClient';
65
import type {
7-
AnnouncementLog,
6+
AnnouncementArgs,
87
GetAnnouncementsParams,
98
GetAnnouncementsReturnType
109
} from './types';
1110

11+
type AnnouncementFilter = GetEventArgs<
12+
typeof ERC5564AnnouncerAbi,
13+
'Announcement'
14+
>;
15+
1216
/**
1317
* This function queries logs for the `Announcement` event emitted by the ERC5564 contract.
1418
*
@@ -29,127 +33,33 @@ async function getAnnouncements({
2933
}: GetAnnouncementsParams): Promise<GetAnnouncementsReturnType> {
3034
const publicClient = handleViemPublicClient(clientParams);
3135

32-
const fetchParams = {
33-
address: ERC5564Address,
34-
args
35-
};
36-
3736
const logs = await fetchLogsInChunks({
38-
publicClient,
39-
fetchParams,
37+
publicClient: publicClient,
38+
abi: ERC5564AnnouncerAbi,
39+
eventName: 'Announcement',
40+
address: ERC5564Address,
41+
args: convertAnnouncementArgs(args),
4042
fromBlock,
4143
toBlock
4244
});
4345

44-
// Extract the relevant data from the logs
45-
const announcements: AnnouncementLog[] = logs.map(log => {
46-
const { args } = log;
47-
48-
return {
49-
schemeId: args.schemeId,
50-
stealthAddress: args.stealthAddress,
51-
caller: args.caller,
52-
ephemeralPubKey: args.ephemeralPubKey,
53-
metadata: args.metadata,
54-
...log
55-
};
56-
});
57-
58-
return announcements;
46+
return logs.map(log => ({
47+
schemeId: log.args.schemeId,
48+
stealthAddress: log.args.stealthAddress,
49+
caller: log.args.caller,
50+
ephemeralPubKey: log.args.ephemeralPubKey,
51+
metadata: log.args.metadata,
52+
...log
53+
}));
5954
}
6055

61-
/**
62-
* Fetches logs in chunks to handle potential large range queries efficiently.
63-
*
64-
* @param {Object} params - The parameters for fetching logs in chunks.
65-
* - `publicClient`: An instance of the viem `PublicClient`.
66-
* - `fetchParams`: Parameters for the log fetch query.
67-
* - `fromBlock`: The starting block number for the fetch.
68-
* - `toBlock`: The ending block number for the fetch.
69-
* - `chunkSize`: The number of blocks to query in each chunk.
70-
* @returns {Promise<GetLogsReturnType>} A flattened array of all logs fetched in chunks.
71-
*/
72-
const fetchLogsInChunks = async ({
73-
publicClient,
74-
fetchParams,
75-
fromBlock,
76-
toBlock,
77-
chunkSize = 5000 // Default chunk size, can be adjusted
78-
}: {
79-
publicClient: PublicClient;
80-
fetchParams: {
81-
address: `0x${string}`;
82-
// biome-ignore lint/suspicious/noExplicitAny: TODO handle better
83-
args: any;
84-
fromBlock?: BlockType;
85-
toBlock?: BlockType;
86-
};
87-
fromBlock?: BlockType;
88-
toBlock?: BlockType;
89-
chunkSize?: number;
90-
}) => {
91-
const resolvedFromBlock =
92-
(await resolveBlockNumber({
93-
publicClient,
94-
block: fromBlock ?? 'earliest'
95-
})) || BigInt(0);
96-
97-
const resolvedToBlock = await resolveBlockNumber({
98-
publicClient,
99-
block: toBlock ?? 'latest'
100-
});
101-
102-
let currentBlock = resolvedFromBlock;
103-
const allLogs = [];
104-
105-
while (currentBlock <= resolvedToBlock) {
106-
// Calculate the end block for the current chunk
107-
const endBlock =
108-
currentBlock + BigInt(chunkSize) < resolvedToBlock
109-
? currentBlock + BigInt(chunkSize)
110-
: resolvedToBlock;
111-
112-
const logs = await getLogs(publicClient, {
113-
...fetchParams,
114-
event: parseAbiItem(
115-
'event Announcement(uint256 indexed schemeId,address indexed stealthAddress,address indexed caller,bytes ephemeralPubKey,bytes metadata)'
116-
),
117-
fromBlock: currentBlock,
118-
toBlock: endBlock,
119-
strict: true
120-
});
121-
allLogs.push(...logs);
122-
currentBlock = endBlock + BigInt(1);
123-
}
124-
125-
return allLogs;
126-
};
127-
128-
/**
129-
* Resolves a block number from a given block type (number, tag, or bigint).
130-
*
131-
* @param {Object} params - Parameters for resolving the block number.
132-
* - `publicClient`: An instance of the viem `PublicClient`.
133-
* - `block`: The block number or tag to resolve.
134-
* @returns {Promise<bigint>} The resolved block number as a bigint or null.
135-
*/
136-
export async function resolveBlockNumber({
137-
publicClient,
138-
block
139-
}: {
140-
publicClient: PublicClient;
141-
block?: BlockType;
142-
}): Promise<bigint> {
143-
if (typeof block === 'bigint') {
144-
return block;
145-
}
146-
147-
const { number } = await getBlock(publicClient, { blockTag: block });
148-
// Get the latest block number if null, since it is the pending block
149-
if (!number) {
150-
return getBlockNumber(publicClient);
151-
}
152-
return number;
56+
// Helper function to convert AnnouncementArgs to the array format Viem expects
57+
function convertAnnouncementArgs(args: AnnouncementArgs) {
58+
return [
59+
args.schemeId === undefined ? undefined : args.schemeId,
60+
args.stealthAddress === undefined ? undefined : args.stealthAddress,
61+
args.caller === undefined ? undefined : args.caller
62+
] as AnnouncementFilter;
15363
}
15464

15565
export default getAnnouncements;

src/lib/actions/getAnnouncements/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Log } from 'viem';
22
import type { EthAddress } from '../../../utils/crypto/types';
33
import type { ClientParams } from '../../stealthClient/types';
4-
import type { BlockType } from '../types';
54

65
export type AnnouncementArgs = {
76
schemeId?: bigint | bigint[] | null | undefined;
@@ -21,7 +20,7 @@ export type GetAnnouncementsParams = {
2120
clientParams?: ClientParams;
2221
ERC5564Address: EthAddress;
2322
args: AnnouncementArgs;
24-
fromBlock?: BlockType;
25-
toBlock?: BlockType;
23+
fromBlock?: bigint | 'earliest';
24+
toBlock?: bigint | 'latest';
2625
};
2726
export type GetAnnouncementsReturnType = AnnouncementLog[];

src/lib/actions/types.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
export type BlockType =
2-
| bigint
3-
| 'latest'
4-
| 'earliest'
5-
| 'pending'
6-
| 'safe'
7-
| 'finalized';
8-
91
export type PreparePayload = {
102
to: `0x${string}`;
113
account: `0x${string}`;

src/lib/actions/watchAnnouncementsForUser/watchAnnouncementsForUser.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const announce = async ({
5858

5959
// Delay to wait for the announcements to be watched in accordance with the polling interval
6060
const delay = async () =>
61-
await new Promise(resolve => setTimeout(resolve, WATCH_POLLING_INTERVAL));
61+
await new Promise(resolve => setTimeout(resolve, WATCH_POLLING_INTERVAL * 2));
6262

6363
describe('watchAnnouncementsForUser', () => {
6464
let stealthClient: StealthActions;

src/lib/helpers/logs.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
type Abi,
3+
type AbiEvent,
4+
type ContractEventName,
5+
type DecodeEventLogReturnType,
6+
type GetEventArgs,
7+
type Log,
8+
type PublicClient,
9+
decodeEventLog
10+
} from 'viem';
11+
import { getBlockNumber, getLogs } from 'viem/actions';
12+
13+
/**
14+
* Parameters for fetching and decoding logs in chunks.
15+
* @template TAbi - The ABI type.
16+
*/
17+
type FetchLogsParams<TAbi extends Abi> = {
18+
/** An instance of the viem PublicClient. */
19+
publicClient: PublicClient;
20+
/** The ABI of the contract. */
21+
abi: TAbi;
22+
/** The name of the event to fetch logs for. */
23+
eventName: ContractEventName<TAbi>;
24+
/** The address of the contract. */
25+
address: `0x${string}`;
26+
/** Optional arguments to filter the logs. */
27+
args?: GetEventArgs<TAbi, ContractEventName<TAbi>>;
28+
/** The starting block number for the fetch. Defaults to 'earliest'. */
29+
fromBlock?: bigint | 'earliest';
30+
/** The ending block number for the fetch. Defaults to 'latest'. */
31+
toBlock?: bigint | 'latest';
32+
/** The number of blocks to query in each chunk. Defaults to 5000. */
33+
chunkSize?: number;
34+
};
35+
36+
type FetchLogsReturnType<TAbi extends Abi> = Array<
37+
DecodeEventLogReturnType<TAbi, ContractEventName<TAbi>> & Log
38+
>;
39+
40+
/**
41+
* Fetches and decodes logs in chunks to handle potentially large range queries efficiently.
42+
*
43+
* @template TAbi - The ABI type.
44+
* @param {FetchLogsParams<TAbi>} params - The parameters for fetching logs in chunks.
45+
* @returns {Promise<FetchLogsReturnType>} - A flattened array of all logs fetched in chunks, including decoded event data.
46+
*
47+
* @example
48+
* const logs = await fetchLogsInChunks({
49+
* publicClient,
50+
* abi: myContractABI,
51+
* eventName: 'Transfer',
52+
* address: '0x...',
53+
* fromBlock: 1000000n,
54+
* toBlock: 2000000n,
55+
* chunkSize: 10000
56+
* });
57+
*/
58+
export const fetchLogsInChunks = async <TAbi extends Abi>({
59+
publicClient,
60+
abi,
61+
eventName,
62+
address,
63+
args,
64+
fromBlock = 'earliest',
65+
toBlock = 'latest',
66+
chunkSize = 5000
67+
}: FetchLogsParams<TAbi>): Promise<FetchLogsReturnType<TAbi>> => {
68+
const [start, end] = await Promise.all([
69+
fromBlock === 'earliest'
70+
? 0n
71+
: typeof fromBlock === 'bigint'
72+
? fromBlock
73+
: getBlockNumber(publicClient),
74+
toBlock === 'latest' ? getBlockNumber(publicClient) : toBlock
75+
]);
76+
77+
const eventAbi = abi.find(
78+
(item): item is AbiEvent => item.type === 'event' && item.name === eventName
79+
);
80+
81+
if (!eventAbi) throw new Error(`Event ${eventName} not found in ABI`);
82+
83+
const allLogs = [];
84+
85+
for (
86+
let currentBlock = start;
87+
currentBlock <= end;
88+
currentBlock += BigInt(chunkSize)
89+
) {
90+
const logs = await getLogs(publicClient, {
91+
address,
92+
event: eventAbi,
93+
args,
94+
fromBlock: currentBlock,
95+
toBlock: BigInt(
96+
Math.min(Number(currentBlock) + chunkSize - 1, Number(end))
97+
),
98+
strict: true
99+
});
100+
101+
allLogs.push(
102+
...logs.map(log => ({
103+
...log,
104+
...decodeEventLog({
105+
abi,
106+
eventName,
107+
topics: log.topics,
108+
data: log.data
109+
})
110+
}))
111+
);
112+
}
113+
114+
return allLogs;
115+
};

0 commit comments

Comments
 (0)