Docs

Caliper documentation

How a deposit becomes a concentrated position, what manages it, what it costs, and exactly which keys exist. Every constant on this page is the one compiled into the deployed bytecode.

Chapter 01

Overview

Providing liquidity to a concentrated range earns trading fees, but it also hands you a directional position you may not have wanted. Caliper separates the two: it keeps the fee income and sheds the delta.

You deposit a single token. The vault works out how much of it the chosen range does not want, sells exactly that much, and mints a Uniswap v3 position between two bounds. From then on the position is harvested and re-centred under a policy fixed when the vault was created, and its price exposure is shorted on a perp venue.

What the system is made of

  • CaliperVault — an ERC-4626 vault holding exactly one concentrated position
  • VaultFactory — deploys vaults as EIP-1167 clones, permissionlessly
  • FeeRouter — where protocol fees land, and the only place a referral is paid
  • TwapGuard — the price-band check every share-moving path runs behind
  • Policy — the management rules, validated once and never settable again
The one fact

No contract in the system has an owner. There is no Ownable, no proxy, no pause switch and no rescue function — those calls were never written, so there is no key that can be stolen to use them.

Chapter 02

The position

A range is two bounds on a price. Concentrating capital between them makes it stand deeper in the book at the current price — and stop earning entirely once price leaves.

Caliper mints the range centred on the tick at the time, widthTicks wide, aligned to the pool's tick spacing. The depth multiple against a full-range position is:

// capital a full-range position needs to match this depth at spot
depth = 1 / (1 − √(pa / pb))

// the seed policy, 1200 ticks on 60 spacing
width   = 1200 ticks
bounds  = ±5.13%
depth   = 20.5×

The presets

PresetWidthBandDepthBehaviour
Tight1000±5.13%20.5×Earns most per unit, leaves range soonest
Balanced4500±25.23%5.0×The middle of the trade-off
Wide8100±49.93%3.0×Rarely re-centres, earns least per unit
The trade-off

Narrow ranges earn more while price sits inside them and nothing at all once it leaves. Width is a judgement about volatility, not a setting with a correct value.

Chapter 03

The vault

An ERC-4626 wrapper over exactly one position. You hold shares; the vault holds the range.

Depositing

deposit(assets, receiver) pulls the asset, mints shares at the current share price and leaves the funds loose. deploy() then puts idle balances to work — it is permissionless, because it can only ever move the vault's own funds into the vault's own position.

Share price is unaffected either way: totalAssets() counts loose balances and position principal identically.

Redeeming

redeem(shares, receiver, owner) burns shares, pulls a proportional slice of the position, sells the non-asset leg through the pool under the policy's slippage bound, and pays out.

How totalAssets is computed

holdings = loose token0 + loose token1
         + principal the position returns at the current sqrtPriceX96

value    = assetLeg + otherLeg × price × (1 − poolFee)

Two choices there are deliberate. The non-asset leg is valued net of the pool fee, because the vault must sell it through the pool to pay anyone out — valued gross, the number would be one the vault cannot realise and the last redeemer would eat the shortfall. And uncollected fees are excluded, because counting them would let anyone move the share price simply by calling harvest.

Caps

Every vault has a deposit cap written once at creation. There is no setter, so no key can raise it.

Chapter 04

Pricing & the TWAP guard

Every call that mints or burns shares at the pool's spot price runs through the guard first.

Spot must sit within TWAP_BAND_BPS of a mean measured over at least TWAP_WINDOW seconds of real observed history, or the call reverts rather than price someone badly.

ConstantValueMeaning
TWAP_WINDOW600Ten minutes of observed history, minimum
TWAP_BAND_BPS300Spot must be within 3% of the mean

The tick-difference test used is tighter than a true ratio test over the band sizes involved, so it never admits a price the ratio test would reject.

A fresh pool cannot accept deposits

A pool with an observation cardinality of 1 has no history to measure, so observe reverts with OLD and the guard refuses. The pool must accumulate a real ten-minute window before the vault will price shares. This is the guard working, not a fault.

Chapter 05

Harvest & re-centre

Both are permissionless. Anyone may call them, for a bounty the policy caps, and neither can move a position anywhere except back into the vault.

harvest()

Collects trading fees. The protocol takes PROTOCOL_FEE_BPS and the caller takes the policy's bounty — both out of fees only. The remainder stays in the vault and is compounded back into the range by the next deploy().

rebalance()

Unwinds the position and mints a new one centred on the current tick. It reverts with InRange unless price has left the range by more than hysteresisTicks, and with TooSoon before minInterval has passed.

The whole round trip is bounded: the value after must not be less than the value before minus maxSlippageBps, or it reverts with SlippageExceeded.

The policy

FieldSeed valueMeaning
widthTicks1200Total width of the range a re-centre mints
hysteresisTicks120How far past the edge before a re-centre is allowed
minInterval3600 sTime that must pass between actions
maxSlippageBps300Bound on the swap, the mint and the round trip
bountyBps100Paid to whoever spends the gas — capped at 300
It mints back to the vault

A re-centre always mints the replacement position to the vault, never to the caller. The caller is paid a bounty out of value, and nothing else.

Chapter 06

The hedge

An LP position is long the asset whether you wanted the bet or not. The hedged vault shorts that delta on a perp venue, so the fee income stays and most of the price exposure goes.

This is the one part of the system with a trust assumption in it, and it is worth stating plainly rather than burying.

The perp settles on a venue this chain cannot read. So the vault relies on one named key to report what the hedge is worth, and that figure enters the share price. Three things bound it:

  • A jump band on every report, so a single report cannot move the share price arbitrarily
  • A staleness pause that triggers without anyone sending a transaction
  • One fixed address it is able to pay
What that key cannot do

It cannot withdraw, upgrade, pause, or reach a depositor's position. Its entire on-chain surface is funding a hedge and reporting its value.

Chapter 07

Fees

The complete schedule. Each figure is the constant that enforces it.

ActivityFeeCharged onConstant
Harvest or re-centre10%Fees collectedPROTOCOL_FEE_BPS = 1000
Keeper bounty≤ 300 bpsFees collectedMAX_BOUNTY_BPS = 300
Referral share20%Protocol revenueREFERRAL_BPS = 2000
It never touches principal

The ten percent comes out of fees earned. A position that has collected nothing pays nothing, and no harvest path can reach the liquidity itself.

Referrals

A referrer is written once by the referred address and can never be changed, so attribution cannot be re-pointed after the fact. Fees accumulate in the router and are pulled with claim(token) — the router never pushes.

Chapter 08

Security model

Built by subtraction. The functions that would let anyone move a depositor's money were never written.

CapabilityExists?Note
owner()NoNo Ownable, no privileged role
upgradeTo()NoNo proxy, no implementation slot
pause()NoA pause is an admin function by another name
rescueTokens()NoNo sweep path of any kind
setCap() / setPolicy()NoWritten once at init, no setters
reportHedge()One keyBounded by jump band, staleness pause, one payee

Clones

Vaults are EIP-1167 clones of one implementation. The factory clones and initialises in the same transaction, so an unconfigured clone never exists in a block for a stranger to claim. The implementation's constructor marks it initialised, so nobody can configure the implementation itself.

Reverts, not silent failures

If a position changed hands since it was enrolled, actions refuse rather than pay the wrong owner. Every value-moving path reverts on a bound rather than proceeding at a bad price.

Unaudited

The contracts have a unit-test suite, not an audit. No invariant or fuzz suite yet. Treat this as pre-audit software.

Chapter 09

Addresses

Two networks. Read the label before you copy.

Token — Robinhood Chain mainnet, 4663

$CALI 0xF9A3F7CA81629c8Ae268CBd2C7B37a25cc48059d

1,000,000,000 supply · 18 decimals · no owner() in the bytecode.

Protocol — Robinhood Chain testnet, 46630

Vault0x6Dfe9B4bA8b93767E74139Aa049faDb1091eeEb1
Factory0xeBbF968f43D788aAde9041d0405aBdc256C3055D
FeeRouter0x853B3C8daAaE8a0e845c4EACA6fbB33BD48dD15f
Pool0xa0D9fAa381ac6CDe70D33f163fDcD15E56E42d0D
Different networks

The token is on mainnet. The vault protocol is currently deployed on testnet, with test tokens that have no value. Do not send mainnet funds to a testnet address.

Chapter 10

Risk

What can go wrong, stated without hedging.

  • Impermanent loss. A concentrated range converts your position between the two assets as price moves. The hedge reduces directional exposure; it does not remove this.
  • The hedge is imperfect. It is rebalanced discretely, not continuously, and it depends on a reported value. Between reports the share price can be stale.
  • Out of range earns nothing. A position outside its bounds collects no fees until it is re-centred.
  • Unaudited code. Unit tests are not an audit.
  • Testnet is not mainnet. The deployed protocol uses valueless test tokens.
  • Yields shown are historical and are not a forecast of anything.
Not advice

Nothing in this documentation is investment advice or an offer to sell a security. Digital assets can lose their entire value.