packages feed

bitcoin-rpc-0.5.0.0: Network/BitcoinRPC/Types.hs

{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
module Network.BitcoinRPC.Types
    ( BitcoinAmount(..)
    , BitcoinAddress(..)
    , TransactionID(..)
    , BlockHash(..)
    , RPCAuth(..)
    , Transaction(..)
    , TransactionHeader(..)
    , TransactionOrigins(..)
    , SerializedTransaction(..)
    , BitcoinAddressInfo(..)
    , SinceBlockInfo(..)
    , RPCResult(..)
    , SendError(..)
    , txidAsByteString
    , addressAsByteString
    , blockHashAsByteString
    )
    where

import Control.Applicative
import Control.Monad
import Data.Aeson
import Data.Aeson.Types
import Data.Maybe
import Data.Serialize
import GHC.Generics

import qualified Data.ByteString as B
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import qualified Data.Text.Encoding as E

newtype BitcoinAmount = BitcoinAmount { btcAmount :: Integer }
                        deriving (Eq,Ord,Show,Read,Generic)

newtype BitcoinAddress = BitcoinAddress { btcAddress :: T.Text }
                         deriving (Eq,Ord,Show,Read)

newtype TransactionID = TransactionID { btcTxID :: T.Text }
                        deriving (Eq,Ord,Show,Read)

newtype SerializedTransaction = SerializedTransaction
                                    { btcSerializedTx :: T.Text }
                                deriving (Eq,Ord,Show,Read)

newtype BlockHash = BlockHash { btcBlockHash :: T.Text }
                    deriving (Eq,Ord,Show,Read)

data RPCAuth = RPCAuth { rpcUrl :: String
                       , rpcUser :: String
                       , rpcPassword :: String
                       }
                       deriving (Show)

data Transaction = ReceiveTx { tEntry :: Integer
                             , tAmount :: BitcoinAmount
                             , tAddress :: BitcoinAddress
                             , tConfirmations :: Integer
                             , tTxid :: TransactionID
                             , tTime :: Integer
                             }
                 | SendTx { tEntry :: Integer
                          , tAmount :: BitcoinAmount
                          , tAddress :: BitcoinAddress
                          , tFee :: BitcoinAmount
                          , tConfirmations :: Integer
                          , tTxid :: TransactionID
                          , tTime :: Integer
                          }
                 | MoveTx { tTime :: Integer }
                 | GenerateTx { tEntry :: Integer
                              , tAmount :: BitcoinAmount
                              , tConfirmations :: Integer
                              , tTxid :: TransactionID
                              , tTime :: Integer
                              }
                 deriving (Eq,Show,Read,Generic)


data TransactionHeader = TransactionHeader { thAmount :: BitcoinAmount
                                           , thConfirmations :: Integer
                                           , thTxid :: TransactionID
                                           , thTime :: Integer
                                           }
                         deriving (Show)

data TransactionOrigins = TransactionOrigins { toConfirmations :: Integer
                                             , toTxid :: TransactionID
                                             , toTime :: Integer
                                             , toOrigins :: [BitcoinAddress]
                                             }
                          deriving (Show)

data RPCResult = RPCSuccess Value
               | RPCError { rpcErrorCode :: Integer
                          , rpcErrorMessage :: T.Text
                          }
               deriving (Show)

data BitcoinAddressInfo = BitcoinAddressInfo { baiIsValid :: Bool
                                             , baiIsMine :: Bool
                                             }
                                             deriving (Show)

data SinceBlockInfo = SinceBlockInfo { sbiTransactions :: [Transaction]
                                     , sbiLastBlock :: BlockHash
                                     }
                                     deriving (Show)

data SendError = InvalidAddress | InsufficientFunds | InvalidAmount | OtherError
                deriving (Show)

instance Serialize BitcoinAmount

instance Serialize BitcoinAddress where
    put = put . T.unpack . btcAddress
    get = BitcoinAddress . T.pack <$> get

instance Serialize TransactionID where
    put = put . T.unpack . btcTxID
    get = TransactionID . T.pack <$> get

instance Serialize SerializedTransaction where
    put = put . T.unpack . btcSerializedTx
    get = SerializedTransaction . T.pack <$> get

instance Serialize Transaction

instance Serialize BlockHash where
    put = put . T.unpack . btcBlockHash
    get = BlockHash . T.pack <$> get

instance Num BitcoinAmount
  where
    (+) (BitcoinAmount x) (BitcoinAmount y) = BitcoinAmount (x + y)
    (*) (BitcoinAmount _) (BitcoinAmount _) = error "not supported"
    (-) (BitcoinAmount x) (BitcoinAmount y) = BitcoinAmount (x - y)
    abs (BitcoinAmount x) = BitcoinAmount (abs x)
    signum (BitcoinAmount x) = BitcoinAmount (signum x)
    fromInteger x = BitcoinAmount (fromInteger x)

instance FromJSON BitcoinAmount
  where
    parseJSON v = do
        amount <- parseJSON v :: Parser Double
        let inSatoshis = round $ amount * 10 ^ (8 :: Integer)
        return $ BitcoinAmount inSatoshis

instance FromJSON BitcoinAddress
  where
    parseJSON (String addr) = return $ BitcoinAddress addr
    parseJSON _ = mzero

instance FromJSON TransactionID
  where
    parseJSON (String txid) = return $ TransactionID txid
    parseJSON _ = mzero

instance FromJSON SerializedTransaction
  where
    parseJSON (String tx) = return $ SerializedTransaction tx
    parseJSON _ = mzero

instance FromJSON BlockHash
  where
    parseJSON (String hash) = return $ BlockHash hash
    parseJSON _ = mzero

instance FromJSON Transaction
  where
    parseJSON (Object o) = case H.lookup "category" o of
        Just "send" -> SendTx <$>
                            o .: "entry" <*>
                            (abs <$> o .: "amount") <*>
                            o .: "address" <*>
                            o .: "fee" <*>
                            o .: "confirmations" <*>
                            o .: "txid" <*>
                            o .: "time"
        Just "receive" -> ReceiveTx <$>
                            o .: "entry" <*>
                            o .: "amount" <*>
                            o .: "address" <*>
                            o .: "confirmations" <*>
                            o .: "txid" <*>
                            o .: "time"
        Just "move" -> MoveTx <$>
                            o .: "time"
        Just "generate" -> GenerateTx <$>
                            o .: "entry" <*>
                            o .: "amount" <*>
                            o .: "confirmations" <*>
                            o .: "txid" <*>
                            o .: "time"
        Just _ -> mzero
        Nothing -> mzero
    parseJSON _ = mzero

instance FromJSON TransactionHeader
  where
    parseJSON (Object o) = TransactionHeader <$>
                            o .: "amount" <*>
                            o .: "confirmations" <*>
                            o .: "txid" <*>
                            o .: "time"
    parseJSON _ = mzero

instance FromJSON TransactionOrigins
  where
    parseJSON (Object o) = TransactionOrigins <$>
                            o .: "confirmations" <*>
                            o .: "txid" <*>
                            o .: "time" <*>
                            o .: "origins"
    parseJSON _ = mzero

instance FromJSON BitcoinAddressInfo
  where
    parseJSON (Object o) = BitcoinAddressInfo <$>
                                o .: "isvalid" <*>
                                (fromMaybe False <$> o .:? "ismine")
    parseJSON _ = mzero

instance FromJSON SinceBlockInfo
  where
    parseJSON (Object o) = SinceBlockInfo <$>
                            o .: "transactions" <*>
                            o .: "lastblock"
    parseJSON _ = mzero

instance FromJSON RPCResult
  where
    parseJSON (Object o) = case H.lookup "result" o of
        Just Null -> case H.lookup "error" o of
            Just (Object errInfo) -> RPCError <$> errInfo .: "code"
                                              <*> errInfo .: "message"
            Just Null -> mzero
            _ -> mzero
        Just result -> return $ RPCSuccess result
        _ -> mzero
    parseJSON _ = mzero

txidAsByteString :: TransactionID -> B.ByteString
txidAsByteString = E.encodeUtf8 . btcTxID

addressAsByteString :: BitcoinAddress -> B.ByteString
addressAsByteString = E.encodeUtf8 . btcAddress

blockHashAsByteString :: BlockHash -> B.ByteString
blockHashAsByteString = E.encodeUtf8 . btcBlockHash