feat(cli): wait for the payment after receive <amount> (#95)

This commit is contained in:
Ashen
2026-09-08 11:35:47 +00:00
committed by GitHub
parent be374601d8
commit 53b5a74db4
6 changed files with 90 additions and 10 deletions
+38 -1
View File
@@ -106,6 +106,40 @@ async function printLightningInvoice(invoice: string): Promise<void> {
console.log(`${qr}\nInvoice:\n${invoice}`);
}
/** Block until the daemon has minted the paid invoice, then show the credit. */
async function waitForMintQuote(operationId: string): Promise<void> {
console.log(
"\nWaiting for payment... (Ctrl+C stops waiting; the daemon keeps checking)",
);
for (;;) {
const result = await callDaemon(
`/wallet/receive/bolt11/${encodeURIComponent(operationId)}`,
);
const quote = result.output as
| { state?: string; amount?: number; error?: string }
| undefined;
if (quote?.state === "finalized" && quote.error) {
// The mint issued the quote but the wallet could not restore the proofs.
console.error(`Payment reached the mint but could not be credited: ${quote.error}`);
process.exit(1);
}
if (quote?.state === "finalized") {
const balance = await callDaemon("/wallet/balance");
const balances =
(balance.output as { balances?: Record<string, number> } | undefined)
?.balances ?? {};
const total = Object.values(balances).reduce((sum, sats) => sum + sats, 0);
console.log(`Received ${quote.amount} sat. Balance: ${total} sat`);
return;
}
if (quote?.state === "failed") {
console.error(`Invoice failed: ${quote.error ?? "unknown reason"}`);
process.exit(1);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
export function initializeWallet(walletDir = defaultWalletDir()): void {
const walletConfig = join(walletDir, "config.json");
@@ -1728,11 +1762,14 @@ program
});
const output = result.output as
| { invoice?: string; amount?: number; mintUrl?: string }
| { invoice?: string; operationId?: string; amount?: number; mintUrl?: string }
| undefined;
if (typeof output?.invoice === "string" && output.invoice) {
await printLightningInvoice(output.invoice);
if (typeof output.operationId === "string") {
await waitForMintQuote(output.operationId);
}
return;
}
+16 -2
View File
@@ -488,8 +488,22 @@ export function createDaemonRequestHandler(deps: {
const body = await readJsonBody(req);
const amount = getRequiredPositiveNumberField(body, "amount");
const mintUrl = optionalStringField(body, "mintUrl");
const invoice = await deps.walletClient.receiveBolt11(amount, mintUrl);
return { output: { invoice, amount, mintUrl } };
const { invoice, operationId } = await deps.walletClient.receiveBolt11(
amount,
mintUrl,
);
return { output: { invoice, operationId, amount, mintUrl } };
});
return;
}
const mintQuoteMatch = url.pathname.match(/^\/wallet\/receive\/bolt11\/([^/]+)$/);
if (req.method === "GET" && mintQuoteMatch?.[1]) {
const operationId = decodeURIComponent(mintQuoteMatch[1]);
await respond(res, async () => {
const quote = await deps.walletClient.getMintQuote?.(operationId);
if (!quote) throw new CocodHttpError(404, "Mint quote not found");
return { output: quote };
});
return;
}
+1 -1
View File
@@ -104,7 +104,7 @@ export function startAutoRefillLoop(
logger.log(
`[auto-refill] Creating BOLT-11 invoice for ${config.amount} sats via ${mintUrl}...`,
);
const invoice = await cocod.receiveBolt11(config.amount, mintUrl);
const { invoice } = await cocod.receiveBolt11(config.amount, mintUrl);
// Step 2: Pay the invoice via NWC (applesauce)
logger.log(`[auto-refill] Paying invoice via NWC...`);
+15 -2
View File
@@ -1383,7 +1383,7 @@ export async function createCocoClient(
}
},
async receiveBolt11(amount: number, mintUrl?: string): Promise<string> {
async receiveBolt11(amount: number, mintUrl?: string) {
await waitForRecovery();
const targetMint = mintUrl
? normalizeMintUrl(mintUrl)
@@ -1399,7 +1399,20 @@ export async function createCocoClient(
if (!("request" in op)) {
throw new Error("mint prepare did not return a payment request");
}
return op.request as string;
return { invoice: op.request as string, operationId: op.id };
},
async getMintQuote(operationId: string) {
const op = await coco.ops.mint.get(operationId);
if (!op || op.state === "init") return null;
return {
operationId: op.id,
state: op.state,
mintState: op.lastObservedRemoteState,
amount: op.amount,
mintUrl: op.mintUrl,
error: op.error,
};
},
async sendCashu(amount: number, mintUrl?: string): Promise<string> {
+19 -3
View File
@@ -109,13 +109,29 @@ export class CocodHttpError extends Error {
}
}
/** Progress of a Lightning top-up created with `receiveBolt11`. */
export interface MintQuoteStatus {
operationId: string;
state: "pending" | "executing" | "finalized" | "failed";
/** Last quote state reported by the mint (UNPAID, PAID, ISSUED). */
mintState?: string;
amount: number;
mintUrl: string;
error?: string;
}
export interface CocodClient {
ping(): Promise<boolean>;
getStatus(): Promise<CocodState>;
unlock(passphrase: string): Promise<string>;
getBalances(): Promise<Record<string, number>>;
receiveCashu(token: string): Promise<string>;
receiveBolt11(amount: number, mintUrl?: string): Promise<string>;
receiveBolt11(
amount: number,
mintUrl?: string,
): Promise<{ invoice: string; operationId?: string }>;
/** Progress of a top-up, when the wallet tracks mint operations. */
getMintQuote?(operationId: string): Promise<MintQuoteStatus | null>;
sendCashu(amount: number, mintUrl?: string): Promise<string>;
sendBolt11(invoice: string, mintUrl?: string): Promise<string>;
listMints(): Promise<string[]>;
@@ -421,8 +437,8 @@ export function createCocodClient(
logger.debug(`[receiveCashu] Full response.output:`, message);
return message;
},
async receiveBolt11(amount: number, mintUrl?: string): Promise<string> {
return post<string>("/receive/bolt11", { amount, mintUrl });
async receiveBolt11(amount: number, mintUrl?: string) {
return { invoice: await post<string>("/receive/bolt11", { amount, mintUrl }) };
},
async sendCashu(amount: number, mintUrl?: string): Promise<string> {
return post<string>("/send/cashu", { amount, mintUrl });
+1 -1
View File
@@ -189,7 +189,7 @@ export async function createWalletAdapter(
// Step 2: Create a BOLT-11 invoice via cocod
logger.log(`[nwc] Creating ${amount}-sat Lightning invoice via cocod...`);
const invoice = await client.receiveBolt11(amount, mintUrl);
const { invoice } = await client.receiveBolt11(amount, mintUrl);
logger.log(`[nwc] Invoice: ${invoice}`);
// Step 3: Pay it via NWC