feat: hot-reload NWC connection (remove restart requirement)

- Add reconnect() method to wallet adapter that tears down old
  WalletConnect/RelayPool and creates a new one on the fly
- Auto-refill loop now uses a getWallet() getter so it always
  references the latest wallet instance after reconnect
- POST /nwc/connect and /nwc/disconnect handlers call reconnect()
  after saving config, no restart needed
- Remove 'Run routstrd restart' messages from CLI connect/disconnect
This commit is contained in:
redshift
2026-05-24 09:22:45 +08:00
parent 5799c5c5b0
commit 99244a8bdb
4 changed files with 59 additions and 11 deletions
-3
View File
@@ -1319,8 +1319,6 @@ nwcCmd
method: "POST",
body: { connectionString },
});
console.log("\nRun 'routstrd restart' to connect to the NWC wallet.");
});
nwcCmd
@@ -1330,7 +1328,6 @@ nwcCmd
await handleDaemonCommand("/nwc/disconnect", {
method: "POST",
});
console.log("\nRun 'routstrd restart' to apply.");
});
nwcCmd
+9 -3
View File
@@ -468,10 +468,12 @@ export function createDaemonRequestHandler(deps: {
};
saveDaemonConfig(config);
// Hot-reload: reconnect the wallet adapter with the new connection string
await deps.walletAdapter.reconnect(connectionString);
return {
output: {
message:
"NWC connection string saved. Restart the daemon to connect.",
message: "NWC connection string saved and connected.",
},
};
});
@@ -488,8 +490,12 @@ export function createDaemonRequestHandler(deps: {
}
}
saveDaemonConfig(config);
// Hot-reload: disconnect the wallet adapter
await deps.walletAdapter.reconnect();
return {
output: { message: "NWC disconnected. Restart the daemon to apply." },
output: { message: "NWC disconnected." },
};
});
return;
+9 -3
View File
@@ -34,7 +34,7 @@ function isFatalError(message: string): boolean {
export function startAutoRefillLoop(
cocod: CocodClient,
wallet: WalletConnect,
getWallet: () => WalletConnect | undefined,
getConfig: () => AutoRefillConfig | undefined,
intervalMs: number = 5000,
): () => void {
@@ -47,7 +47,8 @@ export function startAutoRefillLoop(
async function checkAndRefill(): Promise<void> {
if (!running) return;
if (checkInProgress) return;
if (!wallet.service) {
const wallet = getWallet();
if (!wallet?.service) {
// NWC not connected — nothing to do
return;
}
@@ -108,7 +109,12 @@ export function startAutoRefillLoop(
// Step 2: Pay the invoice via NWC (applesauce)
logger.log(`[auto-refill] Paying invoice via NWC...`);
const { preimage, fees_paid } = await wallet.payInvoice(invoice);
const currentWallet = getWallet();
if (!currentWallet?.service) {
logger.log("[auto-refill] Wallet disconnected during refill check");
return;
}
const { preimage, fees_paid } = await currentWallet.payInvoice(invoice);
// Step 3: The Cashu mint should automatically detect the paid invoice
// and issue tokens. We don't need to explicitly mint here; cocod
+41 -2
View File
@@ -67,6 +67,9 @@ export async function createWalletAdapter(
let wallet: WalletConnect | undefined;
let pool: RelayPool | undefined;
// Getter for the current wallet instance (used by auto-refill loop)
const getWallet = (): WalletConnect | undefined => wallet;
if (options.nwcConnectionString) {
pool = new RelayPool();
wallet = WalletConnect.fromConnectURI(options.nwcConnectionString, { pool });
@@ -84,6 +87,42 @@ export async function createWalletAdapter(
}
const walletAdapter = {
async reconnect(connectionString?: string): Promise<void> {
logger.log(
`[nwc] Reconnecting NWC wallet... ${connectionString ? "new connection string provided" : "disconnecting"}`,
);
// 1. Close existing relay pool connections
if (pool) {
for (const [url] of pool.relays) {
pool.remove(url, true);
}
}
// 2. Update wallet reference
wallet = undefined;
pool = undefined;
// 3. Create new wallet if connection string provided
if (connectionString) {
pool = new RelayPool();
wallet = WalletConnect.fromConnectURI(connectionString, { pool });
// Connect in background (non-blocking)
wallet.waitForService()
.then(() => {
logger.log(
`[nwc] NWC wallet reconnected. Relay: ${wallet!.relays[0]}, Service: ${wallet!.service}`,
);
})
.catch((err) => {
logger.error(`[nwc] NWC reconnection failed: ${err.message}`);
});
} else {
logger.log("[nwc] NWC wallet disconnected.");
}
},
async getBalances(): Promise<Record<string, number>> {
return syncMintState();
},
@@ -278,14 +317,14 @@ export async function createWalletAdapter(
if (autoRefillConfig && wallet) {
const getConfig = options.getAutoRefillConfig ?? (() => options.autoRefill);
stopAutoRefill = startAutoRefillLoop(client, wallet, getConfig);
stopAutoRefill = startAutoRefillLoop(client, getWallet, getConfig);
logger.log(
`[wallet] Auto-refill enabled: threshold=${autoRefillConfig.threshold} sats, amount=${autoRefillConfig.amount} sats, cooldown=${autoRefillConfig.cooldownMs}ms`,
);
} else if (wallet && options.getAutoRefillConfig) {
// Wallet exists but auto-refill is not currently enabled.
// Start the loop anyway so it can pick up changes without a restart.
stopAutoRefill = startAutoRefillLoop(client, wallet, options.getAutoRefillConfig);
stopAutoRefill = startAutoRefillLoop(client, getWallet, options.getAutoRefillConfig);
logger.log("[wallet] Auto-refill loop started (currently disabled — enable via CLI to activate)");
}