Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/frol/near-connect-ios/llms.txt

Use this file to discover all available pages before exploring further.

Overview

NEAR Connect iOS uses Swift’s native error handling with a dedicated NEARError enum. All async wallet operations throw typed errors that you can catch and handle appropriately.

NEARError Enum

All errors thrown by NEARWalletManager are of type NEARError:
public enum NEARError: LocalizedError {
    case operationInProgress
    case notSignedIn
    case invalidURL
    case invalidTransaction
    case noTransactionHash
    case walletError(String)
    case webViewNotReady
    case rpcError(String)
    
    public var errorDescription: String? {
        switch self {
        case .operationInProgress:
            return "Another wallet operation is in progress"
        case .notSignedIn:
            return "Not signed in. Please connect a wallet first."
        case .invalidURL:
            return "Failed to build wallet URL"
        case .invalidTransaction:
            return "Failed to encode transaction"
        case .noTransactionHash:
            return "Wallet did not return a transaction hash"
        case .walletError(let msg):
            return msg
        case .webViewNotReady:
            return "Wallet bridge is not ready yet"
        case .rpcError(let msg):
            return "RPC error: \(msg)"
        }
    }
}

Error Types

operationInProgress

Thrown when attempting to start a new wallet operation while another is in progress.
do {
    let result = try await walletManager.sendNEAR(
        to: "receiver.near",
        amountYocto: yocto
    )
} catch NEARError.operationInProgress {
    print("⏳ Please wait for the current operation to complete")
}
Prevention:
if !walletManager.isBusy {
    try await walletManager.sendNEAR(to: receiver, amountYocto: amount)
} else {
    print("Operation already in progress")
}

notSignedIn

Thrown when attempting an operation that requires a connected wallet.
do {
    let result = try await walletManager.sendNEAR(
        to: "receiver.near",
        amountYocto: yocto
    )
} catch NEARError.notSignedIn {
    print("❌ Please connect a wallet first")
    walletManager.connect()
}
Prevention:
if walletManager.isSignedIn {
    try await walletManager.sendNEAR(to: receiver, amountYocto: amount)
} else {
    print("No wallet connected")
    walletManager.connect()
}

webViewNotReady

Thrown when the JavaScript bridge hasn’t finished loading.
do {
    let result = try await walletManager.sendNEAR(
        to: "receiver.near",
        amountYocto: yocto
    )
} catch NEARError.webViewNotReady {
    print("⏳ Wallet bridge is initializing, please try again")
}
Prevention:
if walletManager.isBridgeReady {
    try await walletManager.sendNEAR(to: receiver, amountYocto: amount)
} else {
    print("Bridge not ready yet")
}

walletError

Thrown when the wallet returns an error (user rejection, insufficient balance, etc.).
do {
    let result = try await walletManager.sendNEAR(
        to: "receiver.near",
        amountYocto: yocto
    )
} catch NEARError.walletError(let message) {
    if message.contains("Cancelled") || message.contains("rejected") {
        print("ℹ️ User cancelled the transaction")
    } else if message.contains("insufficient") {
        print("❌ Insufficient balance")
    } else if message.contains("Smart contract panicked") {
        print("❌ Contract error: \(message)")
    } else {
        print("❌ Wallet error: \(message)")
    }
}

rpcError

Thrown when NEAR RPC calls fail.
do {
    let accountInfo = try await walletManager.viewAccount("alice.near")
} catch NEARError.rpcError(let message) {
    if message.contains("does not exist") {
        print("❌ Account not found")
    } else {
        print("❌ RPC error: \(message)")
    }
}

Complete Error Handling Pattern

Here’s how to handle all errors in a production app:
import SwiftUI
import NEARConnect

struct TransactionView: View {
    @EnvironmentObject var walletManager: NEARWalletManager
    @State private var receiverId = ""
    @State private var amount = "0.01"
    @State private var isProcessing = false
    @State private var showError = false
    @State private var errorMessage = ""
    @State private var errorTitle = "Error"
    
    var body: some View {
        Form {
            TextField("Receiver", text: $receiverId)
            TextField("Amount", text: $amount)
            
            Button(action: sendTransaction) {
                if isProcessing {
                    ProgressView()
                } else {
                    Text("Send")
                }
            }
            .disabled(isProcessing || !canSend)
        }
        .alert(errorTitle, isPresented: $showError) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(errorMessage)
        }
    }
    
    private var canSend: Bool {
        walletManager.isSignedIn && 
        walletManager.isBridgeReady &&
        !walletManager.isBusy &&
        !receiverId.isEmpty &&
        !amount.isEmpty
    }
    
    private func sendTransaction() {
        // Validate amount
        guard let yocto = NEARWalletManager.toYoctoNEAR(amount) else {
            showErrorAlert(title: "Invalid Amount", message: "Please enter a valid number")
            return
        }
        
        Task {
            isProcessing = true
            defer { isProcessing = false }
            
            do {
                let result = try await walletManager.sendNEAR(
                    to: receiverId,
                    amountYocto: yocto
                )
                
                // Success
                print("✅ Transaction sent: \(result.transactionHashes.first ?? "")")
                receiverId = ""
                amount = "0.01"
                
            } catch NEARError.notSignedIn {
                showErrorAlert(
                    title: "Not Connected",
                    message: "Please connect a wallet first"
                )
                walletManager.connect()
                
            } catch NEARError.operationInProgress {
                showErrorAlert(
                    title: "Busy",
                    message: "Please wait for the current operation to complete"
                )
                
            } catch NEARError.webViewNotReady {
                showErrorAlert(
                    title: "Not Ready",
                    message: "Wallet bridge is still loading, please try again in a moment"
                )
                
            } catch NEARError.walletError(let message) {
                handleWalletError(message)
                
            } catch NEARError.rpcError(let message) {
                showErrorAlert(
                    title: "Network Error",
                    message: "Failed to communicate with NEAR: \(message)"
                )
                
            } catch {
                showErrorAlert(
                    title: "Unexpected Error",
                    message: error.localizedDescription
                )
            }
        }
    }
    
    private func handleWalletError(_ message: String) {
        // User cancellation - not really an error
        if message.contains("Cancelled") || message.contains("rejected") {
            print("ℹ️ User cancelled transaction")
            return
        }
        
        // Insufficient balance
        if message.contains("insufficient") || message.contains("balance") {
            showErrorAlert(
                title: "Insufficient Balance",
                message: "You don't have enough NEAR to complete this transaction"
            )
            return
        }
        
        // Generic wallet error
        showErrorAlert(
            title: "Transaction Failed",
            message: message
        )
    }
    
    private func showErrorAlert(title: String, message: String) {
        errorTitle = title
        errorMessage = message
        showError = true
    }
}

Best Practices

1

Check state before operations

Always verify the manager’s state before starting operations:
func canPerformOperation() -> Bool {
    guard walletManager.isSignedIn else {
        print("Not signed in")
        return false
    }
    
    guard walletManager.isBridgeReady else {
        print("Bridge not ready")
        return false
    }
    
    guard !walletManager.isBusy else {
        print("Another operation in progress")
        return false
    }
    
    return true
}
2

Use defer for cleanup

Always reset processing state, even on error:
Task {
    isProcessing = true
    defer { isProcessing = false }  // Runs even if error thrown
    
    try await walletManager.sendNEAR(to: receiver, amountYocto: amount)
}
3

Distinguish user cancellation from errors

Don’t show error alerts for user cancellations:
catch NEARError.walletError(let message) {
    if message.contains("Cancelled") || message.contains("rejected") {
        // Silent - user chose to cancel
        return
    }
    // Show error for actual errors
    showError(message)
}
4

Provide actionable error messages

Guide users on how to fix errors:
catch NEARError.notSignedIn {
    showError("Please connect your wallet to continue")
    walletManager.connect()  // Automatically trigger connection
}

Monitoring Connection State

React to state changes in real-time:
struct StatusIndicator: View {
    @EnvironmentObject var walletManager: NEARWalletManager
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Connection status
            HStack {
                Circle()
                    .fill(walletManager.isSignedIn ? Color.green : Color.gray)
                    .frame(width: 8, height: 8)
                Text(walletManager.isSignedIn ? "Connected" : "Disconnected")
                    .font(.caption)
            }
            
            // Bridge status
            if !walletManager.isBridgeReady {
                HStack {
                    ProgressView()
                        .scaleEffect(0.7)
                    Text("Initializing...")
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            }
            
            // Operation in progress
            if walletManager.isBusy {
                HStack {
                    ProgressView()
                        .scaleEffect(0.7)
                    Text("Processing...")
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            }
            
            // Last error
            if let error = walletManager.lastError {
                HStack {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .foregroundColor(.orange)
                    Text(error)
                        .font(.caption)
                        .foregroundColor(.secondary)
                        .lineLimit(2)
                }
            }
        }
    }
}

Retry Logic

Implement retry for transient errors:
func sendWithRetry(
    to receiverId: String,
    amountYocto: String,
    maxRetries: Int = 3
) async throws -> TransactionResult {
    var lastError: Error?
    
    for attempt in 1...maxRetries {
        do {
            return try await walletManager.sendNEAR(
                to: receiverId,
                amountYocto: amountYocto
            )
        } catch NEARError.webViewNotReady {
            // Bridge still loading - wait and retry
            lastError = error
            if attempt < maxRetries {
                try await Task.sleep(nanoseconds: 1_000_000_000)  // 1 second
                continue
            }
        } catch NEARError.rpcError(let message) where message.contains("timeout") {
            // Network timeout - retry
            lastError = error
            if attempt < maxRetries {
                try await Task.sleep(nanoseconds: 2_000_000_000)  // 2 seconds
                continue
            }
        } catch {
            // Don't retry other errors
            throw error
        }
    }
    
    throw lastError ?? NEARError.walletError("Max retries exceeded")
}

Logging

Implement comprehensive logging for debugging:
enum LogLevel {
    case info, warning, error
}

func logOperation(
    _ operation: String,
    params: String = "",
    result: Result<String, Error>
) {
    let timestamp = ISO8601DateFormatter().string(from: Date())
    
    switch result {
    case .success(let output):
        print("[\(timestamp)] ✅ \(operation) succeeded")
        if !params.isEmpty {
            print("  Params: \(params)")
        }
        print("  Result: \(output)")
        
    case .failure(let error):
        print("[\(timestamp)] ❌ \(operation) failed")
        if !params.isEmpty {
            print("  Params: \(params)")
        }
        print("  Error: \(error.localizedDescription)")
    }
}

// Usage
Task {
    let params = "to: \(receiverId), amount: \(amount)"
    do {
        let result = try await walletManager.sendNEAR(
            to: receiverId,
            amountYocto: yocto
        )
        logOperation(
            "sendNEAR",
            params: params,
            result: .success(result.transactionHashes.first ?? "")
        )
    } catch {
        logOperation(
            "sendNEAR",
            params: params,
            result: .failure(error)
        )
    }
}

Next Steps