Skip to content

How to use a custom paymaster with accounts in permissionless.js

permissionless.js allows you to define custom logic to select the paymaster (if any) to use with the accounts you control. This allows you to use your own paymaster or even use multiple paymasters in different scenarios.

Create the clients

First we must create the public, bundler, and (optionally) paymaster clients that will be used to interact with the Safe account.

export const publicClient = createPublicClient({
	transport: http("https://CHAIN.infura.io/v3/API_KEY"),
});
 
export const bundlerClient = createPimlicoBundlerClient({
	transport: http(
		"https://api.pimlico.io/v1/CHAIN/rpc?apikey=API_KEY",
	),
});
 
export const paymasterClient = createPimlicoPaymasterClient({
	transport: http(
		"https://api.pimlico.io/v2/CHAIN/rpc?apikey=API_KEY",
	),
});

Create an account

Now, create an account. This can any of the accounts supported by permissionless.js or custom accounts conforming to the interface. For this example, we'll use a Safe account.

const safeAccount = await privateKeyToSafeSmartAccount(publicClient, {
	privateKey: "0xPRIVATE_KEY",
	safeVersion: "1.4.1",
	entryPoint: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" // global entrypoint
});

Create the smart account client with the custom paymaster logic

When creating the smartAccountClient, we can pass in a sponsorUserOperation function that will be called before a user operation is signed and sent. This function must return the paymasterAndData, preVerificationGas, verificationGasLimit, and callGasLimit fields.

Pimlico Paymaster
const smartAccountClient = createSmartAccountClient({
	account: safeAccount,
	chain: sepolia,
	transport: http(
		"https://api.pimlico.io/v1/CHAIN/rpc?apikey=API_KEY",
	),
	sponsorUserOperation: paymasterClient.sponsorUserOperation, // optional
});

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 gasPrices = await bundlerClient.getUserOperationGasPrice();
 
const txHash = await smartAccountClient.sendTransaction({
	to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
	value: parseEther("0.1"),
	maxFeePerGas: gasPrices.fast.maxFeePerGas, // if using Pimlico
	maxPriorityFeePerGas: gasPrices.fast.maxPriorityFeePerGas, // if using Pimlico
});