Réel, simulé ou à venir
Cette page fait l'inventaire honnête de l'état du projet au 12 novembre 2026. Elle distingue ce qui fonctionne réellement, ce qui n'existe qu'en mode simulé, ce qui est encore fictif dans le code, ce qui n'a pas été fait et le code mort. Elle est à relire avant toute démonstration et avant toute mise en production.
Synthèse
| Catégorie | Contenu |
|---|---|
| Réel | Contrats Solidity, tirage Chainlink VRF, portefeuilles intégrés chiffrés, dotation, achats et cadeaux signés, versement automatique, POAP, disjoncteur, commission, authentification, vérification d'âge par la Mairie, comptes Mairie et partenaires, consentement aux traceurs, salle d'attente, RGPD, indexeur, keeper |
| Simulé à dessein | Mode BLOCKCHAIN_MODE=simulated, coordinateur VRF local, ethers fictifs |
| Encore fictif | Notifications push, Sentry, libellé multi-signatures, paliers de la jauge, répartition par âge par défaut, rapport PDF serveur, stockage S3 en local, solde ETH en mode simulé |
| Pas encore fait | Déploiement Sepolia, mise en ligne sur etherbay.tech, audit externe, tests utilisateurs |
| Code mort | supprimé : useGaslessWeb3.ts (web et mobile) et WalletConnectButton.tsx (mobile) ; reste le champ gaslessSponsored des réponses |
Ce qui est réel
Ces fonctionnalités fonctionnent de bout en bout en mode onchain, contre un vrai nœud Ethereum. Elles sont couvertes par le test d'intégration backend/__tests__/chain/onchain-lottery.chain.ts, exécuté en CI sur une blockchain locale.
| Fonctionnalité | Ce qui la rend réelle | Référence |
|---|---|---|
| Smart contracts déployables | BlockLucky et BlockLuckyPOAP compilés, testés (54 tests, 100 % des lignes) et déployés automatiquement sur le nœud Hardhat local, avec des rôles séparés (administrateur et propriétaire VRF, trésorerie, opérateur) ; scripts Sepolia prêts |
Blockchain locale, Déploiement sur Sepolia |
| Tirage Chainlink VRF | Le contrat demande le hasard au coordinateur VRF v2.5. En local, le coordinateur est le contrat simulé officiel de Chainlink (VRFCoordinatorV2_5Mock) et un oracle local (smart-contract/scripts/local-vrf-responder.ts) répond aux demandes. Sur Sepolia, ce serait le coordinateur réel de Chainlink |
Tirage Chainlink VRF |
| Portefeuilles intégrés chiffrés | Une clé Ethereum aléatoire par citoyen, chiffrée en AES-256-GCM avec ENCRYPTION_KEY avant d'être stockée |
backend/src/blockchain/wallet.service.ts |
| Dotation de bienvenue | Vrai transfert d'ethers fictifs du compte opérateur vers le portefeuille, une seule fois par compte, protégé par un verrou Redis | grantWelcomeEthers |
| Achats signés par le citoyen | La transaction buyTickets est signée par la clé du portefeuille du citoyen, déchiffrée à la volée par le backend. Le citoyen paie le ticket et les frais de réseau sur sa dotation |
backend/src/blockchain/lottery.gateway.ts |
| Ticket cadeau | buyTicketsFor signé par l'expéditeur au profit du destinataire |
giftOnchain de backend/src/api/lottery/lottery.services.ts |
| Versement automatique de la cagnotte | À l'événement WinnerPicked, l'indexeur met en file un CLAIM_PRIZE signé par le portefeuille du gagnant ; même mécanisme pour les remboursements (CLAIM_REFUND) |
backend/src/blockchain/chain-actions.ts |
| Badge POAP ERC-721 | Frappé par le compte opérateur pour chaque participant, non transférable | Badge POAP |
| Disjoncteur on-chain | pause et unpause du contrat, déclenchés depuis le dashboard Mairie |
Disjoncteur |
| Reversement de la commission | withdrawFees vers l'adresse treasury du contrat |
withdrawMunicipalityTreasury de backend/src/api/admin/admin.services.ts |
| Tirage et clôture automatiques | Le keeper appelle requestDraw une fois écoulé le délai de révélation (DRAW_REVEAL_DELAY_SECONDS, 10 minutes par défaut) après que le contrat l'autorise, ou closeExpiredRound pour un tour expiré |
Keeper du tirage |
| Indexation | Les tickets, tirages et annulations sont projetés en base depuis les événements du contrat, avec un curseur | Indexeur d'événements |
| Connexion par code email ou par mot de passe | Code à 6 chiffres haché, valable 10 minutes, envoyé par email ; mot de passe haché par bcrypt, verrouillage après échecs répétés | Authentification |
| Vérification d'âge | Recto et verso de la pièce d'identité chiffrés en AES-256-GCM, examinés par un agent de la Mairie puis effacés ; achat, cadeau et échange refusés (403 AGE_VERIFICATION_REQUIRED) tant que l'âge n'est pas vérifié |
backend/src/middlewares/age-verification.middleware.ts, frontend-web/src/app/mairie/identites/page.tsx, frontend-web/src/app/(citoyen)/profil/identite/page.tsx |
| 2FA TOTP | Secret chiffré, codes de secours hachés, protection contre le rejeu, code de l'application accepté seulement après le premier facteur (loginTicket), obligatoire pour les administrateurs |
Authentification et 2FA |
| RGPD | Export JSON, suppression confirmée par email avec anonymisation et effacement de la clé du portefeuille | Suppression RGPD |
| Comptes Mairie et partenaires | Page /mairie/comptes : liste, création avec invitation par email, attribution, changement et retrait de rôle, journalisés dans l'audit |
Administration Mairie |
| Documents légaux et consentement | Conditions générales, règlement, mentions légales, confidentialité, traceurs et charte de transparence sur le web et le mobile ; acceptation exigée à l'inscription ; mesure d'audience du web soumise à un bandeau de consentement | Cookies et mentions |
À savoir
« Signé par le citoyen » veut dire signé avec la clé du portefeuille du citoyen. Cette clé est gardée et utilisée par le backend : le modèle est custodial. Le citoyen ne signe rien lui-même. Ce choix est argumenté dans l'ADR 0004.
Ce qui est simulé à dessein
Le mode simulé du backend
BLOCKCHAIN_MODE accepte deux valeurs, définies dans backend/src/config/env.config.ts :
| Valeur | Usage | Comportement |
|---|---|---|
onchain (défaut) |
Développement normal, CI d'intégration, production | Toutes les opérations passent par le contrat |
simulated |
Tests unitaires (backend/__tests__/setup-env.ts), développement sans nœud |
backend/src/services/blockchain.service.ts génère des hash de transaction aléatoires, tire le gagnant avec crypto.randomInt, garde la pause dans Redis ; le webhook signé sert d'entrée d'événements |
Le schéma de configuration refuse de démarrer en production avec une autre valeur que onchain : « BLOCKCHAIN_MODE doit valoir onchain en production ». Le mode simulé ne peut donc pas être déployé par erreur. Voir Mode onchain ou simulé.
Les ethers fictifs
Ce n'est pas une limite mais le principe du projet : en local, les ethers sont ceux du nœud Hardhat (chain ID 31337) ; sur Sepolia, ce seraient des ETH de test. Aucun ne peut être converti en monnaie réelle.
Ce qui est encore fictif
Ces éléments existent dans l'interface ou dans l'API mais ne reposent sur aucune donnée ni aucun traitement réel.
| Élément | Constat | Fichier |
|---|---|---|
| Historique des notifications push | getPushNotificationsHistory renvoie trois notifications codées en dur. Aucun envoi de notification push n'existe côté serveur ; la bannière de notification du mobile est déclenchée localement (triggerSimulatedOsPush) |
backend/src/api/user/user.services.ts, mobile-app/src/hooks/usePushNotifications.ts |
| Sentry | Aucun SDK Sentry n'est installé. sentry.client.ts stocke les erreurs dans un tableau en mémoire ; EXPO_PUBLIC_SENTRY_DSN n'active qu'un message local. Côté web, « Sentry » désigne une simple modale d'erreur |
mobile-app/src/services/sentry.client.ts |
| Trésorerie « multi-signatures » | Le résumé OpenAPI, l'action d'audit TREASURY_WITHDRAWAL_MULTISIG et le champ multisigWallet parlent de multi-signatures. En réalité la trésorerie est une adresse simple (treasury) du contrat. En mode onchain, si l'adresse saisie dans le dashboard diffère, le backend appelle d'abord setTreasury (rôle DEFAULT_ADMIN_ROLE, laissé à l'opérateur en local, réservé à MAIRIE_ADMIN_ADDRESS sur Sepolia, où seul un retrait vers la trésorerie enregistrée fonctionne), puis withdrawFees verse toute la commission accumulée : le montant saisi est ignoré |
backend/src/api/admin/admin.routes.ts, backend/src/blockchain/lottery.gateway.ts |
| Paliers de la jauge | Quatre paliers (5 000, 7 500, 9 000 et 10 000 participants) annoncent les coulisses du tirage, une offre spéciale des commerçants, un jeu bonus et l'ouverture de la salle. Seule la fermeture des achats à 10 000 tickets découle du contrat (maxTickets) ; les trois autres avantages ne sont pas implémentés |
buildMilestones de backend/src/api/lottery/lottery.services.ts |
| Solde ETH en mode simulé | walletService.balanceOf renvoie null hors mode onchain : le mobile n'affiche aucun solde et la billetterie considère le solde comme suffisant |
backend/src/blockchain/wallet.service.ts |
| Répartition par âge | Sans événement portant une tranche d'âge, le dashboard affiche une répartition codée en dur (42 %, 33 %, 17 %, 8 %) ; target18_35Validated vaut toujours true |
backend/src/api/analytics/analytics.services.ts |
| Part de la Mairie au dashboard | Calculée à 10 % de la somme des cagnottes en base, et non lue sur le contrat | backend/src/api/admin/admin.services.ts |
| Rapport PDF côté serveur | Le « PDF » envoyé sur S3 est un texte JSON déclaré application/pdf. Le web produit un vrai PDF par l'impression du navigateur |
generateAccountingReport |
| Stockage S3 | Le backend est configuré pour un MinIO local (AWS_S3_ENDPOINT=http://localhost:9000, identifiants minioadmin), mais docker-compose.yml ne fournit aucun service MinIO. L'envoi échoue, est journalisé en avertissement, et l'URL renvoyée ne pointe vers rien |
backend/src/services/s3.service.ts |
| Quêtes sociales | Validées sur simple déclaration du citoyen, sans vérification ; la liste est codée en dur et l'état est gardé dans Redis | QUESTS_CATALOG de backend/src/api/user/user.services.ts |
| Expiration des offres | La date saisie par le partenaire est écrite dans Redis mais jamais relue | backend/src/api/partners/partners.services.ts |
| Codes promo importés | Gardés uniquement dans Redis (partner:codes:<offre>) ; une purge de Redis les perd |
backend/src/api/marketplace/marketplace.services.ts |
| Événements Socket.IO déclarés | draw:countdown, draw:started, queue:position et network:fallback sont typés mais jamais émis |
backend/src/types/socket.types.ts |
| Traces de Web3Auth | Il n'y a pas de Web3Auth : le portefeuille est créé par le backend. EXPO_PUBLIC_WEB3AUTH_CLIENT_ID figure dans mobile-app/.env.example sans être lu. L'adresse envoyée par le web à la connexion (POST /auth/login) n'est qu'un identifiant de recherche : le vrai portefeuille est créé côté serveur. Le mobile, qui passe par /auth/signin et /auth/signup, n'envoie plus d'adresse |
backend/src/api/auth/auth.routes.ts, frontend-web/src/lib/wallet.ts |
| Tests mobiles | 8 tests écrits mais sans exécuteur : ni script test ni Jest dans mobile-app/package.json |
mobile-app/__tests__/ |
| Connexion Google | Code complet côté serveur et mobile, mais GOOGLE_CLIENT_IDS est vide par défaut : la route répond 503 et le mobile grise son bouton tant qu'aucun identifiant client n'est déclaré. La connexion Apple a été retirée |
backend/src/services/oauth-verifier.service.ts |
Ce qui n'est pas encore fait
| Élément | État | Ce qu'il faut faire |
|---|---|---|
| Déploiement sur Sepolia | Quatre scripts prêts, jamais exécutés : npm run vrf:sepolia (abonnement VRF), deploy:sepolia (contrats et rôles), automation:sepolia (upkeep) et verify:sepolia (Etherscan). Il n'existe pas de fichier deployments/sepolia.json : l'exécution demande les clés de l'équipe (déployeur, multisig de la Mairie, opérateur), de l'ETH et du LINK de test, que le dépôt ne contient pas |
Comptes, fonds et ordre des commandes : voir Déploiement sur Sepolia et Redéployer sur Sepolia |
| Vérification du code sur Etherscan | Dépend du déploiement Sepolia | npm run verify:sepolia, décrit dans Déploiement sur Sepolia |
| Mise en ligne sur etherbay.tech | Application non hébergée | Déploiement sur etherbay.tech |
| Chainlink Automation | Le contrat implémente checkUpkeep et performUpkeep, et npm run automation:sepolia sait inscrire l'upkeep, mais aucun n'est enregistré faute de déploiement ; le keeper du backend joue ce rôle |
Chainlink Automation |
| Audit de sécurité externe | Aucun ; seules les analyses automatiques (Slither, CodeQL, gitleaks, Trivy) tournent | Audits et checklists |
| Tests utilisateurs | Protocole rédigé par l'équipe, entretiens et tests non réalisés | Profils et parcours |
| Garde des secrets en production | ENCRYPTION_KEY et RELAYER_PRIVATE_KEY à confier à un coffre de secrets |
Clés et garde des portefeuilles |
Code mort
La première version prévoyait des achats « gasless », c'est-à-dire relayés et payés par la Mairie. Depuis le passage au portefeuille intégré, le citoyen paie lui-même les frais sur sa dotation. Les trois fichiers de cette époque, que plus rien n'importait, ont été supprimés : les hooks useGaslessWeb3.ts du web et du mobile, remplacés par useBlockLuckyWallet et useCitizenWallet, et le composant mobile WalletConnectButton.tsx, remplacé par IntegratedWalletButton, que plus aucun écran n'affiche depuis la refonte du mobile.
La refonte du mobile du 12 novembre 2026 a aussi supprimé LoginScreen, SettingsStack et sept composants de loterie (ChatBox, CountdownTimer, LeaderboardList, POAPBadge, RouletteAnimation, TicketCard, WinnerClaimCard), remplacés par les écrans et composants décrits dans Navigation et écrans.
Il reste le champ gaslessSponsored des réponses de GET /lottery/status et POST /lottery/buy, conservé pour ne pas casser les clients : il vaut false en mode onchain. D'autres éléments installés mais inutilisés côté mobile sont recensés dans Stack et structure de l'application mobile.
Avant une démonstration
- Lancer la blockchain locale et réinitialiser la base si le conteneur a redémarré (Dépannage).
- Ne pas présenter la répartition par âge, les paliers ni les notifications comme des données réelles.
- Présenter la trésorerie comme une adresse simple, et le tirage local comme non certifié par Chainlink (coordinateur simulé).
- Montrer un ticket, sa transaction et le tirage : c'est la preuve demandée par le livrable de cadrage de l'équipe.
Le déroulé complet est dans Préparer la soutenance. La liste de la dette technique, avec les priorités de correction, est dans Dette technique et limites.