WalletConnect Sign: How to connect a crypto wallet to iOS Swift app?
Published: 2023-10-18 14:18:48

## 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](#installation)
- [Configuration](#configuration)
- [Wallet connection](#wallet-connection)
- [Sign request](#sign-request)

<h2 id="installation">Installation</h2>

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
> 

<h2 id="configuration">Configuration</h2>

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. 

```swift
import WalletConnectNetworking

Networking.configure(
      projectId: "your-project-id",
      socketFactory: WalletConnectSocketFactory(),
      socketConnectionType: .automatic
)
```

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](https://github.com/daltoniam/Starscream) 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)

```swift
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 { }
```

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. 

```swift
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)
```

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!**

<h2 id="wallet-connection">Wallet connection</h2>

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](https://docs.walletconnect.com/2.0/specs/clients/sign/namespaces).

```swift
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
        }
    }
```

**Note:** here is defined `optionalNamespaces` and `sessionProperties` which are optional but MetaMask iOS wallet has an [issue](https://github.com/MetaMask/metamask-mobile/issues/6908) 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.

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

        UIApplication.shared.open(url)
}
```

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.

```swift
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)
```

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.

<h2 id="sign-request">Sign request</h2>

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.**

```swift
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)
}
```

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

```swift
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)
```

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](https://twitter.com/not_bad_dev)