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
11 changes: 10 additions & 1 deletion cmd/common/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, stripSymbols boo
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
53 changes: 27 additions & 26 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,32 +432,33 @@ 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 account": {},
"cre secrets": {},
"cre templates": {},
"cre templates list": {},
"cre templates add": {},
"cre templates remove": {},
"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": {},
}

_, exists := excludedCommands[cmd.CommandPath()]
Expand Down
41 changes: 39 additions & 2 deletions cmd/workflow/simulate/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,41 @@ func (h *handler) ResolveInputs(v *viper.Viper, creSettings *settings.Settings)
}

if len(clients) == 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?

"To fix:\n"+
" • Check that your workflow.yaml has an 'rpcs' section under the target %q\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.

project.yaml?

" • 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,
)
}

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 Inputs{}, 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))
}
// Key not set or placeholder — require it for broadcast, otherwise use default for simulation
if v.GetBool("broadcast") {
return Inputs{}, 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("0000000000000000000000000000000000000000000000000000000000000001")
if err != nil {
Expand Down Expand Up @@ -1140,3 +1167,13 @@ func getEVMTriggerLogFromValues(ctx context.Context, ethClient *ethclient.Client
}
return pbLog, 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
}
18 changes: 17 additions & 1 deletion cmd/workflow/simulate/simulator_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -27,6 +28,21 @@ type ChainConfig struct {
Forwarder string
}

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

// SupportedEVM is the canonical list you can range over.
var SupportedEVM = []ChainConfig{
// Ethereum
Expand Down Expand Up @@ -185,7 +201,7 @@ func redactURL(rawURL string) string {
// experimentalForwarders keys identify experimental chains (not in chain-selectors).
func runRPCHealthCheck(clients map[uint64]*ethclient.Client, experimentalForwarders map[uint64]common.Address) error {
if len(clients) == 0 {
return fmt.Errorf("check your settings: no RPC URLs found for supported or experimental chains")
return fmt.Errorf("no RPC URLs found for supported or experimental chains. Run 'cre workflow supported-chains' to see all supported chain names")
}

var errs []error
Expand Down
2 changes: 1 addition & 1 deletion cmd/workflow/simulate/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestHealthCheck_NoClientsConfigured(t *testing.T) {
if err == nil {
t.Fatalf("expected error for no clients configured")
}
mustContain(t, err.Error(), "check your settings: no RPC URLs found for supported or experimental chains")
mustContain(t, err.Error(), "no RPC URLs found for supported or experimental chains")
}

func TestHealthCheck_NilClient(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions cmd/workflow/workflow.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package workflow

import (
"fmt"

"github.com/spf13/cobra"

"github.com/smartcontractkit/cre-cli/cmd/workflow/activate"
Expand All @@ -23,6 +25,21 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
Long: `The workflow command allows you to register and manage existing workflows.`,
}

supportedChainsCmd := &cobra.Command{
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.

nit: Can we move this to it's own file to follow the pattern of other commands?

Use: "supported-chains",
Short: "List all supported chain names",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
names := simulate.SupportedChainNames()
fmt.Println("Supported chain names:")
for _, name := range names {
fmt.Printf(" %s\n", name)
}
return nil
},
}

workflowCmd.AddCommand(supportedChainsCmd)
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 @@ -38,4 +38,5 @@ cre workflow [optional flags]
* [cre workflow limits](cre_workflow_limits.md) - Manage simulation limits
* [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 @@ -10,7 +10,16 @@ import (
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 @@ -253,7 +253,7 @@ func GetChainNameByChainSelector(chainSelector uint64) (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 @@ -103,7 +103,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