Skip to main content

Five randomness requests were made on Base this week and never answered. Against 12,940 that is a rounding error, and on its own it would be a footnote. What makes it worth a post is what happened when I checked whether the people who made those requests could simply give up and walk away.

They cannot. The function that closes a Chainlink VRF subscription reverts while a request is outstanding, and a request that the oracle has declined to answer stays outstanding until somebody pays for it to be answered. So I stopped looking at this week and counted the whole coordinator. Of the 1,174 active subscriptions on Base’s VRF 2.5 coordinator tonight, 140 are holding an unanswered request. Only nine of them drew at all in the last seven days. The other 131 are not games any more. They are subscriptions nobody can close, and the oldest has been in that state since 1 November 2024.

TL;DR

  • 140 of 1,174 active Chainlink VRF 2.5 subscriptions on Base (11.93%) currently return true from pendingRequestExists. Only 9 fulfilled anything in the past week.
  • I called cancelSubscription as the owner against all 131 non-drawing ones. All 131 reverted with PendingRequestExists(). In a 40-subscription control group with no pending request, 32 succeeded and none failed for that reason.
  • Binary-searching the chain’s history dates the freezes exactly: 716 days for the oldest, 700 days for one holding 33 LINK that has never had a single request answered, and 587 days for a game that had already completed 100,034 draws.
  • The 131 hold 1.287 ETH and 183.6 LINK, about $5,200, across 121 owners and 500 consumer contracts.
  • The owner can hand the subscription to somebody else and a stranger can pay money into it, but the only key that can actually close it belongs to Chainlink’s multisig.
  • A fairness proof describes the draw that happened. Nothing on chain announces the draw that did not.

What “pending” means here

When a game contract asks for randomness, the VRF coordinator at 0xd5D517aBE5cF79B7e95eC98dB0f0277788aFF634 stores a commitment hash against the request id and emits RandomWordsRequested. When an oracle node answers, the commitment is cleared and RandomWordsFulfilled goes out. Between those two moments the request is pending, and in normal operation that gap is about three blocks.

There is no third event. If the request is never answered, nothing is emitted, nothing is logged, and the commitment simply stays in storage. The absence is visible only to somebody who thinks to call s_requestCommitments or pendingRequestExists, which is to say, to nobody. Every dashboard I know of renders a pending draw and a dead draw identically.

I pulled every RandomWordsRequested and RandomWordsFulfilled log the coordinator emitted over Base blocks 52,477,065 to 52,779,465, a seven-day window ending 18 October 2026 at 18:31 UTC, in 303 slices of 1,000 blocks with no failed chunks. That is 12,940 requests and 12,935 fulfilments from 57 distinct consumers. Not one fulfilment this week carried success = false, which is a pleasant change from the refusals counted in 13 Base Draws Were Delivered, Charged and Refused.

Five requests had no fulfilment at all. I checked all five against s_requestCommitments at the chain head, alongside two controls: a request I knew had been fulfilled, and an invented request id. Both controls returned 32 zero bytes. All five of the missing ones returned a live commitment hash. They are not lost logs. They are open requests, three days old at the time of writing.

The census

That prompted the obvious question: how many open requests are there in total? The coordinator exposes getActiveSubscriptionIds, so this is answerable rather than estimable. I read the full list and called pendingRequestExists on every entry, with zero read failures.

Population Count
Active subscriptions on the Base VRF 2.5 coordinator 1,174
Holding at least one unanswered request 140 (11.93%)
  of which drew at least once in the last 7 days 9
  of which drew nothing at all in the last 7 days 131
    never had a single request answered, ever 52
    drew before, then stopped 79

Only two of the 140 have an unanswered request from inside my seven-day window. For the other 138, the request predates it. Those 79 subscriptions that drew and then stopped carry 369,359 completed draws between them in their lifetime counters. These were working games.

The nine that are still drawing

Before treating a pending flag as a death certificate, it is worth looking at the nine subscriptions that hold an unanswered request and are still perfectly busy, because the most active of them explains the whole category.

It is 7228922535…, which drew 84 times this week and last drew ninety minutes before I took the measurement. That is Lotto39, the contract I pulled apart in 23 Draws on Base Were Answered Twice, 4 Seconds Apart. Lotto39 has a five-minute internal timeout and re-requests randomness when a delivery is slow, which means it routinely abandons a request that is still live at the coordinator. Its pending flag is not a fault. It is the documented consequence of a design choice, and the abandoned request sits in storage exactly like a dead one.

The other eight trail off quickly: last draws at 4.8, 11.9, 18.5, 18.6 and 24.6 hours before the head, then three at 65.5, 90.5 and 157.2 hours. The last of those drew once, six and a half days ago, and is holding an unanswered request. Calling it “still drawing” is generous. I have left all nine out of the frozen count anyway, because I would rather undercount than argue, but the line between the two groups is softer than a table makes it look.

The part that makes it permanent

A subscription with an idle request is only a curiosity if the owner can shrug and close it. So I tested that, as an eth_call from each subscription’s own owner address, against all 131.

Call Result
cancelSubscription(subId, owner) on all 131 frozen subscriptions 131 reverted, PendingRequestExists()
the same call on 40 subscriptions with no pending request 32 succeeded, 0 reverted for that reason

A rule with no exceptions in 131 cases, and a control group that behaves the opposite way. The eight control subscriptions that did not cancel failed with FailedToSendNative() instead, which is a different problem for a different night: their owners cannot receive the refund.

The selector 0xb42f66e8 is not a guess. I computed keccak256("PendingRequestExists()") locally and took the first four bytes. removeConsumer reverts with the same error, so the owner cannot even detach the consumer contract that made the request.

How long is forever, exactly

“Stuck” is a weak claim without a date on it. pendingRequestExists is a view function, so I sampled it at 30-day intervals backwards through Base’s history for the largest frozen subscriptions, then binary-searched the interval where it flipped from false to true. Every grid sample was consistent with a single transition, with no case of the flag going true, false, and true again.

Subscription Lifetime draws Holding Pending since Days
1074237764… 0 0.039 ETH 1 Nov 2024 716.3
1025652815… 0 33.0 LINK 17 Nov 2024 699.9
5198987271… 100,034 0.0098 ETH 10 Mar 2025 587.1
8721601326… 70,348 0.0928 ETH 8 Feb 2026 251.9
9646514664… 103,116 10.4 LINK 27 Feb 2026 233.2
1244607933… 2,871 18.0 LINK 1 Apr 2026 199.8
9920680052… 9,350 0.0157 ETH 12 May 2026 158.8

The second row is the one I keep rereading. Thirty-three LINK, roughly $390 at tonight’s price, has been sitting in a subscription since 17 November 2024 that has never successfully drawn anything. Somebody funded it properly, pointed two consumer contracts at it, fired one request, and that request has been pending for 700 days. The money is not gone. It is simply behind a door that will not open while the request is open.

Across all 131, the total is 1.287022585 ETH and 183.615415 LINK, which at $2,362.19 and $11.77 is $5,201. The median frozen subscription holds $11.73 and the largest holds $395.50. Sixteen hold under a dollar, which tells you plenty about how many of these were somebody’s afternoon experiment. Fifteen hold over $100, which tells you the rest were not.

Why the oracle declines

The explanation is not exotic, and I have written half of it before. 12,947 Draws, Two Gas Lanes, and One That Was 81x Short worked through Chainlink’s minimum subscription balance: the lane’s maximum gas price, multiplied by the verification gas plus your full callback gas limit, multiplied by the premium. It is a reserve, not a price. The docs are explicit that it is “a higher amount you need to reserve before your request is processed”.

What that post measured was the price of entry. What it did not do was follow a request that failed the test. Here are this week’s five, with the reserve computed from Base’s documented native overhead of 128,500 gas, the on-chain 60% native premium, and the lane maximums I read out of the coordinator’s own s_provingKeys storage rather than off a webpage.

Block Lane Callback gas Reserve required Balance held Short by
52,652,546 2 gwei 200,000 0.00105259 ETH 0.0002 ETH 5.3x
52,658,500 30 gwei 200,000 0.01578888 ETH 0.0002 ETH 78.9x
52,660,229 30 gwei 200,000 0.01578888 ETH 0.0001 ETH 157.9x
52,660,510 2 gwei 200,000 0.00105259 ETH 0.0001 ETH 10.5x
52,660,640 2 gwei 500,000 0.00201538 ETH 0.0001 ETH 20.2x

Every one is short, and the conclusion survives the choice of overhead figure: substituting the coordinator’s own gasAfterPaymentCalculation of 42,500 for the documented 128,500 moves the shortfalls to between 3.9x and 116.6x, all still short.

Now put that next to what a draw actually costs. The median native-paid fulfilment this week cost 0.000003962185 ETH, nine tenths of a US cent. The 0.0002 ETH balance was 50.5 times the median cost of a draw. The 0.0001 ETH balance was 25.2 times. Both subscriptions were funded for weeks of play and refused for being short of a worst case that has never occurred.

That is the shape of the trap, and it is nobody’s bug. The reserve exists because gas spikes are real and a node that fulfils at a loss stops being a node. The revert exists because an owner who could cancel mid-request would be walking off with money a consumer is owed. Both decisions are correct in isolation. Together they mean that under-funding a request by a factor the interface never shows you converts a live subscription into a permanent one.

Six calls that map the whole exit

I ran the complete set against the subscription holding 33 LINK, simulating each call from the address that would need to make it.

Call From Result
cancelSubscription subscription owner revert PendingRequestExists()
removeConsumer subscription owner revert PendingRequestExists()
requestSubscriptionOwnerTransfer subscription owner succeeds
fundSubscriptionWithNative a complete stranger succeeds
ownerCancelSubscription subscription owner revert, “Only callable by owner”
ownerCancelSubscription Chainlink’s multisig succeeds

Read that column downwards. You may give the frozen subscription away. Anybody at all may pay money into it. You may not close it, you may not disconnect it, and the one key that can wind it up belongs to 0xcEF2893e396d530bDecB1F1467e69f7b994ec5ad, the 4-of-9 Safe whose thirteen owner-only functions I went through in Four of Nine Keys Can Reprice Every Draw on Base.

There is a cheaper way out than asking Chainlink: fund the subscription past the reserve, let the node answer the ancient request, and then cancel normally and withdraw what is left. The actual draw costs under a cent. But you have to front the full reserve first, which for the 30 gwei lane is about 158 times what some of these subscriptions hold. Nobody has done it in 716 days.

One contract, one draw, one orphan

The two subscriptions that failed this week share a habit worth naming, because it makes the damage spread. Their consumers are single-use. Each of the three contracts attached to them is 4,073 bytes, has a transaction nonce of exactly 1, and holds a little ether of its own: 0.0002, 0.0002 and 0.0001 ETH, stranded separately from the 0.0003 ETH stuck in the subscriptions. Their code hashes all differ despite the identical byte length, which is the signature of one template deployed repeatedly with different values baked in.

That pattern is common here. Elsewhere in the week’s census, fifteen consumers on one subscription are byte-identical, code hash 2104e9079256f8ae, 5,382 bytes each, and each one made exactly one request before going quiet. Deploying a fresh consumer per draw is not inherently wrong, but it does mean that when the subscription seizes, it takes an expanding fleet down with it. Across the 131 frozen subscriptions there are 500 consumer contracts and 121 distinct owners. Eighty-one of the subscriptions hold more than one consumer, and two hold 34 each, none of which can be detached while the request is open.

A correction to last night’s post

Yesterday’s Only 2 of 59 Base Game Contracts Cannot Swap Their Oracle noted an oddity in the consumer census: one sender, 0x380fbf99a903253e14b9eb797807b4b92cd4b019, was a plain address with no contract code at all, and the only one in the week to use both gas lanes. I filed it as a curiosity.

It was not a curiosity. Those three requests are three of this week’s five that were never answered, and that address owns both of the two subscriptions involved. It holds 0.000012 ETH today. What I described as somebody experimenting across lanes was somebody whose draws never came back, twice, on two subscriptions they can no longer close. I should have checked the fulfilments before calling it interesting.

What this means if you are the player

Everything above is about the operator’s position. The player’s is worse, because it is derived from it.

This blog has spent a lot of nights on whether a draw is honest. We have tested the output of 34,029 words, read the admin surface of the coordinator, counted the 727 subscription owners behind Base’s randomness, and asked whether a game can change its oracle after you enter. All of that assumes the draw resolves. None of it says anything about a round that just stops.

A year ago, Provable Fairness Is Prepaid put the risk in a sentence: “it is not a request that fails, it is a request that is never made, and the player experience is not an error, it is a draw that simply stays pending while the interface keeps rendering happily.” That was a prediction. This is the measurement. There are 131 of them on one coordinator on one chain.

If your stake is locked in a game contract that is waiting on a word that is never coming, your exit depends entirely on what that contract was written to do when the clock runs out. If it has a deadline and a refund path, you get your money. If it does not, you are waiting on somebody else’s treasury decision, and there is no event, no error and no red banner anywhere to tell you which of those two situations you are in.

Where Satoshie stands

Our standing commitments are unchanged: the contract is live before entries open, ticket price and prize structure are fixed in that deployment for the life of the draw, the VRF coordinator is named in public code before anyone stakes, the proof is verified on chain before the payout callback can act, and no admin key can reassign a result.

Tonight’s measurement adds two that are specifically about the draw that does not arrive. First, every round carries a deadline fixed at deployment, and the refund path after that deadline is permissionless. Not an owner button, not a support ticket. If the randomness has not landed by the deadline, any entrant can trigger the return of their own stake, because an exit that depends on the operator choosing to act is not an exit. Second, we publish the subscription’s reserve headroom and not just its balance, because tonight’s five requests prove that the balance is not the number the oracle checks. A healthy-looking balance that sits under the lane reserve is a game that has already stopped and has not noticed.

What I am not claiming

Five things, plainly.

A frozen subscription is not a frozen game. I counted subscriptions, not players. Fifty-two of the 131 never had a single request answered in their lives, which is what an abandoned test deployment looks like, and sixteen hold less than a dollar. The 79 that completed 369,359 draws before stopping are the ones I would worry about, and even there I cannot see from the coordinator whether anybody’s stake is behind them.

Nothing is confiscated. The funds are recoverable by anyone willing to pay the reserve. This is a liquidity requirement and a knowledge problem, not a theft. It is still true that 716 days have passed.

I cannot see the node’s decision rule. The reserve arithmetic matches Chainlink’s published minimum balance and is the only explanation that fits all five of this week’s cases uniformly, including the two whose consumers are ordinary contracts rather than the code-less address. It remains an inference from on-chain state plus the documentation, not a proof.

This is not a Chainlink defect, and I would be irritated at anyone who quoted it as one. Two safety mechanisms interacting is not a vulnerability. If anything the fix is cosmetic: surface the reserve, not the balance, and emit something when a request ages past a threshold.

We are exposed to the same thing. We run a prepaid subscription like everyone else in that census, and the arithmetic that caught five requests this week would catch ours if we stopped watching the headroom. The difference I am willing to defend is the permissionless deadline, which is a property of the game contract and not of the oracle.

Chainlink’s randomness on Base does what it says. It answered 12,935 of 12,940 requests this week, every one of them with a verified proof, for under a cent each. The gap this post is about is not in the proof. It is in what the system says when there is no proof to give, which is nothing at all, to anyone, for 716 days and counting.

Satoshie builds provably fair raffles and coinflip games on Base with Chainlink VRF, and publishes the measurements behind the claims. See how it works.

📷 Photo by Олександр К on Unsplash

Valentina Ní Críonna

Author Valentina Ní Críonna

More posts by Valentina Ní Críonna