Sponsor gas for your users
Add paymaster-backed gas sponsorship to your dApp in minutes. Native (ADI) or ERC20 gas payment. Backend-controlled signer, EntryPoint v0.7 compatible. Use our API or run your own.
Native (zero-balance accounts) or ERC20 gas. Same API, switch with one parameter.
Deploy your own with our open scripts or call our hosted endpoints for testing.
Compatible with standard EntryPoint and any bundler or custom submit path.
Quick start
Get contract addresses, build a UserOperation, request sponsorship, then submit.
Get deployments
const res = await fetch('/api/erc4337/deployments')
const { entryPoint, nativePaymaster, erc20Paymaster, mockERC20 } = await res.json()Build your UserOp (without paymasterAndData)
Include: sender, nonce, initCode, callData, accountGasLimits, preVerificationGas, gasFees. Use empty paymasterAndData/signature for the hash step.
const partialUserOp = {
sender: smartAccountAddress,
nonce: nonce.toString(),
initCode: initCodeHex,
callData: callDataHex,
accountGasLimits: packedGasLimits,
preVerificationGas: preVerificationGas.toString(),
gasFees: packedGasFees,
paymasterAndData: '0x',
signature: '0x',
}Request sponsorship
const sponsorRes = await fetch('/api/erc4337/sponsor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'native', // or 'erc20'
paymasterAddress: nativePaymaster,
userOp: {
sender: partialUserOp.sender,
nonce: partialUserOp.nonce,
initCode: partialUserOp.initCode,
callData: partialUserOp.callData,
accountGasLimits: partialUserOp.accountGasLimits,
preVerificationGas: partialUserOp.preVerificationGas,
gasFees: partialUserOp.gasFees,
},
validitySeconds: 300,
// maxTokenCost: '1000000000000000000' // required for type: 'erc20'
}),
})
const { paymasterAndData } = await sponsorRes.json()Attach paymasterAndData, sign, and submit
const opWithPaymaster = { ...partialUserOp, paymasterAndData }
// Get userOpHash from EntryPoint.getUserOpHash(opWithPaymaster), sign with owner
const finalOp = { ...opWithPaymaster, signature: ownerSignature }
const submitRes = await fetch('/api/erc4337/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userOp: {
...finalOp,
nonce: finalOp.nonce.toString(),
preVerificationGas: finalOp.preVerificationGas.toString(),
},
beneficiary: null,
}),
})
const { txHash } = await submitRes.json()API reference
All endpoints are relative to your app origin. No API key required for the default setup.
/api/erc4337/deployments{
"deployed": true,
"entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
"nativePaymaster": "0x...",
"erc20Paymaster": "0x...",
"mockERC20": "0x...",
"chainId": 99999
}/api/erc4337/sponsorpaymasterAndData for the given UserOp. Backend signer never leaves the server. Required body: type, paymasterAddress, userOp (UserOp fields only). Optional: validitySeconds (default 300), maxTokenCost (required for type: 'erc20').{
"type": "native",
"paymasterAddress": "0x...",
"userOp": {
"sender": "0x...",
"nonce": "0",
"initCode": "0x",
"callData": "0x...",
"accountGasLimits": "0x...",
"preVerificationGas": "60000",
"gasFees": "0x..."
},
"validitySeconds": 300
}{
"paymasterAndData": "0x...",
"validUntil": 1731234567,
"sponsorAddress": "0x..."
}/api/erc4337/submitEntryPoint.handleOps. Uses the same sponsor wallet as beneficiary. Numeric fields can be strings (e.g. nonce, preVerificationGas) for JSON.{
"userOp": {
"sender": "0x...",
"nonce": "0",
"initCode": "0x",
"callData": "0x...",
"accountGasLimits": "0x...",
"preVerificationGas": "60000",
"gasFees": "0x...",
"paymasterAndData": "0x...",
"signature": "0x..."
},
"beneficiary": null
}{ "txHash": "0x..." }/api/erc4337/mintto, amount (wei string).Integration example
Minimal fetch-based integration you can drop into any frontend (React, Next, vanilla).
async function sponsorAndSubmitUserOp(partialUserOp: {
sender: string
nonce: string
initCode: string
callData: string
accountGasLimits: string
preVerificationGas: string
gasFees: string
}, paymasterAddress: string, type: 'native' | 'erc20' = 'native') {
const sponsorRes = await fetch('/api/erc4337/sponsor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type,
paymasterAddress,
userOp: partialUserOp,
validitySeconds: 300,
...(type === 'erc20' && { maxTokenCost: '1000000000000000000' }),
}),
})
if (!sponsorRes.ok) {
const err = await sponsorRes.json()
throw new Error(err.error || 'Sponsorship failed')
}
const { paymasterAndData } = await sponsorRes.json()
// In your app: get userOpHash from EntryPoint.getUserOpHash({ ...partialUserOp, paymasterAndData, signature: '0x' })
// then sign with owner and build finalOp = { ...partialUserOp, paymasterAndData, signature }
const finalOp = {
...partialUserOp,
paymasterAndData,
signature: '0x...', // from your owner signer
}
const submitRes = await fetch('/api/erc4337/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userOp: {
...finalOp,
nonce: finalOp.nonce.toString(),
preVerificationGas: finalOp.preVerificationGas.toString(),
},
beneficiary: null,
}),
})
if (!submitRes.ok) {
const err = await submitRes.json()
throw new Error(err.error || 'Submit failed')
}
const { txHash } = await submitRes.json()
return txHash
}Try it in the Playground
Run Flow A (native sponsorship) and Flow B (ERC20 gas) end-to-end with live logs. No code required — just load deployments and click Run.
Open Playgroundcontracts/erc4337/ and scripts/erc4337/. Deploy with node scripts/erc4337/deploy.js. Sponsor key: ADI_CHAIN_PRIVATE_KEY (server-only).