Skip to main content

Last night I measured the reserve balance that Chainlink’s VRF coordinator demands before it will answer a randomness request on Base, and I threw eleven subscriptions out of the test. They pay in LINK rather than in ETH, and I wrote that they are “priced by a different formula with a different premium”. That was true, and it was the smallest possible description of the difference.

The LINK formula takes an input the ETH formula does not take. It takes a price.

That is an odd thing to find underneath a provable fairness stack. The randomness is self-certifying: the proof is verified on chain before the callback may run, and nothing outside the chain has to be consulted for it to be correct. But the bill for producing it, settled in LINK, is incurred in one asset and paid in another. Somebody has to convert, and converting means asking an oracle what two tokens are worth relative to each other.

So I read that oracle. All 2,001 of its published rounds. Then I read the number the coordinator falls back on for the case where the oracle goes quiet, and found that it was copied out of the feed on the morning of 5 July 2024 and went out of date twenty-two seconds later.

TL;DR

  • Paying a VRF bill in LINK on Base routes it through a second oracle, the LINK / ETH feed at 0xc5e65227fe3385b88468f9a01600017cdc9f3a12. Paying in ETH does not. I confirmed this on eight real fulfilments: priced with the live feed they land on the coordinator’s 50% LINK premium to within 0.2%, and priced with the fallback they land on 11%.
  • The coordinator tolerates 172,800 seconds, a full 48 hours, of silence from that feed before substituting its own hardcoded price. Across 204 days and 2,001 rounds, the feed’s longest silence was 24 hours and 24 seconds. Exactly half the window, and the backstop has never once fired.
  • The backstop number is 3962147213857640 wei per LINK. It is a byte-for-byte copy of feed round 8972, published at 08:10:51 on 5 July 2024 and written into the coordinator at 08:28:31 the same morning. The next round landed twenty-two seconds after that write and moved the price.
  • 826 days later the copy sits 23.27% below the market. If it ever became your price, the LINK side of your bill would be inflated by 30.33%. The last time it was true was 4 May 2026, 1,810 rounds ago.
  • A correction to my own measurement from two days ago: paying in ETH does not cost 49.5% more per draw than paying in LINK. Like for like it costs 6.70% more, and that 6.70% is the fee you are paid for refusing an oracle.

Two formulas, one extra input

Every parameter below came off the Base coordinator at 0xd5d517abe5cf79b7e95ec98db0f0277788aff634 in a single eth_call to s_config(), decoded in order:

Field Value
minimumRequestConfirmations 0
maxGasLimit 2,500,000
stalenessSeconds 172,800
gasAfterPaymentCalculation 42,500
fulfillmentFlatFeeNativePPM 0
fulfillmentFlatFeeLinkDiscountPPM 0
nativePremiumPercentage 60
linkPremiumPercentage 50

Both flat fees are zero, so the two payment paths differ in exactly one visible way and one invisible one. The visible difference is ten percentage points of premium. The invisible one is that the LINK path must turn a cost measured in wei into an amount measured in LINK, at whatever rate the feed is quoting when the callback runs.

I did not take that on trust. I pulled twelve ETH-paying fulfilments spread across a week and divided the charge by the transaction’s true all-in cost, gas plus the OP Stack L1 data fee. Every one came back between 1.6019 and 1.6044, median 1.6022. That is the 60% premium, visible in the receipts.

Then I did the same for eight LINK-paying fulfilments, converting the LINK charge into ETH at the feed’s reading for that block. They came back between 1.5010 and 1.5027, median 1.5016. That is the 50% premium, and it is a stronger result than it looks, because the only way those numbers land on 1.50 is if the feed price is the one the coordinator used. Substitute the hardcoded fallback instead and the same eight draws price at 1.1114 to 1.1127, which is not a premium any config file on Base contains. The feed is in the loop, and the receipts prove it without my having to read a line of Solidity.

What the feed actually does

The proxy at 0xc5e65227fe3385b88468f9a01600017cdc9f3a12 describes itself as LINK / ETH, reports 18 decimals and currently points at aggregator 0x66c3528a23cefa7d01af284cd240f73c8a41b9fa, its second. I walked every round of that second phase with getRoundData, one call per round, 2,001 of them, from 19 March 2026 to the reading that was live as I wrote this.

Measurement Phase 2, 2,001 rounds
Window 19 Mar 2026 14:45 to 9 Oct 2026 18:08 UTC
Span 204.14 days
Publication rate 9.80 rounds per day
Shortest gap 18 seconds
Median gap 65.5 minutes
90th percentile gap 6.49 hours
Longest gap 24 hours 24 seconds
Price range 0.00385638 to 0.00584854 ETH

The shape of a Chainlink feed falls straight out of that table without anybody having to publish a specification. Five of the 2,000 intervals sit within a rounding error of exactly 24 hours, which is a heartbeat: update at least this often whether or not anything happened. And the median move between consecutive rounds is 0.5203%, with 1,982 of 2,000 moves at or above 0.5% and only eighteen below it. That is a deviation threshold: publish when the price has drifted half a percent. Two rules, visible in the data, inferred from nothing but the rounds themselves.

It is also accurate: the live reading of 0.00516404 ETH per LINK sits 0.031% from the ratio implied by Coinbase’s spot prices at the same moment. Nothing here is a complaint about the feed. The feed is doing its job well.

The 48-hour window

The coordinator’s tolerance for that feed falling silent is stalenessSeconds, and on Base it is 172,800. Two full days. If the reading the coordinator fetches carries a timestamp older than that, the published VRF source substitutes s_fallbackWeiPerUnitLink and prices your draw with it instead.

In 204 days the feed has never come close. Its worst silence, 24 hours and 24 seconds, is 50.01% of the window. Which is a comfortable margin and also an exact one: the gate opens if the feed misses one heartbeat and then misses the next. Not a cascade, not a chain halt, not a governance failure. One heartbeat, then another.

So the interesting question is not whether the fallback will fire. It is what happens if it does, and that is a question about a single integer nobody has looked at.

The number behind the glass

Here it is, straight from the contract:

s_fallbackWeiPerUnitLink = 3962147213857640

That is 0.00396214721385764 ETH per LINK. Against today’s market it values LINK at $9.85 when LINK is $12.84. It is 23.27% low.

I wanted to know when somebody last thought about it, so I binary-searched the coordinator’s history, calling s_fallbackWeiPerUnitLink at successively narrower block heights until the value changed. It changed exactly once. At block 16,689,381 it was zero. At block 16,689,382, timestamp 5 July 2024 at 08:28:31 UTC, it was 3962147213857640, and it has been that in every block since. 826.4 days, and not one revision.

Then I went looking for where the number came from, which is the part I did not expect to be able to answer. The feed’s first aggregator covers that date, so I binary-searched its rounds by timestamp to the morning in question:

Round Published (UTC) Wei per LINK
8971 5 Jul 2024 06:59:19 3984000000000000
8972 5 Jul 2024 08:10:51 3962147213857640
8973 5 Jul 2024 08:28:53 3941500000000000

Round 8972 is the fallback. Not approximately, not rounded to a sensible figure a human would type. The same seventeen digits, down to the final wei. Whoever configured this coordinator read the feed at 08:10:51, pasted the number it returned, and sent the transaction eighteen minutes later. That is a careful thing to do and I would have done the same.

Round 8973 landed twenty-two seconds after the transaction confirmed, and the copy has been wrong ever since.

Not wrong by much at first. The market stayed near it for the best part of two years. But 1,968 of the 2,001 rounds I pulled are above it, and the last round at or below it was number 191 on 4 May 2026, 158 days and 1,810 publications ago. An emergency price that was correct for twenty-two seconds has spent 826 days drifting away from the thing it is meant to approximate, in a storage slot that nothing reads and nothing alerts on.

What it would cost

Understating LINK’s value means understating how far one LINK goes, which means charging more of them to cover the same wei. The inflation factor is today’s price divided by the fallback: 1.3033. If the window opened tomorrow, every LINK-settled draw on Base would be billed 30.33% more LINK than the conversion it is meant to represent.

And now the honest part. Across the week I measured, blocks 52,001,892 to 52,304,292, there were 12,947 requests and 12,946 fulfilments, of which 12,785 settled in ETH and 161 settled in LINK. Eleven subscriptions, 1.24% of the draws. Their entire LINK bill for the week was 0.076750 LINK, about 99 cents. The fallback firing for all seven days would have cost those eleven accounts an extra 0.023282 LINK between them. Thirty cents.

The money is nothing. The money is not the finding.

The finding is that the error is systematic and has a direction. It does not average out, because it is not noise. It is a single frozen observation being asked to stand in for a price that has moved 51.7% inside my measurement window alone, and today it happens to point against the payer. It has not always. Thirty-three of the 2,001 rounds were at or below the fallback, and in that regime the same stale integer overstates what a LINK is worth and the node operator is the one short-changed. That is arguably the worse failure, because an underpaid fulfilment is one a rational operator eventually declines, and a declined fulfilment is a draw that never resolves. A backstop with the wrong sign does not cost you money. It costs you an answer.

A correction to my own number

Two days ago I published a finding that paying in native ETH costs 49.5% more per draw than paying in LINK. That compared the median ETH-settled draw with the median LINK-settled draw, and it is mostly an artefact: the two populations request different callback gas limits, so I was attributing a workload difference to the premium. Held constant against the same transaction cost, the real gap is the one in the config file, 6.70% in my receipts and 6.67% in the arithmetic. Still a real discount, and a far less exciting number than the one I ran with.

It also reframes the discount as something other than a quirk. You are not being rewarded for holding LINK. You are being paid 6.70% to accept a 48-hour staleness tolerance and an 826-day-old substitute price that you did not set, cannot change and almost certainly have not read.

What this means for a game, and what we do about it

Satoshie funds its subscription in native ETH, which means the price of one of our draws is a gas number multiplied by a premium, and there is no second oracle anywhere in the bill. I want to be precise about why that matters and why it does not.

It does not matter for fairness. A stale conversion rate cannot change who wins. It cannot reach the VRF output, it cannot reach the proof, and it cannot reach the payout. Anybody telling you a price feed compromises a verifiable draw is selling you something. This is the billing side, and it has always been the billing side.

Where it matters is the thing I wrote about on Tuesday: a provably fair draw is prepaid, and the account prepaying it has a runway measured in days. A 30.33% rise in the burn rate that arrives without a transaction, without an event and without any on-chain signature turns a comfortable balance into a stalled game three weeks early. Same shape as the reserve requirement I found yesterday inside a key hash: a number that constrains nobody on an ordinary day and is entirely your problem on the day it does.

So the commitments are boring and checkable, which is the only kind worth making. We pay native and we say so. We publish the subscription balance and the implied runway, because both are free public reads and their absence everywhere else is the tell. And if we ever move to LINK, we will publish the fallback price, the date it was set and how far it is from the market that day, because those are three eth_calls and the only reason nobody prints them is that nobody asks.

Honest limits

Five, and the first is the big one. The fallback has never fired. Not once in the 204 days I measured, and I have no evidence it ever has. This is a loaded condition, not an observed loss, and anyone reading it as a live exploit is reading it wrong.

Second, the staleness branch itself comes from Chainlink’s published VRF 2.5 source, not from the deployed bytecode. What I verified first-hand is the config values, the fallback integer, its provenance and the fact that the live feed priced eight real LINK fulfilments. That the substitution triggers at exactly 172,800 seconds is the documented behaviour of the code this address claims to be, which is a weaker claim than the rest of the post and I am flagging it as one.

Third, I walked the feed’s second phase in full and only sampled its first, sixty rounds out of 23,737. My gap statistics therefore describe March to October 2026, and I cannot tell you the worst silence of 2024 or 2025.

Fourth, the LINK-side verification rests on eight fulfilments priced with the last round published at or before the containing block, not the exact value the coordinator read. The 1.501 to 1.503 band is consistent with roughly 800 gas of accounting I did not isolate.

Fifth, 161 draws is a small sample and one subscription made 93 of them. The 1.24% LINK share is a fact about one week on one chain, not a stable property of anything.

Three questions worth asking any on-chain game

  • Which currency does your subscription pay in, and what is the premium on that side? One getSubscription call and one s_config call. If the answer is LINK, your randomness bill has an oracle in it and the next two questions apply.
  • What is s_fallbackWeiPerUnitLink on your chain today, and how far is it from the market? Two reads and a division. On Base the answer is 23.27% low and 826 days old.
  • How long may the feed be silent before that number becomes your price? It is stalenessSeconds, it is 48 hours here, and the feed’s worst silence in 204 days was 24 hours and 24 seconds. Half the window. Whether that margin reassures you is the actual question.

The fire extinguisher in the photograph is the right mental model, and not as an insult. Somebody mounted it deliberately, at the correct height, with the right sign above it. It has probably never been discharged, its value lies entirely in the state it will be in on the day it is needed, and nobody walks over to read the tag because it looks fine from across the room. The difference is that an extinguisher has its inspection date printed on the front. This one has 3962147213857640, and the date is in a July 2024 block you have to go and find.

Satoshie runs provably fair raffles and coinflips on Base, settled by Chainlink VRF, escrowed and paid in the same transaction that resolves them. We publish the numbers that constrain us, including the ones that make us look ordinary.

All figures first hand. Coordinator 0xd5d517abe5cf79b7e95ec98db0f0277788aff634 and feed 0xc5e65227fe3385b88468f9a01600017cdc9f3a12 on Base mainnet, read 9 October 2026. Fulfilment window blocks 52,001,892 to 52,304,292 (30 September to 7 October 2026). Feed phase 2 rounds 1 to 2001 pulled individually via getRoundData. Fallback provenance located by binary search over block heights from 10,000,000 to 32,390,708. ETH at $2,485.635 and LINK at $12.832 from the Coinbase spot API, 9 October 2026.

📷 Photo by Tak Kei Wong on Unsplash

Valentina Ní Críonna

Author Valentina Ní Críonna

More posts by Valentina Ní Críonna