NAV & price-per-share

NAV (Net Asset Value) is the total value of everything a basket holds. Price-per-share is that value divided by the number of shares. Together they tell you what a share is worth — but, importantly, they are used only for display and pricing, never to gate your ability to deposit or withdraw.

How NAV is computed

For each constituent the basket holds, it multiplies the amount held by that token's price, then sums across all constituents:

NAV (USD, 1e18-scaled) = Σ_i  trackedBalance_i × price_i / 10^decimals_i
  • trackedBalance_i — the vault's internal accounting of how much of token i it holds.
  • price_i — the token's USD price from its Chainlink feed, normalized to 18 decimals by OracleLib.
  • decimals_i — the token's own decimals (so a 6-decimal token and an 18-decimal token are compared fairly).

This is exactly what the nav() view function returns.

Price-per-share

price-per-share = NAV / totalShares

At creation, a basket is seeded so that its first share is worth roughly the creator's chosen seed size (e.g. ~$25). From then on, price-per-share moves with the prices of the underlying stocks — up when they rise, down when they fall — plus the slow drag of the management fee.

Why NAV never gates mint or redeem

This is the most important property to understand:

  • In-kind mint and redeem are pro-rata on real balances. When you deposit or withdraw in-kind, the vault just gives or takes tokens in proportion to trackedBalance and your share of the supply. No price is consulted.
  • NAV is only used for: showing you the value, pricing one-click zaps, and accounting for fees.

So if a price feed goes stale or breaks, the basket's displayed NAV may be flagged stale and even understated — but you can still redeem your exact proportional slice of the underlying. Your exit is never blocked by an oracle.

Staleness and the stale flag

OracleLib reads each feed behind two guards — a staleness limit (the price must be recent enough) and a sequencer-uptime check (the L2 sequencer must have been up long enough). If anything is wrong, it returns stale = true instead of reverting.

  • If any constituent's price is stale, the basket's nav() sets its overall stale flag, and that leg contributes 0 to the sum.
  • The app marks stale NAV clearly (with a timestamp) everywhere it appears, and disables one-click zaps that would price off a stale feed — while still offering in-kind flows.

Returns

The backend snapshots NAV over time and computes each basket's return over rolling windows (24h, 7d, 30d) and since inception, which power the leaderboard and the return figures on each basket. A brand-new basket shows for a window until enough history exists (e.g. a 30-day return needs 30 days of snapshots).

For developers

NAV-related fields are represented as raw 1e18 integer strings — divide by 1e18 to display. This includes navUsd, navPerShare, shareSupply, inceptionNavPs, and per-constituent valueUsd. See the wire-format note in Core concepts.