@curator-studio/contracts
v1.0.0
Published
Smart contracts for the Curator platform: strategy-based token distribution on Ethereum with 0xSplits integration.
Readme
Curator Contracts
Smart contracts for the Curator platform: strategy-based token distribution on Ethereum with 0xSplits integration.
Architecture
- Strategy – Core contract for weighted token distribution; deployed as minimal proxies (clones) via the factory.
- StrategyFactory – Deploys new Strategy instances and holds the implementation. Uses the official SplitsWarehouse.
- SplitsWarehouse – 0xSplits integration for unified withdrawals (use existing deployment on testnet/mainnet).
- ForeverSubnameRegistrar – ENS subdomain registration (e.g.
strategy.dacc.eth). - TestToken – ERC20 test tokens for local development only.
Deployment Overview
Deployment is split into two phases:
- Shared infrastructure — deploys Strategy implementation, SplitsWarehouse, and (on hardhat) local ENS.
- Per-tenant deployment — deploys a tenant-specific StrategyFactory with its own ENS domain.
| Network | Shared infra | Per-tenant |
|---------|-------------|------------|
| Local | bun run deploy | bun run deploy:tenant -- --tenant <name>.eth |
| Sepolia | bun run deploy:sepolia | bun run deploy:tenant -- --tenant <name>.eth --network sepolia |
| Base Sepolia | bun run deploy:base-sepolia | bun run deploy:tenant -- --tenant <name>.eth --network base-sepolia |
| Mainnet | bun run deploy:mainnet | bun run deploy:tenant -- --tenant <name>.eth --network mainnet |
Shared infrastructure addresses
- SplitsWarehouse:
0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8(Sepolia, Base Sepolia & mainnet) - ENS (Sepolia): NameWrapper
0x0635513f179D50A207757E05759CbD106d7dFcE8, Resolver0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5 - ENS (Mainnet): NameWrapper
0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401, Resolver0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63 - L2 Reverse Registrar (Base Sepolia):
0x00000BeEF055f7934784D6d81b6BC86665630dbA
Prerequisites
Copy .env.example to .env and fill in the values for the target network:
SEPOLIA_RPC_URL=
SEPOLIA_PRIVATE_KEY=
BASE_SEPOLIA_RPC_URL=
BASE_PRIVATE_KEY=
ETHERSCAN_API_KEY=
BASESCAN_API_KEY=Local Development
# Terminal 1: start Hardhat node
bun run dev
# Terminal 2: deploy shared infrastructure
bun run deploy
# Terminal 3: deploy a tenant
bun run deploy:tenant -- --tenant dacc.ethbun run deploy deploys full shared infrastructure including local ENS, SplitsWarehouse, Strategy, and test tokens. The ENS domain configured in ENS.dev.ts is registered automatically.
deploy:tenant deploys a tenant-specific StrategyFactory and configures ENS for it. On hardhat, it automatically registers the parent ENS name if it doesn't already exist.
Tests
bun run testTestnet (Sepolia / Base Sepolia)
ENS Prerequisites
- Own your domain (e.g.
dacc.eth) on the target network - Wrap it in the NameWrapper — required so
ForeverSubnameRegistrarcan create subnames viasetSubnodeRecord
The easiest way is to open scripts/wrap-ens-domain.html in a browser — it connects MetaMask, detects the network, and walks through approve + wrap with the correct fuse.
Alternatively, with cast:
# Calculate token ID for your label
bun -e "import { id } from 'ethers'; console.log(id('your-label'))"
# 1. Approve NameWrapper to take the domain from BaseRegistrar
cast send 0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85 \
"approve(address,uint256)" \
0x0635513f179D50A207757E05759CbD106d7dFcE8 \
<TOKEN_ID> \
--private-key $SEPOLIA_PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL
# 2. Wrap the name with CANNOT_UNWRAP fuse (1) — required for ForeverSubnameRegistrar
cast send 0x0635513f179D50A207757E05759CbD106d7dFcE8 \
"wrapETH2LD(string,address,uint16,address)" \
"your-label" <OWNER_ADDRESS> 1 0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5 \
--private-key $SEPOLIA_PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL- Approve the deployed contracts on NameWrapper — after running
deploy:tenant, callsetApprovalForAllon the NameWrapper from the wallet that owns the ENS name (if different from the deployer key):
# Approve ForeverSubnameRegistrar and StrategyFactory on NameWrapper (Sepolia)
# Do this via Etherscan Write Contract or cast:
cast send 0x0635513f179D50A207757E05759CbD106d7dFcE8 \
"setApprovalForAll(address,bool)" \
<FOREVER_SUBNAME_REGISTRAR_ADDRESS> true \
--private-key $ENS_OWNER_PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL
cast send 0x0635513f179D50A207757E05759CbD106d7dFcE8 \
"setApprovalForAll(address,bool)" \
<STRATEGY_FACTORY_ADDRESS> true \
--private-key $ENS_OWNER_PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URLThe deployed addresses are printed by
deploy:tenantand stored indeployments.jsonundertenants.<name>.eth.
Parameters
ignition/parameters/sepolia.json contains shared infrastructure addresses:
{
"DeployModule": {
"splitsWarehouse": "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8",
"reverseRegistrar": "0xA0a1AbcDAe1a2a4A2EF8e9113Ff0e02DD81DC0C6"
},
"Registrar": {
"nameWrapper": "0x0635513f179D50A207757E05759CbD106d7dFcE8",
"resolver": "0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5",
"reverseRegistrar": "0xA0a1AbcDAe1a2a4A2EF8e9113Ff0e02DD81DC0C6"
}
}Deploy
# 1. Deploy shared infrastructure
bun run deploy:sepolia
# or
bun run deploy:base-sepolia
# 2. Deploy a tenant
bun run deploy:tenant -- --tenant dacc.eth --network sepoliaVerify on Etherscan
bun run verify:sepolia
bun run verify:base-sepolia
# Verify a specific tenant's contracts
bun run verify:tenant -- --tenant dacc.eth --network sepoliaVerify ENS Configuration
bun run verify-ens -- --network sepolia --tenant dacc.ethChecks parent domain wrapping, registrar/factory approval, factory ENS config, and subname availability.
Mainnet
# 1. Deploy shared infrastructure
bun run deploy:mainnet
# 2. Deploy a tenant
bun run deploy:tenant -- --tenant dacc.eth --network mainnetMulti-Tenant Deployments
Each tenant gets its own StrategyFactory and ForeverSubnameRegistrar under its own ENS domain:
bun run deploy:tenant -- --tenant <name>.eth --network <network>This script:
- Deploys a new
StrategyFactory - On hardhat: registers the parent ENS name if not already registered
- Deploys a new
ForeverSubnameRegistrar - Configures ENS on the factory (
configureENS) - Approves the registrar and factory on NameWrapper
- Writes the tenant entry to
deployments.json
Tenant config is auto-populated in the SDK and indexer from deployments.json — no manual config editing needed.
After Any Deployment
1. Rebuild the SDK
cd packages/sdk && bun run buildThe SDK statically imports deployments.json at build time. Rebuild after any deploy:tenant run to pick up new tenant addresses.
2. Seed strategies (local/testnet)
cd apps/web
PRIVATE_KEY=0x... bun scripts/seed-dacc.ts --network hardhat3. Configure the indexer
In packages/indexer/ponder.config.ts, enable the chain. In .env.local, set PONDER_RPC_URL_<CHAIN_ID>.
Troubleshooting
- "Nonce mismatch" during deploy — Delete
ignition/deployments/chain-<ID>/and redeploy with--reset. - "ENS not configured" revert — Run
deploy:tenantwhich callsconfigureENSautomatically. - "Internal error" revert when creating strategies — ENS misconfigured or parent name not registered. Re-run
deploy:tenant. - "ENS already configured" — Factory is already set up. Nothing to do.
- Stale tenant addresses in SDK — Rebuild the SDK:
bun run buildinpackages/sdk. - Start fresh on a chain — Delete
ignition/deployments/chain-<ID>/then redeploy.
Security notes
- Strategy instances are immutable; owner can change allocations but cannot withdraw recipient funds.
- Funds are distributed into SplitsWarehouse; withdrawals are pull-based by recipients.
- No upgrade path or admin keys on strategy logic.
