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
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
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
};
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..." ,
};
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).
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:
src/styles/theme.js - JavaScript theme object (for Emotion)
src/styles/variables.scss - SCSS variables
tailwind.config.js - Tailwind configuration
Customizing Colors
theme.js
variables.scss
tailwind.config.js
// 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
},
};
// src/styles/variables.scss
$color-primary : #5050ed ;
$color-secondary : #f0f0f0 ;
$color-brand-primary : #5050ed ;
$color-brand-secondary : #1c1c1c ;
$color-wash : #f5f5f5 ;
$color-line : #e6e6e6 ;
// tailwind.config.js
module . exports = {
theme: {
extend: {
colors: {
primary: '#5050ED' ,
secondary: '#F0F0F0' ,
'brand-primary' : '#5050ED' ,
'brand-secondary' : '#1C1C1C' ,
wash: '#F5F5F5' ,
line: '#E6E6E6' ,
},
},
},
};
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 : 16 px ;
$font-size-lg : 18 px ;
$font-size-sm : 14 px ;
$font-size-xs : 12 px ;
$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:
Component.tsx
Hero.module.scss
// 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 1 px , transparent 1 px );
background-size : 20 px 20 px ;
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
Add logo assets
Place logo files in src/assets/ or public/ directory: public/
├── logo.svg
├── logo-dark.svg
├── favicon.ico
└── og-image.png
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 >
);
}
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
Sign-In with Ethereum (SIWE)
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.
Advanced Delegation (Alligator)
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
Create component
Create a new component following the standard structure: src/components/CustomFeature/
├── CustomFeature.tsx
├── CustomFeature.module.scss
└── index.ts
Implement component
// CustomFeature.tsx
import styles from './CustomFeature.module.scss' ;
export function CustomFeature () {
return (
< div className = { styles . container } >
{ /* Your custom feature */ }
</ div >
);
}
Add styles
// CustomFeature.module.scss
@import '@/styles/variables.scss' ;
.container {
padding : 2 rem ;
background : $color-wash ;
}
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
Set development environment
NEXT_PUBLIC_AGORA_ENV = dev
NEXT_PUBLIC_AGORA_INSTANCE_NAME = your-dao
Test features
Verify UI theme loads correctly
Test contract interactions
Check database queries
Validate feature toggles
Run quality checks
npm run prettier-src
npm run lint
npm run typecheck
Preview Deployments
Use Vercel preview deployments to test customizations:
Create a feature branch
Push changes
Vercel automatically creates preview deployment
Test with preview environment variables
Merge to main when ready
Best Practices
Follow these best practices when customizing Agora:
Keep theme files in sync - Update all three theme files when changing colors
Use semantic naming - Name colors and variables based on purpose, not appearance
Test across tenants - Ensure changes don’t break other tenant configurations
Document customizations - Add comments explaining tenant-specific logic
Follow TypeScript conventions - Use proper typing for all customizations
Maintain backwards compatibility - Don’t break existing tenant configurations
Use feature flags - Make new features opt-in via configuration
Keep components reusable - Design components to work across tenants
Examples
Complete Tenant Setup Example
tenantContractFactory.ts
tenantTokenFactory.ts
tenantUIFactory.ts
.env.local
// 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