aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-09 17:22:01 -0800
committerAdam Soltys <[email protected]>2026-02-09 17:22:01 -0800
commit0472ded5b52807f93ce5761ad884928008410e53 (patch)
tree3399462409e8ed1ac6de2390493ff0a27000c57b
parent6f933c6e989f934364a276815826364fec5a85b4 (diff)
downloadcoinos-server-0472ded5b52807f93ce5761ad884928008410e53.tar.gz
coinos-server-0472ded5b52807f93ce5761ad884928008410e53.zip
ark fixes
-rw-r--r--lib/invoices.ts2
-rw-r--r--lib/payments.ts60
-rw-r--r--routes/payments.ts137
3 files changed, 96 insertions, 103 deletions
diff --git a/lib/invoices.ts b/lib/invoices.ts
index e461c1a7..71f95b8c 100644
--- a/lib/invoices.ts
+++ b/lib/invoices.ts
@@ -63,6 +63,7 @@ export const generate = async ({ invoice, user }) => {
if (!id) id = v4();
let text;
+ let path;
let paymentHash;
if (account.type === "ark") {
@@ -156,6 +157,7 @@ export const generate = async ({ invoice, user }) => {
id,
items,
memo,
+ path,
rate,
paymentHash,
pending: 0,
diff --git a/lib/payments.ts b/lib/payments.ts
index 0fd87559..99f07cc8 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -62,6 +62,66 @@ const { URL } = process.env;
const dust = 547;
+export const getUserRate = async (user) => {
+ const rates = await g("rates");
+ const { currency } = user;
+ const rate = rates[currency];
+ return { rates, rate, currency };
+};
+
+export const acquireArkLock = async (hash) => {
+ const locked = await db.set(`arklock:${hash}`, "1", { NX: true, EX: 60 });
+ if (!locked) fail("Already processed");
+};
+
+export const createArkPayment = async ({
+ aid,
+ uid,
+ amount,
+ hash,
+ rate,
+ currency,
+ type = PaymentType.ark,
+ iid = undefined,
+ created = Date.now(),
+ mapHashToId = false,
+ extraHashMappings = [] as string[],
+ extraMultiOps = undefined as ((m: any, p: any) => void) | undefined,
+}) => {
+ const p = {
+ id: v4(),
+ aid,
+ amount,
+ hash,
+ confirmed: true,
+ rate,
+ currency,
+ type,
+ uid,
+ iid,
+ created,
+ };
+
+ const m = db.multi();
+ m.set(`payment:${p.id}`, JSON.stringify(p))
+ .lPush(`${aid}:payments`, p.id)
+ .set(`${aid}:payments:last`, p.created);
+
+ if (mapHashToId) {
+ m.set(`payment:${aid}:${hash}`, JSON.stringify(p.id));
+ }
+
+ for (const h of extraHashMappings) {
+ m.set(`payment:${aid}:${h}`, JSON.stringify(p.id));
+ }
+
+ if (extraMultiOps) extraMultiOps(m, p);
+
+ await m.exec();
+
+ return p;
+};
+
export const debit = async ({
aid = undefined,
hash,
diff --git a/routes/payments.ts b/routes/payments.ts
index a9e14867..93c46077 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -10,11 +10,14 @@ import ln from "$lib/ln";
import { err, l, warn } from "$lib/logging";
import mqtt from "$lib/mqtt";
import {
+ acquireArkLock,
build,
completePayment,
+ createArkPayment,
credit,
debit,
decode,
+ getUserRate,
processWatchedTx,
sendInternal,
sendLightning,
@@ -813,31 +816,17 @@ export default {
try {
const { body, user } = req;
const { hash, amount, aid } = body;
- const { id: uid } = user;
-
- const rates = await g("rates");
- const { currency } = user;
- const rate = rates[currency];
+ const { rate, currency } = await getUserRate(user);
- const p = {
- id: v4(),
+ const p = await createArkPayment({
aid,
+ uid: user.id,
amount: -amount,
hash,
- confirmed: true,
rate,
currency,
- type: PaymentType.ark,
- uid,
- created: Date.now(),
- };
-
- const m = db.multi();
- m.set(`payment:${p.id}`, JSON.stringify(p))
- .set(`payment:${aid}:${hash}`, JSON.stringify(p.id))
- .lPush(`${aid}:payments`, p.id)
- .set(`${aid}:payments:last`, p.created);
- await m.exec();
+ mapHashToId: true,
+ });
res.send(p);
} catch (e) {
@@ -849,35 +838,19 @@ export default {
try {
const { body, user } = req;
const { amount, hash, aid } = body;
- const { id: uid } = user;
if (amount <= 0) fail("Invalid amount");
+ await acquireArkLock(hash);
+ const { rate, currency } = await getUserRate(user);
- const locked = await db.set(`arklock:${hash}`, "1", { NX: true, EX: 60 });
- if (!locked) fail("Already processed");
-
- const rates = await g("rates");
- const { currency } = user;
- const rate = rates[currency];
-
- const p = {
- id: v4(),
+ const p = await createArkPayment({
aid,
+ uid: user.id,
amount,
hash,
- confirmed: true,
rate,
currency,
- type: PaymentType.ark,
- uid,
- created: Date.now(),
- };
-
- const m = db.multi();
- m.set(`payment:${p.id}`, JSON.stringify(p))
- .lPush(`${aid}:payments`, p.id)
- .set(`${aid}:payments:last`, p.created);
- await m.exec();
+ });
res.send(p);
} catch (e) {
@@ -902,65 +875,37 @@ export default {
let { aid, type, currency } = invoice;
- // Verify VTXO belongs to the server's Ark wallet
if (hash) {
- const locked = await db.set(`arklock:${hash}`, "1", { NX: true, EX: 60 });
- if (!locked) fail("Already processed");
+ await acquireArkLock(hash);
- if (!(await verifyArkVtxo(hash)))
- fail("VTXO not found in server wallet");
+ // 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");
+ }
}
invoice.received += amount;
- const rates = await g("rates");
+ 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 = {
- id: v4(),
+ const p = await createArkPayment({
aid: uid,
- iid,
+ uid,
amount,
hash,
- confirmed: true,
rate,
currency,
type,
- uid,
- created: Date.now(),
- };
-
- const m = db.multi();
- m.set(`invoice:${invoice.id}`, JSON.stringify(invoice))
- .set(`payment:${p.id}`, JSON.stringify(p))
- .lPush(`${uid}:payments`, p.id)
- .set(`${uid}:payments:last`, p.created);
-
- if (hash) m.set(`payment:${aid}:${hash}`, p.id);
-
- // Record debit on the vault account if it differs from custodial
- if (aid && aid !== uid) {
- const d = {
- id: v4(),
- aid,
- amount: -amount,
- hash,
- confirmed: true,
- rate,
- currency,
- type,
- uid,
- created: p.created,
- };
-
- m.set(`payment:${d.id}`, JSON.stringify(d))
- .lPush(`${aid}:payments`, d.id)
- .set(`${aid}:payments:last`, d.created);
- }
-
- await m.exec();
+ iid,
+ extraMultiOps: (m, p) => {
+ m.set(`invoice:${invoice.id}`, JSON.stringify(invoice));
+ if (hash) m.set(`payment:${aid}:${hash}`, p.id);
+ },
+ });
await completePayment(invoice, p, user);
@@ -974,7 +919,7 @@ export default {
try {
const { user } = req;
const { transactions, aid } = req.body;
- const { id: uid, currency } = user;
+ const { id: uid } = user;
// Per-account lock prevents concurrent syncs from racing
const lockKey = `arksynclock:${aid}`;
@@ -982,8 +927,7 @@ export default {
if (!gotLock) return res.send({ synced: 0, received: 0, payments: [] });
try {
- const rates = await g("rates");
- const rate = rates[currency];
+ const { rate, currency } = await getUserRate(user);
let synced = 0;
let received = 0;
@@ -1011,29 +955,16 @@ export default {
const primaryHash = hashes[0];
- const p = {
- id: v4(),
+ const p = await createArkPayment({
aid,
+ uid,
amount: tx.amount,
hash: primaryHash,
- confirmed: true,
rate,
currency,
- type: PaymentType.ark,
- uid,
created: tx.createdAt,
- };
-
- const m = db.multi();
- m.set(`payment:${p.id}`, JSON.stringify(p))
- .lPush(`${aid || uid}:payments`, p.id)
- .set(`${aid || uid}:payments:last`, p.created);
-
- for (const h of hashes) {
- m.set(`payment:${aid}:${h}`, JSON.stringify(p.id));
- }
-
- await m.exec();
+ extraHashMappings: hashes,
+ });
payments.push(p);
synced++;