Skip to main content

Overview

Agora is built as a multi-tenant platform that can be customized for different DAOs. This guide covers how to customize your instance with tenant configuration, UI themes, branding, and feature toggles.
Agora uses a tenant-based architecture where each DAO has its own configuration, contracts, tokens, and UI theme.

Tenant Configuration

The tenant system is the foundation of Agora’s customization capabilities.

Understanding Tenants

A tenant represents a DAO instance with its own:
  • Namespace: Unique identifier (e.g., ens, optimism, uniswap)
  • Contracts: Governance contract addresses
  • Token: Governance token configuration
  • UI Theme: Colors, logos, branding
  • Slug: Database schema identifier

Tenant Architecture

The tenant system is implemented across several files:
// src/lib/tenant/tenant.ts
export default class Tenant {
  private static instance: Tenant;
  
  private readonly _namespace: TenantNamespace;
  private readonly _contracts: TenantContracts;
  private readonly _token: TenantToken;
  private readonly _ui: TenantUI;
  private readonly _slug: DaoSlug;
  private readonly _brandName: string;
  
  // Singleton pattern
  public static current(): Tenant {
    if (!Tenant.instance) {
      Tenant.instance = new Tenant();
    }
    return Tenant.instance;
  }
}

Setting Up a New Tenant

1

Configure tenant namespace

Set the instance name in your environment variables:
NEXT_PUBLIC_AGORA_INSTANCE_NAME=ens
NEXT_PUBLIC_AGORA_INSTANCE_TOKEN=ENS
NEXT_PUBLIC_AGORA_ENV=prod
Supported tenants:
  • ens - ENS DAO
  • optimism - Optimism Collective
  • uniswap - Uniswap DAO
  • etherfi - EtherFi
  • boost - Boost
  • cyber - Cyber
  • derive - Derive
  • scroll - Scroll
  • linea - Linea
  • b3 - B3
  • pguild - Protocol Guild
  • xai - Xai
2

Add contract configuration

Create contract configuration in src/lib/tenant/tenantContractFactory.ts:
case "your-dao":
  return {
    governor: {
      address: "0x...", // Governor contract address
      abi: GovernorABI,
    },
    token: {
      address: "0x...", // Token contract address
      abi: TokenABI,
    },
    alligator: {
      address: "0x...", // Alligator (advanced delegation)
      abi: AlligatorABI,
    },
    // Add other contracts as needed
  };
3

Configure token details

Add token configuration in src/lib/tenant/tenantTokenFactory.ts:
case "your-dao":
  return {
    symbol: "TOKEN",
    name: "Your Token",
    decimals: 18,
    address: "0x...",
  };
4

Set database slug

Add database slug mapping in src/lib/tenant/tenantSlugFactory.ts:
case "your-dao":
  return "yourdao" as DaoSlug;
This corresponds to the database schema name (e.g., yourdao.proposals_v2).
5

Configure UI theme

Add UI theme in src/lib/tenant/tenantUIFactory.ts:
case "your-dao":
  return new TenantUI({
    title: "Your DAO Governance",
    hero: {
      headline: "Participate in Your DAO",
      description: "Vote, delegate, and participate in governance",
    },
    ui: {
      toggle: "default", // or "op" for Optimism-style
    },
  });

Brand Name Customization

Customize brand names in src/lib/tenant/tenant.ts:
export const BRAND_NAME_MAPPINGS: Record<string, string> = {
  ens: "ENS",
  etherfi: "EtherFi",
  pguild: "Protocol Guild",
  boost: "Boost",
  demo: "Canopy",
  "your-dao": "Your DAO Name",
};

UI Themes and Styling

Agora uses a combination of Tailwind CSS, SCSS, and Emotion for styling.

Theme System

There are three theme files that must be kept in sync:
  1. src/styles/theme.js - JavaScript theme object (for Emotion)
  2. src/styles/variables.scss - SCSS variables
  3. tailwind.config.js - Tailwind configuration

Customizing Colors

// src/styles/theme.js
export const theme = {
  colors: {
    primary: '#5050ED',      // Primary brand color
    secondary: '#F0F0F0',    // Secondary color
    brandPrimary: '#5050ED', // Brand primary
    brandSecondary: '#1C1C1C', // Brand secondary
    wash: '#F5F5F5',         // Background wash
    line: '#E6E6E6',         // Border/line color
    // ... add more colors
  },
};
When adding or modifying theme colors, update all three files to maintain consistency.

Typography

Customize fonts and typography:
// src/styles/variables.scss
$font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-family-mono: 'JetBrains Mono', 'Courier New', monospace;

$font-size-base: 16px;
$font-size-lg: 18px;
$font-size-sm: 14px;
$font-size-xs: 12px;

$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;

Component Styling

Agora uses a component-based styling approach:
// components/Hero/Hero.tsx
import styles from './Hero.module.scss';

export function Hero() {
  return (
    <div className={styles.hero}>
      <h1 className={styles.headline}>Welcome to Governance</h1>
      <p className={styles.description}>Participate in decision making</p>
    </div>
  );
}

Using Tailwind CSS

For utility classes, use Tailwind directly:
<div className="container mx-auto px-4 sm:px-8">
  <div className="bg-white rounded-lg shadow-md p-6">
    <h2 className="text-2xl font-bold text-gray-900 mb-4">
      Proposal Title
    </h2>
    <p className="text-gray-600">
      Proposal description...
    </p>
  </div>
</div>

Global Styles

Global styles should be prefixed with gl_ and imported into src/styles/globals.scss:
// components/Layout/PageContainer.module.scss
.gl_bg-dotted-pattern {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image: radial-gradient(circle, #ddd 1px, transparent 1px);
  background-size: 20px 20px;
  pointer-events: none;
  z-index: -1;
}
Then import in globals.scss:
// src/styles/globals.scss
@import '../components/Layout/PageContainer.module.scss';

Branding Customization

Logo and Icons

1

Add logo assets

Place logo files in src/assets/ or public/ directory:
public/
├── logo.svg
├── logo-dark.svg
├── favicon.ico
└── og-image.png
2

Update logo references

Update logo imports in components:
import Logo from '@/assets/logo.svg';

export function Header() {
  return (
    <header>
      <Image src={Logo} alt="DAO Name" width={120} height={40} />
    </header>
  );
}
3

Configure metadata

Update metadata in src/app/layout.tsx:
export const metadata = {
  title: 'Your DAO Governance',
  description: 'Participate in Your DAO governance',
  icons: {
    icon: '/favicon.ico',
    apple: '/apple-touch-icon.png',
  },
  openGraph: {
    title: 'Your DAO Governance',
    description: 'Vote, delegate, and participate',
    images: ['/og-image.png'],
  },
};

Page Title and Hero

Customize the homepage hero section in tenant UI configuration:
// src/lib/tenant/tenantUIFactory.ts
new TenantUI({
  title: "Your DAO Governance",
  hero: {
    headline: "Shape the Future of Your DAO",
    description: "Vote on proposals, delegate your voting power, and participate in governance decisions that matter.",
    cta: {
      text: "Get Started",
      link: "/proposals",
    },
  },
  // ... other UI config
})

Feature Toggles

Control features using environment variables and configuration.

Environment-Based Features

Enable wallet-based authentication:
NEXT_PUBLIC_SIWE_ENABLED=true
Implementation:
const siweEnabled = process.env.NEXT_PUBLIC_SIWE_ENABLED === 'true';

if (siweEnabled) {
  // Show SIWE login option
}
Enable business intelligence metrics capture:
NEXT_PUBLIC_ENABLE_BI_METRICS_CAPTURE=true
NEXT_PUBLIC_AGORA_API_KEY=your_api_key
Usage:
if (process.env.NEXT_PUBLIC_ENABLE_BI_METRICS_CAPTURE === 'true') {
  trackEvent('proposal_viewed', { proposalId });
}
Enable Tenderly transaction simulation:
TENDERLY_USER=your_username
TENDERLY_PROJECT=your_project
TENDERLY_ACCESS_KEY=your_access_key
Simulation features automatically activate when all three are set.
Enable gas sponsoring for delegations and votes:
GAS_SPONSOR_PK=your_private_key
Users can delegate/vote without paying gas fees.
Enable Alligator advanced delegation features:Configure in tenant contracts:
alligator: {
  address: "0x...",
  abi: AlligatorABI,
}
Enables:
  • Subdelegation with rules
  • Partial delegation
  • Authority chains

Code-Based Feature Flags

Implement feature flags in tenant UI configuration:
// src/lib/tenant/tenantUIFactory.ts
new TenantUI({
  features: {
    advancedDelegation: true,
    proposalSimulation: true,
    snapshotIntegration: false,
    retroFunding: false, // Optimism-specific
  },
  ui: {
    toggle: "default", // or "op" for Optimism-style UI
  },
})
Use in components:
import Tenant from '@/lib/tenant/tenant';

const tenant = Tenant.current();

if (tenant.ui.features?.advancedDelegation) {
  // Show advanced delegation UI
}

Custom DAO Configuration

Configure DAO-specific settings and behavior.

Proposal Types

Customize proposal types based on your governance system:
// Different DAOs support different proposal types
const proposalTypes = {
  ens: ['social', 'executable'],
  optimism: ['social', 'standard', 'approval'],
  uniswap: ['social', 'onchain'],
};

Voting Parameters

Configure voting parameters:
const votingConfig = {
  quorum: "4000000", // 4M tokens
  proposalThreshold: "100000", // 100K tokens to propose
  votingDelay: 13140, // ~2 days in blocks
  votingPeriod: 46080, // ~7 days in blocks
};

Snapshot Integration

Configure Snapshot space:
# Production space
REACT_APP_DEPLOY_ENV=prod

# Test space
TESTNET_SNAPSHOT_SPACE=test-dao.eth

Advanced Customization

Custom Components

1

Create component

Create a new component following the standard structure:
src/components/CustomFeature/
├── CustomFeature.tsx
├── CustomFeature.module.scss
└── index.ts
2

Implement component

// CustomFeature.tsx
import styles from './CustomFeature.module.scss';

export function CustomFeature() {
  return (
    <div className={styles.container}>
      {/* Your custom feature */}
    </div>
  );
}
3

Add styles

// CustomFeature.module.scss
@import '@/styles/variables.scss';

.container {
  padding: 2rem;
  background: $color-wash;
}
4

Export component

// index.ts
export { CustomFeature } from './CustomFeature';

Custom API Endpoints

Add custom API routes:
// src/app/api/v1/custom/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  // Your custom API logic
  return NextResponse.json({ data: 'custom response' });
}

Database Schema Extensions

While the main schema is managed externally, you can add tenant-specific views:
-- Custom view for your DAO
CREATE VIEW yourdao.custom_metrics AS
SELECT 
  delegate,
  COUNT(*) as proposal_count,
  SUM(voting_power) as total_vp
FROM yourdao.votes
GROUP BY delegate;
Then add to Prisma schema and regenerate:
npx prisma db pull
npx prisma generate

Testing Customizations

Local Testing

1

Set development environment

NEXT_PUBLIC_AGORA_ENV=dev
NEXT_PUBLIC_AGORA_INSTANCE_NAME=your-dao
2

Run development server

npm run dev
3

Test features

  • Verify UI theme loads correctly
  • Test contract interactions
  • Check database queries
  • Validate feature toggles
4

Run quality checks

npm run prettier-src
npm run lint
npm run typecheck

Preview Deployments

Use Vercel preview deployments to test customizations:
  1. Create a feature branch
  2. Push changes
  3. Vercel automatically creates preview deployment
  4. Test with preview environment variables
  5. Merge to main when ready

Best Practices

Follow these best practices when customizing Agora:
  1. Keep theme files in sync - Update all three theme files when changing colors
  2. Use semantic naming - Name colors and variables based on purpose, not appearance
  3. Test across tenants - Ensure changes don’t break other tenant configurations
  4. Document customizations - Add comments explaining tenant-specific logic
  5. Follow TypeScript conventions - Use proper typing for all customizations
  6. Maintain backwards compatibility - Don’t break existing tenant configurations
  7. Use feature flags - Make new features opt-in via configuration
  8. Keep components reusable - Design components to work across tenants

Examples

Complete Tenant Setup Example

// Add to switch statement
case "example-dao":
  return {
    governor: {
      address: "0x1234567890123456789012345678901234567890",
      abi: GovernorABI,
    },
    token: {
      address: "0x0987654321098765432109876543210987654321",
      abi: TokenABI,
    },
  };

Next Steps

Configuration

Learn about all environment variables

Deployment

Deploy your customized instance

Architecture

Understand the application structure

API Reference

Explore the API endpoints

Build docs developers (and LLMs) love