From 048206d1dae4a2b7f7c810fc75328846d5b5aaf7 Mon Sep 17 00:00:00 2001 From: ngoc Date: Sun, 1 Feb 2026 15:18:27 +0700 Subject: [PATCH] feat: get, show, clear wallet command --- cli/client/collect-escrow.ts | 12 ++++-- cli/client/create-escrow.ts | 12 ++++-- cli/client/fulfill-escrow.ts | 12 ++++-- cli/commands/wallet.ts | 72 ++++++++++++++++++++++++++++++++++++ cli/index.ts | 25 +++++++++++++ cli/server/oracle.ts | 13 +++++-- cli/utils.ts | 54 +++++++++++++++++++++++++++ 7 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 cli/commands/wallet.ts diff --git a/cli/client/collect-escrow.ts b/cli/client/collect-escrow.ts index 5ed3f7c..215c83c 100644 --- a/cli/client/collect-escrow.ts +++ b/cli/client/collect-escrow.ts @@ -12,7 +12,7 @@ import { existsSync, readFileSync } from "fs"; import { resolve, dirname, join } from "path"; import { fileURLToPath } from "url"; import { makeClient } from "alkahest-ts"; -import { getChainFromNetwork, loadDeploymentWithDefaults } from "../utils.js"; +import { getChainFromNetwork, loadDeploymentWithDefaults, getPrivateKey } from "../utils.js"; // Get the directory of the current module const __filename = fileURLToPath(import.meta.url); @@ -84,7 +84,7 @@ 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 privateKey = args["private-key"] || getPrivateKey(); const deploymentPath = args.deployment; // Validate required parameters @@ -101,8 +101,12 @@ 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 "); + console.error(" 2. Use for this command only: --private-key "); + console.error(" 3. Set PRIVATE_KEY environment variable"); + console.error("\nRun with --help for usage information."); process.exit(1); } diff --git a/cli/client/create-escrow.ts b/cli/client/create-escrow.ts index 2d1b6cd..1016eb6 100644 --- a/cli/client/create-escrow.ts +++ b/cli/client/create-escrow.ts @@ -15,7 +15,7 @@ import { fileURLToPath } from "url"; import { makeClient } from "alkahest-ts"; import { makeLLMClient } from "../.."; import {fixtures} from "alkahest-ts"; -import { getCurrentEnvironment, getChainFromNetwork, loadDeploymentWithDefaults } from "../utils.js"; +import { getCurrentEnvironment, getChainFromNetwork, loadDeploymentWithDefaults, getPrivateKey } from "../utils.js"; // Get the directory of the current module const __filename = fileURLToPath(import.meta.url); @@ -104,7 +104,7 @@ 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 privateKey = args["private-key"] || getPrivateKey(); const deploymentPath = args.deployment; // Arbitration configuration with defaults @@ -143,8 +143,12 @@ 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 "); + console.error(" 2. Use for this command only: --private-key "); + console.error(" 3. Set PRIVATE_KEY environment variable"); + console.error("\nRun with --help for usage information."); process.exit(1); } diff --git a/cli/client/fulfill-escrow.ts b/cli/client/fulfill-escrow.ts index e3c33de..93a1968 100644 --- a/cli/client/fulfill-escrow.ts +++ b/cli/client/fulfill-escrow.ts @@ -14,7 +14,7 @@ import { resolve, dirname, join } from "path"; import { fileURLToPath } from "url"; import { makeClient } from "alkahest-ts"; import { makeLLMClient } from "../.."; -import { getChainFromNetwork, loadDeploymentWithDefaults } from "../utils.js"; +import { getChainFromNetwork, loadDeploymentWithDefaults, getPrivateKey } from "../utils.js"; // Get the directory of the current module const __filename = fileURLToPath(import.meta.url); @@ -90,7 +90,7 @@ 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 privateKey = args["private-key"] || getPrivateKey(); const deploymentPath = args.deployment ; // Validate required parameters @@ -113,8 +113,12 @@ 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 "); + console.error(" 2. Use for this command only: --private-key "); + console.error(" 3. Set PRIVATE_KEY environment variable"); + console.error("\nRun with --help for usage information."); process.exit(1); } diff --git a/cli/commands/wallet.ts b/cli/commands/wallet.ts new file mode 100644 index 0000000..3108eeb --- /dev/null +++ b/cli/commands/wallet.ts @@ -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 { + // 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 { + const privateKey = getPrivateKey(); + + if (!privateKey) { + console.log('ā„¹ļø No wallet configured'); + console.log('\nšŸ’” Set your wallet with:'); + console.log(' nla wallet:set --private-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 '); + } +} + +/** + * Clear wallet from config + */ +export async function clearWallet(): Promise { + clearPrivateKey(); + console.log('āœ… Wallet cleared from config'); + console.log('\nšŸ’” Set a new wallet with:'); + console.log(' nla wallet:set --private-key '); +} diff --git a/cli/index.ts b/cli/index.ts index de0b697..ea8a17c 100755 --- a/cli/index.ts +++ b/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); @@ -31,6 +32,9 @@ Commands: stop Stop all services (Anvil + Oracle) 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)); diff --git a/cli/server/oracle.ts b/cli/server/oracle.ts index 59eea41..1a26355 100644 --- a/cli/server/oracle.ts +++ b/cli/server/oracle.ts @@ -15,7 +15,8 @@ import { getDeploymentPath, loadEnvFile, loadDeploymentWithDefaults, - getChainFromNetwork + getChainFromNetwork, + getPrivateKey } from "../utils.js"; // Get the directory name for ESM modules @@ -131,7 +132,7 @@ async function main() { const deployment = loadDeploymentWithDefaults(deploymentFile); console.log(`āœ… Loaded deployment (${deployment.network})\n`); - const privateKey = args["private-key"] || process.env.ORACLE_PRIVATE_KEY; + 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; @@ -149,8 +150,12 @@ async function main() { if (!privateKey) { console.error("āŒ Error: Private key is required."); - console.error(" Set ORACLE_PRIVATE_KEY in .env file or use --private-key"); - console.error("Run with --help for usage information."); + console.error("\nšŸ’” You can either:"); + console.error(" 1. Set it globally: nla wallet:set --private-key "); + console.error(" 2. Use for this command only: --private-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); } diff --git a/cli/utils.ts b/cli/utils.ts index 1dd7bff..ed67f99 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -115,6 +115,60 @@ export function setCurrentEnvironment(env: string): void { 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 */