DIM Protocol Docs
The Dormant Inventory Mechanism turns vaulted, appraised collectibles into productive inventory: they sit inside community gacha machines built as Uniswap v4 pools, earn a cut of every play, and restock themselves through incentives instead of procurement.
Overview
Two problems solve each other. Collectors hold authenticated, vaulted items that do nothing. Gacha machines constantly run out of inventory. DIM lets collectors deposit ("stock") their items into a machine where players pay per spin — depositors earn streaming yield from every play, and when a prize tier runs low, the machine raises that tier's rewards until someone restocks it. The shortage is the demand signal.
- A machine is a Uniswap v4 pool. Its two currencies are the payment token and an inert
PlayPass; theRelicMachinehook does all the work. - A play is a swap. Exact-input, priced in multiples of the play price.
beforeSwapconsumes the payment and returns zero output. - No LPs.
beforeAddLiquidityalways reverts — the machine's inventory is relics, not tokens.
The machine
Relics and tiers
Items live in the RelicVault collection. The curator appraises each relic
(valueOf) in payment-token units; the appraisal sorts it into one of five
tiers when stocked:
| Tier | Cutoff (demo) | Hit odds / spin | Buyout on win |
|---|---|---|---|
| Floor | < $50 | 35% | — |
| Standard | < $200 | 20% | — |
| Premium | < $1,000 | 8% | 50% |
| Chase | < $5,000 | 1.5% | 80% |
| Jackpot | ≥ $5,000 | 0.5% | 90% |
Stocking uses stockRelics. Unstocking is a two-step queue
(beginUnstock → finishUnstock) gated by epochs, so inventory
can't flee mid-epoch and draws stay honest.
Restock boost
Tiers can declare a stock target. When a targeted tier falls below target, its yield weight scales up linearly to a max boost (3× in the demo). An empty chase tier paying 3× is the machine "going shopping" — except the community is the supply chain.
Winning and buyouts
When a draw hits a stocked tier, a relic from that tier is transferred to the player.
The stocker keeps all yield earned so far plus a pre-agreed buyout — a
percentage of appraisal paid from the machine's reserve. If the reserve can't cover it,
the shortfall is tracked as debt (buyoutDebt) and becomes claimable as later
plays refill the reserve (collectBuyoutDebt). Anyone can also top the
reserve up directly with fundReserve.
Play economics
Every play's revenue is split at payment time:
- House fee — 2.5% to the protocol.
- Operator fee — up to 10%, set by the machine's operator.
- Reserve — 10% in the demo, funds buyouts.
- Stockers — the rest, streamed per tier by yield weight × restock boost, pro-rata by appraised value within the tier.
Yield accrues per relic (pendingYield) and survives wins and unstocking
(yieldOwed). Claim any time with harvestYield.
Randomness
Draws resolve through Chainlink VRF. The payment transaction only requests
randomness (DrawRequested); the outcome doesn't exist yet, so neither the
player, the operator, nor the block producer can see or steer it. The coordinator's
callback resolves each play (RelicWon / Blank) a few blocks
later. A relic unstocked mid-flight never bricks the callback — the draw cascades to a
lower tier or blanks.
The DIM token
100M DIM, launched single-sided by DIMLaunchpad.ignite: the
entire supply is seeded as a one-sided range order on a v4 pool at the target FDV, with
zero paired capital. The launchpad owns the LP; fees can be skimmed
(skimFees) and the position closed (pullLiquidity) by the
treasury.
- Launch tax —
CurveTaxHooktakes 47% on buys / 3% on sells at launch. Taxes can only ever be lowered (easeTaxes), never raised. - Max buy — a single buy can take at most 1% of supply off the curve during launch; the treasury can lift the cap permanently (
liftMaxBuy). Wallets are never capped. - Burnable —
burn/burnFromshrink supply and market cap. - EIP-2612 — gasless approvals via
permit.
Contract reference
| Contract | Role | Key functions |
|---|---|---|
RelicMachine |
v4 hook — the gacha machine | openMachine, stockRelics, beginUnstock, finishUnstock, harvestYield, collectBuyoutDebt, fundReserve, machineView, tierView, tierRelics, pendingYield |
PlayRouter |
Entry point for players | play(key, payIsZero, amount, player) |
RelicVault |
Appraised ERC721 collection | vaultRelic, reappraise, valueOf |
PlayPass |
Inert pool currency | — (zero supply, untransferable) |
DIMLaunchpad |
Single-sided token launch | ignite, curveStats, skimFees, pullLiquidity |
DIM |
Protocol token (symbol DIM) | burn, burnFrom, permit |
CurveTaxHook |
Launch tax + max buy on the DIM pool | easeTaxes, liftMaxBuy, buyTaxBps, sellTaxBps, maxBuyBps |
Run it locally
# terminal 1 — local chain
anvil
# terminal 2 — deploy the whole stack + demo data
forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --broadcast
# terminal 3 — the app
npm run dev --prefix web
The app auto-detects the local chain and runs live against it; without a chain it falls back to an in-browser simulation of the same math. Draws on Anvil resolve through a mock VRF coordinator — on a real network, Chainlink fulfils them a few blocks later.
Based on "Introducing DIM: FWA for Vaulted Cards" by Rhys. Published odds, visible fees, zero ambiguity about what happens on a win. Not a risk-free savings account.