Skip to main content

Connect to a dApp

Requirements

Make sure you have added the following modules as your dependencies:

:core
:client-wallet

You should have also decided which blockchains will be supported in your application and what transport layers to use to establish the communication. Make sure you have added the appropriate Blockchain and Transport modules as your dependencies as well.

tip

See the Installation page for more information about the modules and how to install them.

caution

Beacon Android SDK by default uses Coroutines to handle asynchronous code. If you don't want to use Coroutines in your application, see the Coroutines Alternatives to learn other approaches.

How to listen for messages and respond

Follow the steps below to learn how to interact with a dApp. The guide assumes all blockchains and transport layers are supported.

Create a wallet client

Create a BeaconWalletClient instance by providing your app's name, registering supported blockchains and transport layers that will be used for communication.

The example below creates a new BeaconWalletClient instance with default settings. See the Configuration guide to learn about more advanced setups.

import it.airgap.beaconsdk.blockchain.substrate.substrate
import it.airgap.beaconsdk.blockchain.tezos.tezos
import it.airgap.beaconsdk.client.wallet.BeaconWalletClient
import it.airgap.beaconsdk.transport.p2p.matrix.p2pMatrix

myCoroutineScope.launch {
val beaconWallet = BeaconWalletClient("My App") {
support(substrate(), tezos()) // set support for Substrate and Tezos blockchains
use(p2pMatrix()) // use Matrix to communicate with the Beacon network
}
}
caution

Currently only one instance of BeaconWalletClient should be created per application.

Subscribe to incoming requests

Listen to requests from the dApp by collecting the Flow returned from the BeaconWalletClient#connect method. The Flow emits BeaconRequest packed in a Result.

import it.airgap.beaconsdk.core.message.BeaconRequest

myCoroutineScope.launch {
// connect to the network and collect the messages
beaconWallet.connect().collect { result -> //: Result<BeaconRequest>
result.getOrNull()?.let { onBeaconRequest(it) } // if success
result.exceptionOrNull()?.let { onError(it) } // if failure
}
}

fun onBeaconRequest(request: BeaconRequest) {
TODO("Not yet implemented")
}

fun onError(e: Throwable) {
e.printStackTrace()
}

Connect to a dApp

To connect to a new dApp take the pairing request (obtained from, for example, a paring QR code) and transform it to P2pPeer. Next, register the new instance of P2pPeer in your wallet client.

import it.airgap.beaconsdk.core.data.P2pPeer

// replace placeholder values with data provided in the dApp's pairing request
val dApp = P2pPeer(
id = "id",
name = "name",
publicKey = "publicKey",
relayServer = "relayServer",
version = "version",
)

myCoroutineScope.launch {
try {
// connect to a new peer
beaconWallet.addPeers(dApp)
} catch (e: Exception) {
e.printStackTrace()
}
}

Handle requests from the dApp

Having received a request, you can create a response and send it back to the dApp. The response should always be created from an incoming request. Attempting to send a response that was not created from a request awaiting answer will result in an error.

The first request your app receives from a dApp is a permission request. The example below shows how to respond to it in the most basic way. To get more information about other kinds of requests or learn more advanced use cases see the Blockchain guides.

import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateAccount
import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateNetwork
import it.airgap.beaconsdk.blockchain.substrate.message.request.PermissionSubstrateRequest
import it.airgap.beaconsdk.blockchain.substrate.message.response.PermissionSubstrateResponse
import it.airgap.beaconsdk.blockchain.tezos.message.request.PermissionTezosRequest
import it.airgap.beaconsdk.blockchain.tezos.message.response.PermissionTezosResponse
import it.airgap.beaconsdk.core.data.BeaconError
import it.airgap.beaconsdk.core.message.BeaconRequest
import it.airgap.beaconsdk.core.message.ErrorBeaconResponse

fun onBeaconRequest(request: BeaconRequest) {
// create a response based on the request
val response = when (request) {
is PermissionSubstrateRequest -> PermissionSubstrateResponse.from(request, request.networks.map { substrateAccount(it) })
is PermissionTezosRequest -> PermissionTezosResponse.from(request, tezosAccount(request.network))
else -> ErrorBeaconResponse.from(request, BeaconError.Aborted)
}

myCoroutineScope.launch {
try {
// send the response
beaconWallet.respond(response)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

// replace placeholder values with valid data
fun substrateAccount(network: SubstrateNetwork) = SubstrateAccount(
"substratePublicKey",
"substrateAddress",
network,
)

fun tezosAccount(network: TezosNetwork) = TezosAccount(
"tezosPublicKey",
"tezosAddress",
network,
)

Example Code

The following example shows how to create a simple Activity which connects to a dApp using Matrix and handles Substrate and Tezos messages based on the steps described earlier.

build.gradle.kts
allprojects {
repositories {
maven("https://jitpack.io")
}
}

stable latest

app/build.gradle.kts
dependencies {
// Beacon
val beaconVersion = "x.y.z"

implementation("com.github.airgap-it.beacon-android-sdk:core:$beaconVersion")
implementation("com.github.airgap-it.beacon-android-sdk:client-wallet:$beaconVersion")
implementation("com.github.airgap-it.beacon-android-sdk:blockchain-substrate:$beaconVersion")
implementation("com.github.airgap-it.beacon-android-sdk:blockchain-tezos:$beaconVersion")
implementation("com.github.airgap-it.beacon-android-sdk:transport-p2p-matrix:$beaconVersion")

// Android
val androidActivityVersion = "x.y.z"
implementation("androidx.activity:activity-ktx:$androidActivityVersion")

val androidLifecycleVersion = "x.y.z"
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$androidLifecycleVersion")
}
app/src/main/java/com/example/MainActivity.kt
package com.example

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateAccount
import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateNetwork
import it.airgap.beaconsdk.blockchain.substrate.message.request.PermissionSubstrateRequest
import it.airgap.beaconsdk.blockchain.substrate.message.response.PermissionSubstrateResponse
import it.airgap.beaconsdk.blockchain.substrate.substrate
import it.airgap.beaconsdk.blockchain.tezos.data.TezosAccount
import it.airgap.beaconsdk.blockchain.tezos.data.TezosNetwork
import it.airgap.beaconsdk.blockchain.tezos.message.request.PermissionTezosRequest
import it.airgap.beaconsdk.blockchain.tezos.message.response.PermissionTezosResponse
import it.airgap.beaconsdk.blockchain.tezos.tezos
import it.airgap.beaconsdk.client.wallet.BeaconWalletClient
import it.airgap.beaconsdk.core.data.BeaconError
import it.airgap.beaconsdk.core.data.P2pPeer
import it.airgap.beaconsdk.core.message.BeaconRequest
import it.airgap.beaconsdk.core.message.ErrorBeaconResponse
import it.airgap.beaconsdk.transport.p2p.matrix.p2pMatrix
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
lateinit var beaconWallet: BeaconWalletClient

val dApp = P2pPeer(
"0b02d44c-de33-b5ab-7730-ff8e62f61869" /* TODO: change me */,
"My dApp",
"6c7870aa1e42477fd7c2baaf4763bd826971e470772f71a0a388c1763de3ea1e" /* TODO: change me */,
"beacon-node-1.sky.papers.tech" /* TODO: change me */,
"2" /* TODO: change me */,
)

fun substrateAccount(network: SubstrateNetwork) = SubstrateAccount(
"f4c6095213a2f6d09464ed882b12d6024d20f7170c3b8bd5c1b071e4b00ced72" /* TODO: change me */,
"16XwWkdUqFXFY1tJNf1Q6fGgxQnGYUS6M95wPcrbp2sjjuoC" /* TODO: change me */,
network,
)

fun tezosAccount(network: TezosNetwork) = TezosAccount(
"9ae0875d510904b0b15d251d8def1f5f3353e9799841c0ed6d7ac718f04459a0" /* TODO: change me */,
"tz1SkbBZg15BXPRkYCrSzhY6rq4tKGtpUSWv" /* TODO: change me */,
network,
)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

lifecycleScope.launch {
createWalletClient()
subscribeToRequests()
connectToDApp()
}
}

suspend fun createWalletClient() {
beaconWallet = BeaconWalletClient("My App") {
support(substrate(), tezos())
use(p2pMatrix())
}
}

fun subscribeToRequests() {
beaconWallet.connect().asLiveData().observe(this) { result ->
result.getOrNull()?.let { onBeaconRequest(it) }
result.exceptionOrNull()?.let { onError(it) }
}
}

suspend fun connectToDApp() {
try {
beaconWallet.addPeers(dApp)
} catch (e: Exception) {
onError(e)
}
}

fun onBeaconRequest(request: BeaconRequest) {
val response = when (request) {
is PermissionSubstrateRequest -> PermissionSubstrateResponse.from(request, request.networks.map { substrateAccount(it) })
is PermissionTezosRequest -> PermissionTezosResponse.from(request, tezosAccount(request.network))
else -> ErrorBeaconResponse.from(request, BeaconError.Aborted)
}

lifecycleScope.launch {
try {
beaconWallet.respond(response)
} catch (e: Exception) {
onError(e)
}
}
}

fun onError(e: Throwable) {
e.printStackTrace()
}
}