@forgesworn/moneyer 0.1.1 → 0.1.2
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 +16 -0
- package/README.md +13 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -1
- package/dist/server.js +13 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.2] - 2026-08-21
|
|
4
|
+
|
|
5
|
+
- `MONEYER_ROUND_FEE_TO_SAT` ceilings the mint fee to a whole sat, as
|
|
6
|
+
dni's lnurl-mint does on purpose so the mint is "never short a sat".
|
|
7
|
+
LUD-25 says nothing about rounding and `lnurlcash-kit`'s `mintFeeBand`
|
|
8
|
+
accepts both readings, so this is a posture choice rather than a
|
|
9
|
+
compliance one. **Off by default**: turning it on raises what the mint
|
|
10
|
+
withholds, which is not a change to make on a redeploy.
|
|
11
|
+
- The mint path called `applyMintFee` directly rather than the fee helper,
|
|
12
|
+
so it would have ignored that setting entirely - advertising one fee and
|
|
13
|
+
withholding another. Every fee site now goes through one
|
|
14
|
+
`netAfterMintFee`.
|
|
15
|
+
- `roundFeeToSat` is optional on `MoneyerConfig`: that type is public API,
|
|
16
|
+
and an embedder constructing a config should not have to name a field it
|
|
17
|
+
does not care about.
|
|
18
|
+
|
|
3
19
|
## [0.1.1] - 2026-08-21
|
|
4
20
|
|
|
5
21
|
- `withdrawLink` is now the plain URL (`https://host/w`), the form
|
package/README.md
CHANGED
|
@@ -97,6 +97,19 @@ import {createMoneyer, configFromEnv} from '@forgesworn/moneyer'
|
|
|
97
97
|
const mint = await createMoneyer(configFromEnv())
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
## The mint fee, and the rounding question
|
|
101
|
+
|
|
102
|
+
LUD-25 gives the fee as a flat `base_fee_msat` plus a ppm cut and says
|
|
103
|
+
nothing about rounding. dni's lnurl-mint, the reference, ceilings it to a
|
|
104
|
+
whole sat on purpose so the mint is never short a sat; moneyer withholds
|
|
105
|
+
the msat-exact amount. Neither is out of spec - lnurlcash-kit's
|
|
106
|
+
`mintFeeBand` treats anything between the two as the mint keeping its
|
|
107
|
+
word, and the conformance grader accepts both.
|
|
108
|
+
|
|
109
|
+
`MONEYER_ROUND_FEE_TO_SAT=true` switches this mint to the reference's
|
|
110
|
+
behaviour. Off by default: turning it on raises what the mint withholds,
|
|
111
|
+
and that is not a change to make behind an operator's back on a redeploy.
|
|
112
|
+
|
|
100
113
|
## Endpoints
|
|
101
114
|
|
|
102
115
|
| | |
|
package/dist/config.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type MoneyerConfig = {
|
|
|
20
20
|
maxSendableMsat: number;
|
|
21
21
|
minMintMsat: number;
|
|
22
22
|
mintFee: MintFee | null;
|
|
23
|
+
roundFeeToSat?: boolean;
|
|
23
24
|
signingKey?: string;
|
|
24
25
|
dbPath: string;
|
|
25
26
|
backend: BackendConfig;
|
|
@@ -40,5 +41,6 @@ export declare const DEFAULTS: {
|
|
|
40
41
|
readonly verify: true;
|
|
41
42
|
readonly maxK1s: 21;
|
|
42
43
|
readonly sunset: false;
|
|
44
|
+
readonly roundFeeToSat: false;
|
|
43
45
|
};
|
|
44
46
|
export declare const configFromEnv: (env?: NodeJS.ProcessEnv) => MoneyerConfig;
|
package/dist/config.js
CHANGED
|
@@ -9,7 +9,8 @@ export const DEFAULTS = {
|
|
|
9
9
|
dbPath: 'moneyer.sqlite',
|
|
10
10
|
verify: true,
|
|
11
11
|
maxK1s: 21,
|
|
12
|
-
sunset: false
|
|
12
|
+
sunset: false,
|
|
13
|
+
roundFeeToSat: false
|
|
13
14
|
};
|
|
14
15
|
const int = (value, fallback) => {
|
|
15
16
|
if (value === undefined || value === '')
|
|
@@ -89,6 +90,7 @@ export const configFromEnv = (env = process.env) => {
|
|
|
89
90
|
maxSendableMsat,
|
|
90
91
|
minMintMsat: int(env.MONEYER_MIN_MINT_MSAT, DEFAULTS.minMintMsat),
|
|
91
92
|
mintFee: baseFeeMsat === 0 && feePpm === 0 ? null : { baseFeeMsat, feePpm },
|
|
93
|
+
roundFeeToSat: flag(env.MONEYER_ROUND_FEE_TO_SAT, DEFAULTS.roundFeeToSat),
|
|
92
94
|
...(signingKey ? { signingKey: signingKey.toLowerCase() } : {}),
|
|
93
95
|
dbPath: env.MONEYER_DB ?? DEFAULTS.dbPath,
|
|
94
96
|
backend,
|
package/dist/server.js
CHANGED
|
@@ -40,7 +40,17 @@ export const createMoneyer = async (config, deps = {}) => {
|
|
|
40
40
|
// Melt payment hashes with a live attempt in this process. Reconcile
|
|
41
41
|
// skips these - see melt.ts.
|
|
42
42
|
const inFlight = new Set();
|
|
43
|
-
const mintFeeMsat = (grossMsat) =>
|
|
43
|
+
const mintFeeMsat = (grossMsat) => {
|
|
44
|
+
if (!config.mintFee)
|
|
45
|
+
return 0;
|
|
46
|
+
const exact = grossMsat - applyMintFee(grossMsat, config.mintFee);
|
|
47
|
+
// Both readings sit inside lnurlcash-kit's mintFeeBand, so a wallet
|
|
48
|
+
// is not misled either way - it is told the range up front.
|
|
49
|
+
return config.roundFeeToSat === true ? Math.ceil(exact / 1000) * 1000 : exact;
|
|
50
|
+
};
|
|
51
|
+
// Every quote and every credit goes through here, so the fee posture
|
|
52
|
+
// cannot drift between what is advertised and what is withheld.
|
|
53
|
+
const netAfterMintFee = (grossMsat) => Math.max(0, grossMsat - mintFeeMsat(grossMsat));
|
|
44
54
|
// What the mint advertises as its minimum must survive its own fee: a
|
|
45
55
|
// payRequest whose minSendable nets below the dust floor invites a
|
|
46
56
|
// payment it would then refuse.
|
|
@@ -164,7 +174,7 @@ export const createMoneyer = async (config, deps = {}) => {
|
|
|
164
174
|
callback: `${origin}/w`,
|
|
165
175
|
minWithdrawable: config.minMintMsat,
|
|
166
176
|
maxWithdrawable: config.mintFee
|
|
167
|
-
?
|
|
177
|
+
? netAfterMintFee(config.maxSendableMsat)
|
|
168
178
|
: config.maxSendableMsat,
|
|
169
179
|
defaultDescription: config.description,
|
|
170
180
|
payLink: `${origin}/.well-known/lnurlp/${lnurlwMatch[1]}`,
|
|
@@ -187,7 +197,7 @@ export const createMoneyer = async (config, deps = {}) => {
|
|
|
187
197
|
if (amount < effectiveMinSendableMsat || amount > config.maxSendableMsat) {
|
|
188
198
|
return fail('Amount out of range.');
|
|
189
199
|
}
|
|
190
|
-
const net =
|
|
200
|
+
const net = netAfterMintFee(amount);
|
|
191
201
|
if (net < config.minMintMsat)
|
|
192
202
|
return fail('Amount too small to mint a note.');
|
|
193
203
|
// The preimage is the future note's spend secret; its hash is the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgesworn/moneyer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test:watch": "vitest",
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
47
47
|
"dev": "node --experimental-strip-types src/cli.ts --dev",
|
|
48
|
-
"conform": "
|
|
48
|
+
"conform": "vitest run test/conformance.test.ts",
|
|
49
49
|
"check": "npm run typecheck && npm run typecheck:web && npm test && npm run build && npm run web:build && npm pack --dry-run",
|
|
50
50
|
"web:dev": "vite web",
|
|
51
51
|
"web:build": "vite build web",
|