support.eth curator

Building Custom Clients

How to build custom frontends, CLIs, bots, and agents on top of Curator Studio

Building Custom Clients

Curator Studio is designed as composable infrastructure. The contracts are permissionless, the indexer exposes a standard GraphQL/REST API, and the SDK works in any JavaScript runtime — not just React apps.

The Stack

┌─────────────────────────────────────────────────────────────────┐
│                           CLIENTS                                │
│                                                                  │
│   React apps          ──▶  CuratorProvider + hooks              │
│   Other web apps      ──▶  CuratorSDK class                    │
│   CLI tools           ──▶  CuratorSDK class                    │
│   AI agents / bots    ──▶  CuratorSDK class                    │
│   Keepers / cron      ──▶  CuratorSDK class                    │
│   Chat bots           ──▶  CuratorSDK class                    │
│   Other protocols     ──▶  contracts directly                   │
├─────────────────────────────────────────────────────────────────┤
│                     @curator-studio/sdk                           │
│                                                                  │
│   React layer    CuratorProvider, hooks (optional, needs React) │
│   Core layer     CuratorSDK class, indexer client (pure TS)     │
├─────────────────────────────────────────────────────────────────┤
│                     @curator-studio/indexer                       │
│                                                                  │
│   GraphQL          /graphql — strategies, distributions, etc.   │
│   REST             /api/strategies/trending, /api/stats          │
├─────────────────────────────────────────────────────────────────┤
│                     ON-CHAIN CONTRACTS                            │
│                                                                  │
│   StrategyFactory          Deploys strategy clones (per tenant) │
│   Strategy                 Allocations, distribute, rebalance   │
│   SplitsWarehouse          ERC-6909 vault, withdraw (shared)    │
│   YieldRedirector4626      Wraps ERC-4626, redirects yield      │
│   ForeverSubnameRegistrar  *.tenant.eth ENS subdomains          │
└─────────────────────────────────────────────────────────────────┘

What You Need

Install the SDK and provide a tenant name. Contract addresses, ABIs, and RPC are handled automatically.

npm install @curator-studio/sdk
import { CuratorSDK } from "@curator-studio/sdk";

const sdk = new CuratorSDK(walletClient, { tenant: "support.eth" });

The indexer is hosted at https://curate-fund-dev.up.railway.app (GraphQL at /graphql, REST at /api/*). Pass it explicitly if you need indexer queries:

const sdk = new CuratorSDK(walletClient, {
  tenant: "support.eth",
  indexerUrl: "https://curate-fund-dev.up.railway.app",
});

SDK Layers

The SDK has two layers — non-React clients are first-class.

Core layerCuratorSDK class. Pure TypeScript, no framework dependency. Works in Node.js, Deno, Bun, browsers, serverless functions.

React layerCuratorProvider + hooks. Wraps the core layer with React context and TanStack Query. Optional — only needed for React apps.

Client Types

React App

Wrap your app with CuratorProvider, then use hooks:

import { CuratorProvider, useStrategies } from "@curator-studio/sdk";

function App() {
  return (
    <CuratorProvider tenant="support.eth" indexerUrl="https://curate-fund-dev.up.railway.app">
      <StrategyList />
    </CuratorProvider>
  );
}

function StrategyList() {
  const { data, isPending } = useStrategies({ orderBy: "timesForked", orderDirection: "desc", limit: 10 });
  if (isPending) return <div>Loading...</div>;
  return data?.items.map((s) => <div key={s.id}>{s.metadata?.title}</div>);
}

CLI, Script, or Non-React App

The CuratorSDK class works anywhere — Node.js, Deno, Bun, Vue, Svelte, etc.:

import { CuratorSDK } from "@curator-studio/sdk";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.PRIVATE_KEY),
  chain: baseSepolia,
  transport: http(),
});

const sdk = new CuratorSDK(wallet, {
  tenant: "support.eth",
  indexerUrl: "https://curate-fund-dev.up.railway.app",
});

const { items } = await sdk.indexer.strategy.query({ limit: 5 });
await sdk.strategy.distribute(items[0].id, tokenAddress);

Keeper / Cron Job

A service that periodically distributes or harvests. Since distribute() and harvest() are permissionless, anyone can call them:

for (const strategy of strategies.items) {
  const balance = await sdk.strategy.balanceOf(strategy.id, tokenAddress);
  if (balance > threshold) {
    await sdk.strategy.distribute(strategy.id, tokenAddress);
  }
}

AI Agent or Bot

The SDK works as a tool for autonomous agents — MCP servers, LangChain tools, Eliza plugins, etc.:

const stats = await sdk.indexer.stats();
const trending = await sdk.indexer.trending({ period: "7d", limit: 5 });

await sdk.strategy.create({
  owner: wallet.account.address,
  allocations: [
    { recipient: "0x...", weight: 60n, label: "Top project" },
    { recipient: "0x...", weight: 40n, label: "Runner up" },
  ],
  metadataURI: "https://...",
  sourceStrategy: "0x0000000000000000000000000000000000000000",
});

Direct Integration (No SDK)

Query the indexer GraphQL endpoint directly and call contracts via viem or ethers. Addresses and ABIs are in @curator-studio/contracts/deployments.json.

query {
  strategys(where: { tenantId: "support.eth" }, limit: 10) {
    items { id, owner, metadataURI, totalWeight }
  }
}

Permissionless Entry Points

The contracts are designed so that most operations don't require special access. This is what makes keepers, agents, and bots viable.

FunctionAccessDescription
strategy.distribute(token)AnyoneDistribute strategy balance to recipients
yieldRedirector.harvest()AnyoneSkim yield surplus and distribute
Fund a strategy (transfer tokens)AnyoneSend ERC-20 or ETH to the strategy address
warehouse.withdraw(owner, token)Owner onlyRecipient claims their balance
strategy.rebalance(allocations)Owner onlyUpdate allocation weights
strategy.create(config)Anyone (via factory)Deploy a new strategy

Tenant Scoping

Each tenant operates under its own ENS domain with isolated factory contracts but shared underlying infrastructure.

LayerTenant-scopedShared
ContractsStrategyFactory, SubnameRegistrarStrategy impl, SplitsWarehouse, ENS infra
IndexertenantId filter on strategiesSame Ponder instance, same endpoint
SDKtenant option resolves factory + ENS domainSame CuratorSDK class
Frontendtenant prop on providerSame codebase serves any tenant

A custom client just sets a different tenant value and sees only that tenant's strategies, while sharing the same warehouse, indexer, and contract infrastructure.

On this page