mcpbeat

Using Nostr

besoeasy/using-nostr

Post notes, send encrypted messages, and interact with relays using the Nostr protocol.

3k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
127
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/besoeasy/open-skills --skill using-nostr

The instruction itself

24 sections, as written by the author

NOSTR Posting Skill

Using nostr-sdk library

Source: https://github.com/besoeasy/nostr-sdk

Overview

Post messages, send encrypted DMs, and interact with the Nostr decentralized social protocol using minimal direct exports from the nostr-sdk module.

Installation:

npm install nostr-sdk

Key Concepts:

  • nsec: Private key in bech32 format (starts with nsec1)
  • npub: Public key in bech32 format (starts with npub1)
  • Relays: WebSocket servers that propagate Nostr events
  • Events: Signed JSON objects representing posts, DMs, etc.
  • POW: Proof of Work (mining) to reduce spam

Default Relays:

  • wss://relay.damus.io
  • wss://nos.lol
  • wss://relay.snort.social
  • wss://nostr-pub.wellorder.net
  • wss://nostr.oxtr.dev
  • And 9+ more for maximum reach

Skills

post_public_note

Post a public text note to Nostr.

Usage:

const { posttoNostr } = require("nostr-sdk");

const result = await posttoNostr("Hello Nostr! #introduction", {
  nsec: "nsec1...your-private-key",
  tags: [],
  relays: null,
  powDifficulty: 4
});
console.log(result);

Parameters:

  • message: Text content to post
  • tags: Optional array of tags (e.g., [['t', 'topic']])
  • relays: Optional custom relay list (uses defaults if null)
  • powDifficulty: Proof of work difficulty (default: 4, 0 to disable)

Auto-extracted Tags:

  • Hashtags: #nostr["t", "nostr"]
  • Mentions: @npub1...["p", <pubkey>]
  • Links: URLs automatically preserved
  • Notes: note1... references → ["e", <event-id>]

Response:

{
  success: true,
  eventId: "abc123...",
  published: 12,      // Successfully published to 12 relays
  failed: 2,          // Failed on 2 relays
  totalRelays: 14,
  powDifficulty: 4,
  errors: []
}

When to use:

  • User wants to post a public message
  • Sharing content with hashtags
  • Broadcasting announcements

reply_to_post

Reply to an existing Nostr post.

Usage:

const { replyToPost } = require("nostr-sdk");

const result = await replyToPost(
  "note1...event-id",           // Event ID (note or hex format)
  "Great post! @npub1...author", // Reply message
  "npub1...author-pubkey",      // Author's public key
  [],                           // Additional tags
  null,                         // Use default relays
  4                             // POW difficulty
);

When to use:

  • Responding to a specific post
  • Thread conversations
  • Engaging with content

send_encrypted_dm (NIP-4)

Send encrypted direct message using legacy NIP-4 standard.

Usage:

const { sendmessage } = require("nostr-sdk");

const result = await sendmessage(
  "npub1...recipient",    // Recipient's public key
  "Secret message here",  // Message content
  { nsec: "nsec1...your-private-key" }
);

When to use:

  • Compatibility with older Nostr clients
  • Basic encrypted messaging
  • Wide client support

Limitations:

  • Sender/recipient metadata visible
  • Older encryption (NIP-04)

send_encrypted_dm_modern (NIP-17)

Send gift-wrapped encrypted message using NIP-17 (recommended).

Usage:

const { sendMessageNIP17 } = require("nostr-sdk");

const result = await sendMessageNIP17(
  "npub1...recipient",    // Recipient's public key
  "Private message!",     // Message content
  { nsec: "nsec1...your-private-key" }
);

Benefits:

  • Sealed sender (hides who sent the message)
  • Better metadata protection
  • Modern NIP-44 encryption
  • Ephemeral keys for each message

When to use:

  • Maximum privacy needed
  • Modern applications
  • Hiding sender identity

receive_messages (NIP-4)

Listen for incoming direct messages.

Usage:

const { getmessage } = require("nostr-sdk");

const unsubscribe = getmessage((message) => {
  console.log("From:", message.senderNpub);
  console.log("Message:", message.content);
  console.log("Time:", new Date(message.timestamp * 1000));
}, {
  nsec: "nsec1...your-private-key",
  since: Math.floor(Date.now() / 1000) - 3600  // Last hour
});

// Stop listening:
// unsubscribe();

Message Object:

{
  id: "event-id",
  sender: "hex-pubkey",
  senderNpub: "npub1...",
  content: "decrypted message",
  timestamp: 1234567890,
  event: { /* full event */ }
}

When to use:

  • Building a chat bot
  • Receiving DMs
  • Monitoring for messages

receive_messages_modern (NIP-17)

Listen for incoming NIP-17 gift-wrapped messages.

Usage:

const { getMessageNIP17 } = require("nostr-sdk");

const unsubscribe = getMessageNIP17((message) => {
  console.log("From:", message.senderNpub);
  console.log("Content:", message.content);
  console.log("Wrapped ID:", message.wrappedEventId);
}, {
  nsec: "nsec1...your-private-key",
  since: Math.floor(Date.now() / 1000) - 300  // Last 5 minutes
});

// Stop listening:
// unsubscribe();

When to use:

  • Receiving modern private messages
  • Maximum privacy for incoming DMs
  • Supporting NIP-17 protocol

get_global_feed

Fetch recent posts from the global Nostr feed.

Usage:

const { getGlobalFeed } = require("nostr-sdk");

const events = await getGlobalFeed({
  limit: 50,                                    // Max 50 posts
  since: Math.floor(Date.now() / 1000) - 3600, // Last hour
  until: null,                                  // Up to now
  kinds: [1],                                   // Text notes only
  authors: null,                                // All authors
  relays: null                                  // Use defaults
});

events.forEach(event => {
  console.log("Author:", event.authorNpub);
  console.log("Content:", event.content);
  console.log("Note ID:", event.noteId);
  console.log("Posted:", event.createdAtDate);
});

When to use:

  • Building a feed reader
  • Monitoring public posts
  • Trending content analysis

generate_keys

Generate new Nostr key pair.

Usage:

const { generateNewKey } = require("nostr-sdk");

const keys = generateNewKey();
console.log(keys);
// {
//   privateKey: "hex-private-key",
//   publicKey: "hex-public-key",
//   nsec: "nsec1...",
//   npub: "npub1..."
// }

Quick Generate:

const { generateRandomNsec } = require("nostr-sdk");
const nsec = generateRandomNsec();
console.log(nsec); // nsec1...

convert_keys

Convert between key formats.

Usage:

const { nsecToPublic } = require("nostr-sdk");

const publicInfo = nsecToPublic("nsec1...your-key");
console.log(publicInfo);
// {
//   publicKey: "hex-public-key",
//   npub: "npub1..."
// }

Quick Start Examples

Example 1: Post a Message

const { posttoNostr } = require("nostr-sdk");

async function postHello() {
  const result = await posttoNostr("Hello from my bot! #nostr #automation", {
    nsec: "nsec1...your-private-key"
  });
  
  console.log("Posted:", result.eventId);
}

postHello();

Example 2: Send Private DM

const { sendMessageNIP17 } = require("nostr-sdk");

async function sendPrivateMessage() {
  const result = await sendMessageNIP17(
    "npub1...recipient",
    "This is a secret message!",
    { nsec: "nsec1...your-private-key" }
  );
  
  console.log("Sent:", result.success ? "Yes" : "No");
}

sendPrivateMessage();

Example 3: Listen for DMs

const { getMessageNIP17 } = require("nostr-sdk");

console.log("Listening for messages...");

const unsubscribe = getMessageNIP17((msg) => {
  console.log(`Message from ${msg.senderNpub}: ${msg.content}`);
}, {
  nsec: "nsec1...your-private-key"
});

// Keep running or call unsubscribe() to stop

Example 4: Quick Post (No Setup)

const { posttoNostr } = require("nostr-sdk");

// Auto-generates keys if not provided
const result = await posttoNostr("Quick post!", {
  nsec: "nsec1...your-key"  // Optional - generates new if omitted
});

Decision Tree

User wants to post to Nostr?
├─ Is it a public post?
│  ├─ Is it a reply to another post?
│  │  ├─ YES → Use replyToPost()
│  │  └─ NO → Use posttoNostr()
│  └─ Need spam protection?
│     ├─ YES → Set powDifficulty to 4+
│     └─ NO → Set powDifficulty to 0
│
├─ Is it a private message?
│  ├─ Maximum privacy needed?
│  │  ├─ YES → Use sendMessageNIP17()
│  │  └─ NO → Use sendmessage()
│  │
│  └─ Need to receive messages?
│     ├─ Use NIP-17 → getMessageNIP17()
│     └─ Use NIP-4 (legacy) → getmessage()
│
└─ Need to read posts?
   └─ Use getGlobalFeed()

Key Management

Security Best Practices:

  • Never commit nsec keys to git
  • Store keys in environment variables or secure vaults
  • Generate new keys for testing
  • Use different keys for different purposes

Environment Variables:

export NOSTR_NSEC="nsec1...your-private-key"
const { posttoNostr } = require("nostr-sdk");

await posttoNostr("Health check log", {
  nsec: process.env.NOSTR_NSEC
});

Error Handling

Common Errors:

  • Private key not set → Provide nsec or generate keys
  • Invalid nsec format → Check bech32 encoding
  • Failed to post to Nostr → Check relay connections
  • Failed to decrypt message → Wrong private key for recipient

Best Practice:

const { posttoNostr } = require("nostr-sdk");

try {
  const result = await posttoNostr("Hello", {
    nsec: process.env.NOSTR_NSEC
  });
  if (!result.success) {
    console.error("Failed to publish:", result.errors);
  }
} catch (error) {
  console.error("Error:", error.message);
}

Cleanup

Direct-export functions do not require a class instance, so there is no client cleanup step.


Resources

  • Library: https://github.com/besoeasy/nostr-sdk
  • Nostr Protocol: https://nostr.com
  • NIPs (Nostr Implementation Possibilities): https://github.com/nostr-protocol/nips
  • Key Tools:
  • https://nostrcheck.me (key converter)
  • https://snort.social (web client)
  • https://damus.io (iOS client)

How to use it

Copy the folder

Take besoeasy/using-nostr from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.

Install what it needs

The instructions reference npm. Without those the skill loads but fails at the first command.