Problem
Ballot creation is the admin entry point for the entire AnonVote
lifecycle and it is broken across three layers. The ballot creation
form on the frontend has no deadline picker, no option management
UI, and no CSV upload capability. The backend has no CSV parsing
or eligibility ingestion route, and raw identifiers are currently
being written directly to the database without being hashed first
— a critical privacy violation. Token issuance runs after
eligibility upload but has no error handling, no retry mechanism,
and no audit trail, meaning failed token deliveries are silent
and unrecoverable.
These three problems together mean an admin cannot successfully
set up a ballot from start to finish without manual database
intervention.
Solution
Implement the full ballot creation, eligibility upload, and token
issuance flow so an admin can create a ballot, upload a voter
list, and have tokens issued and delivered without any manual steps.
Backend
- Add POST /admin/ballots route that accepts title, options array,
and deadline — validate all fields and return structured errors
for missing or invalid inputs
- Call create_ballot on the Soroban contract via Stellar SDK
immediately after database write — store the transaction ID
against the ballot record
- Add POST /admin/ballots/:id/eligibility route that accepts
a CSV file upload via multipart form
- Parse and validate the CSV — reject malformed files early with
row-level error reporting
- Pass every identifier through hashIdentifier from @anonvote/crypto
before any database write — raw identifiers must never be persisted
- Handle duplicate identifiers gracefully — skip and include in
the import summary response
- After eligibility upload, trigger token issuance — generate a
token per voter via generateToken, store only the hash via
hashToken, and deliver the raw token via Resend email
- Log each token issuance attempt to the audit log — success or
failure — without logging the token value itself
- Implement a retry queue for failed token deliveries — failed
sends must not be silently dropped
Frontend
- Build the ballot creation form — title input, dynamic option
adder, deadline date/time picker with timezone display
- Add CSV upload component with drag and drop support, file
validation feedback, and import summary display after upload
- Show token issuance progress after eligibility upload — how
many tokens sent, how many pending, how many failed
- Allow admin to manually trigger a resend for failed token
deliveries from the dashboard
Bug Fixes
- Raw voter identifiers are currently written to the eligibility
table without hashing — this is a critical privacy bug that
must be fixed as part of this issue before any other eligibility
logic is touched
- The ballot creation route returns 500 when the Soroban contract
call fails instead of gracefully handling the failure and
queuing a retry
- The admin dashboard shows all ballots in a flat list with no
status filtering — fix to show draft, active, and closed
ballots in separate tabs
Acceptance Criteria
Note for contributors
The raw identifier hashing bug must be the first thing fixed
before any other work in this issue is started. Do not add
new eligibility logic on top of the existing broken flow —
fix the foundation first. All token values are single-use
secrets. They must never appear in logs, database records,
or API responses — only their SHA-256 hashes are stored.
Problem
Ballot creation is the admin entry point for the entire AnonVote
lifecycle and it is broken across three layers. The ballot creation
form on the frontend has no deadline picker, no option management
UI, and no CSV upload capability. The backend has no CSV parsing
or eligibility ingestion route, and raw identifiers are currently
being written directly to the database without being hashed first
— a critical privacy violation. Token issuance runs after
eligibility upload but has no error handling, no retry mechanism,
and no audit trail, meaning failed token deliveries are silent
and unrecoverable.
These three problems together mean an admin cannot successfully
set up a ballot from start to finish without manual database
intervention.
Solution
Implement the full ballot creation, eligibility upload, and token
issuance flow so an admin can create a ballot, upload a voter
list, and have tokens issued and delivered without any manual steps.
Backend
and deadline — validate all fields and return structured errors
for missing or invalid inputs
immediately after database write — store the transaction ID
against the ballot record
a CSV file upload via multipart form
row-level error reporting
before any database write — raw identifiers must never be persisted
the import summary response
token per voter via generateToken, store only the hash via
hashToken, and deliver the raw token via Resend email
failure — without logging the token value itself
sends must not be silently dropped
Frontend
adder, deadline date/time picker with timezone display
validation feedback, and import summary display after upload
many tokens sent, how many pending, how many failed
deliveries from the dashboard
Bug Fixes
table without hashing — this is a critical privacy bug that
must be fixed as part of this issue before any other eligibility
logic is touched
call fails instead of gracefully handling the failure and
queuing a retry
status filtering — fix to show draft, active, and closed
ballots in separate tabs
Acceptance Criteria
in a single flow
or API responses at any point
and rejection reasons
the admin dashboard
it is logged and queued for retry
handling, token generation, failed delivery retry
to token delivery confirmation
Note for contributors
The raw identifier hashing bug must be the first thing fixed
before any other work in this issue is started. Do not add
new eligibility logic on top of the existing broken flow —
fix the foundation first. All token values are single-use
secrets. They must never appear in logs, database records,
or API responses — only their SHA-256 hashes are stored.