Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ encrypted.secrets.json

# Output produced by e2e Anvil tests
test/test.yaml

.claude
11 changes: 10 additions & 1 deletion cmd/common/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCom
return func() ([]byte, error) {
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("%w\nbuild output:\n%s", err, strings.TrimSpace(string(out)))
outStr := strings.TrimSpace(string(out))
if strings.Contains(outStr, "Script not found") && strings.Contains(outStr, "cre-compile") {
return nil, fmt.Errorf("TypeScript compilation failed: 'cre-compile' command not found.\n\n" +
"The 'cre-compile' tool is provided by the @chainlink/cre-sdk package.\n\n" +
"To fix:\n" +
" • Run 'bun install' in your project to install dependencies\n" +
" • Update your project dependencies with 'cre update <workflow-folder>'\n" +
" • If starting fresh, use 'cre workflow init' to scaffold a properly configured workflow")
}
return nil, fmt.Errorf("%w\nbuild output:\n%s", err, outStr)
}
b, err := os.ReadFile(tmpPath)
_ = os.Remove(tmpPath)
Expand Down
58 changes: 29 additions & 29 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,35 +451,35 @@ func newRootCommand() *cobra.Command {
func isLoadSettings(cmd *cobra.Command) bool {
// It is not expected to have the settings file when running the following commands
var excludedCommands = map[string]struct{}{
"cre version": {},
"cre login": {},
"cre logout": {},
"cre whoami": {},
"cre account access": {},
"cre account list-key": {},
"cre init": {},
"cre generate-bindings": {},
"cre completion bash": {},
"cre completion fish": {},
"cre completion powershell": {},
"cre completion zsh": {},
"cre help": {},
"cre update": {},
"cre workflow": {},
"cre workflow custom-build": {},
"cre workflow limits": {},
"cre workflow limits export": {},
"cre workflow build": {},
"cre workflow list": {},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're missing cre workflow list during merge conflict resolution

"cre account": {},
"cre secrets": {},
"cre templates": {},
"cre templates list": {},
"cre templates add": {},
"cre templates remove": {},
"cre registry": {},
"cre registry list": {},
"cre": {},
"cre version": {},
"cre login": {},
"cre logout": {},
"cre whoami": {},
"cre account access": {},
"cre account list-key": {},
"cre init": {},
"cre generate-bindings": {},
"cre completion bash": {},
"cre completion fish": {},
"cre completion powershell": {},
"cre completion zsh": {},
"cre help": {},
"cre update": {},
"cre workflow": {},
"cre workflow supported-chains": {},
"cre workflow custom-build": {},
"cre workflow limits": {},
"cre workflow limits export": {},
"cre workflow build": {},
"cre account": {},
"cre secrets": {},
"cre templates": {},
"cre templates list": {},
"cre templates add": {},
"cre templates remove": {},
"cre registry": {},
"cre registry list": {},
"cre": {},
}

_, exists := excludedCommands[cmd.CommandPath()]
Expand Down
27 changes: 26 additions & 1 deletion cmd/workflow/simulate/chain/evm/chaintype.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,24 @@ func (ct *EVMChainType) RunHealthCheck(resolved chain.ResolvedChains) error {
func (ct *EVMChainType) ResolveKey(creSettings *settings.Settings, broadcast bool) (interface{}, error) {
pk, err := crypto.HexToECDSA(creSettings.User.EthPrivateKey)
if err != nil {
// If the user explicitly set a key that looks like a hex string but is
// malformed (wrong length, invalid chars), always error with guidance.
// Skip placeholder values like "your-eth-private-key" from the default .env template.
if creSettings.User.EthPrivateKey != "" && isHexString(creSettings.User.EthPrivateKey) {
return nil, fmt.Errorf(
"invalid private key: expected 64 hex characters (256 bits), got %d characters.\n\n"+
"The CLI reads CRE_ETH_PRIVATE_KEY from your .env file or system environment.\n"+
"The 0x prefix is supported and stripped automatically.\n\n"+
"Common issues:\n"+
" • Pasted an Ethereum address (40 chars) instead of a private key (64 chars)\n"+
" • Value has extra quotes — use CRE_ETH_PRIVATE_KEY=abc123... without wrapping quotes\n"+
" • Key was truncated during copy-paste",
len(creSettings.User.EthPrivateKey))
}
if broadcast {
return nil, fmt.Errorf(
"failed to parse private key, required to broadcast. Please check CRE_ETH_PRIVATE_KEY in your .env file or system environment: %w", err)
"a private key is required for --broadcast mode.\n" +
"Set CRE_ETH_PRIVATE_KEY in your .env file or system environment")
}
pk, err = crypto.HexToECDSA(defaultSentinelPrivateKey)
if err != nil {
Expand All @@ -239,6 +254,16 @@ func (ct *EVMChainType) ResolveKey(creSettings *settings.Settings, broadcast boo
return pk, nil
}

// isHexString returns true if s contains only hexadecimal characters (0-9, a-f, A-F).
func isHexString(s string) bool {
for _, c := range s {
if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
return false
}
}
return len(s) > 0
}

// CLI input keys consumed from chain.TriggerParams.ChainTypeInputs.
const (
TriggerInputTxHash = "evm-tx-hash"
Expand Down
14 changes: 7 additions & 7 deletions cmd/workflow/simulate/chain/evm/chaintype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,18 @@ func TestEVMChainType_ResolveKey(t *testing.T) {
checkD1: true,
},
{
name: "too-short key, non-broadcast, falls back + warns",
pk: "ab",
broadcast: false,
wantStderr: "Using default private key",
checkD1: true,
name: "too-short but valid-hex key, non-broadcast, invalid-length hard error",
pk: "ab",
broadcast: false,
wantErr: true,
errContains: "invalid private key: expected 64 hex characters",
},
{
name: "invalid hex, broadcast, hard error",
pk: "notahex",
broadcast: true,
wantErr: true,
errContains: "failed to parse private key, required to broadcast",
errContains: "a private key is required for --broadcast mode",
},
{
name: "empty key, broadcast, hard error",
Expand All @@ -156,7 +156,7 @@ func TestEVMChainType_ResolveKey(t *testing.T) {
pk: "ab",
broadcast: true,
wantErr: true,
errContains: "required to broadcast",
errContains: "invalid private key: expected 64 hex characters",
},
}

Expand Down
18 changes: 18 additions & 0 deletions cmd/workflow/simulate/chain/evm/supported_chains.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
package evm

import (
"sort"

chainselectors "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain"
"github.com/smartcontractkit/cre-cli/internal/settings"
)

// SupportedChainNames returns the human-readable names of all supported EVM chains,
// sorted alphabetically.
func SupportedChainNames() []string {
var names []string
for _, c := range SupportedChains {
name, err := settings.GetChainNameByChainSelector(c.Selector)
if err != nil {
continue
}
names = append(names, name)
}
sort.Strings(names)
return names
}

// SupportedChains is the canonical list of EVM chains supported for simulation.
var SupportedChains = []chain.ChainConfig{
// Ethereum
Expand Down
13 changes: 12 additions & 1 deletion cmd/workflow/simulate/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,18 @@ func (h *handler) ResolveInputs(v *viper.Viper, creSettings *settings.Settings)
totalClients += len(fc)
}
if totalClients == 0 {
return Inputs{}, fmt.Errorf("no RPC URLs found for supported or experimental chains")
target, _ := settings.GetTarget(v)
if target == "" {
target = "(none)"
}
return Inputs{}, fmt.Errorf(
"no RPC URLs found for target %q\n\n"+
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a generalised rpc check in the root of the cmd package?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite. cmd/root.go has isLoadDeploymentRPC() which decides whether to load RPC settings (for deploy/pause/activate/delete). That's a
config-loading gate, not a validation error.

"To fix:\n"+
" • Check that your project.yaml has an 'rpcs' section under the target %q\n"+
" • Ensure chain names are valid (run 'cre workflow supported-chains' to see all supported names)\n"+
" • Verify the correct target is selected via --target or CRE_TARGET",
target, target,
)
}

broadcast := v.GetBool("broadcast")
Expand Down
25 changes: 25 additions & 0 deletions cmd/workflow/supported_chains/supported_chains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package supported_chains

import (
"fmt"

"github.com/spf13/cobra"

"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain/evm"
)

func New() *cobra.Command {
return &cobra.Command{
Use: "supported-chains",
Short: "List all supported chain names",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
names := evm.SupportedChainNames()
fmt.Println("Supported chain names:")
for _, name := range names {
fmt.Printf(" %s\n", name)
}
return nil
},
}
}
2 changes: 2 additions & 0 deletions cmd/workflow/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
workflowlist "github.com/smartcontractkit/cre-cli/cmd/workflow/list"
"github.com/smartcontractkit/cre-cli/cmd/workflow/pause"
"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate"
supported_chains "github.com/smartcontractkit/cre-cli/cmd/workflow/supported_chains"
"github.com/smartcontractkit/cre-cli/cmd/workflow/test"
"github.com/smartcontractkit/cre-cli/internal/runtime"
)
Expand All @@ -24,6 +25,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
Long: `The workflow command allows you to register and manage existing workflows.`,
}

workflowCmd.AddCommand(supported_chains.New())
workflowCmd.AddCommand(activate.New(runtimeContext))
workflowCmd.AddCommand(build.New(runtimeContext))
workflowCmd.AddCommand(convert.New(runtimeContext))
Expand Down
1 change: 1 addition & 0 deletions docs/cre_workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ cre workflow [optional flags]
* [cre workflow list](cre_workflow_list.md) - Lists workflows deployed for your organization
* [cre workflow pause](cre_workflow_pause.md) - Pauses workflow on the Workflow Registry contract
* [cre workflow simulate](cre_workflow_simulate.md) - Simulates a workflow
* [cre workflow supported-chains](cre_workflow_supported-chains.md) - List all supported chain names

28 changes: 28 additions & 0 deletions docs/cre_workflow_supported-chains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## cre workflow supported-chains

List all supported chain names

```
cre workflow supported-chains [optional flags]
```

### Options

```
-h, --help help for supported-chains
```

### Options inherited from parent commands

```
-e, --env string Path to .env file which contains sensitive info
-R, --project-root string Path to the project root
-E, --public-env string Path to .env.public file which contains shared, non-sensitive build config
-T, --target string Use target settings from YAML config
-v, --verbose Run command in VERBOSE mode
```

### SEE ALSO

* [cre workflow](cre_workflow.md) - Manages workflows

9 changes: 8 additions & 1 deletion internal/context/project_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,14 @@ func SetProjectContext(projectPath string) error {
}

if !found {
return fmt.Errorf("no project settings file found in current directory or parent directories")
return fmt.Errorf(
"no CRE project found (could not locate '%s' in '%s' or any parent directory)\n\n"+
"To fix:\n"+
" • Run this command from inside a CRE project directory\n"+
" • Or run 'cre init' to create a new project here\n"+
" • Or use '--%s <path>' to specify the project location",
constants.DefaultProjectSettingsFileName, cwd, "project-root",
)
}

// Get the directory containing the project settings file (this is the project root)
Expand Down
2 changes: 1 addition & 1 deletion internal/context/project_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestSetProjectContext(t *testing.T) {
},
projectPath: "", // Empty path should trigger search
expectError: true,
errorContains: "no project settings file found",
errorContains: "no CRE project found",
},
{
name: "fails when project path doesn't exist",
Expand Down
11 changes: 10 additions & 1 deletion internal/ethkeys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ func FormatWorkflowOwnerAddress(s string) (string, error) {
func DeriveEthAddressFromPrivateKey(privateKeyHex string) (string, error) {
privateKey, err := crypto.HexToECDSA(privateKeyHex)
if err != nil {
return "", fmt.Errorf("failed to parse private key. Please check CRE_ETH_PRIVATE_KEY in your .env file or system environment: %w", err)
return "", fmt.Errorf(
"invalid private key: expected 64 hex characters (256 bits), got %d characters.\n\n"+
"The CLI reads CRE_ETH_PRIVATE_KEY from your .env file or system environment.\n"+
"The 0x prefix is supported and stripped automatically.\n\n"+
"Common issues:\n"+
" • Pasted an Ethereum address (40 chars) instead of a private key (64 chars)\n"+
" • Value has extra quotes — use CRE_ETH_PRIVATE_KEY=abc123... without wrapping quotes\n"+
" • Key was truncated during copy-paste",
len(privateKeyHex),
)
}

publicKey := privateKey.Public()
Expand Down
2 changes: 1 addition & 1 deletion internal/ethkeys/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestDeriveEthAddressFromPrivateKey_InvalidInput(t *testing.T) {
t.Fatalf("expected error, got nil (addr=%q)", addr)
}

if !strings.Contains(strings.ToLower(err.Error()), "failed to parse private key") {
if !strings.Contains(strings.ToLower(err.Error()), "invalid private key") {
t.Fatalf("unexpected error message: %v", err)
}
})
Expand Down
2 changes: 1 addition & 1 deletion internal/settings/settings_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func ChainNameFromSelectorString(raw string) (string, error) {
func GetChainSelectorByChainName(name string) (uint64, error) {
chainID, err := chainSelectors.ChainIdFromName(name)
if err != nil {
return 0, fmt.Errorf("failed to get chain ID from name %q: %w", name, err)
return 0, fmt.Errorf("failed to get chain ID from name %q: %w\n Run 'cre workflow supported-chains' to see all valid chain names", name, err)
}

selector, err := chainSelectors.SelectorFromChainId(chainID)
Expand Down
10 changes: 9 additions & 1 deletion internal/settings/settings_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ func LoadSettingsIntoViper(v *viper.Viper, cmd *cobra.Command) error {
if context.IsWorkflowCommand(cmd) {
// Step 2: Load workflow settings next (overwrites values from project settings)
if err := mergeConfigToViper(v, constants.DefaultWorkflowSettingsFileName); err != nil {
return fmt.Errorf("failed to load workflow settings: %w", err)
cwd, _ := os.Getwd()
return fmt.Errorf(
"workflow settings file not found: no '%s' in '%s'\n\n"+
"To fix:\n"+
" • Run 'cre workflow init' to create a properly initialized workflow\n"+
" • If this workflow was manually created, add a %s with your target configuration\n"+
" • Check that the workflow folder path argument is correct",
constants.DefaultWorkflowSettingsFileName, cwd, constants.DefaultWorkflowSettingsFileName,
)
}
}

Expand Down
Loading