BUIP 224: Move VotePeer to Nexa
Proposer: Dolaned
Submitted: 2026-02-08
Abstract
VotePeer is an on-chain voting system that BU previously developed on top of BCH that is used to vote on BUIPs proposed by members. BU no longer works on the BCH chain and the current VotePeer solution is both Android-only and no longer downloadable via the google play store requiring a growing number of BU members to have to perform their votes manually via the forum. This proposal establishes a development plan to move VotePeer on to Nexa and replace the Android App with a web-based dApp that utilises Nexa’s on-chain capabilities to make voting on BU governance easier.
Motivation
There has been a lot of back and forth discussion on varying ways to move VotePeer to Nexa, it is clear this should be a priority as the organisation and team scales so the motivation for this BUIP is to bring alignment to the team, and make a commitment to proceed using the outlined technical specification in this proposal.
Overview
VotePeer is a covenant-based on-chain voting system that uses Nexa group tokens, script templates, and covenants to provide transparent, verifiable governance for BUIP proposals. It replaces the current VotePeer Android app with a web-based dApp that enforces ballot restriction at the protocol level: each voter receives exactly one fungible group token whose spending is covenant-restricted to a set of predefined vote-option addresses. Vote tallying reduces to a simple address-balance query, producing an immutable, auditable record on-chain.
An election in VotePeer consists of four on-chain transactions followed by a balance query:
TX 0 Vote Creation → Parent group token genesis (32-byte groupId)
TX 1 Token Minting → Mint N voter tokens under the parent group
TX 2 Token Distribution → Send 1 token per voter to covenant-locked P2ST addresses
TX 3+ Vote Casting → Each voter spends their token to a vote-option address
Tally → Query token balance at each vote-option address
TX 4 Cleanup (optional) → Voter can send vote token to a did not vote address
Address-Based Tallying
Each vote option (e.g., Accept, Reject, Abstain) is assigned a unique address derived from a covenant script. When a voter casts their ballot, their single group token is transferred to the chosen option’s address. The vote count for any option equals the confirmed token balance at that address:
acceptVotes = rostrumProvider.getBalance(acceptAddress).confirmed
rejectVotes = rostrumProvider.getBalance(rejectAddress).confirmed
abstainVotes = rostrumProvider.getBalance(abstainAddress).confirmed
This design requires no transaction parsing or indexer logic beyond standard balance queries.
First time setup
Before beginning votes the president needs to setup the parent group that will be used for all votes on BUIPS, and sets ticker name and token data. All vote tokens will be subgroup tokens under this parent group, so that voters know that the vote tokens are real vote tokens for BU.
Group Token Lifecycle
Step 1 (TX 0) — Election creation. The vote manager creates a subgroup token. The transaction embeds the election name (BUIP name), ticker (BUIP + buip number), and an optional document_url pointing to IPFS metadata for the BUIP.
Step 2 (TX 1) — Mint voter tokens. The creator mints exactly N tokens (one per eligible voter) for the BUIP vote subgroup.
Step 3 (TX 2) — Distribute with covenant. Each voter receives exactly 1 token at a Pay-to-Script-Template (P2ST) address.
Step 4 (TX 3+) — Cast votes. Each voter constructs and broadcasts a transaction spending their token to their chosen vote-option address. the outputs are restricted to the vote option addresses by the template script.
Step 5 (TX 4) — Cleanup. After the voting period ends, the voters or vote creator may spend uncast vote tokens to the “did not vote” address, that way the vote organiser can clean up and has the ability to check who did not vote by checking a single address
Election Metadata via IPFS
Election metadata is stored as a JSON document on IPFS, optionally referenced in the group token’s document_url field:
Below is an example of what the ipfs metadata could be.
{
"title": "BUIP XXX: Proposal Title",
"description": "Detailed description of the proposal",
"options": ["Accept", "Reject", "Abstain"],
"endHeight": 800000,
"voteOptionAddresses": {
"Accept": "nexa:...",
"Reject": "nexa:...",
"Abstain": "nexa:..."
},
"voterAddresses": ["nexa:voter1", "nexa:voter2"],
"voterPubkeys": {
"nexa:voter1": "02abcd...",
"nexa:voter2": "02ef01..."
}
}
This enables any participant to reconstruct the election state from a single IPFS hash and the parent group ID, without relying on centralized infrastructure.
Wallet Comms Protocol (WCP) Integration
VotePeer never holds or has access to private keys. All transaction signing is delegated to an external wallet via the wallet-comms-sdk Wallet Comms Protocol:
- The dApp constructs unsigned transactions.
- The user pairs with their mobile wallet (QR code).
- The wallet signs and broadcasts the transaction.
- The dApp receives the transaction ID as confirmation.
Wallet Compatibility
VotePeer delegates all transaction signing to external wallets via the Wallet Comms Protocol (WCP) — a WebSocket-based communication layer that allows any wallet to pair with the dApp via QR code. The dApp constructs unsigned transactions and the paired wallet handles signing and broadcasting. VotePeer never holds or has access to private keys.
WCP is wallet-agnostic by design. The wallet-side integration is lightweight, requiring only standard WebSocket connectivity and the ability to parse a pairing URI and respond to signing requests. This can be implemented in any language or platform, making it straightforward for existing Nexa wallets — including those written in Kotlin — to add WCP support.
Work to add wcp support to existing Nexa wallets that do not yet support it is part of this BUIP, ensuring the broadest possible voter participation from day one.
Implementation
The following components are implemented in TypeScript and Vue 3:
Service Layer
| Service | Responsibility |
|---|---|
VotingElectionWallet |
Election creation, token minting, covenant construction, P2ST distribution |
VoterClientWallet |
Vote casting — UTXO lookup, unlocking script assembly, broadcast |
VoteTally |
Address-balance tallying, turnout calculation, quorum detection, live polling |
RostrumQuery |
Blockchain queries — balance, UTXO, transaction, height, broadcast |
IpfsUploader |
Metadata and vote data upload to IPFS, retrieval, and parsing |
UI Components
| Component | Role |
|---|---|
CreateElectionForm |
Election parameter input, voter registration (address + pubkey CSV), group token creation |
MintTokensModal |
Token minting transaction construction and signing |
DistributeTokensModal |
Covenant-locked distribution to all registered voters |
CastVotePanel |
Vote choice selection, IPFS import, pubkey extraction, vote broadcast |
ResultsCard |
Live tally display with progress bars, turnout percentage, winner detection |
Dependencies
| Package | Version | Purpose |
|---|---|---|
libnexa-ts |
^1.0.0 | Script, Opcode, Hash, Address primitives |
nexa-wallet-sdk |
^0.8.0 | Rostrum provider, transaction builders |
wallet-comms-sdk |
^0.7.2 | Wallet Comms Protocol mobile wallet pairing |
vue |
^3.4.0 | UI framework |
The entire codebase uses TypeScript with Tailwind CSS/Nuxt for styling.
Deployment Plan
Phase 1: Pseudonymous Voting (This BUIP)
Phase 1 replaces the existing VotePeer Android app with the web-based dApp described in this proposal. The current implementation provides pseudonymous voting — each voter’s public key hash is embedded in their constraint script, making votes linkable to a known public key but not to a real-world identity.
Limitations: A determined observer who knows the voter-to-pubkey mapping can determine how each member voted after ballots are cast. This is acceptable for BU governance where membership is public, but insufficient for scenarios requiring true ballot secrecy.
Phase 2: Ring Signature Anonymity (Potential future development)
Phase 2 could use ring signatures to enable anonymous voting similar to how the android app does it currently.
Potential Blockchain Voting as a Service Product
In the future, this voting solution can easily be turned into a white label service that BU would be able to provide/sell to other organisations as a Blockchain Voting as a Service product. This future development is out of the scope of this specific BUIP.