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
| 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.
| 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 |
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
# ββββββ .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_IDRULE: ZERO hardcoded values in frontend code. All config values MUST come from
constants/config.tswhich reads fromprocess.env. If a value doesn't exist yet, add a placeholder in.env.exampleand use the mock.
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 & endpointsindex.tsβ Barrel export
Components NEVER call services directly. The data flow is:
Component β Hook β Service β Blockchain/API
β
Store (Zustand)
Owner: Dev 3 Purpose: Singleton SUI client connected to testnet. Foundation for every other service. Package:
@mysten/suiDocs: 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 instantiateSuiClientelsewhere - All config from env vars
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/zkloginDocs: 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:
- User clicks "Login with Google"
- Generate ephemeral keypair + nonce β redirect to Google OAuth
- Google redirects back to
/callbackwith JWT in URL hash - Send JWT to Enoki β get ZK proof + salt
- Derive SUI address from JWT claims
- Store session in memory β populate
auth.store.ts
Must expose:
getOAuthUrl(provider)β redirect URL stringhandleCallback(jwt)βZkLoginSessionderiveAddress(jwt, salt)β SUI address string
Integrates with: Enoki service (proof generation), auth.store.ts
Owner: Dev 3 Purpose: (1) Generate ZkLogin proofs, (2) Sponsor ALL user transactions β zero gas fees. Package:
@mysten/enokiDocs: 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 + saltsponsorAndExecute(tx, senderAddress, signFn)β transaction result
sponsorAndExecute(). Never call client.signAndExecuteTransaction() directly. The useSponsoredTx hook wraps this.
Owner: Dev 3 Purpose: Store all content on Walrus. Returns a
blobIdstored 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)βUint8ArraygetBlobUrl(blobId)β direct URL string
Flow: Encrypt with Seal β upload to Walrus β store blobId on-chain
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
Owner: Dev 3 Purpose: Display human-readable names (
alice.sui) instead of raw addresses. Package:@mysten/suinsDocs: 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 | nullgetAddressForName(name)βstring | nullbatchResolve(addresses[])βMap<address, name | null>
Frontend: The useSuiName(address) hook calls this and caches results. The <SuiAddress> component uses this hook.
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_IDAll 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;
}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;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 |
components/ui/Badge/
βββ Badge.tsx
βββ index.ts
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
- Every component folder has an
index.tsbarrel export - Types in
.types.ts, never inline - No business logic in components β delegate to hooks
- No hardcoded strings β use
constants/ - No direct service calls β always go through hooks
- All mock data via constants β components never generate fake data
- HeroUI components first β only custom when HeroUI lacks it
- Tailwind only β no inline styles, no CSS modules
- Each complex component gets its own folder β with sub-files
- Props always typed in
.types.tsβ exported and reusable
// β
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';ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β
ββββββββββββββββββββββββββ
- Node.js 18+
- pnpm (
npm install -g pnpm) - Rust + SUI CLI (
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch testnet sui)
# 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- TypeScript strict mode β
"strict": true - No
anyβ useunknown+ narrowing, or define types - No hardcoded values β
constants/or.env - Barrel exports everywhere β
index.tsin every folder - Absolute imports β
@/alias - Prettier + ESLint β enforced
- Commits β
feat:,fix:,refactor:,docs:,chore:
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
}
}- 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
- 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)
- 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)
- 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)
- Feed (explore + personalized)
- Responsive design
- Error handling + loading states
- Creator analytics dashboard
- E2E testing
| 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.