Skip to main content

Here is a question I have never once seen asked of a crypto game, in a review, a listicle, an audit report or a “provably fair” explainer page: what happens if the result never comes?

Not a wrong result. Not a rigged result. No result. Your stake left your wallet, the game acknowledged it, and then nothing happened.

Fairness answers “can the outcome be tampered with”. It does not answer “will there be an outcome”. Those are two different guarantees, and the entire industry, including the honest end of it, markets the first and stays silent on the second.

TL;DR

  • Provable fairness is a safety property: nothing bad happens. Availability is a liveness property: something good eventually happens. Almost every “provably fair” claim covers only the first.
  • Chainlink VRF is deliberately asynchronous. The request and the result are separate transactions, which is what kills front-running, and which also creates a window where a game is mid-flight and your stake is committed.
  • Four things leave a request unfulfilled: an underfunded VRF subscription, a callback gas limit set too low, oracle-side delay, and chain-level unavailability. The gas limit is the nastiest, because the request can be marked fulfilled and charged for while your game’s state never updates.
  • Centralised casinos hide this failure by voiding and refunding at their own discretion, which is the same discretion that lets them void selectively.
  • What separates real architecture from theatre is not whether a recovery path exists, but who may trigger it. If only the operator can, the operator can also decline.

Safety and liveness are not the same promise

Distributed systems people have had precise words for this for forty years. A safety property says a bad thing never happens. A liveness property says a good thing eventually does. They are independent, and you can satisfy one by destroying the other: a game that never resolves anything is perfectly fair, and also useless.

Read any provably fair page. It will tell you at length that the operator cannot influence the number. It will not tell you what the contract does if the number never shows up.

Why verifiable randomness has a waiting room at all

When a game requests randomness from Chainlink VRF, the contract calls the coordinator and the transaction ends. Nothing is decided yet. The oracle network waits a set number of block confirmations, generates the random value along with a cryptographic proof, and submits it back. The coordinator verifies that proof on-chain, and only if it verifies does it call the game contract’s callback with the result.

Those block confirmations are the anti-reorg feature, and the two-transaction split is what stops anyone, bots included, from seeing the outcome and reacting to it inside the same transaction. Every property that makes VRF worth using depends on the result arriving later. The waiting room is the product. But a waiting room is a place where things can go wrong.

The four ways a result fails to arrive

The subscription runs dry. VRF is paid for from a prefunded subscription. If the balance falls below what a request costs, requests stop being fulfilled. This is not exotic. It is a direct debit that bounced, and it is the most common cause of a stalled on-chain game.

The callback gas limit is too low. The requesting contract specifies how much gas the fulfilment callback gets. If the callback logic needs more, it reverts. Here is the ugly detail most people miss: the coordinator can treat the request as fulfilled and take payment while your contract’s state never updated. The randomness was delivered. Your game did not receive it. Fulfilled and stuck at the same time.

Oracle-side delay. Node operators can lag during congestion or fee spikes. Usually seconds. Occasionally not.

The chain itself. Base runs a centralised sequencer, and I am not going to pretend otherwise on a blog that has spent months arguing infrastructure choice is the whole game. If the sequencer halts, nothing lands, VRF fulfilments included. Base does inherit Ethereum’s forced-inclusion path, so transactions can be submitted via L1 rather than sitting permanently at one operator’s mercy. That is a meaningfully better failure mode than an application server going dark, but anyone telling you their chain has no liveness assumptions is selling something.

The centralised version of this problem is worse, and invisible

You might reasonably think a traditional crypto casino handles this better, because you have never seen it happen. That is the point. When a centralised game’s RNG service fails, the operator voids the round and refunds the balance from a database. Instantly, silently, no incident report. It looks like flawless uptime.

It is not uptime. It is discretion. The authority that voids a round when a service breaks is the same authority that can void a round for other reasons, and you cannot tell the two apart. A refund you cannot audit is not a guarantee. It is a favour, and favours are granted case by case by an interested party.

The question that actually matters: who holds the recovery key

So a well-built on-chain game needs a path out of the waiting room: a stuck request must eventually be resolvable, either by reissuing it or by returning the escrowed stake.

The temptation is to give the operator a function that cancels a stuck game and refunds the player. It solves the problem in about six lines. It also reintroduces the exact thing on-chain gaming exists to eliminate. A privileged cancel function is an admin key wearing a helpful expression: whoever can rescue a stuck game can also decide which stuck games get rescued, and can cancel one for reasons that have nothing to do with liveness.

The correct shape is permissionless and time-bounded. After a deadline enforced by the contract, anyone can call the recovery function, and the outcome is fixed in code: the stake returns to the player, or the request is reissued. Not the operator’s choice. Not a support ticket. A function call a stranger could make on your behalf, with a result nobody gets to pick. That is the difference between a system that is honest and a system where honesty is structurally irrelevant.

Three things to check before you play anything

These are on-chain questions, so you can answer them yourself, and you should ask them of us as readily as of anyone else.

Is there a timeout at all? Read the game contract on BaseScan. Look for a deadline, a request timestamp, a cancel or refund function. If a stuck game has no coded exit, recovering your stake depends on someone choosing to help you.

Who can call it? Check the modifiers. onlyOwner on a refund function means the recovery path is a permission, not a guarantee. What you want is a function anyone can call once a block-timestamp condition is met.

Is the subscription funded? The VRF subscription balance is public. A platform running on fumes will stall, and you can see it coming before committing a single transaction.

Where Satoshie sits

Satoshie runs on Base with Chainlink VRF, and every game leaves the same public trail: the randomness request, the coordinator’s verified fulfilment, the resolution. That trail is why the liveness question is answerable here rather than being a matter of faith. A stalled request is visible to everyone at the same moment, including us. No delay gets quietly absorbed and never mentioned, and that constraint is good for us. We would rather compete on a standard that includes the awkward parts.

Fairness is the floor, not the ceiling. Anyone can claim a fair shuffle. Far fewer platforms can show you, in deployed code you did not have to take their word for, what happens on the day it breaks. Ask your platform what happens when the randomness does not arrive. If the answer is “contact support”, you already know what kind of system you are playing on.

📷 Photo by Daniel Dan on Unsplash

Valentina Ní Críonna

Author Valentina Ní Críonna

More posts by Valentina Ní Críonna