SDK Reference: Core

This page provides a reference for the core data structures and functions of the CRE TypeScript SDK. These are the fundamental building blocks that every workflow uses, regardless of trigger types or capabilities.

Key concepts and components

cre.handler()

The cre.handler() function is the cornerstone of every workflow. It registers a handler that links a specific trigger to a callback function containing your workflow logic. It is typically called within your initWorkflow function.

Usage:

import { cre, type Runtime } from "@chainlink/cre-sdk"

const initWorkflow = (config: Config) => {
  return [
    cre.handler(
      // 1. A configured trigger, e.g., cron.trigger(...)
      // This determines WHEN the workflow runs
      triggerInstance,

      // 2. The callback function to execute when the trigger fires
      // This is WHERE your workflow logic lives
      myCallbackFunction
    ),
  ]
}
  • The Trigger: An instance of a trigger capability (e.g., cron.trigger(...)). This defines the event that will start your workflow. See the Triggers reference for details.
  • The Callback: The function to be executed when the trigger fires. The signature of your callback function must match the output type of the trigger you are using.

Runtime and NodeRuntime

These TypeScript interfaces provide access to capabilities and manage the execution context of your workflow. The key difference is who is responsible for creating a single, trusted result from the work of many nodes.

  • Runtime<C> ("DON Mode"): Passed to your main trigger callback, this represents the DON's (Decentralized Oracle Network) execution context. It is used for operations that are already guaranteed to be Byzantine Fault Tolerant (BFT). When you use the Runtime, you ask the network to execute something, and CRE handles the underlying complexity to ensure you get back one final, secure, and trustworthy result. Common use cases include writing transactions to a blockchain with the EVM client or accessing secrets.

  • NodeRuntime<C> ("Node Mode"): Represents an individual node's execution context. This is used when a BFT guarantee cannot be provided automatically (e.g., calling a third-party API). You tell each node to perform a task on its own, and each node returns its own individual answer. You are then responsible for telling the SDK how to combine them into a single, trusted result by providing a consensus and aggregation algorithm. It is used exclusively inside a runtime.runInNodeMode() block and is provided by that function—you do not receive this type directly in your handler's callback.

To learn more about how to aggregate results from NodeRuntime, see the Consensus & Aggregation reference.

Available Methods:

Both Runtime and NodeRuntime provide:

  • config: Access to your workflow's configuration
  • now(): Returns the current Date object
  • log(message: string): Logs a message (accepts a single string argument)
  • callCapability(...): Internal method for calling capabilities (used by generated code)

Runtime additionally provides:

  • runInNodeMode(...): Execute code on individual nodes with consensus aggregation
  • getSecret(...): Access to workflow secrets
  • report(...): Generate cryptographically signed reports

Understanding the .result() Pattern

All SDK capabilities in the TypeScript SDK use a two-step pattern for asynchronous operations:

Step 1: Initiate the operation

const request = httpClient.sendRequest(runtime, { url: "https://api.example.com" })

Step 2: Get the result

const response = request.result()

Common usage: These steps are often chained together for simplicity:

import { cre, encodeCallMsg, LAST_FINALIZED_BLOCK_NUMBER, type Runtime } from "@chainlink/cre-sdk"
import { zeroAddress } from "viem"

const onCronTrigger = (runtime: Runtime<Config>): string => {
  const evmClient = new cre.capabilities.EVMClient(chainSelector)

  // Inline pattern: initiate and get result in one expression
  const contractCall = evmClient
    .callContract(runtime, {
      call: encodeCallMsg({
        from: zeroAddress,
        to: config.contractAddress,
        data: encodedCallData,
      }),
      blockNumber: LAST_FINALIZED_BLOCK_NUMBER,
    })
    .result()

  return "Success"
}

Why this pattern exists

Traditional TypeScript async/await doesn't work with SDK capabilities in the WebAssembly environment where CRE workflows run. WASM execution is fundamentally synchronous—when you call a function, it runs to completion before anything else happens. The interaction between the WASM guest (your workflow) and the Go host (the CRE engine) uses simple, synchronous function calls.

The .result() pattern is a custom solution to this limitation. It simulates asynchronous behavior using a pair of synchronous calls:

  1. The first call (e.g., sendRequest()) sends your request from the TypeScript code (compiled to WASM) to the CRE host
  2. The .result() call blocks your WASM code and waits for the host to complete the async operation and return the response

This allows the host to handle I/O-bound tasks (like network requests) asynchronously without blocking the entire runtime, while providing a simple, blocking interface to your code inside the WASM module.

Preparing multiple operations

You can initiate multiple operations before calling .result() on any of them:

// Initiate two operations
const request1 = httpClient.sendRequest(runtime, { url: "https://api1.example.com" })
const request2 = httpClient.sendRequest(runtime, { url: "https://api2.example.com" })

// Get results as needed
const response1 = request1.result()
const response2 = request2.result()

This pattern allows you to prepare operations and then collect their results in the order you need them.

Operations that use .result()

The .result() pattern applies to all SDK capabilities that perform asynchronous work:

  • HTTP requests: httpClient.sendRequest(...).result()
  • EVM contract calls (read): evmClient.callContract(...).result()
  • EVM contract calls (write): evmClient.writeReport(...).result()
  • Secrets retrieval: runtime.getSecret(...).result()
  • Node-level execution: runtime.runInNodeMode(...)().result()
  • Report generation: runtime.report(...).result()

Workflow entry points

Your workflow code requires two specific functions to serve as entry points for compilation and execution.

main()

This is the entry point of your workflow. You must define this async function to create a WASM runner and start your workflow.

Required Pattern:

import { Runner } from "@chainlink/cre-sdk"
import { z } from "zod"

// Define your config schema with Zod
const configSchema = z.object({
  schedule: z.string(),
  apiUrl: z.string(),
})

type Config = z.infer<typeof configSchema>

export async function main() {
  // Create the runner with your config schema
  const runner = await Runner.newRunner<Config>({ configSchema })

  // Run your workflow initialization function
  await runner.run(initWorkflow)
}

main()

Key points:

  • Must be an async function
  • Must call Runner.newRunner<Config>() with an optional configSchema parameter for validation
  • Must call runner.run(initWorkflow) to execute your workflow
  • Must invoke main() at the end of your file

initWorkflow

This is the second required entry point. The CRE runner calls this function to initialize your workflow and register all its handlers.

Required Signature:

import { cre, type Runtime } from "@chainlink/cre-sdk"

function initWorkflow(config: Config): Array<HandlerEntry<Config, any, any, any>>

Parameters:

  • config: Your workflow's configuration object (validated against your Zod schema if provided)

Returns:

  • An array of handlers created with cre.handler()

Example:

import { cre, type Runtime, type CronPayload } from "@chainlink/cre-sdk"

// Callback function executed by the handler
const onCronTrigger = (runtime: Runtime<Config>, payload: CronPayload): string => {
  runtime.log("Workflow triggered!")
  return "complete"
}

const initWorkflow = (config: Config) => {
  const cron = new cre.capabilities.CronCapability()

  return [cre.handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)]
}

runtime.runInNodeMode()

As explained in the Runtime and NodeRuntime section, this method is the bridge between the DON-level execution context (Runtime) and the individual node-level context (NodeRuntime). It allows you to execute code on individual nodes and then aggregate their results back into a single, trusted outcome.

Signature:

runtime.runInNodeMode<TArgs extends unknown[], TOutput>(
  fn: (nodeRuntime: NodeRuntime<C>, ...args: TArgs) => TOutput,
  consensusAggregation: ConsensusAggregation<TOutput, true>,
  unwrapOptions?: UnwrapOptions<TOutput>
): (...args: TArgs) => { result: () => TOutput }

Parameters:

  • fn: A function that receives a NodeRuntime and executes on each individual node
  • consensusAggregation: An aggregation function (e.g., consensusMedianAggregation<bigint>())
  • unwrapOptions: Optional configuration for how to unwrap complex return types

Returns:

A function that, when called with any additional arguments, returns an object with a .result() method.

Example:

This example uses runInNodeMode to fetch data from an API on each node, and then uses the DON-level Runtime to write the aggregated result onchain.

import {
  cre,
  consensusMedianAggregation,
  type Runtime,
  type NodeRuntime,
} from "@chainlink/cre-sdk"

const fetchPrice = (nodeRuntime: NodeRuntime<Config>): bigint => {
  const httpClient = new cre.capabilities.HTTPClient()
  // Fetch price from API using nodeRuntime
  return fetchOffchainPrice(nodeRuntime)
}

const onTrigger = (runtime: Runtime<Config>, ...): string => {
  // 1. Run code on individual nodes using runInNodeMode
  // The fetchPrice function receives a NodeRuntime
  const price = runtime
    .runInNodeMode(
      fetchPrice,
      consensusMedianAggregation<bigint>()
    )()
    .result()

  // 2. Now, back in the DON context, use the top-level runtime
  // to perform an action that requires consensus, like an onchain write
  const tx = evmClient
    .writeReport(runtime, { /* ... */ })
    .result()

  return "success"
}

Get the latest Chainlink content straight to your inbox.