Each example below is a self-contained Lit Action. Pass the code string to the /core/v1/lit_action endpoint with any required js_params. The pkpId parameter is the wallet address of the PKP you want to use, passed in via js_params.
1. Sign a Message
The simplest pattern: retrieve a PKP’s private key and sign an arbitrary message with it. The signature proves the message was attested by a specific, on-chain-registered key.
// js_params: { pkpId, message }
async function main({ pkpId, message }) {
const wallet = new ethers.Wallet(
await Lit.Actions.getPrivateKey({ pkpId })
);
const signature = await wallet.signMessage(message);
return { message, signature };
}
The caller can verify the signature against the PKP’s public key (or wallet address) to confirm the message originated from this action.
2. Encrypt a Secret
Encrypt a sensitive string so that only the holder of the PKP can later decrypt it. Useful for storing API keys, passwords, or personal data on-chain or in IPFS without exposing the plaintext.
// js_params: { pkpId, secret }
async function main({ pkpId, secret }) {
const ciphertext = await Lit.Actions.Encrypt({ pkpId, message: secret });
return { ciphertext };
}
Store the returned ciphertext anywhere — IPFS, a smart contract, a database — and retrieve the plaintext only when needed using the Decrypt action below.
3. Decrypt a Secret
Decrypt a ciphertext that was previously produced by Lit.Actions.Encrypt using the same PKP. Only an action that is permitted to use the PKP (enforced on-chain) can decrypt it.
// js_params: { pkpId, ciphertext }
async function main({ pkpId, ciphertext }) {
const plaintext = await Lit.Actions.Decrypt({ pkpId, ciphertext });
return { plaintext };
}
4. Fetch a Crypto Price and Sign It
Fetch the current price of ETH from a public API and sign the result. The caller receives both the price and a signature — a verifiable price proof that can be submitted to a smart contract as a trusted oracle update.
// js_params: { pkpId }
async function main({ pkpId }) {
const res = await fetch(
"https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"
);
const data = await res.json();
const price = data?.ethereum?.usd;
if (typeof price !== "number") {
return { error: "Price fetch failed" };
}
const payload = `ETH/USD: ${price}`;
const wallet = new ethers.Wallet(
await Lit.Actions.getPrivateKey({ pkpId })
);
const signature = await wallet.signMessage(payload);
return { price, payload, signature };
}
A smart contract can call ecrecover on the signature to confirm the price was signed by a specific, known PKP address — without trusting any off-chain intermediary.
5. Gate a Signature on Live Weather Data
Fetch live weather for a city using a decrypted API key and only sign a message if the temperature exceeds a threshold. Demonstrates combining decryption, an authenticated HTTP request, and conditional signing in one action.
// js_params: { pkpId, city, minTempCelsius, message, encryptedWeatherApiKey }
// Example: { pkpId: "0x...", city: "London", minTempCelsius: 20, message: "Approved", encryptedWeatherApiKey: "..." }
async function main({ pkpId, city, minTempCelsius, message, encryptedWeatherApiKey }) {
const apiKey = await Lit.Actions.Decrypt({ pkpId, ciphertext: encryptedWeatherApiKey });
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`
);
const data = await res.json();
const temp = data?.main?.temp;
if (typeof temp !== "number") {
return { error: "Weather fetch failed" };
}
if (temp < minTempCelsius) {
return { signed: false, reason: `Temperature ${temp}°C is below threshold of ${minTempCelsius}°C` };
}
const wallet = new ethers.Wallet(
await Lit.Actions.getPrivateKey({ pkpId })
);
const signature = await wallet.signMessage(message);
return { signed: true, temp, message, signature };
}
6. Read from a Smart Contract
Call a view function on an EVM smart contract and return the result. Useful for reading on-chain state (balances, governance votes, NFT ownership) inside an action, or for gating downstream logic on chain data.
// js_params: { pkpId, contractAddress, holderAddress }
// Checks the ERC-20 balance of holderAddress and signs the result.
async function main({ pkpId, contractAddress, holderAddress }) {
const rpcUrl = "https://mainnet.base.org";
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
const erc20Abi = [
"function balanceOf(address owner) view returns (uint256)",
"function symbol() view returns (string)",
];
const contract = new ethers.Contract(contractAddress, erc20Abi, provider);
const [balance, symbol] = await Promise.all([
contract.balanceOf(holderAddress),
contract.symbol(),
]);
const balanceFormatted = ethers.utils.formatUnits(balance, 18);
const payload = `${holderAddress} holds ${balanceFormatted} ${symbol}`;
const wallet = new ethers.Wallet(
await Lit.Actions.getPrivateKey({ pkpId })
);
const signature = await wallet.signMessage(payload);
return { holder: holderAddress, balance: balanceFormatted, symbol, payload, signature };
}
7. Send ETH to an Address
Construct, sign, and broadcast an ETH transfer transaction from a PKP wallet. The PKP pays the gas and the transfer amount, so ensure the PKP wallet holds sufficient ETH on the target chain before running this action.
// js_params: { pkpId, toAddress, amountEth, chainId, rpcUrl }
// Example: { pkpId: "0x...", toAddress: "0x...", amountEth: "0.001", chainId: 8453, rpcUrl: "https://mainnet.base.org" }
async function main({ pkpId, toAddress, amountEth, chainId, rpcUrl }) {
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(
await Lit.Actions.getPrivateKey({ pkpId }),
provider
);
const tx = await wallet.sendTransaction({
to: toAddress,
value: ethers.utils.parseEther(amountEth),
chainId,
});
const receipt = await tx.wait();
return {
txHash: receipt.transactionHash,
from: wallet.address,
to: toAddress,
amountEth,
blockNumber: receipt.blockNumber,
};
}
The PKP wallet at pkpId must hold enough ETH on the target chain to cover both the transfer amount and the gas fee. Use createWallet to get a PKP address, fund it on-chain, then use that address as pkpId.