packages feed

bnb-staking-csvs-0.2.0.0: src/Console/BnbStaking/Api.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RecordWildCards #-}
{- | Binance.org API requests & responses.

-}
module Console.BnbStaking.Api
    ( -- * Rewards
      getAllRewards
    , Reward(..)
      -- * Low-Level Requests & Responses
    , Endpoint(..)
    , makeRequest
    , RewardResponse(..)
    ) where

import           Control.Monad                  ( forM )
import           Data.Aeson                     ( (.:)
                                                , FromJSON(..)
                                                , withObject
                                                )
import           Data.List                      ( sortOn )
import           Data.Maybe                     ( fromMaybe )
import           Data.Scientific                ( Scientific )
import           Data.Time                      ( UTCTime
                                                , defaultTimeLocale
                                                , parseTimeM
                                                )
import           GHC.Generics                   ( Generic )
import           Network.HTTP.Req               ( (/:)
                                                , (=:)
                                                , GET(..)
                                                , MonadHttp
                                                , NoReqBody(..)
                                                , Scheme(Https)
                                                , Url
                                                , https
                                                , jsonResponse
                                                , req
                                                , responseBody
                                                )

import qualified Data.Text                     as T


-- | Fetch all rewards for the given Delegator PubKey.
getAllRewards :: MonadHttp m => T.Text -> m [Reward]
getAllRewards pubKey = do
    let pageSize  = 50
        jPageSize = Just pageSize
    initialResp <- makeRequest $ GetRewards pubKey jPageSize Nothing
    let rewardCount = rrTotal initialResp
    remainingRewards <- if rewardCount < pageSize
        then return []
        else
            fmap concat
            . forM [pageSize, pageSize * 2 .. rewardCount]
            $ \(Just -> offset) ->
                  rrRewards <$> makeRequest (GetRewards pubKey jPageSize offset)
    return . sortResults $ rrRewards initialResp <> remainingRewards
  where
    sortResults :: [Reward] -> [Reward]
    sortResults = sortOn rRewardTime


-- | Represents all endpoints of the binance.org api, as well as their
-- respective response data.
data Endpoint a where
    GetRewards ::T.Text -> Maybe Integer -> Maybe Integer -> Endpoint RewardResponse

-- | Make a request to an endpoint.
makeRequest :: MonadHttp m => Endpoint a -> m a
makeRequest e = case e of
    GetRewards _ mbLimit mbOffset -> responseBody <$> req
        GET
        url
        NoReqBody
        jsonResponse
        (("limit" =: fromMaybe 20 mbLimit) <> ("offset" =: fromMaybe 0 mbOffset)
        )
  where
    url :: Url 'Https
    url = case e of
        GetRewards pubKey _ _ ->
            baseUrl
                /: "staking"
                /: "chains"
                /: "bsc"
                /: "delegators"
                /: pubKey
                /: "rewards"
    baseUrl :: Url 'Https
    baseUrl = https "api.binance.org" /: "v1"


-- | Response of requesting a delegator's rewards.
data RewardResponse = RewardResponse
    { rrTotal   :: Integer
    -- ^ Total number of rewards.
    , rrRewards :: [Reward]
    -- ^ Rewards in this page.
    }
    deriving (Show, Read, Eq, Generic)

instance FromJSON RewardResponse where
    parseJSON = withObject "RewardResponse" $ \o -> do
        rrTotal   <- o .: "total"
        rrRewards <- o .: "rewardDetails"
        return $ RewardResponse { .. }


-- | A single staking reward.
data Reward = Reward
    { rValidatorName    :: T.Text
    , rValidatorAddress :: T.Text
    , rDelegator        :: T.Text
    , rChainId          :: T.Text
    -- ^ Always @bsc@ at the moment - no testnet rewards supported.
    , rHeight           :: Integer
    , rReward           :: Scientific
    , rRewardTime       :: UTCTime
    }
    deriving (Show, Read, Eq, Generic)

instance FromJSON Reward where
    parseJSON = withObject "Reward" $ \o -> do
        rValidatorName    <- o .: "valName"
        rValidatorAddress <- o .: "validator"
        rDelegator        <- o .: "delegator"
        rChainId          <- o .: "chainId"
        rHeight           <- o .: "height"
        rReward           <- o .: "reward"
        rRewardTime       <- o .: "rewardTime" >>= parseTimeM True
                                                              defaultTimeLocale
                                                              "%FT%T%Q%Ez"
        return $ Reward { .. }