#!/usr/bin/env node // Sendet einen echten NIP-57-Zap (kind 9734, mit meinem Key signiert), // bezahlt ueber coinos (geld/coinos.js-Token). Der Empfaenger sieht einen // Zap von "kiel", nicht eine anonyme LN-Zahlung. // Nutzung: node nostr/zap-send.js [event-id-hex] [kommentar] const fs = require('fs'); const path = require('path'); const { finalizeEvent, nip19 } = require('nostr-tools'); const { useWebSocketImplementation, SimplePool } = require('nostr-tools/pool'); try { useWebSocketImplementation(require('ws')); } catch {} const { RELAYS } = require('./lib'); const key = JSON.parse(fs.readFileSync(path.join(__dirname, 'nostr-key.json'), 'utf8')); const sk = Uint8Array.from(Buffer.from(key.sk_hex, 'hex')); const TOKENFILE = path.join(__dirname, '../var/coinos-token.json'); (async () => { let [target, satsStr, eventId, ...commentParts] = process.argv.slice(2); const sats = parseInt(satsStr, 10); if (!target || !sats) { console.error('Nutzung: zap-send.js [event-id] [kommentar]'); process.exit(1); } if (target.startsWith('npub')) target = nip19.decode(target).data; const comment = commentParts.join(' '); const pool = new SimplePool(); const profs = await pool.querySync(RELAYS, { kinds: [0], authors: [target] }); if (!profs.length) { console.error('Kein kind-0 gefunden fuer', target); process.exit(1); } const meta = JSON.parse(profs.sort((a, b) => b.created_at - a.created_at)[0].content); const lud16 = meta.lud16; if (!lud16 || !lud16.includes('@')) { console.error('Keine lud16 im Profil:', JSON.stringify({ lud16: meta.lud16, lud06: meta.lud06 })); process.exit(1); } const [name, domain] = lud16.split('@'); console.log(`Ziel: ${meta.name || target.slice(0, 8)} <${lud16}>`); const lp = await (await fetch(`https://${domain}/.well-known/lnurlp/${name}`)).json(); if (!lp.callback) { console.error('LNURL-Fehler:', JSON.stringify(lp).slice(0, 200)); process.exit(1); } if (!lp.allowsNostr) console.log('WARNUNG: Server meldet kein allowsNostr — wird normale LN-Zahlung.'); const msats = sats * 1000; const zapReq = finalizeEvent({ kind: 9734, created_at: Math.floor(Date.now() / 1000), tags: [ ['p', target], ...(eventId ? [['e', eventId]] : []), ['amount', String(msats)], ['relays', ...RELAYS.slice(0, 6)], ], content: comment || '', }, sk); const url = `${lp.callback}${lp.callback.includes('?') ? '&' : '?'}amount=${msats}&nostr=${encodeURIComponent(JSON.stringify(zapReq))}`; const inv = await (await fetch(url)).json(); if (!inv.pr) { console.error('Keine Invoice:', JSON.stringify(inv).slice(0, 200)); process.exit(1); } const { token } = JSON.parse(fs.readFileSync(TOKENFILE, 'utf8')); const r = await fetch('https://coinos.io/api/payments', { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` }, body: JSON.stringify({ payreq: inv.pr }), }); const res = await r.json(); if (!r.ok) { console.error('Zahlung fehlgeschlagen:', JSON.stringify(res).slice(0, 200)); process.exit(1); } console.log(`GEZAPPT: ${sats} sats an ${lud16}${eventId ? ' (Event ' + eventId.slice(0, 8) + ')' : ''}, fee ${res.fee}, coinos-id ${res.id}`); process.exit(0); })().catch((e) => { console.error('FEHLER:', e.message); process.exit(1); });