From 28f98dee14b2c1398ca8cf933eef198d559d2e1f Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Thu, 26 Sep 2024 17:44:24 -0500 Subject: [PATCH] Lightning address works from db for subscribed users and from the app config --- .../subscription/LightningAddressForm.js | 21 +++++++------ .../api/lightning-address/callback/[slug].js | 19 ++++++++++-- src/pages/api/lightning-address/lnd.js | 30 +++++++++++-------- .../api/lightning-address/lnurlp/[slug].js | 23 ++++++++++---- 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/components/profile/subscription/LightningAddressForm.js b/src/components/profile/subscription/LightningAddressForm.js index 3259159..382dd97 100644 --- a/src/components/profile/subscription/LightningAddressForm.js +++ b/src/components/profile/subscription/LightningAddressForm.js @@ -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 }) => { ) : (

Confirm your Lightning Address details

)} -
+

Only LND is currently supported at this time

+
- setName(e.target.value)} /> + setName(e.target.value)} tooltip='This is your Lightning Address name, it must be unique and will be displayed as name@plebdevs.com' /> - setDescription(e.target.value)} /> + setDescription(e.target.value)} tooltip='This is your Lightning Address description, it will be displayed as the description LUD16 lnurlp endpoint' /> - setMaxSendable(e.target.value)} /> + {/* Todo: max is 2,147,483 sats until i imlement bigInt for sat amounts */} + 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)' /> - setMinSendable(e.target.value)} /> + 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)' /> - setInvoiceMacaroon(e.target.value)} /> + 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' /> - setLndCert(e.target.value)} /> + setLndCert(e.target.value)} tooltip='This is your LND TLS Certificate, it is used to connect to your LND node (this may be optional)' /> - setLndHost(e.target.value)} /> + setLndHost(e.target.value)} tooltip='This is your LND Host, it is the hostname to your LND node' /> - setLndPort(e.target.value)} /> + setLndPort(e.target.value)} tooltip='This is your LND Port, it is the port to your LND node (defaults to 8080)' />
{!existingLightningAddress && (
diff --git a/src/pages/api/lightning-address/callback/[slug].js b/src/pages/api/lightning-address/callback/[slug].js index f4631f1..b282b84 100644 --- a/src/pages/api/lightning-address/callback/[slug].js +++ b/src/pages/api/lightning-address/callback/[slug].js @@ -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 { diff --git a/src/pages/api/lightning-address/lnd.js b/src/pages/api/lightning-address/lnd.js index 4d3127e..6bb117a 100644 --- a/src/pages/api/lightning-address/lnd.js +++ b/src/pages/api/lightning-address/lnd.js @@ -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)); } diff --git a/src/pages/api/lightning-address/lnurlp/[slug].js b/src/pages/api/lightning-address/lnurlp/[slug].js index 9a8ba25..f084a18 100644 --- a/src/pages/api/lightning-address/lnurlp/[slug].js +++ b/src/pages/api/lightning-address/lnurlp/[slug].js @@ -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,