aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-04 12:43:11 -0800
committerAdam Soltys <[email protected]>2026-03-04 12:43:11 -0800
commita6bc64017162bcaf1b7c2f3525f89c0e8dbe8d68 (patch)
tree13e8bc40b4dca5426565a851c7fa372271aa5f88
parent6781fd2935ebc3abdeed182b4cee62c25c5482e7 (diff)
downloadcoinos-server-a6bc64017162bcaf1b7c2f3525f89c0e8dbe8d68.tar.gz
coinos-server-a6bc64017162bcaf1b7c2f3525f89c0e8dbe8d68.zip
fund authorizations
-rw-r--r--index.ts2
-rw-r--r--lib/payments.ts2
-rw-r--r--routes/payments.ts59
3 files changed, 56 insertions, 7 deletions
diff --git a/index.ts b/index.ts
index ef6d9778..62db60f7 100644
--- a/index.ts
+++ b/index.ts
@@ -108,6 +108,8 @@ app.get("/fund/:name/managers", payments.managers);
app.post("/fund/managers", auth, payments.addManager);
app.post("/fund/:name/managers/delete", auth, payments.deleteManager);
app.post("/authorize", auth, payments.authorize);
+app.get("/fund/:id/authorizations", payments.listAuthorizations);
+app.post("/fund/:id/authorization/:authId/delete", auth, payments.deleteAuthorization);
app.post("/take", auth, payments.take);
app.post("/print", auth, payments.print);
app.post("/send/:lnaddress/:amount", auth, payments.lnaddress);
diff --git a/lib/payments.ts b/lib/payments.ts
index bdaf2b1d..d0c43235 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -159,7 +159,7 @@ export const debit = async ({
const userLimit = await g("limit");
const frozen = (await g("hardfreeze")) || ((await g("freeze")) && type !== PaymentType.internal);
- if (frozen || (amount > userLimit && !whitelisted) || amount > serverLimit) {
+ if (frozen || (userLimit != null && amount > userLimit && !whitelisted) || (serverLimit != null && amount > serverLimit)) {
warn("Blocking", user.username, amount, hash, user.id, type, frozen, userLimit, serverLimit);
fail("Problem sending payment");
}
diff --git a/routes/payments.ts b/routes/payments.ts
index dd679816..ab876823 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -360,8 +360,10 @@ export default {
payments = payments.filter((p) => p);
- const authorization = await g(`authorization:${id}`);
- return c.json({ amount, authorization: authorization?.amount, payments });
+ const authIds = await db.lRange(`fund:${id}:authorizations`, 0, -1) || [];
+ const allAuths = await Promise.all(authIds.map((authId) => g(`authorization:${authId}`)));
+ const authorizations = allAuths.filter((a) => a && !a.claimed);
+ return c.json({ amount, authorizations, payments });
},
async authorize(c) {
@@ -373,21 +375,51 @@ export default {
const managers = [...await db.sMembers(`fund:${id}:managers`)];
if (managers.length && !managers.includes(uid)) fail("Unauthorized");
+ const authId = v4();
const authorization = {
+ authId,
+ fundId: id,
uid,
currency,
fiat,
amount,
+ created: Date.now(),
};
- await s(`authorization:${id}`, authorization);
+ await s(`authorization:${authId}`, authorization);
+ await db.lPush(`fund:${id}:authorizations`, authId);
+ return c.json({ authId });
+ },
+
+ async listAuthorizations(c) {
+ const id = c.req.param("id");
+ const authIds = await db.lRange(`fund:${id}:authorizations`, 0, -1) || [];
+ const allAuths = await Promise.all(authIds.map((authId) => g(`authorization:${authId}`)));
+ const authorizations = allAuths.filter((a) => a && !a.claimed);
+ return c.json(authorizations);
+ },
+
+ async deleteAuthorization(c) {
+ const user = c.get("user");
+ const id = c.req.param("id");
+ const authId = c.req.param("authId");
+
+ const managers = [...await db.sMembers(`fund:${id}:managers`)];
+ if (managers.length && !managers.includes(user.id)) fail("Unauthorized");
+
+ const authorization = await g(`authorization:${authId}`);
+ if (!authorization || authorization.fundId !== id) return bail(c, "Authorization not found");
+ if (authorization.claimed) return bail(c, "Authorization already claimed");
+
+ await db.lRem(`fund:${id}:authorizations`, 0, authId);
+ await db.del(`authorization:${authId}`);
return c.json({});
},
async take(c) {
const body = await c.req.json();
const user = c.get("user");
- let { id, amount, invoice: iid } = body;
+ let { id, amount, invoice: iid, authId } = body;
try {
amount = Number.parseInt(amount);
if (amount <= 0) fail("Invalid amount");
@@ -402,14 +434,29 @@ export default {
iid = inv.id;
}
- const authorization = await g(`authorization:${id}`);
+ let authorization;
+ if (authId) {
+ authorization = await g(`authorization:${authId}`);
+ if (authorization && authorization.fundId !== id) authorization = null;
+ } else {
+ const authIds = await db.lRange(`fund:${id}:authorizations`, 0, -1) || [];
+ for (const aid of authIds) {
+ const auth = await g(`authorization:${aid}`);
+ if (auth && !auth.claimed) {
+ authorization = auth;
+ authId = aid;
+ break;
+ }
+ }
+ }
+
if (authorization && !authorization.claimed) {
const { currency, fiat } = authorization;
amount = Math.min(amount, sats(fiat / rates[currency]));
const sender = await getUser(authorization.uid);
authorization.claimed = true;
- await s(`authorization:${id}`, authorization);
+ await s(`authorization:${authId}`, authorization);
const { hash } = await generate({
invoice: { amount, type: "lightning" },