WalletConnect Sign v2.0: Beginner’s Guide for Android Developers

Jakub
WalletConnect
Published in
7 min readJun 1, 2022

Note: If you are a wallet developer and looking to integrate WalletConnect, please use the latest version of our tooling — the Web3Wallet SDK. For more information, you can refer to our docs.

The following guide is designed for Android developers looking forward to integrating the WalletConnect v2.0 Sign SDK for Android devices.

Before diving into the protocol details, I strongly recommend getting familiar with the following links:

The glossary will help you understand the foundations of the protocol, how we named components and the differences between the key parties.

The technical specification docs is a place where our team document and discuss all new features.

In short words, WalletConnect v2.0 provides secure remote signing communication between a Dapp and wallet, which controls the authentication of the user’s private keys.

Having that in mind, now you can jump into the Kotlin implementation of the WalletConnect v2.0 Sign SDK.

Getting Started

First, to start the Kotlin SDK integration you need to add the following repository and dependency to your project. Add MavenCentral to the root build.gradle file:

allprojects {
repositories {
maven { url "https://repo1.maven.org/maven2/" }
maven { url "https://jitpack.io" }
}
}

When it’s done the latest dependency version should be added to the gradle along with the android-core dependency file in the app directory:

implementation("com.walletconnect:android-core:release_version")implementation("com.walletconnect:sign:release_version")

What is more, the SDK is designed for the Android application targeting at least Android SDK 23 and Java 11.

The protocol’s communication is designed to have two peer clients, which are connected to each other when out-of-band data that is shared to define the relay architecture and cryptographic keys to encrypt payloads shared between them.

We can distinguish the relationship between the two clients as a Responder and a Proposer. The Proposer is usually the blockchain application that proposes sessions and provides the JSON-RPC signing requests to the Responder, which does the signing with the keys. On the other hand, the Responder usually acts as the blockchain wallet, which approves sessions, exposes accounts(CAPI10 compatible) and more to the Proposer, and is responsible for signing.

Initialization

The Kotlin SDK for Android, provides both a Wallet and Dapp implementation. To initialize the SignClient, create a Sign.Params.Init object in the Android Application class. The Init object will need the CoreClient. To initialize CoreClient beforehand call CoreClient.initialize in the onCreate method is the Android Application class with following params relayServerUrl, connectionType, application class, and the app's AppMetaData.

We allow developers to take full control of the WebSocket connection by specifying the ConnectionType.MANUAL and ConnectionType.AUTOMATIC connection type. The default type(ConnectionType.AUTOMATIC) disconnects the web-socket connection when app enters the background and reconnects when app is brought back to the foreground. The ConnectionType.MANUAL allows developers to control when to open the WebSocket connection and when to close it. This can be done using CoreClient.Relay.connect() and CoreClient.Relay.close(), respectively

val projectId = "" // Get Project ID at https://cloud.walletconnect.com/
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.AUTOMATIC or ConnectionType.MANUAL
val appMetaData = Core.Model.AppMetaData(
name = "Wallet Name",
description = "Wallet Description",
url = "Wallet Url",
icons = /*list of icon url strings*/,
redirect = "kotlin-wallet-wc:/request" // Custom Redirect URI
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData)val init = Sign.Params.Init(coreClient = CoreClient)SignClient.initalize(init) { error ->
// Error will be thrown if there's an isssue during initalization
}

The next step is to set up either the SignClient.WalletDelegate or SignClient.DappDelegate (according to the developer’s use case) to be able to expose asynchronous updates sent from another peer.

SignClient.WalletDelegate:

val walletDelegate = object : SignClient.WalletDelegate {
fun onSessionProposal(Sign.Model.SessionProposal)
fun onSessionRequest(Sign.Model.SessionRequest)
fun onSessionDelete(Sign.Model.DeletedSession)
fun onSessionSettleResponse(Sign.Model.SettledSessionResponse)
fun onSessionUpdateResponse(Sign.Model.SessionUpdateResponse)
fun onConnectionStateChange(state: Sign.Model.ConnectionState)
fun onError(error: Sign.Model.Error) }
SignClient.setWalletDelegate(walletDelegate)

SignClient.DappDelegate:

val dappDelegate = object : SignClient.DappDelegate {
fun onSessionApproved(Sign.Model.ApprovedSession)
fun onSessionRejected(Sign.Model.RejectedSession)
fun onSessionUpdate(Sign.Model.UpdatedSession)
fun onSessionExtend(Sign.Model.Session)
fun onSessionEvent(Sign.Model.SessionEvent)
fun onSessionDelete(Sign.Model.DeletedSession)
fun onSessionRequestResponse(Sign.Model.SessionRequestResponse)
fun onConnectionStateChange(state: Sign.Model.ConnectionState)
fun onError(error: Sign.Model.Error)}
SignClient.setDapptDelegate(dappDelegate)

Connect Clients

The CoreClient.Pairing.create method is used by Dapp to expose the Pairing with URI shared with Wallet out of bound, as QR code, or through deep links on the mobile device. The Dapp is responsible for constructing the proposal namespaces map, which means what chains, methods, and events are requested to be accepted by Wallet.

To establish a new session between existing peers, you should pass the existing pairing to the connect method. The SDK will send the SessionProposal under the hood for the given topic and expect a session approval or rejection in onSessionApproved and onSessionRejected in DappDelegate, respectively.

val pairing = CoreClient.Pairing.create()val namespaces = mapOf(namespace to Sign.Model.Namespaces.Proposal(chains, methods, events))val connectParams = Sign.Params.Connect(namespaces, pairing)

fun SignClient.connect(connectParams,
onSuccess = { -> /*Session proposal was being sent successfully over pairing topic*/},
onError = { error -> /*callback for error while sending session proposal*/ })

Pair Clients

From the Wallet’s perspective, pairing is performed by consuming the WC formatted URI. The Dapp proposes such URI as the out-of-band information shared between the Wallet and the Dapp to construct the proposal. The URI consists of the information required to initialize the communication between peers, like the symmetric key used for pairing encryption.

To pair the wallet with the Dapp, call the CoreClient.Pairing.pair function, which needs a Core.Params.Pair object. Core.Params.Pair is where the WC URI (either consumed through a QR code or deep-link) will be passed. All active pairing will expire after 30 days.

val pairParams = Core.Params.Pair("wc:...")
CoreClient.Pairing.pair(pairParams)

Session Proposal

When you manage to pair two peers and a session proposal is received by a wallet, you can either approve the given session or reject it. The Sign.Model.SessionProposal class consists of important information like namespaces and the Dapp’s metadata.

In terms of approval, you need the proposerPublicKey from the Sign.Model.SessionProposal object received through the WalletDelegate’s onSessionProposal function along with the accounts, methods, and events you are willing to expose to the other peer. An important note here, the provided account addresses should follow the CAPI10 semantics and Sign.Model.Session namespaces should include at least the proposed namespaces received in the Sign.Model.SessionProposal.

Pass the namespaces to be exposed to the other peer into an instance of the Sign.Params.Approve, which will need to be passed into the SignClient.approveSession function to approve the session proposal. To be sure whether session approval was handled well on the Dapp side, check the onSessionSettleResponse callback on SignClient.WalletDelegate.

val proposerPublicKey: String = /*from SessionProposal object*/
val namespaces = mapOf(namespace to Sign.Model.Namespaces.Session(accounts, methods, events))
val approveParams: Sign.Params.Approve = Sign.Params.Approve(proposerPublicKey, namespaces)SignClient.approveSession(approveParams)
{ error -> /*callback for error while approving a session*/ }

To send a rejection for the Sign.Model.SessionProposal, pass a rejection reason, code, and the proposerPublicKey from the Sign.Model.SessionProposal object to the SignClient.rejectSession function. For rejection reasons and code reference, check CAIP-25.

val proposerPublicKey: String = /*from SessionProposal object*/val rejectParams: Sign.Params.Reject = 
Reject(proposerPublicKey, rejectionReason, rejectionCode)
SignClient.rejectSession(rejectParams)
{ error -> /*callback for error while rejecting a session*/ }

Peer Communication

Once the pairing and session have been established, peers are free to communicate with each other. In the WalletConnect v2.0 Sign protocol, the session communication between two peers is performed via the JSON-RPC methods supported by the connected Dapp. In the most common scenario, the Dapp sends the session requests, and the Wallet is the one that signs them.

Respond session request

Once a Dapp sends the session request, it will be handled by the onSessionRequest callback in the WalletDelegate. To respond to a session request sent from Dapps, submit a Sign.Params.Response with the session’s topic, request ID, and the response data to the SignClient.respond function. Any request’s result should be sent in the stringified json under the result field in the Sign.Model.JsonRpcResponse.JsonRpcResult.

val sessionTopic: String = /*Topic of Session*/
val jsonRpcResponse: Sign.Model.JsonRpcResponse.JsonRpcResult = /*Settled Session Request ID along with request data*/
val result = Sign.Params.Response(sessionTopic, jsonRpcResponse)SignClient.respond(result)
{ error -> /*callback for error while responding session request*/}

Reject session request

Also, when the SessionRequest arrives, you can respond with a rejection. To do that, submit a Sign.Params.Response with the session’s topic and request ID along with the rejection data to the SignClient.respond function.

val sessionTopic: String = /*Topic of Session*/
val jsonRpcResponseError: Sign.Model.JsonRpcResponse.JsonRpcError = /*Session Request ID along with error code and message*/
val result = Sign.Params.Response(sessionTopic,jsonRpcResponseError)SignClient.respond(result)
{ error -> /*callback for error while responding session request*/ }

Get settled sessions

In addition, SDK allows users to get the list of all settled sessions by exposing a method which fetches them from the SDK’s local storage. The method will always return the most up-to-date list of settled session objects of type Sign.Model.Session.

SignClient.getListOfSettledSessions()
SignClient.getSettledSessionByTopic(sessionTopic)

What’s next?

That’s all, folks.

WalletConnect v2.0 Kotlin Sign SDK for Android is final. If you want to play around with the integration, check our GitHub, visit us on Discord to be a part of the community and for more information take a look into our Installation and wallet and Dapp guides.

Your feedback is appreciated!

--

--