Architecture .

Schéma technique complet de l'app DIGGR : frontend, backend serverless, base de données, bot Telegram, intégrations X et Solana. Stack 100 % gratuite jusqu'à ~5 000 users.

Sommaire

  1. Vue d'ensemble
  2. Schéma global
  3. Détail des composants
  4. Flows utilisateur
  5. Schéma base de données
  6. Endpoints API
  7. Sécurité & anti-sybil
  8. Scaling & coûts

1. Vue d'ensemble

L'application est composée de 5 grandes briques qui communiquent entre elles :

  1. Frontend statique (Vite + React) hébergé sur Cloudflare Pages — UI utilisateur
  2. API serverless (Cloudflare Pages Functions) — logique métier, vérifications, signature
  3. Base de données (Supabase Postgres) — users, points, tasks, referrals
  4. Bot Telegram (existant, sur EC2) — entrée principale des users + vérif join canal
  5. Services externes — X OAuth, Solana RPC, Turnstile
Principe directeur : aucun serveur dédié pour le frontend. Tout ce qui scale est serverless. L'EC2 ne sert qu'au bot Telegram (déjà en place). Si le bot crash, l'app web continue de fonctionner.

2. Schéma global

👤 User Browser + wallet FRONTEND Cloudflare Pages Vite + React @solana/wallet-adapter Tailwind / shadcn Static — bandwidth illimité API SERVERLESS Pages Functions (Workers) /auth/wallet /auth/x /tasks/verify /referral/claim 100k req/jour gratuit DATABASE Supabase (Postgres) users · tasks · completions referrals · points_log RLS + Studio admin 500 MB / 50k MAU gratuit BOT TELEGRAM EC2 AWS (existant) Python / Node bot /start /link /points getChatMember() Telegram Cloud Bot API · webhooks Login Widget 100% gratuit X (Twitter) API OAuth 2.0 (free) · oEmbed Solana RPC (Helius) Wallet age · on-chain checks Cloudflare Turnstile Anti-bot · 1M / mois gratuit Wallet Solana Phantom · Solflare · Backpack HTTPS fetch SQL OAuth RPC sign read/write Légende Requête HTTP synchrone Appel API externe Hébergé Cloudflare Hébergé Supabase Hébergé EC2 AWS Service externe (X, Solana) Tout sauf l'EC2 est serverless / managé. Aucun serveur web à maintenir.

3. Détail des composants

3.1 Frontend (Cloudflare Pages) 0 €

StackVite 5 · React 18 · TypeScript · Tailwind · shadcn/ui
Wallet@solana/wallet-adapter-react + adapters Phantom/Solflare/Backpack
RoutingReact Router (4 routes : /, /app, /r/:slug, /admin)
StateZustand pour l'état user, React Query pour le data fetching
Buildvite build → SPA statique → dist/ push GitHub → CF Pages auto-deploy
Domaineapp.diggr.xyz (subdomain de la landing actuelle)

3.2 API serverless (Pages Functions) 0 €

Pages Functions = même runtime que Cloudflare Workers, déployé automatiquement avec le frontend depuis le dossier functions/.

RuntimeV8 isolates (cold start < 5ms)
Limite gratuite100 000 invocations/jour, 10 ms CPU
EndpointsVoir section 6. Endpoints API
SecretsConfigurés via dashboard Cloudflare (chiffrés)

3.3 Base de données (Supabase) 0 €

Postgres 15 managé. Schéma détaillé section 5. Schéma DB.

SécuritéRow Level Security activée sur toutes les tables. Les requêtes frontend utilisent l'anon key + JWT. Les requêtes API/bot utilisent la service_role key (bypass RLS).
AuthOn n'utilise PAS Supabase Auth (qui veut email/password). Auth = signature wallet, on émet notre propre JWT depuis la Pages Function.
RealtimeOptionnel : abonnement aux changements de la table points_log pour afficher le score en live (gratuit).
BackupFree = pas de backup auto. Solution : cron mensuel pg_dump vers GitHub privé.

3.4 Bot Telegram (EC2 existant) déjà payé

Pas touche à l'archi existante. On ajoute juste :

Le bot ne sert pas de proxy pour les autres tasks. Tout passe par l'API frontend.

3.5 Services externes

X OAuth 2.0Login uniquement. PKCE flow. Récupère x_user_id + username. free tier
X oEmbedVérifier qu'un tweet/RT existe et son auteur, sans auth. gratuit
Solana RPCHelius free tier. getSignaturesForAddress pour age wallet, getBalance pour solde min.
TurnstileCaptcha invisible Cloudflare. Token vérifié serverside avant chaque action sensible.

4. Flows utilisateur

Flow 1 — Inscription (premier login)

1.User arrive sur app.diggr.xyz (potentiellement avec ?ref=abc123)
2.Frontend stocke le ?ref en cookie (TTL 30j)
3.User clique "Connect wallet" → Phantom popup → user signe le message SIWS
4.Frontend POST /api/auth/wallet avec { pubkey, signature, message, turnstileToken }
5.API vérifie : Turnstile valide → signature ed25519 valide → nonce non-réutilisé
6.API insert/upsert dans users, lit le cookie ref, lie au parrain dans referrals
7.API émet un JWT (HS256, 7j) → cookie httpOnly
8.Frontend redirige vers /app, affiche les tasks

Flow 2 — Lier compte X

1.User clique "Connect X" → frontend redirige vers https://x.com/i/oauth2/authorize?...&state=NONCE&code_challenge=...
2.User autorise → X redirige vers /auth/x/callback?code=XXX&state=NONCE
3.API échange le code contre un access_token (oneshot)
4.API GET /2/users/me avec ce token → récupère x_user_id, username
5.API update users.x_user_id, users.x_username (vérifie unicité)
6.Le token est jeté (on ne stocke pas, on en a plus besoin)

Flow 3 — Vérifier task "Follow Telegram"

1.User clique "Connect Telegram" → Login Widget Telegram s'ouvre
2.User confirme → widget renvoie { id, username, hash, auth_date } signé HMAC
3.Frontend POST /api/auth/telegram
4.API vérifie HMAC avec BOT_TOKEN → update users.telegram_user_id
5.User clique "Verify" sur la task Follow TG
6.API POST https://api.telegram.org/bot{TOKEN}/getChatMember?chat_id=CANAL_ID&user_id=...
7.Si status ∈ {member, administrator, creator} → insert completions + points_log

Flow 4 — Vérifier task "RT du tweet du jour" (semi-auto)

1.User RT le tweet sur X
2.User colle l'URL de son quote/RT dans l'UI
3.Frontend POST /api/tasks/verify-rt avec { task_id, rt_url }
4.API GET https://publish.twitter.com/oembed?url={rt_url} → récupère author_name
5.API vérifie author_name === users.x_username ET le tweet référence le tweet original
6.Si OK → insert completions + points_log

Flow 5 — Récompense referral (asynchrone)

1.Filleul complète une task → trigger SQL after_completion_insert
2.Trigger compte les tasks de base complétées par le filleul
3.Si == 2 (Follow X + Follow TG) ET referrals.reward_credited = false
4.UPDATE users SET points = points + 50 WHERE id = parrain_id
5.UPDATE referrals SET reward_credited = true
6.INSERT points_log avec reason = 'referral_reward'

5. Schéma base de données

Tables principales (Postgres) :

-- Identité utilisateur
users (
  id              uuid primary key default gen_random_uuid(),
  wallet          text unique not null,            -- adresse Solana base58
  x_user_id       text unique,                     -- nullable jusqu'à connect X
  x_username      text,
  telegram_user_id bigint unique,                  -- nullable jusqu'à connect TG
  telegram_username text,
  referral_slug   text unique not null,            -- 8 chars, généré au signup
  points          int not null default 0,
  created_at      timestamptz default now(),
  last_login_at   timestamptz,
  ip_country      text,                            -- géolocalisation grossière
  flags           jsonb default '{}'::jsonb        -- anti-sybil flags
);

-- Tasks (gérées via Supabase Studio par toi)
tasks (
  id              uuid primary key default gen_random_uuid(),
  type            text not null,                   -- 'follow_x', 'follow_tg', 'like', 'rt', 'onchain'
  title           text not null,
  description     text,
  points          int not null,
  config          jsonb not null,                  -- { tweet_id, channel_id, token_mint, ... }
  active_from     timestamptz default now(),
  active_to       timestamptz,                     -- null = permanent
  recurrence      text default 'once',             -- 'once', 'daily'
  enabled         boolean default true,
  created_at      timestamptz default now()
);

-- Completions (1 ligne par task validée)
completions (
  id              uuid primary key default gen_random_uuid(),
  user_id         uuid references users(id) on delete cascade,
  task_id         uuid references tasks(id) on delete cascade,
  completed_at    timestamptz default now(),
  proof           jsonb,                           -- url RT, tx hash, etc.
  points_awarded  int not null,
  unique (user_id, task_id, date_trunc('day', completed_at))  -- daily tasks
);

-- Referrals
referrals (
  id              uuid primary key default gen_random_uuid(),
  referrer_id     uuid references users(id) on delete cascade,
  referee_id      uuid references users(id) on delete cascade unique,  -- 1 user = 1 parrain
  created_at      timestamptz default now(),
  reward_credited boolean default false,
  rewarded_at     timestamptz
);

-- Log immutable des points (audit + display)
points_log (
  id              uuid primary key default gen_random_uuid(),
  user_id         uuid references users(id) on delete cascade,
  delta           int not null,
  balance_after   int not null,
  reason          text not null,                   -- 'task:{id}', 'referral_reward', 'admin_adjust'
  ref_id          uuid,
  created_at      timestamptz default now()
);

-- Nonces SIWS (TTL 5 min, anti-replay)
auth_nonces (
  nonce           text primary key,
  pubkey          text not null,
  expires_at      timestamptz not null
);
Index importants : users(wallet), users(x_user_id), users(telegram_user_id), users(referral_slug), completions(user_id, task_id), points_log(user_id, created_at desc).

Row Level Security (RLS)

-- users : un user voit seulement son propre row, sauf colonnes publiques
alter table users enable row level security;
create policy "users_self_read" on users for select using (auth.uid() = id);
create policy "users_public_leaderboard" on users for select using (true)
  -- limité aux colonnes safe via une vue 'leaderboard'

-- tasks : tout le monde peut lire les tasks actives
alter table tasks enable row level security;
create policy "tasks_read_active" on tasks for select using (enabled and active_from <= now());

-- completions : user voit ses propres complétions
alter table completions enable row level security;
create policy "completions_self_read" on completions for select using (user_id = auth.uid());

-- INSERT : uniquement via service_role (l'API Cloudflare)

6. Endpoints API

MéthodeEndpointDescriptionAuth
POST/api/auth/nonceGénère un nonce SIWS
POST/api/auth/walletVérifie signature, crée/login user, set JWT cookie
GET/api/auth/x/startRedirige vers X OAuthJWT
GET/api/auth/x/callbackCallback OAuth, lie x_user_idJWT
POST/api/auth/telegramVérifie HMAC widget, lie telegram_user_idJWT
GET/api/meProfil user + pointsJWT
GET/api/tasksListe tasks actives + statut completionJWT
POST/api/tasks/:id/verifyVérifie une task (TG join, RT URL, on-chain...)JWT + Turnstile
GET/api/referral/meMon slug + stats filleulsJWT
GET/api/leaderboardTop 100 (vue restreinte)
POST/api/auth/logoutDétruit le cookie JWTJWT

7. Sécurité & anti-sybil

CoucheMesureCoût
Anti-botCloudflare Turnstile invisible sur signup + claim referral0 €
Replay attackNonce SIWS unique, TTL 5 min en DB0 €
Wallet sybilRefus si wallet créé < 7 jours (Helius getSignaturesForAddress)0 €
Wallet sybilSolde minimum 0,001 SOL pour réclamer le bonus referral0 €
Compte sybil1 telegram_user_id = 1 wallet (contrainte unique)0 €
Compte sybil1 x_user_id = 1 wallet (contrainte unique)0 €
IP rate limitWorkers KV : max 5 signups / IP / 24h0 €
SessionJWT HS256 7j, cookie httpOnly + Secure + SameSite=Lax0 €
SecretsTous chiffrés dans Cloudflare env, jamais exposés au frontend0 €
RLSPostgres Row Level Security pour empêcher cross-user reads0 €
Limitations à accepter : ce stack stoppe les farms low-effort. Un attaquant déterminé avec budget peut bypass. Solutions plus solides (KYC, proof-of-humanity) = payantes et UX dégradée. À considérer seulement si l'airdrop devient gros.

8. Scaling & coûts par stade

StadeUsersBottleneckActionCoût
MVP0 - 500Aucun0 €
Beta500 - 5 000DB Supabase 500MB approcheCleanup vieux auth_nonces + points_log archivage mensuel0 €
Lancement5k - 20kVérif Twitter manuelle ne scale plusActiver X API Basic + Supabase Pro ~225 USD / mois
Croissance20k - 100kRPC Solana free dépassé, CF Workers limitHelius Dev + CF Workers Paid ~280 USD / mois
Mature100k +DB Postgres single-instanceRead replicas Supabase + caching Redis (Upstash)~500-1 000 USD / mois