Measuring success — the USDT objective
This page records the objective, accounting and benchmark suite the
2026-09-05 platform audit (§7) asks for before Nexus can claim it grows the
portfolio. Where it stands the same evening: the Grafana "Nexus Treasury"
dashboard and the nexus.financial alert rules are deployed (nexus-gitops
05c3ffd); the worker job that produces the NAV series, the collections,
the read API and the benchmark engine are deployed since 2026-09-05 17:12 UTC
(nexus-platform ac38aa1; UI /treasury in nexus-ui 13d3f2a); the series
starts at the first snapshot (17:25 UTC, 91,138 USDT) — so the benchmark
comparison has no history yet. Execution-cost capture on the signer side
(fees, rent, realized quantities, costs_today) is live since 17:46 UTC
(nexus-solana c483bbd / a74cc87, live-verified) — see
Costs. There is still no forecast resolver (audit M3). P&L by
source — the owner's "where are we winning money" ledger (JitoSOL, Kamino
lending, swing round trips, market, costs, with an explicit unexplained
residual) — is deployed (nexus-platform 3a91236 … 7d6ed59, worker
rolled ≈ 20:3x UTC; first live rows at the 21:12 UTC snapshot); see
P&L by source. The owner's independent re-verification
(19:29–19:34 UTC) rates the whole of this as measurement implemented and
live; validation immature — its qualifications are under
Measurement qualifications. Section
As built has the item-by-item status; the
status register rows are §7 and O4.
The first day of live P&L produced numbers that were wrong, not merely
young, and they are fixed in nexus-platform 2553748 … 63a2de3
(deployed 2026-09-06, gitops 2c91751) with the exec side in nexus-solana
4804e26 (ea96739). What changed, and by how much:
| Defect | Read | Now | Fix |
|---|---|---|---|
| A native-SOL sell's coin leg was booked as released rent | costs:tx +1,067.96, round trip +1,226.46 | costs:tx −0.0005, round trip +159.40 | exec 4804e26 stops emitting it; platform 2553748 refuses it three ways — Cost rules |
A round trip was always derived (proceeds − cost − fees) | sleeve realized all-time 1,628.48 | 561.66 | the exec's own realized_pnl_usdc is taken verbatim when it read the chain — Profit source |
1d / 7d / 30d windows resolved to from == to, 0 periods, delta 0 | "a flat 30 days" | covered / truncated_to_series / series_starts_at — Window coverage | 72967bd |
| Lending interest sat beside balances it did not explain | +2.96 interest against a +38.03 balance move | balance_delta = net_flows + interest + unattributed, contaminated periods named — Lending reconciliation | 72967bd / 4b5a181 |
An LP position's asset was the uppercased pool address with qty: 0 | exposure.SOL 18.71 % | 19.87 % | one line per leg — LP as legs |
The corrected figures after the 10:09:48Z snapshot (nav_1788689388951)
and the whole of the remaining residual are under
The 2026-09-06 correction. None of this changes
NAV: the exposure and cap inputs were understated and the attribution was
wrong, the portfolio value was not.
What the desk reports today, and what it is not
The swing sleeve reported +402.30 USDC realized over 3 closed round trips at the audit snapshot (2026-09-05 ≈ 14:30 UTC). That number is:
- a ledger figure — computed from the stored requested order amounts, not from realized token deltas and fees on-chain;
- USDC-denominated, with no USDC/USDT conversion;
- realized only — it excludes unrealized losses on held inventory (which the "never sell below basis" doctrine can accumulate), earning positions, capital flows and CEX holdings;
- computed on a ledger that, at the snapshot, disagreed with the wallet
(0.0521 cbBTC in the ledger vs ≈ 0.0000479 in EXEC, audit F5 — repaired
2026-09-05 16:20 UTC: the orphan sell was linked to its lot, the lot closed
with 0.0001 cbBTC dust, and
GET /v1/sleeve/reconcilenow reportsall_explained: true,unresolved: false; inventory is empty andcommitted_usdc= 10,537, the open buy escrow only).
It is therefore not a total-portfolio USDT return, and three profitable closes are not evidence of an edge. Treat it as an operational counter until the accounting below exists.
Objective (proposed by the audit; owner inputs still needed)
Increase total portfolio NAV measured in USDT, net of execution, financing, infrastructure and AI costs, subject to explicit BTC/SOL exposure, drawdown, liquidity, custody and concentration limits; evaluate excess return versus frozen passive benchmarks.
The owner has to specify: investment horizon, maximum acceptable drawdown, minimum / maximum BTC and SOL exposure, stablecoin policy and minimum liquid reserve. Nothing here invents those numbers; the 20 % swing cap is a sleeve budget, not a portfolio loss limit.
USDT is the reporting unit; USDC is the implementation asset. Report
in USDT while settling in USDC by applying real USDC/USDT and asset
conversion rates — not by relabelling $ or assuming every stablecoin is
worth 1. cbBTC is wrapped BTC exposure, not native BTC custody: track BTC
market exposure separately from wrapper, custodian, liquidity and chain risk.
NAV
For every valuation time t:
NAV_USDT(t) = Σ (position quantity × executable or policy-approved USDT mark)
− liabilities − accrued costs
Include, once each: wallet assets (EXEC and VAULT), lending-receipt redemption value, JitoSOL value, open-order escrow, LP position value plus collectable fees, CEX assets, any borrow liabilities. Record price timestamp, source and a liquidity haircut per line. An unavailable balance is unknown, never zero.
Label capital in four buckets rather than "earning / sleeping": available, reserved for execution (order escrow — it earns nothing and may never fill), yield-bearing (lending, staking, LP), deployed risk.
Returns and attribution
- Period profit = ending NAV − starting NAV − net external inflows.
- Time-weighted return (TWR) separates strategy performance from deposits
and withdrawals; money-weighted return (MWR) is the owner's realized
experience. Publish both, with realized and unrealized PnL together. As
built the TWR is flow-timed since nexus-platform
8e1e0cb(inee0cd80, deployed 2026-09-05, live-verified 23:4xZ) and every period carries a method label — TWR method labels. - Attribution, reported separately: trading decisions · passive BTC/SOL
price movement · staking and lending yield · stablecoin basis · execution
costs (fees, slippage, rent) · AI and infrastructure costs. The per-source
ledger that implements this (deployed 2026-09-05, first live rows
21:12 UTC) is P&L by source; the coarse
attributionbuckets were a residual-based split until then — see Measurement qualifications.
Benchmark suite (five frozen counterfactuals)
Run on the same cash flows, timestamps, fees and asset availability as the live book; parameters frozen in advance:
- Stablecoin cash with explicitly stated yield and risk assumptions.
- Fixed BTC/SOL buy-and-hold at an owner-chosen allocation.
- Fixed-interval BTC/SOL DCA.
- The passive configured HARVEST ladder, with realistic recall and slippage assumptions.
- The same deterministic system without AI timing decisions, to isolate the AI's contribution.
As built on 2026-09-05, benchmarks 1–4 are deployed (see below); benchmark 5 — the no-AI counterfactual — is not.
Track against each: net excess return, maximum drawdown, exposure-adjusted return, downside / tail loss, turnover, implementation shortfall, time underwater, idle and reserved capital, and compute cost per accepted decision. Evaluate decisions as of their original timestamps — no future information for entries, labels or benchmark parameters. Store rejected and no-trade decisions to measure selection effects and missed opportunities. Use walk-forward periods over several regimes and report uncertainty; promote strategy changes on preregistered evidence thresholds, not on explanations after profitable outcomes.
Forecasts: define a resolvable event and deadline, freeze the forecast, persist the subsequent observations, and score Brier / calibration against simple baselines. A model-generated confidence or a dream narrative is not a calibrated probability. (No resolver or scorer exists today; the live prompts' "Brier-scored" wording is unbacked — audit M3.)
Policy tensions the accounting must expose
| Current doctrine | Consequence for USDT growth | Improvement |
|---|---|---|
| Never sell below basis | Can accumulate underwater inventory and hide losses from closed-trade statistics | Separate strategic holdings from tactical risk; explicit drawdown and thesis-invalidation exits |
| Nothing idle | Encourages protocol exposure even when marginal yield is small | Treat liquidity / optionality as a deliberate allocation with a risk budget |
| Order escrow is "earning" | Reserved capital may generate no yield and may never fill | Label available, reserved, yield-bearing and deployed risk separately |
| Bottom forecast anchors deployment | Strong dependence on a specific price path | Test no-dip, shallow-dip and prolonged-downtrend alternatives |
| 20 % swing cap | Not a total portfolio loss limit | Bound total correlated exposure and stress loss |
| Highest eligible APY rotation | Yield can be outweighed by costs, withdrawal friction and protocol risk | Require net benefit over the expected holding period plus risk constraints |
| Analyst agreement | Multiple models may share evidence and anchoring | Measure marginal contribution, disagreement quality and forecast calibration |
| LP APR attractive | Fees alone omit inventory drift and impermanent loss versus hold | Evaluate total USDT return and excess return against holding the same assets |
Scenario tests
| Scenario | What the system must demonstrate |
|---|---|
| BTC/SOL rise without reaching rungs | Quantified opportunity cost; governed fallback; no silent strategy drift |
| Rapid crash through several rungs | FIFO execution, available reserves, bounded slippage, no duplicates |
| Long bear market after swing buys | Honest unrealized losses, exposure / drawdown control, no misleading win-rate claim |
| USDC or USDT moves off peg | Correct numeraire conversion, explicit stablecoin limits and recovery policy |
| cbBTC diverges from BTC | Wrapper / liquidity risk separated from BTC index exposure |
| Lending withdrawal unavailable | Liquidity haircut and fallback; capital not counted as immediately spendable |
| JitoSOL exit spread widens | SOL exposure remains correctly valued and liquidity-constrained |
| RPC confirms slowly or the process dies | Durable signature recovery; no automatic duplicate send |
| LP exits during volatility | Net fees, inventory and slippage accounted against the hold benchmark |
| AI emits stale / malformed / adversarial output | No financial authority without deterministic validation |
As built (2026-09-05)
Status words follow the register vocabulary: specified → in progress → built → deployed → live-verified. Everything below was worked on 2026-09-05 after the owner made §7 the priority (16:25 UTC).
Deployed — nexus-gitops 05c3ffd (2026-09-05 ≈ 16:27 UTC)
| Item | Status | What it is |
|---|---|---|
Grafana dashboard Nexus Treasury (uid nexus-treasury, folder Nexus, 30 panels) | deployed | Sections: NAV vs benchmarks, excess return, NAV components, exposure, attribution, flows & costs, marks, execution safety (signer / sleeve / ladder / outbox). Shipped as a sidecar ConfigMap next to Nexus Platform (files/dashboards/nexus-treasury.json). |
PrometheusRule group nexus.financial (15 rules in nexus-platform-alerts) | deployed | NexusNavStale (snapshot > 3 h or absent), NexusNavIncomplete (unknown components for 2 h), NexusNavDrawdown 10 % warn / NexusNavDrawdownCritical 20 %, NexusNavDailyLoss 5 %, NexusStablecoinDepeg (|mark − 1| > 1 %), NexusMarkStale 30 min, NexusSignerUnreachable, NexusSignerPaused, NexusSignerAuthDenied, NexusSignerPolicyDenial (critical), NexusDailyCapNearlySpent 80 %, NexusSleeveUnresolved 30 min, NexusOutboxStuck 30 min (critical), NexusLadderRungFiringStuck 30 min. Thresholds are chart values monitoring.financial.{drawdownWarnFrac,drawdownCritFrac,dailyLossWarnFrac} (0.10 / 0.20 / 0.05). |
Both consume the metric contract below. Until the worker image that exports
it is deployed, every NAV panel is empty and NexusNavStale fires — that
is the expected state, not a fault. The execution-safety rules that read the
signer's existing status (nexus_exec_*, nexus_sleeve_*, nexus_outbox_*,
nexus_ladder_*) also wait on the same exporter.
Deployed 2026-09-05 17:12 UTC — nexus-platform ac38aa1 (first snapshot 17:25 UTC: 91,138 USDT, 0 unknown components)
Two things went wrong in the first hour and are recorded here because they shape what the series shows:
- The first
treasury-navrun was lost to a rollout race. The seeded job was fetched by the outgoing worker pod under the shared Apalis consumer name and sat in-flight; orphan reclaim never fires while the new pod's heartbeat keeps that name alive. It was re-enqueued by hand at 17:25 UTC (that is the first snapshot). Fixed bystrategy: Recreatefor the worker (nexus-gitops441291a) and a per-pod consumer namenexus-jobs-<POD_NAME>(nexus-platform9063b8d, gitops078055c); runbook under Operations → Apalis queue. - The worker was unscrapeable 17:12–17:4x UTC.
nexus_nav_component_usdtcarried a label namedcomponent, which thenexus-observabilityregistry already adds globally, so Prometheus rejected every worker scrape (label name "component" is not unique) — every NAV panel stayed empty andNexusNavStalekept firing although snapshots existed. The label isnav_componentsince nexus-platform513a173/d128e17and dashboard gitopsa5912c7; the rule is under Operations → Metric labels.
| Item | Status | Detail |
|---|---|---|
Worker job treasury_nav (hourly, NAV_INTERVAL_SECS) | deployed ac38aa1 | Values every component, publishes a snapshot to accum_nav_snapshots: per-component valuation with known / unknown / stale; USDT numeraire via Coinbase USDT-USD / USDC-USD spot; TWR as a chain-linked index; high-water mark and drawdown; MWR / XIRR over 30 d and since inception; exposure SOL / BTC / STABLE; attribution market / yield / trading / costs. |
accum_external_flows | deployed ac38aa1 | Operator-recorded deposits and withdrawals (see below). Auto-detection from chain history is phase 2 — not implemented. |
accum_benchmarks + accum_benchmark_config | deployed ac38aa1 | Frozen at inception: cash at an assumed APR; hold_btc_sol 50/50 at inception marks; dca_btc_sol weekly over 180 d; ladder_passive = the live rungs frozen at inception, firing at trigger with 0.3 % slippage. Every benchmark receives the same external flows as the live book. |
accum_costs | deployed ac38aa1; signer-side capture live since 17:46 UTC (nexus-solana a74cc87) | Transaction / priority / venue fees read from the signer's rows, plus an operator-set monthly infra + AI figure accrued pro-rata. What each row means and where it comes from: Costs. Ingestion is incomplete (route-only sends, infra / AI figure) — see Measurement qualifications. |
| Core read API | deployed ac38aa1 | GET /v1/desk/nav, GET /v1/desk/nav/history?from&to&step, GET /v1/desk/nav/flows; POST /v1/desk/nav/flows is the one mutation and goes through the normal Core auth middleware. |
Metric exporter (worker :9100) | deployed ac38aa1 | The contract below, verbatim. |
Inclusion map — each economic asset exactly once:
| Component | Counted as |
|---|---|
| EXEC wallet | SOL, USDC, USDT, JitoSOL, cbBTC |
| VAULT wallet | same assets |
| Lending supplied, per protocol | redemption value, optional liquidity haircut |
| Sleeve open-order escrow | buy escrow USDC + sell escrow coin; the ledger inventory sitting in that escrow is not added again |
| LP positions | both legs + uncollected fees |
| CEX balances, per exchange | as reported; unavailable ⇒ unknown, never zero |
NAV is published from the known components with unknown_components
listed; NexusNavIncomplete alerts when that list stays non-empty.
Metric contract (what the dashboard and rules read):
| Metric | Labels | Meaning |
|---|---|---|
nexus_nav_usdt | — | Total NAV in USDT (known components) |
nexus_nav_usd | — | Same, in USD |
nexus_nav_component_usdt | nav_component, asset | Per-line valuation (component is reserved by the registry) |
nexus_nav_unknown_components | — | Count of components valued unknown |
nexus_nav_snapshot_age_seconds | — | Age of the latest snapshot |
nexus_nav_hwm_usdt | — | High-water mark |
nexus_nav_drawdown_frac | — | Peak-to-now drawdown, net of flows |
nexus_nav_return_frac | window = 1d / 7d / 30d / inception | Time-weighted return. Periods valued before ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ) treat flows as period-end; from that revision each period is flow-timed and labelled in meta.twr.method (TWR method labels, qualifications) |
nexus_nav_mwr_frac | window = 30d / inception | Money-weighted return (XIRR) |
nexus_nav_exposure_frac | asset = SOL / BTC / STABLE | Exposure share |
nexus_nav_exposure_usdt | asset | Exposure in USDT |
nexus_nav_attribution_usdt | bucket = market / yield / trading / costs, window | Attribution |
nexus_nav_costs_accrued_usd | — | Costs accrued to date |
nexus_nav_external_flow_usdt | direction | Recorded external flows |
nexus_benchmark_nav_usdt | benchmark | Benchmark NAV on the same flows |
nexus_benchmark_excess_return_frac | benchmark, window | Live minus benchmark |
nexus_mark_usd | asset | Mark used |
nexus_mark_age_seconds | asset | Age of that mark |
nexus_exec_up | — | Signer reachable |
nexus_exec_armed | — | Signer armed |
nexus_exec_paused | — | Runtime pause active |
nexus_exec_auth_decisions | decision | Caller-auth decisions |
nexus_exec_policy_denials | code | Policy-gate denials |
nexus_exec_policy_spent_today_usdc | — | Daily cap spent |
nexus_exec_policy_max_daily_usdc | — | Daily cap |
nexus_sleeve_unresolved | — | Ledger ≠ wallet flag |
nexus_sleeve_committed_usdc | — | Sleeve committed capital |
nexus_sleeve_realized_usdc | — | Sleeve realized PnL |
nexus_sleeve_open_orders | side | Open sleeve orders |
nexus_outbox_rows | state | Ladder outbox rows |
nexus_ladder_rungs | state | Ladder rungs |
Costs — what accum_costs contains
The costs:* P&L sources and nexus_nav_costs_accrued_usd are built
from accum_costs. The rule (nexus-platform 8e1e0cb, in ee0cd80,
deployed 2026-09-05 ≈ 23:4xZ; published verbatim as
/v1/desk/nav.costs.rule): on-chain fees and rent are booked by
signature for attribution only (costs:tx, costs:venue) — the wallets
already paid them, so NAV never subtracts them again; costs_accrued_usd
deducts off-portfolio costs only (infra, ai_tokens). Before that
revision the same fields were parsed from sleeve rows only and route-only
sends were never persisted.
Durable, keyed by signature. A row's _id is
<kind>:<signature>:<field> and the insert is insert-once, so the same
send reported by up to four feeds — a sleeve row
(/v1/sleeve.recent_trades[]), an outbox fill
(/v1/ladder.outbox.fired[]), the answer of the route the worker called
(costs on /v1/lending/*, /v1/stake/*, /v1/swap/execute,
/v1/squads/fund-exec; persisted at the 11 record_route_costs call
sites in the worker's mob_jobs.rs since ee0cd80) and the day's
aggregate (/v1/status.costs_today) — lands on the same rows. A fill's
fee legs stay inside the sleeve round trip (sleeve trade fee note) and
are excluded from costs:tx. costs_today carries no signatures, so it
cannot be booked per send: it is persisted on the snapshot
(meta.costs.exec_today) and reconciled against the signatures booked that
day — unbooked_sends_today = sends the exec made that have no durable
row (sends made outside the worker, or before a call site persisted its
answer); the exec's costs_today.entries[] with signatures is a recorded
follow-up. /v1/desk/nav.costs exposes
{accrued_off_portfolio_usd, on_chain_usd, by_kind_usd, exec_today, rule}.
The signer reports every confirmed send in one vocabulary
(nexus-solana apps/nexus-solana-exec/src/costs.rs, c483bbd, live since
2026-09-05 17:46 UTC with a74cc87):
| Signer field | NAV cost kind | Meaning |
|---|---|---|
fee_lamports | tx_fee | base signature fee(s) the owner paid (signatures × 5000) |
priority_fee_lamports | priority_fee | the compute-budget priority portion |
tx_fee_lamports | — (not booked; = the sum of the two above, meta.fee when the owner paid) | the two fields above partition the transaction fee, so the parser sums both and never counts the total as well |
rent_lamports | rent | net rent locked (+) / released (−) across the tx boundary (a temporary wSOL account cancels out). Option<i64> since nexus-solana 4804e26 (2026-09-06): null when the figure could not be defended, with rent_unattributed_lamports + rent_note beside it and rent_usd absent with it. Booked only when the pair is not native-SOL — Cost rules |
rent_unattributed_lamports, rent_note | — (never booked) | the rejected figure and why, when a coin leg leaked into the rent delta or the number exceeded SLEEVE_RENT_SANITY_LAMPORTS (0.1 SOL) |
sol_usd_mark, tx_fee_usd, rent_usd | — | the lamport figures at the oracle's SOL mark; absent when the mark is unknown, never 0 |
received_qty, spent_qty | — | realized quantities in UI units (base units in received_base / spent_base) |
venue_fee_base, fee_usd | swap_fee | the venue fee only — e.g. the Jupiter Trigger output fee visible in a sleeve fill (expected output − received), in USD at the coin mark. fee_usd is never the transaction fee in USD. |
Sources the NAV job reads, each once:
/v1/sleeve.recent_trades[]— filled sleeve rows (written at fill time; derived for older rows). This is what the parser inac38aa1consumes (fee_lamports→tx_fee,priority_fee_lamports→priority_fee,fee_usd→swap_fee)./v1/ladder.outbox.fired[]and each outbox row'sfill.costs— from the outbox phase-2 tx-meta verification. Empty in prod whileEXEC_OUTBOX=0(rungs fired on the legacy path carry no cost fields)./v1/status.costs_today—{since, sends, tx_fee_sol, base_fee_sol, priority_fee_sol, rent_sol, fee_usd, unpriced_sends, sources}, since 00:00 UTC, keyed by signature over the in-memory ledger merged with the day's persisted rows (accum_outboxfills,accum_tradesfills); route-only sends (lending, Squads, stake, LP) are in memory since boot on the exec's side. Sinceee0cd80the worker persists those route answers itself (above) and reconciles this aggregate asunbooked_sends_today; it is still not a NAV deduction (on-chain, already out of the wallets).- The operator-set monthly infra + AI figure (
NAV_MONTHLY_FIXED_COST_USD, kindinfra), accrued pro-rata — the only cost NAV deducts.
Rows from sends before the a74cc87 rollout (17:46 UTC) have no fee
fields; until a costed send lands, the costs bucket holds the infra / AI
accrual only (the re-verification at 19:12 UTC read 0 accrued costs);
the dashboard's "flows & costs" panels read whatever accum_costs holds.
Explicitly not built
- Benchmark 5, the same system without AI timing decisions — not implemented; nothing isolates the AI's contribution yet.
- External-flow auto-detection from chain history — phase 2; flows are operator-recorded.
- Historical reconstruction — the NAV series starts the day the job first runs. There is no back-filled history, so benchmark comparisons and drawdown figures are meaningful only after weeks to months of data.
- Forecast resolver / Brier scoring (audit M3) — unchanged, not started.
- Scenario tests in the table above — none automated.
- Cumulative lending interest "since deposit" — the exec exposes
supplied_ui/apyonly, so lending yield is derived per period (Δ balance − own flows) and cannot be restated from a deposit's cToken position; open item for nexus-solana (P&L by source). ladder_realized:<asset>— the source exists in the vocabulary but is never emitted: there is no sell path for fired rungs.costs:slippageand anunattributed_pre_ledgerbucket (with aledger_starts_atmarker) — specified 2026-09-06, no code: verified absent from nexus-platform at63a2de3. Until they exist, the execution cost of an internal conversion and every period before the flow ledger began land inunexplained— the residual.
How the operator records an external flow
Every deposit into or withdrawal from the book (EXEC, VAULT, a CEX account) must be recorded, otherwise it shows up as return. Once the API is deployed:
POST /v1/desk/nav/flows # through the normal Core auth middleware
{ "direction": "in" | "out", # "deposit" / "withdrawal" also accepted
"asset": "USDC", "qty": 1000,
"at": "<RFC 3339 — when the money moved>",
"tx": "<signature, optional>", "note": "…" }
The row lands in accum_external_flows with source: manual,
recorded_at and the caller's identity; value_usdt is computed at the
marks of at. The NAV job applies each flow exactly once — in the first
snapshot whose period contains recorded_at, so a flow recorded late is
still counted (in the next period) — excludes it from TWR, includes it in
MWR, and replays it into every benchmark. Field names are those of the
local ExternalFlow record (nexus-platform 6dbd6ce); confirm against the
deployed route before scripting. Until the route is live there is nothing
to call; do not edit accum_external_flows by hand.
Honesty notes
- A NAV series that starts today has no history; benchmark comparisons are meaningful only after weeks or months.
- Three profitable sleeve closes are not alpha.
- The sleeve's realized USDC ledger profit is not the USDT NAV return; the two will diverge as soon as unrealized moves, yield, costs and the USDC/USDT rate enter.
NexusNavStalefired until the first snapshot was scraped (17:4x UTC, after thecomponentlabel fix) — not until the job ran (17:25 UTC).- Execution costs enter NAV only from sends the exec image with
a74cc87confirms; earlier trades are un-costed inaccum_costs. - The first P&L period (17:25 → 18:25 UTC) contains the 17:37 UTC Kamino
rotation, made before internal-flow recording existed; its Kamino row is
not interest and the period stays a gap in
unexplained— see P&L by source → dry run. - Kamino realised APR vs quoted is now observable
(
lending:kaminorows,nexus_lending_apr_realized{venue="kamino"}): the first live hour (2026-09-05 21:12 UTC) showed +0.24 USDT ≈ 3.7 % annualised against 11.0 % quoted on the JLP reserve. Over 12 clean hours on 2026-09-06 the measurement was +2.96 USDC ≈ 3.8 % annualised, and at the 10:09:48Z snapshot the reconciled row read +3.04 since inception, realised APR 3.63 %, statuscontaminated. That is not a verdict on JLP: the reportedsupplied_uicarries ±$2 of noise on a $56k balance and fell in 2 of 11 hours, and the venue rotation of 2026-09-05 17:37 UTC contaminates the series (lending reconciliation). Judge the real rate frominterest_clean_usdt/apr_realized_cleanover a day with no rotation in it. - The first day's P&L was wrong, not merely young. A cost sign, a rent attribution and a derived profit produced a +1,226.46 round trip and a +1,068 cost row that never happened. It is repaired in the stored series (Cost rules), and it is the reason this page now states a rule for every figure it prints.
Measurement qualifications (owner re-verification, 2026-09-05 19:29–19:34 UTC)
The owner re-verified the series independently against the 19:12:02 UTC
snapshot (worker 7e1adcb, before the per-source rows existed) and rated
it "measurement implemented and live; validation immature". Observed
then: in-scope NAV 91,106.75 USDT (inception 91,138.32 at 17:25 UTC),
inception TWR −0.03464 %, attribution total −31.57 USDT, marked SOL
exposure 20.75 %, BTC 0.004 % (dust), stables 79.25 %, budget SOL after
escrow + ladder reservations 69.37 % vs the 60 % cap, 1d/7d/30d windows
null, accrued costs 0, no unknown components within the configured scope.
The negative two-hour return is an observation, not a strategy verdict;
the cash / hold / DCA / passive-ladder series exist but cannot establish an
edge on this history; the frozen comparator assumptions (hold 50/50, cash
4 % APR, 0.3 % modeled slippage) are configuration, not verified yields or
costs. The qualifications, each of which this page now carries:
| Qualification | What it means for the numbers | Status |
|---|---|---|
| Scope — in-scope NAV | Twelve non-core CEX asset entries (outside NAV_CEX_ASSETS) are listed as out of scope and unvalued. Call the figure the in-scope strategy NAV until every material holding is valued or explicitly excluded from the mandate. Labelled since 0a58575 / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): /v1/desk/nav returns scope: "strategy", in_scope_nav_usdt (= nav_usdt, kept for compatibility) and out_of_scope{value_usdt_estimate, unpriced_entries, entries[{exchange, asset, qty, usdt_estimate | null, priced, pricing}]} — cex-manager's usdValue is an estimate, never counted; when built all 12 entries were unpriced (cex-manager reports 0 for them). | labelled — owner scope decision still open |
| External flows | Only operator-recorded flows existed at 19:12 UTC. Residual detection since 8e1e0cb / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): every snapshot compares the on-chain pool (wallet_exec, wallet_vault, sleeve_escrow, lending, lp) with the previous pool repriced at current marks, nets the sleeve fills' price edge, and treats a residual beyond NAV_FLOW_DETECT_MIN_USDT (50) + NAV_FLOW_DETECT_TOL_FRAC (0.01) × Σ|internal flows + fills| as a flow the ledger does not know: written to accum_external_flows with source: detected_unconfirmed (dated at the snapshot, wallet / asset by the largest move in that direction; skipped when a pool component is unknown), applied to the period like a manual flow, listed under /v1/desk/nav.flows.detected_unconfirmed for the operator to confirm or offset via POST /v1/desk/nav/flows. Unconfirmed because the exec exposes no wallet-history route (GET /v1/chain/transfers is the recorded follow-up; with it source: detected with signature and exact time). | addressed (unconfirmed detection) — signature-confirmed capture open |
| TWR approximation | At 19:12 UTC the period return treated net flows as arriving at period end. Flow-timed since 8e1e0cb / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): the period is split at each flow and the portfolio is valued at the flow's time on marks interpolated between the two snapshots, with a constant residual rate solved so the chain ends at the observed NAV; each period carries meta.twr.method and keeps period_return_end_of_period as the comparison figure — TWR method labels. Periods before the roll keep their end-of-period figure. | addressed — labelled per period |
| Attribution | At 19:12 UTC the coarse attribution.yield bucket was defined as the residual after market, realized trading and costs — the ≈ −28.20 USDT "yield" seen then was an unexplained residual, not negative protocol yield. Superseded since nexus-platform 7d6ed59 (worker rolled ≈ 20:3x UTC, first rows 21:12 UTC): yield is accrued per source (jito_staking, lending:<venue>, lp_fees:<pool>) and the residual is its own row, unexplained — which at 21:12 UTC was −95.61 USDT, the pre-recording 17:37 UTC rotation gap (P&L by source). | superseded by 7d6ed59 |
| Costs | Live NAV reported zero accrued costs at 19:12 UTC: route-only sends (lending, Squads, stake, LP) were kept in memory since boot and the worker's fee_costs read sleeve rows only. Since 8e1e0cb / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): rows are keyed <kind>:<signature>:<field> and insert-once across the four feeds, the 11 route call sites persist their answers' costs, costs_today is reconciled as unbooked_sends_today, and the double-subtraction rule is code and API text — on-chain fees / rent are attribution only (costs:tx / costs:venue), costs_accrued_usd = infra / AI only (Costs). The infra / AI figure is still whatever NAV_MONTHLY_FIXED_COST_USD says (0 unless set). | addressed — durable, deduplicated; infra figure operator-set |
| Learning | Brier resolver / calibration, the no-AI counterfactual (benchmark 5) and out-of-sample evidence remain missing. | open |
Owner priority 5 (register Next):
validate the NAV and cost / flow series, label scope and attribution
honestly, then evaluate benchmarks over meaningful history. The batch
above rolled and was live-verified 2026-09-05 23:4xZ (ledger seeded at
version 2, verdict available, in-scope NAV 90,981). It is the
measurement that is validated, not the result: the very next day the
P&L correctness batch found the first day's figures were wrong
(the correction), and a day of history validates
nothing.
TWR method labels (meta.twr.method)
Written by the worker on every snapshot since 8e1e0cb and returned as
/v1/desk/nav.twr ({method, period_return, period_return_end_of_period, sub_periods[]}):
method | Meaning |
|---|---|
no_flows | no external flow in the period: nav / nav_prev − 1, exact |
flow_time_interpolated_marks | the period was split at each flow; the value at a flow's time is the previous snapshot's holdings repriced on marks interpolated linearly between the two snapshots (SOL-family and BTC units × mark, stables flat), times a constant residual rate solved so the chain ends at the observed NAV (yield, fills, costs and unexplained assumed to accrue uniformly) |
flow_time_flat | same split, but a coin mark was missing at one end, so the value path is the constant residual rate alone |
end_of_period_fallback | the constant-rate solve did not converge (a withdrawal larger than the modelled value at its time): the end-of-period figure is used and labelled |
period_return_end_of_period is the old approximation, kept on every
period as the comparison figure (the benchmark simulators still apply
their flows at the step).
P&L by source (accountability)
nexus-platform 3a91236 (records) → 2c79b0e (worker accrual + internal
flows) → d76eb11 (Core routes) → 750c5ce (gauges) → 3646865 / 7d6ed59
(tests, dry run), pushed ≈ 20:1x UTC, worker rolled ≈ 20:3x UTC; UI
section nexus-ui f8327f1; dashboard row nexus-gitops b5b6079.
First live rows at the 21:12 UTC snapshot (/v1/desk/pnl?window=all; the
identity holds). The figures in the dry-run section below come from the
offline run over captured live data.
The owner's requirement, as stated: see the winning part for JitoSOL, the
Kamino lending and other winnings, and the profit for the swing orders
buy/sell — so we can see easily from where we are winning money. The
attribution buckets above (market / yield / trading / costs) answer it
only in aggregate; this ledger answers it per source, and it is reconciled
to NAV every hour.
The identity
For every snapshot period (previous snapshot → this one):
ΔNAV − external flows = Σ explained sources + unexplained
unexplained is a row like any other — written, exposed in the API and the
dashboard, never folded into a neighbour. A period that does not reconcile
shows a large residual, not a wrong yield figure. The worker job
treasury_nav computes the decomposition after each snapshot
(apps/nexus-worker/src/treasury/pnl.rs) and writes one row per source
into accum_pnl_sources (_id = <snapshot_id>:<source>); realized and
accrued events go to accum_pnl_events (_id = <source>:<ref>, so a fill
is booked once however often the ledger is re-read). Windows (1d, 7d,
30d, inception) are sums of period rows; all adds the sleeve round
trips that predate the NAV series.
Source vocabulary
Fixed in crates/nexus-db/src/treasury.rs (pnl_source); any other string
is rejected. Groups are what the dashboard and the coarse attribution
buckets roll up to.
| Source | Kind | Group | Meaning |
|---|---|---|---|
jito_staking | accrual | yield | JitoSOL qty × Δ(pool rate) × SOL mark — the staking reward, separated from the SOL price move |
lending:<venue> | accrual | yield | per protocol (kamino, marginfi): Δ supplied balance − net own supply / withdraw flows in the period |
lp_fees:<pool> | accrual | yield | Δ uncollected LP fees (each snapshot at its own marks) + fees collected (lp_collect flows). Read the whole rule — this was implemented against a field the exec does not publish and reported 0 until 8123882: The LP fee defect |
sleeve_realized:<pair> | realized | trading | a filled swing sell: proceeds − lot cost − venue fee − tx fees |
ladder_realized:<asset> | realized | trading | a fired rung later sold — never emitted, no sell path exists |
sleeve_unrealized:<pair> | market_mark | market | Δ of open sleeve inventory's (mark − vwap) × qty; nets to 0 over a round trip's life |
market:SOL | market_mark | market | SOL-family units held at period start (incl. JitoSOL × rate, excl. sleeve inventory) × ΔSOL |
market:BTC | market_mark | market | BTC-family units (excl. sleeve inventory) × ΔBTC |
market:STABLE | market_mark | market | stable balances × Δ(USDC / USDT mark in USDT) — peg drift, so it does not land in unexplained |
market:LP | market_mark | market | LP position legs drift, net of add / remove flows at the snapshot's marks. The uncollected-fee legs are excluded, so no dollar is both yield and drift |
costs:tx | fee | costs | on-chain tx / priority / rent fees not already inside a sleeve fill. Since 2553748: never ≤ 0, never above PNL_COST_SANITY_USD, and rent_* is skipped whole on a native-SOL pair — Cost rules |
costs:venue | fee | costs | swap / CEX fees not already inside a sleeve fill; same three rules |
costs:infra | fee | costs | infra + AI accrual (NAV_MONTHLY_FIXED_COST_USD) |
unexplained | residual | unexplained | ΔNAV − external flows − Σ of everything above |
How each source is derived
All amounts are USDT. A quantity change is valued at the end snapshot's marks; a price change uses both ends.
- JitoSOL (
jito_staking):qty × (rate₁ − rate₀) × SOL₁. The SOL price move on the same coins ismarket:SOL(qty × rate₀ × (SOL₁ − SOL₀)), so the split is exact:q·(r₁p₁ − r₀p₀) = q·r₀·(p₁ − p₀) + q·(r₁ − r₀)·p₁. The pool rate steps at epoch boundaries (≈ 2–3 days), so hourly rows show zeros and then one step — that is the reward arriving, not a bug.nexus_staking_apr_realizedannualises the rate change since inception. - Lending (
lending:<venue>):Δ supplied balance − net internal supply / withdraw flowsin the period, valued at the end mark. The exec's/v1/positionsexposessupplied_uiandapyonly — no cToken quantity or exchange rate — so this period method is the only derivation, and a cumulative "interest since deposit" cannot be restated from the position itself. The reported APY rides along in the row (apr_reported) next to the realised one (apr_realized, interest over the average balance, annualised). Open item for nexus-solana: expose cToken qty + exchange rate. - Swing round trips (
sleeve_realized:<pair>): built from the complete sleeve ledger by linking each filled sell to its buy lot(s) (lots[], orparent) — entry and exit price, cost, proceeds, fees, profit, holding time. A sell that closed before the NAV inception (2026-09-05 17:25 UTC) ishistorical: true: it appears in the cumulative table and inwindow=all, never in a period row or in TWR.fees_unknown: truewhile neither row carries cost fields — true for every fill before the exec image witha74cc87; new fills carryfee_lamports/priority_fee_lamports/fee_usd(Costs). - Open swing inventory (
sleeve_unrealized:<pair>): open lots (including the coins sitting in a resting sell's escrow) × (mark − vwap), booked as a period delta. Sleeve inventory is excluded from themarket:*units, so a position's price move sits here while open and is reversed at the fill — over a round trip's life the source nets to 0 and the full profit lands insleeve_realized, which is what the owner reads. - Market (
market:SOL|BTC|STABLE|LP): units held at the period start × Δprice; stables × Δ(USDC/USDT) so peg drift is named. - Costs (
costs:tx|venue|infra): the period'saccum_costsrows, minus the fee legs already inside a sleeve fill (sleeve trade feenote), so nothing is counted twice.
Internal flows. Separating a deposit from interest needs the worker's
own movements. Since 2c79b0e every gate call site records the confirmed
send in accum_internal_flows (_id = <kind>:<ref>): lending_supply,
lending_withdraw, stake, unstake, lp_add, lp_remove, lp_collect,
swing_buy, swing_sell (hygiene rotate / repark, swing placement,
stake, LP open / collect / close in mob_jobs.rs). The kind ladder_fire
exists in the vocabulary but has no call site: rungs fire on the exec
plane and ladder fills are read from /v1/ladder. Anything moved before
the flow ledger existed lands in unexplained by construction, with the
period's inputs in the row's detail so the operator can chase it — as
does swap slippage on DCA / stake conversions, CEX balance changes and
Kamino balance rounding.
Cost rules — what is not a cost (2026-09-06)
nexus-platform 2553748 (apps/nexus-worker/src/treasury/costs.rs),
deployed 2026-09-06. A cost is a positive, small number. Three rules
keep a mis-parsed figure out of the ledger; each one alone would have
caught the defect where the exec reported the sold SOL leaving the order
escrow as rent (rent_lamports −10,005,519,280, −1,068 USD) and the P&L
subtracted it, inflating a 159 USDC round trip to 1,226.
- Never negative. A cost field ≤ 0 (or non-finite) is ignored — never booked, never subtracted from a profit. This retires the old "a rent refund is a negative row" behaviour: a refund is a balance change NAV already sees, not a credit to attribute.
- Never implausible. A single on-chain row above
PNL_COST_SANITY_USD(default 25) is not a fee. It is recorded asCostKind::Unattributedwith the raw value in the note, is in no cost bucket, and is surfaced in/v1/desk/pnl.notesandcosts.unattributed_usdt. - Never the coin leg. For a native-SOL pair the
rent_*fields are skipped outright: the SOL being bought or sold moves through the order escrow / wSOL account, so its lamports land in the exec's rent delta and are the trade, not a cost.native_sol_pairis true when either side of<A>/<B>isSOL;JITOSOL/USDCis not — JitoSOL is an ordinary SPL token with an ordinary ATA.
Every refusal increments nexus_pnl_cost_anomalies_total{field} (one
call site passes a CostKind string rather than a field name, so read the
label as "which figure was refused", not strictly as a field). The exec
side of the same rule is TxCosts::attribute_rent —
nexus-solana → Rent.
The repair pass. The rules stop a bad figure entering the ledger, but
rows already in Mongo would keep the defect alive for ever: an
accum_costs row is insert-once on a deterministic _id, so is an
accum_pnl_events row, and an accum_pnl_sources row belongs to a period
that will never be recomputed. So the hourly treasury_nav job runs a
repair pass inline, before it reads the period's costs (there is no
separate job to trigger). It is idempotent — a clean series makes it a
no-op — looks back LOOKBACK_DAYS 30, and does three things in order:
- Purge bad cost rows — every on-chain row (one with a signature) in
the lookback whose amount fails the rules is deleted and logged.
Deleting is the right repair: the
_idis deterministic, the feeds no longer book it, and a delete run twice is still a delete. Rows already recordedunattributedare kept — they are the audit trail and are in no bucket. - Recompute the sleeve events from the exec's own
recent_trades[]under the current rules, overwriting when the amount moved. - Rewrite the affected period rows, moving that period's
unexplainedby the opposite amount sodelta = explained + unexplainedstill holds. Since63a2de3a lending period is re-judged againstPNL_LENDING_SANITY_MULTfrom the working the row already carries.
Nothing in the pass reads a price feed or the chain: the marks are the
ones the original rows were written with, so a repair never re-marks
history. What one run changed is reported on
/v1/desk/pnl.costs.repair (costs_deleted, events_fixed, rows_fixed,
notes, sanity_usd, rule).
Profit source — the exec's figure, or ours
nexus-platform 2553748. A round trip's profit_usdc is the exec's own
realized_pnl_usdc whenever the exec read the confirmed transaction —
i.e. the fill row carries realized_pnl_usdc and
proceeds_source: "tx_meta". Every round trip says which:
profit_source | Meaning |
|---|---|
exec_realized | the exec's realized_pnl_usdc, taken verbatim from token deltas on the confirmed transaction (exposed on the platform side as exec_realized_usdc) |
derived | the platform computed proceeds − lot cost − known fees because the row carried no such figure — fees_unknown is only ever set on this path |
sleeve.profit_source_counts ({exec_realized: n, derived: n}) says how
much of the cumulative table is which. sleeve.exec_realized_total_usdc
carries the exec's own all-time total beside the platform's.
Refused fee fields are listed per round trip in round_trips[].notes[],
so a trip whose profit looks too clean can be traced to the figure that
was thrown away.
Window coverage — a young series is not a flat one
nexus-platform 72967bd. Before it, 1d / 7d / 30d on a series hours
old resolved to from == to, 0 periods, delta 0 — which the UI drew as
"the desk made nothing", not "the desk is four hours old".
resolve_window now clamps a requested window to the series' own start and
says so. /v1/desk/pnl and /v1/desk/pnl/history carry a coverage
object — requested_days, covered, truncated_to_series,
series_starts_at, covered_days, and a note that is null when
covered is true and a sentence otherwise. /v1/desk/pnl also lifts
covered, truncated_to_series and series_starts_at to the top level;
/v1/desk/pnl/history adds covered_from_ms.
/v1/desk/nav gains returns_coverage — one object per rolling window
(1d, 7d, 30d) with covered, truncated_to_series,
series_starts_at, covered_days. It is hand-built by the worker and,
unlike the P&L coverage, carries no note and no requested_days. A
truncated window returns the since-inception figure with covered: false, never null.
The UI's half of the contract is on
nexus-ui: it opens on the
largest covered window and renders an uncovered cell as n/c, never 0.
Lending reconciliation — interest that ties to its own balances
nexus-platform 72967bd / 4b5a181 (crates/nexus-db/src/treasury.rs,
lending_window), shared verbatim by the worker and the Core API so the
two cannot disagree. The old block put balance_start, balance_end and
interest_usdt side by side without stating how they related — and on
2026-09-06 they did not: the balances differed by +38.03 while the
interest claimed 2.96.
Every lending[] row now carries the identity
balance_delta_usdt = net_flows_usdt + interest_usdt + unattributed_usdt
verbatim in an identity field, with unattributed_usdt computed as the
remainder — so a row that does not tie says so in its own numbers rather
than hiding the gap in interest.
Contamination. A period is contaminated when the venue label changed
between its ends, when the period was already flagged over_sanity, or
when |Δ balance| > PNL_LENDING_SANITY_MULT × |accrual implied by the venue's own reported APR| (default 10). Such a period is not
interest — it is a reserve rotation, a migration, or a read of a different
position. Contaminated periods are listed in contaminated_periods[]
(at, snapshot_id, delta_qty, implied_qty, hours, reason) and
excluded from the clean figures: interest_clean_usdt, clean_hours
and apr_realized_clean sit beside the raw interest_usdt,
apr_realized and the venue's apr_reported. negative_periods counts
the periods whose balance fell.
The same rule runs per period in the worker (interest_qty,
implied_qty, over_sanity, booked_qty — 0 when over) and is what the
repair pass re-judges for stored rows.
Note: these contamination sentences appear on lending[].notes and on
earning[].note, not in the top-level /v1/desk/pnl.notes.
LP as legs
nexus-platform 72967bd (apps/nexus-worker/src/treasury/nav.rs). Until
it, an LP position was one NAV component whose asset was the
uppercased pool address and whose qty was 0, even though the
position held 10 SOL — so exposure.SOL excluded the LP's coin leg, and
with it the F6 sol_exposure cap input.
Now a position is decomposed into ordinary holdings:
- one
lpcomponent per leg (SOL,USDC, …) with the realqty, valuedqty × marklike any other holding, at venue<protocol>:<pool>—orca:<pool>when the exec reportsorca; the protocol is read from the exec's ownvenue/protocol/dexfield, not hard-coded; - uncollected fees on their own line at venue
<protocol>:<pool>:fees— they are earnings not yet collected, not principal; - the note on each line names the position and its range
(
orca SOL/USDC position <id>; range 105.9600–112.0200; in_range=false).
Because the legs are ordinary components, each one lands in its exposure
family by its own asset symbol — no special case in exposures(). A leg
whose qty is 0 emits no line at all.
When a position cannot be decomposed (no readable <BASE>/<QUOTE>
pair, or a missing mark) the exec's own value_usd + fees_usd is kept
under the readable pair label and the note says the quantity is in no
exposure family — the honest fallback, not a silent zero.
Measured effect at the 10:09:48Z snapshot: exposure.SOL 18.71 % →
19.87 %. This was an exposure and cap understatement, never a NAV
understatement — the LP's value was already counted.
The LP fee defect — real fees reported as zero yield
nexus-platform 8123882 / ebf42e6 / 2f9c0db, nexus-ui 2d91151 /
41dff12 / 0fc6392. Found 2026-09-06 by the owner, who could see the
liquidity page reporting $1.00 of uncollected fees on the Orca SOL/USDC
position while Treasury reported that same position earning nothing.
Both pages were reading the platform correctly. The platform was wrong, in two independent places.
1. lp_fees measured a field that does not exist. The decomposition
read the uncollected balance from fees_usd / unclaimed_fees_usd /
pending_fees_usd. The exec publishes fee_coin_ui and fee_usdc_ui,
and none of those three. So fees_start_usd and fees_end_usd were 0.0
every period by construction, and lp_fees was structurally incapable of
being anything but zero. Meanwhile the exec's value_usd — which
market:LP differenced — includes the uncollected fees, so every dollar
of fee income was silently booked as market drift. The money was never
lost from NAV; it was filed under the wrong heading, which is exactly the
question the owner was asking the page.
The fix measures the fee legs the NAV decomposition already emits (the
<protocol>:<pool>:fees lines above), and takes them out of market:LP:
lp_fees:<pool> = Δ(uncollected fee legs, each snapshot at its own marks)
+ fees collected in the period
market:LP = Δ(position legs) − adds/removes at this snapshot's marks
The Δ is split further in the source detail, the way staking is split:
q₁p₁ − q₀p₀ = (q₁−q₀)p₁ + q₀(p₁−p₀), giving fees_accrued_usdt (fees
that actually arrived) beside fees_price_move_usdt (the price move on fees
that had already arrived). A leg present at only one end is priced with the
one unit price that exists, so the whole move lands in the accrual and
nothing is invented.
A collect is not a fee loss. Collecting moves the same dollar from the
fee legs to the wallet, so the pair (Δuncollected + collected) is the
income. lp_collect previously recorded only the USDC half in value_usdt
("USDC side only; coin side per quote"), which would have read as a loss;
both legs now go into legs.outputs.
2. The earning[] LP row keyed the source wrongly. lp_state keyed the
pool as <POOL uppercased>@<venue> and the API built the source id as
lp_fees:<key>, producing lp_fees:CZFQ…@orca while the row had been
written as lp_fees:CZFQ…. Neither earned_*_usdt nor since ever
matched, so the panel showed 0.0 and null regardless of what the
decomposition found.
The pool id is now the exec's own string, never re-cased. Base58 is
case-sensitive; uppercasing it makes lp_fees:<pool> name a pool no
component belongs to. The UI matches pool ids case-insensitively as well, so
a stale uppercased row still resolves.
The repair pass rewrites the history it wrote wrong. It replays the same
pure period function over the stored snapshots' own components and marks,
deletes the stale lp_fees:<UPPERCASE> rows, and moves each period's
unexplained by the opposite amount so delta = explained + unexplained
still holds. Idempotent, and it is what makes the inception figures below
appear rather than only future hours.
A realized APR needs a day behind it. apr_realized_inception is null
while age_days < 1, with the reason in note. The liquidity page applies
the same 24-hour floor. Annualising four hours of fees turns $0.88 into a
headline percentage the position has never earned.
Live-verified at snapshot nav_1788701605541 (2026-09-06T13:33:25.541Z):
lp_fees:Czfq3xZZ… +1.3232 USDT (was 0.0000, and the id now carries the
exec's own base58 case; +1.3233 with e6e1fd2, one lp[] row whose
drift_usdt is the whole +10.7763 and nothing unattributed),
market:LP +10.7763, groups.yield 5.1183
= Kamino 3.7950 + LP 1.3232 + Jito 0.0001, and the earning[] LP row
earned_inception_usdt +1.3232 on principal 1,061.05 since
09:13:02.486Z with apr_realized_inception null at 0.18 days. The
three-way identity is exact over 18 periods: 5.1183 + 159.4049 + 312.9606 −
0.4755 − 95.6093 − 6.3612 = 375.0378 = delta_usdt.
One position keeps one identity
nexus-platform 8839c8d / e6e1fd2. The fee split above immediately
surfaced a second defect it had not caused: lp[] returned two rows for
the one Orca position — the real pool with drift_usdt +1061.05 and a
phantom orca row with −1050.28, plus an empty lp_fees:orca source. They
netted to the correct market:LP and the identity held, but neither figure
was a drift. It was the position's whole value appearing under one identity
and vanishing under the other, across the LP-as-legs boundary at
≈ 10:09Z. market:LP had been a single number, so the artifact was real but
unseen; splitting it per pool is what made it visible.
The shape change is sharper than "the venue gained a pool id" — the whole component changed:
09:13Z CZFQ3XZZDMSDGDUYRNLTRHGC47CXCZTLG4CRRYFU44ZE@orca = 1056.3187
↑ the asset IS the pool address, upper-cased; the venue is bare
10:09Z SOL@orca:Czfq3xZZ… = 935.5123 SOL@orca:Czfq3xZZ…:fees = 0.2095
USDC@orca:Czfq3xZZ… = 131.2648 USDC@orca:Czfq3xZZ…:fees = 0.2550
Three rules now govern pool identity, all inside the one pure period function so the hourly run and the repair pass cannot disagree:
- A relabelling is not a close and an open. One key vanishes, another
appears, same protocol, one-to-one, and no
lp_add/lp_removeon that protocol in the period ⇒ the start values carry onto the new key,detail.renamed_fromnames the old one, and the note states both identities. A genuine close-and-open leaves flows behind, fails the guard, and stays two rows. - The legacy shape names its own pool. A component whose venue carries
no
:and whoseassetis a pool identifier resolves to that pool. Two guards keep it from firing loosely: the asset must be 32–44 ASCII alphanumeric characters (SOL,SOL/USDC,LPall fail), and it must fold-match a pool id the series really carries. The canonical spelling comes from the exec's own payload, never from the upper-cased legacy string. This is recovery of a known encoding, not inference — the oldassetfield literally holds the address. - A bare protocol that names nothing stays unattributed. Its drift
remains in
market:LPand is reported aslp_unattributed_drift_usdtwithlp_rulestatingΣ lp[].drift_usdt + lp_unattributed_drift_usdt = market:LP, rather than being credited to the nearest pool.
market:LP never moves under any of the three — the defect was always a
split, never a total. The repair pass rewrites keys and details only: the
stored lp_fees:orca rows are all 0.0000, so deleting them moves
explained by nothing and leaves unexplained untouched.
Treasury page review, 2026-09-07
The operator asked for a correctness pass over the whole page. Five defects, all fixed the same day:
- A "contaminated" lending row that could never clear. Two of forty
hourly periods had failed the reconciliation — a +996 USDC step (an
operator-direct supply on 09-06 that bypassed the worker, so no flow row)
and a −127 step (a 640 USDC withdraw that delivered 769.02, the reserve's
cToken rate; the flow row recorded the request). Correcting the two rows
changed nothing, because the repair pass re-judged lending periods from
their stored working and never re-read the flows. It now recomputes each
period's net flows from
accum_internal_flowsas they stand, re-derives the interest, and re-judges under the sanity rule — so a flow row written or corrected after the fact reaches the verdict. The exec-side withdraw sizing is a separate defect on the follow-up list. - Fees collected at a close vanished from yield. A close or remove
recorded only the principal leg; the fee legs the quote carries were
dropped, so
lp_fees:<pool>saw the uncollected legs fall to zero with nothing collected and booked a loss equal to everything the position had earned. The 09-07 07:10Z close had collected 0.0157 SOL + 1.67 USDC; the worker now records anlp_collectrow beside thelp_remove, and the missing row was written by hand. - Two positions in one pool showed the last one's quantity.
lp_statekeyed by pool and the second position overwrote the first, so the earning row read 6.19 SOL of principal against a $3,101 value. Pool state now sums quantities and value across positions, is in range if any position is, and lists the ids (positions[],position_count). - The "material" badge on a $2.51 residual. The bar was 1 % of the window's Δ, which on a small window flagged dust. It is now the largest of 5 USDT, 5 % of |Δ| and 0.02 % of NAV; smaller residuals show a muted "small" badge — still shown, never hidden.
- "Black on black" bars in Sources over time. Not the palette (both themes validate against their surfaces); the 1 px surface-coloured gap drawn around every stacked segment. With 44 periods and eight series most segments are under a pixel, and an outline in the surface colour on a sub-pixel fill is a black sliver on the dark theme. The gap is drawn only on segments at least 3 px tall; a thin segment keeps its colour and a hairline of height.
- Components table: an LP line now names its position (
position Ff4o45…), since two positions in one pool share a venue label.
What the desk publishes for a pool now — lp[] gains
fees_uncollected_usdt (the balance still in the pool at window end),
fees_collected_usdt (what left it), fees_accrued_usdt, and a split
string naming the identity; drift_usdt is filled in rather than null.
The liquidity page reads them, so its "Fees collected: not available yet —
the desk P&L carries no LP rows" is gone, and the tile is now Fee income
(desk) because the figure is income, not a cash sweep.
earning — what is making money right now
nexus-platform 72967bd, served on GET /v1/desk/pnl as earning[].
There is no /v1/desk/earning route.
The owner's question is "how much yield did I win", and the window selector is the wrong axis for it: a position earns since it was opened, not since the chart's left edge. So this block is built over the whole series as well as the requested window, and every row says whether it is currently earning — an out-of-range Orca position accrues nothing however good its APY looks.
| Field | Meaning |
|---|---|
kind | lending | staking | lp |
venue, asset | the venue key and a readable asset / pair |
principal_qty, principal_usdt | what is deployed |
rate_reported | the venue's own advertised rate (null for staking) |
earned_window_usdt | this window's P&L rows for that source |
earned_inception_usdt | the same since the series began |
apr_realized_inception | annualised from what was actually earned. null under one day of age (age_days says how much), with the reason in note — annualising a few hours of fees quotes a rate the position has never earned |
age_days | how long the source has been in the ledger, the divisor the APR uses |
status | earning | idle | out_of_range | contaminated |
since | first period the source appears in |
note | see below |
Per-kind extras: a lending row adds earned_inception_clean_usdt and a
reconciliation block (balance_delta_usdt, net_flows_usdt,
unattributed_usdt); an LP row adds uncollected{coin_ui, usdc_ui, usd},
fees_uncollected_usdt, fees_collected_usdt, range and price. Status precedence for lending is idle (no
principal) before contaminated before earning; for staking, a Jito pool
rate that did not move is idle (the rate steps at epoch boundaries, ≈ 2
days — a flat hour is normal); for LP, out_of_range wins over everything.
note is not a string. The platform emits either null or a JSON
array of sentences — the lending row passes lending[].notes straight
through (possibly []), staking and LP emit a one-element array. The
API doc contract is written as string | string[] | null so a future
single-sentence note does not break a consumer, and the UI normalises
string | string[] | object | null defensively. It crashed /treasury
with e.note?.trim is not a function on 2026-09-06 before that
normalisation existed (nexus-ui 068ef0f) — the same class of bug as the
null reserves in /v1/reconcile.
The 2026-09-06 correction, and what is still unattributed
Corrected figures after the 10:09:48Z snapshot (nav_1788689388951),
platform 63a2de3 / exec ea96739:
- NAV 91,611.15 USDT, TWR index 1.005188 (+0.52 % since inception), 0 unknown components.
- ΔNAV since inception +472.83, decomposed:
market:SOL+537.96,sleeve_realized:SOL/USDC+159.40 (was +1,226.46),sleeve_unrealized−134.90,unexplained−101.96,market:LP+16.98,market:STABLE−7.22,lending:kamino+3.04,costs:venue−0.47,costs:tx−0.0005 (was +1,067.96). - Sleeve realized all-time 561.66 (was 1,628.48).
earning: Kamino 57,433.72 principal / +3.04 / realised APR 3.63 % /contaminated; JitoSOL 17,165.63 / 0 /earning; LP orca 1,067.27 / 0 /earning.
The −101.96 residual, itemised (this is analysis of the live series, not a code claim):
| Part | Amount | What it is |
|---|---|---|
| Pre-ledger periods | −95.61 | the three NAV periods before the source ledger's first row (2026-09-05T20:12:14.720Z). No attribution exists for them at all; the Kamino main → JLP rotation of 17:37 UTC sits inside |
| Internal conversion slippage | −6.23 | the execution cost of the JitoSOL → SOL unstake and the LP add — venue slippage on our own conversions, currently booked to no cost source |
| Rounding | ≈ −0.12 |
Neither remedy exists in code. Grepped at platform 63a2de3 (working
tree clean, HEAD == origin/main): unattributed_pre_ledger,
ledger_starts_at and a costs:slippage source have zero occurrences
— the P&L source vocabulary is closed in
crates/nexus-db/src/treasury.rs::pnl_source and slippage is explicitly
documented as landing in unexplained. So, in register vocabulary:
- specified — a separate
unattributed_pre_ledgerbucket with aledger_starts_atmarker, so "we have no attribution for this era" stops looking like "the desk lost money it cannot explain"; - specified — a
costs:slippagesource for internal conversions, so a known execution cost stops arriving as a residual.
Until they exist, read unexplained on any window that reaches back before
2026-09-05 20:12 UTC as mostly the boundary of the ledger, and check
series_starts_at before drawing a conclusion from it.
What the dry run showed
Offline run of pnl_dry_run (nexus-platform 3646865) over the snapshots,
sleeve, positions, LP and ladder payloads captured on 2026-09-05:
- Swing round trips — the three closed trips reconcile exactly to the
ledger's +402.30 USDC: SOL/USDC 67 @ 102.20 → 104.90 = +180.90
(19.2 h); CBBTC/USDC 0.052 @ 76,600 → 78,300 = +88.40 (42.1 h);
SOL/USDC 35 @ 99.00 → 102.80 = +133.00 (43.9 h). All three are
historicalandfees_unknown. - Open position — the 10 SOL resting sell (vwap 90): unrealized ≈ +139 USDT at a 103.89 mark.
- Since inception (2 periods) — JitoSOL +0.00016 USDT (the pool rate
barely moved inside the window). Kamino shows "+36.34" in period 1 —
that is not interest: it is the 17:37 UTC rotation kamino/main →
kamino/jlp, executed before internal-flow recording existed, so the
balance change has no matching flow (true interest at the reported
11.87 % APY ≈ 0.68 USDC for the hour). Period 1 therefore stays a gap in
unexplained(−63.58). Period 2 reconciles to 1e-11.
Read the first period as the boundary of the ledger, not as a Kamino gain.
Where to read it
- API:
GET /v1/desk/pnl?window=…,/pnl/history,/pnl/events,/pnl/flows— shapes on the API reference. - Dashboard: Nexus Treasury → row "Where the money comes from"
(nexus-gitops
b5b6079), and the gauges under Operations → Observability —nexus_pnl_source_usdt{source,window},nexus_pnl_unexplained_usdt{window},nexus_sleeve_round_trips_total,nexus_sleeve_realized_usdt_total,nexus_sleeve_unrealized_usdt,nexus_staking_apr_realized,nexus_lending_apr_realized{venue},nexus_lending_interest_usdt{venue,window}, and since2553748nexus_pnl_cost_anomalies_total{field}(on-chain cost figures the sanity rules refused — a rising counter means the exec is reporting something the rules will not book). - Collections:
accum_pnl_sources,accum_pnl_events,accum_internal_flows— HARVEST §11.
Related
- HARVEST — as built §12 — items 13–14 are this page
- Accumulation desk §7 — the forecast design
- 2026-09-05 platform audit — status register