402-announce 2.1.0 → 2.1.1
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 +33 -13
- package/context7.json +4 -0
- package/llms-full.txt +96 -33
- package/llms.txt +15 -5
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ const handle = await announceService({
|
|
|
47
47
|
pricing: [
|
|
48
48
|
{ capability: 'get_joke', price: 1, currency: 'sats' },
|
|
49
49
|
],
|
|
50
|
-
paymentMethods: ['
|
|
50
|
+
paymentMethods: [['l402', 'lightning'], ['cashu'], ['xcashu']],
|
|
51
51
|
topics: ['comedy', 'ai'],
|
|
52
52
|
capabilities: [
|
|
53
53
|
{ name: 'get_joke', description: 'Returns a random joke' },
|
|
@@ -77,7 +77,7 @@ const handle = await announceService({
|
|
|
77
77
|
],
|
|
78
78
|
about: 'A joke-telling service behind an L402 paywall',
|
|
79
79
|
pricing: [{ capability: 'get_joke', price: 1, currency: 'sats' }],
|
|
80
|
-
paymentMethods: ['
|
|
80
|
+
paymentMethods: [['l402', 'lightning']],
|
|
81
81
|
topics: ['comedy', 'ai'],
|
|
82
82
|
})
|
|
83
83
|
```
|
|
@@ -162,7 +162,7 @@ Lower-level function that builds and signs the event without publishing. Useful
|
|
|
162
162
|
| `urls` | `string[]` | yes | HTTP endpoints for the service (1–10 entries, any parseable URL) |
|
|
163
163
|
| `about` | `string` | yes | Short description |
|
|
164
164
|
| `pricing` | `PricingDef[]` | yes | Per-capability pricing |
|
|
165
|
-
| `paymentMethods` | `string[]`
|
|
165
|
+
| `paymentMethods` | `string[][]` | yes | Accepted payment methods (each entry is `[rail, ...params]`) |
|
|
166
166
|
| `picture` | `string` | no | Icon URL |
|
|
167
167
|
| `topics` | `string[]` | no | Topic tags for filtering |
|
|
168
168
|
| `capabilities` | `CapabilityDef[]` | no | Capability details (stored in event content) |
|
|
@@ -200,8 +200,8 @@ graph TB
|
|
|
200
200
|
U1["url: https://jokes.example.com"]
|
|
201
201
|
U2["url: https://jokesapi.example.onion (optional)"]
|
|
202
202
|
AB["about: A joke-telling service"]
|
|
203
|
-
PMI1["pmi:
|
|
204
|
-
PMI2["pmi:
|
|
203
|
+
PMI1["pmi: l402, lightning"]
|
|
204
|
+
PMI2["pmi: cashu"]
|
|
205
205
|
P["price: get_joke, 1, sats"]
|
|
206
206
|
T["t: comedy"]
|
|
207
207
|
end
|
|
@@ -223,19 +223,22 @@ graph TB
|
|
|
223
223
|
| `name` | yes | Human-readable service name | `Jokes API` |
|
|
224
224
|
| `url` | yes | HTTP endpoint (one tag per URL; repeatable) | `https://jokes.example.com` |
|
|
225
225
|
| `about` | yes | Short description | `A joke-telling service` |
|
|
226
|
-
| `pmi` | yes | Payment method identifier (repeatable)
|
|
226
|
+
| `pmi` | yes | Payment method identifier (repeatable, multi-element) | `l402`, `lightning` |
|
|
227
227
|
| `price` | yes | Capability pricing (repeatable) | `get_joke`, `1`, `sats` |
|
|
228
228
|
| `t` | no | Topic tag for search/filtering (repeatable) | `comedy` |
|
|
229
229
|
| `picture` | no | Icon URL | `https://example.com/icon.png` |
|
|
230
230
|
|
|
231
|
-
### Recognised Payment Method
|
|
231
|
+
### Recognised Payment Method Rails
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
|
236
|
-
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
233
|
+
Each `paymentMethods` entry is an array where the first element is the rail identifier and subsequent elements are rail-specific parameters.
|
|
234
|
+
|
|
235
|
+
| Rail | Example | Description |
|
|
236
|
+
|------|---------|-------------|
|
|
237
|
+
| `l402` | `['l402', 'lightning']` | L402 protocol (Lightning BOLT-11 invoices) |
|
|
238
|
+
| `cashu` | `['cashu']` | Cashu ecash (generic) |
|
|
239
|
+
| `xcashu` | `['xcashu']` | Cashu ecash via NUT-24 (X-Cashu header) |
|
|
240
|
+
| `x402` | `['x402', 'base', 'usdc', '0xabc...']` | x402 stablecoin payments (chain, token, receiver address) |
|
|
241
|
+
| `payment` | `['payment', 'lightning']` | IETF Payment protocol |
|
|
239
242
|
|
|
240
243
|
### Content
|
|
241
244
|
|
|
@@ -277,6 +280,23 @@ In short: same service + different network paths → one event with multiple `ur
|
|
|
277
280
|
| [402-mcp](https://github.com/forgesworn/402-mcp) | MCP server for AI agents to discover, pay, and consume 402 APIs |
|
|
278
281
|
| [Live Dashboard](https://402.pub) | See every service announcing on the network |
|
|
279
282
|
|
|
283
|
+
## Part of the ForgeSworn Toolkit
|
|
284
|
+
|
|
285
|
+
[ForgeSworn](https://forgesworn.dev) builds open-source cryptographic identity, payments, and coordination tools for Nostr.
|
|
286
|
+
|
|
287
|
+
| Library | What it does |
|
|
288
|
+
|---------|-------------|
|
|
289
|
+
| [nsec-tree](https://github.com/forgesworn/nsec-tree) | Deterministic sub-identity derivation |
|
|
290
|
+
| [ring-sig](https://github.com/forgesworn/ring-sig) | SAG/LSAG ring signatures on secp256k1 |
|
|
291
|
+
| [range-proof](https://github.com/forgesworn/range-proof) | Pedersen commitment range proofs |
|
|
292
|
+
| [canary-kit](https://github.com/forgesworn/canary-kit) | Coercion-resistant spoken verification |
|
|
293
|
+
| [spoken-token](https://github.com/forgesworn/spoken-token) | Human-speakable verification tokens |
|
|
294
|
+
| [toll-booth](https://github.com/forgesworn/toll-booth) | L402 payment middleware |
|
|
295
|
+
| [geohash-kit](https://github.com/forgesworn/geohash-kit) | Geohash toolkit with polygon coverage |
|
|
296
|
+
| [nostr-attestations](https://github.com/forgesworn/nostr-attestations) | NIP-VA verifiable attestations |
|
|
297
|
+
| [dominion](https://github.com/forgesworn/dominion) | Epoch-based encrypted access control |
|
|
298
|
+
| [nostr-veil](https://github.com/forgesworn/nostr-veil) | Privacy-preserving Web of Trust |
|
|
299
|
+
|
|
280
300
|
## Licence
|
|
281
301
|
|
|
282
302
|
[MIT](./LICENSE)
|
package/context7.json
ADDED
package/llms-full.txt
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
The library handles event construction, signing, and multi-relay publication. Secret key material is zeroised after use.
|
|
12
12
|
|
|
13
|
+
## Kind 31402 — disambiguation
|
|
14
|
+
|
|
15
|
+
This library uses kind 31402 for **L402/x402 service announcements** (paid API discovery). This is distinct from any SARA (Simple Autonomous Revenue Agreement) proposals that may also reference kind 31402. The 402-announce definition: a parameterised replaceable event (NIP-33) where pubkey + d-tag uniquely identifies one service listing.
|
|
16
|
+
|
|
13
17
|
## Installation
|
|
14
18
|
|
|
15
19
|
```bash
|
|
@@ -28,13 +32,13 @@ High-level function: builds a kind 31402 event, signs it, connects to relays in
|
|
|
28
32
|
|
|
29
33
|
AnnounceConfig:
|
|
30
34
|
- secretKey (string, required): 64-character hex-encoded Nostr secret key
|
|
31
|
-
- relays (string[], required): Relay URLs (must start with wss:// or ws://)
|
|
35
|
+
- relays (string[], required): Relay URLs (must start with wss:// or ws://). Maximum 50.
|
|
32
36
|
- identifier (string, required): Unique listing ID, used as the Nostr `d` tag. Same pubkey + identifier = same listing.
|
|
33
37
|
- name (string, required): Human-readable service name
|
|
34
|
-
- urls (string[], required): Transport endpoint URLs (1–10 entries, each max 2048 characters)
|
|
38
|
+
- urls (string[], required): Transport endpoint URLs (1–10 entries, each max 2048 characters). One `url` tag per entry in the event. Clients try in order.
|
|
35
39
|
- about (string, required): Short description of the service
|
|
36
40
|
- pricing (PricingDef[], required): Per-capability pricing. Each entry: { capability, price, currency }
|
|
37
|
-
- paymentMethods (string[], required): Accepted payment
|
|
41
|
+
- paymentMethods (string[][], required): Accepted payment methods. Each entry is [rail, ...params]
|
|
38
42
|
- picture (string, optional): Icon URL
|
|
39
43
|
- topics (string[], optional): Topic tags for search/filtering
|
|
40
44
|
- capabilities (CapabilityDef[], optional): Capability details stored in event content
|
|
@@ -42,31 +46,37 @@ AnnounceConfig:
|
|
|
42
46
|
|
|
43
47
|
PricingDef:
|
|
44
48
|
- capability (string): Capability name (e.g. "get_joke", "chat", "inference")
|
|
45
|
-
- price (number): Price amount
|
|
49
|
+
- price (number): Price amount (integer or fractional)
|
|
46
50
|
- currency (string): Currency unit (e.g. "sats", "USD")
|
|
47
51
|
|
|
48
52
|
CapabilityDef:
|
|
49
53
|
- name (string): Capability name
|
|
50
54
|
- description (string): Human-readable description
|
|
51
|
-
-
|
|
52
|
-
-
|
|
55
|
+
- endpoint (string, optional): Endpoint path or full URL for this capability (e.g. '/api/joke')
|
|
56
|
+
- schema (unknown, optional): JSON Schema describing the capability's input parameters (max depth 10)
|
|
57
|
+
- outputSchema (unknown, optional): JSON Schema describing the capability's output (max depth 10)
|
|
53
58
|
|
|
54
59
|
**Returns** Announcement:
|
|
55
60
|
- eventId (string): The published Nostr event ID
|
|
56
61
|
- pubkey (string): Nostr pubkey derived from the secret key
|
|
57
|
-
- close(): void — Disconnect from all connected relays
|
|
62
|
+
- close(): void — Disconnect from all connected relays (synchronous; call on shutdown to avoid WebSocket leaks)
|
|
58
63
|
|
|
59
64
|
**Behaviour:**
|
|
60
65
|
- Validates secretKey (must be 64-char hex) and relay URLs (must be wss:// or ws://)
|
|
66
|
+
- Rejects relay URLs pointing to private/loopback addresses (SSRF prevention)
|
|
67
|
+
- Rejects service configs where all urls are private/loopback (at least one public URL required)
|
|
61
68
|
- Connects to all relays in parallel with a 10-second timeout per relay
|
|
62
69
|
- Individual relay failures are logged as warnings but do not reject the promise
|
|
63
70
|
- If no relay accepts the event, a warning is emitted (but the promise still resolves)
|
|
64
71
|
- Secret key bytes are zeroised after signing
|
|
72
|
+
- Warns on insecure ws:// relay URLs
|
|
65
73
|
|
|
66
74
|
**Throws:**
|
|
67
75
|
- If secretKey is not a 64-character hex string
|
|
68
|
-
- If relays array is empty
|
|
76
|
+
- If relays array is empty or contains more than 50 entries
|
|
69
77
|
- If any relay URL does not start with wss:// or ws://
|
|
78
|
+
- If any relay URL points to a private/loopback address
|
|
79
|
+
- If all service URLs are private/loopback addresses
|
|
70
80
|
|
|
71
81
|
### buildAnnounceEvent(secretKeyHex: string, config: Omit<AnnounceConfig, 'relays'>): VerifiedEvent
|
|
72
82
|
|
|
@@ -74,7 +84,11 @@ Lower-level function: builds and signs the kind 31402 event without publishing.
|
|
|
74
84
|
|
|
75
85
|
**Returns** a nostr-tools VerifiedEvent ready for relay publication.
|
|
76
86
|
|
|
77
|
-
Secret key bytes are zeroised after signing (via try/finally).
|
|
87
|
+
Secret key bytes are zeroised after signing (via try/finally). Unlike announceService(), this function does not enforce the "at least one public URL" rule — it is a pure serialisation utility.
|
|
88
|
+
|
|
89
|
+
### isPrivateHost(hostname: string): boolean
|
|
90
|
+
|
|
91
|
+
Utility export. Returns true if the hostname is a private, loopback, link-local, or reserved address (IPv4 or IPv6). Used internally for SSRF prevention; exported for consumers who need the same guard.
|
|
78
92
|
|
|
79
93
|
### Constants
|
|
80
94
|
|
|
@@ -85,7 +99,7 @@ Secret key bytes are zeroised after signing (via try/finally).
|
|
|
85
99
|
- AnnounceConfig — Configuration for announceService()
|
|
86
100
|
- Announcement — Handle returned by announceService()
|
|
87
101
|
- PricingDef — Pricing for a specific capability
|
|
88
|
-
- CapabilityDef — Capability with description
|
|
102
|
+
- CapabilityDef — Capability with description, optional endpoint and schemas
|
|
89
103
|
|
|
90
104
|
## Event Format Specification
|
|
91
105
|
|
|
@@ -99,12 +113,12 @@ A kind 31402 event is a Nostr parameterised replaceable event (NIP-33). This mea
|
|
|
99
113
|
### Tag structure
|
|
100
114
|
|
|
101
115
|
Required tags:
|
|
102
|
-
["d", "<identifier>"]
|
|
103
|
-
["name", "<service name>"]
|
|
104
|
-
["url", "<endpoint URL>"]
|
|
105
|
-
["about", "<description>"]
|
|
106
|
-
["pmi", "<
|
|
107
|
-
["price", "<capability>", "<amount>", "<currency>"]
|
|
116
|
+
["d", "<identifier>"] — Unique listing identifier
|
|
117
|
+
["name", "<service name>"] — Human-readable name
|
|
118
|
+
["url", "<endpoint URL>"] — HTTP endpoint (one tag per URL; 1–10 allowed)
|
|
119
|
+
["about", "<description>"] — Short description
|
|
120
|
+
["pmi", "<rail>", ...params] — Payment method identifier (one tag per method)
|
|
121
|
+
["price", "<capability>", "<amount>", "<currency>"] — Pricing (one tag per capability)
|
|
108
122
|
|
|
109
123
|
Optional tags:
|
|
110
124
|
["t", "<topic>"] — Topic tag for filtering (one tag per topic)
|
|
@@ -115,7 +129,13 @@ Optional tags:
|
|
|
115
129
|
JSON object with optional fields:
|
|
116
130
|
{
|
|
117
131
|
"capabilities": [
|
|
118
|
-
{
|
|
132
|
+
{
|
|
133
|
+
"name": "get_joke",
|
|
134
|
+
"description": "Returns a random joke",
|
|
135
|
+
"endpoint": "/api/joke",
|
|
136
|
+
"schema": { "type": "object", "properties": { "category": { "type": "string" } } },
|
|
137
|
+
"outputSchema": { "type": "object", "properties": { "joke": { "type": "string" } } }
|
|
138
|
+
}
|
|
119
139
|
],
|
|
120
140
|
"version": "1.0.0"
|
|
121
141
|
}
|
|
@@ -130,9 +150,10 @@ JSON object with optional fields:
|
|
|
130
150
|
["d", "jokes-api"],
|
|
131
151
|
["name", "Jokes API"],
|
|
132
152
|
["url", "https://jokes.example.com"],
|
|
153
|
+
["url", "http://jokesxyz...onion"],
|
|
133
154
|
["about", "A joke-telling service behind an L402 paywall"],
|
|
134
|
-
["pmi", "
|
|
135
|
-
["pmi", "
|
|
155
|
+
["pmi", "l402", "lightning"],
|
|
156
|
+
["pmi", "cashu"],
|
|
136
157
|
["price", "get_joke", "1", "sats"],
|
|
137
158
|
["t", "comedy"],
|
|
138
159
|
["t", "ai"]
|
|
@@ -144,19 +165,25 @@ JSON object with optional fields:
|
|
|
144
165
|
|
|
145
166
|
## Payment Method Identifiers
|
|
146
167
|
|
|
147
|
-
|
|
168
|
+
Each `paymentMethods` entry is an array where the first element is the rail identifier and subsequent elements are rail-specific parameters. Known rails:
|
|
148
169
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
x402
|
|
153
|
-
|
|
170
|
+
l402 — L402 protocol (Lightning BOLT-11 invoices). Example: ['l402', 'lightning']
|
|
171
|
+
cashu — Cashu ecash. Example: ['cashu']
|
|
172
|
+
xcashu — Cashu ecash via NUT-24 (X-Cashu header). Example: ['xcashu']
|
|
173
|
+
x402 — x402 stablecoin payments. Example: ['x402', 'base', 'usdc', '0xabc...']
|
|
174
|
+
payment — IETF Payment protocol. Example: ['payment', 'lightning']
|
|
154
175
|
|
|
155
|
-
##
|
|
176
|
+
## Multiple URLs vs Multiple Events
|
|
177
|
+
|
|
178
|
+
**Multiple URLs in one event** — use `urls: ['...', '...']` when the URLs represent the **same service** on different transports (clearnet, Tor, Handshake). Pricing, credentials, and macaroon signing key are identical. Clients try in order and use whichever they can reach. This is for censorship resistance and redundancy.
|
|
156
179
|
|
|
157
|
-
|
|
180
|
+
**Separate kind 31402 events** — publish a new event (different `identifier`) when you have **genuinely different services**: different pricing tiers, different capabilities, or services that operate independently.
|
|
158
181
|
|
|
159
|
-
|
|
182
|
+
Rule: same service + different network paths → one event with multiple `url` tags. Different services → separate events.
|
|
183
|
+
|
|
184
|
+
## Integration Patterns
|
|
185
|
+
|
|
186
|
+
### Producer: toll-booth + 402-announce
|
|
160
187
|
|
|
161
188
|
```typescript
|
|
162
189
|
import { createTollBooth } from 'toll-booth'
|
|
@@ -175,16 +202,31 @@ const handle = await announceService({
|
|
|
175
202
|
urls: ['https://api.example.com'],
|
|
176
203
|
about: 'A paid API service',
|
|
177
204
|
pricing: [{ capability: 'query', price: 10, currency: 'sats' }],
|
|
178
|
-
paymentMethods: ['
|
|
205
|
+
paymentMethods: [['l402', 'lightning']],
|
|
179
206
|
})
|
|
180
207
|
|
|
181
208
|
// 3. Clean up on shutdown
|
|
182
209
|
process.on('SIGTERM', () => handle.close())
|
|
183
210
|
```
|
|
184
211
|
|
|
185
|
-
###
|
|
212
|
+
### Producer shortcut: toll-booth-announce
|
|
213
|
+
|
|
214
|
+
If you're using toll-booth, [toll-booth-announce](https://github.com/forgesworn/toll-booth-announce) maps booth config to 402-announce automatically — pricing and payment methods are derived from your booth config:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { announce } from 'toll-booth-announce'
|
|
218
|
+
|
|
219
|
+
const handle = await announce(boothConfig, {
|
|
220
|
+
secretKey: process.env.NOSTR_SECRET_KEY,
|
|
221
|
+
relays: ['wss://relay.damus.io'],
|
|
222
|
+
urls: ['https://api.example.com'],
|
|
223
|
+
about: 'My toll-booth API',
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Consumer: 402-mcp
|
|
186
228
|
|
|
187
|
-
AI agents use 402-mcp to discover and consume announced services:
|
|
229
|
+
AI agents use [402-mcp](https://github.com/forgesworn/402-mcp) to discover and consume announced services:
|
|
188
230
|
|
|
189
231
|
1. 402-mcp subscribes to kind 31402 events on configured relays
|
|
190
232
|
2. Agent searches for services by topic, capability, or payment method
|
|
@@ -197,20 +239,39 @@ The agent never needs to know about Nostr or payment protocols — 402-mcp abstr
|
|
|
197
239
|
|
|
198
240
|
Because kind 31402 is a parameterised replaceable event, calling announceService() again with the same identifier and pubkey replaces the previous listing. Use this to update pricing, add capabilities, or change the endpoint URL.
|
|
199
241
|
|
|
242
|
+
### Verify on relays
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Using nak (Nostr Army Knife)
|
|
246
|
+
nak req -k 31402 wss://relay.damus.io
|
|
247
|
+
|
|
248
|
+
# Filter by topic
|
|
249
|
+
nak req -k 31402 -t t=ai wss://relay.damus.io
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Live dashboard
|
|
253
|
+
|
|
254
|
+
[402.pub](https://402.pub) shows every service announcing on the network in real time.
|
|
255
|
+
|
|
200
256
|
## Security Considerations
|
|
201
257
|
|
|
202
258
|
- **Key zeroisation**: Secret key bytes (Uint8Array) are filled with zeros immediately after signing, in both announceService() and buildAnnounceEvent(). This limits the window during which key material exists in memory.
|
|
203
|
-
- **Key handling**: Pass the secret key as a hex string. The library converts to bytes, signs, and zeroises. Never log or persist the key yourself.
|
|
259
|
+
- **Key handling**: Pass the secret key as a hex string. The library converts to bytes, signs, and zeroises. Never log or persist the key yourself. Note: JavaScript strings are immutable and cannot be erased — minimise the lifetime of the string in your application.
|
|
260
|
+
- **SSRF prevention**: Relay URLs and service URLs are checked against private/loopback address patterns. DNS rebinding (hostname resolves to private IP at connection time) is not caught — deploy behind network-level egress controls in production.
|
|
204
261
|
- **Relay trust**: Relays are untrusted infrastructure. Events are cryptographically signed — relay operators cannot forge announcements. However, relays can choose not to store or serve your events.
|
|
205
262
|
- **Connection timeouts**: Relay connections time out after 10 seconds to prevent indefinite hangs.
|
|
206
263
|
- **Graceful degradation**: If some relays fail, the event is still published to the others. A warning is emitted only if no relay accepts the event.
|
|
264
|
+
- **Input limits**: URLs max 2048 chars, max 10 service URLs, max 50 relays, JSON schema depth capped at 10, control characters rejected in all string fields.
|
|
207
265
|
|
|
208
266
|
## Error Handling
|
|
209
267
|
|
|
210
268
|
The library throws synchronously for invalid configuration:
|
|
211
269
|
- secretKey not 64-char hex → Error
|
|
212
|
-
- Empty relays array → Error
|
|
270
|
+
- Empty relays array or more than 50 relays → Error
|
|
213
271
|
- Invalid relay URL (not wss:// or ws://) → Error
|
|
272
|
+
- Relay URL with credentials → Error
|
|
273
|
+
- Relay URL pointing to private/loopback address → Error
|
|
274
|
+
- All service URLs pointing to private/loopback addresses → Error
|
|
214
275
|
|
|
215
276
|
Relay-level failures during publication are handled gracefully:
|
|
216
277
|
- Connection timeouts → warning logged, other relays still tried
|
|
@@ -220,5 +281,7 @@ Relay-level failures during publication are handled gracefully:
|
|
|
220
281
|
## Source
|
|
221
282
|
|
|
222
283
|
Repository: https://github.com/forgesworn/402-announce
|
|
284
|
+
npm: https://www.npmjs.com/package/402-announce
|
|
285
|
+
Live dashboard: https://402.pub
|
|
223
286
|
Licence: MIT
|
|
224
287
|
Runtime dependency: nostr-tools
|
package/llms.txt
CHANGED
|
@@ -6,26 +6,36 @@
|
|
|
6
6
|
|
|
7
7
|
402-announce publishes kind 31402 parameterised replaceable Nostr events that describe paid HTTP APIs. AI agents and Nostr clients discover these announcements to find services they can pay for and consume — no central registry needed.
|
|
8
8
|
|
|
9
|
+
## Kind 31402 — disambiguation
|
|
10
|
+
|
|
11
|
+
This library uses kind 31402 for **L402/x402 service announcements** (paid API discovery). This is distinct from any SARA (revenue share) proposals that may also reference kind 31402. The 402-announce definition: a parameterised replaceable event (NIP-33) where pubkey + d-tag uniquely identifies one service listing.
|
|
12
|
+
|
|
9
13
|
## Key concepts
|
|
10
14
|
|
|
11
15
|
- **Kind 31402**: Nostr event kind for L402/x402 service announcements. Parameterised replaceable — same pubkey + identifier updates the existing listing.
|
|
12
|
-
- **Payment methods**: Services declare accepted payment methods via `pmi` tags (e.g. `
|
|
16
|
+
- **Payment methods**: Services declare accepted payment methods via multi-element `pmi` tags. Each `paymentMethods` entry is `[rail, ...params]` (e.g. `['l402', 'lightning']`, `['cashu']`, `['x402', 'base', 'usdc', '<addr>']`, `['payment', 'lightning']`).
|
|
13
17
|
- **Pricing**: Per-capability pricing via `price` tags (capability name, amount, currency).
|
|
14
18
|
- **Decentralised discovery**: Events are published to standard Nostr relays. Any client can subscribe and filter.
|
|
19
|
+
- **Multi-transport**: A single event can carry multiple `url` tags (1–10). Use this when the same service is reachable via clearnet, Tor (.onion), or Handshake domains.
|
|
15
20
|
|
|
16
21
|
## API surface
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
Three exports:
|
|
19
24
|
|
|
20
25
|
- `announceService(config)` — build, sign, and publish to relays. Returns `{ eventId, pubkey, close() }`.
|
|
21
26
|
- `buildAnnounceEvent(secretKey, config)` — build and sign without publishing. Returns a `VerifiedEvent`.
|
|
27
|
+
- `isPrivateHost(hostname)` — utility: returns true if hostname is a private/loopback address (SSRF guard).
|
|
28
|
+
|
|
29
|
+
Exported constant: `L402_ANNOUNCE_KIND = 31402`
|
|
22
30
|
|
|
23
31
|
## Ecosystem position
|
|
24
32
|
|
|
25
33
|
```
|
|
26
|
-
toll-booth (paywall)
|
|
27
|
-
|
|
28
|
-
402-
|
|
34
|
+
toll-booth (paywall) → protects your API with L402
|
|
35
|
+
toll-booth-announce (bridge) → maps toll-booth config to 402-announce automatically
|
|
36
|
+
402-announce (this) → tells the world your API exists via kind 31402
|
|
37
|
+
402-mcp (discovery) → AI agents find, pay, and consume your API
|
|
38
|
+
402.pub (dashboard) → live view of all announcing services
|
|
29
39
|
```
|
|
30
40
|
|
|
31
41
|
## Details
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "402-announce",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Announce HTTP 402 services (L402 and x402) on Nostr for decentralised discovery. Kind 31402 parameterised replaceable events.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"LICENSE",
|
|
18
18
|
"README.md",
|
|
19
19
|
"llms.txt",
|
|
20
|
-
"llms-full.txt"
|
|
20
|
+
"llms-full.txt",
|
|
21
|
+
"context7.json"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "tsc",
|
|
@@ -62,5 +63,9 @@
|
|
|
62
63
|
},
|
|
63
64
|
"engines": {
|
|
64
65
|
"node": ">=18"
|
|
66
|
+
},
|
|
67
|
+
"funding": {
|
|
68
|
+
"type": "lightning",
|
|
69
|
+
"url": "lightning:thedonkey@strike.me"
|
|
65
70
|
}
|
|
66
71
|
}
|