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
Navigate to frontend directory
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
Configure environment
Create .env file: 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
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():
❌ Wrong (will error)
✅ Correct
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
};
Navigation
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:
Connect wallet
import { connectWallet } from '../lib/auth' ;
await connectWallet ();
Sign message
User signs a message with MetaMask
Verify and login
import { signInWithEthereum } from '../lib/auth' ;
await signInWithEthereum ();
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
Activate environment
Install dependencies
Dev server
Build for production
Preview production build
Run tests
Watch tests
Type check
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
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