From a2219e43a9dcc7959bf448ee56626eea3c58cced Mon Sep 17 00:00:00 2001 From: Om Santoshwar Date: Tue, 2 Jun 2026 22:41:18 +0530 Subject: [PATCH] fix: revert cosmetic formatting and document coco-core wallet status --- src/cli.ts | 258 ++++++++++++------------------- src/daemon/wallet/coco-client.ts | 6 +- 2 files changed, 107 insertions(+), 157 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index aeb72af..4e9cde6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -29,11 +29,7 @@ import { logger } from "./utils/logger"; import { setupIntegration, runIntegrationsForClients } from "./integrations"; import { getClientsList } from "./utils/clients"; import * as QRCode from "qrcode"; -import { - normalizeNostrPubkey, - npubFromPubkey, - npubFromSecretKey, -} from "./utils/nip98"; +import { normalizeNostrPubkey, npubFromPubkey, npubFromSecretKey } from "./utils/nip98"; import { generateSecretKey, nip19 } from "nostr-tools"; import { generateMnemonic } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english.js"; @@ -88,12 +84,10 @@ function initializeWallet(): void { process.env.COCOD_DIR || `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`; const walletConfig = `${walletDir}/config.json`; - if (existsSync(walletConfig)) { console.log("Wallet already initialized."); return; } - mkdirSync(walletDir, { recursive: true }); const mnemonic = generateMnemonic(wordlist); const config = { @@ -272,7 +266,7 @@ async function initDaemon(): Promise { console.log(`Your npub: ${npub}`); console.log(`You can view it in the config file at: ${CONFIG_FILE}\n`); } - + console.log(`Database will be stored at: ${DB_PATH}`); initializeWallet(); @@ -339,76 +333,36 @@ program "Mint URL to refund to (defaults to first mint in wallet)", ) .option("-y, --yes", "Skip confirmation prompt", false) - .option( - "--xcashu", - "Refund xcashu tokens only (uses refundXcashuTokens)", - false, - ) - .action( - async (options: { mintUrl?: string; yes: boolean; xcashu: boolean }) => { - await ensureDaemonRunning(); + .option("--xcashu", "Refund xcashu tokens only (uses refundXcashuTokens)", false) + .action(async (options: { mintUrl?: string; yes: boolean; xcashu: boolean }) => { + await ensureDaemonRunning(); - let mintUrl = options.mintUrl; - if (!mintUrl) { - const balanceResult = await callDaemon("/balance"); - if (balanceResult.error) { - console.log(balanceResult.error); - process.exit(1); - } - const balances = ( - balanceResult.output as - | { - balances?: Record; - } - | undefined - )?.balances; - if (!balances || Object.keys(balances).length === 0) { - console.log("No mint URLs found in wallet balance"); - process.exit(1); - } - mintUrl = Object.keys(balances)[0]; - console.log(`Using mint URL: ${mintUrl}`); + let mintUrl = options.mintUrl; + if (!mintUrl) { + const balanceResult = await callDaemon("/balance"); + if (balanceResult.error) { + console.log(balanceResult.error); + process.exit(1); } - - try { - if (options.xcashu) { - // xcashu path: only refund xcashu tokens - const result = await callDaemon("/refund/xcashu", { - method: "POST", - body: { mintUrl }, - }); - - if (result.error) { - console.log(result.error); - process.exit(1); - } - - const output = result.output as - | { - message: string; - results: Array<{ - baseUrl: string; - token: string; - success: boolean; - error?: string; - }>; - } - | undefined; - - if (output) { - console.log(output.message); - console.log("\nResults:"); - for (const r of output.results) { - const status = r.success - ? "success" - : `failed: ${r.error || "unknown"}`; - console.log(` - ${r.baseUrl}: ${status}`); + const balances = ( + balanceResult.output as + | { + balances?: Record; } - } - return; - } + | undefined + )?.balances; + if (!balances || Object.keys(balances).length === 0) { + console.log("No mint URLs found in wallet balance"); + process.exit(1); + } + mintUrl = Object.keys(balances)[0]; + console.log(`Using mint URL: ${mintUrl}`); + } - const result = await callDaemon("/refund", { + try { + if (options.xcashu) { + // xcashu path: only refund xcashu tokens + const result = await callDaemon("/refund/xcashu", { method: "POST", body: { mintUrl }, }); @@ -421,46 +375,68 @@ program const output = result.output as | { message: string; - pendingTokens: number; - apiKeys: number; - results: Array<{ baseUrl: string; success: boolean }>; + results: Array<{ baseUrl: string; token: string; success: boolean; error?: string }>; } | undefined; if (output) { console.log(output.message); - console.log(`\nPending tokens: ${output.pendingTokens}`); - console.log(`API keys: ${output.apiKeys}`); console.log("\nResults:"); for (const r of output.results) { - console.log( - ` - ${r.baseUrl}: ${r.success ? "success" : "failed"}`, - ); + const status = r.success ? "success" : `failed: ${r.error || "unknown"}`; + console.log(` - ${r.baseUrl}: ${status}`); } } - } catch (error) { - const message = (error as Error).message; - if ( - message?.includes("fetch failed") || - message?.includes("Connection refused") - ) { - console.error("Daemon is not running"); - process.exit(1); - } - console.error(message); + return; + } + + const result = await callDaemon("/refund", { + method: "POST", + body: { mintUrl }, + }); + + if (result.error) { + console.log(result.error); process.exit(1); } - }, - ); + + const output = result.output as + | { + message: string; + pendingTokens: number; + apiKeys: number; + results: Array<{ baseUrl: string; success: boolean }>; + } + | undefined; + + if (output) { + console.log(output.message); + console.log(`\nPending tokens: ${output.pendingTokens}`); + console.log(`API keys: ${output.apiKeys}`); + console.log("\nResults:"); + for (const r of output.results) { + console.log(` - ${r.baseUrl}: ${r.success ? "success" : "failed"}`); + } + } + } catch (error) { + const message = (error as Error).message; + if ( + message?.includes("fetch failed") || + message?.includes("Connection refused") + ) { + console.error("Daemon is not running"); + process.exit(1); + } + console.error(message); + process.exit(1); + } + }); // Remote - configure a remote daemon URL program .command("remote ") .description("Configure a remote daemon URL") - .option( - "--auth-url ", - "URL of the auth proxy for management commands (npubs, clients, usage)", - ) + .option("--auth-url ", "URL of the auth proxy for management commands (npubs, clients, usage)") .action(async (url: string, options: { authUrl?: string }) => { try { new URL(url); @@ -513,7 +489,9 @@ program `\nA new Nostr identity has been generated for remote authentication.`, ); console.log(`Your npub: ${generatedNpub}`); - console.log(`You can view it in the config file at: ${CONFIG_FILE}`); + console.log( + `You can view it in the config file at: ${CONFIG_FILE}`, + ); } }); @@ -1028,9 +1006,7 @@ npubsCmd : result; const npubs = (data as { npubs?: NpubEntry[] } | undefined)?.npubs ?? []; if (npubs.length === 0) { - console.log( - "No admin npubs configured. Run 'routstrd npubs register' to register yourself as the first admin.", - ); + console.log("No admin npubs configured. Run 'routstrd npubs register' to register yourself as the first admin."); return; } console.log(`Npubs (${npubs.length}):`); @@ -1051,9 +1027,7 @@ npubsCmd npubsCmd .command("register") - .description( - "Register yourself as the first admin (only when no admins exist)", - ) + .description("Register yourself as the first admin (only when no admins exist)") .action(async () => { await ensureDaemonRunning(); const config = await loadConfig(); @@ -1074,9 +1048,7 @@ npubsCmd : result; const npubs = (data as { npubs?: NpubEntry[] } | undefined)?.npubs ?? []; if (npubs.length > 0) { - console.log( - `Admin npubs already configured (${npubs.length}). Ask your admin to add your npub. \n Your npub: ${userNpub}`, - ); + console.log(`Admin npubs already configured (${npubs.length}). Ask your admin to add your npub. \n Your npub: ${userNpub}`); return; } const normalized = normalizeNostrPubkey(userNpub); @@ -1096,9 +1068,7 @@ npubsCmd | { npub?: string; added?: boolean; error?: string } | undefined; if (output?.npub) { - console.log( - `Successfully registered as first admin npub: ${output.npub}`, - ); + console.log(`Successfully registered as first admin npub: ${output.npub}`); } else { console.log(`Successfully registered as first admin npub: ${userNpub}`); } @@ -1106,14 +1076,8 @@ npubsCmd npubsCmd .command("add ") - .description( - "Add a npub (hex pubkey or npub1...). Defaults to 'user' role unless --role is specified.", - ) - .option( - "-r, --role ", - "Role for the npub: 'admin' or 'user' (default: 'user')", - "user", - ) + .description("Add a npub (hex pubkey or npub1...). Defaults to 'user' role unless --role is specified.") + .option("-r, --role ", "Role for the npub: 'admin' or 'user' (default: 'user')", "user") .action(async (npubArg: string, options: { role: string }) => { await ensureDaemonRunning(); const normalized = normalizeNostrPubkey(npubArg); @@ -1125,10 +1089,7 @@ npubsCmd console.error("Invalid role. Expected 'admin' or 'user'."); process.exit(1); } - const body: Record = { - npub: npubFromPubkey(normalized), - role: options.role, - }; + const body: Record = { npub: npubFromPubkey(normalized), role: options.role }; const result = await callAuth("/npubs", { method: "POST", body, @@ -1445,10 +1406,7 @@ const nwcCmd = program nwcCmd .command("connect") .description("Connect to a Lightning wallet via NWC") - .argument( - "[connection-string]", - "NWC connection string (nostr+walletconnect://...)", - ) + .argument("[connection-string]", "NWC connection string (nostr+walletconnect://...)") .action(async (connectionString?: string) => { if (!connectionString) { // Interactive mode: prompt for connection string @@ -1465,14 +1423,8 @@ nwcCmd } // Quick validation: must be nostr+walletconnect:// with a 64-char hex pubkey - if ( - !/^nostr\+walletconnect:\/\/[0-9a-fA-F]{64}\?relay=/.test( - connectionString, - ) - ) { - console.error( - "Invalid NWC connection string: expected nostr+walletconnect://<64-char-hex>?relay=...", - ); + if (!/^nostr\+walletconnect:\/\/[0-9a-fA-F]{64}\?relay=/.test(connectionString)) { + console.error("Invalid NWC connection string: expected nostr+walletconnect://<64-char-hex>?relay=..."); process.exit(1); } @@ -1527,27 +1479,21 @@ autoRefillCmd "Minimum time between refills in seconds", "300", ) - .action( - async (options: { - threshold: string; - amount: string; - cooldown: string; - }) => { - const threshold = parsePositiveIntOrExit(options.threshold, "threshold"); - const amount = parsePositiveIntOrExit(options.amount, "amount"); - const cooldownSec = parsePositiveIntOrExit(options.cooldown, "cooldown"); + .action(async (options: { threshold: string; amount: string; cooldown: string }) => { + const threshold = parsePositiveIntOrExit(options.threshold, "threshold"); + const amount = parsePositiveIntOrExit(options.amount, "amount"); + const cooldownSec = parsePositiveIntOrExit(options.cooldown, "cooldown"); - await handleDaemonCommand("/nwc/auto-refill", { - method: "POST", - body: { - enabled: true, - threshold, - amount, - cooldownMs: cooldownSec * 1000, - }, - }); - }, - ); + await handleDaemonCommand("/nwc/auto-refill", { + method: "POST", + body: { + enabled: true, + threshold, + amount, + cooldownMs: cooldownSec * 1000, + }, + }); + }); autoRefillCmd .command("off") @@ -1836,4 +1782,4 @@ program export function cli(args: string[]) { program.parse(args); -} +} \ No newline at end of file diff --git a/src/daemon/wallet/coco-client.ts b/src/daemon/wallet/coco-client.ts index d515a03..12bc4fe 100644 --- a/src/daemon/wallet/coco-client.ts +++ b/src/daemon/wallet/coco-client.ts @@ -60,11 +60,15 @@ export async function createCocoClient(): Promise { }, async getStatus(): Promise { + // coco-core has no lock/unlock concept, the wallet is always + // ready once initialized. Return UNLOCKED to satisfy the interface. return "UNLOCKED"; }, async unlock(_passphrase: string): Promise { - return "already unlocked"; + // coco-core does not support passphrase locking. + // Wallet access is controlled via the mnemonic in ~/.cocod/config.json. + return "wallet does not require unlocking"; }, async getBalances(): Promise> {