Overview
The API service is an Apollo GraphQL server that indexes governance data from multiple blockchain networks and protocols. It uses Checkpoint , a blockchain indexing framework, to process events and serve unified governance data.
The API supports Snapshot X , Compound Governor (Governor Bravo) , and OpenZeppelin Governor protocols across multiple EVM chains and Starknet.
Quick Start
Prerequisites
Node.js >= 22.6
MySQL 8.0
RPC endpoints for networks you want to index
Installation
Environment Setup
Copy .env.example to .env and configure:
DATABASE_URL = mysql://user:password@localhost:3306/sx_api
IS_INDEXER = true # Set to false for API-only mode
# Enable/disable protocols
ENABLE_SNAPSHOT_X = true # Default: true
ENABLE_GOVERNOR_BRAVO = true # Default: false
ENABLE_OPENZEPPELIN = true # Default: false
# Network RPC URLs
ETH_NODE_URL = https://...
SEPOLIA_NODE_URL = https://...
BASE_NODE_URL = https://...
# ... other networks
Start Development Server
With Docker (includes MySQL):
docker compose up mysql
yarn dev
The GraphQL API will be available at http://localhost:3000/graphql.
Architecture
Checkpoint Indexer
Checkpoint is a blockchain indexing framework that:
Processes blockchain events in real-time
Stores structured data in MySQL
Automatically generates GraphQL schema and resolvers
Supports multiple networks and protocols
The API can run in indexer mode (processes blocks) or API-only mode (serves data without indexing).
Modes of Operation
const IS_INDEXER = process . env . IS_INDEXER === 'true' ;
if ( IS_INDEXER ) {
// Start both API server and indexer
await startApiServer ( checkpoint );
await startIndexer ( checkpoint );
} else {
// API-only mode
await startApiServer ( checkpoint );
}
Supported Protocols
Snapshot X Hybrid onchain/offchain governance with cross-chain support
Compound Governor Governor Bravo standard used by Compound and forks
OpenZeppelin OpenZeppelin Governor contract standard
Enabling Protocols
From apps/api/src/evm/index.ts:
// SnapshotX runs by default unless explicitly disabled
export const ENABLE_SNAPSHOT_X = process . env . ENABLE_SNAPSHOT_X !== 'false' ;
export const ENABLE_GOVERNOR_BRAVO = process . env . ENABLE_GOVERNOR_BRAVO === 'true' ;
export const ENABLE_OPENZEPPELIN = process . env . ENABLE_OPENZEPPELIN === 'true' ;
const protocols : Protocols = {
snapshotX: ENABLE_SNAPSHOT_X ,
governorBravo: ENABLE_GOVERNOR_BRAVO ,
openZeppelin: ENABLE_OPENZEPPELIN
};
Supported Networks
EVM Networks
Ethereum Mainnet (eth)
Ethereum Sepolia (sep)
Optimism (oeth)
Polygon (matic)
Arbitrum One (arb1)
Base (base)
Mantle (mnt)
ApeChain (ape)
Curtis (curtis)
Starknet Networks
Starknet Mainnet
Starknet Sepolia
Each network can be configured with different protocol support.
GraphQL Schema
Checkpoint auto-generates a GraphQL schema from the data models. The schema includes:
Core Types
type Space {
id : String !
name : String
controller : String !
voting_delay : Int !
min_voting_period : Int !
max_voting_period : Int !
proposal_threshold : String !
validation_strategy : String !
strategies : [ Strategy ! ] !
proposals : [ Proposal ! ] !
}
type Proposal {
id : String !
proposal_id : Int !
space : Space !
author : User !
execution_hash : String !
metadata_uri : String !
start_block_number : Int !
min_end_block_number : Int !
max_end_block_number : Int !
snapshot_block_number : Int !
execution_strategy : String !
execution_strategy_type : String !
state : String !
votes : [ Vote ! ] !
vote_count : Int !
created : Int !
}
type Vote {
id : String !
voter : User !
proposal : Proposal !
choice : Int !
voting_power : String !
created : Int !
}
type User {
id : String !
proposal_count : Int !
vote_count : Int !
proposals : [ Proposal ! ] !
votes : [ Vote ! ] !
}
Example Queries
# Get all spaces
query Spaces {
spaces {
id
name
controller
proposal_count
}
}
# Get proposals for a space
query SpaceProposals ( $spaceId : String ! ) {
proposals ( where : { space : $spaceId }) {
id
proposal_id
author { id }
metadata_uri
state
vote_count
created
}
}
# Get votes for a proposal
query ProposalVotes ( $proposalId : String ! ) {
votes ( where : { proposal : $proposalId }) {
voter { id }
choice
voting_power
created
}
}
Package Scripts
From package.json:
{
"scripts" : {
"codegen" : "checkpoint generate" ,
"build" : "tsc" ,
"dev" : "nodemon src/index.ts" ,
"dev:debug" : "nodemon --exec \" node --inspect --require ts-node/register src/index.ts \" " ,
"start" : "node dist/src/index.js" ,
"test" : "vitest run" ,
"lint" : "eslint src/ --ext .ts" ,
"lint:fix" : "yarn lint --fix"
}
}
Key Commands
Development
Production
Code Generation
Testing
Database Configuration
The API supports multiple database connection methods:
function getDatabaseConnection () {
if ( process . env . DATABASE_URL ) {
return process . env . DATABASE_URL ;
}
if ( process . env . DATABASE_URL_INDEX ) {
return process . env [ `DATABASE_URL_ ${ process . env . DATABASE_URL_INDEX } ` ];
}
throw new Error ( 'No valid database connection URL found.' );
}
Using Docker
# Start MySQL on port 3306
docker compose up mysql
# Default connection from .env.example
DATABASE_URL = mysql://root:password@localhost:3306/sx_api
Checkpoint Configuration
Checkpoint is initialized with:
const checkpoint = new Checkpoint ( schema , {
logLevel: LogLevel . Info ,
resetOnConfigChange: true ,
pinoOptions ,
overridesConfig: overrides ,
dbConnection: getDatabaseConnection ()
});
Schema Generation
Run yarn codegen to generate the GraphQL schema:
This creates .checkpoint/schema.gql used by the UI and other clients.
Protocol Writers
Each protocol has dedicated “writers” that process blockchain events:
apps/api/src/evm/index.ts
function createWriters ( config : EVMConfig ) {
let writers : Record < string , evm . Writer > = {};
if ( config . snapshotXConfig ) {
writers = applyProtocolPrefixToWriters (
'snapshotX' ,
createSnapshotXWriters ( config , config . snapshotXConfig )
);
}
if ( config . governorBravoConfig ) {
writers = {
... writers ,
... applyProtocolPrefixToWriters (
'governorBravo' ,
createGovernorBravoWriters ( config , config . governorBravoConfig )
)
};
}
if ( config . openZeppelinConfig ) {
writers = {
... writers ,
... applyProtocolPrefixToWriters (
'openZeppelin' ,
createOpenZeppelinWriters ( config , config . openZeppelinConfig )
)
};
}
return writers ;
}
Dependencies
Key dependencies from package.json:
{
"dependencies" : {
"@apollo/server" : "^5.1.0" ,
"@snapshot-labs/checkpoint" : "^0.1.0-beta.67" ,
"@snapshot-labs/sx" : "^0.1.0" ,
"express" : "^4.21.2" ,
"graphql" : "^16.11.0" ,
"starknet" : "7.6.4" ,
"viem" : "^2.38.0" ,
"pino" : "^9.9.0" ,
"dotenv" : "^16.0.1"
}
}
Logging
The API uses Pino for structured logging with optional Logtail integration:
import logger from './logger' ;
logger . info ( 'Indexer started' );
logger . error ({ err }, 'Failed to process block' );
logger . fatal ({ err }, 'Uncaught exception' );
Testing
Tests use Vitest and require MySQL to be running.
Production Deployment
Build the application:
Set environment variables
Start the service:
For high availability, run multiple API-only instances (IS_INDEXER=false) behind a load balancer, with a single indexer instance.
GraphQL Playground
Access the interactive GraphQL playground at:
http://localhost:3000/graphql
Using with Local Checkpoint
For development with a local Checkpoint instance:
cd checkpoint/node_modules/graphql
yarn link
cd sx-api
yarn link graphql
This ensures a single copy of the graphql package to avoid conflicts.