aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-01-13 23:50:30 -0800
committerAdam Soltys <[email protected]>2023-01-13 23:50:30 -0800
commit6bafea9bffe24d2227d269812a73a9a513d92ddf (patch)
tree6dd463ffa410f4b7eebc870b24db305785414aa0
parent8a114c50efaac41767042766f05390aa906da157 (diff)
downloadcoinos-server-6bafea9bffe24d2227d269812a73a9a513d92ddf.tar.gz
coinos-server-6bafea9bffe24d2227d269812a73a9a513d92ddf.zip
internal payments
-rw-r--r--lib/sendInternal.js242
-rw-r--r--routes/invoices.js4
-rw-r--r--routes/lightning/receive.js10
-rw-r--r--routes/payments.js9
-rw-r--r--routes/send.js1
-rw-r--r--routes/users.js159
6 files changed, 119 insertions, 306 deletions
diff --git a/lib/sendInternal.js b/lib/sendInternal.js
index 5a8b0b9d..f801c9cd 100644
--- a/lib/sendInternal.js
+++ b/lib/sendInternal.js
@@ -6,197 +6,111 @@ import { v4 } from "uuid";
import { notify } from "$lib/notifications";
import { l, err } from "$lib/logging";
import { fail } from "$lib/utils";
+import ln from "$lib/ln";
+import { g, s, rd } from "$lib/redis";
const { HOSTNAME: hostname } = process.env;
export default async (
- { amount, address, payreq, unconfidential, asset, memo, username, tip },
+ { amount, address, payreq, unconfidential, memo, username, tip },
user
) => {
amount = parseInt(amount);
- if (!asset) asset = config.liquid.btcasset;
-
if (!amount || amount < 0) fail("Amount must be greater than zero");
- return db.transaction(async transaction => {
- let account;
- if (user.account.asset === asset && !user.account.pubkey) {
- account = await db.Account.findOne({
- where: {
- id: user.account.id,
- pubkey: null
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
- } else {
- account = await db.Account.findOne({
- where: {
- user_id: user.id,
- asset,
- pubkey: null
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
- }
-
- if (!account) throw new Error("Account not found");
+ let fee = 0;
+ let recipient;
- if (account.balance < amount) {
- throw new Error("Insufficient funds");
- }
+ let t = async () => {
+ await rd.watch(`user:${user_id}`);
+ user = await g(`user:${user_id}`);
+ if (user.balance < amount) throw new Error("Insufficient funds");
- let fee = 0;
+ user.balance -= amount;
+ let m = await rd.multi();
+ await s(`user:${user.id}`, user);
- await account.decrement({ balance: amount }, { transaction });
- await account.reload({ transaction });
+ if (username && (recipient = await g(`user:${username}`))) {
+ let { payment_hash: h } = await ln.decode(payreq);
+ let invoice = payreq && (await g(`invoice:${h}`));
- let params, recipient, payment;
-
- if (username) {
- recipient = await db.User.findOne(
- {
- where: { username },
- include: {
- model: db.Account,
- as: "account"
- }
- },
- { transaction }
- );
-
- params = {
- where: { user_id: recipient.id },
- order: [["id", "DESC"]]
+ let t = async () => {
+ await rd.watch(`user:${recipient.id}`);
+ recipient = await g(`user:${recipient.id}`);
+ recipient.balance += amount;
+ let m = await rd.multi();
+ await s(`user:${recipient.id}`, recipient);
+ if (!(await m.exec())) t();
};
- if (address) params.where.address = address;
- else if (payreq) params.where.text = payreq;
- let invoice = (address || payreq) && (await db.Invoice.findOne(params));
-
- let a2;
- let acc = {
- user_id: recipient.id,
- asset,
- pubkey: null
- };
-
- if (recipient.account.asset === asset && !recipient.account.pubkey)
- a2 = recipient.account;
- else {
- a2 = await db.Account.findOne({
- where: acc,
- lock: transaction.LOCK.UPDATE,
- transaction
- });
- }
-
- if (a2) {
- await a2.increment({ balance: amount }, { transaction });
- await a2.reload({ transaction });
- } else {
- let name = asset.substr(0, 6);
- let domain;
- let ticker = asset.substr(0, 3).toUpperCase();
- let precision = 8;
-
- if (store.assets[asset]) {
- ({ domain, ticker, precision, name } = store.assets[asset]);
- } else {
- const existing = await db.Account.findOne({
- where: {
- asset
- },
- order: [["id", "ASC"]],
- limit: 1,
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- if (existing) {
- ({ domain, ticker, precision, name } = existing);
- }
- }
+ await t();
- acc = { ...acc, ...{ domain, ticker, precision, name } };
- acc.balance = amount;
- acc.pending = 0;
- acc.network = "liquid";
- a2 = await db.Account.create(acc, { transaction });
- }
-
- params = {
- amount,
- account_id: a2.id,
- user_id: recipient.id,
- rate: store.rates[recipient.currency],
- currency: recipient.currency,
- confirmed: true,
- hash: `#${v4().substr(0, 6)} Payment from ${user.username}`,
- memo,
- network: "COINOS",
- received: true,
- with_id: user.id
- };
+ if (!(await m.exec())) t();
+ }
+ await t();
- if (invoice) {
- let { tip } = invoice;
- params.invoice_id = invoice.id;
- if (tip) {
- params.tip = tip;
- params.amount -= tip;
- }
+ let p2 = {
+ id: v4(),
+ amount,
+ user_id: recipient.id,
+ rate: store.rates[recipient.currency],
+ currency: recipient.currency,
+ memo,
+ type: "internal",
+ with_id: user.id,
+ created_at: Date.now()
+ };
- await invoice.increment({ received: amount }, { transaction });
- await invoice.reload({ transaction });
+ if (invoice) {
+ let { tip } = invoice;
+ params.invoice_id = invoice.id;
+ if (tip) {
+ params.tip = tip;
+ params.amount -= tip;
}
- let p2 = await db.Payment.create(params, { transaction });
-
- p2 = p2.get({ plain: true });
- p2.account = a2.get({ plain: true });
- if (invoice) p2.invoice = invoice.get({ plain: true });
+ invoice.received += amount;
+ await s(`invoice:${h}`, invoice);
+ }
- emit(recipient.username, "payment", p2);
- emit(recipient.username, "account", p2.account);
+ if (invoice) p2.invoice = invoice;
+ await s(`payment:${p2.id}`, p2);
+ await rd.lPush(`${recipient.id}:payments`, p2.id);
- l("received internal", recipient.username, amount);
- notify(recipient, `Received ${amount} ${a2.ticker} sats`);
- callWebhook(invoice, p2);
- }
+ emit(recipient.username, "payment", p2);
- params = {
- amount: -amount,
- account_id: account.id,
- memo,
- user_id: user.id,
- rate: store.rates[user.currency],
- currency: user.currency,
- confirmed: true,
- with_id: recipient && recipient.id,
- hash: `#${v4().substr(0, 6)} ${
- username ? `Payment to ${username}` : "Internal Transfer"
- }`,
- network: "COINOS"
- };
+ l("received internal", recipient.username, amount);
+ notify(recipient, `Received ${amount} sats`);
+ callWebhook(invoice, p2);
+ };
- if (!username) {
- l("creating redeemable payment");
- params.redeemcode = v4();
- params.hash = `${hostname}/redeem/${params.redeemcode}`;
- }
+ let p1 = {
+ id: v4(),
+ amount: -amount,
+ memo,
+ user_id: user.id,
+ rate: store.rates[user.currency],
+ currency: user.currency,
+ with_id: recipient && recipient.id,
+ hash: `#${v4().substr(0, 6)} ${
+ username ? `Payment to ${username}` : "Internal Transfer"
+ }`,
+ type: "internal",
+ created_at: Date.now()
+ };
- payment = await db.Payment.create(params, { transaction });
+ if (!username) {
+ l("creating redeemable payment");
+ params.redeemcode = v4();
+ params.hash = `${hostname}/redeem/${params.redeemcode}`;
+ }
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
+ await s(`payment:${p1.id}`, p1);
+ await rd.lPush(`${user.id}:payments`, p1.id);
- l("sent internal", user.username, -payment.amount);
+ l("sent internal", user.username, -p1.amount);
- emit(user.username, "payment", payment);
- emit(user.username, "account", account);
+ emit(user.username, "payment", p1);
- return payment;
- });
+ return p1;
};
diff --git a/routes/invoices.js b/routes/invoices.js
index f7ab78cf..10a80b90 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -9,7 +9,6 @@ import ln from "$lib/ln";
app.get("/invoice", async ({ query: { id } }, res) => {
let invoice = await g(`invoice:${id}`);
invoice.user = await g(`user:${invoice.user_id}`);
- console.log("INV", invoice)
res.send(invoice);
});
@@ -48,7 +47,8 @@ app.post(
rate,
tip,
user_id,
- text
+ text,
+ received: 0
};
l(
diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js
index a37dc716..ffb0ad95 100644
--- a/routes/lightning/receive.js
+++ b/routes/lightning/receive.js
@@ -1,3 +1,4 @@
+import { v4 } from "uuid";
import store from "$lib/store";
import { emit } from "$lib/sockets";
import config from "$config";
@@ -35,7 +36,9 @@ let handle = async () => {
let amount = parseInt(received) - tip;
if (amount < 0) throw new Error("amount out of range");
+ let id = v4();
let payment = {
+ id,
user_id,
hash,
memo,
@@ -44,7 +47,8 @@ let handle = async () => {
secret,
rate,
tip,
- invoice_id: invoice.id
+ invoice_id: invoice.id,
+ created_at: Date.now()
};
total = amount + tip;
@@ -64,6 +68,10 @@ let handle = async () => {
await t();
payment.invoice = invoice;
+
+ await s(`payment:${id}`, payment);
+ await rd.lPush(`${user.id}:payments`, id);
+
callWebhook(invoice, payment);
emit(user.username, "payment", payment);
diff --git a/routes/payments.js b/routes/payments.js
index 7b094ccc..b328e763 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -1,6 +1,7 @@
import app from "$app";
import config from "$config";
import store from "$lib/store";
+import { g, rd } from "$lib/redis";
import { auth, adminAuth, optionalAuth } from "$lib/passport";
import fs from "fs";
@@ -22,7 +23,7 @@ app.post("/lightning/send", auth, lnRoutes.send);
app.get(
"/payments",
auth,
- async ({ uuid, query: { start, end, limit, offset } }, res) => {
+ async ({ user: { id }, query: { start, end, limit, offset } }, res) => {
if (limit) limit = parseInt(limit);
if (offset) offset = parseInt(offset);
@@ -30,8 +31,10 @@ app.get(
// if (start) where.createdAt[Op.gte] = new Date(parseInt(start));
// if (end) where.createdAt[Op.lte] = new Date(parseInt(end));
- let payments = await redis.get(`${uuid}:payments`);
- res.send({ payments, total });
+ let payments = (await rd.lRange(`${id}:payments`, 0, -1)) || [];
+ payments = await Promise.all(payments.map(id => g(`payment:${id}`)));
+ payments = payments.filter(p => p);
+ res.send({ payments, total: payments.length });
}
);
diff --git a/routes/send.js b/routes/send.js
index 591b5ab7..3f71a496 100644
--- a/routes/send.js
+++ b/routes/send.js
@@ -8,6 +8,7 @@ export default async (req, res, next) => {
let payment = await sendInternal(req.body, req.user);
res.send(payment);
} catch (e) {
+ console.log(e)
err("problem sending internal payment", e.message);
return res.code(500).send(e.message);
}
diff --git a/routes/users.js b/routes/users.js
index 97ff9dd4..c0324803 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -84,72 +84,53 @@ app.post("/register", async (req, res) => {
}
});
-app.post("/disable2fa", auth, async (req, res) => {
- try {
- let {
- user,
- body: { token }
- } = req;
-
- if (user.twofa && !authenticator.check(token, user.otpsecret)) {
- return res.code(401).send("2fa required");
- }
-
- user.twofa = false;
- await user.save();
- emit(user.username, "user", user);
- emit(user.username, "otpsecret", user.otpsecret);
- l("disabled 2fa", user.username);
- res.send({});
- } catch (e) {
- res.code(500).send("Problem disabling 2fa");
+app.post("/disable2fa", auth, async ({ user, body: { token } }, res) => {
+ let { id, twofa, username, otpsecret } = user;
+ if (twofa && !authenticator.check(token, otpsecret)) {
+ return res.code(401).send("2fa required");
}
+
+ user.twofa = false;
+ await s(`user:${id}`, user);
+ emit(username, "user", user);
+ emit(username, "otpsecret", user.otpsecret);
+ l("disabled 2fa", username);
+ res.send({});
});
-app.post("/2fa", auth, async (req, res) => {
- let { user } = req;
- try {
- const isValid = authenticator.check(req.body.token, req.user.otpsecret);
- if (isValid) {
- user.twofa = true;
- user.save();
- emit(user.username, "user", req.user);
- } else {
- return res.code(500).send("Invalid token");
- }
- } catch (e) {
- err("error setting up 2fa", e);
+app.post("/2fa", auth, async ({ user, body: { token } }, res) => {
+ let { id, otpsecret, username } = user;
+ const isValid = authenticator.check(token, otpsecret);
+ if (isValid) {
+ user.twofa = true;
+ await s(`user:${id}`, user);
+ emit(username, "user", user);
+ } else {
+ return res.code(500).send("Invalid token");
}
- l("enabled 2fa", user.username);
+ l("enabled 2fa", username);
res.send({});
});
-app.post("/user", auth, async (req, res) => {
+app.post("/user", auth, async ({ user, body: { confirm, password, pin, newpin, username }}, res) => {
try {
let { user } = req;
l("updating user", user.username);
- let { confirm, password, pin, newpin, username } = req.body;
-
if (user.pin && !(pin === user.pin)) throw new Error("Pin required");
if (typeof newpin !== "undefined") user.pin = newpin;
if (!user.pin) user.pin = null;
let exists;
-
- if (username)
- exists = await db.User.findOne({
- where: { username }
- });
+ if (username) exists = await g(`user:${username}`);
let token;
if (user.username !== username && exists) {
err("username taken", username, user.username, exists.username);
throw new Error("Username taken");
} else if (username) {
- store.sockets[username] = store.sockets[user.username];
if (user.username !== username)
l("changing username", user.username, username);
user.username = username;
@@ -195,99 +176,6 @@ app.post("/user", auth, async (req, res) => {
}
});
-app.post("/keys", auth, async (req, res) => {
- const { key: hex } = req.body;
- const key = await db.Key.create({
- user_id: req.user.id,
- hex
- });
- emit(req.user.username, "key", key);
- res.send(key);
-});
-
-app.post("/keys/delete", auth, async (req, res) => {
- const { hex } = req.body;
- (
- await db.Key.findOne({
- where: {
- user_id: req.user.id,
- hex
- }
- })
- ).destroy();
-});
-
-app.post("/updateSeeds", auth, async (req, res) => {
- let { user } = req;
- let { seeds } = req.body;
- let keys = Object.keys(seeds);
-
- for (let i = 0; i < keys.length; i++) {
- let id = keys[i];
- let seed = seeds[id];
- await db.Account.update(
- { seed },
- {
- where: { id, user_id: user.id }
- }
- );
-
- const account = await db.Account.findOne({
- where: { id }
- });
-
- emit(user.username, "account", account);
- }
-
- res.send({});
-});
-
-app.post("/accounts/delete", auth, async (req, res) => {
- const { id } = req.body;
- const account = await db.Account.findOne({ where: { id } });
- if (account) await account.destroy();
- res.send({});
-});
-
-app.post("/accounts", auth, async (req, res) => {
- const {
- name,
- seed,
- pubkey,
- ticker,
- precision,
- path,
- privkey,
- network
- } = req.body;
- const { user } = req;
-
- let account = await db.Account.create({
- user_id: user.id,
- asset: config.liquid.btcasset,
- balance: 0,
- pending: 0,
- name,
- ticker,
- precision,
- pubkey,
- privkey,
- seed,
- path,
- network
- });
-
- emit(user.username, "account", account);
-
- if (pubkey) user.index++;
- user.account_id = account.id;
- await user.save();
- user.account = account;
- emit(user.username, "user", user);
-
- res.send(account);
-});
-
let login = async (req, res) => {
try {
const { params, sig, key, password, username, token: twofa } = req.body;
@@ -337,7 +225,6 @@ let login = async (req, res) => {
let token = jwt.sign(payload, config.jwt);
res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
user = pick(user, whitelist);
- console.log("SENDING", user, token)
res.send({ user, token });
} catch (e) {
console.log(e);