Skip to content

How to use a Privy signer with permissionless.js

permissionless.js allows you to plug in custom signers to control the accounts that you create. Privy is an embedded wallet provider that allows you to easily onboard users to your dapp. It is possible to use Privy as a signer with permissionless.js, allowing you to use Privy to create and control smart accounts and sign transactions.

Additionally you may want to look at Privy's guide on working with permissionless.js here.

Install the dependencies

npm i @privy-io/react-auth @privy-io/wagmi-connector permissionless viem wagmi

Create the Privy provider

Following Privy's quickstart guide, set up the Privy provider in your app. Also integrate the PrivyWagmiConnector, which will allow you to use Privy as a signer with permissionless.js.

import { PrivyProvider } from '@privy-io/react-auth';
import { PrivyWagmiConnector } from '@privy-io/wagmi-connector';
 
<PrivyProvider
  appId={"<Privy-App-Id>"}
  config={{
    embeddedWallets: {
      createOnLogin: "all-users",
    },
  }}
>
    <PrivyWagmiConnector wagmiChainsConfig={configureChainsConfig}>
        {children}
    </PrivyWagmiConnector>
</PrivyProvider>;
 

Set Privy as the active wallet

In your app, set Privy's embedded wallet as the active wallet for wagmi

import { useWallets } from "@privy-io/react-auth";
import { usePrivyWagmi } from "@privy-io/wagmi-connector";
import { useEffect } from "react";
 
const { wallets } = useWallets();
const embeddedWallet = wallets.find(
  (wallet) => wallet.walletClientType === "privy"
);

Create the SmartAccountClient

Create the smart account client using the Privy signer. This is where you would configure what smart account implementation (e.g. Safe, Kernel, Biconomy, TrustWallet, SimpleAccount) and what paymaster logic you want to use.

import { createSmartAccountClient } from "permissionless";
import { toSimpleSmartAccount } from "permissionless/accounts";
import { useWalletClient } from "wagmi";
import { createPublicClient, http, zeroAddress } from "viem";
import { sepolia } from "viem/chains";
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { entryPoint07Address } from "viem/account-abstraction"
 
const {
  data: walletClient
} = useWalletClient()
 
const publicClient = createPublicClient({
  chain: sepolia, // or whatever chain you are using
  transport: http()
})
 
const pimlicoUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=<PIMLICO_API_KEY>`
 
const pimlicoClient = createPimlicoClient({
	transport: http(pimlicoUrl),
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	}
})
 
const owner = embeddedWallet.getEthereumProvider()
 
if (!owner) {
  throw new Error("No owner found")
}
 
const simpleSmartAccount = await toSimpleSmartAccount({
  owner: owner,
  client: publicClient,
  entryPoint: {
		address: entryPoint07Address,
		version: "0.7"
	}
})
 
const smartAccountClient = createSmartAccountClient({
  account: simpleSmartAccount,
  chain: sepolia,
  bundlerTransport: http(pimlicoUrl),
  paymaster: pimlicoClient,
  userOperation: {
    estimateFeesPerGas: async () => {
      return (await pimlicoClient.getUserOperationGasPrice()).fast
    },
  }
})

Send a transaction

You can now send transactions as normal. The sponsorUserOperation function will be called before each transaction is signed and sent, applying the custom paymaster logic you have set.

const txHash = await smartAccountClient.sendTransaction({
    to: zeroAddress,
    data: "0x",
    value: BigInt(0)
})

For a full example, see the example permissionless + Privy app.