UNPKG

@nostr-dev-kit/ndk-wallet

Version:
165 lines (117 loc) 6.65 kB
# NDKNutzapMonitor The `NDKNutzapMonitor` class monitors a user's nutzap inbox for new nutzaps and processes them automatically. It handles the full lifecycle of nutzaps, from discovery to redemption. ## Features - Monitors relays for nutzaps sent to a specific user - Automatically redeems nutzaps when the appropriate private key is available - Keeps track of nutzap states (initial, processing, redeemed, spent, error) - Persists states across application sessions using the configured NDK Cache Adapter - Emits events for tracking monitor activity ## Basic Usage ```typescript import { NDKNutzapMonitor } from "@nostr-dev-kit/ndk-wallet"; import NDK, { NDKUser, NDKPrivateKeySigner, NDKCashuMintList } from "@nostr-dev-kit/ndk"; import { NDKCashuWallet } from "@nostr-dev-kit/ndk-wallet"; // Example wallet // Assume ndk: NDK, user: NDKUser, mintList?: NDKCashuMintList are initialized // Assume myCashuWallet: NDKCashuWallet is initialized // Assume myPrivateKeySigner: NDKPrivateKeySigner is initialized // Create a monitor for a user // The store is now automatically derived from ndk.cacheAdapter if available const monitor = new NDKNutzapMonitor(ndk, user, { mintList, // No need to pass 'store' manually if using a compatible cache adapter }); // Set the wallet to use for redeeming nutzaps monitor.wallet = myCashuWallet; // If you have extra private keys that might have been used to receive p2pk-locked keys, you can also add them // this is almost always unnecessary -- the nutzap monitor will try to do this for you if the private keys are // properly stored as nostr events per the NIP-60 spec. await monitor.addPrivkey(myPrivateKeySigner); // Start monitoring for nutzaps await monitor.start({}); // Listen for events monitor.on("redeemed", (events, amount) => { console.log(`Redeemed ${events.length} nutzaps for ${amount} sats`); }); ``` ## State Management The monitor uses a state machine to track the status of each nutzap. The possible states (`NdkNutzapStatus`) are defined in `@nostr-dev-kit/ndk`: - `INITIAL`: First time we see a nutzap - `PROCESSING`: Currently processing the nutzap - `REDEEMED`: Successfully redeemed - `SPENT`: The nutzap has already been spent - `MISSING_PRIVKEY`: No private key available to redeem the nutzap - `TEMPORARY_ERROR`: A transient error occurred - `PERMANENT_ERROR`: A permanent error occurred (will not retry) - `INVALID_NUTZAP`: The nutzap data is invalid ## State Persistence (via Cache Adapter) The `NDKNutzapMonitor` now leverages the configured `ndk.cacheAdapter` for persisting nutzap states across application sessions. If the `ndk.cacheAdapter` implements the optional `getAllNutzapStates` and `setNutzapState` methods (as defined in the `NDKCacheAdapter` interface in `@nostr-dev-kit/ndk`), the monitor will automatically use these methods to load initial states and save updates. ### Cache Adapter Interface Methods The relevant methods in the `NDKCacheAdapter` interface are: ```typescript // Defined in @nostr-dev-kit/ndk interface NDKCacheAdapter { // ... other methods /** * Gets all nutzap states from the cache. * @returns A map of event IDs to nutzap states. */ getAllNutzapStates?(): Promise<Map<NDKEventId, NDKNutzapState>>; /** * Sets the state of a nutzap in the cache. * @param id The ID of the nutzap event. * @param stateChange The partial state change to apply. */ setNutzapState?(id: NDKEventId, stateChange: Partial<NDKNutzapState>): Promise<void>; } ``` ### State Updates When updating a nutzap's state, the monitor calls `cacheAdapter.setNutzapState` with only the changed properties (`Partial<NDKNutzapState>`), not the entire state object. This allows the cache adapter implementation to efficiently merge updates. For example: ```typescript // Internal call within NDKNutzapMonitor await ndk.cacheAdapter?.setNutzapState?.(nutzapId, { status: NdkNutzapStatus.REDEEMED, redeemedAmount: 100, }); ``` ### Implementing Persistence If you are creating a custom cache adapter and want it to support nutzap state persistence, you need to implement the `getAllNutzapStates` and `setNutzapState` methods. NDK Mobile (`@nostr-dev-kit/ndk-mobile`) provides an implementation using SQLite via its `NDKCacheAdapterSqlite`. If you use this adapter with your NDK instance, nutzap state persistence will work automatically. ```typescript // Example: Initializing NDK with the SQLite adapter from ndk-mobile import NDK from "@nostr-dev-kit/ndk"; import { NDKCacheAdapterSqlite } from "@nostr-dev-kit/ndk-mobile"; const cacheAdapter = new NDKCacheAdapterSqlite("my-ndk-cache.db"); await cacheAdapter.initialize(); // Important: Initialize the adapter const ndk = new NDK({ cacheAdapter: cacheAdapter, // ... other NDK options }); // Pass the NDK instance to the adapter AFTER NDK is initialized // This allows the adapter to use the NDK instance if needed (e.g., for deserializing events) cacheAdapter.ndk = ndk; // Now, when NDKNutzapMonitor is created with this ndk instance, // it will use the SQLite adapter for persistence. ``` ## Events The monitor emits several events that you can listen for: - `seen_in_unknown_mint`: Emitted when a nutzap is seen in a mint not in the user's mint list - `state_changed`: Emitted when the state of a nutzap changes - `redeemed`: Emitted when a nutzap is successfully redeemed - `seen`: Emitted when a new nutzap is seen - `failed`: Emitted when a nutzap fails to be redeemed ## Managing Private Keys The monitor needs private keys to redeem nutzaps. Typically, you don't need to do this manually; setting the `monitor.wallet` property should provide the necessary keys if the wallet implementation supports it (like `NDKCashuWallet`). You can also add extra private keys using: ```typescript await monitor.addPrivkey(privateKeySigner); ``` You can add multiple private keys, and the monitor will automatically use the appropriate key for each nutzap. If a nutzap requires a private key that isn't available, it will be marked as `MISSING_PRIVKEY`. If the key is later added, the monitor will automatically attempt to redeem the nutzap. ## Error Handling The monitor handles various error conditions: - If a nutzap cannot be redeemed due to a missing private key, it's marked as `MISSING_PRIVKEY` - If the redemption fails with a transient error, it's marked as `TEMPORARY_ERROR` - If the redemption fails with a permanent error (e.g., "unknown public key size"), it's marked as `PERMANENT_ERROR` The monitor will automatically retry nutzaps with temporary errors but not those with permanent errors.