Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
85c9cf4
remove node types and bun types from workflows
ernest-nowacki Feb 25, 2026
a94277d
Update comment
ernest-nowacki Feb 25, 2026
e62a01f
Expose console types through global and add examples that serve as te…
ernest-nowacki Feb 25, 2026
227700f
Add node modules restrictions
ernest-nowacki Feb 26, 2026
a776610
Add more restricted node APIs
ernest-nowacki Feb 26, 2026
1e8ca6a
Expose more apis through runtime
ernest-nowacki Feb 26, 2026
87fa5a8
TS fixes
ernest-nowacki Mar 2, 2026
afd1270
Fix prepare runtime
ernest-nowacki Mar 2, 2026
8352563
Add links to the docs
ernest-nowacki Mar 2, 2026
f55557d
Update versions
ernest-nowacki Mar 2, 2026
d809ee7
Add node modules examples
ernest-nowacki Mar 2, 2026
8934ae7
Add documentation around workflow validation
ernest-nowacki Mar 2, 2026
504996a
Add tests and fixed bug caught up by the test
ernest-nowacki Mar 3, 2026
c8a8218
Merge branch 'main' of github.com:smartcontractkit/cre-sdk-typescript…
ernest-nowacki Mar 3, 2026
28414a3
Set version for alpha
ernest-nowacki Mar 3, 2026
e742d67
Restore 1.1.3 version
ernest-nowacki Mar 3, 2026
ccc00e3
Simplify error messages
ernest-nowacki Mar 3, 2026
3a1136d
Alpha release 2
ernest-nowacki Mar 3, 2026
f4cf527
Restore correct versions
ernest-nowacki Mar 3, 2026
e775de4
Merge branch 'main' of github.com:smartcontractkit/cre-sdk-typescript…
ernest-nowacki Mar 9, 2026
d8acbcd
Update version, add option to use user tsconfig
ernest-nowacki Mar 9, 2026
bd0a140
Small fixes
ernest-nowacki Mar 9, 2026
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.

6 changes: 2 additions & 4 deletions packages/cre-sdk-examples/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@chainlink/cre-sdk-examples",
"private": true,
"version": "1.1.4",
"version": "1.2.0",
"type": "module",
"author": "Ernest Nowacki",
"license": "BUSL-1.1",
Expand All @@ -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": [],
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to make sure the cre-cli tsconfigs match, or do they pull in this file directly now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will follow up with CLI PR that initialize project with this settings.


// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
Expand Down
Binary file modified packages/cre-sdk-javy-plugin/dist/javy_chainlink_sdk.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/cre-sdk-javy-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/cre-sdk-javy-plugin",
"version": "1.1.1",
"version": "1.2.0",
"type": "module",
"bin": {
"cre-setup": "bin/setup.ts",
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);
});
2 changes: 1 addition & 1 deletion packages/cre-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/cre-sdk",
"version": "1.1.4",
"version": "1.2.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
8 changes: 7 additions & 1 deletion packages/cre-sdk/scripts/run.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bun

import { WorkflowRuntimeCompatibilityError } from './src/validate-workflow-runtime-compat'

const availableScripts = [
'build-types',
'compile-to-js',
Expand Down Expand Up @@ -37,7 +39,11 @@ const main = async () => {
process.exit(1)
}
} catch (error) {
console.error(`Failed to load script ${scriptName}:`, error)
if (error instanceof WorkflowRuntimeCompatibilityError) {
console.error(`\n❌ ${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.trim().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
Loading