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: "https://chain-rpc.litprotocol.com/http",
});
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: [
"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,
});