# LANGUAGE GeneralizedNewtypeDeriving #
{-# LANGUAGE RankNTypes                 #-}

module Cardano.Wallet.Kernel.Transactions (
      pay
    , issue
    , estimateFees
    -- * Errors
    , NewTransactionError(..)
    , SignTransactionError(..)
    , PaymentError(..)
    , EstimateFeesError(..)
    , NumberOfMissingUtxos(..)
    , mkStdTx
    , submitSignedTx
    -- * Internal & testing use only low-level APIs
    , newTransaction
    , toMeta
  ) where

import           Universum

import           Control.Lens (makeLenses, to)
import           Control.Monad.Except (MonadError (..), withExceptT)
import           Control.Retry (RetryPolicyM, RetryStatus, applyPolicy,
                     fullJitterBackoff, limitRetries, retrying)
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Vector as V
import           Test.QuickCheck (Arbitrary (..), oneof)

import           Formatting (bprint, build, (%))
import qualified Formatting.Buildable

import qualified Cardano.Wallet.Kernel.Addresses as Kernel
import           Cardano.Wallet.Kernel.DB.AcidState (DB, NewPendingError)
import           Cardano.Wallet.Kernel.DB.HdWallet
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
import           Cardano.Wallet.Kernel.DB.InDb
import           Cardano.Wallet.Kernel.DB.Read as Getters
import           Cardano.Wallet.Kernel.DB.TxMeta.Types
import           Cardano.Wallet.Kernel.Internal (ActiveWallet (..), walletNode)
import qualified Cardano.Wallet.Kernel.Internal as Internal
import qualified Cardano.Wallet.Kernel.Keystore as Keystore
import qualified Cardano.Wallet.Kernel.NodeStateAdaptor as Node
import           Cardano.Wallet.Kernel.Pending (PartialTxMeta, newPending)
import           Cardano.Wallet.Kernel.Read (getWalletSnapshot)
import           Cardano.Wallet.Kernel.Types (AccountId (..),
                     RawResolvedTx (..), WalletId (..))
import           Cardano.Wallet.Kernel.Util.Core
import           Cardano.Wallet.WalletLayer.Kernel.Conv (exceptT)
import           Pos.Chain.Genesis as Genesis (Config (..))
import           Pos.Chain.Txp (Tx (..), TxAux (..), TxId,
                     TxIn (..), TxInWitness (..), TxOut (..), TxOutAux (..),
                     TxSigData (..), Utxo)
import           Pos.Chain.Txp as Core (TxAux, TxIn, TxOut,
                     TxOutAux, toaOut, txOutAddress)
import           Pos.Client.Txp (HasTxFeePolicy (..), MonadAddresses (..), TxError, 
                     InputSelectionPolicy (..), PendingAddresses (..), 
                     makeMPubKeyTxAddrs, createMTx, createGDIssuanceTx) --, estimateTxFee)
import           Pos.Core (Address, Coin, GoldDollar, CoinPair, mkCoin, mkGoldDollar)
import qualified Pos.Core as Core
import           Pos.Core.NetworkMagic (NetworkMagic (..), makeNetworkMagic)
import           Pos.Crypto (EncryptedSecretKey, PassPhrase, ProtocolMagic,
                     PublicKey, SafeSigner (..), ShouldCheckPassphrase (..), 
                     Signature (..), hash,)

------------------------------------------------------------------------------ 
   Monad for call client module 
 ------------------------------------------------------------------------------
  Monad for call client module
-------------------------------------------------------------------------------}

data ClientEnv = ClientEnv
  { _ceActiveWallet :: ActiveWallet
  , _cePassPhrase   :: PassPhrase
  , _ceAccountId    :: HdAccountId
  , _ceSigners      :: (Address -> Maybe SafeSigner)
  }

makeLenses ''ClientEnv

type ClientMode m = ReaderT ClientEnv (ExceptT NewTransactionError m)

instance MonadIO m => HasTxFeePolicy (ClientMode m) where
    getTxFeePolicy = do
        env <- ask
        let activeWallet = env ^. ceActiveWallet
        lift . liftIO $ Node.getFeePolicy (walletPassive activeWallet ^. walletNode)

instance MonadIO m => MonadAddresses (ClientMode m) where
    type AddrData (ClientMode m)  = ()
    getNewAddress _ _ _ = createNewAddress
    getFakeChangeAddress _ _ = createNewAddress

createNewAddress :: MonadIO m => ClientMode m Address
createNewAddress = do
    env <- ask
    let passiveWallet = walletPassive $ env ^. ceActiveWallet
    let spendingPassword = env ^. cePassPhrase
    let srcAccountId = env ^. ceAccountId
    lift $ withExceptT NewTransactionErrorCreateAddressFailed $ ExceptT $ liftIO $
        Kernel.createAddress spendingPassword
                            (AccountIdHdRnd srcAccountId) $
                            passiveWallet

runClientMode :: 
       ActiveWallet 
    -> PassPhrase 
    -> HdAccountId 
    -> (Address -> Maybe SafeSigner)
    -> ClientMode m a 
    -> ExceptT NewTransactionError m a
runClientMode activeWallet spendingPassword accountId hdwSigners action = do
    let env = ClientEnv { _ceActiveWallet = activeWallet
                        , _cePassPhrase   = spendingPassword
                        , _ceAccountId    = accountId
                        , _ceSigners      = hdwSigners
                        }
    runReaderT action env

clientCreateMTx
    :: MonadIO m
    => Genesis.Config
    -> NonEmpty TxOutAux
    -> Utxo
    -> ClientMode m (TxAux, NonEmpty TxOut)
clientCreateMTx genesisConfig outputs utxo = do
    env <- ask
    let hdwSigners = env ^. ceSigners
    resultE <- createMTx 
               genesisConfig 
               (PendingAddresses Set.empty) 
               OptimizeForSecurity 
               utxo 
               hdwSigners
               outputs 
               ()
    case resultE of 
        Left e       -> throwError $ NewTransactionClientError e
        Right result -> return result 

clientCreateGDIssuranceTx
    :: MonadIO m
    => Genesis.Config
    -> GoldDollar
    -> ByteString
    -> Utxo
    -> ClientMode m (TxAux, NonEmpty TxOut)
clientCreateGDIssuranceTx genesisConfig issuedGDs proof utxo = do
    env <- ask
    let hdwSigners = env ^. ceSigners
    resultE <- createGDIssuanceTx 
               genesisConfig 
               (PendingAddresses Set.empty) 
               OptimizeForSecurity 
               utxo 
               hdwSigners 
               issuedGDs 
               proof 
               ()
    case resultE of 
        Left e       -> throwError $ NewTransactionClientError e
        Right result -> return result 

  clientEstimateTxFee
--     :: MonadIO m
      = > Genesis . Config
      - > 
      - > NonEmpty TxOutAux
      - > ClientMode m TxFee
  clientEstimateTxFee genesisConfig utxo outputs = do
      resultE < - estimateTxFee genesisConfig ( PendingAddresses Set.empty ) OptimizeForSecurity utxo outputs
--     case resultE of 
          Left e        - > throwError $ NewTransactionClientError e
--         Right result -> return result 

{-------------------------------------------------------------------------------
  Generating payments and estimating fees
-------------------------------------------------------------------------------}
  TODO xl delete this
data NumberOfMissingUtxos = NumberOfMissingUtxos Int

instance Buildable NumberOfMissingUtxos where
    build (NumberOfMissingUtxos number) =
        bprint ("NumberOfMissingUtxos " % build) number

instance Arbitrary NumberOfMissingUtxos where
    arbitrary = oneof [ NumberOfMissingUtxos <$> arbitrary
                      ]

data NewTransactionError =
    NewTransactionUnknownAccount UnknownHdAccount
  | NewTransactionUnknownAddress UnknownHdAddress
--   | NewTransactionErrorCoinSelectionFailed CoinSelHardErr
  | NewTransactionErrorCreateAddressFailed Kernel.CreateAddressError
  | NewTransactionErrorSignTxFailed SignTransactionError
  | NewTransactionInvalidTxIn
  | NewTransactionNotEnoughUtxoFragmentation NumberOfMissingUtxos
  | NewTransactionClientError TxError
  | NewTransactionNotSupport

instance Buildable NewTransactionError where
    build (NewTransactionUnknownAccount err) =
        bprint ("NewTransactionUnknownAccount " % build) err
    build (NewTransactionUnknownAddress err) =
        bprint ("NewTransactionUnknownAddress " % build) err
    -- build (NewTransactionErrorCoinSelectionFailed err) =
    --     bprint ("NewTransactionErrorCoinSelectionFailed " % build) err
    build (NewTransactionErrorCreateAddressFailed err) =
        bprint ("NewTransactionErrorCreateAddressFailed " % build) err
    build (NewTransactionErrorSignTxFailed err) =
        bprint ("NewTransactionErrorSignTxFailed " % build) err
    build NewTransactionInvalidTxIn =
        bprint "NewTransactionInvalidTxIn"
    build (NewTransactionNotEnoughUtxoFragmentation err) =
        bprint ("NewTransactionNotEnoughUtxoFragmentation" % build) err
    build (NewTransactionClientError err) =
        bprint ("NewTransactionClientError" % build) err
    build NewTransactionNotSupport =
        bprint "NewTransactionNotSupport"


instance Arbitrary NewTransactionError where
    arbitrary = oneof [
        NewTransactionUnknownAccount <$> arbitrary
      , NewTransactionErrorCreateAddressFailed <$> arbitrary
      , NewTransactionErrorSignTxFailed <$> arbitrary
      , pure NewTransactionInvalidTxIn
      , NewTransactionNotEnoughUtxoFragmentation <$> arbitrary
      ]

data PaymentError = PaymentNewTransactionError NewTransactionError
                  | PaymentNewPendingError NewPendingError
                  | PaymentSubmissionMaxAttemptsReached
                  -- ^ When trying to send the newly-created transaction via
                  -- 'newPending' and the submission layer, we hit the number
                  -- of retries/max time allocated for the operation.
                  | PaymentNoHdAddressForSrcAddress HD.UnknownHdAddress
                  -- ^ When we don't have HD-address corresponding to source
                  -- address of transaction.

instance Buildable PaymentError where
    build (PaymentNewTransactionError txErr) =
        bprint ("PaymentNewTransactionError " % build) txErr
    build (PaymentNewPendingError npe) =
        bprint ("PaymentNewPendingError " % build) npe
    build PaymentSubmissionMaxAttemptsReached =
        bprint "PaymentSubmissionMaxAttemptsReached"
    build (PaymentNoHdAddressForSrcAddress addrErr) =
        bprint ("PaymentNoHdAddressForSrcAddress" % build) addrErr

-- | Workhorse kernel function to perform a payment.
pay :: Genesis.Config
    -> ActiveWallet
    -> PassPhrase
    -> HdAccountId
    -- ^ The source HD Account from where the payment was originated
    -> NonEmpty (Address, CoinPair)
    -- ^ The payees
    -> IO (Either PaymentError (Tx, TxMeta))
pay genesisConfig activeWallet spendingPassword accountId payees = do
    newTransactionAndSubmit activeWallet accountId $
        newPayment genesisConfig activeWallet spendingPassword accountId payees

-- | Workhorse kernel function to perform a payment.
issue :: Genesis.Config
      -> ActiveWallet
      -> PassPhrase
      -- -> CoinSelectionOptions
      -> HdAccountId
      -- ^ The source HD Account from where the payment was originated
      -> GoldDollar
      -- ^ GD to issued.
      -> ByteString
  ^ proof of issurance .
      -> IO (Either PaymentError (Tx, TxMeta))
issue genesisConfig activeWallet spendingPassword accountId issuedGDs proof = do
    newTransactionAndSubmit activeWallet accountId $
        newIssurance genesisConfig activeWallet spendingPassword accountId issuedGDs proof

-- | Workhorse kernel function to perform a transaction. It includes logic to
  stop trying to perform a payment if the payment would take more than 30
  seconds , as well as internally retrying up to 5 times to propagate the
-- transaction via 'newPending'.
newTransactionAndSubmit 
    :: ActiveWallet
    -> HdAccountId
    -- ^ The source HD Account from where the payment was originated
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
    -- ^ new transaction action
    -> IO (Either PaymentError (Tx, TxMeta))
newTransactionAndSubmit activeWallet accountId action = do
    retrying retryPolicy shouldRetry $ \rs -> do
        res <- action
        case res of
             Left e      -> return (Left $ PaymentNewTransactionError e)
             Right (txAux, partialMeta, _utxo) -> do
                 succeeded <- newPending activeWallet accountId txAux partialMeta
                 case succeeded of
                      Left e   -> do
                          -- If the next retry would bring us to the
                          -- end of our allowed retries, we fail with
                          -- a proper error
                          retriesLeft <- applyPolicy retryPolicy rs
                          return . Left $ case retriesLeft of
                               Nothing ->
                                   PaymentSubmissionMaxAttemptsReached
                               Just _  ->
                                   PaymentNewPendingError e
                      Right meta -> return $ Right (taTx $ txAux, meta)

-- See <-backoff-and-jitter>
retryPolicy :: RetryPolicyM IO
retryPolicy = fullJitterBackoff 5000000 <> limitRetries 6

-- If this is a hard coin selection error we cannot recover, stop
-- retrying. If we get a 'Tx' as output, stop retrying immediately.
shouldRetry :: RetryStatus -> Either PaymentError a -> IO Bool
shouldRetry _ (Right _)                             = return False
shouldRetry _ (Left (PaymentNewTransactionError _)) = return False
shouldRetry _ _                                     = return True

-- | Submits externally-signed transaction to the blockchain.
-- The result of this function is equal to the result of 'pay' function.
submitSignedTx
    :: ActiveWallet
    -> Tx
    -> NonEmpty (Address, Signature TxSigData, PublicKey)
    -> IO (Either PaymentError (Tx, TxMeta))
submitSignedTx aw@ActiveWallet{..} tx srcAddrsWithProofs =
    retrying retryPolicy shouldRetry $ \rs -> do
        res <- runExceptT $ do
            -- STEP 0: get wallet snapshot.
            snapshot <- liftIO $ getWalletSnapshot walletPassive
  STEP 1 : create witnesses .
  Since we already received inputs signatures with corresponding derived PKs ,
            -- just form witnesses from them.
            let witnesses = V.fromList . NonEmpty.toList $ flip NonEmpty.map srcAddrsWithProofs $
                    \(_srcAddr, txSig, derivedPK) -> PkWitness derivedPK txSig

  STEP 2 : make ' TxAux ' .
            let txAux = TxAux tx (witnesses, empty)

  STEP 3 : Compute metadata
            let txId = hash tx
            -- Currently it's assumed that all source addresses belong to 'the same/ account,
  so we can just take the first source address to find our ' HdAccountId ' .
            let (firstSrcAddress, _, _) = NonEmpty.head srcAddrsWithProofs
            firstSrcHdAddress <- withExceptT PaymentNoHdAddressForSrcAddress $ exceptT $
                lookupCardanoAddress snapshot firstSrcAddress
            let (HD.HdAddress (HD.HdAddressId srcAccountId _) _) = firstSrcHdAddress
            -- We use `getCreationTimestamp` provided by the `NodeStateAdaptor`
  to compute the createdAt timestamp for ` TxMeta ` .
            txMetaCreatedAt_ <- liftIO $ Node.getCreationTimestamp (walletPassive ^. walletNode)

  STEP 4 : Get available 
            utxo <- withExceptT (PaymentNewTransactionError . NewTransactionUnknownAccount) $ exceptT $
                currentAvailableUtxo snapshot srcAccountId

            let inputs = NonEmpty.toList $ _txInputs tx
                maybeInputsWithCoins = map (collectInputCoins utxo) inputs
                inputsWithCoins = NonEmpty.fromList $ catMaybes maybeInputsWithCoins
            -- If 'tx' is valid (i.e. contains correct inputs), we already have
            -- all inputs with corresponding coins.
            let numberOfValidInputs = NonEmpty.length inputsWithCoins
                numberOfAllInputs = length maybeInputsWithCoins
            if numberOfValidInputs /= numberOfAllInputs then
                -- Something is wrong with inputs, this 'tx' should be rejected.
                ExceptT $ return $ Left $ PaymentNewTransactionError NewTransactionInvalidTxIn
            else do
                -- We have to calculate the sum of input coins.
                let spentInputCoins = paymentAmount . NonEmpty.toList $ (toaOut . snd <$> inputsWithCoins)

  STEP 5 : form meta - data .
                partialMeta <- liftIO $ createNewMeta srcAccountId
                                                      txId
                                                      txMetaCreatedAt_
                                                      inputsWithCoins
                                                      (_txOutputs tx)
                                                      spentInputCoins
  STEP 6 : our new pending tx .
                withExceptT PaymentNewPendingError $ ExceptT $
                    newPending aw srcAccountId txAux partialMeta
        case res of
            Left e -> do
                -- If the next retry would bring us to the end of our allowed retries,
                -- we fail with a proper error
                retriesLeft <- applyPolicy retryPolicy rs
                return $ case retriesLeft of
                    Nothing -> Left PaymentSubmissionMaxAttemptsReached
                    Just _  -> Left e
            Right meta ->
                return $ Right (tx, meta)
  where
  If  is valid , we definitely know that .
    collectInputCoins :: Utxo
                      -> TxIn
                      -> Maybe (TxIn, TxOutAux)
    collectInputCoins utxo txInput = case Map.lookup txInput utxo of
        Nothing       -> Nothing
        Just txOutput -> Just (txInput, txOutput)

-- | new transaction for payment
newPayment
    :: Genesis.Config
    -> ActiveWallet
    -> PassPhrase
    -- ^ The spending password.
    -> HdAccountId
    -- ^ The source HD account from where the payment should originate.
    -> NonEmpty (Address, CoinPair)
    -- ^ The payees.
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
newPayment genesisConfig aw spendingPassword accountId payees = do
    let outputs = NonEmpty.fromList $ concatMap toTxOuts payees
    newTransaction aw spendingPassword accountId $
        clientCreateMTx genesisConfig outputs 

  | new transaction for issurance
newIssurance
    :: Genesis.Config
    -> ActiveWallet
    -> PassPhrase
    -- ^ The spending password.
    -> HdAccountId
    -- ^ The source HD account from where the payment should originate.
    -> GoldDollar
    -- ^ GD to issued.
    -> ByteString
  ^ proof of issurance .
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
newIssurance genesisConfig aw spendingPassword accountId issuedGDs proof = do
    newTransaction aw spendingPassword accountId $
        clientCreateGDIssuranceTx genesisConfig issuedGDs proof

  | low - level function , Creates a new ' TxAux ' and corresponding ' TxMeta ' ,
-- without submitting it to the network.
newTransaction
    :: ActiveWallet
    -> PassPhrase
    -- ^ The spending password.
    -> HdAccountId
    -- ^ The source HD account from where the payment should originate.
    -> (Utxo -> ClientMode IO (TxAux, NonEmpty TxOut))
    -- ^ The action.
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
newTransaction aw@ActiveWallet{..} spendingPassword accountId clientAction = do
    let pm = walletPassive ^. Internal.walletProtocolMagic
        nm = makeNetworkMagic pm

    db <- getWalletSnapshot walletPassive
    mbEsk <- Keystore.lookup
               nm
               (WalletIdHdRnd $ accountId ^. hdAccountIdParent)
               (walletPassive ^. Internal.walletKeystore)

    let getSigner :: Address -> Maybe SafeSigner
        getSigner addr = 
            let signerE = mkSigner nm spendingPassword mbEsk db addr
            in case signerE of
                Left _       -> Nothing
                Right signer -> Just signer

    runExceptT $ do
        availableUtxo <- withExceptT NewTransactionUnknownAccount $ exceptT $
                         currentAvailableUtxo db accountId

        (txAux, inputOuts) <- runClientMode aw spendingPassword accountId getSigner $
                              clientAction availableUtxo
       
        txMetaCreatedAt_  <- liftIO $ Node.getCreationTimestamp (walletPassive ^. walletNode)
        let txId = hash $ taTx txAux
            txInputs = _txInputs $ taTx txAux
            txOutputs = _txOutputs $ taTx txAux 
            spentInputs = paymentAmount $ NonEmpty.toList inputOuts
            inputOutsAux = map TxOutAux inputOuts
        partialMeta <- liftIO $ 
            createNewMeta accountId txId txMetaCreatedAt_ (NonEmpty.zip txInputs inputOutsAux) txOutputs spentInputs
        return (txAux, partialMeta, availableUtxo)       

toTxOuts :: (Address, CoinPair) -> [TxOutAux]
toTxOuts (addr, (coins, gds)) = 
    let out   | coins == (mkCoin 0)     = []
              | otherwise               = [TxOut addr coins]
        gdOut | gds == (mkGoldDollar 0) = []
              | otherwise               = [TxOutGD addr gds]
    in map TxOutAux $ out <> gdOut

-- | This is called when we create a new Pending Transaction.
-- This actually returns a function because we don`t know yet our outputs.
createNewMeta :: HdAccountId -> TxId -> Core.Timestamp -> NonEmpty (TxIn, TxOutAux) -> [TxOut] -> CoinPair -> IO PartialTxMeta
createNewMeta hdId txId time inp out spentInputs = do
  this partially applied function indicates the lack of all TxMeta at this stage .
    return $ metaForNewTx time hdId txId inp out spentInputs

metaForNewTx  :: Core.Timestamp -> HdAccountId -> TxId -> NonEmpty (TxIn, TxOutAux) -> [TxOut] -> CoinPair -> CoinPair -> TxMeta
metaForNewTx time accountId txId inputs outputs (spentCoins, spentGDs) (gainedCoins, gainedGDs) =
    TxMeta {
          _txMetaId = txId
        , _txMetaAmount = absCoin spentCoins gainedCoins
        , _txMetaGDAmount = absGoldDollar spentGDs gainedGDs
        , _txMetaInputs = inputsForMeta
        , _txMetaOutputs = aux <$> outputs
        , _txMetaCreationAt = time
        , _txMetaIsOutgoing = gainedCoins < spentCoins -- it`s outgoing if our inputs spent are more than the new utxo.
        , _txMetaIsGDOutgoing = gainedGDs < spentGDs
        , _txMetaWalletId = _fromDb $ getHdRootId (accountId ^. hdAccountIdParent)
        , _txMetaAccountIx = getHdAccountIx $ accountId ^. hdAccountIdIx
    }
  where
    inputsForMeta = toInput <$> inputs
    aux txOut = (txOutAddress txOut, toCoinPair' txOut)
    toInput (TxInUtxo txid index, txOutAux) = 
            let (addr, coins) = aux $ toaOut txOutAux
            in (txid, index, addr, coins)

  | Different wraper for @metaForNewTx@ mainly for testing Only NewPending Transactions .
toMeta :: Core.Timestamp -> HdAccountId -> RawResolvedTx -> PartialTxMeta
toMeta time accountId UnsafeRawResolvedTx{..} outCoin =
    let txId = hash . taTx $ rawResolvedTx
        (txIn :: NonEmpty TxIn) = _txInputs $ taTx rawResolvedTx
        (inputsRes :: NonEmpty TxOutAux) = rawResolvedTxInputs
        spentInputCoins = paymentAmount . NonEmpty.toList $ toaOut <$> inputsRes
        inputs = NonEmpty.zip txIn inputsRes
        txOut = _txOutputs $ taTx rawResolvedTx
    in metaForNewTx time accountId txId inputs txOut spentInputCoins outCoin

{-------------------------------------------------------------------------------
  Estimating fees
-------------------------------------------------------------------------------}

data EstimateFeesError = EstFeesTxCreationFailed NewTransactionError

instance Buildable EstimateFeesError where
    build (EstFeesTxCreationFailed newTxErr) =
        bprint ("EstFeesTxCreationFailed " % build) newTxErr

instance Arbitrary EstimateFeesError where
    arbitrary = EstFeesTxCreationFailed <$> arbitrary

estimateFees 
    :: Genesis.Config
    -> ActiveWallet
    -> HdAccountId
    -- ^ The source HD Account from where the payment should originate
    -> NonEmpty (Address, CoinPair)
    -- ^ The payees
    -> IO (Either EstimateFeesError Coin)
estimateFees _ _ _ _ = do
  res < - newUnsignedTransaction activeWallet options accountId payees
    -- case res of
    --      Left e  -> return . Left . EstFeesTxCreationFailed $ e
       Right ( _ db , _ tx , fees , _ originalUtxo ) - > do
    --          -- sanity check of fees is done.
    --          return $ Right fees
    return . Left $ EstFeesTxCreationFailed  NewTransactionNotSupport

-- | Errors during transaction signing
--
-- NOTE: Under normal circumstances these should /never/ be thrown. If they
-- do, it most likely indicates a bug.
data SignTransactionError =
    SignTransactionMissingKey Address
  | SignTransactionErrorUnknownAddress Address
  | SignTransactionErrorNotOwned Address

instance Buildable SignTransactionError where
    build (SignTransactionMissingKey addr) =
        bprint ("SignTransactionMissingKey " % build) addr
    build (SignTransactionErrorUnknownAddress addr) =
        bprint ("SignTransactionErrorUnknownAddress " % build) addr
    build (SignTransactionErrorNotOwned addr) =
        bprint ("SignTransactionErrorNotOwned " % build) addr

-- in order to be able to generate an Arbitrary address we'd need to use
-- the cardano-sl-core test package
instance Arbitrary SignTransactionError where
    arbitrary = oneof
        [ SignTransactionMissingKey <$> arbitrary
        , SignTransactionErrorUnknownAddress <$> arbitrary
        , SignTransactionErrorNotOwned <$> arbitrary
        ]

mkSigner :: NetworkMagic
         -> PassPhrase
         -> Maybe EncryptedSecretKey
         -> DB
         -> Address
         -> Either SignTransactionError SafeSigner
mkSigner _ _ Nothing _ addr = Left (SignTransactionMissingKey addr)
mkSigner nm spendingPassword (Just esk) snapshot addr =
    case Getters.lookupCardanoAddress snapshot addr of
        Left _ -> Left (SignTransactionErrorUnknownAddress addr)
        Right hdAddr ->
            let addressIndex = hdAddr ^. HD.hdAddressId
                                       . HD.hdAddressIdIx
                                       . to HD.getHdAddressIx
                accountIndex = hdAddr ^. HD.hdAddressId
                                       . HD.hdAddressIdParent
                                       . HD.hdAccountIdIx
                                       . to HD.getHdAccountIx
                res = Core.deriveLvl2KeyPair nm
                                             (Core.IsBootstrapEraAddr True)
                                             (ShouldCheckPassphrase False)
                                             spendingPassword
                                             esk
                                             accountIndex
                                             addressIndex
            -- eks address fix - we need to use the esk as returned
            -- from Core.deriveLvl2KeyPair rather than rely on the
            -- one from encrypted secret key delivered to mkSigner
            in case res of
                 Just (a, eskAddr) | a == addr ->
                     Right (SafeSigner eskAddr spendingPassword)
                 _otherwise              ->
                     Left (SignTransactionErrorNotOwned addr)

-- | Build a transaction

-- | Construct a standard transaction
--
-- " Standard " here refers to the fact that we do not deal with redemption,
-- multisignature transactions, etc.
mkStdTx :: Monad m
        => ProtocolMagic
        -> (forall a. [a] -> m [a])
        -- ^ Shuffle function
        -> (Core.Address -> Either e SafeSigner)
        -- ^ Signer for each input of the transaction
        -> NonEmpty (Core.TxIn, Core.TxOutAux)
        -- ^ Selected inputs
        -> [Core.TxOutAux]
        -- ^ Selected outputs
        -> [Core.TxOutAux]
        -- ^ Change outputs
        -> m (Either e Core.TxAux)
mkStdTx pm shuffleFun hdwSigners inps outs change = do
    allOuts <- shuffleFun $ outs <> change
    return $ makeMPubKeyTxAddrs pm hdwSigners (fmap repack inps) allOuts
    where
         -- | Repacks a utxo-derived tuple into a format suitable for
         -- 'TxOwnedInputs'.
        repack :: (Core.TxIn, Core.TxOutAux) -> (Core.TxOut, Core.TxIn)
        repack (txIn, aux) = (Core.toaOut aux, txIn)

# LANGUAGE RankNTypes                 #
 * Errors
 * Internal & testing use only low-level APIs
, estimateTxFee)
---------------------------------------------------------------------------- 
----------------------------------------------------------------------------
-----------------------------------------------------------------------------}
     :: MonadIO m
     case resultE of 
         Right result -> return result 
------------------------------------------------------------------------------
  Generating payments and estimating fees
------------------------------------------------------------------------------
   | NewTransactionErrorCoinSelectionFailed CoinSelHardErr
 build (NewTransactionErrorCoinSelectionFailed err) =
     bprint ("NewTransactionErrorCoinSelectionFailed " % build) err
 ^ When trying to send the newly-created transaction via
 'newPending' and the submission layer, we hit the number
 of retries/max time allocated for the operation.
 ^ When we don't have HD-address corresponding to source
 address of transaction.
 | Workhorse kernel function to perform a payment.
 ^ The source HD Account from where the payment was originated
 ^ The payees
 | Workhorse kernel function to perform a payment.
 -> CoinSelectionOptions
 ^ The source HD Account from where the payment was originated
 ^ GD to issued.
 | Workhorse kernel function to perform a transaction. It includes logic to
 transaction via 'newPending'.
 ^ The source HD Account from where the payment was originated
 ^ new transaction action
 If the next retry would bring us to the
 end of our allowed retries, we fail with
 a proper error
 See <-backoff-and-jitter>
 If this is a hard coin selection error we cannot recover, stop
 retrying. If we get a 'Tx' as output, stop retrying immediately.
 | Submits externally-signed transaction to the blockchain.
 The result of this function is equal to the result of 'pay' function.
 STEP 0: get wallet snapshot.
 just form witnesses from them.
 Currently it's assumed that all source addresses belong to 'the same/ account,
 We use `getCreationTimestamp` provided by the `NodeStateAdaptor`
 If 'tx' is valid (i.e. contains correct inputs), we already have
 all inputs with corresponding coins.
 Something is wrong with inputs, this 'tx' should be rejected.
 We have to calculate the sum of input coins.
 If the next retry would bring us to the end of our allowed retries,
 we fail with a proper error
 | new transaction for payment
 ^ The spending password.
 ^ The source HD account from where the payment should originate.
 ^ The payees.
 ^ The spending password.
 ^ The source HD account from where the payment should originate.
 ^ GD to issued.
 without submitting it to the network.
 ^ The spending password.
 ^ The source HD account from where the payment should originate.
 ^ The action.
 | This is called when we create a new Pending Transaction.
 This actually returns a function because we don`t know yet our outputs.
 it`s outgoing if our inputs spent are more than the new utxo.
------------------------------------------------------------------------------
  Estimating fees
------------------------------------------------------------------------------
 ^ The source HD Account from where the payment should originate
 ^ The payees
 case res of
      Left e  -> return . Left . EstFeesTxCreationFailed $ e
          -- sanity check of fees is done.
          return $ Right fees
 | Errors during transaction signing

 NOTE: Under normal circumstances these should /never/ be thrown. If they
 do, it most likely indicates a bug.
 in order to be able to generate an Arbitrary address we'd need to use
 the cardano-sl-core test package
 eks address fix - we need to use the esk as returned
 from Core.deriveLvl2KeyPair rather than rely on the
 one from encrypted secret key delivered to mkSigner
 | Build a transaction
 | Construct a standard transaction

 " Standard " here refers to the fact that we do not deal with redemption,
 multisignature transactions, etc.
 ^ Shuffle function
 ^ Signer for each input of the transaction
 ^ Selected inputs
 ^ Selected outputs
 ^ Change outputs
 | Repacks a utxo-derived tuple into a format suitable for
 'TxOwnedInputs'.
# LANGUAGE GeneralizedNewtypeDeriving #

module Cardano.Wallet.Kernel.Transactions (
      pay
    , issue
    , estimateFees
    , NewTransactionError(..)
    , SignTransactionError(..)
    , PaymentError(..)
    , EstimateFeesError(..)
    , NumberOfMissingUtxos(..)
    , mkStdTx
    , submitSignedTx
    , newTransaction
    , toMeta
  ) where

import           Universum

import           Control.Lens (makeLenses, to)
import           Control.Monad.Except (MonadError (..), withExceptT)
import           Control.Retry (RetryPolicyM, RetryStatus, applyPolicy,
                     fullJitterBackoff, limitRetries, retrying)
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Vector as V
import           Test.QuickCheck (Arbitrary (..), oneof)

import           Formatting (bprint, build, (%))
import qualified Formatting.Buildable

import qualified Cardano.Wallet.Kernel.Addresses as Kernel
import           Cardano.Wallet.Kernel.DB.AcidState (DB, NewPendingError)
import           Cardano.Wallet.Kernel.DB.HdWallet
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
import           Cardano.Wallet.Kernel.DB.InDb
import           Cardano.Wallet.Kernel.DB.Read as Getters
import           Cardano.Wallet.Kernel.DB.TxMeta.Types
import           Cardano.Wallet.Kernel.Internal (ActiveWallet (..), walletNode)
import qualified Cardano.Wallet.Kernel.Internal as Internal
import qualified Cardano.Wallet.Kernel.Keystore as Keystore
import qualified Cardano.Wallet.Kernel.NodeStateAdaptor as Node
import           Cardano.Wallet.Kernel.Pending (PartialTxMeta, newPending)
import           Cardano.Wallet.Kernel.Read (getWalletSnapshot)
import           Cardano.Wallet.Kernel.Types (AccountId (..),
                     RawResolvedTx (..), WalletId (..))
import           Cardano.Wallet.Kernel.Util.Core
import           Cardano.Wallet.WalletLayer.Kernel.Conv (exceptT)
import           Pos.Chain.Genesis as Genesis (Config (..))
import           Pos.Chain.Txp (Tx (..), TxAux (..), TxId,
                     TxIn (..), TxInWitness (..), TxOut (..), TxOutAux (..),
                     TxSigData (..), Utxo)
import           Pos.Chain.Txp as Core (TxAux, TxIn, TxOut,
                     TxOutAux, toaOut, txOutAddress)
import           Pos.Client.Txp (HasTxFeePolicy (..), MonadAddresses (..), TxError, 
                     InputSelectionPolicy (..), PendingAddresses (..), 
import           Pos.Core (Address, Coin, GoldDollar, CoinPair, mkCoin, mkGoldDollar)
import qualified Pos.Core as Core
import           Pos.Core.NetworkMagic (NetworkMagic (..), makeNetworkMagic)
import           Pos.Crypto (EncryptedSecretKey, PassPhrase, ProtocolMagic,
                     PublicKey, SafeSigner (..), ShouldCheckPassphrase (..), 
                     Signature (..), hash,)

   Monad for call client module 
  Monad for call client module

data ClientEnv = ClientEnv
  { _ceActiveWallet :: ActiveWallet
  , _cePassPhrase   :: PassPhrase
  , _ceAccountId    :: HdAccountId
  , _ceSigners      :: (Address -> Maybe SafeSigner)
  }

makeLenses ''ClientEnv

type ClientMode m = ReaderT ClientEnv (ExceptT NewTransactionError m)

instance MonadIO m => HasTxFeePolicy (ClientMode m) where
    getTxFeePolicy = do
        env <- ask
        let activeWallet = env ^. ceActiveWallet
        lift . liftIO $ Node.getFeePolicy (walletPassive activeWallet ^. walletNode)

instance MonadIO m => MonadAddresses (ClientMode m) where
    type AddrData (ClientMode m)  = ()
    getNewAddress _ _ _ = createNewAddress
    getFakeChangeAddress _ _ = createNewAddress

createNewAddress :: MonadIO m => ClientMode m Address
createNewAddress = do
    env <- ask
    let passiveWallet = walletPassive $ env ^. ceActiveWallet
    let spendingPassword = env ^. cePassPhrase
    let srcAccountId = env ^. ceAccountId
    lift $ withExceptT NewTransactionErrorCreateAddressFailed $ ExceptT $ liftIO $
        Kernel.createAddress spendingPassword
                            (AccountIdHdRnd srcAccountId) $
                            passiveWallet

runClientMode :: 
       ActiveWallet 
    -> PassPhrase 
    -> HdAccountId 
    -> (Address -> Maybe SafeSigner)
    -> ClientMode m a 
    -> ExceptT NewTransactionError m a
runClientMode activeWallet spendingPassword accountId hdwSigners action = do
    let env = ClientEnv { _ceActiveWallet = activeWallet
                        , _cePassPhrase   = spendingPassword
                        , _ceAccountId    = accountId
                        , _ceSigners      = hdwSigners
                        }
    runReaderT action env

clientCreateMTx
    :: MonadIO m
    => Genesis.Config
    -> NonEmpty TxOutAux
    -> Utxo
    -> ClientMode m (TxAux, NonEmpty TxOut)
clientCreateMTx genesisConfig outputs utxo = do
    env <- ask
    let hdwSigners = env ^. ceSigners
    resultE <- createMTx 
               genesisConfig 
               (PendingAddresses Set.empty) 
               OptimizeForSecurity 
               utxo 
               hdwSigners
               outputs 
               ()
    case resultE of 
        Left e       -> throwError $ NewTransactionClientError e
        Right result -> return result 

clientCreateGDIssuranceTx
    :: MonadIO m
    => Genesis.Config
    -> GoldDollar
    -> ByteString
    -> Utxo
    -> ClientMode m (TxAux, NonEmpty TxOut)
clientCreateGDIssuranceTx genesisConfig issuedGDs proof utxo = do
    env <- ask
    let hdwSigners = env ^. ceSigners
    resultE <- createGDIssuanceTx 
               genesisConfig 
               (PendingAddresses Set.empty) 
               OptimizeForSecurity 
               utxo 
               hdwSigners 
               issuedGDs 
               proof 
               ()
    case resultE of 
        Left e       -> throwError $ NewTransactionClientError e
        Right result -> return result 

  clientEstimateTxFee
      = > Genesis . Config
      - > 
      - > NonEmpty TxOutAux
      - > ClientMode m TxFee
  clientEstimateTxFee genesisConfig utxo outputs = do
      resultE < - estimateTxFee genesisConfig ( PendingAddresses Set.empty ) OptimizeForSecurity utxo outputs
          Left e        - > throwError $ NewTransactionClientError e

  TODO xl delete this
data NumberOfMissingUtxos = NumberOfMissingUtxos Int

instance Buildable NumberOfMissingUtxos where
    build (NumberOfMissingUtxos number) =
        bprint ("NumberOfMissingUtxos " % build) number

instance Arbitrary NumberOfMissingUtxos where
    arbitrary = oneof [ NumberOfMissingUtxos <$> arbitrary
                      ]

data NewTransactionError =
    NewTransactionUnknownAccount UnknownHdAccount
  | NewTransactionUnknownAddress UnknownHdAddress
  | NewTransactionErrorCreateAddressFailed Kernel.CreateAddressError
  | NewTransactionErrorSignTxFailed SignTransactionError
  | NewTransactionInvalidTxIn
  | NewTransactionNotEnoughUtxoFragmentation NumberOfMissingUtxos
  | NewTransactionClientError TxError
  | NewTransactionNotSupport

instance Buildable NewTransactionError where
    build (NewTransactionUnknownAccount err) =
        bprint ("NewTransactionUnknownAccount " % build) err
    build (NewTransactionUnknownAddress err) =
        bprint ("NewTransactionUnknownAddress " % build) err
    build (NewTransactionErrorCreateAddressFailed err) =
        bprint ("NewTransactionErrorCreateAddressFailed " % build) err
    build (NewTransactionErrorSignTxFailed err) =
        bprint ("NewTransactionErrorSignTxFailed " % build) err
    build NewTransactionInvalidTxIn =
        bprint "NewTransactionInvalidTxIn"
    build (NewTransactionNotEnoughUtxoFragmentation err) =
        bprint ("NewTransactionNotEnoughUtxoFragmentation" % build) err
    build (NewTransactionClientError err) =
        bprint ("NewTransactionClientError" % build) err
    build NewTransactionNotSupport =
        bprint "NewTransactionNotSupport"


instance Arbitrary NewTransactionError where
    arbitrary = oneof [
        NewTransactionUnknownAccount <$> arbitrary
      , NewTransactionErrorCreateAddressFailed <$> arbitrary
      , NewTransactionErrorSignTxFailed <$> arbitrary
      , pure NewTransactionInvalidTxIn
      , NewTransactionNotEnoughUtxoFragmentation <$> arbitrary
      ]

data PaymentError = PaymentNewTransactionError NewTransactionError
                  | PaymentNewPendingError NewPendingError
                  | PaymentSubmissionMaxAttemptsReached
                  | PaymentNoHdAddressForSrcAddress HD.UnknownHdAddress

instance Buildable PaymentError where
    build (PaymentNewTransactionError txErr) =
        bprint ("PaymentNewTransactionError " % build) txErr
    build (PaymentNewPendingError npe) =
        bprint ("PaymentNewPendingError " % build) npe
    build PaymentSubmissionMaxAttemptsReached =
        bprint "PaymentSubmissionMaxAttemptsReached"
    build (PaymentNoHdAddressForSrcAddress addrErr) =
        bprint ("PaymentNoHdAddressForSrcAddress" % build) addrErr

pay :: Genesis.Config
    -> ActiveWallet
    -> PassPhrase
    -> HdAccountId
    -> NonEmpty (Address, CoinPair)
    -> IO (Either PaymentError (Tx, TxMeta))
pay genesisConfig activeWallet spendingPassword accountId payees = do
    newTransactionAndSubmit activeWallet accountId $
        newPayment genesisConfig activeWallet spendingPassword accountId payees

issue :: Genesis.Config
      -> ActiveWallet
      -> PassPhrase
      -> HdAccountId
      -> GoldDollar
      -> ByteString
  ^ proof of issurance .
      -> IO (Either PaymentError (Tx, TxMeta))
issue genesisConfig activeWallet spendingPassword accountId issuedGDs proof = do
    newTransactionAndSubmit activeWallet accountId $
        newIssurance genesisConfig activeWallet spendingPassword accountId issuedGDs proof

  stop trying to perform a payment if the payment would take more than 30
  seconds , as well as internally retrying up to 5 times to propagate the
newTransactionAndSubmit 
    :: ActiveWallet
    -> HdAccountId
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
    -> IO (Either PaymentError (Tx, TxMeta))
newTransactionAndSubmit activeWallet accountId action = do
    retrying retryPolicy shouldRetry $ \rs -> do
        res <- action
        case res of
             Left e      -> return (Left $ PaymentNewTransactionError e)
             Right (txAux, partialMeta, _utxo) -> do
                 succeeded <- newPending activeWallet accountId txAux partialMeta
                 case succeeded of
                      Left e   -> do
                          retriesLeft <- applyPolicy retryPolicy rs
                          return . Left $ case retriesLeft of
                               Nothing ->
                                   PaymentSubmissionMaxAttemptsReached
                               Just _  ->
                                   PaymentNewPendingError e
                      Right meta -> return $ Right (taTx $ txAux, meta)

retryPolicy :: RetryPolicyM IO
retryPolicy = fullJitterBackoff 5000000 <> limitRetries 6

shouldRetry :: RetryStatus -> Either PaymentError a -> IO Bool
shouldRetry _ (Right _)                             = return False
shouldRetry _ (Left (PaymentNewTransactionError _)) = return False
shouldRetry _ _                                     = return True

submitSignedTx
    :: ActiveWallet
    -> Tx
    -> NonEmpty (Address, Signature TxSigData, PublicKey)
    -> IO (Either PaymentError (Tx, TxMeta))
submitSignedTx aw@ActiveWallet{..} tx srcAddrsWithProofs =
    retrying retryPolicy shouldRetry $ \rs -> do
        res <- runExceptT $ do
            snapshot <- liftIO $ getWalletSnapshot walletPassive
  STEP 1 : create witnesses .
  Since we already received inputs signatures with corresponding derived PKs ,
            let witnesses = V.fromList . NonEmpty.toList $ flip NonEmpty.map srcAddrsWithProofs $
                    \(_srcAddr, txSig, derivedPK) -> PkWitness derivedPK txSig

  STEP 2 : make ' TxAux ' .
            let txAux = TxAux tx (witnesses, empty)

  STEP 3 : Compute metadata
            let txId = hash tx
  so we can just take the first source address to find our ' HdAccountId ' .
            let (firstSrcAddress, _, _) = NonEmpty.head srcAddrsWithProofs
            firstSrcHdAddress <- withExceptT PaymentNoHdAddressForSrcAddress $ exceptT $
                lookupCardanoAddress snapshot firstSrcAddress
            let (HD.HdAddress (HD.HdAddressId srcAccountId _) _) = firstSrcHdAddress
  to compute the createdAt timestamp for ` TxMeta ` .
            txMetaCreatedAt_ <- liftIO $ Node.getCreationTimestamp (walletPassive ^. walletNode)

  STEP 4 : Get available 
            utxo <- withExceptT (PaymentNewTransactionError . NewTransactionUnknownAccount) $ exceptT $
                currentAvailableUtxo snapshot srcAccountId

            let inputs = NonEmpty.toList $ _txInputs tx
                maybeInputsWithCoins = map (collectInputCoins utxo) inputs
                inputsWithCoins = NonEmpty.fromList $ catMaybes maybeInputsWithCoins
            let numberOfValidInputs = NonEmpty.length inputsWithCoins
                numberOfAllInputs = length maybeInputsWithCoins
            if numberOfValidInputs /= numberOfAllInputs then
                ExceptT $ return $ Left $ PaymentNewTransactionError NewTransactionInvalidTxIn
            else do
                let spentInputCoins = paymentAmount . NonEmpty.toList $ (toaOut . snd <$> inputsWithCoins)

  STEP 5 : form meta - data .
                partialMeta <- liftIO $ createNewMeta srcAccountId
                                                      txId
                                                      txMetaCreatedAt_
                                                      inputsWithCoins
                                                      (_txOutputs tx)
                                                      spentInputCoins
  STEP 6 : our new pending tx .
                withExceptT PaymentNewPendingError $ ExceptT $
                    newPending aw srcAccountId txAux partialMeta
        case res of
            Left e -> do
                retriesLeft <- applyPolicy retryPolicy rs
                return $ case retriesLeft of
                    Nothing -> Left PaymentSubmissionMaxAttemptsReached
                    Just _  -> Left e
            Right meta ->
                return $ Right (tx, meta)
  where
  If  is valid , we definitely know that .
    collectInputCoins :: Utxo
                      -> TxIn
                      -> Maybe (TxIn, TxOutAux)
    collectInputCoins utxo txInput = case Map.lookup txInput utxo of
        Nothing       -> Nothing
        Just txOutput -> Just (txInput, txOutput)

newPayment
    :: Genesis.Config
    -> ActiveWallet
    -> PassPhrase
    -> HdAccountId
    -> NonEmpty (Address, CoinPair)
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
newPayment genesisConfig aw spendingPassword accountId payees = do
    let outputs = NonEmpty.fromList $ concatMap toTxOuts payees
    newTransaction aw spendingPassword accountId $
        clientCreateMTx genesisConfig outputs 

  | new transaction for issurance
newIssurance
    :: Genesis.Config
    -> ActiveWallet
    -> PassPhrase
    -> HdAccountId
    -> GoldDollar
    -> ByteString
  ^ proof of issurance .
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
newIssurance genesisConfig aw spendingPassword accountId issuedGDs proof = do
    newTransaction aw spendingPassword accountId $
        clientCreateGDIssuranceTx genesisConfig issuedGDs proof

  | low - level function , Creates a new ' TxAux ' and corresponding ' TxMeta ' ,
newTransaction
    :: ActiveWallet
    -> PassPhrase
    -> HdAccountId
    -> (Utxo -> ClientMode IO (TxAux, NonEmpty TxOut))
    -> IO (Either NewTransactionError (TxAux, PartialTxMeta, Utxo))
newTransaction aw@ActiveWallet{..} spendingPassword accountId clientAction = do
    let pm = walletPassive ^. Internal.walletProtocolMagic
        nm = makeNetworkMagic pm

    db <- getWalletSnapshot walletPassive
    mbEsk <- Keystore.lookup
               nm
               (WalletIdHdRnd $ accountId ^. hdAccountIdParent)
               (walletPassive ^. Internal.walletKeystore)

    let getSigner :: Address -> Maybe SafeSigner
        getSigner addr = 
            let signerE = mkSigner nm spendingPassword mbEsk db addr
            in case signerE of
                Left _       -> Nothing
                Right signer -> Just signer

    runExceptT $ do
        availableUtxo <- withExceptT NewTransactionUnknownAccount $ exceptT $
                         currentAvailableUtxo db accountId

        (txAux, inputOuts) <- runClientMode aw spendingPassword accountId getSigner $
                              clientAction availableUtxo
       
        txMetaCreatedAt_  <- liftIO $ Node.getCreationTimestamp (walletPassive ^. walletNode)
        let txId = hash $ taTx txAux
            txInputs = _txInputs $ taTx txAux
            txOutputs = _txOutputs $ taTx txAux 
            spentInputs = paymentAmount $ NonEmpty.toList inputOuts
            inputOutsAux = map TxOutAux inputOuts
        partialMeta <- liftIO $ 
            createNewMeta accountId txId txMetaCreatedAt_ (NonEmpty.zip txInputs inputOutsAux) txOutputs spentInputs
        return (txAux, partialMeta, availableUtxo)       

toTxOuts :: (Address, CoinPair) -> [TxOutAux]
toTxOuts (addr, (coins, gds)) = 
    let out   | coins == (mkCoin 0)     = []
              | otherwise               = [TxOut addr coins]
        gdOut | gds == (mkGoldDollar 0) = []
              | otherwise               = [TxOutGD addr gds]
    in map TxOutAux $ out <> gdOut

createNewMeta :: HdAccountId -> TxId -> Core.Timestamp -> NonEmpty (TxIn, TxOutAux) -> [TxOut] -> CoinPair -> IO PartialTxMeta
createNewMeta hdId txId time inp out spentInputs = do
  this partially applied function indicates the lack of all TxMeta at this stage .
    return $ metaForNewTx time hdId txId inp out spentInputs

metaForNewTx  :: Core.Timestamp -> HdAccountId -> TxId -> NonEmpty (TxIn, TxOutAux) -> [TxOut] -> CoinPair -> CoinPair -> TxMeta
metaForNewTx time accountId txId inputs outputs (spentCoins, spentGDs) (gainedCoins, gainedGDs) =
    TxMeta {
          _txMetaId = txId
        , _txMetaAmount = absCoin spentCoins gainedCoins
        , _txMetaGDAmount = absGoldDollar spentGDs gainedGDs
        , _txMetaInputs = inputsForMeta
        , _txMetaOutputs = aux <$> outputs
        , _txMetaCreationAt = time
        , _txMetaIsGDOutgoing = gainedGDs < spentGDs
        , _txMetaWalletId = _fromDb $ getHdRootId (accountId ^. hdAccountIdParent)
        , _txMetaAccountIx = getHdAccountIx $ accountId ^. hdAccountIdIx
    }
  where
    inputsForMeta = toInput <$> inputs
    aux txOut = (txOutAddress txOut, toCoinPair' txOut)
    toInput (TxInUtxo txid index, txOutAux) = 
            let (addr, coins) = aux $ toaOut txOutAux
            in (txid, index, addr, coins)

  | Different wraper for @metaForNewTx@ mainly for testing Only NewPending Transactions .
toMeta :: Core.Timestamp -> HdAccountId -> RawResolvedTx -> PartialTxMeta
toMeta time accountId UnsafeRawResolvedTx{..} outCoin =
    let txId = hash . taTx $ rawResolvedTx
        (txIn :: NonEmpty TxIn) = _txInputs $ taTx rawResolvedTx
        (inputsRes :: NonEmpty TxOutAux) = rawResolvedTxInputs
        spentInputCoins = paymentAmount . NonEmpty.toList $ toaOut <$> inputsRes
        inputs = NonEmpty.zip txIn inputsRes
        txOut = _txOutputs $ taTx rawResolvedTx
    in metaForNewTx time accountId txId inputs txOut spentInputCoins outCoin


data EstimateFeesError = EstFeesTxCreationFailed NewTransactionError

instance Buildable EstimateFeesError where
    build (EstFeesTxCreationFailed newTxErr) =
        bprint ("EstFeesTxCreationFailed " % build) newTxErr

instance Arbitrary EstimateFeesError where
    arbitrary = EstFeesTxCreationFailed <$> arbitrary

estimateFees 
    :: Genesis.Config
    -> ActiveWallet
    -> HdAccountId
    -> NonEmpty (Address, CoinPair)
    -> IO (Either EstimateFeesError Coin)
estimateFees _ _ _ _ = do
  res < - newUnsignedTransaction activeWallet options accountId payees
       Right ( _ db , _ tx , fees , _ originalUtxo ) - > do
    return . Left $ EstFeesTxCreationFailed  NewTransactionNotSupport

data SignTransactionError =
    SignTransactionMissingKey Address
  | SignTransactionErrorUnknownAddress Address
  | SignTransactionErrorNotOwned Address

instance Buildable SignTransactionError where
    build (SignTransactionMissingKey addr) =
        bprint ("SignTransactionMissingKey " % build) addr
    build (SignTransactionErrorUnknownAddress addr) =
        bprint ("SignTransactionErrorUnknownAddress " % build) addr
    build (SignTransactionErrorNotOwned addr) =
        bprint ("SignTransactionErrorNotOwned " % build) addr

instance Arbitrary SignTransactionError where
    arbitrary = oneof
        [ SignTransactionMissingKey <$> arbitrary
        , SignTransactionErrorUnknownAddress <$> arbitrary
        , SignTransactionErrorNotOwned <$> arbitrary
        ]

mkSigner :: NetworkMagic
         -> PassPhrase
         -> Maybe EncryptedSecretKey
         -> DB
         -> Address
         -> Either SignTransactionError SafeSigner
mkSigner _ _ Nothing _ addr = Left (SignTransactionMissingKey addr)
mkSigner nm spendingPassword (Just esk) snapshot addr =
    case Getters.lookupCardanoAddress snapshot addr of
        Left _ -> Left (SignTransactionErrorUnknownAddress addr)
        Right hdAddr ->
            let addressIndex = hdAddr ^. HD.hdAddressId
                                       . HD.hdAddressIdIx
                                       . to HD.getHdAddressIx
                accountIndex = hdAddr ^. HD.hdAddressId
                                       . HD.hdAddressIdParent
                                       . HD.hdAccountIdIx
                                       . to HD.getHdAccountIx
                res = Core.deriveLvl2KeyPair nm
                                             (Core.IsBootstrapEraAddr True)
                                             (ShouldCheckPassphrase False)
                                             spendingPassword
                                             esk
                                             accountIndex
                                             addressIndex
            in case res of
                 Just (a, eskAddr) | a == addr ->
                     Right (SafeSigner eskAddr spendingPassword)
                 _otherwise              ->
                     Left (SignTransactionErrorNotOwned addr)


mkStdTx :: Monad m
        => ProtocolMagic
        -> (forall a. [a] -> m [a])
        -> (Core.Address -> Either e SafeSigner)
        -> NonEmpty (Core.TxIn, Core.TxOutAux)
        -> [Core.TxOutAux]
        -> [Core.TxOutAux]
        -> m (Either e Core.TxAux)
mkStdTx pm shuffleFun hdwSigners inps outs change = do
    allOuts <- shuffleFun $ outs <> change
    return $ makeMPubKeyTxAddrs pm hdwSigners (fmap repack inps) allOuts
    where
        repack :: (Core.TxIn, Core.TxOutAux) -> (Core.TxOut, Core.TxIn)
        repack (txIn, aux) = (Core.toaOut aux, txIn)
