module Data.Blockchain.Builder
( CreateTransactionException(..)
, createSimpleTransaction
) where
import Control.Monad (unless, when)
import Control.Monad.Trans.Class (lift)
import qualified Control.Error.Util as Error
import qualified Control.Monad.Except as Except
import qualified Data.HashMap.Strict as H
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Word as Word
import qualified Data.Blockchain.Crypto as Crypto
import qualified Data.Blockchain.Types as Blockchain
data CreateTransactionException
= SourceAddressEmpty
| SourceAddressInsufficientFunds
| InvalidPrivateKey
deriving (Eq, Show)
createSimpleTransaction
:: Crypto.KeyPair
-> Crypto.PublicKey
-> Word.Word
-> Word.Word
-> Blockchain.Blockchain Blockchain.Validated
-> IO (Either CreateTransactionException Blockchain.Transaction)
createSimpleTransaction (Crypto.KeyPair srcPubKey srcPrivKey) targetPubKey value fee blockchain = Except.runExceptT $ do
let unspentTransactionOutputs = Blockchain.unspentTransactionOutputs blockchain
txOutPairs <- Error.failWith SourceAddressEmpty $ H.lookup srcPubKey unspentTransactionOutputs
let txOuts = snd <$> txOutPairs
totalValue = sum $ Blockchain.value <$> txOuts
when (totalValue < (value + fee)) $ Except.throwError SourceAddressInsufficientFunds
txIns <- sequence $ flip fmap txOutPairs $ \(txOutRef, txOut) -> do
sig <- lift $ Blockchain.signTransaction srcPrivKey txOut
unless (Blockchain.verifyTransactionSignature sig txOut) $ Except.throwError InvalidPrivateKey
return (Blockchain.TransactionIn txOutRef sig)
let refund = totalValue (value + fee)
txOut = Blockchain.TransactionOut value targetPubKey
maybeRefundTxOut = if refund > 0 then Just (Blockchain.TransactionOut refund srcPubKey)
else Nothing
txOuts' = maybe (pure txOut) (: pure txOut) maybeRefundTxOut
return $ Blockchain.Transaction (NonEmpty.fromList txIns) (NonEmpty.fromList txOuts')