DEV Community

not-bad-dev.eth
not-bad-dev.eth

Posted on • Updated on

WalletConnect Sign: How to connect a crypto wallet to iOS Swift app?

Why do you need it?

Whether you need to connect users wallet to your application and get their address, display their assets or do transactions, etc.

There exists two most popular ways for DApp (decentralized application, just your iOS application) connection like specific wallets’ SDK and WalletConnect protocol.

WalletConnect is a highly recommended and widely compatible method for connecting your DApp to a crypto wallet. By implementing WalletConnect SDK support, you can connect with various wallets that have integrated it, such as MetaMask, Rainbow, Trust, Zerion, and many others (which includes almost all existing wallets).

There are exist two ways to connect your DApp: Sign and Auth. Description from wallet connect:

  • WalletConnect Sign is a remote signer protocol to communicate securely between web3 wallets and dapps. The protocol establishes a remote pairing between two apps and/or devices using a Relay server to relay payloads. These payloads are symmetrically encrypted through a shared key between the two peers. The pairing is initiated by one peer displaying a QR Code or deep link with a standard WalletConnect URI and is established when the counter-party approves this pairing request.
  • 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.

The main difference between them is the pairing between a DApp and a wallet in the Sign protocol, which allows initiating transactions from the user's application. Also Auth protocol authenticate a user in one step when Sign require two separate steps (wallet connection and sign) as it always was.

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 clicking here with your crypto wallet.

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

And insert it in the projectId property in code below.

import WalletConnectNetworking

Networking.configure(
      projectId: "your-project-id",
      socketFactory: WalletConnectSocketFactory(),
      socketConnectionType: .automatic
)
Enter fullscreen mode Exit fullscreen mode

Additionally, there is a WalletConnectSocketFactory() class that you need to implement yourself. The WalletConnect SDK does not 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.

And the last step is to configure your app metadata.

import WalletConnectSign

let metadata = AppMetadata(
      name: "Your app name",
      description: "description",
      url: "your app url",
      icons: ["https://icon-url.com"],
      redirect: .init(native: "native-link://", universal: "universal-link.com")
)

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

I think it's quite straightforward. You need to provide your app name, description, URL, and icon. Additionally, you need to pass your universal link and deep link to allow wallets to redirect back to the app.

It’s important to configure the SDK before your call any methods in it!

Wallet connection

After completing all the configurations, you are ready to connect your wallet. First, define the chains and methods that your DApp requires. In example below I have defined the Ethereum chain with a chain ID of 1 ("eip155:1") and the "personal_sign" method. Pass these parameters to the Sign.instance.connect() method. For more information, refer to the WalletConnect documentation.

func connect() async throws {
        let requiredNamespaces: [String: ProposalNamespace] = [
            "eip155": ProposalNamespace(
                chains: [
                    Blockchain("eip155:1")!
                ],
                methods: [
                    "personal_sign"
                ], events: []
            )
        ]

        do {
            let pairUri = try await Pair.instance.create()
            try await Sign.instance.connect(requiredNamespaces: requiredNamespaces,
                                            // metamask doesn't handle a request without these dummy values
                                            optionalNamespaces: [:],
                                            topic: pairUri.topic)
        } catch {
            throw error
        }
    }
Enter fullscreen mode Exit fullscreen mode

Note: here is defined optionalNamespaces and sessionProperties which are optional but MetaMask iOS wallet has an issue that requires passing dummy values in order to handle this request.

Also save pairUri cause it will be required in the next step.

To redirect a user to a wallet app, you need to pass the pairUri as a parameter in the wallet link, as shown in the example below.

The wc/ path and uri parameter are standard for all wallets that support the WCv2 protocol. In this case, we are using the pairUri that was saved in the previous step, along with the Metamask deep link.

func redirectToWallet() {
        guard let deeplinkUrl = pairUri?.deeplinkUri,
            let url = URL(string: "metamask://wc?uri=\(deeplinkUrl)") else { return }

        UIApplication.shared.open(url)
}
Enter fullscreen mode Exit fullscreen mode

After that, the user will be redirected to MetaMask, where the connection request will appear. The response will be received in the Sign.instance.sessionSettlePublisher observer.

var publishers = Set<AnyCancellable>()

Sign.instance.sessionSettlePublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] session in
                let account = session.accounts.first
                self?.sessionTopic = session.topic
                self?.address = account?.address
            }.store(in: &publishers)
Enter fullscreen mode Exit fullscreen mode

If the request is successful, this observer will be triggered. It provides two main properties: wallet address and session topic. The session topic will be required in the next step to retrieve the session and send a Sign request.

Sign request

Next, you need to create a sign request and send it to the established session. Do not confuse the pairing topic with the session topic.

func sign() async {
        guard let chain = Blockchain("eip155:1"),
              let address, // previously saved
              let topic = sessionTopic else { return } // previously saved

        let method = "personal_sign"
        let requestParams = AnyCodable(["Verefication message", address])
        let request = Request(topic: topic,
                              method: method,
                              params: requestParams,
                              chainId: chain)

        try? await Sign.instance.request(params: request)
}
Enter fullscreen mode Exit fullscreen mode

And session response will be in Sign.instance.sessionResponsePublisher observer.

var publishers = Set<AnyCancellable>()

Sign.instance.sessionResponsePublisher
            .receive(on: DispatchQueue.main)
            .sink { response in
                switch response.result {
                case .response:
                    print("Success")
                case .error(let error):
                    print(error)
                }
            }.store(in: &publishers)
Enter fullscreen mode Exit fullscreen mode

In this straightforward example we don’t parse response but It may be important if you pass many different request.

That's it! You’ve successfully authorized user’s wallet, have its address and can continue. You can skip step with requesting sign from user’s wallet but you won’t be sure if user really own this wallet.

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

Follow me on X/Twitter

Top comments (0)