diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 65a46c7..f281581 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -25,6 +25,6 @@ body: options: - live - estimated - - mocked + - last good (cached) validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 31d0e9a..cc7a5c0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 09bbce3..9aeafc4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/README.md b/README.md index 8a87121..1988ec7 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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: @@ -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 @@ -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 @@ -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/` 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/` 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 diff --git a/STATUS.md b/STATUS.md index ed011f4..bb8ea74 100644 --- a/STATUS.md +++ b/STATUS.md @@ -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`. diff --git a/docs/CONTRACT_RESEARCH.md b/docs/CONTRACT_RESEARCH.md index 056b8a0..6b91dec 100644 --- a/docs/CONTRACT_RESEARCH.md +++ b/docs/CONTRACT_RESEARCH.md @@ -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 | diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index e9b2c36..cf8fe34 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -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 @@ -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 @@ -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. diff --git a/docs/METHODOLOGY.md b/docs/METHODOLOGY.md index 8bfa06e..b8c9f75 100644 --- a/docs/METHODOLOGY.md +++ b/docs/METHODOLOGY.md @@ -14,16 +14,18 @@ Every data point on the dashboard carries one of three labels: | Label | Meaning | Criteria | |-------|---------|----------| | **Live** | Read from its source on this render: on-chain RPC, Blockscout, or DefiLlama | No manual assumptions, independently verifiable. Cached up to 1hr | -| **Estimated** | A total that mixes live and mocked rows | Shown when some but not all rows behind a number are live | -| **Mocked** | Dated snapshot or illustrative value, shown when the source fails | Carries its own source and asOf; prefixed with "~" | +| **Estimated** | A total that mixes live and last good rows | Shown when some but not all rows behind a number are live | +| **Last good** | The most recent real read, shown when the source fails | Stored in `src/data/last-good.json`; carries its original asOf, shown in the badge, and a "~" prefix | + +The dashboard has no mocked or illustrative numbers. If a source fails and `last-good.json` has no row for it, the row is left out and the page says the value is unavailable. `scripts/snapshot-last-good.ts` rewrites the file from the live getters and keeps a part only if every row came back live. The `DataSourceBadge` component renders this label inline with every metric. The methodology drawer explains each gap. --- -## Pillar 1: Top Ethereum Holders +## Pillar 1: Top Holders -**What we track:** The top 15 holders of OUSG and USDY on Ethereum, with balance and USD value. Code: `src/lib/topHolders.ts`. +**What we track:** The top 15 holders of OUSG and USDY on Ethereum, and of USDY on Stellar, with balance and USD value. Code: `src/lib/topHolders.ts`. **Data source:** Blockscout token holders API, no key needed: `https://eth.blockscout.com/api/v2/tokens//holders` for OUSG (`0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92`) and USDY (`0x96F6eF951840721AdBF46Ac996b59E0235CB985C`). Balances are 18-decimal integers. @@ -32,10 +34,12 @@ The `DataSourceBadge` component renders this label inline with every metric. The **Names:** First the manual registry in `addressRegistry.ts`, then the highest-ordinal Blockscout public tag of type `name`. Generic tags, ordinal-0 tags, tags equal to the contract name (such as `CErc20DelegatorKYC`) and `GnosisSafeProxy*` tags are ignored, since they name a contract class, not an owner. Anything else shows as "Unknown wallet (0x...)". -**Data source label:** **Live** when both holder calls and the oracle read succeed. Otherwise **Mocked**: a Blockscout snapshot from 2026-09-24 in `mockData.ts`. +**Stellar:** the stellar.expert asset holders API (`api.stellar.expert/explorer/public/asset/USDY-/holders`, keyless), balances in 1e7 units, valued at the same Ethereum oracle price. The top 15 hold 467.49M of the 467.50M USDY on Stellar (read 2026-10-26). Stellar was added because USDY there (467.5M tokens) is the second-largest supply after Ethereum, and stellar.expert serves a keyless holders list. + +**Data source label:** Per group (Ethereum OUSG, Ethereum USDY, Stellar USDY). **Live** when that group's fetch and the oracle read succeed. Otherwise **Last good**: that group's rows from `src/data/last-good.json` with their original date. **Known gaps:** -- Ethereum only. Holders on other chains are not listed. +- Ethereum and Stellar USDY only. Holders on other chains are not listed. - Contract holders (lending markets, Safes, bridges) hold on behalf of others; the dashboard does not look through them. - OUSG's backing composition (which third-party assets) is not on-chain. @@ -64,7 +68,7 @@ The `DataSourceBadge` component renders this label inline with every metric. The **Source:** event logs from Blockscout's keyless v2 logs API (`/api/v2/addresses//logs?topic=`, one series per contract and event), decoded with viem and grouped by week in `src/lib/flowEvents.ts` (ADR-008). The definition is written out in SQL in `queries/mint_redeem_volume.sql`, saved on Dune as [query 8822192](https://dune.com/queries/8822192); the app no longer calls Dune. The on-chain rows matched every Dune row through the week of 2026-09-14. Output: `week, token, mint_volume_usd, redeem_volume_usd, net_flow_usd, mint_count, redeem_count, unique_minters, unique_redeemers, unique_wallets`. -**Data source label:** **Live** when every log read succeeds and returns events. Otherwise **Mocked**: an illustrative series shaped from public disclosures, with a server log line giving the reason. `asOf` is when the logs were read; the home page shows it as "as of " on the flows section and the net flow KPI. +**Data source label:** **Live** when every log read succeeds and returns events. Otherwise **Last good**: the stored series from `src/data/last-good.json` with its original read date, and a server log line giving the reason. `asOf` is when the logs were read; the home page shows it as "as of " on the flows section and the net flow KPI. **Home page KPIs:** 4-week volume, net flow and average transaction size sum the last 4 complete weeks of both tokens. The trend compares volume to the 4 complete weeks before; a change that rounds to 0.0% is shown as flat. @@ -108,7 +112,9 @@ See ADR-004 in DECISIONS.md. | USDY | BNB Chain | `0x608593d17A2decBbc4399e4185bE4922F97eD32E` | ERC-20 `totalSupply()` | | OUSG | XRP Ledger | `4F55534700000000000000000000000000000000.rHuiXXjHLpMP8ZE9sSQU5aADQVWDwv6h5p` | `gateway_balances` obligations on the issuer | -**Data source label:** Per row. **Live** when both the supply read and the oracle read succeed. A failed supply read shows that row's 2026-09-24 snapshot as **Mocked**; a failed oracle read makes every row Mocked. The Total TVL KPI is **Estimated** when rows are mixed. +**Data source label:** Per row. **Live** when both the supply read and the oracle read succeed. A failed supply read shows that row's last good value (its original date in the badge); a failed oracle read does this for every row. The Total TVL KPI is **Estimated** when rows are mixed. + +**Cross-check (2026-10-26, `npx tsx scripts/verify-supply.ts`):** the dashboard's reads match an independent explorer to the token on Ethereum USDY (1,106,268,931.74) and OUSG (1,145,233.42) against Blockscout, Mantle USDY (319,742.91) against Routescan, and Plume USDY (0) against Plume's Blockscout. Stellar USDY is 467,502,151.70 on Horizon and 467,502,127.77 on stellar.expert (0.000005% apart). The other chains were not cross-checked: on 2026-10-26 the Arbitrum and Polygon Blockscout instances answered a scripted request with a Cloudflare challenge and Routescan does not cover Arbitrum or BNB Chain, so those rows rest on the chain's own RPC or API. **Known gaps:** - Supply counts every token in circulation, including any inventory Ondo itself holds (treasury, bridge or market-making wallets). @@ -121,11 +127,11 @@ See ADR-004 in DECISIONS.md. **What we track:** TVL, chain count and redemption terms for Ondo (OUSG + USDY), Invesco USTB on Superstate, OpenEden TBILL and BlackRock BUIDL. Code: `src/lib/benchmark.ts`. -**TVL:** The Ondo row is the Pillar 3 on-chain total when every chain row is live, so it equals the Total TVL KPI. BUIDL is the latest total circulating USD from DefiLlama's stablecoin series (`GET stablecoins.llama.fi/stablecoincharts/all?stablecoin=173`), cached 1hr; this is circulating supply, not DefiLlama's RWA TVL. DefiLlama's `/tvl/` and `/protocol/` return 0 or an empty series for `ondo-yield-assets`, `invesco-ustb`, `openeden-tbill` and `blackrock-buidl` (checked 2026-10-24), so Superstate (USTB) and OpenEden (TBILL) have no free live source and keep their 2026-09-24 rows labeled Mocked. Their TVL needs DefiLlama's keyed RWA API. +**TVL:** The Ondo row is always the Pillar 3 on-chain total, so it equals the Total TVL KPI; if a chain row fell back, the row is Estimated and shows the oldest cached date. BUIDL is the latest total circulating USD from DefiLlama's stablecoin series (`GET stablecoins.llama.fi/stablecoincharts/all?stablecoin=173`), cached 1hr; this is circulating supply, not DefiLlama's RWA TVL. DefiLlama's `/tvl/` and `/protocol/` return 0 or an empty series for `ondo-yield-assets`, `invesco-ustb`, `openeden-tbill` and `blackrock-buidl` (checked 2026-10-24), so Superstate (USTB) and OpenEden (TBILL) have no free live source and keep their 2026-09-24 rows labeled Last good. Their TVL needs DefiLlama's keyed RWA API. **Chain count and redemption:** Static text quoted from each issuer's own docs, with a link. DefiLlama's `chains` for these slugs is a placeholder, so it is not used. BUIDL shows "Not disclosed" for both: no issuer page could be cited. -**Data source label:** Per row. **Live** when the fetch (or on-chain total) succeeds, else the 2026-09-24 DefiLlama snapshot as **Mocked** (always, for USTB and TBILL). +**Data source label:** Per row. **Live** when the fetch (or on-chain total) succeeds, else its last good row (always, for USTB and TBILL: 2026-09-24). **Known gaps:** - Accounting may differ between products (accrued yield, which chains count). @@ -198,5 +204,6 @@ All data sources are cited inline in the dashboard via the `MethodologyDrawer` c - On-chain reads: public RPCs and chain APIs listed in Pillar 3 - OndoOracle on Ethereum for prices - Blockscout (eth.blockscout.com) for Ethereum holders and InstantManager event logs (mint and redeem flows) +- stellar.expert for Stellar USDY holders - DefiLlama (api.llama.fi) for competitor TVL - Ondo Finance official documentation (docs.ondo.finance) and competitor docs diff --git a/scripts/snapshot-last-good.ts b/scripts/snapshot-last-good.ts new file mode 100644 index 0000000..2c46455 --- /dev/null +++ b/scripts/snapshot-last-good.ts @@ -0,0 +1,51 @@ +/** + * Refresh src/data/last-good.json from the live sources. Run by hand when the data is fresh: + * npx tsx scripts/snapshot-last-good.ts + * + * It calls the same getters the pages use and keeps a part only if every row came back live. + * A part that did not is left as it was, and the script exits 1. USTB and TBILL rows have no free + * live source, so they carry over from the file with their original dates. + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { getBenchmark, withOnchainOndo } from '../src/lib/benchmark'; +import { getChainTVL } from '../src/lib/chainSupply'; +import { getTopHolders } from '../src/lib/topHolders'; +import { getWeeklyFlows } from '../src/lib/weeklyFlows'; +import type { LastGood } from '../src/lib/lastGood'; + +const FILE = join(__dirname, '..', 'src', 'data', 'last-good.json'); +const prev = JSON.parse(readFileSync(FILE, 'utf8')) as LastGood; +const next: LastGood = { ...prev, generatedAt: new Date().toISOString() }; +const failed: string[] = []; + +async function main() { + const [chain, holders, flows, bench] = await Promise.all([getChainTVL(), getTopHolders(), getWeeklyFlows(), getBenchmark()]); + const allLive = (rows: { dataSource: string }[]) => rows.length > 0 && rows.every((r) => r.dataSource === 'live'); + + if (allLive(chain)) next.chainTvl = chain; + else failed.push('chainTvl'); + if (allLive(holders.rows)) next.topHolders = holders.rows; + else failed.push('topHolders'); + if (flows.dataSource === 'live' && allLive(flows.rows)) next.weeklyFlows = flows.rows; + else failed.push('weeklyFlows'); + + const benchRows = allLive(chain) ? withOnchainOndo(bench.rows, chain) : bench.rows; + const live = (p: string) => benchRows.find((r) => r.protocol === p)?.dataSource === 'live'; + if (live('nexus') && live('blackrock-buidl')) { + next.competitorBenchmark = benchRows.map((r) => (r.dataSource === 'live' ? r : prev.competitorBenchmark.find((p) => p.protocol === r.protocol) ?? r)); + } else failed.push('competitorBenchmark'); + + writeFileSync(FILE, JSON.stringify(next, null, 1) + '\n'); + console.log(`last-good.json written ${next.generatedAt}; chain ${next.chainTvl.length}, holders ${next.topHolders.length}, flows ${next.weeklyFlows.length}, benchmark ${next.competitorBenchmark.length}`); + if (failed.length) { + console.error(`not refreshed (source not fully live): ${failed.join(', ')}`); + process.exit(1); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/verify-supply.ts b/scripts/verify-supply.ts new file mode 100644 index 0000000..932d23d --- /dev/null +++ b/scripts/verify-supply.ts @@ -0,0 +1,61 @@ +/** + * Compare the dashboard's on-chain supply reads with an independent explorer, per chain. + * npx tsx scripts/verify-supply.ts + * Explorers (all keyless): Blockscout (Ethereum, Plume), Routescan (Mantle), stellar.expert (Stellar). + * Exits 1 when any pair differs by more than 0.5%. + */ + +import { SUPPLY_SOURCES, fromBaseUnits, getChainTVL } from '../src/lib/chainSupply'; + +type Explorer = (s: (typeof SUPPLY_SOURCES)[number]) => Promise<{ name: string; supply: number } | null>; + +const json = async (url: string) => { + const res = await fetch(url, { signal: AbortSignal.timeout(15_000) }); + if (!res.ok) throw new Error(`${url} HTTP ${res.status}`); + return res.json() as Promise>; +}; +const addrOf = (s: (typeof SUPPLY_SOURCES)[number]) => (s.reader.kind === 'evm' ? s.reader.address : ''); + +const explorers: Partial> = { + ethereum: async (s) => { + const j = await json(`https://eth.blockscout.com/api/v2/tokens/${addrOf(s)}`); + return { name: 'Blockscout', supply: fromBaseUnits(String(j.total_supply), 18) }; + }, + plume: async (s) => { + const j = await json(`https://explorer.plume.org/api/v2/tokens/${addrOf(s)}`); + return { name: 'Blockscout (Plume)', supply: fromBaseUnits(String(j.total_supply), 18) }; + }, + mantle: async (s) => { + const j = await json(`https://api.routescan.io/v2/network/mainnet/evm/5000/etherscan/api?module=stats&action=tokensupply&contractaddress=${addrOf(s)}`); + return { name: 'Routescan', supply: fromBaseUnits(String(j.result), 18) }; + }, + stellar: async (s) => { + if (s.reader.kind !== 'stellar') return null; + const j = await json(`https://api.stellar.expert/explorer/public/asset/${s.reader.code}-${s.reader.issuer}`); + return { name: 'stellar.expert', supply: Number(j.supply) / 1e7 }; + }, +}; + +async function main() { + const rows = await getChainTVL(); + let bad = 0; + for (const s of SUPPLY_SOURCES) { + const ours = rows.find((r) => r.chain === s.chain && r.token === s.token); + const explorer = explorers[s.chain]; + if (!ours || !explorer) continue; + try { + const theirs = await explorer(s); + if (!theirs) continue; + const diff = Math.abs(ours.supply - theirs.supply) / Math.max(theirs.supply, 1); + const ok = ours.dataSource === 'live' && diff <= 0.005; + if (!ok) bad++; + console.log(`${ok ? 'ok ' : 'FAIL'} ${s.token} ${s.chain}: dashboard ${ours.supply.toFixed(2)} (${ours.dataSource}), ${theirs.name} ${theirs.supply.toFixed(2)}, diff ${(diff * 100).toFixed(3)}%`); + } catch (err) { + bad++; + console.log(`FAIL ${s.token} ${s.chain}: ${err instanceof Error ? err.message : String(err)}`); + } + } + process.exit(bad ? 1 : 0); +} + +main(); diff --git a/src/app/flows/page.tsx b/src/app/flows/page.tsx index ec2b908..89dfbd8 100644 --- a/src/app/flows/page.tsx +++ b/src/app/flows/page.tsx @@ -28,7 +28,7 @@ export default async function FlowsPage() { OUSG and USDY on Ethereum, from InstantManager Subscription and Redemption events.

- + {asOf && as of {formatWeek(asOf)}} {dataSource === 'live' && queryUrl && ( @@ -38,7 +38,13 @@ export default async function FlowsPage() {

- + {rows.length === 0 ? ( +

+ Unavailable: the event-log source did not respond and no last good series is stored. +

+ ) : ( + + )} diff --git a/src/app/globals.css b/src/app/globals.css index 71fc91b..4cb43b1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -27,8 +27,8 @@ --badge-live-bg: #e8f6ec; --badge-estimated-fg: #8a5300; --badge-estimated-bg: #fdf3e1; - --badge-mocked-fg: #4d4d4d; - --badge-mocked-bg: #f0f0f0; + --badge-cached-fg: #4d4d4d; + --badge-cached-bg: #f0f0f0; color-scheme: light; } @@ -51,8 +51,8 @@ --badge-live-bg: #0f2a18; --badge-estimated-fg: #e3a43a; --badge-estimated-bg: #2e210a; - --badge-mocked-fg: #a1a1a1; - --badge-mocked-bg: #1f1f1f; + --badge-cached-fg: #a1a1a1; + --badge-cached-bg: #1f1f1f; color-scheme: dark; } } diff --git a/src/app/issuance/page.tsx b/src/app/issuance/page.tsx index dcd05da..7f34277 100644 --- a/src/app/issuance/page.tsx +++ b/src/app/issuance/page.tsx @@ -6,7 +6,7 @@ import { ChainTable } from '@/components/issuance/ChainTable'; import { Figure1, Figure2 } from '@/components/issuance/IssuanceFigures'; import { TotalsTable } from '@/components/issuance/TotalsTable'; import { NEWS_EVENTS, PRE_WEEKS, TAIL_WEEKS, eventWindow, issuanceSummary, totals } from '@/lib/issuance'; -import { formatUsdCompact } from '@/lib/format'; +import { formatTrillions, formatUsdCompact } from '@/lib/format'; import { formatWeek } from '@/lib/flowStats'; export const metadata: Metadata = { @@ -30,14 +30,14 @@ export default function IssuancePage() {

- {formatUsdCompact(s.grossUsd)} minted and redeemed across five issuers. + About $1T minted and redeemed across five issuers.

Circle, Tether, PayPal, BlackRock and Ondo, on Ethereum since January 2023. Where the money sits by chain, and what weekly issuance did around the Fed's rate cuts.

- + Event logs read {formatWeek(s.asOf)}, through the week of {formatWeek(s.lastWeek)}

@@ -48,8 +48,8 @@ export default function IssuancePage() {

- Ethereum event logs. Circle CCTP transfers ({formatUsdCompact(s.cctpBridgedUsd)} of USDC mints and burns) are excluded: moving USDC between chains is not - issuance. USDC is {Math.round(s.usdcShareOfGross * 100)}% of the total and is mostly market-maker churn; its net is negative on Ethereum because holders + Ethereum event logs. The total is {formatTrillions(s.grossUsd)} because Circle CCTP transfers ({formatUsdCompact(s.cctpBridgedUsd)} of USDC mints and burns) are excluded: moving USDC between chains is not + issuance. Counting them gives {formatTrillions(s.grossWithCctpUsd)}. USDC is {Math.round(s.usdcShareOfGross * 100)}% of the total and is mostly market-maker churn; its net is negative on Ethereum because holders redeem there and mint natively elsewhere, while its circulating supply on all chains grew.

diff --git a/src/app/page.tsx b/src/app/page.tsx index 03edbbe..2e0f4a8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,7 +3,7 @@ import { formatUsdCompact, formatNumberCompact, formatPercent } from '@/lib/form import { getWeeklyFlows } from '@/lib/weeklyFlows'; import { formatWeek, fourWeekStats, weeklySeries } from '@/lib/flowStats'; import { getChainTVL, toLiquidityCells } from '@/lib/chainSupply'; -import { combinedSource } from '@/lib/dataSource'; +import { cachedAsOf, combinedSource } from '@/lib/dataSource'; import { MetricCard } from '@/components/ui/MetricCard'; import { TopHoldersChart } from '@/components/dashboard/TopHoldersChart'; import { FlowCharts } from '@/components/flows/FlowCharts'; @@ -37,11 +37,11 @@ export default async function DashboardPage() { const stats = fourWeekStats(flows.rows); // formatPercent keeps one decimal of a percent: anything that rounds to 0.0% is flat. const trend = stats.volumeTrend === null ? null : Math.round(stats.volumeTrend * 1000) / 1000; - const prefix = flows.dataSource === 'mocked' ? '~' : ''; + const prefix = flows.dataSource === 'cached' ? '~' : ''; const totalTvlUsd = chainRows.reduce((sum, r) => sum + r.tvlUsd, 0); const tvlSource = combinedSource(chainRows); - // MetricCard adds '~' itself only when fully mocked; a mixed total needs it too. - const tvlPrefix = tvlSource === 'estimated' && chainRows.some((r) => r.dataSource === 'mocked') ? '~' : ''; + // MetricCard adds '~' itself only when fully cached; a mixed total needs it too. + const tvlPrefix = tvlSource === 'estimated' && chainRows.some((r) => r.dataSource === 'cached') ? '~' : ''; return ( @@ -64,12 +64,14 @@ export default async function DashboardPage() { label="Total TVL" value={`${tvlPrefix}${formatUsdCompact(totalTvlUsd)}`} dataSource={tvlSource} + asOf={cachedAsOf(chainRows)} subValue="13 chains, OUSG + USDY" /> 0 ? '+' : ''}${formatUsdCompact(stats.netFlowUsd)}`} dataSource={flows.dataSource} + asOf={flowsAsOf} subValue={flowsAsOf ? `as of ${formatWeek(flowsAsOf)}` : undefined} /> @@ -120,7 +124,7 @@ export default async function DashboardPage() {

- +
{(['OUSG', 'USDY'] as const).map((token) => { diff --git a/src/app/summary/page.tsx b/src/app/summary/page.tsx index 8fed7f1..4c72ec6 100644 --- a/src/app/summary/page.tsx +++ b/src/app/summary/page.tsx @@ -3,12 +3,12 @@ import Link from 'next/link'; import { Figure1, Figure2 } from '@/components/issuance/IssuanceFigures'; import { TotalsTable } from '@/components/issuance/TotalsTable'; import { chainShares, eventWindow, issuanceSummary, totals, zDuring } from '@/lib/issuance'; -import { formatUsdCompact } from '@/lib/format'; +import { formatTrillions, formatUsdCompact } from '@/lib/format'; import { formatWeek } from '@/lib/flowStats'; export const metadata: Metadata = { title: 'Stablecoin and Treasury-fund issuance around Fed rate cuts', - description: 'One-page summary: $1 trillion of mints and redemptions across five issuers, by chain and around rate cuts.', + description: 'One-page summary: about $1 trillion of mints and redemptions across five issuers on Ethereum (Circle CCTP transfers excluded), by chain and around rate cuts.', }; const sd = (v: number | null) => (v === null ? 'n/a' : `${v >= 0 ? '+' : '−'}${Math.abs(v).toFixed(1)}σ`); @@ -43,7 +43,8 @@ export default function SummaryPage() {

- Five issuers, {formatUsdCompact(s.grossUsd)} of mints and redemptions on Ethereum. In the Fed's two easing episodes the two largest stablecoins + Five issuers, about $1T of mints and redemptions on Ethereum.{' '} + That is {formatTrillions(s.grossUsd)} after removing {formatUsdCompact(s.cctpBridgedUsd)} of Circle CCTP transfers, which only move USDC between chains; counting them gives {formatTrillions(s.grossWithCctpUsd)}. In the Fed's two easing episodes the two largest stablecoins grew faster than their own norm in 2024 (USDC {sd(zDuring(r, 'USDC', 0))}, USDT {sd(zDuring(r, 'USDT', 0))}) while the Treasury-backed funds ran below theirs (BUIDL {sd(zDuring(r, 'BUIDL', 0))}, OUSG {sd(zDuring(r, 'OUSG', 0))}). 2025 only half repeated it: BUIDL fell further ({sd(zDuring(r, 'BUIDL', 1))}, the weakest window in its history) and USDT stayed above average ({sd(zDuring(r, 'USDT', 1))}), but USDC fell back to average and OUSG recovered. With two episodes, this is a pattern to watch, not evidence of cause. diff --git a/src/components/dashboard/ChainBreakdown.tsx b/src/components/dashboard/ChainBreakdown.tsx index a620728..7692a2d 100644 --- a/src/components/dashboard/ChainBreakdown.tsx +++ b/src/components/dashboard/ChainBreakdown.tsx @@ -1,7 +1,7 @@ 'use client'; import type { Chain, ChainTVL } from '@/lib/types'; -import { combinedSource } from '@/lib/dataSource'; +import { cachedAsOf, combinedSource } from '@/lib/dataSource'; import { DataSourceBadge } from '@/components/ui/DataSourceBadge'; import { formatUsdCompact, formatPercent } from '@/lib/format'; @@ -26,16 +26,16 @@ const CHAIN_COLORS: Record = { interface ChainTotal { chain: Chain; tvlUsd: number; - mocked: boolean; + cached: boolean; } /** Sum the per-(token, chain) rows into one entry per chain. */ function byChain(rows: ChainTVL[]): ChainTotal[] { const map = new Map(); for (const r of rows) { - const t = map.get(r.chain) ?? { chain: r.chain, tvlUsd: 0, mocked: false }; + const t = map.get(r.chain) ?? { chain: r.chain, tvlUsd: 0, cached: false }; t.tvlUsd += r.tvlUsd; - t.mocked ||= r.dataSource === 'mocked'; + t.cached ||= r.dataSource === 'cached'; map.set(r.chain, t); } return [...map.values()]; @@ -51,7 +51,7 @@ export function ChainBreakdown({ data }: ChainBreakdownProps) {

Chain breakdown

- +
{/* Stacked bar */} @@ -91,7 +91,7 @@ export function ChainBreakdown({ data }: ChainBreakdownProps) { {chain.tvlUsd === 0 ? '–' : formatPercent(pct)} - {chain.tvlUsd === 0 ? '–' : `${chain.mocked ? '~' : ''}${formatUsdCompact(chain.tvlUsd)}`} + {chain.tvlUsd === 0 ? '–' : `${chain.cached ? '~' : ''}${formatUsdCompact(chain.tvlUsd)}`} diff --git a/src/components/dashboard/CompetitiveBenchmark.tsx b/src/components/dashboard/CompetitiveBenchmark.tsx index 49f765c..14e406e 100644 --- a/src/components/dashboard/CompetitiveBenchmark.tsx +++ b/src/components/dashboard/CompetitiveBenchmark.tsx @@ -43,7 +43,7 @@ export function CompetitiveBenchmark({ data }: CompetitiveBenchmarkProps) { {sorted.map((row) => { const barWidth = (row.tvlUsd / maxTvl) * 100; const isNexus = row.protocol === 'nexus'; - const isMocked = row.dataSource === 'mocked'; + const isCached = row.dataSource === 'cached'; return ( - {isMocked ? '~' : ''}{formatUsdCompact(row.tvlUsd)} + {isCached ? '~' : ''}{formatUsdCompact(row.tvlUsd)} @@ -76,7 +76,7 @@ export function CompetitiveBenchmark({ data }: CompetitiveBenchmarkProps) {
- + diff --git a/src/components/dashboard/LiquidityHeatmap.tsx b/src/components/dashboard/LiquidityHeatmap.tsx index 8e0cbdf..58b372e 100644 --- a/src/components/dashboard/LiquidityHeatmap.tsx +++ b/src/components/dashboard/LiquidityHeatmap.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import type { LiquidityCell, Chain, DataSource } from '@/lib/types'; import { DataSourceBadge } from '@/components/ui/DataSourceBadge'; import { formatUsdCompact } from '@/lib/format'; +import { cachedAsOf } from '@/lib/dataSource'; interface LiquidityHeatmapProps { data: LiquidityCell[]; @@ -73,7 +74,7 @@ export function LiquidityHeatmap({ data }: LiquidityHeatmapProps) {
{sources.map((s) => ( - + ))}
@@ -102,7 +103,7 @@ export function LiquidityHeatmap({ data }: LiquidityHeatmapProps) { {CHAIN_ORDER.map((chain) => { const cell = cellMap.get(`${issuer}:${chain}`); const value = cell?.tvlUsd ?? 0; - const isMocked = cell?.dataSource === 'mocked'; + const isCached = cell?.dataSource === 'cached'; return (
- {value === 0 ? '–' : `${isMocked ? '~' : ''}${formatUsdCompact(value)}`} + {value === 0 ? '–' : `${isCached ? '~' : ''}${formatUsdCompact(value)}`} {/* Tooltip on hover */} diff --git a/src/components/dashboard/MethodologyDrawer.tsx b/src/components/dashboard/MethodologyDrawer.tsx index c3682fa..83f6d5b 100644 --- a/src/components/dashboard/MethodologyDrawer.tsx +++ b/src/components/dashboard/MethodologyDrawer.tsx @@ -6,17 +6,17 @@ const SECTIONS = [ { title: 'Data sources', content: - 'Total TVL, the chain breakdown and the heatmap come from on-chain reads. Top holders come from Blockscout. Weekly flows and the three flow KPIs come from InstantManager event logs, read through Blockscout. Competitor TVL comes from DefiLlama. Every number has a badge: Live is read from its source, Mocked is a dated snapshot shown when the source fails, and Estimated mixes the two. Each source is cached for one hour.', + 'Total TVL, the chain breakdown and the heatmap come from on-chain reads. Top holders come from Blockscout. Weekly flows and the three flow KPIs come from InstantManager event logs, read through Blockscout. Competitor TVL comes from DefiLlama. Every number has a badge: Live is read from its source, Last good is the most recent real read, shown with its date when the source fails, and Estimated mixes the two. Nothing on this page is invented. Each source is cached for one hour.', }, { title: 'Total TVL and chains (Pillar 3)', content: - 'TVL is the total token supply on each chain times the OndoOracle price on Ethereum (0x9Cad45a8BF0Ed41Ff33074449B357C7a1fAb4094, getAssetPrice). Every chain uses that one price. EVM chains are read with totalSupply(). Solana uses getTokenSupply, Sui and Aptos their coin supply, Noble its bank supply, Stellar the Horizon asset record, and the XRP Ledger gateway_balances on the issuer. Bridges burn and mint, so each token is counted once, on the chain where it sits. rUSDY, rOUSG and mUSD wrap tokens already counted and are left out. A failed read shows that row\'s dated snapshot, labeled Mocked.', + 'TVL is the total token supply on each chain times the OndoOracle price on Ethereum (0x9Cad45a8BF0Ed41Ff33074449B357C7a1fAb4094, getAssetPrice). Every chain uses that one price. EVM chains are read with totalSupply(). Solana uses getTokenSupply, Sui and Aptos their coin supply, Noble its bank supply, Stellar the Horizon asset record, and the XRP Ledger gateway_balances on the issuer. Bridges burn and mint, so each token is counted once, on the chain where it sits. rUSDY, rOUSG and mUSD wrap tokens already counted and are left out. A failed read shows that row\'s last good value, labeled Last good with its date.', }, { - title: 'Top Ethereum holders (Pillar 1)', + title: 'Top holders (Pillar 1)', content: - 'The top 15 holders of OUSG and USDY on Ethereum, from the Blockscout token holders API, valued at the OndoOracle price. Names come from a manual address registry first, then Blockscout public name tags. Tags that only repeat a contract class name are ignored. Unnamed wallets show their address.', + 'The top 15 holders of OUSG and USDY on Ethereum, from the Blockscout token holders API, and of USDY on Stellar, from the stellar.expert holders API, valued at the OndoOracle price. Names come from a manual address registry first, then Blockscout public name tags. Tags that only repeat a contract class name are ignored. Unnamed wallets show their address.', }, { title: 'Weekly mint and redeem (Pillar 2)', @@ -26,12 +26,12 @@ const SECTIONS = [ { title: 'Competitive benchmark (Pillar 4)', content: - 'TVL for Invesco USTB on Superstate, OpenEden TBILL and BlackRock BUIDL comes from DefiLlama. The Ondo row uses the same on-chain total as Total TVL when every chain read succeeds, else DefiLlama. Chain counts and redemption terms quote each issuer\'s own docs, or say Not disclosed. Accounting may differ between products.', + 'TVL for Invesco USTB on Superstate, OpenEden TBILL and BlackRock BUIDL comes from DefiLlama. The Ondo row always uses the same on-chain total as Total TVL; if a chain read fell back, the row is labeled with the date of the last good value. USTB and TBILL have no free live source, so they show their last good value and its date. Chain counts and redemption terms quote each issuer\'s own docs, or say Not disclosed. Accounting may differ between products.', }, { title: 'Known gaps', content: - 'Holders cover Ethereum only. Supply-based TVL counts every token in circulation, including any inventory Ondo itself holds. The Ondo TVL includes BNB Chain and the XRP Ledger, which DefiLlama does not count.', + 'Holders cover Ethereum and Stellar USDY only. Supply-based TVL counts every token in circulation, including any inventory Ondo itself holds. The Ondo TVL includes BNB Chain and the XRP Ledger, which DefiLlama does not count.', }, ] as const; diff --git a/src/components/dashboard/TopHoldersChart.tsx b/src/components/dashboard/TopHoldersChart.tsx index 16c72bf..45257e4 100644 --- a/src/components/dashboard/TopHoldersChart.tsx +++ b/src/components/dashboard/TopHoldersChart.tsx @@ -1,6 +1,6 @@ 'use client'; -import type { HolderMetric } from '@/lib/types'; +import type { HolderChain, HolderMetric } from '@/lib/types'; import { DataSourceBadge } from '@/components/ui/DataSourceBadge'; import { formatUsdCompact, formatNumberCompact, formatDate } from '@/lib/format'; @@ -8,7 +8,16 @@ interface TopHoldersChartProps { data: HolderMetric[]; } -const TOKENS = ['USDY', 'OUSG'] as const; +const GROUPS: { chain: HolderChain; token: HolderMetric['token']; label: string }[] = [ + { chain: 'ethereum', token: 'USDY', label: 'USDY on Ethereum' }, + { chain: 'ethereum', token: 'OUSG', label: 'OUSG on Ethereum' }, + { chain: 'stellar', token: 'USDY', label: 'USDY on Stellar' }, +]; + +const accountUrl = (row: HolderMetric) => + row.chain === 'stellar' + ? `https://stellar.expert/explorer/public/account/${row.address}` + : `https://etherscan.io/address/${row.address}`; export function TopHoldersChart({ data }: TopHoldersChartProps) { const asOf = data[0]?.asOf; @@ -17,24 +26,29 @@ export function TopHoldersChart({ data }: TopHoldersChartProps) {

- Top Ethereum holders + Top holders

- Ethereum only. Names come from a manual address registry, then Blockscout public tags; unlabeled wallets show their address. + Ethereum (OUSG and USDY) and Stellar (USDY). Names come from a manual address registry, then Blockscout public tags; unlabeled wallets show their address.

- {TOKENS.map((token) => { - const rows = data.filter((d) => d.token === token).sort((a, b) => b.balance - a.balance); + {data.length === 0 && ( +

+ Unavailable: the holder sources did not respond and no last good value is stored. +

+ )} + {GROUPS.map(({ chain, token, label }) => { + const rows = data.filter((d) => d.token === token && d.chain === chain).sort((a, b) => b.balance - a.balance); if (!rows.length) return null; const total = rows.reduce((sum, d) => sum + d.tvlUsd, 0); return ( - +
@@ -43,12 +57,12 @@ export function TopHoldersChart({ data }: TopHoldersChartProps) { {rows.map((row) => { - const prefix = row.dataSource === 'mocked' ? '~' : ''; + const prefix = row.dataSource === 'cached' ? '~' : ''; return ( ); @@ -75,7 +89,7 @@ export function TopHoldersChart({ data }: TopHoldersChartProps) { {asOf && (

- Balances from Blockscout as of {formatDate(asOf)}, valued at the Ondo oracle price. + Balances from Blockscout (Ethereum) and stellar.expert (Stellar) as of {formatDate(asOf)}, valued at the Ondo oracle price.

)} diff --git a/src/components/flows/FlowCharts.tsx b/src/components/flows/FlowCharts.tsx index 89b592a..50b9280 100644 --- a/src/components/flows/FlowCharts.tsx +++ b/src/components/flows/FlowCharts.tsx @@ -98,7 +98,7 @@ interface FlowChartsProps { token: WeeklyFlow['token']; data: WeeklyFlow[]; asOf: string; - /** '~' when the data is mocked, else '' */ + /** '~' when the data is a last good value, else '' */ prefix: string; } diff --git a/src/components/flows/FlowsExplorer.tsx b/src/components/flows/FlowsExplorer.tsx index 3f94f2a..326c108 100644 --- a/src/components/flows/FlowsExplorer.tsx +++ b/src/components/flows/FlowsExplorer.tsx @@ -25,7 +25,7 @@ interface FlowsExplorerProps { export function FlowsExplorer({ rows, dataSource }: FlowsExplorerProps) { const [range, setRange] = useState('52W'); const weeks = RANGES.find((r) => r.id === range)!.weeks; - const prefix = dataSource === 'mocked' ? '~' : ''; + const prefix = dataSource === 'cached' ? '~' : ''; return ( <> @@ -66,7 +66,7 @@ interface TokenSectionProps { } function TokenSection({ token, rows, weeks, dataSource }: TokenSectionProps) { - const prefix = dataSource === 'mocked' ? '~' : ''; + const prefix = dataSource === 'cached' ? '~' : ''; const series = useMemo(() => weeklySeries(rows, token), [rows, token]); const visible = useMemo( () => (Number.isFinite(weeks) ? series.slice(-weeks) : series), @@ -95,7 +95,7 @@ function TokenSection({ token, rows, weeks, dataSource }: TokenSectionProps) { {lastComplete && (

- + Week of {formatWeek(lastComplete.week)}, last complete week

diff --git a/src/components/ui/DataSourceBadge.tsx b/src/components/ui/DataSourceBadge.tsx index 0710357..161103e 100644 --- a/src/components/ui/DataSourceBadge.tsx +++ b/src/components/ui/DataSourceBadge.tsx @@ -1,32 +1,38 @@ 'use client'; import type { DataSource } from '@/lib/types'; +import { formatDate } from '@/lib/format'; const LABELS: Record = { live: 'Live', estimated: 'Estimated', - mocked: 'Mocked', + cached: 'Last good', }; /** Reads the --badge-* tokens (globals.css) so labels pass AA in light and dark. */ const TONE: Record = { live: 'text-[color:var(--badge-live-fg)] bg-[var(--badge-live-bg)]', estimated: 'text-[color:var(--badge-estimated-fg)] bg-[var(--badge-estimated-bg)]', - mocked: 'text-[color:var(--badge-mocked-fg)] bg-[var(--badge-mocked-bg)]', + cached: 'text-[color:var(--badge-cached-fg)] bg-[var(--badge-cached-bg)]', }; interface DataSourceBadgeProps { source: DataSource; + /** Date of the real read behind a cached or mixed value; shown as "Last good, ". */ + asOf?: string; className?: string; } -export function DataSourceBadge({ source, className = '' }: DataSourceBadgeProps) { +export function DataSourceBadge({ source, asOf, className = '' }: DataSourceBadgeProps) { + const dated = asOf && source !== 'live' ? `, ${formatDate(asOf)}` : ''; return ( {LABELS[source]} + {dated} ); } diff --git a/src/components/ui/MetricCard.tsx b/src/components/ui/MetricCard.tsx index 82f546a..e0e4046 100644 --- a/src/components/ui/MetricCard.tsx +++ b/src/components/ui/MetricCard.tsx @@ -8,6 +8,8 @@ interface MetricCardProps { value: string; subValue?: string; dataSource: DataSource; + /** Date of the real read when the value is cached or mixed */ + asOf?: string; trend?: { direction: 'up' | 'down' | 'flat'; label: string; @@ -26,14 +28,14 @@ const TREND_ARROWS = { flat: '\u2192', } as const; -export function MetricCard({ label, value, subValue, dataSource, trend }: MetricCardProps) { - const prefix = dataSource === 'mocked' ? '~' : ''; +export function MetricCard({ label, value, subValue, dataSource, asOf, trend }: MetricCardProps) { + const prefix = dataSource === 'cached' ? '~' : ''; return (
{label} - +
{prefix}{value} diff --git a/src/data/last-good.json b/src/data/last-good.json new file mode 100644 index 0000000..7d2c644 --- /dev/null +++ b/src/data/last-good.json @@ -0,0 +1,3207 @@ +{ + "generatedAt": "2026-10-26T01:28:29.748Z", + "chainTvl": [ + { + "chain": "ethereum", + "token": "USDY", + "supply": 1106268931.7352247, + "tvlUsd": 1273295284.6431036, + "pctOfTotal": 47.298681780534245, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://etherscan.io/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C" + }, + { + "chain": "ethereum", + "token": "OUSG", + "supply": 1145233.4245377753, + "tvlUsd": 134005049.10230085, + "pctOfTotal": 4.977841550910297, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://etherscan.io/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92" + }, + { + "chain": "mantle", + "token": "USDY", + "supply": 319742.91192569473, + "tvlUsd": 368018.23713375727, + "pctOfTotal": 0.013670652595326136, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://mantlescan.xyz/token/0x5bE26527e817998A7206475496fDE1E68957c5A6" + }, + { + "chain": "arbitrum", + "token": "USDY", + "supply": 2728691.3014892153, + "tvlUsd": 3140673.725676357, + "pctOfTotal": 0.11666557547088413, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://arbiscan.io/token/0x35e050d3C0eC2d29D269a8EcEa763a183bDF9A9D" + }, + { + "chain": "polygon", + "token": "OUSG", + "supply": 0, + "tvlUsd": 0, + "pctOfTotal": 0, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://polygonscan.com/token/0xbA11C5effA33c4D6F8f593CFA394241CfE925811" + }, + { + "chain": "solana", + "token": "USDY", + "supply": 156471634.272894, + "tvlUsd": 180095986.05247745, + "pctOfTotal": 6.689966449247701, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://solscan.io/token/A1KLoBrKBde8Ty9qtNQUtq3C2ortoC3u7twggz7sEto6" + }, + { + "chain": "solana", + "token": "OUSG", + "supply": 0, + "tvlUsd": 0, + "pctOfTotal": 0, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://solscan.io/token/i7u4r16TcsJTgq1kAG8opmVZyVnAKBwLKu6ZPMwzxNc" + }, + { + "chain": "sui", + "token": "USDY", + "supply": 12492704.406295, + "tvlUsd": 14378874.030227866, + "pctOfTotal": 0.5341273114890738, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://suiscan.xyz/mainnet/coin/0x960b531667636f39e85867775f52f6b1f220a058c4de786905bdf761e06a56bb::usdy::USDY" + }, + { + "chain": "aptos", + "token": "USDY", + "supply": 1683.253542, + "tvlUsd": 1937.394006469646, + "pctOfTotal": 0.00007196773890606774, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://explorer.aptoslabs.com/coin/0xcfea864b32833f157f042618bd845145256b1bf4c0da34a7013b76e42daa53cc::usdy::USDY?network=mainnet" + }, + { + "chain": "noble", + "token": "USDY", + "supply": 62531.36992881482, + "tvlUsd": 71972.46183868246, + "pctOfTotal": 0.0026735374037167364, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://rest.cosmos.directory/noble/cosmos/bank/v1beta1/supply/by_denom?denom=ausdy" + }, + { + "chain": "stellar", + "token": "USDY", + "supply": 467502151.6966762, + "tvlUsd": 538086416.6384767, + "pctOfTotal": 19.988119407938406, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "plume", + "token": "USDY", + "supply": 0, + "tvlUsd": 0, + "pctOfTotal": 0, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://explorer.plume.org/token/0xD2B65e851Be3d80D3c2ce795eB2E78f16cB088b2" + }, + { + "chain": "sei", + "token": "USDY", + "supply": 225792078.8905642, + "tvlUsd": 259882548.55007493, + "pctOfTotal": 9.653771683941823, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://seitrace.com/token/0x54cD901491AeF397084453F4372B93c33260e2A6" + }, + { + "chain": "bnb", + "token": "USDY", + "supply": 89778219.7390415, + "tvlUsd": 103333087.08043335, + "pctOfTotal": 3.838480250547375, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://bscscan.com/token/0x608593d17A2decBbc4399e4185bE4922F97eD32E" + }, + { + "chain": "xrp-ledger", + "token": "OUSG", + "supply": 1584220.172977341, + "tvlUsd": 185371381.51933295, + "pctOfTotal": 6.88592983218225, + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://xrpscan.com/account/rHuiXXjHLpMP8ZE9sSQU5aADQVWDwv6h5p" + } + ], + "topHolders": [ + { + "chain": "ethereum", + "name": "Flux Finance fOUSG market", + "address": "0x1dD7950c266fB1be96180a8FDb0591F70200E018", + "token": "OUSG", + "balance": 336339.56172586576, + "tvlUsd": 39355469.82687137, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x5D87...3AeB)", + "address": "0x5D87Fa995c54ffF52cfE1C18d8EbF79f0eEb3AeB", + "token": "OUSG", + "balance": 261208.8329463971, + "tvlUsd": 30564338.880577322, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x56e6...A6A0)", + "address": "0x56e60979d5934a05D22606B0455d52F59F20A6A0", + "token": "OUSG", + "balance": 187133.33568220574, + "tvlUsd": 21896681.758910865, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xbd96...fB03)", + "address": "0xbd9676EA1D6cAD553E87EC456869633C669ffB03", + "token": "OUSG", + "balance": 75241.51296965874, + "tvlUsd": 8804093.928798834, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x3918...A437)", + "address": "0x391897d4A5Fddcb9e66a8cB837cfbFd6e918A437", + "token": "OUSG", + "balance": 61318.08092498709, + "tvlUsd": 7174897.509237617, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x5eD4...081f)", + "address": "0x5eD4EBAF21f83959f81b7e7545e25D313C84081f", + "token": "OUSG", + "balance": 61295.76424706415, + "tvlUsd": 7172286.209692226, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x0f36...C149)", + "address": "0x0f365d86809A00761a8DF914c812dbFDF481C149", + "token": "OUSG", + "balance": 47065.159371450296, + "tvlUsd": 5507147.152227373, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x4307...6AF7)", + "address": "0x4307b4C9D5A48AC7CB112eF6af7fbEFf5EFA6AF7", + "token": "OUSG", + "balance": 33150.532728184946, + "tvlUsd": 3878981.062573174, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x3Ee6...d11a)", + "address": "0x3Ee60C57d70a6EC4877Dc24AA7d0e6FaC61cd11a", + "token": "OUSG", + "balance": 17178.793854379262, + "tvlUsd": 2010109.960686416, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x233F...9470)", + "address": "0x233F8aBDcC60088634382ef12C78bdeBf69C9470", + "token": "OUSG", + "balance": 9950.12587661961, + "tvlUsd": 1164275.40281462, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xE05E...B2De)", + "address": "0xE05EC030AC8e066503c6c5Ea0403c74f8a8aB2De", + "token": "OUSG", + "balance": 9389.139184373755, + "tvlUsd": 1098633.719966877, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x6095...66FE)", + "address": "0x609517eACD9Ec2E24DfFD4Eeca54C6fd88C866FE", + "token": "OUSG", + "balance": 9147.24211048065, + "tvlUsd": 1070329.071700231, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x64be...055E)", + "address": "0x64beF4478942d8FD62bE281707076442aa2D055E", + "token": "OUSG", + "balance": 8693.543271862372, + "tvlUsd": 1017241.2610897105, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x02f8...5669)", + "address": "0x02f8319261c904cB8C07E5DE7F19508354705669", + "token": "OUSG", + "balance": 5605.263602365734, + "tvlUsd": 655878.18882384, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x9Ff8...55Cf)", + "address": "0x9Ff88e2c4844A47a70233BA676c3928b05d255Cf", + "token": "OUSG", + "balance": 4922.022038813531, + "tvlUsd": 575931.3261923209, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xA5b6...5302)", + "address": "0xA5b614026dCB1ef6e0E39AA53351b4F4bd225302", + "token": "USDY", + "balance": 359037168.46560884, + "tvlUsd": 413245206.9333612, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x661b...ee1a)", + "address": "0x661b5e00424B56Ba6e369e0c2f21E31B6FB8ee1a", + "token": "USDY", + "balance": 298780676.9813786, + "tvlUsd": 343891088.5313713, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x9C6F...4AC2)", + "address": "0x9C6Fa3b81cE92B7D82980D68CCCE9D6e48fe4AC2", + "token": "USDY", + "balance": 106360871.12102464, + "tvlUsd": 122419415.19274914, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Gate.io: Deposit Address", + "address": "0xC882b111A75C0c657fC507C04FbFcD2cC984F071", + "token": "USDY", + "balance": 94609375.61578274, + "tvlUsd": 108893659.0360984, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xC392...5041)", + "address": "0xC392749B6ff2cd95e5a4e3Ed396c93f813395041", + "token": "USDY", + "balance": 83405034.63963, + "tvlUsd": 95997667.72402988, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x1313...7aFc)", + "address": "0x13134B8d770907eCb263cB88a67F9AF833007aFc", + "token": "USDY", + "balance": 32661115.45848636, + "tvlUsd": 37592345.86769376, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xEeB0...8512)", + "address": "0xEeB066aDa2D5C5eD9E4D9ee042bdEED430ae8512", + "token": "USDY", + "balance": 28843984.010261923, + "tvlUsd": 33198897.462464247, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xC9E3...3D5A)", + "address": "0xC9E397454f5478f4418e45181849C6eA66303D5A", + "token": "USDY", + "balance": 18195193.44975739, + "tvlUsd": 20942334.506678693, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xc0db...8f8C)", + "address": "0xc0db94fDDE74f3902EA7995194d290F6BEE78f8C", + "token": "USDY", + "balance": 15992070.243504109, + "tvlUsd": 18406580.035467073, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x6870...fAED)", + "address": "0x6870289efC708e41A97AD5068A9F84b0DD2bfAED", + "token": "USDY", + "balance": 14473798.679576894, + "tvlUsd": 16659077.264939183, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xCE93...D9f5)", + "address": "0xCE93c2ab2aF688d036aA33b115E6852Bf893D9f5", + "token": "USDY", + "balance": 8241989.3002394205, + "tvlUsd": 9486378.773751486, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0x6d54...Ff94)", + "address": "0x6d542B698541fFd216c45AcDC1C6C1CD233dFf94", + "token": "USDY", + "balance": 4403415.429574929, + "tvlUsd": 5068250.532904228, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xd97e...2eEB)", + "address": "0xd97eCe4a24C4538d96E14296c5544c871caE2eEB", + "token": "USDY", + "balance": 3067064.0428429474, + "tvlUsd": 3530134.555369608, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xEC33...8a12)", + "address": "0xEC33298A3aC7A5F5B3f21A3218a5D8E862Da8a12", + "token": "USDY", + "balance": 2683281.529804244, + "tvlUsd": 3088407.9099198743, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "ethereum", + "name": "Unknown wallet (0xC463...13cF)", + "address": "0xC4630d28E388563351Fd2c152bA1507Fbb1D13cF", + "token": "USDY", + "balance": 2607999.4990271525, + "tvlUsd": 3001759.6709094257, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://eth.blockscout.com/token/0x96F6eF951840721AdBF46Ac996b59E0235CB985C?tab=holders" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GC635K...XSHC)", + "address": "GC635K2GGDMIINZBO6WWLQN6QUVDEBODEYCQ74OLEZUOA37K5E2BXSHC", + "token": "USDY", + "balance": 353217182, + "tvlUsd": 406546509.0753976, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GAY5KW...NMB2)", + "address": "GAY5KWJICHJ5U2QCBRWCPSEF3ARHTNLV4WUOVSCMKSJCET4DBBFANMB2", + "token": "USDY", + "balance": 94072566.6740941, + "tvlUsd": 108275801.7731865, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GBJN4Y...WS32)", + "address": "GBJN4YWKA5OAOX2CTOSRR5DGUHS773L5IAKXD4J6MQVGXV7CV7OSWS32", + "token": "USDY", + "balance": 8966269.09298, + "tvlUsd": 10320011.553632887, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GBUGSW...6IV3)", + "address": "GBUGSWJLP4MTUN5TO5MUP73KK2BU74QBKO4M7VYOJEUEVCPURHIA6IV3", + "token": "USDY", + "balance": 5339434.956995, + "tvlUsd": 6145591.870447183, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (CD2HWO...35VC)", + "address": "CD2HWOVQFUABQEMEI3X4AT2GJUWRRYYBT76O3NWJY5BHCJ3MOLZA35VC", + "token": "USDY", + "balance": 4600556.0960712, + "tvlUsd": 5295155.830395833, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (CAFHLH...HUSM)", + "address": "CAFHLHGZXOVNCGFJ7DOXL7JDNMBCEZKDI3LS5NRQH3GXC7CSIMQZHUSM", + "token": "USDY", + "balance": 893916.6342574, + "tvlUsd": 1028881.6784166942, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (CA75VV...7MHO)", + "address": "CA75VVHLWSM7W6ULNQI7ZJYDFOMQCCPKIDDDHBAL5KOKHWWKWQ5S7MHO", + "token": "USDY", + "balance": 334074.4062379, + "tvlUsd": 384513.5246774447, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (CATOPA...V6FD)", + "address": "CATOPAHXKSGLQ4RL37AP2I5SNPCAQ6Q5XMTHD5WQF27CUDHRYGBQV6FD", + "token": "USDY", + "balance": 46178.9874379, + "tvlUsd": 53151.169003762916, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GBGA77...YXK5)", + "address": "GBGA77CEPDTB6ZRKD3DZSCYGEB3JFZ7BJVUNWYMU2ARHBVR5OEDDYXK5", + "token": "USDY", + "balance": 4279.2836219, + "tvlUsd": 4925.377095123783, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GABNGX...F7KX)", + "address": "GABNGXVFO5LPSDJRT4NDBLS25H6TUJXPMWD5MP3GSOZ7S22SA7JWF7KX", + "token": "USDY", + "balance": 2950.5820082, + "tvlUsd": 3396.06586628163, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GASEUK...7MW4)", + "address": "GASEUKQDJ2ZVMK5UVDOLDAETDQDSRYIEUHN7AK2Z2N6HMY6YMZTM7MW4", + "token": "USDY", + "balance": 2220.064484, + "tvlUsd": 2555.253571703298, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GB2Y63...TPKC)", + "address": "GB2Y633ZHN5ZRECVZPBWJ2ZMOXHBG4TOB3FBZ7LZOKDCI6KJVSAXTPKC", + "token": "USDY", + "balance": 1925.0244005, + "tvlUsd": 2215.667837778727, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GDKRP7...LQFG)", + "address": "GDKRP7COFWG3V6N7TAMH4QZEBLGTWCGK6DUPU7BHF6EWQJ4ZJNCALQFG", + "token": "USDY", + "balance": 1525.2477143, + "tvlUsd": 1755.5321918736513, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (GATQFW...VNTS)", + "address": "GATQFWFOSIHBFIBWTKBD5O3FYBZCSIAOHIGFKJGIW5EN2E5ALT3LVNTS", + "token": "USDY", + "balance": 1378.8744494, + "tvlUsd": 1587.0592440682315, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + }, + { + "chain": "stellar", + "name": "Unknown wallet (CCV2X5...2OX6)", + "address": "CCV2X5THRWCT7HCGJU42DMOVYLAARVIPEDG2LQOVVA2WGONF2MZY2OX6", + "token": "USDY", + "balance": 1257.1182972, + "tvlUsd": 1446.9201422411782, + "dataSource": "live", + "asOf": "2026-10-26T01:28:29.776Z", + "source": "https://stellar.expert/explorer/public/asset/USDY-GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6" + } + ], + "weeklyFlows": [ + { + "week": "2024-04-22", + "token": "OUSG", + "mintVolumeUsd": 130000, + "redeemVolumeUsd": 379998.814562, + "netFlowUsd": -249998.81456199999, + "mintCount": 4, + "redeemCount": 5, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-04-29", + "token": "OUSG", + "mintVolumeUsd": 100286.35131, + "redeemVolumeUsd": 1245641.160387, + "netFlowUsd": -1145354.809077, + "mintCount": 10, + "redeemCount": 12, + "uniqueMinters": 3, + "uniqueRedeemers": 5, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-05-06", + "token": "OUSG", + "mintVolumeUsd": 898421.3669050001, + "redeemVolumeUsd": 808633.375891, + "netFlowUsd": 89787.99101400003, + "mintCount": 3, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-05-13", + "token": "OUSG", + "mintVolumeUsd": 1500000, + "redeemVolumeUsd": 2423873.777057, + "netFlowUsd": -923873.777057, + "mintCount": 4, + "redeemCount": 6, + "uniqueMinters": 3, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-05-20", + "token": "OUSG", + "mintVolumeUsd": 2048632.251021, + "redeemVolumeUsd": 985558.796889, + "netFlowUsd": 1063073.454132, + "mintCount": 7, + "redeemCount": 3, + "uniqueMinters": 6, + "uniqueRedeemers": 2, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-05-27", + "token": "OUSG", + "mintVolumeUsd": 2225099.434388, + "redeemVolumeUsd": 1858892.425668, + "netFlowUsd": 366207.00872000004, + "mintCount": 2, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-06-03", + "token": "OUSG", + "mintVolumeUsd": 734350.503735, + "redeemVolumeUsd": 0, + "netFlowUsd": 734350.503735, + "mintCount": 2, + "redeemCount": 0, + "uniqueMinters": 2, + "uniqueRedeemers": 0, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-06-10", + "token": "OUSG", + "mintVolumeUsd": 250000.004173, + "redeemVolumeUsd": 1042370.135269, + "netFlowUsd": -792370.131096, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-06-17", + "token": "OUSG", + "mintVolumeUsd": 650000.591014, + "redeemVolumeUsd": 239625.242999, + "netFlowUsd": 410375.348015, + "mintCount": 3, + "redeemCount": 1, + "uniqueMinters": 3, + "uniqueRedeemers": 1, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-06-24", + "token": "OUSG", + "mintVolumeUsd": 4270487.009524001, + "redeemVolumeUsd": 622225.4618279999, + "netFlowUsd": 3648261.547696001, + "mintCount": 6, + "redeemCount": 4, + "uniqueMinters": 4, + "uniqueRedeemers": 1, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-07-01", + "token": "OUSG", + "mintVolumeUsd": 3492000, + "redeemVolumeUsd": 1691999.999999, + "netFlowUsd": 1800000.000001, + "mintCount": 2, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-07-08", + "token": "OUSG", + "mintVolumeUsd": 2545999, + "redeemVolumeUsd": 949779.217795, + "netFlowUsd": 1596219.782205, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 3, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-07-15", + "token": "OUSG", + "mintVolumeUsd": 12578130.432426998, + "redeemVolumeUsd": 5256511.2613160005, + "netFlowUsd": 7321619.171110998, + "mintCount": 9, + "redeemCount": 3, + "uniqueMinters": 6, + "uniqueRedeemers": 3, + "uniqueWallets": 8, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-07-22", + "token": "OUSG", + "mintVolumeUsd": 5754776.106977001, + "redeemVolumeUsd": 1456580.5843139999, + "netFlowUsd": 4298195.522663001, + "mintCount": 7, + "redeemCount": 2, + "uniqueMinters": 5, + "uniqueRedeemers": 2, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-07-29", + "token": "OUSG", + "mintVolumeUsd": 3540737.0285139997, + "redeemVolumeUsd": 1420104.0184940002, + "netFlowUsd": 2120633.0100199995, + "mintCount": 3, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-08-05", + "token": "OUSG", + "mintVolumeUsd": 2009184.003315, + "redeemVolumeUsd": 10106831.414919998, + "netFlowUsd": -8097647.411604999, + "mintCount": 2, + "redeemCount": 5, + "uniqueMinters": 2, + "uniqueRedeemers": 5, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-08-12", + "token": "OUSG", + "mintVolumeUsd": 4726999.999999, + "redeemVolumeUsd": 13438528.533522, + "netFlowUsd": -8711528.533523, + "mintCount": 3, + "redeemCount": 5, + "uniqueMinters": 1, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-08-19", + "token": "OUSG", + "mintVolumeUsd": 2499999.999998, + "redeemVolumeUsd": 4593789.999997, + "netFlowUsd": -2093789.9999990002, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-08-26", + "token": "OUSG", + "mintVolumeUsd": 9457782.601677999, + "redeemVolumeUsd": 2407333.856356, + "netFlowUsd": 7050448.745321998, + "mintCount": 2, + "redeemCount": 4, + "uniqueMinters": 2, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-09-02", + "token": "OUSG", + "mintVolumeUsd": 6816869.857687, + "redeemVolumeUsd": 1942262.4728859998, + "netFlowUsd": 4874607.384801, + "mintCount": 3, + "redeemCount": 2, + "uniqueMinters": 3, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-09-09", + "token": "OUSG", + "mintVolumeUsd": 2602926.631048, + "redeemVolumeUsd": 1229547.699972, + "netFlowUsd": 1373378.931076, + "mintCount": 4, + "redeemCount": 4, + "uniqueMinters": 2, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-09-16", + "token": "OUSG", + "mintVolumeUsd": 1727410.956193, + "redeemVolumeUsd": 2050000.0758949998, + "netFlowUsd": -322589.1197019997, + "mintCount": 3, + "redeemCount": 2, + "uniqueMinters": 3, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-09-23", + "token": "OUSG", + "mintVolumeUsd": 2840312.592832, + "redeemVolumeUsd": 3263778.2725699996, + "netFlowUsd": -423465.67973799957, + "mintCount": 4, + "redeemCount": 4, + "uniqueMinters": 3, + "uniqueRedeemers": 2, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-09-30", + "token": "OUSG", + "mintVolumeUsd": 3224158.6907019997, + "redeemVolumeUsd": 10846488.671645999, + "netFlowUsd": -7622329.980943999, + "mintCount": 7, + "redeemCount": 7, + "uniqueMinters": 5, + "uniqueRedeemers": 3, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-10-07", + "token": "OUSG", + "mintVolumeUsd": 1637072.203252, + "redeemVolumeUsd": 625008.952243, + "netFlowUsd": 1012063.251009, + "mintCount": 3, + "redeemCount": 5, + "uniqueMinters": 3, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-10-14", + "token": "OUSG", + "mintVolumeUsd": 5578000, + "redeemVolumeUsd": 949305.6849379999, + "netFlowUsd": 4628694.315062, + "mintCount": 2, + "redeemCount": 8, + "uniqueMinters": 2, + "uniqueRedeemers": 7, + "uniqueWallets": 8, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-10-21", + "token": "OUSG", + "mintVolumeUsd": 1000000.344845, + "redeemVolumeUsd": 11349932.624075, + "netFlowUsd": -10349932.279229999, + "mintCount": 1, + "redeemCount": 5, + "uniqueMinters": 1, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-10-28", + "token": "OUSG", + "mintVolumeUsd": 5010863.919999, + "redeemVolumeUsd": 4059940.545298, + "netFlowUsd": 950923.3747009998, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 1, + "uniqueRedeemers": 3, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-11-04", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 6386310.338872, + "netFlowUsd": -6386310.338872, + "mintCount": 0, + "redeemCount": 1, + "uniqueMinters": 0, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-11-11", + "token": "OUSG", + "mintVolumeUsd": 7811809.215341, + "redeemVolumeUsd": 7281148.323948, + "netFlowUsd": 530660.8913930003, + "mintCount": 4, + "redeemCount": 4, + "uniqueMinters": 4, + "uniqueRedeemers": 2, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-11-18", + "token": "OUSG", + "mintVolumeUsd": 3000000, + "redeemVolumeUsd": 6130718.5053119995, + "netFlowUsd": -3130718.5053119995, + "mintCount": 1, + "redeemCount": 15, + "uniqueMinters": 1, + "uniqueRedeemers": 8, + "uniqueWallets": 9, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-11-25", + "token": "OUSG", + "mintVolumeUsd": 572190.908785, + "redeemVolumeUsd": 1264999.6838910002, + "netFlowUsd": -692808.7751060002, + "mintCount": 2, + "redeemCount": 5, + "uniqueMinters": 2, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-12-02", + "token": "OUSG", + "mintVolumeUsd": 1012429.856648, + "redeemVolumeUsd": 15845497.460755, + "netFlowUsd": -14833067.604107, + "mintCount": 2, + "redeemCount": 9, + "uniqueMinters": 1, + "uniqueRedeemers": 8, + "uniqueWallets": 8, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-12-09", + "token": "OUSG", + "mintVolumeUsd": 81058.25172, + "redeemVolumeUsd": 9749734.732807001, + "netFlowUsd": -9668676.481087001, + "mintCount": 2, + "redeemCount": 6, + "uniqueMinters": 2, + "uniqueRedeemers": 6, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-12-16", + "token": "OUSG", + "mintVolumeUsd": 628139.932785, + "redeemVolumeUsd": 1499969.6231489999, + "netFlowUsd": -871829.6903639999, + "mintCount": 3, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-12-23", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 2735643.405989, + "netFlowUsd": -2735643.405989, + "mintCount": 0, + "redeemCount": 5, + "uniqueMinters": 0, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2024-12-30", + "token": "OUSG", + "mintVolumeUsd": 150000, + "redeemVolumeUsd": 2065972.3982000002, + "netFlowUsd": -1915972.3982000002, + "mintCount": 1, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-01-06", + "token": "OUSG", + "mintVolumeUsd": 3341945.2225679997, + "redeemVolumeUsd": 1050000.032893, + "netFlowUsd": 2291945.1896749996, + "mintCount": 5, + "redeemCount": 2, + "uniqueMinters": 5, + "uniqueRedeemers": 2, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-01-13", + "token": "OUSG", + "mintVolumeUsd": 5081815.05233, + "redeemVolumeUsd": 2498261.773421, + "netFlowUsd": 2583553.278909, + "mintCount": 3, + "redeemCount": 4, + "uniqueMinters": 3, + "uniqueRedeemers": 3, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-01-20", + "token": "OUSG", + "mintVolumeUsd": 15629525.044571001, + "redeemVolumeUsd": 92946.964572, + "netFlowUsd": 15536578.079999002, + "mintCount": 4, + "redeemCount": 1, + "uniqueMinters": 2, + "uniqueRedeemers": 1, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-01-27", + "token": "OUSG", + "mintVolumeUsd": 60450849.746738, + "redeemVolumeUsd": 5455630.77788, + "netFlowUsd": 54995218.968858, + "mintCount": 6, + "redeemCount": 5, + "uniqueMinters": 5, + "uniqueRedeemers": 5, + "uniqueWallets": 8, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-02-03", + "token": "OUSG", + "mintVolumeUsd": 34376799.906381, + "redeemVolumeUsd": 10145008.155148, + "netFlowUsd": 24231791.751233004, + "mintCount": 5, + "redeemCount": 4, + "uniqueMinters": 3, + "uniqueRedeemers": 3, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-02-10", + "token": "OUSG", + "mintVolumeUsd": 27000000, + "redeemVolumeUsd": 2050005.8692080001, + "netFlowUsd": 24949994.130792, + "mintCount": 2, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 1, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-02-17", + "token": "OUSG", + "mintVolumeUsd": 150101000, + "redeemVolumeUsd": 94617.972483, + "netFlowUsd": 150006382.027517, + "mintCount": 5, + "redeemCount": 1, + "uniqueMinters": 4, + "uniqueRedeemers": 1, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-02-24", + "token": "OUSG", + "mintVolumeUsd": 10168857.467513, + "redeemVolumeUsd": 22212070.994764, + "netFlowUsd": -12043213.527251, + "mintCount": 2, + "redeemCount": 4, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-03-03", + "token": "OUSG", + "mintVolumeUsd": 6529331.855395, + "redeemVolumeUsd": 5101071.096520999, + "netFlowUsd": 1428260.758874001, + "mintCount": 7, + "redeemCount": 5, + "uniqueMinters": 3, + "uniqueRedeemers": 5, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-03-10", + "token": "OUSG", + "mintVolumeUsd": 3830855.105281, + "redeemVolumeUsd": 1036052.1180060001, + "netFlowUsd": 2794802.9872749997, + "mintCount": 5, + "redeemCount": 5, + "uniqueMinters": 3, + "uniqueRedeemers": 4, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-03-17", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 15018082.157828001, + "netFlowUsd": -15018082.157828001, + "mintCount": 0, + "redeemCount": 4, + "uniqueMinters": 0, + "uniqueRedeemers": 3, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-03-24", + "token": "OUSG", + "mintVolumeUsd": 17207739.742404003, + "redeemVolumeUsd": 20907248.236317, + "netFlowUsd": -3699508.4939129986, + "mintCount": 4, + "redeemCount": 5, + "uniqueMinters": 4, + "uniqueRedeemers": 4, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-03-31", + "token": "OUSG", + "mintVolumeUsd": 16563650.844679, + "redeemVolumeUsd": 2780085.6633300004, + "netFlowUsd": 13783565.181349, + "mintCount": 7, + "redeemCount": 5, + "uniqueMinters": 5, + "uniqueRedeemers": 4, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-04-07", + "token": "OUSG", + "mintVolumeUsd": 1179067.818513, + "redeemVolumeUsd": 15123134.903701395, + "netFlowUsd": -13944067.085188394, + "mintCount": 3, + "redeemCount": 5, + "uniqueMinters": 3, + "uniqueRedeemers": 5, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-04-14", + "token": "OUSG", + "mintVolumeUsd": 13751522.326238, + "redeemVolumeUsd": 14981491.28527681, + "netFlowUsd": -1229968.959038809, + "mintCount": 2, + "redeemCount": 4, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-04-21", + "token": "OUSG", + "mintVolumeUsd": 34166173.262712, + "redeemVolumeUsd": 1400000, + "netFlowUsd": 32766173.262712, + "mintCount": 4, + "redeemCount": 2, + "uniqueMinters": 4, + "uniqueRedeemers": 2, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-04-28", + "token": "OUSG", + "mintVolumeUsd": 40801883.208484, + "redeemVolumeUsd": 3952899.4918508097, + "netFlowUsd": 36848983.71663319, + "mintCount": 4, + "redeemCount": 4, + "uniqueMinters": 4, + "uniqueRedeemers": 4, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-05-05", + "token": "OUSG", + "mintVolumeUsd": 102003674.495197, + "redeemVolumeUsd": 18738127.99742533, + "netFlowUsd": 83265546.49777167, + "mintCount": 9, + "redeemCount": 8, + "uniqueMinters": 6, + "uniqueRedeemers": 5, + "uniqueWallets": 8, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-05-12", + "token": "OUSG", + "mintVolumeUsd": 41010000, + "redeemVolumeUsd": 4229364.403952304, + "netFlowUsd": 36780635.5960477, + "mintCount": 4, + "redeemCount": 9, + "uniqueMinters": 2, + "uniqueRedeemers": 6, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-05-19", + "token": "OUSG", + "mintVolumeUsd": 57000000, + "redeemVolumeUsd": 3324550.2550138542, + "netFlowUsd": 53675449.74498615, + "mintCount": 3, + "redeemCount": 4, + "uniqueMinters": 3, + "uniqueRedeemers": 4, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-05-26", + "token": "OUSG", + "mintVolumeUsd": 32000000, + "redeemVolumeUsd": 2000000, + "netFlowUsd": 30000000, + "mintCount": 2, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-06-02", + "token": "OUSG", + "mintVolumeUsd": 24517300.606942, + "redeemVolumeUsd": 498644.4354840892, + "netFlowUsd": 24018656.171457913, + "mintCount": 5, + "redeemCount": 2, + "uniqueMinters": 5, + "uniqueRedeemers": 2, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-06-09", + "token": "OUSG", + "mintVolumeUsd": 834992.0099899999, + "redeemVolumeUsd": 3340933.2557663894, + "netFlowUsd": -2505941.2457763897, + "mintCount": 4, + "redeemCount": 5, + "uniqueMinters": 2, + "uniqueRedeemers": 5, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-06-16", + "token": "OUSG", + "mintVolumeUsd": 383997.01344099996, + "redeemVolumeUsd": 49999.99999999999, + "netFlowUsd": 333997.01344099996, + "mintCount": 2, + "redeemCount": 1, + "uniqueMinters": 2, + "uniqueRedeemers": 1, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-06-23", + "token": "OUSG", + "mintVolumeUsd": 588996.7, + "redeemVolumeUsd": 2049500, + "netFlowUsd": -1460503.3, + "mintCount": 3, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-06-30", + "token": "OUSG", + "mintVolumeUsd": 679976.8034989999, + "redeemVolumeUsd": 1451000, + "netFlowUsd": -771023.1965010001, + "mintCount": 2, + "redeemCount": 4, + "uniqueMinters": 2, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-07-07", + "token": "OUSG", + "mintVolumeUsd": 459997.611915, + "redeemVolumeUsd": 2241134.6239942694, + "netFlowUsd": -1781137.0120792694, + "mintCount": 2, + "redeemCount": 5, + "uniqueMinters": 2, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-07-14", + "token": "OUSG", + "mintVolumeUsd": 721842.0590019999, + "redeemVolumeUsd": 1710250.202357885, + "netFlowUsd": -988408.143355885, + "mintCount": 1, + "redeemCount": 5, + "uniqueMinters": 1, + "uniqueRedeemers": 4, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-07-21", + "token": "OUSG", + "mintVolumeUsd": 2848150.921352, + "redeemVolumeUsd": 5937462.4392450005, + "netFlowUsd": -3089311.5178930005, + "mintCount": 3, + "redeemCount": 8, + "uniqueMinters": 2, + "uniqueRedeemers": 7, + "uniqueWallets": 9, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-07-28", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 4525000, + "netFlowUsd": -4525000, + "mintCount": 0, + "redeemCount": 2, + "uniqueMinters": 0, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-08-04", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 1180815.214423377, + "netFlowUsd": -1180815.214423377, + "mintCount": 0, + "redeemCount": 2, + "uniqueMinters": 0, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-08-11", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 1440000, + "netFlowUsd": -1440000, + "mintCount": 0, + "redeemCount": 2, + "uniqueMinters": 0, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-08-18", + "token": "OUSG", + "mintVolumeUsd": 30000000, + "redeemVolumeUsd": 2831494.530535, + "netFlowUsd": 27168505.469465, + "mintCount": 2, + "redeemCount": 6, + "uniqueMinters": 1, + "uniqueRedeemers": 4, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-08-25", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 236340.62966022547, + "netFlowUsd": -236340.62966022547, + "mintCount": 0, + "redeemCount": 2, + "uniqueMinters": 0, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-09-01", + "token": "OUSG", + "mintVolumeUsd": 1284715.778725, + "redeemVolumeUsd": 4200439.819331331, + "netFlowUsd": -2915724.040606331, + "mintCount": 2, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-09-08", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 894839.9269769855, + "netFlowUsd": -894839.9269769855, + "mintCount": 0, + "redeemCount": 5, + "uniqueMinters": 0, + "uniqueRedeemers": 3, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-09-15", + "token": "OUSG", + "mintVolumeUsd": 2078870.7206410002, + "redeemVolumeUsd": 1910050, + "netFlowUsd": 168820.7206410002, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-09-22", + "token": "OUSG", + "mintVolumeUsd": 538907.596815, + "redeemVolumeUsd": 2573713.205887082, + "netFlowUsd": -2034805.609072082, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 1, + "uniqueRedeemers": 3, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-09-29", + "token": "OUSG", + "mintVolumeUsd": 20406250.288295, + "redeemVolumeUsd": 592291.7184876697, + "netFlowUsd": 19813958.569807332, + "mintCount": 3, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-10-06", + "token": "OUSG", + "mintVolumeUsd": 43005777.65381, + "redeemVolumeUsd": 6030867.09375, + "netFlowUsd": 36974910.56006, + "mintCount": 4, + "redeemCount": 3, + "uniqueMinters": 3, + "uniqueRedeemers": 3, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-10-13", + "token": "OUSG", + "mintVolumeUsd": 4911742.864794999, + "redeemVolumeUsd": 350000, + "netFlowUsd": 4561742.864794999, + "mintCount": 6, + "redeemCount": 1, + "uniqueMinters": 3, + "uniqueRedeemers": 1, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-10-20", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 281500, + "netFlowUsd": -281500, + "mintCount": 0, + "redeemCount": 2, + "uniqueMinters": 0, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-10-27", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 7455142.302738274, + "netFlowUsd": -7455142.302738274, + "mintCount": 0, + "redeemCount": 7, + "uniqueMinters": 0, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-11-03", + "token": "OUSG", + "mintVolumeUsd": 2356720.996429, + "redeemVolumeUsd": 270000, + "netFlowUsd": 2086720.996429, + "mintCount": 4, + "redeemCount": 2, + "uniqueMinters": 3, + "uniqueRedeemers": 1, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-11-10", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 1500000, + "netFlowUsd": -1500000, + "mintCount": 0, + "redeemCount": 1, + "uniqueMinters": 0, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-11-17", + "token": "OUSG", + "mintVolumeUsd": 167337.346231, + "redeemVolumeUsd": 6416319.724727274, + "netFlowUsd": -6248982.378496274, + "mintCount": 3, + "redeemCount": 4, + "uniqueMinters": 2, + "uniqueRedeemers": 4, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-11-24", + "token": "OUSG", + "mintVolumeUsd": 148558.55333099997, + "redeemVolumeUsd": 4850630.033564722, + "netFlowUsd": -4702071.480233722, + "mintCount": 3, + "redeemCount": 3, + "uniqueMinters": 1, + "uniqueRedeemers": 3, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-12-01", + "token": "OUSG", + "mintVolumeUsd": 5000, + "redeemVolumeUsd": 1098666.3696578154, + "netFlowUsd": -1093666.3696578154, + "mintCount": 1, + "redeemCount": 3, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-12-08", + "token": "OUSG", + "mintVolumeUsd": 50307033.11813, + "redeemVolumeUsd": 1010500.9824805994, + "netFlowUsd": 49296532.1356494, + "mintCount": 10, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-12-15", + "token": "OUSG", + "mintVolumeUsd": 8200000, + "redeemVolumeUsd": 800000, + "netFlowUsd": 7400000, + "mintCount": 3, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-12-15", + "token": "USDY", + "mintVolumeUsd": 8.000007, + "redeemVolumeUsd": 3.34947523, + "netFlowUsd": 4.650531770000001, + "mintCount": 8, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-12-22", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 2051165.91575, + "netFlowUsd": -2051165.91575, + "mintCount": 0, + "redeemCount": 3, + "uniqueMinters": 0, + "uniqueRedeemers": 3, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2025-12-29", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 4204929.003450001, + "netFlowUsd": -4204929.003450001, + "mintCount": 0, + "redeemCount": 3, + "uniqueMinters": 0, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-01-12", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 6005446.9977, + "netFlowUsd": -6005446.9977, + "mintCount": 0, + "redeemCount": 1, + "uniqueMinters": 0, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-01-19", + "token": "OUSG", + "mintVolumeUsd": 45875.087802, + "redeemVolumeUsd": 6695487.435016677, + "netFlowUsd": -6649612.347214676, + "mintCount": 2, + "redeemCount": 4, + "uniqueMinters": 2, + "uniqueRedeemers": 3, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-01-26", + "token": "OUSG", + "mintVolumeUsd": 6557223.918017, + "redeemVolumeUsd": 17217653.49497293, + "netFlowUsd": -10660429.57695593, + "mintCount": 3, + "redeemCount": 7, + "uniqueMinters": 3, + "uniqueRedeemers": 6, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-01-26", + "token": "USDY", + "mintVolumeUsd": 4.12134, + "redeemVolumeUsd": 3.2427944061185725, + "netFlowUsd": 0.8785455938814275, + "mintCount": 4, + "redeemCount": 3, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-02", + "token": "OUSG", + "mintVolumeUsd": 32455000, + "redeemVolumeUsd": 11658248.028426738, + "netFlowUsd": 20796751.971573263, + "mintCount": 7, + "redeemCount": 4, + "uniqueMinters": 1, + "uniqueRedeemers": 3, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-02", + "token": "USDY", + "mintVolumeUsd": 2, + "redeemVolumeUsd": 0, + "netFlowUsd": 2, + "mintCount": 2, + "redeemCount": 0, + "uniqueMinters": 2, + "uniqueRedeemers": 0, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-09", + "token": "OUSG", + "mintVolumeUsd": 10100000, + "redeemVolumeUsd": 200000.00000000003, + "netFlowUsd": 9900000, + "mintCount": 3, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-09", + "token": "USDY", + "mintVolumeUsd": 1, + "redeemVolumeUsd": 0, + "netFlowUsd": 1, + "mintCount": 1, + "redeemCount": 0, + "uniqueMinters": 1, + "uniqueRedeemers": 0, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-16", + "token": "OUSG", + "mintVolumeUsd": 10199495.111271, + "redeemVolumeUsd": 13245380.405225666, + "netFlowUsd": -3045885.2939546667, + "mintCount": 2, + "redeemCount": 9, + "uniqueMinters": 2, + "uniqueRedeemers": 6, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-16", + "token": "USDY", + "mintVolumeUsd": 1, + "redeemVolumeUsd": 0, + "netFlowUsd": 1, + "mintCount": 1, + "redeemCount": 0, + "uniqueMinters": 1, + "uniqueRedeemers": 0, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-23", + "token": "OUSG", + "mintVolumeUsd": 49278633.134433, + "redeemVolumeUsd": 4851143.765399938, + "netFlowUsd": 44427489.36903306, + "mintCount": 11, + "redeemCount": 3, + "uniqueMinters": 6, + "uniqueRedeemers": 3, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-02-23", + "token": "USDY", + "mintVolumeUsd": 10, + "redeemVolumeUsd": 4.49829656, + "netFlowUsd": 5.50170344, + "mintCount": 1, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-02", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 3056452.5922000473, + "netFlowUsd": -3056452.5922000473, + "mintCount": 0, + "redeemCount": 2, + "uniqueMinters": 0, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-09", + "token": "OUSG", + "mintVolumeUsd": 50000.013106, + "redeemVolumeUsd": 4289568.283839594, + "netFlowUsd": -4239568.270733594, + "mintCount": 1, + "redeemCount": 4, + "uniqueMinters": 1, + "uniqueRedeemers": 3, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-09", + "token": "USDY", + "mintVolumeUsd": 1227071.0000000002, + "redeemVolumeUsd": 0, + "netFlowUsd": 1227071.0000000002, + "mintCount": 2, + "redeemCount": 0, + "uniqueMinters": 1, + "uniqueRedeemers": 0, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-16", + "token": "OUSG", + "mintVolumeUsd": 1678517.84741, + "redeemVolumeUsd": 7126577.849676937, + "netFlowUsd": -5448060.002266937, + "mintCount": 3, + "redeemCount": 9, + "uniqueMinters": 3, + "uniqueRedeemers": 4, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-16", + "token": "USDY", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 1227892.1193576239, + "netFlowUsd": -1227892.1193576239, + "mintCount": 0, + "redeemCount": 3, + "uniqueMinters": 0, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-23", + "token": "OUSG", + "mintVolumeUsd": 122712.78555799999, + "redeemVolumeUsd": 46401657.493770994, + "netFlowUsd": -46278944.708212994, + "mintCount": 2, + "redeemCount": 13, + "uniqueMinters": 2, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-03-30", + "token": "OUSG", + "mintVolumeUsd": 56000000, + "redeemVolumeUsd": 849974.792618, + "netFlowUsd": 55150025.207382, + "mintCount": 3, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-06", + "token": "OUSG", + "mintVolumeUsd": 11187439.371360999, + "redeemVolumeUsd": 4619762.353088, + "netFlowUsd": 6567677.018272999, + "mintCount": 5, + "redeemCount": 3, + "uniqueMinters": 4, + "uniqueRedeemers": 3, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-06", + "token": "USDY", + "mintVolumeUsd": 1, + "redeemVolumeUsd": 1.12866587, + "netFlowUsd": -0.12866587000000007, + "mintCount": 1, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-13", + "token": "OUSG", + "mintVolumeUsd": 5000000, + "redeemVolumeUsd": 4570625.480590999, + "netFlowUsd": 429374.5194090009, + "mintCount": 1, + "redeemCount": 7, + "uniqueMinters": 1, + "uniqueRedeemers": 3, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-13", + "token": "USDY", + "mintVolumeUsd": 39165311.2597, + "redeemVolumeUsd": 3551859.78040388, + "netFlowUsd": 35613451.47929612, + "mintCount": 43, + "redeemCount": 7, + "uniqueMinters": 17, + "uniqueRedeemers": 4, + "uniqueWallets": 17, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-20", + "token": "OUSG", + "mintVolumeUsd": 300000.11536299996, + "redeemVolumeUsd": 4073119.7672431655, + "netFlowUsd": -3773119.6518801656, + "mintCount": 2, + "redeemCount": 8, + "uniqueMinters": 2, + "uniqueRedeemers": 5, + "uniqueWallets": 6, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-20", + "token": "USDY", + "mintVolumeUsd": 2791452.399083, + "redeemVolumeUsd": 2261502.4477888267, + "netFlowUsd": 529949.9512941735, + "mintCount": 26, + "redeemCount": 2, + "uniqueMinters": 11, + "uniqueRedeemers": 2, + "uniqueWallets": 12, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-27", + "token": "OUSG", + "mintVolumeUsd": 11348243.224386, + "redeemVolumeUsd": 150338.606195, + "netFlowUsd": 11197904.618191, + "mintCount": 1, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-04-27", + "token": "USDY", + "mintVolumeUsd": 1283886.7571479997, + "redeemVolumeUsd": 298982.6110575197, + "netFlowUsd": 984904.14609048, + "mintCount": 17, + "redeemCount": 7, + "uniqueMinters": 8, + "uniqueRedeemers": 4, + "uniqueWallets": 10, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-04", + "token": "OUSG", + "mintVolumeUsd": 381413.841451, + "redeemVolumeUsd": 2650071.262764, + "netFlowUsd": -2268657.421313, + "mintCount": 2, + "redeemCount": 3, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-04", + "token": "USDY", + "mintVolumeUsd": 3560828.039173, + "redeemVolumeUsd": 719370.1626836132, + "netFlowUsd": 2841457.876489387, + "mintCount": 24, + "redeemCount": 6, + "uniqueMinters": 9, + "uniqueRedeemers": 6, + "uniqueWallets": 12, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-11", + "token": "OUSG", + "mintVolumeUsd": 2000000.0001, + "redeemVolumeUsd": 16290940.815229546, + "netFlowUsd": -14290940.815129546, + "mintCount": 1, + "redeemCount": 6, + "uniqueMinters": 1, + "uniqueRedeemers": 6, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-11", + "token": "USDY", + "mintVolumeUsd": 536602.145637, + "redeemVolumeUsd": 3813370.6892281775, + "netFlowUsd": -3276768.5435911776, + "mintCount": 8, + "redeemCount": 47, + "uniqueMinters": 7, + "uniqueRedeemers": 11, + "uniqueWallets": 17, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-18", + "token": "OUSG", + "mintVolumeUsd": 18473826.058109, + "redeemVolumeUsd": 20042000, + "netFlowUsd": -1568173.9418909997, + "mintCount": 6, + "redeemCount": 6, + "uniqueMinters": 3, + "uniqueRedeemers": 2, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-18", + "token": "USDY", + "mintVolumeUsd": 2865625.6767689995, + "redeemVolumeUsd": 3299266.8091861, + "netFlowUsd": -433641.1324171005, + "mintCount": 13, + "redeemCount": 16, + "uniqueMinters": 10, + "uniqueRedeemers": 5, + "uniqueWallets": 13, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-25", + "token": "OUSG", + "mintVolumeUsd": 9253059.879068999, + "redeemVolumeUsd": 22943884.391391777, + "netFlowUsd": -13690824.512322778, + "mintCount": 7, + "redeemCount": 8, + "uniqueMinters": 5, + "uniqueRedeemers": 3, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-05-25", + "token": "USDY", + "mintVolumeUsd": 3094870.1387409996, + "redeemVolumeUsd": 3759855.9176289733, + "netFlowUsd": -664985.7788879736, + "mintCount": 29, + "redeemCount": 16, + "uniqueMinters": 14, + "uniqueRedeemers": 11, + "uniqueWallets": 21, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-01", + "token": "OUSG", + "mintVolumeUsd": 68108758.965388, + "redeemVolumeUsd": 78346287.67335188, + "netFlowUsd": -10237528.707963884, + "mintCount": 11, + "redeemCount": 12, + "uniqueMinters": 6, + "uniqueRedeemers": 4, + "uniqueWallets": 8, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-01", + "token": "USDY", + "mintVolumeUsd": 1124804.642313, + "redeemVolumeUsd": 2527464.6653737086, + "netFlowUsd": -1402660.0230607085, + "mintCount": 24, + "redeemCount": 19, + "uniqueMinters": 14, + "uniqueRedeemers": 11, + "uniqueWallets": 22, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-08", + "token": "OUSG", + "mintVolumeUsd": 1398790.370635, + "redeemVolumeUsd": 7140115.824728707, + "netFlowUsd": -5741325.454093707, + "mintCount": 1, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-08", + "token": "USDY", + "mintVolumeUsd": 6682200.241413999, + "redeemVolumeUsd": 251070.01269517493, + "netFlowUsd": 6431130.228718825, + "mintCount": 20, + "redeemCount": 14, + "uniqueMinters": 13, + "uniqueRedeemers": 10, + "uniqueWallets": 19, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-15", + "token": "OUSG", + "mintVolumeUsd": 0, + "redeemVolumeUsd": 5184562.185699406, + "netFlowUsd": -5184562.185699406, + "mintCount": 0, + "redeemCount": 4, + "uniqueMinters": 0, + "uniqueRedeemers": 2, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-15", + "token": "USDY", + "mintVolumeUsd": 6985452.279637, + "redeemVolumeUsd": 250057.78238929997, + "netFlowUsd": 6735394.4972477, + "mintCount": 104, + "redeemCount": 4, + "uniqueMinters": 15, + "uniqueRedeemers": 4, + "uniqueWallets": 17, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-22", + "token": "OUSG", + "mintVolumeUsd": 6105000, + "redeemVolumeUsd": 2354731.2326914193, + "netFlowUsd": 3750268.7673085807, + "mintCount": 3, + "redeemCount": 1, + "uniqueMinters": 3, + "uniqueRedeemers": 1, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-22", + "token": "USDY", + "mintVolumeUsd": 4499368.470479, + "redeemVolumeUsd": 2208890.3563168873, + "netFlowUsd": 2290478.114162113, + "mintCount": 41, + "redeemCount": 46, + "uniqueMinters": 17, + "uniqueRedeemers": 12, + "uniqueWallets": 26, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-29", + "token": "OUSG", + "mintVolumeUsd": 3165471.223224, + "redeemVolumeUsd": 70000, + "netFlowUsd": 3095471.223224, + "mintCount": 4, + "redeemCount": 1, + "uniqueMinters": 3, + "uniqueRedeemers": 1, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-06-29", + "token": "USDY", + "mintVolumeUsd": 2505506.1381429997, + "redeemVolumeUsd": 9492022.496105997, + "netFlowUsd": -6986516.357962998, + "mintCount": 28, + "redeemCount": 109, + "uniqueMinters": 11, + "uniqueRedeemers": 13, + "uniqueWallets": 19, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-06", + "token": "OUSG", + "mintVolumeUsd": 5505000, + "redeemVolumeUsd": 0, + "netFlowUsd": 5505000, + "mintCount": 4, + "redeemCount": 0, + "uniqueMinters": 4, + "uniqueRedeemers": 0, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-06", + "token": "USDY", + "mintVolumeUsd": 1366215.2718860002, + "redeemVolumeUsd": 1013233.9640380966, + "netFlowUsd": 352981.3078479036, + "mintCount": 17, + "redeemCount": 36, + "uniqueMinters": 10, + "uniqueRedeemers": 6, + "uniqueWallets": 13, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-13", + "token": "OUSG", + "mintVolumeUsd": 1172314.98571, + "redeemVolumeUsd": 1138027.8634551074, + "netFlowUsd": 34287.122254892485, + "mintCount": 2, + "redeemCount": 5, + "uniqueMinters": 2, + "uniqueRedeemers": 4, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-13", + "token": "USDY", + "mintVolumeUsd": 2366073.4489940004, + "redeemVolumeUsd": 2934134.055265809, + "netFlowUsd": -568060.6062718085, + "mintCount": 15, + "redeemCount": 63, + "uniqueMinters": 12, + "uniqueRedeemers": 9, + "uniqueWallets": 17, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-20", + "token": "OUSG", + "mintVolumeUsd": 3369426.7306700004, + "redeemVolumeUsd": 99999.99999999999, + "netFlowUsd": 3269426.7306700004, + "mintCount": 4, + "redeemCount": 1, + "uniqueMinters": 3, + "uniqueRedeemers": 1, + "uniqueWallets": 4, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-20", + "token": "USDY", + "mintVolumeUsd": 2069127.0143480003, + "redeemVolumeUsd": 1819258.2423790598, + "netFlowUsd": 249868.7719689405, + "mintCount": 24, + "redeemCount": 12, + "uniqueMinters": 12, + "uniqueRedeemers": 7, + "uniqueWallets": 15, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-27", + "token": "OUSG", + "mintVolumeUsd": 1350000, + "redeemVolumeUsd": 8460954.816869933, + "netFlowUsd": -7110954.816869933, + "mintCount": 2, + "redeemCount": 7, + "uniqueMinters": 2, + "uniqueRedeemers": 4, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-07-27", + "token": "USDY", + "mintVolumeUsd": 3965834.646735001, + "redeemVolumeUsd": 5577785.025226065, + "netFlowUsd": -1611950.378491064, + "mintCount": 22, + "redeemCount": 41, + "uniqueMinters": 9, + "uniqueRedeemers": 10, + "uniqueWallets": 15, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-03", + "token": "OUSG", + "mintVolumeUsd": 49882, + "redeemVolumeUsd": 0, + "netFlowUsd": 49882, + "mintCount": 1, + "redeemCount": 0, + "uniqueMinters": 1, + "uniqueRedeemers": 0, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-03", + "token": "USDY", + "mintVolumeUsd": 1218691.233765, + "redeemVolumeUsd": 10131150.029993735, + "netFlowUsd": -8912458.796228735, + "mintCount": 18, + "redeemCount": 78, + "uniqueMinters": 12, + "uniqueRedeemers": 12, + "uniqueWallets": 21, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-10", + "token": "OUSG", + "mintVolumeUsd": 4248871.978202, + "redeemVolumeUsd": 5000394.58845, + "netFlowUsd": -751522.6102479994, + "mintCount": 1, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-10", + "token": "USDY", + "mintVolumeUsd": 6129101.978030998, + "redeemVolumeUsd": 4580005.068216732, + "netFlowUsd": 1549096.9098142665, + "mintCount": 32, + "redeemCount": 30, + "uniqueMinters": 11, + "uniqueRedeemers": 11, + "uniqueWallets": 14, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-17", + "token": "OUSG", + "mintVolumeUsd": 140000, + "redeemVolumeUsd": 8200000, + "netFlowUsd": -8060000, + "mintCount": 4, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-17", + "token": "USDY", + "mintVolumeUsd": 9107090.304182997, + "redeemVolumeUsd": 923702.643487668, + "netFlowUsd": 8183387.660695329, + "mintCount": 25, + "redeemCount": 17, + "uniqueMinters": 14, + "uniqueRedeemers": 13, + "uniqueWallets": 23, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-24", + "token": "OUSG", + "mintVolumeUsd": 280089.647411, + "redeemVolumeUsd": 280000, + "netFlowUsd": 89.64741099998355, + "mintCount": 1, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 1, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-24", + "token": "USDY", + "mintVolumeUsd": 3302633.080726, + "redeemVolumeUsd": 787336.779868051, + "netFlowUsd": 2515296.300857949, + "mintCount": 19, + "redeemCount": 7, + "uniqueMinters": 15, + "uniqueRedeemers": 7, + "uniqueWallets": 20, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-31", + "token": "OUSG", + "mintVolumeUsd": 16620474.069399, + "redeemVolumeUsd": 21054520.162660427, + "netFlowUsd": -4434046.093261428, + "mintCount": 7, + "redeemCount": 6, + "uniqueMinters": 5, + "uniqueRedeemers": 4, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-08-31", + "token": "USDY", + "mintVolumeUsd": 14552635.248162001, + "redeemVolumeUsd": 545570.4104926711, + "netFlowUsd": 14007064.83766933, + "mintCount": 21, + "redeemCount": 13, + "uniqueMinters": 8, + "uniqueRedeemers": 12, + "uniqueWallets": 17, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-07", + "token": "OUSG", + "mintVolumeUsd": 12837826.081753, + "redeemVolumeUsd": 6343667.890120046, + "netFlowUsd": 6494158.191632954, + "mintCount": 7, + "redeemCount": 6, + "uniqueMinters": 4, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-07", + "token": "USDY", + "mintVolumeUsd": 25401909.459074996, + "redeemVolumeUsd": 388196.08637193206, + "netFlowUsd": 25013713.372703064, + "mintCount": 24, + "redeemCount": 10, + "uniqueMinters": 14, + "uniqueRedeemers": 7, + "uniqueWallets": 19, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-14", + "token": "OUSG", + "mintVolumeUsd": 4234799.638591, + "redeemVolumeUsd": 16186829.073379036, + "netFlowUsd": -11952029.434788037, + "mintCount": 6, + "redeemCount": 5, + "uniqueMinters": 4, + "uniqueRedeemers": 3, + "uniqueWallets": 5, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-14", + "token": "USDY", + "mintVolumeUsd": 14752250.903463997, + "redeemVolumeUsd": 114600.9028181226, + "netFlowUsd": 14637650.000645874, + "mintCount": 35, + "redeemCount": 14, + "uniqueMinters": 16, + "uniqueRedeemers": 11, + "uniqueWallets": 21, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-21", + "token": "OUSG", + "mintVolumeUsd": 14315298.962612, + "redeemVolumeUsd": 19961321.157064162, + "netFlowUsd": -5646022.194452163, + "mintCount": 7, + "redeemCount": 5, + "uniqueMinters": 5, + "uniqueRedeemers": 2, + "uniqueWallets": 7, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-21", + "token": "USDY", + "mintVolumeUsd": 18829338.0343, + "redeemVolumeUsd": 1916396.713487735, + "netFlowUsd": 16912941.320812263, + "mintCount": 30, + "redeemCount": 29, + "uniqueMinters": 16, + "uniqueRedeemers": 13, + "uniqueWallets": 23, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-28", + "token": "OUSG", + "mintVolumeUsd": 8184318.7373939995, + "redeemVolumeUsd": 4503251.539699091, + "netFlowUsd": 3681067.197694909, + "mintCount": 2, + "redeemCount": 1, + "uniqueMinters": 1, + "uniqueRedeemers": 1, + "uniqueWallets": 2, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-09-28", + "token": "USDY", + "mintVolumeUsd": 20152190.142614003, + "redeemVolumeUsd": 1225453.4957475658, + "netFlowUsd": 18926736.646866437, + "mintCount": 23, + "redeemCount": 18, + "uniqueMinters": 11, + "uniqueRedeemers": 13, + "uniqueWallets": 21, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-10-05", + "token": "OUSG", + "mintVolumeUsd": 666502.5287169999, + "redeemVolumeUsd": 211500, + "netFlowUsd": 455002.5287169999, + "mintCount": 3, + "redeemCount": 3, + "uniqueMinters": 3, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-10-05", + "token": "USDY", + "mintVolumeUsd": 15528527.959846001, + "redeemVolumeUsd": 974790.3879092312, + "netFlowUsd": 14553737.57193677, + "mintCount": 44, + "redeemCount": 21, + "uniqueMinters": 14, + "uniqueRedeemers": 12, + "uniqueWallets": 21, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-10-12", + "token": "OUSG", + "mintVolumeUsd": 1097730.259438, + "redeemVolumeUsd": 640000, + "netFlowUsd": 457730.2594379999, + "mintCount": 1, + "redeemCount": 2, + "uniqueMinters": 1, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-10-12", + "token": "USDY", + "mintVolumeUsd": 34994508.547368996, + "redeemVolumeUsd": 2175986.5509188897, + "netFlowUsd": 32818521.996450108, + "mintCount": 26, + "redeemCount": 42, + "uniqueMinters": 16, + "uniqueRedeemers": 17, + "uniqueWallets": 25, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-10-19", + "token": "OUSG", + "mintVolumeUsd": 1142192.4476919998, + "redeemVolumeUsd": 2071917.0698350286, + "netFlowUsd": -929724.6221430288, + "mintCount": 2, + "redeemCount": 2, + "uniqueMinters": 2, + "uniqueRedeemers": 2, + "uniqueWallets": 3, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + }, + { + "week": "2026-10-19", + "token": "USDY", + "mintVolumeUsd": 17286826.641863003, + "redeemVolumeUsd": 2330442.7138407184, + "netFlowUsd": 14956383.928022284, + "mintCount": 31, + "redeemCount": 19, + "uniqueMinters": 13, + "uniqueRedeemers": 11, + "uniqueWallets": 19, + "dataSource": "live", + "asOf": "2026-10-26T01:28:54.610Z", + "source": "https://etherscan.io/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a#events" + } + ], + "competitorBenchmark": [ + { + "protocol": "nexus", + "protocolName": "Ondo (OUSG + USDY)", + "tvlUsd": 2692031229.435083, + "chainCount": 14, + "chainSource": "https://docs.ondo.finance/addresses", + "redemptionSpeed": "OUSG: instant (atomic, limits apply), else typically next business day", + "redemptionSource": "https://docs.ondo.finance/qualified-access-products/ousg/redeeming", + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.394Z", + "source": "https://docs.ondo.finance/addresses" + }, + { + "protocol": "superstate", + "protocolName": "Superstate (Invesco USTB)", + "tvlUsd": 549663644, + "chainCount": 3, + "chainSource": "https://docs.superstate.com/investors/tokenized-funds/available-funds/invesco-ustb", + "redemptionSpeed": "USDC immediate (subject to liquidity); USD same day if before 1pm ET", + "redemptionSource": "https://docs.superstate.com/investors/tokenized-funds/available-funds/invesco-ustb", + "dataSource": "cached", + "asOf": "2026-09-24", + "source": "https://defillama.com/protocol/invesco-ustb" + }, + { + "protocol": "openeden", + "protocolName": "OpenEden (TBILL)", + "tvlUsd": 245857859, + "chainCount": 3, + "chainSource": "https://docs.openeden.com/tbill/smart-contract-addresses", + "redemptionSpeed": "Typically next US business day (FIFO queue)", + "redemptionSource": "https://docs.openeden.com/tbill/redemptions", + "dataSource": "cached", + "asOf": "2026-09-24", + "source": "https://defillama.com/protocol/openeden-tbill" + }, + { + "protocol": "blackrock-buidl", + "protocolName": "BlackRock (BUIDL)", + "tvlUsd": 2165301032.01, + "chainCount": null, + "redemptionSpeed": "Not disclosed", + "dataSource": "live", + "asOf": "2026-10-26T01:28:30.000Z", + "source": "https://defillama.com/stablecoin/blackrock-usd-buidl" + } + ] +} diff --git a/src/lib/CLAUDE.md b/src/lib/CLAUDE.md index 3a3ea46..7853077 100644 --- a/src/lib/CLAUDE.md +++ b/src/lib/CLAUDE.md @@ -3,13 +3,12 @@ ## Types - All data shapes defined in `types.ts` — import from there, never inline types - Every data object extends `Sourced` (dataSource, asOf, source fields) -- `DataSource` is always one of: `'live' | 'estimated' | 'mocked'` +- `DataSource` is always one of: `'live' | 'estimated' | 'cached'` -## Mock Data -- All mocks in `mockData.ts` — never scatter mock values across components -- Every mocked value has `source` (citation string) and `asOf` (ISO date) -- Realistic ranges from Ondo public disclosures, not round numbers -- `MOCK_DATA` holds the labeled fallback each live getter returns when its source fails +## Last Good Values +- No mock data. A failed read falls back to `src/data/last-good.json` (via `lastGood.ts`), labeled `cached`, keeping the original `asOf` +- Only `scripts/snapshot-last-good.ts` writes that file, and only from rows that came back live. Never hand-edit a number into it +- A getter with no stored row returns no row; the page says the value is unavailable ## Flow Events - `flowEvents.ts` reads InstantManager logs from Blockscout's v2 address logs API (no key), decodes them with viem, and groups them by week diff --git a/src/lib/benchmark.check.ts b/src/lib/benchmark.check.ts index 2c3d80d..6dfd14f 100644 --- a/src/lib/benchmark.check.ts +++ b/src/lib/benchmark.check.ts @@ -1,19 +1,19 @@ /** Self-check for the benchmark row mapper. Run: npx tsx src/lib/benchmark.check.ts */ import assert from 'node:assert/strict'; import { DEFILLAMA_SLUGS, latestStablecoinUsd, mapBenchmarkRow, STABLECOIN_IDS, withOnchainOndo } from './benchmark'; -import { MOCK_DATA } from './mockData'; +import { LAST_GOOD } from './lastGood'; const asOf = '2026-09-24T12:00:00.000Z'; -const [ondo, ustb, tbill, buidl] = MOCK_DATA.competitorBenchmark; +const [ondo, ustb, tbill, buidl] = LAST_GOOD.competitorBenchmark; -// Mock rows: four products, BUIDL replaces BENJI, every row mocked with a DefiLlama citation. +// Stored rows: four products, BUIDL replaces BENJI, each with a DefiLlama citation. assert.deepEqual( - MOCK_DATA.competitorBenchmark.map((r) => r.protocol), + LAST_GOOD.competitorBenchmark.map((r) => r.protocol), ['nexus', 'superstate', 'openeden', 'blackrock-buidl'] ); -for (const r of MOCK_DATA.competitorBenchmark) { - assert.equal(r.dataSource, 'mocked'); - assert.ok(r.source.startsWith(`https://defillama.com/protocol/${DEFILLAMA_SLUGS[r.protocol]}`), r.source); +for (const r of LAST_GOOD.competitorBenchmark) { + // Ondo's stored row is the on-chain total (withOnchainOndo); the others cite DefiLlama. + if (r.protocol !== 'nexus') assert.ok(r.source.startsWith('https://defillama.com/'), r.source); assert.ok(r.tvlUsd > 0); // A redemption claim needs a citation; only "Not disclosed" may go without one. assert.ok(r.redemptionSpeed === 'Not disclosed' || r.redemptionSource, r.protocol); @@ -44,15 +44,19 @@ for (const bad of [0, -5, NaN, '123', null, { tvl: 1 }]) { } // Ondo row takes the on-chain total only when every chain row is live; competitors untouched. -const chain = MOCK_DATA.chainBreakdown.map((r) => ({ ...r, dataSource: 'live' as const, asOf })); +const chain = LAST_GOOD.chainTvl.map((r) => ({ ...r, dataSource: 'live' as const, asOf })); const chainTotal = chain.reduce((s, r) => s + r.tvlUsd, 0); const swapped = withOnchainOndo([rwa, live], chain); assert.equal(swapped[0].tvlUsd, chainTotal); assert.equal(swapped[0].dataSource, 'live'); assert.equal(swapped[0].chainCount, 14); assert.equal(swapped[1], live); -const partial = [{ ...chain[0], dataSource: 'mocked' as const }, ...chain.slice(1)]; -assert.equal(withOnchainOndo([rwa, live], partial)[0], rwa); +// Cached chain rows still give the on-chain total, labeled estimated with the cached date. +const partial = [{ ...chain[0], dataSource: 'cached' as const, asOf: '2026-01-01T00:00:00Z' }, ...chain.slice(1)]; +const mixedOndo = withOnchainOndo([rwa, live], partial)[0]; +assert.equal(mixedOndo.dataSource, 'estimated'); +assert.equal(mixedOndo.asOf, '2026-01-01T00:00:00Z'); +assert.equal(mixedOndo.tvlUsd, chainTotal); assert.equal(withOnchainOndo([rwa], [])[0], rwa); // Stablecoin series: last point wins; a bad shape throws so the caller falls back. diff --git a/src/lib/benchmark.ts b/src/lib/benchmark.ts index 22c1687..1ec8f90 100644 --- a/src/lib/benchmark.ts +++ b/src/lib/benchmark.ts @@ -7,12 +7,14 @@ * dashboard). The one free live source is the stablecoin endpoint, which lists BUIDL: * GET stablecoins.llama.fi/stablecoincharts/all?stablecoin=173, last point's total * circulating USD, cached 1h by the Next fetch cache. Ondo's row is replaced by the on-chain - * total (withOnchainOndo); USTB and TBILL keep their dated, labeled mock rows because no - * free live source exists. A failed BUIDL fetch also falls back to its mock row. + * total (withOnchainOndo); USTB and TBILL keep their last good rows (src/data/last-good.json, + * labeled cached with the date of the read) because no free live source exists. A failed BUIDL + * fetch also falls back to its last good row. * Chain count and redemption text always come from the static row, cited to issuer docs. */ -import { MOCK_DATA } from './mockData'; +import { combinedSource, cachedAsOf } from './dataSource'; +import { asCached, LAST_GOOD } from './lastGood'; import type { ChainTVL, CompetitorMetric, DataSource, Protocol } from './types'; export interface BenchmarkResult { @@ -72,34 +74,34 @@ async function fetchJson(url: string): Promise<{ body: unknown; date: string | n async function getRow(fallback: CompetitorMetric): Promise { const live = STABLECOIN_IDS[fallback.protocol]; - if (!live) return fallback; // no free live source: the labeled mock row stands + if (!live) return fallback; // no free live source: the last good row stands try { const res = await fetchJson(`https://stablecoins.llama.fi/stablecoincharts/all?stablecoin=${live.id}`); return mapBenchmarkRow(fallback, latestStablecoinUsd(res.body), new Date(res.date ?? Date.now()).toISOString(), live.page); } catch (err) { console.warn( - `[benchmark] using mock row for ${fallback.protocol}: ${err instanceof Error ? err.message : String(err)}` + `[benchmark] using last good row for ${fallback.protocol}: ${err instanceof Error ? err.message : String(err)}` ); return fallback; } } export async function getBenchmark(): Promise { - const rows = await Promise.all(MOCK_DATA.competitorBenchmark.map(getRow)); - return { rows, dataSource: rows.every((r) => r.dataSource === 'live') ? 'live' : 'mocked' }; + const rows = await Promise.all(asCached(LAST_GOOD.competitorBenchmark).map(getRow)); + return { rows, dataSource: combinedSource(rows) }; } /** - * Ondo row TVL = the on-chain total from getChainTVL, when every chain row is live, so the - * benchmark matches the Total TVL KPI. Otherwise the DefiLlama (or mock) row stays. + * Ondo row TVL = the on-chain total from getChainTVL, so the benchmark matches the Total TVL KPI. + * The row is live when every chain row is live; otherwise it carries the combined label and the + * oldest cached date. With no chain rows the DefiLlama (or last good) row stays. */ export function withOnchainOndo(rows: CompetitorMetric[], chainRows: ChainTVL[]): CompetitorMetric[] { - if (chainRows.length === 0 || !chainRows.every((r) => r.dataSource === 'live')) return rows; + if (chainRows.length === 0) return rows; const tvlUsd = chainRows.reduce((sum, r) => sum + r.tvlUsd, 0); - const asOf = chainRows.reduce((max, r) => (r.asOf > max ? r.asOf : max), ''); + const dataSource = combinedSource(chainRows); + const asOf = cachedAsOf(chainRows) ?? chainRows.reduce((max, r) => (r.asOf > max ? r.asOf : max), ''); return rows.map((r) => - r.protocol === 'nexus' - ? { ...r, tvlUsd, dataSource: 'live', asOf, source: 'https://docs.ondo.finance/addresses' } - : r + r.protocol === 'nexus' ? { ...r, tvlUsd, dataSource, asOf, source: 'https://docs.ondo.finance/addresses' } : r ); } diff --git a/src/lib/chainSupply.check.ts b/src/lib/chainSupply.check.ts index bc2fd99..b344f62 100644 --- a/src/lib/chainSupply.check.ts +++ b/src/lib/chainSupply.check.ts @@ -1,7 +1,7 @@ /** Self-check for chain supply parsing and fallback. No network. Run: npx tsx src/lib/chainSupply.check.ts */ import assert from 'node:assert/strict'; import { buildRows, fromBaseUnits, stellarSupply, SUPPLY_SOURCES, toLiquidityCells, xrplSupply } from './chainSupply'; -import { MOCK_DATA } from './mockData'; +import { LAST_GOOD } from './lastGood'; const close = (a: number, b: number, eps = 1e-6) => assert.ok(Math.abs(a - b) < eps, `${a} != ${b}`); @@ -32,12 +32,13 @@ close(xrplSupply({ status: 'success', obligations: { [CUR]: '1639899.441472341' assert.equal(xrplSupply({ status: 'success' }, CUR), 0); assert.throws(() => xrplSupply({ status: 'error' }, CUR), /xrpl status/); -// Every source has a mock fallback row, and 13 chains are covered. -const mock = MOCK_DATA.chainBreakdown; +// Every source has a last good row, and 13 chains are covered. +const mock = LAST_GOOD.chainTvl; assert.equal(mock.length, SUPPLY_SOURCES.length); assert.equal(new Set(SUPPLY_SOURCES.map((s) => s.chain)).size, 13); +for (const s of SUPPLY_SOURCES) assert.ok(mock.some((m) => m.chain === s.chain && m.token === s.token), `${s.token} ${s.chain}`); const mockTotal = mock.reduce((s, r) => s + r.tvlUsd, 0); -assert.ok(mockTotal > 2.5e9 && mockTotal < 2.7e9, `mock total ${mockTotal}`); +assert.ok(mockTotal > 1e9 && mockTotal < 1e10, `last good total ${mockTotal}`); const prices = { OUSG: 116.662365, USDY: 1.14730001, asOf: '2026-09-24T00:00:00Z', source: 'oracle' }; const asOf = '2026-09-24T12:00:00Z'; @@ -50,26 +51,31 @@ assert.ok(live.every((r) => r.dataSource === 'live' && r.asOf === asOf)); close(live[0].tvlUsd, 1000 * prices[SUPPLY_SOURCES[0].token]); close(sumPct(live), 100); -// One failed read: that row is its mock value labeled mocked, the rest stay live. +// One failed read: that row is its last good value labeled cached, with its own date; the rest stay live. const oneFail = allOk.map((r, i): PromiseSettledResult => (i === 5 ? { status: 'rejected', reason: new Error('timeout') } : r)); const warn = console.warn; console.warn = () => {}; const mixed = buildRows(SUPPLY_SOURCES, oneFail, prices, mock, asOf); console.warn = warn; -assert.equal(mixed[5].dataSource, 'mocked'); +assert.equal(mixed[5].dataSource, 'cached'); +assert.equal(mixed[5].asOf, mock.find((m) => m.chain === SUPPLY_SOURCES[5].chain && m.token === SUPPLY_SOURCES[5].token)!.asOf); assert.equal(mixed[5].tvlUsd, mock.find((m) => m.chain === SUPPLY_SOURCES[5].chain && m.token === SUPPLY_SOURCES[5].token)!.tvlUsd); assert.equal(mixed.filter((r) => r.dataSource === 'live').length, SUPPLY_SOURCES.length - 1); close(sumPct(mixed), 100); -// Failed price: every row mocked. +// Failed price: every row cached. const noPrice = buildRows(SUPPLY_SOURCES, allOk, null, mock, asOf); -assert.ok(noPrice.every((r) => r.dataSource === 'mocked')); +assert.ok(noPrice.every((r) => r.dataSource === 'cached')); close(sumPct(noPrice), 100); // Heatmap cells mirror the rows, issuer = token. const cells = toLiquidityCells(mixed); assert.equal(cells.length, mixed.length); assert.equal(cells[1].issuer, mixed[1].token); -assert.equal(cells[5].dataSource, 'mocked'); +assert.equal(cells[5].dataSource, 'cached'); -console.log(`chainSupply check ok (mock total $${(mockTotal / 1e9).toFixed(3)}B, ${SUPPLY_SOURCES.length} rows)`); +// No last good row and a failed read: the row is left out, never invented. +const gap = buildRows(SUPPLY_SOURCES, oneFail, prices, mock.filter((m) => !(m.chain === SUPPLY_SOURCES[5].chain && m.token === SUPPLY_SOURCES[5].token)), asOf); +assert.equal(gap.length, SUPPLY_SOURCES.length - 1); + +console.log(`chainSupply check ok (last good total $${(mockTotal / 1e9).toFixed(3)}B, ${SUPPLY_SOURCES.length} rows)`); diff --git a/src/lib/chainSupply.ts b/src/lib/chainSupply.ts index c204bf5..9fafa5e 100644 --- a/src/lib/chainSupply.ts +++ b/src/lib/chainSupply.ts @@ -5,12 +5,13 @@ * Bridges are LayerZero OFT burn/mint, so each chain's supply is its own float: * no lockbox subtraction. rUSDY, mUSD and rOUSG wrap tokens already counted here * and are left out. All reads run in parallel with an 8s timeout and a 1hr - * in-memory cache. A failed read falls back to that row's mock value; a failed - * price read falls back on every row. + * in-memory cache. A failed read falls back to that row's last good value (src/data/last-good.json, + * labeled cached, with its original date); a failed price read falls back on every row. A row + * with no last good value is left out, never invented. */ import { createPublicClient, formatUnits, http, parseAbi } from 'viem'; -import { MOCK_DATA } from './mockData'; +import { asCached, LAST_GOOD } from './lastGood'; import { ETHEREUM_RPC, getTokenPrices, OUSG_ETHEREUM, USDY_ETHEREUM, type TokenPrices } from './prices'; import type { Chain, ChainTVL, LiquidityCell } from './types'; @@ -193,22 +194,23 @@ function withPct(rows: ChainTVL[]): ChainTVL[] { } /** - * Build rows from settled supply reads. A rejected read (or null prices) uses - * the matching mock row, labeled 'mocked'. Pure: covered by chainSupply.check.ts. + * Build rows from settled supply reads. A rejected read (or null prices) uses the matching + * last good row, labeled 'cached' with the date of its original read. A source with no last + * good row is left out. Pure: covered by chainSupply.check.ts. */ export function buildRows( sources: SupplySource[], supplies: PromiseSettledResult[], prices: TokenPrices | null, - mock: ChainTVL[], + lastGood: ChainTVL[], asOf: string ): ChainTVL[] { - const mockByKey = new Map(mock.map((m) => [key(m.token, m.chain), m])); - return withPct( - sources.map((s, i) => { - const got = supplies[i]; - if (prices && got?.status === 'fulfilled') { - return { + const goodByKey = new Map(asCached(lastGood).map((m) => [key(m.token, m.chain), m])); + const rows = sources.flatMap((s, i): ChainTVL[] => { + const got = supplies[i]; + if (prices && got?.status === 'fulfilled') { + return [ + { chain: s.chain, token: s.token, supply: got.value, @@ -217,14 +219,14 @@ export function buildRows( dataSource: 'live', asOf, source: s.source, - }; - } - const m = mockByKey.get(key(s.token, s.chain)); - if (!m) throw new Error(`no mock row for ${key(s.token, s.chain)}`); - if (prices) console.warn(`[chainSupply] ${key(s.token, s.chain)} read failed, using mock:`, got?.status === 'rejected' ? String(got.reason) : 'missing'); - return { ...m, dataSource: 'mocked' }; - }) - ); + }, + ]; + } + if (prices) console.warn(`[chainSupply] ${key(s.token, s.chain)} read failed, using last good value:`, got?.status === 'rejected' ? String(got.reason) : 'missing'); + const good = goodByKey.get(key(s.token, s.chain)); + return good ? [good] : []; + }); + return withPct(rows); } /** Heatmap cells from the same rows (issuer = token). */ @@ -248,10 +250,10 @@ export async function getChainTVL(): Promise { ...SUPPLY_SOURCES.map((s) => readSupply(s.reader)), ]); const prices = priceResult.status === 'fulfilled' ? (priceResult.value as TokenPrices) : null; - if (!prices) console.warn('[chainSupply] price read failed, using mock for every row:', String((priceResult as PromiseRejectedResult).reason)); - const data = buildRows(SUPPLY_SOURCES, supplies as PromiseSettledResult[], prices, MOCK_DATA.chainBreakdown, new Date().toISOString()); + if (!prices) console.warn('[chainSupply] price read failed, using last good value for every row:', String((priceResult as PromiseRejectedResult).reason)); + const data = buildRows(SUPPLY_SOURCES, supplies as PromiseSettledResult[], prices, LAST_GOOD.chainTvl, new Date().toISOString()); // Cache only a fully live read: a read with any fallback retries on the next render - // (the next ISR revalidation of /) instead of pinning mock rows for an hour. + // (the next ISR revalidation of /) instead of pinning cached rows for an hour. if (data.every((r) => r.dataSource === 'live')) cached = { data, timestamp: Date.now() }; return data; } diff --git a/src/lib/dataSource.ts b/src/lib/dataSource.ts index adc94aa..f29692b 100644 --- a/src/lib/dataSource.ts +++ b/src/lib/dataSource.ts @@ -1,8 +1,15 @@ import type { DataSource, Sourced } from './types'; -/** One badge for many rows: live if all live, mocked if all mocked, else estimated. */ +/** One badge for many rows: live if all live, cached if all cached, else estimated. */ export function combinedSource(rows: Sourced[]): DataSource { if (rows.length > 0 && rows.every((r) => r.dataSource === 'live')) return 'live'; - if (rows.every((r) => r.dataSource === 'mocked')) return 'mocked'; + if (rows.every((r) => r.dataSource === 'cached')) return 'cached'; return 'estimated'; } + +/** Oldest asOf among the cached rows, or undefined when none is cached. */ +export function cachedAsOf(rows: Sourced[]): string | undefined { + return rows + .filter((r) => r.dataSource === 'cached') + .reduce((min, r) => (min === undefined || r.asOf < min ? r.asOf : min), undefined); +} diff --git a/src/lib/format.ts b/src/lib/format.ts index d062b67..76cde33 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -30,6 +30,11 @@ export function formatUsdCompact(value: number): string { return USD_COMPACT.format(value); } +/** Trillions to two decimals, for headline totals where "$1T" would hide the CCTP gap. */ +export function formatTrillions(value: number): string { + return `$${(value / 1e12).toFixed(2)}T`; +} + export function formatUsd(value: number): string { return USD_FULL.format(value); } diff --git a/src/lib/issuance.check.ts b/src/lib/issuance.check.ts index 7bcbbd0..b283fd5 100644 --- a/src/lib/issuance.check.ts +++ b/src/lib/issuance.check.ts @@ -5,7 +5,7 @@ import assert from 'node:assert/strict'; import issuance from '../data/issuance.json'; import supply from '../data/supply-by-chain.json'; -import { episodeResults, episodes, grossIssuerVolume, supplyTotalAt, totals, zDuring } from './issuance'; +import { episodeResults, episodes, grossIssuerVolume, issuanceSummary, supplyTotalAt, totals, zDuring } from './issuance'; const B = 1e9; const t = new Map(totals().map((x) => [x.asset.id, x])); @@ -40,6 +40,11 @@ assert.ok(supplyTotalAt('USDC', '2026-10-12') > 70 * B && supplyTotalAt('USDC', // 5. Headline is above $0.9T and below $1.1T. assert.ok(grossIssuerVolume() > 0.9e12 && grossIssuerVolume() < 1.1e12, `gross ${grossIssuerVolume()}`); +// 5b. The CCTP caveat: counting CCTP transfers would push the same data to about $1.2T. +const withCctp = issuanceSummary().grossWithCctpUsd; +assert.ok(withCctp > 1.15e12 && withCctp < 1.25e12, `with CCTP ${withCctp}`); +assert.ok(Math.abs(withCctp - grossIssuerVolume() - issuanceSummary().cctpBridgedUsd) < 1); + // 6. Claims in app/summary/page.tsx. assert.equal(episodes().length, 2); const r = episodeResults(); diff --git a/src/lib/issuance.ts b/src/lib/issuance.ts index a781aba..4ac029a 100644 --- a/src/lib/issuance.ts +++ b/src/lib/issuance.ts @@ -341,6 +341,8 @@ export function issuanceSummary() { startWeek: SNAPSHOT.flowsStartWeek, lastWeek: LAST_COMPLETE_WEEK, grossUsd: gross, + /** Gross if Circle CCTP transfers were counted as issuance: the exclusion is what separates $1.2T from about $1T */ + grossWithCctpUsd: gross + usdc.bridged, usdcShareOfGross: usdc.gross / gross, cctpBridgedUsd: usdc.bridged, totals: t.map((x) => ({ asset: x.asset.id, issuer: x.asset.issuer, mintUsd: x.mint, redeemUsd: x.redeem, netUsd: x.net, firstWeek: x.firstWeek })), diff --git a/src/lib/lastGood.ts b/src/lib/lastGood.ts new file mode 100644 index 0000000..aadd508 --- /dev/null +++ b/src/lib/lastGood.ts @@ -0,0 +1,27 @@ +/** + * Last good values: the fallback when a live source fails. + * + * src/data/last-good.json holds real reads, written by scripts/snapshot-last-good.ts from the + * same getters the pages use. A getter that falls back returns these rows labeled 'cached', + * keeping the asOf of the original read. Nothing here is invented: when the file has no row, + * the getter returns no row and the page says the value is unavailable. + */ + +import snapshot from '@/data/last-good.json'; +import type { ChainTVL, CompetitorMetric, HolderMetric, WeeklyFlow } from './types'; + +export interface LastGood { + /** ISO time the file was written; null in a file that has never been refreshed */ + generatedAt: string | null; + chainTvl: ChainTVL[]; + topHolders: HolderMetric[]; + weeklyFlows: WeeklyFlow[]; + competitorBenchmark: CompetitorMetric[]; +} + +/** Re-label rows as cached. Their asOf and source stay those of the real read. */ +export function asCached(rows: T[]): (T & { dataSource: 'cached' })[] { + return rows.map((r) => ({ ...r, dataSource: 'cached' as const })); +} + +export const LAST_GOOD = snapshot as unknown as LastGood; diff --git a/src/lib/mockData.ts b/src/lib/mockData.ts deleted file mode 100644 index 06917c3..0000000 --- a/src/lib/mockData.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Typed mock data for all 4 dashboard pillars: the labeled fallback each live getter - * returns when its source fails (ADR-002). - * - * Every value has a source citation and asOf date. Weekly flows are illustrative; - * holders, chain supply and benchmark rows are dated snapshots of the live sources. - * - * See docs/METHODOLOGY.md for the data source taxonomy. - */ - -import type { - HolderMetric, - WeeklyFlow, - ChainTVL, - CompetitorMetric, - Chain, -} from './types'; -import { holderName } from './addressRegistry'; - -const MOCK_AS_OF = '2026-04-09'; - -// --- Pillar 1: Top Ethereum holders --- - -// Fallback snapshot when Blockscout or the price oracle is unavailable. Balances are the top 15 -// holders per token from eth.blockscout.com/api/v2/tokens//holders on 2026-09-24 -// (with the one Blockscout public name tag among them), valued at the Ondo oracle price -// read the same day (OUSG $116.662365, USDY $1.14730001). -const HOLDERS_AS_OF = '2026-09-24'; -const HOLDERS_SOURCE = - 'Blockscout top holders snapshot 2026-09-24, valued at OndoOracle.getAssetPrice (0x9Cad45a8BF0Ed41Ff33074449B357C7a1fAb4094)'; -const HOLDERS_PRICE = { OUSG: 116.662365, USDY: 1.14730001 } as const; -const HOLDERS_SNAPSHOT: Array<['OUSG' | 'USDY', string, number, string?]> = [ - ['OUSG', '0x1dD7950c266fB1be96180a8FDb0591F70200E018', 336339.56], - ['OUSG', '0x5D87Fa995c54ffF52cfE1C18d8EbF79f0eEb3AeB', 261208.83], - ['OUSG', '0x56e60979d5934a05D22606B0455d52F59F20A6A0', 187133.34], - ['OUSG', '0xbd9676EA1D6cAD553E87EC456869633C669ffB03', 75241.51], - ['OUSG', '0x5eD4EBAF21f83959f81b7e7545e25D313C84081f', 61295.76], - ['OUSG', '0x54752f87a3f8b6c594C9AFb110b2c491614de204', 38593.42], - ['OUSG', '0x4307b4C9D5A48AC7CB112eF6af7fbEFf5EFA6AF7', 33150.53], - ['OUSG', '0x0f365d86809A00761a8DF914c812dbFDF481C149', 23923.62], - ['OUSG', '0x3Ee60C57d70a6EC4877Dc24AA7d0e6FaC61cd11a', 17178.79], - ['OUSG', '0x233F8aBDcC60088634382ef12C78bdeBf69C9470', 9950.13], - ['OUSG', '0x609517eACD9Ec2E24DfFD4Eeca54C6fd88C866FE', 9147.24], - ['OUSG', '0x64beF4478942d8FD62bE281707076442aa2D055E', 8693.54], - ['OUSG', '0x02f8319261c904cB8C07E5DE7F19508354705669', 5605.26], - ['OUSG', '0x9Ff88e2c4844A47a70233BA676c3928b05d255Cf', 4922.02], - ['OUSG', '0xaDf5e32eB413e62DC072Aa5fd12F19a115aC3c12', 4062.49], - ['USDY', '0xA5b614026dCB1ef6e0E39AA53351b4F4bd225302', 359037168.47], - ['USDY', '0x661b5e00424B56Ba6e369e0c2f21E31B6FB8ee1a', 298780676.98], - ['USDY', '0x9C6Fa3b81cE92B7D82980D68CCCE9D6e48fe4AC2', 106360871.12], - ['USDY', '0xC392749B6ff2cd95e5a4e3Ed396c93f813395041', 83405034.64], - ['USDY', '0xC882b111A75C0c657fC507C04FbFcD2cC984F071', 47579905.14, 'Gate.io: Deposit Address'], - ['USDY', '0xEeB066aDa2D5C5eD9E4D9ee042bdEED430ae8512', 28843984.01], - ['USDY', '0xC9E397454f5478f4418e45181849C6eA66303D5A', 17230923.92], - ['USDY', '0x13134B8d770907eCb263cB88a67F9AF833007aFc', 16739969.14], - ['USDY', '0x6870289efC708e41A97AD5068A9F84b0DD2bfAED', 15867912.96], - ['USDY', '0xc0db94fDDE74f3902EA7995194d290F6BEE78f8C', 10280817.53], - ['USDY', '0xD56a32E078be332cDE149c29949a1D4d0C1c9F0E', 7500000.00], - ['USDY', '0x6d542B698541fFd216c45AcDC1C6C1CD233dFf94', 4403415.43], - ['USDY', '0xd97eCe4a24C4538d96E14296c5544c871caE2eEB', 3067064.04], - ['USDY', '0xEC33298A3aC7A5F5B3f21A3218a5D8E862Da8a12', 2683281.53], - ['USDY', '0xaf37c1167910ebC994e266949387d2c7C326b879', 2294280.89], -]; - -const topHolders: HolderMetric[] = HOLDERS_SNAPSHOT.map(([token, address, balance, tag]) => ({ - name: holderName(address, tag ?? null), - address, - token, - balance, - tvlUsd: balance * HOLDERS_PRICE[token], - dataSource: 'mocked', - asOf: HOLDERS_AS_OF, - source: HOLDERS_SOURCE, -})); - -// --- Pillar 2: Mint/Redemption Volume & Frequency --- - -/** Tiny seeded PRNG (mulberry32) so weekly mocks are identical across renders and servers. */ -function seededRandom(seed: number): () => number { - let a = seed; - return () => { - a = (a + 0x6d2b79f5) | 0; - let t = Math.imul(a ^ (a >>> 15), 1 | a); - t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; - return ((t ^ (t >>> 14)) >>> 0) / 4294967296; - }; -} - -const WEEKLY_FLOW_SOURCE = - 'Illustrative mock shaped from Ondo public disclosures, not on-chain data'; - -function generateWeeklyFlows(): WeeklyFlow[] { - const rand = seededRandom(20260409); - const data: WeeklyFlow[] = []; - const WEEKS = 78; - // Monday (UTC) of the MOCK_AS_OF week; the last bucket is a partial week, like live data. - const lastMonday = new Date(`${MOCK_AS_OF}T00:00:00Z`); - lastMonday.setUTCDate(lastMonday.getUTCDate() - ((lastMonday.getUTCDay() + 6) % 7)); - - const profiles = [ - { token: 'OUSG' as const, mintBase: 14_000_000, redeemBase: 11_000_000, wallets: 14 }, - { token: 'USDY' as const, mintBase: 3_200_000, redeemBase: 2_600_000, wallets: 22 }, - ]; - - for (let i = WEEKS - 1; i >= 0; i--) { - const monday = new Date(lastMonday); - monday.setUTCDate(monday.getUTCDate() - i * 7); - const week = monday.toISOString().split('T')[0]; - const trendFactor = 1 + (WEEKS - 1 - i) * 0.01; - // The newest bucket is the partial current week (Mon-Thu of a 7-day week). - const partial = i === 0 ? 0.5 : 1; - - for (const p of profiles) { - // Occasional lumpy weeks: one large subscriber or redeemer dominates. - const mintSpike = rand() < 0.12 ? 1.4 + rand() * 0.8 : 1; - const redeemSpike = rand() < 0.1 ? 1.5 + rand() * 0.9 : 1; - const mintVolumeUsd = Math.round(p.mintBase * trendFactor * partial * mintSpike * (0.35 + rand() * 0.9)); - const redeemVolumeUsd = Math.round(p.redeemBase * trendFactor * partial * redeemSpike * (0.3 + rand() * 0.95)); - const mintCount = Math.max(1, Math.round(p.wallets * 1.6 * trendFactor * partial * (0.5 + rand() * 0.8))); - const redeemCount = Math.max(1, Math.round(p.wallets * 1.2 * trendFactor * partial * (0.4 + rand() * 0.8))); - const uniqueMinters = Math.max(1, Math.round(mintCount * (0.55 + rand() * 0.3))); - const uniqueRedeemers = Math.max(1, Math.round(redeemCount * (0.55 + rand() * 0.3))); - // Some wallets both mint and redeem in the same week. - const overlap = Math.round(Math.min(uniqueMinters, uniqueRedeemers) * rand() * 0.35); - - data.push({ - week, - token: p.token, - mintVolumeUsd, - redeemVolumeUsd, - netFlowUsd: mintVolumeUsd - redeemVolumeUsd, - mintCount, - redeemCount, - uniqueMinters, - uniqueRedeemers, - uniqueWallets: uniqueMinters + uniqueRedeemers - overlap, - dataSource: 'mocked', - asOf: MOCK_AS_OF, - source: WEEKLY_FLOW_SOURCE, - }); - } - } - - return data; -} - -// --- Pillar 3: TVL per chain (supply snapshot) --- - -// Fallback for src/lib/chainSupply.ts: per-chain supply read 2026-09-24 × OndoOracle prices that day. -const SUPPLY_AS_OF = '2026-09-24'; -const SUPPLY_SOURCE = 'on-chain supply snapshot 2026-09-24'; -const SNAPSHOT_PRICES = { OUSG: 116.662365, USDY: 1.14730001 }; - -const SUPPLY_SNAPSHOT: [ChainTVL['token'], Chain, number][] = [ - ['USDY', 'ethereum', 1_038_672_623.98], - ['OUSG', 'ethereum', 1_090_030.63], - ['USDY', 'mantle', 319_917.71], - ['USDY', 'arbitrum', 2_728_685.9], - ['OUSG', 'polygon', 0], - ['USDY', 'solana', 157_215_505.1], - ['OUSG', 'solana', 0], - ['USDY', 'sui', 12_773_284.72], - ['USDY', 'aptos', 1_848_234.81], - ['USDY', 'noble', 5_612_589.04], - ['USDY', 'stellar', 467_502_151.7], - ['USDY', 'plume', 0], - ['USDY', 'sei', 225_844_520.32], - ['USDY', 'bnb', 68_682_990.33], - ['OUSG', 'xrp-ledger', 1_639_899.44], -]; - -const snapshotTotal = SUPPLY_SNAPSHOT.reduce((s, [token, , supply]) => s + supply * SNAPSHOT_PRICES[token], 0); - -const chainBreakdown: ChainTVL[] = SUPPLY_SNAPSHOT.map(([token, chain, supply]) => { - const tvlUsd = supply * SNAPSHOT_PRICES[token]; - return { - chain, - token, - supply, - tvlUsd, - pctOfTotal: (tvlUsd / snapshotTotal) * 100, - dataSource: 'mocked', - asOf: SUPPLY_AS_OF, - source: SUPPLY_SOURCE, - }; -}); - -// --- Pillar 4: Competitive Benchmark --- - -// Fallback rows for src/lib/benchmark.ts. TVL is DefiLlama as fetched on 2026-09-24. -// Chain counts and redemption text are static facts quoted from each issuer's own docs; -// the live path reuses them. BUIDL has neither: no issuer page could be fetched to cite. -const BENCHMARK_AS_OF = '2026-09-24'; - -const competitorBenchmark: CompetitorMetric[] = [ - { - protocol: 'nexus', - protocolName: 'Ondo (OUSG + USDY)', - tvlUsd: 2_560_085_819, - chainCount: 14, - chainSource: 'https://docs.ondo.finance/addresses', - redemptionSpeed: 'OUSG: instant (atomic, limits apply), else typically next business day', - redemptionSource: 'https://docs.ondo.finance/qualified-access-products/ousg/redeeming', - dataSource: 'mocked', - asOf: BENCHMARK_AS_OF, - source: 'https://defillama.com/protocol/ondo-yield-assets', - }, - { - protocol: 'superstate', - protocolName: 'Superstate (Invesco USTB)', - tvlUsd: 549_663_644, - chainCount: 3, // "Ethereum, Solana, and Plume" - chainSource: 'https://docs.superstate.com/investors/tokenized-funds/available-funds/invesco-ustb', - redemptionSpeed: 'USDC immediate (subject to liquidity); USD same day if before 1pm ET', - redemptionSource: 'https://docs.superstate.com/investors/tokenized-funds/available-funds/invesco-ustb', - dataSource: 'mocked', - asOf: BENCHMARK_AS_OF, - source: 'https://defillama.com/protocol/invesco-ustb', - }, - { - protocol: 'openeden', - protocolName: 'OpenEden (TBILL)', - tvlUsd: 245_857_859, - chainCount: 3, - chainSource: 'https://docs.openeden.com/tbill/smart-contract-addresses', - redemptionSpeed: 'Typically next US business day (FIFO queue)', - redemptionSource: 'https://docs.openeden.com/tbill/redemptions', - dataSource: 'mocked', - asOf: BENCHMARK_AS_OF, - source: 'https://defillama.com/protocol/openeden-tbill', - }, - { - protocol: 'blackrock-buidl', - protocolName: 'BlackRock (BUIDL)', - tvlUsd: 3_514_788_159, - chainCount: null, - redemptionSpeed: 'Not disclosed', - dataSource: 'mocked', - asOf: BENCHMARK_AS_OF, - source: 'https://defillama.com/protocol/blackrock-buidl', - }, -]; - -// --- Export --- - -/** Labeled fallbacks for the live getters (weeklyFlows, chainSupply, topHolders, benchmark). */ -export const MOCK_DATA = { - topHolders, - weeklyFlows: generateWeeklyFlows(), - chainBreakdown, - competitorBenchmark, -}; diff --git a/src/lib/topHolders.check.ts b/src/lib/topHolders.check.ts index e679b47..ee06d2a 100644 --- a/src/lib/topHolders.check.ts +++ b/src/lib/topHolders.check.ts @@ -1,7 +1,7 @@ /** Self-check for the Blockscout holder mapper. Run: npx tsx src/lib/topHolders.check.ts */ import assert from 'node:assert/strict'; -import { holdersPageUrl, mapHolderItem, publicTag } from './topHolders'; -import { MOCK_DATA } from './mockData'; +import { assembleHolders, holdersPageUrl, mapHolderItem, mapStellarHolder, publicTag, stellarHoldersPageUrl } from './topHolders'; +import { LAST_GOOD } from './lastGood'; const asOf = '2026-09-24T00:00:00.000Z'; const item = (hash: string, value: unknown, tags: unknown[] = [], name: string | null = null) => ({ @@ -73,18 +73,55 @@ assert.throws(() => mapHolderItem(item(ok, '0'), 'OUSG', 1, asOf), /non-positive assert.throws(() => mapHolderItem(item(ok, '1'), 'OUSG', 0, asOf), /non-positive/); assert.throws(() => mapHolderItem(item(ok, '1'), 'OUSG', NaN, asOf), /non-positive/); -// Mock snapshot: 15 per token, checksummed, sorted by balance, no placeholder addresses. -const mock = MOCK_DATA.topHolders; -for (const token of ['OUSG', 'USDY'] as const) { - const rows = mock.filter((r) => r.token === token); - assert.equal(rows.length, 15, `${token} mock rows`); - rows.forEach((r, i) => i && assert.ok(r.balance <= rows[i - 1].balance, `${token} not sorted`)); +// Stellar record: balance is 1e7 stroops, valued at the given price, cited to stellar.expert. +const G = 'GC635K2GGDMIINZBO6WWLQN6QUVDEBODEYCQ74OLEZUOA37K5E2BXSHC'; +const xlm = mapStellarHolder({ address: G, balance: '3532171820000000' }, 1.15, asOf); +assert.equal(xlm.chain, 'stellar'); +assert.equal(xlm.token, 'USDY'); +assert.equal(xlm.balance, 353217182); +assert.ok(Math.abs(xlm.tvlUsd - 353217182 * 1.15) < 1e-3); +assert.equal(xlm.dataSource, 'live'); +assert.equal(xlm.source, stellarHoldersPageUrl); +assert.equal(xlm.name, 'Unknown wallet (GC635K...XSHC)'); +assert.throws(() => mapStellarHolder({ address: '0xabc', balance: '1' }, 1, asOf), /bad stellar address/); +assert.throws(() => mapStellarHolder({ address: G, balance: '1.5' }, 1, asOf), /bad balance/); +assert.throws(() => mapStellarHolder({ address: G, balance: '0' }, 1, asOf), /non-positive/); +assert.throws(() => mapStellarHolder({ address: G, balance: '10' }, 0, asOf), /non-positive/); + +// Last good snapshot: Ethereum 15 per token plus Stellar USDY 15, sorted, real address shapes, no placeholders. +const stored = LAST_GOOD.topHolders; +const GROUPS = [ + { chain: 'ethereum' as const, token: 'OUSG' as const }, + { chain: 'ethereum' as const, token: 'USDY' as const }, + { chain: 'stellar' as const, token: 'USDY' as const }, +]; +for (const g of GROUPS) { + const rows = stored.filter((r) => r.chain === g.chain && r.token === g.token); + assert.equal(rows.length, 15, `${g.chain} ${g.token} stored rows`); + rows.forEach((r, i) => i && assert.ok(r.balance <= rows[i - 1].balance, `${g.chain} ${g.token} not sorted`)); } -for (const r of mock) { - assert.equal(r.dataSource, 'mocked'); - assert.match(r.address, /^0x[0-9a-fA-F]{40}$/); +for (const r of stored) { + assert.match(r.address, r.chain === 'stellar' ? /^[GC][A-Z2-7]{55}$/ : /^0x[0-9a-fA-F]{40}$/); assert.ok(!/^0x1234|^0xabcdef/i.test(r.address), `placeholder address ${r.address}`); + assert.ok(r.asOf > '2026-10', `${r.address} has no real read date`); } -assert.equal(mock.find((r) => r.address.startsWith('0xC882'))?.name, 'Gate.io: Deposit Address'); -console.log(`topHolders check ok (${mock.length} mock rows)`); +// Assembly: a fulfilled group is live; a rejected group falls back to its own last good rows, +// labeled cached with their original date; a group with no last good rows is left out. +const warn = console.warn; +console.warn = () => {}; +const liveEth = stored.filter((r) => r.chain === 'ethereum'); +const reads: PromiseSettledResult[] = [ + { status: 'fulfilled', value: liveEth.filter((r) => r.token === 'OUSG') }, + { status: 'fulfilled', value: liveEth.filter((r) => r.token === 'USDY') }, + { status: 'rejected', reason: new Error('stellar.expert down') }, +]; +const mixed = assembleHolders(GROUPS, reads, stored); +assert.equal(mixed.length, 45); +const fell = mixed.filter((r) => r.chain === 'stellar'); +assert.equal(fell.length, 15); +assert.ok(fell.every((r, i) => r.dataSource === 'cached' && r.asOf === stored.filter((s) => s.chain === 'stellar')[i].asOf)); +assert.equal(assembleHolders(GROUPS, reads, liveEth).length, 30); +console.warn = warn; + +console.log(`topHolders check ok (${stored.length} stored rows)`); diff --git a/src/lib/topHolders.ts b/src/lib/topHolders.ts index 19754f3..2706d4b 100644 --- a/src/lib/topHolders.ts +++ b/src/lib/topHolders.ts @@ -1,16 +1,20 @@ /** - * Top Ethereum holders (Pillar 1) of OUSG and USDY. + * Top holders (Pillar 1) of OUSG and USDY on Ethereum, and of USDY on Stellar. * - * Live path: Blockscout's token holders API (no key), top 15 per token, valued at the - * Ondo oracle price (getTokenPrices). Any HTTP error, malformed body, or price error - * falls back to the labeled mock snapshot and logs why. + * Ethereum: Blockscout's token holders API (no key), top 15 per token. Stellar: stellar.expert's + * asset holders API (no key), top 15 for USDY, the second-largest USDY chain by supply that has a + * keyless holders list. Both are valued at the Ethereum Ondo oracle price (getTokenPrices). + * A group whose fetch fails, or every group when the price read fails, falls back to its last + * good rows (src/data/last-good.json, labeled cached with their original date). A group with no + * last good rows is left out, never invented. */ import { getAddress } from 'viem'; import { holderName } from './addressRegistry'; -import { MOCK_DATA } from './mockData'; +import { asCached, LAST_GOOD } from './lastGood'; import { OUSG_ETHEREUM, USDY_ETHEREUM, getTokenPrices } from './prices'; -import type { DataSource, HolderMetric } from './types'; +import { combinedSource } from './dataSource'; +import type { DataSource, HolderChain, HolderMetric } from './types'; export const TOP_N = 15; const TOKENS = { OUSG: OUSG_ETHEREUM, USDY: USDY_ETHEREUM } as const; @@ -66,6 +70,7 @@ export function mapHolderItem( throw new Error(`non-positive balance or price for ${address}`); } return { + chain: 'ethereum', name: holderName(address, publicTag(addr)), address, token, @@ -90,25 +95,88 @@ async function fetchHolders(token: Token): Promise { return body.items.slice(0, TOP_N); } -function mockResult(reason: string): TopHoldersResult { - console.warn(`[topHolders] using mock data: ${reason}`); - return { rows: MOCK_DATA.topHolders, dataSource: 'mocked' }; +export const STELLAR_USDY_ISSUER = 'GAJMPX5NBOG6TQFPQGRABJEEB2YE7RFRLUKJDZAZGAD5GFX4J7TADAZ6'; +const STELLAR_HOLDERS_URL = `https://api.stellar.expert/explorer/public/asset/USDY-${STELLAR_USDY_ISSUER}/holders`; +export const stellarHoldersPageUrl = `https://stellar.expert/explorer/public/asset/USDY-${STELLAR_USDY_ISSUER}`; + +/** + * Map one stellar.expert holder record ({address, balance in 1e7 stroops as a string}) to a + * HolderMetric. Throws on a malformed record. + */ +export function mapStellarHolder(item: unknown, price: number, asOf: string): HolderMetric { + const { address, balance: raw } = (item ?? {}) as { address?: unknown; balance?: unknown }; + if (typeof address !== 'string' || !/^[GC][A-Z2-7]{55}$/.test(address)) { + throw new Error(`bad stellar address: ${String(address)}`); + } + if (typeof raw !== 'string' || !/^\d+$/.test(raw)) throw new Error(`bad balance for ${address}: ${String(raw)}`); + const balance = Number(raw) / 1e7; + if (!(balance > 0) || !Number.isFinite(price) || price <= 0) { + throw new Error(`non-positive balance or price for ${address}`); + } + return { + chain: 'stellar', + name: holderName(address, null), + address, + token: 'USDY', + balance, + tvlUsd: balance * price, + dataSource: 'live', + asOf, + source: stellarHoldersPageUrl, + }; +} + +async function fetchStellarHolders(): Promise { + const res = await fetch(`${STELLAR_HOLDERS_URL}?limit=${TOP_N}&order=desc`, { + next: { revalidate: 3600 }, + signal: AbortSignal.timeout(10_000), + }); + if (!res.ok) throw new Error(`stellar.expert USDY holders: HTTP ${res.status}`); + const body = (await res.json()) as { _embedded?: { records?: unknown } }; + const records = body._embedded?.records; + if (!Array.isArray(records) || records.length === 0) throw new Error('stellar.expert USDY holders: no records'); + return records.slice(0, TOP_N); +} + +type Group = { chain: HolderChain; token: Token }; +const GROUPS: Group[] = [ + { chain: 'ethereum', token: 'OUSG' }, + { chain: 'ethereum', token: 'USDY' }, + { chain: 'stellar', token: 'USDY' }, +]; + +/** Live rows for one (chain, token) group. Throws on any fetch or shape error. */ +async function liveGroup(g: Group, price: number, asOf: string): Promise { + if (g.chain === 'stellar') return (await fetchStellarHolders()).map((i) => mapStellarHolder(i, price, asOf)); + return (await fetchHolders(g.token)).map((i) => mapHolderItem(i, g.token, price, asOf)); +} + +/** + * Join per-group reads: a fulfilled group is live, a rejected one uses that group's last good rows. + * Pure: covered by topHolders.check.ts. + */ +export function assembleHolders( + groups: Group[], + reads: PromiseSettledResult[], + lastGood: HolderMetric[] +): HolderMetric[] { + return groups.flatMap((g, i) => { + const got = reads[i]; + if (got?.status === 'fulfilled') return got.value; + console.warn(`[topHolders] ${g.chain} ${g.token} read failed, using last good rows:`, got?.status === 'rejected' ? String(got.reason) : 'missing'); + return asCached(lastGood.filter((r) => r.chain === g.chain && r.token === g.token)); + }); } export async function getTopHolders(): Promise { + const asOf = new Date().toISOString(); + let reads: PromiseSettledResult[]; try { - const [ousg, usdy, prices] = await Promise.all([ - fetchHolders('OUSG'), - fetchHolders('USDY'), - getTokenPrices(), - ]); - const asOf = new Date().toISOString(); - const rows = [ - ...ousg.map((i) => mapHolderItem(i, 'OUSG', prices.OUSG, asOf)), - ...usdy.map((i) => mapHolderItem(i, 'USDY', prices.USDY, asOf)), - ]; - return { rows, dataSource: 'live' }; + const prices = await getTokenPrices(); + reads = await Promise.allSettled(GROUPS.map((g) => liveGroup(g, prices[g.token], asOf))); } catch (err) { - return mockResult(err instanceof Error ? err.message : String(err)); + reads = GROUPS.map(() => ({ status: 'rejected', reason: err })); } + const rows = assembleHolders(GROUPS, reads, LAST_GOOD.topHolders); + return { rows, dataSource: combinedSource(rows) }; } diff --git a/src/lib/types.ts b/src/lib/types.ts index 1234a93..9a39c7d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -6,7 +6,12 @@ // --- Data Source Taxonomy --- -export type DataSource = 'live' | 'estimated' | 'mocked'; +/** + * live: read from its source on this render. cached: the last good value, a real read from an + * earlier date kept in src/data/last-good.json (asOf says when). estimated: a total that mixes the two. + * No label means invented data: the dashboard has no mocked values. + */ +export type DataSource = 'live' | 'estimated' | 'cached'; export interface Sourced { dataSource: DataSource; @@ -16,12 +21,16 @@ export interface Sourced { source: string; } -// --- Pillar 1: Top Ethereum holders --- +// --- Pillar 1: Top holders (Ethereum and Stellar) --- + +export type HolderChain = 'ethereum' | 'stellar'; export interface HolderMetric extends Sourced { + /** Chain the balance sits on */ + chain: HolderChain; /** Registry label, else Blockscout public name tag, else "Unknown wallet (0x…)" */ name: string; - /** Wallet address (checksummed) */ + /** Wallet address (checksummed on Ethereum, a G or C account on Stellar) */ address: string; /** Token: OUSG or USDY */ token: 'OUSG' | 'USDY'; diff --git a/src/lib/weeklyFlows.check.ts b/src/lib/weeklyFlows.check.ts index 9aa218f..da777da 100644 --- a/src/lib/weeklyFlows.check.ts +++ b/src/lib/weeklyFlows.check.ts @@ -1,6 +1,6 @@ /** Self-check for the weekly flow series math. Run: npx tsx src/lib/weeklyFlows.check.ts */ import assert from 'node:assert/strict'; -import { MOCK_DATA } from './mockData'; +import { LAST_GOOD, asCached } from './lastGood'; import { fourWeekStats, growth12w, isCompleteWeek, mondayOf, weeklySeries } from './flowStats'; import type { WeeklyFlow } from './types'; @@ -20,16 +20,18 @@ const row: WeeklyFlow = { source: 'https://eth.blockscout.com/address/0x93358db73B6cd4b98D89c8F5f230E81a95c2643a?tab=logs', }; -// Mock series: deterministic shape, Monday weeks, both tokens, wallets never exceed minters + redeemers. -const mock = MOCK_DATA.weeklyFlows; -assert.equal(mock.length, 156); -for (const f of mock) { +// Last good series: a real read, Monday weeks, both tokens, consistent sums, relabeled cached with its own date. +const stored = LAST_GOOD.weeklyFlows; +assert.ok(stored.length > 100, 'stored flows missing'); +for (const f of stored) { assert.equal(new Date(`${f.week}T00:00:00Z`).getUTCDay(), 1, `${f.week} is not a Monday`); - assert.equal(f.netFlowUsd, f.mintVolumeUsd - f.redeemVolumeUsd); + assert.ok(Math.abs(f.netFlowUsd - (f.mintVolumeUsd - f.redeemVolumeUsd)) < 1, `${f.week} net`); assert.ok(f.uniqueWallets <= f.uniqueMinters + f.uniqueRedeemers); - assert.equal(f.dataSource, 'mocked'); + assert.ok(f.source.startsWith('https://etherscan.io/address/'), 'stored flows must cite the contract, not an illustration'); } -assert.ok(mock.some((f) => f.netFlowUsd < 0), 'expected some negative net flow weeks'); +assert.deepEqual([...new Set(stored.map((f) => f.token))].sort(), ['OUSG', 'USDY']); +const cachedFlows = asCached(stored); +assert.ok(cachedFlows.every((f, i) => f.dataSource === 'cached' && f.asOf === stored[i].asOf)); // Week math (UTC Mondays). assert.equal(mondayOf('2026-04-09'), '2026-04-06'); @@ -96,4 +98,4 @@ const empty = fourWeekStats([]); assert.equal(empty.avgTxSizeUsd, null); assert.equal(empty.volumeTrend, null); -console.log(`weeklyFlows check ok (${mock.length} mock rows)`); +console.log(`weeklyFlows check ok (${stored.length} stored rows)`); diff --git a/src/lib/weeklyFlows.ts b/src/lib/weeklyFlows.ts index 270475d..b486436 100644 --- a/src/lib/weeklyFlows.ts +++ b/src/lib/weeklyFlows.ts @@ -3,11 +3,12 @@ * * Live path: InstantManager event logs from Blockscout (keyless), aggregated by week * (src/lib/flowEvents.ts; definition in queries/mint_redeem_volume.sql). Any error - * falls back to the labeled mock series and logs why. + * falls back to the last good series (src/data/last-good.json, labeled cached, dated by its + * original read) and logs why. With no stored series the rows are empty and the page says so. */ import { aggregateWeekly, fetchFlowEvents, FLOWS_SOURCE_URL } from './flowEvents'; -import { MOCK_DATA } from './mockData'; +import { asCached, LAST_GOOD } from './lastGood'; import type { DataSource, WeeklyFlow } from './types'; export interface WeeklyFlowsResult { @@ -17,20 +18,20 @@ export interface WeeklyFlowsResult { queryUrl: string | null; } -function mockResult(reason: string): WeeklyFlowsResult { - console.warn(`[weeklyFlows] using mock data: ${reason}`); - return { rows: MOCK_DATA.weeklyFlows, dataSource: 'mocked', queryUrl: null }; +function lastGoodResult(reason: string): WeeklyFlowsResult { + console.warn(`[weeklyFlows] using last good series: ${reason}`); + return { rows: asCached(LAST_GOOD.weeklyFlows), dataSource: 'cached', queryUrl: null }; } export async function getWeeklyFlows(): Promise { try { const started = Date.now(); const { events, calls } = await fetchFlowEvents(); - if (!events.length) return mockResult('no InstantManager events returned'); + if (!events.length) return lastGoodResult('no InstantManager events returned'); console.info(`[weeklyFlows] ${events.length} events, ${calls} page requests, ${Date.now() - started}ms`); const rows = aggregateWeekly(events, { asOf: new Date().toISOString(), source: FLOWS_SOURCE_URL }); return { rows, dataSource: 'live', queryUrl: FLOWS_SOURCE_URL }; } catch (err) { - return mockResult(`event logs failed: ${err instanceof Error ? err.message : String(err)}`); + return lastGoodResult(`event logs failed: ${err instanceof Error ? err.message : String(err)}`); } }
- {token} ({formatUsdCompact(total)} in top {rows.length}) + {label} ({formatUsdCompact(total)} in top {rows.length}) Balance Value
@@ -62,7 +76,7 @@ export function TopHoldersChart({ data }: TopHoldersChartProps) { {prefix}{formatUsdCompact(row.tvlUsd)} - +