diff --git a/packages/cre-sdk-examples/secrets.yaml b/packages/cre-sdk-examples/secrets.yaml index d40dcba0..1691a194 100644 --- a/packages/cre-sdk-examples/secrets.yaml +++ b/packages/cre-sdk-examples/secrets.yaml @@ -3,3 +3,5 @@ secretsNames: - SECRET_ADDRESS_ALL CHARACTER_ID: - SECRET_CHARACTER_ID + SECRET_HEADER: + - SECRET_HEADER_VALUE diff --git a/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/config.json b/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/config.json new file mode 100644 index 00000000..6b8dce3c --- /dev/null +++ b/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/config.json @@ -0,0 +1,4 @@ +{ + "schedule": "0 */1 * * * *", + "url": "https://api.mathjs.org/v4?expr=randomInt(1,101)" +} diff --git a/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/index.ts b/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/index.ts new file mode 100644 index 00000000..3001b259 --- /dev/null +++ b/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/index.ts @@ -0,0 +1,75 @@ +import { + type ConfidentialHTTPSendRequester, + consensusMedianAggregation, + cre, + ok, + Runner, + type Runtime, + text, +} from '@chainlink/cre-sdk' +import { z } from 'zod' + +const configSchema = z.object({ + schedule: z.string(), + url: z.string(), +}) + +type Config = z.infer + +const fetchMathResult = (sendRequester: ConfidentialHTTPSendRequester, config: Config) => { + const { responses } = sendRequester + .sendRequests({ + input: { + requests: [ + { + url: config.url, + method: 'GET', + }, + ], + }, + }) + .result() + const response = responses[0] + + if (!ok(response)) { + throw new Error(`HTTP request failed with status: ${response.statusCode}`) + } + + // Convert response body to text using the helper function + const responseText = text(response) + + return Number.parseFloat(responseText) +} + +const onCronTrigger = (runtime: Runtime) => { + runtime.log('Confidential HTTP workflow triggered.') + + const confHTTPClient = new cre.capabilities.ConfidentialHTTPClient() + const result = confHTTPClient + .sendRequests( + runtime, + fetchMathResult, + consensusMedianAggregation(), + )(runtime.config) + .result() + + runtime.log(`Successfully fetched result: ${result}`) + + return { + result, + } +} + +const initWorkflow = (config: Config) => { + const cron = new cre.capabilities.CronCapability() + + return [cre.handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] +} + +export async function main() { + const runner = await Runner.newRunner({ configSchema }) + + await runner.run(initWorkflow) +} + +main() diff --git a/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/workflow.yaml b/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/workflow.yaml new file mode 100644 index 00000000..c667a7f1 --- /dev/null +++ b/packages/cre-sdk-examples/src/workflows/http-confidential-fetch/workflow.yaml @@ -0,0 +1,38 @@ +# ========================================================================== +# CRE WORKFLOW SETTINGS FILE +# ========================================================================== +# This file defines environment-specific workflow settings used by the CRE CLI. +# +# Each top-level key is a target (e.g., `production`, `production-testnet`, etc.). +# You can also define your own custom targets, such as `my-target`, and +# point the CLI to it via an environment variable. +# +# Note: If any setting in this file conflicts with a setting in the CRE Project Settings File, +# the value defined here in the workflow settings file will take precedence. +# +# Below is an example `my-target`: +# +# my-target: +# user-workflow: +# # Optional: The address of the workflow owner (wallet or MSIG contract). +# # Used to establish ownership for encrypting the workflow's secrets. +# # If omitted, defaults to an empty string. +# workflow-owner-address: "0x1234567890abcdef1234567890abcdef12345678" +# +# # Required: The name of the workflow to register with the Workflow Registry contract. +# workflow-name: "MyExampleWorkflow" + +# ========================================================================== +local-simulation: + user-workflow: + workflow-owner-address: "(optional) Multi-signature contract address" + workflow-name: "http-confidential-fetch" + workflow-artifacts: + workflow-path: "./index.ts" + config-path: "./config.json" + +# ========================================================================== +production-testnet: + user-workflow: + workflow-owner-address: "(optional) Multi-signature contract address" + workflow-name: "http-confidential-fetch" diff --git a/packages/cre-sdk-javy-plugin/dist/javy_chainlink_sdk.wasm b/packages/cre-sdk-javy-plugin/dist/javy_chainlink_sdk.wasm index 2c457c29..9a65e623 100755 Binary files a/packages/cre-sdk-javy-plugin/dist/javy_chainlink_sdk.wasm and b/packages/cre-sdk-javy-plugin/dist/javy_chainlink_sdk.wasm differ diff --git a/packages/cre-sdk/scripts/src/generate-sdks.ts b/packages/cre-sdk/scripts/src/generate-sdks.ts index a5d23b5b..c940641f 100644 --- a/packages/cre-sdk/scripts/src/generate-sdks.ts +++ b/packages/cre-sdk/scripts/src/generate-sdks.ts @@ -4,6 +4,7 @@ import { file_capabilities_internal_basicaction_v1_basic_action } from '@cre/gen import { file_capabilities_internal_basictrigger_v1_basic_trigger } from '@cre/generated/capabilities/internal/basictrigger/v1/basic_trigger_pb' import { file_capabilities_internal_consensus_v1alpha_consensus } from '@cre/generated/capabilities/internal/consensus/v1alpha/consensus_pb' import { file_capabilities_internal_nodeaction_v1_node_action } from '@cre/generated/capabilities/internal/nodeaction/v1/node_action_pb' +import { file_capabilities_networking_confidentialhttp_v1alpha_client } from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb' import { file_capabilities_networking_http_v1alpha_client } from '@cre/generated/capabilities/networking/http/v1alpha/client_pb' import { file_capabilities_networking_http_v1alpha_trigger } from '@cre/generated/capabilities/networking/http/v1alpha/trigger_pb' import { file_capabilities_scheduler_cron_v1_trigger } from '@cre/generated/capabilities/scheduler/cron/v1/trigger_pb' @@ -33,4 +34,6 @@ export const main = () => { generateSdk(file_capabilities_networking_http_v1alpha_trigger, './src/generated-sdk') generateSdk(file_capabilities_scheduler_cron_v1_trigger, './src/generated-sdk') + + generateSdk(file_capabilities_networking_confidentialhttp_v1alpha_client, './src/generated-sdk') } diff --git a/packages/cre-sdk/src/generated-sdk/capabilities/networking/confidentialhttp/v1alpha/client_sdk_gen.ts b/packages/cre-sdk/src/generated-sdk/capabilities/networking/confidentialhttp/v1alpha/client_sdk_gen.ts new file mode 100644 index 00000000..6bec3ecc --- /dev/null +++ b/packages/cre-sdk/src/generated-sdk/capabilities/networking/confidentialhttp/v1alpha/client_sdk_gen.ts @@ -0,0 +1,112 @@ +import { fromJson } from '@bufbuild/protobuf' +import { + type EnclaveActionInput, + type EnclaveActionInputJson, + EnclaveActionInputSchema, + type HTTPEnclaveResponseData, + HTTPEnclaveResponseDataSchema, +} from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb' +import { type NodeRuntime, type Runtime } from '@cre/sdk' +import { Report } from '@cre/sdk/report' +import type { ConsensusAggregation, PrimitiveTypes, UnwrapOptions } from '@cre/sdk/utils' + +export class SendRequestser { + constructor( + private readonly runtime: NodeRuntime, + private readonly client: ClientCapability, + ) {} + sendRequests(input: EnclaveActionInput | EnclaveActionInputJson): { + result: () => HTTPEnclaveResponseData + } { + return this.client.sendRequests(this.runtime, input) + } +} + +/** + * Client Capability + * + * Capability ID: confidential-http@1.0.0-alpha + * Capability Name: confidential-http + * Capability Version: 1.0.0-alpha + */ +export class ClientCapability { + /** The capability ID for this service */ + static readonly CAPABILITY_ID = 'confidential-http@1.0.0-alpha' + + static readonly CAPABILITY_NAME = 'confidential-http' + static readonly CAPABILITY_VERSION = '1.0.0-alpha' + + sendRequests( + runtime: NodeRuntime, + input: EnclaveActionInput | EnclaveActionInputJson, + ): { result: () => HTTPEnclaveResponseData } + sendRequests( + runtime: Runtime, + fn: (sendRequestser: SendRequestser, ...args: TArgs) => TOutput, + consensusAggregation: ConsensusAggregation, + unwrapOptions?: TOutput extends PrimitiveTypes ? never : UnwrapOptions, + ): (...args: TArgs) => { result: () => TOutput } + sendRequests(...args: unknown[]): unknown { + // Check if this is the sugar syntax overload (has function parameter) + if (typeof args[1] === 'function') { + const [runtime, fn, consensusAggregation, unwrapOptions] = args as [ + Runtime, + (sendRequestser: SendRequestser, ...args: unknown[]) => unknown, + ConsensusAggregation, + UnwrapOptions | undefined, + ] + return this.sendRequestsSugarHelper(runtime, fn, consensusAggregation, unwrapOptions) + } + // Otherwise, this is the basic call overload + const [runtime, input] = args as [ + NodeRuntime, + EnclaveActionInput | EnclaveActionInputJson, + ] + return this.sendRequestsCallHelper(runtime, input) + } + private sendRequestsCallHelper( + runtime: NodeRuntime, + input: EnclaveActionInput | EnclaveActionInputJson, + ): { result: () => HTTPEnclaveResponseData } { + // Handle input conversion - unwrap if it's a wrapped type, convert from JSON if needed + let payload: EnclaveActionInput + + if ((input as unknown as { $typeName?: string }).$typeName) { + // It's the original protobuf type + payload = input as EnclaveActionInput + } else { + // It's regular JSON, convert using fromJson + payload = fromJson(EnclaveActionInputSchema, input as EnclaveActionInputJson) + } + + const capabilityId = ClientCapability.CAPABILITY_ID + + const capabilityResponse = runtime.callCapability({ + capabilityId, + method: 'SendRequests', + payload, + inputSchema: EnclaveActionInputSchema, + outputSchema: HTTPEnclaveResponseDataSchema, + }) + + return { + result: () => { + const result = capabilityResponse.result() + + return result + }, + } + } + private sendRequestsSugarHelper( + runtime: Runtime, + fn: (sendRequestser: SendRequestser, ...args: TArgs) => TOutput, + consensusAggregation: ConsensusAggregation, + unwrapOptions?: TOutput extends PrimitiveTypes ? never : UnwrapOptions, + ): (...args: TArgs) => { result: () => TOutput } { + const wrappedFn = (runtime: NodeRuntime, ...args: TArgs) => { + const sendRequestser = new SendRequestser(runtime, this) + return fn(sendRequestser, ...args) + } + return runtime.runInNodeMode(wrappedFn, consensusAggregation, unwrapOptions) + } +} diff --git a/packages/cre-sdk/src/pb.ts b/packages/cre-sdk/src/pb.ts index bd92a1ed..8d5d5c7f 100644 --- a/packages/cre-sdk/src/pb.ts +++ b/packages/cre-sdk/src/pb.ts @@ -1,4 +1,5 @@ export * as EVM_PB from '@cre/generated/capabilities/blockchain/evm/v1alpha/client_pb' +export * as CONFIDENTIAL_HTTP_CLIENT_PB from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb' export * as HTTP_CLIENT_PB from '@cre/generated/capabilities/networking/http/v1alpha/client_pb' export * as HTTP_TRIGGER_PB from '@cre/generated/capabilities/networking/http/v1alpha/trigger_pb' export * as CRON_TRIGGER_PB from '@cre/generated/capabilities/scheduler/cron/v1/trigger_pb' diff --git a/packages/cre-sdk/src/sdk/cre/index.ts b/packages/cre-sdk/src/sdk/cre/index.ts index 946b82c9..108f1edf 100644 --- a/packages/cre-sdk/src/sdk/cre/index.ts +++ b/packages/cre-sdk/src/sdk/cre/index.ts @@ -3,6 +3,7 @@ */ import { ClientCapability as EVMClient } from '@cre/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen' +import { ClientCapability as ConfidentialHTTPClient } from '@cre/generated-sdk/capabilities/networking/confidentialhttp/v1alpha/client_sdk_gen' import { ClientCapability as HTTPClient } from '@cre/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen' import { HTTPCapability } from '@cre/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen' import { CronCapability } from '@cre/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen' @@ -26,7 +27,11 @@ export { type WriteCreReportRequest, type WriteCreReportRequestJson, } from '@cre/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen' - +// Confidential HTTP Capability +export { + ClientCapability as ConfidentialHTTPClient, + type SendRequestser as ConfidentialHTTPSendRequester, +} from '@cre/generated-sdk/capabilities/networking/confidentialhttp/v1alpha/client_sdk_gen' // HTTP Capability export { ClientCapability as HTTPClient, @@ -47,6 +52,7 @@ export const cre = { capabilities: { CronCapability, HTTPCapability, + ConfidentialHTTPClient, HTTPClient, EVMClient, }, diff --git a/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts b/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts index 8cd0f8c2..f9e9812d 100644 --- a/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts +++ b/packages/cre-sdk/src/sdk/utils/capabilities/http/http-helpers.ts @@ -1,3 +1,4 @@ +import type { ResponseTemplate } from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb' import type { Request, RequestJson, @@ -61,17 +62,17 @@ import { decodeJson } from '@cre/sdk/utils/decode-json' * @param response - The Response object * @returns The body as a trimmed string */ -export function text(response: Response): string +export function text(response: Response | ResponseTemplate): string /** * Returns the response body as a UTF-8 string, automatically trimmed * @param responseFn - Function that returns an object with result function that returns Response * @returns Object with result function that returns the body as a trimmed string */ -export function text(responseFn: () => { result: Response }): { +export function text(responseFn: () => { result: Response | ResponseTemplate }): { result: () => string } export function text( - responseOrFn: Response | (() => { result: Response }), + responseOrFn: Response | ResponseTemplate | (() => { result: Response | ResponseTemplate }), ): string | { result: () => string } { if (typeof responseOrFn === 'function') { return { @@ -149,17 +150,17 @@ export function getHeader( * @param response - The Response object * @returns True if the status code is in the 200-299 range */ -export function ok(response: Response): boolean +export function ok(response: Response | ResponseTemplate): boolean /** * Checks if the response status indicates success (200-299) * @param responseFn - Function that returns an object with result function that returns Response * @returns Object with result function that returns true if the status code is in the 200-299 range */ -export function ok(responseFn: () => { result: Response }): { +export function ok(responseFn: () => { result: Response | ResponseTemplate }): { result: () => boolean } export function ok( - responseOrFn: Response | (() => { result: Response }), + responseOrFn: Response | ResponseTemplate | (() => { result: Response | ResponseTemplate }), ): boolean | { result: () => boolean } { if (typeof responseOrFn === 'function') { return {