Add NDK skill documentation and examples
- Introduced comprehensive documentation for the Nostr Development Kit (NDK) including an overview, quick reference, and troubleshooting guide. - Added detailed examples covering initialization, authentication, event publishing, querying, and user profile management. - Structured the documentation to facilitate quick lookups and deep learning, based on real-world usage patterns from the Plebeian Market application. - Created an index for examples to enhance usability and navigation. - Bumped version to 1.0.0 to reflect the addition of this new skill set.
This commit is contained in:
376
.claude/skills/ndk/examples/03-publishing-events.ts
Normal file
376
.claude/skills/ndk/examples/03-publishing-events.ts
Normal file
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* NDK Event Publishing Patterns
|
||||
*
|
||||
* Examples from: src/publish/orders.tsx, scripts/gen_products.ts
|
||||
*/
|
||||
|
||||
import NDK, { NDKEvent, NDKTag } from '@nostr-dev-kit/ndk'
|
||||
|
||||
// ============================================================
|
||||
// BASIC EVENT PUBLISHING
|
||||
// ============================================================
|
||||
|
||||
const publishBasicNote = async (ndk: NDK, content: string) => {
|
||||
// Create event
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 1 // Text note
|
||||
event.content = content
|
||||
event.tags = []
|
||||
|
||||
// Sign and publish
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
console.log('✅ Published note:', event.id)
|
||||
return event.id
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// EVENT WITH TAGS
|
||||
// ============================================================
|
||||
|
||||
const publishNoteWithTags = async (
|
||||
ndk: NDK,
|
||||
content: string,
|
||||
options: {
|
||||
mentions?: string[] // pubkeys to mention
|
||||
hashtags?: string[]
|
||||
replyTo?: string // event ID
|
||||
}
|
||||
) => {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 1
|
||||
event.content = content
|
||||
event.tags = []
|
||||
|
||||
// Add mentions
|
||||
if (options.mentions) {
|
||||
options.mentions.forEach(pubkey => {
|
||||
event.tags.push(['p', pubkey])
|
||||
})
|
||||
}
|
||||
|
||||
// Add hashtags
|
||||
if (options.hashtags) {
|
||||
options.hashtags.forEach(tag => {
|
||||
event.tags.push(['t', tag])
|
||||
})
|
||||
}
|
||||
|
||||
// Add reply
|
||||
if (options.replyTo) {
|
||||
event.tags.push(['e', options.replyTo, '', 'reply'])
|
||||
}
|
||||
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
return event.id
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PRODUCT LISTING (PARAMETERIZED REPLACEABLE EVENT)
|
||||
// ============================================================
|
||||
|
||||
interface ProductData {
|
||||
slug: string // Unique identifier
|
||||
title: string
|
||||
description: string
|
||||
price: number
|
||||
currency: string
|
||||
images: string[]
|
||||
shippingRefs?: string[]
|
||||
category?: string
|
||||
}
|
||||
|
||||
const publishProduct = async (ndk: NDK, product: ProductData) => {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 30402 // Product listing kind
|
||||
event.content = product.description
|
||||
|
||||
// Build tags
|
||||
event.tags = [
|
||||
['d', product.slug], // Unique identifier (required for replaceable)
|
||||
['title', product.title],
|
||||
['price', product.price.toString(), product.currency],
|
||||
]
|
||||
|
||||
// Add images
|
||||
product.images.forEach(image => {
|
||||
event.tags.push(['image', image])
|
||||
})
|
||||
|
||||
// Add shipping options
|
||||
if (product.shippingRefs) {
|
||||
product.shippingRefs.forEach(ref => {
|
||||
event.tags.push(['shipping', ref])
|
||||
})
|
||||
}
|
||||
|
||||
// Add category
|
||||
if (product.category) {
|
||||
event.tags.push(['t', product.category])
|
||||
}
|
||||
|
||||
// Optional: set custom timestamp
|
||||
event.created_at = Math.floor(Date.now() / 1000)
|
||||
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
console.log('✅ Published product:', product.title)
|
||||
return event.id
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ORDER CREATION EVENT
|
||||
// ============================================================
|
||||
|
||||
interface OrderData {
|
||||
orderId: string
|
||||
sellerPubkey: string
|
||||
productRef: string
|
||||
quantity: number
|
||||
totalAmount: string
|
||||
currency: string
|
||||
shippingRef?: string
|
||||
shippingAddress?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
const createOrder = async (ndk: NDK, order: OrderData) => {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 16 // Order processing kind
|
||||
event.content = order.notes || ''
|
||||
|
||||
// Required tags per spec
|
||||
event.tags = [
|
||||
['p', order.sellerPubkey],
|
||||
['subject', `Order ${order.orderId.substring(0, 8)}`],
|
||||
['type', 'order-creation'],
|
||||
['order', order.orderId],
|
||||
['amount', order.totalAmount],
|
||||
['item', order.productRef, order.quantity.toString()],
|
||||
]
|
||||
|
||||
// Optional tags
|
||||
if (order.shippingRef) {
|
||||
event.tags.push(['shipping', order.shippingRef])
|
||||
}
|
||||
|
||||
if (order.shippingAddress) {
|
||||
event.tags.push(['address', order.shippingAddress])
|
||||
}
|
||||
|
||||
if (order.email) {
|
||||
event.tags.push(['email', order.email])
|
||||
}
|
||||
|
||||
if (order.phone) {
|
||||
event.tags.push(['phone', order.phone])
|
||||
}
|
||||
|
||||
try {
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
console.log('✅ Order created:', order.orderId)
|
||||
return { success: true, eventId: event.id }
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create order:', error)
|
||||
return { success: false, error }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// STATUS UPDATE EVENT
|
||||
// ============================================================
|
||||
|
||||
const publishStatusUpdate = async (
|
||||
ndk: NDK,
|
||||
orderId: string,
|
||||
recipientPubkey: string,
|
||||
status: 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled',
|
||||
notes?: string
|
||||
) => {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 16
|
||||
event.content = notes || `Order status updated to ${status}`
|
||||
event.tags = [
|
||||
['p', recipientPubkey],
|
||||
['subject', 'order-info'],
|
||||
['type', 'status-update'],
|
||||
['order', orderId],
|
||||
['status', status],
|
||||
]
|
||||
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
return event.id
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// BATCH PUBLISHING
|
||||
// ============================================================
|
||||
|
||||
const publishMultipleEvents = async (
|
||||
ndk: NDK,
|
||||
events: Array<{ kind: number; content: string; tags: NDKTag[] }>
|
||||
) => {
|
||||
const results = []
|
||||
|
||||
for (const eventData of events) {
|
||||
try {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = eventData.kind
|
||||
event.content = eventData.content
|
||||
event.tags = eventData.tags
|
||||
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
results.push({ success: true, eventId: event.id })
|
||||
} catch (error) {
|
||||
results.push({ success: false, error })
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PUBLISH WITH CUSTOM SIGNER
|
||||
// ============================================================
|
||||
|
||||
import { NDKSigner } from '@nostr-dev-kit/ndk'
|
||||
|
||||
const publishWithCustomSigner = async (
|
||||
ndk: NDK,
|
||||
signer: NDKSigner,
|
||||
eventData: { kind: number; content: string; tags: NDKTag[] }
|
||||
) => {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = eventData.kind
|
||||
event.content = eventData.content
|
||||
event.tags = eventData.tags
|
||||
|
||||
// Sign with specific signer (not ndk.signer)
|
||||
await event.sign(signer)
|
||||
await event.publish()
|
||||
|
||||
return event.id
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ERROR HANDLING PATTERN
|
||||
// ============================================================
|
||||
|
||||
const publishWithErrorHandling = async (
|
||||
ndk: NDK,
|
||||
eventData: { kind: number; content: string; tags: NDKTag[] }
|
||||
) => {
|
||||
// Validate NDK
|
||||
if (!ndk) {
|
||||
throw new Error('NDK not initialized')
|
||||
}
|
||||
|
||||
// Validate signer
|
||||
if (!ndk.signer) {
|
||||
throw new Error('No active signer. Please login first.')
|
||||
}
|
||||
|
||||
try {
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = eventData.kind
|
||||
event.content = eventData.content
|
||||
event.tags = eventData.tags
|
||||
|
||||
// Sign
|
||||
await event.sign()
|
||||
|
||||
// Verify signature
|
||||
if (!event.sig) {
|
||||
throw new Error('Event signing failed')
|
||||
}
|
||||
|
||||
// Publish
|
||||
await event.publish()
|
||||
|
||||
// Verify event ID
|
||||
if (!event.id) {
|
||||
throw new Error('Event ID not generated')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
eventId: event.id,
|
||||
pubkey: event.pubkey
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Publishing failed:', error)
|
||||
|
||||
if (error instanceof Error) {
|
||||
// Handle specific error types
|
||||
if (error.message.includes('relay')) {
|
||||
throw new Error('Failed to publish to relays. Check connection.')
|
||||
}
|
||||
if (error.message.includes('sign')) {
|
||||
throw new Error('Failed to sign event. Check signer.')
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// USAGE EXAMPLE
|
||||
// ============================================================
|
||||
|
||||
async function publishingExample(ndk: NDK) {
|
||||
// Simple note
|
||||
await publishBasicNote(ndk, 'Hello Nostr!')
|
||||
|
||||
// Note with tags
|
||||
await publishNoteWithTags(ndk, 'Check out this product!', {
|
||||
hashtags: ['marketplace', 'nostr'],
|
||||
mentions: ['pubkey123...']
|
||||
})
|
||||
|
||||
// Product listing
|
||||
await publishProduct(ndk, {
|
||||
slug: 'bitcoin-tshirt',
|
||||
title: 'Bitcoin T-Shirt',
|
||||
description: 'High quality Bitcoin t-shirt',
|
||||
price: 25,
|
||||
currency: 'USD',
|
||||
images: ['https://example.com/image.jpg'],
|
||||
category: 'clothing'
|
||||
})
|
||||
|
||||
// Order
|
||||
await createOrder(ndk, {
|
||||
orderId: 'order-123',
|
||||
sellerPubkey: 'seller-pubkey',
|
||||
productRef: '30402:pubkey:bitcoin-tshirt',
|
||||
quantity: 1,
|
||||
totalAmount: '25.00',
|
||||
currency: 'USD',
|
||||
email: 'customer@example.com'
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
publishBasicNote,
|
||||
publishNoteWithTags,
|
||||
publishProduct,
|
||||
createOrder,
|
||||
publishStatusUpdate,
|
||||
publishMultipleEvents,
|
||||
publishWithCustomSigner,
|
||||
publishWithErrorHandling
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user