#!/usr/bin/env bun /** * CLI tool to collect a fulfilled Natural Language Agreement escrow * * After the oracle approves the fulfillment, use this to collect the escrowed tokens. */ import { parseArgs } from "util"; import { createWalletClient, http, publicActions } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { foundry } from "viem/chains"; import { existsSync, readFileSync } from "fs"; import { resolve, dirname, join } from "path"; import { fileURLToPath } from "url"; import { makeClient } from "alkahest-ts"; // 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(` Natural Language Agreement Collection CLI Collect escrowed tokens after oracle approves fulfillment. Usage: bun cli/collect-escrow.ts [options] Options: --escrow-uid Escrow UID to collect (required) --fulfillment-uid Fulfillment UID that was approved (required) --private-key Your private key (required) --deployment Path to deployment file (default: ./cli/deployments/devnet.json) --rpc-url RPC URL (default: from deployment file) --help, -h Display this help message Environment Variables (alternative to CLI options): PRIVATE_KEY Your private key RPC_URL Custom RPC URL Examples: # Collect approved escrow bun cli/collect-escrow.ts \\ --escrow-uid 0x... \\ --fulfillment-uid 0x... \\ --private-key 0x... # Using environment variables export PRIVATE_KEY=0x... bun cli/collect-escrow.ts --escrow-uid 0x... --fulfillment-uid 0x... `); } // Parse command line arguments function parseCliArgs() { const { values } = parseArgs({ args: Bun.argv.slice(2), options: { "escrow-uid": { type: "string" }, "fulfillment-uid": { type: "string" }, "private-key": { type: "string" }, "deployment": { type: "string" }, "rpc-url": { type: "string" }, "help": { type: "boolean", short: "h" }, }, strict: true, }); return values; } async function main() { try { const args = parseCliArgs(); // Display help if requested if (args.help) { displayHelp(); process.exit(0); } // 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"; // Validate required parameters if (!escrowUid) { console.error("❌ Error: Escrow UID is required. Use --escrow-uid "); console.error("Run with --help for usage information."); process.exit(1); } if (!fulfillmentUid) { console.error("❌ Error: Fulfillment UID is required. Use --fulfillment-uid "); 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 PRIVATE_KEY"); console.error("Run 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}`); 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; console.log("🚀 Collecting Natural Language Agreement Escrow\n"); console.log("Configuration:"); console.log(` 📦 Escrow UID: ${escrowUid}`); console.log(` ✅ Fulfillment UID: ${fulfillmentUid}`); console.log(` 🌐 RPC URL: ${rpcUrl}\n`); // Create account and wallet const account = privateKeyToAccount(privateKey as `0x${string}`); const walletClient = createWalletClient({ account, chain: foundry, transport: http(rpcUrl), }).extend(publicActions); console.log(`✅ Collector address: ${account.address}\n`); // Check balance const balance = await walletClient.getBalance({ address: account.address }); console.log(`💰 ETH balance: ${parseFloat((balance / 10n ** 18n).toString()).toFixed(4)} ETH\n`); if (balance === 0n) { console.error("❌ Error: Account has no ETH for gas. Please fund the account first."); process.exit(1); } // Create alkahest client const client = makeClient( walletClient as any, deployment.addresses ); console.log("💰 Collecting escrow...\n"); // Collect the escrow const collectionHash = await client.erc20.escrow.nonTierable.collect( escrowUid as `0x${string}`, fulfillmentUid as `0x${string}`, ); console.log("✨ Escrow collected successfully!\n"); console.log("📋 Transaction Details:"); console.log(` Transaction Hash: ${collectionHash}`); console.log(` Block Explorer: ${rpcUrl.includes('localhost') ? 'Local Anvil' : 'View on explorer'}\n`); console.log("🎉 Success! The escrowed tokens have been transferred to you."); } catch (error) { console.error("❌ Failed to collect escrow:", error); process.exit(1); } } // Run the CLI main();