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.
 
 
 
 
 

154 lines
4.6 KiB

<script lang="ts">
import { nostrClient } from '../../services/nostr/nostr-client.js';
import { fetchProfile } from '../../services/user-data.js';
import { onMount } from 'svelte';
import type { NostrEvent } from '../../types/nostr.js';
import { KIND } from '../../types/kind-lookup.js';
import Icon from '../../components/ui/Icon.svelte';
interface Props {
pubkey: string;
}
let { pubkey }: Props = $props();
let paymentAddresses = $state<Array<{ type: string; address: string }>>([]);
let paymentEvent = $state<NostrEvent | null>(null);
let loading = $state(true);
const recognizedTypes = ['bitcoin', 'lightning', 'ethereum', 'nano', 'monero', 'cashme', 'revolut', 'venmo'];
onMount(async () => {
await nostrClient.initialize();
loadPaymentAddresses();
});
async function loadPaymentAddresses() {
loading = true;
try {
const config = nostrClient.getConfig();
// Fetch kind 10133 (payment targets)
const paymentEvents = await nostrClient.fetchEvents(
[{ kinds: [KIND.PAYMENT_ADDRESSES], authors: [pubkey], limit: 1 }],
[...config.defaultRelays, ...config.profileRelays],
{ useCache: true, cacheResults: true }
);
const addresses: Array<{ type: string; address: string }> = [];
const seen = new Set<string>();
// Extract from kind 10133
if (paymentEvents.length > 0) {
paymentEvent = paymentEvents[0];
for (const tag of paymentEvent.tags) {
if (tag[0] === 'payto' && tag[1] && tag[2]) {
const key = `${tag[1]}:${tag[2]}`;
if (!seen.has(key)) {
addresses.push({ type: tag[1], address: tag[2] });
seen.add(key);
}
}
}
}
// Also get lud16 from profile (kind 0)
const profile = await fetchProfile(pubkey);
if (profile && profile.lud16) {
for (const lud16 of profile.lud16) {
const key = `lightning:${lud16}`;
if (!seen.has(key)) {
addresses.push({ type: 'lightning', address: lud16 });
seen.add(key);
}
}
}
paymentAddresses = addresses;
} catch (error) {
console.error('Error loading payment addresses:', error);
} finally {
loading = false;
}
}
function copyAddress(address: string) {
navigator.clipboard.writeText(address);
alert('Address copied to clipboard');
}
function isZappable(type: string): boolean {
return type === 'lightning';
}
function getTypeLabel(type: string): string {
return recognizedTypes.includes(type) ? type.charAt(0).toUpperCase() + type.slice(1) : type;
}
</script>
<div class="payment-addresses">
{#if loading}
<span class="text-sm text-fog-text-light dark:text-fog-dark-text-light">Loading payment addresses...</span>
{:else if paymentAddresses.length > 0 || paymentEvent}
<div class="addresses-list">
<h3 class="text-lg font-semibold mb-2">Payment Addresses</h3>
{#if paymentEvent}
<div class="payment-event-info mb-2">
<span class="text-xs text-fog-text-light dark:text-fog-dark-text-light">
From kind {KIND.PAYMENT_ADDRESSES} event
</span>
</div>
{/if}
{#each paymentAddresses as { type, address }}
<div class="address-item flex items-center gap-2 mb-2">
<span class="text-sm font-medium">{getTypeLabel(type)}:</span>
<code class="text-xs bg-fog-highlight dark:bg-fog-dark-highlight px-2 py-1 rounded">
{address}
</code>
<button
onclick={() => copyAddress(address)}
class="text-xs text-fog-accent dark:text-fog-dark-accent hover:underline"
title="Copy address"
>
Copy
</button>
{#if isZappable(type)}
<a
href="lightning:{address}"
class="text-xs text-fog-accent dark:text-fog-dark-accent hover:underline flex items-center gap-1"
>
<Icon name="zap" size={14} />
<span>Zap</span>
</a>
{/if}
</div>
{/each}
</div>
{/if}
</div>
<style>
.payment-addresses {
margin-top: 1rem;
}
.address-item {
padding: 0.5rem;
background: var(--fog-highlight, #f3f4f6);
border-radius: 0.25rem;
flex-wrap: wrap;
overflow-wrap: break-word;
word-break: break-word;
}
:global(.dark) .address-item {
background: var(--fog-dark-highlight, #374151);
}
code {
font-family: monospace;
word-break: break-all;
overflow-wrap: break-word;
max-width: 100%;
}
</style>