The Votes API provides access to on-chain and off-chain voting data, including vote history for delegates and proposals.
Get Votes for Address
GET /api/common/votes
JavaScript
Python
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://vote.ens.domains/api/common/votes?address=0x1234...5678&blockNumber=18500000&proposalId=123"
Location in code: src/app/api/common/votes/route.ts:3
Query Parameters
Ethereum address of the voter
Block number to query votes at
Proposal ID to get votes for
Response
Returns all votes (direct and advanced) for the specified address on the proposal.
[
{
"transactionHash" : "0xabc...123" ,
"address" : "0x1234567890abcdef1234567890abcdef12345678" ,
"proposalId" : "123" ,
"support" : 1 ,
"weight" : "50000000000000000000000" ,
"reason" : "This proposal improves the protocol." ,
"blockNumber" : "18525000" ,
"params" : null ,
"proposalTitle" : "Upgrade Treasury Timelock" ,
"proposalType" : "STANDARD"
}
]
Vote Structure
Transaction hash of the vote
Unique proposal identifier
Vote choice:
0 = Against
1 = For
2 = Abstain
For approval voting: option index (0, 1, 2, …)
Voting power used (in wei for token-based, count for NFT-based)
Optional vote reason/comment from voter
Block number when vote was cast
Additional vote parameters (for advanced voting types)
Type of proposal (STANDARD, APPROVAL, OPTIMISTIC, etc.)
ISO timestamp of the vote
Type of citizen voter (for Optimism Citizens’ House)
Additional voter metadata
Get Votes for Delegate
Location in code: src/app/api/common/votes/getVotes.ts:29
Fetches all votes cast by a specific delegate across all proposals.
Parameters
{
addressOrENSName : string ;
pagination ?: {
limit: number ; // Default: 20
offset : number ; // Default: 0
};
}
Response
{
"meta" : {
"has_next" : true ,
"next_offset" : 20 ,
"total_returned" : 20 ,
"total_count" : 87
},
"data" : [
{
"transactionHash" : "0xabc...123" ,
"address" : "0x1234567890abcdef1234567890abcdef12345678" ,
"proposalId" : "123" ,
"support" : 1 ,
"weight" : "50000000000000000000000" ,
"reason" : "Strong support for this upgrade." ,
"blockNumber" : "18525000" ,
"proposalTitle" : "Upgrade Treasury Timelock" ,
"proposalType" : "STANDARD" ,
"timestamp" : "2024-01-15T14:23:45Z"
}
]
}
Get Votes for Proposal
Location in code: src/app/api/common/votes/getVotes.ts:585
Fetches all votes cast on a specific proposal.
Parameters
{
proposalId : string ;
pagination ?: {
limit: number ; // Default: 20, Max: varies by endpoint
offset : number ; // Default: 0
};
sort ?: "weight" | "block_number" ; // Default: "weight"
offchainProposalId ?: string ; // For offchain proposals
}
Response
{
"meta" : {
"has_next" : true ,
"next_offset" : 20 ,
"total_returned" : 20 ,
"total_count" : 156
},
"data" : [
{
"address" : "0x1234567890abcdef1234567890abcdef12345678" ,
"support" : 1 ,
"weight" : "50000000000000000000000" ,
"reason" : "Strongly in favor of this proposal." ,
"transactionHash" : "0xabc...123" ,
"blockNumber" : "18525000" ,
"proposalId" : "123" ,
"proposalTitle" : "Upgrade Treasury Timelock" ,
"proposalType" : "STANDARD"
}
]
}
Get Non-Voters for Proposal
Location in code: src/app/api/common/votes/getVotes.ts:443
Fetches delegates who have not voted on a specific proposal.
Parameters
{
proposalId : string ;
pagination ?: {
limit: number ; // Default: 20
offset : number ; // Default: 0
};
offchainProposalId ?: string ;
type ?: "TH" | "APP" | "CHAIN" | "USER" ; // Voter type
}
Voter Types
Type of voters to query:
TH = Token House (on-chain voters)
APP = Application citizens (Optimism)
CHAIN = Chain citizens (Optimism)
USER = User citizens (Optimism)
Response
{
"meta" : {
"has_next" : true ,
"next_offset" : 20 ,
"total_returned" : 20 ,
"total_count" : 1234
},
"data" : [
{
"delegate" : "0x1234567890abcdef1234567890abcdef12345678" ,
"voting_power" : "5000000000000000000000" ,
"citizen_type" : "app" ,
"voterMetadata" : {
"name" : "Protocol Name" ,
"image" : "https://..." ,
"type" : "application"
},
"twitter" : "@handle" ,
"discord" : "user#1234" ,
"warpcast" : null
}
]
}
Snapshot Votes
Location in code: src/app/api/common/votes/getVotes.ts:308
The API also supports Snapshot (off-chain) votes.
Get Snapshot Votes for Delegate
{
addressOrENSName : string ;
pagination ?: PaginationParams ;
}
Get Snapshot Votes for Proposal
{
proposalId : string ;
pagination ?: PaginationParams ;
sort ?: string ; // Default: "vp" (voting power)
}
Snapshot Vote Structure
{
"id" : "0xsnapshotid" ,
"address" : "0x1234567890abcdef1234567890abcdef12345678" ,
"createdAt" : "2024-01-15T14:23:45Z" ,
"choice" : "Option 1" ,
"votingPower" : 5000 ,
"title" : "Proposal Title" ,
"reason" : "I support this because..." ,
"choiceLabels" : {
"1" : "Option 1" ,
"2" : "Option 2"
}
}
Vote Aggregation
Votes from multiple sources are aggregated:
Direct Votes : Standard on-chain votes via Governor contract
Advanced Votes : Votes using Alligator (liquid delegation)
Snapshot Votes : Off-chain votes via Snapshot
Citizen Votes : Optimism Citizens’ House votes
Location in code: src/app/api/common/votes/getVotes.ts:585
-- Vote aggregation query
SELECT
STRING_AGG (transaction_hash, '|' ) as transaction_hash,
proposal_id,
voter,
support,
SUM ( weight ) as weight ,
STRING_AGG ( distinct reason, '\n --------- \n' ) as reason,
MAX (block_number) as block_number,
params
FROM (
SELECT * FROM vote_cast_events WHERE proposal_id = $ 1
UNION ALL
SELECT * FROM vote_cast_with_params_events WHERE proposal_id = $ 1
UNION ALL
SELECT * FROM citizen_votes WHERE proposal_id = $ 1
) t
GROUP BY proposal_id, voter, support, params
ORDER BY weight DESC
Vote Counting
Location in code: src/app/api/common/votes/getVotes.ts:910
Count the number of distinct proposals a delegate has voted on:
async function getVotesCountForDelegateForAddress ({
address : string ;
}) : Promise < number > {
// Returns count of distinct proposals voted on
}
Response
Vote Participation Metrics
Location in code: src/app/api/common/delegates/getDelegates.ts:863
async function getVoterStats (
addressOrENSName : string ,
blockNumberOrTimestamp ?: number
) : Promise < VoterStats > {
// Returns participation statistics
}
Response
{
"voter" : "0x1234567890abcdef1234567890abcdef12345678" ,
"last_10_props" : "8" ,
"total_proposals" : "100"
}
Advanced Voting Types
Approval Voting
For approval voting proposals, the support field contains the option index:
{
"support" : 2 , // Voting for option 2
"params" : "[2]" , // Option index in params
"proposalType" : "APPROVAL"
}
Ranked Choice
Ranked choice votes include multiple options:
{
"support" : 1 ,
"params" : "[1,3,2]" , // Ranked preferences
"proposalType" : "RANKED_CHOICE"
}
Optimistic Voting
Optimistic proposals can be vetoed:
{
"support" : 0 , // 0 = veto
"proposalType" : "OPTIMISTIC"
}
Data Sources
Vote data is aggregated from:
On-chain Events
vote_cast_events - Standard vote events
vote_cast_with_params_events - Advanced voting events
delegate_changed_events - Delegation changes
Snapshot Database
snapshot.votes - Off-chain votes
snapshot.proposals - Off-chain proposals
Citizens’ House (Optimism)
atlas.votes_with_meta_mat - Citizen votes with metadata
Filtering and Sorting
Sort by Voting Power
GET /api/v1/proposals/123/votes?sort=weight
Sort by Timestamp
GET /api/v1/proposals/123/votes?sort=block_number
GET /api/v1/proposals/123/votes?limit= 50 & offset = 100
Use Cases
Get Delegate Voting History
const response = await fetch (
'https://vote.ens.domains/api/common/votes/delegate/0x1234...5678?limit=100' ,
{ headers: { 'Authorization' : 'Bearer YOUR_API_KEY' } }
);
Analyze Proposal Votes
const response = await fetch (
'https://vote.ens.domains/api/v1/proposals/123/votes?sort=weight&limit=1000' ,
{ headers: { 'Authorization' : 'Bearer YOUR_API_KEY' } }
);
Find Non-Voters
const response = await fetch (
'https://vote.ens.domains/api/v1/proposals/123/non-voters?limit=100' ,
{ headers: { 'Authorization' : 'Bearer YOUR_API_KEY' } }
);
Track Participation
const response = await fetch (
'https://vote.ens.domains/api/v1/delegates/0x1234...5678/stats' ,
{ headers: { 'Authorization' : 'Bearer YOUR_API_KEY' } }
);
Error Responses
Missing Parameters
{
"error" : "Missing address, blockNumber, or proposalId" ,
"status" : 400
}
Invalid Address
{
"error" : "Invalid address format" ,
"status" : 400
}
Proposal Not Found
{
"error" : "Proposal not found" ,
"status" : 404
}