Skip to main content

On 1 October the Ethereum Foundation shipped something genuinely good. zkAPI, built with the Open Anonymity Project and live on Ethereum mainnet, lets you pay for a metered API without the provider ever learning who you are. You deposit ETH into a vault contract once. Your balance becomes a private note. When you want to query a model, software on your machine builds a Groth16 proof that a funded note covers the spend and has not already been spent, the server mints a short-lived capped key without learning which note paid, and your prompts go to the provider carrying nothing else. The provider sees the question. The payment layer sees the money. Neither sees both.

The coverage was uniformly positive and it was right to be. Prompts are the most revealing text most people will ever type, and the current arrangement hands a running transcript of your thinking to whoever holds your card details.

So we went and read the contract instead of the announcement. The vault is at 0x4386fdbda35d995beb3bf8625118ec5982ec81fe. It has a function nobody wrote a headline about.

Your deposit expires after 30 days. When it does, anyone may call claimExpired, and the contract sends the entire deposit, not the unspent remainder, to the operator’s treasury. The protocol’s own documentation calls this handling abandoned accounts. It is a perfectly reasonable rule. It is also the one rule in the system that nobody can warn you about, because the whole point of the system is that it does not know who you are.

TL;DR

  • The Ethereum Foundation and the Open Anonymity Project launched zkAPI on Ethereum mainnet on 1 October 2026: anonymous prepaid credits for AI inference, using Groth16 proofs on BN254 and a 32-level Merkle tree of note commitments.
  • Every deposit carries a 30-day expiry, rounded up to the next UTC midnight. After it passes, claimExpired transfers the full original deposit to the operator’s treasury, not just what you failed to spend.
  • We read it off the chain: 59 notes created since launch, about 0.4620 ETH (roughly $1,238) sitting in the vault, typical deposits of 0.00186 ETH (about $5), and every note deposited on 2 October dying at midnight UTC on 2 November.
  • The expiry is documented in the protocol specification and visible in the deployed code. It appears nowhere in the launch announcement, which uses the word “expires” only about the short-lived API key.
  • The structural point: a deadline needs an addressee. Verifiability is a pull and notification is a push, and unlinkability deletes the push. This is not negligence, it is arithmetic.
  • For on-chain gaming the rule falls out cleanly: never attach a privacy feature to a balance that has a deadline, because the privacy is what removes your ability to be reminded.

What the contract actually says

The vault is 13KB of Solidity and the relevant parts are short. deposit takes your commitment and your ETH, computes rawExpiry = block.timestamp + noteTtl, rounds that up to the next multiple of EXPIRY_BUCKET (one day), and writes the result into the note leaf alongside the amount. The expiry is therefore not a server-side policy. It is hashed into the Merkle tree that the proofs are checked against, which means it is as immutable as the balance itself.

Then there is this, in full:

function claimExpired(uint32 noteId, uint256[32] calldata siblings) external nonReentrant

It requires only that the note is still active and that the current block timestamp has passed the expiry. It then closes the note and transfers note.depositAmount to treasury. Two details matter. The function has no access control at all, so anybody can pull the trigger, though only one address collects. And depositAmount is the gross figure recorded at deposit, which the contract never decrements, because spending happens off-chain against a server-signed private balance. The chain does not know you spent 15% of your credit and have 85% left. It knows what you put in. At expiry, that is what leaves.

The protocol model says it plainly: “To handle abandoned accounts, each note carries the expiry time. If the note is still open and no withdrawal is in progress when it expires, the server may call claimExpired. The contract then closes the note and transfers the full deposit to the server treasury.”

We then measured the live deployment rather than taking the documentation’s word for it. Reading storage directly, the vault has issued 59 notes since launch and holds 0.461950661 ETH, about $1,238 at $2,679.71 to the ether. Decoding every deposit event we could reach, the typical note is 0.00186 ETH, almost exactly $5, with the gwei figure drifting upward hour by hour across the day in step with a falling ether price, which is what a client quoting a fixed dollar amount against an oracle looks like from the outside. The largest we saw was 0.025 ETH, about $67.

Every note funded on 2 October carries the same expiry: 00:00 UTC on 2 November. Every note funded on 3 October dies at 00:00 UTC on 3 November. Thirty days, bucketed to a day boundary, uniform across every depositor. The whole cohort goes over the edge together.

Four notes have closed properly so far. One of them, note 53, deposited exactly 0.01 ETH at 21:54 UTC and withdrew exactly 0.01 ETH shortly after, having spent nothing at all. Somebody tested the exit and then left. That is the correct instinct, and it is also the only instinct that protects you here.

Verifiable is not the same as notifiable

Here is the distinction the launch coverage had no column for.

Everything about your note is verifiable. The expiry is in the leaf, the leaf is in the tree, the tree root is on-chain, and the exact second your money becomes claimable is a public integer that anybody can read today. There is no hidden term, no policy page that can be edited, no small print. By the standard this blog usually applies, it is close to exemplary: the deadline lives in a contract you can read rather than in a document the operator controls.

And it will still take money off people, because verification is a pull and a deadline is a push.

Verification requires you to show up and check. It answers a question you asked. A deadline is the opposite shape: it needs to reach a person who is not currently thinking about it, on a date they did not choose, through a channel that knows where to find them. Every prepaid system in the ordinary economy understands this. Gift cards carry statutory expiry disclosure. Phone credit sends you a text before it lapses. Dormant bank accounts generate letters. Those mechanisms all require exactly one thing, which is an issuer who knows who the customer is.

zkAPI deliberately, carefully and successfully destroys that. The payment layer cannot link your note to your deposit, your sessions to each other, or any of it to you. That is the product. It is also why there is nobody in the system holding both the knowledge of who you are and the obligation to tell you your credit is about to die. The notice is not missing. It is unsendable.

Why it cannot simply be patched

The obvious reaction is that this is a gap somebody should close. Send an email. Put a banner in the client. Let the server ping the depositor.

Each of those either does nothing or breaks the guarantee. An email needs an address, and collecting one rebuilds the billing identity the protocol exists to remove. A client-side banner only helps people who open the client, which is precisely the population that does not need the warning, and it is useless to the person who topped up $5, tried it for an evening, and closed the tab. A server-side ping is impossible by construction: the server does not know which note belongs to which user, which is a soundness property, not an oversight.

The protocol’s own model concedes the shape of this. “Because honest off-chain requests are unlinkable, the contract cannot settle funds on a per-request basis. Instead, it pays the server in aggregate when the note closes.” Unlinkability forces aggregate settlement, aggregate settlement requires a closing event, and a closing event that the user never triggers has to be triggered by somebody. The expiry is not bolted on. It is the load-bearing consequence of the privacy property, and the designers knew it.

There is a second compounding wrinkle. Your remaining balance is not something you can prove on your own. The spec is explicit that the server “subtracts the bounded usage charge and adds a fresh blinding delta before signing the next state”, so your current balance is a jointly authored object, valid only with a server signature. There is an escape hatch for when the server stops co-operating, and it is well built: you initiate an escape withdrawal, a challenge period runs (24 hours by default), and if nobody contests it with an archived request proof you finalise and take your money. But the escape path is also a deadline, it also depends on somebody being awake, and the operator’s side of it is a daemon with a funded signer whose own documentation warns you to watch for the log line “MISSED escape challenge deadline”. Every route out of this contract is a clock, and clocks are exactly the things an anonymous user cannot be reminded about.

What makes this different from every other expiring balance

We have written about deadlines before. When CoinEx wound down fully solvent, the argument was that the good version of a shutdown still transfers a loss, and the loss lands on the people least able to notice it. That was a story about imperfect notification: an announcement went out, a date was named, and some fraction of users were always going to miss it.

This is a different category, and it is the first time we have seen it. The notification is not imperfect. It is impossible. Not because the operator is cheap or careless, but because the operator bought, built and shipped a cryptographic guarantee that it cannot identify its own customers, and the users praised it for doing so. The inability to find you is the feature. The forfeiture is the invoice for that feature, and it is denominated in the balances of people who stopped paying attention.

That is worth saying without a sneer, because the trade might well be worth it. Five dollars is a cheap price for not handing a model provider a permanent transcript of your medical questions. But it should be chosen with both halves visible, and right now only one half is in the announcement. The word “expires” appears twice in the Ethereum Foundation’s post, both times about the short-lived API key, never about your deposit. The 30-day fuse is in the specification and in the deployed code, which is public, and that is genuine disclosure. It is just not disclosure delivered to the people whose money it governs. The repository also states that this is an experimental single-party setup with no independent audit, which the Foundation deserves credit for saying out loud.

What this means for on-chain gaming

Crypto gaming is about to be handed privacy as a base-layer primitive, and it will reach for it. The rule that falls out of this launch is sharper than the usual advice, and it is a constraint rather than a boast.

A privacy feature must never be attached to a balance that has a deadline.

Not “should be disclosed carefully”. Must not. The moment a platform makes a player unlinkable and also leaves value sitting somewhere waiting for that player to act, it has built a forfeiture mechanism with no possible reminder, and the forfeiture will land hardest on exactly the casual players the privacy was supposed to protect. Anonymity and dormancy are individually defensible and jointly predatory, and the second property is usually invisible until you read the function that sweeps the balance.

Satoshie’s position here is narrow and it is not a victory lap. We run public draws on Base: entries, VRF requests, the random word and the payout are all legible to anyone forever, and a shielded entry pool is not something we have built. Our structural answer to deadlines is the one we have argued before, which is that escrow, resolution and payout happen in a single transaction so there is no claim step for a deadline to attach to. That answer is about delivery, and it is deliberately boring.

What this launch adds is the forward commitment. If we ever shield player identities, that constraint comes with it: no expiring balances, no dormancy sweep, no prize parked behind a claim, and no state in which an unlinkable player’s money is waiting on them to notice something. Because on that day we would lose the ability to tell them, and a platform that cannot tell you should not be holding a clock over your money.

The broader point for anyone evaluating a provably fair claim: a proof settles the question it was built to settle and nothing else. A VRF proof settles randomness. A Groth16 solvency proof settles that you had the funds. Neither of them gets your money back, extends a date, or taps you on the shoulder. Fairness in the narrow cryptographic sense is fully compatible with you losing your balance to a calendar.

The honest limits

Three, stated against our own argument.

The stakes are currently small and the window is not stingy. Thirty days on a $5 balance is not a scandal, and a month is longer than most prepaid credit in the ordinary economy gets. The entire vault holds about $1,238. We are describing a mechanism at the point where it is cheap to fix, not a loss event.

A diligent user is fine. The expiry is public, deterministic and sitting in the leaf, and a client could reasonably surface it. Nothing is hidden from anyone who looks. That is also the complaint: a protection that requires the user to look is not a protection for the people who forget, and those are the only people the deadline ever reaches.

We are not solving the hard problem here. Satoshie trades privacy away wholesale, and that is a real cost our players pay every time they enter a draw from an address that can be followed. zkAPI is attacking a problem we have not attempted, with better cryptography than we deploy, and if it works it should be copied. Reading its expiry function is not a reason to use it less. It is a reason to read ours.

Three questions

  1. Does my balance expire, what is the number, and is it in a contract I can read or on a page the operator can edit? If the answer is a dollar figure on a dashboard with no date next to it, the date exists anyway and somebody else knows it.
  2. At expiry, does the sweep take the unspent remainder or the original deposit? Those are very different numbers, and on this vault it is the second one.
  3. If the system cannot identify me, which party is supposed to tell me the deadline is coming, and what do they gain when nobody does?

The third question is the one the series exists for. There is no villain in this story. The Ethereum Foundation built a privacy system that works, documented the expiry in the specification, published the contract, and labelled the whole thing experimental. Every individual decision is defensible.

It is just that when the maths finished, the only party with the standing to warn you had been cryptographically removed from the room, and the money that goes unclaimed goes somewhere specific. A deadline nobody can be told about is not a deadline. It is a schedule.

Satoshie runs provably fair raffles and coinflips on Base, using Chainlink VRF for randomness. Escrow, draw and payout happen in one transaction, so there is no balance sitting anywhere waiting for you to remember it. See how it works.

📷 Photo by John Cardamone on Unsplash

Valentina Ní Críonna

Author Valentina Ní Críonna

More posts by Valentina Ní Críonna