ncc-05 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/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # ncc-05
2
+
3
+ Nostr Community Convention 05 - Identity-Bound Service Locator Resolution.
4
+
5
+ This library provides a simple way to publish and resolve identity-bound service endpoints (IP/Port/Onion) using Nostr `kind:30058` events.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install ncc-05
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Resolver
16
+
17
+ Resolve an identity-bound service locator for a given pubkey.
18
+
19
+ ```typescript
20
+ import { NCC05Resolver } from 'ncc-05';
21
+ import { nip19 } from 'nostr-tools';
22
+
23
+ const resolver = new NCC05Resolver();
24
+
25
+ // Your secret key is needed to decrypt the record (NIP-44)
26
+ const mySecretKey = nip19.decode('nsec...').data as Uint8Array;
27
+ const targetPubkey = '...';
28
+
29
+ const payload = await resolver.resolve(targetPubkey, mySecretKey);
30
+
31
+ if (payload) {
32
+ console.log('Resolved Endpoints:');
33
+ payload.endpoints.forEach(ep => {
34
+ console.log(`- ${ep.type}://${ep.uri} (${ep.family})`);
35
+ });
36
+ }
37
+ ```
38
+
39
+ ### Publisher
40
+
41
+ Publish your own service locator record.
42
+
43
+ ```typescript
44
+ import { NCC05Publisher, NCC05Payload } from 'ncc-05';
45
+
46
+ const publisher = new NCC05Publisher();
47
+ const mySecretKey = ...;
48
+
49
+ const payload: NCC05Payload = {
50
+ v: 1,
51
+ ttl: 600,
52
+ updated_at: Math.floor(Date.now() / 1000),
53
+ endpoints: [
54
+ {
55
+ type: 'tcp',
56
+ uri: '127.0.0.1:8080',
57
+ priority: 10,
58
+ family: 'ipv4'
59
+ }
60
+ ]
61
+ };
62
+
63
+ const relays = ['wss://relay.damus.io', 'wss://nos.lol'];
64
+ await publisher.publish(relays, mySecretKey, payload);
65
+ ```
66
+
67
+ ## Features
68
+
69
+ - **NIP-44 Encryption**: All locator records are encrypted by default.
70
+ - **NIP-01/NIP-33**: Uses standard Nostr primitives.
71
+ - **Identity-Centric**: Resolution is bound to a cryptographic identity (Pubkey).
72
+ - **Tor/Proxy Support**: Easily route relay traffic through SOCKS5 in Node.js.
73
+
74
+ ## Tor & Privacy (Node.js)
75
+
76
+ To resolve anonymously through Tor, you can use the `socks-proxy-agent` and a custom `WebSocket` implementation:
77
+
78
+ ```typescript
79
+ import { NCC05Resolver } from 'ncc-05';
80
+ import { SocksProxyAgent } from 'socks-proxy-agent';
81
+ import { WebSocket } from 'ws';
82
+
83
+ // Create a custom WebSocket class that uses the Tor agent
84
+ class TorWebSocket extends WebSocket {
85
+ constructor(address: string, protocols?: string | string[]) {
86
+ const agent = new SocksProxyAgent('socks5h://127.0.0.1:9050');
87
+ super(address, protocols, { agent });
88
+ }
89
+ }
90
+
91
+ const resolver = new NCC05Resolver({
92
+ websocketImplementation: TorWebSocket
93
+ });
94
+ ```
95
+
96
+ ## License
97
+
98
+ MIT
@@ -0,0 +1,46 @@
1
+ import { Event } from 'nostr-tools';
2
+ export interface NCC05Endpoint {
3
+ type: 'tcp' | 'udp' | string;
4
+ uri: string;
5
+ priority: number;
6
+ family: 'ipv4' | 'ipv6' | 'onion' | string;
7
+ }
8
+ export interface NCC05Payload {
9
+ v: number;
10
+ ttl: number;
11
+ updated_at: number;
12
+ endpoints: NCC05Endpoint[];
13
+ caps?: string[];
14
+ notes?: string;
15
+ }
16
+ export interface ResolverOptions {
17
+ bootstrapRelays?: string[];
18
+ timeout?: number;
19
+ websocketImplementation?: any;
20
+ }
21
+ export declare class NCC05Resolver {
22
+ private pool;
23
+ private bootstrapRelays;
24
+ private timeout;
25
+ constructor(options?: ResolverOptions);
26
+ /**
27
+ * Resolve a locator record for a given pubkey.
28
+ * Supports both hex and npub strings.
29
+ */
30
+ resolve(targetPubkey: string, secretKey: Uint8Array, identifier?: string, options?: {
31
+ strict?: boolean;
32
+ gossip?: boolean;
33
+ }): Promise<NCC05Payload | null>;
34
+ close(): void;
35
+ }
36
+ export declare class NCC05Publisher {
37
+ private pool;
38
+ constructor(options?: {
39
+ websocketImplementation?: any;
40
+ });
41
+ /**
42
+ * Create and publish a locator record.
43
+ */
44
+ publish(relays: string[], secretKey: Uint8Array, payload: NCC05Payload, identifier?: string): Promise<Event>;
45
+ close(relays: string[]): void;
46
+ }
package/dist/index.js ADDED
@@ -0,0 +1,121 @@
1
+ import { SimplePool, nip44, nip19, finalizeEvent, verifyEvent, getPublicKey } from 'nostr-tools';
2
+ export class NCC05Resolver {
3
+ pool;
4
+ bootstrapRelays;
5
+ timeout;
6
+ constructor(options = {}) {
7
+ this.pool = new SimplePool();
8
+ if (options.websocketImplementation) {
9
+ // @ts-ignore - Patching pool for custom WebSocket (Tor/Proxy)
10
+ this.pool.websocketImplementation = options.websocketImplementation;
11
+ }
12
+ this.bootstrapRelays = options.bootstrapRelays || ['wss://relay.damus.io', 'wss://nos.lol'];
13
+ this.timeout = options.timeout || 10000;
14
+ }
15
+ /**
16
+ * Resolve a locator record for a given pubkey.
17
+ * Supports both hex and npub strings.
18
+ */
19
+ async resolve(targetPubkey, secretKey, identifier = 'addr', options = {}) {
20
+ let hexPubkey = targetPubkey;
21
+ if (targetPubkey.startsWith('npub1')) {
22
+ const decoded = nip19.decode(targetPubkey);
23
+ hexPubkey = decoded.data;
24
+ }
25
+ let queryRelays = [...this.bootstrapRelays];
26
+ // 1. NIP-65 Gossip Discovery
27
+ if (options.gossip) {
28
+ const relayListEvent = await this.pool.get(this.bootstrapRelays, {
29
+ authors: [hexPubkey],
30
+ kinds: [10002]
31
+ });
32
+ if (relayListEvent) {
33
+ const discoveredRelays = relayListEvent.tags
34
+ .filter(t => t[0] === 'r')
35
+ .map(t => t[1]);
36
+ if (discoveredRelays.length > 0) {
37
+ queryRelays = [...new Set([...queryRelays, ...discoveredRelays])];
38
+ }
39
+ }
40
+ }
41
+ const filter = {
42
+ authors: [hexPubkey],
43
+ kinds: [30058],
44
+ '#d': [identifier],
45
+ limit: 10
46
+ };
47
+ const queryPromise = this.pool.querySync(queryRelays, filter);
48
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), this.timeout));
49
+ const result = await Promise.race([queryPromise, timeoutPromise]);
50
+ if (!result || (Array.isArray(result) && result.length === 0))
51
+ return null;
52
+ // 2. Filter for valid signatures and sort by created_at
53
+ const validEvents = result
54
+ .filter(e => verifyEvent(e))
55
+ .sort((a, b) => b.created_at - a.created_at);
56
+ if (validEvents.length === 0)
57
+ return null;
58
+ const latestEvent = validEvents[0];
59
+ // 2. Decrypt
60
+ try {
61
+ const conversationKey = nip44.getConversationKey(secretKey, hexPubkey);
62
+ const decrypted = nip44.decrypt(latestEvent.content, conversationKey);
63
+ const payload = JSON.parse(decrypted);
64
+ // 3. Basic Validation
65
+ if (!payload.endpoints || !Array.isArray(payload.endpoints)) {
66
+ console.error('Invalid NCC-05 payload structure');
67
+ return null;
68
+ }
69
+ // 4. Freshness check
70
+ const now = Math.floor(Date.now() / 1000);
71
+ if (now > payload.updated_at + payload.ttl) {
72
+ if (options.strict) {
73
+ console.warn('Rejecting expired NCC-05 record (strict mode)');
74
+ return null;
75
+ }
76
+ console.warn('NCC-05 record has expired');
77
+ }
78
+ return payload;
79
+ }
80
+ catch (e) {
81
+ console.error('Failed to decrypt or parse NCC-05 record:', e);
82
+ return null;
83
+ }
84
+ }
85
+ close() {
86
+ this.pool.close(this.bootstrapRelays);
87
+ }
88
+ }
89
+ export class NCC05Publisher {
90
+ pool;
91
+ constructor(options = {}) {
92
+ this.pool = new SimplePool();
93
+ if (options.websocketImplementation) {
94
+ // @ts-ignore - Patching pool for custom WebSocket (Tor/Proxy)
95
+ this.pool.websocketImplementation = options.websocketImplementation;
96
+ }
97
+ }
98
+ /**
99
+ * Create and publish a locator record.
100
+ */
101
+ async publish(relays, secretKey, payload, identifier = 'addr') {
102
+ const pubkey = getPublicKey(secretKey);
103
+ // 1. Encrypt
104
+ const conversationKey = nip44.getConversationKey(secretKey, pubkey);
105
+ const encryptedContent = nip44.encrypt(JSON.stringify(payload), conversationKey);
106
+ // 2. Create and Finalize Event
107
+ const eventTemplate = {
108
+ kind: 30058,
109
+ created_at: Math.floor(Date.now() / 1000),
110
+ tags: [['d', identifier]],
111
+ content: encryptedContent,
112
+ };
113
+ const signedEvent = finalizeEvent(eventTemplate, secretKey);
114
+ // 3. Publish
115
+ await Promise.all(this.pool.publish(relays, signedEvent));
116
+ return signedEvent;
117
+ }
118
+ close(relays) {
119
+ this.pool.close(relays);
120
+ }
121
+ }
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,80 @@
1
+ import { NCC05Publisher, NCC05Resolver } from './index.js';
2
+ import { generateSecretKey, getPublicKey } from 'nostr-tools';
3
+ async function test() {
4
+ const sk = generateSecretKey();
5
+ const pk = getPublicKey(sk);
6
+ const relays = ['wss://relay.damus.io'];
7
+ const publisher = new NCC05Publisher();
8
+ const resolver = new NCC05Resolver({ bootstrapRelays: relays });
9
+ const payload = {
10
+ v: 1,
11
+ ttl: 60,
12
+ updated_at: Math.floor(Date.now() / 1000),
13
+ endpoints: [
14
+ { type: 'tcp', uri: '127.0.0.1:9000', priority: 1, family: 'ipv4' }
15
+ ]
16
+ };
17
+ console.log('Publishing...');
18
+ await publisher.publish(relays, sk, payload);
19
+ console.log('Published.');
20
+ console.log('Resolving...');
21
+ const resolved = await resolver.resolve(pk, sk);
22
+ if (resolved) {
23
+ console.log('Successfully resolved:', JSON.stringify(resolved, null, 2));
24
+ }
25
+ else {
26
+ console.log('Failed to resolve.');
27
+ }
28
+ // Test Strict Mode with Expired Record
29
+ console.log('Testing expired record in strict mode...');
30
+ const expiredPayload = {
31
+ v: 1,
32
+ ttl: 1,
33
+ updated_at: Math.floor(Date.now() / 1000) - 10, // 10s ago
34
+ endpoints: [{ type: 'tcp', uri: '1.1.1.1:1', priority: 1, family: 'ipv4' }]
35
+ };
36
+ await publisher.publish(relays, sk, expiredPayload, 'expired-test');
37
+ const strictResult = await resolver.resolve(pk, sk, 'expired-test', { strict: true });
38
+ if (strictResult === null) {
39
+ console.log('Correctly rejected expired record in strict mode.');
40
+ }
41
+ else {
42
+ console.error('FAILED: Strict mode allowed an expired record.');
43
+ process.exit(1);
44
+ }
45
+ // Test Gossip Mode
46
+ console.log('Testing Gossip discovery...');
47
+ // In this test, we just point kind:10002 to the same relay we are using
48
+ // to verify the code path executes.
49
+ const relayListTemplate = {
50
+ kind: 10002,
51
+ created_at: Math.floor(Date.now() / 1000),
52
+ tags: [['r', relays[0]]],
53
+ content: '',
54
+ };
55
+ const signedRL = (await import('nostr-tools')).finalizeEvent(relayListTemplate, sk);
56
+ await Promise.all(publisher['pool'].publish(relays, signedRL));
57
+ const gossipResult = await resolver.resolve(pk, sk, 'addr', { gossip: true });
58
+ if (gossipResult) {
59
+ console.log('Gossip discovery successful.');
60
+ }
61
+ else {
62
+ console.error('FAILED: Gossip discovery did not find record.');
63
+ process.exit(1);
64
+ }
65
+ // Test npub resolution
66
+ console.log('Testing npub resolution...');
67
+ const npub = (await import('nostr-tools')).nip19.npubEncode(pk);
68
+ const npubResult = await resolver.resolve(npub, sk);
69
+ if (npubResult) {
70
+ console.log('npub resolution successful.');
71
+ }
72
+ else {
73
+ console.error('FAILED: npub resolution did not find record.');
74
+ process.exit(1);
75
+ }
76
+ publisher.close(relays);
77
+ resolver.close();
78
+ process.exit(0);
79
+ }
80
+ test().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "ncc-05",
3
+ "version": "1.0.0",
4
+ "description": "Nostr Community Convention 05 - Identity-Bound Service Locator Resolution",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": ["nostr", "dns", "identity", "resolution", "privacy"],
13
+ "author": "lostcause",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "nostr-tools": "^2.10.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.0.0",
20
+ "@types/node": "^20.0.0"
21
+ }
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,186 @@
1
+ import {
2
+ SimplePool,
3
+ nip44,
4
+ nip19,
5
+ finalizeEvent,
6
+ verifyEvent,
7
+ Event,
8
+ getPublicKey
9
+ } from 'nostr-tools';
10
+
11
+ export interface NCC05Endpoint {
12
+ type: 'tcp' | 'udp' | string;
13
+ uri: string;
14
+ priority: number;
15
+ family: 'ipv4' | 'ipv6' | 'onion' | string;
16
+ }
17
+
18
+ export interface NCC05Payload {
19
+ v: number;
20
+ ttl: number;
21
+ updated_at: number;
22
+ endpoints: NCC05Endpoint[];
23
+ caps?: string[];
24
+ notes?: string;
25
+ }
26
+
27
+ export interface ResolverOptions {
28
+ bootstrapRelays?: string[];
29
+ timeout?: number;
30
+ websocketImplementation?: any; // To support Tor/SOCKS5 proxies in Node.js
31
+ }
32
+
33
+ export class NCC05Resolver {
34
+ private pool: SimplePool;
35
+ private bootstrapRelays: string[];
36
+ private timeout: number;
37
+
38
+ constructor(options: ResolverOptions = {}) {
39
+ this.pool = new SimplePool();
40
+ if (options.websocketImplementation) {
41
+ // @ts-ignore - Patching pool for custom WebSocket (Tor/Proxy)
42
+ this.pool.websocketImplementation = options.websocketImplementation;
43
+ }
44
+ this.bootstrapRelays = options.bootstrapRelays || ['wss://relay.damus.io', 'wss://nos.lol'];
45
+ this.timeout = options.timeout || 10000;
46
+ }
47
+
48
+ /**
49
+ * Resolve a locator record for a given pubkey.
50
+ * Supports both hex and npub strings.
51
+ */
52
+ async resolve(
53
+ targetPubkey: string,
54
+ secretKey: Uint8Array,
55
+ identifier: string = 'addr',
56
+ options: { strict?: boolean, gossip?: boolean } = {}
57
+ ): Promise<NCC05Payload | null> {
58
+ let hexPubkey = targetPubkey;
59
+ if (targetPubkey.startsWith('npub1')) {
60
+ const decoded = nip19.decode(targetPubkey);
61
+ hexPubkey = decoded.data as string;
62
+ }
63
+
64
+ let queryRelays = [...this.bootstrapRelays];
65
+
66
+ // 1. NIP-65 Gossip Discovery
67
+ if (options.gossip) {
68
+ const relayListEvent = await this.pool.get(this.bootstrapRelays, {
69
+ authors: [hexPubkey],
70
+ kinds: [10002]
71
+ });
72
+
73
+ if (relayListEvent) {
74
+ const discoveredRelays = relayListEvent.tags
75
+ .filter(t => t[0] === 'r')
76
+ .map(t => t[1]);
77
+ if (discoveredRelays.length > 0) {
78
+ queryRelays = [...new Set([...queryRelays, ...discoveredRelays])];
79
+ }
80
+ }
81
+ }
82
+
83
+ const filter = {
84
+ authors: [hexPubkey],
85
+ kinds: [30058],
86
+ '#d': [identifier],
87
+ limit: 10
88
+ };
89
+
90
+ const queryPromise = this.pool.querySync(queryRelays, filter);
91
+ const timeoutPromise = new Promise<null>((resolve) =>
92
+ setTimeout(() => resolve(null), this.timeout)
93
+ );
94
+
95
+ const result = await Promise.race([queryPromise, timeoutPromise]);
96
+
97
+ if (!result || (Array.isArray(result) && result.length === 0)) return null;
98
+
99
+ // 2. Filter for valid signatures and sort by created_at
100
+ const validEvents = (result as Event[])
101
+ .filter(e => verifyEvent(e))
102
+ .sort((a, b) => b.created_at - a.created_at);
103
+
104
+ if (validEvents.length === 0) return null;
105
+ const latestEvent = validEvents[0];
106
+
107
+ // 2. Decrypt
108
+ try {
109
+ const conversationKey = nip44.getConversationKey(secretKey, hexPubkey);
110
+ const decrypted = nip44.decrypt(latestEvent.content, conversationKey);
111
+ const payload = JSON.parse(decrypted) as NCC05Payload;
112
+
113
+ // 3. Basic Validation
114
+ if (!payload.endpoints || !Array.isArray(payload.endpoints)) {
115
+ console.error('Invalid NCC-05 payload structure');
116
+ return null;
117
+ }
118
+
119
+ // 4. Freshness check
120
+ const now = Math.floor(Date.now() / 1000);
121
+ if (now > payload.updated_at + payload.ttl) {
122
+ if (options.strict) {
123
+ console.warn('Rejecting expired NCC-05 record (strict mode)');
124
+ return null;
125
+ }
126
+ console.warn('NCC-05 record has expired');
127
+ }
128
+
129
+ return payload;
130
+ } catch (e) {
131
+ console.error('Failed to decrypt or parse NCC-05 record:', e);
132
+ return null;
133
+ }
134
+ }
135
+
136
+ close() {
137
+ this.pool.close(this.bootstrapRelays);
138
+ }
139
+ }
140
+
141
+ export class NCC05Publisher {
142
+ private pool: SimplePool;
143
+
144
+ constructor(options: { websocketImplementation?: any } = {}) {
145
+ this.pool = new SimplePool();
146
+ if (options.websocketImplementation) {
147
+ // @ts-ignore - Patching pool for custom WebSocket (Tor/Proxy)
148
+ this.pool.websocketImplementation = options.websocketImplementation;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Create and publish a locator record.
154
+ */
155
+ async publish(
156
+ relays: string[],
157
+ secretKey: Uint8Array,
158
+ payload: NCC05Payload,
159
+ identifier: string = 'addr'
160
+ ): Promise<Event> {
161
+ const pubkey = getPublicKey(secretKey);
162
+
163
+ // 1. Encrypt
164
+ const conversationKey = nip44.getConversationKey(secretKey, pubkey);
165
+ const encryptedContent = nip44.encrypt(JSON.stringify(payload), conversationKey);
166
+
167
+ // 2. Create and Finalize Event
168
+ const eventTemplate = {
169
+ kind: 30058,
170
+ created_at: Math.floor(Date.now() / 1000),
171
+ tags: [['d', identifier]],
172
+ content: encryptedContent,
173
+ };
174
+
175
+ const signedEvent = finalizeEvent(eventTemplate, secretKey);
176
+
177
+ // 3. Publish
178
+ await Promise.all(this.pool.publish(relays, signedEvent));
179
+
180
+ return signedEvent;
181
+ }
182
+
183
+ close(relays: string[]) {
184
+ this.pool.close(relays);
185
+ }
186
+ }
package/src/test.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { NCC05Publisher, NCC05Resolver, NCC05Payload } from './index.js';
2
+ import { generateSecretKey, getPublicKey } from 'nostr-tools';
3
+
4
+ async function test() {
5
+ const sk = generateSecretKey();
6
+ const pk = getPublicKey(sk);
7
+ const relays = ['wss://relay.damus.io'];
8
+
9
+ const publisher = new NCC05Publisher();
10
+ const resolver = new NCC05Resolver({ bootstrapRelays: relays });
11
+
12
+ const payload: NCC05Payload = {
13
+ v: 1,
14
+ ttl: 60,
15
+ updated_at: Math.floor(Date.now() / 1000),
16
+ endpoints: [
17
+ { type: 'tcp', uri: '127.0.0.1:9000', priority: 1, family: 'ipv4' }
18
+ ]
19
+ };
20
+
21
+ console.log('Publishing...');
22
+ await publisher.publish(relays, sk, payload);
23
+ console.log('Published.');
24
+
25
+ console.log('Resolving...');
26
+ const resolved = await resolver.resolve(pk, sk);
27
+
28
+ if (resolved) {
29
+ console.log('Successfully resolved:', JSON.stringify(resolved, null, 2));
30
+ } else {
31
+ console.log('Failed to resolve.');
32
+ }
33
+
34
+ // Test Strict Mode with Expired Record
35
+ console.log('Testing expired record in strict mode...');
36
+ const expiredPayload: NCC05Payload = {
37
+ v: 1,
38
+ ttl: 1,
39
+ updated_at: Math.floor(Date.now() / 1000) - 10, // 10s ago
40
+ endpoints: [{ type: 'tcp', uri: '1.1.1.1:1', priority: 1, family: 'ipv4' }]
41
+ };
42
+ await publisher.publish(relays, sk, expiredPayload, 'expired-test');
43
+ const strictResult = await resolver.resolve(pk, sk, 'expired-test', { strict: true });
44
+
45
+ if (strictResult === null) {
46
+ console.log('Correctly rejected expired record in strict mode.');
47
+ } else {
48
+ console.error('FAILED: Strict mode allowed an expired record.');
49
+ process.exit(1);
50
+ }
51
+
52
+ // Test Gossip Mode
53
+ console.log('Testing Gossip discovery...');
54
+ // In this test, we just point kind:10002 to the same relay we are using
55
+ // to verify the code path executes.
56
+ const relayListTemplate = {
57
+ kind: 10002,
58
+ created_at: Math.floor(Date.now() / 1000),
59
+ tags: [['r', relays[0]]],
60
+ content: '',
61
+ };
62
+ const signedRL = (await import('nostr-tools')).finalizeEvent(relayListTemplate, sk);
63
+ await Promise.all(publisher['pool'].publish(relays, signedRL));
64
+
65
+ const gossipResult = await resolver.resolve(pk, sk, 'addr', { gossip: true });
66
+ if (gossipResult) {
67
+ console.log('Gossip discovery successful.');
68
+ } else {
69
+ console.error('FAILED: Gossip discovery did not find record.');
70
+ process.exit(1);
71
+ }
72
+
73
+ // Test npub resolution
74
+ console.log('Testing npub resolution...');
75
+ const npub = (await import('nostr-tools')).nip19.npubEncode(pk);
76
+ const npubResult = await resolver.resolve(npub, sk);
77
+ if (npubResult) {
78
+ console.log('npub resolution successful.');
79
+ } else {
80
+ console.error('FAILED: npub resolution did not find record.');
81
+ process.exit(1);
82
+ }
83
+
84
+ publisher.close(relays);
85
+ resolver.close();
86
+ process.exit(0);
87
+ }
88
+
89
+ test().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }