Skip to main content
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.