From d04a7d108699b561d9056608eb9db0f165adb1d4 Mon Sep 17 00:00:00 2001 From: intelliDean Date: Fri, 13 Feb 2026 00:44:48 +0100 Subject: [PATCH 1/2] local simulation working perfectly --- cre-workflow/main.ts | 24 ++++++++++----- cre-workflow/src/ai-analyzer.ts | 50 +++++++++++++++++--------------- cre-workflow/src/data-sources.ts | 10 +++---- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/cre-workflow/main.ts b/cre-workflow/main.ts index cdd248c..f98ce88 100644 --- a/cre-workflow/main.ts +++ b/cre-workflow/main.ts @@ -5,6 +5,7 @@ import { prepareReportRequest, type Runtime, } from "@chainlink/cre-sdk"; +import { encodeAbiParameters } from "viem"; import { BlockchainClient } from "./src/blockchain-client.js"; import { DataSourceClient } from "./src/data-sources.js"; import { AIAnalyzer } from "./src/ai-analyzer.js"; @@ -59,19 +60,26 @@ const onCronTrigger = async (runtime: Runtime) => { config.openAiApiKey || "" ); - if (aiResult.confidence < 0.7) { - runtime.log(`⚠️ Low confidence (${aiResult.confidence}) for market #${market.id}. Skipping.`); + if (!aiResult) { + runtime.log(`⚠️ Consensus failed or AI error for market #${market.id}. Skipping.`); continue; } - runtime.log(`✅ DON Consensus reached: "${aiResult.outcome}"`); + runtime.log(`✅ DON Consensus reached: "${aiResult}"`); // 4. Secure On-chain Write (2-step pattern) - // a) Generate signed report - const settlementData = `receiveSettlement(uint256 ${market.id}, string "${aiResult.outcome}", bytes 0x)`; - // Note: We'll use a manual encoding match for the report - // In a real environment, we'd use encodeAbiParameters, but for simplicity we'll replicate the core logic - const reportRequest = prepareReportRequest(aiResult.outcome as `0x${string}`); + // a) ABI Encode the parameters for receiveSettlement(uint256, string, bytes) + const encodedPayload = encodeAbiParameters( + [ + { name: 'marketId', type: 'uint256' }, + { name: 'outcome', type: 'string' }, + { name: 'proof', type: 'bytes' } + ], + [market.id, aiResult as string, '0x'] + ); + + // b) Generate signed report using the helper + const reportRequest = prepareReportRequest(encodedPayload); const report = runtime.report(reportRequest).result(); // b) Submit report via EVM capability diff --git a/cre-workflow/src/ai-analyzer.ts b/cre-workflow/src/ai-analyzer.ts index cb65b81..3e0ff43 100644 --- a/cre-workflow/src/ai-analyzer.ts +++ b/cre-workflow/src/ai-analyzer.ts @@ -6,6 +6,7 @@ import { HTTPClient, type Runtime, consensusIdenticalAggregation, + Value, } from "@chainlink/cre-sdk"; import type { DataSource, AIAnalysisResult } from "./types.js"; @@ -25,8 +26,8 @@ export class AIAnalyzer { outcomes: string[], dataSources: DataSource[], apiKey: string - ): Promise { - return runtime.runInNodeMode( + ): Promise { + const nodeRun = runtime.runInNodeMode( async (nodeRuntime) => { try { const context = dataSources @@ -64,7 +65,8 @@ IMPORTANT: "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, - body: JSON.stringify({ + // The engine expects bytes/base64 for the body field if it's a 'bytes' type in proto + body: Buffer.from(JSON.stringify({ model: "gpt-4o-mini", messages: [ { @@ -78,44 +80,46 @@ IMPORTANT: ], temperature: 0.3, max_tokens: 500, - }), + })).toString('base64'), }); const response = request.result(); const bodyText = new TextDecoder().decode(response.body); const data = JSON.parse(bodyText); + + if (!data.choices || data.choices.length === 0) { + throw new Error(`OpenAI API Error: ${bodyText}`); + } + const aiText = data.choices[0].message.content; // Extract JSON const jsonMatch = aiText.match(/\{[\s\S]*\}/); - if (!jsonMatch) throw new Error("No JSON found in AI response"); + if (!jsonMatch) throw new Error(`No JSON found in AI response: ${aiText}`); const parsed = JSON.parse(jsonMatch[0]); // Validate outcome const outcomeIndex = outcomes.findIndex( - (o) => o.toLowerCase() === parsed.outcome.toLowerCase() + (o) => o.toLowerCase() === (parsed.outcome || "").toLowerCase() ); if (outcomeIndex === -1) throw new Error(`Invalid outcome: ${parsed.outcome}`); - return { - outcome: outcomes[outcomeIndex], - outcomeIndex, - confidence: parsed.confidence, - reasoning: parsed.reasoning, - sources: [], - }; + return outcomes[outcomeIndex]; } catch (error) { - // Fallback - return { - outcome: outcomes[0], - outcomeIndex: 0, - confidence: 0, - reasoning: `Error: ${error}`, - sources: [], - }; + nodeRuntime.log(`[AI Node] Error: ${error}`); + return outcomes[0]; // Fallback } }, - consensusIdenticalAggregation() as any - )().result(); + consensusIdenticalAggregation() as any + ); + + const execution = await nodeRun(); + const result = execution.result(); + + try { + return Value.from(result).unwrap() as string; + } catch (e) { + return String(result || outcomes[0]); + } } } diff --git a/cre-workflow/src/data-sources.ts b/cre-workflow/src/data-sources.ts index 2dcbbc0..b1fcb17 100644 --- a/cre-workflow/src/data-sources.ts +++ b/cre-workflow/src/data-sources.ts @@ -47,13 +47,13 @@ export class DataSourceClient { name: "NewsAPI", data: articles, confidence: 0.8, - timestamp: Date.now(), + timestamp: 0, // Set to 0 for consensus determinism }; }, // Consensus: For text data, consensus usually means nodes provide identical or similar strings. // We'll use consensusIdenticalAggregation to ensure all nodes agree on the exact string. - consensusIdenticalAggregation() - ).result(); + consensusIdenticalAggregation() as any + )().result() as Promise; } /** @@ -79,7 +79,7 @@ export class DataSourceClient { return data[coinId]?.usd || null; }, consensusIdenticalAggregation() as any - )().result(); + )().result() as Promise; } /** @@ -100,7 +100,7 @@ export class DataSourceClient { name: "General Knowledge (GPT-4 Internal)", data: `The AI analyzer will verify the following question using its internal knowledge base: "${question}".`, confidence: 0.5, - timestamp: Date.now(), + timestamp: 0, // Set to 0 for consensus determinism }); } From 9c3aacacec9774c030cfbd8d7db83e0752b2a652 Mon Sep 17 00:00:00 2001 From: intelliDean Date: Fri, 13 Feb 2026 00:56:51 +0100 Subject: [PATCH 2/2] add automated type-checking for CRE workflow --- .github/workflows/cre-ci.yml | 36 +++++++++++++++++++++++++++++++++ cre-workflow/src/ai-analyzer.ts | 11 +++++----- 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/cre-ci.yml diff --git a/.github/workflows/cre-ci.yml b/.github/workflows/cre-ci.yml new file mode 100644 index 0000000..90bff4a --- /dev/null +++ b/.github/workflows/cre-ci.yml @@ -0,0 +1,36 @@ +name: CRE Workflow CI + +on: + push: + branches: [ main, develop, cre ] + paths: + - 'cre-workflow/**' + - '.github/workflows/cre-ci.yml' + pull_request: + branches: [ main, develop, cre ] + paths: + - 'cre-workflow/**' + +jobs: + validate: + name: Check Types + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./cre-workflow + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Type check + run: bun x tsc --noEmit diff --git a/cre-workflow/src/ai-analyzer.ts b/cre-workflow/src/ai-analyzer.ts index 3e0ff43..26a6007 100644 --- a/cre-workflow/src/ai-analyzer.ts +++ b/cre-workflow/src/ai-analyzer.ts @@ -27,7 +27,7 @@ export class AIAnalyzer { dataSources: DataSource[], apiKey: string ): Promise { - const nodeRun = runtime.runInNodeMode( + const nodeRunFunction = runtime.runInNodeMode( async (nodeRuntime) => { try { const context = dataSources @@ -113,13 +113,14 @@ IMPORTANT: consensusIdenticalAggregation() as any ); - const execution = await nodeRun(); - const result = execution.result(); + // runInNodeMode returns a function that returns a Promise for the ExecutionResult + const executionResult = await nodeRunFunction(); + const resultValue = executionResult.result(); try { - return Value.from(result).unwrap() as string; + return Value.from(resultValue as any).unwrap() as string; } catch (e) { - return String(result || outcomes[0]); + return String(resultValue || outcomes[0]); } } }