Skip to main content
Snapshot X is built as a modular monorepo where three core services work together to provide a complete governance infrastructure. This guide explains the system architecture, data flows, and how components interact.

High-Level Architecture

The Snapshot X platform consists of three main services plus external dependencies:

Core Services

UI - Vue 3 Frontend

Location: apps/ui Purpose: User interface for governance interactions Technology Stack:
  • Vue 3 with Composition API
  • TailwindCSS for styling (custom theme)
  • Vite for build tooling
  • Pinia for state management
  • Vue Query (TanStack Query) for server state
  • Auto-imports for components, composables, and APIs
Key Responsibilities:
  1. Display governance data (spaces, proposals, votes)
  2. Connect user wallets (MetaMask, ArgentX, etc.)
  3. Create and submit proposals
  4. Cast votes through various authenticators
  5. Manage space configurations
Data Sources:
  • Onchain spaces: GraphQL queries to API
  • Offchain spaces: GraphQL queries to snapshot-hub
  • Transactions: JSON-RPC calls to Mana for gasless operations
  • Direct transactions: Direct blockchain calls for non-gasless operations
File Structure:
apps/ui/src/
├── components/     # Auto-imported Vue components
├── composables/    # Auto-imported composition functions
├── stores/         # Pinia stores for client state
├── queries/        # Vue Query composables for server state
├── networks/       # Network-specific implementations
├── helpers/        # Pure utility functions
├── views/          # Page-level components
└── routes/         # Vue Router definitions

API - GraphQL + Checkpoint Indexer

Location: apps/api Purpose: Index blockchain events and serve governance data Technology Stack:
  • Apollo Server (GraphQL)
  • Checkpoint (blockchain indexer)
  • MySQL 8.0 (data storage)
  • TypeScript
Key Responsibilities:
  1. Index governance events from multiple chains:
    • Snapshot X spaces on EVM and Starknet
    • Compound Governor contracts
    • OpenZeppelin Governor contracts
  2. Transform blockchain data into queryable format
  3. Serve unified GraphQL API for spaces, proposals, votes, and users
  4. Support real-time subscriptions for live updates
Indexing Process: Modes of Operation:
  • Full mode: Run both indexer and GraphQL API
  • Indexer only: Index events without serving API
  • API only: Serve API using existing database
Configuration:
// apps/api/src/evm/config.ts
export const config = {
  networks: ['sepolia', 'mainnet'],
  startBlock: 4000000, // Starting block for indexing
  contracts: [
    // Space factories, authenticators, strategies
  ]
};

Mana - Transaction Relayer

Location: apps/mana Purpose: Relay transactions for gasless voting and cross-chain execution Technology Stack:
  • Express.js (HTTP server)
  • ethers.js (Ethereum interactions)
  • starknet.js (Starknet interactions)
  • TypeScript
Key Responsibilities:
  1. Gasless voting: Accept signed messages and submit on behalf of users
  2. Cross-chain messaging: Handle L1↔L2 communication via Starknet messaging
  3. Automated execution: Execute approved proposals automatically
  4. Relayer wallet management: Manage hot wallets for transaction submission
API Endpoints:
// JSON-RPC interface
POST /api/:network

Methods:
- eth_sendTransaction      // Submit EVM transaction
- starknet_sendTransaction  // Submit Starknet transaction
- relay_vote               // Relay gasless vote
- relay_propose            // Relay gasless proposal
Transaction Flow (Gasless Voting): Environment Variables:
# apps/mana/.env
WALLET_SECRET=           # Private key for relayer wallet
HERODOTUS_API_KEY=       # For L1→L2 messaging
HERODOTUS_LEGACY_API_KEY= # Legacy L1→L2 support
ETH_RPC_URL=             # Ethereum RPC endpoint
STARKNET_RPC_URL=        # Starknet RPC endpoint

SX.js - TypeScript SDK

Location: packages/sx.js Purpose: Shared SDK for governance interactions Published Package: @snapshot-labs/sx on npm Key Exports:
import { 
  clients,      // Client classes for all networks
  utils,        // Utility functions
  getExecutionData, // Execution data helpers
  // Network configs
  evmSepolia,
  evmMainnet,
  starknetMainnet,
  offchainMainnet
} from '@snapshot-labs/sx';
Client Architecture:
// Base client types
interface Client {
  propose(params): Promise<Envelope | Transaction>;
  vote(params): Promise<Envelope | Transaction>;
  updateProposal(params): Promise<Envelope | Transaction>;
  execute(params): Promise<Transaction>;
}

// Signature-based clients (for gasless voting)
- EvmEthereumSig
- StarknetSig
- OffchainEthereumSig

// Transaction-based clients (user pays gas)
- EvmEthereumTx
- StarknetTx
Strategy System:
// packages/sx.js/src/strategies/
├── evm/
│   ├── Vanilla         // Simple voting (1 address = 1 vote)
│   ├── Comp            // Compound token balances
│   ├── OZVotes         // OpenZeppelin Votes
│   └── Whitelist       // Whitelist-based voting
├── starknet/
│   ├── MerkleWhitelist
│   ├── ERC20Votes
│   └── EVMSlotValue    // Cross-chain storage proofs
└── offchain/
    └── Various strategies via Snapshot Hub

External Services

snapshot-hub

Repository: snapshot-labs/snapshot-hub Purpose: API and data store for offchain Snapshot spaces Integration Point: UI queries snapshot-hub for offchain governance data Data Stored:
  • Offchain space configurations
  • Offchain proposals (ENS-based spaces like uniswap.eth)
  • Offchain votes
  • User profiles and followers

snapshot-sequencer

Repository: snapshot-labs/snapshot-sequencer Purpose: Receives and validates signed messages for offchain governance Integration Point: UI submits signed proposals, votes, and space updates Message Types:
  • Proposals
  • Votes
  • Space settings updates
  • User profile updates

Data Flow Examples

Creating an Onchain Proposal

Casting a Vote

Cross-Chain Voting (L1 → L2)

Deployment Architecture

Development Setup

# Run all services locally
yarn dev:interactive

# Select services to run:
# ☑ UI (port 8080)
# ☑ API (port 3000)
# ☑ Mana (port 3001)
Local Dependencies:
  • Docker (for MySQL)
  • Node.js >= 22.6
  • Yarn 1.22

Production Architecture

┌─────────────────┐
│   CloudFlare    │  CDN + DDoS Protection
└────────┬────────┘

┌────────▼────────┐
│   UI (Static)   │  Deployed to CDN
└────────┬────────┘

    ┌────┴────┐
    │         │
┌───▼──┐  ┌──▼───┐
│ API  │  │ Mana │  Kubernetes Pods
└───┬──┘  └──┬───┘
    │        │
┌───▼────────▼───┐
│  MySQL Cluster │  RDS / CloudSQL
└────────────────┘

Key Design Decisions

Why Separate API and Mana?

API is read-heavy and stateless, making it easy to scale horizontally. Mana manages hot wallets and transaction queues, requiring careful state management and security.

Why Checkpoint for Indexing?

Checkpoint provides automatic reorg handling, checkpoint/resume functionality, and a unified interface for multiple chains. It reduces boilerplate code for blockchain indexing.

Why Monorepo?

  • Shared code (sx.js) is easily reused across services
  • Coordinated releases and versioning
  • Single dependency management
  • Easier local development with all services

Why Two Types of Clients?

  • Signature clients enable gasless voting through Mana relay
  • Transaction clients allow direct blockchain interaction when users pay gas
  • Flexibility for different governance models and user preferences

Security Considerations

Mana Security: Mana manages hot wallets with real funds. Always:
  • Use separate wallets for each network
  • Implement rate limiting on endpoints
  • Validate all signatures before relaying
  • Monitor wallet balances and set alerts
  • Use hardware security modules (HSM) in production
Signature Validation: Always verify EIP-712 signatures match expected:
  • Domain separator
  • Message structure
  • Signer authorization
  • Replay protection (nonce/timestamp)

Performance Optimization

API Optimization

  • Database Indexing: Index frequently queried columns (space_id, proposal_id, voter)
  • Query Batching: Use DataLoader pattern in GraphQL resolvers
  • Caching: Cache space configurations and static data
  • Pagination: Always paginate large result sets

UI Optimization

  • Code Splitting: Lazy-load routes and heavy components
  • Vue Query Caching: Leverage stale-while-revalidate pattern
  • Auto-imports: Tree-shake unused components and composables
  • Image Optimization: Use CDN for user-uploaded images

Mana Optimization

  • Transaction Queuing: Queue transactions to avoid nonce conflicts
  • Gas Estimation: Cache gas estimates for common operations
  • Batch Processing: Batch multiple votes in single transaction where possible
  • Retry Logic: Implement exponential backoff for failed transactions

Monitoring and Observability

Key Metrics to Track:
  • Query response times
  • Database connection pool usage
  • Indexer sync lag (blocks behind head)
  • GraphQL error rates
  • Transaction success/failure rates
  • Wallet balance levels
  • Transaction queue depth
  • Average gas used per operation
  • Page load times
  • Time to interactive (TTI)
  • Core Web Vitals (LCP, FID, CLS)
  • JavaScript bundle size

Next Steps

Quickstart Guide

Build your first governance integration

SDK Overview

Explore detailed SDK documentation

Contributing

Contribute to the Snapshot X platform

GitHub

View the source code

Build docs developers (and LLMs) love