Skip to main content

Overview

Mana is an Express.js transaction relayer service that enables gasless voting and automated proposal execution for Snapshot X governance. It handles cross-chain messaging between Layer 1 (Ethereum) and Layer 2 (Starknet) networks.
Mana allows users to vote and create proposals without paying gas fees by relaying transactions on their behalf.

Key Features

Gasless Voting

Submit votes without paying transaction fees

Cross-Chain Messaging

Bridge governance actions between L1 and L2

Automated Execution

Execute proposals when conditions are met

Multi-Network Support

Support for Ethereum, Starknet, and multiple chains

Quick Start

Prerequisites

  • Node.js >= 22.6
  • PostgreSQL database
  • Wallet with sufficient funds for gas on supported networks

Installation

cd apps/mana
yarn install

Environment Configuration

Copy .env.example to .env and configure:
.env.example
WALLET_SECRET=                    # Required: Mnemonic or private key
STARKNET_MAINNET_RPC_URL=https://starknet-mainnet.infura.io/v3/...
STARKNET_SEPOLIA_RPC_URL=https://starknet-sepolia.infura.io/v3/...
ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/...
ETH_SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/...
HERODOTUS_API_KEY=                # For storage proofs
HERODOTUS_LEGACY_API_KEY=
DATABASE_URL=postgres://postgres:password@localhost:5432/mana
BROKESTER_API_URL=https://api.brokester.box
DEBUG_SECRET=                     # Optional: for debug endpoints
WALLET_SECRET is required. The service will exit if not provided.

Start Development Server

yarn dev
The service will:
  1. Run database migrations
  2. Start the Express server
  3. Begin monitoring for registered transactions and proposals
Default port: Check PORT in src/constants.ts

Package Scripts

From package.json:
{
  "scripts": {
    "dev": "nodemon src/index.ts",
    "build": "tsc",
    "start": "node dist/src/index.js",
    "migrate": "yarn knex migrate:latest",
    "check-starknet-balances": "node --experimental-strip-types scripts/check-starknet-balances.mts",
    "lint": "eslint . --ext .ts",
    "lint:fix": "yarn lint --fix"
  }
}

Key Commands

yarn dev  # Runs migrations first, then starts server

Architecture

JSON-RPC Endpoints

Mana exposes two main RPC endpoints:
apps/mana/src/index.ts
app.use('/eth_rpc', ethRpc);    // EVM chains
app.use('/stark_rpc', starkRpc); // Starknet

Background Loops

Three continuous processes monitor and execute governance actions:
apps/mana/src/index.ts
async function start() {
  // Monitor and execute registered Starknet transactions
  registeredTransactionsLoop();
  
  // Monitor and execute Starknet proposals
  registeredProposalsLoop();
  
  // Monitor and execute ApeChain gas-optimized proposals
  registeredApeGasProposalsLoop();

  const server = app.listen(PORT, () =>
    logger.info(`Listening at http://localhost:${PORT}`)
  );
}

How It Works

Gasless Voting Flow

  1. User submits vote to Mana via JSON-RPC
  2. Mana validates the vote signature
  3. Mana relays the transaction to the blockchain
  4. User pays no gas - Mana’s wallet covers the cost

Cross-Chain Execution (L1 ↔ L2)

L2 → L1 (Starknet to Ethereum):
  1. Proposal passes on Starknet
  2. Execution hash committed to L1
  3. Mana monitors Starknet Core contract on Ethereum
  4. When message arrives, Mana executes on L1
L1 → L2 (Ethereum to Starknet):
  1. Action triggered on Ethereum
  2. Message sent to Starknet Core
  3. Mana relays message to Starknet
  4. Execution occurs on L2

Automated Proposal Execution

Mana continuously monitors proposals and:
  • Checks if voting period has ended
  • Verifies quorum and threshold requirements
  • Automatically executes passed proposals
  • Handles execution failures and retries

API Endpoints

Health Check

GET /
Returns service status and poster wallet addresses:
{
  "version": "0.1.0#abc1234",
  "port": 3001,
  "posterWallets": {
    "base": "0x...",
    "sep": "0x..."
  }
}

Debug Endpoints

GET /debug/heapdump
Header: secret: <DEBUG_SECRET>
Generates and downloads a heap snapshot for memory debugging.
Debug endpoints require DEBUG_SECRET to be set and provided in the request header.

Wallet Management

Mana manages multiple wallets derived from WALLET_SECRET:
const posterWallets = {
  base: ethHandlers[8453]?.getWallet('poster').address,
  sep: ethHandlers[11155111]?.getWallet('poster').address
};
Ensure these wallets are funded with native tokens for gas:
yarn check-starknet-balances

Database Schema

Mana uses PostgreSQL with Knex.js migrations to track:
  • Registered transactions awaiting execution
  • Proposal execution status
  • Cross-chain message tracking
  • Execution attempts and results

Running Migrations

Migrations run automatically on yarn dev and yarn start, or manually:
yarn migrate

Supported Networks

EVM Networks

Configured via ethHandlers indexed by chain ID:
  • Base (8453)
  • Sepolia (11155111)
  • Additional networks as configured

Starknet Networks

  • Starknet Mainnet
  • Starknet Sepolia

Dependencies

Key dependencies from package.json:
{
  "dependencies": {
    "express": "^4.21.2",
    "@snapshot-labs/sx": "^0.1.0",
    "starknet": "7.6.4",
    "@ethersproject/providers": "^5.8.0",
    "@ethersproject/wallet": "^5.8.0",
    "knex": "^3.1.0",
    "pg": "^8.11.3",
    "pino": "^9.8.0",
    "cors": "^2.8.5",
    "dotenv": "^16.0.0",
    "async-mutex": "^0.4.0",
    "zod": "^3.22.4"
  }
}

Security Considerations

Critical Security Notes:
  • Never commit WALLET_SECRET or .env files
  • Rotate secrets regularly
  • Monitor wallet balances to prevent service interruption
  • Restrict DEBUG_SECRET access in production
  • Use read-only database replicas where possible

Logging

Mana uses Pino for structured logging:
import logger from './logger';

logger.info('Transaction relayed successfully');
logger.error({ err }, 'Failed to execute proposal');
logger.fatal({ err }, 'Uncaught exception');
Optional Logtail integration for centralized logging.

Monitoring

Health Checks

Monitor the root endpoint:
curl http://localhost:3001/

Wallet Balances

Regularly check relayer wallet balances:
yarn check-starknet-balances
Set up alerts when wallet balances drop below operational thresholds to prevent service disruption.

Database Monitoring

Monitor:
  • Transaction queue depth
  • Failed execution attempts
  • Average execution time
  • Database connection pool usage

Error Handling

Mana includes comprehensive error handling:
apps/mana/src/index.ts
process.on('uncaughtException', err => {
  logger.fatal({ err }, 'Uncaught exception');

  server.close(() => {
    process.exit(1);
  });
});

Production Deployment

  1. Build the application:
    yarn build
    
  2. Set production environment variables
  3. Fund relayer wallets with sufficient gas tokens
  4. Start the service:
    yarn start
    
  5. Set up monitoring and alerts
For high availability, run multiple Mana instances with a shared PostgreSQL database and load balancer.

Integration with UI

The UI submits gasless transactions to Mana:
// In UI composables/useActions.ts (example)
const submitVote = async (vote) => {
  const response = await fetch('https://mana.snapshot.org/stark_rpc', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'vote',
      params: [vote],
      id: 1
    })
  });
  
  return await response.json();
};

Troubleshooting

Service won’t start

  • Verify WALLET_SECRET is set
  • Check database connection (DATABASE_URL)
  • Ensure PostgreSQL is running

Transactions not executing

  • Check wallet balances
  • Verify RPC endpoint connectivity
  • Review logs for errors
  • Check database for stuck transactions

High memory usage

  • Use /debug/heapdump endpoint to analyze
  • Review background loop performance
  • Check for memory leaks in long-running processes

Repository

Source code: snapshot-labs/sx-monorepo Package directory: apps/mana

License

MIT

Build docs developers (and LLMs) love