Skip to content

Latest commit

Β 

History

History
1167 lines (1018 loc) Β· 43.1 KB

File metadata and controls

1167 lines (1018 loc) Β· 43.1 KB

CLAUDE.md β€” DePatreon (Decentralized Patreon on SUI)

πŸ“Œ Project Overview

DePatreon is a fully decentralized creator platform inspired by Patreon, built on the SUI blockchain. Creators publish content behind subscription tiers; subscribers unlock content via on-chain access control. All data is stored on-chain via Walrus, encrypted via Seal, and identities are resolved through SuiNS. The UX is Web2-grade thanks to ZkLogin (social login) and Enoki (sponsored transactions β€” zero gas fees for users).

Network: SUI Testnet


πŸ‘₯ Team & Ownership (4 devs)

Role Scope
Dev 1 β€” Smart Contracts (Move) All .move modules, on-chain logic, object model, Seal policies
Dev 2 β€” Frontend Core Next.js app shell, routing, layouts, UI components, HeroUI integration
Dev 3 β€” Blockchain Integration SUI SDK, ZkLogin, Enoki, Walrus, Seal, SuiNS β€” all services/ layer
Dev 4 β€” Features & Polish Creator dashboard, subscriber flows, content rendering, responsive, tests

Each dev MUST read and respect the modularity rules below. No cross-boundary code without discussion.


πŸ—οΈ Tech Stack

Layer Technology Version / Notes
Blockchain SUI (Move lang) Testnet β€” sui testnet
Frontend Framework Next.js 14+ (App Router) TypeScript strict mode
Styling Tailwind CSS 3+ Custom theme tokens
UI Library HeroUI npx heroui-cli@latest init -t app
State Management Zustand Lightweight, modular stores
Blockchain SDK @mysten/sui Official SUI TypeScript SDK
Auth ZkLogin (via Enoki) Google, Twitch, Facebook, Apple
Gas Sponsoring Enoki (Sponsored Txs) Users never pay gas
Storage Walrus Decentralized blob storage
Encryption Seal On-chain access-control encryption
Name Resolution SuiNS Address β†’ human-readable name
Package Manager pnpm Mandatory for all devs

πŸ“ Project Structure

depatreon/
β”œβ”€β”€ CLAUDE.md                          # ← THIS FILE β€” project bible
β”œβ”€β”€ README.md
β”‚
β”œβ”€β”€ contracts/                         # ══════ MOVE SMART CONTRACTS ══════
β”‚   β”œβ”€β”€ sources/
β”‚   β”‚   β”œβ”€β”€ creator.move               # TODO MOVE β€” Creator profile object
β”‚   β”‚   β”œβ”€β”€ subscription.move          # TODO MOVE β€” Subscription tiers & logic
β”‚   β”‚   β”œβ”€β”€ content.move               # TODO MOVE β€” Content metadata (Walrus blob IDs)
β”‚   β”‚   β”œβ”€β”€ access_policy.move         # TODO MOVE β€” Seal access policies
β”‚   β”‚   └── payment.move               # TODO MOVE β€” SUI payment & revenue split
β”‚   β”œβ”€β”€ tests/
β”‚   β”‚   β”œβ”€β”€ creator_tests.move         # TODO MOVE
β”‚   β”‚   β”œβ”€β”€ subscription_tests.move    # TODO MOVE
β”‚   β”‚   └── content_tests.move         # TODO MOVE
β”‚   β”œβ”€β”€ Move.toml
β”‚   └── README.md                      # Contract-specific docs
β”‚
β”œβ”€β”€ frontend/                          # ══════ NEXT.JS APPLICATION ══════
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”œβ”€β”€ images/
β”‚   β”‚   └── fonts/
β”‚   β”‚
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ app/                       # ── Next.js App Router ──
β”‚   β”‚   β”‚   β”œβ”€β”€ layout.tsx             # Root layout (providers wrap here)
β”‚   β”‚   β”‚   β”œβ”€β”€ page.tsx               # Landing / Home
β”‚   β”‚   β”‚   β”œβ”€β”€ (auth)/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login/
β”‚   β”‚   β”‚   β”‚   β”‚   └── page.tsx
β”‚   β”‚   β”‚   β”‚   └── callback/
β”‚   β”‚   β”‚   β”‚       └── page.tsx       # ZkLogin OAuth callback
β”‚   β”‚   β”‚   β”œβ”€β”€ (app)/                 # Authenticated layout group
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ layout.tsx         # App shell (sidebar, topbar)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ feed/
β”‚   β”‚   β”‚   β”‚   β”‚   └── page.tsx
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ explore/
β”‚   β”‚   β”‚   β”‚   β”‚   └── page.tsx
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ creator/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ [address]/
β”‚   β”‚   β”‚   β”‚   β”‚   β”‚   └── page.tsx   # Public creator profile
β”‚   β”‚   β”‚   β”‚   β”‚   └── dashboard/
β”‚   β”‚   β”‚   β”‚   β”‚       └── page.tsx   # Creator's own dashboard
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ content/
β”‚   β”‚   β”‚   β”‚   β”‚   └── [id]/
β”‚   β”‚   β”‚   β”‚   β”‚       └── page.tsx   # Single content view
β”‚   β”‚   β”‚   β”‚   └── settings/
β”‚   β”‚   β”‚   β”‚       └── page.tsx
β”‚   β”‚   β”‚   └── not-found.tsx
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ components/                # ── UI Components ──
β”‚   β”‚   β”‚   β”œβ”€β”€ ui/                    # Atomic / generic
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Button/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Button.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Button.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Card/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Card.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Card.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Modal/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Modal.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Modal.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Avatar/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Avatar.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Avatar.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Badge/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Skeleton/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   └── index.ts           # Barrel export all ui components
β”‚   β”‚   β”‚   β”‚
β”‚   β”‚   β”‚   β”œβ”€β”€ layout/                # Layout components
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Sidebar/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Sidebar.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ SidebarItem.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Sidebar.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Topbar/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Topbar.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Topbar.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Footer/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   └── PageContainer/
β”‚   β”‚   β”‚   β”‚       └── ...
β”‚   β”‚   β”‚   β”‚
β”‚   β”‚   β”‚   β”œβ”€β”€ creator/               # Creator-specific components
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorCard/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorCard.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorCard.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorHeader/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorHeader.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorHeader.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ CreatorStats/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ TierCard/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ TierCard.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ TierCard.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   └── TierList/
β”‚   β”‚   β”‚   β”‚       └── ...
β”‚   β”‚   β”‚   β”‚
β”‚   β”‚   β”‚   β”œβ”€β”€ content/               # Content-specific components
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentCard/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentCard.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentCard.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentFeed/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentFeed.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentFeedItem.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentFeed.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentViewer/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentViewer.tsx      # Renders decrypted content
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ContentViewer.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ LockedContent/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ LockedContent.tsx      # Paywall overlay
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   └── ContentUploadForm/
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ ContentUploadForm.tsx
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ ContentUploadForm.types.ts
β”‚   β”‚   β”‚   β”‚       └── index.ts
β”‚   β”‚   β”‚   β”‚
β”‚   β”‚   β”‚   β”œβ”€β”€ subscription/          # Subscription components
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ SubscribeButton/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ SubscriptionBadge/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   └── SubscriptionManager/
β”‚   β”‚   β”‚   β”‚       └── ...
β”‚   β”‚   β”‚   β”‚
β”‚   β”‚   β”‚   β”œβ”€β”€ wallet/                # Wallet & auth components
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ConnectWallet/
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ConnectWallet.tsx
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ ConnectWallet.types.ts
β”‚   β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ WalletInfo/
β”‚   β”‚   β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”‚   β”‚   └── ZkLoginButton/
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ ZkLoginButton.tsx
β”‚   β”‚   β”‚   β”‚       └── index.ts
β”‚   β”‚   β”‚   β”‚
β”‚   β”‚   β”‚   └── common/                # Shared utility components
β”‚   β”‚   β”‚       β”œβ”€β”€ SuiAddress/
β”‚   β”‚   β”‚       β”‚   β”œβ”€β”€ SuiAddress.tsx         # Displays address or SuiNS name
β”‚   β”‚   β”‚       β”‚   └── index.ts
β”‚   β”‚   β”‚       β”œβ”€β”€ SuiAmount/
β”‚   β”‚   β”‚       β”‚   └── ...
β”‚   β”‚   β”‚       β”œβ”€β”€ LoadingState/
β”‚   β”‚   β”‚       β”‚   └── ...
β”‚   β”‚   β”‚       β”œβ”€β”€ ErrorBoundary/
β”‚   β”‚   β”‚       β”‚   └── ...
β”‚   β”‚   β”‚       └── EmptyState/
β”‚   β”‚   β”‚           └── ...
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ services/                  # ══ BLOCKCHAIN SERVICE LAYER ══
β”‚   β”‚   β”‚   β”‚                          # ⚠️  Chaque service = TODO pour Dev 3
β”‚   β”‚   β”‚   β”‚                          #     Voir section "Services β€” TODO"
β”‚   β”‚   β”‚   β”œβ”€β”€ sui/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ client.ts          # TODO SUI
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ constants.ts       # TODO SUI
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ zklogin/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ zklogin.service.ts # TODO ZKLOGIN
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ zklogin.types.ts   # TODO ZKLOGIN
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ zklogin.constants.ts
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ enoki/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ enoki.service.ts   # TODO ENOKI
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ enoki.types.ts     # TODO ENOKI
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ enoki.constants.ts
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ walrus/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ walrus.service.ts  # TODO WALRUS
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ walrus.types.ts    # TODO WALRUS
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ walrus.constants.ts
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ seal/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ seal.service.ts    # TODO SEAL
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ seal.types.ts      # TODO SEAL
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ seal.constants.ts
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ suins/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ suins.service.ts   # TODO SUINS
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ suins.types.ts     # TODO SUINS
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ suins.constants.ts
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   └── index.ts               # Barrel exports
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ hooks/                     # ── Custom React Hooks ──
β”‚   β”‚   β”‚   β”œβ”€β”€ useAuth.ts             # ZkLogin auth state
β”‚   β”‚   β”‚   β”œβ”€β”€ useWallet.ts           # Wallet connection
β”‚   β”‚   β”‚   β”œβ”€β”€ useSuiName.ts          # SuiNS resolution
β”‚   β”‚   β”‚   β”œβ”€β”€ useSubscription.ts     # Check/manage subscriptions
β”‚   β”‚   β”‚   β”œβ”€β”€ useContent.ts          # Fetch & decrypt content
β”‚   β”‚   β”‚   β”œβ”€β”€ useCreator.ts          # Creator profile data
β”‚   β”‚   β”‚   β”œβ”€β”€ useSponsoredTx.ts      # Enoki sponsored transaction
β”‚   β”‚   β”‚   β”œβ”€β”€ useWalrusUpload.ts     # Upload to Walrus
β”‚   β”‚   β”‚   β”œβ”€β”€ useWalrusDownload.ts   # Download from Walrus
β”‚   β”‚   β”‚   β”œβ”€β”€ useSealEncrypt.ts      # Seal encrypt
β”‚   β”‚   β”‚   β”œβ”€β”€ useSealDecrypt.ts      # Seal decrypt
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ stores/                    # ── Zustand Stores ──
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.store.ts          # Auth state (jwt, address, zkProof)
β”‚   β”‚   β”‚   β”œβ”€β”€ creator.store.ts       # Current creator data
β”‚   β”‚   β”‚   β”œβ”€β”€ subscription.store.ts  # User's active subscriptions
β”‚   β”‚   β”‚   β”œβ”€β”€ ui.store.ts            # UI state (modals, sidebar, theme)
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ providers/                 # ── Context Providers ──
β”‚   β”‚   β”‚   β”œβ”€β”€ SuiProvider.tsx        # SUI client + wallet provider
β”‚   β”‚   β”‚   β”œβ”€β”€ AuthProvider.tsx       # ZkLogin + Enoki auth provider
β”‚   β”‚   β”‚   β”œβ”€β”€ ThemeProvider.tsx       # HeroUI theme
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ constants/                 # ══ MOCK DATA & APP CONSTANTS ══
β”‚   β”‚   β”‚   β”œβ”€β”€ creators.mock.ts       # Mock creator profiles
β”‚   β”‚   β”‚   β”œβ”€β”€ content.mock.ts        # Mock content items
β”‚   β”‚   β”‚   β”œβ”€β”€ tiers.mock.ts          # Mock subscription tiers
β”‚   β”‚   β”‚   β”œβ”€β”€ subscriptions.mock.ts  # Mock user subscriptions
β”‚   β”‚   β”‚   β”œβ”€β”€ feed.mock.ts           # Mock feed data
β”‚   β”‚   β”‚   β”œβ”€β”€ navigation.ts          # Sidebar & nav items
β”‚   β”‚   β”‚   β”œβ”€β”€ routes.ts             # Route path constants
β”‚   β”‚   β”‚   β”œβ”€β”€ config.ts              # App-wide config (non-env)
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ types/                     # ══ SHARED TYPES ══
β”‚   β”‚   β”‚   β”œβ”€β”€ creator.types.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ content.types.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ subscription.types.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ tier.types.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ user.types.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ wallet.types.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ api.types.ts
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ lib/                       # ── Utility functions ──
β”‚   β”‚   β”‚   β”œβ”€β”€ format.ts              # Address truncation, amounts, dates
β”‚   β”‚   β”‚   β”œβ”€β”€ validation.ts          # Form validation schemas (zod)
β”‚   β”‚   β”‚   β”œβ”€β”€ cn.ts                  # Tailwind class merge utility
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚
β”‚   β”‚   └── styles/
β”‚   β”‚       └── globals.css
β”‚   β”‚
β”‚   β”œβ”€β”€ .env.local
β”‚   β”œβ”€β”€ .env.example
β”‚   β”œβ”€β”€ next.config.ts
β”‚   β”œβ”€β”€ tailwind.config.ts
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   β”œβ”€β”€ package.json
β”‚   └── README.md
β”‚
β”œβ”€β”€ docs/                              # ══════ DOCUMENTATION ══════
β”‚   β”œβ”€β”€ architecture.md
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ ZKLOGIN.md
β”‚   β”‚   β”œβ”€β”€ ENOKI.md
β”‚   β”‚   β”œβ”€β”€ WALRUS.md
β”‚   β”‚   β”œβ”€β”€ SEAL.md
β”‚   β”‚   └── SUINS.md
β”‚   β”œβ”€β”€ contracts/
β”‚   β”‚   └── MOVE_CONTRACTS.md
β”‚   └── setup.md
β”‚
β”œβ”€β”€ .gitignore
β”œβ”€β”€ .prettierrc
β”œβ”€β”€ .eslintrc.json
└── pnpm-workspace.yaml

βš™οΈ Environment Variables

# ══════ .env.example ══════

# ── SUI Network ──
NEXT_PUBLIC_SUI_NETWORK=testnet
NEXT_PUBLIC_SUI_RPC_URL=https://fullnode.testnet.sui.io:443

# ── Move Package (deployed on testnet) ──
NEXT_PUBLIC_PACKAGE_ID=0x_YOUR_PACKAGE_ID_HERE
NEXT_PUBLIC_CREATOR_MODULE=creator
NEXT_PUBLIC_SUBSCRIPTION_MODULE=subscription
NEXT_PUBLIC_CONTENT_MODULE=content

# ── ZkLogin / Enoki ──
NEXT_PUBLIC_ENOKI_API_KEY=your_enoki_api_key
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_google_client_id
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/callback

# ── Walrus ──
NEXT_PUBLIC_WALRUS_AGGREGATOR_URL=https://aggregator.walrus-testnet.walrus.space
NEXT_PUBLIC_WALRUS_PUBLISHER_URL=https://publisher.walrus-testnet.walrus.space

# ── Seal ──
NEXT_PUBLIC_SEAL_PACKAGE_ID=0x_SEAL_PACKAGE_ID
NEXT_PUBLIC_SEAL_ALLOWLIST_ID=0x_SEAL_ALLOWLIST_OBJECT_ID

# ── SuiNS ──
NEXT_PUBLIC_SUINS_PACKAGE_ID=0x_SUINS_PACKAGE_ID

RULE: ZERO hardcoded values in frontend code. All config values MUST come from constants/config.ts which reads from process.env. If a value doesn't exist yet, add a placeholder in .env.example and use the mock.


πŸ”— Services β€” Architecture & TODO Map

Architecture Principle

Every service is fully isolated in services/<name>/. Each service folder contains:

  • <name>.service.ts β€” The service class/functions (all logic)
  • <name>.types.ts β€” TypeScript interfaces for that service
  • <name>.constants.ts β€” Service-specific config & endpoints
  • index.ts β€” Barrel export

Components NEVER call services directly. The data flow is:

Component β†’ Hook β†’ Service β†’ Blockchain/API
              ↓
            Store (Zustand)

πŸ”Ή TODO SUI β€” SUI Client (services/sui/)

Owner: Dev 3 Purpose: Singleton SUI client connected to testnet. Foundation for every other service. Package: @mysten/sui Docs: https://sdk.mystenlabs.com/typescript

Files to create:

File Description
constants.ts Network config, RPC URL, package IDs β€” read from process.env
client.ts Singleton SuiClient instance via getSuiClient()
index.ts Barrel export

Requirements:

  • Singleton pattern β€” one client instance for the entire app
  • All RPC calls must go through getSuiClient(), never instantiate SuiClient elsewhere
  • All config from env vars

πŸ”Ή TODO ZKLOGIN β€” Social Login (services/zklogin/)

Owner: Dev 3 Purpose: Allow users to log in with Google (or other OAuth) and derive a SUI address from their JWT. Packages: @mysten/sui, @mysten/zklogin Docs: https://docs.sui.io/concepts/cryptography/zklogin

Files to create:

File Description
zklogin.service.ts OAuth URL generation, callback handling, address derivation
zklogin.types.ts ZkLoginSession, OAuthProvider, ZkLoginProof
zklogin.constants.ts Provider configs (clientId, authUrl, scope, redirectUri)
index.ts Barrel export

Expected flow:

  1. User clicks "Login with Google"
  2. Generate ephemeral keypair + nonce β†’ redirect to Google OAuth
  3. Google redirects back to /callback with JWT in URL hash
  4. Send JWT to Enoki β†’ get ZK proof + salt
  5. Derive SUI address from JWT claims
  6. Store session in memory β†’ populate auth.store.ts

Must expose:

  • getOAuthUrl(provider) β†’ redirect URL string
  • handleCallback(jwt) β†’ ZkLoginSession
  • deriveAddress(jwt, salt) β†’ SUI address string

Integrates with: Enoki service (proof generation), auth.store.ts


πŸ”Ή TODO ENOKI β€” Sponsored Transactions (services/enoki/)

Owner: Dev 3 Purpose: (1) Generate ZkLogin proofs, (2) Sponsor ALL user transactions β€” zero gas fees. Package: @mysten/enoki Docs: https://docs.enoki.mystenlabs.com

Files to create:

File Description
enoki.service.ts Proof generation + sponsored tx execution
enoki.types.ts SponsoredTxResult, EnokiProofResponse
enoki.constants.ts API key, base URL
index.ts Barrel export

Must expose:

  • getZkLoginProof(jwt, ephemeralPublicKey, maxEpoch, randomness) β†’ ZK proof + salt
  • sponsorAndExecute(tx, senderAddress, signFn) β†’ transaction result

⚠️ CRITICAL RULE: EVERY user transaction MUST go through sponsorAndExecute(). Never call client.signAndExecuteTransaction() directly. The useSponsoredTx hook wraps this.


πŸ”Ή TODO WALRUS β€” Decentralized Storage (services/walrus/)

Owner: Dev 3 Purpose: Store all content on Walrus. Returns a blobId stored on-chain. Package: None (pure HTTP fetch) Docs: https://docs.walrus.site

Files to create:

File Description
walrus.service.ts Upload (PUT), download (GET), URL builder
walrus.types.ts WalrusUploadResponse, WalrusStoreOptions, WalrusBlobData
walrus.constants.ts Aggregator URL (read), Publisher URL (write), default epochs
index.ts Barrel export

Must expose:

  • upload(data: Uint8Array, options?) β†’ { blobId, size, endEpoch }
  • download(blobId) β†’ Uint8Array
  • getBlobUrl(blobId) β†’ direct URL string

Flow: Encrypt with Seal β†’ upload to Walrus β†’ store blobId on-chain ⚠️ IMPORTANT: ALWAYS encrypt with Seal BEFORE uploading. Walrus stores opaque bytes.


πŸ”Ή TODO SEAL β€” Encryption & Access Control (services/seal/)

Owner: Dev 3 Purpose: Encrypt content so only authorized subscribers can decrypt. Tied to on-chain policies. Package: Seal SDK (check latest on SUI ecosystem) Docs: https://docs.seal.mystenlabs.com

Files to create:

File Description
seal.service.ts Encrypt, decrypt, create policy
seal.types.ts SealPolicy, SealEncryptResult, SealDecryptResult
seal.constants.ts Seal package ID, allowlist ID
index.ts Barrel export

Must expose:

  • encrypt(data: Uint8Array, policyObjectId) β†’ { encryptedData, policyId }
  • decrypt(encryptedData: Uint8Array, policyObjectId, signFn) β†’ { decryptedData }
  • createPolicy(...) β†’ policyId string

Architecture:

Creator creates Tier β†’ Move creates SealPolicy β†’ policyId stored in Tier
Creator uploads β†’ encrypt(data, policyId) β†’ Walrus β†’ blobId on-chain
User subscribes β†’ gets SubscriptionNFT
User requests content β†’ Seal checks NFT ownership β†’ decrypt if authorized

πŸ”Ή TODO SUINS β€” Name Resolution (services/suins/)

Owner: Dev 3 Purpose: Display human-readable names (alice.sui) instead of raw addresses. Package: @mysten/suins Docs: https://docs.suins.io

Files to create:

File Description
suins.service.ts Address→name, name→address, batch resolve
suins.types.ts SuiNameRecord
suins.constants.ts Package ID, registry object
index.ts Barrel export

Must expose:

  • getNameForAddress(address) β†’ string | null
  • getAddressForName(name) β†’ string | null
  • batchResolve(addresses[]) β†’ Map<address, name | null>

Frontend: The useSuiName(address) hook calls this and caches results. The <SuiAddress> component uses this hook.


πŸ”Ή TODO MOVE β€” Smart Contracts (contracts/)

Owner: Dev 1 Purpose: All on-chain logic: creator profiles, tiers, subscriptions, content, payments, Seal policies. Docs: https://move-book.com, https://docs.sui.io

Files to create:

File Description
sources/creator.move CreatorProfile shared object (create, update, delete)
sources/subscription.move Tier creation, subscribe (mint NFT), renew, cancel
sources/content.move Content metadata (blobId, policyId, tierId), publish, delete
sources/access_policy.move Seal policy creation, access verification
sources/payment.move SUI payment handling, revenue split
tests/* Full test coverage

Expected object model:

Object Type Key fields
CreatorProfile shared owner, name, bio, avatar_blob_id, tiers, subscriber_count
SubscriptionTier child of Creator name, price (MIST), seal_policy_id, benefits, active
ContentObject shared creator, title, blob_id, seal_policy_id, tier_id, is_public
SubscriptionNFT owned by subscriber subscriber, creator, tier_id, started_at, expires_at

Deploy:

cd contracts && sui move build && sui move test && sui client publish --gas-budget 100000000
# β†’ Copy package ID into NEXT_PUBLIC_PACKAGE_ID

πŸ“¦ Types System (types/)

All shared types live in types/. Each mock file in constants/ MUST import and satisfy the corresponding type. These types are the contract between frontend and blockchain β€” agreed upon by all devs.

// types/creator.types.ts
export interface Creator {
  address: string;
  name: string;
  bio: string;
  avatarBlobId: string | null;
  bannerBlobId: string | null;
  suinsName: string | null;
  totalSubscribers: number;
  totalContent: number;
  tiers: Tier[];
  createdAt: number;
}
// types/tier.types.ts
export interface Tier {
  id: string;
  creatorAddress: string;
  name: string;
  description: string;
  priceInMist: number;       // 1 SUI = 1_000_000_000 MIST
  sealPolicyId: string;
  benefits: string[];
  subscriberCount: number;
  order: number;
}
// types/content.types.ts
export type ContentType = 'text' | 'image' | 'video' | 'audio' | 'file';

export interface Content {
  id: string;
  creatorAddress: string;
  title: string;
  description: string;
  contentType: ContentType;
  walrusBlobId: string;
  sealPolicyId: string;
  requiredTierId: string;
  isPublic: boolean;
  previewBlobId: string | null;
  createdAt: number;
  likesCount: number;
  commentsCount: number;
}
// types/subscription.types.ts
export interface Subscription {
  id: string;
  subscriberAddress: string;
  creatorAddress: string;
  tierId: string;
  startedAt: number;
  expiresAt: number;
  isActive: boolean;
  autoRenew: boolean;
}
// types/user.types.ts
export interface User {
  address: string;
  suinsName: string | null;
  avatarUrl: string | null;
  isCreator: boolean;
  subscriptions: Subscription[];
}
// types/wallet.types.ts
export type WalletStatus = 'disconnected' | 'connecting' | 'connected' | 'error';

export interface WalletState {
  status: WalletStatus;
  address: string | null;
  suinsName: string | null;
}
// types/api.types.ts
export interface ApiResponse<T> {
  data: T | null;
  error: string | null;
  isLoading: boolean;
}

πŸ§ͺ Mock Data (constants/)

Rule: Every mock file imports its type and satisfies it fully. When a service is integrated, the hook switches from mock β†’ real data. Components never change.

// constants/creators.mock.ts
import type { Creator } from '@/types';

export const MOCK_CREATORS: Creator[] = [
  {
    address: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    name: 'Alice Art',
    bio: 'Digital artist exploring the intersection of AI and traditional art.',
    avatarBlobId: null,
    bannerBlobId: null,
    suinsName: 'alice.sui',
    totalSubscribers: 142,
    totalContent: 38,
    tiers: [],
    createdAt: 1700000000,
  },
  {
    address: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd',
    name: 'Bob Music',
    bio: 'Independent musician sharing exclusive tracks and behind-the-scenes content.',
    avatarBlobId: null,
    bannerBlobId: null,
    suinsName: 'bob.sui',
    totalSubscribers: 89,
    totalContent: 24,
    tiers: [],
    createdAt: 1701000000,
  },
  {
    address: '0x9999999999999999999999999999999999999999999999999999999999999999',
    name: 'Charlie Dev',
    bio: 'Building the future of Web3. Tutorials, code walkthroughs, and hot takes.',
    avatarBlobId: null,
    bannerBlobId: null,
    suinsName: 'charlie.sui',
    totalSubscribers: 312,
    totalContent: 67,
    tiers: [],
    createdAt: 1698000000,
  },
];
// constants/tiers.mock.ts
import type { Tier } from '@/types';

export const MOCK_TIERS: Tier[] = [
  {
    id: '0xtier001',
    creatorAddress: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    name: 'Supporter',
    description: 'Access to behind-the-scenes posts and community chat.',
    priceInMist: 1_000_000_000,
    sealPolicyId: '0xpolicy001',
    benefits: ['Behind-the-scenes posts', 'Community chat access', 'Monthly Q&A'],
    subscriberCount: 98,
    order: 1,
  },
  {
    id: '0xtier002',
    creatorAddress: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    name: 'Premium',
    description: 'Everything in Supporter + exclusive tutorials and early access.',
    priceInMist: 5_000_000_000,
    sealPolicyId: '0xpolicy002',
    benefits: ['All Supporter benefits', 'Exclusive tutorials', 'Early access', 'HD downloads'],
    subscriberCount: 44,
    order: 2,
  },
  {
    id: '0xtier003',
    creatorAddress: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    name: 'VIP',
    description: 'The ultimate tier β€” 1-on-1 sessions, source files, and more.',
    priceInMist: 15_000_000_000,
    sealPolicyId: '0xpolicy003',
    benefits: ['All Premium benefits', '1-on-1 monthly session', 'Source files', 'Credits in projects'],
    subscriberCount: 12,
    order: 3,
  },
];
// constants/content.mock.ts
import type { Content } from '@/types';

export const MOCK_CONTENT: Content[] = [
  {
    id: '0xcontent001',
    creatorAddress: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    title: 'Speed painting: Neon Cityscape',
    description: 'Watch the full process of creating this cyberpunk cityscape in 4K.',
    contentType: 'video',
    walrusBlobId: 'walrus_blob_mock_001',
    sealPolicyId: '0xpolicy001',
    requiredTierId: '0xtier001',
    isPublic: false,
    previewBlobId: 'walrus_preview_mock_001',
    createdAt: 1705000000,
    likesCount: 32,
    commentsCount: 8,
  },
  {
    id: '0xcontent002',
    creatorAddress: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    title: 'Welcome to my page!',
    description: 'A free introduction post for everyone.',
    contentType: 'text',
    walrusBlobId: 'walrus_blob_mock_002',
    sealPolicyId: '',
    requiredTierId: '',
    isPublic: true,
    previewBlobId: null,
    createdAt: 1704000000,
    likesCount: 67,
    commentsCount: 15,
  },
  {
    id: '0xcontent003',
    creatorAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd',
    title: 'Unreleased Track β€” "Midnight Signal"',
    description: 'Exclusive early listen to my upcoming single.',
    contentType: 'audio',
    walrusBlobId: 'walrus_blob_mock_003',
    sealPolicyId: '0xpolicy_bob_001',
    requiredTierId: '0xtier_bob_001',
    isPublic: false,
    previewBlobId: null,
    createdAt: 1706000000,
    likesCount: 45,
    commentsCount: 12,
  },
];
// constants/subscriptions.mock.ts
import type { Subscription } from '@/types';

export const MOCK_SUBSCRIPTIONS: Subscription[] = [
  {
    id: '0xsub001',
    subscriberAddress: '0xUSER_ADDRESS_MOCK',
    creatorAddress: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    tierId: '0xtier002',
    startedAt: 1704067200,
    expiresAt: 1706745600,
    isActive: true,
    autoRenew: true,
  },
  {
    id: '0xsub002',
    subscriberAddress: '0xUSER_ADDRESS_MOCK',
    creatorAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd',
    tierId: '0xtier_bob_001',
    startedAt: 1705000000,
    expiresAt: 1707678000,
    isActive: true,
    autoRenew: false,
  },
];
// constants/feed.mock.ts
import type { Content } from '@/types';
import { MOCK_CONTENT } from './content.mock';

export const MOCK_FEED: Content[] = [...MOCK_CONTENT].sort(
  (a, b) => b.createdAt - a.createdAt
);
// constants/navigation.ts
export interface NavItem {
  label: string;
  href: string;
  icon: string;
  requiresAuth: boolean;
}

export const SIDEBAR_NAV: NavItem[] = [
  { label: 'Feed', href: '/feed', icon: 'home', requiresAuth: true },
  { label: 'Explore', href: '/explore', icon: 'compass', requiresAuth: false },
  { label: 'My Subscriptions', href: '/subscriptions', icon: 'heart', requiresAuth: true },
  { label: 'Creator Dashboard', href: '/creator/dashboard', icon: 'bar-chart', requiresAuth: true },
  { label: 'Settings', href: '/settings', icon: 'settings', requiresAuth: true },
];
// constants/routes.ts
export const ROUTES = {
  HOME: '/',
  LOGIN: '/login',
  CALLBACK: '/callback',
  FEED: '/feed',
  EXPLORE: '/explore',
  CREATOR: (address: string) => `/creator/${address}`,
  CREATOR_DASHBOARD: '/creator/dashboard',
  CONTENT: (id: string) => `/content/${id}`,
  SETTINGS: '/settings',
} as const;
// constants/config.ts
export const APP_CONFIG = {
  appName: 'DePatreon',
  appDescription: 'Decentralized creator platform on SUI',
  defaultAvatarUrl: '/images/default-avatar.png',
  defaultBannerUrl: '/images/default-banner.png',
  maxUploadSizeMb: 100,
  supportedContentTypes: [
    'image/png', 'image/jpeg', 'image/webp',
    'video/mp4', 'audio/mp3', 'application/pdf',
  ],
  suiDecimals: 9,
  mist: {
    toSui: (mist: number) => mist / 1_000_000_000,
    fromSui: (sui: number) => sui * 1_000_000_000,
  },
} as const;

πŸͺ Hooks β€” Mock β†’ Real Pattern

Each hook encapsulates one concern. While services are TODO, hooks return mock data. When a service is integrated, flip USE_MOCK to false.

// hooks/useCreator.ts β€” PATTERN TO FOLLOW FOR ALL HOOKS
import { useState, useEffect } from 'react';
import { MOCK_CREATORS } from '@/constants';
import type { Creator } from '@/types';

// TODO: When SUI service is ready, import and use:
// import { CreatorService } from '@/services/sui';

const USE_MOCK = true; // ← Flip to false when service is integrated

export function useCreator(address: string | null) {
  const [creator, setCreator] = useState<Creator | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!address) return;
    setIsLoading(true);
    setError(null);

    if (USE_MOCK) {
      const found = MOCK_CREATORS.find((c) => c.address === address) ?? null;
      setTimeout(() => {
        setCreator(found);
        setIsLoading(false);
      }, 300); // simulate async
      return;
    }

    // TODO: Real implementation
    // CreatorService.getByAddress(address)
    //   .then(setCreator)
    //   .catch((err) => setError(err.message))
    //   .finally(() => setIsLoading(false));
  }, [address]);

  return { creator, isLoading, error };
}

All hooks:

Hook Consumes (when integrated) Purpose
useAuth TODO ZKLOGIN + TODO ENOKI Auth state, login/logout, current user
useWallet TODO SUI Wallet connection status, address
useSuiName TODO SUINS Resolve address β†’ SuiNS name (cached)
useSubscription TODO SUI Check/manage user subscriptions
useContent TODO WALRUS + TODO SEAL Fetch, decrypt, return content
useCreator TODO SUI Creator profile data
useSponsoredTx TODO ENOKI Execute any tx with gas sponsoring
useWalrusUpload TODO WALRUS Upload with progress
useWalrusDownload TODO WALRUS Download blob by ID
useSealEncrypt TODO SEAL Encrypt data before upload
useSealDecrypt TODO SEAL Decrypt data after download

🧩 Component Rules

Simple components (≀ 50 lines):

components/ui/Badge/
β”œβ”€β”€ Badge.tsx
└── index.ts

Complex components (folder with split files):

components/content/ContentFeed/
β”œβ”€β”€ ContentFeed.tsx          # Main container
β”œβ”€β”€ ContentFeedItem.tsx      # Individual item
β”œβ”€β”€ ContentFeedSkeleton.tsx  # Loading skeleton
β”œβ”€β”€ ContentFeed.types.ts     # Props & internal types
β”œβ”€β”€ ContentFeed.utils.ts     # Helpers (optional)
└── index.ts                 # Barrel export

10 Rules:

  1. Every component folder has an index.ts barrel export
  2. Types in .types.ts, never inline
  3. No business logic in components β€” delegate to hooks
  4. No hardcoded strings β€” use constants/
  5. No direct service calls β€” always go through hooks
  6. All mock data via constants β€” components never generate fake data
  7. HeroUI components first β€” only custom when HeroUI lacks it
  8. Tailwind only β€” no inline styles, no CSS modules
  9. Each complex component gets its own folder β€” with sub-files
  10. Props always typed in .types.ts β€” exported and reusable

Imports:

// βœ… Good
import { ContentCard } from '@/components/content/ContentCard';
import { MOCK_CONTENT } from '@/constants';
import { useContent } from '@/hooks';
import type { Content } from '@/types';

// ❌ Bad
import { ContentCard } from '@/components/content/ContentCard/ContentCard';
import { someContent } from './hardcoded-data';

πŸ”„ Data Flow Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      FRONTEND                         β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚Component │───▢│  Hook  │───▢│     Service      β”‚  β”‚
β”‚  β”‚  (UI)   β”‚    β”‚        β”‚    β”‚   (TODO / Mock)  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜    β”‚                  β”‚  β”‚
β”‚       β”‚             β”‚         β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  β”‚
β”‚       β”‚         β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”    β”‚ β”‚ TODO SUI     β”‚ β”‚  β”‚
β”‚       β”‚         β”‚ Store  β”‚    β”‚ β”‚ TODO ZKLOGIN β”‚ β”‚  β”‚
β”‚       β”‚         β”‚(Zustand)β”‚   β”‚ β”‚ TODO ENOKI   β”‚ β”‚  β”‚
β”‚       β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚ β”‚ TODO WALRUS  β”‚ β”‚  β”‚
β”‚       β”‚                       β”‚ β”‚ TODO SEAL    β”‚ β”‚  β”‚
β”‚       β”‚                       β”‚ β”‚ TODO SUINS   β”‚ β”‚  β”‚
β”‚       β”‚                       β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚  β”‚
β”‚       β”‚                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                                 β”‚
        β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                    β”‚      SUI TESTNET        β”‚
        β”‚                    β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
        β”‚                    β”‚    β”‚ TODO MOVE       β”‚   β”‚
        β”‚                    β”‚    β”‚ contracts/      β”‚   β”‚
        β”‚                    β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
        β”‚                    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
        β”‚                    β”‚  β”‚ SuiNS  β”‚ β”‚ Seal   β”‚  β”‚
        β”‚                    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
        β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        └───────────────────▢│    WALRUS (Storage)     β”‚
                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸš€ Getting Started

Prerequisites

  • Node.js 18+
  • pnpm (npm install -g pnpm)
  • Rust + SUI CLI (cargo install --locked --git https://github.com/MystenLabs/sui.git --branch testnet sui)

Setup

# 1. Clone
git clone <repo-url> depatreon && cd depatreon

# 2. SUI testnet
sui client switch --env testnet
sui client faucet

# 3. Contracts (when TODO MOVE is done)
cd contracts
sui move build && sui move test
sui client publish --gas-budget 100000000
# β†’ Copy package ID

# 4. Frontend
cd ../frontend
pnpm install
cp .env.example .env.local
# Fill in values
pnpm dev
# β†’ http://localhost:3000

πŸ“ Code Quality Rules

  1. TypeScript strict mode β€” "strict": true
  2. No any β€” use unknown + narrowing, or define types
  3. No hardcoded values β€” constants/ or .env
  4. Barrel exports everywhere β€” index.ts in every folder
  5. Absolute imports β€” @/ alias
  6. Prettier + ESLint β€” enforced
  7. Commits β€” feat:, fix:, refactor:, docs:, chore:

tsconfig.json paths:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["src/*"] }
  }
}

πŸ” Security Notes

  • Never store private keys in frontend β€” ZkLogin uses ephemeral keys only
  • JWT tokens in memory only β€” never localStorage
  • Seal policies = access control β€” Move contract is source of truth
  • Always encrypt before Walrus upload β€” Walrus stores opaque blobs
  • Enoki rate limits β€” configure on dashboard to prevent abuse

πŸ“‹ Phases & TODO Tracker

Phase 1: Foundation

  • TODO MOVE β€” Creator, Tier, Subscription contracts (Dev 1)
  • Frontend β€” App shell, routing, HeroUI, all pages with mocks (Dev 2)
  • TODO ZKLOGIN + TODO ENOKI β€” Login flow (Dev 3)
  • Mocks β€” All screens with mock data (Dev 2 + Dev 4)

Phase 2: Core Features

  • TODO SUI β€” Client + read on-chain data (Dev 3)
  • Frontend β€” Creator profile, tiers, subscribe UI (Dev 4)
  • TODO ENOKI β€” Sponsored txs for all actions (Dev 3)
  • TODO SUINS β€” Name resolution everywhere (Dev 3)

Phase 3: Content System

  • TODO WALRUS β€” Upload/download (Dev 3)
  • TODO SEAL β€” Encrypt/decrypt (Dev 3)
  • Frontend β€” Content publish, locked/unlocked views (Dev 4)
  • TODO MOVE β€” Content + access_policy contracts (Dev 1)

Phase 4: Polish

  • Feed (explore + personalized)
  • Responsive design
  • Error handling + loading states
  • Creator analytics dashboard
  • E2E testing

πŸ“š Reference Links

Resource URL
SUI Docs https://docs.sui.io
SUI TypeScript SDK https://sdk.mystenlabs.com/typescript
Move Book https://move-book.com
ZkLogin Guide https://docs.sui.io/concepts/cryptography/zklogin
Enoki Docs https://docs.enoki.mystenlabs.com
Walrus Docs https://docs.walrus.site
Seal Docs https://docs.seal.mystenlabs.com
SuiNS Docs https://docs.suins.io
HeroUI Docs https://www.heroui.com/docs
Tailwind Docs https://tailwindcss.com/docs

This file is the single source of truth. When implementing a TODO, update this file to mark it done and document the integration.