WalletConnect
WalletConnect is a protocol that enables dApps to connect with various wallets. This document describes how to integrate WalletConnect into your dApp and wallet within the Alephium ecosystem.
Integrate WalletConnect into dApp
Establishing a Connection
In WalletConnect, a dApp sends messages to the wallet via the WalletConnect protocol and receives responses from the wallet. Therefore, the first step is to establish a WalletConnect connection.
To make it easier for dApps to integrate WalletConnect, @alephium/walletconnect-provider
significantly simplifies the process of establishing and managing the connection. You can initialize the WalletConnectProvider
and connect to the wallet as follows:
import QRCodeModal from '@alephium/walletconnect-qrcode-modal'
import { WalletConnectProvider } from '@alephium/walletconnect-provider'
const provider = await WalletConnectProvider.init({
projectId: 'walletconnect-project-id',
metadata: {
name: 'dapp-name',
description: 'dapp-description',
url: 'dapp-url'
},
networkId: 'mainnet',
addressGroup: 0,
onDisconnected: () => {}
})
provider.on('displayUri', QRCodeModal.open(uri, () => console.log('qr closed')))
await provider.connect()
The init
method takes the following parameters:
projectId
: the WalletConnect project id, you can get one from WalletConnect Cloudmetadata
: your dapp metadatanetworkId
: the optional network id, it allows to connect to any network if it isundefined
addressGroup
: the optional address group, it allows to connect to any group if it isundefined
onDisconnected
: the optional disconnect callback, it will be called when the wallet disconnects
Once the connection is established, you can use the WalletConnectProvider
to send requests to the wallet. WalletConnectProvider
is a subclass of SignerProvider
, and you can check the supported request methods of SignerProvider
here.
Connecting via Deep Link
The code above will display a QR code modal, and you can scan the QR code with the wallet to complete the connection. However, sometimes it's more convenient to directly redirect to the wallet app for connection confirmation. You can achieve this using a deep link:
provider.on('displayUri', () => window.open('deep-link-url'))
await provider.connect()
Disconnecting
You can call the disconnect
method to disconnect from the dApp:
await provider.disconnect()
For WalletConnect connections, the dApp also needs to handle disconnection requests initiated by the wallet:
provider.on('session_delete', () => { /* disconnect handler */ })
Auto Reconnect
WalletConnect stores connection information in local storage, allowing you to reuse previous connections when the page is refreshed. You can call the isPreauthorized
method to check if the previous connection can be reused:
if (provider.isPreauthorized()) {
await provider.connect()
}
Integrate WalletConnect into Wallet
Approving Connection
Initiate the WalletConnect client:
import SignClient from '@walletconnect/sign-client'
const signClient = await SignClient.init({
projectId: 'walletconnect-project-id',
metadata: {
name: 'wallet-name',
description: 'wallet description',
url: 'wallet-url'
}
})
Accept a connection uri passed from a dApp when a user scans the dApp's WalletConnect QR code modal:
const onConnect = async (uri: string) => {
await signClient.core.pairing.pair({ uri })
}
Handle the session_proposal
event on the SignClient
. This event is triggered when the pair
method is called to create a pairing session:
import { SignClientTypes } from '@walletconnect/types';
import { formatChain, parseChain, PROVIDER_NAMESPACE } from '@alephium/walletconnect-provider'
signClient.on('session_proposal', onSessionProposal)
const onSessionProposal = async (proposalEvent: SignClientTypes.EventArguments['session_proposal']) => {
const requiredNamespace = proposalEvent.params.requiredNamespaces[PROVIDER_NAMESPACE]
const requiredChains = requiredNamespace ? requiredNamespace.chains : undefined
const requiredChainInfo = requiredChains ? parseChain(requiredChains[0]) : undefined
// the wallet should verify that the proposal includes the required namespaces
if (!requiredChains || requiredChains.length !== 1 || !requiredChainInfo) {
throw new Error('Invalid WalletConnect proposal')
}
const chain = formatChain(requiredChainInfo.networkId, requiredChainInfo.addressGroup)
const signerPublicKey = selectAccount(requiredChainInfo).publicKey
// the wallet should send an approval response containing the approved accounts within the alephium namespace
const namespaces: SessionTypes.Namespaces = {
alephium: {
methods: requiredNamespace.methods,
events: requiredNamespace.events,
accounts: [`${chain}:${signerPublicKey}/default`]
}
}
const { acknowledged } = await signClient.approve({
id: proposalEvent.id,
relayProtocol: proposalEvent.params.relays[0].protocol,
namespaces
})
await acknowledged()
}
When the user rejects a session proposal, the wallet should call reject
to send the reject response:
import { getSdkError } from '@walletconnect/utils';
const rejectProposal = async (proposalEventId: number) => {
await signClient.reject({ id: proposalEventId, reason: getSdkError('USER_REJECTED') })
}
Handling Requests from dApp
The dApp sends a request when it needs the wallet to perform an action, such as signing a transaction. Different request types are distinguished by the request method. You can view the request types that the Alephium wallet needs to implement here. Here is an example of implementing the alph_signAndSubmitTransferTx
request:
import { SignTransferTxParams, TransactionBuilder } from '@alephium/web3'
signClient.on('session_request', onSessionRequest)
const onSessionRequest = async (event: SignClientTypes.EventArguments['session_request']) => {
const request = event.params.request
switch (request.method) {
case 'alph_signAndSubmitTransferTx':
const params = request.params as SignTransferTxParams
const signer = getSignerByAddress(params.signerAddress)
const result = await TransactionBuilder.from(nodeProvider).buildTransferTx(params, signer.publicKey)
const signature = signer.sign(result.txId)
await nodeProvider.transactions.postTransactionsSubmit({ unsignedTx: result.unsignedTx, signature })
}
}
You can refer to the code here for the implementation of other requests.
Managing Multiple dApp Sessions
WalletConnect allows a wallet to manage multiple sessions with various dApps. This is useful for users who interact with several dApps simultaneously and want to manage different sessions within a single wallet. The wallet should update the sessions after the proposal is approved:
const { topic, acknowledged } = await signClient.approve(...)
await acknowledged()
const dAppUrl = proposalEvent.params.proposer.metadata.url
updateSessions(dAppUrl, topic)
You can refer to the code here for more information.
Disconnecting
You can call the disconnect
method to disconnect from the wallet:
await signClient.disconnect({ topic })
The wallet also needs to handle disconnection requests initiated by the dApp:
signClient.on('session_delete', () => { /* disconnect handler */ })