Skip to content

Bring activity endpoints into the SDK + pagination/fan-out redesign #30

Description

@arnaubennassar

Background

AggkitBridgeAggregator.getActivity / getReadyToClaimCount (src/aggkit/aggregator.ts, introduced in #28) fan out across each configured aggkit bridge-service instance to build a paginated cross-network activity feed and a ready-to-claim count. agglayer-dev-ui's aggkit migration (agglayer/agglayer-dev-ui#24, feat/aggkit-backend) consumes this through thin wrappers rather than implementing any fan-out/pagination itself:

  • app/services/transactions.ts — fetchTransactions() (line 24) calls aggregator.getActivity(...) directly and applies only client-side status filtering on top.
  • app/hooks/useTransactions.ts — drives infinite-scroll off page.pagination.nextStartAfterCursor (getNextPageParam, line 92) and de-dupes failedNetworks across loaded pages (aggregateFailedNetworks, lines 18-28), because each page's failedNetworks only reports failures from that page's own fan-out.
  • app/hooks/useReadyToClaimCount.ts — calls aggregator.getReadyToClaimCount(...) (line 28) for the ready-to-claim badge.

During review of #28, @MaximusHaximus flagged that the pagination/fan-out design in aggregator.ts needs a redesign rather than a point fix, and the following comments were left unresolved on that PR pending it. This issue tracks that redesign. Any consumer-facing shape change here needs to stay compatible with, or come with matching updates to, the dev-ui call sites above.

Scope

1. Pagination redesign: claims as per-row enrichment, k-way merge, drop total

Design comment / mechanics follow-up:

Claims shouldn't be a paginated source at all. Calls A/B (bridges by origin/destination) are the actual feed -- same entity, same sort key. Calls C/D (the claims lists) exist only to decorate bridge rows with claim status, and they're not even authoritative for that (confirmClaimed's targeted global_index probe is). Stitching an enrichment lookup into the pagination contract is what poisons everything downstream: anyMore keyed to a network's total claim volume, total summing unfiltered counts, this over-return, and the composite cursor losing keys on partial failure. Claims belong as per-row enrichment, not a co-equal paginated stream.

Even for the two real feeds, page-number stitching can't work. Merging K independently-sorted sources needs a k-way merge with per-source high-water cursors: fetch a page from each source, emit exactly pageSize rows by the global sort key, and record how far you consumed INTO each source. That gives exact page sizes, true global ordering (right now desc only holds within a page -- page 2 can contain rows newer than page 1's oldest), correct hasMore, and a cursor that survives partial failure.

And drop total from the contract:

I'd drop total from the contract entirely -- it's unknowable across federated filtered sources without fetching everything, the current value is a lie, and the consumer is an infinite-scroll UI that doesn't need it. { rows (exactly <= pageSize), cursor, exhausted, failedNetworks } is the whole honest contract.

The mechanics follow-up notes the current anyMore computation keys off all cursor entries, including the two unfiltered /claims calls, whose count is every claim on the network — "a user with 3 bridges on a 100k-claim network gets a cursor for thousands of empty pages" — and that a network failing on page 1 contributes no cursor keys, so it lands in failedNetworks with no way to retry short of a full restart. Both go away once the merge-cursor redesign lands.

2. Promise.all -> Promise.allSettled, degrade per call

Comment:

Promise.all() over 4 calls -- so a single 502 on the global L1 claims list (the least important call, right? e.g. confirmClaimed is the real authority) wipes the entire network's activity into failedNetworks.
Would recommend using Promise.allSettled() instead, and degrade per call -- then the consumer can decide what to do in the case of failures

3. Ready-probe concurrency cap + surface dropped probes

Comment:

Unbounded concurrency: with pageSize = MAX_PAGE_SIZE this can put ~1,600 requests in flight against one aggkit instance (rows x probes x retries), and the catch { return false } below converts every rate-limited/reset response into "not ready" -- so the badge silently under-counts exactly when the instance is struggling. A modest concurrency cap plus surfacing the drop would fix both halves.

Non-goals

This issue captures the redesign requirements and where the current code and its downstream consumers live — not a design for the replacement. All three comments above were deliberately left unresolved on #28 pending this redesign.

Activity

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions