fix(frontend): paginate factory event fetch instead of silently capping the token explorer#962
Open
Macnelson9 wants to merge 1 commit into
Open
Conversation
…ng "all tokens" fetchAllTokens() in useTokens.ts (the no-creator path used for the global token explorer) called stellarService.getContractEvents(contractId, 100) once and filtered for 'created' events, with no pagination and no indication to the user that the result could be partial. Traced getContractEvents's implementation (stellar-impl.ts): it calls Soroban's getEvents RPC, which returns events in ascending ledger order (oldest first) from the earliest retained ledger when no cursor is given. A single capped call therefore drops the *newest* events once history exceeds one page — the opposite of the usual "old data missing" assumption. Concretely: once the factory has emitted more than 100 `created` events, brand-new tokens silently stop appearing in "All Tokens" while old ones keep showing up, with no pagination control and no way to reach them through this code path. getContractEvents already supports cursor-based pagination (used by getTokenEvents), so full pagination is feasible today — no need to settle for an honest-but-partial indicator. Added fetchAllContractEvents (frontend/ src/utils/), a small helper that pages through the cursor using the same short-page end-of-data signal fetchAllTokensByCreator already uses for the contract's paginated view call, and documents the ordering/truncation behavior at the call site as required. TokenExplorer.tsx — the component actually rendered as the Token Explorer — turned out to have its own, independent duplicate of this same bug class (getContractEvents(contractId, 1000), same silent-cap problem, just a higher ceiling). Switched both of its call sites to the same fetchAllContractEvents helper so there's one implementation of "fetch the complete factory event history" instead of two that can independently drift, and the explorer view itself (not just the otherwise-unused useTokens() no-creator path) now shows the complete set. This re-walks full event history per load, which won't scale indefinitely — per the issue, the durable fix is an off-chain indexer (Favourorg#35); noted as a comment in fetchAllContractEvents. Added tests: fetchAllContractEvents.test.ts covers multi-page pagination, a null-cursor-on-a-full-page edge case, and the empty-history case. useTokens.test.tsx adds a regression test with 120 mocked 'created' events across two pages, asserting all 120 resolve rather than only the first 100.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #930
Summary
fetchAllTokens()inuseTokens.ts(the no-creator path intended for the global token explorer) calledstellarService.getContractEvents(contractId, 100)once and filtered forcreatedevents — a hardcoded, undocumented cap on the entire explorer's dataset, with no pagination and no indication to the user that results could be partial.Investigation: what actually gets dropped?
Traced
getContractEvents's implementation instellar-impl.ts: it calls Soroban'sgetEventsRPC, which returns events in ascending ledger order (oldest first) starting from the earliest retained ledger when no cursor is supplied. A single capped call therefore drops the newest events once the factory's event history exceeds one page — the opposite of the usual "old data goes missing" assumption. Concretely: once the factory has emitted more than 100createdevents, brand-new tokens silently stop appearing in "All Tokens" while old ones keep showing up, with no pagination control and no way to reach the missing tokens through this code path. This is documented at the call site now (fetchAllContractEvents.ts).getContractEventsalready supports cursor-based pagination (it's used this way bygetTokenEvents), so full pagination to fetch the complete history was feasible — no need to fall back to an honest-but-partial UI indicator.Fix
frontend/src/utils/fetchAllContractEvents.ts: pages throughgetContractEvents's cursor until a short page signals end-of-data, mirroring the exact end-of-data patternfetchAllTokensByCreatoralready uses for the contract's paginatedget_tokens_by_creatorview call.useTokens.ts'sfetchAllTokens()now uses this helper instead of one fixed-size call.TokenExplorer.tsx— the component actually rendered as the Token Explorer page — has its own, entirely independent duplicate of this same bug class (getContractEvents(contractId, 1000), same silent-cap failure mode, just a higher ceiling, used in both its "browse all tokens" effect and its "search by index" handler). Since the acceptance criteria is about what the explorer view actually shows, not just this one hook, I switched both ofTokenExplorer.tsx's call sites to the samefetchAllContractEventshelper — one implementation of "fetch the complete factory event history" instead of two that could independently drift, and the page users actually see now shows the complete set.Scaling note (per the issue)
This re-walks the full factory event history on every load, which won't scale indefinitely as the platform grows. Per the issue's own framing, the durable long-term fix is an off-chain indexer (#35) rather than the frontend re-deriving state from raw contract events on each request — noted as a comment in
fetchAllContractEvents.tsso this doesn't get relitigated from scratch later.Tests
fetchAllContractEvents.test.ts(new): single short page (no pagination needed), multi-page pagination across 3 pages (230 events), a full-page-with-null-cursor edge case, and the empty-history case.useTokens.test.tsx: added a regression test with 120 mockedcreatedevents split across two pages (100 + 20), asserting all 120 resolve toTokenInfo— not just the first 100 — and that the secondgetContractEventscall is made with the cursor returned by the first.Test plan
npx vitest run— all 47 test files / 466 tests pass.npx tsc --noEmit— clean.npx eslinton changed files — clean.