Skip to content

feat(wallet): distinct error states and a connection timeout - #1797

Open
bilhokista wants to merge 3 commits into
Arena1X:mainfrom
bilhokista:feat/1544-wallet-error-states
Open

feat(wallet): distinct error states and a connection timeout#1797
bilhokista wants to merge 3 commits into
Arena1X:mainfrom
bilhokista:feat/1544-wallet-error-states

Conversation

@bilhokista

Copy link
Copy Markdown

Closes #1544.

Starting point

The modal was further along than the issue suggests: it already detected a missing extension, linked to the install page, and had a Retry button that re-attempts the last selected wallet. Those three requirements were met, and I did not rewrite them.

What I found instead were two real defects and one genuine gap.

Defect 1 — a declined request showed nothing at all

user_rejected was declared in the ErrorType union and never assigned. The rejection branch did this:

if (msg.includes("cancel") || msg.includes("reject") || ...) {
  // User rejected - reset to idle to let them try again
  resetModal();
  return;
}

Click a wallet, press Cancel in the extension, and the modal silently resets to the wallet list. No message, no explanation — indistinguishable from a misclick. It is now a first-class state with its own copy, worded as a choice rather than a fault.

Defect 2 — connectivity failures were reported as the wrong Stellar network

} else if (msg.includes("network") || msg.includes("testnet") || msg.includes("public")) {
  setError("Please switch to the Stellar Public network in your wallet");
}

"Network request failed", "NetworkError when attempting to fetch", and net::ERR_INTERNET_DISCONNECTED all contain the word "network". A user whose connection was down was told to go and switch chains.

The wrong-network rule now requires a chain-specific token (testnet, pubnet, public network, stellar network, wrong network, …) and never bare "network", and genuine connectivity failures are matched first and get their own state. There is a test asserting the old rule's behaviour as a baseline alongside the fixed one.

Gap — no timeout

If the extension never answered, fetchAddress() hung indefinitely and the modal sat on "connecting" with no escape but closing it. withWalletTimeout now races the request against a 30s clock and raises WalletTimeoutError, which classifies to its own state telling the user to check for a pending prompt in the extension.

Structure

Classification moves into frontend/src/lib/wallet-errors.ts as a pure function returning { type, title, message, hint?, canRetry }. The modal renders that object instead of carrying a chain of ternaries over an error-type string, which is what let the two defects above hide.

canRetry is false only for not_installed, where retrying cannot help until the extension exists — the panel offers the install link instead of a button guaranteed to fail. Every other branch keeps the retry path.

Rejection is checked before every other rule: a decline is deliberate, and any other rule matching the same text would mislabel a user's choice as a fault. There is a test for "User rejected: network request failed" landing on user_rejected.

Scope note

WalletContext.tsx and useHookErrorMessage.ts are listed in the issue but turned out not to need changes — the failure surfaces entirely inside the modal, and useHookErrorMessage is a logging helper for hooks rather than a classifier. I left both untouched rather than editing them to match the file list.

Tests

New wallet-errors.test.ts, 17 cases covering all seven branches, the wallet name appearing in the copy, case-insensitivity, non-Error values (string, null, undefined), the rejection-beats-network precedence, the fallback keeping the raw message, and three cases for withWalletTimeout including a fake-timer test that the unanswered request rejects with WalletTimeoutError.

Honest note on verification: I could not run the frontend Vitest suite locally (no full workspace install). All three files were parsed with the TypeScript compiler API, and wallet-errors.ts was transpiled and executed standalone against 37 assertions — all passed, including a real (60ms) timeout race and the baseline assertion showing the old substring rule classified "Network request failed" as wrong_network while the new one classifies it as network. The modal's rendering needs CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CrfEY1tvXrbeMDAUzxfuk7

@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
insight-arena-4rll Ready Ready Preview Sep 10, 2026 2:51pm UTC

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.

[Frontend] — Connect Wallet Modal Error and Retry States

1 participant