- Get governance action details
- Get overview of all proposals
- Get details on a specific proposal
Purpose: Ingest or update a single proposal and all its associated votes into the database
Parameters:
proposal_hash: The transaction hash of the proposal (maps totxHashin database)
Koios API Endpoints Used:
-
GET /proposal_list - Fetch proposal metadata
- Filter by
_tx_hashto get specific proposal - Returns: proposal details, status, epochs, metadata, governance action type
- Maps to:
Proposaltable fields (title, description, rationale, etc.) - TODO: Add inline docs to Proposal schema fields matching Koios response
- Filter by
-
GET /proposal_votes?_proposal_tx_hash={tx_hash} - Get all votes for the proposal
- Returns: Array of votes with voter info, vote type, and voting power
- Maps to:
OnchainVotetable - TODO: Add inline docs to OnchainVote schema fields matching Koios response
-
For each DRep voter (if not exists in DB):
- GET /drep_info?_drep_id={drep_id} - Get DRep details and current voting power
- Maps to:
Dreptable
-
For each SPO voter (if not exists in DB):
- GET /pool_info?_pool_bech32={pool_id} - Get pool details and current voting power
- Maps to:
SPOtable
-
For CC voters (if not exists in DB):
- GET /committee_info (or extract from vote metadata)
- Maps to:
CCtable
Database Operations (in Prisma transaction):
-
Upsert Proposal:
UPSERT WHERE proposalId = "{txHash}#{certIndex}" - If new: CREATE with all fields from Koios - If exists: UPDATE status, metadata, expiryEpoch (mutable fields only) -
For each vote from Koios:
- Check if voter exists (Drep/SPO/CC by unique ID)
- If voter doesn't exist: CREATE voter record with current voting power
- If voter exists: UPDATE voting power (can change between epochs)
- Upsert OnchainVote:
UPSERT WHERE (proposalId, voterType, drepId/spoId/ccId) - If new: CREATE vote record with all fields - If exists: UPDATE vote, votingPower, votingPowerAda (votes can change)
Retry Logic:
- Max retries: 3 attempts
- Retry delay: Exponential backoff (2s, 4s, 8s)
- Retry on: Network errors, Koios API 5xx errors
- Don't retry on: 4xx errors, validation errors
Response:
{
"success": true,
"proposal": {
"id": 123,
"proposalId": "abc123...#0",
"status": "ACTIVE"
},
"stats": {
"votesIngested": 150,
"votesUpdated": 25,
"votersCreated": { "dreps": 120, "spos": 25, "ccs": 5 },
"votersUpdated": { "dreps": 10, "spos": 3, "ccs": 0 }
}
}Implementation Files:
- Service:
src/services/ingestion/proposal.service.ts - Controller:
src/controllers/data/ingestProposal.ts - Route: Registered in
src/routes/data.route.ts - Types:
src/types/koios.types.ts(needs Koios field mappings)
Purpose: Ingest a single vote into the OnchainVote table
Status: Placeholder implementation - requires clarification on how to fetch vote by tx_hash from Koios
Implementation:
- Service:
src/services/ingestion/vote.service.ts - Controller:
src/controllers/data/ingestVote.ts
Purpose: Ingest or update a single DRep into the Drep table
Koios API: GET /drep_info?_drep_id={drep_id}
Implementation:
- Service:
src/services/ingestion/voter.service.ts(ingestDrep) - Controller:
src/controllers/data/ingestVoters.ts(postIngestDrep)
Purpose: Ingest or update a single SPO into the SPO table
Koios API: GET /pool_info?_pool_bech32={pool_id}
Implementation:
- Service:
src/services/ingestion/voter.service.ts(ingestSpo) - Controller:
src/controllers/data/ingestVoters.ts(postIngestSpo)
Purpose: Ingest or update a single Constitutional Committee member into the CC table
Koios API: TBD (may need to extract from vote metadata or dedicated endpoint)
Implementation:
- Service:
src/services/ingestion/voter.service.ts(ingestCc) - Controller:
src/controllers/data/ingestVoters.ts(postIngestCc)
Schedule: Every 5 minutes (configurable via PROPOSAL_SYNC_SCHEDULE env variable)
Job: Sync All Proposals
Process Flow:
-
Fetch all proposals from Koios
- Endpoint: GET /proposal_list (no filters, returns all proposals)
-
For each proposal (processed sequentially with retry):
- Call
ingestProposal(proposal_hash)service - Automatically handles:
- Upserting proposal data
- Fetching and upserting all votes
- Creating/updating voters (DReps, SPOs, CCs)
- If fails: Retry up to 3 times with exponential backoff
- If still fails: Log error and continue to next proposal
- Call
-
After all proposals processed:
- Log summary: X proposals synced, Y failed, Z votes updated
Error Handling:
- Individual proposal failures don't stop the entire sync
- Failed proposals are logged but sync continues
- Retry mechanism (via
withRetryutility) handles transient errors - Prisma transactions ensure atomic operations per proposal
Sync Strategy:
- Syncs ALL proposals every time (not incremental)
- Updates existing proposals if status/metadata changed
- Updates ALL votes (detects and updates changed votes)
- Updates voting power for all voters (can change between epochs)
Implementation Files:
- Job Scheduler:
src/jobs/sync-proposals.job.ts - Job Registry:
src/jobs/index.ts - Started from:
src/index.ts(callsstartAllJobs())
Configuration (.env):
# Koios API
KOIOS_BASE_URL=https://api.koios.rest/api/v1
KOIOS_API_KEY=your_api_key_here
# Cron Jobs
PROPOSAL_SYNC_SCHEDULE=*/5 * * * * # Every 5 minutes
ENABLE_CRON_JOBS=true
Database Tables Involved (in order):
Proposal- Upserted firstDrep/SPO/CC- Created/updated as voters are encounteredOnchainVote- Upserted with foreign keys to above tables
Dependencies:
node-cron- Cron scheduler@types/node-cron- TypeScript definitions