@forgesworn/moneyer 0.1.1 → 0.2.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 +186 -0
- package/README.md +460 -11
- package/THREAT-MODEL.md +15 -6
- package/dist/admin.d.ts +12 -0
- package/dist/admin.js +410 -0
- package/dist/announce.d.ts +12 -0
- package/dist/announce.js +82 -0
- package/dist/backends/cln.js +30 -8
- package/dist/backends/fake-bolt11.d.ts +1 -1
- package/dist/backends/fake-bolt11.js +3 -1
- package/dist/backends/fake.d.ts +7 -1
- package/dist/backends/fake.js +27 -11
- package/dist/backends/lnd.js +30 -6
- package/dist/backends/types.d.ts +3 -0
- package/dist/cli.js +25 -3
- package/dist/config.d.ts +31 -0
- package/dist/config.js +196 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +42 -2
- package/dist/melt.d.ts +1 -0
- package/dist/melt.js +2 -1
- package/dist/names.d.ts +39 -0
- package/dist/names.js +172 -0
- package/dist/privacy.d.ts +2 -0
- package/dist/privacy.js +14 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +573 -43
- package/dist/signing.d.ts +2 -0
- package/dist/signing.js +34 -0
- package/dist/stats.d.ts +31 -0
- package/dist/stats.js +115 -0
- package/dist/store.d.ts +105 -3
- package/dist/store.js +355 -19
- package/dist/version.d.ts +1 -0
- package/dist/version.js +17 -0
- package/dist/zap.d.ts +52 -0
- package/dist/zap.js +294 -0
- package/llms.txt +25 -8
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,191 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-08-22
|
|
4
|
+
|
|
5
|
+
While LUD-25 is a draft, a `0.x` minor bump may be breaking; this one is
|
|
6
|
+
additive on the wire apart from the node-capacity rename below.
|
|
7
|
+
|
|
8
|
+
- The test suite has a 30 second timeout rather than vitest's 5 second
|
|
9
|
+
default. Nearly every test here starts an HTTP server, opens a database
|
|
10
|
+
and does real curve and KDF work; 5 seconds is a unit test's budget, and
|
|
11
|
+
on a loaded machine the slower cases crossed it while passing perfectly
|
|
12
|
+
well. A gate that fails at random teaches people to bypass it.
|
|
13
|
+
- **`moneyer --dev` is a mint a wallet can actually use.** The fake
|
|
14
|
+
funding source settled nothing on its own: settlement was reachable only
|
|
15
|
+
through an in-process handle a test holds, so a wallet pointed at a
|
|
16
|
+
standalone `MONEYER_BACKEND=fake` mint asked for an invoice, nothing ever
|
|
17
|
+
paid it, no note was ever minted, and every flow needing a note - split,
|
|
18
|
+
merge, melt, send - dead-ended behind an empty wallet while the mint
|
|
19
|
+
looked healthy throughout. `--dev` now treats every invoice it issues as
|
|
20
|
+
paid the moment it is issued, so minting, splitting and melting all work
|
|
21
|
+
locally against a wallet. It can only be set on the fake backend, which
|
|
22
|
+
moves no money, and the startup banner says plainly that the notes are
|
|
23
|
+
worth nothing. `MONEYER_FAKE_AUTOSETTLE=true` sets it explicitly for a
|
|
24
|
+
compose file that cannot pass a flag.
|
|
25
|
+
- The mint can announce itself. With `MONEYER_ANNOUNCE=true` and the Nostr
|
|
26
|
+
identity zap-to-note already uses, it publishes its own discovery
|
|
27
|
+
document hourly as a parameterised replaceable event (kind 30078, `d`
|
|
28
|
+
tag `lnurlcash-mint`), alongside the liabilities snapshot. Until now a
|
|
29
|
+
wallet could only learn of a mint by being told its address; this is the
|
|
30
|
+
half that has to exist before finding one means anything. The content is
|
|
31
|
+
the document exactly as the discovery endpoint serves it, so there is one
|
|
32
|
+
description of a mint and not two, plus a `sig` over the canonicalised
|
|
33
|
+
body made with the note signing key, so a holder can check the
|
|
34
|
+
announcement against the same key their notes verify against.
|
|
35
|
+
`verifyAnnouncement` is exported for that. Off unless the operator turns
|
|
36
|
+
it on: a mint that does not want to be listed says nothing. No new event
|
|
37
|
+
kind, and no recommendations or reviews; both are protocol decisions that
|
|
38
|
+
belong with the NIPs.
|
|
39
|
+
- A note melts to an invoice that states no amount. Such an invoice used
|
|
40
|
+
to be refused outright, which is a papercut for anyone paying one: for a
|
|
41
|
+
bearer note the amount was never in question, since the note's value is
|
|
42
|
+
the amount. The mint sends the whole-sat floor of the note's value, the
|
|
43
|
+
same figure it already accepts from a wallet that fills the amount in
|
|
44
|
+
itself, and keeps the sub-sat remainder as the same dust. A note worth
|
|
45
|
+
less than a single satoshi has nothing left once the floor applies and
|
|
46
|
+
is refused with `insufficient value`; it can still be melted by a wallet
|
|
47
|
+
that invoices its exact value. Everything else about the melt path is
|
|
48
|
+
untouched, including the pending and restore discipline.
|
|
49
|
+
- A wallet can name the note it is buying. It chooses the note's spend
|
|
50
|
+
secret itself, keeps it, and sends `h`, the sha256 of that secret, on
|
|
51
|
+
the pay callback; the mint credits the note at `h` when the invoice
|
|
52
|
+
settles. The invoice's payment preimage then buys nothing and is only a
|
|
53
|
+
payment proof, which is what it always should have been: a preimage is
|
|
54
|
+
known to the funding source, to every node that forwarded the payment,
|
|
55
|
+
and to anyone who polls LUD-21 `verify` with the payment hash written
|
|
56
|
+
inside the invoice. Naming the note leaves the buyer as the only party
|
|
57
|
+
who ever held the secret, and replaces "claim and rotate faster than
|
|
58
|
+
anybody else" with nothing to race for. A malformed `h` is refused
|
|
59
|
+
before an invoice is issued, so a wallet never pays for a quote the
|
|
60
|
+
mint would reject; an `h` that already names a note or an invoice is
|
|
61
|
+
refused with `Invalid or already spent k1.`, the same oracle-free
|
|
62
|
+
sentence a colliding output gets on the withdraw callback. The reply
|
|
63
|
+
carries `mintToHash: true` when the binding was made, and the payRequest
|
|
64
|
+
and the discovery document advertise `mintToHash: true` so a wallet
|
|
65
|
+
knows before it asks. Claiming needs no `verify` poll: the wallet asks
|
|
66
|
+
`/w?k1=<its own secret>` directly. Entirely optional and additive: a
|
|
67
|
+
wallet that sends no `h` gets exactly today's behaviour, and no existing
|
|
68
|
+
wallet or service breaks. The draft itself does need one additive
|
|
69
|
+
amendment, because it says a `SERVICE` MUST accept `k1=P` and a bound
|
|
70
|
+
mint deliberately does not; that is proposed separately. Wallets are urged to persist the secret before
|
|
71
|
+
requesting the invoice, which is the one thing that could make this
|
|
72
|
+
worse than what it replaces.
|
|
73
|
+
- A live note's informational GET now carries `payLink`, pointing at this
|
|
74
|
+
mint's payRequest, which is the counterpart of the `withdrawLink` a
|
|
75
|
+
payRequest already advertises. It is the route home for a holder who has
|
|
76
|
+
nothing but a note: from it a wallet reaches the discovery document, and
|
|
77
|
+
so the mint's terms and its retired signing keys. Without it a wallet
|
|
78
|
+
that only ever received notes cannot tell an announced key rotation from
|
|
79
|
+
a substituted key. Optional and additive; unknown and spent notes still
|
|
80
|
+
say nothing about the mint beyond the refusal.
|
|
81
|
+
- Both database connections now wait up to five seconds for a lock instead
|
|
82
|
+
of giving up at once. `node:sqlite` opens with no busy timeout, so a
|
|
83
|
+
second writer met `database is locked` the moment the first one held it.
|
|
84
|
+
The operator CLI is that second writer while the mint is running, and the
|
|
85
|
+
times you reach for `moneyer admin reconcile` are the busy ones. Nothing
|
|
86
|
+
was ever at risk of corruption; the command simply failed when it was
|
|
87
|
+
least convenient.
|
|
88
|
+
- The discovery endpoint carries the human layer: `name`, `description`,
|
|
89
|
+
`contact` (`nostr` as an npub, `email`, `url`), `tosUrl`, `motd`,
|
|
90
|
+
`fees`, `version` and `previousPubkeys`. Every one is optional and an
|
|
91
|
+
unset one is absent rather than empty. New environment variables:
|
|
92
|
+
`MONEYER_NAME`, `MONEYER_CONTACT_NOSTR`, `MONEYER_CONTACT_EMAIL`,
|
|
93
|
+
`MONEYER_CONTACT_URL`, `MONEYER_TOS_URL`, `MONEYER_MOTD`.
|
|
94
|
+
- **Wire-name change**: node capacity now goes out as `nodeCapacity`, the
|
|
95
|
+
name the reference mint, the conformance mock and `lnurlcash-kit` all
|
|
96
|
+
use. `nodeCapacityMsat`, which was moneyer's alone, is emitted alongside
|
|
97
|
+
it for this one release and then removed. Both are milli-satoshis.
|
|
98
|
+
- The mint's own site and the fallback landing page show the message of
|
|
99
|
+
the day, the operator's contact and the terms link, and read capacity
|
|
100
|
+
under either name.
|
|
101
|
+
- `GET /stats` states what the mint owes and what its funding node holds:
|
|
102
|
+
outstanding liabilities and note count, melts in flight and the age of
|
|
103
|
+
the oldest, the node's local balance, and the coverage ratio between
|
|
104
|
+
the two to four decimal places. Cached 30 seconds, never per-note,
|
|
105
|
+
public by design. `MONEYER_STATS=false` switches it off and
|
|
106
|
+
`MONEYER_STATS_RATIO_ONLY=true` publishes the ratio alone. Both the
|
|
107
|
+
mint's site and the fallback landing page carry a coverage row.
|
|
108
|
+
- `MONEYER_STATS_PUBLISH=true` publishes an hourly signed snapshot of
|
|
109
|
+
those figures to Nostr (kind 30078, `d` tag `lnurlcash-liabilities`),
|
|
110
|
+
signed with the note signing key so anyone can check the history
|
|
111
|
+
against the mint's advertised pubkey. `scripts/verify-stats.mjs` does
|
|
112
|
+
exactly that, and `verifyStatsSnapshot` is exported for wallets.
|
|
113
|
+
- The lnd and cln backends report the node's local channel balance;
|
|
114
|
+
the fake one reports a configurable balance, one bitcoin by default.
|
|
115
|
+
- **Self-service lightning addresses.** With `MONEYER_NAME_PRICE_MSAT`
|
|
116
|
+
set, anyone with an npub can claim `name@<host>` by posting to
|
|
117
|
+
`/names` with a NIP-98 Authorization and a note of this mint. The key
|
|
118
|
+
that signs the request owns the name; no other identity is accepted.
|
|
119
|
+
The note is burned and the name recorded in one transaction, so
|
|
120
|
+
nothing is ever paid for a name somebody else got first. Unset means
|
|
121
|
+
registration is closed; `0` means free, three names per pubkey.
|
|
122
|
+
Registered and operator-configured names live in one table and are
|
|
123
|
+
served by one lookup, and `GET /.well-known/nostr.json?name=` resolves
|
|
124
|
+
them over NIP-05 as well, one name at a time. Discovery advertises
|
|
125
|
+
`namePriceMsat` while registration is open.
|
|
126
|
+
- The kind 2525 rumor for a zap-funded note carries the zap request in a
|
|
127
|
+
`description` tag - the same content the kind 9735 receipt carries - so
|
|
128
|
+
a wallet can show who zapped and what they wrote without fetching the
|
|
129
|
+
receipt. Readers take tags by name, so an older one does not notice.
|
|
130
|
+
- `MONEYER_ZAP_NAMES` is now optional: a mint that opens registration can
|
|
131
|
+
start with no names of its own. `MONEYER_NOSTR_KEY` and
|
|
132
|
+
`MONEYER_NOSTR_RELAYS` still go together.
|
|
133
|
+
- `moneyer admin names` gains `add <name> <npub>` and `rm <name>`, and
|
|
134
|
+
`list` shows configured names the running mint has not loaded yet.
|
|
135
|
+
- **A retried rotate, split or merge is answered instead of refused.**
|
|
136
|
+
These are GETs, and transports retry GETs on their own: the retry
|
|
137
|
+
arrives byte-identical after the inputs are burned, and the old answer
|
|
138
|
+
- `Invalid or already spent k1.` - told the wallet to drop the only
|
|
139
|
+
copy of a secret the mint really had minted a note against. The mint
|
|
140
|
+
now records which request minted which outputs, and replays the same
|
|
141
|
+
reply: same signatures, nothing burned, nothing minted, no balance
|
|
142
|
+
moved. A request counts as the same request when it names the same
|
|
143
|
+
input notes and asks for the same `h`, `h2` and `amount`; input order
|
|
144
|
+
does not matter. Anything else naming a burned note is still refused
|
|
145
|
+
with the same reason string, so no oracle appears, and the melt path is
|
|
146
|
+
untouched. moneyer's own behaviour: the draft says nothing about
|
|
147
|
+
retries yet.
|
|
148
|
+
- `moneyer admin <command>`: the operator surface. `status`, `notes`,
|
|
149
|
+
`note`, `melts`, `reconcile`, `sweep`, `snapshot`, `names list`,
|
|
150
|
+
`keys rotate` and `verify-note`, all reading the same `MONEYER_*`
|
|
151
|
+
environment the mint does, and all opening the database read-only
|
|
152
|
+
unless the command mutates. `snapshot` uses `VACUUM INTO`, so it needs
|
|
153
|
+
no sqlite3 binary and is safe against a live mint; `keys rotate`
|
|
154
|
+
generates a key and prints the two environment lines without touching
|
|
155
|
+
anything.
|
|
156
|
+
- `MONEYER_METRICS=true` serves `GET /metrics` in the OpenMetrics text
|
|
157
|
+
format. Off by default and never authenticated by the app: restrict the
|
|
158
|
+
path at the reverse proxy. The README's Operating section names the
|
|
159
|
+
three alerts worth having.
|
|
160
|
+
- A "What the mint knows" section in the README, on the mint's own site
|
|
161
|
+
and on the fallback landing page, in the same words: what a LUD-25 mint
|
|
162
|
+
can see, what it cannot, why the wallet-side mitigations are weak, and
|
|
163
|
+
why the design was chosen anyway. Blindness is the one ecash property
|
|
164
|
+
this cannot offer, and a reader should not have to infer that.
|
|
165
|
+
- `MONEYER_PREVIOUS_SIGNING_PUBKEYS` lists the compressed pubkeys this
|
|
166
|
+
mint signed notes under before, published as `previousPubkeys` on
|
|
167
|
+
discovery. Rotating the signing key no longer invalidates every
|
|
168
|
+
outstanding note's signature or trips a pinned wallet's mismatch
|
|
169
|
+
check. Pubkeys only, validated as points on the curve at startup: the
|
|
170
|
+
old private keys are not needed to verify and should not stay on the
|
|
171
|
+
server. The runbook is in the README.
|
|
172
|
+
|
|
173
|
+
## [0.1.2] - 2026-08-21
|
|
174
|
+
|
|
175
|
+
- `MONEYER_ROUND_FEE_TO_SAT` ceilings the mint fee to a whole sat, as
|
|
176
|
+
dni's lnurl-mint does on purpose so the mint is "never short a sat".
|
|
177
|
+
LUD-25 says nothing about rounding and `lnurlcash-kit`'s `mintFeeBand`
|
|
178
|
+
accepts both readings, so this is a posture choice rather than a
|
|
179
|
+
compliance one. **Off by default**: turning it on raises what the mint
|
|
180
|
+
withholds, which is not a change to make on a redeploy.
|
|
181
|
+
- The mint path called `applyMintFee` directly rather than the fee helper,
|
|
182
|
+
so it would have ignored that setting entirely - advertising one fee and
|
|
183
|
+
withholding another. Every fee site now goes through one
|
|
184
|
+
`netAfterMintFee`.
|
|
185
|
+
- `roundFeeToSat` is optional on `MoneyerConfig`: that type is public API,
|
|
186
|
+
and an embedder constructing a config should not have to name a field it
|
|
187
|
+
does not care about.
|
|
188
|
+
|
|
3
189
|
## [0.1.1] - 2026-08-21
|
|
4
190
|
|
|
5
191
|
- `withdrawLink` is now the plain URL (`https://host/w`), the form
|