btc-lsp-0.1.0.0: src/BtcLsp/Data/Type.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module BtcLsp.Data.Type
( Nonce,
newNonce,
LnInvoice (..),
LnInvoiceStatus (..),
LnChanStatus (..),
Liquidity (..),
Money (..),
FeeRate (..),
UnsafeOnChainAddress (..),
Seconds (..),
LogFormat (..),
YesodLog (..),
MicroSeconds (..),
SwapStatus (..),
swapStatusChain,
swapStatusLn,
swapStatusFinal,
Failure (..),
FailureInternal (..),
FailureInput (..),
tryFailureE,
tryFailureT,
tryFromE,
tryFromT,
SocketAddress (..),
BlkHash (..),
BlkHeight (..),
BlkStatus (..),
SwapUtxoStatus (..),
Privacy (..),
NodePubKeyHex (..),
NodeUri (..),
NodeUriHex (..),
UtxoLockId (..),
RHashHex (..),
Uuid,
unUuid,
newUuid,
Vbyte (..),
RowQty (..),
PsbtUtxo (..),
SwapHash (..),
)
where
import BtcLsp.Data.Kind
import BtcLsp.Data.Orphan ()
import BtcLsp.Import.External
import qualified BtcLsp.Import.Psql as Psql
import qualified BtcLsp.Text as T
import qualified Data.ByteString.Base16 as B16
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Time.Clock as Clock
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as UUID
import qualified LndClient as Lnd
import qualified LndClient.Data.OutPoint as OP
import qualified Network.Bitcoin.BlockChain as Btc
import Text.Julius (ToJavascript)
import qualified Universum
import qualified Witch
import Yesod.Core
newtype Nonce
= Nonce Word64
deriving newtype
( Eq,
Ord,
Show,
Read,
Psql.PersistField,
Psql.PersistFieldSql
)
deriving stock
( Generic
)
instance Out Nonce
instance From Nonce Word64
instance From Word64 Nonce
newNonce :: (MonadIO m) => m Nonce
newNonce =
liftIO $
Nonce
. utcTimeToMicros
<$> Clock.getCurrentTime
utcTimeToMicros :: UTCTime -> Word64
utcTimeToMicros x =
fromInteger $
diffTimeToPicoseconds
( fromRational
. toRational
$ diffUTCTime x epoch
)
`div` 1000000
epoch :: UTCTime
epoch =
posixSecondsToUTCTime 0
data LogFormat
= Bracket
| JSON
deriving stock
( Read
)
data YesodLog
= YesodLogAll
| YesodLogNoMain
| YesodLogNothing
deriving stock
( Eq,
Ord,
Show,
Read,
Generic
)
instance FromJSON YesodLog
newtype Seconds
= Seconds Word64
deriving newtype
( Eq,
Ord,
Show,
Num
)
deriving stock
( Generic
)
instance Out Seconds
newtype MicroSeconds
= MicroSeconds Integer
deriving newtype
( Eq,
Ord,
Show,
Num
)
deriving stock
( Generic
)
instance Out MicroSeconds
newtype LnInvoice (mrel :: MoneyRelation)
= LnInvoice Lnd.PaymentRequest
deriving newtype
( Eq,
Show,
Psql.PersistField,
Psql.PersistFieldSql,
PathPiece
)
deriving stock
( Generic
)
instance Out (LnInvoice mrel)
instance From Lnd.PaymentRequest (LnInvoice mrel)
instance From (LnInvoice mrel) Lnd.PaymentRequest
instance From Text (LnInvoice mrel) where
from =
via @Lnd.PaymentRequest
instance From (LnInvoice mrel) Text where
from =
via @Lnd.PaymentRequest
newtype SwapHash = SwapHash Text
deriving newtype
( Eq,
Show,
Read,
PathPiece,
ToJavascript,
ToJSON
)
deriving stock
( Generic
)
instance Out SwapHash
instance ToTypedContent (Maybe SwapHash) where
toTypedContent = toTypedContent . toJSON
instance ToContent (Maybe SwapHash) where
toContent = toContent . toJSON
data LnInvoiceStatus
= LnInvoiceStatusNew
| LnInvoiceStatusLocked
| LnInvoiceStatusSettled
| LnInvoiceStatusCancelled
| LnInvoiceStatusExpired
deriving stock
( Generic,
Show,
Read,
Eq
)
instance Out LnInvoiceStatus
data LnChanStatus
= LnChanStatusPendingOpen
| LnChanStatusOpened
| LnChanStatusActive
| LnChanStatusFullyResolved
| LnChanStatusInactive
| LnChanStatusPendingClose
| LnChanStatusClosed
deriving stock
( Generic,
Show,
Read,
Eq,
Ord
)
instance Out LnChanStatus
newtype Liquidity (dir :: Direction) = Liquidity
{ unLiquidity :: MSat
}
deriving newtype
( Eq,
Ord,
Show,
Read,
Num
)
deriving stock
( Generic
)
instance Out (Liquidity dir)
newtype
Money
(owner :: Owner)
(btcl :: BitcoinLayer)
(mrel :: MoneyRelation) = Money
{ unMoney :: MSat
}
deriving newtype
( Eq,
Ord,
Show,
Read,
Num,
Psql.PersistField,
Psql.PersistFieldSql
)
deriving stock
( Generic
)
instance Out (Money owner btcl mrel)
instance From MSat (Money owner btcl mrel)
instance From (Money owner btcl mrel) MSat
instance From Word64 (Money owner btcl mrel) where
from =
via @MSat
instance From (Money owner btcl mrel) Word64 where
from =
via @MSat
instance TryFrom Natural (Money owner btcl mrel) where
tryFrom =
from @Word64 `composeTryRhs` tryFrom
instance From (Money owner btcl mrel) Natural where
from =
via @Word64
instance TryFrom (Ratio Natural) (Money owner btcl mrel) where
tryFrom =
tryFrom @Natural
`composeTry` tryFrom
instance From (Money owner btcl mrel) (Ratio Natural) where
from =
via @Natural
instance TryFrom Rational (Money owner btcl mrel) where
tryFrom =
tryFrom @(Ratio Natural)
`composeTry` tryFrom
instance From (Money owner btcl mrel) Rational where
from =
via @(Ratio Natural)
instance ToMessage (Money owner btcl mrel) where
toMessage =
T.displayRational 1
. (/ 1000)
. from
newtype FeeRate
= FeeRate (Ratio Word64)
deriving newtype
( Eq,
Ord,
Show
)
deriving stock
( Generic
)
instance From (Ratio Word64) FeeRate
instance From FeeRate (Ratio Word64)
instance From FeeRate (Ratio Natural) where
from =
via @(Ratio Word64)
instance TryFrom Rational FeeRate where
tryFrom =
from @(Ratio Word64)
`composeTryRhs` tryFrom
instance From FeeRate Rational where
from =
via @(Ratio Word64)
instance ToMessage FeeRate where
toMessage =
(<> "%")
. T.displayRational 1
. (* 100)
. from
newtype UnsafeOnChainAddress (mrel :: MoneyRelation)
= UnsafeOnChainAddress Text
deriving newtype
( Eq,
Ord,
Show,
Read,
PathPiece,
Psql.PersistField,
Psql.PersistFieldSql
)
deriving stock
( Generic
)
instance Out (UnsafeOnChainAddress mrel)
instance From Text (UnsafeOnChainAddress mrel)
instance From (UnsafeOnChainAddress mrel) Text
data SwapStatus
= -- | Waiting on-chain funding trx with
-- given amt from user with
-- some confirmations.
SwapWaitingFundChain
| -- | Swap has been funded on-chain,
-- need to open LN channel now.
SwapWaitingPeer
| -- | Waiting channel opening trx
-- to be mined with some confirmations.
SwapWaitingChan
| -- | Final statuses
SwapSucceeded
| SwapExpired
deriving stock
( Eq,
Ord,
Show,
Read,
Generic,
Enum,
Bounded
)
instance Out SwapStatus
swapStatusChain :: [SwapStatus]
swapStatusChain =
[ SwapWaitingFundChain,
SwapWaitingPeer
]
swapStatusLn :: [SwapStatus]
swapStatusLn =
[ SwapWaitingChan
]
swapStatusFinal :: [SwapStatus]
swapStatusFinal =
[ SwapSucceeded,
SwapExpired
]
instance PathPiece SwapStatus where
fromPathPiece :: Text -> Maybe SwapStatus
fromPathPiece =
readMaybe
. unpack
. T.toTitle
toPathPiece :: SwapStatus -> Text
toPathPiece =
T.toLower
. Universum.show
data Failure
= FailureInp FailureInput
| FailureInt FailureInternal
deriving stock
( Eq,
Show,
Generic
)
instance Out Failure
data FailureInput
= FailureNonce
| FailureNonSegwitAddr
| FailureNonValidAddr
deriving stock
( Eq,
Show,
Generic
)
instance Out FailureInput
data FailureInternal
= FailureGrpcServer Text
| FailureGrpcClient Text
| FailureMath Text
| FailurePrivate Text
| FailureRedacted
deriving stock
( Eq,
Show,
Generic
)
instance Out FailureInternal
tryFailureE ::
forall source target.
( Show source,
Typeable source,
Typeable target
) =>
Text ->
Either (TryFromException source target) target ->
Either Failure target
tryFailureE label =
first $
FailureInt
. FailureMath
. (label <>)
. (" " <>)
. Universum.show
tryFailureT ::
forall source target m.
( Show source,
Typeable source,
Typeable target,
Monad m
) =>
Text ->
Either (TryFromException source target) target ->
ExceptT Failure m target
tryFailureT label =
except . tryFailureE label
tryFromE ::
forall source target.
( Show source,
Typeable source,
Typeable target,
TryFrom source target,
'False ~ (source == target)
) =>
Text ->
source ->
Either Failure target
tryFromE label =
tryFailureE label . tryFrom
tryFromT ::
forall source target m.
( Show source,
Typeable source,
Typeable target,
TryFrom source target,
Monad m,
'False ~ (source == target)
) =>
Text ->
source ->
ExceptT Failure m target
tryFromT label =
except . tryFromE label
data SocketAddress = SocketAddress
{ socketAddressHost :: HostName,
socketAddressPort :: PortNumber
}
deriving stock
( Eq,
Ord,
Show,
Generic
)
instance Out SocketAddress
newtype BlkHash
= BlkHash Btc.BlockHash
deriving stock (Eq, Ord, Show, Generic)
deriving newtype (Psql.PersistField, Psql.PersistFieldSql)
instance Out BlkHash
instance From Btc.BlockHash BlkHash
instance From BlkHash Btc.BlockHash
newtype BlkHeight
= BlkHeight Word64
deriving stock
( Eq,
Ord,
Show,
Generic
)
deriving newtype
( Num,
Psql.PersistField,
Psql.PersistFieldSql
)
instance Out BlkHeight
instance ToJSON BlkHeight
instance From Word64 BlkHeight
instance From BlkHeight Word64
instance From BlkHeight Natural where
from =
via @Word64
instance TryFrom Btc.BlockHeight BlkHeight where
tryFrom =
from @Word64
`composeTryRhs` tryFrom
instance From BlkHeight Btc.BlockHeight where
from =
via @Word64
data BlkStatus
= BlkConfirmed
| BlkOrphan
deriving stock
( Eq,
Ord,
Show,
Read,
Generic
)
instance Out BlkStatus
data SwapUtxoStatus
= SwapUtxoUnspent
| SwapUtxoUnspentDust
| SwapUtxoUnspentChanReserve
| SwapUtxoSpentChanSwapped
| SwapUtxoSpentRefund
| SwapUtxoOrphan
deriving stock
( Eq,
Ord,
Show,
Read,
Generic
)
instance Out SwapUtxoStatus
data Privacy
= Public
| Private
deriving stock
( Eq,
Ord,
Show,
Read,
Enum,
Bounded,
Generic
)
instance Out Privacy
newtype NodePubKeyHex
= NodePubKeyHex Text
deriving newtype (Eq, Ord, Show, Read, IsString)
deriving stock (Generic)
instance Out NodePubKeyHex
instance From NodePubKeyHex Text
instance From Text NodePubKeyHex
instance TryFrom NodePubKey NodePubKeyHex where
tryFrom src =
from
`composeTryRhs` ( first
( TryFromException src
. Just
. toException
)
. TE.decodeUtf8'
. B16.encode
. coerce
)
$ src
newtype UtxoLockId = UtxoLockId ByteString
deriving newtype (Eq, Ord, Show, Read)
deriving stock (Generic)
instance Out UtxoLockId
data NodeUri = NodeUri
{ nodeUriPubKey :: NodePubKey,
nodeUriSocketAddress :: SocketAddress
}
deriving stock
( Eq,
Ord,
Show,
Generic
)
instance Out NodeUri
newtype NodeUriHex
= NodeUriHex Text
deriving newtype (Eq, Ord, Show, Read, IsString)
deriving stock (Generic)
instance Out NodeUriHex
instance From NodeUriHex Text
instance From Text NodeUriHex
instance TryFrom NodeUri NodeUriHex where
tryFrom src =
bimap
(withTarget @NodeUriHex . withSource src)
( \pubHex ->
from @Text $
from pubHex
<> "@"
<> from host
<> ":"
<> from (showIntegral port)
)
$ tryFrom @NodePubKey @NodePubKeyHex $
nodeUriPubKey src
where
sock = nodeUriSocketAddress src
host = socketAddressHost sock
port = socketAddressPort sock
newtype RHashHex = RHashHex
{ unRHashHex :: Text
}
deriving newtype
( Eq,
Ord,
Show,
Read,
PathPiece
)
deriving stock
( Generic
)
instance Out RHashHex
instance From RHashHex Text
instance From Text RHashHex
instance From RHash RHashHex where
from =
--
-- NOTE : decodeUtf8 in general is unsafe
-- but here we know that it will not fail
-- because of B16
--
RHashHex
. decodeUtf8
. B16.encode
. coerce
instance From RHashHex RHash where
from =
--
-- NOTE : this is not RFC 4648-compliant,
-- using only for the practical purposes
--
RHash
. B16.decodeLenient
. encodeUtf8
. unRHashHex
newtype Uuid (tab :: Table) = Uuid
{ unUuid' :: UUID
}
deriving newtype
( Eq,
Ord,
Show,
Read
)
deriving stock (Generic)
unUuid :: Uuid tab -> UUID
unUuid =
unUuid'
instance Out (Uuid tab) where
docPrec x =
docPrec x
. UUID.toText
. unUuid
doc =
docPrec 0
newUuid :: (MonadIO m) => m (Uuid tab)
newUuid =
liftIO $
Uuid <$> UUID.nextRandom
newtype Vbyte = Vbyte
{ unVbyte :: Ratio Natural
}
deriving newtype
( Eq,
Ord,
Show,
Num
)
deriving stock
( Generic
)
instance Out Vbyte
instance From Vbyte (Ratio Natural)
instance From (Ratio Natural) Vbyte
newtype RowQty = RowQty
{ unRowQty :: Int64
}
deriving newtype
( Eq,
Ord,
Show
)
deriving stock
( Generic
)
instance Out RowQty
data PsbtUtxo = PsbtUtxo
{ getOutPoint :: OP.OutPoint,
getAmt :: MSat,
getLockId :: Maybe UtxoLockId
}
deriving stock (Show, Generic)
instance Out PsbtUtxo
instance From RowQty Int64
instance From Int64 RowQty
instance From Int RowQty where
from =
via @Int64
--
-- NOTE : we're taking advantage of
-- PostgreSQL understanding UUID values
--
instance Psql.PersistField (Uuid tab) where
toPersistValue =
Psql.PersistLiteral_ Psql.Escaped
. UUID.toASCIIBytes
. unUuid
fromPersistValue = \case
Psql.PersistLiteral_ Psql.Escaped x ->
maybe
( Left $
"Failed to deserialize a UUID, got literal: "
<> inspectPlain x
)
( Right
. Uuid
)
$ UUID.fromASCIIBytes x
failure ->
Left $
"Failed to deserialize a UUID, got: "
<> inspectPlain failure
instance Psql.PersistFieldSql (Uuid tab) where
sqlType =
const $
Psql.SqlOther "uuid"
instance ToMessage (Uuid tab) where
toMessage =
(<> "...")
. T.take 5
. UUID.toText
. unUuid
instance PathPiece (Uuid tab) where
fromPathPiece =
(Uuid <$>)
. UUID.fromText
toPathPiece =
UUID.toText
. unUuid
Psql.derivePersistField "LnInvoiceStatus"
Psql.derivePersistField "LnChanStatus"
Psql.derivePersistField "SwapStatus"
Psql.derivePersistField "BlkStatus"
Psql.derivePersistField "SwapUtxoStatus"
Psql.derivePersistField "Privacy"
Psql.derivePersistField "UtxoLockId"