support.eth curator

Architecture

System design, capital flows, and technical decisions

Architecture

System Overview

┌───────────────────────────────────────────────────────────────────────┐
│                         ON-CHAIN                                      │
│                                                                       │
│  Curator ──▶ StrategyFactory ──▶ Strategy (clone) ──▶ Warehouse    │
│                                      ▲                    │           │
│              Donors ─── fund ────────┘              withdraw          │
│                                                           ▼           │
│              YieldRedirector ──▶ Source Vault        Recipients      │
│                    │                                                  │
│               harvest() ──▶ Strategy ──▶ Warehouse                  │
│                                                                       │
├───────────────────────────────────────────────────────────────────────┤
│                        OFF-CHAIN                                      │
│                                                                       │
│  Contracts ──events──▶ Ponder ──▶ GraphQL ──▶ SDK ──▶ Web App     │
│                           │                              │   │        │
│                     ┌─────┘                              │   │        │
│                     ▼                                    │   │        │
│                Hono REST API ◀──────────────────────────┘   │        │
│            /api/drafts,comments,                             │        │
│            reactions,notifications                           │        │
│                     │                                        │        │
│                     ▼                                        │        │
│              Drizzle (offchain)                 BetterAuth ◀┘        │
│              ┌──────────────┐                   (SIWE → JWT)          │
│              │ drafts       │                        │                │
│              │ comments     │   ◀── JWT verify ──── JWKS             │
│              │ reactions    │       (middleware)                      │
│              │ notifications│                                         │
│              └──────────────┘                                         │
│                     │                                                 │
│              Same Postgres DB                                         │
│              (offchain schema + Ponder schema)                        │
└───────────────────────────────────────────────────────────────────────┘

On-Chain Layer

ContractPurposePattern
StrategyFactoryDeploys Strategy clones — one per tenantFactory + EIP-1167 clones
StrategyHolds allocations, distributes fundsMinimal proxy clone
SplitsWarehouseHolds recipient balancesExternal singleton (0xSplits)
YieldRedirectorFactoryDeploys YieldRedirector clonesFactory + EIP-1167 clones
YieldRedirector4626Wraps ERC-4626 vaults, redirects yieldMinimal proxy clone
ForeverSubnameRegistrarManages ENS subdomains for a tenant's namespaceExternal singleton per tenant

Off-Chain Layer

ComponentPurpose
Indexer (Ponder)Watches all tenant factory addresses on Base Sepolia; tags each strategy with its tenantId
REST API (Hono)Social endpoints (/api/drafts, /api/comments, etc.) running in the same indexer process
Auth (BetterAuth + SIWE)Web app issues JWTs via SIWE; indexer verifies via JWKS
SDK (@curator-studio/sdk)Tenant-scoped TypeScript client; resolves factory address + ENS domain from tenant registry
Web App (@curator-studio/web)Next.js frontend; tenant configured via NEXT_PUBLIC_TENANT env var

Multi-Tenancy

Each tenant (e.g., support.eth, unicef.eth) deploys its own StrategyFactory and owns its ENS subdomain namespace. The tenant registry in the SDK maps tenant IDs to per-chain configuration:

tenants["support.eth"][sepolia.id] = {
  ensDomain: "support.eth",
  factory:   "0x...",
}

The indexer watches all registered factory addresses simultaneously and records tenantId on every indexed strategy. The SDK and web app are each scoped to a single tenant at runtime, set via the tenant prop on CuratorProvider (or NEXT_PUBLIC_TENANT).

Off-Chain Social Layer

The indexer process hosts two stacks in one server: Ponder (on-chain indexing → GraphQL) and Hono (off-chain social → REST API). Both share the same Postgres database but use separate schemas.

Database. Drizzle ORM manages the offchain PostgreSQL schema (pgSchema("offchain")), keeping social tables isolated from Ponder's on-chain tables while sharing one Postgres instance. Tables:

TablePurpose
draftOff-chain strategy proposals with allocations, visibility, fork lineage
versionImmutable snapshots of title, description, and allocations — created on every draft edit or merge accept
commentThreaded comments on strategies and drafts, with optional allocation diffs
reactionUpvote/flag reactions on comments, drafts, or strategies
notificationActivity feed entries (comments, reactions, merge proposals)

REST API. Hono routes mounted on the indexer at /api/*. Read endpoints are public; write endpoints require a valid JWT.

Auth Middleware. The web app runs BetterAuth which handles SIWE login and issues JWTs. The indexer verifies tokens via the JWKS endpoint BetterAuth exposes — no shared secret, just public-key verification. Write routes pass through a requireAuth middleware that extracts the caller's address from the verified token.

API Surface

The indexer exposes two interfaces from a single process:

EndpointMethodAuthDescription
/graphqlGET / POSTNoOn-chain data (strategies, distributions) via Ponder
/api/draftsGET / POST / PUT / DELETEWrite: YesDraft CRUD, publish, fork
/api/commentsGET / POST / DELETEWrite: YesThreaded comments on strategies and drafts
/api/reactionsPOST / DELETEYesEmoji reactions on comments
/api/notificationsGET / POSTYesActivity feed, mark-read

Capital Flows

Direct Funding

Donor ──(transfer)──▶ Strategy ──(distribute)──▶ Warehouse ──(withdraw)──▶ Recipients
  1. Donor sends ERC-20 or ETH to the strategy address (or ENS name)
  2. Anyone calls distribute(token) — calculates shares by weight, batch-deposits to warehouse
  3. Recipients call withdraw(owner, token) when ready

Yield Funding

Capital Provider ──(deposit)──▶ YieldRedirector ──▶ Source Vault

                                                    generates yield

                                    harvest() ◀──────────┘


                                  Strategy ──(distribute)──▶ Warehouse ──▶ Recipients
  1. Capital provider deposits into YieldRedirector
  2. Assets route to source vault (Morpho, Euler, etc.) which generates yield
  3. Anyone calls harvest() — withdraws harvestable() assets from the source vault, sends them to the recipient Strategy, and calls distribute(asset, amount) on it (subject to minHarvestAmount / minHarvestInterval)
  4. Capital provider can withdraw principal at any time

Fund-of-Funds

When a strategy allocates to another strategy from the same factory, distribution transfers the child's share to the child strategy's contract balance (native ETH or ERC-20). A separate permissionless distribute() on the child routes those funds to its own recipients (warehouse and/or further children). This enables hierarchical curation with independent distribution at each level.


Security Model

The Strategy contract has no withdrawal function — funds can only leave via distribute() routing to the warehouse or to same-factory child strategies by direct transfer. Distribution is permissionless. Warehouse recipients self-custody via the warehouse. Off-chain social data is non-financial — on-chain state is the source of truth.

For the full trust model, security features, known limitations, and invariants, see Security & Roadmap.


Technical Decisions

Weights Over Percentages

Weights allow adding recipients without recalculating existing allocations. No need to sum to 100. Any precision supported.

Pull-Based Warehouse (0xSplits)

One batch deposit instead of N transfers. Reverting recipients don't block distribution. Recipients accumulate from multiple strategies and claim when convenient. Battle-tested code from the Splits ecosystem.

Fees as Allocations

Curator fees are regular allocations — no special fee handling code. Fully transparent, any fee structure, same code path for fees and recipients.

ERC-4626 for Yield Redirector

Standard vault interface for composability. totalAssets() overridden to return principal only, keeping share price stable at 1:1 so yield is cleanly separable.

EIP-1167 Minimal Proxies

~100k gas per strategy deployment instead of ~500k. All clones share the implementation contract. Trade-off: slightly higher per-call gas from DELEGATECALL, cannot upgrade individual clones.

ENS Subdomains

Free, permanent subdomains under the tenant's ENS namespace (e.g., strategy.support.eth) instead of requiring users to buy their own ENS names. Consistent namespace for discovery within each tenant. Both forward resolution (name → address) and reverse resolution (address → name) are set up during registration.

Because the namespace is per-tenant, each StrategyFactory is wired to its own ForeverSubnameRegistrar, and the SDK resolves the correct ENS domain from the active tenant's config.

On this page