aboutsummaryrefslogtreecommitdiffstats
path: root/lib
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 /lib
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 'lib')
-rw-r--r--lib/invoices.ts30
1 files changed, 24 insertions, 6 deletions
diff --git a/lib/invoices.ts b/lib/invoices.ts
index c3722ef5..613287a3 100644
--- a/lib/invoices.ts
+++ b/lib/invoices.ts
@@ -13,14 +13,16 @@ import { PaymentType } from "$lib/types";
const bc = rpc(config.bitcoin);
const lq = rpc(config.liquid);
-// Preimage queue entries are {preimage, price} JSON, but entries queued
-// before prices existed are bare hex strings — treat those as priceless
+// Preimage queue entries are {preimage, price, fiat, currency} JSON, but
+// entries queued before prices existed are bare hex strings — treat those
+// as priceless
export const parseEntry = (e) => {
try {
const parsed = JSON.parse(e);
- if (parsed?.preimage) return { price: null, ...parsed };
+ if (parsed?.preimage)
+ return { price: null, fiat: null, currency: null, ...parsed };
} catch {}
- return { preimage: e, price: null };
+ return { preimage: e, price: null, fiat: null, currency: null };
};
export const generate = async ({ invoice, user }) => {
@@ -114,11 +116,27 @@ export const generate = async ({ invoice, user }) => {
const queue = `${user.id}:preimages`;
let entry;
while ((entry = await db.lPop(queue))) {
- const { preimage, price } = parseEntry(entry);
+ const {
+ preimage,
+ price,
+ fiat: entryFiat,
+ currency: entryCurrency,
+ } = parseEntry(entry);
// The invoice creator is unauthenticated, so a merchant-set price
// must win over the amount in the request — otherwise a buyer
- // could name their own price for the secret
+ // could name their own price for the secret. Fiat prices convert
+ // at the current rate, and the invoice takes on the merchant's
+ // currency so receipts show the fiat price as set.
if (price) amount = price;
+ else if (entryFiat) {
+ if (!rates[entryCurrency]) {
+ await db.lPush(queue, entry);
+ fail(`no rate available for ${entryCurrency}`);
+ }
+ currency = entryCurrency;
+ rate = rates[currency];
+ amount = Math.round((SATS * entryFiat) / rate);
+ }
try {
r = await ln.invoice({
...args,