Skip to content

Conversation

@alextnetto
Copy link
Member

Overview

Add an Activity Feed feature to track governance activities (votes, proposals, transfers, delegations) with relevance scoring across DAOs.

Architecture

flowchart LR
    subgraph Indexing[Event Indexing]
        VoteCast --> feedEvent[feed_event table]
        ProposalCreated --> feedEvent
        Transfer --> feedEvent
        DelegateChanged --> feedEvent
    end
    subgraph API[REST API]
        feedEvent --> Repository
        Repository --> Service
        Service --> Controller
        Controller --> Response[JSON Response]
    end
Loading

Backend Changes (Indexer)

Schema

  • New feed_event table with composite PK (txHash + logIndex)
  • Enums: feed_event_type (vote, proposal, transfer, delegation)
  • Enums: feed_event_relevance (none, low, medium, high)
  • Indexes on timestamp, type, and relevance for efficient filtering

Relevance Scoring

  • Configurable thresholds per DAO and event type
  • Computed at indexing time based on token amounts/voting power
  • Helper function computeRelevance() in utils

Event Handlers

  • voting.ts: Inserts feed events for votes and proposals
  • delegation.ts: Inserts feed events for delegations
  • transfer.ts: Inserts feed events for transfers

REST API

  • GET /feed endpoint with filters:
    • fromTimestamp, toTimestamp - time range
    • types[] - filter by event types
    • relevances[] - filter by relevance levels
    • sortOrder - asc/desc
    • limit, offset - pagination
  • Joins with related tables to return complete event data

Frontend Changes (Dashboard)

Activity Feed Feature

  • New ActivityFeed component with infinite scroll
  • Filter drawer for types and relevance levels
  • Event cards with discriminated union design:
    • Vote events: voter, voting power, proposal title, support
    • Proposal events: proposer, proposal title
    • Transfer events: from/to addresses, amount, address types
    • Delegation events: delegator, delegate, amount

UI/UX

  • Sidebar navigation item for Activity Feed
  • Responsive design matching Figma specs
  • Date grouping with "Today", "Yesterday", full dates
  • Loading states and empty states

Commits

  • feat(indexer): add activity feed feature - Backend implementation
  • feat(dashboard): add activity feed feature - Frontend implementation
  • refactor(dashboard): use drawer pattern for activity feed filters
  • refactor: remove isCex/isDex/isLending from feed event transfers
  • feat(dashboard): add Activity Feed as sidebar navigation item
  • refactor(dashboard): redesign Activity Feed to match Figma design
  • fix(dashboard): improve Activity Feed layout and spacing
  • feat(dashboard): show year in date label if not current year
  • fix(dashboard): fix tokenSymbol type error in FeedEventItem
  • refactor(indexer): move computeRelevance to utils and adjust ENS thresholds

- Add feed_event table with type, relevance, timestamp, txHash, logIndex
- Add FeedEventTypeEnum and FeedEventRelevanceEnum as shared enums
- Add RELEVANCE_THRESHOLDS config for ENS, UNI, TEST DAOs
- Create feed event records in voting, delegation, and transfer handlers
- Add REST endpoint GET /feed with filters for timestamp, type, relevance
- Add repository with join queries to fetch related vote/proposal/transfer/delegation data
- Add logIndex parameter to all DAO governor indexers for feed event tracking
- Add activity feed types (FeedEvent, FeedEventType, FeedEventRelevance)
- Create useActivityFeed hook for REST API data fetching with pagination
- Create FeedEventItem component with support for vote, proposal, transfer, delegation events
- Create FeedEventSkeleton for loading states
- Create ActivityFeedFilters for filtering by event type and relevance
- Create ActivityFeedSection as the main feature component
- Refactor ActivityFeedFilters to ActivityFeedFiltersDrawer component
- Use existing design system components: Drawer, Checkbox, RadioButton, Button, Input
- Add sort by date (newest/oldest first) with RadioButton
- Add type filters (delegation, transfer, vote, proposal) with Checkbox
- Add relevance filters (high, medium, low) with Checkbox
- Add time frame date range inputs
- Add Apply/Clear filters buttons in drawer footer
- Update ActivityFeedSection to use filter drawer with icon button trigger
- Add active filter count badge on filter button
These fields have a different semantic meaning (transaction affecting supply)
vs address type identification. Address type identification will be handled
by a separate service in the future.
- Add activityFeed to PAGES_CONSTANTS
- Create activity-feed page at /[daoId]/activity-feed
- Add Activity Feed to sidebar navigation (HeaderDAOSidebar)
- Add Activity Feed to mobile navigation (HeaderNavMobile)
- Update header with icon, title, description, and Filters button
- Group events by date (TODAY, YESTERDAY, date) with high relevance count
- Redesign FeedEventItem layout:
  - Single line action description with inline elements
  - Metadata line: Relevance Label • Event Type • Time
- Update event content format:
  - Vote: 'Actor (voting power) voted Yes/No on proposal Title'
  - Proposal: 'Actor (voting power) created the proposal Title'
  - Transfer: 'Actor transferred Amount to Recipient'
  - Delegation: 'Actor delegated/redelegated Amount to Delegate'
- Add token symbol to amounts
- Show colored relevance labels (High/Medium/Low/No Relevance)
- Remove colored circle background from event icons
- Use subtle dimmed icon color for all event types
- Reduce padding and gaps for more compact layout
- Remove bordered container around event groups
- Update skeleton to match new compact layout
Date headers now show the year for events from previous years:
- Current year: 'SUNDAY, OCT 2'
- Previous years: 'SUNDAY, OCT 2, 2024'
Use config.name (DAO name) as token symbol instead of non-existent
config.daoOverview.token.symbol property
…sholds

- Move computeRelevance function from constants.ts to utils.ts
- Update imports in event handlers (delegation, transfer, voting)
- Adjust ENS delegation thresholds: medium 10k, high 100k
- Adjust ENS vote thresholds: low 1k, medium 10k, high 100k
@vercel
Copy link

vercel bot commented Jan 21, 2026

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

Project Deployment Review Updated (UTC)
anticapture Ready Ready Preview, Comment Jan 23, 2026 2:31pm
anticapture-storybook Ready Ready Preview, Comment Jan 23, 2026 2:31pm

Request Review

@alextnetto alextnetto changed the title feat: Activity Feed for Governance Events feat: Activity Feed v1 Jan 21, 2026
@pikonha pikonha marked this pull request as draft January 21, 2026 14:44
});

return groups;
};
Copy link
Member

Choose a reason for hiding this comment

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

you're grouping events by date on the frontend? it probably should be done on the query

Comment on lines 88 to 95
const { data, error, isLoading, mutate } = useSWR(
swrKey,
() => fetchActivityFeed(daoId, { ...filters, limit, offset: 0 }),
{
revalidateOnFocus: false,
dedupingInterval: 30000,
},
);
Copy link
Member

Choose a reason for hiding this comment

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

use react-query

export const useActivityFeed = ({
daoId,
filters = {},
enabled = true,
Copy link
Member

Choose a reason for hiding this comment

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

looks like this params is not used at all

Comment on lines +60 to +66
interface PaginationInfo {
totalCount: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
currentPage: number;
itemsPerPage: number;
}
Copy link
Member

Choose a reason for hiding this comment

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

we should be using type safe queries (currently using apollo for that)

Expose activity feed endpoint through the API gateway
Use local timezone consistently for date grouping instead of mixing
local time with UTC (toISOString). This ensures events are grouped
correctly based on the user's local date.
Add USE_MOCK_DATA toggle in mockData.ts to test frontend without backend.
Includes sample transfer events with pagination support.
Replace 'Load more' button with Intersection Observer-based infinite
scroll. Starts loading 200px before reaching the bottom for smooth UX.
Use nuqs library to sync filter state with URL query parameters:
- sort: asc/desc
- types: comma-separated list (vote,proposal,transfer,delegation)
- relevances: comma-separated list (none,low,medium,high)
- from/to: date strings

Allows sharing filtered views and maintains state on refresh.
- Replace SWR with manual fetch to avoid revalidation overwriting accumulated items
- Add deduplication helper to prevent duplicate keys when appending pages
- Use ref guard to prevent multiple simultaneous fetches
- Simplify state management for more predictable behavior
Include all 4 event types with various relevance levels:
- Proposals (always high relevance)
- Votes (for, against, abstain with different voting power)
- Delegations (new, changed, with different amounts)
- Transfers (various sizes)

Uses dynamic timestamps relative to current time for realistic testing.
Replace synthetic mock data with actual API responses:
- 5 proposals (all high relevance)
- 8 votes (high, medium, low relevance)
- 8 delegations (medium, low relevance)
- 10 transfers (low relevance)

Uses real transaction hashes, addresses, and timestamps from ENS DAO.
Copy link
Contributor

@brunod-e brunod-e left a comment

Choose a reason for hiding this comment

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

}: ActivityFeedFiltersDrawerProps) => {
const { isMobile } = useScreenSize();
const [localFilters, setLocalFilters] =
useState<ActivityFeedFilterState>(filters);
Copy link
Contributor

Choose a reason for hiding this comment

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

you can use useQueryState from nuqs so your filters will be in the url and it could be shareable

const hasRedelegation =
event.delegation.previousDelegate &&
event.delegation.previousDelegate !==
"0x0000000000000000000000000000000000000000";
Copy link
Contributor

Choose a reason for hiding this comment

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

use zeroAddress from viem

interface PaginationInfo {
totalCount: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

there is no reason to have previous page if you are using infinite scroll

- Adjusted spacing in ActivityFeedSection for improved layout.
- Updated FeedEventItem to use consistent styling and improved readability.
- Enhanced ActivityFeedFilters to apply filters and close drawer on reset.
- Added isCollapsed prop to HeaderDAOSidebar for better state management.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants