Using Secrets in Simulation

This guide explains how to use secrets during local development and simulation. When you're simulating a workflow on your local machine with cre workflow simulate, secrets are provided via environment variables or a .env file.

At a high level, the process follows a simple, three-step pattern:

  1. Declare: You declare the logical names of your secrets in a secrets.yaml file.
  2. Provide: You provide the actual secret values in a .env file or as environment variables.
  3. Use: You access the secrets in your workflow code using the SDK's secret management API.

This separation of concerns ensures that your workflow code is portable and your secrets are never hard-coded.

Step-by-step guide

Step 1: Declare your secrets (secrets.yaml)

The first step is to create a secrets.yaml file in the root of your project. This file acts as a manifest, defining the "logical names" or "IDs" for the secrets your workflow will use.

In this file, you map a logical name (which you'll use in your workflow code) to one environment variable name that will hold the actual secret value.

Example secrets.yaml:

# in project-root/secrets.yaml
secretsNames:
  # This is the logical ID you will use in your workflow code
  SECRET_ADDRESS:
    # This is the environment variable the CLI will look for
    - SECRET_ADDRESS_ALL

Step 2: Provide the secret values

Next, you need to provide the actual values for the secrets. The cre CLI can read these values in two primary ways.

You can provide secrets as standard environment variables directly in your shell.

For example, in your terminal:

export SECRET_ADDRESS_ALL="0x1234567890abcdef1234567890abcdef12345678"

When you run the cre workflow simulate command in the same terminal session, the CLI will have access to this variable.

Method 2: Using a .env file

Create a .env file in your project's root directory. The cre CLI automatically finds this file and loads the variables defined within it into the environment for your simulation. The variable names here must match those you declared in secrets.yaml.

Example .env file:

# in project-root/.env

# The variable name matches the one in secrets.yaml
SECRET_ADDRESS_ALL="0x1234567890abcdef1234567890abcdef12345678"

Step 3: Use the secret in your workflow

Now you can access the secret in your workflow code. The SDK provides a method to retrieve secrets using the logical ID you defined in secrets.yaml.

The following code shows a complete, runnable workflow that triggers on a schedule, fetches a secret, and logs its value.

Example workflow:

Fetching Single Secret (Go)
Go
1 //go:build wasip1
2 โ€‹
3 package main
4 โ€‹
5 import (
6 "log/slog"
7 โ€‹
8 protos "github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
9 "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
10 "github.com/smartcontractkit/cre-sdk-go/cre"
11 "github.com/smartcontractkit/cre-sdk-go/cre/wasm"
12 )
13 โ€‹
14 // Config can be an empty struct if you don't need any parameters from config.json.
15 type Config struct{}
16 โ€‹
17 // MyResult can be an empty struct if your workflow doesn't need to return a result.
18 type MyResult struct{}
19 โ€‹
20 // Define the logical name of the secret as a constant for clarity.
21 const SecretName = "SECRET_ADDRESS"
22 โ€‹
23 // onCronTrigger is the callback function that gets executed when the cron trigger fires.
24 // This is where you use the secret.
25 func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
26 logger := runtime.Logger()
27 // Build the request with the secret's logical ID.
28 secretReq := &protos.SecretRequest{
29 Id: SecretName,
30 }
31 โ€‹
32 // Call runtime.GetSecret and await the promise.
33 secret, err := runtime.GetSecret(secretReq).Await()
34 if err != nil {
35 logger.Error("Failed to get secret", "name", SecretName, "err", err)
36 return nil, err
37 }
38 โ€‹
39 // Use the secret's value.
40 secretAddress := secret.Value
41 logger.Info("Successfully fetched a secret!", "address", secretAddress)
42 โ€‹
43 // ... now you can use the secretAddress in your logic ...
44 return &MyResult{}, nil
45 }
46 โ€‹
47 // InitWorkflow is the required entry point for a CRE workflow.
48 func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
49 return cre.Workflow[*Config]{
50 cre.Handler(
51 cron.Trigger(&cron.Config{Schedule: "0 */10 * * * *"}),
52 onCronTrigger,
53 ),
54 }, nil
55 }
56 โ€‹
57 // main is the entry point for the WASM binary.
58 func main() {
59 wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
60 }
61 โ€‹
62 โ€‹

Step 4: Configure secrets path in workflow.yaml

Before simulating, you need to tell the CLI where to find your secrets file. This is configured in your workflow.yaml file under workflow-artifacts.secrets-path.

Open your workflow.yaml file and set the secrets-path:

local-simulation:
  user-workflow:
    workflow-name: "my-workflow"
    workflow-artifacts:
      workflow-path: "./main.go"
      config-path: "./config.json"
      secrets-path: "../secrets.yaml" # Path to your secrets file

Notice the path ../secrets.yaml. Because the workflow artifacts are relative to the workflow directory, you need to point to the secrets.yaml file located one level up in the project root.

Step 5: Run the simulation

Now you can simulate your workflow:

cre workflow simulate my-workflow --target staging-settings

The CLI will automatically read the secrets-path from your workflow.yaml and load the secrets from your .env file or environment variables you provided in your terminal session.

Fetching multiple secrets

You can fetch multiple secrets by calling the secret retrieval method multiple times within your workflow.

The following example builds on the previous one. First, update your secrets.yaml to declare two secrets:

secretsNames:
  SECRET_ADDRESS:
    - SECRET_ADDRESS_ALL
  API_KEY:
    - API_KEY_ALL

Then provide the values in your .env file or export them as environment variables in your terminal session:

export SECRET_ADDRESS_ALL="0x1234567890abcdef1234567890abcdef12345678"
export API_KEY_ALL="your-api-key-here"

Now you can fetch both secrets in your workflow code:

Fetching Multiple Secrets (Go)
Go
1 //go:build wasip1
2 โ€‹
3 package main
4 โ€‹
5 import (
6 "log/slog"
7 โ€‹
8 protos "github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
9 "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
10 "github.com/smartcontractkit/cre-sdk-go/cre"
11 "github.com/smartcontractkit/cre-sdk-go/cre/wasm"
12 )
13 โ€‹
14 // Config can be an empty struct if you don't need any parameters from config.json.
15 type Config struct{}
16 โ€‹
17 // MyResult can be an empty struct if your workflow doesn't need to return a result.
18 type MyResult struct{}
19 โ€‹
20 const (
21 SecretAddressName = "SECRET_ADDRESS"
22 ApiKeyName = "API_KEY"
23 )
24 โ€‹
25 func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
26 logger := runtime.Logger()
27 โ€‹
28 // Important: Fetch secrets sequentially, not in parallel.
29 // The WASM host for CRE runtime does not support parallel runtime.GetSecret() requests.
30 // Always call GetSecret(), then Await() before making the next GetSecret() call.
31 โ€‹
32 // 1. Fetch the first secret
33 addressPromise := runtime.GetSecret(&protos.SecretRequest{Id: SecretAddressName})
34 secretAddress, err := addressPromise.Await()
35 if err != nil {
36 logger.Error("Failed to get SECRET_ADDRESS", "err", err)
37 return nil, err
38 }
39 โ€‹
40 // 2. Fetch the second secret (only after the first is complete)
41 apiKeyPromise := runtime.GetSecret(&protos.SecretRequest{Id: ApiKeyName})
42 apiKey, err := apiKeyPromise.Await()
43 if err != nil {
44 logger.Error("Failed to get API_KEY", "err", err)
45 return nil, err
46 }
47 โ€‹
48 // 3. Use your secrets
49 logger.Info("Successfully fetched secrets!",
50 "address", secretAddress.Value,
51 "apiKey", apiKey.Value,
52 )
53 โ€‹
54 return &MyResult{}, nil
55 }
56 โ€‹
57 // InitWorkflow is the required entry point for a CRE workflow.
58 func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
59 return cre.Workflow[*Config]{
60 cre.Handler(
61 cron.Trigger(&cron.Config{Schedule: "0 */10 * * * *"}),
62 onCronTrigger,
63 ),
64 }, nil
65 }
66 โ€‹
67 // main is the entry point for the WASM binary.
68 func main() {
69 wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
70 }
71 โ€‹
72 โ€‹

Get the latest Chainlink content straight to your inbox.