#!/usr/bin/env node import { parseArgs } from "util"; import { parseAbiParameters, createWalletClient, http, publicActions } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { foundry } from "viem/chains"; import { makeLLMClient } from "../.."; import { existsSync, readFileSync } from "fs"; import { resolve } from "path"; import { makeClient } from "alkahest-ts"; import { fixtures } from "alkahest-ts"; import { ProviderName } from "../../nla"; // Helper function to display usage function displayHelp() { console.log(` Natural Language Agreement Oracle CLI Usage: bun oracle.ts [options] 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 (optional) --anthropic-api-key Anthropic API key (optional) --openrouter-api-key OpenRouter API key (optional) --perplexity-api-key Perplexity API key for search tools (optional) --eas-contract
EAS contract address (optional) --deployment Load addresses from deployment file (optional) --polling-interval Polling interval in milliseconds (default: 5000) --help, -h Display this help message 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 PERPLEXITY_API_KEY Perplexity API key for search tools EAS_CONTRACT_ADDRESS EAS contract address Examples: # Using command line options bun oracle.ts --rpc-url http://localhost:8545 --private-key 0x... --openai-api-key sk-... # Using deployment file bun oracle.ts --deployment ./deployments/localhost.json --private-key 0x... --openai-api-key sk-... # Using environment variables export OPENAI_API_KEY=sk-... export RPC_URL=http://localhost:8545 export ORACLE_PRIVATE_KEY=0x... bun oracle.ts `); } // Parse command line arguments function parseCliArgs() { const { values } = parseArgs({ args: process.argv.slice(2), options: { "rpc-url": { type: "string" }, "private-key": { type: "string" }, "openai-api-key": { type: "string" }, "anthropic-api-key": { type: "string" }, "openrouter-api-key": { type: "string" }, "perplexity-api-key": { type: "string" }, "eas-contract": { type: "string" }, "deployment": { type: "string" }, "polling-interval": { type: "string" }, "help": { type: "boolean", short: "h" }, }, strict: true, }); return values; } // Load deployment file function loadDeployment(filePath: string) { if (!existsSync(filePath)) { throw new Error(`Deployment file not found: ${filePath}`); } const content = readFileSync(filePath, "utf-8"); return JSON.parse(content); } // Main function async function main() { try { const args = parseCliArgs(); // Display help if requested if (args.help) { displayHelp(); process.exit(0); } let rpcUrl = args["rpc-url"] || process.env.RPC_URL; let easContract = args["eas-contract"] || process.env.EAS_CONTRACT_ADDRESS; let deploymentAddresses = null; // Load deployment file if provided if (args.deployment) { console.log(`šŸ“„ Loading deployment from: ${args.deployment}\n`); const deployment = loadDeployment(args.deployment); deploymentAddresses = deployment.addresses; if (!rpcUrl) { rpcUrl = deployment.rpcUrl; } if (!easContract) { easContract = deployment.addresses.eas; } console.log(`āœ… Loaded deployment (${deployment.network})\n`); } 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 perplexityApiKey = args["perplexity-api-key"] || process.env.PERPLEXITY_API_KEY; const pollingInterval = parseInt(args["polling-interval"] || "5000"); // Validate required parameters if (!rpcUrl) { console.error("āŒ Error: RPC URL is required. Use --rpc-url or set RPC_URL environment variable."); console.error("Run with --help for usage information."); process.exit(1); } if (!privateKey) { console.error("āŒ Error: Private key is required. Use --private-key or set ORACLE_PRIVATE_KEY environment variable."); console.error("Run with --help for usage information."); process.exit(1); } // 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); } console.log("šŸš€ Starting Natural Language Agreement Oracle...\n"); console.log("Configuration:"); console.log(` šŸ“” RPC URL: ${rpcUrl}`); console.log(` šŸ”‘ Oracle Key: ${privateKey.slice(0, 6)}...${privateKey.slice(-4)}`); // 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 (perplexityApiKey) { console.log(` šŸ” Perplexity Search: Enabled`); } if (easContract) { console.log(` šŸ“ EAS Contract: ${easContract}`); } console.log(` ā±ļø Polling Interval: ${pollingInterval}ms\n`); // Create wallet client const account = privateKeyToAccount(privateKey as `0x${string}`); const walletClient = createWalletClient({ account, chain: foundry, transport: http(rpcUrl), }).extend(publicActions) as any; // Create alkahest client const client = makeClient(walletClient, deploymentAddresses || { eas: easContract }); console.log(`āœ… Oracle initialized with address: ${account.address}\n`); // Create LLM client const llmClient = client.extend(() => ({ llm: makeLLMClient([]), })); // Add all available providers if (openaiApiKey) { llmClient.llm.addProvider({ providerName: ProviderName.OpenAI, apiKey: openaiApiKey, perplexityApiKey: perplexityApiKey, }); console.log("āœ… OpenAI provider configured"); } if (anthropicApiKey) { llmClient.llm.addProvider({ providerName: ProviderName.Anthropic, apiKey: anthropicApiKey, perplexityApiKey: perplexityApiKey, }); console.log("āœ… Anthropic provider configured"); } if (openrouterApiKey) { llmClient.llm.addProvider({ providerName: ProviderName.OpenRouter, apiKey: openrouterApiKey, perplexityApiKey: perplexityApiKey, }); 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 const obligationAbi = parseAbiParameters("(string item)"); // Start listening and arbitrating const { unwatch } = await client.arbiters.general.trustedOracle.arbitrateMany( async ({ attestation, demand }) => { console.log(`\nšŸ“Ø New arbitration request received!`); console.log(` Attestation UID: ${attestation.uid}`); try { // Extract obligation data const obligation = client.extractObligationData( obligationAbi, attestation, ); const obligationItem = obligation[0].item; console.log(` Obligation: "${obligationItem}"`); const trustedOracleDemandData = client.arbiters.general.trustedOracle.decodeDemand(demand); const nlaDemandData = llmClient.llm.decodeDemand(trustedOracleDemandData.data); console.log(` Demand: "${nlaDemandData.demand}"`); console.log(` Model: ${nlaDemandData.arbitrationModel}`); // Perform arbitration using LLM console.log(` šŸ¤” Arbitrating with AI...`); const result = await llmClient.llm.arbitrate( nlaDemandData, obligationItem ); console.log(` ✨ Arbitration result: ${result ? "āœ… APPROVED" : "āŒ REJECTED"}`); return result; } catch (error) { console.error(` āŒ Error during arbitration:`, error); throw error; } }, { onAfterArbitrate: async (decision: any) => { console.log(` šŸ“ Arbitration decision recorded on-chain`); console.log(` Decision UID: ${decision.attestation.uid}`); console.log(` Result: ${decision.decision ? "āœ… Fulfilled" : "āŒ Not Fulfilled"}\n`); }, pollingInterval, }, ); console.log("✨ Oracle is now running. Press Ctrl+C to stop.\n"); // Handle graceful shutdown const shutdown = async () => { console.log("\n\nšŸ›‘ Shutting down oracle..."); unwatch(); console.log("šŸ‘‹ Oracle stopped gracefully"); process.exit(0); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); // Keep the process alive await new Promise(() => { }); } catch (error) { console.error("āŒ Fatal error:", error); process.exit(1); } } // Run the CLI main();