The Vault

The Vault

The Vault is the contract that collects every royalty, holds the ETH, and lets you claim your share. There is no other place for fees to go. There is no admin function to drain it.

What flows in

SourceAmountGoes to
Secondary sale royalty10% of sale priceVault → split 90/10
Mint proceeds0.0015 ETH × quantityVault → 100% deployer

The mint money is on a separate rail — it never touches the holder pool. Only secondary trades feed the holder yield.

The split

Vault.sol
function _intake(uint256 amount) internal {
    if (amount == 0) return;
    uint256 deployerCut = (amount * DEPLOYER_BPS) / 10_000;  // 10%
    uint256 holdersCut  = amount - deployerCut;              // 90%
 
    deployerBalance += deployerCut;
 
    if (totalShares > 0) {
        accETHPerShare += (holdersCut * ACC_PRECISION) / totalShares;
    } else {
        deployerBalance += holdersCut;  // fallback only if no shares exist
    }
    emit RoyaltyReceived(msg.sender, amount, holdersCut, deployerCut);
}

For every 1 ETH of royalty:

  • 0.9 ETH → distributed to all holders, weighted by share
  • 0.1 ETH → deployer wallet

The accumulator

The Vault uses a battle-tested pattern called a MasterChef accumulator. It runs in O(1) — you can claim in a single, cheap transaction no matter how many holders exist.

Every time fees arrive, a global counter accETHPerShare ticks up. Every turtle tracks how far it has claimed via rewardDebt. The pending amount is just the difference:

Vault.sol
pending = (share × accETHPerShare) / 1e18 − rewardDebt

This is the same math that secures billions on Sushi, Pancake, and dozens of other DeFi protocols. It is correct, audited many times over, and impossible to drain repeatedly — once you claim, your rewardDebt updates and your pending falls back to zero until new royalties arrive.

Claiming

You can claim any time. You can claim any subset of your turtles. The Vault verifies you own them at claim time:

Vault.sol
function claim(uint256[] calldata tokenIds) external nonReentrant {
    uint256 accNow = accETHPerShare;
    for (uint256 i; i < tokenIds.length; ) {
        uint256 id = tokenIds[i];
        if (glyphs.ownerOf(id) != msg.sender) revert NotOwnerOfToken();
        uint32 share = glyphs.glyphState(id).shareScaled;
        uint256 owed = (uint256(share) * accNow) / ACC_PRECISION - rewardDebt[id];
        if (owed > 0) {
            unclaimed[msg.sender] += owed;
            rewardDebt[id]         = (uint256(share) * accNow) / ACC_PRECISION;
        }
        unchecked { ++i; }
    }
 
    uint256 amount = unclaimed[msg.sender];
    if (amount == 0) revert NothingToClaim();
    unclaimed[msg.sender] = 0;
    (bool ok, ) = msg.sender.call{value: amount}("");
    if (!ok) revert TransferFailed();
}

After claiming:

  • Your rewardDebt is updated for each claimed turtle.
  • A second claim call immediately after reverts with NothingToClaim — there is nothing left to take until new fees arrive.

1-year unclaimed recovery

If a wallet goes inactive for 365 days (no claim, no transfer, no forge), anyone can call recoverUnclaimed(user) to sweep their settled, un-withdrawn balance (the unclaimed[user] slot) back into the pool. The recovered ETH is then redistributed via the same 90/10 split.

This is abandonment protection, not a fund-grab. Active holders are never affected. The 365-day clock resets every time the wallet interacts with the Vault.

No admin can drain the Vault

Read the entire contract: there is no function that lets the deployer drain the holder pool. The deployer can only withdraw their own 10% cut:

Vault.sol
function withdrawDeployer() external nonReentrant {
    uint256 amount = deployerBalance;
    if (amount == 0) revert NothingToClaim();
    deployerBalance = 0;
    (bool ok, ) = deployerWallet.call{value: amount}("");
    if (!ok) revert TransferFailed();
}

deployerWallet is immutable — set at deploy, cannot be changed. deployerBalance is incremented only by the 10% intake. The holder side of the pool sits in accETHPerShare and is only reachable via claim — which checks ownership.

Don't Trust, Verify