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 theRuntime, 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 aruntime.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 configurationnow(): Returns the currentDateobjectlog(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 aggregationgetSecret(...): Access to workflow secretsreport(...): 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:
- The first call (e.g.,
sendRequest()) sends your request from the TypeScript code (compiled to WASM) to the CRE host - 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
asyncfunction - Must call
Runner.newRunner<Config>()with an optionalconfigSchemaparameter 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 aNodeRuntimeand executes on each individual nodeconsensusAggregation: 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"
}