Skip to main content
Not every transaction completes successfully, and that’s expected. Users cancel actions, wallet policies reject requests, contracts throw errors, and networks experience transient issues. Robust failure handling isn’t an edge case concern — it’s a core part of building a trustworthy transaction experience. This page covers the common failure scenarios you’ll encounter, the error codes you’ll receive, and the patterns you should use to surface them clearly to users.
A resolved promise from requestTransaction() does not guarantee success. Always check result.success before updating any application state. If result.success is false, no on-chain state changed — the user’s wallet, balances, and positions are exactly as they were before the call.

Common failure scenarios

User Rejection

The user saw the approval screen and chose not to approve. This is expected, normal user behavior — not an error. Reset your UI gracefully without showing an error message.

Policy Violation

The user approved, but the smart wallet’s policy blocked execution — for example, a spending limit was exceeded or the contract isn’t on the wallet’s allowlist.

Contract Execution Error

The transaction was authorized and submitted, but the Soroban contract panicked or returned an error during execution. No state changes were applied.

Network / Submission Error

The transaction failed to reach or be processed by the network due to connectivity issues, RPC timeouts, or ledger congestion. These are often transient — a retry may succeed.

Fee Failure

The wallet’s fee evaluation returned CannotProceed — the fee asset isn’t supported, the calculated fee exceeds max_total_fee, or the deferred fee balance has reached its cap.

Authentication / Session Error

The user’s session token has expired or is invalid. The user needs to re-authenticate before attempting any transaction.

Error codes

SocketFi throws structured errors with a code property that lets you handle specific failure scenarios programmatically:

Complete error handling example

The following example demonstrates handling multiple error types from a single requestTransaction() call:

UI patterns for failures

Good failure UX shares three properties: it explains what happened in plain language, it offers a clear path forward, and it preserves the user’s context so they don’t have to start over. Explain, don’t expose. Translate error codes into user-facing language. “Your wallet rejected this transaction due to a policy limit” is more useful than “TRANSACTION_REJECTED.” Reserve raw error codes for developer logs. Distinguish rejection from failure. A user who cancels the approval screen made an intentional choice. Don’t show them a red error banner — just reset the form to its ready state. Reserve error messaging for genuine failures. Offer retry when appropriate. Network errors and transient fee issues are often retryable. Contract errors and policy violations usually aren’t — the underlying condition needs to change before a retry will succeed. Only show a retry button when there’s a reasonable expectation it will help. Preserve form state. If a transfer fails because the popup was blocked, the user shouldn’t have to re-enter the recipient address and amount. Keep form values intact when transitioning to an error state.

Handling failures at each lifecycle stage

Different failure types occur at different points in the transaction lifecycle. Understanding where a failure originates helps you craft the right response:
When result.success is false, you can’t always determine the exact cause from the TransactionResult alone — use try/catch to capture thrown errors with specific code values, which provide more precise failure information for your error handling logic.