diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,27 @@
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## 0.18.0
+### Changed
+- Simplified limits and start point handling on web API.
+- Made transaction streaming algorithm faster for xpub transactions.
+- Extended public key summary output contains all addresses that have received transactions.
+
+### Added
+- Fine-grained control for maximum limits via command line options.
+- Transaction hash as starting point.
+- Block hash as starting point.
+- Timestamp as starting point.
+- Configurable xpub gap limit.
+- Transaction count added to XPub Summary.
+- UTXO count added to XPub Summary.
+
+### Removed
+- Mempool endpoint now has no limits or offsets and always returns full list.
+- Extended public key summary output no longer includes any transactions.
+- Offsets not allowed for transaction lists involving multiple addresses or xpubs.
+- Confusing block position parameter no longer part of web API.
+
 ## 0.17.2
 ### Changed
 - Stream address balances and unspent outputs.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -45,15 +45,15 @@
 import           Web.Scotty.Trans
 
 data Config = Config
-    { configDir      :: !FilePath
-    , configPort     :: !Int
-    , configNetwork  :: !Network
-    , configDiscover :: !Bool
-    , configPeers    :: ![(Host, Maybe Port)]
-    , configVersion  :: !Bool
-    , configCache    :: !FilePath
-    , configDebug    :: !Bool
-    , configMaxCount :: !Word32
+    { configDir       :: !FilePath
+    , configPort      :: !Int
+    , configNetwork   :: !Network
+    , configDiscover  :: !Bool
+    , configPeers     :: ![(Host, Maybe Port)]
+    , configVersion   :: !Bool
+    , configCache     :: !FilePath
+    , configDebug     :: !Bool
+    , configMaxLimits :: !MaxLimits
     }
 
 defPort :: Int
@@ -65,8 +65,15 @@
 netNames :: String
 netNames = intercalate "|" (map getNetworkName allNets)
 
-defMaxCount :: Word32
-defMaxCount = 10000
+defMaxLimits :: MaxLimits
+defMaxLimits =
+    MaxLimits
+        { maxLimitCount = 10000
+        , maxLimitFull = 500
+        , maxLimitOffset = 50000
+        , maxLimitDefault = 100
+        , maxLimitGap = 20
+        }
 
 config :: Parser Config
 config = do
@@ -93,17 +100,39 @@
         help "Network peer (as many as required)"
     configCache <-
         option str $
-        long "cache" <> short 'c' <> help "Memory mapped disk for cache" <>
+        long "cache" <> short 'c' <> help "RAM drive directory for acceleration" <>
         value ""
     configVersion <- switch $ long "version" <> short 'v' <> help "Show version"
     configDebug <- switch $ long "debug" <> help "Show debug messages"
-    configMaxCount <-
+    maxLimitCount <-
         option auto $
-        metavar "INT" <> long "max" <> short 'x' <>
-        help "Max offset or length (0 for no limit)" <>
+        metavar "INT" <> long "maxlimit" <>
+        help "Max limit for listings (0 for no limit)" <>
         showDefault <>
-        value defMaxCount
-    return Config {..}
+        value (maxLimitCount defMaxLimits)
+    maxLimitFull <-
+        option auto $
+        metavar "INT" <> long "maxfull" <>
+        help "Max limit for full listings (0 for no limit)" <>
+        showDefault <>
+        value (maxLimitFull defMaxLimits)
+    maxLimitOffset <-
+        option auto $
+        metavar "INT" <> long "maxoffset" <>
+        help "Max offset (0 for no limit)" <>
+        showDefault <>
+        value (maxLimitOffset defMaxLimits)
+    maxLimitDefault <-
+        option auto $
+        metavar "INT" <> long "deflimit" <> help "Default limit (0 for max)" <>
+        showDefault <>
+        value (maxLimitDefault defMaxLimits)
+    maxLimitGap <-
+        option auto $
+        metavar "INT" <> long "gap" <> help "Extended public key gap" <>
+        showDefault <>
+        value (maxLimitGap defMaxLimits)
+    pure Config {configMaxLimits = MaxLimits {..}, ..}
 
 networkReader :: String -> Either String Network
 networkReader s
@@ -141,7 +170,7 @@
         exitSuccess
     when (null (configPeers conf) && not (configDiscover conf)) . liftIO $
         die "ERROR: Specify peers to connect or enable peer discovery."
-    run conf
+    runResourceT $ run conf
   where
     opts =
         info (helper <*> config) $
@@ -153,7 +182,7 @@
 cacheDir net "" = Nothing
 cacheDir net ch = Just (ch </> getNetworkName net </> "cache")
 
-run :: MonadUnliftIO m => Config -> m ()
+run :: (MonadUnliftIO m, MonadResource m) => Config -> m ()
 run Config { configPort = port
            , configNetwork = net
            , configDiscover = disc
@@ -161,7 +190,7 @@
            , configCache = cache_path
            , configDir = db_dir
            , configDebug = deb
-           , configMaxCount = max_count
+           , configMaxLimits = limits
            } =
     runStderrLoggingT . filterLogger l . flip finally clear $ do
         $(logInfoS) "Main" $
@@ -217,7 +246,7 @@
                                 , webDB = ldb
                                 , webPublisher = pub
                                 , webStore = str
-                                , webMaxCount = max_count
+                                , webMaxLimits = limits
                                 }
                      in runWeb wcfg
   where
diff --git a/haskoin-store.cabal b/haskoin-store.cabal
--- a/haskoin-store.cabal
+++ b/haskoin-store.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: fe384b3ea5c3ecda43c35136aaabfb52c82bf6b08e13da5e1d98852039cdd315
+-- hash: d74abcf656d9e3a1435569934046c4b9ad0b5ee8113bf6ec820f5553a7070ad7
 
 name:           haskoin-store
-version:        0.17.2
+version:        0.18.0
 synopsis:       Storage and index for Bitcoin and Bitcoin Cash
 description:    Store blocks, transactions, and balances for Bitcoin or Bitcoin Cash, and make that information via REST API.
 category:       Bitcoin, Finance, Network
@@ -72,6 +72,7 @@
     , transformers
     , unliftio
     , unordered-containers
+    , vector
     , wai
     , warp
   default-language: Haskell2010
@@ -113,6 +114,7 @@
     , transformers
     , unliftio
     , unordered-containers
+    , vector
     , wai
     , warp
   default-language: Haskell2010
@@ -156,6 +158,7 @@
     , transformers
     , unliftio
     , unordered-containers
+    , vector
     , wai
     , warp
   default-language: Haskell2010
diff --git a/src/Haskoin/Store.hs b/src/Haskoin/Store.hs
--- a/src/Haskoin/Store.hs
+++ b/src/Haskoin/Store.hs
@@ -32,12 +32,14 @@
     , BinSerial(..)
     , Except(..)
     , TxId(..)
-    , StartFrom(..)
     , UnixTime
     , BlockPos
     , BlockDB(..)
     , LayeredDB(..)
     , WebConfig(..)
+    , MaxLimits(..)
+    , Offset
+    , Limit
     , newLayeredDB
     , withStore
     , runWeb
@@ -53,7 +55,6 @@
     , toTransaction
     , getBalance
     , getMempool
-    , getMempoolLimit
     , getAddressUnspents
     , getAddressUnspentsLimit
     , getAddressesUnspentsLimit
@@ -75,6 +76,7 @@
     , healthCheck
     , withBlockMem
     , withLayeredDB
+    , insertNubInSortedBy
     ) where
 
 import           Conduit
diff --git a/src/Network/Haskoin/Store/Block.hs b/src/Network/Haskoin/Store/Block.hs
--- a/src/Network/Haskoin/Store/Block.hs
+++ b/src/Network/Haskoin/Store/Block.hs
@@ -81,7 +81,7 @@
 
 instance (MonadResource m, MonadUnliftIO m) =>
          StoreStream (ReaderT BlockRead m) where
-    getMempool = transPipe runLayered . getMempool
+    getMempool = transPipe runLayered getMempool
     getOrphans = transPipe runLayered getOrphans
     getAddressUnspents a x = transPipe runLayered $ getAddressUnspents a x
     getAddressTxs a x = transPipe runLayered $ getAddressTxs a x
diff --git a/src/Network/Haskoin/Store/Data.hs b/src/Network/Haskoin/Store/Data.hs
--- a/src/Network/Haskoin/Store/Data.hs
+++ b/src/Network/Haskoin/Store/Data.hs
@@ -106,8 +106,26 @@
     sm <- lift $ getSpenders h
     return $ toTransaction d sm
 
+blockAtOrBefore :: (Monad m, StoreRead m) => UnixTime -> m (Maybe BlockData)
+blockAtOrBefore q = runMaybeT $ do
+    a <- g 0
+    b <- MaybeT getBestBlock >>= MaybeT . getBlock
+    f a b
+  where
+    f a b
+        | t b <= q = return b
+        | t a > q = mzero
+        | h b - h a == 1 = return a
+        | otherwise = do
+              let x = h a + (h b - h a) `div` 2
+              m <- g x
+              if t m > q then f a m else f m b
+    g x = MaybeT (listToMaybe <$> getBlocksAtHeight x) >>= MaybeT . getBlock
+    h = blockDataHeight
+    t = fromIntegral . blockTimestamp . blockDataHeader
+
 class StoreStream m where
-    getMempool :: Maybe UnixTime -> ConduitT i (UnixTime, TxHash) m ()
+    getMempool :: ConduitT i (UnixTime, TxHash) m ()
     getOrphans :: ConduitT i (UnixTime, Tx) m ()
     getAddressUnspents :: Address -> Maybe BlockRef -> ConduitT i Unspent m ()
     getAddressTxs :: Address -> Maybe BlockRef -> ConduitT i BlockTx m ()
@@ -896,30 +914,37 @@
 
 data XPubSummary =
     XPubSummary
-        { xPubSummaryReceived  :: !Word64
-        , xPubSummaryConfirmed :: !Word64
+        { xPubSummaryConfirmed :: !Word64
         , xPubSummaryZero      :: !Word64
+        , xPubSummaryReceived  :: !Word64
+        , xPubUnspentCount     :: !Word64
+        , xPubTxCount          :: !Word64
         , xPubExternalIndex    :: !Word32
         , xPubChangeIndex      :: !Word32
         , xPubSummaryPaths     :: !(HashMap Address [KeyIndex])
-        , xPubSummaryTxs       :: ![Transaction]
         }
     deriving (Eq, Show, Generic)
 
 xPubSummaryPairs :: A.KeyValue kv => Network -> XPubSummary -> [kv]
-xPubSummaryPairs net XPubSummary { xPubSummaryReceived = r
-                                 , xPubSummaryConfirmed = c
+xPubSummaryPairs net XPubSummary { xPubSummaryConfirmed = c
                                  , xPubSummaryZero = z
+                                 , xPubSummaryReceived = r
+                                 , xPubUnspentCount = u
+                                 , xPubTxCount = t
                                  , xPubSummaryPaths = ps
-                                 , xPubSummaryTxs = ts
                                  , xPubExternalIndex = ext
                                  , xPubChangeIndex = ch
                                  } =
     [ "balance" .=
-      object ["received" .= r, "confirmed" .= c, "unconfirmed" .= z]
+      object
+          [ "confirmed" .= c
+          , "unconfirmed" .= z
+          , "received" .= r
+          , "utxo" .= u
+          , "txs" .= t
+          ]
     , "indices" .= object ["change" .= ch, "external" .= ext]
     , "paths" .= object (mapMaybe (uncurry f) (M.toList ps))
-    , "txs" .= map (transactionToJSON net) ts
     ]
   where
     f a p = (.= p) <$> addrToString net a
@@ -935,39 +960,39 @@
     jsonValue = xPubSummaryToJSON
 
 instance BinSerial XPubSummary where
-    binSerial net XPubSummary { xPubSummaryReceived = r
-                              , xPubSummaryConfirmed = c
+    binSerial net XPubSummary { xPubSummaryConfirmed = c
                               , xPubSummaryZero = z
+                              , xPubSummaryReceived = r
+                              , xPubUnspentCount = u
+                              , xPubTxCount = t
                               , xPubExternalIndex = ext
                               , xPubChangeIndex = ch
                               , xPubSummaryPaths = ps
-                              , xPubSummaryTxs = ts
                               } = do
-        put r
         put c
         put z
+        put r
+        put u
+        put t
         put ext
         put ch
         put (map (first (runPut . binSerial net)) (M.toList ps))
-        put $ map (runPut . binSerial net) ts
-
     binDeserial net = do
-      r <- get
-      c <- get
-      z <- get
-      ext <- get
-      ch <- get
-      ps <- get
-      let xs = map (first (runGet (binDeserial net))) ps
-      ys <- forM xs $ \(k, v) -> case k of
-        Right a -> return (a, v)
-        Left _  -> mzero
-      ts <- get
-      let txs = map (runGet (binDeserial net)) ts
-      tys <- forM txs $ \case
-        Right a -> return a
-        Left _ -> mzero
-      return $ XPubSummary r c z ext ch (M.fromList ys) tys
+        c <- get
+        z <- get
+        r <- get
+        u <- get
+        t <- get
+        ext <- get
+        ch <- get
+        ps <- get
+        let xs = map (first (runGet (binDeserial net))) ps
+        ys <-
+            forM xs $ \(k, v) ->
+                case k of
+                    Right a -> return (a, v)
+                    Left _ -> mzero
+        return $ XPubSummary c z r u t ext ch (M.fromList ys)
 
 data HealthCheck = HealthCheck
     { healthHeaderBest   :: !(Maybe BlockHash)
@@ -1073,12 +1098,6 @@
 instance BinSerial TxId where
     binSerial _ (TxId th) = put th
     binDeserial _ = TxId <$> get
-
-data StartFrom
-    = StartBlock !BlockHeight !BlockPos
-    | StartMem !UnixTime
-    | StartOffset !Word32
-    deriving (Show, Eq, Generic)
 
 data BalVal = BalVal
     { balValAmount        :: !Word64
diff --git a/src/Network/Haskoin/Store/Data/Cached.hs b/src/Network/Haskoin/Store/Data/Cached.hs
--- a/src/Network/Haskoin/Store/Data/Cached.hs
+++ b/src/Network/Haskoin/Store/Data/Cached.hs
@@ -79,11 +79,10 @@
 
 getMempoolC ::
        (MonadResource m, MonadUnliftIO m)
-    => Maybe UnixTime
-    -> LayeredDB
+    => LayeredDB
     -> ConduitT i (UnixTime, TxHash) m ()
-getMempoolC mpu LayeredDB {layeredCache = Just db} = getMempoolDB mpu db
-getMempoolC mpu LayeredDB {layeredDB = db}         = getMempoolDB mpu db
+getMempoolC LayeredDB {layeredCache = Just db} = getMempoolDB db
+getMempoolC LayeredDB {layeredDB = db}         = getMempoolDB db
 
 getOrphansC ::
        (MonadUnliftIO m, MonadResource m)
@@ -148,9 +147,9 @@
 
 instance (MonadUnliftIO m, MonadResource m) =>
          StoreStream (ReaderT LayeredDB m) where
-    getMempool x = do
+    getMempool = do
         c <- R.ask
-        getMempoolC x c
+        getMempoolC c
     getOrphans = do
         c <- R.ask
         getOrphansC c
diff --git a/src/Network/Haskoin/Store/Data/ImportDB.hs b/src/Network/Haskoin/Store/Data/ImportDB.hs
--- a/src/Network/Haskoin/Store/Data/ImportDB.hs
+++ b/src/Network/Haskoin/Store/Data/ImportDB.hs
@@ -316,26 +316,20 @@
 
 getMempoolI ::
        MonadIO m
-    => Maybe UnixTime
-    -> ImportDB
+    => ImportDB
     -> ConduitT i (UnixTime, TxHash) m ()
-getMempoolI mpu ImportDB {importHashMap = hm, importLayeredDB = db} = do
+getMempoolI ImportDB {importHashMap = hm, importLayeredDB = db} = do
     h <- hMempool <$> readTVarIO hm
     let hmap =
-            M.fromList . filter tfilter $
+            M.fromList $
             concatMap
                 (\(u, l) -> map (\(t, b) -> ((u, t), b)) (M.toList l))
                 (M.toList h)
     dmap <-
         fmap M.fromList . liftIO . runResourceT . withLayeredDB db . runConduit $
-        getMempool mpu .| mapC (, True) .| sinkList
+        getMempool .| mapC (, True) .| sinkList
     let rmap = M.filter id (M.union hmap dmap)
     yieldMany $ sortBy (flip compare) (M.keys rmap)
-  where
-    tfilter =
-        case mpu of
-            Just x -> (<= x) . fst . fst
-            Nothing -> const True
 
 instance MonadIO m => StoreRead (ReaderT ImportDB m) where
     isInitialized = R.ask >>= isInitializedI
@@ -370,7 +364,7 @@
     setBalance b = R.ask >>= setBalanceI b
 
 instance MonadIO m => StoreStream (ReaderT ImportDB m) where
-    getMempool m = R.ask >>= getMempoolI m
+    getMempool = R.ask >>= getMempoolI
     getOrphans = undefined
     getAddressUnspents a m = undefined
     getAddressTxs a m = undefined
diff --git a/src/Network/Haskoin/Store/Data/Memory.hs b/src/Network/Haskoin/Store/Data/Memory.hs
--- a/src/Network/Haskoin/Store/Data/Memory.hs
+++ b/src/Network/Haskoin/Store/Data/Memory.hs
@@ -83,18 +83,9 @@
 getBalanceH :: Address -> BlockMem -> Maybe Balance
 getBalanceH a = fmap (balValToBalance a) . M.lookup a . hBalance
 
-getMempoolH ::
-       Monad m
-    => Maybe UnixTime
-    -> BlockMem
-    -> ConduitT i (UnixTime, TxHash) m ()
-getMempoolH mpu db =
-    let f ts =
-            case mpu of
-                Nothing -> False
-                Just pu -> ts > pu
-        ls =
-            dropWhile (f . fst) .
+getMempoolH :: Monad m => BlockMem -> ConduitT i (UnixTime, TxHash) m ()
+getMempoolH db =
+    let ls =
             sortBy (flip compare) . M.toList . M.map (M.keys . M.filter id) $
             hMempool db
      in yieldMany [(u, h) | (u, hs) <- ls, h <- hs]
@@ -328,9 +319,9 @@
         return $ getBalanceH a v
 
 instance MonadIO m => StoreStream (ReaderT (TVar BlockMem) m) where
-    getMempool m = do
+    getMempool = do
         v <- R.ask >>= readTVarIO
-        getMempoolH m v
+        getMempoolH v
     getOrphans = do
         v <- R.ask >>= readTVarIO
         getOrphansH v
diff --git a/src/Network/Haskoin/Store/Data/RocksDB.hs b/src/Network/Haskoin/Store/Data/RocksDB.hs
--- a/src/Network/Haskoin/Store/Data/RocksDB.hs
+++ b/src/Network/Haskoin/Store/Data/RocksDB.hs
@@ -68,16 +68,12 @@
 
 getMempoolDB ::
        (MonadIO m, MonadResource m)
-    => Maybe UnixTime
-    -> BlockDB
+    => BlockDB
     -> ConduitT i (UnixTime, TxHash) m ()
-getMempoolDB mpu BlockDB {blockDBopts = opts, blockDB = db} =
+getMempoolDB BlockDB {blockDBopts = opts, blockDB = db} =
     x .| mapC (uncurry f)
   where
-    x =
-        case mpu of
-            Nothing -> matching db opts MemKeyS
-            Just pu -> matchingSkip db opts MemKeyS (MemKeyT pu)
+    x = matching db opts MemKeyS
     f (MemKey u t) () = (u, t)
     f _ _             = undefined
 
diff --git a/src/Network/Haskoin/Store/Logic.hs b/src/Network/Haskoin/Store/Logic.hs
--- a/src/Network/Haskoin/Store/Logic.hs
+++ b/src/Network/Haskoin/Store/Logic.hs
@@ -232,7 +232,7 @@
 importBlock net b n = do
     mp <-
         runConduit $
-        getMempool Nothing .| mapC snd .| filterC (`elem` bths) .| sinkList
+        getMempool .| mapC snd .| filterC (`elem` bths) .| sinkList
     getBestBlock >>= \case
         Nothing
             | isGenesis n -> do
diff --git a/src/Network/Haskoin/Store/Web.hs b/src/Network/Haskoin/Store/Web.hs
--- a/src/Network/Haskoin/Store/Web.hs
+++ b/src/Network/Haskoin/Store/Web.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE DeriveGeneric     #-}
 {-# LANGUAGE FlexibleContexts  #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE LambdaCase        #-}
@@ -20,6 +21,7 @@
 import           Data.Aeson.Encoding               (encodingToLazyByteString,
                                                     fromEncoding)
 import           Data.Bits
+import qualified Data.ByteString                   as B
 import           Data.ByteString.Builder
 import qualified Data.ByteString.Lazy              as L
 import qualified Data.ByteString.Lazy.Char8        as C
@@ -37,9 +39,12 @@
 import qualified Data.Text.Encoding                as T
 import qualified Data.Text.Lazy                    as T.Lazy
 import           Data.Time.Clock
+import           Data.Vector                       (Vector, cons, (!))
+import qualified Data.Vector                       as V
 import           Data.Version
-import           Data.Word                         (Word32)
+import           Data.Word
 import           Database.RocksDB                  as R
+import           GHC.Generics
 import           Haskoin
 import           Haskoin.Node
 import           Network.Haskoin.Store.Data
@@ -59,6 +64,9 @@
 
 type WebT m = ActionT Except (ReaderT LayeredDB m)
 
+type Offset = Word32
+type Limit = Word32
+
 data Except
     = ThingNotFound
     | ServerError
@@ -110,9 +118,19 @@
         , webDB        :: !LayeredDB
         , webPublisher :: !(Publisher StoreEvent)
         , webStore     :: !Store
-        , webMaxCount  :: !Word32
+        , webMaxLimits :: !MaxLimits
         }
 
+data MaxLimits =
+    MaxLimits
+        { maxLimitCount   :: !Word32
+        , maxLimitFull    :: !Word32
+        , maxLimitOffset  :: !Word32
+        , maxLimitDefault :: !Word32
+        , maxLimitGap     :: !Word32
+        }
+    deriving (Eq, Show)
+
 instance Parsable BlockHash where
     parseParam =
         maybe (Left "could not decode block hash") Right . hexToBlockHash . cs
@@ -121,6 +139,29 @@
     parseParam =
         maybe (Left "could not decode tx hash") Right . hexToTxHash . cs
 
+data StartParam
+    = StartParamHash
+          { startParamHash :: !Hash256}
+    | StartParamHeight
+          { startParamHeight :: !Word32}
+    | StartParamTime
+          { startParamTime :: !UnixTime}
+
+instance Parsable StartParam where
+    parseParam s = maybe (Left "could not decode start") Right (h <|> g <|> t)
+      where
+        h = do
+            x <- fmap B.reverse (decodeHex (cs s)) >>= eitherToMaybe . decode
+            return StartParamHash {startParamHash = x}
+        g = do
+            x <- readMaybe (cs s) :: Maybe Integer
+            guard $ 0 <= x && x <= 1230768000
+            return StartParamHeight {startParamHeight = fromIntegral x}
+        t = do
+            x <- readMaybe (cs s)
+            guard $ x > 1230768000
+            return StartParamTime {startParamTime = x}
+
 instance MonadIO m => StoreRead (WebT m) where
     isInitialized = lift isInitialized
     getBestBlock = lift getBestBlock
@@ -134,8 +175,8 @@
     getBalance = lift . getBalance
 
 instance (MonadResource m, MonadUnliftIO m) =>
-         StoreStream (WebT (ReaderT LayeredDB m)) where
-    getMempool = transPipe lift . getMempool
+         StoreStream (WebT m) where
+    getMempool = transPipe lift getMempool
     getOrphans = transPipe lift getOrphans
     getAddressUnspents a x = transPipe lift $ getAddressUnspents a x
     getAddressTxs a x = transPipe lift $ getAddressTxs a x
@@ -197,47 +238,55 @@
             return $ pruneTx n b
     maybeSerial net proto res
 
-scottyBlockHeight :: MonadLoggerIO m => Network -> WebT m ()
+scottyBlockHeight ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyBlockHeight net = do
     cors
     height <- param "height"
     n <- parseNoTx
     proto <- setupBin
-    db <- askDB
     hs <- getBlocksAtHeight height
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             yieldMany hs .| concatMapMC getBlock .| mapC (pruneTx n) .|
             streamAny net proto io
         flush'
 
-scottyBlockHeights :: MonadLoggerIO m => Network -> WebT m ()
+scottyBlockHeights ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyBlockHeights net = do
     cors
     heights <- param "heights"
     n <- parseNoTx
     proto <- setupBin
-    db <- askDB
     bs <- concat <$> mapM getBlocksAtHeight (nub heights)
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             yieldMany (nub heights) .| concatMapMC getBlocksAtHeight .|
             concatMapMC getBlock .|
             mapC (pruneTx n) .|
             streamAny net proto io
         flush'
 
-scottyBlockLatest :: MonadLoggerIO m => Network -> WebT m ()
+scottyBlockLatest ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyBlockLatest net = do
     cors
     n <- parseNoTx
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     getBestBlock >>= \case
         Just h ->
             stream $ \io flush' -> do
-                runResourceT . withLayeredDB db . runConduit $
-                    f n h 100 .| streamAny net proto io
+                runner . runConduit $ f n h 100 .| streamAny net proto io
                 flush'
         Nothing -> raise ThingNotFound
   where
@@ -252,29 +301,32 @@
                     else f n (prevBlock (blockDataHeader b)) (i - 1)
 
 
-scottyBlocks :: MonadLoggerIO m => Network -> WebT m ()
+scottyBlocks ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyBlocks net = do
     cors
     blocks <- param "blocks"
     n <- parseNoTx
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             yieldMany (nub blocks) .| concatMapMC getBlock .| mapC (pruneTx n) .|
             streamAny net proto io
         flush'
 
 scottyMempool ::
-       (MonadLoggerIO m, MonadUnliftIO m) => Network -> Word32 -> WebT m ()
-scottyMempool net max_count = do
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
+scottyMempool net = do
     cors
-    (l, s) <- parseLimits max_count
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
-            getMempoolLimit l s .| streamAny net proto io
+        runner . runConduit $ getMempoolStream .| streamAny net proto io
         flush'
 
 scottyTransaction :: MonadLoggerIO m => Network -> WebT m ()
@@ -302,88 +354,111 @@
     res <- cbAfterHeight 10000 height txid
     protoSerial net proto res
 
-scottyTransactions :: MonadLoggerIO m => Network -> WebT m ()
+scottyTransactions ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyTransactions net = do
     cors
     txids <- param "txids"
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             yieldMany (nub txids) .| concatMapMC getTransaction .|
             streamAny net proto io
         flush'
 
-scottyBlockTransactions :: MonadLoggerIO m => Network -> WebT m ()
+scottyBlockTransactions ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyBlockTransactions net = do
     cors
     h <- param "block"
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     getBlock h >>= \case
-        Just b -> stream $ \io flush' -> do
-            runResourceT . withLayeredDB db . runConduit $
-                yieldMany (blockDataTxs b) .| concatMapMC getTransaction .|
-                streamAny net proto io
-            flush'
-        Nothing ->
-            raise ThingNotFound
+        Just b ->
+            stream $ \io flush' -> do
+                runner . runConduit $
+                    yieldMany (blockDataTxs b) .| concatMapMC getTransaction .|
+                    streamAny net proto io
+                flush'
+        Nothing -> raise ThingNotFound
 
-scottyRawTransactions :: MonadLoggerIO m => Network -> WebT m ()
+scottyRawTransactions ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyRawTransactions net = do
     cors
     txids <- param "txids"
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             yieldMany (nub txids) .| concatMapMC getTransaction .|
             mapC transactionData .|
             streamAny net proto io
         flush'
 
-scottyRawBlockTransactions :: MonadLoggerIO m => Network -> WebT m ()
+scottyRawBlockTransactions ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyRawBlockTransactions net = do
     cors
     h <- param "block"
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     getBlock h >>= \case
-        Just b -> stream $ \io flush' -> do
-            runResourceT . withLayeredDB db . runConduit $
-                yieldMany (blockDataTxs b) .| concatMapMC getTransaction .|
-                mapC transactionData .|
-                streamAny net proto io
-            flush'
-        Nothing ->
-            raise ThingNotFound
+        Just b ->
+            stream $ \io flush' -> do
+                runner . runConduit $
+                    yieldMany (blockDataTxs b) .| concatMapMC getTransaction .|
+                    mapC transactionData .|
+                    streamAny net proto io
+                flush'
+        Nothing -> raise ThingNotFound
 
 scottyAddressTxs ::
-       (MonadLoggerIO m, MonadUnliftIO m) => Network -> Word32 -> Bool -> WebT m ()
-scottyAddressTxs net max_count full = do
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m)
+    => Network
+    -> MaxLimits
+    -> Bool
+    -> WebT m ()
+scottyAddressTxs net limits full = do
     cors
     a <- parseAddress net
-    (l, s) <- parseLimits max_count
+    s <- getStart
+    o <- getOffset limits
+    l <- getLimit limits full
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $ f proto l s a io
+        runner . runConduit $ f proto o l s a io
         flush'
   where
-    f proto l s a io
-        | full = getAddressTxsFull l s a .| streamAny net proto io
-        | otherwise = getAddressTxsLimit l s a .| streamAny net proto io
+    f proto o l s a io
+        | full = getAddressTxsFull o l s a .| streamAny net proto io
+        | otherwise = getAddressTxsLimit o l s a .| streamAny net proto io
 
 scottyAddressesTxs ::
-       (MonadLoggerIO m, MonadUnliftIO m) => Network -> Word32 -> Bool -> WebT m ()
-scottyAddressesTxs net max_count full = do
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m)
+    => Network
+    -> MaxLimits
+    -> Bool
+    -> WebT m ()
+scottyAddressesTxs net limits full = do
     cors
     as <- parseAddresses net
-    (l, s) <- parseLimits max_count
+    s <- getStart
+    l <- getLimit limits full
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $ f proto l s as io
+        runner . runConduit $ f proto l s as io
         flush'
   where
     f proto l s as io
@@ -391,28 +466,37 @@
         | otherwise = getAddressesTxsLimit l s as .| streamAny net proto io
 
 scottyAddressUnspent ::
-       (MonadLoggerIO m, MonadUnliftIO m) => Network -> Word32 -> WebT m ()
-scottyAddressUnspent net max_count = do
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m)
+    => Network
+    -> MaxLimits
+    -> WebT m ()
+scottyAddressUnspent net limits = do
     cors
     a <- parseAddress net
-    (l, s) <- parseLimits max_count
+    s <- getStart
+    o <- getOffset limits
+    l <- getLimit limits False
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
-            getAddressUnspentsLimit l s a .| streamAny net proto io
+        runner . runConduit $
+            getAddressUnspentsLimit o l s a .| streamAny net proto io
         flush'
 
 scottyAddressesUnspent ::
-       (MonadLoggerIO m, MonadUnliftIO m) => Network -> Word32 -> WebT m ()
-scottyAddressesUnspent net max_count = do
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m)
+    => Network
+    -> MaxLimits
+    -> WebT m ()
+scottyAddressesUnspent net limits = do
     cors
     as <- parseAddresses net
-    (l, s) <- parseLimits max_count
+    s <- getStart
+    l <- getLimit limits False
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             getAddressesUnspentsLimit l s as .| streamAny net proto io
         flush'
 
@@ -436,12 +520,14 @@
                         }
     protoSerial net proto res
 
-scottyAddressesBalances :: MonadLoggerIO m => Network -> WebT m ()
+scottyAddressesBalances ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> WebT m ()
 scottyAddressesBalances net = do
     cors
     as <- parseAddresses net
     proto <- setupBin
-    db <- askDB
     let f a Nothing =
             Balance
                 { balanceAddress = a
@@ -452,71 +538,79 @@
                 , balanceTotalReceived = 0
                 }
         f _ (Just b) = b
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
+        runner . runConduit $
             yieldMany as .| mapMC (\a -> f a <$> getBalance a) .|
             streamAny net proto io
         flush'
 
-scottyXpubBalances :: (MonadUnliftIO m, MonadLoggerIO m) => Network -> WebT m ()
-scottyXpubBalances net = do
+scottyXpubBalances ::
+       (MonadUnliftIO m, MonadLoggerIO m, MonadResource m)
+    => Network
+    -> MaxLimits
+    -> WebT m ()
+scottyXpubBalances net max_limits = do
     cors
     xpub <- parseXpub net
     proto <- setupBin
-    db <- askDB
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
-            xpubBals xpub .| streamAny net proto io
+        runner . runConduit $
+            xpubBals max_limits xpub .| streamAny net proto io
         flush'
 
 scottyXpubTxs ::
-       (MonadLoggerIO m, MonadUnliftIO m)
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m)
     => Network
-    -> Word32
+    -> MaxLimits
     -> Bool
     -> WebT m ()
-scottyXpubTxs net max_count full = do
+scottyXpubTxs net limits full = do
     cors
     x <- parseXpub net
-    (l, s) <- parseLimits max_count
+    s <- getStart
+    l <- getLimit limits full
     proto <- setupBin
-    db <- askDB
-    bs <-
-        liftIO . runResourceT . withLayeredDB db $
-        runConduit $ xpubBals x .| sinkList
+    as <-
+        lift . runConduit $
+        xpubBals limits x .| mapC (balanceAddress . xPubBal) .| sinkList
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $ f proto l s bs io
+        runner . runConduit $ f proto l s as io
         flush'
   where
-    f proto l s bs io
-        | full =
-            getAddressesTxsFull l s (map (balanceAddress . xPubBal) bs) .|
-            streamAny net proto io
-        | otherwise =
-            getAddressesTxsLimit l s (map (balanceAddress . xPubBal) bs) .|
-            streamAny net proto io
+    f proto l s as io
+        | full = getAddressesTxsFull l s as .| streamAny net proto io
+        | otherwise = getAddressesTxsLimit l s as .| streamAny net proto io
 
-scottyXpubUnspents :: MonadLoggerIO m => Network -> Word32 -> WebT m ()
-scottyXpubUnspents net max_count = do
+scottyXpubUnspents ::
+       (MonadLoggerIO m, MonadResource m, MonadUnliftIO m)
+    => Network
+    -> MaxLimits
+    -> WebT m ()
+scottyXpubUnspents net limits = do
     cors
     x <- parseXpub net
     proto <- setupBin
-    (l, s) <- parseLimits max_count
-    db <- askDB
+    s <- getStart
+    l <- getLimit limits False
+    runner <- lift askRunInIO
     stream $ \io flush' -> do
-        runResourceT . withLayeredDB db . runConduit $
-            xpubUnspentLimit net l s x .| streamAny net proto io
+        runner . runConduit $
+            xpubUnspentLimit net limits l s x .| streamAny net proto io
         flush'
 
 scottyXpubSummary ::
-       (MonadLoggerIO m, MonadUnliftIO m) => Network -> Word32 -> WebT m ()
-scottyXpubSummary net max_count = do
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m)
+    => Network
+    -> MaxLimits
+    -> WebT m ()
+scottyXpubSummary net max_limits = do
     cors
     x <- parseXpub net
-    (l, s) <- parseLimits max_count
     proto <- setupBin
-    db <- askDB
-    res <- liftIO . runResourceT . withLayeredDB db $ xpubSummary l s x
+    res <- lift $ xpubSummary max_limits x
     protoSerial net proto res
 
 scottyPostTx ::
@@ -602,18 +696,18 @@
     when (not (healthOK h) || not (healthSynced h)) $ status status503
     protoSerial net proto h
 
-runWeb :: (MonadLoggerIO m, MonadUnliftIO m) => WebConfig -> m ()
+runWeb ::
+       (MonadLoggerIO m, MonadUnliftIO m, MonadResource m) => WebConfig -> m ()
 runWeb WebConfig { webDB = db
                  , webPort = port
                  , webNetwork = net
                  , webStore = st
                  , webPublisher = pub
-                 , webMaxCount = max_count
+                 , webMaxLimits = limits
                  } = do
-    runner <- askRunInIO
-    logger <- askRunInIO
     l <- logIt
-    scottyOptsT (opts logger) (runner . withLayeredDB db) $ do
+    runner <- askRunInIO
+    scottyT port (runner . withLayeredDB db) $ do
         middleware l
         defaultHandler (defHandler net)
         S.get "/block/best" $ scottyBestBlock net
@@ -622,7 +716,7 @@
         S.get "/block/heights" $ scottyBlockHeights net
         S.get "/block/latest" $ scottyBlockLatest net
         S.get "/blocks" $ scottyBlocks net
-        S.get "/mempool" $ scottyMempool net max_count
+        S.get "/mempool" $ scottyMempool net
         S.get "/transaction/:txid" $ scottyTransaction net
         S.get "/transaction/:txid/raw" $ scottyRawTransaction net
         S.get "/transaction/:txid/after/:height" $ scottyTxAfterHeight net
@@ -631,57 +725,87 @@
         S.get "/transactions/block/:block" $ scottyBlockTransactions net
         S.get "/transactions/block/:block/raw" $ scottyRawBlockTransactions net
         S.get "/address/:address/transactions" $
-            scottyAddressTxs net max_count False
+            scottyAddressTxs net limits False
         S.get "/address/:address/transactions/full" $
-            scottyAddressTxs net max_count True
-        S.get "/address/transactions" $ scottyAddressesTxs net max_count False
-        S.get "/address/transactions/full" $
-            scottyAddressesTxs net max_count True
-        S.get "/address/:address/unspent" $ scottyAddressUnspent net max_count
-        S.get "/address/unspent" $ scottyAddressesUnspent net max_count
+            scottyAddressTxs net limits True
+        S.get "/address/transactions" $ scottyAddressesTxs net limits False
+        S.get "/address/transactions/full" $ scottyAddressesTxs net limits True
+        S.get "/address/:address/unspent" $ scottyAddressUnspent net limits
+        S.get "/address/unspent" $ scottyAddressesUnspent net limits
         S.get "/address/:address/balance" $ scottyAddressBalance net
         S.get "/address/balances" $ scottyAddressesBalances net
-        S.get "/xpub/:xpub/balances" $ scottyXpubBalances net
-        S.get "/xpub/:xpub/transactions" $ scottyXpubTxs net max_count False
-        S.get "/xpub/:xpub/transactions/full" $ scottyXpubTxs net max_count True
-        S.get "/xpub/:xpub/unspent" $ scottyXpubUnspents net max_count
-        S.get "/xpub/:xpub" $ scottyXpubSummary net max_count
+        S.get "/xpub/:xpub/balances" $ scottyXpubBalances net limits
+        S.get "/xpub/:xpub/transactions" $ scottyXpubTxs net limits False
+        S.get "/xpub/:xpub/transactions/full" $ scottyXpubTxs net limits True
+        S.get "/xpub/:xpub/unspent" $ scottyXpubUnspents net limits
+        S.get "/xpub/:xpub" $ scottyXpubSummary net limits
         S.post "/transactions" $ scottyPostTx net st pub
         S.get "/dbstats" scottyDbStats
         S.get "/events" $ scottyEvents net pub
         S.get "/peers" $ scottyPeers net st
         S.get "/health" $ scottyHealth net st
         notFound $ raise ThingNotFound
+
+getStart :: (MonadResource m, MonadUnliftIO m) => WebT m (Maybe BlockRef)
+getStart =
+    runMaybeT $ do
+        s <- MaybeT $ (Just <$> param "height") `rescue` const (return Nothing)
+        do case s of
+               StartParamHash {startParamHash = h} ->
+                   start_tx h <|> start_block h
+               StartParamHeight {startParamHeight = h} -> start_height h
+               StartParamTime {startParamTime = q} -> start_time q
   where
-    opts runner = def {settings = setPort port (setOnOpen f defaultSettings)}
-      where
-        f s =
-            runner $ do
-                $(logDebugS) "Web" $ "Incoming connection: " <> cs (show s)
-                return True
+    start_height h = return $ BlockRef h maxBound
+    start_block h = do
+        b <- MaybeT $ getBlock (BlockHash h)
+        let g = blockDataHeight b
+        return $ BlockRef g maxBound
+    start_tx h = do
+        t <- MaybeT $ getTxData (TxHash h)
+        return $ txDataBlock t
+    start_time q = do
+        b <- MaybeT getBestBlock >>= MaybeT . getBlock
+        if q <= fromIntegral (blockTimestamp (blockDataHeader b))
+            then do
+                b <- MaybeT $ blockAtOrBefore q
+                let g = blockDataHeight b
+                return $ BlockRef g maxBound
+            else return $ MemRef q
 
-parseLimits ::
-       (ScottyError e, Monad m)
-    => Word32
-    -> ActionT e m (Maybe Word32, StartFrom)
-parseLimits max_count = do
-    let b = do
-            height <- param "height"
-            pos <- param "pos" `rescue` const (return maxBound)
-            return $ StartBlock height pos
-        m = do
-            time <- param "time"
-            return $ StartMem time
-        o = do
-            o <- param "offset" `rescue` const (return 0)
-            let o' = if max_count == 0 then o else min o max_count
-            return $ StartOffset o'
-    l <- runMaybeT $ do
-        l <- MaybeT $ (Just <$> param "limit") `rescue` const (return Nothing)
-        return $ if max_count == 0 then l else min l max_count
-    s <- b <|> m <|> o
-    return (l, s)
+getOffset :: Monad m => MaxLimits -> ActionT Except m Offset
+getOffset limits = do
+    o <- param "offset" `rescue` const (return 0)
+    when (maxLimitOffset limits > 0 && o > maxLimitOffset limits) .
+        raise . UserError $
+        "offset exceeded: " <> show o <> " > " <> show (maxLimitOffset limits)
+    return o
 
+getLimit ::
+       Monad m
+    => MaxLimits
+    -> Bool
+    -> ActionT Except m (Maybe Limit)
+getLimit limits full = do
+    l <- (Just <$> param "limit") `rescue` const (return Nothing)
+    let m =
+            if full
+                then if maxLimitFull limits > 0
+                         then maxLimitFull limits
+                         else maxLimitCount limits
+                else maxLimitCount limits
+    let d = maxLimitDefault limits
+    return $
+        case l of
+            Nothing ->
+                if d > 0 || m > 0
+                    then Just (min m d)
+                    else Nothing
+            Just n ->
+                if m > 0
+                    then Just (min m n)
+                    else Just n
+
 parseAddress net = do
     address <- param "address"
     case stringToAddr net address of
@@ -814,11 +938,13 @@
 
 xpubBals ::
        (MonadResource m, MonadUnliftIO m, StoreRead m)
-    => XPubKey
+    => MaxLimits
+    -> XPubKey
     -> ConduitT i XPubBal m ()
-xpubBals xpub = go 0 >> go 1
+xpubBals limits xpub = go 0 >> go 1
   where
-    go m = yieldMany (addrs m) .| mapMC (uncurry bal) .| gap 20
+    go m =
+        yieldMany (addrs m) .| mapMC (uncurry bal) .| gap (maxLimitGap limits)
     bal a p =
         getBalance a >>= \case
             Nothing -> return Nothing
@@ -841,14 +967,15 @@
        , StoreRead m
        )
     => Network
+    -> MaxLimits
     -> Maybe BlockRef
     -> XPubKey
     -> ConduitT i XPubUnspent m ()
-xpubUnspent net mbr xpub = xpubBals xpub .| go
+xpubUnspent net max_limits start xpub = xpubBals max_limits xpub .| go
   where
     go =
         awaitForever $ \XPubBal {xPubBalPath = p, xPubBal = b} ->
-            getAddressUnspents (balanceAddress b) mbr .|
+            getAddressUnspents (balanceAddress b) start .|
             mapC (\t -> XPubUnspent {xPubUnspentPath = p, xPubUnspent = t})
 
 xpubUnspentLimit ::
@@ -858,54 +985,46 @@
        , StoreRead m
        )
     => Network
-    -> Maybe Word32
-    -> StartFrom
+    -> MaxLimits
+    -> Maybe Limit
+    -> Maybe BlockRef
     -> XPubKey
     -> ConduitT i XPubUnspent m ()
-xpubUnspentLimit net l s x =
-    xpubUnspent net (mbr s) x .| (offset s >> limit l)
+xpubUnspentLimit net max_limits limit start xpub =
+    xpubUnspent net max_limits start xpub .| applyLimit limit
 
 xpubSummary ::
        (MonadResource m, MonadUnliftIO m, StoreStream m, StoreRead m)
-    => Maybe Word32
-    -> StartFrom
+    => MaxLimits
     -> XPubKey
     -> m XPubSummary
-xpubSummary l s x = do
-    bs <- runConduit $ xpubBals x .| sinkList
+xpubSummary max_limits x = do
+    bs <- runConduit $ xpubBals max_limits x .| sinkList
     let f XPubBal {xPubBalPath = p, xPubBal = Balance {balanceAddress = a}} =
             (a, p)
         pm = H.fromList $ map f bs
-    txs <-
-        runConduit $
-        getAddressesTxsFull l s (map (balanceAddress . xPubBal) bs) .| sinkList
-    let as =
-            nub
-                [ a
-                | t <- txs
-                , let is = transactionInputs t
-                , let os = transactionOutputs t
-                , let ais =
-                          mapMaybe
-                              (eitherToMaybe . scriptToAddressBS . inputPkScript)
-                              is
-                , let aos =
-                          mapMaybe
-                              (eitherToMaybe . scriptToAddressBS . outputScript)
-                              os
-                , a <- ais ++ aos
+        ex = foldl max 0 [i | XPubBal {xPubBalPath = [0, i]} <- bs]
+        ch = foldl max 0 [i | XPubBal {xPubBalPath = [1, i]} <- bs]
+        uc =
+            sum
+                [ c
+                | XPubBal {xPubBal = Balance {balanceUnspentCount = c}} <- bs
                 ]
-        ps = H.fromList $ mapMaybe (\a -> (a, ) <$> H.lookup a pm) as
-        ex = foldl max 0 [i | XPubBal {xPubBalPath = [x, i]} <- bs, x == 0]
-        ch = foldl max 0 [i | XPubBal {xPubBalPath = [x, i]} <- bs, x == 1]
+        ct = sum [c | XPubBal {xPubBal = Balance {balanceTxCount = c}} <- bs]
+        xt = [b | b@XPubBal {xPubBalPath = [0, _]} <- bs]
+        rx =
+            sum
+                [ r
+                | XPubBal {xPubBal = Balance {balanceTotalReceived = r}} <- xt
+                ]
     return
         XPubSummary
-            { xPubSummaryReceived =
-                  sum (map (balanceTotalReceived . xPubBal) bs)
-            , xPubSummaryConfirmed = sum (map (balanceAmount . xPubBal) bs)
+            { xPubSummaryConfirmed = sum (map (balanceAmount . xPubBal) bs)
             , xPubSummaryZero = sum (map (balanceZero . xPubBal) bs)
-            , xPubSummaryPaths = ps
-            , xPubSummaryTxs = txs
+            , xPubSummaryReceived = rx
+            , xPubUnspentCount = uc
+            , xPubTxCount = ct
+            , xPubSummaryPaths = pm
             , xPubChangeIndex = ch
             , xPubExternalIndex = ex
             }
@@ -963,101 +1082,123 @@
   where
     mergeSealed sources = do
         prefetchedSources <- lift $ traverse ($$++ await) sources
-        go [(a, s) | (s, Just a) <- prefetchedSources]
-    go [] = pure ()
-    go sources = do
-        let (a, src1):sources1 = sortBy (f `on` fst) sources
-        yield a
-        (src2, mb) <- lift $ src1 $$++ await
-        let sources2 =
-                case mb of
-                    Nothing -> sources1
-                    Just b  -> (b, src2) : sources1
-        go sources2
+        go . V.fromList . nubBy (\a b -> f (fst a) (fst b) == EQ) $
+            sortBy (f `on` fst) [(a, s) | (s, Just a) <- prefetchedSources]
+    go sources
+        | V.null sources = pure ()
+        | otherwise = do
+            let (a, src1) = V.head sources
+                sources1 = V.tail sources
+            yield a
+            (src2, mb) <- lift $ src1 $$++ await
+            let sources2 =
+                    case mb of
+                        Nothing -> sources1
+                        Just b ->
+                            insertNubInSortedBy (f `on` fst) (b, src2) sources1
+            go sources2
 
-getMempoolLimit ::
+insertNubInSortedBy :: (a -> a -> Ordering) -> a -> Vector a -> Vector a
+insertNubInSortedBy f x xs
+    | null xs = xs
+    | otherwise =
+        case find_idx 0 (length xs - 1) of
+            Nothing -> xs
+            Just i ->
+                let (xs1, xs2) = V.splitAt i xs
+                 in xs1 <> x `cons` xs2
+  where
+    find_idx a b
+        | f (xs ! a) x == EQ = Nothing
+        | f (xs ! b) x == EQ = Nothing
+        | f (xs ! b) x == LT = Just (b + 1)
+        | f (xs ! a) x == GT = Just a
+        | b - a == 1 = Just b
+        | otherwise =
+            let c = a + (b - a) `div` 2
+                z = xs ! c
+             in if f z x == GT
+                    then find_idx a c
+                    else find_idx c b
+
+getMempoolStream ::
        (Monad m, StoreStream m)
-    => Maybe Word32
-    -> StartFrom
-    -> ConduitT i TxHash m ()
-getMempoolLimit _ StartBlock {} = return ()
-getMempoolLimit l (StartMem t) =
-    getMempool (Just t) .| mapC snd .| limit l
-getMempoolLimit l s =
-    getMempool Nothing .| mapC snd .| (offset s >> limit l)
+    => ConduitT i TxHash m ()
+getMempoolStream = getMempool .| mapC snd
 
 getAddressTxsLimit ::
        (Monad m, StoreStream m)
-    => Maybe Word32
-    -> StartFrom
+    => Offset
+    -> Maybe Limit
+    -> Maybe BlockRef
     -> Address
     -> ConduitT i BlockTx m ()
-getAddressTxsLimit l s a =
-    getAddressTxs a (mbr s) .| (offset s >> limit l)
+getAddressTxsLimit offset limit start addr =
+    getAddressTxs addr start .| applyOffsetLimit offset limit
 
 getAddressTxsFull ::
        (Monad m, StoreStream m, StoreRead m)
-    => Maybe Word32
-    -> StartFrom
+    => Offset
+    -> Maybe Limit
+    -> Maybe BlockRef
     -> Address
     -> ConduitT i Transaction m ()
-getAddressTxsFull l s a =
-    getAddressTxsLimit l s a .| concatMapMC (getTransaction . blockTxHash)
+getAddressTxsFull offset limit start addr =
+    getAddressTxsLimit offset limit start addr .|
+    concatMapMC (getTransaction . blockTxHash)
 
 getAddressesTxsLimit ::
        (MonadResource m, MonadUnliftIO m, StoreStream m)
-    => Maybe Word32
-    -> StartFrom
+    => Maybe Limit
+    -> Maybe BlockRef
     -> [Address]
     -> ConduitT i BlockTx m ()
-getAddressesTxsLimit l s as =
-    mergeSourcesBy (flip compare `on` blockTxBlock) xs .| dedup .|
-    (offset s >> limit l)
+getAddressesTxsLimit limit start addrs =
+    mergeSourcesBy (flip compare `on` blockTxBlock) xs .| applyLimit limit
   where
-    xs = map (\a -> getAddressTxs a (mbr s)) as
+    xs = map (`getAddressTxs` start) addrs
 
 getAddressesTxsFull ::
        (MonadResource m, MonadUnliftIO m, StoreStream m, StoreRead m)
-    => Maybe Word32
-    -> StartFrom
+    => Maybe Limit
+    -> Maybe BlockRef
     -> [Address]
     -> ConduitT i Transaction m ()
-getAddressesTxsFull l s as =
-    getAddressesTxsLimit l s as .| concatMapMC (getTransaction . blockTxHash)
+getAddressesTxsFull limit start addrs =
+    getAddressesTxsLimit limit start addrs .|
+    concatMapMC (getTransaction . blockTxHash)
 
 getAddressUnspentsLimit ::
        (Monad m, StoreStream m)
-    => Maybe Word32
-    -> StartFrom
+    => Offset
+    -> Maybe Limit
+    -> Maybe BlockRef
     -> Address
     -> ConduitT i Unspent m ()
-getAddressUnspentsLimit l s a =
-    getAddressUnspents a (mbr s) .| (offset s >> limit l)
+getAddressUnspentsLimit offset limit start addr =
+    getAddressUnspents addr start .| applyOffsetLimit offset limit
 
 getAddressesUnspentsLimit ::
        (Monad m, StoreStream m)
-    => Maybe Word32
-    -> StartFrom
+    => Maybe Limit
+    -> Maybe BlockRef
     -> [Address]
     -> ConduitT i Unspent m ()
-getAddressesUnspentsLimit l s as =
+getAddressesUnspentsLimit limit start addrs =
     mergeSourcesBy
         (flip compare `on` unspentBlock)
-        (map (`getAddressUnspents` mbr s) as) .|
-    (offset s >> limit l)
+        (map (`getAddressUnspents` start) addrs) .|
+    applyLimit limit
 
-offset :: Monad m => StartFrom -> ConduitT i i m ()
-offset (StartOffset o) = dropC (fromIntegral o)
-offset _               = return ()
+applyOffsetLimit :: Monad m => Offset -> Maybe Limit -> ConduitT i i m ()
+applyOffsetLimit offset limit = applyOffset offset >> applyLimit limit
 
-limit :: Monad m => Maybe Word32 -> ConduitT i i m ()
-limit Nothing  = mapC id
-limit (Just n) = takeC (fromIntegral n)
+applyOffset :: Monad m => Offset -> ConduitT i i m ()
+applyOffset = dropC . fromIntegral
 
-mbr :: StartFrom -> Maybe BlockRef
-mbr (StartBlock h p) = Just (BlockRef h p)
-mbr (StartMem t)     = Just (MemRef t)
-mbr (StartOffset _)  = Nothing
+applyLimit :: Monad m => Maybe Limit -> ConduitT i i m ()
+applyLimit Nothing  = mapC id
+applyLimit (Just l) = takeC (fromIntegral l)
 
 conduitToQueue :: MonadIO m => TBQueue (Maybe a) -> ConduitT a Void m ()
 conduitToQueue q =
