@steerprotocol/curator-tools 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Steer Curator Override Registry
2
+
3
+ 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).
4
+
5
+ ## Components
6
+
7
+ ### Smart Contracts
8
+ - **SteerAuthorityResolver.sol**: An EAS Schema Resolver that enforces curator permissions. It validates that the attester is authorized for the specified vault and that the attestation is made on the correct network.
9
+ - **Access Control**: Uses OpenZeppelin's `Ownable2Step` for secure management of the top-level authority role.
10
+
11
+ ### Management Tooling
12
+ - **ManageCurators.s.sol**: A Foundry script for programmatically authorizing or revoking curators via CLI.
13
+
14
+ ### Client-Side Integration
15
+ - **StrategyStore.ts**: A TypeScript class providing helpers to check admin status, curator authorization, and encode management transactions. Includes built-in `Zod` validation for runtime safety.
16
+
17
+ ## Getting Started
18
+
19
+ ### Prerequisites
20
+ - [Foundry](https://getfoundry.sh/)
21
+ - [Node.js](https://nodejs.org/) (v18+)
22
+
23
+ ### Installation
24
+ 1. Install Foundry dependencies:
25
+ ```bash
26
+ forge install
27
+ ```
28
+ 2. Install Node dependencies:
29
+ ```bash
30
+ npm install
31
+ ```
32
+
33
+ ### Testing
34
+ - Run Smart Contract tests:
35
+ ```bash
36
+ forge test
37
+ ```
38
+ - Run Client-Side tests:
39
+ ```bash
40
+ npm test
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Managing Curators via CLI
46
+ To authorize a curator for a vault, use the `ManageCurators` script:
47
+
48
+ ```bash
49
+ export RESOLVER_ADDRESS=<DEPLYOED_RESOLVER>
50
+ export VAULT=<VAULT_ADDRESS>
51
+ export CURATOR=<CURATOR_ADDRESS>
52
+ export STATUS=true # true to authorize, false to revoke
53
+ export PRIVATE_KEY=<ADMIN_PRIVATE_KEY>
54
+
55
+ forge script script/ManageCurators.s.sol --rpc-url <RPC_URL> --broadcast
56
+ ```
57
+
58
+ ### Using the TypeScript Client
59
+ The `@steerprotocol/curator-tools` client (provided by the `StrategyStore` class) provides helpers to interact with the authority layer. It supports automatic address resolution via the `@steerprotocol/sdk`.
60
+
61
+ #### Basic Initialization
62
+ ```typescript
63
+ import { StrategyStore } from '@steerprotocol/curator-tools';
64
+ import { ethers } from 'ethers';
65
+
66
+ const provider = new ethers.JsonRpcProvider(RPC_URL);
67
+
68
+ // Automatic resolution for supported networks (e.g., Arbitrum: 42161)
69
+ const store = new StrategyStore({ chainId: 42161 }, provider);
70
+
71
+ // OR explicit address
72
+ const store = new StrategyStore({
73
+ resolverAddress: '0x...',
74
+ registryAddress: '0x...' // Optional: Enables strict vault validation
75
+ }, provider);
76
+ ```
77
+
78
+ #### Verification & Transactions
79
+ ```typescript
80
+ // Check if a curator is authorized (includes strict vault validation if registry is set)
81
+ const authorized = await store.isCurator(vaultAddress, curatorAddress);
82
+
83
+ // Generate transaction data for the admin dashboard (async due to registry validation)
84
+ const tx = await store.registerCuratorTx(vaultAddress, curatorAddress, true);
85
+ ```
86
+
87
+ ## CI/CD Pipeline
88
+ The project uses GitHub Actions for continuous integration and delivery.
89
+
90
+ ### Automated Testing
91
+ On every pull request and push to `master`, the following checks are performed:
92
+ - **Smart Contracts**: Solidity formatting check (`forge fmt`) and unit tests (`forge test`).
93
+ - **Client Package**: TypeScript type-checking (`tsc`) and unit tests (`npm test`).
94
+
95
+ ### Automated Releases
96
+ Automated versioning and NPM publishing are handled via **Semantic Release**.
97
+ - Releases are triggered on every push to the `master` branch after tests pass.
98
+ - Version numbers and changelogs are automatically generated based on [Conventional Commits](https://www.conventionalcommits.org/).
99
+ - **Required Secrets**: To enable publishing, ensure the repository has an `NPM_TOKEN` secret configured.
100
+
101
+ ## Security
102
+ - All administrative actions are restricted to the `owner`.
103
+ - Attestations are strictly validated against `block.chainid` to prevent replay attacks.
104
+ - Input validation is enforced on both smart contract and client-side levels.
@@ -0,0 +1,59 @@
1
+ import { ethers } from 'ethers';
2
+
3
+ interface StrategyStoreConfig {
4
+ chainId?: number;
5
+ resolverAddress?: string;
6
+ registryAddress?: string;
7
+ }
8
+ declare class StrategyStore {
9
+ private resolver;
10
+ private registry?;
11
+ private resolverAddress;
12
+ private registryAddress?;
13
+ constructor(config: string | StrategyStoreConfig, providerOrSigner: ethers.Provider | ethers.Signer, overrides?: {
14
+ resolver?: any;
15
+ registry?: any;
16
+ });
17
+ /**
18
+ * Checks if an address is the top-level admin.
19
+ * @param address The address to check.
20
+ */
21
+ isAdmin(address: string): Promise<boolean>;
22
+ /**
23
+
24
+ * Checks if a curator is authorized for a specific vault.
25
+
26
+ * @param vault The vault address.
27
+
28
+ * @param curator The curator address.
29
+
30
+ */
31
+ isCurator(vault: string, curator: string): Promise<boolean>;
32
+ /**
33
+
34
+
35
+
36
+ * Encodes a transaction to register or revoke a curator.
37
+
38
+
39
+
40
+ * @param vault The vault address.
41
+
42
+
43
+
44
+ * @param curator The curator address.
45
+
46
+
47
+
48
+ * @param status The status to set (default: true).
49
+
50
+
51
+
52
+ */
53
+ registerCuratorTx(vault: string, curator: string, status?: boolean): Promise<{
54
+ to: string;
55
+ data: string;
56
+ }>;
57
+ }
58
+
59
+ export { StrategyStore, type StrategyStoreConfig };
package/dist/index.mjs ADDED
@@ -0,0 +1,118 @@
1
+ // src/StrategyStore.ts
2
+ import { ethers } from "ethers";
3
+ import { z } from "zod";
4
+ import { getContractAddressByChainIdAndContractName } from "@steerprotocol/sdk";
5
+ var AddressSchema = z.string().refine((addr) => ethers.isAddress(addr), {
6
+ message: "Invalid Ethereum address"
7
+ });
8
+ var RESOLVER_ABI = [
9
+ "function owner() view returns (address)",
10
+ "function isCurator(address vault, address curator) view returns (bool)",
11
+ "function setCurator(address vault, address curator, bool status) external"
12
+ ];
13
+ var REGISTRY_ABI = [
14
+ "function totalVaultCount() view returns (uint256)",
15
+ "function getVaultDetails(address _address) view returns (tuple(uint8 state, uint256 tokenId, uint256 vaultID, string payloadIpfs, address vaultAddress, string beaconName))"
16
+ ];
17
+ var StrategyStore = class {
18
+ resolver;
19
+ registry;
20
+ resolverAddress;
21
+ registryAddress;
22
+ constructor(config, providerOrSigner, overrides) {
23
+ let resAddr;
24
+ let regAddr;
25
+ if (typeof config === "string") {
26
+ resAddr = config;
27
+ } else {
28
+ resAddr = config.resolverAddress;
29
+ regAddr = config.registryAddress;
30
+ if (!resAddr && config.chainId) {
31
+ resAddr = getContractAddressByChainIdAndContractName(config.chainId, "SteerAuthorityResolver");
32
+ }
33
+ if (!regAddr && config.chainId) {
34
+ regAddr = getContractAddressByChainIdAndContractName(config.chainId, "VaultRegistry");
35
+ if (!regAddr) {
36
+ throw new Error(`VaultRegistry address not found for chainId ${config.chainId}`);
37
+ }
38
+ }
39
+ }
40
+ if (!resAddr) {
41
+ throw new Error("Resolver address is required (either explicitly or via chainId)");
42
+ }
43
+ this.resolverAddress = AddressSchema.parse(resAddr);
44
+ this.resolver = overrides?.resolver || new ethers.Contract(this.resolverAddress, RESOLVER_ABI, providerOrSigner);
45
+ if (regAddr) {
46
+ this.registryAddress = AddressSchema.parse(regAddr);
47
+ this.registry = overrides?.registry || new ethers.Contract(this.registryAddress, REGISTRY_ABI, providerOrSigner);
48
+ }
49
+ }
50
+ /**
51
+ * Checks if an address is the top-level admin.
52
+ * @param address The address to check.
53
+ */
54
+ async isAdmin(address) {
55
+ const validatedAddress = AddressSchema.parse(address);
56
+ const owner = await this.resolver.owner();
57
+ return owner.toLowerCase() === validatedAddress.toLowerCase();
58
+ }
59
+ /**
60
+
61
+ * Checks if a curator is authorized for a specific vault.
62
+
63
+ * @param vault The vault address.
64
+
65
+ * @param curator The curator address.
66
+
67
+ */
68
+ async isCurator(vault, curator) {
69
+ const validatedVault = AddressSchema.parse(vault);
70
+ const validatedCurator = AddressSchema.parse(curator);
71
+ if (this.registry) {
72
+ const details = await this.registry.getVaultDetails(validatedVault);
73
+ if (details.vaultAddress === ethers.ZeroAddress || details.vaultAddress.toLowerCase() !== validatedVault.toLowerCase()) {
74
+ throw new Error(`Address ${validatedVault} is not a valid Steer vault`);
75
+ }
76
+ }
77
+ return await this.resolver.isCurator(validatedVault, validatedCurator);
78
+ }
79
+ /**
80
+
81
+
82
+
83
+ * Encodes a transaction to register or revoke a curator.
84
+
85
+
86
+
87
+ * @param vault The vault address.
88
+
89
+
90
+
91
+ * @param curator The curator address.
92
+
93
+
94
+
95
+ * @param status The status to set (default: true).
96
+
97
+
98
+
99
+ */
100
+ async registerCuratorTx(vault, curator, status = true) {
101
+ const validatedVault = AddressSchema.parse(vault);
102
+ const validatedCurator = AddressSchema.parse(curator);
103
+ if (this.registry) {
104
+ const details = await this.registry.getVaultDetails(validatedVault);
105
+ if (details.vaultAddress === ethers.ZeroAddress || details.vaultAddress.toLowerCase() !== validatedVault.toLowerCase()) {
106
+ throw new Error(`Address ${validatedVault} is not a valid Steer vault`);
107
+ }
108
+ }
109
+ const data = this.resolver.interface.encodeFunctionData("setCurator", [validatedVault, validatedCurator, status]);
110
+ return {
111
+ to: this.resolverAddress,
112
+ data
113
+ };
114
+ }
115
+ };
116
+ export {
117
+ StrategyStore
118
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@steerprotocol/curator-tools",
3
+ "version": "1.0.2",
4
+ "description": "Steer Protocol Curator Override Tools",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "main": "./dist/index.mjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.mts",
17
+ "import": "./dist/index.mjs"
18
+ }
19
+ },
20
+ "directories": {
21
+ "lib": "lib",
22
+ "test": "test"
23
+ },
24
+ "scripts": {
25
+ "test": "jest",
26
+ "build": "tsup src/index.ts --format esm --dts --clean",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [],
30
+ "author": "Derek Barrera",
31
+ "license": "ISC",
32
+ "dependencies": {
33
+ "@steerprotocol/sdk": "^1.22.4",
34
+ "ethers": "^6.16.0",
35
+ "zod": "^4.2.1"
36
+ },
37
+ "devDependencies": {
38
+ "@semantic-release/changelog": "^6.0.3",
39
+ "@semantic-release/git": "^10.0.1",
40
+ "@semantic-release/github": "^12.0.2",
41
+ "@semantic-release/npm": "^13.1.3",
42
+ "@types/jest": "^30.0.0",
43
+ "@types/node": "^25.0.3",
44
+ "@uniswap/sdk-core": "^7.10.0",
45
+ "@uniswap/v3-sdk": "^3.27.0",
46
+ "jest": "^30.2.0",
47
+ "semantic-release": "^25.0.2",
48
+ "ts-jest": "^29.4.6",
49
+ "ts-node": "^10.9.2",
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }