Skip to main content

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

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: 'https://chain-rpc.litprotocol.com/http'
});
await pkpWallet.init();

The controllerAuthSig (or controllerSessionSigs) is used to authorize requests to the Lit nodes. To learn how to leverage different authentication methods, refer to the Authentication section.

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

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: [
'0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
'0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF',
],
},
to: [
{
name: 'Bob',
wallets: [
'0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
'0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57',
'0xB0B0b0b0b0b0B000000000000000000000000000',
],
},
],
},
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 = {
from,
to,
gasLimit,
value,
data,
};

const signedTransactionRequest = await pkpWallet.signTransaction(transactionRequest)

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: [hexMsg, '<Ethereum address to sign with (should match the Ethereum address of your PKP)>'],
};

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