mirror of
https://github.com/aljazceru/dvmcp.git
synced 2025-12-17 05:14:24 +01:00
refactor: improve code maintainability and add direct server tool registration
This commit is contained in:
@@ -8,6 +8,7 @@ A MCP server implementation that aggregates tools from DVMs across the Nostr net
|
|||||||
- Provides a unified interface to access tools from multiple DVMs
|
- Provides a unified interface to access tools from multiple DVMs
|
||||||
- Tool execution handling and status tracking
|
- Tool execution handling and status tracking
|
||||||
- Configurable DVM whitelist
|
- Configurable DVM whitelist
|
||||||
|
- Direct connection to specific providers or servers
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -46,6 +47,28 @@ For production:
|
|||||||
bun run start
|
bun run start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Direct Connection Options
|
||||||
|
|
||||||
|
You can connect directly to a specific provider or server without a configuration file:
|
||||||
|
|
||||||
|
#### Connect to a Provider
|
||||||
|
|
||||||
|
Use the `--provider` flag followed by an nprofile entity to discover and register all tools from a specific provider:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run start --provider nprofile1...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Connect to a Server
|
||||||
|
|
||||||
|
Use the `--server` flag followed by an naddr entity to register only the tools from a specific server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run start --server naddr1...
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful when you want to work with a specific subset of tools rather than discovering all tools from a provider.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Run the test suite:
|
Run the test suite:
|
||||||
|
|||||||
@@ -11,13 +11,41 @@ import { join, resolve } from 'path';
|
|||||||
import type { Config } from './src/config.js';
|
import type { Config } from './src/config.js';
|
||||||
import { argv } from 'process';
|
import { argv } from 'process';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { setConfigPath } from './src/config.js';
|
import {
|
||||||
|
setConfigPath,
|
||||||
|
setInMemoryConfig,
|
||||||
|
createDefaultConfig,
|
||||||
|
} from './src/config.js';
|
||||||
|
import { decodeNaddr, decodeNprofile } from './src/nip19-utils.js';
|
||||||
|
import {
|
||||||
|
fetchProviderAnnouncement,
|
||||||
|
fetchServerAnnouncement,
|
||||||
|
parseAnnouncement,
|
||||||
|
type DVMAnnouncement,
|
||||||
|
} from './src/direct-discovery.js';
|
||||||
|
import type { DirectServerInfo } from './index.js';
|
||||||
|
|
||||||
const defaultConfigPath = join(process.cwd(), 'config.dvmcp.yml');
|
const defaultConfigPath = join(process.cwd(), 'config.dvmcp.yml');
|
||||||
let configPath = defaultConfigPath;
|
let configPath = defaultConfigPath;
|
||||||
|
|
||||||
|
// Check for provider flag
|
||||||
|
const providerArgIndex = argv.indexOf('--provider');
|
||||||
|
const hasProviderFlag = providerArgIndex !== -1 && argv[providerArgIndex + 1];
|
||||||
|
const providerValue = hasProviderFlag ? argv[providerArgIndex + 1] : null;
|
||||||
|
|
||||||
|
// Check for server flag
|
||||||
|
const serverArgIndex = argv.indexOf('--server');
|
||||||
|
const hasServerFlag = serverArgIndex !== -1 && argv[serverArgIndex + 1];
|
||||||
|
const serverValue = hasServerFlag ? argv[serverArgIndex + 1] : null;
|
||||||
|
|
||||||
|
// Check for config path flag (only used if provider and server flags are not present)
|
||||||
const configPathArgIndex = argv.indexOf('--config-path');
|
const configPathArgIndex = argv.indexOf('--config-path');
|
||||||
if (configPathArgIndex !== -1 && argv[configPathArgIndex + 1]) {
|
if (
|
||||||
|
!hasProviderFlag &&
|
||||||
|
!hasServerFlag &&
|
||||||
|
configPathArgIndex !== -1 &&
|
||||||
|
argv[configPathArgIndex + 1]
|
||||||
|
) {
|
||||||
configPath = resolve(argv[configPathArgIndex + 1]);
|
configPath = resolve(argv[configPathArgIndex + 1]);
|
||||||
console.log(`Using config path: ${configPath}`);
|
console.log(`Using config path: ${configPath}`);
|
||||||
setConfigPath(configPath);
|
setConfigPath(configPath);
|
||||||
@@ -89,25 +117,110 @@ const configure = async () => {
|
|||||||
await generator.generate();
|
await generator.generate();
|
||||||
};
|
};
|
||||||
|
|
||||||
const runApp = async () => {
|
const runApp = async (directServerInfo?: DirectServerInfo) => {
|
||||||
const main = await import('./index.js');
|
const main = await import('./index.js');
|
||||||
console.log(`${CONFIG_EMOJIS.INFO} Running main application...`);
|
console.log(`${CONFIG_EMOJIS.INFO} Running main application...`);
|
||||||
await main.default();
|
await main.default(directServerInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupInMemoryConfig = (relays: string[], pubkey: string) => {
|
||||||
|
const config = createDefaultConfig(relays);
|
||||||
|
|
||||||
|
config.whitelist = {
|
||||||
|
allowedDVMs: new Set([pubkey]),
|
||||||
|
};
|
||||||
|
|
||||||
|
setInMemoryConfig(config);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupFromProvider = async (nprofileEntity: string) => {
|
||||||
|
console.log(
|
||||||
|
`${CONFIG_EMOJIS.INFO} Setting up from provider: ${nprofileEntity}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const providerData = decodeNprofile(nprofileEntity);
|
||||||
|
if (!providerData) {
|
||||||
|
console.error('Invalid nprofile entity');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const announcement = await fetchProviderAnnouncement(providerData);
|
||||||
|
if (!announcement) {
|
||||||
|
console.error('Failed to fetch provider announcement');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInMemoryConfig(providerData.relays, providerData.pubkey);
|
||||||
|
console.log(`${CONFIG_EMOJIS.SUCCESS} Successfully set up from provider`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error: ${error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupFromServer = async (naddrEntity: string) => {
|
||||||
|
console.log(`${CONFIG_EMOJIS.INFO} Setting up from server: ${naddrEntity}`);
|
||||||
|
|
||||||
|
const addrData = decodeNaddr(naddrEntity);
|
||||||
|
if (!addrData) {
|
||||||
|
console.error('Invalid naddr entity');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const announcement = await fetchServerAnnouncement(addrData);
|
||||||
|
if (!announcement) {
|
||||||
|
console.error('Failed to fetch server announcement');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedAnnouncement = parseAnnouncement(announcement);
|
||||||
|
if (!parsedAnnouncement) {
|
||||||
|
console.error('Failed to parse server announcement');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInMemoryConfig(addrData.relays, addrData.pubkey);
|
||||||
|
console.log(`${CONFIG_EMOJIS.SUCCESS} Successfully set up from server`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pubkey: addrData.pubkey,
|
||||||
|
announcement: parsedAnnouncement,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error: ${error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cliMain = async () => {
|
const cliMain = async () => {
|
||||||
|
// Handle --configure flag
|
||||||
if (argv.includes('--configure')) {
|
if (argv.includes('--configure')) {
|
||||||
await configure();
|
await configure();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsSync(configPath)) {
|
// Handle --provider flag
|
||||||
|
if (hasProviderFlag && providerValue) {
|
||||||
|
await setupFromProvider(providerValue);
|
||||||
|
await runApp();
|
||||||
|
}
|
||||||
|
// Handle --server flag
|
||||||
|
else if (hasServerFlag && serverValue) {
|
||||||
|
const serverInfo = await setupFromServer(serverValue);
|
||||||
|
await runApp(serverInfo);
|
||||||
|
}
|
||||||
|
// Handle normal config file mode
|
||||||
|
else if (!existsSync(configPath)) {
|
||||||
console.log(
|
console.log(
|
||||||
`${CONFIG_EMOJIS.INFO} No configuration file found. Starting setup...`
|
`${CONFIG_EMOJIS.INFO} No configuration file found. Starting setup...`
|
||||||
);
|
);
|
||||||
await configure();
|
await configure();
|
||||||
}
|
|
||||||
|
|
||||||
await runApp();
|
await runApp();
|
||||||
|
} else {
|
||||||
|
await runApp();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cliMain().catch(console.error);
|
cliMain().catch(console.error);
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
import { CONFIG } from './src/config';
|
import { CONFIG } from './src/config';
|
||||||
import { DiscoveryServer } from './src/discovery-server';
|
import { DiscoveryServer } from './src/discovery-server';
|
||||||
|
import type { DVMAnnouncement } from './src/direct-discovery';
|
||||||
|
|
||||||
async function main() {
|
export interface DirectServerInfo {
|
||||||
|
pubkey: string;
|
||||||
|
announcement: DVMAnnouncement;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(directServerInfo?: DirectServerInfo | null) {
|
||||||
try {
|
try {
|
||||||
const server = new DiscoveryServer(CONFIG);
|
const server = new DiscoveryServer(CONFIG);
|
||||||
|
|
||||||
|
if (directServerInfo) {
|
||||||
|
// If we have direct server info, register tools from that server only
|
||||||
|
console.log(
|
||||||
|
`Using direct server with pubkey: ${directServerInfo.pubkey}`
|
||||||
|
);
|
||||||
|
await server.registerDirectServerTools(
|
||||||
|
directServerInfo.pubkey,
|
||||||
|
directServerInfo.announcement
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise do normal discovery
|
||||||
await server.start();
|
await server.start();
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`DVMCP Discovery Server (${CONFIG.mcp.version}) started`);
|
console.log(`DVMCP Discovery Server (${CONFIG.mcp.version}) started`);
|
||||||
console.log(`Connected to ${CONFIG.nostr.relayUrls.length} relays`);
|
console.log(`Connected to ${CONFIG.nostr.relayUrls.length} relays`);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { parse } from 'yaml';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { existsSync, readFileSync } from 'fs';
|
import { existsSync, readFileSync } from 'fs';
|
||||||
import { HEX_KEYS_REGEX } from '@dvmcp/commons/constants';
|
import { HEX_KEYS_REGEX } from '@dvmcp/commons/constants';
|
||||||
|
import { generateSecretKey } from 'nostr-tools/pure';
|
||||||
|
import { bytesToHex } from '@noble/hashes/utils';
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
nostr: {
|
nostr: {
|
||||||
@@ -19,11 +21,16 @@ export interface Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let CONFIG_PATH = join(process.cwd(), 'config.dvmcp.yml');
|
let CONFIG_PATH = join(process.cwd(), 'config.dvmcp.yml');
|
||||||
|
let IN_MEMORY_CONFIG: Config | null = null;
|
||||||
|
|
||||||
export function setConfigPath(path: string) {
|
export function setConfigPath(path: string) {
|
||||||
CONFIG_PATH = path.startsWith('/') ? path : join(process.cwd(), path);
|
CONFIG_PATH = path.startsWith('/') ? path : join(process.cwd(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setInMemoryConfig(config: Config) {
|
||||||
|
IN_MEMORY_CONFIG = config;
|
||||||
|
}
|
||||||
|
|
||||||
const TEST_CONFIG: Config = {
|
const TEST_CONFIG: Config = {
|
||||||
nostr: {
|
nostr: {
|
||||||
privateKey:
|
privateKey:
|
||||||
@@ -77,6 +84,10 @@ function validateRelayUrls(urls: any): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadConfig(): Config {
|
function loadConfig(): Config {
|
||||||
|
if (IN_MEMORY_CONFIG) {
|
||||||
|
return IN_MEMORY_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
return TEST_CONFIG;
|
return TEST_CONFIG;
|
||||||
}
|
}
|
||||||
@@ -126,4 +137,21 @@ function loadConfig(): Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createDefaultConfig(relayUrls: string[]): Config {
|
||||||
|
return {
|
||||||
|
nostr: {
|
||||||
|
privateKey: bytesToHex(generateSecretKey()),
|
||||||
|
relayUrls: validateRelayUrls(relayUrls),
|
||||||
|
},
|
||||||
|
mcp: {
|
||||||
|
name: 'DVMCP Discovery',
|
||||||
|
version: '1.0.0',
|
||||||
|
about: 'DVMCP Discovery Server for aggregating MCP tools from DVMs',
|
||||||
|
},
|
||||||
|
whitelist: {
|
||||||
|
allowedDVMs: new Set(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const CONFIG = loadConfig();
|
export const CONFIG = loadConfig();
|
||||||
|
|||||||
90
packages/dvmcp-discovery/src/direct-discovery.ts
Normal file
90
packages/dvmcp-discovery/src/direct-discovery.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import type { Event, Filter } from 'nostr-tools';
|
||||||
|
import { RelayHandler } from '@dvmcp/commons/nostr/relay-handler';
|
||||||
|
import { DVM_ANNOUNCEMENT_KIND } from '@dvmcp/commons/constants';
|
||||||
|
import type { NaddrData, NprofileData } from './nip19-utils';
|
||||||
|
|
||||||
|
export interface DVMAnnouncement {
|
||||||
|
name: string;
|
||||||
|
about: string;
|
||||||
|
tools: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAnnouncement(
|
||||||
|
relays: string[],
|
||||||
|
filter: Filter,
|
||||||
|
errorMessage: string
|
||||||
|
): Promise<Event | null> {
|
||||||
|
// Create a new relay handler with the provided relays
|
||||||
|
const relayHandler = new RelayHandler(relays);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Query for the announcement event
|
||||||
|
console.log('Querying for announcement event:', filter);
|
||||||
|
const events = await relayHandler.queryEvents(filter);
|
||||||
|
|
||||||
|
if (events.length === 0) {
|
||||||
|
console.error(errorMessage);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return events[0];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch announcement: ${error}`);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
relayHandler.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchProviderAnnouncement(
|
||||||
|
providerData: NprofileData
|
||||||
|
): Promise<Event | null> {
|
||||||
|
// Query for the provider's DVM announcement
|
||||||
|
const filter: Filter = {
|
||||||
|
kinds: [DVM_ANNOUNCEMENT_KIND],
|
||||||
|
authors: [providerData.pubkey],
|
||||||
|
'#t': ['mcp'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const events = await fetchAnnouncement(
|
||||||
|
providerData.relays,
|
||||||
|
filter,
|
||||||
|
'No DVM announcement found for provider'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!events) return null;
|
||||||
|
|
||||||
|
// If we have multiple events, sort by created_at to get the most recent announcement
|
||||||
|
if (Array.isArray(events)) {
|
||||||
|
events.sort((a, b) => b.created_at - a.created_at);
|
||||||
|
return events[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchServerAnnouncement(
|
||||||
|
addrData: NaddrData
|
||||||
|
): Promise<Event | null> {
|
||||||
|
// Query for the specific announcement event
|
||||||
|
const filter: Filter = {
|
||||||
|
kinds: [addrData.kind],
|
||||||
|
authors: [addrData.pubkey],
|
||||||
|
'#d': addrData.identifier ? [addrData.identifier] : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetchAnnouncement(
|
||||||
|
addrData.relays,
|
||||||
|
filter,
|
||||||
|
'No DVM announcement found for the specified coordinates'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseAnnouncement(event: Event): DVMAnnouncement | null {
|
||||||
|
try {
|
||||||
|
return JSON.parse(event.content);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to parse announcement: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,7 @@ import { DVM_ANNOUNCEMENT_KIND } from '@dvmcp/commons/constants';
|
|||||||
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
||||||
import { ToolRegistry } from './tool-registry';
|
import { ToolRegistry } from './tool-registry';
|
||||||
import { ToolExecutor } from './tool-executor';
|
import { ToolExecutor } from './tool-executor';
|
||||||
|
import type { DVMAnnouncement } from './direct-discovery';
|
||||||
interface DVMAnnouncement {
|
|
||||||
name: string;
|
|
||||||
about: string;
|
|
||||||
tools: Tool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DiscoveryServer {
|
export class DiscoveryServer {
|
||||||
private mcpServer: McpServer;
|
private mcpServer: McpServer;
|
||||||
@@ -54,6 +49,17 @@ export class DiscoveryServer {
|
|||||||
await Promise.all(events.map((event) => this.handleDVMAnnouncement(event)));
|
await Promise.all(events.map((event) => this.handleDVMAnnouncement(event)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createToolId(toolName: string, pubkey: string): string {
|
||||||
|
return `${toolName}:${pubkey.slice(0, 4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerToolsFromAnnouncement(pubkey: string, tools: Tool[]): void {
|
||||||
|
for (const tool of tools) {
|
||||||
|
const toolId = this.createToolId(tool.name, pubkey);
|
||||||
|
this.toolRegistry.registerTool(toolId, tool, pubkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async handleDVMAnnouncement(event: Event) {
|
private async handleDVMAnnouncement(event: Event) {
|
||||||
try {
|
try {
|
||||||
if (!this.isAllowedDVM(event.pubkey)) {
|
if (!this.isAllowedDVM(event.pubkey)) {
|
||||||
@@ -64,10 +70,7 @@ export class DiscoveryServer {
|
|||||||
const announcement = this.parseAnnouncement(event.content);
|
const announcement = this.parseAnnouncement(event.content);
|
||||||
if (!announcement?.tools) return;
|
if (!announcement?.tools) return;
|
||||||
|
|
||||||
for (const tool of announcement.tools) {
|
this.registerToolsFromAnnouncement(event.pubkey, announcement.tools);
|
||||||
const toolId = `${tool.name}:${event.pubkey.slice(0, 4)}`;
|
|
||||||
this.toolRegistry.registerTool(toolId, tool, event.pubkey);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing DVM announcement:', error);
|
console.error('Error processing DVM announcement:', error);
|
||||||
}
|
}
|
||||||
@@ -95,6 +98,30 @@ export class DiscoveryServer {
|
|||||||
return this.toolRegistry.listTools();
|
return this.toolRegistry.listTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async registerDirectServerTools(
|
||||||
|
pubkey: string,
|
||||||
|
announcement: DVMAnnouncement
|
||||||
|
) {
|
||||||
|
console.log('Starting discovery server with direct server tools...');
|
||||||
|
|
||||||
|
if (!announcement?.tools) {
|
||||||
|
console.error('No tools found in server announcement');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registerToolsFromAnnouncement(pubkey, announcement.tools);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Registered ${announcement.tools.length} tools from direct server`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Connect the MCP server
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await this.mcpServer.connect(transport);
|
||||||
|
|
||||||
|
console.log('DVMCP Discovery Server started');
|
||||||
|
}
|
||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
console.log('Starting discovery server...');
|
console.log('Starting discovery server...');
|
||||||
|
|
||||||
|
|||||||
81
packages/dvmcp-discovery/src/nip19-utils.ts
Normal file
81
packages/dvmcp-discovery/src/nip19-utils.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { nip19 } from 'nostr-tools';
|
||||||
|
import { DVM_ANNOUNCEMENT_KIND } from '@dvmcp/commons/constants';
|
||||||
|
|
||||||
|
// Default fallback relay when no relay hints are provided
|
||||||
|
export const DEFAULT_FALLBACK_RELAY = 'wss://relay.dvmcp.fun';
|
||||||
|
|
||||||
|
export interface NprofileData {
|
||||||
|
pubkey: string;
|
||||||
|
relays: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NaddrData {
|
||||||
|
identifier: string;
|
||||||
|
pubkey: string;
|
||||||
|
kind: number;
|
||||||
|
relays: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an nprofile NIP-19 entity
|
||||||
|
* @param nprofileEntity The bech32-encoded nprofile string
|
||||||
|
* @returns The decoded nprofile data or null if invalid
|
||||||
|
*/
|
||||||
|
export function decodeNprofile(nprofileEntity: string): NprofileData | null {
|
||||||
|
try {
|
||||||
|
const { type, data } = nip19.decode(nprofileEntity);
|
||||||
|
if (type !== 'nprofile') {
|
||||||
|
console.error(`Expected nprofile, got ${type}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have at least one relay by using the fallback if necessary
|
||||||
|
const profileData = data as NprofileData;
|
||||||
|
if (!profileData.relays || profileData.relays.length === 0) {
|
||||||
|
console.log(
|
||||||
|
`No relay hints in nprofile, using fallback relay: ${DEFAULT_FALLBACK_RELAY}`
|
||||||
|
);
|
||||||
|
profileData.relays = [DEFAULT_FALLBACK_RELAY];
|
||||||
|
}
|
||||||
|
|
||||||
|
return profileData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to decode nprofile: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an naddr NIP-19 entity
|
||||||
|
* @param naddrEntity The bech32-encoded naddr string
|
||||||
|
* @returns The decoded naddr data or null if invalid
|
||||||
|
*/
|
||||||
|
export function decodeNaddr(naddrEntity: string): NaddrData | null {
|
||||||
|
try {
|
||||||
|
const { type, data } = nip19.decode(naddrEntity);
|
||||||
|
if (type !== 'naddr') {
|
||||||
|
console.error(`Expected naddr, got ${type}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the kind is a DVM announcement
|
||||||
|
if (data.kind !== DVM_ANNOUNCEMENT_KIND) {
|
||||||
|
console.error(`Expected kind ${DVM_ANNOUNCEMENT_KIND}, got ${data.kind}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have at least one relay by using the fallback if necessary
|
||||||
|
const addrData = data as NaddrData;
|
||||||
|
if (!addrData.relays || addrData.relays.length === 0) {
|
||||||
|
console.log(
|
||||||
|
`No relay hints in naddr, using fallback relay: ${DEFAULT_FALLBACK_RELAY}`
|
||||||
|
);
|
||||||
|
addrData.relays = [DEFAULT_FALLBACK_RELAY];
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to decode naddr: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user