packages feed

haskoin-store-data 1.8.0 → 1.8.1

raw patch · 4 files changed

+581/−82 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

haskoin-store-data.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           haskoin-store-data-version:        1.8.0+version:        1.8.1 synopsis:       Data for Haskoin Store description:    Please see the README on GitHub at <https://github.com/jprupp/haskoin-store#readme> category:       Bitcoin, Finance, Network
src/Haskoin/Store/Data.hs view
@@ -13,6 +13,35 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE NoFieldSelectors #-} +-- |+-- Module      : Haskoin.Store.Data+-- Copyright   : (c) Haskoin Developers+-- License     : MIT+-- Maintainer  : jprupp@protonmail.ch+-- Stability   : experimental+-- Portability : POSIX+--+-- Data types of Haskoin Store: what it stores in its database and what its API+-- returns.+--+-- Most types have three encodings:+--+-- * binary ('Serial' and 'Binary'/'Serialize' instances), used in the database+--   and in binary API responses;+-- * JSON ('ToJSON' and 'FromJSON' instances), used in JSON API responses;+-- * for types that contain addresses, JSON functions that take the 'Network'+--   (the @marshal@ functions of 'MarshalJSON'), since the text form of an+--   address depends on the network.+--+-- Types whose names start with @Binfo@ belong to the API compatible with+-- Blockchain.info.+-- These types live in their own package, @haskoin-store-data@, so that clients of+-- the API can use them without depending on RocksDB, Redis or the server.+--+-- Values that refer to positions in the chain sort newest first: the binary+-- encoding of 'BlockRef' stores inverted heights and times, and mempool positions+-- sort before confirmed ones. That is the order in which wallets and explorers+-- want history, so database scans return it directly. module Haskoin.Store.Data   ( -- * Address Balances     Balance (..),@@ -185,18 +214,25 @@ import Haskoin import Web.Scotty.Trans (Parsable (..)) +-- | Type of the addresses derived from an extended public key. data DeriveType-  = DeriveNormal-  | DeriveP2SH-  | DeriveP2WPKH+  = -- | legacy pay-to-public-key-hash addresses (@normal@)+    DeriveNormal+  | -- | pay-to-witness-public-key-hash nested in pay-to-script-hash+    -- (@compat@)+    DeriveP2SH+  | -- | native pay-to-witness-public-key-hash (@segwit@)+    DeriveP2WPKH   deriving (Show, Eq, Generic, NFData) +-- | Parse a derivation type from @normal@, @compat@ or @segwit@. textToDeriveType :: Text -> Maybe DeriveType textToDeriveType "normal" = Just DeriveNormal textToDeriveType "compat" = Just DeriveP2SH textToDeriveType "segwit" = Just DeriveP2WPKH textToDeriveType _ = Nothing +-- | Text form of a derivation type: @normal@, @compat@ or @segwit@. deriveTypeToText :: DeriveType -> Text deriveTypeToText DeriveNormal = "normal" deriveTypeToText DeriveP2SH = "compat"@@ -231,8 +267,11 @@       Nothing -> Left "invalid derivation type"       Just x -> Right x +-- | Extended public key with the type of the addresses to derive from it. data XPubSpec = XPubSpec-  { key :: !XPubKey,+  { -- | the extended public key+    key :: !XPubKey,+    -- | type of addresses to derive     deriv :: !DeriveType   }   deriving (Show, Eq, Generic, NFData)@@ -268,14 +307,17 @@   put = serialize   get = deserialize +-- | Seconds since the Unix epoch. type UnixTime = Word64 +-- | Position of a transaction in a block. type BlockPos = Word32  -- | Binary such that ordering is inverted. putUnixTime :: (MonadPut m) => Word64 -> m () putUnixTime w = putWord64be $ maxBound - w +-- | Inverse of 'putUnixTime'. getUnixTime :: (MonadGet m) => m Word64 getUnixTime = (maxBound -) <$> getWord64be @@ -287,8 +329,10 @@         -- | position of transaction within the block         position :: !Word32       }-  | MemRef-      { timestamp :: !UnixTime+  | -- | the transaction is in the mempool+    MemRef+      { -- | time it entered the mempool+        timestamp :: !UnixTime       }   deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData) @@ -322,6 +366,7 @@   put = serialize   get = deserialize +-- | Whether a position is in a block rather than in the mempool. confirmed :: BlockRef -> Bool confirmed BlockRef {} = True confirmed MemRef {} = False@@ -411,7 +456,8 @@     utxo :: !Word64,     -- | number of transactions     txs :: !Word64,-    -- | total amount from all outputs in this address+    -- | total amount from all outputs in this address, including unconfirmed+    -- and spent ones     received :: !Word64   }   deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)@@ -442,6 +488,7 @@   put = serialize   get = deserialize +-- | Balance of an address that was never used. zeroBalance :: Address -> Balance zeroBalance a =   Balance@@ -453,6 +500,7 @@       received = 0     } +-- | Whether a balance is that of an address that was never used. nullBalance :: Balance -> Bool nullBalance   Balance@@ -498,10 +546,15 @@  -- | Unspent output. data Unspent = Unspent-  { block :: !BlockRef,+  { -- | position of the transaction that created it+    block :: !BlockRef,+    -- | the output     outpoint :: !OutPoint,+    -- | amount in satoshi     value :: !Word64,+    -- | output script     script :: !ByteString,+    -- | address of the output script, if it has one     address :: !(Maybe Address)   }   deriving (Show, Eq, Ord, Generic, Hashable, NFData)@@ -739,20 +792,34 @@    unmarshalValue net = parseJSON +-- | Transaction input with information about the output it spends. data StoreInput-  = StoreCoinbase-      { outpoint :: !OutPoint,+  = -- | input of a coinbase transaction+    StoreCoinbase+      { -- | null outpoint+        outpoint :: !OutPoint,+        -- | sequence number         sequence :: !Word32,+        -- | coinbase data         script :: !ByteString,+        -- | witness stack         witness :: !WitnessStack       }-  | StoreInput-      { outpoint :: !OutPoint,+  | -- | input that spends an output+    StoreInput+      { -- | output spent+        outpoint :: !OutPoint,+        -- | sequence number         sequence :: !Word32,+        -- | input script         script :: !ByteString,+        -- | script of the output spent         pkscript :: !ByteString,+        -- | amount of the output spent, in satoshi         value :: !Word64,+        -- | witness stack         witness :: !WitnessStack,+        -- | address of the output spent, if it has one         address :: !(Maybe Address)       }   deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)@@ -801,9 +868,11 @@   put = serialize   get = deserialize +-- | Whether a transaction is a coinbase: it has one input, which spends the null outpoint. isCoinbaseTx :: Tx -> Bool isCoinbaseTx = all ((== nullOutPoint) . (.outpoint)) . (.inputs) +-- | Whether an input is that of a coinbase transaction. isCoinbase :: StoreInput -> Bool isCoinbase StoreCoinbase {} = True isCoinbase StoreInput {} = False@@ -878,6 +947,7 @@               >>= maybe (return Nothing) (optional . unmarshalValue net)           return StoreInput {..} +-- | Parse a hex string in JSON. jsonHex :: Text -> Parser ByteString jsonHex s =   case decodeHex s of@@ -927,9 +997,13 @@  -- | Output information. data StoreOutput = StoreOutput-  { value :: !Word64,+  { -- | amount in satoshi+    value :: !Word64,+    -- | output script     script :: !ByteString,+    -- | input that spends it, if spent     spender :: !(Maybe Spender),+    -- | address of the output script, if it has one     address :: !(Maybe Address)   }   deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)@@ -985,8 +1059,11 @@           >>= maybe (return Nothing) (optional . unmarshalValue net)       return StoreOutput {..} +-- | Output spent by an input, as stored with the transaction. data Prev = Prev-  { script :: !ByteString,+  { -- | output script+    script :: !ByteString,+    -- | amount in satoshi     value :: !Word64   }   deriving (Show, Eq, Ord, Generic, Hashable, NFData)@@ -1008,6 +1085,7 @@   put = serialize   get = deserialize +-- | Input with information about the output it spends, which is missing for a coinbase. toInput :: Network -> Ctx -> TxIn -> Maybe Prev -> WitnessStack -> StoreInput toInput _ _ i Nothing w =   StoreCoinbase@@ -1027,6 +1105,7 @@       address = eitherToMaybe (scriptToAddressBS net ctx p.script)     } +-- | Output with its address and spender. toOutput :: Network -> Ctx -> TxOut -> Maybe Spender -> StoreOutput toOutput net ctx o s =   StoreOutput@@ -1036,13 +1115,21 @@       address = eitherToMaybe (scriptToAddressBS net ctx o.script)     } +-- | Transaction as stored in the database. data TxData = TxData-  { block :: !BlockRef,+  { -- | position in the chain or mempool+    block :: !BlockRef,+    -- | the transaction     tx :: !Tx,+    -- | outputs spent by each input, by input index     prevs :: !(IntMap Prev),+    -- | the transaction was removed, for example by a double spend     deleted :: !Bool,+    -- | the transaction can be replaced by fee     rbf :: !Bool,+    -- | time it was first seen, or time of its block     timestamp :: !Word64,+    -- | inputs that spend each output, by output index     spenders :: !(IntMap Spender)   }   deriving (Show, Eq, Ord, Generic, NFData)@@ -1074,6 +1161,7 @@   put = serialize   get = deserialize +-- | Fee of a transaction: inputs minus outputs, or zero for a coinbase. txDataFee :: TxData -> Word64 txDataFee t =   if isCoinbaseTx t.tx@@ -1124,6 +1212,7 @@     f n i w = toInput net ctx i (IntMap.lookup n t.prevs) w     g n o = toOutput net ctx o $ IntMap.lookup n t.spenders +-- | Inverse of 'toTransaction'. fromTransaction :: Transaction -> TxData fromTransaction t =   TxData@@ -1215,6 +1304,7 @@   put = serialize   get = deserialize +-- | The transaction without the information added by the store. transactionData :: Transaction -> Tx transactionData t =   Tx@@ -1358,7 +1448,10 @@  -- | Address balances for an extended public key. data XPubBal = XPubBal-  { path :: ![KeyIndex],+  { -- | derivation path below the xpub: chain (@0@ external, @1@ change) and+    -- index+    path :: ![KeyIndex],+    -- | balance of the address     balance :: !Balance   }   deriving (Show, Ord, Eq, Generic, NFData)@@ -1402,7 +1495,9 @@  -- | Unspent transaction for extended public key. data XPubUnspent = XPubUnspent-  { unspent :: !Unspent,+  { -- | the unspent output+    unspent :: !Unspent,+    -- | derivation path of its address below the xpub     path :: ![KeyIndex]   }   deriving (Show, Eq, Ord, Generic, NFData)@@ -1444,12 +1539,19 @@       path <- o .: "path"       return XPubUnspent {..} +-- | Summary of the balances of an extended public key. data XPubSummary = XPubSummary-  { confirmed :: !Word64,+  { -- | confirmed balance in satoshi+    confirmed :: !Word64,+    -- | unconfirmed balance in satoshi     unconfirmed :: !Word64,+    -- | total received by external addresses, in satoshi     received :: !Word64,+    -- | number of unspent outputs     utxo :: !Word64,+    -- | index of the last used external address     external :: !Word32,+    -- | index of the last used change address     change :: !Word32   }   deriving (Eq, Show, Generic, NFData)@@ -1527,12 +1629,18 @@       external <- i .: "external"       return XPubSummary {..} +-- | Result of a health check. class Healthy a where+  -- | Whether the check passed.   isOK :: a -> Bool +-- | Whether the database keeps up with the headers. data BlockHealth = BlockHealth-  { headers :: !BlockHeight,+  { -- | height of the best header+    headers :: !BlockHeight,+    -- | height of the best block in the database     blocks :: !BlockHeight,+    -- | blocks the database may lag behind     max :: !Int32   }   deriving (Show, Eq, Generic, NFData)@@ -1587,8 +1695,11 @@       max <- o .: "max"       return BlockHealth {..} +-- | Whether something happened recently enough. data TimeHealth = TimeHealth-  { age :: !Int64,+  { -- | seconds since it happened+    age :: !Int64,+    -- | maximum seconds allowed     max :: !Int64   }   deriving (Show, Eq, Generic, NFData)@@ -1633,8 +1744,11 @@       max <- o .: "max"       return TimeHealth {..} +-- | Whether there are enough of something. data CountHealth = CountHealth-  { count :: !Int64,+  { -- | how many there are+    count :: !Int64,+    -- | fewest allowed     min :: !Int64   }   deriving (Show, Eq, Generic, NFData)@@ -1678,8 +1792,11 @@       min <- o .: "min"       return CountHealth {..} +-- | Whether there are few enough of something. data MaxHealth = MaxHealth-  { count :: !Int64,+  { -- | how many there are+    count :: !Int64,+    -- | most allowed     max :: !Int64   }   deriving (Show, Eq, Generic, NFData)@@ -1723,14 +1840,24 @@       max <- o .: "max"       return MaxHealth {..} +-- | Health of the store, as returned by @\/health@. It is healthy if every check+-- passes. data HealthCheck = HealthCheck-  { blocks :: !BlockHealth,+  { -- | the database keeps up with the headers+    blocks :: !BlockHealth,+    -- | the best header is recent     lastBlock :: !TimeHealth,+    -- | a transaction or block arrived recently     lastTx :: !TimeHealth,+    -- | few transactions wait to be imported     pendingTxs :: !MaxHealth,+    -- | enough peers are connected     peers :: !CountHealth,+    -- | network name     network :: !String,+    -- | version of the software     version :: !String,+    -- | time of the check     time :: !Word64   }   deriving (Show, Eq, Generic, NFData)@@ -1806,9 +1933,12 @@       let time = round $ utcTimeToPOSIXSeconds utcTime       return HealthCheck {..} +-- | Event streamed by @\/events@. data Event-  = EventBlock !BlockHash-  | EventTx !TxHash+  = -- | a new best block+    EventBlock !BlockHash+  | -- | a transaction entered or left the mempool+    EventTx !TxHash   deriving (Show, Eq, Generic, NFData)  instance Serial Event where@@ -1851,6 +1981,7 @@           return $ EventBlock i         _ -> fail $ "Could not recognize event type: " <> t +-- | Response that wraps a single value, encoded in JSON as @{\"result\": value}@. newtype GenericResult a = GenericResult {get :: a}   deriving (Show, Eq, Generic, NFData) @@ -1874,6 +2005,8 @@   parseJSON =     A.withObject "GenericResult" $ \o -> GenericResult <$> o .: "result" +-- | Response with a value in its peer-to-peer serialization, encoded in JSON as+-- a hex string in @{\"result\": hex}@. newtype RawResult a = RawResult {get :: a}   deriving (Show, Eq, Generic, NFData) @@ -1904,6 +2037,7 @@               =<< decodeHex res       maybe mzero (return . RawResult) m +-- | List response. In binary, a length followed by the items. newtype SerialList a = SerialList {get :: [a]}   deriving (Show, Eq, Generic, NFData) @@ -1924,6 +2058,8 @@ instance (FromJSON a) => FromJSON (SerialList a) where   parseJSON = fmap SerialList . parseJSON +-- | List of values in their peer-to-peer serialization, encoded in JSON as a+-- list of hex strings. newtype RawResultList a = RawResultList {get :: [a]}   deriving (Show, Eq, Generic, NFData) @@ -1967,6 +2103,8 @@         A.withText "RawResultListItem" $ \t ->           maybe mzero return (decodeHex t >>= deser) +-- | Hash of a transaction, as returned when posting one, encoded in JSON as+-- @{\"txid\": hash}@. newtype TxId   = TxId TxHash   deriving (Show, Eq, Generic, NFData)@@ -1990,15 +2128,24 @@ instance FromJSON TxId where   parseJSON = A.withObject "txid" $ \o -> TxId <$> o .: "txid" +-- | Errors returned by the API. data Except-  = ThingNotFound-  | ServerError-  | BadRequest-  | UserError !String-  | StringError !String-  | TxIndexConflict ![TxHash]-  | ServerTimeout-  | RequestTooLarge+  = -- | the resource does not exist (404)+    ThingNotFound+  | -- | internal error (500)+    ServerError+  | -- | the request is malformed (400)+    BadRequest+  | -- | the request is invalid, with a reason (400)+    UserError !String+  | -- | other error, with a message (400)+    StringError !String+  | -- | a short transaction number matches these transactions (409)+    TxIndexConflict ![TxHash]+  | -- | the request took too long (500)+    ServerTimeout+  | -- | the request asks for too much (413)+    RequestTooLarge   deriving (Show, Eq, Ord, Generic, NFData)  instance Exception Except@@ -2070,15 +2217,21 @@ -- Blockchain.info API Compatibility -- --------------------------------------- +-- | Short numeric identifier of a transaction, as used by Blockchain.info: the+-- first eight bytes of its hash, read as a big-endian number, shifted right by+-- eleven bits. toIntTxId :: TxHash -> Word64 toIntTxId h =   let bs = runPutS (serialize h)       Right w64 = runGetS getWord64be bs    in w64 `shift` (-11) +-- | Block identified by hash or by height. data BinfoBlockId-  = BinfoBlockHash !BlockHash-  | BinfoBlockIndex !Word32+  = -- | by hash+    BinfoBlockHash !BlockHash+  | -- | by height+    BinfoBlockIndex !Word32   deriving (Eq, Show, Read, Generic, NFData)  instance Parsable BinfoBlockId where@@ -2090,11 +2243,15 @@         Just h -> Right $ BinfoBlockHash h       igr = BinfoBlockIndex <$> parseParam t +-- | Transaction identified by hash or by short number; see 'toIntTxId'. data BinfoTxId-  = BinfoTxIdHash !TxHash-  | BinfoTxIdIndex !Word64+  = -- | by hash+    BinfoTxIdHash !TxHash+  | -- | by short number+    BinfoTxIdIndex !Word64   deriving (Eq, Show, Read, Generic, NFData) +-- | Identify a transaction by short number if the flag is 'True', or by hash otherwise. encodeBinfoTxId :: Bool -> TxHash -> BinfoTxId encodeBinfoTxId False = BinfoTxIdHash encodeBinfoTxId True = BinfoTxIdIndex . toIntTxId@@ -2120,13 +2277,20 @@     BinfoTxIdHash <$> parseJSON v       <|> BinfoTxIdIndex <$> parseJSON v +-- | Which transactions to list in a Blockchain.info response (@filter@). data BinfoFilter-  = BinfoFilterAll-  | BinfoFilterSent-  | BinfoFilterReceived-  | BinfoFilterMoved-  | BinfoFilterConfirmed-  | BinfoFilterMempool+  = -- | all of them (@0@ or @6@)+    BinfoFilterAll+  | -- | those that decrease the balance (@1@)+    BinfoFilterSent+  | -- | those that increase the balance (@2@)+    BinfoFilterReceived+  | -- | those that leave the balance unchanged except for fees (@3@)+    BinfoFilterMoved+  | -- | confirmed ones (@5@)+    BinfoFilterConfirmed+  | -- | unconfirmed ones (@7@)+    BinfoFilterMempool   deriving (Eq, Show, Generic, NFData)  instance Parsable BinfoFilter where@@ -2141,12 +2305,19 @@       7 -> return BinfoFilterMempool       _ -> Left "could not parse filter parameter" +-- | Response of @\/blockchain\/multiaddr@. data BinfoMultiAddr = BinfoMultiAddr-  { addresses :: ![BinfoBalance],+  { -- | balance of each address and xpub requested+    addresses :: ![BinfoBalance],+    -- | totals of all of them     wallet :: !BinfoWallet,+    -- | page of their transactions, newest first     txs :: ![BinfoTx],+    -- | information about the chain and prices     info :: !BinfoInfo,+    -- | always 'True'     recommendFee :: !Bool,+    -- | addresses are shown in CashAddr format     cashAddr :: !Bool   }   deriving (Eq, Show, Generic, NFData)@@ -2187,13 +2358,21 @@     where       net = if not m.cashAddr && net' == bch then btc else net' +-- | Response of @\/blockchain\/rawaddr\/:addr@. data BinfoRawAddr = BinfoRawAddr-  { address :: !BinfoAddr,+  { -- | address or xpub+    address :: !BinfoAddr,+    -- | balance in satoshi     balance :: !Word64,+    -- | number of transactions     ntx :: !Word64,+    -- | number of unspent outputs     utxo :: !Word64,+    -- | total received in satoshi     received :: !Word64,+    -- | total sent in satoshi     sent :: !Int64,+    -- | page of transactions, newest first     txs :: ![BinfoTx]   }   deriving (Eq, Show, Generic, NFData)@@ -2256,9 +2435,13 @@       txs <- mapM (unmarshalValue (net, ctx)) =<< o .: "txs"       return BinfoRawAddr {..} +-- | Balance of an address or xpub in @\/blockchain\/balance@. data BinfoShortBal = BinfoShortBal-  { final :: !Word64,+  { -- | balance in satoshi+    final :: !Word64,+    -- | number of transactions     ntx :: !Word64,+    -- | total received in satoshi     received :: !Word64   }   deriving (Eq, Show, Read, Generic, NFData)@@ -2286,21 +2469,36 @@       received <- o .: "total_received"       return BinfoShortBal {..} +-- | Balance of an address or xpub in @\/blockchain\/multiaddr@. data BinfoBalance-  = BinfoAddrBalance-      { address :: !Address,+  = -- | balance of an address+    BinfoAddrBalance+      { -- | the address+        address :: !Address,+        -- | number of transactions         txs :: !Word64,+        -- | total received in satoshi         received :: !Word64,+        -- | total sent in satoshi         sent :: !Word64,+        -- | balance in satoshi         balance :: !Word64       }-  | BinfoXPubBalance-      { xpub :: !XPubKey,+  | -- | balance of an xpub+    BinfoXPubBalance+      { -- | the xpub+        xpub :: !XPubKey,+        -- | number of transactions         txs :: !Word64,+        -- | total received in satoshi         received :: !Word64,+        -- | total sent in satoshi         sent :: !Word64,+        -- | balance in satoshi         balance :: !Word64,+        -- | index of the last used external address         external :: !Word32,+        -- | index of the last used change address         change :: !Word32       }   deriving (Eq, Show, Generic, NFData)@@ -2375,11 +2573,17 @@     A.withArray "[BinfoBalance]" $       fmap V.toList . mapM (unmarshalValue (net, ctx)) +-- | Totals of all addresses and xpubs in @\/blockchain\/multiaddr@. data BinfoWallet = BinfoWallet-  { balance :: !Word64,+  { -- | balance in satoshi+    balance :: !Word64,+    -- | number of transactions     txs :: !Word64,+    -- | number of transactions that pass the filter     filtered :: !Word64,+    -- | total received in satoshi     received :: !Word64,+    -- | total sent in satoshi     sent :: !Word64   }   deriving (Eq, Show, Generic, NFData)@@ -2413,6 +2617,7 @@       sent <- o .: "total_sent"       return BinfoWallet {..} +-- | Amount as a big-endian hex string without leading zeros, as Blockchain.info shows values of unspent outputs. binfoHexValue :: Word64 -> Text binfoHexValue w64 =   encodeHex $@@ -2425,13 +2630,21 @@         runPutS $           serialize w64 +-- | Unspent output in @\/blockchain\/unspent@. data BinfoUnspent = BinfoUnspent-  { txid :: !TxHash,+  { -- | hash of the transaction+    txid :: !TxHash,+    -- | index of the output     index :: !Word32,+    -- | output script     script :: !ByteString,+    -- | amount in satoshi     value :: !Word64,+    -- | number of confirmations; @0@ if unconfirmed     confirmations :: !Int32,+    -- | identifier of the transaction     txidx :: !BinfoTxId,+    -- | xpub and path of its address, if it belongs to an xpub requested     xpub :: !(Maybe BinfoXPubPath)   }   deriving (Eq, Show, Generic, NFData)@@ -2477,6 +2690,7 @@       xpub <- mapM (unmarshalValue (net, ctx)) =<< o .:? "xpub"       return BinfoUnspent {..} +-- | Response of @\/blockchain\/unspent@. newtype BinfoUnspents = BinfoUnspents [BinfoUnspent]   deriving (Eq, Show, Generic, NFData) @@ -2499,6 +2713,7 @@       us <- mapM (unmarshalValue (net, ctx)) =<< o .: "unspent_outputs"       return (BinfoUnspents us) +-- | Block in the Blockchain.info format, from its data, its transactions and the hashes of its children. toBinfoBlock :: BlockData -> [BinfoTx] -> [BlockHash] -> BinfoBlock toBinfoBlock b transactions next_blocks =   BinfoBlock@@ -2520,22 +2735,39 @@       txs = transactions     } +-- | Block in the Blockchain.info format. data BinfoBlock = BinfoBlock-  { hash :: !BlockHash,+  { -- | block hash+    hash :: !BlockHash,+    -- | block version     version :: !Word32,+    -- | hash of the previous block     prev :: !BlockHash,+    -- | Merkle root of the transactions     merkle :: !Hash256,+    -- | block timestamp     timestamp :: !Word32,+    -- | difficulty target in compact form     bits :: !Word32,+    -- | hashes of the children of the block     next :: ![BlockHash],+    -- | total fees in satoshi     fee :: !Word64,+    -- | nonce     nonce :: !Word32,+    -- | number of transactions     ntx :: !Word32,+    -- | size in bytes     size :: !Word32,+    -- | block index; the height     index :: !Word32,+    -- | whether it is in the main chain     main :: !Bool,+    -- | height     height :: !Word32,+    -- | weight     weight :: !Word32,+    -- | transactions     txs :: ![BinfoTx]   }   deriving (Eq, Show, Generic, NFData)@@ -2613,24 +2845,44 @@     A.withObject "blocks" $ \o ->       mapM (unmarshalValue (net, ctx)) =<< o .: "blocks" +-- | Transaction in the Blockchain.info format. data BinfoTx = BinfoTx-  { txid :: !TxHash,+  { -- | hash+    txid :: !TxHash,+    -- | version     version :: !Word32,+    -- | number of inputs     inputCount :: !Word32,+    -- | number of outputs     outputCount :: !Word32,+    -- | size in bytes     size :: !Word32,+    -- | weight     weight :: !Word32,+    -- | fee in satoshi     fee :: !Word64,+    -- | IP address of the peer that relayed it; always @0.0.0.0@     relayed :: !ByteString,+    -- | lock time     locktime :: !Word32,+    -- | identifier     index :: !BinfoTxId,+    -- | whether it was removed by a double spend     doubleSpend :: !Bool,+    -- | whether it can be replaced by fee     rbf :: !Bool,+    -- | net change to the balance of the addresses requested, and the+    -- balance after it; only in responses about addresses     balance :: !(Maybe (Int64, Int64)),+    -- | time first seen, or time of its block     timestamp :: !Word64,+    -- | height of its block, if confirmed     blockIndex :: !(Maybe Word32),+    -- | height of its block, if confirmed     blockHeight :: !(Maybe Word32),+    -- | inputs     inputs :: ![BinfoTxInput],+    -- | outputs     outputs :: ![BinfoTxOutput]   }   deriving (Eq, Show, Generic, NFData)@@ -2726,11 +2978,17 @@     A.withArray "[BinfoTx]" $       fmap V.toList . mapM (unmarshalValue (net, ctx)) +-- | Transaction input in the Blockchain.info format. data BinfoTxInput = BinfoTxInput-  { sequence :: !Word32,+  { -- | sequence number+    sequence :: !Word32,+    -- | serialized witness stack     witness :: !ByteString,+    -- | input script     script :: !ByteString,+    -- | index of the input     index :: !Word32,+    -- | output that it spends     output :: !BinfoTxOutput   }   deriving (Eq, Show, Generic, NFData)@@ -2770,15 +3028,25 @@           >>= unmarshalValue (net, ctx)       return BinfoTxInput {..} +-- | Transaction output in the Blockchain.info format. data BinfoTxOutput = BinfoTxOutput-  { typ :: !Int,+  { -- | type; always @0@+    typ :: !Int,+    -- | whether it is spent     spent :: !Bool,+    -- | amount in satoshi     value :: !Word64,+    -- | index of the output     index :: !Word32,+    -- | identifier of its transaction     txidx :: !BinfoTxId,+    -- | output script     script :: !ByteString,+    -- | inputs that spend it     spenders :: ![BinfoSpender],+    -- | address of the output script, if it has one     address :: !(Maybe Address),+    -- | xpub and path of its address, if it belongs to an xpub requested     xpub :: !(Maybe BinfoXPubPath)   }   deriving (Eq, Show, Generic, NFData)@@ -2832,8 +3100,11 @@       xpub <- o .:? "xpub" >>= mapM (unmarshalValue (net, ctx))       return BinfoTxOutput {..} +-- | Input that spends an output, in the Blockchain.info format. data BinfoSpender = BinfoSpender-  { txidx :: !BinfoTxId,+  { -- | identifier of the spending transaction+    txidx :: !BinfoTxId,+    -- | index of the input     input :: !Word32   }   deriving (Eq, Show, Generic, NFData)@@ -2858,8 +3129,11 @@       input <- o .: "n"       return BinfoSpender {..} +-- | Xpub that an address belongs to, with its derivation path. data BinfoXPubPath = BinfoXPubPath-  { key :: !XPubKey,+  { -- | the xpub+    key :: !XPubKey,+    -- | derivation path of the address below the xpub     deriv :: !SoftPath   }   deriving (Eq, Show, Generic, NFData)@@ -2892,11 +3166,17 @@       deriv <- fromMaybe "bad xpub path" . parseSoft <$> o .: "path"       return BinfoXPubPath {..} +-- | Information about the chain and prices in @\/blockchain\/multiaddr@. data BinfoInfo = BinfoInfo-  { connected :: !Word32,+  { -- | number of connected peers+    connected :: !Word32,+    -- | satoshi per unit of the fiat currency     conversion :: !Double,+    -- | fiat currency     fiat :: !BinfoSymbol,+    -- | coin of the network     crypto :: !BinfoSymbol,+    -- | best block     head :: !BinfoBlockInfo   }   deriving (Eq, Show, Generic, NFData)@@ -2930,10 +3210,15 @@       head <- o .: "latest_block"       return BinfoInfo {..} +-- | Short information about a block in the Blockchain.info format. data BinfoBlockInfo = BinfoBlockInfo-  { hash :: !BlockHash,+  { -- | block hash+    hash :: !BlockHash,+    -- | height     height :: !BlockHeight,+    -- | block timestamp     timestamp :: !Word32,+    -- | block index; the height     index :: !BlockHeight   }   deriving (Eq, Show, Generic, NFData)@@ -2964,6 +3249,7 @@       index <- o .: "block_index"       return BinfoBlockInfo {..} +-- | Short information about a block. toBinfoBlockInfo :: BlockData -> BinfoBlockInfo toBinfoBlockInfo d =   BinfoBlockInfo@@ -2973,9 +3259,13 @@       index = d.height     } +-- | Historical price of the coin of the network, from the price service. data BinfoRate = BinfoRate-  { timestamp :: !Word64,+  { -- | time of the price+    timestamp :: !Word64,+    -- | price in the fiat currency     price :: !Double,+    -- | volume in the 24 hours before     vol24 :: !Double   }   deriving (Eq, Show, Generic, NFData)@@ -3003,15 +3293,25 @@       vol24 <- o .: "volume24h"       return BinfoRate {..} +-- | Entry of @\/blockchain\/export-history@. data BinfoHistory = BinfoHistory-  { date :: !Text,+  { -- | date, as @YYYY-MM-DD@+    date :: !Text,+    -- | time of day     time :: !Text,+    -- | @sent@ or @received@     typ :: !Text,+    -- | amount in coins     amount :: !Double,+    -- | value in fiat at the time     valueThen :: !Double,+    -- | value in fiat now     valueNow :: !Double,+    -- | price at the time     rateThen :: !Double,+    -- | transaction hash     txid :: !TxHash,+    -- | fee in coins     fee :: !Double   }   deriving (Eq, Show, Generic, NFData)@@ -3057,6 +3357,8 @@       fee <- o .: "fee"       return BinfoHistory {..} +-- | History entry from a net amount in satoshi, a time, the price then and now, a+-- fee in satoshi, and a transaction hash. toBinfoHistory ::   Int64 ->   Word64 ->@@ -3086,6 +3388,8 @@     v1 = v * r1     v2 = v * r2 +-- | Date given as @DD-MM-YYYY@ or @DD\/MM\/YYYY@ in a request, as seconds+-- since the Unix epoch. newtype BinfoDate = BinfoDate Word64   deriving (Eq, Show, Read, Generic, NFData) @@ -3098,11 +3402,18 @@       s = LazyText.unpack t       p fmt = parseTimeM False defaultTimeLocale fmt s +-- | Price of the coin of the network in a fiat currency, from the ticker+-- service. data BinfoTicker = BinfoTicker-  { fifteen :: !Double,+  { -- | price delayed by fifteen minutes+    fifteen :: !Double,+    -- | last price     last :: !Double,+    -- | buy price     buy :: !Double,+    -- | sell price     sell :: !Double,+    -- | currency symbol     symbol :: !Text   }   deriving (Eq, Show, Generic, NFData)@@ -3146,12 +3457,19 @@       last <- o .: "last"       return BinfoTicker {..} +-- | Currency in the Blockchain.info format. data BinfoSymbol = BinfoSymbol-  { code :: !Text,+  { -- | currency code, such as @USD@+    code :: !Text,+    -- | symbol, such as @$@     symbol :: !Text,+    -- | name     name :: !Text,+    -- | satoshi per unit     conversion :: !Double,+    -- | whether the symbol goes after the amount     after :: !Bool,+    -- | whether it is a fiat currency     local :: !Bool   }   deriving (Eq, Show, Generic, NFData)@@ -3199,6 +3517,8 @@       local <- o .: "local"       return BinfoSymbol {..} +-- | Transactions to fetch to show a transaction: those it spends, and, if outputs+-- are pruned, those that spend its outputs to other addresses. relevantTxs ::   HashSet Address ->   Bool ->@@ -3221,6 +3541,7 @@     g StoreInput {outpoint = OutPoint h i} = Just h     ins = mapMaybe g t.inputs +-- | Balances of addresses and xpubs in the Blockchain.info format. toBinfoAddrs ::   HashMap Address Balance ->   HashMap XPubSpec [XPubBal] ->@@ -3262,6 +3583,7 @@              in BinfoAddrBalance {..}        in map f $ HashMap.elems onlyAddrs +-- | Transaction in the Blockchain.info format, without balance information. toBinfoTxSimple ::   Bool ->   Transaction ->@@ -3269,6 +3591,7 @@ toBinfoTxSimple numtxid =   toBinfoTx numtxid HashMap.empty False 0 +-- | Inputs of a transaction in the Blockchain.info format. toBinfoTxInputs ::   Bool ->   HashMap Address (Maybe BinfoXPubPath) ->@@ -3297,11 +3620,16 @@       putVarInt (B.length bs)       putByteString bs +-- | Height of the block of a transaction, or 'Nothing' if unconfirmed. transactionHeight :: Transaction -> Maybe BlockHeight transactionHeight Transaction {deleted = True} = Nothing transactionHeight Transaction {block = MemRef _} = Nothing transactionHeight Transaction {block = BlockRef h _} = Just h +-- | Transaction in the Blockchain.info format. Arguments: identify transactions+-- by short number; address book of the addresses requested and their xpubs;+-- prune outputs to other addresses of a transaction that increases the balance;+-- balance after the transaction. toBinfoTx ::   Bool ->   HashMap Address (Maybe BinfoXPubPath) ->@@ -3341,6 +3669,7 @@     aset = HashMap.keysSet abook     simple = HashMap.null abook && bal == 0 +-- | Net change that a transaction makes to the balance of a set of addresses. getTxResult :: HashSet Address -> Transaction -> Int64 getTxResult aset t =   inputSum + outputSum@@ -3364,6 +3693,7 @@             then fromIntegral value             else 0 +-- | Output in the Blockchain.info format, or 'Nothing' if it is pruned. toBinfoTxOutput ::   Bool ->   HashMap Address (Maybe BinfoXPubPath) ->@@ -3391,6 +3721,7 @@   where     notInBook = isNothing $ o.address >>= flip HashMap.lookup abook +-- | Spender in the Blockchain.info format. toBinfoSpender :: Bool -> Spender -> BinfoSpender toBinfoSpender numtxid s =   BinfoSpender@@ -3398,6 +3729,7 @@       input = s.index     } +-- | Output spent by an input, in the Blockchain.info format. inputToBinfoTxOutput ::   Bool ->   HashMap Address (Maybe BinfoXPubPath) ->@@ -3434,11 +3766,15 @@           StoreInput {address} -> address >>= join . flip HashMap.lookup abook     } +-- | Address or xpub in a Blockchain.info request. data BinfoAddr-  = BinfoAddr !Address-  | BinfoXpub !XPubKey+  = -- | an address+    BinfoAddr !Address+  | -- | an xpub+    BinfoXpub !XPubKey   deriving (Eq, Show, Generic, Hashable, NFData) +-- | Parse addresses and xpubs separated by pipes or commas. Returns 'Nothing' if any is invalid. parseBinfoAddr :: Network -> Ctx -> Text -> Maybe [BinfoAddr] parseBinfoAddr net ctx "" = Just [] parseBinfoAddr net ctx s =@@ -3449,11 +3785,17 @@       BinfoAddr <$> textToAddr net x         <|> BinfoXpub <$> xPubImport net ctx x +-- | Block header with its transactions in the Blockchain.info format. data BinfoHeader = BinfoHeader-  { hash :: !BlockHash,+  { -- | block hash+    hash :: !BlockHash,+    -- | block timestamp     timestamp :: !Timestamp,+    -- | block index; the height     index :: !Word32,+    -- | height     height :: !BlockHeight,+    -- | identifiers of its transactions     txids :: ![BinfoTxId]   }   deriving (Eq, Show, Generic, NFData)@@ -3487,6 +3829,7 @@       txids <- o .: "txIndexes"       return BinfoHeader {..} +-- | Response of @\/blockchain\/unconfirmed-transactions@. newtype BinfoMempool = BinfoMempool {get :: [BinfoTx]}   deriving (Eq, Show, Generic, NFData) @@ -3501,6 +3844,7 @@     A.withObject "BinfoMempool" $ \o ->       BinfoMempool <$> (mapM (unmarshalValue (net, ctx)) =<< o .: "txs") +-- | List of short block information. newtype BinfoBlockInfos = BinfoBlockInfos {get :: [BinfoBlockInfo]}   deriving (Eq, Show, Generic, NFData) 
src/Haskoin/Store/WebClient.hs view
@@ -8,11 +8,39 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoFieldSelectors #-} +-- |+-- Module      : Haskoin.Store.WebClient+-- Copyright   : (c) Haskoin Developers+-- License     : MIT+-- Maintainer  : jprupp@protonmail.ch+-- Stability   : experimental+-- Portability : POSIX+--+-- Client for the REST API of Haskoin Store.+--+-- Build a request value, such as 'GetAddrBalance', and pass it to 'apiCall'.+-- The response type follows from the request type. Requests and responses use+-- the compact binary encoding of the API. Errors returned by the server are+-- thrown as 'Store.Except' in 'MonadError'.+--+-- @+-- import Control.Monad.Except (runExceptT)+-- import Data.Default (def)+--+-- balance :: Ctx -> Address -> IO (Either Except Balance)+-- balance ctx addr = runExceptT $ 'apiCall' ctx def ('GetAddrBalance' addr)+-- @+--+-- Requests that take long lists of addresses, transactions or blocks can be+-- split into several calls with 'apiBatch'.+-- The client always uses the binary encoding, which is smaller and faster than+-- JSON and decodes straight into the types of "Haskoin.Store.Data". module Haskoin.Store.WebClient-  ( ApiConfig (..),+  ( -- * Calls+    ApiConfig (..),     apiCall,     apiBatch,-    -- Blocks+    -- * Blocks     GetBlock (..),     GetBlocks (..),     GetBlockRaw (..),@@ -24,7 +52,7 @@     GetBlockHeightRaw (..),     GetBlockTime (..),     GetBlockTimeRaw (..),-    -- Transactions+    -- * Transactions     GetTx (..),     GetTxs (..),     GetTxRaw (..),@@ -35,7 +63,7 @@     PostTx (..),     GetMempool (..),     GetEvents (..),-    -- Address+    -- * Addresses     GetAddrTxs (..),     GetAddrsTxs (..),     GetAddrTxsFull (..),@@ -44,17 +72,17 @@     GetAddrsBalance (..),     GetAddrUnspent (..),     GetAddrsUnspent (..),-    -- XPubs+    -- * Extended public keys     GetXPub (..),     GetXPubTxs (..),     GetXPubTxsFull (..),     GetXPubBalances (..),     GetXPubUnspent (..),     DelCachedXPub (..),-    -- Network+    -- * Network     GetPeers (..),     GetHealth (..),-    -- Params+    -- * Parameters     StartParam (..),     OffsetParam (..),     LimitParam (..),@@ -105,7 +133,9 @@ -- } -- @ data ApiConfig = ApiConfig-  { net :: !Network,+  { -- | network to query; its name is appended to the host+    net :: !Network,+    -- | base URL of the API, ending in a slash     host :: !String   }   deriving (Eq, Show)@@ -125,7 +155,9 @@ -- -- With options: ----- > apiCall ctx def $ GetAddrsUnspent addrs def{ paramLimit = Just 10 }+-- > apiCall ctx def $ GetAddrsUnspent addrs def {limit = Just 10}+--+-- Only @GET@ and @POST@ endpoints are supported. apiCall ::   (ApiResource a b, MonadIO m, MonadError Store.Except m) =>   Ctx ->@@ -146,7 +178,10 @@  -- | Batch commands that have a large list of arguments: ----- > apiBatch 20 def (GetAddrsTxs addrs def)+-- > apiBatch ctx 20 def (GetAddrsTxs addrs def)+--+-- The list is split into chunks of the given size, one call is made per chunk,+-- and the results are concatenated. apiBatch ::   (Batchable a b, MonadIO m, MonadError Store.Except m) =>   Ctx ->@@ -157,7 +192,10 @@ apiBatch ctx i conf res =   mconcat <$> mapM (apiCall ctx conf) (resourceBatch i res) +-- | Requests that can be split into several smaller ones whose results are+-- concatenated. class (ApiResource a b, Monoid b) => Batchable a b where+  -- | Split a request into requests with lists of at most this size.   resourceBatch :: Natural -> a -> [a]  instance Batchable GetBlocks (Store.SerialList Store.BlockData) where@@ -197,6 +235,7 @@ -- API Internal -- ------------------ +-- | HTTP options carrying the query string parameters of a request. toOptions ::   (ApiResource a b) =>   Network ->@@ -208,6 +247,7 @@   where     f (ParamBox p) = toOption net ctx p +-- | HTTP option for one query string parameter. toOption ::   (Param a) =>   Network ->@@ -220,9 +260,11 @@       encodeParam net ctx a   return $ applyOpt (paramLabel a) res +-- | Set a query string parameter to comma-separated values. applyOpt :: Text -> [Text] -> Endo HTTP.Options applyOpt p t = Endo $ HTTP.param p .~ [Text.intercalate "," t] +-- | Make a @GET@ request and decode the binary response. getBinary ::   (Serial a) =>   Endo HTTP.Options ->@@ -234,6 +276,7 @@     res <- resE     return . runGetL deserialize $ res ^. HTTP.responseBody +-- | Make a @POST@ request with a binary body and decode the binary response. postBinary ::   (Serial a, Serial r) =>   Endo HTTP.Options ->@@ -246,6 +289,7 @@     res <- resE     return . runGetL deserialize $ res ^. HTTP.responseBody +-- | Request binary responses, and turn error responses into exceptions. binaryOpts :: Endo HTTP.Options -> HTTP.Options binaryOpts opts =   appEndo (opts <> accept <> stat) HTTP.defaults@@ -253,6 +297,9 @@     accept = Endo $ HTTP.header "Accept" .~ ["application/octet-stream"]     stat = Endo $ HTTP.checkResponse ?~ checkStatus +-- | Throw the error in the body of an unsuccessful response as 'Store.Except'.+-- A health check that answers 503 is not an error: its body says what is+-- wrong. checkStatus :: ResponseChecker checkStatus req res   | statusIsSuccessful status = return ()@@ -273,6 +320,7 @@ -- Utilities -- --------------- +-- | Split a list into chunks of at most this size. chunksOf :: Natural -> [a] -> [[a]] chunksOf n xs   | null xs = []
src/Haskoin/Store/WebCommon.hs view
@@ -10,6 +10,27 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoFieldSelectors #-} +-- |+-- Module      : Haskoin.Store.WebCommon+-- Copyright   : (c) Haskoin Developers+-- License     : MIT+-- Maintainer  : jprupp@protonmail.ch+-- Stability   : experimental+-- Portability : POSIX+--+-- Description of the REST API, shared by the server ("Haskoin.Store.Web") and+-- the client ("Haskoin.Store.WebClient").+--+-- Each endpoint is a type, such as 'GetBlock', with an 'ApiResource' instance+-- that gives its HTTP method, path, parameters and response type. Parameters+-- are types with a 'Param' instance, which knows how to encode them in a URL+-- and parse them back.+--+-- Endpoints that return lists take a limit, an offset and a start+-- ('LimitsParam'). Results are ordered from newest to oldest.+-- Describing each endpoint once, as a type, keeps the server and the client from+-- drifting apart: the path, parameters and response type that the server routes+-- are the same ones the client builds requests from. module Haskoin.Store.WebCommon where  import Control.Applicative ((<|>))@@ -42,21 +63,34 @@ -- API Resources -- ------------------- +-- | An endpoint of the API. The request type @a@ determines the response type+-- @b@. class (Serial b) => ApiResource a b | a -> b where+  -- | HTTP method; 'GET' by default.   resourceMethod :: Proxy a -> StdMethod   resourceMethod _ = GET+  -- | Path of the endpoint, given the encoded values of the parameters that+  -- are part of the path.   resourcePath :: Proxy a -> ([Text] -> Text)+  -- | Parameters of a request: those that go in the path, and those that go in+  -- the query string.   queryParams :: a -> ([ParamBox], [ParamBox]) -- (resource, querystring)   queryParams _ = ([], [])+  -- | Types of the parameters that go in the path, used to build the route+  -- pattern of the server.   captureParams :: Proxy a -> [ProxyBox]   captureParams _ = []+  -- | Body of the request, if any.   resourceBody :: a -> Maybe PostBox   resourceBody = const Nothing +-- | Request body of any serializable type. data PostBox = forall s. (Serial s) => PostBox !s +-- | Parameter value of any type. data ParamBox = forall p. (Eq p, Param p) => ParamBox !p +-- | Parameter type, without a value. data ProxyBox = forall p. (Param p) => ProxyBox !(Proxy p)  --------------------@@ -64,86 +98,125 @@ --------------------  -- Blocks+-- | Request for a block by hash: @GET \/block\/:block@. data GetBlock = GetBlock !BlockHash !NoTx +-- | Request for several blocks by hash: @GET \/blocks?blocks=…@. data GetBlocks = GetBlocks ![BlockHash] !NoTx +-- | Request for a block by hash, serialized as in the peer-to-peer protocol: @GET \/block\/:block\/raw@. newtype GetBlockRaw = GetBlockRaw BlockHash +-- | Request for the best block: @GET \/block\/best@. newtype GetBlockBest = GetBlockBest NoTx +-- | Request for the best block, serialized: @GET \/block\/best\/raw@. data GetBlockBestRaw = GetBlockBestRaw +-- | Request for the best block and its ancestors, up to 100 blocks: @GET \/block\/latest@. newtype GetBlockLatest = GetBlockLatest NoTx +-- | Request for the blocks at a height: @GET \/block\/height\/:height@. data GetBlockHeight = GetBlockHeight !HeightParam !NoTx +-- | Request for the blocks at several heights: @GET \/block\/heights?heights=…@. data GetBlockHeights = GetBlockHeights !HeightsParam !NoTx +-- | Request for the blocks at a height, serialized: @GET \/block\/height\/:height\/raw@. newtype GetBlockHeightRaw = GetBlockHeightRaw HeightParam +-- | Request for the last block with a timestamp at or before a time: @GET \/block\/time\/:time@. data GetBlockTime = GetBlockTime !TimeParam !NoTx +-- | Request for the last block with a timestamp at or before a time, serialized: @GET \/block\/time\/:time\/raw@. newtype GetBlockTimeRaw = GetBlockTimeRaw TimeParam +-- | Request for the first block with a median time past at or after a time: @GET \/block\/mtp\/:time@. data GetBlockMTP = GetBlockMTP !TimeParam !NoTx +-- | Request for the first block with a median time past at or after a time, serialized: @GET \/block\/mtp\/:time\/raw@. newtype GetBlockMTPRaw = GetBlockMTPRaw TimeParam  -- Transactions+-- | Request for a transaction: @GET \/transaction\/:txid@. newtype GetTx = GetTx TxHash +-- | Request for several transactions: @GET \/transactions?txids=…@. newtype GetTxs = GetTxs [TxHash] +-- | Request for a transaction, serialized: @GET \/transaction\/:txid\/raw@. newtype GetTxRaw = GetTxRaw TxHash +-- | Request for several transactions, serialized: @GET \/transactions\/raw?txids=…@. newtype GetTxsRaw = GetTxsRaw [TxHash] +-- | Request for the transactions of a block: @GET \/transactions\/block\/:block@. newtype GetTxsBlock = GetTxsBlock BlockHash +-- | Request for the transactions of a block, serialized: @GET \/transactions\/block\/:block\/raw@. newtype GetTxsBlockRaw = GetTxsBlockRaw BlockHash +-- | Request for whether any ancestor of a transaction is a coinbase confirmed after a height, or null if it cannot be computed within limits: @GET \/transaction\/:txid\/after\/:height@. data GetTxAfter = GetTxAfter !TxHash !HeightParam +-- | Request to broadcast a transaction: @POST \/transactions@. newtype PostTx = PostTx Tx +-- | Request for hashes of mempool transactions, newest first: @GET \/mempool@. data GetMempool = GetMempool !(Maybe LimitParam) !OffsetParam +-- | Request for stream of new blocks and mempool transactions: @GET \/events@. data GetEvents = GetEvents  -- Address+-- | Request for transactions of an address: @GET \/address\/:address\/transactions@. data GetAddrTxs = GetAddrTxs !Address !LimitsParam +-- | Request for transactions of several addresses: @GET \/address\/transactions?addresses=…@. data GetAddrsTxs = GetAddrsTxs ![Address] !LimitsParam +-- | Request for full transactions of an address: @GET \/address\/:address\/transactions\/full@. data GetAddrTxsFull = GetAddrTxsFull !Address !LimitsParam +-- | Request for full transactions of several addresses: @GET \/address\/transactions\/full?addresses=…@. data GetAddrsTxsFull = GetAddrsTxsFull ![Address] !LimitsParam +-- | Request for balance of an address: @GET \/address\/:address\/balance@. newtype GetAddrBalance = GetAddrBalance Address +-- | Request for balances of several addresses: @GET \/address\/balances?addresses=…@. newtype GetAddrsBalance = GetAddrsBalance [Address] +-- | Request for unspent outputs of an address: @GET \/address\/:address\/unspent@. data GetAddrUnspent = GetAddrUnspent !Address !LimitsParam +-- | Request for unspent outputs of several addresses: @GET \/address\/unspent?addresses=…@. data GetAddrsUnspent = GetAddrsUnspent ![Address] !LimitsParam  -- XPubs+-- | Request for summary of an extended public key: @GET \/xpub\/:xpub@. data GetXPub = GetXPub !XPubKey !Store.DeriveType !NoCache +-- | Request for transactions of an extended public key: @GET \/xpub\/:xpub\/transactions@. data GetXPubTxs = GetXPubTxs !XPubKey !Store.DeriveType !LimitsParam !NoCache +-- | Request for full transactions of an extended public key: @GET \/xpub\/:xpub\/transactions\/full@. data GetXPubTxsFull = GetXPubTxsFull !XPubKey !Store.DeriveType !LimitsParam !NoCache +-- | Request for balances of the used addresses of an extended public key: @GET \/xpub\/:xpub\/balances@. data GetXPubBalances = GetXPubBalances !XPubKey !Store.DeriveType !NoCache +-- | Request for unspent outputs of an extended public key: @GET \/xpub\/:xpub\/unspent@. data GetXPubUnspent = GetXPubUnspent !XPubKey !Store.DeriveType !LimitsParam !NoCache +-- | Request to remove an extended public key from the cache: @DELETE \/xpub\/:xpub@. data DelCachedXPub = DelCachedXPub !XPubKey !Store.DeriveType  -- Network+-- | Request for connected peers: @GET \/peers@. data GetPeers = GetPeers +-- | Request for health of the store: @GET \/health@. data GetHealth = GetHealth  ------------@@ -365,30 +438,38 @@ -- Helpers -- ------------- +-- | Path made of a prefix followed by one parameter. (<:>) :: Text -> [Text] -> Text (<:>) = (<+> "") +-- | Path made of a prefix, one parameter and a suffix. (<+>) :: Text -> Text -> [Text] -> Text a <+> b = fill 1 [a, b] +-- | Path made of a prefix, a parameter, an infix and another parameter. (<++>) :: Text -> Text -> [Text] -> Text a <++> b = fill 2 [a, b] +-- | Interleave path fragments with this many parameters. Calls 'error' if the number of parameters is wrong. fill :: Int -> [Text] -> [Text] -> Text fill i a b   | length b /= i = error "Invalid query parameters"   | otherwise = mconcat $ uncurry (<>) <$> zip a (b <> repeat "") +-- | Parameter to send, unless it has its default value. noDefBox :: (Default p, Param p, Eq p) => p -> [ParamBox] noDefBox p = [ParamBox p | p /= def] +-- | Parameter to send, if any. noMaybeBox :: (Param p, Eq p) => Maybe p -> [ParamBox] noMaybeBox (Just p) = [ParamBox p] noMaybeBox _ = [] +-- | Proxy for the type of a value. asProxy :: a -> Proxy a asProxy = const Proxy +-- | Path of a request, with its path parameters filled in. queryPath :: (ApiResource a b) => Network -> Ctx -> a -> Text queryPath net ctx a = f $ encParam <$> fst (queryParams a)   where@@ -398,6 +479,7 @@         Just [res] -> res         _ -> error "Invalid query param" +-- | Route pattern of an endpoint for the server, with a named capture for each path parameter. capturePath :: (ApiResource a b) => Proxy a -> Scotty.RoutePattern capturePath proxy =   fromString $ cs $ f $ toLabel <$> captureParams proxy@@ -405,6 +487,7 @@     f = resourcePath proxy     toLabel (ProxyBox p) = ":" <> proxyLabel p +-- | Name of a parameter in URLs. paramLabel :: (Param p) => p -> Text paramLabel = proxyLabel . asProxy @@ -412,9 +495,14 @@ -- Options -- ------------- +-- | Type that can be a parameter of a request. class Param a where+  -- | Name of the parameter in URLs.   proxyLabel :: Proxy a -> Text+  -- | Encode a value as one or more strings. A list parameter is sent as+  -- comma-separated values.   encodeParam :: Network -> Ctx -> a -> Maybe [Text]+  -- | Parse a value from one or more strings.   parseParam :: Network -> Ctx -> [Text] -> Maybe a  instance Param Address where@@ -428,10 +516,16 @@   encodeParam net ctx = mapM (addrToText net)   parseParam net ctx = mapM (textToAddr net) +-- | Where a page of results starts. In URLs it is a transaction hash, a block+-- height, or a Unix timestamp. Numbers up to 1230768000 are heights and larger+-- ones are timestamps. data StartParam-  = StartParamHash {hash :: Hash256}-  | StartParamHeight {height :: Natural}-  | StartParamTime {time :: Store.UnixTime}+  = -- | start at a transaction+    StartParamHash {hash :: Hash256}+  | -- | start at a block height+    StartParamHeight {height :: Natural}+  | -- | start at the last block at or before a time+    StartParamTime {time :: Store.UnixTime}   deriving (Eq, Show)  instance Param StartParam where@@ -462,6 +556,7 @@         return $ StartParamTime x   parseParam net ctx _ = Nothing +-- | Number of results to skip (@offset@). newtype OffsetParam = OffsetParam {get :: Natural}   deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) @@ -474,6 +569,7 @@   parseParam net ctx [s] = OffsetParam <$> readMaybe (cs s)   parseParam net ctx _ = Nothing +-- | Maximum number of results (@limit@). newtype LimitParam = LimitParam {get :: Natural}   deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) @@ -483,9 +579,14 @@   parseParam net ctx [s] = LimitParam <$> readMaybe (cs s)   parseParam net ctx _ = Nothing +-- | Pagination of a list of results. data LimitsParam = LimitsParam-  { limit :: Maybe LimitParam, -- 0 means maximum+  { -- | maximum number of results; @0@ means the maximum the server allows,+    -- and 'Nothing' the server default+    limit :: Maybe LimitParam,+    -- | number of results to skip after the start     offset :: OffsetParam,+    -- | where to start, or from the newest result     start :: Maybe StartParam   }   deriving (Eq, Show)@@ -493,6 +594,7 @@ instance Default LimitsParam where   def = LimitsParam Nothing def Nothing +-- | Block height (@height@). newtype HeightParam = HeightParam {get :: Natural}   deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) @@ -502,6 +604,7 @@   parseParam net ctx [s] = HeightParam <$> readMaybe (cs s)   parseParam net ctx _ = Nothing +-- | List of block heights (@heights@). newtype HeightsParam = HeightsParam {get :: [Natural]}   deriving (Eq, Show, Read) @@ -510,6 +613,7 @@   encodeParam net ctx (HeightsParam hs) = Just $ cs . show <$> hs   parseParam net ctx xs = HeightsParam <$> mapM (readMaybe . cs) xs +-- | Unix timestamp in seconds (@time@). newtype TimeParam = TimeParam {get :: Store.UnixTime}   deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) @@ -537,6 +641,7 @@     guard (net.segWit || res == Store.DeriveNormal)     return res +-- | Do not use the cache to answer an xpub query (@nocache@). newtype NoCache = NoCache {get :: Bool}   deriving (Eq, Show, Read) @@ -552,6 +657,8 @@     ["false"] -> Just $ NoCache False     _ -> Nothing +-- | Leave out the transaction hashes of a block, except that of the coinbase+-- (@notx@). newtype NoTx = NoTx {get :: Bool}   deriving (Eq, Show, Read)