diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index b541d804798..75f13155597 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -387,6 +387,14 @@ export const SIDEBAR: Partial> = { "cre/guides/workflow/using-http-client/submitting-reports-http-go", ], }, + { + title: "Verifying CRE Reports Offchain", + url: "cre/guides/workflow/using-http-client/verifying-reports-offchain", + highlightAsCurrent: [ + "cre/guides/workflow/using-http-client/verifying-reports-offchain-ts", + "cre/guides/workflow/using-http-client/verifying-reports-offchain-go", + ], + }, ], }, { diff --git a/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx b/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx index ca4ca5793d8..364730964e5 100644 --- a/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx +++ b/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx @@ -11,7 +11,9 @@ metadata: import { Aside } from "@components" -This guide shows how to manually generate a report containing a single value (like `uint256`, `address`, or `bool`). This is useful when you need to send a simple value onchain but don't have a struct or binding helper available. +This guide shows how to manually generate a **[CRE report](/cre/key-terms#report-cre-report)** containing a single value (like `uint256`, `address`, or `bool`). See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition; in short, it is a DON-signed package from [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) with your encoded data, workflow metadata, and signatures. + +This guide covers **creating** the signed report ([`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime)). **Delivering** it is a separate step: see the table below. **Use this approach when:** @@ -29,12 +31,14 @@ This guide shows how to manually generate a report containing a single value (li Manually generating a report for a single value involves two main steps: 1. **ABI-encode the value** into bytes using the `go-ethereum/accounts/abi` package -1. **Generate a cryptographically signed report** using `runtime.GenerateReport()` +1. **Generate a cryptographically signed report** using [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) -The resulting report can then be: +| After [`GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) | Guide | Who verifies? | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport) / [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | +| [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) / [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | -- Submitted to the blockchain via `evm.Client.WriteReport()` (see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)) -- Sent to an HTTP endpoint via `http.Client` (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http)) +See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is the signed output your workflow DON produces when you call [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). It packages your encoded data (the payload you computed in the callback), fixed workflow metadata, a report context (config digest and sequence number), and ECDSA signatures from DON nodes. + +This guide covers the **sender** path: create the report in your workflow, then POST it to an HTTP endpoint. The receiver must verify those signatures before trusting the data. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) on the receiver side. + +## Where this guide fits + +| Question | Answer | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | A [CRE report](/cre/key-terms#report-cre-report): output of [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) after DON consensus (encoded payload + metadata + signatures). | +| Where does it come from? | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | +| What does this guide cover? | Steps 3–4 below: [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime), then [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. | +| Who verifies it? | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). | + +**Sender flow in one workflow execution:** + +1. Trigger fires (cron, HTTP, …). +2. Your callback runs (API calls, encoding, etc.). +3. [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime): DON produces a signed `ReportResponse`. +4. [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport): format and POST to your URL. + ## Prerequisites - Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request) -- Familiarity with `runtime.report()` (covered [below](#generating-reports-for-http-submission)) +- Familiarity with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (covered [below](#generating-reports-for-http-submission)) +- **`viem`** as a direct dependency in the workflow `package.json` (for ABI encoding in examples) +- Protobuf HTTP/report types from **`@chainlink/cre-sdk/pb`** (`SDK_PB.ReportResponse`, `HTTP_CLIENT_PB.RequestJson`), not the main `@chainlink/cre-sdk` entry {/* prettier-ignore */} -## Quick start: Minimal example +## Payload contract (if you verify offchain) -Here's the simplest possible workflow that generates and submits a report via HTTP: +If a receiver uses [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts), your HTTP body must expose three fields. The verify guide expects **hex without `0x` in JSON** (field name `context`, not `reportContext`): + +| Your JSON field | SDK field on `ReportResponse` | +| --------------- | ----------------------------- | +| `report` | `rawReport` | +| `context` | `reportContext` | +| `signatures` | `sigs[].signature` | + +Use [Pattern 4 for offchain verification (hex)](#pattern-4-for-offchain-verification-hex) or the [complete working example](#complete-working-example). Other patterns are for APIs with different formats, not the default verify examples. + +## Minimal example (binary) + +This example POSTs **raw report bytes** (`application/octet-stream`). Use this if your API accepts raw binary. It is **not** compatible with the verify guide’s JSON receiver — for the sender → verify flow, use the [complete working example](#complete-working-example) instead. + +Here’s the simplest workflow that generates and submits a report via HTTP: ```typescript -import { ok, type ReportResponse, type RequestJson, type HTTPSendRequester } from "@chainlink/cre-sdk" +import { ok, type HTTPSendRequester, type Report } from "@chainlink/cre-sdk" +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson const formatReportSimple = (r: ReportResponse): RequestJson => { return { @@ -51,8 +93,8 @@ const formatReportSimple = (r: ReportResponse): RequestJson => { "Content-Type": "application/octet-stream", }, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old + store: true, + maxAge: "60s", }, } } @@ -71,7 +113,7 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): { succe **What's happening here:** 1. `formatReportSimple` transforms the report into an HTTP request that your API understands -1. `sendRequester.sendReport()` calls your transformation function and sends the request +1. [`sendRequester.sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) calls your transformation function and sends the request 1. The SDK handles consensus and returns the result The rest of this guide explains how this works and shows different formatting patterns for various API requirements. @@ -80,7 +122,7 @@ The rest of this guide explains how this works and shows different formatting pa ### The report structure -When you call `runtime.report()`, the SDK creates a `ReportResponse` containing: +After [consensus](/cre/key-terms#consensus), [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) returns a `ReportResponse`: the wire-format view of a [CRE report](/cre/key-terms#report-cre-report). It contains: ```typescript interface ReportResponse { @@ -109,7 +151,7 @@ Your transformation function tells the SDK how to format the report for your API **The SDK calls this function internally:** -1. You pass your transformation function to `sendReport()` +1. You pass your transformation function to [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) 1. The SDK calls it with the generated `ReportResponse` 1. Your function returns a `RequestJson` formatted for your API 1. The SDK sends the request and handles consensus @@ -126,6 +168,15 @@ The transformation function gives you complete control over the format. Here are common patterns for formatting reports. Choose the one that matches your API's requirements. +All patterns below use protobuf types and caching settings: + +```typescript +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson +``` + ### Choosing the right pattern | Pattern | When to use | @@ -140,8 +191,6 @@ Here are common patterns for formatting reports. Choose the one that matches you Use this when your API accepts raw binary data: ```typescript -import type { ReportResponse, RequestJson } from "@chainlink/cre-sdk" - const formatReportSimple = (r: ReportResponse): RequestJson => { return { url: "https://api.example.com/reports", @@ -151,8 +200,8 @@ const formatReportSimple = (r: ReportResponse): RequestJson => { "Content-Type": "application/octet-stream", }, cacheSettings: { - readFromCache: true, // Enable caching - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old + store: true, + maxAge: "60s", }, } } @@ -198,8 +247,8 @@ const formatReportWithSignatures = (r: ReportResponse): RequestJson => { "Content-Type": "application/octet-stream", }, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, + store: true, + maxAge: "60s", }, } } @@ -230,8 +279,8 @@ const formatReportWithHeaderSigs = (r: ReportResponse): RequestJson => { body: Buffer.from(r.rawReport).toString("base64"), headers, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, + store: true, + maxAge: "60s", }, } } @@ -273,13 +322,53 @@ const formatReportAsJSON = (r: ReportResponse): RequestJson => { "Content-Type": "application/json", }, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, + store: true, + maxAge: "60s", }, } } ``` +### Pattern 4 for offchain verification (hex) + +Use this variant when testing the [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) receiver in simulation. The verify examples decode **hex without a `0x` prefix**; the receiver adds `0x` when calling `hexToBytes`. + +Pattern 4 in the block above uses **base64** fields. Base64 sender output does **not** match the verify guide’s hex decoder without changes. + +```typescript +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" +import { bytesToHex } from "viem" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson + +const formatReportAsJSONHex = + (config: { apiUrl: string }) => + (r: ReportResponse): RequestJson => { + const payload = { + report: bytesToHex(r.rawReport).slice(2), + context: bytesToHex(r.reportContext).slice(2), + signatures: r.sigs.map((sig) => bytesToHex(sig.signature).slice(2)), + } + const bodyBytes = new TextEncoder().encode(JSON.stringify(payload)) + + return { + url: config.apiUrl, + method: "POST", + body: Buffer.from(bodyBytes).toString("base64"), + headers: { + "Content-Type": "application/json", + }, + cacheSettings: { + store: true, + maxAge: "60s", + }, + } + } +``` + +The `body` field is the UTF-8 JSON string, base64-encoded for the protobuf `bytes` field (same pattern as other JSON POST bodies in TypeScript workflows). + ### Understanding `cacheSettings` for reports You'll notice that all the patterns above include `cacheSettings`. This is critical for report submissions, just like it is for [POST requests](/cre/guides/workflow/using-http-client/post-request). @@ -312,7 +401,7 @@ This approach is reliable because the `rawReport` is identical across all nodes ## Generating reports for HTTP submission -Before you can submit a report via HTTP, you need to generate it using `runtime.report()`. This creates a cryptographically signed report from your encoded data. +Before you can submit a report via HTTP, you need to generate it using [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). This creates a cryptographically signed report from your encoded data. **Basic pattern:** @@ -336,11 +425,11 @@ const report = runtime // Step 3: Submit via HTTP (covered in next section) ``` -The `runtime.report()` method works the same way whether you're encoding a single value or a struct—just use Viem's `encodeAbiParameters()` with the appropriate ABI types. For detailed examples on encoding single values, structs, and complex types, see the [Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain) guide. +The [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) method works the same way whether you're encoding a single value or a struct—just use Viem's `encodeAbiParameters()` with the appropriate ABI types. For detailed examples on encoding single values, structs, and complex types, see the [Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain) guide. ## Using `sendReport()` (recommended approach) -Use the high-level `httpClient.sendRequest()` pattern with `sendRequester.sendReport()`: +Use the high-level `httpClient.sendRequest()` pattern with [`sendRequester.sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport): ```typescript import { @@ -356,7 +445,11 @@ interface SubmitResponse { success: boolean } -const submitReportViaHTTP = (sendRequester: HTTPSendRequester, report: Report): SubmitResponse => { +const submitReportViaHTTP = ( + runtime: Runtime, + sendRequester: HTTPSendRequester, + report: Report +): SubmitResponse => { const response = sendRequester.sendReport(report, formatReportSimple).result() if (!ok(response)) { @@ -377,7 +470,7 @@ const onCronTrigger = (runtime: Runtime): MyResult => { const result = httpClient .sendRequest( runtime, - (sendRequester: HTTPSendRequester) => submitReportViaHTTP(sendRequester, report), + (sendRequester: HTTPSendRequester) => submitReportViaHTTP(runtime, sendRequester, report), consensusIdenticalAggregation() )() .result() @@ -391,25 +484,30 @@ const onCronTrigger = (runtime: Runtime): MyResult => { This example shows a workflow that: 1. Generates a report from a single value -1. Submits it to an HTTP API -1. Uses the simple "report in body" format +1. Submits it to an HTTP API as **Pattern 4 JSON with hex fields** (compatible with the verify guide’s receiver sim loop) +1. Uses `config.apiUrl` from your target config file + +Add **`viem`** to `package.json`. Register handlers with `handler()` from `@chainlink/cre-sdk` (not `cron.handler()`). ```typescript import { CronCapability, HTTPClient, Runner, + handler, consensusIdenticalAggregation, hexToBase64, ok, type Runtime, - type Report, type CronPayload, type HTTPSendRequester, - type ReportResponse, - type RequestJson, + type Report, } from "@chainlink/cre-sdk" -import { encodeAbiParameters, parseAbiParameters } from "viem" +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" +import { encodeAbiParameters, parseAbiParameters, bytesToHex } from "viem" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson interface Config { apiUrl: string @@ -424,32 +522,43 @@ type MyResult = Record const initWorkflow = (config: Config) => { const cron = new CronCapability() - - return [cron.handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] + return [handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] } -// Transformation function: defines how the API expects the report -const formatReportForMyAPI = (r: ReportResponse): RequestJson => { - return { - url: "https://webhook.site/your-unique-id", // Replace with your API - method: "POST", - body: Buffer.from(r.rawReport).toString("base64"), - headers: { - "Content-Type": "application/octet-stream", - "X-Report-SeqNr": r.seqNr.toString(), - }, - cacheSettings: { - readFromCache: true, // Prevent duplicate submissions - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old - }, +const formatReportForMyAPI = + (config: Config) => + (r: ReportResponse): RequestJson => { + const payload = { + report: bytesToHex(r.rawReport).slice(2), + context: bytesToHex(r.reportContext).slice(2), + signatures: r.sigs.map((sig) => bytesToHex(sig.signature).slice(2)), + } + const bodyBytes = new TextEncoder().encode(JSON.stringify(payload)) + + return { + url: config.apiUrl, + method: "POST", + body: Buffer.from(bodyBytes).toString("base64"), + headers: { + "Content-Type": "application/json", + "X-Report-SeqNr": r.seqNr.toString(), // optional metadata for your API + }, + cacheSettings: { + store: true, + maxAge: "60s", + }, + } } -} -// Function that submits the report via HTTP -const submitReportViaHTTP = (sendRequester: HTTPSendRequester, report: Report): SubmitResponse => { - runtime.log("Submitting report to API") +const submitReportViaHTTP = ( + runtime: Runtime, + sendRequester: HTTPSendRequester, + report: Report, + config: Config +): SubmitResponse => { + runtime.log(`Submitting report to API: ${config.apiUrl}`) - const response = sendRequester.sendReport(report, formatReportForMyAPI).result() + const response = sendRequester.sendReport(report, formatReportForMyAPI(config)).result() runtime.log(`Report submitted - status: ${response.statusCode}, bodyLength: ${response.body.length}`) @@ -461,16 +570,13 @@ const submitReportViaHTTP = (sendRequester: HTTPSendRequester, report: Report): return { success: true } } -const onCronTrigger = (runtime: Runtime, payload: CronPayload): MyResult => { - // Step 1: Generate a report (example: a single uint256 value) +const onCronTrigger = (runtime: Runtime, _payload: CronPayload): MyResult => { const myValue = 123456789n runtime.log(`Generating report with value: ${myValue}`) - // Encode the value using Viem const encodedValue = encodeAbiParameters(parseAbiParameters("uint256 value"), [myValue]) - // Generate the report - const reportResponse = runtime + const report = runtime .report({ encodedPayload: hexToBase64(encodedValue), encoderName: "evm", @@ -481,13 +587,12 @@ const onCronTrigger = (runtime: Runtime, payload: CronPayload): MyResult runtime.log("Report generated successfully") - // Step 2: Submit the report via HTTP const httpClient = new HTTPClient() const submitResult = httpClient .sendRequest( runtime, - (sendRequester: HTTPSendRequester) => submitReportViaHTTP(sendRequester, reportResponse), + (sendRequester: HTTPSendRequester) => submitReportViaHTTP(runtime, sendRequester, report, runtime.config), consensusIdenticalAggregation() )() .result() @@ -514,12 +619,21 @@ export async function main() { ### Testing with webhook.site 1. Go to [webhook.site](https://webhook.site/) and get a unique URL -1. Update `config.json` with your webhook URL -1. Run the simulation: +1. Update `config.json` (or `config.staging.json`) with your webhook URL in `apiUrl` +1. From the **CRE project root**, run the simulation: ```bash cre workflow simulate my-workflow --target staging-settings ``` -1. Check webhook.site to see the report data received +1. On webhook.site, open the request **Content** tab. You should see JSON with `report`, `context`, and `signatures` (hex strings). Use that JSON to test a receiver workflow in [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts#testing-locally-with-simulation). + +{/* prettier-ignore */} + + +## Next step: verify on the receiver + +The sender does not validate the report for the receiver. After submission, the ingesting side must verify signatures before trusting the payload. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). ## Advanced: Low-level pattern @@ -560,15 +674,13 @@ const onCronTrigger = (runtime: Runtime): MyResult => { 1. **Always use `cacheSettings`**: Include caching in every transformation function to prevent worst-case duplicate submission scenarios 1. **Implement API-side deduplication**: Your receiving API must implement deduplication using the **hash of the report** (`keccak256(rawReport)`) to detect and reject duplicate submissions -1. **Verify signatures before processing**: Your API must verify the cryptographic signatures against DON public keys before trusting report data (see note below about signature verification) +1. **Verify on the receiver**: The sender does not validate the report; your API or a [receiver CRE workflow](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) must verify before trusting payload data 1. **Match your API's format exactly**: Study your API's documentation to understand the expected format (binary, JSON, headers, etc.) 1. **Handle errors gracefully**: Check HTTP status codes and provide meaningful error messages {/* prettier-ignore */} ## Troubleshooting @@ -584,9 +696,17 @@ const onCronTrigger = (runtime: Runtime): MyResult => { - Your report format likely doesn't match what your API expects - Check if your API expects base64 encoding, JSON wrapping, or specific headers +**TypeScript compile errors on the complete example** + +- Use `handler(cron.trigger(...), fn)` from `@chainlink/cre-sdk`, not `cron.handler()` +- Import `ReportResponse` / `RequestJson` from `@chainlink/cre-sdk/pb` +- Pass `runtime` from the trigger callback into helper functions (no global `runtime`) +- Add `viem` as a direct workflow dependency + ## Learn more -- **[HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts)** — Complete API reference including `sendReport()` and `ReportResponse` -- **[POST Requests](/cre/guides/workflow/using-http-client/post-request)** — Learn about HTTP request patterns and caching -- **[Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain)** — Detailed guide on encoding single values, structs, and complex types using Viem -- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)** — Alternative: Submit reports to smart contracts instead of HTTP +- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts):** verify signatures on the receiver before trusting payload data +- **[HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts):** complete API reference including `sendReport()` and `ReportResponse` +- **[POST Requests](/cre/guides/workflow/using-http-client/post-request):** HTTP request patterns and caching +- **[Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain):** encoding single values, structs, and complex types using Viem +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain):** submit reports to smart contracts instead of HTTP diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx new file mode 100644 index 00000000000..d87b1fffa39 --- /dev/null +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx @@ -0,0 +1,448 @@ +--- +section: cre +title: "Verifying CRE Reports Offchain" +date: Last Modified +sdkLang: "go" +pageId: "guides-workflow-http-verify-reports-offchain" +metadata: + description: "Verify CRE report signatures offchain in Go: parse reports, validate DON signatures against the onchain registry, and read workflow metadata." + datePublished: "2026-05-20" + lastModified: "2026-05-20" +--- + +import { Aside } from "@components" + +This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. + +When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** + +The CRE SDK provides [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) to do this inside a workflow. Verification runs offchain in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. + +{/* prettier-ignore */} + + +{/* prettier-ignore */} + + +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport), then read trusted metadata and `Body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + +## Where this guide fits + +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before you use `Body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | + +**Receiver flow:** + +1. HTTP trigger (or your API) receives the POST payload. +2. Decode hex fields into bytes. +3. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport): verify signatures and read metadata. +4. Use trusted `Body()` in your logic. + +Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. This guide covers local simulation first, then the deploy example with `AuthorizedKeys`. + +## What you'll learn + +- When to verify reports offchain vs relying on onchain forwarders +- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata +- How to build a receiver workflow that accepts reports over HTTP +- How to restrict verification to specific CRE environments or zones + +## Prerequisites + +- **SDK**: `cre-sdk-go` v1.8.0 or later (report verification support) +- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) (report structure and JSON payload patterns) +- For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-go) + +## Onchain vs offchain verification + +| Aspect | Offchain ([`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport)) | Onchain (`KeystoneForwarder`) | +| -------------------- | --------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | + +Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. + +Default (`cre.ProductionEnvironment()`): + +- **Chain**: Ethereum Mainnet (chain selector `5009297550715157269`) +- **Registry**: `0x76c9cf548b4179F8901cda1f8623568b58215E62` + +## How verification works + +1. **Parse the report header** from `rawReport` (109-byte metadata + body). +2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. +3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. +4. **Return a `*cre.Report`** with accessors for workflow ID, owner, execution ID, body, and more. + +If verification fails, [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). + +## Testing locally with simulation + +After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-go#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. + +1. Save the webhook JSON as `test-report-payload.json` in your receiver workflow folder. +2. Register `http.Trigger(&http.Config{})` (empty config) for simulation. +3. From the **CRE project root**, run `cre workflow simulate` with `--http-payload verify-report-receiver/test-report-payload.json` (path relative to where you invoke `cre`). + +### Minimal receiver for simulation + +Use an empty HTTP trigger for sim. Set `SkipSignatureVerification: true` in staging config (or pass it to [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig)). The CLI delivers `--http-payload` file contents as `payload.Input` bytes. + +`config.staging.json`: + +```json +{ + "skipSignatureVerification": true +} +``` + +`main.go`: + +```go +//go:build wasip1 + +package main + +import ( + "encoding/hex" + "encoding/json" + "log/slog" + + "github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http" + "github.com/smartcontractkit/cre-sdk-go/cre" + "github.com/smartcontractkit/cre-sdk-go/cre/wasm" +) + +type Config struct { + SkipSignatureVerification bool `json:"skipSignatureVerification"` +} + +type parsedPayload struct { + Report string `json:"report"` + Context string `json:"context"` + Signatures []string `json:"signatures"` +} + +func InitWorkflow(_ *Config, _ *slog.Logger, _ cre.SecretsProvider) (cre.Workflow[*Config], error) { + return cre.Workflow[*Config]{ + cre.Handler(http.Trigger(&http.Config{}), run), + }, nil +} + +func run(cfg *Config, runtime cre.Runtime, payload *http.Payload) (bool, error) { + var parsed parsedPayload + if err := json.Unmarshal(payload.Input, &parsed); err != nil { + return false, err + } + + rawReport, err := hex.DecodeString(parsed.Report) + if err != nil { + return false, err + } + reportContext, err := hex.DecodeString(parsed.Context) + if err != nil { + return false, err + } + sigs := make([][]byte, len(parsed.Signatures)) + for i, sigHex := range parsed.Signatures { + sigs[i], err = hex.DecodeString(sigHex) + if err != nil { + return false, err + } + } + + report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, cre.ReportParseConfig{ + SkipSignatureVerification: cfg.SkipSignatureVerification, + }) + if err != nil { + return false, err + } + + runtime.Logger().Info("Verified report", + "workflowId", report.WorkflowID(), + "executionId", report.ExecutionID(), + "donId", report.DONID(), + ) + + _ = report.Body() + return true, nil +} + +func main() { + wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow) +} +``` + +### Sim wiring vs full verify + +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig) | JSON + hex decode, metadata accessors, `Body()` | +| **Full crypto verify** | Default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification. | + +```bash +cre workflow simulate verify-report-receiver \ + --target staging-settings \ + --non-interactive \ + --trigger-index 0 \ + --http-payload verify-report-receiver/test-report-payload.json +``` + +**Pass criteria** + +- **Sim wiring:** `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig): logs show metadata and a successful handler return. +- **Full crypto verify:** default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) with a **production-signed** report (not typical for sender-sim → receiver-sim alone). + +`project.yaml` needs **`ethereum-mainnet` RPC** for default verify (registry reads). + +## Complete example: HTTP receiver workflow (deploy) + +Once simulation is working, update the trigger config for deployment with `AuthorizedKeys`. + +It accepts JSON with hex `report`, `context`, and `signatures` from the submit guide’s [complete working example](/cre/guides/workflow/using-http-client/submitting-reports-http-go#complete-working-example) or [Pattern 4 for offchain verification (hex)](/cre/guides/workflow/using-http-client/submitting-reports-http-go#pattern-4-for-offchain-verification-hex), not base64 [Pattern 4](/cre/guides/workflow/using-http-client/submitting-reports-http-go#pattern-4-json-formatted-report) unless you change decoding. + +```go +//go:build wasip1 + +package main + +import ( + "encoding/hex" + "encoding/json" + "log/slog" + + "github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http" + "github.com/smartcontractkit/cre-sdk-go/cre" + "github.com/smartcontractkit/cre-sdk-go/cre/wasm" +) + +type Config struct { + AuthorizedKey string `json:"authorized_key"` +} + +func InitWorkflow(cfg *Config, _ *slog.Logger, _ cre.SecretsProvider) (cre.Workflow[*Config], error) { + return cre.Workflow[*Config]{ + cre.Handler(http.Trigger(&http.Config{AuthorizedKeys: []*http.AuthorizedKey{{PublicKey: cfg.AuthorizedKey}}}), run), + }, nil +} + +type ParsedPayload struct { + Report string `json:"report"` + Context string `json:"context"` + Sigs []string `json:"signatures"` +} + +func (p *ParsedPayload) Decode() (*DecodedReport, error) { + report := &DecodedReport{} + var err error + + if report.Report, err = hex.DecodeString(p.Report); err != nil { + return nil, err + } + if report.Context, err = hex.DecodeString(p.Context); err != nil { + return nil, err + } + + report.Sigs = make([][]byte, len(p.Sigs)) + for i, sigHex := range p.Sigs { + report.Sigs[i], err = hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + } + + return report, nil +} + +type DecodedReport struct { + Report []byte + Context []byte + Sigs [][]byte +} + +func run(_ *Config, runtime cre.Runtime, payload *http.Payload) (bool, error) { + parsed := &ParsedPayload{} + if err := json.Unmarshal(payload.Input, parsed); err != nil { + return false, err + } + + decoded, err := parsed.Decode() + if err != nil { + return false, err + } + + report, err := cre.ParseReport(runtime, decoded.Report, decoded.Sigs, decoded.Context) + if err != nil { + return false, err + } + + runtime.Logger().Info("Verified report", + "workflowId", report.WorkflowID(), + "executionId", report.ExecutionID(), + ) + + // Use report.Body() for your application logic (ABI-encoded payload from the sender workflow) + _ = report.Body() + + return true, nil +} + +func main() { + wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow) +} +``` + +**What's happening:** + +1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. +2. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) verifies signatures against the production CRE registry. +3. On success, you read metadata and `Body()` safely. + +{/* prettier-ignore */} + + +## Report payload format + +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields. The JSON key is `context` even though the SDK field is `ReportContext`: + +| JSON field | SDK field | Description | +| ------------ | --------------- | --------------------------------------------------------------- | +| `report` | `RawReport` | Hex-encoded bytes (metadata header + workflow payload), no `0x` | +| `context` | `ReportContext` | Hex-encoded config digest + sequence number | +| `signatures` | `Sigs` | Array of hex-encoded 65-byte ECDSA signatures, no `0x` | + +The `reportContext` layout used by the SDK: + +- Bytes 0–31: config digest +- Bytes 32–39: sequence number (big-endian `uint64`) + +## API reference + +See [SDK Reference: Core: Report verification](/cre/reference/sdk/core-go#report-verification) for full signatures, types, and errors. + +### `cre.ParseReport()` + +```go +func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) +``` + +Parses and verifies a report against the production CRE environment. Use [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig) for custom environments or zones. + +### `*cre.Report` accessors + +After a successful parse: + +| Method | Description | +| ----------------- | ----------------------------------------- | +| `WorkflowID()` | Workflow hash (`bytes32` as hex) | +| `WorkflowOwner()` | Deployer address (hex) | +| `WorkflowName()` | Workflow name field from metadata | +| `ExecutionID()` | Unique execution identifier | +| `DONID()` | DON that produced the report | +| `Timestamp()` | Report timestamp (Unix seconds) | +| `Body()` | Encoded payload after the 109-byte header | +| `SeqNr()` | Sequence number from report context | +| `ConfigDigest()` | Config digest from report context | + +### `cre.ReportParseConfig` + +```go +config := cre.ReportParseConfig{ + AcceptedZones: []cre.Zone{ + cre.ZoneFromEnvironment(cre.ProductionEnvironment(), 1), + }, + AcceptedEnvironments: []cre.Environment{cre.ProductionEnvironment()}, + SkipSignatureVerification: false, +} +report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, config) +``` + +| Field | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------- | +| `AcceptedEnvironments` | Registry environments to check (defaults to production) | +| `AcceptedZones` | Restrict to specific DON IDs within an environment | +| `SkipSignatureVerification` | Parse header only; call `report.VerifySignatures()` or `VerifySignaturesWithConfig()` afterward when ready | + +### Deferred verification + +This is a **different pattern** from the simulation testing use of `SkipSignatureVerification`. In testing, you skip verification permanently. Here, you parse the header first to inspect metadata (such as `WorkflowID()` or `DONID()` for filtering), then call `VerifySignatures` in a separate step — useful when you want to gate registry reads on workflow identity checks. + +If you set `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig), parse the header first, then verify: + +```go +report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, cre.ReportParseConfig{ + SkipSignatureVerification: true, +}) +if err != nil { + return false, err +} + +// Optional: inspect report.WorkflowID(), report.DONID(), etc. before registry reads + +if err := report.VerifySignatures(runtime); err != nil { + return false, err +} +``` + +## Best practices + +1. **Verify before side effects**: Call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before writing to databases, chains, or external systems. +2. **Permission on metadata**: After verification, check `WorkflowID()`, `WorkflowOwner()`, or `DONID()` match your expectations. +3. **Deduplicate by execution ID**: Use `ExecutionID()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#understanding-cachesettings-for-reports)). +4. **Do not skip signature verification in production** unless you have another trust path. + +## Troubleshooting + +**`ErrUnknownSigner` / `invalid signature` in sim with fresh webhook JSON** + +- **Expected** when using default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- For local wiring tests, use `SkipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. + +**`ErrUnknownSigner` (deployed)** + +- Signatures may be from a different DON or stale registry config. +- Confirm the sender workflow used production CRE and the report was not tampered with. + +**Wrong `--http-payload` path** + +- Invoke `cre` from the **project root**. Use `verify-report-receiver/test-report-payload.json`, not a bare filename unless your cwd matches. + +**Receiver JSON / hex decode error** + +- You copied a **binary** webhook body instead of Pattern 4 JSON with hex fields. + +**`ErrWrongSignatureCount`** + +- At least **f+1** valid signatures are required. + +**`could not read from chain ...`** + +- Registry read failed (RPC/network). Configure **`ethereum-mainnet` RPC** in `project.yaml` (required for default verify, including sim). + +**`ErrRawReportTooShort`** + +- `rawReport` is missing the 109-byte metadata header. + +## Learn more + +- **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go):** sender workflow; create and POST the report +- **[SDK Reference: Core: Report verification](/cre/reference/sdk/core-go#report-verification):** `ParseReport`, `Report`, and `ReportParseConfig` +- **[HTTP Trigger Overview](/cre/guides/workflow/using-triggers/http-trigger/overview-go):** trigger deployed receiver workflows +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain):** onchain forwarder verification path +- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts):** permissioning `onReport` with workflow metadata diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx new file mode 100644 index 00000000000..ed6a799d911 --- /dev/null +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx @@ -0,0 +1,398 @@ +--- +section: cre +title: "Verifying CRE Reports Offchain" +date: Last Modified +sdkLang: "ts" +pageId: "guides-workflow-http-verify-reports-offchain" +metadata: + description: "Verify CRE report signatures offchain in TypeScript: parse reports, validate DON signatures against the onchain registry, and read workflow metadata." + datePublished: "2026-05-20" + lastModified: "2026-05-20" +--- + +import { Aside } from "@components" + +This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. + +When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** + +The CRE SDK provides [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. + +{/* prettier-ignore */} + + +{/* prettier-ignore */} + + +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification), then read trusted metadata and `body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + +## Where this guide fits + +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before you use `body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | + +**Receiver flow:** + +1. HTTP trigger (or your API) receives the POST payload. +2. Decode hex fields into bytes. +3. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification): verify signatures and read metadata. +4. Use trusted `body()` in your logic. + +Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. This guide covers local simulation first, then the deploy example with `authorizedKeys`. + +## What you'll learn + +- When to verify reports offchain vs relying on onchain forwarders +- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata +- How to build a receiver workflow that accepts reports over HTTP +- How to restrict verification to specific CRE environments or zones + +## Prerequisites + +- **SDK**: `@chainlink/cre-sdk` v1.8.0 or later (report verification support) +- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) (report structure and JSON payload patterns) +- For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-ts) + +## Onchain vs offchain verification + +| Aspect | Offchain ([`Report.parse()`](/cre/reference/sdk/core-ts#report-verification)) | Onchain (`KeystoneForwarder`) | +| -------------------- | ----------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | + +Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. + +Default (`productionEnvironment()`): + +- **Chain**: Ethereum Mainnet (chain selector `5009297550715157269`) +- **Registry**: `0x76c9cf548b4179F8901cda1f8623568b58215E62` + +## How verification works + +1. **Parse the report header** from `rawReport` (109-byte metadata + body). +2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. +3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. +4. **Return a `Report` object** with accessors for workflow ID, owner, execution ID, body, and more. + +If verification fails, [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) throws (for example, unknown signer, insufficient signatures, or registry read failure). + +## Testing locally with simulation + +After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. + +1. Save the webhook JSON as `test-report-payload.json` in your receiver workflow folder. +2. Use the minimal receiver below with an **empty HTTP trigger config** (no `authorizedKeys` until deploy). +3. From the **CRE project root**, run `cre workflow simulate` with `--http-payload verify-report-receiver/test-report-payload.json` (path relative to where you invoke `cre`). + +### Sim wiring vs full verify + +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `skipSignatureVerification: true` in staging config | JSON + hex decode, `workflowId()`, `executionId()`, `donId()`, `body()` | +| **Full crypto verify** | Default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification: simulation uses local DON keys; [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) checks the mainnet Capability Registry. | + +### Minimal receiver for simulation + +Use an **empty HTTP trigger config** for sim (add `authorizedKeys` before deploy). Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) from your handler with the `runtime` parameter. The CLI delivers `--http-payload` file contents as `payload.input` bytes. + +`config.staging.json`: + +```json +{ + "skipSignatureVerification": true +} +``` + +`main.ts`: + +```typescript +import { + decodeJson, + handler, + hexToBytes, + HTTPCapability, + Report, + Runner, + type HTTPPayload, + type Runtime, +} from "@chainlink/cre-sdk" + +interface Config { + skipSignatureVerification?: boolean +} + +type ParsedPayload = { + report: string + context: string + signatures: string[] +} + +/** Hex without 0x prefix in JSON → bytes (add 0x before decode). */ +const fromHexNoPrefix = (hex: string): Uint8Array => hexToBytes(`0x${hex}`) + +/** AggregateError from Report.parse often has an empty .message in sim output. */ +const formatError = (err: unknown): string => { + if (err instanceof AggregateError) { + const parts = err.errors.map((e) => (e instanceof Error ? e.message : String(e))) + return parts.join("; ") || "report verification failed" + } + if (err instanceof Error) return err.message + return String(err) +} + +export async function run(runtime: Runtime, payload: HTTPPayload): Promise<{ verified: boolean }> { + try { + const parsed = decodeJson(payload.input) as ParsedPayload + + const rawReport = fromHexNoPrefix(parsed.report) + const reportContext = fromHexNoPrefix(parsed.context) + const sigs = parsed.signatures.map((s) => fromHexNoPrefix(s)) + + runtime.log(`Parsing report (${rawReport.length} bytes, ${sigs.length} signatures)`) + + const report = await Report.parse(runtime, rawReport, sigs, reportContext, { + skipSignatureVerification: runtime.config.skipSignatureVerification ?? false, + }) + + runtime.log( + `Verified report workflowId=${report.workflowId()} executionId=${report.executionId()} donId=${report.donId()}` + ) + report.body() + return { verified: true } + } catch (err) { + const msg = formatError(err) + runtime.log(`Report verification failed: ${msg}`) + throw new Error(msg) + } +} + +export const initWorkflow = () => { + const http = new HTTPCapability() + return [handler(http.trigger({}), run)] +} + +export async function main() { + const runner = await Runner.newRunner() + await runner.run(initWorkflow) +} +``` + +Save webhook JSON as `verify-report-receiver/test-report-payload.json`. From the **CRE project root**: + +```bash +cre workflow simulate verify-report-receiver \ + --target staging-settings \ + --non-interactive \ + --trigger-index 0 \ + --http-payload verify-report-receiver/test-report-payload.json +``` + +**Pass criteria** + +- **Sim wiring:** `skipSignatureVerification: true`: logs show metadata and `{ verified: true }`. +- **Full crypto verify:** default config with a **production-signed** report (not typical for sender-sim → receiver-sim alone). + +## Complete example: HTTP receiver workflow (deploy) + +Once simulation is working, update the trigger config for deployment with `authorizedKeys`. + +It accepts JSON with hex `report`, `context`, and `signatures` (from the submit guide’s [complete working example](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#complete-working-example) or [Pattern 4 for offchain verification (hex)](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-for-offchain-verification-hex)), not base64 [Pattern 4](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-json-formatted-report) unless you change decoding. + +```typescript +import { + HTTPCapability, + handler, + Report, + type HTTPPayload, + type Runtime, + type SecretsProvider, +} from "@chainlink/cre-sdk" +import { hexToBytes } from "viem" +import { z } from "zod" + +export const configSchema = z + .object({ + authorized_key: z.string(), + }) + .transform((data) => ({ + authorizedKey: data.authorized_key, + })) + +export type Config = z.infer + +type ParsedPayload = { + report: string + context: string + signatures: string[] +} + +export async function run(runtime: Runtime, payload: HTTPPayload): Promise { + const parsed: ParsedPayload = JSON.parse(new TextDecoder().decode(payload.input)) + + const rawReport = hexToBytes(`0x${parsed.report}`) + const reportContext = hexToBytes(`0x${parsed.context}`) + const sigs = parsed.signatures.map((s) => hexToBytes(`0x${s}`)) + + const report = await Report.parse(runtime, rawReport, sigs, reportContext) + + runtime.log(`Verified report from workflow ${report.workflowId()}, execution ${report.executionId()}`) + + // Use report.body() for your application logic (ABI-encoded payload from the sender workflow) + report.body() + + return true +} + +export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) => { + const http = new HTTPCapability() + return [ + handler(http.trigger({ authorizedKeys: [{ type: "KEY_TYPE_ECDSA_EVM", publicKey: config.authorizedKey }] }), run), + ] +} +``` + +**What's happening:** + +1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. +2. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) verifies signatures against the production CRE registry. +3. On success, you read metadata and `body()` safely. + +{/* prettier-ignore */} + + +## Report payload format + +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields (plus optional metadata your API may add). The JSON key is `context` even though the SDK field is `reportContext`: + +| JSON field | SDK field | Description | +| ------------ | --------------- | --------------------------------------------------------------- | +| `report` | `rawReport` | Hex-encoded bytes (metadata header + workflow payload), no `0x` | +| `context` | `reportContext` | Hex-encoded config digest + sequence number | +| `signatures` | `sigs` | Array of hex-encoded 65-byte ECDSA signatures, no `0x` | + +The `reportContext` layout used by the SDK: + +- Bytes 0–31: config digest +- Bytes 32–39: sequence number (big-endian `uint64`) + +## API reference + +See [SDK Reference: Core: Report verification](/cre/reference/sdk/core-ts#report-verification) for full signatures, types, and configuration. + +### `Report.parse()` + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses and verifies a report. Throws if verification fails. + +### `Report` accessors + +After a successful parse: + +| Method | Description | +| ----------------- | ----------------------------------------- | +| `workflowId()` | Workflow hash (`bytes32` as hex) | +| `workflowOwner()` | Deployer address (hex) | +| `workflowName()` | Workflow name field from metadata | +| `executionId()` | Unique execution identifier | +| `donId()` | DON that produced the report | +| `timestamp()` | Report timestamp (Unix seconds) | +| `body()` | Encoded payload after the 109-byte header | +| `seqNr()` | Sequence number from report context | +| `configDigest()` | Config digest from report context | + +### `ReportParseConfig` + +```typescript +import { productionEnvironment, zoneFromEnvironment, type ReportParseConfig } from "@chainlink/cre-sdk" + +const config: ReportParseConfig = { + acceptedZones: [zoneFromEnvironment(productionEnvironment(), 1)], + acceptedEnvironments: [productionEnvironment()], + skipSignatureVerification: false, +} +``` + +| Option | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `acceptedEnvironments` | Registry environments to check (defaults to production) | +| `acceptedZones` | Restrict to specific DON IDs within an environment | +| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript; call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) without this flag for production verification. | + +Most workflows should use the default config (production environment only). + +## Best practices + +1. **Verify before side effects**: Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before writing to databases, chains, or external systems. +2. **Permission on metadata**: After verification, check `workflowId()`, `workflowOwner()`, or `donId()` match your expectations. +3. **Deduplicate by execution ID**: Use `executionId()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#understanding-cachesettings-for-reports)). +4. **Do not skip signature verification in production** unless you have another trust path. + +## Troubleshooting + +**Empty error after verify sim** + +- [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) may throw an **`AggregateError`** of multiple `invalid signature` errors. **`AggregateError.message` is often empty**, so the CLI prints `Execution resulted in an error being returned:` with nothing after the colon. +- Format errors in your handler before rethrowing (see the simulation example above). + +**`invalid signature` / `unknown signer` in sim with fresh webhook JSON** + +- **Expected** when using default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- For local wiring tests, set `skipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. + +**`invalid signature` / `unknown signer` (deployed)** + +- Signatures may be from a different DON or stale registry config. +- Confirm the sender workflow used production CRE and the report was not tampered with. + +**`unexpected token: 'test'` on simulate** + +- Wrong `--http-payload` path. Invoke `cre` from the **project root** and use a path such as `verify-report-receiver/test-report-payload.json`. + +**Receiver JSON parse error** + +- You copied a **binary/octet-stream** webhook body instead of Pattern 4 JSON. Use [Pattern 4 for offchain verification (hex)](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-for-offchain-verification-hex). + +**`wrong number of signatures`** + +- At least **f+1** valid signatures are required. Extra invalid signatures are skipped; too few valid ones fails verification. + +**`could not read from chain ...`** + +- Registry read failed (RPC/network). Configure **`ethereum-mainnet` RPC** in `project.yaml` (required for default verify, including sim). Sepolia-only RPC is not sufficient for default `Report.parse()`. + +**`raw report too short`** + +- `rawReport` is missing the 109-byte metadata header. + +## Learn more + +- **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts):** sender workflow; create and POST the report +- **[SDK Reference: Core: Report verification](/cre/reference/sdk/core-ts#report-verification):** `Report.parse`, accessors, and `ReportParseConfig` +- **[HTTP Trigger Overview](/cre/guides/workflow/using-triggers/http-trigger/overview-ts):** trigger deployed receiver workflows +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain):** onchain forwarder verification path +- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts):** permissioning `onReport` with workflow metadata diff --git a/src/content/cre/index.mdx b/src/content/cre/index.mdx index e0d87181230..7f7432d5c44 100644 --- a/src/content/cre/index.mdx +++ b/src/content/cre/index.mdx @@ -110,17 +110,18 @@ Learn more about [Consensus Computing in CRE](/cre/concepts/consensus-computing) ## Glossary: Building blocks -| Concept | One-liner | -| ------------------ | ----------------------------------------------------------------- | -| **Workflow** | Compiled WebAssembly (WASM) binary. | -| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | -| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | -| **Callback** | Function that runs when its trigger fires; contains your logic. | -| **Runtime** | Object passed to a callback; used to invoke capabilities. | -| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | -| **Workflow DON** | Watches triggers and coordinates the workflow. | -| **Capability DON** | Executes a specific capability. | -| **Consensus** | BFT protocol that merges node results into one verifiable report. | +| Concept | One-liner | +| ------------------ | ---------------------------------------------------------------- | +| **Workflow** | Compiled WebAssembly (WASM) binary. | +| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | +| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | +| **Callback** | Function that runs when its trigger fires; contains your logic. | +| **Runtime** | Object passed to a callback; used to invoke capabilities. | +| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | +| **Workflow DON** | Watches triggers and coordinates the workflow. | +| **Capability DON** | Executes a specific capability. | +| **Consensus** | BFT protocol that merges node results into one trusted outcome. | +| **Report** | DON-signed package from `runtime.report()` / `GenerateReport()`. | Full definitions live on **[Key Terms and Concepts](/cre/key-terms)**. diff --git a/src/content/cre/key-terms.mdx b/src/content/cre/key-terms.mdx index 722c51ddc98..98a95fb4b12 100644 --- a/src/content/cre/key-terms.mdx +++ b/src/content/cre/key-terms.mdx @@ -5,7 +5,7 @@ date: Last Modified metadata: description: "Learn essential CRE concepts: workflows, handlers, triggers, callbacks, Runtime, capabilities, DONs, and consensus." datePublished: "2025-11-04" - lastModified: "2025-11-04" + lastModified: "2026-05-20" --- import { Aside } from "@components" @@ -75,6 +75,12 @@ Short-lived objects passed to your callback function during a specific execution Learn more about [Consensus and Aggregation](/cre/reference/sdk/consensus). +### Report (CRE report) + +A cryptographically signed package your workflow [DON](/cre/key-terms#decentralized-oracle-network-don) produces after your callback encodes its result: payload bytes, report context, and DON signatures. Create it with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). + +Deliver onchain with [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) / [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport), or offchain with [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) / [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport). Receivers verify with [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) or [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + ### SDK Clients: `EVMClient` & `HTTPClient` The primary SDK clients you use inside a callback to interact with capabilities. For example, you use an EVM client to read from a smart contract and an HTTP client to make offchain API requests. diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt index c4ba2c00ff9..fde695841ea 100644 --- a/src/content/cre/llms-full-go.txt +++ b/src/content/cre/llms-full-go.txt @@ -99,17 +99,18 @@ Learn more about [Consensus Computing in CRE](/cre/concepts/consensus-computing) ## Glossary: Building blocks -| Concept | One-liner | -| ------------------ | ----------------------------------------------------------------- | -| **Workflow** | Compiled WebAssembly (WASM) binary. | -| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | -| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | -| **Callback** | Function that runs when its trigger fires; contains your logic. | -| **Runtime** | Object passed to a callback; used to invoke capabilities. | -| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | -| **Workflow DON** | Watches triggers and coordinates the workflow. | -| **Capability DON** | Executes a specific capability. | -| **Consensus** | BFT protocol that merges node results into one verifiable report. | +| Concept | One-liner | +| ------------------ | ---------------------------------------------------------------- | +| **Workflow** | Compiled WebAssembly (WASM) binary. | +| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | +| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | +| **Callback** | Function that runs when its trigger fires; contains your logic. | +| **Runtime** | Object passed to a callback; used to invoke capabilities. | +| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | +| **Workflow DON** | Watches triggers and coordinates the workflow. | +| **Capability DON** | Executes a specific capability. | +| **Consensus** | BFT protocol that merges node results into one trusted outcome. | +| **Report** | DON-signed package from `runtime.report()` / `GenerateReport()`. | Full definitions live on **[Key Terms and Concepts](/cre/key-terms)**. @@ -149,7 +150,7 @@ Jump to what you need: # Key Terms and Concepts Source: https://docs.chain.link/cre/key-terms -Last Updated: 2025-11-04 +Last Updated: 2026-05-20 This page defines the fundamental terms and concepts for the Chainlink Runtime Environment (CRE). @@ -216,6 +217,12 @@ Short-lived objects passed to your callback function during a specific execution Learn more about [Consensus and Aggregation](/cre/reference/sdk/consensus). +### Report (CRE report) + +A cryptographically signed package your workflow [DON](/cre/key-terms#decentralized-oracle-network-don) produces after your callback encodes its result: payload bytes, report context, and DON signatures. Create it with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). + +Deliver onchain with [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) / [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport), or offchain with [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) / [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport). Receivers verify with [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) or [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + ### SDK Clients: `EVMClient` & `HTTPClient` The primary SDK clients you use inside a callback to interact with capabilities. For example, you use an EVM client to read from a smart contract and an HTTP client to make offchain API requests. @@ -3770,7 +3777,9 @@ writePromise := reserveManager.WriteReportFromUpdateReserves(runtime, updateData Source: https://docs.chain.link/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values Last Updated: 2025-11-04 -This guide shows how to manually generate a report containing a single value (like `uint256`, `address`, or `bool`). This is useful when you need to send a simple value onchain but don't have a struct or binding helper available. +This guide shows how to manually generate a **[CRE report](/cre/key-terms#report-cre-report)** containing a single value (like `uint256`, `address`, or `bool`). See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition; in short, it is a DON-signed package from [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) with your encoded data, workflow metadata, and signatures. + +This guide covers **creating** the signed report ([`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime)). **Delivering** it is a separate step: see the table below. **Use this approach when:** @@ -3788,12 +3797,14 @@ This guide shows how to manually generate a report containing a single value (li Manually generating a report for a single value involves two main steps: 1. **ABI-encode the value** into bytes using the `go-ethereum/accounts/abi` package -2. **Generate a cryptographically signed report** using `runtime.GenerateReport()` +2. **Generate a cryptographically signed report** using [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) -The resulting report can then be: +| After [`GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) | Guide | Who verifies? | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport) / [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | +| [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) / [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | -- Submitted to the blockchain via `evm.Client.WriteReport()` (see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)) -- Sent to an HTTP endpoint via `http.Client` (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http)) +See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **cre-sdk-go v1.8.0** or later. + + + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `cre.ProductionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +### `cre.ParseReport` + +**Signature:** + +```go +func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) +``` + +Parses a report and verifies signatures against `cre.ProductionEnvironment()`. Registry reads are cached per chain and DON. + +### `cre.ParseReportWithConfig` + +**Signature:** + +```go +func ParseReportWithConfig(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte, config ReportParseConfig) (*Report, error) +``` + +Same as `ParseReport`, with custom accepted environments or zones. Set `SkipSignatureVerification: true` to parse metadata only; call `report.VerifySignatures()` afterward when ready. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | -------- | ------------------------------------------ | +| `WorkflowID()` | `string` | Workflow hash (hex) | +| `WorkflowOwner()` | `string` | Workflow owner (hex) | +| `WorkflowName()` | `string` | Workflow name from metadata | +| `ExecutionID()` | `string` | Execution identifier (hex) | +| `DONID()` | `uint32` | DON that produced the report | +| `Body()` | `[]byte` | Payload after the 109-byte metadata header | +| `SeqNr()` | `uint64` | Sequence number | +| `ConfigDigest()` | `[]byte` | Config digest | +| `ReportContext()` | `[]byte` | Full report context bytes | +| `RawReport()` | `[]byte` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ----------------------------------------------------- | +| `AcceptedEnvironments` | `[]Environment` | Registry environments to try (defaults to production) | +| `AcceptedZones` | `[]Zone` | Restrict verification to specific DON IDs | +| `SkipSignatureVerification` | `bool` | Parse header only; call `VerifySignatures` separately | + +### `cre.ProductionEnvironment` and `cre.ZoneFromEnvironment` + +```go +func ProductionEnvironment() Environment // mainnet Capability Registry +func ZoneFromEnvironment(env Environment, donId uint32) Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `ChainSelector` | `uint64` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `RegistryAddress` | `string` | Capability Registry contract address | + +### Verification errors + +| Error | When | +| ------------------------ | -------------------------------------------- | +| `ErrUnknownSigner` | Recovered signer not in registry allowlist | +| `ErrWrongSignatureCount` | Fewer than f+1 valid signatures | +| `ErrRawReportTooShort` | `rawReport` missing 109-byte metadata header | +| `ErrDuplicateSigner` | Same signer twice in accepted set | + ## `cre.OrderedEntries` and `cre.OrderedEntriesFunc` Go maps iterate in random order, which causes consensus failures in DON mode because different nodes process entries in different sequences. These helpers return a deterministic iterator over a map's entries, sorted by key, so all nodes process items in the same order. @@ -18629,15 +19289,17 @@ The HTTP Client lets you make requests to external APIs from your workflow. Each - Fetching data from REST APIs ([GET requests](/cre/guides/workflow/using-http-client/get-request)) - Sending data to webhooks ([POST requests](/cre/guides/workflow/using-http-client/post-request)) - Submitting reports to offchain systems ([Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http)) +- Verifying reports received over HTTP ([Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go)) ## Quick reference -| Method | Use When | Guide | -| ------------------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | -| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | -| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | -| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| Method | Use When | Guide | +| ------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | +| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | +| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | +| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| [`cre.ParseReport`](/cre/reference/sdk/core-go#report-verification) | Verifying received reports (receiver) | [Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) | ## Core types @@ -19009,6 +19671,11 @@ func formatReport(r *sdk.ReportResponse) (*http.Request, error) { For complete examples of including signatures in different formats (body, headers, JSON), see the [Submitting Reports via HTTP guide](/cre/guides/workflow/using-http-client/submitting-reports-http#formatting-patterns). + + + --- # SDK Reference @@ -19021,7 +19688,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. +- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, report verification (`cre.ParseReport`), and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. - **[Triggers](/cre/reference/sdk/triggers)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client)**: Provides a reference for the `evm.Client`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client)**: Provides a reference for the `http.Client`, used for making offchain API requests from individual nodes. diff --git a/src/content/cre/llms-full-ts.txt b/src/content/cre/llms-full-ts.txt index f63f5974f4d..3e58f8ca273 100644 --- a/src/content/cre/llms-full-ts.txt +++ b/src/content/cre/llms-full-ts.txt @@ -99,17 +99,18 @@ Learn more about [Consensus Computing in CRE](/cre/concepts/consensus-computing) ## Glossary: Building blocks -| Concept | One-liner | -| ------------------ | ----------------------------------------------------------------- | -| **Workflow** | Compiled WebAssembly (WASM) binary. | -| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | -| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | -| **Callback** | Function that runs when its trigger fires; contains your logic. | -| **Runtime** | Object passed to a callback; used to invoke capabilities. | -| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | -| **Workflow DON** | Watches triggers and coordinates the workflow. | -| **Capability DON** | Executes a specific capability. | -| **Consensus** | BFT protocol that merges node results into one verifiable report. | +| Concept | One-liner | +| ------------------ | ---------------------------------------------------------------- | +| **Workflow** | Compiled WebAssembly (WASM) binary. | +| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | +| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | +| **Callback** | Function that runs when its trigger fires; contains your logic. | +| **Runtime** | Object passed to a callback; used to invoke capabilities. | +| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | +| **Workflow DON** | Watches triggers and coordinates the workflow. | +| **Capability DON** | Executes a specific capability. | +| **Consensus** | BFT protocol that merges node results into one trusted outcome. | +| **Report** | DON-signed package from `runtime.report()` / `GenerateReport()`. | Full definitions live on **[Key Terms and Concepts](/cre/key-terms)**. @@ -149,7 +150,7 @@ Jump to what you need: # Key Terms and Concepts Source: https://docs.chain.link/cre/key-terms -Last Updated: 2025-11-04 +Last Updated: 2026-05-20 This page defines the fundamental terms and concepts for the Chainlink Runtime Environment (CRE). @@ -216,6 +217,12 @@ Short-lived objects passed to your callback function during a specific execution Learn more about [Consensus and Aggregation](/cre/reference/sdk/consensus). +### Report (CRE report) + +A cryptographically signed package your workflow [DON](/cre/key-terms#decentralized-oracle-network-don) produces after your callback encodes its result: payload bytes, report context, and DON signatures. Create it with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). + +Deliver onchain with [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) / [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport), or offchain with [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) / [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport). Receivers verify with [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) or [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + ### SDK Clients: `EVMClient` & `HTTPClient` The primary SDK clients you use inside a callback to interact with capabilities. For example, you use an EVM client to read from a smart contract and an HTTP client to make offchain API requests. @@ -4030,7 +4037,7 @@ if (writeResult.txStatus === TxStatus.SUCCESS) { # API Interactions Source: https://docs.chain.link/cre/guides/workflow/using-http-client -Last Updated: 2026-03-17 +Last Updated: 2026-05-20 The CRE SDK provides an HTTP client that allows your workflows to interact with external APIs. Use it to fetch offchain data, send results to other systems, or trigger external events. @@ -4041,16 +4048,26 @@ The CRE SDK provides an HTTP client that allows your workflows to interact with -These guides will walk you through the common use cases for the HTTP client. - ## Guides - **[Making GET Requests](/cre/guides/workflow/using-http-client/get-request)**: Learn how to fetch data from a public API using a `GET` request. - **[Making POST Requests](/cre/guides/workflow/using-http-client/post-request)**: Learn how to send data to an external endpoint using a `POST` request. - **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http)**: Learn how to submit cryptographically signed reports to an external HTTP endpoint. +- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain)**: Verify report signatures and read workflow metadata when receiving reports over HTTP or other offchain channels. + +## CRE reports over HTTP + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package your workflow creates with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). It bundles your encoded payload, workflow metadata, report context, and cryptographic signatures from the DON. See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition, including how onchain delivery differs from HTTP. + +A typical secure integration uses two parties: + +1. **Sender:** a CRE workflow that runs your logic, signs a report, and POSTs it to a URL. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http). +2. **Receiver:** your API or another CRE workflow that verifies the report before using the data. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + +The sender creates the report inside the workflow; the receiver must verify signatures before trusting the payload. --- @@ -13988,6 +14005,17 @@ Here's the journey your workflow's data takes to reach the blockchain: In your workflow code, this process involves two steps: calling `runtime.report()` to generate the signed report, then calling `evmClient.writeReport()` to submit it to the blockchain. +### Where reports can go after generation + +The same signed report from `runtime.report()` can be delivered in different ways: + +| Destination | Guide | Verification | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| Smart contract (via Forwarder) | This section + [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | Onchain in `KeystoneForwarder` | +| HTTP API | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) | [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) on the receiver | + +See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver flow. + ## What you need: A consumer contract Before you can write data onchain, you need a **consumer contract**. This is the smart contract that will receive your workflow's data. @@ -14977,13 +15005,13 @@ export async function main() { # Submitting Reports via HTTP Source: https://docs.chain.link/cre/guides/workflow/using-http-client/submitting-reports-http-ts -Last Updated: 2026-01-20 +Last Updated: 2026-05-20 -This guide shows how to send a cryptographically signed report (generated by your workflow) to an external HTTP API. You'll learn how to write a transformation function that formats the report for your specific API's requirements. +This guide is for the **sender** side: a CRE workflow that **creates** a signed report and **POSTs** it to an HTTP endpoint. **What you'll learn:** -- How to use `sendReport()` to submit reports via HTTP +- How to use [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) to submit reports via HTTP - How to write transformation functions for different API formats - Best practices for report submission and deduplication @@ -14992,22 +15020,64 @@ This guide shows how to send a cryptographically signed report (generated by you This guide covers HTTP submission. For submitting reports to smart contracts, see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/overview-ts). +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is the signed output your workflow DON produces when you call [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). It packages your encoded data (the payload you computed in the callback), fixed workflow metadata, a report context (config digest and sequence number), and ECDSA signatures from DON nodes. + +This guide covers the **sender** path: create the report in your workflow, then POST it to an HTTP endpoint. The receiver must verify those signatures before trusting the data. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) on the receiver side. + +## Where this guide fits + +| Question | Answer | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | A [CRE report](/cre/key-terms#report-cre-report): output of [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) after DON consensus (encoded payload + metadata + signatures). | +| Where does it come from? | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | +| What does this guide cover? | Steps 3–4 below: [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime), then [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. | +| Who verifies it? | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). | + +**Sender flow in one workflow execution:** + +1. Trigger fires (cron, HTTP, …). +2. Your callback runs (API calls, encoding, etc.). +3. [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime): DON produces a signed `ReportResponse`. +4. [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport): format and POST to your URL. + ## Prerequisites - Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request) -- Familiarity with `runtime.report()` (covered [below](#generating-reports-for-http-submission)) +- Familiarity with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (covered [below](#generating-reports-for-http-submission)) +- **`viem`** as a direct dependency in the workflow `package.json` (for ABI encoding in examples) +- Protobuf HTTP/report types from **`@chainlink/cre-sdk/pb`** (`SDK_PB.ReportResponse`, `HTTP_CLIENT_PB.RequestJson`), not the main `@chainlink/cre-sdk` entry -## Quick start: Minimal example +## Payload contract (if you verify offchain) + +If a receiver uses [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts), your HTTP body must expose three fields. The verify guide expects **hex without `0x` in JSON** (field name `context`, not `reportContext`): + +| Your JSON field | SDK field on `ReportResponse` | +| --------------- | ----------------------------- | +| `report` | `rawReport` | +| `context` | `reportContext` | +| `signatures` | `sigs[].signature` | -Here's the simplest possible workflow that generates and submits a report via HTTP: +Use [Pattern 4 for offchain verification (hex)](#pattern-4-for-offchain-verification-hex) or the [complete working example](#complete-working-example). Other patterns are for APIs with different formats, not the default verify examples. + +## Minimal example (binary) + +This example POSTs **raw report bytes** (`application/octet-stream`). Use this if your API accepts raw binary. It is **not** compatible with the verify guide’s JSON receiver — for the sender → verify flow, use the [complete working example](#complete-working-example) instead. + +Here’s the simplest workflow that generates and submits a report via HTTP: ```typescript -import { ok, type ReportResponse, type RequestJson, type HTTPSendRequester } from "@chainlink/cre-sdk" +import { ok, type HTTPSendRequester, type Report } from "@chainlink/cre-sdk" +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson const formatReportSimple = (r: ReportResponse): RequestJson => { return { @@ -15018,8 +15088,8 @@ const formatReportSimple = (r: ReportResponse): RequestJson => { "Content-Type": "application/octet-stream", }, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old + store: true, + maxAge: "60s", }, } } @@ -15038,7 +15108,7 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): { succe **What's happening here:** 1. `formatReportSimple` transforms the report into an HTTP request that your API understands -2. `sendRequester.sendReport()` calls your transformation function and sends the request +2. [`sendRequester.sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) calls your transformation function and sends the request 3. The SDK handles consensus and returns the result The rest of this guide explains how this works and shows different formatting patterns for various API requirements. @@ -15047,7 +15117,7 @@ The rest of this guide explains how this works and shows different formatting pa ### The report structure -When you call `runtime.report()`, the SDK creates a `ReportResponse` containing: +After [consensus](/cre/key-terms#consensus), [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) returns a `ReportResponse`: the wire-format view of a [CRE report](/cre/key-terms#report-cre-report). It contains: ```typescript interface ReportResponse { @@ -15076,7 +15146,7 @@ Your transformation function tells the SDK how to format the report for your API **The SDK calls this function internally:** -1. You pass your transformation function to `sendReport()` +1. You pass your transformation function to [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) 2. The SDK calls it with the generated `ReportResponse` 3. Your function returns a `RequestJson` formatted for your API 4. The SDK sends the request and handles consensus @@ -15093,6 +15163,15 @@ The transformation function gives you complete control over the format. Here are common patterns for formatting reports. Choose the one that matches your API's requirements. +All patterns below use protobuf types and caching settings: + +```typescript +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson +``` + ### Choosing the right pattern | Pattern | When to use | @@ -15107,8 +15186,6 @@ Here are common patterns for formatting reports. Choose the one that matches you Use this when your API accepts raw binary data: ```typescript -import type { ReportResponse, RequestJson } from "@chainlink/cre-sdk" - const formatReportSimple = (r: ReportResponse): RequestJson => { return { url: "https://api.example.com/reports", @@ -15118,8 +15195,8 @@ const formatReportSimple = (r: ReportResponse): RequestJson => { "Content-Type": "application/octet-stream", }, cacheSettings: { - readFromCache: true, // Enable caching - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old + store: true, + maxAge: "60s", }, } } @@ -15165,8 +15242,8 @@ const formatReportWithSignatures = (r: ReportResponse): RequestJson => { "Content-Type": "application/octet-stream", }, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, + store: true, + maxAge: "60s", }, } } @@ -15197,8 +15274,8 @@ const formatReportWithHeaderSigs = (r: ReportResponse): RequestJson => { body: Buffer.from(r.rawReport).toString("base64"), headers, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, + store: true, + maxAge: "60s", }, } } @@ -15240,13 +15317,53 @@ const formatReportAsJSON = (r: ReportResponse): RequestJson => { "Content-Type": "application/json", }, cacheSettings: { - readFromCache: true, - maxAgeMs: 60000, + store: true, + maxAge: "60s", }, } } ``` +### Pattern 4 for offchain verification (hex) + +Use this variant when testing the [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) receiver in simulation. The verify examples decode **hex without a `0x` prefix**; the receiver adds `0x` when calling `hexToBytes`. + +Pattern 4 in the block above uses **base64** fields. Base64 sender output does **not** match the verify guide’s hex decoder without changes. + +```typescript +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" +import { bytesToHex } from "viem" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson + +const formatReportAsJSONHex = + (config: { apiUrl: string }) => + (r: ReportResponse): RequestJson => { + const payload = { + report: bytesToHex(r.rawReport).slice(2), + context: bytesToHex(r.reportContext).slice(2), + signatures: r.sigs.map((sig) => bytesToHex(sig.signature).slice(2)), + } + const bodyBytes = new TextEncoder().encode(JSON.stringify(payload)) + + return { + url: config.apiUrl, + method: "POST", + body: Buffer.from(bodyBytes).toString("base64"), + headers: { + "Content-Type": "application/json", + }, + cacheSettings: { + store: true, + maxAge: "60s", + }, + } + } +``` + +The `body` field is the UTF-8 JSON string, base64-encoded for the protobuf `bytes` field (same pattern as other JSON POST bodies in TypeScript workflows). + ### Understanding `cacheSettings` for reports You'll notice that all the patterns above include `cacheSettings`. This is critical for report submissions, just like it is for [POST requests](/cre/guides/workflow/using-http-client/post-request). @@ -15279,7 +15396,7 @@ This approach is reliable because the `rawReport` is identical across all nodes ## Generating reports for HTTP submission -Before you can submit a report via HTTP, you need to generate it using `runtime.report()`. This creates a cryptographically signed report from your encoded data. +Before you can submit a report via HTTP, you need to generate it using [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). This creates a cryptographically signed report from your encoded data. **Basic pattern:** @@ -15303,11 +15420,11 @@ const report = runtime // Step 3: Submit via HTTP (covered in next section) ``` -The `runtime.report()` method works the same way whether you're encoding a single value or a struct—just use Viem's `encodeAbiParameters()` with the appropriate ABI types. For detailed examples on encoding single values, structs, and complex types, see the [Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain) guide. +The [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) method works the same way whether you're encoding a single value or a struct—just use Viem's `encodeAbiParameters()` with the appropriate ABI types. For detailed examples on encoding single values, structs, and complex types, see the [Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain) guide. ## Using `sendReport()` (recommended approach) -Use the high-level `httpClient.sendRequest()` pattern with `sendRequester.sendReport()`: +Use the high-level `httpClient.sendRequest()` pattern with [`sendRequester.sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport): ```typescript import { @@ -15323,7 +15440,11 @@ interface SubmitResponse { success: boolean } -const submitReportViaHTTP = (sendRequester: HTTPSendRequester, report: Report): SubmitResponse => { +const submitReportViaHTTP = ( + runtime: Runtime, + sendRequester: HTTPSendRequester, + report: Report +): SubmitResponse => { const response = sendRequester.sendReport(report, formatReportSimple).result() if (!ok(response)) { @@ -15344,7 +15465,7 @@ const onCronTrigger = (runtime: Runtime): MyResult => { const result = httpClient .sendRequest( runtime, - (sendRequester: HTTPSendRequester) => submitReportViaHTTP(sendRequester, report), + (sendRequester: HTTPSendRequester) => submitReportViaHTTP(runtime, sendRequester, report), consensusIdenticalAggregation() )() .result() @@ -15358,25 +15479,30 @@ const onCronTrigger = (runtime: Runtime): MyResult => { This example shows a workflow that: 1. Generates a report from a single value -2. Submits it to an HTTP API -3. Uses the simple "report in body" format +2. Submits it to an HTTP API as **Pattern 4 JSON with hex fields** (compatible with the verify guide’s receiver sim loop) +3. Uses `config.apiUrl` from your target config file + +Add **`viem`** to `package.json`. Register handlers with `handler()` from `@chainlink/cre-sdk` (not `cron.handler()`). ```typescript import { CronCapability, HTTPClient, Runner, + handler, consensusIdenticalAggregation, hexToBase64, ok, type Runtime, - type Report, type CronPayload, type HTTPSendRequester, - type ReportResponse, - type RequestJson, + type Report, } from "@chainlink/cre-sdk" -import { encodeAbiParameters, parseAbiParameters } from "viem" +import type { SDK_PB, HTTP_CLIENT_PB } from "@chainlink/cre-sdk/pb" +import { encodeAbiParameters, parseAbiParameters, bytesToHex } from "viem" + +type ReportResponse = SDK_PB.ReportResponse +type RequestJson = HTTP_CLIENT_PB.RequestJson interface Config { apiUrl: string @@ -15391,32 +15517,43 @@ type MyResult = Record const initWorkflow = (config: Config) => { const cron = new CronCapability() - - return [cron.handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] + return [handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] } -// Transformation function: defines how the API expects the report -const formatReportForMyAPI = (r: ReportResponse): RequestJson => { - return { - url: "https://webhook.site/your-unique-id", // Replace with your API - method: "POST", - body: Buffer.from(r.rawReport).toString("base64"), - headers: { - "Content-Type": "application/octet-stream", - "X-Report-SeqNr": r.seqNr.toString(), - }, - cacheSettings: { - readFromCache: true, // Prevent duplicate submissions - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old - }, +const formatReportForMyAPI = + (config: Config) => + (r: ReportResponse): RequestJson => { + const payload = { + report: bytesToHex(r.rawReport).slice(2), + context: bytesToHex(r.reportContext).slice(2), + signatures: r.sigs.map((sig) => bytesToHex(sig.signature).slice(2)), + } + const bodyBytes = new TextEncoder().encode(JSON.stringify(payload)) + + return { + url: config.apiUrl, + method: "POST", + body: Buffer.from(bodyBytes).toString("base64"), + headers: { + "Content-Type": "application/json", + "X-Report-SeqNr": r.seqNr.toString(), // optional metadata for your API + }, + cacheSettings: { + store: true, + maxAge: "60s", + }, + } } -} -// Function that submits the report via HTTP -const submitReportViaHTTP = (sendRequester: HTTPSendRequester, report: Report): SubmitResponse => { - runtime.log("Submitting report to API") +const submitReportViaHTTP = ( + runtime: Runtime, + sendRequester: HTTPSendRequester, + report: Report, + config: Config +): SubmitResponse => { + runtime.log(`Submitting report to API: ${config.apiUrl}`) - const response = sendRequester.sendReport(report, formatReportForMyAPI).result() + const response = sendRequester.sendReport(report, formatReportForMyAPI(config)).result() runtime.log(`Report submitted - status: ${response.statusCode}, bodyLength: ${response.body.length}`) @@ -15428,16 +15565,13 @@ const submitReportViaHTTP = (sendRequester: HTTPSendRequester, report: Report): return { success: true } } -const onCronTrigger = (runtime: Runtime, payload: CronPayload): MyResult => { - // Step 1: Generate a report (example: a single uint256 value) +const onCronTrigger = (runtime: Runtime, _payload: CronPayload): MyResult => { const myValue = 123456789n runtime.log(`Generating report with value: ${myValue}`) - // Encode the value using Viem const encodedValue = encodeAbiParameters(parseAbiParameters("uint256 value"), [myValue]) - // Generate the report - const reportResponse = runtime + const report = runtime .report({ encodedPayload: hexToBase64(encodedValue), encoderName: "evm", @@ -15448,13 +15582,12 @@ const onCronTrigger = (runtime: Runtime, payload: CronPayload): MyResult runtime.log("Report generated successfully") - // Step 2: Submit the report via HTTP const httpClient = new HTTPClient() const submitResult = httpClient .sendRequest( runtime, - (sendRequester: HTTPSendRequester) => submitReportViaHTTP(sendRequester, reportResponse), + (sendRequester: HTTPSendRequester) => submitReportViaHTTP(runtime, sendRequester, report, runtime.config), consensusIdenticalAggregation() )() .result() @@ -15481,12 +15614,21 @@ export async function main() { ### Testing with webhook.site 1. Go to [webhook.site](https://webhook.site/) and get a unique URL -2. Update `config.json` with your webhook URL -3. Run the simulation: +2. Update `config.json` (or `config.staging.json`) with your webhook URL in `apiUrl` +3. From the **CRE project root**, run the simulation: ```bash cre workflow simulate my-workflow --target staging-settings ``` -4. Check webhook.site to see the report data received +4. On webhook.site, open the request **Content** tab. You should see JSON with `report`, `context`, and `signatures` (hex strings). Use that JSON to test a receiver workflow in [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts#testing-locally-with-simulation). + + + + +## Next step: verify on the receiver + +The sender does not validate the report for the receiver. After submission, the ingesting side must verify signatures before trusting the payload. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). ## Advanced: Low-level pattern @@ -15527,15 +15669,13 @@ const onCronTrigger = (runtime: Runtime): MyResult => { 1. **Always use `cacheSettings`**: Include caching in every transformation function to prevent worst-case duplicate submission scenarios 2. **Implement API-side deduplication**: Your receiving API must implement deduplication using the **hash of the report** (`keccak256(rawReport)`) to detect and reject duplicate submissions -3. **Verify signatures before processing**: Your API must verify the cryptographic signatures against DON public keys before trusting report data (see note below about signature verification) +3. **Verify on the receiver**: The sender does not validate the report; your API or a [receiver CRE workflow](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) must verify before trusting payload data 4. **Match your API's format exactly**: Study your API's documentation to understand the expected format (binary, JSON, headers, etc.) 5. **Handle errors gracefully**: Check HTTP status codes and provide meaningful error messages ## Troubleshooting @@ -15551,12 +15691,411 @@ const onCronTrigger = (runtime: Runtime): MyResult => { - Your report format likely doesn't match what your API expects - Check if your API expects base64 encoding, JSON wrapping, or specific headers +**TypeScript compile errors on the complete example** + +- Use `handler(cron.trigger(...), fn)` from `@chainlink/cre-sdk`, not `cron.handler()` +- Import `ReportResponse` / `RequestJson` from `@chainlink/cre-sdk/pb` +- Pass `runtime` from the trigger callback into helper functions (no global `runtime`) +- Add `viem` as a direct workflow dependency + +## Learn more + +- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts):** verify signatures on the receiver before trusting payload data +- **[HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts):** complete API reference including `sendReport()` and `ReportResponse` +- **[POST Requests](/cre/guides/workflow/using-http-client/post-request):** HTTP request patterns and caching +- **[Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain):** encoding single values, structs, and complex types using Viem +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain):** submit reports to smart contracts instead of HTTP + +--- + +# Verifying CRE Reports Offchain +Source: https://docs.chain.link/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts +Last Updated: 2026-05-20 + +This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. + +When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** + +The CRE SDK provides [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. + + + + + + + +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification), then read trusted metadata and `body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + +## Where this guide fits + +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before you use `body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | + +**Receiver flow:** + +1. HTTP trigger (or your API) receives the POST payload. +2. Decode hex fields into bytes. +3. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification): verify signatures and read metadata. +4. Use trusted `body()` in your logic. + +Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. This guide covers local simulation first, then the deploy example with `authorizedKeys`. + +## What you'll learn + +- When to verify reports offchain vs relying on onchain forwarders +- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata +- How to build a receiver workflow that accepts reports over HTTP +- How to restrict verification to specific CRE environments or zones + +## Prerequisites + +- **SDK**: `@chainlink/cre-sdk` v1.8.0 or later (report verification support) +- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) (report structure and JSON payload patterns) +- For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-ts) + +## Onchain vs offchain verification + +| Aspect | Offchain ([`Report.parse()`](/cre/reference/sdk/core-ts#report-verification)) | Onchain (`KeystoneForwarder`) | +| -------------------- | ----------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | + +Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. + +Default (`productionEnvironment()`): + +- **Chain**: Ethereum Mainnet (chain selector `5009297550715157269`) +- **Registry**: `0x76c9cf548b4179F8901cda1f8623568b58215E62` + +## How verification works + +1. **Parse the report header** from `rawReport` (109-byte metadata + body). +2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. +3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. +4. **Return a `Report` object** with accessors for workflow ID, owner, execution ID, body, and more. + +If verification fails, [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) throws (for example, unknown signer, insufficient signatures, or registry read failure). + +## Testing locally with simulation + +After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. + +1. Save the webhook JSON as `test-report-payload.json` in your receiver workflow folder. +2. Use the minimal receiver below with an **empty HTTP trigger config** (no `authorizedKeys` until deploy). +3. From the **CRE project root**, run `cre workflow simulate` with `--http-payload verify-report-receiver/test-report-payload.json` (path relative to where you invoke `cre`). + +### Sim wiring vs full verify + +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `skipSignatureVerification: true` in staging config | JSON + hex decode, `workflowId()`, `executionId()`, `donId()`, `body()` | +| **Full crypto verify** | Default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification: simulation uses local DON keys; [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) checks the mainnet Capability Registry. | + +### Minimal receiver for simulation + +Use an **empty HTTP trigger config** for sim (add `authorizedKeys` before deploy). Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) from your handler with the `runtime` parameter. The CLI delivers `--http-payload` file contents as `payload.input` bytes. + +`config.staging.json`: + +```json +{ + "skipSignatureVerification": true +} +``` + +`main.ts`: + +```typescript +import { + decodeJson, + handler, + hexToBytes, + HTTPCapability, + Report, + Runner, + type HTTPPayload, + type Runtime, +} from "@chainlink/cre-sdk" + +interface Config { + skipSignatureVerification?: boolean +} + +type ParsedPayload = { + report: string + context: string + signatures: string[] +} + +/** Hex without 0x prefix in JSON → bytes (add 0x before decode). */ +const fromHexNoPrefix = (hex: string): Uint8Array => hexToBytes(`0x${hex}`) + +/** AggregateError from Report.parse often has an empty .message in sim output. */ +const formatError = (err: unknown): string => { + if (err instanceof AggregateError) { + const parts = err.errors.map((e) => (e instanceof Error ? e.message : String(e))) + return parts.join("; ") || "report verification failed" + } + if (err instanceof Error) return err.message + return String(err) +} + +export async function run(runtime: Runtime, payload: HTTPPayload): Promise<{ verified: boolean }> { + try { + const parsed = decodeJson(payload.input) as ParsedPayload + + const rawReport = fromHexNoPrefix(parsed.report) + const reportContext = fromHexNoPrefix(parsed.context) + const sigs = parsed.signatures.map((s) => fromHexNoPrefix(s)) + + runtime.log(`Parsing report (${rawReport.length} bytes, ${sigs.length} signatures)`) + + const report = await Report.parse(runtime, rawReport, sigs, reportContext, { + skipSignatureVerification: runtime.config.skipSignatureVerification ?? false, + }) + + runtime.log( + `Verified report workflowId=${report.workflowId()} executionId=${report.executionId()} donId=${report.donId()}` + ) + report.body() + return { verified: true } + } catch (err) { + const msg = formatError(err) + runtime.log(`Report verification failed: ${msg}`) + throw new Error(msg) + } +} + +export const initWorkflow = () => { + const http = new HTTPCapability() + return [handler(http.trigger({}), run)] +} + +export async function main() { + const runner = await Runner.newRunner() + await runner.run(initWorkflow) +} +``` + +Save webhook JSON as `verify-report-receiver/test-report-payload.json`. From the **CRE project root**: + +```bash +cre workflow simulate verify-report-receiver \ + --target staging-settings \ + --non-interactive \ + --trigger-index 0 \ + --http-payload verify-report-receiver/test-report-payload.json +``` + +**Pass criteria** + +- **Sim wiring:** `skipSignatureVerification: true`: logs show metadata and `{ verified: true }`. +- **Full crypto verify:** default config with a **production-signed** report (not typical for sender-sim → receiver-sim alone). + +## Complete example: HTTP receiver workflow (deploy) + +Once simulation is working, update the trigger config for deployment with `authorizedKeys`. + +It accepts JSON with hex `report`, `context`, and `signatures` (from the submit guide’s [complete working example](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#complete-working-example) or [Pattern 4 for offchain verification (hex)](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-for-offchain-verification-hex)), not base64 [Pattern 4](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-json-formatted-report) unless you change decoding. + +```typescript +import { + HTTPCapability, + handler, + Report, + type HTTPPayload, + type Runtime, + type SecretsProvider, +} from "@chainlink/cre-sdk" +import { hexToBytes } from "viem" +import { z } from "zod" + +export const configSchema = z + .object({ + authorized_key: z.string(), + }) + .transform((data) => ({ + authorizedKey: data.authorized_key, + })) + +export type Config = z.infer + +type ParsedPayload = { + report: string + context: string + signatures: string[] +} + +export async function run(runtime: Runtime, payload: HTTPPayload): Promise { + const parsed: ParsedPayload = JSON.parse(new TextDecoder().decode(payload.input)) + + const rawReport = hexToBytes(`0x${parsed.report}`) + const reportContext = hexToBytes(`0x${parsed.context}`) + const sigs = parsed.signatures.map((s) => hexToBytes(`0x${s}`)) + + const report = await Report.parse(runtime, rawReport, sigs, reportContext) + + runtime.log(`Verified report from workflow ${report.workflowId()}, execution ${report.executionId()}`) + + // Use report.body() for your application logic (ABI-encoded payload from the sender workflow) + report.body() + + return true +} + +export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) => { + const http = new HTTPCapability() + return [ + handler(http.trigger({ authorizedKeys: [{ type: "KEY_TYPE_ECDSA_EVM", publicKey: config.authorizedKey }] }), run), + ] +} +``` + +**What's happening:** + +1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. +2. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) verifies signatures against the production CRE registry. +3. On success, you read metadata and `body()` safely. + + + + +## Report payload format + +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields (plus optional metadata your API may add). The JSON key is `context` even though the SDK field is `reportContext`: + +| JSON field | SDK field | Description | +| ------------ | --------------- | --------------------------------------------------------------- | +| `report` | `rawReport` | Hex-encoded bytes (metadata header + workflow payload), no `0x` | +| `context` | `reportContext` | Hex-encoded config digest + sequence number | +| `signatures` | `sigs` | Array of hex-encoded 65-byte ECDSA signatures, no `0x` | + +The `reportContext` layout used by the SDK: + +- Bytes 0–31: config digest +- Bytes 32–39: sequence number (big-endian `uint64`) + +## API reference + +See [SDK Reference: Core: Report verification](/cre/reference/sdk/core-ts#report-verification) for full signatures, types, and configuration. + +### `Report.parse()` + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses and verifies a report. Throws if verification fails. + +### `Report` accessors + +After a successful parse: + +| Method | Description | +| ----------------- | ----------------------------------------- | +| `workflowId()` | Workflow hash (`bytes32` as hex) | +| `workflowOwner()` | Deployer address (hex) | +| `workflowName()` | Workflow name field from metadata | +| `executionId()` | Unique execution identifier | +| `donId()` | DON that produced the report | +| `timestamp()` | Report timestamp (Unix seconds) | +| `body()` | Encoded payload after the 109-byte header | +| `seqNr()` | Sequence number from report context | +| `configDigest()` | Config digest from report context | + +### `ReportParseConfig` + +```typescript +import { productionEnvironment, zoneFromEnvironment, type ReportParseConfig } from "@chainlink/cre-sdk" + +const config: ReportParseConfig = { + acceptedZones: [zoneFromEnvironment(productionEnvironment(), 1)], + acceptedEnvironments: [productionEnvironment()], + skipSignatureVerification: false, +} +``` + +| Option | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `acceptedEnvironments` | Registry environments to check (defaults to production) | +| `acceptedZones` | Restrict to specific DON IDs within an environment | +| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript; call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) without this flag for production verification. | + +Most workflows should use the default config (production environment only). + +## Best practices + +1. **Verify before side effects**: Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before writing to databases, chains, or external systems. +2. **Permission on metadata**: After verification, check `workflowId()`, `workflowOwner()`, or `donId()` match your expectations. +3. **Deduplicate by execution ID**: Use `executionId()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#understanding-cachesettings-for-reports)). +4. **Do not skip signature verification in production** unless you have another trust path. + +## Troubleshooting + +**Empty error after verify sim** + +- [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) may throw an **`AggregateError`** of multiple `invalid signature` errors. **`AggregateError.message` is often empty**, so the CLI prints `Execution resulted in an error being returned:` with nothing after the colon. +- Format errors in your handler before rethrowing (see the simulation example above). + +**`invalid signature` / `unknown signer` in sim with fresh webhook JSON** + +- **Expected** when using default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- For local wiring tests, set `skipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. + +**`invalid signature` / `unknown signer` (deployed)** + +- Signatures may be from a different DON or stale registry config. +- Confirm the sender workflow used production CRE and the report was not tampered with. + +**`unexpected token: 'test'` on simulate** + +- Wrong `--http-payload` path. Invoke `cre` from the **project root** and use a path such as `verify-report-receiver/test-report-payload.json`. + +**Receiver JSON parse error** + +- You copied a **binary/octet-stream** webhook body instead of Pattern 4 JSON. Use [Pattern 4 for offchain verification (hex)](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-for-offchain-verification-hex). + +**`wrong number of signatures`** + +- At least **f+1** valid signatures are required. Extra invalid signatures are skipped; too few valid ones fails verification. + +**`could not read from chain ...`** + +- Registry read failed (RPC/network). Configure **`ethereum-mainnet` RPC** in `project.yaml` (required for default verify, including sim). Sepolia-only RPC is not sufficient for default `Report.parse()`. + +**`raw report too short`** + +- `rawReport` is missing the 109-byte metadata header. + ## Learn more -- **[HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts)** — Complete API reference including `sendReport()` and `ReportResponse` -- **[POST Requests](/cre/guides/workflow/using-http-client/post-request)** — Learn about HTTP request patterns and caching -- **[Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain)** — Detailed guide on encoding single values, structs, and complex types using Viem -- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)** — Alternative: Submit reports to smart contracts instead of HTTP +- **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts):** sender workflow; create and POST the report +- **[SDK Reference: Core: Report verification](/cre/reference/sdk/core-ts#report-verification):** `Report.parse`, accessors, and `ReportParseConfig` +- **[HTTP Trigger Overview](/cre/guides/workflow/using-triggers/http-trigger/overview-ts):** trigger deployed receiver workflows +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain):** onchain forwarder verification path +- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts):** permissioning `onReport` with workflow metadata --- @@ -17868,7 +18407,7 @@ If all nodes fail or consensus cannot be reached, the default value (`0n` in thi # SDK Reference: Core Source: https://docs.chain.link/cre/reference/sdk/core-ts -Last Updated: 2026-01-20 +Last Updated: 2026-05-20 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. @@ -17965,7 +18504,7 @@ Both `Runtime` and `NodeRuntime` provide: - **`runInNodeMode(...)`**: Execute code on individual nodes with consensus aggregation - **`getSecret(...)`**: Access to workflow secrets -- **`report(...)`**: Generate cryptographically signed reports +- **`report(...)`**: Generate cryptographically signed reports. To verify received reports, use [`Report.parse()`](#report-verification). ### Logging @@ -18400,6 +18939,72 @@ const onTrigger = (runtime: Runtime): string => { For a complete walkthrough on creating, storing, and using secrets, see the [Secrets guide](/cre/guides/workflow/secrets). +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **`@chainlink/cre-sdk` v1.8.0** or later. + + + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `productionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +Import from `@chainlink/cre-sdk`: `Report`, `productionEnvironment`, `zoneFromEnvironment`, and `ReportParseConfig`. + +### `Report.parse()` + +**Signature:** + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses a report and verifies signatures. Throws on failure. Registry reads are cached per chain and DON. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | ------------ | ------------------------------------------ | +| `workflowId()` | `string` | Workflow hash (hex) | +| `workflowOwner()` | `string` | Workflow owner (hex) | +| `workflowName()` | `string` | Workflow name from metadata | +| `executionId()` | `string` | Execution identifier (hex) | +| `donId()` | `number` | DON that produced the report | +| `body()` | `Uint8Array` | Payload after the 109-byte metadata header | +| `seqNr()` | `bigint` | Sequence number | +| `configDigest()` | `Uint8Array` | Config digest | +| `reportContext()` | `Uint8Array` | Full report context bytes | +| `rawReport()` | `Uint8Array` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ------------------------------------------------------------------ | +| `acceptedEnvironments` | `Environment[]` | Registry environments to try (defaults to production) | +| `acceptedZones` | `Zone[]` | Restrict verification to specific DON IDs | +| `skipSignatureVerification` | `boolean` | Parse metadata only; not for production without another trust path | + +### `productionEnvironment()` and `zoneFromEnvironment()` + +```typescript +function productionEnvironment(): Environment +function zoneFromEnvironment(environment: Environment, donID: number): Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `chainSelector` | `bigint` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `registryAddress` | `string` | Capability Registry contract address | + --- # SDK Reference: EVM Client @@ -19374,7 +19979,7 @@ Because it operates at the node level, all HTTP requests are wrapped in a consen - **[High-level (recommended)](#high-level-sendrequest-recommended):** Automatically wraps your request in the `runtime.runInNodeMode()` pattern with consensus aggregation. - **[Low-level](#low-level-sendrequest):** Requires manual wrapping in a `runtime.runInNodeMode()` block for complex scenarios. -For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. +For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. To **submit** signed reports, see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts). To **verify** received reports, see [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). ## High-level `sendRequest()` (recommended) @@ -19510,6 +20115,11 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): string } ``` + + + ## Low-level `sendRequest()` The low-level `sendRequest()` method requires manual wrapping in a `runtime.runInNodeMode()` block. It provides more flexibility for complex scenarios but requires more boilerplate code. @@ -20016,7 +20626,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, and the `.result()` pattern for promise resolution. +- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, report verification (`Report.parse`), and the `.result()` pattern for promise resolution. - **[Triggers](/cre/reference/sdk/triggers/overview-ts)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client-ts)**: Provides a reference for the `EVMClient`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client-ts)**: Provides a reference for the `HTTPClient`, used for making offchain API requests from individual nodes. diff --git a/src/content/cre/reference/sdk/core-go.mdx b/src/content/cre/reference/sdk/core-go.mdx index 8e324138bee..df48dc5c80e 100644 --- a/src/content/cre/reference/sdk/core-go.mdx +++ b/src/content/cre/reference/sdk/core-go.mdx @@ -5,9 +5,9 @@ date: Last Modified sdkLang: "go" pageId: "reference-sdk-core" metadata: - description: "Reference for core Go SDK: Workflow, Handler, Runtime, NodeRuntime, and essential functions every CRE workflow uses." + description: "Reference for core Go SDK: Workflow, Handler, Runtime, NodeRuntime, report verification (ParseReport), and essential functions every CRE workflow uses." datePublished: "2025-11-04" - lastModified: "2026-04-20" + lastModified: "2026-05-20" --- import { Aside } from "@components" @@ -321,6 +321,83 @@ func onTrigger(config *Config, runtime cre.Runtime, ...) (string, error) { guide](/cre/guides/workflow/secrets). +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **cre-sdk-go v1.8.0** or later. + +{/* prettier-ignore */} + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `cre.ProductionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +### `cre.ParseReport` + +**Signature:** + +```go +func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) +``` + +Parses a report and verifies signatures against `cre.ProductionEnvironment()`. Registry reads are cached per chain and DON. + +### `cre.ParseReportWithConfig` + +**Signature:** + +```go +func ParseReportWithConfig(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte, config ReportParseConfig) (*Report, error) +``` + +Same as `ParseReport`, with custom accepted environments or zones. Set `SkipSignatureVerification: true` to parse metadata only; call `report.VerifySignatures()` afterward when ready. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | -------- | ------------------------------------------ | +| `WorkflowID()` | `string` | Workflow hash (hex) | +| `WorkflowOwner()` | `string` | Workflow owner (hex) | +| `WorkflowName()` | `string` | Workflow name from metadata | +| `ExecutionID()` | `string` | Execution identifier (hex) | +| `DONID()` | `uint32` | DON that produced the report | +| `Body()` | `[]byte` | Payload after the 109-byte metadata header | +| `SeqNr()` | `uint64` | Sequence number | +| `ConfigDigest()` | `[]byte` | Config digest | +| `ReportContext()` | `[]byte` | Full report context bytes | +| `RawReport()` | `[]byte` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ----------------------------------------------------- | +| `AcceptedEnvironments` | `[]Environment` | Registry environments to try (defaults to production) | +| `AcceptedZones` | `[]Zone` | Restrict verification to specific DON IDs | +| `SkipSignatureVerification` | `bool` | Parse header only; call `VerifySignatures` separately | + +### `cre.ProductionEnvironment` and `cre.ZoneFromEnvironment` + +```go +func ProductionEnvironment() Environment // mainnet Capability Registry +func ZoneFromEnvironment(env Environment, donId uint32) Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `ChainSelector` | `uint64` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `RegistryAddress` | `string` | Capability Registry contract address | + +### Verification errors + +| Error | When | +| ------------------------ | -------------------------------------------- | +| `ErrUnknownSigner` | Recovered signer not in registry allowlist | +| `ErrWrongSignatureCount` | Fewer than f+1 valid signatures | +| `ErrRawReportTooShort` | `rawReport` missing 109-byte metadata header | +| `ErrDuplicateSigner` | Same signer twice in accepted set | + ## `cre.OrderedEntries` and `cre.OrderedEntriesFunc` Go maps iterate in random order, which causes consensus failures in DON mode because different nodes process entries in different sequences. These helpers return a deterministic iterator over a map's entries, sorted by key, so all nodes process items in the same order. diff --git a/src/content/cre/reference/sdk/core-ts.mdx b/src/content/cre/reference/sdk/core-ts.mdx index 5b7cb3edeff..51e4ae7a929 100644 --- a/src/content/cre/reference/sdk/core-ts.mdx +++ b/src/content/cre/reference/sdk/core-ts.mdx @@ -5,9 +5,9 @@ date: Last Modified sdkLang: "ts" pageId: "reference-sdk-core" metadata: - description: "Reference for core TypeScript SDK: Workflow, Handler, Runtime, and essential functions every CRE workflow uses." + description: "Reference for core TypeScript SDK: Workflow, Handler, Runtime, report verification (Report.parse), and essential functions every CRE workflow uses." datePublished: "2025-11-04" - lastModified: "2026-01-20" + lastModified: "2026-05-20" --- import { Aside } from "@components" @@ -107,7 +107,7 @@ Both `Runtime` and `NodeRuntime` provide: - **`runInNodeMode(...)`**: Execute code on individual nodes with consensus aggregation - **`getSecret(...)`**: Access to workflow secrets -- **`report(...)`**: Generate cryptographically signed reports +- **`report(...)`**: Generate cryptographically signed reports. To verify received reports, use [`Report.parse()`](#report-verification). ### Logging @@ -542,3 +542,69 @@ const onTrigger = (runtime: Runtime): string => { + +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **`@chainlink/cre-sdk` v1.8.0** or later. + +{/* prettier-ignore */} + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `productionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +Import from `@chainlink/cre-sdk`: `Report`, `productionEnvironment`, `zoneFromEnvironment`, and `ReportParseConfig`. + +### `Report.parse()` + +**Signature:** + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses a report and verifies signatures. Throws on failure. Registry reads are cached per chain and DON. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | ------------ | ------------------------------------------ | +| `workflowId()` | `string` | Workflow hash (hex) | +| `workflowOwner()` | `string` | Workflow owner (hex) | +| `workflowName()` | `string` | Workflow name from metadata | +| `executionId()` | `string` | Execution identifier (hex) | +| `donId()` | `number` | DON that produced the report | +| `body()` | `Uint8Array` | Payload after the 109-byte metadata header | +| `seqNr()` | `bigint` | Sequence number | +| `configDigest()` | `Uint8Array` | Config digest | +| `reportContext()` | `Uint8Array` | Full report context bytes | +| `rawReport()` | `Uint8Array` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ------------------------------------------------------------------ | +| `acceptedEnvironments` | `Environment[]` | Registry environments to try (defaults to production) | +| `acceptedZones` | `Zone[]` | Restrict verification to specific DON IDs | +| `skipSignatureVerification` | `boolean` | Parse metadata only; not for production without another trust path | + +### `productionEnvironment()` and `zoneFromEnvironment()` + +```typescript +function productionEnvironment(): Environment +function zoneFromEnvironment(environment: Environment, donID: number): Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `chainSelector` | `bigint` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `registryAddress` | `string` | Capability Registry contract address | diff --git a/src/content/cre/reference/sdk/http-client-go.mdx b/src/content/cre/reference/sdk/http-client-go.mdx index 2db1b44cf90..4e46e4be51a 100644 --- a/src/content/cre/reference/sdk/http-client-go.mdx +++ b/src/content/cre/reference/sdk/http-client-go.mdx @@ -19,15 +19,17 @@ The HTTP Client lets you make requests to external APIs from your workflow. Each - Fetching data from REST APIs ([GET requests](/cre/guides/workflow/using-http-client/get-request)) - Sending data to webhooks ([POST requests](/cre/guides/workflow/using-http-client/post-request)) - Submitting reports to offchain systems ([Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http)) +- Verifying reports received over HTTP ([Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go)) ## Quick reference -| Method | Use When | Guide | -| ------------------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | -| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | -| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | -| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| Method | Use When | Guide | +| ------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | +| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | +| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | +| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| [`cre.ParseReport`](/cre/reference/sdk/core-go#report-verification) | Verifying received reports (receiver) | [Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) | ## Core types @@ -397,3 +399,8 @@ func formatReport(r *sdk.ReportResponse) (*http.Request, error) { ``` For complete examples of including signatures in different formats (body, headers, JSON), see the [Submitting Reports via HTTP guide](/cre/guides/workflow/using-http-client/submitting-reports-http#formatting-patterns). + +{/* prettier-ignore */} + diff --git a/src/content/cre/reference/sdk/http-client-ts.mdx b/src/content/cre/reference/sdk/http-client-ts.mdx index 1506ca5bfe9..17c5401733d 100644 --- a/src/content/cre/reference/sdk/http-client-ts.mdx +++ b/src/content/cre/reference/sdk/http-client-ts.mdx @@ -19,7 +19,7 @@ Because it operates at the node level, all HTTP requests are wrapped in a consen - **[High-level (recommended)](#high-level-sendrequest-recommended):** Automatically wraps your request in the `runtime.runInNodeMode()` pattern with consensus aggregation. - **[Low-level](#low-level-sendrequest):** Requires manual wrapping in a `runtime.runInNodeMode()` block for complex scenarios. -For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. +For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. To **submit** signed reports, see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts). To **verify** received reports, see [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). ## High-level `sendRequest()` (recommended) @@ -155,6 +155,11 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): string } ``` +{/* prettier-ignore */} + + ## Low-level `sendRequest()` The low-level `sendRequest()` method requires manual wrapping in a `runtime.runInNodeMode()` block. It provides more flexibility for complex scenarios but requires more boilerplate code. diff --git a/src/content/cre/reference/sdk/overview-go.mdx b/src/content/cre/reference/sdk/overview-go.mdx index 10fcf432b8b..faa00b9dc36 100644 --- a/src/content/cre/reference/sdk/overview-go.mdx +++ b/src/content/cre/reference/sdk/overview-go.mdx @@ -18,7 +18,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. +- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, report verification (`cre.ParseReport`), and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. - **[Triggers](/cre/reference/sdk/triggers)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client)**: Provides a reference for the `evm.Client`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client)**: Provides a reference for the `http.Client`, used for making offchain API requests from individual nodes. diff --git a/src/content/cre/reference/sdk/overview-ts.mdx b/src/content/cre/reference/sdk/overview-ts.mdx index ee95f7e9685..a1881db3db6 100644 --- a/src/content/cre/reference/sdk/overview-ts.mdx +++ b/src/content/cre/reference/sdk/overview-ts.mdx @@ -18,7 +18,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, and the `.result()` pattern for promise resolution. +- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, report verification (`Report.parse`), and the `.result()` pattern for promise resolution. - **[Triggers](/cre/reference/sdk/triggers/overview-ts)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client-ts)**: Provides a reference for the `EVMClient`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client-ts)**: Provides a reference for the `HTTPClient`, used for making offchain API requests from individual nodes.