Connect Your Lit-Powered Cloud Wallet to the Decentralized Web
Learn how to use WalletConnect to connect PKPs to your dApp.
Developers can use Lit and WalletConnect to seamlessly connect Lit-powered cloud wallets to hundreds of decentralized applications. This integration unlocks unique use cases for programmatic signing and condition-based automation, from DeFi to gaming, ultimately helping users streamline their experiences across the dWeb.
This guide touches upon the core pieces you need to add Lit and WalletConnect in your app. To see the pieces in action, check out the live demo and tinker with the code.
How Lit and WalletConnect work together
Each PKP (programmable key pair) is a cloud wallet where encrypted shares of its private key are distributed across Lit's network of nodes, so no party knows or holds the whole private key. Using ECDSA for digital signatures, PKPs can read and write data to any blockchain that also utilizes ECDSA, including Ethereum, most EVM chains, and more.
WalletConnect enables secure communication between wallets and dApps through QR code scanning and deep linking. Through WalletConnect, PKPs can interact with WalletConnect-enabled apps, sign data, and execute transactions without revealing private keys.
To connect a PKP and a dApp, all you need to do is:
- Create a Lit PKP Wallet object
- Initialize WalletConnect
- Subscribe and respond to events
1. Create a Lit PKP Wallet object
LitPKP is a wrapper of PKPWallet, a Wallet class that extends ether.js Signer
and provides convenient methods to sign transactions and messages using Lit Actions.
LitPKP
includes added functionality to handle Ethereum JSON RPC signing requests, which will be used to respond to requests facilitated through WalletConnect.
To create a new LitPKP
instance, you'll need:
- your PKP's public key
- an
authSig
that can be obtained from the client side by invoking checkAndSignAuthMessage - the RPC URL of the network that your PKP should be using
import LitJsSdk from 'lit-js-sdk';
import { LitPKP } from 'lit-pkp-sdk';
const publicKey =
'0x0439e24fbe3332dd2abe3073f663a58fc74674095e5834ebbe7a86fd52f1cbe54b8268d6426fbd66a6979d787b6848b750f3a64a6354da4616f93a3031f3d44e95';
const authSig = await LitJsSdk.checkAndSignAuthMessage({
chain: 'mumbai',
});
const rpcUrl = 'https://rpc-mumbai.maticvigil.com/';
const wallet = new LitPKP({
pkpPubKey: publicKey,
controllerAuthSig: authSig,
provider: rpcUrl,
});
await wallet.init();
If you need a PKP, mint a PKP NFT at the Lit Explorer. You'll need some test Matic in your wallet.
2. Initialize WalletConnect V1 client
To initialize a WalletConnect V1 connector, you'll need a uri
from a dApp. You can get a uri
by visiting this example dApp, tapping 'Connect to WalletConnect' button, and copying the QR code to your clipboard.
import WalletConnect from '@walletconnect/client';
// Create connector
const connector = new WalletConnect({
// Replace this value with the dApp's URI you copied
uri: 'wc:8a5e5bdc-a0e4-47...TJRNmhWJmoxdFo6UDk2WlhaOyQ5N0U=',
// Replace the following details with your own app's info
clientMeta: {
description: 'WalletConnect Developer App',
url: 'https://walletconnect.org',
icons: ['https://walletconnect.org/walletconnect-logo.png'],
name: 'WalletConnect',
},
});
You can also create a WalletConnect connector with an existing session
object, which is automatically stored in the browser's local storage as walletconnect
. You can specify the local storage key by using the storageId
parameter when creating a new connector.
3. Subscribe and respond to events
Once the connector is initialized, the dApp will request to connect to your PKP. To respond to requests from the dApp, you'll need to subscribe to WalletConnect events.
When the subscribed event fires, the connector will respond by invoking the callback function you passed to the event listener.
// Subscribe to session requests
connector.on('session_request', (error, payload) => {
if (error) {
throw error;
}
// Handle session request here
});
// Subscribe to call requests
connector.on('call_request', (error, payload) => {
if (error) {
throw error;
}
// Handle call request here
});
connector.on('disconnect', (error, payload) => {
if (error) {
throw error;
}
// Handle disconnect here
});
You can find more events to listen to in the docs. You should subscribe to at least these events:
session_request
: when a dApp requests to connect to your PKPcall_request
: when a dApp wants your PKP to sign messages or send transactionsdisconnect
: when a dApp disconnects from your PKP
Handling session requests
A session_request
event will fire when a dApp requests to connect to your PKP.
Example session_request
payload from the dApp:
{
id: 1,
jsonrpc: '2.0'.
method: 'session_request',
params: [{
peerId: '15d8b6a3-15bd-493e-9358-111e3a4e6ee4',
peerMeta: {
name: "WalletConnect Example",
description: "Try out WalletConnect v1.0",
icons: ["https://example.walletconnect.org/favicon.ico"],
url: "https://example.walletconnect.org"
}
}]
}
You can approve or reject the session request by calling approveSession
or rejectSession
on the connector.
When approving a session, you will need to pass in a chainId
and accounts
array. The chainId
should be the chain ID of the network your PKP is connected to. The accounts
array should include just the ETH address of your PKP.
// Approve session
connector.approveSession({
accounts: [address],
chainId: chainId,
});
// Reject Session
connector.rejectSession({
message: 'OPTIONAL_ERROR_MESSAGE', // Optional
});
Upon approval of the session request, the connection between the PKP and the dApp is established. You can confirm the connection by checking the connected
property on the connector.
Handling call requests
Once the connection is established, the dApp can now send call requests to your PKP to sign messages, send transactions, and more, triggering call_request
events.
Example call_request
payload from the dApp:
{
id: 1,
jsonrpc: '2.0'.
method: 'eth_sign',
params: [
"0xbc28ea04101f03ea7a94c1379bc3ab32e65e62d3",
"My email is john@doe.com - 1537836206101"
]
}
Handle the call request by invoking approveRequest
or rejectRequest
on the connector.
When approving a call request, you will need to provide the result
from handling the request. You can use the LitPKP
Wallet object to generate the results you need for requests that require interacting with your PKP. Those signing requests include:
- eth_sign
- personal_sign
- signTypedData
- signTypedData_v1
- signTypedData_v3
- signTypedData_v4
- signTransaction
- sendTransaction
// Sign with PKP Wallet
const result = await wallet.signEthereumRequest(payload);
// Approve request
connector.approveRequest({
id: payload.id,
result: result,
});
// Reject request
connector.rejectRequest({
id: payload.id,
error: {
message: 'OPTIONAL_ERROR_MESSAGE', // Optional
},
});
The expected payloads and results for Ethereum JSON RPC signing requests are specified here.
Disconnecting from the dApp
To disconnect the PKP from the dApp, call killSession
on the connector.
connector.killSession();
Testing your app
To test your Lit and WalletConnect V1 integration, you can connect your PKP to these testnet dApps:
Add test tokens to your PKP using these faucets:
PKPs are still in development on the Serrano Testnet, so do not store anything of value on your PKPs at this time.
Migrating to WalletConnect V2
This guide uses WalletConnect V1, which will be deprecated on March 1, 2023. You can find more information about the deprecation here.
The setup is quite similiar. Since both WalletConnect versions have implemented JSON RPC methods, you can still use lit-pkp-sdk
to handle signing requests.
import SignClient from '@walletconnect/sign-client';
// 1. Create a Lit PKP Wallet object
// Same as above
// 2. Initialize WalletConnect V2 client
const signClient = await SignClient.init({
// You'll need to sign up for a project ID. Visit https://cloud.walletconnect.com/sign-in
projectId: '<YOUR PROJECT ID>',
// optional parameters
relayUrl: '<YOUR RELAY URL>',
metadata: {
name: 'Wallet name',
description: 'A short description for your wallet',
url: "<YOUR WALLET'S URL>",
icons: ["<URL TO WALLET'S LOGO/ICON>"],
},
});
// 3. Subscribe and respond to events
signClient.on('session_proposal', event => {
// Session proposals are connection requests
});
signClient.on('session_request', event => {
// Session requests are JSON RPC calls
// Parse SignClient events
const { topic, params } = event;
// Sign with PKP Wallet
const result = await wallet.signEthereumRequest(params.request);
});
Building the open web
Now that you've learned how to connect PKPs to dApps, it's time to build apps that utilizes Lit's decentralized key management network. Find inspiration here and check out our grants program.
If you run into any issues, feel free to reach out to us on Discord.