FantasyXI is an open-source, non-custodial fantasy football platform built on Stellar Testnet and Soroban smart contracts, integrated with live Fantasy Premier League (FPL) data.
Managers assemble squads, compete in custom multi-gameweek leagues, and deposit entry fees directly into a trustless Soroban escrow smart contract. When competition concludes, prize pools (95% distributed 60/30/10 with a 5% platform fee) are settled and disbursed directly on-chain with zero counterparty risk and zero lost cents.
flowchart TB
subgraph Client Layer
Browser[User Browser / Next.js 16 UI]
Freighter[Freighter Wallet Extension]
end
subgraph Backend Application Layer [Express 5 + TypeScript]
AuthSvc[Auth Service: Email/Pass + Google OAuth]
SquadSvc[Squad Validator & Formations]
LeagueSvc[League & Competition Engine]
FplSvc[FPL Upstream Sync & Normalizer]
FinSvc[Financial State Machine & Accounting]
StellarSvc[Stellar & Soroban Verifier Service]
end
subgraph Data Layer
PostgreSQL[(PostgreSQL + Prisma 7)]
FPL_API[FPL Official Upstream API]
end
subgraph Blockchain Layer [Stellar Testnet]
Horizon[Stellar Horizon API]
SorobanRPC[Soroban RPC Node]
EscrowContract[FantasyXI Escrow Smart Contract\nCB4KIK42P32SZHKG...]
UsdcSAC[USDC Stellar Asset Contract\nCBKWOGJ7CQSVZXIO...]
end
Browser -->|JWT Authenticated API Calls| AuthSvc
Browser -->|Squad Selection & Joins| SquadSvc
Browser -->|Freighter Signs invokeHostFunction| Freighter
Freighter -->|Direct Soroban Invocation: deposit()| EscrowContract
FinSvc --> StellarSvc
StellarSvc -->|Verify Envelope XDR & Status| Horizon
StellarSvc -->|Verify On-Chain Ledger State| SorobanRPC
LeagueSvc --> PostgreSQL
SquadSvc --> PostgreSQL
FinSvc --> PostgreSQL
FplSvc -->|Sync Players & Fixtures| FPL_API
FplSvc --> PostgreSQL
EscrowContract -->|Transfer USDC| UsdcSAC
FantasyXI enforces a strict boundary between Web2 application gameplay and Web3 financial settlements:
- Express Owns Game Logic:
- User profiles and JWT authentication (Email/Password + Google OAuth 2.0).
- Squad assembly (15 players, £100m budget, formation rules, captains, vice-captains).
- Live FPL points aggregation, bonus calculations, and auto-substitutions.
- League standings, multi-gameweek aggregations, and deterministic tie-breaking.
- Soroban Owns Financial Escrow:
- Escrow contract manages league partitions identified by
league_id. - Direct USDC SAC transfers from participants into contract storage.
- Enforces atomic invariant guards:
AlreadyDeposited,PayoutExceedsDeposits,AlreadySettled, andNotAuthorized. - Admin triggers automated on-chain settlement (
settle) or full refund (refund).
- Escrow contract manages league partitions identified by
- No Web3 Authentication Requirement:
- Users do not sign in using Stellar keypairs.
- Freighter wallet is invoked strictly when signing financial transactions (
deposit).
| Component | Identifier / Address |
|---|---|
| Escrow Smart Contract | CB4KIK42P32SZHKG4JBDCJUV4A4KGCDN6RHOOTIFSBGZHS2IF653VOEA |
| USDC Stellar Asset Contract (SAC) | CBKWOGJ7CQSVZXIOIIPCDAUT6APQYBCEE7QTSGDZZ2RO6D3JYRMKWZNG |
| USDC Classic Asset Issuer | GC43IGCUMQYECKMRKGSJE2RPQPJ2QNHFB6VAHNNBO4NONKK3PVHEXN25 |
| Network Passphrase | Test SDF Network ; September 2015 |
| Soroban RPC Server | https://soroban-testnet.stellar.org |
| Horizon API Server | https://horizon-testnet.stellar.org |
- Squad Requirements: Exactly 15 players (2 Goalkeepers, 5 Defenders, 5 Midfielders, 3 Forwards).
- Budgetary Constraints: Total squad cost must not exceed £100.0m.
- Club Limits: Maximum of 3 players from any single Premier League club.
- Formations: Supports standard tactical layouts (e.g.,
4-4-2,3-5-2,3-4-3,5-3-2,4-3-3,5-4-1) with a minimum of 1 GKP, 3 DEF, 2 MID, 1 FWD starting. - Multipliers & Substitutions:
- Captain scores 2x points; if the captain plays 0 minutes, the 2x multiplier dynamically transfers to the Vice-Captain.
- Automatic bench substitutions prioritize the highest-priority eligible bench player while maintaining formation validity.
-
Lifecycle Transitions:
UPCOMING$\to$ ACTIVE$\to$ COMPLETEDorCANCELLED. -
Standings & Tie-Breaking:
- Primary: Cumulative fantasy points across all competition gameweeks.
- Secondary (Tie-break 1): Peak single-gameweek score within the league window.
- Tertiary (Tie-break 2): Earliest registration timestamp (deterministic resolution).
Written in Rust using the Soroban SDK (soroban-sdk = "22.0.8"), exposing 7 contract functions:
initialize(admin, usdc_token): One-time contract initialization.create_league(creator, league_id, entry_fee): Partitions an isolated league escrow.deposit(participant, league_id): Transfersentry_feefrom participant via USDC SAC client into contract storage. Requiresparticipant.require_auth().settle(admin, league_id, winners, platform_treasury, platform_fee): Distributes prize pool to top 3 winners and platform treasury. EnforcesPayoutExceedsDepositsand transitions status toSettledatomically.refund(admin, league_id, participants): Refunds entry fees to participants in cancelled competitions.get_league(league_id): View function returning on-chain state (creator,entry_fee,total_deposited,participant_count,status).get_deposit(league_id, participant): View function returning participant deposit amount.
Managed by PrizeService:
- Platform Fee: Flat 5% deducted from gross prize pool.
- Prize Pool (95%):
- 1st Place: 60% of net prize pool
- 2nd Place: 30% of net prize pool
- 3rd Place: 10% of net prize pool
- (2-player leagues split 70% / 30%; 1-player leagues refund 100%)
- Uses integer stroop rounding to ensure zero dropped fractions or orphaned tokens.
FantasyXI/
├── backend/ # Node.js + Express + TypeScript Backend
│ ├── prisma/
│ │ ├── schema.prisma # PostgreSQL schema & enum definitions
│ ├── src/
│ │ ├── config/ # Database, Stellar, JWT, and CORS configuration
│ │ ├── controllers/ # HTTP Request handlers (Auth, League, Squad, Financial)
│ │ ├── middleware/ # JWT Authentication & authorization guards
│ │ ├── routes/ # Express v5 API routes (/api/v1/...)
│ │ ├── services/
│ │ │ ├── financial/ # FinancialService, StellarService, SorobanContractClient
│ │ │ ├── fpl/ # Upstream FPL synchronization & normalization
│ │ │ └── league/ # LeagueService, SquadValidator, ScoringService, PrizeService
│ │ ├── scripts/ # Phase 7.6 Live Stellar Testnet verification scripts
│ │ └── tests/ # Comprehensive unit & integration test suites
│ ├── package.json # Scripts & backend dependencies
│ └── tsconfig.json # NodeNext strict TypeScript configuration
├── frontend/ # Next.js 16 + React 19 + TailwindCSS App
│ ├── src/
│ │ ├── app/ # App Router pages (leagues, fixtures, team, profile, etc.)
│ │ ├── components/ # UI components, Pitch visualizer, PlayerCard, PaymentModal
│ │ ├── context/ # AuthContext (JWT session management)
│ │ └── lib/
│ │ ├── api.ts # Axios/Fetch API client wrapper
│ │ └── stellar/ # sorobanDeposit.ts (Freighter + Soroban RPC client)
│ ├── package.json # Frontend dependencies (@stellar/freighter-api, @stellar/stellar-sdk)
│ └── next.config.ts # Next.js configuration (Turbopack)
├── contracts/ # Soroban Escrow Smart Contract (Rust)
│ ├── src/
│ │ └── lib.rs # Escrow contract implementation & Rust unit tests
│ └── Cargo.toml # Rust package configuration (soroban-sdk = "22.0.8")
├── tools/ # Local developer tooling (Stellar CLI binaries)
└── REGULATORY_CONSIDERATIONS.md # Legal, AML, and non-custodial compliance documentation
- Node.js: v20.x or v24.x
- Rust:
1.75.0+with targetwasm32-unknown-unknown - PostgreSQL:
v14+running locally or via Docker - Stellar CLI:
v22+installed for contract simulation / execution - Freighter Wallet Extension: Installed in your browser (configured to Stellar Testnet)
Ensure PostgreSQL is running and create a local database:
CREATE DATABASE fantasyxi;Navigate to backend/ and create your .env file:
cd backend
cp .env.example .envConfigure backend/.env:
PORT=5000
NODE_ENV=development
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/fantasyxi?schema=public"
JWT_SECRET="your-secure-jwt-secret-key-at-least-32-chars"
JWT_EXPIRES_IN="7d"
# Stellar Testnet Configuration
STELLAR_NETWORK=TESTNET
STELLAR_HORIZON_URL="https://horizon-testnet.stellar.org"
STELLAR_SOROBAN_RPC_URL="https://soroban-testnet.stellar.org"
STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
STELLAR_USDC_ASSET_CODE="USDC"
STELLAR_USDC_ISSUER="GC43IGCUMQYECKMRKGSJE2RPQPJ2QNHFB6VAHNNBO4NONKK3PVHEXN25"
STELLAR_USDC_TOKEN_CONTRACT_ID="CBKWOGJ7CQSVZXIOIIPCDAUT6APQYBCEE7QTSGDZZ2RO6D3JYRMKWZNG"
STELLAR_ESCROW_CONTRACT_ID="CB4KIK42P32SZHKG4JBDCJUV4A4KGCDN6RHOOTIFSBGZHS2IF653VOEA"
STELLAR_TREASURY_ADDRESS="GC43IGCUMQYECKMRKGSJE2RPQPJ2QNHFB6VAHNNBO4NONKK3PVHEXN25"Install dependencies, run database migrations, and generate Prisma client:
npm install
npm run db:migrate
npm run buildStart the backend development server:
npm run devBackend API will be running at http://localhost:5000.
The frontend is a Next.js app and should be started from the frontend/ directory using a supported Node.js version.
Requirements:
- Node.js:
v20.xorv24.x - Package manager:
npm
From the repository root:
cd frontend
node -v
npm install
cp .env.example .env.localConfigure frontend/.env.local:
NEXT_PUBLIC_API_URL="http://localhost:5000"
NEXT_PUBLIC_STELLAR_NETWORK="TESTNET"
NEXT_PUBLIC_STELLAR_ESCROW_CONTRACT_ID="CB4KIK42P32SZHKG4JBDCJUV4A4KGCDN6RHOOTIFSBGZHS2IF653VOEA"
NEXT_PUBLIC_STELLAR_SOROBAN_RPC_URL="https://soroban-testnet.stellar.org"
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"Install dependencies and start the local Next.js development server:
npm install
npm run devFor the current Next.js setup, npm run dev starts the local development server (using the standard Next.js app setup; in v16 this can use Turbopack under the hood). The frontend will be available at http://localhost:3000.
FantasyXI maintains automated test suites across all architectural layers.
Executes domain rules, scoring, formation constraints, tie-breakers, auth, and payment envelope parsing without live network dependencies:
cd backend
npm testOutput: 118 passed, 0 failed across 33 suites
Executes contract unit tests and invariant checks inside the Soroban simulation environment:
cd contracts
cargo testOutput: 6 passed, 0 failed
test_initialize_and_create_league: Validates partition initialization.test_deposit_and_single_settlement: Proves 95/5 payout mechanics.test_duplicate_deposit_rejected: Reverts withAlreadyDeposited(#6).test_settlement_payout_exceeding_deposits_rejected: Reverts withPayoutExceedsDeposits(#9).test_unauthorized_settlement_rejected: Reverts withNotAuthorized(#10).test_refund_cancelled_league: Proves complete token return upon cancellation.
Executes a live on-chain competition (create_league, multi-manager deposit, envelope verification, guard assertions, and settle) against Stellar Testnet:
cd backend
npx tsx src/scripts/provePhase76Lifecycle.tsResults are saved to backend/phase7.6_testnet_evidence.json.
Verifies strict TypeScript compliance and static page optimization:
cd frontend
npm run buildOutput: 13/13 static & dynamic routes compiled with zero errors.
The frontend installs as a PWA (manifest + service worker in frontend/public/). The service worker is only registered in production builds:
cd frontend
npm run build && npm start- Open the app once online and sign in; the squad page and its assets are cached at install, and each successful squad load saves a per-user snapshot on the device.
- In DevTools > Application, check the manifest and service worker, then tick Network > Offline and reload
/team: the squad is shown read-only with an offline banner. Uncached pages fall back to/offline.html. - Saving the squad and transfers always require a connection. Authenticated API responses are never stored in the shared service worker cache, and offline snapshots are cleared on sign-out.
- Create a Web Service on Render.
- Connect your repository and configure:
- Root Directory:
backend - Environment:
Node - Build Command:
npm install && npm run build - Start Command:
npm start(Runs compiled code vianode dist/server.jswithout dev tooling)
- Root Directory:
- Add Environment Variables:
DATABASE_URL: Hosted PostgreSQL connection string.JWT_SECRET: Random 64-character secret.NODE_ENV:production- Plus all
STELLAR_*configuration parameters.
- Create a Frontend Project pointing to
frontend/. - Configure build settings:
- Framework Preset:
Next.js - Build Command:
npm run build - Output Directory:
.next
- Framework Preset:
- Add Environment Variables:
NEXT_PUBLIC_API_URL: URL of your deployed backend.NEXT_PUBLIC_STELLAR_ESCROW_CONTRACT_ID:CB4KIK42P32SZHKG4JBDCJUV4A4KGCDN6RHOOTIFSBGZHS2IF653VOEA
- Non-Custodial Architecture: FantasyXI never takes possession or custody of user stablecoins. Funds reside exclusively in the open-source Soroban smart contract escrow partition until settlement.
- Google Sign-In & Account Linking: Managers can sign in with email/password or Google (OAuth 2.0 / OpenID Connect); both open the same account. A verified Google email matching an existing account is linked automatically, and signed-in managers can link or unlink Google (and add a password to a Google-only account) from their profile. The OAuth state is HMAC-signed and bound to the initiating browser with an HttpOnly nonce cookie, return paths are restricted to same-site paths, and the issued JWT is handed to the frontend in the URL fragment so it never reaches server logs.
- Envelope XDR Verification: Payments are verified on-chain by decoding
invokeHostFunctiontransaction envelopes, matching contract ID, function call, sender public key, and league ID. - SQL & Injection Protection: Database interactions are performed using Prisma ORM with parameterized queries.
- Rate Limiting & Authentication: Endpoints requiring user context are guarded by JWT authorization middleware with CSRF-protected OAuth state tokens.
- Role-Based Access Control: Every protected endpoint declares the permission it needs via
requirePermission; the role-to-permission matrix lives inbackend/src/config/permissions.ts. Roles areUSER(managers),MODERATOR,ADMINandSERVICE(automated callers). Elevated permissions are re-checked against the database on each request, so demoting an account takes effect immediately. A route audit test fails the build if a non-public endpoint is added without a permission guard. - Service Credentials: Schedulers and monitoring authenticate with the
X-Service-Keyheader using keys fromSERVICE_API_KEYS(name:keypairs, keys of at least 32 characters). TheSERVICErole can run syncs, score calculation, reconciliation and queue health checks, but cannot act as a manager, and it can never be claimed through a user JWT. - Consult
REGULATORY_CONSIDERATIONS.mdfor legal classifications, skill-game exemptions, and AML operational considerations.
This project is licensed under the ISC License. See the LICENSE file for details.
This section helps contributors configure the Stellar Testnet environment locally to interact with Soroban contracts and the USDC Stellar Asset used by FantasyXI.
-
Network & endpoints:
- Horizon:
https://horizon-testnet.stellar.org - Soroban RPC:
https://soroban-testnet.stellar.org - Network passphrase:
Test SDF Network ; September 2015
- Horizon:
-
Required environment variables (add these to
backend/.envandfrontend/.env.localas appropriate):STELLAR_NETWORK(e.g.TESTNET)STELLAR_HORIZON_URL(e.g.https://horizon-testnet.stellar.org)STELLAR_SOROBAN_RPC_URL(e.g.https://soroban-testnet.stellar.org)STELLAR_NETWORK_PASSPHRASE(e.g.Test SDF Network ; September 2015)STELLAR_USDC_ASSET_CODE(e.g.USDC)STELLAR_USDC_ISSUER(classic issuer public key, e.g.GC43IGCUMQYECKMRKGSJE2RPQPJ2QNHFB6VAHNNBO4NONKK3PVHEXN25)STELLAR_USDC_TOKEN_CONTRACT_ID(SAC contract id, e.g.CBKWOGJ7CQSVZXIOIIPCDAUT6APQYBCEE7QTSGDZZ2RO6D3JYRMKWZNG)STELLAR_ESCROW_CONTRACT_ID(Escrow contract id, e.g.CB4KIK42P32SZHKG4JBDCJUV4A4KGCDN6RHOOTIFSBGZHS2IF653VOEA)
-
Creating & funding testnet accounts:
- Generate a new keypair using the Stellar Laboratory or the SDK of your choice.
- Stellar Laboratory Keypair tool: https://laboratory.stellar.org/#account-creator?network=test
- Fund your testnet account using Friendbot:
- Friendbot URL:
https://friendbot.stellar.org/?addr=YOUR_PUBLIC_KEY - Example:
curl "https://friendbot.stellar.org/?addr=G...YOUR_PUBLIC...KEY"
- Friendbot URL:
- Generate a new keypair using the Stellar Laboratory or the SDK of your choice.
-
Obtaining testnet USDC:
- On Testnet, USDC is represented by a token issuer and/or SAC contract. If the repo provides a test USDC faucet script, run it; otherwise request USDC by contacting the test asset issuer or minting via a local/authorized issuer key (not in production).
- Helpful links:
- Stellar Laboratory (Transactions & Assets): https://laboratory.stellar.org/
- Horizon Testnet Explorer: https://stellar.expert/explorer/testnet
-
Trustline note: Before receiving USDC or interacting with the USDC SAC, ensure your test account establishes a trustline to the USDC asset (unless using contract-controlled flows that don't require a classic trustline). In the Stellar Laboratory, add an Asset with code
USDCand issuerGC43IGCUMQYECKMRKGSJE2RPQPJ2QNHFB6VAHNNBO4NONKK3PVHEXN25and submit a change-trust operation. -
Quick example: fund + trustline via
stellar-sdk(Node.js)
// install: npm install stellar-sdk
const StellarSdk = require('stellar-sdk');
const server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
const pair = StellarSdk.Keypair.random();
console.log('Public:', pair.publicKey());
console.log('Secret:', pair.secret());
// Fund using Friendbot (curl in shell) then create trustline and optionally request test USDC from issuer-owned faucet.If you follow the steps above you will be able to fund a testnet account and configure your local environment to interact with the Soroban RPC and the FantasyXI escrow contract on Stellar Testnet.