packages feed

bnb-staking-csvs-0.2.2.0: src/Console/BnbStaking/Csv.hs

{-# LANGUAGE RecordWildCards #-}

-- | CSV serialization of BNB Staking Rewards.
module Console.BnbStaking.Csv
    ( makeCsvContents
    , ExportData (..)
    , convertReward
    , MyZonedTime (..)
    ) where

import Data.Csv
    ( DefaultOrdered
    , ToField (..)
    , ToNamedRecord
    , encodeDefaultOrderedByName
    )
import Data.Scientific
    ( FPFormat (..)
    , formatScientific
    )
import Data.Time
    ( ZonedTime (..)
    , defaultTimeLocale
    , formatTime
    , utcToLocalZonedTime
    )
import GHC.Generics (Generic)

import Console.BnbStaking.Api (Reward (..))

import Data.ByteString.Lazy qualified as LBS
import Data.Text qualified as T


-- | Build the CSV contents for the given rewards, including the header
-- row.
makeCsvContents :: [Reward] -> IO LBS.ByteString
makeCsvContents = fmap encodeDefaultOrderedByName . mapM convertReward


-- | Datatype representing a single row in the CSV export.
data ExportData = ExportData
    { time :: MyZonedTime
    -- ^ The time of the reward.
    , amount :: T.Text
    -- ^ The reward amount.
    , currency :: T.Text
    -- ^ Always @BNB@, but sometimes a useful column for CSV imports.
    , delegator :: T.Text
    -- ^ The address that was rewarded.
    , validator :: T.Text
    -- ^ The validator's name.
    , validatorAddress :: T.Text
    -- ^ The address the delegator is validating to.
    , height :: Integer
    -- ^ The height of the reward's block.
    }
    deriving (Show, Read, Generic)


instance ToNamedRecord ExportData
instance DefaultOrdered ExportData


-- | Render a 'Reward' into our target export data by converting to
-- localtime(respecting DST), & formatting the amount column to 8 decimal
-- places.
convertReward :: Reward -> IO ExportData
convertReward Reward {..} = do
    localRewardTime <- utcToLocalZonedTime rRewardTime
    return $
        ExportData
            { time = MyZonedTime localRewardTime
            , amount = T.pack $ formatScientific Fixed (Just 8) rReward
            , currency = "BNB"
            , delegator = rDelegator
            , validator = rValidatorName
            , validatorAddress = rValidatorAddress
            , height = rHeight
            }


-- | Wrapper type to support custom 'ToField' instance.
newtype MyZonedTime = MyZonedTime {fromMyZonedTime :: ZonedTime} deriving (Show, Read)


-- | Render with @%FT%T%Q%Ez@ formatting string.
instance ToField MyZonedTime where
    toField (MyZonedTime zt) =
        toField $ formatTime defaultTimeLocale "%FT%T%Q%Ez" zt