Skip to content

Working with Financial Data

The Arkonix backend API supplies everything that is not on the vault contract: NAV/TVL, share price, returns, history series, user activity, asset distribution, and fees. All of it requires config.arkonixAPI.baseUrl — without it the financial-data hooks stay disabled (see Configuration).

Before rendering anything, internalize the two semantics that partners most often get wrong:

null ≠ 0 — never coerce return fields to zero

All return* fields are percentages (12.5 means 12.5%) and nullable. null means "not enough valid price history for this window" — common on young vaults. null is not 0. Never coerce to 0, never render "0%", never average nulls as zeros. Render a placeholder (—).

Window returns are CUMULATIVE — never label them APY

return7d / return30d / return90d are cumulative over the window, not annualized. A −7% month reads -7, never -60. Don't re-annualize them; don't label them "APY". returnAllTime is the only annualized field anywhere in the API — annualized since inception for vaults with ≥30 days of settled history, otherwise cumulative. It is bounded roughly [−99, +1000].

The address-only narrative

Everything starts from useVaultFinancials(vaultAddress). It returns NAV/TVL (tvlUsd), sharePrice, all return windows, and — critically — the shareClassId that keys every share-class-scoped hook (useReturnHistory, useTvlHistory, useVaultAssetDistribution, useShareClassFees). You never need to know the share-class id in advance.

import { useVaultFinancials, useReturnHistory } from "@arkonix.xyz/arkonix-vault-sdk";

function VaultDashboard({ vaultAddress }: { vaultAddress: `0x${string}` }) {
  const { data: fin, isLoading } = useVaultFinancials(vaultAddress);
  const { data: history } = useReturnHistory(fin?.shareClassId, { days: 90 });

  if (isLoading || !fin) return <div>Loading…</div>;

  return (
    <div>
      <p>NAV (TVL): ${fin.tvlUsd.toLocaleString()}</p>
      <p>Share price: {fin.sharePrice ?? "—"}</p>
      {/* null ≠ 0: render a placeholder for insufficient history */}
      <p>Return (7d): {fin.return7d != null ? `${fin.return7d}%` : "—"}</p>
      <p>Return (all-time, annualized): {fin.returnAllTime != null ? `${fin.returnAllTime}%` : "—"}</p>
      {history && <SparklineChart points={history.points} />}
    </div>
  );
}

Charting rules

useReturnHistory returns headline returns plus a points[] series (~50 points, oldest → newest). Each point carries two cumulative fields:

  • cumulativeReturnPct — relative to the first point of the window; it resets to 0 at the start of every days window.
  • cumulativeReturnSinceInceptionPct — absolute since launch.

Use cumulativeReturnSinceInceptionPct for all-time curves

To chart the all-time cumulative curve, use the per-point cumulativeReturnSinceInceptionPct — not the window-relative cumulativeReturnPct, which resets to 0 at the first point of every days window.

There is no per-point annualized series — by design

Both per-point fields are cumulative. Do not attempt to chart "APY over time" from points — annualizing early history is noisy and explosive, which is why the API doesn't provide it. The single annualized figure is the returnAllTime headline.

Headline returns are daily-cached; points are live

Headline return* fields are daily-cached; the points[] series is live (~15-min data). The chart's last point will not equal returnAllTime — this is expected, not a bug.

The days parameter (default 30 server-side, range 1–365) controls only the points window, not the fixed 7/30/90d/all-time headline fields.

Fees

useShareClassFees(shareClassId) returns management and performance fee rates from the on-chain fee manager.

Returns are already net of fees

Returns from the API are already net of fees. useShareClassFees exists only to display the breakdown ("net of 1% mgmt + 10% perf"). Never subtract fees from returns.

config: null means no fees configured — not 0%

config: null / isInitialized: false means "no fee manager configured" — not "0% fees". Do not render "0%" for an uninitialized fee manager.

Asset distribution

useVaultAssetDistribution(shareClassId) returns per-asset holdings with backend-computed weights (pctOfTvl). It is the Arkonix-native replacement for the deprecated Centrifuge holdings hooks (see Migration).

  • pctOfTvl is computed by the backend — do not re-derive it from valueUsd / totalValueUsd (rounding and off-chain assets can make them differ).
  • Check partial — when true, some holdings couldn't be priced; show partialReasons.
import { useVaultFinancials, useVaultAssetDistribution, useShareClassFees } from "@arkonix.xyz/arkonix-vault-sdk";

function VaultComposition({ vaultAddress }: { vaultAddress: `0x${string}` }) {
  const { data: fin } = useVaultFinancials(vaultAddress);       // step 1: get shareClassId
  const { data: dist } = useVaultAssetDistribution(fin?.shareClassId);
  const { data: fees } = useShareClassFees(fin?.shareClassId);

  if (!dist) return <p>Loading…</p>;

  return (
    <div>
      {dist.assets.map((a) => (
        <p key={a.symbol}>{a.symbol}: {a.pctOfTvl.toFixed(1)}% (${a.valueUsd.toLocaleString()})</p>
      ))}
      {dist.partial && <p>⚠️ Some holdings couldn't be priced: {dist.partialReasons.join(", ")}</p>}
      {fees?.config && (
        <p>Net of {fees.config.managementFeePct}% mgmt + {fees.config.performanceFeePct}% perf</p>
      )}
    </div>
  );
}

User activity

useVaultTransactions(vaultAddress, params) returns a user's deposit/redeem/claim activity plus still-pending requests. Semantics to respect:

  • The hook is disabled until params.userAddress is set — an all-users feed is rarely what a partner UI wants, and this makes the hook safe to mount before a wallet connects.
  • A row with txHash: null / blockNumber: null / timestamp: null is a pending request awaiting its epoch (types DEPOSIT_REQUEST / REDEEM_REQUEST).
  • A claim settles as WITHDRAWAL.
  • Row order is not guaranteed — sort client-side by timestamp if it matters.
  • totalTransactions may exceed transactions.length (pagination via limit/offset).
import { useVaultTransactions, useUserAddress } from "@arkonix.xyz/arkonix-vault-sdk";

const LABELS: Record<string, string> = {
  DEPOSIT: "Deposit",
  WITHDRAWAL: "Withdrawal",              // a claim settles as WITHDRAWAL
  DEPOSIT_REQUEST: "Deposit requested",  // unsettled, awaiting epoch
  REDEEM_REQUEST: "Redeem requested",
};

function UserActivity({ vaultAddress }: { vaultAddress: `0x${string}` }) {
  const address = useUserAddress();
  const { data, isLoading } = useVaultTransactions(vaultAddress, {
    userAddress: address ?? undefined,   // query disabled until set
    transactionType: "ALL",
    limit: 50,
  });

  if (!address) return <p>Connect a wallet to see your activity.</p>;
  if (isLoading || !data) return <p>Loading…</p>;
  if (data.transactions.length === 0) return <p>No activity yet.</p>;

  return (
    <ul>
      {data.transactions.map((tx, i) => (
        <li key={tx.txHash ?? i}>
          {LABELS[tx.type]} — {tx.amount} (@ {tx.sharePrice}/share)
          {tx.timestamp && ` · ${new Date(tx.timestamp).toLocaleString()}`}
        </li>
      ))}
    </ul>
  );
}
  • Hooks Reference — query keys, staleTimes, full signatures
  • API Clients — the same data without React (ArkonixAPIClient)
  • Types Reference — VaultFinancials, ReturnHistory, VaultTransactions, and friends