feat: implement NIP-09 event deletion for service announcements (v0.1.19)

This commit is contained in:
gzuuus
2025-03-25 23:44:18 +01:00
parent d44ab2fe27
commit ff9786c45a
5 changed files with 99 additions and 1 deletions

View File

@@ -8,6 +8,7 @@ A bridge implementation that connects Model Context Protocol (MCP) servers to No
- Automatic service announcement using NIP-89
- Tool discovery and execution through DVM kind:5910/6910 events
- Job status updates and payment handling via kind:7000 events
- Service announcement deletion using NIP-09
- Comprehensive error handling
## Configuration
@@ -47,6 +48,22 @@ For production:
bun run start
```
### Deleting Service Announcements
To remove your service announcements from relays when shutting down or taking your service offline, you can use the `--delete-announcement` flag:
```bash
bun run start --delete-announcement
```
You can also provide an optional reason for the deletion:
```bash
bun run start --delete-announcement --reason "Service maintenance in progress"
```
This will send a NIP-09 deletion event (kind 5) to all connected relays, instructing them to remove your previously published service announcements.
## Testing
Run the test suite:

View File

@@ -14,6 +14,7 @@ import {
import { argv } from 'process';
import type { Config } from './src/types';
import { setConfigPath } from './src/config.js';
import { DVMBridge } from './src/dvm-bridge.js';
const defaultConfigPath = join(process.cwd(), 'config.dvmcp.yml');
let configPath = defaultConfigPath;
@@ -118,9 +119,38 @@ const runApp = async () => {
await main.default();
};
const deleteAnnouncement = async () => {
// Get optional reason from command line arguments
const reasonIndex = argv.indexOf('--reason');
const reason = reasonIndex !== -1 && argv[reasonIndex + 1] ? argv[reasonIndex + 1] : undefined;
// Import DVMBridge and perform deletion
const bridge = new DVMBridge();
try {
console.log(`${CONFIG_EMOJIS.INFO} Deleting service announcement...`);
await bridge.deleteAnnouncement(reason);
console.log(`${CONFIG_EMOJIS.SUCCESS} Service announcement deleted successfully`);
process.exit(0);
} catch (error) {
console.error(`${CONFIG_EMOJIS.INFO} Failed to delete service announcement:`, error);
process.exit(1);
}
};
const cliMain = async () => {
if (argv.includes('--configure')) {
await configure();
return;
}
if (argv.includes('--delete-announcement')) {
if (!existsSync(configPath)) {
console.error(`${CONFIG_EMOJIS.INFO} No configuration file found at ${configPath}`);
process.exit(1);
}
await deleteAnnouncement();
return;
}
if (!existsSync(configPath)) {

View File

@@ -1,6 +1,6 @@
{
"name": "@dvmcp/bridge",
"version": "0.1.18",
"version": "0.1.19",
"description": "Bridge connecting MCP servers to Nostr's DVM ecosystem",
"module": "index.ts",
"type": "module",

View File

@@ -7,6 +7,7 @@ import {
DVM_ANNOUNCEMENT_KIND,
TOOL_REQUEST_KIND,
} from '@dvmcp/commons/constants';
import type { Event } from 'nostr-tools/pure';
export const keyManager = createKeyManager(CONFIG.nostr.privateKey);
@@ -57,4 +58,37 @@ export class NostrAnnouncer {
async updateAnnouncement() {
await Promise.all([this.announceService(), this.announceRelayList()]);
}
/**
* Deletes the service announcement from relays using NIP-09
* @param reason Optional reason for deletion
* @returns The deletion event that was published
*/
async deleteAnnouncement(reason: string = 'Service offline'): Promise<Event> {
// First, query the relays to find our announcement event
const announcementFilter = {
kinds: [DVM_ANNOUNCEMENT_KIND],
authors: [keyManager.getPublicKey()],
};
const events = await this.relayHandler.queryEvents(announcementFilter);
// Create the deletion event (NIP-09)
const deletionEvent = keyManager.signEvent({
...keyManager.createEventTemplate(5), // kind 5 is for deletion requests
content: reason,
tags: [
// Add tags for each event to be deleted
...events.map(event => ['e', event.id]),
// Add the kind of events being deleted
['k', `${DVM_ANNOUNCEMENT_KIND}`],
],
});
// Publish the deletion event
await this.relayHandler.publishEvent(deletionEvent);
console.log(`Published deletion event for service announcement`);
return deletionEvent;
}
}

View File

@@ -82,6 +82,23 @@ export class DVMBridge {
throw error;
}
}
/**
* Deletes the service announcement from relays
* @param reason Optional reason for deletion
* @returns The deletion event that was published
*/
async deleteAnnouncement(reason?: string) {
console.log('Deleting service announcement from relays...');
try {
const deletionEvent = await this.nostrAnnouncer.deleteAnnouncement(reason);
console.log('Service announcement deleted successfully');
return deletionEvent;
} catch (error) {
console.error('Error deleting service announcement:', error);
throw error;
}
}
private async handleRequest(event: Event) {
try {