haskoin-store-data 0.65.12 → 1.0.0
raw patch · 8 files changed
+5724/−4036 lines, 8 filesdep +vectordep ~haskoin-corePVP ok
version bump matches the API change (PVP)
Dependencies added: vector
Dependency ranges changed: haskoin-core
API changes (from Hackage documentation)
- Haskoin.Store.Data: BinfoBlocks :: [BinfoBlockInfo] -> BinfoBlocks
- Haskoin.Store.Data: PeerInformation :: !ByteString -> !String -> !Word32 -> !Word64 -> !Bool -> PeerInformation
- Haskoin.Store.Data: [balanceAddress] :: Balance -> !Address
- Haskoin.Store.Data: [balanceAmount] :: Balance -> !Word64
- Haskoin.Store.Data: [balanceTotalReceived] :: Balance -> !Word64
- Haskoin.Store.Data: [balanceTxCount] :: Balance -> !Word64
- Haskoin.Store.Data: [balanceUnspentCount] :: Balance -> !Word64
- Haskoin.Store.Data: [balanceZero] :: Balance -> !Word64
- Haskoin.Store.Data: [binfoBlocks] :: BinfoBlocks -> [BinfoBlockInfo]
- Haskoin.Store.Data: [binfoHeaderHash] :: BinfoHeader -> !BlockHash
- Haskoin.Store.Data: [binfoHeaderHeight] :: BinfoHeader -> !BlockHeight
- Haskoin.Store.Data: [binfoHeaderIndex] :: BinfoHeader -> !Word32
- Haskoin.Store.Data: [binfoHeaderTime] :: BinfoHeader -> !Timestamp
- Haskoin.Store.Data: [binfoHistoryAmount] :: BinfoHistory -> !Double
- Haskoin.Store.Data: [binfoHistoryDate] :: BinfoHistory -> !Text
- Haskoin.Store.Data: [binfoHistoryExchangeRateThen] :: BinfoHistory -> !Double
- Haskoin.Store.Data: [binfoHistoryFee] :: BinfoHistory -> !Double
- Haskoin.Store.Data: [binfoHistoryTime] :: BinfoHistory -> !Text
- Haskoin.Store.Data: [binfoHistoryTx] :: BinfoHistory -> !TxHash
- Haskoin.Store.Data: [binfoHistoryType] :: BinfoHistory -> !Text
- Haskoin.Store.Data: [binfoHistoryValueNow] :: BinfoHistory -> !Double
- Haskoin.Store.Data: [binfoHistoryValueThen] :: BinfoHistory -> !Double
- Haskoin.Store.Data: [binfoRatePrice] :: BinfoRate -> !Double
- Haskoin.Store.Data: [binfoRateTime] :: BinfoRate -> !Word64
- Haskoin.Store.Data: [binfoRateVol24] :: BinfoRate -> !Double
- Haskoin.Store.Data: [binfoRawAddr] :: BinfoRawAddr -> !BinfoAddr
- Haskoin.Store.Data: [binfoRawBalance] :: BinfoRawAddr -> !Word64
- Haskoin.Store.Data: [binfoRawReceived] :: BinfoRawAddr -> !Word64
- Haskoin.Store.Data: [binfoRawSent] :: BinfoRawAddr -> !Int64
- Haskoin.Store.Data: [binfoRawTxCount] :: BinfoRawAddr -> !Word64
- Haskoin.Store.Data: [binfoRawTxs] :: BinfoRawAddr -> ![BinfoTx]
- Haskoin.Store.Data: [binfoRawUnredeemed] :: BinfoRawAddr -> !Word64
- Haskoin.Store.Data: [binfoShortBalFinal] :: BinfoShortBal -> !Word64
- Haskoin.Store.Data: [binfoShortBalReceived] :: BinfoShortBal -> !Word64
- Haskoin.Store.Data: [binfoShortBalTxCount] :: BinfoShortBal -> !Word64
- Haskoin.Store.Data: [binfoTicker15m] :: BinfoTicker -> !Double
- Haskoin.Store.Data: [binfoTickerBuy] :: BinfoTicker -> !Double
- Haskoin.Store.Data: [binfoTickerLast] :: BinfoTicker -> !Double
- Haskoin.Store.Data: [binfoTickerSell] :: BinfoTicker -> !Double
- Haskoin.Store.Data: [binfoTickerSymbol] :: BinfoTicker -> !Text
- Haskoin.Store.Data: [binfoTxIndices] :: BinfoHeader -> ![BinfoTxId]
- Haskoin.Store.Data: [blockDataFees] :: BlockData -> !Word64
- Haskoin.Store.Data: [blockDataHeader] :: BlockData -> !BlockHeader
- Haskoin.Store.Data: [blockDataHeight] :: BlockData -> !BlockHeight
- Haskoin.Store.Data: [blockDataMainChain] :: BlockData -> !Bool
- Haskoin.Store.Data: [blockDataOutputs] :: BlockData -> !Word64
- Haskoin.Store.Data: [blockDataSize] :: BlockData -> !Word32
- Haskoin.Store.Data: [blockDataSubsidy] :: BlockData -> !Word64
- Haskoin.Store.Data: [blockDataTxs] :: BlockData -> ![TxHash]
- Haskoin.Store.Data: [blockDataWeight] :: BlockData -> !Word32
- Haskoin.Store.Data: [blockDataWork] :: BlockData -> !Integer
- Haskoin.Store.Data: [blockHealthBlocks] :: BlockHealth -> !BlockHeight
- Haskoin.Store.Data: [blockHealthHeaders] :: BlockHealth -> !BlockHeight
- Haskoin.Store.Data: [blockHealthMaxDiff] :: BlockHealth -> !Int32
- Haskoin.Store.Data: [blockRefHeight] :: BlockRef -> !BlockHeight
- Haskoin.Store.Data: [blockRefPos] :: BlockRef -> !Word32
- Haskoin.Store.Data: [countHealthMin] :: CountHealth -> !Int64
- Haskoin.Store.Data: [countHealthNum] :: CountHealth -> !Int64
- Haskoin.Store.Data: [getBinfoAddrBalance] :: BinfoBalance -> !Word64
- Haskoin.Store.Data: [getBinfoAddrReceived] :: BinfoBalance -> !Word64
- Haskoin.Store.Data: [getBinfoAddrSent] :: BinfoBalance -> !Word64
- Haskoin.Store.Data: [getBinfoAddrTxCount] :: BinfoBalance -> !Word64
- Haskoin.Store.Data: [getBinfoAddress] :: BinfoBalance -> !Address
- Haskoin.Store.Data: [getBinfoBTC] :: BinfoInfo -> !BinfoSymbol
- Haskoin.Store.Data: [getBinfoBlockBits] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockFee] :: BinfoBlock -> !Word64
- Haskoin.Store.Data: [getBinfoBlockHash] :: BinfoBlock -> !BlockHash
- Haskoin.Store.Data: [getBinfoBlockHeight] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockIndex] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockInfoHash] :: BinfoBlockInfo -> !BlockHash
- Haskoin.Store.Data: [getBinfoBlockInfoHeight] :: BinfoBlockInfo -> !BlockHeight
- Haskoin.Store.Data: [getBinfoBlockInfoIndex] :: BinfoBlockInfo -> !BlockHeight
- Haskoin.Store.Data: [getBinfoBlockInfoTime] :: BinfoBlockInfo -> !Word32
- Haskoin.Store.Data: [getBinfoBlockMain] :: BinfoBlock -> !Bool
- Haskoin.Store.Data: [getBinfoBlockNonce] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockSize] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockTime] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockTxCount] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockTx] :: BinfoBlock -> ![BinfoTx]
- Haskoin.Store.Data: [getBinfoBlockVer] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoBlockWeight] :: BinfoBlock -> !Word32
- Haskoin.Store.Data: [getBinfoConnected] :: BinfoInfo -> !Word32
- Haskoin.Store.Data: [getBinfoConversion] :: BinfoInfo -> !Double
- Haskoin.Store.Data: [getBinfoLatestBlock] :: BinfoInfo -> !BinfoBlockInfo
- Haskoin.Store.Data: [getBinfoLocal] :: BinfoInfo -> !BinfoSymbol
- Haskoin.Store.Data: [getBinfoMempool] :: BinfoMempool -> [BinfoTx]
- Haskoin.Store.Data: [getBinfoMerkleRoot] :: BinfoBlock -> !Hash256
- Haskoin.Store.Data: [getBinfoMultiAddrAddresses] :: BinfoMultiAddr -> ![BinfoBalance]
- Haskoin.Store.Data: [getBinfoMultiAddrCashAddr] :: BinfoMultiAddr -> !Bool
- Haskoin.Store.Data: [getBinfoMultiAddrInfo] :: BinfoMultiAddr -> !BinfoInfo
- Haskoin.Store.Data: [getBinfoMultiAddrRecommendFee] :: BinfoMultiAddr -> !Bool
- Haskoin.Store.Data: [getBinfoMultiAddrTxs] :: BinfoMultiAddr -> ![BinfoTx]
- Haskoin.Store.Data: [getBinfoMultiAddrWallet] :: BinfoMultiAddr -> !BinfoWallet
- Haskoin.Store.Data: [getBinfoNextBlock] :: BinfoBlock -> ![BlockHash]
- Haskoin.Store.Data: [getBinfoPrevBlock] :: BinfoBlock -> !BlockHash
- Haskoin.Store.Data: [getBinfoSpenderIndex] :: BinfoSpender -> !Word32
- Haskoin.Store.Data: [getBinfoSpenderTxIndex] :: BinfoSpender -> !BinfoTxId
- Haskoin.Store.Data: [getBinfoSymbolAfter] :: BinfoSymbol -> !Bool
- Haskoin.Store.Data: [getBinfoSymbolCode] :: BinfoSymbol -> !Text
- Haskoin.Store.Data: [getBinfoSymbolConversion] :: BinfoSymbol -> !Double
- Haskoin.Store.Data: [getBinfoSymbolLocal] :: BinfoSymbol -> !Bool
- Haskoin.Store.Data: [getBinfoSymbolName] :: BinfoSymbol -> !Text
- Haskoin.Store.Data: [getBinfoSymbolString] :: BinfoSymbol -> !Text
- Haskoin.Store.Data: [getBinfoTxBlockHeight] :: BinfoTx -> !Maybe Word32
- Haskoin.Store.Data: [getBinfoTxBlockIndex] :: BinfoTx -> !Maybe Word32
- Haskoin.Store.Data: [getBinfoTxDoubleSpend] :: BinfoTx -> !Bool
- Haskoin.Store.Data: [getBinfoTxFee] :: BinfoTx -> !Word64
- Haskoin.Store.Data: [getBinfoTxHash] :: BinfoTx -> !TxHash
- Haskoin.Store.Data: [getBinfoTxIndex] :: BinfoTx -> !BinfoTxId
- Haskoin.Store.Data: [getBinfoTxInputIndex] :: BinfoTxInput -> !Word32
- Haskoin.Store.Data: [getBinfoTxInputPrevOut] :: BinfoTxInput -> !BinfoTxOutput
- Haskoin.Store.Data: [getBinfoTxInputScript] :: BinfoTxInput -> !ByteString
- Haskoin.Store.Data: [getBinfoTxInputSeq] :: BinfoTxInput -> !Word32
- Haskoin.Store.Data: [getBinfoTxInputWitness] :: BinfoTxInput -> !ByteString
- Haskoin.Store.Data: [getBinfoTxInputs] :: BinfoTx -> [BinfoTxInput]
- Haskoin.Store.Data: [getBinfoTxLockTime] :: BinfoTx -> !Word32
- Haskoin.Store.Data: [getBinfoTxOutputAddress] :: BinfoTxOutput -> !Maybe Address
- Haskoin.Store.Data: [getBinfoTxOutputIndex] :: BinfoTxOutput -> !Word32
- Haskoin.Store.Data: [getBinfoTxOutputScript] :: BinfoTxOutput -> !ByteString
- Haskoin.Store.Data: [getBinfoTxOutputSpenders] :: BinfoTxOutput -> ![BinfoSpender]
- Haskoin.Store.Data: [getBinfoTxOutputSpent] :: BinfoTxOutput -> !Bool
- Haskoin.Store.Data: [getBinfoTxOutputTxIndex] :: BinfoTxOutput -> !BinfoTxId
- Haskoin.Store.Data: [getBinfoTxOutputType] :: BinfoTxOutput -> !Int
- Haskoin.Store.Data: [getBinfoTxOutputValue] :: BinfoTxOutput -> !Word64
- Haskoin.Store.Data: [getBinfoTxOutputXPub] :: BinfoTxOutput -> !Maybe BinfoXPubPath
- Haskoin.Store.Data: [getBinfoTxOutputs] :: BinfoTx -> [BinfoTxOutput]
- Haskoin.Store.Data: [getBinfoTxRBF] :: BinfoTx -> !Bool
- Haskoin.Store.Data: [getBinfoTxRelayedBy] :: BinfoTx -> !ByteString
- Haskoin.Store.Data: [getBinfoTxResultBal] :: BinfoTx -> !Maybe (Int64, Int64)
- Haskoin.Store.Data: [getBinfoTxSize] :: BinfoTx -> !Word32
- Haskoin.Store.Data: [getBinfoTxTime] :: BinfoTx -> !Word64
- Haskoin.Store.Data: [getBinfoTxVer] :: BinfoTx -> !Word32
- Haskoin.Store.Data: [getBinfoTxVinSz] :: BinfoTx -> !Word32
- Haskoin.Store.Data: [getBinfoTxVoutSz] :: BinfoTx -> !Word32
- Haskoin.Store.Data: [getBinfoTxWeight] :: BinfoTx -> !Word32
- Haskoin.Store.Data: [getBinfoUnspentConfirmations] :: BinfoUnspent -> !Int32
- Haskoin.Store.Data: [getBinfoUnspentHash] :: BinfoUnspent -> !TxHash
- Haskoin.Store.Data: [getBinfoUnspentOutputIndex] :: BinfoUnspent -> !Word32
- Haskoin.Store.Data: [getBinfoUnspentScript] :: BinfoUnspent -> !ByteString
- Haskoin.Store.Data: [getBinfoUnspentTxIndex] :: BinfoUnspent -> !BinfoTxId
- Haskoin.Store.Data: [getBinfoUnspentValue] :: BinfoUnspent -> !Word64
- Haskoin.Store.Data: [getBinfoUnspentXPub] :: BinfoUnspent -> !Maybe BinfoXPubPath
- Haskoin.Store.Data: [getBinfoWalletBalance] :: BinfoWallet -> !Word64
- Haskoin.Store.Data: [getBinfoWalletFilteredCount] :: BinfoWallet -> !Word64
- Haskoin.Store.Data: [getBinfoWalletTotalReceived] :: BinfoWallet -> !Word64
- Haskoin.Store.Data: [getBinfoWalletTotalSent] :: BinfoWallet -> !Word64
- Haskoin.Store.Data: [getBinfoWalletTxCount] :: BinfoWallet -> !Word64
- Haskoin.Store.Data: [getBinfoXPubAccountIndex] :: BinfoBalance -> !Word32
- Haskoin.Store.Data: [getBinfoXPubChangeIndex] :: BinfoBalance -> !Word32
- Haskoin.Store.Data: [getBinfoXPubKey] :: BinfoBalance -> !XPubKey
- Haskoin.Store.Data: [getBinfoXPubPathDeriv] :: BinfoXPubPath -> !SoftPath
- Haskoin.Store.Data: [getBinfoXPubPathKey] :: BinfoXPubPath -> !XPubKey
- Haskoin.Store.Data: [getRawResultList] :: RawResultList a -> [a]
- Haskoin.Store.Data: [getRawResult] :: RawResult a -> a
- Haskoin.Store.Data: [getResult] :: GenericResult a -> a
- Haskoin.Store.Data: [getSerialList] :: SerialList a -> [a]
- Haskoin.Store.Data: [healthBlocks] :: HealthCheck -> !BlockHealth
- Haskoin.Store.Data: [healthLastBlock] :: HealthCheck -> !TimeHealth
- Haskoin.Store.Data: [healthLastTx] :: HealthCheck -> !TimeHealth
- Haskoin.Store.Data: [healthNetwork] :: HealthCheck -> !String
- Haskoin.Store.Data: [healthPeers] :: HealthCheck -> !CountHealth
- Haskoin.Store.Data: [healthPendingTxs] :: HealthCheck -> !MaxHealth
- Haskoin.Store.Data: [healthVersion] :: HealthCheck -> !String
- Haskoin.Store.Data: [inputAddress] :: StoreInput -> !Maybe Address
- Haskoin.Store.Data: [inputAmount] :: StoreInput -> !Word64
- Haskoin.Store.Data: [inputPkScript] :: StoreInput -> !ByteString
- Haskoin.Store.Data: [inputPoint] :: StoreInput -> !OutPoint
- Haskoin.Store.Data: [inputSequence] :: StoreInput -> !Word32
- Haskoin.Store.Data: [inputSigScript] :: StoreInput -> !ByteString
- Haskoin.Store.Data: [inputWitness] :: StoreInput -> !WitnessStack
- Haskoin.Store.Data: [maxHealthMax] :: MaxHealth -> !Int64
- Haskoin.Store.Data: [maxHealthNum] :: MaxHealth -> !Int64
- Haskoin.Store.Data: [memRefTime] :: BlockRef -> !UnixTime
- Haskoin.Store.Data: [outputAddr] :: StoreOutput -> !Maybe Address
- Haskoin.Store.Data: [outputAmount] :: StoreOutput -> !Word64
- Haskoin.Store.Data: [outputScript] :: StoreOutput -> !ByteString
- Haskoin.Store.Data: [outputSpender] :: StoreOutput -> !Maybe Spender
- Haskoin.Store.Data: [peerAddress] :: PeerInformation -> !String
- Haskoin.Store.Data: [peerRelay] :: PeerInformation -> !Bool
- Haskoin.Store.Data: [peerServices] :: PeerInformation -> !Word64
- Haskoin.Store.Data: [peerUserAgent] :: PeerInformation -> !ByteString
- Haskoin.Store.Data: [peerVersion] :: PeerInformation -> !Word32
- Haskoin.Store.Data: [prevAmount] :: Prev -> !Word64
- Haskoin.Store.Data: [prevScript] :: Prev -> !ByteString
- Haskoin.Store.Data: [spenderHash] :: Spender -> !TxHash
- Haskoin.Store.Data: [spenderIndex] :: Spender -> !Word32
- Haskoin.Store.Data: [timeHealthAge] :: TimeHealth -> !Int64
- Haskoin.Store.Data: [timeHealthMax] :: TimeHealth -> !Int64
- Haskoin.Store.Data: [transactionBlock] :: Transaction -> !BlockRef
- Haskoin.Store.Data: [transactionDeleted] :: Transaction -> !Bool
- Haskoin.Store.Data: [transactionFees] :: Transaction -> !Word64
- Haskoin.Store.Data: [transactionId] :: Transaction -> !TxHash
- Haskoin.Store.Data: [transactionInputs] :: Transaction -> ![StoreInput]
- Haskoin.Store.Data: [transactionLockTime] :: Transaction -> !Word32
- Haskoin.Store.Data: [transactionOutputs] :: Transaction -> ![StoreOutput]
- Haskoin.Store.Data: [transactionRBF] :: Transaction -> !Bool
- Haskoin.Store.Data: [transactionSize] :: Transaction -> !Word32
- Haskoin.Store.Data: [transactionTime] :: Transaction -> !Word64
- Haskoin.Store.Data: [transactionVersion] :: Transaction -> !Word32
- Haskoin.Store.Data: [transactionWeight] :: Transaction -> !Word32
- Haskoin.Store.Data: [txDataBlock] :: TxData -> !BlockRef
- Haskoin.Store.Data: [txDataDeleted] :: TxData -> !Bool
- Haskoin.Store.Data: [txDataPrevs] :: TxData -> !IntMap Prev
- Haskoin.Store.Data: [txDataRBF] :: TxData -> !Bool
- Haskoin.Store.Data: [txDataSpenders] :: TxData -> !IntMap Spender
- Haskoin.Store.Data: [txDataTime] :: TxData -> !Word64
- Haskoin.Store.Data: [txData] :: TxData -> !Tx
- Haskoin.Store.Data: [txRefBlock] :: TxRef -> !BlockRef
- Haskoin.Store.Data: [txRefHash] :: TxRef -> !TxHash
- Haskoin.Store.Data: [unspentAddress] :: Unspent -> !Maybe Address
- Haskoin.Store.Data: [unspentAmount] :: Unspent -> !Word64
- Haskoin.Store.Data: [unspentBlock] :: Unspent -> !BlockRef
- Haskoin.Store.Data: [unspentPoint] :: Unspent -> !OutPoint
- Haskoin.Store.Data: [unspentScript] :: Unspent -> !ByteString
- Haskoin.Store.Data: [xPubBalPath] :: XPubBal -> ![KeyIndex]
- Haskoin.Store.Data: [xPubBal] :: XPubBal -> !Balance
- Haskoin.Store.Data: [xPubChangeIndex] :: XPubSummary -> !Word32
- Haskoin.Store.Data: [xPubDeriveType] :: XPubSpec -> !DeriveType
- Haskoin.Store.Data: [xPubExternalIndex] :: XPubSummary -> !Word32
- Haskoin.Store.Data: [xPubSpecKey] :: XPubSpec -> !XPubKey
- Haskoin.Store.Data: [xPubSummaryConfirmed] :: XPubSummary -> !Word64
- Haskoin.Store.Data: [xPubSummaryReceived] :: XPubSummary -> !Word64
- Haskoin.Store.Data: [xPubSummaryZero] :: XPubSummary -> !Word64
- Haskoin.Store.Data: [xPubUnspentCount] :: XPubSummary -> !Word64
- Haskoin.Store.Data: [xPubUnspentPath] :: XPubUnspent -> ![KeyIndex]
- Haskoin.Store.Data: [xPubUnspent] :: XPubUnspent -> !Unspent
- Haskoin.Store.Data: balanceParseJSON :: Network -> Value -> Parser Balance
- Haskoin.Store.Data: balanceToEncoding :: Network -> Balance -> Encoding
- Haskoin.Store.Data: balanceToJSON :: Network -> Balance -> Value
- Haskoin.Store.Data: binfoBalanceParseJSON :: Network -> Value -> Parser BinfoBalance
- Haskoin.Store.Data: binfoBalanceToEncoding :: Network -> BinfoBalance -> Encoding
- Haskoin.Store.Data: binfoBalanceToJSON :: Network -> BinfoBalance -> Value
- Haskoin.Store.Data: binfoBlockParseJSON :: Network -> Value -> Parser BinfoBlock
- Haskoin.Store.Data: binfoBlockToEncoding :: Network -> BinfoBlock -> Encoding
- Haskoin.Store.Data: binfoBlockToJSON :: Network -> BinfoBlock -> Value
- Haskoin.Store.Data: binfoBlocksParseJSON :: Network -> Value -> Parser [BinfoBlock]
- Haskoin.Store.Data: binfoBlocksToEncoding :: Network -> [BinfoBlock] -> Encoding
- Haskoin.Store.Data: binfoBlocksToJSON :: Network -> [BinfoBlock] -> Value
- Haskoin.Store.Data: binfoMempoolParseJSON :: Network -> Value -> Parser BinfoMempool
- Haskoin.Store.Data: binfoMempoolToEncoding :: Network -> BinfoMempool -> Encoding
- Haskoin.Store.Data: binfoMempoolToJSON :: Network -> BinfoMempool -> Value
- Haskoin.Store.Data: binfoMultiAddrParseJSON :: Network -> Value -> Parser BinfoMultiAddr
- Haskoin.Store.Data: binfoMultiAddrToEncoding :: Network -> BinfoMultiAddr -> Encoding
- Haskoin.Store.Data: binfoMultiAddrToJSON :: Network -> BinfoMultiAddr -> Value
- Haskoin.Store.Data: binfoRawAddrParseJSON :: Network -> Value -> Parser BinfoRawAddr
- Haskoin.Store.Data: binfoRawAddrToEncoding :: Network -> BinfoRawAddr -> Encoding
- Haskoin.Store.Data: binfoRawAddrToJSON :: Network -> BinfoRawAddr -> Value
- Haskoin.Store.Data: binfoTxInputParseJSON :: Network -> Value -> Parser BinfoTxInput
- Haskoin.Store.Data: binfoTxInputToEncoding :: Network -> BinfoTxInput -> Encoding
- Haskoin.Store.Data: binfoTxInputToJSON :: Network -> BinfoTxInput -> Value
- Haskoin.Store.Data: binfoTxOutputParseJSON :: Network -> Value -> Parser BinfoTxOutput
- Haskoin.Store.Data: binfoTxOutputToEncoding :: Network -> BinfoTxOutput -> Encoding
- Haskoin.Store.Data: binfoTxOutputToJSON :: Network -> BinfoTxOutput -> Value
- Haskoin.Store.Data: binfoTxParseJSON :: Network -> Value -> Parser BinfoTx
- Haskoin.Store.Data: binfoTxToEncoding :: Network -> BinfoTx -> Encoding
- Haskoin.Store.Data: binfoTxToJSON :: Network -> BinfoTx -> Value
- Haskoin.Store.Data: binfoUnspentParseJSON :: Network -> Value -> Parser BinfoUnspent
- Haskoin.Store.Data: binfoUnspentToEncoding :: Network -> BinfoUnspent -> Encoding
- Haskoin.Store.Data: binfoUnspentToJSON :: Network -> BinfoUnspent -> Value
- Haskoin.Store.Data: binfoUnspentsParseJSON :: Network -> Value -> Parser BinfoUnspents
- Haskoin.Store.Data: binfoUnspentsToEncoding :: Network -> BinfoUnspents -> Encoding
- Haskoin.Store.Data: binfoUnspentsToJSON :: Network -> BinfoUnspents -> Value
- Haskoin.Store.Data: binfoXPubPathParseJSON :: Network -> Value -> Parser BinfoXPubPath
- Haskoin.Store.Data: binfoXPubPathToEncoding :: Network -> BinfoXPubPath -> Encoding
- Haskoin.Store.Data: binfoXPubPathToJSON :: Network -> BinfoXPubPath -> Value
- Haskoin.Store.Data: blockDataToEncoding :: Network -> BlockData -> Encoding
- Haskoin.Store.Data: blockDataToJSON :: Network -> BlockData -> Value
- Haskoin.Store.Data: data PeerInformation
- Haskoin.Store.Data: instance Control.DeepSeq.NFData Haskoin.Store.Data.BinfoBlocks
- Haskoin.Store.Data: instance Control.DeepSeq.NFData Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Store.Data.BinfoBlocks
- Haskoin.Store.Data: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Store.Data.BinfoBlocks
- Haskoin.Store.Data: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance Data.Binary.Class.Binary Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance Data.Bytes.Serial.Serial Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance Data.Serialize.Serialize Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance GHC.Classes.Eq Haskoin.Store.Data.BinfoBlocks
- Haskoin.Store.Data: instance GHC.Classes.Eq Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance GHC.Classes.Ord Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance GHC.Generics.Generic Haskoin.Store.Data.BinfoBlocks
- Haskoin.Store.Data: instance GHC.Generics.Generic Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: instance GHC.Show.Show Haskoin.Store.Data.BinfoBlocks
- Haskoin.Store.Data: instance GHC.Show.Show Haskoin.Store.Data.PeerInformation
- Haskoin.Store.Data: newtype BinfoBlocks
- Haskoin.Store.Data: storeInputParseJSON :: Network -> Value -> Parser StoreInput
- Haskoin.Store.Data: storeInputToEncoding :: Network -> StoreInput -> Encoding
- Haskoin.Store.Data: storeInputToJSON :: Network -> StoreInput -> Value
- Haskoin.Store.Data: storeOutputParseJSON :: Network -> Value -> Parser StoreOutput
- Haskoin.Store.Data: storeOutputToEncoding :: Network -> StoreOutput -> Encoding
- Haskoin.Store.Data: storeOutputToJSON :: Network -> StoreOutput -> Value
- Haskoin.Store.Data: transactionParseJSON :: Network -> Value -> Parser Transaction
- Haskoin.Store.Data: transactionToEncoding :: Network -> Transaction -> Encoding
- Haskoin.Store.Data: transactionToJSON :: Network -> Transaction -> Value
- Haskoin.Store.Data: unspentParseJSON :: Network -> Value -> Parser Unspent
- Haskoin.Store.Data: unspentToEncoding :: Network -> Unspent -> Encoding
- Haskoin.Store.Data: unspentToJSON :: Network -> Unspent -> Value
- Haskoin.Store.Data: xPubBalParseJSON :: Network -> Value -> Parser XPubBal
- Haskoin.Store.Data: xPubBalToEncoding :: Network -> XPubBal -> Encoding
- Haskoin.Store.Data: xPubBalToJSON :: Network -> XPubBal -> Value
- Haskoin.Store.Data: xPubUnspentParseJSON :: Network -> Value -> Parser XPubUnspent
- Haskoin.Store.Data: xPubUnspentToEncoding :: Network -> XPubUnspent -> Encoding
- Haskoin.Store.Data: xPubUnspentToJSON :: Network -> XPubUnspent -> Value
- Haskoin.Store.WebClient: [configHost] :: ApiConfig -> !String
- Haskoin.Store.WebClient: [configNetwork] :: ApiConfig -> !Network
- Haskoin.Store.WebClient: [getHeightParam] :: HeightParam -> Natural
- Haskoin.Store.WebClient: [getHeightsParam] :: HeightsParam -> [Natural]
- Haskoin.Store.WebClient: [getLimitParam] :: LimitParam -> Natural
- Haskoin.Store.WebClient: [getNoCache] :: NoCache -> Bool
- Haskoin.Store.WebClient: [getNoTx] :: NoTx -> Bool
- Haskoin.Store.WebClient: [getOffsetParam] :: OffsetParam -> Natural
- Haskoin.Store.WebClient: [paramLimit] :: LimitsParam -> Maybe LimitParam
- Haskoin.Store.WebClient: [paramOffset] :: LimitsParam -> OffsetParam
- Haskoin.Store.WebClient: [paramStart] :: LimitsParam -> Maybe StartParam
- Haskoin.Store.WebClient: [startParamHash] :: StartParam -> Hash256
- Haskoin.Store.WebClient: [startParamHeight] :: StartParam -> Natural
- Haskoin.Store.WebClient: [startParamTime] :: StartParam -> UnixTime
- Haskoin.Store.WebCommon: [getHeightParam] :: HeightParam -> Natural
- Haskoin.Store.WebCommon: [getHeightsParam] :: HeightsParam -> [Natural]
- Haskoin.Store.WebCommon: [getLimitParam] :: LimitParam -> Natural
- Haskoin.Store.WebCommon: [getNoCache] :: NoCache -> Bool
- Haskoin.Store.WebCommon: [getNoTx] :: NoTx -> Bool
- Haskoin.Store.WebCommon: [getOffsetParam] :: OffsetParam -> Natural
- Haskoin.Store.WebCommon: [getTimeParam] :: TimeParam -> UnixTime
- Haskoin.Store.WebCommon: [paramLimit] :: LimitsParam -> Maybe LimitParam
- Haskoin.Store.WebCommon: [paramOffset] :: LimitsParam -> OffsetParam
- Haskoin.Store.WebCommon: [paramStart] :: LimitsParam -> Maybe StartParam
- Haskoin.Store.WebCommon: [startParamHash] :: StartParam -> Hash256
- Haskoin.Store.WebCommon: [startParamHeight] :: StartParam -> Natural
- Haskoin.Store.WebCommon: [startParamTime] :: StartParam -> UnixTime
- Haskoin.Store.WebCommon: instance Haskoin.Store.WebCommon.ApiResource Haskoin.Store.WebCommon.GetPeers (Haskoin.Store.Data.SerialList Haskoin.Store.Data.PeerInformation)
- Haskoin.Store.WebCommon: instance Haskoin.Store.WebCommon.Param Haskoin.Keys.Extended.XPubKey
+ Haskoin.Store.Data: BinfoBlockInfos :: [BinfoBlockInfo] -> BinfoBlockInfos
+ Haskoin.Store.Data: PeerInfo :: !ByteString -> !String -> !Word32 -> !Word64 -> !Bool -> PeerInfo
+ Haskoin.Store.Data: [$sel:address:Balance] :: Balance -> !Address
+ Haskoin.Store.Data: [$sel:address:BinfoAddrBalance] :: BinfoBalance -> !Address
+ Haskoin.Store.Data: [$sel:address:BinfoRawAddr] :: BinfoRawAddr -> !BinfoAddr
+ Haskoin.Store.Data: [$sel:address:BinfoTxOutput] :: BinfoTxOutput -> !Maybe Address
+ Haskoin.Store.Data: [$sel:address:PeerInfo] :: PeerInfo -> !String
+ Haskoin.Store.Data: [$sel:address:StoreCoinbase] :: StoreInput -> !Maybe Address
+ Haskoin.Store.Data: [$sel:address:StoreOutput] :: StoreOutput -> !Maybe Address
+ Haskoin.Store.Data: [$sel:address:Unspent] :: Unspent -> !Maybe Address
+ Haskoin.Store.Data: [$sel:addresses:BinfoMultiAddr] :: BinfoMultiAddr -> ![BinfoBalance]
+ Haskoin.Store.Data: [$sel:after:BinfoSymbol] :: BinfoSymbol -> !Bool
+ Haskoin.Store.Data: [$sel:age:TimeHealth] :: TimeHealth -> !Int64
+ Haskoin.Store.Data: [$sel:amount:BinfoHistory] :: BinfoHistory -> !Double
+ Haskoin.Store.Data: [$sel:balance:BinfoAddrBalance] :: BinfoBalance -> !Word64
+ Haskoin.Store.Data: [$sel:balance:BinfoRawAddr] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [$sel:balance:BinfoTx] :: BinfoTx -> !Maybe (Int64, Int64)
+ Haskoin.Store.Data: [$sel:balance:BinfoWallet] :: BinfoWallet -> !Word64
+ Haskoin.Store.Data: [$sel:balance:XPubBal] :: XPubBal -> !Balance
+ Haskoin.Store.Data: [$sel:bits:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:block:Transaction] :: Transaction -> !BlockRef
+ Haskoin.Store.Data: [$sel:block:TxData] :: TxData -> !BlockRef
+ Haskoin.Store.Data: [$sel:block:TxRef] :: TxRef -> !BlockRef
+ Haskoin.Store.Data: [$sel:block:Unspent] :: Unspent -> !BlockRef
+ Haskoin.Store.Data: [$sel:blockHeight:BinfoTx] :: BinfoTx -> !Maybe Word32
+ Haskoin.Store.Data: [$sel:blockIndex:BinfoTx] :: BinfoTx -> !Maybe Word32
+ Haskoin.Store.Data: [$sel:blocks:BlockHealth] :: BlockHealth -> !BlockHeight
+ Haskoin.Store.Data: [$sel:blocks:HealthCheck] :: HealthCheck -> !BlockHealth
+ Haskoin.Store.Data: [$sel:buy:BinfoTicker] :: BinfoTicker -> !Double
+ Haskoin.Store.Data: [$sel:cashAddr:BinfoMultiAddr] :: BinfoMultiAddr -> !Bool
+ Haskoin.Store.Data: [$sel:change:BinfoAddrBalance] :: BinfoBalance -> !Word32
+ Haskoin.Store.Data: [$sel:change:XPubSummary] :: XPubSummary -> !Word32
+ Haskoin.Store.Data: [$sel:code:BinfoSymbol] :: BinfoSymbol -> !Text
+ Haskoin.Store.Data: [$sel:confirmations:BinfoUnspent] :: BinfoUnspent -> !Int32
+ Haskoin.Store.Data: [$sel:confirmed:Balance] :: Balance -> !Word64
+ Haskoin.Store.Data: [$sel:confirmed:XPubSummary] :: XPubSummary -> !Word64
+ Haskoin.Store.Data: [$sel:connected:BinfoInfo] :: BinfoInfo -> !Word32
+ Haskoin.Store.Data: [$sel:conversion:BinfoInfo] :: BinfoInfo -> !Double
+ Haskoin.Store.Data: [$sel:conversion:BinfoSymbol] :: BinfoSymbol -> !Double
+ Haskoin.Store.Data: [$sel:count:CountHealth] :: CountHealth -> !Int64
+ Haskoin.Store.Data: [$sel:count:MaxHealth] :: MaxHealth -> !Int64
+ Haskoin.Store.Data: [$sel:crypto:BinfoInfo] :: BinfoInfo -> !BinfoSymbol
+ Haskoin.Store.Data: [$sel:date:BinfoHistory] :: BinfoHistory -> !Text
+ Haskoin.Store.Data: [$sel:deleted:Transaction] :: Transaction -> !Bool
+ Haskoin.Store.Data: [$sel:deleted:TxData] :: TxData -> !Bool
+ Haskoin.Store.Data: [$sel:deriv:BinfoXPubPath] :: BinfoXPubPath -> !SoftPath
+ Haskoin.Store.Data: [$sel:deriv:XPubSpec] :: XPubSpec -> !DeriveType
+ Haskoin.Store.Data: [$sel:doubleSpend:BinfoTx] :: BinfoTx -> !Bool
+ Haskoin.Store.Data: [$sel:external:BinfoAddrBalance] :: BinfoBalance -> !Word32
+ Haskoin.Store.Data: [$sel:external:XPubSummary] :: XPubSummary -> !Word32
+ Haskoin.Store.Data: [$sel:fee:BinfoBlock] :: BinfoBlock -> !Word64
+ Haskoin.Store.Data: [$sel:fee:BinfoHistory] :: BinfoHistory -> !Double
+ Haskoin.Store.Data: [$sel:fee:BinfoTx] :: BinfoTx -> !Word64
+ Haskoin.Store.Data: [$sel:fee:BlockData] :: BlockData -> !Word64
+ Haskoin.Store.Data: [$sel:fee:Transaction] :: Transaction -> !Word64
+ Haskoin.Store.Data: [$sel:fiat:BinfoInfo] :: BinfoInfo -> !BinfoSymbol
+ Haskoin.Store.Data: [$sel:fifteen:BinfoTicker] :: BinfoTicker -> !Double
+ Haskoin.Store.Data: [$sel:filtered:BinfoWallet] :: BinfoWallet -> !Word64
+ Haskoin.Store.Data: [$sel:final:BinfoShortBal] :: BinfoShortBal -> !Word64
+ Haskoin.Store.Data: [$sel:get:BinfoBlockInfos] :: BinfoBlockInfos -> [BinfoBlockInfo]
+ Haskoin.Store.Data: [$sel:get:BinfoMempool] :: BinfoMempool -> [BinfoTx]
+ Haskoin.Store.Data: [$sel:get:GenericResult] :: GenericResult a -> a
+ Haskoin.Store.Data: [$sel:get:RawResultList] :: RawResultList a -> [a]
+ Haskoin.Store.Data: [$sel:get:RawResult] :: RawResult a -> a
+ Haskoin.Store.Data: [$sel:get:SerialList] :: SerialList a -> [a]
+ Haskoin.Store.Data: [$sel:hash:BinfoBlockInfo] :: BinfoBlockInfo -> !BlockHash
+ Haskoin.Store.Data: [$sel:hash:BinfoBlock] :: BinfoBlock -> !BlockHash
+ Haskoin.Store.Data: [$sel:hash:BinfoHeader] :: BinfoHeader -> !BlockHash
+ Haskoin.Store.Data: [$sel:head:BinfoInfo] :: BinfoInfo -> !BinfoBlockInfo
+ Haskoin.Store.Data: [$sel:header:BlockData] :: BlockData -> !BlockHeader
+ Haskoin.Store.Data: [$sel:headers:BlockHealth] :: BlockHealth -> !BlockHeight
+ Haskoin.Store.Data: [$sel:height:BinfoBlockInfo] :: BinfoBlockInfo -> !BlockHeight
+ Haskoin.Store.Data: [$sel:height:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:height:BinfoHeader] :: BinfoHeader -> !BlockHeight
+ Haskoin.Store.Data: [$sel:height:BlockData] :: BlockData -> !BlockHeight
+ Haskoin.Store.Data: [$sel:height:BlockRef] :: BlockRef -> !BlockHeight
+ Haskoin.Store.Data: [$sel:index:BinfoBlockInfo] :: BinfoBlockInfo -> !BlockHeight
+ Haskoin.Store.Data: [$sel:index:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:index:BinfoHeader] :: BinfoHeader -> !Word32
+ Haskoin.Store.Data: [$sel:index:BinfoTxInput] :: BinfoTxInput -> !Word32
+ Haskoin.Store.Data: [$sel:index:BinfoTxOutput] :: BinfoTxOutput -> !Word32
+ Haskoin.Store.Data: [$sel:index:BinfoTx] :: BinfoTx -> !BinfoTxId
+ Haskoin.Store.Data: [$sel:index:BinfoUnspent] :: BinfoUnspent -> !Word32
+ Haskoin.Store.Data: [$sel:index:Spender] :: Spender -> !Word32
+ Haskoin.Store.Data: [$sel:info:BinfoMultiAddr] :: BinfoMultiAddr -> !BinfoInfo
+ Haskoin.Store.Data: [$sel:input:BinfoSpender] :: BinfoSpender -> !Word32
+ Haskoin.Store.Data: [$sel:inputCount:BinfoTx] :: BinfoTx -> !Word32
+ Haskoin.Store.Data: [$sel:inputs:BinfoTx] :: BinfoTx -> ![BinfoTxInput]
+ Haskoin.Store.Data: [$sel:inputs:Transaction] :: Transaction -> ![StoreInput]
+ Haskoin.Store.Data: [$sel:key:BinfoXPubPath] :: BinfoXPubPath -> !XPubKey
+ Haskoin.Store.Data: [$sel:key:XPubSpec] :: XPubSpec -> !XPubKey
+ Haskoin.Store.Data: [$sel:last:BinfoTicker] :: BinfoTicker -> !Double
+ Haskoin.Store.Data: [$sel:lastBlock:HealthCheck] :: HealthCheck -> !TimeHealth
+ Haskoin.Store.Data: [$sel:lastTx:HealthCheck] :: HealthCheck -> !TimeHealth
+ Haskoin.Store.Data: [$sel:local:BinfoSymbol] :: BinfoSymbol -> !Bool
+ Haskoin.Store.Data: [$sel:locktime:BinfoTx] :: BinfoTx -> !Word32
+ Haskoin.Store.Data: [$sel:locktime:Transaction] :: Transaction -> !Word32
+ Haskoin.Store.Data: [$sel:main:BinfoBlock] :: BinfoBlock -> !Bool
+ Haskoin.Store.Data: [$sel:main:BlockData] :: BlockData -> !Bool
+ Haskoin.Store.Data: [$sel:max:BlockHealth] :: BlockHealth -> !Int32
+ Haskoin.Store.Data: [$sel:max:MaxHealth] :: MaxHealth -> !Int64
+ Haskoin.Store.Data: [$sel:max:TimeHealth] :: TimeHealth -> !Int64
+ Haskoin.Store.Data: [$sel:merkle:BinfoBlock] :: BinfoBlock -> !Hash256
+ Haskoin.Store.Data: [$sel:min:CountHealth] :: CountHealth -> !Int64
+ Haskoin.Store.Data: [$sel:name:BinfoSymbol] :: BinfoSymbol -> !Text
+ Haskoin.Store.Data: [$sel:network:HealthCheck] :: HealthCheck -> !String
+ Haskoin.Store.Data: [$sel:next:BinfoBlock] :: BinfoBlock -> ![BlockHash]
+ Haskoin.Store.Data: [$sel:nonce:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:ntx:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:ntx:BinfoRawAddr] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [$sel:ntx:BinfoShortBal] :: BinfoShortBal -> !Word64
+ Haskoin.Store.Data: [$sel:outpoint:StoreCoinbase] :: StoreInput -> !OutPoint
+ Haskoin.Store.Data: [$sel:outpoint:Unspent] :: Unspent -> !OutPoint
+ Haskoin.Store.Data: [$sel:output:BinfoTxInput] :: BinfoTxInput -> !BinfoTxOutput
+ Haskoin.Store.Data: [$sel:outputCount:BinfoTx] :: BinfoTx -> !Word32
+ Haskoin.Store.Data: [$sel:outputs:BinfoTx] :: BinfoTx -> ![BinfoTxOutput]
+ Haskoin.Store.Data: [$sel:outputs:BlockData] :: BlockData -> !Word64
+ Haskoin.Store.Data: [$sel:outputs:Transaction] :: Transaction -> ![StoreOutput]
+ Haskoin.Store.Data: [$sel:path:XPubBal] :: XPubBal -> ![KeyIndex]
+ Haskoin.Store.Data: [$sel:path:XPubUnspent] :: XPubUnspent -> ![KeyIndex]
+ Haskoin.Store.Data: [$sel:peers:HealthCheck] :: HealthCheck -> !CountHealth
+ Haskoin.Store.Data: [$sel:pendingTxs:HealthCheck] :: HealthCheck -> !MaxHealth
+ Haskoin.Store.Data: [$sel:pkscript:StoreCoinbase] :: StoreInput -> !ByteString
+ Haskoin.Store.Data: [$sel:position:BlockRef] :: BlockRef -> !Word32
+ Haskoin.Store.Data: [$sel:prev:BinfoBlock] :: BinfoBlock -> !BlockHash
+ Haskoin.Store.Data: [$sel:prevs:TxData] :: TxData -> !IntMap Prev
+ Haskoin.Store.Data: [$sel:price:BinfoRate] :: BinfoRate -> !Double
+ Haskoin.Store.Data: [$sel:rateThen:BinfoHistory] :: BinfoHistory -> !Double
+ Haskoin.Store.Data: [$sel:rbf:BinfoTx] :: BinfoTx -> !Bool
+ Haskoin.Store.Data: [$sel:rbf:Transaction] :: Transaction -> !Bool
+ Haskoin.Store.Data: [$sel:rbf:TxData] :: TxData -> !Bool
+ Haskoin.Store.Data: [$sel:received:Balance] :: Balance -> !Word64
+ Haskoin.Store.Data: [$sel:received:BinfoAddrBalance] :: BinfoBalance -> !Word64
+ Haskoin.Store.Data: [$sel:received:BinfoRawAddr] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [$sel:received:BinfoShortBal] :: BinfoShortBal -> !Word64
+ Haskoin.Store.Data: [$sel:received:BinfoWallet] :: BinfoWallet -> !Word64
+ Haskoin.Store.Data: [$sel:received:XPubSummary] :: XPubSummary -> !Word64
+ Haskoin.Store.Data: [$sel:recommendFee:BinfoMultiAddr] :: BinfoMultiAddr -> !Bool
+ Haskoin.Store.Data: [$sel:relay:PeerInfo] :: PeerInfo -> !Bool
+ Haskoin.Store.Data: [$sel:relayed:BinfoTx] :: BinfoTx -> !ByteString
+ Haskoin.Store.Data: [$sel:script:BinfoTxInput] :: BinfoTxInput -> !ByteString
+ Haskoin.Store.Data: [$sel:script:BinfoTxOutput] :: BinfoTxOutput -> !ByteString
+ Haskoin.Store.Data: [$sel:script:BinfoUnspent] :: BinfoUnspent -> !ByteString
+ Haskoin.Store.Data: [$sel:script:Prev] :: Prev -> !ByteString
+ Haskoin.Store.Data: [$sel:script:StoreCoinbase] :: StoreInput -> !ByteString
+ Haskoin.Store.Data: [$sel:script:StoreOutput] :: StoreOutput -> !ByteString
+ Haskoin.Store.Data: [$sel:script:Unspent] :: Unspent -> !ByteString
+ Haskoin.Store.Data: [$sel:sell:BinfoTicker] :: BinfoTicker -> !Double
+ Haskoin.Store.Data: [$sel:sent:BinfoAddrBalance] :: BinfoBalance -> !Word64
+ Haskoin.Store.Data: [$sel:sent:BinfoRawAddr] :: BinfoRawAddr -> !Int64
+ Haskoin.Store.Data: [$sel:sent:BinfoWallet] :: BinfoWallet -> !Word64
+ Haskoin.Store.Data: [$sel:sequence:BinfoTxInput] :: BinfoTxInput -> !Word32
+ Haskoin.Store.Data: [$sel:sequence:StoreCoinbase] :: StoreInput -> !Word32
+ Haskoin.Store.Data: [$sel:services:PeerInfo] :: PeerInfo -> !Word64
+ Haskoin.Store.Data: [$sel:size:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:size:BinfoTx] :: BinfoTx -> !Word32
+ Haskoin.Store.Data: [$sel:size:BlockData] :: BlockData -> !Word32
+ Haskoin.Store.Data: [$sel:size:Transaction] :: Transaction -> !Word32
+ Haskoin.Store.Data: [$sel:spender:StoreOutput] :: StoreOutput -> !Maybe Spender
+ Haskoin.Store.Data: [$sel:spenders:BinfoTxOutput] :: BinfoTxOutput -> ![BinfoSpender]
+ Haskoin.Store.Data: [$sel:spenders:TxData] :: TxData -> !IntMap Spender
+ Haskoin.Store.Data: [$sel:spent:BinfoTxOutput] :: BinfoTxOutput -> !Bool
+ Haskoin.Store.Data: [$sel:subsidy:BlockData] :: BlockData -> !Word64
+ Haskoin.Store.Data: [$sel:symbol:BinfoSymbol] :: BinfoSymbol -> !Text
+ Haskoin.Store.Data: [$sel:symbol:BinfoTicker] :: BinfoTicker -> !Text
+ Haskoin.Store.Data: [$sel:time:BinfoHistory] :: BinfoHistory -> !Text
+ Haskoin.Store.Data: [$sel:time:HealthCheck] :: HealthCheck -> !Word64
+ Haskoin.Store.Data: [$sel:timestamp:BinfoBlockInfo] :: BinfoBlockInfo -> !Word32
+ Haskoin.Store.Data: [$sel:timestamp:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:timestamp:BinfoHeader] :: BinfoHeader -> !Timestamp
+ Haskoin.Store.Data: [$sel:timestamp:BinfoRate] :: BinfoRate -> !Word64
+ Haskoin.Store.Data: [$sel:timestamp:BinfoTx] :: BinfoTx -> !Word64
+ Haskoin.Store.Data: [$sel:timestamp:BlockRef] :: BlockRef -> !UnixTime
+ Haskoin.Store.Data: [$sel:timestamp:Transaction] :: Transaction -> !Word64
+ Haskoin.Store.Data: [$sel:timestamp:TxData] :: TxData -> !Word64
+ Haskoin.Store.Data: [$sel:tx:TxData] :: TxData -> !Tx
+ Haskoin.Store.Data: [$sel:txid:BinfoHistory] :: BinfoHistory -> !TxHash
+ Haskoin.Store.Data: [$sel:txid:BinfoTx] :: BinfoTx -> !TxHash
+ Haskoin.Store.Data: [$sel:txid:BinfoUnspent] :: BinfoUnspent -> !TxHash
+ Haskoin.Store.Data: [$sel:txid:Spender] :: Spender -> !TxHash
+ Haskoin.Store.Data: [$sel:txid:Transaction] :: Transaction -> !TxHash
+ Haskoin.Store.Data: [$sel:txid:TxRef] :: TxRef -> !TxHash
+ Haskoin.Store.Data: [$sel:txids:BinfoHeader] :: BinfoHeader -> ![BinfoTxId]
+ Haskoin.Store.Data: [$sel:txidx:BinfoSpender] :: BinfoSpender -> !BinfoTxId
+ Haskoin.Store.Data: [$sel:txidx:BinfoTxOutput] :: BinfoTxOutput -> !BinfoTxId
+ Haskoin.Store.Data: [$sel:txidx:BinfoUnspent] :: BinfoUnspent -> !BinfoTxId
+ Haskoin.Store.Data: [$sel:txs:Balance] :: Balance -> !Word64
+ Haskoin.Store.Data: [$sel:txs:BinfoAddrBalance] :: BinfoBalance -> !Word64
+ Haskoin.Store.Data: [$sel:txs:BinfoBlock] :: BinfoBlock -> ![BinfoTx]
+ Haskoin.Store.Data: [$sel:txs:BinfoMultiAddr] :: BinfoMultiAddr -> ![BinfoTx]
+ Haskoin.Store.Data: [$sel:txs:BinfoRawAddr] :: BinfoRawAddr -> ![BinfoTx]
+ Haskoin.Store.Data: [$sel:txs:BinfoWallet] :: BinfoWallet -> !Word64
+ Haskoin.Store.Data: [$sel:txs:BlockData] :: BlockData -> ![TxHash]
+ Haskoin.Store.Data: [$sel:typ:BinfoHistory] :: BinfoHistory -> !Text
+ Haskoin.Store.Data: [$sel:typ:BinfoTxOutput] :: BinfoTxOutput -> !Int
+ Haskoin.Store.Data: [$sel:unconfirmed:Balance] :: Balance -> !Word64
+ Haskoin.Store.Data: [$sel:unconfirmed:XPubSummary] :: XPubSummary -> !Word64
+ Haskoin.Store.Data: [$sel:unspent:XPubUnspent] :: XPubUnspent -> !Unspent
+ Haskoin.Store.Data: [$sel:userAgent:PeerInfo] :: PeerInfo -> !ByteString
+ Haskoin.Store.Data: [$sel:utxo:Balance] :: Balance -> !Word64
+ Haskoin.Store.Data: [$sel:utxo:BinfoRawAddr] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [$sel:utxo:XPubSummary] :: XPubSummary -> !Word64
+ Haskoin.Store.Data: [$sel:value:BinfoTxOutput] :: BinfoTxOutput -> !Word64
+ Haskoin.Store.Data: [$sel:value:BinfoUnspent] :: BinfoUnspent -> !Word64
+ Haskoin.Store.Data: [$sel:value:Prev] :: Prev -> !Word64
+ Haskoin.Store.Data: [$sel:value:StoreCoinbase] :: StoreInput -> !Word64
+ Haskoin.Store.Data: [$sel:value:StoreOutput] :: StoreOutput -> !Word64
+ Haskoin.Store.Data: [$sel:value:Unspent] :: Unspent -> !Word64
+ Haskoin.Store.Data: [$sel:valueNow:BinfoHistory] :: BinfoHistory -> !Double
+ Haskoin.Store.Data: [$sel:valueThen:BinfoHistory] :: BinfoHistory -> !Double
+ Haskoin.Store.Data: [$sel:version:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:version:BinfoTx] :: BinfoTx -> !Word32
+ Haskoin.Store.Data: [$sel:version:HealthCheck] :: HealthCheck -> !String
+ Haskoin.Store.Data: [$sel:version:PeerInfo] :: PeerInfo -> !Word32
+ Haskoin.Store.Data: [$sel:version:Transaction] :: Transaction -> !Word32
+ Haskoin.Store.Data: [$sel:vol24:BinfoRate] :: BinfoRate -> !Double
+ Haskoin.Store.Data: [$sel:wallet:BinfoMultiAddr] :: BinfoMultiAddr -> !BinfoWallet
+ Haskoin.Store.Data: [$sel:weight:BinfoBlock] :: BinfoBlock -> !Word32
+ Haskoin.Store.Data: [$sel:weight:BinfoTx] :: BinfoTx -> !Word32
+ Haskoin.Store.Data: [$sel:weight:BlockData] :: BlockData -> !Word32
+ Haskoin.Store.Data: [$sel:weight:Transaction] :: Transaction -> !Word32
+ Haskoin.Store.Data: [$sel:witness:BinfoTxInput] :: BinfoTxInput -> !ByteString
+ Haskoin.Store.Data: [$sel:witness:StoreCoinbase] :: StoreInput -> !WitnessStack
+ Haskoin.Store.Data: [$sel:work:BlockData] :: BlockData -> !Integer
+ Haskoin.Store.Data: [$sel:xpub:BinfoAddrBalance] :: BinfoBalance -> !XPubKey
+ Haskoin.Store.Data: [$sel:xpub:BinfoTxOutput] :: BinfoTxOutput -> !Maybe BinfoXPubPath
+ Haskoin.Store.Data: [$sel:xpub:BinfoUnspent] :: BinfoUnspent -> !Maybe BinfoXPubPath
+ Haskoin.Store.Data: data PeerInfo
+ Haskoin.Store.Data: instance Control.DeepSeq.NFData Haskoin.Store.Data.BinfoBlockInfos
+ Haskoin.Store.Data: instance Control.DeepSeq.NFData Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Store.Data.BinfoBlockInfos
+ Haskoin.Store.Data: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Store.Data.BinfoBlockInfos
+ Haskoin.Store.Data: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance Data.Binary.Class.Binary Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance Data.Bytes.Serial.Serial Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance Data.Serialize.Serialize Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance GHC.Classes.Eq Haskoin.Store.Data.BinfoBlockInfos
+ Haskoin.Store.Data: instance GHC.Classes.Eq Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance GHC.Classes.Ord Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance GHC.Generics.Generic Haskoin.Store.Data.BinfoBlockInfos
+ Haskoin.Store.Data: instance GHC.Generics.Generic Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance GHC.Show.Show Haskoin.Store.Data.BinfoBlockInfos
+ Haskoin.Store.Data: instance GHC.Show.Show Haskoin.Store.Data.PeerInfo
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoBalance
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoBlock
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoMempool
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoMultiAddr
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoRawAddr
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoTx
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoTxInput
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoTxOutput
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoUnspent
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoUnspents
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) Haskoin.Store.Data.BinfoXPubPath
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) [Haskoin.Store.Data.BinfoBalance]
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) [Haskoin.Store.Data.BinfoBlock]
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Ctx) [Haskoin.Store.Data.BinfoTx]
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.Balance
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.BlockData
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.StoreInput
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.StoreOutput
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.Transaction
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.Unspent
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.XPubBal
+ Haskoin.Store.Data: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Store.Data.XPubUnspent
+ Haskoin.Store.Data: newtype BinfoBlockInfos
+ Haskoin.Store.WebClient: [$sel:get:HeightParam] :: HeightParam -> Natural
+ Haskoin.Store.WebClient: [$sel:get:HeightsParam] :: HeightsParam -> [Natural]
+ Haskoin.Store.WebClient: [$sel:get:LimitParam] :: LimitParam -> Natural
+ Haskoin.Store.WebClient: [$sel:get:NoCache] :: NoCache -> Bool
+ Haskoin.Store.WebClient: [$sel:get:NoTx] :: NoTx -> Bool
+ Haskoin.Store.WebClient: [$sel:get:OffsetParam] :: OffsetParam -> Natural
+ Haskoin.Store.WebClient: [$sel:hash:StartParamHash] :: StartParam -> Hash256
+ Haskoin.Store.WebClient: [$sel:height:StartParamHash] :: StartParam -> Natural
+ Haskoin.Store.WebClient: [$sel:host:ApiConfig] :: ApiConfig -> !String
+ Haskoin.Store.WebClient: [$sel:limit:LimitsParam] :: LimitsParam -> Maybe LimitParam
+ Haskoin.Store.WebClient: [$sel:net:ApiConfig] :: ApiConfig -> !Network
+ Haskoin.Store.WebClient: [$sel:offset:LimitsParam] :: LimitsParam -> OffsetParam
+ Haskoin.Store.WebClient: [$sel:start:LimitsParam] :: LimitsParam -> Maybe StartParam
+ Haskoin.Store.WebClient: [$sel:time:StartParamHash] :: StartParam -> UnixTime
+ Haskoin.Store.WebCommon: [$sel:get:HeightParam] :: HeightParam -> Natural
+ Haskoin.Store.WebCommon: [$sel:get:HeightsParam] :: HeightsParam -> [Natural]
+ Haskoin.Store.WebCommon: [$sel:get:LimitParam] :: LimitParam -> Natural
+ Haskoin.Store.WebCommon: [$sel:get:NoCache] :: NoCache -> Bool
+ Haskoin.Store.WebCommon: [$sel:get:NoTx] :: NoTx -> Bool
+ Haskoin.Store.WebCommon: [$sel:get:OffsetParam] :: OffsetParam -> Natural
+ Haskoin.Store.WebCommon: [$sel:get:TimeParam] :: TimeParam -> UnixTime
+ Haskoin.Store.WebCommon: [$sel:hash:StartParamHash] :: StartParam -> Hash256
+ Haskoin.Store.WebCommon: [$sel:height:StartParamHash] :: StartParam -> Natural
+ Haskoin.Store.WebCommon: [$sel:limit:LimitsParam] :: LimitsParam -> Maybe LimitParam
+ Haskoin.Store.WebCommon: [$sel:offset:LimitsParam] :: LimitsParam -> OffsetParam
+ Haskoin.Store.WebCommon: [$sel:start:LimitsParam] :: LimitsParam -> Maybe StartParam
+ Haskoin.Store.WebCommon: [$sel:time:StartParamHash] :: StartParam -> UnixTime
+ Haskoin.Store.WebCommon: instance Haskoin.Store.WebCommon.ApiResource Haskoin.Store.WebCommon.GetPeers (Haskoin.Store.Data.SerialList Haskoin.Store.Data.PeerInfo)
+ Haskoin.Store.WebCommon: instance Haskoin.Store.WebCommon.Param Haskoin.Crypto.Keys.Extended.XPubKey
- Haskoin.Store.Data: BinfoTx :: !TxHash -> !Word32 -> !Word32 -> !Word32 -> !Word32 -> !Word32 -> !Word64 -> !ByteString -> !Word32 -> !BinfoTxId -> !Bool -> !Bool -> !Maybe (Int64, Int64) -> !Word64 -> !Maybe Word32 -> !Maybe Word32 -> [BinfoTxInput] -> [BinfoTxOutput] -> BinfoTx
+ Haskoin.Store.Data: BinfoTx :: !TxHash -> !Word32 -> !Word32 -> !Word32 -> !Word32 -> !Word32 -> !Word64 -> !ByteString -> !Word32 -> !BinfoTxId -> !Bool -> !Bool -> !Maybe (Int64, Int64) -> !Word64 -> !Maybe Word32 -> !Maybe Word32 -> ![BinfoTxInput] -> ![BinfoTxOutput] -> BinfoTx
- Haskoin.Store.Data: HealthCheck :: !BlockHealth -> !TimeHealth -> !TimeHealth -> !MaxHealth -> !CountHealth -> !String -> !String -> HealthCheck
+ Haskoin.Store.Data: HealthCheck :: !BlockHealth -> !TimeHealth -> !TimeHealth -> !MaxHealth -> !CountHealth -> !String -> !String -> !Word64 -> HealthCheck
- Haskoin.Store.Data: parseBinfoAddr :: Network -> Text -> Maybe [BinfoAddr]
+ Haskoin.Store.Data: parseBinfoAddr :: Network -> Ctx -> Text -> Maybe [BinfoAddr]
- Haskoin.Store.Data: toTransaction :: TxData -> Transaction
+ Haskoin.Store.Data: toTransaction :: Ctx -> TxData -> Transaction
- Haskoin.Store.WebClient: apiBatch :: (Batchable a b, MonadIO m, MonadError Except m) => Natural -> ApiConfig -> a -> m b
+ Haskoin.Store.WebClient: apiBatch :: (Batchable a b, MonadIO m, MonadError Except m) => Ctx -> Natural -> ApiConfig -> a -> m b
- Haskoin.Store.WebClient: apiCall :: (ApiResource a b, MonadIO m, MonadError Except m) => ApiConfig -> a -> m b
+ Haskoin.Store.WebClient: apiCall :: (ApiResource a b, MonadIO m, MonadError Except m) => Ctx -> ApiConfig -> a -> m b
- Haskoin.Store.WebCommon: class Serial b => ApiResource a b | a -> b
+ Haskoin.Store.WebCommon: class (Serial b) => ApiResource a b | a -> b
- Haskoin.Store.WebCommon: encodeParam :: Param a => Network -> a -> Maybe [Text]
+ Haskoin.Store.WebCommon: encodeParam :: Param a => Network -> Ctx -> a -> Maybe [Text]
- Haskoin.Store.WebCommon: parseParam :: Param a => Network -> [Text] -> Maybe a
+ Haskoin.Store.WebCommon: parseParam :: Param a => Network -> Ctx -> [Text] -> Maybe a
- Haskoin.Store.WebCommon: queryPath :: ApiResource a b => Network -> a -> Text
+ Haskoin.Store.WebCommon: queryPath :: ApiResource a b => Network -> Ctx -> a -> Text
Files
- CHANGELOG.md +1758/−0
- README.md +57/−0
- haskoin-store-data.cabal +11/−6
- src/Haskoin/Store/Data.hs +3474/−3621
- src/Haskoin/Store/WebClient.hs +50/−32
- src/Haskoin/Store/WebCommon.hs +80/−96
- test/Haskoin/Store/DataSpec.hs +279/−267
- test/Haskoin/Store/WebCommonSpec.hs +15/−14
+ CHANGELOG.md view
@@ -0,0 +1,1758 @@+# Changelog+All notable changes to this project will be documented in this file.++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)++## [1.0.0] - 2023-07-28++### Changed++- Compatibility with latest haskoin-core and haskoin-node packages.+- Use DuplicateRecordFields and OverloadedRecordDot language extensions.+- Modernize package in a backwards-incompatible manner.+- Refactor package extensively.++## [0.65.10] - 2023-05-12++### Changed++- Revert to older LTS Haskell to attempt to resolve memory leak.++### Removed++- Remove unnecessary lock release.++## [0.65.9] - 2023-05-09++### Changed++- Be more aggressive caching individual transactions.+- Introduce a periodic cache mempool sync task.++## [0.65.8] - 2023-05-08++### Changed++- Perform health check in separate thread.++## [0.65.7] - 2023-05-05++### Changed++- Do not sync mempool to Redis if node is not up-to-date.++## [0.65.6] - 2023-05-04++### Changed++- Improve caching ingestion performance.++## [0.65.5] - 2022-09-19++### Added++- Fee field added to Blockchain.info-style export-history endpoint.++### Changed++- Bump version due to Git mistake.++## [0.65.3] - 2022-04-13++### Added++- IPv6 peer support.++## [0.65.2] - 2022-04-13++### Fixed++- Correct lazy bytestring deserialization algorithm for web client.++## [0.65.1] - 2022-04-13++### Changed++- Simplify function in Cache module.+- Bump depenedencies.++## [0.65.0] - 2022-02-03++### Changed++- Spenders now embedded into tx objects in database for performance.+- Use faster IORef instead of slow TVar when ingesting.+- Use mutable hash tables when ingesting.+- Allow to disable unscalable API endpoints.++## [0.64.19] - 2022-02-01++### Added++- Allow to set query timeouts.++## [0.64.18] - 2022-01-21++### Fixed++- Off-by-one error when importing change addresses to cache.++## [0.64.17] - 2022-01-21++### Fixed++- Add change addresses to cache when necessary.++## [0.64.16] - 2022-01-10++### Fixed++- Correct fee calculation for blocks that do not claim all rewards.++## [0.64.15] - 2022-01-07++### Fixed++- Correct scale of cache pruning code.++## [0.64.14] - 2022-01-07++### Fixed++- Always prune to 80% of max cache size.++## [0.64.13] - 2022-01-07++### Fixed++- Avoid doing a hissy fit on transient cache issue.++## [0.64.12] - 2022-01-07++### Fixed++- Prune a little more cache to allow for new keys.++## [0.64.11] - 2022-01-06++### Fixed++- Prune cache more often.++## [0.64.10] - 2021-12-30++### Changed++- Return empty addresses in xpub balances call.++## [0.64.9] - 2021-12-29++### Fixed++- Avoid notifying same transactions in mempool.++## [0.64.8] - 2021-12-26++### Changed++- Use better Fourmolu format.+- Be more precise when counting retrieved items.++### Fixed++- Avoid transactions duplicated in xpub data.+- Refactor some functions to make them more robust.++## [0.64.7] - 2021-12-26++### Fixed++- Optimisations for various Blockchain endpoints.+- More accurate item counts for various web endpoints.++## [0.64.6] - 2021-12-25++### Fixed++- Record cache index time just once per xpub.++## [0.64.5] - 2021-12-25++### Changed++- Add suffix cached to cache entries to avoid conflicts with data entries.++## [0.64.4] - 2021-12-25++### Fixed+- Bring dots back and avoid stats hacks.++## [0.64.3] - 2021-12-25++### Fixed++- Fix typo.++## [0.64.2] - 2021-12-25++### Changed++- Replace dots for underscores in metrics.++## [0.64.1] - 2021-12-25++### Changed++- Automatic formatting.+- Improve cache debug logging.++### Fixed++- Add item count to 'all' query metrics.++## [0.64.0] - 2021-12-25++### Changed++- Improve metrics.++### Fixed++- Get lock before importing transactions to cache.++## 0.63.0+### Fixed+- Make cache updates lightweight.++## 0.62.1+### Fixed+- Fix idempotent transaction elimination bug.++## 0.62.0+### Added+- Configurable Blockchain price URLs.+- Reuse HTTP connections with wreq sessions.++## 0.61.1+### Changed+- Upstream improvements on Base58 performance.++## 0.61.0+### Fixed+- Fixed recursion bug in Blockchain format conversion function.++## 0.60.0+### Fixed+- Correct notifications subsystem.++## 0.59.0+### Fixed+- Correct internal dependency.++## 0.58.0+### Fixed+- Correct output freeing algorithm.++## 0.57.0+### Changed+- Report errors freeing outputs.++## 0.56.0+### Changed+- Report all deleted mempool transactions.+- Crash on certain data corruption.++## 0.55.0+### Changed+- Always have a previous output object in Blockchain inputs.++## 0.54.0+### Added+- WebSocket support.+- Testnet4 support.++## 0.53.11+### Changed+- Dummy version increase to signal upstream update of Haskoin Core.++## 0.53.10+### Fixed+- Correct test that was reversed in previous version.++## 0.53.9+### Removed+- Mempool is no longer synced by default to fix public Bitcoin Core regression.++## 0.53.8+### Changed+- Put derivations stat inside database.++## 0.53.7+### Added+- Added counters for database retrievals.+- Added counter for xpub derivations.++### Changed+- Removed some buggy or unnecessary stats.++## 0.53.6+### Changed+- Improve web server statistics.++## 0.53.5+### Addded+- Endpoint to delete an xpub from cache.++## 0.53.4+### Added+- Debugging when reverting block.++### Fixed+- Negative (headers - blocks) diff shown incorrectly.+- Infinite loop when removing a block's coinbase transaction.++## 0.53.3+### Added+- Blockchain.info endpoint: `q/pubkeyhash/:addr`.+- Blockchain.info endpoint: `q/getsentbyaddress/:addr`.+- Blockchain.info endpoint: `q/pubkeyaddr/:addr`.+- Configurable request body limit size.++## 0.53.2+### Fixed+- Return output in satoshi for some endpoints.++## 0.53.1+### Fixed+- Return 404 in case of bad address.++## 0.53.0+### Added+- Blockchain.info endpoint: `q/addresstohash/:addr`.+- Blockchain.info endpoint: `q/addrpubkey/:pubkey`.+- Blockchain.info endpoint: `q/hashpubkey/:pubkey`.+- Blockchain.info endpoint: `q/getblockcount`.+- Blockchain.info endpoint: `q/latesthash`.+- Blockchain.info endpoint: `q/bcperblock`.+- Blockchain.info endpoint: `q/txtotalbtcoutput/:txid`.+- Blockchain.info endpoint: `q/txtotalbtcinput/:txid`.+- Blockchain.info endpoint: `q/txfee/:txid`.+- Blockchain.info endpoint: `q/txresult/:txhash/:addr`.+- Blockchain.info endpoint: `q/getreceivedbyaddress/:addr`.+- Blockchain.info endpoint: `q/addressbalance/:addr`.+- Blockchain.info endpoint: `q/addressfirstseen/:addr`.+- Support xpubs in `blockchain/rawaddr` endpoint.++### Fixed+- Various latest blocks endpoint fixes.+- Allow empty parameters in Blockchain.info POST requests.++## 0.52.13+### Fixed+- Upstream fixes for unknown inv types.++## 0.52.12+### Fixed+- Fix limits for legacy endpoints.++## 0.52.11+### Added+- Alias for Blockchain.info raw address endpoint.++### Fixed+- Fix unknown message types without payload upstream.+- Randomise peer timeouts upstream.++## 0.52.10+### Fixed+- Fix binary search algorithm for blocks upstream.++## 0.52.9+### Added+- Add Blockchain.info latest block endpoint.+- Add Blockchain.info unconfirmed-transactions endpoint.+- Add Blockchain.info blocks for one day endpoint.++## 0.52.8+### Fixed+- Fix special case for zero limit.++## 0.52.7+### Fixed+- Fix cache bug when too many transactions in mempool.++## 0.52.6+### Changed+- Use rationals for Blockchain.info export-history endpoint arithmetics.++## 0.52.5+### Added+- Support for Blockchain.info export-history endpoint.++### Fixed+- Use strict bytestring decoder for HTTP API client.++## 0.52.4+### Fixed+- Use Base58 addresses by default in Blockchain.info balance querios.++## 0.52.3+### Fixed+- Errors should also contain CORS headers.++## 0.52.2+### Fixed+- Blockchain.info raw address limit should use `limit` parametre and not `n`.++## 0.52.1+### Changed+- Stream multiple transactions when requested.++### Fixed+- Fix missing unspent outputs.++## 0.52.0+### Changed+- Remove unnecessary performance optimisations.++### Fixed+- Reduce balance retrievals for xpubs.+- Hack UTXO ordering to avoid unwanted behaviours.+- Do not merge conduits when there are too many.++## 0.51.0+### Fixed+- Data type for RequestTooLarge was missing.++## 0.50.3+### Added+- Log HTTP request bodies.++### Removed+- Remove web request timeout.++### Fixed+- Do not ignore unknown start tx anchors in conduits.+- Use fast transaction counting in cache.++### Changed+- Use WAI middlewares to manage stats.++## 0.50.2+### Fixed+- Do not use lazy parsers for incoming data that may throw exceptions.++## 0.50.1+### Fixed+- Do not allow incoming POST requests of unlimited body size.++## 0.50.0+### Added+- Limit number of simultaneous xpubs being cached.++## 0.49.0+### Changed+- Improve conduit merging algorithm.++## 0.48.0+### Added+- Timeouts and token bucket for web requests.+- Configurable price fetch interval.++## 0.47.6+### Fixed+- Fix web.all stat.++## 0.47.5+### Changed+- Simplify stats.++## 0.47.4+### Added+- Implement Blockchain.info block height.+- Implement Blockchain.info raw address.+- Implement Blockchain.info balances.++### Fixed+- Make Blockchain.info unspent endpoint more efficient.+- Make Blockchain.info xpubs endpoint more efficient.++### Changed+- Use compact JSON serialization by default everywhere.++## 0.47.3+### Fixed+- Fix serialization bug with unspents.++## 0.47.2+### Fixed+- Fix serialization bug for health check.++## 0.47.1+### Fixed+- Fix serialization bugs for web data types.++## 0.47.0+### Added+- Support for legacy block endpoints.++### Changed+- Serialization now uses bytes library and does interleaved IO.++## 0.46.6+### Added+- Allow comma-separated values in multiaddr.++## 0.46.5+### Changed+- Throw error when offset exceeded instead of truncating it.++## 0.46.4+### Fixed+- Do not compute balances separately for loose addresses when part of an xpub.++### Changed+- Do not fetch entire transaction list for loose addresses.+- Transaction count in wallet object is just a synonym for filtered tx count.+- Use conduits to prevent over-fetching transactions from database.++## 0.46.3+### Fixed+- Make hex value encoding compatible with Java BigInteger decoder.++## 0.46.2+### Changed+- Allow negative confirmations for compatibility with old API.++## 0.46.1+### Added+- Filters for transactions in multiaddr endpoint.++## 0.46.0+### Fixed+- Release to fix unintentional upload.++## 0.45.0+### Added+- Using a tx_index that results in a txid conflict returns a 409 - Conflict.++## 0.44.0+### Changed+- Numeric txid now 53 bits long and doesn't return transactions when hashes collide.++## 0.43.1+### Fixed+- Do not overcount statistics.++## 0.43.0+### Changed+- Transaction index now simply encodes txid as a long number (sorry JS users).++## 0.42.4+### Changed+- Optimise encoding of JSON data for Blockchain.info API.++## 0.42.3+### Changed+- Narrow down the balance for transactions to onlyShow set.++## 0.42.2+### Changed+- Make unspent algorithm more efficient.++## 0.42.1+### Added+- Support for Blockchain.info API unspent endpoint.++### Fixed+- Fix RBF transactions on Blockchain.info API.++## 0.42.0+### Removed+- No more pruning of old mempool transactions.++## 0.41.4+### Fixed+- Correct error computing wallet balances.++## 0.41.3+### Changed+- Use whole active set when computing results for multiaddr.+- Compute correct count of transactions for wallet.++## 0.41.2+### Changed+- Caching mempool is not much slower when pruning, so reversing that change.++## 0.41.1+### Changed+- Only prune mempool every ten minutes or so.++## 0.41.0+### Removed+- No more cache eviction endpoint.++## 0.40.22+### Added+- More metrics for cache.++## 0.40.21+### Added+- Support HTTP GET for multiaddr endpoint.++## 0.40.20+### Added+- Prefix 'app.' in statsd when using Nomad variables.++## 0.40.19+### Added+- Statsd support and pleny of statistics.++### Removed+- Removed use of sync lock for importing xpubs in cache due to volume.++## 0.40.18+### Added+- New options to control cache lock retry count and delay.++### Changed+- Cache xpubs asyncrhonously.+- Mempool syncs fully every refresh cycle.++## 0.40.17+### Changed+- Delete more than 1000 xpubs from Redis if needed.+- Attempt to get lock ten times in order to index a new xpub.++## 0.40.16+### Fixed+- Fix best block not set.++## 0.40.15+### Fixed+- Do not update cache best block if it is higher.++## 0.40.14+### Fixed+- Always respond to cache pings to avoid getting stuck.++## 0.40.13+### Added+- Be more careful about importing blocks to the cache.++### Removed+- Reduce excessive cache logging.++## 0.40.12+### Fixed+- Correct bad arithmetics in cache cooldown.++## 0.40.11+### Added+- Debug log cache cooldown.++### Fixed+- Unlimited retry lock acquisition now works.+- Make locking tighter++## 0.40.10+### Changed+- Reduce the number of things that cache do when pinged.++## 0.40.9+### Fixed+- Avoid filling the cache actor mailbox with pings.++## 0.40.8+### Fixed+- Correct a typo in cache code.++## 0.40.7+### Changed+- Improve cache mempool sync algorithm.++## 0.40.6+### Changed+- Make cache cooldown a number.++## 0.40.5+### Added+- Log every non-successful query.++## 0.40.4+### Fixed+- Use Redis TTL for cache cooldown.++## 0.40.3+### Fixed+- Mempool no longer gets saved in cache when node is not in sync.++## 0.40.2+### Changed+- Change source of price data to https://blockchain.info/ticker.++## 0.40.1+### Removed+- Do not expose Paths_haskoin_store module explicitly.++## 0.40.0+### Added+- Blockchain.info API compatibility for multiaddr and rawtx endpoints.++### Changed+- Segwit fields are no longer omitted on incompatible networks.++## 0.39.0+### Changed+- Upgrade Haskoin Core to support higher version on witness addresses.++## 0.38.4+### Fixed+- Fix block search by timestamp.++## 0.38.3+### Changed+- Disable low latency garbage collector by default.++## 0.38.2+### Changed+- Use GHC 8.10 and its low latency garbage collector.++## 0.38.1+### Changed+- Optimise algorithm for fork safety check.++## 0.38.0+### Added+- Support for Bitcoin Cash hard fork.++## 0.37.5+### Added+- Configurable cache refresh time.++## 0.37.4+### Fixed+- Now request mempool when connecting even if already in sync.++### Changed+- Add a delay to mempool sync to allow for block headers to catch up.+- Attempt to sync mempool whenever syncing is done or a new peer connects.++## 0.37.3+### Changed+- Correct logic error when deleting superseded transactions.++## 0.37.2+### Changed+- Simplify multiple algorithms to hopefully solve corruption bug.+- Allow to disable mempool transaction indexing via command line.++## 0.37.1+### Changed+- Better locking around caching operations.+- Have a global cooldown on cache mempool updates.++### Fixed+- Fix address unspent retrieval using correct column family.++## 0.37.0+### Added+- Add support for column families and prefixes breaking database backwards compatibility.++## 0.36.2+### Fixed+- Fix another place where mempool sorting was wrong.++## 0.36.1+### Fixed+- Fix wrong sorting of mempool transactions in database.++## 0.36.0+### Changed+- Do not use STM transactions because it is impossible to reason about preloading.+- Make importing blocks after mempool is populated faster.++## 0.35.2+### Changed+- Reduce frequency of cache mempool updates.++## 0.35.1+### Changed+- Remove cache lock retries.+- Do not use lock when caching a new xpub.++## 0.35.0+### Changed+- Use new RocksDB Haskell bindings.+- Make headers - blocks diff configurable in health check.+- Simplify locking in cache.++## 0.34.8+### Changed+- Minor refactoring.+- Only send one mempool message ever.++## 0.34.7+### Changed+- Fix block heights endpoint URL.++## 0.34.6+### Removed+- Remove asynchronous database retrieval permanently due to instability.++## 0.34.5+### Added+- Preload memory before freeing outputs.++## 0.34.4+### Added+- Asynchronous preloading only for blocks.++## 0.34.3+### Removed+- Disable asynchronous preloading to prevent segmentation fault error.++## 0.34.2+### Removed+- No more upper limit for mempool call.++### Fixed+- Health check uses minimum between last block and mempool tx.++## 0.34.1+### Added+- Pending mempool transactions health check.++### Removed+- No more upper limit for mempool call.++## 0.34.0+### Changed+- New format for health check.++## 0.33.1+### Changed+- Upload with updated Cabal files.++## 0.33.0+### Fixed+- Fix outputs showing up as both spent and unspent.++### Changed+- Simplify data structures used to hold uncommitted changes.+- Split StoreRead class in two.+- Improve pre-loading algorithms.++## 0.32.3+### Fixed+- Fix command line argument mappings to env vars.++## 0.32.2+### Fixed+- Do not complain when deleting non-existent unconfirmed txs.++## 0.32.1+### Fixed+- Fix corruption bug when deleting transactions during some reorgs.+- Fix tests on data package.+- Rename upstream address string functions.++## 0.32.0+### Changed+- Concurrently load dependencies when importing transaction data.+- Import each transaction using STM.+- Rework logging.+- Refactor all block chain importing algorithms.+- Use peer locking when importing data.+- Change license to MIT.++### Added+- Make max peer count configurable.++### Fixed+- Prevent premature peer timeouts when importing blocks.++## 0.31.0+### Added+- Add Haskell web client code to data library.+- Use type-safe definitions for web paths and parameters.+- Support JSON pretty encoding and enable by default for some endpoints.+- Allow environment variables for configuration.++### Changed+- Change command-line arguments to better fit GNU style.+- Refactor code to manipulate address balances.+- Improve logging.+- Rate-limit mempool importing.+- Do not add xpubs to cache if node not in sync.+- Do not import transactions to cache if no xpubs in cache.+- Refactor transaction importing logic extremely.+- Refactor memory database and writer.++### Fixed+- Fix BTC double-count transaction bug.+- Add missing xpub derive parameter to xpub evict documentation.++## 0.30.1+### Changed+- Limit mempool API endpoint list.++## 0.30.0+### Changed+- Raw JSON values are now wrapped in an object with a result property.+- Better logging when importing transactions into mempool.++### Fixed+- Transaction limit by block height works now.++## 0.29.3+### Changed+- Revert concurrent retrieval changes from 0.29.1 and 0.29.0 to solve stability+ issues.+- Bitcoin Cash (BCH) replaces BTC as default network in CLI.+- Peer discovery enabled automatically if no peers specified in CLI.+- Use separate Stack configuration file for data package.++## 0.29.2+### Changed+- Increase timeouts for manager and chain health checks.++## 0.29.1+### Added+- Perform xpub operations concurrently.++## 0.29.0+### Changed+- BlockTx renamed to TxRef.+- Remove unnecessary monad transformers to simplify import code.++### Added+- Retrieve balances and unspent outputs concurrently when importing.+- Coin instance for Unspent in data package.++## 0.28.0+### Changed+- Change the way that limits, start, and offset values are handled.++### Fixed+- Fix incorrect limit responses.++## 0.27.0+### Added+- Complete support for offset.++### Changed+- Improve monad stack for cache.++## 0.26.6+### Added+- Support for offset in all endpoints where it makes sense.++## 0.26.5+### Fixed+- Sort after using the nub function or use Data.List.nub in exceptional cases.++## 0.26.4+### Changed+- Don't do certain validity checks on confirmed transactions.++### Fixed+- Do not crash when syncing the blockchain from scratch on a populated cache.++## 0.26.3+### Changed+- Replace nub function everywhere with faster version written using hash sets.++### Fixed+- Fix shared cache locking.++## 0.26.2+### Changed+- Simplify cache block import code.+- Improve cache import performance.++## 0.26.1+### Fixed+- Do not call zadd or zrem in Redis with incorrect argument count.++### Changed+- Do not use slow Transaction type in Logic.+- Improve logging while importing blocks.+- Better transaction import algorithm.++## 0.26.0+### Changed+- Add information to some binary types for feature parity with JSON.++## 0.25.4+### Changed+- Hide segwit data on non-segwit networks.++### Fixed+- Fix work which now always serialises as a number literal.++## 0.25.3+### Changed+- Fix version display on health check.++## 0.25.2+### Changed+- Fix wrong version of Haskoin Store Data dependency.++## 0.25.1+### Changed+- Improve code organisation.+- Split data definitions and serialisation into its own package.++## 0.24.0+### Changed+- Use mocking to simulate peers instead of connecting to the network while testing.+- Depend on latest version of Haskoin Core for faster JSON serialisation.+- Clean up confusing JSON encoding/decoding codebase.++## 0.23.24+### Changed+- Implement faster JSON encoding using toEncoding from the Aeson library.++## 0.23.23+### Changed+- Do not read full mempool so often from cache code.++## 0.23.22+### Changed+- Depend on latest Haskoin Node that fixes debugging regression.++## 0.23.21+### Changed+- Reduce non-debug log verbosity.++## 0.23.20+### Changed+- Better Haskoin Node logging.++## 0.23.19+### Changed+- Use set peer timeout instead of constant in block store. ++## 0.23.18+### Changed+- Allow setting peer timeouts in command line.++### Fixed+- Bump Haskoin Node dependency to fix another premature timeout condition.++## 0.23.17+### Fixed+- Touch syncing peer whenever we process one of its blocks to avoid premature timeout.++## 0.23.16+### Changed+- Better algorithm to avoid importing transaction multiple times in cache.+- Depend on latest Haskoin Node.++## 0.23.15+### Changed+- Do not storm the cache with mempool transactions.++## 0.23.13+### Fixed+- Do not ignore deleted incoming transactions.++## 0.23.12+### Fixed+- Do not do RBF checks when replacing a mempool transaction with a confirmed one.++## 0.23.11+### Fixed+- Streamline mempool transaction importing.++## 0.23.10+### Fixed+- Fix transactions not recorded in cache mempool set.+- Fix transactions being downloaded multiple times.++### Removed+- Do not store orphan transactions in database.++### Changed+- Use sets for incoming transactions instead of lists.+- Do not do anything to the cache if there are no xpubs in it.++## 0.23.9+### Fixed+- Wiping mempool fixed.++## 0.23.8+### Added+- Chunk transactions to be deleted from mempool.++## 0.23.7+### Added+- Mempool improvements.++## 0.23.6+### Added+- Ability to wipe mempool at start.+- Improvements to mempool processing code.++## 0.23.5+### Changed+- Tighten the locking loop to avoid slow cache building.++## 0.23.5+### Fixed+- Wrong error in cache when acquiring lock.++## 0.23.4+### Added+- Add extra debug logging for cache code.++### Fixed+- Fix a bug with xpub growing algorithm.++### Changed+- Use locks instead of transactions to update cache.++## 0.23.3+### Fixed+- Reduce contention when many instances of Haskoin Store share a cache.++## 0.23.2+### Added+- Allow retrieving xpub data without using cache.++## 0.23.1+### Added+- Allow xpub eviction from cache via API.+- Clarify cache address addition code.++### Fixed+- Balances were incorrectly computed in cache when new transactions arrived.++## 0.23.0+### Added+- Support for Redis transactions.+- Use a smaller initial gap for empty xpubs.++### Changed+- Remove custom JSON encoding class.+- Refactor and code simplification.++## 0.22.5+### Fixed+- Cache was being completely pruned.++## 0.22.4+### Fixed+- Cache now prunes correctly.++## 0.22.3+### Fixed+- Bug was making cache get stuck when pruning.++## 0.22.2+### Changed+- More efficient algorithms for caching and cache misses.+- Better debug logging of cache hits and misses.++## 0.22.1+### Added+- More debug logging for cache hits.++### Fixed+- Bug using maximum against empty list.++## 0.22.0+### Changed+- Extreme code refactoring.+- Move all code to Haskoin and drop Network from modules.++### Added+- Use Redis pipelining when importing multiple transactions into cache.+- Implement configurable LRU for Redis cache.+- Import xpubs directly into cache from web worker thread when a key is requested.++### Removed+- Only expose a few modules to external API.++## 0.21.7+### Changed+- Improve build configuration.++### Fixed+- Use minimum used addresses instead of minimum index for xpub cache decision.++## 0.21.6+### Fixed+- Fix missing xpub unspent outputs when using cache.++## 0.21.5+### Added+- Only store xpubs in cache if they have more than a threshold addresses used.++## 0.21.4+### Fixed+- Fix shared cache case where head is set beyond header chain by another node.++## 0.21.3+### Fixed+- Fix bug where best head was not being registered in cache.+- Fix best head in cache being decoded incorrectly.++## 0.21.2+### Added+- Complete support for Redis xpub cache.++## 0.21.1+### Fixed+- Latest version of secp256k1-haskell works with Debian 9.++## 0.21.0+### Fixed+- Fix output of web API calls when issuing limits with offsets.++### Changed+- Massive refactoring of entire codebase.++### Added+- Work in progress Redis caching for extended public keys.++## 0.20.2+### Changed+- Filter xpub address balances on web API to show only addresses that have been used.++### Removed+- Remove paths and addresses from xpub summary.++## 0.20.1+### Changed+- Refactor code greatly.+- Depend on new Haskoin Store package to avoid missing tx broadcasts.+- Merge StoreStream class into StoreRead.+- Move former streaming functions to use lists instead of conduits.+- Remove excessively verbose debugging.++## 0.20.0+### Added+- Set minimum version bounds for some dependencies.+- Now compatible with GHC 8.8.+- Extended key caching system using Redis.++### Changed+- Massively refactored codebase.+- Less verbose debug logging.++### Removed+- Removed conduits for faster queries.++## 0.19.6+### Added+- Use a parallel strategy to compute key derivations.++### Fixed+- Do not fail health check upon transaction timeout while syncing.++### Changed+- Do not use conduits for xpub balance streams.+- Multiple minor refactorings.++## 0.19.5+### Changed+- Minor refactor to block import code.++### Fixed+- Minor fix to transaction timeout check.++## 0.19.4+### Fixed+- Clarify and correct health check algorithm.++## 0.19.3+### Changed+- Add address transactions to cache.+- Improve multi-address transaction retrieval algorithms.++## 0.19.2+### Removed+- Cache-Control header turned out to be unnecessary.++### Fixed+- Fix some minor errors in web module.++## 0.19.1+### Added+- Set Cache-Control header to no-cache.++## 0.19.0+### Changed+- Store mempool in single key/value pair.++## 0.18.11+### Changed+- Do not stream mempool.++## 0.18.10+### Removed+- Disable timeout checks for testnets.++## 0.18.9+### Added+- Endpoint to locate a block by unix timestamp.++### Removed+- No more persistence for peers due to dependency on newest haskoin-node.++## 0.18.8+### Added+- Transaction and block timeouts for health check.+- Raw blocks.++## 0.18.7+### Fixed+- Missing tranasctions on xpub listings.++## 0.18.6+### Removed+- Follow Stack advise removing `-O2` GHC option.++## 0.18.5+### Added+- Compatibility with SegWit on extended public key endpoints.++### Changed+- Fix syncing peer not reset after timeout.+- Use simpler monad for streaming data.++## 0.18.4+### Changed+- Bump Haskoin Node to fix peers not stored and excessively verbose logging.++## 0.18.3+### Changed+- Configurable HTTP request logging. Disabled by default.++## 0.18.2+### Changed+- Fix for memory leak.++## 0.18.1+### Removed+- Remove transaction count from xpub summary object.++## 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.+- Add configurable max value for limit and offset which defaults at 10000.++## 0.17.1+### Changed+- When posting a transaction to the network, timeout is now five seconds.+- Improve error message when transaction post timeout reached.+- Remove obsolete `not found` error for transaction post.+- Endpoints for retrieving blocks now do streaming for better performance.+- Improve Swagger API documentation.++## 0.17.0+### Added+- Endpoints for retrieving block transactions.+- Endpoint for retrieving set of latest blocks.++### Changed+- Use standardized JSON and binary serialization schemes for raw transaction endpoints.++## 0.16.6+### Changed+- Now logging info messages too by default.+- Consolidated web logging in middleware.++### Removed+- UUIDs for web requests.++## 0.16.5+### Removed+- Remove concurrency from xpub balance requests to prevent RocksDB segfaults.++## 0.16.4+### Removed+- Remove concurrency from requests using iterators to prevent RocksDB from segfaulting.++## 0.16.3+### Added+- Debugging information for web API.++## 0.16.2+### Changed+- Debugging disabled by default (use `--debug` to enable).++## 0.16.1+### Added+- Cache mempool transactions.+- Improve initial syncing performance.++## 0.16.0+### Added+- Orphan transaction support.+- Full address balance cache in RocksDB.+- Full unspent output cache in RocksDB.++### Changed+- Significantly refactor code.+- Move web stuff to its own module.+- Change types related to databases.+- Make xpub balance, transaction and unspent queries fetch data in parallel.++## 0.15.2+### Added+- Internal data types to support orphan transactions.++### Changed+- Do not spam block actor with pings.+- Fix balance/unspent cache not reverting when importing fails.+- Fix transaction sorting algorithm not including transaction position information.+- Fix conflicting mempool transaction preventing block from importing.++## 0.15.1+### Changed+- Fix duplicate coinbase transaction id bug.++## 0.15.0+### Removed+- Removed `PreciseUnixTime` data type.++### Changed+- Use 64 bits for Unix time representation.+- Data model now uses simplified Unix time representation.++## 0.14.9+### Added+- Last external/change index information to xpub summary object.++## 0.14.8+### Added+- Endpoint for xpub summaries.+- Endpoints for full transactions.+- Ability to query by offset/limit.+- More API documentation.++## 0.14.7+### Added+- Debug information for `publishTx`.++### Changed+- Transaction publishing no longer requests mempool.+- Fixed serialization for `TxId` freezing the entire program by infinitely recursing.++## 0.14.6+### Changed+- Enable full threading again as it was deemed not responsible for the freezing behaviour.++## 0.14.5+### Changed+- Enable threading but leave it at a single thread to be able to open more than 1024 files.++## 0.14.4+### Changed+- Target LTS Haskell 13.20 and disable threading in new attempt to fix freezing bug.++## 0.14.3+### Changed+- Remove `-O2`.++## 0.14.2+### Changed+- Target LTS Haskell 12.26 to attempt to fix freezing bug.++## 0.14.1+### Added+- Extra debugging around code that freezes.++### Changed+- Bump dependency on `haskoin-node`.++## 0.14.0+### Removed+- Dump slow protobuf serialization.++### Added+- Add custom serialization.+- Extra debug logging.++### Changed+- Bump `haskoin-core` and `haskoin-node`.++## 0.13.1+### Changed+- Bump `haskoin-node` in `stack.yaml`.+- Do not send empty `getdata` messages.++## 0.13.0+### Added+- Primitive content negotiation for web exceptions.+- Protobuf support for errors.+- Protobuf support for tranasction ids.++### Changed+- Protobuf format changed in non-backwards-compatible manner for transaction ids.++## 0.12.0+### Added+- Support for binary serialization using Protocol Buffers.+- New endpoints for binary raw transactions (not hex-encoded).++### Changed+- Services field now a hex string instead of a number to avoid overflowing signed 64-bit integer.+- Flatten list of block data objects when responding to request for multiple block heights.+- Errors now reported in plain text without container JSON object.+- Transaction broadcasts are responded to with transaction id in plaintext hex (no JSON).+- Remove database snapshots to improve performance.++## 0.11.2+### Changed+- Fix duplicate mempool transaction announcements in event stream.++## 0.11.1+### Removed+- Removed latest block time check.++## 0.11.0+### Changed+- Improve post transactions endpoint.++## 0.10.1+### Changed+- Fix bug where transaction lists from multiple addresses would sort incorrectly.+- Address gap reduced to 20.++## 0.10.0+### Removed+- Remove addresses from transaction lists.+- No longer use container objects for xpub transactions.++## 0.9.3+### Added+- Permissive CORS headers to allow queries from any domain.+- Improved documentation using real-world examples from the BCH testnet.++## 0.9.2+### Added+- HTTP JSON API switch to turn off transaction list when retrieving blocks.++## 0.9.1+### Added+- Total block fees.+- Total block outputs.+- Block subsidy.++### Changed+- Do not consider the blocks less one block away from headers as out of sync in health check.+- Health check now returns HTTP 503 when not OK or out of sync.++## 0.9.0+### Added+- Version to health check output.+- Block weight for segwit.+- Transaction weight for segwit.++## 0.8.1+### Added+- Health check endpoint.++## 0.8.0+### Added+- Limits and skips.+- Add timestamps to transactions.+- Add transaction count to address balance object.+- Add Merkle root to block data.+- Total funds received by an address now shows up in balance.+- Balances for any address that ever had funds show up in xpub endpoints.++### Changed+- Data model update.+- Performance improvement for xpub calls.+- Transactions are returned in reverse mempool/block order (highest or most recent first).+- Balance objects do not get deleted from database ever.++## 0.6.9+### Changed+- Reduce number of coinbase checks to 10,000 ancestors.++## 0.6.8+### Changed+- Further optimize coinbase after height checks.++## 0.6.7+### Changed+- Impose restrictions on recursion for coinbase after height checks.++## 0.6.6+### Added+- Check whether a transaction can be traced back to a coinbase after certain height.++## 0.6.5+### Changed+- Delete transactions in reverse topological order when reverting a block.++## 0.6.4+### Changed+- Do not fail silently when importing orphan transactions into the mempool.++## 0.6.3+### Changed+- Dummy release to bump haskoin-node in stack.yaml.++## 0.6.2+### Changed+- Correct bug where coinbase transactions weren't properly flagged.++## 0.6.1+### Changed+- Compatibility with Bitcoin Cash hard fork.+- Various bug fixes.++## 0.6.0+### Added+- Address balance cache in memory.++### Changed+- Simplify data model further.+- Fix bug importing outputs with UTXO cache.+- Unspent balances cannot be negative.++## 0.5.0+### Added+- Add UTXO cache in memory.+- Get transactions with witness data in segwit networks.++### Changed+- Paths for derivations in xpubs is a list and no longer a string.+- Various bug fixes.++## 0.4.2+### Removed+- Remove extended public key itself from output of relevant endpoints to save bandwidth.++## 0.4.1+### Changed+- Fix bug when deleting coinbase transactions.+- Extended public key API support.++## 0.4.0+### Changed+- Generate events for mempool transactions.+- Respond with entire block data when querying blocks by height.++## 0.3.1+### Changed+- Do not import transactions to mempool while not synced.+- Only sync mempool against a single peer.+- Allow duplicate transactions to fix re-introduced sync bug.++## 0.3.0+### Added+- Update dependencies.+- Keep orphan blocks and deleted transactions in database.+- Add a `mainchain` field for block data and a `deleted` field for transactions.+- Stream records for performance.+- Show witness data for transaction inputs in SegWit network.+- Support RBF in SegWit network.++### Changed+- Refactor all data access code away from actor.+- Refactor import logic away from actor.+- Abstract data access using typeclasses.+- Implement data access using clean layered architecture.+- Make most of import logic code pure.+- Database now in `db` as opposed to `blocks` directory.+- Use latest `haskoin-node`.++### Removed+- Remove some data from peer information output.+- Remove full transaction from address transaction data.+- Remove limits from address transaction data.+- Remove block data from previous output.+- Remove spender from JSON response when output not spent.+- Remove block hash from block reference.++## 0.2.3+### Removed+- Do not send transaction notifications if a block is being imported.++## 0.2.2+### Added+- Peer information endpoint.++### Changed+- Update `haskoin-node`.++## 0.2.1+### Changed+- Fix tests++## 0.2.0+### Added+- Documentation everywhere.+- Ability to retrieve address transactions.++### Changed+- New versions of NQE and Haskoin Node upstream.+- Improve and simplify API.+- Multi-element endpoints return arrays of arrays.+- Database snapshots for all queries are now mandatory.++### Removed+- Retrieving unspent and spent outputs for an address.+- Redundant API endpoints for multiple elements.++## 0.1.3+### Changed+- Fix a bug with transaction notifications.+- Improve handling orphan transactions.++## 0.1.2+### Changed+- Specify dependencies better.++## 0.1.1+### Changed+- Dependency `secp256k1` is now `secp256k1-haskell`.++## 0.1.0+### Added+- New `CHANGELOG.md` file.+- Bitcoin (BTC) and Bitcoin Cash (BCH) compatibility.+- RocksDB database.+- Mempool support.+- HTTP streaming for events.+- CashAddr support.+- Bech32 support.+- Rate limits.++### Changed+- Split out of former `haskoin` repository.+- Use hpack and `package.yaml`.++### Removed+- Removed Stylish Haskell configuration file.+- Remvoed `haskoin-core` and `haskoin-wallet` packages from this repository.
+ README.md view
@@ -0,0 +1,57 @@+# Haskoin Store++Block chain store and index featuring:++- Persistent storage using [RocksDB](https://rocksdb.org/).+- [Bitcoin Cash (BCH)](https://www.bitcoincash.org/) support.+- [Bitcoin Core (BTC)](https://bitcoin.org/) support.+- Indices for address balances, transactions, and unspent outputs (UTXO).+- Persistent mempool.+- Replace Bitcoin Core (BTC) RBF transactions by default.+- Query transactions, balances and UTXO on extended keys (xpub).+- Optional accelerated xpub cache using Redis.+- RESTful API with JSON and binary serialization.+- High performance concurrent architecture.+- Compatibility with Blockchain.info-style endpoints.++## Install with Nix on any distribution++* Get [Nix](https://nixos.org/nix/)++```sh+nix-env --install stack+git clone https://github.com/haskoin/haskoin-store.git+cd haskoin-store+stack --nix build --copy-bins+~/.local/bin/haskoin-store --help+```++## Install on Ubuntu or Debian++```sh+apt install git zlib1g-dev libsecp256k1-dev librocksdb-dev pkg-config haskell-stack+git clone https://github.com/haskoin/haskoin-store.git+cd haskoin-store+stack build --copy-bins+~/.local/bin/haskoin-store --help+```++## Install on Fedora++```sh+dnf install git zlib-devel libsecp256k1-devel rocksdb-devel stack+git clone https://github.com/haskoin/haskoin-store.git+cd haskoin-store+stack build --copy-bins+~/.local/bin/haskoin-store --help+```++## Non-Haskell Dependencies++* [zlib](https://zlib.net/)+* [libsecp256k1](https://github.com/Bitcoin-ABC/secp256k1)+* [RocksDB](https://github.com/facebook/rocksdb)++## API Documentation++* [Swagger API Documentation](https://api.haskoin.com/)
haskoin-store-data.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.36.0.+-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack ----- hash: 03532ff6fc9ef1e166af059c92a3233d35b358009d28980754f5bbce1abec023+-- hash: 5c6d0f79cd18954de7032dc7b391fccf7ff90f5afd84f693d4533a84f7a2afcc name: haskoin-store-data-version: 0.65.12+version: 1.0.0 synopsis: Data for Haskoin Store description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-store#readme> category: Bitcoin, Finance, Network@@ -18,6 +18,9 @@ license: MIT license-file: LICENSE build-type: Simple+extra-source-files:+ README.md+ CHANGELOG.md source-repository head type: git@@ -43,7 +46,7 @@ , data-default >=0.7.1.1 , deepseq >=1.4.4.0 , hashable >=1.3.0.0- , haskoin-core >=0.21.0+ , haskoin-core >=1.0.0 , http-client >=0.6.4.1 , http-types >=0.12.3 , lens >=4.18.1@@ -54,10 +57,11 @@ , text >=1.2.3.0 , time >=1.9.3 , unordered-containers >=0.2.10+ , vector >=0.13.0.0 , wreq >=0.5.3.2 default-language: Haskell2010 -test-suite haskoin-store-test+test-suite spec type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:@@ -79,7 +83,7 @@ , data-default >=0.7.1.1 , deepseq >=1.4.4.0 , hashable >=1.3.0.0- , haskoin-core >=0.21.0+ , haskoin-core >=1.0.0 , haskoin-store-data , hspec >=2.7.1 , http-client >=0.6.4.1@@ -92,6 +96,7 @@ , text >=1.2.3.0 , time >=1.9.3 , unordered-containers >=0.2.10+ , vector >=0.13.0.0 , wreq >=0.5.3.2 default-language: Haskell2010 build-tool-depends: hspec-discover:hspec-discover
src/Haskoin/Store/Data.hs view
@@ -1,3623 +1,3476 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Haskoin.Store.Data- ( -- * Address Balances- Balance (..),- balanceToJSON,- balanceToEncoding,- balanceParseJSON,- zeroBalance,- nullBalance,-- -- * Block Data- BlockData (..),- blockDataToJSON,- blockDataToEncoding,- confirmed,-- -- * Transactions- TxRef (..),- TxData (..),- txDataFee,- isCoinbaseTx,- Transaction (..),- transactionToJSON,- transactionToEncoding,- transactionParseJSON,- transactionData,- fromTransaction,- toTransaction,- StoreInput (..),- storeInputToJSON,- storeInputToEncoding,- storeInputParseJSON,- isCoinbase,- StoreOutput (..),- storeOutputToJSON,- storeOutputToEncoding,- storeOutputParseJSON,- Prev (..),- Spender (..),- BlockRef (..),- UnixTime,- getUnixTime,- putUnixTime,- BlockPos,-- -- * Unspent Outputs- Unspent (..),- unspentToJSON,- unspentToEncoding,- unspentParseJSON,-- -- * Extended Public Keys- XPubSpec (..),- XPubBal (..),- xPubBalToJSON,- xPubBalToEncoding,- xPubBalParseJSON,- XPubUnspent (..),- xPubUnspentToJSON,- xPubUnspentToEncoding,- xPubUnspentParseJSON,- XPubSummary (..),- DeriveType (..),- textToDeriveType,- deriveTypeToText,-- -- * Other Data- TxId (..),- GenericResult (..),- SerialList (..),- RawResult (..),- RawResultList (..),- PeerInformation (..),- Healthy (..),- BlockHealth (..),- TimeHealth (..),- CountHealth (..),- MaxHealth (..),- HealthCheck (..),- Event (..),- Except (..),-- -- * Blockchain.info API- BinfoBlockId (..),- BinfoTxId (..),- encodeBinfoTxId,- BinfoFilter (..),- BinfoMultiAddr (..),- binfoMultiAddrToJSON,- binfoMultiAddrToEncoding,- binfoMultiAddrParseJSON,- BinfoShortBal (..),- BinfoBalance (..),- toBinfoAddrs,- binfoBalanceToJSON,- binfoBalanceToEncoding,- binfoBalanceParseJSON,- BinfoRawAddr (..),- binfoRawAddrToJSON,- binfoRawAddrToEncoding,- binfoRawAddrParseJSON,- BinfoAddr (..),- parseBinfoAddr,- BinfoWallet (..),- BinfoUnspent (..),- binfoUnspentToJSON,- binfoUnspentToEncoding,- binfoUnspentParseJSON,- binfoHexValue,- BinfoUnspents (..),- binfoUnspentsToJSON,- binfoUnspentsToEncoding,- binfoUnspentsParseJSON,- BinfoBlock (..),- toBinfoBlock,- binfoBlockToJSON,- binfoBlockToEncoding,- binfoBlockParseJSON,- binfoBlocksToJSON,- binfoBlocksToEncoding,- binfoBlocksParseJSON,- BinfoTx (..),- relevantTxs,- toBinfoTx,- toBinfoTxSimple,- binfoTxToJSON,- binfoTxToEncoding,- binfoTxParseJSON,- BinfoTxInput (..),- binfoTxInputToJSON,- binfoTxInputToEncoding,- binfoTxInputParseJSON,- BinfoTxOutput (..),- binfoTxOutputToJSON,- binfoTxOutputToEncoding,- binfoTxOutputParseJSON,- BinfoSpender (..),- BinfoXPubPath (..),- binfoXPubPathToJSON,- binfoXPubPathToEncoding,- binfoXPubPathParseJSON,- BinfoInfo (..),- BinfoBlockInfo (..),- toBinfoBlockInfo,- BinfoSymbol (..),- BinfoTicker (..),- BinfoRate (..),- BinfoHistory (..),- toBinfoHistory,- BinfoDate (..),- BinfoHeader (..),- BinfoMempool (..),- binfoMempoolToJSON,- binfoMempoolToEncoding,- binfoMempoolParseJSON,- BinfoBlocks (..),- )-where--import Control.Applicative ((<|>))-import Control.DeepSeq (NFData)-import Control.Exception (Exception)-import Control.Monad (join, mzero, unless, (<=<))-import Data.Aeson- ( Encoding,- FromJSON (..),- ToJSON (..),- Value (..),- (.!=),- (.:),- (.:?),- (.=),- )-import qualified Data.Aeson as A-import qualified Data.Aeson.Encoding as AE-import Data.Aeson.Types (Parser)-import Data.Binary (Binary (get, put))-import Data.Bits (Bits (..))-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as BSB-import Data.Bytes.Get-import qualified Data.Bytes.Get as Bytes.Get-import Data.Bytes.Put-import Data.Bytes.Serial-import Data.Default (Default (..))-import Data.Foldable (toList)-import Data.Function (on)-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HashMap-import Data.HashSet (HashSet)-import qualified Data.HashSet as HashSet-import Data.Hashable (Hashable (..))-import Data.Int (Int32, Int64)-import qualified Data.IntMap as IntMap-import Data.IntMap.Strict (IntMap)-import Data.Maybe- ( catMaybes,- fromMaybe,- isJust,- isNothing,- mapMaybe,- maybeToList,- )-import Data.Serialize (Serialize (..))-import Data.String.Conversions (cs)-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Encoding as TLE-import Data.Time.Clock.POSIX- ( posixSecondsToUTCTime,- utcTimeToPOSIXSeconds,- )-import Data.Time.Format- ( defaultTimeLocale,- formatTime,- parseTimeM,- )-import Data.Word (Word32, Word64)-import GHC.Generics (Generic)-import Haskoin hiding (inputAddress)-import Web.Scotty.Trans (Parsable (..), ScottyError (..))--data DeriveType- = DeriveNormal- | DeriveP2SH- | DeriveP2WPKH- deriving (Show, Eq, Generic, NFData)--textToDeriveType :: Text -> Maybe DeriveType-textToDeriveType "normal" = Just DeriveNormal-textToDeriveType "compat" = Just DeriveP2SH-textToDeriveType "segwit" = Just DeriveP2WPKH-textToDeriveType _ = Nothing--deriveTypeToText :: DeriveType -> Text-deriveTypeToText DeriveNormal = "normal"-deriveTypeToText DeriveP2SH = "compat"-deriveTypeToText DeriveP2WPKH = "segwit"--instance Serial DeriveType where- serialize DeriveNormal = putWord8 0x00- serialize DeriveP2SH = putWord8 0x01- serialize DeriveP2WPKH = putWord8 0x02-- deserialize =- getWord8 >>= \case- 0x00 -> return DeriveNormal- 0x01 -> return DeriveP2SH- 0x02 -> return DeriveP2WPKH- _ -> return DeriveNormal--instance Binary DeriveType where- put = serialize- get = deserialize--instance Serialize DeriveType where- put = serialize- get = deserialize--instance Default DeriveType where- def = DeriveNormal--instance Parsable DeriveType where- parseParam txt =- case textToDeriveType (TL.toStrict txt) of- Nothing -> Left "invalid derivation type"- Just x -> Right x--data XPubSpec = XPubSpec- { xPubSpecKey :: !XPubKey,- xPubDeriveType :: !DeriveType- }- deriving (Show, Eq, Generic, NFData)--instance Hashable XPubSpec where- hashWithSalt i XPubSpec {xPubSpecKey = XPubKey {xPubKey = pubkey}} =- hashWithSalt i pubkey--instance Serial XPubSpec where- serialize XPubSpec {xPubSpecKey = k, xPubDeriveType = t} = do- putWord8 (xPubDepth k)- serialize (xPubParent k)- putWord32be (xPubIndex k)- serialize (xPubChain k)- serialize (wrapPubKey True (xPubKey k))- serialize t- deserialize = do- d <- getWord8- p <- deserialize- i <- getWord32be- c <- deserialize- k <- deserialize- t <- deserialize- let x =- XPubKey- { xPubDepth = d,- xPubParent = p,- xPubIndex = i,- xPubChain = c,- xPubKey = pubKeyPoint k- }- return XPubSpec {xPubSpecKey = x, xPubDeriveType = t}--instance Serialize XPubSpec where- put = serialize- get = deserialize--instance Binary XPubSpec where- put = serialize- get = deserialize--type UnixTime = Word64--type BlockPos = Word32---- | Binary such that ordering is inverted.-putUnixTime :: MonadPut m => Word64 -> m ()-putUnixTime w = putWord64be $ maxBound - w--getUnixTime :: MonadGet m => m Word64-getUnixTime = (maxBound -) <$> getWord64be---- | Reference to a block where a transaction is stored.-data BlockRef- = BlockRef- { -- | block height in the chain- blockRefHeight :: !BlockHeight,- -- | position of transaction within the block- blockRefPos :: !Word32- }- | MemRef- { memRefTime :: !UnixTime- }- deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)---- | Serial entities will sort in reverse order.-instance Serial BlockRef where- serialize MemRef {memRefTime = t} = do- putWord8 0x00- putUnixTime t- serialize BlockRef {blockRefHeight = h, blockRefPos = p} = do- putWord8 0x01- putWord32be (maxBound - h)- putWord32be (maxBound - p)- deserialize =- getWord8 >>= \case- 0x00 -> getmemref- 0x01 -> getblockref- _ -> fail "Cannot decode BlockRef"- where- getmemref = do- MemRef <$> getUnixTime- getblockref = do- h <- (maxBound -) <$> getWord32be- p <- (maxBound -) <$> getWord32be- return BlockRef {blockRefHeight = h, blockRefPos = p}--instance Serialize BlockRef where- put = serialize- get = deserialize--instance Binary BlockRef where- put = serialize- get = deserialize--confirmed :: BlockRef -> Bool-confirmed BlockRef {} = True-confirmed MemRef {} = False--instance ToJSON BlockRef where- toJSON BlockRef {blockRefHeight = h, blockRefPos = p} =- A.object ["height" .= h, "position" .= p]- toJSON MemRef {memRefTime = t} = A.object ["mempool" .= t]- toEncoding BlockRef {blockRefHeight = h, blockRefPos = p} =- AE.pairs ("height" .= h <> "position" .= p)- toEncoding MemRef {memRefTime = t} = AE.pairs ("mempool" .= t)--instance FromJSON BlockRef where- parseJSON = A.withObject "blockref" $ \o -> b o <|> m o- where- b o = do- height <- o .: "height"- position <- o .: "position"- return BlockRef {blockRefHeight = height, blockRefPos = position}- m o = do- mempool <- o .: "mempool"- return MemRef {memRefTime = mempool}---- | Transaction in relation to an address.-data TxRef = TxRef- { -- | block information- txRefBlock :: !BlockRef,- -- | transaction hash- txRefHash :: !TxHash- }- deriving (Show, Eq, Ord, Generic, Hashable, NFData)--instance Serial TxRef where- serialize (TxRef b h) = do- serialize b- serialize h-- deserialize =- TxRef <$> deserialize <*> deserialize--instance Binary TxRef where- put = serialize- get = deserialize--instance Serialize TxRef where- put = serialize- get = deserialize--instance ToJSON TxRef where- toJSON btx =- A.object- [ "txid" .= txRefHash btx,- "block" .= txRefBlock btx- ]- toEncoding btx =- AE.pairs $- "txid" .= txRefHash btx- <> "block" .= txRefBlock btx--instance FromJSON TxRef where- parseJSON =- A.withObject "blocktx" $ \o -> do- txid <- o .: "txid"- block <- o .: "block"- return TxRef {txRefBlock = block, txRefHash = txid}---- | Address balance information.-data Balance = Balance- { -- | address balance- balanceAddress :: !Address,- -- | confirmed balance- balanceAmount :: !Word64,- -- | unconfirmed balance- balanceZero :: !Word64,- -- | number of unspent outputs- balanceUnspentCount :: !Word64,- -- | number of transactions- balanceTxCount :: !Word64,- -- | total amount from all outputs in this address- balanceTotalReceived :: !Word64- }- deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)--instance Serial Balance where- serialize Balance {..} = do- serialize balanceAddress- putWord64be balanceAmount- putWord64be balanceZero- putWord64be balanceUnspentCount- putWord64be balanceTxCount- putWord64be balanceTotalReceived-- deserialize = do- balanceAddress <- deserialize- balanceAmount <- getWord64be- balanceZero <- getWord64be- balanceUnspentCount <- getWord64be- balanceTxCount <- getWord64be- balanceTotalReceived <- getWord64be- return Balance {..}--instance Binary Balance where- put = serialize- get = deserialize--instance Serialize Balance where- put = serialize- get = deserialize--zeroBalance :: Address -> Balance-zeroBalance a =- Balance- { balanceAddress = a,- balanceAmount = 0,- balanceUnspentCount = 0,- balanceZero = 0,- balanceTxCount = 0,- balanceTotalReceived = 0- }--nullBalance :: Balance -> Bool-nullBalance- Balance- { balanceAmount = 0,- balanceUnspentCount = 0,- balanceZero = 0,- balanceTxCount = 0,- balanceTotalReceived = 0- } = True-nullBalance _ = False--balanceToJSON :: Network -> Balance -> Value-balanceToJSON net b =- A.object- [ "address" .= addrToJSON net (balanceAddress b),- "confirmed" .= balanceAmount b,- "unconfirmed" .= balanceZero b,- "utxo" .= balanceUnspentCount b,- "txs" .= balanceTxCount b,- "received" .= balanceTotalReceived b- ]--balanceToEncoding :: Network -> Balance -> Encoding-balanceToEncoding net b =- AE.pairs $- "address" `AE.pair` addrToEncoding net (balanceAddress b)- <> "confirmed" .= balanceAmount b- <> "unconfirmed" .= balanceZero b- <> "utxo" .= balanceUnspentCount b- <> "txs" .= balanceTxCount b- <> "received" .= balanceTotalReceived b--balanceParseJSON :: Network -> Value -> Parser Balance-balanceParseJSON net =- A.withObject "balance" $ \o -> do- amount <- o .: "confirmed"- unconfirmed <- o .: "unconfirmed"- utxo <- o .: "utxo"- txs <- o .: "txs"- received <- o .: "received"- address <- addrFromJSON net =<< o .: "address"- return- Balance- { balanceAddress = address,- balanceAmount = amount,- balanceUnspentCount = utxo,- balanceZero = unconfirmed,- balanceTxCount = txs,- balanceTotalReceived = received- }---- | Unspent output.-data Unspent = Unspent- { unspentBlock :: !BlockRef,- unspentPoint :: !OutPoint,- unspentAmount :: !Word64,- unspentScript :: !ByteString,- unspentAddress :: !(Maybe Address)- }- deriving (Show, Eq, Generic, Hashable, NFData)---- | Follow same order as in database and cache by inverting outpoint sort--- order.-instance Ord Unspent where- compare a b =- compare- (unspentBlock a, unspentPoint b)- (unspentBlock b, unspentPoint a)--instance Serial Unspent where- serialize Unspent {..} = do- serialize unspentBlock- serialize unspentPoint- putWord64be unspentAmount- putLengthBytes unspentScript- putMaybe serialize unspentAddress-- deserialize = do- unspentBlock <- deserialize- unspentPoint <- deserialize- unspentAmount <- getWord64be- unspentScript <- getLengthBytes- unspentAddress <- getMaybe deserialize- return Unspent {..}--instance Binary Unspent where- put = serialize- get = deserialize--instance Serialize Unspent where- put = serialize- get = deserialize--instance Coin Unspent where- coinValue = unspentAmount--unspentToJSON :: Network -> Unspent -> Value-unspentToJSON net u =- A.object- [ "address" .= (addrToJSON net <$> unspentAddress u),- "block" .= unspentBlock u,- "txid" .= outPointHash (unspentPoint u),- "index" .= outPointIndex (unspentPoint u),- "pkscript" .= encodeHex (unspentScript u),- "value" .= unspentAmount u- ]--unspentToEncoding :: Network -> Unspent -> Encoding-unspentToEncoding net u =- AE.pairs $- "address" `AE.pair` maybe AE.null_ (addrToEncoding net) (unspentAddress u)- <> "block" .= unspentBlock u- <> "txid" .= outPointHash (unspentPoint u)- <> "index" .= outPointIndex (unspentPoint u)- <> "pkscript" `AE.pair` AE.text (encodeHex (unspentScript u))- <> "value" .= unspentAmount u--unspentParseJSON :: Network -> Value -> Parser Unspent-unspentParseJSON net =- A.withObject "unspent" $ \o -> do- block <- o .: "block"- txid <- o .: "txid"- index <- o .: "index"- value <- o .: "value"- script <- o .: "pkscript" >>= jsonHex- addr <-- o .: "address" >>= \case- Nothing -> return Nothing- Just a -> Just <$> addrFromJSON net a <|> return Nothing- return- Unspent- { unspentBlock = block,- unspentPoint = OutPoint txid index,- unspentAmount = value,- unspentScript = script,- unspentAddress = addr- }---- | Database value for a block entry.-data BlockData = BlockData- { -- | height of the block in the chain- blockDataHeight :: !BlockHeight,- -- | is this block in the main chain?- blockDataMainChain :: !Bool,- -- | accumulated work in that block- blockDataWork :: !Integer,- -- | block header- blockDataHeader :: !BlockHeader,- -- | size of the block including witnesses- blockDataSize :: !Word32,- -- | weight of this block (for segwit networks)- blockDataWeight :: !Word32,- -- | block transactions- blockDataTxs :: ![TxHash],- -- | sum of all transaction outputs- blockDataOutputs :: !Word64,- -- | sum of all transaction fees- blockDataFees :: !Word64,- -- | block subsidy- blockDataSubsidy :: !Word64- }- deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)--instance Serial BlockData where- serialize BlockData {..} = do- putWord32be blockDataHeight- serialize blockDataMainChain- putInteger blockDataWork- serialize blockDataHeader- putWord32be blockDataSize- putWord32be blockDataWeight- putList serialize blockDataTxs- putWord64be blockDataOutputs- putWord64be blockDataFees- putWord64be blockDataSubsidy-- deserialize = do- blockDataHeight <- getWord32be- blockDataMainChain <- deserialize- blockDataWork <- getInteger- blockDataHeader <- deserialize- blockDataSize <- getWord32be- blockDataWeight <- getWord32be- blockDataTxs <- getList deserialize- blockDataOutputs <- getWord64be- blockDataFees <- getWord64be- blockDataSubsidy <- getWord64be- return BlockData {..}--instance Serialize BlockData where- put = serialize- get = deserialize--instance Binary BlockData where- put = serialize- get = deserialize--blockDataToJSON :: Network -> BlockData -> Value-blockDataToJSON net bv =- A.object $- [ "hash" .= headerHash (blockDataHeader bv),- "height" .= blockDataHeight bv,- "mainchain" .= blockDataMainChain bv,- "previous" .= prevBlock (blockDataHeader bv),- "time" .= blockTimestamp (blockDataHeader bv),- "version" .= blockVersion (blockDataHeader bv),- "bits" .= blockBits (blockDataHeader bv),- "nonce" .= bhNonce (blockDataHeader bv),- "size" .= blockDataSize bv,- "tx" .= blockDataTxs bv,- "merkle" .= TxHash (merkleRoot (blockDataHeader bv)),- "subsidy" .= blockDataSubsidy bv,- "fees" .= blockDataFees bv,- "outputs" .= blockDataOutputs bv,- "work" .= blockDataWork bv- ]- <> ["weight" .= blockDataWeight bv | getSegWit net]--blockDataToEncoding :: Network -> BlockData -> Encoding-blockDataToEncoding net bv =- AE.pairs $- "hash"- `AE.pair` AE.text- (blockHashToHex (headerHash (blockDataHeader bv)))- <> "height" .= blockDataHeight bv- <> "mainchain" .= blockDataMainChain bv- <> "previous" .= prevBlock (blockDataHeader bv)- <> "time" .= blockTimestamp (blockDataHeader bv)- <> "version" .= blockVersion (blockDataHeader bv)- <> "bits" .= blockBits (blockDataHeader bv)- <> "nonce" .= bhNonce (blockDataHeader bv)- <> "size" .= blockDataSize bv- <> "tx" .= blockDataTxs bv- <> "merkle"- `AE.pair` AE.text- (txHashToHex (TxHash (merkleRoot (blockDataHeader bv))))- <> "subsidy" .= blockDataSubsidy bv- <> "fees" .= blockDataFees bv- <> "outputs" .= blockDataOutputs bv- <> "work" .= blockDataWork bv- <> (if getSegWit net then "weight" .= blockDataWeight bv else mempty)--instance FromJSON BlockData where- parseJSON =- A.withObject "blockdata" $ \o -> do- height <- o .: "height"- mainchain <- o .: "mainchain"- previous <- o .: "previous"- time <- o .: "time"- version <- o .: "version"- bits <- o .: "bits"- nonce <- o .: "nonce"- size <- o .: "size"- tx <- o .: "tx"- TxHash merkle <- o .: "merkle"- subsidy <- o .: "subsidy"- fees <- o .: "fees"- outputs <- o .: "outputs"- work <- o .: "work"- weight <- o .:? "weight" .!= 0- return- BlockData- { blockDataHeader =- BlockHeader- { prevBlock = previous,- blockTimestamp = time,- blockVersion = version,- blockBits = bits,- bhNonce = nonce,- merkleRoot = merkle- },- blockDataMainChain = mainchain,- blockDataWork = work,- blockDataSize = size,- blockDataWeight = weight,- blockDataTxs = tx,- blockDataOutputs = outputs,- blockDataFees = fees,- blockDataHeight = height,- blockDataSubsidy = subsidy- }--data StoreInput- = StoreCoinbase- { inputPoint :: !OutPoint,- inputSequence :: !Word32,- inputSigScript :: !ByteString,- inputWitness :: !WitnessStack- }- | StoreInput- { inputPoint :: !OutPoint,- inputSequence :: !Word32,- inputSigScript :: !ByteString,- inputPkScript :: !ByteString,- inputAmount :: !Word64,- inputWitness :: !WitnessStack,- inputAddress :: !(Maybe Address)- }- deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)--instance Serial StoreInput where- serialize StoreCoinbase {..} = do- putWord8 0x00- serialize inputPoint- putWord32be inputSequence- putLengthBytes inputSigScript- putList putLengthBytes inputWitness- serialize StoreInput {..} = do- putWord8 0x01- serialize inputPoint- putWord32be inputSequence- putLengthBytes inputSigScript- putLengthBytes inputPkScript- putWord64be inputAmount- putList putLengthBytes inputWitness- putMaybe serialize inputAddress-- deserialize =- getWord8 >>= \case- 0x00 -> do- inputPoint <- deserialize- inputSequence <- getWord32be- inputSigScript <- getLengthBytes- inputWitness <- getList getLengthBytes- return StoreCoinbase {..}- 0x01 -> do- inputPoint <- deserialize- inputSequence <- getWord32be- inputSigScript <- getLengthBytes- inputPkScript <- getLengthBytes- inputAmount <- getWord64be- inputWitness <- getList getLengthBytes- inputAddress <- getMaybe deserialize- return StoreInput {..}- x -> fail $ "Unknown input id: " <> cs (show x)--instance Serialize StoreInput where- put = serialize- get = deserialize--instance Binary StoreInput where- put = serialize- get = deserialize--isCoinbaseTx :: Tx -> Bool-isCoinbaseTx = all ((== nullOutPoint) . prevOutput) . txIn--isCoinbase :: StoreInput -> Bool-isCoinbase StoreCoinbase {} = True-isCoinbase StoreInput {} = False--storeInputToJSON :: Network -> StoreInput -> Value-storeInputToJSON- net- StoreInput- { inputPoint = OutPoint oph opi,- inputSequence = sq,- inputSigScript = ss,- inputPkScript = ps,- inputAmount = val,- inputWitness = wit,- inputAddress = a- } =- A.object- [ "coinbase" .= False,- "txid" .= oph,- "output" .= opi,- "sigscript" .= String (encodeHex ss),- "sequence" .= sq,- "pkscript" .= String (encodeHex ps),- "value" .= val,- "address" .= (addrToJSON net <$> a),- "witness" .= map encodeHex wit- ]-storeInputToJSON- _- StoreCoinbase- { inputPoint = OutPoint oph opi,- inputSequence = sq,- inputSigScript = ss,- inputWitness = wit- } =- A.object- [ "coinbase" .= True,- "txid" .= oph,- "output" .= opi,- "sigscript" .= String (encodeHex ss),- "sequence" .= sq,- "pkscript" .= Null,- "value" .= Null,- "address" .= Null,- "witness" .= map encodeHex wit- ]--storeInputToEncoding :: Network -> StoreInput -> Encoding-storeInputToEncoding- net- StoreInput- { inputPoint = OutPoint oph opi,- inputSequence = sq,- inputSigScript = ss,- inputPkScript = ps,- inputAmount = val,- inputWitness = wit,- inputAddress = a- } =- AE.pairs $- "coinbase" .= False- <> "txid" .= oph- <> "output" .= opi- <> "sigscript" `AE.pair` AE.text (encodeHex ss)- <> "sequence" .= sq- <> "pkscript" `AE.pair` AE.text (encodeHex ps)- <> "value" .= val- <> "address" `AE.pair` maybe AE.null_ (addrToEncoding net) a- <> "witness" .= map encodeHex wit-storeInputToEncoding- _- StoreCoinbase- { inputPoint = OutPoint oph opi,- inputSequence = sq,- inputSigScript = ss,- inputWitness = wit- } =- AE.pairs $- "coinbase" .= True- <> "txid" `AE.pair` AE.text (txHashToHex oph)- <> "output" .= opi- <> "sigscript" `AE.pair` AE.text (encodeHex ss)- <> "sequence" .= sq- <> "pkscript" `AE.pair` AE.null_- <> "value" `AE.pair` AE.null_- <> "address" `AE.pair` AE.null_- <> "witness" .= map encodeHex wit--storeInputParseJSON :: Network -> Value -> Parser StoreInput-storeInputParseJSON net =- A.withObject "storeinput" $ \o -> do- coinbase <- o .: "coinbase"- outpoint <- OutPoint <$> o .: "txid" <*> o .: "output"- sequ <- o .: "sequence"- witness <- mapM jsonHex =<< o .:? "witness" .!= []- sigscript <- o .: "sigscript" >>= jsonHex- if coinbase- then- return- StoreCoinbase- { inputPoint = outpoint,- inputSequence = sequ,- inputSigScript = sigscript,- inputWitness = witness- }- else do- pkscript <- o .: "pkscript" >>= jsonHex- value <- o .: "value"- addr <-- o .: "address" >>= \case- Nothing -> return Nothing- Just a -> Just <$> addrFromJSON net a <|> return Nothing- return- StoreInput- { inputPoint = outpoint,- inputSequence = sequ,- inputSigScript = sigscript,- inputPkScript = pkscript,- inputAmount = value,- inputWitness = witness,- inputAddress = addr- }--jsonHex :: Text -> Parser ByteString-jsonHex s =- case decodeHex s of- Nothing -> fail "Could not decode hex"- Just b -> return b---- | Information about input spending output.-data Spender = Spender- { -- | input transaction hash- spenderHash :: !TxHash,- -- | input position in transaction- spenderIndex :: !Word32- }- deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)--instance Serial Spender where- serialize Spender {..} = do- serialize spenderHash- putWord32be spenderIndex- deserialize = Spender <$> deserialize <*> getWord32be--instance Serialize Spender where- put = serialize- get = deserialize--instance Binary Spender where- put = serialize- get = deserialize--instance ToJSON Spender where- toJSON n =- A.object- [ "txid" .= txHashToHex (spenderHash n),- "input" .= spenderIndex n- ]- toEncoding n =- AE.pairs $- "txid" .= txHashToHex (spenderHash n)- <> "input" .= spenderIndex n--instance FromJSON Spender where- parseJSON =- A.withObject "spender" $ \o ->- Spender <$> o .: "txid" <*> o .: "input"---- | Output information.-data StoreOutput = StoreOutput- { outputAmount :: !Word64,- outputScript :: !ByteString,- outputSpender :: !(Maybe Spender),- outputAddr :: !(Maybe Address)- }- deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)--instance Serial StoreOutput where- serialize StoreOutput {..} = do- putWord64be outputAmount- putLengthBytes outputScript- putMaybe serialize outputSpender- putMaybe serialize outputAddr- deserialize = do- outputAmount <- getWord64be- outputScript <- getLengthBytes- outputSpender <- getMaybe deserialize- outputAddr <- getMaybe deserialize- return StoreOutput {..}--instance Serialize StoreOutput where- put = serialize- get = deserialize--instance Binary StoreOutput where- put = serialize- get = deserialize--storeOutputToJSON :: Network -> StoreOutput -> Value-storeOutputToJSON net d =- A.object- [ "address" .= (addrToJSON net <$> outputAddr d),- "pkscript" .= encodeHex (outputScript d),- "value" .= outputAmount d,- "spent" .= isJust (outputSpender d),- "spender" .= outputSpender d- ]--storeOutputToEncoding :: Network -> StoreOutput -> Encoding-storeOutputToEncoding net d =- AE.pairs $- "address" `AE.pair` maybe AE.null_ (addrToEncoding net) (outputAddr d)- <> "pkscript" `AE.pair` AE.text (encodeHex (outputScript d))- <> "value" .= outputAmount d- <> "spent" .= isJust (outputSpender d)- <> "spender" .= outputSpender d--storeOutputParseJSON :: Network -> Value -> Parser StoreOutput-storeOutputParseJSON net =- A.withObject "storeoutput" $ \o -> do- value <- o .: "value"- pkscript <- o .: "pkscript" >>= jsonHex- spender <- o .: "spender"- addr <-- o .: "address" >>= \case- Nothing -> return Nothing- Just a -> Just <$> addrFromJSON net a <|> return Nothing- return- StoreOutput- { outputAmount = value,- outputScript = pkscript,- outputSpender = spender,- outputAddr = addr- }--data Prev = Prev- { prevScript :: !ByteString,- prevAmount :: !Word64- }- deriving (Show, Eq, Ord, Generic, Hashable, NFData)--instance Serial Prev where- serialize Prev {..} = do- putLengthBytes prevScript- putWord64be prevAmount- deserialize = do- prevScript <- getLengthBytes- prevAmount <- getWord64be- return Prev {..}--instance Binary Prev where- put = serialize- get = deserialize--instance Serialize Prev where- put = serialize- get = deserialize--toInput :: TxIn -> Maybe Prev -> WitnessStack -> StoreInput-toInput i Nothing w =- StoreCoinbase- { inputPoint = prevOutput i,- inputSequence = txInSequence i,- inputSigScript = scriptInput i,- inputWitness = w- }-toInput i (Just p) w =- StoreInput- { inputPoint = prevOutput i,- inputSequence = txInSequence i,- inputSigScript = scriptInput i,- inputPkScript = prevScript p,- inputAmount = prevAmount p,- inputWitness = w,- inputAddress = eitherToMaybe (scriptToAddressBS (prevScript p))- }--toOutput :: TxOut -> Maybe Spender -> StoreOutput-toOutput o s =- StoreOutput- { outputAmount = outValue o,- outputScript = scriptOutput o,- outputSpender = s,- outputAddr = eitherToMaybe (scriptToAddressBS (scriptOutput o))- }--data TxData = TxData- { txDataBlock :: !BlockRef,- txData :: !Tx,- txDataPrevs :: !(IntMap Prev),- txDataDeleted :: !Bool,- txDataRBF :: !Bool,- txDataTime :: !Word64,- txDataSpenders :: !(IntMap Spender)- }- deriving (Show, Eq, Ord, Generic, NFData)--instance Serial TxData where- serialize TxData {..} = do- serialize txDataBlock- serialize txData- putIntMap (putWord64be . fromIntegral) serialize txDataPrevs- serialize txDataDeleted- serialize txDataRBF- putWord64be txDataTime- putIntMap (putWord64be . fromIntegral) serialize txDataSpenders- deserialize = do- txDataBlock <- deserialize- txData <- deserialize- txDataPrevs <- getIntMap (fromIntegral <$> getWord64be) deserialize- txDataDeleted <- deserialize- txDataRBF <- deserialize- txDataTime <- getWord64be- txDataSpenders <- getIntMap (fromIntegral <$> getWord64be) deserialize- return TxData {..}--instance Serialize TxData where- put = serialize- get = deserialize--instance Binary TxData where- put = serialize- get = deserialize--txDataFee :: TxData -> Word64-txDataFee TxData {..} =- if isCoinbaseTx txData then 0 else inputs - outputs- where- inputs = sum . map prevAmount $ IntMap.elems txDataPrevs- outputs = sum . map outValue $ txOut txData--toTransaction :: TxData -> Transaction-toTransaction t =- Transaction- { transactionBlock = txDataBlock t,- transactionVersion = txVersion (txData t),- transactionLockTime = txLockTime (txData t),- transactionInputs = ins,- transactionOutputs = outs,- transactionDeleted = txDataDeleted t,- transactionRBF = txDataRBF t,- transactionTime = txDataTime t,- transactionId = txid,- transactionSize = txsize,- transactionWeight = txweight,- transactionFees = fees- }- where- txid = txHash (txData t)- txsize = fromIntegral $ BS.length (runPutS (serialize (txData t)))- txweight =- let b = BS.length $ runPutS $ serialize (txData t) {txWitness = []}- x = BS.length $ runPutS $ serialize (txData t)- in fromIntegral $ b * 3 + x- inv = sum (map inputAmount ins)- outv = sum (map outputAmount outs)- fees = if any isCoinbase ins then 0 else inv - outv- ws = take (length (txIn (txData t))) $ txWitness (txData t) <> repeat []- f n i = toInput i (IntMap.lookup n (txDataPrevs t)) (ws !! n)- ins = zipWith f [0 ..] (txIn (txData t))- g n o = toOutput o (IntMap.lookup n (txDataSpenders t))- outs = zipWith g [0 ..] (txOut (txData t))--fromTransaction :: Transaction -> TxData-fromTransaction t =- TxData- { txDataBlock = transactionBlock t,- txData = transactionData t,- txDataPrevs = ps,- txDataDeleted = transactionDeleted t,- txDataRBF = transactionRBF t,- txDataTime = transactionTime t,- txDataSpenders = sm- }- where- f _ StoreCoinbase {} = Nothing- f n StoreInput {inputPkScript = s, inputAmount = v} =- Just (n, Prev {prevScript = s, prevAmount = v})- ps = IntMap.fromList . catMaybes $ zipWith f [0 ..] (transactionInputs t)- g _ StoreOutput {outputSpender = Nothing} = Nothing- g n StoreOutput {outputSpender = Just s} = Just (n, s)- sm = IntMap.fromList . catMaybes $ zipWith g [0 ..] (transactionOutputs t)---- | Detailed transaction information.-data Transaction = Transaction- { -- | block information for this transaction- transactionBlock :: !BlockRef,- -- | transaction version- transactionVersion :: !Word32,- -- | lock time- transactionLockTime :: !Word32,- -- | transaction inputs- transactionInputs :: ![StoreInput],- -- | transaction outputs- transactionOutputs :: ![StoreOutput],- -- | this transaction has been deleted and is no longer valid- transactionDeleted :: !Bool,- -- | this transaction can be replaced in the mempool- transactionRBF :: !Bool,- -- | time the transaction was first seen or time of block- transactionTime :: !Word64,- -- | transaction id- transactionId :: !TxHash,- -- | serialized transaction size (includes witness data)- transactionSize :: !Word32,- -- | transaction weight- transactionWeight :: !Word32,- -- | fees that this transaction pays (0 for coinbase)- transactionFees :: !Word64- }- deriving (Show, Eq, Ord, Generic, Hashable, NFData)--instance Serial Transaction where- serialize Transaction {..} = do- serialize transactionBlock- putWord32be transactionVersion- putWord32be transactionLockTime- putList serialize transactionInputs- putList serialize transactionOutputs- serialize transactionDeleted- serialize transactionRBF- putWord64be transactionTime- serialize transactionId- putWord32be transactionSize- putWord32be transactionWeight- putWord64be transactionFees- deserialize = do- transactionBlock <- deserialize- transactionVersion <- getWord32be- transactionLockTime <- getWord32be- transactionInputs <- getList deserialize- transactionOutputs <- getList deserialize- transactionDeleted <- deserialize- transactionRBF <- deserialize- transactionTime <- getWord64be- transactionId <- deserialize- transactionSize <- getWord32be- transactionWeight <- getWord32be- transactionFees <- getWord64be- return Transaction {..}--instance Serialize Transaction where- put = serialize- get = deserialize--instance Binary Transaction where- put = serialize- get = deserialize--transactionData :: Transaction -> Tx-transactionData t =- Tx- { txVersion = transactionVersion t,- txIn = map i (transactionInputs t),- txOut = map o (transactionOutputs t),- txWitness = w $ map inputWitness (transactionInputs t),- txLockTime = transactionLockTime t- }- where- i StoreCoinbase {inputPoint = p, inputSequence = q, inputSigScript = s} =- TxIn {prevOutput = p, scriptInput = s, txInSequence = q}- i StoreInput {inputPoint = p, inputSequence = q, inputSigScript = s} =- TxIn {prevOutput = p, scriptInput = s, txInSequence = q}- o StoreOutput {outputAmount = v, outputScript = s} =- TxOut {outValue = v, scriptOutput = s}- w xs- | all null xs = []- | otherwise = xs--transactionToJSON :: Network -> Transaction -> Value-transactionToJSON net dtx =- A.object- [ "txid" .= transactionId dtx,- "size" .= transactionSize dtx,- "version" .= transactionVersion dtx,- "locktime" .= transactionLockTime dtx,- "fee" .= transactionFees dtx,- "inputs" .= map (storeInputToJSON net) (transactionInputs dtx),- "outputs" .= map (storeOutputToJSON net) (transactionOutputs dtx),- "block" .= transactionBlock dtx,- "deleted" .= transactionDeleted dtx,- "time" .= transactionTime dtx,- "rbf" .= transactionRBF dtx,- "weight" .= transactionWeight dtx- ]--transactionToEncoding :: Network -> Transaction -> Encoding-transactionToEncoding net dtx =- AE.pairs $- "txid" .= transactionId dtx- <> "size" .= transactionSize dtx- <> "version" .= transactionVersion dtx- <> "locktime" .= transactionLockTime dtx- <> "fee" .= transactionFees dtx- <> "inputs"- `AE.pair` AE.list- (storeInputToEncoding net)- (transactionInputs dtx)- <> "outputs"- `AE.pair` AE.list- (storeOutputToEncoding net)- (transactionOutputs dtx)- <> "block" .= transactionBlock dtx- <> "deleted" .= transactionDeleted dtx- <> "time" .= transactionTime dtx- <> "rbf" .= transactionRBF dtx- <> "weight" .= transactionWeight dtx--transactionParseJSON :: Network -> Value -> Parser Transaction-transactionParseJSON net = A.withObject "transaction" $ \o -> do- version <- o .: "version"- locktime <- o .: "locktime"- inputs <- o .: "inputs" >>= mapM (storeInputParseJSON net)- outputs <- o .: "outputs" >>= mapM (storeOutputParseJSON net)- block <- o .: "block"- deleted <- o .: "deleted"- time <- o .: "time"- rbf <- o .:? "rbf" .!= False- weight <- o .:? "weight" .!= 0- size <- o .: "size"- txid <- o .: "txid"- fees <- o .: "fee"- return- Transaction- { transactionBlock = block,- transactionVersion = version,- transactionLockTime = locktime,- transactionInputs = inputs,- transactionOutputs = outputs,- transactionDeleted = deleted,- transactionTime = time,- transactionRBF = rbf,- transactionWeight = weight,- transactionSize = size,- transactionId = txid,- transactionFees = fees- }---- | Information about a connected peer.-data PeerInformation = PeerInformation- { -- | user agent string- peerUserAgent :: !ByteString,- -- | network address- peerAddress :: !String,- -- | version number- peerVersion :: !Word32,- -- | services field- peerServices :: !Word64,- -- | will relay transactions- peerRelay :: !Bool- }- deriving (Show, Eq, Ord, Generic, NFData)--instance Serial PeerInformation where- serialize PeerInformation {..} = do- putLengthBytes peerUserAgent- putLengthBytes (TE.encodeUtf8 (T.pack peerAddress))- putWord32be peerVersion- putWord64be peerServices- serialize peerRelay- deserialize = do- peerUserAgent <- getLengthBytes- peerAddress <- T.unpack . TE.decodeUtf8 <$> getLengthBytes- peerVersion <- getWord32be- peerServices <- getWord64be- peerRelay <- deserialize- return PeerInformation {..}--instance Serialize PeerInformation where- put = serialize- get = deserialize--instance Binary PeerInformation where- put = serialize- get = deserialize--instance ToJSON PeerInformation where- toJSON p =- A.object- [ "useragent" .= String (cs (peerUserAgent p)),- "address" .= peerAddress p,- "version" .= peerVersion p,- "services"- .= String (encodeHex (runPutS (serialize (peerServices p)))),- "relay" .= peerRelay p- ]- toEncoding p =- AE.pairs $- "useragent" `AE.pair` AE.text (cs (peerUserAgent p))- <> "address" .= peerAddress p- <> "version" .= peerVersion p- <> "services"- `AE.pair` AE.text- (encodeHex (runPutS (serialize (peerServices p))))- <> "relay" .= peerRelay p--instance FromJSON PeerInformation where- parseJSON =- A.withObject "peerinformation" $ \o -> do- String useragent <- o .: "useragent"- address <- o .: "address"- version <- o .: "version"- services <-- o .: "services" >>= jsonHex >>= \b ->- case runGetS deserialize b of- Left e -> fail $ "Could not decode services: " <> e- Right s -> return s- relay <- o .: "relay"- return- PeerInformation- { peerUserAgent = cs useragent,- peerAddress = address,- peerVersion = version,- peerServices = services,- peerRelay = relay- }---- | Address balances for an extended public key.-data XPubBal = XPubBal- { xPubBalPath :: ![KeyIndex],- xPubBal :: !Balance- }- deriving (Show, Ord, Eq, Generic, NFData)--instance Serial XPubBal where- serialize XPubBal {..} = do- putList putWord32be xPubBalPath- serialize xPubBal- deserialize = do- xPubBalPath <- getList getWord32be- xPubBal <- deserialize- return XPubBal {..}--instance Serialize XPubBal where- put = serialize- get = deserialize--instance Binary XPubBal where- put = serialize- get = deserialize--xPubBalToJSON :: Network -> XPubBal -> Value-xPubBalToJSON net XPubBal {xPubBalPath = p, xPubBal = b} =- A.object ["path" .= p, "balance" .= balanceToJSON net b]--xPubBalToEncoding :: Network -> XPubBal -> Encoding-xPubBalToEncoding net XPubBal {xPubBalPath = p, xPubBal = b} =- AE.pairs ("path" .= p <> "balance" `AE.pair` balanceToEncoding net b)--xPubBalParseJSON :: Network -> Value -> Parser XPubBal-xPubBalParseJSON net =- A.withObject "xpubbal" $ \o -> do- path <- o .: "path"- balance <- balanceParseJSON net =<< o .: "balance"- return XPubBal {xPubBalPath = path, xPubBal = balance}---- | Unspent transaction for extended public key.-data XPubUnspent = XPubUnspent- { xPubUnspent :: !Unspent,- xPubUnspentPath :: ![KeyIndex]- }- deriving (Show, Eq, Ord, Generic, NFData)--instance Serial XPubUnspent where- serialize XPubUnspent {..} = do- putList putWord32be xPubUnspentPath- serialize xPubUnspent- deserialize = do- xPubUnspentPath <- getList getWord32be- xPubUnspent <- deserialize- return XPubUnspent {..}--instance Serialize XPubUnspent where- put = serialize- get = deserialize--instance Binary XPubUnspent where- put = serialize- get = deserialize--xPubUnspentToJSON :: Network -> XPubUnspent -> Value-xPubUnspentToJSON net XPubUnspent {xPubUnspentPath = p, xPubUnspent = u} =- A.object ["unspent" .= unspentToJSON net u, "path" .= p]--xPubUnspentToEncoding :: Network -> XPubUnspent -> Encoding-xPubUnspentToEncoding net XPubUnspent {xPubUnspentPath = p, xPubUnspent = u} =- AE.pairs $ "unspent" `AE.pair` unspentToEncoding net u <> "path" .= p--xPubUnspentParseJSON :: Network -> Value -> Parser XPubUnspent-xPubUnspentParseJSON net =- A.withObject "xpubunspent" $ \o -> do- u <- o .: "unspent" >>= unspentParseJSON net- p <- o .: "path"- return XPubUnspent {xPubUnspentPath = p, xPubUnspent = u}--data XPubSummary = XPubSummary- { xPubSummaryConfirmed :: !Word64,- xPubSummaryZero :: !Word64,- xPubSummaryReceived :: !Word64,- xPubUnspentCount :: !Word64,- xPubExternalIndex :: !Word32,- xPubChangeIndex :: !Word32- }- deriving (Eq, Show, Generic, NFData)--instance Serial XPubSummary where- serialize XPubSummary {..} = do- putWord64be xPubSummaryConfirmed- putWord64be xPubSummaryZero- putWord64be xPubSummaryReceived- putWord64be xPubUnspentCount- putWord32be xPubExternalIndex- putWord32be xPubChangeIndex- deserialize = do- xPubSummaryConfirmed <- getWord64be- xPubSummaryZero <- getWord64be- xPubSummaryReceived <- getWord64be- xPubUnspentCount <- getWord64be- xPubExternalIndex <- getWord32be- xPubChangeIndex <- getWord32be- return XPubSummary {..}--instance Binary XPubSummary where- put = serialize- get = deserialize--instance Serialize XPubSummary where- put = serialize- get = deserialize--instance ToJSON XPubSummary where- toJSON- XPubSummary- { xPubSummaryConfirmed = c,- xPubSummaryZero = z,- xPubSummaryReceived = r,- xPubUnspentCount = u,- xPubExternalIndex = ext,- xPubChangeIndex = ch- } =- A.object- [ "balance"- .= A.object- [ "confirmed" .= c,- "unconfirmed" .= z,- "received" .= r,- "utxo" .= u- ],- "indices"- .= A.object- [ "change" .= ch,- "external" .= ext- ]- ]- toEncoding- XPubSummary- { xPubSummaryConfirmed = c,- xPubSummaryZero = z,- xPubSummaryReceived = r,- xPubUnspentCount = u,- xPubExternalIndex = ext,- xPubChangeIndex = ch- } =- AE.pairs $- "balance"- `AE.pair` AE.pairs- ( "confirmed" .= c- <> "unconfirmed" .= z- <> "received" .= r- <> "utxo" .= u- )- <> "indices"- `AE.pair` AE.pairs- ( "change" .= ch- <> "external" .= ext- )--instance FromJSON XPubSummary where- parseJSON =- A.withObject "xpubsummary" $ \o -> do- b <- o .: "balance"- i <- o .: "indices"- conf <- b .: "confirmed"- unconfirmed <- b .: "unconfirmed"- received <- b .: "received"- utxo <- b .: "utxo"- change <- i .: "change"- external <- i .: "external"- return- XPubSummary- { xPubSummaryConfirmed = conf,- xPubSummaryZero = unconfirmed,- xPubSummaryReceived = received,- xPubUnspentCount = utxo,- xPubExternalIndex = external,- xPubChangeIndex = change- }--class Healthy a where- isOK :: a -> Bool--data BlockHealth = BlockHealth- { blockHealthHeaders :: !BlockHeight,- blockHealthBlocks :: !BlockHeight,- blockHealthMaxDiff :: !Int32- }- deriving (Show, Eq, Generic, NFData)--instance Serial BlockHealth where- serialize h@BlockHealth {..} = do- serialize (isOK h)- putWord32be blockHealthHeaders- putWord32be blockHealthBlocks- putInt32be blockHealthMaxDiff- deserialize = do- k <- deserialize- blockHealthHeaders <- getWord32be- blockHealthBlocks <- getWord32be- blockHealthMaxDiff <- getInt32be- let h = BlockHealth {..}- unless (k == isOK h) $ fail "Inconsistent health check"- return h--instance Serialize BlockHealth where- put = serialize- get = deserialize--instance Binary BlockHealth where- put = serialize- get = deserialize--instance Healthy BlockHealth where- isOK BlockHealth {..} =- h - b <= blockHealthMaxDiff- where- h = fromIntegral blockHealthHeaders- b = fromIntegral blockHealthBlocks--instance ToJSON BlockHealth where- toJSON h@BlockHealth {..} =- A.object- [ "headers" .= blockHealthHeaders,- "blocks" .= blockHealthBlocks,- "diff" .= diff,- "max" .= blockHealthMaxDiff,- "ok" .= isOK h- ]- where- diff = toInteger blockHealthHeaders - toInteger blockHealthBlocks--instance FromJSON BlockHealth where- parseJSON =- A.withObject "BlockHealth" $ \o -> do- blockHealthHeaders <- o .: "headers"- blockHealthBlocks <- o .: "blocks"- blockHealthMaxDiff <- o .: "max"- return BlockHealth {..}--data TimeHealth = TimeHealth- { timeHealthAge :: !Int64,- timeHealthMax :: !Int64- }- deriving (Show, Eq, Generic, NFData)--instance Serial TimeHealth where- serialize h@TimeHealth {..} = do- serialize (isOK h)- putInt64be timeHealthAge- putInt64be timeHealthMax- deserialize = do- k <- deserialize- timeHealthAge <- getInt64be- timeHealthMax <- getInt64be- let t = TimeHealth {..}- unless (k == isOK t) $ fail "Inconsistent health check"- return t--instance Binary TimeHealth where- put = serialize- get = deserialize--instance Serialize TimeHealth where- put = serialize- get = deserialize--instance Healthy TimeHealth where- isOK TimeHealth {..} =- timeHealthAge <= timeHealthMax--instance ToJSON TimeHealth where- toJSON h@TimeHealth {..} =- A.object- [ "age" .= timeHealthAge,- "max" .= timeHealthMax,- "ok" .= isOK h- ]--instance FromJSON TimeHealth where- parseJSON =- A.withObject "TimeHealth" $ \o -> do- timeHealthAge <- o .: "age"- timeHealthMax <- o .: "max"- return TimeHealth {..}--data CountHealth = CountHealth- { countHealthNum :: !Int64,- countHealthMin :: !Int64- }- deriving (Show, Eq, Generic, NFData)--instance Serial CountHealth where- serialize h@CountHealth {..} = do- serialize (isOK h)- putInt64be countHealthNum- putInt64be countHealthMin- deserialize = do- k <- deserialize- countHealthNum <- getInt64be- countHealthMin <- getInt64be- let c = CountHealth {..}- unless (k == isOK c) $ fail "Inconsistent health check"- return c--instance Serialize CountHealth where- put = serialize- get = deserialize--instance Binary CountHealth where- put = serialize- get = deserialize--instance Healthy CountHealth where- isOK CountHealth {..} =- countHealthMin <= countHealthNum--instance ToJSON CountHealth where- toJSON h@CountHealth {..} =- A.object- [ "count" .= countHealthNum,- "min" .= countHealthMin,- "ok" .= isOK h- ]--instance FromJSON CountHealth where- parseJSON =- A.withObject "CountHealth" $ \o -> do- countHealthNum <- o .: "count"- countHealthMin <- o .: "min"- return CountHealth {..}--data MaxHealth = MaxHealth- { maxHealthNum :: !Int64,- maxHealthMax :: !Int64- }- deriving (Show, Eq, Generic, NFData)--instance Serial MaxHealth where- serialize h@MaxHealth {..} = do- serialize $ isOK h- putInt64be maxHealthNum- putInt64be maxHealthMax- deserialize = do- k <- deserialize- maxHealthNum <- getInt64be- maxHealthMax <- getInt64be- let h = MaxHealth {..}- unless (k == isOK h) $ fail "Inconsistent health check"- return h--instance Binary MaxHealth where- put = serialize- get = deserialize--instance Serialize MaxHealth where- put = serialize- get = deserialize--instance Healthy MaxHealth where- isOK MaxHealth {..} = maxHealthNum <= maxHealthMax--instance ToJSON MaxHealth where- toJSON h@MaxHealth {..} =- A.object- [ "count" .= maxHealthNum,- "max" .= maxHealthMax,- "ok" .= isOK h- ]--instance FromJSON MaxHealth where- parseJSON =- A.withObject "MaxHealth" $ \o -> do- maxHealthNum <- o .: "count"- maxHealthMax <- o .: "max"- return MaxHealth {..}--data HealthCheck = HealthCheck- { healthBlocks :: !BlockHealth,- healthLastBlock :: !TimeHealth,- healthLastTx :: !TimeHealth,- healthPendingTxs :: !MaxHealth,- healthPeers :: !CountHealth,- healthNetwork :: !String,- healthVersion :: !String- }- deriving (Show, Eq, Generic, NFData)--instance Serial HealthCheck where- serialize h@HealthCheck {..} = do- serialize (isOK h)- serialize healthBlocks- serialize healthLastBlock- serialize healthLastTx- serialize healthPendingTxs- serialize healthPeers- putLengthBytes ((TE.encodeUtf8 . T.pack) healthNetwork)- putLengthBytes ((TE.encodeUtf8 . T.pack) healthVersion)- deserialize = do- k <- deserialize- healthBlocks <- deserialize- healthLastBlock <- deserialize- healthLastTx <- deserialize- healthPendingTxs <- deserialize- healthPeers <- deserialize- healthNetwork <- T.unpack . TE.decodeUtf8 <$> getLengthBytes- healthVersion <- T.unpack . TE.decodeUtf8 <$> getLengthBytes- let h = HealthCheck {..}- unless (k == isOK h) $ fail "Inconsistent health check"- return h--instance Binary HealthCheck where- put = serialize- get = deserialize--instance Serialize HealthCheck where- put = serialize- get = deserialize--instance Healthy HealthCheck where- isOK HealthCheck {..} =- isOK healthBlocks- && isOK healthLastBlock- && isOK healthLastTx- && isOK healthPendingTxs- && isOK healthPeers--instance ToJSON HealthCheck where- toJSON h@HealthCheck {..} =- A.object- [ "blocks" .= healthBlocks,- "last-block" .= healthLastBlock,- "last-tx" .= healthLastTx,- "pending-txs" .= healthPendingTxs,- "peers" .= healthPeers,- "net" .= healthNetwork,- "version" .= healthVersion,- "ok" .= isOK h- ]--instance FromJSON HealthCheck where- parseJSON =- A.withObject "HealthCheck" $ \o -> do- healthBlocks <- o .: "blocks"- healthLastBlock <- o .: "last-block"- healthLastTx <- o .: "last-tx"- healthPendingTxs <- o .: "pending-txs"- healthPeers <- o .: "peers"- healthNetwork <- o .: "net"- healthVersion <- o .: "version"- return HealthCheck {..}--data Event- = EventBlock !BlockHash- | EventTx !TxHash- deriving (Show, Eq, Generic, NFData)--instance Serial Event where- serialize (EventBlock bh) = putWord8 0x00 >> serialize bh- serialize (EventTx th) = putWord8 0x01 >> serialize th- deserialize =- getWord8 >>= \case- 0x00 -> EventBlock <$> deserialize- 0x01 -> EventTx <$> deserialize- _ -> fail "Not an Event"--instance Serialize Event where- put = serialize- get = deserialize--instance Binary Event where- put = serialize- get = deserialize--instance ToJSON Event where- toJSON (EventTx h) =- A.object- [ "type" .= String "tx",- "id" .= h- ]- toJSON (EventBlock h) =- A.object- [ "type" .= String "block",- "id" .= h- ]- toEncoding (EventTx h) =- AE.pairs $- "type" `AE.pair` AE.text "tx"- <> "id" `AE.pair` AE.text (txHashToHex h)- toEncoding (EventBlock h) =- AE.pairs $- "type" `AE.pair` AE.text "block"- <> "id" `AE.pair` AE.text (blockHashToHex h)--instance FromJSON Event where- parseJSON =- A.withObject "event" $ \o -> do- t <- o .: "type"- case t of- "tx" -> do- i <- o .: "id"- return $ EventTx i- "block" -> do- i <- o .: "id"- return $ EventBlock i- _ -> fail $ "Could not recognize event type: " <> t--newtype GenericResult a = GenericResult- { getResult :: a- }- deriving (Show, Eq, Generic, NFData)--instance Serial a => Serial (GenericResult a) where- serialize (GenericResult x) = serialize x- deserialize = GenericResult <$> deserialize--instance Serial a => Serialize (GenericResult a) where- put = serialize- get = deserialize--instance Serial a => Binary (GenericResult a) where- put = serialize- get = deserialize--instance ToJSON a => ToJSON (GenericResult a) where- toJSON (GenericResult b) = A.object ["result" .= b]- toEncoding (GenericResult b) = AE.pairs ("result" .= b)--instance FromJSON a => FromJSON (GenericResult a) where- parseJSON =- A.withObject "GenericResult" $ \o ->- GenericResult <$> o .: "result"--newtype RawResult a = RawResult- { getRawResult :: a- }- deriving (Show, Eq, Generic, NFData)--instance Serial a => Serial (RawResult a) where- serialize (RawResult x) = serialize x- deserialize = RawResult <$> deserialize--instance Serial a => Serialize (RawResult a) where- put = serialize- get = deserialize--instance Serial a => Binary (RawResult a) where- put = serialize- get = deserialize--instance Serial a => ToJSON (RawResult a) where- toJSON (RawResult b) =- A.object ["result" .= x b]- where- x =- TLE.decodeUtf8- . BSB.toLazyByteString- . BSB.lazyByteStringHex- . runPutL- . serialize- toEncoding (RawResult b) =- AE.pairs $ "result" `AE.pair` AE.unsafeToEncoding str- where- str =- BSB.char7 '"'- <> BSB.lazyByteStringHex (runPutL (serialize b))- <> BSB.char7 '"'--instance Serial a => FromJSON (RawResult a) where- parseJSON =- A.withObject "RawResult" $ \o -> do- res <- o .: "result"- let m =- eitherToMaybe . Bytes.Get.runGetS deserialize- =<< decodeHex res- maybe mzero (return . RawResult) m--newtype SerialList a = SerialList {getSerialList :: [a]}- deriving (Show, Eq, Generic, NFData)--instance Semigroup (SerialList a) where- SerialList a <> SerialList b = SerialList (a <> b)--instance Monoid (SerialList a) where- mempty = SerialList mempty--instance Serial a => Serial (SerialList a) where- serialize (SerialList ls) = putList serialize ls- deserialize = SerialList <$> getList deserialize--instance ToJSON a => ToJSON (SerialList a) where- toJSON (SerialList ls) = toJSON ls- toEncoding (SerialList ls) = toEncoding ls--instance FromJSON a => FromJSON (SerialList a) where- parseJSON = fmap SerialList . parseJSON--newtype RawResultList a = RawResultList- { getRawResultList :: [a]- }- deriving (Show, Eq, Generic, NFData)--instance Serial a => Serial (RawResultList a) where- serialize (RawResultList xs) =- mapM_ serialize xs- deserialize = RawResultList <$> go- where- go =- isEmpty >>= \case- True -> return []- False -> (:) <$> deserialize <*> go--instance Serial a => Serialize (RawResultList a) where- put = serialize- get = deserialize--instance Serial a => Binary (RawResultList a) where- put = serialize- get = deserialize--instance Semigroup (RawResultList a) where- (RawResultList a) <> (RawResultList b) = RawResultList $ a <> b--instance Monoid (RawResultList a) where- mempty = RawResultList mempty--instance Serial a => ToJSON (RawResultList a) where- toJSON (RawResultList xs) =- toJSON $- TLE.decodeUtf8- . BSB.toLazyByteString- . BSB.lazyByteStringHex- . runPutL- . serialize- <$> xs- toEncoding (RawResultList xs) =- AE.list (AE.unsafeToEncoding . str) xs- where- str x =- BSB.char7 '"'- <> BSB.lazyByteStringHex (runPutL (serialize x))- <> BSB.char7 '"'--instance Serial a => FromJSON (RawResultList a) where- parseJSON =- A.withArray "RawResultList" $ \vec ->- RawResultList <$> mapM parseElem (toList vec)- where- parseElem = A.withText "RawResultListElem" $ maybe mzero return . f- f = eitherToMaybe . Bytes.Get.runGetS deserialize <=< decodeHex--newtype TxId- = TxId TxHash- deriving (Show, Eq, Generic, NFData)--instance Serial TxId where- serialize (TxId h) = serialize h- deserialize = TxId <$> deserialize--instance Serialize TxId where- put = serialize- get = deserialize--instance Binary TxId where- put = serialize- get = deserialize--instance ToJSON TxId where- toJSON (TxId h) = A.object ["txid" .= h]- toEncoding (TxId h) = AE.pairs ("txid" `AE.pair` AE.text (txHashToHex h))--instance FromJSON TxId where- parseJSON = A.withObject "txid" $ \o -> TxId <$> o .: "txid"--data Except- = ThingNotFound- | ServerError- | BadRequest- | UserError !String- | StringError !String- | TxIndexConflict ![TxHash]- | ServerTimeout- | RequestTooLarge- deriving (Show, Eq, Ord, Generic, NFData)--instance Exception Except--instance ScottyError Except where- stringError = StringError- showError = TL.pack . show--instance ToJSON Except where- toJSON e =- A.object $- case e of- ThingNotFound ->- [ "error" .= String "not-found-or-invalid-arg",- "message" .= String "Item not found or argument invalid"- ]- ServerError ->- [ "error" .= String "server-error",- "message" .= String "Server error"- ]- BadRequest ->- [ "error" .= String "bad-request",- "message" .= String "Invalid request"- ]- UserError msg' ->- [ "error" .= String "user-error",- "message" .= String (cs msg')- ]- StringError msg' ->- [ "error" .= String "string-error",- "message" .= String (cs msg')- ]- TxIndexConflict txids ->- [ "error" .= String "multiple-tx-index",- "message" .= String "Multiple txs match that index",- "txids" .= txids- ]- ServerTimeout ->- [ "error" .= String "server-timeout",- "message" .= String "Request is taking too long"- ]- RequestTooLarge ->- [ "error" .= String "request-too-large",- "message" .= String "Request body too large"- ]--instance FromJSON Except where- parseJSON =- A.withObject "Except" $ \o -> do- ctr <- o .: "error"- msg' <- o .:? "message" .!= ""- case ctr of- String "not-found-or-invalid-arg" ->- return ThingNotFound- String "server-error" ->- return ServerError- String "bad-request" ->- return BadRequest- String "user-error" ->- return $ UserError msg'- String "string-error" ->- return $ StringError msg'- String "multiple-tx-index" -> do- txids <- o .: "txids"- return $ TxIndexConflict txids- String "server-timeout" ->- return ServerTimeout- String "request-too-large" ->- return RequestTooLarge- _ -> mzero--toIntTxId :: TxHash -> Word64-toIntTxId h =- let bs = runPutS (serialize h)- Right w64 = runGetS getWord64be bs- in w64 `shift` (-11)-------------------------------------------- Blockchain.info API Compatibility --------------------------------------------data BinfoBlockId- = BinfoBlockHash !BlockHash- | BinfoBlockIndex !Word32- deriving (Eq, Show, Read, Generic, NFData)--instance Parsable BinfoBlockId where- parseParam t =- hex <> igr- where- hex = case hexToBlockHash (TL.toStrict t) of- Nothing -> Left "could not decode txid"- Just h -> Right $ BinfoBlockHash h- igr = BinfoBlockIndex <$> parseParam t--data BinfoTxId- = BinfoTxIdHash !TxHash- | BinfoTxIdIndex !Word64- deriving (Eq, Show, Read, Generic, NFData)--encodeBinfoTxId :: Bool -> TxHash -> BinfoTxId-encodeBinfoTxId False = BinfoTxIdHash-encodeBinfoTxId True = BinfoTxIdIndex . toIntTxId--instance Parsable BinfoTxId where- parseParam t =- hex <> igr- where- hex =- case hexToTxHash (TL.toStrict t) of- Nothing -> Left "could not decode txid"- Just h -> Right $ BinfoTxIdHash h- igr = BinfoTxIdIndex <$> parseParam t--instance ToJSON BinfoTxId where- toJSON (BinfoTxIdHash h) = toJSON h- toJSON (BinfoTxIdIndex i) = toJSON i- toEncoding (BinfoTxIdHash h) = toEncoding h- toEncoding (BinfoTxIdIndex i) = toEncoding i--instance FromJSON BinfoTxId where- parseJSON v =- BinfoTxIdHash <$> parseJSON v- <|> BinfoTxIdIndex <$> parseJSON v--data BinfoFilter- = BinfoFilterAll- | BinfoFilterSent- | BinfoFilterReceived- | BinfoFilterMoved- | BinfoFilterConfirmed- | BinfoFilterMempool- deriving (Eq, Show, Generic, NFData)--instance Parsable BinfoFilter where- parseParam t =- parseParam t >>= \case- (0 :: Int) -> return BinfoFilterAll- 1 -> return BinfoFilterSent- 2 -> return BinfoFilterReceived- 3 -> return BinfoFilterMoved- 5 -> return BinfoFilterConfirmed- 6 -> return BinfoFilterAll- 7 -> return BinfoFilterMempool- _ -> Left "could not parse filter parameter"--data BinfoMultiAddr = BinfoMultiAddr- { getBinfoMultiAddrAddresses :: ![BinfoBalance],- getBinfoMultiAddrWallet :: !BinfoWallet,- getBinfoMultiAddrTxs :: ![BinfoTx],- getBinfoMultiAddrInfo :: !BinfoInfo,- getBinfoMultiAddrRecommendFee :: !Bool,- getBinfoMultiAddrCashAddr :: !Bool- }- deriving (Eq, Show, Generic, NFData)--binfoMultiAddrToJSON :: Network -> BinfoMultiAddr -> Value-binfoMultiAddrToJSON net' BinfoMultiAddr {..} =- A.object $- [ "addresses" .= map (binfoBalanceToJSON net) getBinfoMultiAddrAddresses,- "wallet" .= getBinfoMultiAddrWallet,- "txs" .= map (binfoTxToJSON net) getBinfoMultiAddrTxs,- "info" .= getBinfoMultiAddrInfo,- "recommend_include_fee" .= getBinfoMultiAddrRecommendFee- ]- ++ ["cash_addr" .= True | getBinfoMultiAddrCashAddr]- where- net = if not getBinfoMultiAddrCashAddr && net' == bch then btc else net'--binfoMultiAddrParseJSON :: Network -> Value -> Parser BinfoMultiAddr-binfoMultiAddrParseJSON net = A.withObject "multiaddr" $ \o -> do- getBinfoMultiAddrAddresses <-- mapM (binfoBalanceParseJSON net) =<< o .: "addresses"- getBinfoMultiAddrWallet <- o .: "wallet"- getBinfoMultiAddrTxs <-- mapM (binfoTxParseJSON net) =<< o .: "txs"- getBinfoMultiAddrInfo <- o .: "info"- getBinfoMultiAddrRecommendFee <- o .: "recommend_include_fee"- getBinfoMultiAddrCashAddr <- o .:? "cash_addr" .!= False- return BinfoMultiAddr {..}--binfoMultiAddrToEncoding :: Network -> BinfoMultiAddr -> Encoding-binfoMultiAddrToEncoding net' BinfoMultiAddr {..} =- AE.pairs- ( "addresses" `AE.pair` as- <> "wallet" .= getBinfoMultiAddrWallet- <> "txs" `AE.pair` ts- <> "info" .= getBinfoMultiAddrInfo- <> "recommend_include_fee" .= getBinfoMultiAddrRecommendFee- <> if getBinfoMultiAddrCashAddr then "cash_addr" .= True else mempty- )- where- as = AE.list (binfoBalanceToEncoding net) getBinfoMultiAddrAddresses- ts = AE.list (binfoTxToEncoding net) getBinfoMultiAddrTxs- net = if not getBinfoMultiAddrCashAddr && net' == bch then btc else net'--data BinfoRawAddr = BinfoRawAddr- { binfoRawAddr :: !BinfoAddr,- binfoRawBalance :: !Word64,- binfoRawTxCount :: !Word64,- binfoRawUnredeemed :: !Word64,- binfoRawReceived :: !Word64,- binfoRawSent :: !Int64,- binfoRawTxs :: ![BinfoTx]- }- deriving (Eq, Show, Generic, NFData)--binfoRawAddrToJSON :: Network -> BinfoRawAddr -> Value-binfoRawAddrToJSON net BinfoRawAddr {..} =- A.object- [ "hash160" .= h160,- "address" .= address,- "n_tx" .= binfoRawTxCount,- "n_unredeemed" .= binfoRawUnredeemed,- "total_received" .= binfoRawReceived,- "total_sent" .= binfoRawSent,- "final_balance" .= binfoRawBalance,- "txs" .= map (binfoTxToJSON net) binfoRawTxs- ]- where- address = case binfoRawAddr of- BinfoAddr a -> addrToJSON net a- BinfoXpub x -> xPubToJSON net x- h160 =- encodeHex . runPutS . serialize- <$> case binfoRawAddr of- BinfoAddr a -> case a of- PubKeyAddress h -> Just h- ScriptAddress h -> Just h- WitnessPubKeyAddress h -> Just h- _ -> Nothing- _ -> Nothing--binfoRawAddrToEncoding :: Network -> BinfoRawAddr -> Encoding-binfoRawAddrToEncoding net BinfoRawAddr {..} =- AE.pairs $- "hash160" .= h160- <> "address" `AE.pair` address- <> "n_tx" .= binfoRawTxCount- <> "n_unredeemed" .= binfoRawUnredeemed- <> "total_received" .= binfoRawReceived- <> "total_sent" .= binfoRawSent- <> "final_balance" .= binfoRawBalance- <> "txs" `AE.pair` AE.list (binfoTxToEncoding net) binfoRawTxs- where- address = case binfoRawAddr of- BinfoAddr a -> addrToEncoding net a- BinfoXpub x -> xPubToEncoding net x- h160 =- encodeHex . runPutS . serialize- <$> case binfoRawAddr of- BinfoAddr a -> case a of- PubKeyAddress h -> Just h- ScriptAddress h -> Just h- WitnessPubKeyAddress h -> Just h- _ -> Nothing- _ -> Nothing--binfoRawAddrParseJSON :: Network -> Value -> Parser BinfoRawAddr-binfoRawAddrParseJSON net = A.withObject "balancetxs" $ \o -> do- addr <- o .: "address"- binfoRawAddr <-- BinfoAddr <$> addrFromJSON net addr- <|> BinfoXpub <$> xPubFromJSON net addr- binfoRawBalance <- o .: "final_balance"- binfoRawUnredeemed <- o .: "n_unredeemed"- binfoRawTxCount <- o .: "n_tx"- binfoRawReceived <- o .: "total_received"- binfoRawSent <- o .: "total_sent"- binfoRawTxs <- mapM (binfoTxParseJSON net) =<< o .: "txs"- return BinfoRawAddr {..}--data BinfoShortBal = BinfoShortBal- { binfoShortBalFinal :: !Word64,- binfoShortBalTxCount :: !Word64,- binfoShortBalReceived :: !Word64- }- deriving (Eq, Show, Read, Generic, NFData)--instance ToJSON BinfoShortBal where- toJSON BinfoShortBal {..} =- A.object- [ "final_balance" .= binfoShortBalFinal,- "n_tx" .= binfoShortBalTxCount,- "total_received" .= binfoShortBalReceived- ]- toEncoding BinfoShortBal {..} =- AE.pairs $- "final_balance" .= binfoShortBalFinal- <> "n_tx" .= binfoShortBalTxCount- <> "total_received" .= binfoShortBalReceived--instance FromJSON BinfoShortBal where- parseJSON = A.withObject "short_balance" $ \o -> do- binfoShortBalFinal <- o .: "final_balance"- binfoShortBalTxCount <- o .: "n_tx"- binfoShortBalReceived <- o .: "total_received"- return BinfoShortBal {..}--data BinfoBalance- = BinfoAddrBalance- { getBinfoAddress :: !Address,- getBinfoAddrTxCount :: !Word64,- getBinfoAddrReceived :: !Word64,- getBinfoAddrSent :: !Word64,- getBinfoAddrBalance :: !Word64- }- | BinfoXPubBalance- { getBinfoXPubKey :: !XPubKey,- getBinfoAddrTxCount :: !Word64,- getBinfoAddrReceived :: !Word64,- getBinfoAddrSent :: !Word64,- getBinfoAddrBalance :: !Word64,- getBinfoXPubAccountIndex :: !Word32,- getBinfoXPubChangeIndex :: !Word32- }- deriving (Eq, Show, Generic, NFData)--binfoBalanceToJSON :: Network -> BinfoBalance -> Value-binfoBalanceToJSON net BinfoAddrBalance {..} =- A.object- [ "address" .= addrToJSON net getBinfoAddress,- "final_balance" .= getBinfoAddrBalance,- "n_tx" .= getBinfoAddrTxCount,- "total_received" .= getBinfoAddrReceived,- "total_sent" .= getBinfoAddrSent- ]-binfoBalanceToJSON net BinfoXPubBalance {..} =- A.object- [ "address" .= xPubToJSON net getBinfoXPubKey,- "change_index" .= getBinfoXPubChangeIndex,- "account_index" .= getBinfoXPubAccountIndex,- "final_balance" .= getBinfoAddrBalance,- "n_tx" .= getBinfoAddrTxCount,- "total_received" .= getBinfoAddrReceived,- "total_sent" .= getBinfoAddrSent- ]--binfoBalanceParseJSON :: Network -> Value -> Parser BinfoBalance-binfoBalanceParseJSON net = A.withObject "address" $ \o -> x o <|> a o- where- x o = do- getBinfoXPubKey <- xPubFromJSON net =<< o .: "address"- getBinfoXPubChangeIndex <- o .: "change_index"- getBinfoXPubAccountIndex <- o .: "account_index"- getBinfoAddrBalance <- o .: "final_balance"- getBinfoAddrTxCount <- o .: "n_tx"- getBinfoAddrReceived <- o .: "total_received"- getBinfoAddrSent <- o .: "total_sent"- return BinfoXPubBalance {..}- a o = do- getBinfoAddress <- addrFromJSON net =<< o .: "address"- getBinfoAddrBalance <- o .: "final_balance"- getBinfoAddrTxCount <- o .: "n_tx"- getBinfoAddrReceived <- o .: "total_received"- getBinfoAddrSent <- o .: "total_sent"- return BinfoAddrBalance {..}--binfoBalanceToEncoding :: Network -> BinfoBalance -> Encoding-binfoBalanceToEncoding net BinfoAddrBalance {..} =- AE.pairs- ( "address" `AE.pair` addrToEncoding net getBinfoAddress- <> "final_balance" .= getBinfoAddrBalance- <> "n_tx" .= getBinfoAddrTxCount- <> "total_received" .= getBinfoAddrReceived- <> "total_sent" .= getBinfoAddrSent- )-binfoBalanceToEncoding net BinfoXPubBalance {..} =- AE.pairs- ( "address" `AE.pair` xPubToEncoding net getBinfoXPubKey- <> "change_index" .= getBinfoXPubChangeIndex- <> "account_index" .= getBinfoXPubAccountIndex- <> "final_balance" .= getBinfoAddrBalance- <> "n_tx" .= getBinfoAddrTxCount- <> "total_received" .= getBinfoAddrReceived- <> "total_sent" .= getBinfoAddrSent- )--data BinfoWallet = BinfoWallet- { getBinfoWalletBalance :: !Word64,- getBinfoWalletTxCount :: !Word64,- getBinfoWalletFilteredCount :: !Word64,- getBinfoWalletTotalReceived :: !Word64,- getBinfoWalletTotalSent :: !Word64- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoWallet where- toJSON BinfoWallet {..} =- A.object- [ "final_balance" .= getBinfoWalletBalance,- "n_tx" .= getBinfoWalletTxCount,- "n_tx_filtered" .= getBinfoWalletFilteredCount,- "total_received" .= getBinfoWalletTotalReceived,- "total_sent" .= getBinfoWalletTotalSent- ]- toEncoding BinfoWallet {..} =- AE.pairs- ( "final_balance" .= getBinfoWalletBalance- <> "n_tx" .= getBinfoWalletTxCount- <> "n_tx_filtered" .= getBinfoWalletFilteredCount- <> "total_received" .= getBinfoWalletTotalReceived- <> "total_sent" .= getBinfoWalletTotalSent- )--instance FromJSON BinfoWallet where- parseJSON = A.withObject "wallet" $ \o -> do- getBinfoWalletBalance <- o .: "final_balance"- getBinfoWalletTxCount <- o .: "n_tx"- getBinfoWalletFilteredCount <- o .: "n_tx_filtered"- getBinfoWalletTotalReceived <- o .: "total_received"- getBinfoWalletTotalSent <- o .: "total_sent"- return BinfoWallet {..}--binfoHexValue :: Word64 -> Text-binfoHexValue w64 =- let bs = BS.dropWhile (== 0x00) (runPutS (serialize w64))- in encodeHex $- if BS.null bs || BS.head bs `testBit` 7- then BS.cons 0x00 bs- else bs--data BinfoUnspent = BinfoUnspent- { getBinfoUnspentHash :: !TxHash,- getBinfoUnspentOutputIndex :: !Word32,- getBinfoUnspentScript :: !ByteString,- getBinfoUnspentValue :: !Word64,- getBinfoUnspentConfirmations :: !Int32,- getBinfoUnspentTxIndex :: !BinfoTxId,- getBinfoUnspentXPub :: !(Maybe BinfoXPubPath)- }- deriving (Eq, Show, Generic, NFData)--binfoUnspentToJSON :: Network -> BinfoUnspent -> Value-binfoUnspentToJSON net BinfoUnspent {..} =- A.object $- [ "tx_hash_big_endian" .= getBinfoUnspentHash,- "tx_hash"- .= encodeHex (runPutS (serialize (getTxHash getBinfoUnspentHash))),- "tx_output_n" .= getBinfoUnspentOutputIndex,- "script" .= encodeHex getBinfoUnspentScript,- "value" .= getBinfoUnspentValue,- "value_hex" .= binfoHexValue getBinfoUnspentValue,- "confirmations" .= getBinfoUnspentConfirmations,- "tx_index" .= getBinfoUnspentTxIndex- ]- <> [ "xpub" .= binfoXPubPathToJSON net x- | x <- maybeToList getBinfoUnspentXPub- ]--binfoUnspentToEncoding :: Network -> BinfoUnspent -> Encoding-binfoUnspentToEncoding net BinfoUnspent {..} =- AE.pairs $- "tx_hash_big_endian" .= getBinfoUnspentHash- <> "tx_hash"- .= encodeHex (runPutS (serialize (getTxHash getBinfoUnspentHash)))- <> "tx_output_n"- .= getBinfoUnspentOutputIndex- <> "script"- .= encodeHex getBinfoUnspentScript- <> "value"- .= getBinfoUnspentValue- <> "value_hex"- .= binfoHexValue getBinfoUnspentValue- <> "confirmations"- .= getBinfoUnspentConfirmations- <> "tx_index"- .= getBinfoUnspentTxIndex- <> maybe- mempty- (("xpub" `AE.pair`) . binfoXPubPathToEncoding net)- getBinfoUnspentXPub--binfoUnspentParseJSON :: Network -> Value -> Parser BinfoUnspent-binfoUnspentParseJSON net = A.withObject "unspent" $ \o -> do- getBinfoUnspentHash <- o .: "tx_hash_big_endian"- getBinfoUnspentOutputIndex <- o .: "tx_output_n"- getBinfoUnspentScript <- maybe mzero return . decodeHex =<< o .: "script"- getBinfoUnspentValue <- o .: "value"- getBinfoUnspentConfirmations <- o .: "confirmations"- getBinfoUnspentTxIndex <- o .: "tx_index"- getBinfoUnspentXPub <- mapM (binfoXPubPathParseJSON net) =<< o .:? "xpub"- return BinfoUnspent {..}--newtype BinfoUnspents = BinfoUnspents [BinfoUnspent]- deriving (Eq, Show, Generic, NFData)--binfoUnspentsToJSON :: Network -> BinfoUnspents -> Value-binfoUnspentsToJSON net (BinfoUnspents us) =- A.object- [ "notice" .= T.empty,- "unspent_outputs" .= map (binfoUnspentToJSON net) us- ]--binfoUnspentsToEncoding :: Network -> BinfoUnspents -> Encoding-binfoUnspentsToEncoding net (BinfoUnspents us) =- AE.pairs $- "notice" .= T.empty- <> "unspent_outputs" `AE.pair` AE.list (binfoUnspentToEncoding net) us--binfoUnspentsParseJSON :: Network -> Value -> Parser BinfoUnspents-binfoUnspentsParseJSON net = A.withObject "unspents" $ \o -> do- us <- mapM (binfoUnspentParseJSON net) =<< o .: "unspent_outputs"- return (BinfoUnspents us)--binfoBlocksToJSON :: Network -> [BinfoBlock] -> Value-binfoBlocksToJSON net blocks =- A.object ["blocks" .= map (binfoBlockToJSON net) blocks]--binfoBlocksToEncoding :: Network -> [BinfoBlock] -> Encoding-binfoBlocksToEncoding net blocks =- AE.pairs $ "blocks" `AE.pair` AE.list (binfoBlockToEncoding net) blocks--binfoBlocksParseJSON :: Network -> Value -> Parser [BinfoBlock]-binfoBlocksParseJSON net =- A.withObject "blocks" $ \o ->- mapM (binfoBlockParseJSON net) =<< o .: "blocks"--toBinfoBlock :: BlockData -> [BinfoTx] -> [BlockHash] -> BinfoBlock-toBinfoBlock BlockData {..} txs next_blocks =- BinfoBlock- { getBinfoBlockHash = headerHash blockDataHeader,- getBinfoBlockVer = blockVersion blockDataHeader,- getBinfoPrevBlock = prevBlock blockDataHeader,- getBinfoMerkleRoot = merkleRoot blockDataHeader,- getBinfoBlockTime = blockTimestamp blockDataHeader,- getBinfoBlockBits = blockBits blockDataHeader,- getBinfoNextBlock = next_blocks,- getBinfoBlockFee = blockDataFees,- getBinfoBlockNonce = bhNonce blockDataHeader,- getBinfoBlockTxCount = fromIntegral (length txs),- getBinfoBlockSize = blockDataSize,- getBinfoBlockIndex = blockDataHeight,- getBinfoBlockMain = blockDataMainChain,- getBinfoBlockHeight = blockDataHeight,- getBinfoBlockWeight = blockDataWeight,- getBinfoBlockTx = txs- }--data BinfoBlock = BinfoBlock- { getBinfoBlockHash :: !BlockHash,- getBinfoBlockVer :: !Word32,- getBinfoPrevBlock :: !BlockHash,- getBinfoMerkleRoot :: !Hash256,- getBinfoBlockTime :: !Word32,- getBinfoBlockBits :: !Word32,- getBinfoNextBlock :: ![BlockHash],- getBinfoBlockFee :: !Word64,- getBinfoBlockNonce :: !Word32,- getBinfoBlockTxCount :: !Word32,- getBinfoBlockSize :: !Word32,- getBinfoBlockIndex :: !Word32,- getBinfoBlockMain :: !Bool,- getBinfoBlockHeight :: !Word32,- getBinfoBlockWeight :: !Word32,- getBinfoBlockTx :: ![BinfoTx]- }- deriving (Eq, Show, Generic, NFData)--binfoBlockToJSON :: Network -> BinfoBlock -> Value-binfoBlockToJSON net BinfoBlock {..} =- A.object- [ "hash" .= getBinfoBlockHash,- "ver" .= getBinfoBlockVer,- "prev_block" .= getBinfoPrevBlock,- "mrkl_root" .= TxHash getBinfoMerkleRoot,- "time" .= getBinfoBlockTime,- "bits" .= getBinfoBlockBits,- "next_block" .= getBinfoNextBlock,- "fee" .= getBinfoBlockFee,- "nonce" .= getBinfoBlockNonce,- "n_tx" .= getBinfoBlockTxCount,- "size" .= getBinfoBlockSize,- "block_index" .= getBinfoBlockIndex,- "main_chain" .= getBinfoBlockMain,- "height" .= getBinfoBlockHeight,- "weight" .= getBinfoBlockWeight,- "tx" .= map (binfoTxToJSON net) getBinfoBlockTx- ]--binfoBlockToEncoding :: Network -> BinfoBlock -> Encoding-binfoBlockToEncoding net BinfoBlock {..} =- AE.pairs $- "hash" .= getBinfoBlockHash- <> "ver" .= getBinfoBlockVer- <> "prev_block" .= getBinfoPrevBlock- <> "mrkl_root" .= TxHash getBinfoMerkleRoot- <> "time" .= getBinfoBlockTime- <> "bits" .= getBinfoBlockBits- <> "next_block" .= getBinfoNextBlock- <> "fee" .= getBinfoBlockFee- <> "nonce" .= getBinfoBlockNonce- <> "n_tx" .= getBinfoBlockTxCount- <> "size" .= getBinfoBlockSize- <> "block_index" .= getBinfoBlockIndex- <> "main_chain" .= getBinfoBlockMain- <> "height" .= getBinfoBlockHeight- <> "weight" .= getBinfoBlockWeight- <> "tx" `AE.pair` AE.list (binfoTxToEncoding net) getBinfoBlockTx--binfoBlockParseJSON :: Network -> Value -> Parser BinfoBlock-binfoBlockParseJSON net = A.withObject "block" $ \o -> do- getBinfoBlockHash <- o .: "hash"- getBinfoBlockVer <- o .: "ver"- getBinfoPrevBlock <- o .: "prev_block"- getBinfoMerkleRoot <- getTxHash <$> o .: "mrkl_root"- getBinfoBlockTime <- o .: "time"- getBinfoBlockBits <- o .: "bits"- getBinfoNextBlock <- o .: "next_block"- getBinfoBlockFee <- o .: "fee"- getBinfoBlockNonce <- o .: "nonce"- getBinfoBlockTxCount <- o .: "n_tx"- getBinfoBlockSize <- o .: "size"- getBinfoBlockIndex <- o .: "block_index"- getBinfoBlockMain <- o .: "main_chain"- getBinfoBlockHeight <- o .: "height"- getBinfoBlockWeight <- o .: "weight"- getBinfoBlockTx <- o .: "tx" >>= mapM (binfoTxParseJSON net)- return BinfoBlock {..}--data BinfoTx = BinfoTx- { getBinfoTxHash :: !TxHash,- getBinfoTxVer :: !Word32,- getBinfoTxVinSz :: !Word32,- getBinfoTxVoutSz :: !Word32,- getBinfoTxSize :: !Word32,- getBinfoTxWeight :: !Word32,- getBinfoTxFee :: !Word64,- getBinfoTxRelayedBy :: !ByteString,- getBinfoTxLockTime :: !Word32,- getBinfoTxIndex :: !BinfoTxId,- getBinfoTxDoubleSpend :: !Bool,- getBinfoTxRBF :: !Bool,- getBinfoTxResultBal :: !(Maybe (Int64, Int64)),- getBinfoTxTime :: !Word64,- getBinfoTxBlockIndex :: !(Maybe Word32),- getBinfoTxBlockHeight :: !(Maybe Word32),- getBinfoTxInputs :: [BinfoTxInput],- getBinfoTxOutputs :: [BinfoTxOutput]- }- deriving (Eq, Show, Generic, NFData)--binfoTxToJSON :: Network -> BinfoTx -> Value-binfoTxToJSON net BinfoTx {..} =- A.object $- [ "hash" .= getBinfoTxHash,- "ver" .= getBinfoTxVer,- "vin_sz" .= getBinfoTxVinSz,- "vout_sz" .= getBinfoTxVoutSz,- "size" .= getBinfoTxSize,- "weight" .= getBinfoTxWeight,- "fee" .= getBinfoTxFee,- "relayed_by" .= TE.decodeUtf8 getBinfoTxRelayedBy,- "lock_time" .= getBinfoTxLockTime,- "tx_index" .= getBinfoTxIndex,- "double_spend" .= getBinfoTxDoubleSpend,- "time" .= getBinfoTxTime,- "block_index" .= getBinfoTxBlockIndex,- "block_height" .= getBinfoTxBlockHeight,- "inputs" .= map (binfoTxInputToJSON net) getBinfoTxInputs,- "out" .= map (binfoTxOutputToJSON net) getBinfoTxOutputs- ]- ++ bal- ++ rbf- where- bal =- case getBinfoTxResultBal of- Nothing -> []- Just (res, bal') -> ["result" .= res, "balance" .= bal']- rbf = ["rbf" .= True | getBinfoTxRBF]--binfoTxToEncoding :: Network -> BinfoTx -> Encoding-binfoTxToEncoding net BinfoTx {..} =- AE.pairs $- "hash" .= getBinfoTxHash- <> "ver" .= getBinfoTxVer- <> "vin_sz" .= getBinfoTxVinSz- <> "vout_sz" .= getBinfoTxVoutSz- <> "size" .= getBinfoTxSize- <> "weight" .= getBinfoTxWeight- <> "fee" .= getBinfoTxFee- <> "relayed_by" .= TE.decodeUtf8 getBinfoTxRelayedBy- <> "lock_time" .= getBinfoTxLockTime- <> "tx_index" .= getBinfoTxIndex- <> "double_spend" .= getBinfoTxDoubleSpend- <> "time" .= getBinfoTxTime- <> "block_index" .= getBinfoTxBlockIndex- <> "block_height" .= getBinfoTxBlockHeight- <> "inputs" `AE.pair` AE.list (binfoTxInputToEncoding net) getBinfoTxInputs- <> "out" `AE.pair` AE.list (binfoTxOutputToEncoding net) getBinfoTxOutputs- <> bal- <> rbf- where- bal =- case getBinfoTxResultBal of- Nothing -> mempty- Just (res, bal') -> "result" .= res <> "balance" .= bal'- rbf = if getBinfoTxRBF then "rbf" .= True else mempty--binfoTxParseJSON :: Network -> Value -> Parser BinfoTx-binfoTxParseJSON net = A.withObject "tx" $ \o -> do- getBinfoTxHash <- o .: "hash"- getBinfoTxVer <- o .: "ver"- getBinfoTxVinSz <- o .: "vin_sz"- getBinfoTxVoutSz <- o .: "vout_sz"- getBinfoTxSize <- o .: "size"- getBinfoTxWeight <- o .: "weight"- getBinfoTxFee <- o .: "fee"- getBinfoTxRelayedBy <- TE.encodeUtf8 <$> o .: "relayed_by"- getBinfoTxLockTime <- o .: "lock_time"- getBinfoTxIndex <- o .: "tx_index"- getBinfoTxDoubleSpend <- o .: "double_spend"- getBinfoTxTime <- o .: "time"- getBinfoTxBlockIndex <- o .: "block_index"- getBinfoTxBlockHeight <- o .: "block_height"- getBinfoTxInputs <- o .: "inputs" >>= mapM (binfoTxInputParseJSON net)- getBinfoTxOutputs <- o .: "out" >>= mapM (binfoTxOutputParseJSON net)- getBinfoTxRBF <- o .:? "rbf" .!= False- res <- o .:? "result"- bal <- o .:? "balance"- let getBinfoTxResultBal = (,) <$> res <*> bal- return BinfoTx {..}--data BinfoTxInput = BinfoTxInput- { getBinfoTxInputSeq :: !Word32,- getBinfoTxInputWitness :: !ByteString,- getBinfoTxInputScript :: !ByteString,- getBinfoTxInputIndex :: !Word32,- getBinfoTxInputPrevOut :: !BinfoTxOutput- }- deriving (Eq, Show, Generic, NFData)--binfoTxInputToJSON :: Network -> BinfoTxInput -> Value-binfoTxInputToJSON net BinfoTxInput {..} =- A.object- [ "sequence" .= getBinfoTxInputSeq,- "witness" .= encodeHex getBinfoTxInputWitness,- "script" .= encodeHex getBinfoTxInputScript,- "index" .= getBinfoTxInputIndex,- "prev_out" .= binfoTxOutputToJSON net getBinfoTxInputPrevOut- ]--binfoTxInputToEncoding :: Network -> BinfoTxInput -> Encoding-binfoTxInputToEncoding net BinfoTxInput {..} =- AE.pairs- ( "sequence" .= getBinfoTxInputSeq- <> "witness" .= encodeHex getBinfoTxInputWitness- <> "script" .= encodeHex getBinfoTxInputScript- <> "index" .= getBinfoTxInputIndex- <> "prev_out" .= binfoTxOutputToJSON net getBinfoTxInputPrevOut- )--binfoTxInputParseJSON :: Network -> Value -> Parser BinfoTxInput-binfoTxInputParseJSON net = A.withObject "txin" $ \o -> do- getBinfoTxInputSeq <- o .: "sequence"- getBinfoTxInputWitness <-- maybe mzero return . decodeHex- =<< o .: "witness"- getBinfoTxInputScript <-- maybe mzero return . decodeHex- =<< o .: "script"- getBinfoTxInputIndex <- o .: "index"- getBinfoTxInputPrevOut <-- o .: "prev_out"- >>= binfoTxOutputParseJSON net- return BinfoTxInput {..}--data BinfoTxOutput = BinfoTxOutput- { getBinfoTxOutputType :: !Int,- getBinfoTxOutputSpent :: !Bool,- getBinfoTxOutputValue :: !Word64,- getBinfoTxOutputIndex :: !Word32,- getBinfoTxOutputTxIndex :: !BinfoTxId,- getBinfoTxOutputScript :: !ByteString,- getBinfoTxOutputSpenders :: ![BinfoSpender],- getBinfoTxOutputAddress :: !(Maybe Address),- getBinfoTxOutputXPub :: !(Maybe BinfoXPubPath)- }- deriving (Eq, Show, Generic, NFData)--binfoTxOutputToJSON :: Network -> BinfoTxOutput -> Value-binfoTxOutputToJSON net BinfoTxOutput {..} =- A.object $- [ "type" .= getBinfoTxOutputType,- "spent" .= getBinfoTxOutputSpent,- "value" .= getBinfoTxOutputValue,- "spending_outpoints" .= getBinfoTxOutputSpenders,- "n" .= getBinfoTxOutputIndex,- "tx_index" .= getBinfoTxOutputTxIndex,- "script" .= encodeHex getBinfoTxOutputScript- ]- <> [ "addr" .= addrToJSON net a- | a <- maybeToList getBinfoTxOutputAddress- ]- <> [ "xpub" .= binfoXPubPathToJSON net x- | x <- maybeToList getBinfoTxOutputXPub- ]--binfoTxOutputToEncoding :: Network -> BinfoTxOutput -> Encoding-binfoTxOutputToEncoding net BinfoTxOutput {..} =- AE.pairs $- mconcat $- [ "type" .= getBinfoTxOutputType,- "spent" .= getBinfoTxOutputSpent,- "value" .= getBinfoTxOutputValue,- "spending_outpoints" .= getBinfoTxOutputSpenders,- "n" .= getBinfoTxOutputIndex,- "tx_index" .= getBinfoTxOutputTxIndex,- "script" .= encodeHex getBinfoTxOutputScript- ]- <> [ "addr" .= addrToJSON net a- | a <- maybeToList getBinfoTxOutputAddress- ]- <> [ "xpub" .= binfoXPubPathToJSON net x- | x <- maybeToList getBinfoTxOutputXPub- ]--binfoTxOutputParseJSON :: Network -> Value -> Parser BinfoTxOutput-binfoTxOutputParseJSON net = A.withObject "txout" $ \o -> do- getBinfoTxOutputType <- o .: "type"- getBinfoTxOutputSpent <- o .: "spent"- getBinfoTxOutputValue <- o .: "value"- getBinfoTxOutputSpenders <- o .: "spending_outpoints"- getBinfoTxOutputIndex <- o .: "n"- getBinfoTxOutputTxIndex <- o .: "tx_index"- getBinfoTxOutputScript <- maybe mzero return . decodeHex =<< o .: "script"- getBinfoTxOutputAddress <- o .:? "addr" >>= mapM (addrFromJSON net)- getBinfoTxOutputXPub <- o .:? "xpub" >>= mapM (binfoXPubPathParseJSON net)- return BinfoTxOutput {..}--data BinfoSpender = BinfoSpender- { getBinfoSpenderTxIndex :: !BinfoTxId,- getBinfoSpenderIndex :: !Word32- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoSpender where- toJSON BinfoSpender {..} =- A.object- [ "tx_index" .= getBinfoSpenderTxIndex,- "n" .= getBinfoSpenderIndex- ]- toEncoding BinfoSpender {..} =- AE.pairs- ( "tx_index" .= getBinfoSpenderTxIndex- <> "n" .= getBinfoSpenderIndex- )--instance FromJSON BinfoSpender where- parseJSON = A.withObject "spender" $ \o -> do- getBinfoSpenderTxIndex <- o .: "tx_index"- getBinfoSpenderIndex <- o .: "n"- return BinfoSpender {..}--data BinfoXPubPath = BinfoXPubPath- { getBinfoXPubPathKey :: !XPubKey,- getBinfoXPubPathDeriv :: !SoftPath- }- deriving (Eq, Show, Generic, NFData)--instance Ord BinfoXPubPath where- compare = compare `on` f- where- f b =- ( xPubParent (getBinfoXPubPathKey b),- getBinfoXPubPathDeriv b- )--binfoXPubPathToJSON :: Network -> BinfoXPubPath -> Value-binfoXPubPathToJSON net BinfoXPubPath {..} =- A.object- [ "m" .= xPubToJSON net getBinfoXPubPathKey,- "path" .= ("M" ++ pathToStr getBinfoXPubPathDeriv)- ]--binfoXPubPathToEncoding :: Network -> BinfoXPubPath -> Encoding-binfoXPubPathToEncoding net BinfoXPubPath {..} =- AE.pairs $- "m" `AE.pair` xPubToEncoding net getBinfoXPubPathKey- <> "path" .= ("M" ++ pathToStr getBinfoXPubPathDeriv)--binfoXPubPathParseJSON :: Network -> Value -> Parser BinfoXPubPath-binfoXPubPathParseJSON net = A.withObject "xpub" $ \o -> do- getBinfoXPubPathKey <- o .: "m" >>= xPubFromJSON net- getBinfoXPubPathDeriv <-- fromMaybe "bad xpub path" . parseSoft <$> o .: "path"- return BinfoXPubPath {..}--data BinfoInfo = BinfoInfo- { getBinfoConnected :: !Word32,- getBinfoConversion :: !Double,- getBinfoLocal :: !BinfoSymbol,- getBinfoBTC :: !BinfoSymbol,- getBinfoLatestBlock :: !BinfoBlockInfo- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoInfo where- toJSON BinfoInfo {..} =- A.object- [ "nconnected" .= getBinfoConnected,- "conversion" .= getBinfoConversion,- "symbol_local" .= getBinfoLocal,- "symbol_btc" .= getBinfoBTC,- "latest_block" .= getBinfoLatestBlock- ]- toEncoding BinfoInfo {..} =- AE.pairs- ( "nconnected" .= getBinfoConnected- <> "conversion" .= getBinfoConversion- <> "symbol_local" .= getBinfoLocal- <> "symbol_btc" .= getBinfoBTC- <> "latest_block" .= getBinfoLatestBlock- )--instance FromJSON BinfoInfo where- parseJSON = A.withObject "info" $ \o -> do- getBinfoConnected <- o .: "nconnected"- getBinfoConversion <- o .: "conversion"- getBinfoLocal <- o .: "symbol_local"- getBinfoBTC <- o .: "symbol_btc"- getBinfoLatestBlock <- o .: "latest_block"- return BinfoInfo {..}--data BinfoBlockInfo = BinfoBlockInfo- { getBinfoBlockInfoHash :: !BlockHash,- getBinfoBlockInfoHeight :: !BlockHeight,- getBinfoBlockInfoTime :: !Word32,- getBinfoBlockInfoIndex :: !BlockHeight- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoBlockInfo where- toJSON BinfoBlockInfo {..} =- A.object- [ "hash" .= getBinfoBlockInfoHash,- "height" .= getBinfoBlockInfoHeight,- "time" .= getBinfoBlockInfoTime,- "block_index" .= getBinfoBlockInfoIndex- ]- toEncoding BinfoBlockInfo {..} =- AE.pairs- ( "hash" .= getBinfoBlockInfoHash- <> "height" .= getBinfoBlockInfoHeight- <> "time" .= getBinfoBlockInfoTime- <> "block_index" .= getBinfoBlockInfoIndex- )--instance FromJSON BinfoBlockInfo where- parseJSON = A.withObject "block_info" $ \o -> do- getBinfoBlockInfoHash <- o .: "hash"- getBinfoBlockInfoHeight <- o .: "height"- getBinfoBlockInfoTime <- o .: "time"- getBinfoBlockInfoIndex <- o .: "block_index"- return BinfoBlockInfo {..}--toBinfoBlockInfo :: BlockData -> BinfoBlockInfo-toBinfoBlockInfo BlockData {..} =- BinfoBlockInfo- { getBinfoBlockInfoHash = headerHash blockDataHeader,- getBinfoBlockInfoHeight = blockDataHeight,- getBinfoBlockInfoTime = blockTimestamp blockDataHeader,- getBinfoBlockInfoIndex = blockDataHeight- }--data BinfoRate = BinfoRate- { binfoRateTime :: !Word64,- binfoRatePrice :: !Double,- binfoRateVol24 :: !Double- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoRate where- toJSON BinfoRate {..} =- A.object- [ "timestamp" .= binfoRateTime,- "price" .= binfoRatePrice,- "volume24h" .= binfoRateVol24- ]--instance FromJSON BinfoRate where- parseJSON = A.withObject "price" $ \o -> do- binfoRateTime <- o .: "timestamp"- binfoRatePrice <- o .: "price"- binfoRateVol24 <- o .: "volume24h"- return BinfoRate {..}--data BinfoHistory = BinfoHistory- { binfoHistoryDate :: !Text,- binfoHistoryTime :: !Text,- binfoHistoryType :: !Text,- binfoHistoryAmount :: !Double,- binfoHistoryValueThen :: !Double,- binfoHistoryValueNow :: !Double,- binfoHistoryExchangeRateThen :: !Double,- binfoHistoryTx :: !TxHash,- binfoHistoryFee :: !Double- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoHistory where- toJSON BinfoHistory {..} =- A.object- [ "date" .= binfoHistoryDate,- "time" .= binfoHistoryTime,- "type" .= binfoHistoryType,- "amount" .= binfoHistoryAmount,- "value_then" .= binfoHistoryValueThen,- "value_now" .= binfoHistoryValueNow,- "exchange_rate_then" .= binfoHistoryExchangeRateThen,- "tx" .= binfoHistoryTx,- "fee" .= binfoHistoryFee- ]--instance FromJSON BinfoHistory where- parseJSON = A.withObject "history" $ \o -> do- binfoHistoryDate <- o .: "date"- binfoHistoryTime <- o .: "time"- binfoHistoryType <- o .: "type"- binfoHistoryAmount <- o .: "amount"- binfoHistoryValueThen <- o .: "value_then"- binfoHistoryValueNow <- o .: "value_now"- binfoHistoryExchangeRateThen <- o .: "exchange_rate_then"- binfoHistoryTx <- o .: "tx"- binfoHistoryFee <- o .: "fee"- return BinfoHistory {..}--toBinfoHistory ::- Int64 ->- Word64 ->- Double ->- Double ->- Word64 ->- TxHash ->- BinfoHistory-toBinfoHistory satoshi timestamp rate_then rate_now fee txhash =- BinfoHistory- { binfoHistoryDate =- T.pack $ formatTime defaultTimeLocale "%Y-%m-%d" t,- binfoHistoryTime =- T.pack $ formatTime defaultTimeLocale "%H:%M:%S GMT %Ez" t,- binfoHistoryType =- if satoshi <= 0 then "sent" else "received",- binfoHistoryAmount =- fromRational v,- binfoHistoryValueThen =- fromRational v1,- binfoHistoryValueNow =- fromRational v2,- binfoHistoryExchangeRateThen =- rate_then,- binfoHistoryTx =- txhash,- binfoHistoryFee =- fromRational f- }- where- t = posixSecondsToUTCTime (realToFrac timestamp)- v = toRational satoshi / (100 * 1000 * 1000)- r1 = toRational rate_then- r2 = toRational rate_now- f = toRational fee / (100 * 1000 * 1000)- v1 = v * r1- v2 = v * r2--newtype BinfoDate = BinfoDate Word64- deriving (Eq, Show, Read, Generic, NFData)--instance Parsable BinfoDate where- parseParam t =- maybeToEither "Cannot parse date"- . fmap (BinfoDate . round . utcTimeToPOSIXSeconds)- $ p "%d-%m-%Y" <|> p "%d/%m/%Y"- where- s = TL.unpack t- p fmt = parseTimeM False defaultTimeLocale fmt s--data BinfoTicker = BinfoTicker- { binfoTicker15m :: !Double,- binfoTickerLast :: !Double,- binfoTickerBuy :: !Double,- binfoTickerSell :: !Double,- binfoTickerSymbol :: !Text- }- deriving (Eq, Show, Generic, NFData)--instance Default BinfoTicker where- def =- BinfoTicker- { binfoTickerSymbol = "XXX",- binfoTicker15m = 0.0,- binfoTickerLast = 0.0,- binfoTickerBuy = 0.0,- binfoTickerSell = 0.0- }--instance ToJSON BinfoTicker where- toJSON BinfoTicker {..} =- A.object- [ "symbol" .= binfoTickerSymbol,- "sell" .= binfoTickerSell,- "buy" .= binfoTickerBuy,- "last" .= binfoTickerLast,- "15m" .= binfoTicker15m- ]- toEncoding BinfoTicker {..} =- AE.pairs $- "symbol" .= binfoTickerSymbol- <> "sell" .= binfoTickerSell- <> "buy" .= binfoTickerBuy- <> "last" .= binfoTickerLast- <> "15m" .= binfoTicker15m--instance FromJSON BinfoTicker where- parseJSON = A.withObject "ticker" $ \o -> do- binfoTickerSymbol <- o .: "symbol"- binfoTicker15m <- o .: "15m"- binfoTickerSell <- o .: "sell"- binfoTickerBuy <- o .: "buy"- binfoTickerLast <- o .: "last"- return BinfoTicker {..}--data BinfoSymbol = BinfoSymbol- { getBinfoSymbolCode :: !Text,- getBinfoSymbolString :: !Text,- getBinfoSymbolName :: !Text,- getBinfoSymbolConversion :: !Double,- getBinfoSymbolAfter :: !Bool,- getBinfoSymbolLocal :: !Bool- }- deriving (Eq, Show, Generic, NFData)--instance Default BinfoSymbol where- def =- BinfoSymbol- { getBinfoSymbolCode = "XXX",- getBinfoSymbolString = "¤",- getBinfoSymbolName = "No currency",- getBinfoSymbolConversion = 0.0,- getBinfoSymbolAfter = False,- getBinfoSymbolLocal = True- }--instance ToJSON BinfoSymbol where- toJSON BinfoSymbol {..} =- A.object- [ "code" .= getBinfoSymbolCode,- "symbol" .= getBinfoSymbolString,- "name" .= getBinfoSymbolName,- "conversion" .= getBinfoSymbolConversion,- "symbolAppearsAfter" .= getBinfoSymbolAfter,- "local" .= getBinfoSymbolLocal- ]- toEncoding BinfoSymbol {..} =- AE.pairs- ( "code" .= getBinfoSymbolCode- <> "symbol" .= getBinfoSymbolString- <> "name" .= getBinfoSymbolName- <> "conversion" .= getBinfoSymbolConversion- <> "symbolAppearsAfter" .= getBinfoSymbolAfter- <> "local" .= getBinfoSymbolLocal- )--instance FromJSON BinfoSymbol where- parseJSON = A.withObject "symbol" $ \o -> do- getBinfoSymbolCode <- o .: "code"- getBinfoSymbolString <- o .: "symbol"- getBinfoSymbolName <- o .: "name"- getBinfoSymbolConversion <- o .: "conversion"- getBinfoSymbolAfter <- o .: "symbolAppearsAfter"- getBinfoSymbolLocal <- o .: "local"- return BinfoSymbol {..}--relevantTxs ::- HashSet Address ->- Bool ->- Transaction ->- HashSet TxHash-relevantTxs addrs prune t@Transaction {..} =- let p a = prune && getTxResult addrs t > 0 && not (HashSet.member a addrs)- f StoreOutput {..} =- case outputSpender of- Nothing -> Nothing- Just Spender {..} ->- case outputAddr of- Nothing -> Nothing- Just a- | p a -> Nothing- | otherwise -> Just spenderHash- outs = mapMaybe f transactionOutputs- g StoreCoinbase {} = Nothing- g StoreInput {inputPoint = OutPoint {..}} = Just outPointHash- ins = mapMaybe g transactionInputs- in HashSet.fromList $ ins <> outs--toBinfoAddrs ::- HashMap Address Balance ->- HashMap XPubSpec [XPubBal] ->- HashMap XPubSpec Word64 ->- [BinfoBalance]-toBinfoAddrs only_addrs only_xpubs xpub_txs =- xpub_bals <> addr_bals- where- xpub_bal k xs =- let f x = case xPubBalPath x of- [0, _] -> balanceTotalReceived (xPubBal x)- _ -> 0- g x = balanceAmount (xPubBal x) + balanceZero (xPubBal x)- i m x = case xPubBalPath x of- [m', n] | m == m' -> n + 1- _ -> 0- received = sum (map f xs)- bal = fromIntegral (sum (map g xs))- sent = if bal <= received then received - bal else 0- count = HashMap.lookupDefault 0 k xpub_txs- ax = foldl max 0 (map (i 0) xs)- cx = foldl max 0 (map (i 1) xs)- in BinfoXPubBalance- { getBinfoXPubKey = xPubSpecKey k,- getBinfoAddrTxCount = count,- getBinfoAddrReceived = received,- getBinfoAddrSent = sent,- getBinfoAddrBalance = bal,- getBinfoXPubAccountIndex = ax,- getBinfoXPubChangeIndex = cx- }- xpub_bals = map (uncurry xpub_bal) (HashMap.toList only_xpubs)- addr_bals =- let f Balance {..} =- let addr = balanceAddress- sent = recv - bal- recv = balanceTotalReceived- tx_count = balanceTxCount- bal = balanceAmount + balanceZero- in BinfoAddrBalance- { getBinfoAddress = addr,- getBinfoAddrTxCount = tx_count,- getBinfoAddrReceived = recv,- getBinfoAddrSent = sent,- getBinfoAddrBalance = bal- }- in map f $ HashMap.elems only_addrs--toBinfoTxSimple ::- Bool ->- Transaction ->- BinfoTx-toBinfoTxSimple numtxid =- toBinfoTx numtxid HashMap.empty False 0--toBinfoTxInputs ::- Bool ->- HashMap Address (Maybe BinfoXPubPath) ->- Transaction ->- [BinfoTxInput]-toBinfoTxInputs numtxid abook t =- zipWith f [0 ..] (transactionInputs t)- where- f n i =- BinfoTxInput- { getBinfoTxInputIndex = n,- getBinfoTxInputSeq = inputSequence i,- getBinfoTxInputScript = inputSigScript i,- getBinfoTxInputWitness = wit i,- getBinfoTxInputPrevOut = prev n i- }- wit i =- case inputWitness i of- [] -> BS.empty- ws -> runPutS (put_witness ws)- prev = inputToBinfoTxOutput numtxid abook t- put_witness ws = do- putVarInt (length ws)- mapM_ put_item ws- put_item bs = do- putVarInt (BS.length bs)- putByteString bs--toBinfoBlockIndex :: Transaction -> Maybe BlockHeight-toBinfoBlockIndex Transaction {transactionDeleted = True} = Nothing-toBinfoBlockIndex Transaction {transactionBlock = MemRef _} = Nothing-toBinfoBlockIndex Transaction {transactionBlock = BlockRef h _} = Just h--toBinfoTx ::- Bool ->- HashMap Address (Maybe BinfoXPubPath) ->- Bool ->- Int64 ->- Transaction ->- BinfoTx-toBinfoTx numtxid abook prune bal t@Transaction {..} =- BinfoTx- { getBinfoTxHash = txHash (transactionData t),- getBinfoTxVer = transactionVersion,- getBinfoTxVinSz = fromIntegral (length transactionInputs),- getBinfoTxVoutSz = fromIntegral (length transactionOutputs),- getBinfoTxSize = transactionSize,- getBinfoTxWeight = transactionWeight,- getBinfoTxFee = transactionFees,- getBinfoTxRelayedBy = "0.0.0.0",- getBinfoTxLockTime = transactionLockTime,- getBinfoTxIndex =- encodeBinfoTxId numtxid (txHash (transactionData t)),- getBinfoTxDoubleSpend = transactionDeleted,- getBinfoTxRBF = transactionRBF,- getBinfoTxTime = transactionTime,- getBinfoTxBlockIndex = toBinfoBlockIndex t,- getBinfoTxBlockHeight = toBinfoBlockIndex t,- getBinfoTxInputs = toBinfoTxInputs numtxid abook t,- getBinfoTxOutputs = outs,- getBinfoTxResultBal = resbal- }- where- simple = HashMap.null abook && bal == 0- resbal = if simple then Nothing else Just (getTxResult aset t, bal)- aset = HashMap.keysSet abook- outs =- let p = prune && getTxResult aset t > 0- f = toBinfoTxOutput numtxid abook p t- in catMaybes $ zipWith f [0 ..] transactionOutputs--getTxResult :: HashSet Address -> Transaction -> Int64-getTxResult aset Transaction {..} =- let input_sum = sum $ map input_value transactionInputs- input_value StoreCoinbase {} = 0- input_value StoreInput {..} =- case inputAddress of- Nothing -> 0- Just a ->- if test_addr a- then negate $ fromIntegral inputAmount- else 0- test_addr a = HashSet.member a aset- output_sum = sum $ map out_value transactionOutputs- out_value StoreOutput {..} =- case outputAddr of- Nothing -> 0- Just a ->- if test_addr a- then fromIntegral outputAmount- else 0- in input_sum + output_sum--toBinfoTxOutput ::- Bool ->- HashMap Address (Maybe BinfoXPubPath) ->- Bool ->- Transaction ->- Word32 ->- StoreOutput ->- Maybe BinfoTxOutput-toBinfoTxOutput numtxid abook prune t n StoreOutput {..} =- let getBinfoTxOutputType = 0- getBinfoTxOutputSpent = isJust outputSpender- getBinfoTxOutputValue = outputAmount- getBinfoTxOutputIndex = n- getBinfoTxOutputTxIndex =- encodeBinfoTxId numtxid (txHash (transactionData t))- getBinfoTxOutputScript = outputScript- getBinfoTxOutputSpenders =- maybeToList $ toBinfoSpender numtxid <$> outputSpender- getBinfoTxOutputAddress = outputAddr- getBinfoTxOutputXPub =- outputAddr >>= join . (`HashMap.lookup` abook)- in if prune && isNothing (outputAddr >>= (`HashMap.lookup` abook))- then Nothing- else Just BinfoTxOutput {..}--toBinfoSpender :: Bool -> Spender -> BinfoSpender-toBinfoSpender numtxid Spender {..} =- let getBinfoSpenderTxIndex = encodeBinfoTxId numtxid spenderHash- getBinfoSpenderIndex = spenderIndex- in BinfoSpender {..}--inputToBinfoTxOutput ::- Bool ->- HashMap Address (Maybe BinfoXPubPath) ->- Transaction ->- Word32 ->- StoreInput ->- BinfoTxOutput-inputToBinfoTxOutput numtxid abook t n i =- BinfoTxOutput- { getBinfoTxOutputIndex = out_index,- getBinfoTxOutputType = 0,- getBinfoTxOutputSpent = True,- getBinfoTxOutputValue = inputAmount,- getBinfoTxOutputTxIndex = encodeBinfoTxId numtxid out_hash,- getBinfoTxOutputScript = inputPkScript,- getBinfoTxOutputSpenders = [spender],- getBinfoTxOutputAddress = inputAddress,- getBinfoTxOutputXPub = xpub- }- where- inputAmount =- case i of- StoreCoinbase {} -> 0- StoreInput {..} -> inputAmount- inputPkScript =- case i of- StoreCoinbase {} -> BS.empty- StoreInput {..} -> inputPkScript- inputAddress =- case i of- StoreCoinbase {} -> Nothing- StoreInput {..} -> inputAddress- OutPoint out_hash out_index = inputPoint i- spender =- BinfoSpender- (encodeBinfoTxId numtxid (txHash (transactionData t)))- n- xpub = inputAddress >>= join . (`HashMap.lookup` abook)--data BinfoAddr- = BinfoAddr !Address- | BinfoXpub !XPubKey- deriving (Eq, Show, Generic, Hashable, NFData)--parseBinfoAddr :: Network -> Text -> Maybe [BinfoAddr]-parseBinfoAddr _ "" = Just []-parseBinfoAddr net s =- mapM f $- filter (not . T.null) $- concatMap (T.splitOn ",") (T.splitOn "|" s)- where- f x = BinfoAddr <$> textToAddr net x <|> BinfoXpub <$> xPubImport net x--data BinfoHeader = BinfoHeader- { binfoHeaderHash :: !BlockHash,- binfoHeaderTime :: !Timestamp,- binfoHeaderIndex :: !Word32,- binfoHeaderHeight :: !BlockHeight,- binfoTxIndices :: ![BinfoTxId]- }- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoHeader where- toJSON BinfoHeader {..} =- A.object- [ "hash" .= binfoHeaderHash,- "time" .= binfoHeaderTime,- "block_index" .= binfoHeaderIndex,- "height" .= binfoHeaderHeight,- "txIndexes" .= binfoTxIndices- ]--instance FromJSON BinfoHeader where- parseJSON = A.withObject "header" $ \o -> do- binfoHeaderHash <- o .: "hash"- binfoHeaderTime <- o .: "time"- binfoHeaderIndex <- o .: "block_index"- binfoHeaderHeight <- o .: "height"- binfoTxIndices <- o .: "txIndexes"- return BinfoHeader {..}--newtype BinfoMempool = BinfoMempool {getBinfoMempool :: [BinfoTx]}- deriving (Eq, Show, Generic, NFData)--binfoMempoolToJSON :: Network -> BinfoMempool -> Value-binfoMempoolToJSON net (BinfoMempool txs) =- A.object ["txs" .= map (binfoTxToJSON net) txs]--binfoMempoolToEncoding :: Network -> BinfoMempool -> Encoding-binfoMempoolToEncoding net (BinfoMempool txs) =- AE.pairs $ AE.pair "txs" (AE.list (binfoTxToEncoding net) txs)--binfoMempoolParseJSON :: Network -> Value -> Parser BinfoMempool-binfoMempoolParseJSON net =- A.withObject "mempool" $ \o ->- BinfoMempool <$> (mapM (binfoTxParseJSON net) =<< o .: "txs")--newtype BinfoBlocks = BinfoBlocks {binfoBlocks :: [BinfoBlockInfo]}- deriving (Eq, Show, Generic, NFData)--instance ToJSON BinfoBlocks where- toJSON BinfoBlocks {..} = A.object ["blocks" .= binfoBlocks]--instance FromJSON BinfoBlocks where- parseJSON = A.withObject "blocks" $ \o -> BinfoBlocks <$> o .: "blocks"+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoFieldSelectors #-}++module Haskoin.Store.Data+ ( -- * Address Balances+ Balance (..),+ zeroBalance,+ nullBalance,++ -- * Block Data+ BlockData (..),+ confirmed,++ -- * Transactions+ TxRef (..),+ TxData (..),+ txDataFee,+ isCoinbaseTx,+ Transaction (..),+ transactionData,+ fromTransaction,+ toTransaction,+ StoreInput (..),+ isCoinbase,+ StoreOutput (..),+ Prev (..),+ Spender (..),+ BlockRef (..),+ UnixTime,+ getUnixTime,+ putUnixTime,+ BlockPos,++ -- * Unspent Outputs+ Unspent (..),++ -- * Extended Public Keys+ XPubSpec (..),+ XPubBal (..),+ XPubUnspent (..),+ XPubSummary (..),+ DeriveType (..),+ textToDeriveType,+ deriveTypeToText,++ -- * Other Data+ TxId (..),+ GenericResult (..),+ SerialList (..),+ RawResult (..),+ RawResultList (..),+ PeerInfo (..),+ Healthy (..),+ BlockHealth (..),+ TimeHealth (..),+ CountHealth (..),+ MaxHealth (..),+ HealthCheck (..),+ Event (..),+ Except (..),++ -- * Blockchain.info API+ BinfoInfo (..),+ BinfoBlockId (..),+ BinfoTxId (..),+ encodeBinfoTxId,+ BinfoFilter (..),+ BinfoMultiAddr (..),+ BinfoShortBal (..),+ BinfoBalance (..),+ toBinfoAddrs,+ BinfoRawAddr (..),+ BinfoAddr (..),+ parseBinfoAddr,+ BinfoWallet (..),+ BinfoUnspent (..),+ binfoHexValue,+ BinfoUnspents (..),+ BinfoBlock (..),+ toBinfoBlock,+ BinfoTx (..),+ relevantTxs,+ toBinfoTx,+ toBinfoTxSimple,+ BinfoTxInput (..),+ BinfoTxOutput (..),+ BinfoSpender (..),+ BinfoXPubPath (..),+ BinfoBlockInfo (..),+ toBinfoBlockInfo,+ BinfoSymbol (..),+ BinfoTicker (..),+ BinfoRate (..),+ BinfoHistory (..),+ toBinfoHistory,+ BinfoDate (..),+ BinfoHeader (..),+ BinfoMempool (..),+ BinfoBlockInfos (..),+ )+where++import Control.Applicative (optional, (<|>))+import Control.DeepSeq (NFData)+import Control.Exception (Exception)+import Control.Monad (guard, join, mzero, unless, (<=<))+import Data.Aeson+ ( Encoding,+ FromJSON (..),+ ToJSON (..),+ Value (..),+ (.!=),+ (.:),+ (.:?),+ (.=),+ )+import Data.Aeson qualified as A+import Data.Aeson.Encoding qualified as A+import Data.Aeson.Types (Parser)+import Data.Binary (Binary (get, put))+import Data.Bits (Bits (..))+import Data.Bool (bool)+import Data.ByteString (ByteString)+import Data.ByteString qualified as B+import Data.ByteString.Builder qualified as Builder+import Data.Bytes.Get+import Data.Bytes.Get qualified as Bytes.Get+import Data.Bytes.Put+import Data.Bytes.Serial+import Data.Default (Default (..))+import Data.Foldable (toList)+import Data.Function (on)+import Data.HashMap.Strict (HashMap)+import Data.HashMap.Strict qualified as HashMap+import Data.HashSet (HashSet)+import Data.HashSet qualified as HashSet+import Data.Hashable (Hashable (..))+import Data.Int (Int32, Int64)+import Data.IntMap qualified as IntMap+import Data.IntMap.Strict (IntMap)+import Data.Maybe+ ( catMaybes,+ fromMaybe,+ isJust,+ isNothing,+ mapMaybe,+ maybeToList,+ )+import Data.Serialize (Serialize (..))+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding qualified as T+import Data.Text.Lazy qualified as LazyText+import Data.Text.Lazy.Encoding qualified as LazyText+import Data.Time (UTCTime (UTCTime), rfc822DateFormat)+import Data.Time.Clock.POSIX+ ( posixSecondsToUTCTime,+ utcTimeToPOSIXSeconds,+ )+import Data.Time.Format+ ( defaultTimeLocale,+ formatTime,+ parseTimeM,+ )+import Data.Time.Format.ISO8601+ ( iso8601ParseM,+ iso8601Show,+ )+import Data.Vector qualified as V+import Data.Word (Word32, Word64)+import GHC.Generics (Generic)+import Haskoin+import Web.Scotty.Trans (Parsable (..), ScottyError (..))++data DeriveType+ = DeriveNormal+ | DeriveP2SH+ | DeriveP2WPKH+ deriving (Show, Eq, Generic, NFData)++textToDeriveType :: Text -> Maybe DeriveType+textToDeriveType "normal" = Just DeriveNormal+textToDeriveType "compat" = Just DeriveP2SH+textToDeriveType "segwit" = Just DeriveP2WPKH+textToDeriveType _ = Nothing++deriveTypeToText :: DeriveType -> Text+deriveTypeToText DeriveNormal = "normal"+deriveTypeToText DeriveP2SH = "compat"+deriveTypeToText DeriveP2WPKH = "segwit"++instance Serial DeriveType where+ serialize DeriveNormal = putWord8 0x00+ serialize DeriveP2SH = putWord8 0x01+ serialize DeriveP2WPKH = putWord8 0x02++ deserialize =+ getWord8 >>= \case+ 0x00 -> return DeriveNormal+ 0x01 -> return DeriveP2SH+ 0x02 -> return DeriveP2WPKH+ _ -> return DeriveNormal++instance Binary DeriveType where+ put = serialize+ get = deserialize++instance Serialize DeriveType where+ put = serialize+ get = deserialize++instance Default DeriveType where+ def = DeriveNormal++instance Parsable DeriveType where+ parseParam txt =+ case textToDeriveType (LazyText.toStrict txt) of+ Nothing -> Left "invalid derivation type"+ Just x -> Right x++data XPubSpec = XPubSpec+ { key :: !XPubKey,+ deriv :: !DeriveType+ }+ deriving (Show, Eq, Generic, NFData)++instance Hashable XPubSpec where+ hashWithSalt i = hashWithSalt i . (.key.key)++instance Serial XPubSpec where+ serialize XPubSpec {key = k, deriv = t} = do+ putWord8 k.depth+ serialize k.parent+ putWord32be k.index+ serialize k.chain+ putByteString k.key.get+ serialize t+ deserialize = do+ depth <- getWord8+ parent <- deserialize+ index <- getWord32be+ chain <- deserialize+ key <- PubKey <$> getByteString 64+ deriv <- deserialize+ return XPubSpec {key = XPubKey {..}, deriv}++instance Serialize XPubSpec where+ put = serialize+ get = deserialize++instance Binary XPubSpec where+ put = serialize+ get = deserialize++type UnixTime = Word64++type BlockPos = Word32++-- | Binary such that ordering is inverted.+putUnixTime :: (MonadPut m) => Word64 -> m ()+putUnixTime w = putWord64be $ maxBound - w++getUnixTime :: (MonadGet m) => m Word64+getUnixTime = (maxBound -) <$> getWord64be++-- | Reference to a block where a transaction is stored.+data BlockRef+ = BlockRef+ { -- | block height in the chain+ height :: !BlockHeight,+ -- | position of transaction within the block+ position :: !Word32+ }+ | MemRef+ { timestamp :: !UnixTime+ }+ deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)++-- | Serial entities will sort in reverse order.+instance Serial BlockRef where+ serialize MemRef {timestamp = t} = do+ putWord8 0x00+ putUnixTime t+ serialize BlockRef {height = h, position = p} = do+ putWord8 0x01+ putWord32be (maxBound - h)+ putWord32be (maxBound - p)+ deserialize =+ getWord8 >>= \case+ 0x00 -> getmemref+ 0x01 -> getblockref+ _ -> fail "Cannot decode BlockRef"+ where+ getmemref = do+ MemRef <$> getUnixTime+ getblockref = do+ h <- (maxBound -) <$> getWord32be+ p <- (maxBound -) <$> getWord32be+ return BlockRef {height = h, position = p}++instance Serialize BlockRef where+ put = serialize+ get = deserialize++instance Binary BlockRef where+ put = serialize+ get = deserialize++confirmed :: BlockRef -> Bool+confirmed BlockRef {} = True+confirmed MemRef {} = False++instance ToJSON BlockRef where+ toJSON BlockRef {height = h, position = p} =+ A.object+ [ "height" .= h,+ "position" .= p+ ]+ toJSON MemRef {timestamp = t} =+ A.object ["mempool" .= t]+ toEncoding BlockRef {height = h, position = p} =+ A.pairs $+ mconcat+ [ "height" `A.pair` A.word32 h,+ "position" `A.pair` A.word32 p+ ]+ toEncoding MemRef {timestamp = t} =+ A.pairs $ "mempool" `A.pair` A.word64 t++instance FromJSON BlockRef where+ parseJSON =+ A.withObject "BlockRef" $ \o -> b o <|> m o+ where+ b o = do+ height <- o .: "height"+ position <- o .: "position"+ return BlockRef {..}+ m o =+ MemRef <$> o .: "mempool"++-- | Transaction in relation to an address.+data TxRef = TxRef+ { -- | block information+ block :: !BlockRef,+ -- | transaction hash+ txid :: !TxHash+ }+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)++instance Serial TxRef where+ serialize (TxRef b h) = do+ serialize b+ serialize h++ deserialize =+ TxRef <$> deserialize <*> deserialize++instance Binary TxRef where+ put = serialize+ get = deserialize++instance Serialize TxRef where+ put = serialize+ get = deserialize++instance ToJSON TxRef where+ toJSON x =+ A.object+ [ "txid" .= x.txid,+ "block" .= x.block+ ]+ toEncoding btx =+ A.pairs $+ mconcat+ [ "txid" `A.pair` toEncoding btx.txid,+ "block" `A.pair` toEncoding btx.block+ ]++instance FromJSON TxRef where+ parseJSON =+ A.withObject "TxRef" $ \o -> do+ txid <- o .: "txid"+ block <- o .: "block"+ return TxRef {..}++-- | Address balance information.+data Balance = Balance+ { -- | address balance+ address :: !Address,+ -- | confirmed balance+ confirmed :: !Word64,+ -- | unconfirmed balance+ unconfirmed :: !Word64,+ -- | number of unspent outputs+ utxo :: !Word64,+ -- | number of transactions+ txs :: !Word64,+ -- | total amount from all outputs in this address+ received :: !Word64+ }+ deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)++instance Serial Balance where+ serialize b = do+ serialize b.address+ putWord64be b.confirmed+ putWord64be b.unconfirmed+ putWord64be b.utxo+ putWord64be b.txs+ putWord64be b.received++ deserialize = do+ address <- deserialize+ confirmed <- getWord64be+ unconfirmed <- getWord64be+ utxo <- getWord64be+ txs <- getWord64be+ received <- getWord64be+ return Balance {..}++instance Binary Balance where+ put = serialize+ get = deserialize++instance Serialize Balance where+ put = serialize+ get = deserialize++zeroBalance :: Address -> Balance+zeroBalance a =+ Balance+ { address = a,+ confirmed = 0,+ unconfirmed = 0,+ utxo = 0,+ txs = 0,+ received = 0+ }++nullBalance :: Balance -> Bool+nullBalance+ Balance+ { confirmed = 0,+ unconfirmed = 0,+ utxo = 0,+ txs = 0,+ received = 0+ } = True+nullBalance _ = False++instance MarshalJSON Network Balance where+ marshalValue net b =+ A.object+ [ "address" .= marshalValue net b.address,+ "confirmed" .= b.confirmed,+ "unconfirmed" .= b.unconfirmed,+ "utxo" .= b.utxo,+ "txs" .= b.txs,+ "received" .= b.received+ ]++ marshalEncoding net b =+ A.pairs $+ mconcat+ [ "address" `A.pair` marshalEncoding net b.address,+ "confirmed" `A.pair` A.word64 b.confirmed,+ "unconfirmed" `A.pair` A.word64 b.unconfirmed,+ "utxo" `A.pair` A.word64 b.utxo,+ "txs" `A.pair` A.word64 b.txs,+ "received" `A.pair` A.word64 b.received+ ]++ unmarshalValue net =+ A.withObject "Balance" $ \o -> do+ confirmed <- o .: "confirmed"+ unconfirmed <- o .: "unconfirmed"+ utxo <- o .: "utxo"+ txs <- o .: "txs"+ received <- o .: "received"+ address <- unmarshalValue net =<< o .: "address"+ return Balance {..}++-- | Unspent output.+data Unspent = Unspent+ { block :: !BlockRef,+ outpoint :: !OutPoint,+ value :: !Word64,+ script :: !ByteString,+ address :: !(Maybe Address)+ }+ deriving (Show, Eq, Generic, Hashable, NFData)++-- | Follow same order as in database and cache by inverting outpoint sort+-- order.+instance Ord Unspent where+ compare a b =+ compare+ (a.block, b.block)+ (b.block, a.block)++instance Serial Unspent where+ serialize u = do+ serialize u.block+ serialize u.outpoint+ putWord64be u.value+ putLengthBytes u.script+ putMaybe serialize u.address++ deserialize = do+ block <- deserialize+ outpoint <- deserialize+ value <- getWord64be+ script <- getLengthBytes+ address <- getMaybe deserialize+ return Unspent {..}++instance Binary Unspent where+ put = serialize+ get = deserialize++instance Serialize Unspent where+ put = serialize+ get = deserialize++instance Coin Unspent where+ coinValue = (.value)++instance MarshalJSON Network Unspent where+ marshalValue net u =+ A.object+ [ "address" .= fmap (marshalValue net) u.address,+ "block" .= u.block,+ "txid" .= u.outpoint.hash,+ "index" .= u.outpoint.index,+ "pkscript" .= encodeHex u.script,+ "value" .= u.value+ ]++ marshalEncoding net u =+ A.pairs $+ mconcat+ [ "address" `A.pair` maybe A.null_ (marshalEncoding net) u.address,+ "block" `A.pair` toEncoding u.block,+ "txid" `A.pair` toEncoding u.outpoint.hash,+ "index" `A.pair` A.word32 u.outpoint.index,+ "pkscript" `A.pair` hexEncoding (B.fromStrict u.script),+ "value" `A.pair` A.word64 u.value+ ]++ unmarshalValue net =+ A.withObject "Unspent" $ \o -> do+ block <- o .: "block"+ hash <- o .: "txid"+ index <- o .: "index"+ value <- o .: "value"+ script <- o .: "pkscript" >>= jsonHex+ address <-+ o .: "address"+ >>= maybe (return Nothing) (optional . unmarshalValue net)+ return Unspent {outpoint = OutPoint {..}, ..}++-- | Database value for a block entry.+data BlockData = BlockData+ { -- | height of the block in the chain+ height :: !BlockHeight,+ -- | is this block in the main chain?+ main :: !Bool,+ -- | accumulated work in that block+ work :: !Integer,+ -- | block header+ header :: !BlockHeader,+ -- | size of the block including witnesses+ size :: !Word32,+ -- | weight of this block (for segwit networks)+ weight :: !Word32,+ -- | block transactions+ txs :: ![TxHash],+ -- | sum of all transaction outputs+ outputs :: !Word64,+ -- | sum of all transaction fee+ fee :: !Word64,+ -- | block subsidy+ subsidy :: !Word64+ }+ deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)++instance Serial BlockData where+ serialize b = do+ putWord32be b.height+ serialize b.main+ putInteger b.work+ serialize b.header+ putWord32be b.size+ putWord32be b.weight+ putList serialize b.txs+ putWord64be b.outputs+ putWord64be b.fee+ putWord64be b.subsidy++ deserialize = do+ height <- getWord32be+ main <- deserialize+ work <- getInteger+ header <- deserialize+ size <- getWord32be+ weight <- getWord32be+ txs <- getList deserialize+ outputs <- getWord64be+ fee <- getWord64be+ subsidy <- getWord64be+ return BlockData {..}++instance Serialize BlockData where+ put = serialize+ get = deserialize++instance Binary BlockData where+ put = serialize+ get = deserialize++instance FromJSON BlockData where+ parseJSON =+ A.withObject "BlockData" $ \o -> do+ height <- o .: "height"+ main <- o .: "mainchain"+ prev <- o .: "previous"+ timestamp <- o .: "time"+ version <- o .: "version"+ bits <- o .: "bits"+ nonce <- o .: "nonce"+ size <- o .: "size"+ txs <- o .: "tx"+ TxHash merkle <- o .: "merkle"+ subsidy <- o .: "subsidy"+ fee <- o .: "fees"+ outputs <- o .: "outputs"+ work <- o .: "work"+ weight <- o .:? "weight" .!= 0+ return BlockData {header = BlockHeader {..}, ..}++instance MarshalJSON Network BlockData where+ marshalValue net b =+ A.object $+ [ "hash" .= headerHash b.header,+ "height" .= b.height,+ "mainchain" .= b.main,+ "previous" .= b.header.prev,+ "time" .= b.header.timestamp,+ "version" .= b.header.version,+ "bits" .= b.header.bits,+ "nonce" .= b.header.nonce,+ "size" .= b.size,+ "tx" .= b.txs,+ "merkle" .= TxHash b.header.merkle,+ "subsidy" .= b.subsidy,+ "fees" .= b.fee,+ "outputs" .= b.outputs,+ "work" .= b.work+ ]+ <> ["weight" .= b.weight | net.segWit]++ marshalEncoding net b =+ A.pairs $+ mconcat+ [ "hash" `A.pair` toEncoding (headerHash b.header),+ "height" `A.pair` A.word32 b.height,+ "mainchain" `A.pair` A.bool b.main,+ "previous" `A.pair` toEncoding b.header.prev,+ "time" `A.pair` A.word32 b.header.timestamp,+ "version" `A.pair` A.word32 b.header.version,+ "bits" `A.pair` A.word32 b.header.bits,+ "nonce" `A.pair` A.word32 b.header.nonce,+ "size" `A.pair` A.word32 b.size,+ "tx" `A.pair` A.list toEncoding b.txs,+ "merkle" `A.pair` toEncoding (TxHash b.header.merkle),+ "subsidy" `A.pair` A.word64 b.subsidy,+ "fees" `A.pair` A.word64 b.fee,+ "outputs" `A.pair` A.word64 b.outputs,+ "work" `A.pair` A.integer b.work,+ bool mempty ("weight" `A.pair` A.word32 b.weight) net.segWit+ ]++ unmarshalValue net = parseJSON++data StoreInput+ = StoreCoinbase+ { outpoint :: !OutPoint,+ sequence :: !Word32,+ script :: !ByteString,+ witness :: !WitnessStack+ }+ | StoreInput+ { outpoint :: !OutPoint,+ sequence :: !Word32,+ script :: !ByteString,+ pkscript :: !ByteString,+ value :: !Word64,+ witness :: !WitnessStack,+ address :: !(Maybe Address)+ }+ deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)++instance Serial StoreInput where+ serialize i@StoreCoinbase {} = do+ putWord8 0x00+ serialize i.outpoint+ putWord32be i.sequence+ putLengthBytes i.script+ putList putLengthBytes i.witness+ serialize i@StoreInput {} = do+ putWord8 0x01+ serialize i.outpoint+ putWord32be i.sequence+ putLengthBytes i.script+ putLengthBytes i.pkscript+ putWord64be i.value+ putList putLengthBytes i.witness+ putMaybe serialize i.address++ deserialize =+ getWord8 >>= \case+ 0x00 -> do+ outpoint <- deserialize+ sequence <- getWord32be+ script <- getLengthBytes+ witness <- getList getLengthBytes+ return StoreCoinbase {..}+ 0x01 -> do+ outpoint <- deserialize+ sequence <- getWord32be+ script <- getLengthBytes+ pkscript <- getLengthBytes+ value <- getWord64be+ witness <- getList getLengthBytes+ address <- getMaybe deserialize+ return StoreInput {..}+ x -> fail $ "Unknown input id: " <> cs (show x)++instance Serialize StoreInput where+ put = serialize+ get = deserialize++instance Binary StoreInput where+ put = serialize+ get = deserialize++isCoinbaseTx :: Tx -> Bool+isCoinbaseTx = all ((== nullOutPoint) . (.outpoint)) . (.inputs)++isCoinbase :: StoreInput -> Bool+isCoinbase StoreCoinbase {} = True+isCoinbase StoreInput {} = False++instance MarshalJSON Network StoreInput where+ marshalValue net i@StoreInput {} =+ A.object+ [ "coinbase" .= False,+ "txid" .= i.outpoint.hash,+ "output" .= i.outpoint.index,+ "sigscript" .= String (encodeHex i.script),+ "sequence" .= i.sequence,+ "pkscript" .= String (encodeHex i.pkscript),+ "value" .= i.value,+ "address" .= (marshalValue net <$> i.address),+ "witness" .= map encodeHex i.witness+ ]+ marshalValue net i@StoreCoinbase {} =+ A.object+ [ "coinbase" .= True,+ "txid" .= i.outpoint.hash,+ "output" .= i.outpoint.index,+ "sigscript" .= String (encodeHex i.script),+ "sequence" .= i.sequence,+ "pkscript" .= Null,+ "value" .= Null,+ "address" .= Null,+ "witness" .= map encodeHex i.witness+ ]++ marshalEncoding net i@StoreInput {} =+ A.pairs $+ mconcat+ [ "coinbase" `A.pair` A.bool False,+ "txid" `A.pair` toEncoding i.outpoint.hash,+ "output" `A.pair` A.word32 i.outpoint.index,+ "sigscript" `A.pair` hexEncoding (B.fromStrict i.script),+ "sequence" `A.pair` A.word32 i.sequence,+ "pkscript" `A.pair` hexEncoding (B.fromStrict i.pkscript),+ "value" `A.pair` A.word64 i.value,+ "address" `A.pair` maybe A.null_ (marshalEncoding net) i.address,+ "witness" `A.pair` A.list (hexEncoding . B.fromStrict) i.witness+ ]+ marshalEncoding net i@StoreCoinbase {} =+ A.pairs $+ mconcat+ [ "coinbase" .= True,+ "txid" `A.pair` toEncoding i.outpoint.hash,+ "output" `A.pair` A.word32 i.outpoint.index,+ "sigscript" `A.pair` hexEncoding (B.fromStrict i.script),+ "sequence" `A.pair` A.word32 i.sequence,+ "pkscript" `A.pair` A.null_,+ "value" `A.pair` A.null_,+ "address" `A.pair` A.null_,+ "witness" `A.pair` A.list (hexEncoding . B.fromStrict) i.witness+ ]++ unmarshalValue net =+ A.withObject "StoreInput" $ \o -> do+ coinbase <- o .: "coinbase"+ outpoint <- OutPoint <$> o .: "txid" <*> o .: "output"+ sequence <- o .: "sequence"+ witness <- mapM jsonHex =<< o .:? "witness" .!= []+ script <- o .: "sigscript" >>= jsonHex+ if coinbase+ then return StoreCoinbase {..}+ else do+ pkscript <- o .: "pkscript" >>= jsonHex+ value <- o .: "value"+ address <-+ o .: "address"+ >>= maybe (return Nothing) (optional . unmarshalValue net)+ return StoreInput {..}++jsonHex :: Text -> Parser ByteString+jsonHex s =+ case decodeHex s of+ Nothing -> fail "Could not decode hex"+ Just b -> return b++-- | Information about input spending output.+data Spender = Spender+ { -- | input transaction hash+ txid :: !TxHash,+ -- | input position in transaction+ index :: !Word32+ }+ deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)++instance Serial Spender where+ serialize s = do+ serialize s.txid+ putWord32be s.index+ deserialize = Spender <$> deserialize <*> getWord32be++instance Serialize Spender where+ put = serialize+ get = deserialize++instance Binary Spender where+ put = serialize+ get = deserialize++instance ToJSON Spender where+ toJSON s =+ A.object+ [ "txid" .= s.txid,+ "input" .= s.index+ ]+ toEncoding s =+ A.pairs $+ mconcat+ [ "txid" `A.pair` toEncoding s.txid,+ "input" `A.pair` A.word32 s.index+ ]++instance FromJSON Spender where+ parseJSON =+ A.withObject "Spender" $ \o ->+ Spender <$> o .: "txid" <*> o .: "input"++-- | Output information.+data StoreOutput = StoreOutput+ { value :: !Word64,+ script :: !ByteString,+ spender :: !(Maybe Spender),+ address :: !(Maybe Address)+ }+ deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)++instance Serial StoreOutput where+ serialize o = do+ putWord64be o.value+ putLengthBytes o.script+ putMaybe serialize o.spender+ putMaybe serialize o.address+ deserialize = do+ value <- getWord64be+ script <- getLengthBytes+ spender <- getMaybe deserialize+ address <- getMaybe deserialize+ return StoreOutput {..}++instance Serialize StoreOutput where+ put = serialize+ get = deserialize++instance Binary StoreOutput where+ put = serialize+ get = deserialize++instance MarshalJSON Network StoreOutput where+ marshalValue net o =+ A.object+ [ "address" .= (marshalValue net <$> o.address),+ "pkscript" .= encodeHex o.script,+ "value" .= o.value,+ "spent" .= isJust o.spender,+ "spender" .= o.spender+ ]++ marshalEncoding net o =+ A.pairs $+ mconcat+ [ "address" `A.pair` maybe A.null_ (marshalEncoding net) o.address,+ "pkscript" `A.pair` hexEncoding (B.fromStrict o.script),+ "value" `A.pair` A.word64 o.value,+ "spent" `A.pair` A.bool (isJust o.spender),+ "spender" `A.pair` toEncoding o.spender+ ]++ unmarshalValue net =+ A.withObject "StoreOutput" $ \o -> do+ value <- o .: "value"+ script <- o .: "pkscript" >>= jsonHex+ spender <- o .: "spender"+ address <-+ o .: "address"+ >>= maybe (return Nothing) (optional . unmarshalValue net)+ return StoreOutput {..}++data Prev = Prev+ { script :: !ByteString,+ value :: !Word64+ }+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)++instance Serial Prev where+ serialize p = do+ putLengthBytes p.script+ putWord64be p.value+ deserialize = do+ script <- getLengthBytes+ value <- getWord64be+ return Prev {..}++instance Binary Prev where+ put = serialize+ get = deserialize++instance Serialize Prev where+ put = serialize+ get = deserialize++toInput :: Ctx -> TxIn -> Maybe Prev -> WitnessStack -> StoreInput+toInput ctx i Nothing w =+ StoreCoinbase+ { outpoint = i.outpoint,+ sequence = i.sequence,+ script = i.script,+ witness = w+ }+toInput ctx i (Just p) w =+ StoreInput+ { outpoint = i.outpoint,+ sequence = i.sequence,+ script = i.script,+ pkscript = p.script,+ value = p.value,+ witness = w,+ address = eitherToMaybe (scriptToAddressBS ctx p.script)+ }++toOutput :: Ctx -> TxOut -> Maybe Spender -> StoreOutput+toOutput ctx o s =+ StoreOutput+ { value = o.value,+ script = o.script,+ spender = s,+ address = eitherToMaybe (scriptToAddressBS ctx o.script)+ }++data TxData = TxData+ { block :: !BlockRef,+ tx :: !Tx,+ prevs :: !(IntMap Prev),+ deleted :: !Bool,+ rbf :: !Bool,+ timestamp :: !Word64,+ spenders :: !(IntMap Spender)+ }+ deriving (Show, Eq, Ord, Generic, NFData)++instance Serial TxData where+ serialize t = do+ serialize t.block+ serialize t.tx+ putIntMap (putWord64be . fromIntegral) serialize t.prevs+ serialize t.deleted+ serialize t.rbf+ putWord64be t.timestamp+ putIntMap (putWord64be . fromIntegral) serialize t.spenders+ deserialize = do+ block <- deserialize+ tx <- deserialize+ prevs <- getIntMap (fromIntegral <$> getWord64be) deserialize+ deleted <- deserialize+ rbf <- deserialize+ timestamp <- getWord64be+ spenders <- getIntMap (fromIntegral <$> getWord64be) deserialize+ return TxData {..}++instance Serialize TxData where+ put = serialize+ get = deserialize++instance Binary TxData where+ put = serialize+ get = deserialize++txDataFee :: TxData -> Word64+txDataFee t =+ if isCoinbaseTx t.tx+ then 0+ else inputs - outputs+ where+ inputs = sum $ map (.value) $ IntMap.elems t.prevs+ outputs = sum $ map (.value) t.tx.outputs++toTransaction :: Ctx -> TxData -> Transaction+toTransaction ctx t =+ Transaction+ { version = t.tx.version,+ locktime = t.tx.locktime,+ block = t.block,+ deleted = t.deleted,+ rbf = t.rbf,+ timestamp = t.timestamp,+ txid = txHash t.tx,+ inputs = ins,+ outputs = outs,+ size =+ fromIntegral $+ B.length $+ runPutS $+ serialize t.tx,+ weight =+ let b = B.length $ runPutS $ serialize witless+ x = B.length $ runPutS $ serialize t.tx+ in fromIntegral $ b * 3 + x,+ fee =+ if any isCoinbase ins+ then 0+ else inv - outv+ }+ where+ ins = zipWith f [0 ..] t.tx.inputs+ witless =+ let Tx {..} = t.tx+ in Tx {witness = [], ..}+ inv = sum (map (.value) ins)+ outs = zipWith g [0 ..] t.tx.outputs+ outv = sum $ map (.value) outs+ ws = take (length t.tx.inputs) $ t.tx.witness <> repeat []+ f n i = toInput ctx i (IntMap.lookup n t.prevs) (ws !! n)+ g n o = toOutput ctx o $ IntMap.lookup n t.spenders++fromTransaction :: Transaction -> TxData+fromTransaction t =+ TxData+ { block = t.block,+ tx = tx,+ deleted = t.deleted,+ rbf = t.rbf,+ timestamp = t.timestamp,+ prevs =+ IntMap.fromList $+ catMaybes $+ zipWith f [0 ..] t.inputs,+ spenders =+ IntMap.fromList $+ catMaybes $+ zipWith g [0 ..] t.outputs+ }+ where+ tx = transactionData t+ f _ StoreCoinbase {} = Nothing+ f n StoreInput {script = s, value = v} =+ Just (n, Prev {script = s, value = v})+ g _ StoreOutput {spender = Nothing} = Nothing+ g n StoreOutput {spender = Just s} = Just (n, s)++-- | Detailed transaction information.+data Transaction = Transaction+ { -- | block information for this transaction+ block :: !BlockRef,+ -- | transaction version+ version :: !Word32,+ -- | lock time+ locktime :: !Word32,+ -- | transaction inputs+ inputs :: ![StoreInput],+ -- | transaction outputs+ outputs :: ![StoreOutput],+ -- | this transaction has been deleted and is no longer valid+ deleted :: !Bool,+ -- | this transaction can be replaced in the mempool+ rbf :: !Bool,+ -- | time the transaction was first seen or time of block+ timestamp :: !Word64,+ -- | transaction id+ txid :: !TxHash,+ -- | serialized transaction size (includes witness data)+ size :: !Word32,+ -- | transaction weight+ weight :: !Word32,+ -- | fees that this transaction pays (0 for coinbase)+ fee :: !Word64+ }+ deriving (Show, Eq, Ord, Generic, Hashable, NFData)++instance Serial Transaction where+ serialize t = do+ serialize t.block+ putWord32be t.version+ putWord32be t.locktime+ putList serialize t.inputs+ putList serialize t.outputs+ serialize t.deleted+ serialize t.rbf+ putWord64be t.timestamp+ serialize t.txid+ putWord32be t.size+ putWord32be t.weight+ putWord64be t.fee+ deserialize = do+ block <- deserialize+ version <- getWord32be+ locktime <- getWord32be+ inputs <- getList deserialize+ outputs <- getList deserialize+ deleted <- deserialize+ rbf <- deserialize+ timestamp <- getWord64be+ txid <- deserialize+ size <- getWord32be+ weight <- getWord32be+ fee <- getWord64be+ return Transaction {..}++instance Serialize Transaction where+ put = serialize+ get = deserialize++instance Binary Transaction where+ put = serialize+ get = deserialize++transactionData :: Transaction -> Tx+transactionData t =+ Tx+ { inputs = map i t.inputs,+ outputs = map o t.outputs,+ version = t.version,+ locktime = t.locktime,+ witness = w $ map (.witness) t.inputs+ }+ where+ i StoreCoinbase {..} = TxIn {..}+ i StoreInput {..} = TxIn {..}+ o StoreOutput {..} = TxOut {..}+ w xs+ | all null xs = []+ | otherwise = xs++instance MarshalJSON Network Transaction where+ marshalValue net t =+ A.object+ [ "txid" .= t.txid,+ "size" .= t.size,+ "version" .= t.version,+ "locktime" .= t.locktime,+ "fee" .= t.fee,+ "inputs" .= map (marshalValue net) t.inputs,+ "outputs" .= map (marshalValue net) t.outputs,+ "block" .= t.block,+ "deleted" .= t.deleted,+ "time" .= t.timestamp,+ "rbf" .= t.rbf,+ "weight" .= t.weight+ ]++ marshalEncoding net t =+ A.pairs $+ mconcat+ [ "txid" `A.pair` toEncoding t.txid,+ "size" `A.pair` A.word32 t.size,+ "version" `A.pair` A.word32 t.version,+ "locktime" `A.pair` A.word32 t.locktime,+ "fee" `A.pair` A.word64 t.fee,+ "inputs" `A.pair` A.list (marshalEncoding net) t.inputs,+ "outputs" `A.pair` A.list (marshalEncoding net) t.outputs,+ "block" `A.pair` toEncoding t.block,+ "deleted" `A.pair` A.bool t.deleted,+ "time" `A.pair` A.word64 t.timestamp,+ "rbf" `A.pair` A.bool t.rbf,+ "weight" `A.pair` A.word32 t.weight+ ]++ unmarshalValue net = A.withObject "Transaction" $ \o -> do+ version <- o .: "version"+ locktime <- o .: "locktime"+ inputs <- o .: "inputs" >>= mapM (unmarshalValue net)+ outputs <- o .: "outputs" >>= mapM (unmarshalValue net)+ block <- o .: "block"+ deleted <- o .: "deleted"+ timestamp <- o .: "time"+ rbf <- o .:? "rbf" .!= False+ weight <- o .:? "weight" .!= 0+ size <- o .: "size"+ txid <- o .: "txid"+ fee <- o .: "fee"+ return Transaction {..}++-- | Information about a connected peer.+data PeerInfo = PeerInfo+ { -- | user agent string+ userAgent :: !ByteString,+ -- | network address+ address :: !String,+ -- | version number+ version :: !Word32,+ -- | services field+ services :: !Word64,+ -- | will relay transactions+ relay :: !Bool+ }+ deriving (Show, Eq, Ord, Generic, NFData)++instance Serial PeerInfo where+ serialize p = do+ putLengthBytes p.userAgent+ putLengthBytes $ T.encodeUtf8 $ T.pack p.address+ putWord32be p.version+ putWord64be p.services+ serialize p.relay+ deserialize = do+ userAgent <- getLengthBytes+ address <- T.unpack . T.decodeUtf8 <$> getLengthBytes+ version <- getWord32be+ services <- getWord64be+ relay <- deserialize+ return PeerInfo {..}++instance Serialize PeerInfo where+ put = serialize+ get = deserialize++instance Binary PeerInfo where+ put = serialize+ get = deserialize++instance ToJSON PeerInfo where+ toJSON p =+ A.object+ [ "useragent" .= String (T.decodeUtf8 p.userAgent),+ "address" .= p.address,+ "version" .= p.version,+ "services"+ .= String (encodeHex $ runPutS $ serialize p.services),+ "relay" .= p.relay+ ]+ toEncoding p =+ A.pairs $+ mconcat+ [ "useragent" `A.pair` A.text (T.decodeUtf8 p.userAgent),+ "address" `A.pair` toEncoding p.address,+ "version" `A.pair` A.word32 p.version,+ "services" `A.pair` hexEncoding (runPutL $ serialize p.services),+ "relay" `A.pair` A.bool p.relay+ ]++instance FromJSON PeerInfo where+ parseJSON =+ A.withObject "PeerInfo" $ \o -> do+ String userAgentText <- o .: "useragent"+ let userAgent = T.encodeUtf8 userAgentText+ address <- o .: "address"+ version <- o .: "version"+ services <-+ o .: "services" >>= jsonHex >>= \b ->+ case runGetS deserialize b of+ Left e -> fail $ "Could not decode services: " <> e+ Right s -> return s+ relay <- o .: "relay"+ return PeerInfo {..}++-- | Address balances for an extended public key.+data XPubBal = XPubBal+ { path :: ![KeyIndex],+ balance :: !Balance+ }+ deriving (Show, Ord, Eq, Generic, NFData)++instance Serial XPubBal where+ serialize b = do+ putList putWord32be b.path+ serialize b.balance+ deserialize = do+ path <- getList getWord32be+ balance <- deserialize+ return XPubBal {..}++instance Serialize XPubBal where+ put = serialize+ get = deserialize++instance Binary XPubBal where+ put = serialize+ get = deserialize++instance MarshalJSON Network XPubBal where+ marshalValue net b =+ A.object+ [ "path" .= b.path,+ "balance" .= marshalValue net b.balance+ ]++ marshalEncoding net b =+ A.pairs $+ mconcat+ [ "path" `A.pair` A.list A.word32 b.path,+ "balance" `A.pair` marshalEncoding net b.balance+ ]++ unmarshalValue net =+ A.withObject "XPubBal" $ \o -> do+ path <- o .: "path"+ balance <- unmarshalValue net =<< o .: "balance"+ return XPubBal {..}++-- | Unspent transaction for extended public key.+data XPubUnspent = XPubUnspent+ { unspent :: !Unspent,+ path :: ![KeyIndex]+ }+ deriving (Show, Eq, Ord, Generic, NFData)++instance Serial XPubUnspent where+ serialize u = do+ putList putWord32be u.path+ serialize u.unspent+ deserialize = do+ path <- getList getWord32be+ unspent <- deserialize+ return XPubUnspent {..}++instance Serialize XPubUnspent where+ put = serialize+ get = deserialize++instance Binary XPubUnspent where+ put = serialize+ get = deserialize++instance MarshalJSON Network XPubUnspent where+ marshalValue net u =+ A.object+ [ "unspent" .= marshalValue net u.unspent,+ "path" .= u.path+ ]++ marshalEncoding net u =+ A.pairs $+ mconcat+ [ "unspent" `A.pair` marshalEncoding net u.unspent,+ "path" `A.pair` A.list A.word32 u.path+ ]++ unmarshalValue net =+ A.withObject "XPubUnspent" $ \o -> do+ unspent <- o .: "unspent" >>= unmarshalValue net+ path <- o .: "path"+ return XPubUnspent {..}++data XPubSummary = XPubSummary+ { confirmed :: !Word64,+ unconfirmed :: !Word64,+ received :: !Word64,+ utxo :: !Word64,+ external :: !Word32,+ change :: !Word32+ }+ deriving (Eq, Show, Generic, NFData)++instance Serial XPubSummary where+ serialize s = do+ putWord64be s.confirmed+ putWord64be s.unconfirmed+ putWord64be s.received+ putWord64be s.utxo+ putWord32be s.external+ putWord32be s.change+ deserialize = do+ confirmed <- getWord64be+ unconfirmed <- getWord64be+ received <- getWord64be+ utxo <- getWord64be+ external <- getWord32be+ change <- getWord32be+ return XPubSummary {..}++instance Binary XPubSummary where+ put = serialize+ get = deserialize++instance Serialize XPubSummary where+ put = serialize+ get = deserialize++instance ToJSON XPubSummary where+ toJSON s =+ A.object+ [ "balance"+ .= A.object+ [ "confirmed" .= s.confirmed,+ "unconfirmed" .= s.unconfirmed,+ "received" .= s.received,+ "utxo" .= s.utxo+ ],+ "indices"+ .= A.object+ [ "change" .= s.change,+ "external" .= s.external+ ]+ ]+ toEncoding s =+ A.pairs $+ mconcat+ [ A.pair "balance" $+ A.pairs $+ mconcat+ [ "confirmed" `A.pair` A.word64 s.confirmed,+ "unconfirmed" `A.pair` A.word64 s.unconfirmed,+ "received" `A.pair` A.word64 s.received,+ "utxo" `A.pair` A.word64 s.utxo+ ],+ A.pair "indices" $+ A.pairs $+ mconcat+ [ "change" `A.pair` A.word32 s.change,+ "external" `A.pair` A.word32 s.external+ ]+ ]++instance FromJSON XPubSummary where+ parseJSON =+ A.withObject "XPubSummary" $ \o -> do+ b <- o .: "balance"+ i <- o .: "indices"+ confirmed <- b .: "confirmed"+ unconfirmed <- b .: "unconfirmed"+ received <- b .: "received"+ utxo <- b .: "utxo"+ change <- i .: "change"+ external <- i .: "external"+ return XPubSummary {..}++class Healthy a where+ isOK :: a -> Bool++data BlockHealth = BlockHealth+ { headers :: !BlockHeight,+ blocks :: !BlockHeight,+ max :: !Int32+ }+ deriving (Show, Eq, Generic, NFData)++instance Serial BlockHealth where+ serialize h = do+ serialize (isOK h)+ putWord32be h.headers+ putWord32be h.blocks+ putInt32be h.max+ deserialize = do+ k <- deserialize+ headers <- getWord32be+ blocks <- getWord32be+ max <- getInt32be+ let h = BlockHealth {..}+ unless (k == isOK h) $ fail "Inconsistent health check"+ return h++instance Serialize BlockHealth where+ put = serialize+ get = deserialize++instance Binary BlockHealth where+ put = serialize+ get = deserialize++instance Healthy BlockHealth where+ isOK x =+ h - b <= x.max+ where+ h = fromIntegral x.headers+ b = fromIntegral x.blocks++instance ToJSON BlockHealth where+ toJSON h =+ A.object+ [ "headers" .= h.headers,+ "blocks" .= h.blocks,+ "diff" .= diff,+ "max" .= h.max,+ "ok" .= isOK h+ ]+ where+ diff = toInteger h.headers - toInteger h.blocks++instance FromJSON BlockHealth where+ parseJSON =+ A.withObject "BlockHealth" $ \o -> do+ headers <- o .: "headers"+ blocks <- o .: "blocks"+ max <- o .: "max"+ return BlockHealth {..}++data TimeHealth = TimeHealth+ { age :: !Int64,+ max :: !Int64+ }+ deriving (Show, Eq, Generic, NFData)++instance Serial TimeHealth where+ serialize h = do+ serialize (isOK h)+ putInt64be h.age+ putInt64be h.max+ deserialize = do+ k <- deserialize+ age <- getInt64be+ max <- getInt64be+ let t = TimeHealth {..}+ unless (k == isOK t) $ fail "Inconsistent health check"+ return t++instance Binary TimeHealth where+ put = serialize+ get = deserialize++instance Serialize TimeHealth where+ put = serialize+ get = deserialize++instance Healthy TimeHealth where+ isOK TimeHealth {..} =+ age <= max++instance ToJSON TimeHealth where+ toJSON h =+ A.object+ [ "age" .= h.age,+ "max" .= h.max,+ "ok" .= isOK h+ ]++instance FromJSON TimeHealth where+ parseJSON =+ A.withObject "TimeHealth" $ \o -> do+ age <- o .: "age"+ max <- o .: "max"+ return TimeHealth {..}++data CountHealth = CountHealth+ { count :: !Int64,+ min :: !Int64+ }+ deriving (Show, Eq, Generic, NFData)++instance Serial CountHealth where+ serialize h = do+ serialize (isOK h)+ putInt64be h.count+ putInt64be h.min+ deserialize = do+ k <- deserialize+ count <- getInt64be+ min <- getInt64be+ let c = CountHealth {..}+ unless (k == isOK c) $ fail "Inconsistent health check"+ return c++instance Serialize CountHealth where+ put = serialize+ get = deserialize++instance Binary CountHealth where+ put = serialize+ get = deserialize++instance Healthy CountHealth where+ isOK CountHealth {..} = min <= count++instance ToJSON CountHealth where+ toJSON h =+ A.object+ [ "count" .= h.count,+ "min" .= h.min,+ "ok" .= isOK h+ ]++instance FromJSON CountHealth where+ parseJSON =+ A.withObject "CountHealth" $ \o -> do+ count <- o .: "count"+ min <- o .: "min"+ return CountHealth {..}++data MaxHealth = MaxHealth+ { count :: !Int64,+ max :: !Int64+ }+ deriving (Show, Eq, Generic, NFData)++instance Serial MaxHealth where+ serialize h = do+ serialize $ isOK h+ putInt64be h.count+ putInt64be h.max+ deserialize = do+ k <- deserialize+ count <- getInt64be+ max <- getInt64be+ let h = MaxHealth {..}+ unless (k == isOK h) $ fail "Inconsistent health check"+ return h++instance Binary MaxHealth where+ put = serialize+ get = deserialize++instance Serialize MaxHealth where+ put = serialize+ get = deserialize++instance Healthy MaxHealth where+ isOK MaxHealth {..} = count <= max++instance ToJSON MaxHealth where+ toJSON h =+ A.object+ [ "count" .= h.count,+ "max" .= h.max,+ "ok" .= isOK h+ ]++instance FromJSON MaxHealth where+ parseJSON =+ A.withObject "MaxHealth" $ \o -> do+ count <- o .: "count"+ max <- o .: "max"+ return MaxHealth {..}++data HealthCheck = HealthCheck+ { blocks :: !BlockHealth,+ lastBlock :: !TimeHealth,+ lastTx :: !TimeHealth,+ pendingTxs :: !MaxHealth,+ peers :: !CountHealth,+ network :: !String,+ version :: !String,+ time :: !Word64+ }+ deriving (Show, Eq, Generic, NFData)++instance Serial HealthCheck where+ serialize h = do+ serialize $ isOK h+ serialize h.blocks+ serialize h.lastBlock+ serialize h.lastTx+ serialize h.pendingTxs+ serialize h.peers+ putLengthBytes $ T.encodeUtf8 $ T.pack h.network+ putLengthBytes $ T.encodeUtf8 $ T.pack h.version+ putWord64be h.time+ deserialize = do+ k <- deserialize+ blocks <- deserialize+ lastBlock <- deserialize+ lastTx <- deserialize+ pendingTxs <- deserialize+ peers <- deserialize+ network <- T.unpack . T.decodeUtf8 <$> getLengthBytes+ version <- T.unpack . T.decodeUtf8 <$> getLengthBytes+ time <- getWord64be+ let h = HealthCheck {..}+ unless (k == isOK h) $ fail "Inconsistent health check"+ return h++instance Binary HealthCheck where+ put = serialize+ get = deserialize++instance Serialize HealthCheck where+ put = serialize+ get = deserialize++instance Healthy HealthCheck where+ isOK h =+ and+ [ isOK h.blocks,+ isOK h.lastBlock,+ isOK h.lastTx,+ isOK h.pendingTxs,+ isOK h.peers+ ]++instance ToJSON HealthCheck where+ toJSON h =+ A.object+ [ "blocks" .= h.blocks,+ "last-block" .= h.lastBlock,+ "last-tx" .= h.lastTx,+ "pending-txs" .= h.pendingTxs,+ "peers" .= h.peers,+ "net" .= h.network,+ "version" .= h.version,+ "time" .= show (posixSecondsToUTCTime $ fromIntegral h.time),+ "ok" .= isOK h+ ]++instance FromJSON HealthCheck where+ parseJSON =+ A.withObject "HealthCheck" $ \o -> do+ blocks <- o .: "blocks"+ lastBlock <- o .: "last-block"+ lastTx <- o .: "last-tx"+ pendingTxs <- o .: "pending-txs"+ peers <- o .: "peers"+ network <- o .: "net"+ version <- o .: "version"+ utcTime <- read <$> o .: "time"+ let time = round $ utcTimeToPOSIXSeconds utcTime+ return HealthCheck {..}++data Event+ = EventBlock !BlockHash+ | EventTx !TxHash+ deriving (Show, Eq, Generic, NFData)++instance Serial Event where+ serialize (EventBlock bh) = putWord8 0x00 >> serialize bh+ serialize (EventTx th) = putWord8 0x01 >> serialize th+ deserialize =+ getWord8 >>= \case+ 0x00 -> EventBlock <$> deserialize+ 0x01 -> EventTx <$> deserialize+ _ -> fail "Not an Event"++instance Serialize Event where+ put = serialize+ get = deserialize++instance Binary Event where+ put = serialize+ get = deserialize++instance ToJSON Event where+ toJSON (EventTx h) =+ A.object ["type" .= String "tx", "id" .= h]+ toJSON (EventBlock h) =+ A.object ["type" .= String "block", "id" .= h]+ toEncoding (EventTx h) =+ A.pairs ("type" `A.pair` A.text "tx" <> "id" `A.pair` toEncoding h)+ toEncoding (EventBlock h) =+ A.pairs ("type" `A.pair` A.text "block" <> "id" `A.pair` toEncoding h)++instance FromJSON Event where+ parseJSON =+ A.withObject "event" $ \o -> do+ t <- o .: "type"+ case t of+ "tx" -> do+ i <- o .: "id"+ return $ EventTx i+ "block" -> do+ i <- o .: "id"+ return $ EventBlock i+ _ -> fail $ "Could not recognize event type: " <> t++newtype GenericResult a = GenericResult {get :: a}+ deriving (Show, Eq, Generic, NFData)++instance (Serial a) => Serial (GenericResult a) where+ serialize (GenericResult x) = serialize x+ deserialize = GenericResult <$> deserialize++instance (Serial a) => Serialize (GenericResult a) where+ put = serialize+ get = deserialize++instance (Serial a) => Binary (GenericResult a) where+ put = serialize+ get = deserialize++instance (ToJSON a) => ToJSON (GenericResult a) where+ toJSON (GenericResult b) = A.object ["result" .= b]+ toEncoding (GenericResult b) = A.pairs ("result" `A.pair` toEncoding b)++instance (FromJSON a) => FromJSON (GenericResult a) where+ parseJSON =+ A.withObject "GenericResult" $ \o -> GenericResult <$> o .: "result"++newtype RawResult a = RawResult {get :: a}+ deriving (Show, Eq, Generic, NFData)++instance (Serial a) => Serial (RawResult a) where+ serialize (RawResult x) = serialize x+ deserialize = RawResult <$> deserialize++instance (Serial a) => Serialize (RawResult a) where+ put = serialize+ get = deserialize++instance (Serial a) => Binary (RawResult a) where+ put = serialize+ get = deserialize++instance (Serial a) => ToJSON (RawResult a) where+ toJSON (RawResult b) =+ A.object ["result" .= String (encodeHex $ runPutS $ serialize b)]+ toEncoding (RawResult b) =+ A.pairs $ "result" `A.pair` hexEncoding (runPutL $ serialize b)++instance (Serial a) => FromJSON (RawResult a) where+ parseJSON =+ A.withObject "RawResult" $ \o -> do+ res <- o .: "result"+ let m =+ eitherToMaybe . Bytes.Get.runGetS deserialize+ =<< decodeHex res+ maybe mzero (return . RawResult) m++newtype SerialList a = SerialList {get :: [a]}+ deriving (Show, Eq, Generic, NFData)++instance Semigroup (SerialList a) where+ SerialList a <> SerialList b = SerialList (a <> b)++instance Monoid (SerialList a) where+ mempty = SerialList mempty++instance (Serial a) => Serial (SerialList a) where+ serialize (SerialList ls) = putList serialize ls+ deserialize = SerialList <$> getList deserialize++instance (ToJSON a) => ToJSON (SerialList a) where+ toJSON (SerialList ls) = toJSON ls+ toEncoding (SerialList ls) = A.list toEncoding ls++instance (FromJSON a) => FromJSON (SerialList a) where+ parseJSON = fmap SerialList . parseJSON++newtype RawResultList a = RawResultList {get :: [a]}+ deriving (Show, Eq, Generic, NFData)++instance (Serial a) => Serial (RawResultList a) where+ serialize (RawResultList xs) =+ mapM_ serialize xs+ deserialize = RawResultList <$> go+ where+ go =+ isEmpty >>= \case+ True -> return []+ False -> (:) <$> deserialize <*> go++instance (Serial a) => Serialize (RawResultList a) where+ put = serialize+ get = deserialize++instance (Serial a) => Binary (RawResultList a) where+ put = serialize+ get = deserialize++instance Semigroup (RawResultList a) where+ (RawResultList a) <> (RawResultList b) = RawResultList $ a <> b++instance Monoid (RawResultList a) where+ mempty = RawResultList mempty++instance (Serial a) => ToJSON (RawResultList a) where+ toJSON (RawResultList xs) =+ toJSON $ String . encodeHex . runPutS . serialize <$> xs+ toEncoding (RawResultList xs) =+ A.list (hexEncoding . runPutL . serialize) xs++instance (Serial a) => FromJSON (RawResultList a) where+ parseJSON =+ A.withArray "RawResultList" $ \vec ->+ RawResultList <$> mapM parseElem (toList vec)+ where+ deser = eitherToMaybe . runGetS deserialize+ parseElem =+ A.withText "RawResultListItem" $ \t ->+ maybe mzero return (decodeHex t >>= deser)++newtype TxId+ = TxId TxHash+ deriving (Show, Eq, Generic, NFData)++instance Serial TxId where+ serialize (TxId h) = serialize h+ deserialize = TxId <$> deserialize++instance Serialize TxId where+ put = serialize+ get = deserialize++instance Binary TxId where+ put = serialize+ get = deserialize++instance ToJSON TxId where+ toJSON (TxId h) = A.object ["txid" .= h]+ toEncoding (TxId h) = A.pairs ("txid" `A.pair` toEncoding h)++instance FromJSON TxId where+ parseJSON = A.withObject "txid" $ \o -> TxId <$> o .: "txid"++data Except+ = ThingNotFound+ | ServerError+ | BadRequest+ | UserError !String+ | StringError !String+ | TxIndexConflict ![TxHash]+ | ServerTimeout+ | RequestTooLarge+ deriving (Show, Eq, Ord, Generic, NFData)++instance Exception Except++instance ScottyError Except where+ stringError = StringError+ showError = LazyText.pack . show++instance ToJSON Except where+ toJSON e =+ A.object $+ case e of+ ThingNotFound ->+ [ "error" .= String "not-found-or-invalid-arg",+ "message" .= String "Item not found or argument invalid"+ ]+ ServerError ->+ [ "error" .= String "server-error",+ "message" .= String "Server error"+ ]+ BadRequest ->+ [ "error" .= String "bad-request",+ "message" .= String "Invalid request"+ ]+ UserError msg' ->+ [ "error" .= String "user-error",+ "message" .= String (cs msg')+ ]+ StringError msg' ->+ [ "error" .= String "string-error",+ "message" .= String (cs msg')+ ]+ TxIndexConflict txids ->+ [ "error" .= String "multiple-tx-index",+ "message" .= String "Multiple txs match that index",+ "txids" .= txids+ ]+ ServerTimeout ->+ [ "error" .= String "server-timeout",+ "message" .= String "Request is taking too long"+ ]+ RequestTooLarge ->+ [ "error" .= String "request-too-large",+ "message" .= String "Request body too large"+ ]++instance FromJSON Except where+ parseJSON =+ A.withObject "Except" $ \o -> do+ ctr <- o .: "error"+ msg' <- o .:? "message" .!= ""+ case ctr of+ String "not-found-or-invalid-arg" ->+ return ThingNotFound+ String "server-error" ->+ return ServerError+ String "bad-request" ->+ return BadRequest+ String "user-error" ->+ return $ UserError msg'+ String "string-error" ->+ return $ StringError msg'+ String "multiple-tx-index" -> do+ txids <- o .: "txids"+ return $ TxIndexConflict txids+ String "server-timeout" ->+ return ServerTimeout+ String "request-too-large" ->+ return RequestTooLarge+ _ -> mzero++---------------------------------------+-- Blockchain.info API Compatibility --+---------------------------------------++toIntTxId :: TxHash -> Word64+toIntTxId h =+ let bs = runPutS (serialize h)+ Right w64 = runGetS getWord64be bs+ in w64 `shift` (-11)++data BinfoBlockId+ = BinfoBlockHash !BlockHash+ | BinfoBlockIndex !Word32+ deriving (Eq, Show, Read, Generic, NFData)++instance Parsable BinfoBlockId where+ parseParam t =+ hex <> igr+ where+ hex = case hexToBlockHash (LazyText.toStrict t) of+ Nothing -> Left "could not decode txid"+ Just h -> Right $ BinfoBlockHash h+ igr = BinfoBlockIndex <$> parseParam t++data BinfoTxId+ = BinfoTxIdHash !TxHash+ | BinfoTxIdIndex !Word64+ deriving (Eq, Show, Read, Generic, NFData)++encodeBinfoTxId :: Bool -> TxHash -> BinfoTxId+encodeBinfoTxId False = BinfoTxIdHash+encodeBinfoTxId True = BinfoTxIdIndex . toIntTxId++instance Parsable BinfoTxId where+ parseParam t =+ hex <> igr+ where+ hex =+ case hexToTxHash (LazyText.toStrict t) of+ Nothing -> Left "could not decode txid"+ Just h -> Right $ BinfoTxIdHash h+ igr = BinfoTxIdIndex <$> parseParam t++instance ToJSON BinfoTxId where+ toJSON (BinfoTxIdHash h) = toJSON h+ toJSON (BinfoTxIdIndex i) = toJSON i+ toEncoding (BinfoTxIdHash h) = toEncoding h+ toEncoding (BinfoTxIdIndex i) = A.word64 i++instance FromJSON BinfoTxId where+ parseJSON v =+ BinfoTxIdHash <$> parseJSON v+ <|> BinfoTxIdIndex <$> parseJSON v++data BinfoFilter+ = BinfoFilterAll+ | BinfoFilterSent+ | BinfoFilterReceived+ | BinfoFilterMoved+ | BinfoFilterConfirmed+ | BinfoFilterMempool+ deriving (Eq, Show, Generic, NFData)++instance Parsable BinfoFilter where+ parseParam t =+ parseParam t >>= \case+ (0 :: Int) -> return BinfoFilterAll+ 1 -> return BinfoFilterSent+ 2 -> return BinfoFilterReceived+ 3 -> return BinfoFilterMoved+ 5 -> return BinfoFilterConfirmed+ 6 -> return BinfoFilterAll+ 7 -> return BinfoFilterMempool+ _ -> Left "could not parse filter parameter"++data BinfoMultiAddr = BinfoMultiAddr+ { addresses :: ![BinfoBalance],+ wallet :: !BinfoWallet,+ txs :: ![BinfoTx],+ info :: !BinfoInfo,+ recommendFee :: !Bool,+ cashAddr :: !Bool+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoMultiAddr where+ marshalValue (net', ctx) m =+ A.object $+ [ "addresses" .= map (marshalValue (net, ctx)) m.addresses,+ "wallet" .= m.wallet,+ "txs" .= map (marshalValue (net, ctx)) m.txs,+ "info" .= m.info,+ "recommend_include_fee" .= m.recommendFee+ ]+ ++ ["cash_addr" .= True | m.cashAddr]+ where+ net = if not m.cashAddr && net' == bch then btc else net'++ unmarshalValue (net, ctx) =+ A.withObject "BinfoMultiAddr" $ \o -> do+ addresses <- mapM (unmarshalValue (net, ctx)) =<< o .: "addresses"+ wallet <- o .: "wallet"+ txs <- mapM (unmarshalValue (net, ctx)) =<< o .: "txs"+ info <- o .: "info"+ recommendFee <- o .: "recommend_include_fee"+ cashAddr <- o .:? "cash_addr" .!= False+ return BinfoMultiAddr {..}++ marshalEncoding (net', ctx) m =+ A.pairs $+ mconcat+ [ "addresses" `A.pair` A.list (marshalEncoding (net, ctx)) m.addresses,+ "wallet" `A.pair` toEncoding m.wallet,+ "txs" `A.pair` A.list (marshalEncoding (net, ctx)) m.txs,+ "info" `A.pair` toEncoding m.info,+ "recommend_include_fee" `A.pair` A.bool m.recommendFee,+ if m.cashAddr then "cash_addr" `A.pair` A.bool True else mempty+ ]+ where+ net = if not m.cashAddr && net' == bch then btc else net'++data BinfoRawAddr = BinfoRawAddr+ { address :: !BinfoAddr,+ balance :: !Word64,+ ntx :: !Word64,+ utxo :: !Word64,+ received :: !Word64,+ sent :: !Int64,+ txs :: ![BinfoTx]+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoRawAddr where+ marshalValue (net, ctx) r =+ A.object+ [ "hash160" .= h160,+ "address" .= address,+ "n_tx" .= r.ntx,+ "n_unredeemed" .= r.utxo,+ "total_received" .= r.received,+ "total_sent" .= r.sent,+ "final_balance" .= r.balance,+ "txs" .= map (marshalValue (net, ctx)) r.txs+ ]+ where+ address = case r.address of+ BinfoAddr a -> marshalValue net a+ BinfoXpub x -> marshalValue (net, ctx) x+ h160 =+ encodeHex . runPutS . serialize+ <$> case r.address of+ BinfoAddr a -> case a of+ PubKeyAddress h -> Just h+ ScriptAddress h -> Just h+ WitnessPubKeyAddress h -> Just h+ _ -> Nothing+ _ -> Nothing++ marshalEncoding (net, ctx) r =+ A.pairs $+ mconcat+ [ "hash160" `A.pair` fromMaybe A.null_ h160,+ "address" `A.pair` address,+ "n_tx" `A.pair` A.word64 r.ntx,+ "n_unredeemed" `A.pair` A.word64 r.utxo,+ "total_received" `A.pair` A.word64 r.received,+ "total_sent" `A.pair` A.int64 r.sent,+ "final_balance" `A.pair` A.word64 r.balance,+ "txs" `A.pair` A.list (marshalEncoding (net, ctx)) r.txs+ ]+ where+ address = case r.address of+ BinfoAddr a -> marshalEncoding net a+ BinfoXpub x -> marshalEncoding (net, ctx) x+ h160 =+ hexEncoding . runPutL . serialize+ <$> case r.address of+ BinfoAddr a -> case a of+ PubKeyAddress h -> Just h+ ScriptAddress h -> Just h+ WitnessPubKeyAddress h -> Just h+ _ -> Nothing+ _ -> Nothing++ unmarshalValue (net, ctx) =+ A.withObject "BinfoRawAddr" $ \o -> do+ addr <- o .: "address"+ address <-+ BinfoAddr <$> unmarshalValue net addr+ <|> BinfoXpub <$> unmarshalValue (net, ctx) addr+ balance <- o .: "final_balance"+ utxo <- o .: "n_unredeemed"+ ntx <- o .: "n_tx"+ received <- o .: "total_received"+ sent <- o .: "total_sent"+ txs <- mapM (unmarshalValue (net, ctx)) =<< o .: "txs"+ return BinfoRawAddr {..}++data BinfoShortBal = BinfoShortBal+ { final :: !Word64,+ ntx :: !Word64,+ received :: !Word64+ }+ deriving (Eq, Show, Read, Generic, NFData)++instance ToJSON BinfoShortBal where+ toJSON b =+ A.object+ [ "final_balance" .= b.final,+ "n_tx" .= b.ntx,+ "total_received" .= b.received+ ]+ toEncoding b =+ A.pairs $+ mconcat+ [ "final_balance" `A.pair` A.word64 b.final,+ "n_tx" `A.pair` A.word64 b.ntx,+ "total_received" `A.pair` A.word64 b.received+ ]++instance FromJSON BinfoShortBal where+ parseJSON =+ A.withObject "BinfoShortBal" $ \o -> do+ final <- o .: "final_balance"+ ntx <- o .: "n_tx"+ received <- o .: "total_received"+ return BinfoShortBal {..}++data BinfoBalance+ = BinfoAddrBalance+ { address :: !Address,+ txs :: !Word64,+ received :: !Word64,+ sent :: !Word64,+ balance :: !Word64+ }+ | BinfoXPubBalance+ { xpub :: !XPubKey,+ txs :: !Word64,+ received :: !Word64,+ sent :: !Word64,+ balance :: !Word64,+ external :: !Word32,+ change :: !Word32+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoBalance where+ marshalValue (net, ctx) b@BinfoAddrBalance {} =+ A.object+ [ "address" .= marshalValue net b.address,+ "final_balance" .= b.balance,+ "n_tx" .= b.txs,+ "total_received" .= b.received,+ "total_sent" .= b.sent+ ]+ marshalValue (net, ctx) b@BinfoXPubBalance {} =+ A.object+ [ "address" .= marshalValue (net, ctx) b.xpub,+ "change_index" .= b.change,+ "account_index" .= b.external,+ "final_balance" .= b.balance,+ "n_tx" .= b.txs,+ "total_received" .= b.received,+ "total_sent" .= b.sent+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoBalance" $ \o -> x o <|> a o+ where+ x o = do+ xpub <- unmarshalValue (net, ctx) =<< o .: "address"+ change <- o .: "change_index"+ external <- o .: "account_index"+ balance <- o .: "final_balance"+ txs <- o .: "n_tx"+ received <- o .: "total_received"+ sent <- o .: "total_sent"+ return BinfoXPubBalance {..}+ a o = do+ address <- unmarshalValue net =<< o .: "address"+ balance <- o .: "final_balance"+ txs <- o .: "n_tx"+ received <- o .: "total_received"+ sent <- o .: "total_sent"+ return BinfoAddrBalance {..}++ marshalEncoding (net, ctx) b@BinfoAddrBalance {} =+ A.pairs $+ mconcat+ [ "address" `A.pair` marshalEncoding net b.address,+ "final_balance" `A.pair` A.word64 b.balance,+ "n_tx" `A.pair` A.word64 b.txs,+ "total_received" `A.pair` A.word64 b.received,+ "total_sent" `A.pair` A.word64 b.sent+ ]+ marshalEncoding (net, ctx) b@BinfoXPubBalance {} =+ A.pairs $+ mconcat+ [ "address" `A.pair` marshalEncoding (net, ctx) b.xpub,+ "change_index" `A.pair` A.word32 b.change,+ "account_index" `A.pair` A.word32 b.external,+ "final_balance" `A.pair` A.word64 b.balance,+ "n_tx" `A.pair` A.word64 b.txs,+ "total_received" `A.pair` A.word64 b.received,+ "total_sent" `A.pair` A.word64 b.sent+ ]++instance MarshalJSON (Network, Ctx) [BinfoBalance] where+ marshalValue (net, ctx) addrs =+ toJSON $ map (marshalValue (net, ctx)) addrs+ marshalEncoding (net, ctx) =+ A.list (marshalEncoding (net, ctx))+ unmarshalValue (net, ctx) =+ A.withArray "[BinfoBalance]" $+ fmap V.toList . mapM (unmarshalValue (net, ctx))++data BinfoWallet = BinfoWallet+ { balance :: !Word64,+ txs :: !Word64,+ filtered :: !Word64,+ received :: !Word64,+ sent :: !Word64+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoWallet where+ toJSON w =+ A.object+ [ "final_balance" .= w.balance,+ "n_tx" .= w.txs,+ "n_tx_filtered" .= w.filtered,+ "total_received" .= w.received,+ "total_sent" .= w.sent+ ]+ toEncoding w =+ A.pairs $+ mconcat+ [ "final_balance" `A.pair` A.word64 w.balance,+ "n_tx" `A.pair` A.word64 w.txs,+ "n_tx_filtered" `A.pair` A.word64 w.filtered,+ "total_received" `A.pair` A.word64 w.received,+ "total_sent" `A.pair` A.word64 w.sent+ ]++instance FromJSON BinfoWallet where+ parseJSON =+ A.withObject "BinfoWallet" $ \o -> do+ balance <- o .: "final_balance"+ txs <- o .: "n_tx"+ filtered <- o .: "n_tx_filtered"+ received <- o .: "total_received"+ sent <- o .: "total_sent"+ return BinfoWallet {..}++binfoHexValue :: Word64 -> Text+binfoHexValue w64 =+ encodeHex $+ if B.null bs || B.head bs `testBit` 7+ then B.cons 0x00 bs+ else bs+ where+ bs =+ B.dropWhile (== 0x00) $+ runPutS $+ serialize w64++data BinfoUnspent = BinfoUnspent+ { txid :: !TxHash,+ index :: !Word32,+ script :: !ByteString,+ value :: !Word64,+ confirmations :: !Int32,+ txidx :: !BinfoTxId,+ xpub :: !(Maybe BinfoXPubPath)+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoUnspent where+ marshalValue (net, ctx) u =+ A.object $+ [ "tx_hash_big_endian" .= u.txid,+ "tx_hash" .= encodeHex (runPutS (serialize u.txid.get)),+ "tx_output_n" .= u.index,+ "script" .= encodeHex u.script,+ "value" .= u.value,+ "value_hex" .= binfoHexValue u.value,+ "confirmations" .= u.confirmations,+ "tx_index" .= u.txidx+ ]+ <> [ "xpub" .= marshalValue (net, ctx) x+ | x <- maybeToList u.xpub+ ]++ marshalEncoding (net, ctx) u =+ A.pairs $+ mconcat+ [ "tx_hash_big_endian" `A.pair` toEncoding u.txid,+ "tx_hash" `A.pair` hexEncoding (runPutL (serialize u.txid.get)),+ "tx_output_n" `A.pair` A.word32 u.index,+ "script" `A.pair` hexEncoding (B.fromStrict u.script),+ "value" `A.pair` A.word64 u.value,+ "value_hex" `A.pair` A.text (binfoHexValue u.value),+ "confirmations" `A.pair` A.int32 u.confirmations,+ "tx_index" `A.pair` toEncoding u.txidx,+ maybe mempty (("xpub" `A.pair`) . marshalEncoding (net, ctx)) u.xpub+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoUnspent" $ \o -> do+ txid <- o .: "tx_hash_big_endian"+ index <- o .: "tx_output_n"+ script <- maybe mzero return . decodeHex =<< o .: "script"+ value <- o .: "value"+ confirmations <- o .: "confirmations"+ txidx <- o .: "tx_index"+ xpub <- mapM (unmarshalValue (net, ctx)) =<< o .:? "xpub"+ return BinfoUnspent {..}++newtype BinfoUnspents = BinfoUnspents [BinfoUnspent]+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoUnspents where+ marshalValue (net, ctx) (BinfoUnspents us) =+ A.object+ [ "notice" .= T.empty,+ "unspent_outputs" .= map (marshalValue (net, ctx)) us+ ]++ marshalEncoding (net, ctx) (BinfoUnspents us) =+ A.pairs $+ mconcat+ [ "notice" `A.pair` A.text T.empty,+ "unspent_outputs" `A.pair` A.list (marshalEncoding (net, ctx)) us+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoUnspents" $ \o -> do+ us <- mapM (unmarshalValue (net, ctx)) =<< o .: "unspent_outputs"+ return (BinfoUnspents us)++toBinfoBlock :: BlockData -> [BinfoTx] -> [BlockHash] -> BinfoBlock+toBinfoBlock b transactions next_blocks =+ BinfoBlock+ { hash = headerHash b.header,+ version = b.header.version,+ prev = b.header.prev,+ merkle = b.header.merkle,+ timestamp = b.header.timestamp,+ bits = b.header.bits,+ next = next_blocks,+ fee = b.fee,+ nonce = b.header.nonce,+ ntx = fromIntegral (length transactions),+ size = b.size,+ index = b.height,+ main = b.main,+ height = b.height,+ weight = b.weight,+ txs = transactions+ }++data BinfoBlock = BinfoBlock+ { hash :: !BlockHash,+ version :: !Word32,+ prev :: !BlockHash,+ merkle :: !Hash256,+ timestamp :: !Word32,+ bits :: !Word32,+ next :: ![BlockHash],+ fee :: !Word64,+ nonce :: !Word32,+ ntx :: !Word32,+ size :: !Word32,+ index :: !Word32,+ main :: !Bool,+ height :: !Word32,+ weight :: !Word32,+ txs :: ![BinfoTx]+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoBlock where+ marshalValue (net, ctx) b =+ A.object+ [ "hash" .= b.hash,+ "ver" .= b.version,+ "prev_block" .= b.prev,+ "mrkl_root" .= TxHash b.merkle,+ "time" .= b.timestamp,+ "bits" .= b.bits,+ "next_block" .= b.next,+ "fee" .= b.fee,+ "nonce" .= b.nonce,+ "n_tx" .= b.ntx,+ "size" .= b.size,+ "block_index" .= b.index,+ "main_chain" .= b.main,+ "height" .= b.height,+ "weight" .= b.weight,+ "tx" .= map (marshalValue (net, ctx)) b.txs+ ]++ marshalEncoding (net, ctx) b =+ A.pairs $+ mconcat+ [ "hash" `A.pair` toEncoding b.hash,+ "ver" `A.pair` A.word32 b.version,+ "prev_block" `A.pair` toEncoding b.prev,+ "mrkl_root" `A.pair` toEncoding (TxHash b.merkle),+ "time" `A.pair` A.word32 b.timestamp,+ "bits" `A.pair` A.word32 b.bits,+ "next_block" `A.pair` toEncoding b.next,+ "fee" `A.pair` A.word64 b.fee,+ "nonce" `A.pair` A.word32 b.nonce,+ "n_tx" `A.pair` A.word32 b.ntx,+ "size" `A.pair` A.word32 b.size,+ "block_index" `A.pair` A.word32 b.index,+ "main_chain" `A.pair` A.bool b.main,+ "height" `A.pair` A.word32 b.height,+ "weight" `A.pair` A.word32 b.weight,+ "tx" `A.pair` A.list (marshalEncoding (net, ctx)) b.txs+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoBlock" $ \o -> do+ hash <- o .: "hash"+ version <- o .: "ver"+ prev <- o .: "prev_block"+ merkle <- (\(TxHash h) -> h) <$> o .: "mrkl_root"+ timestamp <- o .: "time"+ bits <- o .: "bits"+ next <- o .: "next_block"+ fee <- o .: "fee"+ nonce <- o .: "nonce"+ ntx <- o .: "n_tx"+ size <- o .: "size"+ index <- o .: "block_index"+ main <- o .: "main_chain"+ height <- o .: "height"+ weight <- o .: "weight"+ txs <- o .: "tx" >>= mapM (unmarshalValue (net, ctx))+ return BinfoBlock {..}++instance MarshalJSON (Network, Ctx) [BinfoBlock] where+ marshalValue (net, ctx) blocks =+ A.object ["blocks" .= map (marshalValue (net, ctx)) blocks]++ marshalEncoding (net, ctx) blocks =+ A.pairs $ "blocks" `A.pair` A.list (marshalEncoding (net, ctx)) blocks++ unmarshalValue (net, ctx) =+ A.withObject "blocks" $ \o ->+ mapM (unmarshalValue (net, ctx)) =<< o .: "blocks"++data BinfoTx = BinfoTx+ { txid :: !TxHash,+ version :: !Word32,+ inputCount :: !Word32,+ outputCount :: !Word32,+ size :: !Word32,+ weight :: !Word32,+ fee :: !Word64,+ relayed :: !ByteString,+ locktime :: !Word32,+ index :: !BinfoTxId,+ doubleSpend :: !Bool,+ rbf :: !Bool,+ balance :: !(Maybe (Int64, Int64)),+ timestamp :: !Word64,+ blockIndex :: !(Maybe Word32),+ blockHeight :: !(Maybe Word32),+ inputs :: ![BinfoTxInput],+ outputs :: ![BinfoTxOutput]+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoTx where+ marshalValue (net, ctx) t =+ A.object $+ [ "hash" .= t.txid,+ "ver" .= t.version,+ "vin_sz" .= t.inputCount,+ "vout_sz" .= t.outputCount,+ "size" .= t.size,+ "weight" .= t.weight,+ "fee" .= t.fee,+ "relayed_by" .= T.decodeUtf8 t.relayed,+ "lock_time" .= t.locktime,+ "tx_index" .= t.index,+ "double_spend" .= t.doubleSpend,+ "time" .= t.timestamp,+ "block_index" .= t.blockIndex,+ "block_height" .= t.blockHeight,+ "inputs" .= map (marshalValue (net, ctx)) t.inputs,+ "out" .= map (marshalValue (net, ctx)) t.outputs+ ]+ ++ bal+ ++ rbf+ where+ bal =+ case t.balance of+ Nothing -> []+ Just (r, b) -> ["result" .= r, "balance" .= b]+ rbf = ["rbf" .= True | t.rbf]++ marshalEncoding (net, ctx) t =+ A.pairs $+ mconcat+ [ "hash" `A.pair` toEncoding t.txid,+ "ver" `A.pair` A.word32 t.version,+ "vin_sz" `A.pair` A.word32 t.inputCount,+ "vout_sz" `A.pair` A.word32 t.outputCount,+ "size" `A.pair` A.word32 t.size,+ "weight" `A.pair` A.word32 t.weight,+ "fee" `A.pair` A.word64 t.fee,+ "relayed_by" `A.pair` A.text (T.decodeUtf8 t.relayed),+ "lock_time" `A.pair` A.word32 t.locktime,+ "tx_index" `A.pair` toEncoding t.index,+ "double_spend" `A.pair` A.bool t.doubleSpend,+ "time" `A.pair` A.word64 t.timestamp,+ "block_index" `A.pair` toEncoding t.blockIndex,+ "block_height" `A.pair` maybe A.null_ A.word32 t.blockHeight,+ "inputs" `A.pair` A.list (marshalEncoding (net, ctx)) t.inputs,+ "out" `A.pair` A.list (marshalEncoding (net, ctx)) t.outputs,+ bal,+ rbf+ ]+ where+ bal =+ case t.balance of+ Nothing -> mempty+ Just (r, b) ->+ "result" `A.pair` A.int64 r <> "balance" `A.pair` A.int64 b+ rbf = if t.rbf then "rbf" .= True else mempty++ unmarshalValue (net, ctx) = A.withObject "BinfoTx" $ \o -> do+ txid <- o .: "hash"+ version <- o .: "ver"+ inputCount <- o .: "vin_sz"+ outputCount <- o .: "vout_sz"+ size <- o .: "size"+ weight <- o .: "weight"+ fee <- o .: "fee"+ relayed <- T.encodeUtf8 <$> o .: "relayed_by"+ locktime <- o .: "lock_time"+ index <- o .: "tx_index"+ doubleSpend <- o .: "double_spend"+ timestamp <- o .: "time"+ blockIndex <- o .: "block_index"+ blockHeight <- o .: "block_height"+ inputs <- o .: "inputs" >>= mapM (unmarshalValue (net, ctx))+ outputs <- o .: "out" >>= mapM (unmarshalValue (net, ctx))+ rbf <- o .:? "rbf" .!= False+ res <- o .:? "result"+ bal <- o .:? "balance"+ let balance = (,) <$> res <*> bal+ return BinfoTx {..}++instance MarshalJSON (Network, Ctx) [BinfoTx] where+ marshalValue (net, ctx) txs =+ toJSON $ map (marshalValue (net, ctx)) txs+ marshalEncoding (net, ctx) =+ A.list (marshalEncoding (net, ctx))+ unmarshalValue (net, ctx) =+ A.withArray "[BinfoTx]" $+ fmap V.toList . mapM (unmarshalValue (net, ctx))++data BinfoTxInput = BinfoTxInput+ { sequence :: !Word32,+ witness :: !ByteString,+ script :: !ByteString,+ index :: !Word32,+ output :: !BinfoTxOutput+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoTxInput where+ marshalValue (net, ctx) i =+ A.object+ [ "sequence" .= i.sequence,+ "witness" .= encodeHex i.witness,+ "script" .= encodeHex i.script,+ "index" .= i.index,+ "prev_out" .= marshalValue (net, ctx) i.output+ ]++ marshalEncoding (net, ctx) i =+ A.pairs $+ mconcat+ [ "sequence" `A.pair` A.word32 i.sequence,+ "witness" `A.pair` hexEncoding (B.fromStrict i.witness),+ "script" `A.pair` hexEncoding (B.fromStrict i.script),+ "index" `A.pair` A.word32 i.index,+ "prev_out" `A.pair` marshalEncoding (net, ctx) i.output+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoTxInput" $ \o -> do+ sequence <- o .: "sequence"+ witness <-+ maybe mzero return . decodeHex+ =<< o .: "witness"+ script <-+ maybe mzero return . decodeHex+ =<< o .: "script"+ index <- o .: "index"+ output <-+ o .: "prev_out"+ >>= unmarshalValue (net, ctx)+ return BinfoTxInput {..}++data BinfoTxOutput = BinfoTxOutput+ { typ :: !Int,+ spent :: !Bool,+ value :: !Word64,+ index :: !Word32,+ txidx :: !BinfoTxId,+ script :: !ByteString,+ spenders :: ![BinfoSpender],+ address :: !(Maybe Address),+ xpub :: !(Maybe BinfoXPubPath)+ }+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoTxOutput where+ marshalValue (net, ctx) o =+ A.object $+ [ "type" .= o.typ,+ "spent" .= o.spent,+ "value" .= o.value,+ "spending_outpoints" .= o.spenders,+ "n" .= o.index,+ "tx_index" .= o.txidx,+ "script" .= encodeHex o.script+ ]+ <> [ "addr" .= marshalValue net a+ | a <- maybeToList o.address+ ]+ <> [ "xpub" .= marshalValue (net, ctx) x+ | x <- maybeToList o.xpub+ ]++ marshalEncoding (net, ctx) o =+ A.pairs $+ mconcat $+ [ "type" `A.pair` A.int o.typ,+ "spent" `A.pair` A.bool o.spent,+ "value" `A.pair` A.word64 o.value,+ "spending_outpoints" `A.pair` A.list toEncoding o.spenders,+ "n" `A.pair` A.word32 o.index,+ "tx_index" `A.pair` toEncoding o.txidx,+ "script" `A.pair` hexEncoding (B.fromStrict o.script)+ ]+ <> [ "addr" `A.pair` marshalEncoding net a+ | a <- maybeToList o.address+ ]+ <> [ "xpub" `A.pair` marshalEncoding (net, ctx) x+ | x <- maybeToList o.xpub+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoTxOutput" $ \o -> do+ typ <- o .: "type"+ spent <- o .: "spent"+ value <- o .: "value"+ spenders <- o .: "spending_outpoints"+ index <- o .: "n"+ txidx <- o .: "tx_index"+ script <- maybe mzero return . decodeHex =<< o .: "script"+ address <- o .:? "addr" >>= mapM (unmarshalValue net)+ xpub <- o .:? "xpub" >>= mapM (unmarshalValue (net, ctx))+ return BinfoTxOutput {..}++data BinfoSpender = BinfoSpender+ { txidx :: !BinfoTxId,+ input :: !Word32+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoSpender where+ toJSON s =+ A.object+ [ "tx_index" .= s.txidx,+ "n" .= s.input+ ]+ toEncoding s =+ A.pairs $+ mconcat+ [ "tx_index" `A.pair` toEncoding s.txidx,+ "n" `A.pair` A.word32 s.input+ ]++instance FromJSON BinfoSpender where+ parseJSON =+ A.withObject "BinfoSpender" $ \o -> do+ txidx <- o .: "tx_index"+ input <- o .: "n"+ return BinfoSpender {..}++data BinfoXPubPath = BinfoXPubPath+ { key :: !XPubKey,+ deriv :: !SoftPath+ }+ deriving (Eq, Show, Generic, NFData)++instance Ord BinfoXPubPath where+ compare = compare `on` f+ where+ f b =+ ( b.key.parent,+ b.deriv+ )++instance MarshalJSON (Network, Ctx) BinfoXPubPath where+ marshalValue (net, ctx) p =+ A.object+ [ "m" .= marshalValue (net, ctx) p.key,+ "path" .= ("M" ++ pathToStr p.deriv)+ ]++ marshalEncoding (net, ctx) p =+ A.pairs $+ mconcat+ [ "m" `A.pair` marshalEncoding (net, ctx) p.key,+ "path" `A.pair` A.string ("M" ++ pathToStr p.deriv)+ ]++ unmarshalValue (net, ctx) =+ A.withObject "BinfoXPubPath" $ \o -> do+ key <- o .: "m" >>= unmarshalValue (net, ctx)+ deriv <- fromMaybe "bad xpub path" . parseSoft <$> o .: "path"+ return BinfoXPubPath {..}++data BinfoInfo = BinfoInfo+ { connected :: !Word32,+ conversion :: !Double,+ fiat :: !BinfoSymbol,+ crypto :: !BinfoSymbol,+ head :: !BinfoBlockInfo+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoInfo where+ toJSON i =+ A.object+ [ "nconnected" .= i.connected,+ "conversion" .= i.conversion,+ "symbol_local" .= i.fiat,+ "symbol_btc" .= i.crypto,+ "latest_block" .= i.head+ ]+ toEncoding i =+ A.pairs $+ mconcat+ [ "nconnected" `A.pair` A.word32 i.connected,+ "conversion" `A.pair` A.double i.conversion,+ "symbol_local" `A.pair` toEncoding i.fiat,+ "symbol_btc" `A.pair` toEncoding i.crypto,+ "latest_block" `A.pair` toEncoding i.head+ ]++instance FromJSON BinfoInfo where+ parseJSON =+ A.withObject "BinfoInfo" $ \o -> do+ connected <- o .: "nconnected"+ conversion <- o .: "conversion"+ fiat <- o .: "symbol_local"+ crypto <- o .: "symbol_btc"+ head <- o .: "latest_block"+ return BinfoInfo {..}++data BinfoBlockInfo = BinfoBlockInfo+ { hash :: !BlockHash,+ height :: !BlockHeight,+ timestamp :: !Word32,+ index :: !BlockHeight+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoBlockInfo where+ toJSON i =+ A.object+ [ "hash" .= i.hash,+ "height" .= i.height,+ "time" .= i.timestamp,+ "block_index" .= i.index+ ]+ toEncoding i =+ A.pairs $+ mconcat+ [ "hash" `A.pair` toEncoding i.hash,+ "height" `A.pair` A.word32 i.height,+ "time" `A.pair` A.word32 i.timestamp,+ "block_index" `A.pair` A.word32 i.index+ ]++instance FromJSON BinfoBlockInfo where+ parseJSON =+ A.withObject "BinfoBlockInfo" $ \o -> do+ hash <- o .: "hash"+ height <- o .: "height"+ timestamp <- o .: "time"+ index <- o .: "block_index"+ return BinfoBlockInfo {..}++toBinfoBlockInfo :: BlockData -> BinfoBlockInfo+toBinfoBlockInfo d =+ BinfoBlockInfo+ { hash = headerHash d.header,+ height = d.height,+ timestamp = d.header.timestamp,+ index = d.height+ }++data BinfoRate = BinfoRate+ { timestamp :: !Word64,+ price :: !Double,+ vol24 :: !Double+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoRate where+ toJSON r =+ A.object+ [ "timestamp" .= r.timestamp,+ "price" .= r.price,+ "volume24h" .= r.vol24+ ]+ toEncoding r =+ A.pairs $+ mconcat+ [ "timestamp" `A.pair` A.word64 r.timestamp,+ "price" `A.pair` A.double r.price,+ "volume24h" `A.pair` A.double r.vol24+ ]++instance FromJSON BinfoRate where+ parseJSON =+ A.withObject "BinfoRate" $ \o -> do+ timestamp <- o .: "timestamp"+ price <- o .: "price"+ vol24 <- o .: "volume24h"+ return BinfoRate {..}++data BinfoHistory = BinfoHistory+ { date :: !Text,+ time :: !Text,+ typ :: !Text,+ amount :: !Double,+ valueThen :: !Double,+ valueNow :: !Double,+ rateThen :: !Double,+ txid :: !TxHash,+ fee :: !Double+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoHistory where+ toJSON h =+ A.object+ [ "date" .= h.date,+ "time" .= h.time,+ "type" .= h.typ,+ "amount" .= h.amount,+ "value_then" .= h.valueThen,+ "value_now" .= h.valueNow,+ "exchange_rate_then" .= h.rateThen,+ "tx" .= h.txid,+ "fee" .= h.fee+ ]+ toEncoding h =+ A.pairs $+ mconcat+ [ "date" `A.pair` A.text h.date,+ "time" `A.pair` A.text h.time,+ "type" `A.pair` A.text h.typ,+ "amount" `A.pair` A.double h.amount,+ "value_then" `A.pair` A.double h.valueThen,+ "value_now" `A.pair` A.double h.valueNow,+ "exchange_rate_then" `A.pair` A.double h.rateThen,+ "tx" `A.pair` toEncoding h.txid,+ "fee" `A.pair` A.double h.fee+ ]++instance FromJSON BinfoHistory where+ parseJSON =+ A.withObject "BinfoHistory" $ \o -> do+ date <- o .: "date"+ time <- o .: "time"+ typ <- o .: "type"+ amount <- o .: "amount"+ valueThen <- o .: "value_then"+ valueNow <- o .: "value_now"+ rateThen <- o .: "exchange_rate_then"+ txid <- o .: "tx"+ fee <- o .: "fee"+ return BinfoHistory {..}++toBinfoHistory ::+ Int64 ->+ Word64 ->+ Double ->+ Double ->+ Word64 ->+ TxHash ->+ BinfoHistory+toBinfoHistory satoshi timestamp rateThen rateNow fee txid =+ BinfoHistory+ { date = T.pack $ formatTime defaultTimeLocale "%Y-%m-%d" t,+ time = T.pack $ formatTime defaultTimeLocale "%H:%M:%S GMT %Ez" t,+ typ = if satoshi <= 0 then "sent" else "received",+ amount = fromRational v,+ fee = fromRational f,+ valueThen = fromRational v1,+ valueNow = fromRational v2,+ rateThen,+ txid+ }+ where+ t = posixSecondsToUTCTime (realToFrac timestamp)+ v = toRational satoshi / (100 * 1000 * 1000)+ r1 = toRational rateThen+ r2 = toRational rateNow+ f = toRational fee / (100 * 1000 * 1000)+ v1 = v * r1+ v2 = v * r2++newtype BinfoDate = BinfoDate Word64+ deriving (Eq, Show, Read, Generic, NFData)++instance Parsable BinfoDate where+ parseParam t =+ maybeToEither "Cannot parse date"+ . fmap (BinfoDate . round . utcTimeToPOSIXSeconds)+ $ p "%d-%m-%Y" <|> p "%d/%m/%Y"+ where+ s = LazyText.unpack t+ p fmt = parseTimeM False defaultTimeLocale fmt s++data BinfoTicker = BinfoTicker+ { fifteen :: !Double,+ last :: !Double,+ buy :: !Double,+ sell :: !Double,+ symbol :: !Text+ }+ deriving (Eq, Show, Generic, NFData)++instance Default BinfoTicker where+ def =+ BinfoTicker+ { symbol = "XXX",+ fifteen = 0.0,+ last = 0.0,+ buy = 0.0,+ sell = 0.0+ }++instance ToJSON BinfoTicker where+ toJSON t =+ A.object+ [ "symbol" .= t.symbol,+ "sell" .= t.sell,+ "buy" .= t.buy,+ "last" .= t.last,+ "15m" .= t.fifteen+ ]+ toEncoding t =+ A.pairs $+ mconcat+ [ "symbol" `A.pair` A.text t.symbol,+ "sell" `A.pair` A.double t.sell,+ "buy" `A.pair` A.double t.buy,+ "last" `A.pair` A.double t.last,+ "15m" `A.pair` A.double t.fifteen+ ]++instance FromJSON BinfoTicker where+ parseJSON =+ A.withObject "BinfoTicker" $ \o -> do+ symbol <- o .: "symbol"+ fifteen <- o .: "15m"+ sell <- o .: "sell"+ buy <- o .: "buy"+ last <- o .: "last"+ return BinfoTicker {..}++data BinfoSymbol = BinfoSymbol+ { code :: !Text,+ symbol :: !Text,+ name :: !Text,+ conversion :: !Double,+ after :: !Bool,+ local :: !Bool+ }+ deriving (Eq, Show, Generic, NFData)++instance Default BinfoSymbol where+ def =+ BinfoSymbol+ { code = "XXX",+ symbol = "¤",+ name = "No currency",+ conversion = 0.0,+ after = False,+ local = True+ }++instance ToJSON BinfoSymbol where+ toJSON s =+ A.object+ [ "code" .= s.code,+ "symbol" .= s.symbol,+ "name" .= s.name,+ "conversion" .= s.conversion,+ "symbolAppearsAfter" .= s.after,+ "local" .= s.local+ ]+ toEncoding s =+ A.pairs $+ mconcat+ [ "code" `A.pair` A.text s.code,+ "symbol" `A.pair` A.text s.symbol,+ "name" `A.pair` A.text s.name,+ "conversion" `A.pair` A.double s.conversion,+ "symbolAppearsAfter" `A.pair` A.bool s.after,+ "local" `A.pair` A.bool s.local+ ]++instance FromJSON BinfoSymbol where+ parseJSON =+ A.withObject "BinfoSymbol" $ \o -> do+ code <- o .: "code"+ symbol <- o .: "symbol"+ name <- o .: "name"+ conversion <- o .: "conversion"+ after <- o .: "symbolAppearsAfter"+ local <- o .: "local"+ return BinfoSymbol {..}++relevantTxs ::+ HashSet Address ->+ Bool ->+ Transaction ->+ HashSet TxHash+relevantTxs addrs prune t =+ HashSet.fromList $ ins <> outs+ where+ p a =+ prune+ && getTxResult addrs t > 0+ && not (HashSet.member a addrs)+ f o = do+ Spender {..} <- o.spender+ a <- o.address+ guard $ p a+ return txid+ outs = mapMaybe f t.outputs+ g StoreCoinbase {} = Nothing+ g StoreInput {outpoint = OutPoint h i} = Just h+ ins = mapMaybe g t.inputs++toBinfoAddrs ::+ HashMap Address Balance ->+ HashMap XPubSpec [XPubBal] ->+ HashMap XPubSpec Word64 ->+ [BinfoBalance]+toBinfoAddrs onlyAddrs onlyXpubs xpubTxs =+ xpubBals <> addrBals+ where+ xpubBal k xs =+ let f x =+ case x.path of+ [0, _] -> x.balance.received+ _ -> 0+ g x = x.balance.confirmed + x.balance.unconfirmed+ i m x =+ case x.path of+ [m', n] | m == m' -> n + 1+ _ -> 0+ received = sum $ map f xs+ bal = fromIntegral $ sum $ map g xs+ sent = if bal <= received then received - bal else 0+ count = HashMap.lookupDefault 0 k xpubTxs+ ax = foldl max 0 $ map (i 0) xs+ cx = foldl max 0 $ map (i 1) xs+ in BinfoXPubBalance+ { xpub = k.key,+ txs = count,+ received = received,+ sent = sent,+ balance = bal,+ external = ax,+ change = cx+ }+ xpubBals = map (uncurry xpubBal) $ HashMap.toList onlyXpubs+ addrBals =+ let f Balance {..} =+ let sent = received - balance+ balance = confirmed + unconfirmed+ in BinfoAddrBalance {..}+ in map f $ HashMap.elems onlyAddrs++toBinfoTxSimple ::+ Bool ->+ Transaction ->+ BinfoTx+toBinfoTxSimple numtxid =+ toBinfoTx numtxid HashMap.empty False 0++toBinfoTxInputs ::+ Bool ->+ HashMap Address (Maybe BinfoXPubPath) ->+ Transaction ->+ [BinfoTxInput]+toBinfoTxInputs numtxid abook t =+ zipWith f [0 ..] t.inputs+ where+ f n i =+ BinfoTxInput+ { index = n,+ sequence = i.sequence,+ script = i.script,+ witness = wit i,+ output = prev n i+ }+ wit i =+ case i.witness of+ [] -> B.empty+ ws -> runPutS (put_witness ws)+ prev = inputToBinfoTxOutput numtxid abook t+ put_witness ws = do+ putVarInt (length ws)+ mapM_ put_item ws+ put_item bs = do+ putVarInt (B.length bs)+ putByteString bs++transactionHeight :: Transaction -> Maybe BlockHeight+transactionHeight Transaction {deleted = True} = Nothing+transactionHeight Transaction {block = MemRef _} = Nothing+transactionHeight Transaction {block = BlockRef h _} = Just h++toBinfoTx ::+ Bool ->+ HashMap Address (Maybe BinfoXPubPath) ->+ Bool ->+ Int64 ->+ Transaction ->+ BinfoTx+toBinfoTx numtxid abook prune bal t =+ BinfoTx+ { version = t.version,+ weight = t.weight,+ relayed = "0.0.0.0",+ txid = txHash tx,+ index = encodeBinfoTxId numtxid (txHash tx),+ inputCount = fromIntegral $ length t.inputs,+ outputCount = fromIntegral $ length t.outputs,+ blockIndex = transactionHeight t,+ blockHeight = transactionHeight t,+ doubleSpend = t.deleted,+ balance =+ if simple+ then Nothing+ else Just (getTxResult aset t, bal),+ outputs =+ let p = prune && getTxResult aset t > 0+ f = toBinfoTxOutput numtxid abook p t+ in catMaybes $ zipWith f [0 ..] t.outputs,+ inputs = toBinfoTxInputs numtxid abook t,+ size = t.size,+ rbf = t.rbf,+ fee = t.fee,+ locktime = t.locktime,+ timestamp = t.timestamp+ }+ where+ tx = transactionData t+ aset = HashMap.keysSet abook+ simple = HashMap.null abook && bal == 0++getTxResult :: HashSet Address -> Transaction -> Int64+getTxResult aset t =+ inputSum + outputSum+ where+ inputSum = sum $ map inputValue t.inputs+ inputValue StoreCoinbase {} = 0+ inputValue StoreInput {..} =+ case address of+ Nothing -> 0+ Just a ->+ if testAddr a+ then negate $ fromIntegral value+ else 0+ testAddr a = HashSet.member a aset+ outputSum = sum $ map outValue t.outputs+ outValue StoreOutput {..} =+ case address of+ Nothing -> 0+ Just a ->+ if testAddr a+ then fromIntegral value+ else 0++toBinfoTxOutput ::+ Bool ->+ HashMap Address (Maybe BinfoXPubPath) ->+ Bool ->+ Transaction ->+ Word32 ->+ StoreOutput ->+ Maybe BinfoTxOutput+toBinfoTxOutput numtxid abook prune t index o =+ if prune && notInBook+ then Nothing+ else+ Just+ BinfoTxOutput+ { typ = 0,+ spent = isJust o.spender,+ value = o.value,+ index = index,+ txidx = encodeBinfoTxId numtxid $ txHash $ transactionData t,+ script = o.script,+ spenders = maybeToList $ toBinfoSpender numtxid <$> o.spender,+ address = o.address,+ xpub = o.address >>= join . flip HashMap.lookup abook+ }+ where+ notInBook = isNothing $ o.address >>= flip HashMap.lookup abook++toBinfoSpender :: Bool -> Spender -> BinfoSpender+toBinfoSpender numtxid s =+ BinfoSpender+ { txidx = encodeBinfoTxId numtxid s.txid,+ input = s.index+ }++inputToBinfoTxOutput ::+ Bool ->+ HashMap Address (Maybe BinfoXPubPath) ->+ Transaction ->+ Word32 ->+ StoreInput ->+ BinfoTxOutput+inputToBinfoTxOutput numtxid abook t n i =+ BinfoTxOutput+ { typ = 0,+ spent = True,+ txidx = encodeBinfoTxId numtxid i.outpoint.hash,+ value =+ case i of+ StoreCoinbase {} -> 0+ StoreInput {value} -> value,+ script =+ case i of+ StoreCoinbase {} -> B.empty+ StoreInput {pkscript} -> pkscript,+ address =+ case i of+ StoreCoinbase {} -> Nothing+ StoreInput {address} -> address,+ index = i.outpoint.index,+ spenders =+ [ BinfoSpender+ (encodeBinfoTxId numtxid (txHash (transactionData t)))+ n+ ],+ xpub = i.address >>= join . flip HashMap.lookup abook+ }++data BinfoAddr+ = BinfoAddr !Address+ | BinfoXpub !XPubKey+ deriving (Eq, Show, Generic, Hashable, NFData)++parseBinfoAddr :: Network -> Ctx -> Text -> Maybe [BinfoAddr]+parseBinfoAddr net ctx "" = Just []+parseBinfoAddr net ctx s =+ mapM f $+ filter (not . T.null) $+ concatMap (T.splitOn ",") (T.splitOn "|" s)+ where+ f x =+ BinfoAddr <$> textToAddr net x+ <|> BinfoXpub <$> xPubImport net ctx x++data BinfoHeader = BinfoHeader+ { hash :: !BlockHash,+ timestamp :: !Timestamp,+ index :: !Word32,+ height :: !BlockHeight,+ txids :: ![BinfoTxId]+ }+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoHeader where+ toJSON h =+ A.object+ [ "hash" .= h.hash,+ "time" .= h.timestamp,+ "block_index" .= h.index,+ "height" .= h.height,+ "txIndexes" .= h.txids+ ]+ toEncoding h =+ A.pairs $+ mconcat+ [ "hash" `A.pair` toEncoding h.hash,+ "time" `A.pair` A.word32 h.timestamp,+ "block_index" `A.pair` A.word32 h.index,+ "height" `A.pair` A.word32 h.height,+ "txIndexes" `A.pair` A.list toEncoding h.txids+ ]++instance FromJSON BinfoHeader where+ parseJSON =+ A.withObject "BinfoHeader" $ \o -> do+ hash <- o .: "hash"+ timestamp <- o .: "time"+ index <- o .: "block_index"+ height <- o .: "height"+ txids <- o .: "txIndexes"+ return BinfoHeader {..}++newtype BinfoMempool = BinfoMempool {get :: [BinfoTx]}+ deriving (Eq, Show, Generic, NFData)++instance MarshalJSON (Network, Ctx) BinfoMempool where+ marshalValue (net, ctx) (BinfoMempool txs) =+ A.object ["txs" .= map (marshalValue (net, ctx)) txs]++ marshalEncoding (net, ctx) (BinfoMempool txs) =+ A.pairs $ A.pair "txs" $ A.list (marshalEncoding (net, ctx)) txs++ unmarshalValue (net, ctx) =+ A.withObject "BinfoMempool" $ \o ->+ BinfoMempool <$> (mapM (unmarshalValue (net, ctx)) =<< o .: "txs")++newtype BinfoBlockInfos = BinfoBlockInfos {get :: [BinfoBlockInfo]}+ deriving (Eq, Show, Generic, NFData)++instance ToJSON BinfoBlockInfos where+ toJSON b = A.object ["blocks" .= b.get]+ toEncoding b =+ A.pairs $ A.pair "blocks" $ A.list toEncoding b.get++instance FromJSON BinfoBlockInfos where+ parseJSON =+ A.withObject "BinfoBlockInfos" $ \o ->+ BinfoBlockInfos <$> o .: "blocks"
src/Haskoin/Store/WebClient.hs view
@@ -1,8 +1,12 @@+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-} module Haskoin.Store.WebClient ( ApiConfig (..),@@ -67,8 +71,8 @@ import Control.Exception import Control.Lens ((.~), (?~), (^.)) import Control.Monad.Except-import qualified Data.Aeson as A-import qualified Data.ByteString.Lazy as BL+import Data.Aeson qualified as A+import Data.ByteString.Lazy qualified as BL import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial@@ -76,17 +80,17 @@ import Data.Monoid (Endo (..), appEndo) import Data.String.Conversions (cs) import Data.Text (Text)-import qualified Data.Text as Text-import Haskoin.Constants-import Haskoin.Data-import qualified Haskoin.Store.Data as Store+import Data.Text qualified as Text+import Haskoin.Crypto (Ctx)+import Haskoin.Network+import Haskoin.Store.Data qualified as Store import Haskoin.Store.WebCommon import Haskoin.Transaction import Haskoin.Util import Network.HTTP.Client (Request (..)) import Network.HTTP.Types (StdMethod (..)) import Network.HTTP.Types.Status-import qualified Network.Wreq as HTTP+import Network.Wreq qualified as HTTP import Network.Wreq.Types (ResponseChecker) import Numeric.Natural (Natural) @@ -95,40 +99,41 @@ -- -- @ -- ApiConfig--- { configNetwork = bch--- , configHost = "https://api.haskoin.com/"+-- { net = bch+-- , host = "https://api.haskoin.com/" -- } -- @ data ApiConfig = ApiConfig- { configNetwork :: !Network,- configHost :: !String+ { net :: !Network,+ host :: !String } deriving (Eq, Show) instance Default ApiConfig where def = ApiConfig- { configNetwork = bch,- configHost = "https://api.haskoin.com/"+ { net = bch,+ host = "https://api.haskoin.com/" } -- | Make a call to the haskoin-store API. -- -- Usage (default options): ----- > apiCall def $ GetAddrsTxs addrs def+-- > apiCall ctx def $ GetAddrsTxs addrs def -- -- With options: ----- > apiCall def $ GetAddrsUnspent addrs def{ paramLimit = Just 10 }+-- > apiCall ctx def $ GetAddrsUnspent addrs def{ paramLimit = Just 10 } apiCall :: (ApiResource a b, MonadIO m, MonadError Store.Except m) =>+ Ctx -> ApiConfig -> a -> m b-apiCall (ApiConfig net apiHost) res = do- args <- liftEither $ toOptions net res- let url = apiHost <> getNetworkName net <> cs (queryPath net res)+apiCall ctx (ApiConfig net apiHost) res = do+ args <- liftEither $ toOptions net ctx res+ let url = apiHost <> net.name <> cs (queryPath net ctx res) case resourceMethod $ asProxy res of GET -> liftEither =<< liftIO (getBinary args url) POST ->@@ -143,11 +148,13 @@ -- > apiBatch 20 def (GetAddrsTxs addrs def) apiBatch :: (Batchable a b, MonadIO m, MonadError Store.Except m) =>+ Ctx -> Natural -> ApiConfig -> a -> m b-apiBatch i conf res = mconcat <$> mapM (apiCall conf) (resourceBatch i res)+apiBatch ctx i conf res =+ mconcat <$> mapM (apiCall ctx conf) (resourceBatch i res) class (ApiResource a b, Monoid b) => Batchable a b where resourceBatch :: Natural -> a -> [a]@@ -190,22 +197,33 @@ ------------------ toOptions ::- ApiResource a b => Network -> a -> Either Store.Except (Endo HTTP.Options)-toOptions net res =+ (ApiResource a b) =>+ Network ->+ Ctx ->+ a ->+ Either Store.Except (Endo HTTP.Options)+toOptions net ctx res = mconcat <$> mapM f (snd $ queryParams res) where- f (ParamBox p) = toOption net p+ f (ParamBox p) = toOption net ctx p -toOption :: Param a => Network -> a -> Either Store.Except (Endo HTTP.Options)-toOption net a = do- res <- maybeToEither (Store.UserError "Invalid Param") $ encodeParam net a+toOption ::+ (Param a) =>+ Network ->+ Ctx ->+ a ->+ Either Store.Except (Endo HTTP.Options)+toOption net ctx a = do+ res <-+ maybeToEither (Store.UserError "Invalid Param") $+ encodeParam net ctx a return $ applyOpt (paramLabel a) res applyOpt :: Text -> [Text] -> Endo HTTP.Options applyOpt p t = Endo $ HTTP.param p .~ [Text.intercalate "," t] getBinary ::- Serial a =>+ (Serial a) => Endo HTTP.Options -> String -> IO (Either Store.Except a)@@ -239,11 +257,11 @@ | statusIsSuccessful status = return () | isHealthPath && code == 503 = return () -- Ignore health checks | otherwise = do- e <- A.decodeStrict <$> res ^. HTTP.responseBody- throwIO $- case e of- Just except -> except :: Store.Except- Nothing -> Store.StringError "could not decode error"+ e <- A.decodeStrict <$> res ^. HTTP.responseBody+ throwIO $+ case e of+ Just except -> except :: Store.Except+ Nothing -> Store.StringError "could not decode error" where code = res ^. HTTP.responseStatus . HTTP.statusCode message = res ^. HTTP.responseStatus . HTTP.statusMessage@@ -258,4 +276,4 @@ chunksOf n xs | null xs = [] | otherwise =- uncurry (:) $ second (chunksOf n) $ splitAt (fromIntegral n) xs+ uncurry (:) $ second (chunksOf n) $ splitAt (fromIntegral n) xs
src/Haskoin/Store/WebCommon.hs view
@@ -1,10 +1,14 @@+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-} module Haskoin.Store.WebCommon where @@ -16,7 +20,7 @@ import Data.String (IsString (..)) import Data.String.Conversions (cs) import Data.Text (Text)-import qualified Data.Text as T+import Data.Text qualified as T import Haskoin.Address import Haskoin.Block ( Block,@@ -24,22 +28,21 @@ blockHashToHex, hexToBlockHash, )-import Haskoin.Constants-import Haskoin.Crypto (Hash256)-import Haskoin.Data-import Haskoin.Keys-import qualified Haskoin.Store.Data as Store+import Haskoin.Crypto (Ctx, Hash256)+import Haskoin.Crypto.Keys+import Haskoin.Network.Data+import Haskoin.Store.Data qualified as Store import Haskoin.Transaction import Network.HTTP.Types (StdMethod (..)) import Numeric.Natural (Natural) import Text.Read (readMaybe)-import qualified Web.Scotty.Trans as Scotty+import Web.Scotty.Trans qualified as Scotty ------------------- -- API Resources -- ------------------- -class Serial b => ApiResource a b | a -> b where+class (Serial b) => ApiResource a b | a -> b where resourceMethod :: Proxy a -> StdMethod resourceMethod _ = GET resourcePath :: Proxy a -> ([Text] -> Text)@@ -50,11 +53,11 @@ resourceBody :: a -> Maybe PostBox resourceBody = const Nothing -data PostBox = forall s. Serial s => PostBox !s+data PostBox = forall s. (Serial s) => PostBox !s data ParamBox = forall p. (Eq p, Param p) => ParamBox !p -data ProxyBox = forall p. Param p => ProxyBox !(Proxy p)+data ProxyBox = forall p. (Param p) => ProxyBox !(Proxy p) -------------------- -- Resource Paths --@@ -352,7 +355,7 @@ -- Network -- ------------- -instance ApiResource GetPeers (Store.SerialList Store.PeerInformation) where+instance ApiResource GetPeers (Store.SerialList Store.PeerInfo) where resourcePath _ _ = "/peers" instance ApiResource GetHealth Store.HealthCheck where@@ -386,23 +389,23 @@ asProxy :: a -> Proxy a asProxy = const Proxy -queryPath :: ApiResource a b => Network -> a -> Text-queryPath net a = f $ encParam <$> fst (queryParams a)+queryPath :: (ApiResource a b) => Network -> Ctx -> a -> Text+queryPath net ctx a = f $ encParam <$> fst (queryParams a) where f = resourcePath $ asProxy a encParam (ParamBox p) =- case encodeParam net p of+ case encodeParam net ctx p of Just [res] -> res _ -> error "Invalid query param" -capturePath :: ApiResource a b => Proxy a -> Scotty.RoutePattern+capturePath :: (ApiResource a b) => Proxy a -> Scotty.RoutePattern capturePath proxy = fromString $ cs $ f $ toLabel <$> captureParams proxy where f = resourcePath proxy toLabel (ProxyBox p) = ":" <> proxyLabel p -paramLabel :: Param p => p -> Text+paramLabel :: (Param p) => p -> Text paramLabel = proxyLabel . asProxy -------------@@ -411,35 +414,29 @@ class Param a where proxyLabel :: Proxy a -> Text- encodeParam :: Network -> a -> Maybe [Text]- parseParam :: Network -> [Text] -> Maybe a+ encodeParam :: Network -> Ctx -> a -> Maybe [Text]+ parseParam :: Network -> Ctx -> [Text] -> Maybe a instance Param Address where proxyLabel = const "address"- encodeParam net a = (: []) <$> addrToText net a- parseParam net [a] = textToAddr net a- parseParam _ _ = Nothing+ encodeParam net ctx a = (: []) <$> addrToText net a+ parseParam net ctx [a] = textToAddr net a+ parseParam net ctx _ = Nothing instance Param [Address] where proxyLabel = const "addresses"- encodeParam = mapM . addrToText- parseParam = mapM . textToAddr+ encodeParam net ctx = mapM (addrToText net)+ parseParam net ctx = mapM (textToAddr net) data StartParam- = StartParamHash- { startParamHash :: Hash256- }- | StartParamHeight- { startParamHeight :: Natural- }- | StartParamTime- { startParamTime :: Store.UnixTime- }+ = StartParamHash {hash :: Hash256}+ | StartParamHeight {height :: Natural}+ | StartParamTime {time :: Store.UnixTime} deriving (Eq, Show) instance Param StartParam where proxyLabel = const "height"- encodeParam _ p =+ encodeParam net ctx p = case p of StartParamHash h -> return [txHashToHex (TxHash h)] StartParamHeight h -> do@@ -448,7 +445,8 @@ StartParamTime t -> do guard $ t > 1230768000 return [cs $ show t]- parseParam _ [s] = parseHash <|> parseHeight <|> parseUnix+ parseParam net ctx [s] =+ parseHash <|> parseHeight <|> parseUnix where parseHash = do guard (T.length s == 32 * 2)@@ -462,11 +460,9 @@ x <- readMaybe $ cs s guard $ x > 1230768000 return $ StartParamTime x- parseParam _ _ = Nothing+ parseParam net ctx _ = Nothing -newtype OffsetParam = OffsetParam- { getOffsetParam :: Natural- }+newtype OffsetParam = OffsetParam {get :: Natural} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) instance Default OffsetParam where@@ -474,84 +470,74 @@ instance Param OffsetParam where proxyLabel = const "offset"- encodeParam _ (OffsetParam o) = Just [cs $ show o]- parseParam _ [s] = OffsetParam <$> readMaybe (cs s)- parseParam _ _ = Nothing+ encodeParam net ctx (OffsetParam o) = Just [cs $ show o]+ parseParam net ctx [s] = OffsetParam <$> readMaybe (cs s)+ parseParam net ctx _ = Nothing -newtype LimitParam = LimitParam- { getLimitParam :: Natural- }+newtype LimitParam = LimitParam {get :: Natural} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) instance Param LimitParam where proxyLabel = const "limit"- encodeParam _ (LimitParam l) = Just [cs $ show l]- parseParam _ [s] = LimitParam <$> readMaybe (cs s)- parseParam _ _ = Nothing+ encodeParam net ctx (LimitParam l) = Just [cs $ show l]+ parseParam net ctx [s] = LimitParam <$> readMaybe (cs s)+ parseParam net ctx _ = Nothing data LimitsParam = LimitsParam- { paramLimit :: Maybe LimitParam, -- 0 means maximum- paramOffset :: OffsetParam,- paramStart :: Maybe StartParam+ { limit :: Maybe LimitParam, -- 0 means maximum+ offset :: OffsetParam,+ start :: Maybe StartParam } deriving (Eq, Show) instance Default LimitsParam where def = LimitsParam Nothing def Nothing -newtype HeightParam = HeightParam- { getHeightParam :: Natural- }+newtype HeightParam = HeightParam {get :: Natural} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) instance Param HeightParam where proxyLabel = const "height"- encodeParam _ (HeightParam h) = Just [cs $ show h]- parseParam _ [s] = HeightParam <$> readMaybe (cs s)- parseParam _ _ = Nothing+ encodeParam net ctx (HeightParam h) = Just [cs $ show h]+ parseParam net ctx [s] = HeightParam <$> readMaybe (cs s)+ parseParam net ctx _ = Nothing -newtype HeightsParam = HeightsParam- { getHeightsParam :: [Natural]- }+newtype HeightsParam = HeightsParam {get :: [Natural]} deriving (Eq, Show, Read) instance Param HeightsParam where proxyLabel = const "heights"- encodeParam _ (HeightsParam hs) = Just $ cs . show <$> hs- parseParam _ xs = HeightsParam <$> mapM (readMaybe . cs) xs+ encodeParam net ctx (HeightsParam hs) = Just $ cs . show <$> hs+ parseParam net ctx xs = HeightsParam <$> mapM (readMaybe . cs) xs -newtype TimeParam = TimeParam- { getTimeParam :: Store.UnixTime- }+newtype TimeParam = TimeParam {get :: Store.UnixTime} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral) instance Param TimeParam where proxyLabel = const "time"- encodeParam _ (TimeParam t) = Just [cs $ show t]- parseParam _ [s] = TimeParam <$> readMaybe (cs s)- parseParam _ _ = Nothing+ encodeParam net ctx (TimeParam t) = Just [cs $ show t]+ parseParam net ctx [s] = TimeParam <$> readMaybe (cs s)+ parseParam net ctx _ = Nothing instance Param XPubKey where proxyLabel = const "xpub"- encodeParam net p = Just [xPubExport net p]- parseParam net [s] = xPubImport net s- parseParam _ _ = Nothing+ encodeParam net ctx p = Just [xPubExport net ctx p]+ parseParam net ctx [s] = xPubImport net ctx s+ parseParam net ctx _ = Nothing instance Param Store.DeriveType where proxyLabel = const "derive"- encodeParam net p = do- guard (getSegWit net || p == Store.DeriveNormal)+ encodeParam net ctx p = do+ guard (net.segWit || p == Store.DeriveNormal) Just [Store.deriveTypeToText p]- parseParam net d = do+ parseParam net ctx d = do res <- case d of [x] -> Store.textToDeriveType x _ -> Nothing- guard (getSegWit net || res == Store.DeriveNormal)+ guard (net.segWit || res == Store.DeriveNormal) return res -newtype NoCache = NoCache- { getNoCache :: Bool- }+newtype NoCache = NoCache {get :: Bool} deriving (Eq, Show, Read) instance Default NoCache where@@ -559,16 +545,14 @@ instance Param NoCache where proxyLabel = const "nocache"- encodeParam _ (NoCache True) = Just ["true"]- encodeParam _ (NoCache False) = Just ["false"]- parseParam _ = \case+ encodeParam net ctx (NoCache True) = Just ["true"]+ encodeParam net ctx (NoCache False) = Just ["false"]+ parseParam net ctx = \case ["true"] -> Just $ NoCache True ["false"] -> Just $ NoCache False _ -> Nothing -newtype NoTx = NoTx- { getNoTx :: Bool- }+newtype NoTx = NoTx {get :: Bool} deriving (Eq, Show, Read) instance Default NoTx where@@ -576,31 +560,31 @@ instance Param NoTx where proxyLabel = const "notx"- encodeParam _ (NoTx True) = Just ["true"]- encodeParam _ (NoTx False) = Just ["false"]- parseParam _ = \case+ encodeParam net ctx (NoTx True) = Just ["true"]+ encodeParam net ctx (NoTx False) = Just ["false"]+ parseParam net ctx = \case ["true"] -> Just $ NoTx True ["false"] -> Just $ NoTx False _ -> Nothing instance Param BlockHash where proxyLabel = const "block"- encodeParam _ b = Just [blockHashToHex b]- parseParam _ [s] = hexToBlockHash s- parseParam _ _ = Nothing+ encodeParam net ctx b = Just [blockHashToHex b]+ parseParam net ctx [s] = hexToBlockHash s+ parseParam net ctx _ = Nothing instance Param [BlockHash] where proxyLabel = const "blocks"- encodeParam _ bs = Just $ blockHashToHex <$> bs- parseParam _ = mapM hexToBlockHash+ encodeParam net ctx bs = Just $ blockHashToHex <$> bs+ parseParam net ctx = mapM hexToBlockHash instance Param TxHash where proxyLabel = const "txid"- encodeParam _ t = Just [txHashToHex t]- parseParam _ [s] = hexToTxHash s- parseParam _ _ = Nothing+ encodeParam net ctx t = Just [txHashToHex t]+ parseParam net ctx [s] = hexToTxHash s+ parseParam net ctx _ = Nothing instance Param [TxHash] where proxyLabel = const "txids"- encodeParam _ ts = Just $ txHashToHex <$> ts- parseParam _ = mapM hexToTxHash+ encodeParam net ctx ts = Just $ txHashToHex <$> ts+ parseParam net ctx = mapM hexToTxHash
test/Haskoin/Store/DataSpec.hs view
@@ -1,29 +1,33 @@+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE NoFieldSelectors #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -module Haskoin.Store.DataSpec- ( spec,- )-where+module Haskoin.Store.DataSpec (spec) where +import Control.Arrow (second) import Control.Monad (forM_) import Data.Aeson (FromJSON (..))-import qualified Data.ByteString as B+import Data.ByteString qualified as B import Data.String.Conversions (cs) import Haskoin import Haskoin.Store.Data+import Haskoin.Util import Haskoin.Util.Arbitrary-import Test.Hspec (Spec, describe)+import Test.Hspec import Test.QuickCheck -serialVals :: [SerialBox]-serialVals =+serialVals :: Ctx -> [SerialBox]+serialVals ctx = [ SerialBox (arbitrary :: Gen DeriveType),- SerialBox (arbitrary :: Gen XPubSpec),+ SerialBox (arbitraryXPubSpec ctx :: Gen XPubSpec), SerialBox (arbitrary :: Gen BlockRef), SerialBox (arbitrary :: Gen TxRef), SerialBox (arbitrary :: Gen Balance),@@ -33,7 +37,7 @@ SerialBox (arbitrary :: Gen Spender), SerialBox (arbitrary :: Gen StoreOutput), SerialBox (arbitrary :: Gen Prev),- SerialBox (arbitrary :: Gen TxData),+ SerialBox (arbitraryTxData ctx :: Gen TxData), SerialBox (arbitrary :: Gen Transaction), SerialBox (arbitrary :: Gen XPubBal), SerialBox (arbitrary :: Gen XPubUnspent),@@ -41,7 +45,7 @@ SerialBox (arbitrary :: Gen HealthCheck), SerialBox (arbitrary :: Gen Event), SerialBox (arbitrary :: Gen TxId),- SerialBox (arbitrary :: Gen PeerInformation),+ SerialBox (arbitrary :: Gen PeerInfo), SerialBox (arbitrary :: Gen (GenericResult BlockData)), SerialBox (arbitrary :: Gen (RawResult BlockData)), SerialBox (arbitrary :: Gen (RawResultList BlockData))@@ -56,7 +60,7 @@ JsonBox (arbitrary :: Gen HealthCheck), JsonBox (arbitrary :: Gen Event), JsonBox (arbitrary :: Gen TxId),- JsonBox (arbitrary :: Gen PeerInformation),+ JsonBox (arbitrary :: Gen PeerInfo), JsonBox (arbitrary :: Gen (GenericResult XPubSummary)), JsonBox (arbitrary :: Gen (RawResult BlockData)), JsonBox (arbitrary :: Gen (RawResultList BlockData)),@@ -72,135 +76,138 @@ JsonBox (arbitrary :: Gen BinfoShortBal), JsonBox (arbitrary :: Gen BinfoHistory), JsonBox (arbitrary :: Gen BinfoHeader),- JsonBox (arbitrary :: Gen BinfoBlocks)+ JsonBox (arbitrary :: Gen BinfoBlockInfos) ] -netVals :: [NetBox]-netVals =+netVals :: Ctx -> [NetBox]+netVals ctx = [ NetBox- ( balanceToJSON,- balanceToEncoding,- balanceParseJSON,- arbitraryNetData+ ( marshalValue,+ marshalEncoding,+ unmarshalValue,+ arbitraryNetData :: Gen (Network, Balance) ), NetBox- ( storeOutputToJSON,- storeOutputToEncoding,- storeOutputParseJSON,- arbitraryNetData+ ( marshalValue,+ marshalEncoding,+ unmarshalValue,+ arbitraryNetData :: Gen (Network, StoreOutput) ), NetBox- ( unspentToJSON,- unspentToEncoding,- unspentParseJSON,- arbitraryNetData+ ( marshalValue,+ marshalEncoding,+ unmarshalValue,+ arbitraryNetData :: Gen (Network, Unspent) ), NetBox- ( xPubBalToJSON,- xPubBalToEncoding,- xPubBalParseJSON,- arbitraryNetData+ ( marshalValue,+ marshalEncoding,+ unmarshalValue,+ arbitraryNetData :: Gen (Network, XPubBal) ), NetBox- ( xPubUnspentToJSON,- xPubUnspentToEncoding,- xPubUnspentParseJSON,- arbitraryNetData+ ( marshalValue,+ marshalEncoding,+ unmarshalValue,+ arbitraryNetData :: Gen (Network, XPubUnspent) ), NetBox- ( storeInputToJSON,- storeInputToEncoding,- storeInputParseJSON,+ ( marshalValue,+ marshalEncoding,+ unmarshalValue, arbitraryStoreInputNet ), NetBox- ( blockDataToJSON,- blockDataToEncoding,- const parseJSON,+ ( marshalValue,+ marshalEncoding,+ unmarshalValue, arbitraryBlockDataNet ), NetBox- ( transactionToJSON,- transactionToEncoding,- transactionParseJSON,- arbitraryNetData+ ( marshalValue,+ marshalEncoding,+ unmarshalValue,+ arbitraryNetData :: Gen (Network, Transaction) ), NetBox- ( binfoMultiAddrToJSON,- binfoMultiAddrToEncoding,- binfoMultiAddrParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoMultiAddr ctx) ), NetBox- ( binfoBalanceToJSON,- binfoBalanceToEncoding,- binfoBalanceParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoBalance ctx) ), NetBox- ( binfoBlockToJSON,- binfoBlockToEncoding,- binfoBlockParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoBlock ctx) ), NetBox- ( binfoTxToJSON,- binfoTxToEncoding,- binfoTxParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoTx ctx) ), NetBox- ( binfoTxInputToJSON,- binfoTxInputToEncoding,- binfoTxInputParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoTxInput ctx) ), NetBox- ( binfoTxOutputToJSON,- binfoTxOutputToEncoding,- binfoTxOutputParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoTxOutput ctx) ), NetBox- ( binfoXPubPathToJSON,- binfoXPubPathToEncoding,- binfoXPubPathParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoXPubPath ctx) ), NetBox- ( binfoUnspentToJSON,- binfoUnspentToEncoding,- binfoUnspentParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoUnspent ctx) ), NetBox- ( binfoBlocksToJSON,- binfoBlocksToEncoding,- binfoBlocksParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (listOf (arbitraryBinfoBlock ctx)) ), NetBox- ( binfoRawAddrToJSON,- binfoRawAddrToEncoding,- binfoRawAddrParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoRawAddr ctx) ), NetBox- ( binfoMempoolToJSON,- binfoMempoolToEncoding,- binfoMempoolParseJSON,- arbitraryNetData+ ( marshalValue . (,ctx),+ marshalEncoding . (,ctx),+ unmarshalValue . (,ctx),+ genNetData (arbitraryBinfoMempool ctx) ) ] spec :: Spec-spec = do+spec = prepareContext $ \ctx -> do describe "Binary Encoding" $- forM_ serialVals $ \(SerialBox g) -> testSerial g+ forM_ (serialVals ctx) $+ \(SerialBox g) -> testSerial g describe "JSON Encoding" $- forM_ jsonVals $ \(JsonBox g) -> testJson g+ forM_ jsonVals $+ \(JsonBox g) -> testJson g describe "JSON Encoding with Network" $- forM_ netVals $ \(NetBox (j, e, p, g)) -> testNetJson j e p g+ forM_ (netVals ctx) $+ \(NetBox (j, e, p, g)) -> testNetJson j e p g instance Arbitrary BlockRef where arbitrary =@@ -209,16 +216,16 @@ instance Arbitrary Prev where arbitrary = Prev <$> arbitraryBS1 <*> arbitrary -instance Arbitrary TxData where- arbitrary =- TxData- <$> arbitrary- <*> arbitraryTx btc- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary+arbitraryTxData :: Ctx -> Gen TxData+arbitraryTxData ctx =+ TxData+ <$> arbitrary+ <*> arbitraryTx btc ctx+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary instance Arbitrary StoreInput where arbitrary =@@ -243,9 +250,12 @@ net <- arbitraryNetwork store <- arbitrary let res- | getSegWit net = store- | otherwise = store {inputWitness = []}+ | net.segWit = store+ | otherwise = witless store return (net, res)+ where+ witless StoreInput {..} = StoreInput {witness = [], ..}+ witless StoreCoinbase {..} = StoreCoinbase {witness = [], ..} instance Arbitrary Spender where arbitrary = Spender <$> arbitraryTxHash <*> arbitrary@@ -274,9 +284,9 @@ <*> arbitrary <*> arbitrary -instance Arbitrary PeerInformation where+instance Arbitrary PeerInfo where arbitrary =- PeerInformation+ PeerInfo <$> (cs <$> listOf arbitraryUnicodeChar) <*> listOf arbitraryPrintableChar <*> arbitrary@@ -318,6 +328,7 @@ <*> arbitrary <*> arbitrary <*> arbitrary+ <*> arbitrary instance Arbitrary RejectCode where arbitrary =@@ -332,8 +343,8 @@ RejectCheckpoint ] -instance Arbitrary XPubSpec where- arbitrary = XPubSpec <$> (snd <$> arbitraryXPubKey) <*> arbitrary+arbitraryXPubSpec :: Ctx -> Gen XPubSpec+arbitraryXPubSpec ctx = XPubSpec <$> (snd <$> arbitraryXPubKey ctx) <*> arbitrary instance Arbitrary DeriveType where arbitrary = elements [DeriveNormal, DeriveP2SH, DeriveP2WPKH]@@ -380,19 +391,19 @@ arbitraryBlockDataNet :: Gen (Network, BlockData) arbitraryBlockDataNet = do net <- arbitraryNetwork- dat <- arbitrary+ dat@BlockData {..} <- arbitrary let res- | getSegWit net = dat- | otherwise = dat {blockDataWeight = 0}+ | net.segWit = dat+ | otherwise = BlockData {weight = 0, ..} return (net, res) -instance Arbitrary a => Arbitrary (GenericResult a) where+instance (Arbitrary a) => Arbitrary (GenericResult a) where arbitrary = GenericResult <$> arbitrary -instance Arbitrary a => Arbitrary (RawResult a) where+instance (Arbitrary a) => Arbitrary (RawResult a) where arbitrary = RawResult <$> arbitrary -instance Arbitrary a => Arbitrary (RawResultList a) where+instance (Arbitrary a) => Arbitrary (RawResultList a) where arbitrary = RawResultList <$> arbitrary instance Arbitrary XPubBal where@@ -441,157 +452,156 @@ BinfoTxIdIndex <$> arbitrary ] -instance Arbitrary BinfoMultiAddr where- arbitrary = do- getBinfoMultiAddrAddresses <- arbitrary- getBinfoMultiAddrWallet <- arbitrary- getBinfoMultiAddrTxs <- resize 10 arbitrary- getBinfoMultiAddrInfo <- arbitrary- getBinfoMultiAddrRecommendFee <- arbitrary- getBinfoMultiAddrCashAddr <- arbitrary- return BinfoMultiAddr {..}+arbitraryBinfoMultiAddr :: Ctx -> Gen BinfoMultiAddr+arbitraryBinfoMultiAddr ctx = do+ addresses <- listOf1 $ arbitraryBinfoBalance ctx+ wallet <- arbitrary+ txs <- listOf $ arbitraryBinfoTx ctx+ info <- arbitrary+ recommendFee <- arbitrary+ cashAddr <- arbitrary+ return BinfoMultiAddr {..} -instance Arbitrary BinfoRawAddr where- arbitrary = do- binfoRawAddr <-- oneof- [ BinfoAddr <$> arbitraryAddress,- BinfoXpub . snd <$> arbitraryXPubKey- ]- binfoRawBalance <- arbitrary- binfoRawTxCount <- arbitrary- binfoRawUnredeemed <- arbitrary- binfoRawReceived <- arbitrary- binfoRawSent <- arbitrary- binfoRawTxs <- arbitrary- return $ BinfoRawAddr {..}+arbitraryBinfoRawAddr :: Ctx -> Gen BinfoRawAddr+arbitraryBinfoRawAddr ctx = do+ address <-+ oneof+ [ BinfoAddr <$> arbitraryAddress,+ BinfoXpub . snd <$> arbitraryXPubKey ctx+ ]+ balance <- arbitrary+ ntx <- arbitrary+ utxo <- arbitrary+ received <- arbitrary+ sent <- arbitrary+ txs <- listOf $ arbitraryBinfoTx ctx+ return $ BinfoRawAddr {..} instance Arbitrary BinfoShortBal where arbitrary = BinfoShortBal <$> arbitrary <*> arbitrary <*> arbitrary -instance Arbitrary BinfoBalance where- arbitrary = do- getBinfoAddress <- arbitraryAddress- getBinfoAddrTxCount <- arbitrary- getBinfoAddrReceived <- arbitrary- getBinfoAddrSent <- arbitrary- getBinfoAddrBalance <- arbitrary- getBinfoXPubKey <- snd <$> arbitraryXPubKey- getBinfoXPubAccountIndex <- arbitrary- getBinfoXPubChangeIndex <- arbitrary- elements [BinfoAddrBalance {..}, BinfoXPubBalance {..}]+arbitraryBinfoBalance :: Ctx -> Gen BinfoBalance+arbitraryBinfoBalance ctx = do+ address <- arbitraryAddress+ txs <- arbitrary+ received <- arbitrary+ sent <- arbitrary+ balance <- arbitrary+ xpub <- snd <$> arbitraryXPubKey ctx+ external <- arbitrary+ change <- arbitrary+ elements [BinfoAddrBalance {..}, BinfoXPubBalance {..}] instance Arbitrary BinfoWallet where arbitrary = do- getBinfoWalletBalance <- arbitrary- getBinfoWalletTxCount <- arbitrary- getBinfoWalletFilteredCount <- arbitrary- getBinfoWalletTotalReceived <- arbitrary- getBinfoWalletTotalSent <- arbitrary+ balance <- arbitrary+ txs <- arbitrary+ filtered <- arbitrary+ received <- arbitrary+ sent <- arbitrary return BinfoWallet {..} -instance Arbitrary BinfoBlock where- arbitrary = do- getBinfoBlockHash <- arbitraryBlockHash- getBinfoBlockVer <- arbitrary- getBinfoPrevBlock <- arbitraryBlockHash- getBinfoMerkleRoot <- getTxHash <$> arbitraryTxHash- getBinfoBlockTime <- arbitrary- getBinfoBlockBits <- arbitrary- getBinfoNextBlock <- listOf arbitraryBlockHash- getBinfoBlockTxCount <- arbitrary- getBinfoBlockFee <- arbitrary- getBinfoBlockNonce <- arbitrary- getBinfoBlockSize <- arbitrary- getBinfoBlockIndex <- arbitrary- getBinfoBlockMain <- arbitrary- getBinfoBlockHeight <- arbitrary- getBinfoBlockWeight <- arbitrary- getBinfoBlockTx <- resize 5 arbitrary- return BinfoBlock {..}+arbitraryBinfoBlock :: Ctx -> Gen BinfoBlock+arbitraryBinfoBlock ctx = do+ hash <- arbitraryBlockHash+ version <- arbitrary+ prev <- arbitraryBlockHash+ merkle <- (.get) <$> arbitraryTxHash+ timestamp <- arbitrary+ bits <- arbitrary+ next <- listOf arbitraryBlockHash+ ntx <- arbitrary+ fee <- arbitrary+ nonce <- arbitrary+ size <- arbitrary+ index <- arbitrary+ main <- arbitrary+ height <- arbitrary+ weight <- arbitrary+ txs <- resize 5 $ listOf $ arbitraryBinfoTx ctx+ return BinfoBlock {..} -instance Arbitrary BinfoTx where- arbitrary = do- getBinfoTxHash <- arbitraryTxHash- getBinfoTxVer <- arbitrary- getBinfoTxInputs <- resize 5 $ listOf1 arbitrary- getBinfoTxOutputs <- resize 5 $ listOf1 arbitrary- let getBinfoTxVinSz = fromIntegral (length getBinfoTxInputs)- getBinfoTxVoutSz = fromIntegral (length getBinfoTxOutputs)- getBinfoTxSize <- arbitrary- getBinfoTxWeight <- arbitrary- getBinfoTxFee <- arbitrary- getBinfoTxRelayedBy <- cs <$> listOf arbitraryUnicodeChar- getBinfoTxLockTime <- arbitrary- getBinfoTxIndex <- arbitrary- getBinfoTxDoubleSpend <- arbitrary- getBinfoTxRBF <- arbitrary- getBinfoTxTime <- arbitrary- getBinfoTxBlockIndex <- arbitrary- getBinfoTxBlockHeight <- arbitrary- getBinfoTxResultBal <- arbitrary- return BinfoTx {..}+arbitraryBinfoTx :: Ctx -> Gen BinfoTx+arbitraryBinfoTx ctx = do+ txid <- arbitraryTxHash+ version <- arbitrary+ inputs <- resize 5 $ listOf1 $ arbitraryBinfoTxInput ctx+ outputs <- resize 5 $ listOf1 $ arbitraryBinfoTxOutput ctx+ let inputCount = fromIntegral $ length inputs+ outputCount = fromIntegral $ length outputs+ size <- arbitrary+ weight <- arbitrary+ fee <- arbitrary+ relayed <- cs <$> listOf arbitraryUnicodeChar+ locktime <- arbitrary+ index <- arbitrary+ doubleSpend <- arbitrary+ rbf <- arbitrary+ timestamp <- arbitrary+ blockIndex <- arbitrary+ blockHeight <- arbitrary+ balance <- arbitrary+ return BinfoTx {..} -instance Arbitrary BinfoTxInput where- arbitrary = do- getBinfoTxInputSeq <- arbitrary- getBinfoTxInputWitness <- B.pack <$> listOf arbitrary- getBinfoTxInputScript <- B.pack <$> listOf arbitrary- getBinfoTxInputIndex <- arbitrary- getBinfoTxInputPrevOut <- arbitrary- return BinfoTxInput {..}+arbitraryBinfoTxInput :: Ctx -> Gen BinfoTxInput+arbitraryBinfoTxInput ctx = do+ sequence <- arbitrary+ witness <- B.pack <$> listOf arbitrary+ script <- B.pack <$> listOf arbitrary+ index <- arbitrary+ output <- arbitraryBinfoTxOutput ctx+ return BinfoTxInput {..} -instance Arbitrary BinfoTxOutput where- arbitrary = do- getBinfoTxOutputType <- arbitrary- getBinfoTxOutputSpent <- arbitrary- getBinfoTxOutputValue <- arbitrary- getBinfoTxOutputIndex <- arbitrary- getBinfoTxOutputTxIndex <- arbitrary- getBinfoTxOutputScript <- B.pack <$> listOf arbitrary- getBinfoTxOutputSpenders <- arbitrary- getBinfoTxOutputAddress <-- oneof [return Nothing, Just <$> arbitraryAddress]- getBinfoTxOutputXPub <- arbitrary- return BinfoTxOutput {..}+arbitraryBinfoTxOutput :: Ctx -> Gen BinfoTxOutput+arbitraryBinfoTxOutput ctx = do+ typ <- arbitrary+ spent <- arbitrary+ value <- arbitrary+ index <- arbitrary+ txidx <- arbitrary+ script <- B.pack <$> listOf arbitrary+ spenders <- arbitrary+ address <- arbitraryMaybe arbitraryAddress+ xpub <- arbitraryMaybe $ arbitraryBinfoXPubPath ctx+ return BinfoTxOutput {..} instance Arbitrary BinfoSpender where arbitrary = do- getBinfoSpenderTxIndex <- arbitrary- getBinfoSpenderIndex <- arbitrary+ txidx <- arbitrary+ input <- arbitrary return BinfoSpender {..} -instance Arbitrary BinfoXPubPath where- arbitrary = do- getBinfoXPubPathKey <- snd <$> arbitraryXPubKey- getBinfoXPubPathDeriv <- arbitrarySoftPath- return BinfoXPubPath {..}+arbitraryBinfoXPubPath :: Ctx -> Gen BinfoXPubPath+arbitraryBinfoXPubPath ctx = do+ key <- snd <$> arbitraryXPubKey ctx+ deriv <- arbitrarySoftPath+ return BinfoXPubPath {..} instance Arbitrary BinfoInfo where arbitrary = do- getBinfoConnected <- arbitrary- getBinfoConversion <- arbitrary- getBinfoLocal <- arbitrary- getBinfoBTC <- arbitrary- getBinfoLatestBlock <- arbitrary+ connected <- arbitrary+ conversion <- arbitrary+ fiat <- arbitrary+ crypto <- arbitrary+ head <- arbitrary return BinfoInfo {..} instance Arbitrary BinfoBlockInfo where arbitrary = do- getBinfoBlockInfoHash <- arbitraryBlockHash- getBinfoBlockInfoHeight <- arbitrary- getBinfoBlockInfoTime <- arbitrary- getBinfoBlockInfoIndex <- arbitrary+ hash <- arbitraryBlockHash+ height <- arbitrary+ timestamp <- arbitrary+ index <- arbitrary return BinfoBlockInfo {..} instance Arbitrary BinfoSymbol where arbitrary = do- getBinfoSymbolCode <- cs <$> listOf1 arbitraryUnicodeChar- getBinfoSymbolString <- cs <$> listOf1 arbitraryUnicodeChar- getBinfoSymbolName <- cs <$> listOf1 arbitraryUnicodeChar- getBinfoSymbolConversion <- arbitrary- getBinfoSymbolAfter <- arbitrary- getBinfoSymbolLocal <- arbitrary+ code <- cs <$> listOf1 arbitraryUnicodeChar+ symbol <- cs <$> listOf1 arbitraryUnicodeChar+ name <- cs <$> listOf1 arbitraryUnicodeChar+ conversion <- arbitrary+ after <- arbitrary+ local <- arbitrary return BinfoSymbol {..} instance Arbitrary BinfoRate where@@ -599,39 +609,40 @@ instance Arbitrary BinfoTicker where arbitrary = do- binfoTicker15m <- arbitrary- binfoTickerSell <- arbitrary- binfoTickerBuy <- arbitrary- binfoTickerLast <- arbitrary- binfoTickerSymbol <- cs <$> listOf1 arbitraryUnicodeChar+ fifteen <- arbitrary+ sell <- arbitrary+ buy <- arbitrary+ last <- arbitrary+ symbol <- cs <$> listOf1 arbitraryUnicodeChar return BinfoTicker {..} instance Arbitrary BinfoHistory where arbitrary = do- binfoHistoryDate <- cs <$> listOf1 arbitraryUnicodeChar- binfoHistoryTime <- cs <$> listOf1 arbitraryUnicodeChar- binfoHistoryType <- cs <$> listOf1 arbitraryUnicodeChar- binfoHistoryAmount <- arbitrary- binfoHistoryValueThen <- arbitrary- binfoHistoryValueNow <- arbitrary- binfoHistoryExchangeRateThen <- arbitrary- binfoHistoryTx <- arbitraryTxHash- binfoHistoryFee <- arbitrary+ date <- cs <$> listOf1 arbitraryUnicodeChar+ time <- cs <$> listOf1 arbitraryUnicodeChar+ typ <- cs <$> listOf1 arbitraryUnicodeChar+ amount <- arbitrary+ valueThen <- arbitrary+ valueNow <- arbitrary+ rateThen <- arbitrary+ txid <- arbitraryTxHash+ fee <- arbitrary return BinfoHistory {..} -instance Arbitrary BinfoUnspent where- arbitrary = do- getBinfoUnspentHash <- arbitraryTxHash- getBinfoUnspentOutputIndex <- arbitrary- getBinfoUnspentScript <- B.pack <$> listOf arbitrary- getBinfoUnspentValue <- arbitrary- getBinfoUnspentConfirmations <- arbitrary- getBinfoUnspentTxIndex <- arbitrary- getBinfoUnspentXPub <- arbitrary- return BinfoUnspent {..}+arbitraryBinfoUnspent :: Ctx -> Gen BinfoUnspent+arbitraryBinfoUnspent ctx = do+ txid <- arbitraryTxHash+ index <- arbitrary+ script <- B.pack <$> listOf arbitrary+ value <- arbitrary+ confirmations <- arbitrary+ txidx <- arbitrary+ xpub <- arbitraryMaybe $ arbitraryBinfoXPubPath ctx+ return BinfoUnspent {..} -instance Arbitrary BinfoUnspents where- arbitrary = BinfoUnspents <$> arbitrary+arbitraryBinfoUnspents :: Ctx -> Gen BinfoUnspents+arbitraryBinfoUnspents ctx =+ fmap BinfoUnspents $ listOf $ arbitraryBinfoUnspent ctx instance Arbitrary BinfoHeader where arbitrary =@@ -642,8 +653,9 @@ <*> arbitrary <*> arbitrary -instance Arbitrary BinfoMempool where- arbitrary = BinfoMempool <$> arbitrary+arbitraryBinfoMempool :: Ctx -> Gen BinfoMempool+arbitraryBinfoMempool ctx =+ fmap BinfoMempool $ listOf $ arbitraryBinfoTx ctx -instance Arbitrary BinfoBlocks where- arbitrary = BinfoBlocks <$> arbitrary+instance Arbitrary BinfoBlockInfos where+ arbitrary = BinfoBlockInfos <$> arbitrary
test/Haskoin/Store/WebCommonSpec.hs view
@@ -1,14 +1,13 @@+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -module Haskoin.Store.WebCommonSpec- ( spec,- )-where+module Haskoin.Store.WebCommonSpec (spec) where import Control.Monad (forM_) import Data.Proxy@@ -18,6 +17,7 @@ import Haskoin.Store.Data import Haskoin.Store.DataSpec () import Haskoin.Store.WebCommon+import Haskoin.Util import Haskoin.Util.Arbitrary import Test.Hspec import Test.Hspec.QuickCheck@@ -25,8 +25,8 @@ data GenBox = forall p. (Show p, Eq p, Param p) => GenBox (Gen p) -params :: [GenBox]-params =+params :: Ctx -> [GenBox]+params ctx = [ GenBox arbitraryAddress, GenBox (listOf arbitraryAddress), GenBox (arbitrary :: Gen StartParam),@@ -35,7 +35,7 @@ GenBox (arbitrarySizedNatural :: Gen HeightParam), GenBox (arbitrary :: Gen HeightsParam), GenBox (arbitrarySizedNatural :: Gen TimeParam),- GenBox (snd <$> arbitraryXPubKey :: Gen XPubKey),+ GenBox (snd <$> arbitraryXPubKey ctx :: Gen XPubKey), GenBox (arbitrary :: Gen DeriveType), GenBox (NoCache <$> arbitrary :: Gen NoCache), GenBox (NoTx <$> arbitrary :: Gen NoTx),@@ -46,16 +46,17 @@ ] spec :: Spec-spec =+spec = prepareContext $ \ctx -> describe "Parameter encoding" $- forM_ params $ \(GenBox g) -> testParam g+ forM_ (params ctx) $+ \(GenBox g) -> testParam ctx g -testParam :: (Eq a, Show a, Param a) => Gen a -> Spec-testParam pGen =+testParam :: (Eq a, Show a, Param a) => Ctx -> Gen a -> Spec+testParam ctx pGen = prop ("encodeParam/parseParam identity for parameter " <> name) $ forAll pGen $ \p ->- case encodeParam btc p of- Just txts -> parseParam btc txts `shouldBe` Just p+ case encodeParam btc ctx p of+ Just txts -> parseParam btc ctx txts `shouldBe` Just p _ -> expectationFailure "Param encoding failed" where name = cs $ proxyLabel $ proxy pGen