Skip to main content

Paymaster

AbstractionKit includes two paymaster clients. CandidePaymaster can sponsor one-shot UserOperations, but new integrations should prefer the standard Erc7677Paymaster for one-shot sponsorship and ERC-20 token gas flows. Erc7677Paymaster auto-detects supported providers such as Candide and Pimlico and handles provider-specific metadata, supported token data, exchange rates, and paymaster addresses internally. Use CandidePaymaster when you need Candide-specific APIs or the EntryPoint v0.9 two-phase commit -> sign -> finalize flow.

Supports:

  • Gas sponsorship through Gas Policies
  • ERC-20 Token Sponsorship
  • Multi-EntryPoint

Which Paymaster Client to Use

FlowRecommended clientMethod
Sponsored gas, one-shotErc7677PaymasterRecommended standard path: createPaymasterUserOperation(..., context?)
ERC-20 token gasErc7677PaymastercreatePaymasterUserOperation(..., { token })
Provider metadata and supported token details for Candide/PimlicoErc7677PaymasterHandled automatically from the paymaster URL
Candide-specific one-shot sponsorshipCandidePaymasterSupported via createSponsorPaymasterUserOperation(...)
Two-phase paymaster signing to reduce user waiting timeCandidePaymastercreateSponsorPaymasterUserOperation(..., { signingPhase })

Usage

Import

import { CandidePaymaster } from "abstractionkit";

How to Use

Initialize a Paymaster with your RPC URL. Get an API key from the dashboard.

paymaster.ts
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC = "https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

Then consume Paymaster methods:

const supportedEntryPoints = await paymaster.getSupportedEntrypoints();

Methods

createSponsorPaymasterUserOperation

Returns the paymaster data if the the userOperation has a Gas Policy. Otherwise it returns an error message. Supports two types of Gas Policies:

  • Public Gas Policies: These are gas policies provided by third parties, which do not require a sponsorship policy ID.
  • Private Gas Policies: These require a sponsorship policy ID and can be used if no public gas policy matches the user operation.

Usage

example.ts
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC = "https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);
const sponsorshipPolicyId = '1234';

// Use createUserOperation() to help you construct a userOp
const userOperation = smartAccount.createUserOperation(..)

const { userOperation: sponsoredUserOperation, sponsorMetadata } =
await paymaster.createSponsorPaymasterUserOperation(
smartAccount,
userOperation,
bundlerUrl,
sponsorshipPolicyId, // optional
);

Source code

createSponsorPaymasterUserOperation

fetchSupportedERC20TokensAndPaymasterMetadata

Returns a promise with the supported erc20 tokens and their exchange rate, along with the paymaster metadata

Usage

example.ts
import { CandidePaymaster, SafeAccountV0_3_0 as SafeAccount } from "abstractionkit";

const paymasterRPC="https://api.candide.dev/public/v3/11155111";
const paymaster = new CandidePaymaster(paymasterRPC);

const supportedERC20Tokens = await paymaster.fetchSupportedERC20TokensAndPaymasterMetadata(SafeAccount.DEFAULT_ENTRYPOINT_ADDRESS);
Example Response
{
tokens: [
{
name: 'USD Coin',
symbol: 'USDC',
address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
decimals: 6,
exchangeRate: 123n
},
{
name: 'Tether USDT',
symbol: 'USDT',
address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
decimals: 6,
exchangeRate: 123n
},
{
name: 'Dai Stablecoin',
symbol: 'DAI',
address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
decimals: 18,
exchangeRate: 123n
}
],
paymasterMetadata: {
name: 'CANDIDE Paymaster',
description: 'CANDIDE Paymaster a fast, secure and feature-rich 4337 Paymaster',
icons: [],
address: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
sponsoredEventTopic: '0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471',
dummyPaymasterAndData: {
paymaster: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
paymasterVerificationGasLimit: '0xffff',
paymasterPostOpGasLimit: '0xffff',
paymasterData: '0x00010000000000ffff000000000000000000000000000000000000000000000000000000000000ffff010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101011c'
}
}
}

createTokenPaymasterUserOperation

Estimates gas limits and returns the user operation with the paymaster data for ERC-20 Token sponsorship.

Token Approval Reset

Some tokens like USDT do not allow changing a non-zero allowance directly. For known tokens (e.g. USDT on Ethereum mainnet), the SDK automatically prepends an approve(0) call before the actual approval. You can also manually trigger this behavior for other tokens by setting resetApproval: true in the overrides.

The overrides parameter also accepts an entrypoint field, which allows you to manually specify the EntryPoint address instead of relying on auto-detection from the UserOperation structure.

Usage

import { SafeAccountV0_3_0 as SafeAccount, CandidePaymaster } from "abstractionkit";

const paymasterRPC="https://api.candide.dev/public/v3/11155111";
const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT test token
const bundlerRPC = "https://api.candide.dev/public/v3/11155111";

const ownerPublicAddress = "0x2Ef844456580b6e1E22e1D584EBbC2467D9298B2"
const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress])

// Use createUserOperation() to help you construct a userOp
let userOperation = smartAccount.createUserOperation(..);

const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

const { userOperation: sponsoredUserOperation, tokenQuote } =
await paymaster.createTokenPaymasterUserOperation(
smartAccount,
userOperation,
erc20TokenAddress,
bundlerRPC,
);
userOperation = sponsoredUserOperation;
Example Response
{
userOperation: {
sender: '0xb8741a449d50ed0dcfe395287f85be152884c8d9',
nonce: 10n,
initCode: '0x',
callData: '0x541d63c8...095ea7b3...',
callGasLimit: 116807n,
verificationGasLimit: 75441n,
preVerificationGas: 50444n,
maxFeePerGas: 66195658616n,
maxPriorityFeePerGas: 120000n,
paymasterAndData: '0x3fE285DcD76BCcE4Ac92d38A6F2F8E964041e020...',
signature: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
},
tokenQuote: {
token: '0xFa5854FBf9964330d761961F46565AB7326e5a3b',
exchangeRate: 1001219705870085130n,
tokenCost: 3391931975665260n,
},
}

Source code

createTokenPaymasterUserOperation

calculateUserOperationErc20TokenMaxGasCost

Calculates the maximum gas cost in ERC-20 tokens for a given userOperation

Usage

import { CandidePaymaster } from "abstractionkit";

const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT test token
// Use createUserOperation() to help you construct a userOp
const userOperation = smartAccount.createUserOperation(..)

const paymasterRPC="https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

const cost = await paymaster.calculateUserOperationErc20TokenMaxGasCost(
userOperation,
erc20TokenAddress,
);
Example Response
3391931975665260

Source code

calculateUserOperationErc20TokenMaxGasCost

Advanced Methods

getPaymasterMetaData

Returns the metadata associated with the Paymaster, along with dummyPaymasterAndData useful for gas estimates

Usage

example.ts
import { CandidePaymaster, SafeAccountV0_3_0 as SafeAccount } from "abstractionkit";

const paymasterRPC="https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

const paymasterResult = await paymaster.getPaymasterMetaData(SafeAccount.DEFAULT_ENTRYPOINT_ADDRESS);
Example Response
{
name: 'CANDIDE Paymaster',
description: 'CANDIDE Paymaster a fast, secure and feature-rich 4337 Paymaster',
icons: [],
address: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
sponsoredEventTopic: '0xa050a122b4c0e369e3385eb6b7cccd8019638b2764de67bec0af99130ddf8471',
dummyPaymasterAndData: {
paymaster: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
paymasterVerificationGasLimit: '0xffff',
paymasterPostOpGasLimit: '0xffff',
paymasterData: '0x00010000000000ffff000000000000000000000000000000000000000000000000000000000000ffff010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101011c'
}
}

source code

getPaymasterMetaData

getSupportedEntrypoints

Returns the supported Entrypoints by the paymaster

Usage

example.ts
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC="https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

const paymasterResult = await paymaster.getSupportedEntrypoints();
Example Response
[
'0x0000000071727De22E5E9d8BAf0edAc6f37da032',
'0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
]

source code

getSupportedEntrypoints

isSupportedERC20Token

Checks if a particular ERC-20 token is accepted as gas payment by the paymaster. Returns a boolean

Usage

example.ts
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC="https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT on sepolia testnet

const isSupported = await paymaster.isSupportedERC20Token(erc20TokenAddress);
Example Response
true

Source

isSupportedERC20Token

getSupportedERC20TokenData

Returns the token data given an erc20 address

Usage

example.ts
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC = "https://api.candide.dev/public/v3/11155111";

const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);
const erc20TokenData = await paymaster.getSupportedERC20TokenData(erc20TokenAddress);
Example Response
{
name: 'Candide Test Token',
symbol: 'CTT',
address: '0xFa5854FBf9964330d761961F46565AB7326e5a3b',
decimals: 18,
fee: 0n,
exchangeRate: 1001219705870085130n
}

Source code

getSupportedERC20TokenData

fetchTokenPaymasterExchangeRate

Fetches the current exchange rate for an ERC-20 token from the paymaster. The exchange rate represents the token cost per unit of native currency, used to calculate how much of the token is needed to cover gas fees.

Usage

example.ts
import { CandidePaymaster } from "abstractionkit";

const paymasterRPC = "https://api.candide.dev/public/v3/11155111";
const paymaster: CandidePaymaster = new CandidePaymaster(paymasterRPC);

const erc20TokenAddress = "0xFa5854FBf9964330d761961F46565AB7326e5a3b"; // CTT on sepolia testnet

const exchangeRate = await paymaster.fetchTokenPaymasterExchangeRate(erc20TokenAddress);
Example Response
1001219705870085130n

Source code

fetchTokenPaymasterExchangeRate

createPaymasterUserOperation

Internal Method

createPaymasterUserOperation is an internal method. Use createSponsorPaymasterUserOperation for gas sponsorship or createTokenPaymasterUserOperation for ERC-20 token payment instead.

Source code

createPaymasterUserOperation

Erc7677Paymaster

Erc7677Paymaster is a provider-agnostic ERC-7677 paymaster client. It speaks pm_getPaymasterStubData and pm_getPaymasterData, so it works with any compliant provider (Candide, Pimlico, Alchemy, or a self-hosted paymaster). Provider is auto-detected from the RPC URL; for Candide and Pimlico it also fetches exchange rates and paymaster addresses automatically for ERC-20 token flows.

For new one-shot sponsorship and ERC-20 gas flows, prefer Erc7677Paymaster. For Candide and Pimlico URLs, it handles provider-specific metadata and token quote details behind the scenes. CandidePaymaster also supports one-shot sponsorship, but it is most useful when you need Candide-specific APIs or two-phase paymaster signing with signingPhase: "commit" and signingPhase: "finalize", which lets the user sign while the paymaster prepares its signature in parallel and reduces wallet or remote-signer latency.

Usage

Import

import { Erc7677Paymaster } from "abstractionkit";
example.ts
import { Erc7677Paymaster } from "abstractionkit";

const bundlerRpc = "https://api.candide.dev/public/v3/11155111";
const paymaster = new Erc7677Paymaster(bundlerRpc);

// Use createUserOperation() to help you construct a userOp
let userOperation = await smartAccount.createUserOperation(/* ... */);

const { userOperation: sponsoredOp } = await paymaster.createPaymasterUserOperation(
smartAccount,
userOperation,
bundlerRpc,
{ sponsorshipPolicyId: "sp_..." }, // provider-specific context
);
userOperation = sponsoredOp;

ERC-20 token sponsorship

Passing { token } in the context triggers the ERC-20 gas flow automatically. For Candide and Pimlico the exchange rate is fetched for you; for unknown providers, supply exchangeRate in the context. tokenQuote surfaces the exchange rate and max token cost applied to the UserOperation so you can display the charge to the user without a second round trip.

example.ts
const bundlerRpc = "https://api.candide.dev/public/v3/11155111";
const usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; // USDC on sepolia

// Candide or Pimlico (auto-detected): exchange rate fetched from the RPC
const { userOperation: tokenOp, tokenQuote } = await paymaster.createPaymasterUserOperation(
smartAccount,
userOperation,
bundlerRpc,
{ token: usdcAddress },
);
userOperation = tokenOp;

// Unknown provider: supply exchangeRate (scaled by 1e18)
const { userOperation: tokenOp2 } = await paymaster.createPaymasterUserOperation(
smartAccount,
userOperation,
bundlerRpc,
{ token: usdcAddress, exchangeRate: "1000000000000000000" },
);
userOperation = tokenOp2;

Source code

Erc7677Paymaster