@steerprotocol/curator-tools 1.2.0 → 1.4.0
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 +141 -18
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +11 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ This project implements a centralized "Top-Level Authority" for the EAS (Ethereu
|
|
|
5
5
|
## Components
|
|
6
6
|
|
|
7
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
|
|
8
|
+
- **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 `targetChainId` for cross-chain overrides stored canonically on Arbitrum One.
|
|
9
9
|
- **Access Control**: Uses OpenZeppelin's `Ownable2Step` for secure management of the top-level authority role.
|
|
10
10
|
|
|
11
11
|
### Management Tooling
|
|
@@ -40,25 +40,132 @@ This project implements a centralized "Top-Level Authority" for the EAS (Ethereu
|
|
|
40
40
|
npm test
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
## Features
|
|
44
|
+
- **Curator gating (per vault)**: Only admins can authorize/revoke curators per vault via `setCurator(vault, curator, status)`.
|
|
45
|
+
- **Cross-chain overrides (canonical on Arbitrum One)**: The payload includes a `targetChainId` (encoded in the `chainId` field) so overrides for many chains can be stored on Arbitrum One.
|
|
46
|
+
- **Two-step admin transfers**: Uses OpenZeppelin `Ownable2Step` (`transferOwnership` + `acceptOwnership`) to reduce lockout risk.
|
|
47
|
+
- **Operational scripting**: Foundry scripts for deployment, curator management, and admin migration.
|
|
48
|
+
- **TypeScript client** (`StrategyStore`):
|
|
49
|
+
- Reads admin + curator status (`isAdmin`, `isCurator`).
|
|
50
|
+
- Encodes admin transactions (`registerCuratorTx`) for dashboards.
|
|
51
|
+
- Optional strict vault validation via `VaultRegistry.getVaultDetails(...)`.
|
|
52
|
+
- Address resolution via `@steerprotocol/sdk` with a temporary Arbitrum resolver override.
|
|
53
|
+
|
|
54
|
+
## EAS Schema (Option B: Cross-Chain Overrides)
|
|
55
|
+
|
|
56
|
+
Canonical overrides are stored on **Arbitrum One**, and the payload’s `chainId` field is treated as a `targetChainId`.
|
|
57
|
+
|
|
58
|
+
- **Schema string**: `address vault,uint256 chainId,uint256 strategyTokenId,string manifestCid`
|
|
59
|
+
- **Semantics**: `chainId` is `targetChainId` (it may differ from `block.chainid`)
|
|
60
|
+
- **SchemaRegistry (Arbitrum One)**: `0xA310da9c5B885E7fb3fbA9D66E9Ba6Df512b78eB`
|
|
61
|
+
- **Resolver (Arbitrum One)**: `0xD36E3f33c6f1814F6923835Ae7dC508FEDA14b62`
|
|
62
|
+
- **Schema revocable**: `true`
|
|
63
|
+
- **Schema UID**: `0x2a8ed2dea14b650384d87e1a9fdcd56ab7489fac437134f594f518d9538cbab9`
|
|
64
|
+
- **Deprecated schema (no resolver)**: `0x62b656756a16bd3d2ef501cd9493c603fd90b050d6e9cfffc8e450639ce30a27` (registered with resolver `address(0)`, so it enforces no on-chain curator checks)
|
|
65
|
+
|
|
66
|
+
## User Flows
|
|
67
|
+
|
|
68
|
+
### Admin
|
|
69
|
+
1. Deploy `SteerAuthorityResolver`.
|
|
70
|
+
2. Authorize/revoke curators per vault.
|
|
71
|
+
3. Migrate admin control to a new owner (EOA or multisig) using the 2-step ownership flow.
|
|
72
|
+
|
|
73
|
+
### Curator
|
|
74
|
+
1. Create an EAS attestation for a vault override.
|
|
75
|
+
2. Resolver validates curator authorization for the target vault.
|
|
76
|
+
3. If valid, the attestation is recorded by EAS. The payload includes a `targetChainId` so indexers/integrators can interpret which chain the override applies to.
|
|
77
|
+
|
|
78
|
+
### Integrator (frontend / indexer / backend)
|
|
79
|
+
1. Read curator authorization per vault.
|
|
80
|
+
2. (Optional) Validate vault addresses via `VaultRegistry` before trusting overrides.
|
|
81
|
+
3. Build admin UI flows by generating `to`/`data` transactions via `StrategyStore`.
|
|
82
|
+
|
|
43
83
|
## Usage
|
|
44
84
|
|
|
85
|
+
### Deploying the Resolver (Foundry)
|
|
86
|
+
Deploys on Arbitrum One using the official Arbitrum One EAS address hardcoded in `script/DeploySteerAuthorityResolver.s.sol`.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
export PRIVATE_KEY=<DEPLOYER_PRIVATE_KEY>
|
|
90
|
+
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
|
|
91
|
+
|
|
92
|
+
forge script script/DeploySteerAuthorityResolver.s.sol:DeploySteerAuthorityResolver --rpc-url "$RPC_URL" --broadcast
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Broadcast results (including deployed address) are written under `broadcast/`.
|
|
96
|
+
|
|
97
|
+
### Registering the Cross-Chain Schema (Foundry)
|
|
98
|
+
Registers a new schema on Arbitrum One’s SchemaRegistry with `SteerAuthorityResolver` as the resolver.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
export PRIVATE_KEY=<REGISTERER_PRIVATE_KEY>
|
|
102
|
+
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
|
|
103
|
+
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
|
|
104
|
+
|
|
105
|
+
forge script script/RegisterSteerCrosschainSchema.s.sol:RegisterSteerCrosschainSchema --rpc-url "$RPC_URL" --broadcast
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Encoding (Solidity)**
|
|
109
|
+
```solidity
|
|
110
|
+
bytes memory data = abi.encode(vault, targetChainId, strategyTokenId, manifestCid);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Encoding (TypeScript)**
|
|
114
|
+
```ts
|
|
115
|
+
const data = StrategyStore.encodeOverrideAttestationData(vault, targetChainId, 1n, manifestCid);
|
|
116
|
+
```
|
|
117
|
+
|
|
45
118
|
### Managing Curators via CLI
|
|
46
|
-
|
|
119
|
+
Authorize/revoke a curator for a specific vault.
|
|
47
120
|
|
|
48
121
|
```bash
|
|
49
|
-
export RESOLVER_ADDRESS=<
|
|
122
|
+
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
|
|
50
123
|
export VAULT=<VAULT_ADDRESS>
|
|
51
124
|
export CURATOR=<CURATOR_ADDRESS>
|
|
52
125
|
export STATUS=true # true to authorize, false to revoke
|
|
53
126
|
export PRIVATE_KEY=<ADMIN_PRIVATE_KEY>
|
|
127
|
+
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
|
|
128
|
+
|
|
129
|
+
forge script script/ManageCurators.s.sol:ManageCurators --rpc-url "$RPC_URL" --broadcast
|
|
130
|
+
```
|
|
54
131
|
|
|
55
|
-
|
|
132
|
+
### Migrating Admin Control (Ownership)
|
|
133
|
+
Admin migration is a 2-step flow because the resolver uses `Ownable2Step`.
|
|
134
|
+
|
|
135
|
+
**Step 1: Initiate transfer (current admin)**
|
|
136
|
+
```bash
|
|
137
|
+
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
|
|
138
|
+
export NEW_OWNER=<NEW_ADMIN_ADDRESS>
|
|
139
|
+
export PRIVATE_KEY=<CURRENT_ADMIN_PRIVATE_KEY>
|
|
140
|
+
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
|
|
141
|
+
|
|
142
|
+
forge script script/MigrateAdminControl.s.sol:MigrateAdminControl --rpc-url "$RPC_URL" --broadcast
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Step 2: Accept ownership (new admin)**
|
|
146
|
+
```bash
|
|
147
|
+
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
|
|
148
|
+
export ONLY_ACCEPT=true
|
|
149
|
+
export NEW_OWNER_PRIVATE_KEY=<NEW_ADMIN_PRIVATE_KEY>
|
|
150
|
+
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
|
|
151
|
+
|
|
152
|
+
forge script script/MigrateAdminControl.s.sol:MigrateAdminControl --rpc-url "$RPC_URL" --broadcast
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Verifying Admin Migration
|
|
156
|
+
Use `cast` to confirm `owner()` and `pendingOwner()` on-chain.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
export RESOLVER_ADDRESS=<DEPLOYED_RESOLVER>
|
|
160
|
+
export RPC_URL=<ARBITRUM_ONE_RPC_URL>
|
|
161
|
+
|
|
162
|
+
cast call "$RESOLVER_ADDRESS" "owner()(address)" --rpc-url "$RPC_URL"
|
|
163
|
+
cast call "$RESOLVER_ADDRESS" "pendingOwner()(address)" --rpc-url "$RPC_URL"
|
|
56
164
|
```
|
|
57
165
|
|
|
58
166
|
### Using the TypeScript Client
|
|
59
|
-
The `@steerprotocol/curator-tools` client (provided by the `StrategyStore` class) provides helpers to interact with the authority layer.
|
|
167
|
+
The `@steerprotocol/curator-tools` client (provided by the `StrategyStore` class) provides helpers to interact with the authority layer.
|
|
60
168
|
|
|
61
|
-
#### Basic Initialization
|
|
62
169
|
```typescript
|
|
63
170
|
import { StrategyStore } from '@steerprotocol/curator-tools';
|
|
64
171
|
import { ethers } from 'ethers';
|
|
@@ -68,20 +175,35 @@ const provider = new ethers.JsonRpcProvider(RPC_URL);
|
|
|
68
175
|
// Automatic resolution for supported networks (e.g., Arbitrum: 42161)
|
|
69
176
|
const store = new StrategyStore({ chainId: 42161 }, provider);
|
|
70
177
|
|
|
71
|
-
// OR explicit
|
|
72
|
-
const
|
|
178
|
+
// OR explicit addresses
|
|
179
|
+
const store2 = new StrategyStore(
|
|
180
|
+
{
|
|
73
181
|
resolverAddress: '0x...',
|
|
74
|
-
registryAddress: '0x...' // Optional: Enables strict vault validation
|
|
75
|
-
},
|
|
76
|
-
|
|
182
|
+
registryAddress: '0x...', // Optional: Enables strict vault validation
|
|
183
|
+
},
|
|
184
|
+
provider
|
|
185
|
+
);
|
|
77
186
|
|
|
78
|
-
#### Verification & Transactions
|
|
79
|
-
```typescript
|
|
80
187
|
// Check if a curator is authorized (includes strict vault validation if registry is set)
|
|
81
|
-
const authorized = await
|
|
188
|
+
const authorized = await store2.isCurator(vaultAddress, curatorAddress);
|
|
82
189
|
|
|
83
|
-
// Generate
|
|
84
|
-
const tx = await
|
|
190
|
+
// Generate tx data for an admin dashboard
|
|
191
|
+
const tx = await store2.registerCuratorTx(vaultAddress, curatorAddress, true);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Contract Verification on Arbiscan (optional)
|
|
195
|
+
If you want to verify the deployed resolver source, you can use `forge verify-contract`.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
export ARBISCAN_API_KEY=<API_KEY>
|
|
199
|
+
|
|
200
|
+
forge verify-contract \
|
|
201
|
+
--chain-id 42161 \
|
|
202
|
+
--watch \
|
|
203
|
+
--etherscan-api-key "$ARBISCAN_API_KEY" \
|
|
204
|
+
<DEPLOYED_RESOLVER> \
|
|
205
|
+
src/SteerAuthorityResolver.sol:SteerAuthorityResolver \
|
|
206
|
+
--constructor-args $(cast abi-encode "constructor(address)" 0xbD75f629A22Dc1ceD33dDA0b68c546A1c035c458)
|
|
85
207
|
```
|
|
86
208
|
|
|
87
209
|
## CI/CD Pipeline
|
|
@@ -100,5 +222,6 @@ Automated versioning and NPM publishing are handled via **Semantic Release**.
|
|
|
100
222
|
|
|
101
223
|
## Security
|
|
102
224
|
- All administrative actions are restricted to the `owner`.
|
|
103
|
-
-
|
|
104
|
-
-
|
|
225
|
+
- Resolver validation enforces curator authorization per vault on both attestation and revocation (removing a curator prevents them from attesting *and* revoking prior attestations).
|
|
226
|
+
- The payload includes a `targetChainId` for cross-chain overrides stored on Arbitrum One.
|
|
227
|
+
- Input validation is enforced on both smart contract and client-side levels.
|
package/dist/index.d.mts
CHANGED
|
@@ -6,6 +6,9 @@ interface StrategyStoreConfig {
|
|
|
6
6
|
registryAddress?: string;
|
|
7
7
|
}
|
|
8
8
|
declare class StrategyStore {
|
|
9
|
+
static readonly ARBITRUM_ONE_RESOLVER_ADDRESS = "0xD36E3f33c6f1814F6923835Ae7dC508FEDA14b62";
|
|
10
|
+
static readonly ARBITRUM_ONE_CROSSCHAIN_SCHEMA_UID = "0x2a8ed2dea14b650384d87e1a9fdcd56ab7489fac437134f594f518d9538cbab9";
|
|
11
|
+
static encodeOverrideAttestationData(vault: string, targetChainId: number | bigint, strategyTokenId: bigint, manifestCid: string): string;
|
|
9
12
|
private resolver;
|
|
10
13
|
private registry?;
|
|
11
14
|
private resolverAddress;
|
package/dist/index.mjs
CHANGED
|
@@ -14,7 +14,16 @@ var REGISTRY_ABI = [
|
|
|
14
14
|
"function totalVaultCount() view returns (uint256)",
|
|
15
15
|
"function getVaultDetails(address _address) view returns (tuple(uint8 state, uint256 tokenId, uint256 vaultID, string payloadIpfs, address vaultAddress, string beaconName))"
|
|
16
16
|
];
|
|
17
|
-
var StrategyStore = class {
|
|
17
|
+
var StrategyStore = class _StrategyStore {
|
|
18
|
+
static ARBITRUM_ONE_RESOLVER_ADDRESS = "0xD36E3f33c6f1814F6923835Ae7dC508FEDA14b62";
|
|
19
|
+
static ARBITRUM_ONE_CROSSCHAIN_SCHEMA_UID = "0x2a8ed2dea14b650384d87e1a9fdcd56ab7489fac437134f594f518d9538cbab9";
|
|
20
|
+
static encodeOverrideAttestationData(vault, targetChainId, strategyTokenId, manifestCid) {
|
|
21
|
+
const validatedVault = AddressSchema.parse(vault);
|
|
22
|
+
return ethers.AbiCoder.defaultAbiCoder().encode(
|
|
23
|
+
["address", "uint256", "uint256", "string"],
|
|
24
|
+
[validatedVault, targetChainId, strategyTokenId, manifestCid]
|
|
25
|
+
);
|
|
26
|
+
}
|
|
18
27
|
resolver;
|
|
19
28
|
registry;
|
|
20
29
|
resolverAddress;
|
|
@@ -29,7 +38,7 @@ var StrategyStore = class {
|
|
|
29
38
|
regAddr = config.registryAddress;
|
|
30
39
|
if (!resAddr && config.chainId) {
|
|
31
40
|
if (config.chainId === 42161) {
|
|
32
|
-
resAddr =
|
|
41
|
+
resAddr = _StrategyStore.ARBITRUM_ONE_RESOLVER_ADDRESS;
|
|
33
42
|
} else {
|
|
34
43
|
resAddr = getContractAddressByChainIdAndContractName(config.chainId, "SteerAuthorityResolver");
|
|
35
44
|
}
|