AA21 didn't pay prefund
The EntryPoint contract reverts with AA21 didn't pay prefund when a smart accountMetaMask smart account A smart contract account created using the Smart Accounts Kit that supports programmable behavior, flexible signing options, and ERC-7710 delegations. doesn't
have enough native token balance to cover the gas cost of the user operationUser operation A pseudo-transaction object defined by ERC-4337 that describes what a smart account should execute. User operations are submitted to the alternate mempool managed by bundlers..
Before executing a user operation, the EntryPoint requires the sender account to prefund the
expected gas cost. If the account's balance is lower than the required prefund, the EntryPoint
reverts the operation.
Solution
Fund the smart account
Fund the smart account with enough native tokens to cover the required prefund.
Use Viem's estimateUserOperationGas
to get the gas estimates from your bundlerBundler An ERC-4337 component that manages the alternate mempool: it collects user operations from smart accounts, packages them, and submits them to the network., then calculate the required prefund based on the
EntryPoint version.
- EntryPoint v0.7
- EntryPoint v0.6
import { formatEther } from 'viem'
const gasEstimate = await bundlerClient.estimateUserOperationGas({
account: smartAccount,
calls: [{ to: '0x...', value: 0n }],
})
const { maxFeePerGas } = await publicClient.estimateFeesPerGas()
const requiredGas =
gasEstimate.verificationGasLimit +
gasEstimate.callGasLimit +
(gasEstimate.paymasterVerificationGasLimit ?? 0n) +
(gasEstimate.paymasterPostOpGasLimit ?? 0n) +
gasEstimate.preVerificationGas
const requiredPrefund = requiredGas * maxFeePerGas
const balance = await publicClient.getBalance({
address: smartAccount.address,
})
if (balance < requiredPrefund) {
console.log(
`Insufficient balance: account has ${formatEther(balance)} ETH, ` +
`but needs ${formatEther(requiredPrefund)} ETH`
)
}
import { formatEther } from 'viem'
const gasEstimate = await bundlerClient.estimateUserOperationGas({
account: smartAccount,
calls: [{ to: '0x...', value: 0n }],
})
const { maxFeePerGas } = await publicClient.estimateFeesPerGas()
const requiredGas =
gasEstimate.callGasLimit + gasEstimate.verificationGasLimit + gasEstimate.preVerificationGas
const requiredPrefund = requiredGas * maxFeePerGas
const balance = await publicClient.getBalance({
address: smartAccount.address,
})
if (balance < requiredPrefund) {
console.log(
`Insufficient balance: account has ${formatEther(balance)} ETH, ` +
`but needs ${formatEther(requiredPrefund)} ETH`
)
}
Use a paymaster
You can use a paymasterPaymaster A service that pays for user operations on behalf of a smart account. to sponsor the gas fees for the smart account, so the account doesn't need to hold native tokens. For more information about configuring a paymaster, see Send a gasless transaction.