Unimodular · protocol status · July 2026

Unimod — where the protocol stands

A non-custodial AMM + lending protocol: a V4-style singleton AMM with pluggable pricing models, and Morpho-style isolated lending that takes LP shares as collateral — with no price oracle anywhere. This is the current status, the product decisions behind the UX paths, and the first look at the interface.

01 Contract status

Feature-complete pre-release, verified end-to-end on anvil

Everything in the core product works today against a local deployment: pools, swaps, lending, and all three atomic flash flows. What remains before mainnet is hardening, not construction.

2
core contracts — Unimod (AMM) + UnimodLending, independently deployable
~36
Foundry test suites
~220
test functions: unit / fuzz / integration
3
stateful invariant suites — share accounting, LP conservation, bad debt

02 Architecture

Two contracts, cleanly split — satellites plug in

Unimod — AMM

Singleton, flash accounting (unlock → deltas → settle), ERC6909 LP shares. n-asset Star pools (LP-as-numeraire, ≤16 assets) and Delta pools (pairwise price matrix, ≤8). Knows nothing about lending.

UnimodLending

Morpho-Blue-style isolated / permissioned markets. Own ERC20 reserves; collateral is Unimod LP shares pulled over ERC6909. No flash accounting — plain transfers, checks-effects-interactions.

StarSpm / DeltaSpm · Irm

Pluggable pricing models own the whole pricing pipeline per pool type; adaptive-curve IRM sets per-market rates. Both swappable by governance without touching the core.

Router · Lens · Permit2

Router is the single user entry point and implements every callback (unlock, borrow, liquidate, withdraw-collateral). Lens answers every read/preview off-chain. Permit2 is the only ERC20 pull surface.

The split is deliberate: the AMM stays reviewable on its own, and lending failure modes cannot reach into swap accounting. The two only meet through public surfaces (LP share transfers, pool balance reads).

Who calls what

every action enters through the Router · solid = direct call · dashed = callback · from web3/ui-flows.md

Deliberately piggy-backing on audited code

The reference implementations are vendored in reference/ and mirrored closely on purpose: the audit surface shrinks to our deltas from code that has already survived review, rather than a from-scratch codebase.

03 Users & UX paths

Four roles, three signatures to onboard, one click per strategy

The product decision of the last cycle: every multi-step DeFi maneuver a user actually wants — leverage up, unwind, liquidate — is one Router transaction, with the setup cost front-loaded into three one-time approvals.

RoleWhat they doTheir path
SwapperTrades between pool assets, pays the swap feeswapExactIn / swapExactOut — quotes via Lens.previewSwap*
Liquidity providerSupplies pool assets, earns swap fees, receives ERC6909 LP sharesaddLiquidity (proportional), zapIn/zapOut on Star pools
LenderSupplies ERC20s to isolated markets, earns borrow interestsupply / withdraw (+ shares-mode variants)
Borrower / leveragerPosts LP shares as collateral, borrows per-assetdepositCollateral, borrow — or flashLeverage in one tx (≈4× max, balanced basket)
LiquidatorCloses unhealthy positions for a ~2% bonusliquidate — zero capital, zero approvals needed

Onboarding — three one-time approvals, then never again

  1. Permit2token.approve(permit2) + permit2.approve(token, router, …). One approval surface covers every token; the Router never reads token allowances itself.
  2. LP shares — ERC6909 setOperator(router), for posting collateral.
  3. Lending authorizationsetAuthorization(router), or signed EIP-712 and bundled via multicall with the first lending action, so onboarding costs zero extra transactions.

04 The flows, drawn

Each maneuver is one transaction across four actors

The same four lanes appear in every figure — User, Router, Unimod (AMM), Lending. Reading left to right is reading the transaction. Hover any step or cell for detail.

What each entry point touches

Router entry points × protocol surfaces · = fires a callback into the Router

The three flash composites are the only flows that cross the whole protocol — and they are exactly the ones bundled into a single click.

One-time approvals before first action

on-chain approvals per role (per token) · lending auth rides along as a signature, not a transaction

0 · 1 · 2 · 3 approvals →

A liquidator needs zero approvals and zero capital — the entire close is funded by the seized LP inside the transaction.

05 Protocol mechanics

The choices that make it oracle-free

One constraint drives most of the design: lending never reads a price. Health, liquidation, and collateral valuation all derive from pool balances the borrower can actually redeem — so manipulating the AMM's price cannot touch anyone's loan.

Per-asset, price-free health

For each borrowed asset i: borrowedᵢ ≤ ltv·s·bᵢ — the borrower's LP share s times the pool's balance bᵢ is their redeemable claim.

Why — no oracle to manipulate, no cross-asset pricing assumption. A swap can tilt balances, but the liquidity guard + buffer bound it, and any liquidation it forces is in-kind (locked in by TiltToLiquidate.t.sol).

Borrow buffer: 5%

Borrow and collateral-withdraw check against lltv − 0.05; only the liquidation trigger uses full lltv (default 0.80).

Why — a fresh max-borrower shouldn't sit on the liquidation edge; the gap absorbs normal balance drift.

Gentler liquidations than Morpho

Morpho's incentive formula, but cursor 0.1 (vs 0.3) and a 10% cap (vs 15%) → ~2% bonus at the default lltv.

Why — liquidation here needs zero capital and zero approvals, so keepers don't need to be paid as much; borrowers keep more.

In-kind liquidation returns

Seized LP is burned proportionally; the borrowed-asset slice repays the debt (+bonus), every other slice goes back to the borrower untouched.

Why — swapping the borrower's remainder would require pricing it. In-kind keeps the whole path price-free and caps what a manipulator can extract at the bonus.

Zaps: Star only

zapIn/zapOut exist on Star pools; Delta reverts. Single-asset Delta entry = swap → proportional add, composed by the caller.

Why — Delta's pricing is pairwise; a separate zap primitive would create a second price path that can diverge from swap pricing and open arb. One price path, reviewable math.

Concentration parameter

Replaced the dead "leverage" param. In bps (default 500 = 5%); price multiplier is exp(c·√(v/V)/3) — lower = tighter pricing. maxLiquidityRatio (default 4) bounds post-swap imbalance.

Why — one interpretable knob for liquidity concentration, and the imbalance bound doubles as the lending-side tilt guard.

Fees: LPs first

Swap fees accrue entirely to LPs inside reserves. The protocol's cut (protocolShare) applies to the mint fee only.

Why — keeps swap pricing clean and makes LP returns legible; protocol revenue rides growth, not volume.

SDK split, ABIs one-way

A separate unimod-sdk repo (seeded from web3/examples); ABI codegen and reference docs stay in this repo, pinned to the same SHA as the contracts.

Why — the SDK can iterate at frontend speed while the ABI source of truth stays next to the Solidity it describes. ABIs flow contracts → SDK, never backwards.

No profitable atomic manipulation — shown in silico, locked in on-chain

The oracle-free health rule has exactly one candidate atomic attack: tilt-to-liquidate — buy an asset out of the pool to push a borrower's per-asset claim below their debt, liquidate, pocket the bonus, all in one transaction. Two marimo notebooks (notebooks/tilt_to_liquidate.py, tilt_liquidate_concentration.py) work the economics end-to-end, and a Foundry suite (TiltLiquidateArb.t.sol / TiltToLiquidate.t.sol) runs the identical scenario against the real SPM + lending as ground truth.

The result: pool size cancels out of the profit equation entirely, and with the restore-to-health close factor clamping the repayable amount and in-kind returns capping extraction at the bonus, the attacker's net is ≈ bonus − (1 − attacker_LP_share)·tilt_slippage — negative at every concentration for any realistic LP holder. Without the close factor, a max-borrower could be liquidated in full off a near-free tilt; the notebooks are what surfaced that asymmetry and drove the RCF design.

Residual, stated honestly — an attacker owning nearly the entire pool's LP recovers their own tilt slippage and can profit on a very tight pool; that regime is visible in the notebook sweeps and is a market-curation concern (don't borrow against pools with a dominant LP), not a contract bug.

06 Markets, fees & governance

Two market segments, three thin fee taps

The protocol runs a permissionless segment and a sponsored segment side by side, on the same contracts. Governance surface is deliberately small: curation and fee knobs, never the risk math.

Permissionless · isolated (marketId = 0)

Anyone creates a pool over any registered assets — no approval step. Each pool's lending markets are per-pool, per-asset: risk is fully isolated, one bad pool can never contaminate another. LLTV, IRM and lending fee start at protocol defaults (LLTV 0.80, adaptive-curve IRM) and can be tuned per isolated market by the protocol owner.

One honest asterisk: the asset registry itself is protocol-owner gated (registerAsset) — permissionless means any pool over the registered universe, not arbitrary tokens. A deliberate rug/weird-token filter at the front door.

Sponsored · permissioned (marketId > 0)

Anyone can propose a market (createMarket); it goes live once the protocol owner enables it. From there the sponsor (market owner) curates their venue: whitelist assets, allow Star and/or Delta pools, enable / disable / destroy pools, freeze the market, transfer ownership.

The structural difference from the isolated segment: lending markets are shared across all pools in the market — one supply-side per asset serving the sponsor's whole pool family. That's the venue for a curator who wants deeper, unified lending liquidity behind a vetted asset set.

Where the protocol takes fees — and where it deliberately doesn't

FeeCharged onGoes toProtocol's cut
Swap fee Every swap, per pool 100% to LPs — accrues inside pool reserves Zero. Swaps are deliberately protocol-free to keep pricing competitive
Mint fee LP minting (zaps pay swap/2 + mint) LPs, minus the protocol slice protocolShare of the mint fee, minted as LP shares to feeRecipient
Lending fee Interest accrual, per market Suppliers, minus the protocol slice Per-market fee on accrued interest, minted as supply shares to the lending feeRecipient

The pattern in both taps: the protocol is paid in the same instrument as the participants it sits behind — LP shares on the AMM side, supply shares on the lending side — so protocol revenue is staked to the same pools and markets it's extracted from, and rides growth rather than volume.

07 Interface & identity

Two live explorations

Brand and product are being explored in parallel, in separate deployments — the identity work feeds the product UI, not the other way round.

Brand / design system unimodular.pages.dev

Generative identity built on Conway's Game of Life — gliders, oscillators and still-lifes as visual primitives under the thesis "conserved under transformation." Monochrome wordmark at the core, 15 procedural palettes, live parameter controls (font, palette, noise, intensity, speed). Works offline; deployed from unimod-design on Cloudflare Pages. The background of this page runs the same automaton.

Tentative product UI unimod-fe.vercel.app

First functional pass at the dapp (React/Vite SPA, wallet-connected): pools, swap, supply / borrow / collateral, and positions — tracking the decided tab structure and built against the web3/examples + Lens read patterns. Tentative by design: it exists to pressure-test the UX paths in section 03 before committing to the final frontend.

08 Next

The path to mainnet is hardening + surface