mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-06 04:38:23 +00:00
feat(cli): wait for the payment after receive <amount> (#95)
This commit is contained in:
+38
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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...`);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user