# Teddies Platform Teddies Platform is a **full-stack Web3 collector experience** built around Solana NFT ownership. Collectors authenticate with their wallet, sync verified holdings, earn **badges** from rules applied to NFT metadata and on-chain behaviour, and showcase a **public profile** with leaderboard ranking and optional **Twitter** linkage. The codebase is organised as a **TypeScript monorepo** (`pnpm` workspaces + **Turborepo**) with a **Next.js** web application and an **Express** API backed by **PostgreSQL** via **Prisma**. **GitHub:** [https://github.com/soladdev/teddis-nft](https://github.com/soladdev/teddis-nft) (`soladdev/teddis-nft`) --- teddis ## Architecture overview ```mermaid flowchart TB subgraph Client["Collector browser"] UI["Next.js 14 App Router"] WA["Solana Wallet Adapter"] NA["NextAuth.js v5 Credentials"] UI --> WA UI --> NA end subgraph API["Backend API"] EX["Express"] CRON["node-cron jobs"] EX --> CRON end subgraph Data["Data & assets"] PG[("PostgreSQL")] PR["Prisma ORM"] S3["AWS S3"] PG --- PR end subgraph Chain["Blockchain & indexing"] SOL["Solana RPC / metadata"] IDX["Indexer service BACKEND_WEB3_URL"] end subgraph External["Third parties"] TW["Twitter OAuth 2"] SE["Sentry"] end NA -->|"POST /auth/login , JWT cookie"| EX UI -->|"REST /api , /auth"| EX WA -.->|"Sign nonce"| SOL EX --> PR EX --> S3 EX --> TW EX --> SE CRON -->|"calculateBadges"| PR CRON -->|"processNftsOfUser"| IDX IDX -->|"POST /api/nfts/webhook"| EX EX -.->|"NFT catalogue sync"| SOL ``` --- ## Repository layout | Path | Role | |------|------| | `apps/frontend` | Next.js collector UI: wallet connection, profiles (`/collectors/...`), sessions, animations | | `apps/backend` | Express REST API, Prisma models, badge engine, cron jobs, screenshot/export utilities | | `packages/eslint-config` | Shared ESLint configuration | | `packages/typescript-config` | Shared TypeScript `tsconfig` bases | --- ## Technology stack (in depth) ### Monorepo & developer experience - **[Turborepo](https://turbo.build/repo)** orchestrates `build`, `dev`, `lint`, `format`, and `test` across packages with task caching and dependency ordering (`dependsOn: ["^build"]` where applicable). - **`pnpm` workspaces** (`pnpm-workspace.yaml`) deduplicate dependencies and link `@repo/*` packages efficiently. - **[Husky](https://typicode.github.io/husky/)** + **[Commitlint](https://commitlint.js.org/)** (Conventional Commits) support consistent commits; **[lint-staged](https://github.com/lint-staged/lint-staged)** + **[Prettier](https://prettier.io/)** keep formatting aligned. ### Frontend (`apps/frontend`) - **[Next.js 14](https://nextjs.org/)** delivers server/client rendering, routing, and optimized assets (including **`sharp`** for image pipelines where configured). - **[React 18](https://react.dev/)** for UI composition. - **[Tailwind CSS](https://tailwindcss.com/)** with **`tailwind-merge`**, **`clsx`**, and **`class-variance-authority`** for scalable styling; **`tailwindcss-animate`** for motion primitives. - **[Radix UI](https://www.radix-ui.com/)** primitives (`Dialog`, `Select`, `Tooltip`, etc.) provide accessible, unstyled building blocks. - **[Solana Wallet Adapter](https://github.com/anza-xyz/wallet-adapter)** (`@solana/wallet-adapter-react`, Phantom and multi-wallet support) connects browsers to Solana for signing. - **`@solana/web3.js`** is used where low-level Solana interactions are required alongside adapters. - **[NextAuth.js v5 (beta)](https://authjs.dev/)** with a **Credentials** provider integrates wallet login with the backend: the frontend posts wallet address, nonce, signature, and wallet type to `/auth/login`; the backend returns a JWT that is mirrored into the NextAuth JWT/session so UI code can use `useSession()`. - **[Sentry Next.js SDK](https://docs.sentry.io/platforms/javascript/guides/nextjs/)** captures errors and performance signals in production-like setups. - **[GSAP](https://greensock.com/gsap/)** + **`@gsap/react`** and **`@studio-freight/react-lenis`** support polished motion and scroll smoothing where used in the UI. - Supporting libraries include **`dayjs`** (dates), **`jwt-decode`**, **`lucide-react`** / **`bootstrap-icons`** (icons), **`react-infinite-scroll-component`**, and **`file-saver`** for downloads. ### Backend (`apps/backend`) - **[Express](https://expressjs.com/)** exposes HTTP APIs under `/auth` and `/api`, with **`cors`** restricted via **`ALLOWED_ORIGINS`**, **`cookie-parser`**, **`express-session`**, and **`morgan`** logs routed through **Winston**. - **[Prisma](https://www.prisma.io/)** maps the domain to **PostgreSQL** (`DATABASE_URL`, `directUrl` for migrations). The schema models users, profiles, NFT catalogue rows, owned mints per profile, badges, conditions, and earned badge state. - **`tsup`** bundles TypeScript to **`dist/`** for production; **`ts-node`** + **`nodemon`** power local development. - **[jsonwebtoken](https://github.com/auth0/node-jsonwebtoken)** issues tokens after wallet verification; **`tweetnacl`** / **`bs58`** support cryptographic verification aligned with Solana wallet signatures (see `signature.utils`). - **[twitter-api-v2](https://github.com/plhery/node-twitter-api-v2)** implements OAuth-style flows to link Twitter identities to wallets (authorization URL + callback). - **[node-cron](https://www.npmjs.com/package/node-cron)** schedules recurring jobs (badge recalculation and NFT status refresh—see Business logic). - **[AWS SDK for JavaScript v3 — S3 client](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/)** uploads assets where the profile/screenshot pipeline stores images. - **[Puppeteer](https://pptr.dev/)** renders profile views for downloadable screenshots; **[Sharp](https://sharp.pixelplumbing.com/)** and **[Jimp](https://github.com/jimp-dev/jimp)** handle image processing. - **[Sentry Node](https://docs.sentry.io/platforms/node/)** + **`sentry-cli`** inject and upload source maps from the build pipeline. --- ## Business logic ### Identity and access 1. **Nonce** — `POST /auth/nonce` stores a server-generated nonce on the `User` row for the given wallet address (upsert). 2. **Wallet login** — The user signs the nonce with their Solana wallet. `POST /auth/login` verifies the signature (wallet-type aware), then: - Requires **at least one Teddies NFT** resolved from chain/catalogue logic (`getWalletTeddiesNFTs`). If none are found, login fails with a clear error instead of issuing a token. - Reconciles **`OwnedNftMint`** rows with current holdings (including resolving duplicates across profiles where applicable). - Ensures **`UserProfile`** exists with a generated username if missing and seeds **displayed NFT** URLs when appropriate. - Issues a **JWT** and sets an **`httpOnly` cookie** (`token`). 3. **Frontend session** — NextAuth Credentials provider mirrors backend success into an app session (JWT strategy, configurable max age in `auth.ts`). ### Profile and leaderboard - **Public profile** — `GET /api/profiles/:wallet` returns showcase fields (username, colours, displayed mints, points, rarest badge, Twitter fields). Rank is computed relative to other collectors (`getUserRank`). - **Updates** — `PUT /api/profiles` (authenticated) accepts whitelisted fields (`background`, `color`, `username`, displayed mints, Twitter identifiers). Usernames are unique and length-limited; changing username marks **`changedUsername`**. - **Leaderboard** — `GET /api/leaderboard` returns ranked collectors (top profiles by engagement rules defined in service layer); optional **`authBypass`** allows read patterns suited for both anonymous and authenticated views. ### NFT catalogue and holder enrichment - **Catalogue reads** — `GET /api/nfts` combines database NFT metadata with optional **wallet-scoped** filtering: when `walletAddress` is supplied, responses merge **`OwnedNftMint`** fields (`streak`, `neverListed`, `latestSignature`) into each document for richer UI. - **Indexer integration** — `processNftsOfUser` calls an external **`BACKEND_WEB3_URL`** endpoint with mints and a **webhook URL** pointing at **`POST /api/nfts/webhook`**. That webhook applies batched updates to **`OwnedNftMint`** so traits used by badge rules stay aligned with indexer-backed signals. ### Badge system - **Models** — `Badge` defines name, rarity, imagery, and **`Condition`** rows with point weights. **`UserBadge`** / **`UserBadgeCondition`** persist what was satisfied and which mints contributed. - **Evaluation** — `calculateBadges` loads users with profiles, owned mints, and existing badges, then scores each badge via named criteria functions (`evaluateBadge`), including ownership counts, trait simplicity (`attributes.rank_explain`), never-listed behaviour, rankings (Top 10 / 100 / 500), streak-related scoring, and other campaign-specific rules mapped by badge name. - **Scheduling** — A **minute-level** cron recomputes badges to keep points and rarity summaries fresh; a **daily** cron triggers **`processNftsOfUser()`** without arguments to refresh holder metrics across **distinct mints** via the indexer. ### Twitter linking - Authenticated users obtain a Twitter authorization URL (`GET /auth/twitter/auth`). State and PKCE verifier are persisted on `User`, then **`GET /auth/twitter/callback`** validates state, exchanges the code, and updates profile Twitter fields before redirecting to the frontend collector route. ### Observability and ops - Structured logging (**Winston**) and HTTP logging (**Morgan**). - **Sentry** on API and Next.js for error tracking and optional profiling. --- ## Prerequisites - **Node.js** ≥ 18 (see root `package.json` `engines`) - **pnpm** (recommended for this workspace) - **PostgreSQL** reachable via `DATABASE_URL` (and `DIRECT_URL` for Prisma migrations if split) Additional integrations expect environment configuration on both apps (backend URL, frontend URL, CORS origins, JWT/session secrets, AWS credentials for S3, Sentry DSN/org/project, Twitter OAuth credentials, indexer URL/API key, Solana RPC if applicable). --- ## Getting started Install dependencies from the repository root: ```sh pnpm install ``` Run database migrations and generate Prisma Client for the backend (from `apps/backend`): ```sh cd apps/backend pnpm prisma migrate deploy pnpm prisma generate ``` Development — run both apps via Turborepo from the root: ```sh pnpm dev ``` Build everything: ```sh pnpm build ``` Quality gates: ```sh pnpm lint pnpm format:check pnpm test ``` --- ## API surface (summary) | Area | Methods | Notes | |------|---------|--------| | Auth | `POST /auth/nonce`, `POST /auth/login`, `GET /auth/twitter/auth`, `GET /auth/twitter/callback` | Wallet + Twitter flows | | Profiles | `GET /api/profiles/:wallet`, `PUT /api/profiles`, `GET /api/profile/screenshot`, `GET /api/leaderboard` | Screenshot requires auth | | NFTs | `GET /api/nfts`, `POST /api/nfts/webhook` | Webhook driven by indexer | | Badges | `GET /api/badges`, `GET /api/badges/:wallet` | Optional query flags per controller | --- ## License and naming Package metadata uses the name **`teddies-platform`**. This repository may appear under paths such as **`teddis-platform`** depending on checkout—the application behaviour and documentation above refer to the **Teddies** collector product implemented here.