Ethereum

Ethereum

Swift

web3.swift, from the Argent team, is the go-to native library for reaching Ethereum from Apple platforms. It targets iOS 13+ and macOS 10.15+ — the same versions that introduced Combine and modern Swift concurrency — so it slots into a current SwiftUI app without back-deployment headaches. Add it through Swift Package Manager by pointing Xcode at https://github.com/argentlabs/web3.swift, and you get a Swift-native client for querying balances and signing transactions on chain 1, no Objective-C bridging or web view in sight.

web3.swift supports iOS 13+ and macOS 10.15+, covering the bulk of devices in the field. Add it in Xcode through Swift Package Manager using the argentlabs/web3.swift repository — no CocoaPods or manual framework wrangling needed to start calling Ethereum.

Web3.swift

Create the client with Web3(rpcURL: "https://ethereum.therpc.io/YOUR_API_KEY") and it's ready to talk to Ethereum. The API is built on Swift's async throws, so calls like web3.eth.getBalance both suspend and can fail — you await the result and the call site trys it. From SwiftUI that means calling these inside a Task { } or a .task modifier, never from a synchronous body, and letting the throws carry a network or address error up to where you can show it.

import Web3
import BigInt
class EthereumClient {
private let web3 = Web3(rpcURL: "https://ethereum.therpc.io/YOUR_API_KEY")
func getBalance(address: String) async throws -> Double {
let address = try EthereumAddress(hex: address, eip55: true)
let balance = try await web3.eth.getBalance(address: address)
return balance.converted(to: .ether).value
}
func sendTransaction(
from: EthereumPrivateKey,
to: String,
amount: Double
) async throws -> String {
let toAddress = try EthereumAddress(hex: to, eip55: true)
let amount = EthereumAmount(value: amount, unit: .ether)
let transaction = try await web3.eth.prepareTransaction(
to: toAddress,
value: amount,
from: from.address
)
let signed = try transaction.sign(with: from)
return try await web3.eth.send(transaction: signed)
}
}
  • GitHub: https://github.com/argentlabs/web3.swift
  • A fully native Swift implementation — no web view or JS bridge
  • iOS and macOS support from a single package
  • Built on async/await for clean SwiftUI integration
  • Type-safe contract interaction from Solidity ABIs
  • Wallet management — key handling and transaction signing
  • ENS resolution for .eth names

SwiftUI Integration

For reactive UI, put the client and the balance in an ObservableObject view model and hold it with @StateObject so SwiftUI keeps the instance alive across redraws. Mark the displayed balance @Published and the view repaints the moment a fresh ETH value lands. Kick the work off from a Button action or a .task modifier by opening a Task { await viewModel.updateBalance() } — that bridges the synchronous SwiftUI body to the async Ethereum call without blocking the main actor.

import SwiftUI
import Web3
struct WalletView: View {
@StateObject private var viewModel = WalletViewModel()
var body: some View {
VStack {
Text("Balance: \(viewModel.balance) ETH")
Button("Refresh") {
Task {
await viewModel.updateBalance()
}
}
}
}
}
class WalletViewModel: ObservableObject {
private let client = EthereumClient()
@Published var balance: Double = 0
func updateBalance() async {
do {
balance = try await client.getBalance(address: "YOUR_ADDRESS")
} catch {
print("Error: \(error)")
}
}
}

Smart Contract Integration

To work with a deployed contract on Ethereum, load its ABI JSON and the address into web3.eth.Contract, then invoke functions by name through .method(...).call(). A read like an ERC-20 balanceOf runs as a gas-free eth_call and returns the decoded value; the same pattern reaches any view function on a contract your app cares about, from a token to a DeFi pool.

struct Contract {
let web3 = Web3(rpcURL: "https://ethereum.therpc.io/YOUR_API_KEY")
let contractAddress: EthereumAddress
func callMethod() async throws -> String {
let contract = try await web3.eth.Contract(
json: contractABI,
address: contractAddress
)
return try await contract.method(
"methodName",
parameters: [param1, param2],
extraData: Data()
).call()
}
}

Error Handling

A typed EthereumError enum lets the UI react to causes, not just a generic failure: separate cases for an invalid address, insufficient funds, and a network error each map to a different message or recovery. Validate the address shape locally first — a simple 0x prefix check, or better, the EIP-55 checksum that EthereumAddress(hex:eip55:) enforces — before spending a round-trip on Ethereum. Catching a malformed address on-device saves the call and gives the user instant feedback instead of an opaque RPC rejection.

enum EthereumError: Error {
case invalidAddress
case insufficientFunds
case networkError(String)
}
extension EthereumClient {
func safeGetBalance(address: String) async throws -> Double {
guard address.hasPrefix("0x") else {
throw EthereumError.invalidAddress
}
do {
return try await getBalance(address: address)
} catch {
throw EthereumError.networkError(error.localizedDescription)
}
}
}

Ready to call this in production?

Free tier covers personal projects. Pay-as-you-go scales without a card.