mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 10:51:20 +00:00
fix zaps on lightning address
This commit is contained in:
parent
8f2935a7dc
commit
2e25beea71
@ -17,6 +17,10 @@ module.exports = removeImports({
|
||||
{
|
||||
source: "/.well-known/nostr.json",
|
||||
destination: "/api/nip05",
|
||||
},
|
||||
{
|
||||
source: '/.well-known/lnurlp/:slug',
|
||||
destination: '/api/lightning-address/lnurlp/:slug',
|
||||
}
|
||||
];
|
||||
},
|
||||
|
23
package-lock.json
generated
23
package-lock.json
generated
@ -26,6 +26,7 @@
|
||||
"bech32": "^2.0.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"discord.js": "^14.15.3",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
"lucide-react": "^0.441.0",
|
||||
@ -5842,6 +5843,19 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -13542,6 +13556,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz",
|
||||
|
@ -27,6 +27,7 @@
|
||||
"bech32": "^2.0.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"discord.js": "^14.15.3",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
"lucide-react": "^0.441.0",
|
||||
|
@ -57,6 +57,7 @@ const promotions = [
|
||||
},
|
||||
]
|
||||
|
||||
// todo bigger ore simple CTA to get users into the content
|
||||
const InteractivePromotionalCarousel = () => {
|
||||
const [selectedPromotion, setSelectedPromotion] = useState(promotions[0])
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
|
@ -11,7 +11,30 @@ const appConfig = {
|
||||
"wss://purplerelay.com/",
|
||||
// "wss://relay.devs.tools/"
|
||||
],
|
||||
authorPubkeys: ["8cb60e215678879cda0bef4d5b3fc1a5c5925d2adb5d8c4fa7b7d03b5f2deaea", "676c02247668d5b18479be3d1a80933044256f3fbd03640a8c234684e641b6d6"]
|
||||
authorPubkeys: ["8cb60e215678879cda0bef4d5b3fc1a5c5925d2adb5d8c4fa7b7d03b5f2deaea", "676c02247668d5b18479be3d1a80933044256f3fbd03640a8c234684e641b6d6"],
|
||||
customLightningAddresses: [
|
||||
{
|
||||
// todo remove need for lowercase
|
||||
// name will appear as name@plebdevs.com (lowercase)
|
||||
name: "austin",
|
||||
// If enabled, zaps are enabled
|
||||
allowsNostr: true,
|
||||
// make you're own lud06 metadata description
|
||||
description: "Austin's Lightning Address",
|
||||
// millisats
|
||||
maxSendable: 10000000000,
|
||||
// millisats
|
||||
minSendable: 1000,
|
||||
// Your LND invoice macaroon
|
||||
invoiceMacaroon: process.env.LND_MACAROON,
|
||||
// your LND TLS certificate (may be optional depending on your LND configuration)
|
||||
lndCert: "",
|
||||
// your LND host (do not include https:// or port)
|
||||
lndHost: process.env.LND_HOST,
|
||||
// your LND REST API port (default is 8080)
|
||||
lndPort: "8080",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default appConfig;
|
@ -1,24 +1,28 @@
|
||||
import axios from "axios";
|
||||
import crypto from "crypto";
|
||||
import { runMiddleware, corsMiddleware } from "../../../utils/middleware";
|
||||
import { verifyEvent } from 'nostr-tools/pure';
|
||||
import appConfig from "@/config/appConfig";
|
||||
import { runMiddleware, corsMiddleware } from "@/utils/corsMiddleware";
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL;
|
||||
const RELAY_PUBKEY = process.env.RELAY_PUBKEY;
|
||||
|
||||
export default async function handler(req, res) {
|
||||
await runMiddleware(req, res, corsMiddleware);
|
||||
const { slug, ...queryParams } = req.query;
|
||||
|
||||
if (slug === 'austin') {
|
||||
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === slug);
|
||||
|
||||
if (customAddress) {
|
||||
if (queryParams.amount) {
|
||||
const amount = parseInt(queryParams.amount);
|
||||
let metadata, metadataString, hash, descriptionHash;
|
||||
|
||||
if (queryParams.nostr) {
|
||||
if (queryParams?.nostr) {
|
||||
// This is a zap request
|
||||
const zapRequest = JSON.parse(decodeURIComponent(queryParams.nostr));
|
||||
|
||||
console.log("ZAP REQUEST", zapRequest)
|
||||
|
||||
// Verify the zap request
|
||||
if (!verifyEvent(zapRequest)) {
|
||||
res.status(400).json({ error: 'Invalid zap request' });
|
||||
@ -37,7 +41,7 @@ export default async function handler(req, res) {
|
||||
} else {
|
||||
// This is a regular lnurl-pay request
|
||||
metadata = [
|
||||
["text/plain", "PlebDevs LNURL endpoint, CHEERS!"]
|
||||
["text/plain", `${customAddress.name}'s LNURL endpoint, CHEERS!`]
|
||||
];
|
||||
metadataString = JSON.stringify(metadata);
|
||||
hash = crypto.createHash('sha256').update(metadataString).digest('hex');
|
||||
@ -45,13 +49,15 @@ export default async function handler(req, res) {
|
||||
}
|
||||
|
||||
// Convert amount from millisatoshis to satoshis
|
||||
const value = amount / 1000;
|
||||
if (value < 1) {
|
||||
if (amount < (customAddress.minSendable)) {
|
||||
res.status(400).json({ error: 'Amount too low' });
|
||||
return;
|
||||
} else if (amount > (customAddress.maxSendable || Number.MAX_SAFE_INTEGER)) {
|
||||
res.status(400).json({ error: 'Amount too high' });
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
const response = await axios.post(`${BACKEND_URL}/api/lnd`, { amount: value, description_hash: descriptionHash });
|
||||
const response = await axios.post(`${BACKEND_URL}/api/lightning-address/lnd`, { amount: amount, description_hash: descriptionHash, name: slug, zap_request: queryParams?.nostr ? queryParams.nostr : null });
|
||||
res.status(200).json({ pr: response.data });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -61,5 +67,7 @@ export default async function handler(req, res) {
|
||||
} else {
|
||||
res.status(400).json({ error: 'Amount not specified' });
|
||||
}
|
||||
} else {
|
||||
res.status(404).json({ error: 'Lightning address not found' });
|
||||
}
|
||||
}
|
@ -1,16 +1,42 @@
|
||||
import axios from "axios";
|
||||
import { finalizeEvent } from 'nostr-tools/pure';
|
||||
import { SimplePool } from 'nostr-tools/pool';
|
||||
import appConfig from "@/config/appConfig";
|
||||
|
||||
const LND_HOST = process.env.LND_HOST;
|
||||
const LND_MACAROON = process.env.LND_MACAROON;
|
||||
const RELAY_PRIVKEY = process.env.RELAY_PRIVKEY;
|
||||
const ZAP_PRIVKEY = process.env.ZAP_PRIVKEY;
|
||||
|
||||
export default async function handler(req, res) {
|
||||
try {
|
||||
const { amount, description_hash, zap_request=null, name } = req.body;
|
||||
|
||||
// Find the custom lightning address
|
||||
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === name);
|
||||
|
||||
if (!customAddress) {
|
||||
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;
|
||||
|
||||
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 && !customAddress.allowsNostr) {
|
||||
res.status(400).json({ error: 'Nostr zaps not allowed for this address' });
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await axios.post(`https://${LND_HOST}/v1/invoices`, {
|
||||
value: req.body.amount,
|
||||
description_hash: req.body.description_hash
|
||||
value_msat: amount,
|
||||
description_hash: description_hash
|
||||
}, {
|
||||
headers: {
|
||||
'Grpc-Metadata-macaroon': LND_MACAROON,
|
||||
@ -20,12 +46,13 @@ export default async function handler(req, res) {
|
||||
const invoice = response.data.payment_request;
|
||||
|
||||
// If this is a zap, publish a zap receipt
|
||||
if (req.body.zap_request) {
|
||||
const zapRequest = JSON.parse(req.body.zap_request);
|
||||
if (zap_request && customAddress.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: '',
|
||||
content: customAddress.zapMessage || appConfig.defaultZapMessage || '',
|
||||
tags: [
|
||||
['p', zapRequest.pubkey],
|
||||
['e', zapRequest.id],
|
||||
@ -34,12 +61,12 @@ export default async function handler(req, res) {
|
||||
]
|
||||
};
|
||||
|
||||
const signedZapReceipt = finalizeEvent(zapReceipt, RELAY_PRIVKEY);
|
||||
const signedZapReceipt = finalizeEvent(zapReceipt, customAddress.relayPrivkey || ZAP_PRIVKEY);
|
||||
|
||||
// Publish zap receipt to relays
|
||||
const pool = new SimplePool();
|
||||
const relays = zapRequest.tags.find(tag => tag[0] === 'relays')?.[1] || [];
|
||||
await pool.publish(relays, signedZapReceipt);
|
||||
const relays = customAddress.defaultRelays || appConfig.defaultRelayUrls || [];
|
||||
await Promise.any(pool.publish(relays, signedZapReceipt));
|
||||
}
|
||||
|
||||
res.status(200).json(invoice);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { runMiddleware, corsMiddleware } from "../../../utils/middleware"
|
||||
import appConfig from "@/config/appConfig"
|
||||
import { runMiddleware, corsMiddleware } from "@/utils/corsMiddleware";
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL
|
||||
const RELAY_PUBKEY = process.env.RELAY_PUBKEY
|
||||
const ZAP_PUBKEY = process.env.ZAP_PUBKEY
|
||||
|
||||
export default async function handler(req, res) {
|
||||
await runMiddleware(req, res, corsMiddleware);
|
||||
|
||||
const { slug } = req.query
|
||||
|
||||
if (!slug || slug === 'undefined') {
|
||||
@ -13,20 +13,24 @@ export default async function handler(req, res) {
|
||||
return
|
||||
}
|
||||
|
||||
if (slug === 'austin') {
|
||||
const customAddress = appConfig.customLightningAddresses.find(addr => addr.name === slug)
|
||||
|
||||
if (customAddress) {
|
||||
const metadata = [
|
||||
["text/plain", "PlebDevs LNURL endpoint, CHEERS!"]
|
||||
["text/plain", `${customAddress.description}`]
|
||||
];
|
||||
|
||||
res.status(200).json({
|
||||
callback: `${BACKEND_URL}/api/callback/austin`,
|
||||
maxSendable: 10000000000,
|
||||
minSendable: 1000,
|
||||
callback: `${BACKEND_URL}/api/lightning-address/callback/${customAddress.name}`,
|
||||
maxSendable: customAddress.maxSendable || 10000000000,
|
||||
minSendable: customAddress.minSendable || 1000,
|
||||
metadata: JSON.stringify(metadata),
|
||||
tag: 'payRequest',
|
||||
allowsNostr: true,
|
||||
nostrPubkey: RELAY_PUBKEY
|
||||
nostrPubkey: ZAP_PUBKEY
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
res.status(404).json({ error: 'Lightning address not found' })
|
||||
}
|
21
src/utils/corsMiddleware.js
Normal file
21
src/utils/corsMiddleware.js
Normal file
@ -0,0 +1,21 @@
|
||||
import Cors from 'cors';
|
||||
|
||||
// Initialize the cors middleware
|
||||
export const corsMiddleware = Cors({
|
||||
methods: ['GET', 'HEAD', 'POST'],
|
||||
origin: '*',
|
||||
});
|
||||
|
||||
|
||||
// Helper method to wait for a middleware to execute before continuing
|
||||
// And to throw an error when an error happens in a middleware
|
||||
export async function runMiddleware(req, res, middleware) {
|
||||
return new Promise((resolve, reject) => {
|
||||
middleware(req, res, (result) => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result);
|
||||
}
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user