Skip to content

How to use Pimlico Lock

This guide will show you how to use Pimlico Lock to request ETH from the MagicSpend++ contracts.

Steps

Define imports and create the clients

import { createSmartAccountClient } from "permissionless"
import { Address, Hex, createPublicClient, http, parseEther, toHex } from "viem"
import { sepolia } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
import { entryPoint07Address } from "viem/account-abstraction";
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { toSimpleSmartAccount } from "permissionless/accounts";
 
import "dotenv/config"
import { getPimlicoUrl, MagicSpend } from "./magic-spend";
 
const RPC_URL = "https://11155111.rpc.thirdweb.com"
const ETH: Address = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
const amount = "0.001"; // 0.001 ETH
 
 
const PRIVATE_KEY = process.env.ACCOUNT_PRIVATE_KEY;
 
if (PRIVATE_KEY === undefined) {
    throw new Error("ACCOUNT_PRIVATE_KEY env var is required")
}
 
const pimlicoUrl = getPimlicoUrl(sepolia.id);
 
export const publicClient = createPublicClient({
	transport: http(RPC_URL),
	chain: sepolia,
})
  
const pimlicoClient = createPimlicoClient({
	transport: http(pimlicoUrl),
    chain: sepolia,
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
})
 
 
// This is the address of the account that owns the stake
const signer = privateKeyToAccount(PRIVATE_KEY as Hex)
console.log(`Signer address: ${signer.address}`)
 
const simpleAccount = await toSimpleSmartAccount({
    client: publicClient,
    owner: signer,
    entryPoint: {
        address: entryPoint07Address,
        version: "0.7",
    },
});
 
const smartAccountClient = createSmartAccountClient({
    account: simpleAccount,
    chain: sepolia,
    bundlerTransport: http(pimlicoUrl, {
        timeout: 60_000,
    }),
    userOperation: {
        estimateFeesPerGas: async () => {
            return (await pimlicoClient.getUserOperationGasPrice()).fast
        },
    },
    paymaster: pimlicoClient,
})
 
const magicSpend = new MagicSpend();
magicSpend.setChainId(sepolia.id);
 

Add funds in the resource lock

const [stakeAddress, stakeCalldata, stakeValue] = await magicSpend.prepareStake({
    type: "pimlico_lock",
    data: {
        token: ETH,
        amount: toHex(parseEther(amount)),
        unstakeDelaySec: "86400",
    }
})
 
await smartAccountClient.sendUserOperation({
    account: simpleAccount,
    calls: [
        {
            to: stakeAddress,
            data: stakeCalldata,
            value: parseEther(stakeValue),
        },
    ],
})
 

Get the stakes

const stakes = await magicSpend.getStakes({
    account: simpleAccount.address,
})
console.log(`Stakes: ${JSON.stringify(stakes, null, 2)}`);

Prepare the allowance

const allowance = await magicSpend.prepareAllowance({
    type: "pimlico_lock",
    data: {
        account: simpleAccount.address,
        token: ETH,
        amount: toHex(parseEther(amount)),
        recipient: simpleAccount.address,
    },
});
 
const signature = await signer.signTypedData({
    domain: {
        name: "Pimlico Lock",
        chainId: sepolia.id,
        verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
        version: "1",
    },
    types: {
        AssetAllowance: [
            { name: "token", type: "address" },
            { name: "amount", type: "uint128" },
            { name: "chainId", type: "uint128" },
        ],
        Allowance: [
            { name: "account", type: "address" },
            { name: "assets", type: "AssetAllowance[]" },
            { name: "validUntil", type: "uint48" },
            { name: "validAfter", type: "uint48" },
            { name: "salt", type: "uint48" },
            { name: "version", type: "uint32" },
            { name: "metadata", type: "bytes" },
        ],
    },
    primaryType: "Allowance",
    message: {
        account: allowance.account,
        assets: allowance.assets,
        validUntil: Number(allowance.validUntil),
        validAfter: Number(allowance.validAfter),
        salt: Number(allowance.salt),
        version: Number(allowance.version),
        metadata: allowance.metadata,
    },
});

Request a withdrawal sponsorship

const [contract, calldata] = await magicSpend.sponsorWithdrawal({
    type: "pimlico_lock",
    data: {
        allowance,
        signature,
    }
})

Execute the withdrawal

// Send user operation and withdraw funds
// You can add subsequent calls after the withdrawal, like "buy NFT on OpenSea for ETH"
const userOpHash = await smartAccountClient.sendUserOperation({
    account: simpleAccount,
    calls: [
        {
            to: contract,
            value: parseEther("0"),
            data: calldata,
        }
    ]
})
 
console.log(`Userop hash: ${userOpHash}`);
 
const receipt = await pimlicoClient.waitForUserOperationReceipt({
    hash: userOpHash
})
 
console.log(`Transaction hash: ${receipt.receipt.transactionHash}`);