commit
079cf4dec9
14
bun.lock
14
bun.lock
|
|
@ -11,13 +11,15 @@
|
|||
"@perplexity-ai/ai-sdk": "^0.1.2",
|
||||
"@viem/anvil": "^0.0.10",
|
||||
"ai": "^6.0.5",
|
||||
"alkahest-ts": "github:arkhai-io/alkahest",
|
||||
"alkahest-ts": "^0.6.1",
|
||||
"arktype": "^2.1.23",
|
||||
"viem": "^2.42.1",
|
||||
"zod": "^3.25.76",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
|
|
@ -65,7 +67,7 @@
|
|||
|
||||
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA=="],
|
||||
"@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="],
|
||||
|
||||
"@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="],
|
||||
|
||||
|
|
@ -75,7 +77,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:arkhai-io/alkahest#80a8273", { "dependencies": { "@viem/anvil": "^0.0.10", "arktype": "^2.1.23", "zod": "^3.25.76" }, "peerDependencies": { "typescript": "^5.9.3", "viem": "^2.38.3" } }, "arkhai-io-alkahest-80a8273"],
|
||||
"alkahest-ts": ["alkahest-ts@0.6.1", "", { "dependencies": { "@viem/anvil": "^0.0.10", "arktype": "^2.1.23", "zod": "^3.25.76" }, "peerDependencies": { "typescript": "^5.9.3", "viem": "^2.38.3" } }, "sha512-0u1xUM9OLca6emKDVzn6ISx4VFg7TGZtA/7hnT3SOHOWVPLk5ruX12tbX5MO7hG1jxVHAO3wgIE2t7vl864/nQ=="],
|
||||
|
||||
"arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="],
|
||||
|
||||
|
|
@ -133,7 +135,7 @@
|
|||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"viem": ["viem@2.42.1", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.1.0", "isows": "1.0.7", "ox": "0.9.6", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NzT/f54jT+b0Um6pYzN/uAGMLg+3twhricAzXS+XH8pVIREzPEh7P25rlhPQnLYiPWzQd9mrFcvnm73Sc8bx+A=="],
|
||||
|
||||
|
|
@ -143,8 +145,12 @@
|
|||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@25.0.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"ox/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,46 +6,18 @@
|
|||
*/
|
||||
|
||||
import { parseArgs } from "util";
|
||||
import { createWalletClient, http, publicActions } from "viem";
|
||||
import { createWalletClient, http, publicActions, formatEther } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve, dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { makeClient } from "alkahest-ts";
|
||||
import { getChainFromNetwork } from "../utils.js";
|
||||
import { getChainFromNetwork, loadDeploymentWithDefaults, getPrivateKey } from "../utils.js";
|
||||
|
||||
// Get the directory of the current module
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Helper function to find deployment file
|
||||
function findDeploymentFile(deploymentPath: string): string | null {
|
||||
// Try the provided path first
|
||||
if (existsSync(resolve(deploymentPath))) {
|
||||
return resolve(deploymentPath);
|
||||
}
|
||||
|
||||
// Try relative to current working directory
|
||||
const cwdPath = resolve(process.cwd(), deploymentPath);
|
||||
if (existsSync(cwdPath)) {
|
||||
return cwdPath;
|
||||
}
|
||||
|
||||
// Try relative to the CLI installation directory
|
||||
const cliPath = resolve(__dirname, "..", "deployments", "devnet.json");
|
||||
if (existsSync(cliPath)) {
|
||||
return cliPath;
|
||||
}
|
||||
|
||||
// Try in the project root (for local development)
|
||||
const projectPath = resolve(__dirname, "..", "..", "cli", "deployments", "devnet.json");
|
||||
if (existsSync(projectPath)) {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to display usage
|
||||
function displayHelp() {
|
||||
console.log(`
|
||||
|
|
@ -112,8 +84,8 @@ async function main() {
|
|||
// Get configuration
|
||||
const escrowUid = args["escrow-uid"];
|
||||
const fulfillmentUid = args["fulfillment-uid"];
|
||||
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
|
||||
const deploymentPath = args.deployment || "./cli/deployments/devnet.json";
|
||||
const privateKey = args["private-key"] || getPrivateKey();
|
||||
const deploymentPath = args.deployment;
|
||||
|
||||
// Validate required parameters
|
||||
if (!escrowUid) {
|
||||
|
|
@ -129,24 +101,25 @@ async function main() {
|
|||
}
|
||||
|
||||
if (!privateKey) {
|
||||
console.error("❌ Error: Private key is required. Use --private-key or set PRIVATE_KEY");
|
||||
console.error("Run with --help for usage information.");
|
||||
console.error("❌ Error: Private key is required");
|
||||
console.error("\n💡 You can either:");
|
||||
console.error(" 1. Set it globally: nla wallet:set --private-key <your-key>");
|
||||
console.error(" 2. Use for this command only: --private-key <your-key>");
|
||||
console.error(" 3. Set PRIVATE_KEY environment variable");
|
||||
console.error("\nRun with --help for usage information.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load deployment file
|
||||
const resolvedDeploymentPath = findDeploymentFile(deploymentPath);
|
||||
if (!resolvedDeploymentPath) {
|
||||
console.error(`❌ Error: Deployment file not found: ${deploymentPath}`);
|
||||
// Load deployment file (auto-detects current network if not specified)
|
||||
let deployment;
|
||||
try {
|
||||
deployment = loadDeploymentWithDefaults(deploymentPath);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error: ${(error as Error).message}`);
|
||||
console.error("Please deploy contracts first or specify correct path with --deployment");
|
||||
console.error("\nSearched in:");
|
||||
console.error(` - ${resolve(deploymentPath)}`);
|
||||
console.error(` - ${resolve(process.cwd(), deploymentPath)}`);
|
||||
console.error(` - ${resolve(__dirname, "..", "deployments", "devnet.json")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const deployment = JSON.parse(readFileSync(resolvedDeploymentPath, "utf-8"));
|
||||
const rpcUrl = args["rpc-url"] || deployment.rpcUrl;
|
||||
const chain = getChainFromNetwork(deployment.network);
|
||||
|
||||
|
|
@ -168,7 +141,7 @@ async function main() {
|
|||
|
||||
// Check balance
|
||||
const balance = await walletClient.getBalance({ address: account.address });
|
||||
console.log(`💰 ETH balance: ${parseFloat((balance / 10n ** 18n).toString()).toFixed(4)} ETH\n`);
|
||||
console.log(`💰 ETH balance: ${parseFloat(formatEther(balance)).toFixed(4)} ETH\n`);
|
||||
|
||||
if (balance === 0n) {
|
||||
console.error("❌ Error: Account has no ETH for gas. Please fund the account first.");
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { parseArgs } from "util";
|
||||
import { createWalletClient, http, publicActions, parseEther } from "viem";
|
||||
import { createWalletClient, http, publicActions, parseEther, formatEther } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve, dirname, join } from "path";
|
||||
|
|
@ -15,48 +15,12 @@ import { fileURLToPath } from "url";
|
|||
import { makeClient } from "alkahest-ts";
|
||||
import { makeLLMClient } from "../..";
|
||||
import {fixtures} from "alkahest-ts";
|
||||
import { getCurrentEnvironment } from "../commands/switch.js";
|
||||
import { getChainFromNetwork } from "../utils.js";
|
||||
import { getCurrentEnvironment, getChainFromNetwork, loadDeploymentWithDefaults, getPrivateKey } from "../utils.js";
|
||||
|
||||
// Get the directory of the current module
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Helper function to find deployment file
|
||||
function findDeploymentFile(deploymentPath: string, environment?: string): string | null {
|
||||
// If no path provided, use current environment
|
||||
if (!deploymentPath) {
|
||||
const env = environment || getCurrentEnvironment();
|
||||
deploymentPath = `./cli/deployments/${env}.json`;
|
||||
}
|
||||
|
||||
// Try the provided path first
|
||||
if (existsSync(resolve(deploymentPath))) {
|
||||
return resolve(deploymentPath);
|
||||
}
|
||||
|
||||
// Try relative to current working directory
|
||||
const cwdPath = resolve(process.cwd(), deploymentPath);
|
||||
if (existsSync(cwdPath)) {
|
||||
return cwdPath;
|
||||
}
|
||||
|
||||
// Try relative to the CLI installation directory with current environment
|
||||
const env = environment || getCurrentEnvironment();
|
||||
const cliPath = resolve(__dirname, "..", "deployments", `${env}.json`);
|
||||
if (existsSync(cliPath)) {
|
||||
return cliPath;
|
||||
}
|
||||
|
||||
// Try in the project root (for local development)
|
||||
const projectPath = resolve(__dirname, "..", "..", "cli", "deployments", `${env}.json`);
|
||||
if (existsSync(projectPath)) {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to display usage
|
||||
function displayHelp() {
|
||||
const currentEnv = getCurrentEnvironment();
|
||||
|
|
@ -140,8 +104,8 @@ async function main() {
|
|||
const amount = args.amount;
|
||||
const tokenAddress = args.token;
|
||||
const oracleAddress = args.oracle;
|
||||
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
|
||||
const deploymentPath = args.deployment || `./cli/deployments/${getCurrentEnvironment()}.json`;
|
||||
const privateKey = args["private-key"] || getPrivateKey();
|
||||
const deploymentPath = args.deployment;
|
||||
|
||||
// Arbitration configuration with defaults
|
||||
const arbitrationProvider = args["arbitration-provider"] || "OpenAI";
|
||||
|
|
@ -179,24 +143,25 @@ Fulfillment: {{obligation}}`;
|
|||
}
|
||||
|
||||
if (!privateKey) {
|
||||
console.error("❌ Error: Private key is required. Use --private-key or set PRIVATE_KEY");
|
||||
console.error("Run with --help for usage information.");
|
||||
console.error("❌ Error: Private key is required");
|
||||
console.error("\n💡 You can either:");
|
||||
console.error(" 1. Set it globally: nla wallet:set --private-key <your-key>");
|
||||
console.error(" 2. Use for this command only: --private-key <your-key>");
|
||||
console.error(" 3. Set PRIVATE_KEY environment variable");
|
||||
console.error("\nRun with --help for usage information.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load deployment file
|
||||
const resolvedDeploymentPath = findDeploymentFile(deploymentPath);
|
||||
if (!resolvedDeploymentPath) {
|
||||
console.error(`❌ Error: Deployment file not found: ${deploymentPath}`);
|
||||
// Load deployment file (auto-detects current network if not specified)
|
||||
let deployment;
|
||||
try {
|
||||
deployment = loadDeploymentWithDefaults(deploymentPath);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error: ${(error as Error).message}`);
|
||||
console.error("Please deploy contracts first or specify correct path with --deployment");
|
||||
console.error("\nSearched in:");
|
||||
console.error(` - ${resolve(deploymentPath)}`);
|
||||
console.error(` - ${resolve(process.cwd(), deploymentPath)}`);
|
||||
console.error(` - ${resolve(__dirname, "..", "deployments", "devnet.json")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const deployment = JSON.parse(readFileSync(resolvedDeploymentPath, "utf-8"));
|
||||
const rpcUrl = args["rpc-url"] || deployment.rpcUrl;
|
||||
const chain = getChainFromNetwork(deployment.network);
|
||||
|
||||
|
|
@ -221,7 +186,7 @@ Fulfillment: {{obligation}}`;
|
|||
|
||||
// Check balance
|
||||
const balance = await walletClient.getBalance({ address: account.address });
|
||||
console.log(`💰 ETH balance: ${parseFloat((balance / 10n ** 18n).toString()).toFixed(4)} ETH\n`);
|
||||
console.log(`💰 ETH balance: ${parseFloat(formatEther(balance)).toFixed(4)} ETH\n`);
|
||||
|
||||
if (balance === 0n) {
|
||||
console.error("❌ Error: Account has no ETH for gas. Please fund the account first.");
|
||||
|
|
@ -257,7 +222,7 @@ Fulfillment: {{obligation}}`;
|
|||
console.log("📋 Creating escrow\n");
|
||||
|
||||
// Encode the demand with oracle arbiter
|
||||
const arbiter = deployment.addresses.trustedOracleArbiter;
|
||||
const arbiter = deployment.addresses.trustedOracleArbiter as `0x${string}`;
|
||||
const encodedDemand = client.arbiters.general.trustedOracle.encodeDemand({
|
||||
oracle: oracleAddress as `0x${string}`,
|
||||
data: llmClient.llm.encodeDemand({
|
||||
|
|
@ -266,7 +231,7 @@ Fulfillment: {{obligation}}`;
|
|||
arbitrationPrompt,
|
||||
demand: demand
|
||||
})
|
||||
});
|
||||
}) as `0x${string}`;
|
||||
|
||||
// Create the escrow
|
||||
const { attested: escrow } = await client.erc20.escrow.nonTierable.permitAndCreate(
|
||||
|
|
|
|||
|
|
@ -7,47 +7,19 @@
|
|||
*/
|
||||
|
||||
import { parseArgs } from "util";
|
||||
import { createWalletClient, http, publicActions } from "viem";
|
||||
import { createWalletClient, http, publicActions, formatEther } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve, dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { makeClient } from "alkahest-ts";
|
||||
import { makeLLMClient } from "../..";
|
||||
import { getChainFromNetwork } from "../utils.js";
|
||||
import { getChainFromNetwork, loadDeploymentWithDefaults, getPrivateKey } from "../utils.js";
|
||||
|
||||
// Get the directory of the current module
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Helper function to find deployment file
|
||||
function findDeploymentFile(deploymentPath: string): string | null {
|
||||
// Try the provided path first
|
||||
if (existsSync(resolve(deploymentPath))) {
|
||||
return resolve(deploymentPath);
|
||||
}
|
||||
|
||||
// Try relative to current working directory
|
||||
const cwdPath = resolve(process.cwd(), deploymentPath);
|
||||
if (existsSync(cwdPath)) {
|
||||
return cwdPath;
|
||||
}
|
||||
|
||||
// Try relative to the CLI installation directory
|
||||
const cliPath = resolve(__dirname, "..", "deployments", "devnet.json");
|
||||
if (existsSync(cliPath)) {
|
||||
return cliPath;
|
||||
}
|
||||
|
||||
// Try in the project root (for local development)
|
||||
const projectPath = resolve(__dirname, "..", "..", "cli", "deployments", "devnet.json");
|
||||
if (existsSync(projectPath)) {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to display usage
|
||||
function displayHelp() {
|
||||
console.log(`
|
||||
|
|
@ -118,8 +90,8 @@ async function main() {
|
|||
const escrowUid = args["escrow-uid"];
|
||||
const fulfillment = args.fulfillment;
|
||||
const oracleAddress = args.oracle;
|
||||
const privateKey = args["private-key"] || process.env.PRIVATE_KEY;
|
||||
const deploymentPath = args.deployment || "./cli/deployments/devnet.json";
|
||||
const privateKey = args["private-key"] || getPrivateKey();
|
||||
const deploymentPath = args.deployment ;
|
||||
|
||||
// Validate required parameters
|
||||
if (!escrowUid) {
|
||||
|
|
@ -141,24 +113,25 @@ async function main() {
|
|||
}
|
||||
|
||||
if (!privateKey) {
|
||||
console.error("❌ Error: Private key is required. Use --private-key or set PRIVATE_KEY");
|
||||
console.error("Run with --help for usage information.");
|
||||
console.error("❌ Error: Private key is required");
|
||||
console.error("\n💡 You can either:");
|
||||
console.error(" 1. Set it globally: nla wallet:set --private-key <your-key>");
|
||||
console.error(" 2. Use for this command only: --private-key <your-key>");
|
||||
console.error(" 3. Set PRIVATE_KEY environment variable");
|
||||
console.error("\nRun with --help for usage information.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load deployment file
|
||||
const resolvedDeploymentPath = findDeploymentFile(deploymentPath);
|
||||
if (!resolvedDeploymentPath) {
|
||||
console.error(`❌ Error: Deployment file not found: ${deploymentPath}`);
|
||||
// Load deployment file (auto-detects current network if not specified)
|
||||
let deployment;
|
||||
try {
|
||||
deployment = loadDeploymentWithDefaults(deploymentPath);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error: ${(error as Error).message}`);
|
||||
console.error("Please deploy contracts first or specify correct path with --deployment");
|
||||
console.error("\nSearched in:");
|
||||
console.error(` - ${resolve(deploymentPath)}`);
|
||||
console.error(` - ${resolve(process.cwd(), deploymentPath)}`);
|
||||
console.error(` - ${resolve(__dirname, "..", "deployments", "devnet.json")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const deployment = JSON.parse(readFileSync(resolvedDeploymentPath, "utf-8"));
|
||||
const rpcUrl = args["rpc-url"] || deployment.rpcUrl;
|
||||
const chain = getChainFromNetwork(deployment.network);
|
||||
|
||||
|
|
@ -182,7 +155,7 @@ async function main() {
|
|||
|
||||
// Check balance
|
||||
const balance = await walletClient.getBalance({ address: account.address });
|
||||
console.log(`💰 ETH balance: ${parseFloat((balance / 10n ** 18n).toString()).toFixed(4)} ETH\n`);
|
||||
console.log(`💰 ETH balance: ${parseFloat(formatEther(balance)).toFixed(4)} ETH\n`);
|
||||
|
||||
if (balance === 0n) {
|
||||
console.error("❌ Error: Account has no ETH for gas. Please fund the account first.");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { spawn, spawnSync } from "child_process";
|
||||
import { existsSync, readFileSync, writeFileSync, createWriteStream, unlinkSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { getCurrentEnvironment, setCurrentEnvironment } from "../utils.js";
|
||||
|
||||
// Colors for console output
|
||||
const colors = {
|
||||
|
|
@ -71,6 +72,16 @@ export async function runDevCommand(cliDir: string, envPath?: string) {
|
|||
console.log(`${colors.blue} Natural Language Agreement Oracle - Quick Setup${colors.reset}`);
|
||||
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}\n`);
|
||||
|
||||
// Auto-switch to devnet environment
|
||||
const currentEnv = getCurrentEnvironment();
|
||||
if (currentEnv !== 'devnet') {
|
||||
console.log(`${colors.yellow}🔄 Switching environment from ${currentEnv} to devnet...${colors.reset}`);
|
||||
setCurrentEnvironment('devnet');
|
||||
console.log(`${colors.green}✅ Switched to devnet${colors.reset}\n`);
|
||||
} else {
|
||||
console.log(`${colors.green}✅ Already on devnet environment${colors.reset}\n`);
|
||||
}
|
||||
|
||||
// Load .env file first
|
||||
loadEnvFile(envPath);
|
||||
console.log('');
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|||
import { join, dirname } from "path";
|
||||
import { homedir } from "os";
|
||||
import { fileURLToPath } from "url";
|
||||
import {
|
||||
getNLAConfigDir,
|
||||
getCurrentEnvironment,
|
||||
setCurrentEnvironment,
|
||||
getDeploymentPath
|
||||
} from "../utils.js";
|
||||
|
||||
// Get the directory of the current module
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
|
@ -16,65 +22,6 @@ const colors = {
|
|||
reset: '\x1b[0m'
|
||||
};
|
||||
|
||||
// Get NLA config directory
|
||||
function getNLAConfigDir(): string {
|
||||
const configDir = join(homedir(), '.nla');
|
||||
if (!existsSync(configDir)) {
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
return configDir;
|
||||
}
|
||||
|
||||
// Get current environment
|
||||
export function getCurrentEnvironment(): string {
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
// Default to devnet
|
||||
return 'devnet';
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||
return config.environment || 'devnet';
|
||||
} catch (e) {
|
||||
return 'devnet';
|
||||
}
|
||||
}
|
||||
|
||||
// Set current environment
|
||||
function setCurrentEnvironment(env: string): void {
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
const config = existsSync(configPath)
|
||||
? JSON.parse(readFileSync(configPath, 'utf-8'))
|
||||
: {};
|
||||
|
||||
config.environment = env;
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
// Get deployment path for environment
|
||||
export function getDeploymentPath(cliDir: string, env?: string): string {
|
||||
const environment = env || getCurrentEnvironment();
|
||||
const filename = `${environment}.json`;
|
||||
|
||||
// Try multiple locations
|
||||
const paths = [
|
||||
join(cliDir, 'deployments', filename), // dist/cli/deployments/
|
||||
join(__dirname, '..', 'deployments', filename), // Relative to switch.ts
|
||||
join(process.cwd(), 'cli', 'deployments', filename), // Project root
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
if (existsSync(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first path as default (even if it doesn't exist yet)
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
// Switch command
|
||||
export function runSwitchCommand(env?: string) {
|
||||
console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}`);
|
||||
|
|
@ -86,19 +33,20 @@ export function runSwitchCommand(env?: string) {
|
|||
const current = getCurrentEnvironment();
|
||||
console.log(`${colors.blue}Current environment:${colors.reset} ${colors.green}${current}${colors.reset}\n`);
|
||||
console.log('Available environments:');
|
||||
console.log(' • devnet (local Anvil blockchain)');
|
||||
console.log(' • sepolia (Ethereum Sepolia testnet)');
|
||||
console.log(' • mainnet (Ethereum mainnet)\n');
|
||||
console.log(' • devnet (local Anvil blockchain)');
|
||||
console.log(' • sepolia (Ethereum Sepolia testnet)');
|
||||
console.log(' • base-sepolia (Base Sepolia testnet)');
|
||||
console.log(' • mainnet (Ethereum mainnet)\n');
|
||||
console.log(`${colors.yellow}Usage:${colors.reset} nla switch <environment>`);
|
||||
console.log(`${colors.yellow}Example:${colors.reset} nla switch sepolia\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate environment
|
||||
const validEnvs = ['devnet', 'sepolia', 'mainnet'];
|
||||
const validEnvs = ['devnet', 'sepolia', 'base-sepolia', 'mainnet'];
|
||||
if (!validEnvs.includes(env)) {
|
||||
console.error(`${colors.red}❌ Invalid environment: ${env}${colors.reset}`);
|
||||
console.log('Valid environments: devnet, sepolia, mainnet\n');
|
||||
console.log('Valid environments: devnet, sepolia, base-sepolia, mainnet\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +68,9 @@ export function runSwitchCommand(env?: string) {
|
|||
} else if (env === 'sepolia') {
|
||||
console.log('📝 Using Ethereum Sepolia testnet');
|
||||
console.log(' Make sure you have deployed contracts and updated sepolia.json\n');
|
||||
} else if (env === 'base-sepolia') {
|
||||
console.log('📝 Using Base Sepolia testnet');
|
||||
console.log(' Make sure you have deployed contracts and updated base-sepolia.json\n');
|
||||
} else if (env === 'mainnet') {
|
||||
console.log('📝 Using Ethereum mainnet');
|
||||
console.log(` ${colors.yellow}⚠️ WARNING: This is production! Use with caution.${colors.reset}\n`);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Wallet management commands
|
||||
*/
|
||||
|
||||
import { setPrivateKey, clearPrivateKey, getPrivateKey } from "../utils.js";
|
||||
import { privateKeyToAddress } from "viem/accounts";
|
||||
|
||||
/**
|
||||
* Set wallet private key
|
||||
*/
|
||||
export async function setWallet(privateKey: string): Promise<void> {
|
||||
// Validate private key format
|
||||
if (!privateKey.startsWith('0x')) {
|
||||
console.error('❌ Private key must start with 0x');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (privateKey.length !== 66) {
|
||||
console.error('❌ Invalid private key length. Expected 66 characters (0x + 64 hex chars)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate by deriving address
|
||||
const address = privateKeyToAddress(privateKey as `0x${string}`);
|
||||
|
||||
// Store in config
|
||||
setPrivateKey(privateKey);
|
||||
|
||||
console.log('✅ Wallet configured successfully');
|
||||
console.log(`📍 Address: ${address}`);
|
||||
console.log('\n💡 Your private key is stored in ~/.nla/config.json');
|
||||
console.log(' It will be used automatically for all transactions');
|
||||
} catch (error) {
|
||||
console.error('❌ Invalid private key format');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show current wallet address
|
||||
*/
|
||||
export async function showWallet(): Promise<void> {
|
||||
const privateKey = getPrivateKey();
|
||||
|
||||
if (!privateKey) {
|
||||
console.log('ℹ️ No wallet configured');
|
||||
console.log('\n💡 Set your wallet with:');
|
||||
console.log(' nla wallet:set --private-key <your-key>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const address = privateKeyToAddress(privateKey as `0x${string}`);
|
||||
console.log('✅ Wallet configured');
|
||||
console.log(`📍 Address: ${address}`);
|
||||
} catch (error) {
|
||||
console.error('❌ Invalid private key in config');
|
||||
console.log('\n💡 Update your wallet with:');
|
||||
console.log(' nla wallet:set --private-key <your-key>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear wallet from config
|
||||
*/
|
||||
export async function clearWallet(): Promise<void> {
|
||||
clearPrivateKey();
|
||||
console.log('✅ Wallet cleared from config');
|
||||
console.log('\n💡 Set a new wallet with:');
|
||||
console.log(' nla wallet:set --private-key <your-key>');
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"network": "Base Sepolia",
|
||||
"chainId": 84532,
|
||||
"rpcUrl": "https://base-sepolia.infura.io/v3/e8b0c66293ae484083cf9e1d793d68bd",
|
||||
"addresses": {
|
||||
"eas": "",
|
||||
"easSchemaRegistry": "",
|
||||
"erc20EscrowObligation": "",
|
||||
"erc20PaymentObligation": "",
|
||||
"erc20BarterUtils": "",
|
||||
"erc721EscrowObligation": "",
|
||||
"erc721PaymentObligation": "",
|
||||
"erc721BarterUtils": "",
|
||||
"erc1155EscrowObligation": "",
|
||||
"erc1155BarterUtils": "",
|
||||
"erc1155PaymentObligation": "",
|
||||
"tokenBundleEscrowObligation": "",
|
||||
"tokenBundlePaymentObligation": "",
|
||||
"tokenBundleBarterUtils": "",
|
||||
"attestationEscrowObligation": "",
|
||||
"attestationEscrowObligation2": "",
|
||||
"attestationBarterUtils": "",
|
||||
"stringObligation": "",
|
||||
"trivialArbiter": "",
|
||||
"trustedOracleArbiter": "",
|
||||
"anyArbiter": "",
|
||||
"allArbiter": "",
|
||||
"intrinsicsArbiter": "",
|
||||
"intrinsicsArbiter2": "",
|
||||
"exclusiveRevocableConfirmationArbiter": "",
|
||||
"exclusiveUnrevocableConfirmationArbiter": "",
|
||||
"nonexclusiveRevocableConfirmationArbiter": "",
|
||||
"nonexclusiveUnrevocableConfirmationArbiter": "",
|
||||
"nativeTokenEscrowObligation": "",
|
||||
"nativeTokenPaymentObligation": "",
|
||||
"nativeTokenBarterUtils": "",
|
||||
"recipientArbiter": "",
|
||||
"attesterArbiter": "",
|
||||
"schemaArbiter": "",
|
||||
"uidArbiter": "",
|
||||
"refUidArbiter": "",
|
||||
"revocableArbiter": "",
|
||||
"timeAfterArbiter": "",
|
||||
"timeBeforeArbiter": "",
|
||||
"timeEqualArbiter": "",
|
||||
"expirationTimeAfterArbiter": "",
|
||||
"expirationTimeBeforeArbiter": "",
|
||||
"expirationTimeEqualArbiter": ""
|
||||
}
|
||||
}
|
||||
27
cli/index.ts
27
cli/index.ts
|
|
@ -12,6 +12,7 @@ import { contracts } from "alkahest-ts";
|
|||
import { runDevCommand } from "./commands/dev.js";
|
||||
import { runStopCommand } from "./commands/stop.js";
|
||||
import { runSwitchCommand } from "./commands/switch.js";
|
||||
import { setWallet, showWallet, clearWallet } from "./commands/wallet.js";
|
||||
|
||||
// Get the directory name for ESM modules (compatible with both Node and Bun)
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
|
@ -29,8 +30,11 @@ Commands:
|
|||
deploy Deploy contracts to blockchain
|
||||
start-oracle Start the oracle service
|
||||
stop Stop all services (Anvil + Oracle)
|
||||
switch [env] Switch between environments (devnet, sepolia, mainnet)
|
||||
switch [env] Switch between environments (devnet, sepolia, base-sepolia, mainnet)
|
||||
network Show current network/environment
|
||||
wallet:set Set wallet private key
|
||||
wallet:show Show current wallet address
|
||||
wallet:clear Clear wallet from config
|
||||
escrow:create Create a new escrow with natural language demand
|
||||
escrow:fulfill Fulfill an existing escrow
|
||||
escrow:collect Collect an approved escrow
|
||||
|
|
@ -182,6 +186,27 @@ async function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle wallet commands
|
||||
if (command === "wallet:set") {
|
||||
const privateKey = args["private-key"] as string | undefined;
|
||||
if (!privateKey) {
|
||||
console.error("❌ Missing required option: --private-key");
|
||||
process.exit(1);
|
||||
}
|
||||
await setWallet(privateKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "wallet:show") {
|
||||
await showWallet();
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "wallet:clear") {
|
||||
await clearWallet();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle TypeScript commands that can run directly
|
||||
if (command === "deploy") {
|
||||
await runServerCommand("deploy.js", process.argv.slice(3));
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import { parseArgs } from "util";
|
||||
import { createWalletClient, http, publicActions, parseEther } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { mainnet, sepolia, foundry } from "viem/chains";
|
||||
import { mainnet, sepolia, baseSepolia, foundry } from "viem/chains";
|
||||
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { fixtures, contracts } from "alkahest-ts";
|
||||
|
|
@ -23,7 +23,7 @@ Usage:
|
|||
bun deploy.ts [options]
|
||||
|
||||
Options:
|
||||
--network <name> Network to deploy to: mainnet, sepolia, localhost (required)
|
||||
--network <name> Network to deploy to: mainnet, sepolia, base-sepolia, localhost (required)
|
||||
--rpc-url <url> Custom RPC URL (overrides network default)
|
||||
--private-key <key> Deployer's private key (required)
|
||||
--output <path> Output file for deployment addresses (default: ./cli/deployments/<network>.json)
|
||||
|
|
@ -36,6 +36,7 @@ Environment Variables (alternative to CLI options):
|
|||
Networks:
|
||||
mainnet Ethereum Mainnet
|
||||
sepolia Ethereum Sepolia Testnet
|
||||
base-sepolia Base Sepolia Testnet
|
||||
localhost Local development (Anvil/Hardhat)
|
||||
|
||||
Examples:
|
||||
|
|
@ -75,11 +76,13 @@ function getChain(network: string) {
|
|||
return mainnet;
|
||||
case "sepolia":
|
||||
return sepolia;
|
||||
case "base-sepolia":
|
||||
return baseSepolia;
|
||||
case "localhost":
|
||||
case "local":
|
||||
return foundry;
|
||||
default:
|
||||
throw new Error(`Unknown network: ${network}. Use mainnet, sepolia, or localhost`);
|
||||
throw new Error(`Unknown network: ${network}. Use mainnet, sepolia, base-sepolia, or localhost`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -368,16 +371,6 @@ async function main() {
|
|||
console.log("\n🎯 Next steps:");
|
||||
console.log("1. Start the oracle:");
|
||||
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 \\`);
|
||||
console.log(` --arbitration-provider "OpenAI" \\`);
|
||||
console.log(` --arbitration-model "gpt-4o-mini"`);
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Deployment failed:", error);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,26 @@
|
|||
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 { resolve, dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { makeClient } from "alkahest-ts";
|
||||
import { fixtures } from "alkahest-ts";
|
||||
import { ProviderName } from "../../nla";
|
||||
import { contractAddresses } from "alkahest-ts";
|
||||
import {
|
||||
getCurrentEnvironment,
|
||||
getDeploymentPath,
|
||||
loadEnvFile,
|
||||
loadDeploymentWithDefaults,
|
||||
getChainFromNetwork,
|
||||
getPrivateKey
|
||||
} from "../utils.js";
|
||||
|
||||
// Get the directory name for ESM modules
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Helper function to display usage
|
||||
function displayHelp() {
|
||||
|
|
@ -19,38 +32,45 @@ Usage:
|
|||
bun oracle.ts [options]
|
||||
|
||||
Options:
|
||||
--rpc-url <url> RPC URL for the blockchain network (required)
|
||||
--private-key <key> Private key of the oracle operator (required)
|
||||
--openai-api-key <key> OpenAI API key (optional)
|
||||
--anthropic-api-key <key> Anthropic API key (optional)
|
||||
--openrouter-api-key <key> OpenRouter API key (optional)
|
||||
--perplexity-api-key <key> Perplexity API key for search tools (optional)
|
||||
--eas-contract <address> EAS contract address (optional)
|
||||
--deployment <file> Load addresses from deployment file (optional)
|
||||
--private-key <key> Private key of the oracle operator (optional, loaded from .env)
|
||||
--openai-api-key <key> OpenAI API key (optional, loaded from .env)
|
||||
--anthropic-api-key <key> Anthropic API key (optional, loaded from .env)
|
||||
--openrouter-api-key <key> OpenRouter API key (optional, loaded from .env)
|
||||
--perplexity-api-key <key> Perplexity API key (optional, loaded from .env)
|
||||
--env <file> Path to .env file (default: .env)
|
||||
--deployment <file> Load addresses from deployment file (optional, auto-detected from current network)
|
||||
--polling-interval <ms> 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
|
||||
Environment Variables (from .env file or environment):
|
||||
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/devnet.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...
|
||||
# Using .env file (default)
|
||||
bun oracle.ts
|
||||
|
||||
# Using custom .env file
|
||||
bun oracle.ts --env /path/to/.env.production
|
||||
|
||||
# Override with command-line parameters
|
||||
bun oracle.ts --private-key 0x... --openai-api-key sk-...
|
||||
|
||||
# Using specific deployment file
|
||||
bun oracle.ts --deployment ./deployments/sepolia.json
|
||||
|
||||
# Mix of .env and command-line parameters
|
||||
bun oracle.ts --openai-api-key sk-... --env .env.local
|
||||
|
||||
Example .env file:
|
||||
ORACLE_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||
OPENAI_API_KEY=sk-...
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
OPENROUTER_API_KEY=sk-or-...
|
||||
PERPLEXITY_API_KEY=pplx-...
|
||||
`);
|
||||
}
|
||||
|
||||
|
|
@ -59,13 +79,12 @@ 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" },
|
||||
"env": { type: "string" },
|
||||
"deployment": { type: "string" },
|
||||
"polling-interval": { type: "string" },
|
||||
"help": { type: "boolean", short: "h" },
|
||||
|
|
@ -76,16 +95,6 @@ function parseCliArgs() {
|
|||
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 {
|
||||
|
|
@ -97,27 +106,33 @@ async function main() {
|
|||
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`);
|
||||
// Load .env file
|
||||
const envPath = args.env || ".env";
|
||||
const resolvedEnvPath = resolve(process.cwd(), envPath);
|
||||
|
||||
if (existsSync(resolvedEnvPath)) {
|
||||
console.log(`📁 Loading environment from: ${resolvedEnvPath}\n`);
|
||||
loadEnvFile(resolvedEnvPath);
|
||||
} else if (args.env) {
|
||||
// Only error if user explicitly specified an env file
|
||||
console.error(`❌ Error: .env file not found: ${resolvedEnvPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const privateKey = args["private-key"] || process.env.ORACLE_PRIVATE_KEY;
|
||||
// Load deployment file (auto-detects current network if not specified)
|
||||
const currentEnv = getCurrentEnvironment();
|
||||
const deploymentFile = args.deployment;
|
||||
|
||||
if (deploymentFile) {
|
||||
console.log(`<EFBFBD> Loading deployment from: ${deploymentFile}\n`);
|
||||
} else {
|
||||
console.log(`<EFBFBD> Auto-detected environment: ${currentEnv}\n`);
|
||||
}
|
||||
|
||||
const deployment = loadDeploymentWithDefaults(deploymentFile);
|
||||
console.log(`✅ Loaded deployment (${deployment.network})\n`);
|
||||
|
||||
const privateKey = args["private-key"] || getPrivateKey();
|
||||
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;
|
||||
|
|
@ -125,29 +140,39 @@ async function main() {
|
|||
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.");
|
||||
if (!deployment?.rpcUrl) {
|
||||
console.error("❌ Error: RPC URL not found in deployment file.");
|
||||
console.error(" Current environment:", getCurrentEnvironment());
|
||||
console.error(" Make sure the deployment file exists for your current network.");
|
||||
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.");
|
||||
console.error("❌ Error: Private key is required.");
|
||||
console.error("\n💡 You can either:");
|
||||
console.error(" 1. Set it globally: nla wallet:set --private-key <your-key>");
|
||||
console.error(" 2. Use for this command only: --private-key <your-key>");
|
||||
console.error(" 3. Set ORACLE_PRIVATE_KEY in .env file");
|
||||
console.error(" 4. Set PRIVATE_KEY environment variable");
|
||||
console.error("\nRun 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(" Set one of these in your .env file:");
|
||||
console.error(" - OPENAI_API_KEY");
|
||||
console.error(" - ANTHROPIC_API_KEY");
|
||||
console.error(" - 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(` 📡 RPC URL: ${deployment.rpcUrl}`);
|
||||
console.log(` 🔑 Oracle Key: ${privateKey.slice(0, 6)}...${privateKey.slice(-4)}`);
|
||||
|
||||
// Show available providers
|
||||
|
|
@ -161,21 +186,24 @@ async function main() {
|
|||
console.log(` 🔍 Perplexity Search: Enabled`);
|
||||
}
|
||||
|
||||
if (easContract) {
|
||||
console.log(` 📝 EAS Contract: ${easContract}`);
|
||||
if (deployment.addresses.eas) {
|
||||
console.log(` 📝 EAS Contract: ${deployment.addresses.eas}`);
|
||||
}
|
||||
console.log(` ⏱️ Polling Interval: ${pollingInterval}ms\n`);
|
||||
|
||||
// Create wallet client
|
||||
const chain = getChainFromNetwork(deployment.network);
|
||||
const account = privateKeyToAccount(privateKey as `0x${string}`);
|
||||
const walletClient = createWalletClient({
|
||||
account,
|
||||
chain: foundry,
|
||||
transport: http(rpcUrl),
|
||||
chain,
|
||||
transport: http(deployment.rpcUrl),
|
||||
}).extend(publicActions) as any;
|
||||
|
||||
|
||||
|
||||
// Create alkahest client
|
||||
const client = makeClient(walletClient, deploymentAddresses || { eas: easContract });
|
||||
const client = makeClient(walletClient, deployment.addresses);
|
||||
|
||||
console.log(`✅ Oracle initialized with address: ${account.address}\n`);
|
||||
|
||||
|
|
@ -234,12 +262,25 @@ async function main() {
|
|||
console.log(` Obligation: "${obligationItem}"`);
|
||||
|
||||
const trustedOracleDemandData = client.arbiters.general.trustedOracle.decodeDemand(demand);
|
||||
console.log(` DEBUG - trustedOracleDemandData:`, trustedOracleDemandData);
|
||||
|
||||
const nlaDemandData = llmClient.llm.decodeDemand(trustedOracleDemandData.data);
|
||||
console.log(` DEBUG - nlaDemandData:`, nlaDemandData);
|
||||
|
||||
console.log(` Demand: "${nlaDemandData.demand}"`);
|
||||
console.log(` Provider: ${nlaDemandData.arbitrationProvider}`);
|
||||
console.log(` Model: ${nlaDemandData.arbitrationModel}`);
|
||||
|
||||
// Validate the demand data before proceeding
|
||||
if (!nlaDemandData.demand || !nlaDemandData.arbitrationModel || nlaDemandData.arbitrationModel.includes('\u0000')) {
|
||||
console.error(` ❌ Invalid demand data - contains null bytes or empty fields`);
|
||||
console.error(` This usually means the demand was encoded incorrectly`);
|
||||
console.error(` Skipping this attestation (throwing error to avoid on-chain recording)...\n`);
|
||||
throw new Error('Invalid demand data - skipping attestation');
|
||||
}
|
||||
|
||||
// Perform arbitration using LLM
|
||||
console.log(` 🤔 Arbitrating with AI...`);
|
||||
console.log(` 🤔 Arbitrating with ${nlaDemandData.arbitrationProvider}...`);
|
||||
const result = await llmClient.llm.arbitrate(
|
||||
nlaDemandData,
|
||||
obligationItem
|
||||
|
|
@ -249,14 +290,21 @@ async function main() {
|
|||
return result;
|
||||
} catch (error) {
|
||||
console.error(` ❌ Error during arbitration:`, error);
|
||||
throw error;
|
||||
console.error(` Continuing to listen for new requests...\n`);
|
||||
return false; // Return false instead of throwing to keep oracle running
|
||||
}
|
||||
},
|
||||
{
|
||||
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`);
|
||||
try {
|
||||
console.log(` 📝 Arbitration decision recorded on-chain`);
|
||||
console.log(` Decision UID: ${decision.attestation.uid}`);
|
||||
console.log(` Result: ${decision.decision ? "✅ Fulfilled" : "❌ Not Fulfilled"}\n`);
|
||||
} catch (error: any) {
|
||||
console.error(` ⚠️ Failed to record arbitration on-chain:`, error.message);
|
||||
console.error(` This may be due to transaction conflicts or gas issues`);
|
||||
console.error(` Continuing to listen for new requests...\n`);
|
||||
}
|
||||
},
|
||||
pollingInterval,
|
||||
},
|
||||
|
|
@ -264,6 +312,36 @@ async function main() {
|
|||
|
||||
console.log("✨ Oracle is now running. Press Ctrl+C to stop.\n");
|
||||
|
||||
// Show next steps for creating escrow
|
||||
if (deployment) {
|
||||
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
console.log("📝 Next Steps - Create Your First Escrow:\n");
|
||||
|
||||
if (currentEnv === 'devnet') {
|
||||
console.log("1. Export your private key (use a test account):");
|
||||
console.log(" export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\n");
|
||||
} else {
|
||||
console.log("1. Export your private key:");
|
||||
console.log(" export PRIVATE_KEY=<your-private-key>\n");
|
||||
}
|
||||
|
||||
console.log("2. Create an escrow:");
|
||||
console.log(" nla escrow:create \\");
|
||||
console.log(" --demand \"The sky is blue\" \\");
|
||||
console.log(" --amount 10 \\");
|
||||
|
||||
if (currentEnv === 'devnet' && deployment.addresses.mockERC20A) {
|
||||
console.log(` --token ${deployment.addresses.mockERC20A} \\`);
|
||||
} else {
|
||||
console.log(" --token <ERC20_TOKEN_ADDRESS> \\");
|
||||
}
|
||||
|
||||
console.log(` --oracle ${account.address} \\`);
|
||||
console.log(" --arbitration-provider \"OpenAI\" \\");
|
||||
console.log(" --arbitration-model \"gpt-4o-mini\"");
|
||||
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
const shutdown = async () => {
|
||||
console.log("\n\n🛑 Shutting down oracle...");
|
||||
|
|
|
|||
229
cli/utils.ts
229
cli/utils.ts
|
|
@ -2,22 +2,247 @@
|
|||
* Shared utilities for NLA CLI
|
||||
*/
|
||||
|
||||
import { foundry, sepolia, mainnet } from "viem/chains";
|
||||
import { foundry, sepolia, mainnet, baseSepolia } from "viem/chains";
|
||||
import type { Chain } from "viem/chains";
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { homedir } from "os";
|
||||
import { contractAddresses } from "alkahest-ts";
|
||||
|
||||
/**
|
||||
* Load .env file and set environment variables
|
||||
*/
|
||||
export function loadEnvFile(envPath: string): void {
|
||||
if (!existsSync(envPath)) {
|
||||
throw new Error(`.env file not found: ${envPath}`);
|
||||
}
|
||||
|
||||
const content = readFileSync(envPath, "utf-8");
|
||||
const lines = content.split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (!trimmed || trimmed.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse KEY=VALUE format
|
||||
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
||||
if (match) {
|
||||
const key = match[1].trim();
|
||||
let value = match[2].trim();
|
||||
|
||||
// Remove surrounding quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// Only set if not already set
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get viem chain configuration from network name
|
||||
*/
|
||||
export function getChainFromNetwork(network: string): Chain {
|
||||
switch (network.toLowerCase()) {
|
||||
// Normalize the network name: lowercase and replace spaces with dashes
|
||||
const normalized = network.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
switch (normalized) {
|
||||
case "localhost":
|
||||
case "devnet":
|
||||
return foundry;
|
||||
case "sepolia":
|
||||
case "ethereum-sepolia":
|
||||
return sepolia;
|
||||
case "base-sepolia":
|
||||
return baseSepolia;
|
||||
case "mainnet":
|
||||
case "ethereum":
|
||||
return mainnet;
|
||||
default:
|
||||
return foundry;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get NLA config directory (~/.nla)
|
||||
*/
|
||||
export function getNLAConfigDir(): string {
|
||||
const configDir = join(homedir(), '.nla');
|
||||
if (!existsSync(configDir)) {
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
return configDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current environment from config
|
||||
*/
|
||||
export function getCurrentEnvironment(): string {
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
// Default to devnet
|
||||
return 'devnet';
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||
return config.environment || 'devnet';
|
||||
} catch (e) {
|
||||
return 'devnet';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current environment in config
|
||||
*/
|
||||
export function setCurrentEnvironment(env: string): void {
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
const config = existsSync(configPath)
|
||||
? JSON.parse(readFileSync(configPath, 'utf-8'))
|
||||
: {};
|
||||
|
||||
config.environment = env;
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get private key from config or environment
|
||||
*/
|
||||
export function getPrivateKey(): string | undefined {
|
||||
// First check environment variable
|
||||
if (process.env.PRIVATE_KEY) {
|
||||
return process.env.PRIVATE_KEY;
|
||||
}
|
||||
|
||||
// Then check config file
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
if (!existsSync(configPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||
return config.privateKey;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set private key in config
|
||||
*/
|
||||
export function setPrivateKey(privateKey: string): void {
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
const config = existsSync(configPath)
|
||||
? JSON.parse(readFileSync(configPath, 'utf-8'))
|
||||
: {};
|
||||
|
||||
config.privateKey = privateKey;
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear private key from config
|
||||
*/
|
||||
export function clearPrivateKey(): void {
|
||||
const configPath = join(getNLAConfigDir(), 'config.json');
|
||||
if (!existsSync(configPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||
delete config.privateKey;
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get deployment path for environment
|
||||
*/
|
||||
export function getDeploymentPath(cliDir: string, env?: string): string {
|
||||
const environment = env || getCurrentEnvironment();
|
||||
const filename = `${environment}.json`;
|
||||
|
||||
// Try multiple locations
|
||||
const paths = [
|
||||
join(cliDir, 'deployments', filename), // dist/cli/deployments/
|
||||
join(process.cwd(), 'cli', 'deployments', filename), // Project root
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
if (existsSync(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first path as default (even if it doesn't exist yet)
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load deployment file and fill empty addresses with defaults from contractAddresses
|
||||
* If deploymentFilePath doesn't exist, tries to load deployment for current network
|
||||
*/
|
||||
export function loadDeploymentWithDefaults(deploymentFilePath?: string): {
|
||||
network: string;
|
||||
chainId: number;
|
||||
rpcUrl: string;
|
||||
addresses: Record<string, string>;
|
||||
} {
|
||||
let actualPath = deploymentFilePath;
|
||||
|
||||
// If no path provided or path doesn't exist, try current network
|
||||
if (!actualPath || !existsSync(actualPath)) {
|
||||
const currentEnv = getCurrentEnvironment();
|
||||
const autoPath = getDeploymentPath(process.cwd(), currentEnv);
|
||||
|
||||
if (existsSync(autoPath)) {
|
||||
actualPath = autoPath;
|
||||
} else if (!actualPath) {
|
||||
throw new Error(`No deployment file found for current environment: ${currentEnv}`);
|
||||
} else {
|
||||
throw new Error(`Deployment file not found: ${actualPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
const content = readFileSync(actualPath, "utf-8");
|
||||
const deployment = JSON.parse(content);
|
||||
|
||||
let finalAddresses: Record<string, string> = {};
|
||||
|
||||
// Start with default addresses from contractAddresses if available
|
||||
// contractAddresses is indexed by chain name (e.g., "Base Sepolia", "foundry")
|
||||
const chainName = deployment.network;
|
||||
if (contractAddresses[chainName]) {
|
||||
finalAddresses = { ...contractAddresses[chainName] };
|
||||
}
|
||||
|
||||
// Override with deployment addresses, but only if they're not empty strings
|
||||
if (deployment.addresses && Object.keys(deployment.addresses).length > 0) {
|
||||
for (const [key, value] of Object.entries(deployment.addresses)) {
|
||||
if (value && value !== "") {
|
||||
finalAddresses[key] = value as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
network: deployment.network,
|
||||
chainId: deployment.chainId,
|
||||
rpcUrl: deployment.rpcUrl,
|
||||
addresses: finalAddresses,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"@perplexity-ai/ai-sdk": "^0.1.2",
|
||||
"@viem/anvil": "^0.0.10",
|
||||
"ai": "^6.0.5",
|
||||
"alkahest-ts": "github:arkhai-io/alkahest",
|
||||
"alkahest-ts": "^0.6.1",
|
||||
"arktype": "^2.1.23",
|
||||
"viem": "^2.42.1",
|
||||
"zod": "^3.25.76"
|
||||
|
|
|
|||
Loading…
Reference in New Issue