Skip to content

feat(web): map simulate errors to ErrorBanner via parseContractError (#561) - #630

Open
davismas23 wants to merge 2 commits into
Vatix-Protocol:devfrom
davismas23:feat/561-contract-client-simulate-error-banner
Open

feat(web): map simulate errors to ErrorBanner via parseContractError (#561)#630
davismas23 wants to merge 2 commits into
Vatix-Protocol:devfrom
davismas23:feat/561-contract-client-simulate-error-banner

Conversation

@davismas23

Copy link
Copy Markdown
Contributor

Summary

Closes #561

Wires contract simulation failures through the existing parseContractError helper and surfaces them in both the inline error alert and the global toast (ErrorBanner) so users see a short, human-readable message rather than a raw Soroban SDK error string.

Problem

queryContract (used by every read-only call including getPosition) was re-throwing raw errors. PositionPanel's catch block forwarded err.message directly without mapping it through parseContractError, and had no toast notification at all.

A Soroban simulation error like:

Simulation failed: Error(Contract, #6) ...

was displayed verbatim. After this PR it becomes:

Deposits are currently closed for this market.

Changes

apps/web/lib/contract-client.ts

queryContract now parses every thrown error through parseContractError before re-throwing:

catch (error) {
  console.error('Contract query error:', error);
  const { parseContractError } = await import('@/lib/errors');
  throw new Error(parseContractError(error));
}
  • Contract error codes → human copy from MARKET_ERROR_MESSAGES
  • Simulation failed: ...Transaction would fail: ...
  • Wallet rejections → Request was rejected in the wallet.
  • Unknown errors pass through unchanged

The import is dynamic (same pattern as the Freighter import in invokeContract) so this module stays safe on server-rendered paths.

apps/web/components/PositionPanel.tsx

  • Imports useToast from @/context/ToastContext
  • Imports parseContractError from @/lib/errors (defensive passthrough if already parsed)
  • useToast() destructured in the component body
  • showToast added to the useCallback dependency array
  • catch block now:
    const reason = parseContractError(err);
    setError(reason);           // inline role="alert" block
    showToast(reason, 'error'); // global toast — auto-dismisses after 6 s
  • Updated JSDoc to document the simulation-error surfacing behaviour

Error flow after this PR

getPosition()
  └─ queryContract()
       └─ simulateTransaction()     ← throws on simulate failure
            └─ parseContractError() ← maps to user-facing copy
                 └─ throw new Error(reason)
PositionPanel.refresh() catches:
  const reason = parseContractError(err)  ← already parsed, passthrough
  setError(reason)     →  inline <p role="alert">
  showToast(reason)    →  global toast (ErrorBanner, 6 s auto-dismiss)

Acceptance criteria

  • Failed simulate shows recoverable message (inline alert + toast)

Notes

…atix-Protocol#561)

Simulation failures surfaced by queryContract (used for all read-only
contract calls, including getPosition) were re-thrown as raw SDK error
strings. UI components that called getPosition had to decide themselves
how to handle them — PositionPanel was just forwarding the raw message
and had no toast notification at all.

Changes
-------

### apps/web/lib/contract-client.ts

queryContract now parses every caught error through parseContractError
before re-throwing:

  catch (error) {
    const { parseContractError } = await import('@/lib/errors');
    throw new Error(parseContractError(error));
  }

This means:
- Soroban host traps like Error(Contract, Vatix-Protocol#6) become
  'Deposits are currently closed for this market.'
- 'Simulation failed: <raw>...' becomes
  'Transaction would fail: <raw>...'
- Wallet rejections become 'Request was rejected in the wallet.'
- Unrecognised errors pass through unchanged

The import is dynamic (same pattern as the Freighter import in
invokeContract) so this module stays safe on server-rendered paths.

### apps/web/components/PositionPanel.tsx

- Added useToast import from @/context/ToastContext
- Added parseContractError import from @/lib/errors (defensive layer
  in case any caller re-wraps before reaching here)
- useToast() destructured; showToast added to the useCallback deps array
- catch block now calls:
    const reason = parseContractError(err);
    setError(reason);          // inline role='alert' block
    showToast(reason, 'error'); // global ErrorBanner toast
- Updated JSDoc to document the simulation-error surfacing behaviour

Error flow (after this PR)
--------------------------

  getPosition()
    └─ queryContract()
         └─ simulateTransaction()   ← throws on simulate error
              └─ parseContractError()  ← maps to user-facing copy
                   └─ throw new Error(reason)
  PositionPanel.refresh() catches:
    const reason = parseContractError(err)  ← already parsed, passthrough
    setError(reason)     → inline <p role='alert'>
    showToast(reason)    → global toast banner (auto-dismisses after 6 s)

Acceptance criteria
-------------------
- Failed simulate shows recoverable message (inline + toast)

Closes Vatix-Protocol#561
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.

Contract client maps simulate errors to ErrorBanner

1 participant