aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docker-compose.yml1
-rw-r--r--index.js9
-rw-r--r--lib/invoices.js73
-rw-r--r--routes/invoices.js75
-rw-r--r--routes/lnurl.js66
-rw-r--r--routes/payments.js46
6 files changed, 187 insertions, 83 deletions
diff --git a/docker-compose.yml b/docker-compose.yml
index 421b6a45..70d12c24 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,6 +12,7 @@ services:
environment:
NODE_OPTIONS: "--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js"
NODE_ENV: development
+ URL: http://localhost:3119
ports:
- '3119:3119'
volumes:
diff --git a/index.js b/index.js
index b2796203..2c59b8e6 100644
--- a/index.js
+++ b/index.js
@@ -10,6 +10,7 @@ import { sendHeartbeat } from "$lib/sockets";
import email from "$routes/email";
import info from "$routes/info";
import locations from "$routes/locations";
+import lnurl from "$routes/lnurl";
import nostr from "$routes/nostr";
import rates from "$routes/rates";
import invoices from "$routes/invoices";
@@ -50,9 +51,11 @@ app.get("/payments/:hash", auth, payments.get);
app.post("/parse", payments.parse);
app.get("/pot/:name", payments.pot);
app.post("/take", auth, payments.take);
-app.get("/encode", payments.encode);
-app.get("/decode", payments.decode);
-app.get("/lnurl/:code", payments.lnurl);
+
+app.get("/encode", lnurl.encode);
+app.get("/decode", lnurl.decode);
+app.get("/lnurlp/:username", lnurl.lnurlp);
+app.get("/lnurl/:id", lnurl.lnurl);
app.post("/bitcoin", payments.bitcoin);
app.post("/bitcoin/fee", auth, payments.fee);
diff --git a/lib/invoices.js b/lib/invoices.js
new file mode 100644
index 00000000..7a45b1c0
--- /dev/null
+++ b/lib/invoices.js
@@ -0,0 +1,73 @@
+import store from "$lib/store";
+import { l } from "$lib/logging";
+import { emit } from "$lib/sockets";
+import { db, g, s } from "$lib/db";
+import { bip21, fail } from "$lib/utils";
+import { types } from "$lib/payments";
+import { v4 } from "uuid";
+
+import bc from "$lib/bitcoin";
+import ln from "$lib/ln";
+
+export let invoice = async ({ invoice, user, sender }) => {
+ let { currency, tip, amount, rate, request_id, type } = invoice;
+
+ if (amount < 0) fail("amount out of range");
+ if (tip > 5 * amount || tip < 0) fail("tip amount out of range");
+ tip = tip || 0;
+
+ if (!user) user = sender;
+ let uid = await g(`user:${user.username}`);
+ user = await g(`user:${uid}`);
+
+ if (!user) fail("user not provided");
+
+ if (!currency) currency = user.currency;
+ if (!rate) rate = store.rates[currency];
+ if (amount < 0) fail("invalid amount");
+
+ let hash, text;
+ if (type === types.lightning) {
+ let amt = amount ? `${amount + tip}sat` : "any";
+ let r = await ln.invoice(amt, new Date(), "", 3600);
+
+ hash = r.payment_hash;
+ text = r.bolt11;
+ } else if (type === types.bitcoin) {
+ hash = await bc.getNewAddress();
+ text = bip21(hash, invoice);
+ } else if (type === types.internal) {
+ hash = v4();
+ } else {
+ fail("unrecognized type");
+ }
+
+ invoice = {
+ ...invoice,
+ amount,
+ hash,
+ text,
+ currency,
+ rate,
+ tip,
+ uid,
+ received: 0,
+ created: Date.now()
+ };
+
+ l("creating invoice", user.username, amount, tip, currency, invoice.hash);
+
+ await s(`invoice:${hash}`, invoice);
+ await db.lPush(`${uid}:invoices`, hash);
+
+ if (request_id) {
+ let request = await g(`request:${request_id}`);
+ let { invoice_id: prev } = request;
+ request.invoice_id = invoice.id;
+ await s(`request:${request_id}`, request);
+
+ if (!prev) emit(request.requester.username, "invoice", invoice);
+ }
+
+ return invoice;
+};
diff --git a/routes/invoices.js b/routes/invoices.js
index 5da481e1..a4155d94 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -1,13 +1,5 @@
-import store from "$lib/store";
-import { l } from "$lib/logging";
-import { emit } from "$lib/sockets";
-import { db, g, s } from "$lib/db";
-import { bip21, fail } from "$lib/utils";
-import { types } from "$lib/payments";
-import { v4 } from "uuid";
-
-import bc from "$lib/bitcoin";
-import ln from "$lib/ln";
+import { g, s } from "$lib/db";
+import { invoice } from "$lib/invoices";
export default {
async get({ query: { hash } }, res) {
@@ -16,66 +8,7 @@ export default {
res.send(invoice);
},
- async create({ body: { invoice, user }, user: me }, res) {
- let { currency, tip, amount, rate, request_id, type } = invoice;
-
- if (amount < 0) fail("amount out of range");
- if (tip > 5 * amount || tip < 0) fail("tip amount out of range");
- tip = tip || 0;
-
- if (!user) user = me;
- let uid = await g(`user:${user.username}`);
- user = await g(`user:${uid}`);
-
- if (!user) fail("user not provided");
-
- if (!currency) currency = user.currency;
- if (!rate) rate = store.rates[currency];
- if (amount < 0) fail("invalid amount");
-
- let hash, text;
- if (type === types.lightning) {
- let amt = amount ? `${amount + tip}sat` : "any";
- let r = await ln.invoice(amt, new Date(), "", 3600);
-
- hash = r.payment_hash;
- text = r.bolt11;
- } else if (type === types.bitcoin) {
- hash = await bc.getNewAddress();
- text = bip21(hash, invoice);
- } else if (type === types.internal) {
- hash = v4();
- } else {
- fail("unrecognized type");
- }
-
- invoice = {
- ...invoice,
- amount,
- hash,
- text,
- currency,
- rate,
- tip,
- uid,
- received: 0,
- created: Date.now()
- };
-
- l("creating invoice", user.username, amount, tip, currency, invoice.hash);
-
- await s(`invoice:${hash}`, invoice);
- await db.lPush(`${uid}:invoices`, hash);
-
- if (request_id) {
- let request = await g(`request:${request_id}`);
- let { invoice_id: prev } = request;
- request.invoice_id = invoice.id;
- await s(`request:${request_id}`, request);
-
- if (!prev) emit(request.requester.username, "invoice", invoice);
- }
-
- res.send(invoice);
+ async create({ body: { invoice, user }, user: sender }, res) {
+ res.send(await invoice({ invoice, user, sender }));
}
};
diff --git a/routes/lnurl.js b/routes/lnurl.js
new file mode 100644
index 00000000..cb1b75c5
--- /dev/null
+++ b/routes/lnurl.js
@@ -0,0 +1,66 @@
+import { g, s } from "$lib/db";
+import { l } from "$lib/logging";
+import { fail } from "$lib/utils";
+import { v4 } from "uuid";
+import got from "got";
+import { invoice } from "$lib/invoices";
+import { bech32 } from "bech32";
+
+export default {
+ async encode({ query: { address } }, res) {
+ let [name, domain] = address.split("@");
+ let url = `https://${domain}/.well-known/lnurlp/${name}`;
+ let r = await got(url).json();
+ if (r.tag !== "payRequest") fail("not an ln address");
+ let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000);
+ res.send(enc);
+ },
+
+ async decode({ query: { text } }, res) {
+ let url = Buffer.from(
+ bech32.fromWords(bech32.decode(text, 20000).words)
+ ).toString();
+
+ res.send(await got(url).json());
+ },
+
+ async lnurlp({ params: { username } }, res) {
+ let uid = await g(`user:${username}`);
+ if (!uid) fail("user not found");
+
+ let id = v4();
+ await s(`lnurl:${id}`, uid);
+ let { URL } = process.env;
+ let host = URL.split("/").at(-1);
+
+ res.send({
+ metadata: [
+ ["text/plain", `Paying ${username}@${host}`],
+ ["text/identifier", `${username}@${host}`]
+ ],
+ callback: `${URL}/lnurl/${id}`,
+ tag: "payRequest"
+ });
+ },
+
+ async lnurl({ params: { id }, query: { amount } }, res) {
+ let pr = await g(`lnurlp:${id}`);
+
+ if (!pr) {
+ let uid = await g(`lnurl:${id}`);
+ let user = await g(`user:${uid}`);
+
+ ({ text: pr } = await invoice({
+ invoice: {
+ amount,
+ type: types.lightning
+ },
+ user
+ }));
+
+ await s(`lnurlp:${id}`, pr);
+ }
+
+ res.send({ pr });
+ }
+};
diff --git a/routes/payments.js b/routes/payments.js
index 53bb6e4d..b80d54f6 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -8,13 +8,12 @@ import { fail, btc, sats } from "$lib/utils";
import { requirePin } from "$lib/auth";
import { debit, credit, confirm, types } from "$lib/payments";
import { bech32 } from "bech32";
+import { invoice } from "$lib/invoices";
import got from "got";
import bc from "$lib/bitcoin";
import ln from "$lib/ln";
-const { HOSTNAME: hostname } = process.env;
-
export default {
async create({ body, user }, res) {
let { amount, hash, maxfee, name, memo, payreq, tip } = body;
@@ -227,7 +226,6 @@ export default {
let r = await got(url).json();
if (r.tag !== "payRequest") fail("not an ln address");
let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000);
- console.log(enc);
res.send(enc);
},
@@ -239,13 +237,43 @@ export default {
res.send(await got(url).json());
},
- async lnurl({ params: { code }, query: { amount } }, res) {
- let { uid } = await g(`code:${code}`);
+ async lnurlp({ params: { username } }, res) {
+ let uid = await g(`user:${username}`);
+ if (!uid) fail("user not found");
- let url = Buffer.from(
- bech32.fromWords(bech32.decode(text, 20000).words)
- ).toString();
+ let id = v4();
+ await s(`lnurl:${id}`, uid);
+ let { URL } = process.env;
+ let host = URL.split("/").at(-1);
- res.send(await got(url).json());
+ res.send({
+ metadata: [
+ ["text/plain", `Paying ${username}@${host}`],
+ ["text/identifier", `${username}@${host}`]
+ ],
+ callback: `${host}/lnurl/${id}`,
+ tag: "payRequest"
+ });
+ },
+
+ async lnurl({ params: { id }, query: { amount } }, res) {
+ let pr = await g(`lnurlp:${id}`);
+
+ if (!pr) {
+ let uid = await g(`lnurl:${id}`);
+ let user = await g(`user:${uid}`);
+
+ ({ text: pr } = await invoice({
+ invoice: {
+ amount,
+ type: types.lightning
+ },
+ user
+ }));
+
+ await s(`lnurlp:${id}`, pr);
+ }
+
+ res.send({ pr });
}
};