Repository navigation
[FEATURE] improve getDecodedPublicEvents #20332
Description
Problem Statement
Problem
The current getDecodedPublicEvents only accepts (node, eventMetadata, fromBlock, limit), forcing callers to filter by block range alone. The underlying node.getPublicLogs already supports richer filtering via LogFilter (txHash, contractAddress, fromBlock, toBlock, afterLog), but none of those fields are passed through.
This means common patterns like "get Transfer events from this tx on this contract" require callers to drop down to raw log parsing, defeating the purpose of the helper.
On the private side, wallet.getPrivateEvents already accepts a rich filter with contractAddress, txHash, fromBlock, toBlock. This proposal brings public events to parity.
Proposed Solution
Proposed Signature
Overloaded to preserve backward compatibility with existing callers:
// New: accepts the full LogFilter
function getDecodedPublicEvents<T>(
node: AztecNode,
eventMetadata: EventMetadataDefinition,
filter: LogFilter,
): Promise<T[]>
// Legacy (unchanged): convenience wrapper that builds { fromBlock, toBlock }
function getDecodedPublicEvents<T>(
node: AztecNode,
eventMetadata: EventMetadataDefinition,
from: number,
limit: number,
): Promise<T[]>No new types -- LogFilter from @aztec/stdlib is reused directly, since the fields and semantics are identical.
Behaviour
- Pass the
LogFiltertonode.getPublicLogs(filter)as-is. - For each returned log, match the event selector (last emitted field) against
eventMetadata.eventSelector. Skip non-matching logs silently. - For matching logs, assert
log.fields.length === eventMetadata.fieldNames.length + 1. Throw on mismatch -- a matching selector with wrong payload length indicates ABI drift, not a benign skip. - Decode via
decodeFromAbi([eventMetadata.abiType], log.fields). - Return the decoded array typed as
T[].
Pagination
The function returns one page of results. When node.getPublicLogs signals maxLogsHit, the caller is responsible for paginating using afterLog set to the last returned log's LogId. This keeps the function simple and predictable -- callers that need exhaustive iteration can wrap it in a loop.
Example Use Case
Usage Examples
By transaction + contract (most common in tests):
const events = await getDecodedPublicEvents<Transfer>(
node,
TokenContract.events.Transfer,
{ txHash: receipt.txHash, contractAddress: token.address },
);By block range (indexer-style):
const events = await getDecodedPublicEvents<Transfer>(
node,
TokenContract.events.Transfer,
{ fromBlock: 100, toBlock: 110 },
);Legacy call sites (no changes needed):
const events = await getDecodedPublicEvents<Transfer>(
node,
TokenContract.events.Transfer,
receipt.blockNumber!,
1,
);Alternative Solutions
No response
Additional Context
Current vs Proposed
| Capability | Current | Proposed |
|---|---|---|
| Filter by block range | yes | yes |
| Filter by txHash | no | yes |
| Filter by contractAddress | no | yes |
Pagination (afterLog) |
no | yes |
| Silent skip on selector mismatch | yes | yes |
| Throw on field length mismatch | yes | yes |
| Backward compatible | n/a | yes (overload) |
Scope
This spec covers the standalone getDecodedPublicEvents function only. A follow-up could add wallet.getPublicEvents(metadata, filter) for symmetry with wallet.getPrivateEvents, so callers don't need to reach for the raw node reference.
Activity
nventuro commentedon Feb 10, 2026
@mverzilli did we pause the work on the event APIs in the end? I recall this coming up a bunch.
mverzilli commentedon Feb 10, 2026
Most of this was addressed in: #20189
(Already merged)
When I'm in front of a computer I'll check if there's anything else missing (probably pagination OTOH)
wei3erHase commentedon Feb 13, 2026
Feel free to close when you think appropiate, thanks indeed! 🫡
benesjan commentedon Mar 5, 2026
AI found there are still some changes to do:
Bellow I'll tackle them one by one
Exposing pagination
Exposed it in #21197
Legacy overload
This is no longer relevant as backwards compatibility was broken.
Missing assertion
This is incorrect because one refers to fields as in struct fields and another refers to fields as in Noir fields (and one struct fields can occupy multiple noir fields after serialization).
This is admittedly very confusing and we have once had a discussion with Noir people about renaming struct fields to struct members. They didn't want to do it as they wanted to keep the terminology of Rust. Bad call IMO
Documented this in #21172
Exposing
getPublicEventsonWalletI think it makes sense to keep the interface as simple as possible for now so keeping it only as util in Aztec.js seems like a good idea. In the past we were wrapping a lot of functionality like this and it just worsened maintenance and the benefit was not that great.
Thanks for the feedback @wei3erHase
AztecBot commentedon Mar 6, 2026
This issue was automatically closed because it was referenced in PR #21172 which has been merged to the default branch.
View workflow run
docs: clarifying Noir fields vs struct fields in event metadata (#21172)
feat!: exposing pub event pagination on wallet (#21197)
nventuro commentedon Mar 13, 2026
These features should now be available on the latest v4 nightly release.