402-mcp 4.1.1 → 4.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/README.md +11 -3
- package/build/index.js +18 -2
- package/build/index.js.map +1 -1
- package/build/store/encryption.js +77 -1
- package/build/store/encryption.js.map +1 -1
- package/build/tools/fetch-preview.d.ts +3 -0
- package/build/tools/fetch-preview.js +17 -1
- package/build/tools/fetch-preview.js.map +1 -1
- package/build/tools/fetch.d.ts +11 -0
- package/build/tools/fetch.js +49 -2
- package/build/tools/fetch.js.map +1 -1
- package/build/tools/pay.js +1 -1
- package/build/tools/pay.js.map +1 -1
- package/build/wallet/lnurlcash-ops.d.ts +80 -0
- package/build/wallet/lnurlcash-ops.js +137 -0
- package/build/wallet/lnurlcash-ops.js.map +1 -0
- package/build/wallet/lnurlcash.js +6 -115
- package/build/wallet/lnurlcash.js.map +1 -1
- package/build/xlnurlcash/parse.d.ts +27 -0
- package/build/xlnurlcash/parse.js +71 -0
- package/build/xlnurlcash/parse.js.map +1 -0
- package/build/xlnurlcash/payment.d.ts +37 -0
- package/build/xlnurlcash/payment.js +102 -0
- package/build/xlnurlcash/payment.js.map +1 -0
- package/llms-full.txt +10 -3
- package/llms.txt +2 -1
- package/package.json +6 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createNoteOps, noteUrl, NoteGone } from '../wallet/lnurlcash-ops.js';
|
|
2
|
+
/**
|
|
3
|
+
* Mints arrive as bare hosts (`mint.example.com`), origins, or full discovery
|
|
4
|
+
* URLs. Compared on host so all three forms match a stored note's origin.
|
|
5
|
+
*/
|
|
6
|
+
export function mintHost(value) {
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
if (trimmed.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
|
|
11
|
+
? trimmed.replace(/^lnurlw:\/\//i, 'https://')
|
|
12
|
+
: `https://${trimmed}`;
|
|
13
|
+
try {
|
|
14
|
+
const host = new URL(withScheme).host;
|
|
15
|
+
return host.length > 0 ? host.toLowerCase() : null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Builds a bearer-note payment for an lnurlcash challenge: an exact-value note
|
|
23
|
+
* from an accepted mint, or one split at the mint to the exact price so the
|
|
24
|
+
* paywall is never overpaid and the change stays in the store. Returns null
|
|
25
|
+
* when no note can cover it, so the caller falls through to another rail.
|
|
26
|
+
*
|
|
27
|
+
* The chosen note is held pending before it is returned. Handing a bearer note
|
|
28
|
+
* to a server puts it beyond our control: the server settles by rotating it,
|
|
29
|
+
* and only the mint knows whether that happened.
|
|
30
|
+
*/
|
|
31
|
+
export async function attemptLnurlcashPayment(opts) {
|
|
32
|
+
const { challenge, noteStore } = opts;
|
|
33
|
+
const ops = opts.ops ?? createNoteOps(noteStore, opts.options ?? {});
|
|
34
|
+
const accepted = new Set(challenge.mints.map(mintHost).filter((h) => h !== null));
|
|
35
|
+
if (accepted.size === 0)
|
|
36
|
+
return null;
|
|
37
|
+
const needMsat = challenge.amount * 1000;
|
|
38
|
+
try {
|
|
39
|
+
await ops.reconcile();
|
|
40
|
+
}
|
|
41
|
+
catch { /* best effort; selection below simply sees fewer notes */ }
|
|
42
|
+
const live = noteStore.live().filter(n => {
|
|
43
|
+
const host = mintHost(n.mint);
|
|
44
|
+
return host !== null && accepted.has(host);
|
|
45
|
+
});
|
|
46
|
+
// Exact-value notes first: handing one over avoids a split round trip
|
|
47
|
+
// entirely. Then the smallest note that covers the price, so large notes
|
|
48
|
+
// stay whole.
|
|
49
|
+
const candidates = [
|
|
50
|
+
...live.filter(n => n.amountMsat === needMsat),
|
|
51
|
+
...live.filter(n => n.amountMsat > needMsat).sort((a, b) => a.amountMsat - b.amountMsat),
|
|
52
|
+
];
|
|
53
|
+
for (const candidate of candidates) {
|
|
54
|
+
// The stored value is a cache. Only the mint knows what a note is worth
|
|
55
|
+
// now, and a bearer note anyone else holds a copy of may already be spent,
|
|
56
|
+
// so it is confirmed before it is handed over.
|
|
57
|
+
const probe = await ops.probeNote(candidate.mint, candidate.secret);
|
|
58
|
+
if (!probe.ok) {
|
|
59
|
+
// A mint that refuses the note has retired it: drop it and try the next.
|
|
60
|
+
// A mint that cannot be reached has said nothing, so nothing is dropped
|
|
61
|
+
// and no further note is risked against it.
|
|
62
|
+
if (probe.reason === 'unreachable')
|
|
63
|
+
return null;
|
|
64
|
+
noteStore.remove(candidate.secret);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (probe.note.amountMsat !== candidate.amountMsat) {
|
|
68
|
+
noteStore.setAmount(candidate.secret, probe.note.amountMsat);
|
|
69
|
+
}
|
|
70
|
+
if (probe.note.amountMsat < needMsat)
|
|
71
|
+
continue;
|
|
72
|
+
const current = noteStore.find(candidate.secret);
|
|
73
|
+
if (!current)
|
|
74
|
+
continue;
|
|
75
|
+
let exact;
|
|
76
|
+
try {
|
|
77
|
+
exact = probe.note.amountMsat === needMsat
|
|
78
|
+
? current
|
|
79
|
+
: await ops.split(current, needMsat);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// A note the mint no longer recognises is normal for a bearer asset:
|
|
83
|
+
// drop it and try the next. Anything else means the mint is refusing or
|
|
84
|
+
// unreachable, so stop rather than burn more notes against it.
|
|
85
|
+
if (error instanceof NoteGone)
|
|
86
|
+
continue;
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
noteStore.setState(exact.secret, 'melting');
|
|
90
|
+
const secret = exact.secret;
|
|
91
|
+
return {
|
|
92
|
+
header: noteUrl(exact),
|
|
93
|
+
amountSats: challenge.amount,
|
|
94
|
+
settle(granted) {
|
|
95
|
+
if (granted)
|
|
96
|
+
noteStore.remove(secret);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.js","sourceRoot":"","sources":["../../src/xlnurlcash/payment.ts"],"names":[],"mappings":"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"}
|
package/llms-full.txt
CHANGED
|
@@ -85,6 +85,11 @@ src/
|
|
|
85
85
|
xcashu/
|
|
86
86
|
parse.ts # Detect X-Cashu header; parse NUT-18 payment request
|
|
87
87
|
payment.ts # Autonomous ecash payment: coin-select, send exact token amount
|
|
88
|
+
xlnurlcash/
|
|
89
|
+
parse.ts # Detect X-LNURLcash header; parse the lnurlcash payment request
|
|
90
|
+
# (v/id/amount-as-string/currency/methodDetails.mints, per the
|
|
91
|
+
# conformance vectors; the older short form is still read)
|
|
92
|
+
payment.ts # Autonomous bearer-note payment: pick or split a note to the exact price
|
|
88
93
|
ietf-payment/
|
|
89
94
|
parse.ts # Detect/parse WWW-Authenticate: Payment challenges (draft-ryan-httpauth-payment-01)
|
|
90
95
|
credential.ts # Build Authorization: Payment base64url credential after Lightning payment
|
|
@@ -277,25 +282,27 @@ The L402 protocol is an extension of HTTP 402 (Payment Required):
|
|
|
277
282
|
|
|
278
283
|
## Supported 402 Protocol Variants
|
|
279
284
|
|
|
280
|
-
`l402-fetch` detects and handles
|
|
285
|
+
`l402-fetch` detects and handles five HTTP 402 challenge variants. Detection order in source: LNURLcash, x402, IETF Payment, L402, with xCashu tried immediately after LNURLcash.
|
|
281
286
|
|
|
282
287
|
| Protocol | Detection | Credential sent | Autonomous? |
|
|
283
288
|
|----------|-----------|----------------|-------------|
|
|
284
289
|
| **L402** | `WWW-Authenticate: L402 macaroon=…, invoice=…` | `Authorization: L402 <macaroon>:<preimage>` | Yes (NWC or Cashu) |
|
|
285
290
|
| **IETF Payment** (`draft-ryan-httpauth-payment-01`) | `WWW-Authenticate: Payment id=… method=lightning …` | `Authorization: Payment <base64url-json>` | Yes (NWC or Cashu) |
|
|
291
|
+
| **LNURLcash** (LUD-25) | `X-LNURLcash: lnurlcashreq1<base64url>` | `X-LNURLcash: <bearer note URL>` | Yes (note store only) |
|
|
286
292
|
| **xCashu** (NUT-18) | `X-Cashu: creqA<base64url>` | `X-Cashu: <cashuB-token>` | Yes (Cashu wallet only) |
|
|
287
293
|
| **x402** | `X-Payment-Required: x402` + JSON body | Human presents EIP-681 deeplink | No — on-chain EVM; surfaced to human |
|
|
288
294
|
|
|
289
|
-
xCashu uses coin selection via `wallet.send(amount, proofs, { includeFees: true })` and restores change proofs to the token store. IETF Payment decodes the base64url charge request to extract the BOLT11 invoice, then pays it through the normal wallet stack and wraps the preimage in a JSON credential.
|
|
295
|
+
LNURLcash is tried before the others because a bearer note is already money in hand: the note URL goes straight into the retry header and the server settles it by rotating the note. A note worth exactly the price is used as it is; otherwise one is split at the mint and the change stays in the store. The chosen note is held pending until the outcome is known, so nothing else can spend it in the meantime. xCashu uses coin selection via `wallet.send(amount, proofs, { includeFees: true })` and restores change proofs to the token store. IETF Payment decodes the base64url charge request to extract the BOLT11 invoice, then pays it through the normal wallet stack and wraps the preimage in a JSON credential.
|
|
290
296
|
|
|
291
297
|
## Wallet Priority and Resolution
|
|
292
298
|
|
|
293
|
-
Wallets are tried in order: NWC → Cashu → Human. The first wallet that succeeds is used.
|
|
299
|
+
Wallets are tried in order: NWC → Cashu → LNURLcash → Human. The first wallet that succeeds is used.
|
|
294
300
|
|
|
295
301
|
| Wallet | Autonomy | Requirements | Speed |
|
|
296
302
|
|--------|----------|-------------|-------|
|
|
297
303
|
| NWC | Fully autonomous | `NWC_URI_FILE` pointing to a private 0600 bearer file | ~2-5 seconds |
|
|
298
304
|
| Cashu | Fully autonomous | `CASHU_TOKENS` file with ecash tokens | ~3-8 seconds |
|
|
305
|
+
| LNURLcash | Fully autonomous | `LNURLCASH_NOTES` file with LUD-25 bearer notes | ~2-6 seconds |
|
|
299
306
|
| Human | Requires human | None — always available as fallback | Minutes |
|
|
300
307
|
|
|
301
308
|
The agent can force a specific method per-call using the `paymentMethod` parameter.
|
package/llms.txt
CHANGED
|
@@ -120,10 +120,11 @@ Three payer methods are tried in priority order:
|
|
|
120
120
|
|
|
121
121
|
The agent can override the payment method per-call, or you can configure only the methods you want available.
|
|
122
122
|
|
|
123
|
-
`l402-fetch` handles
|
|
123
|
+
`l402-fetch` handles five HTTP 402 challenge variants transparently:
|
|
124
124
|
|
|
125
125
|
- **L402** (`WWW-Authenticate: L402`) — the primary protocol; Lightning invoice paid via the wallet stack.
|
|
126
126
|
- **IETF Payment** (`WWW-Authenticate: Payment`, `draft-ryan-httpauth-payment-01`) — alternative Lightning path; builds a base64url credential after payment.
|
|
127
|
+
- **LNURLcash** (`X-LNURLcash: lnurlcashreq1…`, LUD-25): autonomous bearer-note payment; hands over a note worth exactly the price, splitting one at the mint if needed. Tried first: no Lightning hop and no swap at the mint.
|
|
127
128
|
- **xCashu** (`X-Cashu: creqA…`, NUT-18) — autonomous ecash payment; selects and sends the exact token amount without a Lightning round-trip.
|
|
128
129
|
- **x402** (`X-Payment-Required: x402`) — on-chain EVM payment; surfaces an EIP-681 deeplink to the human (no autonomous settlement).
|
|
129
130
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "402-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"mcpName": "dev.forgesworn/402-mcp",
|
|
5
5
|
"description": "L402 + x402 client MCP - AI agents discover, pay for, and consume any payment-gated API",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./l402/*": "./build/l402/*.js",
|
|
15
15
|
"./x402/*": "./build/x402/*.js",
|
|
16
16
|
"./xcashu/*": "./build/xcashu/*.js",
|
|
17
|
+
"./xlnurlcash/*": "./build/xlnurlcash/*.js",
|
|
17
18
|
"./ietf-payment/*": "./build/ietf-payment/*.js",
|
|
18
19
|
"./store/*": "./build/store/*.js",
|
|
19
20
|
"./fetch/*": "./build/fetch/*.js",
|
|
@@ -81,7 +82,10 @@
|
|
|
81
82
|
"ai-agent",
|
|
82
83
|
"autonomous-payments",
|
|
83
84
|
"macaroon",
|
|
84
|
-
"stablecoin"
|
|
85
|
+
"stablecoin",
|
|
86
|
+
"lnurlcash",
|
|
87
|
+
"lud-25",
|
|
88
|
+
"bearer-notes"
|
|
85
89
|
],
|
|
86
90
|
"author": "forgesworn",
|
|
87
91
|
"homepage": "https://github.com/forgesworn/402-mcp",
|