aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/send.js32
-rw-r--r--lib/utils.js84
-rw-r--r--routes/invoices.js86
-rw-r--r--routes/payments.js1
-rw-r--r--routes/users.js108
5 files changed, 152 insertions, 159 deletions
diff --git a/lib/send.js b/lib/send.js
index f20185e5..e7255219 100644
--- a/lib/send.js
+++ b/lib/send.js
@@ -66,10 +66,11 @@ export default async (amount, memo, hash, user) => {
});
conversionFeeDeduction = Math.min(account.lightning_credits, conversionFee);
+ total = amount + conversionFee - conversionFeeDeduction;
if (
!account ||
- account.balance < amount + conversionFee - conversionFeeDeduction
+ account.balance < total
) {
warn(
"user attempting to send more than they have",
@@ -81,12 +82,12 @@ export default async (amount, memo, hash, user) => {
}
await account.decrement(
- { balance: amount, lightning_credits: conversionFeeDeduction },
+ { balance: total, lightning_credits: conversionFeeDeduction },
{ transaction }
);
await account.reload({ transaction });
- l("debited account", account.id);
+ l("debited account", account.id, total);
let tokens;
if (!payreq.satoshis) tokens = amount;
@@ -100,26 +101,22 @@ export default async (amount, memo, hash, user) => {
if (!m) throw new Error(`lightning payment failed ${amount} ${hash}`);
if (m.payment_error) throw new Error(m.payment_error);
- ({ fee, secret: preimage, tokens: total } = m);
+ ({ fee, secret, tokens: total } = m);
+ preimage = secret.toString("hex");
} else {
- m = await ln.pay(hash, tokens ? `${tokens}sats` : undefined);
- let sent;
- ({
- msatoshi: total,
- msatoshi_sent: sent,
- payment_preimage: preimage
- } = m);
-
- fee = parseInt((sent - total) / 1000);
- total = parseInt(total / 1000);
+ let { msatoshi, msatoshi_sent, payment_preimage } = await ln.pay(hash, tokens ? `${tokens}sats` : undefined);
+ preimage = payment_preimage;
+ fee = parseInt((msatoshi_sent - msatoshi) / 1000);
+ total = parseInt(msatoshi / 1000);
}
- preimage = preimage.toString("hex");
-
if (store.seen.includes(preimage)) {
warn("duplicate payment detected", preimage);
throw new Error("Duplicate payment detected");
}
+
+ store.seen.push(preimage);
+ store.seen.push(hash);
});
await db.transaction(async transaction => {
@@ -226,9 +223,6 @@ export default async (amount, memo, hash, user) => {
emit(user.username, "account", payment.account);
emit(user.username, "payment", payment);
- store.seen.push(preimage);
- store.seen.push(hash);
-
l("sent lightning", user.username, total - fee, fee);
});
diff --git a/lib/utils.js b/lib/utils.js
index 1ff24c67..036ccd6d 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,5 +1,13 @@
+import config from "$config";
import db from "$db";
import sequelize from "@sequelize/core";
+import { fromBase58 } from "bip32";
+import { payments as bcPayments, networks as bcNetworks } from "bitcoinjs-lib";
+import { payments as lqPayments, networks as lqNetworks } from "liquidjs-lib";
+import { createInvoice } from "lightning";
+import lnd from "$lib/lnd";
+import ln from "$lib/ln";
+
const { Op } = sequelize;
export const getUser = async (username, transaction) => {
@@ -90,3 +98,79 @@ export const getBlockHeight = message => {
readUInt32();
return readUInt32();
};
+
+export const deriveAddress = async (account, type) => {
+ let { index, pubkey, path, network } = account;
+ let n = { bitcoin: bcNetworks, liquid: lqNetworks }[network][
+ prod ? "bitcoin" : "regtest"
+ ];
+ let p = { bitcoin: bcPayments, liquid: lqPayments }[network];
+ let root = fromBase58(pubkey, n);
+ let parts = path.split("/");
+ let hd = root.derive(parseInt(parts[parts.length - 1]));
+
+ type = {
+ bech32: "p2wpkh",
+ "p2sh-segwit": "p2sh",
+ legacy: "p2pkh"
+ }[type];
+
+ console.log("TYPE", type);
+
+ let r;
+ if (type !== "p2sh") {
+ r = p[type]({
+ pubkey: hd.publicKey,
+ network: n
+ });
+ } else {
+ r = p[type]({
+ redeem: p.p2wpkh({
+ pubkey: hd.publicKey,
+ network: n
+ }),
+ network: n
+ });
+ }
+
+ index++;
+ account.index = index;
+ account.address = r.address;
+ await account.save();
+
+ return r;
+};
+
+export const bip21 = ({ address, amount, memo, tip, network }, { asset }) => {
+ if (network === "liquid") network = "liquidnetwork";
+
+ let url = amount || memo ? `${network}:${address}?` : address;
+ if (amount)
+ url += `amount=${((amount + tip) / SATS).toFixed(8)}${memo ? "&" : ""}`;
+ if (memo) url += `message=${memo}`;
+
+ if (network === "liquidnetwork" && amount) {
+ url += `&asset=${asset}`;
+ }
+
+ return url;
+};
+
+export const derivePayRequest = async ({ amount, memo, tip }) => {
+ if (!tip) tip = 0;
+ let value = amount + tip;
+
+ if (config.lna.clightning) {
+ if (!memo) memo = "coinos";
+ return (await ln.invoice(value ? `${value}sat` : "any", new Date(), memo, 360))
+ .bolt11;
+ } else {
+ return (
+ await createInvoice({
+ lnd,
+ tokens: value,
+ description: memo
+ })
+ ).request;
+ }
+};
diff --git a/routes/invoices.js b/routes/invoices.js
index b5926fec..9f93180d 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -5,6 +5,9 @@ import { optionalAuth } from "$lib/passport";
import { Op } from "@sequelize/core";
import axios from "axios";
import { err, l } from "$lib/logging";
+import bc from "$lib/bitcoin";
+import lq from "$lib/liquid";
+import { SATS, bip21, deriveAddress, derivePayRequest } from "$lib/utils";
app.get("/invoice", async (req, res, next) => {
try {
@@ -27,15 +30,12 @@ app.get("/invoice", async (req, res, next) => {
app.post("/invoice", optionalAuth, async (req, res, next) => {
try {
- let { liquidAddress, id, invoice, user, tx } = req.body;
- let { blindkey } = invoice;
+ let { type = "bech32", liquidAddress, id, invoice, user, tx } = req.body;
+ let { blindkey, currency, tip, amount, rate, network } = invoice;
+ let address, unconfidential, text;
- if (invoice.amount < 0) throw new Error("amount out of range");
- if (
- invoice.tip > invoice.amount ||
- invoice.tip > 1000000 ||
- invoice.tip < 0
- )
+ if (amount < 0) throw new Error("amount out of range");
+ if (tip > amount || tip > 1000000 || tip < 0)
throw new Error("tip amount out of range");
if (liquidAddress) {
@@ -48,18 +48,56 @@ app.post("/invoice", optionalAuth, async (req, res, next) => {
user = await db.User.findOne({
where: {
username: user.username
+ },
+ include: {
+ model: db.Account,
+ as: "account"
}
});
}
if (!user) throw new Error("user not provided");
- if (!invoice.currency) invoice.currency = user.currency;
- if (!invoice.rate) invoice.rate = store.rates[invoice.currency];
- if (invoice.tip > invoice.amount || invoice.tip > 1000000)
- throw new Error("tip is too large");
- if (invoice.tip < 0 || invoice.amount < 0)
- throw new Error("invalid amount");
- invoice.user_id = user.id;
- invoice.account_id = user.account_id;
+ if (!currency) currency = user.currency;
+ if (!rate) rate = store.rates[currency];
+ if (tip > amount || tip > 1000000) throw new Error("tip is too large");
+ if (tip < 0 || amount < 0) throw new Error("invalid amount");
+
+ if (user.account.pubkey) {
+ let { address, confidentialAddress } = await deriveAddress(
+ user.account,
+ type
+ );
+ if (confidentialAddress) {
+ address = confidentialAddress;
+ unconfidential = address;
+ } else {
+ address = address;
+ }
+ } else if (network !== "lightning") {
+ console.log(network, { bitcoin: bc, liquid: lq }[network])
+ address = await { bitcoin: bc, liquid: lq }[network].getNewAddress();
+ if (network === "liquid")
+ ({ unconfidential } = await lq.getAddressInfo(address));
+ }
+
+ invoice = {
+ ...invoice,
+ account_id: user.account.id,
+ address,
+ amount,
+ currency,
+ rate,
+ tip,
+ unconfidential,
+ user_id: user.id
+ };
+
+ if (network === "lightning") {
+ invoice.text = await derivePayRequest(invoice);
+ } else {
+ invoice.text = bip21(invoice, user.account);
+ }
+
+ console.log("TEXT", invoice.text)
l(
"creating invoice",
@@ -73,21 +111,7 @@ app.post("/invoice", optionalAuth, async (req, res, next) => {
if (!invoice.tip) invoice.tip = 0;
- const exists =
- invoice.text &&
- (await db.Invoice.findOne({
- where: {
- [Op.or]: {
- address: invoice.address || "",
- unconfidential: invoice.unconfidential || "",
- text: invoice.text
- }
- }
- }));
-
- invoice = exists
- ? await exists.update(invoice)
- : await db.Invoice.create(invoice);
+ invoice = await db.Invoice.create(invoice);
store.addresses[invoice.address] = user.username;
if (invoice.unconfidential) {
store.addresses[invoice.unconfidential] = user.username;
diff --git a/routes/payments.js b/routes/payments.js
index 298309e0..8b91d9fb 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -58,7 +58,6 @@ app.get("/except", adminAuth, (req, res) => {
});
app.post("/lightning/channel", lnRoutes.channel);
-app.post("/lightning/invoice", lnRoutes.invoice);
app.post("/lightning/query", auth, lnRoutes.query);
app.post("/lightning/send", auth, lnRoutes.send);
diff --git a/routes/users.js b/routes/users.js
index 61d5d33a..3063758d 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -360,114 +360,6 @@ app.post("/logout", optionalAuth, async (req, res) => {
res.send({});
});
-app.get("/address", async (req, res) => {
- let { network, type } = req.query;
- let address, confidentialAddress;
-
- let paymentType = {
- bech32: "p2wpkh",
- "p2sh-segwit": "p2sh",
- legacy: "p2pkh",
- }[type];
- if (!paymentType) paymentType = "p2wpkh";
-
- let hd, i, p, n;
- const mutex = new Mutex();
- const release = await mutex.acquire();
- try {
- if (network === "bitcoin") {
- if (!config.bitcoin) return res.code(500).send("Bitcoin not configured");
-
- address = await bc.getNewAddress("", type);
- store.bcAddressIndex = i + 1;
- return res.send({ address });
-
- p = bitcoin.payments;
- n = prod ? bitcoin.networks["bitcoin"] : bitcoin.networks["regtest"];
-
- i = parseInt(store.bcAddressIndex);
- hd = fromBase58(config.bitcoin.masterkey, n).derivePath(`m/0'/0'/${i}'`);
-
- // async request to node to bump its internal index but don't use result
- bc.getNewAddress().catch(console.error);
- res.send({ address, confidentialAddress });
-
- store.bcAddressIndex = i + 1;
- } else if (network === "liquid") {
- p = liquid.payments;
- n =
- liquid.networks[
- config.liquid.network === "mainnet" ? "liquid" : config.liquid.network
- ];
- paymentType = "p2sh";
-
- // async request to node to bump its internal index but don't use result
- lq.getNewAddress().catch((e) =>
- warn("Problem bumping liquid address index", e.message)
- );
-
- i = parseInt(store.lqAddressIndex);
-
- if (!i) {
- const { hdkeypath } = await lq.getAddressInfo(await lq.getNewAddress());
- const parts = hdkeypath.split("/");
- i = parseInt(parts[parts.length - 1].slice(0, -1));
- }
-
- l("liquid address index", i);
- if (!i) throw new Error("Problem generating address");
-
- hd = fromBase58(config.liquid.masterkey, n).derivePath(`m/0'/0'/${i}'`);
- store.lqAddressIndex = i + 1;
- } else {
- throw new Error("Unsupported network");
- }
- } finally {
- release();
- }
-
- if (paymentType !== "p2sh") {
- ({ address } = p[paymentType]({
- pubkey: hd.publicKey,
- network: n,
- }));
- } else {
- if (network === "liquid") {
- const p2wpkh = p.p2wpkh({
- pubkey: hd.publicKey,
- network: n,
- });
-
- const blindkey = fromPrivateKey(
- Buffer.from(config.liquid.blindkey, "hex"),
- hd.chainCode,
- n
- );
-
- ({ address, confidentialAddress } = p[paymentType]({
- redeem: p2wpkh,
- network: n,
- blindkey: blindkey.publicKey,
- }));
-
- lq.importBlindingKey(
- confidentialAddress,
- blindkey.privateKey.toString("hex")
- );
- } else {
- ({ address } = p[paymentType]({
- redeem: p.p2wpkh({
- pubkey: hd.publicKey,
- network: n,
- }),
- network: n,
- }));
- }
- }
-
- res.send({ address, confidentialAddress });
-});
-
app.post("/account", auth, async (req, res) => {
const { user } = req;
const {