Skip to content

Payments 5/8: Failure taxonomy and blockchain/network-failure handling with retry/backoff #142

Closed

Description

@wumibals

Problem / Context

app/src/app/deposit/page.tsx has a failed UI state (lines 185-194) but nothing in the app or SDK today ever produces a real failure — it's only reachable by manually forcing state in dev. There is no handling anywhere for RPC timeouts, ledger congestion, expired transaction time-bounds, missing trustlines, or simulation reverts. The relevant contract error surface already exists and is richer than anything the client currently understands: contracts/vault_router/src/error.rs defines InvalidTier, BelowMinDeposit, LockNotExpired, AssetNotAllowed, DepositCapExceeded, ProtocolPaused, Unauthorized; contracts/vault_l3/src/lib.rs additionally defines NotYetMatured and AmountExceedsBalance. The SDK's sdks/typescript/src/errors.ts has exactly two error classes (LockNotExpiredError, BelowMinDepositError); every other failure mode surfaces as an unstructured thrown Error, or is silently swallowed.

Objective

Give every payment operation a complete, typed failure taxonomy and well-defined retry/recovery behavior, so the app (and its users) can distinguish "this failed, and here's why," from "we don't know yet, keep waiting," from "safe to retry automatically."

Technical approach

  • Extend sdks/typescript/src/errors.ts into a full error hierarchy covering: contract-level reverts (one class per VaultError/VaultL3 error variant, decoded from simulate/getTransaction diagnostic events), network-level failures (RPC unreachable, timeout), transaction-level failures (expired time-bounds, insufficient fee, bad sequence number), and wallet-level failures (user rejected signing, wallet disconnected mid-flow).
  • Classify each error as retryable-safely / retryable-with-a-new-intent / not-retryable, and encode that classification on the error type itself so the UI doesn't need its own ad hoc branching logic.
  • Implement retry-with-exponential-backoff-and-jitter for transient network/timeout failures in the RPC layer (from Payments 4/8: Real transaction status tracking and confirmation lifecycle #141), bounded by a max-attempts/timeout ceiling that hands off to a terminal "failed, needs manual action" state rather than retrying forever.
  • Wire the deposit page's and EarlyExitModal's failure paths to these real, classified errors instead of the currently-unreachable mock state, showing the correct message per error class.
  • Ensure any resubmission after a recoverable failure (e.g. expired time-bounds) goes through the idempotency guard from Payments 3/8: Idempotency and duplicate-payment-submission prevention #140 rather than blindly resubmitting.

Detailed scope

sdks/typescript/src/errors.ts, the RPC/transaction-submission layer from #139/#141, app/src/app/deposit/page.tsx and app/src/components/EarlyExitModal.tsx failure-state wiring, and app/src/lib/toast.tsx for surfacing non-blocking failure notifications where appropriate.

Important edge cases and failure scenarios

  • Partial-success ambiguity: VaultRouter.withdraw performs a two-hop transfer (vault call, then token::Client::transfer from vault to user — contracts/vault_router/src/lib.rs lines 178-190). Confirm and document Soroban's actual atomicity guarantee for this pattern (a single host-function invocation either commits or reverts as a whole), so the client-side failure taxonomy doesn't invent a "half-completed withdrawal" state that can't actually occur on-chain — and correctly handles the states that can occur.
  • Network partition: RPC submission succeeds but all subsequent status polls fail — must not be reported to the user as "failed" when the transaction may, in fact, be confirmed on-chain. This requires re-querying rather than trusting local retry-exhaustion as proof of failure.
  • Wallet-level rejection (user cancels in Freighter) vs. contract-level rejection (revert) must produce distinct messages and distinct retry affordances — a cancelled signature is trivially retryable, a BelowMinDeposit revert is not without changing the input.
  • Distinguish "definitely failed, safe to retry immediately with the same intent" (e.g. a pre-submission simulation revert) from "ambiguous, must query chain state before retrying" (e.g. RPC timeout after the transaction was plausibly already accepted).

Dependencies

Depends on #141 (status tracking, to know when a failure is real vs. merely ambiguous) and #140 (idempotency, to safely gate any retry).

Acceptance criteria

  • Every VaultError/VaultL3 error variant has a corresponding typed SDK error with a distinct, actionable, user-facing message.
  • RPC calls transparently retry transient failures with backoff and terminate with a clear, non-looping failed state after a bounded number of attempts.
  • The deposit and early-exit failure UI states are reachable via real, injected failures in tests — not just hardcoded/manual triggers.

Definition of done

PR merged; test suite includes simulated network-timeout, contract-revert, and wallet-rejection scenarios, each producing the correct classified error and UI outcome.

Activity

  1. devwums commented on Aug 19, 2026

    @devwums
    Contributor

    Please can i work on this issue. I will deliver beyond expectations. ETA is 1 day/ A well structured, modular and maintainable pr.

  2. grantfox-oss commented on Aug 19, 2026

    @grantfox-oss

    🦊 GrantFox — @devwums has been assigned to this issue as part of the Third Campaign campaign!

    Next steps:

    1. Open a Pull Request referencing this issue (e.g., Closes #142)
    2. Your PR will be reviewed by the LadderMine maintainers

    Good luck! Track your progress on GrantFox.

  3. added a commit that references this issue on Aug 22, 2026
    bda0489
  4. grantfox-oss commented on Aug 22, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!

    @devwums's PR #150 was approved and merged by @wumibals.

    🏆 @devwums: You earned 40 FoxPoints for this contribution! Your current tier: Builder (1,225 total points). Track your full progress on GrantFox.

    👏 Great work, @devwums! Keep contributing to LadderMine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions