Open-source project
argentlabs/web3.swift avatar
argentlabs/web3.swift

web3.swift: an Ethereum API for Swift, with static types for contract calls

Ethereum Swift API with support for smart contracts, ENS & ERC20

718 stars186 forksSwiftMIT

At a glance

What is it?
web3.swift is Argent's Swift client for Ethereum, covering JSON-RPC over HTTP and WebSocket, ENS, ERC20 and ERC721, and ZKSync Era. The distinguishing part is that contract calls are described as Swift structs with encodable properties, so a call site is type checked rather than assembled from a string.
Who is it for?
Adopt web3.swift if you are building a Swift iOS or server application that talks to Ethereum and you want contract calls expressed as typed structs rather than hand-built data fields, since the ABIFunction encoding path is the part of the design that pays for itself.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 65 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Swift client for Ethereum, from a wallet company

web3.swift is an Ethereum API for Swift, with support for smart contracts, ENS and ERC20. It is built by the Argent team, whose repository description frames the feature set.

The licence is MIT, the primary language is Swift, the default branch is develop, and the release history shows 1.5.1 in March 2023, 1.6.0 on 2023-03-24 and 1.6.1 on 2023-12-13. The last push was on 2026-08-19, so the branch is still being touched, but the tagged releases are nearly three years old.

That gap is the first thing to weigh. For a library whose job is talking to a chain whose own protocol keeps moving, a release line that stopped in 2023 is a question you should answer before you build a product on it.

The repository layout shows a fairly conventional Swift package. There is a Package.swift at the top with a Package.resolved beside it, a web3swift/ directory holding the library, a web3sTests/ directory for the tests, a scripts/ directory, a docker-run.sh, and a.swiftpm/ directory. The presence of a committed Package.resolved is a good sign, because it means the dependency versions are pinned and you can see them.

The CI badge is there, and the README notes that the tests hit public RPC endpoints, which tells you the test suite is an integration suite rather than a set of mocks.

Installing it, and the CocoaPods situation

The recommended installation route is Swift Package Manager. You use Xcode to add it to the project through File and then Swift Packages, or you add the package line to your Package.swift file:

swift
.package(url: "https://github.com/argentlabs/web3.swift", from: "1.1.0")

The CocoaPods route is explicitly retired, and the README explains why in more detail than you would expect from a deprecation note.

The last published pod is 1.6.0 from March 2023. It stays installable but it will not be updated, because CocoaPods trunk stops accepting new podspecs in December 2026, and several of the pod's dependencies were frozen years earlier at versions well behind what this package uses. The instruction is to migrate to Swift Package Manager.

That is a concrete deadline rather than a vague deprecation, and the reasoning is the useful part. The pod was not simply abandoned, it was frozen with stale transitive dependencies, which is a specific failure mode that the Swift Package Manager route avoids because it resolves from the same manifest.

Note that the version floor in the Package.swift snippet is 1.1.0 while the latest release is 1.6.1, so the manifest example is not pinned to the newest version.

The Docker file in the repository root suggests there is a containerised way to run something, likely the test suite, which is consistent with the note that the tests need a network and a funded account for one of them.

The account, the key storage, and the two clients

The getting started flow has three parts, and the first one carries an important warning.

You create an instance of EthereumAccount with an EthereumKeyStorage provider, which wraps your key for the library to use. The warning is that the authors recommend you implement your own KeyStorage provider instead of relying on the provided EthereumKeyLocalStorage class, which is there as an example of conformity to the EthereumSingleKeyStorageProtocol.

swift
import web3

// This is just an example. EthereumKeyLocalStorage should not be used in production code
let keyStorage = EthereumKeyLocalStorage()
let account = try? EthereumAccount.create(replacing: keyStorage, keystorePassword: "MY_PASSWORD")

The comment in the example is the documentation. A local storage class for a private key is fine in a sample and wrong in production, and the library gives you a protocol so you can supply something better, such as a keychain or a hardware-backed store.

Then you create a client. There are two, and the difference is the transport. The HTTP client:

swift
guard let clientUrl = URL(string: "https://an-infura-or-similar-url.com/123") else { return }
let client = EthereumHttpClient(url: clientUrl)

and the WebSocket client, whose example URL is a Sepolia endpoint:

swift
guard let clientUrl = URL(string: "wss://sepolia.infura.io/ws/v3//123") else { return }
let client = EthereumWebSocketClient(url: clientUrl)

The client methods come in two flavours, which is the second thing to notice. There is a completion handler form:

swift
client.eth_gasPrice { (error, currentPrice) in
    print("The current gas price is \(currentPrice)")
}

and an async and await form for the same call:

swift
let gasPrice = try await client.eth_gasPrice()

So the library was updated to modern Swift concurrency without dropping the older callback style, which is the right call for a library with existing users on older codebases.

Contract calls as Swift structs, and the encoding step

This is the part of the library that is worth understanding, because it is the reason to use it rather than a generic JSON-RPC client.

Given a smart contract function ABI such as an ERC20 transfer:

javascript
function transfer(address recipient, uint256 amount) public returns (bool)

you define an ABIFunction with corresponding encodable Swift types, and the example is a struct with the function name, optional gas price and gas limit, the contract address, an optional from address, a to address and a value:

swift
public struct Transfer: ABIFunction {
    public static let name = "transfer"
    public let gasPrice: BigUInt? = nil
    public let gasLimit: BigUInt? = nil
    public var contract: EthereumAddress
    public let from: EthereumAddress?

    public let to: EthereumAddress
    public let value: BigUInt

    public init(contract: EthereumAddress,
                from: EthereumAddress? = nil,
                to: EthereumAddress,
                value: BigUInt) {
        self.contract = contract
        self.from = from
        self.to = to
        self.value = value
    }

    public func encode(to encoder: ABIFunctionEncoder) throws {
        try encoder.encode(to)
        try encoder.encode(value)
    }
}

The encode method is the point. It writes the arguments in ABI order into an encoder, so the field order of the struct and the order of the encoding calls are two things you have to keep in step, and the compiler checks the types but not the correspondence.

Using the function then produces a transaction you send with the client:

swift
let function = transfer(contract: "0xtokenaddress", from: "0xfrom", to: "0xto", value: 100)
let transaction = try function.transaction()

client.eth_sendRawTransaction(transaction, withAccount: account) { (error, txHash) in
    print("TX Hash: \(txHash)")
}

with the async form being a single await of the same call. The transaction method is what turns the encoded arguments into a signed transaction for the account.

Types, token helpers, and what is missing

The library provides a set of types and helpers, and they are worth listing because they show where the effort went.

EthereumAddress represents an address, with checksum support. BigInt and BigUInt come from the attaswift BigInt library, since a 256-bit value does not fit in a Swift integer. EthereumBlock represents a block, either by number or by one of the RPC-specific definitions like Earliest or Latest. EthereumTransaction wraps a transaction, and the encoders and decoders work with it to generate proper data fields.

The conversions are namespaced under a type extension, so converting an Int to a hex string is a property on the value:

swift
let gwei = 100
let hexgwei = gwei.web3.hexString

The supported conversions are a short list: from a hex byte string to Data, to Int, or to BigUInt, and from String, Int, BigUInt or Data to a hex byte string, plus adding and removing the hex prefix when working with a String.

The token support is two structs. ERC20 lets you get the token symbol, name and decimals, get a token balance, and retrieve Transfer events. ERC721 does the same for non-fungible tokens and additionally decodes standard JSON for NFT metadata, with the honest caveat that some smart contracts are not fully compliant with the standard.

There is also ZKSync Era support, imported as web3_zksync, with a ZKSyncTransaction type and a ZKSyncClient that has a similar API to the EthereumClient.

What is not here is code generation. The README says explicitly that it does not support it, because doing it properly is a bigger project and should possibly live outside the repository, and it points at a third party project, swiftabigen, as something to try. So the typed struct you saw earlier is hand written, and for a contract with twenty functions that is a lot of typing, which is the main practical cost of this approach.

Where web3.swift is the wrong choice

The first wrong case is a project with a large contract surface. The library has no code generation, so every function you call is a struct you write by hand, and a contract with many entry points becomes a large file of boilerplate that nobody wants to maintain. If your interaction is mostly calling a handful of well known functions, the effort is fine. If it is a wide contract, the missing generator is a real cost.

The second is a chain other than Ethereum mainnet or the common testnets. There is ZKSync Era support, which shows the design allows for other chains, but the client methods are Ethereum JSON-RPC methods, and a chain with a different RPC surface or a different signing scheme is a bigger job than the library has taken on.

The third is a project on CocoaPods, which the README has closed off for a concrete reason and with a deadline.

The fourth is a team that needs an actively maintained client. The tagged releases stop in December 2023. A branch that is still being pushed to is not the same as a maintained release line, and for a library that wraps a protocol which changes, the difference shows up as an issue with a chain upgrade or a new RPC method.

The alternative worth weighing is calling the JSON-RPC endpoint yourself with a plain HTTP client and a small ABI encoder, which is a weekend of work for a simple read-only integration, and which you then own. That trade makes sense if you only need to read balances and send transactions, and it stops making sense the moment you want typed contract calls, a key storage abstraction and the token and event helpers, which is exactly the set of things this library provides.

Editorial conclusion

Adopt web3.swift if you are building a Swift iOS or server application that talks to Ethereum and you want contract calls expressed as typed structs rather than hand-built data fields, since the ABIFunction encoding path is the part of the design that pays for itself. Keep away if you need code generation from a contract ABI, because the project does not support it and points elsewhere, and be aware the release line stopped at 1.6.1 in December 2023 with the last push on 2026-08-19. Before you depend on it, check that the RPC provider and the chain you target are covered, and migrate off CocoaPods, since the README says it is no longer supported and trunk stops accepting new podspecs in December 2026.

Frequently asked questions

How do I install web3.swift?

Through Swift Package Manager, either by adding it in Xcode under File and then Swift Packages, or by adding .package(url: "https://github.com/argentlabs/web3.swift", from: "1.1.0") to your Package.swift. CocoaPods is no longer supported, the last pod is 1.6.0 from March 2023, and trunk stops accepting new podspecs in December 2026.

How does web3.swift handle smart contract calls?

You define a struct conforming to ABIFunction with typed properties, and an encode method that writes the arguments into an ABIFunctionEncoder in ABI order. The function's transaction method then produces a transaction you send with the client. The library does not generate these structs from a contract ABI, so they are written by hand.

Is it safe to use the EthereumKeyLocalStorage class in web3.swift?

The README recommends implementing your own EthereumKeyStorage provider, and says the provided EthereumKeyLocalStorage class is only an example of conformity to the EthereumSingleKeyStorageProtocol, and the code comment says it should not be used in production code.

How do I run the web3.swift tests?

Run swift test, which works on a fresh clone with no setup because the tests hit public RPC endpoints and sign with a throwaway key committed in TestConfig.swift. One test spends Ether and needs the funded account whose key is a repository secret, so it skips unless that key is present, which you can supply by exporting TESTS_PRIVATEKEY.

Official sources

  1. argentlabs/web3.swift on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/argentlabs-web3-swift.svg)](https://hysenlabs.com/projects/argentlabs-web3-swift)

Was this useful?