# ARCHIVED: NDK Comprehensive Guide for POWR App

**ARCHIVED:** This document has been superseded by [docs/technical/ndk/comprehensive_guide.md](../technical/ndk/comprehensive_guide.md) as part of the documentation reorganization.

**UPDATED FOR MVP: This guide has been consolidated and aligned with our simplified social approach for the MVP release.**

This guide combines key information from our various NDK analysis documents and the ndk-mobile package to provide a comprehensive reference for implementing Nostr features in POWR app. It's organized to support our MVP strategy with a focus on the core NDK capabilities we need.

## Table of Contents

1. [NDK Core Concepts](#ndk-core-concepts)
2. [Proper State Management](#proper-state-management)
3. [Subscription Lifecycle](#subscription-lifecycle)
4. [NIP-19 and Encoding/Decoding](#nip-19-and-encodingdecoding)
5. [Context Provider Pattern](#context-provider-pattern)
6. [Mobile-Specific Considerations](#mobile-specific-considerations)
7. [Best Practices for POWR MVP](#best-practices-for-powr-mvp)

## NDK Core Concepts

NDK (Nostr Development Kit) provides a set of tools for working with the Nostr protocol. Key components include:

- **NDK Instance**: Central object for interacting with the Nostr network
- **NDKEvent**: Represents a Nostr event
- **NDKUser**: Represents a user (pubkey)
- **NDKFilter**: Defines criteria for querying events
- **NDKSubscription**: Manages subscriptions to event streams
- **NDKRelay**: Represents a connection to a relay

### Basic Setup

```typescript
import NDK from '@nostr-dev-kit/ndk';

// Initialize NDK with relays
const ndk = new NDK({
  explicitRelayUrls: [
    'wss://relay.damus.io',
    'wss://relay.nostr.band'
  ]
});

// Connect to relays
await ndk.connect();
```

## Proper State Management

The ndk-mobile library emphasizes proper state management using Zustand. This approach is critical for avoiding the issues we've been seeing in our application.

### Zustand Store Pattern

```typescript
// Create a singleton store using Zustand
import { create } from 'zustand';

// Private store instance - not exported
let store: ReturnType<typeof createNDKStore> | undefined;

// Store creator function
const createNDKStore = () => create<NDKState>((set) => ({
  ndk: undefined,
  initialized: false,
  connecting: false,
  connectionError: null,
  
  initialize: (config) => set((state) => {
    if (state.initialized) return state;
    
    const ndk = new NDK(config);
    return { ndk, initialized: true };
  }),
  
  connect: async () => {
    set({ connecting: true, connectionError: null });
    try {
      const ndk = store?.getState().ndk;
      if (!ndk) throw new Error("NDK not initialized");
      
      await ndk.connect();
      set({ connecting: false });
    } catch (error) {
      set({ connecting: false, connectionError: error });
    }
  }
}));

// Getter for the singleton store
export const getNDKStore = () => {
  if (!store) {
    store = createNDKStore();
  }
  return store;
};

// Hook for components to use
export const useNDKStore = <T>(selector: (state: NDKState) => T) => {
  // Ensure the store exists
  getNDKStore();
  // Return the selected state
  return useStore(store)(selector);
};
```

### Critical State Management Points

1. **Single Store Instance**: Always use a singleton pattern for NDK stores
2. **Lazy Initialization**: Only create the store when first accessed
3. **Proper Selectors**: Select only what you need from the store
4. **Clear State Transitions**: Define explicit state transitions (connecting, connected, error)

## Subscription Lifecycle

Proper subscription management is crucial for app stability and performance. The subscribe.ts file in ndk-mobile provides advanced subscription handling.

### Basic Subscription Pattern

```typescript
import { useEffect } from 'react';
import { useNDK } from './hooks/useNDK';

function EventComponent({ filter }) {
  const { ndk } = useNDK();
  const [events, setEvents] = useState([]);
  
  useEffect(() => {
    if (!ndk) return;
    
    // Create subscription
    const sub = ndk.subscribe(filter, {
      closeOnEose: false,  // Keep connection open
    });
    
    // Handle incoming events
    sub.on('event', (event) => {
      setEvents(prev => [...prev, event]);
    });
    
    // Start subscription
    sub.start();
    
    // Critical: Clean up subscription when component unmounts
    return () => {
      sub.stop();
    };
  }, [ndk, JSON.stringify(filter)]);
  
  return (/* render events */);
}
```

### Enhanced Subscription Hook

The ndk-mobile package includes an enhanced useSubscribe hook with additional features:

```typescript
// Example based on ndk-mobile implementation
function useEnhancedSubscribe(filter, options = {}) {
  const { ndk } = useNDK();
  const [events, setEvents] = useState([]);
  const [eose, setEose] = useState(false);
  const subRef = useRef(null);
  
  useEffect(() => {
    if (!ndk) return;
    
    // Create subscription
    const sub = ndk.subscribe(filter, {
      closeOnEose: options.closeOnEose || false,
      wrap: options.wrap || false
    });
    
    subRef.current = sub;
    
    // Handle incoming events
    sub.on('event', (event) => {
      // Process events (filtering, wrapping, etc.)
      
      // Add to state
      setEvents(prev => {
        // Check for duplicates using event.id
        if (prev.some(e => e.id === event.id)) return prev;
        return [...prev, event];
      });
    });
    
    // Handle end of stored events
    sub.on('eose', () => {
      setEose(true);
    });
    
    // Start subscription
    sub.start();
    
    // Clean up
    return () => {
      if (subRef.current) {
        subRef.current.stop();
      }
    };
  }, [ndk, JSON.stringify(filter), options]);
  
  return { events, eose };
}
```

## NIP-19 and Encoding/Decoding

NIP-19 functions are essential for handling Nostr identifiers like npub, note, and naddr.

### Decoding NIP-19 Entities

```typescript
import { nip19 } from '@nostr-dev-kit/ndk';

// Decode any NIP-19 entity (naddr, npub, nsec, note, etc.)
function decodeNIP19(encoded: string) {
  try {
    const decoded = nip19.decode(encoded);
    // decoded.type will be 'npub', 'note', 'naddr', etc.
    // decoded.data will contain the data specific to that type
    return decoded;
  } catch (error) {
    console.error('Invalid NIP-19 format:', error);
    return null;
  }
}

// Convert npub to hex pubkey
function npubToHex(npub: string) {
  try {
    const decoded = nip19.decode(npub);
    if (decoded.type === 'npub') {
      return decoded.data as string; // This is the hex pubkey
    }
    return null;
  } catch (error) {
    console.error('Invalid npub format:', error);
    return null;
  }
}
```

### Encoding to NIP-19 Formats

```typescript
// Create an npub from a hex public key
function hexToNpub(hexPubkey: string) {
  return nip19.npubEncode(hexPubkey);
}

// Create a note (event reference) from event ID
function eventIdToNote(eventId: string) {
  return nip19.noteEncode(eventId);
}

// Create an naddr for addressable events
function createNaddr(pubkey: string, kind: number, identifier: string) {
  return nip19.naddrEncode({
    pubkey,  // Hex pubkey
    kind,    // Event kind (number)
    identifier // The 'd' tag value
  });
}
```

## Context Provider Pattern

The ndk-mobile package emphasizes the Context Provider pattern for proper NDK integration:

```typescript
import { NDKProvider } from '@nostr-dev-kit/ndk-mobile';
import App from './App';

// Root component
export default function Root() {
  return (
    <NDKProvider
      params={{
        explicitRelayUrls: [
          'wss://relay.damus.io',
          'wss://relay.nostr.band'
        ]
      }}
    >
      <App />
    </NDKProvider>
  );
}
```

This pattern ensures:

1. **Single NDK Instance**: The entire app shares one NDK instance
2. **Consistent State**: Auth state and relay connections are managed in one place
3. **Hooks Availability**: All NDK hooks (useNDK, useSubscribe, etc.) work correctly
4. **Proper Cleanup**: Connections and subscriptions are managed appropriately

## Mobile-Specific Considerations

The ndk-mobile package includes several mobile-specific optimizations:

### Caching Strategy

Mobile devices need efficient caching to reduce network usage and improve performance:

```typescript
import { SQLiteAdapter } from '@nostr-dev-kit/ndk-mobile/cache-adapter/sqlite';

// Initialize NDK with SQLite caching
const ndk = new NDK({
  explicitRelayUrls: [...],
  cacheAdapter: new SQLiteAdapter({
    dbName: 'nostr-cache.db'
  })
});
```

### Mobile Signers

For secure key management on mobile devices:

```typescript
import { SecureStorageSigner } from '@nostr-dev-kit/ndk-mobile/signers/securestorage';

// Use secure storage for private keys
const signer = new SecureStorageSigner({
  storageKey: 'nostr-private-key'
});

// Add to NDK
ndk.signer = signer;
```

## Best Practices for POWR MVP

Based on our analysis and the ndk-mobile implementation, here are key best practices for our MVP:

### 1. State Management

- **Use singleton stores** for NDK and session state
- **Implement proper state machine** for auth transitions with Zustand
- **Add event listeners/callbacks** for components to respond to auth changes

```typescript
// Authentication state using Zustand (aligned with ndk-mobile patterns)
export const useAuthStore = create((set) => ({
  state: 'unauthenticated', // 'unauthenticated', 'authenticating', 'authenticated', 'deauthenticating'
  user: null,
  error: null,
  
  startAuthentication: async (npub) => {
    set({ state: 'authenticating', error: null });
    try {
      // Authentication logic here
      const user = await authenticateUser(npub);
      set({ state: 'authenticated', user });
    } catch (error) {
      set({ state: 'unauthenticated', error });
    }
  },
  
  logout: async () => {
    set({ state: 'deauthenticating' });
    // Cleanup logic
    set({ state: 'unauthenticated', user: null });
  },
}));
```

### 2. Subscription Management

- **Always clean up subscriptions** when components unmount
- **Keep subscription references** in useRef to ensure proper cleanup
- **Use appropriate cache strategies** to reduce relay load
- **Implement proper error handling** for subscription failures

```typescript
// Example component with proper subscription management
function WorkoutShareComponent({ workoutId }) {
  const { ndk } = useNDK();
  const [relatedPosts, setRelatedPosts] = useState([]);
  const subRef = useRef(null);
  
  useEffect(() => {
    if (!ndk) return;
    
    // Create subscription for posts mentioning this workout
    const sub = ndk.subscribe({
      kinds: [1],
      '#e': [workoutId]
    }, {
      closeOnEose: true,
      cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST
    });
    
    subRef.current = sub;
    
    // Handle events
    sub.on('event', (event) => {
      setRelatedPosts(prev => [...prev, event]);
    });
    
    // Start subscription
    sub.start();
    
    // Clean up on unmount
    return () => {
      if (subRef.current) {
        subRef.current.stop();
      }
    };
  }, [ndk, workoutId]);
  
  // Component JSX
}
```

### 3. Simplified Social Features

For the MVP, we're focusing on:

- **Workout sharing**: Publishing kind 1301 workout records and kind 1 notes quoting them
- **Official feed**: Display only POWR official account posts in the social tab
- **Static content**: Prefer static content over complex real-time feeds

```typescript
// Example function to share a workout
async function shareWorkout(ndk, workout, caption) {
  // First, publish the workout record (kind 1301)
  const workoutEvent = new NDKEvent(ndk);
  workoutEvent.kind = 1301;
  workoutEvent.content = JSON.stringify(workout);
  // Add appropriate tags
  workoutEvent.tags = [
    ['d', workout.id],
    ['title', workout.title],
    ['duration', workout.duration.toString()]
  ];
  
  // Publish workout record
  const workoutPub = await workoutEvent.publish();
  
  // Then create a kind 1 note quoting the workout
  const noteEvent = new NDKEvent(ndk);
  noteEvent.kind = 1;
  noteEvent.content = caption || `Just completed ${workout.title}!`;
  // Add e tag to reference the workout event
  noteEvent.tags = [
    ['e', workoutEvent.id, '', 'quote']
  ];
  
  // Publish social note
  await noteEvent.publish();
  
  return { workoutEvent, noteEvent };
}
```

### 4. Error Handling & Offline Support

- **Implement graceful fallbacks** for network errors
- **Store pending publish operations** for later retry
- **Use SQLite caching** for offline access to previously loaded data

```typescript
// Publication queue service example (aligned with mobile patterns)
class PublicationQueue {
  async queueForPublication(event) {
    try {
      // Try immediate publication
      await event.publish();
      return true;
    } catch (error) {
      // Store for later retry
      await this.storeEventForLater(event);
      return false;
    }
  }
  
  async publishPendingEvents() {
    const pendingEvents = await this.getPendingEvents();
    for (const event of pendingEvents) {
      try {
        await event.publish();
        await this.markAsPublished(event);
      } catch (error) {
        // Keep in queue for next retry
        console.error("Failed to publish:", error);
      }
    }
  }
}
```

### 5. Provider Pattern

- **Use NDKProvider** to wrap the application
- **Configure NDK once** at the app root level
- **Access NDK via hooks** rather than creating instances

```typescript
// App.tsx
export default function App() {
  return (
    <NDKProvider
      params={{
        explicitRelayUrls: [
          'wss://relay.damus.io',
          'wss://relay.nostr.band'
        ]
      }}
    >
      <YourAppComponents />
    </NDKProvider>
  );
}

// Using NDK in components
function SomeComponent() {
  const { ndk } = useNDK();
  
  // Use ndk here
  
  return (/* component JSX */);
}
```

### 6. MVP Implementation Focus

For the MVP, focus on implementing:

1. **Core Authentication**: Proper login/logout with state management
2. **Workout Sharing**: Publication of workouts to Nostr
3. **Limited Social Features**: Static feed of official POWR account
4. **User Profile**: Basic user information display

Defer these for post-MVP:
1. Full social feed implementation
2. Real-time following/interaction
3. Complex subscription patterns

By following these best practices, we can create a stable foundation for the POWR app's MVP release that addresses the current architectural issues while providing a simplified but functional social experience.