Add treasury composition, DAO NFT holdings, and revenue analytics to the Treasury tab - #981
Add treasury composition, DAO NFT holdings, and revenue analytics to the Treasury tab#981sktbrd wants to merge 9 commits into
Conversation
…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>
|
@sktbrd is attempting to deploy a commit to the Nouns Builder Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe 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. ChangesTreasury asset composition
Recent treasury activity
Auction revenue analytics
NFT holdings and integration
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/dao-ui/src/components/Treasury/TreasuryComposition.tsx (1)
88-101: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winEnsure 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 becauseseen.has()is not updated within the loop. This can lead to redundantbalanceOfmulticall queries and trigger React's "two children with the same key" warning in the UI.Update the
seenset 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 winWindow-tab buttons don't expose selected state to assistive tech.
Selection is conveyed only via
windowTab.selected/unselectedborder styling; there's noaria-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 valueHardcoded SVG gradient
idcan 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 resolveurl(#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
gradientIdinstead 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 valuePrefer
reduceover spreading intoMath.max/Math.minfor large arrays.
Math.max(...amounts)andMath.min/max(...xs)spread the full auction array into function arguments. WithMAX_PAGES=25andPAGE=1000in 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
📒 Files selected for processing (17)
.changeset/treasury-composition.mdpackages/dao-ui/src/components/Treasury/NFTBalance.tsxpackages/dao-ui/src/components/Treasury/TokenBalance.tsxpackages/dao-ui/src/components/Treasury/Treasury.tsxpackages/dao-ui/src/components/Treasury/TreasuryAnalytics.css.tspackages/dao-ui/src/components/Treasury/TreasuryAnalytics.tsxpackages/dao-ui/src/components/Treasury/TreasuryComposition.css.tspackages/dao-ui/src/components/Treasury/TreasuryComposition.tsxpackages/dao-ui/src/components/Treasury/TreasuryNfts.css.tspackages/dao-ui/src/components/Treasury/TreasuryNfts.tsxpackages/dao-ui/src/components/Treasury/index.tsxpackages/dao-ui/src/components/Treasury/tokenLogos.tspackages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.test.tspackages/dao-ui/src/components/Treasury/treasuryAnalytics.helper.tspackages/dao-ui/src/components/Treasury/treasuryComposition.helper.test.tspackages/dao-ui/src/components/Treasury/treasuryComposition.helper.tspackages/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
|
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 ( Pure helpers are unit-tested ( |
…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).
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
packages/dao-ui/src/components/Treasury/Treasury.tsxpackages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.css.tspackages/dao-ui/src/components/Treasury/TreasuryRecentTransactions.tsxpackages/dao-ui/src/components/Treasury/recentTransactions.helper.test.tspackages/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
| const proposalTxs: RecentTx[] = proposals | ||
| .filter((p) => !!p.executedAt) | ||
| .map((p) => ({ |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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 } |
There was a problem hiding this comment.
🎯 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 3Repository: 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.tsRepository: 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
fiRepository: 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}")
PYRepository: 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:
- 1: https://swr.vercel.app/docs/api
- 2: https://github.com/vercel/swr/
- 3: https://swr.vercel.app/docs/getting-started
- 4: https://github.com/vercel/swr/blob/main/README.md
- 5: https://swr.vercel.app/docs/data-fetching
- 6: https://swr.vercel.app/docs/error-handling
- 7: fallbackData does not behave as initialData vercel/swr#2179
- 8: Option to treat fallback data as initial data for comparison vercel/swr#2988
- 9: data is undefined when
fallbackDataandrevalidateOnMount: falseare provided vercel/swr#1422 - 10: Errors thrown in fetcher are swallowed by SWR vercel/swr#1881
- 11: When Suspense is enabled, error thrown inside fetcher function is not thrown by SWR (swallowed) vercel/swr#2194
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
packages/dao-ui/src/components/Treasury/TreasuryComposition.css.tspackages/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
…-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] ?? [] |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|

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:
useReadContractsmulticallbalanceOf(treasury).tokensQueryby owner).Screenshots
Composition — allocation donut + curated asset rows valued in USD via an on-chain
balanceOfmulticall (no API key):Auction Revenue — cumulative-revenue chart + metric tiles, paged auction history (2,012 auctions shown):
NFT holdings — the DAO's own treasury NFTs from the Builder subgraph:
Captured against Gnars (Base) on the merged branch.
Motivation & context
The existing
TokenBalance/NFTBalancesections 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:Part of Gnars DAO Proposal 61, Milestone 3 (Treasury Analytics).
Code review
useEthUsdPrice), other tokens balance-only.keepPreviousDataon the multicall keeps rows stable across the app's 5s refetch interval (imported the same waypackages/hooksalready imports@tanstack/react-query, via wagmi's peer — no new dependency).treasuryComposition.helper,treasuryAnalytics.helper).TokenBalance/NFTBalancecomponents (not exported anywhere).Type of change
Checklist
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation