diff options
| author | Adam Soltys <[email protected]> | 2026-09-16 04:53:22 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-16 04:53:22 -0700 |
| commit | c72fa2a5ccf1cc04073f9ff12606491971dadd36 (patch) | |
| tree | e58124db24fb734c4a443b4ef20e9f25efe78c24 /routes | |
| parent | 19f73ae9cf2afffbbb2abc71eb80c6d5bdccca52 (diff) | |
| download | coinos-server-c72fa2a5ccf1cc04073f9ff12606491971dadd36.tar.gz coinos-server-c72fa2a5ccf1cc04073f9ff12606491971dadd36.zip | |
ecash: redeem tokens by melting to the user's own invoice
Redeeming ecash is now a plain NUT-05 melt into a Lightning invoice owned
by the redeeming user, credited by the regular lightning listener. Drop
the house proof pool and its lock, the /mint and /melt routes, and the
special-casing of a mint account in NWC and payment routing. Tokens are
decoded against the mint's keyset list so short v1 ids resolve, and an
empty keyset list is never cached.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'routes')
| -rw-r--r-- | routes/ecash.ts | 143 | ||||
| -rw-r--r-- | routes/info.ts | 4 | ||||
| -rw-r--r-- | routes/payments.ts | 2 |
3 files changed, 21 insertions, 128 deletions
diff --git a/routes/ecash.ts b/routes/ecash.ts index 4a1806a3..d089ed15 100644 --- a/routes/ecash.ts +++ b/routes/ecash.ts @@ -1,31 +1,10 @@ -import { db, g, s } from "$lib/db"; -import { check, claim, get, mint } from "$lib/ecash"; -import { err, l } from "$lib/logging"; -import { credit, debit } from "$lib/payments"; -import { emit } from "$lib/sockets"; -import { bail, fail, getInvoice } from "$lib/utils"; +import { s } from "$lib/db"; +import { check, get, redeem } from "$lib/ecash"; +import { err } from "$lib/logging"; +import { bail, fail, getInvoice, getUser } from "$lib/utils"; import { getEncodedToken } from "@cashu/cashu-ts"; import { v4 } from "uuid"; -import { PaymentType } from "$lib/types"; -const { ecash: type } = PaymentType; - -Error.stackTraceLimit = 100; // Set this to the desired limit - -const sendCash = async ({ amount, user }) => { - const id = v4(); - const hash = v4(); - - const p = await debit({ hash, amount, user, type }); - const token = await mint(parseInt(amount)); - p.memo = id; - const { id: pid } = p; - await s(`payment:${pid}`, p); - s(`cash:${id}`, token); - - return { id, token, pid }; -}; - export default { async save(req, res) { const { @@ -47,6 +26,7 @@ export default { } = req; try { const token = await get(id); + if (!token) fail("Ecash not found"); const status = await check(token); res.send({ token, status }); } catch (e) { @@ -61,114 +41,31 @@ export default { user, } = req; try { - const amount = await claim(token); - - let memo; - const hash = v4(); - const { currency, id: uid } = user; - const rates = await g("rates"); - await s(`invoice:${hash}`, { - currency, - id: hash, - hash, - rate: rates[currency], - uid, - received: 0, - }); - - await credit({ hash, amount, memo, ref: user.id, type }); - - res.send({ ok: true }); + const amount = await redeem({ token, user }); + res.send({ ok: true, amount }); } catch (e) { err(e.message); bail(res, e.message); } }, - async mint(req, res) { - const { body, user } = req; - const amount = parseInt(body.amount); - fail("disabled"); - res.send(await sendCash({ amount, user })); - }, - - async melt(req, res) { - let { - body: { amount, bolt11: hash, preimage }, - user, - } = req; - try { - const ref = preimage; - const { lightning: type } = PaymentType; - if (user.username !== "mint") fail("unauthorized"); - - amount = Math.round(amount / 1000); - // A negative (or NaN) amount here is a direct ledger-inflation primitive: - // db.debit subtracts it, so -N ADDS N to the balance, and the stored - // record gets amount: -(-N). The caller gate above limits this to the - // mint service account, but that account is not a trust boundary we want - // the ledger's integrity resting on — mirror the check debit() makes in - // lib/payments.ts before any balance write. - if (!Number.isFinite(amount) || amount <= 0) - fail("Amount must be greater than zero"); - const { id: uid, currency } = user; - const ourfee = await db.debit( - `balance:${uid}`, - `credit:${type}:${uid}`, - "Insufficient funds", - amount || 0, - 0, - 0, - 0, - 0 - ); - - const rates = await g("rates"); - const rate = rates[currency]; - - if (ourfee.err) fail(ourfee.err); - - const id = v4(); - const p = { - id, - amount: -amount, - hash, - ourfee, - uid, - confirmed: true, - rate, - currency, - type, - ref, - created: Date.now(), - }; - - await s(`payment:${hash}`, id); - await s(`payment:${id}`, p); - await db.lPush(`${uid}:payments`, id); - - l(user.username, "sent", type, amount); - emit(user.id, "payment", p); - - res.send(p); - } catch (e) { - bail(res, e.message); - } - }, - + // NUT-18 payment-request transport: a wallet POSTs proofs for one of our + // ecash-type invoices, which are melted into a Lightning invoice for the + // invoice's owner under the same id. async receive(req, res) { try { const { id, proofs, mint, memo } = req.body; - const { uid: ref } = await getInvoice(id); - - const amount = await claim( - getEncodedToken({ - mint, - proofs, - }), - ); + const invoice = await getInvoice(id); + if (!invoice) fail("Invoice not found"); + const user = await getUser(invoice.uid); + if (!user) fail("User not found"); - await credit({ hash: id, amount, memo, ref, type }); + await redeem({ + token: getEncodedToken({ mint, proofs }), + user, + invoice, + memo, + }); res.send({ id }); } catch (e) { diff --git a/routes/info.ts b/routes/info.ts index aae130bf..4ab11872 100644 --- a/routes/info.ts +++ b/routes/info.ts @@ -2,7 +2,6 @@ import { archive } from "$lib/db"; import { db } from "$lib/db"; import { getHealthStatus } from "$lib/health"; import ln from "$lib/ln"; -import { pool } from "$lib/ecash"; export default { async health(_, res) { @@ -28,10 +27,7 @@ export default { ); const lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0)); - const cash = (await pool()).reduce((a, b) => a + b.amount, 0); - const info = { - cash, lnchannel, lnwallet, }; diff --git a/routes/payments.ts b/routes/payments.ts index accb09fd..cf0248fa 100644 --- a/routes/payments.ts +++ b/routes/payments.ts @@ -66,7 +66,7 @@ export default { const invoice = await getInvoice(payreq || hash); const recipient = invoice ? await getUser(invoice.uid) : undefined; if (payreq) { - if (invoice && recipient.username !== "mint") { + if (invoice) { if (invoice.aid === user.id) fail("Cannot send to self"); hash = payreq; if (!amount) ({ amount } = invoice); |
