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 everydayswindow.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).
pctOfTvlis computed by the backend — do not re-derive it fromvalueUsd / totalValueUsd(rounding and off-chain assets can make them differ).- Check
partial— whentrue, some holdings couldn't be priced; showpartialReasons.
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.userAddressis 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: nullis a pending request awaiting its epoch (typesDEPOSIT_REQUEST/REDEEM_REQUEST). - A claim settles as
WITHDRAWAL. - Row order is not guaranteed — sort client-side by
timestampif it matters. totalTransactionsmay exceedtransactions.length(pagination vialimit/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>
);
}
Related pages¶
- Hooks Reference — query keys, staleTimes, full signatures
- API Clients — the same data without React (
ArkonixAPIClient) - Types Reference —
VaultFinancials,ReturnHistory,VaultTransactions, and friends