Building Ethereum Dapps on iOS with web3.swift

A new, open source library for Dapps

At Argent we’re committed to helping the Ethereum community flourish. We’re excited to share how we’ve built our own, open source, Swift library to interact with the Ethereum blockchain.

Web3.swift is designed to facilitate easy access to the Ethereum blockchain via a Swift native library, primarily for the creation of decentralized iOS apps.

It’s available on Github here. We’d love feedback and ideas on how to improve this library. We welcome all contributions and pull requests.

Why web3.swift?

We faced a problem when we started building Argent 12 months ago: there were no web3 libraries available in Swift (in fact, the closest solution came to running web3.js via WebKit). This mattered as the Argent mobile wallet is Ethereum-based and we interact directly with nodes using the standardised Ethereum JSON-RPC interface.

The solution? We built web3.swift. We’re delighted to share it with the wider community.

It builds on our active use of, and contributions to, the web3.js (Javascript) and web3j (Java) libraries. We’re seeking to learn from them what features and approaches work best on each platform.

Writing software in different languages often entails different approaches to data management and application logic, and as such we’ve structured web3.swift to make building decentralised apps (Dapps) on mobile as familiar as possible.

Main features

Our web3.swift library provides functionality for core interactions with the Ethereum blockchain. You can create accounts, call functions and perform transactions against smart contracts.

The primary benefit, however, is our dual support for working with contract code (ABI’s) via JSON or statically typed Swift structs. You may wish to use either route depending on the complexity of your product and team coding style.

We’ve made security a paramount concern with this library, especially given the trust users will place in it when handling private keys. For this reason we’re using only minimal dependencies (BigInt) and well proven cryptographic C libraries bundled with web3.swift.

We’ve also included support for working with ERC20 tokens (view the source code for an example of developing statically typed models) and Ethereum Name Service (via dynamic JSON).

Working with ABI contracts

To illustrate how to work with web3.swift, here’s a real world example on the Ropsten test network.

Let’s use this HelloWorld Kittens smart contract ABI example, which stores a list of kittens and their corresponding names. We can look up how many kittens have a certain name (howManyKittensCalled), or create a new kitten with a name (createKitten). The interface is described as:

howManyKittiesCalled (_name: Str) -> Int createKitten -> (_name: Str)

Firstly, we install via CocoaPods and import web3 to gain access to the main classes and functions. We create an instance of EthereumClient to connect to the blockchian, using any node which supports the JSON-RPC proxy. For testing we usually use Infura:

let client = EthereumClient(url: URL(string: “https://ropsten.infura.io/<YOUR API KEY>")!)

Please ignore the use of explicit bangs ( ! ) and lack of error handling. Whilst bad practice it makes providing example code easier!

Now, let’s set up the contract code. The above ABI example we’ve saved as Contract.json, and can use to create an instance of EthereumJSONContract:

let contractAddress = EthereumAddress(“0x93919cf5981de7c24a303e07f56f4eefa57454bb”) let abiUrl = Bundle.main.url(forResource: “Contract”, withExtension: “json”)! let contract = EthereumJSONContract(url: abiUrl, address: contractAddress)!

Here, we’re referencing the deployed contract on the Ropsten test network.

After this we can call the ABI methods easily to lookup data from the blockchain. To see how many kittens are called “fred”, we can do the following:

let transaction = try! contract.transaction(function: “howManyKittensCalled”, args: [“fred”]) client.eth_call(transaction) { (error, data) in

let numberOfKittens = try! ABIDecoder.decode(data!, to: UInt64.self)

print(“There’s \(numberOfKittens) kitten(s) called fred”)

}

We should find that the number of kittens returned is > 1 at the time of writing this. Note the use of ABIDecoder to decode the single value response to UInt64, as found in the original ABI.

Statically typed structs

If we’re building a more complex app, we may wish to create Swift structs to represent the ABI methods. This allows us to work with strictly typed data structures to create a native interface to our smart contract.

Here are example structs for the same function request & response:

struct HowManyKittens: ABIFunction {

static let name = “howManyKittens”

let gasPrice = BigUInt(0)

let gasLimit = BigUInt(0)

let contract: EthereumAddress

let from: EthereumAddress? let kittenName: String func encode(to encoder: ABIFunctionEncoder) throws {

try encoder.encode(kittenName)

}

} struct HowManyKittensResponse: ABIResponse {

static var types: [ABIType.Type] = [ UInt64.self ]

let numberOfKittens: UInt64 init?(values: [String]) throws {

numberOfKittens = try ABIDecoder.decode(values[0], to: UInt64.self)

}

}

As shown, we have modelled our function into two separate structs conforming to ABIFunction and ABIResponse. The serialisation and deserialisation of data will look familiar to anyone who has used the native Codable protocol.

To get the above code to work, you will have to import BigInt, which is a dependency of web3.swift. BigInt is a third-party library that provides support for 256-bit integers, which is not a foundation type in Swift.

We can now execute the function like so:

let function = HowManyKittens(contract: contractAddress, from: nil, kittenName: “fred”) function.call(withClient: client, responseType: HowManyKittensResponse.self) { (error, response) in

print(“There’s \(response!.numberOfKittens) kitten(s) called fred”)

}

The advantages of this approach are that all encoding and decoding are hidden in the library, so you can build up a repository of function calls to match your ABI in one place. The structs conforming to ABIFunction and ABIResponse are thus very easy to follow.

Manipulating the Blockchain

What about manipulating data on the blockchain? We now want to create a new kitten, but to do so we’ll need an Ethereum account (public/private key-pair) and load it with some Ether to pay the transaction (gas) cost.

This can be done via the above approach, setting gas price and gas limit, and calling function.execute with an EthereumAccount object. I’ll provide a full example in a follow-up post.

What’s next

There’s a couple of missing features we’ll be looking to add as we build out this library, such as batch transaction support & bloom filters for searching logs. We’re also looking to automatically generate Swift structs for a given ABI JSON interface via Sourcery. We hope the community will contribute to making this the main library for building iOS apps on Ethereum.

We’re working hard at Argent to create the very best products. We see web3.swift as one of our core components. It’s still in its early days but we hope it has a bright future ahead.