128 lines
5.0 KiB
JavaScript
Raw Normal View History

2024-09-16 17:13:23 -05:00
import axios from "axios";
import { finalizeEvent } from 'nostr-tools/pure';
import { SimplePool } from 'nostr-tools/pool';
2024-09-18 14:59:04 -05:00
import appConfig from "@/config/appConfig";
import { getLightningAddressByName } from "@/db/models/lightningAddressModels";
2024-09-16 17:13:23 -05:00
2024-09-18 14:59:04 -05:00
const ZAP_PRIVKEY = process.env.ZAP_PRIVKEY;
2024-10-02 17:27:38 -05:00
const PLEBDEVS_API_KEY = process.env.PLEBDEVS_API_KEY;
const BACKEND_URL = process.env.BACKEND_URL;
async function pollPaymentStatus(baseUrl, name, paymentHash, maxAttempts = 300, interval = 1000) {
for (let i = 0; i < maxAttempts; i++) {
2024-11-06 18:02:08 -06:00
console.log(`Polling payment status for ${name}... (${i}/${maxAttempts})`);
try {
const response = await axios.get(`${baseUrl}/api/lightning-address/verify/${name}/${paymentHash}`);
if (response.data.status === "OK" && response.data.settled) {
return true;
}
await new Promise(resolve => setTimeout(resolve, interval));
} catch (error) {
console.error('Error polling payment status:', error.message);
}
}
return false;
}
2024-09-16 17:13:23 -05:00
export default async function handler(req, res) {
2024-10-02 17:27:38 -05:00
// make sure api key is in authorization header
const apiKey = req.headers['authorization'];
2024-10-02 17:31:57 -05:00
if (!apiKey || apiKey !== PLEBDEVS_API_KEY) {
2024-10-02 17:27:38 -05:00
res.status(401).json({ error: 'Unauthorized' });
return;
}
2024-09-16 17:13:23 -05:00
try {
2024-09-18 14:59:04 -05:00
const { amount, description_hash, zap_request=null, name } = req.body;
// Find the custom lightning address
let foundAddress = null;
2024-09-18 14:59:04 -05:00
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === name);
if (customAddress) {
foundAddress = customAddress;
} else {
foundAddress = await getLightningAddressByName(name);
}
if (!foundAddress) {
2024-09-18 14:59:04 -05:00
res.status(404).json({ error: 'Lightning address not found' });
return;
}
// Check if amount is within allowed range
const minSendable = foundAddress.minSendable || appConfig.defaultMinSendable || 1;
const maxSendable = foundAddress.maxSendable || appConfig.defaultMaxSendable || Number.MAX_SAFE_INTEGER;
2024-09-18 14:59:04 -05:00
if (amount < minSendable || amount > maxSendable) {
res.status(400).json({ error: 'Amount out of allowed range' });
return;
}
// Check if the custom address allows zaps
if (zap_request && !foundAddress.allowsNostr) {
2024-09-18 14:59:04 -05:00
res.status(400).json({ error: 'Nostr zaps not allowed for this address' });
return;
}
2024-11-06 17:10:19 -06:00
const response = await axios.post(`https://${foundAddress.lndHost}:${foundAddress.lndPort}/v1/invoices`, {
2024-09-18 14:59:04 -05:00
value_msat: amount,
description_hash: description_hash
2024-09-16 17:13:23 -05:00
}, {
headers: {
'Grpc-Metadata-macaroon': foundAddress.invoiceMacaroon,
2024-09-16 17:13:23 -05:00
}
});
const invoice = response.data.payment_request;
2024-11-06 17:24:36 -06:00
const paymentHash = Buffer.from(response.data.r_hash, 'base64');
2024-11-06 17:17:25 -06:00
const paymentHashHex = paymentHash.toString('hex');
2024-09-16 17:13:23 -05:00
// If this is a zap, wait for payment and then publish a zap receipt
if (zap_request && foundAddress.allowsNostr) {
2024-09-18 14:59:04 -05:00
const zapRequest = JSON.parse(zap_request);
2024-09-16 17:13:23 -05:00
const zapReceipt = {
kind: 9735,
created_at: Math.floor(Date.now() / 1000),
content: foundAddress.zapMessage || appConfig.defaultZapMessage || '',
2024-09-16 17:13:23 -05:00
tags: [
['p', zapRequest.pubkey],
['e', zapRequest.id],
['bolt11', invoice],
['description', JSON.stringify(zapRequest)]
]
};
// Start payment polling in the background
const pollPromise = pollPaymentStatus(BACKEND_URL, name, paymentHashHex);
// Send the response immediately
res.status(200).json({ invoice, payment_hash: paymentHashHex });
2024-09-16 17:13:23 -05:00
// Wait for payment to settle
const isSettled = await pollPromise;
2024-11-06 18:02:08 -06:00
console.log("Payment settled??", isSettled);
if (isSettled) {
const signedZapReceipt = finalizeEvent(zapReceipt, foundAddress.relayPrivkey || ZAP_PRIVKEY);
// Publish zap receipt to relays
const pool = new SimplePool();
const relays = foundAddress.defaultRelays || appConfig.defaultRelayUrls || [];
await Promise.any(pool.publish(relays, signedZapReceipt));
console.log("ZAP RECEIPT PUBLISHED", signedZapReceipt);
} else {
console.log("Payment not settled after 60 seconds, skipping zap receipt");
}
return;
2024-09-16 17:13:23 -05:00
}
// For non-zap requests, send response immediately
2024-11-06 17:17:25 -06:00
res.status(200).json({ invoice, payment_hash: paymentHashHex });
2024-09-16 17:13:23 -05:00
} catch (error) {
console.error('Error (server) fetching data from LND:', error.message);
res.status(500).json({ message: 'Error fetching data' });
}
}