Skip to content

Commit fefb4b5

Browse files
add config creation (Layr-Labs#6)
* add config creation * update comment
1 parent 0425d5c commit fefb4b5

11 files changed

+383
-15
lines changed

.gitignore

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
# bin directory
2-
bin
2+
bin
3+
4+
# Config files
5+
metadata.json
6+
operator-config.yaml
7+
operator.yaml
8+
config/*

Makefile

+13-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
include .env
44

5+
GO_LINES_IGNORED_DIRS=
6+
GO_PACKAGES=./pkg/... ./cmd/...
7+
GO_FOLDERS=$(shell echo ${GO_PACKAGES} | sed -e "s/\.\///g" | sed -e "s/\/\.\.\.//g")
8+
59
help:
610
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
711

@@ -14,4 +18,12 @@ mocks: ## generates mocks
1418
go generate ./...
1519

1620
tests: ## runs all tests
17-
go test ./... -covermode=atomic
21+
go test ./... -covermode=atomic
22+
23+
fmt: ## formats all go files
24+
go fmt ./...
25+
make format-lines
26+
27+
format-lines: ## formats all go files with golines
28+
go install github.com/segmentio/golines@latest
29+
golines -w -m 120 --ignore-generated --shorten-comments --ignored-dirs=${GO_LINES_IGNORED_DIRS} ${GO_FOLDERS}

pkg/operator.go

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func OperatorCmd(p utils.Prompter) *cli.Command {
1212
Usage: "Execute onchain operations for the operator",
1313
Subcommands: []*cli.Command{
1414
operator.KeysCmd(p),
15+
operator.ConfigCmd(p),
1516
},
1617
}
1718

pkg/operator/config.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package operator
2+
3+
import (
4+
"github.com/Layr-Labs/eigenlayer-cli/pkg/operator/config"
5+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
6+
"github.com/urfave/cli/v2"
7+
)
8+
9+
func ConfigCmd(p utils.Prompter) *cli.Command {
10+
var configCmd = &cli.Command{
11+
Name: "config",
12+
Usage: "Manage the operator's config",
13+
Subcommands: []*cli.Command{
14+
config.CreateCmd(p),
15+
},
16+
}
17+
18+
return configCmd
19+
20+
}

pkg/operator/config/create.go

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"math/big"
8+
"os"
9+
10+
"github.com/Layr-Labs/eigenlayer-cli/pkg/types"
11+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
12+
eigensdkTypes "github.com/Layr-Labs/eigensdk-go/types"
13+
eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils"
14+
"github.com/urfave/cli/v2"
15+
"gopkg.in/yaml.v3"
16+
)
17+
18+
func CreateCmd(p utils.Prompter) *cli.Command {
19+
createCmd := &cli.Command{
20+
Name: "create",
21+
Usage: "Used to create operator config and metadata json sample file",
22+
UsageText: "create",
23+
Description: `
24+
This command is used to create a sample empty operator config file
25+
and also an empty metadata json file which you need to upload for
26+
operator metadata
27+
28+
Both of these are needed for operator registration
29+
`,
30+
Action: func(ctx *cli.Context) error {
31+
op := types.OperatorConfig{}
32+
33+
// Prompt user to generate empty or non-empty files
34+
populate, err := p.Confirm("Would you like to populate the operator config file?")
35+
if err != nil {
36+
return err
37+
}
38+
39+
if populate {
40+
op, err = promptOperatorInfo(&op, p)
41+
if err != nil {
42+
return err
43+
}
44+
}
45+
46+
yamlData, err := yaml.Marshal(&op)
47+
if err != nil {
48+
return err
49+
}
50+
operatorFile := "operator.yaml"
51+
err = os.WriteFile(operatorFile, yamlData, 0o644)
52+
if err != nil {
53+
return err
54+
}
55+
56+
metadata := eigensdkTypes.OperatorMetadata{}
57+
jsonData, err := json.MarshalIndent(metadata, "", " ")
58+
if err != nil {
59+
return err
60+
}
61+
62+
metadataFile := "metadata.json"
63+
err = os.WriteFile(metadataFile, jsonData, 0o644)
64+
if err != nil {
65+
return err
66+
}
67+
68+
fmt.Println(
69+
"Created operator.yaml and metadata.json files. Please fill in the smart contract configuration details(el_slasher_address and bls_public_key_compendium_address) provided by EigenLayer team. ",
70+
)
71+
fmt.Println(
72+
"Please fill in the metadata.json file and upload it to a public url. Then update the operator.yaml file with the url (metadata_url).",
73+
)
74+
fmt.Println(
75+
"Once you have filled in the operator.yaml file, you can register your operator using the configuration file.",
76+
)
77+
return nil
78+
},
79+
}
80+
81+
return createCmd
82+
}
83+
84+
func promptOperatorInfo(config *types.OperatorConfig, p utils.Prompter) (types.OperatorConfig, error) {
85+
// Prompt and set operator address
86+
operatorAddress, err := p.InputString("Enter your operator address:", "", "",
87+
func(s string) error {
88+
return validateAddressIsNonZeroAndValid(s)
89+
},
90+
)
91+
if err != nil {
92+
return types.OperatorConfig{}, err
93+
}
94+
config.Operator.Address = operatorAddress
95+
96+
// TODO(madhur): Disabling this for now as the feature doesn't work but
97+
// we need to keep the code around for future
98+
// Prompt to gate stakers approval
99+
//gateApproval, err := p.Confirm("Do you want to gate stakers approval?")
100+
//if err != nil {
101+
// return types.OperatorConfig{}, err
102+
//}
103+
// Prompt for address if operator wants to gate approvals
104+
//if gateApproval {
105+
// delegationApprover, err := p.InputString("Enter your staker approver address:", "", "",
106+
// func(s string) error {
107+
// isValidAddress := eigenSdkUtils.IsValidEthereumAddress(s)
108+
//
109+
// if !isValidAddress {
110+
// return errors.New("address is invalid")
111+
// }
112+
//
113+
// return nil
114+
// },
115+
// )
116+
// if err != nil {
117+
// return types.OperatorConfig{}, err
118+
// }
119+
// config.Operator.DelegationApproverAddress = delegationApprover
120+
//} else {
121+
// config.Operator.DelegationApproverAddress = eigensdkTypes.ZeroAddress
122+
//}
123+
124+
// TODO(madhur): Remove this once we have the feature working and want to prompt users for this address
125+
config.Operator.DelegationApproverAddress = eigensdkTypes.ZeroAddress
126+
127+
// Prompt and set earnings address
128+
earningsAddress, err := p.InputString(
129+
"Enter your earnings address (default to your operator address):",
130+
config.Operator.Address,
131+
"",
132+
func(s string) error {
133+
return validateAddressIsNonZeroAndValid(s)
134+
},
135+
)
136+
if err != nil {
137+
return types.OperatorConfig{}, err
138+
}
139+
config.Operator.EarningsReceiverAddress = earningsAddress
140+
141+
// Prompt for eth node
142+
rpcUrl, err := p.InputString("Enter your ETH rpc url:", "http://localhost:8545", "",
143+
func(s string) error { return nil },
144+
)
145+
if err != nil {
146+
return types.OperatorConfig{}, err
147+
}
148+
config.EthRPCUrl = rpcUrl
149+
150+
// Prompt for ecdsa key path
151+
ecdsaKeyPath, err := p.InputString("Enter your ecdsa key path:", "", "",
152+
func(s string) error { return nil },
153+
)
154+
if err != nil {
155+
return types.OperatorConfig{}, err
156+
}
157+
config.PrivateKeyStorePath = ecdsaKeyPath
158+
159+
// Prompt for bls key path
160+
blsKeyPath, err := p.InputString("Enter your bls key path:", "", "",
161+
func(s string) error { return nil },
162+
)
163+
if err != nil {
164+
return types.OperatorConfig{}, err
165+
}
166+
config.BlsPrivateKeyStorePath = blsKeyPath
167+
168+
// Prompt for network & set chainId
169+
chainId, err := p.Select("Select your network:", []string{"mainnet", "goerli", "holesky", "local"})
170+
if err != nil {
171+
return types.OperatorConfig{}, err
172+
}
173+
174+
switch chainId {
175+
case "mainnet":
176+
config.ChainId = *big.NewInt(1)
177+
case "goerli":
178+
config.ChainId = *big.NewInt(5)
179+
case "holesky":
180+
config.ChainId = *big.NewInt(17000)
181+
case "local":
182+
config.ChainId = *big.NewInt(31337)
183+
}
184+
185+
config.SignerType = types.LocalKeystoreSigner
186+
187+
return *config, nil
188+
}
189+
190+
func validateAddressIsNonZeroAndValid(address string) error {
191+
if address == eigensdkTypes.ZeroAddress {
192+
return errors.New("address is 0")
193+
}
194+
195+
addressIsValid := eigenSdkUtils.IsValidEthereumAddress(address)
196+
197+
if !addressIsValid {
198+
return errors.New("address is invalid")
199+
}
200+
201+
return nil
202+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "Some operator",
3+
"website": "https://www.example.com",
4+
"description": "I operate on some data",
5+
"logo": "https://www.example.com/logo.png",
6+
"twitter": "https://x.com/example"
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# All the below fields are required for successful operator registration.
2+
3+
# To learn more about operator fields
4+
# https://github.com/Layr-Labs/eigenlayer-contracts/blob/92ccacc868785350973afc15e90a18dcd39fbc0b/src/contracts/interfaces/IDelegationManager.sol#L21:
5+
operator:
6+
# This is the standard Ethereum address format (ex: 0x6a8c0D554a694899041E52a91B4EC3Ff23d8aBD5) of your operator
7+
# which is the ecdsa key you created or imported using EigenLayer CLI
8+
address:
9+
# This is the standard Ethereum address format (ex: 0x6a8c0D554a694899041E52a91B4EC3Ff23d8aBD5)
10+
# This is the address where your operator will receive earnings. This could be same as operator address
11+
earnings_receiver_address:
12+
# This is the standard Ethereum address format (0x...)
13+
# This is the address which operator will use to approve delegation requests from stakers.
14+
# if set, this address must sign and approve new delegation from Stakers to this Operator
15+
# For now, you can leave it with the default value for un-gated delegation requests
16+
# Once we enable gated delegation requests, you can update this field with the address of the approver
17+
delegation_approver_address: 0x0000000000000000000000000000000000000000
18+
# Please refer to this link for more details on this field https://github.com/Layr-Labs/eigenlayer-contracts/blob/92ccacc868785350973afc15e90a18dcd39fbc0b/src/contracts/interfaces/IDelegationManager.sol#L33:
19+
# Please keep this field to 0, and it can be updated later using EigenLayer CLI
20+
staker_opt_out_window_blocks: 0
21+
metadata_url: https://example.com/metadata.json
22+
23+
# EigenLayer Slasher contract address
24+
# This will be provided by EigenLayer team
25+
el_slasher_address:
26+
27+
# Address of BLS Public Key Compendium contract
28+
# This will be provided by EigenLayer team
29+
bls_public_key_compendium_address:
30+
31+
# ETH RPC URL to the ethereum node you are using for on-chain operations
32+
eth_rpc_url: http://localhost:8545
33+
34+
# Signer Type to use
35+
# Supported values: local_keystore
36+
signer_type: local_keystore
37+
38+
# Full path to local ecdsa private key store file
39+
private_key_store_path: <path-to>/test.ecdsa.key.json
40+
41+
# Full path to local bls private key store file
42+
bls_private_key_store_path: <path-to>/test.bls.key.json
43+
44+
# Chain ID: 1 for mainnet, 5 for Goerli, 17000 for holesky, 31337 for local
45+
chain_id: 5

pkg/operator/keys/create.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ func checkIfKeyExists(fileLoc string) bool {
186186
func validatePassword(password string) error {
187187
err := passwordvalidator.Validate(password, MinEntropyBits)
188188
if err != nil {
189-
fmt.Println("if you want to create keys for testing with weak/no password, use --insecure flag. Do NOT use those keys in production")
189+
fmt.Println(
190+
"if you want to create keys for testing with weak/no password, use --insecure flag. Do NOT use those keys in production",
191+
)
190192
}
191193
return err
192194
}

0 commit comments

Comments
 (0)