@curator-studio/sdk
v0.2.1
Published
TypeScript SDK and React hooks for the Curator Studio capital allocation protocol
Maintainers
Readme
@curator-studio/sdk
TypeScript SDK and React hooks for the Curator Studio capital allocation protocol.
Install
npm install @curator-studio/sdkCore usage (Node scripts, non-React apps) only needs viem as a peer dependency.
React apps that use hooks should also install:
npm install @tanstack/react-query react wagmi viemreact, wagmi, and @tanstack/react-query are optional peers: the default @curator-studio/sdk entry is core-only (no React in the import graph). Hooks live under @curator-studio/sdk/react.
The published package bundles deployment artifacts from the monorepo’s contracts package at build time; you do not need to publish or install @curator-studio/contracts separately when consuming this SDK from npm.
Mutation hooks do not show toasts; handle success and errors in your UI (for example with React Query’s onSuccess / onError or a global handler).
Package exports
| Import path | Use case |
|-------------|----------|
| @curator-studio/sdk | CuratorSDK, indexer types, createIndexer, pure helpers (allocations, tokens, …) |
| @curator-studio/sdk/react | CuratorProvider, useCuratorSDK, and all React Query hooks |
Quick Start
Standalone (no React)
import { CuratorSDK } from "@curator-studio/sdk";
import { zeroAddress } from "viem";
const sdk = new CuratorSDK(walletClient, {
tenant: "support.eth",
chain: 84532,
indexerUrl: "https://your-indexer.example.com/graphql",
uploadMetadata: async (metadata) => {
// Upload JSON and return a public metadata URI
return "https://example.com/meta/abc.json";
},
});
const { strategy } = await sdk.strategy.create({
owner: "0xYourAddress",
sourceStrategy: zeroAddress,
ensLabel: "my-strategy",
metadata: {
title: "My strategy",
description: "Funding allocation",
},
allocations: [
{ recipient: "0xProjectA...", weight: 4000n, label: "Project A" },
{ recipient: "0xProjectB...", weight: 3500n, label: "Project B" },
{ recipient: "0xCurator...", weight: 2500n, label: "Curator Fee" },
],
});
await sdk.strategy.distribute(strategy, tokenAddress);React
import { createUploadFn } from "@curator-studio/sdk";
import {
CuratorProvider,
useStrategies,
} from "@curator-studio/sdk/react";
function App() {
return (
<CuratorProvider
tenant="support.eth"
defaultChain={84532}
indexerUrl="https://your-indexer.example.com/graphql"
uploadMetadata={createUploadFn("/api/upload/metadata", "<server-upload-secret>")}
>
<StrategiesList />
</CuratorProvider>
);
}
function StrategiesList() {
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>);
}Types: allocations
Shared naming in @curator-studio/sdk:
OnChainAllocation—weight: bigint(contracts /sdk.strategy)IndexerAllocation—weight: string(GraphQL indexer rows; also exported as deprecated aliasAllocationon indexer types)FormAllocation—weight: number(forms andallocations.tshelpers)
Helpers such as toFormAllocations, toOnChainAllocations, and validateAllocations live on the main entry.
API
CuratorSDK
const sdk = new CuratorSDK(wallet?, options?)| Option | Type | Description |
|--------|------|-------------|
| chain | SupportedChainId | Chain ID (1, 11155111, 84532, 31337) |
| tenant | string | Tenant id (e.g. "support.eth"). Used for indexer tenant filter and tenant-specific factory addresses. |
| indexerUrl | string | GraphQL endpoint for the indexer (required for createIndexer; empty string throws) |
| uploadMetadata | UploadMetadataFn | Required for strategy.create / rebalance when using those methods |
sdk.strategy
| Method | Description |
|--------|-------------|
| create(config) | Deploy a new strategy |
| getData(address) | Read on-chain strategy data |
| balanceOf(address, token) | Token balance held by a strategy |
| rebalance(address, allocations, metadata) | Update allocations (owner only) |
| distribute(address, token) | Distribute funds to recipients |
| setENSName(address, label) | Set ENS subdomain for a strategy |
sdk.warehouse
| Method | Description |
|--------|-------------|
| withdraw(owner, token) | Withdraw from SplitsWarehouse |
| balanceOf(owner, token) | Check warehouse balance |
sdk.yieldRedirector
| Method | Description |
|--------|-------------|
| create(sourceVault, yieldRecipient, owner) | Deploy a yield redirector |
| createDeterministic(sourceVault, yieldRecipient, owner, salt) | Deploy at a predictable address |
| harvest(address) | Harvest yield and distribute |
| surplus(address) | Check available yield |
| principal(address) | Check deposited principal |
| sourceVaultValue(address) | Total value in source vault |
| setYieldRecipient(address, newRecipient) | Change yield recipient |
sdk.ens
| Method | Description |
|--------|-------------|
| available(label) | Check if ENS label is available |
| register(label, owner?) | Register ENS subdomain |
| getAddress(name) | Resolve ENS name to address |
| setAddress(name, address) | Set forward resolution |
| setReverseRecord(name) | Set reverse resolution |
| registerWithAddress(label, address, owner?) | Register with forward + reverse resolution |
sdk.indexer.draft
Authenticated REST methods for managing off-chain strategy drafts. Requires authToken.
When the SDK is constructed with tenant, draft.create sends that tenant as tenantId automatically, and draft.list filters by it unless you pass tenantId explicitly.
| Method | Description |
|--------|-------------|
| create(input) | Create a new draft |
| get(id) | Get a draft by ID |
| list(params?) | List drafts (filterable by tenantId, curator, sourceStrategyId, sourceDraftId, mergeStatus, …) |
| update(id, input) | Update draft title, description, or allocations (creates new version) |
| fork(input) | Fork a strategy or draft (accepts sourceStrategyId or sourceDraftId) |
| proposeMerge(id) | Propose merging a fork into its parent |
| acceptMerge(id, input?) | Accept a merge proposal (parent owner) |
| rejectMerge(id, input?) | Reject a merge proposal (parent owner) |
| withdrawMerge(id) | Withdraw a merge proposal (fork author) |
| updateVisibility(id, visibility) | Set draft visibility (private / unlisted / public) |
| publish(id, onchainId) | Mark draft as published and link to on-chain strategy |
| delete(id) | Soft-delete a draft |
sdk.indexer.comment
| Method | Description |
|--------|-------------|
| create(input) | Post a comment on a strategy or draft (supports parentCommentId for threads) |
| list(params) | List comments for a target (targetId + targetType required) |
| update(id, body) | Edit a comment body |
| delete(id) | Soft-delete a comment |
sdk.indexer.reaction
| Method | Description |
|--------|-------------|
| toggle(input) | Toggle an upvote or flag on a comment, draft, or strategy |
| get(targetId, targetType) | Get reaction counts and the current user's reactions |
sdk.indexer.notification
| Method | Description |
|--------|-------------|
| list(params?) | List notifications for the authenticated user |
| unreadCount() | Get the number of unread notifications |
| markRead() | Mark all notifications as read |
| markOneRead(id) | Mark a single notification as read |
React hooks (@curator-studio/sdk/react)
Provider and auth
<CuratorProvider
tenant="support.eth"
defaultChain={11155111}
indexerUrl="https://..."
uploadMetadata={createUploadFn("/api/upload", secret)}
>CuratorProvider exposes authToken / setAuthToken via useCuratorSDK(). When set, the token is sent as Bearer for REST APIs (drafts, comments, reactions, notifications).
const { setAuthToken } = useCuratorSDK();
setAuthToken(jwt);Strategy query hooks
| Hook | Description |
|------|-------------|
| useCuratorSDK() | Access the SDK instance |
| useStrategies(variables?) | List strategies |
| useStrategyById(id) | Get strategy by address |
| useStrategyData(address) | On-chain strategy data |
| useStrategyBalance(address, token) | On-chain token balance |
| useDistributions(variables?) | List distributions |
| usePayouts(variables?) | List payouts |
| useDonors(variables?) | List donors |
| useStrategyBalances(variables?) | List strategy balances |
| useForks(variables?) | List forks |
| useWarehouseBalances(variables?) | List warehouse balances |
| useWarehouseBalance(user, token) | Single warehouse balance |
| useYieldRedirectors(variables?) | List yield redirectors |
| useYieldRedirectorById(id) | Get yield redirector by address |
| useHarvests(variables?) | List harvests |
| useTrendingStrategies(options?) | Trending strategies |
| useProtocolStats() | Protocol-wide stats |
| useStrategyLineage(address) | Fork tree |
| useENSGetAddress(name) | Resolve ENS name |
| useENSAvailable(label) | Check ENS availability |
| useCuratorStats(address) | Get curator stats (strategies, drafts, forks) |
Strategy mutation hooks
| Hook | Description |
|------|-------------|
| useCreateStrategy() | Create a strategy |
| useRebalanceStrategy() | Rebalance allocations |
| useDistributeStrategy() | Distribute funds |
| useSetENSName() | Set ENS name |
| useWithdrawFromWarehouse() | Withdraw from warehouse |
| useCreateYieldRedirector() | Create yield redirector |
| useHarvestYield() | Harvest yield |
Draft hooks
| Hook | Description |
|------|-------------|
| useDrafts(params?, opts?) | List drafts with optional filters |
| useDraft(id, opts?) | Get a single draft by ID |
| useDraftForks(source, params?, opts?) | List forks of a strategy or draft (source: { sourceStrategyId } \| { sourceDraftId }) |
| useVersions(draftId, opts?) | List version history for a draft |
| useCreateDraft() | Create a new draft |
| useUpdateDraft() | Update a draft ({ id, ...input }) |
| useForkDraft() | Fork a strategy or draft ({ sourceStrategyId } \| { sourceDraftId }) |
| usePublishDraft() | Publish draft on-chain ({ draftId, ensLabel? }) |
| useDeleteDraft() | Delete a draft |
| useProposeMerge() | Propose merging a fork into its parent |
| useAcceptMerge() | Accept a merge proposal (calls rebalance() for strategy targets) |
| useRejectMerge() | Reject a merge proposal ({ draftId, reason? }) |
| useWithdrawMerge() | Withdraw a merge proposal |
| useUpdateDraftVisibility() | Change draft visibility ({ id, visibility }) |
Comment hooks
| Hook | Description |
|------|-------------|
| useComments(params, opts?) | List comments for a target |
| useCreateComment() | Post a comment ({ targetId, targetType, body, parentCommentId? }) |
| useUpdateComment() | Edit a comment ({ id, body }) |
| useDeleteComment() | Delete a comment |
Reaction hooks
| Hook | Description |
|------|-------------|
| useReactions(targetId, targetType, opts?) | Get reaction counts for a target |
| useToggleReaction() | Toggle a reaction ({ targetId, targetType, type }) |
Notification hooks
| Hook | Description |
|------|-------------|
| useNotifications(params?, opts?) | List notifications (requires auth) |
| useUnreadNotificationCount(opts?) | Unread count (polls every 30s by default) |
| useMarkNotificationsRead() | Mark one or all notifications read (id?) |
Utility hooks
| Hook | Description |
|------|-------------|
| useInvalidate() | Invalidate query cache keys |
| useInvalidateENS() | Invalidate ENS-related queries |
Development
From the monorepo root:
pnpm --filter @curator-studio/sdk test
pnpm --filter @curator-studio/sdk buildOptional on-chain integration test (requires local Hardhat on http://127.0.0.1:8545 with deployments matching deployments.json for chain 31337):
RUN_SDK_CHAIN_TESTS=1 pnpm --filter @curator-studio/sdk test -- src/__tests__/strategy-lifecycle.test.tsSupported chains
| Network | Chain ID |
|---------|----------|
| Mainnet | 1 |
| Sepolia | 11155111 |
| Base Sepolia | 84532 |
| Hardhat | 31337 |
License
MIT
