{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Singular.Registry.TxBuilder.Internal.Identity
Description : builder script identity and ledger/data conversion
License     : Apache-2.0

One owner for what a builder calls a thing: script construction and
hashing from config bytes, the cage and request identities derived
from a 'CageConfig', request datum encoding, and the conversions
between ledger and on-chain reference types.

This is the identity-and-conversion owner extracted from
@Singular.Registry.TxBuilder.Internal@; the public module re-exports
it and is its only intended consumer surface.
-}
module Singular.Registry.TxBuilder.Internal.Identity
    ( -- * Script construction
      mkCageScript
    , mkRequestScript
    , scriptFromBytes
    , scriptHashBytes
    , computeScriptHash

      -- * Derived identity
    , cagePolicyIdFromCfg
    , cageAddrFromCfg
    , requestAddrFromCfg
    , onChainTokenId

      -- * Datum helpers
    , mkRequestDatum
    , mkRequestDatumWith
    , toPlcData
    , toLedgerData
    , mkInlineDatum
    , extractCageDatum

      -- * Reference conversion
    , txInToRef
    , addrKeyHashBytes
    , addrFromKeyHashBytes
    , addrWitnessKeyHash

      -- * Request helpers
    , extractOwnerBytes

      -- * Constants
    , emptyRoot

      -- * Reference identity from pins
    , policyIdFromPin
    , addrFromBytes
    ) where

import Cardano.Crypto.Hash (hashFromBytes, hashToBytes)
import Cardano.Ledger.Address (Addr (..), decodeAddrEither)
import Cardano.Ledger.Alonzo.Scripts
    ( fromPlutusScript
    , mkPlutusScript
    )
import Cardano.Ledger.Api.Scripts.Data
    ( Data (..)
    , Datum (..)
    , binaryDataToData
    , dataToBinaryData
    )
import Cardano.Ledger.Api.Tx.Out
    ( TxOut
    , datumTxOutL
    )
import Cardano.Ledger.BaseTypes
    ( Network
    , TxIx (..)
    )
import Cardano.Ledger.Core
    ( Script
    , extractHash
    , hashScript
    )
import Cardano.Ledger.Credential
    ( Credential (..)
    , StakeReference (..)
    )
import Cardano.Ledger.Hashes (ScriptHash (..))
import Cardano.Ledger.Keys
    ( KeyHash (..)
    , KeyRole (..)
    )
import Cardano.Ledger.Mary.Value (PolicyID (..))
import Cardano.Ledger.Plutus.Language
    ( Language (PlutusV3)
    , Plutus (..)
    , PlutusBinary (..)
    )
import Cardano.Ledger.TxIn (TxId (..), TxIn (..))
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Short qualified as SBS
import Data.Coerce (coerce)
import Lens.Micro ((^.))
import PlutusCore.Data qualified as PLC
import PlutusTx.Builtins.Internal
    ( BuiltinByteString (..)
    , BuiltinData (..)
    )
import PlutusTx.IsData.Class
    ( FromData (..)
    , ToData (..)
    )
import Singular.Registry.Blueprint.Params (applyRequestParams)
import Singular.Registry.Config (CageConfig (..))
import Singular.Registry.Ledger
    ( AssetName (..)
    , ConwayEra
    , TokenId (..)
    )
import Singular.Registry.Types
    ( CageDatum (..)
    , Edge
    , OnChainRequest (..)
    , OnChainTokenId (..)
    , OnChainTxOutRef (..)
    )

-- | Empty MPF root (32 zero bytes).
emptyRoot :: ByteString
emptyRoot :: ByteString
emptyRoot = Int -> Word8 -> ByteString
BS.replicate Int
32 Word8
0

-- | Build the cage 'Script' from config bytes.
mkCageScript
    :: CageConfig
    -> Script ConwayEra
mkCageScript :: CageConfig -> Script ConwayEra
mkCageScript CageConfig
cfg =
    [Char] -> ShortByteString -> Script ConwayEra
scriptFromBytes
        [Char]
"mkCageScript"
        (CageConfig -> ShortByteString
cageScriptBytes CageConfig
cfg)

-- | Build the per-cage request 'Script' from config bytes.
mkRequestScript
    :: CageConfig
    -> TokenId
    -> Script ConwayEra
mkRequestScript :: CageConfig -> TokenId -> Script ConwayEra
mkRequestScript CageConfig
cfg TokenId
tid =
    [Char] -> ShortByteString -> Script ConwayEra
scriptFromBytes
        [Char]
"mkRequestScript"
        (CageConfig -> TokenId -> ShortByteString
requestScriptBytesFromCfg CageConfig
cfg TokenId
tid)

scriptFromBytes
    :: String
    -> SBS.ShortByteString
    -> Script ConwayEra
scriptFromBytes :: [Char] -> ShortByteString -> Script ConwayEra
scriptFromBytes [Char]
label ShortByteString
sbs =
    let plutus :: Plutus 'PlutusV3
plutus =
            forall (l :: Language). PlutusBinary -> Plutus l
Plutus @PlutusV3 (PlutusBinary -> Plutus 'PlutusV3)
-> PlutusBinary -> Plutus 'PlutusV3
forall a b. (a -> b) -> a -> b
$
                ShortByteString -> PlutusBinary
PlutusBinary ShortByteString
sbs
    in  case Plutus 'PlutusV3 -> Maybe (PlutusScript ConwayEra)
forall era (l :: Language) (m :: * -> *).
(AlonzoEraScript era, PlutusLanguage l, MonadFail m) =>
Plutus l -> m (PlutusScript era)
forall (l :: Language) (m :: * -> *).
(PlutusLanguage l, MonadFail m) =>
Plutus l -> m (PlutusScript ConwayEra)
mkPlutusScript Plutus 'PlutusV3
plutus of
            Just PlutusScript ConwayEra
ps -> PlutusScript ConwayEra -> Script ConwayEra
forall era. AlonzoEraScript era => PlutusScript era -> Script era
fromPlutusScript PlutusScript ConwayEra
ps
            Maybe (PlutusScript ConwayEra)
Nothing ->
                [Char] -> AlonzoScript ConwayEra
forall a. HasCallStack => [Char] -> a
error
                    ( [Char]
label
                        [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
": invalid PlutusV3 \
                           \script"
                    )

-- | Compute the 'ScriptHash' from raw script bytes.
computeScriptHash
    :: SBS.ShortByteString
    -> ScriptHash
computeScriptHash :: ShortByteString -> ScriptHash
computeScriptHash ShortByteString
sbs =
    let plutus :: Plutus 'PlutusV3
plutus =
            forall (l :: Language). PlutusBinary -> Plutus l
Plutus @PlutusV3 (PlutusBinary -> Plutus 'PlutusV3)
-> PlutusBinary -> Plutus 'PlutusV3
forall a b. (a -> b) -> a -> b
$
                ShortByteString -> PlutusBinary
PlutusBinary ShortByteString
sbs
    in  case forall era (l :: Language) (m :: * -> *).
(AlonzoEraScript era, PlutusLanguage l, MonadFail m) =>
Plutus l -> m (PlutusScript era)
mkPlutusScript @ConwayEra Plutus 'PlutusV3
plutus of
            Just PlutusScript ConwayEra
ps ->
                forall era. EraScript era => Script era -> ScriptHash
hashScript @ConwayEra (Script ConwayEra -> ScriptHash) -> Script ConwayEra -> ScriptHash
forall a b. (a -> b) -> a -> b
$
                    PlutusScript ConwayEra -> Script ConwayEra
forall era. AlonzoEraScript era => PlutusScript era -> Script era
fromPlutusScript PlutusScript ConwayEra
ps
            Maybe (PlutusScript ConwayEra)
Nothing ->
                [Char] -> ScriptHash
forall a. HasCallStack => [Char] -> a
error
                    [Char]
"computeScriptHash: invalid \
                    \PlutusV3 script"

-- | Compute the cage minting policy ID from config.
cagePolicyIdFromCfg :: CageConfig -> PolicyID
cagePolicyIdFromCfg :: CageConfig -> PolicyID
cagePolicyIdFromCfg =
    ScriptHash -> PolicyID
PolicyID (ScriptHash -> PolicyID)
-> (CageConfig -> ScriptHash) -> CageConfig -> PolicyID
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CageConfig -> ScriptHash
cfgScriptHash

-- | Compute the cage script address from config.
cageAddrFromCfg
    :: CageConfig
    -> Network
    -> Addr
cageAddrFromCfg :: CageConfig -> Network -> Addr
cageAddrFromCfg CageConfig
cfg Network
net =
    Network -> Credential Payment -> StakeReference -> Addr
Addr
        Network
net
        (ScriptHash -> Credential Payment
forall (kr :: KeyRole). ScriptHash -> Credential kr
ScriptHashObj (ScriptHash -> Credential Payment)
-> ScriptHash -> Credential Payment
forall a b. (a -> b) -> a -> b
$ CageConfig -> ScriptHash
cfgScriptHash CageConfig
cfg)
        StakeReference
StakeRefNull

-- | Compute the request script address for a token.
requestAddrFromCfg
    :: CageConfig
    -> TokenId
    -> Network
    -> Addr
requestAddrFromCfg :: CageConfig -> TokenId -> Network -> Addr
requestAddrFromCfg CageConfig
cfg TokenId
tid Network
net =
    Network -> Credential Payment -> StakeReference -> Addr
Addr
        Network
net
        ( ScriptHash -> Credential Payment
forall (kr :: KeyRole). ScriptHash -> Credential kr
ScriptHashObj (ScriptHash -> Credential Payment)
-> ScriptHash -> Credential Payment
forall a b. (a -> b) -> a -> b
$
            ShortByteString -> ScriptHash
computeScriptHash
                (CageConfig -> TokenId -> ShortByteString
requestScriptBytesFromCfg CageConfig
cfg TokenId
tid)
        )
        StakeReference
StakeRefNull

-- | Convert a ledger token id to its on-chain token id.
onChainTokenId :: TokenId -> OnChainTokenId
onChainTokenId :: TokenId -> OnChainTokenId
onChainTokenId TokenId
tid =
    BuiltinByteString -> OnChainTokenId
OnChainTokenId (BuiltinByteString -> OnChainTokenId)
-> BuiltinByteString -> OnChainTokenId
forall a b. (a -> b) -> a -> b
$
        ByteString -> BuiltinByteString
BuiltinByteString (ByteString -> BuiltinByteString)
-> ByteString -> BuiltinByteString
forall a b. (a -> b) -> a -> b
$
            ShortByteString -> ByteString
SBS.fromShort (ShortByteString -> ByteString) -> ShortByteString -> ByteString
forall a b. (a -> b) -> a -> b
$
                let AssetName ShortByteString
sbs = TokenId -> AssetName
unTokenId TokenId
tid
                in  ShortByteString
sbs

requestScriptBytesFromCfg
    :: CageConfig
    -> TokenId
    -> SBS.ShortByteString
requestScriptBytesFromCfg :: CageConfig -> TokenId -> ShortByteString
requestScriptBytesFromCfg CageConfig
cfg TokenId
tid =
    ByteString -> OnChainTokenId -> ShortByteString -> ShortByteString
applyRequestParams
        (ScriptHash -> ByteString
scriptHashBytes (ScriptHash -> ByteString) -> ScriptHash -> ByteString
forall a b. (a -> b) -> a -> b
$ CageConfig -> ScriptHash
cfgScriptHash CageConfig
cfg)
        (TokenId -> OnChainTokenId
onChainTokenId TokenId
tid)
        (CageConfig -> ShortByteString
requestScriptBytes CageConfig
cfg)

scriptHashBytes :: ScriptHash -> ByteString
scriptHashBytes :: ScriptHash -> ByteString
scriptHashBytes (ScriptHash Hash ADDRHASH EraIndependentScript
h) =
    Hash ADDRHASH EraIndependentScript -> ByteString
forall h a. Hash h a -> ByteString
hashToBytes Hash ADDRHASH EraIndependentScript
h

-- | Build a 'CageDatum' for a request at one seven-admitted-edges edge (#183).
mkRequestDatum
    :: TokenId
    -> Addr
    -> ByteString
    -> Edge
    -- ^ The seven-admitted-edges row index the request names
    -> Integer
    -- ^ The deposit the request rides with, over and above the tip
    -> Integer
    -> PLC.Data
mkRequestDatum :: TokenId
-> Addr -> ByteString -> Integer -> Integer -> Integer -> Data
mkRequestDatum TokenId
tid Addr
addr ByteString
key Integer
edge Integer
deposit Integer
submittedAt =
    TokenId
-> Addr
-> ByteString
-> Integer
-> Integer
-> Integer
-> (ByteString, Maybe Data)
-> Data
mkRequestDatumWith
        TokenId
tid
        Addr
addr
        ByteString
key
        Integer
edge
        Integer
deposit
        Integer
submittedAt
        (ByteString
BS.empty, Maybe Data
forall a. Maybe a
Nothing)

{- | A request datum naming where the edge it books delivers (#157
request-destination-binding) and carrying the datum the delivered output
must hold (#419). The cage reads the destination for every edge that mints
an active or terminal token, and the approval that certifies the edge
binds the address and the hash of that datum, so the booking and the fold
cannot disagree about where the token goes or what it carries.
-}
mkRequestDatumWith
    :: TokenId
    -> Addr
    -> ByteString
    -> Edge
    -- ^ The seven-admitted-edges row index the request names
    -> Integer
    -- ^ The deposit the request rides with, over and above the tip
    -> Integer
    -> (ByteString, Maybe PLC.Data)
    -> PLC.Data
mkRequestDatumWith :: TokenId
-> Addr
-> ByteString
-> Integer
-> Integer
-> Integer
-> (ByteString, Maybe Data)
-> Data
mkRequestDatumWith TokenId
tid Addr
addr ByteString
key Integer
edge Integer
deposit Integer
submittedAt (ByteString, Maybe Data)
destination =
    let datum :: OnChainRequest
datum =
            OnChainRequest
                { requestToken :: OnChainTokenId
requestToken = TokenId -> OnChainTokenId
onChainTokenId TokenId
tid
                , requestOwner :: BuiltinByteString
requestOwner =
                    ByteString -> BuiltinByteString
BuiltinByteString
                        (Addr -> ByteString
addrKeyHashBytes Addr
addr)
                , requestKey :: ByteString
requestKey = ByteString
key
                , requestEdge :: Integer
requestEdge = Integer
edge
                , requestDeposit :: Integer
requestDeposit = Integer
deposit
                , requestSubmittedAt :: Integer
requestSubmittedAt = Integer
submittedAt
                , requestDestination :: (ByteString, Maybe Data)
requestDestination = (ByteString, Maybe Data)
destination
                }
    in  CageDatum -> Data
forall a. ToData a => a -> Data
toPlcData (OnChainRequest -> CageDatum
RequestDatum OnChainRequest
datum)

{- | Convert a 'ToData' value to
'PlutusCore.Data.Data'.
-}
toPlcData :: (ToData a) => a -> PLC.Data
toPlcData :: forall a. ToData a => a -> Data
toPlcData a
x =
    let BuiltinData Data
d = a -> BuiltinData
forall a. ToData a => a -> BuiltinData
toBuiltinData a
x in Data
d

-- | Convert a 'ToData' value to a ledger 'Data'.
toLedgerData
    :: (ToData a) => a -> Data ConwayEra
toLedgerData :: forall a. ToData a => a -> Data ConwayEra
toLedgerData = Data -> Data ConwayEra
forall era. Era era => Data -> Data era
Data (Data -> Data ConwayEra) -> (a -> Data) -> a -> Data ConwayEra
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Data
forall a. ToData a => a -> Data
toPlcData

{- | Wrap 'PlutusCore.Data.Data' as an inline
'Datum'.
-}
mkInlineDatum :: PLC.Data -> Datum ConwayEra
mkInlineDatum :: Data -> Datum ConwayEra
mkInlineDatum Data
d =
    BinaryData ConwayEra -> Datum ConwayEra
forall era. BinaryData era -> Datum era
Datum (BinaryData ConwayEra -> Datum ConwayEra)
-> BinaryData ConwayEra -> Datum ConwayEra
forall a b. (a -> b) -> a -> b
$
        Data ConwayEra -> BinaryData ConwayEra
forall era. Data era -> BinaryData era
dataToBinaryData
            (Data -> Data ConwayEra
forall era. Era era => Data -> Data era
Data Data
d :: Data ConwayEra)

{- | Convert a ledger 'TxIn' to an on-chain
'OnChainTxOutRef'.
-}
txInToRef :: TxIn -> OnChainTxOutRef
txInToRef :: TxIn -> OnChainTxOutRef
txInToRef (TxIn (TxId SafeHash EraIndependentTxBody
h) (TxIx Word16
ix)) =
    OnChainTxOutRef
        { txOutRefId :: BuiltinByteString
txOutRefId =
            ByteString -> BuiltinByteString
BuiltinByteString
                (Hash HASH EraIndependentTxBody -> ByteString
forall h a. Hash h a -> ByteString
hashToBytes (SafeHash EraIndependentTxBody -> Hash HASH EraIndependentTxBody
forall i. SafeHash i -> Hash HASH i
extractHash SafeHash EraIndependentTxBody
h))
        , txOutRefIdx :: Integer
txOutRefIdx = Word16 -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
ix
        }

{- | Extract the payment key hash raw bytes from
an 'Addr'.
-}
addrKeyHashBytes :: Addr -> ByteString
addrKeyHashBytes :: Addr -> ByteString
addrKeyHashBytes
    (Addr Network
_ (KeyHashObj (KeyHash Hash ADDRHASH (VerKeyDSIGN DSIGN)
h)) StakeReference
_) =
        Hash ADDRHASH (VerKeyDSIGN DSIGN) -> ByteString
forall h a. Hash h a -> ByteString
hashToBytes Hash ADDRHASH (VerKeyDSIGN DSIGN)
h
addrKeyHashBytes Addr
_ = ByteString
BS.empty

{- | Reconstruct an 'Addr' from raw payment key
hash bytes.
-}
addrFromKeyHashBytes
    :: Network
    -> ByteString
    -> Addr
addrFromKeyHashBytes :: Network -> ByteString -> Addr
addrFromKeyHashBytes Network
net ByteString
bs =
    case ByteString -> Maybe (Hash ADDRHASH (VerKeyDSIGN DSIGN))
forall h a. HashAlgorithm h => ByteString -> Maybe (Hash h a)
hashFromBytes ByteString
bs of
        Just Hash ADDRHASH (VerKeyDSIGN DSIGN)
h ->
            Network -> Credential Payment -> StakeReference -> Addr
Addr
                Network
net
                (KeyHash Payment -> Credential Payment
forall (kr :: KeyRole). KeyHash kr -> Credential kr
KeyHashObj (Hash ADDRHASH (VerKeyDSIGN DSIGN) -> KeyHash Payment
forall (r :: KeyRole).
Hash ADDRHASH (VerKeyDSIGN DSIGN) -> KeyHash r
KeyHash Hash ADDRHASH (VerKeyDSIGN DSIGN)
h))
                StakeReference
StakeRefNull
        Maybe (Hash ADDRHASH (VerKeyDSIGN DSIGN))
Nothing ->
            [Char] -> Addr
forall a. HasCallStack => [Char] -> a
error
                [Char]
"addrFromKeyHashBytes: \
                \invalid hash"

{- | Extract a 'KeyHash' ''Witness' from raw
payment key hash bytes.
-}
addrWitnessKeyHash
    :: ByteString -> KeyHash Guard
addrWitnessKeyHash :: ByteString -> KeyHash Guard
addrWitnessKeyHash ByteString
bs =
    case ByteString -> Maybe (Hash ADDRHASH (VerKeyDSIGN DSIGN))
forall h a. HashAlgorithm h => ByteString -> Maybe (Hash h a)
hashFromBytes ByteString
bs of
        Just Hash ADDRHASH (VerKeyDSIGN DSIGN)
h ->
            KeyHash Payment -> KeyHash Guard
forall a b. Coercible a b => a -> b
coerce
                (Hash ADDRHASH (VerKeyDSIGN DSIGN) -> KeyHash Payment
forall (r :: KeyRole).
Hash ADDRHASH (VerKeyDSIGN DSIGN) -> KeyHash r
KeyHash Hash ADDRHASH (VerKeyDSIGN DSIGN)
h :: KeyHash Payment)
        Maybe (Hash ADDRHASH (VerKeyDSIGN DSIGN))
Nothing ->
            [Char] -> KeyHash Guard
forall a. HasCallStack => [Char] -> a
error
                [Char]
"addrWitnessKeyHash: \
                \invalid hash"

{- | Extract a 'CageDatum' from an inline datum
in a 'TxOut'.
-}
extractCageDatum
    :: TxOut ConwayEra -> Maybe CageDatum
extractCageDatum :: TxOut ConwayEra -> Maybe CageDatum
extractCageDatum TxOut ConwayEra
txOut =
    case TxOut ConwayEra
BabbageTxOut ConwayEra
txOut BabbageTxOut ConwayEra
-> Getting
     (Datum ConwayEra) (BabbageTxOut ConwayEra) (Datum ConwayEra)
-> Datum ConwayEra
forall s a. s -> Getting a s a -> a
^. (Datum ConwayEra -> Const (Datum ConwayEra) (Datum ConwayEra))
-> TxOut ConwayEra -> Const (Datum ConwayEra) (TxOut ConwayEra)
Getting
  (Datum ConwayEra) (BabbageTxOut ConwayEra) (Datum ConwayEra)
forall era. BabbageEraTxOut era => Lens' (TxOut era) (Datum era)
Lens' (TxOut ConwayEra) (Datum ConwayEra)
datumTxOutL of
        Datum BinaryData ConwayEra
bd ->
            let Data Data
plcData =
                    BinaryData ConwayEra -> Data ConwayEra
forall era. Era era => BinaryData era -> Data era
binaryDataToData BinaryData ConwayEra
bd
            in  BuiltinData -> Maybe CageDatum
forall a. FromData a => BuiltinData -> Maybe a
fromBuiltinData (Data -> BuiltinData
BuiltinData Data
plcData)
        Datum ConwayEra
_ -> Maybe CageDatum
forall a. Maybe a
Nothing

{- | Extract the owner key hash bytes from a
request 'TxOut'.
-}
extractOwnerBytes
    :: TxOut ConwayEra -> ByteString
extractOwnerBytes :: TxOut ConwayEra -> ByteString
extractOwnerBytes TxOut ConwayEra
out =
    case TxOut ConwayEra -> Maybe CageDatum
extractCageDatum TxOut ConwayEra
out of
        Just (RequestDatum OnChainRequest
req) ->
            let OnChainRequest
                    { requestOwner :: OnChainRequest -> BuiltinByteString
requestOwner =
                        BuiltinByteString ByteString
bs
                    } = OnChainRequest
req
            in  ByteString
bs
        Maybe CageDatum
_ ->
            [Char] -> ByteString
forall a. HasCallStack => [Char] -> a
error
                [Char]
"extractOwnerBytes: \
                \not a request"

{- | The policy id a 28-byte pin names. The four pins the state datum
carries are raw script hashes; this is the one place that turns one back
into the ledger's own type.
-}
policyIdFromPin :: SBS.ShortByteString -> PolicyID
policyIdFromPin :: ShortByteString -> PolicyID
policyIdFromPin ShortByteString
pin = case ByteString -> Maybe (Hash ADDRHASH EraIndependentScript)
forall h a. HashAlgorithm h => ByteString -> Maybe (Hash h a)
hashFromBytes (ShortByteString -> ByteString
SBS.fromShort ShortByteString
pin) of
    Just Hash ADDRHASH EraIndependentScript
h -> ScriptHash -> PolicyID
PolicyID (Hash ADDRHASH EraIndependentScript -> ScriptHash
ScriptHash Hash ADDRHASH EraIndependentScript
h)
    Maybe (Hash ADDRHASH EraIndependentScript)
Nothing ->
        [Char] -> PolicyID
forall a. HasCallStack => [Char] -> a
error
            ( [Char]
"policyIdFromPin: a pin is not a 28-byte script hash: "
                [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> ShortByteString -> [Char]
forall a. Show a => a -> [Char]
show ShortByteString
pin
            )

{- | The address a request's binary destination names (#157 request-destination-binding). The
bytes are a full Cardano address, network byte and all, so nothing here
supplies a network of its own.
-}
addrFromBytes :: ByteString -> Maybe Addr
addrFromBytes :: ByteString -> Maybe Addr
addrFromBytes ByteString
bs = case ByteString -> Either [Char] Addr
decodeAddrEither ByteString
bs of
    Right Addr
a -> Addr -> Maybe Addr
forall a. a -> Maybe a
Just Addr
a
    Left [Char]
_ -> Maybe Addr
forall a. Maybe a
Nothing