support.eth curator

Architecture

System design, capital flows, security model, 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──▶ Indexer (Ponder) ◀──queries──▶ SDK ──▶ Web │
└───────────────────────────────────────────────────────────────────────┘

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
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).


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() — skims surplus above principal, sends to Strategy, triggers distribute()
  4. Capital provider can withdraw principal at any time

Fund-of-Funds

When a strategy allocates to another strategy, distribution credits the child strategy's warehouse balance. The child strategy then needs a separate distribute() call to route funds to its own recipients. This enables hierarchical curation with independent distribution at each level.


Security Model

Trust Assumptions

ActorTrust LevelCapabilities
Strategy OwnerTrusted for allocations onlyCan rebalance. Cannot withdraw, pause, or upgrade.
DonorsTrustlessSend funds directly. No approval needed.
RecipientsTrustlessPull from warehouse. Isolated failures.
ProtocolTrustlessNo admin keys. No upgrade authority.

Non-Custodial Properties

The Strategy contract has no withdrawal function. Funds can only leave via distribute(), which routes to configured recipients through the warehouse. The owner can change where funds go (rebalance) but can never extract funds.

Distribution is permissionless — anyone can call distribute(). This prevents funds from being locked by an inactive owner.

Recipients self-custody — only the balance owner (or delegate) can call warehouse.withdraw(). No one can redirect, freeze, or force withdrawals.

Failure Isolation

  • Each strategy is independent — no shared state except warehouse
  • Batch deposit to warehouse means reverting recipients don't block others
  • Factory is stateless beyond the implementation address — no admin, no pause

Security Features

  • ReentrancyGuard on all state-changing functions (distribute, harvest, deposit, withdraw)
  • SafeERC20 for all token operations
  • Checks-Effects-Interactions pattern throughout
  • Custom errors for gas efficiency
  • _disableInitializers() on implementation contracts

Known Limitations (POC)

LimitationRiskProduction Solution
No rebalance timelockOwner can front-run large donations24-48h timelock on rebalance()
No minimum distributionGas griefing via tiny distributionsMIN_DISTRIBUTION threshold
No emergency pauseCannot halt on vulnerability discoveryPausable with multisig + timelock
No harvest cooldownGas griefing on yield redirectorCooldown period between harvests
Fee-on-transfer tokensBalance calculations incorrectToken allowlist or explicit warning
Rebasing tokensShare accounting breaksNot supported

Invariants

Strategy: totalWeight == sum(allocations[i].weight), allocations.length <= 50, all recipients non-zero, all weights > 0.

YieldRedirector: principal <= sourceVault value, totalSupply() == principal (1:1 shares), surplus() >= 0.


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