aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-12-27 23:22:27 -0800
committerAdam Soltys <[email protected]>2023-12-28 22:51:02 -0800
commit02a373b8128c1544711d14ecd6ed19e20c7e3b0b (patch)
tree51327752718eac827273ada093fe6060d7184f56
parentaaf8dd49b07066e534408a2d41d84c518a45c2b6 (diff)
downloadcoinos-server-02a373b8128c1544711d14ecd6ed19e20c7e3b0b.tar.gz
coinos-server-02a373b8128c1544711d14ecd6ed19e20c7e3b0b.zip
liquid wip
-rw-r--r--index.js7
-rw-r--r--lib/auth.js5
-rw-r--r--lib/db.js23
-rw-r--r--lib/invoices.js4
-rw-r--r--lib/liquid.js3
-rw-r--r--lib/migrate.js52
-rw-r--r--lib/payments.js21
-rw-r--r--lib/utils.js5
-rw-r--r--routes/payments.js57
-rw-r--r--routes/users.js6
10 files changed, 135 insertions, 48 deletions
diff --git a/index.js b/index.js
index 5dbfd209..43cdde73 100644
--- a/index.js
+++ b/index.js
@@ -67,7 +67,8 @@ app.get("/lnurlp/:username", lnurl.lnurlp);
app.get("/lnurl/:id", lnurl.lnurl);
app.post("/freeze", payments.freeze);
-app.post("/bitcoin", payments.bitcoin);
+
+app.post("/confirm", payments.confirm);
app.post("/bitcoin/fee", auth, payments.fee);
app.post("/bitcoin/send", auth, payments.send);
@@ -106,5 +107,5 @@ let port = process.env.PORT || 3119;
app.listen({ host, port });
-process.on("unhandledRejection", () => err("Unhandled error"));
-process.on("uncaughtException", (e) => err(e.message));
+// process.on("unhandledRejection", (e) => err("Unhandled error", e.message));
+// process.on("uncaughtException", (e) => err(e.message));
diff --git a/lib/auth.js b/lib/auth.js
index 575d826e..b17a1f2d 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -1,3 +1,4 @@
+import migrate from "$lib/migrate";
import fastifyPassport from "@fastify/passport";
import { g } from "$lib/db";
import config from "$config";
@@ -36,8 +37,10 @@ export const jwtStrategy = new jwt.Strategy(
async (payload, next) => {
let user;
if (payload.id) {
- user = await g(`user:${payload.id}`);
+ let { id } = payload;
+ user = await g(`user:${id}`);
if (typeof user === "string") user = await g(`user:${user}`);
+ if (!user) migrate(id);
}
next(null, user);
diff --git a/lib/db.js b/lib/db.js
index d14e4962..c44c7177 100644
--- a/lib/db.js
+++ b/lib/db.js
@@ -41,16 +41,33 @@ export let db = createClient({
},
});
-async function connectWithRetry() {
+export let archive = createClient({
+ url: config.archive,
+ socket: {
+ reconnectStrategy: (retries) => Math.min(retries * 50, 5000),
+ },
+});
+
+async function dbReconnect() {
try {
await db.connect();
} catch (err) {
console.error("Failed to connect to Redis, retrying...", err);
- setTimeout(connectWithRetry, 5000); // Retry after 5 seconds
+ setTimeout(dbReconnect, 5000); // Retry after 5 seconds
+ }
+}
+
+async function archiveReconnect() {
+ try {
+ await archive.connect();
+ } catch (err) {
+ console.error("Failed to connect to Redis, retrying...", err);
+ setTimeout(archiveReconnect, 5000); // Retry after 5 seconds
}
}
-connectWithRetry();
+dbReconnect();
+archiveReconnect();
db.on("error", (e) => {
if (e.message.startsWith("getaddr")) return;
diff --git a/lib/invoices.js b/lib/invoices.js
index 23af1aa4..b5718e23 100644
--- a/lib/invoices.js
+++ b/lib/invoices.js
@@ -6,6 +6,7 @@ import { getUser, bip21, fail, SATS } from "$lib/utils";
import { types } from "$lib/payments";
import { v4 } from "uuid";
+import lq from "$lib/liquid";
import bc from "$lib/bitcoin";
import ln from "$lib/ln";
@@ -54,6 +55,9 @@ export let generate = async ({ invoice, user, sender, memo }) => {
} else if (type === types.bitcoin) {
hash = await bc.getNewAddress();
text = bip21(hash, invoice);
+ } else if (type === types.liquid) {
+ hash = await lq.getNewAddress();
+ text = bip21(hash, invoice);
} else if (type === types.internal) {
hash = id;
} else {
diff --git a/lib/liquid.js b/lib/liquid.js
new file mode 100644
index 00000000..ec8ec7cb
--- /dev/null
+++ b/lib/liquid.js
@@ -0,0 +1,3 @@
+import config from "$config";
+import rpc from "$lib/rpc";
+export default rpc(config.liquid);
diff --git a/lib/migrate.js b/lib/migrate.js
new file mode 100644
index 00000000..e8ae5993
--- /dev/null
+++ b/lib/migrate.js
@@ -0,0 +1,52 @@
+import { archive, db, g } from "$lib/db";
+
+export default async (id) => {
+ let user = JSON.parse(await archive.get(`user:${id}`));
+
+ if (typeof user === "string") {
+ id = user;
+ user = JSON.parse(await archive.get(`user:${id}`));
+ }
+
+ if (user) {
+ let multi = await db
+ .multi()
+ .set(`user:${user.username}`, id)
+ .set(`user:${user.pubkey}`, id)
+ .set(`user:${id}`, JSON.stringify(user))
+ .set(`${id}:contacts`, await archive.get(`${id}:contacts`))
+ .set(`credit:bitcoin:${id}`, await archive.get(`credit:bitcoin:${id}`) || 0)
+ .set(
+ `credit:lightning:${id}`,
+ await archive.get(`credit:lightning:${id}`) || 0,
+ )
+ .set(`${id}:lastlen`, (await archive.get(`${id}:lastlen`)) || 0)
+ .set(`${id}:cindex`, (await archive.get(`${id}:cindex`)) || 0)
+ .set(`balance:${id}`, (await archive.get(`balance:${id}`)) || 0)
+ .set(`pending:${id}`, (await archive.get(`pending:${id}`)) || 0);
+
+ let payments = await archive.lRange(`${id}:payments`, 0, -1);
+ await Promise.all(
+ payments.map(async (pid) => {
+ let p = await archive.get(`payment:${pid}`);
+ multi = await multi
+ .lPush(`${id}:payments`, pid)
+ .set(`payment:${pid}`, p);
+ }),
+ );
+
+ let invoices = await archive.lRange(`${id}:invoices`, 0, -1);
+ await Promise.all(
+ invoices.map(async (iid) => {
+ let inv = await archive.get(`invoice:${iid}`);
+ multi = await multi
+ .lPush(`${id}:invoices`, iid)
+ .set(`invoice:${iid}`, inv);
+ }),
+ );
+
+ await multi.exec();
+
+ return user;
+ }
+};
diff --git a/lib/payments.js b/lib/payments.js
index 604a78d8..e0aff192 100644
--- a/lib/payments.js
+++ b/lib/payments.js
@@ -23,6 +23,7 @@ export const types = {
lightning: "lightning",
fund: "fund",
classic: "classic",
+ liquid: "liquid",
};
export let debit = async ({
@@ -208,26 +209,6 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => {
return p;
};
-export let confirm = async (address, txid, vout) => {
- let id = `payment:${txid}:${vout}`;
- let p = await g(id);
- if (typeof p === "string") p = await g(`payment:${p}`);
- if (!p) return db.sAdd("missed", id);
- if (p.confirmed) return;
-
- p.confirmed = true;
- emit(p.uid, "payment", p);
-
- l("confirming", txid);
-
- let r = await db
- .multi()
- .set(`payment:${p.id}`, JSON.stringify(p))
- .decrBy(`pending:${p.uid}`, p.amount)
- .incrBy(`balance:${p.uid}`, p.amount)
- .exec();
-};
-
export let payLightning = async (user, payreq, amount, maxfee, memo) => {
let p;
let hash = payreq;
diff --git a/lib/utils.js b/lib/utils.js
index 1e12214b..0c382a0c 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -27,8 +27,9 @@ 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;
+export const bip21 = (address, { amount, memo, tip, type }) => {
+ let network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type];
+ let url = amount || memo ? `${network}:${address}?` : address;
if (amount)
url += `amount=${((amount + tip) / SATS).toFixed(8)}${memo ? "&" : ""}`;
if (memo) url += `message=${memo}`;
diff --git a/routes/payments.js b/routes/payments.js
index ba707358..f4846a64 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -6,10 +6,11 @@ import { db, g, s, t } from "$lib/db";
import { l, err, warn } from "$lib/logging";
import { bail, fail, btc, sats, SATS } from "$lib/utils";
import { requirePin } from "$lib/auth";
-import { debit, credit, confirm, types, payLightning } from "$lib/payments";
+import { debit, credit, types, payLightning } from "$lib/payments";
import { mqtt1, mqtt2 } from "$lib/mqtt";
import got from "got";
+import lq from "$lib/liquid";
import bc from "$lib/bitcoin";
import ln from "$lib/ln";
@@ -20,8 +21,8 @@ let catchUp = async () => {
for (let { txid } of txns) {
try {
if (seen.includes(txid)) continue;
- await got.post(`http://localhost:${process.env.PORT || 3119}/bitcoin`, {
- json: { txid, wallet: config.bitcoin.wallet },
+ await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, {
+ json: { txid, wallet: config.bitcoin.wallet, type: types.bitcoin },
});
seen.push(txid);
@@ -233,25 +234,36 @@ export default {
}
},
- async bitcoin({ body: { txid, wallet } }, res) {
+ async confirm({ body: { txid, wallet, type } }, res) {
+ let node = type === types.bitcoin ? bc : lq;
try {
if (wallet === config.bitcoin.wallet) {
- let { confirmations, details } = await bc.getTransaction(txid);
+ let { confirmations, details } = await node.getTransaction(txid);
for (let { address, amount, category, vout } of details) {
if (category !== "receive") continue;
let p = await g(`payment:${txid}:${vout}`);
if (typeof p === "string") p = await g(`payment:${p}`);
if (!p) {
- await credit(
- address,
- sats(amount),
- "",
- `${txid}:${vout}`,
- types.bitcoin,
- );
+ await credit(address, sats(amount), "", `${txid}:${vout}`, type);
} else if (confirmations >= 1) {
- await confirm(address, txid, vout);
+ let id = `payment:${txid}:${vout}`;
+ let p = await g(id);
+ if (typeof p === "string") p = await g(`payment:${p}`);
+ if (!p) return db.sAdd("missed", id);
+ if (p.confirmed) return;
+
+ p.confirmed = true;
+ emit(p.uid, "payment", p);
+
+ l("confirming", txid);
+
+ let r = await db
+ .multi()
+ .set(`payment:${p.id}`, JSON.stringify(p))
+ .decrBy(`pending:${p.uid}`, p.amount)
+ .incrBy(`balance:${p.uid}`, p.amount)
+ .exec();
}
}
}
@@ -263,20 +275,27 @@ export default {
}
},
- async fee({ body: { amount, address, feeRate: fee_rate }, user }, res) {
+ async fee(
+ {
+ body: { amount, address, feeRate: fee_rate, type = types.bitcoin },
+ user,
+ },
+ res,
+ ) {
try {
+ let node = type === types.bitcoin ? bc : lq;
amount = parseInt(amount);
let replaceable = true;
let ourfee = Math.round(amount * config.fee);
- let credit = await g(`credit:bitcoin:${user.id}`);
+ let credit = await g(`credit:${type}:${user.id}`);
let covered = Math.min(credit, ourfee) || 0;
ourfee -= covered;
let outs = [{ [address]: btc(amount) }];
- let raw = await bc.createRawTransaction([], outs, 0, replaceable);
+ let raw = await node.createRawTransaction([], outs, 0, replaceable);
- let tx = await bc.fundRawTransaction(raw, {
+ let tx = await node.fundRawTransaction(raw, {
fee_rate,
replaceable,
subtractFeeFromOutputs: [],
@@ -292,9 +311,9 @@ export default {
return bail(res, "insufficient funds");
outs = [{ [address]: btc(amount - ourfee) }];
- raw = await bc.createRawTransaction([], outs, 0, replaceable);
+ raw = await node.createRawTransaction([], outs, 0, replaceable);
- tx = await bc.fundRawTransaction(raw, {
+ tx = await node.fundRawTransaction(raw, {
fee_rate,
replaceable,
subtractFeeFromOutputs: [0],
diff --git a/routes/users.js b/routes/users.js
index a0796d63..5ebb2878 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -1,4 +1,5 @@
import { g, s, db } from "$lib/db";
+import migrate from "$lib/migrate";
import config from "$config";
import store from "$lib/store";
import {
@@ -278,6 +279,11 @@ export default {
let uid = await g(`user:${username}`);
let user = await getUser(username);
+ if (!user) {
+ user = await migrate(username);
+ if (user) uid = user.id;
+ }
+
if (
!user ||
(user.password &&