From ef3679051214a9b3047b68f236eac656edcf8d38 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 18 Jan 2023 23:14:48 -0800 Subject: bitcoin invoices --- index.js | 1 + lib/bitcoin.js | 2 +- lib/rpc.js | 26 ++++++++++++++++++++++++++ lib/utils.js | 9 +++++++++ routes/invoices.js | 38 ++++++++++++++++++++++++-------------- routes/payments.js | 7 ++++++- 6 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 lib/rpc.js diff --git a/index.js b/index.js index 1ade59d8..6a34f543 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,7 @@ app.get("/payments/:hash", auth, payments.get); app.post("/lightning/send", auth, payments.sendLightning); app.get("/pot/:name", payments.pot); app.post("/withdraw", auth, payments.withdraw); +app.post("/bitcoin", payments.bitcoin); app.get("/voucher/:id", payments.voucher); app.post("/redeem", optional, payments.redeem); diff --git a/lib/bitcoin.js b/lib/bitcoin.js index eda8b4b0..a811dc14 100644 --- a/lib/bitcoin.js +++ b/lib/bitcoin.js @@ -1,3 +1,3 @@ import config from "$config"; -import rpc from "./rpc"; +import rpc from "$lib/rpc"; export default rpc(config.bitcoin); diff --git a/lib/rpc.js b/lib/rpc.js new file mode 100644 index 00000000..b8a2d0dd --- /dev/null +++ b/lib/rpc.js @@ -0,0 +1,26 @@ +import got from "got"; + +export default ({ host, port, wallet, username, password }) => + new Proxy( + {}, + { + get: (target, prop) => (...params) => + ((method, ...params) => { + let url = `http://${host}:${port}/wallet/${wallet}`; + return got + .post(url, { + json: { + method, + params + }, + headers: { + authorization: `Basic ${Buffer.from( + `${username}:${password}` + ).toString("base64")}` + } + }) + .json() + .then(({ result }) => result); + })(prop.toLowerCase(), ...params) + } + ); diff --git a/lib/utils.js b/lib/utils.js index 92b34be5..e653f235 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -28,3 +28,12 @@ export const toSats = n => Math.round(n * SATS); export const uniq = (a, k) => [...new Map(a.map(x => [k(x), x])).values()]; export const pick = (O, K) => K.reduce((o, k) => (typeof O[k] !== "undefined" && (o[k] = O[k]), o), {}); + +export const bip21 = (address, { amount, memo, tip }) => { + let url = amount || memo ? `bitcoin:${address}?` : address; + if (amount) + url += `amount=${((amount + tip) / SATS).toFixed(8)}${memo ? "&" : ""}`; + if (memo) url += `message=${memo}`; + + return url; +}; diff --git a/routes/invoices.js b/routes/invoices.js index acd9457e..1287de88 100644 --- a/routes/invoices.js +++ b/routes/invoices.js @@ -2,6 +2,8 @@ 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 bc from "$lib/bitcoin"; import ln from "$lib/ln"; export default { @@ -12,49 +14,57 @@ export default { }, async create({ body: { invoice, user }, user: me }, res) { - let { currency, tip, amount, rate, request_id } = invoice; + let { currency, tip, amount, rate, request_id, type } = invoice; - if (amount < 0) throw new Error("amount out of range"); - if (tip > 5 * amount || tip < 0) throw new Error("tip amount out of range"); + 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) throw new Error("user not provided"); + if (!user) fail("user not provided"); if (!currency) currency = user.currency; if (!rate) rate = store.rates[currency]; - if (amount < 0) throw new Error("invalid amount"); + if (amount < 0) fail("invalid amount"); - let { payment_hash: hash, bolt11: text } = await ln.invoice( - amount ? `${amount + tip}sat` : "any", - new Date(), - "", - 3600 - ); + let hash, text; + if (type === "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 === "bitcoin") { + hash = await bc.getNewAddress(); + text = bip21(hash, invoice); + } else { + fail("unrecognized type"); + } invoice = { ...invoice, - hash, amount, + hash, + text, currency, rate, tip, uid, - text, received: 0, created: Date.now() }; + l( "creating invoice", user.username, amount, tip, currency, - `${text.substr(0, 8)}..${text.substr(-6)}` + invoice.hash ); await s(`invoice:${hash}`, invoice); diff --git a/routes/payments.js b/routes/payments.js index ba701bef..fc506685 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -265,7 +265,7 @@ export default { }, async withdraw({ body: { name, amount }, user }, res) { - amount = parseInt(amount) + amount = parseInt(amount); await t(`pot:${name}`, async balance => { await new Promise(r => setTimeout(r, 100)); if (balance < amount) fail("Insufficient funds"); @@ -282,5 +282,10 @@ export default { await db.lPush(`pot:${name}:payments`, hash); res.send({ payment }); + }, + + async bitcoin({ body }, res) { + console.log(body); + res.send({}); } }; -- cgit v1.2.3