https://github.com/blockful/spp2-voting-app
A custom voting UI used to distribute $4.5m for ENS service provider, in a more granular manner, using copleand method. Built on top of Snapshot API.
https://github.com/blockful/spp2-voting-app
Last synced: 5 months ago
JSON representation
A custom voting UI used to distribute $4.5m for ENS service provider, in a more granular manner, using copleand method. Built on top of Snapshot API.
- Host: GitHub
- URL: https://github.com/blockful/spp2-voting-app
- Owner: blockful
- Created: 2025-03-31T23:18:37.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-05-12T21:39:52.000Z (about 1 year ago)
- Last Synced: 2025-08-02T13:08:09.294Z (11 months ago)
- Language: TypeScript
- Homepage: https://spp.vote
- Size: 1.29 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# SPP2 Voting App - Copeland Ranking System
## Overview
This application implements a Service Provider Program (SPP) allocation system using the Copeland method for ranked choice voting. It processes voting data from Snapshot, ranks candidates, and allocates budgets based on configurable rules.
## Quick Start Guide
### Prerequisites
- Node.js (v18 or higher recommended)
- npm or yarn package manager
- A modern web browser
### Frontend Setup
1. Clone the repository:
```bash
git clone https://github.com/yourusername/spp2-voting-app.git
cd spp2-voting-app
```
2. Install dependencies:
```bash
# Using npm
npm install
# Using yarn
yarn install
```
3. Create a `.env` file in the root directory by copying .env.example:
```bash
# Copy the example environment file
cp .env.example .env
```
Content of `.env.example`:
```bash
NEXT_PUBLIC_ETH_RPC_URL=https://eth.drpc.org
NEXT_PUBLIC_WALLETCONNECT_ID=
NEXT_PUBLIC_PROPOSAL_ID=0x98c65ac02f738ddb430fcd723ea5852a45168550b3daf20f75d5d508ecf28aa1
NEXT_PUBLIC_PROPOSAL_SPACE=ens.eth
NEXT_PUBLIC_CACHE=5
```
Make sure to set `NEXT_PUBLIC_WALLETCONNECT_ID` in your `.env` file. This is required if you plan to vote using a multisig wallet or WalletConnect.
4. Start the development server:
```bash
# Using npm
npm run dev
# Using yarn
yarn dev
```
The application will be available at `http://localhost:3000` (or your configured PORT).
### Environment Variables
- `NEXT_PUBLIC_ETH_RPC_URL`: Ethereum RPC URL for blockchain interactions (default provided)
- `NEXT_PUBLIC_WALLETCONNECT_ID`: Required for WalletConnect integration, especially for multisig wallets. Get it from [WalletConnect Cloud](https://cloud.walletconnect.com/)
- `NEXT_PUBLIC_PROPOSAL_ID`: The Snapshot proposal ID to interact with
- `NEXT_PUBLIC_PROPOSAL_SPACE`: The Snapshot space name (e.g., 'ens.eth')
- `NEXT_PUBLIC_CACHE`: Cache duration in minutes for API responses
### Available Scripts
- `yarn dev` or `npm run dev`: Starts the development server with hot-reload
- `yarn build` or `npm run build`: Creates a production build
- `yarn start` or `npm start`: Runs the production build
- `yarn lint` or `npm run lint`: Runs the linter
- `yarn script` or `npm run script`: Runs the Copeland ranking script
## Features
- Processes ranked choice voting data from Snapshot or CSV files
- Implements the Copeland method for fair candidate ranking
- Allocates budgets according to program-specific rules
- Handles special "None Below" voting marker
- Generates detailed reports of allocation results
- Provides head-to-head comparison data for all candidates
- Parses service provider names and budget types from choice options
- Supports bidimensional voting with automatic grouping of related choices
## Algorithm: The Copeland Method
The Copeland method is a rank-determination algorithm that works as follows:
1. **Pairwise Comparisons**: Each candidate is compared head-to-head with every other candidate.
2. **Voting Mechanism**:
- For each pair of candidates (A, B), we count how much voting power ranked A above B.
- "None Below" option serves as a special marker - any candidate ranked below it is considered unranked.
- Ranked candidates always beat unranked candidates in head-to-head contests.
- No votes are counted between two unranked candidates.
3. **Scoring**:
- A candidate receives 1 point for each head-to-head matchup they win.
- No points are awarded for losses or ties.
- Total points determine the final ranking.
- In case of equal points, average support percentage is used as a tiebreaker.
4. **Budget Allocation**:
- Candidates are processed in ranking order
- SPP1 projects can receive 2-year funding streams
- Remaining projects are allocated 1-year funding streams
- Extended budgets are attempted first, falling back to basic budgets if necessary
- Any remaining 2-year stream budget is transferred to the 1-year stream
5. **Bidimensional Voting** (when enabled):
- Service providers with multiple budget options (basic/extended) are treated as related
- In each voter's ranking, all options from the same provider are grouped together
- The highest-ranked option determines the position for all related options
- This ensures that voters cannot rank different budget options from the same provider in arbitrary positions
## Installation
```bash
# Clone the repository
git clone https://github.com/yourusername/spp2-voting-app.git
cd spp2-voting-app
# Install dependencies
npm install
```
## Usage
### Configuration
Edit `src/helpers/config.js` to set your specific parameters:
```javascript
// Budget parameters
const PROGRAM_BUDGET = 4500000; // Total budget in USD per year
const TWO_YEAR_STREAM_RATIO = 1 / 3; // Proportion allocated to 2-year streams
const ONE_YEAR_STREAM_RATIO = 2 / 3; // Proportion allocated to 1-year stream
// Data source configuration
const USE_LOCAL_DATA = true; // Set to false to use Snapshot API
const USE_CSV_DATA = true; // Use CSV files for service provider data
// Feature flags
const BIDIMENSIONAL_ENABLED = true; // Group choices from the same provider in voting ranks
// Snapshot proposal ID
const PROPOSAL_ID =
"0xbbf155b669bcc99133148320ca7876d8bb53a870cd9b7f9ec51b8db29cd7a0f8";
```
### CSV Data Format
Place your CSV files in the `src/helpers/data` directory:
1. **choices.csv**: Contains service provider data
```
choiceId,choiceName,budgetAmount,isSpp
1,sp a,400000,FALSE
2,sp b - basic,400000,TRUE
3,sp b - ext,500000,FALSE
4,sp c,700000,TRUE
5,None below,0,FALSE
```
2. **votes.csv**: Contains voting data
```
Name,Votes,Choice 1,Choice 2,Choice 3,Choice 4,Choice 5
0xAddress1,1.00,Provider A,Provider B,Provider C,None Below,Provider D
```
### Running the Application
```bash
node src/helpers/index.js
```
## Code Structure
### Core Files
- **index.js**: Main entry point that orchestrates the entire process
- **voteProcessing.js**: Implements the Copeland ranking algorithm
- **budgetAllocation.js**: Handles budget allocation logic based on rankings
- **reporting.js**: Formats and exports results
- **candidateComparisons.js**: Provides utilities for analyzing head-to-head results
- **csvUtils.js**: Handles CSV file processing and conversion
- **choiceParser.ts**: Parses service provider names and budget types from choice options
- **snapshot.js**: Interfaces with Snapshot API or loads mock data
- **config.js**: Contains application configuration parameters
## The Helpers Folder
The `src/helpers` folder is the core of the application, containing modular components that handle different aspects of the voting and allocation process:
### Main Module Files
- **index.js** (292 lines): The orchestrator that ties everything together
- Contains the `main()` function that executes the full allocation workflow
- Provides wrapper functions for CSV data handling (`getChoiceOptions()`, `getServiceProviderData()`, `prepareVotesFromCsv()`)
- Includes detailed logging for the entire process
- Exports all key functions for external use
- **voteProcessing.js** (246 lines): The voting algorithm implementation
- Implements the Copeland method in `processCopelandRanking()`
- Handles the special "None Below" option as both a marker and a candidate
- Calculates pairwise comparisons between all candidates
- Computes scores and rankings based on head-to-head results
- Combines ranking data with service provider metadata
- **budgetAllocation.js** (216 lines): The budget distribution logic
- Allocates budgets based on ranking order
- Implements the two-stream allocation model (2-year and 1-year funding)
- Handles budget transfers between streams
- Preserves the original ranking order throughout allocation
- Provides detailed allocation statistics
- **reporting.js** (231 lines): Output formatting and report generation
- Formats allocation results for display
- Generates structured JSON data for reporting
- Exports results to timestamped files
- Provides currency formatting utilities
- **candidateComparisons.js** (118 lines): Head-to-head analysis tools
- Extracts match data for specific candidates
- Formats match results for frontend display
- Calculates statistics like win percentages and vote shares
- Sorts results by votes or other criteria
- **csvUtils.js** (473 lines): Data processing utilities
- Low-level CSV parsing for votes and service provider data
- Handles complex CSV formats including quoted fields
- Converts between CSV and JSON formats
- Supports multiple CSV format variations
- **choiceParser.ts** (~50 lines): Choice name parsing utilities
- Parses service provider names and budget types from choice strings
- Extracts provider base name from formatted options (e.g., "sp b - basic" → "sp b")
- Determines budget type as "basic", "extended", or "none"
- Handles special cases like "None Below" option
- **snapshot.js** (~100 lines): Integration with Snapshot
- Interfaces with Snapshot API for live vote data
- Falls back to local data when configured
- Standardizes data format from multiple sources
- **config.js** (32 lines): Application configuration
- Defines all configurable parameters
- Controls data sources and budget allocation rules
- Sets Snapshot proposal IDs and file paths
### Data Directory
The `src/helpers/data` directory holds all input and output files:
- **Input Files**:
- `choices.csv`: Service provider options and metadata
- `votes.csv`: Raw vote data from Snapshot or other sources
- **Generated Files**:
- `mocked-votes.json`: Processed vote data in JSON format
- `spp-allocation-[proposalId]-[timestamp].json`: Allocation results
- `spp-allocation-[proposalId]-latest.json`: Latest allocation results
### Function Relationships
- **Data Flow**:
1. CSV data → JSON conversion (`csvUtils.js`)
2. Vote processing and ranking (`voteProcessing.js`)
3. Budget allocation (`budgetAllocation.js`)
4. Name and budget type parsing (`choiceParser.ts`)
5. Reporting and export (`reporting.js`)
- **Helper Layers**:
- Low-level utilities (`loadServiceProvidersFromCsv`, `convertVotesFromCsv`, `parseChoiceName`)
- High-level wrappers (`getServiceProviderData`, `prepareVotesFromCsv`)
- Integration functions (in `index.js`)
### Key Functions
| File | Function | Description |
| ----------------------- | --------------------------------- | ---------------------------------------------- |
| voteProcessing.js | `processCopelandRanking()` | Core algorithm for Copeland method ranking |
| voteProcessing.js | `combineData()` | Merges rankings with provider metadata |
| budgetAllocation.js | `allocateBudgets()` | Distributes budgets according to rules |
| csvUtils.js | `loadServiceProvidersFromCsv()` | Low-level CSV parsing for provider data |
| index.js | `getServiceProviderData()` | High-level wrapper for provider data loading |
| csvUtils.js | `convertVotesFromCsv()` | Converts vote CSV data to JSON format |
| candidateComparisons.js | `getCandidateHeadToHeadResults()` | Extracts match data for a candidate |
| reporting.js | `displayResults()` | Formats allocation results |
| reporting.js | `exportResults()` | Saves results to JSON file |
| choiceParser.ts | `parseChoiceName()` | Parses service provider names and budget types |
## Output
The application generates:
1. Console output showing the full allocation process
2. JSON files with detailed results in the `src/helpers/data` directory
3. Head-to-head comparison data that can be accessed programmatically
4. Structured choice data with parsed names and budget types
## Example
When executed, the program will:
1. Load data from CSV files or Snapshot
2. Process votes using the Copeland method
3. Display full rankings with scores
4. Show budget allocations for each service provider
5. Generate detailed head-to-head match statistics
6. Export complete results to a timestamped JSON file
## License
[MIT License](LICENSE)
## Contributing
Contributions welcome! Please feel free to submit a Pull Request.
## CSV Format
### choices.csv
The choices.csv file contains service provider data with the following columns:
```
choiceId,choiceName,budgetAmount,isSpp
1,sp a,400000,FALSE
2,sp b - basic,400000,TRUE
3,sp b - ext,500000,FALSE
4,sp c,700000,TRUE
5,None below,0,FALSE
```
- `choiceId`: Numeric ID for the choice option
- `choiceName`: Display name of the service provider
- `budgetAmount`: Budget amount requested (in USD without commas)
- `isSpp`: Boolean flag indicating if the provider was part of SPP1 (TRUE/FALSE)
Note: The "None below" option is special and should always be included.