Chainlink’s VRF coordinator on Base keeps a list of its live subscriptions, and you can read the whole thing in one call. At block 52,909,050 on 21 October 2026 it held 1,175 entries. That number gets quoted as though it means something about adoption. It does not. I pulled every one of those subscriptions, then pulled the coordinator’s entire funding history going back to deployment, and the result is blunt: 582 of the 1,175 hold no money at all, and not one of them has ever held any. Not drained. Never started.
TL;DR
- 582 of 1,175 active Base VRF subscriptions hold a zero balance in both currencies, and zero of the 582 have ever received a funding event across the coordinator’s full history (2,085 events, 726 distinct subscriptions). They were abandoned at creation, not spent down.
- The funding decision is made in minutes. Median time from creation to first top-up is 3.5 minutes; 601 of 726 were funded within the hour and 682 within a day. Only 7 were ever funded more than 30 days later. The median empty subscription is 335 days old.
- 159 subscriptions cannot be closed by anyone, including Chainlink.
cancelSubscriptionmakes an unconditional zero-value native transfer to the owner. If the owner is a contract that rejects a bare call, it revertsFailedToSendNative()forever, andownerCancelSubscriptionroutes through the same helper. - That welds $578.94 of real deposits shut in the 24 stuck subscriptions that do hold a balance, and keeps 135 permanently empty rows in the active list that nobody can ever remove.
- One address owns 70 of the 1,175, with one identical minimal-proxy clone wired to each, 68 of them empty, and a grand total of one draw across all seventy.
- The coordinator does not check your balance when you request randomness. Three never-funded subscriptions are carrying a live request that nobody will ever answer, the oldest for 756 days.

What the list actually is
Start with the honest part. The owner census I published five days ago already said the population was mostly asleep: 827 of the then-1,174 subscriptions had never requested randomness, and 483 had no consumer contract attached. Tonight those figures are 824 and 483. I am not going to re-sell them as news.
What that post did not do is ask why. “Asleep” implies a thing that was once awake. A subscription with nothing in it could be a game that ran out of money, a project that wound down and withdrew, or a wallet that opened an account and walked away. Those are three completely different stories about an ecosystem, and the coordinator’s own logs can tell you which one is true.
So here is the balance side of the same 1,175, read with getSubscription at a single pinned block.
| Cohort | Subscriptions | Share |
|---|---|---|
| Holds nothing in either currency | 582 | 49.5% |
| Holds LINK only | 180 | 15.3% |
| Holds native ETH only | 345 | 29.4% |
| Holds both | 68 | 5.8% |
Those balances add to 1,479.151882 LINK and 10.304022 ETH, which at the coordinator’s own feeds at that block is $44,797.44. I did not take that total on trust. The coordinator’s unwithdrawn fee pot lives packed into storage slot 11, and the two numbers have to close: 1,479.151882 plus 834.922697 of fees equals s_totalBalance at 2,314.074579 exactly, and 10.304022 plus 7.518091 equals s_totalNativeBalance at 17.822113 exactly. Residual zero on both currencies, which is the only reason I am willing to publish a per-subscription breakdown at all.
Not drained. Never started.
Money enters a VRF subscription two ways, and both of them emit an event. SubscriptionFunded for LINK, SubscriptionFundedWithNative for ETH. I pulled every one of those from the coordinator’s deployment to the pinned block: 599 LINK events and 1,486 native events, 2,085 in total, covering 726 distinct subscriptions and 2,827.08 LINK plus 24.77 ETH of deposits.
Of the 582 empty subscriptions, the number that appear anywhere in that history is zero. Not a small number. Zero.
The control runs the other way too: of the 593 subscriptions that do hold a balance, the number with no funding event is also zero. So the dataset is not missing a funding path, and the 582 did not quietly receive money through some side door. They were created, and then nothing happened. The second confirmation is sitting in the same struct: all 582 have a lifetime fulfilment count of zero, so nothing was ever charged against them either. A subscription cannot lose money any other way.
That reframes the whole population. “Mostly asleep” was too kind. Half of Base’s active randomness accounts have never been switched on.
The window is minutes wide
The obvious objection is that some of these are simply waiting. Somebody is mid-build, the contract is deployed, the top-up comes on launch day. It is a fair objection and it is answerable, because every funded subscription has a creation timestamp and a first-funding timestamp, and the gap between them is a distribution.
| Time from creation to first top-up | Subscriptions (of 726) |
|---|---|
| Within 1 minute | 186 |
| Within 1 hour | 601 |
| Within 24 hours | 682 |
| Within 7 days | 702 |
| More than 30 days later | 7 |
Median: 3.5 minutes. Upper quartile: 26 minutes. The longest wait in the coordinator’s entire history was 157 days, and exactly seven subscriptions took longer than a month. Funding a subscription is not a later step in a project plan. It is part of the same sitting as creating it, for 94% of everyone who has ever done it.
Now put the empty cohort against that clock. The median empty subscription is 335 days old. Two hundred and forty-nine of them are over a year old, and the oldest was created on 23 July 2024, 820 days ago. Against a funding window with a median of three and a half minutes, these are not pending. They are dead, and they have been dead since roughly four minutes after they were born.
The full funnel, including the ones that are gone
1,360 subscriptions have ever been created on this coordinator. 185 have been cancelled, and 1,360 minus 185 is 1,175, which is a pleasing way to confirm that both log pulls are complete. Sorting all 1,360 by what their owners actually did gives the shape of the thing.
| What happened after creation | Subscriptions | Share |
|---|---|---|
| Nothing at all: never funded, never wired to a contract | 476 | 35.0% |
| A consumer contract was registered, but never funded | 158 | 11.6% |
| Funded, but no consumer contract ever registered | 68 | 5.0% |
| Both | 658 | 48.4% |
The 158 in the second row are the interesting ones, because somebody did real work there. Those subscriptions carry 174 distinct consumer addresses between them, and when I ran eth_getCode against every one, 170 came back with deployed bytecode. Somebody wrote a contract, deployed it to Base, registered it against a subscription, and never put a penny in. The median of those contracts is 4,357 bytes, so these are not stubs.
Four of the 174 have no code at all. Registering an address with no code as a VRF consumer is a configuration that can never work: the callback has nowhere to land. Two others are 23-byte EIP-7702 delegation designators, which is to say the owner registered their own smart account as the consumer of their own subscription. Nobody is going to find out, because none of these were ever funded.
The third row has its own quiet failure mode. Those owners paid before they built, and some never built: 59 subscriptions on the live list hold a balance today with no consumer contract registered at all, which is $1,010.65 sitting in an account that is structurally incapable of spending it. The ordering across the 658 who did both says this is the normal habit rather than an aberration. 466 of them funded before they registered a contract and only 179 did it the other way round, so the money usually goes in first and the build decides afterwards whether it was worth it.
The clause that welds them shut
An abandoned account is tidy-uppable in most systems. You close it, you get your deposit back, the row disappears. So I tested that: cancelSubscription(subId, owner), simulated as an eth_call from each subscription’s own owner address, against all 1,175.
| Result | Subscriptions |
|---|---|
| Closes cleanly | 876 |
Reverts PendingRequestExists() |
140 |
Reverts FailedToSendNative() |
159 |
The middle row is a story I already told on 18 October, when the count was 131. That post sampled 40 subscriptions as a control group, found eight failing on something else, and wrote the sentence I have been waiting to cash in: “a different problem for a different night: their owners cannot receive the refund.” This is that night, and the eight were 159.
Here is the mechanism, from the coordinator’s own verified source in SubscriptionAPI.sol:
// Only withdraw LINK if the token is active and there is a balance.
if (address(LINK) != address(0) && balance != 0) { ... }
// send native to the "to" address using call
_mustSendNative(to, uint256(nativeBalance));
Read those two statements next to each other. The LINK transfer is guarded by balance != 0, and the comment above it says so in English. The native send immediately underneath has no guard at all. _mustSendNative does to.call{value: amount}("") and reverts FailedToSendNative() if that returns false. When the balance is zero it still fires: a zero-value call with empty calldata at the owner address. An externally owned account accepts that silently. A contract without a payable receive or fallback rejects it, and the whole cancellation reverts.
Which is exactly what the data says. All 68 distinct owners of those 159 subscriptions are contracts. Zero are plain keys. The asymmetry is one missing conditional, and it is the difference between a closeable account and a permanent one.
Not even the multisig
The coordinator has an escape hatch. ownerCancelSubscription is callable by the contract’s owner, which on Base is a four-of-nine Gnosis Safe at 0xcEf2893e396d530bdecb1f1467e69f7b994ec5ad, and I have written before about how that admin can do things a user cannot, including closing a subscription with a request in flight.
It does not help here. Simulated from the Safe’s own address against six of the stuck subscriptions, ownerCancelSubscription reverted FailedToSendNative() six times out of six, because it calls the same _cancelSubscriptionHelper with the same unguarded send. The controls behave: the same call from the Safe against three closeable subscriptions returns success, and from a junk address it reverts “Only callable by owner”. So the result is not an artefact of how I framed the call.
159 subscriptions on Base’s VRF coordinator cannot be closed by their owner, cannot be closed by Chainlink, and cannot be closed by anyone else. 135 of them are empty and will sit in getActiveSubscriptionIds until the contract is replaced. The other 24 are holding 23.9078 LINK and 0.108899 ETH, which is $578.94 that has nowhere to go. Thirteen of the 159 are live games with 9,563 draws between them, so this is not purely a graveyard problem. Those contracts can keep spending their deposit on randomness. They just can never have it back.
One address is eleven per cent of the problem
Before anyone generalises from 582, here is the concentration. The address 0x3e940248975374da309376290f87ac699488e23b owns 70 of the 1,175 subscriptions. Each one has exactly one consumer, and 68 of those consumers are byte-identical ERC-1167 minimal proxies pointing at the same implementation. 68 of the 70 are empty. Across all seventy subscriptions, spread over sixteen months of creations from October 2024 to February 2026, the total lifetime draw count is one.
That single actor is 11.7% of the empty population and 43% of the un-closeable one, since its contract owner cannot receive a bare call either. Strip it out and 514 of 1,107 subscriptions are empty, which is 46.4% instead of 49.5%. The headline survives the adjustment, but anyone quoting 582 without that sentence is quoting a factory.
The request nobody will ever answer
Three of the 582 are stranger than the rest. They have never been funded, they have never had a draw fulfilled, and pendingRequestExists returns true for all three. Someone asked a subscription with no money for a random number.
The coordinator let them. requestRandomWords validates the subscription, the consumer, the confirmations, the gas limit and the word count, and it never once looks at the balance. I confirmed that against the verified source and then live: simulating a request from a never-funded subscription’s own consumer returns a request ID and does not revert, while the same call from a non-consumer reverts InvalidConsumer. The gate is entirely off-chain. The oracle simply declines to answer a request it cannot be paid for, and the commitment stays in storage forever.
That is a trap with teeth, because a pending request blocks cancellation. Two of the three were created on 26 September 2024 and one on 30 December 2024, so they have been frozen for 756 and 660 days respectively on an account that has never held a penny. As a cross-check on the whole exercise, the pendingRequestExists flag reads true for exactly 140 of the 1,175, which matches the 140 PendingRequestExists() cancellation reverts precisely. Two independent methods, same number.
Why any of this matters to a player
Satoshie runs raffles and coinflips whose outcomes come from this coordinator, and the pitch is that you do not have to trust us, because the draw is verifiable. The subscription layer is the unglamorous half of that promise. A game’s randomness is only as live as the account paying for it, and tonight’s numbers say the account layer is full of things that look alive and are not.
Three practical consequences. First, a subscription will accept a request it cannot pay for, so “the contract called for randomness” is not evidence that randomness is coming. Second, a balance is not a commitment, because 242 subscriptions hold $4,464.60 between them and have never drawn once. Third, and this is the one that should bother operators: if your subscription’s owner is a contract that cannot accept a bare call, your deposit is not refundable, and you will not discover that until the day you try to leave.
The fix on our side is dull and worth stating anyway. Own the subscription with an address that can receive ETH, check the balance before requesting rather than after, and publish the subscription ID so anyone can run the calls in this post against it. All of them are free and none of them need our permission.
What this does not show
- It is a single-block snapshot of balances. Nothing here measures burn rate or runway, and a subscription funded an hour after my pin reads as empty.
- Subscriptions that have been cancelled are deleted from storage, so the 1,360-row funnel can describe what their owners did but not what they held.
- The $578.94 and $44,797.44 figures use the coordinator’s LINK/ETH feed and a Base ETH/USD feed at the pinned block. Deposits were made at other prices, so the 2,827.08 LINK and 24.77 ETH of lifetime funding should not be converted at today’s rate and called a total spend.
- “Cannot be closed” is a statement about the current code and the current owner. If an owner contract is upgradeable and gains a payable fallback, its subscriptions become closeable, and I have not checked proxy patterns across all 68.
- I tested the admin path on six of the 159, not all of them. The mechanism is shared code, so I expect the rest to behave identically, but I have not run them.
- The three never-funded subscriptions with pending requests are identified by flag and by the absence of any funding event, not by the request log itself, which I could not retrieve through the indexer in this session.
Three questions I would like answered
- Why is the native send unguarded when the LINK transfer three lines above it is not? If it is deliberate, what does a zero-value call to the recipient accomplish that a balance check would prevent?
- Should
requestRandomWordsreject a request from a subscription that cannot possibly pay for it, given that accepting one permanently freezes the account? - If half of every monthly cohort of subscriptions is abandoned within minutes of creation, is that a developer-experience problem worth measuring, or is creating a subscription just so cheap that it is the new equivalent of an idle browser tab?
Run it yourself
Everything above is three calls and a log pull. getActiveSubscriptionIds(0, 0) on 0xd5d517abe5cf79b7e95ec98db0f0277788aff634 returns the whole list, and calling it with the start index equal to the count reverts 0x1390f2a1, which proves you have all of it. getSubscription(id) gives balances, lifetime fulfilments, owner and consumers. The two funding events and SubscriptionCreated cover the history, and an indexer that takes a topic filter with no block-range cap will serve them in a handful of requests. Then simulate cancelSubscription from each owner as an eth_call and sort on the revert selector.
The one thing I would insist on is the reconciliation. Sum the subscription balances, add storage slot 11, and check it equals s_totalBalance and s_totalNativeBalance with nothing left over. If it does not close, your list is incomplete and every percentage you derive from it is fiction. Mine closed at zero, which is the only reason I was willing to write 582 down.
📷 Photo by MountainAsh on Unsplash


