Skip to content

feat: per page data#264

Open
gsteenkamp89 wants to merge 18 commits intomasterfrom
feat/gsteenkamp/query-per-page-data
Open

feat: per page data#264
gsteenkamp89 wants to merge 18 commits intomasterfrom
feat/gsteenkamp/query-per-page-data

Conversation

@gsteenkamp89
Copy link
Collaborator

@gsteenkamp89 gsteenkamp89 commented Mar 17, 2026

closes FE-482

Summary

  • Per-page data loading: Each page (verify, propose, settled) now fetches only the subgraph data it needs via dedicated Tanstack Query hooks, replacing the monolithic reducer that loaded all data upfront across every page. Results trickle in as individual subgraph queries resolve.
  • Simplified oracle SDK: Deleted the complex client.ts orchestrator and per-service GQL factory classes (oraclev1/gql/factory.ts, oraclev3/gql/factory.ts, managedv2/gql/factory.ts, oracles/factory.ts). Query functions are now called directly from thin fetcher modules (src/hooks/oracle/fetchers.ts).
  • Server-side deeplink resolution: Deeplinks (?transactionHash=0x...) are now resolved by a new API route (/api/resolve-deeplink) that queries subgraphs directly, determines the correct page from entity state, and returns the result. The client hook redirects to the right page and opens the panel — no dependency on in-memory data. This replaces useQueryInSearchParams which searched the in-memory lookup table (broken after per-page split) and fell back to RPC calls against every ethers provider.

Other changes

  • Settled page data is capped at NEXT_PUBLIC_MAX_SETTLED_REQUESTS (default: 1000) per subgraph to prevent UI lockups from loading hundreds of MB of settlement history
  • Nav links no longer carry search/filter query params between pages
  • router.push / router.replace calls use scroll: false to prevent scroll-to-top when opening the panel or updating URL params
  • PanelContext distinguishes table row clicks (openPanel) from deeplink resolution (openPanelWithQuery) via an openedFromTable flag, preventing unnecessary API calls
  • makeUrlParamsForQuery now includes chainId and oracleType so deeplink URLs are self-contained

mrice32 and others added 9 commits February 4, 2026 00:04
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
@gsteenkamp89 gsteenkamp89 requested a review from mrice32 as a code owner March 17, 2026 13:52
@vercel
Copy link

vercel bot commented Mar 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
amoy-optimistic-oracle-dapp-v2 Ready Ready Preview, Comment Mar 20, 2026 8:07pm
optimistic-oracle-dapp-v2 Ready Ready Preview, Comment Mar 20, 2026 8:07pm
sepolia-optimistic-oracle-dapp-v2 Ready Ready Preview, Comment Mar 20, 2026 8:07pm

Request Review

@linear
Copy link

linear bot commented Mar 17, 2026

Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
@0xjorgen
Copy link
Collaborator

GQL Injection in deeplink query functions

The getRequestByHash, getAssertionByHash, and getRequestByDetails functions interpolate user input directly into GQL template strings (e.g. where: { requestHash: "${txHash}" }).

Hash-based path: isTransactionHash only checks startsWith("0x") && length === 66 — doesn't verify hex chars. A " can be embedded to break out of the GQL string.

Legacy detail-based path (higher risk): requester, timestamp, identifier, ancillaryData have no validation at all. A crafted ancillaryData can inject arbitrary GQL — e.g. closing the original query early and appending _meta { block { number } } or additional aliased queries. Full valid-GQL injection is possible.

Impact is low for The Graph (read-only, public data, no mutations), but it's still a bad pattern — especially if these query functions are reused against a private GraphQL backend later. Recommend adding:

  • Strict hex regex (/^0x[0-9a-fA-F]{64}$/) in getRequestByHash / getAssertionByHash
  • Reject " and \ in getRequestByDetails params, or switch to GraphQL variables

@0xjorgen
Copy link
Collaborator

0xjorgen commented Mar 18, 2026

I think the new deeplink flow regresses support for the legacy detail-based URL format.

Before this branch, useQueryInSearchParams handled both:

  • tx-hash links via transactionHash / eventIndex
  • legacy links via chainId / oracleType / requester / timestamp / identifier / ancillaryData

In this branch, Layout switches to useDeeplinkQuery, but that hook only parses transactionHash, eventIndex, chainId, and oracleType, and it only enables resolution when transactionHash is present.

That means a legacy URL like:
/?chainId=137&oracleType=OptimisticV2&requester=0x...&timestamp=...&identifier=...&ancillaryData=...
will never call /api/resolve-deeplink, so the panel never opens.

The API route and docs still claim to support the legacy detail-based lookup, so right now the server path exists but the client no longer exercises it.

I'll let you decide whether this is still worth supporting, but perhaps we should add a to-do item to tidy it up if we no longer intend to support it.

removeSearchParams(...DEEPLINK_PARAM_KEYS);
}, [removeSearchParams]);

// Handle browser back button
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a state bug here: when the panel was opened from a table row, hitting browser back closes the panel and clears directQuery, but it never resets openedFromTable.

That leaves useDeeplinkQuery permanently disabled for the rest of this provider session (enabled: hasDeeplink && !openedFromTable), so a later in-app deeplink navigation on the same page can be ignored.

I think this handler should also clear openedFromTable when it dismisses the panel.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
oracleEthersServices.forEach((factory) => {
factory({
// Ethers transaction updates are rare — for now they're no-ops since
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: do we still want tx-hash deeplinks to resolve immediately for fresh transactions, before the subgraph has indexed them?

Previously useQueryInSearchParams pushed inbound transactionHash values through updateFromTransactionHash on the ethers APIs, which gave us a receipt-based fallback while indexing caught up. With these callbacks now as no-ops, and /api/resolve-deeplink only searching subgraphs, it looks like a fresh tx deeplink can 404 until indexing completes.

If that old behavior was intentional product functionality, should we preserve some version of it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting one: the complexity and cost for this partial fallback was never worth it, in my opinion. subgraphs take about 5 -20 seconds to index, if a user visits a deeplink for a newly submitted transaction, they would only receive partial data from the tx receipt. And the app would have to search about ~26 RPCs to reslve the receipt. furthermore, some contracts like MOOv2 and OOv2 have identical event signatures so sometimes one would resolve before the other marking a request as originating from te wrong contract. very. brittle indeed.

For the (very few) users would navigate to a deeplink immediately after submitting, I have instead implemented some retry logic to the resolve-deeplink endpoint that relies on subgraphs only.

Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
@0xjorgen 0xjorgen self-requested a review March 18, 2026 08:32
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
@gsteenkamp89
Copy link
Collaborator Author

I think the new deeplink flow regresses support for the legacy detail-based URL format.

Before this branch, useQueryInSearchParams handled both:

  • tx-hash links via transactionHash / eventIndex
  • legacy links via chainId / oracleType / requester / timestamp / identifier / ancillaryData

In this branch, Layout switches to useDeeplinkQuery, but that hook only parses transactionHash, eventIndex, chainId, and oracleType, and it only enables resolution when transactionHash is present.

That means a legacy URL like: /?chainId=137&oracleType=OptimisticV2&requester=0x...&timestamp=...&identifier=...&ancillaryData=... will never call /api/resolve-deeplink, so the panel never opens.

The API route and docs still claim to support the legacy detail-based lookup, so right now the server path exists but the client no longer exercises it.

I'll let you decide whether this is still worth supporting, but perhaps we should add a to-do item to tidy it up if we no longer intend to support it.

Great find! I did not test legacy links.
I've pushed a fix. 770ef38

Copy link
Collaborator

@0xjorgen 0xjorgen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mooi!

Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Signed-off-by: Gerhard Steenkamp <gerhard@umaproject.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants