402-announce 0.0.1 → 1.0.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/LICENSE +21 -0
- package/README.md +97 -0
- package/build/announce.d.ts +13 -0
- package/build/announce.js +70 -0
- package/build/announce.js.map +1 -0
- package/build/event.d.ts +12 -0
- package/build/event.js +58 -0
- package/build/event.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/types.d.ts +52 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/build/utils.d.ts +1 -0
- package/build/utils.js +8 -0
- package/build/utils.js.map +1 -0
- package/package.json +51 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Crypto Donkey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 402-announce
|
|
2
|
+
|
|
3
|
+
Announce HTTP 402 services on Nostr for decentralised discovery. Supports both L402 and x402 payment protocols.
|
|
4
|
+
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
Publishes **kind 31402** parameterised replaceable events so that AI agents (and any Nostr client) can discover paid APIs without a central registry.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install 402-announce
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { announceService } from '402-announce'
|
|
17
|
+
|
|
18
|
+
const handle = await announceService({
|
|
19
|
+
secretKey: '64-char-hex-nostr-secret-key',
|
|
20
|
+
relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
|
|
21
|
+
identifier: 'jokes-api',
|
|
22
|
+
name: 'Jokes API',
|
|
23
|
+
url: 'https://jokes.example.com',
|
|
24
|
+
about: 'A joke-telling service behind an L402 paywall',
|
|
25
|
+
pricing: [
|
|
26
|
+
{ capability: 'get_joke', price: 1, currency: 'sats' },
|
|
27
|
+
],
|
|
28
|
+
paymentMethods: ['bitcoin-lightning-bolt11', 'bitcoin-cashu'],
|
|
29
|
+
topics: ['comedy', 'ai'],
|
|
30
|
+
capabilities: [
|
|
31
|
+
{ name: 'get_joke', description: 'Returns a random joke' },
|
|
32
|
+
],
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
console.log('Published event:', handle.eventId)
|
|
37
|
+
console.log('From pubkey:', handle.pubkey)
|
|
38
|
+
|
|
39
|
+
// Later, when shutting down:
|
|
40
|
+
handle.close()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Event format
|
|
44
|
+
|
|
45
|
+
Each announcement is a **kind 31402** parameterised replaceable event. The combination of `pubkey` + `d` tag uniquely identifies a listing -- publishing again with the same values updates the existing listing.
|
|
46
|
+
|
|
47
|
+
### Tags
|
|
48
|
+
|
|
49
|
+
| Tag | Description | Example |
|
|
50
|
+
|-----------|----------------------------------------------|-----------------------------------|
|
|
51
|
+
| `d` | Unique identifier for this listing | `jokes-api` |
|
|
52
|
+
| `name` | Human-readable service name | `Jokes API` |
|
|
53
|
+
| `url` | HTTP endpoint for the 402 service | `https://jokes.example.com` |
|
|
54
|
+
| `about` | Short description | `A joke-telling service` |
|
|
55
|
+
| `pmi` | Payment method identifier (repeatable) | `bitcoin-lightning-bolt11` |
|
|
56
|
+
| `price` | Capability pricing (repeatable) | `get_joke`, `1`, `sats` |
|
|
57
|
+
| `t` | Topic tag for search/filtering (repeatable) | `comedy` |
|
|
58
|
+
| `picture` | Optional icon URL | `https://example.com/icon.png` |
|
|
59
|
+
|
|
60
|
+
### Content
|
|
61
|
+
|
|
62
|
+
The event content is a JSON object with optional fields:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"capabilities": [
|
|
67
|
+
{ "name": "get_joke", "description": "Returns a random joke" }
|
|
68
|
+
],
|
|
69
|
+
"version": "1.0.0"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## What it does
|
|
74
|
+
|
|
75
|
+
- Builds and signs kind 31402 Nostr events
|
|
76
|
+
- Publishes to one or more Nostr relays
|
|
77
|
+
- Zeroises secret key bytes after use
|
|
78
|
+
- Degrades gracefully when individual relays fail
|
|
79
|
+
- Provides a `close()` handle for clean disconnection
|
|
80
|
+
|
|
81
|
+
## What it does not do
|
|
82
|
+
|
|
83
|
+
- Does not run an L402 paywall (use [toll-booth](https://github.com/TheCryptoDonkey/toll-booth) for that)
|
|
84
|
+
- Does not subscribe to or search for announcements (use [l402-mcp](https://github.com/TheCryptoDonkey/l402-mcp) for that)
|
|
85
|
+
- Does not handle payments or token verification
|
|
86
|
+
|
|
87
|
+
## Ecosystem
|
|
88
|
+
|
|
89
|
+
| Package | Purpose |
|
|
90
|
+
|---------|---------|
|
|
91
|
+
| [toll-booth](https://github.com/TheCryptoDonkey/toll-booth) | L402 middleware -- any API becomes a toll booth in minutes |
|
|
92
|
+
| [satgate](https://github.com/TheCryptoDonkey/satsgate) | Production L402 gateway with Lightning and Cashu support |
|
|
93
|
+
| [l402-mcp](https://github.com/TheCryptoDonkey/l402-mcp) | MCP server for AI agents to discover, pay, and consume L402 APIs |
|
|
94
|
+
|
|
95
|
+
## Licence
|
|
96
|
+
|
|
97
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AnnounceConfig, Announcement } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Publish a kind 31402 L402 service announcement to Nostr relays.
|
|
4
|
+
*
|
|
5
|
+
* Connects to each relay, publishes the signed event, and returns an
|
|
6
|
+
* {@link Announcement} handle whose `close()` method disconnects all relays.
|
|
7
|
+
*
|
|
8
|
+
* Relay failures are logged but do not reject the promise -- the function
|
|
9
|
+
* degrades gracefully. A warning is emitted if *no* relay accepted the event.
|
|
10
|
+
*
|
|
11
|
+
* @throws If the relay list is empty or contains invalid URLs.
|
|
12
|
+
*/
|
|
13
|
+
export declare function announceService(config: AnnounceConfig): Promise<Announcement>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getPublicKey } from 'nostr-tools/pure';
|
|
2
|
+
import { Relay } from 'nostr-tools/relay';
|
|
3
|
+
import { buildAnnounceEvent } from './event.js';
|
|
4
|
+
import { hexToBytes } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Publish a kind 31402 L402 service announcement to Nostr relays.
|
|
7
|
+
*
|
|
8
|
+
* Connects to each relay, publishes the signed event, and returns an
|
|
9
|
+
* {@link Announcement} handle whose `close()` method disconnects all relays.
|
|
10
|
+
*
|
|
11
|
+
* Relay failures are logged but do not reject the promise -- the function
|
|
12
|
+
* degrades gracefully. A warning is emitted if *no* relay accepted the event.
|
|
13
|
+
*
|
|
14
|
+
* @throws If the relay list is empty or contains invalid URLs.
|
|
15
|
+
*/
|
|
16
|
+
export async function announceService(config) {
|
|
17
|
+
const { relays, secretKey } = config;
|
|
18
|
+
if (!/^[0-9a-f]{64}$/i.test(secretKey)) {
|
|
19
|
+
throw new Error('secretKey must be a 64-character hex string');
|
|
20
|
+
}
|
|
21
|
+
if (relays.length === 0) {
|
|
22
|
+
throw new Error('At least one relay URL is required');
|
|
23
|
+
}
|
|
24
|
+
for (const url of relays) {
|
|
25
|
+
if (!/^wss?:\/\//i.test(url)) {
|
|
26
|
+
throw new Error(`Invalid relay URL: ${url} — must start with wss:// or ws://`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Derive pubkey (zeroises sk bytes internally)
|
|
30
|
+
const skBytes = hexToBytes(secretKey);
|
|
31
|
+
const pubkey = getPublicKey(skBytes);
|
|
32
|
+
skBytes.fill(0);
|
|
33
|
+
// Build and sign the event
|
|
34
|
+
const event = buildAnnounceEvent(secretKey, config);
|
|
35
|
+
// Connect to relays in parallel and publish
|
|
36
|
+
const connectedRelays = [];
|
|
37
|
+
let accepted = 0;
|
|
38
|
+
const results = await Promise.allSettled(relays.map(async (url) => {
|
|
39
|
+
const relay = await Promise.race([
|
|
40
|
+
Relay.connect(url),
|
|
41
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Relay connection timeout: ${url}`)), 10_000)),
|
|
42
|
+
]);
|
|
43
|
+
connectedRelays.push(relay);
|
|
44
|
+
await relay.publish(event);
|
|
45
|
+
accepted++;
|
|
46
|
+
}));
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
if (result.status === 'rejected') {
|
|
49
|
+
console.warn(`[402-announce] Failed to publish:`, result.reason);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (accepted === 0) {
|
|
53
|
+
console.warn('[402-announce] No relays accepted the event');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
eventId: event.id,
|
|
57
|
+
pubkey,
|
|
58
|
+
close() {
|
|
59
|
+
for (const relay of connectedRelays) {
|
|
60
|
+
try {
|
|
61
|
+
relay.close();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Ignore close errors
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=announce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announce.js","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAsB;IAC1D,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAA;IAEpC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACvD,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,oCAAoC,CAAC,CAAA;QAChF,CAAC;IACH,CAAC;IAED,+CAA+C;IAC/C,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAEf,2BAA2B;IAC3B,MAAM,KAAK,GAAG,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAEnD,4CAA4C;IAC5C,MAAM,eAAe,GAAiC,EAAE,CAAA;IACxD,IAAI,QAAQ,GAAG,CAAC,CAAA;IAEhB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC/B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAClB,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAChF;SACF,CAAC,CAAA;QACF,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3B,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC1B,QAAQ,EAAE,CAAA;IACZ,CAAC,CAAC,CACH,CAAA;IAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAA;IAC7D,CAAC;IAED,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,MAAM;QACN,KAAK;YACH,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;gBACpC,IAAI,CAAC;oBACH,KAAK,CAAC,KAAK,EAAE,CAAA;gBACf,CAAC;gBAAC,MAAM,CAAC;oBACP,sBAAsB;gBACxB,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/build/event.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AnnounceConfig } from './types.js';
|
|
2
|
+
import type { VerifiedEvent } from 'nostr-tools/pure';
|
|
3
|
+
/**
|
|
4
|
+
* Build and sign a kind 31402 Nostr event announcing an L402 service.
|
|
5
|
+
*
|
|
6
|
+
* The secret key bytes are zeroised after signing.
|
|
7
|
+
*
|
|
8
|
+
* @param secretKeyHex - 64-character hex-encoded Nostr secret key
|
|
9
|
+
* @param config - Service announcement configuration
|
|
10
|
+
* @returns Signed Nostr event ready for relay publication
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildAnnounceEvent(secretKeyHex: string, config: Omit<AnnounceConfig, 'relays'>): VerifiedEvent;
|
package/build/event.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { finalizeEvent } from 'nostr-tools/pure';
|
|
2
|
+
import { L402_ANNOUNCE_KIND } from './types.js';
|
|
3
|
+
import { hexToBytes } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build and sign a kind 31402 Nostr event announcing an L402 service.
|
|
6
|
+
*
|
|
7
|
+
* The secret key bytes are zeroised after signing.
|
|
8
|
+
*
|
|
9
|
+
* @param secretKeyHex - 64-character hex-encoded Nostr secret key
|
|
10
|
+
* @param config - Service announcement configuration
|
|
11
|
+
* @returns Signed Nostr event ready for relay publication
|
|
12
|
+
*/
|
|
13
|
+
export function buildAnnounceEvent(secretKeyHex, config) {
|
|
14
|
+
if (!/^[0-9a-f]{64}$/i.test(secretKeyHex)) {
|
|
15
|
+
throw new Error('secretKey must be a 64-character hex string');
|
|
16
|
+
}
|
|
17
|
+
const sk = hexToBytes(secretKeyHex);
|
|
18
|
+
try {
|
|
19
|
+
const tags = [
|
|
20
|
+
['d', config.identifier],
|
|
21
|
+
['name', config.name],
|
|
22
|
+
['url', config.url],
|
|
23
|
+
['about', config.about],
|
|
24
|
+
];
|
|
25
|
+
if (config.picture) {
|
|
26
|
+
tags.push(['picture', config.picture]);
|
|
27
|
+
}
|
|
28
|
+
for (const pm of config.paymentMethods) {
|
|
29
|
+
tags.push(['pmi', pm]);
|
|
30
|
+
}
|
|
31
|
+
for (const p of config.pricing) {
|
|
32
|
+
tags.push(['price', p.capability, String(p.price), p.currency]);
|
|
33
|
+
}
|
|
34
|
+
if (config.topics) {
|
|
35
|
+
for (const topic of config.topics) {
|
|
36
|
+
tags.push(['t', topic]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const contentObj = {};
|
|
40
|
+
if (config.capabilities) {
|
|
41
|
+
contentObj.capabilities = config.capabilities;
|
|
42
|
+
}
|
|
43
|
+
if (config.version) {
|
|
44
|
+
contentObj.version = config.version;
|
|
45
|
+
}
|
|
46
|
+
const event = finalizeEvent({
|
|
47
|
+
kind: L402_ANNOUNCE_KIND,
|
|
48
|
+
tags,
|
|
49
|
+
content: JSON.stringify(contentObj),
|
|
50
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
51
|
+
}, sk);
|
|
52
|
+
return event;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
sk.fill(0);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAIvC;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,YAAoB,EACpB,MAAsC;IAEtC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,MAAM,EAAE,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,IAAI,GAAe;YACvB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;YACxB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;YACrB,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;YACnB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;SACxB,CAAA;QAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;QACxC,CAAC;QAED,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAA;QACxB,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;QACjE,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAA4B,EAAE,CAAA;QAC9C,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,UAAU,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAA;QAC/C,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QACrC,CAAC;QAED,MAAM,KAAK,GAAG,aAAa,CACzB;YACE,IAAI,EAAE,kBAAkB;YACxB,IAAI;YACJ,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;YACnC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;SAC1C,EACD,EAAE,CACH,CAAA;QAED,OAAO,KAAK,CAAA;IACd,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACZ,CAAC;AACH,CAAC"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Pricing for a specific capability. */
|
|
2
|
+
export interface PricingDef {
|
|
3
|
+
/** Capability name (e.g. 'chat', 'get_joke', 'route') */
|
|
4
|
+
capability: string;
|
|
5
|
+
/** Price amount */
|
|
6
|
+
price: number;
|
|
7
|
+
/** Currency unit (e.g. 'sats') */
|
|
8
|
+
currency: string;
|
|
9
|
+
}
|
|
10
|
+
/** Capability with description (optional extended metadata). */
|
|
11
|
+
export interface CapabilityDef {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
}
|
|
15
|
+
/** Configuration for announceService(). */
|
|
16
|
+
export interface AnnounceConfig {
|
|
17
|
+
/** Nostr secret key (64-char hex) for signing events */
|
|
18
|
+
secretKey: string;
|
|
19
|
+
/** Nostr relay URLs to publish to (wss:// or ws://) */
|
|
20
|
+
relays: string[];
|
|
21
|
+
/** Unique identifier for this service listing (d tag). Same pubkey + identifier = same listing. */
|
|
22
|
+
identifier: string;
|
|
23
|
+
/** Human-readable service name */
|
|
24
|
+
name: string;
|
|
25
|
+
/** HTTP URL where the L402 service is accessible */
|
|
26
|
+
url: string;
|
|
27
|
+
/** Short description of what the service does */
|
|
28
|
+
about: string;
|
|
29
|
+
/** Optional icon URL */
|
|
30
|
+
picture?: string;
|
|
31
|
+
/** Pricing for capabilities */
|
|
32
|
+
pricing: PricingDef[];
|
|
33
|
+
/** Accepted payment methods (e.g. ['bitcoin-lightning-bolt11', 'bitcoin-cashu']) */
|
|
34
|
+
paymentMethods: string[];
|
|
35
|
+
/** Optional topic tags for search/filtering (e.g. ['ai', 'inference']) */
|
|
36
|
+
topics?: string[];
|
|
37
|
+
/** Optional capability details (goes in content field) */
|
|
38
|
+
capabilities?: CapabilityDef[];
|
|
39
|
+
/** Optional service version (goes in content field) */
|
|
40
|
+
version?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Handle returned by announceService() for cleanup. */
|
|
43
|
+
export interface Announcement {
|
|
44
|
+
/** Published event ID */
|
|
45
|
+
eventId: string;
|
|
46
|
+
/** Nostr pubkey derived from the secret key */
|
|
47
|
+
pubkey: string;
|
|
48
|
+
/** Close relay connections. Synchronous. */
|
|
49
|
+
close(): void;
|
|
50
|
+
}
|
|
51
|
+
/** The Nostr event kind used for L402 service announcements. */
|
|
52
|
+
export declare const L402_ANNOUNCE_KIND = 31402;
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAsDA,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAA"}
|
package/build/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
package/build/utils.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "402-announce",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Announce HTTP 402 services (L402 and x402) on Nostr for decentralised discovery. Kind 31402 parameterised replaceable events.",
|
|
5
6
|
"license": "MIT",
|
|
7
|
+
"main": "./build/index.js",
|
|
8
|
+
"types": "./build/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./build/index.js",
|
|
12
|
+
"types": "./build/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"build",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"prepublishOnly": "npm run typecheck && npm run build && npm test"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"nostr-tools": "^2.12.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
31
|
+
"@semantic-release/git": "^10.0.1",
|
|
32
|
+
"@semantic-release/github": "^12.0.6",
|
|
33
|
+
"@types/node": "^25.5.0",
|
|
34
|
+
"semantic-release": "^25.0.3",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"vitest": "^4.0.0"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"l402",
|
|
40
|
+
"x402",
|
|
41
|
+
"402",
|
|
42
|
+
"nostr",
|
|
43
|
+
"discovery",
|
|
44
|
+
"lightning",
|
|
45
|
+
"cashu",
|
|
46
|
+
"mcp",
|
|
47
|
+
"ai-agents",
|
|
48
|
+
"machine-payments",
|
|
49
|
+
"http-402"
|
|
50
|
+
],
|
|
6
51
|
"repository": {
|
|
7
52
|
"type": "git",
|
|
8
|
-
"url": "
|
|
53
|
+
"url": "https://github.com/TheCryptoDonkey/402-announce.git"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18"
|
|
9
57
|
}
|
|
10
58
|
}
|