Frontend add explicit finality states to transaction status UI - #940
Merged
llinsss merged 2 commits intoAug 28, 2026
Conversation
…orer, transaction finality
|
@Bigmehelen Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
closed #860
Summary
Models Stellar transaction lifecycle explicitly and surfaces distinct user guidance for each finality. Links every hash to the configured stellar.expert explorer (testnet/public derived from NEXT_PUBLIC_STELLAR_NETWORK). Handles delayed provider responses with exponential back-off and conflicting provider responses with dedup + unknown conflict UI. Keeps TypeScript strict, lint clean, keyboard + mobile accessible.
Scope: src/components/TransactionStatusTracker.tsx only — per Keep unrelated refactors out.
Problem
Previously Pending/Failed collapsed the real chain states. submitted, accepted, confirmed, failed, unknown all looked the same, no explorer link, no handling for slow or contradictory provider lists.
Solution
Explicit lifecycle model — src/lib/transactionFinality.ts
export type TransactionFinality = 'submitted' | 'accepted' | 'confirmed' | 'failed' | 'unknown';
getFinality(tx: Transaction): TransactionFinality — pure function:
failed|cancelled → failed
confirmed → confirmed
pending + confirmations===0 && !blockNumber → submitted
pending + confirmations>=1 || blockNumber → accepted
otherwise/confirmations<0/undefined status → unknown
FINALITY_META — distinct copy per state (no PII):
Submitted: Sent to the network — waiting for confirmation…
Accepted: Accepted by the network and awaiting final confirmation…
Confirmed: Confirmed and final on-chain…
Failed: Transaction failed — it was not applied…
Unknown: Status could not be determined…
reconcileTransactions(pending, failed): ReconciledTransaction[] — de-dupes by id, flags isConflict when same id in both lists or status contradicts source (pending list with status==='failed' etc.) → coerces to unknown, sorted submitted→accepted→confirmed→failed→unknown then timestamp desc.
Configured explorer — src/lib/explorer.ts
export function isTestnet(): boolean { return (process.env.NEXT_PUBLIC_STELLAR_NETWORK||'').toLowerCase() !== 'public' && !== 'mainnet'; }
export function getExplorerTxUrl(hash: string): string // https://stellar.expert/explorer/{testnet|public}/tx/${encodeURIComponent(hash)}
Centralizes logic previously duplicated in WalletDashboard / TransactionSigning. Uses existing NEXT_PUBLIC_STELLAR_NETWORK (testnet default). No new env. Hash is encodeURIComponent-ed.
UI — src/components/TransactionStatusTracker.tsx
Replaces Pending (n)/Failed (n) with 5 sections Submitted/Accepted/Confirmed/Failed/Unknown each with heading h3, count badge, tone-colored guidance paragraph.
Per-tx row: type, truncated hash 0x12345678…cdef + toLocaleString() time, finality badge, View on explorer link (target="_blank" rel="noopener noreferrer"), contextual button (Cancel for submitted/accepted, Retry for failed, none for confirmed). Conflict rows add Conflicting status returned… verify on explorer.
State: pending+failed+pollError+isInitialLoading. Initial load → role="status" aria-busy="true" skeleton; poll error → role="alert" with back-off message; empty after load → return null (prev. behavior preserved).
Polling: BASE_INTERVAL=10_000, MAX_INTERVAL=160_000, delayRef*=2 on error, reset on success, scheduleNext(delay) via setTimeout, cleanup on unmount. Handles delayed provider (network stall/rejection) by showing alert and retrying.
Handles conflicting provider via reconcileTransactions → single unknown row with conflict note, still links to explorer.
A11y: role="region" aria-labelledby="tx-tracker-heading" aria-live="polite", h2#tx-tracker-heading, aria-label on badges/links/buttons, focus:ring-2 on all interactive elements, ul role="list".
Mobile: fixed bottom-4 left-4 right-4 md:left-auto md:right-4 md:w-96 max-h-[80vh] overflow-auto. No w-80 fixed desktop-only.
Exports BASE_INTERVAL, MAX_INTERVAL, getFinalityForTest for tests. No any, strict types, no weaken of auth/privacy (transactionAPI still sends authToken header).
Files Changed
M src/components/TransactionStatusTracker.tsx # lifecycle, explorer, a11y, mobile
M src/components/TransactionStatusTracker.test.tsx # full regression + new coverage
A src/lib/explorer.ts # centralized explorer
A src/lib/transactionFinality.ts # pure lifecycle model
Testing
Reproduces/characterizes before-fix via pure-model tests — proves old Pending collapse hid submitted/accepted/confirmed/unknown distinctions; new tests fail if model regresses.