Lifecycle
Two lanes run on the same contracts: a loan, and the Earn pool that is one way to be the lender in it. Both are told here end to end, naming the repository responsible at each step. Lending has the contracts these flows run through and why they are shaped that way.
A loan
1. The market exists
A lending market is a set of terms — collateral, loan token, debt D per whole collateral token, maturity
— registered once and immutably with LendingController (bivium-lending). Registration is
permissionless. LendingSeries maps the terms onto the series fields; the market id is the series
id, so the same terms always name the same market and there is nothing to fragment.
2. A lender quotes
A market maker (bivium-protocol-mm) prices the credit using @bivium/protocol-sdk's pricing/ —
the credit is short a put at the strike, and that is what is being valued. It reads the collateral's
price from the pair feed (bivium-protocol-spot) rather than from the pool, signs an Offer to buy
credit, and publishes it.
The maker chooses how the fill will be funded, and the choice is in the offer: a wallet pull, a funding account, or an Earn pool's callback. The SDK owns the encoding of all three.
3. The borrower borrows
The borrower takes the lender's bid through Exchange.fill (bivium-protocol), usually via the web
app (bivium-protocol-frontend). In one transaction, inside one core session:
- the borrower's collateral is locked and the series mints — the Exchange's mint-on-shortfall path, so the borrower never has to hold the credit it is about to sell;
- the credit (the short) goes to the lender;
- the loan tokens go to the borrower;
- the borrower keeps the long — the right to repay D and take the collateral back.
LendingController is asked to approve the mint and every recipient. If the market has an
AllowlistGate, that is where canBorrow and canLend are checked.
4. Before maturity
The borrower may repay at any time, because exerciseStart = 0: Router.exercise pays D and
releases the collateral. Repayment in whole lots, or a holder's entire remaining balance. Repayment
is never gated, and anyone may repay on a borrower's behalf.
Either side may also trade out. The credit is a fungible ERC-6909 token, so selling it is the same
Exchange.fill as buying it was — secondary and primary are one path.
A borrower who wants cheaper financing can sell a call against their long through CollarRouter
(bivium-option): one transaction locks the lending long into a call series, sells that call into a
market maker's bid, and pays the premium to the borrower, who keeps the call's short. On a gated
market the router must be one of the controller's intermediaries, because it holds the long
transiently.
The bid comes from bivium-protocol-mm, which bids for calls on the lending markets it is configured for. The window is the one the frontend lists calls on, so borrowers' asks and these bids meet on the same series. The price is the bid of a same-strike Deribit call that expires no later than the window's close, which can never be worth more. mm also exercises the calls it bought, in their window, when the collateral beats the strike. The relayer has to accept a bid on a market no one has borrowed on yet, because the first borrower borrows and sells the call in one batch. The core has no series for such a market, so the relayer reads what the call locks from the lending controller's registration instead.
The same writer sells those calls back, so a collared loan can still be repaid early. It offers every
call it holds on the configured markets from inventory only (reduceOnly, never minting), priced at
the ask of a same-strike Deribit call that expires at or after the window's close, which can never
be worth less. The relayer serves these resale asks under the same locks= index as the bids, and the
frontend's Portfolio fills one, burns the call with the borrower's short (Core.burnPair) to free
the lending long, and can repay in the same run (Lending).
5. Maturity
The window closes at exerciseEnd. Each unit of credit resolves to exactly one of two things:
- repaid — the loan tokens the borrower paid in; or
- delivered — the collateral that was never redeemed.
The lender takes whichever applies with Core.collect (accrued strike) and Core.redeem (the share
of still-locked collateral). A holder who does nothing can still be served by MaturitySettler,
which lets a keeper exercise in the final window on a Dutch schedule bounded by the holder's own
floor — paid out of the freed collateral, with no custody at any point. The holder arms a position
in the frontend, by default when borrowing or buying a protective put. bivium-protocol-mm is the
keeper: it asks only the strike's value at spot plus a margin, and it leaves out-of-the-money
positions alone. FlashExerciser turns an
in-the-money exercise into cash through a swap adapter without an oracle.
When the last collateral leaves, the generation closes and the series is reusable for new mints.
A TBV loan
bivium-protocol-tbv borrows against whole Babylon BTC vaults. The terms are encoded exactly as in bivium-lending, so to a lender, a lens or a market maker a TBV market reads like any other market. What differs is the collateral and who may touch it.
The one rule: the collateral is vaultBTC
Babylon's registry has two exits, and the two tokens are its two states:
| vaultBTC | TBVBTC | |
|---|---|---|
| Backed by | one specific vault | the pool of all delivered vaults |
| Belongs to | the depositor | the keeper pool |
| Exit | reclaim → redeemVaultForDepositor, at par, trustless |
redemption book / settleDelivered → redeemVaultForAVK |
| Transfers | soulbound to its origin | ordinary ERC-20 |
A live loan locks vaultBTC, never TBVBTC. Locking TBVBTC would hand the vault to the keeper pool for the loan's lifetime, so a keeper could settle it before maturity and the repaying borrower would get a pool claim back instead of their own vault.
The flow
A borrow is a group of whole vaults locked into one lending series, and one bit decides who may redeem their BTC: was the group's long exercised before maturity.
- Activate. Babylon's registry calls
activateVault; the depositor receives the vault's sats as vaultBTC. - Borrow.
borrowAgainst(...)mints the group's exact sats into the series and sells all of the credit into a lender's signed bid, in one core session. Payment goes to the named receiver. - Repay, strictly before maturity, and never while a collar binds the group.
repay(groupId)chargesdebtOf(groupId)— anyone may pay. The app exercises the group's long and the vaultBTC returns to each depositor, whose lots unbind.reclaimthen redeems a vault to the depositor's own BTC key, with no keeper and no counterparty. This is the trustless return, and it is the product. - Auto-repay, if the borrower armed the group (
TbvAutoRepay.setFloor). In its last day any keeper may repay it, and the lenders are paid in full exactly as byrepay. The keeper's fee cannot come out of a vault: a vault is whole, and its vaultBTC can only go back to its depositor. So the app converts the group's vaults instead. Their sats become TBVBTC,askof it goes to the keeper (capped byMaturitySettler's schedule) and the rest to the borrower. Each vault stays the borrower's converted lot, so the borrower can buy it back withunconvertuntil a keeper settles it. This is never worse than a default, which leaves the borrower nothing. Arming is the borrower's consent to the conversion, and the only way any keeper reaches a vault the borrower still owns. Contract: bivium-protocol-tbv. Keeper: bivium-protocol-mm, which acts only when the vaults are worth more than the debt. Arming: the frontend's borrow panel and Portfolio. - Default. At maturity the long lapses.
markDelivered(groupId)records it, permissionlessly, and the lots become Delivered. Lenders redeem their credit through the app and receive the collateral as TBVBTC; the repaid loan token accrued to their credit is collected on the core as usual. A keeper in the vault's peg-in set then settles a Delivered vault by burning that much TBVBTC. The same keeper fills the redemption book, front-paying BTC to TBVBTC holders who exit. bivium-protocol-mm'stbv-redemptionslane is that keeper on Sepolia. Its payouts are simulated there: the claim's txid readsSIM-NO-BTC-PAID.
The core admits only one of the two outcomes, because exercise is allowed only before
exerciseEnd and redeem only from then on. No block admits both, and no keeper can touch a
Reserved vault. Auto-repay does not break this: it repays first, so the vaults leave the loan
already repaid.
A depositor who is offline can instead sign a BorrowIntent (EIP-712) naming the vaults, the market,
a lowest price, a receiver, a nonce and a deadline; any lender fills it later. The depositor grants
no allowance — their vaultBTC stays in their wallet until the fill, and the app moves it with
VaultBTC.pull, a power the app alone has and no broader than its existing ability to mint and burn
any holder's vaultBTC. Cancelling spends the nonce; moving the vaults meanwhile makes the fill fail.
Calls on vaults
A depositor can also sell the upside of its vaults. TbvCallBook, deployed by the app and the only
caller of its call hooks, takes two kinds of call: a covered call on free vaults, and a
collar on one of the depositor's loan groups — the borrower selling the loan's upside to cut its
financing cost. The writer signs a CallOffer (EIP-712 under the book's chain id and address).
Either it goes offline and any buyer fills it with buyCall, paying the premium to the writer, or
the writer sells it at once into a buyer's signed bid on TbvCallBids (below).
- A call is a record, never a core token. Calls with the same terms on different vaults would otherwise be one fungible series, and a partial exercise would draw on every writer's vaultBTC pro rata — no vault whole, and no way to say whose was exercised. As records, each exercise moves exactly its own vaults.
- Exercise is whole, against the strike, inside the call's window. Each call carries its own
window
[exerciseStart, expiry), which the writer sets in the offer:exerciseStart = 0makes it American (exercisable from the sale), a later start makes it European. The book enforces the window (NotYetExercisable,CallExpired) and refuses an empty one. The holder pays the strike in the same transaction; the call's vaults become Delivered and the holder receives their sats as TBVBTC. That is the only trustless way to hand a vault to anyone but its depositor, because its BTC can only go to the depositor's key or a keeper's.- Covered: the strike goes to the writer.
- Collar: struck in the loan token at or above the group's debt, and expiring before the
group's maturity. The app repays the group out of the strike exactly as
repaywould, locks the vaultBTC for the holder instead of returning it, and pays the borrower the strike above the debt.
- European is the default, and the contract does not set it. An American collar lets its buyer end the borrower's loan, and take its vaults, at any moment of a price spike. The borrower is paid the strike either way, but loses the vault and the financing it chose until the expiry. The buyers a collar is designed for hedge on Deribit, whose options are European, so early exercise gains them nothing. The frontend therefore sells European calls by default: the expiry is 08:00 UTC on a Deribit date and the window is the last day before it. A European offer must close before its window opens. Otherwise a buyer could buy and exercise in one transaction, against a premium priced earlier.
- While a call binds, from its sale to its expiry whatever its window, its vaults cannot be borrowed against, reclaimed or converted
(
StillBound), and a collared group cannot be repaid (CallOpen). It ends when the holder releases it, the writer buys it back, or the expiry passes — after which it binds nothing, whether or not anyone releases it.
The writer sells into a signed bid, as the taker. The book holds only asks — a writer's signed
CallOffer — so on its own a writer could only rest an offer and wait for a buyer. TbvCallBids
(bivium-protocol-tbv), deployed beside the book without changing it or the app, adds the other
side. A buyer signs a CallBid (EIP-712 under that contract): per whole BTC, the most it strikes at
and the most it pays, the exact window, the largest call it buys, a group whose bids share one cap
of sats, and a deadline. The writer calls sell(bid, bidSignature, offer, offerSignature). On the
call's sats, read from the app, the offer's strike and premium must be no more than the bid's,
rounded down. The contract pulls the writer's premium from the bidder, buys the call with buyCall,
and transfers it to the bidder, in one transaction. Only the writer may sell — its offer may be
resting for any buyer, and a third party pairing it with a bid would choose the buyer for it — and
cancel(group) withdraws every bid in a group. The book and the app check the call itself as for any
buyCall.
So a borrower sells its collar in the same batch as its borrow. It signs the offer first, on the loan
group the borrow is about to create (nextGroupId), then sends borrowAgainst and sell together,
and the premium lands with the loan. A Safe runs the batch atomically. An ordinary wallet sends two
transactions, so the borrow can land without the sale (the bid closed, or its cap was taken); the
loan then stands, and the call is offered instead.
bivium-protocol-mm's tbv-call-bids lane is the bidder. It signs a sheet of what it pays, per
whole BTC, for European calls at the strikes Deribit lists for its 08:00 expiries. Each row is also a
CallBid, and the sheet is posted to the relayer's /api/call-bids, which takes sheets only from
bidders the deployment lists. The lane still buys resting offers on /api/calls asking no more than
its bid (the fallback above, and offers at other prices). It exercises the calls it holds inside
their windows when the vaults, as TBVBTC, beat the strike. American calls get no bid, because no
Deribit option matches them. Pricing and caps are in Tooling.
So a collar narrows what the trustless return promises: the vault comes back to the depositor's key unless the depositor sold its upside and the holder exercised. Then the borrower is paid the strike less the debt, and the vault goes to the keeper pool.
Puts need none of this. A put's deliverable can be TBVBTC, an ordinary fungible ERC-20, so a put is an ordinary pooled series on the core. A lender hedging a default delivers the TBVBTC its redemption pays; a depositor delivers a vault by converting it first, reversibly until a keeper settles it.
What holds it together
The repo states seven invariants and checks them under random interleavings of every action —
writing, exercising and releasing calls included — with fail_on_revert = true. Among them: vaultBTC
totals match Reserved plus Delivered lots, the escrow's vaultBTC equals TBVBTC's total supply, the
app's long balance equals open groups' units before maturity, a vault is redeemed at the registry
exactly once, and the app holds no vaultBTC between calls.
Several limits are worth carrying off the page, because they constrain deployment rather than code:
- A repaid market lends again; a TBV market is never swept. On a core with generations, the
repay that takes a market's last vault out closes its generation, so the market takes new loans.
TbvControllerdoes not implementcanSweep, so the core refusessweephere; whole-vault loans keep a market above the core's share floor in practice (Architecture). - The time fee must be off or exempted. A fee would be owed in vaultBTC beyond the group's own
sats, so
borrowAgainstreverts (FeeNotSupported) rather than guess. - Loan tokens should be standard ERC-20s. Market registration is permissionless, and a loan token that calls back during a lender's redemption could redirect that lender's own payout into the app and strand it there.
- An Earn pool lends here only through the app. Its signed bid is filled by
borrowAgainst, it redeems through the app, and a default reaches it as TBVBTC, which its puts are written on (Lending).
There is also a known accounting edge: delivered collateral reaches the escrow only as lenders
redeem, so credit that is never redeemed leaves the last Delivered lot a few sats short for
settleDelivered and unconvert.
The Earn pool
The Earn pool (InvestModule in bivium-lending, curated by bivium-curator) is a
single-currency way to be the lender above: a depositor holds one transferable loan-token share and
never touches collateral. One pool per currency — a dollar pool lends dollars against BTC, a BTC
pool lends BTC against dollars, and the second is the first read from the other side.
Lending has the contracts and the rules they enforce, and Curator has who operates them and with what authority. This is the cycle, in order.
- List. The curator lists rungs — lending markets, each with a face cap. A pool built with the TBV app as its redeemer lists TBV markets instead, and borrowers sell it credit through the app.
- Lend. The module buys credit on those rungs, only as a maker, through its own ratified Exchange bids. It funds each buy at the moment of the fill, through the Exchange's funding callback, rather than holding a standing approval — and optionally out of a yield source, though no deployed module has one (why). Every fill has to pass the module's on-chain mandate before it can happen at all, because the module is its own ratifier.
- Hedge. Each rung's credit is short a put at that rung's strike, and
HedgeDeskbuys the put back — on-chain, or off-chain under custody against an attestation. The rule is hedge first, lend second. This is what a depositor is paying the curator for: a hedged default returns face, instead of leaving the pool holding collateral to sell. - Harvest once a rung matures, which anyone may do (on TBV markets, through the app, the
collateral arriving as TBVBTC): repayments go to idle, hedged
collateral settles through the desk, and whatever was not covered falls through to
CollateralDutchBook. Depositors never receive collateral. - Settle. Deposits and redemptions clear together once per epoch — never instantly, and never while a matured rung is still unharvested.
Off-chain, bivium-curator runs the markout monitor, the harvest and epoch cron, and the hedge purchases, consuming bivium-protocol-mm as a library.
Vendored from bivium-protocol-doc adde07689a26, synced 2026-10-27. Where this and a repository disagree about that repository, the repository wins.