@forgesworn/moneyer 0.17.0 → 0.18.0
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 +28 -0
- package/README.md +11 -7
- package/THREAT-MODEL.md +3 -3
- package/dist/admin.js +4 -2
- package/dist/backends/lnd.js +8 -1
- package/dist/melt.d.ts +9 -1
- package/dist/melt.js +15 -12
- package/dist/server.d.ts +5 -1
- package/dist/server.js +67 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 - 2026-10-07
|
|
4
|
+
|
|
5
|
+
- **A melt that cannot leave is answered with its reason.** The callback
|
|
6
|
+
used to reply `OK` the moment the note was reserved, so a melt with no
|
|
7
|
+
route looked like success until the wallet checked again. It now waits up
|
|
8
|
+
to two seconds for the payment: if the funding source confirms nothing
|
|
9
|
+
went out, the note is restored first and the answer is `ERROR` with the
|
|
10
|
+
reason (for lnd, "Could not find a route to pay this invoice."). A
|
|
11
|
+
payment still in flight, or one that settles, is `OK` as before. The
|
|
12
|
+
hodl-invoice rule stands: a failure report is acted on only once the
|
|
13
|
+
funding source confirms it. The invoice itself is still refused if tried
|
|
14
|
+
again, as it was after a background restore.
|
|
15
|
+
|
|
16
|
+
## 0.17.1 - 2026-10-06
|
|
17
|
+
|
|
18
|
+
- **The expiry sweep no longer deletes paid mint invoices.** A mint invoice
|
|
19
|
+
was recorded as paid only when a wallet came back to `/verify` or to
|
|
20
|
+
claim its note, and the sweep deleted every unrecorded one an hour past
|
|
21
|
+
its expiry. A payer whose wallet never came back lost the payment: the
|
|
22
|
+
funding source kept the money and the note's id went with the row. The
|
|
23
|
+
sweep, and `moneyer admin sweep`, now ask the funding source first. Paid,
|
|
24
|
+
the invoice is settled and its note minted; unpaid, the row goes; no
|
|
25
|
+
answer, the row stays.
|
|
26
|
+
- **lnd: a refused invoice lookup is no longer read as unpaid.** Only a 404
|
|
27
|
+
(an invoice lnd does not hold) now means unpaid; any other non-2xx throws.
|
|
28
|
+
A transient lnd error on `/verify` now returns an error instead of "not
|
|
29
|
+
paid yet".
|
|
30
|
+
|
|
3
31
|
## 0.17.0 - 2026-09-26
|
|
4
32
|
|
|
5
33
|
LUD-25's unified taproot model (lnurl/luds 6e865b1). Every note is now a
|
package/README.md
CHANGED
|
@@ -37,10 +37,13 @@ lesson, that behaviour is kept deliberately and tested.
|
|
|
37
37
|
from the start.
|
|
38
38
|
- Signs every note it mints with its own mint key (secp256k1, the standard
|
|
39
39
|
`Lightning Signed Message` construction) for LUD-25 offline verification.
|
|
40
|
-
- The melt discipline:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
- The melt discipline: reserve the note, then give the payment up to two
|
|
41
|
+
seconds. A payment the funding source confirms never left (no route, say)
|
|
42
|
+
is answered `ERROR` with its reason and the note is outstanding again;
|
|
43
|
+
anything else is answered `OK` and finishes in the background. Burn only
|
|
44
|
+
on confirmed payment, restore only on confirmed non-payment, and park
|
|
45
|
+
everything else as pending for reconciliation - which also runs at
|
|
46
|
+
startup, so a crash mid-melt never guesses.
|
|
44
47
|
|
|
45
48
|
## Run
|
|
46
49
|
|
|
@@ -703,7 +706,7 @@ path should not silently create an empty database to answer from.
|
|
|
703
706
|
| `note <id\|k1>` | one note; 64 hex that names no note is hashed and looked up as the secret |
|
|
704
707
|
| `melts [--pending]` | list melts |
|
|
705
708
|
| `reconcile` | one pending-melt reconcile pass, printing what changed |
|
|
706
|
-
| `sweep` | delete mint invoices
|
|
709
|
+
| `sweep` | delete expired mint invoices the funding source confirms unpaid, and mint the notes of any it finds paid |
|
|
707
710
|
| `snapshot <path>` | a consistent copy of the database, taken live, refusing to overwrite |
|
|
708
711
|
| `names list\|add <name> <npub>\|rm <name>` | the lightning addresses this mint pays out as notes |
|
|
709
712
|
| `keys rotate` | generate a signing key and print the two environment lines; writes nothing |
|
|
@@ -735,8 +738,9 @@ is not.
|
|
|
735
738
|
Whether that is a channel imbalance or something worse, it is the one
|
|
736
739
|
number a custodial mint must never be relaxed about.
|
|
737
740
|
- **unsettled invoices growing.** Invoices are issued and never paid all
|
|
738
|
-
day, and the sweep clears the expired ones
|
|
739
|
-
a sweep means invoices are being
|
|
741
|
+
day, and the sweep clears the expired ones the funding source confirms
|
|
742
|
+
unpaid. A count that climbs through a sweep means invoices are being
|
|
743
|
+
issued that nobody can pay, or that the funding source is not answering.
|
|
740
744
|
|
|
741
745
|
## Testing
|
|
742
746
|
|
package/THREAT-MODEL.md
CHANGED
|
@@ -147,9 +147,9 @@ is what notecase and lnurl-wallet do.
|
|
|
147
147
|
- No rate limiting is built in; put it at the proxy. This matters most for
|
|
148
148
|
`/p/cb`: each unauthenticated call creates a real invoice at the funding
|
|
149
149
|
source. moneyer sweeps its own unsettled invoices once their bolt11
|
|
150
|
-
expiry has passed
|
|
151
|
-
|
|
152
|
-
what keeps the RPC pile-up and the node database from growing at all.
|
|
150
|
+
expiry has passed and the funding source confirms them unpaid, but the
|
|
151
|
+
node's side of that growth is the operator's to bound (cln's autoclean,
|
|
152
|
+
or an equivalent cron) - and a proxy limit is what keeps the RPC pile-up and the node database from growing at all.
|
|
153
153
|
- A fee-free configuration pays the melt routing-fee floor (0.5% of the
|
|
154
154
|
amount or 5000 msat) out of its own channel balance, and mint-and-melt
|
|
155
155
|
cycling costs a griefer nothing. Set `MONEYER_BASE_FEE_MSAT` to cover the
|
package/dist/admin.js
CHANGED
|
@@ -18,7 +18,7 @@ const COMMANDS = [
|
|
|
18
18
|
['note <id|k1>', 'one note, by id or by the secret itself'],
|
|
19
19
|
['melts [--pending]', 'list melts, newest first'],
|
|
20
20
|
['reconcile', 'resolve melts left in flight, and say what changed'],
|
|
21
|
-
['sweep', 'delete mint invoices
|
|
21
|
+
['sweep', 'delete expired mint invoices the funding source confirms unpaid'],
|
|
22
22
|
['snapshot <path>', 'a consistent copy of the database, taken live'],
|
|
23
23
|
['names list|add <name> <npub>|rm <name>', 'the lightning addresses this mint pays out as notes'],
|
|
24
24
|
['keys rotate', 'generate a signing key and print the two env lines'],
|
|
@@ -271,8 +271,10 @@ export const runAdmin = async (argv, deps = {}) => {
|
|
|
271
271
|
return 0;
|
|
272
272
|
}
|
|
273
273
|
case 'sweep': {
|
|
274
|
-
const swept = sweepExpiredMintInvoices(store, now());
|
|
274
|
+
const { swept, settled } = await sweepExpiredMintInvoices(store, fundingSource(), now());
|
|
275
275
|
out(`swept ${swept} expired mint invoice${swept === 1 ? '' : 's'}`);
|
|
276
|
+
if (settled > 0)
|
|
277
|
+
out(`found ${settled} paid after all - note${settled === 1 ? '' : 's'} minted`);
|
|
276
278
|
return 0;
|
|
277
279
|
}
|
|
278
280
|
case 'snapshot': {
|
package/dist/backends/lnd.js
CHANGED
|
@@ -158,9 +158,16 @@ export const createLndBackend = (config) => {
|
|
|
158
158
|
throw new Error('lnd did not report a payment status.');
|
|
159
159
|
return result;
|
|
160
160
|
},
|
|
161
|
+
// 404 is lnd's answer for an invoice it does not hold, which can never
|
|
162
|
+
// settle. Any other refusal is no answer at all, and reading it as
|
|
163
|
+
// "unpaid" would let the expiry sweep delete a paid invoice's row.
|
|
161
164
|
async isInvoiceSettled(paymentHashHex) {
|
|
162
165
|
const res = await json(`/v1/invoice/${paymentHashHex}`);
|
|
163
|
-
|
|
166
|
+
if (res.status === 404)
|
|
167
|
+
return false;
|
|
168
|
+
if (!res.ok)
|
|
169
|
+
throw new Error(`lnd invoice lookup failed: HTTP ${res.status}`);
|
|
170
|
+
return Boolean(res.json?.settled);
|
|
164
171
|
},
|
|
165
172
|
async invoicePreimage(paymentHashHex) {
|
|
166
173
|
const res = await json(`/v1/invoice/${paymentHashHex}`);
|
package/dist/melt.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export type MeltJob = {
|
|
|
7
7
|
amountMsat: number;
|
|
8
8
|
payAmountMsat?: number;
|
|
9
9
|
};
|
|
10
|
+
export type MeltOutcome = {
|
|
11
|
+
kind: 'paid';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'restored';
|
|
14
|
+
reason: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'unresolved';
|
|
17
|
+
};
|
|
10
18
|
export type MeltDeps = {
|
|
11
19
|
store: NoteStore;
|
|
12
20
|
backend: LightningBackend;
|
|
@@ -14,5 +22,5 @@ export type MeltDeps = {
|
|
|
14
22
|
confirmDelaysMs?: number[];
|
|
15
23
|
log?: (message: string) => void;
|
|
16
24
|
};
|
|
17
|
-
export declare const runMelt: (job: MeltJob, deps: MeltDeps) => Promise<
|
|
25
|
+
export declare const runMelt: (job: MeltJob, deps: MeltDeps) => Promise<MeltOutcome>;
|
|
18
26
|
export declare const reconcilePendingMelts: (store: NoteStore, backend: LightningBackend, inFlight: ReadonlySet<string>, log?: (message: string) => void) => Promise<void>;
|
package/dist/melt.js
CHANGED
|
@@ -2,7 +2,7 @@ import { verifyPreimage } from 'farrier-kit/preimage';
|
|
|
2
2
|
import { PaymentAlreadyKnownError, PaymentFailedError, PaymentPendingError } from "./backends/types.js";
|
|
3
3
|
const DEFAULT_CONFIRM_DELAYS_MS = [0, 2_000, 4_000, 9_000, 16_000];
|
|
4
4
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
5
|
-
const confirmThenSettle = async (job, deps) => {
|
|
5
|
+
const confirmThenSettle = async (job, deps, reason) => {
|
|
6
6
|
const log = deps.log ?? (() => { });
|
|
7
7
|
for (const delay of deps.confirmDelaysMs ?? DEFAULT_CONFIRM_DELAYS_MS) {
|
|
8
8
|
if (delay > 0)
|
|
@@ -11,12 +11,11 @@ const confirmThenSettle = async (job, deps) => {
|
|
|
11
11
|
const complete = await deps.backend.isPaymentComplete(job.paymentHash);
|
|
12
12
|
if (complete) {
|
|
13
13
|
deps.store.finalizeMelt(job.paymentHash);
|
|
14
|
+
return { kind: 'paid' };
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
return;
|
|
16
|
+
log(`melt ${job.noteId}: confirmed not paid - restoring`);
|
|
17
|
+
deps.store.restoreMelt(job.paymentHash);
|
|
18
|
+
return { kind: 'restored', reason };
|
|
20
19
|
}
|
|
21
20
|
catch (err) {
|
|
22
21
|
if (!(err instanceof PaymentPendingError)) {
|
|
@@ -25,6 +24,7 @@ const confirmThenSettle = async (job, deps) => {
|
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
log(`melt ${job.noteId}: payment status unconfirmable - note left pending for reconciliation`);
|
|
27
|
+
return { kind: 'unresolved' };
|
|
28
28
|
};
|
|
29
29
|
export const runMelt = async (job, deps) => {
|
|
30
30
|
const log = deps.log ?? (() => { });
|
|
@@ -47,23 +47,26 @@ export const runMelt = async (job, deps) => {
|
|
|
47
47
|
// lands between that check and the send.
|
|
48
48
|
log(`melt ${job.noteId}: the funding source already knew this hash - restored (shared-node replay?)`);
|
|
49
49
|
deps.store.restoreMelt(job.paymentHash);
|
|
50
|
-
return;
|
|
50
|
+
return { kind: 'restored', reason: 'Invoice already used by an earlier melt - use a fresh one.' };
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
// A clean failure's message is the backend's reason, written for a
|
|
53
|
+
// wallet ("Could not find a route..."); an ambiguous one is transport
|
|
54
|
+
// detail, so a wallet gets a plain sentence instead.
|
|
55
|
+
const clean = err instanceof PaymentFailedError;
|
|
56
|
+
if (!clean) {
|
|
53
57
|
log(`melt ${job.noteId}: payment attempt failed ambiguously (${err.message})`);
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
return;
|
|
59
|
+
return confirmThenSettle(job, deps, clean ? err.message : 'Payment failed.');
|
|
57
60
|
}
|
|
58
61
|
if (outcome.preimageHex !== null && !verifyPreimage(outcome.preimageHex, job.paymentHash)) {
|
|
59
62
|
// The backend claims success with a preimage that does not settle this
|
|
60
63
|
// invoice. That is a statement about the evidence, not the money - fall
|
|
61
64
|
// back to the tracker rather than trusting either way.
|
|
62
65
|
log(`melt ${job.noteId}: backend preimage does not settle this invoice - reconfirming`);
|
|
63
|
-
|
|
64
|
-
return;
|
|
66
|
+
return confirmThenSettle(job, deps, 'Payment failed.');
|
|
65
67
|
}
|
|
66
68
|
deps.store.finalizeMelt(job.paymentHash);
|
|
69
|
+
return { kind: 'paid' };
|
|
67
70
|
};
|
|
68
71
|
// Resolves melts an earlier process left pending - a crash mid-melt, or an
|
|
69
72
|
// outcome that could not be confirmed at the time. Melts whose attempt is
|
package/dist/server.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type MoneyerDeps = {
|
|
|
25
25
|
store?: NoteStore;
|
|
26
26
|
log?: (message: string) => void;
|
|
27
27
|
confirmDelaysMs?: number[];
|
|
28
|
+
meltAnswerWindowMs?: number;
|
|
28
29
|
webAssets?: WebAssets | null;
|
|
29
30
|
nostr?: NostrTransport;
|
|
30
31
|
zapPollMs?: number;
|
|
@@ -33,4 +34,7 @@ export type MoneyerDeps = {
|
|
|
33
34
|
};
|
|
34
35
|
export { describeFee } from './fee-words.ts';
|
|
35
36
|
export declare const createMoneyer: (config: MoneyerConfig, deps?: MoneyerDeps) => Promise<Moneyer>;
|
|
36
|
-
export declare const sweepExpiredMintInvoices: (store: NoteStore, nowMs?: number) =>
|
|
37
|
+
export declare const sweepExpiredMintInvoices: (store: NoteStore, backend: Pick<LightningBackend, "isInvoiceSettled">, nowMs?: number) => Promise<{
|
|
38
|
+
swept: number;
|
|
39
|
+
settled: number;
|
|
40
|
+
}>;
|
package/dist/server.js
CHANGED
|
@@ -45,6 +45,9 @@ const renamedRef = (q, name, legacy) => {
|
|
|
45
45
|
// more than a name and a note, and an unbounded read on a public endpoint
|
|
46
46
|
// is a way to run a mint out of memory.
|
|
47
47
|
const MAX_BODY_BYTES = 8 * 1024;
|
|
48
|
+
// How long a melt callback waits for the payment's outcome before answering
|
|
49
|
+
// OK. Long enough for a no-route failure, short against a wallet's timeout.
|
|
50
|
+
const MELT_ANSWER_WINDOW_MS = 2_000;
|
|
48
51
|
const readBody = async (req, limit = MAX_BODY_BYTES) => {
|
|
49
52
|
const chunks = [];
|
|
50
53
|
let size = 0;
|
|
@@ -1161,17 +1164,34 @@ export const createMoneyer = async (config, deps = {}) => {
|
|
|
1161
1164
|
return fail('Invoice already used by an earlier melt - use a fresh one.');
|
|
1162
1165
|
}
|
|
1163
1166
|
inFlight.add(paymentHash);
|
|
1164
|
-
|
|
1167
|
+
const melt = runMelt({
|
|
1165
1168
|
paymentHash,
|
|
1166
1169
|
noteId: inputIds[0],
|
|
1167
1170
|
pr: pr.trim(),
|
|
1168
1171
|
amountMsat: totalMsat,
|
|
1169
1172
|
...(payAmountMsat !== null ? { payAmountMsat } : {})
|
|
1170
1173
|
}, { store, backend, feeLimitMsat: meltFeeLimitMsat, log, ...(deps.confirmDelaysMs ? { confirmDelaysMs: deps.confirmDelaysMs } : {}) })
|
|
1171
|
-
.catch(err =>
|
|
1174
|
+
.catch((err) => {
|
|
1175
|
+
log(`melt ${inputIds[0]}: ${err.message}`);
|
|
1176
|
+
return { kind: 'unresolved' };
|
|
1177
|
+
})
|
|
1172
1178
|
.finally(() => inFlight.delete(paymentHash));
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1179
|
+
// A payment that cannot leave usually says so within a second (lnd
|
|
1180
|
+
// answers no-route before sending any HTLC), so wait briefly: a
|
|
1181
|
+
// melt the funding source has confirmed never went out is answered
|
|
1182
|
+
// with its reason, and the note is already outstanding again. Any
|
|
1183
|
+
// other outcome, or none yet, is OK as LUD-03 has it: the melt is
|
|
1184
|
+
// requested and the note reserved, never that it settled.
|
|
1185
|
+
let windowTimer;
|
|
1186
|
+
const outcome = await Promise.race([
|
|
1187
|
+
melt,
|
|
1188
|
+
new Promise(resolve => {
|
|
1189
|
+
windowTimer = setTimeout(() => resolve(null), deps.meltAnswerWindowMs ?? MELT_ANSWER_WINDOW_MS);
|
|
1190
|
+
})
|
|
1191
|
+
]);
|
|
1192
|
+
clearTimeout(windowTimer);
|
|
1193
|
+
if (outcome?.kind === 'restored')
|
|
1194
|
+
return fail(outcome.reason);
|
|
1175
1195
|
return send({
|
|
1176
1196
|
status: 'OK',
|
|
1177
1197
|
...(config.verify ? { pr: pr.trim(), verify: `${origin}/verify/${paymentHash}` } : {})
|
|
@@ -1262,9 +1282,12 @@ export const createMoneyer = async (config, deps = {}) => {
|
|
|
1262
1282
|
// otherwise grow by one dead row per /p/cb call forever. The timer is
|
|
1263
1283
|
// unref'd so it never keeps the process alive.
|
|
1264
1284
|
const housekeeping = async () => {
|
|
1265
|
-
const swept = sweepExpiredMintInvoices(store
|
|
1285
|
+
const { swept: mintSwept, settled } = await sweepExpiredMintInvoices(store, backend);
|
|
1286
|
+
const swept = mintSwept + (zap?.sweep() ?? 0);
|
|
1266
1287
|
if (swept > 0)
|
|
1267
1288
|
log(`swept ${swept} expired mint invoice${swept === 1 ? '' : 's'}`);
|
|
1289
|
+
if (settled > 0)
|
|
1290
|
+
log(`found ${settled} expired mint invoice${settled === 1 ? '' : 's'} paid after all - note${settled === 1 ? '' : 's'} minted`);
|
|
1268
1291
|
await reconcilePendingMelts(store, backend, inFlight, log);
|
|
1269
1292
|
reconciledAt = Date.now();
|
|
1270
1293
|
};
|
|
@@ -1337,15 +1360,25 @@ export const createMoneyer = async (config, deps = {}) => {
|
|
|
1337
1360
|
}
|
|
1338
1361
|
};
|
|
1339
1362
|
};
|
|
1340
|
-
//
|
|
1341
|
-
//
|
|
1342
|
-
//
|
|
1343
|
-
//
|
|
1344
|
-
//
|
|
1345
|
-
//
|
|
1346
|
-
//
|
|
1347
|
-
//
|
|
1348
|
-
|
|
1363
|
+
// Clears mint invoices this mint has not seen paid once their bolt11 expiry
|
|
1364
|
+
// is comfortably past, returning how many it deleted and how many it found
|
|
1365
|
+
// paid after all.
|
|
1366
|
+
//
|
|
1367
|
+
// "Unsettled" here is only this mint's record. A payment is recorded when a
|
|
1368
|
+
// wallet comes back to /verify or to claim its note, so a payer whose wallet
|
|
1369
|
+
// never comes back - a closed tab, an invoice paid from another app - leaves
|
|
1370
|
+
// a row that reads unsettled while the funding source holds the money.
|
|
1371
|
+
// Deleting that row would strand the payment for good: the note's id lives
|
|
1372
|
+
// only here. So the funding source is asked first. Paid, the invoice is
|
|
1373
|
+
// settled and its note minted, exactly as a /verify would have. Unpaid, the
|
|
1374
|
+
// expiry is the proof it never can be, and the row goes. No answer, no
|
|
1375
|
+
// delete.
|
|
1376
|
+
//
|
|
1377
|
+
// The hour's margin keeps the delete unambiguously behind every
|
|
1378
|
+
// implementation's reading of that expiry, an undecodable invoice is kept,
|
|
1379
|
+
// and the delete is conditional on the row still being unsettled, so a
|
|
1380
|
+
// settle racing the sweep always wins.
|
|
1381
|
+
export const sweepExpiredMintInvoices = async (store, backend, nowMs = Date.now()) => {
|
|
1349
1382
|
const stale = [];
|
|
1350
1383
|
for (const invoice of store.unsettledMintInvoices()) {
|
|
1351
1384
|
const decoded = tryDecodeBolt11(invoice.pr);
|
|
@@ -1355,7 +1388,24 @@ export const sweepExpiredMintInvoices = (store, nowMs = Date.now()) => {
|
|
|
1355
1388
|
if (nowMs > expiresAtMs + 3_600_000)
|
|
1356
1389
|
stale.push(invoice.paymentHash);
|
|
1357
1390
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1391
|
+
let swept = 0;
|
|
1392
|
+
let settled = 0;
|
|
1393
|
+
for (const paymentHash of stale) {
|
|
1394
|
+
let paid;
|
|
1395
|
+
try {
|
|
1396
|
+
paid = await backend.isInvoiceSettled(paymentHash);
|
|
1397
|
+
}
|
|
1398
|
+
catch {
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
if (paid) {
|
|
1402
|
+
store.settleMintInvoice(paymentHash);
|
|
1403
|
+
settled++;
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
store.deleteUnsettledMintInvoice(paymentHash);
|
|
1407
|
+
swept++;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return { swept, settled };
|
|
1361
1411
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgesworn/moneyer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
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",
|