> For the complete documentation index, see [llms.txt](https://hinkal-team.gitbook.io/hinkal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hinkal-team.gitbook.io/hinkal/hinkal-mobile-sdk/ios/guides/error-handling.md).

# Error Handling

Every operation that can fail surfaces an `NSError` through the `error:` out-parameter. The `call` helper from Getting Started rethrows it as a Swift `try`, so you handle failures with ordinary `do/catch`.

### Basic pattern

```swift
do {
    let txHash = try call {
        hinkal.deposit(chainId, tokenAddrsJSON: "[\"\(tokenAddress)\"]", amountsWeiJSON: "[\"\(wei)\"]",
                       preEstimateGas: true, returnTxData: false, error: &$0)
    }
} catch {
    print("Operation failed:", error.localizedDescription)
}
```

The SDK surfaces the underlying enclave/relayer reason as the error message. There is no typed error enum - inspect `error.localizedDescription`.

### Categorizing errors

Group failures so the UI can respond correctly. Since errors are message-based, match on the localized message:

```swift
enum ErrorCategory { case userAction, retryable, fatal }

func categorize(_ error: Error) -> ErrorCategory {
    let msg = error.localizedDescription.lowercased()
    if msg.contains("insufficient") || msg.contains("invalid") || msg.contains("decimals") {
        return .userAction   // the user must fix the input
    }
    if msg.contains("network") || msg.contains("timeout") || msg.contains("relayer") {
        return .retryable    // transient - offer a retry
    }
    return .fatal            // e.g. a failed handshake - reconnect the wallet
}
```

### Retry with backoff

Reads (`getTotalBalance`, quotes) are safe to retry. Wrap them with exponential backoff:

```swift
func withRetry<T>(_ attempts: Int = 3, _ op: () throws -> T) throws -> T {
    var lastError: Error?
    for i in 0..<attempts {
        do { return try op() }
        catch {
            lastError = error
            if categorize(error) != .retryable { throw error }
            Thread.sleep(forTimeInterval: pow(2.0, Double(i)) * 0.5)
        }
    }
    throw lastError!
}

let balance = try withRetry {
    try call { hinkal.getTotalBalance(chainId, userKeysSignature: signature,
        ethAddress: address, resetCache: false, useBlockedUtxos: false, error: &$0) }
}
```

{% hint style="warning" %}
Only retry **reads** automatically. For fund-moving calls, confirm the previous attempt did not land before retrying - re-read the balance with `resetCache: true` first.
{% endhint %}

### User-friendly messages

Map raw errors to strings you show the user:

```swift
func userMessage(_ error: Error) -> String {
    switch categorize(error) {
    case .userAction: return "Please check the amount and recipient, then try again."
    case .retryable:  return "Network issue. Please try again."
    case .fatal:      return "Session expired. Please reconnect your wallet."
    }
}
```

### Validate before submitting

Catch predictable failures up front, before spending:

```swift
var err: NSError?
let wei = MobileAmountToWei(chainId, tokenAddress, amountText, &err)
guard err == nil else {
    // amount has more decimals than the token supports
    return
}
guard MobileIsValidPrivateAddress(recipient) else {
    // invalid recipient
    return
}
```

### SwiftUI pattern

```swift
@MainActor
class OperationViewModel: ObservableObject {
    @Published var errorMessage: String?

    func deposit(_ hinkal: MobileHinkal, chainId: Int64, tokens: String, amounts: String) {
        do {
            _ = try call { hinkal.deposit(chainId, tokenAddrsJSON: tokens, amountsWeiJSON: amounts,
                                          preEstimateGas: true, returnTxData: false, error: &$0) }
            errorMessage = nil
        } catch {
            errorMessage = userMessage(error)
        }
    }
}
```

### Recover stuck funds

If a scheduled operation is interrupted, funds can end up in blocked UTXOs. Detect and recover:

```swift
let stuck = try call { hinkal.getStuckShieldedBalances(chainId, userKeysSignature: signature, ethAddress: address, error: &$0) }
let recoverTx = try call { hinkal.withdrawStuckUtxos(chainId, tokenAddr: tokenAddress, recipientAddress: recipientPublicAddress, error: &$0) }
```
