fix: revert cosmetic formatting and document coco-core wallet status

This commit is contained in:
Om Santoshwar
2026-07-13 17:02:22 +08:00
committed by redshift
parent 9ca937d4ad
commit a2219e43a9
2 changed files with 107 additions and 157 deletions
+102 -156
View File
@@ -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<void> {
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<string, number>;
}
| 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<string, number>;
}
}
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 <url>")
.description("Configure a remote daemon URL")
.option(
"--auth-url <authUrl>",
"URL of the auth proxy for management commands (npubs, clients, usage)",
)
.option("--auth-url <authUrl>", "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 <npub>")
.description(
"Add a npub (hex pubkey or npub1...). Defaults to 'user' role unless --role is specified.",
)
.option(
"-r, --role <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>", "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<string, string> = {
npub: npubFromPubkey(normalized),
role: options.role,
};
const body: Record<string, string> = { 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);
}
}
+5 -1
View File
@@ -60,11 +60,15 @@ export async function createCocoClient(): Promise<CocodClient> {
},
async getStatus(): Promise<CocodState> {
// 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<string> {
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<Record<string, number>> {