aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2025-02-07 22:23:44 -0800
committerAdam Soltys <[email protected]>2025-02-07 22:23:44 -0800
commite68c302e1082da29020d1efa932f986032ae5ce2 (patch)
tree25a46c19ec2d528be4f2d8e4587765b1aaebd90a
parenteae37f75a77236d7929d8ce1f573c0f5bd428c79 (diff)
downloadcoinos-server-e68c302e1082da29020d1efa932f986032ae5ce2.tar.gz
coinos-server-e68c302e1082da29020d1efa932f986032ae5ce2.zip
nwc apps endpoints
-rw-r--r--index.ts11
-rw-r--r--lib/nwc.ts28
-rw-r--r--routes/users.ts67
3 files changed, 94 insertions, 12 deletions
diff --git a/index.ts b/index.ts
index bbcbfbbf..31ca79fe 100644
--- a/index.ts
+++ b/index.ts
@@ -113,7 +113,9 @@ app.post("/superuser", users.superuser);
app.get("/verify/:code", users.verify);
app.post("/request", auth, users.request);
app.post("/forgot", users.forgot);
-app.post("/login", {
+app.post(
+ "/login",
+ {
config: {
rateLimit: {
max: 5,
@@ -122,10 +124,15 @@ app.post("/login", {
},
},
},
- users.login);
+ users.login,
+);
app.post("/printer", users.printer);
app.get("/challenge", users.challenge);
app.post("/nostrLogin", users.nostrLogin);
+app.get("/app/:pubkey", auth, users.app);
+app.get("/apps", auth, users.apps);
+app.post("/app", auth, users.updateApp);
+app.post("/apps/delete", auth, users.deleteApp);
app.get("/subscriptions", auth, users.subscriptions);
app.post("/subscription", auth, users.subscription);
diff --git a/lib/nwc.ts b/lib/nwc.ts
index 4055a9f4..9e1ad597 100644
--- a/lib/nwc.ts
+++ b/lib/nwc.ts
@@ -52,11 +52,11 @@ export default () => {
if (!methods.includes(method)) return;
- const uid = await g(pubkey);
- const user = await g(`user:${uid}`);
+ const app = await g(pubkey);
+ const user = await g(`user:${app.uid}`);
try {
- const result = await handle(method, params, user, ev);
+ const result = await handle(method, params, ev, app, user);
const payload = JSON.stringify({ result_type: method, ...result });
content = await nip04.encrypt(sk, pubkey, payload);
@@ -77,7 +77,7 @@ export default () => {
err(
"problem with nwc",
pubkey,
- user?.username,
+ app?.name,
JSON.stringify(params),
e.message,
);
@@ -89,7 +89,7 @@ export default () => {
});
};
-const handle = (method, params, user, ev) =>
+const handle = (method, params, ev, app, user) =>
({
async pay_invoice() {
const { invoice: pr } = params;
@@ -102,13 +102,17 @@ const handle = (method, params, user, ev) =>
const recipient = await g(`user:${invoice.uid}`);
if (recipient?.username !== "mint") {
- const { id: preimage } = await sendInternal({
+ const { id: pid } = await sendInternal({
amount,
invoice,
recipient,
sender: user,
});
+ const preimage = pid;
+ if (app.pubkey !== user.pubkey)
+ await db.lPush(`${pubkey}:payments`, pid);
+
if (invoice.memo?.includes("9734")) {
const { invoices } = await ln.listinvoices({ invstring: pr });
const inv = invoices[0];
@@ -124,12 +128,15 @@ const handle = (method, params, user, ev) =>
return result({ preimage });
}
}
- await sendLightning({
+
+ const { id: pid } = await sendLightning({
amount,
user,
pr,
});
+ if (app.pubkey !== user.pubkey) await db.lPush(`${pubkey}:payments`, pid);
+
for (let i = 0; i < 10; i++) {
const { pays } = await ln.listpays(pr);
const p = pays.find((p) => p.status === "complete");
@@ -144,7 +151,8 @@ const handle = (method, params, user, ev) =>
},
async pay_keysend() {
- const { amount, pubkey, tlv_records: extratlvs } = params;
+ const { amount: amount_msat, pubkey, tlv_records: extratlvs } = params;
+ const amount = Math.round(amount_msat / 1000);
try {
const { payment_hash } = await sendKeysend({
@@ -276,7 +284,9 @@ const handle = (method, params, user, ev) =>
async list_transactions() {
const { from, until, limit = 10, offset = 0, type } = params;
- const payments = await db.lRange(`${user.id}:payments`, 0, -1);
+ const { pubkey } = app;
+ const k = pubkey === user.pubkey ? user.id : pubkey;
+ const payments = await db.lRange(`${k}:payments`, 0, -1);
let transactions = [];
for (const pid of payments) {
diff --git a/routes/users.ts b/routes/users.ts
index 16c2d407..ddadec5f 100644
--- a/routes/users.ts
+++ b/routes/users.ts
@@ -5,7 +5,7 @@ import { requirePin } from "$lib/auth";
import { db, g, s } from "$lib/db";
import { err, l, warn } from "$lib/logging";
import { mail, templates } from "$lib/mail";
-import { getNostrUser, getProfile } from "$lib/nostr";
+import { getNostrUser, getProfile, serverPubkey } from "$lib/nostr";
import { reconcile, types } from "$lib/payments";
import register from "$lib/register";
import { emit } from "$lib/sockets";
@@ -261,6 +261,7 @@ export default {
}
user.haspin = !!user.pin;
+ user.destination = user.destination.trim();
await s(`user:${user.pubkey}`, user.id);
await s(
`user:${user.username.toLowerCase().replace(/\s/g, "")}`,
@@ -821,4 +822,68 @@ export default {
res.code(500).send({ error: "Failed to generate LittleFS image" });
}
},
+
+ async app(req, res) {
+ const { pubkey } = req.params;
+ const app = await g(pubkey);
+ res.send(app);
+ },
+
+ async apps(req, res) {
+ const { user } = req;
+ const pubkeys = await db.sMembers(`${user.id}:apps`);
+ const apps = await Promise.all(pubkeys.map((p) => g(p)));
+
+ const { host } = new URL(process.env.URL);
+ const lud16 = `${user.username}@${host}`;
+ const relay = encodeURIComponent(config.nostr);
+
+ await Promise.all(
+ apps.map(async (a) => {
+ if (a.secret)
+ a.nwc = `nostr+walletconnect://${serverPubkey}?relay=${relay}&secret=${a.secret}&lud16=${lud16}`;
+
+ a.spent = (await db.lRange(`${a.pubkey}:payments`, 0, -1)).reduce(
+ (a, b) => a + (Math.abs(b.amount) + b.fee + b.ourfee),
+ 0,
+ );
+ }),
+ );
+
+ res.send(apps);
+ },
+
+ async updateApp(req, res) {
+ try {
+ let { secret, pubkey, max_amount, budget_renewal, name } = req.body;
+ const { user } = req;
+ const uid = user.id;
+ let app = await g(pubkey);
+ if (app && uid !== app.uid) fail("Unauthorized");
+ if (secret) pubkey = getPublicKey(secret);
+ app = { ...app, pubkey, max_amount, budget_renewal, name, uid, secret };
+ await s(pubkey, app);
+ await db.sAdd(`${uid}:apps`, pubkey);
+ res.send({});
+ } catch (e) {
+ console.log(e);
+ bail(res, e.message);
+ }
+ },
+
+ async deleteApp(req, res) {
+ try {
+ const { user } = req;
+ const uid = user.id;
+ const { pubkey } = req.body;
+ const app = await g(pubkey);
+ if (app && uid !== app.uid) fail("Unauthorized");
+ await db.sRem(`${uid}:apps`, pubkey);
+ await db.del(pubkey);
+ res.send({});
+ } catch (e) {
+ console.log(e);
+ bail(res, e.message);
+ }
+ },
};