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.
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) callsaggregator.getActivity(...)directly and applies only client-sidestatusfiltering on top.app/hooks/useTransactions.ts— drives infinite-scroll offpage.pagination.nextStartAfterCursor(getNextPageParam, line 92) and de-dupesfailedNetworksacross loaded pages (aggregateFailedNetworks, lines 18-28), because each page'sfailedNetworksonly reports failures from that page's own fan-out.app/hooks/useReadyToClaimCount.ts— callsaggregator.getReadyToClaimCount(...)(line 28) for the ready-to-claim badge.During review of #28, @MaximusHaximus flagged that the pagination/fan-out design in
aggregator.tsneeds 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
totalDesign comment / mechanics follow-up:
And drop
totalfrom the contract:The mechanics follow-up notes the current
anyMorecomputation keys off all cursor entries, including the two unfiltered/claimscalls, whosecountis 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 infailedNetworkswith 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 callComment:
3. Ready-probe concurrency cap + surface dropped probes
Comment:
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.