Skip to content

feat: integrate Tornado Cash DAO (TORN)#1783

Draft
alextnetto wants to merge 10 commits intodevfrom
feat/index-tornado-cash
Draft

feat: integrate Tornado Cash DAO (TORN)#1783
alextnetto wants to merge 10 commits intodevfrom
feat/index-tornado-cash

Conversation

@alextnetto
Copy link
Copy Markdown
Member

Summary

Full integration of Tornado Cash DAO (TORN) into the Anticapture platform — indexer, API, gateway, and dashboard.

Tornado Cash uses a custom stake-to-vote governance (not OZ Governor or Compound GovernorBravo):

  • Users lock TORN in the Governance contract for voting power (lockedBalance = voting power)
  • Binary voting (for/against, no abstain)
  • Timestamp-based governance (all timing in seconds, not blocks)
  • Built-in timelock (no separate timelock contract)
  • Governor-level delegation (Delegated/Undelegated events)

Changes

  • Indexer: Custom token handler with lock-based delegatedSupply tracking, custom governor handler with timestamp-to-block conversion, ABIs, Ponder config
  • API: TORNClient with full getProposalStatus() override using timestamp-based state machine (Pending -> Active -> Defeated/Queued -> PendingExecution -> Executed/Expired)
  • Dashboard: DAO config, icon, feature flags
  • Enum sync: TORN added to DaoIdEnum across indexer, API, and dashboard
  • INTEGRATION.md: Documents architecture, what's integrated, what's pending

Verified locally

  • Indexer starts and processes Transfer, Voted, ProposalCreated, Delegated events without errors
  • Token record created with correct supply data (~10M TORN totalSupply)
  • Proposals indexed with vote tallies matching on-chain data
  • Delegation records created from governor-level events
  • delegatedSupply tracked via lock/unlock detection (Transfer to/from governance contract)
  • RPC calls verified against local reth node (Merkle RPC blocks Tornado Cash - sanctions)

Pending / Limitations (documented in INTEGRATION.md)

  • No per-account votingPowerHistory (TORN lacks DelegateVotesChanged events)
  • No abstain votes (binary voting only)
  • Vote extension mechanism not tracked (no on-chain event for extension)
  • Staking rewards not tracked (TornadoStakingRewards contract)
  • No intermediate proposal state events (all computed at API level)

Test plan

  • Run indexer locally with DAO_ID=TORN against a non-censoring RPC - verify proposals, votes, and supply data
  • Compare indexed proposal states against on-chain state(proposalId) for all 65 proposals
  • Compare indexed delegatedSupply against TORN balance of governance contract
  • Verify API client returns correct proposal statuses via timestamp logic
  • Verify dashboard renders TORN DAO page correctly

Generated with Claude Code

alextnetto and others added 8 commits March 25, 2026 01:31
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds: enum sync step, custom ProposalCreated handler (timestamp-based),
getProposalStatus() override requirement, delegation tokenId clarification,
NonCirculatingAddresses classification, reason field handling, wiring steps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some Tornado Cash proposals use JSON format {"title":"...","description":"..."}
instead of markdown. Try JSON.parse first before falling back to markdown parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 25, 2026

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

Project Deployment Actions Updated (UTC)
anticapture Ready Ready Preview, Comment Mar 25, 2026 6:33pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
anticapture-storybook Skipped Skipped Mar 25, 2026 6:33pm

Request Review

@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Copy Markdown

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

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: 21447d37c5

ℹ️ 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 on lines +56 to +60
this.cache.votingPeriod = (await this.readContract({
abi: this.abi,
address: this.address,
functionName: "VOTING_PERIOD",
})) as bigint;
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 Return Torn voting windows in block units

TORNClient.getVotingPeriod()/getVotingDelay() currently return second-based governor constants directly, but ProposalsActivityService treats these values as blocks and multiplies them by blockTime (apps/api/src/services/proposals-activity/index.ts, lines 123-127). For TORN this inflates the activity window by ~12x on mainnet, so the proposals-activity endpoint includes stale proposals and computes incorrect vote-timing analytics. Convert these values to block units (or add a seconds-aware path) before exposing them through the DAO client contract.

Useful? React with 👍 / 👎.

description,
timestamp: event.block.timestamp,
logIndex: event.log.logIndex,
status: ProposalStatus.ACTIVE,
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 Persist Torn proposals as PENDING at creation

New Torn proposals are inserted with status: ACTIVE immediately, even though this governor has a non-zero voting delay and TORNClient.getProposalStatus() can return PENDING before start time. Because proposal listing first filters by stored DB status, requests for pending proposals will miss Torn proposals during their pre-vote window. Store the initial status as PENDING (or persist start-time metadata and adjust DB status mapping) so status-filtered queries remain correct.

Useful? React with 👍 / 👎.

@railway-app railway-app bot temporarily deployed to anticapture-infra / anticapture-pr-1783 March 25, 2026 15:40 Destroyed
@railway-app
Copy link
Copy Markdown

railway-app bot commented Mar 25, 2026

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

Service Status Web Updated (UTC)
otelcol ◻️ Removed (View Logs) Mar 25, 2026 at 5:44 pm
ens-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:42 pm
erpc ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:42 pm
aave-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:41 pm
scroll-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:41 pm
gitcoin-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:41 pm
fluid-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:41 pm
uniswap-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:41 pm
address-enrichment ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:41 pm
fluid-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:31 pm
gitcoin-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:30 pm
scroll-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:30 pm
lil-nouns-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:30 pm
prometheus ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:30 pm
nouns-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:22 pm
compound-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:21 pm
lil-nouns-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:21 pm
obol-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:21 pm
nouns-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:21 pm
alertmanager ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:21 pm
obol-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:21 pm
compound-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:21 pm
uniswap-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:18 pm
shutter-api ◻️ Removed (View Logs) Web Mar 25, 2026 at 5:18 pm
tempo ◻️ Removed (View Logs) Mar 25, 2026 at 5:17 pm
aave-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:17 pm
ens-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:17 pm
shutter-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:17 pm
gnosis-indexer ◻️ Removed (View Logs) Mar 25, 2026 at 5:17 pm
api-gateway ⏭️ Skipped (View Logs) Web Mar 25, 2026 at 3:40 pm
30 services not affected by this PR
  • telegram-bot-db
  • RabbitMQ
  • RabbitMQ Web UI
  • dispatcher
  • optimism-db
  • arbitrum-db
  • zk-db
  • arbitrum-api
  • zk-indexer
  • zk-api
  • logic-system
  • subscription-server
  • gnosis-indexer-offchain
  • gnosis-db
  • consumer
  • optimism-api
  • arbitrum-indexer
  • uniswap-indexer-offchain
  • nodeful
  • optimism-indexer
  • erpc-monitoring
  • tailscale-entrance
  • gateful
  • grafana
  • ens-indexer-offchain
  • gitcoin-indexer-offchain
  • comp-indexer-offchain
  • caddy-zero-trust
  • s3manager
  • Postgres

alextnetto and others added 2 commits March 25, 2026 15:53
…te key crashes

Replace shared voteCast() with custom handler that uses onConflictDoUpdate
on votes_onchain insert. Ponder's batch flushing can cause duplicate key
violations with the plain insert approach. Also removes dead abstain vote
comparison (Tornado Cash has binary voting only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sults

- Custom vote handler with onConflictDoNothing prevents Ponder's
  DelayedInsertError during batch flushing (duplicate key on votes_onchain)
- Shared voteCast() plain insert crashes the indexer; this is specific to TORN
- Updated INTEGRATION.md with full backfill verification results:
  65/65 proposals, 49/49 executed, delegatedSupply exact match,
  1089 votes with zero duplicates, 458K transfers, 42K accounts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

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

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: 6de07687a9

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

ZK: "zksync",
SHU: "shutter",
FLUID: "fluid",
TORN: "tornado-cash",
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 Map TORN token ID to a CoinGecko asset platform

Adding TORN to CoingeckoTokenIdEnum without adding a corresponding entry in CoingeckoIdToAssetPlatformId makes CoingeckoService.getTokenPrice() resolve assetPlatform as undefined for TORN, so it requests /simple/token_price/undefined?... and fails token price lookups for TORN-backed endpoints.

Useful? React with 👍 / 👎.

Comment on lines +185 to +189
.onConflictDoNothing();

await context.db
.update(proposalsOnchain, { id: proposalIdStr })
.set((current) => ({
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 Skip proposal tally updates when vote insert conflicts

This handler suppresses duplicate-vote insert errors with onConflictDoNothing(), but it still unconditionally increments forVotes/againstVotes right after. In replay/backfill duplicate scenarios (the exact scenario this conflict handler addresses), the vote row is ignored while the aggregate tally is still incremented, which corrupts proposal totals and downstream status/quorum analytics.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this file should not be here

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this file should not be here

@pikonha pikonha marked this pull request as draft March 30, 2026 17:20
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