Borrow
Deposit, borrow, repay, and withdraw against Bitcoin, with health you can read.
Borrowing is the core mechanic underneath the money app: deposit BTC, draw MUSD against it, and get all your Bitcoin back when you repay. Health is shown as a plain number with a plain meaning.
Known gap, pre-audit, and it is the one place an offered action can still revert. The borrow
ceiling is derived from the right getter with the wrong subtrahend: it subtracts the user's own
drawn principal, where Mezo checks maxBorrowingCapacity >= netDebtChange + getTroveDebt(trove) —
a figure that includes the 200 MUSD gas compensation and accrued interest, and on the pooled route
belongs to the whole shared trove rather than to one user. The 0.1% origination fee gross-up is
missing too. Measured on matsnet on 2026-09-07: maxBorrowable() quoted 1,556.98 MUSD for a
position whose true limit simulated at 834.68 MUSD. The fix is to carry getTroveDebt()
through and gross up for the fee. Until it lands, an amount near the top of the slider can revert.
The four actions
Add native BTC as collateral, from 0.001 BTC in the pooled vault. Collateral is passed as
msg.value, so there is no ERC-20 approval step for it. Below the minimum, deposit
reverts with DepositBelowMinimum.
Draw MUSD against your collateral. The borrow slider is bounded by
preflight, and the real per-trove ceiling is
TroveManager.getTroveMaxBorrowingCapacity(), not an ICR-derived number.
Return MUSD to reduce your debt. Repaying restores borrowing headroom and moves your collateral ratio up.
Take Bitcoin back out, down to zero once your debt is repaid. Exits stay open even when the vault halts new entries.
The 180% floor, and why it is not 110%
The MUSD protocol liquidates a trove at a 110% collateral ratio. Maren refuses to let you borrow below 180%, warns hard below 160%, and recommends a 250% target. The buffer is the product: at the recommended 250%, Bitcoin must roughly halve before a position approaches Maren's internal threshold.
These are two different numbers doing two different jobs. MAREN_POLICY is what Maren
recommends. MAREN_VAULT is what the deployed contract enforces before it reverts. They
currently agree at 180%, and the duplication is deliberate: if governance ever moved one,
silently reusing the other would put a slider's hard stop on the wrong side of a revert.
Reading health
Health is shown as how far Bitcoin can fall before anything is at risk, computed by the SDK so every client shows the same number. The math rounds in one direction only: collateral ratios round down, liquidation prices round up, so a position can never display as safer than it is.
import { assessHealth } from '@maren/sdk';
const health = assessHealth(collateralWei, debtWei, priceWei);
// {
// status: 'healthy' | 'caution' | 'warning' | 'danger' | 'liquidatable' | 'none',
// collateralRatio, // rounds DOWN, so 179.99% never shows as 180%
// liquidationPrice, // rounds UP, a low quote would cost a user collateral
// priceDropToLiquidationBps, // how far BTC can fall before protocol liquidation
// canBorrow, // true only at or above the 180% floor
// }
With no debt there is no liquidation price at all. An empty account is at no risk, not infinite risk, so the app states that in a sentence rather than showing an empty gauge.
What MUSD does that a ported frontend gets wrong
MUSD is a Liquity v1 descendant, and that is a trap: close enough that a ported frontend compiles, far enough that it fails in production. The borrow path touches several of these.
No slippage parameter on any borrower operation
Liquity's openTrove and adjustTrove take a _maxFeePercentage guard. MUSD's rates are
flat and timelocked, so the parameter is simply gone. A ported ABI encodes an extra
argument and every call reverts on decode.
The borrow limit does not rise when you add collateral
maxBorrowingCapacity is a Mezo-only per-trove ceiling fixed at the price when the trove
was opened. Adding collateral does not raise it; only refinance() does. Always display
getTroveMaxBorrowingCapacity() as the real limit.
Debt is principal plus interest, six values not four
getEntireDebtAndColl returns six values, not Liquity v1's four. Interest must be added to
principal to get total debt; treating principal as the debt understates what you owe.
Interest is 1.00% APR, simple, and fixed for the life of the loan.
Recovery Mode does not move the liquidation threshold
In MUSD the liquidation threshold stays 110% in both normal and Recovery Mode. A ported frontend that widens liquidations in Recovery Mode would tell a user at 130% they are about to be liquidated when they are not. See the conformance audit.
The price feed can revert. PriceFeed.fetchPrice() reverts if the oracle is more than 60
seconds stale, and every borrow, adjust, and repay path can revert with it. Maren has an
explicit "price feed unavailable" state and never renders a stale figure as live. See
Networks.
