the void, explained
The Void is a shared ETH vault that pays yield while a dice roll slowly eats it. You deposit, you earn, you age — and eventually the protocol chooses someone to consume. The rest of the vault gets richer. This page explains every mechanic, every contract, and every flow in plain language.
introduction
The Void is an on-chain survival protocol. You deposit ETH into a shared vault. The vault converts your ETH to a yield-bearing asset (wstETH in production, a mock on public testnet). While your position is inside, three things happen at once:
- Your position earns staking yield on the underlying asset.
- Your position earns $VOID emissions that are auto-staked for additional ETH yield.
- Your position ages into higher tiers that reduce your withdrawal fee — but every cycle, the protocol rolls a weighted die and consumes one active player, removing their entire position.
If you are consumed, your position is burned from the vault and distributed to the surviving depositors. In return you mint a soulbound Void Soul NFT that earns a share of every future withdrawal fee, forever. If you withdraw first, you exit with your yield minus a tier-dependent fee.
Deposit ETH. Earn yield. Age into safer tiers. Be consumed or withdraw. If consumed, collect protocol fees forever as a Void Soul.
the premise
Most yield protocols are zero-sum against an external market — they pay you rewards drained from trading flow, LP incentives, or token inflation. The Void is a positive-sum game against time.
Every consumption removes one depositor's shares and redistributes their value across the rest of the vault. The total ETH in the vault does not drop — it just belongs to fewer people. For survivors, every consumption is a pay raise. For the consumed, it is not a wipe: it is the moment your exposure converts from "share of the vault" to "share of every future fee the protocol ever earns."
Tiers make this geometric. The older a position gets, the lower its probability of being selected and the lower its withdrawal fee. The protocol intentionally rewards conviction: the longer you stay, the safer and cheaper you are to leave.
player lifecycle
Every position moves through the same five states. You can think of it as a pipeline — each phase has a clear on-chain trigger.
- 01enter
Call
depositETH()onVoidVaultwith any non-zero ETH value. The vault wraps your ETH to yield tokens, mints you non-transferable vault shares (vVOID), and places you at the Initiate rank. Your deposit timestamp is recorded. - 02earn
Your shares automatically accrue yield from the underlying asset (wstETH rebasing) and $VOID emissions (per-second Synthetix-style distribution). $VOID rewards are auto-staked with a 14-day lock so you earn ETH yield on top of ETH yield.
- 03age
Once enough time has passed since your deposit timestamp, call
upgradeTier()to move into the next tier. Each upgrade reduces your consumption weight and your withdrawal fee. Tier arrays are maintained with swap-and-pop for gas-efficient weighted random selection. - 04fork — consumed or exit
Every epoch,
VoidConsumerrequests randomness and runs a tier-weighted selection across all active players. If you are selected, your shares are burned and a Soul NFT is minted to you. Otherwise, you may leave at any time viarequestWithdrawETH()— this initiates an async unwind from the yield asset. - 05settle
For withdrawals, the yield token is routed through CoW Protocol to swap directly into ETH at the best available rate — no Lido unstake queue, no multi-day wait. A keeper advances the request (request → process → settle), then you call
claimWithdrawETH(requestId)and receive ETH net of the tier fee. If you were consumed, you callVoidConsumedPool.claim(tokenId)to collect accumulated fees from your Soul NFT.
tiers & consumption
There are six ranks — Initiate, Resistant, Defiant, Unyielding, Relentless, and The Hardened. A rank has two effects: a consumption weight that biases random selection, and a withdrawal fee applied only if you exit voluntarily. Both decrease as you age.
Weights form a geometric falloff (60, 45, 30, 20, 12, 6). When randomness is drawn, the victim is chosen by first picking a rank proportional to its total weight × player count, then picking a uniformly random player within that rank. A fresh Initiate is ten times more exposed than one of The Hardened.
Rank boundaries on mainnet are 3d / 7d / 14d / 30d / 60d. On testnet the same boundaries are compressed to minutes so the full lifecycle can be observed end-to-end without waiting months.
consumption flow
Consumption is a fully on-chain, asynchronous ritual. It spans four transactions across two contracts, one keeper, and a VRF callback.
- atrigger
When the epoch duration elapses and the player count is at or above the hibernation threshold, Chainlink Automation automatically calls
triggerConsumption()on the consumer. It requests one random word from Chainlink VRF and setspendingConsumption = true. No human trigger and no keeper intervention is required. - bvrf callback
Chainlink calls
fulfillRandomWords(requestId, words). The consumer selects a weighted tier, samples a player, burns their shares inside the vault, and mints a Soul NFT with the consumption record. - credistribution
100% of the consumed player's value stays in the vault. Shares are burned but the underlying yield token is not, so every remaining share is instantly worth more. Survivors absorb the entire position — no slice is diverted at consumption time.
- dregister & claim
VoidConsumerregisters the new NFT inVoidConsumedPool. The NFT's per-token debt is snapshot so the holder earns from this moment forward, with any pre-registration ETH held inheldForFirstNFTdistributed to the first Soul when the NFT supply goes from 0 to 1.
If VRF fails to respond within CONSUMPTION_TIMEOUT (1 hour), admins can call resetPendingConsumption() to unstick the state machine and retrigger. No player is ever consumed without a fulfilled VRF response.
yield & eth-only ux
The Void is an ETH-only product. At no point does a depositor handle stETH, wstETH, or any LST directly. The yield layer is hidden behind an adapter.
- On mainnet,
WstETHAdapterwraps incoming ETH into wstETH via Lido and exposes an ERC-20 1:1 token to the vault. Every deposit automatically earns Lido staking yield with no user action required. - On testnet, a mock yield token simulates the same interface without touching Lido. UX, indexer, and consumption flows are identical — only the yield source changes.
- Withdrawals do not sit in Lido's unstake queue. Yield tokens are routed through CoW Protocol via
CowSwapYieldLiquidatorto find the best execution into ETH, so exits settle in minutes rather than days. - The vault holds adapter tokens and only ever quotes values in ETH via
yieldToken.getETHValue(). Every event payload is denominated in ETH terms at the moment of the action.
Because the yield token rebases, vault shares (vVOID) are non-transferable. Allowing transfers would break the tier arrays that drive consumption. Deposits and withdrawals are the only legal share operations.
fees & value flow
Withdrawal fees are the single revenue source that powers the entire ecosystem. Consumption redistribution is zero-fee — the protocol never takes a cut when a player is consumed. Only voluntary exits pay fees, and those fees fan out to four destinations.
Example: a Defiant-rank player exits with 10 ETH of position. 3.00% (0.30 ETH) is taken as fee, split as 0.12 / 0.075 / 0.06 / 0.045 ETH across buyback / staking / consumed / treasury. The player keeps 9.70 ETH.
Fees are pull-based: destinations receive ETH by calling claimFees(recipient) on the vault, which drains that recipient's pending balance. This removes push-failure risk during the hot path of a withdrawal.
epochs & hibernation
Epoch duration is dynamic: the more players are in the vault, the faster epochs tick. VoidConsumer stores up to eight descending-order thresholds of the form (minPlayers, duration). The first threshold whoseminPlayers is met sets the current epoch duration.
If player count drops below hibernationThreshold, the protocol enters hibernation. Consumption is paused, existing positions keep earning yield, and epochs stop advancing. The vault wakes up as soon as player count recovers. Hibernation protects a tiny player set from being trivially wiped.
Chainlink Automation upkeep is registered against VoidConsumer. checkUpkeep() returns true when the epoch has elapsed and the vault is not in hibernation or a pending state; performUpkeep() calls triggerConsumption().
$void token
$VOID is an ERC-20 burnable token with a fixed supply of 100,000,000. There is no mint function: total supply is monotonically decreasing over time via buybacks.
Emissions flow to vault depositors using a Synthetix-style accumulator (voidAccRewardPerShare). Emission rate voidRewardRate is tokens-per-second, set by protocol admins, and bounded by voidRewardEndTimestamp to allow predictable runway. Claimed $VOID is auto-staked on the depositor's behalf with a 14-day lock.
staking
VoidStaking converts $VOID holders into ETH earners. Stakers get a proportional share of the 25% fee bucket using an immediate accumulator pattern — ETH is distributed the instant the contract receives it, not dripped over time.
- Manual stake: call
stake(amount). Tokens are freely unstakeable after the cooldown. - Auto-stake: the vault calls
stakeFor(user, amount, lockDuration)when claiming $VOID emissions. Locked batches unlock at expiry and behave like manual stakes afterwards. - Claim: call
claimRewards()to pull accrued ETH without touching your stake. - Unstake: call
requestUnstake(amount), wait the cooldown, thencompleteUnstake().
Stakes are tracked as batches with optional unlockTime stamps. Unstaking consumes unlocked batches oldest-first using swap-and-pop, keeping the per-user array bounded.
soul nfts
VoidSoulNFT is a soulbound ERC-721 minted to every consumed player. Each token stores a permanent record of the event:
epoch— the epoch index in which consumption occurred.timestamp— block time of the VRF callback.vaultTVL— total assets in the vault at consumption (ETH terms).sharesLost— the vault shares burned for this consumption.player— the consumed address. Permanently bound to the recipient; transfers between non-zero addresses revert.
Holding a Soul entitles you to 20% of every future withdrawal fee, forever. VoidConsumedPool uses a MasterChef accumulator (accETHPerNFT), so distribution is O(1) regardless of how many NFTs exist. Holders call claim(tokenId) or claimAll() to pull accrued ETH.
buyback & burn
40% of withdrawal fees accumulate in VoidBuyback. Once the balance crosses minSwapAmount, anyone can call buybackAndBurn(), which swaps ETH for $VOID through an IBuybackRouter (CoW Protocol in production via CowSwapBuybackRouter) and immediately burns the received tokens.
maxSlippageBps caps acceptable slippage at swap time; the router reverts if the received amount is below the implied minimum. The burn is irrevocable — tokens are destroyed, not locked.
contract reference
Each contract below is deployed with identical bytecode across networks — adapters are the only network-specific piece. Source paths are relative to the repository root.
Core vault. Accepts ETH deposits, wraps to yield tokens, manages shares, tier arrays, withdrawal fees, and consumption.
Game engine. Runs epochs, requests Chainlink VRF randomness, selects a victim by weighted tier, and triggers consumption on the vault.
Soulbound ERC-721 minted to every consumed player. Records the epoch, timestamp, vault TVL, and shares lost at the moment of consumption.
Distributes ETH fees to Soul NFT holders using a MasterChef-style accumulator. Consumed players earn yield forever.
Stake $VOID to earn real ETH yield from protocol fees. Supports manual stakes and auto-staked vault emissions with a 14-day lock.
$VOID ERC-20. Fixed supply of 100,000,000. No mint function — supply is only decreased through burns.
Receives the buyback share of withdrawal fees, swaps ETH for $VOID via an IBuybackRouter, and burns the tokens.
Adapters: WstETHAdapter, CowSwapBuybackRouter, and CowSwapYieldLiquidator live under contracts/src/adapters/. Swap them per network without touching core logic.
networks
The live dashboard connects to Sepolia testnet. Vault at 0xa688…1c39, consumer at 0x0DAc…41Cc, soul NFT at 0xa5B7…4C7c.
glossary
- vault
- The shared ETH pool (
VoidVault). Your share of the vault is represented by non-transferable vVOID tokens. - yield token
- The ERC-20 the vault holds internally. wstETH (mainnet, Hoodi) or a mock (Sepolia). Abstracted behind
IYieldToken. - tier
- Your age bracket within the vault. Higher tier = lower consumption weight and lower withdrawal fee.
- epoch
- A consumption window. When an epoch elapses, the consumer triggers a VRF round and picks a victim.
- consumption
- The burn of a selected player's shares. Distributes their value to survivors and mints them a Soul NFT.
- hibernation
- A safety mode when player count falls below the threshold. Epochs and consumption pause; yield and emissions continue.
- soul nft
- Soulbound ERC-721 minted to consumed players. Entitles the holder to a perpetual share of withdrawal fees.
- genesis period
- The short window right after deployment where all deposits share the same timestamp. Prevents a first-block advantage.
- keeper
- An off-chain bot (or Chainlink Automation) that advances the async withdrawal pipeline: process → settle → claim.
faq
Can I lose more than I deposited?
No. The maximum loss is your deposited position at the time of consumption, and you simultaneously receive a Soul NFT entitled to a perpetual share of protocol fees. You never owe the protocol anything.
What stops an attacker from spamming deposits?
The Initiate rank carries the highest consumption weight (60) and the highest withdrawal fee (5%). Fresh positions are both most likely to be consumed and most expensive to exit. There is no incentive to churn.
Who controls consumption? Can the team pick a victim?
No human can influence selection. Consumption flows through Chainlink VRF, and the weighted lookup is deterministic in the received random word. Admin roles can only update parameters (tier weights, fee splits, thresholds) — never mint shares, pick a victim, or burn a Soul.
Why are vault shares non-transferable?
Tier arrays are indexed by address. Allowing share transfers would allow a player to dodge consumption by handing off their position mid-epoch. Non-transferability guarantees tier integrity.
What happens to yield earned during the epoch I was consumed?
All yield accrued up to the moment of the VRF callback is redistributed to survivors along with the rest of your position. There is no partial claim.
Is there a deposit cap?
depositCap is a protocol parameter. On testnet it is set to 0 (unlimited). Mainnet may launch with a cap to guard early liquidity.
How do I know when I can upgrade tier?
The dashboard reads depositTimestamp and tier boundaries from the vault and surfaces a countdown. When it hits zero, call upgradeTier() — anyone can pay the gas, but only you benefit.
Do Soul NFTs ever stop earning?
No. As long as the protocol exists and receives withdrawal fees, every registered Soul earns a proportional share. The accumulator does not expire.