Documentation

Everything here describes what the code does, not what it might do. Where a figure cannot be read, the interface says so rather than filling the gap with a guess.

Overview

Sorrel does three things. It wraps a concentrated liquidity position in an ERC-4626 vault so the position becomes a transferable ERC-20 called a Rootshare. It routes orders as signed intents rather than public swaps, so size does not leak before it fills. And it accepts Rootshares as collateral in isolated borrow markets, so the yield underneath a loan keeps accruing while the loan is open.

None of these depend on each other. A Rootshare is useful with nothing else in the system; the borrow markets would work with any collateral that has an oracle.

Quick start

  1. Open the vault screen and connect a wallet.
  2. Pick a vault. The pair, the fee tier and the range are all shown; none of them change after deployment.
  3. Deposit the pool asset, or pay in the stablecoin and let the zap split it.
  4. You now hold rs- shares. Send them, sell them, post them as collateral, or leave them.
  5. Redeem whenever. There is no queue, no notice period and no exit fee.

Vault accounting

A vault is an ERC-4626 vault whose underlying asset is the pool's quote token. Its total assets are the value of the liquidity position plus any uncollected fees, expressed in that asset.

sharePrice = totalAssets() / totalSupply()

deposit(assets)  → shares = assets / sharePrice
redeem(shares)   → assets = shares * sharePrice

Your balance never changes on its own. What changes is what a share converts to: harvested fees raise totalAssets without minting anything, so every share is redeemable for slightly more than it was.

The first deposit

The classic ERC-4626 inflation attack works by being the first depositor, minting one wei of shares, then donating a large amount to the vault so the second depositor's shares round to zero. The vault mints a fixed quantity of dead shares to itself on deployment, which makes that rounding impossible.

Harvest and fees

harvest() is permissionless. It collects the position's accrued trading fees, takes the protocol's cut, and puts the rest back into the same range. Anyone can call it, and calling it does not advantage the caller.

ParameterValueChangeable
protocolFeeBps10% of harvested feesGovernance, downward or up to the cap
maxFeeBps20%No, compiled in, reverts above it
Deposit feeNoneNo
Withdrawal feeNoneNo
RangeSet in the constructorNo

The fee applies to harvested fees only. It never touches principal, because no function in the vault can move principal anywhere except back to a redeeming holder.

Quiet orders

A public swap tells the mempool your size and your slippage before it settles, which is an invitation. A quiet order is an EIP-712 signature that says what you will accept: sell this much, receive at least that much, before this deadline.

  • Solvers compete to fill the order. Only the winning fill is broadcast.
  • You are quoted before signing. An order that cannot be filled at your limit expires unfilled.
  • No fill, no fee. Solvers are paid out of the surplus they find, not out of your principal.
  • Signing is not spending. Until a solver settles, nothing has moved.

Borrowing

Each market is isolated: one collateral, one loan asset, one oracle, one liquidation threshold. Those parameters are set when the market is created and can never be changed, so a market cannot be reconfigured out from under a position.

ltv     = debtValue / collateralValue
healthy = ltv < lltv          // else liquidatable

Because the collateral is a Rootshare, its price is the vault's share price, which drifts upward as fees are harvested. A position left alone gets slightly healthier over time rather than slightly worse. That is not a safety margin to rely on; it is just the direction the drift runs.

Autopilot

Autopilot is a schedule, not a fund. Each run pulls the amount you set, splits it by your allocation, deposits into each vault and mints the shares directly to your address. Between runs it holds nothing, so cancelling costs you a signature and nothing else.

  • Allocations must total 100%. The editor will not let you save otherwise.
  • A run that would fail, insufficient balance, a retired vault, is skipped, not retried at a worse price.
  • Revoking the allowance cancels the schedule whether or not you use the button.

Contract interface

The vault is a standard ERC-4626, so anything that already speaks that standard works without integration:

function asset()               external view returns (address);
function totalAssets()         external view returns (uint256);
function convertToShares(uint256 assets) external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256);
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

// Sorrel additions
function harvest()             external;               // permissionless
function range()               external view returns (int24 lower, int24 upper);
function retired()             external view returns (bool);

Configuring the site

The front end is static, HTML, one stylesheet, a few scripts, no framework and no build server. Everything that names a brand, a chain or an address lives in assets/config.js.

# serve it locally
npm run serve            # http://localhost:4173

# rebuild the pages after editing src/parts or src/pages
node tools/build.mjs

# audit every page in a real browser
npm run audit

Security

  • No admin path to principal. The vault can mint on deposit and burn on redeem. There is no owner-callable transfer.
  • The fee cap is compiled in. Governance can lower the protocol cut; setting it above the cap reverts.
  • Retiring is one-way and narrow. It stops new deposits. Redemptions keep working forever.
  • Reentrancy. Deposit and redeem follow checks-effects-interactions and hold a guard across the pool callback.
  • Oracles. Borrow markets price collateral from the vault's own share price plus the pool's TWAP, never a spot read.

Contracts should be audited before any deployment holds real funds. This repository ships the front end only.

Risks

  • Divergence loss is real. A concentrated position that goes out of range stops earning and holds the wrong side of the pair. You can end up behind simply holding the two assets.
  • Smart contracts fail. Audits reduce that risk; they do not remove it.
  • Liquidation. A borrowed position can be liquidated at any time once it crosses the threshold. There is no grace period.
  • Rates are not promises. An APY is what fees did recently, annualised. It is not what they will do.

Licence

This site and everything in this repository are released under the MIT Licence. You may use, copy, modify, publish and distribute it, including commercially, provided the copyright notice and the licence text travel with it.

MIT License

Copyright (c) 2026 Sorrel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

The full text is in LICENSE at the root of the repository.