This post was kindly reviewed by Nam, Zoey & Andy.
The article is part of a series on decentralised identity and private payments for humanitarian assistance on Ethereum. Read the overview: Decentralized Identity and Private Payments on Ethereum.
Summary
Every transaction on Ethereum must pay a gas fee, otherwise it will not be included in any block. Organisations want to offer financial tools to their users, but their users do not have wallets, or their own funds to pay for these transactions. Solutions that do exist often add a trusted vendor into the transaction pipeline, and by nature of doing so, a trusted vendor can see every sponsored transaction at the expense of user privacy.
We built a simple onchain smart contract that organisations can use if they want their users to have higher data sovereignty, and provide sponsorship without risk of downtime from a specific third party service. This simple transaction sponsorship contract provides private transaction sponsorship for your ecosystem.
Transaction sponsorship when distributing humanitarian aid
We initially built this as part of a wider project examining what it would take to run a humanitarian aid distribution network fully on Ethereum. The currently outlined culmination of this development is designed for real aid organisations and aid claimants.
The problem statement for an aid use case: An organisation needs to sponsor transactions because certain users do not have devices to hold funded wallets and send transactions, and we don’t want to link organisations originating transactions to specific retailers and beneficiaries. E.g. if organisation A sponsors user 1, we don’t want to see that transaction come from organisation A’s account, otherwise an external observer can see that organisation A is sending transactions for user 1.
For the aid use-case, we are concerned with 3 user types: 1) Organisations, 2) Retailers, and 3) Beneficiaries. Organisations are the aid institutions providing humanitarian aid, retailers are entities that materially distribute aid, and beneficiaries are the people that receive aid. We renamed these domain-specific user types in order to make the project more generic. The codebase supports those three user types with the corresponding 3 names: 1) Network members, 2) users, and 3) delegated users. Beneficiaries are potentially delegated users since they sometimes don’t own a phone and must take actions via a chosen delegator.
We need transaction sponsorship for 3 reasons:
- Retailers and beneficiaries should not need ETH to transact.
- Aid beneficiaries may not have mobile devices, so cannot hold or control an Ethereum account without additional support from a delegation mechanism.
- In a setting with many organisations, aid-giving organisations should not reveal which retailers and beneficiaries they are supporting.
An interesting aspect of this project was the interplay between maximising CROPS alignment, and supporting real-world use-cases which place restrictions on the implementation of CROPS solutions. CROPS stands for Censorship Resistance, Open source, Privacy, Security — it is the set of values the Ethereum Foundation upholds for Ethereum development. An important part of our trust model is that certain aspects of the system we can assume are trusted, and some aspects we can assume are untrusted. For example, the governance process that decides which network members are added to a given aid jurisdiction is an offchain governance process that cannot be automated away in a completely decentralised way using smart contracts.
Comparison to existing solutions
Privacy protocols on Ethereum today such as Tornado Cash and Railgun solve “private sponsorship”, by submitting transactions through relayers. These services are trusted. Even when anyone can run one, the relayer sees each transaction and can censor it.
Account abstraction standards and protocol improvements such as ERC-4337 and EIP-7702 have also been providing support for transaction sponsorship for some time. However, existing primitives, contracts and services do not support private transaction sponsorship. A common component for handling sponsorship is called a paymaster, which is a smart contract that acts like a gas tank that can pay for any valid transactions. When you normally use a paymaster, either use of the paymaster itself reveals what account you are using, or the entity running the paymaster service knows what account you are using.
The main problem we set out to solve was user privacy with censorship resistance. Existing production paymasters do not protect user privacy. Production paymasters in use today such as those run by Pimlico and Alchemy sign each operation offchain, so the operator sees every account it pays for. And other sponsorship solutions such as relayers (e.g. those run for Tornado Cash and Railgun) are trusted infrastructure.
The design makes iterations on some earlier attempts in order to meet the requirements of this project. We use a project called Semaphore to hide which entity is being sponsored by the paymaster — Semaphore allows you to take private actions within a group of other entities. For example, voting for a candidate in an election, without revealing who you are. We built a privacy-preserving paymaster that pays for any member of a Semaphore group without learning which member sent the operation.

Any group member can be sponsored. The paymaster checks only that the sender is in the group, not which member it is.
A number of open source privacy-preserving paymaster designs have been proposed previously and exist today — some of which use Semaphore too. Some of these differences include:
- Our Semaphore paymaster sponsors users using third party-sourced funds, rather than funds being provided by the user themselves.
- We modify the standard Semaphore proof verification logic in our paymaster contract to be fully compatible with ERC-7562 validation rules.
- The paymaster is relatively simple compared to others.
With our proposed design, the system pays gas through one shared Semaphore paymaster on a public chain. No one is able to link a transaction to a member from the chain. Each operation comes from a fresh stealth address with a group membership proof. A stealth address is a fresh one-time-use address for unlinkable payments. The result is that censorship resistance and user privacy are improved from the status quo.
Use cases
The ideal use-case for this paymaster is a specific network with its own user base that it admits to the network. Alternatively, a network seeking to replace relayer-based sponsorship with private and censorship resistant sponsorship.
In our Sepolia demo, the paymaster paid for registrations and revocations by two network members, using the reference apps on one Android phone. The Sepolia demo guide has a recorded run, and outlines an additional run where a stealth account sends a sponsored token transfer.
Candidate use cases
- Any network sponsoring its own users. This should apply where there is some form of authentication or governance that controls and enforces which users can enter the system.
- Anonymous DAO voting. The DAO pays gas for members’ votes. Paying from a member’s own wallet would reveal who voted.
- Any other privacy protocol could sponsor its own users. A protocol that only wants to sponsor its users could use the Semaphore paymaster.
What an integrator gets
If you are an organisation integrating this software, the paymaster is useful if you want to pay for user transactions while ensuring user sovereignty and privacy, but still operate within the constraints of legacy project governance systems. An integrator gets:
- Private transaction sponsorship for your ecosystem.
- Control over who enters, but users get permissionless sponsorship when in the system.
- You don’t need to run a relayer or a signing service. The paymaster contract is MIT-licensed, and the
@coordination-networkSDK generates the proof.
You do need the following: A chosen bundler (the service that sends 4337 transactions); to fund the stake for your deployed instance of the paymaster which is staked (This can be 0.1-1 ETH for the stake); funds for the deposit to pay for your users’ transactions; an owner account or smart contract who can add each member to the group; monitoring set up to manage the paymaster deposit.
Any integrator should be mindful of the tradeoffs of censorship resistant paymasters without the offchain signing that production paymasters use today, notably that a group member can drain the paymaster’s deposit — these tradeoffs are outlined further below.
Technical implementation deep dive
The following section dives deeper into the technical aspects of the system.
We produced a simple Semaphore paymaster that can sponsor transactions across the system. It is used with EIP-7702 accounts, since regular EOAs (standard Ethereum accounts) cannot be sponsored. The main exception to sponsorship is that a network member self-pays for its own registration record, and some other actions involving project governance.
Validation is done against a Semaphore proof. We call the Semaphore verifier directly instead of Semaphore’s verifyProof, because that reads block.timestamp, which ERC-7562 bans during validation.

Permissionlessness
We opted to make paymaster usage permissionless. By permissionless, we mean any member already in the Semaphore group can use it, without the sponsor approving each operation. However, joining the group in the first place remains gated. One important user operation parameter in this context is preVerificationGas. This value is used to declare how much to pay the bundler for gas the EntryPoint can’t measure onchain, such as calldata and per-bundle overhead. preVerificationGas is self-declared and never refunded. This means a caller can inflate this value and drain the paymaster, since it is permissionless for any Semaphore group member. This is currently mitigated as the paymaster membership is gated, so you can control who enters the system, and MAX_COST_PER_OP. It can further be mitigated by adding gas or operation number limits to the paymaster (this issue is discussed further in limitations below).
Existing paymasters in production today are not permissionless in this sense, so do not have to worry about users draining funds from the paymaster maliciously. We add a cap on gas costs to limit the damage such an attack can have on paymaster funds. MAX_COST_PER_OP caps individual operations to a specific gas cost, but not the number of operations. This is a tradeoff of our paymaster, we outline other current and future mitigations in the “Limitations and next steps” section. Since we already allow users to use the paymaster an unlimited number of times, and trust specific aspects of the system, we only add a single cap to limit damage in a worst case scenario. This is made possible as the network being supported is gated, so we have control over who enters the system.
Censorship resistance
An entity that seeks to censor needs 1) a target user(s) and 2) a chokepoint to censor.
The design handles #1 as it anonymises the target user by using Semaphore commitments and proofs, so a bundler can’t tell from the operation which member is being sponsored. However, it can still see the IP address or API key that submitted it, and the anonymity set is limited to the group’s members.
For #2, the censorship chokepoint itself is the bundler. We opted for 4337 infrastructure over using a centralised relayer. A 4337 bundler is still a relayer — it sees each operation and can refuse it. The difference is that any bundler that follows ERC-7562 will execute the operation, and the paymaster pays for any valid membership proof without discrimination. While the 4337 mempool is not fully operational and decentralised, the current 4337 ecosystem supports paths for transaction inclusion should a single bundler decide to censor user operations: 1) it is a straightforward config change to switch bundler URLs; 2) you can host a dedicated bundler; 3) Failing that, anyone can submit the operation to the EntryPoint themselves. One tradeoff of directly submitting to the EntryPoint is that the submitting account needs ETH, and operation is fully public, so whoever submits the operation is linked to the fee payment. A beneficiary with no device can’t do that.
Ethereum is shortly adding a number of EIPs such as Frame Transactions (EIP-8141) to enable censorship resistant privacy protocols. As the EIP is outlined, Frame Transactions will not support our paymaster in the public mempool due to the public mempool’s 100k gas cap on validation. Exploring compatibility is a future research task.
Cost amortisation and governance
No nullifier is checked, so one proof sponsors its account indefinitely until the group’s Merkle root changes, and for up to an hour after that. Adding or removing any member changes the root. This is a deliberate feature to enable amortisation, since a membership proof costs over 200k gas which may be cost prohibitive to high volume mainnet use-cases. We don’t explicitly use this feature yet. Our client sends each operation from a fresh stealth address, and a proof only works for one address.
The owner of the paymaster is a single point of control. The owner can remove any member, but can’t tell which member is spamming. Each operation comes from a fresh address, with a proof that can’t be linked back to a member. One limitation of this design is that a user can abuse the paymaster deposit. We cover this under limitations.
Integration: send a sponsored user operation
The snippet below is pseudo code. Visit our docs for full integration guides.
import {
fetchPaymasterSemaphoreGroup,
generatePaymasterProof,
Identity
} from "@coordination-network/account"
import { derivePaymasterKeypair } from "@coordination-network/identity"
// 1. Import dependencies & define Viem clients
// https://viem.sh/
// 2. Add a Semaphore member to the paymaster group.
// Pull groupId from paymaster contract
const ecdhSharedSecret = p256.getSharedSecret(aliceDevicePrivateKey)
const paymasterKeypair = derivePaymasterKeypair(ecdhSharedSecret)
const addMemberHash = await walletClient.writeContract({
address: paymasterAddress,
abi: paymasterAbi,
functionName: "addMember",
args: [groupId, paymasterKeypair.commitment],
})
await publicClient.waitForTransactionReceipt({ hash: addMemberHash })
// 3. generate paymaster proof
const paymasterIdentity = new Identity(hexToBytes(paymasterKeypair.seed))
const paymasterGroup = await fetchPaymasterSemaphoreGroup({
rpcUrl,
semaphoreContract: paymasterAddress,
groupId,
startBlock,
blockScanWindowSize,
})
const paymasterData = await generatePaymasterProof({
identity: paymasterIdentity,
group: paymasterGroup,
sender: aliceStealthAddress
})
// 4. send user operation with paymaster sponsoring and paymaster data
const userOperationHash = await bundlerClient.sendUserOperation({
account: aliceStealthAddress,
calls,
paymaster: paymasterAddress,
paymasterData
})
const { receipt } = await bundlerClient.waitForUserOperationReceipt({ hash: userOperationHash })
Limitations and next steps
Limitations
- In practice we submit through one bundler. The shared 4337 mempool is still limited to whitelisted bundlers.
- Frame Transactions (EIP-8141) won’t support this paymaster using the public mempool as drafted. A frame can indeed verify a ZK proof, but this is not generally feasible via the public mempool. The public mempool caps validation at 100,000 gas and only allows reads of the sender’s storage (EIP-8141,
MAX_VERIFY_GAS). Our proof check needs about 181,000 gas for its pairings alone (four pairings at 34,000 each plus 45,000, per EIP-1108). Raising the cap to at least 250k gas has been proposed: EIP-8141 and minimum required validation budget for privacy applications. MAX_COST_PER_OPcaps each operation, not each member. The owner can’t tell which member is spamming, because every operation comes from a fresh address with an unlinkable proof. The main responses are pausing sponsorship by withdrawing the deposit, or rebuilding the group. Reusing addresses for a period would not stop it, because a commitment to remove is still hidden by the Semaphore proof. A cap per member would work: each group member gets a fixed amount of gas (or number of operations), as inGasLimitedPaymaster.- The contract allows a proof to be reused for the same account, but we don’t use that, so every operation pays for a fresh proof.
- A removed group member can still be sponsored for up to an hour after removal, using a proof against the Merkle root from before they were removed. It is possible to configure how long a superseded root remains valid.
- The EIP-7702 delegation on each stealth account is visible on chain.
- The contracts have had an internal audit, not an external one.
- Tested on Sepolia only, with two network members and no real users.
Next steps
- Add a per-member sponsorship limit. This could be done in two ways: 1) limiting gas/operation usage per period, like how this GasLimitedPaymaster works. Or 2) using RLN: Rate-Limiting Nullifier. RLN would allow users to execute a fixed number of transactions per period, and they can be revealed and kicked out if they go over this limit. Both solutions prevent one member draining the entire deposit.
- Research ways to make compatible with Frame Transactions. ZK verification in the public mempool is one known limitation that must be overcome. Others may exist. The existing path of using 4337 infrastructure remains viable.
- Integrate and test amortisation in client (Paymaster supports proof reuse per group member to optimise costs, this is not taken advantage of in consuming code yet)
Talk to us or try it yourself
yarn add @coordination-network/account@0.2.0
Quickstart: integration.mdx
Code repository: https://github.com/ethereum/coordination-network
Running a coordination programme and want to pilot this? Contact epic@ethereum.org
Sources
- Our SDK:
@coordination-network - Semaphore: Semaphore
- Account abstraction: ERC-4337
- Delegated EOAs: EIP-7702
- Stealth addresses: ERC-5564
- Account Abstraction validation rules: ERC-7562
- Threat model:
specs/threat-model-phase-1.md - Privacy limits: privacy disclosure
- Demo run: Sepolia demo guide