-- SPDX-FileCopyrightText: 2022 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

module Morley.Tezos.Address.Alias
  ( AddressOrAlias(..)
  , addressOrAliasKindSanity
  , Alias(..)
  , SomeAlias
  , pattern SomeAlias
  , ImplicitAlias
  , ContractAlias
  , ImplicitAddressOrAlias
  , ContractAddressOrAlias
  , unAlias
  , mkAlias
  , aliasKindSanity
  , SomeAddressOrAlias(..)
  , aliasPrefix
  , contractPrefix
  , implicitPrefix
  )
  where

import Data.Aeson (FromJSON(..), ToJSON(..))
import Data.Constraint (Dict(..), (\\))
import Data.Constraint.Extras (has)
import Data.Constraint.Extras.TH (deriveArgDict)
import Data.GADT.Compare.TH (deriveGCompare, deriveGEq)
import Data.Singletons (SingI(..), demote)
import Data.Text qualified as T
import Fmt (Buildable(..), Builder, pretty)
import Options.Applicative qualified as Opt

import Data.Char qualified as Char
import Morley.Tezos.Address
import Morley.Tezos.Address.Kinds
import Morley.Util.CLI (HasCLReader(..))
import Morley.Util.Constrained
import Morley.Util.Interpolate (itu)
import Morley.Util.Sing
import Morley.Util.TH

-- | @octez-client@ can associate addresses with textual aliases.
-- This type denotes such an alias.
data Alias (kind :: AddressKind) where
  ImplicitAlias :: Text -> Alias 'AddressKindImplicit
  ContractAlias :: Text -> Alias 'AddressKindContract

-- | A type only allowing v'ImplicitAlias' values.
type ImplicitAlias = Alias 'AddressKindImplicit

-- | A type only allowing v'ContractAlias' values.
type ContractAlias = Alias 'AddressKindContract

deriving stock instance Show (Alias kind)
deriving stock instance Eq (Alias kind)
deriving stock instance Ord (Alias kind)

deriveGADTNFData ''Alias
deriveGEq ''Alias
deriveGCompare ''Alias
deriveArgDict ''Alias

-- | Get raw alias text from 'Alias'
unAlias :: Alias kind -> Text
unAlias :: forall (kind :: AddressKind). Alias kind -> Text
unAlias = \case
  ImplicitAlias Text
x -> Text
x
  ContractAlias Text
x -> Text
x

-- | Construct an 'Alias' from alias 'Text'.
mkAlias :: forall kind. (SingI kind, L1AddressKind kind) => Text -> Alias kind
mkAlias :: forall (kind :: AddressKind).
(SingI kind, L1AddressKind kind) =>
Text -> Alias kind
mkAlias = forall (kind :: AddressKind) a. L1AddressKind kind => a -> a
usingImplicitOrContractKind @kind ((Text -> Alias kind) -> Text -> Alias kind)
-> (Text -> Alias kind) -> Text -> Alias kind
forall a b. (a -> b) -> a -> b
$ case forall {k} (a :: k). SingI a => Sing a
forall (a :: AddressKind). SingI a => Sing a
sing @kind of
  Sing kind
SingAddressKind kind
SAddressKindImplicit -> Text -> Alias kind
Text -> Alias 'AddressKindImplicit
ImplicitAlias
  Sing kind
SingAddressKind kind
SAddressKindContract -> Text -> Alias kind
Text -> Alias 'AddressKindContract
ContractAlias

instance Buildable (Alias kind) where
  build :: Alias kind -> Builder
build = Text -> Builder
forall p. Buildable p => p -> Builder
build (Text -> Builder) -> (Alias kind -> Text) -> Alias kind -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Alias kind -> Text
forall (kind :: AddressKind). Alias kind -> Text
unAlias

instance ToJSON (Alias kind) where
  toJSON :: Alias kind -> Value
toJSON = Text -> Value
forall a. ToJSON a => a -> Value
toJSON (Text -> Value) -> (Alias kind -> Text) -> Alias kind -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Alias kind -> Text
forall (kind :: AddressKind). Alias kind -> Text
unAlias

instance (SingI kind, L1AddressKind kind) => FromJSON (Alias kind) where
  parseJSON :: Value -> Parser (Alias kind)
parseJSON = (Text -> Alias kind) -> Parser Text -> Parser (Alias kind)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> Alias kind
forall (kind :: AddressKind).
(SingI kind, L1AddressKind kind) =>
Text -> Alias kind
mkAlias (Parser Text -> Parser (Alias kind))
-> (Value -> Parser Text) -> Value -> Parser (Alias kind)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Text
forall a. FromJSON a => Value -> Parser a
parseJSON

-- | Existential wrapper over 'Alias'.
type SomeAlias = Constrained NullConstraint Alias

pattern SomeAlias :: Alias a -> SomeAlias
pattern $bSomeAlias :: forall (a :: AddressKind). Alias a -> SomeAlias
$mSomeAlias :: forall {r}.
SomeAlias
-> (forall {a :: AddressKind}. Alias a -> r) -> (Void# -> r) -> r
SomeAlias x = Constrained x
{-# COMPLETE SomeAlias #-}

-- | Given an 'Alias', prove it's @kind@ is well-defined (i.e. it has a 'SingI'
-- instance and satisfies 'L1AddressKind' constraint)
aliasKindSanity :: Alias kind -> Dict (L1AddressKind kind, SingI kind)
aliasKindSanity :: forall (kind :: AddressKind).
Alias kind -> Dict (L1AddressKind kind, SingI kind)
aliasKindSanity Alias kind
x = forall {k} (c :: k -> Constraint) (f :: k -> *) (a :: k) r.
Has c f =>
f a -> (c a => r) -> r
forall (c :: AddressKind -> Constraint) (f :: AddressKind -> *)
       (a :: AddressKind) r.
Has c f =>
f a -> (c a => r) -> r
has @AliasKindSanityHelper Alias kind
x AliasKindSanityHelper kind => Dict (L1AddressKind kind, SingI kind)
forall (a :: Constraint). a => Dict a
Dict

class (L1AddressKind kind, SingI kind) => AliasKindSanityHelper kind
instance (L1AddressKind kind, SingI kind) => AliasKindSanityHelper kind

{- | This type is meant to be used to parse CLI options where either an address or an alias
of an implicit account or a contract can be accepted.

This can be later converted to an address using @Morley.Client.resolveAddress@
or an alias using @Morley.Client.getAlias@.

This polymorphic type can be instantiated with 'AddressKindImplicit' or 'AddressKindContract'
(see 'ImplicitAddressOrAlias' and 'ContractAddressOrAlias'),
but not 'AddressKindTxRollup'.
There is no @octez-client@ command to list tx rollup aliases,
unlike @octez-client list known addresses/contracts@, therefore:

1. It wouldn't be possible to implement @Morley.Client.resolveAddress@ for @AddressAlias _ :: AddressOrAlias 'AddressKindTxRollup@.
2. It wouldn't be possible to implement @Morley.Client.getAlias@ for @AddressResolved _ :: AddressOrAlias 'AddressKindTxRollup@.

This should be revisited if/when @octez-client@ adds support for tx rollup aliases.
-}
data AddressOrAlias kind where
  AddressResolved :: L1AddressKind kind => KindedAddress kind -> AddressOrAlias kind
  -- ^ Address itself, can be used as is.
  AddressAlias :: Alias kind -> AddressOrAlias kind
  -- ^ Address alias, should be resolved by @octez-client@.

deriving stock instance Show (AddressOrAlias kind)
deriving stock instance Eq (AddressOrAlias kind)
deriving stock instance Ord (AddressOrAlias kind)

instance (SingI kind, L1AddressKind kind) => HasCLReader (AddressOrAlias kind) where
  getReader :: ReadM (AddressOrAlias kind)
getReader =
    forall a. HasCLReader a => ReadM a
getReader @SomeAddressOrAlias ReadM SomeAddressOrAlias
-> (SomeAddressOrAlias -> ReadM (AddressOrAlias kind))
-> ReadM (AddressOrAlias kind)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
      SAOAKindSpecified AddressOrAlias kind
aoa ->
        case forall {k} (a :: k) (b :: k) (t :: k -> *).
(SingI a, SingI b, SDecide k) =>
t a -> Maybe (t b)
forall (a :: AddressKind) (b :: AddressKind)
       (t :: AddressKind -> *).
(SingI a, SingI b, SDecide AddressKind) =>
t a -> Maybe (t b)
castSing @_ @kind AddressOrAlias kind
aoa ((L1AddressKind kind, SingI kind) => Maybe (AddressOrAlias kind))
-> Dict (L1AddressKind kind, SingI kind)
-> Maybe (AddressOrAlias kind)
forall (c :: Constraint) e r. HasDict c e => (c => r) -> e -> r
\\ AddressOrAlias kind -> Dict (L1AddressKind kind, SingI kind)
forall (kind :: AddressKind).
AddressOrAlias kind -> Dict (L1AddressKind kind, SingI kind)
addressOrAliasKindSanity AddressOrAlias kind
aoa of
          Just AddressOrAlias kind
aoa' -> AddressOrAlias kind -> ReadM (AddressOrAlias kind)
forall (f :: * -> *) a. Applicative f => a -> f a
pure AddressOrAlias kind
aoa'
          Maybe (AddressOrAlias kind)
Nothing -> String -> ReadM (AddressOrAlias kind)
forall a. String -> ReadM a
Opt.readerError
            let expectedKind :: Demote AddressKind
expectedKind = forall {k} (a :: k). (SingKind k, SingI a) => Demote k
forall (a :: AddressKind).
(SingKind AddressKind, SingI a) =>
Demote AddressKind
demote @kind in
            [itu|Unexpected address kind: expected #{expectedKind} address or alias, but got: '#{aoa}'|]
      SAOAKindUnspecified Text
aliasText ->
        AddressOrAlias kind -> ReadM (AddressOrAlias kind)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AddressOrAlias kind -> ReadM (AddressOrAlias kind))
-> AddressOrAlias kind -> ReadM (AddressOrAlias kind)
forall a b. (a -> b) -> a -> b
$ Alias kind -> AddressOrAlias kind
forall (kind :: AddressKind). Alias kind -> AddressOrAlias kind
AddressAlias (Alias kind -> AddressOrAlias kind)
-> Alias kind -> AddressOrAlias kind
forall a b. (a -> b) -> a -> b
$ Text -> Alias kind
forall (kind :: AddressKind).
(SingI kind, L1AddressKind kind) =>
Text -> Alias kind
mkAlias Text
aliasText
  getMetavar :: String
getMetavar = ((Char -> Char) -> ShowS
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Char -> Char
Char.toUpper ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ AddressKind -> String
forall a b. (Buildable a, FromBuilder b) => a -> b
pretty (AddressKind -> String) -> AddressKind -> String
forall a b. (a -> b) -> a -> b
$ forall {k} (a :: k). (SingKind k, SingI a) => Demote k
forall (a :: AddressKind).
(SingKind AddressKind, SingI a) =>
Demote AddressKind
demote @kind) String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" ADDRESS OR ALIAS"

instance Buildable (AddressOrAlias kind) where
  build :: AddressOrAlias kind -> Builder
build = \case
    AddressResolved KindedAddress kind
addr -> KindedAddress kind -> Builder
forall p. Buildable p => p -> Builder
build KindedAddress kind
addr
    AddressAlias Alias kind
alias -> forall (addressKind :: AddressKind).
(L1AddressKind addressKind, SingI addressKind) =>
Builder
aliasPrefix @kind Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
":" Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Alias kind -> Builder
forall p. Buildable p => p -> Builder
build Alias kind
alias ((L1AddressKind kind, SingI kind) => Builder)
-> Dict (L1AddressKind kind, SingI kind) -> Builder
forall (c :: Constraint) e r. HasDict c e => (c => r) -> e -> r
\\ Alias kind -> Dict (L1AddressKind kind, SingI kind)
forall (kind :: AddressKind).
Alias kind -> Dict (L1AddressKind kind, SingI kind)
aliasKindSanity Alias kind
alias

{- This type is meant to be used to parse CLI options where either an address or an alias
of an implicit account can be accepted.

Example inputs:
  * "tz1hZ7o4bhFTo6AXpWZsXzbnddEK3dSCv1S8": an address belonging to an implicit account.
  * "implicit:some-alias" or "some-alias": an alias that is expected to be associated with an implicit account.
      * If it's associated with a contract, @Morley.Client.resolveAddress@ will fail.
      * If it's associated with __both__ a contract and an implicit account,
        @Morley.Client.resolveAddress@ will return the implicit account address.

Parsing will fail on these inputs:
  * "KT1STb2aG7NpoBBNRggvummqsxNQZmuAVFvG"
  * "contract:some-alias"

Refer to the 'HasCLReader' and @Morley.Client.Resolve@ instances for implementation details.
-}
type ImplicitAddressOrAlias = AddressOrAlias 'AddressKindImplicit

{- This type is meant to be used to parse CLI options where either an address or an alias
of a contract can be accepted.

Example inputs:
  * "KT1STb2aG7NpoBBNRggvummqsxNQZmuAVFvG": an address belonging to a contract.
  * "contract:some-alias" or "some-alias": an alias that is expected to be associated with a contract.
      * If it's associated with an implicit account, @Morley.Client.resolveAddress@ will fail.
      * If it's associated with __both__ a contract and an implicit account,
        @Morley.Client.resolveAddress@ will return the contract address.

Parsing will fail on these inputs:
  * "tz1hZ7o4bhFTo6AXpWZsXzbnddEK3dSCv1S8"
  * "implicit:some-alias"

Refer to the 'HasCLReader' and @Morley.Client.Resolve@ instances for implementation details.
-}
type ContractAddressOrAlias = AddressOrAlias 'AddressKindContract

-- | Given an 'AddressOrAlias', prove it's @kind@ is well-defined (i.e. it has a 'SingI'
-- instance and satisfies 'L1AddressKind' constraint)
addressOrAliasKindSanity :: forall kind. AddressOrAlias kind -> Dict (L1AddressKind kind, SingI kind)
addressOrAliasKindSanity :: forall (kind :: AddressKind).
AddressOrAlias kind -> Dict (L1AddressKind kind, SingI kind)
addressOrAliasKindSanity = \case
  AddressResolved KindedAddress kind
addr -> SingI kind => Dict (L1AddressKind kind, SingI kind)
forall (a :: Constraint). a => Dict a
Dict (SingI kind => Dict (L1AddressKind kind, SingI kind))
-> Dict (SingI kind) -> Dict (L1AddressKind kind, SingI kind)
forall (c :: Constraint) e r. HasDict c e => (c => r) -> e -> r
\\ KindedAddress kind -> Dict (SingI kind)
forall (kind :: AddressKind).
KindedAddress kind -> Dict (SingI kind)
addressKindSanity KindedAddress kind
addr
  AddressAlias Alias kind
alias -> Alias kind -> Dict (L1AddressKind kind, SingI kind)
forall (kind :: AddressKind).
Alias kind -> Dict (L1AddressKind kind, SingI kind)
aliasKindSanity Alias kind
alias

{- This type is meant to be used to parse CLI options where either an address or an alias
of either a contract or an implicit account can be accepted.

Example inputs:
  * "KT1STb2aG7NpoBBNRggvummqsxNQZmuAVFvG": an address belonging to a contract
  * "tz1hZ7o4bhFTo6AXpWZsXzbnddEK3dSCv1S8": an address belonging to an implicit account
  * "contract:some-alias": an alias that is expected to be associated with a contract.
      * If it's associated with an implicit account, @Morley.Client.resolveAddress@ will fail.
  * "implicit:some-alias": an alias that is expected to be associated with an implicit account.
      * If it's associated with a contract, @Morley.Client.resolveAddress@ will fail.
  * "some-alias": an alias that is expected to be associated with either a contract or an implicit account.
      * If it's associated with __both__ a contract and an implicit account,
        @Morley.Client.resolveAddress@ will fail.

Refer to the 'HasCLReader' and @Morley.Client.Resolve@ instances for implementation details.
-}
data SomeAddressOrAlias where
  SAOAKindSpecified :: AddressOrAlias kind -> SomeAddressOrAlias
  SAOAKindUnspecified :: Text -> SomeAddressOrAlias

deriving stock instance Show SomeAddressOrAlias

-- | The output of 'build' should be parseable by the 'HasCLReader' instance.
instance Buildable SomeAddressOrAlias where
  build :: SomeAddressOrAlias -> Builder
build = \case
    SAOAKindUnspecified Text
alias -> Text -> Builder
forall p. Buildable p => p -> Builder
build Text
alias
    SAOAKindSpecified AddressOrAlias kind
aoa -> AddressOrAlias kind -> Builder
forall p. Buildable p => p -> Builder
build AddressOrAlias kind
aoa

instance HasCLReader SomeAddressOrAlias where
  getMetavar :: String
getMetavar = String
"CONTRACT OR IMPLICIT ADDRESS OR ALIAS"
  getReader :: ReadM SomeAddressOrAlias
getReader =
    ReadM Text
forall s. IsString s => ReadM s
Opt.str ReadM Text
-> (Text -> ReadM SomeAddressOrAlias) -> ReadM SomeAddressOrAlias
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Text
str ->
      case Text -> Either ParseAddressError Address
parseAddress Text
str of
        Right (Constrained KindedAddress a
addr) ->
          case KindedAddress a
addr of
            ImplicitAddress{} -> SomeAddressOrAlias -> ReadM SomeAddressOrAlias
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SomeAddressOrAlias -> ReadM SomeAddressOrAlias)
-> SomeAddressOrAlias -> ReadM SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$ AddressOrAlias a -> SomeAddressOrAlias
forall (kind :: AddressKind).
AddressOrAlias kind -> SomeAddressOrAlias
SAOAKindSpecified (AddressOrAlias a -> SomeAddressOrAlias)
-> AddressOrAlias a -> SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$ KindedAddress a -> AddressOrAlias a
forall (kind :: AddressKind).
L1AddressKind kind =>
KindedAddress kind -> AddressOrAlias kind
AddressResolved KindedAddress a
addr
            ContractAddress{} -> SomeAddressOrAlias -> ReadM SomeAddressOrAlias
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SomeAddressOrAlias -> ReadM SomeAddressOrAlias)
-> SomeAddressOrAlias -> ReadM SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$ AddressOrAlias a -> SomeAddressOrAlias
forall (kind :: AddressKind).
AddressOrAlias kind -> SomeAddressOrAlias
SAOAKindSpecified (AddressOrAlias a -> SomeAddressOrAlias)
-> AddressOrAlias a -> SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$ KindedAddress a -> AddressOrAlias a
forall (kind :: AddressKind).
L1AddressKind kind =>
KindedAddress kind -> AddressOrAlias kind
AddressResolved KindedAddress a
addr
            TxRollupAddress{} -> String -> ReadM SomeAddressOrAlias
forall a. String -> ReadM a
Opt.readerError (String -> ReadM SomeAddressOrAlias)
-> String -> ReadM SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$ String
"Unexpected transaction rollup address: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> KindedAddress a -> String
forall a b. (Buildable a, FromBuilder b) => a -> b
pretty KindedAddress a
addr
        Left ParseAddressError
_ ->
          SomeAddressOrAlias -> ReadM SomeAddressOrAlias
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SomeAddressOrAlias -> ReadM SomeAddressOrAlias)
-> SomeAddressOrAlias -> ReadM SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$
            forall (kind :: AddressKind).
(L1AddressKind kind, SingI kind) =>
Text -> Maybe SomeAddressOrAlias
parseAliasWithPrefix @'AddressKindImplicit Text
str Maybe SomeAddressOrAlias
-> Maybe SomeAddressOrAlias -> Maybe SomeAddressOrAlias
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> forall (kind :: AddressKind).
(L1AddressKind kind, SingI kind) =>
Text -> Maybe SomeAddressOrAlias
parseAliasWithPrefix @'AddressKindContract Text
str
              Maybe SomeAddressOrAlias
-> (Maybe SomeAddressOrAlias -> SomeAddressOrAlias)
-> SomeAddressOrAlias
forall a b. a -> (a -> b) -> b
& SomeAddressOrAlias
-> Maybe SomeAddressOrAlias -> SomeAddressOrAlias
forall a. a -> Maybe a -> a
fromMaybe (Text -> SomeAddressOrAlias
SAOAKindUnspecified Text
str)
      where
        -- Try parsing an alias with an explicit kind prefix.
        parseAliasWithPrefix
          :: forall kind. (L1AddressKind kind, SingI kind)
          => Text
          -> Maybe SomeAddressOrAlias
        parseAliasWithPrefix :: forall (kind :: AddressKind).
(L1AddressKind kind, SingI kind) =>
Text -> Maybe SomeAddressOrAlias
parseAliasWithPrefix Text
str =
          Text -> Text -> Maybe Text
T.stripPrefix (Builder -> Text
forall a b. (Buildable a, FromBuilder b) => a -> b
pretty (Builder -> Text) -> Builder -> Text
forall a b. (a -> b) -> a -> b
$ forall (addressKind :: AddressKind).
(L1AddressKind addressKind, SingI addressKind) =>
Builder
aliasPrefix @kind Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
":") Text
str Maybe Text
-> (Text -> SomeAddressOrAlias) -> Maybe SomeAddressOrAlias
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Text
alias ->
            AddressOrAlias kind -> SomeAddressOrAlias
forall (kind :: AddressKind).
AddressOrAlias kind -> SomeAddressOrAlias
SAOAKindSpecified (AddressOrAlias kind -> SomeAddressOrAlias)
-> AddressOrAlias kind -> SomeAddressOrAlias
forall a b. (a -> b) -> a -> b
$ Alias kind -> AddressOrAlias kind
forall (kind :: AddressKind). Alias kind -> AddressOrAlias kind
AddressAlias (Alias kind -> AddressOrAlias kind)
-> Alias kind -> AddressOrAlias kind
forall a b. (a -> b) -> a -> b
$ forall (kind :: AddressKind).
(SingI kind, L1AddressKind kind) =>
Text -> Alias kind
mkAlias @kind Text
alias

-- | The prefix used to specify whether an alias belongs to a contract or an implicit account.
aliasPrefix :: forall addressKind. (L1AddressKind addressKind, SingI addressKind) => Builder
aliasPrefix :: forall (addressKind :: AddressKind).
(L1AddressKind addressKind, SingI addressKind) =>
Builder
aliasPrefix =
  forall (kind :: AddressKind) a. L1AddressKind kind => a -> a
usingImplicitOrContractKind @addressKind (Builder -> Builder) -> Builder -> Builder
forall a b. (a -> b) -> a -> b
$
    AddressKind -> Builder
forall p. Buildable p => p -> Builder
build (AddressKind -> Builder) -> AddressKind -> Builder
forall a b. (a -> b) -> a -> b
$ forall {k} (a :: k). (SingKind k, SingI a) => Demote k
forall (a :: AddressKind).
(SingKind AddressKind, SingI a) =>
Demote AddressKind
demote @addressKind

contractPrefix, implicitPrefix :: Builder
contractPrefix :: Builder
contractPrefix = forall (addressKind :: AddressKind).
(L1AddressKind addressKind, SingI addressKind) =>
Builder
aliasPrefix @'AddressKindContract
implicitPrefix :: Builder
implicitPrefix = forall (addressKind :: AddressKind).
(L1AddressKind addressKind, SingI addressKind) =>
Builder
aliasPrefix @'AddressKindImplicit