Connect Your Lit-Powered Cloud Wallet to the Decentralized Web

Learn how to use WalletConnect to connect PKPs to your dApp.

Lit and WalletConnect

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:

  1. Create a Lit PKP Wallet object
  2. Initialize WalletConnect
  3. 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 PKP
  • call_request: when a dApp wants your PKP to sign messages or send transactions
  • disconnect: 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.


Resources