Skip to main content

Connect to a dApp

Requirements

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

BeaconCore
BeaconClientWallet

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 packages as your dependencies as well.

tip

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

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 Beacon.WalletClient 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 Beacon.WalletClient instance with default settings. See the Configuration guide to learn about more advanced setups.

import BeaconCore
import BeaconBlockchainSubstrate
import BeaconBlockchainTezos
import BeaconClientWallet
import BeaconTransportP2PMatrix

...

var beaconWallet: Beacon.WalletClient!

do {
Beacon.WalletClient.create(
with: .init(
name: "MyApp",
blockchains: [Substrate.factory, Tezos.factory], // set support for Substrate and Tezos blockchains
connections: [try Transport.P2P.Matrix.connection()] // use Matrix to communicate with the Beacon network
)
) { result in
switch result {
case let .success(client):
beaconWallet = client
case let .failure(error):
print(error)
}
}
} catch {
print(error)
}
caution

Currently only one instance of Beacon.WalletClient should be created per application.

Subscribe to incoming requests

Subscribe to requests from the dApp by connecting to the Beacon network and listening to incoming requests.

import BeaconCore
import BeaconBlockchainSubstrate
import BeaconBlockchainTezos
import BeaconClientWallet

...

beaconWallet.connect { result in
switch result {
case .success(_):
beaconWallet.listen(onRequest: onSubstrateRequest) // listen for Substrate requests
beaconWallet.listen(onRequest: onTezosRequest) // listen for Tezos requests
case let .failure(error):
print(error)
}
}

...

func onSubstrateRequest(_ request: Result<BeaconRequest<Substrate>, Beacon.Error>) {
// TODO: Not yet implemented
}

func onTezosRequest(_ request: Result<BeaconRequest<Tezos>, Beacon.Error>) {
// TODO: Not yet implemented
}

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 Beacon.P2PPeer. Next, register the new instance of Beacon.P2PPeer in your wallet client.

import BeaconCore
import BeaconClientWallet

// replace placeholder values with data provided in the dApp's pairing request
let dApp = Beacon.P2PPeer(
id: "id",
name: "name",
publicKey: "publicKey",
relayServer: "relayServer",
version: "version"
)

// connect to a new peer
beaconWallet.add([.p2p(dApp)]) { result in
switch result {
case .success(_):
print("dApp connected")
case let .failure(error):
print(error)
}
}

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 BeaconCore
import BeaconBlockchainSubstrate
import BeaconBlockchainTezos
import BeaconClientWallet

func onSubstrateRequest(_ request: Result<BeaconRequest<Substrate>, Beacon.Error>) {
do {
let request = try request.get()
let response = try response(from: request)

beaconWallet.respond(with: response) { result in
switch result {
case .success(_):
print("Response sent")
case let .failure(error):
print(error)
}
}
} catch {
print(error)
}
}

func response(from request: BeaconRequest<Substrate>) throws -> BeaconResponse<Substrate> {
switch request {
case let .permission(content):
let accounts = try content.networks.map { try substrateAccount(network: $0) }
return .permission(PermissionSubstrateResponse(from: content, accounts: accounts))
case let .blockchain(blockchain):
return .error(ErrorBeaconResponse(from: blockchain, errorType: .aborted))
}
}

func onTezosRequest(_ request: Result<BeaconRequest<Tezos>, Beacon.Error>) {
do {
let request = try request.get()
let response = try response(from: request)

beaconWallet.respond(with: response) { result in
switch result {
case .success(_):
print("Response sent")
case let .failure(error):
print(error)
}
}
} catch {
print(error)
}
}

func response(from request: BeaconRequest<Tezos>) throws -> BeaconResponse<Tezos> {
switch request {
case let .permission(content):
let account = try tezosAccount(network: content.network)
return .permission(PermissionTezosResponse(from: content, account: account))
case let .blockchain(blockchain):
return .error(ErrorBeaconResponse(from: blockchain, errorType: .aborted))
}
}

// replace placeholder values with valid data
func substrateAccount(network: Substrate.Network) throws -> Substrate.Account {
try Substrate.Account(
publicKey: "substratePublicKey",
address: "substrateAddress",
network: network
)
}

func tezosAccount(network: Tezos.Network) throws -> Tezos.Account {
try Tezos.Network(
publicKey: "tezosPublicKey",
address: "tezosAddress",
network: network
)
}

Example Code

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

stable latest

Package.swift
dependencies: [
.package(url: "https://github.com/airgap-it/beacon-ios-sdk", from: "x.y.z")
],

Or in Xcode open the Add Package Dependency window (as described in the official guide) and enter the Beacon iOS SDK GitHub repository URL

https://github.com/airgap-it/beacon-ios-sdk
BeaconExample.swift
import Foundation
import BeaconCore
import BeaconBlockchainSubstrate
import BeaconBlockchainTezos
import BeaconClientWallet
import BeaconTransportP2PMatrix

class BeaconExample {
var beaconWallet: Beacon.WalletClient!

let dApp = Beacon.P2PPeer(
id: "0b02d44c-de33-b5ab-7730-ff8e62f61869" /* TODO: change me */,,
name: "My dApp",
publicKey: "6c7870aa1e42477fd7c2baaf4763bd826971e470772f71a0a388c1763de3ea1e" /* TODO: change me */,
relayServer: "beacon-node-1.sky.papers.tech" /* TODO: change me */,
version: "2" /* TODO: change me */
)

func substrateAccount(network: Substrate.Network) throws -> Substrate.Account {
try Substrate.Account(
publicKey: "f4c6095213a2f6d09464ed882b12d6024d20f7170c3b8bd5c1b071e4b00ced72" /* TODO: change me */,
address: "16XwWkdUqFXFY1tJNf1Q6fGgxQnGYUS6M95wPcrbp2sjjuoC" /* TODO: change me */,
network: network
)
}

func tezosAccount(network: Tezos.Network) throws -> Tezos.Account {
try Tezos.Network(
publicKey: "9ae0875d510904b0b15d251d8def1f5f3353e9799841c0ed6d7ac718f04459a0" /* TODO: change me */,
address: "tz1SkbBZg15BXPRkYCrSzhY6rq4tKGtpUSWv" /* TODO: change me */,
network: network
)
}

func run() {
createBeaconWallet { result in
guard case .success(_) = result else {
return
}

self.subscribeToRequests { result in
guard case .success(_) = result else {
return
}

self.connectToDApp { result in
guard case .success(_) = result else {
return
}
}
}
}
}

func createBeaconWallet(completion: @escaping (Result<(), Error>) -> ()) {
do {
Beacon.WalletClient.create(
with: .init(
name: "MyApp",
blockchains: [Substrate.factory, Tezos.factory],
connections: [try Transport.P2P.Matrix.connection()]
)
) { result in
switch result {
case let .success(client):
self.beaconWallet = client
completion(.success(()))
case let .failure(error):
completion(.failure(error))
}
}
} catch {
completion(.failure(error))
}
}

func subscribeToRequests(completion: @escaping (Result<(), Error>) -> ()) {
beaconWallet.connect { result in
switch result {
case .success(_):
self.beaconWallet.listen(onRequest: self.onSubstrateRequest)
self.beaconWallet.listen(onRequest: self.onTezosRequest)
completion(.success(()))
case let .failure(error):
completion(.failure(error))
}
}
}

func connectToDApp(completion: @escaping (Result<(), Error>) -> ()) {
beaconWallet.add([.p2p(dApp)]) { result in
switch result {
case .success(_):
completion(.success(()))
case let .failure(error):
completion(.failure(error))
}
}
}

func onSubstrateRequest(_ request: Result<BeaconRequest<Substrate>, Beacon.Error>) {
do {
let request = try request.get()
let response = try response(from: request)

beaconWallet.respond(with: response) { result in
switch result {
case .success(_):
print("Response sent")
case let .failure(error):
print(error)
}
}
} catch {
print(error)
}
}

func response(from request: BeaconRequest<Substrate>) throws -> BeaconResponse<Substrate> {
switch request {
case let .permission(content):
let accounts = try content.networks.map { try substrateAccount(network: $0) }
return .permission(PermissionSubstrateResponse(from: content, accounts: accounts))
case let .blockchain(blockchain):
return .error(ErrorBeaconResponse(from: blockchain, errorType: .aborted))
}
}

func onTezosRequest(_ request: Result<BeaconRequest<Tezos>, Beacon.Error>) {
do {
let request = try request.get()
let response = try response(from: request)

beaconWallet.respond(with: response) { result in
switch result {
case .success(_):
print("Response sent")
case let .failure(error):
print(error)
}
}
} catch {
print(error)
}
}

func response(from request: BeaconRequest<Tezos>) throws -> BeaconResponse<Tezos> {
switch request {
case let .permission(content):
let account = try tezosAccount(network: content.network)
return .permission(PermissionTezosResponse(from: content, account: account))
case let .blockchain(blockchain):
return .error(ErrorBeaconResponse(from: blockchain, errorType: .aborted))
}
}
}