Cover your users' transaction fees from a domain treasury — the four conditions that must hold, and how to set them up.

Fee Sponsorship

What Fee Sponsorship Is

Normally each wallet pays its own fee (in the fee token) for every operation. A domain owner can instead sponsor those fees: the fee is pulled from the domain's treasury account, and the end-user's wallet pays nothing.

On-chain, a sponsored fee is the Domains contract executing transferFrom(treasury → feeWithdraw) on the fee token. That single fact drives everything below.

The Four Conditions

A sponsored transaction only succeeds when every one of these is configured. Each missing piece reverts with a different error:

#ConditionError when missing
1paysFees — the domain is set to sponsor. Off → users pay their own fee.— (not an error; users just pay)
2treasury — a treasury address is set and holds the fee token.TreasuryNotSet
3coin limit — a per-coin spending budget for the fee token.CoinLimitExceeded
4allowance — the treasury approved the Domains contract to spend its fee token.ERC20InsufficientAllowance

Condition 3 — The Spending Budget

setCoinLimit(origin, feeCoin, budget) caps how much the domain can spend on sponsored fees, per coin. When the accumulated spend plus the next fee would exceed the budget, the transaction reverts CoinLimitExceeded.

The error text for CoinLimitExceeded says "reduce the number of coins" — that is misleading. It means the budget ran out(or was never set). Raise the limit; don't remove coins.

Amounts are wei (smallest unit) on the API and contract. The Passkallet dashboard converts from decimal for you.

Condition 4 — The Allowance

The treasury holds the fee tokens, but holding is not authorizing: the Domains contract can only transferFrom the treasury after the treasury has called approve(domainsContract, amount) on the fee token.

With allowance 0, every sponsored transaction reverts ERC20InsufficientAllowance even when budget is left — the args show (spender = Domains contract, allowance = 0, needed = fee).

One generous approve covers all future sponsored transactions until the allowance runs down.

Easiest Path: the Dashboard's Two Steps

The domain screen in the Passkallet dashboard sets up conditions 3 and 4 as a guided two-step card, always on the configured fee coin (no token picker):

StepWhat it doesNotes
1 · Spending budgetSets the fee-coin budget (setCoinLimit)Shows spent vs. limit; decimal amounts
2 · Allow the cost to be takenThe treasury approves the Domains contractShows the current allowance; signed by the treasury wallet's passkey — sign in as the treasury owner

Both steps convert decimal amounts to wei automatically.

Doing It via the API

If the treasury is one of your org's Passkallet wallets, you can prepare the approve through the BaaS API instead:

bash
curl -X POST "https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/tokens/erc20/approve" \
  -H "x-api-key: pk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4" \
  -H "Content-Type: application/json" \
  -d '{
    "walletAddress": "<treasury wallet address>",
    "tokenAddress": "<fee token address>",
    "spender": "<Domains contract address>",
    "amount": "1000000000000000000000"
  }'

The amount is wei — the example is 1000 tokens at 18 decimals. Sign the returned object with the treasury's passkey and submit via POST /baas/transactions/execute as usual.

Required scope: token:approve

Symptom Guide

Sponsored transactions failing one-by-one as you fix things is normal — the on-chain checks run in the order of the table above, so each fix surfaces the next missing condition until all four hold:

text
TreasuryNotSet             →  set the treasury
CoinLimitExceeded          →  raise the fee-coin budget
ERC20InsufficientAllowance →  authorize collection (allowance)
✓ sponsored txs succeed