packages feed

nostr-1.3.0.0: lib/Nostr/Nip57.hs

{-# LANGUAGE OverloadedStrings #-}

{-|
Module      : Nostr.Nip57
Description : NIP-57 Lightning Zaps
Copyright   : (c) Emre YILMAZ, 2026
License     : MIT
Maintainer  : z@emre.xyz

This module provides support for NIP-57 Zap Requests and Receipts.
-}

module Nostr.Nip57
  ( kindZapRequest
  , kindZapReceipt
  , ZapRequest(..)
  , ZapReceipt(..)
  , parseZapRequest
  , parseZapReceipt
  ) where

import Data.Text (Text)
import qualified Data.Text as T
import Data.Maybe (listToMaybe, mapMaybe)
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text.Encoding as TE

import Nostr.Event (Event(..), Kind, Tag, PubKey)

kindZapRequest :: Kind
kindZapRequest = 9734

kindZapReceipt :: Kind
kindZapReceipt = 9735

-- | Represents a Zap Request (kind 9734)
data ZapRequest = ZapRequest
  { zrRelays  :: [Text]
  , zrAmount  :: Maybe Text
  , zrLnurl   :: Maybe Text
  , zrP       :: Text -- Recipient pubkey
  , zrE       :: Maybe Text -- Event id being zapped
  } deriving (Show, Eq)

-- | Represents a Zap Receipt (kind 9735)
data ZapReceipt = ZapReceipt
  { zrpBolt11      :: Text
  , zrpPreimage    :: Maybe Text
  , zrpDescription :: Text -- The JSON representation of the Zap Request event
  , zrpP           :: Text
  , zrpE           :: Maybe Text
  } deriving (Show, Eq)

findTagValue :: Text -> [Tag] -> Maybe Text
findTagValue name tags = listToMaybe $ mapMaybe (extract name) tags
  where
    extract n (t:v:_) | t == n = Just v
    extract _ _ = Nothing

findTagValues :: Text -> [Tag] -> [Text]
findTagValues name tags = mapMaybe (extract name) tags
  where
    extract n (t:v:_) | t == n = Just v
    extract _ _ = Nothing

-- | Parse an Event into a ZapRequest if it matches kind 9734
parseZapRequest :: Event -> Maybe ZapRequest
parseZapRequest ev
  | eventKind ev /= kindZapRequest = Nothing
  | otherwise = do
      let tags = eventTags ev
      p <- findTagValue "p" tags
      
      -- relays tag can have multiple values in a single tag ["relays", "url1", "url2"]
      -- or multiple ["relays", "url1"] tags. We handle both by concat.
      let relays = concatMap (drop 1) (filter (\t -> case t of ("relays":_) -> True; _ -> False) tags)
      
      return $ ZapRequest
        { zrRelays = relays
        , zrAmount = findTagValue "amount" tags
        , zrLnurl  = findTagValue "lnurl" tags
        , zrP      = p
        , zrE      = findTagValue "e" tags
        }

-- | Parse an Event into a ZapReceipt if it matches kind 9735
parseZapReceipt :: Event -> Maybe ZapReceipt
parseZapReceipt ev
  | eventKind ev /= kindZapReceipt = Nothing
  | otherwise = do
      let tags = eventTags ev
      bolt11 <- findTagValue "bolt11" tags
      description <- findTagValue "description" tags
      p <- findTagValue "p" tags
      
      return $ ZapReceipt
        { zrpBolt11 = bolt11
        , zrpPreimage = findTagValue "preimage" tags
        , zrpDescription = description
        , zrpP = p
        , zrpE = findTagValue "e" tags
        }