lnurlcash-conformance 0.2.0 → 0.2.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 CHANGED
@@ -4,6 +4,28 @@ Semantic versioning. While the LUD-25 draft is unmerged, `0.x` minor bumps
4
4
  may add or tighten checks that a previously-passing mint now fails; pin an
5
5
  exact version if you gate CI on the grade.
6
6
 
7
+ ## 0.2.2 - 2026-08-22
8
+
9
+ - The mock mint publishes `payLink` on a note's informational GET, as the
10
+ reference mint does. That is the way home for a holder who has nothing
11
+ but a note: without it a wallet that only ever received notes cannot
12
+ reach the document carrying the mint's retired signing keys, so a
13
+ correctly announced key rotation is indistinguishable from a substituted
14
+ key. `noteInfoPayLink: false` models a mint that does not publish it, and
15
+ `payLinkOffOrigin: true` is the misbehaviour where a mint points its
16
+ `payLink` at a different origin, nominating a third party to vouch for
17
+ its own key history - a client must ignore that.
18
+
19
+ ## 0.2.1 - 2026-08-22
20
+
21
+ - The grader ships its own TypeScript declarations. The mock mint had them
22
+ and the runner did not, so a TypeScript consumer hand-wrote a
23
+ `declare module` shim, and those drift: moneyer carried one, `gradeBoundMint`
24
+ landed here, and the shim did not know about it. A consumer could not call
25
+ the newest check without editing a copy of a declaration it does not own.
26
+ Now `runner/index.d.ts` sits next to the code it describes and the exports
27
+ map points at it. No runtime change of any kind.
28
+
7
29
  ## 0.2.0 - 2026-08-22
8
30
 
9
31
  - Naming the note you are buying. In LUD-25 a minted note's `k1` is the
@@ -38,7 +38,19 @@ export interface MockMintOptions {
38
38
  verify?: boolean
39
39
  privateKey?: string
40
40
 
41
+ /**
42
+ * Publish `payLink` on a note's informational GET - the way home for a
43
+ * holder who has nothing but the note. On by default, as the reference
44
+ * mint does it.
45
+ */
46
+ noteInfoPayLink?: boolean
47
+
41
48
  // ---- misbehaviour ----
49
+ /**
50
+ * Point `payLink` at a different origin, nominating a third party to
51
+ * vouch for this mint's key history. A client must ignore it.
52
+ */
53
+ payLinkOffOrigin?: boolean
42
54
  /** answer the informational GET with a k1 other than the one queried */
43
55
  echoWrongK1?: boolean
44
56
  /** report a maxWithdrawable that is not what the note is worth */
@@ -70,7 +70,14 @@ const DEFAULTS = {
70
70
  // preimage it serves IS a bearer secret, so an operator needs a real
71
71
  // off switch.
72
72
  verify: true,
73
+ // Publish `payLink` on a note's informational GET, the way home for a
74
+ // holder who has nothing but the note. On by default because the
75
+ // reference mint does it; turn it off for a mint that does not.
76
+ noteInfoPayLink: true,
73
77
  // ---- misbehaviour ----
78
+ // point `payLink` at a DIFFERENT origin, nominating a third party to
79
+ // vouch for this mint's key history. A client must ignore it.
80
+ payLinkOffOrigin: false,
74
81
  // answer the informational GET with a k1 other than the one queried
75
82
  echoWrongK1: false,
76
83
  // report a maxWithdrawable that is not what the note is worth
@@ -673,6 +680,19 @@ export const createMockMint = async (options = {}) => {
673
680
  minWithdrawable: 0,
674
681
  maxWithdrawable: note.amountMsat + opts.lieAboutValue,
675
682
  defaultDescription: 'an LNURLcash note',
683
+ // The way home, as the reference mint publishes it. A holder with
684
+ // nothing but a note can reach the document carrying this mint's
685
+ // terms and its retired signing keys; without it a wallet that only
686
+ // ever received notes cannot tell an announced key rotation from a
687
+ // substituted key, because the document lives under a username the
688
+ // note never mentions.
689
+ ...(opts.noteInfoPayLink
690
+ ? {
691
+ payLink: opts.payLinkOffOrigin
692
+ ? 'https://elsewhere.example/.well-known/lnurlp/mint'
693
+ : `${origin}/.well-known/lnurlp/${opts.username}`
694
+ }
695
+ : {}),
676
696
  mintPubkey: pubkey
677
697
  })
678
698
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lnurlcash-conformance",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Language-neutral conformance vectors, an adversarial mock mint, and a grader for LNURLcash (LUD-25) implementations",
5
5
  "author": "TheCryptoDonkey",
6
6
  "license": "MIT",
@@ -34,7 +34,10 @@
34
34
  "llms.txt"
35
35
  ],
36
36
  "exports": {
37
- ".": "./runner/index.mjs",
37
+ ".": {
38
+ "types": "./runner/index.d.ts",
39
+ "default": "./runner/index.mjs"
40
+ },
38
41
  "./vectors/*": "./vectors/*",
39
42
  "./mock-mint": {
40
43
  "types": "./mock-mint/index.d.ts",
@@ -0,0 +1,100 @@
1
+ // Types for the grader, so a TypeScript consumer gets its shape checked
2
+ // rather than hand-writing a `declare module` shim of its own. Hand-written
3
+ // to match index.mjs.
4
+ //
5
+ // This file exists because those shims drift. moneyer carried one, the
6
+ // grader grew gradeBoundMint, and the shim did not: the consumer could not
7
+ // call the newest check without editing a copy of a declaration it does not
8
+ // own. Shipping the declarations puts that in one place, next to the code
9
+ // they describe.
10
+
11
+ /** what the mint fee is, in LUD-25's own two terms */
12
+ export interface MintFee {
13
+ baseFeeMsat: number
14
+ feePpm: number
15
+ }
16
+
17
+ export type ReportStatus = 'pass' | 'fail' | 'warn' | 'skip'
18
+
19
+ export interface ReportResult {
20
+ status: ReportStatus
21
+ name: string
22
+ /** what the check saw, in a sentence. Present on nearly every result */
23
+ detail?: string
24
+ }
25
+
26
+ export interface Report {
27
+ results: ReportResult[]
28
+ pass(name: string, detail?: string): void
29
+ fail(name: string, detail?: string): void
30
+ warn(name: string, detail?: string): void
31
+ skip(name: string, detail?: string): void
32
+ /**
33
+ * Runs `fn` and records the outcome. A thrown error is a failure; an
34
+ * error carrying a truthy `warning` is a warning, which is how an
35
+ * optional extension in the wrong shape is reported without failing a
36
+ * mint that need not have published it at all.
37
+ */
38
+ check(name: string, fn: () => Promise<unknown> | unknown): Promise<void>
39
+ /** how many results are failures */
40
+ readonly failed: number
41
+ }
42
+
43
+ export declare const createReport: () => Report
44
+
45
+ /** an `lnurlw://` or `lnurlp://` URL as its https equivalent, per LUD-17 */
46
+ export declare const fromLud17: (value: string) => string
47
+
48
+ /** a lightning address or LNURL as the payRequest URL to fetch */
49
+ export declare const resolveMint: (input: string) => string
50
+
51
+ /** the msat a bolt11 invoice states, or null where it states none */
52
+ export declare const invoiceAmountMsat: (pr: string) => number | null
53
+
54
+ /** what a gross mint of `gross` msat leaves once `fee` is withheld */
55
+ export declare const applyMintFee: (gross: number, fee: MintFee | null) => number
56
+
57
+ /** the `Mint fees: base,ppm` line out of a payRequest's metadata array */
58
+ export declare const parseAdvertisedMintFee: (metadata: string) => MintFee | null
59
+
60
+ /** the read-only mint checks: payRequest, withdrawLink, fees, verify, extensions */
61
+ export declare const gradeMint: (payUrl: string, report: Report) => Promise<void>
62
+
63
+ /**
64
+ * Read-only. Needs a freshly minted, never-rotated note and what its mint
65
+ * invoice was paid at, and grades the note's value against the advertised
66
+ * fee.
67
+ */
68
+ export declare const gradeMintedValue: (
69
+ noteUrl: string,
70
+ report: Report,
71
+ options: {paidMsat: number; mintFee?: MintFee | null}
72
+ ) => Promise<void>
73
+
74
+ /**
75
+ * Read-only. Needs a note minted against a hash the WALLET chose, plus the
76
+ * payment preimage of the invoice that funded it. Checks that the note is
77
+ * at the wallet's own secret and that the preimage opens nothing.
78
+ *
79
+ * `payCallback` is optional: with it, the runner also checks that the id
80
+ * the note occupies cannot be sold again as a mint quote.
81
+ */
82
+ export declare const gradeBoundMint: (
83
+ noteUrl: string,
84
+ report: Report,
85
+ options: {preimage: string; payCallback?: string | null}
86
+ ) => Promise<void>
87
+
88
+ /**
89
+ * SPENDS. Burns the note it is given and leaves the value in a fresh one.
90
+ *
91
+ * `mintFee` absent means unknown, and the conservation checks are bounded
92
+ * rather than exact; null means known fee-free. `previousPubkeys` are keys
93
+ * the mint has signed under before, so a note issued before a rotation is
94
+ * not graded as a bad signature.
95
+ */
96
+ export declare const gradeNote: (
97
+ noteUrl: string,
98
+ report: Report,
99
+ options?: {mintFee?: MintFee | null; previousPubkeys?: string[]}
100
+ ) => Promise<void>