packages feed

blockfrost-api (empty) → 0.1.0.0

raw patch · 73 files changed

+6383/−0 lines, 73 filesdep +QuickCheckdep +aesondep +basesetup-changed

Dependencies added: QuickCheck, aeson, base, blockfrost-api, bytestring, data-default, data-default-class, deriving-aeson, hspec, lens, quickcheck-instances, raw-strings-qq, safe-money, servant, servant-docs, servant-multipart-api, tasty, tasty-hspec, tasty-hunit, template-haskell, text, time, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,10 @@+# Version [0.1.0.0](https://github.com/blockfrost/blockfrost-haskell/compare/initial...0.1.0.0) (2021-09-14)++* Initial release++---++`blockfrost-api` uses [PVP Versioning][1].++[1]: https://pvp.haskell.org+
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright 2021 blockfrost.io++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.
+ README.md view
@@ -0,0 +1,44 @@+# blockfrost-api++Core types and Servant API definitions.++## Exploring data types++All data types have a `ToSample` instance+for [servant-docs](https://hackage.haskell.org/package/servant-docs)+which can be used to get a sample response in `cabal repl`:++``` haskell+λ: import Data.Proxy+λ: import Servant.Docs+λ: Just block = toSample (Proxy :: Proxy Block)+λ: _blockHash block+BlockHash "4ea1ba291e8eef538635a53e59fddba7810d1679631cc3aed7c8e6c4091a516a"+```++## Lenses++Instead of using long record names, it is recommended+to use provided lenses and a [lens](https://hackage.haskell.org/package/lens) or similar package.++``` haskell+λ: import Control.Lens (^.)+λ: import Blockfrost.Lens+λ: block ^. epoch+Just (Epoch 425)+```++## Monetary values++Ada values and values of assets are represented using [`Discrete`](https://hackage.haskell.org/package/safe-money/docs/Money.html#t:Discrete) type+from [safe-money](https://hackage.haskell.org/package/safe-money) library.++We use a type alias `type Lovelaces = Money.Discrete "ADA" "lovelace"`+for Ada values and [`SomeDiscrete`](https://hackage.haskell.org/package/safe-money/docs/Money.html#t:SomeDiscrete) for asset values. This should allow working+with monetary values safely, preventing summing different currencies.+See the [blog post](https://ren.zone/articles/safe-money) by the library author.++``` haskell+λ: block ^. fees+Just (Discrete "ADA" 1000000%1 592661)+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ blockfrost-api.cabal view
@@ -0,0 +1,167 @@+cabal-version:       2.2+name:                blockfrost-api+version:             0.1.0.0+synopsis:            API definitions for blockfrost.io+description:         Core types and Servant API description+homepage:            https://github.com/blockfrost/blockfrost-haskell+license:             Apache-2.0+license-file:        LICENSE+author:              blockfrost.io+maintainer:          srk@48.io+copyright:           2021 blockfrost.io+category:            Web+build-type:          Simple++extra-source-files:+    CHANGELOG.md+    LICENSE+    README.md++flag BuildFast+     Default: True+     Description: Turn off optimizations++flag Production+     Default: False+     Manual: True+     Description: Production build++common libstuff+  default-language:    Haskell2010+  default-extensions:+    DataKinds+    DeriveAnyClass+    DeriveGeneric+    DerivingVia+    GADTs+    GeneralizedNewtypeDeriving+    FlexibleContexts+    FlexibleInstances+    InstanceSigs+    LambdaCase+    MultiParamTypeClasses+    QuasiQuotes+    RankNTypes+    ScopedTypeVariables+    TemplateHaskell+    TypeApplications+    TypeFamilies+    TypeOperators+    ViewPatterns+    OverloadedStrings+  ghc-options:         -Wall -Wunused-packages -fno-warn-orphans+  if flag(BuildFast)+    ghc-options: -O0+  if flag(Production)+    ghc-options: -Werror++library+   import:              libstuff+   hs-source-dirs:      src+   exposed-modules:     Blockfrost.API+                      , Blockfrost.API.Common+                      , Blockfrost.API.Cardano+                      , Blockfrost.API.Cardano.Accounts+                      , Blockfrost.API.Cardano.Addresses+                      , Blockfrost.API.Cardano.Assets+                      , Blockfrost.API.Cardano.Blocks+                      , Blockfrost.API.Cardano.Epochs+                      , Blockfrost.API.Cardano.Ledger+                      , Blockfrost.API.Cardano.Metadata+                      , Blockfrost.API.Cardano.Network+                      , Blockfrost.API.Cardano.Pools+                      , Blockfrost.API.Cardano.Transactions+                      , Blockfrost.API.IPFS+                      , Blockfrost.API.NutLink+                      , Blockfrost.Auth+                      , Blockfrost.Env+                      , Blockfrost.Lens+                      , Blockfrost.Types+                      , Blockfrost.Types.ApiError+                      , Blockfrost.Types.Common+                      , Blockfrost.Types.Cardano+                      , Blockfrost.Types.Cardano.Accounts+                      , Blockfrost.Types.Cardano.Addresses+                      , Blockfrost.Types.Cardano.Assets+                      , Blockfrost.Types.Cardano.Blocks+                      , Blockfrost.Types.Cardano.Epochs+                      , Blockfrost.Types.Cardano.Genesis+                      , Blockfrost.Types.Cardano.Metadata+                      , Blockfrost.Types.Cardano.Network+                      , Blockfrost.Types.Cardano.Pools+                      , Blockfrost.Types.Cardano.Transactions+                      , Blockfrost.Types.IPFS+                      , Blockfrost.Types.NutLink+                      , Blockfrost.Types.Shared+                      , Blockfrost.Types.Shared.Ada+                      , Blockfrost.Types.Shared.Address+                      , Blockfrost.Types.Shared.BlockHash+                      , Blockfrost.Types.Shared.BlockIndex+                      , Blockfrost.Types.Shared.Epoch+                      , Blockfrost.Types.Shared.CBOR+                      , Blockfrost.Types.Shared.Opts+                      , Blockfrost.Types.Shared.POSIXMillis+                      , Blockfrost.Types.Shared.Slot+                      , Blockfrost.Types.Shared.TxHash+                      , Blockfrost.Types.Shared.Amount+                      , Blockfrost.Types.Shared.AssetId+                      , Blockfrost.Types.Shared.PolicyId+                      , Blockfrost.Types.Shared.PoolId+                      , Blockfrost.Types.Shared.Quantity+                      , Blockfrost.Util.LensRules+                      , Blockfrost.Util.Pagination+                      , Blockfrost.Util.Sorting+                      , Blockfrost.Util.Tag+   build-depends:       base >= 4.7 && < 5+                      , bytestring+                      , data-default-class+                      , text+                      , time+                      , aeson+                      , deriving-aeson+                      , lens+                      , template-haskell+                      , servant                  ^>= 0.18+                      , servant-docs+                      , servant-multipart-api+                      , safe-money+                      , quickcheck-instances+                      , QuickCheck++test-suite blockfrost-api-tests+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  other-modules:       AmountSpec+                     , APISpec+                     , Cardano.Accounts+                     , Cardano.Addresses+                     , Cardano.Assets+                     , Cardano.Epochs+                     , Cardano.Ledger+                     , Cardano.Metadata+                     , Cardano.Network+                     , Cardano.Pools+                     , Cardano.Transactions+                     , IPFS+                     , NutLink+  build-depends:       base >= 4.7 && < 5+                     , blockfrost-api+                     , aeson+                     , bytestring+                     , text+                     , data-default+                     , safe-money+                     , raw-strings-qq+                     , vector+                     , hspec+                     , tasty+                     , tasty-hspec+                     , tasty-hunit+  build-tool-depends:+    tasty-discover:tasty-discover+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/blockfrost/blockfrost-haskell
+ src/Blockfrost/API.hs view
@@ -0,0 +1,136 @@+-- | Blockfrost API definitions++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API+  ( BlockfrostAPI (..)+  , BlockfrostV0API (..)+  , CommonAPI (..)+  , CardanoAPI (..)+  , IPFSAPI (..)+  , NutLinkAPI (..)+  , module Blockfrost.API.Cardano+  , api+  , api0+  , ServantBlockfrostAPI+  , BlockfrostAuth+  , Project (..)+  , Form (..)+  ) where++import Data.Proxy (Proxy (..))+import Servant.API+import Servant.API.Generic++import Blockfrost.API.Cardano+import Blockfrost.API.Common+import Blockfrost.API.IPFS+import Blockfrost.API.NutLink+import Blockfrost.Auth+import Blockfrost.Types+import Blockfrost.Util.Tag (Tag)++-- * API++-- ** Our custom auth++type BlockfrostAuth = ProjectAuth '[APIKeyInHeader "project_id"] Project++-- ** Generic API types++-- *** Toplevel++newtype BlockfrostAPI route =+  BlockfrostAPI+    {+      _apiV0 :: route+      :- "api"+      :> "v0"+      :> BlockfrostAuth+      :> ToServantApi BlockfrostV0API+    } deriving (Generic)++-- *** V0, requiring auth++data BlockfrostV0API route =+  BlockfrostV0API+    {+      _common  :: route :- ToServantApi CommonAPI+    , _cardano :: route :- ToServantApi CardanoAPI+    , _ipfs    :: route :- "ipfs" :> ToServantApi IPFSAPI+    , _nutLink :: route :- "nutlink"  :> Tag "Nut.link" :> ToServantApi NutLinkAPI+    } deriving (Generic)++-- *** Services++data CardanoAPI route =+  CardanoAPI+    {+      _accounts+      :: route+      :- "accounts"+      :> Tag "Cardano » Accounts"+      :> ToServantApi AccountsAPI+    , _addresses+      :: route+      :- "addresses"+      :> Tag "Cardano » Addresses"+      :> ToServantApi AddressesAPI+    , _assets+      :: route+      :- "assets"+      :> Tag "Cardano » Assets"+      :> ToServantApi AssetsAPI+    , _blocks+      :: route+      :- "blocks"+      :> Tag "Cardano » Blocks"+      :> ToServantApi BlocksAPI+    , _epochs+      :: route+      :- "epochs"+      :> Tag "Cardano » Epochs"+      :> ToServantApi EpochsAPI+    , _ledger+      :: route+      :- "genesis"+      :> Tag "Cardano » Ledger"+      :> ToServantApi LedgerAPI+    , _metadata+      :: route+      :- "metadata"+      :> Tag "Cardano » Metadata"+      :> ToServantApi MetadataAPI+    , _network+      :: route+      :- "network"+      :> Tag "Cardano » Network"+      :> ToServantApi NetworkAPI+    , _pools+      :: route+      :- "pools"+      :> Tag "Cardano » Pools"+      :> ToServantApi PoolsAPI+    , _transactions+      :: route+      :- "txs"+      :> Tag "Cardano » Transactions"+      :> ToServantApi TransactionsAPI+    , _txSubmit+      :: route+      :- Summary "Submit a transaction"+      :> Description "Submit an already serialized transaction to the network."+      :> Tag "Cardano » Transactions"+      :> "tx"+      :> "submit"+      :> ReqBody '[CBOR] CBORString+      :> Post '[JSON] TxHash+    } deriving (Generic)++type ServantBlockfrostAPI = ToServantApi BlockfrostAPI++api :: Proxy (ToServantApi BlockfrostAPI)+api = genericApi (Proxy :: Proxy BlockfrostAPI)++api0 :: Proxy (ToServantApi BlockfrostV0API)+api0 = genericApi (Proxy :: Proxy BlockfrostV0API)
+ src/Blockfrost/API/Cardano.hs view
@@ -0,0 +1,27 @@+-- | Cardano services++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano+  ( module Blockfrost.API.Cardano.Accounts+  , module Blockfrost.API.Cardano.Addresses+  , module Blockfrost.API.Cardano.Assets+  , module Blockfrost.API.Cardano.Blocks+  , module Blockfrost.API.Cardano.Epochs+  , module Blockfrost.API.Cardano.Ledger+  , module Blockfrost.API.Cardano.Metadata+  , module Blockfrost.API.Cardano.Network+  , module Blockfrost.API.Cardano.Pools+  , module Blockfrost.API.Cardano.Transactions+  ) where++import Blockfrost.API.Cardano.Accounts+import Blockfrost.API.Cardano.Addresses+import Blockfrost.API.Cardano.Assets+import Blockfrost.API.Cardano.Blocks+import Blockfrost.API.Cardano.Epochs+import Blockfrost.API.Cardano.Ledger+import Blockfrost.API.Cardano.Metadata+import Blockfrost.API.Cardano.Network+import Blockfrost.API.Cardano.Pools+import Blockfrost.API.Cardano.Transactions
+ src/Blockfrost/API/Cardano/Accounts.hs view
@@ -0,0 +1,79 @@+-- | Accounts API endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Accounts+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Accounts+import Blockfrost.Types.Shared+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data AccountsAPI route =+  AccountsAPI+    {+      _account+        :: route+        :- Summary "Specific account address"+        :> Description "Obtain information about a specific stake account."+        :> Capture "stake_address" Address+        :> Get '[JSON] AccountInfo+    , _accountRewards+        :: route+        :- Summary "Specific reward history"+        :> Description "Obtain information about the reward history of a specific account."+        :> Capture "stake_address" Address+        :> "rewards"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AccountReward]+     , _accountHistory+        :: route+        :- Summary "Account history"+        :> Description "Obtain information about the history of a specific account."+        :> Capture "stake_address" Address+        :> "history"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AccountHistory]+     , _accountDelegations+        :: route+        :- Summary "Account delegation history"+        :> Description "Obtain information about the delegation of a specific account."+        :> Capture "stake_address" Address+        :> "delegations"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AccountDelegation]+     , _accountRegistrations+        :: route+        :- Summary "Account registration history"+        :> Description "Obtain information about the registrations and deregistrations of a specific account."+        :> Capture "stake_address" Address+        :> "registrations"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AccountRegistration]+     , _accountWithdrawals+        :: route+        :- Summary "Account withdrawal history"+        :> Description "Obtain information about the withdrawals of a specific account."+        :> Capture "stake_address" Address+        :> "withdrawals"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AccountWithdrawal]+     , _accountMirs+        :: route+        :- Summary "Account MIR history"+        :> Description "Obtain information about the MIRs of a specific account."+        :> Capture "stake_address" Address+        :> "mirs"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AccountMir]+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Addresses.hs view
@@ -0,0 +1,52 @@+-- | Addresses API endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Addresses+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Addresses+import Blockfrost.Types.Shared+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data AddressesAPI route =+  AddressesAPI+    {+      _addressInfo+        :: route+        :- Summary "Specific address"+        :> Description "Obtain information about a specific address."+        :> Capture "address" Address+        :> Get '[JSON] AddressInfo+    , _addressDetails+        :: route+        :- Summary "Address details"+        :> Description "Obtain details about an address."+        :> Capture "address" Address+        :> "total"+        :> Get '[JSON] AddressDetails+    , _addressUtxos+        :: route+        :- Summary "Address UTXOs"+        :> Description "UTXOs of the address."+        :> Capture "address" Address+        :> "utxos"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AddressUTXO]+    , _addressTransactions+        :: route+        :- Summary "Address transactions"+        :> Description "Transactions on the address."+        :> Capture "address" Address+        :> "transactions"+        :> Pagination+        :> Sorting+        :> QueryParam "from" BlockIndex+        :> QueryParam "to" BlockIndex+        :> Get '[JSON] [AddressTransaction]+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Assets.hs view
@@ -0,0 +1,68 @@+-- | Assets API endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Assets+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Assets+import Blockfrost.Types.Shared+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data AssetsAPI route =+  AssetsAPI+    {+      _listAssets+        :: route+        :- Summary "Assets"+        :> Description "List of assets."+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AssetInfo]+    , _assetDetails+        :: route+        :- Summary "Specific asset"+        :> Description "Information about a specific asset."+        :> Capture "asset" AssetId+        :> Get '[JSON] AssetDetails+    , _assetHistory+        :: route+        :- Summary "Asset history"+        :> Description "History of a specific asset."+        :> Capture "asset" AssetId+        :> "history"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AssetHistory]+    , _assetTransactions+        :: route+        :- Summary "Asset transactions"+        :> Description "List of a specific asset transactions"+        :> Capture "asset" AssetId+        :> "transactions"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AssetTransaction]+    , _assetAddresses+        :: route+        :- Summary "Asset addresses"+        :> Description "List of a addresses containing a specific asset"+        :> Capture "asset" AssetId+        :> "addresses"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AssetAddress]+    , _listAssetsPolicy+        :: route+        :- Summary "Assets of a specific policy"+        :> Description "List of asset minted under a specific policy."+        :> "policy"+        :> Capture "policy_id" PolicyId+        :> Pagination+        :> Sorting+        :> Get '[JSON] [AssetInfo]+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Blocks.hs view
@@ -0,0 +1,82 @@+-- | Blocks API endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Blocks+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Blocks+import Blockfrost.Types.Shared+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data BlocksAPI route =+  BlocksAPI+    {+      _latest+        :: route+        :- Summary "Latest block"+        :> Description "Return the latest block available to the backends, \+                        \also known as the tip of the blockchain."+        :> "latest"+        :> Get '[JSON] Block+    , _latestTxs+        :: route+        :- Summary "Latest block transactions"+        :> Description "Return the transactions within the latest block."+        :> "latest"+        :> "txs"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [TxHash]+     , _block+        :: route+        :- Summary "Latest block transactions"+        :> Description "Return the transactions within the latest block."+        :> Capture "hash_or_number" (Either Integer BlockHash)+        :> Get '[JSON] Block+     , __blockSlot+        :: route+        :- Summary "Specific block in a slot"+        :> Description "Return the content of a requested block for a specific slot."+        :> "slot"+        :> Capture "slot_number" Slot+        :> Get '[JSON] Block+     , __blockEpochSlot+        :: route+        :- Summary "Specific block in a slot in an epoch"+        :> Description "Return the content of a requested block for a specific slot in an epoch."+        :> "epoch"+        :> Capture "epoch_number" Epoch+        :> "slot"+        :> Capture "slot_number" Slot+        :> Get '[JSON] Block+      , _blockNext+        :: route+        :- Summary "Listing of next blocks"+        :> Description "Return the list of blocks following a specific block."+        :> Capture "hash_or_number" (Either Integer BlockHash)+        :> "next"+        :> Pagination+        :> Get '[JSON] [Block]+      , _blockPrevious+        :: route+        :- Summary "Listing of preious blocks"+        :> Description "Return the list of blocks preceeding a specific block."+        :> Capture "hash_or_number" (Either Integer BlockHash)+        :> "previous"+        :> Pagination+        :> Get '[JSON] [Block]+      , _blockTxs+        :: route+        :- Summary "Block transactions"+        :> Description "Return the transactions within the block."+        :> Capture "hash_or_number" (Either Integer BlockHash)+        :> "txs"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [TxHash]+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Epochs.hs view
@@ -0,0 +1,97 @@+-- | Epochs API endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Epochs+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Epochs+import Blockfrost.Types.Shared+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data EpochsAPI route =+  EpochsAPI+    {+      _latestEpoch+        :: route+        :- Summary "Latest epoch"+        :> Description "Return the information about the latest, therefore current, epoch."+        :> "latest"+        :> Get '[JSON] EpochInfo+    , _latestEpochProtocolParams+        :: route+        :- Summary "Latest epoch protocol parameters"+        :> Description "Return the protocol parameters for the latest epoch."+        :> "latest"+        :> "parameters"+        :> Get '[JSON] ProtocolParams+    , _getEpoch+        :: route+        :- Summary "Specific epoch"+        :> Description "Return the content of the requested epoch."+        :> Capture "epoch_number" Epoch+        :> Get '[JSON] EpochInfo+    , _getNextEpochs+        :: route+        :- Summary "List of next epochs"+        :> Description "Return the list of epochs following a specific epoch."+        :> Capture "epoch_number" Epoch+        :> "next"+        :> Pagination+        :> Get '[JSON] [EpochInfo]+     , _getPreviousEpochs+        :: route+        :- Summary "List of previous epochs"+        :> Description "Return the list of epochs preceding a specific epoch."+        :> Capture "epoch_number" Epoch+        :> "previous"+        :> Pagination+        :> Get '[JSON] [EpochInfo]+     , _getEpochStake+        :: route+        :- Summary "Stake distribution"+        :> Description "Return the active stake distribution for the specified epoch."+        :> Capture "epoch_number" Epoch+        :> "stakes"+        :> Pagination+        :> Get '[JSON] [StakeDistribution]+     , _getEpochStakeByPool+        :: route+        :- Summary "Stake distribution by pool"+        :> Description "Return the active stake distribution for the epoch specified by stake pool."+        :> Capture "epoch_number" Epoch+        :> "stakes"+        :> Capture "pool_id" PoolId+        :> Pagination+        :> Get '[JSON] [StakeDistribution]+     , _getEpochBlocks+        :: route+        :- Summary "Block distribution"+        :> Description "Return the blocks minted for the epoch specified."+        :> Capture "epoch_number" Epoch+        :> "blocks"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [BlockHash]+     , _getEpochBlocksByPool+        :: route+        :- Summary "Block distribution"+        :> Description "Return the block minted for the epoch specified by stake pool."+        :> Capture "epoch_number" Epoch+        :> "blocks"+        :> Capture "pool_id" PoolId+        :> Pagination+        :> Sorting+        :> Get '[JSON] [BlockHash]+    , _getEpochProtocolParams+        :: route+        :- Summary "Protocol parameters"+        :> Description "Return the protocol parameters for the specified epoch."+        :> Capture "epoch_number" Epoch+        :> "parameters"+        :> Get '[JSON] ProtocolParams+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Ledger.hs view
@@ -0,0 +1,21 @@+-- | Cardano Ledger endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Ledger+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Genesis++data LedgerAPI route =+  LedgerAPI+    {+      _genesis+        :: route+        :- Summary "Blockchain genesis"+        :> Description "Return the information about blockchain genesis."+        :> Get '[JSON] Genesis+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Metadata.hs view
@@ -0,0 +1,53 @@+-- | Cardano Metadata endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Metadata+  where++import Data.Text (Text)+import Servant.API+import Servant.API.Generic+import Servant.Docs (DocCapture (..), ToCapture (..))++import Blockfrost.Types.Cardano.Metadata+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data MetadataAPI route =+  MetadataAPI+    {+      _txMetadataLabels+        :: route+        :- Summary "Transaction metadata labels"+        :> Description "List of all used transaction metadata labels."+        :> "txs"+        :> "labels"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [TxMeta]+    , _txMetadataByLabelJSON+        :: route+        :- Summary "Transaction metadata content in JSON"+        :> Description "Transaction metadata per label."+        :> "txs"+        :> "labels"+        :> Capture "label" Text+        :> Pagination+        :> Sorting+        :> Get '[JSON] [TxMetaJSON]+    , _txMetadataByLabelCBOR+        :: route+        :- Summary "Transaction metadata content in CBOR"+        :> Description "Transaction metadata per label."+        :> "txs"+        :> "labels"+        :> Capture "label" Text+        :> "cbor"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [TxMetaCBOR]+    } deriving (Generic)++instance ToCapture (Capture "label" Text) where+  toCapture _ = DocCapture "label" "Metadata label"
+ src/Blockfrost/API/Cardano/Network.hs view
@@ -0,0 +1,21 @@+-- | Cardano Network endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Network+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Network++data NetworkAPI route =+  NetworkAPI+    {+      _networkInfo+        :: route+        :- Summary "Network information"+        :> Description "Return detailed network information."+        :> Get '[JSON] Network+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Pools.hs view
@@ -0,0 +1,98 @@+-- | Cardano Pools endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Pools+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Pools+import Blockfrost.Types.Shared+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting++data PoolsAPI route =+  PoolsAPI+    {+      _listPools+        :: route+        :- Summary "List of stake pools"+        :> Description "List of registered stake pools."+        :> Pagination+        :> Sorting+        :> Get '[JSON] [PoolId]+    , _listRetiredPools+        :: route+        :- Summary "List of retired stake pools"+        :> Description "List of already retired stake pools."+        :> "retired"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [PoolEpoch]+    , _listRetiringPools+        :: route+        :- Summary "List of retiring stake pools"+        :> Description "List of stake pools retiring in the upcoming epochs"+        :> "retiring"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [PoolEpoch]+    , _getPool+        :: route+        :- Summary "Specific stake pool"+        :> Description "Pool information."+        :> Capture "pool_id" PoolId+        :> Get '[JSON] PoolInfo+   , _getPoolHistory+        :: route+        :- Summary "Stake pool history"+        :> Description "History of stake pool parameters over epochs."+        :> Capture "pool_id" PoolId+        :> "history"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [PoolHistory]+    , _getPoolMetadata+        :: route+        :- Summary "Stake pool metadata"+        :> Description "Stake pool registration metadata."+        :> Capture "pool_id" PoolId+        :> "metadata"+        :> Get '[JSON] (Maybe PoolMetadata)+    , _getPoolRelays+        :: route+        :- Summary "Stake pool relays"+        :> Description "Relays of a stake pool."+        :> Capture "pool_id" PoolId+        :> "relays"+        :> Get '[JSON] [PoolRelay]+    , _getPoolDelegators+        :: route+        :- Summary "Stake pool delegators"+        :> Description "List of current stake pools delegators."+        :> Capture "pool_id" PoolId+        :> "delegators"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [PoolDelegator]+    , _getPoolBlocks+        :: route+        :- Summary "Stake pool blocks"+        :> Description "List of stake pool blocks."+        :> Capture "pool_id" PoolId+        :> "blocks"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [BlockHash]+    , _getPoolUpdates+        :: route+        :- Summary "Stake pool updates"+        :> Description "List of certificate updates to the stake pool."+        :> Capture "pool_id" PoolId+        :> "updates"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [PoolUpdate]+    } deriving (Generic)
+ src/Blockfrost/API/Cardano/Transactions.hs view
@@ -0,0 +1,87 @@+-- | Cardano Pools endpoints++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Cardano.Transactions+  where++import Servant.API+import Servant.API.Generic++import Blockfrost.Types.Cardano.Transactions+import Blockfrost.Types.Shared++data TransactionsAPI route =+  TransactionsAPI+    {+      _tx+        :: route+        :- Summary "Specific transaction"+        :> Description "Return content of the requested transaction."+        :> Capture "hash" TxHash+        :> Get '[JSON] Transaction+    , _txUtxos+        :: route+        :- Summary "Transaction UTXOs"+        :> Description "Return the inputs and UTXOs of the specific transaction."+        :> Capture "hash" TxHash+        :> "utxos"+        :> Get '[JSON] TransactionUtxos+    , _txStakes+        :: route+        :- Summary "Transaction stake addresses certificates "+        :> Description "Obtain information about (de)registration of stake addresses within a transaction."+        :> Capture "hash" TxHash+        :> "stakes"+        :> Get '[JSON] [TransactionStake]+    , _txDelegations+        :: route+        :- Summary "Transaction delegation certificates"+        :> Description "Obtain information about delegation certificates of a specific transaction."+        :> Capture "hash" TxHash+        :> "delegations"+        :> Get '[JSON] [TransactionDelegation]+    , _txWithdrawals+        :: route+        :- Summary "Transaction withdrawal"+        :> Description "Obtain information about withdrawals of a specific transaction."+        :> Capture "hash" TxHash+        :> "withdrawals"+        :> Get '[JSON] [TransactionWithdrawal]+    , _txMirs+        :: route+        :- Summary "Transaction MIRs"+        :> Description "Obtain information about Move Instantaneous Rewards (MIRs) of a specific transaction."+        :> Capture "hash" TxHash+        :> "mirs"+        :> Get '[JSON] [TransactionMir]+    , _txPoolUpdates+        :: route+        :- Summary "Transaction stake pool registration and update certificates"+        :> Description "Obtain information about stake pool registration and update certificates of a specific transaction."+        :> Capture "hash" TxHash+        :> "pool_updates"+        :> Get '[JSON] [TransactionPoolUpdate]+    , _txPoolRetiring+        :: route+        :- Summary "Transaction stake pool retirement certificates"+        :> Description "Obtain information about stake pool retirements within a specific transaction."+        :> Capture "hash" TxHash+        :> "pool_retires"+        :> Get '[JSON] [TransactionPoolRetiring]+    , _txMetadataJSON+        :: route+        :- Summary "Transaction metadata"+        :> Description "Obtain the transaction metadata."+        :> Capture "hash" TxHash+        :> "metadata"+        :> Get '[JSON] [TransactionMetaJSON]+    , _txMetadataCBOR+        :: route+        :- Summary "Transaction metadata in CBOR"+        :> Description "Obtain the transaction metadata in CBOR."+        :> Capture "hash" TxHash+        :> "metadata"+        :> "cbor"+        :> Get '[JSON] [TransactionMetaCBOR]+    } deriving (Generic)
+ src/Blockfrost/API/Common.hs view
@@ -0,0 +1,56 @@+-- | Common services++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.Common+  where++import Blockfrost.Types+import Blockfrost.Util.Tag (Tag)+import Data.Text (Text)+import Servant.API+import Servant.API.Generic++data CommonAPI route =+  CommonAPI+    {+      _getRoot+        :: route+        :- Summary "Root endpoint"+        :> Description "Root endpoint has no other function than to point end users to documentation."+        :> Tag "Health"+        :> Get '[JSON] URLVersion+    , _getHealth+        :: route+        :- Summary "Backend health status"+        :> Description "Return backend status as a boolean. \+                        \Your application should handle situations when backend for the given chain is unavailable."+        :> Tag "Health"+        :> "health"+        :> Get '[JSON] Healthy+    , _getClock+        :: route+        :- Summary "Current backend time"+        :> Description "This endpoint provides the current UNIX time. \+                         \Your application might use this to verify if \+                         \the client clock is not out of sync."+        :> Tag "Health"+        :> "health"+        :> "clock"+        :> Get '[JSON] ServerTime+    , _metrics+        :: route+        :- Summary "Blockfrost usage metrics"+        :> Description "History of your Blockfrost usage metrics in the past 30 days."+        :> Tag "Metrics"+        :> "metrics"+        :> Get '[JSON] [Metric]+    , _metricsEndpoints+        :: route+        :- Summary "Blockfrost endpoint usage metrics"+        :> Description "History of your Blockfrost usage metrics per endpoint in the past 30 days."+        :> Tag "Metrics"+        :> "metrics"+        :> "endpoints"+        :> Get '[JSON] [(Text, Metric)]+    } deriving (Generic)
+ src/Blockfrost/API/IPFS.hs view
@@ -0,0 +1,84 @@+-- | IPFS services++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.IPFS+  where++import Blockfrost.Types+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting+import Blockfrost.Util.Tag (Tag)+import Data.Text (Text)+import Servant.API+import Servant.API.Generic+import Servant.Docs (DocCapture (..), ToCapture (..))+import Servant.Multipart.API++data IPFSAPI route =+  IPFSAPI+    {+      _add+        :: route+        :- Summary "Add a file or directory to IPFS"+        :> Description "You need to `/ipfs/pin/add` an object to avoid it being garbage collected. \+                        \This usage is being counted in your user account quota."+        :> Tag "IPFS » Add"+        :> "add"+        :> MultipartForm Tmp Form+        :> Post '[JSON] IPFSAdd+    , _gateway+        :: route+        :- Summary "Relay to an IPFS gateway"+        :> Description "Retrieve an object from the IFPS gateway. \+                        \(Useful if you do not want to rely on a public gateway, such as ``ipfs.blockfrost.dev`)."+        :> Tag "IPFS » Gateway"+        :> "gateway"+        :> Capture "IPFS_path" Text+        :> Get '[PlainText, OctetStream] IPFSData+    , _pin+        :: route+        :- Summary "Pin an object"+        :> Description "Pinned objects are counted in your user storage quota."+        :> Tag "IPFS » Pins"+        :> "pin"+        :> "add"+        :> Capture "IPFS_path" Text+        :> Post '[JSON] IPFSPinChange+    , _listPins+        :: route+        :- Summary "List pinned objects"+        :> Description "List objects pinned to local storage."+        :> Tag "IPFS » Pins"+        :> "pin"+        :> "list"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [IPFSPin]+    , _getPin+        :: route+        :- Summary "Get pinned object details"+        :> Description "Obtain inormation about specific pinned object."+        :> Tag "IPFS » Pins"+        :> "pin"+        :> "list"+        :> Capture "IPFS_path" Text+        :> Get '[JSON] IPFSPin+    , _removePin+        :: route+        :- Summary "Remove pinned object from local storage"+        :> Description "Remove pinned object from local storage"+        :> Tag "IPFS » Pins"+        :> "pin"+        :> "remove"+        :> Capture "IPFS_path" Text+        :> Post '[JSON] IPFSPinChange+    } deriving (Generic)++instance ToCapture (Capture "IPFS_path" Text) where+  toCapture _ = DocCapture "IPFS_path" "Path to the IPFS object"++data Form = Form {+    formFileName :: Text+  , formFilePath :: FilePath+  }
+ src/Blockfrost/API/NutLink.hs view
@@ -0,0 +1,56 @@+-- | Nut.link services++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.API.NutLink+  where++import Blockfrost.Types+import Blockfrost.Util.Pagination+import Blockfrost.Util.Sorting+import Data.Text (Text)+import Servant.API+import Servant.API.Generic+import Servant.Docs (DocCapture (..), ToCapture (..))++data NutLinkAPI route =+  NutLinkAPI+    {+      _address+        :: route+        :- Summary "List metadata about specific address"+        :> Description "List metadata about specific address"+        :> Capture "address" Address+        :> Get '[JSON] NutlinkAddress+    , _listAddressTickers+        :: route+        :- Summary "List tickers for a specific metadata oracle"+        :> Description "List tickers for a specific metadata oracle"+        :> Capture "address" Address+        :> "tickers"+        :> Pagination+        :> Sorting+        :> Get '[JSON] [NutlinkAddressTicker]+    , _addressTickers+        :: route+        :- Summary "List of records of a specific ticker"+        :> Description "List of records of a specific ticker"+        :> Capture "address" Address+        :> "tickers"+        :> Capture "ticker" Text+        :> Pagination+        :> Sorting+        :> Get '[JSON] [NutlinkTicker]+    , _tickers+        :: route+        :- Summary "List of records of a specific ticker"+        :> Description "List of records of a specific ticker"+        :> "tickers"+        :> Capture "ticker" Text+        :> Pagination+        :> Sorting+        :> Get '[JSON] [(Address, NutlinkTicker)]+    } deriving (Generic)++instance ToCapture (Capture "ticker" Text) where+  toCapture _ = DocCapture "ticker" "Ticker for the pool record"
+ src/Blockfrost/Auth.hs view
@@ -0,0 +1,74 @@+-- | Blockfrost authentication schemes+{-# LANGUAGE PolyKinds #-}++module Blockfrost.Auth+  ( APIKeyInHeader+  , APIKeyInHeaderSettings (..)+  , Env (..)+  , Project (..)+  , ProjectAuth+  , mkProject+  , mkProjectEnv+  ) where++import Data.Kind (Type)+import Data.Proxy (Proxy (..))+import Data.Text as Text+import GHC.Generics (Generic)+import GHC.TypeLits+import Servant.API (HasLink (..), (:>))++import qualified Data.Text++import Data.String (IsString (..))++import Blockfrost.Env++-- * Authentication result and clients token++data Project = Project {+    projectEnv :: Env+  , projectId  :: Text+  }+  deriving (Eq, Show, Generic)++-- | Parse @Project@ from @Text@+mkProject' :: Text -> Either Text Project+mkProject' t =+  let st = Data.Text.strip t+      tEnv = Data.Text.dropEnd 32 st+      token = Data.Text.drop (Data.Text.length tEnv) st+  in Project <$> parseEnv tEnv <*> pure token++-- | Parse @Project@ from @Text@ or fail with error+mkProject :: Text -> Project+mkProject = either (error . Data.Text.unpack) id . mkProject'++-- | @Project@ constructor+mkProjectEnv :: Env -> Text -> Project+mkProjectEnv = Project++instance IsString Project where+  fromString = mkProject . Data.Text.pack++-- * Common++-- | The type of Auth scheme.+data APIKeyInHeader (headerName :: Symbol)++-- | Auth scheme settings+-- Needs IO action to verify passed in token and maybe return Project+newtype APIKeyInHeaderSettings =+  APIKeyInHeaderSettings+    { apiKeySettingsQueryProject :: Text -> IO (Maybe Project)+    }++-- * Custom Auth+-- we use custom ProjectAuth instead of Auth from servant-auth+-- to get rid of superfluous Contexts like JWTSettings and CookiesSettings++data ProjectAuth (auths :: [Type]) val++instance HasLink sub => HasLink (ProjectAuth (tag :: [Type]) value :> sub) where+  type MkLink (ProjectAuth (tag :: [Type]) value :> sub) r = MkLink sub r+  toLink toA _ = toLink toA (Proxy @sub)
+ src/Blockfrost/Env.hs view
@@ -0,0 +1,32 @@+-- | Blockfrost environments++module Blockfrost.Env+  ( Env (..)+  , parseEnv+  ) where++import Data.Text (Text)+import qualified Data.Text+import GHC.Generics (Generic)+import qualified Text.Read++-- | Blockfrost environments+--+-- Corresponds to Network when creating a Blockfrost project.+-- Each environment has separate token.+data Env =+    Alonzo+  | Ipfs+  | Mainnet+  | Testnet+  | Localhost+  deriving (Eq, Read, Show, Ord, Generic)++-- | Try parsing @Env@ from @Text@+parseEnv :: Text -> Either Text Env+parseEnv tEnv = case Text.Read.readMaybe (Data.Text.unpack $ Data.Text.toTitle tEnv) of+  Just env -> pure env+  Nothing ->+    Left+      $ "Unknown environment: `" <> tEnv <> "`"+      <> " expecting one of `alonzo`, `ipfs`, `mainnet`, `testnet`, `localhost`"
+ src/Blockfrost/Lens.hs view
@@ -0,0 +1,86 @@+-- | Lenses for Blockfrost types++{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE UndecidableInstances #-}++module Blockfrost.Lens+  where++import Blockfrost.Types+import Blockfrost.Util.LensRules+import Control.Lens++makeLensesWith blockfrostFieldRules ''URLVersion++makeFields ''AccountInfo+makeFields ''AccountReward+makeFields ''AccountHistory+makeFields ''AccountDelegation+makeFields ''AccountRegistration+makeFields ''AccountWithdrawal+makeFields ''AccountMir++makeLensesWith blockfrostFieldRules ''AddressInfo+makeFields ''AddressDetails+makeFields ''AddressUTXO+makeFields ''AddressTransaction++makeFields ''AssetInfo+makeFields ''AssetDetails+makeFields ''AssetOnChainMetadata+makeFields ''AssetMetadata+makeFields ''AssetHistory+makeFields ''AssetTransaction+makeFields ''AssetAddress++makeFields ''Block++makeFields ''EpochInfo+makeFields ''ProtocolParams+makeFields ''StakeDistribution+makeFields ''PoolStakeDistribution++makeFields ''Genesis++makeFields ''TxMeta+makeFields ''TxMetaJSON+makeFields ''TxMetaCBOR++makeFields ''Network+makeFields ''NetworkSupply+makeFields ''NetworkStake++makeFields ''PoolEpoch+makeFields ''PoolInfo+makeFields ''PoolHistory+makeFields ''PoolMetadata+makeFields ''PoolRelay+makeFields ''PoolDelegator+makeFields ''PoolUpdate+makeFields ''PoolRegistrationAction++makeFields ''Transaction+makeFields ''TransactionUtxos+makeFields ''UtxoInput+makeFields ''UtxoOutput+makeFields ''TransactionStake+makeFields ''TransactionDelegation+makeFields ''TransactionWithdrawal+makeFields ''TransactionMir+makeFields ''TransactionPoolUpdate+makeFields ''PoolUpdateMetadata+makeFields ''TransactionPoolRetiring+makeFields ''TransactionMetaJSON+makeFields ''TransactionMetaCBOR++makeLensesWith blockfrostFieldRules ''IPFSAdd+makeLensesWith blockfrostFieldRules ''IPFSPinChange+makeLensesWith blockfrostFieldRules ''IPFSPin++makeFields ''NutlinkAddress+makeFields ''NutlinkAddressTicker+makeFields ''NutlinkTicker++-- * Shared+makeFields ''BlockIndex+makePrisms ''Amount
+ src/Blockfrost/Types.hs view
@@ -0,0 +1,17 @@+-- | Blockfrost types++module Blockfrost.Types+  ( module Blockfrost.Types.ApiError+  , module Blockfrost.Types.Cardano+  , module Blockfrost.Types.Common+  , module Blockfrost.Types.IPFS+  , module Blockfrost.Types.NutLink+  , module Blockfrost.Types.Shared+  ) where++import Blockfrost.Types.ApiError+import Blockfrost.Types.Cardano+import Blockfrost.Types.Common+import Blockfrost.Types.IPFS+import Blockfrost.Types.NutLink+import Blockfrost.Types.Shared
+ src/Blockfrost/Types/ApiError.hs view
@@ -0,0 +1,35 @@+-- | Internal API error type++{-# LANGUAGE RecordWildCards #-}++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.Types.ApiError+  ( ApiError (..)+  ) where++import Data.Aeson+import Data.Text (Text)+import GHC.Generics++-- | Fancy JSON error returned+-- by the server+data ApiError = ApiError+  { apiError        :: Text+  , apiErrorMessage :: Text+  , apiErrorCode    :: Int+  } deriving (Eq, Show, Generic)++instance ToJSON ApiError  where+  toJSON ApiError{..} =+    object [ "error" .= toJSON apiError+           , "message" .= toJSON apiErrorMessage+           , "status_code" .= toJSON apiErrorCode+           ]++instance FromJSON ApiError  where+  parseJSON = withObject "error" $ \o -> do+    apiError <- o .: "error"+    apiErrorMessage <- o .: "message"+    apiErrorCode <- o .: "status_code"+    pure $ ApiError {..}
+ src/Blockfrost/Types/Cardano.hs view
@@ -0,0 +1,25 @@+-- | Types of Cardano++module Blockfrost.Types.Cardano+  ( module Blockfrost.Types.Cardano.Accounts+  , module Blockfrost.Types.Cardano.Addresses+  , module Blockfrost.Types.Cardano.Assets+  , module Blockfrost.Types.Cardano.Blocks+  , module Blockfrost.Types.Cardano.Epochs+  , module Blockfrost.Types.Cardano.Genesis+  , module Blockfrost.Types.Cardano.Metadata+  , module Blockfrost.Types.Cardano.Network+  , module Blockfrost.Types.Cardano.Pools+  , module Blockfrost.Types.Cardano.Transactions+  ) where++import Blockfrost.Types.Cardano.Accounts+import Blockfrost.Types.Cardano.Addresses+import Blockfrost.Types.Cardano.Assets+import Blockfrost.Types.Cardano.Blocks+import Blockfrost.Types.Cardano.Epochs+import Blockfrost.Types.Cardano.Genesis+import Blockfrost.Types.Cardano.Metadata+import Blockfrost.Types.Cardano.Network+import Blockfrost.Types.Cardano.Pools+import Blockfrost.Types.Cardano.Transactions
+ src/Blockfrost/Types/Cardano/Accounts.hs view
@@ -0,0 +1,204 @@+-- | Responses for Cardano accounts queries++module Blockfrost.Types.Cardano.Accounts+  ( AccountInfo (..)+  , AccountReward (..)+  , AccountHistory (..)+  , AccountDelegation (..)+  , AccountRegistration (..)+  , AccountRegistrationAction (..)+  , AccountWithdrawal (..)+  , AccountMir (..)+  ) where++import Blockfrost.Types.Shared+import Deriving.Aeson+import Servant.Docs (ToSample (..), samples, singleSample)++-- | Information about an account, identified by its stake address+data AccountInfo = AccountInfo+  { _accountInfoStakeAddress       :: Address -- ^ Bech32 stake address+  , _accountInfoActive             :: Bool -- ^ Registration state of an account+  , _accountInfoActiveEpoch        :: Integer -- ^ Epoch of the most recent action - registration or deregistration+  , _accountInfoControlledAmount   :: Lovelaces  -- ^ Balance of the account in Lovelaces+  , _accountInfoRewardsSum         :: Lovelaces -- ^ Sum of all funds rewards for the account in the Lovelaces+  , _accountInfoWithdrawalsSum     :: Lovelaces -- ^ Sum of all the withdrawals for the account in the Lovelaces+  , _accountInfoReservesSum        :: Lovelaces -- ^ Sum of all funds from reserves for the account in the Lovelaces+  , _accountInfoTreasurySum        :: Lovelaces -- ^ Sum of all funds from treasury for the account in the Lovelaces+  , _accountInfoWithdrawableAmount :: Lovelaces -- ^ Sum of available rewards that haven't been withdrawn yet for the account in the Lovelaces+  , _accountInfoPoolId             :: Maybe PoolId -- ^ Bech32 pool ID that owns the account+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountInfo", CamelToSnake]] AccountInfo++instance ToSample AccountInfo where+  toSamples = pure $ singleSample $ AccountInfo+    { _accountInfoStakeAddress = "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7"+    , _accountInfoActive = True+    , _accountInfoActiveEpoch = 412+    , _accountInfoControlledAmount = 619154618165+    , _accountInfoRewardsSum = 319154618165+    , _accountInfoWithdrawalsSum = 12125369253+    , _accountInfoReservesSum = 319154618165+    , _accountInfoTreasurySum = 12000000+    , _accountInfoWithdrawableAmount = 319154618165+    , _accountInfoPoolId = pure "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    }++-- | Reward received by an account+data AccountReward = AccountReward+  { _accountRewardEpoch  :: Epoch -- ^ Epoch of the associated reward+  , _accountRewardAmount :: Lovelaces -- ^ Rewards for given epoch in Lovelaces+  , _accountRewardPoolId :: PoolId -- ^ Bech32 pool ID being delegated to+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountReward", CamelToSnake]] AccountReward++instance ToSample AccountReward where+  toSamples = pure $ samples+    [ AccountReward+        { _accountRewardEpoch = 214+        , _accountRewardAmount = 1395265+        , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    , AccountReward+        { _accountRewardEpoch = 215+        , _accountRewardAmount = 58632+        , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    , AccountReward+        { _accountRewardEpoch = 216+        , _accountRewardAmount = 0+        , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    , AccountReward+        { _accountRewardEpoch = 217+        , _accountRewardAmount = 1395265+        , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    ]++-- | History of accounts stake delegation+data AccountHistory = AccountHistory+  { _accountHistoryActiveEpoch :: Integer -- ^ Epoch in which the stake was active+  , _accountHistoryAmount      :: Lovelaces -- ^ Stake amount in Lovelaces+  , _accountHistoryPoolId      :: PoolId -- ^ Bech32 ID of pool being delegated to+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountHistory", CamelToSnake]] AccountHistory++instance ToSample AccountHistory where+  toSamples  = pure $ samples+    [ AccountHistory+        { _accountHistoryActiveEpoch = 260+        , _accountHistoryAmount = 1395265+        , _accountHistoryPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    , AccountHistory+        { _accountHistoryActiveEpoch = 211+        , _accountHistoryAmount = 22695385+        , _accountHistoryPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    ]++-- | Account delegations and associated transaction IDs+data AccountDelegation = AccountDelegation+  { _accountDelegationActiveEpoch :: Epoch -- ^ Epoch in which the delegation becomes active+  , _accountDelegationTxHash      :: TxHash -- ^ Hash of the transaction containing the delegation+  , _accountDelegationAmount      :: Lovelaces -- ^ Rewards for given epoch in Lovelaces+  , _accountDelegationPoolId      :: PoolId -- ^ Bech32 ID of pool being delegated to+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountDelegation", CamelToSnake]] AccountDelegation++instance ToSample AccountDelegation where+  toSamples = pure $ samples+    [ AccountDelegation+        { _accountDelegationActiveEpoch = 210+        , _accountDelegationTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+        , _accountDelegationAmount = 12695385+        , _accountDelegationPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+        }+    , AccountDelegation+        { _accountDelegationActiveEpoch = 242+        , _accountDelegationTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0"+        , _accountDelegationAmount = 12691385+        , _accountDelegationPoolId = "pool1kchver88u3kygsak8wgll7htr8uxn5v35lfrsyy842nkscrzyvj"+        }+    ]++-- | Registration action+data AccountRegistrationAction = Registered | Deregistered+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[ConstructorTagModifier '[ToLower]] AccountRegistrationAction++instance ToSample AccountRegistrationAction where+  toSamples = pure $ samples [ Registered, Deregistered ]++-- | Account (de)registration+data AccountRegistration = AccountRegistration+  { _accountRegistrationAction :: AccountRegistrationAction -- ^ Action in the certificate+  , _accountRegistrationTxHash :: TxHash -- ^ Hash of the transaction containing the (de)registration certificate+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountRegistration", CamelToSnake]] AccountRegistration++instance ToSample AccountRegistration where+  toSamples = pure $ samples+    [ AccountRegistration+        { _accountRegistrationAction = Registered+        , _accountRegistrationTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+        }+    , AccountRegistration+        { _accountRegistrationAction = Deregistered+        , _accountRegistrationTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0"+        }+    ]++-- | Withdrawal from an account+data AccountWithdrawal = AccountWithdrawal+  { _accountWithdrawalAmount :: Lovelaces -- ^ Withdrawal amount in Lovelaces+  , _accountWithdrawalTxHash :: TxHash -- ^ Hash of the transaction containing the withdrawal+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountWithdrawal", CamelToSnake]] AccountWithdrawal++instance ToSample AccountWithdrawal where+  toSamples = pure $ samples+    [ AccountWithdrawal+        { _accountWithdrawalAmount = 454541212442+        , _accountWithdrawalTxHash = "48a9625c841eea0dd2bb6cf551eabe6523b7290c9ce34be74eedef2dd8f7ecc5"+        }+    , AccountWithdrawal+        { _accountWithdrawalAmount = 97846969+        , _accountWithdrawalTxHash = "4230b0cbccf6f449f0847d8ad1d634a7a49df60d8c142bb8cc2dbc8ca03d9e34"+        }+    ]++-- | Account MIR (Move Instantaneous Reward)+data AccountMir = AccountMir+  { _accountMirAmount :: Lovelaces -- ^ MIR amount in Lovelaces+  , _accountMirTxHash :: TxHash -- ^ Hash of the transaction containing the MIR+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_accountMir", CamelToSnake]] AccountMir++instance ToSample AccountMir where+  toSamples = pure $ samples+    [ AccountMir+        { _accountMirAmount = 6202170+        , _accountMirTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+        }+    , AccountMir+        { _accountMirAmount = 1202170+        , _accountMirTxHash = "1dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+        }+    ]
+ src/Blockfrost/Types/Cardano/Addresses.hs view
@@ -0,0 +1,143 @@+-- | Responses for Cardano address queries++module Blockfrost.Types.Cardano.Addresses+  ( AddressInfo (..)+  , AddressType (..)+  , AddressDetails (..)+  , AddressUTXO (..)+  , AddressTransaction (..)+  ) where++import Blockfrost.Types.Shared+import Deriving.Aeson+import qualified Money+import Servant.Docs (ToSample (..), samples, singleSample)++-- | Information about Cardano address+data AddressInfo = AddressInfo+  { _addressInfoAddress      :: Address -- ^ Bech32 encoded addresses+  , _addressInfoAmount       :: [Amount] -- ^ Lovelaces or tokens stored on this address+  , _addressInfoStakeAddress :: Maybe Address -- ^ Stake address that controls the key+  , _addressInfoType         :: AddressType -- ^ Address era+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_addressInfo", CamelToSnake]] AddressInfo++instance ToSample AddressInfo where+  toSamples = pure $ singleSample+    AddressInfo+      { _addressInfoAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+      , _addressInfoAmount =+        [ AdaAmount 42000000+        , AssetAmount+            $ Money.mkSomeDiscrete+                "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                unitScale+                12+        ]+      , _addressInfoStakeAddress = pure "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7"+      , _addressInfoType = Shelley+      }++-- | Type (era) of an address+data AddressType = Byron | Shelley+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[ConstructorTagModifier '[ToLower]] AddressType++instance ToSample AddressType where+  toSamples = pure $ samples [ Byron, Shelley ]++-- | Details about Cardano address+data AddressDetails = AddressDetails+  { _addressDetailsAddress     :: Address -- ^ Bech32 encoded address+  , _addressDetailsReceivedSum :: [Amount] -- ^ Total Lovelaces or tokens received by this address+  , _addressDetailsSentSum     :: [Amount] -- ^ Total Lovelaces or tokens sent by this address+  , _addressDetailsTxCount     :: Integer -- ^ Count of all transactions on the address+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_addressDetails", CamelToSnake]] AddressDetails++instance ToSample AddressDetails where+  toSamples = pure $ singleSample+    AddressDetails+      { _addressDetailsAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+      , _addressDetailsReceivedSum = amounts+      , _addressDetailsSentSum = amounts+      , _addressDetailsTxCount = 12+      }+    where amounts =+            [ AdaAmount 42000000+            , AssetAmount+                $ Money.mkSomeDiscrete+                        "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                        unitScale+                        12+            ]++-- | UTxOs of the address+data AddressUTXO = AddressUTXO+  { _addressUTXOTxHash      :: TxHash -- ^ Transaction hash of the UTXO+  , _addressUTXOOutputIndex :: Integer -- ^ UTXO index in the transaction+  , _addressUTXOAmount      :: [Amount] -- ^ Amounts of Lovelaces or tokens+  , _addressUTXOBlock       :: BlockHash -- ^ Block hash of the UTXO+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_addressUTXO", CamelToSnake]] AddressUTXO++instance ToSample AddressUTXO where+  toSamples = pure $ samples+    [ AddressUTXO+      { _addressUTXOTxHash = "39a7a284c2a0948189dc45dec670211cd4d72f7b66c5726c08d9b3df11e44d58"+      , _addressUTXOOutputIndex = 0+      , _addressUTXOAmount = [ AdaAmount 42000000 ]+      , _addressUTXOBlock = "7eb8e27d18686c7db9a18f8bbcfe34e3fed6e047afaa2d969904d15e934847e6"+      }+    , AddressUTXO+      { _addressUTXOTxHash = "4c4e67bafa15e742c13c592b65c8f74c769cd7d9af04c848099672d1ba391b49"+      , _addressUTXOOutputIndex = 0+      , _addressUTXOAmount = [ AdaAmount 729235000 ]+      , _addressUTXOBlock = "953f1b80eb7c11a7ffcd67cbd4fde66e824a451aca5a4065725e5174b81685b7"+      }+    , AddressUTXO+      { _addressUTXOTxHash = "768c63e27a1c816a83dc7b07e78af673b2400de8849ea7e7b734ae1333d100d2"+      , _addressUTXOOutputIndex = 1+      , _addressUTXOAmount =+          [ AdaAmount 42000000+          , AssetAmount+              $ Money.mkSomeDiscrete+                  "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                   unitScale+                   12+          ]+      , _addressUTXOBlock = "5c571f83fe6c784d3fbc223792627ccf0eea96773100f9aedecf8b1eda4544d7"+      }+    ]++-- | Transactions on the address+data AddressTransaction = AddressTransaction {+    _addressTransactionTxHash      :: TxHash -- ^ Hash of the transaction+  , _addressTransactionTxIndex     :: Integer -- ^ Transaction index within the block+  , _addressTransactionBlockHeight :: Integer -- ^ Block height+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_addressTransaction", CamelToSnake]] AddressTransaction++instance ToSample AddressTransaction where+  toSamples = pure $ samples+    [ AddressTransaction+      { _addressTransactionTxHash = "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b"+      , _addressTransactionTxIndex = 6+      , _addressTransactionBlockHeight = 69+      }+    , AddressTransaction+      { _addressTransactionTxHash = "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f"+      , _addressTransactionTxIndex = 9+      , _addressTransactionBlockHeight = 4547+      }+    , AddressTransaction+      { _addressTransactionTxHash = "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+      , _addressTransactionTxIndex = 0+      , _addressTransactionBlockHeight = 564654+      }+    ]
+ src/Blockfrost/Types/Cardano/Assets.hs view
@@ -0,0 +1,221 @@+-- | Responses for Cardano asset queries++module Blockfrost.Types.Cardano.Assets+  ( AssetInfo (..)+  , AssetDetails (..)+  , AssetOnChainMetadata (..)+  , AssetMetadata (..)+  , AssetHistory (..)+  , AssetAction (..)+  , AssetTransaction (..)+  , AssetAddress (..)+  ) where++import Blockfrost.Types.Shared+import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), samples, singleSample)++-- | Asset information, result of listing assets+data AssetInfo = AssetInfo+  { _assetInfoAsset    :: Text+  , _assetInfoQuantity :: Quantity+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetInfo", CamelToSnake]] AssetInfo++instance ToSample AssetInfo where+  toSamples = pure $ samples+    [ AssetInfo+        { _assetInfoAsset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+        , _assetInfoQuantity = 1+        }+    , AssetInfo+        { _assetInfoAsset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e75d"+        , _assetInfoQuantity = 100000+        }+    , AssetInfo+        { _assetInfoAsset = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+        , _assetInfoQuantity = 18605647+        }+    ]++-- | On-chain metadata stored in the minting transaction under label 721,+-- community discussion around the standard ongoing at https://github.com/cardano-foundation/CIPs/pull/85+data AssetOnChainMetadata = AssetOnChainMetadata+  { _assetOnChainMetadataName  :: Text -- ^ Name of the asset+  , _assetOnChainMetadataImage :: Text -- ^ URI of the associated asset+  -- TODO: in schema has `additionalProperties: true`+  -- so it can carry more arbitrary properties, keep as Value?+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetOnChainMetadata", CamelToSnake]] AssetOnChainMetadata++instance ToSample AssetOnChainMetadata where+  toSamples = pure $ singleSample+    AssetOnChainMetadata+      { _assetOnChainMetadataName = "My NFT token"+      , _assetOnChainMetadataImage = "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"+      }++-- | Asset metadata obtained from Cardano token registry+-- https://github.com/cardano-foundation/cardano-token-registry+data AssetMetadata = AssetMetadata+  { _assetMetadataName        :: Text -- ^ Asset name+  , _assetMetadataDescription :: Text -- ^ Asset description+  , _assetMetadataTicker      :: Maybe Text+  , _assetMetadataUrl         :: Maybe Text -- ^ Asset website+  , _assetMetadataLogo        :: Maybe Text -- ^ Base64 encoded logo of the asset+  , _assetMetadataDecimals    :: Maybe Int -- ^ Number of decimal places of the asset unit+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetMetadata", CamelToSnake]] AssetMetadata++instance ToSample AssetMetadata where+  toSamples = pure $ singleSample+    AssetMetadata+      { _assetMetadataName = "nutcoin"+      , _assetMetadataDescription = "The Nut Coin"+      , _assetMetadataTicker = pure "nutc"+      , _assetMetadataUrl = pure "https://www.stakenuts.com/"+      , _assetMetadataLogo =  pure "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"+      , _assetMetadataDecimals = pure 6+      }++-- | Details of an asset+data AssetDetails = AssetDetails+  { _assetDetailsAsset             :: Text -- ^ Hex-encoded asset full name+  , _assetDetailsPolicyId          :: PolicyId -- ^ Policy ID of the asset+  , _assetDetailsAssetName         :: Maybe Text -- ^ Hex-encoded asset name of the asset+  , _assetDetailsFingerprint       :: Text -- ^ CIP14 based user-facing fingerprint+  , _assetDetailsQuantity          :: Quantity -- ^ Current asset quantity+  , _assetDetailsInitialMintTxHash :: TxHash -- ^ ID of the initial minting transaction+  , _assetDetailsMintOrBurnCount   :: Integer -- ^ Count of mint and burn transactions+  , _assetDetailsOnchainMetadata   :: Maybe AssetOnChainMetadata+  -- ^ On-chain metadata stored in the minting transaction under label 721,+  -- community discussion around the standard ongoing at https://github.com/cardano-foundation/CIPs/pull/85+  , _assetDetailsMetadata          :: Maybe AssetMetadata+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetDetails", CamelToSnake]] AssetDetails++instance ToSample AssetDetails where+  toSamples = pure $ singleSample+    AssetDetails+      { _assetDetailsAsset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+      , _assetDetailsPolicyId = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7"+      , _assetDetailsAssetName = pure "6e7574636f696e"+      , _assetDetailsFingerprint = "asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w"+      , _assetDetailsQuantity = 12000+      , _assetDetailsInitialMintTxHash = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+      , _assetDetailsMintOrBurnCount = 1+      , _assetDetailsOnchainMetadata = pure $+          AssetOnChainMetadata+            { _assetOnChainMetadataName = "My NFT token"+            , _assetOnChainMetadataImage = "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"+            }+      , _assetDetailsMetadata = pure $+          AssetMetadata+            { _assetMetadataName = "nutcoin"+            , _assetMetadataDescription = "The Nut Coin"+            , _assetMetadataTicker = pure "nutc"+            , _assetMetadataUrl = pure "https://www.stakenuts.com/"+            , _assetMetadataLogo =  pure "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"+            , _assetMetadataDecimals = pure 6+            }+      }++-- | Action of the asset.+-- Created (`Minted`) or destroyed (`Burned`).+data AssetAction = Minted | Burned+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[ConstructorTagModifier '[ToLower]] AssetAction++instance ToSample AssetAction where+  toSamples = pure $ samples [ Minted, Burned ]++-- | History of an asset+data AssetHistory = AssetHistory+  { _assetHistoryTxHash :: TxHash -- ^ Hash of the transaction containing the asset action+  , _assetHistoryAmount :: Quantity -- ^ Asset amount of the specific action+  , _assetHistoryAction :: AssetAction -- ^ Action executed upon the asset policy+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetHistory", CamelToSnake]] AssetHistory++instance ToSample AssetHistory where+  toSamples = pure $ samples+    [ AssetHistory+        { _assetHistoryTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+        , _assetHistoryAmount = 10+        , _assetHistoryAction = Minted+        }+    , AssetHistory+        { _assetHistoryTxHash = "9c190bc1ac88b2ab0c05a82d7de8b71b67a9316377e865748a89d4426c0d3005"+        , _assetHistoryAmount = 5+        , _assetHistoryAction = Burned+        }+    , AssetHistory+        { _assetHistoryTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0"+        , _assetHistoryAmount = 5+        , _assetHistoryAction = Burned+        }+    ]++-- | Transaction of an asset+data AssetTransaction = AssetTransaction+  { _assetTransactionTxHash      :: TxHash -- ^ Hash of the transaction+  , _assetTransactionTxIndex     :: Integer -- ^ Transaction index within the block+  , _assetTransactionBlockHeight :: Integer -- ^ Block height+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetTransaction", CamelToSnake]] AssetTransaction++instance ToSample AssetTransaction where+  toSamples = pure $ samples+    [ AssetTransaction+        { _assetTransactionTxHash = "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b"+        , _assetTransactionTxIndex = 6+        , _assetTransactionBlockHeight = 69+        }+    , AssetTransaction+        { _assetTransactionTxHash = "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f"+        , _assetTransactionTxIndex = 9+        , _assetTransactionBlockHeight = 4547+        }+     , AssetTransaction+        { _assetTransactionTxHash = "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+        , _assetTransactionTxIndex = 0+        , _assetTransactionBlockHeight = 564654+        }+    ]++-- | An address containing specific asset+data AssetAddress = AssetAddress+  { _assetAddressAddress  :: Address -- ^ Address containing the specific asset+  , _assetAddressQuantity :: Quantity -- ^ Asset quantity on the specific address+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetAddress", CamelToSnake]] AssetAddress++instance ToSample AssetAddress where+  toSamples = pure $ samples+    [ AssetAddress+        { _assetAddressAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+        , _assetAddressQuantity = 1+        }+    , AssetAddress+        { _assetAddressAddress = "addr1qyhr4exrgavdcn3qhfcc9f939fzsch2re5ry9cwvcdyh4x4re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qdpvhza"+        , _assetAddressQuantity = 100000+        }+     , AssetAddress+        { _assetAddressAddress = "addr1q8zup8m9ue3p98kxlxl9q8rnyan8hw3ul282tsl9s326dfj088lvedv4zckcj24arcpasr0gua4c5gq4zw2rpcpjk2lq8cmd9l"+        , _assetAddressQuantity = 18605647+        }+    ]
+ src/Blockfrost/Types/Cardano/Blocks.hs view
@@ -0,0 +1,52 @@+-- | Information about produced blocks++module Blockfrost.Types.Cardano.Blocks+  ( Block (..)+  ) where++import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), singleSample)++import Blockfrost.Types.Shared++-- | Information about a block+data Block = Block+  { _blockTime          :: POSIXTime -- ^ Block creation time in UNIX time+  , _blockHeight        :: Maybe Integer -- ^ Block number+  , _blockHash          :: BlockHash -- ^ Hash of the block+  , _blockSlot          :: Maybe Slot -- ^ Slot number+  , _blockEpoch         :: Maybe Epoch -- ^ Epoch number+  , _blockEpochSlot     :: Maybe Integer -- ^ Slot within the epoch+  , _blockSlotLeader    :: Text -- ^ Bech32 ID of the slot leader or specific block description in case there is no slot leader+  , _blockSize          :: Integer -- ^ Block size in Bytes+  , _blockTxCount       :: Integer -- ^ Number of transactions in the block+  , _blockOutput        :: Maybe Lovelaces -- ^ Total output within the block in Lovelaces+  , _blockFees          :: Maybe Lovelaces -- ^ Total fees within the block in Lovelaces+  , _blockBlockVrf      :: Maybe Text -- ^ VRF key of the block+  , _blockPreviousBlock :: Maybe BlockHash -- ^ Hash of the previous block+  , _blockNextBlock     :: Maybe BlockHash -- ^ Hash of the next block+  , _blockConfirmations :: Integer -- ^ Number of block confirmations+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_block", CamelToSnake]] Block++instance ToSample Block where+  toSamples _ = singleSample $ Block+    { _blockTime = 1641338934+    , _blockHeight = pure 15243593+    , _blockHash = "4ea1ba291e8eef538635a53e59fddba7810d1679631cc3aed7c8e6c4091a516a"+    , _blockSlot = pure 412162133+    , _blockEpoch = pure 425+    , _blockEpochSlot = pure 12+    , _blockSlotLeader = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2qnikdy"+    , _blockSize = 3+    , _blockTxCount = 1+    , _blockOutput = pure 128314491794+    , _blockFees = pure 592661+    , _blockBlockVrf = pure "vrf_vk1wf2k6lhujezqcfe00l6zetxpnmh9n6mwhpmhm0dvfh3fxgmdnrfqkms8ty"+    , _blockPreviousBlock = pure "43ebccb3ac72c7cebd0d9b755a4b08412c9f5dcb81b8a0ad1e3c197d29d47b05"+    , _blockNextBlock = pure "8367f026cf4b03e116ff8ee5daf149b55ba5a6ec6dec04803b8dc317721d15fa"+    , _blockConfirmations = 4698+    }
+ src/Blockfrost/Types/Cardano/Epochs.hs view
@@ -0,0 +1,133 @@+-- | Responses for Cardano epoch quries++module Blockfrost.Types.Cardano.Epochs+  ( EpochInfo (..)+  , PoolStakeDistribution (..)+  , ProtocolParams (..)+  , StakeDistribution (..)+  ) where++import Blockfrost.Types.Shared+import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), singleSample)++-- | Information about an epoch+data EpochInfo = EpochInfo+  { _epochInfoEpoch          :: Epoch -- ^ Epoch number+  , _epochInfoStartTime      :: POSIXTime -- ^ Unix time of the start of the epoch+  , _epochInfoEndTime        :: POSIXTime -- ^ Unix time of the end of the epoch+  , _epochInfoFirstBlockTime :: POSIXTime -- ^ Unix time of the first block of the epoch+  , _epochInfoLastBlockTime  :: POSIXTime -- ^ Unix time of the last block of the epoch+  , _epochInfoBlockCount     :: Integer -- ^ Number of blocks within the epoch+  , _epochInfoTxCount        :: Integer -- ^ Number of transactions within the epoch+  , _epochInfoOutput         :: Lovelaces -- ^ Sum of all the transactions within the epoch in Lovelaces+  , _epochInfoFees           :: Lovelaces -- ^ Sum of all the fees within the epoch in Lovelaces+  , _epochInfoActiveStake    :: Maybe Lovelaces -- ^ Sum of all the active stakes within the epoch in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_epochInfo", CamelToSnake]] EpochInfo++instance ToSample EpochInfo where+  toSamples = pure $ singleSample+    EpochInfo+      { _epochInfoEpoch = 225+      , _epochInfoStartTime = 1603403091+      , _epochInfoEndTime = 1603835086+      , _epochInfoFirstBlockTime = 1603403092+      , _epochInfoLastBlockTime = 1603835084+      , _epochInfoBlockCount = 21298+      , _epochInfoTxCount = 17856+      , _epochInfoOutput = 7849943934049314+      , _epochInfoFees = 4203312194+      , _epochInfoActiveStake = pure 784953934049314+      }+++-- | Protocol parameters+data ProtocolParams = ProtocolParams+  { _protocolParamsEpoch                 :: Epoch -- ^ Epoch number+  , _protocolParamsMinFeeA               :: Integer -- ^ The linear factor for the minimum fee calculation for given epoch+  , _protocolParamsMinFeeB               :: Integer -- ^ The constant factor for the minimum fee calculation+  , _protocolParamsMaxBlockSize          :: Integer -- ^ Maximum block body size in Bytes+  , _protocolParamsMaxTxSize             :: Integer -- ^ Maximum transaction size+  , _protocolParamsMaxBlockHeaderSize    :: Integer -- ^ Maximum block header size+  , _protocolParamsKeyDeposit            :: Lovelaces -- ^ The amount of a key registration deposit in Lovelaces+  , _protocolParamsPoolDeposit           :: Lovelaces -- ^ The amount of a pool registration deposit in Lovelaces+  , _protocolParamsEMax                  :: Integer -- ^ Epoch bound on pool retirement+  , _protocolParamsNOpt                  :: Integer -- ^ Desired number of pools+  , _protocolParamsA0                    :: Double -- ^ Pool pledge influence+  , _protocolParamsRho                   :: Double -- ^ Monetary expansion+  , _protocolParamsTau                   :: Double -- ^ Treasury expansion+  , _protocolParamsDecentralisationParam :: Double -- ^ Percentage of blocks produced by federated nodes+-- ?? TODO: object Nullable+--  , protocolParamsExtraEntropy :: Maybe Value+  , _protocolParamsProtocolMajorVer      :: Integer -- ^ Accepted protocol major version+  , _protocolParamsProtocolMinorVer      :: Integer -- ^ Accepted protocol minor version+  , _protocolParamsMinUtxo               :: Lovelaces -- ^ Minimum UTXO value+  , _protocolParamsMinPoolCost           :: Lovelaces  -- ^ Minimum stake cost forced on the pool+  , _protocolParamsNonce                 :: Text -- ^ Epoch number only used once+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_protocolParams", CamelToSnake]] ProtocolParams++instance ToSample ProtocolParams where+  toSamples = pure $ singleSample+    ProtocolParams+      { _protocolParamsEpoch = 225+      , _protocolParamsMinFeeA = 44+      , _protocolParamsMinFeeB = 155381+      , _protocolParamsMaxBlockSize = 65536+      , _protocolParamsMaxTxSize = 16384+      , _protocolParamsMaxBlockHeaderSize = 1100+      , _protocolParamsKeyDeposit = 2000000+      , _protocolParamsPoolDeposit = 500000000+      , _protocolParamsEMax = 18+      , _protocolParamsNOpt = 150+      , _protocolParamsA0 = 0.3+      , _protocolParamsRho = 0.003+      , _protocolParamsTau = 0.2+      , _protocolParamsDecentralisationParam = 0.5+--      , _protocolParamsExtraEntropy = Nothing+      , _protocolParamsProtocolMajorVer = 2+      , _protocolParamsProtocolMinorVer = 0+      , _protocolParamsMinUtxo = 1000000+      , _protocolParamsMinPoolCost = 340000000+      , _protocolParamsNonce = "1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81"+      }++-- | Active stake distribution for an epoch+data StakeDistribution = StakeDistribution+  { _stakeDistributionStakeAddress :: Address -- ^ Stake address+  , _stakeDistributionPoolId       :: PoolId -- ^ Bech32 prefix of the pool delegated to+  , _stakeDistributionAmount       :: Lovelaces -- ^ Amount of active delegated stake in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_stakeDistribution", CamelToSnake]] StakeDistribution++instance ToSample StakeDistribution where+  toSamples = pure $ singleSample+    StakeDistribution+      { _stakeDistributionStakeAddress = "stake1u9l5q5jwgelgagzyt6nuaasefgmn8pd25c8e9qpeprq0tdcp0e3uk"+      , _stakeDistributionPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      , _stakeDistributionAmount = 4440295078+      }++-- | Stake distribution for an epoch for specific pool+data PoolStakeDistribution = PoolStakeDistribution+  { _poolStakeDistributionStakeAddress :: Address -- ^ Stake address+  , _poolStakeDistributionAmount       :: Lovelaces -- ^ Amount of active delegated stake in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolStakeDistribution", CamelToSnake]] PoolStakeDistribution++instance ToSample PoolStakeDistribution where+  toSamples = pure $ singleSample+    PoolStakeDistribution+      { _poolStakeDistributionStakeAddress = "stake1u9l5q5jwgelgagzyt6nuaasefgmn8pd25c8e9qpeprq0tdcp0e3uk"+      , _poolStakeDistributionAmount = 4440295078+      }
+ src/Blockfrost/Types/Cardano/Genesis.hs view
@@ -0,0 +1,44 @@+-- | Blockchain genesis++{-# LANGUAGE NumericUnderscores #-}++module Blockfrost.Types.Cardano.Genesis+  ( Genesis (..)+  ) where++import Deriving.Aeson+import Servant.Docs (ToSample (..), singleSample)++import Blockfrost.Types.Shared++-- | Information about blockchain genesis+data Genesis = Genesis+  { _genesisActiveSlotsCoefficient :: Double -- ^ The proportion of slots in which blocks should be issued+  , _genesisUpdateQuorum           :: Integer -- ^ Determines the quorum needed for votes on the protocol parameter updates+  , _genesisMaxLovelaceSupply      :: Lovelaces -- ^ The total number of lovelace in the system+  , _genesisNetworkMagic           :: Integer -- ^ Network identifier+  , _genesisEpochLength            :: Integer -- ^ Number of slots in an epoch+  , _genesisSystemStart            :: POSIXTime -- ^ Time of slot 0 in UNIX time+  , _genesisSlotsPerKesPeriod      :: Integer -- ^ Number of slots in an KES period+  , _genesisSlotLength             :: Integer -- ^ Duration of one slot in seconds+  , _genesisMaxKesEvolutions       :: Integer -- ^ The maximum number of time a KES key can be evolved before a pool operator must create a new operational certificate+  , _genesisSecurityParam          :: Integer -- ^ Security parameter @k@+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_genesis", CamelToSnake]] Genesis++instance ToSample Genesis where+  toSamples _ = singleSample $+    Genesis+      { _genesisActiveSlotsCoefficient = 0.05+      , _genesisUpdateQuorum = 5+      , _genesisMaxLovelaceSupply = 45_000_000_000_000_000+      , _genesisNetworkMagic = 764824073+      , _genesisEpochLength = 432_000+      , _genesisSystemStart = 1506203091+      , _genesisSlotsPerKesPeriod = 129600+      , _genesisSlotLength = 1+      , _genesisMaxKesEvolutions  = 62+      , _genesisSecurityParam  = 2160+      }
+ src/Blockfrost/Types/Cardano/Metadata.hs view
@@ -0,0 +1,83 @@+-- | Transaction metadata++module Blockfrost.Types.Cardano.Metadata+  ( TxMeta (..)+  , TxMetaJSON (..)+  , TxMetaCBOR (..)+  ) where++import Data.Aeson (Value, object, (.=))+import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), samples)++import Blockfrost.Types.Shared++-- | Transaction metadata label in use+data TxMeta = TxMeta+  { _txMetaLabel :: Text -- ^ Metadata label+  , _txMetaCip10 :: Maybe Text -- ^ CIP10 defined description+  , _txMetaCount :: Quantity -- ^ The count of metadata entries with a specific label+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_txMeta", CamelToSnake]] TxMeta++instance ToSample TxMeta where+  toSamples = pure $ samples+    [ TxMeta "1990" Nothing 1+    , TxMeta "1967" (Just "nut.link metadata oracles registry") 3+    , TxMeta "1968" (Just "nut.link metadata oracles data points") 16321+    ]++-- | Transaction metadata content in JSON+data TxMetaJSON = TxMetaJSON+  { _txMetaJSONTxHash       :: Text -- ^ Transaction hash that contains the specific metadata+  , _txMetaJSONJSONMetadata :: Maybe Value -- ^ Content of the JSON metadata+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_txMetaJSON", CamelToSnake]] TxMetaJSON++instance ToSample TxMetaJSON where+  toSamples =+    let oracleMeta val =+          object [+            "ADAUSD" .=+              [ object [ "value" .= (val :: Text)+                       , "source" .= ("ergoOracles" :: Text) ]+              ]+          ]+    in pure $ samples+    [ TxMetaJSON+        "257d75c8ddb0434e9b63e29ebb6241add2b835a307aa33aedba2effe09ed4ec8"+        (Just $ oracleMeta "0.10409800535729975")+    , TxMetaJSON+        "e865f2cc01ca7381cf98dcdc4de07a5e8674b8ea16e6a18e3ed60c186fde2b9c"+        (Just $ oracleMeta "0.15409850555139935")+    , TxMetaJSON+        "4237501da3cfdd53ade91e8911e764bd0699d88fd43b12f44a1f459b89bc91be"+        Nothing+    ]++-- | Transaction metadata content in CBOR+data TxMetaCBOR = TxMetaCBOR+  { _txMetaCBORTxHash       :: Text -- ^ Transaction hash that contains the specific metadata+  , _txMetaCBORCBORMetadata :: Maybe Text -- ^ Content of the CBOR metadata+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_txMetaCBOR", CamelToSnake]] TxMetaCBOR++instance ToSample TxMetaCBOR where+  toSamples = pure $ samples+    [ TxMetaCBOR+        "257d75c8ddb0434e9b63e29ebb6241add2b835a307aa33aedba2effe09ed4ec8"+        Nothing+    , TxMetaCBOR+        "e865f2cc01ca7381cf98dcdc4de07a5e8674b8ea16e6a18e3ed60c186fde2b9c"+        Nothing+    , TxMetaCBOR+        "4237501da3cfdd53ade91e8911e764bd0699d88fd43b12f44a1f459b89bc91be"+        (Just "\\xa100a16b436f6d62696e6174696f6e8601010101010c")+    ]
+ src/Blockfrost/Types/Cardano/Network.hs view
@@ -0,0 +1,65 @@+-- | Cardano Network reponses++{-# LANGUAGE NumericUnderscores #-}++module Blockfrost.Types.Cardano.Network+  ( Network (..)+  , NetworkStake (..)+  , NetworkSupply (..)+  ) where++import Deriving.Aeson+import Servant.Docs (ToSample (..), singleSample)++import Blockfrost.Types.Shared++-- | Lovelace supply data+data NetworkSupply = NetworkSupply+  { _supplyMax         :: Lovelaces -- ^ Maximum supply in Lovelaces+  , _supplyTotal       :: Lovelaces -- ^ Current total (max supply - reserves) supply in Lovelaces+  , _supplyCirculating :: Lovelaces -- ^ Current circulating (UTXOs + withdrawables) supply in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_supply", CamelToSnake]] NetworkSupply++netSupplySample :: NetworkSupply+netSupplySample =+  NetworkSupply+    45_000_000_000_000_000+    32_890_715_183_299_160+    32_412_601_976_210_393++instance ToSample NetworkSupply where+  toSamples = pure $ singleSample netSupplySample++-- | Live and active stake of the whole network+data NetworkStake = NetworkStake+  { _stakeLive   :: Lovelaces -- ^ Current live stake in Lovelaces+  , _stakeActive :: Lovelaces -- ^ Current active stake in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_stake", CamelToSnake]] NetworkStake++netStakeSample :: NetworkStake+netStakeSample =+  NetworkStake+    23_204_950_463_991_654+    22_210_233_523_456_321++instance ToSample NetworkStake where+  toSamples = pure $ singleSample $ netStakeSample++-- | Detailed network information+data Network = Network+  { _networkSupply :: NetworkSupply -- ^ Supply data+  , _networkStake  :: NetworkStake -- ^ Stake data+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_network", CamelToSnake]] Network++instance ToSample Network where+  toSamples = pure $ singleSample $+    Network netSupplySample netStakeSample
+ src/Blockfrost/Types/Cardano/Pools.hs view
@@ -0,0 +1,254 @@+-- | Cardano Pools reponses++module Blockfrost.Types.Cardano.Pools+  ( PoolEpoch (..)+  , PoolInfo (..)+  , PoolHistory (..)+  , PoolMetadata (..)+  , PoolRelay (..)+  , PoolDelegator (..)+  , PoolUpdate (..)+  , PoolRegistrationAction (..)+  , samplePoolRelay+  ) where++import Data.Aeson (FromJSON (..), ToJSON (..), object, pairs, withText)+import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), samples, singleSample)++import Blockfrost.Types.Shared++-- | Retirement epoch for pool+data PoolEpoch = PoolEpoch+  { _poolEpochPoolId :: PoolId -- ^ Bech32 encoded pool ID+  , _poolEpochEpoch  :: Epoch -- ^ Retirement epoch number+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolEpoch", CamelToSnake]] PoolEpoch++instance ToSample PoolEpoch where+  toSamples = pure $ samples+    [ PoolEpoch "pool19u64770wqp6s95gkajc8udheske5e6ljmpq33awxk326zjaza0q" 225+    , PoolEpoch "pool1dvla4zq98hpvacv20snndupjrqhuc79zl6gjap565nku6et5zdx" 215+    , PoolEpoch "pool1wvccajt4eugjtf3k0ja3exjqdj7t8egsujwhcw4tzj4rzsxzw5w" 231+    ]++-- | Detailed pool information+data PoolInfo = PoolInfo+  { _poolInfoPoolId         :: PoolId -- ^ Bech32 encoded pool ID+  , _poolInfoHex            :: Text -- ^ Hexadecimal pool ID.+  , _poolInfoVrfKey         :: Text -- ^ VRF key hash+  , _poolInfoBlocksMinted   :: Integer -- ^ Total minted blocks+  , _poolInfoLiveStake      :: Lovelaces+  , _poolInfoLiveSize       :: Double+  , _poolInfoLiveSaturation :: Double+  , _poolInfoActiveStake    :: Lovelaces+  , _poolInfoActiveSize     :: Double+  , _poolInfoDeclaredPledge :: Lovelaces -- ^ Stake pool certificate pledge+  , _poolInfoLivePledge     :: Lovelaces -- ^ Stake pool current pledge+  , _poolInfoMarginCost     :: Double -- ^ Margin tax cost of the stake pool+  , _poolInfoFixedCost      :: Lovelaces -- ^ Fixed tax cost of the stake pool+  , _poolInfoRewardAccount  :: Address -- ^ Bech32 reward account of the stake pool+  , _poolInfoOwners         :: [Address]+  , _poolInfoRegistration   :: [Text]+  , _poolInfoRetirement     :: [Text]+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolInfo", CamelToSnake]] PoolInfo++instance ToSample PoolInfo where+  toSamples = pure $ singleSample+    PoolInfo+      { _poolInfoPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      , _poolInfoHex = "0f292fcaa02b8b2f9b3c8f9fd8e0bb21abedb692a6d5058df3ef2735"+      , _poolInfoVrfKey = "0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233"+      , _poolInfoBlocksMinted = 69+      , _poolInfoLiveStake = 6900000000+      , _poolInfoLiveSize = 0.42+      , _poolInfoLiveSaturation = 0.93+      , _poolInfoActiveStake = 4200000000+      , _poolInfoActiveSize = 0.43+      , _poolInfoDeclaredPledge = 5000000000+      , _poolInfoLivePledge = 5000000001+      , _poolInfoMarginCost = 0.05+      , _poolInfoFixedCost = 340000000+      , _poolInfoRewardAccount = "stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f"+      , _poolInfoOwners = [ "stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v" ]+      , _poolInfoRegistration =+          [ "9f83e5484f543e05b52e99988272a31da373f3aab4c064c76db96643a355d9dc"+          , "7ce3b8c433bf401a190d58c8c483d8e3564dfd29ae8633c8b1b3e6c814403e95"+          , "3e6e1200ce92977c3fe5996bd4d7d7e192bcb7e231bc762f9f240c76766535b9"+          ]+      , _poolInfoRetirement = [ "252f622976d39e646815db75a77289cf16df4ad2b287dd8e3a889ce14c13d1a8" ]+      }++-- | History of a stake pool parameters over epochs+data PoolHistory = PoolHistory+  { _poolHistoryEpoch           :: Epoch -- ^ Epoch number+  , _poolHistoryBlocks          :: Integer -- ^ Number of blocks created by pool+  , _poolHistoryActiveStake     :: Lovelaces -- ^ Active (Snapshot of live stake 2 epochs ago) stake in Lovelaces+  , _poolHistoryActiveSize      :: Double -- ^ Pool size (percentage) of overall active stake at that epoch+  , _poolHistoryDelegatorsCount :: Integer -- ^ Number of delegators for epoch+  , _poolHistoryRewards         :: Lovelaces -- ^ Total rewards received before distribution to delegators+  , _poolHistoryFees            :: Lovelaces -- ^ Pool operator rewards+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolHistory", CamelToSnake]] PoolHistory++instance ToSample PoolHistory where+  toSamples = pure $ singleSample+    PoolHistory+      { _poolHistoryEpoch = 233+      , _poolHistoryBlocks = 22+      , _poolHistoryActiveStake = 20485965693569+      , _poolHistoryActiveSize = 1.2345+      , _poolHistoryDelegatorsCount = 115+      , _poolHistoryRewards = 206936253674159+      , _poolHistoryFees = 1290968354+      }++-- | Stake pool registration metadata+data PoolMetadata = PoolMetadata+  { _poolMetadataPoolId      :: PoolId -- ^ Bech32 pool ID+  , _poolMetadataHex         :: Text -- ^ Hexadecimal pool ID+  , _poolMetadataUrl         :: Maybe Text -- ^ URL to the stake pool metadata+  , _poolMetadataHash        :: Maybe Text -- ^ Hash of the metadata file+  , _poolMetadataTicker      :: Maybe Text -- ^ Ticker of the stake pool+  , _poolMetadataName        :: Maybe Text -- ^ Name of the stake pool+  , _poolMetadataDescription :: Maybe Text -- ^ Description of the stake pool+  , _poolMetadataHomepage    :: Maybe Text -- ^ Home page of the stake pool+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolMetadata", CamelToSnake]] PoolMetadata++-- We need this more specific+-- instance since API returns+-- empty object if there's no metadata+instance {-# OVERLAPS #-} ToJSON (Maybe PoolMetadata) where+  toJSON Nothing   = object mempty+  toJSON (Just pm) = toJSON pm+  toEncoding Nothing   = pairs mempty+  toEncoding (Just pm) = toEncoding pm+instance {-# OVERLAPS #-} FromJSON (Maybe PoolMetadata) where+  parseJSON x | x == object [] = pure Nothing+  parseJSON x = Just <$> parseJSON x++instance ToSample PoolMetadata where+  toSamples = pure $ singleSample samplePoolMetadata++samplePoolMetadata :: PoolMetadata+samplePoolMetadata =+  PoolMetadata+    { _poolMetadataPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _poolMetadataHex = "0f292fcaa02b8b2f9b3c8f9fd8e0bb21abedb692a6d5058df3ef2735"+    , _poolMetadataUrl = Just "https://stakenuts.com/mainnet.json"+    , _poolMetadataHash = Just "47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c"+    , _poolMetadataTicker = Just "NUTS"+    , _poolMetadataName = Just "Stake Nuts"+    , _poolMetadataDescription = Just "The best pool ever"+    , _poolMetadataHomepage = Just "https://stakentus.com/"+    }++-- | Relays of a stake pool+data PoolRelay = PoolRelay+  { _poolRelayIpv4   :: Maybe Text -- ^ IPv4 address of the relay+  , _poolRelayIpv6   :: Maybe Text -- ^ IPv6 address of the relay+  , _poolRelayDns    :: Maybe Text -- ^ DNS name of the relay+  , _poolRelayDnsSrv :: Maybe Text -- ^ DNS SRV entry of the relay+  , _poolRelayPort   :: Integer -- ^ Network port of the relay+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolRelay", CamelToSnake]] PoolRelay++instance ToSample PoolRelay where+  toSamples = pure $ singleSample samplePoolRelay++-- | Example of `PoolRelay`+samplePoolRelay :: PoolRelay+samplePoolRelay =+  PoolRelay+    { _poolRelayIpv4 = Just "4.4.4.4"+    , _poolRelayIpv6 = Just "https://stakenuts.com/mainnet.json"+    , _poolRelayDns = Just "relay1.stakenuts.com"+    , _poolRelayDnsSrv = Just "_relays._tcp.relays.stakenuts.com"+    , _poolRelayPort = 3001+    }++-- | Stake pool delegator+data PoolDelegator = PoolDelegator+  { _poolDelegatorAddress   :: Text -- ^ Bech32 encoded stake addresses+  , _poolDelegatorLiveStake :: Lovelaces -- ^ Currently delegated amount+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolDelegator", CamelToSnake]] PoolDelegator++instance ToSample PoolDelegator where+  toSamples = pure $ samples+    [ PoolDelegator+        { _poolDelegatorAddress = "stake1ux4vspfvwuus9uwyp5p3f0ky7a30jq5j80jxse0fr7pa56sgn8kha"+        , _poolDelegatorLiveStake = 1137959159981411+        }+    , PoolDelegator+        { _poolDelegatorAddress = "stake1uylayej7esmarzd4mk4aru37zh9yz0luj3g9fsvgpfaxulq564r5u"+        , _poolDelegatorLiveStake = 16958865648+        }+    , PoolDelegator+        { _poolDelegatorAddress = "stake1u8lr2pnrgf8f7vrs9lt79hc3sxm8s2w4rwvgpncks3axx6q93d4ck"+        , _poolDelegatorLiveStake = 18605647+        }+    ]++-- | Registration action of a pool+data PoolRegistrationAction = PoolRegistered | PoolDeregistered+  deriving stock (Show, Eq, Generic)++instance ToJSON PoolRegistrationAction where+  toJSON PoolRegistered   = toJSON ("registered" :: Text)+  toJSON PoolDeregistered = toJSON ("deregistered" :: Text)+  toEncoding PoolRegistered   = toEncoding ("registered" :: Text)+  toEncoding PoolDeregistered = toEncoding ("deregistered" :: Text)++instance FromJSON PoolRegistrationAction where+  parseJSON = withText "action" $ \case+    "registered"   -> pure PoolRegistered+    "deregistered" -> pure PoolDeregistered+    x              -> fail ("Expected registration action got " ++ show x)++instance ToSample PoolRegistrationAction where+  toSamples = pure $ samples [ PoolRegistered, PoolDeregistered ]++-- | Certificate update to the stake pool+data PoolUpdate = PoolUpdate+  { _poolUpdateTxHash    :: TxHash -- ^ Transaction ID+  , _poolUpdateCertIndex :: Integer -- ^ Certificate within the transaction+  , _poolUpdateAction    :: PoolRegistrationAction -- ^ Action in the certificate+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolUpdate", CamelToSnake]] PoolUpdate++instance ToSample PoolUpdate where+  toSamples = pure $ samples+    [ PoolUpdate+        { _poolUpdateTxHash = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+        , _poolUpdateCertIndex = 0+        , _poolUpdateAction = PoolRegistered+        }+    , PoolUpdate+        { _poolUpdateTxHash = "9c190bc1ac88b2ab0c05a82d7de8b71b67a9316377e865748a89d4426c0d3005"+        , _poolUpdateCertIndex = 0+        , _poolUpdateAction = PoolDeregistered+        }+    , PoolUpdate+        { _poolUpdateTxHash = "e14a75b0eb2625de7055f1f580d70426311b78e0d36dd695a6bdc96c7b3d80e0"+        , _poolUpdateCertIndex = 1+        , _poolUpdateAction = PoolRegistered+        }+    ]
+ src/Blockfrost/Types/Cardano/Transactions.hs view
@@ -0,0 +1,359 @@+-- | Cardano Transactions responses++module Blockfrost.Types.Cardano.Transactions+  ( Transaction (..)+  , TransactionUtxos (..)+  , UtxoInput (..)+  , UtxoOutput (..)+  , TransactionStake (..)+  , TransactionDelegation (..)+  , TransactionWithdrawal (..)+  , Pot (..)+  , TransactionMir (..)+  , TransactionPoolUpdate (..)+  , PoolUpdateMetadata (..)+  , TransactionPoolRetiring (..)+  , TransactionMetaJSON (..)+  , TransactionMetaCBOR (..)+  ) where++import Data.Aeson (Value, object, (.=))+import Data.Text (Text)+import Deriving.Aeson+import qualified Money+import Servant.Docs (ToSample (..), samples, singleSample)++import Blockfrost.Types.Cardano.Pools+import Blockfrost.Types.Shared++-- | Information about a transaction+data Transaction = Transaction+  { _transactionHash                 :: Text -- ^ Transaction hash+  , _transactionBlock                :: BlockHash -- ^ Block hash+  , _transactionBlockHeight          :: Integer -- ^ Block number+  , _transactionSlot                 :: Slot -- ^ Slot number+  , _transactionIndex                :: Integer -- ^ Transaction index within the block+  , _transactionOutputAmount         :: [Amount] -- ^ Transaction outputs+  , _transactionFees                 :: Lovelaces -- ^ Fees of the transaction in Lovelaces+  , _transactionDeposit              :: Lovelaces -- ^ Deposit within the transaction in Lovelaces+  , _transactionSize                 :: Integer -- ^ Size of the transaction in Bytes+  --_ TODO: Text Slots?+  , _transactionInvalidBefore        :: Maybe Text -- ^ Left (included) endpoint of the timelock validity intervals+  , _transactionInvalidHereafter     :: Maybe Text -- ^ Right (excluded) endpoint of the timelock validity intervals+  , _transactionUtxoCount            :: Integer -- ^ Count of UTXOs within the transaction+  , _transactionWithdrawalCount      :: Integer -- ^ Count of the withdrawals within the transaction+  , _transactionMirCertCount         :: Integer -- ^  Count of the MIR certificates within the transaction+  , _transactionDelegationCount      :: Integer -- ^ Count of the delegations within the transaction+  , _transactionStakeCertCount       :: Integer -- ^ Count of the stake keys (de)registration and delegation certificates within the transaction+  , _transactionPoolUpdateCount      :: Integer -- ^ Count of the stake pool registration and update certificates within the transaction+  , _transactionPoolRetireCount      :: Integer -- ^ Count of the stake pool retirement certificates within the transaction+  , _transactionAssetMintOrBurnCount :: Integer -- ^ Count of asset mints and burns within the transaction+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transaction", CamelToSnake]] Transaction++instance ToSample Transaction where+  toSamples = pure $ singleSample+    Transaction+      { _transactionHash = "1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477"+      , _transactionBlock = "356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940"+      , _transactionBlockHeight = 123456+      , _transactionSlot = 42000000+      , _transactionIndex = 1+      , _transactionOutputAmount = sampleAmounts+      , _transactionFees = 182485+      , _transactionDeposit = 0+      , _transactionSize = 433+      , _transactionInvalidBefore = Nothing+      , _transactionInvalidHereafter = Just "13885913"+      , _transactionUtxoCount = 4+      , _transactionWithdrawalCount = 0+      , _transactionMirCertCount =  0+      , _transactionDelegationCount = 0+      , _transactionStakeCertCount = 0+      , _transactionPoolUpdateCount = 0+      , _transactionPoolRetireCount = 0+      , _transactionAssetMintOrBurnCount = 0+      }++-- | Transaction input UTxO+data UtxoInput = UtxoInput+  { _utxoInputAddress     :: Address -- ^ Input address+  , _utxoInputAmount      :: [Amount]+  , _utxoInputTxHash      :: Text -- ^ Hash of the UTXO transaction+  , _utxoInputOutputIndex :: Integer -- ^ UTXO index in the transaction+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxoInput", CamelToSnake]] UtxoInput++instance ToSample UtxoInput where+  toSamples = pure $ singleSample utxoInSample++utxoInSample :: UtxoInput+utxoInSample =+  UtxoInput+    { _utxoInputAddress = "addr1q9ld26v2lv8wvrxxmvg90pn8n8n5k6tdst06q2s856rwmvnueldzuuqmnsye359fqrk8hwvenjnqultn7djtrlft7jnq7dy7wv"+    , _utxoInputAmount = sampleAmounts+    , _utxoInputTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dce628516157f0"+    , _utxoInputOutputIndex = 0+    }++-- | Transaction output UTxO+data UtxoOutput = UtxoOutput+  { _utxoOutputAddress :: Address -- ^ Output address+  , _utxoOutputAmount  :: [Amount]+  } deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxoOutput", CamelToSnake]] UtxoOutput++instance ToSample UtxoOutput where+  toSamples = pure $ singleSample utxoOutSample++utxoOutSample :: UtxoOutput+utxoOutSample =+  UtxoOutput+    { _utxoOutputAddress = "addr1q9ld26v2lv8wvrxxmvg90pn8n8n5k6tdst06q2s856rwmvnueldzuuqmnsye359fqrk8hwvenjnqultn7djtrlft7jnq7dy7wv"+    , _utxoOutputAmount = sampleAmounts+    }++-- | Transaction UTxOs+data TransactionUtxos = TransactionUtxos+  { _transactionUtxosHash    :: TxHash -- ^ Transaction hash+  , _transactionUtxosInputs  :: [UtxoInput]+  , _transactionUtxosOutputs :: [UtxoOutput]+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionUtxos", CamelToSnake]] TransactionUtxos++instance ToSample TransactionUtxos where+  toSamples = pure $ singleSample+    TransactionUtxos+      { _transactionUtxosHash = "1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477"+      , _transactionUtxosInputs = pure utxoInSample+      , _transactionUtxosOutputs = pure utxoOutSample+      }++sampleAmounts :: [Amount]+sampleAmounts =+  [ AdaAmount 42000000+  , AssetAmount+      $ Money.mkSomeDiscrete+          "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+          unitScale+          12+  ]++-- | Information about (de-)registration of a stake address+-- within a transaction+data TransactionStake = TransactionStake+  { _transactionStakeCertIndex    :: Integer -- ^ Index of the certificate within the transaction+  , _transactionStakeAddress      :: Address -- ^ Delegation stake address+  , _transactionStakeRegistration :: Bool -- ^ Registration boolean, false if deregistration+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionStake", CamelToSnake]] TransactionStake++instance ToSample TransactionStake where+  toSamples = pure $ singleSample+    TransactionStake+      { _transactionStakeCertIndex = 0+      , _transactionStakeAddress = "stake1u9t3a0tcwune5xrnfjg4q7cpvjlgx9lcv0cuqf5mhfjwrvcwrulda"+      , _transactionStakeRegistration = True+      }++-- | Information about delegation certificates of a specific transaction+data TransactionDelegation = TransactionDelegation+  { _transactionDelegationCertIndex   :: Integer -- ^ Index of the certificate within the transaction+  , _transactionDelegationAddress     :: Address -- ^ Delegation stake address+  , _transactionDelegationPoolId      :: PoolId -- ^ Bech32 ID of delegated stake pool+  , _transactionDelegationActiveEpoch :: Epoch -- ^ Epoch in which the delegation becomes active+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionDelegation", CamelToSnake]] TransactionDelegation++instance ToSample TransactionDelegation where+  toSamples = pure $ singleSample+    TransactionDelegation+      { _transactionDelegationCertIndex = 0+      , _transactionDelegationAddress = "stake1u9t3a0tcwune5xrnfjg4q7cpvjlgx9lcv0cuqf5mhfjwrvcwrulda"+      , _transactionDelegationPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      , _transactionDelegationActiveEpoch = 210+      }++-- | Information about withdrawals of a specific transaction+data TransactionWithdrawal = TransactionWithdrawal+  { _transactionWithdrawalAddress :: Address -- ^ Bech32 withdrawal address+  , _transactionWithdrawalAmount  :: Lovelaces -- ^ Withdrawal amount in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionWithdrawal", CamelToSnake]] TransactionWithdrawal++instance ToSample TransactionWithdrawal where+  toSamples = pure $ singleSample+    TransactionWithdrawal+      { _transactionWithdrawalAddress = "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc"+      , _transactionWithdrawalAmount = 431833601+      }++-- | Pot from which MIRs are transferred+data Pot = Reserve | Treasury+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[ConstructorTagModifier '[ToLower]] Pot++instance ToSample Pot where+  toSamples = pure $ samples [ Reserve, Treasury ]++-- | Information about Move Instantaneous Rewards (MIRs) of a specific transaction+data TransactionMir = TransactionMir+  { _transactionMirPot       :: Pot -- ^ Source of MIR funds+  , _transactionMirCertIndex :: Integer -- ^ Index of the certificate within the transaction+  , _transactionMirAddress   :: Address -- ^ Bech32 stake address+  , _transactionMirAmount    :: Lovelaces -- ^ MIR amount in Lovelaces+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionMir", CamelToSnake]] TransactionMir++instance ToSample TransactionMir where+  toSamples = pure $ singleSample+    TransactionMir+      { _transactionMirPot = Reserve+      , _transactionMirCertIndex = 0+      , _transactionMirAddress = "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc"+      , _transactionMirAmount = 431833601+      }++-- | Information about stake pool registration and update certificates+-- of a specific transaction+data TransactionPoolUpdate = TransactionPoolUpdate+  { _transactionPoolUpdateCertIndex     :: Integer -- ^ Index of the certificate within the transaction+  , _transactionPoolUpdatePoolId        :: PoolId -- ^ Bech32 encoded pool ID+  , _transactionPoolUpdateVrfKey        :: Text -- ^ VRF key hash+  , _transactionPoolUpdatePledge        :: Lovelaces -- ^ Stake pool certificate pledge in Lovelaces+  , _transactionPoolUpdateMarginCost    :: Double -- ^  Margin tax cost of the stake pool+  , _transactionPoolUpdateFixedCost     :: Lovelaces -- ^ Fixed tax cost of the stake pool in Lovelaces+  , _transactionPoolUpdateRewardAccount :: Address -- ^ Bech32 reward account of the stake pool+  , _transactionPoolUpdateOwners        :: [Address]+  , _transactionPoolUpdateMetadata      ::  Maybe PoolUpdateMetadata+  , _transactionPoolUpdateRelays        :: [PoolRelay]+  , _transactionPoolUpdateActiveEpoch   :: Epoch -- ^ Epoch that the delegation becomes active+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionPoolUpdate", CamelToSnake]] TransactionPoolUpdate++instance ToSample TransactionPoolUpdate where+  toSamples = pure $ singleSample+    TransactionPoolUpdate+      { _transactionPoolUpdateCertIndex = 0+      , _transactionPoolUpdatePoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      , _transactionPoolUpdateVrfKey = "0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233"+      , _transactionPoolUpdatePledge = 5000000000+      , _transactionPoolUpdateMarginCost = 0.05+      , _transactionPoolUpdateFixedCost = 340000000+      , _transactionPoolUpdateRewardAccount = "stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f"+      , _transactionPoolUpdateOwners = [ "stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v" ]+      , _transactionPoolUpdateMetadata = Just samplePoolUpdateMetadata+      , _transactionPoolUpdateRelays = [ samplePoolRelay ]+      , _transactionPoolUpdateActiveEpoch = 210+      }++-- | Information about stake pool retirements+-- within a specific transaction+data TransactionPoolRetiring = TransactionPoolRetiring+  { _transactionPoolRetiringCertIndex     :: Integer -- ^ Index of the certificate within the transaction+  , _transactionPoolRetiringPoolId        :: PoolId -- ^ Bech32 stake pool ID+  , _transactionPoolRetiringRetiringEpoch :: Epoch -- ^ Retiring epoch+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionPoolRetiring", CamelToSnake]] TransactionPoolRetiring++instance ToSample TransactionPoolRetiring where+  toSamples = pure $ singleSample+    TransactionPoolRetiring+      { _transactionPoolRetiringCertIndex = 0+      , _transactionPoolRetiringPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      , _transactionPoolRetiringRetiringEpoch = 216+      }++-- | Transaction metadata in JSON+data TransactionMetaJSON = TransactionMetaJSON+  { _transactionMetaJSONLabel        :: Text -- ^ Metadata label+  , _transactionMetaJSONJSONMetadata :: Maybe Value -- ^ Content of the JSON metadata+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionMetaJSON", CamelToSnake]] TransactionMetaJSON++instance ToSample TransactionMetaJSON where+  toSamples =+    let oracleMeta val =+          object [+            "ADAUSD" .=+              [ object [ "value" .= (val :: Text)+                       , "source" .= ("ergoOracles" :: Text) ]+              ]+          ]+    in pure $ samples+    [ TransactionMetaJSON+        "1967"+        (Just $ object+           [ "metadata" .= ("https://nut.link/metadata.json" :: Text)+           , "hash" .= ("6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af" :: Text)+           ])+    , TransactionMetaJSON+        "1968"+        (Just $ oracleMeta "0.15409850555139935")+    ]++-- | Transaction metadata in CBOR+data TransactionMetaCBOR = TransactionMetaCBOR+  { _transactionMetaCBORLabel        :: Text -- ^ Metadata label+  , _transactionMetaCBORCBORMetadata :: Maybe Text -- ^ Content of the CBOR metadata+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_transactionMetaCBOR", CamelToSnake]] TransactionMetaCBOR++instance ToSample TransactionMetaCBOR where+  toSamples = pure $ singleSample $+    TransactionMetaCBOR+      "1968"+      (Just "\\xa100a16b436f6d62696e6174696f6e8601010101010c")++-- | Update of a pool metadata+data PoolUpdateMetadata = PoolUpdateMetadata+  { _poolUpdateMetadataUrl         :: Maybe Text -- ^ URL to the stake pool metadata+  , _poolUpdateMetadataHash        :: Maybe Text -- ^ Hash of the metadata file+  , _poolUpdateMetadataTicker      :: Maybe Text -- ^ Ticker of the stake pool+  , _poolUpdateMetadataName        :: Maybe Text -- ^ Name of the stake pool+  , _poolUpdateMetadataDescription :: Maybe Text -- ^ Description of the stake pool+  , _poolUpdateMetadataHomepage    :: Maybe Text -- ^ Home page of the stake pool+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolUpdateMetadata", CamelToSnake]] PoolUpdateMetadata++-- Note: Similar to PoolMetadata but w/o PoolId and Hex fields++instance ToSample PoolUpdateMetadata where+  toSamples = pure $ singleSample samplePoolUpdateMetadata++samplePoolUpdateMetadata :: PoolUpdateMetadata+samplePoolUpdateMetadata =+  PoolUpdateMetadata+    { _poolUpdateMetadataUrl = Just "https://stakenuts.com/mainnet.json"+    , _poolUpdateMetadataHash = Just "47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c"+    , _poolUpdateMetadataTicker = Just "NUTS"+    , _poolUpdateMetadataName = Just "Stake Nuts"+    , _poolUpdateMetadataDescription = Just "The best pool ever"+    , _poolUpdateMetadataHomepage = Just "https://stakentus.com/"+    }
+ src/Blockfrost/Types/Common.hs view
@@ -0,0 +1,103 @@+-- | Types for common servics++module Blockfrost.Types.Common+  ( URLVersion (..)+  , Healthy (..)+  , ServerTime (..)+  , Metric (..)+  ) where++import Blockfrost.Types.Shared+import Data.Aeson+import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), samples, singleSample)+import Test.QuickCheck.Arbitrary (Arbitrary (..))+import Test.QuickCheck.Instances ()+import Test.QuickCheck.Modifiers++-- | Root endpoint reply+data URLVersion = URLVersion+  { _urlVersionUrl     :: Text+  , _urlVersionVersion :: Text+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_urlVersion", CamelToSnake]] URLVersion++instance ToSample URLVersion where+  toSamples = pure $ singleSample $+    URLVersion "http://blockfrost.io" "0.0.0"++-- | Health endpoint reply+newtype Healthy = Healthy+  { isHealthy :: Bool+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[CamelToSnake]] Healthy++instance ToSample Healthy where+  toSamples = pure $ singleSample $ Healthy False++-- | Health clock endpoint reply+newtype ServerTime = ServerTime+  { serverTime :: POSIXTime+  }+  deriving stock (Show, Eq, Generic)++instance FromJSON ServerTime where+  parseJSON = withObject "ServerTime"+    $ \v -> v .: "server_time"+    >>= \t -> ServerTime . millisecondsToPosix <$> parseJSON @Integer t++instance ToJSON ServerTime where+  toJSON (ServerTime t) =+    object [+      "server_time" .= posixToMilliseconds t+    ]++instance ToSample ServerTime where+    toSamples _ = singleSample $ ServerTime $ millisecondsToPosix 1603400958947++instance Arbitrary ServerTime where+  arbitrary = do+    Positive (n :: Integer) <- arbitrary+    pure $ ServerTime (fromInteger n / 1000)++-- | Metrics response+data Metric = Metric {+    _metricTime  :: POSIXTime+  , _metricCalls :: Integer+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_metric", CamelToSnake]] Metric++instance ToSample Metric where+    toSamples _ = samples $+      [ Metric 1612543884 42+      , Metric 1614523884 6942+      ]++-- Re-use @Metric@ for response with endpoint field+instance {-# OVERLAPS #-} ToJSON (Text, Metric) where+  toJSON (enp, m) = case toJSON m of+    (Object o) -> Object (o <> ("endpoint" .= (toJSON enp)))+    _          -> error "Absurd"++instance {-# OVERLAPS #-} FromJSON (Text, Metric) where+  parseJSON v@(Object o) = do+    enp <- o .: "endpoint"+    ticker <- parseJSON v+    return (enp, ticker)+  parseJSON _ = fail "Unexpected type for (Text, Metric)"++instance {-# OVERLAPS #-} ToSample (Text, Metric) where+  toSamples _ = samples $+    [ ("block", Metric 1612543814 182)+    , ("epoch", Metric 1612543814 42)+    , ("block", Metric 1612543812 775)+    , ("epoch", Metric 1612523884 4)+    , ("block", Metric 1612553884 89794)+    ]
+ src/Blockfrost/Types/IPFS.hs view
@@ -0,0 +1,102 @@+-- | Types for IPFS servics++module Blockfrost.Types.IPFS+  ( IPFSAdd (..)+  , IPFSPinChange (..)+  , IPFSPin (..)+  , PinState (..)+  , IPFSData (..)+  ) where++import Blockfrost.Types.Shared+import Data.Aeson+import Data.ByteString.Lazy (ByteString)+import Data.Text (Text)+import Deriving.Aeson+import Servant.API+import Servant.Docs (ToSample (..), singleSample)++-- | IPFS Add response+data IPFSAdd = IPFSAdd+  { _ipfsAddName     :: Text -- ^ Name of the file+  , _ipfsAddIpfsHash :: Text -- ^ IPFS hash of the file+  , _ipfsAddSize     :: Quantity -- ^ Size of the IPFS node+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_ipfsAdd", CamelToSnake]] IPFSAdd++instance ToSample IPFSAdd where+  toSamples = pure $ singleSample $+    IPFSAdd+      "README.md"+      "QmZbHqiCxKEVX7QfijzJTkZiSi3WEVTcvANgNAWzDYgZDr"+      125297++-- | State of the pinned object,+-- which is @Queued@ when we are retriving object.+--+-- If this is successful the state is changed to @Pinned@ or @Failed@ if not.+-- The state @Gc@ means the pinned item has been garbage collected+-- due to account being over storage quota or after it has been+-- moved to @Unpinned@ state by removing the object pin.+data PinState = Queued | Pinned | Unpinned | Failed | Gc+  deriving (Eq, Show, Ord, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[ConstructorTagModifier '[ToLower]] PinState+++-- | IPFS Pin Add response+data IPFSPinChange = IPFSPinChange+  { _ipfsPinChangeIpfsHash :: Text -- ^ IPFS hash of pinned object+  , _ipfsPinChangeState    :: PinState -- ^ State of the pin action+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_ipfsPinChange", CamelToSnake]] IPFSPinChange++instance ToSample IPFSPinChange where+  toSamples = pure $ singleSample $+    IPFSPinChange+      "QmPojRfAXYAXV92Dof7gtSgaVuxEk64xx9CKvprqu9VwA8"+      Queued++-- | IPFS Pin information+data IPFSPin = IPFSPin+  { _ipfsPinTimeCreated :: POSIXTime -- ^ Creation time of the IPFS object on our backends+  , _ipfsPinTimePinned  :: POSIXTime -- ^ Pin time of the IPFS object on our backends+  , _ipfsPinIpfsHash    :: Text -- ^ IPFS hash of the pinned object+  , _ipfsPinSize        :: Quantity -- ^ Size of the IPFS node+  , _ipfsPinState       :: PinState -- ^ State of the pinned object+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_ipfsPin", CamelToSnake]] IPFSPin++instance ToSample IPFSPin where+  toSamples = pure $ singleSample $+    IPFSPin+      { _ipfsPinTimeCreated = 1615551024+      , _ipfsPinTimePinned = 1615551024+      , _ipfsPinIpfsHash = "QmdVMnULrY95mth2XkwjxDtMHvzuzmvUPTotKE1tgqKbCx"+      , _ipfsPinSize = 1615551024+      , _ipfsPinState = Pinned+      }++newtype IPFSData = IPFSData ByteString+  deriving (Show, Eq, Generic)++instance ToSample IPFSData where+  toSamples = pure $ singleSample $ IPFSData "sample ipfs bytestring"++instance MimeRender OctetStream IPFSData where+  mimeRender _ (IPFSData cs) = cs++instance MimeUnrender OctetStream IPFSData where+  mimeUnrender _ lbs = pure $ IPFSData lbs++instance MimeRender PlainText IPFSData where+  mimeRender _ (IPFSData cs) = cs++instance MimeUnrender PlainText IPFSData where+  mimeUnrender _ lbs = pure $ IPFSData lbs
+ src/Blockfrost/Types/NutLink.hs view
@@ -0,0 +1,95 @@+-- | Types for Nut.link servics++module Blockfrost.Types.NutLink+  ( NutlinkAddress (..)+  , NutlinkAddressTicker (..)+  , NutlinkTicker (..)+  ) where++import Blockfrost.Types.Shared+import Data.Aeson+import Data.Text (Text)+import Deriving.Aeson+import Servant.Docs (ToSample (..), samples, singleSample)++-- | Specific address metadata+data NutlinkAddress = NutlinkAddress+  { _nutlinkAddressAddress      :: Address -- ^ Bech32 encoded address+  , _nutlinkAddressMetadataUrl  :: Text -- ^ URL of the specific metadata file+  , _nutlinkAddressMetadataHash :: Text -- ^ Hash of the metadata file+  , _nutlinkAddressMetadata     :: Maybe Value -- ^ The cached metadata of the metadata_url file.+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_nutlinkAddress", CamelToSnake]] NutlinkAddress++instance ToSample NutlinkAddress where+  toSamples = pure $ singleSample $+    NutlinkAddress+      { _nutlinkAddressAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+      , _nutlinkAddressMetadataUrl = "https://nut.link/metadata.json"+      , _nutlinkAddressMetadataHash = "6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af"+      , _nutlinkAddressMetadata = pure $ object []+      }++-- | Ticker for specific metadata oracle+data NutlinkAddressTicker = NutlinkAddressTicker+  { _nutlinkAddressTickerName        :: Text -- ^ Name of the ticker+  , _nutlinkAddressTickerCount       :: Integer -- ^ Number of ticker records+  , _nutlinkAddressTickerLatestBlock :: Integer -- ^ Block height of the latest record+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_nutlinkAddressTicker", CamelToSnake]] NutlinkAddressTicker++instance ToSample NutlinkAddressTicker where+  toSamples = pure $ samples $+    [ NutlinkAddressTicker+        { _nutlinkAddressTickerName = "ADAUSD"+        , _nutlinkAddressTickerCount = 1980038+        , _nutlinkAddressTickerLatestBlock = 2657092+        }+    , NutlinkAddressTicker+        { _nutlinkAddressTickerName = "ADAEUR"+        , _nutlinkAddressTickerCount = 1980038+        , _nutlinkAddressTickerLatestBlock = 2657092+        }+    , NutlinkAddressTicker+        { _nutlinkAddressTickerName = "ADABTC"+        , _nutlinkAddressTickerCount = 1980038+        , _nutlinkAddressTickerLatestBlock = 2657092+        }+    ]++-- | Specific ticker record+data NutlinkTicker = NutlinkTicker+  { _nutlinkTickerTxHash      :: TxHash -- ^ Hash of the transaction+  , _nutlinkTickerBlockHeight :: Integer -- ^ Block height of the record+  , _nutlinkTickerTxIndex     :: Integer -- ^ Transaction index within the block+  , _nutlinkTickerPayload     :: Value -- ^ Content of the ticker+  }+  deriving stock (Show, Eq, Generic)+  deriving (FromJSON, ToJSON)+  via CustomJSON '[FieldLabelModifier '[StripPrefix "_nutlinkTicker", CamelToSnake]] NutlinkTicker++instance ToSample NutlinkTicker where+  toSamples = pure $ singleSample $+    NutlinkTicker+      { _nutlinkTickerTxHash = "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+      , _nutlinkTickerBlockHeight = 2657092+      , _nutlinkTickerTxIndex = 8+      , _nutlinkTickerPayload = object []+      }++-- Re-use @NutlinkTicker@ for response with address field+instance {-# OVERLAPS #-} ToJSON (Address, NutlinkTicker) where+  toJSON (addr, nt) = case toJSON nt of+    (Object o) -> Object (o <> ("address" .= (toJSON addr)))+    _          -> error "Absurd"++instance {-# OVERLAPS #-} FromJSON (Address, NutlinkTicker) where+  parseJSON v@(Object o) = do+    addr <- o .: "address"+    ticker <- parseJSON v+    return (addr, ticker)+  parseJSON _ = fail "Unexpected type for (Address, NutlinkTicker)"
+ src/Blockfrost/Types/Shared.hs view
@@ -0,0 +1,35 @@+-- | Shared types and utilities++module Blockfrost.Types.Shared+  ( module Blockfrost.Types.Shared.Ada+  , module Blockfrost.Types.Shared.Address+  , module Blockfrost.Types.Shared.Amount+  , module Blockfrost.Types.Shared.AssetId+  , module Blockfrost.Types.Shared.BlockHash+  , module Blockfrost.Types.Shared.BlockIndex+  , module Blockfrost.Types.Shared.CBOR+  , module Blockfrost.Types.Shared.Epoch+  , module Blockfrost.Types.Shared.Opts+  , module Blockfrost.Types.Shared.POSIXMillis+  , module Blockfrost.Types.Shared.PolicyId+  , module Blockfrost.Types.Shared.PoolId+  , module Blockfrost.Types.Shared.Quantity+  , module Blockfrost.Types.Shared.Slot+  , module Blockfrost.Types.Shared.TxHash+  ) where++import Blockfrost.Types.Shared.Ada+import Blockfrost.Types.Shared.Address+import Blockfrost.Types.Shared.Amount+import Blockfrost.Types.Shared.AssetId+import Blockfrost.Types.Shared.BlockHash+import Blockfrost.Types.Shared.BlockIndex+import Blockfrost.Types.Shared.CBOR+import Blockfrost.Types.Shared.Epoch+import Blockfrost.Types.Shared.Opts+import Blockfrost.Types.Shared.POSIXMillis+import Blockfrost.Types.Shared.PolicyId+import Blockfrost.Types.Shared.PoolId+import Blockfrost.Types.Shared.Quantity+import Blockfrost.Types.Shared.Slot+import Blockfrost.Types.Shared.TxHash
+ src/Blockfrost/Types/Shared/Ada.hs view
@@ -0,0 +1,55 @@+-- | Lovelaces++{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE UndecidableInstances #-}++module Blockfrost.Types.Shared.Ada+  ( Lovelaces+  ) where++import Data.Aeson (Encoding, FromJSON (..), ToJSON (..), Value)+import Data.Proxy (Proxy (..))+import Data.Text (Text)+import GHC.TypeLits (KnownSymbol)+import qualified Money+import Servant.Docs (ToSample (..), samples)++type Lovelaces = Money.Discrete "ADA" "lovelace"++instance (KnownSymbol cur, Money.GoodScale sc) => ToSample (Money.Discrete' cur sc) where+  toSamples _ = samples $ map Money.discrete [+      592661+    , 3592661+    , 1337+    , 42+    , 0+    , 3000100+    ]++lovelaceDecimalConfig :: Money.DecimalConf+lovelaceDecimalConfig =+  Money.defaultDecimalConf+    { Money.decimalConf_digits = 0+    , Money.decimalConf_scale =+        Money.scale (Proxy @(Money.UnitScale "ADA" "lovelace"))+    }++instance ToJSON (Money.Discrete' "ADA" '(1_000_000, 1)) where+  toJSON =+      (toJSON :: Text -> Value)+    . Money.discreteToDecimal+        lovelaceDecimalConfig+        Money.Round+  toEncoding =+      (toEncoding :: Text -> Encoding)+    . Money.discreteToDecimal+        lovelaceDecimalConfig+        Money.Round++instance FromJSON (Money.Discrete' "ADA" '(1000000, 1)) where+  parseJSON v = do+     t <- parseJSON v+     maybe+        (fail "Can't parse Discrete ADA value")+        pure+        (Money.discreteFromDecimal lovelaceDecimalConfig t)
+ src/Blockfrost/Types/Shared/Address.hs view
@@ -0,0 +1,37 @@+-- | Address newtype++module Blockfrost.Types.Shared.Address+  where++import Data.Aeson (FromJSON, ToJSON)+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++newtype Address = Address Text+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++mkAddress :: Text -> Address+mkAddress = Address++unAddress :: Address -> Text+unAddress (Address a) = a++instance IsString Address where+  fromString = mkAddress . Data.Text.pack++instance ToCapture (Capture "address" Address) where+  toCapture _ = DocCapture "address" "Bech32 encoded address"++instance ToCapture (Capture "stake_address" Address) where+  toCapture _ = DocCapture "stake_address" "Bech32 stake address"++instance ToSample Address where+    toSamples = pure $ samples [+        "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+      , "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7"+      ]
+ src/Blockfrost/Types/Shared/Amount.hs view
@@ -0,0 +1,79 @@+-- | Amount sum type++{-# LANGUAGE NumericUnderscores #-}++module Blockfrost.Types.Shared.Amount+  where++import Blockfrost.Types.Shared.Ada (Lovelaces)+import Data.Aeson+  ( FromJSON (..)+  , ToJSON (..)+  , Value (Object)+  , object+  , withObject+  , (.:)+  , (.=)+  )+import GHC.Generics+import qualified Money+import Servant.Docs (ToSample (..), samples)+import qualified Text.Read++-- | Amount, which is either `AdaAmount Lovelaces` representing+-- amount of lovelaces or `AssetAmount SomeDiscrete` for asset amounts,+-- identified by concatenation of asset policy ID+-- and hex-encoded asset_name+data Amount =+    AdaAmount Lovelaces+  | AssetAmount Money.SomeDiscrete+  deriving (Eq, Show, Ord, Generic)++-- | SomeDiscrete values always use scale of 1+unitScale :: Money.Scale+unitScale = let (Just s) = Money.scaleFromRational 1 in s++instance ToJSON Money.SomeDiscrete where+  toJSON sd =+    object [ "unit" .= Money.someDiscreteCurrency sd+           , "quantity" .= show (Money.someDiscreteAmount sd)+           ]++instance FromJSON Money.SomeDiscrete where+  parseJSON = withObject "amount" $ \o -> do+    u <- o .: "unit"+    (strQuant :: String) <- o .: "quantity"+    case Text.Read.readMaybe strQuant of+      Nothing    -> fail "Unable to read quantity as Integer"+      Just quant -> pure $ Money.mkSomeDiscrete u unitScale quant++instance ToJSON Amount where+  toJSON (AdaAmount lovelaces) =+    object [ "unit" .= ("lovelace" :: String)+           , "quantity" .= toJSON lovelaces+           ]+  toJSON (AssetAmount av) = toJSON av++instance FromJSON Amount where+  parseJSON x@(Object o) = do+    (u :: String) <- o .: "unit"+    v <- o .: "quantity"+    case u of+      "lovelace" -> AdaAmount <$> parseJSON v+      _          -> AssetAmount <$> parseJSON x+  parseJSON other = fail $ "Amount expecting object, got" ++ show other++instance ToSample Amount where+  toSamples = pure $ samples+    [ AdaAmount 42000000+    , AssetAmount+        $ Money.toSomeDiscrete+          (12 :: Money.Discrete'+                    "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                    '(1,1))+    , AssetAmount+        $ Money.toSomeDiscrete+          (18605647 :: Money.Discrete'+                          "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+                          '(1,1))+    ]
+ src/Blockfrost/Types/Shared/AssetId.hs view
@@ -0,0 +1,36 @@+-- |  AssetIds++module Blockfrost.Types.Shared.AssetId+  where++import Data.Aeson (FromJSON, ToJSON)+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++-- | Concatenation of asset policy ID+-- and hex-encoded asset name+newtype AssetId = AssetId Text+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++mkAssetId :: Text -> AssetId+mkAssetId = AssetId++unAssetId :: AssetId -> Text+unAssetId (AssetId a) = a++instance IsString AssetId where+  fromString = mkAssetId . Data.Text.pack++instance ToCapture (Capture "asset" AssetId) where+  toCapture _ = DocCapture "asset" "Concatenation of the policy_id and hex-encoded asset_name"++instance ToSample AssetId where+    toSamples = pure $ samples [+        "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+      , "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+      ]
+ src/Blockfrost/Types/Shared/BlockHash.hs view
@@ -0,0 +1,50 @@+-- | Hash of the block++module Blockfrost.Types.Shared.BlockHash+  where++import Data.Aeson (FromJSON, ToJSON)+import Data.Char (isDigit)+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)+import qualified Text.Read++newtype BlockHash = BlockHash Text+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++mkBlockHash :: Text -> BlockHash+mkBlockHash = BlockHash++unBlockHash :: BlockHash -> Text+unBlockHash (BlockHash a) = a++instance IsString BlockHash where+  fromString = mkBlockHash . Data.Text.pack++instance ToCapture (Capture "block_hash" BlockHash) where+  toCapture _ = DocCapture "block_hash" "Specific block hash"++instance ToSample BlockHash where+    toSamples = pure $ samples+      [ "d0fa315687e99ccdc96b14cc2ea74a767405d64427b648c470731a9b69e4606e"+      , "38bc6efb92a830a0ed22a64f979d120d26483fd3c811f6622a8c62175f530878"+      , "f3258fcd8b975c061b4fcdcfcbb438807134d6961ec278c200151274893b6b7d"+      ]++instance ToCapture (Capture "hash_or_number" (Either Integer BlockHash)) where+  toCapture _ = DocCapture "hash_or_number" "Hash or number of the requested block."++instance {-# OVERLAPS #-} ToHttpApiData (Either Integer BlockHash) where+  toUrlPiece = either (Data.Text.pack . show) unBlockHash++instance {-# OVERLAPS #-} FromHttpApiData (Either Integer BlockHash) where+  parseUrlPiece x | Data.Text.all isDigit x =+    case Text.Read.readMaybe (Data.Text.unpack x) of+        Nothing      -> Left "Unable to read block id"+        Just blockId -> pure (Left blockId)+  parseUrlPiece x | otherwise = pure (Right (BlockHash x))
+ src/Blockfrost/Types/Shared/BlockIndex.hs view
@@ -0,0 +1,60 @@+-- | BlockIndex query parameter++module Blockfrost.Types.Shared.BlockIndex+  where++import qualified Data.Text+import GHC.Generics+import Servant.API (FromHttpApiData (..), QueryParam, ToHttpApiData (..))+import Servant.Docs+  ( DocQueryParam (..)+  , ParamKind (..)+  , ToParam (..)+  , ToSample (..)+  , samples+  )++-- | Block height (number) and optional index+data BlockIndex = BlockIndex {+    blockIndexHeight :: Integer+  , blockIndexIndex  :: Maybe Integer+  }+  deriving stock (Eq, Show, Generic)++instance ToHttpApiData BlockIndex where+  toUrlPiece bi = Data.Text.pack $+       show (blockIndexHeight bi)+    <> maybe mempty ((':':) .show) (blockIndexIndex bi)++instance FromHttpApiData BlockIndex where+  parseUrlPiece x = case Data.Text.splitOn ":" x of+    [""] -> Left "Empty block index"+    [bh] -> (`BlockIndex` Nothing) <$> parseUrlPiece bh+    [bh, idx] -> BlockIndex <$> parseUrlPiece bh <*> (Just <$> parseUrlPiece idx)+    _ -> Left "Invalid block index"++instance ToParam (QueryParam "from" BlockIndex) where+  toParam _ =+    DocQueryParam+      "blockIndex"+      []+      "The block number and optionally also index from which (inclusive)\+      \ to start search for results, concatenated using colon.\+      \ Has to be lower than or equal to `to` parameter."+      Normal++instance ToParam (QueryParam "to" BlockIndex) where+  toParam _ =+    DocQueryParam+      "blockIndex"+      []+      "The block number and optionally also index from which (inclusive)\+      \ to end the search for results, concatenated using colon.\+      \ Has to be higher than or equal to `from` parameter."+      Normal++instance ToSample BlockIndex where+    toSamples = pure $ samples [+        BlockIndex 892961 Nothing+      , BlockIndex 9999269 (Just 10)+      ]
+ src/Blockfrost/Types/Shared/CBOR.hs view
@@ -0,0 +1,27 @@+-- | CBOR Servant support and wrapper type++module Blockfrost.Types.Shared.CBOR+  where++import Data.ByteString.Lazy (ByteString)+import Servant.API (Accept (..), MimeRender (..), MimeUnrender (..))+import Servant.Docs (ToSample (..), singleSample)++data CBOR++-- | Wrapper for CBOR encoded `ByteString`s+-- used for submitting a transaction+newtype CBORString = CBORString ByteString+  deriving stock (Eq, Show)++instance Accept CBOR where+  contentType = pure "application/cbor"++instance MimeRender CBOR CBORString where+  mimeRender _ (CBORString cs) = cs++instance MimeUnrender CBOR CBORString where+  mimeUnrender _ lbs = pure $ CBORString lbs++instance ToSample CBORString where+  toSamples = pure $ singleSample $ CBORString "adef"
+ src/Blockfrost/Types/Shared/Epoch.hs view
@@ -0,0 +1,22 @@+-- | Epoch++module Blockfrost.Types.Shared.Epoch+  where++import Data.Aeson (FromJSON, ToJSON)+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++newtype Epoch = Epoch Integer+  deriving stock (Eq, Show, Generic)+  deriving newtype (Num, FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++unEpoch :: Epoch -> Integer+unEpoch (Epoch i) = i++instance ToCapture (Capture "epoch_number" Epoch) where+  toCapture _ = DocCapture "epoch_number" "Epoch for specific epoch slot."++instance ToSample Epoch where+    toSamples = pure $ samples [425, 500, 1200]
+ src/Blockfrost/Types/Shared/Opts.hs view
@@ -0,0 +1,25 @@+-- | Aeson options and helpers++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.Types.Shared.Opts+  ( ToLower+  , aesonOptions+  ) where++import Data.Aeson (Options (..), camelTo2, defaultOptions)+import Data.Char (toLower)+import Deriving.Aeson (StringModifier (..))++data ToLower++instance StringModifier ToLower where+  getStringModifier ""       = ""+  getStringModifier (c : xs) = toLower c : xs++aesonOptions :: Maybe String -> Options+aesonOptions mPrefix = defaultOptions {+   fieldLabelModifier = camelTo2 '_'  . dropIfPrefixed+ , constructorTagModifier = map toLower+ }+ where dropIfPrefixed = maybe id (drop . length) mPrefix
+ src/Blockfrost/Types/Shared/POSIXMillis.hs view
@@ -0,0 +1,48 @@+-- | POSIX Milliseconds wrapper++module Blockfrost.Types.Shared.POSIXMillis+  ( POSIXMillis+  , POSIXTime+  , millisecondsToPosix+  , posixToMilliseconds+  , seconds+  ) where++import Data.Aeson (FromJSON (..), ToJSON (..))+import Data.Time.Clock.POSIX+import GHC.Generics+import Servant.Docs (ToSample (..), singleSample)+import Test.QuickCheck.Arbitrary+import Test.QuickCheck.Instances ()+import Test.QuickCheck.Modifiers++-- | Convert `Integer` milliseconds to `POSIXTime`+millisecondsToPosix :: Integer -> POSIXTime+millisecondsToPosix n = fromInteger n / 1000++-- | Convert `POSIXTime` to `Integer` milliseconds+posixToMilliseconds :: POSIXTime -> Integer+posixToMilliseconds t = round (t * 1000)++newtype POSIXMillis = POSIXMillis {+    unPOSIXMillis :: POSIXTime+  }+  deriving stock (Show, Eq, Generic)++seconds :: POSIXTime -> POSIXMillis+seconds = POSIXMillis++instance FromJSON POSIXMillis where+  parseJSON t = POSIXMillis . millisecondsToPosix <$> parseJSON @Integer t++instance ToJSON POSIXMillis where+  toJSON (POSIXMillis t) = toJSON $ posixToMilliseconds t+  toEncoding (POSIXMillis t) = toEncoding $ posixToMilliseconds t++instance ToSample POSIXMillis where+    toSamples _ = singleSample $ POSIXMillis $ millisecondsToPosix 1603400958947++instance Arbitrary POSIXMillis where+  arbitrary = do+    Positive (n :: Integer) <- arbitrary+    pure $ POSIXMillis (fromInteger n / 1000)
+ src/Blockfrost/Types/Shared/PolicyId.hs view
@@ -0,0 +1,34 @@+-- | PolicyId++module Blockfrost.Types.Shared.PolicyId+  where++import Data.Aeson (FromJSON, ToJSON)+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++-- | Minting policy Id+newtype PolicyId = PolicyId Text+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++mkPolicyId :: Text -> PolicyId+mkPolicyId = PolicyId++unPolicyId :: PolicyId -> Text+unPolicyId (PolicyId a) = a++instance IsString PolicyId where+  fromString = mkPolicyId . Data.Text.pack++instance ToCapture (Capture "policy_id" PolicyId) where+  toCapture _ = DocCapture "policy_id" "Specific policy_id"++instance ToSample PolicyId where+    toSamples = pure $ samples [+        "476039a0949cf0b22f6a800f56780184c44533887ca6e821007840c3"+      ]
+ src/Blockfrost/Types/Shared/PoolId.hs view
@@ -0,0 +1,33 @@+-- | Pool identifier++module Blockfrost.Types.Shared.PoolId+  where++import Data.Aeson (FromJSON, ToJSON)+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++newtype PoolId = PoolId Text+  deriving stock (Eq, Show, Generic)+  deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++mkPoolId :: Text -> PoolId+mkPoolId = PoolId++unPoolId :: PoolId -> Text+unPoolId (PoolId a) = a++instance IsString PoolId where+  fromString = mkPoolId . Data.Text.pack++instance ToCapture (Capture "pool_id" PoolId) where+  toCapture _ = DocCapture "pool_id" "Specific pool_id"++instance ToSample PoolId where+    toSamples = pure $ samples [+        "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      ]
+ src/Blockfrost/Types/Shared/Quantity.hs view
@@ -0,0 +1,31 @@+-- | Quantity wrapper++module Blockfrost.Types.Shared.Quantity+  where++import Data.Aeson (FromJSON (..), ToJSON (..), withText)+import qualified Data.Text+import GHC.Generics+import Servant.API (FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (ToSample (..), samples)+import qualified Text.Read++newtype Quantity = Quantity Integer+  deriving stock (Eq, Show, Generic)+  deriving newtype (Num, Read, FromHttpApiData, ToHttpApiData)++unQuantity :: Quantity -> Integer+unQuantity (Quantity i) = i++instance ToJSON Quantity where+  toJSON = toJSON . show . unQuantity+  toEncoding = toEncoding . show . unQuantity++instance FromJSON Quantity where+  parseJSON = withText "quantity" $ \q -> do+    case Text.Read.readMaybe (Data.Text.unpack q) of+      Nothing    -> fail "Unable to read quantity as Integer"+      Just quant -> pure quant++instance ToSample Quantity where+    toSamples = pure $ samples [37040682, 412162133]
+ src/Blockfrost/Types/Shared/Slot.hs view
@@ -0,0 +1,22 @@+-- | Slot wrapper++module Blockfrost.Types.Shared.Slot+  where++import Data.Aeson (FromJSON, ToJSON)+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++newtype Slot = Slot Integer+  deriving stock (Eq, Show, Generic)+  deriving newtype (Num, FromHttpApiData, ToHttpApiData, FromJSON, ToJSON)++unSlot :: Slot -> Integer+unSlot (Slot i) = i++instance ToCapture (Capture "slot_number" Slot) where+  toCapture _ = DocCapture "slot_number" "Slot position for requested block."++instance ToSample Slot where+    toSamples = pure $ samples [37040682, 412162133]
+ src/Blockfrost/Types/Shared/TxHash.hs view
@@ -0,0 +1,37 @@+-- | Transaction Id++module Blockfrost.Types.Shared.TxHash+  ( TxHash (..)+  ) where++import Data.Aeson (FromJSON (..), ToJSON (..))+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text+import GHC.Generics+import Servant.API (Capture, FromHttpApiData (..), ToHttpApiData (..))+import Servant.Docs (DocCapture (..), ToCapture (..), ToSample (..), samples)++-- | Id (hash) of the transaction+newtype TxHash = TxHash { unTxHash :: Text }+  deriving stock (Show, Eq, Generic)+  deriving newtype (FromHttpApiData, ToHttpApiData)++instance IsString TxHash where+  fromString = TxHash . Data.Text.pack++instance ToJSON TxHash where+  toJSON = toJSON . unTxHash+  toEncoding = toEncoding . unTxHash+instance FromJSON TxHash where+  parseJSON = fmap TxHash <$> parseJSON++instance ToSample TxHash where+    toSamples _ = samples $ map TxHash+      [ "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b"+      , "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f"+      , "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+      ]++instance ToCapture (Capture "hash" TxHash) where+  toCapture _ = DocCapture "hash" "Hash of the requested transaction."
+ src/Blockfrost/Util/LensRules.hs view
@@ -0,0 +1,35 @@+-- | Rules for lens generation++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.Util.LensRules+  where++import Control.Lens+import Language.Haskell.TH (mkName, nameBase)++-- Bit of an overkill, since we only alter+-- one field name which would end up called `type`+blockfrostFieldRules :: LensRules+blockfrostFieldRules = defaultFieldRules+  & lensField %~ modNamer+  where+    modNamer namer dname fnames fname =+      map fixDefName (namer (fixTypeName dname) fnames fname)++    fixDefName (MethodName cname mname)=MethodName cname (fixName mname)+    fixDefName (TopName name)           = TopName (fixName name)++    fixTypeName = mkName . fixTypeName' . nameBase+    -- we coerce IPFS (and similar) to Ipfs so+    -- camelCase namer is happy+    -- and we don't have to rename our type or fields+    fixTypeName' "IPFSAdd"       = "IpfsAdd"+    fixTypeName' "IPFSPinChange" = "IpfsPinChange"+    fixTypeName' "IPFSPin"       = "IpfsPin"+    fixTypeName' "URLVersion"    = "UrlVersion"+    fixTypeName' x               = x++    fixName = mkName . fixName' . nameBase+    fixName' "type" = "type_"+    fixName' n      = n
+ src/Blockfrost/Util/Pagination.hs view
@@ -0,0 +1,54 @@+-- | Pagination utilities++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.Util.Pagination+  ( Paged (..)+  , Pagination+  , PaginationExpanded+  , page+  , paged+  , nextPage+  , maxPageSize+  ) where++import Data.Default.Class+import Servant.API (QueryParam, (:>))++-- | Pagination parameters+data Paged = Paged+  { countPerPage :: Int -- ^ Count of results per page+  , pageNumber   :: Int -- ^ Page number+  } deriving (Eq, Ord, Show)++-- | Maximum number of items per page+maxPageSize :: Int+maxPageSize = 100++instance Default Paged where+  def = Paged maxPageSize 1++-- | Default `Paged` at specific page number+page :: Int -> Paged+page n | n >= 1 = def { pageNumber = n }+page _ = error "Page number not in range [1..]"++-- | Construct `Paged` from page size and page number.+--+-- Throws error on invalid values.+paged :: Int -> Int -> Paged+paged size _ | size > maxPageSize = error "Page size exceeds 100"+paged _ n | n < 1 = error "Page number not in range [1..]"+paged size n = Paged size n++-- | Increment page number+nextPage :: Paged -> Paged+nextPage p = p { pageNumber = 1 + pageNumber p }++-- | Adds Pagination to an API+data Pagination++type PaginationExpanded subApi =+     QueryParam "count" Int+  :> QueryParam "page" Int+  :> subApi
+ src/Blockfrost/Util/Sorting.hs view
@@ -0,0 +1,45 @@+-- | Sorting utilities++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.Util.Sorting+  ( SortOrder (..)+  , Sorting+  , SortingExpanded+  , asc+  , desc+  ) where++import Data.Default.Class+import Servant.API (FromHttpApiData (..), QueryParam, ToHttpApiData (..), (:>))++data SortOrder = Ascending | Descending+  deriving (Eq, Show, Ord)++-- | @Ascending@ shortcut+asc :: SortOrder+asc = Ascending++-- | @Descending@ shortcut+desc :: SortOrder+desc = Descending++instance Default SortOrder where+  def = Ascending++instance ToHttpApiData SortOrder where+  toUrlPiece Ascending  = "asc"+  toUrlPiece Descending = "desc"++instance FromHttpApiData SortOrder where+  parseUrlPiece "asc"  = Right Ascending+  parseUrlPiece "desc" = Right Descending+  parseUrlPiece z      = Left ("unknown SortOrder: " <> z)++-- | Adds sorting to an API+data Sorting++-- | Adds sorting to an API+type SortingExpanded a =+  QueryParam "order" SortOrder+  :> a
+ src/Blockfrost/Util/Tag.hs view
@@ -0,0 +1,13 @@+-- | Tag for OpenAPI 3 docs++{-# OPTIONS_HADDOCK hide #-}++module Blockfrost.Util.Tag+  ( Tag+  ) where++import GHC.TypeLits (Symbol)++-- | Attaches a tag to documentation.+-- Server / client implementations remains intact.+data Tag (name :: Symbol)
+ test/APISpec.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module APISpec+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_sample :: Spec+spec_sample = do+  it "parses server time sample" $ do+    decode serverTimeSample+    `shouldBe`+    Just (ServerTime 1603400958.947)++  it "parses block sample" $ do+    eitherDecode blockSample+    `shouldBe`+    Right blockExpected++  it "parses metrics sample" $ do+    eitherDecode metricsSample+    `shouldBe`+    Right metricsExpected++  it "parses metrics endpoints sample" $ do+    eitherDecode metricsEndpointsSample+    `shouldBe`+    Right metricsEndpointsExpected++serverTimeSample = [r| { "server_time": 1603400958947 } |]++blockSample = [r|+{+    "time": 1641338934,+    "height": 15243593,+    "hash": "4ea1ba291e8eef538635a53e59fddba7810d1679631cc3aed7c8e6c4091a516a",+    "slot": 412162133,+    "epoch": 425,+    "epoch_slot": 12,+    "slot_leader": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2qnikdy",+    "size": 3,+    "tx_count": 1,+    "output": "128314491794",+    "fees": "592661",+    "block_vrf": "vrf_vk1wf2k6lhujezqcfe00l6zetxpnmh9n6mwhpmhm0dvfh3fxgmdnrfqkms8ty",+    "previous_block": "43ebccb3ac72c7cebd0d9b755a4b08412c9f5dcb81b8a0ad1e3c197d29d47b05",+    "next_block": "8367f026cf4b03e116ff8ee5daf149b55ba5a6ec6dec04803b8dc317721d15fa",+    "confirmations": 4698+}+|]++blockExpected = Block+  { _blockTime = 1641338934+  , _blockHeight = pure 15243593+  , _blockHash = "4ea1ba291e8eef538635a53e59fddba7810d1679631cc3aed7c8e6c4091a516a"+  , _blockSlot = pure 412162133+  , _blockEpoch = pure 425+  , _blockEpochSlot = pure 12+  , _blockSlotLeader = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2qnikdy"+  , _blockSize = 3+  , _blockTxCount = 1+  , _blockOutput = pure $ 128314491794+  , _blockFees = pure $ 592661+  , _blockBlockVrf = pure "vrf_vk1wf2k6lhujezqcfe00l6zetxpnmh9n6mwhpmhm0dvfh3fxgmdnrfqkms8ty"+  , _blockPreviousBlock = pure "43ebccb3ac72c7cebd0d9b755a4b08412c9f5dcb81b8a0ad1e3c197d29d47b05"+  , _blockNextBlock = pure "8367f026cf4b03e116ff8ee5daf149b55ba5a6ec6dec04803b8dc317721d15fa"+  , _blockConfirmations = 4698+  }++metricsSample = [r|+[+  {+    "time": 1612543884,+    "calls": 42+  },+  {+    "time": 1614523884,+    "calls": 6942+  }+]+|]++metricsExpected =+  [ Metric 1612543884 42+  , Metric 1614523884 6942+  ]++metricsEndpointsSample = [r|+[+  {+    "time": 1612543814,+    "calls": 182,+    "endpoint": "block"+  },+  {+    "time": 1612543814,+    "calls": 42,+    "endpoint": "epoch"+  },+  {+    "time": 1612543812,+    "calls": 775,+    "endpoint": "block"+  },+  {+    "time": 1612523884,+    "calls": 4,+    "endpoint": "epoch"+  },+  {+    "time": 1612553884,+    "calls": 89794,+    "endpoint": "block"+  }+]+|]++metricsEndpointsExpected :: [(Text, Metric)]+metricsEndpointsExpected =+  [ ("block", Metric 1612543814 182)+  , ("epoch", Metric 1612543814 42)+  , ("block", Metric 1612543812 775)+  , ("epoch", Metric 1612523884 4)+  , ("block", Metric 1612553884 89794)+  ]
+ test/AmountSpec.hs view
@@ -0,0 +1,76 @@+-- | Amount (de)serialization+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module AmountSpec+  where++import Blockfrost.Types.Shared.Amount+import Data.Aeson (decode, encode)+import Data.Default+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++spec_sample :: Spec+spec_sample = do+  it "my samples decodes to expected value" $ do+    decode mySamples `shouldBe` Just mySamplesExpected+  it "doc samples decodes to expected value" $ do+    decode docSamples `shouldBe` Just docSamplesExpected++mySamples = [r|+[+    { "unit":"lovelace"+    , "quantity":"42000000"+    }++  , { "unit":"b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+    , "quantity":"12"+    }++  , { "unit":"6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+    , "quantity":"18605647"+    }+]+|]++mySamplesExpected =+  [ AdaAmount 42000000+  , AssetAmount+      $ Money.toSomeDiscrete+        (12 :: Money.Discrete'+                  "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                  '(1,1))+  , AssetAmount+      $ Money.toSomeDiscrete+        (18605647 :: Money.Discrete'+                        "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+                        '(1,1))+  ]++docSamples = [r|+[+    {+        "unit": "lovelace",+        "quantity": "42000000"+    },+    {+        "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+        "quantity": "12"+    }+]+|]++docSamplesExpected =+  [ AdaAmount 42000000+  , AssetAmount+      $ Money.toSomeDiscrete+        (12 :: Money.Discrete'+                  "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                  '(1,1))+  ]
+ test/Cardano/Accounts.hs view
@@ -0,0 +1,266 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Accounts+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_sample :: Spec+spec_sample = do+  it "parses account sample" $ do+    eitherDecode accountSample+    `shouldBe`+    Right accountExpected++  it "parses account rewards sample" $ do+    eitherDecode accountRewardsSample+    `shouldBe`+    Right accountRewardsExpected++  it "parses account history sample" $ do+    eitherDecode accountHistorySample+    `shouldBe`+    Right accountHistoryExpected++  it "parses account delegation history sample" $ do+    eitherDecode accountDelegationHistorySample+    `shouldBe`+    Right accountDelegationHistoryExpected++  it "parses account withdrawal history sample" $ do+    eitherDecode accountWithdrawalsSample+    `shouldBe`+    Right accountWithdrawalsExpected++  it "parses account mirs sample" $ do+    eitherDecode accountMirsSample+    `shouldBe`+    Right accountMirsExpected++accountSample = [r|+{+    "stake_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7",+    "active": true,+    "active_epoch": 412,+    "controlled_amount": "619154618165",+    "rewards_sum": "319154618165",+    "withdrawals_sum": "12125369253",+    "reserves_sum": "319154618165",+    "treasury_sum": "12000000",+    "withdrawable_amount": "319154618165",+    "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+}+|]++accountExpected = AccountInfo+    { _accountInfoStakeAddress = "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7"+    , _accountInfoActive = True+    , _accountInfoActiveEpoch = 412+    , _accountInfoControlledAmount = 619154618165+    , _accountInfoRewardsSum = 319154618165+    , _accountInfoWithdrawalsSum = 12125369253+    , _accountInfoReservesSum = 319154618165+    , _accountInfoTreasurySum = 12000000+    , _accountInfoWithdrawableAmount = 319154618165+    , _accountInfoPoolId = pure "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    }++accountRewardsSample = [r|+[+    {+        "epoch": 215,+        "amount": "12695385",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    },+    {+        "epoch": 216,+        "amount": "12695385",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    },+    {+        "epoch": 216,+        "amount": "3586329",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    },+    {+        "epoch": 217,+        "amount": "0",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    },+    {+        "epoch": 218,+        "amount": "1395265",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    }+]+|]++accountRewardsExpected =+  [ AccountReward+      { _accountRewardEpoch = 215+      , _accountRewardAmount = 12695385+      , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  , AccountReward+      { _accountRewardEpoch = 216+      , _accountRewardAmount = 12695385+      , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  , AccountReward+      { _accountRewardEpoch = 216+      , _accountRewardAmount = 3586329+      , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  , AccountReward+      { _accountRewardEpoch = 217+      , _accountRewardAmount = 0+      , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  , AccountReward+      { _accountRewardEpoch = 218+      , _accountRewardAmount = 1395265+      , _accountRewardPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  ]++accountHistorySample = [r|+[+    {+        "active_epoch": 210,+        "amount": "12695385",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    },+    {+        "active_epoch": 211,+        "amount": "22695385",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    }+]+|]++accountHistoryExpected =+  [ AccountHistory+      { _accountHistoryActiveEpoch = 210+      , _accountHistoryAmount = 12695385+      , _accountHistoryPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  , AccountHistory+      { _accountHistoryActiveEpoch = 211+      , _accountHistoryAmount = 22695385+      , _accountHistoryPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  ]++accountDelegationHistorySample = [r|+[+    {+        "active_epoch": 210,+        "tx_hash": "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531",+        "amount": "12695385",+        "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    },+    {+        "active_epoch": 242,+        "tx_hash": "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0",+        "amount": "12691385",+        "pool_id": "pool1kchver88u3kygsak8wgll7htr8uxn5v35lfrsyy842nkscrzyvj"+    }+]+|]++accountDelegationHistoryExpected =+  [ AccountDelegation+      { _accountDelegationActiveEpoch = 210+      , _accountDelegationTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+      , _accountDelegationAmount = 12695385+      , _accountDelegationPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+      }+  , AccountDelegation+      { _accountDelegationActiveEpoch = 242+      , _accountDelegationTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0"+      , _accountDelegationAmount = 12691385+      , _accountDelegationPoolId = "pool1kchver88u3kygsak8wgll7htr8uxn5v35lfrsyy842nkscrzyvj"+      }+  ]++accountRegistrationSample = [r|+[+    {+        "tx_hash": "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531",+        "action": "registered"+    },+    {+        "tx_hash": "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0",+        "action": "deregistered"+    }+]+|]++accountRegistrationExpected =+  [ AccountRegistration+      { _accountRegistrationAction = Registered+      , _accountRegistrationTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+      }+  , AccountRegistration+      { _accountRegistrationAction = Deregistered+      , _accountRegistrationTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0"+      }+  ]++accountWithdrawalsSample = [r|+[+    {+        "tx_hash": "48a9625c841eea0dd2bb6cf551eabe6523b7290c9ce34be74eedef2dd8f7ecc5",+        "amount": "454541212442"+    },+    {+        "tx_hash": "4230b0cbccf6f449f0847d8ad1d634a7a49df60d8c142bb8cc2dbc8ca03d9e34",+        "amount": "97846969"+    }+]+|]++accountWithdrawalsExpected =+  [ AccountWithdrawal+      { _accountWithdrawalAmount = 454541212442+      , _accountWithdrawalTxHash = "48a9625c841eea0dd2bb6cf551eabe6523b7290c9ce34be74eedef2dd8f7ecc5"+      }+  , AccountWithdrawal+      { _accountWithdrawalAmount = 97846969+      , _accountWithdrawalTxHash = "4230b0cbccf6f449f0847d8ad1d634a7a49df60d8c142bb8cc2dbc8ca03d9e34"+      }+  ]++accountMirsSample = [r|+[+    {+        "tx_hash": "69705bba1d687a816ff5a04ec0c358a1f1ef075ab7f9c6cc2763e792581cec6d",+        "amount": "2193707473"+    },+    {+        "tx_hash": "baaa77b63d4d7d2bb3ab02c9b85978c2092c336dede7f59e31ad65452d510c13",+        "amount": "14520198574"+    }+]+|]++accountMirsExpected =+  [ AccountMir+      { _accountMirAmount = 2193707473+      , _accountMirTxHash = "69705bba1d687a816ff5a04ec0c358a1f1ef075ab7f9c6cc2763e792581cec6d"+      }+  , AccountMir+      { _accountMirAmount = 14520198574+      , _accountMirTxHash = "baaa77b63d4d7d2bb3ab02c9b85978c2092c336dede7f59e31ad65452d510c13"+      }+  ]
+ test/Cardano/Addresses.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Addresses+  where++import Data.Aeson (decode, eitherDecode, encode, object, (.=))+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_sample :: Spec+spec_sample = do+  it "parses address info sample" $ do+    eitherDecode addressInfoSample+    `shouldBe`+    Right addressInfoExpected++  it "parses address info sample" $ do+    eitherDecode addressInfoSample+    `shouldBe`+    Right addressInfoExpected++  it "parses address details sample" $ do+    eitherDecode addressDetailsSample+    `shouldBe`+    Right addressDetailsExpected++  it "parses address utxos sample" $ do+    eitherDecode addressUTXOsSample+    `shouldBe`+    Right addressUTXOsExpected++  it "parses address transactions sample" $ do+    eitherDecode addressTransactionsSample+    `shouldBe`+    Right addressTransactionsExpected++addressInfoSample = [r|+{+  "address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",+  "amount": [+    {+      "unit": "lovelace",+      "quantity": "42000000"+    },+    {+      "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+      "quantity": "12"+    }+  ],+  "stake_address": "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7",+  "type": "shelley"+}+|]++addressInfoExpected =+  AddressInfo+    { _addressInfoAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+    , _addressInfoAmount =+      [ AdaAmount 42000000+      , AssetAmount+          $ Money.mkSomeDiscrete+              "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+              unitScale+              12+      ]+    , _addressInfoStakeAddress = pure "stake1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7"+    , _addressInfoType = Shelley+    }++addressDetailsSample = [r|+{+  "address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",+  "received_sum": [+    {+      "unit": "lovelace",+      "quantity": "42000000"+    },+    {+      "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+      "quantity": "12"+    }+  ],+  "sent_sum": [+    {+      "unit": "lovelace",+      "quantity": "42000000"+    },+    {+      "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+      "quantity": "12"+    }+  ],+  "tx_count": 12+}+|]++addressDetailsExpected =+    AddressDetails+    { _addressDetailsAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+    , _addressDetailsReceivedSum = amounts+    , _addressDetailsSentSum = amounts+    , _addressDetailsTxCount = 12+    }+    where amounts =+            [ AdaAmount 42000000+            , AssetAmount+                $ Money.mkSomeDiscrete+                        "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                        unitScale+                        12+            ]+++addressUTXOsSample = [r|+[+    {+        "tx_hash": "39a7a284c2a0948189dc45dec670211cd4d72f7b66c5726c08d9b3df11e44d58",+        "output_index": 0,+        "amount":+        [+            {+                "unit": "lovelace",+                "quantity": "42000000"+            }+        ],+        "block": "7eb8e27d18686c7db9a18f8bbcfe34e3fed6e047afaa2d969904d15e934847e6"+    },+    {+        "tx_hash": "4c4e67bafa15e742c13c592b65c8f74c769cd7d9af04c848099672d1ba391b49",+        "output_index": 0,+        "amount":+        [+            {+                "unit": "lovelace",+                "quantity": "729235000"+            }+        ],+        "block": "953f1b80eb7c11a7ffcd67cbd4fde66e824a451aca5a4065725e5174b81685b7"+    },+    {+        "tx_hash": "768c63e27a1c816a83dc7b07e78af673b2400de8849ea7e7b734ae1333d100d2",+        "output_index": 1,+        "amount":+        [+            {+                "unit": "lovelace",+                "quantity": "42000000"+            },+            {+                "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+                "quantity": "12"+            }+        ],+        "block": "5c571f83fe6c784d3fbc223792627ccf0eea96773100f9aedecf8b1eda4544d7"+    }+]+|]++addressUTXOsExpected =+  [ AddressUTXO+    { _addressUTXOTxHash = "39a7a284c2a0948189dc45dec670211cd4d72f7b66c5726c08d9b3df11e44d58"+    , _addressUTXOOutputIndex = 0+    , _addressUTXOAmount = [ AdaAmount 42000000 ]+    , _addressUTXOBlock = "7eb8e27d18686c7db9a18f8bbcfe34e3fed6e047afaa2d969904d15e934847e6"+    }+  , AddressUTXO+    { _addressUTXOTxHash = "4c4e67bafa15e742c13c592b65c8f74c769cd7d9af04c848099672d1ba391b49"+    , _addressUTXOOutputIndex = 0+    , _addressUTXOAmount = [ AdaAmount 729235000 ]+    , _addressUTXOBlock = "953f1b80eb7c11a7ffcd67cbd4fde66e824a451aca5a4065725e5174b81685b7"+    }+  , AddressUTXO+    { _addressUTXOTxHash = "768c63e27a1c816a83dc7b07e78af673b2400de8849ea7e7b734ae1333d100d2"+    , _addressUTXOOutputIndex = 1+    , _addressUTXOAmount =+        [ AdaAmount 42000000+        , AssetAmount+            $ Money.mkSomeDiscrete+                "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+                 unitScale+                 12+        ]+    , _addressUTXOBlock = "5c571f83fe6c784d3fbc223792627ccf0eea96773100f9aedecf8b1eda4544d7"+    }+  ]++addressTransactionsSample = [r|+[+    {+        "tx_hash": "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b",+        "tx_index": 6,+        "block_height": 69+    },+    {+        "tx_hash": "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f",+        "tx_index": 9,+        "block_height": 4547+    },+    {+        "tx_hash": "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b",+        "tx_index": 0,+        "block_height": 564654+    }+]+|]++addressTransactionsExpected =+  [ AddressTransaction+    { _addressTransactionTxHash = "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b"+    , _addressTransactionTxIndex = 6+    , _addressTransactionBlockHeight = 69+    }+  , AddressTransaction+    { _addressTransactionTxHash = "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f"+    , _addressTransactionTxIndex = 9+    , _addressTransactionBlockHeight = 4547+    }+  , AddressTransaction+    { _addressTransactionTxHash = "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+    , _addressTransactionTxIndex = 0+    , _addressTransactionBlockHeight = 564654+    }+  ]
+ test/Cardano/Assets.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Assets+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_assets :: Spec+spec_assets = do+  it "parses assets sample" $ do+    eitherDecode assetInfoSample+    `shouldBe`+    Right assetInfoExpected++  it "parses assets details sample" $ do+    eitherDecode assetDetailsSample+    `shouldBe`+    Right assetDetailsExpected++  it "parses assets history sample" $ do+    eitherDecode assetHistorySample+    `shouldBe`+    Right assetHistoryExpected++  it "parses assets transaction sample" $ do+    eitherDecode assetTransactionsSample+      `shouldBe`+      Right assetTransactionsExpected++  it "parses assets adresses sample" $ do+    eitherDecode assetAddressesSample+    `shouldBe`+    Right assetAddressesExpected++assetInfoSample = [r|+[+  {+    "asset": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+    "quantity": "1"+  },+  {+    "asset": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e75d",+    "quantity": "100000"+  },+  {+    "asset": "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad",+    "quantity": "18605647"+  }+]+|]++assetInfoExpected =+  [ AssetInfo+      { _assetInfoAsset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+      , _assetInfoQuantity = 1+      }+  , AssetInfo+      { _assetInfoAsset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e75d"+      , _assetInfoQuantity = 100000+      }+  , AssetInfo+      { _assetInfoAsset = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+      , _assetInfoQuantity = 18605647+      }+  ]+++assetDetailsSample = [r|+{+  "asset": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+  "policy_id": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7",+  "asset_name": "6e7574636f696e",+  "fingerprint": "asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w",+  "quantity": "12000",+  "initial_mint_tx_hash": "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad",+  "mint_or_burn_count": 1,+  "onchain_metadata": {+    "name": "My NFT token",+    "image": "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"+  },+  "metadata": {+    "name": "nutcoin",+    "description": "The Nut Coin",+    "ticker": "nutc",+    "url": "https://www.stakenuts.com/",+    "logo": "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",+    "decimals": 6+  }+}+|]++assetDetailsExpected =+  AssetDetails+    { _assetDetailsAsset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+    , _assetDetailsPolicyId = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7"+    , _assetDetailsAssetName = pure "6e7574636f696e"+    , _assetDetailsFingerprint = "asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w"+    , _assetDetailsQuantity = 12000+    , _assetDetailsInitialMintTxHash = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+    , _assetDetailsMintOrBurnCount = 1+    , _assetDetailsOnchainMetadata = pure $+        AssetOnChainMetadata+          { _assetOnChainMetadataName = "My NFT token"+          , _assetOnChainMetadataImage = "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"+          }+    , _assetDetailsMetadata = pure $+        AssetMetadata+          { _assetMetadataName = "nutcoin"+          , _assetMetadataDescription = "The Nut Coin"+          , _assetMetadataTicker = pure "nutc"+          , _assetMetadataUrl = pure "https://www.stakenuts.com/"+          , _assetMetadataLogo =  pure "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"+          , _assetMetadataDecimals = pure 6+          }+    }++assetHistorySample = [r|+[+  {+    "tx_hash": "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531",+    "amount": "10",+    "action": "minted"+  },+  {+    "tx_hash": "9c190bc1ac88b2ab0c05a82d7de8b71b67a9316377e865748a89d4426c0d3005",+    "amount": "5",+    "action": "burned"+  },+  {+    "tx_hash": "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0",+    "amount": "5",+    "action": "burned"+  }+]+|]++assetHistoryExpected =+  [ AssetHistory+      { _assetHistoryTxHash = "2dd15e0ef6e6a17841cb9541c27724072ce4d4b79b91e58432fbaa32d9572531"+      , _assetHistoryAmount = 10+      , _assetHistoryAction = Minted+      }+  , AssetHistory+      { _assetHistoryTxHash = "9c190bc1ac88b2ab0c05a82d7de8b71b67a9316377e865748a89d4426c0d3005"+      , _assetHistoryAmount = 5+      , _assetHistoryAction = Burned+      }+  , AssetHistory+      { _assetHistoryTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dde628516157f0"+      , _assetHistoryAmount = 5+      , _assetHistoryAction = Burned+      }+  ]++assetTransactionsSample = [r|+[+  {+    "tx_hash": "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b",+    "tx_index": 6,+    "block_height": 69+  },+  {+    "tx_hash": "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f",+    "tx_index": 9,+    "block_height": 4547+  },+  {+    "tx_hash": "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b",+    "tx_index": 0,+    "block_height": 564654+  }+]+|]++assetTransactionsExpected =+  [ AssetTransaction+      { _assetTransactionTxHash = "8788591983aa73981fc92d6cddbbe643959f5a784e84b8bee0db15823f575a5b"+      , _assetTransactionTxIndex = 6+      , _assetTransactionBlockHeight = 69+      }+  , AssetTransaction+      { _assetTransactionTxHash = "52e748c4dec58b687b90b0b40d383b9fe1f24c1a833b7395cdf07dd67859f46f"+      , _assetTransactionTxIndex = 9+      , _assetTransactionBlockHeight = 4547+      }+   , AssetTransaction+      { _assetTransactionTxHash = "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+      , _assetTransactionTxIndex = 0+      , _assetTransactionBlockHeight = 564654+      }+  ]++assetAddressesSample = [r|+[+  {+    "address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",+    "quantity": "1"+  },+  {+    "address": "addr1qyhr4exrgavdcn3qhfcc9f939fzsch2re5ry9cwvcdyh4x4re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qdpvhza",+    "quantity": "100000"+  },+  {+    "address": "addr1q8zup8m9ue3p98kxlxl9q8rnyan8hw3ul282tsl9s326dfj088lvedv4zckcj24arcpasr0gua4c5gq4zw2rpcpjk2lq8cmd9l",+    "quantity": "18605647"+  }+]+|]++assetAddressesExpected =+  [ AssetAddress+      { _assetAddressAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+      , _assetAddressQuantity = 1+      }+  , AssetAddress+      { _assetAddressAddress = "addr1qyhr4exrgavdcn3qhfcc9f939fzsch2re5ry9cwvcdyh4x4re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qdpvhza"+      , _assetAddressQuantity = 100000+      }+   , AssetAddress+      { _assetAddressAddress = "addr1q8zup8m9ue3p98kxlxl9q8rnyan8hw3ul282tsl9s326dfj088lvedv4zckcj24arcpasr0gua4c5gq4zw2rpcpjk2lq8cmd9l"+      , _assetAddressQuantity = 18605647+      }+  ]
+ test/Cardano/Epochs.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Epochs+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_epochs :: Spec+spec_epochs = do+  it "parses epoch info sample" $ do+    eitherDecode epochInfoSample+    `shouldBe`+    Right epochInfoExpected++  it "parses protocol params sample" $ do+    eitherDecode protocolParamsSample+    `shouldBe`+    Right protocolParamsExpected++  it "parses stake distribution sample" $ do+    eitherDecode stakeDistributionSample+    `shouldBe`+    Right stakeDistributionExpected++  it "parses pool stake distribution sample" $ do+    eitherDecode poolStakeDistributionSample+    `shouldBe`+    Right poolStakeDistributionExpected++epochInfoSample = [r|+{+    "epoch": 225,+    "start_time": 1603403091,+    "end_time": 1603835086,+    "first_block_time": 1603403092,+    "last_block_time": 1603835084,+    "block_count": 21298,+    "tx_count": 17856,+    "output": "7849943934049314",+    "fees": "4203312194",+    "active_stake": "784953934049314"+}+|]++epochInfoExpected =+  EpochInfo+    { _epochInfoEpoch = 225+    , _epochInfoStartTime = 1603403091+    , _epochInfoEndTime = 1603835086+    , _epochInfoFirstBlockTime = 1603403092+    , _epochInfoLastBlockTime = 1603835084+    , _epochInfoBlockCount = 21298+    , _epochInfoTxCount = 17856+    , _epochInfoOutput = 7849943934049314+    , _epochInfoFees = 4203312194+    , _epochInfoActiveStake = pure 784953934049314+    }++protocolParamsSample = [r|+{+    "epoch": 225,+    "min_fee_a": 44,+    "min_fee_b": 155381,+    "max_block_size": 65536,+    "max_tx_size": 16384,+    "max_block_header_size": 1100,+    "key_deposit": "2000000",+    "pool_deposit": "500000000",+    "e_max": 18,+    "n_opt": 150,+    "a0": 0.3,+    "rho": 0.003,+    "tau": 0.2,+    "decentralisation_param": 0.5,+    "extra_entropy": null,+    "protocol_major_ver": 2,+    "protocol_minor_ver": 0,+    "min_utxo": "1000000",+    "min_pool_cost": "340000000",+    "nonce": "1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81"+}+|]++protocolParamsExpected =+  ProtocolParams+    { _protocolParamsEpoch = 225+    , _protocolParamsMinFeeA = 44+    , _protocolParamsMinFeeB = 155381+    , _protocolParamsMaxBlockSize = 65536+    , _protocolParamsMaxTxSize = 16384+    , _protocolParamsMaxBlockHeaderSize = 1100+    , _protocolParamsKeyDeposit = 2000000+    , _protocolParamsPoolDeposit = 500000000+    , _protocolParamsEMax = 18+    , _protocolParamsNOpt = 150+    , _protocolParamsA0 = 0.3+    , _protocolParamsRho = 0.003+    , _protocolParamsTau = 0.2+    , _protocolParamsDecentralisationParam = 0.5+--    , _protocolParamsExtraEntropy = Nothing+    , _protocolParamsProtocolMajorVer = 2+    , _protocolParamsProtocolMinorVer = 0+    , _protocolParamsMinUtxo = 1000000+    , _protocolParamsMinPoolCost = 340000000+    , _protocolParamsNonce = "1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81"+    }++stakeDistributionSample = [r|+{+  "stake_address": "stake1u9l5q5jwgelgagzyt6nuaasefgmn8pd25c8e9qpeprq0tdcp0e3uk",+  "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",+  "amount": "4440295078"+}+|]++stakeDistributionExpected =+  StakeDistribution+    { _stakeDistributionStakeAddress = "stake1u9l5q5jwgelgagzyt6nuaasefgmn8pd25c8e9qpeprq0tdcp0e3uk"+    , _stakeDistributionPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _stakeDistributionAmount = 4440295078+    }++poolStakeDistributionSample = [r|+{+  "stake_address": "stake1u9l5q5jwgelgagzyt6nuaasefgmn8pd25c8e9qpeprq0tdcp0e3uk",+  "amount": "4440295078"+}+|]++poolStakeDistributionExpected =+  PoolStakeDistribution+    { _poolStakeDistributionStakeAddress = "stake1u9l5q5jwgelgagzyt6nuaasefgmn8pd25c8e9qpeprq0tdcp0e3uk"+    , _poolStakeDistributionAmount = 4440295078+    }++
+ test/Cardano/Ledger.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Ledger+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_ledger :: Spec+spec_ledger = do+  it "parses ledger genesis sample" $ do+    eitherDecode ledgerGenesisSample+    `shouldBe`+    Right ledgerGenesisExpected++ledgerGenesisSample = [r|+{+    "active_slots_coefficient": 0.05,+    "update_quorum": 5,+    "max_lovelace_supply": "45000000000000000",+    "network_magic": 764824073,+    "epoch_length": 432000,+    "system_start": 1506203091,+    "slots_per_kes_period": 129600,+    "slot_length": 1,+    "max_kes_evolutions": 62,+    "security_param": 2160+}+|]++ledgerGenesisExpected =+  Genesis+    { _genesisActiveSlotsCoefficient = 0.05+    , _genesisUpdateQuorum = 5+    , _genesisMaxLovelaceSupply = 45_000_000_000_000_000+    , _genesisNetworkMagic = 764824073+    , _genesisEpochLength = 432_000+    , _genesisSystemStart = 1506203091+    , _genesisSlotsPerKesPeriod = 129600+    , _genesisSlotLength = 1+    , _genesisMaxKesEvolutions  = 62+    , _genesisSecurityParam  = 2160+    }++
+ test/Cardano/Metadata.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Metadata+  where++import Data.Aeson (decode, eitherDecode, encode, object, (.=))+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_metadata :: Spec+spec_metadata = do+  it "parses tx meta sample" $ do+    eitherDecode txMetaSample+    `shouldBe`+    Right txMetaExpected++  it "parses tx meta JSON sample" $ do+    eitherDecode txMetaJSONSample+    `shouldBe`+    Right txMetaJSONExpected++  it "parses tx meta CBOR sample" $ do+    eitherDecode txMetaCBORSample+    `shouldBe`+    Right txMetaCBORExpected++txMetaSample = [r|+[+  {+    "label": "1990",+    "cip10": null,+    "count": "1"+  },+  {+    "label": "1967",+    "cip10": "nut.link metadata oracles registry",+    "count": "3"+  },+  {+    "label": "1968",+    "cip10": "nut.link metadata oracles data points",+    "count": "16321"+  }+]+|]++txMetaExpected =+  [ TxMeta "1990" Nothing 1+  , TxMeta "1967" (Just "nut.link metadata oracles registry") 3+  , TxMeta "1968" (Just "nut.link metadata oracles data points") 16321+  ]++txMetaJSONSample = [r|+[+  {+    "tx_hash": "257d75c8ddb0434e9b63e29ebb6241add2b835a307aa33aedba2effe09ed4ec8",+    "json_metadata": {+      "ADAUSD": [+        {+          "value": "0.10409800535729975",+          "source": "ergoOracles"+        }+      ]+    }+  },+  {+    "tx_hash": "e865f2cc01ca7381cf98dcdc4de07a5e8674b8ea16e6a18e3ed60c186fde2b9c",+    "json_metadata": {+      "ADAUSD": [+        {+          "value": "0.15409850555139935",+          "source": "ergoOracles"+        }+      ]+    }+  },+  {+    "tx_hash": "4237501da3cfdd53ade91e8911e764bd0699d88fd43b12f44a1f459b89bc91be",+    "json_metadata": null+  }+]+|]++txMetaJSONExpected =+  let oracleMeta val =+        object [+          "ADAUSD" .=+            [ object [+                        "value" .= (val :: Text)+                      , "source" .= ("ergoOracles" :: Text)+                      ]+            ]+        ]+  in+  [ TxMetaJSON+      "257d75c8ddb0434e9b63e29ebb6241add2b835a307aa33aedba2effe09ed4ec8"+      (Just $ oracleMeta "0.10409800535729975")+  , TxMetaJSON+      "e865f2cc01ca7381cf98dcdc4de07a5e8674b8ea16e6a18e3ed60c186fde2b9c"+      (Just $ oracleMeta "0.15409850555139935")+  , TxMetaJSON+      "4237501da3cfdd53ade91e8911e764bd0699d88fd43b12f44a1f459b89bc91be"+      Nothing+  ]++txMetaCBORSample = [r|+[+  {+    "tx_hash": "257d75c8ddb0434e9b63e29ebb6241add2b835a307aa33aedba2effe09ed4ec8",+    "cbor_metadata": null+  },+  {+    "tx_hash": "e865f2cc01ca7381cf98dcdc4de07a5e8674b8ea16e6a18e3ed60c186fde2b9c",+    "cbor_metadata": null+  },+  {+    "tx_hash": "4237501da3cfdd53ade91e8911e764bd0699d88fd43b12f44a1f459b89bc91be",+    "cbor_metadata": "\\xa100a16b436f6d62696e6174696f6e8601010101010c"+  }+]+|]++txMetaCBORExpected =+  [ TxMetaCBOR+      "257d75c8ddb0434e9b63e29ebb6241add2b835a307aa33aedba2effe09ed4ec8"+      Nothing+  , TxMetaCBOR+      "e865f2cc01ca7381cf98dcdc4de07a5e8674b8ea16e6a18e3ed60c186fde2b9c"+      Nothing+  , TxMetaCBOR+      "4237501da3cfdd53ade91e8911e764bd0699d88fd43b12f44a1f459b89bc91be"+      (Just "\\xa100a16b436f6d62696e6174696f6e8601010101010c")+  ]++
+ test/Cardano/Network.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Network+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_network :: Spec+spec_network = do+  it "parses network sample" $ do+    eitherDecode networkSample+    `shouldBe`+    Right networkExpected++networkSample = [r|+{+  "supply": {+    "max": "45000000000000000",+    "total": "32890715183299160",+    "circulating": "32412601976210393"+  },+  "stake": {+    "live": "23204950463991654",+    "active": "22210233523456321"+  }+}+|]++networkExpected =+  Network+    (NetworkSupply+      45_000_000_000_000_000+      32_890_715_183_299_160+      32_412_601_976_210_393)+    (NetworkStake+      23_204_950_463_991_654+      22_210_233_523_456_321)
+ test/Cardano/Pools.hs view
@@ -0,0 +1,277 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Pools+  where++import Data.Aeson (decode, eitherDecode, encode)+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_pools :: Spec+spec_pools = do+  it "parses pool epoch sample" $ do+    eitherDecode poolEpochSample+    `shouldBe`+    Right poolEpochExpected++  it "parses pool info sample" $ do+    eitherDecode poolInfoSample+    `shouldBe`+    Right poolInfoExpected++  it "parses pool history sample" $ do+    eitherDecode poolHistorySample+    `shouldBe`+    Right poolHistoryExpected++  it "parses pool metadata sample" $ do+    eitherDecode poolMetadataSample+    `shouldBe`+    Right poolMetadataExpected++  it "parses empty object pool metadata sample" $ do+    eitherDecode "{}"+    `shouldBe`+    Right (Nothing :: Maybe PoolMetadata)++  it "parses pool relay sample" $ do+    eitherDecode poolRelaySample+    `shouldBe`+    Right poolRelayExpected++  it "parses pool delegators sample" $ do+    eitherDecode poolDelegatorsSample+    `shouldBe`+    Right poolDelegatorsExpected++  it "parses pool updates sample" $ do+    eitherDecode poolUpdatesSample+    `shouldBe`+    Right poolUpdatesExpected++poolEpochSample = [r|+[+  {+    "pool_id": "pool19u64770wqp6s95gkajc8udheske5e6ljmpq33awxk326zjaza0q",+    "epoch": 225+  },+  {+    "pool_id": "pool1dvla4zq98hpvacv20snndupjrqhuc79zl6gjap565nku6et5zdx",+    "epoch": 215+  },+  {+    "pool_id": "pool1wvccajt4eugjtf3k0ja3exjqdj7t8egsujwhcw4tzj4rzsxzw5w",+    "epoch": 231+  }+]+|]++poolEpochExpected =+  [ PoolEpoch "pool19u64770wqp6s95gkajc8udheske5e6ljmpq33awxk326zjaza0q" 225+  , PoolEpoch "pool1dvla4zq98hpvacv20snndupjrqhuc79zl6gjap565nku6et5zdx" 215+  , PoolEpoch "pool1wvccajt4eugjtf3k0ja3exjqdj7t8egsujwhcw4tzj4rzsxzw5w" 231+  ]++poolInfoSample = [r|+{+  "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",+  "hex": "0f292fcaa02b8b2f9b3c8f9fd8e0bb21abedb692a6d5058df3ef2735",+  "vrf_key": "0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233",+  "blocks_minted": 69,+  "live_stake": "6900000000",+  "live_size": 0.42,+  "live_saturation": 0.93,+  "live_delegators": 127,+  "active_stake": "4200000000",+  "active_size": 0.43,+  "declared_pledge": "5000000000",+  "live_pledge": "5000000001",+  "margin_cost": 0.05,+  "fixed_cost": "340000000",+  "reward_account": "stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f",+  "owners": [+    "stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v"+  ],+  "registration": [+    "9f83e5484f543e05b52e99988272a31da373f3aab4c064c76db96643a355d9dc",+    "7ce3b8c433bf401a190d58c8c483d8e3564dfd29ae8633c8b1b3e6c814403e95",+    "3e6e1200ce92977c3fe5996bd4d7d7e192bcb7e231bc762f9f240c76766535b9"+  ],+  "retirement": [+    "252f622976d39e646815db75a77289cf16df4ad2b287dd8e3a889ce14c13d1a8"+  ]+}+|]++poolInfoExpected =+  PoolInfo+    { _poolInfoPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _poolInfoHex = "0f292fcaa02b8b2f9b3c8f9fd8e0bb21abedb692a6d5058df3ef2735"+    , _poolInfoVrfKey = "0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233"+    , _poolInfoBlocksMinted = 69+    , _poolInfoLiveStake = 6900000000+    , _poolInfoLiveSize = 0.42+    , _poolInfoLiveSaturation = 0.93+    , _poolInfoActiveStake = 4200000000+    , _poolInfoActiveSize = 0.43+    , _poolInfoDeclaredPledge = 5000000000+    , _poolInfoLivePledge = 5000000001+    , _poolInfoMarginCost = 0.05+    , _poolInfoFixedCost = 340000000+    , _poolInfoRewardAccount = "stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f"+    , _poolInfoOwners = [ "stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v" ]+    , _poolInfoRegistration =+        [ "9f83e5484f543e05b52e99988272a31da373f3aab4c064c76db96643a355d9dc"+        , "7ce3b8c433bf401a190d58c8c483d8e3564dfd29ae8633c8b1b3e6c814403e95"+        , "3e6e1200ce92977c3fe5996bd4d7d7e192bcb7e231bc762f9f240c76766535b9"+        ]+    , _poolInfoRetirement = [ "252f622976d39e646815db75a77289cf16df4ad2b287dd8e3a889ce14c13d1a8" ]+    }+++poolHistorySample = [r|+{+  "epoch": 233,+  "blocks": 22,+  "active_stake": "20485965693569",+  "active_size": 1.2345,+  "delegators_count": 115,+  "rewards": "206936253674159",+  "fees": "1290968354"+}+|]++poolHistoryExpected =+  PoolHistory+    { _poolHistoryEpoch = 233+    , _poolHistoryBlocks = 22+    , _poolHistoryActiveStake = 20485965693569+    , _poolHistoryActiveSize = 1.2345+    , _poolHistoryDelegatorsCount = 115+    , _poolHistoryRewards = 206936253674159+    , _poolHistoryFees = 1290968354+    }++poolMetadataSample = [r|+{+  "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",+  "hex": "0f292fcaa02b8b2f9b3c8f9fd8e0bb21abedb692a6d5058df3ef2735",+  "url": "https://stakenuts.com/mainnet.json",+  "hash": "47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c",+  "ticker": "NUTS",+  "name": "Stake Nuts",+  "description": "The best pool ever",+  "homepage": "https://stakentus.com/"+}+|]++poolMetadataExpected =+  PoolMetadata+    { _poolMetadataPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _poolMetadataHex = "0f292fcaa02b8b2f9b3c8f9fd8e0bb21abedb692a6d5058df3ef2735"+    , _poolMetadataUrl = Just "https://stakenuts.com/mainnet.json"+    , _poolMetadataHash = Just "47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c"+    , _poolMetadataTicker = Just "NUTS"+    , _poolMetadataName = Just "Stake Nuts"+    , _poolMetadataDescription = Just "The best pool ever"+    , _poolMetadataHomepage = Just "https://stakentus.com/"+    }++poolRelaySample = [r|+{+  "ipv4": "4.4.4.4",+  "ipv6": "https://stakenuts.com/mainnet.json",+  "dns": "relay1.stakenuts.com",+  "dns_srv": "_relays._tcp.relays.stakenuts.com",+  "port": 3001+}+|]++poolRelayExpected =+  PoolRelay+    { _poolRelayIpv4 = Just "4.4.4.4"+    , _poolRelayIpv6 = Just "https://stakenuts.com/mainnet.json"+    , _poolRelayDns = Just "relay1.stakenuts.com"+    , _poolRelayDnsSrv = Just "_relays._tcp.relays.stakenuts.com"+    , _poolRelayPort = 3001+    }++poolDelegatorsSample = [r|+[+  {+    "address": "stake1ux4vspfvwuus9uwyp5p3f0ky7a30jq5j80jxse0fr7pa56sgn8kha",+    "live_stake": "1137959159981411"+  },+  {+    "address": "stake1uylayej7esmarzd4mk4aru37zh9yz0luj3g9fsvgpfaxulq564r5u",+    "live_stake": "16958865648"+  },+  {+    "address": "stake1u8lr2pnrgf8f7vrs9lt79hc3sxm8s2w4rwvgpncks3axx6q93d4ck",+    "live_stake": "18605647"+  }+]+|]++poolDelegatorsExpected =+  [ PoolDelegator+      { _poolDelegatorAddress = "stake1ux4vspfvwuus9uwyp5p3f0ky7a30jq5j80jxse0fr7pa56sgn8kha"+      , _poolDelegatorLiveStake = 1137959159981411+      }+  , PoolDelegator+      { _poolDelegatorAddress = "stake1uylayej7esmarzd4mk4aru37zh9yz0luj3g9fsvgpfaxulq564r5u"+      , _poolDelegatorLiveStake = 16958865648+      }+  , PoolDelegator+      { _poolDelegatorAddress = "stake1u8lr2pnrgf8f7vrs9lt79hc3sxm8s2w4rwvgpncks3axx6q93d4ck"+      , _poolDelegatorLiveStake = 18605647+      }+  ]++poolUpdatesSample = [r|+[+  {+    "tx_hash": "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad",+    "cert_index": 0,+    "action": "registered"+  },+  {+    "tx_hash": "9c190bc1ac88b2ab0c05a82d7de8b71b67a9316377e865748a89d4426c0d3005",+    "cert_index": 0,+    "action": "deregistered"+  },+  {+    "tx_hash": "e14a75b0eb2625de7055f1f580d70426311b78e0d36dd695a6bdc96c7b3d80e0",+    "cert_index": 1,+    "action": "registered"+  }+]+|]++poolUpdatesExpected =+    [ PoolUpdate+        { _poolUpdateTxHash = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"+        , _poolUpdateCertIndex = 0+        , _poolUpdateAction = PoolRegistered+        }+    , PoolUpdate+        { _poolUpdateTxHash = "9c190bc1ac88b2ab0c05a82d7de8b71b67a9316377e865748a89d4426c0d3005"+        , _poolUpdateCertIndex = 0+        , _poolUpdateAction = PoolDeregistered+        }+    , PoolUpdate+        { _poolUpdateTxHash = "e14a75b0eb2625de7055f1f580d70426311b78e0d36dd695a6bdc96c7b3d80e0"+        , _poolUpdateCertIndex = 1+        , _poolUpdateAction = PoolRegistered+        }+    ]++
+ test/Cardano/Transactions.hs view
@@ -0,0 +1,387 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Cardano.Transactions+  where++import Data.Aeson (decode, eitherDecode, encode, object, (.=))+import Data.Text (Text)+import qualified Money+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_txs :: Spec+spec_txs = do+  it "parses transaction sample" $ do+    eitherDecode transactionSample+    `shouldBe`+    Right transactionExpected++  it "parses transaction utxos sample" $ do+    eitherDecode transactionUtxosSample+    `shouldBe`+    Right transactionUtxosExpected++  it "parses transaction stake sample" $ do+    eitherDecode transactionStakeSample+    `shouldBe`+    Right transactionStakeExpected++  it "parses transaction delegation sample" $ do+    eitherDecode transactionDelegationSample+    `shouldBe`+    Right transactionDelegationExpected++  it "parses transaction withdrawal sample" $ do+    eitherDecode transactionWithdrawalSample+    `shouldBe`+    Right transactionWithdrawalExpected++  it "parses transaction mir sample" $ do+    eitherDecode transactionMirSample+    `shouldBe`+    Right transactionMirExpected++  it "parses transaction pool update sample" $ do+    eitherDecode transactionPoolUpdateSample+    `shouldBe`+    Right transactionPoolUpdateExpected++  it "parses transaction pool retiring sample" $ do+    eitherDecode transactionPoolRetiringSample+    `shouldBe`+    Right transactionPoolRetiringExpected++  it "parses transaction meta (JSON) sample" $ do+    eitherDecode transactionMetaJSONSample+    `shouldBe`+    Right transactionMetaJSONExpected++  it "parses transaction meta (CBOR) sample" $ do+    eitherDecode transactionMetaCBORSample+    `shouldBe`+    Right transactionMetaCBORExpected++transactionSample = [r|+{+  "hash": "1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477",+  "block": "356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940",+  "block_height": 123456,+  "slot": 42000000,+  "index": 1,+  "output_amount": [+    {+      "unit": "lovelace",+      "quantity": "42000000"+    },+    {+      "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+      "quantity": "12"+    }+  ],+  "fees": "182485",+  "deposit": "0",+  "size": 433,+  "invalid_before": null,+  "invalid_hereafter": "13885913",+  "utxo_count": 4,+  "withdrawal_count": 0,+  "mir_cert_count": 0,+  "delegation_count": 0,+  "stake_cert_count": 0,+  "pool_update_count": 0,+  "pool_retire_count": 0,+  "asset_mint_or_burn_count": 0+}+|]++sampleAmounts :: [Amount]+sampleAmounts =+  [ AdaAmount 42000000+  , AssetAmount+      $ Money.mkSomeDiscrete+          "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"+          unitScale+          12+  ]++transactionExpected =+  Transaction+    { _transactionHash = "1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477"+    , _transactionBlock = "356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940"+    , _transactionBlockHeight = 123456+    , _transactionSlot = 42000000+    , _transactionIndex = 1+    , _transactionOutputAmount = sampleAmounts+    , _transactionFees = 182485+    , _transactionDeposit = 0+    , _transactionSize = 433+    , _transactionInvalidBefore = Nothing+    , _transactionInvalidHereafter = Just "13885913"+    , _transactionUtxoCount = 4+    , _transactionWithdrawalCount = 0+    , _transactionMirCertCount =  0+    , _transactionDelegationCount = 0+    , _transactionStakeCertCount = 0+    , _transactionPoolUpdateCount = 0+    , _transactionPoolRetireCount = 0+    , _transactionAssetMintOrBurnCount = 0+    }++transactionUtxosSample = [r|+{+  "hash": "1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477",+  "inputs": [+    {+      "address": "addr1q9ld26v2lv8wvrxxmvg90pn8n8n5k6tdst06q2s856rwmvnueldzuuqmnsye359fqrk8hwvenjnqultn7djtrlft7jnq7dy7wv",+      "amount": [+        {+          "unit": "lovelace",+          "quantity": "42000000"+        },+        {+          "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+          "quantity": "12"+        }+      ],+      "tx_hash": "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dce628516157f0",+      "output_index": 0+    }+  ],+  "outputs": [+    {+      "address": "addr1q9ld26v2lv8wvrxxmvg90pn8n8n5k6tdst06q2s856rwmvnueldzuuqmnsye359fqrk8hwvenjnqultn7djtrlft7jnq7dy7wv",+      "amount": [+        {+          "unit": "lovelace",+          "quantity": "42000000"+        },+        {+          "unit": "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e",+          "quantity": "12"+        }+      ]+    }+  ]+}+|]++utxoInSample :: UtxoInput+utxoInSample =+    UtxoInput+      { _utxoInputAddress = "addr1q9ld26v2lv8wvrxxmvg90pn8n8n5k6tdst06q2s856rwmvnueldzuuqmnsye359fqrk8hwvenjnqultn7djtrlft7jnq7dy7wv"+      , _utxoInputAmount = sampleAmounts+      , _utxoInputTxHash = "1a0570af966fb355a7160e4f82d5a80b8681b7955f5d44bec0dce628516157f0"+      , _utxoInputOutputIndex = 0+      }++utxoOutSample :: UtxoOutput+utxoOutSample =+  UtxoOutput+    { _utxoOutputAddress = "addr1q9ld26v2lv8wvrxxmvg90pn8n8n5k6tdst06q2s856rwmvnueldzuuqmnsye359fqrk8hwvenjnqultn7djtrlft7jnq7dy7wv"+    , _utxoOutputAmount = sampleAmounts+    }++transactionUtxosExpected =+  TransactionUtxos+    { _transactionUtxosHash = "1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477"+    , _transactionUtxosInputs = pure utxoInSample+    , _transactionUtxosOutputs = pure utxoOutSample+    }++transactionStakeSample = [r|+{+  "cert_index": 0,+  "address": "stake1u9t3a0tcwune5xrnfjg4q7cpvjlgx9lcv0cuqf5mhfjwrvcwrulda",+  "registration": true+}+|]++transactionStakeExpected =+  TransactionStake+    { _transactionStakeCertIndex = 0+    , _transactionStakeAddress = "stake1u9t3a0tcwune5xrnfjg4q7cpvjlgx9lcv0cuqf5mhfjwrvcwrulda"+    , _transactionStakeRegistration = True+    }++transactionDelegationSample =  [r|+{+  "index": 0,+  "cert_index": 0,+  "address": "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc",+  "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",+  "active_epoch": 210+}+|]++transactionDelegationExpected =+  TransactionDelegation+    { _transactionDelegationCertIndex = 0+    , _transactionDelegationAddress = "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc"+    , _transactionDelegationPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _transactionDelegationActiveEpoch = 210+    }++transactionWithdrawalSample = [r|+{+  "address": "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc",+  "amount": "431833601"+}+|]++transactionWithdrawalExpected =+  TransactionWithdrawal+    { _transactionWithdrawalAddress = "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc"+    , _transactionWithdrawalAmount = 431833601+    }++transactionMirSample = [r|+{+  "pot": "reserve",+  "cert_index": 0,+  "address": "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc",+  "amount": "431833601"+}+|]++transactionMirExpected =+  TransactionMir+    { _transactionMirPot = Reserve+    , _transactionMirCertIndex = 0+    , _transactionMirAddress = "stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc"+    , _transactionMirAmount = 431833601+    }++transactionPoolUpdateSample = [r|+{+  "cert_index": 0,+  "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",+  "vrf_key": "0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233",+  "pledge": "5000000000",+  "margin_cost": 0.05,+  "fixed_cost": "340000000",+  "reward_account": "stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f",+  "owners": [+    "stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v"+  ],+  "metadata": {+    "url": "https://stakenuts.com/mainnet.json",+    "hash": "47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c",+    "ticker": "NUTS",+    "name": "Stake Nuts",+    "description": "The best pool ever",+    "homepage": "https://stakentus.com/"+  },+  "relays": [+    {+      "ipv4": "4.4.4.4",+      "ipv6": "https://stakenuts.com/mainnet.json",+      "dns": "relay1.stakenuts.com",+      "dns_srv": "_relays._tcp.relays.stakenuts.com",+      "port": 3001+    }+  ],+  "active_epoch": 210+}+|]++transactionPoolUpdateExpected =+  TransactionPoolUpdate+    { _transactionPoolUpdateCertIndex = 0+    , _transactionPoolUpdatePoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _transactionPoolUpdateVrfKey = "0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233"+    , _transactionPoolUpdatePledge = 5000000000+    , _transactionPoolUpdateMarginCost = 0.05+    , _transactionPoolUpdateFixedCost = 340000000+    , _transactionPoolUpdateRewardAccount = "stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f"+    , _transactionPoolUpdateOwners = [ "stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v" ]+    , _transactionPoolUpdateMetadata = Just samplePoolUpdateMetadata+    , _transactionPoolUpdateRelays = [ samplePoolRelay ]+    , _transactionPoolUpdateActiveEpoch = 210+    }++samplePoolUpdateMetadata :: PoolUpdateMetadata+samplePoolUpdateMetadata =+  PoolUpdateMetadata+    { _poolUpdateMetadataUrl = Just "https://stakenuts.com/mainnet.json"+    , _poolUpdateMetadataHash = Just "47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c"+    , _poolUpdateMetadataTicker = Just "NUTS"+    , _poolUpdateMetadataName = Just "Stake Nuts"+    , _poolUpdateMetadataDescription = Just "The best pool ever"+    , _poolUpdateMetadataHomepage = Just "https://stakentus.com/"+    }++transactionPoolRetiringSample = [r|+{+  "cert_index": 0,+  "pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",+  "retiring_epoch": 216+}+|]++transactionPoolRetiringExpected =+  TransactionPoolRetiring+    { _transactionPoolRetiringCertIndex = 0+    , _transactionPoolRetiringPoolId = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"+    , _transactionPoolRetiringRetiringEpoch = 216+    }++transactionMetaJSONSample = [r|+[+  {+    "label": "1967",+    "json_metadata": {+      "metadata": "https://nut.link/metadata.json",+      "hash": "6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af"+    }+  },+  {+    "label": "1968",+    "json_metadata": {+      "ADAUSD": [+        {+          "value": "0.15409850555139935",+          "source": "ergoOracles"+        }+      ]+    }+  }+]+|]++transactionMetaJSONExpected =+    let oracleMeta val =+          object [+            "ADAUSD" .=+              [ object [ "value" .= (val :: Text)+                       , "source" .= ("ergoOracles" :: Text) ]+              ]+          ]+    in+    [ TransactionMetaJSON+        "1967"+        (Just $ object+           [ "metadata" .= ("https://nut.link/metadata.json" :: Text)+           , "hash" .= ("6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af" :: Text)+           ])+    , TransactionMetaJSON+        "1968"+        (Just $ oracleMeta "0.15409850555139935")+    ]++transactionMetaCBORSample = [r|+{+  "label": "1968",+  "cbor_metadata": "\\xa100a16b436f6d62696e6174696f6e8601010101010c"+}+|]++transactionMetaCBORExpected =+    TransactionMetaCBOR+      "1968"+      (Just "\\xa100a16b436f6d62696e6174696f6e8601010101010c")
+ test/IPFS.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module IPFS+  where++import Data.Aeson (eitherDecode)+import Data.Text (Text)+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_ipfs :: Spec+spec_ipfs = do+  it "parses add sample" $ do+    eitherDecode pinAddSample+    `shouldBe`+    Right pinAddExpected++  it "parses change sample" $ do+    eitherDecode pinChangeSample+    `shouldBe`+    Right pinChangeExpected++  it "parses pin sample" $ do+    eitherDecode pinSample+    `shouldBe`+    Right pinExpected++pinAddSample = [r|+{+  "name": "README.md",+  "ipfs_hash": "QmZbHqiCxKEVX7QfijzJTkZiSi3WEVTcvANgNAWzDYgZDr",+  "size": "125297"+}+|]++pinAddExpected =+  IPFSAdd+    "README.md"+    "QmZbHqiCxKEVX7QfijzJTkZiSi3WEVTcvANgNAWzDYgZDr"+    125297++pinChangeSample = [r|+{+  "ipfs_hash": "QmPojRfAXYAXV92Dof7gtSgaVuxEk64xx9CKvprqu9VwA8",+  "state": "queued"+}+|]++pinChangeExpected =+  IPFSPinChange+    "QmPojRfAXYAXV92Dof7gtSgaVuxEk64xx9CKvprqu9VwA8"+    Queued++pinSample = [r|+{+  "time_created": 1615551024,+  "time_pinned": 1615551024,+  "ipfs_hash": "QmdVMnULrY95mth2XkwjxDtMHvzuzmvUPTotKE1tgqKbCx",+  "size": "1615551024",+  "state": "pinned"+}+|]++pinExpected =+  IPFSPin+    { _ipfsPinTimeCreated = 1615551024+    , _ipfsPinTimePinned = 1615551024+    , _ipfsPinIpfsHash = "QmdVMnULrY95mth2XkwjxDtMHvzuzmvUPTotKE1tgqKbCx"+    , _ipfsPinSize = 1615551024+    , _ipfsPinState = Pinned+    }
+ test/NutLink.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module NutLink+  where++import Data.Aeson (Value (..), eitherDecode, object, (.=))+import Data.Text (Text)+import qualified Data.Vector+import Test.Hspec+import Test.Tasty.Hspec+import Text.RawString.QQ++import Blockfrost.Types++spec_nutlink :: Spec+spec_nutlink = do+  it "parses nutlink address sample" $ do+    eitherDecode nutlinkAddressSample+    `shouldBe`+    Right nutlinkAddressExpected++  it "parses nutlink address tickers sample" $ do+    eitherDecode nutlinkAddressTickersSample+    `shouldBe`+    Right nutlinkAddressTickersExpected++  it "parses nutlink ticker sample" $ do+    eitherDecode nutlinkTickerSample+    `shouldBe`+    Right nutlinkTickerExpected++  it "parses nutlink ticker with address sample" $ do+    eitherDecode nutlinkTickerWithAddressSample+    `shouldBe`+    Right nutlinkTickerWithAddressExpected++nutlinkAddressSample = [r|+{+  "address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",+  "metadata_url": "https://nut.link/metadata.json",+  "metadata_hash": "6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af",+  "metadata": {}+}+|]++nutlinkAddressExpected =+  NutlinkAddress+    { _nutlinkAddressAddress = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz"+    , _nutlinkAddressMetadataUrl = "https://nut.link/metadata.json"+    , _nutlinkAddressMetadataHash = "6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af"+    , _nutlinkAddressMetadata = pure $ object []+    }++nutlinkAddressTickersSample = [r|+[+  {+    "name": "ADAUSD",+    "count": 1980038,+    "latest_block": 2657092+  },+  {+    "name": "ADAEUR",+    "count": 1980038,+    "latest_block": 2657092+  },+  {+    "name": "ADABTC",+    "count": 1980038,+    "latest_block": 2657092+  }+]+|]++nutlinkAddressTickersExpected =+  [ NutlinkAddressTicker+      { _nutlinkAddressTickerName = "ADAUSD"+      , _nutlinkAddressTickerCount = 1980038+      , _nutlinkAddressTickerLatestBlock = 2657092+      }+  , NutlinkAddressTicker+      { _nutlinkAddressTickerName = "ADAEUR"+      , _nutlinkAddressTickerCount = 1980038+      , _nutlinkAddressTickerLatestBlock = 2657092+      }+  , NutlinkAddressTicker+      { _nutlinkAddressTickerName = "ADABTC"+      , _nutlinkAddressTickerCount = 1980038+      , _nutlinkAddressTickerLatestBlock = 2657092+      }+  ]+++nutlinkTickerSample = [r|+{+  "tx_hash": "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b",+  "block_height": 2657092,+  "tx_index": 8,+  "payload": [+    {+      "source": "coinGecko",+      "value": "1.29"+    },+    {+      "source": "cryptoCompare",+      "value": "1.283"+    }+  ]+}+|]++sampleArray = Array+  $ Data.Vector.fromList+  [ object [ "source" .= ("coinGecko" :: Text)+           , "value" .= ("1.29" :: Text) ]+  , object [ "source" .= ("cryptoCompare" :: Text),+             "value" .= ("1.283" :: Text) ]+  ]++nutlinkTickerExpected =+  NutlinkTicker+    { _nutlinkTickerTxHash = "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b"+    , _nutlinkTickerBlockHeight = 2657092+    , _nutlinkTickerTxIndex = 8+    , _nutlinkTickerPayload = sampleArray+    }++nutlinkTickerWithAddressSample = [r|+{+  "address": "addr_test1qpmtp5t0t5y6cqkaz7rfsyrx7mld77kpvksgkwm0p7en7qum7a589n30e80tclzrrnj8qr4qvzj6al0vpgtnmrkkksnqd8upj0",+  "tx_hash": "e8073fd5318ff43eca18a852527166aa8008bee9ee9e891f585612b7e4ba700b",+  "block_height": 2657092,+  "tx_index": 8,+  "payload": [+    {+      "source": "coinGecko",+      "value": "1.29"+    },+    {+      "source": "cryptoCompare",+      "value": "1.283"+    }+  ]+}+|]++nutlinkTickerWithAddressExpected :: (Address, NutlinkTicker)+nutlinkTickerWithAddressExpected =+  ("addr_test1qpmtp5t0t5y6cqkaz7rfsyrx7mld77kpvksgkwm0p7en7qum7a589n30e80tclzrrnj8qr4qvzj6al0vpgtnmrkkksnqd8upj0"+  , nutlinkTickerExpected)
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}