Skip to content
Open

Dev #2104

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
7d4c104
feat(dashboard): add data inconsistency reports
pikonha Jul 22, 2026
4e01543
fix(dashboard): preserve report route context
pikonha Jul 22, 2026
4e59732
feat: holders and delegates v3 (DEV-562, DEV-476)
brunod-e Jul 23, 2026
96c17b7
chore: make report incorrect data a help dropdown option
pikonha Jul 24, 2026
c4a1424
Merge branch 'dev' into feat/holders-delegates-v3
brunod-e Jul 27, 2026
6cf03ef
fix(dashboard): cache address enrichment to stop refetch storm
brunod-e Jul 27, 2026
bd783fa
feat(dashboard): refine holders & delegates drawer UI
brunod-e Jul 27, 2026
20c79ff
fix(dashboard): center delegate avatar and paint table row borders
brunod-e Jul 27, 2026
9e10022
fix(dashboard): match Figma for delegate cell and row borders
brunod-e Jul 27, 2026
3af2f54
fix(dashboard): six holders and delegates fixes, plus relevance=ALL
brunod-e Jul 27, 2026
367a581
style(dashboard): stakeholders title, For Rate label and column align…
brunod-e Jul 27, 2026
5004bf8
fix(dashboard): truncate long names in drawer tables, center VP Impact
brunod-e Jul 27, 2026
c5e06c8
fix(dashboard): keep feed relevance narrowing independent of the gene…
brunod-e Jul 27, 2026
d28e0ac
fix: address self-review findings across the holders and delegates mo…
brunod-e Jul 27, 2026
30daf70
refactor: drop the proposal final result filter from this PR
brunod-e Jul 27, 2026
37e4fcc
fix(dashboard): honor the period end in activity, and keep the holdin…
brunod-e Jul 27, 2026
26511d4
fix: address codex and isadorable review feedback
brunod-e Jul 27, 2026
9406d31
Merge branch 'dev' into feat/holders-delegates-v3
pikonha Jul 28, 2026
34079fa
fix: address the remaining codex review round on PR 2084
brunod-e Jul 29, 2026
c038ed7
Merge remote-tracking branch 'origin/feat/holders-delegates-v3' into …
brunod-e Jul 29, 2026
923d62f
fix(dashboard): preserve clicked address entity types (review #367514…
brunod-e Jul 29, 2026
60daa3c
fix(dashboard): reject invalid tab query values (review #3675140874)
brunod-e Jul 29, 2026
f812090
feat(api): expose every delegatee of a split delegation feed event (r…
brunod-e Jul 29, 2026
72a6302
fix(dashboard): keep the dust toggle reachable on empty interaction p…
brunod-e Jul 29, 2026
40f4414
fix(dashboard): scope the drawer entity override to its address (revi…
brunod-e Jul 29, 2026
23c90fe
Merge branch 'dev' into feat/holders-delegates-v3
brunod-e Jul 29, 2026
cd8d62a
chore: remove redundant code comments
brunod-e Jul 29, 2026
6b6c836
fix: align the AAVE amount filter with delegation received (codex rev…
brunod-e Jul 30, 2026
cd97976
fix: bound activity votes to the selected period, plus self-review fixes
brunod-e Jul 30, 2026
c867c85
Merge remote-tracking branch 'origin/dev' into feat/holders-delegates-v3
brunod-e Jul 30, 2026
9434352
Merge branch 'dev' into feat/holders-delegates-v3
pikonha Jul 30, 2026
5373f2e
fix: h&d v3 ui
pikonha Jul 30, 2026
274dc96
fix(dashboard): stop rendering a double ellipsis on shortened addresses
brunod-e Jul 30, 2026
e5d9f06
chore: drop committed browser-automation artifacts
brunod-e Jul 30, 2026
31c2b1b
fix(dashboard): keep AAVE on its specialized stakeholders page
brunod-e Jul 30, 2026
0cac05f
fix(dashboard): validate the period and feed relevance query params
brunod-e Jul 30, 2026
d7d1def
fix(api): bound activity votes to the start of the selected period
brunod-e Jul 30, 2026
dd95b49
feat(dashboard): renew ENS security council term, add Compound propos…
alextnetto Jul 30, 2026
a219ff4
fix(dashboard): re-point drawers that own their address in local state
brunod-e Jul 31, 2026
b15001f
fix(dashboard): sort Top Interactions by the value the column shows
brunod-e Jul 31, 2026
304ef48
fix(dashboard): point the whitelabel nav at the stakeholders page
brunod-e Jul 31, 2026
5ac3925
Merge branch 'dev' into feat/holders-delegates-v3
brunod-e Jul 31, 2026
fe23bc1
Merge branch 'dev' into feat/holders-delegates-v3
brunod-e Jul 31, 2026
899fbe6
fix(dashboard): call Compound's veto entity the Proposal Guardian con…
alextnetto Jul 31, 2026
db75781
fix(dashboard): remove 10k character limit on proposal description
alextnetto Aug 1, 2026
ea8a749
Merge branch 'dev' into feat/holders-delegates-v3
brunod-e Aug 3, 2026
3cefcbc
fix(api): bound activity windows on voting start, not proposal creation
brunod-e Aug 3, 2026
e27c8e3
fix(api): classify unlock contracts as vesting by address
brunod-e Aug 3, 2026
8b81fd6
refactor: report wrong data panel button
pikonha Aug 3, 2026
0338426
Merge pull request #2100 from blockful/main
pikonha Aug 3, 2026
7e5cdd1
fix(dashboard): address report PR review comments
pikonha Aug 3, 2026
8798616
Merge branch 'dev' into chore/clickup-user-reports-list
pikonha Aug 3, 2026
822a451
fix(dashboard): close report rate-limit race between concurrent requests
pikonha Aug 3, 2026
a03a953
fix: remaining issues with h&d
brunod-e Aug 3, 2026
64a5ec6
fix(dashboard): send an explicit all-time floor for the MAX period
brunod-e Aug 4, 2026
e52f2d1
Merge pull request #2084 from blockful/feat/holders-delegates-v3
pikonha Aug 4, 2026
6b781d9
Merge branch 'dev' into chore/clickup-user-reports-list
pikonha Aug 4, 2026
b694ba2
fix(dashboard): address report review feedback
pikonha Aug 4, 2026
53b82c1
fix(dashboard): update ENS security council risk copy to the new term
alextnetto Aug 4, 2026
e0fc7a6
fix(dashboard): cap proposal body at the drafts endpoint's 100k ceiling
alextnetto Aug 4, 2026
293a887
Merge pull request #2097 from blockful/feat/security-council-renewal-…
pikonha Aug 4, 2026
d19b3d8
Merge pull request #2101 from blockful/proposal-description-limit-476c45
pikonha Aug 4, 2026
9c18755
Merge pull request #2083 from blockful/chore/clickup-user-reports-list
pikonha Aug 4, 2026
4a291c6
chore: version packages
github-actions[bot] Aug 4, 2026
acb1ca7
fix: comments
pikonha Aug 4, 2026
a1105b3
Merge pull request #2103 from blockful/changeset-release/dev
pikonha Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions .changeset/gateful-upstream-commit-gate.md

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,7 @@ apps/gateful/openapi/gateful.json
# Local planning/spec artifacts (superpowers) — not part of the repo
docs/superpowers/

# Browser-automation debugging output (page snapshots, screenshots)
.playwright-mcp/

.work/
39 changes: 39 additions & 0 deletions apps/address-enrichment/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# @anticapture/address-enrichment

## 1.1.2

### Patch Changes

- [#2098](https://github.com/blockful/anticapture/pull/2098) [`002b33c`](https://github.com/blockful/anticapture/commit/002b33ca39cd7e2aaa5373732fe02aa09a25dd93) Thanks [@pikonha](https://github.com/pikonha)! - Stop the production dashboard build from generating its SDK against the
previous release's OpenAPI spec.

Gateful merges the DAO APIs' specs into `/docs/json` on every request, so the
deploy gate waiting for gateful's own commit proved nothing about the schemas
it would serve: on [#2093](https://github.com/blockful/anticapture/issues/2093) gateful reported the new commit at 14:33:54, codegen
read the spec at 14:34:23, and `ens-api` only came up at 14:34:34 — the
dashboard build failed on a field the API hadn't started advertising yet.

Every service whose OpenAPI gateful merges into `/docs/json` — the DAO APIs,
the relayer and address enrichment — now reports its running commit on
`/health`, and gateful passes it through as `upstreams.<name>.commit` alongside
an `upstreams.<name>.kind`. `scripts/wait-for-gateful.mjs` then holds the
deploy until each of them reports a commit listed for its kind in
`EXPECTED_UPSTREAM_SHAS`: the last commit that touched the paths Railway
watches to rebuild that service, plus everything after it.

Expressing it as "that commit or newer" per service, rather than "does this
push change the API", keeps the gate correct when a push that leaves a service
alone supersedes an in-flight push that changed it, and when a push carries
more than one commit. An upstream reporting no commit counts as stale — the
previous release is still answering — which cannot deadlock, because teaching a
service to report its commit necessarily touches its own watched paths.
Authful contributes no merged schemas and only has to be reachable.

`@anticapture/client#codegen` is also no longer cached by turbo: its real input
is a live URL no hash can see, so the poisoned output above was replayed on
every retry of that commit and no re-run could ever fix it. `#build` had to go
with it: tsup runs with `dts: true`, so that same unhashable spec is compiled
into `dist`, and a retry that re-ran codegen would restore the stale `dist`
straight over the freshly generated output. Declaring `generated/**` as an
input does not close this — turbo hashes inputs before the run, and the
directory is gitignored, so on a fresh checkout it is still empty at the moment
the hash is taken.

## 1.1.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion apps/address-enrichment/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anticapture/address-enrichment",
"version": "1.1.1",
"version": "1.1.2",
"private": true,
"scripts": {
"dev": "tsx watch src/index.ts",
Expand Down
103 changes: 103 additions & 0 deletions apps/api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
# @anticapture/api

## 1.8.0

### Minor Changes

- [#2084](https://github.com/blockful/anticapture/pull/2084) [`3af2f54`](https://github.com/blockful/anticapture/commit/3af2f542ad10c1e944f76510d8c65d46ab910654) Thanks [@brunod-e](https://github.com/brunod-e)! - `GET /:dao/feed/events` accepts `relevance=ALL`, which drops the value threshold and returns every event instead of only those at or above a tier. The relevance tiers are cumulative value floors (LOW already includes MEDIUM and HIGH), so there was previously no way to ask for events below the LOW floor. Omitting the param still defaults to MEDIUM, so existing consumers are unaffected.

- [#2084](https://github.com/blockful/anticapture/pull/2084) [`4e59732`](https://github.com/blockful/anticapture/commit/4e59732daf40b800986ab9ec42a10127b29465f4) Thanks [@brunod-e](https://github.com/brunod-e)! - Holders & Delegates v3 (DEV-562, DEV-476)

API: new endpoints backing the module. `GET /:dao/voting-powers/inactive-summary`
(delegated VP parked with inactive delegates), `GET /:dao/accounts/:address/delegators/historical`
(former delegators with VP impact, start/end and redelegation target), and
`GET /:dao/addresses/labels` (per-DAO treasury/vesting labels, where an unlock
contract whose label does not mention vesting is classified by address so the
dashboard can still relabel its transfers as a vesting unlock; contracts whose
outgoing transfers are not unlocks, such as airdrop distributors and staking
vaults, stay out). Adds an optional
`address` filter to `GET /:dao/feed/events`, and an optional `toDate` upper bound
to `GET /:dao/proposals-activity` so a bounded period counts only the proposals
inside it. That upper bound is keyed on when a proposal's voting opens (creation
plus the DAO voting delay), not on when it was created, so on DAOs with a
non-zero voting delay a proposal created inside the period whose voting only
opens after it no longer counts: no vote could land in the window, and counting
it marked delegates inactive on proposals they could not yet vote on. On
`GET /:dao/voting-powers/inactive-summary` that also keeps `totalProposals` at
zero when the window holds nothing votable, instead of reporting every delegate
as inactive. Both `GET /:dao/proposals-activity` and
`GET /:dao/voting-powers/inactive-summary` also bound the vote by `toDate`: a
proposal that opens near the end of the period stays votable after it, so a vote
cast later no longer counts as activity inside a period that closed before the
vote existed. The proposal is still listed, with no vote attached. The same bound
applies at the other end: a proposal whose voting period overlaps `fromDate` is
in scope, but a vote cast on it before that date happened outside the period and
no longer counts as activity inside it either. On
`GET /:dao/accounts/:address/delegators/historical`, `amount` reports the voting
power the queried address actually lost at the move away rather than the value
stored on the last delegation event: balances that move while a delegation stands
write no delegation row, so that value is a stale snapshot, and the share it
represented is instead applied to the balance the move-away event carries. Full
delegation therefore reports the whole balance moved, and partial delegation
(SCR) keeps its fraction rather than claiming the sibling delegates' part. On AAVE, `fromValue`/`toValue` on `GET /:dao/voting-powers` now filter the
delegated voting power alone instead of the combined total (delegated power plus
the account's own balance), matching both the `votingPower` ordering on the same
endpoint and every other DAO's behavior, so the range a client asks for matches
the delegation figure it renders.
Feed DELEGATION metadata gains an optional `delegatees` array of
`{ delegate, amount }`, present only when the source event split voting power
across more than one delegatee (partial delegation, as SCR does), ordered by
delegate address ascending; `delegate`/`amount` stay as they were and describe
the primary delegatee, so existing consumers are unaffected. Gateful re-exposes
the expanded surface through its aggregated OpenAPI spec (no gateway code
change).

Dashboard: value min/max filters on the Delegates and Token Holders tables;
Delegates as the default tab and the sidebar renamed to "Stakeholders"; larger
rows with bottom borders and a continuous activity ring; voting power shown as a
percentage of quorum; inactive-delegate flagging and 0/0 activity states
("Inactive" / "No proposals" / "Never voted"); the inactive-VP alert banner on
Token Holders; clickable addresses that re-point the drawer everywhere; a
per-address Activity tab in the drawer, on the DAOs that serve the activity
feed; Buy/Sell relabeled to In / Out / Vested;
a dust badge and "Hide dust" switch on Top Interactions; a "Filter low importance"
toggle and "All time" range on Voting Power History; a MAX option and a custom
calendar range on the time selector, single days included; and a Former
Delegators view in the
delegate profile.

### Patch Changes

- [#2098](https://github.com/blockful/anticapture/pull/2098) [`002b33c`](https://github.com/blockful/anticapture/commit/002b33ca39cd7e2aaa5373732fe02aa09a25dd93) Thanks [@pikonha](https://github.com/pikonha)! - Stop the production dashboard build from generating its SDK against the
previous release's OpenAPI spec.

Gateful merges the DAO APIs' specs into `/docs/json` on every request, so the
deploy gate waiting for gateful's own commit proved nothing about the schemas
it would serve: on [#2093](https://github.com/blockful/anticapture/issues/2093) gateful reported the new commit at 14:33:54, codegen
read the spec at 14:34:23, and `ens-api` only came up at 14:34:34 — the
dashboard build failed on a field the API hadn't started advertising yet.

Every service whose OpenAPI gateful merges into `/docs/json` — the DAO APIs,
the relayer and address enrichment — now reports its running commit on
`/health`, and gateful passes it through as `upstreams.<name>.commit` alongside
an `upstreams.<name>.kind`. `scripts/wait-for-gateful.mjs` then holds the
deploy until each of them reports a commit listed for its kind in
`EXPECTED_UPSTREAM_SHAS`: the last commit that touched the paths Railway
watches to rebuild that service, plus everything after it.

Expressing it as "that commit or newer" per service, rather than "does this
push change the API", keeps the gate correct when a push that leaves a service
alone supersedes an in-flight push that changed it, and when a push carries
more than one commit. An upstream reporting no commit counts as stale — the
previous release is still answering — which cannot deadlock, because teaching a
service to report its commit necessarily touches its own watched paths.
Authful contributes no merged schemas and only has to be reachable.

`@anticapture/client#codegen` is also no longer cached by turbo: its real input
is a live URL no hash can see, so the poisoned output above was replayed on
every retry of that commit and no re-run could ever fix it. `#build` had to go
with it: tsup runs with `dts: true`, so that same unhashable spec is compiled
into `dist`, and a retry that re-ran codegen would restore the stale `dist`
straight over the freshly generated output. Declaring `generated/**` as an
input does not close this — turbo hashes inputs before the run, and the
directory is gitignored, so on a fresh checkout it is still empty at the moment
the hash is taken.

## 1.7.0

### Minor Changes
Expand Down
3 changes: 3 additions & 0 deletions apps/api/cmd/aave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fromZodError } from "zod-validation-error";
import { DaoCache } from "@/cache/dao-cache";
import {
accountBalances,
addressLabels,
dao,
historicalBalances,
historicalVotingPower,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
import { AAVEVotingPowerRepository } from "@/repositories/voting-power/aave";
import {
AccountBalanceService,
AddressLabelsService,
DaoService,
HealthService,
HistoricalBalancesService,
Expand Down Expand Up @@ -218,6 +220,7 @@ transfers(
),
);
dao(app, daoService);
addressLabels(app, new AddressLabelsService(env.DAO_ID));
docs(app);

serve(
Expand Down
29 changes: 29 additions & 0 deletions apps/api/cmd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
accountBalanceVariations,
accountBalances,
accountInteractions,
addressLabels,
dao,
delegationPercentage,
governanceActivity,
Expand All @@ -32,7 +33,9 @@ import {
votingPowers,
delegations,
delegators,
formerDelegators,
historicalDelegations,
inactiveVotingPowerSummary,
votes,
offchainProposals,
offchainVotes,
Expand Down Expand Up @@ -71,7 +74,9 @@ import {
VotingPowerRepository,
DelegationsRepository,
DelegatorsRepository,
FormerDelegatorsRepository,
HistoricalDelegationsRepository,
InactiveVotingPowerSummaryRepository,
VotesRepository,
FeedRepository,
AccountBalanceQueryFragments,
Expand All @@ -81,6 +86,7 @@ import {
} from "@/repositories";
import {
AccountBalanceService,
AddressLabelsService,
BalanceVariationsService,
CoingeckoService,
DaoService,
Expand All @@ -98,6 +104,8 @@ import {
HistoricalDelegationsService,
DelegationsService,
DelegatorsService,
FormerDelegatorsService,
InactiveVotingPowerSummaryService,
VotesService,
FeedService,
OffchainProposalsService,
Expand Down Expand Up @@ -289,6 +297,14 @@ delegators(
new DelegatorsService(wrapWithTracing(new DelegatorsRepository(pgClient))),
),
);
formerDelegators(
app,
wrapWithTracing(
new FormerDelegatorsService(
wrapWithTracing(new FormerDelegatorsRepository(pgClient)),
),
),
);

const treasuryService = wrapWithTracing(
createTreasuryService(
Expand Down Expand Up @@ -340,7 +356,20 @@ lastUpdate(app, pgClient);
delegationPercentage(app, delegationPercentageService);
historicalVotingPower(app, votingPowerService);
votingPowerVariations(app, votingPowerService);
// Registered before votingPowers so the static /voting-powers/inactive-summary
// path is matched ahead of the /voting-powers/{address} param route.
inactiveVotingPowerSummary(
app,
wrapWithTracing(
new InactiveVotingPowerSummaryService(
wrapWithTracing(new InactiveVotingPowerSummaryRepository(pgClient)),
daoClient,
blockTime,
),
),
);
votingPowers(app, votingPowerService);
addressLabels(app, new AddressLabelsService(env.DAO_ID));
accountBalanceVariations(app, balanceVariationsService);
accountBalances(app, env.DAO_ID, accountBalanceService);
accountInteractions(
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anticapture/api",
"version": "1.7.0",
"version": "1.8.0",
"private": true,
"main": "dist/index.js",
"type": "module",
Expand Down
34 changes: 34 additions & 0 deletions apps/api/src/controllers/addresses/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { OpenAPIHono as Hono, createRoute } from "@hono/zod-openapi";

import { AddressLabelsResponseSchema } from "@/mappers";
import { setCacheControl } from "@/middlewares";
import { AddressLabelsService } from "@/services";

export function addressLabels(app: Hono, service: AddressLabelsService) {
app.openapi(
createRoute({
method: "get",
operationId: "addressLabels",
path: "/addresses/labels",
summary: "Get labeled addresses",
description:
"Returns the DAO's known labeled addresses (treasury and vesting contracts)",
tags: ["addresses"],
middleware: [setCacheControl(60)],
responses: {
200: {
description: "Successfully retrieved labeled addresses",
content: {
"application/json": {
schema: AddressLabelsResponseSchema,
},
},
},
},
}),
(context) => {
const result = service.getAddressLabels();
return context.json(AddressLabelsResponseSchema.parse(result), 200);
},
);
}
Loading
Loading