aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-19 00:09:21 -0700
committerAdam Soltys <[email protected]>2026-06-19 00:09:21 -0700
commit2f0e6d052efbca7f695dda942ab7da6221da3aa9 (patch)
treec3c631549538785e3577f6af537238a28a1b429a /src
parent28cc6c706b0bd2b5322393dd3869e4161ec50423 (diff)
downloadcoinos-ui-2f0e6d052efbca7f695dda942ab7da6221da3aa9.tar.gz
coinos-ui-2f0e6d052efbca7f695dda942ab7da6221da3aa9.zip
receive/invoice: build POST body from snapshot, don't mutate $derived
The merchant receive and invoice pages mutated the $derived `invoice` proxy (setType/setAmount/setMemo) then POSTed it. Mutating a $derived and JSON.stringify-ing it serializes unreliably, so a stale type (on-chain when lightning was expected) and a stale/10x-shifted amount could leak from a prior interaction into POST /invoice (funk POS bug). Build the body as { ...$state.snapshot(invoice), ...changes } so the intended type/amount/address_type reliably win; update() takes the plain body. Front-end only. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/routes/(app)/[username]/receive/+page.svelte23
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte23
2 files changed, 24 insertions, 22 deletions
diff --git a/src/routes/(app)/[username]/receive/+page.svelte b/src/routes/(app)/[username]/receive/+page.svelte
index cd692abf..4bc37a7f 100644
--- a/src/routes/(app)/[username]/receive/+page.svelte
+++ b/src/routes/(app)/[username]/receive/+page.svelte
@@ -53,10 +53,10 @@
amountFiat,
tip;
- let update = async () => {
+ let update = async (body) => {
try {
({ id } = await post(`/invoice`, {
- invoice,
+ invoice: body,
user: { username, currency },
}));
@@ -77,10 +77,12 @@
if (type === types.lightning && !amount && typeof newAmount === "undefined")
goto(`/${username}/receive`, { invalidateAll: true, noScroll: true });
else {
- if (typeof newAmount !== "undefined") invoice.amount = newAmount;
- invoice.address_type = address_type;
- invoice.type = type;
- await update();
+ // Build the POST body from a snapshot + explicit changes. Mutating the
+ // $derived `invoice` proxy and POSTing it serializes unreliably, leaking
+ // stale type/amount from a prior interaction (see funk POS bug).
+ const body = { ...$state.snapshot(invoice), type, address_type };
+ if (typeof newAmount !== "undefined") body.amount = newAmount;
+ await update(body);
}
settingType = false;
};
@@ -93,10 +95,9 @@
if (typeof $amountPrompt === "undefined") $amountPrompt = true;
amount = newAmount;
- invoice.amount = newAmount;
- invoice.tip = null;
+ const body = { ...$state.snapshot(invoice), amount: newAmount, tip: null };
- await update();
+ await update(body);
};
let newAmount = $state(0);
@@ -109,8 +110,8 @@
e.stopPropagation();
settingMemo = false;
- invoice.memo = memo;
- await update();
+ const body = { ...$state.snapshot(invoice), memo };
+ await update(body);
};
let memo = $state();
diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte
index 29dd815d..a2a299bd 100644
--- a/src/routes/(app)/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/+page.svelte
@@ -70,10 +70,10 @@
if ($amountPrompt && !amount) toggleAmount();
});
- let update = async () => {
+ let update = async (body) => {
try {
({ id } = await post(`/invoice`, {
- invoice,
+ invoice: body,
user: { username, currency },
}));
@@ -94,10 +94,12 @@
if (type === types.lightning && !amount && typeof newAmount === "undefined")
goto(`/${username}/receive`, { invalidateAll: true, noScroll: true });
else {
- if (typeof newAmount !== "undefined") invoice.amount = newAmount;
- invoice.address_type = address_type;
- invoice.type = type;
- await update();
+ // Build the POST body from a snapshot + explicit changes. Mutating the
+ // $derived `invoice` proxy and POSTing it serializes unreliably, leaking
+ // stale type/amount/hash from a prior interaction (see funk POS bug).
+ const body = { ...$state.snapshot(invoice), type, address_type };
+ if (typeof newAmount !== "undefined") body.amount = newAmount;
+ await update(body);
}
settingType = false;
};
@@ -113,10 +115,9 @@
settingAmount = false;
if (typeof $amountPrompt === "undefined") $amountPrompt = true;
- invoice.amount = newAmount;
- invoice.tip = 0;
+ const body = { ...$state.snapshot(invoice), amount: newAmount, tip: 0 };
- await update();
+ await update(body);
};
let setMemo = async (e) => {
@@ -124,8 +125,8 @@
e.stopPropagation();
settingMemo = false;
- invoice.memo = memo;
- await update();
+ const body = { ...$state.snapshot(invoice), memo };
+ await update(body);
};
let settingMemo = $state();