diff options
| author | Adam Soltys <[email protected]> | 2023-01-24 12:28:49 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-01-24 12:28:49 -0800 |
| commit | 692315c0d4259a274322ca579a7b6118d033bf7a (patch) | |
| tree | 50dab3d2760130c5822b21f7293da3b34639579c | |
| parent | f24aec1fb7dd4080d961c26ab5216fd2dfe5d971 (diff) | |
| download | coinos-server-692315c0d4259a274322ca579a7b6118d033bf7a.tar.gz coinos-server-692315c0d4259a274322ca579a7b6118d033bf7a.zip | |
lnurl
| -rw-r--r-- | docker-compose.yml | 1 | ||||
| -rw-r--r-- | index.js | 9 | ||||
| -rw-r--r-- | lib/invoices.js | 73 | ||||
| -rw-r--r-- | routes/invoices.js | 75 | ||||
| -rw-r--r-- | routes/lnurl.js | 66 | ||||
| -rw-r--r-- | routes/payments.js | 46 |
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: @@ -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 }); } }; |
