UI Elements
We strongly recommend keeping the default UI Elements. Consistent UI helps users to interact with different dApps more easily.
The default UI elements have been designed with all wallets and user setups in mind. It handles a lot of cases that are not obvious at first glance. If you really must overwrite the default UI.
Custom UI Guidelines
- When the user wants to connect to a wallet, a selection of wallets must be shown. The selection depends on the device the user is using and should display different options on different devices.
- Desktop
- iOS
- Android
On desktop there is only one view with some wallets and the QR code.
On iOS, each mobile and webapp has its own entry because the device doesn't allow selection of a specific if multiple apps are installed.
On Android, there is one link for all mobile apps because the device lets you choose the specific app you want to open.
- Every message sent by the dApp should trigger a persistent element on screen, indicating that a request is in progress.
- If no message has been received for a certain amount of time (eg. 5 seconds), the UI should indicate that there are potential connection issues. The dApp should NOT do any automatic action at this point, because some delays are normal, especially when working with wallets that communicate over the P2P network.
- In the UI element that is shown, the user SHOULD have the option to reset his connection, meaning to disconnect a wallet. This will abort the whole flow. The user can start the action again, which will now trigger a new "pairing" alert because the previous connection was reset.
- Once the Wallet receives the request, it will immediately send back an Acknowledgement Message. When this message is received in the dApp, the dApp knows that the connection is still valid and the user can handle the request. The persistent UI element that is shown on screen should now be updated, indicating that we are waiting for user input on the wallet.
- When the response is received, the persistent element can be removed again and the successful (or error) response can be displayed to the user and the application flow can be continued.
Overwriting Default UI elements
You can overwrite all of the default UI elements by doing the following.
- Beacon
- Taquito
import { BeaconEvent, DAppClient, defaultEventCallbacks, } from "@airgap/beacon-sdk"; const dAppClient = new DAppClient({ name: "Beacon Docs", disableDefaultEvents: true, // Disable all events / UI. This also disables the pairing alert. eventHandlers: { // To keep the pairing alert, we have to add the following default event handlers back [BeaconEvent.PAIR_INIT]: { handler: defaultEventCallbacks.PAIR_INIT, }, [BeaconEvent.PAIR_SUCCESS]: { handler: defaultEventCallbacks.PAIR_SUCCESS, }, }, }); try { console.log("Requesting permissions..."); const permissions = await dAppClient.requestPermissions(); console.log("Got permissions:", permissions.address); } catch (error) { console.error("Got error:", error); }
import { BeaconEvent, defaultEventCallbacks } from "@airgap/beacon-sdk"; import { TezosToolkit } from "@taquito/taquito"; import { BeaconWallet } from "@taquito/beacon-wallet"; const Tezos = new TezosToolkit("https://mainnet.api.tez.ie"); const wallet = new BeaconWallet({ name: "Beacon Docs Taquito", disableDefaultEvents: true, // Disable all events / UI. This also disables the pairing alert. eventHandlers: { // To keep the pairing alert, we have to add the following default event handlers back [BeaconEvent.PAIR_INIT]: { handler: defaultEventCallbacks.PAIR_INIT, }, [BeaconEvent.PAIR_SUCCESS]: { handler: defaultEventCallbacks.PAIR_SUCCESS, }, }, }); Tezos.setWalletProvider(wallet); try { await wallet.requestPermissions(); const address = await wallet.getPKH(); console.log("Got permissions:", address); } catch (error) { console.error("Got error:", error); }
You can also add your own logic to specific events and still keep the original behaviour.
The same can be achieved without overwriting the default event handler by subscribing to an event. This method is preferred, if possible.
- Beacon
- Taquito
import { BeaconEvent, DAppClient, defaultEventCallbacks, P2PPairingRequest, PostMessagePairingRequest, NetworkType, WalletConnectPairingRequest, AnalyticsInterface, } from "@airgap/beacon-sdk"; const dAppClient = new DAppClient({ name: "Beacon Docs", eventHandlers: { [BeaconEvent.PAIR_INIT]: { // Every BeaconEvent can be overriden by passing a handler here. // The default will not be executed anymore. To keep the default, // you will have to call it again. handler: async ( data: { p2pPeerInfo: () => Promise<P2PPairingRequest>; postmessagePeerInfo: () => Promise<PostMessagePairingRequest>; walletConnectPeerInfo: () => Promise<WalletConnectPairingRequest>; networkType: NetworkType; abortedHandler?(): void; disclaimerText?: string; analytics: AnalyticsInterface; featuredWallets?: string[]; }, eventCallback?: any[] | undefined, ): Promise<void> => { await defaultEventCallbacks.PAIR_INIT(data); // Add this if you want to keep the default behaviour. console.log("syncInfo", data, eventCallback); }, }, }, }); try { console.log("Requesting permissions..."); const permissions = await dAppClient.requestPermissions(); console.log("Got permissions:", permissions.address); } catch (error) { console.error("Got error:", error); }
import { TezosToolkit } from "@taquito/taquito"; import { BeaconWallet } from "@taquito/beacon-wallet"; import { BeaconEvent, DAppClient, defaultEventCallbacks, P2PPairingRequest, PostMessagePairingRequest, NetworkType, WalletConnectPairingRequest, AnalyticsInterface, } from "@airgap/beacon-sdk"; const Tezos = new TezosToolkit("https://mainnet.api.tez.ie"); const wallet = new BeaconWallet({ name: "Beacon Docs Taquito", eventHandlers: { [BeaconEvent.PAIR_INIT]: { // Every BeaconEvent can be overriden by passing a handler here. // The default will not be executed anymore. To keep the default, // you will have to call it again. handler: async ( data: { p2pPeerInfo: () => Promise<P2PPairingRequest>; postmessagePeerInfo: () => Promise<PostMessagePairingRequest>; walletConnectPeerInfo: () => Promise<WalletConnectPairingRequest>; networkType: NetworkType; abortedHandler?(): void; disclaimerText?: string; analytics: AnalyticsInterface; featuredWallets?: string[]; }, eventCallback?: any[] | undefined, ): Promise<void> => { await defaultEventCallbacks.PAIR_INIT(data); // Add this if you want to keep the default behaviour. console.log("syncInfo", data, eventCallback); }, }, }, }); Tezos.setWalletProvider(wallet); try { console.log("Requesting permissions..."); const permissions = await wallet.client.requestPermissions(); console.log("Got permissions:", permissions.address); } catch (error) { console.error("Got error:", error); }
The closing of the pairing alert can not be listened to by default. The reason for this is the delay in the P2P connections. It is possible that a user scans the pairing QR code with his wallet and then closes the alert while waiting for the connection to be established. If the dApp interprets the "closing" of the alert as an abort, and a few seconds later the connection is established successfully, the behaviour could be unexpected.
If you still want to be notified of the closing of the pairing window, you can do it in the following way, while keeping the default behaviour.
- Beacon
- Taquito
import { BeaconEvent, DAppClient, defaultEventCallbacks, P2PPairingRequest, PostMessagePairingRequest, NetworkType, WalletConnectPairingRequest, AnalyticsInterface, } from "@airgap/beacon-sdk"; const dAppClient = new DAppClient({ name: "Beacon Docs", eventHandlers: { [BeaconEvent.PAIR_INIT]: { // Every BeaconEvent can be overriden by passing a handler here. // The default will not be executed anymore. To keep the default, // you will have to call it again. handler: async (data: { p2pPeerInfo: () => Promise<P2PPairingRequest>; postmessagePeerInfo: () => Promise<PostMessagePairingRequest>; walletConnectPeerInfo: () => Promise<WalletConnectPairingRequest>; networkType: NetworkType; abortedHandler?(): void; disclaimerText?: string; analytics: AnalyticsInterface; featuredWallets?: string[]; }): Promise<void> => { // If you want to attach your own "on alert closed" handler // eslint-disable-next-line @typescript-eslint/unbound-method const oldHandler = data.abortedHandler; const newHandler = (): void => { if (oldHandler) { // Make sure to call the internal abortedHandler oldHandler(); } // Add your own logic here console.log("My logic"); data.abortedHandler = newHandler; // Replace the internal abortedHandler with the new one await defaultEventCallbacks.PAIR_INIT(data); // Add this if you want to keep the default behaviour. console.log("syncInfo", data); }, }, }, }); try { console.log("Requesting permissions..."); const permissions = await dAppClient.requestPermissions(); console.log("Got permissions:", permissions.address); } catch (error) { console.error("Got error:", error); }
import { TezosToolkit } from "@taquito/taquito"; import { BeaconWallet } from "@taquito/beacon-wallet"; import { BeaconEvent, DAppClient, defaultEventCallbacks, P2PPairingRequest, PostMessagePairingRequest, NetworkType, WalletConnectPairingRequest, AnalyticsInterface, } from "@airgap/beacon-sdk"; const Tezos = new TezosToolkit("https://mainnet.api.tez.ie"); const wallet = new BeaconWallet({ name: "Beacon Docs Taquito", eventHandlers: { [BeaconEvent.PAIR_INIT]: { // Every BeaconEvent can be overriden by passing a handler here. // The default will not be executed anymore. To keep the default, // you will have to call it again. handler: async (data: { p2pPeerInfo: () => Promise<P2PPairingRequest>; postmessagePeerInfo: () => Promise<PostMessagePairingRequest>; walletConnectPeerInfo: () => Promise<WalletConnectPairingRequest>; networkType: NetworkType; abortedHandler?(): void; disclaimerText?: string; analytics: AnalyticsInterface; featuredWallets?: string[]; }): Promise<void> => { // If you want to attach your own "on alert closed" handler // eslint-disable-next-line @typescript-eslint/unbound-method const oldHandler = data.abortedHandler; const newHandler = (): void => { if (oldHandler) { // Make sure to call the internal abortedHandler oldHandler(); } // Add your own logic here console.log("My logic"); data.abortedHandler = newHandler; // Replace the internal abortedHandler with the new one await defaultEventCallbacks.PAIR_INIT(data); // Add this if you want to keep the default behaviour. console.log("syncInfo", data); }, }, }, }); Tezos.setWalletProvider(wallet); try { console.log("Requesting permissions..."); const permissions = await wallet.client.requestPermissions(); console.log("Got permissions:", permissions.address); } catch (error) { console.error("Got error:", error); }