@forgesworn/moneyer 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.3] - 2026-08-22
4
+
5
+ - **Refusals are logged, so an operator can see why a wallet is failing.**
6
+ A holder reported "notes already spent" on every melt and the journal for
7
+ that whole day carried startup banners and one invoice sweep - nothing to
8
+ compare the report against. Refusals now log the method, the path and the
9
+ reason. Never the query string, because a note URL carries the k1 and log
10
+ files travel; and never the informational GET, which a single restore
11
+ walks twenty-odd unknown indexes of by design.
12
+
13
+ ## [0.3.2] - 2026-08-22
14
+
15
+ - **A note with a melt in flight is no longer advertised as withdrawable.**
16
+ The mutating callback already refused a pending note; the informational
17
+ GET did not, so the check LUD-25 points a careful holder at was the one
18
+ that lied. That is the exact gap a sell-during-melt needs: start a melt,
19
+ show the buyer a healthy GET, take payment out of band, let the melt
20
+ settle. `fetchNoteInfo` on a pending note now fails the same way the
21
+ callback does, and lnurlcash-kit classifies it as `PendingNoteError`, so
22
+ a wallet parks the note rather than writing it off as unknown. Found by
23
+ re-reading LUD-25 against dni's lnurl-mint, which has had this guard.
24
+ - A flake in the melt suite: `leaves an unconfirmable outcome pending`
25
+ slept a fixed 100 ms, which was waiting for an in-flight melt to finish
26
+ exhausting its confirmations rather than for any state, and reconcile
27
+ deliberately skips a melt this process still has in flight. It now
28
+ retries reconcile until it can act.
29
+
3
30
  ## [0.3.1] - 2026-08-22
4
31
 
5
32
  - **Rounding the fee to a whole sat now really is the default.** 0.3.0
package/dist/server.js CHANGED
@@ -312,7 +312,20 @@ export const createMoneyer = async (config, deps = {}) => {
312
312
  });
313
313
  res.end(JSON.stringify(body));
314
314
  };
315
- const fail = (reason, status = 200) => send({ status: 'ERROR', reason }, status);
315
+ // Refusals go to the operator's log, because a mint that refuses in
316
+ // silence cannot be debugged from the outside: a wallet shows its user
317
+ // "already spent" and the operator has nothing at all to compare it to.
318
+ //
319
+ // The PATHNAME only, never the query string: a note URL carries the k1,
320
+ // and a k1 is the money. And never the informational GET, which a
321
+ // single restore walks twenty-odd unknown indexes of by design - that
322
+ // is not news, and burying the rare refusals under it defeats the point.
323
+ const fail = (reason, status = 200) => {
324
+ if (requestUrl.pathname !== '/w') {
325
+ log(`refused ${req.method ?? 'GET'} ${requestUrl.pathname}: ${reason}`);
326
+ }
327
+ send({ status: 'ERROR', reason }, status);
328
+ };
316
329
  const knownUser = (user) => user === config.username || user === '_';
317
330
  // ---- the mint's face ----
318
331
  if (requestUrl.pathname === '/' && (req.method === 'GET' || req.method === 'HEAD')) {
@@ -676,6 +689,16 @@ export const createMoneyer = async (config, deps = {}) => {
676
689
  return fail('Unknown note.');
677
690
  if (note.state === 'burned')
678
691
  return fail('Note already spent.');
692
+ // A note reserved by an in-flight melt is not withdrawable, and must
693
+ // not be advertised as though it were. LUD-25 makes this GET the way
694
+ // anyone checks what a note is worth, so answering "live, worth all
695
+ // of it" about a note halfway out of the door is the exact lie a
696
+ // sell-during-melt needs: the seller starts a melt, shows the buyer a
697
+ // healthy GET, takes payment out of band, and the melt settles. Same
698
+ // reason the mutating callback already gives, and the same one dni's
699
+ // lnurl-mint gives here.
700
+ if (note.state === 'pending')
701
+ return fail('pending');
679
702
  // maxWithdrawable states the note's value, as the reference does.
680
703
  // minWithdrawable is that value floored to a whole sat: most Lightning
681
704
  // wallets can only invoice whole sats, and a note of 94.9 sat that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgesworn/moneyer",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "An LNURLcash (LUD-25) mint - strikes Lightning bearer notes. Independent implementation, cln/lnd funding sources, SQLite, zero HTTP framework.",
5
5
  "author": "TheCryptoDonkey",
6
6
  "license": "MIT",