The unified DeFi term sheet
Every prime broker hands you a term sheet. DeFi hands you a contract address. We built a structured termSheet for every lending market and vault we index - one shape, every protocol - and it makes the differences between Aave V4, Midnight, Liquity V2, Frankencoin and LlamaLend legible instead of tribal knowledge.
Ask a prime broker for financing and you get a term sheet: rate, tenor, what happens at maturity, what it costs to get out early, what they can do to you and when. One page, and every desk on the street writes it the same way, so you can put two of them side by side and actually compare.
Ask an onchain lending protocol the same question and you get a contract address.
The information exists — all of it, onchain, in the open. What does not exist is a shape. Aave calls it a liquidation threshold, Liquity calls it an MCR, Frankencoin has no threshold at all because it has no oracle. Morpho Midnight's "interest rate" is not a rate stored anywhere; it is implied by the price you paid. LlamaLend's collateral factor is not a market parameter — it is a number the borrower picks. Every integrator who has shipped more than one protocol has ended up writing the same file: a pile of per-protocol special cases, in application code, that nobody outside the team can read.
So we built the missing shape. Every market we index — and every vault — now carries a structured termSheet, in one schema, describing what you are actually agreeing to.
What it is
A term sheet hangs off a marketUid and has two sides plus an envelope.
The envelope carries what is true regardless of which side you take: the oracle (as a single address wherever one exists), governance and its timelock, utilization, whether positions are cross-margin or isolated, and a coverage block that says explicitly which parts we could fill and which we could not. That last one matters more than it sounds: an absent oracle block must never be read as "this market has no oracle" when the truth is "we have not classified it yet". coverage.notApplicable and coverage.pending are different states, and both are said out loud.
The supply side is what a depositor agrees to. The borrow side is what a borrower agrees to. Markets that only have one — a Stability Pool, a savings vault, a collateral-only asset — simply omit the other, and coverage records why.
Each side ends with an info block: a one-line headline you can put in a table row, a paragraph of prose, and a set of machine-readable tags. The long prose is not repeated per market — hundreds of markets share the same wording — so the market carries a profileId like liquity.cdp@v1 or midnight.orderbook@v1 and the prose is served once. There are 21 such profiles today, and the @v1 suffix is load-bearing: bumping it re-arms the "I have read these terms" acknowledgement in any UI keyed on it.
Vaults are the same problem
The second half of this is that a vault is a term sheet with only a supply side.
That sounds like a simplification and it is the opposite. Everything a depositor needs to know about a savings vault is exactly the vocabulary the borrow side made us invent: is the exit instant, cooling down, queued, or does it need a buyer? Is the share price backed by an onchain invariant or attested by an operator? Does this tranche absorb losses before the other one?
So savings vaults, curated lending vaults, NAV-attested funds, senior and junior tranches and undercollateralised credit pools all get the same object with borrow omitted — vault.savings@v1, vault.nav@v1, vault.tranche-junior@v1, vault.credit@v1. A junior tranche is not a footnote in a description; it is principal.protected: false with a first-loss tag that ranks as critical. A NAV vault is nav-attested, meaning the share price is published rather than proven. A queued exit is exit.mode: 'queued' with the actual cooldown in seconds, next to a liquidity figure that says how much can actually leave today.
One consumer, one renderer, and the deposit side of a vault and the deposit side of a lending market stop being two separate integrations.
Example one: Midnight and Aave V4
Take two markets that both let you borrow a dollar stablecoin against BTC, and look at what the term sheet says about each.
Aave V4 is the shape everyone already has in their head — and its interesting property is one that a naive integration gets wrong. V4 splits the protocol into a Hub holding liquidity and Spokes holding risk configuration, so several spokes can draw on the same asset. The interest-rate model lives on the hub. That means the utilization that sets your rate is drawn / (liquidity + drawn + swept) aggregated across every spoke sharing that asset, not the ratio of the market you happen to be looking at. Compute utilization per-spoke and you get a number that is not merely imprecise, it is meaningless — for a borrow-only spoke it is not even well-formed. So the sheet carries utilization.basis, which says which denominator produced the number, and ships the hub totals alongside it so a rate-impact simulation shifts the right ones.
Everything else about Aave V4 is what the generic model already describes, and that is the point — it needs no protocol-specific adapter at all. Variable rate, maturity.kind: 'perpetual', debtShape: 'accruing', exit.earlyRepay: 'free', liquidation.model: 'repay-seize' with a price trigger, and crossMargin: true against a long acceptedCollateral set. E-modes come through as modes[], each with its own LTV and threshold — on the V3 mainnet market that is 49 of them behind one asset, which is why they are structured data rather than a sentence.
Morpho Midnight produces an object with identical field names and almost nothing in common.
- The rate is fixed, and there is no rate stored anywhere: Midnight is a Base-only order book of zero-coupon units. Interest lives entirely in the entry price — you pay a discount now and owe the face value at maturity.
counterparty.kind: 'orderbook', not'pool'. debtShape: 'static-face'. The debt does not accrue. It is a unit count that sits still.- Being past due IS the liquidation trigger.
maturity.atMaturity: 'liquidatable'andliquidation.trigger: 'price-and-time'. An unrepaid loan can be liquidated after maturity regardless of how over-collateralised it is. No variable-rate intuition prepares anyone for that, which is why the tagtime-liquidationis classified critical and surfaces before a user signs. - Over-repaying reverts.
exit.overRepayReverts: true— auint128underflow with no dedicated error. The repayment must be sized to the exact debt. This is the kind of fact that lives in a Discord message until someone writes a field for it. - Repaying early is
'free'butatMaturityCost: 'face'— neither fee nor rebate, so paying sooner is not a saving, it just gives up the time value. The generic sheet's cheerful "repay any time at no extra cost" would be true and misleading. - On the lend side, the exit is not a withdrawal.
exit.mode: 'market-sale'withpriceRisk: 'market-price': you are selling your units into the bid side, and there may be no bid at any price. Two ongoing fees are charged to the lender, and bad debt is socialised across lenders.
Same schema. Same three lines of consumer code. One market lets you leave whenever you like at the cost of a floating rate; the other locks the rate, locks the date, and will liquidate you for being late on a position that never became unhealthy.
Example two: three protocols that are all "a CDP"
The second example is sharper, because here the protocols are nominally the same category. All three mint or lend a stablecoin against collateral you post, with no depositor on the other side. A category label tells you nothing. The term sheet tells you everything.
| Liquity V2 (BOLD) | Frankencoin (ZCHF) | Curve LlamaLend | |
|---|---|---|---|
| Who sets the rate | You do | Fixed at open, prepaid | Utilization curve |
| Oracle | Price feed | None — by design | Price feed |
| Liquidation model | stability-pool | auction | soft-band |
| Reversible | No | No | Yes |
| Penalty | Two, path-dependent | Challenger reward | Zero (soft) |
| Maturity | Perpetual | Fixed expiry | Perpetual |
| Debt = what you received | Yes | No | Yes |
| Can be taken while healthy | Yes — redemption | Yes — challenge | No |
Liquity V2: the borrower sets the rate, and the rate is a queue position
rate.kind: 'user-set'. Not a number we read — a number the borrower supplies, with userSet.required: true so a borrow form knows the input is mandatory, plus min, max, and a sensible default. Without that flag a UI has no way to know it must ask.
And the choice is not cosmetic, which is the term that surprises people: your collateral can be redeemed at par while your position is perfectly healthy. Any holder of BOLD may exchange it for collateral at face value, and the protocol serves them from the lowest-rate trove on the branch first. No health factor predicts this. It is not a liquidation, and no liquidationLtv describes it.
So redemption gets its own object rather than a footnote, and every field in it answers a question a borrower actually asks:
trigger: 'permissionless-arbitrage'— not a governance action, not a liquidation. Anyone, any time.driver: 'below-peg'— it only pays while BOLD trades under target, which is the entire point: redemptions are the mechanism that pushes it back up.order: 'lowest-rate-first'— the rate you picked IS your position in the queue. Cheapest is first.valueImpact: 'usd-neutral'— the redemption fee stays in your position as extra collateral, so you are roughly whole in dollars. What you lose is exposure, involuntarily, at the worst possible moment.defence: 'Raising your rate moves you back in that queue.'— the one action available.
Liquidation is absorber: 'stability-pool' with two penalties, because a single number cannot express it: one rate when the Stability Pool absorbs the debt (the normal path) and a materially worse one when the pool is empty and the debt is redistributed across the branch's other troves — badDebt: 'redistributed'. Plus a minDebt floor you cannot leave a position below, and a positionModel: 'cdp-id' where the id is a keccak of owner, branch and index.
Frankencoin: there is no oracle
oracle.kind: 'none', and this is not a coverage gap. Frankencoin has no price feed by design. The liquidation price is declared by the position owner and policed by a Dutch-auction challenge game: anyone may challenge a declared price, and the auction decides whether they were right.
The consequences run straight through the data model:
liquidation.model: 'auction', with the challenge period aswindowSecsand the challenger's reward as the penalty.challenge()has no health gate. The trigger ismarketPrice < declaredPrice, independent of how much is borrowed. Repaying does not reduce your challenge risk — only re-declaring the price does. Our health factor here is a borrow-capacity metric, and the universal rule "HF below 1 means liquidatable" must never be applied to it.- Debt is not what you received. Minting withholds a 10–40 %
reserveContributioninto the equity reserve and charges an upfront pro-rata fee. Both arefees[]entries withwhen: 'entry'and an explicit note that you owe the full amount but receive materially less. The generic sheet's "repay any time at no extra cost" is false on three separate counts here, which is exactly why the adapter exists. - Positions expire.
maturity.kind: 'fixed-date'with a real expiration andatMaturity: 'liquidatable'. A protocol filed under "CDP" turns out to sit on the fixed-term seam. - Each position is its own contract, cloned permissionlessly from the market's "original" — so
positionIdMeaningsays so, and a market key is an original position rather than an asset pair.
LlamaLend: the collateral factor is an input, not a parameter
We wrote about LlamaLend's soft liquidation in detail. In term-sheet terms it is the one lender the generic model describes wrongly rather than incompletely:
liquidation.model: 'soft-band',absorber: 'amm', and the field that matters most —reversible: true. Collateral is spread across N price bands inside the market's own AMM and converted gradually as the price enters the range; if the price recovers, the conversion unwinds. "In liquidation" is not terminal, which every other lender's UI implies that it is.penalty: 0. Soft liquidation costs nothing. The hard-liquidation discount still exists, so it appears as a named entry inpenalties[]with an explicit note that it applies only in the terminal case — visible, and not mistaken for the ordinary path.- There is no liquidation price to render, because none exists.
- And the collateral factor is a function of N, which the borrower chooses at open: roughly 0.991 at N=4 against 0.886 at N=50, on the same market. The headline LTV is only true at the default. So the sheet ships the whole
bandLtvcurve plus anopenParameterblock —domain: 4..50, a default, andimmutableAfterOpen: true, because changing N on a live loan means closing and reopening it.
That last field is a small thing that shows what the format is for. openParameter is a generic concept: a choice the borrower makes at open that changes the terms. LlamaLend's band count is the first instance. It will not be the last, and the next one will not need a new consumer.
What the shape buys you
Three things, in order of how much they matter.
Comparison actually works. Rate is not the offer. A 3 % fixed borrow that liquidates you for being one hour late is not comparable to a 5 % floating borrow you can exit in a block, and until the difference is structured, no ranking of the two means anything.
Risk gets classified from data, not from a list. Every tag maps to a severity: critical means you can lose more than the amount at stake, or lose it without doing anything wrong — full-collateral seizure, time-based liquidation, redeemable-while-healthy, first-loss capital, an EOA that can change parameters with no delay. warn means it costs money or blocks you. Nothing else. There is no hand-maintained list of scary markets, so a newly integrated protocol is classified correctly the moment its fields are right — and only critical is ever allowed to gate a signature, because a disclosure that fires on a plain USDC deposit trains exactly the reflex it exists to prevent.
Unknown is a value. The most useful discipline the format imposed on us was refusing to default. A liquidation penalty we could not read used to render as "liquidator takes debt repaid + 0 %" — a confident, wrong number that came from nothing. 0 means no bonus; absent means we do not know. The row disappears instead of lying, and every enum is open (| (string & {})), so a protocol whose liquidation model we have never seen renders as something unrecognised rather than silently as the default.
Getting it
Term sheets are attached to every lending market, every pool row, every leverage pair and every comparable we serve. Endpoints take ?terms=digest (the default — headline, tags and the rollup, cheap enough for a table of hundreds of rows) or ?terms=full, which adds exit terms, liquidation parameters, fees, modes, oracle and governance.
Full reference in the 1delta API docs. If you are integrating lending for the first time, how we make three thousand markets comparable is the companion piece; if you want the protocol-by-protocol detail behind the examples above, we have written up fixed-term lending, order-book lending, CDPs and LlamaLend individually.
The protocols are not going to converge. The description of them can.