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.
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
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.
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
| Preset | Width | Band | Depth | Behaviour |
|---|---|---|---|---|
| Tight | 1000 | ±5.13% | 20.5× | Earns most per unit, leaves range soonest |
| Balanced | 4500 | ±25.23% | 5.0× | The middle of the trade-off |
| Wide | 8100 | ±49.93% | 3.0× | Rarely re-centres, earns least per unit |
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.
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.
Every vault has a deposit cap written once at creation. There is no setter, so no key can raise it.
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.
| Constant | Value | Meaning |
|---|---|---|
| TWAP_WINDOW | 600 | Ten minutes of observed history, minimum |
| TWAP_BAND_BPS | 300 | Spot 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 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.
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
| Field | Seed value | Meaning |
|---|---|---|
| widthTicks | 1200 | Total width of the range a re-centre mints |
| hysteresisTicks | 120 | How far past the edge before a re-centre is allowed |
| minInterval | 3600 s | Time that must pass between actions |
| maxSlippageBps | 300 | Bound on the swap, the mint and the round trip |
| bountyBps | 100 | Paid to whoever spends the gas — capped at 300 |
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.
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
It cannot withdraw, upgrade, pause, or reach a depositor's position. Its entire on-chain surface is funding a hedge and reporting its value.
Fees
The complete schedule. Each figure is the constant that enforces it.
| Activity | Fee | Charged on | Constant |
|---|---|---|---|
| Harvest or re-centre | 10% | Fees collected | PROTOCOL_FEE_BPS = 1000 |
| Keeper bounty | ≤ 300 bps | Fees collected | MAX_BOUNTY_BPS = 300 |
| Referral share | 20% | Protocol revenue | REFERRAL_BPS = 2000 |
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.
Security model
Built by subtraction. The functions that would let anyone move a depositor's money were never written.
| Capability | Exists? | Note |
|---|---|---|
| owner() | No | No Ownable, no privileged role |
| upgradeTo() | No | No proxy, no implementation slot |
| pause() | No | A pause is an admin function by another name |
| rescueTokens() | No | No sweep path of any kind |
| setCap() / setPolicy() | No | Written once at init, no setters |
| reportHedge() | One key | Bounded 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.
The contracts have a unit-test suite, not an audit. No invariant or fuzz suite yet. Treat this as pre-audit software.
Addresses
Two networks. Read the label before you copy.
Token — Robinhood Chain mainnet, 4663
1,000,000,000 supply · 18 decimals · no owner() in the bytecode.
Protocol — Robinhood Chain testnet, 46630
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.
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.
Nothing in this documentation is investment advice or an offer to sell a security. Digital assets can lose their entire value.