mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
fix: improve zap payment robustness and debugging
Addresses payment hanging issues by adding comprehensive error handling, timeout protection, and detailed logging throughout the zap flow. Payment improvements: - Add 60-second timeout to prevent indefinite hanging on wallet.payInvoice() - Validate invoice format before attempting payment (must start with 'lnbc') - Better error messages for timeout, insufficient balance, and invalid invoices - Log wallet state changes for debugging connectivity issues Invoice generation improvements: - Add detailed console logging at each step of the LNURL flow - Log account selection, endpoint fetching, signing, and invoice retrieval - Better error messages when LNURL endpoint fails or doesn't support zaps - Log LNURL error responses for debugging Debugging enhancements: - Track wallet connection state (hasWallet, balance) via useEffect - Log all major operations with [ZapViewer] prefix for easy filtering - Include relevant details in logs (amounts, endpoints, pubkey prefixes) These changes make it easier to diagnose issues with: - Wallet connection problems - LNURL endpoint failures - Signing issues - Network timeouts
This commit is contained in:
@@ -88,6 +88,15 @@ export function ZapViewer({ pubkey, eventId, address }: ZapViewerProps) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Debug: Log wallet state changes
|
||||
useEffect(() => {
|
||||
console.log("[ZapViewer] Wallet state:", {
|
||||
hasWallet: !!wallet,
|
||||
balance: balance,
|
||||
balanceInSats: balance ? Math.floor(balance / 1000) : 0,
|
||||
});
|
||||
}, [wallet, balance]);
|
||||
|
||||
// Get sorted presets (most frequent first, then defaults)
|
||||
const sortedPresets = useMemo(() => {
|
||||
const historyAmounts = amountHistory
|
||||
@@ -134,23 +143,30 @@ export function ZapViewer({ pubkey, eventId, address }: ZapViewerProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[ZapViewer] Generating invoice for", lud16, "amount:", amount);
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const amountMsats = Number(amount) * 1000;
|
||||
console.log("[ZapViewer] Amount in msats:", amountMsats);
|
||||
|
||||
// Get active account for signing zap request
|
||||
const account = accountManager.active;
|
||||
if (!account) {
|
||||
throw new Error("No active account. Please login first.");
|
||||
}
|
||||
console.log("[ZapViewer] Using account:", account.pubkey.slice(0, 8));
|
||||
|
||||
// Get zap endpoint from LNURL
|
||||
console.log("[ZapViewer] Fetching zap endpoint from LNURL...");
|
||||
const zapEndpoint = await getZapEndpoint(lud16);
|
||||
if (!zapEndpoint) {
|
||||
throw new Error("Failed to fetch LNURL endpoint");
|
||||
throw new Error(
|
||||
"Failed to fetch LNURL endpoint. The recipient may not support zaps.",
|
||||
);
|
||||
}
|
||||
console.log("[ZapViewer] Zap endpoint:", zapEndpoint);
|
||||
|
||||
// Create zap request
|
||||
const relays = state.activeAccount?.relays?.map((r) => r.url) || [
|
||||
@@ -180,8 +196,10 @@ export function ZapViewer({ pubkey, eventId, address }: ZapViewerProps) {
|
||||
}
|
||||
|
||||
// Sign the zap request
|
||||
console.log("[ZapViewer] Signing zap request...");
|
||||
const signedZapRequest =
|
||||
await account.signer.signEvent(zapRequestTemplate);
|
||||
console.log("[ZapViewer] Zap request signed:", signedZapRequest.id);
|
||||
|
||||
// Request invoice from LNURL endpoint
|
||||
const params = new URLSearchParams({
|
||||
@@ -189,12 +207,17 @@ export function ZapViewer({ pubkey, eventId, address }: ZapViewerProps) {
|
||||
nostr: JSON.stringify(signedZapRequest),
|
||||
});
|
||||
|
||||
console.log("[ZapViewer] Requesting invoice from LNURL...");
|
||||
const response = await fetch(`${zapEndpoint}?${params.toString()}`);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("[ZapViewer] LNURL error response:", errorText);
|
||||
throw new Error(`LNURL request failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("[ZapViewer] LNURL response:", data);
|
||||
|
||||
if (data.status === "ERROR") {
|
||||
throw new Error(data.reason || "LNURL error");
|
||||
}
|
||||
@@ -203,6 +226,10 @@ export function ZapViewer({ pubkey, eventId, address }: ZapViewerProps) {
|
||||
throw new Error("No invoice returned from LNURL endpoint");
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[ZapViewer] Invoice received:",
|
||||
data.pr.slice(0, 20) + "...",
|
||||
);
|
||||
setInvoice(data.pr);
|
||||
|
||||
// Generate QR code
|
||||
@@ -230,21 +257,59 @@ export function ZapViewer({ pubkey, eventId, address }: ZapViewerProps) {
|
||||
|
||||
// Pay with NWC
|
||||
const handlePayWithWallet = async () => {
|
||||
if (!wallet || !invoice) return;
|
||||
if (!wallet || !invoice) {
|
||||
console.error("[ZapViewer] Missing wallet or invoice");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[ZapViewer] Starting payment with wallet");
|
||||
setIsLoading(true);
|
||||
setPaymentStatus("pending");
|
||||
setError("");
|
||||
|
||||
try {
|
||||
await payInvoice(invoice);
|
||||
// Validate invoice format (basic check)
|
||||
if (!invoice.toLowerCase().startsWith("lnbc")) {
|
||||
throw new Error("Invalid invoice format (must start with lnbc)");
|
||||
}
|
||||
|
||||
console.log("[ZapViewer] Calling payInvoice...");
|
||||
|
||||
// Add timeout wrapper to prevent hanging
|
||||
const paymentPromise = payInvoice(invoice);
|
||||
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error("Payment timeout after 60 seconds")),
|
||||
60000,
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.race([paymentPromise, timeoutPromise]);
|
||||
|
||||
console.log("[ZapViewer] Payment successful");
|
||||
setPaymentStatus("success");
|
||||
|
||||
// Start monitoring for zap receipt
|
||||
monitorForZapReceipt();
|
||||
} catch (err) {
|
||||
console.error("Payment failed:", err);
|
||||
setError(err instanceof Error ? err.message : "Payment failed");
|
||||
console.error("[ZapViewer] Payment failed:", err);
|
||||
|
||||
// Provide user-friendly error messages
|
||||
let errorMessage = "Payment failed";
|
||||
if (err instanceof Error) {
|
||||
if (err.message.includes("timeout")) {
|
||||
errorMessage =
|
||||
"Payment timed out. Please try again or check your wallet.";
|
||||
} else if (err.message.includes("insufficient")) {
|
||||
errorMessage = "Insufficient balance in wallet.";
|
||||
} else if (err.message.includes("Invalid")) {
|
||||
errorMessage = err.message;
|
||||
} else {
|
||||
errorMessage = `Payment failed: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
setError(errorMessage);
|
||||
setPaymentStatus("failed");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
Reference in New Issue
Block a user