Anvil is an options venue with no liquidation price: every contract is backed by collateral before it exists, so there is nothing to liquidate. This page is the whole design in one place: the mechanics, the arithmetic, the deliberate omissions and the contract surface, written for someone who wants to understand it before touching it.
Everything here about the protocol is drawn from docs/ARCHITECTURE.md, the ten module READMEs, and the Solidity itself. Where a README and the Solidity disagree, the Solidity wins and the disagreement is listed rather than resolved quietly.
Section 01
What Anvil is a venue with nothing to liquidate.
Someone holding an asset escrows it, picks a price and a date, names their own premium, and lists it. A buyer pays that premium for the right (not the obligation) to buy the asset at that price before the date. If they exercise, they pay the strike and take the asset. If they do not, it expires and the writer keeps both the asset and the premium.
Anvil itself never takes a position. It is the venue, not the counterparty. Its only revenue is a cut of each premium, capped in code at 2% and shipping at 0.5%, which means it earns whether the market goes up, down or nowhere, and it cannot lose money on a trade. The fee never rests in a protocol account: it moves from buyer to treasury inside the same call that moves the premium from buyer to writer.
Three properties that fall out of the design
-Nothing can default. A writer must escrow the collateral before an offer appears on the shelf: OfferBook.list calls escrow.commit before it writes the offer struct. Every option in circulation is backed by collateral already sitting in WriterEscrow. There is no margin, no leverage on the writer’s side, and no liquidation engine, because there is nothing to liquidate.
-There is no oracle. Anvil settles physically: the holder pays the strike and receives the underlying. Nobody rational does that unless the option is in the money, so the holder’s own decision carries exactly the information a price feed would have supplied.
-The protocol never prices anything. Writers name their own premiums. A writer who prices badly is the one who finds out. Anvil has no volatility surface to maintain and no model risk, and it cannot be arbitraged through mispricing because it never quotes.
A worked example
Gold at $4,500 an ounce. A writer escrows 100 oz, lists calls at a $4,700 strike expiring in thirty days, and asks $35 per contract of one ounce.
Outcomes at expiry
Gold at $4,400 not exercised buyer -$3,500 premium
writer keeps 100 oz + $3,500
Gold at $4,650 not exercised buyer -$3,500 premium
writer keeps 100 oz + $3,500
Gold at $5,200 exercised buyer pays $470,000 for gold worth $520,000
net +$46,500 after premium
writer gets $470,000 + $3,500,
gave up gold worth $520,000
The writer’s downside is never losing money. It is underperforming holding. They keep the first 4.4% of any rally, from spot up to the strike, plus the premium, and give up everything above $4,700. That is the trade, and it should be stated plainly to anyone writing.
One thing the prose gets wrong
Every document in this repository describes a single instrument: the writer escrows the underlying, the holder pays quote to take it. The contracts are more general than that. AnvilTypes.sol declares OptionKind { CALL, PUT }, createSeries takes a kind, and AnvilMath.collateralLeg and strikeLeg swap the two tokens for a put:
contracts/shared/libraries/AnvilMath.sol
collateral leg strike leg
CALL underlying, size quote, strike
PUT quote, strike underlying, size
A put is the same machine with the tokens swapped, and it has an integration test of its own. Where this page says “underlying” and “quote”, the contracts say “collateral leg” and “strike leg”, and for a put those are the other way round.
Section 02
Core concepts
Four ideas carry the whole design: the series, the tick grid, the availability identity, and the ladder that routes around a sold-out strike. Everything else is plumbing around them.
Vocabulary
Term
Meaning
Underlying
The asset being sold. For a call, what the writer escrows.
Quote
The token the strike is paid in. For a call, what the buyer pays to exercise.
Strike
Quote paid per contract to exercise. In the quote token's own units.
Contract size
Underlying delivered per contract. In the underlying's own units.
Series
One line: (underlying, quote, strike, expiry) plus a kind. The atomic unit.
Offer
One writer's listing on a series, at their own premium. Filled by id.
Available
Contracts written and still unsold at a strike.
Assignment
A writer's pro-rata share of the exercises in their series.
The series is the atomic unit
A series is one tradeable line, fully described by its terms. Every offer, option token and escrow balance is scoped to one. The id is a hash of the terms, so the same terms always name the same series and two people opening “$4,700 gold calls expiring Friday” independently land on the same line rather than on two thin ones.
contracts/shared/types/AnvilTypes.sol
struct Series {
address underlying; // the asset being priced
address quote; // the token the strike is denominated in
uint128 contractSize; // underlying per contract, in its own units
uint128 strike; // quote per contract, in its own units
uint64 expiry; // unix seconds; exercise is impossible after
OptionKind kind; // CALL or PUT - decides which token is collateral
SeriesStatus status; // NONE | ACTIVE | EXPIRED
}
There are no fixed-point prices anywhere in Anvil. A strike is quote-per-contract in the quote token’s own units and a contract size is underlying units, so every number in the system is a raw token amount. That removes an entire class of decimal-scaling bugs, at the cost of a frontend having to know both tokens’ decimals.
Listing an asset is admin-gated; opening a strike is not. SeriesFactory.listAsset fixes the quote token, contract size and tick for an underlying, and after that createSeries is permissionless: an empty series holds no collateral and obliges nobody, so gating it would only slow the market down. Expiry must be in the future and within maxHorizon, which initialises to 180 days.
The tick grid
Strikes must be an exact multiple of the asset’s tick. createSeries reverts with StrikeOffGrid otherwise. Without it you get a long tail of near-identical strikes ($150.00, $150.01, $150.02), each holding thin inventory that nobody can compare and nobody can fill.
Snapping concentrates writers onto rungs a buyer can actually compare. It is the only place in the protocol where a design decision exists purely to make a market legible rather than to make it safe.
Availability is an identity, not a cap
A strike is not an infinitely deep order book. It holds exactly as many contracts as writers have collateralized:
contracts/InventoryManager/InventoryManager.sol
available = listed − sold
listed rises when a writer escrows and offers, and falls when they pull an unsold offer. sold only ever rises. So “132 available” means 132 contracts are genuinely backed by underlying sitting in escrow right now: not a limit somebody chose, and not a quote that evaporates when you click it. recordSold reverts with InsufficientAvailability rather than overselling.
The ladder, and sold-out routing
InventoryManager keeps the strikes for an underlying and expiry in a sorted list, inserted in position at series creation. When a strike sells out it exposes the nearest live rung above and below, so a buyer can be routed on-chain rather than trusting the frontend to know what is left.
Routing a buyer past a sold-out rung
ladder(underlying, expiry) -> Rung[] every strike, ascending
nextRungUp(underlying, expiry, K) -> Rung nearest at or above K
nextRungDown(underlying, expiry, K) -> Rung nearest at or below K
struct Rung { bytes32 seriesId; uint128 strike; uint128 available; }
Both searches skip rungs with nothing behind them. A registered but unwritten strike is not somewhere a buyer can go, so it is not offered as an alternative. If no rung qualifies, the call reverts with NoLadderRung rather than returning an empty struct that a caller might mistake for a real one.
Section 03
The lifecycle six steps, and what moves.
Every trade on Anvil walks the same path. The order of the first step is the load-bearing decision in the whole protocol: collateral moves before the offer exists, which is why nothing on the shelf can be unbacked and why there is no liquidation engine anywhere in the repository.
The path
LIST ──▶ BUY ──▶ HOLD ──▶ EXERCISE ──▶ (expiry) ──▶ SETTLE
│ ▲
└── or let it lapse ───────┘
Step 01
List
OfferBook → WriterEscrow → InventoryManager
A writer escrows the collateral leg, names their own premium, and lists an offer. Collateral moves before the offer exists, so nothing on the shelf is ever unbacked.
A buyer picks an offer by id and pays the premium. It goes straight from buyer to writer, minus the protocol fee, which goes straight from buyer to treasury. Anvil never holds premium. maxPremium and deadline bound what the buyer can be charged and how stale the transaction may be.
quote: buyer → writer (total − fee), buyer → treasury (fee). options minted to buyer. sold += quantity.
Step 03
Hold
OptionToken
Options are ERC-1155 and freely transferable while the series is live. Transferring one moves the right to exercise and no obligation, because the holder never had one. The worst a recipient can do is let it expire.
options: holder → anyone. Nothing else moves.
Step 04
Exercise
ExerciseEngine → OptionToken → WriterEscrow
Before expiry, the holder pays strike times quantity and receives the underlying. Their option tokens burn first, so a holder without the balance fails before any token moves. The strike leg is pulled in before the collateral leg goes out. No oracle is involved.
After expiry the series stops trading and can no longer be exercised. Anyone may flip its status, and the first writer to settle flips it as a side effect, so writers never need a keeper to get paid.
status: ACTIVE → EXPIRED. No tokens move.
Step 06
Settle
ExerciseEngine → WriterEscrow
Each writer collects their pro-rata assignment: quote for the contracts assigned to them, underlying back for everything else, including anything they wrote but never sold. Once per writer per series.
A writer cannot cancel an option someone is holding. cancel returns only the unsold remainder; contracts already bought stay collateralized until expiry. A buyer cannot buy their own offer; fill reverts with BuyingOwnOffer. A holder cannot exercise at or after expiry; exercise reverts with Expired. A writer cannot settle before expiry, or twice.
The one thing that can go wrong is a holder forgetting. An in-the-money option left alone until expiry pays nothing, the same as on any physically settled venue, and the reason a frontend should nag.
Section 04
Assignment pro-rata, and deferred.
When a holder exercises, Anvil does not decide whose collateral was taken. The swap happens at the series level: underlying leaves the pool, quote enters it, and nobody is assigned at that moment. Only after expiry does each writer compute their share.
They then collect assigned contracts’ worth of quote, plus everything else back as underlying, including anything they wrote but never sold. previewSettle returns exactly what settleWriter would pay, because settlement calls it.
Why it is deferred
Gas. Exercise costs O(1) regardless of how many writers back a strike. The obvious alternative (walking a writer list on every exercise and marking each one assigned) gets more expensive the more writers a series attracts, which is exactly backwards: the most popular series would be the most expensive to trade.
The cost of deferring is that a writer does not know their assignment until expiry. They know the ratio can only move against them as more exercises land, and they know their maximum: they can be assigned on every contract they sold, and never on one they did not.
Why the ratio has to stay fixed
settleWriter zeroes the writer’s own position but deliberately does not decrement the series totals. If it did, the denominator would shrink as writers settled and a later settler would compute a different assignment than an earlier one for the same series. The pool totals are frozen at expiry so every writer gets the same answer whenever they show up.
contracts/WriterEscrow/WriterEscrow.sol
// Zero the writer's position before paying out. `_pools` totals are
// deliberately NOT decremented: the assignment ratio must stay fixed
// across every writer's settlement, or later settlers get a different
// answer than earlier ones.
_committed[seriesId][writer] = 0;
_sold[seriesId][writer] = 0;
Rounding favours the pool
Assignment rounds down. The sum of every writer’s assignment can therefore fall a few units short of the quote the pool actually took in: at most one unit per writer. That dust stays in escrow and is recoverable by admin through skim, which can only ever move the difference between the held balance and the tracked obligations for that token.
The direction of the rounding is the point. It always errs toward the pool holding too much rather than too little, so it can never leave a writer unpaid. skim cannot reach collateral, because collateral is inside obligations.
The same accounting is what makes solvency checkable from outside: obligations(token) is what the escrow owes in that token, and the escrow’s balance should always cover it.
Section 05
What Anvil deliberately does not do.
Most of this design is a list of refusals. Each one deletes a subsystem, and every deleted subsystem takes a class of failure with it. These are not features waiting to be added later, and each has a price that is stated here rather than hidden.
There is no PricingEngine.sol, no PriceFeed.sol, no Liquidator.sol, and no protocol-owned position anywhere in the venue. Those are not files that have yet to be written. They are files the design is arranged to make unnecessary.
01
No pricing engine
Writers name their own premiums, and the ladder shows what is on offer. Nothing in the protocol has an opinion about what an option is worth. A writer who prices badly is the one who finds out: the offer sits unfilled, or it fills instantly and they wish it had not.
Removes model risk. There is no volatility surface to maintain, nothing to recalibrate when a regime changes, and no way to arbitrage the protocol through mispricing, because it never quotes. The failure class deleted is the one where the venue's own model is wrong and the venue pays for it.
The costA thin book looks empty rather than looking liquid at a bad price. That is a real cost, and it is what the liquidity vault exists to address: outside the venue, at the depositors' risk.
02
No oracle
Settlement is physical: the holder pays the strike and receives the underlying. Nobody rational does that unless the option is in the money, so the holder's own decision carries exactly the information a price feed would have supplied. The protocol never has to know what anything is worth.
Removes the largest attack surface an options protocol normally has. No settlement price to manipulate, no staleness window, no dispute period, no guardian veto, and no way for one block of price action at expiry to decide who gets paid.
The costHolders must remember to exercise. An in-the-money option left alone until expiry pays nothing. It also means moneyness cannot be enforced on-chain, which is the one place this decision genuinely hurts; see the risk limits section.
03
No protocol counterparty in the venue
Anvil does not write options, does not take the other side, and does not run a vault inside the venue. Its only revenue is a capped cut of each premium, so it earns whether the market goes up, down or nowhere.
Removes every path by which the protocol itself can lose money on a trade. A protocol-owned writer vault would make the protocol short volatility: it would win small and often and lose large and rarely, and a bad month would be paid for out of the same balance sheet that guarantees everyone else's settlement.
The costThere is a LiquidityVault in the repository, and it is deliberately a client rather than a module: it writes through AnvilCore exactly as a person would, and the protocol cannot tell it apart from one. Nothing in the venue changed to accommodate it.
04
No liquidations
Every position is fully collateralized at listing time. The writer's collateral is in escrow before the offer exists; the buyer's only outlay is the premium they already paid. There is no margin and no leverage on either side.
Removes the liquidation engine entirely, and with it the keeper network, the liquidation bonus, the bad-debt socialisation mechanism, and the cascade where forced selling into a falling market triggers the next liquidation. There is nothing to liquidate because nothing can default.
The costCapital efficiency. Writing a hundred contracts means locking a hundred contracts' worth of collateral for the full tenor, with no way to do more with less.
What is left is a venue. Writers bring collateral and a price, buyers bring premium, and Anvil takes a capped cut of that premium for making the exchange safe. It earns whether the market goes up, down or nowhere, and it never takes the other side of anything.
Section 06
Markets fourteen, and why these.
Fourteen markets are listed in web/lib/markets.ts: three commodities, eight equities, three crypto. The ordering is deliberate and follows the risk tiers in contracts/RiskLimits. None of these real assets exist on any chain this protocol runs on; the testnet deployment lists mock stand-ins for two of them (tXAU and tAAPL), and everything else on this list trades only in the simulator.
web/lib/markets.ts: the listed set
Symbol
Name
Class
Quote
Contract
Tick
Vol
Tier
XAU
Gold
Commodities
USDC
1 oz
$50
15%
1
XAG
Silver
Commodities
USDC
10 oz
$1
26%
1
WTI
Crude oil
Commodities
USDC
10 bbl
$1
35%
2
AAPL
Apple
Equities
USDC
1 share
$5
26%
1
MSFT
Microsoft
Equities
USDC
1 share
$10
24%
1
NVDA
Nvidia
Equities
USDC
1 share
$2.5
45%
2
META
Meta
Equities
USDC
1 share
$10
35%
2
AMD
AMD
Equities
USDC
1 share
$10
50%
2
MU
Micron
Equities
USDC
1 share
$20
45%
2
SNDK
SanDisk
Equities
USDC
1 share
$25
50%
2
TSLA
Tesla
Equities
USDC
1 share
$10
55%
2
BTC
Bitcoin
Crypto
USDC
0.1 BTC
$2500
55%
3
ETH
Ethereum
Crypto
USDC
1 ETH
$100
70%
3
SOL
Solana
Crypto
USDC
1 SOL
$5
100%
3
“Contract” is what one contract delivers on exercise, and it is the contractSize passed to SeriesFactory.listAsset, in the underlying’s own units. “Tick” is the strike grid in the quote’s units. Silver and oil are the only assets here where one contract is more than one unit, which is the usual convention for both: a one-ounce silver contract would carry a premium too small to be worth a transaction. Bitcoin runs the other way (one contract is 0.1 BTC), because a whole-coin contract would make the smallest possible position cost the full price of a coin.
The reference spots in that file are round illustrative numbers used to generate a plausibly shaped chain. They are not quotes, there is no feed behind them, and every surface that renders them says so.
Why commodities and equities lead
The instinct to open with the safest asset is right about risk and wrong about revenue, if “safest” means a tokenized T-bill. An at-the-money option is worth roughly
The approximation the whole argument rests on
premium ≈ 0.4 × S × σ × √T
which means premium scales with volatility. Over thirty days, as a fraction of notional:
30-day at-the-money premium by volatility
Underlying
Annualized vol
30-day ATM premium
Tokenized T-bill
~0.5%
0.06%
Gold
~15%
1.7%
Large-cap equity
~25%
2.9%
Crude oil
~35%
4.0%
BTC
~45%
5.2%
SOL
~100%
11.5%
A T-bill is safe to write because there is nothing to insure. At 0.06% the writer earns about $60 per $100,000 committed for a month, and locks the capital up to do it. That is not a conservative first product; it is no product. Tokenized T-bills are absent from the listed set for exactly this reason.
Gold and equities are the sweet spot: real premium, and a tail that behaves nothing like crypto. They lead the ladder. Real-world assets still belong on the collateral side (idle base asset parked in a yield-bearing T-bill token), just not on the underlying side. One caveat from WriterEscrow: rebasing tokens break its fixed-amount accounting, so use a non-rebasing wrapper.
How the sample chains are generated
None of the numbers on the trade desk are market data. What is real is the shape: each chain is generated from one two-term model rather than typed by hand, so the relationships an options chain cannot violate hold everywhere by construction.
web/lib/markets.ts
call(K) = max(S − K, 0) + TV(K)
put(K) = max(K − S, 0) + TV(K)
// Both sides take the SAME time value at a given strike, so
// put-call parity holds exactly:
call(K) − put(K) === S − K
Strikes are centred on spot, snapped to the market’s tick, five rungs either side. One rung per side is sold out on purpose, so routing to the nearest live rung stays demonstrable on the desk. That is a display decision, not a protocol one.
Section 07
Risk limits three tiers, one principle.
RiskLimits decides which assets the liquidity vault may write, how much of its capital may point at any one of them, and what has to be true before a riskier tier unlocks. It constrains the vault only. Nothing in it applies to a person writing an option directly through AnvilCore, who is limited by their own collateral and nothing else.
Every cap is a percentage of base-asset capital, and every input is a base-asset amount. That is deliberate: it is what lets the whole risk engine work without a price feed. It holds because the vault’s primary product is the cash-secured put, whose collateral is the base asset, so the size of the risk is known exactly at write time with no valuation involved. Covered calls are collateralized in underlying the vault only holds because assignment delivered it, which makes them self-limiting: you cannot write more calls than you were assigned.
The tiers, as applied by script/ConfigureRisk.s.sol
Limit
Tier 1
Tier 2
Tier 3
Assets
Gold, silver, large-cap equity
Oil, high-beta equity, BTC, ETH
Crypto majors
Typical vol
15–30%
35–60%
80–120%
Unlocks at
$100,000
$500,000
$2,000,000
Max per asset
45%
25%
12%
Max per series
15%
8%
4%
Max tenor
90 days
45 days
21 days
Max utilization
85%
65%
45%
None of these numbers live in RiskLimits.sol. The contract ships with an empty tier mapping and every asset at UNTIERED, which means unwritable. The table above is applied afterwards by script/ConfigureRisk.s.sol, which has never been run. An unconfigured RiskLimits blocks every write, which is the safe default and also the current state.
Where the per-series numbers come from
They are set so the worst realistic single-series loss lands at roughly the same fraction of capital in every tier, about 1.5%. A more volatile asset is not banned; it is sized down until it hurts the same amount when it goes wrong.
The derivation, from contracts/RiskLimits/README.md
Tier
Plausible drawdown
Loss if ~15% OTM
× series cap
Damage to capital
1
20%
~10% of collateral
15%
1.5%
2
35%
~20% of collateral
8%
1.6%
3
55%
~35% of collateral
4%
1.4%
That equalisation is the principle worth keeping if the numbers are ever retuned. The specific percentages are governance’s opinion; the property that a tier-3 blowup and a tier-1 blowup cost the vault the same is the design.
Why utilization is attached to the tier
Utilization is a global measure (total capital committed across everything), but the cap checked is the one belonging to the tier being written. The effect is that the riskiest asset carries the tightest gate on the whole book: a tier-3 write only clears if total commitment is under 45%, while tier 1 can run the book to 85%.
Why tenor tightens as volatility rises
Variance grows with the square root of time, so a 90-day position on a 100%-vol token carries far more of it than a 90-day position on gold. Tenor is also lockup: deposits and redemptions are shut until every position settles, so a long-dated write on a volatile name holds every depositor hostage to it.
The limit that is not enforced
Moneyness. “The strike must be within 20% of spot” is the single most useful limit an options book can have, and it cannot be checked without a price. Enforcing it would put an oracle back in the critical path, the dependency the whole protocol was designed to avoid.
The tradeoff is not symmetric, and it is worth being precise about. An oracle used only for pre-trade limits is far weaker than one used for settlement: if it breaks, the vault stops writing, it does not pay the wrong person. That makes it a defensible thing to add later. It is left out for now so the assumption stays visible.
A manager choosing sane strikes is an off-chain assumption, not an on-chain guarantee. A manager who writes deep in-the-money options passes every check in this contract and loses the vault its money on the first expiry. Volatility is unverified too: tiers encode governance’s view of how volatile an asset is, and an asset can change regime faster than governance moves.
script/ConfigureRisk.s.sol, last two lines of output
console2.log("Reminder: moneyness is NOT enforced on-chain.");
console2.log("A manager can write deep-ITM strikes and pass every check here.");
Section 08
The liquidity vault depositors are underwriters.
Read this before depositing
Depositors here are underwriters, not lenders. The word “lending” does not describe what happens. A lender is owed principal back. A depositor here owns a share of whatever the vault is holding when they leave, and that can be worth less than they put in. The share balance can fall.
The vault writes options. Writing options earns a premium and takes on an obligation: it wins small and often, and loses larger and rarely. At a fair premium the expected value is zero: the profit is entirely whatever margin the manager prices above fair value, and the risk is entirely that the manager prices below it.
Why it exists
Anvil’s order book cannot fill an order nobody wrote. Early on there are no writers, so a buyer clicks buy and nothing happens. The vault is the counterparty of last resort: capital that is always willing to write, so the shelf is never bare. That convenience is paid for by the people whose capital it is.
It is a client, not a core module
The vault writes through AnvilCore exactly as a person would, and the protocol cannot tell it apart from one. Nothing in the venue changed to accommodate it. That separation is the point: the venue keeps its property of being unable to lose money, and the risk lives behind an explicit deposit instead of in the protocol everyone else uses. Someone who only wants to trade never touches it.
Share pricing without an oracle
An ordinary vault marks its open positions to compute a share price, and marking an option needs a price feed, the exact dependency Anvil removed. Two decisions avoid reintroducing it.
-Rounds. Deposits and redemptions are only open while the vault is flat, with no unsettled positions. roundActive() is the gate, and both deposit and redeem revert with RoundIsActive while it is true. There is never a moment where someone can enter or exit at a stale mark, because when the gate is open there is nothing to mark.
-In-kind redemption. Leaving hands you a pro-rata slice of every token the vault holds, not a valuation of it. If assignment left the vault holding the underlying, redeemers receive underlying. Nobody has to agree on what anything is worth.
The cost is a lockup: capital cannot leave mid-round. That is disclosed rather than engineered around, because the alternative is an oracle.
One asymmetry worth understanding
Redemption works on a mixed vault; it pays out in kind across everything held. Deposits do not. If a previous round ended in assignment, the vault is sitting on underlying, and someone depositing only the base asset would buy a share of that underlying for nothing, diluting everyone already there. Pricing that deposit fairly would need to value the underlying, which needs a feed.
contracts/LiquidityVault/LiquidityVault.sol
if (roundActive()) revert Errors.RoundIsActive(round);
if (!_holdsOnlyBase()) revert Errors.VaultNotInBaseAsset();
// First depositor sets the peg 1:1. Afterwards shares price off the
// base-asset balance, which is exact because it is all the vault has.
shares = totalShares == 0 ? assets : (assets * totalShares) / held;
LPs can always leave; they cannot always enter. That asymmetry is the price of not having a price feed, and it falls on the person trying to get in rather than the person trying to get out, which is the right way round.
Trust assumptions
Worth being explicit, because this module has more of them than the rest of Anvil put together.
-The manager sets every premium. Underpricing bleeds depositors slowly and invisibly. This is the single largest risk and it is not enforceable on-chain.
-The manager chooses what to write, and can therefore concentrate the vault into one strike or expiry, up to whatever RiskLimits allows, and RiskLimits is optional: writeOffer skips the check entirely when the address is zero.
-Admin can replace the manager, but cannot touch deposits or force a redemption.
-Settlement is permissionless. Depositors never need the manager’s cooperation to get the vault back to a redeemable state; anyone may call settlePosition.
This module has no tests. Not incomplete tests: no test file at all. It is also absent from script/Deploy.s.sol on purpose; script/DeployVault.s.sol exists for it, and has never been broadcast to any chain.
Section 09
Contract reference ten modules.
Each module lives in its own folder under contracts/, holding its implementation, its interface, a README explaining the design decisions behind it, and co-located tests where any exist. Signatures below are transcribed from the Solidity rather than the READMEs, and returns clauses are given in the interface’s own names.
Eight of the ten are deployed and wired by script/Deploy.s.sol, and are live on the testnet. The remaining two, LiquidityVault and RiskLimits, have their own scripts (DeployVault.s.sol, ConfigureRisk.s.sol) which compile but have never been broadcast anywhere. shared/ holds types, errors, math, the auth base and safe ERC-20 transfers, and is not deployed at all.
All of it is fully open source, each module in its own public repository under github.com/anvilcash. That is a position, not a disclosure ritual: a venue that asks people to escrow collateral has no business being a black box, and the deployed bytecode is verified against this source on the chain explorer so anyone can check that what is published is what runs. One periphery contract sits alongside the ten: a PaymentRouter that routes every desk purchase through the protocol token, verified on the explorer, and a client of the venue like any other caller.
Dependency direction: modules never call up into Core
The graph stays acyclic and every contract is independently testable. shared/ never imports a module. Access control is uniform: every module inherits AnvilAuth, which gives it an admin, a single core address, and a set of trusted module addresses, and exposes the three modifiers named in the tables below.
The orchestrator, and the only address anyone needs to call.
Holds no balances and owns no market state. It exists to be a stable address, to enforce the pause, and to establish caller identity; every module is guarded by onlyCore, so routing through Core is what makes the module graph safe to expose. Every entry point passes msg.sender through and never lets a caller name a different beneficiary: there is deliberately no writeFor(address) or exerciseFor(address). While paused, reads stay live so positions remain inspectable, but writers cannot settle.
Defines the terms every other module refers to: (underlying, quote, strike, expiry), plus a per-asset contract size.
Listing an asset is admin-gated; opening a strike is not. Admin fixes the quote token, contract size and strike tick per underlying; after that anyone may create a series, because an empty series holds no collateral and obliges nobody. Strikes must be an exact multiple of the tick or createSeries reverts with StrikeOffGrid. Expiry must be in the future and within maxHorizon, which initialises to 180 days. Delisting stops new series but never strands collateral already escrowed; existing series keep trading and settling.
The shelf: how many contracts exist at each strike, and how many are left.
Availability is not a cap somebody chose. It is the sum of collateral writers have actually escrowed, minus what buyers have taken. This module also owns the ladder: the strikes for an underlying and expiry, kept sorted on insert, so a buyer arriving at a sold-out strike is routed to the nearest live rung on-chain rather than trusting the frontend to know. Rungs with no collateral behind them are skipped: a registered-but-unwritten strike is not somewhere a buyer can go.
Ordering inside list matters: collateral is escrowed before the offer exists, so nothing on the shelf is ever unbacked. Premium moves buyer to writer directly inside fill, minus the protocol fee, which moves buyer to treasury in the same call; Anvil never holds premium. Offers are filled individually by id rather than swept by a matching engine; a frontend sorts by premium and points the buyer at the cheapest, which costs nothing on-chain. fill also refuses BuyingOwnOffer.
Custody for every writer's collateral, and the pro-rata settlement after expiry.
This is what makes Anvil unable to default: a writer cannot offer a contract without first moving collateral in here, so every option in circulation is backed by collateral already present. Per-token obligations are tracked so solvency is checkable: held balance should always cover what is owed, and skim can only ever remove the excess. On exercise the strike leg is pulled in before the collateral leg goes out, so a failed payment reverts before anything leaves. At settlement the pool totals are deliberately not decremented, because the assignment ratio must give every writer the same answer no matter who settles first.
Turns an option into the collateral leg before expiry, and pays writers after it.
This is why Anvil has no oracle. A physically settled option needs no price feed because the holder decides: exercising means paying the strike and taking the collateral, and nobody rational does that unless the option is in the money, so the holder's own choice carries exactly the information a feed would have supplied. Options burn before any token moves, so a holder without the balance fails first. settle calls markExpired, so the first writer to settle flips the series status for everyone. The cost of this design is real: an in-the-money option left alone until expiry pays nothing, which is why a frontend should nag.
The options themselves, as ERC-1155. One fungible id per series.
Only one side is tokenized. The writer's obligation lives in WriterEscrow as collateral, not as a token, so there is no short token and the id is simply the series id cast to uint256: no bit-packing, no encoding to get wrong. Options are freely transferable while a series is live; transferring one moves the right to exercise and no obligation, because the holder never had one. Known deviation: safeTransferFrom does not call onERC1155Received and there is no safeBatchTransferFrom. The hook is omitted deliberately, because it hands control to an arbitrary callee mid-transfer. Treat this as a transfer-only ERC-1155 and track balances by reading balanceOf.
Anvil's entire business model: a cut of each premium.
One fee, charged once, on the premium a buyer pays a writer. Nothing is charged on exercise or settlement: a holder exercising is already paying the strike, and a writer collecting assignment proceeds has been paid nothing new. The rate is capped by MAX_TRADE_FEE_BPS, an immutable 200 bps, so governance can lower the fee but a compromised admin key cannot turn a venue fee into confiscation. The contract ships with tradeFeeBps = 50, which is 0.5%. It never takes custody: fees move straight from buyer to treasury inside the same transfer as the premium, and this contract only sets the rate and counts.
A standing writer funded by depositors, so a buyer's order fills instead of waiting for a counterparty.
It is a client, not a core module: it writes through AnvilCore exactly as a person would, and the protocol cannot tell it apart from one. Nothing in the venue changed to accommodate it, which is the point: the venue keeps its property of being unable to lose money, and the risk lives here, behind an explicit deposit. Depositors are underwriters, not lenders: the share balance can fall. Deposits and redemptions are open only while the vault is flat, and redemption pays out in kind, which is what lets the vault price shares without a feed.
External functions of LiquidityVault
Signature
Notes
deposit(uint256 assets) returns (uint256 shares)
Reverts with RoundIsActive while positions are open, and VaultNotInBaseAsset if a past round left the vault holding underlying.
redeem(uint256 shares)
In kind: a pro-rata slice of every token held. Nothing is priced or sold on your behalf.
Which assets the vault may write, how much of the treasury may point at one of them, and what has to be true before a riskier tier unlocks.
Every cap is a percentage of base-asset capital and every input is a base-asset amount, which is what lets the whole risk engine work without a price feed. It holds because the vault's primary product is the cash-secured put, whose collateral is the base asset, so the size of the risk is known exactly at write time. Covered calls are collateralized in underlying the vault only holds because assignment delivered it, so they are self-limiting. The contract ships with no tiers and no assigned assets; an unassigned asset cannot be written at all, which is the safe default.
[onlyAdmin]Every number in the tier table comes from here.
setAssetTier(address underlying, uint8 tierId)
[onlyAdmin]Assign only what you are ready to underwrite.
untierAsset(address underlying)
[onlyAdmin]Back to unwritable.
Where the READMEs and the Solidity disagree
Found while transcribing the signatures above. In every case this page follows the Solidity, because the Solidity is what would run.
Calls vs puts
Docs sayARCHITECTURE.md and every module README describe one instrument: the writer escrows the underlying and the holder pays quote to take it.
Code saysAnvilTypes.sol declares OptionKind { CALL, PUT }, createSeries takes a kind, and AnvilMath.collateralLeg / strikeLeg swap the two tokens for a put. Puts are a real code path, with an integration test of their own. The prose describes only half of what the contracts do.
OptionToken name and symbol
Docs sayOptionToken/README.md and IOptionToken describe ERC-1155 call options.
Code saysOptionToken.sol hard-codes name = "Anvil Call Option" and symbol = "ANVIL-CALL", but the same contract mints the id for a PUT series. A put position is labelled a call by the token that represents it.
RiskLimits tiers
Docs sayRiskLimits/README.md presents three tiers with capital gates, per-asset, per-series, tenor and utilization caps, as though they were properties of the contract.
Code saysRiskLimits.sol ships with no tiers configured. The constructor takes only an admin, the tier mapping starts empty, and tierOf defaults to UNTIERED. Every number in that table is applied afterwards by script/ConfigureRisk.s.sol. An unconfigured RiskLimits blocks every write.
The fee that actually ships
Docs sayFeeController/README.md states the cap: an immutable constant of 2%.
Code saysTrue: MAX_TRADE_FEE_BPS is 200. The README does not mention that tradeFeeBps initialises to 50, so the fee the contract ships with is 0.5%, not 2%.
AnvilCore's surface
Docs sayAnvilCore/README.md lists createSeries and availabilityOf among the calls Core exposes.
Code saysBoth exist on AnvilCore.sol, but neither is declared in IAnvilCore. An integrator coding against the interface will not see them.
Module count
Docs sayThe root README says the deploy script deploys all eight modules, and ARCHITECTURE.md's contract list has eight entries.
Code saysThere are ten module folders. LiquidityVault and RiskLimits are absent from both documents and from script/Deploy.s.sol.
The expiry horizon
Docs sayNo README mentions a limit on how far out a series may be opened.
Code saysSeriesFactory.maxHorizon initialises to 180 days and createSeries reverts with ExpiryTooFar beyond it. Admin can change it with setMaxHorizon.
Section 10
Testnet the chain, walked.
The venue is live on the Robinhood testnet (chain id 46630): series open, offers on the shelf, faucet tokens for anyone who wants to paper-trade it. What follows is what is deployed, not a recipe.
The venue
live
All eight core modules are deployed on the Robinhood testnet (chain id 46630) with the permission graph fully wired, and the wiring was verified the only way that counts: real writes and fills executed against it without a revert. AnvilCore, the only address callers use, is at 0xFe0eaeA76a64F174a16F3baeF1dCAAC23c33c886.
The books
live
Gold (tXAU) and Apple (tAAPL) are listed against tUSDC, with series opened around thirty days out. The first offers were written on-chain before the desk went up, so the shelf was never empty.
The tokens
live
tUSDC, tXAU and tAAPL are mock tokens with an open mint: a faucet, on purpose, testnet only. The desk exposes it as a button, so paper-trading needs nothing but a wallet.
The payment rail
live
tANVIL (0xc70D774D4B879E3A6b5549302A2b9479A4D9fED5) and a PaymentRouter (0x954Baed6caE30166Fe883Fc4AB42837CF46a74F7) route every purchase across the ANVIL book, whether the buyer shows up with ETH, tUSDC or tANVIL; the 30 bps spread on that leg accrues to the treasury, and the route is printed on the ticket, fee included. The venue's books stay quoted in tUSDC: dollar-stable strikes with physical settlement is what keeps Anvil oracle-free, so the token is the payment rail, not the unit of account. The router is periphery, and the venue does not know it exists.
The terminal
live
Sign-in works with a wallet or an email, the desk trades the live on-chain books, and the terminal's status strip shows all three configuration checks green.
Section 11
Roadmap paced by the treasury.
The venue starts where full collateral makes a backstop unnecessary, covered calls and cash-secured puts, and widens toward full options only as fast as the treasury grows into the risk. The milestones are ordered; the pace is set by a balance sheet, not a calendar.
Token launch
ahead
ANVIL graduates from its testnet rehearsal to a real token. The payment rail is already built and exercised: every purchase on the desk crosses the ANVIL book today, so launch is a change of collateral, not of plumbing. From the first live trade, the venue's take starts accruing in the token it runs on.
Treasury accumulation
ahead
Every trade pays the treasury twice: the fee on premium, and the 30 bps spread on the payment leg. Nothing is asked of the treasury early; it compounds quietly while the venue trades, because the size of that balance sheet is what every later step is gated on.
Mainnet launch
ahead
The venue deploys to mainnet with the same eight modules and the same one-directional permission graph that run on the testnet today, hardened by everything the testnet surfaces between now and then, and opens with the instruments that need no backstop at all: covered calls and cash-secured puts.
Mobile app
ahead
The terminal in your pocket: the same desk, the same books, sized for a phone. Covered calls are a check-twice-a-day position, which is exactly what a phone is for.
The instrument ladder
Every rung keeps the founding rule, collateral before the contract exists, and each one asks a little more of the treasury than the last. The first rung asks nothing, which is why it is the one trading today.
Covered calls & cash-secured puts
live
The launch set, and the only instruments that need zero treasury: the writer escrows the full underlying (calls) or the full strike in quote (puts) before the offer exists, so the worst case is already funded. This is the venue as it runs today.
Spreads
ahead
Two fully-backed legs netted into one position: a written call collateralized by a held call instead of by the underlying. The escrow math gets richer but the backing rule survives, and the treasury only underwrites the gap between the strikes, a bounded, knowable number per position.
Full options
ahead
Undercollateralized writing, margin against a portfolio rather than a position. This is the stage everything else exists to reach, and the stage that cannot be rushed: a venue with no liquidation price can only offer it when the treasury is large enough to be the buyer of last resort on the tail it creates. The treasury's size sets the pace, which is why it accumulates first.
The flywheel
The venue’s take is two small tolls on activity: the fee on premium (FeeController, capped by an immutable constant) and the 30 bps spread on the payment leg every desk purchase crosses. Neither requires the treasury to trade, predict, or hold risk; both scale with nothing but usage. That is the whole engine.
01
The venue trades
Writers escrow collateral and list; buyers pay premium. Every fill routes the fee on premium to the treasury, and every purchase crosses the ANVIL book on the payment rail, where the 30 bps spread on that leg accrues to the treasury too.
02
The treasury grows
Two streams, both proportional to volume, neither dependent on the treasury taking risk. Part of the take accrues in ANVIL itself, so venue usage is structural demand for the token rather than a story about one.
03
The balance sheet unlocks capacity
Position limits in the risk tiers are sized so the worst plausible loss on a single series costs the same share of the treasury whichever asset it is. A larger treasury therefore means larger limits, more listed markets, and eventually the next rung of the instrument ladder.
04
Capacity attracts flow
Deeper books, bigger size and richer instruments bring more writers and buyers, whose trades are step one. The loop closes; each turn is funded by the last one.
The order of operations matters. A venue that sold undercollateralized options first would be borrowing against a treasury it had not earned; this one sells the instruments that need no backstop, lets the tolls compound, and buys each new capability with retained earnings. Usage funds capacity, capacity earns usage, and the treasury is the flywheel’s bearing: everything turns around how much stands behind the venue.
Section 12
Precedent each piece proven somewhere.
Anvil is a new combination of old, proven parts. The strategy is the most-validated options product in finance, the collateral model ran at nine figures on-chain, and the settlement mechanic has a working proof of concept. What has not been proven is the three together; that is the bet, and it should be stated as one.
The strategy: covered calls at mass-market scale
Covered calls are the oldest conservative options strategy there is, and packaged “buy-write” products are enormous in traditional finance: JPMorgan’s JEPI grew into one of the largest actively managed ETFs doing essentially what an Anvil writer does, and Global X’s QYLD built roughly eight billion dollars doing it mechanically. “Own the asset, sell the upside, collect income” is demonstrated mass-market demand, not a thesis.
The collateral model: the DOV wave
The 2021 DeFi Option Vault generation: Ribbon Finance (peaked above $300M TVL), Thetanuts, Friktion, Katana, sold fully collateralized covered calls and marketed exactly the property this protocol is named for: full collateralization eliminates liquidations entirely. The writer side of this design is proven at scale. The epilogue is instructive too: vault yields compressed as everyone sold the same expiries, the 2022 bear punished depositors, and Ribbon pivoted into Aevo, a leveraged derivatives exchange; the model this protocol deliberately rejects.
The settlement mechanic: Opyn v1, and oracle-free precedent
Opyn’s first protocol (2020) issued fully collateralized, physically settled options: the holder paid the strike and took the asset, no oracle involved. Structurally it is the closest ancestor of this design, and it worked mechanically; it never found liquidity, and Opyn v2 chased capital efficiency into margin and cash settlement. Panoptic later showed “oracle-free options” can carry a protocol, via a very different mechanism over concentrated-liquidity positions.
A live neighbor
Meraki offers fully collateralized calls and puts on tokenized equities, deployed on the same chain this protocol targets, with one decisive difference: it settles in cash against live oracles. It validates the market while leaving this design its differentiation: physical settlement, no oracle to manipulate, and writer-set premiums instead of oracle marks.
The lesson every predecessor teaches: the writer side is easy to fill and the buyer side is hard. Ribbon sold its entire flow to a handful of market-making firms in weekly auctions; organic buyers never materialized on-chain. This design’s structural answer is a buyer-facing chain with real availability and writer-named prices rather than auctions dumping flow, but “who buys the calls” is the question the whole graveyard says to obsess over.
-Proof of concept for the mechanic: Opyn v1 (2020), fully collateralized, physically settled, oracle-free.
-Proof of the collateral model at scale: Ribbon and the DOV wave, $300M+ TVL, “no liquidations” as the headline.
-Proof of the strategy at mass-market scale: JEPI, QYLD and the buy-write ETF complex.
-Unclaimed territory: physically settled, oracle-free, writer-priced, in one venue.
Section 13
Backing and what it does not buy.
Anvil has raised a $500,000 pre-seed round led by Lightspeed, with participation from Blockwall and a group of angel investors.
Round
Pre-seed
Raised
$500,000
Lead
Lightspeed
Participation
Blockwall · angels
Funding is not an audit. Nothing in this section makes the contracts any safer than they were before the round closed.
That distinction is worth being explicit about, because “backed by” is routinely read as diligence on the code. It is not. The investors above bought equity in the company building Anvil. They did not review the contracts, they do not warrant them, and no amount of capital on the balance sheet makes an unaudited protocol safe to put money into.
The operative statement of risk stays simple: the tests pass and a testnet deployment exists, but there has been no audit and nothing of value has ever touched the contracts. Read this section as who is paying for the time it takes to change that.