{-# 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 (foldM, forM, unless, (<=<))
import Control.Monad.Except (ExceptT, runExceptT)
import Control.Monad.Reader (MonadIO, ReaderT, liftIO, runReaderT)
import Data.Bifunctor (bimap, second)
import Data.List (sortOn)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Time (LocalTime (..), ZonedTime (..), utcToLocalZonedTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
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.CoinTracking (makeCoinTrackingImport)
import Console.SolanaStaking.Csv (makeCsvContents)
import Paths_solana_staking_csvs (version)

import Data.ByteString.Lazy qualified as LBS
import Data.Map.Strict qualified as M
import Data.Text qualified as T


-- | 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 -> IO ()
run Args {Bool
String
Maybe Integer
Maybe String
argApiKey :: String
argPubKey :: String
argOutputFile :: Maybe String
argCoinTracking :: Bool
argYear :: Maybe Integer
argAggregate :: Bool
argApiKey :: Args -> String
argPubKey :: Args -> String
argOutputFile :: Args -> Maybe String
argCoinTracking :: Args -> Bool
argYear :: Args -> Maybe Integer
argAggregate :: Args -> Bool
..} = (APIError -> IO ()) -> (() -> IO ()) -> Either APIError () -> IO ()
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (String -> IO ()
forall a. HasCallStack => String -> a
error (String -> IO ()) -> (APIError -> String) -> APIError -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. APIError -> String
forall a. Show a => a -> String
show) () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either APIError () -> IO ())
-> (ReaderT Config (ExceptT APIError IO) ()
    -> IO (Either APIError ()))
-> ReaderT Config (ExceptT APIError IO) ()
-> IO ()
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< ReaderT Config (ExceptT APIError IO) () -> IO (Either APIError ())
forall a.
ReaderT Config (ExceptT APIError IO) a -> IO (Either APIError a)
runner (ReaderT Config (ExceptT APIError IO) () -> IO ())
-> ReaderT Config (ExceptT APIError IO) () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
    -- Grab all stake accounts
    [StakingAccount]
stakes <- StakingAccounts -> [StakingAccount]
saResults (StakingAccounts -> [StakingAccount])
-> ReaderT Config (ExceptT APIError IO) StakingAccounts
-> ReaderT Config (ExceptT APIError IO) [StakingAccount]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (ReaderT Config (ExceptT APIError IO) (APIResponse StakingAccounts)
forall (m :: * -> *).
(MonadReader Config m, MonadCatch m, MonadIO m) =>
m (APIResponse StakingAccounts)
getAccountStakes ReaderT Config (ExceptT APIError IO) (APIResponse StakingAccounts)
-> (APIResponse StakingAccounts
    -> ReaderT Config (ExceptT APIError IO) StakingAccounts)
-> ReaderT Config (ExceptT APIError IO) StakingAccounts
forall a b.
ReaderT Config (ExceptT APIError IO) a
-> (a -> ReaderT Config (ExceptT APIError IO) b)
-> ReaderT Config (ExceptT APIError IO) b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= APIResponse StakingAccounts
-> ReaderT Config (ExceptT APIError IO) StakingAccounts
forall (m :: * -> *) a.
MonadError APIError m =>
APIResponse a -> m a
raiseAPIError)
    -- Grab rewards for each stake account
    ([APIError]
stakeErrors, [(StakingAccount, StakeReward)]
stakeRewards) <-
        ([([APIError], [(StakingAccount, StakeReward)])]
 -> ([APIError], [(StakingAccount, StakeReward)]))
-> ReaderT
     Config
     (ExceptT APIError IO)
     [([APIError], [(StakingAccount, StakeReward)])]
-> ReaderT
     Config
     (ExceptT APIError IO)
     ([APIError], [(StakingAccount, StakeReward)])
forall a b.
(a -> b)
-> ReaderT Config (ExceptT APIError IO) a
-> ReaderT Config (ExceptT APIError IO) b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (([[APIError]] -> [APIError])
-> ([[(StakingAccount, StakeReward)]]
    -> [(StakingAccount, StakeReward)])
-> ([[APIError]], [[(StakingAccount, StakeReward)]])
-> ([APIError], [(StakingAccount, StakeReward)])
forall a b c d. (a -> b) -> (c -> d) -> (a, c) -> (b, d)
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap [[APIError]] -> [APIError]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[(StakingAccount, StakeReward)]]
-> [(StakingAccount, StakeReward)]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (([[APIError]], [[(StakingAccount, StakeReward)]])
 -> ([APIError], [(StakingAccount, StakeReward)]))
-> ([([APIError], [(StakingAccount, StakeReward)])]
    -> ([[APIError]], [[(StakingAccount, StakeReward)]]))
-> [([APIError], [(StakingAccount, StakeReward)])]
-> ([APIError], [(StakingAccount, StakeReward)])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [([APIError], [(StakingAccount, StakeReward)])]
-> ([[APIError]], [[(StakingAccount, StakeReward)]])
forall a b. [(a, b)] -> ([a], [b])
unzip) (ReaderT
   Config
   (ExceptT APIError IO)
   [([APIError], [(StakingAccount, StakeReward)])]
 -> ReaderT
      Config
      (ExceptT APIError IO)
      ([APIError], [(StakingAccount, StakeReward)]))
-> ((StakingAccount
     -> ReaderT
          Config
          (ExceptT APIError IO)
          ([APIError], [(StakingAccount, StakeReward)]))
    -> ReaderT
         Config
         (ExceptT APIError IO)
         [([APIError], [(StakingAccount, StakeReward)])])
-> (StakingAccount
    -> ReaderT
         Config
         (ExceptT APIError IO)
         ([APIError], [(StakingAccount, StakeReward)]))
-> ReaderT
     Config
     (ExceptT APIError IO)
     ([APIError], [(StakingAccount, StakeReward)])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [StakingAccount]
-> (StakingAccount
    -> ReaderT
         Config
         (ExceptT APIError IO)
         ([APIError], [(StakingAccount, StakeReward)]))
-> ReaderT
     Config
     (ExceptT APIError IO)
     [([APIError], [(StakingAccount, StakeReward)])]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [StakingAccount]
stakes ((StakingAccount
  -> ReaderT
       Config
       (ExceptT APIError IO)
       ([APIError], [(StakingAccount, StakeReward)]))
 -> ReaderT
      Config
      (ExceptT APIError IO)
      ([APIError], [(StakingAccount, StakeReward)]))
-> (StakingAccount
    -> ReaderT
         Config
         (ExceptT APIError IO)
         ([APIError], [(StakingAccount, StakeReward)]))
-> ReaderT
     Config
     (ExceptT APIError IO)
     ([APIError], [(StakingAccount, StakeReward)])
forall a b. (a -> b) -> a -> b
$ \StakingAccount
sa -> do
            ([StakeReward] -> [(StakingAccount, StakeReward)])
-> ([APIError], [StakeReward])
-> ([APIError], [(StakingAccount, StakeReward)])
forall b c a. (b -> c) -> (a, b) -> (a, c)
forall (p :: * -> * -> *) b c a.
Bifunctor p =>
(b -> c) -> p a b -> p a c
second ((StakeReward -> (StakingAccount, StakeReward))
-> [StakeReward] -> [(StakingAccount, StakeReward)]
forall a b. (a -> b) -> [a] -> [b]
map (StakingAccount
sa,))
                (([APIError], [StakeReward])
 -> ([APIError], [(StakingAccount, StakeReward)]))
-> ReaderT Config (ExceptT APIError IO) ([APIError], [StakeReward])
-> ReaderT
     Config
     (ExceptT APIError IO)
     ([APIError], [(StakingAccount, StakeReward)])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ReaderT Config (ExceptT APIError IO) ([APIError], [StakeReward])
-> (Integer
    -> ReaderT
         Config (ExceptT APIError IO) ([APIError], [StakeReward]))
-> Maybe Integer
-> ReaderT Config (ExceptT APIError IO) ([APIError], [StakeReward])
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
                    (StakingPubKey
-> ReaderT Config (ExceptT APIError IO) ([APIError], [StakeReward])
forall (m :: * -> *).
(MonadReader Config m, MonadCatch m, MonadIO m) =>
StakingPubKey -> m ([APIError], [StakeReward])
getAllStakeRewards (StakingAccount -> StakingPubKey
saPubKey StakingAccount
sa))
                    (StakingPubKey
-> Integer
-> ReaderT Config (ExceptT APIError IO) ([APIError], [StakeReward])
forall (m :: * -> *).
(MonadReader Config m, MonadCatch m, MonadIO m) =>
StakingPubKey -> Integer -> m ([APIError], [StakeReward])
getYearsStakeRewards (StakingPubKey
 -> Integer
 -> ReaderT
      Config (ExceptT APIError IO) ([APIError], [StakeReward]))
-> StakingPubKey
-> Integer
-> ReaderT Config (ExceptT APIError IO) ([APIError], [StakeReward])
forall a b. (a -> b) -> a -> b
$ StakingAccount -> StakingPubKey
saPubKey StakingAccount
sa)
                    Maybe Integer
argYear
    Bool
-> ReaderT Config (ExceptT APIError IO) ()
-> ReaderT Config (ExceptT APIError IO) ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([APIError] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [APIError]
stakeErrors) (ReaderT Config (ExceptT APIError IO) ()
 -> ReaderT Config (ExceptT APIError IO) ())
-> (IO () -> ReaderT Config (ExceptT APIError IO) ())
-> IO ()
-> ReaderT Config (ExceptT APIError IO) ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a. IO a -> ReaderT Config (ExceptT APIError IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> ReaderT Config (ExceptT APIError IO) ())
-> IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a b. (a -> b) -> a -> b
$ do
        Handle -> String -> IO ()
hPutStrLn Handle
stderr String
"Got errors while fetching stake rewards:"
        (APIError -> IO ()) -> [APIError] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Handle -> String -> IO ()
hPutStrLn Handle
stderr (String -> IO ()) -> (APIError -> String) -> APIError -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String
"\t" String -> String -> String
forall a. Semigroup a => a -> a -> a
<>) (String -> String) -> (APIError -> String) -> APIError -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. APIError -> String
forall a. Show a => a -> String
show) [APIError]
stakeErrors
    -- Write the CSV
    let orderedRewards :: [(StakingAccount, StakeReward)]
orderedRewards = ((StakingAccount, StakeReward) -> POSIXTime)
-> [(StakingAccount, StakeReward)]
-> [(StakingAccount, StakeReward)]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (StakeReward -> POSIXTime
srTimestamp (StakeReward -> POSIXTime)
-> ((StakingAccount, StakeReward) -> StakeReward)
-> (StakingAccount, StakeReward)
-> POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (StakingAccount, StakeReward) -> StakeReward
forall a b. (a, b) -> b
snd) [(StakingAccount, StakeReward)]
stakeRewards
        aggregator :: [(StakingAccount, StakeReward)]
-> ReaderT
     Config (ExceptT APIError IO) [(StakingAccount, StakeReward)]
aggregator = if Bool
argAggregate then [(StakingAccount, StakeReward)]
-> ReaderT
     Config (ExceptT APIError IO) [(StakingAccount, StakeReward)]
forall (m :: * -> *).
MonadIO m =>
[(StakingAccount, StakeReward)]
-> m [(StakingAccount, StakeReward)]
aggregateRewards else [(StakingAccount, StakeReward)]
-> ReaderT
     Config (ExceptT APIError IO) [(StakingAccount, StakeReward)]
forall a. a -> ReaderT Config (ExceptT APIError IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return
        outputFile :: String
outputFile = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"-" Maybe String
argOutputFile
    [(StakingAccount, StakeReward)]
rewards <- [(StakingAccount, StakeReward)]
-> ReaderT
     Config (ExceptT APIError IO) [(StakingAccount, StakeReward)]
aggregator [(StakingAccount, StakeReward)]
orderedRewards
    if Bool
argCoinTracking
        then IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a. IO a -> ReaderT Config (ExceptT APIError IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> ReaderT Config (ExceptT APIError IO) ())
-> IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a b. (a -> b) -> a -> b
$ String -> [(StakingAccount, StakeReward)] -> IO ()
makeCoinTrackingImport String
outputFile [(StakingAccount, StakeReward)]
rewards
        else do
            let output :: ByteString
output = [(StakingAccount, StakeReward)] -> ByteString
makeCsvContents [(StakingAccount, StakeReward)]
rewards
            if String
outputFile String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"-"
                then IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a. IO a -> ReaderT Config (ExceptT APIError IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> ReaderT Config (ExceptT APIError IO) ())
-> IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a b. (a -> b) -> a -> b
$ ByteString -> IO ()
LBS.putStr ByteString
output
                else IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a. IO a -> ReaderT Config (ExceptT APIError IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> ReaderT Config (ExceptT APIError IO) ())
-> IO () -> ReaderT Config (ExceptT APIError IO) ()
forall a b. (a -> b) -> a -> b
$ String -> ByteString -> IO ()
LBS.writeFile String
outputFile ByteString
output
  where
    runner :: ReaderT Config (ExceptT APIError IO) a -> IO (Either APIError a)
    runner :: forall a.
ReaderT Config (ExceptT APIError IO) a -> IO (Either APIError a)
runner = ExceptT APIError IO a -> IO (Either APIError a)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT APIError IO a -> IO (Either APIError a))
-> (ReaderT Config (ExceptT APIError IO) a
    -> ExceptT APIError IO a)
-> ReaderT Config (ExceptT APIError IO) a
-> IO (Either APIError a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ReaderT Config (ExceptT APIError IO) a
 -> Config -> ExceptT APIError IO a)
-> Config
-> ReaderT Config (ExceptT APIError IO) a
-> ExceptT APIError IO a
forall a b c. (a -> b -> c) -> b -> a -> c
flip ReaderT Config (ExceptT APIError IO) a
-> Config -> ExceptT APIError IO a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (String -> String -> Config
mkConfig String
argApiKey String
argPubKey)
    aggregateRewards :: (MonadIO m) => [(StakingAccount, StakeReward)] -> m [(StakingAccount, StakeReward)]
    aggregateRewards :: forall (m :: * -> *).
MonadIO m =>
[(StakingAccount, StakeReward)]
-> m [(StakingAccount, StakeReward)]
aggregateRewards =
        (Map Day [(StakingAccount, StakeReward)]
 -> [(StakingAccount, StakeReward)])
-> m (Map Day [(StakingAccount, StakeReward)])
-> m [(StakingAccount, StakeReward)]
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((Day, [(StakingAccount, StakeReward)])
 -> Maybe (StakingAccount, StakeReward))
-> [(Day, [(StakingAccount, StakeReward)])]
-> [(StakingAccount, StakeReward)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ([(StakingAccount, StakeReward)]
-> Maybe (StakingAccount, StakeReward)
aggregate ([(StakingAccount, StakeReward)]
 -> Maybe (StakingAccount, StakeReward))
-> ((Day, [(StakingAccount, StakeReward)])
    -> [(StakingAccount, StakeReward)])
-> (Day, [(StakingAccount, StakeReward)])
-> Maybe (StakingAccount, StakeReward)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Day, [(StakingAccount, StakeReward)])
-> [(StakingAccount, StakeReward)]
forall a b. (a, b) -> b
snd) ([(Day, [(StakingAccount, StakeReward)])]
 -> [(StakingAccount, StakeReward)])
-> (Map Day [(StakingAccount, StakeReward)]
    -> [(Day, [(StakingAccount, StakeReward)])])
-> Map Day [(StakingAccount, StakeReward)]
-> [(StakingAccount, StakeReward)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map Day [(StakingAccount, StakeReward)]
-> [(Day, [(StakingAccount, StakeReward)])]
forall k a. Map k a -> [(k, a)]
M.toList)
            (m (Map Day [(StakingAccount, StakeReward)])
 -> m [(StakingAccount, StakeReward)])
-> ([(StakingAccount, StakeReward)]
    -> m (Map Day [(StakingAccount, StakeReward)]))
-> [(StakingAccount, StakeReward)]
-> m [(StakingAccount, StakeReward)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Map Day [(StakingAccount, StakeReward)]
 -> (StakingAccount, StakeReward)
 -> m (Map Day [(StakingAccount, StakeReward)]))
-> Map Day [(StakingAccount, StakeReward)]
-> [(StakingAccount, StakeReward)]
-> m (Map Day [(StakingAccount, StakeReward)])
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM
                ( \Map Day [(StakingAccount, StakeReward)]
acc (StakingAccount, StakeReward)
r -> do
                    ZonedTime
rewardTime <- IO ZonedTime -> m ZonedTime
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO ZonedTime -> m ZonedTime)
-> (UTCTime -> IO ZonedTime) -> UTCTime -> m ZonedTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> IO ZonedTime
utcToLocalZonedTime (UTCTime -> m ZonedTime) -> UTCTime -> m ZonedTime
forall a b. (a -> b) -> a -> b
$ POSIXTime -> UTCTime
posixSecondsToUTCTime (POSIXTime -> UTCTime) -> POSIXTime -> UTCTime
forall a b. (a -> b) -> a -> b
$ StakeReward -> POSIXTime
srTimestamp (StakeReward -> POSIXTime) -> StakeReward -> POSIXTime
forall a b. (a -> b) -> a -> b
$ (StakingAccount, StakeReward) -> StakeReward
forall a b. (a, b) -> b
snd (StakingAccount, StakeReward)
r
                    Map Day [(StakingAccount, StakeReward)]
-> m (Map Day [(StakingAccount, StakeReward)])
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Map Day [(StakingAccount, StakeReward)]
 -> m (Map Day [(StakingAccount, StakeReward)]))
-> Map Day [(StakingAccount, StakeReward)]
-> m (Map Day [(StakingAccount, StakeReward)])
forall a b. (a -> b) -> a -> b
$
                        ([(StakingAccount, StakeReward)]
 -> [(StakingAccount, StakeReward)]
 -> [(StakingAccount, StakeReward)])
-> Day
-> [(StakingAccount, StakeReward)]
-> Map Day [(StakingAccount, StakeReward)]
-> Map Day [(StakingAccount, StakeReward)]
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith
                            [(StakingAccount, StakeReward)]
-> [(StakingAccount, StakeReward)]
-> [(StakingAccount, StakeReward)]
forall a. Semigroup a => a -> a -> a
(<>)
                            (LocalTime -> Day
localDay (LocalTime -> Day) -> LocalTime -> Day
forall a b. (a -> b) -> a -> b
$ ZonedTime -> LocalTime
zonedTimeToLocalTime ZonedTime
rewardTime)
                            [(StakingAccount, StakeReward)
r]
                            Map Day [(StakingAccount, StakeReward)]
acc
                )
                Map Day [(StakingAccount, StakeReward)]
forall k a. Map k a
M.empty

    aggregate :: [(StakingAccount, StakeReward)] -> Maybe (StakingAccount, StakeReward)
    aggregate :: [(StakingAccount, StakeReward)]
-> Maybe (StakingAccount, StakeReward)
aggregate = \case
        [] -> Maybe (StakingAccount, StakeReward)
forall a. Maybe a
Nothing
        [(StakingAccount, StakeReward)]
rs ->
            (StakingAccount, StakeReward)
-> Maybe (StakingAccount, StakeReward)
forall a. a -> Maybe a
Just
                ( StakingAccount
                    { saValidatorName :: Text
saValidatorName = Text
"AGGREGATE-" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ [(StakingAccount, StakeReward)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(StakingAccount, StakeReward)]
rs)
                    , saPubKey :: StakingPubKey
saPubKey = Text -> StakingPubKey
StakingPubKey (Text -> StakingPubKey) -> Text -> StakingPubKey
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack String
argPubKey
                    , saLamports :: Lamports
saLamports = [Lamports] -> Lamports
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Lamports] -> Lamports) -> [Lamports] -> Lamports
forall a b. (a -> b) -> a -> b
$ ((StakingAccount, StakeReward) -> Lamports)
-> [(StakingAccount, StakeReward)] -> [Lamports]
forall a b. (a -> b) -> [a] -> [b]
map (StakingAccount -> Lamports
saLamports (StakingAccount -> Lamports)
-> ((StakingAccount, StakeReward) -> StakingAccount)
-> (StakingAccount, StakeReward)
-> Lamports
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (StakingAccount, StakeReward) -> StakingAccount
forall a b. (a, b) -> a
fst) [(StakingAccount, StakeReward)]
rs
                    }
                , StakeReward
                    { srTimestamp :: POSIXTime
srTimestamp = [POSIXTime] -> POSIXTime
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum ([POSIXTime] -> POSIXTime) -> [POSIXTime] -> POSIXTime
forall a b. (a -> b) -> a -> b
$ ((StakingAccount, StakeReward) -> POSIXTime)
-> [(StakingAccount, StakeReward)] -> [POSIXTime]
forall a b. (a -> b) -> [a] -> [b]
map (StakeReward -> POSIXTime
srTimestamp (StakeReward -> POSIXTime)
-> ((StakingAccount, StakeReward) -> StakeReward)
-> (StakingAccount, StakeReward)
-> POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (StakingAccount, StakeReward) -> StakeReward
forall a b. (a, b) -> b
snd) [(StakingAccount, StakeReward)]
rs
                    , srSlot :: Integer
srSlot = StakeReward -> Integer
srSlot (StakeReward -> Integer) -> StakeReward -> Integer
forall a b. (a -> b) -> a -> b
$ (StakingAccount, StakeReward) -> StakeReward
forall a b. (a, b) -> b
snd ((StakingAccount, StakeReward) -> StakeReward)
-> (StakingAccount, StakeReward) -> StakeReward
forall a b. (a -> b) -> a -> b
$ [(StakingAccount, StakeReward)] -> (StakingAccount, StakeReward)
forall a. HasCallStack => [a] -> a
head [(StakingAccount, StakeReward)]
rs
                    , srEpoch :: Integer
srEpoch = StakeReward -> Integer
srEpoch (StakeReward -> Integer) -> StakeReward -> Integer
forall a b. (a -> b) -> a -> b
$ (StakingAccount, StakeReward) -> StakeReward
forall a b. (a, b) -> b
snd ((StakingAccount, StakeReward) -> StakeReward)
-> (StakingAccount, StakeReward) -> StakeReward
forall a b. (a -> b) -> a -> b
$ [(StakingAccount, StakeReward)] -> (StakingAccount, StakeReward)
forall a. HasCallStack => [a] -> a
head [(StakingAccount, StakeReward)]
rs
                    , srAmount :: Lamports
srAmount = [Lamports] -> Lamports
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Lamports] -> Lamports) -> [Lamports] -> Lamports
forall a b. (a -> b) -> a -> b
$ ((StakingAccount, StakeReward) -> Lamports)
-> [(StakingAccount, StakeReward)] -> [Lamports]
forall a b. (a -> b) -> [a] -> [b]
map (StakeReward -> Lamports
srAmount (StakeReward -> Lamports)
-> ((StakingAccount, StakeReward) -> StakeReward)
-> (StakingAccount, StakeReward)
-> Lamports
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (StakingAccount, StakeReward) -> StakeReward
forall a b. (a, b) -> b
snd) [(StakingAccount, StakeReward)]
rs
                    }
                )


-- | CLI arguments supported by the executable.
data Args = Args
    { Args -> String
argApiKey :: String
    -- ^ Solana Beach API Key
    , Args -> String
argPubKey :: String
    -- ^ Delegator's PubKey.
    , Args -> Maybe String
argOutputFile :: Maybe String
    -- ^ Optional output file. 'Nothing' or @'Just' "-"@ means print to
    -- 'System.IO.stdout'.
    , Args -> Bool
argCoinTracking :: Bool
    -- ^ Flag to enable writing/printing files formatted for CoinTracking
    -- Imports.
    , Args -> Maybe Integer
argYear :: Maybe Integer
    -- ^ Year to limit output to.
    , Args -> Bool
argAggregate :: Bool
    -- ^ Aggregate rewards into single-day transactions.
    }
    deriving (Int -> Args -> String -> String
[Args] -> String -> String
Args -> String
(Int -> Args -> String -> String)
-> (Args -> String) -> ([Args] -> String -> String) -> Show Args
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
$cshowsPrec :: Int -> Args -> String -> String
showsPrec :: Int -> Args -> String -> String
$cshow :: Args -> String
show :: Args -> String
$cshowList :: [Args] -> String -> String
showList :: [Args] -> String -> String
Show, ReadPrec [Args]
ReadPrec Args
Int -> ReadS Args
ReadS [Args]
(Int -> ReadS Args)
-> ReadS [Args] -> ReadPrec Args -> ReadPrec [Args] -> Read Args
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS Args
readsPrec :: Int -> ReadS Args
$creadList :: ReadS [Args]
readList :: ReadS [Args]
$creadPrec :: ReadPrec Args
readPrec :: ReadPrec Args
$creadListPrec :: ReadPrec [Args]
readListPrec :: ReadPrec [Args]
Read, Args -> Args -> Bool
(Args -> Args -> Bool) -> (Args -> Args -> Bool) -> Eq Args
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Args -> Args -> Bool
== :: Args -> Args -> Bool
$c/= :: Args -> Args -> Bool
/= :: Args -> Args -> Bool
Eq, Typeable Args
Typeable Args =>
(forall (c :: * -> *).
 (forall d b. Data d => c (d -> b) -> d -> c b)
 -> (forall g. g -> c g) -> Args -> c Args)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c Args)
-> (Args -> Constr)
-> (Args -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c Args))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args))
-> ((forall b. Data b => b -> b) -> Args -> Args)
-> (forall r r'.
    (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r)
-> (forall r r'.
    (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r)
-> (forall u. (forall d. Data d => d -> u) -> Args -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> Args -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> Args -> m Args)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Args -> m Args)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Args -> m Args)
-> Data Args
Args -> Constr
Args -> DataType
(forall b. Data b => b -> b) -> Args -> Args
forall a.
Typeable a =>
(forall (c :: * -> *).
 (forall d b. Data d => c (d -> b) -> d -> c b)
 -> (forall g. g -> c g) -> a -> c a)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c a)
-> (a -> Constr)
-> (a -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c a))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c a))
-> ((forall b. Data b => b -> b) -> a -> a)
-> (forall r r'.
    (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall r r'.
    (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall u. (forall d. Data d => d -> u) -> a -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> a -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> Data a
forall u. Int -> (forall d. Data d => d -> u) -> Args -> u
forall u. (forall d. Data d => d -> u) -> Args -> [u]
forall r r'.
(r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
forall r r'.
(r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Args -> m Args
forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Args)
forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args)
$cgfoldl :: forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
gfoldl :: forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
$cgunfold :: forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
gunfold :: forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
$ctoConstr :: Args -> Constr
toConstr :: Args -> Constr
$cdataTypeOf :: Args -> DataType
dataTypeOf :: Args -> DataType
$cdataCast1 :: forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Args)
dataCast1 :: forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Args)
$cdataCast2 :: forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args)
dataCast2 :: forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args)
$cgmapT :: (forall b. Data b => b -> b) -> Args -> Args
gmapT :: (forall b. Data b => b -> b) -> Args -> Args
$cgmapQl :: forall r r'.
(r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
gmapQl :: forall r r'.
(r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
$cgmapQr :: forall r r'.
(r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
gmapQr :: forall r r'.
(r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
$cgmapQ :: forall u. (forall d. Data d => d -> u) -> Args -> [u]
gmapQ :: forall u. (forall d. Data d => d -> u) -> Args -> [u]
$cgmapQi :: forall u. Int -> (forall d. Data d => d -> u) -> Args -> u
gmapQi :: forall u. Int -> (forall d. Data d => d -> u) -> Args -> u
$cgmapM :: forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Args -> m Args
gmapM :: forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Args -> m Args
$cgmapMp :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
gmapMp :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
$cgmapMo :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
gmapMo :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
Data, Typeable)


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


argSpec :: Args
argSpec :: Args
argSpec =
    Args
        { argApiKey :: String
argApiKey = String
forall a. Default a => a
def String -> Ann -> String
forall val. Data val => val -> Ann -> val
&= Int -> Ann
argPos Int
0 String -> Ann -> String
forall val. Data val => val -> Ann -> val
&= String -> Ann
typ String
"API_KEY"
        , argPubKey :: String
argPubKey = String
forall a. Default a => a
def String -> Ann -> String
forall val. Data val => val -> Ann -> val
&= Int -> Ann
argPos Int
1 String -> Ann -> String
forall val. Data val => val -> Ann -> val
&= String -> Ann
typ String
"ACCOUNT_PUBKEY"
        , argOutputFile :: Maybe String
argOutputFile =
            Maybe String
forall a. Maybe a
Nothing
                Maybe String -> Ann -> Maybe String
forall val. Data val => val -> Ann -> val
&= String -> Ann
help String
"File to write the export to. Default: stdout"
                Maybe String -> Ann -> Maybe String
forall val. Data val => val -> Ann -> val
&= Ann
explicit
                Maybe String -> Ann -> Maybe String
forall val. Data val => val -> Ann -> val
&= String -> Ann
name String
"output-file"
                Maybe String -> Ann -> Maybe String
forall val. Data val => val -> Ann -> val
&= String -> Ann
name String
"o"
                Maybe String -> Ann -> Maybe String
forall val. Data val => val -> Ann -> val
&= String -> Ann
typ String
"FILE"
        , argCoinTracking :: Bool
argCoinTracking =
            Bool
False
                Bool -> Ann -> Bool
forall val. Data val => val -> Ann -> val
&= String -> Ann
help String
"Generate a CoinTracking Import file."
                Bool -> Ann -> Bool
forall val. Data val => val -> Ann -> val
&= Ann
explicit
                Bool -> Ann -> Bool
forall val. Data val => val -> Ann -> val
&= String -> Ann
name String
"cointracking"
        , argYear :: Maybe Integer
argYear =
            Maybe Integer
forall a. Maybe a
Nothing
                Maybe Integer -> Ann -> Maybe Integer
forall val. Data val => val -> Ann -> val
&= String -> Ann
help String
"Limit to given year"
                Maybe Integer -> Ann -> Maybe Integer
forall val. Data val => val -> Ann -> val
&= Ann
explicit
                Maybe Integer -> Ann -> Maybe Integer
forall val. Data val => val -> Ann -> val
&= String -> Ann
name String
"y"
                Maybe Integer -> Ann -> Maybe Integer
forall val. Data val => val -> Ann -> val
&= String -> Ann
name String
"year"
                Maybe Integer -> Ann -> Maybe Integer
forall val. Data val => val -> Ann -> val
&= String -> Ann
typ String
"YYYY"
        , argAggregate :: Bool
argAggregate =
            Bool
False
                Bool -> Ann -> Bool
forall val. Data val => val -> Ann -> val
&= String -> Ann
help String
"Output one aggregate row per day."
                Bool -> Ann -> Bool
forall val. Data val => val -> Ann -> val
&= Ann
explicit
                Bool -> Ann -> Bool
forall val. Data val => val -> Ann -> val
&= String -> Ann
name String
"aggregate"
        }
        Args -> Ann -> Args
forall val. Data val => val -> Ann -> val
&= String -> Ann
summary
            ( String
"solana-staking-csvs v"
                String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Version -> String
showVersion Version
version
                String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
", Pavan Rikhi 2021-2024"
            )
        Args -> Ann -> Args
forall val. Data val => val -> Ann -> val
&= String -> Ann
program String
"solana-staking-csvs"
        Args -> Ann -> Args
forall val. Data val => val -> Ann -> val
&= [Ann] -> Ann
helpArg [String -> Ann
name String
"h"]
        Args -> Ann -> Args
forall val. Data val => val -> Ann -> val
&= String -> Ann
help String
"Generate CSV Exports of your Solana Staking Rewards."