Skip to content
/ grump Public

A simple monitoring tool that tracks voting on The Graph Council's governance proposals and alerts us when council members haven't voted after 5 days.

License

Notifications You must be signed in to change notification settings

pdiomede/grump

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

41 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

The Graph Council Voting Monitor

A Python-based monitoring tool that tracks voting activity on The Graph Council's governance proposals via Snapshot and alerts when council members haven't voted after a configurable threshold (default: 5 days).

πŸ” Authentication System (NEW!)

The GRUMP dashboard now includes a secure email-based OTP authentication system! Users must login before accessing the dashboard.

Quick Setup:

# Run the interactive setup script
./setup_auth.sh

# Or manually configure:
cp .env.example .env
# Edit .env with your SMTP credentials

# Start the authentication gateway
python3 auth_gate.py

Visit: http://localhost:38081 (login page will be shown)

For detailed setup instructions, see: AUTH_SETUP.md

Key Features:

  • πŸ” Email-based OTP authentication
  • πŸ“§ 6-digit codes sent via email (10 minute expiry)
  • πŸͺ 7-day session cookies
  • πŸ‘₯ Whitelist-based access control
  • πŸ›‘οΈ Rate limiting & audit logging
  • 🎨 Beautiful login UI

🎯 Purpose

The Graph Council consists of 6 members who vote on Graph Governance Proposals (GGPs) posted on https://snapshot.org/#/council.graphprotocol.eth. This tool helps ensure full participation by:

  • Monitoring active proposals on Snapshot
  • Tracking which council members have/haven't voted
  • Generating alerts when members haven't voted after a specified period
  • Producing a beautiful HTML report with copy-to-clipboard functionality

πŸ”§ How It Works

Script Logic Flow

  1. Load Configuration

    • Reads environment variables from .env file
    • Loads council member wallet addresses from wallets.txt
    • Sets alert threshold and output paths
  2. Fetch Active Proposals

    • Connects to Snapshot GraphQL API (https://hub.snapshot.org/graphql)
    • Queries for active proposals in the council.graphprotocol.eth space
    • Retrieves proposal metadata: title, creation date, end date, state
  3. Fetch Voting Data

    • For each active proposal, fetches all votes
    • Extracts voter addresses (normalized to lowercase)
    • Creates a set of voters for quick lookup
  4. Analyze Voting Status

    • Compares council member addresses against voters
    • Identifies non-voters for each proposal
    • Calculates:
      • Days since proposal creation
      • Days remaining until voting ends
      • Voting percentage (council votes / total council members)
  5. Generate Alerts

    • Creates alerts for proposals older than threshold (default: 5 days)
    • Only alerts for council members who haven't voted
    • Alerts are displayed inline within each proposal card
  6. Color-Code Metrics

    • Voting Status:
      • 🟒 Green: All council members voted (100%)
      • 🟑 Yellow: β‰₯50% voted but not all
      • πŸ”΄ Red: <50% voted
    • Time Remaining:
      • 🟒 Green: 5+ days left
      • 🟑 Yellow: 2-4 days left
      • πŸ”΄ Red: <2 days left
  7. Generate HTML Report

    • Creates a static HTML dashboard with:
      • Summary cards (alerts, proposals, members)
      • Proposal cards with voting statistics
      • Inline alerts for non-voters
      • Copy-to-clipboard buttons
      • Direct links to Snapshot
    • Uses dark theme with Poppins font
    • Fully responsive design

Data Flow Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Configuration  β”‚
β”‚  (.env, txt)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Snapshot API   │◄──── GraphQL Query (Active Proposals)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Vote Analysis  │◄──── Compare voters vs council members
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Alert Logic    │◄──── Check thresholds & generate alerts
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  HTML Generator │◄──── Create styled dashboard
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   index.html    │◄──── Static report with all data
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Algorithms

Voting Percentage Calculation:

vote_percentage = (council_votes / COUNCIL_MEMBERS_COUNT) * 100
if council_votes == COUNCIL_MEMBERS_COUNT:
    color = "green"  # All voted
elif vote_percentage >= 50:
    color = "yellow"  # Most voted
else:
    color = "red"  # Few voted

Days Left Calculation:

now = datetime.now(timezone.utc)
end_date = datetime.fromtimestamp(end_timestamp, tz=timezone.utc)
days_left = (end_date - now).days

Alert Threshold Check:

if days_old >= ALERT_THRESHOLD_DAYS and non_voters:
    generate_alert(proposal, non_voters)

πŸ“‹ Features

  • βœ… Fetches active proposals from Snapshot GraphQL API
  • βœ… Tracks voting status for specific council members
  • βœ… Configurable alert threshold (default: 5 days)
  • βœ… Optional hiding of completed proposals (with all votes)
  • βœ… Color-coded voting status and time remaining indicators
  • βœ… Generates a modern, responsive HTML report
  • βœ… One-click copy of Ethereum addresses
  • βœ… Direct links to proposals on Snapshot
  • βœ… Slack integration - sends notifications for proposals with missing votes
  • βœ… Suitable for scheduled execution (cron job)

πŸš€ Quick Start

Prerequisites

  • Python 3.7 or higher
  • pip (Python package manager)
  • Internet connection (to access Snapshot API)

Installation

  1. Clone or download this repository

  2. Install dependencies:

pip install -r requirements.txt
  1. Configure the environment:

Copy the example environment file:

cp .env.example .env

Edit .env if you want to change any defaults:

# Slack Configuration (optional - leave empty to disable)
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL

# Snapshot space to monitor
SNAPSHOT_SPACE=council.graphprotocol.eth

# Number of days before alerting for non-voters
ALERT_THRESHOLD_DAYS=5

# Path to file containing council member wallet addresses
WALLETS_FILE=wallets.txt

# Output HTML file path
OUTPUT_HTML=index.html

# Expected number of council members
COUNCIL_MEMBERS_COUNT=6

# Show proposals with all council members voted (Y/N)
SHOW_COMPLETED_PROPOSALS=N
  1. Add council member wallet addresses:

Edit wallets.txt and add the Ethereum wallet addresses of council members (one per line):

0x1234567890123456789012345678901234567890
0x2345678901234567890123456789012345678901
0x3456789012345678901234567890123456789012

Note: Lines starting with # are treated as comments and will be ignored.

  1. Configure Slack notifications (Optional):

To receive Slack notifications when proposals have missing votes:

a. Create a Slack incoming webhook:

  • Go to https://api.slack.com/messaging/webhooks
  • Click "Create your Slack app"
  • Choose "From scratch" and select your workspace
  • Navigate to "Incoming Webhooks" and activate it
  • Click "Add New Webhook to Workspace"
  • Select the channel where you want notifications
  • Copy the Webhook URL

b. Add the webhook URL to your .env file:

SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR_WORKSPACE_ID/YOUR_CHANNEL_ID/YOUR_SECRET_TOKEN

c. (Optional) Add Slack user IDs to mention at the end of notifications:

SLACK_MENTION_USERS=U01234ABCDE,U56789FGHIJ

To get Slack User IDs:

  • Click on a user's profile in Slack
  • Click "More" (three dots) > "Copy member ID"
  • See HOW_TO_GET_SLACK_USER_IDS.md for detailed instructions

d. Choose where to send notifications (Slack or local file):

POST_TO_SLACK=N  # N = Save to file, Y = Send to Slack

When POST_TO_SLACK=N:

  • Messages are saved to slack_message.txt in the project directory
  • Messages are appended (not overwritten) with timestamps
  • Useful for testing before sending to Slack
  • Review messages before setting POST_TO_SLACK=Y

Slack Message Format:

When a proposal has missing votes after the alert threshold, you'll receive a message like:

πŸ€– Reminder: GGP-0055 - "Deploying GRT token to Avalanche, Base, and Solana" has 2 missing votes, and is ending in 3 days.
Missing votes in the last 5 days:
0x1234567890123456789012345678901234567890
0x2345678901234567890123456789012345678901

Please cast your vote here asap: https://snapshot.org/#/council.graphprotocol.eth/proposal/0xabc...
Thank you!

Full Details here:
https://dashboards.thegraph.foundation/grump/

cc @Pedro @Andrew Clews

Note: The cc mentions at the end appear if you configure SLACK_MENTION_USERS in your .env file.

Note: If SLACK_WEBHOOK_URL is not set or empty, Slack notifications will be skipped.

Usage

Run the monitoring script:

python monitor_council_votes.py

This will:

  1. Fetch active proposals from Snapshot
  2. Check voting status for all council members
  3. Generate alerts for members who haven't voted after the threshold
  4. Create an index.html file with the full report

Open index.html in your browser to view the report.

πŸ“… Scheduling (Recommended)

To run the monitor automatically every 24 hours on a VPS:

Using Cron (Linux/Unix/macOS)

  1. Make the script executable:
chmod +x monitor_council_votes.py
  1. Edit your crontab:
crontab -e
  1. Add a line to run daily at 9 AM:
0 9 * * * cd /path/to/grump && /usr/bin/python3 monitor_council_votes.py >> /path/to/logs/monitor.log 2>&1

Note: Replace /path/to/grump with the actual path to this directory.

Using systemd timer (Linux)

  1. Create a service file /etc/systemd/system/graph-council-monitor.service:
[Unit]
Description=The Graph Council Voting Monitor
After=network.target

[Service]
Type=oneshot
User=your-username
WorkingDirectory=/path/to/grump
ExecStart=/usr/bin/python3 monitor_council_votes.py
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
  1. Create a timer file /etc/systemd/system/graph-council-monitor.timer:
[Unit]
Description=Run Graph Council Monitor daily
Requires=graph-council-monitor.service

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
  1. Enable and start the timer:
sudo systemctl enable graph-council-monitor.timer
sudo systemctl start graph-council-monitor.timer

πŸ“Š HTML Report Features

The generated index.html report includes:

  • Summary Dashboard: Overview of active proposals, council members, and alerts
  • Alert Section: Highlighted warnings for members who haven't voted
  • Proposal Details: Complete list of active proposals with voting statistics
  • Copy Functionality: One-click copy buttons for all Ethereum addresses
  • Direct Links: Quick access to view proposals on Snapshot
  • Responsive Design: Works on desktop, tablet, and mobile devices

πŸ”§ Configuration Options

Environment Variables (.env)

Variable Default Description
SLACK_WEBHOOK_URL (empty) Slack webhook URL for notifications (optional)
SLACK_MENTION_USERS (empty) Comma-separated Slack User IDs to mention (optional)
POST_TO_SLACK N Post to Slack (Y) or save to file (N)
SNAPSHOT_SPACE council.graphprotocol.eth The Snapshot space to monitor
PROPOSAL_MAX_AGE_DAYS 10 Maximum age of proposals to monitor (in days)
ALERT_THRESHOLD_DAYS 5 Number of days before alerting for non-voters
WALLETS_FILE wallets.txt Path to file containing council member addresses
OUTPUT_HTML index.html Output HTML file path
COUNCIL_MEMBERS_COUNT 6 Expected number of council members
SHOW_COMPLETED_PROPOSALS N Show proposals with all votes (Y/N)
FUN_MODE N Enable fun mode with emojis and casual messaging (Y/N)

Wallet File Format

The wallet file supports two formats:

Format 1: Address with Name (Recommended)

# The Graph Council Members
0x7EAbE4F636B937628A7Fe503bD7F06772C047FEe,Chris
0x68AfAbC57e048b29E0741816167777c148a02b57,Adam
0xB02ce52E8B7344d306b60CB1E0d4Db1EF86b80b0,Fubhy

Format 2: Address Only (Legacy)

# The Graph Council Members
0xAddress1...
0xAddress2...

Features:

  • One entry per line in format: address,name
  • Names are displayed in dashboard and Slack messages instead of addresses
  • Comments (lines starting with #) are supported
  • Empty lines are ignored
  • Addresses are automatically normalized to lowercase
  • Falls back to showing address if no name provided

πŸ› οΈ Troubleshooting

Common Issues

"Wallets file not found"

  • Ensure wallets.txt exists in the project directory
  • Check the WALLETS_FILE path in your .env file

"API request failed"

  • Check your internet connection
  • Snapshot API might be temporarily unavailable (retry later)
  • Check for API rate limiting

No proposals showing

  • Verify that there are active proposals on the Snapshot space
  • Check that SNAPSHOT_SPACE is configured correctly

HTML not displaying correctly

  • Ensure the entire HTML file was generated (check file size)
  • Try opening in a different browser
  • Check browser console for JavaScript errors

πŸ“ Example Output

Console output:

============================================================
The Graph Council Voting Monitor
============================================================
Space: council.graphprotocol.eth
Alert threshold: 5 days
Output: index.html
============================================================

Loading council member wallets...
Loaded 6 council member addresses

Fetching active proposals from Snapshot...

Found 2 active proposal(s)
Generated 3 alert(s)

Generating HTML report: index.html
βœ“ Report generated successfully!
βœ“ Open index.html in your browser to view the report

πŸ“€ Sending Slack notifications for 2 proposal(s)...
  βœ“ Sent notification for: GGP-001: Treasury Allocation
  βœ“ Sent notification for: GGP-002: Protocol Update

πŸ“Š Slack notifications: 2/2 sent successfully

⚠️  ALERTS:
  β€’ 0x12345678... hasn't voted on 'GGP-001: Treasury Allocation' (7 days)
  β€’ 0x23456789... hasn't voted on 'GGP-001: Treasury Allocation' (7 days)
  β€’ 0x34567890... hasn't voted on 'GGP-002: Protocol Update' (6 days)

πŸ”’ Security Notes

  • Never commit your .env file with sensitive information to version control
  • The .gitignore is configured to exclude .env automatically
  • Council member addresses are public on the blockchain, so wallets.txt can be committed
  • Consider restricting access to the generated HTML if it contains sensitive information

🀝 Contributing

Feel free to submit issues or pull requests to improve this tool!

πŸ“„ License

This tool is provided as-is for monitoring The Graph Council voting activity.

πŸ”— Useful Links


Last Updated: November 5, 2025 (v0.0.11)

About

A simple monitoring tool that tracks voting on The Graph Council's governance proposals and alerts us when council members haven't voted after 5 days.

Resources

License

Stars

Watchers

Forks

Packages

No packages published