A structured note is a vault with an end date. StructuredVault splits each deposit between an LPVault and a second sleeve, and after maturity it unwinds everything back to its asset. In Income mode the second sleeve is a long perp position run off chain, and the upside is capped; in Protected mode it is SGOV, a T-bill token, which puts a floor under the downside. This chapter explains how a note is built, what it pays, how you leave it and where the floor stops protecting you.
Two modes, one split
sleeveBps is the share of every deposit that goes to the mode's own sleeve. The rest goes to the other one.
In Income, the sleeveBps share goes into the LP vault and the rest stays in the note as idle asset, which the contract treats as collateral for a long the hedger runs off chain; nothing moves it to Lighter. In Protected, the sleeveBps share is swapped to SGOV through the asset/SGOV pool and the rest goes into the LP vault.
| Note | Symbol | Mode | sleeveBps | LP sleeve | Second sleeve |
|---|---|---|---|---|---|
Levee Income NVDA | lvNVDAi | Income | 8000 | 80 % into Levee NVDA Hedged | 20 % idle WETH, backing a reported long |
Levee Protected SPY | lvSPYp | Protected | 8500 | 15 % into Levee SPY Hedged | 85 % SGOV from the deepest WETH/SGOV pool |
Both seed notes sit on hedged vaults and use their ordinary deposit and redeem, so the LP vault's price check and pause apply to the note's money too: a pool off its mean or a paused hedge makes the note's deposit or exit revert as well.
Why the Income note is not open yet
The hedged vaults under both notes run: each hedges on a Lighter account owned by its own funder key, because a contract cannot bind a Lighter trading key (see where the margin lives).
- Protected SPY needs no Lighter account and no sleeve report of its own: SGOV plus a
15 %slice in SPY Hedged. It opens at deploy with no deposit cap (PROTECTED_NOTE_CAPisNO_CAPinLeveeWiring). Its slice follows SPY Hedged: a paused SPY Hedged makes the note's entries and exits revert. - Income NVDA needs a contract change. Its long is supposed to be backed by the
20 %kept idle in the note, but nothing moves that collateral to Lighter:StructuredVaulthas no funder and nofundSleeve. Opening it means a funded, bounded path likefundHedgeinto the Lighter account of a dedicated funder key (the same custody trade the hedged vaults make), and a hedger leg that trades the long and callsreportSleeve. The hedger does not report sleeves today. Until then the deploy creates it withINCOME_NOTE_CAP,0:maxDeposit()reads zero anddepositandmintrevertCapExceeded. That value never changes, so opening the Income note means creating a new one throughVaultFactoryand pointing the manifest at it.
What a note is worth
totalAssets() is the idle asset, plus the note's LP shares at lpVault.convertToAssets, plus the second sleeve: SGOV valued at the pool's TWAP for Protected, or the reported sleeve equity for Income.
A deposit checks, in order, that the note has not matured (Matured), that it is not paused (HedgePaused_) and, on a Protected note, that the SGOV pool passes its price check, and then that the cap, if any, has room (CapExceeded). maxDeposit() returns zero after maturity and while paused, type(uint256).max for a note with no cap, and the room under the cap otherwise.
The payoff at maturity
The chart draws both payoffs per unit deposited, against s, the underlying's price at maturity as a multiple of its price at deposit.
Both curves value the LP sleeve as √s, what a full-range position is worth after a price move of s.
income = sleeve × (√s + feeYield) + (1 − sleeve) × min(s, 1.5)
protected = sleeve × (1 + sgovYield) + (1 − sleeve) × √s
In Income, the long stops gaining above 1.5x (PAYOFF_CAP_MULT_X18); that cap is the price of the fee income. In Protected, the first term does not depend on s at all, which is where the floor comes from.
The contract's payoffCurve(spotMultiplierX18) computes the same formulas as packages/core/src/payoff.ts with two pins: feeYield is zero, because fees already sit in the LP sleeve's share price, and sgovYield is SGOV_ANNUAL_YIELD_X18 (4.5 % a year) accrued over the time left to maturity, so a Protected curve flattens toward par as the date nears. contracts/fixtures/payoff.json pins eight points of each curve for comparing the two.
For the seed notes at the start of their 90-day term, the contract's curve gives:
| Price multiple | lvNVDAi (80 / 20) | lvSPYp (85 / 15) |
|---|---|---|
0.25x | 0.450 | 0.934 |
0.5x | 0.666 | 0.965 |
1x | 1.000 | 1.009 |
1.5x | 1.280 | 1.043 |
2x | 1.431 | 1.072 |
The Protected floor here is 0.85 × (1 + 0.045 × 90 / 365), about 0.859.
Leaving the note
You can withdraw at any time. Before maturity, EARLY_EXIT_BPS (50, 0.5 %) goes to the FeeRouter, because an early exit unwinds part of a sleeve at that minute's prices and the fee keeps that cost off the holders who stay. The fee is built into the ERC-4626 views:
previewRedeem(shares)returns the gross value lessceil(gross × 50 / 10 000).previewWithdraw(assets)returns the shares forassets + ceil(assets × 50 / 9 950), sowithdraw(assets)really paysassetsand the fee comes on top.maxWithdraw(owner)ispreviewRedeemof the owner's whole balance.
After maturity the fee is zero and the views say so.
A partial exit pays from idle asset first, then redeems LP shares and, on a Protected note, sells SGOV, each in proportion to the shortfall. The Income perp sleeve is off chain, so an exit larger than the rest of the note reverts Shortfall. If your shares are the whole supply, the note unwinds everything, takes the fee from the gross balance and pays you the rest; redeem() returns what was paid.
Settling at maturity
settle() unwinds everything once the date has passed: it redeems every LP share the note holds, sells all its SGOV under the price check and emits Settled(assetsOut). Anyone can call it, once (NotMatured before the date, AlreadySettled after the first call). The note's own pause does not block it, because it prices no shares. It can still revert, for instance while the LP vault is paused or the SGOV pool is off its mean, and can be retried later. After settlement the note is a pile of WETH, and no exit depends on the SGOV pool or the LP vault again.
The SGOV pool
A Protected note buys, sells and values SGOV through one Uniswap pool, so that pool is treated as an oracle. Every deposit, every withdrawal while the note holds SGOV, and every settlement first runs TwapGuard.check on it: at least 10 min of history, and spot within 3 % of the mean. VaultFactory grows the pool's observation ring when the note is created.
The SGOV sleeve is valued at the TWAP, net of the pool fee it costs to sell, so pushing spot buys nobody a cheaper entry. Each swap's floor is SGOV_SLIPPAGE_BPS (100, 1 %) under the TWAP quote, because a floor taken from a spot the caller just set bounds nothing. Once the note holds no SGOV, in practice after settle, the check is skipped. An Income note has no SGOV pool.
Reporting the Income sleeve
The hedger reports the Income sleeve with reportSleeve(unrealizedPnlUsd, equityUsd, asOf). equityUsd is the sleeve's excess equity, what the perp has earned on top of the collateral, because the collateral never left the note and counting it again would double it. The value is unsigned: a losing sleeve reports zero, and the loss shows up only when the collateral comes back short.
The gate is the hedged vault's without the notional: NotHedger, StaleReport, the jump test against the previous stored equity, and a pause on a jump or on a report older than maxHedgeAge. A paused note reverts deposit, mint, withdraw and redeem with HedgePaused_. On a Protected note the call reverts BadSleeve, so the hedger key holds no pause over it. Reports are still accepted after maturity, while holders remain.
The equity converts to WETH through assetUsdFeed (ETH/USD in the seed notes), or one for one when the feed is unset for a 6-decimal USD asset; a bad or stale feed values the sleeve at zero. Nothing in the repository calls reportSleeve yet, so a report is an operator action with the hedger key, and a note never reported on never pauses and never counts a sleeve.
What "protected" does not cover
The floor also leaves out the LP sleeve, 15 % of the seed note, which falls with √s. And the note is measured in WETH, so it is protected in ETH terms, not in dollars.
Reference
Settings
| Name | Allowed | Default | Effect |
|---|---|---|---|
asset | WETH or USDG | WETH | Must equal lpVault.asset(). |
mode | Income or Protected | - | Which second sleeve the note runs. |
sleeveBps | 1 - 9999 bps | 8000 / 8500 | Share of each deposit for the mode’s own sleeve: the LP vault for Income, SGOV for Protected. |
maturity | in the future unix s | deploy + 90 days | After it: no deposits, no fee, settle allowed. |
lpVault | LPVault or HedgedLPVault | - | The LP sleeve. |
sgov / sgovPool | token / asset-SGOV pool | - | Protected only. |
assetUsdFeed | Chainlink feed or zero | ETH/USD | Converts reported sleeve equity; zero means 1:1 for a USD asset. |
hedger | address | - | The only caller of reportSleeve. |
cap | asset units, or type(uint256).max | NO_CAP / 0 | Deposit cap. Cannot change. Protected SPY has none (NO_CAP); Income NVDA deploys at 0 and takes no deposits. |
maxHedgeAge | uint32 seconds | 1 h | Report age after which the note pauses. |
maxEquityJumpBps | uint16 bps | 1500 | Largest equity move one report may make without pausing. |
sleeveConfig() returns the live staleness and jump bounds, and sleeveState() the last report and whether the note is paused.
Reverts and events
| Revert | Raised by | Meaning |
|---|---|---|
Matured | initialize, deposit, mint | A maturity not in the future, or a deposit on or after the date. |
HedgePaused_ | deposit, mint, withdraw, redeem | The Income sleeve report jumped or went stale. |
CapExceeded, ZeroShares | deposit, mint | Over the cap, or no shares to mint. |
Shortfall | withdraw, redeem | The on-chain sleeves cannot raise the amount. |
NotMatured, AlreadySettled | settle | Before the date, or a second call. |
NotHedger, StaleReport | reportSleeve | Wrong caller, or an asOf in the future or not after the last report. |
BadSleeve | initialize, reportSleeve | A sleeveBps of 0 or 10 000 and up, or a report on a Protected note. |
NotAsset, AlreadyInitialized | initialize | The LP vault or SGOV pool does not match the asset, or a second initialisation. |
TwapWindowTooShort, PriceDeviation | Protected entry, exit, settle | The SGOV pool failed its price check. |
Events: CapUpdated(cap) once, SleeveReported(unrealizedPnlUsd, equityUsd, asOf), HedgePaused(reason) and HedgeResumed() (the hedged vault's signatures, so the indexer tracks every vault's pause from one pair), Settled(assetsOut), and ERC-4626 Deposit and Withdraw, which reports the net paid. The factory's VaultCreated has kind 2 and the SGOV pool as pool, zero for an Income note.
contracts/src/StructuredVault.sol_deposit, _withdraw, _raise, settle, reportSleeve, payoffCurve, _requireOpenpackages/core/src/payoff.tsincomePayoff, protectedPayoff and the full-range approximationcontracts/fixtures/payoff.jsoneight pinned points per curvecontracts/script/LeveeWiring.solINCOME_SLEEVE_BPS, PROTECTED_SLEEVE_BPS, NOTE_TERM, PROTECTED_NOTE_CAP, INCOME_NOTE_CAP and the two seed notesapps/web/components/docs/PayoffDemo.tsxthe chart in this chapterapps/web/lib/earn/view.tspayoffPointsFor, withdrawFeeNote, countdowncontracts/test/StructuredVault.t.solboth splits, the report gate, the SGOV guard, the fee before and after maturity, settle