{"id":29927429,"url":"https://github.com/blockful/spp2-voting-app","last_synced_at":"2026-02-10T10:32:15.099Z","repository":{"id":290621025,"uuid":"958266709","full_name":"blockful/spp2-voting-app","owner":"blockful","description":"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.","archived":false,"fork":false,"pushed_at":"2025-05-12T21:39:52.000Z","size":1352,"stargazers_count":0,"open_issues_count":11,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-02T13:08:09.294Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://spp.vote","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blockful.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-31T23:18:37.000Z","updated_at":"2025-05-12T21:39:55.000Z","dependencies_parsed_at":"2025-04-29T19:27:53.814Z","dependency_job_id":"ce90aaf0-786e-4bba-b586-c98ad5211f5b","html_url":"https://github.com/blockful/spp2-voting-app","commit_stats":null,"previous_names":["blockful-io/spp2-voting-app","blockful/spp2-voting-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/blockful/spp2-voting-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockful%2Fspp2-voting-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockful%2Fspp2-voting-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockful%2Fspp2-voting-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockful%2Fspp2-voting-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blockful","download_url":"https://codeload.github.com/blockful/spp2-voting-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockful%2Fspp2-voting-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29297169,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-10T09:11:24.369Z","status":"ssl_error","status_checked_at":"2026-02-10T09:10:47.789Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-02T13:03:04.344Z","updated_at":"2026-02-10T10:32:15.064Z","avatar_url":"https://github.com/blockful.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SPP2 Voting App - Copeland Ranking System\n\n## Overview\n\nThis 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.\n\n## Quick Start Guide\n\n### Prerequisites\n\n- Node.js (v18 or higher recommended)\n- npm or yarn package manager\n- A modern web browser\n\n### Frontend Setup\n\n1. Clone the repository:\n```bash\ngit clone https://github.com/yourusername/spp2-voting-app.git\ncd spp2-voting-app\n```\n\n2. Install dependencies:\n```bash\n# Using npm\nnpm install\n\n# Using yarn\nyarn install\n```\n\n3. Create a `.env` file in the root directory by copying .env.example:\n```bash\n# Copy the example environment file\ncp .env.example .env\n```\n\nContent of `.env.example`:\n```bash\nNEXT_PUBLIC_ETH_RPC_URL=https://eth.drpc.org\nNEXT_PUBLIC_WALLETCONNECT_ID=\nNEXT_PUBLIC_PROPOSAL_ID=0x98c65ac02f738ddb430fcd723ea5852a45168550b3daf20f75d5d508ecf28aa1\nNEXT_PUBLIC_PROPOSAL_SPACE=ens.eth\nNEXT_PUBLIC_CACHE=5\n```\n\nMake 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.\n\n4. Start the development server:\n```bash\n# Using npm\nnpm run dev\n\n# Using yarn\nyarn dev\n```\n\nThe application will be available at `http://localhost:3000` (or your configured PORT).\n\n### Environment Variables\n\n- `NEXT_PUBLIC_ETH_RPC_URL`: Ethereum RPC URL for blockchain interactions (default provided)\n- `NEXT_PUBLIC_WALLETCONNECT_ID`: Required for WalletConnect integration, especially for multisig wallets. Get it from [WalletConnect Cloud](https://cloud.walletconnect.com/)\n- `NEXT_PUBLIC_PROPOSAL_ID`: The Snapshot proposal ID to interact with\n- `NEXT_PUBLIC_PROPOSAL_SPACE`: The Snapshot space name (e.g., 'ens.eth')\n- `NEXT_PUBLIC_CACHE`: Cache duration in minutes for API responses\n\n### Available Scripts\n\n- `yarn dev` or `npm run dev`: Starts the development server with hot-reload\n- `yarn build` or `npm run build`: Creates a production build\n- `yarn start` or `npm start`: Runs the production build\n- `yarn lint` or `npm run lint`: Runs the linter\n- `yarn script` or `npm run script`: Runs the Copeland ranking script\n\n## Features\n\n- Processes ranked choice voting data from Snapshot or CSV files\n- Implements the Copeland method for fair candidate ranking\n- Allocates budgets according to program-specific rules\n- Handles special \"None Below\" voting marker\n- Generates detailed reports of allocation results\n- Provides head-to-head comparison data for all candidates\n- Parses service provider names and budget types from choice options\n- Supports bidimensional voting with automatic grouping of related choices\n\n## Algorithm: The Copeland Method\n\nThe Copeland method is a rank-determination algorithm that works as follows:\n\n1. **Pairwise Comparisons**: Each candidate is compared head-to-head with every other candidate.\n2. **Voting Mechanism**:\n\n   - For each pair of candidates (A, B), we count how much voting power ranked A above B.\n   - \"None Below\" option serves as a special marker - any candidate ranked below it is considered unranked.\n   - Ranked candidates always beat unranked candidates in head-to-head contests.\n   - No votes are counted between two unranked candidates.\n\n3. **Scoring**:\n\n   - A candidate receives 1 point for each head-to-head matchup they win.\n   - No points are awarded for losses or ties.\n   - Total points determine the final ranking.\n   - In case of equal points, average support percentage is used as a tiebreaker.\n\n4. **Budget Allocation**:\n\n   - Candidates are processed in ranking order\n   - SPP1 projects can receive 2-year funding streams\n   - Remaining projects are allocated 1-year funding streams\n   - Extended budgets are attempted first, falling back to basic budgets if necessary\n   - Any remaining 2-year stream budget is transferred to the 1-year stream\n\n5. **Bidimensional Voting** (when enabled):\n   - Service providers with multiple budget options (basic/extended) are treated as related\n   - In each voter's ranking, all options from the same provider are grouped together\n   - The highest-ranked option determines the position for all related options\n   - This ensures that voters cannot rank different budget options from the same provider in arbitrary positions\n\n## Installation\n\n```bash\n# Clone the repository\ngit clone https://github.com/yourusername/spp2-voting-app.git\ncd spp2-voting-app\n\n# Install dependencies\nnpm install\n```\n\n## Usage\n\n### Configuration\n\nEdit `src/helpers/config.js` to set your specific parameters:\n\n```javascript\n// Budget parameters\nconst PROGRAM_BUDGET = 4500000; // Total budget in USD per year\nconst TWO_YEAR_STREAM_RATIO = 1 / 3; // Proportion allocated to 2-year streams\nconst ONE_YEAR_STREAM_RATIO = 2 / 3; // Proportion allocated to 1-year stream\n\n// Data source configuration\nconst USE_LOCAL_DATA = true; // Set to false to use Snapshot API\nconst USE_CSV_DATA = true; // Use CSV files for service provider data\n\n// Feature flags\nconst BIDIMENSIONAL_ENABLED = true; // Group choices from the same provider in voting ranks\n\n// Snapshot proposal ID\nconst PROPOSAL_ID =\n  \"0xbbf155b669bcc99133148320ca7876d8bb53a870cd9b7f9ec51b8db29cd7a0f8\";\n```\n\n### CSV Data Format\n\nPlace your CSV files in the `src/helpers/data` directory:\n\n1. **choices.csv**: Contains service provider data\n\n   ```\n   choiceId,choiceName,budgetAmount,isSpp\n   1,sp a,400000,FALSE\n   2,sp b - basic,400000,TRUE\n   3,sp b - ext,500000,FALSE\n   4,sp c,700000,TRUE\n   5,None below,0,FALSE\n   ```\n\n2. **votes.csv**: Contains voting data\n   ```\n   Name,Votes,Choice 1,Choice 2,Choice 3,Choice 4,Choice 5\n   0xAddress1,1.00,Provider A,Provider B,Provider C,None Below,Provider D\n   ```\n\n### Running the Application\n\n```bash\nnode src/helpers/index.js\n```\n\n## Code Structure\n\n### Core Files\n\n- **index.js**: Main entry point that orchestrates the entire process\n- **voteProcessing.js**: Implements the Copeland ranking algorithm\n- **budgetAllocation.js**: Handles budget allocation logic based on rankings\n- **reporting.js**: Formats and exports results\n- **candidateComparisons.js**: Provides utilities for analyzing head-to-head results\n- **csvUtils.js**: Handles CSV file processing and conversion\n- **choiceParser.ts**: Parses service provider names and budget types from choice options\n- **snapshot.js**: Interfaces with Snapshot API or loads mock data\n- **config.js**: Contains application configuration parameters\n\n## The Helpers Folder\n\nThe `src/helpers` folder is the core of the application, containing modular components that handle different aspects of the voting and allocation process:\n\n### Main Module Files\n\n- **index.js** (292 lines): The orchestrator that ties everything together\n\n  - Contains the `main()` function that executes the full allocation workflow\n  - Provides wrapper functions for CSV data handling (`getChoiceOptions()`, `getServiceProviderData()`, `prepareVotesFromCsv()`)\n  - Includes detailed logging for the entire process\n  - Exports all key functions for external use\n\n- **voteProcessing.js** (246 lines): The voting algorithm implementation\n\n  - Implements the Copeland method in `processCopelandRanking()`\n  - Handles the special \"None Below\" option as both a marker and a candidate\n  - Calculates pairwise comparisons between all candidates\n  - Computes scores and rankings based on head-to-head results\n  - Combines ranking data with service provider metadata\n\n- **budgetAllocation.js** (216 lines): The budget distribution logic\n\n  - Allocates budgets based on ranking order\n  - Implements the two-stream allocation model (2-year and 1-year funding)\n  - Handles budget transfers between streams\n  - Preserves the original ranking order throughout allocation\n  - Provides detailed allocation statistics\n\n- **reporting.js** (231 lines): Output formatting and report generation\n\n  - Formats allocation results for display\n  - Generates structured JSON data for reporting\n  - Exports results to timestamped files\n  - Provides currency formatting utilities\n\n- **candidateComparisons.js** (118 lines): Head-to-head analysis tools\n\n  - Extracts match data for specific candidates\n  - Formats match results for frontend display\n  - Calculates statistics like win percentages and vote shares\n  - Sorts results by votes or other criteria\n\n- **csvUtils.js** (473 lines): Data processing utilities\n\n  - Low-level CSV parsing for votes and service provider data\n  - Handles complex CSV formats including quoted fields\n  - Converts between CSV and JSON formats\n  - Supports multiple CSV format variations\n\n- **choiceParser.ts** (~50 lines): Choice name parsing utilities\n\n  - Parses service provider names and budget types from choice strings\n  - Extracts provider base name from formatted options (e.g., \"sp b - basic\" → \"sp b\")\n  - Determines budget type as \"basic\", \"extended\", or \"none\"\n  - Handles special cases like \"None Below\" option\n\n- **snapshot.js** (~100 lines): Integration with Snapshot\n\n  - Interfaces with Snapshot API for live vote data\n  - Falls back to local data when configured\n  - Standardizes data format from multiple sources\n\n- **config.js** (32 lines): Application configuration\n  - Defines all configurable parameters\n  - Controls data sources and budget allocation rules\n  - Sets Snapshot proposal IDs and file paths\n\n### Data Directory\n\nThe `src/helpers/data` directory holds all input and output files:\n\n- **Input Files**:\n  - `choices.csv`: Service provider options and metadata\n  - `votes.csv`: Raw vote data from Snapshot or other sources\n- **Generated Files**:\n  - `mocked-votes.json`: Processed vote data in JSON format\n  - `spp-allocation-[proposalId]-[timestamp].json`: Allocation results\n  - `spp-allocation-[proposalId]-latest.json`: Latest allocation results\n\n### Function Relationships\n\n- **Data Flow**:\n\n  1. CSV data → JSON conversion (`csvUtils.js`)\n  2. Vote processing and ranking (`voteProcessing.js`)\n  3. Budget allocation (`budgetAllocation.js`)\n  4. Name and budget type parsing (`choiceParser.ts`)\n  5. Reporting and export (`reporting.js`)\n\n- **Helper Layers**:\n  - Low-level utilities (`loadServiceProvidersFromCsv`, `convertVotesFromCsv`, `parseChoiceName`)\n  - High-level wrappers (`getServiceProviderData`, `prepareVotesFromCsv`)\n  - Integration functions (in `index.js`)\n\n### Key Functions\n\n| File                    | Function                          | Description                                    |\n| ----------------------- | --------------------------------- | ---------------------------------------------- |\n| voteProcessing.js       | `processCopelandRanking()`        | Core algorithm for Copeland method ranking     |\n| voteProcessing.js       | `combineData()`                   | Merges rankings with provider metadata         |\n| budgetAllocation.js     | `allocateBudgets()`               | Distributes budgets according to rules         |\n| csvUtils.js             | `loadServiceProvidersFromCsv()`   | Low-level CSV parsing for provider data        |\n| index.js                | `getServiceProviderData()`        | High-level wrapper for provider data loading   |\n| csvUtils.js             | `convertVotesFromCsv()`           | Converts vote CSV data to JSON format          |\n| candidateComparisons.js | `getCandidateHeadToHeadResults()` | Extracts match data for a candidate            |\n| reporting.js            | `displayResults()`                | Formats allocation results                     |\n| reporting.js            | `exportResults()`                 | Saves results to JSON file                     |\n| choiceParser.ts         | `parseChoiceName()`               | Parses service provider names and budget types |\n\n## Output\n\nThe application generates:\n\n1. Console output showing the full allocation process\n2. JSON files with detailed results in the `src/helpers/data` directory\n3. Head-to-head comparison data that can be accessed programmatically\n4. Structured choice data with parsed names and budget types\n\n## Example\n\nWhen executed, the program will:\n\n1. Load data from CSV files or Snapshot\n2. Process votes using the Copeland method\n3. Display full rankings with scores\n4. Show budget allocations for each service provider\n5. Generate detailed head-to-head match statistics\n6. Export complete results to a timestamped JSON file\n\n## License\n\n[MIT License](LICENSE)\n\n## Contributing\n\nContributions welcome! Please feel free to submit a Pull Request.\n\n## CSV Format\n\n### choices.csv\n\nThe choices.csv file contains service provider data with the following columns:\n\n```\nchoiceId,choiceName,budgetAmount,isSpp\n1,sp a,400000,FALSE\n2,sp b - basic,400000,TRUE\n3,sp b - ext,500000,FALSE\n4,sp c,700000,TRUE\n5,None below,0,FALSE\n```\n\n- `choiceId`: Numeric ID for the choice option\n- `choiceName`: Display name of the service provider\n- `budgetAmount`: Budget amount requested (in USD without commas)\n- `isSpp`: Boolean flag indicating if the provider was part of SPP1 (TRUE/FALSE)\n\nNote: The \"None below\" option is special and should always be included.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblockful%2Fspp2-voting-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblockful%2Fspp2-voting-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblockful%2Fspp2-voting-app/lists"}