Gas Sponsorship
Pay gas on behalf of your users. Kevo bundles the user's signed UserOperation through ERC-4337 v0.8 EntryPoint and bills your team for the consumed gas.
How it works
Sponsorship runs on a deposit-based model — no paymaster contract required. For each user, the sponsor wallet deposits ETH into the EntryPoint on the user's behalf, then submits the UserOperation. The EntryPoint debits the deposit during validation; refunds go back to the same deposit.
- SDK calls
/sponsor-tx/quote— server builds the UserOp. - User signs the UserOp hash through the embedded wallet flow (and a 7702 authorization on first use).
- SDK calls
/sponsor-tx/submit— server deposits if needed, then broadcastshandleOps.
Enable in your project
Sponsorship requires smartAccounts.enabled = true and gasSponsorship.enabled = true on the project's features. Optional policy fields cap exposure per user/project.
{
"smartAccounts": { "enabled": true, "delegateImpl": "simple7702" },
"gasSponsorship": {
"enabled": true,
"allowedChainIds": [11155111],
"allowedContracts": ["0xabc..."],
"maxWeiPerProjectMonth": "1000000000000000000",
"maxWeiPerUserMonth": "100000000000000000",
"maxTxPerUserPerHour": 20
}
}/sponsor-tx/submit returns 429 and the SDK falls back to user-paid execution.SDK usage
Sponsorship is fully transparent. Just call sendTransaction — if sponsorship is enabled and the chain is allowed, the SDK takes the sponsored path automatically; otherwise it sends a normal user-paid transaction.
import { useKevo } from '@kevo-ws/sdk/react'
function Pay() {
const { client } = useKevo()
const handle = async () => {
const txHash = await client.sendTransaction({
chainId: 11155111,
to: '0xRecipient...',
value: '0x0',
data: '0xa9059cbb...', // encoded transfer()
})
console.log('tx:', txHash)
}
return <button onClick={handle}>Pay (gasless)</button>
}tryFallback for non-sponsored chains is automatic. You write the same code regardless of whether the user has funds.HTTP API
/v1/wallets/me/sponsor-tx/quoteBearer (user)Build the UserOperation and return its hash plus all fields needed for client-side signing.
Request
{
chainId: number
to: string // 0x address
data?: string // 0x bytes
value?: string // 0x or decimal
}Response 200
{
supported: true
chainId: number
userOpHash: string
userOpFields: {
sender: string; nonce: string; initCode: string; callData: string;
accountGasLimits: string; preVerificationGas: string;
gasFees: string; paymasterAndData: string;
}
domainSeparator: string
initCodeHashOverride?: string
needsAuth: boolean
authFields: { chainId: number; address: string; nonce: number } | null
delegateAddress: string
sponsorAddress: string
gasEstimateWei: string
userAddress: string
}/v1/wallets/me/sponsor-tx/submitBearer (user)Submit the signed UserOp and (on first use) the 7702 authorization. Server deposits ETH to EntryPoint if needed and broadcasts handleOps.
Request
{
chainId: number
to: string
data?: string
value?: string
userOpSignature: string // 0x.. (65-byte ECDSA from the embedded wallet)
userOpFields: { /* exact object from /quote */ }
signedAuth?: { // only when needsAuth=true
chainId: number
address: string
nonce: number
yParity: 0 | 1
r: string
s: string
}
}Response 200
{
txHash: string
sponsorAddress: string
estimatedGasWei: string
}Error codes
400 { code: 'chain_not_7702_compatible' } // SDK falls back to user-paid
403 { error: 'Sponsorship not enabled' | '... not in allowlist' }
429 { error: 'Project monthly gas sponsorship budget exhausted' }
502 { code: 'broadcast_failed' }Webhooks
Successful sponsored transactions emit a signing.completed event with type: 'sponsored_evm_tx' in the payload.
{
"event": "signing.completed",
"data": {
"userId": "uuid",
"type": "sponsored_evm_tx",
"chainId": 11155111,
"txHash": "0x...",
"sponsorAddress": "0x..."
},
"timestamp": 1750000000000
}Operator setup
The API process needs sponsor keys and RPC endpoints per chain:
# packages/api/.env
SPONSOR_KEYS={"11155111":"0x<sponsor_priv_key>"}
SPONSOR_RPCS={"11155111":"https://sepolia.example/v2/<key>"}
# Optional overrides (defaults shown)
SIMPLE7702_DELEGATE_ADDRESS=0xe6Cae83BdE06E4c305530e199D7217f42808555B
ENTRYPOINT_V08_ADDRESS=0x4337084d9e255ff0702461cf8895ce9e3b5ff108submit returns 502. For production, swap the env-based key for a KMS/HSM signer.