Skip to content

How to Integrate Pimlico with Lit Protocol OTP Authentication

This how-to guide will walk you through the steps to integrate Lit Protocol's OTP sign-in with email, SMS, and Whatsapp with a smart account whose user operations are relayed and sponsored by Pimlico.

Stytch will be used to manage the OTP authentication flow.

Install the required packages

npm install stytch @lit-protocol/pkp-ethers @lit-protocol/lit-auth-client @lit-protocol/auth-helpers @lit-protocol/types @lit-protocol/lit-node-client-nodejs

Make an account with Stytch and get the Project ID and Secret

You can sign up for a Stytch account here. Once you have an account, you can find your Project ID and Secret in the Stytch Dashboard API Keys page.

Create a Stytch client with your Project ID and Secret

const stytchClient = new stytch.Client({
    project_id: "project-test-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",

Send an OTP to the user's email, SMS, or Whatsapp

const stytchResponse = await{
    email: "<Your Email Address>",

Authenticate the user with the OTP and get a session token

const authResponse = await stytchClient.otps.authenticate({
    method_id: stytchResponse.email_id,
    code: otpResponse.code,
    session_duration_minutes: 60 * 24 * 7,
const sessionStatus = await stytchClient.sessions.authenticate({
    session_token: authResponse.session_token,

Get a Lit Relay Server API Key

You can get a Lit Relay Server API Key by filling out the Lit Protocol team's form

Mint a PKPs through Lit Protocol

const litClient = new LitAuthClient({
    litRelayConfig: {
        relayApiKey: '<Your Lit Relay Server API Key from the previous step>',
const session = litClient.initProvider(ProviderType.StytchOtp, {
    userId: sessionStatus.session.user_id,
const authMethod = await session.authenticate({ 
    accessToken: sessionStatus.session_jwt 
await session.mintPKPThroughRelayer(authMethod)
const pkps = await session.fetchPKPsThroughRelayer(authMethod)

Generate the Controller Session Signatures

const litNodeClient = new LitNodeClientNodeJs({
    litNetwork: 'serrano',
    debug: false,
await litNodeClient.connect();
const resourceAbilities = [
        resource: new LitActionResource("*"),
        ability: LitAbility.PKPSigning,
const sessionKeyPair = litNodeClient.getSessionKey();
const authNeededCallback = async (params: AuthCallbackParams) => {
    const response = await litNodeClient.signSessionKey({
        sessionKey: sessionKeyPair,
        statement: params.statement,
        authMethods: [authMethod],
        pkpPublicKey: pkp[pkp.length - 1].publicKey,
        expiration: params.expiration,
        resources: params.resources,
        chainId: 1,
    return response.authSig;
const sessionSigs = await litNodeClient.getSessionSigs({
    chain: "ethereum",
    expiration: new Date( + 1000 * 60 * 60 * 24 * 7).toISOString(),
    resourceAbilityRequests: resourceAbilities,
    sessionKey: sessionKeyPair,
}).catch((err) => {
    console.log("error while attempting to access session signatures: ", err)
    throw err;

Initialize the PKP Wallet

We will now generate a wallet that can act a regular Ethers.js wallet, but will use the PKPs minted through Lit Protocol to sign transactions under the hood.

const pkpWallet = new PKPEthersWallet({
    pkpPubKey: pkp[pkp.length - 1].publicKey,
    rpc: "<standard RPC URL for the chain you are using>", // e.g.
    controllerSessionSigs: sessionSigs
await pkpWallet.init();

Use the PKP Wallet to sign user operations and send them through Pimlico

You can now use the pkpWallet as a regular Ethers.js wallet to sign user operations. To submit a user operation to Pimlico, you can follow the steps to sponsor a user operation with Pimlico's verifying paymaster and/or submit a user operation through Pimlico's bundler. If you would like to integrate Lit Protocol with the full flow of generating, signing, and submitting a user operation, you can follow the steps in tutorial 1, replacing the signing step with the PKP wallet and using pkpWallet.address as the owner address of the smart account.

Modified from tutorial 1, an example of how to use the PKP wallet to sign a user operation is shown below:

const signature = await pkpWallet.signMessage(
	ethers.utils.arrayify(await entryPoint.getUserOpHash(userOperation)),
userOperation.signature = signature

And an example of how you would generate the initCode for a SimpleAccount using the PKP wallet is shown below:

const initCode = ethers.utils.hexConcat([
	simpleAccountFactory.interface.encodeFunctionData("createAccount", [pkpWallet.address, 0]),