diff --git a/README.md b/README.md index 7aaf524..83a5b57 100644 --- a/README.md +++ b/README.md @@ -32,26 +32,35 @@ 1. Add `maestro-sdk` to the `build-depends` of your project. 2. Create a [Maestro API key](https://docs.gomaestro.org/docs/Getting-started/Sign-up-login). -3. Create environment for accessing the API: -```haskell -import Maestro.Client.Env +3. Code below explains sample usage. + ```haskell + module Main (main) where -myEnvPreprod <- mkMaestroEnv "Your-API-Key" Preprod -myEnvMainnet <- mkMaestroEnv "Your-API-Key" Mainnet -``` -4. Example: chain tip -```haskell -getChainTip myEnvPreprod -- Preprod -getChainTip myEnvMainnet -- Mainnet -``` + import Control.Exception (try) + import Maestro.Client.V1 -- @Maestro.Client.V1@ defines all the client utilities to query Maestro API endpoints. + import Maestro.Types.V1 -- @Maestro.Types.V1@ defines all the types used. -Other endpoints in the `General` category can be exmained in the [`Maestro.Client.General`](https://haddock.gomaestro.org/Maestro-Client-General.html) Haddock module. + main :: IO () + main = do + env <- mkMaestroEnv @'V1 "" Preprod -- This is how we create an environment against which we'll query endpoints. + chainTip :: ChainTip <- getTimestampedData <$> getChainTip env -- Maestro endpoint to get for chain-tip has data & timestamp against which data was calculated. All endpoints which are timestamped, has functions `getTimestampedData` to get for underlying data & `getTimestamp` to get the timestamp. + addressesUTxOs :: Either MaestroError [UtxoWithSlot] <- + try -- To catch for any errors, given in type `MaestroError`. + $ allPages -- Since this endpoint is paged, we have a helper utility `allPages` to accumulate data from all the pages. + $ flip + ( + utxosAtMultiAddresses env + (Just True) -- We would like to have datums resolved. This is for @resolve_datums@ query parameter. + (Just False) -- We would not like to include CBOR encodings of the transaction outputs in the response. + ) ["addr_test1...", "addr_test1...", "addr_test1..."] -- Mention your list of addresses to query for. + print addressesUTxOs + ``` # Documentation * [SDK Haddock](https://haddock.gomaestro.org/) * [Maestro public docs](https://docs.gomaestro.org/) -* [Maestro API reference](https://reference.gomaestro.org/) +* [Maestro API reference](https://docs.gomaestro.org/docs/category/rest-api-reference) # Contributing diff --git a/maestro-exe/Maestro/Run/Address.hs b/maestro-exe/Maestro/Run/Address.hs new file mode 100644 index 0000000..7ba2703 --- /dev/null +++ b/maestro-exe/Maestro/Run/Address.hs @@ -0,0 +1,20 @@ +module Maestro.Run.Address where + +import Control.Monad (unless) +import Data.List (sort) +import qualified Data.Text as T (pack) +import Maestro.Client.Env +import qualified Maestro.Client.V0 as V0 +import qualified Maestro.Client.V1 as V1 +import Maestro.Types.V1.Common (v1UtxoWithSlotToV0) + +runAddressAPI :: String -> IO () +runAddressAPI apiKey = do + mEnvV0 <- mkMaestroEnv @'V0 (T.pack apiKey) Preprod + mEnvV1 <- mkMaestroEnv @'V1 (T.pack apiKey) Preprod + let addrs = undefined -- Mention list of addresses. + utxos <- V0.allPages $ flip (V0.utxosAtMultiAddresses mEnvV0 Nothing Nothing) addrs + let utxosSorted = sort utxos + utxos' <- fmap (fmap v1UtxoWithSlotToV0) $ V1.allPages $ flip (V1.utxosAtMultiAddresses mEnvV1 Nothing Nothing) addrs + let utxos'Sorted = sort utxos' + unless (utxosSorted == utxos'Sorted) $ error "Not same" diff --git a/maestro-exe/Maestro/Run/Datum.hs b/maestro-exe/Maestro/Run/Datum.hs index 5d7c855..acdb9c7 100644 --- a/maestro-exe/Maestro/Run/Datum.hs +++ b/maestro-exe/Maestro/Run/Datum.hs @@ -1,9 +1,9 @@ module Maestro.Run.Datum where -import Maestro.Client +import Maestro.Client.V0 import Text.Printf (printf) -runDatumAPI :: MaestroEnv -> IO () +runDatumAPI :: MaestroEnv 'V0 -> IO () runDatumAPI mEnv = do let datumHash = "938dc15a5faa3da8e7f1e3ed8ca50b49248f8fffdfc04ff3cf7dffa0d06343eb" -- Quiet an involved datum. printf "Fetching datum from hash %s...\n" datumHash diff --git a/maestro-exe/Maestro/Run/Epochs.hs b/maestro-exe/Maestro/Run/Epochs.hs index 2a08084..535a0b2 100644 --- a/maestro-exe/Maestro/Run/Epochs.hs +++ b/maestro-exe/Maestro/Run/Epochs.hs @@ -1,8 +1,8 @@ module Maestro.Run.Epochs where -import Maestro.Client +import Maestro.Client.V0 -runEpochsAPI :: MaestroEnv -> IO () +runEpochsAPI :: MaestroEnv 'V0 -> IO () runEpochsAPI mEnv = do putStrLn "Fetching Current Epoch's Info ..." currentEpochInfo <- getCurrentEpoch mEnv diff --git a/maestro-exe/Maestro/Run/General.hs b/maestro-exe/Maestro/Run/General.hs new file mode 100644 index 0000000..774eb31 --- /dev/null +++ b/maestro-exe/Maestro/Run/General.hs @@ -0,0 +1,9 @@ +module Maestro.Run.General where + +import Maestro.Client.V0 +import Text.Printf (printf) + +runGeneralAPI :: MaestroEnv 'V0 -> IO () +runGeneralAPI mEnv = do + chainTip <- getChainTip mEnv + printf "Querying chain-tip, received: топ\n%s\n" (show chainTip) diff --git a/maestro-exe/Maestro/Run/Pools.hs b/maestro-exe/Maestro/Run/Pools.hs index d767b60..b97564d 100644 --- a/maestro-exe/Maestro/Run/Pools.hs +++ b/maestro-exe/Maestro/Run/Pools.hs @@ -1,12 +1,12 @@ module Maestro.Run.Pools where -import Maestro.Client -import Maestro.Types +import Maestro.Client.V0 +import Maestro.Types.V0 poolId :: Bech32StringOf PoolId poolId = "pool1rkfs9glmfva3jd0q9vnlqvuhnrflpzj4l07u6sayfx5k7d788us" -runPoolsAPI :: MaestroEnv -> IO () +runPoolsAPI :: MaestroEnv 'V0 -> IO () runPoolsAPI mEnv = do putStrLn "Fetching List Pools ..." lstPools <- runListPools mEnv @@ -40,26 +40,26 @@ runPoolsAPI mEnv = do updates <- runPoolInfo mEnv putStrLn $ "fetched pool Updates: \n " ++ show updates -runPoolUpdates :: MaestroEnv -> IO [PoolUpdate] +runPoolUpdates :: MaestroEnv 'V0 -> IO [PoolUpdate] runPoolUpdates mEnv = poolUpdates mEnv poolId -runListPools :: MaestroEnv -> IO [PoolListInfo] +runListPools :: MaestroEnv 'V0 -> IO [PoolListInfo] runListPools mEnv = listPools mEnv (Page 1 1) -runPoolBlocks :: MaestroEnv -> IO [PoolBlock] +runPoolBlocks :: MaestroEnv 'V0 -> IO [PoolBlock] runPoolBlocks mEnv = poolBlocks mEnv poolId (Page 1 1) Nothing (Just Ascending) -runPoolDelegators :: MaestroEnv -> IO [DelegatorInfo] +runPoolDelegators :: MaestroEnv 'V0 -> IO [DelegatorInfo] runPoolDelegators mEnv = poolDelegators mEnv poolId (Page 1 1) -runPoolHistory :: MaestroEnv -> IO [PoolHistory] +runPoolHistory :: MaestroEnv 'V0 -> IO [PoolHistory] runPoolHistory mEnv = poolHistory mEnv poolId (Page 1 1) Nothing (Just Ascending) -runPoolInfo :: MaestroEnv -> IO PoolInfo +runPoolInfo :: MaestroEnv 'V0 -> IO PoolInfo runPoolInfo mEnv = poolInfo mEnv poolId -runPoolMetadata :: MaestroEnv -> IO PoolMetadata +runPoolMetadata :: MaestroEnv 'V0 -> IO PoolMetadata runPoolMetadata mEnv = poolMetadata mEnv poolId -runPoolRelay :: MaestroEnv -> IO [PoolRelay] +runPoolRelay :: MaestroEnv 'V0 -> IO [PoolRelay] runPoolRelay mEnv = poolRelays mEnv poolId diff --git a/maestro-exe/Maestro/Run/Scripts.hs b/maestro-exe/Maestro/Run/Scripts.hs index ad64052..afdd6d2 100644 --- a/maestro-exe/Maestro/Run/Scripts.hs +++ b/maestro-exe/Maestro/Run/Scripts.hs @@ -1,9 +1,9 @@ module Maestro.Run.Scripts where -import Maestro.Client -import Text.Printf (printf) +import Maestro.Client.V0 +import Text.Printf (printf) -runScriptsAPI :: MaestroEnv -> IO () +runScriptsAPI :: MaestroEnv 'V0 -> IO () runScriptsAPI mEnv = do let scriptHash = "3a888d65f16790950a72daee1f63aa05add6d268434107cfa5b67712" printf "Fetching script from hash %s...\n" scriptHash diff --git a/maestro-exe/Maestro/Run/Tx.hs b/maestro-exe/Maestro/Run/Tx.hs index 73acec3..0e3899d 100644 --- a/maestro-exe/Maestro/Run/Tx.hs +++ b/maestro-exe/Maestro/Run/Tx.hs @@ -1,12 +1,12 @@ module Maestro.Run.Tx where -import Maestro.Client -import Maestro.Types +import Maestro.Client.V0 +import Maestro.Types.V0 txHash :: HashStringOf Tx txHash = "7fdf7a20ba50d841344ab0cb368da6a047ce1e2a29b707586f61f0b8fea6bcf2" -runTxApi :: MaestroEnv -> IO () +runTxApi :: MaestroEnv 'V0 -> IO () runTxApi mEnv = do putStrLn "Fetching Tx Address ..." txAddr <- runTxAddress mEnv @@ -20,11 +20,11 @@ runTxApi mEnv = do utxo <- runTxUtxo mEnv putStrLn $ "fetched Tx Utxos: \n " ++ show utxo -runTxAddress :: MaestroEnv -> IO UtxoAddress +runTxAddress :: MaestroEnv 'V0 -> IO UtxoAddress runTxAddress mEnv = txAddress mEnv txHash $ TxIndex 0 -runTxCbor :: MaestroEnv -> IO TxCbor +runTxCbor :: MaestroEnv 'V0 -> IO TxCbor runTxCbor mEnv = txCbor mEnv txHash -runTxUtxo :: MaestroEnv -> IO Utxo +runTxUtxo :: MaestroEnv 'V0 -> IO Utxo runTxUtxo mEnv = txUtxo mEnv txHash (TxIndex 0) (Just True) (Just True) diff --git a/maestro-exe/Main.hs b/maestro-exe/Main.hs index 37d0d33..64f50f8 100644 --- a/maestro-exe/Main.hs +++ b/maestro-exe/Main.hs @@ -2,8 +2,10 @@ module Main (main) where import qualified Data.Text as T import Maestro.Client.Env +-- import Maestro.Run.Address import Maestro.Run.Datum import Maestro.Run.Epochs +import Maestro.Run.General import Maestro.Run.Pools import Maestro.Run.Scripts import Maestro.Run.Tx @@ -14,12 +16,14 @@ main :: IO () main = do apiKey <- maestroKey - env <- mkMaestroEnv (T.pack apiKey) Preprod + env <- mkMaestroEnv @'V0 (T.pack apiKey) Preprod runPoolsAPI env runTxApi env runEpochsAPI env runDatumAPI env runScriptsAPI env + runGeneralAPI env + -- runAddressAPI apiKey where maestroKey = getEnv "MAESTRO_API_KEY" diff --git a/maestro-sdk.cabal b/maestro-sdk.cabal index 682c993..bfac535 100644 --- a/maestro-sdk.cabal +++ b/maestro-sdk.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: maestro-sdk -version: 0.1.0.0 +version: 1.0.0 synopsis: Maestro Blockchain Indexer SDK description: Maestro provides blockchain indexers, APIs and event management systems for the Cardano blockchain. license: Apache-2.0 @@ -12,6 +12,7 @@ build-type: Simple category: Blockchain, Cardano, SDK, API, REST extra-doc-files: CHANGELOG.md extra-source-files: README.md +tested-with: GHC == 8.10.7, GHC == 9.2.8 source-repository head type: git @@ -20,11 +21,14 @@ source-repository head common common ghc-options: -Wall default-extensions: + GADTs DataKinds DeriveGeneric DerivingStrategies DerivingVia GeneralisedNewtypeDeriving + FlexibleInstances + FlexibleContexts MultiParamTypeClasses NumericUnderscores OverloadedStrings @@ -32,53 +36,86 @@ common common RoleAnnotations ScopedTypeVariables TemplateHaskell + TypeApplications + TypeFamilies TypeOperators QuasiQuotes library import: common exposed-modules: - Maestro.API - Maestro.API.Accounts - Maestro.API.Address - Maestro.API.Assets - Maestro.API.Datum - Maestro.API.Epochs - Maestro.API.General - Maestro.API.Pool - Maestro.API.Scripts - Maestro.API.Transaction - Maestro.API.TxManager + Maestro.API.V0 + Maestro.API.V0.Accounts + Maestro.API.V0.Address + Maestro.API.V0.Assets + Maestro.API.V0.Datum + Maestro.API.V0.Epochs + Maestro.API.V0.General + Maestro.API.V0.Pool + Maestro.API.V0.Scripts + Maestro.API.V0.Transaction + Maestro.API.V0.TxManager + + Maestro.API.V1 + Maestro.API.V1.Addresses + Maestro.API.V1.Datum + Maestro.API.V1.General + Maestro.API.V1.Pools + Maestro.API.V1.Transactions + Maestro.API.V1.TxManager - Maestro.Client - Maestro.Client.Core - Maestro.Client.Core.Pagination - Maestro.Client.Datum Maestro.Client.Env - Maestro.Client.Epochs - Maestro.Client.Accounts - Maestro.Client.Address - Maestro.Client.Assets - Maestro.Client.General - Maestro.Client.Pools - Maestro.Client.Scripts - Maestro.Client.Transaction - Maestro.Client.TxManager + Maestro.Client.Error + Maestro.Client.V0 + Maestro.Client.V0.Core + Maestro.Client.V0.Core.Pagination + Maestro.Client.V0.Datum + Maestro.Client.V0.Epochs + Maestro.Client.V0.Accounts + Maestro.Client.V0.Address + Maestro.Client.V0.Assets + Maestro.Client.V0.General + Maestro.Client.V0.Pools + Maestro.Client.V0.Scripts + Maestro.Client.V0.Transaction + Maestro.Client.V0.TxManager + + Maestro.Client.V1 + Maestro.Client.V1.Core + Maestro.Client.V1.Core.Pagination + Maestro.Client.V1.Addresses + Maestro.Client.V1.Datum + Maestro.Client.V1.General + Maestro.Client.V1.Pools + Maestro.Client.V1.Transactions + Maestro.Client.V1.TxManager - Maestro.Types - Maestro.Types.Accounts - Maestro.Types.Address - Maestro.Types.Assets Maestro.Types.Common - Maestro.Types.Datum - Maestro.Types.Epochs - Maestro.Types.General - Maestro.Types.Pool + Maestro.Types.V0 + Maestro.Types.V0.Accounts + Maestro.Types.V0.Address + Maestro.Types.V0.Assets + Maestro.Types.V0.Common + Maestro.Types.V0.Datum + Maestro.Types.V0.Epochs + Maestro.Types.V0.General + Maestro.Types.V0.Pool + Maestro.Types.V0.Transactions + + Maestro.Types.V1 + Maestro.Types.V1.Addresses + Maestro.Types.V1.Datum + Maestro.Types.V1.Common + Maestro.Types.V1.Common.Pagination + Maestro.Types.V1.Common.Timestamped + Maestro.Types.V1.General + Maestro.Types.V1.Pools + Maestro.Types.V1.Transactions -- other-modules: -- other-extensions: build-depends: - , base ^>=4.14.3.0 + , base >= 4.14.3.0 && < 4.19 , bytestring , aeson , containers @@ -111,7 +148,7 @@ test-suite maestro-sdk-tests Maestro.Test.Transaction build-depends: - base ^>=4.14.3.0 + base , maestro-sdk , aeson , bytestring @@ -129,14 +166,16 @@ executable maestro-exe default-language: Haskell2010 other-modules: Maestro.Run.Datum + Maestro.Run.General Maestro.Run.Pools Maestro.Run.Scripts Maestro.Run.Tx Maestro.Run.Epochs + Maestro.Run.Address -- other-extensions: hs-source-dirs: maestro-exe main-is: Main.hs build-depends: - base ^>=4.14.3.0, + base, maestro-sdk, text diff --git a/src/Maestro/API.hs b/src/Maestro/API/V0.hs similarity index 65% rename from src/Maestro/API.hs rename to src/Maestro/API/V0.hs index 0b7c9b0..414549f 100644 --- a/src/Maestro/API.hs +++ b/src/Maestro/API/V0.hs @@ -1,16 +1,16 @@ -module Maestro.API where +module Maestro.API.V0 where -import Data.Text (Text) -import Maestro.API.Accounts -import Maestro.API.Address -import Maestro.API.Assets -import Maestro.API.Datum -import Maestro.API.Epochs -import Maestro.API.General -import Maestro.API.Pool -import Maestro.API.Scripts -import Maestro.API.Transaction -import Maestro.API.TxManager +import Data.Text (Text) +import Maestro.API.V0.Accounts +import Maestro.API.V0.Address +import Maestro.API.V0.Assets +import Maestro.API.V0.Datum +import Maestro.API.V0.Epochs +import Maestro.API.V0.General +import Maestro.API.V0.Pool +import Maestro.API.V0.Scripts +import Maestro.API.V0.Transaction +import Maestro.API.V0.TxManager import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/Accounts.hs b/src/Maestro/API/V0/Accounts.hs similarity index 82% rename from src/Maestro/API/Accounts.hs rename to src/Maestro/API/V0/Accounts.hs index 4262273..fca412d 100644 --- a/src/Maestro/API/Accounts.hs +++ b/src/Maestro/API/V0/Accounts.hs @@ -1,9 +1,8 @@ -module Maestro.API.Accounts where +module Maestro.API.V0.Accounts where -import Data.Text (Text) -import Maestro.Client.Core.Pagination -import Maestro.Types.Accounts -import Maestro.Types.Common +import Data.Text (Text) +import Maestro.Client.V0.Core.Pagination +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/Address.hs b/src/Maestro/API/V0/Address.hs similarity index 64% rename from src/Maestro/API/Address.hs rename to src/Maestro/API/V0/Address.hs index 362a175..f690007 100644 --- a/src/Maestro/API/Address.hs +++ b/src/Maestro/API/V0/Address.hs @@ -1,9 +1,8 @@ -module Maestro.API.Address where +module Maestro.API.V0.Address where -import Data.Text (Text) -import Maestro.Types.Address -import Maestro.Types.Common (Utxo) -import Maestro.Client.Core.Pagination +import Data.Text (Text) +import Maestro.Client.V0.Core.Pagination +import Maestro.Types.V0 import Servant.API import Servant.API.Generic @@ -13,8 +12,8 @@ data AddressAPI route = AddressAPI _addressesUtxos :: route :- "utxos" - :> QueryParam "resolve_datums" Bool - :> QueryParam "with_cbor" Bool + :> QueryParam "resolve_datums" Bool + :> QueryParam "with_cbor" Bool :> Pagination :> ReqBody '[JSON] [Text] :> Post '[JSON] [Utxo] @@ -23,8 +22,8 @@ data AddressAPI route = AddressAPI :: route :- Capture "address" Text :> "utxos" - :> QueryParam "resolve_datums" Bool - :> QueryParam "with_cbor" Bool + :> QueryParam "resolve_datums" Bool + :> QueryParam "with_cbor" Bool :> Pagination :> Get '[JSON] [Utxo] diff --git a/src/Maestro/API/Assets.hs b/src/Maestro/API/V0/Assets.hs similarity index 91% rename from src/Maestro/API/Assets.hs rename to src/Maestro/API/V0/Assets.hs index 1c03736..d5ec5d9 100644 --- a/src/Maestro/API/Assets.hs +++ b/src/Maestro/API/V0/Assets.hs @@ -1,9 +1,8 @@ -module Maestro.API.Assets where +module Maestro.API.V0.Assets where import Data.Text (Text) -import Maestro.Client.Core.Pagination -import Maestro.Types.Assets -import Maestro.Types.Common +import Maestro.Client.V0.Core.Pagination +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/Datum.hs b/src/Maestro/API/V0/Datum.hs similarity index 80% rename from src/Maestro/API/Datum.hs rename to src/Maestro/API/V0/Datum.hs index 1a19637..0a57746 100644 --- a/src/Maestro/API/Datum.hs +++ b/src/Maestro/API/V0/Datum.hs @@ -1,7 +1,7 @@ -module Maestro.API.Datum where +module Maestro.API.V0.Datum where import Data.Text (Text) -import Maestro.Types.Datum +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/Epochs.hs b/src/Maestro/API/V0/Epochs.hs similarity index 75% rename from src/Maestro/API/Epochs.hs rename to src/Maestro/API/V0/Epochs.hs index 7b07bcd..219fc9a 100644 --- a/src/Maestro/API/Epochs.hs +++ b/src/Maestro/API/V0/Epochs.hs @@ -1,7 +1,6 @@ -module Maestro.API.Epochs where +module Maestro.API.V0.Epochs where -import Maestro.Types.Common (EpochNo) -import Maestro.Types.Epochs +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/General.hs b/src/Maestro/API/V0/General.hs similarity index 87% rename from src/Maestro/API/General.hs rename to src/Maestro/API/V0/General.hs index 272def3..ccb78c2 100644 --- a/src/Maestro/API/General.hs +++ b/src/Maestro/API/V0/General.hs @@ -1,6 +1,6 @@ -module Maestro.API.General where +module Maestro.API.V0.General where -import Maestro.Types.General +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/Pool.hs b/src/Maestro/API/V0/Pool.hs similarity index 88% rename from src/Maestro/API/Pool.hs rename to src/Maestro/API/V0/Pool.hs index ef38cbb..bee49f1 100644 --- a/src/Maestro/API/Pool.hs +++ b/src/Maestro/API/V0/Pool.hs @@ -1,9 +1,9 @@ -module Maestro.API.Pool where +module Maestro.API.V0.Pool where -import Maestro.Types -import Maestro.Client.Core.Pagination -import Servant.API -import Servant.API.Generic +import Maestro.Client.V0.Core.Pagination +import Maestro.Types.V0 +import Servant.API +import Servant.API.Generic data PoolAPI route = PoolAPI { _listPools :: diff --git a/src/Maestro/API/Scripts.hs b/src/Maestro/API/V0/Scripts.hs similarity index 65% rename from src/Maestro/API/Scripts.hs rename to src/Maestro/API/V0/Scripts.hs index 84b6586..46d1536 100644 --- a/src/Maestro/API/Scripts.hs +++ b/src/Maestro/API/V0/Scripts.hs @@ -1,7 +1,7 @@ -module Maestro.API.Scripts where +module Maestro.API.V0.Scripts where -import Data.Text (Text) -import Maestro.Types.Common (Script) +import Data.Text (Text) +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/Transaction.hs b/src/Maestro/API/V0/Transaction.hs similarity index 85% rename from src/Maestro/API/Transaction.hs rename to src/Maestro/API/V0/Transaction.hs index 293e523..399f4ae 100644 --- a/src/Maestro/API/Transaction.hs +++ b/src/Maestro/API/V0/Transaction.hs @@ -1,8 +1,8 @@ -module Maestro.API.Transaction where +module Maestro.API.V0.Transaction where -import qualified Data.ByteString as BS -import qualified Data.Text as T -import Maestro.Types.Common +import qualified Data.ByteString as BS +import qualified Data.Text as T +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/TxManager.hs b/src/Maestro/API/V0/TxManager.hs similarity index 62% rename from src/Maestro/API/TxManager.hs rename to src/Maestro/API/V0/TxManager.hs index df2fd43..6ca5945 100644 --- a/src/Maestro/API/TxManager.hs +++ b/src/Maestro/API/V0/TxManager.hs @@ -1,8 +1,8 @@ -module Maestro.API.TxManager where +module Maestro.API.V0.TxManager where -import qualified Data.ByteString as BS -import qualified Data.Text as T -import Maestro.Types.Common +import qualified Data.ByteString as BS +import qualified Data.Text as T +import Maestro.Types.V0 import Servant.API import Servant.API.Generic diff --git a/src/Maestro/API/V1.hs b/src/Maestro/API/V1.hs new file mode 100644 index 0000000..d1f23d0 --- /dev/null +++ b/src/Maestro/API/V1.hs @@ -0,0 +1,24 @@ +module Maestro.API.V1 where + +import Data.Text (Text) +import Maestro.API.V1.Addresses +import Maestro.API.V1.Datum +import Maestro.API.V1.General +import Maestro.API.V1.Pools +import Maestro.API.V1.Transactions +import Maestro.API.V1.TxManager +import Servant.API +import Servant.API.Generic + +data MaestroApiV1 route = MaestroApiV1 + { _general :: route :- ToServantApi GeneralAPI + , _addresses :: route :- "addresses" :> ToServantApi AddressesAPI + , _datum :: route :- "datum" :> ToServantApi DatumAPI + , _pools :: route :- "pools" :> ToServantApi PoolsAPI + , _txManager :: route :- "txmanager" :> ToServantApi TxManagerAPI + , _transactions :: route :- ToServantApi TransactionsAPI + } deriving Generic + +newtype MaestroApiV1Auth route = MaestroApiV1Auth + { _apiV1 :: route :- Header' '[Required] "api-key" Text :> ToServantApi MaestroApiV1 } + deriving Generic diff --git a/src/Maestro/API/V1/Addresses.hs b/src/Maestro/API/V1/Addresses.hs new file mode 100644 index 0000000..6f8483d --- /dev/null +++ b/src/Maestro/API/V1/Addresses.hs @@ -0,0 +1,32 @@ +module Maestro.API.V1.Addresses where + +import Maestro.Client.V1.Core.Pagination +import Maestro.Types.V1 +import Servant.API +import Servant.API.Generic + +data AddressesAPI route = AddressesAPI + { + _decodeAddress + :: route + :- Capture "address" (TaggedText AddressToDecode) + :> "decode" + :> Get '[JSON] AddressInfo + + , _addressesUtxos + :: route + :- "utxos" + :> QueryParam "resolve_datums" Bool + :> QueryParam "with_cbor" Bool + :> Pagination + :> ReqBody '[JSON] [Bech32StringOf Address] + :> Post '[JSON] PaginatedUtxoWithSlot + + , _addressUtxoRefs + :: route + :- Capture "address" (Bech32StringOf Address) + :> "utxo_refs" + :> Pagination + :> Get '[JSON] PaginatedOutputReferenceObject + + } deriving (Generic) diff --git a/src/Maestro/API/V1/Datum.hs b/src/Maestro/API/V1/Datum.hs new file mode 100644 index 0000000..5054937 --- /dev/null +++ b/src/Maestro/API/V1/Datum.hs @@ -0,0 +1,14 @@ +module Maestro.API.V1.Datum where + +import Maestro.Types.V1 +import Servant.API +import Servant.API.Generic + +newtype DatumAPI route = + DatumAPI + { + _datumByHash + :: route + :- Capture "datum_hash" (HexStringOf DatumHash) + :> Get '[JSON] TimestampedDatum + } deriving Generic diff --git a/src/Maestro/API/V1/General.hs b/src/Maestro/API/V1/General.hs new file mode 100644 index 0000000..2755af3 --- /dev/null +++ b/src/Maestro/API/V1/General.hs @@ -0,0 +1,26 @@ +module Maestro.API.V1.General where + +import Maestro.Types.V1 +import Servant.API +import Servant.API.Generic + +data GeneralAPI route = + GeneralAPI + { + _systemStart + :: route + :- "system-start" + :> Get '[JSON] TimestampedSystemStart + , _eraHistory + :: route + :- "era-history" + :> Get '[JSON] TimestampedEraSummaries + , _protocolParams + :: route + :- "protocol-params" + :> Get '[JSON] TimestampedProtocolParameters + , _chainTip + :: route + :- "chain-tip" + :> Get '[JSON] TimestampedChainTip + } deriving Generic diff --git a/src/Maestro/API/V1/Pools.hs b/src/Maestro/API/V1/Pools.hs new file mode 100644 index 0000000..960a899 --- /dev/null +++ b/src/Maestro/API/V1/Pools.hs @@ -0,0 +1,14 @@ +module Maestro.API.V1.Pools where + +import Maestro.Client.V1.Core.Pagination +import Maestro.Types.V1 +import Servant.API +import Servant.API.Generic + +newtype PoolsAPI route = PoolsAPI + { _listPools :: + route + :- Pagination + :> Get '[JSON] PaginatedPoolListInfo + } + deriving (Generic) diff --git a/src/Maestro/API/V1/Transactions.hs b/src/Maestro/API/V1/Transactions.hs new file mode 100644 index 0000000..72d382c --- /dev/null +++ b/src/Maestro/API/V1/Transactions.hs @@ -0,0 +1,19 @@ +module Maestro.API.V1.Transactions where + +import Maestro.Client.V1.Core.Pagination +import Maestro.Types.V1 +import Servant.API +import Servant.API.Generic + +newtype TransactionsAPI route = TransactionsAPI + { _txOutputs :: + route + :- "transactions" + :> "outputs" + :> QueryParam "resolve_datums" Bool + :> QueryParam "with_cbor" Bool + :> Pagination + :> ReqBody '[JSON] [OutputReference] + :> Post '[JSON] PaginatedUtxo + } + deriving (Generic) diff --git a/src/Maestro/API/V1/TxManager.hs b/src/Maestro/API/V1/TxManager.hs new file mode 100644 index 0000000..6da8cd7 --- /dev/null +++ b/src/Maestro/API/V1/TxManager.hs @@ -0,0 +1,19 @@ +module Maestro.API.V1.TxManager where + +import qualified Data.ByteString as BS +import qualified Data.Text as T +import Maestro.Types.V1 +import Servant.API +import Servant.API.Generic + +data TxManagerAPI route = TxManagerAPI + { _monitoredTxSubmit :: + route + :- ReqBody' '[Required] '[CBORStream] BS.ByteString + :> PostAccepted '[JSON] T.Text + , _monitoredTurboTxSubmit :: + route + :- ReqBody' '[Required] '[CBORStream] BS.ByteString + :> PostAccepted '[JSON] T.Text + } + deriving (Generic) diff --git a/src/Maestro/Client.hs b/src/Maestro/Client.hs deleted file mode 100644 index 74d270f..0000000 --- a/src/Maestro/Client.hs +++ /dev/null @@ -1,29 +0,0 @@ -module Maestro.Client - ( - module Maestro.Client.Accounts - , module Maestro.Client.Address - , module Maestro.Client.Assets - , module Maestro.Client.Core - , module Maestro.Client.Datum - , module Maestro.Client.Env - , module Maestro.Client.Epochs - , module Maestro.Client.General - , module Maestro.Client.Pools - , module Maestro.Client.Scripts - , module Maestro.Client.Transaction - , module Maestro.Client.TxManager - ) where - - -import Maestro.Client.Accounts -import Maestro.Client.Address -import Maestro.Client.Assets -import Maestro.Client.Core -import Maestro.Client.Datum -import Maestro.Client.Env -import Maestro.Client.Epochs -import Maestro.Client.General -import Maestro.Client.Pools -import Maestro.Client.Scripts -import Maestro.Client.Transaction -import Maestro.Client.TxManager diff --git a/src/Maestro/Client/Accounts.hs b/src/Maestro/Client/Accounts.hs deleted file mode 100644 index 88966a2..0000000 --- a/src/Maestro/Client/Accounts.hs +++ /dev/null @@ -1,31 +0,0 @@ -module Maestro.Client.Accounts where - -import Data.Text (Text) -import Maestro.API -import Maestro.API.Accounts -import Maestro.Client.Core -import Maestro.Client.Env -import Maestro.Types -import Servant.API.Generic -import Servant.Client - -accountsClient :: MaestroEnv -> AccountsAPI (AsClientT IO) -accountsClient = fromServant . _accounts . apiV0Client - -getAccount :: MaestroEnv -> Text -> IO AccountInfo -getAccount = _account . accountsClient - -listAccountAddresses :: MaestroEnv -> Text -> Page -> IO [Text] -listAccountAddresses = _accountAddresses . accountsClient - -listAccountAssets :: MaestroEnv -> Text -> Page -> IO [Asset] -listAccountAssets = _accountAssets . accountsClient - -listAccountHistory :: MaestroEnv -> Text -> Maybe EpochNo -> Page -> IO [AccountHistory] -listAccountHistory = _accountsHistory . accountsClient - -listAccountRewards :: MaestroEnv -> Text -> Page -> IO [AccountReward] -listAccountRewards = _accountsReward . accountsClient - -listAccountUpdates :: MaestroEnv -> Text -> Page -> IO [AccountUpdate] -listAccountUpdates = _accountsUpdates . accountsClient diff --git a/src/Maestro/Client/Datum.hs b/src/Maestro/Client/Datum.hs deleted file mode 100644 index 58a6c55..0000000 --- a/src/Maestro/Client/Datum.hs +++ /dev/null @@ -1,19 +0,0 @@ -module Maestro.Client.Datum - ( getDatumByHash - ) where - -import Data.Text (Text) -import Maestro.API (_datum) -import Maestro.API.Datum -import Maestro.Client.Core -import Maestro.Client.Env -import Maestro.Types -import Servant.API.Generic -import Servant.Client - -datumClient :: MaestroEnv -> DatumAPI (AsClientT IO) -datumClient = fromServant . _datum . apiV0Client - --- | Get information about the datum from it's hash. -getDatumByHash :: MaestroEnv -> Text -> IO Datum -getDatumByHash = _datumByHash . datumClient diff --git a/src/Maestro/Client/Env.hs b/src/Maestro/Client/Env.hs index c3763b0..6b15c7f 100644 --- a/src/Maestro/Client/Env.hs +++ b/src/Maestro/Client/Env.hs @@ -1,8 +1,9 @@ module Maestro.Client.Env ( - MaestroEnv(..) + MaestroEnv (..) + , MaestroNetwork (..) + , MaestroApiVersion (..) , mkMaestroEnv - , MaestroNetwork(..) ) where import Data.Text (Text) @@ -13,20 +14,40 @@ import qualified Servant.Client as Servant type MaestroToken = Text -data MaestroEnv = MaestroEnv +data MaestroApiVersion = V0 | V1 + +instance Show MaestroApiVersion where + show V0 = "v0" + show V1 = "v1" + +data SingMaestroApiVersion (v :: MaestroApiVersion) where + SingV0 :: SingMaestroApiVersion 'V0 + SingV1 :: SingMaestroApiVersion 'V1 + +fromSingMaestroApiVersion :: SingMaestroApiVersion v -> MaestroApiVersion +fromSingMaestroApiVersion SingV0 = V0 +fromSingMaestroApiVersion SingV1 = V1 + +class SingMaestroApiVersionI (v :: MaestroApiVersion) + where singMaestroApiVersion :: SingMaestroApiVersion v + +instance SingMaestroApiVersionI 'V0 where singMaestroApiVersion = SingV0 +instance SingMaestroApiVersionI 'V1 where singMaestroApiVersion = SingV1 + +data MaestroEnv (v :: MaestroApiVersion) = MaestroEnv { _maeClientEnv :: !Servant.ClientEnv , _maeToken :: !MaestroToken } data MaestroNetwork = Mainnet | Preprod -maestroBaseUrl :: MaestroNetwork -> String -maestroBaseUrl Preprod = "https://preprod.gomaestro-api.org/v0" -maestroBaseUrl Mainnet = "https://mainnet.gomaestro-api.org/v0" +maestroBaseUrl :: MaestroNetwork -> MaestroApiVersion -> String +maestroBaseUrl Preprod v = "https://preprod.gomaestro-api.org/" <> show v +maestroBaseUrl Mainnet v = "https://mainnet.gomaestro-api.org/" <> show v -mkMaestroEnv :: MaestroToken -> MaestroNetwork -> IO MaestroEnv +mkMaestroEnv :: forall (apiVersion :: MaestroApiVersion). SingMaestroApiVersionI apiVersion => MaestroToken -> MaestroNetwork -> IO (MaestroEnv apiVersion) mkMaestroEnv token nid = do - clientEnv <- servantClientEnv $ maestroBaseUrl nid + clientEnv <- servantClientEnv $ maestroBaseUrl nid (fromSingMaestroApiVersion $ singMaestroApiVersion @apiVersion) pure $ MaestroEnv { _maeClientEnv = clientEnv, _maeToken = token } servantClientEnv :: String -> IO Servant.ClientEnv diff --git a/src/Maestro/Client/Core.hs b/src/Maestro/Client/Error.hs similarity index 71% rename from src/Maestro/Client/Core.hs rename to src/Maestro/Client/Error.hs index 4f7ea44..08c4502 100644 --- a/src/Maestro/Client/Core.hs +++ b/src/Maestro/Client/Error.hs @@ -1,26 +1,19 @@ {-# LANGUAGE DeriveAnyClass #-} -module Maestro.Client.Core +module Maestro.Client.Error ( ApiError (..) - , apiV0Client , MaestroError (..) , fromServantClientError - , module Maestro.Client.Core.Pagination ) where -import Control.Exception (Exception, throwIO) -import Data.Aeson (decode) -import Data.Text (Text) +import Control.Exception (Exception) +import Data.Aeson (decode) +import Data.Text (Text) import Deriving.Aeson -import Maestro.API -import Maestro.Client.Core.Pagination -import Maestro.Client.Env -import Maestro.Types.Common (LowerFirst) +import Maestro.Types.V0.Common (LowerFirst) import Network.HTTP.Types -import Servant.API.Generic (fromServant) import Servant.Client -import Servant.Client.Generic -- | In cases of failure, at times, Maestro returns a JSON object with an error message. data ApiError = ApiError @@ -82,9 +75,3 @@ fromServantClientError e = case e of case decode body of Just (m :: Text) -> m Nothing -> mempty - -apiV0ClientAuth :: MaestroEnv -> MaestroApiV0Auth (AsClientT IO) -apiV0ClientAuth MaestroEnv{..} = genericClientHoist $ \x -> runClientM x _maeClientEnv >>= either (throwIO . fromServantClientError) pure - -apiV0Client :: MaestroEnv -> MaestroApiV0 (AsClientT IO) -apiV0Client mEnv@MaestroEnv {..} = fromServant $ _apiV0 (apiV0ClientAuth mEnv) _maeToken diff --git a/src/Maestro/Client/Scripts.hs b/src/Maestro/Client/Scripts.hs deleted file mode 100644 index 6bd2f38..0000000 --- a/src/Maestro/Client/Scripts.hs +++ /dev/null @@ -1,19 +0,0 @@ -module Maestro.Client.Scripts - ( getScriptByHash - ) where - -import Data.Text (Text) -import Maestro.API (_scripts) -import Maestro.API.Scripts -import Maestro.Client.Core -import Maestro.Client.Env -import Maestro.Types -import Servant.API.Generic -import Servant.Client - -scriptsClient :: MaestroEnv -> ScriptsAPI (AsClientT IO) -scriptsClient = fromServant . _scripts . apiV0Client - --- | Get information about the script from it's hash. -getScriptByHash :: MaestroEnv -> Text -> IO Script -getScriptByHash = _scriptByHash . scriptsClient diff --git a/src/Maestro/Client/V0.hs b/src/Maestro/Client/V0.hs new file mode 100644 index 0000000..a5239ca --- /dev/null +++ b/src/Maestro/Client/V0.hs @@ -0,0 +1,30 @@ +module Maestro.Client.V0 + ( module Maestro.Client.Env + , module Maestro.Client.Error + , module Maestro.Client.V0.Accounts + , module Maestro.Client.V0.Address + , module Maestro.Client.V0.Assets + , module Maestro.Client.V0.Core + , module Maestro.Client.V0.Datum + , module Maestro.Client.V0.Epochs + , module Maestro.Client.V0.General + , module Maestro.Client.V0.Pools + , module Maestro.Client.V0.Scripts + , module Maestro.Client.V0.Transaction + , module Maestro.Client.V0.TxManager + ) where + + +import Maestro.Client.Env +import Maestro.Client.Error +import Maestro.Client.V0.Accounts +import Maestro.Client.V0.Address +import Maestro.Client.V0.Assets +import Maestro.Client.V0.Core +import Maestro.Client.V0.Datum +import Maestro.Client.V0.Epochs +import Maestro.Client.V0.General +import Maestro.Client.V0.Pools +import Maestro.Client.V0.Scripts +import Maestro.Client.V0.Transaction +import Maestro.Client.V0.TxManager diff --git a/src/Maestro/Client/V0/Accounts.hs b/src/Maestro/Client/V0/Accounts.hs new file mode 100644 index 0000000..bf40793 --- /dev/null +++ b/src/Maestro/Client/V0/Accounts.hs @@ -0,0 +1,31 @@ +module Maestro.Client.V0.Accounts where + +import Data.Text (Text) +import Maestro.API.V0 +import Maestro.API.V0.Accounts +import Maestro.Client.Env +import Maestro.Client.V0.Core +import Maestro.Types.V0 +import Servant.API.Generic +import Servant.Client + +accountsClient :: MaestroEnv 'V0 -> AccountsAPI (AsClientT IO) +accountsClient = fromServant . _accounts . apiV0Client + +getAccount :: MaestroEnv 'V0 -> Text -> IO AccountInfo +getAccount = _account . accountsClient + +listAccountAddresses :: MaestroEnv 'V0 -> Text -> Page -> IO [Text] +listAccountAddresses = _accountAddresses . accountsClient + +listAccountAssets :: MaestroEnv 'V0 -> Text -> Page -> IO [Asset] +listAccountAssets = _accountAssets . accountsClient + +listAccountHistory :: MaestroEnv 'V0 -> Text -> Maybe EpochNo -> Page -> IO [AccountHistory] +listAccountHistory = _accountsHistory . accountsClient + +listAccountRewards :: MaestroEnv 'V0 -> Text -> Page -> IO [AccountReward] +listAccountRewards = _accountsReward . accountsClient + +listAccountUpdates :: MaestroEnv 'V0 -> Text -> Page -> IO [AccountUpdate] +listAccountUpdates = _accountsUpdates . accountsClient diff --git a/src/Maestro/Client/Address.hs b/src/Maestro/Client/V0/Address.hs similarity index 79% rename from src/Maestro/Client/Address.hs rename to src/Maestro/Client/V0/Address.hs index d1683b8..926d29e 100644 --- a/src/Maestro/Client/Address.hs +++ b/src/Maestro/Client/V0/Address.hs @@ -1,23 +1,22 @@ -module Maestro.Client.Address where +module Maestro.Client.V0.Address where -import Data.Text (Text) -import Maestro.API -import Maestro.API.Address -import Maestro.Client.Core +import Data.Text (Text) +import Maestro.API.V0 +import Maestro.API.V0.Address import Maestro.Client.Env -import Maestro.Types.Address -import Maestro.Types.Common (Utxo) +import Maestro.Client.V0.Core +import Maestro.Types.V0 import Servant.API.Generic import Servant.Client -addressClient :: MaestroEnv -> AddressAPI (AsClientT IO) +addressClient :: MaestroEnv 'V0 -> AddressAPI (AsClientT IO) addressClient = fromServant . _address . apiV0Client -- | -- Returns list of utxos for multiple addresses utxosAtMultiAddresses :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Query param to include the corresponding datums for datum hashes Maybe Bool -> -- | Query Param to include the CBOR encodings of the transaction outputs in the response @@ -32,7 +31,7 @@ utxosAtMultiAddresses = _addressesUtxos . addressClient -- | -- Returns list of utxo for specific address utxosAtAddress :: - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Address in bech32 format Text -> -- | Query param to include the corresponding datums for datum hashes @@ -47,7 +46,7 @@ utxosAtAddress = _addressUtxo . addressClient -- | -- Returns list of utxo ref for address getRefsAtAddress :: - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Address in bech32 format Text -> -- | The pagination attributes @@ -58,7 +57,7 @@ getRefsAtAddress = _addressUtxoRefs . addressClient -- | -- Get the transaction count for an address getTxCountForAddress :: - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Address in bech32 format Text -> IO [AddressTxCount] diff --git a/src/Maestro/Client/Assets.hs b/src/Maestro/Client/V0/Assets.hs similarity index 86% rename from src/Maestro/Client/Assets.hs rename to src/Maestro/Client/V0/Assets.hs index 9c7d616..44af505 100644 --- a/src/Maestro/Client/Assets.hs +++ b/src/Maestro/Client/V0/Assets.hs @@ -1,23 +1,22 @@ -module Maestro.Client.Assets where +module Maestro.Client.V0.Assets where -import Data.Text (Text) -import Maestro.API (_assets) -import Maestro.API.Assets -import Maestro.Client.Core +import Data.Text (Text) +import Maestro.API.V0 (_assets) +import Maestro.API.V0.Assets import Maestro.Client.Env -import Maestro.Types.Assets -import Maestro.Types.Common +import Maestro.Client.V0.Core +import Maestro.Types.V0 import Servant.API.Generic import Servant.Client -assetClient :: MaestroEnv -> AssetsAPI (AsClientT IO) +assetClient :: MaestroEnv 'V0 -> AssetsAPI (AsClientT IO) assetClient = fromServant . _assets . apiV0Client -- | -- Returns list of Information about the assets of the given policy ID listAssetInfoByPolicyId :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Hex encoded policy ID PolicyId -> -- | Pagination @@ -29,7 +28,7 @@ listAssetInfoByPolicyId = _assetPolicyInfo . assetClient -- Returns a list of addresses which holding some of an asset of the given policy ID listAssetAddressByPolicyId :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Hex encoded policy ID PolicyId -> -- | Pagination @@ -41,7 +40,7 @@ listAssetAddressByPolicyId = _assetPolicyAddress . assetClient -- Returns list of transactions in which an address receives an asset of the specified policy listTxByPolicyId :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Hex encoded policy ID PolicyId -> -- | Pagination @@ -53,7 +52,7 @@ listTxByPolicyId = _assetPolicyTxs . assetClient -- Returns UTxOs which contain assets of the given policy ID, with the asset names and amounts listUtxosByPolicyId :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | The Hex encoded policy ID PolicyId -> -- | Pagination @@ -65,7 +64,7 @@ listUtxosByPolicyId = _assetPolicyUtxos . assetClient -- Returns UTxOs which contain assets of the given policy ID, with the asset names and amounts getAssetDetail :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Asset, encoded as concatenation of hex of policy ID and asset name AssetId -> IO AssetInfo @@ -75,7 +74,7 @@ getAssetDetail = _assetDetail . assetClient -- Returns a list of addresses which hold some amount of the specified asset listAssetAddresses :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Asset, encoded as concatenation of hex of policy ID and asset name AssetId -> Page -> @@ -86,7 +85,7 @@ listAssetAddresses = _assetAddresses . assetClient -- Returns list of transactions in which an address receives an asset of the specified policy listAssetTx :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Asset, encoded as concatenation of hex of policy ID and asset name AssetId -> -- | Return only transactions after supplied block height @@ -101,7 +100,7 @@ listAssetTx = _assetTxs . assetClient -- Returns list of transactions which minted or burned the specified asset listAssetUpdates :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Asset, encoded as concatenation of hex of policy ID and asset name AssetId -> -- | The Pagination @@ -115,7 +114,7 @@ listAssetUpdates = _assetUpdates . assetClient -- Returns UTxOs containing the specified asset, each paired with the amount of the asset listAssetUtxos :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Asset, encoded as concatenation of hex of policy ID and asset name AssetId -> -- | The Pagination diff --git a/src/Maestro/Client/V0/Core.hs b/src/Maestro/Client/V0/Core.hs new file mode 100644 index 0000000..621ca52 --- /dev/null +++ b/src/Maestro/Client/V0/Core.hs @@ -0,0 +1,20 @@ +module Maestro.Client.V0.Core + ( apiV0Client + , module Maestro.Client.V0.Core.Pagination + ) where + +import Control.Exception (throwIO) +import Maestro.API.V0 +import Maestro.Client.Env +import Maestro.Client.Error (fromServantClientError) +import Maestro.Client.V0.Core.Pagination +import Servant.API.Generic (fromServant) +import Servant.Client +import Servant.Client.Generic + + +apiV0ClientAuth :: MaestroEnv 'V0 -> MaestroApiV0Auth (AsClientT IO) +apiV0ClientAuth MaestroEnv {..} = genericClientHoist $ \x -> runClientM x _maeClientEnv >>= either (throwIO . fromServantClientError) pure + +apiV0Client :: MaestroEnv 'V0 -> MaestroApiV0 (AsClientT IO) +apiV0Client mEnv@MaestroEnv {..} = fromServant $ _apiV0 (apiV0ClientAuth mEnv) _maeToken diff --git a/src/Maestro/Client/V0/Core/Pagination.hs b/src/Maestro/Client/V0/Core/Pagination.hs new file mode 100644 index 0000000..d9a2100 --- /dev/null +++ b/src/Maestro/Client/V0/Core/Pagination.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} + +module Maestro.Client.V0.Core.Pagination where + +import Data.Default.Class +import Data.Proxy (Proxy (..)) +import Servant.API (QueryParam, (:>)) +import Servant.Client.Core (Client, HasClient, clientWithRoute, + hoistClientMonad) + +-- | Pagination parameters +data Page = Page + { resultPerPage :: !Int -- ^ Total result per page + , pageNumber :: !Int -- ^ Page Number + } + +-- | maximum number of result +maxPageResult :: Int +maxPageResult = 100 + +instance Default Page where + def = Page maxPageResult 1 + +page :: Int -> Page +page n + | n >= 1 = Page maxPageResult n + | otherwise = error "Page number not in range [1..]" + +-- Utility for querying all results from a paged endpoint. +allPages :: (Monad m, Foldable t, Monoid (t a)) => (Page -> m (t a)) -> m (t a) +allPages act = fetch 1 + where + fetch pageNo = do + xs <- act $ Page maxPageResult pageNo + if length xs < maxPageResult then + pure xs + else do + next <- fetch $ pageNo + 1 + pure $ xs <> next -- Note: In case of list, concatenation takes linear time in the number of elements of the first list, thus, `xs` should come before. + +data Pagination + +type PaginationApi api = + QueryParam "count" Int + :> QueryParam "page" Int + :> api + +instance HasClient m api => HasClient m (Pagination :> api) where + type Client m (Pagination :> api) = Page -> Client m api + + clientWithRoute pm _ req Page{..} = + clientWithRoute + pm + (Proxy @(PaginationApi api)) + req + (Just resultPerPage) + (Just pageNumber) + + hoistClientMonad pm _pa hst subClient = hoistClientMonad pm (Proxy @api) hst . subClient diff --git a/src/Maestro/Client/V0/Datum.hs b/src/Maestro/Client/V0/Datum.hs new file mode 100644 index 0000000..90c002d --- /dev/null +++ b/src/Maestro/Client/V0/Datum.hs @@ -0,0 +1,19 @@ +module Maestro.Client.V0.Datum + ( getDatumByHash + ) where + +import Data.Text (Text) +import Maestro.API.V0 (_datum) +import Maestro.API.V0.Datum +import Maestro.Client.Env +import Maestro.Client.V0.Core +import Maestro.Types.V0 +import Servant.API.Generic +import Servant.Client + +datumClient :: MaestroEnv 'V0 -> DatumAPI (AsClientT IO) +datumClient = fromServant . _datum . apiV0Client + +-- | Get information about the datum from it's hash. +getDatumByHash :: MaestroEnv 'V0 -> Text -> IO Datum +getDatumByHash = _datumByHash . datumClient diff --git a/src/Maestro/Client/Epochs.hs b/src/Maestro/Client/V0/Epochs.hs similarity index 51% rename from src/Maestro/Client/Epochs.hs rename to src/Maestro/Client/V0/Epochs.hs index d3a07b6..347aaf1 100644 --- a/src/Maestro/Client/Epochs.hs +++ b/src/Maestro/Client/V0/Epochs.hs @@ -1,23 +1,23 @@ -module Maestro.Client.Epochs +module Maestro.Client.V0.Epochs ( getCurrentEpoch , getEpochInfo ) where -import Maestro.API (_epochs) -import Maestro.API.Epochs -import Maestro.Client.Core +import Maestro.API.V0 (_epochs) +import Maestro.API.V0.Epochs import Maestro.Client.Env -import Maestro.Types +import Maestro.Client.V0.Core +import Maestro.Types.V0 import Servant.API.Generic import Servant.Client -epochsClient :: MaestroEnv -> EpochsAPI (AsClientT IO) +epochsClient :: MaestroEnv 'V0 -> EpochsAPI (AsClientT IO) epochsClient = fromServant . _epochs . apiV0Client -- | Get information about the current epoch. -getCurrentEpoch :: MaestroEnv -> IO CurrentEpochInfo +getCurrentEpoch :: MaestroEnv 'V0 -> IO CurrentEpochInfo getCurrentEpoch = _currentEpochInfo . epochsClient -- | Get information about a specific epoch. -getEpochInfo :: MaestroEnv -> EpochNo -> IO EpochInfo +getEpochInfo :: MaestroEnv 'V0 -> EpochNo -> IO EpochInfo getEpochInfo = _epochInfo . epochsClient diff --git a/src/Maestro/Client/General.hs b/src/Maestro/Client/V0/General.hs similarity index 56% rename from src/Maestro/Client/General.hs rename to src/Maestro/Client/V0/General.hs index 30103c3..dfb280c 100644 --- a/src/Maestro/Client/General.hs +++ b/src/Maestro/Client/V0/General.hs @@ -1,33 +1,33 @@ -module Maestro.Client.General +module Maestro.Client.V0.General ( getChainTip , getSystemStart , getEraHistory , getProtocolParameters ) where -import Maestro.API (_general) -import Maestro.API.General -import Maestro.Client.Core +import Maestro.API.V0 (_general) +import Maestro.API.V0.General import Maestro.Client.Env -import Maestro.Types.General +import Maestro.Client.V0.Core +import Maestro.Types.V0 import Servant.API.Generic import Servant.Client -generalClient :: MaestroEnv -> GeneralAPI (AsClientT IO) +generalClient :: MaestroEnv 'V0 -> GeneralAPI (AsClientT IO) generalClient = fromServant . _general . apiV0Client -- | Get details about the latest block of the network. -getChainTip :: MaestroEnv -> IO ChainTip +getChainTip :: MaestroEnv 'V0 -> IO ChainTip getChainTip = _chainTip . generalClient -- | Get network start time since genesis. -getSystemStart :: MaestroEnv -> IO SystemStart +getSystemStart :: MaestroEnv 'V0 -> IO SystemStart getSystemStart = _systemStart . generalClient -- | Get network era history. -getEraHistory :: MaestroEnv -> IO [EraSummary] +getEraHistory :: MaestroEnv 'V0 -> IO [EraSummary] getEraHistory = _eraHistory . generalClient -- | Get protocol parameters for the latest epoch. -getProtocolParameters :: MaestroEnv -> IO ProtocolParameters +getProtocolParameters :: MaestroEnv 'V0 -> IO ProtocolParameters getProtocolParameters = _protocolParams . generalClient diff --git a/src/Maestro/Client/Core/Pagination.hs b/src/Maestro/Client/V0/Pagination.hs similarity index 97% rename from src/Maestro/Client/Core/Pagination.hs rename to src/Maestro/Client/V0/Pagination.hs index aeffc0f..2a572a1 100644 --- a/src/Maestro/Client/Core/Pagination.hs +++ b/src/Maestro/Client/V0/Pagination.hs @@ -5,7 +5,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -module Maestro.Client.Core.Pagination where +module Maestro.Client.V0.Pagination where import Data.Default.Class import Data.Proxy (Proxy (..)) diff --git a/src/Maestro/Client/Pools.hs b/src/Maestro/Client/V0/Pools.hs similarity index 56% rename from src/Maestro/Client/Pools.hs rename to src/Maestro/Client/V0/Pools.hs index d6f0057..355a139 100644 --- a/src/Maestro/Client/Pools.hs +++ b/src/Maestro/Client/V0/Pools.hs @@ -1,4 +1,4 @@ -module Maestro.Client.Pools +module Maestro.Client.V0.Pools ( listPools, poolBlocks, poolDelegators, @@ -10,47 +10,46 @@ module Maestro.Client.Pools ) where -import Maestro.API -import Maestro.API.Pool -import Maestro.Client.Core +import Maestro.API.V0 +import Maestro.API.V0.Pool import Maestro.Client.Env -import Maestro.Types.Common -import Maestro.Types.Pool +import Maestro.Client.V0.Core +import Maestro.Types.V0 import Servant.API.Generic import Servant.Client -poolsClient :: MaestroEnv -> PoolAPI (AsClientT IO) +poolsClient :: MaestroEnv 'V0 -> PoolAPI (AsClientT IO) poolsClient = fromServant . _pools . apiV0Client -- | Returns a list of currently registered stake pools -listPools :: MaestroEnv -> Page -> IO [PoolListInfo] +listPools :: MaestroEnv 'V0 -> Page -> IO [PoolListInfo] listPools = _listPools . poolsClient -- | Return information about blocks minted by a given pool for all epochs -- (or just epoch `epoch_no` if provided) -poolBlocks :: MaestroEnv -> Bech32StringOf PoolId -> Page -> Maybe EpochNo -> Maybe Order -> IO [PoolBlock] +poolBlocks :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> Page -> Maybe EpochNo -> Maybe Order -> IO [PoolBlock] poolBlocks = _poolBlocks . poolsClient -poolDelegators :: MaestroEnv -> Bech32StringOf PoolId -> Page -> IO [DelegatorInfo] +poolDelegators :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> Page -> IO [DelegatorInfo] poolDelegators = _poolDelegators . poolsClient -- | Returns per-epoch information about the specified pool -- (or just epoch `epoch_no` if provided) -poolHistory :: MaestroEnv -> Bech32StringOf PoolId -> Page -> Maybe EpochNo -> Maybe Order -> IO [PoolHistory] +poolHistory :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> Page -> Maybe EpochNo -> Maybe Order -> IO [PoolHistory] poolHistory = _poolHistory . poolsClient -- | Returns current information about the specified pool -poolInfo :: MaestroEnv -> Bech32StringOf PoolId -> IO PoolInfo +poolInfo :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> IO PoolInfo poolInfo = _poolInfo . poolsClient -- | Returns the metadata declared by a specific pool -poolMetadata :: MaestroEnv -> Bech32StringOf PoolId -> IO PoolMetadata +poolMetadata :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> IO PoolMetadata poolMetadata = _poolMetadata . poolsClient -- | Returns a list of relays declared by the specified pool -poolRelays :: MaestroEnv -> Bech32StringOf PoolId -> IO [PoolRelay] +poolRelays :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> IO [PoolRelay] poolRelays = _poolRelays . poolsClient -- | Returns a list of updates relating to the specified pool -poolUpdates :: MaestroEnv -> Bech32StringOf PoolId -> IO [PoolUpdate] +poolUpdates :: MaestroEnv 'V0 -> Bech32StringOf PoolId -> IO [PoolUpdate] poolUpdates = _poolUpdates . poolsClient diff --git a/src/Maestro/Client/V0/Scripts.hs b/src/Maestro/Client/V0/Scripts.hs new file mode 100644 index 0000000..c7455a4 --- /dev/null +++ b/src/Maestro/Client/V0/Scripts.hs @@ -0,0 +1,19 @@ +module Maestro.Client.V0.Scripts + ( getScriptByHash + ) where + +import Data.Text (Text) +import Maestro.API.V0 (_scripts) +import Maestro.API.V0.Scripts +import Maestro.Client.Env +import Maestro.Client.V0.Core +import Maestro.Types.V0 +import Servant.API.Generic +import Servant.Client + +scriptsClient :: MaestroEnv 'V0 -> ScriptsAPI (AsClientT IO) +scriptsClient = fromServant . _scripts . apiV0Client + +-- | Get information about the script from it's hash. +getScriptByHash :: MaestroEnv 'V0 -> Text -> IO Script +getScriptByHash = _scriptByHash . scriptsClient diff --git a/src/Maestro/Client/Transaction.hs b/src/Maestro/Client/V0/Transaction.hs similarity index 75% rename from src/Maestro/Client/Transaction.hs rename to src/Maestro/Client/V0/Transaction.hs index 4fe43a3..02b5329 100644 --- a/src/Maestro/Client/Transaction.hs +++ b/src/Maestro/Client/V0/Transaction.hs @@ -1,4 +1,4 @@ -module Maestro.Client.Transaction +module Maestro.Client.V0.Transaction ( submitTx, txCbor, txAddress, @@ -6,17 +6,17 @@ module Maestro.Client.Transaction ) where -import qualified Data.ByteString as BS -import Data.Text (Text) -import Maestro.API (_tx) -import Maestro.API.Transaction -import Maestro.Client.Core +import qualified Data.ByteString as BS +import Data.Text (Text) +import Maestro.API.V0 (_tx) +import Maestro.API.V0.Transaction import Maestro.Client.Env -import Maestro.Types.Common +import Maestro.Client.V0.Core +import Maestro.Types.V0 import Servant.API.Generic import Servant.Client -txClient :: MaestroEnv -> TxAPI (AsClientT IO) +txClient :: MaestroEnv 'V0 -> TxAPI (AsClientT IO) txClient = fromServant . _tx . apiV0Client -- | @@ -24,7 +24,7 @@ txClient = fromServant . _tx . apiV0Client -- Interaction with this endpoint is identical to IOG's Cardano Submit API and will not be monitored by Maestro. submitTx :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | CBOR encoded Transaction BS.ByteString -> IO Text @@ -34,7 +34,7 @@ submitTx = _submitTx . txClient -- Returns hex-encoded CBOR bytes of a transaction txCbor :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Hex Encoded Transaction Hash HashStringOf Tx -> -- | hex-encoded CBOR bytes of a transaction @@ -45,7 +45,7 @@ txCbor = _txCborApi . txClient -- Returns the address specified in the given transaction output txAddress :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | Hex Encoded Transaction Hash HashStringOf Tx -> -- | The Transaction Output Index @@ -56,8 +56,8 @@ txAddress = _txAddressApi . txClient -- | -- Returns the specified transaction output of a transaction output reference txUtxo :: - -- | The MaestroEnv - MaestroEnv -> + -- | The Maestro Environment. + MaestroEnv 'V0 -> -- | Hex encoded transaction hash HashStringOf Tx -> -- | The Transaction Output Index diff --git a/src/Maestro/Client/TxManager.hs b/src/Maestro/Client/V0/TxManager.hs similarity index 58% rename from src/Maestro/Client/TxManager.hs rename to src/Maestro/Client/V0/TxManager.hs index f0a76a7..171ab5b 100644 --- a/src/Maestro/Client/TxManager.hs +++ b/src/Maestro/Client/V0/TxManager.hs @@ -1,18 +1,18 @@ -module Maestro.Client.TxManager +module Maestro.Client.V0.TxManager ( submitAndMonitorTx ) where -import qualified Data.ByteString as BS -import Data.Text (Text) -import Maestro.API (_txManager) -import Maestro.API.TxManager -import Maestro.Client.Core +import qualified Data.ByteString as BS +import Data.Text (Text) +import Maestro.API.V0 (_txManager) +import Maestro.API.V0.TxManager import Maestro.Client.Env +import Maestro.Client.V0.Core import Servant.API.Generic import Servant.Client -txClient :: MaestroEnv -> TxManagerAPI (AsClientT IO) +txClient :: MaestroEnv 'V0 -> TxManagerAPI (AsClientT IO) txClient = fromServant . _txManager . apiV0Client -- | @@ -20,7 +20,7 @@ txClient = fromServant . _txManager . apiV0Client -- A transaction submited with this endpoint will be monitored by Maestro. submitAndMonitorTx :: -- | The Maestro Environment - MaestroEnv -> + MaestroEnv 'V0 -> -- | CBOR encoded Transaction BS.ByteString -> IO Text diff --git a/src/Maestro/Client/V1.hs b/src/Maestro/Client/V1.hs new file mode 100644 index 0000000..265cc3d --- /dev/null +++ b/src/Maestro/Client/V1.hs @@ -0,0 +1,22 @@ +module Maestro.Client.V1 + ( module Maestro.Client.Env + , module Maestro.Client.Error + , module Maestro.Client.V1.Core + , module Maestro.Client.V1.Addresses + , module Maestro.Client.V1.Datum + , module Maestro.Client.V1.General + , module Maestro.Client.V1.Pools + , module Maestro.Client.V1.Transactions + , module Maestro.Client.V1.TxManager + ) where + + +import Maestro.Client.Env +import Maestro.Client.Error +import Maestro.Client.V1.Addresses +import Maestro.Client.V1.Core +import Maestro.Client.V1.Datum +import Maestro.Client.V1.General +import Maestro.Client.V1.Pools +import Maestro.Client.V1.Transactions +import Maestro.Client.V1.TxManager diff --git a/src/Maestro/Client/V1/Addresses.hs b/src/Maestro/Client/V1/Addresses.hs new file mode 100644 index 0000000..346618f --- /dev/null +++ b/src/Maestro/Client/V1/Addresses.hs @@ -0,0 +1,44 @@ +-- | Module to query for /"addresses"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/addresses). + +module Maestro.Client.V1.Addresses ( + utxosAtMultiAddresses, + getRefsAtAddress, + ) where + +import Maestro.API.V1 +import Maestro.API.V1.Addresses +import Maestro.Client.Env +import Maestro.Client.V1.Core +import Maestro.Types.Common (Address, Bech32StringOf) +import Maestro.Types.V1 (PaginatedOutputReferenceObject, + PaginatedUtxoWithSlot) +import Servant.API.Generic +import Servant.Client + +addressClient :: MaestroEnv 'V1 -> AddressesAPI (AsClientT IO) +addressClient = fromServant . _addresses . apiV1Client + +-- | Returns list of utxos for multiple addresses. +utxosAtMultiAddresses :: + -- | The Maestro Environment. + MaestroEnv 'V1 -> + -- | Query param to include the corresponding datums for datum hashes. + Maybe Bool -> + -- | Query Param to include the CBOR encodings of the transaction outputs in the response. + Maybe Bool -> + -- | The pagination attributes. + Cursor -> + -- | List of Address in bech32 format to fetch utxo from. + [Bech32StringOf Address] -> + IO PaginatedUtxoWithSlot +utxosAtMultiAddresses = _addressesUtxos . addressClient + +-- | UTxO IDs for all the unspent transaction outputs at an address. +getRefsAtAddress :: + MaestroEnv 'V1 -> + -- | The Address in Bech32 format. + Bech32StringOf Address -> + -- | The pagination attributes. + Cursor -> + IO PaginatedOutputReferenceObject +getRefsAtAddress = _addressUtxoRefs . addressClient diff --git a/src/Maestro/Client/V1/Core.hs b/src/Maestro/Client/V1/Core.hs new file mode 100644 index 0000000..266d1f6 --- /dev/null +++ b/src/Maestro/Client/V1/Core.hs @@ -0,0 +1,19 @@ +module Maestro.Client.V1.Core + ( apiV1Client + , module Maestro.Client.V1.Core.Pagination + ) where + +import Control.Exception (throwIO) +import Maestro.API.V1 +import Maestro.Client.Env +import Maestro.Client.Error (fromServantClientError) +import Maestro.Client.V1.Core.Pagination +import Servant.API.Generic (fromServant) +import Servant.Client +import Servant.Client.Generic + +apiV1ClientAuth :: MaestroEnv 'V1 -> MaestroApiV1Auth (AsClientT IO) +apiV1ClientAuth MaestroEnv{..} = genericClientHoist $ \x -> runClientM x _maeClientEnv >>= either (throwIO . fromServantClientError) pure + +apiV1Client :: MaestroEnv 'V1 -> MaestroApiV1 (AsClientT IO) +apiV1Client mEnv@MaestroEnv {..} = fromServant $ _apiV1 (apiV1ClientAuth mEnv) _maeToken diff --git a/src/Maestro/Client/V1/Core/Pagination.hs b/src/Maestro/Client/V1/Core/Pagination.hs new file mode 100644 index 0000000..6d918c4 --- /dev/null +++ b/src/Maestro/Client/V1/Core/Pagination.hs @@ -0,0 +1,59 @@ +module Maestro.Client.V1.Core.Pagination where + +import Data.Default.Class +import Data.Maybe (isNothing) +import Data.Proxy (Proxy (..)) +import Maestro.Types.V1.Common (IsTimestamped (getTimestampedData), + TimestampedData) +import Maestro.Types.V1.Common.Pagination +import Servant.API (QueryParam, (:>)) +import Servant.Client.Core (Client, HasClient, + clientWithRoute, + hoistClientMonad) + +-- | Pagination parameters. +data Cursor = Cursor + { resultPerPage :: !Int -- ^ Total result to have per page. + , cursor :: !(Maybe NextCursor) -- ^ Cursor. + } + +-- | Maximum number of result per page. +maxResultsPerPage :: Int +maxResultsPerPage = 100 + +instance Default Cursor where + def = Cursor maxResultsPerPage Nothing + +-- Utility for querying all results from a paged endpoint. +allPages :: (Monad m, HasCursor a) => (Cursor -> m a) -> m (TimestampedData a) +allPages act = fetch Nothing + where + fetch cursor = do + xs <- act $ Cursor maxResultsPerPage cursor + let nextCursor = getNextCursor xs + cursorData = getTimestampedData xs + if isNothing nextCursor then + pure cursorData + else do + next <- fetch nextCursor + pure $ cursorData <> next -- Note: In case of list, concatenation takes linear time in the number of elements of the first list, thus, `cursorData` should come before. + +data Pagination + +type PaginationApi api = + QueryParam "count" Int + :> QueryParam "cursor" NextCursor + :> api + +instance HasClient m api => HasClient m (Pagination :> api) where + type Client m (Pagination :> api) = Cursor -> Client m api + + clientWithRoute pm _ req Cursor {..} = + clientWithRoute + pm + (Proxy @(PaginationApi api)) + req + (Just resultPerPage) + cursor + + hoistClientMonad pm _pa hst subClient = hoistClientMonad pm (Proxy @api) hst . subClient diff --git a/src/Maestro/Client/V1/Datum.hs b/src/Maestro/Client/V1/Datum.hs new file mode 100644 index 0000000..b190d21 --- /dev/null +++ b/src/Maestro/Client/V1/Datum.hs @@ -0,0 +1,20 @@ +-- | Module to query for /"datum"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/datum). + +module Maestro.Client.V1.Datum + ( getDatumByHash + ) where + +import Maestro.API.V1 (_datum) +import Maestro.API.V1.Datum +import Maestro.Client.Env +import Maestro.Client.V1.Core +import Maestro.Types.V1 +import Servant.API.Generic +import Servant.Client + +datumClient :: MaestroEnv 'V1 -> DatumAPI (AsClientT IO) +datumClient = fromServant . _datum . apiV1Client + +-- | Get information about the datum from it's hash. +getDatumByHash :: MaestroEnv 'V1 -> HexStringOf DatumHash -> IO TimestampedDatum +getDatumByHash = _datumByHash . datumClient diff --git a/src/Maestro/Client/V1/General.hs b/src/Maestro/Client/V1/General.hs new file mode 100644 index 0000000..4a90b89 --- /dev/null +++ b/src/Maestro/Client/V1/General.hs @@ -0,0 +1,35 @@ +-- | Module to query for /"general"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/general). + +module Maestro.Client.V1.General + ( getChainTip + , getSystemStart + , getEraHistory + , getProtocolParameters + ) where + +import Maestro.API.V1 (_general) +import Maestro.API.V1.General +import Maestro.Client.Env +import Maestro.Client.V1.Core +import Maestro.Types.V1 +import Servant.API.Generic +import Servant.Client + +generalClient :: MaestroEnv 'V1 -> GeneralAPI (AsClientT IO) +generalClient = fromServant . _general . apiV1Client + +-- | Get details about the latest block of the network. +getChainTip :: MaestroEnv 'V1 -> IO TimestampedChainTip +getChainTip = _chainTip . generalClient + +-- | Get network start time since genesis. +getSystemStart :: MaestroEnv 'V1 -> IO TimestampedSystemStart +getSystemStart = _systemStart . generalClient + +-- | Get network era history. +getEraHistory :: MaestroEnv 'V1 -> IO TimestampedEraSummaries +getEraHistory = _eraHistory . generalClient + +-- | Get protocol parameters for the latest epoch. +getProtocolParameters :: MaestroEnv 'V1 -> IO TimestampedProtocolParameters +getProtocolParameters = _protocolParams . generalClient diff --git a/src/Maestro/Client/V1/Pools.hs b/src/Maestro/Client/V1/Pools.hs new file mode 100644 index 0000000..f87fd20 --- /dev/null +++ b/src/Maestro/Client/V1/Pools.hs @@ -0,0 +1,21 @@ +-- | Module to query for /"pools"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/pools). + +module Maestro.Client.V1.Pools + ( listPools, + ) +where + +import Maestro.API.V1 +import Maestro.API.V1.Pools +import Maestro.Client.Env +import Maestro.Client.V1.Core +import Maestro.Types.V1 +import Servant.API.Generic +import Servant.Client + +poolsClient :: MaestroEnv 'V1 -> PoolsAPI (AsClientT IO) +poolsClient = fromServant . _pools . apiV1Client + +-- | Returns a list of currently registered stake pools. +listPools :: MaestroEnv 'V1 -> Cursor -> IO PaginatedPoolListInfo +listPools = _listPools . poolsClient diff --git a/src/Maestro/Client/V1/Transactions.hs b/src/Maestro/Client/V1/Transactions.hs new file mode 100644 index 0000000..96f4e75 --- /dev/null +++ b/src/Maestro/Client/V1/Transactions.hs @@ -0,0 +1,31 @@ +-- | Module to query for /"transactions"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/transactions). + +module Maestro.Client.V1.Transactions + ( outputsByReferences, + ) where + +import Maestro.API.V1 (_transactions) +import Maestro.API.V1.Transactions +import Maestro.Client.Env +import Maestro.Client.V1.Core +import Maestro.Types.V1 +import Servant.API.Generic +import Servant.Client + +txClient :: MaestroEnv 'V1 -> TransactionsAPI (AsClientT IO) +txClient = fromServant . _transactions . apiV1Client + +-- | Returns outputs for given output references. +outputsByReferences :: + -- | The Maestro Environment. + MaestroEnv 'V1 -> + -- | Try find and include the corresponding datums for datum hashes. + Maybe Bool -> + -- | Include the CBOR encodings of the transaction outputs in the response. + Maybe Bool -> + -- | The pagination attributes. + Cursor -> + -- | Output references. + [OutputReference] -> + IO PaginatedUtxo +outputsByReferences = _txOutputs . txClient diff --git a/src/Maestro/Client/V1/TxManager.hs b/src/Maestro/Client/V1/TxManager.hs new file mode 100644 index 0000000..fe82b78 --- /dev/null +++ b/src/Maestro/Client/V1/TxManager.hs @@ -0,0 +1,35 @@ +module Maestro.Client.V1.TxManager + ( submitAndMonitorTx + , turboSubmitAndMonitorTx + ) +where + +import qualified Data.ByteString as BS +import Data.Text (Text) +import Maestro.API.V1 (_txManager) +import Maestro.API.V1.TxManager +import Maestro.Client.Env +import Maestro.Client.V1.Core +import Servant.API.Generic +import Servant.Client + +txClient :: MaestroEnv 'V1 -> TxManagerAPI (AsClientT IO) +txClient = fromServant . _txManager . apiV1Client + +-- | Submit a signed and serialized transaction to the network. A transaction submited with this endpoint will be monitored by Maestro. +submitAndMonitorTx :: + -- | The Maestro Environment. + MaestroEnv 'V1 -> + -- | CBOR encoded Transaction. + BS.ByteString -> + IO Text +submitAndMonitorTx = _monitoredTxSubmit . txClient + +-- | Submit a signed and serialized transaction to the network. A transaction submited with this endpoint will be [turbo submitted & monitored by Maestro](https://docs.gomaestro.org/docs/API%20reference/Transaction%20Manager/tx-manager-turbo-submit). +turboSubmitAndMonitorTx :: + -- | The Maestro Environment. + MaestroEnv 'V1 -> + -- | CBOR encoded Transaction. + BS.ByteString -> + IO Text +turboSubmitAndMonitorTx = _monitoredTurboTxSubmit . txClient diff --git a/src/Maestro/Types.hs b/src/Maestro/Types.hs deleted file mode 100644 index e2fa7d0..0000000 --- a/src/Maestro/Types.hs +++ /dev/null @@ -1,21 +0,0 @@ --- | Maestro types - -module Maestro.Types - ( module Maestro.Types.Accounts - , module Maestro.Types.Address - , module Maestro.Types.Assets - , module Maestro.Types.Datum - , module Maestro.Types.Epochs - , module Maestro.Types.Common - , module Maestro.Types.General - , module Maestro.Types.Pool - ) where - -import Maestro.Types.Accounts -import Maestro.Types.Address -import Maestro.Types.Assets -import Maestro.Types.Common -import Maestro.Types.Datum -import Maestro.Types.Epochs -import Maestro.Types.General -import Maestro.Types.Pool diff --git a/src/Maestro/Types/Common.hs b/src/Maestro/Types/Common.hs index 9cc4db5..163c017 100644 --- a/src/Maestro/Types/Common.hs +++ b/src/Maestro/Types/Common.hs @@ -1,28 +1,31 @@ +-- | Common (shared) types between different versions of Maestro-API. + module Maestro.Types.Common ( Tx, + TxOutCbor, + DatumHash, + Address, TxIndex (..), PolicyId (..), - AssetId (..), - CBORStream, + TokenName (..), EpochNo (..), EpochSize (..), AbsoluteSlot (..), + SlotNo (..), BlockHeight (..), BlockHash (..), TxHash (..), Bech32StringOf (..), - HexStringOf, + HexStringOf (..), HashStringOf (..), DatumOptionType (..), DatumOption (..), ScriptType (..), Script (..), - Asset (..), - Utxo (..), - TxCbor (..), - UtxoAddress (..), Order (..), + CBORStream, LowerFirst, + LowerAll, ) where @@ -42,20 +45,29 @@ import Servant.API -- | Phantom datatype to be used with constructors like `HashStringOf`. data Tx +-- | Phantom datatype to be used with `HexStringOf` to represent hex encoded CBOR bytes of transaction output. +data TxOutCbor + +-- | Phantom datatype to be used with `HexStringOf` to represent hex encoded datum hash. +data DatumHash + +-- | Phantom datatype to be used with, say `Bech32StringOf` to represent Bech32 representation of an address. +data Address + -- | Index of UTxO in a transaction outputs. newtype TxIndex = TxIndex Natural deriving stock (Eq, Ord, Show, Generic) deriving newtype (Num, Enum, Real, Integral, FromHttpApiData, ToHttpApiData, FromJSON, ToJSON) --- | Minting policy ID. +-- | Hex encoded minting policy ID (for non-ada native asset). newtype PolicyId = PolicyId Text - deriving stock (Eq, Show, Generic) + deriving stock (Eq, Ord, Show, Generic) deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) --- | Concatenation of hex encoded policy ID and hex encoded asset name. -newtype AssetId = AssetId Text +-- | Hex encoded token name (for non-ada native asset). +newtype TokenName = TokenName Text deriving stock (Eq, Ord, Show, Generic) - deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON) + deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) -- | An epoch, i.e. the number of the epoch. newtype EpochNo = EpochNo {unEpochNo :: Word64} @@ -75,6 +87,11 @@ newtype AbsoluteSlot = AbsoluteSlot {unAbsoluteSlot :: Natural} deriving stock (Show, Eq, Generic) deriving (FromJSON, ToJSON) +-- | The 0-based index for the Ourboros time slot. +newtype SlotNo = SlotNo {unSlotNo :: Word64} + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (Num, Bounded, Enum, Real, Integral, FromJSON, ToJSON) + -- | Block Height newtype BlockHeight = BlockHeight {unBlockHeight :: Natural} deriving stock (Show, Eq, Ord, Generic) @@ -82,105 +99,88 @@ newtype BlockHeight = BlockHeight {unBlockHeight :: Natural} -- | Hash of the block. newtype BlockHash = BlockHash {unBlockHash :: Text} - deriving stock (Show, Eq, Generic) + deriving stock (Show, Eq, Ord, Generic) deriving (FromJSON, ToJSON) -- | Hash of the Transaction. -newtype TxHash = TxHash {unTxHash :: Text} - deriving stock (Show, Eq, Generic) - deriving newtype (IsString) - deriving (FromJSON, ToJSON) +newtype TxHash = TxHash Text + deriving stock (Show, Eq, Ord, Generic) + deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) +-- | Type to label the string is question is a @Bech32@ representation of the given type @a@. newtype Bech32StringOf a = Bech32StringOf Text - deriving stock (Eq, Show, Generic) + deriving stock (Eq, Ord, Show, Generic) deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) -type HexStringOf a = Text +-- | Type to label the string is question is a hexadecimal representation of the given type @a@. +newtype HexStringOf a = HexStringOf Text + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) +-- | Type to label the string is question is a hash string of the given type @a@, like hash of the transaction body. newtype HashStringOf a = HashStringOf Text - deriving stock (Eq, Show, Generic) + deriving stock (Eq, Ord, Show, Generic) deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) +-- | Datum in output is either inlined or not. data DatumOptionType = Inline | Hash - deriving stock (Show, Eq, Generic) + deriving stock (Show, Eq, Ord, Generic) deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[LowerFirst]] DatumOptionType +-- | Description of datum in an output. If datum is successfully resolved for (when mentioning to resolve for it by giving @resolve_datums@ flag in query parameters) then fields like @_datumOptionBytes@ would have non `Nothing` value even if UTxO just had hash of datum. data DatumOption = DatumOption { _datumOptionBytes :: !(Maybe Text), + -- ^ Hex encoded datum CBOR bytes. _datumOptionHash :: !Text, + -- ^ Hash of the datum. _datumOptionJson :: !(Maybe Aeson.Value), + -- ^ JSON representation of the datum. _datumOptionType :: !DatumOptionType + -- ^ See `DatumOptionType`. } - deriving stock (Show, Eq, Generic) + deriving stock (Show, Eq, Ord, Generic) deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_datumOption", LowerFirst]] DatumOption +-- | Type of script. data ScriptType = Native | PlutusV1 | PlutusV2 - deriving stock (Show, Eq, Generic) + deriving stock (Show, Eq, Ord, Generic) deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[LowerAll]] ScriptType +-- | Type to represent script in an UTxO. data Script = Script { _scriptBytes :: !(Maybe Text), + -- ^ Script bytes (`Nothing` if `Native` script). _scriptHash :: !Text, + -- ^ Hash of script. _scriptJson :: !(Maybe Aeson.Value), + -- ^ JSON representation of script (`Nothing` if not `Native` script). _scriptType :: !ScriptType + -- ^ See `ScriptType`. } - deriving stock (Show, Eq, Generic) + deriving stock (Show, Eq, Ord, Generic) deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_script", LowerFirst]] Script -data Asset = Asset - { _assetQuantity :: !Integer - , _assetUnit :: !Text - } - deriving stock (Show, Eq, Generic) - deriving - (FromJSON, ToJSON) - via CustomJSON '[FieldLabelModifier '[StripPrefix "_asset", CamelToSnake]] Asset - --- | Transaction output -data Utxo = Utxo - { _utxoAddress :: !Text, - _utxoAssets :: ![Asset], - _utxoDatum :: !(Maybe DatumOption), - _utxoIndex :: !Natural, - _utxoReferenceScript :: !(Maybe Script), - _utxoTxHash :: !Text - } - deriving stock (Show, Eq, Generic) - deriving - (FromJSON, ToJSON) - via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxo", CamelToSnake]] Utxo - -newtype TxCbor = TxCbor {_txCbor :: Text} - deriving stock (Show, Eq, Generic) - deriving - (FromJSON, ToJSON) - via CustomJSON '[FieldLabelModifier '[StripPrefix "_tx", LowerFirst]] TxCbor - -newtype UtxoAddress = UtxoAddress {_utxoAddressAddress :: Text} - deriving stock (Show, Eq, Generic) - deriving - (FromJSON, ToJSON) - via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxoAddress", LowerFirst]] UtxoAddress - +-- Datatype to represent for /"order"/ query parameter in some of the API requests. data Order = Ascending | Descending +-- Don't change @Show@ instance blindly, as `ToHttpApiData` instance is making use of it. +instance Show Order where + show Ascending = "asc" + show Descending = "desc" + instance ToHttpApiData Order where - toQueryParam Ascending = "asc" - toQueryParam Descending = "desc" + toQueryParam order = T.pack $ show order instance Default Order where def = Ascending -instance Show Order where - show Ascending = "asc" - show Descending = "desc" - +-- | Content-Type to represent transaction when submitting for it. data CBORStream instance Accept CBORStream where diff --git a/src/Maestro/Types/V0.hs b/src/Maestro/Types/V0.hs new file mode 100644 index 0000000..26a45a5 --- /dev/null +++ b/src/Maestro/Types/V0.hs @@ -0,0 +1,23 @@ +-- | Maestro types + +module Maestro.Types.V0 + ( module Maestro.Types.V0.Accounts + , module Maestro.Types.V0.Address + , module Maestro.Types.V0.Assets + , module Maestro.Types.V0.Datum + , module Maestro.Types.V0.Epochs + , module Maestro.Types.V0.Common + , module Maestro.Types.V0.General + , module Maestro.Types.V0.Pool + , module Maestro.Types.V0.Transactions + ) where + +import Maestro.Types.V0.Accounts +import Maestro.Types.V0.Address +import Maestro.Types.V0.Assets +import Maestro.Types.V0.Common +import Maestro.Types.V0.Datum +import Maestro.Types.V0.Epochs +import Maestro.Types.V0.General +import Maestro.Types.V0.Pool +import Maestro.Types.V0.Transactions diff --git a/src/Maestro/Types/Accounts.hs b/src/Maestro/Types/V0/Accounts.hs similarity index 92% rename from src/Maestro/Types/Accounts.hs rename to src/Maestro/Types/V0/Accounts.hs index eae6d23..757955d 100644 --- a/src/Maestro/Types/Accounts.hs +++ b/src/Maestro/Types/V0/Accounts.hs @@ -1,14 +1,13 @@ -module Maestro.Types.Accounts +module Maestro.Types.V0.Accounts ( AccountInfo (..) , AccountHistory (..) , AccountReward (..) , AccountUpdate (..) ) where -import Data.Text (Text) +import Data.Text (Text) import Deriving.Aeson -import Maestro.Types.Common (EpochNo, LowerFirst) -import Maestro.Types.General (SlotNo) +import Maestro.Types.V0.Common (EpochNo, LowerFirst, SlotNo) -- | Information about an account data AccountInfo = AccountInfo diff --git a/src/Maestro/Types/Address.hs b/src/Maestro/Types/V0/Address.hs similarity index 93% rename from src/Maestro/Types/Address.hs rename to src/Maestro/Types/V0/Address.hs index 1c92918..7958dc5 100644 --- a/src/Maestro/Types/Address.hs +++ b/src/Maestro/Types/V0/Address.hs @@ -1,4 +1,4 @@ -module Maestro.Types.Address where +module Maestro.Types.V0.Address where import Data.Text (Text) import Deriving.Aeson diff --git a/src/Maestro/Types/Assets.hs b/src/Maestro/Types/V0/Assets.hs similarity index 90% rename from src/Maestro/Types/Assets.hs rename to src/Maestro/Types/V0/Assets.hs index 53d9696..0dd2871 100644 --- a/src/Maestro/Types/Assets.hs +++ b/src/Maestro/Types/V0/Assets.hs @@ -1,11 +1,11 @@ -module Maestro.Types.Assets where +module Maestro.Types.V0.Assets where -import qualified Data.Aeson as Aeson -import Data.Text (Text) -import Data.Time.Clock.POSIX (POSIXTime) +import qualified Data.Aeson as Aeson +import Data.Text (Text) +import Data.Time.Clock.POSIX (POSIXTime) import Deriving.Aeson -import GHC.Natural (Natural) -import Maestro.Types.Common (BlockHeight, EpochNo, LowerFirst) +import GHC.Natural (Natural) +import Maestro.Types.V0.Common (BlockHeight, EpochNo, LowerFirst) data TokenRegistryMetadata = TokenRegistryMetadata { _tokenRegMetDecimals :: !Integer diff --git a/src/Maestro/Types/V0/Common.hs b/src/Maestro/Types/V0/Common.hs new file mode 100644 index 0000000..950b965 --- /dev/null +++ b/src/Maestro/Types/V0/Common.hs @@ -0,0 +1,43 @@ +module Maestro.Types.V0.Common + ( AssetId (..), + Asset (..), + Utxo (..), + module Maestro.Types.Common + ) +where + +import Data.Text (Text) +import Deriving.Aeson +import GHC.Natural (Natural) +import Maestro.Types.Common +import Servant.API (FromHttpApiData, ToHttpApiData) + +-- | Concatenation of hex encoded policy ID and hex encoded asset name. +newtype AssetId = AssetId Text + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON) + +-- | Representation of asset in an UTxO. +data Asset = Asset + { _assetQuantity :: !Integer + , _assetUnit :: !Text + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_asset", CamelToSnake]] Asset + +-- | Transaction output. +data Utxo = Utxo + { _utxoAddress :: !(Bech32StringOf Address), + _utxoAssets :: ![Asset], + _utxoDatum :: !(Maybe DatumOption), + _utxoIndex :: !Natural, + _utxoReferenceScript :: !(Maybe Script), + _utxoTxHash :: !Text, + _utxoTxoutCbor :: !(Maybe (HexStringOf TxOutCbor)) + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxo", CamelToSnake]] Utxo diff --git a/src/Maestro/Types/Datum.hs b/src/Maestro/Types/V0/Datum.hs similarity index 74% rename from src/Maestro/Types/Datum.hs rename to src/Maestro/Types/V0/Datum.hs index 9c5cebf..79dbc8c 100644 --- a/src/Maestro/Types/Datum.hs +++ b/src/Maestro/Types/V0/Datum.hs @@ -1,13 +1,13 @@ -- | Module to define types for /\"Datum\"/ endpoints defined at [reference.gomaestro.org](https://reference.gomaestro.org/). -module Maestro.Types.Datum +module Maestro.Types.V0.Datum ( Datum (..) ) where -import Data.Aeson (Value) -import Data.Text (Text) +import Data.Aeson (Value) +import Data.Text (Text) import Deriving.Aeson -import Maestro.Types.Common (LowerFirst) +import Maestro.Types.V0.Common (LowerFirst) -- | Details of datum when queried by it's hash. data Datum = Datum diff --git a/src/Maestro/Types/Epochs.hs b/src/Maestro/Types/V0/Epochs.hs similarity index 79% rename from src/Maestro/Types/Epochs.hs rename to src/Maestro/Types/V0/Epochs.hs index 2c01bdd..35b79f2 100644 --- a/src/Maestro/Types/Epochs.hs +++ b/src/Maestro/Types/V0/Epochs.hs @@ -1,14 +1,14 @@ -module Maestro.Types.Epochs ( EpochInfoFees (..), CurrentEpochInfo (..), EpochInfo (..) ) where +module Maestro.Types.V0.Epochs ( EpochInfoFees (..), CurrentEpochInfo (..), EpochInfo (..) ) where -import Data.Aeson (FromJSON (parseJSON), toEncoding, - toJSON, withText) -import Data.Text (unpack) -import Data.Time.Clock.POSIX (POSIXTime) -import Data.Word (Word64) +import Data.Aeson (FromJSON (parseJSON), toEncoding, + toJSON, withText) +import Data.Text (unpack) +import Data.Time.Clock.POSIX (POSIXTime) +import Data.Word (Word64) import Deriving.Aeson -import Maestro.Types.Common -import Numeric.Natural (Natural) -import Text.Read (readMaybe) +import Maestro.Types.V0.Common +import Numeric.Natural (Natural) +import Text.Read (readMaybe) -- | Sum of all the fees within the epoch in lovelaces. newtype EpochInfoFees = EpochInfoFees Natural diff --git a/src/Maestro/Types/General.hs b/src/Maestro/Types/V0/General.hs similarity index 91% rename from src/Maestro/Types/General.hs rename to src/Maestro/Types/V0/General.hs index f55e611..bb8f33d 100644 --- a/src/Maestro/Types/General.hs +++ b/src/Maestro/Types/V0/General.hs @@ -1,10 +1,9 @@ -- | Module to define types for /\"General\"/ endpoints defined at [reference.gomaestro.org](https://reference.gomaestro.org/). -module Maestro.Types.General +module Maestro.Types.V0.General ( -- * Types for @/system-start@ endpoint SystemStart (..) -- * Types for @/era-history@ endpoint - , SlotNo (..) , EraSummary (..) , EraParameters (..) , EraBound (..) @@ -21,20 +20,20 @@ module Maestro.Types.General , ChainTip (..) ) where -import Control.Monad (unless, when) -import Data.Aeson (FromJSON (parseJSON), toEncoding, toJSON, - withText) -import Data.Map.Strict (Map) -import Data.Ratio (denominator, numerator, (%)) -import Data.Text (Text) -import qualified Data.Text as Txt -import qualified Data.Text.Read as TxtRead -import Data.Time (LocalTime, NominalDiffTime) -import Data.Word (Word64) +import Control.Monad (unless, when) +import Data.Aeson (FromJSON (parseJSON), toEncoding, + toJSON, withText) +import Data.Map.Strict (Map) +import Data.Ratio (denominator, numerator, (%)) +import Data.Text (Text) +import qualified Data.Text as Txt +import qualified Data.Text.Read as TxtRead +import Data.Time (LocalTime, NominalDiffTime) +import Data.Word (Word64) import Deriving.Aeson -import Maestro.Types.Common (BlockHash, EpochNo, EpochSize, - LowerFirst) -import Numeric.Natural (Natural) +import Maestro.Types.V0.Common (BlockHash, EpochNo, EpochSize, + LowerFirst, SlotNo) +import Numeric.Natural (Natural) ------------------------------------------------------------------ -- Types for @/system-start@ endpoint. @@ -49,11 +48,6 @@ newtype SystemStart = SystemStart { _systemStartTime :: LocalTime } -- Types for @/era-history@ endpoint ------------------------------------------------------------------ --- | The 0-based index for the Ourboros time slot. -newtype SlotNo = SlotNo {unSlotNo :: Word64} - deriving stock (Eq, Ord, Show, Generic) - deriving newtype (Num, Bounded, Enum, Real, Integral, FromJSON, ToJSON) - -- | Network era summary. data EraSummary = EraSummary { _eraSummaryStart :: !EraBound diff --git a/src/Maestro/Types/Pool.hs b/src/Maestro/Types/V0/Pool.hs similarity index 97% rename from src/Maestro/Types/Pool.hs rename to src/Maestro/Types/V0/Pool.hs index 13fa772..87f9d08 100644 --- a/src/Maestro/Types/Pool.hs +++ b/src/Maestro/Types/V0/Pool.hs @@ -1,4 +1,4 @@ -module Maestro.Types.Pool +module Maestro.Types.V0.Pool ( ActiveStake (..), DelegatorInfo (..), PoolListInfo (..), @@ -14,11 +14,11 @@ module Maestro.Types.Pool ) where -import Data.Text (Text) -import Data.Time.Clock.POSIX (POSIXTime) +import Data.Text (Text) +import Data.Time.Clock.POSIX (POSIXTime) import Deriving.Aeson -import GHC.Natural (Natural) -import Maestro.Types.Common +import GHC.Natural (Natural) +import Maestro.Types.V0.Common data PoolId diff --git a/src/Maestro/Types/V0/Transactions.hs b/src/Maestro/Types/V0/Transactions.hs new file mode 100644 index 0000000..dd98e18 --- /dev/null +++ b/src/Maestro/Types/V0/Transactions.hs @@ -0,0 +1,22 @@ +module Maestro.Types.V0.Transactions + ( TxCbor (..), + UtxoAddress (..), + ) +where + +import Data.Text (Text) +import Deriving.Aeson +import Maestro.Types.Common (LowerFirst) + +newtype TxCbor = TxCbor { _txCbor :: Text } + deriving stock (Show, Eq, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_tx", LowerFirst]] TxCbor + +newtype UtxoAddress = UtxoAddress { _utxoAddressAddress :: Text } + deriving stock (Show, Eq, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxoAddress", LowerFirst]] UtxoAddress + diff --git a/src/Maestro/Types/V1.hs b/src/Maestro/Types/V1.hs new file mode 100644 index 0000000..3bc07f6 --- /dev/null +++ b/src/Maestro/Types/V1.hs @@ -0,0 +1,17 @@ +-- | Module exporting all available types of this specific Maestro-API version. + +module Maestro.Types.V1 + ( module Maestro.Types.V1.Addresses + , module Maestro.Types.V1.Common + , module Maestro.Types.V1.Datum + , module Maestro.Types.V1.General + , module Maestro.Types.V1.Pools + , module Maestro.Types.V1.Transactions + ) where + +import Maestro.Types.V1.Addresses +import Maestro.Types.V1.Common +import Maestro.Types.V1.Datum +import Maestro.Types.V1.General +import Maestro.Types.V1.Pools +import Maestro.Types.V1.Transactions diff --git a/src/Maestro/Types/V1/Addresses.hs b/src/Maestro/Types/V1/Addresses.hs new file mode 100644 index 0000000..1ea8332 --- /dev/null +++ b/src/Maestro/Types/V1/Addresses.hs @@ -0,0 +1,133 @@ +-- | Module to define types for /\"Addresses\"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/addresses). + +module Maestro.Types.V1.Addresses ( + AddressToDecode, + NetworkId (..), + PaymentCredKind (..), + PaymentCredential (..), + StakingCredKind (..), + CertIndex (..), + ChainPointer (..), + StakingCredential (..), + AddressInfo (..), + OutputReferenceObject (..), + PaginatedOutputReferenceObject (..), + ) where + +import Deriving.Aeson +import GHC.Natural (Natural) +import Maestro.Types.V1.Common +import Servant.API (FromHttpApiData, ToHttpApiData) + +-- | Address to decode. Given address should be in either Bech32 or Hex or Base58 format. Base58 is for Byron addresses whereas others use Bech32. +type AddressToDecode = "Bech32/Hex/Base58 encoded address" + +-- | Denotes network for the entity in question, such as address. +data NetworkId = NIDMainnet | NIDTestnet + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[StripPrefix "NID", LowerFirst]] NetworkId + +-- | Denotes kind of a payment credential. +data PaymentCredKind = PCKKey | PCKScript + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[StripPrefix "PCK", LowerFirst]] PaymentCredKind + +-- | Payment credential, the payment part of a Cardano address. +data PaymentCredential = PaymentCredential + { _paymentCredentialBech32:: !(Bech32StringOf PaymentCredentialAddress) + -- ^ Bech32-encoding of the credential key hash or script hash. + , _paymentCredentialHex :: !(HexStringOf PaymentCredentialAddress) + -- ^ Hex-encoding of the script or key credential. + , _paymentCredentialKind :: !PaymentCredKind + -- ^ See `PaymentCredKind`. + } + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_paymentCredential", CamelToSnake]] PaymentCredential + +-- | Denotes kind of a staking credential. +data StakingCredKind = SCKKey | SCKScript | SCKPointer + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[StripPrefix "SCK", LowerFirst]] StakingCredKind + +-- | To understand it, see it's use in `ChainPointer` datatype. +newtype CertIndex = CertIndex Natural + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (Num, Enum, Real, Integral, FromHttpApiData, ToHttpApiData, FromJSON, ToJSON) + +-- | In an address, a chain pointer refers to a point of the chain containing a stake key registration certificate. A point is identified by 3 coordinates, as listed in the type. +data ChainPointer = ChainPointer + { _chainPointerSlot :: !SlotNo + -- ^ An absolute slot number. + , _chainPointerTxIndex :: !TxIndex + -- ^ A transaction index (within that slot). + , _chainPointerCertIndex :: !CertIndex + -- ^ A (delegation) certificate index (within that transaction). + } + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_chainPointer", CamelToSnake]] ChainPointer + +-- | Payment credential, the payment part of a Cardano address. +data StakingCredential = StakingCredential + { _stakingCredentialBech32:: !(Maybe (Bech32StringOf StakingCredentialAddress)) + -- ^ Bech32-encoding of the credential key hash or script hash. + , _stakingCredentialHex :: !(Maybe (HexStringOf StakingCredentialAddress)) + -- ^ Hex-encoding of the script or key credential. + , _stakingCredentialKind :: !StakingCredKind + -- ^ See `StakingCredKind`. + , _stakingCredentialPointer :: !(Maybe ChainPointer) + -- ^ See `ChainPointer`. + , _stakingCredentialRewardAddress :: !(Maybe (Bech32StringOf RewardAddress)) + -- ^ See `RewardAddress`. + } + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_stakingCredential", CamelToSnake]] StakingCredential + +-- | Information decoded from a Cardano address. +data AddressInfo = AddressInfo + { _addressInfoHex :: !(HexStringOf Address) + -- ^ Hexadecimal format encoding of the given address. + , _addressInfoBech32 :: !(Maybe (Bech32StringOf Address)) + -- ^ Bech32 representation of the given address. Present for Shelly & stake addresses whereas byron addresses are encoded in Base58. + , _addressInfoNetwork :: !(Maybe NetworkId) + -- ^ See `NetworkId`. + , _addressInfoPaymentCred :: !(Maybe PaymentCredential) + -- ^ See `PaymentCredential`. + , _addressInfoStakingCred :: !(Maybe StakingCredential) + -- ^ See `StakingCredential`. + } + deriving stock (Show, Eq, Ord, Generic) + deriving (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_addressInfo", CamelToSnake]] AddressInfo + +-- | Output reference of an UTxO. This is different from `OutputReference` type as later JSON representation is a string whereas this has an object format. +data OutputReferenceObject = OutputReferenceObject + { _outputReferenceObjectIndex :: !TxIndex + , _outputReferenceObjectTxHash :: !TxHash + } + deriving stock (Show, Eq, Generic) + deriving (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_outputReferenceObject", CamelToSnake]] OutputReferenceObject + +-- | UTxO IDs for all the unspent transaction outputs at an address. +data PaginatedOutputReferenceObject = PaginatedOutputReferenceObject + { _paginatedOutputReferenceObjectData :: ![OutputReferenceObject] + -- ^ See `OutputReferenceObject`. + , _paginatedOutputReferenceObjectLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + , _paginatedOutputReferenceObjectNextCursor :: !(Maybe NextCursor) + -- ^ See `NextCursor`. + } + deriving stock (Show, Eq, Generic) + deriving (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_paginatedOutputReferenceObject", CamelToSnake]] PaginatedOutputReferenceObject + +instance IsTimestamped PaginatedOutputReferenceObject where + type TimestampedData PaginatedOutputReferenceObject = [OutputReferenceObject] + getTimestampedData = _paginatedOutputReferenceObjectData + getTimestamp = _paginatedOutputReferenceObjectLastUpdated + +instance HasCursor PaginatedOutputReferenceObject where + getNextCursor = _paginatedOutputReferenceObjectNextCursor diff --git a/src/Maestro/Types/V1/Common.hs b/src/Maestro/Types/V1/Common.hs new file mode 100644 index 0000000..d1545bf --- /dev/null +++ b/src/Maestro/Types/V1/Common.hs @@ -0,0 +1,175 @@ +-- | Common (shared) types used which are not specific to single category of endpoints. + +module Maestro.Types.V1.Common + ( PaymentCredentialAddress, + StakingCredentialAddress, + RewardAddress, + TaggedText (..), + NonAdaNativeToken (..), + AssetUnit (..), + Asset (..), + v1AssetToV0, + IsUtxo (..), + UtxoWithSlot (..), + v1UtxoWithSlotToV0, + PaginatedUtxoWithSlot (..), + module Maestro.Types.Common, + module Maestro.Types.V1.Common.Pagination, + module Maestro.Types.V1.Common.Timestamped + ) where + +import Data.Aeson (FromJSON (..), + ToJSON (..), Value (..), + withText) +import Data.Coerce (coerce) +import Data.String (IsString) +import Data.Text (Text) +import qualified Data.Text as T (splitAt) +import Deriving.Aeson +import GHC.TypeLits (Symbol) +import Maestro.Types.Common +import qualified Maestro.Types.V0 as V0 (Asset (..), + Utxo (..)) +import Maestro.Types.V1.Common.Pagination +import Maestro.Types.V1.Common.Timestamped +import Servant.API (FromHttpApiData (..), + ToHttpApiData (..)) + +-- | Phantom datatype to be used with, say `Bech32StringOf` to represent Bech32 representation of payment credential of an address. +data PaymentCredentialAddress + +-- | Phantom datatype to be used with, say `Bech32StringOf` to represent Bech32 representation of staking credential of an address. +data StakingCredentialAddress + +-- | Phantom datatype to be used with, say `Bech32StringOf` to represent Bech32 representation of stake address (See [CIP-19](https://cips.cardano.org/cips/cip19/) for more details). +data RewardAddress + +-- | Wrapper around `Text` type with mentioned description of it. +newtype TaggedText (description :: Symbol) = TaggedText Text + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) + +-- | Type to denote for native tokens (besides ada). +data NonAdaNativeToken = NonAdaNativeToken !PolicyId !TokenName + deriving stock (Eq, Ord, Show) + +instance ToHttpApiData NonAdaNativeToken where + toUrlPiece (NonAdaNativeToken policyId tokenName) = coerce policyId <> coerce tokenName + +-- | Given asset name is either /lovelace/ or concatenation of hex encoded policy ID and hex encoded asset name for native asset. +data AssetUnit = Lovelace + -- ^ Lovelace. + | UserMintedToken !NonAdaNativeToken + -- ^ For non-ada native-tokens. + deriving stock (Eq, Ord) + +instance Show AssetUnit where + show Lovelace = "lovelace" + show (UserMintedToken nonAdaNativeToken) = show nonAdaNativeToken + +instance FromJSON AssetUnit where + parseJSON = withText "AssetUnit" $ \t -> + if t == "lovelace" then pure Lovelace + else + let (policyId, tokenName) = T.splitAt 56 t + in pure $ UserMintedToken $ NonAdaNativeToken (coerce policyId) (coerce tokenName) + +instance ToJSON AssetUnit where + toJSON Lovelace = String "lovelace" + toJSON (UserMintedToken nonAdaNativeToken) = String $ toUrlPiece nonAdaNativeToken + +-- | Representation of asset in an UTxO. +data Asset = Asset + { _assetAmount :: !Integer + -- ^ Amount of the asset. + , _assetUnit :: !AssetUnit + -- ^ See `AssetUnit`. + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_asset", CamelToSnake]] Asset + +-- | Convert @V1@ API version `Asset` type into corresponding @V0@ type. +v1AssetToV0 :: Asset -> V0.Asset +v1AssetToV0 Asset {..} = V0.Asset { + V0._assetQuantity = _assetAmount + , V0._assetUnit = case _assetUnit of + Lovelace -> "lovelace" + UserMintedToken (NonAdaNativeToken policyId tokenName) -> coerce policyId <> "#" <> coerce tokenName + } + +-- | To get basic details from an UTxO. +class IsUtxo a where + getAddress :: a -> Bech32StringOf Address + getAssets :: a -> [Asset] + getDatum :: a -> Maybe DatumOption + getTxHash :: a -> TxHash + getIndex :: a -> TxIndex + getReferenceScript :: a -> Maybe Script + +-- | Transaction output. +data UtxoWithSlot = UtxoWithSlot + { _utxoWithSlotAddress :: !(Bech32StringOf Address), + -- ^ UTxO's address. + _utxoWithSlotAssets :: ![Asset], + -- ^ UTxO's assets. + _utxoWithSlotDatum :: !(Maybe DatumOption), + -- ^ UTxO's datum. + _utxoWithSlotIndex :: !TxIndex, + -- ^ UTxO's transaction index. + _utxoWithSlotReferenceScript :: !(Maybe Script), + -- ^ UTxO's script. + _utxoWithSlotTxHash :: !TxHash, + -- ^ UTxO's transaction hash. + _utxoWithSlotSlot :: !SlotNo, + -- ^ Absolute slot of block which produced the UTxO. + _utxoWithSlotTxoutCbor :: !(Maybe (HexStringOf TxOutCbor)) + -- ^ Hex encoded transaction output CBOR bytes. + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxoWithSlot", CamelToSnake]] UtxoWithSlot + +instance IsUtxo UtxoWithSlot where + getAddress = _utxoWithSlotAddress + getAssets = _utxoWithSlotAssets + getDatum = _utxoWithSlotDatum + getTxHash = _utxoWithSlotTxHash + getIndex = _utxoWithSlotIndex + getReferenceScript = _utxoWithSlotReferenceScript + +-- | Convert @V1@ API version UTxO (with slot) type into corresponding @V0@ type. +v1UtxoWithSlotToV0 :: UtxoWithSlot -> V0.Utxo +v1UtxoWithSlotToV0 UtxoWithSlot {..} = V0.Utxo { + V0._utxoAddress = _utxoWithSlotAddress + , V0._utxoAssets = map v1AssetToV0 _utxoWithSlotAssets + , V0._utxoDatum = _utxoWithSlotDatum + , V0._utxoIndex = coerce _utxoWithSlotIndex + , V0._utxoReferenceScript = _utxoWithSlotReferenceScript + , V0._utxoTxHash = coerce _utxoWithSlotTxHash + , V0._utxoTxoutCbor = _utxoWithSlotTxoutCbor + } + +-- | A paginated response of transaction outputs. +data PaginatedUtxoWithSlot = PaginatedUtxoWithSlot + { _paginatedUtxoWithSlotData :: ![UtxoWithSlot], + -- ^ List of UTxOs. + _paginatedUtxoWithSlotLastUpdated :: !LastUpdated, + -- ^ See `LastUpdated`. + _paginatedUtxoWithSlotNextCursor :: !(Maybe NextCursor) + -- ^ See `NextCursor` + } + deriving stock (Show, Eq, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_paginatedUtxoWithSlot", CamelToSnake]] PaginatedUtxoWithSlot + +instance IsTimestamped PaginatedUtxoWithSlot where + type TimestampedData PaginatedUtxoWithSlot = [UtxoWithSlot] + getTimestampedData = _paginatedUtxoWithSlotData + getTimestamp = _paginatedUtxoWithSlotLastUpdated + +instance HasCursor PaginatedUtxoWithSlot where + getNextCursor = _paginatedUtxoWithSlotNextCursor diff --git a/src/Maestro/Types/V1/Common/Pagination.hs b/src/Maestro/Types/V1/Common/Pagination.hs new file mode 100644 index 0000000..75e7968 --- /dev/null +++ b/src/Maestro/Types/V1/Common/Pagination.hs @@ -0,0 +1,24 @@ +-- | Module to define types demanded by cursor based pagination to be used by other types defined in @Maestro.Types.V1@. + +module Maestro.Types.V1.Common.Pagination ( + NextCursor (..) + , HasCursor (..) + ) where + +import Data.Aeson (FromJSON, ToJSON) +import Data.String (IsString) +import Data.Text (Text) +import GHC.Generics (Generic) +import Maestro.Types.V1.Common.Timestamped +import Servant.API (FromHttpApiData, + ToHttpApiData) + +-- | Type to denote for cursor to be returned in a paginated endpoint. +newtype NextCursor = NextCursor Text + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (FromHttpApiData, ToHttpApiData, FromJSON, ToJSON, IsString) + +-- | Is the endpoint paged? +class (IsTimestamped a, Monoid (TimestampedData a)) => HasCursor a where + -- | Get the next cursor from the value of the given type @a@. + getNextCursor :: a -> Maybe NextCursor diff --git a/src/Maestro/Types/V1/Common/Timestamped.hs b/src/Maestro/Types/V1/Common/Timestamped.hs new file mode 100644 index 0000000..76c8e3c --- /dev/null +++ b/src/Maestro/Types/V1/Common/Timestamped.hs @@ -0,0 +1,29 @@ +-- | Module to define typeclass for timestamped response, i.e. response which has a @data@ & @last_updated@ field. + +module Maestro.Types.V1.Common.Timestamped ( + LastUpdated (..), + IsTimestamped (..), + ) where + +import Data.Kind (Type) +import Deriving.Aeson +import Maestro.Types.Common (BlockHash, SlotNo) + +-- | Details of the most recent block processed by the indexer (aka chain tip); that is, the data returned is correct as of this block in time. +data LastUpdated = LastUpdated + { _lastUpdatedBlockHash :: !BlockHash + -- ^ Hash of the latest block. + , _lastUpdatedBlockSlot :: !SlotNo + -- ^ Slot number for the tip. + } + deriving stock (Eq, Ord, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_lastUpdated", CamelToSnake]] LastUpdated + +-- | Is the endpoint timestamped? +class IsTimestamped a where + -- | What is the type of the main data in question? + type TimestampedData a :: Type + -- | Get the main data from the value of the given type @a@. + getTimestampedData :: a -> TimestampedData a + -- | Get the `LastUpdated` field. + getTimestamp :: a -> LastUpdated diff --git a/src/Maestro/Types/V1/Datum.hs b/src/Maestro/Types/V1/Datum.hs new file mode 100644 index 0000000..70fcdcd --- /dev/null +++ b/src/Maestro/Types/V1/Datum.hs @@ -0,0 +1,37 @@ +-- | Module to define types for /\"Datum\"/ endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/datum). + +module Maestro.Types.V1.Datum + ( Datum (..) + , TimestampedDatum (..) + ) where + +import Data.Aeson (Value) +import Data.Text (Text) +import Deriving.Aeson +import Maestro.Types.V1.Common (IsTimestamped (..), LastUpdated, + LowerFirst) + +-- | Details of datum when queried by it's hash. +data Datum = Datum + { _datumBytes :: !Text + -- ^ Hex encoded datum CBOR bytes. + , _datumJson :: !Value + -- ^ JSON representation of the datum. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_datum", LowerFirst]] Datum + +-- | Timestamped `Datum` response. +data TimestampedDatum = TimestampedDatum + { _timestampedDatumData :: !Datum + -- ^ See `Datum`. + , _timestampedDatumLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_timestampedDatum", CamelToSnake]] TimestampedDatum + +instance IsTimestamped TimestampedDatum where + type TimestampedData TimestampedDatum = Datum + getTimestampedData = _timestampedDatumData + getTimestamp = _timestampedDatumLastUpdated diff --git a/src/Maestro/Types/V1/General.hs b/src/Maestro/Types/V1/General.hs new file mode 100644 index 0000000..d0229b4 --- /dev/null +++ b/src/Maestro/Types/V1/General.hs @@ -0,0 +1,287 @@ +-- | Module to define types for /\"General\"/ endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/general). + +module Maestro.Types.V1.General + ( -- * Types for @/system-start@ endpoint + TimestampedSystemStart (..) + -- * Types for @/era-history@ endpoint + , TimestampedEraSummaries (..) + , EraSummary (..) + , EraParameters (..) + , EraBound (..) + -- * Types for @/protocol-params@ endpoint + , ProtocolVersion (..) + , MemoryStepsWith (..) + , CostModel (..) + , CostModels (..) + , MaestroRational (..) + , textToMaestroRational + , textFromMaestroRational + , TimestampedProtocolParameters (..) + , ProtocolParameters (..) + -- * Types for @/chain-tip@ endpoint + , TimestampedChainTip (..) + , ChainTip (..) + ) where + +import Control.Monad (unless, when) +import Data.Aeson (FromJSON (parseJSON), toEncoding, + toJSON, withText) +import Data.Map.Strict (Map) +import Data.Ratio (denominator, numerator, (%)) +import Data.Text (Text) +import qualified Data.Text as Txt +import qualified Data.Text.Read as TxtRead +import Data.Time (LocalTime, NominalDiffTime) +import Data.Word (Word64) +import Deriving.Aeson +import Maestro.Types.V0.Common (BlockHash, EpochNo, EpochSize, + LowerFirst, SlotNo) +import Maestro.Types.V1.Common (IsTimestamped (..), LastUpdated (..)) +import Numeric.Natural (Natural) + +------------------------------------------------------------------ +-- Types for @/system-start@ endpoint. +------------------------------------------------------------------ + +-- | Network start time since genesis. +data TimestampedSystemStart = TimestampedSystemStart + { _timestampedSystemStartData :: !LocalTime + -- ^ Network start time since genesis. + , _timestampedSystemStartLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + } + deriving stock (Eq, Ord, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_timestampedSystemStart", CamelToSnake]] TimestampedSystemStart + +instance IsTimestamped TimestampedSystemStart where + type TimestampedData TimestampedSystemStart = LocalTime + getTimestampedData = _timestampedSystemStartData + getTimestamp = _timestampedSystemStartLastUpdated + +------------------------------------------------------------------ +-- Types for @/era-history@ endpoint +------------------------------------------------------------------ + +-- | Network era summaries. +data TimestampedEraSummaries = TimestampedEraSummaries + { _timestampedEraSummariesData :: ![EraSummary] + -- ^ Era summaries, see `EraSummary`. + , _timestampedEraSummariesLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_timestampedEraSummaries", CamelToSnake]] TimestampedEraSummaries + +instance IsTimestamped TimestampedEraSummaries where + type TimestampedData TimestampedEraSummaries = [EraSummary] + getTimestampedData = _timestampedEraSummariesData + getTimestamp = _timestampedEraSummariesLastUpdated + +-- | Network era summary. +data EraSummary = EraSummary + { _eraSummaryStart :: !EraBound + -- ^ Start of this era. + , _eraSummaryEnd :: !(Maybe EraBound) + -- ^ End of this era. + , _eraSummaryParameters :: !EraParameters + -- ^ Parameters of this era. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_eraSummary", LowerFirst]] EraSummary + +-- | Parameters for a network era which can vary between hardforks. +data EraParameters = EraParameters + { _eraParametersEpochLength :: !EpochSize + -- ^ Number of slots in an epoch. + , _eraParametersSlotLength :: !NominalDiffTime + -- ^ How long a slot lasts. + , _eraParametersSafeZone :: !(Maybe Word64) + -- ^ Number of slots from the tip of the ledger in which a hardfork will not happen. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_eraParameters", CamelToSnake]] EraParameters + +-- | Bounds of an era. +data EraBound = EraBound + { _eraBoundEpoch :: !EpochNo + -- ^ Epoch number bounding this era. + , _eraBoundSlot :: !SlotNo + -- ^ Absolute slot number bounding this era. + , _eraBoundTime :: !NominalDiffTime + -- ^ Time relative to the start time of the network. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_eraBound", LowerFirst]] EraBound + +------------------------------------------------------------------ +-- Types for @/protocol-params@ endpoint. +------------------------------------------------------------------ + +-- | Current accepted protocol version. An increase in the major version indicates a hard fork, and the minor version a soft fork (meaning old software can validate but not produce new blocks). +data ProtocolVersion = ProtocolVersion + { _protocolVersionMajor :: !Natural + -- ^ Accepted protocol major version. + , _protocolVersionMinor :: !Natural + -- ^ Accepted protocol minor version. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_protocolVersion", LowerFirst]] ProtocolVersion + +-- | Pair of memory & steps for the given type. +data MemoryStepsWith i = MemoryStepsWith + { _memoryStepsWithMemory :: !i + , _memoryStepsWithSteps :: !i + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_memoryStepsWith", LowerFirst]] (MemoryStepsWith i) + +-- | A cost model is a vector of coefficients that are used to compute the execution units required to execute a script. Its specifics depend on specific versions of the Plutus interpreter it is used with. +newtype CostModel = CostModel (Map Text Integer) + deriving (Eq, Show) + deriving newtype (ToJSON, FromJSON) + +-- | Cost models (see `CostModel`) for script languages that use them. +data CostModels = CostModels + { _costModelsPlutusV1 :: !CostModel + , _costModelsPlutusV2 :: !CostModel + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_costModels", Rename "PlutusV1" "plutus:v1", Rename "PlutusV2" "plutus:v2"]] CostModels + +-- | Maestro's represents rational numbers as string with numerator and denominator demarcated by \'\/\', example: @"1/3"@. +newtype MaestroRational = MaestroRational { unMaestroRational :: Rational } + deriving stock Eq + +instance Show MaestroRational where + show (MaestroRational r) = show (numerator r) ++ '/' : show (denominator r) + +-- | Get original `Text` from `MaestroRational`. +textFromMaestroRational :: MaestroRational -> Text +textFromMaestroRational = Txt.pack . show . unMaestroRational + +-- | Parses given `Text` to `MaestroRational`. +textToMaestroRational :: Text -> Either String MaestroRational +textToMaestroRational ratTxt = + case TxtRead.signed rationalReader ratTxt of + Right (rat, remainingTxt) -> if Txt.null remainingTxt + then pure $ MaestroRational rat + else Left "Expected full string to be parsed" + Left e -> Left e + where + rationalReader :: TxtRead.Reader Rational + rationalReader ratTxt' = do + (numr, remaining) <- TxtRead.decimal ratTxt' + (nextChar, denmrTxt) <- maybe + (Left "Unexpected end of string after parsing numerator") + pure + $ Txt.uncons remaining + unless (nextChar == '/') + . Left + $ "Expected numerator to be immediately followed by '/', but it was followed by: " ++ show nextChar + (denmr, finalRemaining) <- TxtRead.decimal denmrTxt + when (denmr == 0) + $ Left "Expected non-zero denominator" + pure (numr % denmr, finalRemaining) + +instance ToJSON MaestroRational where + toEncoding = toEncoding . textFromMaestroRational + toJSON = toJSON . textFromMaestroRational + +instance FromJSON MaestroRational where + parseJSON = withText "MaestroRational" $ \ratTxt -> either fail pure $ textToMaestroRational ratTxt + +-- | Timestamped `ProtocolParameters` response. +data TimestampedProtocolParameters = TimestampedProtocolParameters + { _timestampedProtocolParametersData :: !ProtocolParameters + -- ^ See `ProtocolParametersData`. + , _timestampedProtocolParametersLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_timestampedProtocolParameters", CamelToSnake]] TimestampedProtocolParameters + +instance IsTimestamped TimestampedProtocolParameters where + type TimestampedData TimestampedProtocolParameters = ProtocolParameters + getTimestampedData = _timestampedProtocolParametersData + getTimestamp = _timestampedProtocolParametersLastUpdated + +-- | Protocol parameters for the latest epoch. +data ProtocolParameters = ProtocolParameters + { _protocolParametersProtocolVersion :: !ProtocolVersion + -- ^ See `ProtocolVersion`. + , _protocolParametersMinFeeConstant :: !Natural + -- ^ The linear factor for the minimum fee calculation for given epoch /AKA/ @min_fee_b@ and @tx_fee_fixed@. + , _protocolParametersMinFeeCoefficient :: !Natural + -- ^ The constant factor for the minimum fee calculation /AKA/ @min_fee_a@ and @tx_fee_per_byte@. + , _protocolParametersMaxBlockBodySize :: !Natural + -- ^ Maximum block body size. + , _protocolParametersMaxBlockHeaderSize :: !Natural + -- ^ Maximum block header size. + , _protocolParametersMaxTxSize :: !Natural + -- ^ Maximum transaction size. + , _protocolParametersStakeKeyDeposit :: !Natural + -- The deposit required to register a stake address. + , _protocolParametersPoolDeposit :: !Natural + -- ^ The amount of a pool registration deposit in lovelaces /AKA/ @stake_pool_deposit@. + , _protocolParametersPoolRetirementEpochBound :: !EpochNo + -- ^ The maximum number of epochs into the future that stake pools are permitted to schedule a retirement /AKA/ @pool_retire_max_epoch@, @e_max@. + , _protocolParametersDesiredNumberOfPools :: !Natural + -- The equilibrium target number of stake pools. This is the \"k\" incentives parameter from the design document, /AKA/ @n_opt@, @stake_pool_target@. + , _protocolParametersPoolInfluence :: !MaestroRational + -- The influence of the pledge in stake pool rewards. This is the \"a_0\" incentives parameter from the design document. + , _protocolParametersMonetaryExpansion :: !MaestroRational + -- ^ The monetary expansion rate. This determines the fraction of the reserves that are added to the fee pot each epoch. This is the \"rho\" incentives parameter from the design document. + , _protocolParametersTreasuryExpansion :: !MaestroRational + -- ^ The fraction of the fee pot each epoch that goes to the treasury. This is the \"tau\" incentives parameter from the design document, /AKA/ @treasury_cut@. + , _protocolParametersMinPoolCost :: !Natural + -- ^ The minimum value that stake pools are permitted to declare for their cost parameter. + , _protocolParametersPrices :: !(MemoryStepsWith MaestroRational) + -- ^ The price per unit memory & price per reduction step corresponding to abstract notions of the relative memory usage and script execution steps respectively. + , _protocolParametersMaxExecutionUnitsPerTransaction :: !(MemoryStepsWith Natural) + -- ^ The maximum number of execution memory & steps allowed to be used in a single transaction. + , _protocolParametersMaxExecutionUnitsPerBlock :: !(MemoryStepsWith Natural) + -- ^ The maximum number of execution memory & steps allowed to be used in a single block. + , _protocolParametersMaxValueSize :: !Natural + -- ^ Maximum size of the /value/ part of an output in a serialized transaction. + , _protocolParametersCollateralPercentage :: !Natural + -- ^ The percentage of the transactions fee which must be provided as collateral when including non-native scripts. + , _protocolParametersMaxCollateralInputs :: !Natural + -- ^ The maximum number of collateral inputs allowed in a transaction. + , _protocolParametersCoinsPerUtxoByte :: !Natural + -- ^ The cost per UTxO size. Cost per UTxO /word/ for Alozno. Cost per UTxO /byte/ for Babbage and later. + , _protocolParametersCostModels :: !CostModels + -- ^ See `CostModels`. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_protocolParameters", CamelToSnake]] ProtocolParameters + +------------------------------------------------------------------ +-- Types for @/chain-tip@ endpoint. +------------------------------------------------------------------ + +-- | Details about the most recently adopted block. +data ChainTip = ChainTip + { _chainTipBlockHash :: !BlockHash + -- ^ Hash of this most recent block. + , _chainTipSlot :: !SlotNo + -- ^ Slot number for this most recent block. + , _chainTipHeight :: !Word64 + -- ^ Block number (height) of this most recent block. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_chainTip", CamelToSnake]] ChainTip + +-- | Timestamped `ChainTip` response. +data TimestampedChainTip = TimestampedChainTip + { _timestampedChainTipData :: !ChainTip + -- ^ See `ChainTip`. + , _timestampedChainTipLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + } + deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[FieldLabelModifier '[StripPrefix "_timestampedChainTip", CamelToSnake]] TimestampedChainTip + +instance IsTimestamped TimestampedChainTip where + type TimestampedData TimestampedChainTip = ChainTip + getTimestampedData = _timestampedChainTipData + getTimestamp = _timestampedChainTipLastUpdated diff --git a/src/Maestro/Types/V1/Pools.hs b/src/Maestro/Types/V1/Pools.hs new file mode 100644 index 0000000..0e8547a --- /dev/null +++ b/src/Maestro/Types/V1/Pools.hs @@ -0,0 +1,47 @@ +-- | Module to define types for /\"Pools\"/ category of endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/pools). + +module Maestro.Types.V1.Pools + ( PoolId, + PoolListInfo (..), + PaginatedPoolListInfo (..), + ) +where + +import Deriving.Aeson +import Maestro.Types.V1.Common + +-- | Phantom datatype to be used with, say `Bech32StringOf` to represent Bech32 representation of a pool id. +data PoolId + +-- | Information about a registered stake pool. +data PoolListInfo = PoolListInfo + { _poolListInfoPoolIdBech32 :: !(Bech32StringOf PoolId), + -- ^ Bech32 encoded Pool ID. + _poolListInfoTicker :: !(Maybe (TaggedText "pool-ticker")) + -- ^ Pool ticker symbol. + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_poolListInfo", CamelToSnake]] PoolListInfo + +-- | Paginated list of registered stake pools. +data PaginatedPoolListInfo = PaginatedPoolListInfo + { _paginatedPoolListInfoData :: ![PoolListInfo] + -- ^ See `PoolListInfo`. + , _paginatedPoolListInfoLastUpdated :: !LastUpdated + -- ^ See `LastUpdated`. + , _paginatedPoolListInfoNextCursor :: !(Maybe NextCursor) + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_paginatedPoolListInfo", CamelToSnake]] PaginatedPoolListInfo + +instance IsTimestamped PaginatedPoolListInfo where + type TimestampedData PaginatedPoolListInfo = [PoolListInfo] + getTimestampedData = _paginatedPoolListInfoData + getTimestamp = _paginatedPoolListInfoLastUpdated + +instance HasCursor PaginatedPoolListInfo where + getNextCursor = _paginatedPoolListInfoNextCursor diff --git a/src/Maestro/Types/V1/Transactions.hs b/src/Maestro/Types/V1/Transactions.hs new file mode 100644 index 0000000..54c8b47 --- /dev/null +++ b/src/Maestro/Types/V1/Transactions.hs @@ -0,0 +1,90 @@ +-- | Module to define types for /\"Transactions\"/ category endpoints defined at [docs.gomaestro.org](https://docs.gomaestro.org/docs/category/transactions). + +module Maestro.Types.V1.Transactions + ( OutputReference (..) + , UtxoWithBytes (..) + , v1UtxoToV0 + , PaginatedUtxo (..) + ) where + +import Data.Aeson (ToJSON (..), Value (..)) +import Data.Coerce (coerce) +import Deriving.Aeson +import Maestro.Types.Common +import qualified Maestro.Types.V0 as V0 (Utxo (..)) +import Maestro.Types.V1.Common +import Servant.API (ToHttpApiData (..)) + +-- | An UTxO output reference. +data OutputReference = OutputReference !TxHash !TxIndex + deriving stock (Show, Eq, Ord) + +instance ToHttpApiData OutputReference where + toQueryParam (OutputReference txHash txIndex) = toUrlPiece txHash <> "#" <> toUrlPiece txIndex + +instance ToJSON OutputReference where + toJSON outputReference = String $ toQueryParam outputReference + +-- | Transaction output. +data UtxoWithBytes = UtxoWithBytes + { _utxoWithBytesAddress :: !(Bech32StringOf Address), + -- ^ UTxO's address. + _utxoWithBytesAssets :: ![Asset], + -- ^ UTxO's assets. + _utxoWithBytesDatum :: !(Maybe DatumOption), + -- ^ UTxO's datum. + _utxoWithBytesIndex :: !TxIndex, + -- ^ UTxO's transaction index. + _utxoWithBytesReferenceScript :: !(Maybe Script), + -- ^ UTxO's script. + _utxoWithBytesTxHash :: !TxHash, + -- ^ UTxO's transaction hash. + _utxoWithBytesTxoutCbor :: !(Maybe (HexStringOf TxOutCbor)) + -- ^ Hex encoded transaction output CBOR bytes. + } + deriving stock (Show, Eq, Ord, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_utxoWithBytes", CamelToSnake]] UtxoWithBytes + +instance IsUtxo UtxoWithBytes where + getAddress = _utxoWithBytesAddress + getAssets = _utxoWithBytesAssets + getDatum = _utxoWithBytesDatum + getTxHash = _utxoWithBytesTxHash + getIndex = _utxoWithBytesIndex + getReferenceScript = _utxoWithBytesReferenceScript + +-- | Convert @V1@ API version UTxO type into corresponding @V0@ type. +v1UtxoToV0 :: UtxoWithBytes -> V0.Utxo +v1UtxoToV0 UtxoWithBytes {..} = V0.Utxo { + V0._utxoAddress = _utxoWithBytesAddress + , V0._utxoAssets = map v1AssetToV0 _utxoWithBytesAssets + , V0._utxoDatum = _utxoWithBytesDatum + , V0._utxoIndex = coerce _utxoWithBytesIndex + , V0._utxoReferenceScript = _utxoWithBytesReferenceScript + , V0._utxoTxHash = coerce _utxoWithBytesTxHash + , V0._utxoTxoutCbor = _utxoWithBytesTxoutCbor + } + +-- | A paginated response of transaction outputs. +data PaginatedUtxo = PaginatedUtxo + { _paginatedUtxoData :: ![UtxoWithBytes], + -- ^ List of UTxOs. + _paginatedUtxoLastUpdated :: !LastUpdated, + -- ^ See `LastUpdated`. + _paginatedUtxoNextCursor :: !(Maybe NextCursor) + -- ^ See `NextCursor` + } + deriving stock (Show, Eq, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "_paginatedUtxo", CamelToSnake]] PaginatedUtxo + +instance IsTimestamped PaginatedUtxo where + type TimestampedData PaginatedUtxo = [UtxoWithBytes] + getTimestampedData = _paginatedUtxoData + getTimestamp = _paginatedUtxoLastUpdated + +instance HasCursor PaginatedUtxo where + getNextCursor = _paginatedUtxoNextCursor diff --git a/test/Maestro/Test/Datum.hs b/test/Maestro/Test/Datum.hs index 5c1870e..3199480 100644 --- a/test/Maestro/Test/Datum.hs +++ b/test/Maestro/Test/Datum.hs @@ -5,7 +5,7 @@ import Test.Hspec import Text.RawString.QQ import Data.ByteString.Lazy (ByteString) -import Maestro.Types +import Maestro.Types.V0 spec_general :: Spec spec_general = do diff --git a/test/Maestro/Test/Epochs.hs b/test/Maestro/Test/Epochs.hs index d287ea6..ba80cbb 100644 --- a/test/Maestro/Test/Epochs.hs +++ b/test/Maestro/Test/Epochs.hs @@ -5,7 +5,7 @@ import Test.Hspec import Text.RawString.QQ import Data.ByteString.Lazy (ByteString) -import Maestro.Types +import Maestro.Types.V0 spec_general :: Spec spec_general = do diff --git a/test/Maestro/Test/General.hs b/test/Maestro/Test/General.hs index 95a42a1..ee17d07 100644 --- a/test/Maestro/Test/General.hs +++ b/test/Maestro/Test/General.hs @@ -9,7 +9,7 @@ import Text.RawString.QQ import Data.ByteString.Lazy (ByteString) import Data.Time.Calendar.OrdinalDate (fromOrdinalDate) -import Maestro.Types +import Maestro.Types.V0 spec_general :: Spec spec_general = do diff --git a/test/Maestro/Test/Pool.hs b/test/Maestro/Test/Pool.hs index b2b2017..ba89dcc 100644 --- a/test/Maestro/Test/Pool.hs +++ b/test/Maestro/Test/Pool.hs @@ -6,7 +6,7 @@ import Test.Hspec import Text.RawString.QQ import Data.ByteString.Lazy (ByteString) -import Maestro.Types +import Maestro.Types.V0 spec_pool :: Spec spec_pool = do diff --git a/test/Maestro/Test/Transaction.hs b/test/Maestro/Test/Transaction.hs index 30f2f7a..56be7f6 100644 --- a/test/Maestro/Test/Transaction.hs +++ b/test/Maestro/Test/Transaction.hs @@ -6,7 +6,7 @@ import Test.Hspec import Text.RawString.QQ import Data.ByteString.Lazy (ByteString) -import Maestro.Types +import Maestro.Types.V0 spec_pool :: Spec spec_pool = do @@ -107,4 +107,5 @@ txUtxoExpected = , _scriptType = PlutusV2 , _scriptJson = Nothing } + , _utxoTxoutCbor = Nothing }