@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 +104 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.mjs +118 -0
- package/package.json +53 -0
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|