WalletConnect Auth: how to connect a crypto wallet to iOS Swift DApp

not-bad-dev

not-bad-dev.eth

Posted on January 4, 2024

WalletConnect Auth: how to connect a crypto wallet to iOS Swift DApp

Written by not-bad-dev.eth

Intro

This is the second article in a series of WalletConnect usage on iOS. Link to previous one. Third one is coming soon.

What is Auth protocol? Wallet connect definition from documentation:

  • WalletConnect Auth is an authentication protocol that can be used to log-in blockchain wallets into apps. With a simple and lean interface, this API verifies wallet address ownership through a single signature request, realizing login in one action. It enables apps to set up a decentralized and passwordless onboarding flow.

So it’s one-step way to identify user’s wallet. It may seems like a better solution than Sign protocol but it doesn't allow handle connected sessions and call transactions on user’s wallet.

It would be better to think about possible use cases for your application before choosing wallet connection protocol.

Table of Contents

Installation

Add WalletConnect SDK to your app. Link to installation process is below. It may be added using SPM or CocoaPods.

https://github.com/WalletConnect/WalletConnectSwiftV2#installation

Configuration

To configure the WalletConnect SDK with your app information, you need to provide the project ID. You can register for the project ID by connecting here with your crypto wallet.

https://cloud.walletconnect.com/sign-in

Provide your projectId to Networking configuration. Fill the project metadata with your projects name and links. Such modules as DefaultSocketFactory and DefaultCryptoProvider are described below.

import Foundation
import WalletConnectNetworking
import WalletConnectPairing
import Auth

func configure() {
        Networking.configure(projectId: "your-project-id", socketFactory: DefaultSocketFactory())
        Auth.configure(crypto: DefaultCryptoProvider())

        let metadata = AppMetadata(
            name: "WalletConnectDemo",
            description: "WalletConnectDemo",
            url: "https://walletconnect.org",
            icons: []
        )

        Pair.configure(metadata: metadata)
    }
Enter fullscreen mode Exit fullscreen mode

Additionally, there is a WalletConnectSocketFactory class that you need to implement yourself. The WalletConnect SDK doesn’t handle socket management on its own.

The easiest way is to use Starscream SDK of 3.1.2 version which completley complies with their WebSocketConnecting protocol. (yep, it’s not new and may have some issues but it works)

import Foundation
import WalletConnectSign
import Starscream

struct WalletConnectSocketFactory: WebSocketFactory {
    func create(with url: URL) -> WebSocketConnecting {
                let socket = WalletConnectStarscreamSocket(url: url)
        let queue = DispatchQueue(label: "com.walletconnect.sdk.socket", attributes: .concurrent)
        socket.callbackQueue = queue
        return socket
    }
}

class WalletConnectStarscreamSocket: WebSocket, WebSocketConnecting { }
Enter fullscreen mode Exit fullscreen mode

It also possible to write your own sockets implementation using URLSessionWebSocketTask or any other library.

Unlike the previous article about Sign protocol here we have new DefaultCryptoProvider class which is additionally required for Auth protocol. It’s copy paste form WalletConnect demo example project. Don’t forget to add listed libraries and be careful! Here is used forked Web3.swift lib from WalletConnect repo. It’s also form the demo project.

https://github.com/WalletConnect/Web3.swift

import Auth
import CryptoSwift
import Foundation
import Web3

struct DefaultCryptoProvider: CryptoProvider {
    public func recoverPubKey(signature: EthereumSignature, message: Data) throws -> Data {
        let publicKey = try EthereumPublicKey(
            message: message.bytes,
            v: EthereumQuantity(quantity: BigUInt(signature.v)),
            r: EthereumQuantity(signature.r),
            s: EthereumQuantity(signature.s)
        )
        return Data(publicKey.rawPublicKey)
    }

    public func keccak256(_ data: Data) -> Data {
        let digest = SHA3(variant: .keccak256)
        let hash = digest.calculate(for: [UInt8](data))
        return Data(hash)
    }
}
Enter fullscreen mode Exit fullscreen mode

Connection

You need to define RequestParams wiht own data, create pairUri wich contains uniquie identifier and send request to WalletConnect. Request parameters correspond to CAIP-74 Cacao object.

func connect() async throws {
        let params = RequestParams(
            domain: "https://your-best-site.com",
            chainId: "eip155:1",
            nonce: "777",
            aud: "https://your-best-site.com",
            nbf: nil,
            exp: nil,
            statement: "Sign this message to verify your wallet and connect to Surreal.",
            requestId: nil,
            resources: nil
        )

        do {
            let pairUri = try await Pair.instance.create()
            self.pairUri = pairUri
            try await Auth.instance.request(params, topic: pairUri.topic)
        } catch {
            throw error
        }
    }
Enter fullscreen mode Exit fullscreen mode

Next step is to redirect to users wallet using provided URL structure where with /wc route and uri parameter which contains previously create pairUri. Note that it must be percent encoded so use only deeplinkUri property which follows this rule.
Note: here used Rainbow wallet link. You can replace it with your preferrable wallet’s link.

func redirectToWallet() async {
        guard let deeplinkUrl = pairUri?.deeplinkUri,
              /*
              here we use rainbow because metamask dosen't support
              auth protocol
               */
              let url = URL(string: "https://rnbwapp.com/wc?uri=\(deeplinkUrl)") else { return }

        await MainActor.run(body: {
            UIApplication.shared.open(url)
        })
    }
Enter fullscreen mode Exit fullscreen mode

And the last step is handle wallets response. You should subscribe onto authResponsePublisher before redirect to a wallet. Here we have simple result property where you can retrieve users wallet address and signature hash.

var publishers = Set<AnyCancellable>()

func setupObservers() {
        Auth.instance.authResponsePublisher.sink { [weak self] _, result in
            guard let self else { return }

            Task { @MainActor in
                switch result {
                case .success(let cacao):
                    do {
                        let address = try DIDPKH(did: cacao.p.iss).account.address
                        self.onWalletAuthorized?(address)
                    } catch {
                        print(error.localizedDescription)
                    }
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
        .store(in: &publishers)
    }
Enter fullscreen mode Exit fullscreen mode

That’s it! You’ve successfully connected and authenticated users wallet and can continue. Note that Auth protocol works fine for one-time user authentication but you won't be able to ask user for transactions signing. Sign protocol is better if you will need to send transactions periodically.

Good luck! Fell free to contact me and leave your feedback.

Follow me on X/Twitter

💖 💪 🙅 🚩
not-bad-dev
not-bad-dev.eth

Posted on January 4, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related