Skip to content

Add slot filters for addresses' transactions#1024

Open
anselsol wants to merge 22 commits into
solana-foundation:masterfrom
anselsol:feat/add-slot-filters-for-programs
Open

Add slot filters for addresses' transactions#1024
anselsol wants to merge 22 commits into
solana-foundation:masterfrom
anselsol:feat/add-slot-filters-for-programs

Conversation

@anselsol

@anselsol anselsol commented May 26, 2026

Copy link
Copy Markdown

Description

The goal of this PR is to provide a way for users of explorer.solana.com to filter an address's transactions — by slot range, block time, and status — without relying on any kind of indexing for the explorer.

To do this, the PR switches the transaction history from the getSignaturesForAddress + per-signature getTransaction pattern over to Triton's getTransactionsForAddress method, which combines address-history lookup, filtering, and pagination into a single RPC call.

The filter API exposed in the UI maps one-to-one onto the method's filters object, so the explorer's URL params share the same names and shape as the RPC:

Filter URL param(s) gTFA filter path
Slot range slot.gte, slot.lte filters.slot.{gte,lte}
Block time range blockTime.gte, blockTime.lte filters.blockTime.{gte,lte}
Status status (succeeded / failed) filters.status

Pagination uses the opaque paginationToken cursor returned by the method (replacing the old trailing-signature before cursor); loading stops once the RPC returns a null token.

Graceful degradation on non-Triton/non-Helius endpoints

getTransactionsForAddress is a Triton extension. If the configured RPC endpoint doesn't implement it (the call returns a JSON-RPC "method not found"), the explorer:

  • falls back to the standard getSignaturesForAddress so transaction history still loads, and
  • disables the filter UI (the "Filters" button is shown disabled, and any active filter params are cleared) — since the fallback can't honour block-time/status filters, this avoids showing unfiltered results next to misleading "active filter" pills.

Detection is behavioral (based on the method-not-found response), so it works for Triton, Helius, or any other endpoint that implements the method — no provider allowlist is hardcoded.

How it's implemented in the UI:

Funnel 1: user filters from the UI

  1. User lands on https://explorer.solana.com/address/LGDSXVcDx4Ynw7UXavGEe5nwzyUZZ5d3sLkwYk26LUf
  2. User clicks the "Filters" button, next to the "Refresh" button, and sets any of the slot bounds, block-time bounds, or status
  3. The selection is written to the URL (slot.gte, slot.lte, blockTime.gte, blockTime.lte, status) and picked up by app/components/account/history/HistoryFilterBar.tsx
  4. That address's history cache is reset (superseding any in-flight request) and transactions are re-fetched directly from the RPC with the corresponding filters object
  5. User experience is then similar to an unfiltered view (load next transactions via the "Load More" button; transactions stop loading once the filtered range is exhausted)

Funnel 2: the URL already contains filter params

  1. User lands on
    https://explorer.solana.com/address/LGDSXVcDx4Ynw7UXavGEe5nwzyUZZ5d3sLkwYk26LUf?slot.gte=375952400
  2. The params are parsed by useHistoryFilters in app/components/account/history/HistoryFilterBar.tsx
  3. Transactions are fetched directly from the RPC using the parsed filters object, and the active filters render as removable pills above the table
  4. User experience is then similar to an unfiltered view (load next transactions via the "Load More" button; transactions stop loading once the filtered range is exhausted)

Type of change

  • Bug fix
  • New feature
  • Protocol integration
  • Documentation update
  • Other (please describe):

Screenshots

Screenshot 2026-04-28 at 15 15 54 - Screenshot 2026-04-28 at 15 15 59 Screenshot 2026-05-26 at 13 34 30

Testing

app/components/account/history/__tests__/HistoryFilterBar.spec.tsx: tests the filters button, URL param round-tripping, and pills behavior across slot, block-time, and status filters

app/providers/accounts/__tests__/history.spec.tsx: tests that the provider maps filters onto the getTransactionsForAddress request, threads the paginationToken correctly when loading more (including short pages that still carry a token), discards superseded in-flight responses on filter change, clears only the target address on reset, and falls back to getSignaturesForAddress (disabling filtering) when the method isn't available

Related Issues

Checklist

  • My code follows the project's style guidelines
  • I have added tests that prove my fix/feature works
  • All tests pass locally and in CI
  • I have updated documentation as needed
  • I have run build:info script to update build information
  • CI/CD checks pass
  • I have included screenshots for protocol screens (if applicable)
  • For security-related features, I have included links to related information

@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

@anselsol is attempting to deploy a commit to the Solana Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds address transaction filters backed by Triton's transaction-history RPC. The main changes are:

  • URL-driven slot, block-time, and status filters for address history.
  • Transaction history fetching through getTransactionsForAddress with cursor pagination.
  • Fallback to getSignaturesForAddress when the endpoint does not support the Triton method.
  • Filter UI disabling and filter clearing on unsupported endpoints.
  • Per-address history reset and stale response suppression for filter changes.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The latest fixes keep stale history responses from overwriting filtered results.
  • Per-address clearing avoids evicting unrelated cached history.
  • Unsupported endpoints fall back without leaving active filters visible.

Important Files Changed

Filename Overview
app/providers/accounts/history.tsx Adds filtered transaction-history fetching, fallback behavior, cursor handling, and per-address reset support.
app/features/transaction-history/ui/TransactionHistoryCard.tsx Connects URL filters to history fetching and resets the current address when filters change.
app/components/account/history/HistoryFilterBar.tsx Adds filter parsing, filter chips, and the filter popover UI.
app/providers/cache.tsx Adds keyed cache clearing while preserving the existing full-clear path.

Reviews (10): Last reviewed commit: "Merge upstream/master into feat/add-slot..." | Re-trigger Greptile

Comment thread app/components/account/history/TransactionHistoryCard.tsx Outdated
Comment thread app/providers/accounts/history.tsx Outdated
Comment thread app/providers/accounts/history.tsx
Comment thread app/providers/accounts/history.tsx
Comment thread app/providers/accounts/history.tsx Outdated
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
explorer Ready Ready Preview, Comment Jun 1, 2026 12:04pm

Request Review

@rogaldh

rogaldh commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Hi. Thank you for contribution!
Could you please make a rebase from the latest master? eslint.config was improved. To ensure that this change does not break those

@anselsol anselsol force-pushed the feat/add-slot-filters-for-programs branch from c5b0b67 to cc9e865 Compare May 27, 2026 07:45
The rebase onto upstream master pulled in stricter rules and updated
prettier/tailwind config (solana-foundation#1015, solana-foundation#1007):
- Prefix test titles with 'should' (vitest/valid-title)
- File-level eslint-disable for RegExp test assertions (no-restricted-syntax)
- Return undefined instead of null in app/components (unicorn/no-null)
- Re-sort the HistoryFilterBar import in TransactionHistoryCard (simple-import-sort)
- Reflow imports/JSX and tailwind class order to satisfy prettier

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@anselsol

Copy link
Copy Markdown
Author

Gm gm @rogaldh ! Rebased and fixed code for ESLint new rules, I've also ran the full CI locally and it goes through 🤝

…ters-for-programs

# Conflicts:
#	app/providers/accounts/history.tsx

@askov askov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice migration — the URL ↔ filters ↔ RPC mapping is clean, and the generation-counter pattern for invalidating in-flight requests on filter change is the right primitive (the dropped-stale-write test is exactly the right shape). A few inline notes on things I'm fairly sure are real, mostly around the fallback path and HTTP-level error handling.

Comment thread app/providers/accounts/history.tsx
Comment thread app/providers/accounts/history.tsx
Comment thread app/providers/accounts/history.tsx Outdated
});
}

export function useClearAccountHistories() {

@askov askov May 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nitpick (non-blocking): useClearAccountHistories is exported but I don't see a caller — the existing URL-change handler in HistoryProvider still dispatches { type: ActionType.Clear, url } inline.

suggestion: Delete this hook, or use it in place of the inline dispatch so the public surface of the provider matches what's actually consumed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Dead code, nuking it

Comment thread app/features/transaction-history/ui/TransactionHistoryCard.tsx
Comment thread app/components/account/history/HistoryFilterBar.tsx
@anselsol

Copy link
Copy Markdown
Author

@askov all your findings are fixed 🤝

@anselsol

anselsol commented Jun 3, 2026

Copy link
Copy Markdown
Author

What are the next steps here guys?

@jeduardo2092-design

Copy link
Copy Markdown

@askov all your findings are fixed 🤝

@anselsol

Copy link
Copy Markdown
Author

Gm gm guys, how can I help push this through the finish line?

@askov

askov commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Gm gm guys, how can I help push this through the finish line?

Sorry for the long response. Please do a rebase/merge and make sure nothing breaks regarding the UI (there was a huge ui migration, no more e- prefixes for tailwind classes). We will try to merge it as soon as possible

anselsol added 2 commits June 29, 2026 12:35
Reconciled three conflicts:
- app/providers/accounts/history.tsx: kept HEAD's filter machinery
  (HistoryFilters, getTransactionsForAddress, fallback, generation/method-support
  contexts) and accepted upstream's simpler `Set<string>` InFlightContext typing
  over HEAD's `Readonly<Set<string>>` wrapper.
- app/components/account/HistoryCardComponents.tsx: ported HEAD's
  actions/subHeader slots onto upstream's new <CardHeader ui="dashkit"> /
  <CardTitle> components, switching to a vertical layout only when a subHeader
  (filter chips) is present.
- app/components/shared/ui/input.tsx: kept HEAD's calendar-picker-indicator
  invert rule, applied to upstream's prefix-free Tailwind classes.

Also dropped the `e-` Tailwind prefix from HistoryFilterBar.tsx and replaced
its Bootstrap chip markup (`badge bg-info-soft`, `btn btn-link`) with the
upstream <Badge ui="dashkit" variant="info"> component to match the post-
Dashkit-SCSS-removal conventions.
The repo's global stylesheet reverts Tailwind Preflight's button reset, so
unstyled <button>s render with native UA chrome (dark background, border,
padding) that broke out of the chip's teal pill. Reset bg/border/padding
via utility classes (higher specificity than the element-level revert) and
inherit text color so the X glyph matches the badge text.
@anselsol anselsol force-pushed the feat/add-slot-filters-for-programs branch from 69d6346 to 40541e2 Compare June 29, 2026 10:36
Upstream extracted HistoryCardHeader/Footer into app/shared/ui/HistoryCard/
and split the transaction history card into BaseTransactionHistoryCard
(presentational) + TransactionHistoryCard (container). Reconciled:

- Ported the actions/subHeader slots onto the new shared HistoryCardHeader.
- Threaded those slots through BaseTransactionHistoryCard as headerActions /
  headerSubRow so the container can supply the filter trigger and chip row.
- Rebuilt TransactionHistoryCard as a container that keeps the filter wiring
  (filters, hasActiveFilters, resetHistory, clearFilters, filtersSupported)
  and renders BaseTransactionHistoryCard with the filter UI plugged into the
  header.
- Accepted upstream's gutted HistoryCardComponents.tsx (now only the row type
  and getTransactionRows; header/footer live at the new location).

In history.tsx, reconciled with upstream's empty-page robustness work:
- Added fetchSignatures (with empty-first-page retry) and routed it through
  the getSignaturesForAddress fallback path, where the protection actually
  applies; the Triton getTransactionsForAddress path is unchanged.
- Added the reconcile-time empty-refresh guard, adapted to the existing
  `append` flag (the equivalent of upstream's `before === undefined`).
- Updated history.spec.ts to use `append: true|false` instead of `before`.
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.

6 participants