Skip to content

[FEATURE] improve getDecodedPublicEvents #20332

Closed

Description

@wei3erHase

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

  1. Pass the LogFilter to node.getPublicLogs(filter) as-is.
  2. For each returned log, match the event selector (last emitted field) against eventMetadata.eventSelector. Skip non-matching logs silently.
  3. 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.
  4. Decode via decodeFromAbi([eventMetadata.abiType], log.fields).
  5. 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

  1. added
    T-feature-requestType: Adding a brand new feature (not to be confused with improving an existing feature).
    from-communityThis originated from the community :)
    on Feb 10, 2026
  2. nventuro commented on Feb 10, 2026

    @nventuro
    Contributor

    @mverzilli did we pause the work on the event APIs in the end? I recall this coming up a bunch.

  3. mverzilli commented on Feb 10, 2026

    @mverzilli
    Contributor

    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)

  4. wei3erHase commented on Feb 13, 2026

    @wei3erHase
    Author

    Feel free to close when you think appropiate, thanks indeed! 🫡

  5. self-assigned this
    on Mar 5, 2026
  6. benesjan commented on Mar 5, 2026

    @benesjan
    Contributor

    AI found there are still some changes to do:

    Image

    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

    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.

    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 getPublicEvents on Wallet

    I 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

  7. AztecBot commented on Mar 6, 2026

    @AztecBot
    Collaborator

    This issue was automatically closed because it was referenced in PR #21172 which has been merged to the default branch.

    View workflow run

  8. nventuro commented on Mar 13, 2026

    @nventuro
    Contributor

    These features should now be available on the latest v4 nightly release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

T-feature-requestType: Adding a brand new feature (not to be confused with improving an existing feature).from-communityThis originated from the community :)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions