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).
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.pyVisit: 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
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
-
Load Configuration
- Reads environment variables from
.envfile - Loads council member wallet addresses from
wallets.txt - Sets alert threshold and output paths
- Reads environment variables from
-
Fetch Active Proposals
- Connects to Snapshot GraphQL API (
https://hub.snapshot.org/graphql) - Queries for active proposals in the
council.graphprotocol.ethspace - Retrieves proposal metadata: title, creation date, end date, state
- Connects to Snapshot GraphQL API (
-
Fetch Voting Data
- For each active proposal, fetches all votes
- Extracts voter addresses (normalized to lowercase)
- Creates a set of voters for quick lookup
-
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)
-
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
-
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
- Voting Status:
-
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
- Creates a static HTML dashboard with:
βββββββββββββββββββ
β 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
βββββββββββββββββββ
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 votedDays Left Calculation:
now = datetime.now(timezone.utc)
end_date = datetime.fromtimestamp(end_timestamp, tz=timezone.utc)
days_left = (end_date - now).daysAlert Threshold Check:
if days_old >= ALERT_THRESHOLD_DAYS and non_voters:
generate_alert(proposal, non_voters)- β 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)
- Python 3.7 or higher
- pip (Python package manager)
- Internet connection (to access Snapshot API)
-
Clone or download this repository
-
Install dependencies:
pip install -r requirements.txt- Configure the environment:
Copy the example environment file:
cp .env.example .envEdit .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- 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.
- 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_TOKENc. (Optional) Add Slack user IDs to mention at the end of notifications:
SLACK_MENTION_USERS=U01234ABCDE,U56789FGHIJTo 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.mdfor detailed instructions
d. Choose where to send notifications (Slack or local file):
POST_TO_SLACK=N # N = Save to file, Y = Send to SlackWhen POST_TO_SLACK=N:
- Messages are saved to
slack_message.txtin 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.
Run the monitoring script:
python monitor_council_votes.pyThis will:
- Fetch active proposals from Snapshot
- Check voting status for all council members
- Generate alerts for members who haven't voted after the threshold
- Create an
index.htmlfile with the full report
Open index.html in your browser to view the report.
To run the monitor automatically every 24 hours on a VPS:
- Make the script executable:
chmod +x monitor_council_votes.py- Edit your crontab:
crontab -e- 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>&1Note: Replace /path/to/grump with the actual path to this directory.
- 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- 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- Enable and start the timer:
sudo systemctl enable graph-council-monitor.timer
sudo systemctl start graph-council-monitor.timerThe 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
| 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) |
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
"Wallets file not found"
- Ensure
wallets.txtexists in the project directory - Check the
WALLETS_FILEpath in your.envfile
"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_SPACEis 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
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)
- Never commit your
.envfile with sensitive information to version control - The
.gitignoreis configured to exclude.envautomatically - Council member addresses are public on the blockchain, so
wallets.txtcan be committed - Consider restricting access to the generated HTML if it contains sensitive information
Feel free to submit issues or pull requests to improve this tool!
This tool is provided as-is for monitoring The Graph Council voting activity.
Last Updated: November 5, 2025 (v0.0.11)