content: {-# LANGUAGE CPP                        #-}
  {-# LANGUAGE ConstraintKinds            #-}
  {-# LANGUAGE DeriveGeneric              #-}
  {-# LANGUAGE DerivingStrategies         #-}
  {-# LANGUAGE ExplicitNamespaces         #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  {-# LANGUAGE KindSignatures             #-}
  {-# LANGUAGE LambdaCase                 #-}
  {-# LANGUAGE OverloadedStrings          #-}
  {-# LANGUAGE PolyKinds                  #-}
  {-# LANGUAGE StandaloneDeriving         #-}
  {-# LANGUAGE StrictData                 #-}
  {-# LANGUAGE TemplateHaskell            #-}
  -- The hlint parser fails on the `pattern` function, so we disable the
  -- language extension here.
  {-# LANGUAGE NoPatternSynonyms          #-}
  
  -- Needed for the `Buildable`, `SubscriptionStatus` and `NodeId` orphans.
  {-# OPTIONS_GHC -fno-warn-orphans #-}
  
  module Cardano.Wallet.API.V1.Types (
      V1 (..)
    , unV1
    -- * Swagger & REST-related types
    , PasswordUpdate (..)
    , AccountUpdate (..)
    , NewAccount (..)
    , Update
    , New
    , ForceNtpCheck (..)
    -- * Domain-specific types
    -- * Wallets
    , Wallet (..)
    , AssuranceLevel (..)
    , NewWallet (..)
    , WalletUpdate (..)
    , WalletId (..)
    , exampleWalletId
    , WalletOperation (..)
    , SpendingPassword
    , mkSpendingPassword
    -- * Addresses
    , AddressOwnership (..)
    , AddressValidity (..)
    -- * Accounts
    , Account (..)
    , accountsHaveSameId
    , AccountIndex
    , AccountAddresses (..)
    , AccountBalance (..)
    , getAccIndex
    , mkAccountIndex
    , mkAccountIndexM
    , unsafeMkAccountIndex
    , AccountIndexError(..)
    -- * Addresses
    , WalletAddress (..)
    , NewAddress (..)
    , BatchImportResult(..)
    -- * Payments
    , Payment (..)
    , PaymentSource (..)
    , PaymentDistribution (..)
    , Issurance (..)
    , IssuranceInfo (..)
  --   , Proof (..)
    , Transaction (..)
    , TransactionType (..)
    , TransactionDirection (..)
    , TransactionStatus(..)
    , EstimatedFees (..)
    -- * Updates
    , WalletSoftwareUpdate (..)
    -- * Importing a wallet from a backup
    , WalletImport (..)
    -- * Settings
    , NodeSettings (..)
    , SlotDuration
    , mkSlotDuration
    , BlockchainHeight
    , mkBlockchainHeight
    , LocalTimeDifference
    , mkLocalTimeDifference
    , EstimatedCompletionTime
    , mkEstimatedCompletionTime
    , SyncThroughput
    , mkSyncThroughput
    , SyncState (..)
    , SyncProgress (..)
    , SyncPercentage
    , mkSyncPercentage
    , NodeInfo (..)
    , TimeInfo(..)
    , SubscriptionStatus(..)
    , Redemption(..)
    , RedemptionMnemonic(..)
    , BackupPhrase(..)
    , ShieldedRedemptionCode(..)
    , WAddressMeta (..)
    -- * Some types for the API
    , CaptureWalletId
    , CaptureAccountId
    -- * Core re-exports
    , Core.Address
    , Core.InputSelectionPolicy(..)
    , Core.Coin
    , Core.Timestamp(..)
    , Core.mkCoin
    -- * Wallet Errors
    , WalletError(..)
    , ErrNotEnoughMoney(..)
    , ErrUtxoNotEnoughFragmented(..)
    , msgUtxoNotEnoughFragmented
    , toServantError
    , toHttpErrorStatus
    , module Cardano.Wallet.Types.UtxoStatistics
    ) where
  
  import qualified Prelude
  import           Universum
  
  import qualified Cardano.Crypto.Wallet as CC
  import           Control.Lens (at, to, (?~))
  import           Data.Aeson
  import qualified Data.Aeson.Options as Aeson
  import           Data.Aeson.TH as A
  import           Data.Aeson.Types (Parser, Value (..), typeMismatch)
  import           Data.Bifunctor (first)
  import qualified Data.ByteArray as ByteArray
  import qualified Data.ByteString as BS
  import           Data.ByteString.Base58 (bitcoinAlphabet, decodeBase58)
  import qualified Data.Char as C
  import           Data.Default (Default (def))
  import           Data.List ((!!))
  import           Data.Maybe (fromJust)
  import           Data.Semigroup (Semigroup)
  import           Data.Swagger hiding (Example, example)
  import           Data.Text (Text, dropEnd, toLower)
  import           Formatting (bprint, build, fconst, int, sformat, stext, (%))
  import qualified Formatting.Buildable
  import           Generics.SOP.TH (deriveGeneric)
  import           Serokell.Util (listJson)
  import qualified Serokell.Util.Base16 as Base16
  import           Servant
  import           Test.QuickCheck
  import           Test.QuickCheck.Gen (Gen (..))
  import qualified Test.QuickCheck.Gen as Gen
  import qualified Test.QuickCheck.Modifiers as Gen
  
  import           Pos.Node.API
  
  import           Cardano.Wallet.API.Response.JSend (HasDiagnostic (..),
                       noDiagnosticKey)
  import           Cardano.Wallet.API.Types.UnitOfMeasure (MeasuredIn (..),
                       UnitOfMeasure (..))
  import           Cardano.Wallet.API.V1.Errors (ToHttpErrorStatus (..),
                       ToServantError (..))
  import           Cardano.Wallet.API.V1.Generic (jsendErrorGenericParseJSON,
                       jsendErrorGenericToJSON)
  import           Cardano.Wallet.API.V1.Swagger.Example (Example, example)
  import           Cardano.Wallet.Types.UtxoStatistics
  import           Cardano.Wallet.Util (mkJsonKey, showApiUtcTime)
  
  import           Cardano.Mnemonic (Mnemonic)
  import qualified Pos.Chain.Txp as Txp
  import qualified Pos.Client.Txp.Util as Core
  import qualified Pos.Core as Core
  import           Pos.Crypto (PublicKey (..), decodeHash, hashHexF)
  import qualified Pos.Crypto.Signing as Core
  import           Pos.Infra.Communication.Types.Protocol ()
  import           Pos.Infra.Diffusion.Subscription.Status
                       (SubscriptionStatus (..))
  import           Pos.Infra.Util.LogSafe (BuildableSafeGen (..), buildSafe,
                       buildSafeList, buildSafeMaybe, deriveSafeBuildable,
                       plainOrSecureF)
  -- import           Pos.Util.Util (leftToPanic)
  
  import           Test.Pos.Core.Arbitrary ()
  
  -- | Declare generic schema, while documenting properties
  --   For instance:
  --
  --    data MyData = MyData
  --      { myDataField1 :: String
  --      , myDataField2 :: String
  --      } deriving (Generic)
  --
  --   instance ToSchema MyData where
  --     declareNamedSchema =
  --       genericSchemaDroppingPrefix "myData" (\(--^) props -> props
  --         & ("field1" --^ "Description 1")
  --         & ("field2" --^ "Description 2")
  --       )
  --
  --   -- or, if no descriptions are added to the underlying properties
  --
  --   instance ToSchema MyData where
  --     declareNamedSchema =
  --       genericSchemaDroppingPrefix "myData" (\_ -> id)
  --
  
  optsADTCamelCase :: A.Options
  optsADTCamelCase = defaultOptions
      { A.constructorTagModifier = mkJsonKey
      , A.sumEncoding            = A.ObjectWithSingleField
      }
  
  
  --
  -- Versioning
  --
  
  mkSpendingPassword :: Text -> Either Text SpendingPassword
  mkSpendingPassword = fmap V1 . mkPassPhrase
  
  mkPassPhrase :: Text -> Either Text Core.PassPhrase
  mkPassPhrase text =
      case Base16.decode text of
          Left e -> Left e
          Right bs -> do
              let bl = BS.length bs
              -- Currently passphrase may be either 32-byte long or empty (for
              -- unencrypted keys).
              if bl == 0 || bl == Core.passphraseLength
                  then Right $ ByteArray.convert bs
                  else Left $ sformat
                       ("Expected spending password to be of either length 0 or "%int%", not "%int)
                       Core.passphraseLength bl
  
  instance ToJSON (V1 Core.PassPhrase) where
      toJSON = String . Base16.encode . ByteArray.convert
  
  instance FromJSON (V1 Core.PassPhrase) where
      parseJSON (String pp) = case mkPassPhrase pp of
          Left e    -> fail (toString e)
          Right pp' -> pure (V1 pp')
      parseJSON x           = typeMismatch "parseJSON failed for PassPhrase" x
  
  instance Arbitrary (V1 Core.PassPhrase) where
      arbitrary = fmap V1 arbitrary
  
  instance ToSchema (V1 Core.PassPhrase) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "V1PassPhrase") $ mempty
              & type_ .~ SwaggerString
              & format ?~ "hex|base16"
  
  instance ToJSON (V1 Core.Coin) where
      toJSON (V1 c) = toJSON . Core.unsafeGetCoin $ c
  
  instance FromJSON (V1 Core.Coin) where
      parseJSON v = do
          i <- Core.Coin <$> parseJSON v
          either (fail . toString) (const (pure (V1 i)))
              $ Core.checkCoin i
  
  instance Arbitrary (V1 Core.Coin) where
      arbitrary = fmap V1 arbitrary
  
  instance ToSchema (V1 Core.Coin) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "V1Coin") $ mempty
              & type_ .~ SwaggerNumber
              & maximum_ .~ Just (fromIntegral Core.maxCoinVal)
  
  instance ToJSON (V1 Core.GoldDollar) where
      toJSON (V1 c) = toJSON . Core.unsafeGetGoldDollar $ c
  
  instance FromJSON (V1 Core.GoldDollar) where
      parseJSON v = do
          i <- Core.GoldDollar <$> parseJSON v
          either (fail . toString) (const (pure (V1 i)))
              $ Core.checkGoldDollar i
  
  instance Arbitrary (V1 Core.GoldDollar) where
      arbitrary = fmap V1 arbitrary
  
  instance ToSchema (V1 Core.GoldDollar) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "V1GoldDollar") $ mempty
              & type_ .~ SwaggerNumber
              & maximum_ .~ Just (fromIntegral Core.maxGoldDollarVal)
  
  instance ToJSON (V1 Core.CoinPair) where
      toJSON (V1 (coins, gds)) = 
          object [ "coins" .= Core.coinToInteger coins
                 , "gds"   .= Core.goldDollarToInteger gds ]
  
  instance FromJSON (V1 Core.CoinPair) where
      parseJSON = withObject "CoinPair" $ \o -> do
              coins <- o .: "coins"
              gds <- o .: "gds"
              let coinPair = (Core.Coin coins, Core.GoldDollar gds)
              either (fail . toString) (const (pure (V1 coinPair)))
                  $ Core.checkCoinPair coinPair
  
  instance Arbitrary (V1 Core.CoinPair) where
      arbitrary = fmap V1 arbitrary
  
  instance ToSchema (V1 Core.CoinPair) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "V1CoinPair") $ mempty
              & type_ .~ SwaggerObject
              & required .~ ["coins", "gds"]
              & properties .~ (mempty
                  & at "coins" ?~ (Inline $ mempty
                      & type_ .~ SwaggerNumber
                      & minimum_ .~ Just 0
                      & maximum_ .~ Just (fromIntegral Core.maxCoinVal)
                      )
                  & at "gds" ?~ (Inline $ mempty
                      & type_ .~ SwaggerNumber
                      & minimum_ .~ Just 0
                      & maximum_ .~ Just (fromIntegral Core.maxGoldDollarVal)
                      )
                  )
  
  instance ToJSON (V1 Core.Address) where
      toJSON (V1 c) = String $ sformat Core.addressF c
  
  instance FromJSON (V1 Core.Address) where
      parseJSON (String a) = case Core.decodeTextAddress a of
          Left e     -> fail $ "Not a valid Cardano Address: " <> toString e
          Right addr -> pure (V1 addr)
      parseJSON x = typeMismatch "parseJSON failed for Address" x
  
  instance Arbitrary (V1 Core.Address) where
      arbitrary = fmap V1 arbitrary
  
  instance ToSchema (V1 Core.Address) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "Address") $ mempty
              & type_ .~ SwaggerString
              & format ?~ "base58"
  
  instance FromHttpApiData (V1 Core.Address) where
      parseQueryParam = fmap (fmap V1) Core.decodeTextAddress
  
  instance ToHttpApiData (V1 Core.Address) where
      toQueryParam (V1 a) = sformat build a
  
  deriving instance Hashable (V1 Core.Address)
  deriving instance NFData (V1 Core.Address)
  
  -- | Represents according to 'apiTimeFormat' format.
  instance ToJSON (V1 Core.Timestamp) where
      toJSON timestamp =
          let utcTime = timestamp ^. _V1 . Core.timestampToUTCTimeL
          in  String $ showApiUtcTime utcTime
  
  instance ToHttpApiData (V1 Core.Timestamp) where
      toQueryParam = view (_V1 . Core.timestampToUTCTimeL . to showApiUtcTime)
  
  instance FromHttpApiData (V1 Core.Timestamp) where
      parseQueryParam t =
          maybe
              (Left ("Couldn't parse timestamp or datetime out of: " <> t))
              (Right . V1)
              (Core.parseTimestamp t)
  
  -- | Parses from both UTC time in 'apiTimeFormat' format and a fractional
  -- timestamp format.
  instance FromJSON (V1 Core.Timestamp) where
      parseJSON = withText "Timestamp" $ \t ->
          maybe
              (fail ("Couldn't parse timestamp or datetime out of: " <> toString t))
              (pure . V1)
              (Core.parseTimestamp t)
  
  instance Arbitrary (V1 Core.Timestamp) where
      arbitrary = fmap V1 arbitrary
  
  instance ToSchema (V1 Core.Timestamp) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "Timestamp") $ mempty
              & type_ .~ SwaggerString
              & description ?~ "Time in ISO 8601 format"
  
  --
  -- Domain-specific types, mostly placeholders.
  --
  
  -- | A 'SpendingPassword' represent a secret piece of information which can be
  -- optionally supplied by the user to encrypt the private keys. As private keys
  -- are needed to spend funds and this password secures spending, here the name
  -- 'SpendingPassword'.
  -- Practically speaking, it's just a type synonym for a PassPhrase, which is a
  -- base16-encoded string.
  type SpendingPassword = V1 Core.PassPhrase
  
  instance Semigroup (V1 Core.PassPhrase) where
      V1 a <> V1 b = V1 (a <> b)
  
  instance Monoid (V1 Core.PassPhrase) where
      mempty = V1 mempty
      mappend = (<>)
  
  instance BuildableSafeGen SpendingPassword where
      buildSafeGen sl pwd =
          bprint (plainOrSecureF sl build (fconst "<spending password>")) pwd
  
  type WalletName = Text
  
  -- | Wallet's Assurance Level
  data AssuranceLevel =
      NormalAssurance
    | StrictAssurance
    deriving (Eq, Ord, Show, Enum, Bounded)
  
  instance Arbitrary AssuranceLevel where
      arbitrary = elements [minBound .. maxBound]
  
  deriveJSON
      Aeson.defaultOptions
          { A.constructorTagModifier = toString . toLower . dropEnd 9 . fromString
          }
      ''AssuranceLevel
  
  instance ToSchema AssuranceLevel where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "AssuranceLevel") $ mempty
              & type_ .~ SwaggerString
              & enum_ ?~ ["normal", "strict"]
  
  deriveSafeBuildable ''AssuranceLevel
  instance BuildableSafeGen AssuranceLevel where
      buildSafeGen _ NormalAssurance = "normal"
      buildSafeGen _ StrictAssurance = "strict"
  
  -- | A Wallet ID.
  newtype WalletId = WalletId Text deriving (Show, Eq, Ord, Generic)
  
  exampleWalletId :: WalletId
  exampleWalletId = WalletId "J7rQqaLLHBFPrgJXwpktaMB1B1kQBXAyc2uRSfRPzNVGiv6TdxBzkPNBUWysZZZdhFG9gRy3sQFfX5wfpLbi4XTFGFxTg"
  
  deriveJSON Aeson.defaultOptions ''WalletId
  
  instance ToSchema WalletId where
    declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
  
  instance ToJSONKey WalletId
  
  instance Arbitrary WalletId where
      arbitrary = elements [exampleWalletId]
  
  deriveSafeBuildable ''WalletId
  instance BuildableSafeGen WalletId where
      buildSafeGen sl (WalletId wid) =
          bprint (plainOrSecureF sl build (fconst "<wallet id>")) wid
  
  instance FromHttpApiData WalletId where
      parseQueryParam = Right . WalletId
  
  instance ToHttpApiData WalletId where
      toQueryParam (WalletId wid) = wid
  
  instance Hashable WalletId
  instance NFData WalletId
  
  -- | A Wallet Operation
  data WalletOperation =
      CreateWallet
    | RestoreWallet
    deriving (Eq, Show, Enum, Bounded)
  
  instance Arbitrary WalletOperation where
      arbitrary = elements [minBound .. maxBound]
  
  -- Drops the @Wallet@ suffix.
  deriveJSON Aeson.defaultOptions  { A.constructorTagModifier = reverse . drop 6 . reverse . map C.toLower
                                      } ''WalletOperation
  
  instance ToSchema WalletOperation where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "WalletOperation") $ mempty
              & type_ .~ SwaggerString
              & enum_ ?~ ["create", "restore"]
  
  deriveSafeBuildable ''WalletOperation
  instance BuildableSafeGen WalletOperation where
      buildSafeGen _ CreateWallet  = "create"
      buildSafeGen _ RestoreWallet = "restore"
  
  
  newtype BackupPhrase = BackupPhrase
      { unBackupPhrase :: Mnemonic 12
      }
      deriving stock (Eq, Show)
      deriving newtype (ToJSON, FromJSON, Arbitrary)
  
  deriveSafeBuildable ''BackupPhrase
  instance BuildableSafeGen BackupPhrase where
      buildSafeGen _ _  = "<backup phrase>"
  
  instance ToSchema BackupPhrase where
      declareNamedSchema _ =
          pure
              . NamedSchema (Just "V1BackupPhrase")
              $ toSchema (Proxy @(Mnemonic 12))
  
  -- | A type modelling the request for a new 'Wallet'.
  data NewWallet = NewWallet {
        newwalBackupPhrase     :: !BackupPhrase
      , newwalSpendingPassword :: !(Maybe SpendingPassword)
      , newwalAssuranceLevel   :: !AssuranceLevel
      , newwalName             :: !WalletName
      , newwalOperation        :: !WalletOperation
      } deriving (Eq, Show, Generic)
  
  deriveJSON Aeson.defaultOptions  ''NewWallet
  
  instance Arbitrary NewWallet where
    arbitrary = NewWallet <$> arbitrary
                          <*> pure Nothing
                          <*> arbitrary
                          <*> pure "My Wallet"
                          <*> arbitrary
  
  instance ToSchema NewWallet where
    declareNamedSchema =
      genericSchemaDroppingPrefix "newwal" (\(--^) props -> props
        & ("backupPhrase"     --^ "Backup phrase to restore the wallet.")
        & ("spendingPassword" --^ "Optional (but recommended) password to protect the wallet on sensitive operations.")
        & ("assuranceLevel"   --^ "Desired assurance level based on the number of confirmations counter of each transaction.")
        & ("name"             --^ "Wallet's name.")
        & ("operation"        --^ "Create a new wallet or Restore an existing one.")
      )
  
  
  deriveSafeBuildable ''NewWallet
  instance BuildableSafeGen NewWallet where
      buildSafeGen sl NewWallet{..} = bprint ("{"
          %" backupPhrase="%buildSafe sl
          %" spendingPassword="%(buildSafeMaybe mempty sl)
          %" assuranceLevel="%buildSafe sl
          %" name="%buildSafe sl
          %" operation"%buildSafe sl
          %" }")
          newwalBackupPhrase
          newwalSpendingPassword
          newwalAssuranceLevel
          newwalName
          newwalOperation
  
  -- | A type modelling the update of an existing wallet.
  data WalletUpdate = WalletUpdate {
        uwalAssuranceLevel :: !AssuranceLevel
      , uwalName           :: !Text
      } deriving (Eq, Show, Generic)
  
  deriveJSON Aeson.defaultOptions  ''WalletUpdate
  
  instance ToSchema WalletUpdate where
    declareNamedSchema =
      genericSchemaDroppingPrefix "uwal" (\(--^) props -> props
        & ("assuranceLevel" --^ "New assurance level.")
        & ("name"           --^ "New wallet's name.")
      )
  
  instance Arbitrary WalletUpdate where
    arbitrary = WalletUpdate <$> arbitrary
                             <*> pure "My Wallet"
  
  deriveSafeBuildable ''WalletUpdate
  instance BuildableSafeGen WalletUpdate where
      buildSafeGen sl WalletUpdate{..} = bprint ("{"
          %" assuranceLevel="%buildSafe sl
          %" name="%buildSafe sl
          %" }")
          uwalAssuranceLevel
          uwalName
  
  newtype EstimatedCompletionTime = EstimatedCompletionTime (MeasuredIn 'Milliseconds Word)
    deriving (Show, Eq)
  
  mkEstimatedCompletionTime :: Word -> EstimatedCompletionTime
  mkEstimatedCompletionTime = EstimatedCompletionTime . MeasuredIn
  
  instance Ord EstimatedCompletionTime where
      compare (EstimatedCompletionTime (MeasuredIn w1))
              (EstimatedCompletionTime (MeasuredIn w2)) = compare w1 w2
  
  instance Arbitrary EstimatedCompletionTime where
      arbitrary = EstimatedCompletionTime . MeasuredIn <$> arbitrary
  
  deriveSafeBuildable ''EstimatedCompletionTime
  instance BuildableSafeGen EstimatedCompletionTime where
      buildSafeGen _ (EstimatedCompletionTime (MeasuredIn w)) = bprint ("{"
          %" quantity="%build
          %" unit=milliseconds"
          %" }")
          w
  
  instance ToJSON EstimatedCompletionTime where
      toJSON (EstimatedCompletionTime (MeasuredIn w)) =
          object [ "quantity" .= toJSON w
                 , "unit"     .= String "milliseconds"
                 ]
  
  instance FromJSON EstimatedCompletionTime where
      parseJSON = withObject "EstimatedCompletionTime" $ \sl -> mkEstimatedCompletionTime <$> sl .: "quantity"
  
  instance ToSchema EstimatedCompletionTime where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "EstimatedCompletionTime") $ mempty
              & type_ .~ SwaggerObject
              & required .~ ["quantity", "unit"]
              & properties .~ (mempty
                  & at "quantity" ?~ (Inline $ mempty
                      & type_ .~ SwaggerNumber
                      & minimum_ .~ Just 0
                      )
                  & at "unit" ?~ (Inline $ mempty
                      & type_ .~ SwaggerString
                      & enum_ ?~ ["milliseconds"]
                      )
                  )
  
  newtype SyncThroughput
      = SyncThroughput (MeasuredIn 'BlocksPerSecond Core.BlockCount)
    deriving (Show, Eq)
  
  mkSyncThroughput :: Core.BlockCount -> SyncThroughput
  mkSyncThroughput = SyncThroughput . MeasuredIn
  
  instance Ord SyncThroughput where
      compare (SyncThroughput (MeasuredIn (Core.BlockCount b1)))
              (SyncThroughput (MeasuredIn (Core.BlockCount b2))) =
          compare b1 b2
  
  instance Arbitrary SyncThroughput where
      arbitrary = SyncThroughput . MeasuredIn <$> arbitrary
  
  deriveSafeBuildable ''SyncThroughput
  instance BuildableSafeGen SyncThroughput where
      buildSafeGen _ (SyncThroughput (MeasuredIn (Core.BlockCount blocks))) = bprint ("{"
          %" quantity="%build
          %" unit=blocksPerSecond"
          %" }")
          blocks
  
  instance ToJSON SyncThroughput where
      toJSON (SyncThroughput (MeasuredIn (Core.BlockCount blocks))) =
        object [ "quantity" .= toJSON blocks
               , "unit"     .= String "blocksPerSecond"
               ]
  
  instance FromJSON SyncThroughput where
      parseJSON = withObject "SyncThroughput" $ \sl -> mkSyncThroughput . Core.BlockCount <$> sl .: "quantity"
  
  instance ToSchema SyncThroughput where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "SyncThroughput") $ mempty
              & type_ .~ SwaggerObject
              & required .~ ["quantity", "unit"]
              & properties .~ (mempty
                  & at "quantity" ?~ (Inline $ mempty
                      & type_ .~ SwaggerNumber
                      )
                  & at "unit" ?~ (Inline $ mempty
                      & type_ .~ SwaggerString
                      & enum_ ?~ ["blocksPerSecond"]
                      )
                  )
  
  data SyncProgress = SyncProgress {
      spEstimatedCompletionTime :: !EstimatedCompletionTime
    , spThroughput              :: !SyncThroughput
    , spPercentage              :: !SyncPercentage
    } deriving (Show, Eq, Ord, Generic)
  
  deriveJSON Aeson.defaultOptions ''SyncProgress
  
  instance ToSchema SyncProgress where
      declareNamedSchema =
          genericSchemaDroppingPrefix "sp" (\(--^) props -> props
              & "estimatedCompletionTime"
              --^ "The estimated time the wallet is expected to be fully sync, based on the information available."
              & "throughput"
              --^ "The sync throughput, measured in blocks/s."
              & "percentage"
              --^ "The sync percentage, from 0% to 100%."
          )
  
  deriveSafeBuildable ''SyncProgress
  -- Nothing secret to redact for a SyncProgress.
  instance BuildableSafeGen SyncProgress where
      buildSafeGen sl SyncProgress {..} = bprint ("{"
          %" estimatedCompletionTime="%buildSafe sl
          %" throughput="%buildSafe sl
          %" percentage="%buildSafe sl
          %" }")
          spEstimatedCompletionTime
          spThroughput
          spPercentage
  
  instance Example SyncProgress where
      example = do
          exPercentage <- example
          pure $ SyncProgress
              { spEstimatedCompletionTime = mkEstimatedCompletionTime 3000
              , spThroughput              = mkSyncThroughput (Core.BlockCount 400)
              , spPercentage              = exPercentage
              }
  
  instance Arbitrary SyncProgress where
    arbitrary = SyncProgress <$> arbitrary
                             <*> arbitrary
                             <*> arbitrary
  
  data SyncState =
        Restoring SyncProgress
      -- ^ Restoring from seed or from backup.
      | Synced
      -- ^ Following the blockchain.
      deriving (Eq, Show, Ord)
  
  instance ToJSON SyncState where
      toJSON ss = object [ "tag"  .= toJSON (renderAsTag ss)
                         , "data" .= renderAsData ss
                         ]
        where
          renderAsTag :: SyncState -> Text
          renderAsTag (Restoring _) = "restoring"
          renderAsTag Synced        = "synced"
  
          renderAsData :: SyncState -> Value
          renderAsData (Restoring sp) = toJSON sp
          renderAsData Synced         = Null
  
  instance FromJSON SyncState where
      parseJSON = withObject "SyncState" $ \ss -> do
          t <- ss .: "tag"
          case (t :: Text) of
              "synced"    -> pure Synced
              "restoring" -> Restoring <$> ss .: "data"
              _           -> typeMismatch "unrecognised tag" (Object ss)
  
  instance ToSchema SyncState where
      declareNamedSchema _ = do
        syncProgress <- declareSchemaRef @SyncProgress Proxy
        pure $ NamedSchema (Just "SyncState") $ mempty
            & type_ .~ SwaggerObject
            & required .~ ["tag"]
            & properties .~ (mempty
                & at "tag" ?~ (Inline $ mempty
                    & type_ .~ SwaggerString
                    & enum_ ?~ ["restoring", "synced"]
                    )
                & at "data" ?~ syncProgress
                )
  
  instance Arbitrary SyncState where
    arbitrary = oneof [ Restoring <$> arbitrary
                      , pure Synced
                      ]
  
  -- | A 'Wallet'.
  data Wallet = Wallet {
        walId                         :: !WalletId
      , walName                       :: !WalletName
      , walBalance                    :: !(V1 Core.CoinPair)
      , walHasSpendingPassword        :: !Bool
      , walSpendingPasswordLastUpdate :: !(V1 Core.Timestamp)
      , walCreatedAt                  :: !(V1 Core.Timestamp)
      , walAssuranceLevel             :: !AssuranceLevel
      , walSyncState                  :: !SyncState
      } deriving (Eq, Ord, Show, Generic)
  deriveJSON Aeson.defaultOptions ''Wallet
  
  instance ToSchema Wallet where
      declareNamedSchema =
          genericSchemaDroppingPrefix "wal" (\(--^) props -> props
              & "id"
              --^ "Unique wallet identifier."
              & "name"
              --^ "Wallet's name."
              & "balance"
              --^ "Current balance, in Lovelace."
              & "hasSpendingPassword"
              --^ "Whether or not the wallet has a passphrase."
              & "spendingPasswordLastUpdate"
              --^ "The timestamp that the passphrase was last updated."
              & "createdAt"
              --^ "The timestamp that the wallet was created."
              & "assuranceLevel"
              --^ "The assurance level of the wallet."
              & "syncState"
              --^ "The sync state for this wallet."
          )
  
  instance Arbitrary Wallet where
    arbitrary = Wallet <$> arbitrary
                       <*> pure "My wallet"
                       <*> arbitrary
                       <*> arbitrary
                       <*> arbitrary
                       <*> arbitrary
                       <*> arbitrary
                       <*> arbitrary
  
  deriveSafeBuildable ''Wallet
  instance BuildableSafeGen Wallet where
    buildSafeGen sl Wallet{..} = bprint ("{"
      %" id="%buildSafe sl
      %" name="%buildSafe sl
      %" balance="%buildSafe sl
      %" }")
      walId
      walName
      walBalance
  
  instance Buildable [Wallet] where
      build = bprint listJson
  
  instance ToSchema PublicKey where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "PublicKey") $ mempty
              & type_ .~ SwaggerString
              & format ?~ "base58"
  
  --------------------------------------------------------------------------------
  -- Addresses
  --------------------------------------------------------------------------------
  
  -- | Whether an address is valid or not.
  newtype AddressValidity = AddressValidity { isValid :: Bool }
    deriving (Eq, Show, Generic)
  
  deriveJSON Aeson.defaultOptions ''AddressValidity
  
  instance ToSchema AddressValidity where
      declareNamedSchema = genericSchemaDroppingPrefix "is" (const identity)
  
  instance Arbitrary AddressValidity where
    arbitrary = AddressValidity <$> arbitrary
  
  deriveSafeBuildable ''AddressValidity
  instance BuildableSafeGen AddressValidity where
      buildSafeGen _ AddressValidity{..} =
          bprint ("{ valid="%build%" }") isValid
  
  -- | An address is either recognised as "ours" or not. An address that is not
  --   recognised may still be ours e.g. an address generated by another wallet instance
  --   will not be considered "ours" until the relevant transaction is confirmed.
  --
  --   In other words, `AddressAmbiguousOwnership` makes an inconclusive statement about
  --   an address, whereas `AddressOwnership` is unambiguous.
  data AddressOwnership
      = AddressIsOurs
      | AddressAmbiguousOwnership
      deriving (Show, Eq, Generic, Ord)
  
  instance ToJSON (V1 AddressOwnership) where
      toJSON = genericToJSON optsADTCamelCase . unV1
  
  instance FromJSON (V1 AddressOwnership) where
      parseJSON = fmap V1 . genericParseJSON optsADTCamelCase
  
  instance ToSchema (V1 AddressOwnership) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "V1AddressOwnership") $ mempty
              & type_ .~ SwaggerString
              & enum_ ?~ ["isOurs", "ambiguousOwnership"]
  
  instance Arbitrary (V1 AddressOwnership) where
      arbitrary = fmap V1 $ oneof
          [ pure AddressIsOurs
          , pure AddressAmbiguousOwnership
          ]
  
  -- | Address with associated metadata locating it in an account in a wallet.
  data WAddressMeta = WAddressMeta
      { _wamWalletId     :: !WalletId
      , _wamAccountIndex :: !Word32
      , _wamAddressIndex :: !Word32
      , _wamAddress      :: !(V1 Core.Address)
      } deriving (Eq, Ord, Show, Generic, Typeable)
  
  instance Hashable WAddressMeta
  instance NFData WAddressMeta
  
  instance Buildable WAddressMeta where
      build WAddressMeta{..} =
          bprint (build%"@"%build%"@"%build%" ("%build%")")
          _wamWalletId _wamAccountIndex _wamAddressIndex _wamAddress
  --------------------------------------------------------------------------------
  -- Accounts
  --------------------------------------------------------------------------------
  
  -- | Summary about single address.
  data WalletAddress = WalletAddress
      { addrId            :: !(V1 Core.Address)
      , addrUsed          :: !Bool
      , addrChangeAddress :: !Bool
      , addrOwnership     :: !(V1 AddressOwnership)
      } deriving (Show, Eq, Generic, Ord)
  
  deriveJSON Aeson.defaultOptions ''WalletAddress
  
  instance ToSchema WalletAddress where
      declareNamedSchema =
          genericSchemaDroppingPrefix "addr" (\(--^) props -> props
              & ("id"            --^ "Actual address.")
              & ("used"          --^ "True if this address has been used.")
              & ("changeAddress" --^ "True if this address stores change from a previous transaction.")
              & ("ownership"     --^ "'isOurs' if this address is recognised as ours, 'ambiguousOwnership' if the node doesn't have information to make a unambiguous statement.")
          )
  
  instance Arbitrary WalletAddress where
      arbitrary = WalletAddress <$> arbitrary
                                <*> arbitrary
                                <*> arbitrary
                                <*> arbitrary
  
  newtype AccountIndex = AccountIndex { getAccIndex :: Word32 }
      deriving (Show, Eq, Ord, Generic)
  
  newtype AccountIndexError = AccountIndexError Word32
      deriving (Eq, Show)
  
  instance Buildable AccountIndexError where
      build (AccountIndexError i) =
          bprint
              ("Account index should be in range ["%int%".."%int%"], but "%int%" was provided.")
              (getAccIndex minBound)
              (getAccIndex maxBound)
              i
  
  mkAccountIndex :: Word32 -> Either AccountIndexError AccountIndex
  mkAccountIndex index
      | index >= getAccIndex minBound = Right $ AccountIndex index
      | otherwise = Left $ AccountIndexError index
  
  mkAccountIndexM :: MonadFail m => Word32 -> m AccountIndex
  mkAccountIndexM =
      either (fail . toString . sformat build) pure . mkAccountIndex
  
  unsafeMkAccountIndex :: Word32 -> AccountIndex
  unsafeMkAccountIndex =
      either (error . sformat build) identity . mkAccountIndex
  
  instance Bounded AccountIndex where
      -- NOTE: minimum for hardened key. See https://iohk.myjetbrains.com/youtrack/issue/CO-309
      minBound = AccountIndex 2147483648
      maxBound = AccountIndex maxBound
  
  instance ToJSON AccountIndex where
      toJSON = toJSON . getAccIndex
  
  instance FromJSON AccountIndex where
      parseJSON =
          mkAccountIndexM <=< parseJSON
  
  instance Arbitrary AccountIndex where
      arbitrary =
          AccountIndex <$> choose (getAccIndex minBound, getAccIndex maxBound)
  
  deriveSafeBuildable ''AccountIndex
  -- Nothing secret to redact for a AccountIndex.
  instance BuildableSafeGen AccountIndex where
      buildSafeGen _ =
          bprint build . getAccIndex
  
  instance ToParamSchema AccountIndex where
      toParamSchema _ = mempty
          & type_ .~ SwaggerNumber
          & minimum_ .~ Just (fromIntegral $ getAccIndex minBound)
          & maximum_ .~ Just (fromIntegral $ getAccIndex maxBound)
  
  instance ToSchema AccountIndex where
      declareNamedSchema =
          pure . paramSchemaToNamedSchema defaultSchemaOptions
  
  instance FromHttpApiData AccountIndex where
      parseQueryParam =
          first (sformat build) . mkAccountIndex <=< parseQueryParam
  
  instance ToHttpApiData AccountIndex where
      toQueryParam =
          fromString . show . getAccIndex
  
  
  -- | A wallet 'Account'.
  data Account = Account
      { accIndex     :: !AccountIndex
      , accAddresses :: ![WalletAddress]
      , accAmount    :: !(V1 Core.CoinPair)
      , accName      :: !Text
      , accWalletId  :: !WalletId
      } deriving (Show, Ord, Eq, Generic)
  
  
  --
  -- IxSet indices
  --
  
  
  
  
  
  -- | Datatype wrapping addresses for per-field endpoint
  newtype AccountAddresses = AccountAddresses
      { acaAddresses :: [WalletAddress]
      } deriving (Show, Ord, Eq, Generic)
  
  -- | Datatype wrapping balance for per-field endpoint
  newtype AccountBalance = AccountBalance
      { acbAmount    :: V1 Core.CoinPair
      } deriving (Show, Ord, Eq, Generic)
  
  accountsHaveSameId :: Account -> Account -> Bool
  accountsHaveSameId a b =
      accWalletId a == accWalletId b
      &&
      accIndex a == accIndex b
  
  deriveJSON Aeson.defaultOptions ''Account
  deriveJSON Aeson.defaultOptions ''AccountAddresses
  deriveJSON Aeson.defaultOptions ''AccountBalance
  
  instance ToSchema Account where
      declareNamedSchema =
          genericSchemaDroppingPrefix "acc" (\(--^) props -> props
              & ("index"     --^ "Account's index in the wallet, starting at 0.")
              & ("addresses" --^ "Public addresses pointing to this account.")
              & ("amount"    --^ "Available funds, in Lovelace.")
              & ("name"      --^ "Account's name.")
              & ("walletId"  --^ "Id of the wallet this account belongs to.")
            )
  
  instance ToSchema AccountAddresses where
      declareNamedSchema =
          genericSchemaDroppingPrefix "aca" (\(--^) props -> props
              & ("addresses" --^ "Public addresses pointing to this account.")
            )
  
  instance ToSchema AccountBalance where
      declareNamedSchema =
          genericSchemaDroppingPrefix "acb" (\(--^) props -> props
              & ("amount"    --^ "Available funds, in Lovelace.")
            )
  
  instance Arbitrary Account where
      arbitrary = Account <$> arbitrary
                          <*> arbitrary
                          <*> arbitrary
                          <*> pure "My account"
                          <*> arbitrary
  
  instance Arbitrary AccountAddresses where
      arbitrary =
          AccountAddresses <$> arbitrary
  
  instance Arbitrary AccountBalance where
      arbitrary =
          AccountBalance <$> arbitrary
  
  deriveSafeBuildable ''Account
  instance BuildableSafeGen Account where
      buildSafeGen sl Account{..} = bprint ("{"
          %" index="%buildSafe sl
          %" name="%buildSafe sl
          %" addresses="%buildSafe sl
          %" amount="%buildSafe sl
          %" walletId="%buildSafe sl
          %" }")
          accIndex
          accName
          accAddresses
          accAmount
          accWalletId
  
  instance Buildable AccountAddresses where
      build =
          bprint listJson . acaAddresses
  
  instance Buildable AccountBalance where
      build =
          bprint build . acbAmount
  
  instance Buildable [Account] where
      build =
          bprint listJson
  
  -- | Account Update
  data AccountUpdate = AccountUpdate {
      uaccName      :: !Text
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''AccountUpdate
  
  instance ToSchema AccountUpdate where
    declareNamedSchema =
      genericSchemaDroppingPrefix "uacc" (\(--^) props -> props
        & ("name" --^ "New account's name.")
      )
  
  instance Arbitrary AccountUpdate where
    arbitrary = AccountUpdate <$> pure "myAccount"
  
  deriveSafeBuildable ''AccountUpdate
  instance BuildableSafeGen AccountUpdate where
      buildSafeGen sl AccountUpdate{..} =
          bprint ("{ name="%buildSafe sl%" }") uaccName
  
  
  -- | New Account
  data NewAccount = NewAccount
    { naccSpendingPassword :: !(Maybe SpendingPassword)
    , naccName             :: !Text
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''NewAccount
  
  instance Arbitrary NewAccount where
    arbitrary = NewAccount <$> arbitrary
                           <*> arbitrary
  
  instance ToSchema NewAccount where
    declareNamedSchema =
      genericSchemaDroppingPrefix "nacc" (\(--^) props -> props
        & ("spendingPassword" --^ "Wallet's protection password, required if defined.")
        & ("name"             --^ "Account's name.")
      )
  
  deriveSafeBuildable ''NewAccount
  instance BuildableSafeGen NewAccount where
      buildSafeGen sl NewAccount{..} = bprint ("{"
          %" spendingPassword="%(buildSafeMaybe mempty sl)
          %" name="%buildSafe sl
          %" }")
          naccSpendingPassword
          naccName
  
  deriveSafeBuildable ''WalletAddress
  instance BuildableSafeGen WalletAddress where
      buildSafeGen sl WalletAddress{..} = bprint ("{"
          %" id="%buildSafe sl
          %" used="%build
          %" changeAddress="%build
          %" }")
          addrId
          addrUsed
          addrChangeAddress
  
  instance Buildable [WalletAddress] where
      build = bprint listJson
  
  instance Buildable [V1 Core.Address] where
      build = bprint listJson
  
  data BatchImportResult a = BatchImportResult
      { aimTotalSuccess :: !Natural
      , aimFailures     :: ![a]
      } deriving (Show, Ord, Eq, Generic)
  
  instance Buildable (BatchImportResult a) where
      build res = bprint
          ("BatchImportResult (success:"%int%", failures:"%int%")")
          (aimTotalSuccess res)
          (length $ aimFailures res)
  
  instance ToJSON a => ToJSON (BatchImportResult a) where
      toJSON = genericToJSON Aeson.defaultOptions
  
  instance FromJSON a => FromJSON (BatchImportResult a) where
      parseJSON = genericParseJSON Aeson.defaultOptions
  
  instance (ToJSON a, ToSchema a, Arbitrary a) => ToSchema (BatchImportResult a) where
      declareNamedSchema =
          genericSchemaDroppingPrefix "aim" (\(--^) props -> props
              & ("totalSuccess" --^ "Total number of entities successfully imported")
              & ("failures" --^ "Entities failed to be imported, if any")
          )
  
  instance Arbitrary a => Arbitrary (BatchImportResult a) where
      arbitrary = BatchImportResult
          <$> arbitrary
          <*> scale (`mod` 3) arbitrary -- NOTE Small list
  
  instance Arbitrary a => Example (BatchImportResult a)
  
  instance Semigroup (BatchImportResult a) where
      (BatchImportResult a0 b0) <> (BatchImportResult a1 b1) =
          BatchImportResult (a0 + a1) (b0 <> b1)
  
  instance Monoid (BatchImportResult a) where
      mempty = BatchImportResult 0 mempty
  
  
  -- | Create a new Address
  data NewAddress = NewAddress
    { newaddrSpendingPassword :: !(Maybe SpendingPassword)
    , newaddrAccountIndex     :: !AccountIndex
    , newaddrWalletId         :: !WalletId
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''NewAddress
  
  instance ToSchema NewAddress where
    declareNamedSchema =
      genericSchemaDroppingPrefix "newaddr" (\(--^) props -> props
        & ("spendingPassword" --^ "Wallet's protection password, required if defined.")
        & ("accountIndex"     --^ "Target account's index to store this address in.")
        & ("walletId"         --^ "Corresponding wallet identifier.")
      )
  
  instance Arbitrary NewAddress where
    arbitrary = NewAddress <$> arbitrary
                           <*> arbitrary
                           <*> arbitrary
  
  deriveSafeBuildable ''NewAddress
  instance BuildableSafeGen NewAddress where
      buildSafeGen sl NewAddress{..} = bprint("{"
          %" spendingPassword="%(buildSafeMaybe mempty sl)
          %" accountIndex="%buildSafe sl
          %" walletId="%buildSafe sl
          %" }")
          newaddrSpendingPassword
          newaddrAccountIndex
          newaddrWalletId
  
  -- | A type incapsulating a password update request.
  data PasswordUpdate = PasswordUpdate {
      pwdOld :: !SpendingPassword
    , pwdNew :: !SpendingPassword
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''PasswordUpdate
  
  instance ToSchema PasswordUpdate where
    declareNamedSchema =
      genericSchemaDroppingPrefix "pwd" (\(--^) props -> props
        & ("old" --^ "Old password.")
        & ("new" --^ "New passowrd.")
      )
  
  instance Arbitrary PasswordUpdate where
    arbitrary = PasswordUpdate <$> arbitrary
                               <*> arbitrary
  
  deriveSafeBuildable ''PasswordUpdate
  instance BuildableSafeGen PasswordUpdate where
      buildSafeGen sl PasswordUpdate{..} = bprint("{"
          %" old="%buildSafe sl
          %" new="%buildSafe sl
          %" }")
          pwdOld
          pwdNew
  
  
  -- | 'EstimatedFees' represents the fees which would be generated
  -- for a 'Payment' in case the latter would actually be performed.
  data EstimatedFees = EstimatedFees {
      feeEstimatedAmount :: !(V1 Core.Coin)
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''EstimatedFees
  
  instance ToSchema EstimatedFees where
    declareNamedSchema =
      genericSchemaDroppingPrefix "fee" (\(--^) props -> props
        & ("estimatedAmount" --^ "Estimated fees, in Lovelace.")
      )
  
  instance Arbitrary EstimatedFees where
    arbitrary = EstimatedFees <$> arbitrary
  
  deriveSafeBuildable ''EstimatedFees
  instance BuildableSafeGen EstimatedFees where
      buildSafeGen sl EstimatedFees{..} = bprint("{"
          %" estimatedAmount="%buildSafe sl
          %" }")
          feeEstimatedAmount
  
  
  -- | Maps an 'Address' to some 'Coin's, and it's
  -- typically used to specify where to send money during a 'Payment'.
  data PaymentDistribution = PaymentDistribution {
        pdAddress :: !(V1 Core.Address)
      , pdAmount  :: !(V1 Core.CoinPair)
      } deriving (Show, Ord, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''PaymentDistribution
  
  instance ToSchema PaymentDistribution where
    declareNamedSchema =
      genericSchemaDroppingPrefix "pd" (\(--^) props -> props
        & ("address" --^ "Address to map coins to.")
        & ("amount"  --^ "Amount of coin to bind, in Lovelace.")
      )
  
  instance Arbitrary PaymentDistribution where
    arbitrary = PaymentDistribution <$> arbitrary
                                    <*> arbitrary
  
  deriveSafeBuildable ''PaymentDistribution
  instance BuildableSafeGen PaymentDistribution where
      buildSafeGen sl PaymentDistribution{..} = bprint ("{"
          %" address="%buildSafe sl
          %" amount="%buildSafe sl
          %" }")
          pdAddress
          pdAmount
  
  
  -- | A 'PaymentSource' encapsulate two essentially piece of data to reach for some funds:
  -- a 'WalletId' and an 'AccountIndex' within it.
  data PaymentSource = PaymentSource
    { psWalletId     :: !WalletId
    , psAccountIndex :: !AccountIndex
    } deriving (Show, Ord, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''PaymentSource
  
  instance ToSchema PaymentSource where
    declareNamedSchema =
      genericSchemaDroppingPrefix "ps" (\(--^) props -> props
        & ("walletId"     --^ "Target wallet identifier to reach.")
        & ("accountIndex" --^ "Corresponding account's index on the wallet.")
      )
  
  instance Arbitrary PaymentSource where
    arbitrary = PaymentSource <$> arbitrary
                              <*> arbitrary
  
  deriveSafeBuildable ''PaymentSource
  instance BuildableSafeGen PaymentSource where
      buildSafeGen sl PaymentSource{..} = bprint ("{"
          %" walletId="%buildSafe sl
          %" accountIndex="%buildSafe sl
          %" }")
          psWalletId
          psAccountIndex
  
  
  -- | A 'Payment' from one source account to one or more 'PaymentDistribution'(s).
  data Payment = Payment
    { pmtSource           :: !PaymentSource
    , pmtDestinations     :: !(NonEmpty PaymentDistribution)
    , pmtGroupingPolicy   :: !(Maybe (V1 Core.InputSelectionPolicy))
    , pmtSpendingPassword :: !(Maybe SpendingPassword)
    } deriving (Show, Eq, Generic)
  
  instance ToJSON (V1 Core.InputSelectionPolicy) where
      toJSON (V1 Core.OptimizeForSecurity)       = String "OptimizeForSecurity"
      toJSON (V1 Core.OptimizeForHighThroughput) = String "OptimizeForHighThroughput"
  
  instance FromJSON (V1 Core.InputSelectionPolicy) where
      parseJSON (String "OptimizeForSecurity")       = pure (V1 Core.OptimizeForSecurity)
      parseJSON (String "OptimizeForHighThroughput") = pure (V1 Core.OptimizeForHighThroughput)
      parseJSON x = typeMismatch "Not a valid InputSelectionPolicy" x
  
  instance ToSchema (V1 Core.InputSelectionPolicy) where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "V1InputSelectionPolicy") $ mempty
              & type_ .~ SwaggerString
              & enum_ ?~ ["OptimizeForSecurity", "OptimizeForHighThroughput"]
  
  instance Arbitrary (V1 Core.InputSelectionPolicy) where
      arbitrary = fmap V1 arbitrary
  
  
  deriveJSON Aeson.defaultOptions ''Payment
  
  instance Arbitrary Payment where
    arbitrary = Payment <$> arbitrary
                        <*> arbitrary
                        <*> arbitrary
                        <*> arbitrary
  
  instance ToSchema Payment where
    declareNamedSchema =
      genericSchemaDroppingPrefix "pmt" (\(--^) props -> props
        & ("source"           --^ "Source for the payment.")
        & ("destinations"     --^ "One or more destinations for the payment.")
        & ("groupingPolicy"   --^ "Optional strategy to use for selecting the transaction inputs.")
        & ("spendingPassword" --^ "Wallet's protection password, required to spend funds if defined.")
      )
  
  deriveSafeBuildable ''Payment
  instance BuildableSafeGen Payment where
      buildSafeGen sl (Payment{..}) = bprint ("{"
          %" source="%buildSafe sl
          %" destinations="%buildSafeList sl
          %" groupingPolicty="%build
          %" spendingPassword="%(buildSafeMaybe mempty sl)
          %" }")
          pmtSource
          (toList pmtDestinations)
          pmtGroupingPolicy
          pmtSpendingPassword
  
  -- | proof for issurance
  -- newtype Proof = Proof ByteString
  --     deriving (Show, Ord, Eq, Generic)
  
  -- instance ToJSON Proof where
  --   toJSON (Proof bs) = toJSON $ Base16.encode bs
  
  -- instance FromJSON Proof where
  --     parseJSON = withText "Proof" $ \p -> do
  --        case (Base16.decode p) of
  --            Left err -> fail $ "Failed to parse Proof: " <> toString err
  --            Right a  -> pure $ Proof a
  
  -- instance ToSchema Proof where
  --     declareNamedSchema _ =
  --         pure $ NamedSchema (Just "Proof") $ mempty
  --              & type_ .~ SwaggerString
  --              & format ?~ "hex|base16"
  
  -- instance Arbitrary Proof where
  --   arbitrary = pure . Proof . leftToPanic "Never fail" $ 
  --               Base16.decode "692068617665206120746f6e206f6620676f6c647320696e204a50204d6f7267616e2e"
  
  -- deriveSafeBuildable ''Proof
  -- instance BuildableSafeGen Proof where
  --     buildSafeGen _ (Proof bs) = bprint stext $ Base16.encode bs 
  
  -- | Issue number of GDs with proof
  data IssuranceInfo = IssuranceInfo {
        iiIncrement :: !(V1 Core.GoldDollar)
      , iiProof     :: !Text
        -- ^ Hex style
      } deriving (Show, Ord, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''IssuranceInfo
  
  instance ToSchema IssuranceInfo where
    declareNamedSchema =
      genericSchemaDroppingPrefix "ii" (\(--^) props -> props
        & ("increment" --^ "Increment of GDs.")
        & ("proof"     --^ "The hex style proof info for this issurance.")
      )
  
  instance Arbitrary IssuranceInfo where
    arbitrary = IssuranceInfo <$> arbitrary
                              <*> arbitrary
  
  deriveSafeBuildable '' IssuranceInfo
  instance BuildableSafeGen IssuranceInfo where
      buildSafeGen sl IssuranceInfo{..} = bprint ("{"
          %" increment="%buildSafe sl
          %" proof="%buildSafe sl
          %" }")
          iiIncrement
          iiProof
  
  -- | A 'Issurance' for issuing GD
  data Issurance = Issurance
    { issSource           :: !PaymentSource
    , issInfo             :: !IssuranceInfo
    , issGroupingPolicy   :: !(Maybe (V1 Core.InputSelectionPolicy))
    , issSpendingPassword :: !(Maybe SpendingPassword)
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''Issurance
  
  instance Arbitrary Issurance where
    arbitrary = Issurance <$> arbitrary
                          <*> arbitrary
                          <*> arbitrary
                          <*> arbitrary
  
  instance ToSchema Issurance where
    declareNamedSchema =
      genericSchemaDroppingPrefix "iss" (\(--^) props -> props
        & ("source"           --^ "Source for the payment.")
        & ("info"             --^ "Increment and proof for the issurance.")
        & ("groupingPolicy"   --^ "Optional strategy to use for selecting the transaction inputs.")
        & ("spendingPassword" --^ "Wallet's protection password, required to spend funds if defined.")
      )
  
  deriveSafeBuildable ''Issurance
  instance BuildableSafeGen Issurance where
      buildSafeGen sl (Issurance{..}) = bprint ("{"
          %" source="%buildSafe sl
          %" destinations="%build
          %" groupingPolicty="%build
          %" spendingPassword="%(buildSafeMaybe mempty sl)
          %" }")
          issSource
          issInfo
          issGroupingPolicy
          issSpendingPassword
  
  ----------------------------------------------------------------------------
  -- TxId
  ----------------------------------------------------------------------------
  instance Arbitrary (V1 Txp.TxId) where
    arbitrary = V1 <$> arbitrary
  
  instance ToJSON (V1 Txp.TxId) where
    toJSON (V1 t) = String (sformat hashHexF t)
  
  instance FromJSON (V1 Txp.TxId) where
      parseJSON = withText "TxId" $ \t -> do
         case decodeHash t of
             Left err -> fail $ "Failed to parse transaction ID: " <> toString err
             Right a  -> pure (V1 a)
  
  instance FromHttpApiData (V1 Txp.TxId) where
      parseQueryParam = fmap (fmap V1) decodeHash
  
  instance ToHttpApiData (V1 Txp.TxId) where
      toQueryParam (V1 txId) = sformat hashHexF txId
  
  instance ToSchema (V1 Txp.TxId) where
      declareNamedSchema _ = declareNamedSchema (Proxy @Text)
  
  ----------------------------------------------------------------------------
    -- Transaction types
  ----------------------------------------------------------------------------
  
  -- | The 'Transaction' type.
  data TransactionType =
      LocalTransaction
    -- ^ This transaction is local, which means all the inputs
    -- and all the outputs belongs to the wallet from which the
    -- transaction was originated.
    | ForeignTransaction
    -- ^ This transaction is not local to this wallet.
    deriving (Show, Ord, Eq, Enum, Bounded)
  
  instance Arbitrary TransactionType where
    arbitrary = elements [minBound .. maxBound]
  
  -- Drops the @Transaction@ suffix.
  deriveJSON defaultOptions { A.constructorTagModifier = reverse . drop 11 . reverse . map C.toLower
                            } ''TransactionType
  
  instance ToSchema TransactionType where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "TransactionType") $ mempty
              & type_ .~ SwaggerString
              & enum_ ?~ ["local", "foreign"]
              & description ?~ mconcat
                  [ "A transaction is 'local' if all the inputs and outputs "
                  , "belong to the current wallet. A transaction is foreign "
                  , "if the transaction is not local to this wallet."
                  ]
  
  deriveSafeBuildable ''TransactionType
  instance BuildableSafeGen TransactionType where
      buildSafeGen _ LocalTransaction   = "local"
      buildSafeGen _ ForeignTransaction = "foreign"
  
  
  -- | The 'Transaction' @direction@
  data TransactionDirection =
      IncomingTransaction
    -- ^ This represents an incoming transactions.
    | OutgoingTransaction
    -- ^ This qualifies external transactions.
    deriving (Show, Ord, Eq, Enum, Bounded)
  
  instance Arbitrary TransactionDirection where
    arbitrary = elements [minBound .. maxBound]
  
  -- Drops the @Transaction@ suffix.
  deriveJSON defaultOptions { A.constructorTagModifier = reverse . drop 11 . reverse . map C.toLower
                            } ''TransactionDirection
  
  instance ToSchema TransactionDirection where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "TransactionDirection") $ mempty
              & type_ .~ SwaggerString
              & enum_ ?~ ["outgoing", "incoming"]
  
  -- | This is an information-less variant of 'PtxCondition'.
  data TransactionStatus
      = Applying
      | InNewestBlocks
      | Persisted
      | WontApply
      | Creating
      deriving (Eq, Show, Ord)
  
  allTransactionStatuses :: [TransactionStatus]
  allTransactionStatuses =
      [Applying, InNewestBlocks, Persisted, WontApply, Creating]
  
  transactionStatusToText :: TransactionStatus -> Text
  transactionStatusToText x = case x of
      Applying {} ->
          "applying"
      InNewestBlocks {} ->
          "inNewestBlocks"
      Persisted {} ->
          "persisted"
      WontApply {} ->
          "wontApply"
      Creating {} ->
          "creating"
  
  instance ToJSON TransactionStatus where
      toJSON x = object
          [ "tag" .= transactionStatusToText x
          , "data" .= Object mempty
          ]
  
  instance ToSchema TransactionStatus where
      declareNamedSchema _ =
          pure $ NamedSchema (Just "TransactionStatus") $ mempty
              & type_ .~ SwaggerObject
              & required .~ ["tag", "data"]
              & properties .~ (mempty
                  & at "tag" ?~ Inline (mempty
                      & type_ .~ SwaggerString
                      & enum_ ?~
                          map (String . transactionStatusToText)
                              allTransactionStatuses
                  )
                  & at "data" ?~ Inline (mempty
                      & type_ .~ SwaggerObject
                  )
              )
  
  instance FromJSON TransactionStatus where
      parseJSON = withObject "TransactionStatus" $ \o -> do
         tag <- o .: "tag"
         case tag of
             "applying" ->
                  pure Applying
             "inNewestBlocks" ->
                  pure InNewestBlocks
             "persisted" ->
                  pure Persisted
             "wontApply" ->
                  pure WontApply
             "creating" ->
                  pure Creating
             _ ->
                  fail $ "Couldn't parse out of " ++ toString (tag :: Text)
  
  instance Arbitrary TransactionStatus where
      arbitrary = elements allTransactionStatuses
  
  deriveSafeBuildable ''TransactionDirection
  instance BuildableSafeGen TransactionDirection where
      buildSafeGen _ IncomingTransaction = "incoming"
      buildSafeGen _ OutgoingTransaction = "outgoing"
  
  -- | A 'Wallet''s 'Transaction'.
  data Transaction = Transaction
    { txId            :: !(V1 Txp.TxId)
    , txConfirmations :: !Word
    , txAmount        :: !(V1 Core.Coin)
    , txAmountGD      :: !(V1 Core.GoldDollar)
    , txInputs        :: !(NonEmpty PaymentDistribution)
    , txOutputs       :: ![PaymentDistribution]
      -- ^ The output money distribution.
    , txDirection     :: !TransactionDirection
      -- ^ The direction for this transaction (e.g incoming, outgoing).
    , txDirectionGD   :: !TransactionDirection
      -- ^ The direction for this transaction (e.g incoming, outgoing).
    , txCreationTime  :: !(V1 Core.Timestamp)
      -- ^ The time when transaction was created.
    , txStatus        :: !TransactionStatus
    } deriving (Show, Ord, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''Transaction
  
  instance ToSchema Transaction where
    declareNamedSchema =
      genericSchemaDroppingPrefix "tx" (\(--^) props -> props
        & ("id"            --^ "Transaction's id.")
        & ("confirmations" --^ "Number of confirmations.")
        & ("amount"        --^ "Coins moved as part of the transaction, in Lovelace.")
        & ("amountGD"      --^ "GDs moved as part of the transaction, in Lovelace.")
        & ("inputs"        --^ "One or more input money distributions.")
        & ("outputs"       --^ "One or more ouputs money distributions.")
        & ("direction"     --^ "Direction of coins for this transaction.")
        & ("directionGD"   --^ "Direction of GDs for this transaction.")
        & ("creationTime"  --^ "Timestamp indicating when the transaction was created.")
        & ("status"        --^ "Shows whether or not the transaction is accepted.")
      )
  
  instance Arbitrary Transaction where
    arbitrary = Transaction <$> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
                            <*> arbitrary
  
  deriveSafeBuildable ''Transaction
  instance BuildableSafeGen Transaction where
      buildSafeGen sl Transaction{..} = bprint ("{"
          %" id="%buildSafe sl
          %" confirmations="%build
          %" amount="%buildSafe sl
          %" amountGD="%buildSafe sl
          %" inputs="%buildSafeList sl
          %" outputs="%buildSafeList sl
          %" direction"%buildSafe sl
          %" directionGD"%buildSafe sl
          %" }")
          txId
          txConfirmations
          txAmount
          txAmountGD
          (toList txInputs)
          txOutputs
          txDirection
          txDirectionGD
  
  instance Buildable [Transaction] where
      build = bprint listJson
  
  -- | A type representing an upcoming wallet update.
  data WalletSoftwareUpdate = WalletSoftwareUpdate
    { updSoftwareVersion   :: !Text
    , updBlockchainVersion :: !Text
    , updScriptVersion     :: !Int
    -- Other types omitted for now.
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''WalletSoftwareUpdate
  
  instance ToSchema WalletSoftwareUpdate where
    declareNamedSchema =
      genericSchemaDroppingPrefix "upd" (\(--^) props -> props
        & ("softwareVersion"   --^ "Current software (wallet) version.")
        & ("blockchainVersion" --^ "Version of the underlying blockchain.")
        & ("scriptVersion"     --^ "Update script version.")
      )
  
  instance Arbitrary WalletSoftwareUpdate where
    arbitrary = WalletSoftwareUpdate <$> arbitrary
                                     <*> arbitrary
                                     <*> fmap getPositive arbitrary
  
  deriveSafeBuildable ''WalletSoftwareUpdate
  instance BuildableSafeGen WalletSoftwareUpdate where
      buildSafeGen _ WalletSoftwareUpdate{..} = bprint("{"
          %" softwareVersion="%build
          %" blockchainVersion="%build
          %" scriptVersion="%build
          %" }")
          updSoftwareVersion
          updBlockchainVersion
          updScriptVersion
  
  -- | A type encapsulating enough information to import a wallet from a
  -- backup file.
  data WalletImport = WalletImport
    { wiSpendingPassword :: !(Maybe SpendingPassword)
    , wiFilePath         :: !FilePath
    } deriving (Show, Eq, Generic)
  
  deriveJSON Aeson.defaultOptions ''WalletImport
  
  instance ToSchema WalletImport where
    declareNamedSchema =
      genericSchemaDroppingPrefix "wi" (\(--^) props -> props
        & ("spendingPassword"   --^ "An optional spending password to set for the imported wallet.")
        & ("filePath" --^ "The path to the .key file holding the backup.")
      )
  
  instance Arbitrary WalletImport where
    arbitrary = WalletImport <$> arbitrary
                             <*> arbitrary
  
  deriveSafeBuildable ''WalletImport
  instance BuildableSafeGen WalletImport where
      buildSafeGen sl WalletImport{..} = bprint("{"
          %" spendingPassword="%build
          %" filePath="%build
          %" }")
          (maybe "null" (buildSafeGen sl) wiSpendingPassword)
          wiFilePath
  
  -- | A redemption mnemonic.
  newtype RedemptionMnemonic = RedemptionMnemonic
      { unRedemptionMnemonic :: Mnemonic 9
      }
      deriving stock (Eq, Show, Generic)
      deriving newtype (ToJSON, FromJSON, Arbitrary)
  
  instance ToSchema RedemptionMnemonic where
      declareNamedSchema _ = pure $
          NamedSchema (Just "RedemptionMnemonic") (toSchema (Proxy @(Mnemonic 9)))
  
  -- | A shielded redemption code.
  newtype ShieldedRedemptionCode = ShieldedRedemptionCode
      { unShieldedRedemptionCode :: Text
      } deriving (Eq, Show, Generic)
        deriving newtype (ToJSON, FromJSON)
  
  -- | This instance could probably be improved. A 'ShieldedRedemptionCode' is
  -- a hash of the redemption key.
  instance Arbitrary ShieldedRedemptionCode where
      arbitrary = ShieldedRedemptionCode <$> arbitrary
  
  instance ToSchema ShieldedRedemptionCode where
      declareNamedSchema _ =
          pure
              $ NamedSchema (Just "ShieldedRedemptionCode") $ mempty
              & type_ .~ SwaggerString
  
  deriveSafeBuildable ''ShieldedRedemptionCode
  instance BuildableSafeGen ShieldedRedemptionCode where
      buildSafeGen _ _ =
          bprint "<shielded redemption code>"
  
  -- | The request body for redeeming some Ada.
  data Redemption = Redemption
      { redemptionRedemptionCode   :: ShieldedRedemptionCode
      -- ^ The redemption code associated with the Ada to redeem.
      , redemptionMnemonic         :: Maybe RedemptionMnemonic
      -- ^ An optional mnemonic. This mnemonic was included with paper
      -- certificates, and the presence of this field indicates that we're
      -- doing a paper vend.
      , redemptionSpendingPassword :: SpendingPassword
      -- ^ The user must provide a spending password that matches the wallet that
      -- will be receiving the redemption funds.
      , redemptionWalletId         :: WalletId
      -- ^ Redeem to this wallet
      , redemptionAccountIndex     :: AccountIndex
      -- ^ Redeem to this account index in the wallet
      } deriving (Eq, Show, Generic)
  
  deriveSafeBuildable ''Redemption
  instance BuildableSafeGen Redemption where
      buildSafeGen sl r = bprint ("{"
          %" redemptionCode="%buildSafe sl
          %" mnemonic=<mnemonic>"
          %" spendingPassword="%buildSafe sl
          %" }")
          (redemptionRedemptionCode r)
          (redemptionSpendingPassword r)
  
  deriveJSON Aeson.defaultOptions ''Redemption
  
  instance ToSchema Redemption where
      declareNamedSchema =
          genericSchemaDroppingPrefix "redemption" (\(--^) props -> props
              & "redemptionCode"
              --^ "The redemption code associated with the Ada to redeem."
              & "mnemonic"
              --^ ( "An optional mnemonic. This must be provided for a paper "
                  <> "certificate redemption."
                  )
              & "spendingPassword"
              --^ ( "An optional spending password. This must match the password "
                  <> "for the provided wallet ID and account index."
                  )
          )
  
  instance Arbitrary Redemption where
      arbitrary = Redemption <$> arbitrary
                             <*> arbitrary
                             <*> arbitrary
                             <*> arbitrary
                             <*> arbitrary
  
  --
  -- POST/PUT requests isomorphisms
  --
  
  type family Update (original :: *) :: * where
      Update Wallet =
          WalletUpdate
      Update Account =
          AccountUpdate
      Update WalletAddress =
          () -- read-only
  
  type family New (original :: *) :: * where
      New Wallet =
          NewWallet
      New Account =
          NewAccount
      New WalletAddress =
          NewAddress
  
  type CaptureWalletId = Capture "walletId" WalletId
  
  type CaptureAccountId = Capture "accountId" AccountIndex
  
  --
  -- Example typeclass instances
  --
  
  instance Example Core.Address
  instance Example AccountIndex
  instance Example AccountBalance
  instance Example AccountAddresses
  instance Example WalletId
  instance Example AssuranceLevel
  instance Example LocalTimeDifference
  instance Example PaymentDistribution
  instance Example AccountUpdate
  instance Example Wallet
  instance Example WalletUpdate
  instance Example WalletOperation
  instance Example PasswordUpdate
  instance Example EstimatedFees
  instance Example Transaction
  instance Example WalletSoftwareUpdate
  instance Example WalletAddress
  instance Example NewAccount
  instance Example AddressValidity
  instance Example NewAddress
  instance Example ShieldedRedemptionCode
  instance Example (V1 Core.PassPhrase)
  instance Example (V1 Core.Coin)
  instance Example (V1 Core.GoldDollar)
  instance Example (V1 Core.CoinPair)
  
  -- | We have a specific 'Example' instance for @'V1' 'Address'@ because we want
  -- to control the length of the examples. It is possible for the encoded length
  -- to become huge, up to 1000+ bytes, if the 'UnsafeMultiKeyDistr' constructor
  -- is used. We do not use this constructor, which keeps the address between
  -- ~80-150 bytes long.
  instance Example (V1 Core.Address) where
      example = fmap V1 . Core.makeAddress
          <$> arbitrary
          <*> arbitraryAttributes
        where
          arbitraryAttributes =
              Core.AddrAttributes
                  <$> arbitrary
                  <*> oneof
                      [ pure Core.BootstrapEraDistr
                      , Core.SingleKeyDistr <$> arbitrary
                      ]
                  <*> arbitrary
  
  instance Example BackupPhrase where
      example = pure (BackupPhrase def)
  
  instance Example Core.InputSelectionPolicy where
      example = pure Core.OptimizeForHighThroughput
  
  instance Example (V1 Core.InputSelectionPolicy) where
      example = pure (V1 Core.OptimizeForHighThroughput)
  
  instance Example Account where
      example = Account <$> example
                        <*> example -- NOTE: this will produce non empty list
                        <*> example
                        <*> pure "My account"
                        <*> example
  
  instance Example NewWallet where
      example = NewWallet <$> example
                          <*> example -- Note: will produce `Just a`
                          <*> example
                          <*> pure "My Wallet"
                          <*> example
  
  instance Example PublicKey where
      example = PublicKey <$> pure xpub
        where
          xpub = rights
              [ CC.xpub
              . fromJust
              . decodeBase58 bitcoinAlphabet
              . encodeUtf8 $ encodedPublicKey
              ] !! 0
  
          encodedPublicKey :: Text
          encodedPublicKey =
              "bNfWjshJG9xxy6VkpV2KurwGah3jQWjGb4QveDGZteaCwupdKWAi371r8uS5yFCny5i5EQuSNSLKqvRHmWEoHe45pZ"
  
  instance Example PaymentSource where
      example = PaymentSource <$> example
                              <*> example
  
  instance Example Payment where
      example = Payment <$> example
                        <*> example
                        <*> example -- TODO: will produce `Just groupingPolicy`
                        <*> example
  
  -- instance Example Proof where
  --     example = example
  
  instance Example IssuranceInfo where
      example = flip IssuranceInfo "692068617665206120746f6e206f6620676f6c647320696e204a50204d6f7267616e2e" <$> example
                              
  
  instance Example Issurance where
      example = Issurance <$> example
                          <*> example
                          <*> example -- TODO: will produce `Just groupingPolicy`
                          <*> example
  
  instance Example Redemption where
      example = Redemption <$> example
                           <*> pure Nothing
                           <*> example
                           <*> example
                           <*> example
  
  instance Example WalletImport where
      example = WalletImport <$> example
                             <*> pure "/Users/foo/Documents/wallet_to_import.key"
  
  --
  -- Wallet Errors
  --
  
  -- | Details about what 'NotEnoughMoney' means
  data ErrNotEnoughMoney
      -- | UTxO exhausted whilst trying to pick inputs to cover remaining fee
      = ErrCannotCoverFee
  
      -- | UTxO exhausted during input selection
      --
      -- We record the available balance of the UTxO
      | ErrAvailableBalanceIsInsufficient Int
  
      deriving (Eq, Show, Generic)
  
  instance Buildable ErrNotEnoughMoney where
      build = \case
          ErrCannotCoverFee ->
               bprint "Not enough coins to cover fee."
          ErrAvailableBalanceIsInsufficient _ ->
               bprint "Not enough available coins to proceed."
  
  instance ToJSON ErrNotEnoughMoney where
      toJSON = \case
          e@ErrCannotCoverFee -> object
              [ "msg" .= sformat build e
              ]
          e@(ErrAvailableBalanceIsInsufficient balance) -> object
              [ "msg"              .= sformat build e
              , "availableBalance" .= balance
              ]
  
  instance FromJSON ErrNotEnoughMoney where
      parseJSON v =
              withObject "AvailableBalanceIsInsufficient" availableBalanceIsInsufficientParser v
          <|> withObject "CannotCoverFee" cannotCoverFeeParser v
        where
          cannotCoverFeeParser :: Object -> Parser ErrNotEnoughMoney
          cannotCoverFeeParser o = do
              msg <- o .: "msg"
              when (msg /= sformat build ErrCannotCoverFee) mempty
              pure ErrCannotCoverFee
  
          availableBalanceIsInsufficientParser :: Object -> Parser ErrNotEnoughMoney
          availableBalanceIsInsufficientParser o = do
              msg <- o .: "msg"
              when (msg /= sformat build (ErrAvailableBalanceIsInsufficient 0)) mempty
              ErrAvailableBalanceIsInsufficient <$> (o .: "availableBalance")
  
  data ErrUtxoNotEnoughFragmented = ErrUtxoNotEnoughFragmented {
        theMissingUtxos :: !Int
      , theHelp         :: !Text
      } deriving (Eq, Generic, Show)
  
  
  msgUtxoNotEnoughFragmented :: Text
  msgUtxoNotEnoughFragmented = "Utxo is not enough fragmented to handle the number of outputs of this transaction. Query /api/v1/wallets/{walletId}/statistics/utxos endpoint for more information"
  
  deriveJSON Aeson.defaultOptions ''ErrUtxoNotEnoughFragmented
  
  instance Buildable ErrUtxoNotEnoughFragmented where
      build (ErrUtxoNotEnoughFragmented missingUtxos _ ) =
          bprint ("Missing "%build%" utxo(s) to accommodate all outputs of the transaction") missingUtxos
  
  
  -- | Type representing any error which might be thrown by wallet.
  --
  -- Errors are represented in JSON in the JSend format (<https://labs.omniti.com/labs/jsend>):
  -- ```
  -- {
  --     "status": "error"
  --     "message" : <constr_name>,
  --     "diagnostic" : <data>
  -- }
  -- ```
  -- where `<constr_name>` is a string containing name of error's constructor (e. g. `NotEnoughMoney`),
  -- and `<data>` is an object containing additional error data.
  -- Additional data contains constructor fields, field names are record field names without
  -- a `we` prefix, e. g. for `OutputIsRedeem` error "diagnostic" field will be the following:
  -- ```
  -- {
  --     "address" : <address>
  -- }
  -- ```
  --
  -- Additional data in constructor should be represented as record fields.
  -- Otherwise TemplateHaskell will raise an error.
  --
  -- If constructor does not have additional data (like in case of `WalletNotFound` error),
  -- then "diagnostic" field will be empty object.
  --
  -- TODO: change fields' types to actual Cardano core types, like `Coin` and `Address`
  data WalletError =
      -- | NotEnoughMoney weNeedMore
        NotEnoughMoney !ErrNotEnoughMoney
      -- | OutputIsRedeem weAddress
      | OutputIsRedeem !(V1 Core.Address)
      -- | UnknownError weMsg
      | UnknownError !Text
      -- | InvalidAddressFormat weMsg
      | InvalidAddressFormat !Text
      | WalletNotFound
      | WalletAlreadyExists !WalletId
      | AddressNotFound
      | TxFailedToStabilize
      | InvalidPublicKey !Text
      | UnsignedTxCreationError
      | TooBigTransaction
      -- ^ Size of transaction (in bytes) is greater than maximum.
      | SignedTxSubmitError !Text
      | TxRedemptionDepleted
      -- | TxSafeSignerNotFound weAddress
      | TxSafeSignerNotFound !(V1 Core.Address)
      -- | MissingRequiredParams requiredParams
      | MissingRequiredParams !(NonEmpty (Text, Text))
      -- | WalletIsNotReadyToProcessPayments weStillRestoring
      | CannotCreateAddress !Text
      -- ^ Cannot create derivation path for new address (for external wallet).
      | WalletIsNotReadyToProcessPayments !SyncProgress
      -- ^ The @Wallet@ where a @Payment@ is being originated is not fully
      -- synced (its 'WalletSyncState' indicates it's either syncing or
      -- restoring) and thus cannot accept new @Payment@ requests.
      -- | NodeIsStillSyncing wenssStillSyncing
      | NodeIsStillSyncing !SyncPercentage
      -- ^ The backend couldn't process the incoming request as the underlying
      -- node is still syncing with the blockchain.
      | RequestThrottled !Word64
      -- ^ The request has been throttled. The 'Word64' is a count of microseconds
      -- until the user should retry.
      | UtxoNotEnoughFragmented !ErrUtxoNotEnoughFragmented
      -- ^ available Utxo is not enough fragmented, ie., there is more outputs of transaction than
      -- utxos
      | GeneralError !Text
      -- ^ General Tx creation error
      deriving (Generic, Show, Eq)
  
  deriveGeneric ''WalletError
  
  instance Exception WalletError
  
  instance ToHttpErrorStatus WalletError
  
  instance ToJSON WalletError where
      toJSON = jsendErrorGenericToJSON
  
  instance FromJSON WalletError where
      parseJSON = jsendErrorGenericParseJSON
  
  instance Arbitrary WalletError where
      arbitrary = Gen.oneof
          [ NotEnoughMoney <$> Gen.oneof
              [ pure ErrCannotCoverFee
              , ErrAvailableBalanceIsInsufficient <$> Gen.choose (1, 1000)
              ]
          , OutputIsRedeem . V1 <$> arbitrary
          , UnknownError <$> arbitraryText
          , InvalidAddressFormat <$> arbitraryText
          , pure WalletNotFound
          , WalletAlreadyExists <$> arbitrary
          , pure AddressNotFound
          , InvalidPublicKey <$> arbitraryText
          , pure UnsignedTxCreationError
          , SignedTxSubmitError <$> arbitraryText
          , pure TooBigTransaction
          , pure TxFailedToStabilize
          , pure TxRedemptionDepleted
          , TxSafeSignerNotFound . V1 <$> arbitrary
          , MissingRequiredParams <$> Gen.oneof
              [ unsafeMkNonEmpty <$> Gen.vectorOf 1 arbitraryParam
              , unsafeMkNonEmpty <$> Gen.vectorOf 2 arbitraryParam
              , unsafeMkNonEmpty <$> Gen.vectorOf 3 arbitraryParam
              ]
          , WalletIsNotReadyToProcessPayments <$> arbitrary
          , NodeIsStillSyncing <$> arbitrary
          , CannotCreateAddress <$> arbitraryText
          , RequestThrottled <$> arbitrary
          , UtxoNotEnoughFragmented <$> Gen.oneof
            [ ErrUtxoNotEnoughFragmented <$> Gen.choose (1, 10) <*> arbitrary
            ]
          ]
        where
          arbitraryText :: Gen Text
          arbitraryText =
              toText . Gen.getASCIIString <$> arbitrary
  
          arbitraryParam :: Gen (Text, Text)
          arbitraryParam =
              (,) <$> arbitrary <*> arbitrary
  
          unsafeMkNonEmpty :: [a] -> NonEmpty a
          unsafeMkNonEmpty (h:q) = h :| q
          unsafeMkNonEmpty _     = error "unsafeMkNonEmpty called with empty list"
  
  
  -- | Give a short description of an error
  instance Buildable WalletError where
      build = \case
          NotEnoughMoney x ->
               bprint build x
          OutputIsRedeem _ ->
               bprint "One of the TX outputs is a redemption address."
          UnknownError _ ->
               bprint "Unexpected internal error."
          InvalidAddressFormat _ ->
               bprint "Provided address format is not valid."
          WalletNotFound ->
               bprint "Reference to an unexisting wallet was given."
          WalletAlreadyExists _ ->
               bprint "Can't create or restore a wallet. The wallet already exists."
          AddressNotFound ->
               bprint "Reference to an unexisting address was given."
          InvalidPublicKey _ ->
              bprint "Extended public key (for external wallet) is invalid."
          UnsignedTxCreationError ->
              bprint "Unable to create unsigned transaction for an external wallet."
          TooBigTransaction ->
              bprint "Transaction size is greater than 4096 bytes."
          SignedTxSubmitError _ ->
              bprint "Unable to submit externally-signed transaction."
          MissingRequiredParams _ ->
              bprint "Missing required parameters in the request payload."
          WalletIsNotReadyToProcessPayments _ ->
              bprint "This wallet is restoring, and it cannot send new transactions until restoration completes."
          NodeIsStillSyncing _ ->
              bprint "The node is still syncing with the blockchain, and cannot process the request yet."
          TxRedemptionDepleted ->
              bprint "The redemption address was already used."
          TxSafeSignerNotFound _ ->
              bprint "The safe signer at the specified address was not found."
          TxFailedToStabilize ->
              bprint "We were unable to find a set of inputs to satisfy this transaction."
          CannotCreateAddress _ ->
              bprint "Cannot create derivation path for new address, for external wallet."
          RequestThrottled _ ->
              bprint "You've made too many requests too soon, and this one was throttled."
          UtxoNotEnoughFragmented x ->
              bprint build x
          GeneralError t ->
              bprint ("Unable to create transaction, "%stext ) t
  
  -- | Convert wallet errors to Servant errors
  instance ToServantError WalletError where
      declareServantError = \case
          NotEnoughMoney{} ->
              err403
          OutputIsRedeem{} ->
              err403
          UnknownError{} ->
              err500
          WalletNotFound{} ->
              err404
          WalletAlreadyExists{} ->
              err403
          InvalidAddressFormat{} ->
              err401
          AddressNotFound{} ->
              err404
          InvalidPublicKey{} ->
              err400
          UnsignedTxCreationError{} ->
              err500
          TooBigTransaction{} ->
              err400
          SignedTxSubmitError{} ->
              err500
          MissingRequiredParams{} ->
              err400
          WalletIsNotReadyToProcessPayments{} ->
              err403
          NodeIsStillSyncing{} ->
              err412 -- Precondition failed
          TxFailedToStabilize{} ->
              err500
          TxRedemptionDepleted{} ->
              err400
          TxSafeSignerNotFound{} ->
              err400
          CannotCreateAddress{} ->
              err500
          RequestThrottled{} ->
              err400 { errHTTPCode = 429 }
          UtxoNotEnoughFragmented{} ->
              err403
          GeneralError{} ->
              err500
  
  -- | Declare the key used to wrap the diagnostic payload, if any
  instance HasDiagnostic WalletError where
      getDiagnosticKey = \case
          NotEnoughMoney{} ->
              "details"
          OutputIsRedeem{} ->
              "address"
          UnknownError{} ->
              "msg"
          WalletNotFound{} ->
              noDiagnosticKey
          WalletAlreadyExists{} ->
              "walletId"
          InvalidAddressFormat{} ->
              "msg"
          AddressNotFound{} ->
              noDiagnosticKey
          InvalidPublicKey{} ->
              "msg"
          UnsignedTxCreationError{} ->
              noDiagnosticKey
          TooBigTransaction{} ->
              noDiagnosticKey
          SignedTxSubmitError{} ->
              "msg"
          MissingRequiredParams{} ->
              "params"
          WalletIsNotReadyToProcessPayments{} ->
              "stillRestoring"
          NodeIsStillSyncing{} ->
              "stillSyncing"
          TxFailedToStabilize{} ->
              noDiagnosticKey
          TxRedemptionDepleted{} ->
              noDiagnosticKey
          TxSafeSignerNotFound{} ->
              "address"
          CannotCreateAddress{} ->
              "msg"
          RequestThrottled{} ->
              "microsecondsUntilRetry"
          UtxoNotEnoughFragmented{} ->
              "details"
          GeneralError{} ->
              "common"
  