nostr-1.3.0.0: app/Example.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Concurrent (threadDelay)
import Control.Monad (forM_)
import Control.Monad.IO.Class (liftIO)
import Data.Function ((&))
import qualified Data.Text as T
import Nostr.Client
import Nostr.Event
import Nostr.Relay
import Nostr.Crypto
import Nostr.Nip19
import Nostr.Nip05
main :: IO ()
main = do
putStrLn "=== Nostr High-Level Client Example ==="
-- 1. Generate Keys
(secKey, pubKey) <- generateKeyPair
let keys = Keys secKey pubKey (Just "wss://relay.damus.io") -- Example
putStrLn $ "Generated Public Key (Hex): " ++ T.unpack (unPubKey pubKey)
case toNpub pubKey of
Just npub -> putStrLn $ "Generated Public Key (NIP-19): " ++ T.unpack npub
Nothing -> putStrLn "Failed to encode npub"
case toNsec secKey of
Just nsec -> putStrLn $ "Generated Secret Key (NIP-19): " ++ T.unpack nsec
Nothing -> putStrLn "Failed to encode nsec"
-- 2. Define relays
let relays =
[ "wss://relay.damus.io"
, "wss://nos.lol"
-- Add more if needed, but these are reliable public ones
]
-- 3. Connect (creates NostrEnv)
putStrLn "Connecting to relays..."
env <- connectRelays relays
-- 4. Run App
runNostrApp env $ do
-- Publish a simple note (convenience function)
let note = "Hello Nostr from Haskell Monad! 2026-02-16"
liftIO $ putStrLn $ "Publishing note: " ++ show note
publishShortNote keys note
-- Publish using builder pattern with tags
liftIO $ putStrLn "Publishing long-form article..."
publish keys $ shortNote "My first long-form article from Haskell!"
& withKind 30023
& withAddressRef 30023 pubKey "haskell-nostr-intro"
& withTag ["title", "Getting Started with Nostr in Haskell"]
& withMention pubKey
liftIO $ putStrLn "Wait 3 seconds for propagation..."
liftIO $ threadDelay 3000000
-- NIP-02: Follow a user
liftIO $ putStrLn "Following Jack (founder)..."
-- delirehberi's pubkey: npub1gmeu0wenescpjpymwmwgnkaedc6vy3aamf5tdtvxxf5z0yll3gdqatwl3v
let jackPubkey = "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbf71d22"
case parsePubKey jackPubkey of
Just pk -> follow keys pk (Just "wss://relay.damus.io") (Just "jack")
Nothing -> liftIO $ putStrLn "Invalid Jack pubkey"
let delirehberiNpub = "npub1gmeu0wenescpjpymwmwgnkaedc6vy3aamf5tdtvxxf5z0yll3gdqatwl3v"
case parsePubKey delirehberiNpub of
Just pk -> do
liftIO $ putStrLn $ "Following delirehberi (decoded from " ++ T.unpack delirehberiNpub ++ ")"
-- Verify NIP-05
let nip05 = "delirehberi@emre.xyz"
liftIO $ putStrLn $ "Verifying NIP-05: " ++ T.unpack nip05 ++ "..."
isVerified <- liftIO $ verifyNip05 nip05 pk
if isVerified
then liftIO $ putStrLn "UNKNOWN: Verified NIP-05 [OK]"
else liftIO $ putStrLn "WARNING: NIP-05 Verification Failed [FAILED]"
follow keys pk (Just "wss://relay.damus.io") (Just "delirehberi@emre.xyz")
Nothing -> liftIO $ putStrLn "Error: Not a public key"
liftIO $ putStrLn "Wait 3 seconds..."
liftIO $ threadDelay 3000000
-- Fetch contacts
contacts <- getContacts keys
liftIO $ putStrLn $ "My Contacts: " ++ show contacts
-- Query for the note
liftIO $ putStrLn "Querying for my notes..."
let f = defaultFilter
{ filterAuthors = Just [pubKey]
, filterKinds = Just [1]
, filterLimit = Just 5
}
events <- queryEvents f
liftIO $ do
putStrLn $ "Found " ++ show (length events) ++ " events."
forM_ events $ \e -> do
putStrLn $ " - " ++ show (eventContent e)
-- Delete the event if it matches our note
if eventContent e == note then do
putStrLn $ "Deleting event: " ++ show (eventId e)
return ()
else return ()
let eventsToDelete = Prelude.filter (\e -> eventContent e == note) events
if not (null eventsToDelete) then do
let eids = map eventId eventsToDelete
liftIO $ putStrLn $ "Deleting " ++ show (length eids) ++ " events (NIP-09)..."
deleteEvents keys eids (Just "test deletion")
else
liftIO $ putStrLn "No events found to delete."
-- 5. Cleanup
putStrLn "Disconnecting..."
disconnect env
putStrLn "Done."