Skip to content

Commit 2952893

Browse files
committed
Demo plugin for EigenLayer CLI for AVS registrations
Signed-off-by: Tharindu Dissanayake <[email protected]>
1 parent 9f917fb commit 2952893

File tree

9 files changed

+336
-1
lines changed

9 files changed

+336
-1
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
APP_NAME=eigenlayer-cli-plugin

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib/

Makefile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.PHONY: help build tests fmt format-lines lint
2+
3+
include .env
4+
5+
GO_LINES_IGNORED_DIRS=
6+
GO_PACKAGES=./main/...
7+
GO_FOLDERS=$(shell echo ${GO_PACKAGES} | sed -e "s/\.\///g" | sed -e "s/\/\.\.\.//g")
8+
9+
help:
10+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
11+
12+
build: ## Compile the binary
13+
@mkdir -p bin
14+
@go build -buildmode=plugin -o lib/$(APP_NAME).so $(GO_PACKAGES)
15+
16+
tests: ## runs all tests
17+
go test ./... -covermode=atomic
18+
19+
fmt: ## formats all go files
20+
go fmt ./...
21+
make format-lines
22+
23+
format-lines: ## formats all go files with golines
24+
go install github.com/segmentio/golines@latest
25+
golines -w -m 120 --ignore-generated --shorten-comments --ignored-dirs=${GO_LINES_IGNORED_DIRS} ${GO_FOLDERS}
26+
27+
lint: ## runs all linters
28+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
29+
golangci-lint run ./...

README.md

Lines changed: 210 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,210 @@
1-
# eigen-cli-plugin
1+
# EigenLayer CLI Plugin
2+
3+
This repository contains a demo plugin for EigenLayer CLI for custom off-chain code execution for AVS registration flows.
4+
5+
## Creating a Minimal Plugin
6+
7+
A minimal plugin for EigenLayer CLI requires a shared library to be built that includes a symbol named `PluginCoordinator` that implement the following interface.
8+
9+
```go
10+
type Coordinator interface {
11+
Type() string
12+
Register() error
13+
OptIn() error
14+
OptOut() error
15+
Deregister() error
16+
Status() (string, error)
17+
}
18+
```
19+
20+
In the above interface, the `Type()` function should return the type of the coordinator, which can be a non-empty string that identifies the coordinator implementation.
21+
22+
The `Register()`, `OptIn()`, `OptOut()` and `Deregister()` functions are invoked when the plugin is to execute the logic for the corresponding AVS registration workflows.
23+
24+
The `Status()` function is called to query the registration status of an operator for an AVS.
25+
26+
### Create the Project
27+
28+
Create a new directory to hold the project (following example uses `eigenlayer-cli-plugin-demo`).
29+
30+
```bash
31+
$ mkdir eigenlayer-plugin-demo
32+
$ cd eigenlayer-plugin-demo
33+
```
34+
35+
Initialize the project as a module (following example uses `github.com/eigenlayer/eigenlayer-cli-demo`).
36+
37+
```bash
38+
$ go mod init github.com/eigenlayer/eigenlayer-cli-demo
39+
```
40+
41+
### Add a Coordinator
42+
43+
Create the plugin coordinator implementation (following is added as `coordinator.go`).
44+
45+
```go
46+
package main
47+
48+
import (
49+
"fmt"
50+
)
51+
52+
type Coordinator struct {
53+
}
54+
55+
func (coordinator Coordinator) Type() string {
56+
return "eigenlayer-cli-demo"
57+
}
58+
59+
func (coordinator Coordinator) Register() error {
60+
fmt.Println("eigenlayer-cli-demo:register")
61+
return nil
62+
}
63+
64+
func (coordinator Coordinator) OptIn() error {
65+
fmt.Println("eigenlayer-cli-demo:opt-in")
66+
return nil
67+
}
68+
69+
func (coordinator Coordinator) OptOut() error {
70+
fmt.Println("eigenlayer-cli-demo:opt-out")
71+
return nil
72+
}
73+
74+
func (coordinator Coordinator) Deregister() error {
75+
fmt.Println("eigenlayer-cli-demo:deregister")
76+
return nil
77+
}
78+
79+
func (coordinator Coordinator) Status() (string, error) {
80+
fmt.Println("eigenlayer-cli-demo:status")
81+
return "", nil
82+
}
83+
84+
var PluginCoordinator Coordinator
85+
```
86+
87+
### Build the Plugin
88+
89+
Build the project as a plugin (following example build `eigenlayer-cli-demo.so`).
90+
91+
```bash
92+
$ go build -buildmode=plugin -o eigenlayer-cli-demo.so
93+
```
94+
95+
### Host the Plugin
96+
97+
Host the plugin shared library so that it can be accessible from a public URL.
98+
99+
### Use it in a Specification
100+
101+
The plugin can be used in a specification by setting the specification's `coordinator` and `library_url` attributes.
102+
103+
The following example shows an `avs.json` of a specification that uses a plugin hosted at `https://download.eigenlayer.xys/cli/eigenlayer-cli-demo.so`.
104+
105+
```json
106+
{
107+
"name": "eigenlayer-cli-demo",
108+
"network": "mainnet",
109+
"contract_address": "0x870679e138bcdf293b7ff14dd44b70fc97e12fc0",
110+
"coordinator": "plugin",
111+
"remote_signing": false,
112+
"library_url": "https://download.eigenlayer.xys/cli/eigenlayer-cli-demo.so"
113+
}
114+
```
115+
116+
## Accessing the Specification
117+
118+
The plugin can access the specification used for launching the coordinator by including a symbol named `PluginSpecification` that implements the following interface.
119+
120+
```go
121+
type Specification interface {
122+
Type() string
123+
Validate() error
124+
}
125+
```
126+
127+
The `Type()` function should return the type of the specification implementation. The `Validate()` function is called after loading the specification in order to check its validity.
128+
129+
Note that this plugin specification can also include custom properties included in the corresponding `avs.json` file.
130+
131+
For example, consider the following `avs.json`.
132+
133+
```json
134+
{
135+
"name": "eigenlayer-cli-demo",
136+
"description": "Specification for CLI Plugin Demo",
137+
"network": "mainnet",
138+
"contract_address": "0x870679e138bcdf293b7ff14dd44b70fc97e12fc0",
139+
"coordinator": "plugin",
140+
"remote_signing": false,
141+
"library_url": "https://download.eigenlayer.xys/cli/eigenlayer-cli-demo.so",
142+
"foo": "bar"
143+
}
144+
```
145+
146+
The plugin can access the specification as follows.
147+
148+
```go
149+
package main
150+
151+
import (
152+
"errors"
153+
)
154+
155+
type Specification struct {
156+
Name string `json:"name"`
157+
Description string `json:"description"`
158+
Network string `json:"network"`
159+
ContractAddress string `json:"contract_address"`
160+
Coordinator string `json:"coordinator"`
161+
RemoteSigning bool `json:"remote_signing"`
162+
LibraryURL string `json:"library_url"`
163+
Foo string `json:"foo"`
164+
}
165+
166+
func (spec Specification) Type() string {
167+
return spec.Coordinator
168+
}
169+
170+
func (spec Specification) Validate() error {
171+
if spec.Foo == "" {
172+
return errors.New("specification: foo is required")
173+
}
174+
175+
return nil
176+
}
177+
178+
var PluginSpecification Specification
179+
```
180+
181+
## Accessing Configuration Parameters
182+
183+
The plugin can access the configuration parameters used when workflows are invoked by including a symbol named `PluginConfiguration` that implements the following interface.
184+
185+
```go
186+
type Configuration interface {
187+
Get(key string) interface{}
188+
Set(key string, value interface{})
189+
}
190+
```
191+
192+
The following is a full example.
193+
194+
```go
195+
package main
196+
197+
type Configuration struct {
198+
registry map[string]interface{}
199+
}
200+
201+
func (config Configuration) Get(key string) interface{} {
202+
return config.registry[key]
203+
}
204+
205+
func (config Configuration) Set(key string, value interface{}) {
206+
config.registry[key] = value
207+
}
208+
209+
var PluginConfiguration Configuration
210+
```

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/tharindud/eigenlayer-cli-plugin
2+
3+
go 1.22.2

main/configuration.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
type Configuration struct {
4+
registry map[string]interface{}
5+
}
6+
7+
func (config Configuration) Get(key string) interface{} {
8+
return config.registry[key]
9+
}
10+
11+
func (config Configuration) Set(key string, value interface{}) {
12+
config.registry[key] = value
13+
}
14+
15+
var PluginConfiguration Configuration

main/coordinator.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type Coordinator struct {
8+
}
9+
10+
func (coordinator Coordinator) Type() string {
11+
return "eigenlayer-cli-plugin"
12+
}
13+
14+
func (coordinator Coordinator) Register() error {
15+
fmt.Println("eigenlayer-cli-plugin:register")
16+
fmt.Printf("specification:name=%s\n", PluginSpecification.Name)
17+
fmt.Printf("specification:foo=%s\n", PluginSpecification.Foo)
18+
fmt.Printf("%+v", PluginConfiguration)
19+
return nil
20+
}
21+
22+
func (coordinator Coordinator) OptIn() error {
23+
fmt.Println("eigenlayer-cli-plugin:opt-in")
24+
return nil
25+
}
26+
27+
func (coordinator Coordinator) OptOut() error {
28+
fmt.Println("eigenlayer-cli-plugin:opt-out")
29+
return nil
30+
}
31+
32+
func (coordinator Coordinator) Deregister() error {
33+
fmt.Println("eigenlayer-cli-plugin:deregister")
34+
return nil
35+
}
36+
37+
func (coordinator Coordinator) Status() (string, error) {
38+
fmt.Println("eigenlayer-cli-plugin:status")
39+
return "", nil
40+
}
41+
42+
var PluginCoordinator Coordinator

main/plugin.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package main
2+
3+
func init() {
4+
PluginConfiguration.registry = make(map[string]interface{})
5+
}

main/specification.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
)
6+
7+
type Specification struct {
8+
Name string `json:"name"`
9+
Description string `json:"description"`
10+
Network string `json:"network"`
11+
ContractAddress string `json:"contract_address"`
12+
Coordinator string `json:"coordinator"`
13+
RemoteSigning bool `json:"remote_signing"`
14+
LibraryURL string `json:"library_url"`
15+
Foo string `json:"foo"`
16+
}
17+
18+
func (spec Specification) Type() string {
19+
return spec.Coordinator
20+
}
21+
22+
func (spec Specification) Validate() error {
23+
if spec.Foo == "" {
24+
return errors.New("specification: foo is required")
25+
}
26+
27+
return nil
28+
}
29+
30+
var PluginSpecification Specification

0 commit comments

Comments
 (0)