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",