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:
701
.claude/skills/ndk/ndk-skill.md
Normal file
701
.claude/skills/ndk/ndk-skill.md
Normal file
@@ -0,0 +1,701 @@
|
||||
# NDK (Nostr Development Kit) - Claude Skill Reference
|
||||
|
||||
## Overview
|
||||
|
||||
NDK is the primary Nostr development kit with outbox-model support, designed for building Nostr applications with TypeScript/JavaScript. This reference is based on analyzing production usage in the Plebeian Market codebase.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. NDK Initialization
|
||||
|
||||
**Basic Pattern:**
|
||||
```typescript
|
||||
import NDK from '@nostr-dev-kit/ndk'
|
||||
|
||||
// Simple initialization
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: ['wss://relay.damus.io', 'wss://relay.nostr.band']
|
||||
})
|
||||
|
||||
await ndk.connect()
|
||||
```
|
||||
|
||||
**Store-based Pattern (Production):**
|
||||
```typescript
|
||||
// From src/lib/stores/ndk.ts
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: relays || defaultRelaysUrls,
|
||||
})
|
||||
|
||||
// Separate NDK for zaps on specialized relays
|
||||
const zapNdk = new NDK({
|
||||
explicitRelayUrls: ZAP_RELAYS,
|
||||
})
|
||||
|
||||
// Connect with timeout protection
|
||||
const connectPromise = ndk.connect()
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Connection timeout')), timeoutMs)
|
||||
)
|
||||
await Promise.race([connectPromise, timeoutPromise])
|
||||
```
|
||||
|
||||
### 2. Authentication & Signers
|
||||
|
||||
NDK supports multiple signer types for different authentication methods:
|
||||
|
||||
#### NIP-07 (Browser Extension)
|
||||
```typescript
|
||||
import { NDKNip07Signer } from '@nostr-dev-kit/ndk'
|
||||
|
||||
const signer = new NDKNip07Signer()
|
||||
await signer.blockUntilReady()
|
||||
ndk.signer = signer
|
||||
|
||||
const user = await signer.user()
|
||||
```
|
||||
|
||||
#### Private Key Signer
|
||||
```typescript
|
||||
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'
|
||||
|
||||
const signer = new NDKPrivateKeySigner(privateKeyHex)
|
||||
await signer.blockUntilReady()
|
||||
ndk.signer = signer
|
||||
|
||||
const user = await signer.user()
|
||||
```
|
||||
|
||||
#### NIP-46 (Remote Signer / Bunker)
|
||||
```typescript
|
||||
import { NDKNip46Signer } from '@nostr-dev-kit/ndk'
|
||||
|
||||
const localSigner = new NDKPrivateKeySigner(localPrivateKey)
|
||||
const remoteSigner = new NDKNip46Signer(ndk, bunkerUrl, localSigner)
|
||||
await remoteSigner.blockUntilReady()
|
||||
ndk.signer = remoteSigner
|
||||
|
||||
const user = await remoteSigner.user()
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Always call `blockUntilReady()` before using a signer
|
||||
- Store signer reference in your state management
|
||||
- Set `ndk.signer` to enable signing operations
|
||||
- Use `await signer.user()` to get the authenticated user
|
||||
|
||||
### 3. Event Creation & Publishing
|
||||
|
||||
#### Basic Event Pattern
|
||||
```typescript
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||
|
||||
// Create event
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 1 // Kind 1 = text note
|
||||
event.content = "Hello Nostr!"
|
||||
event.tags = [
|
||||
['t', 'nostr'],
|
||||
['p', recipientPubkey]
|
||||
]
|
||||
|
||||
// Sign and publish
|
||||
await event.sign() // Uses ndk.signer automatically
|
||||
await event.publish()
|
||||
|
||||
// Get event ID after signing
|
||||
console.log(event.id)
|
||||
```
|
||||
|
||||
#### Production Pattern with Error Handling
|
||||
```typescript
|
||||
// From src/publish/orders.tsx
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = ORDER_PROCESS_KIND
|
||||
event.content = orderNotes || ''
|
||||
event.tags = [
|
||||
['p', sellerPubkey],
|
||||
['subject', `Order for ${productName}`],
|
||||
['type', 'order-creation'],
|
||||
['order', orderId],
|
||||
['amount', totalAmount],
|
||||
['item', productRef, quantity.toString()],
|
||||
]
|
||||
|
||||
// Optional tags
|
||||
if (shippingRef) {
|
||||
event.tags.push(['shipping', shippingRef])
|
||||
}
|
||||
|
||||
try {
|
||||
await event.sign(signer) // Can pass explicit signer
|
||||
await event.publish()
|
||||
return event.id
|
||||
} catch (error) {
|
||||
console.error('Failed to publish event:', error)
|
||||
throw error
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Create event with `new NDKEvent(ndk)`
|
||||
- Set `kind`, `content`, and `tags` properties
|
||||
- Optional: Set `created_at` timestamp (defaults to now)
|
||||
- Call `await event.sign()` before publishing
|
||||
- Call `await event.publish()` to broadcast to relays
|
||||
- Access `event.id` after signing for the event hash
|
||||
|
||||
### 4. Querying Events with Filters
|
||||
|
||||
#### fetchEvents() - One-time Fetch
|
||||
```typescript
|
||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
||||
|
||||
// Simple filter
|
||||
const filter: NDKFilter = {
|
||||
kinds: [30402], // Product listings
|
||||
authors: [merchantPubkey],
|
||||
limit: 50
|
||||
}
|
||||
|
||||
const events = await ndk.fetchEvents(filter)
|
||||
// Returns Set<NDKEvent>
|
||||
|
||||
// Convert to array and process
|
||||
const eventArray = Array.from(events)
|
||||
const sortedEvents = eventArray.sort((a, b) =>
|
||||
(b.created_at || 0) - (a.created_at || 0)
|
||||
)
|
||||
```
|
||||
|
||||
#### Advanced Filters
|
||||
```typescript
|
||||
// Multiple kinds
|
||||
const filter: NDKFilter = {
|
||||
kinds: [16, 17], // Orders and payment receipts
|
||||
'#order': [orderId], // Tag filter (# prefix)
|
||||
since: Math.floor(Date.now() / 1000) - 86400, // Last 24 hours
|
||||
limit: 100
|
||||
}
|
||||
|
||||
// Event ID lookup
|
||||
const filter: NDKFilter = {
|
||||
ids: [eventIdHex],
|
||||
}
|
||||
|
||||
// Tag filtering
|
||||
const filter: NDKFilter = {
|
||||
kinds: [1],
|
||||
'#p': [pubkey], // Events mentioning pubkey
|
||||
'#t': ['nostr'], // Events with hashtag 'nostr'
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Subscriptions (Real-time)
|
||||
|
||||
#### Basic Subscription
|
||||
```typescript
|
||||
// From src/queries/blacklist.tsx
|
||||
const filter = {
|
||||
kinds: [10000],
|
||||
authors: [appPubkey],
|
||||
}
|
||||
|
||||
const subscription = ndk.subscribe(filter, {
|
||||
closeOnEose: false, // Keep open for real-time updates
|
||||
})
|
||||
|
||||
subscription.on('event', (event: NDKEvent) => {
|
||||
console.log('New event received:', event)
|
||||
// Process event
|
||||
})
|
||||
|
||||
subscription.on('eose', () => {
|
||||
console.log('End of stored events')
|
||||
})
|
||||
|
||||
// Cleanup
|
||||
subscription.stop()
|
||||
```
|
||||
|
||||
#### Production Pattern with React Query
|
||||
```typescript
|
||||
// From src/queries/orders.tsx
|
||||
useEffect(() => {
|
||||
if (!orderId || !ndk) return
|
||||
|
||||
const filter = {
|
||||
kinds: [ORDER_PROCESS_KIND, PAYMENT_RECEIPT_KIND],
|
||||
'#order': [orderId],
|
||||
}
|
||||
|
||||
const subscription = ndk.subscribe(filter, {
|
||||
closeOnEose: false,
|
||||
})
|
||||
|
||||
subscription.on('event', (newEvent) => {
|
||||
// Invalidate React Query cache to trigger refetch
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: orderKeys.details(orderId)
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
subscription.stop()
|
||||
}
|
||||
}, [orderId, ndk, queryClient])
|
||||
```
|
||||
|
||||
#### Monitoring Specific Events
|
||||
```typescript
|
||||
// From src/queries/payment.tsx - Payment receipt monitoring
|
||||
const receiptFilter = {
|
||||
kinds: [17], // Payment receipts
|
||||
'#order': [orderId],
|
||||
'#payment-request': [invoiceId],
|
||||
since: sessionStartTime - 30, // Clock skew buffer
|
||||
}
|
||||
|
||||
const subscription = ndk.subscribe(receiptFilter, {
|
||||
closeOnEose: false,
|
||||
})
|
||||
|
||||
subscription.on('event', (receiptEvent: NDKEvent) => {
|
||||
// Verify this is the correct invoice
|
||||
const paymentRequestTag = receiptEvent.tags.find(
|
||||
tag => tag[0] === 'payment-request'
|
||||
)
|
||||
|
||||
if (paymentRequestTag?.[1] === invoiceId) {
|
||||
const paymentTag = receiptEvent.tags.find(tag => tag[0] === 'payment')
|
||||
const preimage = paymentTag?.[3] || 'external-payment'
|
||||
|
||||
// Stop subscription after finding payment
|
||||
subscription.stop()
|
||||
handlePaymentReceived(preimage)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Key Subscription Patterns:**
|
||||
- Use `closeOnEose: false` for real-time monitoring
|
||||
- Use `closeOnEose: true` for one-time historical fetch
|
||||
- Always call `subscription.stop()` in cleanup
|
||||
- Listen to both `'event'` and `'eose'` events
|
||||
- Filter events in the handler for specific conditions
|
||||
- Integrate with React Query for reactive UI updates
|
||||
|
||||
### 6. User & Profile Handling
|
||||
|
||||
#### Fetching User Profiles
|
||||
```typescript
|
||||
// From src/queries/profiles.tsx
|
||||
|
||||
// By npub
|
||||
const user = ndk.getUser({ npub })
|
||||
const profile = await user.fetchProfile()
|
||||
// Returns NDKUserProfile with name, picture, about, etc.
|
||||
|
||||
// By hex pubkey
|
||||
const user = ndk.getUser({ hexpubkey: pubkey })
|
||||
const profile = await user.fetchProfile()
|
||||
|
||||
// By NIP-05 identifier
|
||||
const user = await ndk.getUserFromNip05('user@domain.com')
|
||||
if (user) {
|
||||
const profile = await user.fetchProfile()
|
||||
}
|
||||
|
||||
// Profile fields
|
||||
const name = profile?.name || profile?.displayName
|
||||
const avatar = profile?.picture || profile?.image
|
||||
const bio = profile?.about
|
||||
const nip05 = profile?.nip05
|
||||
const lud16 = profile?.lud16 // Lightning address
|
||||
```
|
||||
|
||||
#### Getting Current User
|
||||
```typescript
|
||||
// Active user (authenticated)
|
||||
const user = ndk.activeUser
|
||||
|
||||
// From signer
|
||||
const user = await ndk.signer?.user()
|
||||
|
||||
// User properties
|
||||
const pubkey = user.pubkey // Hex format
|
||||
const npub = user.npub // NIP-19 encoded
|
||||
```
|
||||
|
||||
### 7. NDK Event Object
|
||||
|
||||
#### Essential Properties
|
||||
```typescript
|
||||
interface NDKEvent {
|
||||
id: string // Event hash (after signing)
|
||||
kind: number // Event kind
|
||||
content: string // Event content
|
||||
tags: NDKTag[] // Array of tag arrays
|
||||
created_at?: number // Unix timestamp
|
||||
pubkey?: string // Author pubkey (after signing)
|
||||
sig?: string // Signature (after signing)
|
||||
|
||||
// Methods
|
||||
sign(signer?: NDKSigner): Promise<void>
|
||||
publish(): Promise<void>
|
||||
tagValue(tagName: string): string | undefined
|
||||
}
|
||||
|
||||
type NDKTag = string[] // e.g., ['p', pubkey, relay, petname]
|
||||
```
|
||||
|
||||
#### Tag Helpers
|
||||
```typescript
|
||||
// Get first value of a tag
|
||||
const orderId = event.tagValue('order')
|
||||
const recipientPubkey = event.tagValue('p')
|
||||
|
||||
// Find specific tag
|
||||
const paymentTag = event.tags.find(tag => tag[0] === 'payment')
|
||||
const preimage = paymentTag?.[3]
|
||||
|
||||
// Get all tags of a type
|
||||
const pTags = event.tags.filter(tag => tag[0] === 'p')
|
||||
const allPubkeys = pTags.map(tag => tag[1])
|
||||
|
||||
// Common tag patterns
|
||||
event.tags.push(['p', pubkey]) // Mention
|
||||
event.tags.push(['e', eventId]) // Reference event
|
||||
event.tags.push(['t', 'nostr']) // Hashtag
|
||||
event.tags.push(['d', identifier]) // Replaceable event ID
|
||||
event.tags.push(['a', '30402:pubkey:d-tag']) // Addressable event reference
|
||||
```
|
||||
|
||||
### 8. Parameterized Replaceable Events (NIP-33)
|
||||
|
||||
Used for products, collections, profiles that need updates:
|
||||
|
||||
```typescript
|
||||
// Product listing (kind 30402)
|
||||
const event = new NDKEvent(ndk)
|
||||
event.kind = 30402
|
||||
event.content = JSON.stringify(productDetails)
|
||||
event.tags = [
|
||||
['d', productSlug], // Unique identifier
|
||||
['title', productName],
|
||||
['price', price, currency],
|
||||
['image', imageUrl],
|
||||
['shipping', shippingRef],
|
||||
]
|
||||
|
||||
await event.sign()
|
||||
await event.publish()
|
||||
|
||||
// Querying replaceable events
|
||||
const filter = {
|
||||
kinds: [30402],
|
||||
authors: [merchantPubkey],
|
||||
'#d': [productSlug], // Specific product
|
||||
}
|
||||
|
||||
const events = await ndk.fetchEvents(filter)
|
||||
// Returns only the latest version due to replaceable nature
|
||||
```
|
||||
|
||||
### 9. Relay Management
|
||||
|
||||
#### Getting Relay Status
|
||||
```typescript
|
||||
// From src/lib/stores/ndk.ts
|
||||
const connectedRelays = Array.from(ndk.pool?.relays.values() || [])
|
||||
.filter(relay => relay.status === 1) // 1 = connected
|
||||
.map(relay => relay.url)
|
||||
|
||||
const outboxRelays = Array.from(ndk.outboxPool?.relays.values() || [])
|
||||
```
|
||||
|
||||
#### Adding Relays
|
||||
```typescript
|
||||
// Add explicit relays
|
||||
ndk.addExplicitRelay('wss://relay.example.com')
|
||||
|
||||
// Multiple relays
|
||||
const relays = ['wss://relay1.com', 'wss://relay2.com']
|
||||
relays.forEach(url => ndk.addExplicitRelay(url))
|
||||
```
|
||||
|
||||
### 10. Common Patterns & Best Practices
|
||||
|
||||
#### Null Safety
|
||||
```typescript
|
||||
// Always check NDK initialization
|
||||
const ndk = ndkActions.getNDK()
|
||||
if (!ndk) throw new Error('NDK not initialized')
|
||||
|
||||
// Check signer before operations requiring auth
|
||||
const signer = ndk.signer
|
||||
if (!signer) throw new Error('No active signer')
|
||||
|
||||
// Check user authentication
|
||||
const user = ndk.activeUser
|
||||
if (!user) throw new Error('Not authenticated')
|
||||
```
|
||||
|
||||
#### Error Handling
|
||||
```typescript
|
||||
try {
|
||||
const events = await ndk.fetchEvents(filter)
|
||||
if (events.size === 0) {
|
||||
return null // No results found
|
||||
}
|
||||
return Array.from(events)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch events:', error)
|
||||
throw new Error('Could not fetch data from relays')
|
||||
}
|
||||
```
|
||||
|
||||
#### Connection Lifecycle
|
||||
```typescript
|
||||
// Initialize once at app startup
|
||||
const ndk = new NDK({ explicitRelayUrls: relays })
|
||||
|
||||
// Connect with timeout
|
||||
await Promise.race([
|
||||
ndk.connect(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), 10000)
|
||||
)
|
||||
])
|
||||
|
||||
// Check connection status
|
||||
const isConnected = ndk.pool?.connectedRelays().length > 0
|
||||
|
||||
// Reconnect if needed
|
||||
if (!isConnected) {
|
||||
await ndk.connect()
|
||||
}
|
||||
```
|
||||
|
||||
#### Subscription Cleanup
|
||||
```typescript
|
||||
// In React components
|
||||
useEffect(() => {
|
||||
if (!ndk) return
|
||||
|
||||
const sub = ndk.subscribe(filter, { closeOnEose: false })
|
||||
|
||||
sub.on('event', handleEvent)
|
||||
sub.on('eose', handleEose)
|
||||
|
||||
// Critical: cleanup on unmount
|
||||
return () => {
|
||||
sub.stop()
|
||||
}
|
||||
}, [dependencies])
|
||||
```
|
||||
|
||||
#### Event Validation
|
||||
```typescript
|
||||
// Check required fields before processing
|
||||
if (!event.pubkey) {
|
||||
console.error('Event missing pubkey')
|
||||
return
|
||||
}
|
||||
|
||||
if (!event.created_at) {
|
||||
console.error('Event missing timestamp')
|
||||
return
|
||||
}
|
||||
|
||||
// Verify event age
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const eventAge = now - (event.created_at || 0)
|
||||
if (eventAge > 86400) { // Older than 24 hours
|
||||
console.log('Event is old, skipping')
|
||||
return
|
||||
}
|
||||
|
||||
// Validate specific tags exist
|
||||
const orderId = event.tagValue('order')
|
||||
if (!orderId) {
|
||||
console.error('Order event missing order ID')
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 11. Common Event Kinds
|
||||
|
||||
```typescript
|
||||
// NIP-01: Basic Events
|
||||
const KIND_METADATA = 0 // User profile
|
||||
const KIND_TEXT_NOTE = 1 // Short text note
|
||||
const KIND_RECOMMEND_RELAY = 2 // Relay recommendation
|
||||
|
||||
// NIP-04: Encrypted Direct Messages
|
||||
const KIND_ENCRYPTED_DM = 4
|
||||
|
||||
// NIP-25: Reactions
|
||||
const KIND_REACTION = 7
|
||||
|
||||
// NIP-51: Lists
|
||||
const KIND_MUTE_LIST = 10000
|
||||
const KIND_PIN_LIST = 10001
|
||||
const KIND_RELAY_LIST = 10002
|
||||
|
||||
// NIP-57: Lightning Zaps
|
||||
const KIND_ZAP_REQUEST = 9734
|
||||
const KIND_ZAP_RECEIPT = 9735
|
||||
|
||||
// Marketplace (Plebeian/Gamma spec)
|
||||
const ORDER_PROCESS_KIND = 16 // Order processing
|
||||
const PAYMENT_RECEIPT_KIND = 17 // Payment receipts
|
||||
const DIRECT_MESSAGE_KIND = 14 // Direct messages
|
||||
const ORDER_GENERAL_KIND = 27 // General order events
|
||||
const SHIPPING_KIND = 30405 // Shipping options
|
||||
const PRODUCT_KIND = 30402 // Product listings
|
||||
const COLLECTION_KIND = 30401 // Product collections
|
||||
const REVIEW_KIND = 30407 // Product reviews
|
||||
|
||||
// Application Handlers
|
||||
const APP_HANDLER_KIND = 31990 // NIP-89 app handlers
|
||||
```
|
||||
|
||||
## Integration with TanStack Query
|
||||
|
||||
NDK works excellently with TanStack Query for reactive data fetching:
|
||||
|
||||
### Query Functions
|
||||
```typescript
|
||||
// From src/queries/products.tsx
|
||||
export const fetchProductsByPubkey = async (pubkey: string) => {
|
||||
const ndk = ndkActions.getNDK()
|
||||
if (!ndk) throw new Error('NDK not initialized')
|
||||
|
||||
const filter: NDKFilter = {
|
||||
kinds: [30402],
|
||||
authors: [pubkey],
|
||||
}
|
||||
|
||||
const events = await ndk.fetchEvents(filter)
|
||||
return Array.from(events).map(parseProductEvent)
|
||||
}
|
||||
|
||||
export const useProductsByPubkey = (pubkey: string) => {
|
||||
return useQuery({
|
||||
queryKey: productKeys.byAuthor(pubkey),
|
||||
queryFn: () => fetchProductsByPubkey(pubkey),
|
||||
enabled: !!pubkey,
|
||||
staleTime: 30000,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Combining Queries with Subscriptions
|
||||
```typescript
|
||||
// Query for initial data
|
||||
const { data: order, refetch } = useQuery({
|
||||
queryKey: orderKeys.details(orderId),
|
||||
queryFn: () => fetchOrderById(orderId),
|
||||
enabled: !!orderId,
|
||||
})
|
||||
|
||||
// Subscription for real-time updates
|
||||
useEffect(() => {
|
||||
if (!orderId || !ndk) return
|
||||
|
||||
const sub = ndk.subscribe(
|
||||
{ kinds: [16, 17], '#order': [orderId] },
|
||||
{ closeOnEose: false }
|
||||
)
|
||||
|
||||
sub.on('event', () => {
|
||||
// Invalidate query to trigger refetch
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: orderKeys.details(orderId)
|
||||
})
|
||||
})
|
||||
|
||||
return () => sub.stop()
|
||||
}, [orderId, ndk, queryClient])
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Events Not Received
|
||||
- Check relay connections: `ndk.pool?.connectedRelays()`
|
||||
- Verify filter syntax (especially tag filters with `#` prefix)
|
||||
- Check event timestamps match filter's `since`/`until`
|
||||
- Ensure `closeOnEose: false` for real-time subscriptions
|
||||
|
||||
### Signing Errors
|
||||
- Verify signer is initialized: `await signer.blockUntilReady()`
|
||||
- Check signer is set: `ndk.signer !== undefined`
|
||||
- For NIP-07, ensure browser extension is installed and enabled
|
||||
- For NIP-46, verify bunker URL and local signer are correct
|
||||
|
||||
### Connection Timeouts
|
||||
- Implement connection timeout pattern shown above
|
||||
- Try connecting to fewer, more reliable relays initially
|
||||
- Use fallback relays in production
|
||||
|
||||
### Duplicate Events
|
||||
- NDK deduplicates by event ID automatically
|
||||
- For subscriptions, track processed event IDs if needed
|
||||
- Use replaceable events (kinds 10000-19999, 30000-39999) when appropriate
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Batching Queries
|
||||
```typescript
|
||||
// Instead of multiple fetchEvents calls
|
||||
const [products, orders, profiles] = await Promise.all([
|
||||
ndk.fetchEvents(productFilter),
|
||||
ndk.fetchEvents(orderFilter),
|
||||
ndk.fetchEvents(profileFilter),
|
||||
])
|
||||
```
|
||||
|
||||
### Limiting Results
|
||||
```typescript
|
||||
const filter = {
|
||||
kinds: [1],
|
||||
authors: [pubkey],
|
||||
limit: 50, // Limit results
|
||||
since: recentTimestamp, // Only recent events
|
||||
}
|
||||
```
|
||||
|
||||
### Caching with React Query
|
||||
```typescript
|
||||
export const useProfile = (npub: string) => {
|
||||
return useQuery({
|
||||
queryKey: profileKeys.byNpub(npub),
|
||||
queryFn: () => fetchProfileByNpub(npub),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
cacheTime: 30 * 60 * 1000, // 30 minutes
|
||||
enabled: !!npub,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **NDK GitHub**: https://github.com/nostr-dev-kit/ndk
|
||||
- **NDK Documentation**: https://ndk.fyi
|
||||
- **Nostr NIPs**: https://github.com/nostr-protocol/nips
|
||||
- **Production Example**: Plebeian Market codebase
|
||||
|
||||
## Key Files in This Codebase
|
||||
|
||||
- `src/lib/stores/ndk.ts` - NDK store and initialization
|
||||
- `src/lib/stores/auth.ts` - Authentication with NDK signers
|
||||
- `src/queries/*.tsx` - Query patterns with NDK
|
||||
- `src/publish/*.tsx` - Event publishing patterns
|
||||
- `scripts/gen_*.ts` - Event creation examples
|
||||
|
||||
---
|
||||
|
||||
*This reference is based on NDK version used in production and real-world patterns from the Plebeian Market application.*
|
||||
|
||||
Reference in New Issue
Block a user