MarenDocs
The product

Borrow

Deposit, borrow, repay, and withdraw against Bitcoin, with health you can read.

On this page
  1. The four actions
  2. The 180% floor, and why it is not 110%
  3. Reading health
  4. What MUSD does that a ported frontend gets wrong

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.

Note

The raw getter and the slider disagree on purpose, and the slider is the binding one. The clients no longer quote maxBorrowable() as a borrow ceiling. borrowCeiling() in packages/sdk/src/borrow.ts takes the smallest of three bounds: the user's own ratio headroom; Mezo's maxBorrowingCapacity less the whole shared trove's debt — the figure that includes the 200 MUSD gas compensation and accrued interest — grossed up for the 0.1% origination fee; and the pooled trove's own headroom against the 180% floor. The web and iOS apps both clamp the slider to that number and name which bound is binding. The earlier defect, where a quoted maximum could exceed the true limit and revert, is closed.

MarenVault.maxBorrowable() itself is still pool-blind, by design, and its own docstring says so (MarenVault.sol:1106-1116): it answers only how much more MUSD this user could owe before their own share hits the 180% floor, and it deliberately ignores both the trove-level ICR check and Mezo's per-trove maxBorrowingCapacity, either of which may bind first. A reviewer calling that getter directly will therefore read a higher number than any client will ever offer. That is the getter being narrow, not the app being wrong.

The four actions

Deposit

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.

Borrow

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.

Repay

Return MUSD to reduce your debt. Repaying restores borrowing headroom and moves your collateral ratio up.

Withdraw

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.

Info

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.

Warning

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.