A modern, Swift-native Ethereum JSON-RPC client built with Swift Concurrency (async/await). Perfect for iOS, macOS, watchOS, and tvOS applications.
Add EthereumKit to your project:
dependencies: [
.package(url: "https://github.com/asyncswap/EthereumKit.git", from: "1.0.0")
]Or in Xcode:
- File → Add Package Dependencies
- Enter the repository URL
- Select version and add to your target
import EthereumKit
// Initialize the service
let service = EthereumService(rpcURL: "https://ethereum-rpc.publicnode.com")
// Get latest block number
let blockNumber = try await service.getLatestBlockNumber()
print("Latest block: \(blockNumber)")
// Get ETH balance
let balance = try await service.getBalanceInEther(address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb")
print("Balance: \(balance) ETH")
// Get USDC balance
let usdc = try await service.getUSDCBalance(
address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
usdcContractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
)
print("USDC Balance: \(usdc)")// Use pre-configured networks
let config = NetworkConfig.polygon
let service = EthereumService(rpcURL: config.rpcURL)
if let usdcAddress = config.usdcAddress {
let balance = try await service.getUSDCBalance(
address: "0x...",
usdcContractAddress: usdcAddress
)
}let client = JSONRPCClient(rpcURLString: "https://ethereum-rpc.publicnode.com")!
// Simple call
let blockNumber: String = try await client.call(method: "eth_blockNumber")
// Call with parameters
let balance: String = try await client.call(
method: "eth_getBalance",
params: [
.string("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"),
.string("latest")
]
)// Convert hex to integers
let blockNum = "0x1234".hexToInt // Returns 4660
// Convert integers to hex
let hex = 100.toHex // Returns "0x64"
// Convert Wei to Ether
let ether = "0xde0b6b3a7640000".weiToEther // Returns 1.0
// Pad addresses for ABI encoding
let padded = HexUtils.padAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb")Pre-configured networks available out of the box:
| Network | Chain ID | USDC Address |
|---|---|---|
| Ethereum Mainnet | 1 | ✅ |
| Polygon | 137 | ✅ |
| Arbitrum One | 42161 | ✅ |
| Base | 8453 | ✅ |
| Optimism | 10 | ✅ |
| Sepolia Testnet | 11155111 | ✅ |
getLatestBlockNumber()- Get current block numbergetChainId()- Get network chain ID
getBalance()- Get ETH balance (Wei as hex)getBalanceInEther()- Get ETH balance (as Double)getBalanceInWei()- Get ETH balance (as UInt64)
getERC20Balance()- Get token balance (raw hex)getERC20BalanceFormatted()- Get token balance with decimalsgetUSDCBalance()- Convenience method for USDC
getGasPrice()- Get current gas pricegetGasPriceInGwei()- Get gas price in GweiestimateGas()- Estimate gas for transactiongetTransactionCount()- Get nonce for addresssendTransaction()- Send signed transactiongetTransactionReceipt()- Get transaction receipt
All standard Ethereum JSON-RPC methods are available:
eth_blockNumbereth_getBalanceeth_calleth_sendRawTransactioneth_estimateGas- And more...
do {
let balance = try await service.getBalanceInEther(address: "0x...")
print("Balance: \(balance)")
} catch let error as JSONRPCError {
// JSON-RPC error from server
print("RPC Error [\(error.code)]: \(error.message)")
} catch let error as EthereumServiceError {
// Service-level error
print("Service Error: \(error.localizedDescription)")
} catch {
// Network or other errors
print("Error: \(error)")
}- JSONRPCClient (Actor) - Thread-safe, low-level RPC client
- EthereumService (Class) - High-level API with convenience methods
- HexUtils - Utilities for hex/decimal conversions
- NetworkConfig - Pre-configured network settings
JSONRPCClient is an actor, ensuring all RPC calls are thread-safe and can be called from any context:
Task {
let balance = try await client.eth_getBalance(address: "0x...")
}
Task.detached {
let gasPrice = try await client.eth_gasPrice()
}let addresses = ["0xAddress1", "0xAddress2", "0xAddress3"]
await withTaskGroup(of: (String, Double).self) { group in
for address in addresses {
group.addTask {
let balance = try await service.getBalanceInEther(address: address)
return (address, balance)
}
}
for await (address, balance) in group {
print("\(address): \(balance) ETH")
}
}// Example: Get ERC-20 token symbol
func getTokenSymbol(contractAddress: String) async throws -> String {
let functionSelector = "0x95d89b41" // symbol()
let result = try await client.eth_call(to: contractAddress, data: functionSelector)
// Parse result...
return result
}Run tests using Swift Testing:
swift test
# or
RUN_NETWORK_TESTS=1 swift testOr in Xcode: Cmd + U
- iOS 16.0+ / macOS 13.0+ / watchOS 9.0+ / tvOS 16.0+
- Swift 5.9+
- Xcode 15.0+
MIT License - feel free to use in your projects!
Contributions welcome! Please feel free to submit a Pull Request.
- Transaction signing support
- ENS resolution
- WebSocket support for subscriptions
- Batch RPC calls
- Gas price estimation strategies
- Transaction status monitoring
Built with ❤️ for the iOS Ethereum community.