Beginner’s Guide to WalletConnect v2.0 for Android Developers

Jakub
WalletConnect
Published in
7 min readDec 23, 2021

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.

WalletConnect v2.0 for Android developers?

Yes! Here we are.

The following guide is designed for Android developers that look forward to playing around and experimenting with the WalletConnect v2.0 on 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 next step for you should be the technical specification document. After that, you can find a detailed explanation of the Relay architecture, out-of-band communication, Relay protocol API, difference between the pairing and session, and much more.

In short words, WalletConnect v2.0 provides secure remote signing communication between a blockchain application 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.

Getting started

First things first. To start the Kotlin SDK integration you should remember to add the following dependencies to your project. Add Jitpack to the root build.gradle file:

allprojects {
repositories {
maven(url = "https://jitpack.io")
}
}

When it’s done the 1.0.0-beta01 dependency should be added to the gradle file in the app directory:

implementation("com.github.WalletConnect:WalletConnectKotlinV2:1.0.0-beta01")

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 some 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 Proposer and a Responder. 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 proper keys. On the other hand, the Responder usually acts as the blockchain wallet, which approves sessions, exposes the account to the Proposer, and is responsible for signing. After the session settlement, the Proposer acts as the non-controller, and the Responder acts as the controller.

Initialization

The Kotlin SDK for Android, at the Beta stage, provides the Responder implementation. To initialize the singleton instance of the WalletConnectClient, create a ClientTypes.InitialParams object in the Android Application class. The InitialParams object will need at least the application class, the ProjectID, and the wallet’s AppMetaData. The InitialParams object will then be passed to the WalletConnectClient initialize function. IntitalParams also allows for custom URLs by passing URL string into the hostName property.

val appMetaData = AppMetaData(name = "Wallet Name", description = "Wallet Description", url = "Wallet Url", icons = listOfIconUrlStrings)val initializeParams = ClientTypes.InitialParams(application = application, projectId = "project id", appMetaData = appMetaData)WalletConnectClient.initalize(initalizeParams)

The next step is to set up the WalletConnectListener, which is needed to handle the following asynchronous events: SessionProposals, SessionRequests, SessionNotifications, and SessionDeleted.

val listener = object: WalletConnectClientListener {   override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) {
// Session Proposal object sent by Dapp after pairing was successful
}
override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {
// JSON-RPC methods wrapped by SessionRequest object sent by Dapp
}
override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {
// Triggered when the session is deleted by the peer
}
override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {
// Triggered when the peer emits events as notifications that match the list of types agreed upon session settlement
}
}
WalletConnectClient.setWalletConnectListener(listener)

Pair clients

From the Responder’s perspective, pairing is performed by passing the WC formatted URI. The Proposer proposes such URI as the out-of-band information shared between the Proposer and the Responder to construct the proposal. The URI consists of the information required to initialize the communication between peers, like the proposer’s public key or topic required to receive session proposals.

To pair the wallet with the blockchain application, call the WalletConnectClient.pair function, which needs a ClientTypes.PairParams and WalletConnectClientListeners.Pairing. ClientTypes.PairParams is where the Proposer URI will be passed. Either from a QR code or a URI string. WalletConnectClientListeners.Pairing is the callback that will be asynchronously called once the pairing has been made with the Dapp. The pairing will expire after 30 days, this is the default behavior of the protocol. Also, the SDK will hold onto a pairing for up to 24 hours to receive a response from the Responder before getting rid of the pending pairing.

val pairParams = ClientTypes.PairParams("wc:...")val pairListener = object: WalletConnectClientListeners.Pairing {
override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) {
// Settled pairing
}
override fun onError(error: Throwable) {
// Pairing approval error
}
}
WalletConnectClient.pair(pairParams, pairListener)

Session proposal

When you manage to pair two peers, and a session proposal is received, you can either approve the given session or reject it. The SessionProposal, which is the part of the WalletConnectClientData class, consists of important information like blockchains and JSON-RPC methods that the Dapp is requesting access for.

Session proposal dialog from our sample app

In terms of approval, you need the WalletConnectClientData.SessionProposal object and the accounts you are willing to expose to the other peer. An important note here, the provided address should follow the CAPI10 semantics. Pass the accounts to be exposed to the other peer into an instance of the ClientTypes.ApproveParams which will need to be passed into the WalletConnectClient.approve function to approve the session proposal. The approve function will also need the WalletConnectClientListeners.SessionApprove callback. The listener will asynchronously expose the settled session if the operation is successful. Session will automatically expire in 7 days. A new session will be needed to continue to use the connect Dapp.

val accounts: List<String> = /*list of accounts on chains*/
val sessionProposal: WalletConnectClientData = /*Session Proposal object*/
val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts)val listener: WalletConnectClientListeners.SessionApprove {
override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) {
// Approve session success
}
override fun onError(error: Throwable) {
// Approve session error
}
}
WalletConnectClient.approve(approveParams, listener)

To send a rejection for the Session Proposal, pass a rejection reason, and the topic from the WalletConnectClientData.SessionProposal object to the WalletConnectClient.reject function. The listener will asynchronously expose a RejectedSession object that will mirror the data sent for rejection.

val rejectionReason: String = /*The reason for rejecting the Session Proposal*/
val proposalTopic: String = /*Topic from the Session Proposal*/
val rejectParams: ClientTypes.RejectParams = ClientTypes.RejectParams(rejectionReason, proposalTopic)val listener: WalletConnectClientListneners.SessionReject {
override fun onSuccess(rejectedSession: WalletConnectClientData.RejectedSession) {
// Rejection proposal
}
override fun onError(error: Throwable) {
//Rejected proposal error
}
}
WalletConnectClient.reject(rejectParams, listener)

Peer communication

Once the pairing and session have been established, now it’s high time to communicate with the other peer. In the WalletConnect v2.0 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 payload requests, and the Wallet is the one that sings them.

Respond session request

Once the session request is sent by a Dapp, it will be handled by the onSessionRequest callback. To respond to JSON-RPC methods sent from Dapps for a settle session, submit a ClientTypes.ResponseParams with the settled session’s topic and request ID along with the response data to the WalletConnectClient.respond function. Any request’s result should be sent in the stringified json under the result field in the WalletConnectClientData.JsonRpcResponse.JsonRpcResult. Any errors will be exposed through the WalletConnectClientListeners.SessionPayload listener.

val sessionRequestTopic: String = /*Topic of Settled Session*/
val jsonRpcResponse: WalletConnectClientData.JsonRpcResponse.JsonRpcResult = /*Settled Session Request ID along with request data*/
val result = ClientTypes.ResponseParams(sessionTopic = sessionRequestTopic, jsonRpcResponse = jsonRpcResponse)val listener = object : WalletConnectClientListeners.SessionPayload {
override fun onError(error: Throwable) {
// Error
}
}
WalletConnectClient.respond(result, listener)

Reject session request

Also, when the SessionRequest arrives, you can respond with a rejection. To do that, submit a ClientTypes.ResponseParams with the settled session’s topic and request ID along with the rejection data to the WalletConnectClient.respond function. Any errors will be exposed through the WalletConnectClientListeners.SessionPayload listener.

val sessionRequestTopic: String = /*Topic of Settled Session*/
val jsonRpcResponseError: WalletConnectClientData.JsonRpcResponse.JsonRpcError = /*Settled Session Request ID along with error code and message*/
val result = ClientTypes.ResponseParams(sessionTopic = sessionRequestTopic, jsonRpcResponse = jsonRpcResponseError)val listener = object : WalletConnectClientListeners.SessionPayload {
override fun onError(error: Throwable) {
// Error
}
}
WalletConnectClient.respond(result, listener)

Get settled sessions

In addition, SDK enables 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 up-to-date list of settled session objects of type WalletConnectClientData.SettledSession.

WalletConnectClient.getListOfSettledSessions()

Shutdown SDK

Lastly, to ensure that the internal concurrency framework is properly handled when leaving the application, call WalletConnectClient.shutdown() before exiting from the application.

WalletConnectClient.shutdown()

What’s next?

That’s all, folks. Please keep in mind that WalletConnect v2.0 Kotlin SDK for Android is still at the Beta stage and should only be used for testing purposes. If you want to play around with the integration, check our GitHub, visit us on Discord to be a part of the community, read the documentation and have a look into the Kotlin SDK guide.

Your feedback is appreciated!

--

--