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

Enough Signatures Is Not Enough: Adding Real Rules to a Shared Vault

How a small guard contract turns “enough people approved” into “enough people approved, and it obeys the rules” — with no admin key and no way to lock a vault out of its own money

The transaction that had every signature it needed

A three-owner treasury runs a two-of-three multisig: any two owners must approve before money moves. One Tuesday a proposal appears in the queue — send 180,000 USDC to an address nobody recognizes. Owner one approves it from a phone between meetings; the amount looks like the quarterly market-maker payment, and the description says exactly that. Owner two approves an hour later for the same reason. Two approvals: threshold met. The transaction executes. The address belonged to whoever phished owner one and quietly queued the proposal.

Nothing in the multisig failed. That’s the uncomfortable part. A multisig has exactly one control — enough owners agree — and once that bar is cleared, it will send any amount, to any destination, at any time. Everything else teams rely on is procedure: “we review destinations carefully,” “we never approve on mobile,” “big transfers get a phone call first.” Procedures are rules that live in people’s heads, and people approve things between meetings.

Part 1 covered FairWins’ shared vaults: a group holds a multisig with a propose-and-approve queue. This post adds the missing layer — a policy engine that constrains what an already-approved transaction may do, after the threshold is met but before the money moves. The phished proposal above dies at that final step against a per-transaction limit or a recipient allowlist, no matter how many signatures it collected.

The interesting question is where such rules can live so they’re actually binding. Checks inside the app are only advisory — anyone can talk to the multisig directly. A rules service on a company server is just another party you have to trust. The place that actually works is a transaction guard: a contract the multisig itself consults, on-chain, before every execution.

Where the guard sits

A modern Safe multisig can register a guard contract with a veto over every transaction. Just before executing an approved transaction, the Safe hands the guard the full details; if the guard objects, the transaction never runs.

FairWins’ guard is a single shared contract per network: one immutable contract holds the rule settings and running totals for every vault that has opted in, filed under each vault’s address. Because the multisig itself is the one asking, the guard always knows which vault’s rules to apply — no setup handshake needed.

Two properties define the trust model. First, the guard is restriction-only: it can block a transaction but never start, approve, or execute one, and it holds no money. Second, a vault is the only authority over its own rules: changing a vault’s policy is itself a transaction that must clear that vault’s normal approval threshold. There is no owner, no admin role, and the guard is deliberately not upgradeable — an upgrade key over it would be a master backdoor over every vault’s enforcement.

The rules

Version 1 ships the classic treasury controls, each independently switchable per vault:

  • Per-transaction limit — per asset; no single outgoing transaction may exceed it.
  • Daily limit — per asset, over a rolling 24-hour window.
  • Recipient allowlist — outgoing money may only go to pre-approved addresses.
  • Cooldown — a minimum wait between money-moving transactions.

A transaction “counts” against these limits when it moves value — sending the native coin, or one of the standard token operations the guard recognizes: transfer, transfer-on-behalf-of, and approve. Counting approve matters: approving a spender is a spending grant, and ignoring it would leave an obvious loophole where the vault grants an unlimited allowance and the spender quietly drains it later.

The allowlist has a subtlety worth copying. For token movements it checks the beneficiary — whoever ultimately receives or can pull the tokens — not just the token contract being called. For everything else it checks the direct target, so even a transaction the guard can’t fully interpret still can’t reach an un-approved contract. A locked-down vault genuinely stays locked down.

Closing the escape hatches first

Limits are worthless if a transaction can step outside the accounting entirely. So while any rule is active, the guard flatly rejects two dangerous shapes:

  • Code that runs inside the vault’s own context. One transaction type lets external code execute with the vault’s full authority — it could move funds without tripping any rule, or even rewrite the guard’s own settings. Refused outright. (The cost is that a certain batching trick isn’t available on rule-governed vaults; since these flows are single-action anyway, nothing is lost.)
  • Gas-refund transactions. The multisig can be told to reimburse the submitter’s gas out of vault funds — an outflow the limits wouldn’t otherwise see. Refused too.

Both rejections come back as clear, named errors, so the app can explain exactly why something was blocked instead of guessing at a cryptic failure.

You can always loosen the rules

The classic failure mode of on-chain rules is locking yourself in: owners set a cooldown of a year, or turn on an allowlist whose only address is now defunct, and the vault is bricked. The fix is a simple, deliberate carve-out. Two kinds of transaction skip all the spending rules entirely: managing the vault itself (owners, threshold, the guard), and changing the vault’s own policy. Both still require the vault’s normal approval threshold — so the only thing exempt is the collective ability to change the rules.

The result: a too-strict policy can always be loosened by the same group consent that set it, and no vault can ever be locked out by its own rules. This isn’t just asserted — it’s tested against a real multisig deployment, not a stand-in mock.

What you see is what gets enforced

Owners should learn a transaction breaks policy before they spend approvals on it. The guard offers a read-only preview that runs the exact same rule check the real enforcement uses, and the app runs every proposal through it first — so what the interface warns you about can never drift from what the chain enforces. Enforcement itself is careful about ordering: the guard reads and evaluates first, then records the updated totals, and never calls out to another contract, so there’s no room for the reentrancy trickery that plagues contracts that move money.

Rules from the very first transaction

A guard attached after creation leaves a window where early transactions are unpoliced. So FairWins can wire the guard in during vault creation itself: the guard address and starting rules are part of the setup, baked into the vault’s predicted address, and if any part of that setup fails the whole creation is aborted — a half-configured vault can never come into existence. For an existing vault, attachment is ordered so there’s never a moment where the guard is half-on.

Design decisions and accepted limits

Immutable, not upgradeable — on purpose. Elsewhere FairWins leans on upgradeable contracts, but the guard has no upgrade path by design: whoever could swap it out could disable every vault’s enforcement. Improvements ship as a brand-new guard that each vault chooses to adopt through its own approval — consent per vault, never a decision imposed by a single deploy key.

Fixed daily window, not perfectly rolling. A truly continuous rolling window would require storing unbounded transaction history on-chain. The simpler fixed-reset window can, in a worst case straddling a reset, allow up to twice the limit — a small, bounded weakening that’s disclosed in the interface rather than hidden.

Uninterpreted transactions pass the spending limits. The guard values native transfers and the standard token operations; something exotic like a DEX swap passes the spending limits unmeasured. It still faces the allowlist on its target — so a vault that needs a hard lockdown turns on the allowlist and gets one.

Conservative accounting. Totals are recorded before the transaction executes. If the inner action later fails quietly, the spend still counts. Overcounting can only ever restrict, never over-permit — the safe direction to err.

The through-line: every rule that exists is enforced exactly, every gap that exists is named, bounded, and disclosed, and no key anywhere can quietly waive either.

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

Passkey Smart Accounts: A Wallet You Open With Your Fingerprint

How FairWins turned Face ID into a real, self-custodial crypto account — no seed phrase, no browser extension, nothing to write down

The twelve words nobody wants to write down

Picture how someone joins a peer-to-peer wager app. A friend sends a link. You have never installed a crypto wallet, you have never copied twelve recovery words onto a scrap of paper, and you are not about to start tonight. What you do have is a phone with a fingerprint sensor and a small, tamper-resistant security chip that has been quietly signing things for you — payments, app logins — for years.

That chip speaks a standard called passkeys — the same WebAuthn technology behind Face ID and fingerprint sign-in on the sites you already use. When you create a passkey, your device generates a private key that it never hands out. It will only prove who you are after a biometric check, and the secret itself never leaves the hardware. That is exactly the security model crypto wallets have chased for a decade: a key that can’t be exported, phished, or pasted into a fake support chat.

There is one stubborn catch. Passkeys and Ethereum accounts speak different mathematical “dialects” for signatures. The security chip in your phone signs using one type of cryptographic curve; ordinary Ethereum accounts expect a different one. They simply don’t recognize each other’s signatures, and no amount of interface polish papers over that. If you want a passkey to control real funds, the account itself has to become a small smart contract — a program on the blockchain — that knows how to read the passkey’s dialect. And something other than the user has to be able to submit that first transaction, because a brand-new passkey can’t pay a network fee from an account that doesn’t exist on-chain yet.

That is the whole idea behind FairWins passkey accounts: a smart-contract wallet controlled by a passkey, able to verify the phone’s signatures directly, living at an address that’s known before the wallet is ever deployed.

An account is a list of owners, not a single key

Rather than build this from scratch, FairWins uses a widely deployed, professionally audited smart-wallet design — Coinbase’s Smart Wallet — and adopts it as-is, without rewriting its logic. The rule is deliberate: an audited contract is only worth something if you don’t quietly fork and change it. Reusing it unmodified means those outside audits still apply.

The clever part of that design is how it defines ownership. An “owner” of the account isn’t one fixed key; it’s simply an entry in a list, and each entry can be one of two things:

  • a linked ordinary Ethereum wallet address (say, a MetaMask you already have), or
  • a passkey — represented by the two numbers that make up its public key.

Both kinds sit in the same list and carry equal authority. Any owner can add or remove other owners, and the contract refuses to remove the last one — so an account can never accidentally lock itself out by deleting its only controller. When a signature arrives, the account looks at which owner produced it and checks it the right way: the passkey path for a passkey, the ordinary path for a linked wallet. One mechanism covers both signing in for transactions and approving off-chain messages.

Checking a passkey signature on the blockchain

A passkey signature is more than a scribble over some data. When your device signs, it wraps the thing you’re approving inside a small standardized bundle that also records details like “this was a genuine WebAuthn sign-in” and “a user was present.” To trust that signature, the contract re-runs the important checks from the official WebAuthn specification right on-chain: it confirms the bundle is the expected kind, that the challenge inside it matches what was really being approved, that a user was actually present, and it rejects a known signature-tampering trick. It deliberately skips a few checks that the phone and the app’s site association already enforce — an honest trade that keeps verification affordable.

Then there’s the heavy math of actually verifying the signature, which is expensive to do on a blockchain. Where the network offers a fast built-in helper for exactly this kind of signature, the contract uses it (cheap — a few thousand units of gas). Where a network doesn’t, the same contract quietly falls back to doing the math the slow, pure-software way. Same code, both worlds — which is why supporting a new network later is a configuration change, not a contract rewrite.

An address before there’s an account

Here’s a nice trick that makes onboarding feel instant: your account address exists before the wallet is actually deployed. The address is calculated purely from your initial list of owners, so the app can show it — and a friend can send funds to it — while the contract itself is still just a plan. FairWins deploys the piece that mints these addresses in an identical way on every supported network, so your address is the same everywhere.

Deployment happens lazily, the first time you actually do something. That first action carries a little bundle of setup instructions: the network deploys your account and performs your transaction together, in one shot. (One hard-won lesson from building this: a popular off-the-shelf toolkit assumed a different deployment source than the one FairWins uses, which quietly produced the wrong predicted address and made every early transaction fail. The fix was to pin everything to the exact same source. If you ever wire a custom wallet into a generic toolkit, check its address assumptions first.)

No seed phrase doesn’t mean no keys

Passkeys are great at signing but they don’t encrypt. Some FairWins features — the private ones — need encryption keys too. So the app uses a companion capability of the passkey standard to derive a stable secret from your authenticator, stretch it into an encryption key, and use that to wrap a single master seed. Every owner on the account unwraps the same seed, which is why your encrypted data survives switching devices. If an authenticator doesn’t support this capability, the app says so plainly rather than silently generating the wrong keys.

Why we built it this way

  • Reuse an audited design, don’t fork it. Using the Coinbase Smart Wallet unmodified keeps its outside security audits meaningful. A private fork would need re-auditing forever.
  • Upgrades belong to the user. These accounts can be upgraded, but only the account’s own owner can authorize that. FairWins holds no override switch over anyone’s wallet — which is what makes this genuinely self-custodial, not “self-custodial” in scare quotes.
  • Fast where possible, correct everywhere. Using the network’s built-in signature helper where it exists, and falling back to software elsewhere, costs a bit more on some chains but means one single codebase runs everywhere.
  • Honest about fees. The confirm screen only says a transaction is sponsored when it truly is; otherwise it says you pay the network fee. (A later post covers how sponsorship works.)

The result is an account you open with a thumbprint, funded at an address that exists before the contract does, and controlled by keys that no server — including ours — ever holds.

Further reading

Sanctions Screening as a Shared Building Block: One Guard, Every Money Path

Why FairWins moved compliance out of the frontend and into a single, fail-closed on-chain gate — and how the same small piece of code protects wagers, pools, memberships, and token issuance

The check that wasn’t there

Picture the code review. Your team ships wallet screening for a peer-to-peer wager platform: when a user connects, the website checks their address against a sanctions list, and if the address is listed, the interface refuses to proceed. The compliance box is ticked. The demo looks great.

A week later, someone on the security review asks the obvious question: what happens if a sanctioned address never opens your website? The contracts live on a public blockchain. Anyone with a script can call them directly, bypassing your site entirely. Your screening layer — the one your Terms of Service describe as a control — turns out to be a polite suggestion.

This is the trap that catches most “compliance-aware” crypto apps. Sanctions exposure under US law is strict liability: it doesn’t matter that you intended to block the address, or that your interface would have blocked it. If your contract accepted money from a listed address, the violation happened. A check that lives only in the website is not a control; it’s theater with good intentions.

FairWins’ answer is to treat sanctions screening the way a careful team treats any core safety check: as a shared building block baked into the contracts themselves. One small, shared piece of code — call it the sanctions guard — is consulted by every entry point on the platform where money moves. The website still checks first, for fast feedback and to avoid wasting anyone’s gas, but the layer that actually enforces is the one nobody can route around.

The guard itself

The guard is deliberately tiny: about a hundred lines, holds no funds, and can’t be upgraded. It combines two lists into a single yes-or-no verdict:

  1. A public sanctions oracle maintained by Chainalysis — an on-chain service that answers, for any address, whether it appears on the US Treasury’s sanctions list.
  2. A discretionary block-list the operator maintains — a simple list for addresses tied to illicit finance beyond the official set, editable only by the holder of the narrow compliance permission described in part 1 of this series.

Consumers get two ways to ask the guard a question. One returns a plain yes-or-no, for callers that want to branch on it — that’s what the website’s advisory check uses. The other simply stops the transaction cold if the address is blocked, which is the form the contracts use, because refusing to let a transaction complete is the cheapest and safest way to make sure a forbidden action never happens.

The block-list editing has one nice property worth calling out: every change records who made it, which address was affected, in which direction, and a human-readable reason — all written permanently to the blockchain. So the block-list’s entire history is a built-in audit trail. There’s no separate off-chain compliance database to subpoena or lose; the on-chain event log is the record. And the keys that can edit those lists follow the platform’s air-gapped, offline signing process, so no change happens casually.

Fail-closed, for real

The interesting engineering is in how the guard talks to the outside sanctions oracle. The naive version — just call the oracle and wrap it in a try/catch — has a sharp edge. If the oracle address is misconfigured, pointing at nothing or at the wrong network, that kind of failure isn’t reliably caught, and the system can silently start treating everyone as clean.

So the guard makes the query in a careful, low-level way that gives it full control over every possible failure: the oracle reverts, runs out of gas, returns nothing, or returns garbage. Anything short of a clean, well-formed “yes” or “no” is treated as the oracle gave no usable answer — and in that case the guard blocks every address. The rule is stated plainly in the platform’s requirements: if the screening source is unavailable, refuse the action rather than allow it unscreened. This is what “fail-closed” means — when in doubt, deny.

There’s exactly one deliberate exception, and it’s a configuration, not a failure. Setting the oracle to “none” means block-list-only enforcement — the intended posture for networks where Chainalysis simply doesn’t operate. Test networks get a stand-in; production gets the real oracle address injected at deployment, never hardcoded. The distinction is precise: a configured but broken oracle blocks everyone, while an intentionally unset oracle blocks only the block-list. Confusing those two states is exactly how fail-closed systems quietly turn fail-open.

One guard, four subsystems

What makes this a reusable building block rather than a one-off feature is that the same guard protects four independent parts of the platform in the same way:

Wagers. Every escrow entry point screens first. Creating a wager screens the creator. Accepting one screens both sides — the person accepting and the original creator — because acceptance is the moment the second stake enters escrow, and the creator might have been added to a sanctions list since they first posted the wager. Screen at every entry, every time.

Memberships. Buying, upgrading, extending, or redeeming a voucher into a membership all screen the person before any USDC moves. Even the admin-only path that grants a membership directly screens the recipient — the guard can’t be bypassed even by operators, so a permission-holder can’t accidentally hand access to a listed address.

Wager pools. Group pools screen creators and joiners through the same guard, with one extra safeguard worth stealing: on production networks the pool factory refuses to run at all if screening is supposed to be on but no guard is configured. The “unset means off” convenience that’s fine on a laptop becomes an impossible, boot-blocking state in production.

Token issuance and naming. Issuing a token screens the issuer and passes the same guard into every token it creates. The naming registry checks the guard before letting anyone claim a name.

Each of these holds its own pointer to the guard, so the guard can be swapped out without touching any of them — and a single block-list update propagates instantly to all four. One list, one place to edit it, one event stream, four enforcement points.

What is deliberately not screened

Here’s the design decision most teams get backwards: the exit paths — claiming a refund, claiming a payout, sweeping up expired wagers — are not screened.

The reasoning matters. If an address is added to the block-list after their stake is already sitting in escrow, screening the exit would permanently trap their funds inside your contract. That turns a screening control into an asset freeze — a much heavier and legally distinct act than simply refusing new business, and one that effectively makes your escrow contract a custodian of blocked property. FairWins draws the line cleanly: a listed address can take no new action that moves value in, but can always recover what’s already theirs. The guard gates entry, never exit.

Trade-offs

A little gas on every entry. Each screened action pays for an extra cross-contract call, plus a second one into the oracle when it’s set. That’s real overhead on the busy path. The team judged it worth it, because the alternative — screening only once, at membership time — leaves a gap: an address listed mid-membership could keep wagering until renewal. Re-screening at every entry closes it.

Trusting an outside oracle. The Chainalysis oracle is a centralized, permissioned data source, and FairWins takes its answers as ground truth. The safeguards are structural rather than trustless: the guard only ever reads from it, it can be swapped out if it’s ever compromised or retired, and the discretionary block-list keeps working even with the oracle unset. This is an honest trade — no decentralized sanctions feed exists, and pretending otherwise helps no one.

Fail-closed can mean downtime. If the oracle ever broke for everyone, every screened entry point would halt until an operator re-pointed or unset it. That’s the accepted cost of failing closed: a brief outage is recoverable; a strict-liability violation is not.

Defense in depth, not defense in one place. The on-chain guard is one layer of several: an edge network geo-gate that blocks restricted regions before a request even reaches the app, the website’s fast advisory check, versioned legal documents, and on-chain records of user consent. The guard is the layer that holds when every other layer is skipped — because on a public blockchain, one of them always can be.

Further reading

Put Your Idle Crypto to Work: Staking Comes to FairWins

Earn staking rewards on ETH and POL, right from your wallet — with the same honest, self-custodial design you already trust for wagers.


Most of the crypto sitting in a wallet is doing nothing. It waits. Staking changes that: you put an asset to work securing a network, and the network pays you for it. Until now, doing that meant leaving FairWins for a maze of unfamiliar apps, wrapped tokens, and fine print.

Not anymore. Staking is now live under Finance → Earn → Staking. Pick an asset, see exactly what you’ll earn and what you’ll pay, sign once, and you’re staked — without ever handing your funds to us.

Two ways to stake

We launched with the two staking styles that cover the most ground, each surfaced as its own clearly-labeled option in the Stake list.

Liquid staking keeps you flexible. You stake ETH with Lido and receive wstETH, or stake POL with Polygon’s sPOL and receive sPOL. These liquid staking tokens quietly grow in value as rewards accrue — and because they’re ordinary tokens in your wallet, you can hold them, move them, or swap back to the underlying asset whenever you like. No lock-up to think about for the token itself.

Delegated staking goes straight to the source. You delegate POL to a curated Polygon validator and earn the validator’s rewards directly. We maintain a hand-picked allowlist of reputable, healthy validators — filtered for strong uptime, sensible commission, and a named operator — so you’re choosing from a short, vetted list rather than a sea of unknowns. Delegated positions have an unbonding wait when you exit, and we tell you that up front, every time.

Honesty is the whole point

FairWins has one rule that shapes every screen: never imply something the chain hasn’t actually done. Staking is no exception.

  • You see the real numbers before you sign. Estimated APR, the asset you’ll receive, and — where one applies — the platform fee as its own line item with the exact amount that will actually be staked. No surprises after the fact.
  • You can always get your funds back. Unstaking, withdrawing, and claiming rewards are always available. Nothing we do can trap your position.
  • Unbonding and slashing are stated plainly. Delegated staking carries an unbonding period and, like all delegation, a slashing risk. We put both in front of you rather than burying them.
  • When something isn’t available, we say so. If a network’s staking is temporarily unavailable, you’ll see an honest “not available right now” state — never a broken screen or a guessed rate.

And it’s non-custodial from end to end. You stake from your own wallet, straight to the provider. FairWins never takes custody of your assets between transactions — a stake either completes atomically or reverts and leaves you exactly where you started.

A transparent platform fee that funds the commons

Running a trustworthy financial surface costs something, and we’d rather be honest about how it’s funded than hide it. Liquid staking now carries a small platform fee that flows to the FairWins treasury — the shared pot that keeps the lights on and the platform improving.

Here’s how we’ve kept it fair:

  • It’s disclosed before you sign, always — a clear line showing the rate and the net amount you’ll stake. You are never charged more than the rate you were shown.
  • It applies only to liquid staking. Delegated staking is fee-free. (This isn’t arbitrary: a delegation is bound to your wallet by design, and routing it through a fee layer would have meant taking custody — which we won’t do. So we charge only where we can do it cleanly and atomically.)
  • When the rate is zero, there’s no fee line at all — the experience is byte-for-byte identical to fee-free staking.

The fee lives in the same single, on-chain fee configuration every other FairWins service uses. One source of truth, publicly visible, no hidden second ledger.

Built to be governed — and to be stopped

Behind the friendly Stake button is a new on-chain control surface that makes staking safe to operate at scale.

If a provider is ever compromised, a validator misbehaves, or a contract address comes into question, an authorized responder can pause new staking on that network instantly — no app update, no waiting. Within moments, the Stake area stops offering new positions and shows an honest paused state. Crucially, a pause never touches your exits: unstake, withdraw, and claim keep working the entire time, because those paths never route through our contracts.

Every operator action — pausing, resuming, updating a provider address, curating the validator list — is recorded on-chain as an auditable history of who changed what and when. And these controls are held by a multisig, so no single key can move them. It’s the kind of plumbing you shouldn’t have to think about, precisely because we did.

Woven into the app you already use

Staking isn’t a bolt-on. It’s wired into the surfaces you rely on:

  • Portfolio bottom sheets surface your staked positions and let you act on them in place.
  • Notifications keep you posted on the moments that matter.
  • Your activity log records every stake, unstake, and claim as part of your unified history.
  • Passkey and classic wallets both just work — a passkey stakes in a single confirmation that covers the whole action, spending permission included.

Get started

  1. Open Finance → Earn → Staking.
  2. Pick an option — Lido (ETH), sPOL (POL), or a curated Polygon validator (POL).
  3. Review the terms: estimated APR, what you’ll receive, the unbonding wait if any, and the fee line if one applies.
  4. Enter an amount and confirm. You’re staked.

Your crypto has been sitting still long enough. Put it to work — on your terms, in your custody, with every number on the table.

Staking involves risk, including validator slashing and provider-protocol risk, and rewards are variable and not guaranteed. Availability depends on the network. Always review the terms shown before you stake.

Soulbound Memberships, Transferable Vouchers: Splitting a Token in Two

Why FairWins made membership non-transferable, then built a gift-and-resale market on top of it anyway

Responsible use. FairWins wagers are based on publicly available information and legitimate forecasting. Memberships gate access to that activity; they are not a mechanism for circumventing any law. All participants remain fully subject to applicable laws and compliance requirements, and every membership — however acquired — passes sanctions screening.

The gift you can’t give

A FairWins membership is deliberately boring. You pay in USDC — $2 for Bronze, $8 for Silver, $25 for Gold, $100 for Platinum — and for the next 30 days your wallet can create and accept wagers, up to a limit set by your tier. The membership is soulbound, a term for something permanently bound to a single wallet: it lives at your address, it can’t be transferred, and there’s no market for it. That’s a feature, not a limitation. An access record that can move is an access record that can be stolen, rented out to a sanctioned party, or briefly borrowed to sneak past a compliance check.

Then a product request landed: let me buy a membership for a friend. And its sibling: let me resell the one I bought and don’t want. Both are completely reasonable — gift cards and resale markets are table stakes for any paid product — and both are, on their face, impossible. A record welded to your address has nothing to hand over. And making it movable would destroy the very properties the platform relies on: sanctions screening happens when membership is granted, usage limits are tracked per wallet, and the wager engine trusts that the wallet holding a membership is the one that was screened.

The tempting-but-wrong fix is to bolt a “transfer, and re-screen the new owner” button onto the membership itself — turning a fixed access record into a moving target every other part of the system has to special-case. The fix FairWins shipped is cleaner: don’t make the membership transferable. Make the right to claim one transferable, and keep the two ideas in completely separate contracts.

Two rails, one membership

Start with what “soulbound” actually means here. A FairWins membership isn’t an NFT with transfers switched off — it isn’t a token at all. It’s just a record in the platform’s membership ledger, filed under your wallet address: which tier you have, when it expires, and how much of your usage limit you’ve used this month. There’s no “transfer” button to disable because there’s nothing to hand over — non-transferability simply falls out of how the data is shaped.

It’s worth contrasting this with the popular “soulbound token” — a standard for NFTs permanently locked to a wallet but still visible in it. FairWins didn’t need the token part at all: memberships are read by contracts, not shown off in wallets, so a plain ledger entry is simpler, cheaper, and has no transfer machinery to audit.

Buying a membership directly writes that record: it screens the buyer against sanctions lists, pulls the tier’s price in USDC, marks the tier and its expiry, and resets the usage counters. Thirty days later, it lapses.

The new idea adds a second way in that lands on the exact same record. A membership voucher is a real, freely transferable NFT — think of it as a prepaid gift card for a membership — minted for the tier’s normal price. The whole trick is in what a voucher doesn’t do:

  • It grants no membership while you hold it. No clock is running, no usage limits accrue, your wallet has no access.
  • It never expires. A voucher is a bearer claim you can sit on for a year and still redeem into a fresh, full 30-day membership.
  • It locks in its tier and duration at the moment it’s minted. If the team later reprices or retires that tier, the voucher still delivers exactly what it was bought for.

Because the voucher is inert — it confers nothing until redeemed — it is completely safe to trade. Gifting it is an ordinary transfer. Reselling it works on any standard NFT marketplace. The contract even suggests a small resale royalty back to the treasury (2.5% by default, capped in the code at 5% so it can never be cranked higher). None of that touches compliance, because none of it grants anyone access.

Redemption: where the rails converge

A voucher becomes a membership at one moment: redemption. This is the single control point where everything a direct buyer faces gets applied to the person redeeming.

In plain terms, the redemption does this, in order:

  1. Confirm the person redeeming actually owns the voucher.
  2. Confirm they don’t already have an active membership for that role.
  3. Screen them against the sanctions lists — and if they’re listed, stop everything right here.
  4. Write the membership: grant the exact tier and duration the voucher locked in, reset the usage counters, record which terms they agreed to.
  5. Only then, as the very last step, burn the voucher.

The ordering carries the product’s failure semantics. If the redeemer is sanctioned, or already holds an active membership, the entire call is undone and the voucher is left completely untouched — still owned, still tradable. A legitimate buyer is never punished because some previous holder couldn’t redeem, and because the voucher is destroyed only at the very end, any earlier failure rolls the whole thing back safely.

Notice who gets screened: only the person redeeming, and only at the moment of redemption. Minters and resale buyers are deliberately not screened. That’s a conscious trade-off: a sanctioned party could profit by reselling a voucher they never redeem. What they can never do is turn one into actual platform access, because the screen sits exactly where access is granted. Compliance lives at the point of use, not the point of trade.

After redemption, the two routes are indistinguishable. The wager engine reads the same membership record either way and has no idea how it was obtained — and that’s a hard requirement, verified by running the full test suite against both routes.

The economics are deliberately flat

A voucher costs exactly the tier’s normal price — the same amount the direct route charges — so neither path is cheaper and there’s no buy-here-redeem-there arbitrage to game. The money goes to the treasury the moment the voucher is minted, which works because granting the membership later costs the platform essentially nothing, so there’s no need to hold reserves against outstanding vouchers. There are no primary refunds: buyer’s remorse is resolved by reselling or redeeming. And the voucher’s own artwork and description — generated entirely on-chain — literally reads “utility access token, not an investment.”

Buying a batch of vouchers as gifts and sending them straight to a recipient is handled by a small, separate helper, since the voucher itself only mints one at a time. The helper pulls the exact total, mints the batch, and forwards every one to the recipient in a single transaction. It holds no funds at rest and has no admin or withdrawal path. If it isn’t deployed on a given network, buying one at a time still works.

Privacy: pseudonymity, stated plainly

The voucher route has a quiet second use. Because redemption only checks that you own the voucher — never that you minted it — you can move a voucher to a fresh wallet and redeem it there. The resulting membership keeps no back-reference to who bought it or how it changed hands, so your wagering activity isn’t chained on the public ledger to the wallet that originally paid. A gasless version goes further: since redeeming moves no money, a helper service can submit the transaction for you, so even the wallet paying the network fee needn’t be your trading wallet.

FairWins is careful not to oversell this. Voucher mints, transfers, and burns are all public events — anyone can watch a voucher move. What redeeming from a fresh wallet buys you is pseudonymity, not cryptographic unlinkability, and the interface is required to say exactly that.

Design decisions

Changeable logic, frozen asset. The membership ledger can be upgraded in place — the voucher feature itself arrived as one such upgrade — because screening, terms, and grant logic must be able to evolve. The voucher is the opposite: deliberately not upgradeable, because the rules of a tradable, paid-for bearer asset must not change after someone buys it. The thing people pay for stays fixed; the machinery around it can improve.

The membership is a ledger entry, not a locked NFT. No transfer function to disable, no locked-token standard to implement. The only cost is that it’s invisible in your wallet — which doesn’t matter for something only contracts ever read.

Royalty as a hint, not a cage. Forcing royalties would mean whitelisting marketplaces or running our own, killing open trading and the trade privacy that comes with it. A flat, capped suggestion keeps the utility framing honest, accepts that some marketplaces will ignore it, and lets the platform earn reliably on the first sale.

The general pattern travels well. When you need a token to be both non-transferable (for compliance and integrity) and transferable (for gifting and resale), you don’t need one token that does both badly. You need two artifacts — an inert, tradable claim and a soulbound grant — joined by a single, guarded redemption that burns one and writes the other.

Further reading

  • ERC-721, the non-fungible token standard the voucher is built on: https://eips.ethereum.org/EIPS/eip-721
  • EIP-2981, the NFT royalty standard: https://eips.ethereum.org/EIPS/eip-2981
  • EIP-5192, the minimal soulbound (locked) NFT standard discussed above: https://eips.ethereum.org/EIPS/eip-5192
  • OpenZeppelin Contracts, the audited building blocks behind the token and access logic: https://docs.openzeppelin.com/contracts