> 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/android/guides/error-handling.md).

# Error Handling

Native SDK methods declare `throws Exception`. Handle failures with an ordinary `try/catch`.

### Basic pattern

```kotlin
try {
    val txHash = hinkal.deposit(chainId, "[\"$tokenAddress\"]", "[\"$wei\"]", true, false)
} catch (e: Exception) {
    Log.e("Hinkal", "Operation failed: ${e.message}")
}
```

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

### Categorizing errors

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

```kotlin
enum class ErrorCategory { USER_ACTION, RETRYABLE, FATAL }

fun categorize(e: Exception): ErrorCategory {
    val msg = e.message?.lowercase() ?: ""
    return when {
        msg.contains("insufficient") || msg.contains("invalid") || msg.contains("decimals") -> ErrorCategory.USER_ACTION
        msg.contains("network") || msg.contains("timeout") || msg.contains("relayer") -> ErrorCategory.RETRYABLE
        else -> ErrorCategory.FATAL
    }
}
```

### Retry with backoff

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

```kotlin
fun <T> withRetry(attempts: Int = 3, op: () -> T): T {
    var lastError: Exception? = null
    repeat(attempts) { i ->
        try { return op() }
        catch (e: Exception) {
            lastError = e
            if (categorize(e) != ErrorCategory.RETRYABLE) throw e
            Thread.sleep((Math.pow(2.0, i.toDouble()) * 500).toLong())
        }
    }
    throw lastError!!
}

val balance = withRetry {
    hinkal.getTotalBalance(chainId, signature, address, false, false)
}
```

{% 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:

```kotlin
fun userMessage(e: Exception): String = when (categorize(e)) {
    ErrorCategory.USER_ACTION -> "Please check the amount and recipient, then try again."
    ErrorCategory.RETRYABLE   -> "Network issue. Please try again."
    ErrorCategory.FATAL       -> "Session expired. Please reconnect your wallet."
}
```

### Validate before submitting

Catch predictable failures up front, before spending:

```kotlin
val wei = try {
    Mobile.amountToWei(chainId, tokenAddress, amountText)
} catch (e: Exception) {
    // amount has more decimals than the token supports
    return
}
if (!Mobile.isValidPrivateAddress(recipient)) {
    // invalid recipient
    return
}
```

### Jetpack Compose pattern

```kotlin
class OperationViewModel : ViewModel() {
    private val _errorMessage = MutableStateFlow<String?>(null)
    val errorMessage: StateFlow<String?> = _errorMessage

    fun deposit(hinkal: Hinkal, chainId: Long, tokens: String, amounts: String) = viewModelScope.launch(Dispatchers.IO) {
        try {
            hinkal.deposit(chainId, tokens, amounts, true, false)
            _errorMessage.value = null
        } catch (e: Exception) {
            _errorMessage.value = userMessage(e)
        }
    }
}
```

### Recover stuck funds

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

```kotlin
val stuck = hinkal.getStuckShieldedBalances(chainId, signature, address)
val recoverTx = hinkal.withdrawStuckUtxos(chainId, tokenAddress, recipientPublicAddress)
```
