aboutsummaryrefslogtreecommitdiffstats
path: root/routes
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-14 22:06:25 -0700
committerAdam Soltys <[email protected]>2026-07-14 22:06:25 -0700
commit81c3593f1eec7f94dc9cbc22edc3456fa57b95cb (patch)
tree731ca6c880d7a6dc4d29ad4f28d156fb69720b72 /routes
parent0d55c0ccceb378a9c1992f6e7d82a6786cdd19b8 (diff)
downloadcoinos-server-81c3593f1eec7f94dc9cbc22edc3456fa57b95cb.tar.gz
coinos-server-81c3593f1eec7f94dc9cbc22edc3456fa57b95cb.zip
preimages: support fiat prices, converted at invoice-creation time
POST /preimages now accepts fiat + optional currency (defaults to the merchant's) as an alternative to a sat price. The fiat amount is stored per entry and converted to sats at the moment a usePreimage invoice is created, so the price tracks the exchange rate instead of freezing at upload time. The invoice also takes on the entry's currency and rate so receipts display the fiat price exactly as the merchant set it. If no rate is available for the entry's currency, the preimage is pushed back onto the queue and creation fails rather than issuing an any-amount invoice. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'routes')
-rw-r--r--routes/preimages.ts33
1 files changed, 25 insertions, 8 deletions
diff --git a/routes/preimages.ts b/routes/preimages.ts
index f6632b38..49673d17 100644
--- a/routes/preimages.ts
+++ b/routes/preimages.ts
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
-import { db } from "$lib/db";
+import { db, g } from "$lib/db";
import { parseEntry } from "$lib/invoices";
import { bail, fail } from "$lib/utils";
@@ -7,9 +7,10 @@ import { bail, fail } from "$lib/utils";
// with usePreimage: true — each such invoice consumes the next queued preimage
// so its payment hash is sha256(preimage). Once paid, the preimage is exposed
// on the invoice record, letting a static storefront sell decryption secrets
-// with no backend of its own. An optional per-batch price (sats) is enforced
-// at invoice creation — invoice creation is unauthenticated, so without it a
-// buyer could name their own price for the secret.
+// with no backend of its own. An optional per-batch price — sats, or a fiat
+// amount converted at invoice-creation time — is enforced at invoice creation;
+// creation is unauthenticated, so without it a buyer could name their own
+// price for the secret.
const MAX_QUEUE = 1000;
const key = (uid) => `${uid}:preimages`;
@@ -19,7 +20,8 @@ const sha256 = (hex) =>
export default {
async add(req, res) {
try {
- const { preimages, price = null } = req.body;
+ const { preimages, price = null, fiat = null } = req.body;
+ let { currency = null } = req.body;
if (!Array.isArray(preimages) || !preimages.length)
fail("preimages array required");
if (
@@ -28,8 +30,19 @@ export default {
)
)
fail("preimages must be 64-character hex strings");
+ if (price !== null && fiat !== null)
+ fail("specify price (sats) or fiat, not both");
if (price !== null && (!Number.isInteger(price) || price <= 0))
fail("price must be a positive integer amount of sats");
+ if (fiat !== null) {
+ if (typeof fiat !== "number" || !(fiat > 0))
+ fail("fiat must be a positive number");
+ currency ||= req.user.currency;
+ const rates = await g("rates");
+ if (!rates?.[currency]) fail(`unsupported currency ${currency}`);
+ } else {
+ currency = null;
+ }
const k = key(req.user.id);
const existing = await db.lRange(k, 0, -1);
@@ -47,7 +60,9 @@ export default {
if (fresh.length)
await db.rPush(
k,
- fresh.map((preimage) => JSON.stringify({ preimage, price })),
+ fresh.map((preimage) =>
+ JSON.stringify({ preimage, price, fiat, currency }),
+ ),
);
res.send({
@@ -56,6 +71,8 @@ export default {
preimage,
hash: sha256(preimage),
price,
+ fiat,
+ currency,
})),
});
} catch (e) {
@@ -69,8 +86,8 @@ export default {
res.send({
queued: entries.length,
preimages: entries.map((e) => {
- const { preimage, price } = parseEntry(e);
- return { preimage, hash: sha256(preimage), price };
+ const { preimage, price, fiat, currency } = parseEntry(e);
+ return { preimage, hash: sha256(preimage), price, fiat, currency };
}),
});
} catch (e) {