From ac45c5c949fc55f7ca086c8928645d75199a6042 Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 11:31:17 +0700 Subject: [PATCH 1/6] Move nla to root dir and export from index.ts --- cli/client/create-escrow.ts | 2 +- cli/deployments/localhost.json | 2 +- cli/server/oracle.ts | 2 +- index.ts | 14 ++------------ clients/nla.ts => nla.ts | 0 tests/nla.test.ts | 4 ++-- tests/nlaOracle.test.ts | 4 ++-- 7 files changed, 9 insertions(+), 19 deletions(-) rename clients/nla.ts => nla.ts (100%) diff --git a/cli/client/create-escrow.ts b/cli/client/create-escrow.ts index 242437f..827bcb0 100644 --- a/cli/client/create-escrow.ts +++ b/cli/client/create-escrow.ts @@ -13,7 +13,7 @@ import { foundry } from "viem/chains"; import { existsSync, readFileSync } from "fs"; import { resolve } from "path"; import { makeClient } from "alkahest-ts"; -import { makeLLMClient } from "../../clients/nla.ts"; +import { makeLLMClient } from "../.."; import {fixtures} from "alkahest-ts"; // Helper function to display usage diff --git a/cli/deployments/localhost.json b/cli/deployments/localhost.json index 5c43d7d..06c6916 100644 --- a/cli/deployments/localhost.json +++ b/cli/deployments/localhost.json @@ -2,7 +2,7 @@ "network": "localhost", "chainId": 31337, "rpcUrl": "http://localhost:8545", - "deployedAt": "2025-12-15T17:03:01.822Z", + "deployedAt": "2026-01-02T04:20:18.975Z", "deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "addresses": { "easSchemaRegistry": "0x5fbdb2315678afecb367f032d93f642f64180aa3", diff --git a/cli/server/oracle.ts b/cli/server/oracle.ts index 335ce14..585b59a 100644 --- a/cli/server/oracle.ts +++ b/cli/server/oracle.ts @@ -3,7 +3,7 @@ import { parseArgs } from "util"; import { parseAbiParameters, createWalletClient, http, publicActions } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { foundry } from "viem/chains"; -import { makeLLMClient } from "../../clients/nla"; +import { makeLLMClient } from "../.."; import { existsSync, readFileSync } from "fs"; import { resolve } from "path"; import { makeClient } from "alkahest-ts"; diff --git a/index.ts b/index.ts index afea74a..65a94ed 100644 --- a/index.ts +++ b/index.ts @@ -1,12 +1,2 @@ -import { generateText } from "ai" -import { createOpenAI } from "@ai-sdk/openai" - -const openai = createOpenAI({ - apiKey: undefined, -}) - -const { text } = await generateText({ - model: openai("gpt-4.1"), - prompt: "What is love?", -}) -console.log(text); \ No newline at end of file +export { makeLLMClient } from "./nla"; +export type { LLMProvider, LLMDemand } from "./nla"; diff --git a/clients/nla.ts b/nla.ts similarity index 100% rename from clients/nla.ts rename to nla.ts diff --git a/tests/nla.test.ts b/tests/nla.test.ts index dfa4264..1c342fb 100644 --- a/tests/nla.test.ts +++ b/tests/nla.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, beforeEach, expect, test } from "bun:test"; -import { makeLLMClient } from "../clients/nla"; -import type { LLMProvider, LLMDemand } from "../clients/nla"; +import { makeLLMClient } from ".."; +import type { LLMProvider, LLMDemand } from ".."; import { setupTestEnvironment, type TestContext, diff --git a/tests/nlaOracle.test.ts b/tests/nlaOracle.test.ts index e76c6a3..f971c93 100644 --- a/tests/nlaOracle.test.ts +++ b/tests/nlaOracle.test.ts @@ -8,7 +8,7 @@ import { setupTestEnvironment, type TestContext, } from "alkahest-ts"; -import { makeLLMClient } from "../clients/nla"; +import { makeLLMClient } from ".."; let testContext: TestContext; let charlieClient: ReturnType }>>; @@ -108,7 +108,7 @@ Fulfillment: {{obligation}}`, ); //Should call WaitForArbitration() - await Bun.sleep(2000); + await Bun.sleep(5000); const collectionHash = await testContext.bob.client.erc20.collectEscrow( escrow.uid, From a779177958c53a8d897de2bde9963251b76cd1e4 Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 12:59:13 +0700 Subject: [PATCH 2/6] Update the sample command --- bun.lock | 19 ++--- cli/client/create-escrow.ts | 22 +++--- cli/client/fulfill-escrow.ts | 7 +- cli/deployments/localhost.json | 2 +- cli/server/deploy.ts | 12 +++- nla.ts | 123 ++++++++++++++++++++++++++++----- package.json | 5 +- 7 files changed, 143 insertions(+), 47 deletions(-) diff --git a/bun.lock b/bun.lock index 54ad8ae..441adc3 100644 --- a/bun.lock +++ b/bun.lock @@ -5,9 +5,10 @@ "": { "name": "natural-language-agreement-extension", "dependencies": { - "@ai-sdk/openai": "^2.0.50", + "@ai-sdk/anthropic": "^3.0.2", + "@ai-sdk/openai": "^3.0.2", "@viem/anvil": "^0.0.10", - "ai": "^5.0.68", + "ai": "^6.0.5", "alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package", "arktype": "^2.1.23", "viem": "^2.42.1", @@ -24,13 +25,15 @@ "packages": { "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-D6iSsrOYryBSPsFtOiEDv54jnjVCU/flIuXdjuRY7LdikB0KGjpazN8Dt4ONXzL+ux69ds2nzFNKke/w/fgLAA=="], - "@ai-sdk/openai": ["@ai-sdk/openai@2.0.86", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-obsLIOyA93lbQiSt1rvBItoVQp1U2RDPs0bNG0JYhm6Gku8Dg/0Cm8e4NUWT5p5PN10/doKSb3SMSKCixwIAKA=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.4", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OlccjNYZ5+4FaNyvs0kb3N5H6U/QCKlKPTGsgUo8IZkqfMQu8ALI1XD6l/BCuTKto+OO9xUPObT/W7JhbqJ5nA=="], - "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], + "@ai-sdk/openai": ["@ai-sdk/openai@3.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GONwavgSWtcWO+t9+GpGK8l7nIYh+zNtCL/NYDSeHxHiw6ksQS9XMRWrZyE5NpJ0EXNxSAWCHIDmb1WvTqhq9Q=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + "@ai-sdk/provider": ["@ai-sdk/provider@3.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-2lR4w7mr9XrydzxBSjir4N6YMGdXD+Np1Sh0RXABh7tWdNFFwIeRI1Q+SaYZMbfL8Pg8RRLcrxQm51yxTLhokg=="], + + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-KaykkuRBdF/ffpI5bwpL4aSCmO/99p8/ci+VeHwJO8tmvXtiVAb99QeyvvvXmL61e9Zrvv4GBGoajW19xdjkVQ=="], "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], @@ -50,7 +53,7 @@ "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], @@ -62,7 +65,7 @@ "abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - "ai": ["ai@5.0.113", "", { "dependencies": { "@ai-sdk/gateway": "2.0.21", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g=="], + "ai": ["ai@6.0.5", "", { "dependencies": { "@ai-sdk/gateway": "3.0.4", "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CKL3dDHedWskC6EY67LrULonZBU9vL+Bwa+xQEcprBhJfxpogntG3utjiAkYuy5ZQatyWk+SmWG8HLvcnhvbRg=="], "alkahest-ts": ["alkahest-ts@github:VAR-META-Tech/alkahest#d40de37", {}, "VAR-META-Tech-alkahest-d40de37"], diff --git a/cli/client/create-escrow.ts b/cli/client/create-escrow.ts index 827bcb0..300374e 100644 --- a/cli/client/create-escrow.ts +++ b/cli/client/create-escrow.ts @@ -167,17 +167,11 @@ async function main() { deployment.addresses ); - // Extend with LLM client + // Extend with LLM client (only for encoding the demand, no API calls needed) const llmClient = client.extend((c) => ({ llm: makeLLMClient([]), })); - // Add OpenAI provider (needed for encoding demands) - llmClient.llm.addProvider({ - providerName: "OpenAI", - apiKey: process.env.OPENAI_API_KEY || "", - }); - // Check token balance const tokenBalance = await walletClient.readContract({ address: tokenAddress as `0x${string}`, @@ -228,10 +222,16 @@ Fulfillment: {{obligation}}`, console.log(` Recipient: ${escrow.recipient}`); console.log("šŸŽÆ Next Steps:"); - console.log("1. Wait for someone to fulfill the obligation"); - console.log("2. The oracle will arbitrate the fulfillment"); - console.log("3. If approved, you can collect the escrow"); - console.log(`\n Escrow UID: ${escrow.uid}`); + console.log("1. Someone fulfills the obligation:"); + console.log(` nla escrow:fulfill \\`); + console.log(` --escrow-uid ${escrow.uid} \\`); + console.log(` --fulfillment "Yes, the sky is blue" \\`); + console.log(` --oracle ${oracleAddress}`); + console.log("\n2. The oracle will arbitrate the fulfillment automatically"); + console.log("\n3. If approved, collect the escrow:"); + console.log(` nla escrow:collect \\`); + console.log(` --escrow-uid ${escrow.uid} \\`); + console.log(` --fulfillment-uid `); } catch (error) { console.error("āŒ Failed to create escrow:", error); diff --git a/cli/client/fulfill-escrow.ts b/cli/client/fulfill-escrow.ts index 7a24010..d6cf1d2 100644 --- a/cli/client/fulfill-escrow.ts +++ b/cli/client/fulfill-escrow.ts @@ -178,9 +178,10 @@ async function main() { console.log("✨ Arbitration requested successfully!\n"); console.log("šŸŽÆ Next Steps:"); console.log("1. Wait for the oracle to arbitrate (usually a few seconds)"); - console.log("2. Check the result with the oracle"); - console.log("3. If approved, collect the escrow"); - console.log(`\n Fulfillment UID: ${fulfillmentAttestation.uid}`); + console.log("\n2. If approved, collect the escrow:"); + console.log(` nla escrow:collect \\`); + console.log(` --escrow-uid ${escrowUid} \\`); + console.log(` --fulfillment-uid ${fulfillmentAttestation.uid}`); } catch (error) { console.error("āŒ Failed to fulfill escrow:", error); diff --git a/cli/deployments/localhost.json b/cli/deployments/localhost.json index 06c6916..be515f7 100644 --- a/cli/deployments/localhost.json +++ b/cli/deployments/localhost.json @@ -2,7 +2,7 @@ "network": "localhost", "chainId": 31337, "rpcUrl": "http://localhost:8545", - "deployedAt": "2026-01-02T04:20:18.975Z", + "deployedAt": "2026-01-02T05:54:03.735Z", "deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "addresses": { "easSchemaRegistry": "0x5fbdb2315678afecb367f032d93f642f64180aa3", diff --git a/cli/server/deploy.ts b/cli/server/deploy.ts index fa55ea7..40f3db7 100644 --- a/cli/server/deploy.ts +++ b/cli/server/deploy.ts @@ -363,9 +363,15 @@ async function main() { console.log("\nšŸŽÆ Next steps:"); console.log("1. Start the oracle:"); - console.log(" ./scripts/start-oracle.sh " + network); - console.log("\n2. Create an escrow:"); - console.log(` bun run escrow:create --demand "Your demand" --amount 10 --token ${addresses.mockERC20A} --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --private-key 0xac09...`); + console.log(` nla start-oracle`); + console.log("\n2. Export your private key (use a test account private key):"); + console.log(` export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80`); + console.log("\n3. Create an escrow:"); + console.log(` nla escrow:create \\`); + console.log(` --demand "The sky is blue" \\`); + console.log(` --amount 10 \\`); + console.log(` --token ${addresses.mockERC20A} \\`); + console.log(` --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8`); } catch (error) { console.error("āŒ Deployment failed:", error); diff --git a/nla.ts b/nla.ts index 8711f48..f0c4425 100644 --- a/nla.ts +++ b/nla.ts @@ -1,3 +1,49 @@ +/** + * Natural Language Agreement (NLA) Client + * + * Supports multiple LLM providers for arbitration: + * + * 1. OpenAI: + * - providerName: "OpenAI" + * - models: "gpt-4", "gpt-4-turbo", "gpt-3.5-turbo", etc. + * - Get API key from: https://platform.openai.com/api-keys + * + * 2. Anthropic (Claude): + * - providerName: "Anthropic" or "Claude" + * - models: "claude-3-5-sonnet-20241022", "claude-3-opus-20240229", etc. + * - Get API key from: https://console.anthropic.com/ + * + * 3. OpenRouter: + * - providerName: "OpenRouter" + * - models: Any model available on OpenRouter (e.g., "openai/gpt-4", "anthropic/claude-3-opus") + * - Get API key from: https://openrouter.ai/keys + * - baseURL: "https://openrouter.ai/api/v1" (default) + * + * Example usage: + * ```typescript + * const llmClient = makeLLMClient([]); + * + * // Add OpenAI provider + * llmClient.addProvider({ + * providerName: "OpenAI", + * apiKey: "sk-..." + * }); + * + * // Add Anthropic provider + * llmClient.addProvider({ + * providerName: "Anthropic", + * apiKey: "sk-ant-..." + * }); + * + * // Add OpenRouter provider + * llmClient.addProvider({ + * providerName: "OpenRouter", + * apiKey: "sk-or-...", + * baseURL: "https://openrouter.ai/api/v1" + * }); + * ``` + */ + import { decodeAbiParameters, encodeAbiParameters, @@ -5,11 +51,13 @@ import { } from "viem"; import { generateText } from "ai"; import { createOpenAI } from "@ai-sdk/openai"; +import { createAnthropic } from "@ai-sdk/anthropic"; export type LLMProvider = { providerName: string; apiKey?: string; + baseURL?: string; // For OpenRouter or custom endpoints }; export type LLMDemand = { @@ -47,32 +95,69 @@ export const makeLLMClient = ( } console.log(`Using provider: ${selectedProvider.providerName} for arbitration demand: ${JSON.stringify(demand)}`); - if (selectedProvider.providerName.toLowerCase() === 'openai') { + + // Replace placeholders with actual values + const promptTemplate = `${demand.arbitrationPrompt}` + .replace(/\{\{demand\}\}/g, demand.demand) + .replace(/\{\{obligation\}\}/g, obligation); + const systemPrompt = "You are an arbitrator that always tells the truth. You must respond with only 'true' or 'false' - no other words or explanations."; + const userPrompt = `${promptTemplate} +Based on the above information, determine if the fulfillment satisfies the demand. +Answer ONLY with 'true' or 'false' - no explanations or additional text.`; + + let text: string; + const providerName = selectedProvider.providerName.toLowerCase(); + + if (providerName === 'openai' || providerName.includes('openai')) { const openai = createOpenAI({ apiKey: selectedProvider.apiKey, - }) - - // Replace placeholders with actual values - const promptTemplate = `${demand.arbitrationPrompt}` - .replace(/\{\{demand\}\}/g, demand.demand) - .replace(/\{\{obligation\}\}/g, obligation); - - - const { text } = await generateText({ - model: openai(demand.arbitrationModel), - system: "You are an arbitrator that always tells the truth. You must respond with only 'true' or 'false' - no other words or explanations.", - prompt: `${promptTemplate} -Based on the above information, determine if the fulfillment satisfies the demand. -Answer ONLY with 'true' or 'false' - no explanations or additional text.` + baseURL: selectedProvider.baseURL, }); - console.log(`LLM Response: ${text}`); + const result = await generateText({ + model: openai(demand.arbitrationModel), + system: systemPrompt, + prompt: userPrompt, + }); + text = result.text; - const cleanedResponse = text.trim().toLowerCase(); - return cleanedResponse === 'true'; + } else if (providerName === 'anthropic' || providerName.includes('anthropic') || providerName.includes('claude')) { + const anthropic = createAnthropic({ + apiKey: selectedProvider.apiKey, + baseURL: selectedProvider.baseURL, + }); + + const result = await generateText({ + model: anthropic(demand.arbitrationModel), + system: systemPrompt, + prompt: userPrompt, + }); + text = result.text; + + } else if (providerName === 'openrouter' || providerName.includes('openrouter')) { + // OpenRouter uses OpenAI-compatible API + const openrouter = createOpenAI({ + apiKey: selectedProvider.apiKey, + baseURL: selectedProvider.baseURL, + }); + + const result = await generateText({ + model: openrouter(demand.arbitrationModel), + system: systemPrompt, + prompt: userPrompt, + }); + text = result.text; + + } else { + throw new Error(`Unsupported provider: ${selectedProvider.providerName}`); } - return false; + + console.log(`LLM Response: ${text}`); + + const cleanedResponse = text.trim().toLowerCase(); + return cleanedResponse === 'true'; + } catch (error) { console.error('Error in LLM arbitration:', error); throw new Error(`LLM arbitration failed: ${error}`); diff --git a/package.json b/package.json index 0665723..9fe223a 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,10 @@ "typescript": "^5.9.3" }, "dependencies": { - "@ai-sdk/openai": "^2.0.50", + "@ai-sdk/anthropic": "^3.0.2", + "@ai-sdk/openai": "^3.0.2", "@viem/anvil": "^0.0.10", - "ai": "^5.0.68", + "ai": "^6.0.5", "alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package", "arktype": "^2.1.23", "viem": "^2.42.1", From 5a66f56f11788b8f40af993b698c9b844e2375a2 Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 13:07:47 +0700 Subject: [PATCH 3/6] Support config AI models and providers --- cli/client/create-escrow.ts | 26 +++++++++++++++++++------- cli/deployments/localhost.json | 2 +- cli/index.ts | 6 ++++++ cli/server/deploy.ts | 4 +++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/cli/client/create-escrow.ts b/cli/client/create-escrow.ts index 300374e..a99510e 100644 --- a/cli/client/create-escrow.ts +++ b/cli/client/create-escrow.ts @@ -34,6 +34,9 @@ Options: --private-key Your private key (required) --deployment Path to deployment file (default: ./cli/deployments/localhost.json) --rpc-url RPC URL (default: from deployment file) + --arbitration-provider Arbitration provider (default: OpenAI) + --arbitration-model Arbitration model (default: gpt-4o-mini) + --arbitration-prompt Custom arbitration prompt (optional) --help, -h Display this help message Environment Variables (alternative to CLI options): @@ -67,6 +70,9 @@ function parseCliArgs() { "private-key": { type: "string" }, "deployment": { type: "string" }, "rpc-url": { type: "string" }, + "arbitration-provider": { type: "string" }, + "arbitration-model": { type: "string" }, + "arbitration-prompt": { type: "string" }, "help": { type: "boolean", short: "h" }, }, strict: true, @@ -92,6 +98,16 @@ async function main() { const oracleAddress = args.oracle; const privateKey = args["private-key"] || process.env.PRIVATE_KEY; const deploymentPath = args.deployment || "./cli/deployments/localhost.json"; + + // Arbitration configuration with defaults + const arbitrationProvider = args["arbitration-provider"] || "OpenAI"; + const arbitrationModel = args["arbitration-model"] || "gpt-4o-mini"; + const arbitrationPrompt = args["arbitration-prompt"] || + `Evaluate the fulfillment against the demand and decide whether the demand was validly fulfilled + +Demand: {{demand}} + +Fulfillment: {{obligation}}`; // Validate required parameters if (!demand) { @@ -194,13 +210,9 @@ async function main() { const encodedDemand = client.arbiters.general.trustedOracle.encode({ oracle: oracleAddress as `0x${string}`, data: llmClient.llm.encodeDemand({ - arbitrationProvider: "OpenAI", - arbitrationModel: "gpt-4.1", - arbitrationPrompt: `Evaluate the fulfillment against the demand and decide whether the demand was validly fulfilled - -Demand: {{demand}} - -Fulfillment: {{obligation}}`, + arbitrationProvider, + arbitrationModel, + arbitrationPrompt, demand: demand }) }); diff --git a/cli/deployments/localhost.json b/cli/deployments/localhost.json index be515f7..b71648e 100644 --- a/cli/deployments/localhost.json +++ b/cli/deployments/localhost.json @@ -2,7 +2,7 @@ "network": "localhost", "chainId": 31337, "rpcUrl": "http://localhost:8545", - "deployedAt": "2026-01-02T05:54:03.735Z", + "deployedAt": "2026-01-02T06:06:32.864Z", "deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "addresses": { "easSchemaRegistry": "0x5fbdb2315678afecb367f032d93f642f64180aa3", diff --git a/cli/index.ts b/cli/index.ts index ad6d548..3fb3c4d 100755 --- a/cli/index.ts +++ b/cli/index.ts @@ -36,6 +36,9 @@ Options (vary by command): --private-key Private key (all commands) --rpc-url RPC URL (default: http://localhost:8545) --deployment Load addresses from deployment file + --arbitration-provider Arbitration provider (create, default: OpenAI) + --arbitration-model Arbitration model (create, default: gpt-4o-mini) + --arbitration-prompt Custom arbitration prompt (create, optional) Environment Variables: PRIVATE_KEY Private key for transactions @@ -107,6 +110,9 @@ function parseCliArgs() { "private-key": { type: "string" }, "rpc-url": { type: "string" }, "deployment": { type: "string" }, + "arbitration-provider": { type: "string" }, + "arbitration-model": { type: "string" }, + "arbitration-prompt": { type: "string" }, }, strict: true, }); diff --git a/cli/server/deploy.ts b/cli/server/deploy.ts index 40f3db7..9e99c5d 100644 --- a/cli/server/deploy.ts +++ b/cli/server/deploy.ts @@ -371,7 +371,9 @@ async function main() { console.log(` --demand "The sky is blue" \\`); console.log(` --amount 10 \\`); console.log(` --token ${addresses.mockERC20A} \\`); - console.log(` --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8`); + console.log(` --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \\`); + console.log(` --arbitration-provider "OpenAI" \\`); + console.log(` --arbitration-model "gpt-4o-mini"`); } catch (error) { console.error("āŒ Deployment failed:", error); From 193ebcc69aec64f3e76017651f719bb41a2b5b6e Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 14:14:20 +0700 Subject: [PATCH 4/6] Remove localhost.json from version control --- cli/deployments/localhost.json | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 cli/deployments/localhost.json diff --git a/cli/deployments/localhost.json b/cli/deployments/localhost.json deleted file mode 100644 index b71648e..0000000 --- a/cli/deployments/localhost.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "network": "localhost", - "chainId": 31337, - "rpcUrl": "http://localhost:8545", - "deployedAt": "2026-01-02T06:06:32.864Z", - "deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "addresses": { - "easSchemaRegistry": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "eas": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "trustedOracleArbiter": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0", - "stringObligation": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", - "erc20EscrowObligation": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9", - "erc20PaymentObligation": "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707", - "erc20BarterUtils": "0x0165878a594ca255338adfa4d48449f69242eb8f", - "mockERC20A": "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853", - "mockERC20B": "0x2279b7a0a67db372996a5fab50d91eaa73d2ebe6", - "mockERC20C": "0x8a791620dd6260079bf849dc5567adc3f2fdc318" - } -} \ No newline at end of file From f42408c825b71ff18374d78d6b1a75662b630cd7 Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 16:30:17 +0700 Subject: [PATCH 5/6] Update to new version ts sdk, update readme --- .gitignore | 3 + .vscode/settings.json | 5 ++ README.md | 156 ++++++++++++++++++++++++++--------- bun.lock | 4 +- cli/client/collect-escrow.ts | 2 +- cli/client/create-escrow.ts | 4 +- cli/client/fulfill-escrow.ts | 8 +- cli/index.ts | 8 +- cli/server/deploy.ts | 25 +++--- cli/server/oracle.ts | 2 +- package.json | 2 +- tests/nlaOracle.test.ts | 11 +-- 12 files changed, 162 insertions(+), 68 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 8d6c402..ac666dd 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ anvil.log .DS_Store .env + +# Local deployment files +cli/deployments/localhost.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..58f6305 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Tierable" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 536f55f..dadc439 100644 --- a/README.md +++ b/README.md @@ -2,46 +2,29 @@ ## Prerequisites -This project depends on the `alkahest` repository, which must be cloned in the same parent directory. +- [Bun](https://bun.sh) - Fast all-in-one JavaScript runtime +- [Foundry](https://book.getfoundry.sh/getting-started/installation) - Ethereum development toolkit (for Anvil) ### Setup Instructions -1. **Clone both repositories in the same parent directory:** +1. **Clone the repository:** ```bash # Navigate to your projects directory cd ~/Desktop # or your preferred location -# Clone the alkahest repository -git clone https://github.com/arkhai-io/alkahest.git - # Clone this repository git clone https://github.com/arkhai-io/natural-language-agreements.git - -# Your directory structure should look like: -# parent-directory/ -# ā”œā”€ā”€ alkahest/ -# │ └── sdks/ -# │ └── ts/ -# └── natural-language-agreements/ -``` - -2. **Install alkahest dependencies:** - -```bash -cd alkahest -bun install -cd .. -``` - -3. **Install this project's dependencies:** - -```bash cd natural-language-agreements +``` + +2. **Install dependencies:** + +```bash bun install ``` -4. **Install the `nla` CLI globally (optional but recommended):** +3. **Install the `nla` CLI globally (optional but recommended):** ```bash # Link the CLI to make it available globally @@ -110,7 +93,26 @@ Watch the oracle terminal - you'll see it process arbitration requests in real-t ## CLI Tools -The `nla` CLI provides a unified interface for all Natural Language Agreement operations. +The `nla` CLI provides a unified interface for all Natural Language Agreement operations with support for multiple LLM providers. + +### Supported LLM Providers + +The oracle supports multiple AI providers for arbitration: + +1. **OpenAI** (default) + - Models: `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`, `gpt-3.5-turbo` + - API Key: Get from [OpenAI Platform](https://platform.openai.com/api-keys) + - Environment Variable: `OPENAI_API_KEY` + +2. **Anthropic (Claude)** + - Models: `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229` + - API Key: Get from [Anthropic Console](https://console.anthropic.com/) + - Environment Variable: `ANTHROPIC_API_KEY` + +3. **OpenRouter** + - Models: Any model available on OpenRouter (e.g., `openai/gpt-4`, `anthropic/claude-3-opus`) + - API Key: Get from [OpenRouter](https://openrouter.ai/keys) + - Environment Variable: `OPENROUTER_API_KEY` ### Installation @@ -139,13 +141,22 @@ For a complete guide to all CLI commands and options, see [CLI Documentation](cl ### Quick CLI Examples ```bash -# Create an escrow +# Create an escrow with OpenAI (default) nla escrow:create \ --demand "The sky is blue" \ --amount 10 \ --token 0xa513e6e4b8f2a923d98304ec87f64353c4d5c853 \ --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +# Create an escrow with custom arbitration settings +nla escrow:create \ + --demand "Deliver package by Friday" \ + --amount 100 \ + --token 0xa513e6e4b8f2a923d98304ec87f64353c4d5c853 \ + --oracle 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \ + --arbitration-provider "Anthropic" \ + --arbitration-model "claude-3-5-sonnet-20241022" + # Fulfill an escrow nla escrow:fulfill \ --escrow-uid 0x... \ @@ -212,6 +223,26 @@ nla escrow:status [options] # Check escrow status nla help # Show help ``` +### Escrow Creation Options + +When creating an escrow, you can customize the arbitration settings: + +```bash +nla escrow:create \ + --demand "Your natural language demand" \ + --amount \ + --token \ + --oracle \ + --arbitration-provider "OpenAI|Anthropic|OpenRouter" \ # Optional, default: OpenAI + --arbitration-model "model-name" \ # Optional, default: gpt-4o-mini + --arbitration-prompt "Custom prompt template" # Optional +``` + +**Default Arbitration Settings:** +- Provider: `OpenAI` +- Model: `gpt-4o-mini` +- Prompt: Standard evaluation template + **NPM Scripts (alternative):** ```bash bun run setup # Same as: nla dev @@ -273,12 +304,12 @@ natural-language-agreements/ │ ā”œā”€ā”€ index.ts # Main CLI entry point (nla command) │ ā”œā”€ā”€ README.md # CLI documentation │ ā”œā”€ā”€ client/ # User-facing escrow tools -│ │ ā”œā”€ā”€ create-escrow.ts # Create escrow CLI +│ │ ā”œā”€ā”€ create-escrow.ts # Create escrow CLI with arbitration config │ │ ā”œā”€ā”€ fulfill-escrow.ts # Fulfill escrow CLI │ │ └── collect-escrow.ts # Collect escrow CLI │ ā”œā”€ā”€ server/ # Server-side components │ │ ā”œā”€ā”€ deploy.ts # Contract deployment script -│ │ └── oracle.ts # Oracle service +│ │ └── oracle.ts # Multi-provider oracle service │ ā”œā”€ā”€ scripts/ # Shell scripts for orchestration │ │ ā”œā”€ā”€ dev.sh # Development environment setup │ │ ā”œā”€ā”€ deploy.sh # Deployment wrapper @@ -288,12 +319,14 @@ natural-language-agreements/ │ ā”œā”€ā”€ localhost.json │ ā”œā”€ā”€ sepolia.json │ └── mainnet.json -ā”œā”€ā”€ clients/ -│ └── nla.ts # Natural Language Agreement client library +ā”œā”€ā”€ nla.ts # Natural Language Agreement client library +│ # - Multi-provider LLM support +│ # - Arbitration encoding/decoding +│ # - OpenAI, Anthropic, OpenRouter integration ā”œā”€ā”€ tests/ │ ā”œā”€ā”€ nla.test.ts # Basic tests │ └── nlaOracle.test.ts # Oracle arbitration tests -ā”œā”€ā”€ index.ts # Development entry point +ā”œā”€ā”€ index.ts # Main exports ā”œā”€ā”€ package.json └── README.md ``` @@ -301,8 +334,9 @@ natural-language-agreements/ ## Troubleshooting ### "Cannot find module 'alkahest-ts'" -- Ensure alkahest is cloned in the parent directory -- Run `bun install` in both alkahest and this project +- Run `bun install` to ensure all dependencies are installed +- Clear the cache: `rm -rf node_modules && bun install` +- Check that package.json includes alkahest-ts dependency ### "Deployer has no ETH" - Fund your deployer account before running deployment @@ -317,7 +351,22 @@ natural-language-agreements/ ### "OpenAI API errors" - Verify API key is valid and active - Check OpenAI usage limits and billing -- Ensure model name is correct (e.g., "gpt-4o") +- Ensure model name is correct (e.g., "gpt-4o-mini", "gpt-4o") + +### "Anthropic API errors" +- Verify ANTHROPIC_API_KEY is set correctly +- Check Anthropic usage limits and billing +- Ensure model name is correct (e.g., "claude-3-5-sonnet-20241022") + +### "Arbitration provider not found" +- The oracle was configured with a different provider than the escrow +- Make sure the oracle has the correct API keys for the provider specified in the escrow +- Supported providers: OpenAI, Anthropic, OpenRouter + +### "Module resolution errors" +- Run `bun install` to ensure alkahest-ts is properly installed +- Check that you're using the correct version of Bun: `bun --version` +- Clear Bun's cache: `rm -rf node_modules && bun install` ## Security Notes @@ -327,5 +376,38 @@ natural-language-agreements/ - Use environment variables or secure secret management for production - The `.env` file is gitignored by default - The example private key in `.env.example` is from Anvil and should NEVER be used in production -- Ensure your OpenAI API key is kept secure and not exposed in logs or error messages +- Keep all API keys secure (OpenAI, Anthropic, OpenRouter): + * Don't expose them in logs or error messages + * Use environment variables or secure secret management + * Rotate keys regularly + * Monitor usage for unauthorized access - Run the oracle in a secure environment with proper access controls +- For production deployments: + * Use hardware wallets or secure key management services + * Implement rate limiting on the oracle + * Monitor arbitration decisions for anomalies + * Consider using a multi-signature setup for critical operations + +## Features + +✨ **Multi-Provider LLM Support** +- OpenAI (GPT-4, GPT-4o, GPT-3.5-turbo) +- Anthropic (Claude 3 family) +- OpenRouter (Access to any model) +- Configurable per-escrow arbitration settings + +šŸ”§ **Flexible Configuration** +- Custom arbitration prompts +- Provider and model selection +- Default settings with override capability + +šŸš€ **Easy Deployment** +- One-command development setup (`nla dev`) +- Automated contract deployment +- Built-in test token distribution + +⚔ **Developer Friendly** +- TypeScript support +- Comprehensive CLI tools +- Unified interface for all operations +- Detailed error messages and logging diff --git a/bun.lock b/bun.lock index 441adc3..5152949 100644 --- a/bun.lock +++ b/bun.lock @@ -9,8 +9,8 @@ "@ai-sdk/openai": "^3.0.2", "@viem/anvil": "^0.0.10", "ai": "^6.0.5", - "alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package", "arktype": "^2.1.23", + "alkahest-ts": "github:arkhai-io/alkahest", "viem": "^2.42.1", "zod": "^3.25.76", }, @@ -67,7 +67,7 @@ "ai": ["ai@6.0.5", "", { "dependencies": { "@ai-sdk/gateway": "3.0.4", "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CKL3dDHedWskC6EY67LrULonZBU9vL+Bwa+xQEcprBhJfxpogntG3utjiAkYuy5ZQatyWk+SmWG8HLvcnhvbRg=="], - "alkahest-ts": ["alkahest-ts@github:VAR-META-Tech/alkahest#d40de37", {}, "VAR-META-Tech-alkahest-d40de37"], + "alkahest-ts": ["alkahest-ts@github:arkhai-io/alkahest#5df4180", {}, "arkhai-io-alkahest-5df4180"], "arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="], diff --git a/cli/client/collect-escrow.ts b/cli/client/collect-escrow.ts index f144dd0..3746fa3 100644 --- a/cli/client/collect-escrow.ts +++ b/cli/client/collect-escrow.ts @@ -145,7 +145,7 @@ async function main() { console.log("šŸ’° Collecting escrow...\n"); // Collect the escrow - const collectionHash = await client.erc20.collectEscrow( + const collectionHash = await client.erc20.escrow.nonTierable.collect( escrowUid as `0x${string}`, fulfillmentUid as `0x${string}`, ); diff --git a/cli/client/create-escrow.ts b/cli/client/create-escrow.ts index a99510e..0ac0731 100644 --- a/cli/client/create-escrow.ts +++ b/cli/client/create-escrow.ts @@ -207,7 +207,7 @@ Fulfillment: {{obligation}}`; // Encode the demand with oracle arbiter const arbiter = deployment.addresses.trustedOracleArbiter; - const encodedDemand = client.arbiters.general.trustedOracle.encode({ + const encodedDemand = client.arbiters.general.trustedOracle.encodeDemand({ oracle: oracleAddress as `0x${string}`, data: llmClient.llm.encodeDemand({ arbitrationProvider, @@ -218,7 +218,7 @@ Fulfillment: {{obligation}}`; }); // Create the escrow - const { attested: escrow } = await client.erc20.permitAndBuyWithErc20( + const { attested: escrow } = await client.erc20.escrow.nonTierable.permitAndCreate( { address: tokenAddress as `0x${string}`, value: BigInt(amount), diff --git a/cli/client/fulfill-escrow.ts b/cli/client/fulfill-escrow.ts index d6cf1d2..9a72570 100644 --- a/cli/client/fulfill-escrow.ts +++ b/cli/client/fulfill-escrow.ts @@ -13,6 +13,7 @@ import { foundry } from "viem/chains"; import { existsSync, readFileSync } from "fs"; import { resolve } from "path"; import { makeClient } from "alkahest-ts"; +import { makeLLMClient } from "../.."; // Helper function to display usage function displayHelp() { @@ -161,18 +162,19 @@ async function main() { fulfillment, escrowUid as `0x${string}`, ); - console.log("āœ… Fulfillment created!\n"); console.log("šŸ“‹ Fulfillment Details:"); console.log(` UID: ${fulfillmentAttestation.uid}`); console.log(` Attester: ${fulfillmentAttestation.attester}\n`); console.log("šŸ“¤ Requesting arbitration from oracle...\n"); - + const escrow = await client.getAttestation(escrowUid as `0x${string}`); + const decodedEscrow = client.erc20.escrow.nonTierable.decodeObligation(escrow.data); // Request arbitration - await client.oracle.requestArbitration( + await client.arbiters.general.trustedOracle.requestArbitration( fulfillmentAttestation.uid, oracleAddress as `0x${string}`, + decodedEscrow.demand ); console.log("✨ Arbitration requested successfully!\n"); diff --git a/cli/index.ts b/cli/index.ts index 3fb3c4d..e50beff 100755 --- a/cli/index.ts +++ b/cli/index.ts @@ -267,7 +267,7 @@ async function runStatusCommand(args: any) { const escrow = await publicClient.readContract({ address: addresses.eas, - abi: contracts.IEAS.abi, + abi: contracts.IEAS.abi.abi, functionName: "getAttestation", args: [escrowUid], }) as any; @@ -298,7 +298,7 @@ async function runStatusCommand(args: any) { const filter = await publicClient.createContractEventFilter({ address: addresses.eas, - abi: contracts.IEAS.abi, + abi: contracts.IEAS.abi.abi, eventName: "Attested", fromBlock: 0n, }); @@ -319,7 +319,7 @@ async function runStatusCommand(args: any) { const fulfillmentUid = (fulfillment as any).args?.uid; const fulfillmentAttestation = await publicClient.readContract({ address: addresses.eas, - abi: contracts.IEAS.abi, + abi: contracts.IEAS.abi.abi, functionName: "getAttestation", args: [fulfillmentUid], }) as any; @@ -336,7 +336,7 @@ async function runStatusCommand(args: any) { const decisionUid = (decision as any).args?.uid; const decisionAttestation = await publicClient.readContract({ address: addresses.eas, - abi: contracts.IEAS.abi, + abi: contracts.IEAS.abi.abi, functionName: "getAttestation", args: [decisionUid], }) as any; diff --git a/cli/server/deploy.ts b/cli/server/deploy.ts index 9e99c5d..092adb5 100644 --- a/cli/server/deploy.ts +++ b/cli/server/deploy.ts @@ -159,7 +159,7 @@ async function main() { const StringObligation = contracts.StringObligation; const ERC20EscrowObligation = contracts.ERC20EscrowObligation; const ERC20PaymentObligation = contracts.ERC20PaymentObligation; - const ERC20BarterCrossToken = contracts.ERC20BarterCrossToken; + const ERC20BarterUtils = contracts.ERC20BarterUtils; console.log("āœ… Contract artifacts loaded\n"); @@ -213,8 +213,8 @@ async function main() { addresses.trustedOracleArbiter = await deployContract( "Trusted Oracle Arbiter", - TrustedOracleArbiter.abi, - TrustedOracleArbiter.bytecode.object, + TrustedOracleArbiter.abi.abi, + TrustedOracleArbiter.abi.bytecode.object, [addresses.eas] ); @@ -223,32 +223,31 @@ async function main() { addresses.stringObligation = await deployContract( "String Obligation", - StringObligation.abi, - StringObligation.bytecode.object, + StringObligation.abi.abi, + StringObligation.abi.bytecode.object, [addresses.eas, addresses.easSchemaRegistry] ); addresses.erc20EscrowObligation = await deployContract( "ERC20 Escrow Obligation", - ERC20EscrowObligation.abi, - ERC20EscrowObligation.bytecode.object, + ERC20EscrowObligation.abi.abi, + ERC20EscrowObligation.abi.bytecode.object, [addresses.eas, addresses.easSchemaRegistry] ); addresses.erc20PaymentObligation = await deployContract( "ERC20 Payment Obligation", - ERC20PaymentObligation.abi, - ERC20PaymentObligation.bytecode.object, + ERC20PaymentObligation.abi.abi, + ERC20PaymentObligation.abi.bytecode.object, [addresses.eas, addresses.easSchemaRegistry] ); - // Deploy barter utils (required for permitAndBuyWithErc20) console.log("šŸ”„ Deploying barter utils...\n"); addresses.erc20BarterUtils = await deployContract( "ERC20 Barter Utils", - ERC20BarterCrossToken.abi, - ERC20BarterCrossToken.bytecode.object, + ERC20BarterUtils.abi.abi, + ERC20BarterUtils.abi.bytecode.object, [ addresses.eas, addresses.erc20EscrowObligation, @@ -259,6 +258,8 @@ async function main() { "0x0000000000000000000000000000000000000000", // erc1155Payment (not used) "0x0000000000000000000000000000000000000000", // tokenBundleEscrow (not used) "0x0000000000000000000000000000000000000000", // tokenBundlePayment (not used) + "0x0000000000000000000000000000000000000000", // nativeEscrow (not used) + "0x0000000000000000000000000000000000000000", // nativePayment (not used) ] ); diff --git a/cli/server/oracle.ts b/cli/server/oracle.ts index 585b59a..3ab3d59 100644 --- a/cli/server/oracle.ts +++ b/cli/server/oracle.ts @@ -170,7 +170,7 @@ async function main() { const obligationAbi = parseAbiParameters("(string item)"); // Start listening and arbitrating - const { unwatch } = await client.oracle.listenAndArbitrate( + const { unwatch } = await client.arbiters.general.trustedOracle.listenAndArbitrate( async (attestation: any) => { console.log(`\nšŸ“Ø New arbitration request received!`); console.log(` Attestation UID: ${attestation.uid}`); diff --git a/package.json b/package.json index 9fe223a..fd33227 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@ai-sdk/openai": "^3.0.2", "@viem/anvil": "^0.0.10", "ai": "^6.0.5", - "alkahest-ts": "git+https://github.com/VAR-META-Tech/alkahest.git#ts-package", + "alkahest-ts": "github:arkhai-io/alkahest", "arktype": "^2.1.23", "viem": "^2.42.1", "zod": "^3.25.76" diff --git a/tests/nlaOracle.test.ts b/tests/nlaOracle.test.ts index f971c93..7aaf051 100644 --- a/tests/nlaOracle.test.ts +++ b/tests/nlaOracle.test.ts @@ -40,7 +40,7 @@ afterAll(async () => { test("listenAndArbitrate Natural Language", async () => { const arbiter = testContext.addresses.trustedOracleArbiter; - const demand = testContext.alice.client.arbiters.general.trustedOracle.encode({ + const demand = testContext.alice.client.arbiters.general.trustedOracle.encodeDemand({ oracle: testContext.bob.address, data: charlieClient.llm.encodeDemand({ arbitrationProvider: "OpenAI", @@ -55,7 +55,7 @@ Fulfillment: {{obligation}}`, }); const { attested: escrow } = - await testContext.alice.client.erc20.permitAndBuyWithErc20( + await testContext.alice.client.erc20.escrow.nonTierable.permitAndCreate( { address: testContext.mockAddresses.erc20A, value: 10n, @@ -66,7 +66,7 @@ Fulfillment: {{obligation}}`, const obligationAbi = parseAbiParameters("(string item)"); const { decisions, unwatch } = - await testContext.bob.client.oracle.listenAndArbitrate( + await testContext.bob.client.arbiters.general.trustedOracle.listenAndArbitrate( async (attestation) => { console.log("arbitrating"); const obligation = testContext.bob.client.extractObligationData( @@ -102,15 +102,16 @@ Fulfillment: {{obligation}}`, escrow.uid, ); - await testContext.bob.client.oracle.requestArbitration( + await testContext.bob.client.arbiters.general.trustedOracle.requestArbitration( fulfillment.uid, testContext.bob.address, + demand ); //Should call WaitForArbitration() await Bun.sleep(5000); - const collectionHash = await testContext.bob.client.erc20.collectEscrow( + const collectionHash = await testContext.bob.client.erc20.escrow.nonTierable.collect( escrow.uid, fulfillment.uid, ); From 29f8e5c617fe3482ed6c436583232d6fab63fed0 Mon Sep 17 00:00:00 2001 From: ngoc Date: Fri, 2 Jan 2026 16:45:02 +0700 Subject: [PATCH 6/6] Init oracle with others providers --- .env.example | 14 ++++++++++- cli/server/oracle.ts | 55 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 2dd1a25..aae978b 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,19 @@ ORACLE_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b7 # ================================ # AI CONFIGURATION # ================================ +# At least ONE of the following API keys is required for the oracle to function -# OpenAI API Key (required for oracle) +# OpenAI API Key (optional) # Get your API key from https://platform.openai.com/api-keys +# Supports models: gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo OPENAI_API_KEY=sk-proj... + +# Anthropic API Key (optional) +# Get your API key from https://console.anthropic.com/ +# Supports models: claude-3-5-sonnet-20241022, claude-3-opus-20240229, claude-3-sonnet-20240229 +# ANTHROPIC_API_KEY=sk-ant-... + +# OpenRouter API Key (optional) +# Get your API key from https://openrouter.ai/keys +# Supports any model available on OpenRouter (e.g., openai/gpt-4, anthropic/claude-3-opus) +# OPENROUTER_API_KEY=sk-or-... diff --git a/cli/server/oracle.ts b/cli/server/oracle.ts index 3ab3d59..3c1c251 100644 --- a/cli/server/oracle.ts +++ b/cli/server/oracle.ts @@ -20,7 +20,9 @@ Usage: Options: --rpc-url RPC URL for the blockchain network (required) --private-key Private key of the oracle operator (required) - --openai-api-key OpenAI API key (required) + --openai-api-key OpenAI API key (optional) + --anthropic-api-key Anthropic API key (optional) + --openrouter-api-key OpenRouter API key (optional) --eas-contract
EAS contract address (optional) --deployment Load addresses from deployment file (optional) --polling-interval Polling interval in milliseconds (default: 5000) @@ -30,6 +32,8 @@ Environment Variables (alternative to CLI options): RPC_URL RPC URL for the blockchain network ORACLE_PRIVATE_KEY Private key of the oracle operator OPENAI_API_KEY OpenAI API key + ANTHROPIC_API_KEY Anthropic API key + OPENROUTER_API_KEY OpenRouter API key EAS_CONTRACT_ADDRESS EAS contract address Examples: @@ -55,6 +59,8 @@ function parseCliArgs() { "rpc-url": { type: "string" }, "private-key": { type: "string" }, "openai-api-key": { type: "string" }, + "anthropic-api-key": { type: "string" }, + "openrouter-api-key": { type: "string" }, "eas-contract": { type: "string" }, "deployment": { type: "string" }, "polling-interval": { type: "string" }, @@ -109,6 +115,8 @@ async function main() { const privateKey = args["private-key"] || process.env.ORACLE_PRIVATE_KEY; const openaiApiKey = args["openai-api-key"] || process.env.OPENAI_API_KEY; + const anthropicApiKey = args["anthropic-api-key"] || process.env.ANTHROPIC_API_KEY; + const openrouterApiKey = args["openrouter-api-key"] || process.env.OPENROUTER_API_KEY; const pollingInterval = parseInt(args["polling-interval"] || "5000"); // Validate required parameters @@ -124,8 +132,10 @@ async function main() { process.exit(1); } - if (!openaiApiKey) { - console.error("āŒ Error: OpenAI API key is required. Use --openai-api-key or set OPENAI_API_KEY environment variable."); + // Check if at least one API key is provided + if (!openaiApiKey && !anthropicApiKey && !openrouterApiKey) { + console.error("āŒ Error: At least one LLM provider API key is required."); + console.error(" Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, or OPENROUTER_API_KEY"); console.error("Run with --help for usage information."); process.exit(1); } @@ -134,7 +144,14 @@ async function main() { console.log("Configuration:"); console.log(` šŸ“” RPC URL: ${rpcUrl}`); console.log(` šŸ”‘ Oracle Key: ${privateKey.slice(0, 6)}...${privateKey.slice(-4)}`); - console.log(` šŸ¤– AI Provider: OpenAI`); + + // Show available providers + const availableProviders = []; + if (openaiApiKey) availableProviders.push("OpenAI"); + if (anthropicApiKey) availableProviders.push("Anthropic"); + if (openrouterApiKey) availableProviders.push("OpenRouter"); + console.log(` šŸ¤– AI Providers: ${availableProviders.join(", ")}`); + if (easContract) { console.log(` šŸ“ EAS Contract: ${easContract}`); } @@ -158,12 +175,32 @@ async function main() { llm: makeLLMClient([]), })); - llmClient.llm.addProvider({ - providerName: "OpenAI", - apiKey: openaiApiKey, - }); + // Add all available providers + if (openaiApiKey) { + llmClient.llm.addProvider({ + providerName: "OpenAI", + apiKey: openaiApiKey, + }); + console.log("āœ… OpenAI provider configured"); + } - console.log("šŸŽÆ LLM Arbitrator configured and ready\n"); + if (anthropicApiKey) { + llmClient.llm.addProvider({ + providerName: "Anthropic", + apiKey: anthropicApiKey, + }); + console.log("āœ… Anthropic provider configured"); + } + + if (openrouterApiKey) { + llmClient.llm.addProvider({ + providerName: "OpenRouter", + apiKey: openrouterApiKey, + }); + console.log("āœ… OpenRouter provider configured"); + } + + console.log("\nšŸŽÆ LLM Arbitrator configured and ready\n"); console.log("šŸ‘‚ Listening for arbitration requests...\n"); // Define the obligation ABI