Skip to content

Add treasury composition, DAO NFT holdings, and revenue analytics to the Treasury tab - #981

Open
sktbrd wants to merge 9 commits into
BuilderOSS:stagingfrom
sktbrd:feat/m3-treasury-analytics
Open

Add treasury composition, DAO NFT holdings, and revenue analytics to the Treasury tab#981
sktbrd wants to merge 9 commits into
BuilderOSS:stagingfrom
sktbrd:feat/m3-treasury-analytics

Conversation

@sktbrd

@sktbrd sktbrd commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Treasury Analytics view to the DAO Treasury tab, replacing the Alchemy-dependent token/NFT balance sections with subgraph/RPC-backed equivalents that scale to every DAO with no API key:

  • Composition — an allocation donut + curated asset rows (ETH plus a global per-chain token registry and the DAO's clanker token) valued in USD, read via on-chain useReadContracts multicall balanceOf(treasury).
  • NFT holdings — the DAO's own NFTs held in the treasury, read natively from the Builder subgraph (tokensQuery by owner).
  • Auction Revenue — a cumulative-revenue chart + aggregates (revenue, auctions, average winning bid excluding no-bid burns, highest sale), paged so high-volume DAOs aren't undercounted. Custom SVG, no chart library.
  • Real token brand logos embedded as base64 (Trust Wallet assets).

Screenshots

Composition — allocation donut + curated asset rows valued in USD via an on-chain balanceOf multicall (no API key):

Treasury composition

Auction Revenue — cumulative-revenue chart + metric tiles, paged auction history (2,012 auctions shown):

Auction revenue analytics

NFT holdings — the DAO's own treasury NFTs from the Builder subgraph:

Treasury NFTs

Captured against Gnars (Base) on the merged branch.

Motivation & context

The existing TokenBalance / NFTBalance sections use Alchemy's enumerate-all endpoints, which (a) require an API key, (b) return HTTP 500 on treasuries holding many tokens/NFTs (per-item metadata/price enrichment fails as one batch), and (c) spam-flag a DAO's own NFTs. This replaces them with:

  • token holdings via a global per-chain registry (keyed by chain, not by DAO) + the DAO's clanker token from the subgraph → a single multicall (can't 500, no key, inherently "main tokens only");
  • NFT holdings via the Builder subgraph, which already indexes every token a DAO mints.

Part of Gnars DAO Proposal 61, Milestone 3 (Treasury Analytics).

Code review

  • Valuation is dependency-free: stables 1:1, WETH × ETH/USD (useEthUsdPrice), other tokens balance-only.
  • keepPreviousData on the multicall keeps rows stable across the app's 5s refetch interval (imported the same way packages/hooks already imports @tanstack/react-query, via wagmi's peer — no new dependency).
  • Pure helpers are unit-tested (treasuryComposition.helper, treasuryAnalytics.helper).
  • Conventions: Vanilla Extract + zord, custom SVG charts (per the AuctionGraph / VoteMetrics precedent), SWR / wagmi / subgraph SDK, changeset included.
  • Deletes the now-unused TokenBalance / NFTBalance components (not exported anywhere).
  • Open question for maintainers: replacing the Alchemy token/NFT sections drops long-tail token visibility in favor of a curated set — happy to keep the Alchemy path behind a toggle if preferred.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • I have done a self-review of my own code
  • Any new and existing tests pass locally with my changes
  • My changes generate no new warnings (lint warnings, console warnings, etc)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a treasury composition view with allocation charts, asset balances, USD values, and percentages.
    • Added DAO NFT holdings with images, labels, and collection counts.
    • Added recent treasury activity for proposal outflows and auction proceeds.
    • Added auction revenue analytics with selectable time ranges, cumulative charts, and key metrics.
    • Improved treasury asset visibility across supported networks, including ETH and common tokens.
  • Documentation

    • Added product documentation for the treasury composition experience.

…the Treasury tab

Milestone 3 (Gnars Prop 61) — Treasury Analytics, Batch 1.

- TreasuryComposition: allocation donut + curated asset rows (ETH plus a
  global per-chain token registry and the DAO's clanker token) valued in USD
  via on-chain multicall. Scales to every DAO with no per-DAO config and no
  Alchemy enumerate-all (which 500s on large treasuries).
- TreasuryNfts: the DAO's own NFTs held in the treasury, read natively from
  the Builder subgraph (tokensQuery by owner) — no Alchemy, no spam heuristics.
- TreasuryAnalytics: cumulative auction-revenue chart + aggregates (revenue,
  auctions, average winning bid excluding no-bid burns, highest sale), paged
  so high-volume DAOs aren't undercounted. Custom SVG, no chart library.
- Real token brand logos embedded as base64 (Trust Wallet assets).
- Replaces the Alchemy-dependent TokenBalance/NFTBalance sections with
  key-free, subgraph/RPC-backed equivalents.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

@sktbrd is attempting to deploy a commit to the Nouns Builder Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The DAO Treasury tab now renders asset composition, subgraph-backed NFT holdings, recent activity, and selectable auction-revenue analytics. New helpers, styles, embedded token logos, tests, and exports support the replacement Treasury experience.

Changes

Treasury asset composition

Layer / File(s) Summary
Asset registry, valuation, and visualization
packages/dao-ui/src/components/Treasury/treasuryTokens.ts, packages/dao-ui/src/components/Treasury/treasuryComposition.helper.ts, packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx, packages/dao-ui/src/components/Treasury/TreasuryComposition.css.ts, packages/dao-ui/src/components/Treasury/tokenLogos.ts, packages/dao-ui/src/components/Treasury/treasuryComposition.helper.test.ts
Chain-specific tokens, USD valuation, donut calculations, embedded logos, balances, allocation rows, and helper tests are added.

Recent treasury activity

Layer / File(s) Summary
Subgraph activity feed
packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts, packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx, packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.css.ts, packages/dao-ui/src/components/Treasury/recentTransactions.helper.test.ts
Executed proposals become ETH outflows. Positive auction bids become ETH inflows. The feed is sorted, limited, styled, and tested.

Auction revenue analytics

Layer / File(s) Summary
Revenue aggregation and chart rendering
packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.ts, packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx, packages/dao-ui/src/components/Treasury/TreasuryAnalytics.css.ts, packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.test.ts
Auction history is paginated by time window, aggregated into metrics and cumulative revenue, rendered as an SVG chart, and tested.

NFT holdings and integration

Layer / File(s) Summary
Subgraph NFT display and Treasury wiring
packages/dao-ui/src/components/Treasury/TreasuryNfts.tsx, packages/dao-ui/src/components/Treasury/TreasuryNfts.css.ts, packages/dao-ui/src/components/Treasury/Treasury.tsx, packages/dao-ui/src/components/Treasury/index.tsx, .changeset/treasury-composition.md
Treasury NFTs are fetched from the Builder subgraph and displayed with the composition, activity, and analytics components. Public exports and a minor-release changeset are added.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Treasury
  participant TreasuryAnalytics
  participant SWR
  participant AuctionAPI
  Treasury->>TreasuryAnalytics: render analytics section
  TreasuryAnalytics->>SWR: request auction data for selected window
  SWR->>AuctionAPI: fetch paginated auction batches
  AuctionAPI-->>SWR: return auction batches
  SWR-->>TreasuryAnalytics: provide deduplicated auction history
  TreasuryAnalytics->>TreasuryAnalytics: calculate metrics and render chart
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main Treasury tab additions: composition, DAO NFT holdings, and revenue analytics.
Description check ✅ Passed The description includes all template sections, explains the changes and motivation, identifies review notes, and records type and checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx (1)

88-101: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Ensure unique keys and prevent redundant on-chain calls if the subgraph returns duplicates.

If the subgraph query (clankerTokens) returns multiple entries for the same token address, the current filter will allow duplicates because seen.has() is not updated within the loop. This can lead to redundant balanceOf multicall queries and trigger React's "two children with the same key" warning in the UI.

Update the seen set during filtering to guarantee a strictly unique token list.

♻️ Proposed refactor
   const tokenList = useMemo<RegistryToken[]>(() => {
     const common = COMMON_TREASURY_TOKENS[chain.id] ?? []
     const seen = new Set(common.map((t) => t.address.toLowerCase()))
     const clankers: RegistryToken[] = (clankerTokens ?? [])
-      .filter((c) => c.tokenAddress && !seen.has(c.tokenAddress.toLowerCase()))
+      .filter((c) => {
+        if (!c.tokenAddress) return false
+        const lower = c.tokenAddress.toLowerCase()
+        if (seen.has(lower)) return false
+        seen.add(lower)
+        return true
+      })
       .map((c) => ({
         symbol: c.tokenSymbol || 'TOKEN',
         address: c.tokenAddress.toLowerCase() as `0x${string}`,
         decimals: 18,
         kind: 'other' as const,
         logo: c.tokenImage || undefined,
       }))
     return [...common, ...clankers]
   }, [chain.id, clankerTokens])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx` around lines
88 - 101, Update the tokenList useMemo’s clankerTokens filtering to add each
accepted token address to the seen set as it is processed, not only check
membership. Preserve the existing common-token deduplication and mapping
behavior so the returned list contains one entry per address and avoids
duplicate balance calls and React keys.
packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx (2)

152-164: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Window-tab buttons don't expose selected state to assistive tech.

Selection is conveyed only via windowTab.selected/unselected border styling; there's no aria-pressed (or similar) so screen-reader users can't tell which window is active.

♻️ Suggested fix
             <Button
               key={w}
               variant={'ghost'}
               size={'sm'}
               px={'x2'}
+              aria-pressed={w === window}
               className={w === window ? windowTab.selected : windowTab.unselected}
               onClick={() => setWindow(w)}
             >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx` around lines
152 - 164, Update the RevenueWindow buttons rendered in TreasuryAnalytics to
expose their active selection state through an appropriate ARIA attribute such
as aria-pressed, setting it true only when w equals window and false otherwise.
Preserve the existing styling and window-switching behavior.

53-53: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Hardcoded SVG gradient id can collide across multiple instances.

GRADIENT_ID = 'treasuryRevGradient' is a fixed string used as the <linearGradient id>. If this component is ever rendered more than once on the same page (e.g. a multi-DAO dashboard), the duplicate SVG ids would collide, and browsers resolve url(#id) references to the first matching element in the DOM.

♻️ Suggested fix (React 19 `useId`)
+import { useId } from 'react'
...
-const GRADIENT_ID = 'treasuryRevGradient'
...
+  const gradientId = useId()

Then reference gradientId instead of the module-level constant.

Also applies to: 184-189

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx` at line 53,
Replace the module-level GRADIENT_ID constant in TreasuryAnalytics with a
per-instance React useId value, and use that gradientId for both the
linearGradient id and all corresponding url(#...) references. Ensure multiple
TreasuryAnalytics instances produce distinct SVG gradient identifiers.
packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.ts (1)

57-57: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Prefer reduce over spreading into Math.max/Math.min for large arrays.

Math.max(...amounts) and Math.min/max(...xs) spread the full auction array into function arguments. With MAX_PAGES=25 and PAGE=1000 in the caller, this can spread up to ~25,000 elements — currently within engine limits, but fragile if the caller's pagination bounds ever grow.

♻️ Suggested refactor
-  highestSale: Math.max(...amounts),
+  highestSale: amounts.reduce((max, v) => (v > max ? v : max), 0),

Also applies to: 90-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.ts` at line
57, Replace the spread-based Math.max/Math.min calculations in the treasury
analytics helper, including highestSale and the related calculations around
lines 90–92, with reduce-based aggregation over amounts. Preserve the existing
results and behavior for the full auction array without expanding its elements
into function arguments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx`:
- Line 84: Rename the state tuple in TreasuryAnalytics from window/setWindow to
selectedWindow/setSelectedWindow, and update every subsequent reference in the
component, including the usages around the analytics rendering logic, while
preserving the existing RevenueWindow state behavior.
- Around line 86-96: Update the useSWR handling in
packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx#L86-L96 to
destructure error and render a distinct error state before the empty-data
message. Apply the same change in
packages/dao-ui/src/components/Treasury/TreasuryNfts.tsx#L24-L28, ensuring
subgraph or network failures are not presented as legitimate empty states.
- Around line 106-109: Update the paged axios.get call inside the MAX_PAGES loop
to include a finite request timeout in its configuration, ensuring stalled
auction-history requests fail and allow the existing flow to recover instead of
blocking indefinitely.

---

Nitpick comments:
In `@packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.ts`:
- Line 57: Replace the spread-based Math.max/Math.min calculations in the
treasury analytics helper, including highestSale and the related calculations
around lines 90–92, with reduce-based aggregation over amounts. Preserve the
existing results and behavior for the full auction array without expanding its
elements into function arguments.

In `@packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx`:
- Around line 152-164: Update the RevenueWindow buttons rendered in
TreasuryAnalytics to expose their active selection state through an appropriate
ARIA attribute such as aria-pressed, setting it true only when w equals window
and false otherwise. Preserve the existing styling and window-switching
behavior.
- Line 53: Replace the module-level GRADIENT_ID constant in TreasuryAnalytics
with a per-instance React useId value, and use that gradientId for both the
linearGradient id and all corresponding url(#...) references. Ensure multiple
TreasuryAnalytics instances produce distinct SVG gradient identifiers.

In `@packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx`:
- Around line 88-101: Update the tokenList useMemo’s clankerTokens filtering to
add each accepted token address to the seen set as it is processed, not only
check membership. Preserve the existing common-token deduplication and mapping
behavior so the returned list contains one entry per address and avoids
duplicate balance calls and React keys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 234ede7f-1402-44dc-ab59-07c860045a1c

📥 Commits

Reviewing files that changed from the base of the PR and between 58e0311 and 0abc87c.

📒 Files selected for processing (17)
  • .changeset/treasury-composition.md
  • packages/dao-ui/src/components/Treasury/NFTBalance.tsx
  • packages/dao-ui/src/components/Treasury/TokenBalance.tsx
  • packages/dao-ui/src/components/Treasury/Treasury.tsx
  • packages/dao-ui/src/components/Treasury/TreasuryAnalytics.css.ts
  • packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx
  • packages/dao-ui/src/components/Treasury/TreasuryComposition.css.ts
  • packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx
  • packages/dao-ui/src/components/Treasury/TreasuryNfts.css.ts
  • packages/dao-ui/src/components/Treasury/TreasuryNfts.tsx
  • packages/dao-ui/src/components/Treasury/index.tsx
  • packages/dao-ui/src/components/Treasury/tokenLogos.ts
  • packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.test.ts
  • packages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.ts
  • packages/dao-ui/src/components/Treasury/treasuryComposition.helper.test.ts
  • packages/dao-ui/src/components/Treasury/treasuryComposition.helper.ts
  • packages/dao-ui/src/components/Treasury/treasuryTokens.ts
💤 Files with no reviewable changes (2)
  • packages/dao-ui/src/components/Treasury/NFTBalance.tsx
  • packages/dao-ui/src/components/Treasury/TokenBalance.tsx

Comment thread packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx Outdated
Comment thread packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx Outdated
Comment thread packages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsx
@sktbrd
sktbrd marked this pull request as draft July 15, 2026 13:57
@sktbrd
sktbrd marked this pull request as ready for review July 30, 2026 23:09
@sktbrd

sktbrd commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Marking ready for review — this is the Treasury Analytics track of Gnars DAO Prop 61, Milestone 3.

Main decision for maintainers: this replaces the Alchemy-backed token/NFT sections with subgraph/RPC-backed equivalents — no API key, and it can't 500 on token/NFT-heavy treasuries. The tradeoff is long-tail token visibility: a curated per-chain registry (treasuryTokens.ts) + the DAO's clanker token, instead of Alchemy's enumerate-all. Happy to keep the Alchemy path behind a toggle if you'd rather not drop it — just let me know the preference.

Pure helpers are unit-tested (treasuryAnalytics.helper, treasuryComposition.helper); charts are custom SVG (per the AuctionGraph / VoteMetrics precedent), no new chart dependency.

sktbrd and others added 4 commits July 30, 2026 20:25
…owing window state

Addresses CodeRabbit review on BuilderOSS#981:
- TreasuryAnalytics/TreasuryNfts now destructure useSWR error and render a
  distinct error state, so a subgraph/network failure is no longer shown as a
  legitimate empty ('no data') state.
- Add a 15s timeout to the paged auctionHistory axios request so one stalled
  page can't hang the chart on its skeleton indefinitely.
- Rename the 'window' state (and startTimeFromNow param) to selectedWindow/
  windowKey so they no longer shadow the global window object.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per maintainer feedback, the Revenue / Auctions / Avg. Winning Bid / Highest Sale
tiles sat in a detached row below the chart box. Move them inside the same
bordered card, separated by a divider, so the chart and its stats read as one
unit.
Per maintainer feedback: the 320px|1fr donut/rows grid leaves a sparse, lopsided
right column when a DAO holds only one or two priced assets (e.g. a 100%-ETH
treasury). Stack the donut over full-width rows in that case; keep the 2-column
layout once there are several assets.
Ports the template's Recent-transactions panel maintainers preferred: executed
proposals as ETH outflows (amount = sum of their tx values) and settled auctions
as inflows (winning bid), merged newest-first from the Builder subgraph, with
direction badges, Prop #/Auction labels, signed amounts, timestamps, and an
explorer link. Pure derivation is unit-tested (deriveRecentTransactions).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts`:
- Around line 52-54: Update the proposal filter in the proposalTxs construction
to require p.executed === true and a non-null executedAt before mapping
transactions. Preserve the existing transaction mapping and add a regression
test covering an unexecuted proposal with a non-null timestamp, ensuring it is
excluded.

In `@packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx`:
- Around line 29-50: The recent proposals and auctions queries used by
TreasuryRecentTransactions need to enforce the displayed 30-day window. In the
useSWR loaders for proposals and auctionHistory, add descending
executedAt/endTime ordering and filter each query with a Unix timestamp cutoff
computed as 30 days before the current time; retain the existing result mappings
and limits.
- Around line 29-50: Track the SWR loading and error states returned by the
proposals and auctions useSWR calls, and render the appropriate loading or error
UI before calling deriveRecentTransactions. Ensure request failures are not
converted into empty arrays that produce “No recent treasury activity,” while
preserving the existing transaction derivation for successful responses.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb92c401-18f2-4eb0-91d3-4d5712c771f5

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0195e and 402c45c.

📒 Files selected for processing (5)
  • packages/dao-ui/src/components/Treasury/Treasury.tsx
  • packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.css.ts
  • packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx
  • packages/dao-ui/src/components/Treasury/recentTransactions.helper.test.ts
  • packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/dao-ui/src/components/Treasury/Treasury.tsx

Comment on lines +52 to +54
const proposalTxs: RecentTx[] = proposals
.filter((p) => !!p.executedAt)
.map((p) => ({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Filter proposals by executed.

executedAt alone controls inclusion. A record with executed: false and a timestamp is rendered as a treasury outflow. Require p.executed === true and a non-null executedAt. Add a regression test with executed: false and a non-null timestamp.

Proposed fix
   const proposalTxs: RecentTx[] = proposals
-    .filter((p) => !!p.executedAt)
+    .filter((p) => p.executed === true && p.executedAt != null)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const proposalTxs: RecentTx[] = proposals
.filter((p) => !!p.executedAt)
.map((p) => ({
const proposalTxs: RecentTx[] = proposals
.filter((p) => p.executed === true && p.executedAt != null)
.map((p) => ({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts` around
lines 52 - 54, Update the proposal filter in the proposalTxs construction to
require p.executed === true and a non-null executedAt before mapping
transactions. Preserve the existing transaction mapping and add a regression
test covering an unexecuted proposal with a non-null timestamp, ensuring it is
excluded.

Comment on lines +29 to +50
const { data: proposals } = useSWR(
token && chain.id ? (['treasury-recent-proposals', chain.id, token] as const) : null,
([, chainId, t]) =>
SubgraphSDK.connect(chainId)
.proposals({ where: { dao: t.toLowerCase() }, first: 100 })
.then((d) => d.proposals),
{ revalidateOnFocus: false }
)

const { data: auctions } = useSWR(
token && chain.id ? (['treasury-recent-auctions', chain.id, token] as const) : null,
([, chainId, t]) =>
SubgraphSDK.connect(chainId)
.auctionHistory({
daoId: t.toLowerCase(),
startTime: 0,
orderBy: Auction_OrderBy.EndTime,
orderDirection: OrderDirection.Desc,
first: 20,
})
.then((d) => d.dao?.auctions ?? []),
{ revalidateOnFocus: false }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect generated ordering options and existing subgraph pagination patterns.
rg -n -C 3 'Proposal_OrderBy|Auction_OrderBy|\.proposals\(' .

Repository: BuilderOSS/nouns-builder

Length of output: 19182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map and inspect the TreasuryRecentTransactions component and nearby helpers.
wc -l packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx
cat -n packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx

printf '\n--- related symbols ---\n'
rg -n 'deriveRecentTransactions|recentProposalCount|voteEnd_lt|Proposal_OrderBy|OrderDirection|last 30|30 day|30d' packages/dao-ui packages/sdk -C 3

Repository: BuilderOSS/nouns-builder

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant SDK request source files with line numbers.
wc -l packages/sdk/src/subgraph/requests/daoActivity.ts packages/sdk/src/subgraph/requests/auctionHistory.ts packages/sdk/src/subgraph/requests/proposalsQuery.ts
cat -n packages/sdk/src/subgraph/requests/daoActivity.ts
printf '\n--- auctionHistory ---\n'
cat -n packages/sdk/src/subgraph/requests/auctionHistory.ts
printf '\n--- relevant generated Proposal_Filter/order fields ---\n'
python3 - <<'PY'
from pathlib import Path
p=Path('packages/sdk/src/subgraph/sdk.generated.ts')
s=p.read_text()
for needle in ['export enum Proposal_OrderBy', 'export type DaoProposalsArgs', 'export type Proposal_Filter']:
    i=s.index(needle + '\n')
    start=s.rfind('\n',0,i)+1
    end=s.find('\n}',i)
    print(f'\n--- {needle} ---')
    print(s[start:end+1].replace('\n', '\n')[1:1200])
PY

printf '\n--- helper window implementation ---\n'
cat -n packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts | sed -n '1,140p'

Repository: BuilderOSS/nouns-builder

Length of output: 5666


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focused, line-numbered extraction for the generated filters and Treasury helper.
rg -n -C 8 'export enum Proposal_OrderBy|export enum Auction_OrderBy|export type DaoProposalsArgs|export type Proposal_Filter|export type Auction_Filter|last 30d|startTime_gte|voteStart|voteEnd_|executedAt|timestamp|EndTime' packages/sdk/src/subgraph/sdk.generated.ts packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts

printf '\n--- Treasury helper ---\n'
cat -n packages/dao-ui/src/components/Treasury/recentTransactions.helper.ts

Repository: BuilderOSS/nouns-builder

Length of output: 50381


Query a descending 30-day treasury activity window.

The UI labels this the last 30d, but proposal requests have no orderBy/orderDirection and no cutoff, while auction history starts at Unix time zero. With only 100 proposals and 20 auctions fetched, deriveRecentTransactions can still show older activity and omit newer treasury movements; add descending executedAt/endTime ordering plus a 30-day lower-bound timestamp filter, or change the label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx`
around lines 29 - 50, The recent proposals and auctions queries used by
TreasuryRecentTransactions need to enforce the displayed 30-day window. In the
useSWR loaders for proposals and auctionHistory, add descending
executedAt/endTime ordering and filter each query with a Unix timestamp cutoff
computed as 30 days before the current time; retain the existing result mappings
and limits.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching TreasuryRecentTransactions:"
fd -a 'TreasuryRecentTransactions\.tsx$' . || true

file="$(fd 'TreasuryRecentTransactions\.tsx$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  echo
  echo "Outline for $file:"
  ast-grep outline "$file" || true
  echo
  echo "Relevant contents:"
  cat -n "$file"
fi

echo
echo "Search for SWR error/loading patterns in component:"
if [ -n "${file:-}" ]; then
  rg -n "useSWR|loading|error|undefined|isError|proposals|auctions|No recent" "$file" || true
fi

Repository: BuilderOSS/nouns-builder

Length of output: 6265


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate recentTransactions helper and package manifests:"
fd -a 'recentTransactions\.helper\.(ts|tsx)$|package\.json$' . | sed 's#^\./##' | head -n 50

file="$(fd 'recentTransactions\.helper\.(ts|tsx)$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  echo
  echo "Outline for $file:"
  ast-grep outline "$file" || true
  echo
  echo "Contents for $file:"
  cat -n "$file"
fi

echo
echo "Check dependency versions for SWR and TypeScript from manifests:"
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').rglob('package.json'):
    try:
        data = json.loads(p.read_text())
    except Exception:
        continue
    deps = {}
    for k in ('dependencies','devDependencies','peerDependencies'):
        deps.update(data.get(k) or {})
    s = deps.get('swr') or deps.get('`@buildeross/sdk`@subgraph', None) or deps.get('`@buildeross/sdk`', None)
    print(f"{p}: deps.swr={s}")
PY

Repository: BuilderOSS/nouns-builder

Length of output: 50380


🌐 Web query:

SWR useSWR returns undefined data on loading initially and error undefined when fetcher fails

💡 Result:

In SWR, the useSWR hook follows a standard pattern for data fetching where data is undefined while a request is in progress [1][2]. The behavior you are observing is the expected design of the library [3][4]. ### Initial Loading Behavior When useSWR first runs, it initiates the fetcher function [1][5]. During this initial loading phase: * data is undefined [1][2]. * isLoading is true (unless cached data is already available or fallbackData is provided) [1][2]. * error is undefined [1]. Once the fetcher completes successfully, data is updated with the result, isLoading becomes false, and the component re-renders [2][4]. ### Fetcher Failure Behavior If the fetcher function throws an error (e.g., via throw new Error(...) or by rejecting a promise), SWR catches this error [5][6]. Upon failure: * data remains undefined (or the previous cached value, if any) [1][2]. * error is populated with the error object thrown by the fetcher [1][6]. * isLoading becomes false [2][4]. ### Common Considerations * Error Handling: You should use the error return value to conditionally render your UI [3][2]. For example, if (error) return <div>Failed to load</div> [3][4]. * Fallback Data: If you want to avoid undefined on the first render, you can use the fallbackData configuration option [7][8]. Note that fallbackData does not automatically populate the cache in all versions or configurations in the same way initialData (a deprecated feature) did, and its behavior can sometimes cause the request to still be made on mount depending on your configuration [9][7][8]. * Error Bubbling: Errors thrown in the fetcher are caught by SWR to manage the hook's state declaratively [10]. They will not bubble up to your application's error boundaries unless specific configurations (like Suspense) are used in ways that interact with the error state [11]. Using the onError configuration option is the recommended way to handle or log errors globally or per-hook [6][10].

Citations:


Render SWR pending and error states for treasury requests.

proposals and auctions are undefined while either fetch loads or fails. Since these feed deriveRecentTransactions(proposals ?? [], auctions ?? [], 12), a subgraph request failure can make the treasury show “No recent treasury activity.” Return a loading or error UI before deriving transactions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsx`
around lines 29 - 50, Track the SWR loading and error states returned by the
proposals and auctions useSWR calls, and render the appropriate loading or error
UI before calling deriveRecentTransactions. Ensure request failures are not
converted into empty arrays that produce “No recent treasury activity,” while
preserving the existing transaction derivation for successful responses.

…blink

- Few-asset layout: the donut was a centered 360px card sitting over a
  full-width row (a lopsided 'third' layout). Stack both in one centered,
  width-matched 460px column so they line up.
- Token rows blinked: the global 5s wagmi refetchInterval re-ran the balance
  multicall, and an occasional failed call dropped a token row for a cycle.
  Disable polling for treasury balances (they refresh on mount/focus) so rows
  stay put; ETH was already stable via useBalance.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx`:
- Line 120: Remove the keepPreviousData placeholder configuration from the
treasury balances query so recomputation does not display positional balances
from the previous token list, treasury, or chain. Update the query containing
placeholderData and preserve the existing balance mapping and loading behavior
for newly fetched data.
- Around line 113-118: Update the TreasuryComposition balance-mapping logic to
avoid converting failed or null/undefined multicall results into 0n. Preserve
the last successful value for each chain, treasury, and token, or mark the value
unavailable and exclude/suppress the aggregate total until required reads
succeed; ensure transient refetch failures do not remove rows or reduce
totalUsd.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f2ef821-4a95-44f6-8d07-94475cc5db6d

📥 Commits

Reviewing files that changed from the base of the PR and between 402c45c and 5ac46b9.

📒 Files selected for processing (2)
  • packages/dao-ui/src/components/Treasury/TreasuryComposition.css.ts
  • packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/dao-ui/src/components/Treasury/TreasuryComposition.css.ts

Comment thread packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx Outdated
Comment thread packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx Outdated
…-token balance cache

- Recent tx feed: only count proposals that actually executed. executedAt is
  set iff executed, but also guard on executed !== false so a false record with
  a stray timestamp is never rendered as an outflow.
- Drop the inaccurate 'last 30d' subtitle — the feed shows the newest proposals
  (query is orderBy timeCreated desc) and 20 latest auctions, with no 30d cutoff.
- Composition: replace keepPreviousData (which could paint one DAO's balances
  positionally onto another during a switch) with a per-token last-good cache
  keyed by address and reset when the DAO changes — a failed/pending read keeps
  its own row instead of blinking to zero, and stale data never leaks cross-DAO.

// Global per-chain registry + this DAO's clanker token(s), deduped.
const tokenList = useMemo<RegistryToken[]>(() => {
const common = COMMON_TREASURY_TOKENS[chain.id] ?? []

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing this? Earlier we were using

  const { balances, isLoading: balancesLoading } = useTokenBalances(
    chain.id,
    addresses.treasury,
    { filterLowValue: !showLowValueTokens }
  )

  // Fetch pinned assets
  const { pinnedAssets, isLoading: pinnedLoading } = usePinnedAssets(
    chain.id,
    addresses.token
  )

and we are losing both other erc20s outside of the hardcoded list + pinned assets. I like the UI upgrades, but this is a downgrade in functionalities.

const treasury = addresses.treasury as AddressType | undefined

const { data, isValidating, error } = useSWR(
treasury && chain.id ? (['treasury-dao-nfts', chain.id, treasury] as const) : null,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly we are using tokensQuery which only fetches governance tokens of the DAO. It no longer uses alchemy apis to fetch all the NFTs. Could you re-add functionalities to fetch all NFTs using alchemy API?

@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
testnet-nouns-builder Ready Ready Preview Aug 19, 2026 1:59pm

Request Review

@dan13ram

Copy link
Copy Markdown
Collaborator
image

Here could you make each of these links to the respective txs on the explorer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants