This document provides technical details and guidance for developers working with the Notedeck crate.
Notedeck is built around a modular architecture that separates concerns into distinct components:
-
App Framework (
app.rs
)Notedeck
- The main application framework that ties everything togetherApp
- The trait that specific applications must implement
-
Data Layer
Ndb
- NostrDB database for efficient storage and queryingNoteCache
- In-memory cache for expensive-to-compute note data like nip10 structureImages
- Image and GIF cache management
-
Network Layer
RelayPool
- Manages connections to Nostr relaysUnknownIds
- Tracks and resolves unknown profiles and notes
-
User Accounts
Accounts
- Manages user keypairs and account informationAccountStorage
- Handles persistent storage of account data
-
Wallet Integration
Wallet
- Lightning wallet integrationZaps
- Handles Nostr zap functionality
-
UI Components
NotedeckTextStyle
- Text styling utilitiesColorTheme
- Theme management- Various UI helpers
Notes have associated context and actions that define how users can interact with them:
pub enum NoteAction {
Reply(NoteId), // Reply to a note
Quote(NoteId), // Quote a note
Hashtag(String), // Click on a hashtag
Profile(Pubkey), // View a profile
Note(NoteId), // View a note
Context(ContextSelection), // Context menu options
Zap(ZapAction), // Zap (tip) interaction
}
Notedeck handles relays through the RelaySpec
structure, which implements NIP-65 functionality for marking relays as read or write.
The FilterState
enum manages the state of subscriptions to Nostr relays:
pub enum FilterState {
NeedsRemote(Vec<Filter>),
FetchingRemote(UnifiedSubscription),
GotRemote(Subscription),
Ready(Vec<Filter>),
Broken(FilterError),
}
- Clone the repository
- Build with
cargo build
- Test with
cargo test
- Import the notedeck crate
- Implement the
App
trait - Use the
Notedeck
struct as your application framework
Example:
use notedeck::{App, Notedeck, AppContext};
struct MyNostrApp {
// Your app-specific state
}
impl App for MyNostrApp {
fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
// Your app's UI and logic here
}
}
fn main() {
let notedeck = Notedeck::new(...).app(MyNostrApp { /* ... */ });
// Run your app
}
Notes are the core data structure in Nostr. Here's how to work with them:
// Get a note by ID
let txn = Transaction::new(&ndb).expect("txn");
if let Ok(note) = ndb.get_note_by_id(&txn, note_id.bytes()) {
// Process the note
}
// Create a cached note
let cached_note = note_cache.cached_note_or_insert(note_key, ¬e);
Account management is handled through the Accounts
struct:
// Add a new account
let action = accounts.add_account(keypair);
action.process_action(&mut unknown_ids, &ndb, &txn);
// Get the current account
if let Some(account) = accounts.get_selected_account() {
// Use the account
}
Notedeck implements the zap (tipping) functionality according to the Nostr protocol:
- Creates a zap request note (kind 9734)
- Fetches a Lightning invoice via LNURL or LUD-16
- Pays the invoice using a connected wallet
- Tracks the zap state
The image caching system efficiently manages images and animated GIFs:
- Downloads images from URLs
- Stores them in a local cache
- Handles conversion between formats
- Manages memory usage
Notedeck provides several persistence mechanisms:
AccountStorage
- For user accountsTimedSerializer
- For settings that need to be saved after a delay- Various handlers for specific settings (zoom, theme, app size)
-
Relay Connection Issues
- Check network connectivity
- Verify relay URLs are correct
- Look for relay debug messages
-
Database Errors
- Ensure the database path is writable
- Check for database corruption
- Increase map size if needed
-
Performance Issues
- Monitor the frame history
- Check for large image caches
- Consider reducing the number of active subscriptions
When contributing to Notedeck:
- Follow the existing code style
- Add tests for new functionality
- Update documentation as needed
- Keep performance in mind, especially for mobile targets