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
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>
|
|
|