Skip to content

fix: clear recipient check loading state on RPC timeout - #301

Open
Bellamzy wants to merge 1 commit into
conduit-protocol:mainfrom
Bellamzy:fix/token-selector-loading-timeout
Open

fix: clear recipient check loading state on RPC timeout#301
Bellamzy wants to merge 1 commit into
conduit-protocol:mainfrom
Bellamzy:fix/token-selector-loading-timeout

Conversation

@Bellamzy

@Bellamzy Bellamzy commented Aug 3, 2026

Copy link
Copy Markdown

fix: clear recipient check loading state on RPC timeout

Problem

When the RPC provider timed out or hung during the on-chain recipient existence check, the
recipientStatus state was left at 'checking' indefinitely. This kept the submit button stuck
showing "Verifying recipient…" with no way for the user to proceed or recover — they had to
reload the page.

Two secondary issues existed alongside it:

  • If the user changed the address while a check was already in-flight, the stale result could
    land after the new check started, incorrectly flipping the status.
  • The component had no cleanup on unmount, so a pending check could call setRecipientStatus on
    an unmounted component.

Changes

lib/soroban.ts

  • checkRecipientExists now accepts an optional { signal?, timeoutMs? } parameter.
  • The underlying getAccount() call is wrapped in the existing withTimeout helper (default 10
    s), so a hung RPC provider always causes a rejection rather than an eternal pending promise.
  • OperationAbortedError is re-thrown explicitly so callers can distinguish cancellation from a
    genuine network failure and skip stale state updates.

app/create/page.tsx

  • The useEffect creates a new AbortController on each run.
  • Cleanup aborts any in-flight check and immediately resets recipientStatus to 'idle', covering
    both address-change and component-unmount cases.
  • A hard 10 s setTimeout inside the debounce callback aborts the controller if the RPC never
    responds, ensuring the spinner is always cleared regardless of network behaviour.
  • State updates are guarded by controller.signal.aborted to prevent stale results from a
    superseded check from landing.

Testing

  • Simulate a hung RPC (e.g. block the Soroban endpoint in DevTools → Network): status
    transitions to 'error' after 10 s and the button becomes usable again.
  • Change the address rapidly while a check is in-flight: no stale 'valid'/'not-found' flash.
  • Normal happy path (account exists / not found) unchanged.
    Bug: Token Selector hangs infinitely #123

- checkRecipientExists now accepts { signal?, timeoutMs? } and wraps
  getAccount() in withTimeout (default 10s) so a hung RPC provider
  never leaves the promise unsettled indefinitely
- Re-throws OperationAbortedError so callers can skip stale state updates
- useEffect in CreatePage creates an AbortController per run; cleanup
  aborts in-flight checks and resets status to idle immediately
- A hard 10s setTimeout inside the debounce callback aborts if the RPC
  never responds, guaranteeing the spinner is always cleared
- Stale result race (address changed mid-flight) eliminated by checking
  signal.aborted before calling setRecipientStatus

Fixes: stuck 'Verifying recipient…' button state on RPC timeout
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.

1 participant