Xelma is a collective market-intelligence prediction game on the Stellar blockchain. Users predict short-term price direction (and, in Legend mode, exact prices) on active rounds that settle trustlessly on-chain via a Soroban smart contract.
This repository contains the web client for Xelma — a React + TypeScript single-page application built with Vite, Tailwind CSS, Zustand, and Socket.IO.
- Backend repo: https://github.com/TevaLabs/Xelma-Backend
- Issue tracker: https://github.com/TevaLabs/Xelma-Frontend/issues
- Design reference: Figma – Xelma
- Tech stack
- Getting started
- Available scripts
- Environment variables
- Deployment ← production / Vercel guide
- Education & Learn page
- Project structure notes
- Testing
- React Compiler / ESLint notes
| Layer | Choice |
|---|---|
| Framework | React 19 + TypeScript 5.9 |
| Build tool | Vite 7 |
| Styling | Tailwind CSS 4 via the @tailwindcss/vite plugin |
| State | Zustand |
| Routing | react-router-dom 7 |
| Realtime | socket.io-client 4 |
| Wallet | Freighter (@stellar/freighter-api) |
| On-chain | @stellar/stellar-sdk 12 (Soroban contracts) |
| Charts | lightweight-charts |
| Testing | Vitest 4 + React Testing Library + MSW |
| Linting | ESLint 9 flat config + typescript-eslint |
- Node.js 20.x (matches CI; Vite 7 +
@tailwindcss/vite4 both target modern Node). - pnpm 9+ (recommended — the lockfile of record is
pnpm-lock.yaml).npmworks as a fallback butpnpmis what Vercel and CI run on by default.
git clone https://github.com/Richardkingz2019/Xelma-Frontend.git
cd Xelma-Frontend
pnpm installIf you don't have pnpm yet:
npm install -g pnpm.
npm installalso works; the project ships bothpnpm-lock.yamlandpackage-lock.json.
pnpm devThe app boots on http://localhost:5173. During development it
proxies /api and /socket.io to http://localhost:3000 (configure via
vite.config.ts and src/lib/config.ts).
| Script | Purpose |
|---|---|
pnpm dev |
Start Vite with HMR (default port 5173). |
pnpm build |
Type-check (tsc -b) + produce a production bundle in dist/. |
pnpm preview |
Serve the contents of dist/ locally to smoke-test the prod build. |
pnpm lint |
Run the ESLint flat config over the repo. |
pnpm test:unit |
Run the Vitest unit/integration suite once (CI mode). |
pnpm test |
Full CI bundle: lint && build && test:unit. Use this before pushing. |
npm run <script>works identically if you prefer npm.
All client-side variables must be prefixed with VITE_ so Vite exposes them to the
browser via import.meta.env. Anything without that prefix is silently dropped from the
bundle.
| Variable | Required | Where it is read | Default (dev) | Purpose |
|---|---|---|---|---|
VITE_API_BASE_URL |
✅ yes | src/lib/config.ts |
http://localhost:3000 |
Root URL of the Xelma backend (REST + Socket.IO origin). |
VITE_API_URL |
src/lib/config.ts (fallback) |
— | Deprecated — kept as a fallback for VITE_API_BASE_URL. New deploys should use VITE_API_BASE_URL. |
|
VITE_STELLAR_NETWORK |
optional | src/components/Navbar.tsx |
TESTNET |
Human-readable network label (TESTNET, PUBLIC, or MAINNET). Drives the navbar badge. |
VITE_STELLAR_NETWORK_PASSPHRASE |
✅ prod | src/lib/xelma-contract.ts, src/components/Footer.tsx |
Test SDF Network ; September 2015 (Networks.TESTNET) |
Network passphrase used to build/sign Soroban transactions. Use Public Global Stellar Network ; September 2015 for mainnet. |
VITE_STELLAR_RPC_URL |
✅ prod | src/lib/xelma-contract.ts |
https://soroban-testnet.stellar.org |
Soroban JSON-RPC endpoint used to simulate/submit/polling. |
VITE_XELMA_CONTRACT_ID |
✅ prod | src/lib/xelma-contract.ts |
Testnet placeholder contract id | Deployed Xelma Soroban contract id (C…) for the target network. |
Local development (.env in the project root):
VITE_API_BASE_URL=http://localhost:3000
VITE_STELLAR_NETWORK=TESTNET
VITE_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
VITE_STELLAR_RPC_URL=https://soroban-testnet.stellar.org
# VITE_XELMA_CONTRACT_ID is optional in dev (uses the bundled testnet address)Production (.env.production — do not commit, set these in Vercel instead):
VITE_API_BASE_URL=https://xelma-backend.onrender.com
VITE_STELLAR_NETWORK=PUBLIC
VITE_STELLAR_NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015"
VITE_STELLAR_RPC_URL=https://soroban-mainnet.stellar.org
VITE_XELMA_CONTRACT_ID=<your-mainnet-contract-id>
⚠️ The Xelma backend must allow CORS for the frontend's origin. On the backend, setCLIENT_URL=https://<your-vercel-domain>.vercel.app(or your custom domain).
This section is the source of truth for shipping a new build to production. It is written so that a first-time contributor can deploy without re-discovering every gotcha. If anything here diverges from what Vercel actually shows, fix the README first — that's the bug.
When importing the repo into Vercel, set the following on Project Settings → General:
| Setting | Value |
|---|---|
| Framework Preset | Vite |
| Build Command | pnpm build |
| Output Directory | dist |
| Install Command | pnpm install --frozen-lockfile |
| Node.js Version | 20.x (override via NODE_VERSION=20 env var, or engines in package.json) |
| Root Directory | ./ |
These match the values used by CI (.github/workflows/ci.yml, which pins Node 20 and
runs npm ci / npm test), so a build that passes locally with pnpm test will
behave identically on Vercel.
ℹ️ Why
pnpmand notnpm? The project is committed withpnpm-lock.yamlas the lockfile of record. Mixingnpm installandpnpm installon the same codebase can desync the lockfile and cause Vercel to install slightly different transitive dependencies than CI, which is the #1 source of "works on my machine" deploy bugs. If you must use npm locally, regenerate the npm lockfile withnpm installand commit the result.
Set the following on Project Settings → Environment Variables. Use separate values per environment (Development / Preview / Production) so PR previews don't talk to production Stellar / database state.
| Variable | Production value (example) | Preview value (example) |
|---|---|---|
VITE_API_BASE_URL |
https://xelma-backend.onrender.com |
https://xelma-backend-staging.onrender.com |
VITE_STELLAR_NETWORK |
PUBLIC |
TESTNET |
VITE_STELLAR_NETWORK_PASSPHRASE |
Public Global Stellar Network ; September 2015 |
Test SDF Network ; September 2015 |
VITE_STELLAR_RPC_URL |
https://soroban-mainnet.stellar.org |
https://soroban-testnet.stellar.org |
VITE_XELMA_CONTRACT_ID |
<mainnet contract C-address> |
(leave blank — uses default testnet contract id) |
Every
VITE_*variable is embedded into the JS bundle at build time. Changing a value requires a redeploy. If the variable isn't set, Vercel will fall back to the dev defaults listed in Environment variables — which is fine in Preview, never in Production.
Tick these off before clicking Deploy (or pushing to main):
-
pnpm-lock.yamlis committed and up to date (pnpm installreports "already up to date"). - All five
VITE_*variables in §2 are set in the Production environment. - Backend has been redeployed with
CLIENT_URLmatching the Vercel domain (and the preview domain wildcard if you want previews to work). -
pnpm testpasses locally, includingpnpm lint,pnpm build, andpnpm test:unit. - No
.env,.env.production, or secrets are about to be committed. Production secrets live in Vercel, not in the repo. - Backend dependency matrix is in sync — see §6.
git remote -v
# origin → https://github.com/Richardkingz2019/Xelma-Frontend.git (your fork)
# upstream → https://github.com/TevaLabs/Xelma-Frontend.git (canonical)
# Rebase on upstream before opening a PR
git fetch upstream
git rebase upstream/main
# Push your branch to the fork, then open the PR against TevaLabs/Xelma-Frontend
git push -u origin docs/issue-213-vercel-deployment-envVercel is configured to deploy on push to main of TevaLabs/Xelma-Frontend; PR
preview URLs are produced automatically for every branch pushed to origin.
Most production deploy failures come from one of these five causes. Walk through them in order — they're ordered from most → least likely.
- Stale lockfile. Symptom: Vercel fails with
ERR_PNPM_LOCKFILE_BREAKING_CHANGEor "frozen lockfile can't be satisfied". Fix: runpnpm installlocally, commit the updatedpnpm-lock.yaml, push again. VITE_API_BASE_URLmissing in Production. Symptom: app loads but every API call routes tohttp://localhost:3000, which fails in the browser. Fix: set the variable on Vercel and redeploy.- Wrong Stellar network passphrase. Symptom: wallet signs the transaction but
Soroban simulation rejects it with
Invalid network. Fix: ensureVITE_STELLAR_NETWORK_PASSPHRASEmatches the network inVITE_STELLAR_RPC_URL. - CORS blocked on the backend. Symptom: browser shows
No 'Access-Control-Allow-Origin'. Fix: add the Vercel domain to the backend'sCLIENT_URLenv var (and redeploy the backend). - Wrong contract id. Symptom: simulation fails with
contract not found. Fix: confirmVITE_XELMA_CONTRACT_IDis the deployed contract for the same network thatVITE_STELLAR_RPC_URLpoints at.
Frontend features depend on backend endpoints contractually. Issue
#152 — Document backend dependency matrix for frontend features
tracks a single source-of-truth doc that lives next to this README. Until that
lands, treat the API paths called out in src/lib/api.ts and the
backend repo's OpenAPI spec as the working contract. If a frontend feature ships
against an endpoint the backend doesn't expose (or vice versa), update both sides
in the same PR rather than relying on tribal knowledge.
The /learn page is fully integrated with the backend API. Content can be updated
dynamically without requiring a frontend redeployment.
GET /api/education/guides— fetches the list of educational guides.GET /api/education/tip— fetches the current "alpha" tip of the day.
LearnPage— main container fetching and managing state for education content.GuideCard— premium card component for displaying individual guides.TipCard— high-impact card component for the daily tip.StatusStates— reusable Loading, Error, and Empty state components (undersrc/components/ui/).
src/
├── lib/ # Shared helpers (config, api, socket, stellar contract, utils)
├── store/ # Zustand stores (auth, wallet, profile, round, notifications)
├── pages/ # Top-level routes (Dashboard, Pools, Learn, Profile, …)
├── components/ # Reusable UI (Navbar, Footer, StatsCard, BetModal, …)
├── hooks/ # Cross-cutting React hooks
├── types/ # Shared TypeScript types
└── utils/ # Pure utility functions
Unit and integration tests are implemented with Vitest + React Testing Library. The HTTP layer is mocked with MSW (Mock Service Worker) so tests do not hit the real backend.
Run unit tests:
pnpm test:unitRun the full CI gate (lint + build + tests):
pnpm testThe React Compiler is not enabled on this project because of its impact on dev & build performance. To add it, see https://react.dev/learn/react-compiler/installation.
The ESLint flat config (eslint.config.js) uses typescript-eslint's recommended
ruleset. For stricter type-aware linting, swap tseslint.configs.recommended for
tseslint.configs.recommendedTypeChecked and add the matching parser options shown
in the default Vite template — we deliberately left it lighter for build speed.
Currently, two official React Fast Refresh plugins are available:
@vitejs/plugin-react(Babel / oxc)@vitejs/plugin-react-swc(SWC)
This project uses @vitejs/plugin-react.