diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,224 @@
+# nostr.hs
+
+A NIP-01 compliant Nostr library and client implementation in Haskell.
+
+## Features
+
+- **NIP-01 Compliant**: Full implementation of event structure, rules, and tags.
+- **Schnorr Signatures**: Pure Haskell BIP-340 Schnorr signatures using `ppad-secp256k1`.
+- **Relay Communication**: WebSocket-based communication with Nostr relays.
+- **Type Safety**: Strong types for Event IDs, Public Keys, and Signatures.
+
+## Implemented NIPs
+
+- [x] NIP-01: Basic protocol flow
+- [x] NIP-02: Contact Lists
+- [x] NIP-05: DNS-based verification
+- [x] NIP-09: Event Deletion Request
+- [x] NIP-10: Text Notes and Threads
+- [x] NIP-19: bech32-encoded entities
+- [x] NIP-21: nostr: URI scheme
+- [x] NIP-42: Relay Authentication
+
+
+## Installation
+
+This project uses **Nix** for reproducible builds.
+
+1. **Clone the repository:**
+   ```bash
+   git clone https://github.com/delirehberi/nostr.hs.git
+   cd nostr.hs
+   ```
+
+2. **Enter the development shell:**
+   ```bash
+   nix develop
+   ```
+
+3. **Build the project:**
+   ```bash
+   cabal build all
+   ```
+
+## Development
+
+### Building and Testing
+
+```bash
+make build    # Build the project
+make test     # Run tests
+make clean    # Clean build artifacts
+make docs     # Generate documentation
+make sdist    # Generate source distribution tar.gz
+```
+
+All `make` commands automatically use the Nix development environment, so you don't need to run `nix develop` first.
+
+### Uploading to Hackage
+
+To package and upload the library to Hackage:
+
+```bash
+make upload-hackage
+```
+
+This will:
+1. Generate a source distribution tar.gz file
+2. Generate Haddock documentation for Hackage
+3. Upload the package source to Hackage
+4. Upload the documentation to Hackage
+
+You'll need to be authenticated with Hackage and have upload permissions. The script assumes you have `cabal` configured with your Hackage credentials.
+
+## Usage Examples
+
+### High-Level API (Recommended)
+
+The `Nostr.Client` module provides a monadic interface for managing connections, signing, and publishing. It handles robust reconnections (exponential backoff) and TLS usage automatically.
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+
+import Nostr.Client
+import Nostr.Event
+import Nostr.Crypto
+import Data.Function ((&))
+import Control.Monad.IO.Class (liftIO)
+
+main :: IO ()
+main = do
+  -- 1. Generate Keys
+  -- Note: In a real app, you'd load these from safe storage
+  (secKey, pubKey) <- generateKeyPair
+  let keys = Keys secKey pubKey Nothing
+
+  -- 2. Connect to Relays
+  -- connectRelays establishes background threads for each relay and manages reconnection
+  env <- connectRelays ["wss://relay.damus.io", "wss://nos.lol"]
+
+  runNostrApp env $ do
+    -- A. Publish a simple short text note (Kind 1)
+    publishShortNote keys "Hello Nostr from Haskell!"
+
+    -- B. Publish a complex event using the Builder Pattern
+    -- Use the (&) operator to chain combinators for tags and kinds
+    liftIO $ putStrLn "Publishing a reply..."
+    publish keys $ shortNote "Replying to an event..."
+      & withKind 1
+      & withReply "event-id-hex"
+      & withMention "pubkey-hex"
+      & withTag ["t", "haskell"]
+      
+    -- C. Query Events
+    -- Queries all connected relays concurrently and aggregates unique results
+    let filter = defaultFilter
+          { filterKinds = Just [1]
+          , filterAuthors = Just [pubKey]
+          , filterLimit = Just 10
+          }
+    
+    events <- queryEvents filter
+    liftIO $ print events
+
+    -- D. Follow Users (NIP-02)
+    -- This fetches your existing contact list (Kind 3), adds the user, and republishes.
+    liftIO $ putStrLn "Following Jack..."
+    follow keys "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbf71d22" (Just "wss://relay.damus.io") (Just "jack")
+
+    -- E. Fetch Contacts
+    contacts <- getContacts keys
+    liftIO $ print contacts
+
+    -- F. Delete Events (NIP-09)
+    -- Deletes a list of event IDs with an optional reason.
+    -- let eventIds = [EventId "hex_id_1...", EventId "hex_id_2..."]
+    -- deleteEvents keys eventIds (Just "mistake")
+
+  -- 3. Disconnect
+  disconnect env
+```
+
+### Low-Level Event Creation
+
+If you need manual control over event creation without the `NostrApp` environment:
+
+```haskell
+import Nostr.Event
+import Nostr.Crypto
+import Data.Time.Clock.POSIX (getPOSIXTime)
+
+main :: IO ()
+main = do
+  (secKey, pubKey) <- generateKeyPair
+  now <- round <$> getPOSIXTime
+  
+  -- Create unsigned event
+  let unsigned = createUnsignedEvent pubKey now 1 [] "Manual event content"
+  
+  -- Sign event
+  signed <- signEvent secKey unsigned
+  case signed of
+    Right event -> putStrLn $ "Event ID: " ++ show (eventId event)
+    Left err    -> putStrLn $ "Error: " ++ show err
+
+### Encrypted Direct Messages (NIP-04)
+
+Use the `Nostr.Nip04` module to securely encrypt and decrypt direct messages using AES-256-CBC and ECDH:
+
+```haskell
+import Nostr.Crypto
+import Nostr.Nip04
+import qualified Data.Text as T
+
+main :: IO ()
+main = do
+  (senderSec, senderPub) <- generateKeyPair
+  (receiverSec, receiverPub) <- generateKeyPair
+  
+  let msg = "Super secret message"
+  
+  -- Encrypt (Sender -> Receiver)
+  Just encrypted <- encryptNip04 senderSec receiverPub msg
+  putStrLn $ "Encrypted: " ++ T.unpack encrypted
+  
+  -- Decrypt (Receiver)
+  let decrypted = decryptNip04 receiverSec senderPub encrypted
+  print decrypted -- Just "Super secret message"
+```
+
+### Fetching Relay Information (NIP-11)
+
+Query a relay's metadata document using the `Nostr.Nip11` module:
+
+```haskell
+import Nostr.Nip11
+
+main :: IO ()
+main = do
+  info <- fetchRelayInfo "wss://relay.damus.io"
+  case info of
+    Right i  -> print (riSoftware i, riSupportedNips i)
+    Left err -> putStrLn $ "Failed to fetch info: " ++ err
+```
+
+### Parsing Lightning Zaps (NIP-57)
+
+Easily parse Zap Requests (Kind 9734) and Zap Receipts (Kind 9735):
+
+```haskell
+import Nostr.Nip57
+import Nostr.Event
+
+-- Assuming `event` is a parsed Event of kind 9734
+handleEvent :: Event -> IO ()
+handleEvent event = do
+  case parseZapRequest event of
+    Just zr -> putStrLn $ "Zap Request for " ++ T.unpack (zrP zr)
+    Nothing -> return ()
+```
+```
+
+## Contributing
+
+Contributions are welcome! Please ensure all tests pass before submitting a PR.
diff --git a/lib/Nostr/Crypto.hs b/lib/Nostr/Crypto.hs
--- a/lib/Nostr/Crypto.hs
+++ b/lib/Nostr/Crypto.hs
@@ -21,6 +21,7 @@
   , generateKeyPair
   , pubKeyFromSecKey
   , exportSecKey
+  , widerToInteger
   , exportPubKey
   , secKeyFromBytes
   , Keys(..)
@@ -77,9 +78,8 @@
 -- | Derive public key from secret key (x-only 32-byte for BIP-340)
 pubKeyFromSecKey :: SecKey -> IO PubKey
 pubKeyFromSecKey secKey = do
-  -- ppad-secp256k1 uses derive_pub to create public key from secret key
-  -- It returns Maybe Pub (Projective point)
-  case Secp.derive_pub secKey of
+  let pkInt = widerToInteger secKey
+  case Secp.derive_pub pkInt of
     Just pubKey -> do
       -- Serialize to compressed format (33 bytes) and drop the first byte
       -- to get the 32-byte x-only public key as per BIP-340
@@ -203,29 +203,34 @@
 -- BIP-340 Schnorr Signatures
 -- ============================================================================
 
--- | Sign an event and set its id and sig fields using BIP-340 Schnorr signatures
+widerToInteger :: SecKey -> Integer
+widerToInteger sec =
+  case hexToBytes (exportSecKey sec) of
+    Right bs -> BS.foldl' (\acc b -> acc * 256 + toInteger b) 0 bs
+    Left _ -> 0
+
+-- | Sign an event with a secret key
 signEvent :: SecKey -> Event -> IO (Either Text Event)
 signEvent secKey event = do
-  -- First compute the event ID
-  let eventWithId = event { eventId = computeEventId event }
+  -- 1. Create the serialized event to get the ID
+  let idBytes = computeEventId event
   
-  -- Get the event ID bytes for signing (must be 32 bytes for Schnorr)
-  case hexToBytes (unEventId (eventId eventWithId)) of
-    Left err -> return $ Left err
-    Right idBytes -> do
-      -- Generate 32 bytes of auxiliary randomness (recommended by BIP-340)
+  case hexToBytes (unEventId idBytes) of
+    Right idBytesRaw -> do
+      let eventWithId = event { eventId = idBytes }
+      
+      -- 2. Generate 32 bytes of auxiliary randomness as recommended by BIP-340
       auxRand <- E.getEntropy 32
       
-      -- Sign with BIP-340 Schnorr
-      case Secp.sign_schnorr secKey idBytes auxRand of
-        Nothing -> return $ Left "Failed to create Schnorr signature"
+      -- 3. Sign the 32-byte event ID
+      case Secp.sign_schnorr (widerToInteger secKey) idBytesRaw auxRand of
         Just sigBytes -> do
-          let sigHex = bytesToHex sigBytes
-          
-          -- Create Signature with proper validation (should be 64 bytes = 128 hex chars)
-          case mkSignature sigHex of
-            Right signature -> return $ Right $ eventWithId { eventSig = signature }
-            Left err -> return $ Left err
+            let sigHex = bytesToHex sigBytes
+            case mkSignature sigHex of
+                Right signature -> return $ Right $ eventWithId { eventSig = signature }
+                Left err -> return $ Left err
+        Nothing -> return $ Left "Failed to create Schnorr signature"
+    Left err -> return $ Left err
 
 -- | Verify event signature using BIP-340 Schnorr verification
 verifySignature :: Event -> IO Bool
diff --git a/lib/Nostr/Event.hs b/lib/Nostr/Event.hs
--- a/lib/Nostr/Event.hs
+++ b/lib/Nostr/Event.hs
@@ -113,12 +113,20 @@
 validateHex128 t = T.length t == 128 && T.all isValidHex t
 
 -- | Smart constructor for EventId with validation
+--
+-- >>> mkEventId "41ce9bc50daea4225efcd82edcb90e7eb25ce8d2d6cba2dfbb61e15e86979601"
+-- Right (EventId "41ce9bc50daea4225efcd82edcb90e7eb25ce8d2d6cba2dfbb61e15e86979601")
+-- >>> mkEventId "invalid"
+-- Left "Invalid EventId: must be 64 lowercase hex characters, got: invalid"
 mkEventId :: Text -> Either Text EventId
 mkEventId t
   | validateHex64 t = Right (EventId t)
   | otherwise = Left $ "Invalid EventId: must be 64 lowercase hex characters, got: " <> t
 
 -- | Smart constructor for PubKey with validation
+--
+-- >>> mkPubKey "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
+-- Right (PubKey "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")
 mkPubKey :: Text -> Either Text PubKey
 mkPubKey t
   | validateHex64 t = Right (PubKey t)
diff --git a/lib/Nostr/Nip04.hs b/lib/Nostr/Nip04.hs
--- a/lib/Nostr/Nip04.hs
+++ b/lib/Nostr/Nip04.hs
@@ -30,7 +30,7 @@
 import qualified Data.Text.Encoding as TE
 import qualified System.Entropy as E
 
-import Nostr.Crypto (SecKey, hexToBytes)
+import Nostr.Crypto (SecKey, hexToBytes, widerToInteger)
 import Nostr.Event (PubKey(..))
 
 -- | Compute shared secret using ECDH
@@ -41,10 +41,9 @@
   let compressedPubKey = BS.cons 0x02 pubBytes
   pubPoint <- Secp.parse_point compressedPubKey
   
-  -- Use ppad-secp256k1 ecdh. Note: If this computes SHA256 of the point instead of
   -- just returning the X coordinate, we might need to use `mul` directly.
   -- Let's try `ecdh` first.
-  Secp.ecdh pubPoint secKey
+  Secp.ecdh pubPoint (widerToInteger secKey)
 
 -- | PKCS7 padding
 padPKCS7 :: Int -> ByteString -> ByteString
diff --git a/nostr.cabal b/nostr.cabal
--- a/nostr.cabal
+++ b/nostr.cabal
@@ -20,7 +20,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version:            1.3.0.0
+version:            1.3.1.0
 bug-reports:        https://github.com/delirehberi/nostr.hs/issues
 
 -- A short (one-line) description of the package.
@@ -53,7 +53,7 @@
 build-type:         Simple
 
 -- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
-extra-doc-files:    CHANGELOG.md
+extra-doc-files:    CHANGELOG.md README.md
 
 -- Extra source files to be distributed with the package, such as examples, or a tutorial module.
 -- extra-source-files:
@@ -81,28 +81,28 @@
     
     -- Other library packages from which modules are imported.
     build-depends:    base >=4.18.0.0 && <4.20
-        , aeson 
-        , bytestring 
-        , text 
-        , unix-time 
-        , websockets
-        , ppad-secp256k1 >= 0.4.0
-        , ppad-fixed
-        , nonempty-wrapper-text
-        , cryptonite
-        , memory
-        , base16-bytestring
-        , vector
-        , entropy
-        , containers
-        , async
-        , stm
-        , http-conduit
-        , mtl
-        , time
-        , wuss
-        , bech32
-        , base64-bytestring
+        , aeson < 3
+        , bytestring < 0.13
+        , text < 3
+        , unix-time < 0.5
+        , websockets < 0.14
+        , ppad-secp256k1 >= 0.4.0 && < 0.5
+        , ppad-fixed < 0.2
+        , nonempty-wrapper-text < 0.2
+        , cryptonite < 0.31
+        , memory < 0.19
+        , base16-bytestring < 1.1
+        , vector < 0.14
+        , entropy < 0.5
+        , containers < 0.8
+        , async < 2.3
+        , stm < 2.6
+        , http-conduit < 2.4
+        , mtl < 2.4
+        , time < 1.13
+        , wuss < 2.1
+        , bech32 < 1.2
+        , base64-bytestring < 1.3
 
     -- Directories containing source files.
     hs-source-dirs:   lib
@@ -127,10 +127,10 @@
     build-depends:
         base >=4.18.0.0 && <4.20,
         nostr,
-        aeson,
-        bytestring,
-        text,
-        time
+        aeson < 3,
+        bytestring < 0.13,
+        text < 3,
+        time < 1.13
 
     -- Directories containing source files.
     hs-source-dirs:   app
@@ -144,9 +144,9 @@
     build-depends:
         base >=4.18.0.0 && <4.20,
         nostr,
-        text,
-        bytestring,
-        time
+        text < 3,
+        bytestring < 0.13,
+        time < 1.13
 
     hs-source-dirs:   app
     default-language: GHC2021
@@ -172,10 +172,10 @@
     build-depends:
         base >=4.18.0.0 && <4.20,
         nostr,
-        hspec,
-        QuickCheck,
-        text,
-        bytestring,
-        aeson,
-        containers,
-        base64-bytestring
+        hspec < 3.0,
+        QuickCheck < 2.15,
+        text < 3,
+        bytestring < 0.13,
+        aeson < 3,
+        containers < 0.8,
+        base64-bytestring < 1.3
