@steerprotocol/curator-tools
v1.7.0
Published
Steer Protocol Curator Override Tools
Readme
Steer Curator Override Registry
This project implements a centralized "Top-Level Authority" for the EAS (Ethereum Attestation Service) Key-Value store. It allows a protocol administrator to manage a registry of authorized curators who can attest to vault-specific overrides (e.g., updating strategy IPFS hashes).
Components
Smart Contracts
- SteerAuthorityResolver.sol: An EAS Schema Resolver that enforces curator permissions. It validates that the attester is authorized for the specified vault; the attestation payload includes a
targetChainIdfor cross-chain overrides stored canonically on Arbitrum One. - Access Control: Uses OpenZeppelin's
Ownable2Stepfor secure management of the top-level authority role.
Management Tooling
- ManageCurators.s.sol: A Foundry script for programmatically authorizing or revoking curators via CLI.
Client-Side Integration
- StrategyStore.ts: A TypeScript class providing helpers to check admin status, curator authorization, and encode management transactions. Includes built-in
Zodvalidation for runtime safety.
Getting Started
Prerequisites
Installation
- Install Foundry dependencies:
forge install - Install Node dependencies:
npm install
Testing
- Run Smart Contract tests:
forge test - Run Client-Side tests:
npm test
Features
- Curator gating (per vault): Only admins can authorize/revoke curators per vault via
setCurator(vault, curator, status). - Cross-chain overrides (canonical on Arbitrum One): The payload includes a
targetChainId(encoded in thechainIdfield) so overrides for many chains can be stored on Arbitrum One. - Two-step admin transfers: Uses OpenZeppelin
Ownable2Step(transferOwnership+acceptOwnership) to reduce lockout risk. - Operational scripting: Foundry scripts for deployment, curator management, and admin migration.
- TypeScript client (
StrategyStore):- Reads admin + curator status (
isAdmin,isCurator). - Encodes admin transactions (
registerCuratorTx) for dashboards. - Optional strict vault validation via
VaultRegistry.getVaultDetails(...). - Address resolution via
@steerprotocol/sdkwith a temporary Arbitrum resolver override.
- Reads admin + curator status (
EAS Schema (Option B: Cross-Chain Overrides)
Canonical overrides are stored on Arbitrum One, and the payload’s chainId field is treated as a targetChainId.
- Schema string:
address vault,uint256 chainId,uint256 strategyTokenId,string manifestCid - Semantics:
chainIdistargetChainId(it may differ fromblock.chainid) - SchemaRegistry (Arbitrum One):
0xA310da9c5B885E7fb3fbA9D66E9Ba6Df512b78eB - Resolver (Arbitrum One):
0x6628d1Bf82F34de132d0e1c60DEB574C1352d5A7 - Schema revocable:
true - Schema UID:
0xf1a6a394d1f3dd832a15e5b750963d929812e2b74d2dcea68ddf9f75691f16bf - Deprecated schema (no resolver):
0x62b656756a16bd3d2ef501cd9493c603fd90b050d6e9cfffc8e450639ce30a27(registered with resolveraddress(0), so it enforces no on-chain curator checks)
User Flows
Admin
- Deploy
SteerAuthorityResolver. - Authorize/revoke curators per vault.
- Migrate admin control to a new owner (EOA or multisig) using the 2-step ownership flow.
Curator
- Create an EAS attestation for a vault override.
- Resolver validates curator authorization for the target vault.
- If valid, the attestation is recorded by EAS. The payload includes a
targetChainIdso indexers/integrators can interpret which chain the override applies to.
Integrator (frontend / indexer / backend)
- Read curator authorization per vault.
- (Optional) Validate vault addresses via
VaultRegistrybefore trusting overrides. - Build admin UI flows by generating
to/datatransactions viaStrategyStore.
Usage
Deploying the Resolver (Foundry)
Deploys on Arbitrum One using the official Arbitrum One EAS address hardcoded in script/DeploySteerAuthorityResolver.s.sol.
export PRIVATE_KEY=<DEPLOYER_PRIVATE_KEY>
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
forge script script/DeploySteerAuthorityResolver.s.sol:DeploySteerAuthorityResolver --rpc-url "$RPC_URL" --broadcastBroadcast results (including deployed address) are written under broadcast/.
Registering the Cross-Chain Schema (Foundry)
Registers a new schema on Arbitrum One’s SchemaRegistry with SteerAuthorityResolver as the resolver.
export PRIVATE_KEY=<REGISTERER_PRIVATE_KEY>
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
forge script script/RegisterSteerCrosschainSchema.s.sol:RegisterSteerCrosschainSchema --rpc-url "$RPC_URL" --broadcastEncoding (Solidity)
bytes memory data = abi.encode(vault, targetChainId, strategyTokenId, manifestCid);Encoding (TypeScript)
const data = StrategyStore.encodeOverrideAttestationData(vault, targetChainId, 1n, manifestCid);Managing Curators via CLI
Authorize/revoke a curator for a specific vault.
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
export VAULT=<VAULT_ADDRESS>
export CURATOR=<CURATOR_ADDRESS>
export STATUS=true # true to authorize, false to revoke
export PRIVATE_KEY=<ADMIN_PRIVATE_KEY>
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
forge script script/ManageCurators.s.sol:ManageCurators --rpc-url "$RPC_URL" --broadcastMigrating Admin Control (Ownership)
Admin migration is a 2-step flow because the resolver uses Ownable2Step.
Step 1: Initiate transfer (current admin)
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
export NEW_OWNER=<NEW_ADMIN_ADDRESS>
export PRIVATE_KEY=<CURRENT_ADMIN_PRIVATE_KEY>
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
forge script script/MigrateAdminControl.s.sol:MigrateAdminControl --rpc-url "$RPC_URL" --broadcastStep 2: Accept ownership (new admin)
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
export ONLY_ACCEPT=true
export NEW_OWNER_PRIVATE_KEY=<NEW_ADMIN_PRIVATE_KEY>
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
forge script script/MigrateAdminControl.s.sol:MigrateAdminControl --rpc-url "$RPC_URL" --broadcastVerifying Admin Migration
Use cast to confirm owner() and pendingOwner() on-chain.
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
cast call "$RESOLVER_ADDRESS" "owner()(address)" --rpc-url "$RPC_URL"
cast call "$RESOLVER_ADDRESS" "pendingOwner()(address)" --rpc-url "$RPC_URL"Using the TypeScript Client
The @steerprotocol/curator-tools client (provided by the StrategyStore class) provides helpers to interact with the authority layer.
import { StrategyStore } from '@steerprotocol/curator-tools';
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(RPC_URL);
// Automatic resolution for supported networks (e.g., Arbitrum: 42161)
const store = new StrategyStore({ chainId: 42161 }, provider);
// OR explicit addresses
const store2 = new StrategyStore(
{
resolverAddress: '0x...',
registryAddress: '0x...', // Optional: Enables strict vault validation
chainId: 42161, // Required for curator checks/updates
},
provider
);
// Check if a curator is authorized (includes strict vault validation if registry is set)
const authorized = await store2.isCurator(vaultAddress, curatorAddress);
// Generate tx data for an admin dashboard
const tx = await store2.registerCuratorTx(vaultAddress, curatorAddress, true);Contract Verification on Arbiscan (optional)
If you want to verify the deployed resolver source, you can use forge verify-contract.
export ARBISCAN_API_KEY=<API_KEY>
forge verify-contract \
--chain-id 42161 \
--watch \
--etherscan-api-key "$ARBISCAN_API_KEY" \
<DEPLOYED_RESOLVER> \
src/SteerAuthorityResolver.sol:SteerAuthorityResolver \
--constructor-args $(cast abi-encode "constructor(address)" 0xbD75f629A22Dc1ceD33dDA0b68c546A1c035c458)CI/CD Pipeline
The project uses GitHub Actions for continuous integration and delivery.
Automated Testing
On every pull request and push to master, the following checks are performed:
- Smart Contracts: Solidity formatting check (
forge fmt) and unit tests (forge test). - Client Package: TypeScript type-checking (
tsc) and unit tests (npm test).
Automated Releases
Automated versioning and NPM publishing are handled via Semantic Release.
- Releases are triggered on every push to the
masterbranch after tests pass. - Version numbers and changelogs are automatically generated based on Conventional Commits.
- Required Secrets: To enable publishing, ensure the repository has an
NPM_TOKENsecret configured.
Security
- All administrative actions are restricted to the
owner. - Resolver validation enforces curator authorization per vault on both attestation and revocation (removing a curator prevents them from attesting and revoking prior attestations).
- The payload includes a
targetChainIdfor cross-chain overrides stored on Arbitrum One. - Input validation is enforced on both smart contract and client-side levels.
\n- Multi-chain support: Enhanced
StrategyStoreto handle multi-chain configurations and deterministic vault identifiers.
