Connecting PKPs to dApps
Leverage Lit Protocol and WalletConnect V2 to seamlessly connect PKPs to hundreds of dApps. WalletConnect enables secure communication between wallets and dApps through QR code scanning and deep linking. With WalletConnect, PKPs act as MPC wallets, interacting with dApps without ever exposing private keys.
This guide will show you how to implement this for an Ethereum wallet. If you'd like to do the same for Sui with PKPSuiWallet
or Cosmos with PKPCosmosWallet
.
Please note that this example requires you install the @walletconnect/web3wallet
package.
1. Create a PKPClient
Connecting a PKP to a dApp requires:
- Creation of a
PKPEthersWallet
- Initialization of
PKPWalletConnect
using thePKPEthersWallet
- Subscribing and responding to events
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import { LIT_NETWORK, LIT_ABILITY, LIT_RPC } from "@lit-protocol/constants";
import {
createSiweMessage,
generateAuthSig,
LitPKPResource
} from "@lit-protocol/auth-helpers";
import { PKPEthersWallet } from "@lit-protocol/pkp-ethers";
import { PKPWalletConnect } from "@lit-protocol/pkp-walletconnect";
import * as ethers from "ethers";
const ETHEREUM_PRIVATE_KEY = "<Your Ethereum private key>";
const LIT_PKP_PUBLIC_KEY = "<Your Lit PKP public key>";
const ethersWallet = new ethers.Wallet(
ETHEREUM_PRIVATE_KEY,
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
);
const litNodeClient = new LitNodeClient({
litNetwork: LIT_NETWORK.DatilDev,
debug: false,
});
await litNodeClient.connect();
const sessionSignatures = await litNodeClient.getSessionSigs({
chain: "ethereum",
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
resourceAbilityRequests: [
{
resource: new LitPKPResource("*"),
ability: LIT_ABILITY.PKPSigning,
},
],
authNeededCallback: async ({
uri,
expiration,
resourceAbilityRequests,
}) => {
const toSign = await createSiweMessage({
uri,
expiration,
resources: resourceAbilityRequests,
walletAddress: await ethersWallet.getAddress(),
nonce: await litNodeClient.getLatestBlockhash(),
litNodeClient,
});
return await generateAuthSig({
signer: ethersWallet,
toSign,
});
},
});
const pkpEthersWallet = new PKPEthersWallet({
litNodeClient,
pkpPubKey: LIT_PKP_PUBLIC_KEY!,
controllerSessionSigs: sessionSignatures
});
const pkpWalletConnect = new PKPWalletConnect();
pkpWalletConnect.addPKPEthersWallet(pkpEthersWallet);
The authContext
or controllerSessionSigs
are used to authorize requests to the Lit nodes. The only difference between these methods are that controllerSessionSigs
accepts the Session Signatures object, while authContext
accepts the same properties that Session Signatures do during creation. Those properties can be found here.
2. Initialize PKPWalletConnect
with the PKPClient
PKPWalletConnect
wraps @walletconnect/web3wallet
to manage WalletConnect session proposals and requests using the given PKPClient.
const config = {
projectId: "<Your WalletConnect project ID>",
metadata: {
name: "Test Lit Wallet",
description: "Test Lit Wallet",
url: "https://litprotocol.com/",
icons: ["https://litprotocol.com/favicon.png"],
},
};
await pkpWalletConnect.initWalletConnect(config);
3. Subscribe and respond to events
Session Proposal
Once the WalletConnect client is initialized, the PKP is ready to connect to dApps. The dApp will request to connect to your PKP through a session proposal. To respond to session proposals, subscribe to the session_proposal
event.
pkpWalletConnect.on("session_proposal", async (proposal) => {
console.log("Received session proposal: ", proposal);
// Accept session proposal
await pkpWalletConnect.approveSessionProposal(proposal);
// Log active sessions
const sessions = Object.values(pkpWalletConnect.getActiveSessions());
for (const session of sessions) {
const { name, url } = session.peer.metadata;
console.log(`Active Session: ${name} (${url})`);
}
});
To trigger the session proposal, visit any WalletConnect V2 compatible dApp to obtain an URI. For an example, navigate to WalletConnect's test dApp, choose 'Ethereum' network, and click "Connect". A "Connect wallet" modal should appear with a copy icon located at the top right. Click on the icon to copy the URI.
// Pair using the given URI
await pkpWalletConnect.pair({ uri: uri });
Session Request
Once the session proposal is approved, the dApp can then request your PKP to perform actions, such as signing, via a session request. To acknowledge and respond to these session requests, set up an event listener for the session_request
event.
pkpWalletConnect.on("session_request", async (requestEvent) => {
console.log("Received session request: ", requestEvent);
const { topic, params } = requestEvent;
const { request } = params;
const requestSession = signClient.session.get(topic);
const { name, url } = requestSession.peer.metadata;
// Accept session request
console.log(
`\nApproving ${request.method} request for session ${name} (${url})...\n`
);
await pkpWalletConnect.approveSessionRequest(requestEvent);
console.log(
`Check the ${name} dapp to confirm whether the request was approved`
);
});
Using SignClient
The @lit-protocol/pkp-walletconnect
library exposes base functionality needed to pair PKPs to dApps, approve and reject session proposals, and respond to session requests. For extended capabilities, you can retrieve WalletConnect's SignClient
from the PKPWalletConnect
instance.
const signClient = pkpWalletConnect.getSignClient();
Refer to the WalletConnect V2 docs for more information on their protocol and SDKs.
Not finding the answer you're looking for? Share your feedback on these docs by creating an issue in our GitHub Issues and Reports repository or get support by visiting our Support page.