Censor, Never Steal: Why the Service That Decides Is Never the Service That Signs

How FairWins runs a funded gas wallet on the open internet by splitting one server into two — a bouncer that decides, and an engine that pays

The button that spends someone else’s money

In part 1 of this series we covered the friendly half of gasless transactions: on FairWins, a user with no cryptocurrency for network fees can still act. They sign an instruction — “accept this wager,” “claim my winnings” — with their wallet, off to the side, for free. Someone else then pays the small network fee to actually submit it. That post ended on an uncomfortable word: someone.

Someone has to run a server. That server holds a funded wallet, accepts signed instructions from anyone on the internet, and turns them into paid transactions. Every bad outcome you can picture is on the table: the wallet’s key gets stolen; a bot floods the server until the gas budget is gone; a sanctioned wallet slips an action through; one deliberately expensive transaction burns a week’s budget in a single shot; or the whole thing falls over at 2 a.m. and people are stuck mid-wager.

FairWins had an extra constraint. The platform’s standing rule is no backend — just smart contracts, a static web app, and an indexer that reads the blockchain. This gas-paying server is the one deliberate exception, and being the exception raised the bar: if a server has to exist, its potential for damage has to be small enough to describe in a sentence.

The design that shipped does exactly that. One service decides whether a transaction should exist at all. A completely different service decides how it gets paid for and confirmed. Neither can do the other’s job. The goal, in the team’s own words: the hosted system can only ever censor, never steal.

The split: a bouncer in front, an engine behind

Picture two machines wired together.

The first is the bouncer. It faces the internet, receives each signed instruction, and runs it through a checklist before letting anything through. Is the system paused by its emergency switch? Is this a supported network and action? Who actually signed this — worked out mathematically from the signature itself, never taken on the sender’s word? Have we already processed this exact instruction (no double-submitting)? Does the signer pass a sanctions screen and stay under their rate limit? Would this blow the fee budget? Only an instruction that clears every gate gets packaged up and handed onward.

The second is the engine. It is a well-known open-source piece of infrastructure, run as-is, that does one unglamorous job well: take a fully-formed transaction, pay for it, and get it confirmed on the blockchain — managing the fiddly mechanics of ordering, pricing, retries, and switching to a backup connection if one fails. Crucially, the engine holds the gas wallet’s key. That key lives inside a hardware security module — a tamper-resistant vault that can use the key to sign but can never hand the key out.

Between the two machines is a deliberately tiny opening. The engine receives only a finished transaction: where it’s going, and how fast to push it. It never sees the original instruction, never learns who signed it, and holds no opinions. All the judgment lives in the bouncer; all the mechanics live in the engine.

That narrow seam buys three things. The engine is swappable — it’s off-the-shelf, so it could be replaced without touching a line of policy logic. The engine is auditable by its settings rather than its code — a plain configuration file pins a spending cap per network and, most importantly, a list of the only addresses it is ever allowed to pay. Even if hijacked, it could only ever pay into FairWins’ own contracts. And the policy is testable without touching a real blockchain, because it’s cleanly separated from the machinery.

Status flows back honestly: the engine tells the bouncer when a transaction is genuinely confirmed on-chain, and only then does the app report it as done. The system never guesses or reports success early.

Fail closed where money moves

The most telling item on the checklist is the sanctions screen. The smart contracts already screen everyone — so why screen again? Because the gas wallet pays the network fee before the contract ever gets a chance to reject the transaction, and “we paid to submit a sanctioned wallet’s transaction” is a sentence nobody wants to write. So the bouncer re-checks the true signer against the same on-chain screen.

The rule when that screen can’t give a clear answer is strict: if it says no, reject; if it’s unreachable, also reject — never “assume it’s fine and pay anyway.” This is the instinct across the whole checklist: when money is about to move, refuse rather than guess.

What a total compromise actually buys an attacker

Because of the split, the worst case fits in a small table:

PartHoldsCan doCannot do
Bouncertwo shared passwordsaccept or refuse instructions, screen, rate-limitsign anything, move funds, fake a signer
Enginethe ability to use the gas keypay for transactions to approved addresses onlyexceed the spending cap, pay anyone else, touch user funds
Hardware vaultthe actual gas keyproduce signaturesever reveal the key
Gas walleta small balance for feespay network feesanything else — it has no special authority

Take over the entire hosted system and here is your whole prize: the small gas balance, plus the power to refuse service for a while. No user funds are reachable — the stakes sit in escrow contracts that independently verify every signature themselves. No administrative power is reachable — the keys that could change the contracts live offline, on physically separate storage, in an entirely different security tier. The blockchain re-checks and re-screens everything regardless of what the server claims. That’s what “censor, never steal” means as an engineering property rather than a slogan.

One checklist, two gasless systems

FairWins actually runs two flavors of gasless transactions, and this is where the split pays off twice.

The first is the one above: signed instructions submitted on a user’s behalf. The second serves the platform’s passkey wallets — accounts you unlock with Face ID or a fingerprint (the same WebAuthn standard your phone already uses for passwordless sign-in). For these, FairWins can sponsor the network fee directly, so the user pays nothing.

Rather than build a second server, the same bouncer simply grew a second door. Sponsoring a fee reuses the same emergency switch, the same sanctions screen, and the same rate limits — plus two extra ceilings that cap what any single sponsored action can cost, so one deliberately expensive request can’t drain the sponsorship fund. One perimeter, one audit trail, one emergency switch, covering two economically different systems. The same checklist has since taken on the platform’s other outside connections too — the collectibles marketplace and the prediction-market trading feature. This bouncer turned out to be the platform’s one reusable piece of backend.

Optional by construction: the never-stranded rule

All of this would still be a liability if people needed it. They don’t. A firm rule holds throughout: every action that can go through the gas-paying server must also complete perfectly well without it, landing exactly the same result on the blockchain — the user just pays their own small network fee.

The app enforces this automatically. Before asking you to sign, it quietly checks whether the server is healthy. If that check fails, the emergency switch is on, the network looks down, or anything goes wrong mid-flow, the app silently routes the very same action through your own wallet as an ordinary paid transaction. Same destination, same result; the only difference is who covered the fee. The sponsored-passkey path behaves the same way — any problem, and the confirmation screen honestly says the user is paying.

Because the worst case is merely “users pay their own fee,” the emergency switch is cheap to pull — which means operators will actually pull it when they should.

Design decisions

Build the judgment, buy the machinery. Transaction ordering, fee pricing, and connection failover are solved problems with sharp edges; a mature open-source engine handles them well. The judgment layer — which contracts, which actions, whose signatures, what limits — encodes FairWins-specific calls no off-the-shelf tool could know. The split puts custom code exactly where the custom decisions are.

Fail closed on money, fail soft on availability. Screening and address-pinning fail closed — the bouncer would rather refuse than guess. Availability fails soft — every refusal degrades to self-pay. The asymmetry is the whole point: the only thing this infrastructure is ever allowed to break is its own usefulness.

Further reading

One Signature, Zero Gas: How Gasless Payments Actually Work

How FairWins turned every action into a signed “intent” — you sign what you want, and someone else pays the fee to submit it

The Stranded User

A user wins a wager on FairWins. Fifty USDC sits in escrow with their address recorded as the winner. All they have to do is claim it. One problem: their wallet holds exactly zero of Polygon’s native coin — the token every transaction needs to pay its network fee, its “gas.” They funded their wallet with USDC from an exchange, never touched the native token, and have no intention of learning what a gas station is. Their money is on-chain, provably theirs, and completely out of reach.

The old flow was worse than one stuck action. Just creating a wager took two transactions: a step to authorize the platform to move your stablecoin, then the step that actually creates the wager. Accepting one meant the same dance. Every step needed native gas, and the failure mode was plain: a user who holds only the stablecoin can’t act at all.

The cruelest version is lopsided — a user who scraped together enough gas to place a bet but can’t afford the gas to claim their winnings. Money in, no money out. Any gasless design that fixes deposits but not withdrawals has built a lobster trap.

The fix is to make the entire platform run on signed intents. Instead of sending a transaction and paying the fee, the user signs a message — for free, no gas — describing exactly what they want to do. Anyone can then carry that signed message onto the blockchain and pay the fee to submit it. Crucially, the on-chain effect is always credited to the person who signed it, never to whoever submitted it. One signature replaces the whole authorize-then-act dance, and a helper service — a “relayer” — pays the fee.

What an Intent Actually Is

An intent is a structured, human-readable message the user signs with their wallet, built on EIP-712 — a widely used standard for signable data a wallet can display in plain terms, so you see the actual stake, deadline, and counterparty, not a wall of hex. Every action on the platform has its own intent: create a wager, accept one, claim a payout, declare a draw, buy a membership tier, and so on.

Each intent locks down three things:

  1. Who is acting — a named field that must match whoever actually signed. You can’t sign on someone else’s behalf.
  2. Every detail of the action — stake amounts, deadlines, which wager. Nothing is left blank for the submitter to fill in later.
  3. A replay guard and an expiry — a random one-time code so the same intent can’t be reused, plus a window so a stale intent eventually expires.

Signatures are checked under a fingerprint unique to each contract and each network, so a signed intent is valid on exactly one contract on exactly one blockchain — a one-time code used up on one contract can never be replayed on another. The check also accepts signatures from smart-contract wallets, so FairWins’ passkey wallets can sign intents too. A failed intent never uses up its one-time code; a successful one can never run twice.

Paying With a Signature, Not a Gas Balance

Signing an action is the easy half. The hard half is money-in actions. The intent says “stake 50 USDC” — but the platform still has to pull 50 USDC out of the wallet, which normally needs a separate, gas-paying authorization first.

This is what EIP-3009 is for — a feature built into Circle’s USDC that lets a holder authorize a specific payment purely by signing. The user signs an authorization naming who gets paid, how much, and a validity window; the receiving contract presents that signature to the USDC token, and the transfer happens. No standing “allowance” ever exists, and no separate transaction is needed.

So a money-in intent carries two signatures: the FairWins intent (“accept wager 41”) and the USDC payment authorization (“pay 50 USDC”). Which raises the attack that shapes the whole design: what stops the submitter from mixing and matching? Could they staple your payment authorization to a different action, or pair your accept-intent with a payment meant for something else?

They can’t, because the two signatures are cryptographically stapled together. The intent you sign references the exact payment it’s meant to travel with, and the contract refuses to proceed unless the payment matches — same amount, same one-time code — the one you committed to. The USDC token adds the final lock: the payment can only land in the contract that’s asking. The net result: a submitter can refuse to carry your intent, but can never substitute, redirect, or resize the payment.

Two Twins, Identical Rules

Every gasless action is a twin of an ordinary one. Accepting a wager the normal way and accepting it via a signed intent run the exact same checks against the person acting — sanctions screening, membership gating, ownership, account freezes. The only difference is how the acting identity is established: from the sender on the direct path, from the recovered signature on the intent path. The submitter’s own standing is never consulted, and because both twins run the same underlying action, they can’t quietly drift apart.

Keeping Three Codebases in Perfect Sync

Here’s the lesson most write-ups skip. The exact shape of each intent has to be defined in three separate places: the smart contract that verifies it, the app that helps the user sign it, and the relayer service that handles it.

These three definitions must be byte-identical — not just similar in spirit. The signing standard fingerprints the entire structure, so a single reordered field, or a field’s type written slightly differently, produces a completely different fingerprint and a signature that verifies to a random, wrong address. The failure is silent when you build it and total when a real user tries: every signature rejected, every time.

The discipline FairWins landed on treats the deployed contract as authoritative, and every other copy documents exactly where it matches. New intents ship in all three places at once, with cross-references so no one edits one and forgets the others. It’s unglamorous bookkeeping, and it’s the entire difference between a working gasless system and a flood of mysterious support tickets.

Never Stranded

The final rule is architectural humility: the relayer is optional, and the design has to survive without it. Every gasless flow keeps a pay-your-own-gas fallback — the original path is never removed. The app enforces this at the wiring level: a screen literally can’t ship a gasless-only dead end, because the code refuses to build one. If the relayer is switched off, overloaded, rate-limiting, or timing out, the app quietly falls back to the user paying their own gas, and the on-chain result is identical.

The fallback also covers networks where the signature-based payment simply doesn’t exist — one test network’s stablecoin lacks it entirely, so the app detects the gap before opening the signing prompt and uses the normal path. And status is honest end to end: the interface never shows “confirmed” before a transaction is actually mined. Signed, pending, confirmed, expired, and failed are all distinct, truthful states.

Design Decisions

Random one-time codes, not a counter. Each intent’s replay guard is a random value, usable in any order. A counter would force a user’s actions into strict sequence and let one stuck intent block everything behind it; random codes let someone sign three intents and have them land in any order. Cancellation is precise — a user can kill one specific unsubmitted intent, and even that can be gasless.

Sign everything, trust nothing. Every detail the action uses is inside the signed message. The alternative — signing a compact code that stands for details delivered separately — is fewer bytes but shows the user nothing legible to approve. Human-readable signed data means the wallet displays the real stake, deadline, and counterparty.

Fee recovery is bounded and segregated. An optional second payment authorization lets the platform recover its gas cost in USDC, but it’s capped on-chain, settles in the same transaction as the action, and pays into a segregated account — never the relayer’s own wallet. A compromised relayer gains no power to skim fees.

Censorship is the accepted residual risk. A relayer can decline to submit an intent; it cannot forge, alter, or replay one. And because the pay-your-own-gas path always exists, refusal degrades to mere inconvenience. That trade — accept the possibility of censorship, eliminate any custody of user funds — is the entire design in one sentence.


Note: FairWins wagers are peer-to-peer agreements based on publicly available information and legitimate forecasting. Gasless submission changes who pays the transaction fee, not who is accountable: every intent is credited to its signer, who remains fully subject to applicable law and the platform’s compliance screening.

Further reading

Sponsored Gas Without a Vendor: How “No Network Fee” Became True

How FairWins made “no network fee” an honest promise — by quietly covering the fee itself, using infrastructure it already ran

The lie in the confirm screen

A member creates a FairWins passkey account, receives 40 USDC from a friend, and tries to send 10 of it onward. The confirm screen says “Gasless · sponsored — no network fee.” They tap confirm with their fingerprint. The transfer fails.

Here’s why. Every blockchain transaction costs a small network fee, paid in the chain’s own native token — not in the stablecoin the member actually holds. This account had 40 USDC and zero native token, so there was nothing to pay the fee with, and the transaction was rejected. It’s worse on a member’s very first action, which also has to pay to deploy their account on-chain — so during a busy period even an account holding some native token could come up short.

In other words, the product had promised “sponsored” before the machinery to sponsor anything existed. That broke a standing FairWins principle: the interface must never claim something that isn’t true. There were two ways to fix it — change the words, or make them true. FairWins chose to make them true, with one firm constraint: no third-party sponsorship service. The company already ran the plumbing that submits these gasless transactions and the gateway that screens them, so sponsorship had to be built from those parts.

This post walks through what that took: a deliberately tiny “paymaster” contract, an approval service bolted onto the gateway FairWins already ran, a securely held signing key, and — because nobody may ever be stranded — a fallback that keeps every member able to act even when sponsorship is down.

What sponsoring a fee actually means

The smart-account standard these wallets use lets a transaction name a paymaster: a contract that tells the network, “I’ll cover the fee for this one.” The network then charges the fee to the paymaster’s pre-funded balance instead of the user’s. The entire design question is how the paymaster decides which transactions to pay for.

verifying paymaster answers that with a signature. An off-chain service looks at each transaction and, if it approves, signs a stamp of approval tied to that exact transaction. The contract’s only on-chain job is to check the stamp is genuine — signed by the one authorized signer — and, if so, cover the fee.

The FairWins paymaster is intentionally minimal: its approval stamp is bound to the specific transaction — its sender, its exact contents, the fee ceiling, the network, the paymaster’s own address, and a short expiry window. Because the stamp is nailed to all of that, an approval can’t be reused on a different transaction, on a different network, or after it expires. And it needs no memory of past transactions: the account’s own built-in transaction counter already makes each one single-use. A contract that checks a signature and stores no state is the simplest and safest kind, and stays portable to other networks later.

The economics are just as simple. The paymaster holds a pre-funded deposit with the network, and that deposit is both the sponsorship pool and the hard cap on how much can ever be lost. When the network runs a sponsored transaction, it draws the fee from that deposit — FairWins paying its own gas back, one transaction at a time, all accounted for on-chain.

The decision half: an approval service on the gateway FairWins already ran

The approval signature has to come from somewhere, and where is the real policy decision. There’s an open standard for exactly this handshake — how a wallet asks a sponsorship service for approval — with two steps: a dummy approval so the wallet can estimate the transaction size, then the real, signed one. Because the FairWins frontend already builds transactions with standard tooling, adopting it made the client-side work nearly free.

Rather than stand up a whole new service, FairWins added a sponsorship route to the gateway that already fronts its other gasless features. That gateway already had the three controls sponsorship needs, and the new route reuses them instead of reinventing them:

  1. A kill switch — one setting halts all sponsorship instantly; clients quietly fall back to paying their own fee.
  2. Sanctions screening — the account is checked against sanctions lists, and it fails closed: if it can’t be screened, it isn’t sponsored.
  3. Quotas — per-account and platform-wide rate limits, so no one account and no single day can drain the pool.

Two extra ceilings are new, because sponsorship spends real money on every transaction: a sanity limit on transaction size and a worst-case cost cap, so a single deliberately expensive transaction can’t burn a big slice of the deposit. Every refusal happens before anything is signed, so a rejected request costs the operator nothing.

That signature comes from a securely managed cloud key service — the gateway never holds the raw signing key. Critically, the gateway refuses to start if its signing key doesn’t match the signer the on-chain contract expects. A mismatch that would otherwise silently break every sponsorship instead fails loudly at deploy time, where someone will notice.

One discipline is worth calling out: the “stamp” being signed is computed in two separate codebases — the contract and the gateway — and they must produce byte-for-byte identical results, or every sponsored transaction fails. A cross-check test deploys the real contract and asserts the two agree.

Never stranded, never dishonest

Sponsorship is a nice-to-have, and the platform rule is that a nice-to-have must never strand a member. So the app treats every failure to get sponsorship — service down, kill switch on, quota hit, screening refusal, network blip — identically: rebuild the transaction to pay its own fee and quietly retry once.

There’s a subtle trap the code is careful about. If a transaction would fail on its own merits — say, sending more than the account holds — that’s not a sponsorship failure and must not be silently retried, because the retry would fail identically and hide the real reason. So the fallback only kicks in for genuine sponsorship or network failures.

The disclosure follows the same honesty rule that created this feature. The system tracks a single fact: was this transaction actually sponsored? The confirm screen renders straight from it — “Gasless — no network fee” when true, “You pay the network fee” when false, including the exact shortfall when the account can’t cover it. The rule that the words must match reality is built into the mechanism, not left to good intentions.

Why we built it this way

Sponsor the fee, don’t charge it in stablecoin. An alternative was a paymaster that quietly takes a little USDC to cover the fee — but that needs price feeds, extra accounting, and an approval step, all for an outcome where the member still pays. Sponsoring is the smallest pattern and the only one that makes “no network fee” literally true.

Self-hosted, eyes open. Refusing outside vendors costs real work — key management, quota design, watching the runway — and buys independence: no vendor lock-in, no per-transaction markup, and every policy decision enforced in FairWins’ own gateway.

A small, bounded blast radius. If the approval-signing key were ever stolen, the thief could approve sponsorships — wasting the deposit on other people’s fees — but could not withdraw a cent: withdrawal is restricted to a separate owner key kept in offline, air-gapped storage. The worst case is capped at the deposit, throttled by quotas, cut off by the kill switch, and cleaned up by rotating to a new signer. Keeping the deposit deliberately small keeps that worst case small by policy, not by hope.

Open to everyone. Sponsorship isn’t reserved for higher membership tiers. Any passkey account that passes screening and stays within quota qualifies — spend is bounded by the ceilings, the pool, and the kill switch, not by narrowing who benefits.

The result: when the confirm screen says “no network fee,” it’s telling the truth — the app quietly covered it — and when it can’t, it says so plainly.

Further reading