nostr-bray 0.0.0-development

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/dist/cli.d.ts +20 -0
  4. package/dist/cli.js +229 -0
  5. package/dist/config.d.ts +5 -0
  6. package/dist/config.js +87 -0
  7. package/dist/context.d.ts +35 -0
  8. package/dist/context.js +184 -0
  9. package/dist/identity/handlers.d.ts +31 -0
  10. package/dist/identity/handlers.js +32 -0
  11. package/dist/identity/migration.d.ts +37 -0
  12. package/dist/identity/migration.js +84 -0
  13. package/dist/identity/shamir.d.ts +18 -0
  14. package/dist/identity/shamir.js +46 -0
  15. package/dist/identity/tools.d.ts +11 -0
  16. package/dist/identity/tools.js +168 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.js +125 -0
  19. package/dist/nip65.d.ts +15 -0
  20. package/dist/nip65.js +71 -0
  21. package/dist/relay/handlers.d.ts +36 -0
  22. package/dist/relay/handlers.js +96 -0
  23. package/dist/relay/tools.d.ts +3 -0
  24. package/dist/relay/tools.js +60 -0
  25. package/dist/relay-pool.d.ts +42 -0
  26. package/dist/relay-pool.js +150 -0
  27. package/dist/safety/handlers.d.ts +15 -0
  28. package/dist/safety/handlers.js +12 -0
  29. package/dist/safety/tools.d.ts +3 -0
  30. package/dist/safety/tools.js +29 -0
  31. package/dist/social/dm.d.ts +28 -0
  32. package/dist/social/dm.js +90 -0
  33. package/dist/social/handlers.d.ts +42 -0
  34. package/dist/social/handlers.js +107 -0
  35. package/dist/social/notifications.d.ts +32 -0
  36. package/dist/social/notifications.js +74 -0
  37. package/dist/social/tools.d.ts +3 -0
  38. package/dist/social/tools.js +168 -0
  39. package/dist/trust/handlers.d.ts +65 -0
  40. package/dist/trust/handlers.js +133 -0
  41. package/dist/trust/ring.d.ts +19 -0
  42. package/dist/trust/ring.js +54 -0
  43. package/dist/trust/spoken.d.ts +33 -0
  44. package/dist/trust/spoken.js +14 -0
  45. package/dist/trust/tools.d.ts +3 -0
  46. package/dist/trust/tools.js +182 -0
  47. package/dist/types.d.ts +41 -0
  48. package/dist/types.js +1 -0
  49. package/dist/validation.d.ts +5 -0
  50. package/dist/validation.js +5 -0
  51. package/dist/zap/handlers.d.ts +80 -0
  52. package/dist/zap/handlers.js +165 -0
  53. package/dist/zap/tools.d.ts +3 -0
  54. package/dist/zap/tools.js +98 -0
  55. package/llms-full.txt +281 -0
  56. package/llms.txt +73 -0
  57. package/package.json +96 -0
@@ -0,0 +1,133 @@
1
+ import { createAttestation, createRevocation } from 'nostr-attestations';
2
+ import { validateAttestation } from 'nostr-attestations';
3
+ import { attestationFilter } from 'nostr-attestations';
4
+ import { wrapEvent } from 'nostr-tools/nip17';
5
+ import { toUnsignedEvent } from 'nsec-tree/event';
6
+ import { decode } from 'nostr-tools/nip19';
7
+ /** Create and publish a kind 31000 attestation */
8
+ export async function handleTrustAttest(ctx, pool, args) {
9
+ const template = createAttestation({
10
+ type: args.type,
11
+ identifier: args.identifier,
12
+ subject: args.subject,
13
+ summary: args.summary,
14
+ content: args.content,
15
+ expiration: args.expiration,
16
+ });
17
+ const sign = ctx.getSigningFunction();
18
+ const event = await sign({
19
+ kind: template.kind,
20
+ created_at: template.created_at ?? Math.floor(Date.now() / 1000),
21
+ tags: template.tags,
22
+ content: template.content,
23
+ });
24
+ const publish = await pool.publish(ctx.activeNpub, event);
25
+ // Warn if attesting as a derived persona
26
+ const identities = ctx.listIdentities();
27
+ const active = identities.find(i => i.npub === ctx.activeNpub);
28
+ let warning;
29
+ if (active && active.purpose !== 'master') {
30
+ warning = `Attesting as derived identity (purpose: ${active.purpose}). Attestations are usually issued from the master identity.`;
31
+ }
32
+ return { event, publish, warning };
33
+ }
34
+ /** Read attestations from relays, filtered by subject/type/attestor */
35
+ export async function handleTrustRead(pool, npub, args) {
36
+ const filter = attestationFilter({
37
+ subject: args.subject,
38
+ type: args.type,
39
+ authors: args.attestor ? [args.attestor] : undefined,
40
+ });
41
+ return pool.query(npub, filter);
42
+ }
43
+ /** Validate attestation event structure */
44
+ export function handleTrustVerify(event) {
45
+ const result = validateAttestation(event);
46
+ return { valid: result.valid, errors: result.errors ?? [] };
47
+ }
48
+ /** Create and publish a revocation for an attestation */
49
+ export async function handleTrustRevoke(ctx, pool, args) {
50
+ // Check that active identity matches the original attestor
51
+ if (args.originalAttestorPubkey) {
52
+ // Decode active npub to hex for comparison
53
+ const activeHex = decode(ctx.activeNpub).data;
54
+ if (args.originalAttestorPubkey !== activeHex) {
55
+ throw new Error(`Active identity does not match original attestor. Switch identity before revoking.`);
56
+ }
57
+ }
58
+ const template = createRevocation({
59
+ type: args.type,
60
+ identifier: args.identifier,
61
+ });
62
+ const sign = ctx.getSigningFunction();
63
+ const event = await sign({
64
+ kind: template.kind,
65
+ created_at: template.created_at ?? Math.floor(Date.now() / 1000),
66
+ tags: template.tags,
67
+ content: template.content,
68
+ });
69
+ const publish = await pool.publish(ctx.activeNpub, event);
70
+ return { event, publish };
71
+ }
72
+ // --- Task 14: Attestation request via NIP-17 ---
73
+ const ATTESTATION_REQUEST_TYPE = 'nip-va/attestation-request';
74
+ /** Send an attestation request as a NIP-17 structured DM */
75
+ export async function handleTrustRequest(ctx, pool, args) {
76
+ const payload = JSON.stringify({
77
+ type: ATTESTATION_REQUEST_TYPE,
78
+ v: 1,
79
+ subject: args.subject,
80
+ attestation_type: args.attestationType,
81
+ message: args.message,
82
+ });
83
+ const event = wrapEvent(ctx.activePrivateKey, { publicKey: args.recipientPubkeyHex }, payload);
84
+ const publish = await pool.publish(ctx.activeNpub, event);
85
+ return { event, publish };
86
+ }
87
+ /** Scan NIP-17 DMs for attestation request payloads */
88
+ export async function handleTrustRequestList(ctx, pool) {
89
+ const { handleDmRead } = await import('../social/dm.js');
90
+ const dms = await handleDmRead(ctx, pool);
91
+ const requests = [];
92
+ for (const dm of dms) {
93
+ if (!dm.decrypted || !dm.content)
94
+ continue;
95
+ try {
96
+ const payload = JSON.parse(dm.content);
97
+ if (payload.type === ATTESTATION_REQUEST_TYPE && payload.v === 1) {
98
+ requests.push({
99
+ from: dm.from,
100
+ subject: payload.subject,
101
+ attestationType: payload.attestation_type,
102
+ message: payload.message,
103
+ });
104
+ }
105
+ }
106
+ catch { /* not a structured DM */ }
107
+ }
108
+ return requests;
109
+ }
110
+ // --- Task 15: Linkage proof publishing ---
111
+ /** Publish a linkage proof as a kind 30078 event. Requires confirmation. */
112
+ export async function handleTrustProofPublish(ctx, pool, args) {
113
+ const proof = ctx.prove(args.mode ?? 'blind');
114
+ const reveals = args.mode === 'full'
115
+ ? `Full proof — reveals purpose "${proof.purpose}" and index ${proof.index}. This is irreversible.`
116
+ : 'Blind proof — reveals that child belongs to master, but NOT the derivation path.';
117
+ if (!args.confirm) {
118
+ return {
119
+ published: false,
120
+ warning: `About to publish linkage proof. ${reveals} Set confirm: true to proceed.`,
121
+ };
122
+ }
123
+ const unsigned = toUnsignedEvent(proof);
124
+ const sign = ctx.getSigningFunction();
125
+ const event = await sign({
126
+ kind: unsigned.kind,
127
+ created_at: unsigned.created_at,
128
+ tags: unsigned.tags,
129
+ content: unsigned.content,
130
+ });
131
+ const publish = await pool.publish(ctx.activeNpub, event);
132
+ return { event, published: true, publish };
133
+ }
@@ -0,0 +1,19 @@
1
+ import type { RingSignature } from '@forgesworn/ring-sig';
2
+ import type { IdentityContext } from '../context.js';
3
+ import type { RelayPool } from '../relay-pool.js';
4
+ import type { PublishResult } from '../types.js';
5
+ import type { Event as NostrEvent } from 'nostr-tools';
6
+ /** Create a ring signature proving membership in a group */
7
+ export declare function handleTrustRingProve(ctx: IdentityContext, pool: RelayPool, args: {
8
+ ring: string[];
9
+ attestationType: string;
10
+ message?: string;
11
+ }): Promise<{
12
+ signature: RingSignature;
13
+ event: NostrEvent;
14
+ publish: PublishResult;
15
+ }>;
16
+ /** Verify a ring signature */
17
+ export declare function handleTrustRingVerify(signatureOrEvent: RingSignature | NostrEvent): {
18
+ valid: boolean;
19
+ };
@@ -0,0 +1,54 @@
1
+ import { ringSign, ringVerify } from '@forgesworn/ring-sig';
2
+ /** Create a ring signature proving membership in a group */
3
+ export async function handleTrustRingProve(ctx, pool, args) {
4
+ const createdAt = Math.floor(Date.now() / 1000);
5
+ const canonicalMessage = args.message ?? `ring-membership:${args.attestationType}:${createdAt}`;
6
+ // Find our index in the ring
7
+ const activeHex = (await import('nostr-tools/nip19')).decode(ctx.activeNpub).data;
8
+ const signerIndex = args.ring.indexOf(activeHex);
9
+ if (signerIndex === -1) {
10
+ throw new Error('Active identity not found in ring. Derive and switch to a matching identity first.');
11
+ }
12
+ // Get private key as hex — strings can't be zeroised, so limit scope
13
+ let privateKeyHex = Buffer.from(ctx.activePrivateKey).toString('hex');
14
+ let signature;
15
+ try {
16
+ signature = ringSign(canonicalMessage, args.ring, signerIndex, privateKeyHex);
17
+ }
18
+ finally {
19
+ privateKeyHex = ''; // remove reference; original string remains until GC
20
+ }
21
+ // Build kind 30078 event
22
+ const sign = ctx.getSigningFunction();
23
+ const msgHash = Buffer.from(signature.message, 'hex').toString('hex').slice(0, 16);
24
+ const tags = [
25
+ ['d', `ring-proof:${msgHash}`],
26
+ ['attestation-type', args.attestationType],
27
+ ...args.ring.map(pk => ['p', pk]),
28
+ ];
29
+ const event = await sign({
30
+ kind: 30078,
31
+ created_at: createdAt,
32
+ tags,
33
+ content: JSON.stringify(signature),
34
+ });
35
+ const publish = await pool.publish(ctx.activeNpub, event);
36
+ return { signature, event, publish };
37
+ }
38
+ /** Verify a ring signature */
39
+ export function handleTrustRingVerify(signatureOrEvent) {
40
+ let sig;
41
+ if ('content' in signatureOrEvent && 'kind' in signatureOrEvent) {
42
+ // It's a Nostr event — parse the signature from content
43
+ try {
44
+ sig = JSON.parse(signatureOrEvent.content);
45
+ }
46
+ catch {
47
+ return { valid: false };
48
+ }
49
+ }
50
+ else {
51
+ sig = signatureOrEvent;
52
+ }
53
+ return { valid: ringVerify(sig) };
54
+ }
@@ -0,0 +1,33 @@
1
+ /** Generate a challenge token for spoken verification */
2
+ export declare function handleTrustSpokenChallenge(args: {
3
+ secret: string;
4
+ context: string;
5
+ counter: number;
6
+ encoding?: {
7
+ format: 'words';
8
+ count?: number;
9
+ } | {
10
+ format: 'pin';
11
+ digits?: number;
12
+ };
13
+ }): {
14
+ token: string;
15
+ };
16
+ /** Verify a spoken token response */
17
+ export declare function handleTrustSpokenVerify(args: {
18
+ secret: string;
19
+ context: string;
20
+ counter: number;
21
+ input: string;
22
+ tolerance?: number;
23
+ encoding?: {
24
+ format: 'words';
25
+ count?: number;
26
+ } | {
27
+ format: 'pin';
28
+ digits?: number;
29
+ };
30
+ }): {
31
+ valid: boolean;
32
+ identity?: string;
33
+ };
@@ -0,0 +1,14 @@
1
+ import { deriveToken, verifyToken } from 'spoken-token';
2
+ /** Generate a challenge token for spoken verification */
3
+ export function handleTrustSpokenChallenge(args) {
4
+ const token = deriveToken(args.secret, args.context, args.counter, args.encoding);
5
+ return { token };
6
+ }
7
+ /** Verify a spoken token response */
8
+ export function handleTrustSpokenVerify(args) {
9
+ const result = verifyToken(args.secret, args.context, args.counter, args.input, undefined, {
10
+ tolerance: args.tolerance ?? 1,
11
+ encoding: args.encoding,
12
+ });
13
+ return { valid: result.status === 'valid', identity: result.identity };
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ToolDeps } from '../identity/tools.js';
3
+ export declare function registerTrustTools(server: McpServer, deps: ToolDeps): void;
@@ -0,0 +1,182 @@
1
+ import { z } from 'zod';
2
+ import { hexId } from '../validation.js';
3
+ import { handleTrustAttest, handleTrustRead, handleTrustVerify, handleTrustRevoke, handleTrustRequest, handleTrustRequestList, handleTrustProofPublish, } from './handlers.js';
4
+ import { handleTrustRingProve, handleTrustRingVerify } from './ring.js';
5
+ import { handleTrustSpokenChallenge, handleTrustSpokenVerify } from './spoken.js';
6
+ export function registerTrustTools(server, deps) {
7
+ server.registerTool('trust_attest', {
8
+ description: 'Create and publish a kind 31000 verifiable attestation as the active identity.',
9
+ inputSchema: {
10
+ type: z.string().describe('Attestation type (e.g. "identity-verification", "endorsement")'),
11
+ identifier: z.string().optional().describe('D-tag identifier (hex pubkey or context string)'),
12
+ subject: z.string().optional().describe('Subject hex pubkey (for third-party attestations)'),
13
+ summary: z.string().optional().describe('Human-readable summary'),
14
+ content: z.string().optional().describe('Event content (text or JSON)'),
15
+ expiration: z.number().optional().describe('Unix timestamp for attestation expiry'),
16
+ },
17
+ annotations: { readOnlyHint: false },
18
+ }, async ({ type, identifier, subject, summary, content, expiration }) => {
19
+ const result = await handleTrustAttest(deps.ctx, deps.pool, {
20
+ type, identifier, subject, summary, content, expiration,
21
+ });
22
+ const response = {
23
+ id: result.event.id,
24
+ publish: result.publish,
25
+ };
26
+ if (result.warning)
27
+ response.warning = result.warning;
28
+ return {
29
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
30
+ };
31
+ });
32
+ server.registerTool('trust_read', {
33
+ description: 'Read kind 31000 attestations from relays. Filter by subject, type, or attestor.',
34
+ inputSchema: {
35
+ subject: hexId.optional().describe('Subject hex pubkey to filter by'),
36
+ type: z.string().optional().describe('Attestation type to filter by'),
37
+ attestor: hexId.optional().describe('Attestor hex pubkey to filter by'),
38
+ },
39
+ annotations: { readOnlyHint: true },
40
+ }, async ({ subject, type, attestor }) => {
41
+ const events = await handleTrustRead(deps.pool, deps.ctx.activeNpub, { subject, type, attestor });
42
+ return {
43
+ content: [{ type: 'text', text: JSON.stringify(events.map(e => ({
44
+ id: e.id,
45
+ pubkey: e.pubkey,
46
+ kind: e.kind,
47
+ tags: e.tags,
48
+ content: e.content,
49
+ created_at: e.created_at,
50
+ })), null, 2) }],
51
+ };
52
+ });
53
+ server.registerTool('trust_verify', {
54
+ description: 'Validate the structural correctness of a kind 31000 attestation event.',
55
+ inputSchema: {
56
+ event: z.record(z.string(), z.unknown()).describe('The attestation event object to validate'),
57
+ },
58
+ annotations: { readOnlyHint: true },
59
+ }, async ({ event }) => {
60
+ const result = handleTrustVerify(event);
61
+ return {
62
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
63
+ };
64
+ });
65
+ server.registerTool('trust_revoke', {
66
+ description: 'Revoke a previously issued attestation. Active identity must match the original attestor.',
67
+ inputSchema: {
68
+ type: z.string().describe('Attestation type being revoked'),
69
+ identifier: z.string().describe('D-tag identifier of the attestation'),
70
+ originalAttestorPubkey: z.string().optional().describe('Hex pubkey of the original attestor (for verification)'),
71
+ },
72
+ annotations: { readOnlyHint: false },
73
+ }, async ({ type, identifier, originalAttestorPubkey }) => {
74
+ const result = await handleTrustRevoke(deps.ctx, deps.pool, {
75
+ type, identifier, originalAttestorPubkey,
76
+ });
77
+ return {
78
+ content: [{ type: 'text', text: JSON.stringify({
79
+ id: result.event.id,
80
+ publish: result.publish,
81
+ }, null, 2) }],
82
+ };
83
+ });
84
+ server.registerTool('trust_request', {
85
+ description: 'Send an attestation request to another Nostr identity via NIP-17 encrypted DM.',
86
+ inputSchema: {
87
+ recipientPubkeyHex: hexId.describe('Hex pubkey of the attestor you are requesting from'),
88
+ subject: hexId.describe('Hex pubkey of the subject to be attested'),
89
+ attestationType: z.string().describe('Type of attestation requested'),
90
+ message: z.string().optional().describe('Optional message explaining the request'),
91
+ },
92
+ annotations: { readOnlyHint: false },
93
+ }, async ({ recipientPubkeyHex, subject, attestationType, message }) => {
94
+ const result = await handleTrustRequest(deps.ctx, deps.pool, {
95
+ recipientPubkeyHex, subject, attestationType, message,
96
+ });
97
+ return {
98
+ content: [{ type: 'text', text: JSON.stringify({ id: result.event.id, publish: result.publish }, null, 2) }],
99
+ };
100
+ });
101
+ server.registerTool('trust_request_list', {
102
+ description: 'Scan received NIP-17 DMs for attestation request payloads.',
103
+ annotations: { readOnlyHint: true },
104
+ }, async () => {
105
+ const requests = await handleTrustRequestList(deps.ctx, deps.pool);
106
+ return {
107
+ content: [{ type: 'text', text: JSON.stringify(requests, null, 2) }],
108
+ };
109
+ });
110
+ server.registerTool('trust_proof_publish', {
111
+ description: 'Publish a cryptographic linkage proof as a kind 30078 event. Requires confirmation — this is irreversible.',
112
+ inputSchema: {
113
+ mode: z.enum(['blind', 'full']).default('blind').describe('Proof mode: blind hides derivation path, full reveals it'),
114
+ confirm: z.boolean().default(false).describe('Set true to publish (irreversible)'),
115
+ },
116
+ annotations: { readOnlyHint: false },
117
+ }, async ({ mode, confirm }) => {
118
+ const result = await handleTrustProofPublish(deps.ctx, deps.pool, { mode, confirm });
119
+ if (!result.published) {
120
+ return { content: [{ type: 'text', text: JSON.stringify({ warning: result.warning }, null, 2) }] };
121
+ }
122
+ return {
123
+ content: [{ type: 'text', text: JSON.stringify({ id: result.event.id, published: true }, null, 2) }],
124
+ };
125
+ });
126
+ server.registerTool('trust_ring_prove', {
127
+ description: 'Create a ring signature proving anonymous membership in a group of public keys.',
128
+ inputSchema: {
129
+ ring: z.array(hexId).describe('Hex x-only public keys of ring members (must include active identity)'),
130
+ attestationType: z.string().describe('Attestation type context for the canonical message'),
131
+ message: z.string().optional().describe('Custom message to sign (defaults to canonical format)'),
132
+ },
133
+ annotations: { readOnlyHint: false },
134
+ }, async ({ ring, attestationType, message }) => {
135
+ const result = await handleTrustRingProve(deps.ctx, deps.pool, { ring, attestationType, message });
136
+ return {
137
+ content: [{ type: 'text', text: JSON.stringify({ id: result.event.id, ringSize: ring.length }, null, 2) }],
138
+ };
139
+ });
140
+ server.registerTool('trust_ring_verify', {
141
+ description: 'Verify a ring signature proof.',
142
+ inputSchema: {
143
+ signature: z.record(z.string(), z.unknown()).describe('Ring signature object or Nostr event containing one'),
144
+ },
145
+ annotations: { readOnlyHint: true },
146
+ }, async ({ signature }) => {
147
+ const result = handleTrustRingVerify(signature);
148
+ return {
149
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
150
+ };
151
+ });
152
+ server.registerTool('trust_spoken_challenge', {
153
+ description: 'Generate a spoken verification token for in-person identity confirmation.',
154
+ inputSchema: {
155
+ secret: z.string().regex(/^[0-9a-f]{32,}$/, 'Hex string, min 32 chars').describe('Shared secret'),
156
+ context: z.string().describe('Context string for domain separation'),
157
+ counter: z.number().int().describe('Time-based or usage counter'),
158
+ },
159
+ annotations: { readOnlyHint: true },
160
+ }, async ({ secret, context, counter }) => {
161
+ const result = handleTrustSpokenChallenge({ secret, context, counter });
162
+ return {
163
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
164
+ };
165
+ });
166
+ server.registerTool('trust_spoken_verify', {
167
+ description: 'Verify a spoken token response against the shared secret.',
168
+ inputSchema: {
169
+ secret: z.string().regex(/^[0-9a-f]{32,}$/, 'Hex string, min 32 chars').describe('Shared secret'),
170
+ context: z.string().describe('Context string used during challenge'),
171
+ counter: z.number().int().describe('Current counter value'),
172
+ input: z.string().describe('The spoken/entered token to verify'),
173
+ tolerance: z.number().int().min(0).max(10).default(1).describe('Counter tolerance window (default 1)'),
174
+ },
175
+ annotations: { readOnlyHint: true },
176
+ }, async ({ secret, context, counter, input, tolerance }) => {
177
+ const result = handleTrustSpokenVerify({ secret, context, counter, input, tolerance });
178
+ return {
179
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
180
+ };
181
+ });
182
+ }
@@ -0,0 +1,41 @@
1
+ import type { Event as NostrEvent, EventTemplate } from 'nostr-tools';
2
+ /** Public identity info returned by tools — never includes private keys */
3
+ export interface PublicIdentity {
4
+ readonly npub: string;
5
+ readonly purpose?: string;
6
+ readonly index?: number;
7
+ readonly personaName?: string;
8
+ }
9
+ /** Snapshot of an identity's replaceable events from relays */
10
+ export interface IdentitySnapshot {
11
+ profile?: NostrEvent;
12
+ contacts?: NostrEvent;
13
+ relayList?: NostrEvent;
14
+ }
15
+ /** Signing function — signs an event template, returns signed event */
16
+ export type SignFn = (template: EventTemplate) => Promise<NostrEvent>;
17
+ /** Relay set with read/write separation */
18
+ export interface RelaySet {
19
+ read: string[];
20
+ write: string[];
21
+ }
22
+ /** Result of a publish operation */
23
+ export interface PublishResult {
24
+ success: boolean;
25
+ accepted: string[];
26
+ rejected: string[];
27
+ errors: string[];
28
+ }
29
+ /** Parsed configuration */
30
+ export interface BrayConfig {
31
+ readonly secretKey: string;
32
+ readonly secretFormat: 'nsec' | 'hex' | 'mnemonic';
33
+ readonly relays: string[];
34
+ readonly nwcUri?: string;
35
+ readonly torProxy?: string;
36
+ readonly allowClearnetWithTor: boolean;
37
+ readonly nip04Enabled: boolean;
38
+ readonly transport: 'stdio' | 'http';
39
+ readonly port: number;
40
+ readonly bindAddress: string;
41
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ /** 64-character lowercase hex string (pubkey or event ID) */
3
+ export declare const hexId: z.ZodString;
4
+ /** Relay WebSocket URL — wss:// or ws:// only */
5
+ export declare const relayUrl: z.ZodString;
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ /** 64-character lowercase hex string (pubkey or event ID) */
3
+ export const hexId = z.string().regex(/^[0-9a-f]{64}$/, 'Must be a 64-character hex string');
4
+ /** Relay WebSocket URL — wss:// or ws:// only */
5
+ export const relayUrl = z.string().regex(/^wss?:\/\//, 'Must be a wss:// or ws:// URL');
@@ -0,0 +1,80 @@
1
+ import type { Event as NostrEvent } from 'nostr-tools';
2
+ import type { IdentityContext } from '../context.js';
3
+ import type { RelayPool } from '../relay-pool.js';
4
+ import type { PublishResult } from '../types.js';
5
+ export interface NwcConnection {
6
+ pubkey: string;
7
+ relay: string;
8
+ secret: string;
9
+ }
10
+ /** Parse a nostr+walletconnect:// URI */
11
+ export declare function parseNwcUri(uri: string): NwcConnection;
12
+ export interface ZapReceipt {
13
+ id: string;
14
+ sender?: string;
15
+ amountMsats?: number;
16
+ message?: string;
17
+ createdAt: number;
18
+ }
19
+ /** Parse zap receipts (kind 9735) for the active identity */
20
+ export declare function handleZapReceipts(ctx: IdentityContext, pool: RelayPool, opts?: {
21
+ since?: number;
22
+ limit?: number;
23
+ }): Promise<ZapReceipt[]>;
24
+ /** Decode basic bolt11 invoice fields */
25
+ export declare function handleZapDecode(bolt11: string): {
26
+ amountMsats?: number;
27
+ description?: string;
28
+ expiry?: number;
29
+ };
30
+ /** Pay a Lightning invoice via NWC */
31
+ export declare function handleZapSend(ctx: IdentityContext, pool: RelayPool, args: {
32
+ invoice: string;
33
+ nwcUri?: string;
34
+ }): Promise<{
35
+ event: NostrEvent;
36
+ publish: PublishResult;
37
+ }>;
38
+ /** Request wallet balance via NWC */
39
+ export declare function handleZapBalance(ctx: IdentityContext, pool: RelayPool, args: {
40
+ nwcUri?: string;
41
+ }): Promise<{
42
+ event: NostrEvent;
43
+ publish: PublishResult;
44
+ }>;
45
+ /** Generate a Lightning invoice via NWC */
46
+ export declare function handleZapMakeInvoice(ctx: IdentityContext, pool: RelayPool, args: {
47
+ amountMsats: number;
48
+ description?: string;
49
+ nwcUri?: string;
50
+ }): Promise<{
51
+ event: NostrEvent;
52
+ publish: PublishResult;
53
+ }>;
54
+ /** Look up an invoice via NWC */
55
+ export declare function handleZapLookupInvoice(ctx: IdentityContext, pool: RelayPool, args: {
56
+ paymentHash?: string;
57
+ invoice?: string;
58
+ nwcUri?: string;
59
+ }): Promise<{
60
+ event: NostrEvent;
61
+ publish: PublishResult;
62
+ }>;
63
+ /** List recent transactions via NWC */
64
+ export declare function handleZapListTransactions(ctx: IdentityContext, pool: RelayPool, args: {
65
+ limit?: number;
66
+ offset?: number;
67
+ nwcUri?: string;
68
+ }): Promise<{
69
+ event: NostrEvent;
70
+ publish: PublishResult;
71
+ }>;
72
+ /** Parse a NWC wallet response event */
73
+ export declare function handleZapParseResponse(nwcUri: string, event: NostrEvent): {
74
+ result_type: string;
75
+ result?: Record<string, unknown>;
76
+ error?: {
77
+ code: string;
78
+ message: string;
79
+ };
80
+ };