Skip to main content
Version: v2.x.x

Using PKPs as Wallets

With PKPs, you can build secure, customizable MPC wallets that offer intuitive onboarding experiences without the pain of private key management.

The @lit-protocol/pkp-ethers package provides a familiar wallet interface that makes it easy to sign data, send transactions, and handle Ethereum JSON RPC requests using PKPs.

Initialize PKPEthersWallet

PKPEthersWallet must be initialized with an AuthSig or a SessionSig in order to authorize signing requests. To learn how to generate these signatures, refer to the Authentication section.

import { PKPEthersWallet } from "@lit-protocol/pkp-ethers";

const pkpWallet = new PKPEthersWallet({
controllerAuthSig: "<Your AuthSig>",
// Or you can also pass in controllerSessionSigs
pkpPubKey: "<Your PKP public key>",
rpc: "",
await pkpWallet.init();

To view more PKPEthersWallet constructor options, refer to the API docs.


Passing SessionSigs

When generating session signatures for PKPEthersWallet, be sure to request the ability to execute Lit Actions by passing the following object in the resourceAbilityRequests array:

resource: new LitActionResource('*'),
ability: LitAbility.LitActionExecution,

Sign Message

const message = "Free the web";
const hexMsg = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message));
await pkpWallet.signMessage(hexMsg);

Sign Typed Data

const example = {
domain: {
chainId: 80001,
name: "Ether Mail",
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
version: "1",
message: {
contents: "Hello, Bob!",
from: {
name: "Cow",
wallets: [
to: [
name: "Bob",
wallets: [
primaryType: "Mail",
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
Mail: [
{ name: "from", type: "Person" },
{ name: "to", type: "Person[]" },
{ name: "contents", type: "string" },
Person: [
{ name: "name", type: "string" },
{ name: "wallets", type: "address[]" },

const { types, domain, primaryType, message } = example;

if (types["EIP712Domain"]) {
delete types["EIP712Domain"];

await pkpWallet._signTypedData(domain, types, message);

Sign Transaction

const from = address;
const to = address;
const gasLimit = BigNumber.from("21000");
const value = BigNumber.from("10");
const data = "0x";

// @lit-protocol/pkp-ethers will automatically add missing fields (nonce, chainId, gasPrice, gasLimit)
const transactionRequest = {

const signedTransactionRequest = await pkpWallet.signTransaction(

Send Transaction

With the signed transaction from the example above,

await pkpWallet.sendTransaction(signedTransactionRequest);

Handle Ethereum JSON RPC Requests

The following Ethereum JSON RPC requests are supported:

  • eth_sign
  • personal_sign
  • eth_signTypedData
  • eth_signTypedData_v1
  • eth_signTypedData_v3
  • eth_signTypedData_v4
  • eth_signTransaction
  • eth_sendTransaction
  • eth_sendRawTransaction

Responding to requests is as easy as calling ethRequestHandler with a PKPEthersWallet instance and request payload.

import { ethRequestHandler } from "@lit-protocol/pkp-ethers";

const message = "Free the web";
const hexMsg = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message));

const payload = {
method: "personal_sign",
params: [
"<Ethereum address to sign with (should match the Ethereum address of your PKP)>",

const result = await ethRequestHandler({
signer: pkpWallet,
payload: payload,