Sepolia

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:

  1. 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;
  2. the credit (the short) goes to the lender;
  3. the loan tokens go to the borrower;
  4. 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.

  1. Activate. Babylon's registry calls activateVault; the depositor receives the vault's sats as vaultBTC.
  2. 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.
  3. Repay, strictly before maturity, and never while a collar binds the group. repay(groupId) charges debtOf(groupId) — anyone may pay. The app exercises the group's long and the vaultBTC returns to each depositor, whose lots unbind. reclaim then 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.
  4. 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 by repay. 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, ask of it goes to the keeper (capped by MaturitySettler's schedule) and the rest to the borrower. Each vault stays the borrower's converted lot, so the borrower can buy it back with unconvert until 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.
  5. 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's tbv-redemptions lane is that keeper on Sepolia. Its payouts are simulated there: the claim's txid reads SIM-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 = 0 makes 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 repay would, 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. TbvController does not implement canSweep, so the core refuses sweep here; 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 borrowAgainst reverts (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.

  1. 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.
  2. 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.
  3. Hedge. Each rung's credit is short a put at that rung's strike, and HedgeDesk buys 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.
  4. 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.
  5. 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.