Skip to content

feat(dashboard): enable the Tornado Cash whitelabel with full governance - #2136

Merged
brunod-e merged 8 commits into
devfrom
feat/tornado-whitelabel
Aug 20, 2026
Merged

feat(dashboard): enable the Tornado Cash whitelabel with full governance#2136
brunod-e merged 8 commits into
devfrom
feat/tornado-whitelabel

Conversation

@brunod-e

Copy link
Copy Markdown
Collaborator

What

  • tornado.gov.blockful.io now resolves to the whitelabel shell (hostnames + whitelabel in dao-config), with govPlatform pointing at Anticapture
  • TORN joins canCreateProposalForDao with a Tornado-specific propose path: Tornado proposals are pre-deployed contracts that governance delegatecalls on execution, so submitProposalRequest sends propose(target, description) using the single action's contract address as the proposal contract, and rejects multi-action or ETH-sending drafts before they reach the wallet
  • submitGovernanceAction gains a TORN execute(proposalId) path (no queue step: the timelock is built into the governance contract)
  • Bug fix: voting was broken for any TORN account without delegators. castDelegatedVote reverts on-chain with "Can not be empty" for an empty from list (the code comment claimed empty works). Those voters now go through castVote(proposalId, support).

Validation

Full lifecycle PASS on an anvil mainnet fork via the governance fork test (#2133), running the dashboard's own transaction code with impersonated locked-balance holders:

PASS delegates: 50 delegates with power on the fork, 50 above the 1,000 threshold
PASS propose: proposal 68 created via submitProposalRequest (propose(target, description))
PASS state:pending, state:active (75s voting delay)
PASS vote: 2 for (541,375), 1 against via voteOnProposal (no abstain on Tornado)
PASS tallies: on-chain tallies match the cast voting power exactly
PASS state:succeeded (Timelocked)
PASS execute: proposal Executed via executeProposal after the 2-day execution delay

The "Can not be empty" revert was reproduced on the fork before the fix and is covered by a new unit test; the Tornado propose path has its own unit tests (single-action guard, ETH guard, event ABI matching the indexer's). Typecheck, lint and the create-proposal + governance + dao-config suites (415 tests) pass.

Notes

  • The create-proposal form stays generic; for TORN the expected usage is a single custom action pointing at the deployed proposal contract. Publishing anything else fails fast with a clear error.
  • The fork-test harness gets its TORN config flipped to the dashboard propose/execute paths in a follow-up commit on feat(dashboard): anvil fork test for the full governance lifecycle #2133 once this merges.

🤖 Generated with Claude Code

Serve the Tornado Cash whitelabel from tornado.gov.blockful.io and let
TORN join ENS and Shutter as a DAO you can draft, publish, vote on and
execute proposals for.

Tornado governance is custom: proposals are pre-deployed contracts that
the governance delegatecalls on execution, so proposal creation gets a
propose(target, description) path that takes the single action's contract
address as the proposal contract and rejects multi-action or ETH-sending
drafts before they reach the wallet. Execution gets an execute(proposalId)
path; there is no queue step since the timelock is built into the
governance contract.

Voting is fixed for accounts without delegators: castDelegatedVote
reverts on-chain with "Can not be empty" for an empty from list, so those
voters now go through castVote(proposalId, support) instead.

Validated end to end on an anvil mainnet fork with the governance fork
test: propose via submitProposalRequest, for and against votes from
impersonated locked-balance holders with exact tally matches, then
execute via executeProposal after the 2-day execution delay.

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

vercel Bot commented Aug 18, 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 Aug 20, 2026 12:07am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
anticapture Ignored Ignored Aug 20, 2026 12:07am

Request Review

Copy link
Copy Markdown
Collaborator

🎨 UI Review

Automated review · Figma: not applicable — this PR wires an already-shipped, already-designed whitelabel governance feature (the one ENS, Shutter, Gitcoin, and Compound already use) to a new DAO; no new screens or copy to check against a mock · Preview: the anticapture (dashboard) Vercel deployment is Ignored for this branch — only the Storybook project deployed, so no live preview to click through
ℹ️ Reviewed by comparing this DAO's config wiring against the four DAOs already enabled for full whitelabel governance (ENS, Shutter, Gitcoin, Compound), which share the exact same components.


Tornado Cash whitelabel enablement

Question for author

  • TORN's whitelabel config is set to an empty object ({}) — same gap as Gitcoin's (feat(dashboard): enable the Gitcoin whitelabel with full governance #2134) and Compound's (feat(dashboard): enable the Compound whitelabel with full governance #2135) PRs — while ENS and Shutter both set requestFeatureLink to a ClickUp form URL. Per WhitelabelConfig's own doc comment ("the button is hidden when absent"), this won't break anything — the "Request a feature" button in WhitelabelShell.tsx/WhitelabelSidebar.tsx will simply not render for Tornado visitors — but every other whitelabel DAO offers this CTA, so confirm the omission is intentional (no ClickUp form set up yet for Tornado) rather than a copy-paste miss. (apps/dashboard/shared/dao-config/torn.ts)

Validated — no change needed

  • hostnames: ["tornado.gov.blockful.io"] follows the exact <dao>.gov.blockful.io pattern used by ens.ts/shu.ts/gtc.ts/comp.ts.
  • govPlatform now points at toAbsoluteUrl("/torn/proposals/") with name: "Anticapture" — matches how ENS, Shutter, Gitcoin, and Compound replaced their old external governance link when they got full governance.
  • canCreateProposalForDao gains DaoIdEnum.TORN — the single gate controlling whether the Create Proposal UI is reachable for a DAO — wired the same way ENS/Shutter/Gitcoin/Compound were.

Out of scope for this review (code, not UI)

  • The Tornado-specific propose/vote/execute transaction logic (submitProposalRequest.ts, submitGovernanceAction.ts, voteOnProposal.ts) and the castVote bug fix don't render anything different in the UI — they're covered by the author's own unit tests and the anvil fork validation described in the PR. No UI surface to review there.

Findings: 1 total — 0 must-fix, 1 question for author.


Generated by Claude Code

@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: ae5520130c

ℹ️ 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/features/create-proposal/utils/submitProposalRequest.test.ts Outdated
@railway-app

railway-app Bot commented Aug 18, 2026

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
loki ✅ Success (View Logs) Aug 20, 2026 at 12:07 am
grafana ✅ Success (View Logs) Web Aug 20, 2026 at 12:07 am
prometheus ✅ Success (View Logs) Aug 20, 2026 at 12:06 am
alertmanager ✅ Success (View Logs) Web Aug 20, 2026 at 12:06 am
gateful ✅ Success (View Logs) Web Aug 20, 2026 at 12:06 am
authful ✅ Success (View Logs) Web Aug 20, 2026 at 12:06 am
otelcol ✅ Success (View Logs) Aug 20, 2026 at 12:06 am
tempo ✅ Success (View Logs) Aug 20, 2026 at 12:06 am
gitcoin-indexer-offchain ✅ Success (View Logs) Aug 19, 2026 at 12:18 pm
uniswap-indexer-offchain ✅ Success (View Logs) Aug 19, 2026 at 12:10 pm
shutter-indexer-offchain ✅ Success (View Logs) Aug 19, 2026 at 12:10 pm
compound-indexer-offchain ✅ Success (View Logs) Aug 19, 2026 at 12:09 pm
ens-indexer-offchain ✅ Success (View Logs) Aug 19, 2026 at 12:09 pm
docs ✅ Success (View Logs) Web Aug 19, 2026 at 2:24 am
uniswap-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:23 am
obol-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:21 am
nouns-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:21 am
lil-nouns-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:19 am
scroll-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:16 am
fluid-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:15 am
shutter-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:14 am
mcp ✅ Success (View Logs) Web Aug 19, 2026 at 2:03 am
aave-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:01 am
ens-indexer ✅ Success (View Logs) Aug 19, 2026 at 2:01 am
compound-indexer ✅ Success (View Logs) Aug 19, 2026 at 1:59 am
tornado-indexer ✅ Success (View Logs) Aug 19, 2026 at 1:59 am
gitcoin-indexer ✅ Success (View Logs) Aug 19, 2026 at 1:59 am
tornado-api ✅ Success (View Logs) Aug 19, 2026 at 1:21 am
fluid-api ✅ Success (View Logs) Aug 19, 2026 at 1:21 am
compound-api ✅ Success (View Logs) Aug 19, 2026 at 1:21 am
ens-api ✅ Success (View Logs) Aug 19, 2026 at 1:21 am
obol-api ✅ Success (View Logs) Aug 19, 2026 at 1:21 am
address-enrichment ✅ Success (View Logs) Web Aug 19, 2026 at 1:21 am
shutter-api ✅ Success (View Logs) Aug 19, 2026 at 1:21 am
aave-api ✅ Success (View Logs) Aug 19, 2026 at 1:20 am
uniswap-api ✅ Success (View Logs) Aug 19, 2026 at 1:20 am
lil-nouns-api ✅ Success (View Logs) Aug 19, 2026 at 1:20 am
gitcoin-api ✅ Success (View Logs) Aug 19, 2026 at 1:20 am
nouns-api ✅ Success (View Logs) Aug 19, 2026 at 1:20 am
ens-relayer ✅ Success (View Logs) Aug 19, 2026 at 1:19 am
erpc ✅ Success (View Logs) Aug 19, 2026 at 1:19 am
user-api ✅ Success (View Logs) Web Aug 19, 2026 at 1:19 am
nodeful ✅ Success (View Logs) Aug 19, 2026 at 1:18 am
scroll-api ✅ Success (View Logs) Aug 19, 2026 at 1:17 am

…posals

Address Codex review: a transfer or arbitrary custom action would create
a Tornado proposal whose delegatecalled execution does nothing the DAO
reviewed, so publishing now requires the single action to be exactly an
executeProposal() call on the pre-deployed proposal contract and rejects
anything else before the wallet. Also replace the unsafe unknown cast in
the test's contract-writer fake with a properly typed mock.

Co-Authored-By: Claude Fable 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: 564725aff5

ℹ️ 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/features/governance/utils/submitGovernanceAction.ts
Comment thread apps/dashboard/features/governance/utils/voteOnProposal.test.ts Outdated
…test fixtures

Hide the Execute button for TORN proposals while they are queued: Tornado
proposals stay timelocked for two days after passing and execute(proposalId)
reverts until the API advances them to pending_execution, mirroring the
existing SHU exclusion in both action-button predicates.

Rebuild the voteOnProposal tests on a real viem wallet client over a fake
JSON-RPC transport, asserting on the encoded calldata, so the fixtures
satisfy the genuine Account and WalletClient contracts with no casts
through unknown anywhere in the file.

Co-Authored-By: Claude Fable 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: 0c66528d08

ℹ️ 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/features/governance/utils/voteOnProposal.ts
…solved

Address Codex review: a failed delegators request left an empty list that
read as a verified solo voter, so the vote fell back to castVote and
silently dropped the delegated voting power shown for the account. The
voting modal now disables submission and bails out of handleSubmit while
the TORN delegator query is loading or errored, and passes the account as
a typed json-rpc account instead of casting the address through unknown.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 0639c48ae8

ℹ️ 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".

# Conflicts:
#	apps/dashboard/features/create-proposal/constants.ts
@railway-app
railway-app Bot temporarily deployed to anticapture-infra / anticapture-pr-2136 August 19, 2026 22:42 Destroyed
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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

# Conflicts:
#	apps/dashboard/features/create-proposal/constants.ts

@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: ddd22fb6ae

ℹ️ 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".

@railway-app
railway-app Bot temporarily deployed to anticapture-infra / anticapture-pr-2136 August 20, 2026 00:06 Destroyed

@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: 93d6e6885f

ℹ️ 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 on lines +153 to +156
const isTornDelegatorListIncomplete =
isLoadingTornDelegators ||
isFetchingMoreTornDelegators ||
!!hasMoreTornDelegators;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make delegator pagination deterministic

When a TORN voter has more than 1,000 delegators and multiple delegators have the same amount, this can mark the list complete while still missing addresses. The API applies offset pagination after ordering only by the aggregate amount (apps/api/src/repositories/delegations/delegators.ts:27-51), so tied rows can move between requests; getNextPageParam counts raw rows, while useDelegators and this modal deduplicate repeated addresses. The new pagination loop can therefore reach hasMoreTornDelegators === false after loading totalCount raw rows but fewer unique delegators, and castDelegatedVote silently omits the skipped voting power. Add a stable address tie-breaker to the API ordering or verify the unique-address count against totalCount before enabling submission.

Useful? React with 👍 / 👎.

@brunod-e
brunod-e merged commit dfb3984 into dev Aug 20, 2026
60 checks passed
@brunod-e
brunod-e deleted the feat/tornado-whitelabel branch August 20, 2026 00:21
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.

2 participants