SDK Reference: Confidential Workflows Client

handlerInTee registers a handler whose callback runs inside a secure enclave—a running instance of a TEE—receiving a TeeRuntime instead of a Runtime. Unlike the regular handler, secrets are fetched dynamically at runtime from the Vault DON with runtime.getSecret() and any operation that needs Workflow DON consensus must explicitly cross over with runtime.usingTheDons().

Quick reference

MethodDescription
handlerInTeeRegisters a handler whose callback runs inside a TEE.
runtime.getSecretFetches a single secret dynamically, inside the enclave.
runtime.getSecretsFetches multiple secrets in a single batch call, inside the enclave.
runtime.usingTheDonsReturns a regular Runtime for operations that need DON consensus.
runtime.reportFromDonGenerates a report from the DON directly, without a full usingTheDons() crossover.

Core types

TeeRuntime<C>

The runtime passed to a callback registered with handlerInTee. Requests made through it execute inside the enclave; requests made through the runtime returned by usingTheDons() are routed outside the enclave to the Workflow DON.

interface TeeRuntime<C> extends BaseRuntime<C>, SecretsProvider {
  reportFromDon(input: ReportRequest | ReportRequestJson): { result: () => Report }
  usingTheDons(): Runtime<C>
}

BaseRuntime<C> provides config, now(), log(), and emitMetric(), same as on a regular Runtime<C>. SecretsProvider provides getSecret() and getSecrets().

TeeConstraint

Describes which TEEs a handler will accept. Pass one of the following as the third argument to handlerInTee:

ShapeDescription
{}Accepts any registered TEE, in any region.
{ regions: [...] }Accepts any TEE, restricted to the listed regions.
[{ tee: 'nitro', regions: [...] }]Accepts specific TEE types, each optionally restricted to its own regions.
// Accept any TEE, any region
{
}

// Accept any TEE, restricted to a region
{
  regions: ["us-west-2"]
}

// Accept only Nitro, restricted to a region
;[{ tee: "nitro", regions: ["us-west-2"] }]

Nitro (tee: 'nitro') is currently the only registered TEE type, and 'us-west-2' is currently the only supported region—check your installed SDK version if you expect otherwise, since this is an actively evolving API.

Registering a handler

handlerInTee

Registers a handler whose callback runs inside a TEE.

Signature:

function handlerInTee<TRawTriggerOutput, TTriggerOutput, TConfig, TResult>(
  trigger: Trigger<TRawTriggerOutput, TTriggerOutput>,
  fn: (runtime: TeeRuntime<TConfig>, triggerOutput: TTriggerOutput) => TResult,
  tees: TeeConstraint,
  hooks?: Hooks<TConfig, TTriggerOutput>
): HandlerEntry<TConfig, TRawTriggerOutput, TTriggerOutput, TResult, TeeRuntime<TConfig>>

Parameters:

  • trigger: Any CRE trigger, same as for handler.
  • fn: Your handler function. It receives a TeeRuntime instead of a Runtime.
  • tees: A TeeConstraint describing which TEEs are acceptable.
  • hooks: Optional. Same preHook mechanism available on a regular handler.

Example:

const initWorkflow = (config: Config) => {
  const cron = new CronCapability()
  return [handlerInTee(cron.trigger({ schedule: config.schedule }), onCronTrigger, {})]
}

Guide: Making a Workflow Confidential

Fetching secrets

runtime.getSecret

Fetches a single secret, decrypted only inside the enclave.

Signature:

getSecret(request: SecretRequest | SecretRequestJson): { result: () => Secret }

Example:

const secret = runtime.getSecret({ id: "MY_API_KEY" }).result()
// secret.value holds the decrypted value.

There's no upfront declaration like Confidential HTTP's vaultDonSecrets—the secret is requested and decrypted inside the enclave at the moment getSecret() runs.

runtime.getSecrets

Fetches multiple secrets in a single batch call, decrypted only inside the enclave.

Signature:

getSecrets(requests: Array<SecretRequest | SecretRequestJson>): {
  result: () => Record<string, Secret>
}

The result is a record keyed by each secret's id. If any secret in the batch fails, .result() throws a SecretsBatchError that includes the failing secret ids and their error messages. Requesting the same id more than once in a single call also throws a SecretsBatchError.

Example:

const secrets = runtime.getSecrets([{ id: "API_KEY" }, { id: "DB_URL" }]).result()

const apiKey = secrets["API_KEY"].value
const dbUrl = secrets["DB_URL"].value

Crossing back to the DON

runtime.usingTheDons

Returns a regular Runtime<C> for operations that need Workflow DON consensus.

Signature:

usingTheDons(): Runtime<C>

Example:

const donRuntime = runtime.usingTheDons()
donRuntime
  .report({
    // encodedPayload is the base64-encoded form of your payload bytes, e.g.
    // Buffer.from(payloadBytes).toString("base64")
    encodedPayload: encodedValue,
    encoderName: "evm",
    signingAlgo: "ecdsa",
    hashingAlgo: "keccak256",
  })
  .result()

runtime.reportFromDon

A shortcut for generating a report from the DON without a full usingTheDons() crossover.

Signature:

reportFromDon(input: ReportRequest | ReportRequestJson): { result: () => Report }

Data requested through this method is routed outside the TEE, same as with usingTheDons().

Making capability calls inside the enclave

Capabilities that support Confidential Workflows expose a TeeRuntime-accepting overload of their regular method. For the HTTP capability, HTTPClient.sendRequest() accepts either a Runtime/NodeRuntime or a TeeRuntime directly:

const response = new HTTPClient()
  .sendRequest(runtime, {
    url: config.url,
    method: "GET",
    multiHeaders: { Authorization: { values: [`Bearer ${secret.value}`] } },
  })
  .result()

Guide: Making a Workflow Confidential

Not every capability has a TeeRuntime overload

ConfidentialHTTPClient is a notable exception: its sendRequest() only accepts a Runtime—there's no overload for TeeRuntime. Passing a TeeRuntime directly won't type-check.

// Does NOT type-check: ConfidentialHTTPClient.sendRequest has no overload accepting a TeeRuntime.
const confHttpClient = new ConfidentialHTTPClient()
confHttpClient.sendRequest(runtime, { request: { url, method: "POST" } }) // runtime is TeeRuntime

Get the latest Chainlink content straight to your inbox.