eigen-skills 1.0.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 +753 -0
- package/index.js +23 -0
- package/package.json +29 -0
- package/scripts/demo.js +93 -0
- package/skills/eigen-avs/SKILL.md +92 -0
- package/skills/eigen-avs/scripts/avs-api.js +131 -0
- package/skills/eigen-compute/SKILL.md +198 -0
- package/skills/eigen-compute/scripts/compute-api.js +153 -0
- package/skills/eigen-da/SKILL.md +148 -0
- package/skills/eigen-da/scripts/da-api.js +128 -0
- package/skills/eigen-delegation/SKILL.md +98 -0
- package/skills/eigen-delegation/scripts/delegation-api.js +160 -0
- package/skills/eigen-restaking/SKILL.md +92 -0
- package/skills/eigen-restaking/scripts/eigen-api.js +186 -0
- package/skills/eigen-rewards/SKILL.md +83 -0
- package/skills/eigen-rewards/scripts/rewards-api.js +108 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EigenCompute API — Wrapper around ecloud CLI for programmatic TEE management
|
|
3
|
+
*
|
|
4
|
+
* Provides a JS API over the ecloud CLI commands for app deployment,
|
|
5
|
+
* lifecycle management, and TEE attestation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
class EigenCompute {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.cliAvailable = this._checkCLI();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_checkCLI() {
|
|
18
|
+
try {
|
|
19
|
+
execSync('ecloud --version', { stdio: 'pipe' });
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_exec(cmd) {
|
|
27
|
+
if (!this.cliAvailable) throw new Error('ecloud CLI not installed. Run: npm install -g @layr-labs/ecloud-cli');
|
|
28
|
+
try {
|
|
29
|
+
return execSync(`ecloud ${cmd}`, { encoding: 'utf-8', timeout: 30000 }).trim();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
throw new Error(`ecloud command failed: ${err.stderr || err.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Authentication ───────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check which address the CLI is authenticated as.
|
|
39
|
+
*/
|
|
40
|
+
whoami() {
|
|
41
|
+
return this._exec('auth whoami');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── App Management ───────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* List all deployed apps.
|
|
48
|
+
*/
|
|
49
|
+
listApps() {
|
|
50
|
+
const output = this._exec('compute app list');
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get info for a specific app.
|
|
56
|
+
* @param {string} appId
|
|
57
|
+
*/
|
|
58
|
+
getAppInfo(appId) {
|
|
59
|
+
return this._exec(`compute app info ${appId}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get logs for a specific app.
|
|
64
|
+
* @param {string} appId
|
|
65
|
+
*/
|
|
66
|
+
getAppLogs(appId) {
|
|
67
|
+
return this._exec(`compute app logs ${appId}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stop a running app.
|
|
72
|
+
* @param {string} appId
|
|
73
|
+
*/
|
|
74
|
+
stopApp(appId) {
|
|
75
|
+
return this._exec(`compute app stop ${appId}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start a stopped app.
|
|
80
|
+
* @param {string} appId
|
|
81
|
+
*/
|
|
82
|
+
startApp(appId) {
|
|
83
|
+
return this._exec(`compute app start ${appId}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── TEE Attestation ──────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Collect attestation data from inside a TEE container.
|
|
90
|
+
* Call this from within a running EigenCompute deployment.
|
|
91
|
+
*/
|
|
92
|
+
collectAttestation() {
|
|
93
|
+
return {
|
|
94
|
+
appId: process.env.ECLOUD_APP_ID || null,
|
|
95
|
+
platform: 'Intel TDX (EigenCompute)',
|
|
96
|
+
imageDigest: this._getImageDigest(),
|
|
97
|
+
kmsKeyFingerprint: this._getKMSFingerprint(),
|
|
98
|
+
nodeVersion: process.version,
|
|
99
|
+
uptimeSeconds: Math.floor(process.uptime()),
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hash config files to prove integrity.
|
|
106
|
+
* @param {string[]} filePaths - paths to config files
|
|
107
|
+
*/
|
|
108
|
+
computeConfigHash(filePaths) {
|
|
109
|
+
const contents = [];
|
|
110
|
+
const resolved = [];
|
|
111
|
+
for (const p of filePaths) {
|
|
112
|
+
try {
|
|
113
|
+
const data = fs.readFileSync(p, 'utf-8');
|
|
114
|
+
contents.push(data);
|
|
115
|
+
resolved.push(p);
|
|
116
|
+
} catch { /* skip missing files */ }
|
|
117
|
+
}
|
|
118
|
+
if (contents.length === 0) return { hash: null, files: [] };
|
|
119
|
+
const hash = 'sha256:' + crypto.createHash('sha256').update(contents.join('\n')).digest('hex');
|
|
120
|
+
return { hash, files: resolved };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_getKMSFingerprint() {
|
|
124
|
+
try {
|
|
125
|
+
const pem = fs.readFileSync('/usr/local/bin/kms-signing-public-key.pem', 'utf-8');
|
|
126
|
+
return 'sha256:' + crypto.createHash('sha256').update(pem.trim()).digest('hex');
|
|
127
|
+
} catch { return null; }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_getImageDigest() {
|
|
131
|
+
if (process.env.IMAGE_DIGEST) return process.env.IMAGE_DIGEST;
|
|
132
|
+
if (process.env.HOSTNAME) {
|
|
133
|
+
try {
|
|
134
|
+
const cgroup = fs.readFileSync('/proc/self/cgroup', 'utf-8');
|
|
135
|
+
const match = cgroup.match(/docker[/-]([a-f0-9]{64})/);
|
|
136
|
+
if (match) return 'container:' + match[1].substring(0, 12);
|
|
137
|
+
} catch { /* not in docker */ }
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─── Health ───────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
healthCheck() {
|
|
145
|
+
return {
|
|
146
|
+
cliInstalled: this.cliAvailable,
|
|
147
|
+
insideTEE: !!this._getKMSFingerprint(),
|
|
148
|
+
hasAppId: !!process.env.ECLOUD_APP_ID,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = EigenCompute;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eigen-da
|
|
3
|
+
description: "Store and retrieve data blobs on EigenDA (Data Availability layer) — disperse blobs, retrieve by commitment, check status via proxy or direct API"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
metadata:
|
|
6
|
+
emoji: "📦"
|
|
7
|
+
tags: ["eigenlayer", "eigenda", "data-availability", "blobs", "storage"]
|
|
8
|
+
user-invocable: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# EigenDA Skill
|
|
12
|
+
|
|
13
|
+
Store and retrieve data blobs on EigenDA — EigenLayer's Data Availability layer. EigenDA provides high-throughput, low-cost data availability for rollups and applications.
|
|
14
|
+
|
|
15
|
+
## What is EigenDA?
|
|
16
|
+
|
|
17
|
+
EigenDA is a **data availability (DA) service** built on EigenLayer. It lets you:
|
|
18
|
+
- **Store blobs** — submit arbitrary data, get back a cryptographic commitment
|
|
19
|
+
- **Retrieve blobs** — fetch data using the commitment hash
|
|
20
|
+
- **Verify** — KZG commitments prove data integrity
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Your App → EigenDA Proxy (localhost:3100) → EigenDA Disperser → EigenDA Operators
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The **EigenDA Proxy** is a local REST server that handles encoding, KZG verification, and communication with the EigenDA disperser network.
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
Run the EigenDA Proxy locally via Docker:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
docker run -d \
|
|
36
|
+
--name eigenda-proxy \
|
|
37
|
+
-p 3100:3100 \
|
|
38
|
+
ghcr.io/layr-labs/eigenda-proxy:latest \
|
|
39
|
+
--eigenda.disperser-rpc=disperser-sepolia.eigenda.xyz:443 \
|
|
40
|
+
--eigenda.service-manager-addr=0xD4A7E1Bd8015057293f0D0A557088c286942e84b \
|
|
41
|
+
--eigenda.eth-rpc=YOUR_SEPOLIA_RPC_URL \
|
|
42
|
+
--eigenda.status-query-timeout=45s \
|
|
43
|
+
--eigenda.signer-private-key-hex=YOUR_PRIVATE_KEY \
|
|
44
|
+
--memstore.enabled=false \
|
|
45
|
+
--eigenda.disable-tls=false
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## When to use this skill
|
|
49
|
+
|
|
50
|
+
Use when the user asks about:
|
|
51
|
+
- Storing data on EigenDA
|
|
52
|
+
- Retrieving data from EigenDA by commitment
|
|
53
|
+
- Data availability for rollups
|
|
54
|
+
- Blob storage and retrieval
|
|
55
|
+
- EigenDA proxy setup
|
|
56
|
+
- Verifying data commitments
|
|
57
|
+
- EigenDA health/status checks
|
|
58
|
+
|
|
59
|
+
## How to use
|
|
60
|
+
|
|
61
|
+
### Store a blob via proxy
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
curl -s -X POST "http://127.0.0.1:3100/put?commitment_mode=standard" \
|
|
65
|
+
-H "Content-Type: application/json" \
|
|
66
|
+
-d '{"key": "value", "timestamp": "2025-01-01T00:00:00Z"}'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Returns: a hex commitment string (the blob's address on EigenDA).
|
|
70
|
+
|
|
71
|
+
### Retrieve a blob via proxy
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
curl -s "http://127.0.0.1:3100/get/COMMITMENT_HASH?commitment_mode=standard"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Returns: the original JSON data.
|
|
78
|
+
|
|
79
|
+
### Health check
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
curl -s "http://127.0.0.1:3100/health"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### View blob on explorer
|
|
86
|
+
|
|
87
|
+
After storing, view the blob at:
|
|
88
|
+
```
|
|
89
|
+
https://blobs-sepolia.eigenda.xyz/blobs/COMMITMENT_HASH
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## EigenDA v2 Disperser API (Direct)
|
|
93
|
+
|
|
94
|
+
For direct access without the proxy (advanced):
|
|
95
|
+
|
|
96
|
+
### Disperse a blob
|
|
97
|
+
The v2 disperser uses gRPC at `disperser-sepolia.eigenda.xyz:443`:
|
|
98
|
+
- `DisperseBlob()` — enqueue a blob for dispersal
|
|
99
|
+
- `GetBlobStatus()` — poll dispersal status
|
|
100
|
+
- `GetBlobCommitment()` — get the KZG commitment
|
|
101
|
+
|
|
102
|
+
### Retrieve from relay
|
|
103
|
+
The v2 Relay API provides:
|
|
104
|
+
- `GetBlob(blob_key)` — retrieve blob from relay nodes
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
| Env Var | Default | Description |
|
|
109
|
+
|---------|---------|-------------|
|
|
110
|
+
| `EIGENDA_PROXY_URL` | `http://127.0.0.1:3100` | Proxy address |
|
|
111
|
+
| `EIGENDA_COMMITMENT_MODE` | `standard` | `standard` or `optimistic` |
|
|
112
|
+
| `EIGENDA_TIMEOUT` | `60000` | Timeout in ms |
|
|
113
|
+
|
|
114
|
+
### Commitment modes
|
|
115
|
+
|
|
116
|
+
- **standard** — full KZG commitment, highest security
|
|
117
|
+
- **optimistic** — faster but with weaker guarantees
|
|
118
|
+
|
|
119
|
+
## Networks
|
|
120
|
+
|
|
121
|
+
| Network | Disperser RPC | Service Manager |
|
|
122
|
+
|---------|---------------|-----------------|
|
|
123
|
+
| Sepolia | `disperser-sepolia.eigenda.xyz:443` | `0xD4A7E1Bd8015057293f0D0A557088c286942e84b` |
|
|
124
|
+
| Mainnet | `disperser.eigenda.xyz:443` | Check EigenLayer docs |
|
|
125
|
+
|
|
126
|
+
## Programmatic Usage
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const EigenDA = require('eigen-skills/skills/eigen-da/scripts/da-api');
|
|
130
|
+
|
|
131
|
+
const da = new EigenDA({
|
|
132
|
+
proxyUrl: 'http://127.0.0.1:3100', // default
|
|
133
|
+
commitmentMode: 'standard', // default
|
|
134
|
+
timeout: 60000, // default
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Store data
|
|
138
|
+
const commitment = await da.store({ key: 'value', timestamp: new Date() });
|
|
139
|
+
console.log('Commitment:', commitment);
|
|
140
|
+
console.log('Explorer:', da.getExplorerUrl(commitment));
|
|
141
|
+
|
|
142
|
+
// Retrieve data
|
|
143
|
+
const data = await da.retrieve(commitment);
|
|
144
|
+
console.log('Retrieved:', data);
|
|
145
|
+
|
|
146
|
+
// Health check
|
|
147
|
+
const healthy = await da.healthCheck();
|
|
148
|
+
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EigenDA API Client — Blob Storage via Local Proxy
|
|
3
|
+
*
|
|
4
|
+
* Stores and retrieves data blobs on EigenDA through the local proxy.
|
|
5
|
+
* Proxy handles KZG commitments, encoding, and disperser communication.
|
|
6
|
+
*
|
|
7
|
+
* Proxy endpoints:
|
|
8
|
+
* POST /put?commitment_mode=standard → store blob, get commitment hash
|
|
9
|
+
* GET /get/<commitment>?commitment_mode=standard → retrieve stored blob
|
|
10
|
+
* GET /health → proxy health check
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const axios = require('axios');
|
|
14
|
+
|
|
15
|
+
class EigenDA {
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} opts
|
|
18
|
+
* @param {string} [opts.proxyUrl='http://127.0.0.1:3100']
|
|
19
|
+
* @param {string} [opts.commitmentMode='standard'] - 'standard' | 'optimistic'
|
|
20
|
+
* @param {number} [opts.timeout=60000]
|
|
21
|
+
*/
|
|
22
|
+
constructor(opts = {}) {
|
|
23
|
+
this.proxyUrl = opts.proxyUrl || process.env.EIGENDA_PROXY_URL || 'http://127.0.0.1:3100';
|
|
24
|
+
this.commitmentMode = opts.commitmentMode || process.env.EIGENDA_COMMITMENT_MODE || 'standard';
|
|
25
|
+
this.timeout = opts.timeout || parseInt(process.env.EIGENDA_TIMEOUT || '60000', 10);
|
|
26
|
+
|
|
27
|
+
this.client = axios.create({
|
|
28
|
+
baseURL: this.proxyUrl,
|
|
29
|
+
timeout: this.timeout,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Store a JSON payload on EigenDA.
|
|
35
|
+
* @param {object} data - the data to store
|
|
36
|
+
* @returns {Promise<string>} commitment hash
|
|
37
|
+
*/
|
|
38
|
+
async store(data) {
|
|
39
|
+
const payload = JSON.stringify(data);
|
|
40
|
+
console.log(`[EigenDA] Storing blob (${payload.length} bytes)...`);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.client.post(
|
|
44
|
+
`/put?commitment_mode=${this.commitmentMode}`,
|
|
45
|
+
payload,
|
|
46
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const commitment = response.data;
|
|
50
|
+
if (!commitment || typeof commitment !== 'string') {
|
|
51
|
+
throw new Error(`Invalid commitment: ${JSON.stringify(response.data)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`[EigenDA] ✅ Stored. Commitment: ${commitment.substring(0, 40)}...`);
|
|
55
|
+
return commitment;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const msg = this._formatError(error);
|
|
58
|
+
console.error(`[EigenDA] ❌ Store failed: ${msg}`);
|
|
59
|
+
throw new Error(`EigenDA store failed: ${msg}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Retrieve a blob from EigenDA by commitment.
|
|
65
|
+
* @param {string} commitment
|
|
66
|
+
* @returns {Promise<object>} parsed JSON
|
|
67
|
+
*/
|
|
68
|
+
async retrieve(commitment) {
|
|
69
|
+
console.log(`[EigenDA] Retrieving blob: ${commitment.substring(0, 40)}...`);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await this.client.get(
|
|
73
|
+
`/get/${commitment}?commitment_mode=${this.commitmentMode}`
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const payload = response.data;
|
|
77
|
+
if (typeof payload === 'string') {
|
|
78
|
+
return JSON.parse(payload);
|
|
79
|
+
}
|
|
80
|
+
return payload;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const msg = this._formatError(error);
|
|
83
|
+
console.error(`[EigenDA] ❌ Retrieve failed: ${msg}`);
|
|
84
|
+
throw new Error(`EigenDA retrieve failed: ${msg}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the EigenDA blob explorer URL for a commitment.
|
|
90
|
+
* @param {string} commitment
|
|
91
|
+
* @param {string} [network='sepolia']
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
getExplorerUrl(commitment, network = 'sepolia') {
|
|
95
|
+
const base = network === 'mainnet'
|
|
96
|
+
? 'https://blobs.eigenda.xyz/blobs'
|
|
97
|
+
: 'https://blobs-sepolia.eigenda.xyz/blobs';
|
|
98
|
+
return `${base}/${commitment}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Health check — ping the proxy.
|
|
103
|
+
* @returns {Promise<boolean>}
|
|
104
|
+
*/
|
|
105
|
+
async healthCheck() {
|
|
106
|
+
try {
|
|
107
|
+
const response = await this.client.get('/health', {
|
|
108
|
+
validateStatus: () => true,
|
|
109
|
+
timeout: 5000,
|
|
110
|
+
});
|
|
111
|
+
return response.status === 200 || response.status === 404;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_formatError(error) {
|
|
118
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
119
|
+
return `HTTP ${error.response.status}: ${typeof error.response.data === 'string'
|
|
120
|
+
? error.response.data
|
|
121
|
+
: JSON.stringify(error.response.data)
|
|
122
|
+
}`;
|
|
123
|
+
}
|
|
124
|
+
return error.message || String(error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = EigenDA;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eigen-delegation
|
|
3
|
+
description: "Query EigenLayer delegation data — delegation events, operator delegations, staker positions, operator-sets, and top delegated operators"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
metadata:
|
|
6
|
+
emoji: "🤝"
|
|
7
|
+
tags: ["eigenlayer", "delegation", "operators", "stakers", "operator-sets"]
|
|
8
|
+
user-invocable: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# EigenLayer Delegation Skill
|
|
12
|
+
|
|
13
|
+
Query live delegation data from EigenLayer: delegation/undelegation events, operator delegation profiles, staker delegation positions, operator-sets, and find the most delegated operators.
|
|
14
|
+
|
|
15
|
+
## Data Source
|
|
16
|
+
|
|
17
|
+
**EigenExplorer API** — `https://api.eigenexplorer.com`
|
|
18
|
+
- Auth: `x-api-token` header (free key at https://developer.eigenexplorer.com)
|
|
19
|
+
|
|
20
|
+
## When to use this skill
|
|
21
|
+
|
|
22
|
+
Use when the user asks about:
|
|
23
|
+
- Who is delegating to whom
|
|
24
|
+
- Delegation/undelegation events
|
|
25
|
+
- Top operators by delegation (most stakers, most TVL)
|
|
26
|
+
- A specific staker's delegation position
|
|
27
|
+
- Which operator a staker is delegated to
|
|
28
|
+
- Operator-sets
|
|
29
|
+
- Withdrawal queue or pending undelegations
|
|
30
|
+
|
|
31
|
+
## How to query
|
|
32
|
+
|
|
33
|
+
### Get top operators by total stakers (most delegated)
|
|
34
|
+
```bash
|
|
35
|
+
curl -s "https://api.eigenexplorer.com/operators?withTvl=true&sortByTotalStakers=desc&take=10" -H "x-api-token: $EIGEN_API_KEY"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Get top operators by TVL (most capital delegated)
|
|
39
|
+
```bash
|
|
40
|
+
curl -s "https://api.eigenexplorer.com/operators?withTvl=true&sortByTvl=desc&take=10" -H "x-api-token: $EIGEN_API_KEY"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Get a specific operator's delegation profile
|
|
44
|
+
```bash
|
|
45
|
+
curl -s "https://api.eigenexplorer.com/operators/0xOPERATOR_ADDRESS?withTvl=true" -H "x-api-token: $EIGEN_API_KEY"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Get stakers delegating to an operator
|
|
49
|
+
```bash
|
|
50
|
+
curl -s "https://api.eigenexplorer.com/operators/0xOPERATOR_ADDRESS/stakers?take=20" -H "x-api-token: $EIGEN_API_KEY"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Get which operator a staker has delegated to
|
|
54
|
+
```bash
|
|
55
|
+
curl -s "https://api.eigenexplorer.com/stakers/0xSTAKER_ADDRESS" -H "x-api-token: $EIGEN_API_KEY"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Get staker withdrawals (undelegations)
|
|
59
|
+
```bash
|
|
60
|
+
curl -s "https://api.eigenexplorer.com/stakers/0xSTAKER_ADDRESS/withdrawals?take=20" -H "x-api-token: $EIGEN_API_KEY"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Get delegation events
|
|
64
|
+
```bash
|
|
65
|
+
curl -s "https://api.eigenexplorer.com/events?take=20" -H "x-api-token: $EIGEN_API_KEY"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Get all operator-sets
|
|
69
|
+
```bash
|
|
70
|
+
curl -s "https://api.eigenexplorer.com/operator-sets?take=20" -H "x-api-token: $EIGEN_API_KEY"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Get operator-sets for a specific AVS
|
|
74
|
+
```bash
|
|
75
|
+
curl -s "https://api.eigenexplorer.com/avs/0xAVS_ADDRESS/operator-sets?take=20" -H "x-api-token: $EIGEN_API_KEY"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Response Format
|
|
79
|
+
|
|
80
|
+
Format results for the user with:
|
|
81
|
+
- **Bold operator names** and abbreviated addresses
|
|
82
|
+
- Total stakers count
|
|
83
|
+
- TVL in human-readable form
|
|
84
|
+
- Shares breakdown by strategy where relevant
|
|
85
|
+
- Active vs inactive AVS registrations
|
|
86
|
+
- Use bullet points, never tables in chat
|
|
87
|
+
|
|
88
|
+
## Programmatic Usage
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const DelegationAPI = require('eigen-agent-skills/skills/eigen-delegation/scripts/delegation-api');
|
|
92
|
+
const api = new DelegationAPI('YOUR_API_KEY');
|
|
93
|
+
|
|
94
|
+
const topDelegated = await api.getTopDelegatedOperators(10);
|
|
95
|
+
const topByTVL = await api.getTopOperatorsByTVL(10);
|
|
96
|
+
const opProfile = await api.getOperatorDelegation('0x...');
|
|
97
|
+
const stakerPos = await api.getStakerDelegation('0x...');
|
|
98
|
+
```
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EigenLayer Delegation API Client
|
|
3
|
+
*
|
|
4
|
+
* Data source: EigenExplorer REST API
|
|
5
|
+
* Covers: delegation events, operator-sets, staker ↔ operator delegation tracking
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const axios = require('axios');
|
|
9
|
+
|
|
10
|
+
const MAINNET = 'https://api.eigenexplorer.com';
|
|
11
|
+
const HOLESKY = 'https://api-holesky.eigenexplorer.com';
|
|
12
|
+
|
|
13
|
+
class DelegationAPI {
|
|
14
|
+
constructor(apiKey, { network = 'mainnet' } = {}) {
|
|
15
|
+
if (!apiKey) throw new Error('EigenExplorer API key required. Get one free at https://developer.eigenexplorer.com');
|
|
16
|
+
this.client = axios.create({
|
|
17
|
+
baseURL: network === 'holesky' ? HOLESKY : MAINNET,
|
|
18
|
+
headers: {
|
|
19
|
+
'x-api-token': apiKey,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Delegation Events ────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get recent delegation/undelegation events across the protocol.
|
|
30
|
+
* @param {object} opts
|
|
31
|
+
* @param {string} [opts.type] - 'DELEGATION' | 'UNDELEGATION'
|
|
32
|
+
* @param {number} [opts.skip=0]
|
|
33
|
+
* @param {number} [opts.take=12]
|
|
34
|
+
*/
|
|
35
|
+
async getDelegationEvents(opts = {}) {
|
|
36
|
+
const params = {
|
|
37
|
+
skip: opts.skip || 0,
|
|
38
|
+
take: opts.take || 12,
|
|
39
|
+
};
|
|
40
|
+
if (opts.type) params.type = opts.type;
|
|
41
|
+
|
|
42
|
+
const { data } = await this.client.get('/events', { params });
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Operator Delegation Details ──────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get an operator's full delegation profile (stakers, shares, strategies, TVL).
|
|
50
|
+
* @param {string} operatorAddress
|
|
51
|
+
*/
|
|
52
|
+
async getOperatorDelegation(operatorAddress) {
|
|
53
|
+
const { data } = await this.client.get(`/operators/${operatorAddress}`, {
|
|
54
|
+
params: { withTvl: 'true' },
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
address: data.address,
|
|
58
|
+
name: data.metadataName,
|
|
59
|
+
totalStakers: data.totalStakers,
|
|
60
|
+
totalAvs: data.totalAvs,
|
|
61
|
+
shares: data.shares,
|
|
62
|
+
tvl: data.tvl,
|
|
63
|
+
avsRegistrations: data.avsRegistrations,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get all stakers delegating to a specific operator.
|
|
69
|
+
* @param {string} operatorAddress
|
|
70
|
+
* @param {object} opts
|
|
71
|
+
*/
|
|
72
|
+
async getOperatorStakers(operatorAddress, opts = {}) {
|
|
73
|
+
const { data } = await this.client.get(`/operators/${operatorAddress}/stakers`, {
|
|
74
|
+
params: { skip: opts.skip || 0, take: opts.take || 12 },
|
|
75
|
+
});
|
|
76
|
+
return data;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Staker Delegation ────────────────────────────────
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get which operator a staker has delegated to and their position details.
|
|
83
|
+
* @param {string} stakerAddress
|
|
84
|
+
*/
|
|
85
|
+
async getStakerDelegation(stakerAddress) {
|
|
86
|
+
const { data } = await this.client.get(`/stakers/${stakerAddress}`);
|
|
87
|
+
return data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get a staker's withdrawal history (partial/full undelegations).
|
|
92
|
+
* @param {string} stakerAddress
|
|
93
|
+
* @param {object} opts
|
|
94
|
+
*/
|
|
95
|
+
async getStakerWithdrawals(stakerAddress, opts = {}) {
|
|
96
|
+
const { data } = await this.client.get(`/stakers/${stakerAddress}/withdrawals`, {
|
|
97
|
+
params: { skip: opts.skip || 0, take: opts.take || 12 },
|
|
98
|
+
});
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── Operator-Sets ────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all operator-sets (the new slashing-era grouping model).
|
|
106
|
+
* @param {object} opts
|
|
107
|
+
*/
|
|
108
|
+
async getOperatorSets(opts = {}) {
|
|
109
|
+
const { data } = await this.client.get('/operator-sets', {
|
|
110
|
+
params: { skip: opts.skip || 0, take: opts.take || 12 },
|
|
111
|
+
});
|
|
112
|
+
return data;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get a specific operator-set by AVS address + operatorSetId.
|
|
117
|
+
* @param {string} avsAddress
|
|
118
|
+
* @param {string} operatorSetId
|
|
119
|
+
*/
|
|
120
|
+
async getOperatorSet(avsAddress, operatorSetId) {
|
|
121
|
+
const { data } = await this.client.get(`/operator-sets/${avsAddress}/${operatorSetId}`);
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Top Delegated Operators ──────────────────────────
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get operators sorted by total stakers (most delegated first).
|
|
129
|
+
* @param {number} [limit=10]
|
|
130
|
+
*/
|
|
131
|
+
async getTopDelegatedOperators(limit = 10) {
|
|
132
|
+
const { data } = await this.client.get('/operators', {
|
|
133
|
+
params: {
|
|
134
|
+
withTvl: 'true',
|
|
135
|
+
sortByTotalStakers: 'desc',
|
|
136
|
+
take: limit,
|
|
137
|
+
skip: 0,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
return data;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get operators sorted by TVL (most capital delegated first).
|
|
145
|
+
* @param {number} [limit=10]
|
|
146
|
+
*/
|
|
147
|
+
async getTopOperatorsByTVL(limit = 10) {
|
|
148
|
+
const { data } = await this.client.get('/operators', {
|
|
149
|
+
params: {
|
|
150
|
+
withTvl: 'true',
|
|
151
|
+
sortByTvl: 'desc',
|
|
152
|
+
take: limit,
|
|
153
|
+
skip: 0,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = DelegationAPI;
|