Guide
Gas & payers
An EIP-8130 transaction can name a payer: a second account that co-signs and pays gas. The sender authorizes the calls (sender_auth); the payer authorizes paying (payer_auth). This is the native mechanism behind sponsorship and pay-with-token, no bundler or EntryPoint.
Co-sign locally
When you hold the payer key (self-sponsor or your own backend), pass a payer signer to sendCalls8130. It signs payer_auth bound to the sender and sets the payer wire field. Set payer.address when the onchain payer account differs from the signing key.
import { privateKeyToAccount } from 'viem/accounts'import { sendCalls8130 } from 'viem/experimental/eip8130'
const sponsor = privateKeyToAccount(process.env.SPONSOR_KEY as `0x${string}`)
const hash = await sendCalls8130(client, { account, // signs sender_auth calls: [{ to: recipient, data }], gas: 250_000n, payer: { account: sponsor }, // signs payer_auth, pays the fee})Whether a key may pay depends on its scope: SELF_PAYER funds the account's own transactions, while SPONSOR_PAYER funds others. The distinction and its risk model live in Gas & payers (protocol).
Estimating a sponsored transaction
Pass the payer address (and payerAuthVerifier if the payer uses a non-K1 key) so the node prices the payer authentication too.
import { canonicalAuthenticators, estimateGas8130 } from 'viem/experimental/eip8130'
const gas = await estimateGas8130(client, { sender: account.address, calls: [[{ to: recipient, data }]], payer: sponsor.address, // Only needed if the payer uses a non-K1 key. payerAuthVerifier: canonicalAuthenticators.k1,})Pay with a token
To charge the sender in an ERC-20 (e.g. USDV) while the payer fronts native gas, run a two-phase transaction: phase 0 transfers the token to the payer, phase 1 runs the user's calls. The payer co-signs because it can see its fee is paid in phase 0.

import { encodeTokenTransfer } from 'viem/experimental/eip8168'import { sendCalls8130 } from 'viem/experimental/eip8130'
const hash = await sendCalls8130(client, { account, calls: [ // phase 0: pay the payer in the token [encodeTokenTransfer({ token, to: sponsor.address, amount: fee })], // phase 1: the actual calls [{ to: recipient, data }], ], gas: 300_000n, payer: { account: sponsor },})Payer services (ERC-8168)
When a service holds the payer key, ERC-8168 standardizes a payer_* JSON-RPC endpoint the wallet queries to sponsor a transaction or accept a token as gas. Viem exposes the client and helpers under viem/experimental/eip8168. sendSponsoredCalls runs the whole flow: fetch terms, select an offer, build the phases (including any phase-0 token transfer), sign, and hand off to the payer.
import { createPayerClient, sendSponsoredCalls } from 'viem/experimental/eip8168'
const payerClient = createPayerClient({ url: 'https://payer.example.com/v1' })
const { transactionHash, tokenCharged } = await sendSponsoredCalls(client, { account, // signs sender_auth; payer_auth is filled by the service payerClient, calls: [{ to: recipient, data }], token, // omit to prefer a fully-sponsored offer})If the payer rejects with a recoverable reason (a higher token charge or gas limit), sendSponsoredCalls re-signs only when you approve each attempt via confirmRetry.
import { formatUnits } from 'viem'import { sendSponsoredCalls } from 'viem/experimental/eip8168'
const result = await sendSponsoredCalls(client, { account, payerClient, calls: [{ to: recipient, data }], token, retries: 2, // Each retry mints a NEW sender_auth (and a fresh passkey gesture), // so nothing is re-signed without explicit approval. confirmRetry: async (request) => { if (request.kind === 'requote') return confirm(`Pay ${formatUnits(request.paymentAmount, 6)} in token?`) return confirm(`Raise gas limit to ${request.gasLimit}?`) },})For full control, drive the primitives directly: payerClient.getTerms, selectPaymentOption, buildSponsoredCalls, then prepare, sign, and payerClient.sendTransaction. Decode rejections with parsePayerError.
Session keys and gas
- A
POLICY-only session key cannot touch account ETH; sponsor it, or route it through a payer service that accepts spend-limit gas. - Adding
SELF_PAYERlets a session key self-fund gas, but a griefing key could then burn the balance on fees. Treat it as a deliberate opt-in. See Session keys.