packages feed

solana-staking-csvs-0.1.0.0: src/Console/SolanaStaking/Main.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RecordWildCards #-}
{-| This module contains the @main@ function used by the exectuable.

-}
module Console.SolanaStaking.Main
    ( run
    , getArgs
    , Args(..)
    ) where

import           Control.Monad                  ( (<=<)
                                                , forM
                                                , unless
                                                )
import           Control.Monad.Except           ( ExceptT
                                                , runExceptT
                                                )
import           Control.Monad.Reader           ( ReaderT
                                                , liftIO
                                                , runReaderT
                                                )
import           Data.Bifunctor                 ( bimap
                                                , second
                                                )
import           Data.List                      ( sortOn )
import           Data.Maybe                     ( fromMaybe )
import           Data.Version                   ( showVersion )
import           System.Console.CmdArgs         ( (&=)
                                                , Data
                                                , Typeable
                                                , argPos
                                                , cmdArgs
                                                , def
                                                , explicit
                                                , help
                                                , helpArg
                                                , name
                                                , program
                                                , summary
                                                , typ
                                                )
import           System.IO                      ( hPutStrLn
                                                , stderr
                                                )

import           Console.SolanaStaking.Api
import           Console.SolanaStaking.Csv      ( makeCsvContents )
import           Paths_solana_staking_csvs      ( version )

import qualified Data.ByteString.Lazy          as LBS


-- | Pull staking rewards data for the account & print a CSV to stdout.
--
-- TODO: concurrent/pooled requests, but w/ 100 req/s limit
run :: Args -> IO ()
run Args {..} = either (error . show) return <=< runner $ do
    -- Grab all stake accounts
    stakes <- saResults <$> (getAccountStakes >>= raiseAPIError)
    -- Grab rewards for each stake account
    (stakeErrors, stakeRewards) <-
        fmap (bimap concat concat . unzip) . forM stakes $ \sa -> do
            second (map (sa, )) <$> getAllStakeRewards (saPubKey sa)
    unless (null stakeErrors) . liftIO $ do
        hPutStrLn stderr "Got errors while fetching stake rewards:"
        mapM_ (hPutStrLn stderr . ("\t" <>) . show) stakeErrors
    -- Write the CSV
    let orderedRewards = sortOn (srTimestamp . snd) stakeRewards
        output         = makeCsvContents orderedRewards
        outputFile     = fromMaybe "-" argOutputFile
    if outputFile == "-"
        then liftIO $ LBS.putStr output
        else liftIO $ LBS.writeFile outputFile output
  where
    runner :: ReaderT Config (ExceptT APIError IO) a -> IO (Either APIError a)
    runner = runExceptT . flip runReaderT (mkConfig argApiKey argPubKey)


-- | CLI arguments supported by the executable.
data Args = Args
    { argApiKey     :: String
    -- ^ Solana Beach API Key
    , argPubKey     :: String
    -- ^ Delegator's PubKey.
    , argOutputFile :: Maybe String
    -- ^ Optional output file. 'Nothing' or @'Just' "-"@ means print to
    -- 'System.IO.stdout'.
    }
    deriving (Show, Read, Eq, Data, Typeable)

-- | Parse the CLI arguments with 'System.Console.CmdArgs'.
getArgs :: IO Args
getArgs = cmdArgs argSpec

argSpec :: Args
argSpec =
    Args
            { argApiKey     = def &= argPos 0 &= typ "API_KEY"
            , argPubKey     = def &= argPos 1 &= typ "ACCOUNT_PUBKEY"
            , argOutputFile =
                Nothing
                &= help "File to write the export to. Default: stdout"
                &= explicit
                &= name "output-file"
                &= name "o"
                &= typ "FILE"
            }
        &= summary
               (  "solana-staking-csvs v"
               <> showVersion version
               <> ", Pavan Rikhi 2021"
               )
        &= program "solana-staking-csvs"
        &= helpArg [name "h"]
        &= help "Generate CSV Exports of your Solana Staking Rewards."