@nostr-dev-kit/ndk-wallet
Version:
165 lines (117 loc) • 6.65 kB
Markdown
# 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 `-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 `-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 (`-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.