2024-09-16 17:13:23 -05:00
|
|
|
import axios from "axios";
|
2024-09-18 14:59:04 -05:00
|
|
|
import appConfig from "@/config/appConfig";
|
2024-09-26 17:44:24 -05:00
|
|
|
import { getLightningAddressByName } from "@/db/models/lightningAddressModels";
|
2024-11-07 14:34:03 -06:00
|
|
|
import { kv } from '@vercel/kv';
|
2024-09-16 17:13:23 -05:00
|
|
|
|
2024-10-02 17:27:38 -05:00
|
|
|
const PLEBDEVS_API_KEY = process.env.PLEBDEVS_API_KEY;
|
2024-11-06 17:39:57 -06:00
|
|
|
const BACKEND_URL = process.env.BACKEND_URL;
|
|
|
|
|
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
|
2024-09-26 17:44:24 -05:00
|
|
|
let foundAddress = null;
|
2024-09-18 14:59:04 -05:00
|
|
|
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === name);
|
|
|
|
|
2024-09-26 17:44:24 -05:00
|
|
|
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
|
2024-09-26 17:44:24 -05:00
|
|
|
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
|
2024-09-26 17:44:24 -05:00
|
|
|
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: {
|
2024-09-26 17:44:24 -05:00
|
|
|
'Grpc-Metadata-macaroon': foundAddress.invoiceMacaroon,
|
2024-09-16 17:13:23 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const invoice = response.data.payment_request;
|
2024-11-07 14:34:03 -06:00
|
|
|
const expiry = response.data.expiry;
|
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
|
|
|
|
2024-11-07 14:34:03 -06:00
|
|
|
// If this is a zap, store verification URL and zap request in Redis
|
2024-09-26 17:44:24 -05:00
|
|
|
if (zap_request && foundAddress.allowsNostr) {
|
2024-09-18 14:59:04 -05:00
|
|
|
const zapRequest = JSON.parse(zap_request);
|
2024-11-07 14:34:03 -06:00
|
|
|
const verifyUrl = `${BACKEND_URL}/api/lightning-address/verify/${name}/${paymentHashHex}`;
|
|
|
|
|
|
|
|
// Store in Redis with 24-hour expiration
|
|
|
|
await kv.set(`invoice:${paymentHashHex}`, {
|
|
|
|
verifyUrl,
|
|
|
|
zapRequest,
|
|
|
|
name,
|
|
|
|
invoice,
|
|
|
|
foundAddress,
|
|
|
|
settled: false
|
|
|
|
}, { ex: expiry || 86400 }); // expiry matches invoice expiry
|
|
|
|
|
2024-11-08 10:55:01 -06:00
|
|
|
// Start polling for this zap request
|
|
|
|
let attempts = 0;
|
|
|
|
const pollInterval = setInterval(async () => {
|
2024-11-08 11:01:25 -06:00
|
|
|
console.log('Polling for invoice', attempts);
|
2024-11-08 10:55:01 -06:00
|
|
|
try {
|
|
|
|
const pollResponse = await axios.get(`${BACKEND_URL}/api/invoices/polling`, {
|
|
|
|
headers: {
|
|
|
|
'Authorization': PLEBDEVS_API_KEY
|
|
|
|
}
|
|
|
|
});
|
2024-11-08 11:01:25 -06:00
|
|
|
console.log('Polling response', pollResponse.data);
|
2024-11-08 10:55:01 -06:00
|
|
|
|
|
|
|
// If no pending invoices or we've reached max attempts, stop polling
|
|
|
|
if (pollResponse.data.pending === 0 || attempts >= 120) {
|
|
|
|
clearInterval(pollInterval);
|
|
|
|
}
|
|
|
|
|
|
|
|
attempts++;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Polling error:', error);
|
|
|
|
clearInterval(pollInterval);
|
|
|
|
}
|
|
|
|
}, 1000); // Poll every second
|
|
|
|
|
2024-11-07 14:34:03 -06:00
|
|
|
res.status(200).json({
|
|
|
|
invoice,
|
|
|
|
payment_hash: paymentHashHex,
|
|
|
|
verify_url: verifyUrl
|
|
|
|
});
|
2024-11-06 17:39:57 -06:00
|
|
|
return;
|
2024-09-16 17:13:23 -05:00
|
|
|
}
|
|
|
|
|
2024-11-06 17:39:57 -06: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' });
|
|
|
|
}
|
|
|
|
}
|