Skip to main content

Overview

The frontend is built with Svelte 5 using the new runes mode, providing a modern reactive UI for the GenLayer Points platform. Tech Stack:
  • Svelte 5 (runes mode)
  • Vite (build tool)
  • Tailwind CSS (styling)
  • svelte-spa-router (routing)
  • Axios (API client)
  • wagmi/viem (Web3 wallet integration)

Installation

1

Navigate to frontend directory

cd frontend
2

Activate Python environment

Important: The frontend requires the Python virtual environment to be activated first because the project uses nodeenv to manage Node.js.
source ../backend/env/bin/activate
3

Install dependencies

npm install
4

Configure environment

Create .env file:
cp .env.example .env
Edit with your configuration:
VITE_API_URL=http://localhost:8000
VITE_VALIDATOR_RPC_URL=https://zksync-os-testnet-genlayer.zksync.dev
VITE_VALIDATOR_CONTRACT_ADDRESS=0x10eCB157734c8152f1d84D00040c8AA46052CB27
VITE_EXPLORER_URL=https://explorer-asimov.genlayer.com
VITE_RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
5

Start development server

npm run dev
The app will be available at http://localhost:5173

Dependencies

Key packages from package.json:
{
  "dependencies": {
    "svelte": "5.32.1",              // UI framework
    "svelte-spa-router": "^4.0.0",   // Client-side routing
    "axios": "^1.6.2",               // HTTP client
    "@wagmi/core": "^2.17.2",        // Web3 wallet connection
    "viem": "^2.30.0",               // Ethereum utilities
    "ethers": "^6.14.1",             // Ethereum library
    "chart.js": "^4.5.0",            // Charts
    "marked": "^16.3.0",             // Markdown rendering
    "date-fns": "^2.30.0"            // Date utilities
  },
  "devDependencies": {
    "vite": "^5.0.0",                // Build tool
    "tailwindcss": "^3.3.5",         // CSS framework
    "vitest": "^0.34.6",             // Testing
    "@testing-library/svelte": "^4.2.3"
  }
}

Project Structure

frontend/src/
├── components/          # Reusable UI components
│   ├── Navbar.svelte   # Top navigation
│   ├── Sidebar.svelte  # Side navigation
│   ├── AuthButton.svelte
│   ├── LeaderboardTable.svelte
│   ├── ContributionsList.svelte
│   └── ...
├── routes/             # Page components
│   ├── Overview.svelte
│   ├── TestnetAsimov.svelte
│   ├── Leaderboard.svelte
│   ├── Profile.svelte
│   └── ...
├── lib/                # Core utilities
│   ├── api.js         # API client
│   ├── auth.js        # Authentication
│   ├── userStore.js   # User state
│   ├── toastStore.js  # Notifications
│   └── wallet/        # Web3 integration
├── App.svelte          # Main app with routing
├── main.js             # Entry point
└── styles.css          # Global styles

Svelte 5 Runes Mode

Critical: This project uses Svelte 5 with runes mode enabled. Traditional Svelte syntax will cause errors.

Component Props

Never use export let - Always use $props():
export let params = {};
export let userData;

Reactive State

Use $state() for reactive variables:
// Reactive state
let count = $state(0);
let user = $state(null);
let items = $state([]);

// Derived state
let doubled = $derived(count * 2);
let hasUser = $derived(user !== null);

// Effects
$effect(() => {
  console.log('Count changed:', count);
});

Event Handling

Use onclick instead of on:click:
// ✅ Correct Svelte 5 syntax
<button onclick={() => handleClick()}>Click me</button>

// ❌ Old syntax (don't use)
<button on:click={handleClick}>Click me</button>

Routing

Routes are defined in src/App.svelte:
const routes = {
  '/': Overview,
  '/asimov': TestnetAsimov,
  '/leaderboard': Leaderboard,
  '/validators': Validators,
  '/participant/:address': Profile,
  '/submit-contribution': SubmitContribution,
  '/profile': EditProfile,
  '*': NotFound
};
import { push, location } from 'svelte-spa-router';

// Navigate programmatically
push('/leaderboard');

// Get current location
$location // reactive store with current path

API Integration

The API client is in src/lib/api.js:
import { 
  getCurrentUser, 
  updateUserProfile,
  getLeaderboard,
  submitContribution 
} from '../lib/api';

// Fetch data
const user = await getCurrentUser();
const leaderboard = await getLeaderboard({ type: 'validator' });

// Submit data
const contribution = await submitContribution({
  contribution_type: 1,
  notes: 'My contribution',
  recaptcha_token: token
});

API Configuration

Base URL is set via environment variable:
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';

Authentication

Sign-In With Ethereum (SIWE) flow:
1

Connect wallet

import { connectWallet } from '../lib/auth';

await connectWallet();
2

Sign message

User signs a message with MetaMask
3

Verify and login

import { signInWithEthereum } from '../lib/auth';

await signInWithEthereum();
4

Access protected routes

Session cookie is set, user can access authenticated endpoints

Auth State

import { authState } from '../lib/auth.js';

// Check authentication
if ($authState.isAuthenticated) {
  // User is logged in
}

// Access user data
console.log($authState.address);

User Store

Central store for logged-in user data:
import { userStore } from '../lib/userStore';

// Reactive access to user data
$: userName = $userStore.user?.name;

// Update user data
userStore.updateUser({ name: 'New Name' });

// Reload from API
await userStore.loadUser();

Styling

Tailwind CSS

Utility-first CSS framework:
<div class="bg-white shadow rounded-lg p-6">
  <h2 class="text-2xl font-bold mb-4">Title</h2>
  <p class="text-gray-600">Content</p>
</div>

Custom Fonts

  • Headings: Geist (automatic on h1-h6)
  • Body: Switzer (automatic on all text)
  • Display/Numbers: F37 Lineca (use font-display class)
<h1 class="text-2xl font-bold">Page Title</h1>
<p class="text-[32px] font-medium font-display">12,345</p>

Spacing Guidelines

// Padding
p-2  // 8px - tight
p-4  // 16px - standard
p-6  // 24px - spacious
p-8  // 32px - extra

// Vertical spacing
space-y-3  // between cards
space-y-4  // sections
space-y-6  // major sections

// Gaps
gap-2  // small
gap-4  // comfortable
gap-6  // large

State Management

Use Svelte stores for global state:
import { writable } from 'svelte/store';

// Create store
export const myStore = writable(initialValue);

// In component
import { myStore } from './stores';

// Read
$myStore

// Update
myStore.set(newValue);
myStore.update(val => val + 1);

Toast Notifications

Global notification system:
import { showSuccess, showError, showWarning } from '../lib/toastStore';

showSuccess('Profile updated!');
showError('Failed to save changes');
showWarning('Session will expire soon');

Common Commands

cd frontend
source ../backend/env/bin/activate

Testing

Test Structure

Tests are in src/tests/:
import { render, screen } from '@testing-library/svelte';
import MyComponent from '../components/MyComponent.svelte';

test('renders component', () => {
  render(MyComponent, { props: { name: 'Test' } });
  expect(screen.getByText('Test')).toBeInTheDocument();
});

Run Tests

npm test                  # Run once
npm run test:watch        # Watch mode
npm run test:coverage     # With coverage

Common Issues

Props Not Working

Problem: Component props throw errors Solution: Use $props() instead of export let

State Not Reactive

Problem: Changes don’t update UI Solution: Use $state() for reactive variables

API Calls Fail

Problem: CORS or authentication errors Solution: Check VITE_API_URL and ensure backend is running

MetaMask Not Detected

Problem: Wallet connection fails Solution: Install MetaMask extension and refresh page

Next Steps

Architecture

System architecture overview

Data Model

Database structure

Authentication

SIWE authentication flow

Deployment

Deploy to production

Build docs developers (and LLMs) love