Lightning address works from db for subscribed users and from the app config

This commit is contained in:
austinkelsay 2024-09-26 17:44:24 -05:00
parent d13ddfcaa9
commit 28f98dee14
4 changed files with 64 additions and 29 deletions

View File

@ -5,6 +5,7 @@ import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import { InputText } from 'primereact/inputtext';
import { ProgressSpinner } from 'primereact/progressspinner';
import { InputNumber } from 'primereact/inputnumber';
import GenericButton from '@/components/buttons/GenericButton';
const LightningAddressForm = ({ visible, onHide }) => {
@ -91,23 +92,25 @@ const LightningAddressForm = ({ visible, onHide }) => {
) : (
<p>Confirm your Lightning Address details</p>
)}
<div className="flex flex-col gap-2 max-mob:min-w-[80vw] max-tab:min-w-[60vw] min-w-[40vw]">
<p className="text-sm text-gray-500">Only LND is currently supported at this time</p>
<div className="mt-4 flex flex-col gap-2 max-mob:min-w-[80vw] max-tab:min-w-[60vw] min-w-[40vw]">
<label>Name</label>
<InputText placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
<InputText placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} tooltip='This is your Lightning Address name, it must be unique and will be displayed as name@plebdevs.com' />
<label>Description</label>
<InputText placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} />
<InputText placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} tooltip='This is your Lightning Address description, it will be displayed as the description LUD16 lnurlp endpoint' />
<label>Max Sendable</label>
<InputText placeholder="Max Sendable" value={maxSendable} onChange={(e) => setMaxSendable(e.target.value)} />
{/* Todo: max is 2,147,483 sats until i imlement bigInt for sat amounts */}
<InputNumber placeholder="Max Sendable" value={maxSendable} onChange={(e) => setMaxSendable(e.target.value)} max={2147483647} min={1000} tooltip='This is the maximum amount of sats that can be sent to your Lightning Address (currently denominated in sats NOT msat)' />
<label>Min Sendable</label>
<InputText placeholder="Min Sendable" value={minSendable} onChange={(e) => setMinSendable(e.target.value)} />
<InputNumber placeholder="Min Sendable" value={minSendable} onChange={(e) => setMinSendable(e.target.value)} min={1} max={2147483647} tooltip='This is the minimum amount of sats that can be sent to your Lightning Address (currently denominated in sats NOT msat)' />
<label>Invoice Macaroon</label>
<InputText placeholder="Invoice Macaroon" value={invoiceMacaroon} onChange={(e) => setInvoiceMacaroon(e.target.value)} />
<InputText placeholder="Invoice Macaroon" value={invoiceMacaroon} onChange={(e) => setInvoiceMacaroon(e.target.value)} tooltip='This is your LND Invoice Macaroon, it is used to create invoices for your Lightning Address but DOES NOT grant access to move funds from your LND node' />
<label>LND Cert</label>
<InputText placeholder="LND Cert" value={lndCert} onChange={(e) => setLndCert(e.target.value)} />
<InputText placeholder="LND Cert" value={lndCert} onChange={(e) => setLndCert(e.target.value)} tooltip='This is your LND TLS Certificate, it is used to connect to your LND node (this may be optional)' />
<label>LND Host</label>
<InputText placeholder="LND Host" value={lndHost} onChange={(e) => setLndHost(e.target.value)} />
<InputText placeholder="LND Host" value={lndHost} onChange={(e) => setLndHost(e.target.value)} tooltip='This is your LND Host, it is the hostname to your LND node' />
<label>LND Port</label>
<InputText placeholder="LND Port" value={lndPort} onChange={(e) => setLndPort(e.target.value)} />
<InputText placeholder="LND Port" value={lndPort} onChange={(e) => setLndPort(e.target.value)} tooltip='This is your LND Port, it is the port to your LND node (defaults to 8080)' />
</div>
{!existingLightningAddress && (
<div className="flex flex-row justify-center mt-6">

View File

@ -3,6 +3,7 @@ import crypto from "crypto";
import { verifyEvent } from 'nostr-tools/pure';
import appConfig from "@/config/appConfig";
import { runMiddleware, corsMiddleware } from "@/utils/corsMiddleware";
import { getLightningAddressByName } from "@/db/models/lightningAddressModels";
const BACKEND_URL = process.env.BACKEND_URL;
@ -10,9 +11,21 @@ export default async function handler(req, res) {
await runMiddleware(req, res, corsMiddleware);
const { slug, ...queryParams } = req.query;
let foundAddress = null;
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === slug);
if (customAddress) {
foundAddress = customAddress;
} else {
foundAddress = await getLightningAddressByName(slug);
}
if (!foundAddress) {
res.status(404).json({ error: 'Lightning address not found' });
return;
}
if (foundAddress) {
if (queryParams.amount) {
const amount = parseInt(queryParams.amount);
let metadata, metadataString, hash, descriptionHash;
@ -41,7 +54,7 @@ export default async function handler(req, res) {
} else {
// This is a regular lnurl-pay request
metadata = [
["text/plain", `${customAddress.name}'s LNURL endpoint, CHEERS!`]
["text/plain", `${foundAddress.name}'s LNURL endpoint, CHEERS!`]
];
metadataString = JSON.stringify(metadata);
hash = crypto.createHash('sha256').update(metadataString).digest('hex');
@ -49,10 +62,10 @@ export default async function handler(req, res) {
}
// Convert amount from millisatoshis to satoshis
if (amount < (customAddress.minSendable)) {
if (amount < (foundAddress.minSendable)) {
res.status(400).json({ error: 'Amount too low' });
return;
} else if (amount > (customAddress.maxSendable || Number.MAX_SAFE_INTEGER)) {
} else if (amount > (foundAddress.maxSendable || Number.MAX_SAFE_INTEGER)) {
res.status(400).json({ error: 'Amount too high' });
return;
} else {

View File

@ -2,9 +2,8 @@ import axios from "axios";
import { finalizeEvent } from 'nostr-tools/pure';
import { SimplePool } from 'nostr-tools/pool';
import appConfig from "@/config/appConfig";
import { getLightningAddressByName } from "@/db/models/lightningAddressModels";
const LND_HOST = process.env.LND_HOST;
const LND_MACAROON = process.env.LND_MACAROON;
const ZAP_PRIVKEY = process.env.ZAP_PRIVKEY;
export default async function handler(req, res) {
@ -12,16 +11,23 @@ export default async function handler(req, res) {
const { amount, description_hash, zap_request=null, name } = req.body;
// Find the custom lightning address
let foundAddress = null;
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === name);
if (!customAddress) {
if (customAddress) {
foundAddress = customAddress;
} else {
foundAddress = await getLightningAddressByName(name);
}
if (!foundAddress) {
res.status(404).json({ error: 'Lightning address not found' });
return;
}
// Check if amount is within allowed range
const minSendable = customAddress.minSendable || appConfig.defaultMinSendable || 1;
const maxSendable = customAddress.maxSendable || appConfig.defaultMaxSendable || Number.MAX_SAFE_INTEGER;
const minSendable = foundAddress.minSendable || appConfig.defaultMinSendable || 1;
const maxSendable = foundAddress.maxSendable || appConfig.defaultMaxSendable || Number.MAX_SAFE_INTEGER;
if (amount < minSendable || amount > maxSendable) {
res.status(400).json({ error: 'Amount out of allowed range' });
@ -29,30 +35,30 @@ export default async function handler(req, res) {
}
// Check if the custom address allows zaps
if (zap_request && !customAddress.allowsNostr) {
if (zap_request && !foundAddress.allowsNostr) {
res.status(400).json({ error: 'Nostr zaps not allowed for this address' });
return;
}
const response = await axios.post(`https://${LND_HOST}/v1/invoices`, {
const response = await axios.post(`https://${foundAddress.lndHost}/v1/invoices`, {
value_msat: amount,
description_hash: description_hash
}, {
headers: {
'Grpc-Metadata-macaroon': LND_MACAROON,
'Grpc-Metadata-macaroon': foundAddress.invoiceMacaroon,
}
});
const invoice = response.data.payment_request;
// If this is a zap, publish a zap receipt
if (zap_request && customAddress.allowsNostr) {
if (zap_request && foundAddress.allowsNostr) {
console.log("ZAP REQUEST", zap_request)
const zapRequest = JSON.parse(zap_request);
const zapReceipt = {
kind: 9735,
created_at: Math.floor(Date.now() / 1000),
content: customAddress.zapMessage || appConfig.defaultZapMessage || '',
content: foundAddress.zapMessage || appConfig.defaultZapMessage || '',
tags: [
['p', zapRequest.pubkey],
['e', zapRequest.id],
@ -61,11 +67,11 @@ export default async function handler(req, res) {
]
};
const signedZapReceipt = finalizeEvent(zapReceipt, customAddress.relayPrivkey || ZAP_PRIVKEY);
const signedZapReceipt = finalizeEvent(zapReceipt, foundAddress.relayPrivkey || ZAP_PRIVKEY);
// Publish zap receipt to relays
const pool = new SimplePool();
const relays = customAddress.defaultRelays || appConfig.defaultRelayUrls || [];
const relays = foundAddress.defaultRelays || appConfig.defaultRelayUrls || [];
await Promise.any(pool.publish(relays, signedZapReceipt));
}

View File

@ -1,5 +1,6 @@
import appConfig from "@/config/appConfig"
import { runMiddleware, corsMiddleware } from "@/utils/corsMiddleware";
import { getLightningAddressByName } from "@/db/models/lightningAddressModels";
const BACKEND_URL = process.env.BACKEND_URL
const ZAP_PUBKEY = process.env.ZAP_PUBKEY
@ -13,17 +14,29 @@ export default async function handler(req, res) {
return
}
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === slug)
let foundAddress = null;
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === slug);
if (customAddress) {
foundAddress = customAddress;
} else {
foundAddress = await getLightningAddressByName(slug);
}
if (!foundAddress) {
res.status(404).json({ error: 'Lightning address not found' })
return
}
if (foundAddress) {
const metadata = [
["text/plain", `${customAddress.description}`]
["text/plain", `${foundAddress.description}`]
];
res.status(200).json({
callback: `${BACKEND_URL}/api/lightning-address/callback/${customAddress.name}`,
maxSendable: customAddress.maxSendable || 10000000000,
minSendable: customAddress.minSendable || 1000,
callback: `${BACKEND_URL}/api/lightning-address/callback/${foundAddress.name}`,
maxSendable: foundAddress.maxSendable || 10000000000,
minSendable: foundAddress.minSendable || 1000,
metadata: JSON.stringify(metadata),
tag: 'payRequest',
allowsNostr: true,