{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}

{- |
Module      : Singular.Provider.Koios.Client
Description : Typed Koios calls over a chosen transport
License     : Apache-2.0

The Koios client: typed calls built from a 'Transport' and the request
descriptions and decoders of "Singular.Provider.Koios.Wire".

A transport carries one request to one raw answer — status, headers and
body — and nothing else. It does not page and it does not decode, so the
recorded transport ("Singular.Provider.Koios.Recorded") and the live
HTTP transport (@koios-http@) hand this module the same raw answers and
every answer is paged and decoded here, once. The status is classified
here too: 2xx is an answer, 429 is rate limited, 5xx is server failing,
any other status is refused by the server — except a 400 at @submittx@,
which is the server's refusal of the transaction.

Every call ends in its decoded answer or in one 'ClientFailure' naming
the call, the attempts the transport made and the evidence. An honestly
empty answer, such as an address with no outputs, is a success with an
empty list and is distinct from every failure. A requested fact absent
from an answer — a transaction, a registration, the tip — is an
'UnknownFact', never a default.

Paged calls ask for an exact row total (@Prefer: count=exact@) and are
read in a stable order, page after page, until the total is reached or a
page comes back shorter than the limit. A page that is cut off, a page
larger than the limit, a range that does not match the page asked for, a
total that is missing or changes between pages, rows missing before the
total, or more pages than the configured ceiling each refuse the whole
call: the client never returns a shorter answer as a whole one.
-}
module Singular.Provider.Koios.Client
    ( -- * Transport
      Transport (..)
    , RawRequest (..)
    , Retry (..)
    , Answer (..)
    , NoAnswer (..)
    , Exchange (..)
    , rawRequest

      -- * Client
    , Koios (..)
    , ClientConfig (..)
    , defaultClientConfig

      -- * Failures
    , ClientFailure (..)
    , FailureReason (..)
    , PageRefusal (..)
    , UnknownFact (..)

      -- * Calls
    , tip
    , addressUtxos
    , assetUtxos
    , assetTxs
    , Page
    , pageRows
    , nextPage
    , assetTxsPage
    , txInfo
    , txCbor
    , epochParams
    , cliProtocolParams
    , SubmitOutcome (..)
    , submitTx
    , txStatus
    , accountRegistered
    , referenceScriptUtxos
    , utxoInfo
    , assetInfo
    ) where

import Data.Aeson (Value)
import Data.Bifunctor (first)
import Data.ByteString (ByteString)
import Data.Foldable (toList)
import Data.List (find)
import Data.List.NonEmpty (NonEmpty)
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE

import Cardano.Ledger.Address (AccountAddress, Addr)
import Cardano.Ledger.Api.Tx.Out (TxOut)
import Cardano.Ledger.BaseTypes (EpochNo)
import Cardano.Ledger.Binary (serialize')
import Cardano.Ledger.Conway (ConwayEra)
import Cardano.Ledger.Core (PParams, eraProtVerHigh)
import Cardano.Ledger.Hashes (ScriptHash)
import Cardano.Ledger.Mary.Value (AssetName, PolicyID)
import Cardano.Ledger.TxIn (TxId, TxIn)

import Singular.Provider.Koios.Wire
    ( AccountStatus (..)
    , AssetInfo
    , AssetTx
    , Body (..)
    , Call (..)
    , DecodeFailure
    , Method
    , Request (..)
    , Tip
    , TxCbor (..)
    , TxInfo (..)
    , TxStatus (..)
    , UtxoInfo
    , accountInfoRequest
    , addressUtxosRequest
    , assetInfoRequest
    , assetTxsRequest
    , assetUtxosRequest
    , cliProtocolParamsRequest
    , decodeAccountStatuses
    , decodeAssetInfos
    , decodeAssetTxs
    , decodeCliProtocolParams
    , decodeEpochParams
    , decodeReferenceScriptUtxos
    , decodeSubmitted
    , decodeTip
    , decodeTxCbors
    , decodeTxInfos
    , decodeTxStatuses
    , decodeUtxoInfos
    , decodeUtxos
    , epochParamsRequest
    , parseBody
    , referenceScriptUtxosRequest
    , renderRewardAccount
    , submitTxRequest
    , tipRequest
    , txCborRequest
    , txInfoRequest
    , txStatusRequest
    , utxoInfoRequest
    )
import Singular.Registry.Signing (SignedTx, signedTx)

-- | How a transport may retry a request.
data Retry
    = {- | Retry unreachable, timed out, rate limited and server-failing
      answers within the transport's bounds
      -}
      RetryTransient
    | {- | Retry only when no answer arrived: unreachable or timed out.
      Submission uses this, since resending the same signed bytes cannot
      apply them twice, while a refusal or a server error is final.
      -}
      RetryUnanswered
    deriving stock (Retry -> Retry -> Bool
(Retry -> Retry -> Bool) -> (Retry -> Retry -> Bool) -> Eq Retry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Retry -> Retry -> Bool
== :: Retry -> Retry -> Bool
$c/= :: Retry -> Retry -> Bool
/= :: Retry -> Retry -> Bool
Eq, Int -> Retry -> ShowS
[Retry] -> ShowS
Retry -> String
(Int -> Retry -> ShowS)
-> (Retry -> String) -> ([Retry] -> ShowS) -> Show Retry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Retry -> ShowS
showsPrec :: Int -> Retry -> ShowS
$cshow :: Retry -> String
show :: Retry -> String
$cshowList :: [Retry] -> ShowS
showList :: [Retry] -> ShowS
Show)

-- | One HTTP request as the transport sends it.
data RawRequest = RawRequest
    { RawRequest -> Call
rawCall :: Call
    , RawRequest -> Method
rawMethod :: Method
    , RawRequest -> Text
rawPath :: Text
    , RawRequest -> [(Text, Text)]
rawQuery :: [(Text, Text)]
    , RawRequest -> [(Text, Text)]
rawHeaders :: [(Text, Text)]
    , RawRequest -> Body
rawBody :: Body
    , RawRequest -> Retry
rawRetry :: Retry
    }
    deriving stock (RawRequest -> RawRequest -> Bool
(RawRequest -> RawRequest -> Bool)
-> (RawRequest -> RawRequest -> Bool) -> Eq RawRequest
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RawRequest -> RawRequest -> Bool
== :: RawRequest -> RawRequest -> Bool
$c/= :: RawRequest -> RawRequest -> Bool
/= :: RawRequest -> RawRequest -> Bool
Eq, Int -> RawRequest -> ShowS
[RawRequest] -> ShowS
RawRequest -> String
(Int -> RawRequest -> ShowS)
-> (RawRequest -> String)
-> ([RawRequest] -> ShowS)
-> Show RawRequest
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RawRequest -> ShowS
showsPrec :: Int -> RawRequest -> ShowS
$cshow :: RawRequest -> String
show :: RawRequest -> String
$cshowList :: [RawRequest] -> ShowS
showList :: [RawRequest] -> ShowS
Show)

{- | The raw request of an unpaged request: its body's content type, and
retries limited to unanswered requests for a submission.
-}
rawRequest :: Request a -> RawRequest
rawRequest :: forall (a :: Access). Request a -> RawRequest
rawRequest Request a
r =
    RawRequest
        { rawCall :: Call
rawCall = Request a -> Call
forall (a :: Access). Request a -> Call
requestCall Request a
r
        , rawMethod :: Method
rawMethod = Request a -> Method
forall (a :: Access). Request a -> Method
requestMethod Request a
r
        , rawPath :: Text
rawPath = Request a -> Text
forall (a :: Access). Request a -> Text
requestPath Request a
r
        , rawQuery :: [(Text, Text)]
rawQuery = Request a -> [(Text, Text)]
forall (a :: Access). Request a -> [(Text, Text)]
requestQuery Request a
r
        , rawHeaders :: [(Text, Text)]
rawHeaders = (Text
"accept", Text
"application/json") (Text, Text) -> [(Text, Text)] -> [(Text, Text)]
forall a. a -> [a] -> [a]
: [(Text, Text)]
contentType
        , rawBody :: Body
rawBody = Request a -> Body
forall (a :: Access). Request a -> Body
requestBody Request a
r
        , rawRetry :: Retry
rawRetry =
            if Request a -> Call
forall (a :: Access). Request a -> Call
requestCall Request a
r Call -> Call -> Bool
forall a. Eq a => a -> a -> Bool
== Call
CallSubmitTx
                then Retry
RetryUnanswered
                else Retry
RetryTransient
        }
  where
    contentType :: [(Text, Text)]
contentType = case Request a -> Body
forall (a :: Access). Request a -> Body
requestBody Request a
r of
        Body
NoBody -> []
        JsonBody Value
_ -> [(Text
"content-type", Text
"application/json")]
        CborBody ByteString
_ -> [(Text
"content-type", Text
"application/cbor")]

-- | One raw answer: status, headers with lower-case names, and body.
data Answer = Answer
    { Answer -> Int
answerStatus :: Int
    , Answer -> [(Text, Text)]
answerHeaders :: [(Text, Text)]
    , Answer -> ByteString
answerBody :: ByteString
    }
    deriving stock (Answer -> Answer -> Bool
(Answer -> Answer -> Bool)
-> (Answer -> Answer -> Bool) -> Eq Answer
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Answer -> Answer -> Bool
== :: Answer -> Answer -> Bool
$c/= :: Answer -> Answer -> Bool
/= :: Answer -> Answer -> Bool
Eq, Int -> Answer -> ShowS
[Answer] -> ShowS
Answer -> String
(Int -> Answer -> ShowS)
-> (Answer -> String) -> ([Answer] -> ShowS) -> Show Answer
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Answer -> ShowS
showsPrec :: Int -> Answer -> ShowS
$cshow :: Answer -> String
show :: Answer -> String
$cshowList :: [Answer] -> ShowS
showList :: [Answer] -> ShowS
Show)

-- | Why a transport has no answer to return.
data NoAnswer
    = -- | Connection, DNS or TLS failure
      NoConnection Text
    | -- | No complete answer within the request timeout
      NoAnswerInTime
    | -- | The connection delivered fewer body bytes than announced
      BodyCutOff Text
    | -- | A recorded transport holds no answer for this request
      NoRecording Text
    deriving stock (NoAnswer -> NoAnswer -> Bool
(NoAnswer -> NoAnswer -> Bool)
-> (NoAnswer -> NoAnswer -> Bool) -> Eq NoAnswer
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: NoAnswer -> NoAnswer -> Bool
== :: NoAnswer -> NoAnswer -> Bool
$c/= :: NoAnswer -> NoAnswer -> Bool
/= :: NoAnswer -> NoAnswer -> Bool
Eq, Int -> NoAnswer -> ShowS
[NoAnswer] -> ShowS
NoAnswer -> String
(Int -> NoAnswer -> ShowS)
-> (NoAnswer -> String) -> ([NoAnswer] -> ShowS) -> Show NoAnswer
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> NoAnswer -> ShowS
showsPrec :: Int -> NoAnswer -> ShowS
$cshow :: NoAnswer -> String
show :: NoAnswer -> String
$cshowList :: [NoAnswer] -> ShowS
showList :: [NoAnswer] -> ShowS
Show)

-- | One request's outcome after the transport's retries.
data Exchange = Exchange
    { Exchange -> Int
exchangeAttempts :: Int
    , Exchange -> Either NoAnswer Answer
exchangeResult :: Either NoAnswer Answer
    }
    deriving stock (Exchange -> Exchange -> Bool
(Exchange -> Exchange -> Bool)
-> (Exchange -> Exchange -> Bool) -> Eq Exchange
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Exchange -> Exchange -> Bool
== :: Exchange -> Exchange -> Bool
$c/= :: Exchange -> Exchange -> Bool
/= :: Exchange -> Exchange -> Bool
Eq, Int -> Exchange -> ShowS
[Exchange] -> ShowS
Exchange -> String
(Int -> Exchange -> ShowS)
-> (Exchange -> String) -> ([Exchange] -> ShowS) -> Show Exchange
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Exchange -> ShowS
showsPrec :: Int -> Exchange -> ShowS
$cshow :: Exchange -> String
show :: Exchange -> String
$cshowList :: [Exchange] -> ShowS
showList :: [Exchange] -> ShowS
Show)

-- | One request to one raw answer, in any monad.
newtype Transport m = Transport
    { forall (m :: * -> *). Transport m -> RawRequest -> m Exchange
exchange :: RawRequest -> m Exchange
    }

-- | Paging configuration.
data ClientConfig = ClientConfig
    { ClientConfig -> Int
pageSize :: Int
    -- ^ Rows asked for per page
    , ClientConfig -> Int
pageCeiling :: Int
    -- ^ Most pages one call reads
    }
    deriving stock (ClientConfig -> ClientConfig -> Bool
(ClientConfig -> ClientConfig -> Bool)
-> (ClientConfig -> ClientConfig -> Bool) -> Eq ClientConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ClientConfig -> ClientConfig -> Bool
== :: ClientConfig -> ClientConfig -> Bool
$c/= :: ClientConfig -> ClientConfig -> Bool
/= :: ClientConfig -> ClientConfig -> Bool
Eq, Int -> ClientConfig -> ShowS
[ClientConfig] -> ShowS
ClientConfig -> String
(Int -> ClientConfig -> ShowS)
-> (ClientConfig -> String)
-> ([ClientConfig] -> ShowS)
-> Show ClientConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ClientConfig -> ShowS
showsPrec :: Int -> ClientConfig -> ShowS
$cshow :: ClientConfig -> String
show :: ClientConfig -> String
$cshowList :: [ClientConfig] -> ShowS
showList :: [ClientConfig] -> ShowS
Show)

-- | Pages of 1000 rows, Koios's maximum, and at most 100 pages.
defaultClientConfig :: ClientConfig
defaultClientConfig :: ClientConfig
defaultClientConfig = ClientConfig{pageSize :: Int
pageSize = Int
1000, pageCeiling :: Int
pageCeiling = Int
100}

-- | A Koios client: a paging configuration and a transport.
data Koios m = Koios
    { forall (m :: * -> *). Koios m -> ClientConfig
koiosConfig :: ClientConfig
    , forall (m :: * -> *). Koios m -> Transport m
koiosTransport :: Transport m
    }

-- | Why a call stopped.
data ClientFailure = ClientFailure
    { ClientFailure -> Call
failureCall :: Call
    , ClientFailure -> Int
failureAttempts :: Int
    -- ^ Attempts the transport made for the request that failed
    , ClientFailure -> FailureReason
failureReason :: FailureReason
    }
    deriving stock (ClientFailure -> ClientFailure -> Bool
(ClientFailure -> ClientFailure -> Bool)
-> (ClientFailure -> ClientFailure -> Bool) -> Eq ClientFailure
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ClientFailure -> ClientFailure -> Bool
== :: ClientFailure -> ClientFailure -> Bool
$c/= :: ClientFailure -> ClientFailure -> Bool
/= :: ClientFailure -> ClientFailure -> Bool
Eq, Int -> ClientFailure -> ShowS
[ClientFailure] -> ShowS
ClientFailure -> String
(Int -> ClientFailure -> ShowS)
-> (ClientFailure -> String)
-> ([ClientFailure] -> ShowS)
-> Show ClientFailure
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ClientFailure -> ShowS
showsPrec :: Int -> ClientFailure -> ShowS
$cshow :: ClientFailure -> String
show :: ClientFailure -> String
$cshowList :: [ClientFailure] -> ShowS
showList :: [ClientFailure] -> ShowS
Show)

-- | The closed list of reasons a call stops.
data FailureReason
    = -- | Connection, DNS or TLS failure past the retry bound
      Unreachable Text
    | -- | No complete answer within the timeout, past the retry bound
      TimedOut
    | {- | 429 past the retry bound or beyond the wait ceiling; the last
      wait asked, in seconds
      -}
      RateLimited (Maybe Int)
    | -- | 5xx past the retry bound: status and body
      ServerFailing Int Text
    | -- | Any other 4xx, including a rejected token: status and body
      RefusedByServer Int Text
    | -- | A page that cannot be part of a whole answer
      IncompletePage PageRefusal
    | -- | A requested fact absent from the answer
      UnknownFact UnknownFact
    | -- | An answer that does not decode
      Undecodable DecodeFailure
    | -- | The token file could not be read; no request was made
      TokenFileUnreadable FilePath Text
    | -- | The file's token is outside the accepted bearer domain; nothing was sent
      TokenFileInvalid FilePath Text
    | -- | A recorded transport holds no answer for the request
      NotRecorded Text
    deriving stock (FailureReason -> FailureReason -> Bool
(FailureReason -> FailureReason -> Bool)
-> (FailureReason -> FailureReason -> Bool) -> Eq FailureReason
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FailureReason -> FailureReason -> Bool
== :: FailureReason -> FailureReason -> Bool
$c/= :: FailureReason -> FailureReason -> Bool
/= :: FailureReason -> FailureReason -> Bool
Eq, Int -> FailureReason -> ShowS
[FailureReason] -> ShowS
FailureReason -> String
(Int -> FailureReason -> ShowS)
-> (FailureReason -> String)
-> ([FailureReason] -> ShowS)
-> Show FailureReason
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FailureReason -> ShowS
showsPrec :: Int -> FailureReason -> ShowS
$cshow :: FailureReason -> String
show :: FailureReason -> String
$cshowList :: [FailureReason] -> ShowS
showList :: [FailureReason] -> ShowS
Show)

-- | Why a page cannot be part of a whole answer.
data PageRefusal
    = -- | The body at this offset is cut off or not one JSON document
      PageCutOff Int Text
    | -- | The page at this offset has more rows than the limit: rows, limit
      PageOverLimit Int Int Int
    | -- | The total at this offset differs from the first page's: first, now
      TotalChanged Int Integer Integer
    | -- | The answer at this offset states no exact total: its range header
      TotalMissing Int (Maybe Text)
    | -- | The range header at this offset does not match the rows
      RangeMismatch Int Text
    | -- | Fewer rows than the stated total arrived: total, received
      RowsMissing Integer Integer
    | -- | The configured number of pages was read and more remain
      PageCeilingReached Int
    deriving stock (PageRefusal -> PageRefusal -> Bool
(PageRefusal -> PageRefusal -> Bool)
-> (PageRefusal -> PageRefusal -> Bool) -> Eq PageRefusal
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PageRefusal -> PageRefusal -> Bool
== :: PageRefusal -> PageRefusal -> Bool
$c/= :: PageRefusal -> PageRefusal -> Bool
/= :: PageRefusal -> PageRefusal -> Bool
Eq, Int -> PageRefusal -> ShowS
[PageRefusal] -> ShowS
PageRefusal -> String
(Int -> PageRefusal -> ShowS)
-> (PageRefusal -> String)
-> ([PageRefusal] -> ShowS)
-> Show PageRefusal
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PageRefusal -> ShowS
showsPrec :: Int -> PageRefusal -> ShowS
$cshow :: PageRefusal -> String
show :: PageRefusal -> String
$cshowList :: [PageRefusal] -> ShowS
showList :: [PageRefusal] -> ShowS
Show)

-- | A fact the answer does not contain.
data UnknownFact
    = UnknownTip
    | UnknownEpoch EpochNo
    | UnknownTransaction TxId
    | {- | The stake address had no row, or a status that is neither
      registered nor not registered
      -}
      UnknownRegistration Text (Maybe Text)
    deriving stock (UnknownFact -> UnknownFact -> Bool
(UnknownFact -> UnknownFact -> Bool)
-> (UnknownFact -> UnknownFact -> Bool) -> Eq UnknownFact
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: UnknownFact -> UnknownFact -> Bool
== :: UnknownFact -> UnknownFact -> Bool
$c/= :: UnknownFact -> UnknownFact -> Bool
/= :: UnknownFact -> UnknownFact -> Bool
Eq, Int -> UnknownFact -> ShowS
[UnknownFact] -> ShowS
UnknownFact -> String
(Int -> UnknownFact -> ShowS)
-> (UnknownFact -> String)
-> ([UnknownFact] -> ShowS)
-> Show UnknownFact
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> UnknownFact -> ShowS
showsPrec :: Int -> UnknownFact -> ShowS
$cshow :: UnknownFact -> String
show :: UnknownFact -> String
$cshowList :: [UnknownFact] -> ShowS
showList :: [UnknownFact] -> ShowS
Show)

-- ---------------------------------------------------------------------------
-- Exchanges
-- ---------------------------------------------------------------------------

-- | A failure of a call.
type Fails = Int -> FailureReason -> ClientFailure

failing :: Call -> Fails
failing :: Call -> Fails
failing = Call -> Fails
ClientFailure

-- | The answer of an exchange when its status is a success.
answerOf :: Fails -> Int -> Exchange -> Either ClientFailure Answer
answerOf :: Fails -> Int -> Exchange -> Either ClientFailure Answer
answerOf Fails
failure Int
offset Exchange
ex = case Exchange -> Either NoAnswer Answer
exchangeResult Exchange
ex of
    Left NoAnswer
none -> ClientFailure -> Either ClientFailure Answer
forall a b. a -> Either a b
Left (Fails
failure Int
attempts (NoAnswer -> FailureReason
noAnswer NoAnswer
none))
    Right Answer
a
        | Int
status Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
200 Bool -> Bool -> Bool
&& Int
status Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
300 -> Answer -> Either ClientFailure Answer
forall a b. b -> Either a b
Right Answer
a
        | Bool
otherwise -> ClientFailure -> Either ClientFailure Answer
forall a b. a -> Either a b
Left (Fails
failure Int
attempts (Answer -> FailureReason
refusal Answer
a))
      where
        status :: Int
status = Answer -> Int
answerStatus Answer
a
  where
    attempts :: Int
attempts = Exchange -> Int
exchangeAttempts Exchange
ex
    noAnswer :: NoAnswer -> FailureReason
noAnswer = \case
        NoConnection Text
t -> Text -> FailureReason
Unreachable Text
t
        NoAnswer
NoAnswerInTime -> FailureReason
TimedOut
        BodyCutOff Text
t -> PageRefusal -> FailureReason
IncompletePage (Int -> Text -> PageRefusal
PageCutOff Int
offset Text
t)
        NoRecording Text
t -> Text -> FailureReason
NotRecorded Text
t

-- | The failure an unsuccessful status names.
refusal :: Answer -> FailureReason
refusal :: Answer -> FailureReason
refusal Answer
a
    | Int
status Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
429 = Maybe Int -> FailureReason
RateLimited (Text -> Answer -> Maybe Text
header Text
"retry-after" Answer
a Maybe Text -> (Text -> Maybe Int) -> Maybe Int
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Maybe Int
readInt)
    | Int
status Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
500 = Int -> Text -> FailureReason
ServerFailing Int
status (Answer -> Text
bodyText Answer
a)
    | Bool
otherwise = Int -> Text -> FailureReason
RefusedByServer Int
status (Answer -> Text
bodyText Answer
a)
  where
    status :: Int
status = Answer -> Int
answerStatus Answer
a

header :: Text -> Answer -> Maybe Text
header :: Text -> Answer -> Maybe Text
header Text
name = Text -> [(Text, Text)] -> Maybe Text
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup Text
name ([(Text, Text)] -> Maybe Text)
-> (Answer -> [(Text, Text)]) -> Answer -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Answer -> [(Text, Text)]
answerHeaders

bodyText :: Answer -> Text
bodyText :: Answer -> Text
bodyText = ByteString -> Text
TE.decodeUtf8Lenient (ByteString -> Text) -> (Answer -> ByteString) -> Answer -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Answer -> ByteString
answerBody

readInt :: Text -> Maybe Int
readInt :: Text -> Maybe Int
readInt Text
t = case ReadS Int
forall a. Read a => ReadS a
reads (Text -> String
T.unpack (Text -> Text
T.strip Text
t)) of
    [(Int
n, String
"")] -> Int -> Maybe Int
forall a. a -> Maybe a
Just Int
n
    [(Int, String)]
_ -> Maybe Int
forall a. Maybe a
Nothing

-- | One unpaged request, decoded, with the attempts it took.
single
    :: (Monad m)
    => Koios m
    -> Request a
    -> (Value -> Either DecodeFailure b)
    -> m (Either ClientFailure (Int, b))
single :: forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k Request a
req Value -> Either DecodeFailure b
decode = do
    ex <- Transport m -> RawRequest -> m Exchange
forall (m :: * -> *). Transport m -> RawRequest -> m Exchange
exchange (Koios m -> Transport m
forall (m :: * -> *). Koios m -> Transport m
koiosTransport Koios m
k) (Request a -> RawRequest
forall (a :: Access). Request a -> RawRequest
rawRequest Request a
req)
    let failure = Call -> Fails
failing (Request a -> Call
forall (a :: Access). Request a -> Call
requestCall Request a
req)
        attempts = Exchange -> Int
exchangeAttempts Exchange
ex
        undecodable = Fails
failure Int
attempts (FailureReason -> ClientFailure)
-> (DecodeFailure -> FailureReason)
-> DecodeFailure
-> ClientFailure
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DecodeFailure -> FailureReason
Undecodable
    pure $ do
        a <- answerOf failure 0 ex
        v <- first undecodable (parseBody (answerBody a))
        (,) attempts <$> first undecodable (decode v)

{- | A checked page and its deferred continuation over the same request.
No subsequent HTTP exchange happens until the continuation is executed.
The continuation retains the first total and the configured page ceiling;
it cannot turn a cut-off answer into a shorter successful history.
-}
data Page m a = Page
    { forall (m :: * -> *) a. Page m a -> [a]
pageRows :: [a]
    -- ^ Rows on this page, after the shared decoder and range checks.
    , forall (m :: * -> *) a.
Page m a -> Maybe (m (Either ClientFailure (Page m a)))
nextPage :: Maybe (m (Either ClientFailure (Page m a)))
    -- ^ Nothing only when this page reaches the exact advertised total.
    }

-- | Convenience join of the same checked, incremental pages.
paged
    :: (Monad m)
    => Koios m
    -> Request a
    -> (Value -> Either DecodeFailure [b])
    -> m (Either ClientFailure [b])
paged :: forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure [b])
paged Koios m
k Request a
req Value -> Either DecodeFailure [b]
decode = Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure (Page m b))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure (Page m b))
firstPage Koios m
k Request a
req Value -> Either DecodeFailure [b]
decode m (Either ClientFailure (Page m b))
-> (Either ClientFailure (Page m b)
    -> m (Either ClientFailure [b]))
-> m (Either ClientFailure [b])
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [[b]]
-> Either ClientFailure (Page m b) -> m (Either ClientFailure [b])
forall {f :: * -> *} {a}.
Monad f =>
[[a]]
-> Either ClientFailure (Page f a) -> f (Either ClientFailure [a])
collect []
  where
    collect :: [[a]]
-> Either ClientFailure (Page f a) -> f (Either ClientFailure [a])
collect [[a]]
chunks = \case
        Left ClientFailure
failure -> Either ClientFailure [a] -> f (Either ClientFailure [a])
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ClientFailure -> Either ClientFailure [a]
forall a b. a -> Either a b
Left ClientFailure
failure)
        Right Page f a
page ->
            let chunks' :: [[a]]
chunks' = Page f a -> [a]
forall (m :: * -> *) a. Page m a -> [a]
pageRows Page f a
page [a] -> [[a]] -> [[a]]
forall a. a -> [a] -> [a]
: [[a]]
chunks
            in  case Page f a -> Maybe (f (Either ClientFailure (Page f a)))
forall (m :: * -> *) a.
Page m a -> Maybe (m (Either ClientFailure (Page m a)))
nextPage Page f a
page of
                    Maybe (f (Either ClientFailure (Page f a)))
Nothing -> Either ClientFailure [a] -> f (Either ClientFailure [a])
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([a] -> Either ClientFailure [a]
forall a b. b -> Either a b
Right ([[a]] -> [a]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[a]] -> [[a]]
forall a. [a] -> [a]
reverse [[a]]
chunks')))
                    Just f (Either ClientFailure (Page f a))
more -> f (Either ClientFailure (Page f a))
more f (Either ClientFailure (Page f a))
-> (Either ClientFailure (Page f a)
    -> f (Either ClientFailure [a]))
-> f (Either ClientFailure [a])
forall a b. f a -> (a -> f b) -> f b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [[a]]
-> Either ClientFailure (Page f a) -> f (Either ClientFailure [a])
collect [[a]]
chunks'

-- | Start a paged request using the single shared decoder/checking path.
firstPage
    :: (Monad m)
    => Koios m
    -> Request a
    -> (Value -> Either DecodeFailure [b])
    -> m (Either ClientFailure (Page m b))
firstPage :: forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure (Page m b))
firstPage Koios m
k Request a
req Value -> Either DecodeFailure [b]
decode = Int -> Int -> Maybe Integer -> m (Either ClientFailure (Page m b))
go Int
0 Int
0 Maybe Integer
forall a. Maybe a
Nothing
  where
    ClientConfig{pageSize :: ClientConfig -> Int
pageSize = Int
size, pageCeiling :: ClientConfig -> Int
pageCeiling = Int
ceiling'} = Koios m -> ClientConfig
forall (m :: * -> *). Koios m -> ClientConfig
koiosConfig Koios m
k
    failure :: Fails
failure = Call -> Fails
failing (Request a -> Call
forall (a :: Access). Request a -> Call
requestCall Request a
req)
    order :: [(Text, Text)]
order = [(Text, Text)]
-> (Text -> [(Text, Text)]) -> Maybe Text -> [(Text, Text)]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\Text
o -> [(Text
"order", Text
o)]) (Request a -> Maybe Text
forall (a :: Access). Request a -> Maybe Text
requestOrder Request a
req)
    pageRequest :: a -> RawRequest
pageRequest a
offset =
        let raw :: RawRequest
raw = Request a -> RawRequest
forall (a :: Access). Request a -> RawRequest
rawRequest Request a
req
        in  RawRequest
raw
                { rawQuery =
                    rawQuery raw
                        <> order
                        <> [ ("offset", T.pack (show offset))
                           , ("limit", T.pack (show size))
                           ]
                , rawHeaders = ("prefer", "count=exact") : rawHeaders raw
                }
    go :: Int -> Int -> Maybe Integer -> m (Either ClientFailure (Page m b))
go Int
pages Int
offset Maybe Integer
total
        | Int
pages Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
ceiling' =
            Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ClientFailure -> Either ClientFailure (Page m b)
forall a b. a -> Either a b
Left (Fails
failure Int
0 (PageRefusal -> FailureReason
IncompletePage (Int -> PageRefusal
PageCeilingReached Int
ceiling'))))
        | Bool
otherwise = do
            ex <- Transport m -> RawRequest -> m Exchange
forall (m :: * -> *). Transport m -> RawRequest -> m Exchange
exchange (Koios m -> Transport m
forall (m :: * -> *). Koios m -> Transport m
koiosTransport Koios m
k) (Int -> RawRequest
forall {a}. Show a => a -> RawRequest
pageRequest Int
offset)
            let attempts = Exchange -> Int
exchangeAttempts Exchange
ex
                refuse = ClientFailure -> Either ClientFailure b
forall a b. a -> Either a b
Left (ClientFailure -> Either ClientFailure b)
-> (PageRefusal -> ClientFailure)
-> PageRefusal
-> Either ClientFailure b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Fails
failure Int
attempts (FailureReason -> ClientFailure)
-> (PageRefusal -> FailureReason) -> PageRefusal -> ClientFailure
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PageRefusal -> FailureReason
IncompletePage
                page = do
                    a <- Fails -> Int -> Exchange -> Either ClientFailure Answer
answerOf Fails
failure Int
offset Exchange
ex
                    v <-
                        first
                            (failure attempts . IncompletePage . PageCutOff offset . reasonOf)
                            (parseBody (answerBody a))
                    rows <- first (failure attempts . Undecodable) (decode v)
                    pure (a, rows)
            case page of
                Left ClientFailure
f -> Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ClientFailure -> Either ClientFailure (Page m b)
forall a b. a -> Either a b
Left ClientFailure
f)
                Right (Answer
a, [b]
rows) ->
                    let n :: Int
n = [b] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [b]
rows
                        here :: Integer
here = Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
offset Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
n
                    in  case Answer -> Maybe (Maybe (Integer, Integer), Integer)
rangeOf Answer
a of
                            Maybe (Maybe (Integer, Integer), Integer)
_ | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
size -> Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (PageRefusal -> Either ClientFailure (Page m b)
forall {b}. PageRefusal -> Either ClientFailure b
refuse (Int -> Int -> Int -> PageRefusal
PageOverLimit Int
offset Int
n Int
size))
                            Maybe (Maybe (Integer, Integer), Integer)
Nothing ->
                                Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (PageRefusal -> Either ClientFailure (Page m b)
forall {b}. PageRefusal -> Either ClientFailure b
refuse (Int -> Maybe Text -> PageRefusal
TotalMissing Int
offset (Text -> Answer -> Maybe Text
header Text
"content-range" Answer
a)))
                            Just (Maybe (Integer, Integer)
span', Integer
t)
                                | Bool -> Bool
not (Int -> Int -> Maybe (Integer, Integer) -> Bool
spanMatches Int
offset Int
n Maybe (Integer, Integer)
span') Bool -> Bool -> Bool
|| Integer
here Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
t ->
                                    Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (PageRefusal -> Either ClientFailure (Page m b)
forall {b}. PageRefusal -> Either ClientFailure b
refuse (Int -> Text -> PageRefusal
RangeMismatch Int
offset (Answer -> Text
rangeText Answer
a)))
                                | Just Integer
t0 <- Maybe Integer
total
                                , Integer
t0 Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
/= Integer
t ->
                                    Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (PageRefusal -> Either ClientFailure (Page m b)
forall {b}. PageRefusal -> Either ClientFailure b
refuse (Int -> Integer -> Integer -> PageRefusal
TotalChanged Int
offset Integer
t0 Integer
t))
                                | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
size Bool -> Bool -> Bool
|| Integer
here Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
t ->
                                    if Integer
here Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
t
                                        then Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Page m b -> Either ClientFailure (Page m b)
forall a b. b -> Either a b
Right ([b] -> Maybe (m (Either ClientFailure (Page m b))) -> Page m b
forall (m :: * -> *) a.
[a] -> Maybe (m (Either ClientFailure (Page m a))) -> Page m a
Page [b]
rows Maybe (m (Either ClientFailure (Page m b)))
forall a. Maybe a
Nothing))
                                        else Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (PageRefusal -> Either ClientFailure (Page m b)
forall {b}. PageRefusal -> Either ClientFailure b
refuse (Integer -> Integer -> PageRefusal
RowsMissing Integer
t Integer
here))
                                | Bool
otherwise ->
                                    Either ClientFailure (Page m b)
-> m (Either ClientFailure (Page m b))
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
                                        ( Page m b -> Either ClientFailure (Page m b)
forall a b. b -> Either a b
Right
                                            ( [b] -> Maybe (m (Either ClientFailure (Page m b))) -> Page m b
forall (m :: * -> *) a.
[a] -> Maybe (m (Either ClientFailure (Page m a))) -> Page m a
Page
                                                [b]
rows
                                                (m (Either ClientFailure (Page m b))
-> Maybe (m (Either ClientFailure (Page m b)))
forall a. a -> Maybe a
Just (Int -> Int -> Maybe Integer -> m (Either ClientFailure (Page m b))
go (Int
pages Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int
offset Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
size) (Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
t)))
                                            )
                                        )
    reasonOf :: DecodeFailure -> Text
reasonOf = String -> Text
T.pack (String -> Text)
-> (DecodeFailure -> String) -> DecodeFailure -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DecodeFailure -> String
forall a. Show a => a -> String
show
    rangeText :: Answer -> Text
rangeText = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
"" (Maybe Text -> Text) -> (Answer -> Maybe Text) -> Answer -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Answer -> Maybe Text
header Text
"content-range"

{- | The span and exact total of a @Content-Range@ header: @a-b/T@, or
@*/T@ for an empty page. 'Nothing' when absent or when the total is not
exact.
-}
rangeOf :: Answer -> Maybe (Maybe (Integer, Integer), Integer)
rangeOf :: Answer -> Maybe (Maybe (Integer, Integer), Integer)
rangeOf Answer
a = do
    range <- Text -> Answer -> Maybe Text
header Text
"content-range" Answer
a
    let (span', rest) = T.breakOn "/" range
    total <- readInteger (T.drop 1 rest)
    case span' of
        Text
"*" -> (Maybe (Integer, Integer), Integer)
-> Maybe (Maybe (Integer, Integer), Integer)
forall a. a -> Maybe a
Just (Maybe (Integer, Integer)
forall a. Maybe a
Nothing, Integer
total)
        Text
_ ->
            let (Text
from, Text
to) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"-" Text
span'
            in  do
                    f <- Text -> Maybe Integer
forall {a}. Read a => Text -> Maybe a
readInteger Text
from
                    t <- readInteger (T.drop 1 to)
                    Just (Just (f, t), total)
  where
    readInteger :: Text -> Maybe a
readInteger Text
t = case ReadS a
forall a. Read a => ReadS a
reads (Text -> String
T.unpack Text
t) of
        [(a
n, String
"")] -> a -> Maybe a
forall a. a -> Maybe a
Just a
n
        [(a, String)]
_ -> Maybe a
forall a. Maybe a
Nothing

-- | Whether a range span names exactly the rows at an offset.
spanMatches :: Int -> Int -> Maybe (Integer, Integer) -> Bool
spanMatches :: Int -> Int -> Maybe (Integer, Integer) -> Bool
spanMatches Int
offset Int
n = \case
    Maybe (Integer, Integer)
Nothing -> Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0
    Just (Integer
from, Integer
to) ->
        Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
            Bool -> Bool -> Bool
&& Integer
from Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
offset
            Bool -> Bool -> Bool
&& Integer
to Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
offset Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Int -> Integer
forall a. Integral a => a -> Integer
toInteger Int
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1

-- | The first row, or the unknown fact.
firstOr
    :: Fails
    -> UnknownFact
    -> Either ClientFailure (Int, [a])
    -> Either ClientFailure a
firstOr :: forall a.
Fails
-> UnknownFact
-> Either ClientFailure (Int, [a])
-> Either ClientFailure a
firstOr Fails
failure UnknownFact
unknown = \case
    Left ClientFailure
f -> ClientFailure -> Either ClientFailure a
forall a b. a -> Either a b
Left ClientFailure
f
    Right (Int
_, a
x : [a]
_) -> a -> Either ClientFailure a
forall a b. b -> Either a b
Right a
x
    Right (Int
attempts, []) -> ClientFailure -> Either ClientFailure a
forall a b. a -> Either a b
Left (Fails
failure Int
attempts (UnknownFact -> FailureReason
UnknownFact UnknownFact
unknown))

-- | Each asked transaction's row, in the order asked.
eachAsked
    :: Fails
    -> (a -> TxId)
    -> [TxId]
    -> Either ClientFailure (Int, [a])
    -> Either ClientFailure [a]
eachAsked :: forall a.
Fails
-> (a -> TxId)
-> [TxId]
-> Either ClientFailure (Int, [a])
-> Either ClientFailure [a]
eachAsked Fails
failure a -> TxId
key [TxId]
ids Either ClientFailure (Int, [a])
result = do
    (attempts, found) <- Either ClientFailure (Int, [a])
result
    traverse
        ( \TxId
i -> case (a -> Bool) -> [a] -> Maybe a
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((TxId -> TxId -> Bool
forall a. Eq a => a -> a -> Bool
== TxId
i) (TxId -> Bool) -> (a -> TxId) -> a -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> TxId
key) [a]
found of
            Just a
row -> a -> Either ClientFailure a
forall a b. b -> Either a b
Right a
row
            Maybe a
Nothing -> ClientFailure -> Either ClientFailure a
forall a b. a -> Either a b
Left (Fails
failure Int
attempts (UnknownFact -> FailureReason
UnknownFact (TxId -> UnknownFact
UnknownTransaction TxId
i)))
        )
        ids

-- ---------------------------------------------------------------------------
-- Calls
-- ---------------------------------------------------------------------------

-- | The chain tip.
tip :: (Monad m) => Koios m -> m (Either ClientFailure Tip)
tip :: forall (m :: * -> *).
Monad m =>
Koios m -> m (Either ClientFailure Tip)
tip Koios m
k =
    Fails
-> UnknownFact
-> Either ClientFailure (Int, [Tip])
-> Either ClientFailure Tip
forall a.
Fails
-> UnknownFact
-> Either ClientFailure (Int, [a])
-> Either ClientFailure a
firstOr (Call -> Fails
failing Call
CallTip) UnknownFact
UnknownTip (Either ClientFailure (Int, [Tip]) -> Either ClientFailure Tip)
-> m (Either ClientFailure (Int, [Tip]))
-> m (Either ClientFailure Tip)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [Tip])
-> m (Either ClientFailure (Int, [Tip]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k Request 'Read
tipRequest Value -> Either DecodeFailure [Tip]
decodeTip

-- | Every output at the addresses, read page by page.
addressUtxos
    :: (Monad m)
    => Koios m
    -> [Addr]
    -> m (Either ClientFailure [(TxIn, TxOut ConwayEra)])
addressUtxos :: forall (m :: * -> *).
Monad m =>
Koios m
-> [Addr] -> m (Either ClientFailure [(TxIn, TxOut ConwayEra)])
addressUtxos Koios m
k [Addr]
addrs = Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [(TxIn, BabbageTxOut ConwayEra)])
-> m (Either ClientFailure [(TxIn, BabbageTxOut ConwayEra)])
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure [b])
paged Koios m
k ([Addr] -> Request 'Read
addressUtxosRequest [Addr]
addrs) Value -> Either DecodeFailure [(TxIn, TxOut ConwayEra)]
Value -> Either DecodeFailure [(TxIn, BabbageTxOut ConwayEra)]
decodeUtxos

-- | Every output holding the assets, read page by page.
assetUtxos
    :: (Monad m)
    => Koios m
    -> [(PolicyID, AssetName)]
    -> m (Either ClientFailure [(TxIn, TxOut ConwayEra)])
assetUtxos :: forall (m :: * -> *).
Monad m =>
Koios m
-> [(PolicyID, AssetName)]
-> m (Either ClientFailure [(TxIn, TxOut ConwayEra)])
assetUtxos Koios m
k [(PolicyID, AssetName)]
assets = Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [(TxIn, BabbageTxOut ConwayEra)])
-> m (Either ClientFailure [(TxIn, BabbageTxOut ConwayEra)])
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure [b])
paged Koios m
k ([(PolicyID, AssetName)] -> Request 'Read
assetUtxosRequest [(PolicyID, AssetName)]
assets) Value -> Either DecodeFailure [(TxIn, TxOut ConwayEra)]
Value -> Either DecodeFailure [(TxIn, BabbageTxOut ConwayEra)]
decodeUtxos

{- | Every transaction that included the asset, read page by page, in
block height then transaction id order. That order makes pages stable;
it is not the order in which the asset was passed on.
-}
assetTxs
    :: (Monad m)
    => Koios m
    -> PolicyID
    -> AssetName
    -> m (Either ClientFailure [AssetTx])
assetTxs :: forall (m :: * -> *).
Monad m =>
Koios m
-> PolicyID -> AssetName -> m (Either ClientFailure [AssetTx])
assetTxs Koios m
k PolicyID
p AssetName
n = Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [AssetTx])
-> m (Either ClientFailure [AssetTx])
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure [b])
paged Koios m
k (PolicyID -> AssetName -> Request 'Read
assetTxsRequest PolicyID
p AssetName
n) Value -> Either DecodeFailure [AssetTx]
decodeAssetTxs

{- | Start the asset transaction listing without joining its entire history.
Each continuation uses the same Wire decoder and range/total/refusal
checks as 'assetTxs'. HTTP ordering is stable height/hash order; consumers
must assemble a whole block and resolve spend dependencies before replay.
-}
assetTxsPage
    :: (Monad m)
    => Koios m
    -> PolicyID
    -> AssetName
    -> m (Either ClientFailure (Page m AssetTx))
assetTxsPage :: forall (m :: * -> *).
Monad m =>
Koios m
-> PolicyID
-> AssetName
-> m (Either ClientFailure (Page m AssetTx))
assetTxsPage Koios m
k PolicyID
p AssetName
n = Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [AssetTx])
-> m (Either ClientFailure (Page m AssetTx))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure (Page m b))
firstPage Koios m
k (PolicyID -> AssetName -> Request 'Read
assetTxsRequest PolicyID
p AssetName
n) Value -> Either DecodeFailure [AssetTx]
decodeAssetTxs

-- | The named transactions, in the order asked.
txInfo
    :: (Monad m) => Koios m -> [TxId] -> m (Either ClientFailure [TxInfo])
txInfo :: forall (m :: * -> *).
Monad m =>
Koios m -> [TxId] -> m (Either ClientFailure [TxInfo])
txInfo Koios m
k [TxId]
ids =
    Fails
-> (TxInfo -> TxId)
-> [TxId]
-> Either ClientFailure (Int, [TxInfo])
-> Either ClientFailure [TxInfo]
forall a.
Fails
-> (a -> TxId)
-> [TxId]
-> Either ClientFailure (Int, [a])
-> Either ClientFailure [a]
eachAsked (Call -> Fails
failing Call
CallTxInfo) TxInfo -> TxId
txInfoId [TxId]
ids
        (Either ClientFailure (Int, [TxInfo])
 -> Either ClientFailure [TxInfo])
-> m (Either ClientFailure (Int, [TxInfo]))
-> m (Either ClientFailure [TxInfo])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [TxInfo])
-> m (Either ClientFailure (Int, [TxInfo]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k ([TxId] -> Request 'Read
txInfoRequest [TxId]
ids) Value -> Either DecodeFailure [TxInfo]
decodeTxInfos

-- | The named transactions' bytes, in the order asked.
txCbor
    :: (Monad m) => Koios m -> [TxId] -> m (Either ClientFailure [TxCbor])
txCbor :: forall (m :: * -> *).
Monad m =>
Koios m -> [TxId] -> m (Either ClientFailure [TxCbor])
txCbor Koios m
k [TxId]
ids =
    Fails
-> (TxCbor -> TxId)
-> [TxId]
-> Either ClientFailure (Int, [TxCbor])
-> Either ClientFailure [TxCbor]
forall a.
Fails
-> (a -> TxId)
-> [TxId]
-> Either ClientFailure (Int, [a])
-> Either ClientFailure [a]
eachAsked (Call -> Fails
failing Call
CallTxCbor) TxCbor -> TxId
txCborId [TxId]
ids
        (Either ClientFailure (Int, [TxCbor])
 -> Either ClientFailure [TxCbor])
-> m (Either ClientFailure (Int, [TxCbor]))
-> m (Either ClientFailure [TxCbor])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [TxCbor])
-> m (Either ClientFailure (Int, [TxCbor]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k ([TxId] -> Request 'Read
txCborRequest [TxId]
ids) Value -> Either DecodeFailure [TxCbor]
decodeTxCbors

-- | The protocol parameters of an epoch.
epochParams
    :: (Monad m)
    => Koios m
    -> EpochNo
    -> m (Either ClientFailure (PParams ConwayEra))
epochParams :: forall (m :: * -> *).
Monad m =>
Koios m -> EpochNo -> m (Either ClientFailure (PParams ConwayEra))
epochParams Koios m
k EpochNo
e =
    Fails
-> UnknownFact
-> Either ClientFailure (Int, [PParams ConwayEra])
-> Either ClientFailure (PParams ConwayEra)
forall a.
Fails
-> UnknownFact
-> Either ClientFailure (Int, [a])
-> Either ClientFailure a
firstOr (Call -> Fails
failing Call
CallEpochParams) (EpochNo -> UnknownFact
UnknownEpoch EpochNo
e)
        (Either ClientFailure (Int, [PParams ConwayEra])
 -> Either ClientFailure (PParams ConwayEra))
-> m (Either ClientFailure (Int, [PParams ConwayEra]))
-> m (Either ClientFailure (PParams ConwayEra))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [PParams ConwayEra])
-> m (Either ClientFailure (Int, [PParams ConwayEra]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k (EpochNo -> Request 'Read
epochParamsRequest EpochNo
e) Value -> Either DecodeFailure [PParams ConwayEra]
decodeEpochParams

-- | The current protocol parameters in the node's own JSON form.
cliProtocolParams
    :: (Monad m)
    => Koios m
    -> m (Either ClientFailure (PParams ConwayEra))
cliProtocolParams :: forall (m :: * -> *).
Monad m =>
Koios m -> m (Either ClientFailure (PParams ConwayEra))
cliProtocolParams Koios m
k =
    ((Int, PParams ConwayEra) -> PParams ConwayEra)
-> Either ClientFailure (Int, PParams ConwayEra)
-> Either ClientFailure (PParams ConwayEra)
forall a b.
(a -> b) -> Either ClientFailure a -> Either ClientFailure b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int, PParams ConwayEra) -> PParams ConwayEra
forall a b. (a, b) -> b
snd (Either ClientFailure (Int, PParams ConwayEra)
 -> Either ClientFailure (PParams ConwayEra))
-> m (Either ClientFailure (Int, PParams ConwayEra))
-> m (Either ClientFailure (PParams ConwayEra))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure (PParams ConwayEra))
-> m (Either ClientFailure (Int, PParams ConwayEra))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k Request 'Read
cliProtocolParamsRequest Value -> Either DecodeFailure (PParams ConwayEra)
decodeCliProtocolParams

-- | What the server did with a submitted transaction.
data SubmitOutcome
    = -- | Accepted, with the transaction id Koios returned
      SubmitAccepted TxId
    | -- | Refused (400), with the server's reason
      SubmitRefused Text
    deriving stock (SubmitOutcome -> SubmitOutcome -> Bool
(SubmitOutcome -> SubmitOutcome -> Bool)
-> (SubmitOutcome -> SubmitOutcome -> Bool) -> Eq SubmitOutcome
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SubmitOutcome -> SubmitOutcome -> Bool
== :: SubmitOutcome -> SubmitOutcome -> Bool
$c/= :: SubmitOutcome -> SubmitOutcome -> Bool
/= :: SubmitOutcome -> SubmitOutcome -> Bool
Eq, Int -> SubmitOutcome -> ShowS
[SubmitOutcome] -> ShowS
SubmitOutcome -> String
(Int -> SubmitOutcome -> ShowS)
-> (SubmitOutcome -> String)
-> ([SubmitOutcome] -> ShowS)
-> Show SubmitOutcome
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SubmitOutcome -> ShowS
showsPrec :: Int -> SubmitOutcome -> ShowS
$cshow :: SubmitOutcome -> String
show :: SubmitOutcome -> String
$cshowList :: [SubmitOutcome] -> ShowS
showList :: [SubmitOutcome] -> ShowS
Show)

{- | Submit a 'SignedTx', whose hidden constructor requires a payment-key
witness through 'Singular.Registry.Signing.signTx'. Serialize that
signed transaction as Conway CBOR without changing its body or witnesses.
A 400 is the server's refusal of the transaction and is returned as its
text; the transport retries the submission only when no answer arrived.
-}
submitTx
    :: (Monad m)
    => Koios m
    -> SignedTx
    -> m (Either ClientFailure SubmitOutcome)
submitTx :: forall (m :: * -> *).
Monad m =>
Koios m -> SignedTx -> m (Either ClientFailure SubmitOutcome)
submitTx Koios m
k SignedTx
tx = do
    let bytes :: ByteString
bytes = Version -> ConwayTx -> ByteString
forall a. EncCBOR a => Version -> a -> ByteString
serialize' (forall era. Era era => Version
eraProtVerHigh @ConwayEra) (SignedTx -> ConwayTx
signedTx SignedTx
tx)
    ex <- Transport m -> RawRequest -> m Exchange
forall (m :: * -> *). Transport m -> RawRequest -> m Exchange
exchange (Koios m -> Transport m
forall (m :: * -> *). Koios m -> Transport m
koiosTransport Koios m
k) (Request 'Submission -> RawRequest
forall (a :: Access). Request a -> RawRequest
rawRequest (ByteString -> Request 'Submission
submitTxRequest ByteString
bytes))
    let failure = Call -> Fails
failing Call
CallSubmitTx
        attempts = Exchange -> Int
exchangeAttempts Exchange
ex
    pure $ case exchangeResult ex of
        Right Answer
a
            | Answer -> Int
answerStatus Answer
a Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
400 -> SubmitOutcome -> Either ClientFailure SubmitOutcome
forall a b. b -> Either a b
Right (Text -> SubmitOutcome
SubmitRefused (Answer -> Text
bodyText Answer
a))
        Either NoAnswer Answer
_ -> do
            a <- Fails -> Int -> Exchange -> Either ClientFailure Answer
answerOf Fails
failure Int
0 Exchange
ex
            first
                (failure attempts . Undecodable)
                (SubmitAccepted <$> decodeSubmitted (answerBody a))

{- | Confirmations of the named transactions, in the order asked. A
transaction absent from the answer is not yet seen.
-}
txStatus
    :: (Monad m)
    => Koios m
    -> [TxId]
    -> m (Either ClientFailure [TxStatus])
txStatus :: forall (m :: * -> *).
Monad m =>
Koios m -> [TxId] -> m (Either ClientFailure [TxStatus])
txStatus Koios m
k [TxId]
ids =
    ((Int, [TxStatus]) -> [TxStatus])
-> Either ClientFailure (Int, [TxStatus])
-> Either ClientFailure [TxStatus]
forall a b.
(a -> b) -> Either ClientFailure a -> Either ClientFailure b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([TxStatus] -> [TxStatus]
forall {t :: * -> *}. Foldable t => t TxStatus -> [TxStatus]
complete ([TxStatus] -> [TxStatus])
-> ((Int, [TxStatus]) -> [TxStatus])
-> (Int, [TxStatus])
-> [TxStatus]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, [TxStatus]) -> [TxStatus]
forall a b. (a, b) -> b
snd)
        (Either ClientFailure (Int, [TxStatus])
 -> Either ClientFailure [TxStatus])
-> m (Either ClientFailure (Int, [TxStatus]))
-> m (Either ClientFailure [TxStatus])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [TxStatus])
-> m (Either ClientFailure (Int, [TxStatus]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k ([TxId] -> Request 'Read
txStatusRequest [TxId]
ids) Value -> Either DecodeFailure [TxStatus]
decodeTxStatuses
  where
    complete :: t TxStatus -> [TxStatus]
complete t TxStatus
found =
        [ TxStatus -> Maybe TxStatus -> TxStatus
forall a. a -> Maybe a -> a
fromMaybe (TxId -> Maybe Word64 -> TxStatus
TxStatus TxId
i Maybe Word64
forall a. Maybe a
Nothing) ((TxStatus -> Bool) -> t TxStatus -> Maybe TxStatus
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((TxId -> TxId -> Bool
forall a. Eq a => a -> a -> Bool
== TxId
i) (TxId -> Bool) -> (TxStatus -> TxId) -> TxStatus -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TxStatus -> TxId
txStatusId) t TxStatus
found)
        | TxId
i <- [TxId]
ids
        ]

{- | Whether the reward account is registered: 'True' or 'False' only
when Koios says registered or not registered; no row, or any other
status, is an 'UnknownRegistration'.
-}
accountRegistered
    :: (Monad m)
    => Koios m
    -> AccountAddress
    -> m (Either ClientFailure Bool)
accountRegistered :: forall (m :: * -> *).
Monad m =>
Koios m -> AccountAddress -> m (Either ClientFailure Bool)
accountRegistered Koios m
k AccountAddress
account = do
    result <- Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [AccountStatus])
-> m (Either ClientFailure (Int, [AccountStatus]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k (AccountAddress -> Request 'Read
accountInfoRequest AccountAddress
account) Value -> Either DecodeFailure [AccountStatus]
decodeAccountStatuses
    let stake = AccountAddress -> Text
renderRewardAccount AccountAddress
account
    pure $ do
        (attempts, statuses) <- result
        let unknown =
                ClientFailure -> Either ClientFailure b
forall a b. a -> Either a b
Left
                    (ClientFailure -> Either ClientFailure b)
-> (Maybe Text -> ClientFailure)
-> Maybe Text
-> Either ClientFailure b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Call -> Fails
failing Call
CallAccountInfo Int
attempts
                    (FailureReason -> ClientFailure)
-> (Maybe Text -> FailureReason) -> Maybe Text -> ClientFailure
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UnknownFact -> FailureReason
UnknownFact
                    (UnknownFact -> FailureReason)
-> (Maybe Text -> UnknownFact) -> Maybe Text -> FailureReason
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Maybe Text -> UnknownFact
UnknownRegistration Text
stake
        case find ((== stake) . accountStakeAddress) statuses of
            Maybe AccountStatus
Nothing -> Maybe Text -> Either ClientFailure Bool
forall {b}. Maybe Text -> Either ClientFailure b
unknown Maybe Text
forall a. Maybe a
Nothing
            Just AccountStatus{accountStatus :: AccountStatus -> Text
accountStatus = Text
s}
                | Text
s Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"registered" -> Bool -> Either ClientFailure Bool
forall a b. b -> Either a b
Right Bool
True
                | Text
s Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"not registered" -> Bool -> Either ClientFailure Bool
forall a b. b -> Either a b
Right Bool
False
                | Bool
otherwise -> Maybe Text -> Either ClientFailure Bool
forall {b}. Maybe Text -> Either ClientFailure b
unknown (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
s)

{- | Every live output whose reference script is one of the hashes, with
the hash Koios names for it, read page by page. An empty answer means
not found by this provider.
-}
referenceScriptUtxos
    :: (Monad m)
    => Koios m
    -> NonEmpty ScriptHash
    -> m (Either ClientFailure [(ScriptHash, TxIn)])
referenceScriptUtxos :: forall (m :: * -> *).
Monad m =>
Koios m
-> NonEmpty ScriptHash
-> m (Either ClientFailure [(ScriptHash, TxIn)])
referenceScriptUtxos Koios m
k NonEmpty ScriptHash
hashes =
    Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [(ScriptHash, TxIn)])
-> m (Either ClientFailure [(ScriptHash, TxIn)])
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure [b])
-> m (Either ClientFailure [b])
paged
        Koios m
k
        ([ScriptHash] -> Request 'Read
referenceScriptUtxosRequest (NonEmpty ScriptHash -> [ScriptHash]
forall a. NonEmpty a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList NonEmpty ScriptHash
hashes))
        Value -> Either DecodeFailure [(ScriptHash, TxIn)]
decodeReferenceScriptUtxos

-- | Whether each named output is spent, as Koios reports it.
utxoInfo
    :: (Monad m)
    => Koios m
    -> NonEmpty TxIn
    -> m (Either ClientFailure [UtxoInfo])
utxoInfo :: forall (m :: * -> *).
Monad m =>
Koios m -> NonEmpty TxIn -> m (Either ClientFailure [UtxoInfo])
utxoInfo Koios m
k NonEmpty TxIn
references =
    ((Int, [UtxoInfo]) -> [UtxoInfo])
-> Either ClientFailure (Int, [UtxoInfo])
-> Either ClientFailure [UtxoInfo]
forall a b.
(a -> b) -> Either ClientFailure a -> Either ClientFailure b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int, [UtxoInfo]) -> [UtxoInfo]
forall a b. (a, b) -> b
snd
        (Either ClientFailure (Int, [UtxoInfo])
 -> Either ClientFailure [UtxoInfo])
-> m (Either ClientFailure (Int, [UtxoInfo]))
-> m (Either ClientFailure [UtxoInfo])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [UtxoInfo])
-> m (Either ClientFailure (Int, [UtxoInfo]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k ([TxIn] -> Request 'Read
utxoInfoRequest (NonEmpty TxIn -> [TxIn]
forall a. NonEmpty a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList NonEmpty TxIn
references)) Value -> Either DecodeFailure [UtxoInfo]
decodeUtxoInfos

-- | The asset's latest minting transaction and supply, or no row.
assetInfo
    :: (Monad m)
    => Koios m
    -> (PolicyID, AssetName)
    -> m (Either ClientFailure (Maybe AssetInfo))
assetInfo :: forall (m :: * -> *).
Monad m =>
Koios m
-> (PolicyID, AssetName)
-> m (Either ClientFailure (Maybe AssetInfo))
assetInfo Koios m
k (PolicyID, AssetName)
asset =
    ((Int, [AssetInfo]) -> Maybe AssetInfo)
-> Either ClientFailure (Int, [AssetInfo])
-> Either ClientFailure (Maybe AssetInfo)
forall a b.
(a -> b) -> Either ClientFailure a -> Either ClientFailure b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([AssetInfo] -> Maybe AssetInfo
forall a. [a] -> Maybe a
listToMaybe ([AssetInfo] -> Maybe AssetInfo)
-> ((Int, [AssetInfo]) -> [AssetInfo])
-> (Int, [AssetInfo])
-> Maybe AssetInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, [AssetInfo]) -> [AssetInfo]
forall a b. (a, b) -> b
snd)
        (Either ClientFailure (Int, [AssetInfo])
 -> Either ClientFailure (Maybe AssetInfo))
-> m (Either ClientFailure (Int, [AssetInfo]))
-> m (Either ClientFailure (Maybe AssetInfo))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Koios m
-> Request 'Read
-> (Value -> Either DecodeFailure [AssetInfo])
-> m (Either ClientFailure (Int, [AssetInfo]))
forall (m :: * -> *) (a :: Access) b.
Monad m =>
Koios m
-> Request a
-> (Value -> Either DecodeFailure b)
-> m (Either ClientFailure (Int, b))
single Koios m
k ((PolicyID, AssetName) -> Request 'Read
assetInfoRequest (PolicyID, AssetName)
asset) Value -> Either DecodeFailure [AssetInfo]
decodeAssetInfos