For the complete documentation index, see llms.txt. This page is also available as Markdown.

Code examples

Read NAV, positions, and prices from the NAV Calculator with viem, ethers.js, and Python.

Practical examples for reading NAV, prices, and position data from KPK funds via the NAV Calculator.

Source: INAVCalculator.sol

Addresses below are placeholders. Use the canonical proxy address for your chain from Deployment addresses, and call the proxy — never the implementation.

USD values are returned with 8 decimals (usdDecimals()). Pass address(0) as the quote asset for USD.


1. Check chain health

Before trusting a NAV reading, verify the chain is healthy — no stale prices, sequencer up.

function healthCheck()
    external view returns (bool sequencerDown, address[] memory stalePriceAssets, address[] memory irregularPriceAssets);

healthCheck returns three values, not four: it deliberately carries no monitorsUnhealthyPriceAssets, because widening its return was not affordable against the implementation's EIP-170 headroom. So an empty irregularPriceAssets here does not establish that the divergence check ran. Read that array off a getAccountNav / getAccountNavForAdapters result — see Stale prices & sequencer.

import { createPublicClient, http, parseAbi } from "viem";
import { mainnet } from "viem/chains";

const NAV_CALCULATOR = "0x..."; // proxy — see Deployment addresses

const ABI = parseAbi([
  "function healthCheck() view returns (bool sequencerDown, address[] stalePriceAssets, address[] irregularPriceAssets)",
]);

const client = createPublicClient({ chain: mainnet, transport: http() });

async function checkHealth() {
  const [sequencerDown, stalePriceAssets, irregularPriceAssets] = await client.readContract({
    address: NAV_CALCULATOR, abi: ABI, functionName: "healthCheck",
  });
  if (sequencerDown || stalePriceAssets.length > 0) {
    console.warn("Chain unhealthy:", { sequencerDown, stalePriceAssets });
    return false;
  }
  if (irregularPriceAssets.length > 0) {
    console.warn("Price divergence (soft alert):", { irregularPriceAssets }); // still usable — investigate
  }
  return true;
}

2. Read NAV — single chain

Pass the account (portfolio Safe) and the quote asset (address(0) for USD).

Paging a read that no longer fits

getAccountNav scans every registered adapter in one eth_call. If that ever exceeds the node's gas cap you get no NAV at all — so getAccountNavForAdapters lets you ask the same question over a subset and reassemble.

Reassembly is where this goes wrong, so the example below is the reassembly, not the call. Three rules:

  • Only value adds up. stalePriceAssets / irregularPriceAssets / monitorsUnhealthyPriceAssets cover only the assets that slice touched, and sequencerDown / quoteAssetStale / quoteAssetIrregular are reported per slice. Union the arrays and OR the booleans across every slice — checking them on one slice yields a NAV that looks healthy while silently dropping the others' warnings. Dropping the third array under-reports monitor failure, which is the falsely-reassuring direction.

  • Slice by adapter set, never by index range. The adapter list is swap-and-pop, so a removal between calls moves the tail adapter down into a slot you already read, and it is skipped.

  • Verify with adaptersCovered. Concatenate it across slices and compare against getAllAdapters(). A missing address means the total is short by that adapter's positions — undetectable from value alone, because a short NAV is a perfectly plausible number.

Pass the same quoteAsset to every slice.

A slice is rejected rather than returning a quietly wrong value: EmptyAdapterSlice(), AdapterNotRegistered(address) for anything absent from getAllAdapters(), and DuplicateAdapterInSlice(address) for a repeat. A meta-adapter is atomic — naming one covers all its governed instances, and there is no paging within a single adapter, so pagination cannot rescue one that is oversized on its own.


3. Read global NAV — all chains

Funds deployed on multiple chains: read each chain and sum the per-chain value once every chain is healthy.


4. Enumerate positions with labels

getAccountPositions returns the typed positions. For human-readable labels in one call, use the NAV Calculator's verbose read getAccountPositionsVerbose, which adds protocolName and a labels array (the breadcrumb each adapter exposes through IPositionDescribable). There is no separate lens contract — call the NAV Calculator proxy directly.

Position identity is the typed key protocolSubId + positionId + balanceAsset.asset + positionKind (per chain). The broader protocolBrand / protocolId are grouping layers; positionInstanceId is an ephemeral per-item handle and isLocked is a mutable per-leg attribute — both excluded from the key (so claimable/locked and per-item legs sum). See Position identity for decode schemas and the reconciliation key. The protocolName string and labels[] array from the verbose reads are display-only.


5. Read the latest price for an asset

Each asset is priced by its primary feed. Read it with getPriceData(asset), which returns the full PriceFeedData snapshot — price, decimals, stale, plus the primary feed's type, heartbeat, updatedAt, healthyFeedCount, and the divergence signal (irregular, divergenceBps, monitorFeedCount).

For a Custom (composite) feed, updatedAt and chainlinkHeartbeat report the governing leg — the underlying Chainlink leg closest to its own staleness deadline — rather than the registered config heartbeat. Treat that pair as advisory and gate on stale / sequencerDown.


6. Value a basket of (asset, amount) pairs

calculateValue prices an arbitrary set of (asset, amount) pairs in one call — handy for valuing assets you already know off-chain (e.g. the inputs of a pending subscription) without enumerating positions. Returns the total in the quote currency (USD = 8 decimals) and a quoteAssetStale flag.


7. Decode position identity & group by protocol

Each position carries the 3-level protocol taxonomy plus positionId + positionKind + isLocked (see Position identity). The identity-key level is protocolSubId (keccak256 of the product slug); positionId is abi.encode(...) whose decode schema is fixed by that slug. This example maps each position back to its product, decodes the coordinate, totals value per product, and builds the stable reconciliation key.

The slug list above is illustrative — the full protocolSubId catalogue and every positionId decode schema are in Position identity. The reconciliation key deliberately excludes protocolBrand/protocolId (grouping only), positionInstanceId (ephemeral), isLocked (mutable per-leg), balanceAdapter, amount, and pricing, so it stays stable across reads and adapter redeploys.


8. Single-asset exposure

hasPositions is a cheap pre-check before enumerating; getAssetsWithPositions lists the assets an account holds something in; getAccountPositionsForAsset returns the positions for one asset across every adapter that reports it (e.g. all WETH exposure — wallet, supplied, staked).


9. List the assets a fund tracks

getRegisteredAssets returns every asset registered on the chain's NAV Calculator (with symbol and decimals); isAssetRegistered checks a single one. Useful to discover coverage before pricing or to validate an integration.

Last updated