aboutsummaryrefslogtreecommitdiffstats
path: root/routes
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-28 14:14:30 -0800
committerAdam Soltys <[email protected]>2026-02-28 14:14:30 -0800
commita1632ae402d11fdd7ffc360de97c55e0b4fad677 (patch)
treeaad5a1ea73f95dcd055c1d0b4ebf2708c36b4e01 /routes
parent76c7e52172f3dc25ee109434157d4d982ad9d2bd (diff)
downloadcoinos-server-a1632ae402d11fdd7ffc360de97c55e0b4fad677.tar.gz
coinos-server-a1632ae402d11fdd7ffc360de97c55e0b4fad677.zip
migrate to hono
Diffstat (limited to 'routes')
-rw-r--r--routes/ecash.ts63
-rw-r--r--routes/email.ts7
-rw-r--r--routes/info.ts16
-rw-r--r--routes/invoices.ts51
-rw-r--r--routes/items.ts59
-rw-r--r--routes/lnurl.ts67
-rw-r--r--routes/locations.ts20
-rw-r--r--routes/nostr.ts139
-rw-r--r--routes/payments.ts358
-rw-r--r--routes/rates.ts12
-rw-r--r--routes/shopify.ts19
-rw-r--r--routes/square.ts24
-rw-r--r--routes/users.ts579
13 files changed, 681 insertions, 733 deletions
diff --git a/routes/ecash.ts b/routes/ecash.ts
index b4c16404..fc6a8d3a 100644
--- a/routes/ecash.ts
+++ b/routes/ecash.ts
@@ -11,7 +11,7 @@ import { v4 } from "uuid";
import { PaymentType } from "$lib/types";
const { ecash: type } = PaymentType;
-Error.stackTraceLimit = 100; // Set this to the desired limit
+Error.stackTraceLimit = 100;
const sendCash = async ({ amount, user }) => {
const id = v4();
@@ -28,39 +28,33 @@ const sendCash = async ({ amount, user }) => {
};
export default {
- async save(req, res) {
- const {
- body: { token },
- } = req;
+ async save(c) {
+ const { token } = await c.req.json();
try {
const id = v4();
await s(`cash:${id}`, token);
- res.send({ id });
+ return c.json({ id });
} catch (e) {
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async get(req, res) {
- const {
- params: { id },
- } = req;
+ async get(c) {
+ const id = c.req.param("id");
try {
const token = await get(id);
const status = await check(token);
- res.send({ token, status });
+ return c.json({ token, status });
} catch (e) {
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async claim(req, res) {
- const {
- body: { token },
- user,
- } = req;
+ async claim(c) {
+ const { token } = await c.req.json();
+ const user = c.get("user");
try {
const amount = await claim(token);
@@ -79,25 +73,25 @@ export default {
await credit({ hash, amount, memo, ref: user.id, type });
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async mint(req, res) {
- const { body, user } = req;
+ async mint(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
const amount = parseInt(body.amount);
fail("disabled");
- res.send(await sendCash({ amount, user }));
+ return c.json(await sendCash({ amount, user }));
},
- async melt(req, res) {
- let {
- body: { amount, bolt11: hash, preimage },
- user,
- } = req;
+ async melt(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
+ let { amount, bolt11: hash, preimage } = body;
try {
amount = Math.round(amount / 1000);
const ref = preimage;
@@ -133,15 +127,16 @@ export default {
l(user.username, "sent", type, amount);
emit(user.id, "payment", p);
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async receive(req, res) {
+ async receive(c) {
try {
- const { id, proofs, mint, memo } = req.body;
+ const body = await c.req.json();
+ const { id, proofs, mint, memo } = body;
const { uid: ref } = await getInvoice(id);
const amount = await claim(
@@ -153,11 +148,11 @@ export default {
await credit({ hash: id, amount, memo, ref, type });
- res.send({ id });
+ return c.json({ id });
} catch (e) {
console.log(e);
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/email.ts b/routes/email.ts
index 25c90ee7..ff6ffaeb 100644
--- a/routes/email.ts
+++ b/routes/email.ts
@@ -6,8 +6,9 @@ import { SESClient } from "@aws-sdk/client-ses";
import { SendEmailCommand } from "@aws-sdk/client-ses";
export default {
- async send({ body }, res) {
+ async send(c) {
try {
+ const body = await c.req.json();
const { email, message, username, token: response } = body;
const Charset = "UTF-8";
@@ -48,9 +49,9 @@ export default {
}),
);
- res.send({ ok: true });
+ return c.json({ ok: true });
} else {
- bail(res, "failed captcha");
+ return bail(c, "failed captcha");
}
} catch (e) {
console.log(e);
diff --git a/routes/info.ts b/routes/info.ts
index 35d1c315..adad02c6 100644
--- a/routes/info.ts
+++ b/routes/info.ts
@@ -5,23 +5,15 @@ import ln from "$lib/ln";
import { getDecodedToken } from "@cashu/cashu-ts";
export default {
- async health(_, res) {
+ async health(c) {
const status = getHealthStatus();
const httpStatus = status.healthy ? 200 : 503;
- res.status(httpStatus).send(status);
+ return c.json(status, httpStatus);
},
- async balances(_, res) {
+ async balances(c) {
let total = 0;
- // for await (const k of db.scanIterator({ MATCH: "balance:*" })) {
- // total += parseInt(await db.get(k));
- // }
- //
- // for await (const k of archive.scanIterator({ MATCH: "balance:*" })) {
- // total += parseInt(await archive.get(k));
- // }
-
const funds = await ln.listfunds();
const lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0));
const lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0));
@@ -34,6 +26,6 @@ export default {
lnwallet,
};
- res.send(info);
+ return c.json(info);
},
};
diff --git a/routes/invoices.ts b/routes/invoices.ts
index 7f44a28d..6fc0b67a 100644
--- a/routes/invoices.ts
+++ b/routes/invoices.ts
@@ -6,11 +6,9 @@ import { bail, fail, fields, getAccount, getInvoice, getUser } from "$lib/utils"
import rpc from "@coinos/rpc";
export default {
- async get(req, res) {
+ async get(c) {
try {
- const {
- params: { id },
- } = req;
+ const id = c.req.param("id");
if (id === "undefined") fail("invalid id");
const invoice = await getInvoice(id);
@@ -21,36 +19,37 @@ export default {
invoice.items ||= [];
}
- if (invoice) res.send(invoice);
+ if (invoice) return c.json(invoice);
else fail("invoice not found");
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async create(req, res) {
- let { body, user } = req;
+ async create(c) {
+ const body = await c.req.json();
+ let user = c.get("user");
const { invoice } = body;
if (body.user) user = body.user;
- if (req.user.username === user.username) invoice.own = true;
+ if (c.get("user")?.username === user.username) invoice.own = true;
else invoice.own = false;
try {
const result = await generate({ invoice, user });
- res.send(result);
+ return c.json(result);
} catch (e) {
console.trace();
console.log(e);
- err("problem generating invoice", req.user?.username, body.user?.username, e.message);
- bail(res, e.message);
+ err("problem generating invoice", c.get("user")?.username, body.user?.username, e.message);
+ return bail(c, e.message);
}
},
- async update(req, res) {
+ async update(c) {
try {
- const { id } = req.params;
- const { body } = req;
+ const id = c.req.param("id");
+ const body = await c.req.json();
const { tip, webhook, secret, received } = body.invoice;
if (tip < 0) fail("Invalid tip");
@@ -61,7 +60,7 @@ export default {
if (typeof tip !== "undefined") invoice.tip = tip;
if (webhook && secret) {
- if (invoice.uid !== req.user?.id) fail("Unauthorized");
+ if (invoice.uid !== c.get("user")?.id) fail("Unauthorized");
invoice.webhook = webhook;
invoice.secret = secret;
}
@@ -70,30 +69,32 @@ export default {
await s(`invoice:${id}`, invoice);
- res.send(invoice);
+ return c.json(invoice);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async list(req, res) {
- const { id } = req.user;
+ async list(c) {
+ const user = c.get("user");
+ const { id } = user;
let invoices = await db.lRange(`${id}:invoices`, 0, -1);
invoices = (await Promise.all(invoices.map((i) => getInvoice(i)))).filter(Boolean);
- res.send(invoices);
+ return c.json(invoices);
},
- async sign(req, res) {
+ async sign(c) {
try {
- const { address, message, type = "bitcoin" } = req.body;
+ const body = await c.req.json();
+ const { address, message, type = "bitcoin" } = body;
const node = rpc(config[type]);
if (config[type].walletpass) await node.walletPassphrase(config[type].walletpass, 300);
const signature = await node.signMessage({ address, message });
- res.send({ signature });
+ return c.json({ signature });
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/items.ts b/routes/items.ts
index df1d86ea..b9706b4c 100644
--- a/routes/items.ts
+++ b/routes/items.ts
@@ -3,10 +3,8 @@ import { v4 } from "uuid";
import { bail, fail } from "$lib/utils";
export default {
- async list(req, res) {
- const {
- params: { id: uid },
- } = req;
+ async list(c) {
+ const uid = c.req.param("id");
const items = [];
for (const id of await db.lRange(`${uid}:items`, 0, -1)) {
const item = await g(`item:${id}`);
@@ -14,27 +12,24 @@ export default {
else await db.lRem(`${uid}:items`, 0, id);
}
- res.send(items);
+ return c.json(items);
},
- async get(req, res) {
- const {
- params: { id },
- } = req;
+ async get(c) {
+ const id = c.req.param("id");
try {
const item = await g(`item:${id}`);
if (!item) fail("Item not found");
- res.send(item);
+ return c.json(item);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async create(req, res) {
- const {
- body: item,
- user: { id },
- } = req;
+ async create(c) {
+ const item = await c.req.json();
+ const user = c.get("user");
+ const { id } = user;
try {
if (!item.id) {
item.id = v4();
@@ -46,33 +41,33 @@ export default {
await s(`item:${item.id}`, item);
- res.send(item);
+ return c.json(item);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async del(req, res) {
- const {
- body: { item },
- user: { id },
- } = req;
+ async del(c) {
+ const body = await c.req.json();
+ const { item } = body;
+ const user = c.get("user");
+ const { id } = user;
try {
const n = await db.lRem(`${id}:items`, 0, item.id);
if (n) db.del(`item:${item.id}`);
else fail("item not found");
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async sort(req, res) {
- const {
- body: { items },
- user: { id },
- } = req;
+ async sort(c) {
+ const body = await c.req.json();
+ const { items } = body;
+ const user = c.get("user");
+ const { id } = user;
try {
await db.del(`${id}:items`);
@@ -80,9 +75,9 @@ export default {
await db.rPush(`${id}:items`, item.id);
}
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/lnurl.ts b/routes/lnurl.ts
index cb74e2b5..9cd6e8d5 100644
--- a/routes/lnurl.ts
+++ b/routes/lnurl.ts
@@ -14,10 +14,8 @@ const host = URL.split("/").at(-1);
const fiveMinutes = 1000 * 60 * 5;
export default {
- async encode(req, res) {
- const {
- query: { address },
- } = req;
+ async encode(c) {
+ const address = c.req.query("address");
const [name, domain] = address.split("@");
const url = `https://${domain}/.well-known/lnurlp/${name.toLowerCase().replace(/\s/g, "")}`;
@@ -27,32 +25,29 @@ export default {
} catch (e) {
const m = `failed to lookup lightning address ${address}`;
warn(m);
- return bail(res, m);
+ return bail(c, m);
}
const enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000);
- res.send(enc);
+ return c.json(enc);
},
- async decode(req, res) {
- const {
- query: { text },
- } = req;
+ async decode(c) {
+ const text = c.req.query("text");
try {
const url = Buffer.from(bech32.fromWords(bech32.decode(text, 20000).words)).toString();
const r = await got(url).json();
- res.send(r);
+ return c.json(r);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async lnurlp(req, res) {
- const {
- params: { username },
- query: { minSendable = 1000, maxSendable = 100000000000 },
- } = req;
+ async lnurlp(c) {
+ const username = c.req.param("username");
+ const minSendable = c.req.query("minSendable") || 1000;
+ const maxSendable = c.req.query("maxSendable") || 100000000000;
try {
const user = await getUser(
username.replace("lightning:", "").replace(/\s/g, "").replace("=", "").toLowerCase(),
@@ -69,7 +64,7 @@ export default {
const id = v4();
await s(`lnurl:${id}`, uid);
- res.send({
+ return c.json({
allowsNostr: true,
minSendable,
maxSendable,
@@ -82,15 +77,15 @@ export default {
} catch (e) {
if (!e.message.includes("found"))
warn("problem generating lnurlp request", username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async lnurl(req, res) {
- const {
- params: { id },
- query: { amount, comment, nostr },
- } = req;
+ async lnurl(c) {
+ const id = c.req.param("id");
+ const amount = c.req.query("amount");
+ const comment = c.req.query("comment");
+ const nostr = c.req.query("nostr");
try {
const iid = await g(`lnurl:${id}:invoice`);
const uid = await g(`lnurl:${id}`);
@@ -109,7 +104,6 @@ export default {
if (nostr) {
try {
const event = JSON.parse(decodeURIComponent(nostr));
- // TODO: validate the event
await s(`zap:${id}`, event);
metadata = nostr;
} catch (e) {
@@ -128,31 +122,30 @@ export default {
user,
});
- res.send({
+ return c.json({
pr: invoice.text,
routes: [],
verify: `${URL}/api/lnurl/verify/${invoice.id}`,
});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async verify(req, res) {
- const {
- params: { id },
- } = req;
+ async verify(c) {
+ const id = c.req.param("id");
const inv = await getInvoice(id);
- if (!inv) return res.send({ status: "ERROR", reason: "Not found" });
+ if (!inv) return c.json({ status: "ERROR", reason: "Not found" });
const { hash, received, amount, preimage } = inv;
const settled = received >= amount;
- res.send({ pr: hash, status: "OK", settled, preimage: preimage || null });
+ return c.json({ pr: hash, status: "OK", settled, preimage: preimage || null });
},
- async pay(req, res) {
- const { amount, username } = req.params;
+ async pay(c) {
+ const amount = c.req.param("amount");
+ const username = c.req.param("username");
try {
const user = await getUser(username);
@@ -206,7 +199,7 @@ export default {
await s(`lnurl:${id}`, uid);
if (total > 0) await s(`lnurl:${id}:invoice`, invoice.id);
- res.send({
+ return c.json({
allowsNostr: true,
minSendable: invoice.amount ? total : 1000,
maxSendable: invoice.amount ? total : 10 * 1000 * SATS,
@@ -219,7 +212,7 @@ export default {
} catch (e) {
if (!e.message.includes("found"))
warn("problem generating lnurlp request", username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/locations.ts b/routes/locations.ts
index 23b0f3b4..6dd364ec 100644
--- a/routes/locations.ts
+++ b/routes/locations.ts
@@ -1,19 +1,19 @@
import { db, g } from "$lib/db";
export default {
- async list(_, res) {
+ async list(c) {
const locations = await g("locations");
- res.send({ locations });
+ return c.json({ locations });
},
- async nearby(req, res) {
- const lat = parseFloat(req.query.lat);
- const lon = parseFloat(req.query.lon);
- const radius = parseFloat(req.query.radius) || 50;
- const count = parseInt(req.query.count) || 50;
+ async nearby(c) {
+ const lat = parseFloat(c.req.query("lat"));
+ const lon = parseFloat(c.req.query("lon"));
+ const radius = parseFloat(c.req.query("radius")) || 50;
+ const count = parseInt(c.req.query("count")) || 50;
if (isNaN(lat) || isNaN(lon)) {
- return res.status(400).send({ error: "lat and lon required" });
+ return c.json({ error: "lat and lon required" }, 400);
}
try {
@@ -34,11 +34,11 @@ export default {
if (loc) locations.push(loc);
}
- res.send({ locations });
+ return c.json({ locations });
} catch (e) {
console.log("nearby search failed", e);
const locations = await g("locations");
- res.send({ locations });
+ return c.json({ locations });
}
},
};
diff --git a/routes/nostr.ts b/routes/nostr.ts
index 4436a1c7..554a230c 100644
--- a/routes/nostr.ts
+++ b/routes/nostr.ts
@@ -22,20 +22,20 @@ import type { ProfilePointer } from "nostr-tools/nip19";
import { getZapEndpoint, makeZapRequest } from "nostr-tools/nip57";
export default {
- async event(req, res) {
+ async event(c) {
try {
- let { id } = req.params;
- const full = req.url.endsWith("full");
+ let id = c.req.param("id");
+ const full = c.req.url.endsWith("full");
if (id.startsWith("nevent")) id = decode(id).data.id;
if (id.startsWith("note")) id = decode(id).data;
const k = `event:${id}${full ? ":full" : ""}`;
let event = await g(k);
- if (event) return res.send(event);
+ if (event) return c.json(event);
event = await get({ ids: [id] });
- if (!full) return res.send(event);
+ if (!full) return c.json(event);
const parts = parseContent(event);
@@ -98,15 +98,16 @@ export default {
await db.set(k, JSON.stringify(event), { EX });
- res.send(event);
+ return c.json(event);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async parse(req, res) {
- const { event } = req.body;
+ async parse(c) {
+ const body = await c.req.json();
+ const { event } = body;
const parts = parseContent(event);
const names = {};
@@ -117,12 +118,12 @@ export default {
}
}
- res.send({ parts, names });
+ return c.json({ parts, names });
},
- async thread(req, res) {
+ async thread(c) {
try {
- const { id } = req.params;
+ const id = c.req.param("id");
const event = await get({ ids: [id] });
@@ -147,16 +148,16 @@ export default {
}
}
- res.send(thread);
+ return c.json(thread);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async zaps(req, res) {
+ async zaps(c) {
try {
- let { id } = req.params;
+ let id = c.req.param("id");
if (id.startsWith("nevent")) id = decode(id).data.id;
if (id.startsWith("note")) id = decode(id).data;
const filter = { kinds: [9735], "#e": [id] };
@@ -165,7 +166,7 @@ export default {
await sync("wss://relay.primal.net", filter);
events = await scan(filter);
}
- if (!events.length) return res.send([]);
+ if (!events.length) return c.json([]);
const zaps = [];
for (const { tags } of events) {
@@ -187,17 +188,19 @@ export default {
}
}
- res.send(zaps.filter((z) => z.amount > 0));
+ return c.json(zaps.filter((z) => z.amount > 0));
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async publish(req, res) {
+ async publish(c) {
try {
- const { event } = req.body;
- const { pubkey } = req.user;
+ const body = await c.req.json();
+ const { event } = body;
+ const user = c.get("user");
+ const { pubkey } = user;
await publish(event);
@@ -206,17 +209,15 @@ export default {
db.del(`${pubkey}:follows:n`);
}
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async events(req, res) {
- const {
- params: { pubkey },
- } = req;
+ async events(c) {
+ const pubkey = c.req.param("pubkey");
try {
const events = await q({ kinds: [1], authors: [pubkey], limit: 20 });
@@ -233,25 +234,25 @@ export default {
}
}
- res.send(events);
+ return c.json(events);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async follows(req, res) {
- const {
- params: { pubkey },
- query: { limit = 20, offset = 0, pubkeysOnly },
- } = req;
+ async follows(c) {
+ const pubkey = c.req.param("pubkey");
+ const limit = parseInt(c.req.query("limit") || "20");
+ const offset = parseInt(c.req.query("offset") || "0");
+ const pubkeysOnly = c.req.query("pubkeysOnly");
try {
const k = `${pubkey}:follows${pubkeysOnly ? ":pubkeys" : ""}`;
let follows = await g(k);
- if (follows?.length) return res.send(follows);
+ if (follows?.length) return c.json(follows);
const event = await get({ authors: [pubkey], kinds: [3] });
- if (!event) return res.send([]);
+ if (!event) return c.json([]);
let pubkeys = event.tags.filter((tag) => tag[0] === "p").map((tag) => tag[1]);
@@ -273,24 +274,23 @@ export default {
await db.set(k, JSON.stringify(follows), { EX });
- res.send(follows);
+ return c.json(follows);
} catch (e) {
console.log("follows fail", e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async followers(req, res) {
- const {
- params: { pubkey },
- query: { limit = 20, offset = 0 },
- } = req;
+ async followers(c) {
+ const pubkey = c.req.param("pubkey");
+ const limit = parseInt(c.req.query("limit") || "20");
+ const offset = parseInt(c.req.query("offset") || "0");
try {
let followers = await g(`${pubkey}:followers`);
- if (followers?.length) return res.send(followers);
+ if (followers?.length) return c.json(followers);
const events = await q({ kinds: [3], "#p": [pubkey], limit });
- if (!events.length) return res.send([]);
+ if (!events.length) return c.json([]);
const pubkeys = events.map((e) => e.pubkey).slice(offset, offset + limit);
followers = (
@@ -306,28 +306,25 @@ export default {
await db.set(`${pubkey}:followers`, JSON.stringify(followers), { EX });
- res.send(followers);
+ return c.json(followers);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async count(req, res) {
+ async count(c) {
try {
- const { pubkey } = req.params;
- // res.send({ followers: 0, follows: 0 });
- res.send(await getCount(pubkey));
+ const pubkey = c.req.param("pubkey");
+ return c.json(await getCount(pubkey));
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async identities(req, res) {
- const {
- query: { name },
- } = req;
+ async identities(c) {
+ const name = c.req.query("name");
let names = {};
if (name) {
names = { [name]: (await getUser(name, fields)).pubkey };
@@ -339,25 +336,26 @@ export default {
}
}
- res.send({ names });
+ return c.json({ names });
},
- async info(_, res) {
- res.send({ pubkey: serverPubkey });
+ async info(c) {
+ return c.json({ pubkey: serverPubkey });
},
- async profile(req, res) {
- const { profile } = req.params;
+ async profile(c) {
+ const profile = c.req.param("profile");
const { data } = decode(profile);
const { pubkey, relays } = data as ProfilePointer;
const recipient = await getProfile(pubkey, relays);
recipient.relays = relays;
- res.send(recipient);
+ return c.json(recipient);
},
- async zapRequest(req, res) {
+ async zapRequest(c) {
try {
- const { amount, id } = req.body;
+ const body = await c.req.json();
+ const { amount, id } = body;
const { pubkey } = await get({ ids: [id] });
const event = await makeZapRequest({
profile: pubkey,
@@ -367,15 +365,16 @@ export default {
comment: "",
});
- res.send(event);
+ return c.json(event);
} catch (e) {
console.log(e);
}
},
- async zap(req, res) {
+ async zap(c) {
try {
- const { event } = req.body;
+ const body = await c.req.json();
+ const { event } = body;
const amount = event.tags.find((t) => t[0] === "amount")[1];
const pubkey = event.tags.find((t) => t[0] === "p")[1];
const content = JSON.stringify(await getProfile(pubkey));
@@ -386,10 +385,10 @@ export default {
const url = `${callback}?amount=${amount}&nostr=${encodedEvent}`;
const json = await got(url).json();
- res.send(json);
+ return c.json(json);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/payments.ts b/routes/payments.ts
index 33bf3381..ab7a6711 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -41,12 +41,13 @@ import got from "got";
import { v4 } from "uuid";
export default {
- async info(_, res) {
- res.send(await ln.getinfo());
+ async info(c) {
+ return c.json(await ln.getinfo());
},
- async create(req, res) {
- const { body, user } = req;
+ async create(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
let { amount, hash, fee, fund, memo, payreq, aid } = body;
const balance = await getBalance(user.id);
@@ -93,7 +94,6 @@ export default {
await s(`payment:${p.id}`, p);
await s(`payment:${txid}`, p.id);
- // Instant vault notification
try {
const vaultInfo = await g(`arkaddr:${invoice.text}`);
if (vaultInfo) {
@@ -125,7 +125,6 @@ export default {
throw e;
}
} else {
- // Check if recipient account is non-custodial (vault)
const recipientAccount = invoice?.aid ? await g(`account:${invoice.aid}`) : null;
if (recipientAccount?.pubkey || recipientAccount?.seed) {
p = await sendOnchain({
@@ -157,19 +156,24 @@ export default {
}
}
- res.send(p);
+ return c.json(p);
} catch (e) {
warn(user.username, "payment failed", amount, balance, hash, payreq);
err(e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async list(req, res) {
- let {
- user: { id },
- query: { aid, start, end, limit, offset, received },
- } = req;
+ async list(c) {
+ const user = c.get("user");
+ let { id } = user;
+ let aid = c.req.query("aid");
+ const start = c.req.query("start");
+ const end = c.req.query("end");
+ let limit = c.req.query("limit");
+ let offset = c.req.query("offset");
+ const received = c.req.query("received");
+
if (!aid || aid === "undefined") aid = id;
const index = await db.lPos(`${id}:accounts`, aid);
@@ -244,30 +248,27 @@ export default {
const { length: count } = payments;
if (limit) payments = payments.slice(offset, offset + limit);
- res.send({ payments, count, incoming, outgoing });
+ return c.json({ payments, count, incoming, outgoing });
},
- async get(req, res) {
+ async get(c) {
try {
- const {
- params: { hash },
- } = req;
+ const hash = c.req.param("hash");
const p = await getPayment(hash);
if (p?.type === PaymentType.internal) p.with = await getUser(p.ref, fields);
if (p?.type === PaymentType.fund) p.with = await getUser(p.uid, fields);
- res.send(p);
+ return c.json(p);
} catch (e) {
console.log(e);
err("failed to get payment", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async parse(req, res) {
- const {
- body: { payreq },
- user,
- } = req;
+ async parse(c) {
+ const body = await c.req.json();
+ const { payreq } = body;
+ const user = c.get("user");
try {
const hour = 1000 * 60 * 60;
let nodes = await g("nodes");
@@ -297,20 +298,18 @@ export default {
const covered = Math.min(creditBal, ourfee) || 0;
ourfee -= covered;
- res.send({ alias, amount, ourfee });
+ return c.json({ alias, amount, ourfee });
} catch (e) {
console.log(e);
err("problem parsing", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async fund(req, res) {
- const {
- params: { id },
- } = req;
+ async fund(c) {
+ const id = c.req.param("id");
const amount = await getFundBalance(id);
- if (amount === null) return bail(res, "fund not found");
+ if (amount === null) return bail(c, "fund not found");
let payments = (await db.lRange(`fund:${id}:payments`, 0, -1)) || [];
payments = await Promise.all(payments.map((hash) => gf(`payment:${hash}`)));
@@ -319,30 +318,14 @@ export default {
payments = payments.filter((p) => p);
const authorization = await g(`authorization:${id}`);
- res.send({ amount, authorization: authorization?.amount, payments });
+ return c.json({ amount, authorization: authorization?.amount, payments });
},
- // async withdraw(req, res) {
- // const {
- // params: { name },
- // } = req;
- // const { user } = req;
- // const balance = await g(`fund:${name}`);
- // const managers = await db.sMembers(`fund:${name}:managers`);
- // if (managers.length && !managers.includes(user.id)) fail("Unauthorized");
- // res.send({
- // tag: "withdrawRequest",
- // callback: `${URL}/api/lnurlw`,
- // k1: name,
- // defaultDescription: `Withdraw from coinos fund ${name}`,
- // minWithdrawable: balance > 0 ? 1000 : 0,
- // maxWithdrawable: balance * 1000,
- // });
- // },
-
- async authorize(req, res) {
- const { id: uid } = req.user;
- const { id, fiat, currency, amount } = req.body;
+ async authorize(c) {
+ const user = c.get("user");
+ const { id: uid } = user;
+ const body = await c.req.json();
+ const { id, fiat, currency, amount } = body;
const managers = await db.sMembers(`fund:${id}:managers`);
if (managers.length && !managers.includes(uid)) fail("Unauthorized");
@@ -355,14 +338,13 @@ export default {
};
await s(`authorization:${id}`, authorization);
- res.send({});
+ return c.json({});
},
- async take(req, res) {
- let {
- body: { id, amount, invoice: iid },
- user,
- } = req;
+ async take(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
+ let { id, amount, invoice: iid } = body;
try {
amount = Number.parseInt(amount);
if (amount <= 0) fail("Invalid amount");
@@ -421,15 +403,15 @@ export default {
await db.lPush(`fund:${id}:payments`, payment.id);
- res.send(payment);
+ return c.json(payment);
} catch (e) {
warn("problem withdrawing from fund", user.username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async managers(req, res) {
- const { name } = req.params;
+ async managers(c) {
+ const name = c.req.param("name");
const ids = await db.sMembers(`fund:${name}:managers`);
@@ -437,12 +419,13 @@ export default {
Boolean,
);
- res.send(managers);
+ return c.json(managers);
},
- async addManager(req, res) {
- const { id, username } = req.body;
- const { user } = req;
+ async addManager(c) {
+ const body = await c.req.json();
+ const { id, username } = body;
+ const user = c.get("user");
const k = `fund:${id}:managers`;
@@ -463,14 +446,15 @@ export default {
if (!managers.length)
managers = await Promise.all(ids.map(async (id) => await getUser(id, fields)));
- res.send(managers);
+ return c.json(managers);
},
- async deleteManager(req, res) {
+ async deleteManager(c) {
try {
- const { name } = req.params;
- const { id: uid } = req.body;
- const { user } = req;
+ const name = c.req.param("name");
+ const body = await c.req.json();
+ const { id: uid } = body;
+ const user = c.get("user");
const k = `fund:${name}:managers`;
let managers = await db.sMembers(k);
@@ -484,16 +468,15 @@ export default {
const ids = await db.sMembers(k);
managers = await Promise.all(ids.map(async (id) => await getUser(id, fields)));
- res.send(managers);
+ return c.json(managers);
} catch (e) {}
},
- async confirm(req, res) {
- const {
- body: { txid, wallet, type },
- } = req;
+ async confirm(c) {
+ const body = await c.req.json();
+ const { txid, wallet, type } = body;
- if (type !== PaymentType.liquid) return res.send({});
+ if (type !== PaymentType.liquid) return c.json({});
try {
const node = rpc({ ...config[type], wallet });
@@ -546,17 +529,18 @@ export default {
await completePayment(invoice, p, user);
}
}
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
warn(`problem processing ${txid}`);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async txWebhook(req, res) {
- const { txid, secret } = req.body || {};
- const headerSecret = req.headers["x-hook-secret"];
+ async txWebhook(c) {
+ const body = await c.req.json();
+ const { txid, secret } = body || {};
+ const headerSecret = c.req.header("x-hook-secret");
const hookSecret = secret || headerSecret;
try {
@@ -566,59 +550,59 @@ export default {
const tx = await getTx(txid);
await processWatchedTx(tx);
- res.send({});
+ return c.json({});
} catch (e) {
warn("problem processing tx webhook", e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async fee(req, res) {
- const { body, user } = req;
+ async fee(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
try {
- res.send(await build({ ...body, user }));
+ return c.json(await build({ ...body, user }));
} catch (e) {
warn("problem estimating fee", e.message, user.username, body.amount, body.address);
let msg = e.message;
if (msg.includes("500")) msg = "";
- bail(res, `Failed to prepare transaction ${msg}`);
+ return bail(c, `Failed to prepare transaction ${msg}`);
}
},
- async send(req, res) {
- const { body, user } = req;
+ async send(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
try {
await requirePin({ body, user });
const { hash: txid } = await sendOnchain({ ...body, user });
const pid = await g(`payment:${txid}`);
const p = await g(`payment:${pid}`);
- res.send(p);
+ return c.json(p);
} catch (e) {
warn(user.username, "payment failed", e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async freeze(req, res) {
- const {
- body: { secret },
- } = req;
+ async freeze(c) {
+ const body = await c.req.json();
+ const { secret } = body;
try {
if (secret !== config.adminpass) fail("unauthorized");
await s("freeze", true);
- res.send("ok");
+ return c.json("ok");
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async print(req, res) {
- const {
- body: { id },
- user,
- } = req;
+ async print(c) {
+ const body = await c.req.json();
+ const { id } = body;
+ const user = c.get("user");
try {
const p = await gf(`payment:${id}`);
if (!p) fail("Payment not found");
@@ -629,18 +613,17 @@ export default {
mqtt.publish(username, `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`);
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async lnaddress(req, res) {
- let {
- params: { lnaddress, amount },
- body,
- user,
- } = req;
+ async lnaddress(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
+ let lnaddress = c.req.param("lnaddress");
+ let amount = c.req.param("amount");
const { fee } = body;
try {
lnaddress = decodeURIComponent(lnaddress);
@@ -667,27 +650,25 @@ export default {
await credit({ hash: pr, amount, memo, ref: user.id });
} else p = await sendLightning({ user, pr, amount, fee, memo });
- res.send(p);
+ return c.json(p);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async gateway(req, res) {
- const {
- body: { short_channel_id, webhook },
- } = req;
+ async gateway(c) {
+ const body = await c.req.json();
+ const { short_channel_id, webhook } = body;
await s(short_channel_id, webhook);
- res.send({ ok: true });
+ return c.json({ ok: true });
},
- async replace(req, res) {
- const {
- body: { id },
- user,
- } = req;
+ async replace(c) {
+ const body = await c.req.json();
+ const { id } = body;
+ const user = c.get("user");
try {
const p = await gf(`payment:${id}`);
if (!p) fail("Payment not found");
@@ -724,47 +705,50 @@ export default {
if (!r[0].allowed) fail(`transaction rejected ${p.hex}`);
warn("bump", user.username, p.hex);
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
err("failed to bump payment", id, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async internal(req, res) {
- const {
- body: { username, amount },
- user: sender,
- } = req;
+ async internal(c) {
+ const body = await c.req.json();
+ const { username, amount } = body;
+ const sender = c.get("user");
const recipient = await getUser(username);
- res.send(await sendInternal({ amount, sender, recipient }));
+ return c.json(await sendInternal({ amount, sender, recipient }));
},
- async decode(req, res) {
- const { bolt11 } = req.params;
- res.send(await ln.decode(bolt11));
+ async decode(c) {
+ const bolt11 = c.req.param("bolt11");
+ return c.json(await ln.decode(bolt11));
},
- async fetchinvoice(req, res) {
- const { amount, offer } = req.body;
- res.send(await ln.fetchinvoice(offer, amount ? amount * 1000 : null));
+ async fetchinvoice(c) {
+ const body = await c.req.json();
+ const { amount, offer } = body;
+ return c.json(await ln.fetchinvoice(offer, amount ? amount * 1000 : null));
},
- async auth(req, res) {
- console.log(req.query);
- res.send(req.query);
+ async auth(c) {
+ const query = c.req.query();
+ console.log(query);
+ return c.json(query);
},
- async order(req, res) {
- console.log(req.body);
- res.send(req.body);
+ async order(c) {
+ const body = await c.req.json();
+ console.log(body);
+ return c.json(body);
},
- async sendinvoice(req, res) {
+ async sendinvoice(c) {
try {
- const { user } = req;
- const { invreq } = req.body;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { invreq } = body;
const { amount_msat, bolt12, pay_index } = await ln.sendinvoice({
invreq,
@@ -782,14 +766,15 @@ export default {
const p = await replay(pay_index);
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async ark(req, res) {
- const { body, user } = req;
+ async ark(c) {
+ const body = await c.req.json();
+ const user = c.get("user");
const { address, amount, aid } = body;
try {
@@ -812,16 +797,11 @@ export default {
await s(`payment:${p.id}`, p);
await s(`payment:${txid}`, p.id);
- // Instant vault notification: if destination is a vault on our platform,
- // create the receive payment immediately so the client doesn't have to
- // wait for ASP indexer + arkSync backoff delays
try {
const vaultInfo = await g(`arkaddr:${address}`);
if (vaultInfo) {
const { aid: vaultAid, uid: vaultUid } = vaultInfo;
- // Skip notification if this is a forward (e.g. vault → lightning);
- // the funds are transient and will leave again immediately
const isForward = await g(`custodial-ark-invoice:${address}`);
const vaultOwner = await g(`user:${vaultUid}`);
@@ -848,16 +828,17 @@ export default {
throw e;
}
- res.send(p);
+ return c.json(p);
} catch (e) {
warn(user.username, "ark payment failed", e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async arkVaultSend(req, res) {
+ async arkVaultSend(c) {
try {
- const { body, user } = req;
+ const body = await c.req.json();
+ const user = c.get("user");
const { hash, amount, aid } = body;
const { rate, currency } = await getUserRate(user);
@@ -871,15 +852,16 @@ export default {
mapHashToId: true,
});
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async arkVaultReceive(req, res) {
+ async arkVaultReceive(c) {
try {
- const { body, user } = req;
+ const body = await c.req.json();
+ const user = c.get("user");
const { amount, hash, aid } = body;
if (amount <= 0) fail("Invalid amount");
@@ -895,19 +877,20 @@ export default {
currency,
});
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async arkAddress(_, res) {
- res.send(await getArkAddress());
+ async arkAddress(c) {
+ return c.json(await getArkAddress());
},
- async arkReceive(req, res) {
+ async arkReceive(c) {
try {
- const { body, user } = req;
+ const body = await c.req.json();
+ const user = c.get("user");
const { iid, amount, hash } = body;
const { id: uid } = user;
@@ -921,7 +904,6 @@ export default {
if (hash) {
await acquireArkLock(hash);
- // Skip VTXO verification for vault→custodial (same user, debit handled by arkVaultSend)
if (!(aid && aid !== uid)) {
if (!(await verifyArkVtxo(hash))) fail("VTXO not found in server wallet");
}
@@ -931,7 +913,6 @@ export default {
const { rates } = await getUserRate(user);
const rate = rates[currency];
- // Always credit custodial balance (Ark funds received by server)
await tbCredit(uid, uid, type, amount, false);
const p = await createArkPayment({
@@ -951,26 +932,25 @@ export default {
await completePayment(invoice, p, user);
- // Clean up custodial ark invoice index
if (invoice.text) await db.del(`custodial-ark-invoice:${invoice.text}`);
- res.send(p);
+ return c.json(p);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async arkSync(req, res) {
+ async arkSync(c) {
try {
- const { user } = req;
- const { transactions = [], aid } = req.body;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { transactions = [], aid } = body;
const { id: uid } = user;
- l("arkSync", user.username, aid, "txs:", transactions.length, "bal:", req.body.balance);
+ l("arkSync", user.username, aid, "txs:", transactions.length, "bal:", body.balance);
- // Per-account lock prevents concurrent syncs from racing
const lockKey = `arksynclock:${aid}`;
const gotLock = await db.set(lockKey, "1", { NX: true, EX: 30 });
- if (!gotLock) return res.send({ synced: 0, received: 0, payments: [] });
+ if (!gotLock) return c.json({ synced: 0, received: 0, payments: [] });
try {
const { rate, currency } = await getUserRate(user);
@@ -983,7 +963,6 @@ export default {
const hashes = [tx.arkTxid, tx.commitmentTxid, tx.hash].filter(Boolean);
if (!hashes.length) continue;
- // Check if any hash is already known
let found = false;
for (const h of hashes) {
const existingId = await g(`payment:${aid}:${h}`);
@@ -999,8 +978,6 @@ export default {
}
if (found) continue;
- // Skip sent/zero transactions not recorded via vault-send — these are
- // from internal wallet operations (settle, refresh) not real sends
if (tx.amount <= 0) continue;
const primaryHash = hashes[0];
@@ -1021,7 +998,6 @@ export default {
if (tx.amount > 0) {
received += tx.amount;
- // Find an unpaid ark invoice for this account and mark it received
const invoiceIds = await db.lRange(`${aid}:invoices`, 0, 20);
for (const iid of invoiceIds) {
const inv = await getInvoice(iid);
@@ -1038,8 +1014,7 @@ export default {
}
}
- // Reconcile expired VTXOs
- const { balance } = req.body;
+ const { balance } = body;
if (typeof balance === "number" && balance >= 0) {
const paymentIds = await db.lRange(`${aid}:payments`, 0, -1);
let expectedBalance = 0;
@@ -1049,8 +1024,6 @@ export default {
const pay = await g(`payment:${pid}`);
if (pay) {
expectedBalance += pay.amount;
- // Skip reconciliation if there are recent payments the SDK
- // may not have indexed yet (e.g. instant vault credits)
if (pay.amount > 0 && now - pay.created < 120_000) {
hasRecentPayments = true;
}
@@ -1075,7 +1048,6 @@ export default {
}
}
- // Check for pending custodial ark invoice to forward
let forward;
if (received > 0) {
const account = await g(`account:${aid}`);
@@ -1098,12 +1070,12 @@ export default {
}
}
- res.send({ synced, received, payments, forward });
+ return c.json({ synced, received, payments, forward });
} finally {
await db.del(lockKey);
}
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/rates.ts b/routes/rates.ts
index e532ec78..d4426a6e 100644
--- a/routes/rates.ts
+++ b/routes/rates.ts
@@ -2,17 +2,17 @@ import { g } from "$lib/db";
import { rate } from "$lib/rates";
export default {
- async fx(_, res) {
+ async fx(c) {
const { fx } = await g("fx");
- res.send({ fx });
+ return c.json({ fx });
},
- async last(_, res) {
- res.send(rate || (await g("rate")));
+ async last(c) {
+ return c.json(rate || (await g("rate")));
},
- async index(_, res) {
+ async index(c) {
const { date, fx, ...rates } = await g("rates");
- res.send(rates);
+ return c.json(rates);
},
};
diff --git a/routes/shopify.ts b/routes/shopify.ts
index d955d73d..abd7c81b 100644
--- a/routes/shopify.ts
+++ b/routes/shopify.ts
@@ -3,21 +3,20 @@ import { g } from "$lib/db";
import { l, err } from "$lib/logging";
import { getPayment } from "$lib/utils";
-const query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) {
- orderMarkAsPaid(input: $input) {
- order { id }
- userErrors {
+const query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) {
+ orderMarkAsPaid(input: $input) {
+ order { id }
+ userErrors {
field
message
}
}
}`;
-export default async (req, res) => {
- const {
- body: { hash },
- params: { id },
- } = req;
+export default async (c) => {
+ const body = await c.req.json();
+ const { hash } = body;
+ const id = c.req.param("id");
const p = await getPayment(hash);
const user = await g(`user:${p.uid}`);
@@ -39,7 +38,7 @@ export default async (req, res) => {
l("shopify success", r);
- res.send(r);
+ return c.json(r);
} catch (e) {
err("problem marking shopify order as paid", e.message);
}
diff --git a/routes/square.ts b/routes/square.ts
index f738e1ef..269cdd0d 100644
--- a/routes/square.ts
+++ b/routes/square.ts
@@ -12,11 +12,11 @@ import { v4 } from "uuid";
const { scopes, url, appId, clientSecret, environment } = config.square;
export default {
- async connect(req, res) {
- const { user } = req;
+ async connect(c) {
+ const user = c.get("user");
if (await db.exists(`${user.id}:square`)) {
- return res.send("connected");
+ return c.json("connected");
}
const codeVerifier = base64.encode(crypto.randomBytes(32));
@@ -27,13 +27,13 @@ export default {
const state = base64.encode(crypto.randomBytes(12));
const scope = scopes.join("+");
- res.send(
+ return c.json(
`${url}oauth2/authorize?client_id=${appId}&session=false&scope=${scope}&state=${state}&code_challenge=${challenge}`,
);
},
- async auth(req, res) {
- const { user } = req;
+ async auth(c) {
+ const user = c.get("user");
const codeVerifier = await g(`${user.id}:codeVerifier`);
@@ -41,7 +41,7 @@ export default {
environment,
});
- const { code } = req.query;
+ const code = c.req.query("code");
try {
const result = await client.oAuth.obtainToken({
@@ -53,15 +53,15 @@ export default {
await s(`${user.id}:square`, result);
await s(result.merchantId, user.id);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
}
},
- async payment(req, res) {
+ async payment(c) {
try {
- const { body } = req;
+ const body = await c.req.json();
const { data, type, merchant_id } = body;
const { payment } = data.object;
@@ -86,9 +86,9 @@ export default {
await generate({ invoice, user });
}
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
};
diff --git a/routes/users.ts b/routes/users.ts
index 07e4fb85..f3b747af 100644
--- a/routes/users.ts
+++ b/routes/users.ts
@@ -27,6 +27,7 @@ import { getPublicKey, nip19, verifyEvent } from "nostr-tools";
import { hexToBytes } from "@noble/hashes/utils.js";
import { authenticator } from "otplib";
import { v4 } from "uuid";
+import { setCookie } from "hono/cookie";
import { PaymentType } from "$lib/types";
import { createBalanceAccount, getBalance, getCredit, getPending } from "$lib/tb";
@@ -36,16 +37,14 @@ import type { ProfilePointer } from "nostr-tools/nip19";
const { host } = new URL(process.env.URL);
const relay = encodeURIComponent(config.publicRelay);
-const verifyRecaptcha = async (response, req?) => {
+const verifyRecaptcha = async (response, c?, body?) => {
const { recaptcha: secret } = config;
if (!secret) return true;
- // Skip captcha for Tor users accessing via .onion
- const host = req?.headers?.["host"] || "";
- if (host.endsWith(".onion")) return true;
+ const reqHost = c?.req?.header("host") || "";
+ if (reqHost.endsWith(".onion")) return true;
- // Skip captcha for allowlisted User-Agents (prefix match)
- const uaRaw = req?.headers?.["user-agent"] || req?.headers?.["User-Agent"];
+ const uaRaw = c?.req?.header("user-agent");
const ua = Array.isArray(uaRaw) ? uaRaw[0] : uaRaw;
if (typeof ua === "string") {
const prefixes = await db.sMembers("nocaptcha_ua");
@@ -55,20 +54,19 @@ const verifyRecaptcha = async (response, req?) => {
}
}
- // Check for valid API key to bypass captcha
- const apiKey = req?.headers?.["x-api-key"];
+ const apiKey = c?.req?.header("x-api-key");
if (apiKey && (await db.sIsMember("apikeys", apiKey))) {
return true;
}
- const { username } = req.body;
+ const { username } = body || {};
if (username && (await db.sIsMember("nocaptcha", username.toLowerCase().replace(/\s/g, ""))))
return true;
if (!response) return false;
try {
- const ip = req?.headers?.["cf-connecting-ip"] || req?.socket?.remoteAddress;
+ const ip = c?.req?.header("cf-connecting-ip") || c?.env?.ip;
const { success } = await got
.post("https://www.google.com/recaptcha/api/siteverify", {
form: {
@@ -87,8 +85,8 @@ const verifyRecaptcha = async (response, req?) => {
export default {
upload,
- async me(req, res) {
- const { user } = req;
+ async me(c) {
+ const user = c.get("user");
try {
user.balance = await getBalance(user.id);
user.locked = await ga(`balance:${user.id}`);
@@ -104,15 +102,15 @@ export default {
user.prompt = !!user.prompt;
if (user.pubkey) user.npub = nip19.npubEncode(user.pubkey);
- res.send(pick(user, whitelist));
+ return c.json(pick(user, whitelist));
} catch (e) {
console.log("problem fetching user", e);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async list(req, res) {
- const { user } = req;
+ async list(c) {
+ const user = c.get("user");
if (!user.admin) fail("unauthorized");
const users = [];
@@ -140,13 +138,11 @@ export default {
users.push(u);
}
- res.send(users);
+ return c.json(users);
},
- async get(req, res) {
- let {
- params: { key },
- } = req;
+ async get(c) {
+ let key = c.req.param("key");
key = key.toLowerCase().replace(/\s/g, "");
try {
if (key.startsWith("npub")) {
@@ -162,43 +158,43 @@ export default {
}
const user = await getNostrUser(key);
- res.send(pick(user, fields));
+ return c.json(pick(user, fields));
} catch (e) {
err("problem getting user", key, e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async create(req, res) {
- const { body, headers } = req;
+ async create(c) {
+ const body = await c.req.json();
+ const headers = c.req.header();
try {
const ip = headers["cf-connecting-ip"];
if (!body.user) fail("no user object provided");
let { user } = body;
- const fields = ["pubkey", "password", "username", "picture", "fresh", "authPubkey"];
- user = await register(pick(user, fields), ip);
+ const flds = ["pubkey", "password", "username", "picture", "fresh", "authPubkey"];
+ user = await register(pick(user, flds), ip);
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
l("registered new user", user.username);
- res.send({ ...pick(user, whitelist), sk: user.sk, token });
+ return c.json({ ...pick(user, whitelist), sk: user.sk, token });
} catch (e) {
err("problem registering", e.message);
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async disable2fa(req, res) {
- const {
- user,
- body: { token },
- } = req;
+ async disable2fa(c) {
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { token } = body;
const { id, twofa, username, otpsecret } = user;
if (twofa && !authenticator.check(token, otpsecret)) {
- return res.code(401).send("2fa required");
+ return c.json("2fa required", 401);
}
user.twofa = false;
@@ -206,15 +202,14 @@ export default {
emit(username, "user", user);
emit(username, "otpsecret", user.otpsecret);
l("disabled 2fa", username);
- res.send({});
+ return c.json({});
},
- async enable2fa(req, res) {
+ async enable2fa(c) {
try {
- const {
- user,
- body: { token },
- } = req;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const { token } = body;
const { id, otpsecret, username } = user;
const isValid = authenticator.check(token, otpsecret);
if (isValid) {
@@ -222,21 +217,22 @@ export default {
await s(`user:${id}`, user);
emit(username, "user", user);
} else {
- return res.code(500).send("Invalid token");
+ return c.json("Invalid token", 500);
}
l("enabled 2fa", username);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async update(req, res) {
- const { user, body } = req;
+ async update(c) {
+ const user = c.get("user");
+ const body = await c.req.json();
try {
- const { id: tokid } = jwt.verify(req.headers.authorization.split(" ")[1], config.jwt);
+ const { id: tokid } = jwt.verify(c.req.header("authorization").split(" ")[1], config.jwt);
l("updating user", user.username, tokid);
if (user.id !== tokid) fail("unauthorized");
@@ -265,8 +261,8 @@ export default {
const event = JSON.parse(body.event);
const challenge = event.tags.find((t) => t[0] === "challenge")[1];
- const c = await g(`challenge:${challenge}`);
- if (!c) fail("Invalid or expired challenge");
+ const ch = await g(`challenge:${challenge}`);
+ if (!ch) fail("Invalid or expired challenge");
if (!verifyEvent(event) || event.pubkey !== pubkey)
fail("Invalid signature or challenge mismatch.");
@@ -361,27 +357,28 @@ export default {
if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`);
emit(user.id, "user", user);
- res.send({ user });
+ return c.json({ user });
} catch (e) {
console.log(e);
warn("failed to update", user.username, e.message);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async login(req, res) {
+ async login(c) {
try {
- let { username, password, token: twofa, recaptcha } = req.body;
- const ip = req.headers["cf-connecting-ip"] || req.socket.remoteAddress;
+ const body = await c.req.json();
+ let { username, password, token: twofa, recaptcha } = body;
+ const ip = c.req.header("cf-connecting-ip") || c.env?.ip || "unknown";
const ipKey = `ip:${ip}:login`;
const ipCount = await db.incr(ipKey);
if (ipCount === 1) await db.expire(ipKey, 10);
- if (ipCount > 30) return res.code(429).send({});
+ if (ipCount > 30) return c.json({}, 429);
- const recaptchaOk = await verifyRecaptcha(recaptcha, req);
+ const recaptchaOk = await verifyRecaptcha(recaptcha, c, body);
if (!recaptchaOk) {
- return res.code(401).send("failed captcha");
+ return c.json("failed captcha", 401);
}
username = username.toLowerCase().replace(/\s/g, "");
@@ -391,7 +388,7 @@ export default {
const failures = await g(fk);
const ipFailKey = `ip:${ip}:login:fail`;
const ipFailures = await g(ipFailKey);
- if (ipFailures > 20) return res.code(429).send({});
+ if (ipFailures > 20) return c.json({}, 429);
let user = await getUser(username);
@@ -401,7 +398,7 @@ export default {
if (!(await db.ttl(ipFailKey))) await db.expire(ipFailKey, 600);
await db.incrBy(fk, 1);
setTimeout(() => db.decrBy(fk, 1), 120000);
- return res.code(401).send({});
+ return c.json({}, 401);
}
if (
@@ -410,56 +407,57 @@ export default {
) {
await db.incrBy(ipFailKey, 1);
if (!(await db.ttl(ipFailKey))) await db.expire(ipFailKey, 600);
- return res.code(401).send("2fa required");
+ return c.json("2fa required", 401);
}
}
- if (req.body.authPubkey && !user.authPubkey) {
- user.authPubkey = req.body.authPubkey;
+ if (body.authPubkey && !user.authPubkey) {
+ user.authPubkey = body.authPubkey;
await s(`user:${user.id}`, user);
}
- if (username !== "coinos") l("logged in", username, req.headers["cf-connecting-ip"]);
+ if (username !== "coinos") l("logged in", username, c.req.header("cf-connecting-ip"));
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ setCookie(c, "token", token, { expires: new Date(Date.now() + 432000000), path: "/" });
user = pick(user, whitelist);
- res.send({ user, token });
+ return c.json({ user, token });
} catch (e) {
console.log(e);
- err("login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
+ err("login error", e.message, c.env?.ip);
+ return c.json({}, 401);
}
},
- async challenge(req, res) {
+ async challenge(c) {
const id = v4();
await db.set(`challenge:${id}`, id, { EX: 300 });
- const username = req.query?.username;
+ const username = c.req.query("username");
let hasAuthKey = false;
if (username) {
const user = await getUser(username.toLowerCase().replace(/\s/g, ""));
if (user?.authPubkey) hasAuthKey = true;
}
- res.send({ challenge: id, hasAuthKey });
+ return c.json({ challenge: id, hasAuthKey });
},
- async authKeyLogin(req, res) {
+ async authKeyLogin(c) {
try {
- const { event, challenge, username: rawUsername, twofa, recaptcha } = req.body;
- const ip = req.headers["cf-connecting-ip"] || req.socket.remoteAddress;
+ const body = await c.req.json();
+ const { event, challenge, username: rawUsername, twofa, recaptcha } = body;
+ const ip = c.req.header("cf-connecting-ip") || c.env?.ip || "unknown";
const ipKey = `ip:${ip}:login`;
const ipCount = await db.incr(ipKey);
if (ipCount === 1) await db.expire(ipKey, 10);
- if (ipCount > 30) return res.code(429).send({});
+ if (ipCount > 30) return c.json({}, 429);
- const recaptchaOk = await verifyRecaptcha(recaptcha, req);
- if (!recaptchaOk) return res.code(401).send("failed captcha");
+ const recaptchaOk = await verifyRecaptcha(recaptcha, c, body);
+ if (!recaptchaOk) return c.json("failed captcha", 401);
- const c = await g(`challenge:${challenge}`);
- if (!c) fail("Invalid or expired challenge");
+ const ch = await g(`challenge:${challenge}`);
+ if (!ch) fail("Invalid or expired challenge");
const { kind } = event;
if (kind !== 27235) fail("Invalid event");
@@ -477,35 +475,36 @@ export default {
user.twofa &&
(typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret))
) {
- return res.code(401).send("2fa required");
+ return c.json("2fa required", 401);
}
l("authkey login", username, ip);
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ setCookie(c, "token", token, { expires: new Date(Date.now() + 432000000), path: "/" });
user = pick(user, whitelist);
- res.send({ user, token });
+ return c.json({ user, token });
} catch (e) {
console.log(e);
- err("authkey login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
+ err("authkey login error", e.message, c.env?.ip);
+ return c.json({}, 401);
}
},
- async nostrAuth(req, res) {
+ async nostrAuth(c) {
try {
- const { event, challenge, twofa, recaptcha } = req.body;
- const ip = req.headers["cf-connecting-ip"];
- const recaptchaOk = await verifyRecaptcha(recaptcha, req);
+ const body = await c.req.json();
+ const { event, challenge, twofa, recaptcha } = body;
+ const ip = c.req.header("cf-connecting-ip");
+ const recaptchaOk = await verifyRecaptcha(recaptcha, c, body);
if (!recaptchaOk) {
- return res.code(401).send("failed captcha");
+ return c.json("failed captcha", 401);
}
- const c = await g(`challenge:${challenge}`);
+ const ch = await g(`challenge:${challenge}`);
const { pubkey: key, kind } = event;
if (kind !== 27235) fail("Invalid event");
- if (!c) fail("Invalid or expired login challenge");
+ if (!ch) fail("Invalid or expired login challenge");
if (!verifyEvent(event) || event.tags.find((t) => t[0] === "challenge")?.[1] !== challenge)
fail("Invalid signature or challenge mismatch.");
@@ -536,86 +535,89 @@ export default {
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ setCookie(c, "token", token, { expires: new Date(Date.now() + 432000000), path: "/" });
user = pick(user, whitelist);
- res.send({ user, token });
+ return c.json({ user, token });
} catch (e) {
console.log(e);
- err("nostr login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
+ err("nostr login error", e.message, c.env?.ip);
+ return c.json({}, 401);
}
},
- async subscriptions(req, res) {
+ async subscriptions(c) {
try {
- const { user } = req;
+ const user = c.get("user");
const subscriptions = await db.sMembers(`${user.id}:subscriptions`);
- res.send(subscriptions);
+ return c.json(subscriptions);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async subscription(req, res) {
+ async subscription(c) {
try {
- const { subscription } = req.body;
- const { id } = req.user;
+ const body = await c.req.json();
+ const { subscription } = body;
+ const { id } = c.get("user");
await db.sAdd(`${id}:subscriptions`, JSON.stringify(subscription));
- res.send(subscription);
+ return c.json(subscription);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async deleteSubscription(req, res) {
+ async deleteSubscription(c) {
try {
- const { subscription } = req.body;
- const { id } = req.user;
+ const body = await c.req.json();
+ const { subscription } = body;
+ const { id } = c.get("user");
await db.sRem(`${id}:subscriptions`, JSON.stringify(subscription));
- res.send(subscription);
+ return c.json(subscription);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async password(req, res) {
- const {
- body: { password },
- user,
- } = req;
- if (!user.password) return res.send(true);
+ async password(c) {
+ const body = await c.req.json();
+ const { password } = body;
+ const user = c.get("user");
+ if (!user.password) return c.json(true);
try {
if (!password) fail("password not provided");
- res.send(await Bun.password.verify(password, user.password));
+ return c.json(await Bun.password.verify(password, user.password));
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async pin(req, res) {
- const {
- body: { pin },
- user,
- } = req;
- res.send(!user.pin || user.pin === pin);
+ async pin(c) {
+ const body = await c.req.json();
+ const { pin } = body;
+ const user = c.get("user");
+ return c.json(!user.pin || user.pin === pin);
},
- async otpsecret(req, res) {
+ async otpsecret(c) {
try {
- await requirePin(req);
- const { otpsecret, username } = req.user;
- res.send({ secret: otpsecret, username });
+ const user = c.get("user");
+ const body = await c.req.json();
+ await requirePin({ body, user });
+ const { otpsecret, username } = user;
+ return c.json({ secret: otpsecret, username });
} catch (e) {
- res.code(500).send(e.message);
+ return c.json(e.message, 500);
}
},
- async contacts(req, res) {
- const { params, user } = req;
+ async contacts(c) {
+ const user = c.get("user");
const { id } = user;
+ const limit = c.req.param("limit");
const lastlen = (await g(`${id}:lastlen`)) || 0;
const len = await db.lLen(`${id}:payments`);
const payments = (await db.lRange(`${id}:payments`, 0, len - lastlen)) || [];
@@ -654,24 +656,21 @@ export default {
c.trusted = true;
});
- let { limit } = params;
- limit ||= contacts.length;
+ let contactLimit = limit || contacts.length;
contacts = contacts.filter((c) => !pins.includes(c.id));
- contacts = contacts.slice(0, limit);
+ contacts = contacts.slice(0, contactLimit);
const combined = [...pinned, ...contacts];
- res.send(combined);
+ return c.json(combined);
},
- async del(req, res) {
- let {
- params: { username },
- headers: { authorization },
- } = req;
+ async del(c) {
+ let username = c.req.param("username");
+ const authorization = c.req.header("authorization");
fail("Unauthorized");
username = username.toLowerCase();
- if (!authorization?.includes(config.admin)) return res.code(401).send("unauthorized");
+ if (!authorization?.includes(config.admin)) return c.json("unauthorized", 401);
const { id, pubkey } = await g(
`user:${await g(`user:${username.replace(/\s/g, "").toLowerCase()}`)}`,
@@ -685,14 +684,13 @@ export default {
db.del(`user:${id}`);
db.del(`user:${pubkey}`);
- res.send({});
+ return c.json({});
},
- async reset(req, res) {
- const {
- body: { code, username, password },
- user: u,
- } = req;
+ async reset(c) {
+ const body = await c.req.json();
+ const { code, username, password } = body;
+ const u = c.get("user");
try {
const admin = u?.admin;
let id;
@@ -704,7 +702,7 @@ export default {
if (!user) fail("user not found");
- warn("password reset", user.username, code, req.headers["cf-connecting-ip"]);
+ warn("password reset", user.username, code, c.req.header("cf-connecting-ip"));
user.pin = null;
user.nsec = null;
@@ -718,40 +716,38 @@ export default {
await s(`user:${id}`, user);
await db.del(`reset:${code}`);
- res.send(pick(user, whitelist));
+ return c.json(pick(user, whitelist));
} catch (e) {
- err("password reset failed", e.message, req.headers["cf-connecting-ip"]);
- bail(res, e.message);
+ err("password reset failed", e.message, c.req.header("cf-connecting-ip"));
+ return bail(c, e.message);
}
},
- async printerlogin(req, res) {
- const {
- body: { username, topic },
- } = req;
- if (username === topic) res.send({ ok: true });
- else bail(res, "unauthorized");
+ async printerlogin(c) {
+ const body = await c.req.json();
+ const { username, topic } = body;
+ if (username === topic) return c.json({ ok: true });
+ else return bail(c, "unauthorized");
},
- async acl(req, res) {
- const {
- body: { username, topic },
- } = req;
- if (username === topic) res.send({ ok: true });
- else bail(res, "unauthorized");
+ async acl(c) {
+ const body = await c.req.json();
+ const { username, topic } = body;
+ if (username === topic) return c.json({ ok: true });
+ else return bail(c, "unauthorized");
},
- async superuser(req, res) {
- const {
- body: { username },
- } = req;
- if (username === config.mqtt2.username) res.send({ ok: true });
- else bail(res, "unauthorized");
+ async superuser(c) {
+ const body = await c.req.json();
+ const { username } = body;
+ if (username === config.mqtt2.username) return c.json({ ok: true });
+ else return bail(c, "unauthorized");
},
- async request(req, res) {
- const { email } = req.body;
- const { user } = req;
+ async request(c) {
+ const body = await c.req.json();
+ const { email } = body;
+ const user = c.get("user");
const { id } = user;
try {
@@ -777,16 +773,14 @@ export default {
link,
});
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async verify(req, res) {
- const {
- params: { code },
- } = req;
+ async verify(c) {
+ const code = c.req.param("code");
try {
const { id, email } = await g(`verify:${code}`);
if (!id) fail("verification failed");
@@ -796,16 +790,15 @@ export default {
await s(`user:${id}`, user);
await s(`email:${email.toLowerCase()}`, id);
- res.send(pick(user, fields));
+ return c.json(pick(user, fields));
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async forgot(req, res) {
- const {
- body: { email },
- } = req;
+ async forgot(c) {
+ const body = await c.req.json();
+ const { email } = body;
try {
const uid = await g(`email:${email.toLowerCase()}`);
const user = await g(`user:${uid}`);
@@ -821,47 +814,45 @@ export default {
});
}
- res.send({});
+ return c.json({});
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async hidepay(req, res) {
- const {
- body: { username },
- } = req;
+ async hidepay(c) {
+ const body = await c.req.json();
+ const { username } = body;
const u = await getUser(username);
u.hidepay = true;
await s(`user:${u.id}`, u);
- res.send({});
+ return c.json({});
},
- async unlimit(req, res) {
- const {
- body: { username },
- } = req;
+ async unlimit(c) {
+ const body = await c.req.json();
+ const { username } = body;
const u = await getUser(username);
u.unlimited = true;
await s(`user:${u.id}`, u);
- res.send({});
+ return c.json({});
},
- async account(req, res) {
- const { id } = req.params;
- const { id: uid } = req.user;
+ async account(c) {
+ const id = c.req.param("id");
+ const { id: uid } = c.get("user");
const pos = await db.lPos(`${uid}:accounts`, id);
if (pos == null) fail("account not found");
const account = await g(`account:${id}`);
if (account) account.balance = await getBalance(id);
- res.send(account);
+ return c.json(account);
},
- async accounts(req, res) {
+ async accounts(c) {
try {
- const { user } = req;
+ const user = c.get("user");
const accounts = [];
for (const id of await db.lRange(`${user.id}:accounts`, 0, -1)) {
@@ -886,17 +877,18 @@ export default {
}
}
- res.send(accounts.reverse());
+ return c.json(accounts.reverse());
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async createAccount(req, res) {
+ async createAccount(c) {
try {
- const { fingerprint, pubkey, name, seed, type, arkAddress, accountIndex } = req.body;
- const { user } = req;
+ const body = await c.req.json();
+ const { fingerprint, pubkey, name, seed, type, arkAddress, accountIndex } = body;
+ const user = c.get("user");
const { id: uid } = user;
const id = v4();
@@ -917,14 +909,12 @@ export default {
await createBalanceAccount(id);
- // ARK accounts don't need Bitcoin Core wallet setup
if (type === "ark") {
const accountIds = await db.lRange(`${user.id}:accounts`, 0, -1);
for (const accId of accountIds) {
const acc = await g(`account:${accId}`);
if (acc?.type === "ark") {
- bail(res, "You already have an Ark vault");
- return;
+ return bail(c, "You already have an Ark vault");
}
}
const m = db
@@ -934,11 +924,9 @@ export default {
if (arkAddress) m.set(`arkaddr:${arkAddress}`, JSON.stringify({ aid: id, uid }));
await m.exec();
- res.send(account);
- return;
+ return c.json(account);
}
- // Bitcoin on-chain wallet setup
let node = rpc(config[type]);
await node.createWallet({
@@ -976,17 +964,18 @@ export default {
.lPush(`${user.id}:accounts`, id)
.exec();
- res.send(account);
+ return c.json(account);
} catch (e) {
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async updateAccount(req, res) {
- const { id } = req.params;
- const { id: uid } = req.user;
+ async updateAccount(c) {
+ const id = c.req.param("id");
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
const { name, autowithdraw, threshold, reserve, destination, currency, fingerprint, pubkey } =
- req.body;
+ body;
const pos = await db.lPos(`${uid}:accounts`, id);
if (pos == null) fail("account not found");
@@ -1017,19 +1006,19 @@ export default {
.catch((e) => console.error("importAccountHistory failed:", e.message));
}
- res.send(account);
+ return c.json(account);
},
- async deleteAccount(req, res) {
+ async deleteAccount(c) {
try {
- const { id } = req.body;
- const { id: uid } = req.user;
+ const body = await c.req.json();
+ const { id } = body;
+ const { id: uid } = c.get("user");
const account = await g(`account:${id}`);
const pos = await db.lPos(`${uid}:accounts`, id);
if (!(account && pos != null)) fail("account not found");
- // Only unload Bitcoin Core wallet for non-ARK accounts
if (account.type !== "ark") {
try {
const node = rpc({ ...config[account.type], wallet: id });
@@ -1046,25 +1035,27 @@ export default {
.del(`${id}:payments`)
.exec();
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async flash(req, res) {
- const { ssid, key, token } = req.body;
+ async flash(c) {
+ const body = await c.req.json();
+ const { ssid, key, token } = body;
const cfg = `${ssid.trim()}\n${key.trim()}\n${token.trim()}\n`;
await writeFile("./printer/config.txt", cfg, "utf8");
await $`./mklittlefs -c ./printer -p 256 -b 4096 -s 0x20000 ./littlefs.img`;
- res.header("Content-Type", "application/octet-stream");
- return res.send(createReadStream("./littlefs.img"));
+ return new Response(Bun.file("./littlefs.img"), {
+ headers: { "Content-Type": "application/octet-stream" },
+ });
},
- async app(req, res) {
- const { pubkey } = req.params;
- const { user } = req;
+ async app(c) {
+ const pubkey = c.req.param("pubkey");
+ const user = c.get("user");
const app = await g(`app:${pubkey}`);
if (app.uid !== user.id) fail("unauthorized");
@@ -1083,11 +1074,11 @@ export default {
app.nwc = `nostr+walletconnect://${serverPubkey2}?relay=${relay}&secret=${app.secret}&lud16=${lud16}`;
app.payments = payments.filter((p) => p);
- res.send(app);
+ return c.json(app);
},
- async apps(req, res) {
- const { user } = req;
+ async apps(c) {
+ const user = c.get("user");
const pubkeys = await db.sMembers(`${user.id}:apps`);
const apps = await Promise.all(pubkeys.map((p) => g(`app:${p}`)));
@@ -1112,14 +1103,15 @@ export default {
}),
);
- res.send(apps);
+ return c.json(apps);
},
- async updateApp(req, res) {
+ async updateApp(c) {
try {
- let { secret, pubkey, max_amount, max_fee, budget_renewal, name, notify } = req.body;
+ const body = await c.req.json();
+ let { secret, pubkey, max_amount, max_fee, budget_renewal, name, notify } = body;
- const { user } = req;
+ const user = c.get("user");
const uid = user.id;
let app = await g(pubkey);
@@ -1144,18 +1136,19 @@ export default {
await s(`app:${pubkey}`, app);
await db.sAdd(`${uid}:apps`, pubkey);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async deleteApp(req, res) {
+ async deleteApp(c) {
try {
- const { user } = req;
+ const user = c.get("user");
const uid = user.id;
- const { pubkey } = req.body;
+ const body = await c.req.json();
+ const { pubkey } = body;
const app = await g(`app:${pubkey}`);
if (app && uid !== app.uid) {
warn(app.uid, uid);
@@ -1163,110 +1156,118 @@ export default {
}
await db.sRem(`${uid}:apps`, pubkey);
await db.del(`app:${pubkey}`);
- res.send({});
+ return c.json({});
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async addPin(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async addPin(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sAdd(`${uid}:pins`, id);
- res.send({});
+ return c.json({});
},
- async deletePin(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async deletePin(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sRem(`${uid}:pins`, id);
- res.send({});
+ return c.json({});
},
- async trust(req, res) {
- const { id } = req.user;
- res.send(await db.sMembers(`${id}:trust`));
+ async trust(c) {
+ const { id } = c.get("user");
+ return c.json(await db.sMembers(`${id}:trust`));
},
- async addTrust(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async addTrust(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sAdd(`${uid}:trust`, id);
- res.send({});
+ return c.json({});
},
- async deleteTrust(req, res) {
- const { id: uid } = req.user;
- const { id } = req.body;
+ async deleteTrust(c) {
+ const { id: uid } = c.get("user");
+ const body = await c.req.json();
+ const { id } = body;
await db.sRem(`${uid}:trust`, id);
- res.send({});
+ return c.json({});
},
- async passkeyRegisterOptions(req, res) {
+ async passkeyRegisterOptions(c) {
try {
- const { user } = req;
- const origin = req.body.origin || `https://${config.hostname}`;
+ const user = c.get("user");
+ const body = await c.req.json();
+ const origin = body.origin || `https://${config.hostname}`;
const options = await generatePasskeyRegistration(user, origin);
- res.send(options);
+ return c.json(options);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async passkeyRegisterVerify(req, res) {
+ async passkeyRegisterVerify(c) {
try {
- const { user } = req;
- const origin = req.body.origin || `https://${config.hostname}`;
- const credential = await verifyPasskeyRegistration(user, req.body, origin);
+ const user = c.get("user");
+ const body = await c.req.json();
+ const origin = body.origin || `https://${config.hostname}`;
+ const credential = await verifyPasskeyRegistration(user, body, origin);
if (!user.passkeys) user.passkeys = [];
user.passkeys.push(credential);
await s(`user:${user.id}`, user);
- res.send({ ok: true });
+ return c.json({ ok: true });
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async passkeyLoginOptions(req, res) {
+ async passkeyLoginOptions(c) {
try {
- const origin = req.body.origin || `https://${config.hostname}`;
+ const body = await c.req.json();
+ const origin = body.origin || `https://${config.hostname}`;
const options = await generatePasskeyLogin(origin);
- res.send(options);
+ return c.json(options);
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async passkeyLoginVerify(req, res) {
+ async passkeyLoginVerify(c) {
try {
- const { credential, challengeId, origin: reqOrigin } = req.body;
+ const body = await c.req.json();
+ const { credential, challengeId, origin: reqOrigin } = body;
const origin = reqOrigin || `https://${config.hostname}`;
const user = await verifyPasskeyLogin(credential, challengeId, origin);
const payload = { id: user.id };
const token = jwt.sign(payload, config.jwt);
- res.send({ user: pick(user, whitelist), token });
+ return c.json({ user: pick(user, whitelist), token });
} catch (e) {
console.log(e);
- bail(res, e.message);
+ return bail(c, e.message);
}
},
- async credits(req, res) {
- const { id } = req.user;
+ async credits(c) {
+ const { id } = c.get("user");
const bitcoin = await getCredit(id, "bitcoin");
const lightning = await getCredit(id, "lightning");
const liquid = await getCredit(id, "liquid");
- res.send({ bitcoin, lightning, liquid });
+ return c.json({ bitcoin, lightning, liquid });
},
- async ro(req, res) {
- const { user } = req;
+ async ro(c) {
+ const user = c.get("user");
const payload = { id: `${user.id}-ro` };
const token = jwt.sign(payload, config.jwt);
- res.send(token);
+ return c.json(token);
},
};