Part 2: Fetching Offchain Data

In Part 1, you successfully built and ran a minimal workflow. Now, it's time to connect it to the outside world. In this section, you will modify your workflow to fetch data from a public API using the CRE SDK's http.Client.

What you'll do

  • Add a new URL to your workflow's config file.
  • Learn about the http.SendRequest helper for offchain operations.
  • Write a new function to fetch data from the public api.mathjs.org API.
  • Integrate the offchain data into your main workflow logic.

Step 1: Update your configuration

First, you need to add the API endpoint to your workflow's configuration. This allows you to easily change the URL without modifying your Go code.

Open the config.staging.json file in your my-calculator-workflow directory and add the apiUrl key. Your file should now look like this:

{
  "schedule": "0 */1 * * * *",
  "apiUrl": "https://api.mathjs.org/v4/?expr=randomInt(1,101)"
}

This URL calls the public mathjs.org API and uses its randomInt(min, max) function to return a random integer between 1 and 100. Note that the upper bound is exclusive, so we use 101 to get values up to 100. The API returns the number as a raw string in the response body.

Step 2: Understand the http.SendRequest pattern

Many offchain data sources are non-deterministic, meaning different nodes calling the same API might get slightly different answers due to timing, load balancing, or other factors. The api.mathjs.org API with the randomInt function is a perfect exampleโ€”each call will return a different random number.

The CRE SDK solves this with http.SendRequest, a helper function that transforms these potentially varied results into a single, highly reliable result. It works like a "map-reduce" for the DON:

  1. Map: You provide a function (e.g., fetchMathResult) that will be executed by every node in the DON independently. Each node "maps" the offchain world by fetching its own version of the data.
  2. Reduce: You provide a consensus algorithm (e.g., ConsensusMedianAggregation) that takes all the individual results and "reduces" them into a single, trusted outcome.

This pattern is fundamental to securely and reliably bringing offchain data into your workflow.

Step 3: Add the HTTP fetch logic

Now, let's modify your main.go file. You will add a new function, fetchMathResult, that contains the logic for calling the API. You'll also update the onCronTrigger function to call the http.SendRequest helper.

Replace the entire content of onchain-calculator/my-calculator-workflow/main.go with the following code.

onchain-calculator/my-calculator-workflow/main.go
Go
1 //go:build wasip1
2 โ€‹
3 package main
4 โ€‹
5 import (
6 "fmt"
7 "log/slog"
8 "math/big"
9 โ€‹
10 "github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http"
11 "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
12 "github.com/smartcontractkit/cre-sdk-go/cre"
13 "github.com/smartcontractkit/cre-sdk-go/cre/wasm"
14 )
15 โ€‹
16 // Add the ApiUrl to your config struct
17 type Config struct {
18 Schedule string `json:"schedule"`
19 ApiUrl string `json:"apiUrl"`
20 }
21 โ€‹
22 type MyResult struct {
23 Result *big.Int
24 }
25 โ€‹
26 func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
27 return cre.Workflow[*Config]{
28 cre.Handler(
29 cron.Trigger(&cron.Config{Schedule: config.Schedule}),
30 onCronTrigger,
31 ),
32 }, nil
33 }
34 โ€‹
35 // fetchMathResult is the function passed to the http.SendRequest helper.
36 // It contains the logic for making the request and parsing the response.
37 func fetchMathResult(config *Config, logger *slog.Logger, sendRequester *http.SendRequester) (*big.Int, error) {
38 req := &http.Request{
39 Url: config.ApiUrl,
40 Method: "GET",
41 }
42 โ€‹
43 // Send the request using the provided sendRequester
44 resp, err := sendRequester.SendRequest(req).Await()
45 if err != nil {
46 return nil, fmt.Errorf("failed to get API response: %w", err)
47 }
48 โ€‹
49 // The mathjs.org API returns the result as a raw string in the body.
50 // We need to parse it into a big.Int.
51 val, ok := new(big.Int).SetString(string(resp.Body), 10)
52 if !ok {
53 return nil, fmt.Errorf("failed to parse API response into big.Int")
54 }
55 return val, nil
56 }
57 โ€‹
58 // onCronTrigger is our main DON-level callback.
59 func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
60 logger := runtime.Logger()
61 logger.Info("Hello, Calculator! Workflow triggered.")
62 โ€‹
63 client := &http.Client{}
64 // Use the http.SendRequest helper to execute the offchain fetch.
65 mathPromise := http.SendRequest(config, runtime, client,
66 fetchMathResult,
67 // The API returns a random number, so each node can get a different result. We use Median Aggregation to find a median value.
68 cre.ConsensusMedianAggregation[*big.Int](),
69 )
70 โ€‹
71 // Await the final, aggregated result.
72 result, err := mathPromise.Await()
73 if err != nil {
74 return nil, err
75 }
76 โ€‹
77 logger.Info("Successfully fetched and aggregated math result", "result", result)
78 โ€‹
79 return &MyResult{
80 Result: result,
81 }, nil
82 }
83 โ€‹
84 func main() {
85 wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
86 }

Step 4: Sync your dependencies

  1. Sync Dependencies: Your new code imports a new package. Run the following go get command to add the required dependency at its specific version:

    go get github.com/smartcontractkit/cre-sdk-go/capabilities/networking/[email protected]
    
  2. Clean up and organize your module files:

    After fetching the new dependencies, run go mod tidy to clean up the go.mod and go.sum files.

    go mod tidy
    

Step 5: Run the simulation and review the output

Run the simulate command from your project root directory (the onchain-calculator/ folder). Because there is only one trigger, the simulator runs it automatically.

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

The output shows the new user logs from your workflow, followed by the final Workflow Simulation Result.

Workflow compiled
2025-11-03T22:35:51Z [SIMULATION] Simulator Initialized

2025-11-03T22:35:51Z [SIMULATION] Running trigger trigger=[email protected]
2025-11-03T22:35:51Z [USER LOG] msg="Hello, Calculator! Workflow triggered."
2025-11-03T22:35:52Z [USER LOG] msg="Successfully fetched and aggregated math result" result=50

Workflow Simulation Result:
 {
  "Result": 50
}

2025-11-03T22:35:52Z [SIMULATION] Execution finished signal received
2025-11-03T22:35:52Z [SIMULATION] Skipping WorkflowEngineV2
  • [USER LOG]: You can now see both of your logger.Info() calls in the output. The second log shows the fetched and aggregated value (result=50), confirming that the API call and consensus worked correctly.
  • [SIMULATION]: These are system-level messages from the simulator showing its internal state.
  • Workflow Simulation Result: This is the final return value of your workflow. The Result field now contains the aggregated value from the API as a number.

Your workflow can now fetch and process data from an external source.

Next Steps

Next, you'll learn how to interact with a smart contract to read data from the blockchain and combine it with this offchain result.

Get the latest Chainlink content straight to your inbox.