Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ body:
options:
- live
- estimated
- mocked
- last good (cached)
validations:
required: true
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
## Checklist

- [ ] CI green
- [ ] Every new metric renders a `DataSourceBadge` (live | estimated | mocked)
- [ ] Mock values live only in `src/lib/mockData.ts`, with `source` + `asOf`
- [ ] Every new metric renders a `DataSourceBadge` (live | estimated | cached)
- [ ] No mocked values; fallbacks come from `src/data/last-good.json` with their original `asOf`
- [ ] Dune SQL changes mirrored in `/queries`
- [ ] Live numbers spot-checked against Etherscan (link txs):
- [ ] `STATUS.md` / `DEVLOG.md` updated if the phase changed
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ Portfolio project. Solo. Stack: Next.js 16, TypeScript, Tailwind, Recharts, viem
Run /project:log-session — writes DEVLOG entry + updates STATUS.md automatically.

## Non-Negotiables
- Every metric needs a DataSourceBadge: live | estimated | mocked
- Mock data lives only in /src/lib/mockData.ts with source + asOf date
- Every metric needs a DataSourceBadge: live | estimated | cached (shown as "Last good" with its date)
- No mocked or invented values. Fallbacks are real earlier reads in /src/data/last-good.json (written by scripts/snapshot-last-good.ts)
- No PDF generation — deferred to v2
- Design system: `DESIGN.md` (tokens in `src/app/globals.css`).

## Structure
- /queries — metric definitions in Dune SQL (the app computes the same from on-chain logs)
- /docs — RAG context, reference with @docs/filename.md
- /src/lib — data layer (flowEvents.ts, types.ts, mockData.ts, addressRegistry.ts)
- /src/lib — data layer (flowEvents.ts, types.ts, lastGood.ts, addressRegistry.ts)
- /src/components — UI (see subdirectory CLAUDE.md)

## Tooling
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

![The /summary page: Fed rate cuts lined up with stronger USDC and USDT issuance and weaker BUIDL and OUSG issuance in 2024, but the 2025 easing episode only partly repeated the pattern](docs/img/summary.png)

A Next.js dashboard that decodes Ondo's OUSG and USDY InstantManager events on Ethereum and charts weekly mint volume, redeem volume, net flow and unique wallets. The `/issuance` page puts the same measurement on six assets from five issuers: USDC (Circle), USDT (Tether), PYUSD (PayPal), BUIDL (BlackRock) and OUSG and USDY (Ondo). It also shows where each stablecoin's supply sits by chain and how net issuance moved around the Fed's rate cuts. Every number on the page carries a **live**, **estimated** or **mocked** badge.
A Next.js dashboard that decodes Ondo's OUSG and USDY InstantManager events on Ethereum and charts weekly mint volume, redeem volume, net flow and unique wallets. The `/issuance` page puts the same measurement on six assets from five issuers: USDC (Circle), USDT (Tether), PYUSD (PayPal), BUIDL (BlackRock) and OUSG and USDY (Ondo). It also shows where each stablecoin's supply sits by chain and how net issuance moved around the Fed's rate cuts. Every number on the page carries a **live**, **estimated** or **last good** badge. A last good value is a real earlier read, shown with its date. The dashboard shows no mocked numbers.

## Results

Expand All @@ -24,14 +24,14 @@ From the dated snapshot in `src/data/issuance.json` (event logs read 2026-10-24,
| **Five issuers** | **$526.3B** | **$490.5B** | **$35.8B** |

- The snapshot holds 3,212,656 decoded event logs (the `audit` counts in `src/data/issuance.json`). USDC is 3.17M of them.
- $192.1B of USDC mints and burns are Circle CCTP transfers between chains. The tracker excludes them because moving USDC between chains is not new money. It also excludes Paxos's mistaken 300 trillion PYUSD mint and its burn on 2025-10-15.
- The five issuers total about $1T of mints and redemptions: $1.02T after removing $192.1B of Circle CCTP transfers, $1.21T if they were counted (`grossUsd` and `grossWithCctpUsd` in `src/lib/issuance.ts`). The tracker excludes CCTP because moving USDC between chains is not new money. It also excludes Paxos's mistaken 300 trillion PYUSD mint and its burn on 2025-10-15.
- In the 2024 easing episode (Sep to Dec 2024, 100 bp), USDC and USDT net issuance ran above their own norm (+0.5σ each), and BUIDL and OUSG ran below theirs (−0.2σ and −0.3σ). The 2025 episode (Sep to Dec 2025, 75 bp) only partly repeated it. With two episodes this is descriptive, not evidence of cause. `/summary` renders these figures from `src/lib/issuance.ts`.
- Ethereum log-derived net issuance matched DefiLlama's Ethereum supply change within 17% for USDC and 7% for PYUSD (`src/lib/issuance.check.ts`, run 2026-10-24).

## How it works

- **Ondo flows (`/`, `/flows`).** `src/lib/flowEvents.ts` pages the InstantManager logs from Blockscout's keyless v2 API, decodes them with viem and groups them by Monday-UTC week. It reads three contracts: the current OUSG manager, the legacy OUSG manager (Apr 2024 to Apr 2025) and the USDY manager. Log pages older than a fixed block cache for a week. Newer pages refresh hourly. `queries/mint_redeem_volume.sql` holds the same definition in Dune SQL as a written reference.
- **Home page pillars.** `src/lib/chainSupply.ts` reads total supply on 13 chains and prices it with the Ethereum `OndoOracle`. `src/lib/topHolders.ts` reads Ethereum holders from Blockscout. `src/lib/benchmark.ts` reads BUIDL from DefiLlama's stablecoin series and the Ondo row from the on-chain total; Superstate and OpenEden TVL have no free live source and stay labeled snapshots. When a source fails, each getter returns a dated, labeled snapshot from `src/lib/mockData.ts`.
- **Home page pillars.** `src/lib/chainSupply.ts` reads total supply on 13 chains and prices it with the Ethereum `OndoOracle`. `src/lib/topHolders.ts` reads Ethereum holders from Blockscout and Stellar USDY holders from stellar.expert. `src/lib/benchmark.ts` reads BUIDL from DefiLlama's stablecoin series and the Ondo row from the on-chain total; Superstate and OpenEden TVL have no free live source and stay labeled snapshots. When a source fails, each getter returns its last good rows from `src/data/last-good.json`, labeled **last good** with the date of the original read. A row with no stored value is left out. `scripts/snapshot-last-good.ts` refreshes that file from the live sources; `scripts/verify-supply.ts` compares the supply reads with independent explorers.
- **Stablecoin flows (`/issuance`, `/summary`).** `scripts/backfill-issuance.mjs` reads `eth_getLogs` from Tenderly's public mainnet gateway in 100,000-block windows. `scripts/backfill-context.mjs` adds DefiLlama supply by chain and the FRED `DFEDTARU` rate. Both scripts write `src/data/*.json`. The pages read these snapshots through `src/lib/issuance.ts`, not on each request.
- `/api/metrics` returns the same data as JSON.

Expand All @@ -44,10 +44,10 @@ npm ci
npm run lint
npx tsc --noEmit
npm test
npm run dev # http://localhost:3000
npm run dev # serves on port 3000; the deployed copy is ondo-nexus.vercel.app
```

`npm test` runs the seven self-check scripts in `src/lib/*.check.ts` with `tsx` (pinned in `package.json`). They need no network. `issuance.check.ts` reconciles the committed snapshots. CI runs lint, the type check, `npm test` and `npm run build`.
`npm test` runs the seven self-check scripts in `src/lib/*.check.ts` with `tsx` (pinned in `package.json`). They need no network. All seven passed on 2026-10-26. `issuance.check.ts` reconciles the committed snapshots. CI runs lint, the type check, `npm test` and `npm run build`.

To refresh the stablecoin snapshots (about 25 minutes on the free public RPC), then rebuild the study guide:

Expand All @@ -65,9 +65,9 @@ The USDC share card (`docs/img/usdc-share-card.svg` and `.png`, 1024px wide, nav
```
src/app/ Pages (/, /flows, /issuance, /summary) and /api/metrics
src/components/ Dashboard charts, issuance figures and UI atoms
src/lib/ Data getters, types, mock fallbacks and *.check.ts self-checks
src/data/ Dated snapshots for the stablecoin flows tracker
scripts/ Backfill scripts that write src/data/*.json
src/lib/ Data getters, types, last-good fallbacks and *.check.ts self-checks
src/data/ Dated snapshots: the stablecoin flows tracker and the last good values
scripts/ Backfill and snapshot scripts that write src/data/*.json, and the supply cross-check
queries/ Metric definitions in Dune SQL (reference; the app does not call Dune)
docs/ Methodology, contract research, decisions and the study guide
.claude/ Claude Code agents, commands and the live-pillar skill
Expand All @@ -87,10 +87,10 @@ docs/ Methodology, contract research, decisions and the study guide

- **Ondo flows.** InstantManager event logs from [Blockscout](https://eth.blockscout.com) (v2 API, keyless), read at render time and cached for one hour. Full history from Apr 2024 (OUSG) and Dec 2025 (USDY).
- **Chain supply and price.** public RPCs and chain APIs on 13 chains, priced with `OndoOracle` on Ethereum. The per-chain table is in [docs/METHODOLOGY.md](docs/METHODOLOGY.md).
- **Holders.** Blockscout token holders API, Ethereum only.
- **Holders.** Blockscout token holders API for Ethereum (OUSG, USDY) and the stellar.expert asset holders API for Stellar USDY.
- **Benchmark.** [DefiLlama](https://defillama.com) TVL per product, plus redemption terms quoted from each issuer's docs.
- **Stablecoin flows.** Ethereum event logs through Tenderly's public gateway, DefiLlama stablecoin supply and FRED `DFEDTARU`. The backfill scripts freeze these as dated JSON in `src/data/`, with `asOf` 2026-10-24.
- Fallback snapshots in `src/lib/mockData.ts` carry their own source and date.
- **Last good values.** `src/data/last-good.json` holds real reads from the sources above, written on 2026-10-26 (Superstate USTB and OpenEden TBILL rows date from 2026-09-24).

## Scope and limits

Expand All @@ -100,7 +100,7 @@ docs/ Methodology, contract research, decisions and the study guide
- Gross flows are Ethereum only. Tron holds about half of USDT supply, so Ethereum logs understate USDT and USDC issuance. Net issuance for the three stablecoins uses all-chain supply instead.
- The stablecoin data are a snapshot from 2026-10-24. It is stale until someone reruns the backfill scripts.
- The rate-cut comparison covers two easing episodes, so it is descriptive only.
- On 2026-10-21, DefiLlama's `/tvl/<slug>` endpoint returned `0` for all four benchmark products. The competitor rows therefore show the 2026-09-24 snapshot, labeled **mocked**.
- On 2026-10-21, DefiLlama's `/tvl/<slug>` endpoint returned `0` for all four benchmark products. The USTB and TBILL rows therefore show their 2026-09-24 last good value, labeled **last good**.

## License

Expand Down
6 changes: 3 additions & 3 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ Last updated: 2026-10-24
## Sources (ADR-007, ADR-008)
- Weekly flows + 4-week KPIs: InstantManager event logs from Blockscout, refreshed hourly (ADR-008)
- Total TVL, chain breakdown, heatmap: on-chain supply on 13 chains × OndoOracle price
- Top holders: Blockscout, Ethereum only
- Benchmark: BUIDL from DefiLlama's stablecoin series (circulating supply); Ondo row is the on-chain total when all chains are live; USTB and TBILL are the 2026-09-24 snapshot (DefiLlama's `/tvl` and `/protocol` return nothing for these RWA slugs; needs its keyed RWA API)
- Every getter falls back to a dated, labeled snapshot in `src/lib/mockData.ts`
- Top holders: Blockscout for Ethereum, stellar.expert for Stellar USDY
- Benchmark: BUIDL from DefiLlama's stablecoin series (circulating supply); Ondo row is the on-chain total; USTB and TBILL are the 2026-09-24 snapshot (DefiLlama's `/tvl` and `/protocol` return nothing for these RWA slugs; needs its keyed RWA API)
- Every getter falls back to its last good rows in `src/data/last-good.json` (real reads, labeled Last good with their date); `mockData.ts` is gone (ADR-010)

## Open Items
- After refreshing the snapshots, rebuild the textbook: `npx tsx docs/textbook/build.ts`.
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTRACT_RESEARCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Verified on Dune 2026-09-23: both OUSG InstantManagers are decoded, USDY Instant

| Pillar | Data Available | Source | Gaps |
|--------|---------------|--------|------|
| 1: Top Ethereum holders | Top 15 balances per token | Blockscout token holders API, valued at OndoOracle | Ethereum only; names are manual registry or Blockscout tags |
| 1: Top holders (Ethereum, Stellar USDY) | Top 15 balances per token | Blockscout token holders API, valued at OndoOracle | Ethereum only; names are manual registry or Blockscout tags |
| 2: Mint/Redeem Volume | Full event logs with USD values | Blockscout v2 logs API, InstantManager events (ADR-008) | No settlement delta (atomic); volume + frequency only |
| 3: TVL by chain | OUSG + USDY supply on 13 chains | Direct RPC/API supply reads × OndoOracle price | Counts Ondo-held inventory; see METHODOLOGY.md for the per-chain table |
| 4: Competitive Benchmark | TVL per product | DefiLlama (Ondo row on-chain when all chains live) | Accounting differs by product; BUIDL chain count and redemption not disclosed |
Expand Down
17 changes: 15 additions & 2 deletions docs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Why the dashboard is built the way it is: each record gives the context, the dec

## ADR-002: Mock Data as First-Class Feature
**Date:** 2026-04-09
**Status:** Accepted
**Status:** Superseded by ADR-010 (2026-10-26): the dashboard no longer shows mocked values
**Context:** Nexus may have limited third-party issuer activity at launch
**Decision:** Build typed mockData.ts layer with source citations and DataSourceBadge
UI component from day one, not as a fallback
Expand Down Expand Up @@ -111,7 +111,7 @@ Why the dashboard is built the way it is: each record gives the context, the dec
DefiLlama leaves out, so Ondo TVL runs above DefiLlama's
- Holders are Ethereum only
- Flow data is only as fresh as the last Dune run; the page shows its date
- Each getter falls back to a dated, labeled snapshot in mockData.ts (ADR-002 still holds)
- Each getter falls back to a dated, labeled snapshot (mockData.ts then; last-good.json since ADR-010)

## ADR-008: Weekly Flows from Blockscout Logs Instead of Dune
**Date:** 2026-09-24
Expand Down Expand Up @@ -169,3 +169,16 @@ Why the dashboard is built the way it is: each record gives the context, the dec
- BUIDL mints include its daily dividend issuance, about 1% of mint volume; counts are not reported.
- USDT and USDC volumes on other chains (Tron is the largest USDT chain) are not in the gross flows.
- No claim about causation: there are two easing episodes in the sample.

## ADR-010: Last Good Values Replace Mocked Fallbacks; Stellar Holders; "About $1T" With the CCTP Caveat
**Date:** 2026-10-26
**Status:** Accepted. Supersedes the mockData.ts part of ADR-002 and ADR-007.
**Context:** The home page fell back to `src/lib/mockData.ts` when a live read failed. Some of those values were invented (the weekly flows were a seeded random series) and others had drifted far from the chain (Noble USDY mock 5.6M against 0.06M live). The headline was also read as "$1 trillion" although the figure depends on excluding Circle CCTP transfers ($1.02T without them, $1.21T with them).
**Decision:**
1. Delete `mockData.ts`. `src/data/last-good.json` stores real reads, written by `scripts/snapshot-last-good.ts` only from a fully live read. A getter that falls back returns those rows labeled `cached` ("Last good") with their original date. A row with no stored value is left out and the page says "Unavailable".
2. Add Stellar USDY to the top-holders pillar (stellar.expert holders API, keyless), next to Ethereum. Supply already covered 13 chains; `scripts/verify-supply.ts` checks it against explorers.
3. The summary says "about $1T" and states the CCTP exclusion and both figures beside it.
**Consequences:**
- No number on the dashboard is invented. A stale number is labeled with its date.
- `last-good.json` goes stale until someone reruns the snapshot script. It adds no scheduled job.
- USTB and TBILL have no free live source, so they always show their 2026-09-24 value as Last good.
Loading
Loading