Skip to content

feat: holders and delegates v3 (DEV-562, DEV-476) - #2084

Open
brunod-e wants to merge 39 commits into
devfrom
feat/holders-delegates-v3
Open

feat: holders and delegates v3 (DEV-562, DEV-476)#2084
brunod-e wants to merge 39 commits into
devfrom
feat/holders-delegates-v3

Conversation

@brunod-e

@brunod-e brunod-e commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

What

Holders & Delegates v3. Closes DEV-562 and DEV-476.

Dashboard

  • Min/max value filters on the Delegates (voting power) and Token Holders (balance) tables (DEV-476).
  • Delegates is the default tab; sidebar and page title read "Stakeholders".
  • Main table: default row height, row bottom borders (new withRowBorders on the design-system Table), continuous activity ring, voting power as a percentage of quorum (red at 100%+, yellow at 50%+).
  • Inactive-delegate flag and 0/0 activity states (Inactive / No proposals / Never voted), respecting the selected period.
  • Inactive-VP alert banner on Token Holders, using the design-system InlineAlert; "Voted X/Y (Inactive)" under the delegate name.
  • Clickable addresses that re-point the drawer everywhere; per-address Activity tab in the drawer.
  • Balance History: Buy/Sell relabeled to In / Out / Vesting (vesting resolved through the labels endpoint).
  • Top Interactions: Dust badge (< $1 at spot price) and a "Hide dust" switch (new design-system Switch); Total Interactions shown as a USD value.
  • Voting Power History: "Filter low importance" (default on), sitting on the CSV row.
  • Time selector: MAX option and a custom calendar range (new SwitcherDateRange); "Max available data" replaces "All time".
  • Delegate drawer tabs aligned to Figma (Voting Power, Delegation History); Former Delegators view with the VP-impact transition; both variation charts show their net change value in the header.
  • Design-system SegmentedControl for the current/former delegators view and for the Activity feed filters.

API

  • New endpoints: GET /:dao/voting-powers/inactive-summary, GET /:dao/accounts/:address/delegators/historical, GET /:dao/addresses/labels.
  • GET /:dao/feed/events gains an optional address filter (matches the address in any role, case-insensitive) and accepts relevance=ALL, which drops the value floor. Omitting relevance still defaults to MEDIUM, so existing consumers are unaffected.
  • Gateful re-exposes the expanded surface through its aggregated OpenAPI spec (no gateway code change).

Testing

  • API: typecheck and lint clean, 1045 unit tests pass.
  • Dashboard: typecheck and lint clean, 225 tests pass.
  • Verified the address filter end to end against a local API + Gateful with real ENS data: unfiltered totalCount 8729 versus 1980 for a single address, and every returned event involves that address.
  • Measured the delegate cell alignment, the table row borders and the long-name truncation in the rendered page rather than eyeballing them, including a box-model repro for the truncation chain.

Self-review

Ran a multi-dimension review over the whole diff (API logic, API contract, data fetching, dashboard computation, design system and accessibility, tests, conventions) with an adversarial pass that tried to refute each finding. What survived and got fixed in this PR:

  • Unbounded retry loop. useDelegatesActivity only recorded addresses whose fetch succeeded. A rejected or empty response left the address selectable again the moment it left the loading set, and since the effect depends on both sets it refetched the same addresses forever while the endpoint kept failing. Fetches now settle into their own set, and one address failing no longer discards the others.
  • AAVE. Its API registers no proposal endpoints, so the shared Token Holders table fired a 404 per delegate for proposals-activity and a 400 for the inactive summary, which falls through to the /voting-powers/{address} param route. Both are now gated on the DAO exposing proposal activity.
  • Address enrichment refetch storm. useGetAddress/useGetAddresses had no stale time while the global QueryClient defaults to 0, so every avatar refetched on remount. Opening a tab fired hundreds of requests, many cancelled and refired. Now cached for 5 minutes.
  • Unstable cache key. activityFromDate was recomputed from Date.now() on every render, so it changed whenever a render crossed a second boundary and re-keyed both the activity cache and the banner query.
  • Total VP Lost summed only the pages already loaded while printing the API's count of every former delegator beside it. It now claims a total only once there is nothing left to load.
  • Inactive summary window now excludes canceled proposals, so it agrees with proposals-activity instead of flagging a delegate as inactive for skipping a vote that never happened.
  • Failed requests in the drawer Activity feed and the Former Delegators table no longer render as "nothing found"; the USD column shows a dash instead of a confident "$0" while the token price is in flight; and an address inside DrawerAddressButton no longer nests the tooltip trigger button inside the row button, which cost a second tab stop and handed the accessible name to the tooltip.

Removed from scope

The proposal Final Result filter (and the proposalStatusIn param that backed it) was pulled out of this PR. The filter matched the stored proposals_onchain.status, but the service overwrites each proposal's status at read time with a derived value, and the indexer only ever persists ACTIVE, CANCELED, EXECUTED, PENDING, QUEUED and VETOED. So DEFEATED, SUCCEEDED, NO_QUORUM, EXPIRED and PENDING_EXECUTION could never match: on ENS, "Failed" returned zero rows and zeroed all four metric cards, "Passed" silently dropped SUCCEEDED, and "Canceled" was unsatisfiable because the query already excludes canceled proposals. Making it correct means either persisting the derived status from the indexer or expressing the derivation in SQL over end block, vote tallies, quorum and timelock, which is its own task rather than a detail of this one.

Notes for review

  • The @anticapture/client SDK regenerates on the preview from the deployed Gateful spec, so the features backed by the new endpoints (inactive banner, Former Delegators, per-address Activity, Vesting labels) only show live data once this branch's API and Gateful are deployed. Local typechecks can pass or fail depending on which Gateful the codegen reached, so code should not compare against generated enum values it cannot guarantee exist.
  • Known limitations, worth a follow-up rather than a blocker: the dust filter on Top Interactions runs client-side after server pagination, so a page whose rows are all dust renders empty; toDate reaches the voting-powers query and the banner but not the per-delegate activity request, so activity is computed over fromDate..now; and the new design-system Switch still hardcodes two colors instead of semantic tokens and ships without a story.
  • Open design decisions kept as-is: the min/max filter lives in the funnel on the Voting Power / Balance headers (Figma shows only sort arrows there), and the shared drawer header uses the default EnsAvatar tag set rather than Figma's Individual/EAO badges.

🤖 Generated with Claude Code

Dashboard:
- min/max value filters on Delegates (voting power) and Token Holders (balance)
- Delegates as default tab; sidebar renamed to "Stakeholders"
- larger rows with bottom borders, continuous activity ring, VP as percent of quorum
- inactive-delegate flag and 0/0 states (Inactive / No proposals / Never voted)
- inactive-VP alert banner on Token Holders; "Voted X/Y (Inactive)" on delegate column
- clickable addresses that re-point the drawer; per-address Activity tab
- Balance History In / Out / Vesting; dust badge and Hide dust switch on Top Interactions
- VP History low-importance filter and All time; time selector MAX plus custom calendar range
- delegate drawer tabs renamed (Voting Power, Delegation History); Former Delegators view
- proposal final-result filter on the votes tab

API:
- new endpoints: voting-powers/inactive-summary, accounts/:address/delegators/historical,
  addresses/labels; address filter on feed/events; proposalStatusIn on proposals-activity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
anticapture-storybook Ready Ready Preview Jul 31, 2026 2:53pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
anticapture Ignored Ignored Jul 31, 2026 2:53pm

Request Review

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🔍 Vercel preview: https://anticapture-5ts4uxhpk-ful.vercel.app

@railway-app

railway-app Bot commented Jul 23, 2026

Copy link
Copy Markdown

🚅 Deployed to the anticapture-pr-2084 environment in anticapture-infra

Service Status Web Updated (UTC)
uniswap-indexer-offchain ✅ Success (View Logs) Jul 31, 2026 at 3:21 pm
shutter-indexer-offchain ✅ Success (View Logs) Jul 31, 2026 at 3:21 pm
ens-indexer-offchain ✅ Success (View Logs) Jul 31, 2026 at 3:21 pm
compound-indexer-offchain ✅ Success (View Logs) Jul 31, 2026 at 3:21 pm
gitcoin-indexer-offchain ✅ Success (View Logs) Jul 31, 2026 at 3:21 pm
otelcol ✅ Success (View Logs) Jul 31, 2026 at 2:52 pm
gateful ✅ Success (View Logs) Web Jul 31, 2026 at 2:52 pm
authful ✅ Success (View Logs) Web Jul 31, 2026 at 2:52 pm
grafana ✅ Success (View Logs) Web Jul 31, 2026 at 2:52 pm
prometheus ✅ Success (View Logs) Jul 31, 2026 at 2:52 pm
loki ✅ Success (View Logs) Jul 31, 2026 at 2:52 pm
alertmanager ✅ Success (View Logs) Jul 31, 2026 at 2:52 pm
tempo ✅ Success (View Logs) Jul 31, 2026 at 2:51 pm
user-api ✅ Success (View Logs) Web Jul 31, 2026 at 12:54 pm
tornado-api ✅ Success (View Logs) Jul 31, 2026 at 12:54 pm
uniswap-api ✅ Success (View Logs) Jul 31, 2026 at 12:54 pm
shutter-api ✅ Success (View Logs) Jul 31, 2026 at 12:54 pm
nouns-api ✅ Success (View Logs) Jul 31, 2026 at 12:54 pm
aave-api ✅ Success (View Logs) Jul 31, 2026 at 12:54 pm
docs ✅ Success (View Logs) Web Jul 31, 2026 at 12:54 pm
gitcoin-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
compound-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
mcp ✅ Success (View Logs) Web Jul 31, 2026 at 12:53 pm
scroll-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
fluid-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
lil-nouns-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
ens-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
obol-api ✅ Success (View Logs) Jul 31, 2026 at 12:53 pm
scroll-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:55 am
uniswap-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:54 am
shutter-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:54 am
ens-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:54 am
compound-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:54 am
tornado-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:54 am
aave-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:54 am
lil-nouns-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:53 am
fluid-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:53 am
gitcoin-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:53 am
obol-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:53 am
nouns-indexer ✅ Success (View Logs) Jul 28, 2026 at 11:52 am
erpc ✅ Success (View Logs) Web Jul 27, 2026 at 11:35 am
address-enrichment ✅ Success (View Logs) Web Jul 23, 2026 at 11:14 pm
ens-relayer ✅ Success (View Logs) Jul 23, 2026 at 11:14 pm
nodeful ✅ Success (View Logs) Jul 23, 2026 at 11:13 pm

Address enrichment (ENS, contract flag, arkham labels) had no staleTime, and
the global QueryClient defaults to 0, so every EnsAvatar/TypeCell refetched on
each remount. Table re-renders (amplified by the inactive banner and per-row
activity fetch) remounted rows and fired/canceled these requests repeatedly,
flooding the address-enrichment API on every tab open. Give the useGetAddress
and useGetAddresses calls a 5m staleTime / 30m gcTime so identical addresses
dedupe and stay cached across remounts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- votes: rename user-vote filter labels For/Against to Yes/No
- voting power: DS SegmentedControl for current/former view
- voting power: summary (Current VP / Total VP Lost) on the selector row
- former delegators: short date (Jan 3, 25), dedup VP impact when unchanged
- delegation history: net VP change on graph, low-importance toggle on CSV row
- activity: DS SegmentedControl for date and relevance
- token holders: banner uses DS InlineAlert, table fills height below it
- top interactions: hide-dust on CSV row, total as USD, Net Tokens In/Out (90D)
- balance history: net balance change value on graph
- period label: All time renamed to Max available data
- table: footerActions slot; inline alert accepts ReactNode content

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brunod-e and others added 2 commits July 27, 2026 09:57
- EnsAvatar: optional subtitle slot rendered under the name; the avatar
  stays vertically centered against the whole name + subtitle block
- token holders: delegate column renders "Voted X/Y" via the subtitle slot
  so the avatar aligns with both lines
- table: row dividers now live on the cells, since border-separate tables
  do not paint borders set on the <tr>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified against the Figma frame measured pixel by pixel, and against the
locally rendered table:

- delegate cell: avatar is vertically centered against the whole
  name + "Voted x/y" block (measured offset 0), and the subtitle starts at
  the same x as the name, as in the design
- row borders: border-separate tables never paint borders declared on the
  <tr>, and the first cell additionally cleared them on desktop, so the
  divider was missing entirely and never reached the Address column. The
  line is now drawn by a cell pseudo element spanning the full cell width,
  ignoring the horizontal padding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- address column: long arkham labels are truncated instead of overflowing. The
  chain was broken by the tooltip trigger button, an inline-block whose
  shrink-to-fit width was set by the label, so `truncate` never had a width to
  work with. Clamping the trigger fixes every table using EnsAvatar.
- avg vote timing: shows a skeleton while the per-row proposals activity loads,
  instead of the "-" it uses for delegates with no votes.
- calendar popover: uses rounded-base, so the radius follows the DS token
  (0 for Anticapture, non-zero for whitelabels) instead of a hardcoded md.
- token holders change column: right aligned, matching the delegates tab.
- balance history and voting power graphs: the heading no longer changes between
  loading and loaded states.
- drawer activity: the feed is scoped to the inspected wallet again. The address
  filter was fine; "All" omitted `relevance`, which the API reads as MEDIUM, so
  its value thresholds hid almost everything. The API now takes relevance=ALL to
  drop the threshold, and the drawer always sends the value explicitly.
- drawer activity: infinite scroll works. The observer used the viewport as root
  while the list scrolls in its own container, leaving the sentinel on the
  clipped edge and the feed stuck on page one.

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

- holders and delegates page title reads "Stakeholders", matching its nav entry
- delegate votes: the rate metric card is labelled "For Rate"
- former delegators: VP Impact header aligns left
- voting power summary: the loading skeleton aligns left with the value it
  replaces

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An ENS record can be a 42 character address plus ".eth", which overflowed the
address column and ran into the next one. DrawerAddressButton wraps the avatar
in a button, and a button is inline-block, so it was sized by its content and
the name below it never had a width to truncate against. Clamping it fixes every
drawer table that renders an address.

VP Impact is centered in both the header and the cells.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5373f2e6a3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/dashboard/next.config.ts
Comment thread .playwright-mcp/page-2026-07-30T16-55-12-835Z.yml Outdated
A shortened address is already ellipsized in the middle ("0x39441f...bE12"),
so the CSS `truncate` on the EnsAvatar name span appended a second ellipsis
whenever the cell was too narrow, showing six dots instead of three.

The name span now clips shortened addresses without an ellipsis and keeps
`text-ellipsis` for real names. The delegation history and balance history
cells stop passing a redundant `truncate` through `nameClassName`, which
would otherwise override the fix via tailwind-merge.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 274dc96dee

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

brunod-e and others added 4 commits July 30, 2026 16:13
The nine `.playwright-mcp/page-*.yml` page snapshots and the root `repro1.png`
are debugging output from a local browser session, not fixtures or tests, and
every run would add more. The output directory is ignored from now on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `/:daoId/holders-and-delegates` redirect also matches AAVE, so the dedicated
page was unreachable and AAVE users landed on the generic `[daoId]/stakeholders`
one. That page renders the shared Delegates table, which reads the combined
balance as voting power and requests `/proposals-activity`, an endpoint the AAVE
API does not register.

AAVE's page moves to a static `app/aave/stakeholders` route instead. Static
segments win over `[daoId]`, so the redirect now lands on the specialized page
and the AAVE nav entry (already pointing at `stakeholders`) resolves there too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`days` was parsed as an arbitrary string, so a stale or hand-edited `?days=foo`
reached the switcher as a selected value while the tables silently fell back to
90 days. It is now parsed as an enum over the presets plus `max`/`custom`, and
`custom` is demoted to the default unless `rangeFrom`/`rangeTo` both parse, which
is the other way the controls could disagree with the data on screen.

`feedRelevance` had the same problem with a worse ending: the API rejects
anything outside ALL/HIGH/MEDIUM/LOW with a 400, so a stale value left the
drawer's Activity tab on a permanent error state until the query string was
cleared by hand. It now falls back to ALL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both `/:dao/proposals-activity` and `/:dao/voting-powers/inactive-summary`
already ignored votes cast after the period's upper bound, but not votes cast
before its lower one. A proposal whose voting period overlaps `fromDate` is in
scope, so a vote cast on it days earlier still filled `userVote`, counted toward
"Voted X/Y", and cleared the delegate out of `inactiveDelegatedVotingPower`,
against the "no votes cast in the selected period" semantics the UI states.

Both vote queries now require `timestamp >= activityStart`. On the paginated
query the bound stays in the LEFT JOIN's ON clause, so the proposal is still
listed with no vote attached. With no `fromDate` the service passes the address's
first vote, which no vote can precede, so nothing changes for unbounded periods.

Also documents why former-delegator sequencing can rely on
`(timestamp, log_index)`: no chain indexed today has a block time under two
seconds, so two blocks cannot share a timestamp and interleave.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7d1def9ea

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Clicking an address inside the drawer only wrote `drawerAddress` to the URL, and
not every owner reads it: the DAO overview chart, the activity feed and the
proposal page hold the drawer's address in local state. In those drawers the
click updated the query string and left the visible profile on the previous
address.

The drawer now also tracks a re-point of its own, exposed to its subtree through
a context, and drops it as soon as the owner supplies a different address, which
is what already re-points the URL-driven owners. The URL write stays, so links
into a page-level drawer keep working and stay shareable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last column renders the volume in fiat but was keyed on the transfer count
it used to show, and its header read "Total Interactions". Its control never set
a sort field either, only a direction, so it reordered rows by whatever `orderBy`
happened to be active: transfer count by default, volume once the Volume filter
had been used. Same arrow, two meanings, and dollars in an order the reader
cannot see.

It is now "Total Value (USD)", keyed on volume, and its control sorts by volume,
which the endpoint already accepts. Since the fiat figure is the volume times a
single price, ordering by volume is ordering by the number on screen. The arrow
also stays neutral until the rows really are ordered by this column.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b15001f338

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/shared/utils/whitelabelNav.ts Outdated
The item reads "Stakeholders" but built its href from the legacy
`holders-and-delegates` slug. The sidebar and the mobile drawer mark the active
item by comparing the pathname to that href, so on `/whitelabel/:daoId/stakeholders`
nothing was highlighted, and clicking the item took the redirect hop first.

The legacy slug stays in the base-path candidate list, so old whitelabel URLs
keep resolving and still redirect onto the page.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe23bc1417

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

timestampColumn: string,
) {
if (activityEnd === undefined) return sql.raw("");
return sql` AND ${sql.raw(timestampColumn)} <= ${activityEnd}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use voting start for activity upper bounds

On DAOs with a non-zero voting delay, this upper bound compares the selected toDate to the proposal creation timestamp, not the time voting opens. A custom period that ends after a proposal is created but before its voting delay expires will still include that proposal with no vote attached, so Voted X/Y and the mirrored inactive-summary window can mark delegates inactive for proposals they could not yet vote on; pass the voting delay separately and require the vote-start timestamp to be within the window.

Useful? React with 👍 / 👎.

former_delegators AS (
SELECT
ls.delegator AS delegator_address,
last_event.target_value::text AS amount,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report VP lost from the move-away amount

When a delegator changes balance while still delegated here and later redelegates away, the last event that mentioned this delegate contains the old balance, while the move-away event contains the current amount actually removed. Returning last_event.target_value makes the Former Delegators table and Total VP Lost under- or over-state the VP lost after interim transfers; use the move-away value or reconstruct the target's current delegated amount at end_timestamp.

Useful? React with 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants