Curator
bivium-curator is the operations layer for an Earn pool: a Worker that harvests, processes epochs, watches its own fills and buys back the put each rung's credit is short. Lifecycle has the cycle in order and Lending has the contracts; this page is about who decides what, and with what authority.
It is the end of the dependency chain — it consumes bivium-protocol-mm as a package exactly as mm consumes the SDK — and it is deliberately the thinnest link in it.
It prices nothing
What credit is worth comes from @bivium/protocol-sdk's pricing/credit: the same arithmetic the
SDK hands every lender and market maker. So the fair value a pool's fills are marked against and the
fair value a lender is shown at the lend door cannot drift apart.
The repository states the reason better than a summary would: a pricing model private to this Worker would be exactly the kind of second opinion the stack's shared-tooling principle rules out. The quoting is mm's too — the put writer's prices, sizes and venue hedging come from there, not from a copy here.
Mandate and policy
The single most useful distinction in the whole Earn design, and the one to get right before reading anything else about it.
| Where | Who sets it | What it does | |
|---|---|---|---|
| Mandate | on-chain, in the module | the curator, behind a timelock | binds: the yield floor, the cap on unhedged face, premium and off-chain limits, rung caps, the buffer |
| Policy | off-chain, in this Worker | the curator, freely | decides: the opportunity cost fills are valued at, the IV band that fails safe, when markouts pause, how far above the venue's cost a put may be bought, what the cover aims at |
A depositor is protected by the first column, not the second. That is why loosening anything in it waits out a timelock — long enough to redeem first — while tightening takes effect at once (the rule in full). The second column is the curator exercising judgement inside those bounds, and changing it needs no one's permission because it cannot exceed them.
The guardian key can only pause
The Worker holds the module's guardian key, and that key's authority is deliberately almost
nothing: it can setPaused(true), and it pays gas for permissionless calls. It cannot unpause —
only the curator can — and it cannot move funds.
So the worst an automated run can do, including on bad data or a bug, is stop the pool. Restarting it takes a human with a different key.
The run
Behind a single-flight Durable Object that also holds the markout cursor, each run does four things in order:
- Harvest — every matured rung, live puts first, and close lapsed off-chain settlement windows so their collateral goes to the Dutch book. Ordering matters: the puts are what turn a default into face, so they are exercised before anything is written off.
- Epochs — process a due epoch's deposits and redemptions.
- Markout — scan
Boughtevents from the cursor and value each fill once, after a horizon, asface · fair − cost. When the fills of the trailing window sum below a loss threshold, pause. This is the pool watching its own quoting rather than trusting it. - Put hedge, then publishing the writer's asks.
A run can be triggered by hand over an authenticated endpoint, and the Worker reports the lane's status on its root path.
Why a put, and not the Dutch book
A rung's credit is short a put on the collateral at the rung's strike: when a borrower walks away
the pool receives collateral instead of face. The CollateralDutchBook can sell that collateral,
but only down to a floor — and in a crash below the floor, not at all.
Selling the collateral is disposal, not protection. The protection is the put itself, bought back, and that is what a depositor in an Earn pool is paying the curator for. A hedged default returns face.
How the cover is bought
Each run, for every unmatured rung:
- Target — cover the collateral the pool's credit would actually deliver, or, for a mandate with no room for unhedged face, the rung's whole face cap before it lends at all.
- Ceiling — the venue's executable cost of the same put right now, taken from the order book rather than from a mark, plus a tolerance. The tolerance must clear the writer's own edge plus one tick-grid step, and the lane refuses to start if it does not — a ceiling that cannot accommodate the writer's own ask would silently never buy anything.
- Market first — take other makers' asks for that put series from the relayer, cheapest first, each at or under the ceiling, after checking they can actually trade. The writer's own posted asks are skipped, because its fresh quote follows. Asks that could not be taken are reported with a count per reason.
- Writer — quote mm's writer for the cover still missing and take it with
buyPut, passing the ceiling asmaxCost. The desk re-checks series, window, strike, the rung's cap, the mandate's premium limit, the buffer and the pause on-chain, so the Worker's arithmetic is never the last word.
The put is written by mm, whose hedge lane is built to offset it back-to-back on Deribit, but the pool's protection does not depend on that: the put it bought is a fully collateralised bivium-protocol series, so it holds whatever later happens to the writer or its venue account. External liquidity, internal collateral.
That collateral is also the cost. A fully collateralised put locks its whole strike in the loan token, so its writer must charge for the capital, and an on-chain put struggles to match a venue's. At scale the cover is expected to come mostly through the desk's off-chain path — a put bought on a venue, attested to the desk, and settled by buying the delivered collateral at the strike — with on-chain puts kept for what a venue cannot cover (README).
The limit worth knowing
The Worker cannot see mm's hedge state, so it cannot stop buying when the writer is failing to hedge what it sells. The pool stays protected either way — its puts are collateralised on-chain — but the writer's own book is then naked, and mm's hedge alerts, not this Worker, are what catch that. Two roles, two monitors, and neither covers the other.
Three more, by design: a hedged rung must be harvested inside the put's window, so the cron is not optional; cover rounds down to whole venue lots; and cover left after early repayment stays with the rung until harvest drops it.
The mark, and failing safe
Valuing fills needs a view of the collateral. Three sources behind one interface: a venue surface (Deribit or Derive) with an explicit pair feed, or the pair's own pool.
Two details are the interesting ones:
poolderives the pool id from the module's own two tokens, so no configuration value can point the mark at a different market's price. A wrong fee tier then fails loudly as an uninitialised pool instead of quietly pricing the wrong thing.- Its σ is flat across strike and expiry, a measurement taken offline and pasted into the config. The repository's framing is worth keeping: one measurement is what a price history gives, and it does not pretend to a smile it cannot see. Walking a price history is thousands of requests and no business of a ten-minute cron.
A venue source requires an explicit token-to-underlying mapping, and that mapping is an operator assertion that the collateral tracks the underlying — not proof of it. Wrapped-token basis risk remains, and the loan token is never silently equated with USD.
Fail safe. If the mark is unavailable — venue unreachable, stale or mismatched pair data, an uninitialised pool — or the implied volatility falls outside its configured band, no fill is valued: they wait, unmarked, and nothing pauses on bad data. That direction is deliberate. The action this Worker can take is to stop the pool, so bad data must not be able to trigger it.
This is the same discipline as the price boundary: the mark is a reference, not an oracle and not an executable hedge quote, and there is no automatic fallback or cross-venue mixing.
One Worker per pool, and where state lives
The code is one thing; the deployments are several. Each pool gets its own Worker from its own
wrangler.*.toml, with its own Durable Object and its own module address — same code, separate
cadence and separate single-flight. A test parses each file's [vars] through the lanes' real
config parsers, so a broken deployment config fails CI rather than a cron.
The Cloudflare egress problem splits this further. A lane whose venue refuses Cloudflare runs the same handler as a node process on a host with its own egress, and its Worker stays deployed with no cron. But a cron job on such a host has no disk that outlives the run, and the markout monitor needs its scan cursor and marked fills to survive — so a separate state Worker holds them, keyed by chain and module. The lane is off Cloudflare; its memory is not.
A pool on TBV markets needs no code of its own, only its own file. Its rungs read through
seriesOf, which TbvController shares; its collateral is TBVBTC, so the mark is the spot service's
TBVBTC/USDC pair and the puts are TBVBTC puts at each rung's strike; and harvest goes through the TBV
app inside the module (Lending).
Publishing the writer's asks
Optionally, each run ends by quoting every listing in the writer's config and posting the signed asks to a frontend relayer, so any taker can buy them. Two consequences of the ordering are worth planning for: within a run the pool hedges first and publishes second, so the pool takes what it needs before the public does — but across runs a public fill can consume collateral a later pool hedge wanted, so the writer's allowance has to be sized for both. Asks signed in one quote window share an Exchange group, so re-posting within a window cannot sell the same collateral twice, and every public fill is the same maker's, so mm's hedge loop covers them too.
Vendored from bivium-protocol-doc adde07689a26, synced 2026-10-27. Where this and a repository disagree about that repository, the repository wins.