packages feed

lnd-client-0.1.0.1: src/LndClient/Data/Newtype.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module LndClient.Data.Newtype
  ( AddIndex (..),
    SettleIndex (..),
    PaymentRequest (..),
    RHash (..),
    RPreimage (..),
    MSat (..),
    toGrpcSat,
    toGrpcMSat,
    toGrpcMaybe,
    fromGrpcSat,
    fromGrpcMSat,
    CipherSeedMnemonic (..),
    AezeedPassphrase (..),
    Seconds (..),
    NodePubKey (..),
    NodeLocation (..),
    GrpcTimeoutSeconds,
    newRHash,
    newRPreimage,
    newPendingChanId,
    newGrpcTimeout,
    unGrpcTimeout,
    defaultSyncGrpcTimeout,
    defaultAsyncGrpcTimeout,
    TxId (..),
    Vout (..),
    ChanId (..),
    PendingChannelId (..),
    Psbt (..),
    RawTx (..)
  )
where

import Codec.QRCode as QR (ToText)
import qualified Crypto.Hash.SHA256 as SHA256 (hash)
import Crypto.Random (getRandomBytes)
import Data.ByteString.Base16 as B16 (decode, encode)
import Data.ByteString.Char8 as C8
import Data.ProtoLens.Message
import LndClient.Class
import LndClient.Data.Kind
import LndClient.Data.Type
import LndClient.Import.External
import LndClient.Util
import qualified Proto.Invoicesrpc.Invoices as IGrpc
import qualified Proto.Invoicesrpc.Invoices_Fields as IGrpc
import qualified Proto.Lnrpc.Ln1 as LnGrpc
import qualified Proto.Lnrpc.Ln1_Fields as LnGrpc

newtype PendingChannelId = PendingChannelId ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out PendingChannelId

newtype Psbt = Psbt ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out Psbt

newtype RawTx = RawTx ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out RawTx

newtype ChanId = ChanId Word64
  deriving newtype
    ( PersistField,
      PersistFieldSql,
      Eq,
      Ord,
      Show,
      Read
    )
  deriving stock (Generic)

instance Out ChanId

newtype Vout (a :: TxKind) = Vout Word32
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show, Read)
  deriving stock (Generic)

instance Out (Vout a)

newtype TxId (a :: TxKind) = TxId ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show, Read)
  deriving stock (Generic)

instance Out (TxId a)

newtype NodePubKey = NodePubKey ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show, Read)
  deriving stock (Generic)

instance Out NodePubKey

newtype NodeLocation = NodeLocation Text
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show, Read)
  deriving stock (Generic)

instance Out NodeLocation

newtype AddIndex = AddIndex Word64
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out AddIndex

newtype SettleIndex = SettleIndex Word64
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out SettleIndex

newtype PaymentRequest = PaymentRequest Text
  deriving newtype (PersistField, PersistFieldSql, Eq, QR.ToText, Show)
  deriving stock (Generic)

instance Out PaymentRequest

newtype RHash = RHash ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out RHash

newtype RPreimage = RPreimage ByteString
  deriving newtype (PersistField, PersistFieldSql, Eq, Ord, Show)
  deriving stock (Generic)

instance Out RPreimage

newtype MSat = MSat Word64
  deriving newtype
    ( PersistField,
      PersistFieldSql,
      Eq,
      Num,
      Ord,
      FromJSON,
      Show,
      Read
    )
  deriving stock
    ( Generic
    )

instance Out MSat

newtype CipherSeedMnemonic = CipherSeedMnemonic [Text]
  deriving newtype (PersistField, PersistFieldSql, Eq, FromJSON)

newtype AezeedPassphrase = AezeedPassphrase Text
  deriving newtype (PersistField, PersistFieldSql, Eq, FromJSON)

newtype Seconds = Seconds Word64
  deriving newtype
    ( PersistField,
      PersistFieldSql,
      Eq,
      Ord,
      FromJSON,
      Show,
      Num,
      Real,
      Enum,
      Integral
    )
  deriving stock (Generic)

instance Out Seconds

newtype GrpcTimeoutSeconds = GrpcTimeoutSeconds Int
  deriving newtype (Eq, Ord, FromJSON, Show)

instance ToGrpc NodePubKey ByteString where
  toGrpc = Right . coerce

instance ToGrpc NodePubKey Text where
  toGrpc =
    first (const $ ToGrpcError "UTF8_DECODE_ERROR")
      . decodeUtf8'
      . B16.encode
      . coerce

instance ToGrpc NodeLocation Text where
  toGrpc = Right . coerce

--
-- TODO : smart constructors for NodePubKey and NodeLocation ???
--

instance FromGrpc (TxId a) ByteString where
  fromGrpc = Right . TxId

instance FromGrpc (TxId a) Text where
  fromGrpc = (TxId <$>) . txIdParser

instance FromGrpc (Vout a) Word32 where
  fromGrpc = Right . Vout

instance FromGrpc NodePubKey ByteString where
  fromGrpc = Right . NodePubKey

instance FromGrpc NodePubKey Text where
  fromGrpc =
    bimap (const $ FromGrpcError "NodePubKey hex decoding error") NodePubKey . B16.decode . encodeUtf8

instance FromGrpc NodeLocation Text where
  fromGrpc = Right . NodeLocation

instance ToGrpc (TxId a) ByteString where
  toGrpc = Right . coerce

instance ToGrpc (Vout a) Word32 where
  toGrpc = Right . coerce

instance ToGrpc ChanId Word64 where
  toGrpc = Right . coerce

instance ToGrpc PendingChannelId ByteString where
  toGrpc = Right . coerce

instance ToGrpc Psbt ByteString where
  toGrpc = Right . coerce

instance ToGrpc RawTx ByteString where
  toGrpc = Right . coerce

instance ToGrpc AddIndex Word64 where
  toGrpc = Right . coerce

instance ToGrpc SettleIndex Word64 where
  toGrpc = Right . coerce

instance FromGrpc ChanId Word64 where
  fromGrpc = Right . coerce

instance FromGrpc RHash ByteString where
  fromGrpc = Right . RHash

instance FromGrpc RPreimage ByteString where
  fromGrpc = Right . RPreimage

instance FromGrpc Psbt ByteString where
  fromGrpc = Right . Psbt

instance FromGrpc AddIndex Word64 where
  fromGrpc = Right . AddIndex

instance FromGrpc SettleIndex Word64 where
  fromGrpc = Right . SettleIndex

instance FromGrpc PaymentRequest Text where
  fromGrpc = Right . PaymentRequest

instance FromGrpc PaymentRequest IGrpc.AddHoldInvoiceResp where
  fromGrpc x = fromGrpc (x ^. IGrpc.paymentRequest)

instance FromGrpc Seconds Int64 where
  fromGrpc =
    (Seconds <$>)
      . maybeToRight (FromGrpcError "Seconds overflow")
      . safeFromIntegral

instance FromGrpc RHash Text where
  fromGrpc x0 =
    case B16.decode $ encodeUtf8 x0 of
      Right x1 -> Right $ RHash x1
      Left {} -> Left $ FromGrpcError "NON_HEX_RHASH"

instance FromGrpc RPreimage Text where
  fromGrpc x0 =
    case B16.decode $ encodeUtf8 x0 of
      Right x1 -> Right $ RPreimage x1
      Left {} -> Left $ FromGrpcError "NON_HEX_RPREIMAGE"

instance ToGrpc PaymentRequest Text where
  toGrpc x = Right (coerce x :: Text)

instance ToGrpc Seconds Int64 where
  toGrpc x =
    maybeToRight
      (ToGrpcError "Seconds overflow")
      $ safeFromIntegral (coerce x :: Word64)

instance ToGrpc CipherSeedMnemonic [Text] where
  toGrpc = Right . coerce

instance ToGrpc AezeedPassphrase ByteString where
  toGrpc x = Right $ encodeUtf8 (coerce x :: Text)

instance ToGrpc RHash ByteString where
  toGrpc = Right . coerce

instance ToGrpc RHash IGrpc.CancelInvoiceMsg where
  toGrpc x = do
    ph <- toGrpc x
    Right $ defMessage & IGrpc.paymentHash .~ ph

instance ToGrpc RPreimage ByteString where
  toGrpc = Right . coerce

instance ToGrpc RPreimage IGrpc.SettleInvoiceMsg where
  toGrpc x = do
    p <- toGrpc x
    Right $ defMessage & IGrpc.preimage .~ p

instance ToGrpc PaymentRequest LnGrpc.PayReqString where
  toGrpc x = Right $ defMessage & LnGrpc.payReq .~ coerce x

instance ToGrpc RHash LnGrpc.PaymentHash where
  toGrpc x = Right $ defMessage & LnGrpc.rHash .~ coerce x

instance ToGrpc RHash IGrpc.SubscribeSingleInvoiceRequest where
  toGrpc x = do
    rh <- toGrpc x
    Right $ defMessage & IGrpc.rHash .~ rh

newRHash :: RPreimage -> RHash
newRHash = RHash . SHA256.hash . coerce

newRPreimage :: MonadIO m => m RPreimage
newRPreimage = RPreimage <$> liftIO (getRandomBytes 32)


newPendingChanId :: MonadIO m => m PendingChannelId
newPendingChanId = PendingChannelId <$> liftIO (getRandomBytes 32)

newGrpcTimeout :: Int -> Maybe GrpcTimeoutSeconds
newGrpcTimeout x =
  if x > 0
    then Just $ GrpcTimeoutSeconds x
    else Nothing

unGrpcTimeout :: GrpcTimeoutSeconds -> Int
unGrpcTimeout = coerce

defaultSyncGrpcTimeout :: GrpcTimeoutSeconds
defaultSyncGrpcTimeout = GrpcTimeoutSeconds 60

defaultAsyncGrpcTimeout :: GrpcTimeoutSeconds
defaultAsyncGrpcTimeout = GrpcTimeoutSeconds 3600

toGrpcSat :: (Integral a, Bounded a) => MSat -> Either LndError a
toGrpcSat mSat = do
  let mVal :: Word64 = coerce mSat
  case divMod mVal 1000 of
    (val, 0) -> maybeToRight (ToGrpcError "MSat overflow") $ safeFromIntegral val
    _ -> Left $ ToGrpcError ("Cannot convert " <> inspect mVal <> " to Sat")

fromGrpcSat :: (Integral a) => a -> Either LndError MSat
fromGrpcSat sat =
  maybeToRight
    (FromGrpcError ("Cannot convert " <> (inspect . toInteger) sat <> " to MSat"))
    $ MSat . (1000 *) <$> safeFromIntegral sat

toGrpcMSat :: (Integral a, Bounded a) => MSat -> Either LndError a
toGrpcMSat x =
  maybeToRight
    (ToGrpcError "MSat overflow")
    $ safeFromIntegral (coerce x :: Word64)

toGrpcMaybe :: (ToGrpc a b) => Maybe a -> Either LndError (Maybe b)
toGrpcMaybe (Just fs) = Just <$> toGrpc fs
toGrpcMaybe Nothing = Right Nothing

fromGrpcMSat :: (Integral a) => a -> Either LndError MSat
fromGrpcMSat x =
  maybeToRight
    (FromGrpcError "MSat overflow")
    $ MSat <$> safeFromIntegral x