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.
L'application est composée de 5 grandes briques qui communiquent entre elles :
| Stack | Vite 5 · React 18 · TypeScript · Tailwind · shadcn/ui |
|---|---|
| Wallet | @solana/wallet-adapter-react + adapters Phantom/Solflare/Backpack |
| Routing | React Router (4 routes : /, /app, /r/:slug, /admin) |
| State | Zustand pour l'état user, React Query pour le data fetching |
| Build | vite build → SPA statique → dist/ push GitHub → CF Pages auto-deploy |
| Domaine | app.diggr.xyz (subdomain de la landing actuelle) |
Pages Functions = même runtime que Cloudflare Workers, déployé automatiquement avec le frontend depuis le dossier functions/.
| Runtime | V8 isolates (cold start < 5ms) |
|---|---|
| Limite gratuite | 100 000 invocations/jour, 10 ms CPU |
| Endpoints | Voir section 6. Endpoints API |
| Secrets | Configurés via dashboard Cloudflare (chiffrés) |
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). |
|---|---|
| Auth | On n'utilise PAS Supabase Auth (qui veut email/password). Auth = signature wallet, on émet notre propre JWT depuis la Pages Function. |
| Realtime | Optionnel : abonnement aux changements de la table points_log pour afficher le score en live (gratuit). |
| Backup | Free = pas de backup auto. Solution : cron mensuel pg_dump vers GitHub privé. |
Pas touche à l'archi existante. On ajoute juste :
/link <wallet_address> qui appelle l'API /auth/telegram/link avec le telegram_user_id + signature HMAC/points qui lit users.points dans Supabase (lecture directe via Postgres connection string + service_role)verify_join(user_id) qui appelle getChatMember sur le canal officielLe bot ne sert pas de proxy pour les autres tasks. Tout passe par l'API frontend.
| X OAuth 2.0 | Login uniquement. PKCE flow. Récupère x_user_id + username. free tier |
|---|---|
| X oEmbed | Vérifier qu'un tweet/RT existe et son auteur, sans auth. gratuit |
| Solana RPC | Helius free tier. getSignaturesForAddress pour age wallet, getBalance pour solde min. |
| Turnstile | Captcha invisible Cloudflare. Token vérifié serverside avant chaque action sensible. |
app.diggr.xyz (potentiellement avec ?ref=abc123)?ref en cookie (TTL 30j)SIWS/api/auth/wallet avec { pubkey, signature, message, turnstileToken }users, lit le cookie ref, lie au parrain dans referrals/app, affiche les taskshttps://x.com/i/oauth2/authorize?...&state=NONCE&code_challenge=.../auth/x/callback?code=XXX&state=NONCE/2/users/me avec ce token → récupère x_user_id, usernameusers.x_user_id, users.x_username (vérifie unicité){ id, username, hash, auth_date } signé HMAC/api/auth/telegramBOT_TOKEN → update users.telegram_user_idhttps://api.telegram.org/bot{TOKEN}/getChatMember?chat_id=CANAL_ID&user_id=...status ∈ {member, administrator, creator} → insert completions + points_log/api/tasks/verify-rt avec { task_id, rt_url }https://publish.twitter.com/oembed?url={rt_url} → récupère author_nameauthor_name === users.x_username ET le tweet référence le tweet originalcompletions + points_logafter_completion_insertreferrals.reward_credited = falseusers SET points = points + 50 WHERE id = parrain_idreferrals SET reward_credited = truepoints_log avec reason = 'referral_reward'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
);
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).
-- 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)
| Méthode | Endpoint | Description | Auth |
|---|---|---|---|
POST | /api/auth/nonce | Génère un nonce SIWS | — |
POST | /api/auth/wallet | Vérifie signature, crée/login user, set JWT cookie | — |
GET | /api/auth/x/start | Redirige vers X OAuth | JWT |
GET | /api/auth/x/callback | Callback OAuth, lie x_user_id | JWT |
POST | /api/auth/telegram | Vérifie HMAC widget, lie telegram_user_id | JWT |
GET | /api/me | Profil user + points | JWT |
GET | /api/tasks | Liste tasks actives + statut completion | JWT |
POST | /api/tasks/:id/verify | Vérifie une task (TG join, RT URL, on-chain...) | JWT + Turnstile |
GET | /api/referral/me | Mon slug + stats filleuls | JWT |
GET | /api/leaderboard | Top 100 (vue restreinte) | — |
POST | /api/auth/logout | Détruit le cookie JWT | JWT |
| Couche | Mesure | Coût |
|---|---|---|
| Anti-bot | Cloudflare Turnstile invisible sur signup + claim referral | 0 € |
| Replay attack | Nonce SIWS unique, TTL 5 min en DB | 0 € |
| Wallet sybil | Refus si wallet créé < 7 jours (Helius getSignaturesForAddress) | 0 € |
| Wallet sybil | Solde minimum 0,001 SOL pour réclamer le bonus referral | 0 € |
| Compte sybil | 1 telegram_user_id = 1 wallet (contrainte unique) | 0 € |
| Compte sybil | 1 x_user_id = 1 wallet (contrainte unique) | 0 € |
| IP rate limit | Workers KV : max 5 signups / IP / 24h | 0 € |
| Session | JWT HS256 7j, cookie httpOnly + Secure + SameSite=Lax | 0 € |
| Secrets | Tous chiffrés dans Cloudflare env, jamais exposés au frontend | 0 € |
| RLS | Postgres Row Level Security pour empêcher cross-user reads | 0 € |
| Stade | Users | Bottleneck | Action | Coût |
|---|---|---|---|---|
| MVP | 0 - 500 | Aucun | — | 0 € |
| Beta | 500 - 5 000 | DB Supabase 500MB approche | Cleanup vieux auth_nonces + points_log archivage mensuel | 0 € |
| Lancement | 5k - 20k | Vérif Twitter manuelle ne scale plus | Activer X API Basic 200 USD + Supabase Pro 25 USD | ~225 USD / mois |
| Croissance | 20k - 100k | RPC Solana free dépassé, CF Workers limit | Helius Dev 49 USD + CF Workers Paid 5 USD | ~280 USD / mois |
| Mature | 100k + | DB Postgres single-instance | Read replicas Supabase + caching Redis (Upstash) | ~500-1 000 USD / mois |