Seneschal Paymaster LIVE

Pay ERC-4337 transaction gas in stablecoins on Base mainnet. No ETH required in your smart account; the paymaster fronts the gas and pulls USDC/EURC/USDT/DAI/cbBTC from you at Chainlink-quoted rates.

Status: v2 deployed, verified, profitable. Smoke-tested end-to-end via Pimlico. Source verified on BaseScan and Sourcify (exact_match). Integration questions: GitHub issues or @OrknetP on Telegram.

Live address

Paymaster0xb6E8d189285003cF0000388b01BA0C3433ee9f14
ChainBase mainnet (chainId 8453)
EntryPoint0x0000000071727De22E5E9d8BAf0edAc6f37da032 (ERC-4337 v0.7)
SourceRotwang9000/seneschal-paymaster
VerificationBaseScan · Sourcify
Smoke-tested viaPimlico (api.pimlico.io/v2/base/rpc)

The earlier v1 paymaster at 0xC5642B37000F9559b4628D7BA228051010150017 deprecated is undeployed (deposit withdrawn). v1 under-charged for its own postOp gas; v2 fixes that with an explicit overhead inside _postOp and a validUntil window inside _validatePaymasterUserOp.

Supported tokens

SymbolAddressDecimalsMarkup over Chainlink spot
USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA0291367%
EURC0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb4267%
USDT0xfde4C96c8593536E31F229EA8f37b2ADa2699bb268%
DAI0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb188%
cbBTC0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf810%

Prices feed from Chainlink. Stablecoins use the canonical token/USD feeds with a 25h heartbeat tolerance; cbBTC uses a 2h tolerance.

paymasterAndData layout

[ 0..20)   paymaster address               (set by EntryPoint)
[20..36)   paymasterVerificationGasLimit   (recommended ≥ 200 000)
[36..52)   paymasterPostOpGasLimit         (recommended ≥ 120 000)
[52..72)   token address                   (one of the supported tokens)
[72..104)  maxTokenCost (uint256)          (max token-native units you'll spend)

maxTokenCost is a slippage guard. Quote off-chain with paymaster.quote(token, expectedGasWei) and supply 110-120% of that to absorb a baseFee uptick between simulation and inclusion.

Gas budget rationale

Validation reads two Chainlink feeds (ETH/USD + token/USD), executes a transferFrom from the sender, and emits the prefund context. Empirical gas: ~140–160k. PostOp reads the token feed again, executes a transfer of the refund, and emits an event. Empirical gas: ~55–75k.

Setting paymasterVerificationGasLimit below 150 000 or paymasterPostOpGasLimit below 90 000 will cause Pimlico/Alchemy/Stackup to drop the userOp with AA33 reverted 0x (out-of-gas inside the paymaster). The contract refunds unused gas via the postOp refund mechanism, so generous budgets are essentially free.

validationData

_validatePaymasterUserOp returns a validUntil of block.timestamp + 5 minutes. Bundlers will therefore drop any userOp that doesn't get included within that window — important because the Chainlink-read prices in validation could otherwise drift. If you simulate a userOp and then sit on it for >5 minutes before submitting, re-simulate.

What the smart account must have

Before sending the userOp:

  1. Token balance: at least maxTokenCost of the chosen token in the smart account.
  2. Token allowance: the paymaster needs approve(paymaster, maxTokenCost) (or more — MaxUint256 once is fine). The allowance can be granted either:
    • in a prior userOp the user signs (one-off "approve paymaster for unlimited USDC"), or
    • in a batched call within the same userOp (if your account supports batched calls and your bundler allows transferFrom-from-sender during validation, which Pimlico does).

If the allowance or balance is insufficient, the bundler will drop the userOp during simulation. There's no on-chain failure path.

Live example — viem + permissionless

import { createPublicClient, http, encodePacked, parseAbi } from 'viem';
import { base } from 'viem/chains';
import { createBundlerClient, entryPoint07Address } from 'viem/account-abstraction';

const PAYMASTER = '0xb6E8d189285003cF0000388b01BA0C3433ee9f14';
const USDC      = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

const pub = createPublicClient({
	chain: base,
	transport: http('https://base-rpc.publicnode.com')
});

const quote = await pub.readContract({
	address: PAYMASTER,
	abi: parseAbi(['function quote(address,uint256) view returns (uint256)']),
	functionName: 'quote',
	args: [USDC, 900_000_000_000_000n]  // 0.0009 ETH ≈ 300k gas @ 3 gwei
});
const maxTokenCost = (quote * 12n) / 10n;  // +20% slippage buffer
const paymasterData = encodePacked(['address','uint256'], [USDC, maxTokenCost]);

const bundler = createBundlerClient({
	chain: base,
	transport: http('https://api.pimlico.io/v2/base/rpc?apikey=' + process.env.PIMLICO_KEY),
	paymaster: {
		async getPaymasterData() {
			return {
				paymaster: PAYMASTER,
				paymasterData,
				paymasterVerificationGasLimit: 200_000n,
				paymasterPostOpGasLimit: 120_000n
			};
		}
	}
});
// then bundler.sendUserOperation({ account, calls, … }) as usual.

Live example — ethers.js v6

import { ethers } from 'ethers';

const PAYMASTER = '0xb6E8d189285003cF0000388b01BA0C3433ee9f14';
const USDC      = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const provider  = new ethers.JsonRpcProvider('https://base-rpc.publicnode.com');

const paymaster = new ethers.Contract(
	PAYMASTER,
	[ 'function quote(address token, uint256 ethWei) view returns (uint256)' ],
	provider
);

// 1. Estimate the userOp's total gas (callGasLimit + verificationGasLimit
//    + preVerificationGas + paymasterVerificationGasLimit + paymasterPostOpGasLimit)
//    multiplied by maxFeePerGas. Most ERC-4337 SDKs expose this directly.
const expectedGasWei = 300_000n * 3_000_000_000n;   // 300k gas × 3 gwei

// 2. Quote the USDC cost.
const quote = await paymaster.quote(USDC, expectedGasWei);
const maxTokenCost = (quote * 12n) / 10n;            // 20% slippage buffer

// 3. Build the paymasterAndData suffix (SDK / bundler prepends the
//    address + gas limits; you only contribute the trailing 52 bytes).
const paymasterData = ethers.solidityPacked(
	[ 'address', 'uint256' ],
	[ USDC, maxTokenCost ]
);

// 4. In your bundler client (Pimlico, Stackup, Alchemy AA, biconomy SDK):
//      paymaster:                       PAYMASTER,
//      paymasterData:                   paymasterData,
//      paymasterVerificationGasLimit:   200_000,
//      paymasterPostOpGasLimit:         120_000,

Validation rules (bundler will check)

The paymaster passes ERC-7562 storage-access rules:

If a bundler reports a simulation failure not listed below, open a GitHub issue with the bundler trace.

Common errors

ErrorCauseFix
AA33 reverted: TokenNotSupportedToken addr not configuredUse one from the table above
AA33 reverted: MaxTokenCostExceededYour maxTokenCost was below the live quoteRe-quote and bump by ≥20%
AA33 reverted: BadPaymasterDataLengthpaymasterAndData suffix isn't exactly 52 bytesRe-pack with encodePacked(['address','uint256'], …)
AA33 reverted: StalePriceChainlink feed hasn't updated within the staleness windowAlmost always a Chainlink outage — retry in a few minutes
AA33 reverted: EnforcedPausePaymaster is pausedUse a different paymaster until ops un-pauses
AA34 ERC20InsufficientAllowanceUser didn't approve the paymasterSend an approve(paymaster, MAX) userOp first
AA33 reverted 0x (empty data)Validation OOG (two Chainlink reads + transferFrom won't fit)Set paymasterVerificationGasLimit to ≥ 200 000
AA40 over verificationGasLimitValidation gas limit too lowSet paymasterVerificationGasLimit to ≥ 200 000
AA50 paymaster postOp ran OOGpostOp gas limit too lowSet paymasterPostOpGasLimit to ≥ 120 000

Quoting at scale

If you're embedding the paymaster in an SDK, batch-quote at startup and refresh every ~5 blocks. The on-chain quote() takes ~70k gas to read all 5 token configs + Chainlink feeds — fine for one-off, expensive for per-userOp. Cache the markup constants client-side and only refresh the Chainlink prices.

We do not throttle reads of quote() — feel free to hammer it.

Trust model

You're trusting:

  1. The Chainlink ETH/USD + token/USD feeds on Base. Industry standard.
  2. Us (the paymaster owner) not to pause the contract maliciously while your userOp is in flight. The 1-block pause window between a bundler accepting and submitting your userOp is the only realistic risk; impact is your userOp drops, no funds lost.
  3. The contract's source matches the deployed bytecode. Both Sourcify (exact_match) and BaseScan verification confirm this — open the BaseScan link above and you can read the source there.

You're not trusting us with:

Why this exists

Seneschal runs an Ethereum mainnet block builder and a liquidation searcher. The paymaster is a side-project of the same operator, offered as a public good on Base: a small markup (7–10%) over the Chainlink spot price covers the operator's gas float and Chainlink read costs, with anything beyond that going toward keeping the builder running.

Open-source, MIT-licensed, no SDK lock-in. The contract is roughly 350 lines of Solidity; you can read every line before integrating.

Support & status