Skip to main content

Integrate Web3Auth with the Songbird Blockchain in iOS/Swift Applications

While using the Web3Auth iOS SDK, you get the private key within the user scope after successful authorization. This private key can be used to retrieve the user's address, and interact with Songbird to make any blockchain calls. We have highlighted a few here for getting you started quickly on that.

Prerequisites

This doc assumes you have already setup your project in Web3Auth Dashboard, and have integrated Web3Auth in your iOS app. If you haven't done that yet, you can learn how to integrate Web3Auth in your iOS app.

Installation

To interact with the Ethereum compatible blockchains in Swift, you can use any EIP1193 compatible package. Here, we're using web3swift to demonstrate how to make blockchain calls using it with Web3Auth.

To install the web3swift package, you have two options. You can either use Swift Package Manager or CocoaPods.

  1. In Xcode, with your app project open, navigate to File > Add Packages.

  2. When prompted, add the single-factor-auth-swift iOS SDK repository:

    https://github.com/argentlabs/web3.swift
  3. When finished, Xcode will automatically begin resolving and downloading your dependencies in the background.

Initialize

To interact with the blockchain, initialize the EthereumHttpClient with the RPC URL, and chain id of the network you want to connect to. To get the public RPC URL, and other details as chain id, you can checkout ChainList.

import web3

let client = EthereumHttpClient(
// Please avoid using public RPC URL in production, use services
// like Infura, Quicknode, etc.
url: URL.init(string: rpcUrl)!,
// Replace with the chain id of the network you want to connect to
network: .custom(chainId)
)

Get Account

To retrieve the user's Ethereum address, you can use the EthereumAccount class. This account can also be used to sign transactions and messages.

The package doesn't provides any direct way to consume the the private key and create an account. Hence, we'll create an extension for the Web3AuthState extending the EthereumSingleKeyStorageProtocol to retrieve the private key and create an account.

import web3
import Web3Auth

extension Web3AuthState: EthereumSingleKeyStorageProtocol {
public func storePrivateKey(key: Data) throws {

}

public func loadPrivateKey() throws -> Data {
guard let data = self.privKey?.web3.hexData else {
throw PlaygroundError.decodingError
}

return data
}
}

Once we have created the extension, we can use the Web3AuthState to create an EthereumAccount. Please note, that this assumes that the user has already logged in and the state is available.

import web3

// Use your existing Web3Auth instance
let web3authState = web3Auth.state!

let account = try EthereumAccount(keyStorage: web3authState)
let address = account.address

Get Balance

To retrieve the balance of an Ethereum address, you can use the EthereumClient.eth_getBalance method. The response we get from this method is in Wei, so we need to convert it to Ether. The get the ether value, you can divide the wei value with 1 / 10^18.

import web3

let etherInWei = pow(Double(10), 18)

/// Convert Wei(BInt) unit to Ether(Decimal) unit
public func toEther(wei: Wei) -> Ether {
guard let decimalWei = Double(wei.description) else {
return 0
}
return decimalWei / etherInWei
}

let balanceResponse = try await client.eth_getBalance(
// Use the address from previous step
address: address,
block: .Latest
)

let balance = toEther(wei: balanceResponse)

Sign a message

To sign the message, you can use the EthereumAccount.sign method. The sign method takes in the data of the message, and returns the signature.

import web3

let message = "Hello World"
// Use the account from previous step
guard let data = message.data(using: .ascii) else {
// throw error
}

let signature = try ethereumAccount.signMessage(message: data)

Send Transaction

For signing and sending the transaction, we'll create helper methods to get the nonce, gas price, estimate the gas and send the transaction. The nonce is the number of transactions sent from the address. The gas price is the price of the gas in Wei. The gas limit is the maximum amount of gas that can be used to send the transaction. Apart form that, we'll also create a helper method to convert the amount to Wei.

Once the transaction object is created, we'll estimate the gas and create a new transaction object with the gas limit. Finally, we'll send the transaction using EthereumHttpClient.eth_sendRawTransaction method and get the hash.

import web3

private let etherInWei = pow(Double(10), 18)

private func getNonce() async throws -> Int {
let nonce = try await client.eth_getTransactionCount(address: account.address, block: .Latest)
return nonce
}

private func getGasPrice() async throws -> BigUInt {
return try await client.eth_gasPrice()
}

private func estimateGas(ethereumTransaction: EthereumTransaction) async throws -> BigUInt {
return try await client.eth_estimateGas(ethereumTransaction)
}

public func toWei(ether: Ether) -> Wei {
let wei = Wei(ether * etherInWei)
return wei
}

let nonce = try await getNonce()
let gasPrice = try await getGasPrice()

// Convert the amount to Wei
let value = toWei(ether: Ether(floatLiteral: 0.001))

let ethereumTransaction = EthereumTransaction(
// Use the account from previous step
from: account.address,
to: EthereumAddress.init(stringLiteral: "RECIPIENT_ADDRESS_IN_HEX_FORMAT"),
value: value,
// Empty data, 0x
data: Data(),
nonce: nonce,
gasPrice: gasPrice,
gasLimit: nil,
// Use the chain id from previous step
chainId: CHAIN_ID_IN_INTEGER
)

// Estimate the gas
let gasLimit = try await estimateGas(ethereumTransaction: ethereumTransaction)

let transaction = EthereumTransaction(
// Use the account from previous step
from: account.address,
to: EthereumAddress.init(stringLiteral: "RECIPIENT_ADDRESS_IN_HEX_FORMAT"),
value: value,
// Empty data, 0x
data: Data(),
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
// Use the chain id from previous step
chainId: CHAIN_ID_IN_INTEGER
)

// Use the account from previous step
let hash = try await client.eth_sendRawTransaction(transaction, withAccount: account)