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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/cre-sdk-examples/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules
dist
.turbo
tmp.js
tmp.wasm
tmp.wasm
.cre_build_tmp.js
2 changes: 1 addition & 1 deletion packages/cre-sdk-examples/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@chainlink/cre-sdk-examples",
"private": true,
"version": "1.6.0",
"version": "1.7.0",
"type": "module",
"author": "Ernest Nowacki",
"license": "BUSL-1.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"schedule": "0 */1 * * * *",
"url": "https://api.mathjs.org/v4?expr=randomInt(1,101)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
ConfidentialHTTPClient,
CronCapability,
handler,
httpRequest,
json,
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<typeof configSchema>

// Workflow demonstrate a usage of `httpRequest` helper
// to build type-safe payloads for `ConfidentialHTTPClient`.
const onCronTrigger = (runtime: Runtime<Config>) => {
const client = new ConfidentialHTTPClient()

// Example 1: request config as separate variable
const separateRequestConfig = {
request: httpRequest({
url: runtime.config.url,
method: 'POST',
bodyString: '{ hello: "world" }',
multiHeaders: {
'content-type': { values: ['application/json'] },
},
}),
}

client.sendRequest(runtime, separateRequestConfig).result()

// Example 2: using helper inline
client
.sendRequest(runtime, {
request: httpRequest({
url: runtime.config.url,
method: 'POST',
body: { hello: 'world' },
multiHeaders: {
'content-type': { values: ['application/json'] },
},
}),
})
.result()

// Example 3: not using helper at all
client
.sendRequest(runtime, {
request: {
url: runtime.config.url,
method: 'POST',
// no helper -> must use bodyString/bodyBytes (proto oneof keys)
bodyString: JSON.stringify({ hello: 'world' }),
multiHeaders: {
'content-type': { values: ['application/json'] },
},
},
})
.result()

return { success: true }
}

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

export async function main() {
const runner = await Runner.newRunner<Config>({ configSchema })
await runner.run(initWorkflow)
}
Original file line number Diff line number Diff line change
@@ -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: "confidential-http-with-body"
workflow-artifacts:
workflow-path: "./index.ts"
config-path: "./config.json"

# ==========================================================================
production-testnet:
user-workflow:
workflow-owner-address: "(optional) Multi-signature contract address"
workflow-name: "confidential-http-with-body"
2 changes: 1 addition & 1 deletion packages/cre-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/cre-sdk",
"version": "1.6.0",
"version": "1.7.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { fromJson } from '@bufbuild/protobuf'
import {
type ConfidentialHTTPRequest,
type ConfidentialHTTPRequestJson,
ConfidentialHTTPRequestSchema,
type HTTPResponse,
HTTPResponseSchema,
} from '@cre/generated/capabilities/networking/confidentialhttp/v1alpha/client_pb'
import type { Runtime } from '@cre/sdk'
import { Report } from '@cre/sdk/report'
import {
type ConfidentialHttpRequestInput,
normalizeConfidentialHttpRequestInput,
} from '@cre/sdk/utils/capabilities/confidentialhttp/confidential-http-helpers'
import { hexToBytes } from '@cre/sdk/utils/hex-utils'

/**
Expand All @@ -26,7 +29,7 @@ export class ClientCapability {

sendRequest(
runtime: Runtime<unknown>,
input: ConfidentialHTTPRequest | ConfidentialHTTPRequestJson,
input: ConfidentialHTTPRequest | ConfidentialHttpRequestInput,
): { result: () => HTTPResponse } {
// Handle input conversion - unwrap if it's a wrapped type, convert from JSON if needed
let payload: ConfidentialHTTPRequest
Expand All @@ -36,7 +39,10 @@ export class ClientCapability {
payload = input as ConfidentialHTTPRequest
} else {
// It's regular JSON, convert using fromJson
payload = fromJson(ConfidentialHTTPRequestSchema, input as ConfidentialHTTPRequestJson)
payload = fromJson(
ConfidentialHTTPRequestSchema,
normalizeConfidentialHttpRequestInput(input as ConfidentialHttpRequestInput),
)
}

const capabilityId = ClientCapability.CAPABILITY_ID
Expand Down
12 changes: 11 additions & 1 deletion packages/cre-sdk/src/generator/generate-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export function generateActionMethod(
const inputTypes = hasWrappedInput
? [wrappedInputType.name, `${wrappedInputType.name}Json`]
: [method.input.name, `${method.input.name}Json`]
const isConfidentialHttpRequestInput =
method.input.file.name === 'capabilities/networking/confidentialhttp/v1alpha/client' &&
method.input.name === 'ConfidentialHTTPRequest'
if (isConfidentialHttpRequestInput) {
inputTypes[1] = 'ConfidentialHttpRequestInput'
}

// Build output type
const hasWrappedOutput = wrappedOutputType !== method.output
Expand Down Expand Up @@ -61,7 +67,11 @@ export function generateActionMethod(
payload = input as ${method.input.name}
} else {
// It's regular JSON, convert using fromJson
payload = fromJson(${method.input.name}Schema, input as ${method.input.name}Json)
payload = fromJson(${method.input.name}Schema, ${
isConfidentialHttpRequestInput
? 'normalizeConfidentialHttpRequestInput(input as ConfidentialHttpRequestInput)'
: `input as ${method.input.name}Json`
})
}`
}

Expand Down
17 changes: 16 additions & 1 deletion packages/cre-sdk/src/generator/generate-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,20 @@ export function generateSdk(file: GenFile, outputDir: string) {

let hasTriggers = false
let hasActions = false
let hasConfidentialHttpRequestInput = false
// Process each method to collect types
service.methods.forEach((method) => {
if (method.methodKind === 'server_streaming') {
hasTriggers = true
} else {
hasActions = true
}
const isConfidentialHttpRequestInput =
method.input.file.name === 'capabilities/networking/confidentialhttp/v1alpha/client' &&
method.input.name === 'ConfidentialHTTPRequest'
if (isConfidentialHttpRequestInput) {
hasConfidentialHttpRequestInput = true
}

// Handle input type
const inputFile = method.input.file
Expand All @@ -76,7 +83,9 @@ export function generateSdk(file: GenFile, outputDir: string) {

const inputPathTypes = typeImports.get(inputPath)!
inputPathTypes.add(`${method.input.name}Schema`)
inputPathTypes.add(`type ${method.input.name}Json`)
if (!isConfidentialHttpRequestInput) {
inputPathTypes.add(`type ${method.input.name}Json`)
}
inputPathTypes.add(`type ${method.input.name}`)

// Handle output type
Expand Down Expand Up @@ -108,6 +117,12 @@ export function generateSdk(file: GenFile, outputDir: string) {
imports.add(`import { type Any, AnySchema, anyPack } from "@bufbuild/protobuf/wkt"`)
}

if (hasConfidentialHttpRequestInput) {
imports.add(
'import { normalizeConfidentialHttpRequestInput, type ConfidentialHttpRequestInput } from "@cre/sdk/utils/capabilities/confidentialhttp/confidential-http-helpers"',
)
}

if (hasActions) {
if (modePrefix !== '') {
imports.add(`import type { Runtime, ${modePrefix}Runtime } from "@cre/sdk"`)
Expand Down
18 changes: 9 additions & 9 deletions packages/cre-sdk/src/sdk/impl/runtime-impl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ describe('test getSecret', () => {
test('getSecret in node mode throws DonModeError', () => {
const helpers = createRuntimeHelpersMock()

ConsensusCapability.prototype.simple = mock(() => {
;(ConsensusCapability.prototype as any).simple = mock(() => {
return { result: () => Value.from(0).proto() }
})

Expand Down Expand Up @@ -695,7 +695,7 @@ describe('test run in node mode', () => {
}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, inputs: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
expect(modes).toEqual([Mode.DON, Mode.NODE, Mode.DON])
expect(inputs.default).toBeUndefined()
Expand Down Expand Up @@ -770,7 +770,7 @@ describe('test run in node mode', () => {
switchModes: mock((_: Mode) => {}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, inputs: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
expect(inputs.default).toBeUndefined()
expect(inputs.descriptors).toEqual(
Expand Down Expand Up @@ -804,7 +804,7 @@ describe('test run in node mode', () => {
switchModes: mock((_: Mode) => {}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, inputs: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
const inputsProto = inputs as SimpleConsensusInputs
expect(inputsProto.observation.case).toEqual('value')
Expand Down Expand Up @@ -837,7 +837,7 @@ describe('test run in node mode', () => {
switchModes: mock((_: Mode) => {}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, inputs: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
const inputsProto = inputs as SimpleConsensusInputs
expect(inputsProto.observation.case).toEqual('error')
Expand Down Expand Up @@ -869,7 +869,7 @@ describe('test run in node mode', () => {
}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, __: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
return { result: () => Value.from(0).proto() }
},
Expand All @@ -896,7 +896,7 @@ describe('test run in node mode', () => {
switchModes: mock((_: Mode) => {}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, inputs: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
expect(inputs.default).toBeUndefined()
expect(inputs.descriptors).toEqual(
Expand Down Expand Up @@ -948,7 +948,7 @@ describe('test run in node mode', () => {
}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, __: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
return {
result: () => Value.from(create(NodeOutputsSchema, { outputThing: 42 })).proto(),
Expand Down Expand Up @@ -1026,7 +1026,7 @@ describe('test run in node mode', () => {
switchModes: mock((_: Mode) => {}),
})

ConsensusCapability.prototype.simple = mock(
;(ConsensusCapability.prototype as any).simple = mock(
(_: Runtime<unknown>, inputs: SimpleConsensusInputs | SimpleConsensusInputsJson) => {
const inputsProto = inputs as SimpleConsensusInputs
if (inputsProto.observation.case === 'value') {
Expand Down
3 changes: 2 additions & 1 deletion packages/cre-sdk/src/sdk/impl/runtime-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,8 @@ export class RuntimeImpl<C> extends BaseRuntimeImpl<C> implements Runtime<C> {
*/
report(input: ReportRequest | ReportRequestJson): { result: () => Report } {
const consensus = new ConsensusCapability()
const call = consensus.report(this, input)
// Cast to native overload signature - the impl dispatches on $typeName.
const call = consensus.report(this, input as ReportRequest)
return {
result: () => call.result(),
}
Expand Down
1 change: 1 addition & 0 deletions packages/cre-sdk/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './runtime'
export * from './types/bufbuild-types'
export * from './utils'
// Export HTTP response helpers
export * from './utils/capabilities/confidentialhttp/confidential-http-helpers'
export * from './utils/capabilities/http/http-helpers'
export * from './wasm'
export * from './workflow'
Loading
Loading