You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

184 lines
5.4 KiB

<script lang="ts">
import { sessionManager } from '../../services/auth/session-manager.js';
import { nostrClient } from '../../services/nostr/nostr-client.js';
import type { NostrEvent } from '../../types/nostr.js';
import { KIND } from '../../types/kind-lookup.js';
import ZapInvoiceModal from './ZapInvoiceModal.svelte';
import Icon from '../../components/ui/Icon.svelte';
interface Props {
event: NostrEvent;
pubkey?: string; // Optional: zap a specific pubkey (for profile zaps)
}
let { event, pubkey }: Props = $props();
let showInvoiceModal = $state(false);
let invoice = $state<string | null>(null);
let lnurl = $state<string | null>(null);
let amount = $state<number>(1000); // Default 1000 sats
const targetPubkey = $derived(pubkey || event.pubkey);
async function handleZap() {
if (!sessionManager.isLoggedIn()) {
alert('Please log in to zap');
return;
}
try {
// Fetch profile to get lud16 or lnurl
const config = nostrClient.getConfig();
const profileEvents = await nostrClient.fetchEvents(
[{ kinds: [KIND.METADATA], authors: [targetPubkey], limit: 1 }],
[...config.defaultRelays, ...config.profileRelays],
{ useCache: true }
);
let zapRequest: NostrEvent | null = null;
if (profileEvents.length > 0) {
const profile = profileEvents[0];
// Extract lud16 from profile tags
const lud16Tag = profile.tags.find((t) => t[0] === 'lud16');
const lud16 = lud16Tag?.[1];
if (lud16) {
// Create zap request
const tags: string[][] = [
['relays', ...config.defaultRelays],
['amount', amount.toString()],
['lnurl', lud16],
['p', targetPubkey]
];
if (event.kind !== KIND.METADATA) {
// Zap to an event, not just a profile
tags.push(['e', event.id]);
tags.push(['k', event.kind.toString()]);
}
const currentPubkey = sessionManager.getCurrentPubkey()!;
const zapRequestEvent = {
kind: 9734,
pubkey: currentPubkey,
created_at: Math.floor(Date.now() / 1000),
tags,
content: 'Zap!'
};
const signed = await sessionManager.signEvent(zapRequestEvent);
zapRequest = signed;
// Try to send via lightning: URI (primary method)
const lightningUri = `lightning:${lud16}?amount=${amount}&nostr=${encodeURIComponent(JSON.stringify(zapRequest))}`;
try {
window.location.href = lightningUri;
return; // Success, wallet should handle it
} catch (error) {
console.log('lightning: URI not supported, falling back to lnurl');
}
// Fallback: Fetch invoice from lnurl
await fetchInvoiceFromLnurl(lud16, zapRequest);
} else {
alert('User has no lightning address configured');
}
} else {
alert('Could not fetch user profile');
}
} catch (error) {
console.error('Error creating zap:', error);
alert('Error creating zap');
}
}
async function fetchInvoiceFromLnurl(lud16: string, zapRequest: NostrEvent) {
try {
// Parse lud16 (format: user@domain.com)
const [username, domain] = lud16.split('@');
const callbackUrl = `https://${domain}/.well-known/lnurlp/${username}`;
// Fetch lnurlp
const response = await fetch(callbackUrl);
const data = await response.json();
if (data.callback) {
// Create zap request JSON
const zapRequestJson = JSON.stringify(zapRequest);
// Call callback with zap request
const callbackUrlWithParams = `${data.callback}?amount=${amount * 1000}&nostr=${encodeURIComponent(zapRequestJson)}`;
const invoiceResponse = await fetch(callbackUrlWithParams);
const invoiceData = await invoiceResponse.json();
if (invoiceData.pr) {
invoice = invoiceData.pr;
lnurl = lud16;
showInvoiceModal = true;
} else {
alert('Failed to get invoice from wallet');
}
}
} catch (error) {
console.error('Error fetching invoice:', error);
alert('Error fetching invoice from wallet');
}
}
</script>
<button
onclick={handleZap}
class="zap-button"
data-zap-button
title="Zap (z)"
aria-label="Zap"
>
<Icon name="zap" size={16} />
<span>Zap</span>
</button>
{#if showInvoiceModal && invoice}
<ZapInvoiceModal
{invoice}
{lnurl}
{amount}
onClose={() => {
showInvoiceModal = false;
invoice = null;
}}
/>
{/if}
<style>
.zap-button {
padding: 0.25rem 0.75rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
line-height: 1.5;
display: inline-flex;
align-items: center;
gap: 0.375rem;
}
:global(.dark) .zap-button {
background: var(--fog-dark-post, #1f2937);
border-color: var(--fog-dark-border, #374151);
color: var(--fog-dark-text, #f9fafb);
}
.zap-button:hover {
background: var(--fog-highlight, #f3f4f6);
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .zap-button:hover {
background: var(--fog-dark-highlight, #374151);
}
</style>