Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions packages/cre-sdk-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
"viem": "2.34.0",
"zod": "3.25.76"
},
"devDependencies": {
"@types/bun": "1.3.8"
},
"devDependencies": {},
"engines": {
"bun": ">=1.2.21"
}
Expand Down
30 changes: 30 additions & 0 deletions packages/cre-sdk-examples/src/restricted-apis-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* This example shows how CRE workflows mark restricted APIs as deprecated in TS.
*
* The restricted APIs covered in this example are:
* - fetch
* - setTimeout
* - setInterval
*
* Other unsupported globals/modules are enforced by cre-compile runtime checks.
* There are also NodeJS APIs that do work with the QuickJS runtime, like console.log.
*/

export const testFetch = async () => {
// @ts-expect-error - fetch is not available in the CRE SDK
fetch('https://api.chain.link/v1/price?symbol=ETH/USD')
}

export const testSetTimeout = async () => {
// @ts-expect-error - setTimeout is not available in the CRE SDK
setTimeout(() => {
console.log('Hello, world!')
}, 1000)
}

export const testSetInterval = async () => {
// @ts-expect-error - setInterval is not available in the CRE SDK
setInterval(() => {
console.log('Hello, world!')
}, 1000)
}
132 changes: 132 additions & 0 deletions packages/cre-sdk-examples/src/restricted-node-modules-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* This example shows how CRE workflows mark restricted Node.js modules as `never` in TS.
*
* CRE workflows run on QuickJS (via Javy/WASM), not full Node.js.
* All exports from restricted modules are typed as `never`, so any usage
* produces a clear TypeScript error at the call site.
*
* The restricted modules covered in this example are:
* - node:crypto
* - node:fs
* - node:fs/promises
* - node:net
* - node:http
* - node:https
* - node:child_process
* - node:os
* - node:stream
* - node:worker_threads
* - node:dns
* - node:zlib
*
* For HTTP requests, use cre.capabilities.HTTPClient instead of node:http/node:https/node:net.
*
* @see https://docs.chain.link/cre/concepts/typescript-wasm-runtime
*/

import { exec } from 'node:child_process'
import { createHash, randomBytes } from 'node:crypto'
import { lookup } from 'node:dns'
import { readFileSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { request as httpRequest } from 'node:http'
import { request as httpsRequest } from 'node:https'
import { createConnection } from 'node:net'
import { cpus, hostname } from 'node:os'
import { Readable } from 'node:stream'
import { Worker } from 'node:worker_threads'
import { createGzip } from 'node:zlib'

// --- node:crypto ---

export const testCryptoRandomBytes = () => {
// @ts-expect-error - node:crypto is not available in CRE WASM workflows
randomBytes(32)
}

export const testCryptoCreateHash = () => {
// @ts-expect-error - node:crypto is not available in CRE WASM workflows
createHash('sha256')
}

// --- node:fs ---

export const testFsReadFileSync = () => {
// @ts-expect-error - node:fs is not available in CRE WASM workflows
readFileSync('/etc/passwd', 'utf-8')
}

// --- node:fs/promises ---

export const testFsPromisesReadFile = async () => {
// @ts-expect-error - node:fs/promises is not available in CRE WASM workflows
await readFile('/etc/passwd', 'utf-8')
}

// --- node:net ---

export const testNetCreateConnection = () => {
// @ts-expect-error - node:net is not available in CRE WASM workflows
createConnection({ host: 'localhost', port: 8080 })
}

// --- node:http ---

export const testHttpRequest = () => {
// @ts-expect-error - node:http is not available in CRE WASM workflows
httpRequest('http://example.com')
}

// --- node:https ---

export const testHttpsRequest = () => {
// @ts-expect-error - node:https is not available in CRE WASM workflows
httpsRequest('https://example.com')
}

// --- node:child_process ---

export const testChildProcessExec = () => {
// @ts-expect-error - node:child_process is not available in CRE WASM workflows
exec('ls -la')
}

// --- node:os ---

export const testOsHostname = () => {
// @ts-expect-error - node:os is not available in CRE WASM workflows
hostname()
}

export const testOsCpus = () => {
// @ts-expect-error - node:os is not available in CRE WASM workflows
cpus()
}

// --- node:stream ---

export const testStreamReadable = () => {
// @ts-expect-error - node:stream is not available in CRE WASM workflows
new Readable()
}

// --- node:worker_threads ---

export const testWorkerThreads = () => {
// @ts-expect-error - node:worker_threads is not available in CRE WASM workflows
new Worker('./worker.js')
}

// --- node:dns ---

export const testDnsLookup = () => {
// @ts-expect-error - node:dns is not available in CRE WASM workflows
lookup('example.com', () => {})
}

// --- node:zlib ---

export const testZlibCreateGzip = () => {
// @ts-expect-error - node:zlib is not available in CRE WASM workflows
createGzip()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type NodeRuntime,
Runner,
type Runtime,
text,
} from '@chainlink/cre-sdk'
import { z } from 'zod'

Expand Down Expand Up @@ -38,8 +39,8 @@ const fetchMathResult = (nodeRuntime: NodeRuntime<Config>): bigint => {
const resp = httpClient.sendRequest(nodeRuntime, req).result()
// The mathjs.org API returns the result as a raw string in the body.
// We need to parse it into a bigint.
const bodyText = new TextDecoder().decode(resp.body)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ensure we don't break existing workflows. TextDecoder still needs to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just "extra" cleanup in my example as using text shall be more convenient for the users. TextDecoder does in fact still work and you can still use the previous notation if you prefer.

const val = BigInt(bodyText.trim())
const bodyText = text(resp)
const val = BigInt(bodyText)
return val
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Runner,
type Runtime,
TxStatus,
text,
} from '@chainlink/cre-sdk'
import { type Address, decodeFunctionResult, encodeFunctionData, zeroAddress } from 'viem'
import { z } from 'zod'
Expand Down Expand Up @@ -66,7 +67,7 @@ const fetchReserveInfo = (sendRequester: HTTPSendRequester, config: Config): Res
throw new Error(`HTTP request failed with status: ${response.statusCode}`)
}

const responseText = Buffer.from(response.body).toString('utf-8')
const responseText = text(response)
const porResp: PORResponse = JSON.parse(responseText)

if (porResp.ripcord) {
Expand Down
3 changes: 3 additions & 0 deletions packages/cre-sdk-examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,

// Do not auto-include @types/* — mirrors the customer cre cli initialized environment
"types": [],

// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
Expand Down
13 changes: 12 additions & 1 deletion packages/cre-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The Chainlink Runtime Environment (CRE) SDK for TypeScript enables developers to
- [Examples](#examples)
- [Simulate locally with CRE CLI](#simulate-locally-with-cre-cli)
- [Installation](#installation)
- [Runtime Compatibility Constraints](#runtime-compatibility-constraints)
- [Core Concepts](#core-concepts)
- [Workflows](#workflows)
- [Runtime Modes](#runtime-modes)
Expand Down Expand Up @@ -47,10 +48,20 @@ This package must be used along with the [CRE CLI tool](https://github.com/smart

## Prerequisites

1. the [bun runtime](https://bun.com/). The wasm compilation currently is only supported by the bun runtime which has near-complete NodeJS compatibility.
1. the [bun runtime](https://bun.com/) for local tooling and workflow compilation.

2. the [CRE CLI tool](https://github.com/smartcontractkit/cre-cli) installed.

## Runtime Compatibility Constraints

CRE workflows are compiled to WASM and executed through Javy (QuickJS). This is **not** a full Node.js runtime.

- Node built-ins like `node:fs`, `node:crypto`, `node:http`, `node:net`, etc. are not supported in workflows.
- Browser globals like `fetch`, `window`, and `setTimeout` are also not available in workflow runtime.
- `cre compile:workflow` / `cre-compile` now validates workflow source and fails fast when unsupported APIs are used.

Use CRE capabilities (for example, `cre.capabilities.HTTPClient`) instead of direct Node/browser APIs.

## Getting Started

We recommend you consult the [getting started docs](https://docs.chain.link/cre/getting-started/cli-installation) and install the CRE CLI.
Expand Down
7 changes: 6 additions & 1 deletion packages/cre-sdk/bin/cre-compile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bun

import { main as compileWorkflow } from "../scripts/src/compile-workflow";
import { WorkflowRuntimeCompatibilityError } from "../scripts/src/validate-workflow-runtime-compat";

const main = async () => {
const cliArgs = process.argv.slice(2);
Expand All @@ -26,6 +27,10 @@ const main = async () => {

// CLI entry point
main().catch((e) => {
console.error(e);
if (e instanceof WorkflowRuntimeCompatibilityError) {
console.error(`\n❌ ${e.message}`);
} else {
console.error(e);
}
process.exit(1);
});
6 changes: 5 additions & 1 deletion packages/cre-sdk/scripts/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ const main = async () => {
process.exit(1)
}
} catch (error) {
console.error(`Failed to load script ${scriptName}:`, error)
if (error instanceof Error && error.name === 'WorkflowRuntimeCompatibilityError') {
console.error(error.message)
} else {
console.error(`Failed to run script ${scriptName}:`, error)
}
process.exit(1)
}
}
Expand Down
32 changes: 31 additions & 1 deletion packages/cre-sdk/scripts/src/build-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { glob } from 'fast-glob'
import { copyFile, mkdir } from 'fs/promises'
import { copyFile, mkdir, readFile, writeFile } from 'fs/promises'
import { join } from 'path'

const buildTypes = async () => {
Expand Down Expand Up @@ -28,6 +28,36 @@ const buildTypes = async () => {
}

console.log(`✅ Copied ${typeFiles.length} type definition file(s) to dist/sdk/types`)

// Prepend triple-slash references to dist/index.d.ts so consumers pick up
// global type augmentations (e.g. restricted-apis.d.ts) automatically.
// tsc strips these from the emitted .d.ts, so we add them back here.
const indexDts = join(packageRoot, 'dist/index.d.ts')
const sourceIndex = join(packageRoot, 'src/index.ts')
const sourceContent = await readFile(sourceIndex, 'utf-8')

const refsFromSource = sourceContent
.split('\n')
.filter((line) => line.startsWith('/// <reference types='))

// Add references for consumer-only type declarations that cannot be in src/index.ts
// because they would break the SDK's own scripts/tests (which legitimately use Node.js APIs).
const consumerOnlyRefs = ['/// <reference types="./sdk/types/restricted-node-modules" />']

const tripleSlashRefs = [...refsFromSource, ...consumerOnlyRefs].join('\n')

if (tripleSlashRefs) {
const indexContent = await readFile(indexDts, 'utf-8')
// Strip any existing triple-slash references from the top of the file
// so that re-running build-types is idempotent.
const withoutExistingRefs = indexContent
.split('\n')
.filter((line) => !line.startsWith('/// <reference types='))
.join('\n')
.replace(/^\n+/, '') // trim leading blank lines left after stripping
await writeFile(indexDts, `${tripleSlashRefs}\n${withoutExistingRefs}`)
console.log('✅ Added triple-slash references to dist/index.d.ts')
}
}

export const main = buildTypes
2 changes: 2 additions & 0 deletions packages/cre-sdk/scripts/src/compile-to-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
import { mkdir } from 'node:fs/promises'
import path from 'node:path'
import { $ } from 'bun'
import { assertWorkflowRuntimeCompatibility } from './validate-workflow-runtime-compat'
import { wrapWorkflowCode } from './workflow-wrapper'

export const main = async (tsFilePath?: string, outputFilePath?: string) => {
Expand All @@ -19,6 +20,7 @@ export const main = async (tsFilePath?: string, outputFilePath?: string) => {
}

const resolvedInput = path.resolve(inputPath)
assertWorkflowRuntimeCompatibility(resolvedInput)
console.info(`📁 Using input file: ${resolvedInput}`)

// If no explicit output path → same dir, swap extension to .js
Expand Down
Loading