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 dedicatedNEARError enum. All async wallet operations throw typed errors that you can catch and handle appropriately.
NEARError Enum
All errors thrown byNEARWalletManager 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")
}
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()
}
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")
}
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
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
}
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)
}
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)
}
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)
)
}
}