From b5b0365710e09d06bd49efb79df15e6fbbbf187a Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Tue, 7 May 2024 16:10:58 +0530 Subject: [PATCH 1/5] feat: add taptools provider, add nft token in place order response Related to #57. --- README.md | 2 + geniusyield-dex-api/geniusyield-dex-api.cabal | 2 - .../src/GeniusYield/Api/Dex/PartialOrder.hs | 72 ++++++++- .../geniusyield-server-lib.cabal | 7 + .../src/GeniusYield/Server/Api.hs | 16 +- .../src/GeniusYield/Server/Config.hs | 1 + .../src/GeniusYield/Server/Ctx.hs | 11 ++ .../Server/Dex/HistoricalPrices/Maestro.hs | 5 - .../Server/Dex/HistoricalPrices/TapTools.hs | 30 ++++ .../Dex/HistoricalPrices/TapTools/Client.hs | 143 ++++++++++++++++++ .../GeniusYield/Server/Dex/PartialOrder.hs | 14 +- .../src/GeniusYield/Server/Run.hs | 14 +- .../src/GeniusYield/Server/Utils.hs | 24 +++ web/swagger/api.yaml | 97 ++++++++++++ 14 files changed, 413 insertions(+), 25 deletions(-) create mode 100644 geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs create mode 100644 geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs diff --git a/README.md b/README.md index ac62917c..9d5b41c5 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ For details please see the following section: maestroToken: YOUR_MAESTRO_TOKEN # API key to protect server endpoints with. It's value must be provided under `api-key` header of request. serverApiKey: YOUR_SECRET_KEY + # TapTools API key, to access historical prices using TapTools. + tapToolsApiKey: YOUR_TAP_TOOLS_KEY # Optionally, wallet key details if one wants server to be able to sign transactions using this key. wallet: tag: mnemonicWallet diff --git a/geniusyield-dex-api/geniusyield-dex-api.cabal b/geniusyield-dex-api/geniusyield-dex-api.cabal index 9cb137ad..cd76ce36 100644 --- a/geniusyield-dex-api/geniusyield-dex-api.cabal +++ b/geniusyield-dex-api/geniusyield-dex-api.cabal @@ -72,10 +72,8 @@ library build-depends: , aeson , base ^>=4.16.4.0 - , bytestring , containers , data-default - , file-embed , http-types , lens , mtl diff --git a/geniusyield-dex-api/src/GeniusYield/Api/Dex/PartialOrder.hs b/geniusyield-dex-api/src/GeniusYield/Api/Dex/PartialOrder.hs index 15d45a8f..6f855a0d 100644 --- a/geniusyield-dex-api/src/GeniusYield/Api/Dex/PartialOrder.hs +++ b/geniusyield-dex-api/src/GeniusYield/Api/Dex/PartialOrder.hs @@ -40,6 +40,10 @@ module GeniusYield.Api.Dex.PartialOrder ( -- * Tx constructors placePartialOrder, placePartialOrder', + placePartialOrder'', + placePartialOrderWithVersion, + placePartialOrderWithVersion', + placePartialOrderWithVersion'', completelyFillPartialOrder, partiallyFillPartialOrder, fillPartialOrder, @@ -603,7 +607,33 @@ placePartialOrder' → GYTxOutRef → PartialOrderConfigInfoF GYAddress → m (GYTxSkeleton 'PlutusV2) -placePartialOrder' pors = placePartialOrderWithVersion' pors defaultPOCVersion +placePartialOrder' pors addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd = snd <$> placePartialOrder'' pors addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd + +placePartialOrder'' + ∷ (GYDexApiMonad m a, HasCallStack) + ⇒ PORefs + → GYAddress + -- ^ Order owner + → (Natural, GYAssetClass) + -- ^ Amount and asset to offer. + → GYAssetClass + -- ^ The asset being asked for as payment. + → GYRational + -- ^ The price for one unit of the offered asset. + → Maybe GYTime + -- ^ The earliest time when the order can be filled (optional). + → Maybe GYTime + -- ^ The latest time when the order can be filled (optional). + → Natural + -- ^ Additional lovelace fee. + → Natural + -- ^ Additional fee in offered tokens. + → Maybe GYStakeCredential + -- ^ Stake credential of user. We do not support pointer reference. + → GYTxOutRef + → PartialOrderConfigInfoF GYAddress + → m (GYAssetClass, GYTxSkeleton 'PlutusV2) +placePartialOrder'' pors = placePartialOrderWithVersion'' pors defaultPOCVersion placePartialOrderWithVersion' ∷ (GYDexApiMonad m a, HasCallStack) @@ -630,7 +660,34 @@ placePartialOrderWithVersion' → GYTxOutRef → PartialOrderConfigInfoF GYAddress → m (GYTxSkeleton 'PlutusV2) -placePartialOrderWithVersion' pors pocVersion addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd = do +placePartialOrderWithVersion' pors pocVersion addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd = snd <$> placePartialOrderWithVersion'' pors pocVersion addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd + +placePartialOrderWithVersion'' + ∷ (GYDexApiMonad m a, HasCallStack) + ⇒ PORefs + → POCVersion + → GYAddress + -- ^ Order owner + → (Natural, GYAssetClass) + -- ^ Amount and asset to offer. + → GYAssetClass + -- ^ The asset being asked for as payment. + → GYRational + -- ^ The price for one unit of the offered asset. + → Maybe GYTime + -- ^ The earliest time when the order can be filled (optional). + → Maybe GYTime + -- ^ The latest time when the order can be filled (optional). + → Natural + -- ^ Additional lovelace fee. + → Natural + -- ^ Additional fee in offered tokens. + → Maybe GYStakeCredential + -- ^ Stake credential of user. We do not support pointer reference. + → GYTxOutRef + → PartialOrderConfigInfoF GYAddress + → m (GYAssetClass, GYTxSkeleton 'PlutusV2) +placePartialOrderWithVersion'' pors pocVersion addr (offerAmt, offerAC) priceAC price start end addLov addOff stakeCred cfgRef pocd = do when (offerAmt == 0) $ throwAppError $ PodNonPositiveAmount $ toInteger offerAmt when (price <= 0) $ throwAppError $ PodNonPositivePrice price when (offerAC == priceAC) $ throwAppError $ PodNonDifferentAssets offerAC @@ -696,11 +753,12 @@ placePartialOrderWithVersion' pors pocVersion addr (offerAmt, offerAC) priceAC p o = mkGYTxOut outAddr' offerV (datumFromPlutusData od) return $ - mustHaveInput nftInput - <> mustHaveOutput o - <> mustMint (GYMintReference porMintRef $ mintingPolicyToScript policy) nftRedeemer nftName 1 - <> mustHaveRefInput cfgRef - <> mustHaveTxMetadata stampPlaced + (nft,) $ + mustHaveInput nftInput + <> mustHaveOutput o + <> mustMint (GYMintReference porMintRef $ mintingPolicyToScript policy) nftRedeemer nftName 1 + <> mustHaveRefInput cfgRef + <> mustHaveTxMetadata stampPlaced -- | Fills an order. If the provided amount of offered tokens to buy is equal to the offered amount, then we completely fill the order. Otherwise, it gets partially filled. fillPartialOrder diff --git a/geniusyield-server-lib/geniusyield-server-lib.cabal b/geniusyield-server-lib/geniusyield-server-lib.cabal index 919440bd..2b3c5b23 100644 --- a/geniusyield-server-lib/geniusyield-server-lib.cabal +++ b/geniusyield-server-lib/geniusyield-server-lib.cabal @@ -58,6 +58,8 @@ library GeniusYield.Server.Constants GeniusYield.Server.Ctx GeniusYield.Server.Dex.HistoricalPrices.Maestro + GeniusYield.Server.Dex.HistoricalPrices.TapTools + GeniusYield.Server.Dex.HistoricalPrices.TapTools.Client GeniusYield.Server.Dex.Markets GeniusYield.Server.Dex.PartialOrder GeniusYield.Server.ErrorMiddleware @@ -89,6 +91,8 @@ library , geniusyield-dex-api , geniusyield-orderbot-lib , githash + , http-client + , http-client-tls , http-types , insert-ordered-containers , lens @@ -96,6 +100,9 @@ library , plutus-ledger-api , ply-core , rio + , servant + , servant-client + , servant-client-core , servant-foreign , servant-server , servant-swagger diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Api.hs b/geniusyield-server-lib/src/GeniusYield/Server/Api.hs index c37e131a..81dd197a 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Api.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Api.hs @@ -34,6 +34,7 @@ import GeniusYield.Server.Auth (APIKeyAuthProtect, V0) import GeniusYield.Server.Constants (gitHash) import GeniusYield.Server.Ctx import GeniusYield.Server.Dex.HistoricalPrices.Maestro +import GeniusYield.Server.Dex.HistoricalPrices.TapTools (TapToolsPriceHistoryAPI, handleTapToolsPriceHistoryApi) import GeniusYield.Server.Dex.Markets (MarketsAPI, handleMarketsApi) import GeniusYield.Server.Dex.PartialOrder (OrderInfo (..), OrdersAPI, handleOrdersApi, poiToOrderInfo) import GeniusYield.Server.Tx (TxAPI, handleTxApi) @@ -186,6 +187,10 @@ type OrderBookAPI = Summary "Order book" :> Description "Get order book for a sp type BalancesAPI = Summary "Balances" :> Description "Get token balances of an address." :> Capture "address" GYAddressBech32 :> Get '[JSON] GYBalance +type HistoricalPricesAPI = + "maestro" :> MaestroPriceHistoryAPI + :<|> "tap-tools" :> TapToolsPriceHistoryAPI + type V0API = "settings" :> SettingsAPI :<|> "orders" :> OrdersAPI @@ -194,7 +199,7 @@ type V0API = :<|> "trading-fees" :> TradingFeesAPI :<|> "assets" :> AssetsAPI :<|> "order-books" :> OrderBookAPI - :<|> "historical-prices" :> "maestro" :> MaestroPriceHistoryAPI + :<|> "historical-prices" :> HistoricalPricesAPI :<|> "balances" :> BalancesAPI type GeniusYieldAPI = APIKeyAuthProtect :> V0 :> V0API @@ -239,7 +244,7 @@ geniusYieldAPISwagger = & applyTagsFor (subOperations (Proxy ∷ Proxy ("trading-fees" +> TradingFeesAPI)) (Proxy ∷ Proxy GeniusYieldAPI)) ["Trading Fees" & description ?~ "Endpoint to get trading fees of DEX."] & applyTagsFor (subOperations (Proxy ∷ Proxy ("assets" +> AssetsAPI)) (Proxy ∷ Proxy GeniusYieldAPI)) ["Assets" & description ?~ "Endpoint to fetch asset details."] & applyTagsFor (subOperations (Proxy ∷ Proxy ("order-books" +> OrderBookAPI)) (Proxy ∷ Proxy GeniusYieldAPI)) ["Order Book" & description ?~ "Endpoint to fetch order book."] - & applyTagsFor (subOperations (Proxy ∷ Proxy ("historical-prices" +> "maestro" :> MaestroPriceHistoryAPI)) (Proxy ∷ Proxy GeniusYieldAPI)) ["Historical Prices" & description ?~ "Endpoints to fetch historical prices."] + & applyTagsFor (subOperations (Proxy ∷ Proxy ("historical-prices" +> HistoricalPricesAPI)) (Proxy ∷ Proxy GeniusYieldAPI)) ["Historical Prices" & description ?~ "Endpoints to fetch historical prices."] & applyTagsFor (subOperations (Proxy ∷ Proxy ("balances" +> BalancesAPI)) (Proxy ∷ Proxy GeniusYieldAPI)) ["Balances" & description ?~ "Endpoint to fetch token balances."] geniusYieldServer ∷ Ctx → ServerT GeniusYieldAPI IO @@ -252,11 +257,16 @@ geniusYieldServer ctx = :<|> handleTradingFeesApi ctx :<|> handleAssetsApi ctx :<|> handleOrderBookApi ctx - :<|> handleMaestroPriceHistoryApi ctx + :<|> handleHistoricalPricesApi ctx :<|> handleBalancesApi ctx where ignoredAuthResult f _authResult = f +handleHistoricalPricesApi ∷ Ctx → ServerT HistoricalPricesAPI IO +handleHistoricalPricesApi ctx = + handleMaestroPriceHistoryApi ctx + :<|> handleTapToolsPriceHistoryApi ctx + type MainAPI = GeniusYieldAPI diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Config.hs b/geniusyield-server-lib/src/GeniusYield/Server/Config.hs index e04b15df..293c3b50 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Config.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Config.hs @@ -56,6 +56,7 @@ data ServerConfig = ServerConfig scPort ∷ !Port, scWallet ∷ !(Maybe UserWallet), scServerApiKey ∷ !(Confidential Text), + scTapToolsApiKey ∷ !(Maybe (Confidential Text)), scCollateral ∷ !(Maybe GYTxOutRef), scStakeAddress ∷ !(Maybe GYStakeAddressBech32) } diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Ctx.hs b/geniusyield-server-lib/src/GeniusYield/Server/Ctx.hs index 59ac32a7..9e2775b8 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Ctx.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Ctx.hs @@ -2,6 +2,8 @@ module GeniusYield.Server.Ctx ( DEXInfo (..), dexInfoDefaultMainnet, dexInfoDefaultPreprod, + TapToolsApiKey, + TapToolsEnv (..), Ctx (..), runSkeletonI, runSkeletonWithStrategyI, @@ -19,6 +21,14 @@ import GeniusYield.Transaction import GeniusYield.TxBuilder import GeniusYield.Types import RIO +import Servant.Client (ClientEnv) + +type TapToolsApiKey = Text + +data TapToolsEnv = TapToolsEnv + { tteClientEnv ∷ !ClientEnv, + tteApiKey ∷ !TapToolsApiKey + } -- | Server context: configuration & shared state. data Ctx = Ctx @@ -26,6 +36,7 @@ data Ctx = Ctx ctxProviders ∷ !GYProviders, ctxDexInfo ∷ !DEXInfo, ctxMaestroProvider ∷ !MaestroProvider, + ctxTapToolsProvider ∷ !(Maybe TapToolsEnv), ctxSigningKey ∷ !(Maybe (Pair GYSomePaymentSigningKey GYAddress)), ctxCollateral ∷ !(Maybe GYTxOutRef), ctxStakeAddress ∷ !(Maybe GYStakeAddressBech32) diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs index 6958e79c..d2d64ed6 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs @@ -8,9 +8,7 @@ module GeniusYield.Server.Dex.HistoricalPrices.Maestro ( import Control.Lens ((?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) -import Data.Kind (Type) import Data.Swagger qualified as Swagger -import Data.Swagger.Internal qualified as Swagger import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import Deriving.Aeson import Fmt @@ -93,9 +91,6 @@ newtype MaestroOrder = MaestroOrder {unMaestroOrder ∷ Order} deriving stock (Show) deriving newtype (ToHttpApiData, FromHttpApiData, Enum, Bounded, ToJSON) -commonEnumParamSchemaRecipe ∷ ∀ a (t ∷ Swagger.SwaggerKind Type). (Bounded a, Enum a, ToJSON a) ⇒ Proxy a → Swagger.ParamSchema t -commonEnumParamSchemaRecipe _ = mempty & Swagger.type_ ?~ Swagger.SwaggerString & Swagger.enum_ ?~ fmap toJSON [(minBound ∷ a) .. maxBound] - instance Swagger.ToParamSchema MaestroOrder where toParamSchema = commonEnumParamSchemaRecipe diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs new file mode 100644 index 00000000..a375be00 --- /dev/null +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs @@ -0,0 +1,30 @@ +module GeniusYield.Server.Dex.HistoricalPrices.TapTools ( + TapToolsPriceHistoryAPI, + handleTapToolsPriceHistoryApi, +) where + +import Fmt +import GeniusYield.Server.Ctx +import GeniusYield.Server.Dex.HistoricalPrices.TapTools.Client (TapToolsInterval, TapToolsOHLCV, TapToolsUnit (TapToolsUnit), handleTapToolsError, tapToolsOHLCV) +import GeniusYield.Server.Utils +import GeniusYield.Types +import RIO hiding (logDebug, logInfo) +import Servant + +type TapToolsPriceHistoryAPI = + Summary "Get price history using TapTools." + :> Description "This endpoint internally calls TapTools's \"Token price OHLCV\" endpoint. Note that only the liquidity pools involving ADA and the given asset class is considered to get for aggregated price information. Price returned is in ADA." + :> Capture "asset" GYAssetClass + :> QueryParam' '[Required, Strict] "interval" TapToolsInterval + :> QueryParam "numIntervals" Natural + :> Get '[JSON] [TapToolsOHLCV] + +throwNoTapToolsKeyError ∷ IO a +throwNoTapToolsKeyError = throwIO $ err500 {errBody = "No API key configured for TapTools."} + +handleTapToolsPriceHistoryApi ∷ Ctx → GYAssetClass → TapToolsInterval → Maybe Natural → IO [TapToolsOHLCV] +handleTapToolsPriceHistoryApi ctx token tti mttni = do + logInfo ctx $ "Fetching price history. Token: " +|| token ||+ ", interval: " +|| tti ||+ "" + case ctxTapToolsProvider ctx of + Nothing → throwNoTapToolsKeyError + Just te → try (tapToolsOHLCV te (Just (TapToolsUnit token)) tti mttni) >>= handleTapToolsError "handleTapToolsPriceHistory" diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs new file mode 100644 index 00000000..76fa2894 --- /dev/null +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs @@ -0,0 +1,143 @@ +module GeniusYield.Server.Dex.HistoricalPrices.TapTools.Client ( + TapToolsUnit (..), + TapToolsInterval (..), + TapToolsOHLCV (..), + TapToolsAPI, + TapToolsOHLCVAPI, + tapToolsClientEnv, + tapToolsOHLCV, + TapToolsException, + handleTapToolsError, +) where + +import Data.Aeson (ToJSON (..)) +import Data.Swagger qualified as Swagger +import Data.Time.Clock.POSIX +import Deriving.Aeson +import GHC.TypeLits (Symbol, symbolVal) +import GeniusYield.Server.Ctx (TapToolsApiKey, TapToolsEnv (tteApiKey, tteClientEnv)) +import GeniusYield.Server.Utils (commonEnumParamSchemaRecipe, hideServantClientErrorHeader) +import GeniusYield.Swagger.Utils +import GeniusYield.Types (GYAssetClass) +import Maestro.Types.Common (LowerFirst) +import Network.HTTP.Client (newManager) +import Network.HTTP.Client.TLS (tlsManagerSettings) +import RIO +import RIO.Text qualified as Text +import Servant.API +import Servant.Client + +{- $setup + +>>> :set -XOverloadedStrings -XTypeApplications +>>> import GeniusYield.Types +-} + +newtype TapToolsUnit = TapToolsUnit {unTapToolsUnit ∷ GYAssetClass} + deriving stock (Eq, Ord, Show) + +{- | + +>>> toUrlPiece $ TapToolsUnit "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53" +"dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb0014df1047454e53" +-} +instance ToHttpApiData TapToolsUnit where + toUrlPiece (TapToolsUnit ac) = removeDot $ toUrlPiece ac + where + removeDot = Text.filter (/= '.') + +data TapToolsInterval = TTI3m | TTI5m | TTI15m | TTI30m | TTI1h | TTI2h | TTI4h | TTI12h | TTI1d | TTI3d | TTI1w | TTI1M + deriving stock (Eq, Ord, Enum, Bounded, Data, Typeable, Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[StripPrefix "TTI"]] TapToolsInterval + +-- >>> show TTI1M +-- "1M" +instance Show TapToolsInterval where + show = toConstr >>> show >>> drop 3 + +instance ToHttpApiData TapToolsInterval where + toQueryParam = Text.pack . show + +instance FromHttpApiData TapToolsInterval where + parseQueryParam = \case + "3m" → Right TTI3m + "5m" → Right TTI5m + "15m" → Right TTI15m + "30m" → Right TTI30m + "1h" → Right TTI1h + "2h" → Right TTI2h + "4h" → Right TTI4h + "12h" → Right TTI12h + "1d" → Right TTI1d + "3d" → Right TTI3d + "1w" → Right TTI1w + "1M" → Right TTI1M + x → Left $ "Invalid TapToolsInterval: " <> x + +instance Swagger.ToParamSchema TapToolsInterval where + toParamSchema = commonEnumParamSchemaRecipe + +type TapToolsOHLCVPrefix ∷ Symbol +type TapToolsOHLCVPrefix = "tapToolsOHLCV" + +data TapToolsOHLCV = TapToolsOHLCV + { tapToolsOHLCVTime ∷ !POSIXTime, + tapToolsOHLCVOpen ∷ !Double, + tapToolsOHLCVHigh ∷ !Double, + tapToolsOHLCVLow ∷ !Double, + tapToolsOHLCVClose ∷ !Double, + tapToolsOHLCVVolume ∷ !Double + } + deriving stock (Eq, Ord, Show, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix TapToolsOHLCVPrefix, LowerFirst]] TapToolsOHLCV + +instance Swagger.ToSchema TapToolsOHLCV where + declareNamedSchema = + let open = 0.15800583264941748 + in Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions {Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @TapToolsOHLCVPrefix} + & addSwaggerDescription "Get a specific token's trended (open, high, low, close, volume) price data." + & addSwaggerExample (toJSON $ TapToolsOHLCV {tapToolsOHLCVTime = 1_715_007_300, tapToolsOHLCVOpen = open, tapToolsOHLCVHigh = open, tapToolsOHLCVLow = open, tapToolsOHLCVClose = open, tapToolsOHLCVVolume = 120}) + +type TapToolsApiKeyHeaderName ∷ Symbol +type TapToolsApiKeyHeaderName = "x-api-key" + +type TapToolsAPI = + Header' '[Required] TapToolsApiKeyHeaderName TapToolsApiKey :> TapToolsOHLCVAPI + +type TapToolsOHLCVAPI = + "token" + :> "ohlcv" + :> QueryParam "unit" TapToolsUnit + :> QueryParam' '[Required, Strict] "interval" TapToolsInterval + :> QueryParam "numIntervals" Natural + :> Get '[JSON] [TapToolsOHLCV] + +_tapToolsOHLCV ∷ TapToolsApiKey → Maybe TapToolsUnit → TapToolsInterval → Maybe Natural → ClientM [TapToolsOHLCV] +_tapToolsOHLCV = client (Proxy @TapToolsAPI) + +tapToolsBaseUrl ∷ String +tapToolsBaseUrl = "https://openapi.taptools.io/api/v1" + +tapToolsClientEnv ∷ IO ClientEnv +tapToolsClientEnv = do + baseUrl ← parseBaseUrl tapToolsBaseUrl + manager ← newManager tlsManagerSettings + pure $ mkClientEnv manager baseUrl + +runTapToolsClient ∷ TapToolsEnv → ClientM a → IO (Either ClientError a) +runTapToolsClient (tteClientEnv → ce) c = runClientM c ce + +-- | Exceptions. +data TapToolsException + = -- | Error from the TapTools API. + TapToolsApiError !Text !ClientError + deriving stock (Eq, Show) + deriving anyclass (Exception) + +handleTapToolsError ∷ Text → Either ClientError a → IO a +handleTapToolsError locationInfo = either (throwIO . TapToolsApiError locationInfo . hideServantClientErrorHeader (fromString $ symbolVal (Proxy @TapToolsApiKeyHeaderName))) pure -- TODO: Check if api-key is actually hidden. + +tapToolsOHLCV ∷ TapToolsEnv → Maybe TapToolsUnit → TapToolsInterval → Maybe Natural → IO [TapToolsOHLCV] +tapToolsOHLCV env@(tteApiKey → apiKey) ttu tti mttni = _tapToolsOHLCV apiKey ttu tti mttni & runTapToolsClient env >>= handleTapToolsError "tapToolsOHLCV" diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs index a8e1185e..464ac9ca 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs @@ -15,7 +15,7 @@ import Data.Swagger.Internal.Schema qualified as Swagger import Deriving.Aeson import Fmt import GHC.TypeLits (AppendSymbol, Symbol) -import GeniusYield.Api.Dex.PartialOrder (PartialOrderInfo (..), cancelMultiplePartialOrders', fillMultiplePartialOrders', getPartialOrdersInfos, getPartialOrdersInfos', getVersionsInOrders, orderByNft, partialOrderPrice', placePartialOrder', preferentiallySelectLatestPocd, preferentiallySelectLatestVersion, roundFunctionForPOCVersion) +import GeniusYield.Api.Dex.PartialOrder (PartialOrderInfo (..), cancelMultiplePartialOrders', fillMultiplePartialOrders', getPartialOrdersInfos, getPartialOrdersInfos', getVersionsInOrders, orderByNft, partialOrderPrice', placePartialOrder'', preferentiallySelectLatestPocd, preferentiallySelectLatestVersion, roundFunctionForPOCVersion) import GeniusYield.Api.Dex.PartialOrderConfig (RefPocd (..), SomeRefPocd (SomeRefPocd), fetchPartialOrderConfig, fetchPartialOrderConfigs) import GeniusYield.HTTP.Errors import GeniusYield.OrderBot.Domain.Markets (OrderAssetPair (..)) @@ -211,7 +211,8 @@ data PlaceOrderTransactionDetails = PlaceOrderTransactionDetails potdMakerOfferedPercentFee ∷ !GYRational, potdMakerOfferedPercentFeeAmount ∷ !GYNatural, potdLovelaceDeposit ∷ !GYNatural, - potdOrderRef ∷ !GYTxOutRef + potdOrderRef ∷ !GYTxOutRef, + potdNFTToken ∷ !GYAssetClass } deriving stock (Generic) deriving @@ -370,9 +371,9 @@ handlePlaceOrder ctx@Ctx {..} pops@PlaceOrderParameters {..} = do let unitPrice = rationalFromGHC $ toInteger popPriceAmount % toInteger popOfferAmount - txBody ← - runSkeletonI ctx (NonEmpty.toList popAddresses') changeAddr popCollateral $ - placePartialOrder' + (nftAC, txBody) ← + runSkeletonF ctx (NonEmpty.toList popAddresses') changeAddr popCollateral $ + placePartialOrder'' porefs changeAddr (naturalToGHC popOfferAmount, popOfferToken) @@ -395,7 +396,8 @@ handlePlaceOrder ctx@Ctx {..} pops@PlaceOrderParameters {..} = do potdMakerOfferedPercentFee = 100 * pociMakerFeeRatio pocd, potdMakerOfferedPercentFeeAmount = roundFunctionForPOCVersion pocVersion $ toRational popOfferAmount * rationalToGHC (pociMakerFeeRatio pocd), potdLovelaceDeposit = fromIntegral $ pociMinDeposit pocd, - potdOrderRef = txOutRefFromTuple (txId, 0) + potdOrderRef = txOutRefFromTuple (txId, 0), + potdNFTToken = nftAC } resolveCtxSigningKeyInfo ∷ Ctx → IO (Strict.Pair GYSomePaymentSigningKey GYAddress) diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Run.hs b/geniusyield-server-lib/src/GeniusYield/Server/Run.hs index 99712005..4b0df25b 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Run.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Run.hs @@ -18,6 +18,11 @@ import GeniusYield.Server.Auth import GeniusYield.Server.Config (ServerConfig (..), coreConfigFromServerConfig, optionalSigningKeyFromServerConfig, serverConfigOptionalFPIO) import GeniusYield.Server.Constants (gitHash) import GeniusYield.Server.Ctx +-- import RIO.ByteString.Lazy qualified as BL + +-- import Servant.PY (requests, writePythonForAPI) + +import GeniusYield.Server.Dex.HistoricalPrices.TapTools.Client (tapToolsClientEnv) import GeniusYield.Server.ErrorMiddleware import GeniusYield.Server.RequestLoggerMiddleware (gcpReqLogger) import GeniusYield.Server.Utils @@ -27,10 +32,8 @@ import Network.Wai.Handler.Warp qualified as Warp import PackageInfo_geniusyield_server_lib qualified as PackageInfo import RIO hiding (Handler, logDebug, logErrorS, logInfo, logInfoS, onException) import RIO.ByteString qualified as B --- import RIO.ByteString.Lazy qualified as BL import RIO.Text.Lazy qualified as LT import Servant --- import Servant.PY (requests, writePythonForAPI) import Servant.Server.Experimental.Auth (AuthHandler) import Servant.Server.Internal.ServerError (responseServerError) import System.TimeManager (TimeoutThread (..)) @@ -39,6 +42,12 @@ runServer ∷ Maybe FilePath → IO () runServer mfp = do serverConfig ← serverConfigOptionalFPIO mfp menv ← networkIdToMaestroEnv (case scMaestroToken serverConfig of Confidential t → t) (scNetworkId serverConfig) + mtenv ← + case scTapToolsApiKey serverConfig of + Nothing → pure Nothing + Just (Confidential apiKey) → do + tce ← tapToolsClientEnv + pure $ Just $ TapToolsEnv {tteClientEnv = tce, tteApiKey = apiKey} optionalSigningKey ← optionalSigningKeyFromServerConfig serverConfig let nid = scNetworkId serverConfig coreCfg = coreConfigFromServerConfig serverConfig @@ -82,6 +91,7 @@ runServer mfp = do | nid == GYTestnetPreprod → dexInfoDefaultPreprod | otherwise → error "Only mainnet & preprod network are supported", ctxMaestroProvider = MaestroProvider menv, + ctxTapToolsProvider = mtenv, ctxSigningKey = optionalSigningKey, ctxCollateral = scCollateral serverConfig, ctxStakeAddress = scStakeAddress serverConfig diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs b/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs index 7b25693e..36ad5f42 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs @@ -9,14 +9,23 @@ module GeniusYield.Server.Utils ( addSwaggerDescription, addSwaggerExample, bytestringToString, + hideServantClientErrorHeader, + commonEnumParamSchemaRecipe, ) where +import Control.Lens ((?~)) +import Data.Swagger qualified as Swagger +import Data.Swagger.Internal qualified as Swagger import GeniusYield.Imports import GeniusYield.Server.Ctx import GeniusYield.Swagger.Utils (addSwaggerDescription, addSwaggerExample, dropSymbolAndCamelToSnake) import GeniusYield.Types +import Network.HTTP.Client qualified as Http +import Network.HTTP.Types qualified as Http import RIO hiding (logDebug, logInfo) import RIO.Text qualified as Text +import Servant.Client qualified as Servant +import Servant.Client.Core qualified as Servant logDebug ∷ HasCallStack ⇒ Ctx → String → IO () logDebug ctx = gyLogDebug (ctxProviders ctx) mempty @@ -40,3 +49,18 @@ isMatchedException (etype :>> etypes) se = isJust (f etype) || isMatchedExceptio bytestringToString ∷ ByteString → String bytestringToString = RIO.decodeUtf8Lenient >>> Text.unpack + +hideServantClientErrorHeader ∷ Http.HeaderName → Servant.ClientError → Servant.ClientError +hideServantClientErrorHeader headerName clientError = case clientError of + Servant.FailureResponse reqF res → Servant.FailureResponse reqF {Servant.requestHeaders = renameHeader <$> Servant.requestHeaders reqF} res + Servant.ConnectionError se → case fromException @Http.HttpException se of + Just he → case he of + Http.HttpExceptionRequest req content → Servant.ConnectionError $ SomeException $ Http.HttpExceptionRequest req {Http.requestHeaders = renameHeader <$> Http.requestHeaders req} content + _anyOther → clientError + Nothing → clientError + _anyOther → clientError + where + renameHeader (h, v) = if h == headerName then (h, "hidden") else (h, v) + +commonEnumParamSchemaRecipe ∷ ∀ a (t ∷ Swagger.SwaggerKind Type). (Bounded a, Enum a, ToJSON a) ⇒ Proxy a → Swagger.ParamSchema t +commonEnumParamSchemaRecipe _ = mempty & Swagger.type_ ?~ Swagger.SwaggerString & Swagger.enum_ ?~ fmap toJSON [(minBound ∷ a) .. maxBound] diff --git a/web/swagger/api.yaml b/web/swagger/api.yaml index 39bfb11b..acc8444d 100644 --- a/web/swagger/api.yaml +++ b/web/swagger/api.yaml @@ -457,6 +457,8 @@ definitions: type: string maker_offered_percent_fee_amount: $ref: '#/definitions/GYNatural' + nft_token: + $ref: '#/definitions/GYAssetClass' order_ref: $ref: '#/definitions/GYTxOutRef' transaction: @@ -474,6 +476,7 @@ definitions: - maker_offered_percent_fee_amount - lovelace_deposit - order_ref + - nft_token type: object Settings: description: Genius Yield Server settings. @@ -498,6 +501,43 @@ definitions: - revision - backend type: object + TapToolsOHLCV: + description: Get a specific token's trended (open, high, low, close, volume) price + data. + example: + close: 0.15800583264941748 + high: 0.15800583264941748 + low: 0.15800583264941748 + open: 0.15800583264941748 + time: 1715007300 + volume: 120 + properties: + close: + format: double + type: number + high: + format: double + type: number + low: + format: double + type: number + open: + format: double + type: number + time: + multipleOf: 1.0e-12 + type: number + volume: + format: double + type: number + required: + - time + - open + - high + - low + - close + - volume + type: object TradingFees: description: Trading fees of DEX. properties: @@ -666,6 +706,63 @@ paths: summary: Get price history using Maestro. tags: - Historical Prices + /v0/historical-prices/tap-tools/{asset}: + get: + description: This endpoint internally calls TapTools's "Token price OHLCV" endpoint. + Note that only the liquidity pools involving ADA and the given asset class + is considered to get for aggregated price information. Price returned is in + ADA. + parameters: + - in: path + name: asset + required: true + type: string + - enum: + - 3m + - 5m + - 15m + - 30m + - 1h + - 2h + - 4h + - 12h + - 1d + - 3d + - 1w + - 1M + in: query + name: interval + required: true + type: string + - exclusiveMinimum: false + in: query + minimum: 0 + name: numIntervals + required: false + type: integer + produces: + - application/json;charset=utf-8 + responses: + '200': + description: '' + schema: + items: + $ref: '#/definitions/TapToolsOHLCV' + type: array + '400': + description: Invalid `numIntervals` or `interval` or `asset` + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '500': + description: Internal server error + security: + - api-key: [] + summary: Get price history using TapTools. + tags: + - Historical Prices /v0/markets: get: description: Returns the list of markets information supported by GeniusYield From a63161fae182ae82b6c403ceacd202511ff7da48 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Tue, 7 May 2024 17:10:40 +0530 Subject: [PATCH 2/5] feat: add endpoint to get details of an order from it's nft token identifier Related to #57. --- .../geniusyield-server-lib.cabal | 4 +- .../src/GeniusYield/Server/Api.hs | 1 + .../src/GeniusYield/Server/Auth.hs | 72 ------------------- .../GeniusYield/Server/Dex/PartialOrder.hs | 41 +++++++++++ .../src/GeniusYield/Server/ErrorMiddleware.hs | 5 +- .../src/GeniusYield/Server/Orphans.hs | 70 ++++++++++++++++++ web/swagger/api.yaml | 33 +++++++++ 7 files changed, 151 insertions(+), 75 deletions(-) create mode 100644 geniusyield-server-lib/src/GeniusYield/Server/Orphans.hs diff --git a/geniusyield-server-lib/geniusyield-server-lib.cabal b/geniusyield-server-lib/geniusyield-server-lib.cabal index 2b3c5b23..72b2cb9c 100644 --- a/geniusyield-server-lib/geniusyield-server-lib.cabal +++ b/geniusyield-server-lib/geniusyield-server-lib.cabal @@ -64,6 +64,7 @@ library GeniusYield.Server.Dex.PartialOrder GeniusYield.Server.ErrorMiddleware GeniusYield.Server.Options + GeniusYield.Server.Orphans GeniusYield.Server.RequestLoggerMiddleware GeniusYield.Server.Run GeniusYield.Server.Tx @@ -80,7 +81,7 @@ library build-depends: , aeson , atlas-cardano - , base ^>=4.16.4.0 + , base ^>=4.16.4.0 , binary , bytestring , cardano-api @@ -101,6 +102,7 @@ library , ply-core , rio , servant + , servant-checked-exceptions , servant-client , servant-client-core , servant-foreign diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Api.hs b/geniusyield-server-lib/src/GeniusYield/Server/Api.hs index 81dd197a..51c4573a 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Api.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Api.hs @@ -37,6 +37,7 @@ import GeniusYield.Server.Dex.HistoricalPrices.Maestro import GeniusYield.Server.Dex.HistoricalPrices.TapTools (TapToolsPriceHistoryAPI, handleTapToolsPriceHistoryApi) import GeniusYield.Server.Dex.Markets (MarketsAPI, handleMarketsApi) import GeniusYield.Server.Dex.PartialOrder (OrderInfo (..), OrdersAPI, handleOrdersApi, poiToOrderInfo) +import GeniusYield.Server.Orphans () import GeniusYield.Server.Tx (TxAPI, handleTxApi) import GeniusYield.Server.Utils import GeniusYield.TxBuilder (GYTxQueryMonad (utxosAtAddress)) diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Auth.hs b/geniusyield-server-lib/src/GeniusYield/Server/Auth.hs index 6cba7563..4718973a 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Auth.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Auth.hs @@ -1,5 +1,3 @@ -{-# OPTIONS_GHC -Wno-orphans #-} - module GeniusYield.Server.Auth ( V0, ApiKey, @@ -10,17 +8,12 @@ module GeniusYield.Server.Auth ( APIKeyAuthProtect, ) where -import Control.Lens (at, (?~)) -import Data.HashMap.Strict.InsOrd qualified as IOHM -import Data.Swagger import GHC.TypeLits (Symbol, symbolVal) import Network.Wai (Request (requestHeaders)) import RIO import RIO.Text qualified as T import Servant -import Servant.Foreign import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData, mkAuthHandler) -import Servant.Swagger type V0 ∷ Symbol type V0 = "v0" @@ -50,68 +43,3 @@ apiKeyAuthHandler (ApiKey key) = mkAuthHandler handler type APIKeyAuthProtect = AuthProtect ApiKeyHeader type instance AuthServerData APIKeyAuthProtect = () - -instance HasSwagger api ⇒ HasSwagger (APIKeyAuthProtect :> api) where - toSwagger _ = - toSwagger (Proxy ∷ Proxy api) - & securityDefinitions - .~ SecurityDefinitions (IOHM.fromList [(apiKeyHeaderText, apiKeySecurityScheme)]) - -- & paths - -- . at signingKeyReqEndpoint - -- . _Just - -- . post - -- . _Just - -- . responses - -- %~ add500SigningKeyFailureResponse - -- & paths - -- . at signingKeyReqEndpoint - -- . _Just - -- . delete - -- . _Just - -- . responses - -- %~ add500SigningKeyFailureResponse - & allOperations - . security - .~ [SecurityRequirement (IOHM.singleton apiKeyHeaderText [])] - & allOperations - . responses - %~ addCommonResponses - where - apiKeySecurityScheme ∷ SecurityScheme - apiKeySecurityScheme = - SecurityScheme - { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams apiKeyHeaderText ApiKeyHeader), - _securitySchemeDescription = Just "API key for accessing the server's API." - } - addCommonResponses ∷ Responses → Responses - addCommonResponses resps = resps & at 401 ?~ Inline response401 & at 403 ?~ Inline response403 & at 500 ?~ Inline response500 - - -- add500SigningKeyFailureResponse ∷ Responses → Responses - -- add500SigningKeyFailureResponse resps = resps & at 500 ?~ Inline response500SigningKeyFailure - - response401 ∷ Response - response401 = mempty & description .~ "Unauthorized access - API key missing" - - response403 ∷ Response - response403 = mempty & description .~ "Forbidden - The API key does not have permission to perform the request" - - response500 ∷ Response - response500 = mempty & description .~ "Internal server error" - --- response500SigningKeyFailure ∷ Response --- response500SigningKeyFailure = mempty & description .~ "Internal server error - Corresponding signing key is not configured" - --- signingKeyReqEndpoint = "/" <> symbolVal (Proxy ∷ Proxy V0) <> "/orders" - --- `HasForeign` instance for `APIKeyAuthProtect :> api` is required to generate client code using libraries such as `servant-py`. --- This is written with help from https://github.com/haskell-servant/servant-auth/issues/8#issue-185541839. -instance ∀ lang ftype api. (HasForeign lang ftype api, HasForeignType lang ftype Text) ⇒ HasForeign lang ftype (APIKeyAuthProtect :> api) where - type Foreign ftype (APIKeyAuthProtect :> api) = Foreign ftype api - foreignFor lang Proxy Proxy subR = foreignFor lang Proxy (Proxy ∷ Proxy api) subR' - where - subR' = subR {_reqHeaders = HeaderArg arg : _reqHeaders subR} - arg = - Arg - { _argName = "api-key", - _argType = typeFor lang (Proxy ∷ Proxy ftype) (Proxy ∷ Proxy Text) - } diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs index 464ac9ca..0bd7c4fe 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs @@ -4,6 +4,8 @@ module GeniusYield.Server.Dex.PartialOrder ( OrderInfo (..), poiToOrderInfo, PodServerException (..), + PodOrderNotFound (..), + ErrDescription (..), ) where import Data.Aeson (ToJSON (..)) @@ -31,6 +33,7 @@ import RIO.Map qualified as Map import RIO.NonEmpty qualified as NonEmpty import RIO.Text qualified as T import Servant +import Servant.Checked.Exceptions -- | Number of orders that we at most allow to be filled in a single transaction. maxFillOrders ∷ GYNatural @@ -44,6 +47,20 @@ data PodServerException deriving stock (Show) deriving anyclass (Exception) +-- | When order whose details is queried for is not found. +data PodOrderNotFound = PodOrderNotFound + deriving (Eq, Show, Generic) + deriving anyclass (Exception, ToJSON) + +instance ErrStatus PodOrderNotFound where + toErrStatus _ = status404 + +class ErrDescription e where + toErrDescription ∷ e → Text + +instance ErrDescription PodOrderNotFound where + toErrDescription _ = "Order not found" + instance IsGYApiError PodServerException where toApiError PodMultiFillMoreThanAllowed = GYApiError @@ -58,6 +75,14 @@ instance IsGYApiError PodServerException where gaeMsg = "Given orders are not having same payment token" } +instance IsGYApiError PodOrderNotFound where + toApiError PodOrderNotFound = + GYApiError + { gaeErrorCode = "ORDER_NOT_FOUND", + gaeHttpStatus = status404, + gaeMsg = toErrDescription PodOrderNotFound + } + type OrderInfoPrefix ∷ Symbol type OrderInfoPrefix = "oi" @@ -344,6 +369,12 @@ type OrdersAPI = :> "details" :> ReqBody '[JSON] [GYAssetClass] :> Post '[JSON] [OrderInfoDetailed] + :<|> Summary "Get order details" + :> Description "Get details of an order using it's unique NFT token. Note that each order is identified uniquely by an associated NFT token which can then later be used to retrieve it's details across partial fills." + :> "details" + :> Capture "nft-token" GYAssetClass + :> Throws PodOrderNotFound + :> Get '[JSON] OrderInfoDetailed :<|> Summary "Build transaction to fill order(s)" :> Description ("Build a transaction to fill order(s). " `AppendSymbol` CommonCollateralText) :> "tx" @@ -358,6 +389,7 @@ handleOrdersApi ctx = :<|> handleCancelOrders ctx :<|> handleCancelOrdersAndSignSubmit ctx :<|> handleOrdersDetails ctx + :<|> handleOrderDetails ctx :<|> handleFillOrders ctx handlePlaceOrder ∷ Ctx → PlaceOrderParameters → IO PlaceOrderTransactionDetails @@ -442,6 +474,15 @@ handleCancelOrdersAndSignSubmit ctx BotCancelOrderParameters {..} = do -- Though transaction id would be same, but we are returning it again, just in case... pure $ details {cotdTransactionId = txId, cotdTransaction = signedTx} +handleOrderDetails ∷ Ctx → GYAssetClass → IO (Envelope '[PodOrderNotFound] OrderInfoDetailed) +handleOrderDetails ctx@Ctx {..} ac = do + logInfo ctx $ "Getting order details for NFT token: " +|| ac ||+ "" + let porefs = dexPORefs ctxDexInfo + os ← runQuery ctx $ fmap poiToOrderInfoDetailed <$> orderByNft porefs ac + case os of + Nothing → throwIO PodOrderNotFound + Just o → pureSuccEnvelope o + handleOrdersDetails ∷ Ctx → [GYAssetClass] → IO [OrderInfoDetailed] handleOrdersDetails ctx@Ctx {..} acs = do logInfo ctx $ "Getting orders details for NFT tokens: " +|| acs ||+ "" diff --git a/geniusyield-server-lib/src/GeniusYield/Server/ErrorMiddleware.hs b/geniusyield-server-lib/src/GeniusYield/Server/ErrorMiddleware.hs index 84ea0e2e..545f7be3 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/ErrorMiddleware.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/ErrorMiddleware.hs @@ -15,7 +15,7 @@ import Data.ByteString.Builder (toLazyByteString) import GeniusYield.HTTP.Errors import GeniusYield.Imports (lazyDecodeUtf8Lenient) import GeniusYield.Providers.Common (SubmitTxException (SubmitTxException)) -import GeniusYield.Server.Dex.PartialOrder (PodServerException) +import GeniusYield.Server.Dex.PartialOrder (PodOrderNotFound, PodServerException) import GeniusYield.Transaction (BuildTxException (..)) import GeniusYield.Transaction.Common (BalancingError (..)) import GeniusYield.TxBuilder @@ -151,7 +151,8 @@ exceptionHandler = GYQueryDatumException qdErr → someBackendError $ tShow qdErr GYDatumMismatch actualDatum scriptWitness → someBackendError $ "Actual datum in UTxO is: " <> tShow actualDatum <> ", but witness has wrong corresponding datum information: " <> tShow scriptWitness GYApplicationException e → toApiError e, - WH $ \(e ∷ PodServerException) → toApiError e + WH $ \(e ∷ PodServerException) → toApiError e, + WH $ \(e ∷ PodOrderNotFound) → toApiError e ] sinkStreamingBody ∷ ((Wai.StreamingBody → IO ()) → IO ()) → IO LBS.ByteString diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Orphans.hs b/geniusyield-server-lib/src/GeniusYield/Server/Orphans.hs new file mode 100644 index 00000000..ea978c81 --- /dev/null +++ b/geniusyield-server-lib/src/GeniusYield/Server/Orphans.hs @@ -0,0 +1,70 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +module GeniusYield.Server.Orphans () where + +import Control.Lens (at, (?~)) +import Data.HashMap.Strict.InsOrd qualified as IOHM +import Data.Swagger +import GeniusYield.Server.Auth (APIKeyAuthProtect, apiKeyHeaderText) +import GeniusYield.Server.Dex.PartialOrder (ErrDescription (..)) +import RIO +import Servant +import Servant.Checked.Exceptions +import Servant.Foreign +import Servant.Swagger + +type IsErr err = (ErrDescription err, ErrStatus err) + +instance (IsErr err, HasSwagger sub) ⇒ HasSwagger (Throws err :> sub) where + toSwagger _ = + toSwagger (Proxy ∷ Proxy sub) + & setResponseWith + (\old _ → addDescription old) + (fromEnum $ toErrStatus (undefined ∷ err)) + (return $ mempty & description .~ errDescription) + where + addDescription = description %~ ((errDescription <> " OR ") <>) + errDescription = toErrDescription (undefined ∷ err) + +instance HasSwagger api ⇒ HasSwagger (APIKeyAuthProtect :> api) where + toSwagger _ = + toSwagger (Proxy ∷ Proxy api) + & securityDefinitions + .~ SecurityDefinitions (IOHM.fromList [(apiKeyHeaderText, apiKeySecurityScheme)]) + & allOperations + . security + .~ [SecurityRequirement (IOHM.singleton apiKeyHeaderText [])] + & allOperations + . responses + %~ addCommonResponses + where + apiKeySecurityScheme ∷ SecurityScheme + apiKeySecurityScheme = + SecurityScheme + { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams apiKeyHeaderText ApiKeyHeader), + _securitySchemeDescription = Just "API key for accessing the server's API." + } + addCommonResponses ∷ Responses → Responses + addCommonResponses resps = resps & at 401 ?~ Inline response401 & at 403 ?~ Inline response403 & at 500 ?~ Inline response500 + + response401 ∷ Response + response401 = mempty & description .~ "Unauthorized access - API key missing" + + response403 ∷ Response + response403 = mempty & description .~ "Forbidden - The API key does not have permission to perform the request" + + response500 ∷ Response + response500 = mempty & description .~ "Internal server error" + +-- `HasForeign` instance for `APIKeyAuthProtect :> api` is required to generate client code using libraries such as `servant-py`. +-- This is written with help from https://github.com/haskell-servant/servant-auth/issues/8#issue-185541839. +instance ∀ lang ftype api. (HasForeign lang ftype api, HasForeignType lang ftype Text) ⇒ HasForeign lang ftype (APIKeyAuthProtect :> api) where + type Foreign ftype (APIKeyAuthProtect :> api) = Foreign ftype api + foreignFor lang Proxy Proxy subR = foreignFor lang Proxy (Proxy ∷ Proxy api) subR' + where + subR' = subR {_reqHeaders = HeaderArg arg : _reqHeaders subR} + arg = + Arg + { _argName = "api-key", + _argType = typeFor lang (Proxy ∷ Proxy ftype) (Proxy ∷ Proxy Text) + } diff --git a/web/swagger/api.yaml b/web/swagger/api.yaml index acc8444d..6a9f21af 100644 --- a/web/swagger/api.yaml +++ b/web/swagger/api.yaml @@ -925,6 +925,39 @@ paths: summary: Get order(s) details tags: - Orders + /v0/orders/details/{nft-token}: + get: + description: Get details of an order using it's unique NFT token. Note that + each order is identified uniquely by an associated NFT token which can then + later be used to retrieve it's details across partial fills. + parameters: + - in: path + name: nft-token + required: true + type: string + produces: + - application/json;charset=utf-8 + responses: + '200': + description: '' + schema: + $ref: '#/definitions/OrderInfoDetailed' + '400': + description: Invalid `nft-token` + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '404': + description: Order not found + '500': + description: Internal server error + security: + - api-key: [] + summary: Get order details + tags: + - Orders /v0/orders/tx/build-cancel: post: consumes: From a15f05781ac747b385c30165a196fc9c77683105 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Tue, 7 May 2024 18:10:23 +0530 Subject: [PATCH 3/5] feat: Clarify at what all places is "maestroToken" required and which endpoints use signing key from server to derive for wallet's address, etc. Done along #57. --- .../src/GeniusYield/Server/Assets.hs | 3 ++- .../Server/Dex/HistoricalPrices/Maestro.hs | 4 ++-- .../Server/Dex/HistoricalPrices/TapTools.hs | 22 ++++++++++++++++--- .../src/GeniusYield/Server/Dex/Markets.hs | 5 +++-- .../GeniusYield/Server/Dex/PartialOrder.hs | 7 ++++-- .../src/GeniusYield/Server/Utils.hs | 5 +++++ web/swagger/api.yaml | 15 +++++++++---- 7 files changed, 47 insertions(+), 14 deletions(-) diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Assets.hs b/geniusyield-server-lib/src/GeniusYield/Server/Assets.hs index a3891332..19a0bebd 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Assets.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Assets.hs @@ -4,6 +4,7 @@ module GeniusYield.Server.Assets ( ) where import Fmt +import GHC.TypeLits (AppendSymbol) import GeniusYield.OrderBot.Domain.Assets import GeniusYield.Server.Ctx import GeniusYield.Server.Utils @@ -11,7 +12,7 @@ import GeniusYield.Types import RIO hiding (logDebug, logInfo) import Servant -type AssetsAPI = Summary "Get assets information" :> Description "Get information for a specific asset." :> Capture "asset" GYAssetClass :> Get '[JSON] AssetDetails +type AssetsAPI = Summary "Get assets information" :> Description ("Get information for a specific asset. " `AppendSymbol` CommonMaestroKeyRequirementText) :> Capture "asset" GYAssetClass :> Get '[JSON] AssetDetails handleAssetsApi ∷ Ctx → GYAssetClass → IO AssetDetails handleAssetsApi ctx@Ctx {..} ac = do diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs index d2d64ed6..d47e7a7f 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/Maestro.hs @@ -12,7 +12,7 @@ import Data.Swagger qualified as Swagger import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import Deriving.Aeson import Fmt -import GHC.TypeLits (Symbol) +import GHC.TypeLits (AppendSymbol, Symbol) import GeniusYield.OrderBot.Adapter.Maestro (MaestroProvider (..), handleMaestroError) import GeniusYield.OrderBot.Domain.Assets (AssetDetails (adAssetTicker), AssetTicker (..)) import GeniusYield.OrderBot.Domain.Markets (OrderAssetPair (..)) @@ -110,7 +110,7 @@ instance Swagger.ToParamSchema MaestroDex where type MaestroPriceHistoryAPI = Summary "Get price history using Maestro." - :> Description "This endpoint internally calls Maestro's \"DEX And Pair OHLC\" endpoint." + :> Description ("This endpoint internally calls Maestro's \"DEX And Pair OHLC\" endpoint. " `AppendSymbol` CommonMaestroKeyRequirementText) :> Capture "market-id" OrderAssetPair :> Capture "dex" MaestroDex :> QueryParam "resolution" MaestroResolution diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs index a375be00..10653a3f 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools.hs @@ -3,6 +3,9 @@ module GeniusYield.Server.Dex.HistoricalPrices.TapTools ( handleTapToolsPriceHistoryApi, ) where +import Control.Lens ((?~)) +import Data.Swagger qualified as Swagger +import Data.Swagger.Internal.Schema qualified as Swagger import Fmt import GeniusYield.Server.Ctx import GeniusYield.Server.Dex.HistoricalPrices.TapTools.Client (TapToolsInterval, TapToolsOHLCV, TapToolsUnit (TapToolsUnit), handleTapToolsError, tapToolsOHLCV) @@ -11,19 +14,32 @@ import GeniusYield.Types import RIO hiding (logDebug, logInfo) import Servant +newtype TapToolsNumIntervals = TapToolsNumIntervals {unTapToolsNumIntervals ∷ Natural} + deriving stock (Eq, Ord, Show) + deriving newtype (FromHttpApiData, Swagger.ToParamSchema) + +-- Since this is a query parameter, our schema description wouldn't be registered for in swagger specification :(. Following OpenAPI 3.0 would allow for it. +instance Swagger.ToSchema TapToolsNumIntervals where + declareNamedSchema p = + pure $ + Swagger.named "TapToolsNumIntervals" $ + Swagger.paramSchemaToSchema p + & Swagger.description + ?~ "The number of intervals to return, e.g. if you want 180 days of data in 1d intervals, then pass 180 here." + type TapToolsPriceHistoryAPI = Summary "Get price history using TapTools." :> Description "This endpoint internally calls TapTools's \"Token price OHLCV\" endpoint. Note that only the liquidity pools involving ADA and the given asset class is considered to get for aggregated price information. Price returned is in ADA." :> Capture "asset" GYAssetClass :> QueryParam' '[Required, Strict] "interval" TapToolsInterval - :> QueryParam "numIntervals" Natural + :> QueryParam "numIntervals" TapToolsNumIntervals :> Get '[JSON] [TapToolsOHLCV] throwNoTapToolsKeyError ∷ IO a throwNoTapToolsKeyError = throwIO $ err500 {errBody = "No API key configured for TapTools."} -handleTapToolsPriceHistoryApi ∷ Ctx → GYAssetClass → TapToolsInterval → Maybe Natural → IO [TapToolsOHLCV] -handleTapToolsPriceHistoryApi ctx token tti mttni = do +handleTapToolsPriceHistoryApi ∷ Ctx → GYAssetClass → TapToolsInterval → Maybe TapToolsNumIntervals → IO [TapToolsOHLCV] +handleTapToolsPriceHistoryApi ctx token tti (fmap unTapToolsNumIntervals → mttni) = do logInfo ctx $ "Fetching price history. Token: " +|| token ||+ ", interval: " +|| tti ||+ "" case ctxTapToolsProvider ctx of Nothing → throwNoTapToolsKeyError diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/Markets.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/Markets.hs index fc972d0e..6fee88ce 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/Markets.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/Markets.hs @@ -6,9 +6,10 @@ module GeniusYield.Server.Dex.Markets ( import Data.Aeson (camelTo2) import Data.Swagger qualified as Swagger import Deriving.Aeson +import GHC.TypeLits (AppendSymbol) import GeniusYield.OrderBot.Domain.Markets (HasMarkets (getMarkets), OrderAssetPair (commodityAsset, currencyAsset)) import GeniusYield.Server.Ctx -import GeniusYield.Server.Utils (addSwaggerDescription, logInfo) +import GeniusYield.Server.Utils (CommonMaestroKeyRequirementText, addSwaggerDescription, logInfo) import GeniusYield.Types import RIO hiding (logDebug, logInfo) import Servant @@ -43,7 +44,7 @@ instance Swagger.ToSchema Market where type MarketsAPI = Summary "Get markets information for the DEX." - :> Description "Returns the list of markets information supported by GeniusYield DEX." + :> Description ("Returns the list of markets information supported by GeniusYield DEX. " `AppendSymbol` CommonMaestroKeyRequirementText) :> Get '[JSON] [Market] handleMarketsApi ∷ Ctx → ServerT MarketsAPI IO diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs index 0bd7c4fe..ddf9972c 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/PartialOrder.hs @@ -343,6 +343,9 @@ instance Swagger.ToSchema FillOrderTransactionDetails where type CommonCollateralText ∷ Symbol type CommonCollateralText = "Note that if \"collateral\" field is not provided, then framework would try to pick collateral UTxO on it's own and in that case would also be free to spend it (i.e., would be made available to coin balancer)." +type CommonSignText ∷ Symbol +type CommonSignText = "It uses the signing key from configuration to compute for wallet address. If collateral is specified in the configuration, then it would be used for." + type OrdersAPI = Summary "Build transaction to create order" :> Description ("Build a transaction to create an order. In case \"stakeAddress\" field is provided then order is placed at a mangled address having the given staking credential. " `AppendSymbol` CommonCollateralText) @@ -351,7 +354,7 @@ type OrdersAPI = :> ReqBody '[JSON] PlaceOrderParameters :> Post '[JSON] PlaceOrderTransactionDetails :<|> Summary "Create an order" - :> Description "Create an order. This endpoint would also sign & submit the built transaction." + :> Description ("Create an order. This endpoint would also sign & submit the built transaction. " `AppendSymbol` CommonSignText `AppendSymbol` " \"stakeAddress\" field from configuration, if provided, is used to place order at a mangled address.") :> ReqBody '[JSON] BotPlaceOrderParameters :> Post '[JSON] PlaceOrderTransactionDetails :<|> Summary "Build transaction to cancel order(s)" @@ -361,7 +364,7 @@ type OrdersAPI = :> ReqBody '[JSON] CancelOrderParameters :> Post '[JSON] CancelOrderTransactionDetails :<|> Summary "Cancel order(s)" - :> Description "Cancel order(s). This endpoint would also sign & submit the built transaction." + :> Description ("Cancel order(s). This endpoint would also sign & submit the built transaction. " `AppendSymbol` CommonSignText) :> ReqBody '[JSON] BotCancelOrderParameters :> Delete '[JSON] CancelOrderTransactionDetails :<|> Summary "Get order(s) details" diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs b/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs index 36ad5f42..f70f9351 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Utils.hs @@ -11,11 +11,13 @@ module GeniusYield.Server.Utils ( bytestringToString, hideServantClientErrorHeader, commonEnumParamSchemaRecipe, + CommonMaestroKeyRequirementText, ) where import Control.Lens ((?~)) import Data.Swagger qualified as Swagger import Data.Swagger.Internal qualified as Swagger +import GHC.TypeLits (Symbol) import GeniusYield.Imports import GeniusYield.Server.Ctx import GeniusYield.Swagger.Utils (addSwaggerDescription, addSwaggerExample, dropSymbolAndCamelToSnake) @@ -64,3 +66,6 @@ hideServantClientErrorHeader headerName clientError = case clientError of commonEnumParamSchemaRecipe ∷ ∀ a (t ∷ Swagger.SwaggerKind Type). (Bounded a, Enum a, ToJSON a) ⇒ Proxy a → Swagger.ParamSchema t commonEnumParamSchemaRecipe _ = mempty & Swagger.type_ ?~ Swagger.SwaggerString & Swagger.enum_ ?~ fmap toJSON [(minBound ∷ a) .. maxBound] + +type CommonMaestroKeyRequirementText ∷ Symbol +type CommonMaestroKeyRequirementText = "\"maestroToken\" field in the configuration is required for this operation." diff --git a/web/swagger/api.yaml b/web/swagger/api.yaml index 6a9f21af..ef6df7bd 100644 --- a/web/swagger/api.yaml +++ b/web/swagger/api.yaml @@ -573,7 +573,8 @@ info: paths: /v0/assets/{asset}: get: - description: Get information for a specific asset. + description: Get information for a specific asset. "maestroToken" field in the + configuration is required for this operation. parameters: - in: path name: asset @@ -633,6 +634,7 @@ paths: /v0/historical-prices/maestro/{market-id}/{dex}: get: description: This endpoint internally calls Maestro's "DEX And Pair OHLC" endpoint. + "maestroToken" field in the configuration is required for this operation. parameters: - in: path name: market-id @@ -766,7 +768,7 @@ paths: /v0/markets: get: description: Returns the list of markets information supported by GeniusYield - DEX. + DEX. "maestroToken" field in the configuration is required for this operation. produces: - application/json;charset=utf-8 responses: @@ -827,7 +829,9 @@ paths: consumes: - application/json;charset=utf-8 description: Cancel order(s). This endpoint would also sign & submit the built - transaction. + transaction. It uses the signing key from configuration to compute for wallet + address. If collateral is specified in the configuration, then it would be + used for. parameters: - in: body name: body @@ -859,7 +863,10 @@ paths: consumes: - application/json;charset=utf-8 description: Create an order. This endpoint would also sign & submit the built - transaction. + transaction. It uses the signing key from configuration to compute for wallet + address. If collateral is specified in the configuration, then it would be + used for. "stakeAddress" field from configuration, if provided, is used to + place order at a mangled address. parameters: - in: body name: body From eb9945ac52b2db5d2362f73a2123110938f25707 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Tue, 7 May 2024 18:17:52 +0530 Subject: [PATCH 4/5] chore: versioning and changelog Related to #57. --- geniusyield-dex-api/CHANGELOG.md | 5 +++++ geniusyield-dex-api/geniusyield-dex-api.cabal | 2 +- geniusyield-server-lib/CHANGELOG.md | 8 ++++++++ geniusyield-server-lib/geniusyield-server-lib.cabal | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/geniusyield-dex-api/CHANGELOG.md b/geniusyield-dex-api/CHANGELOG.md index 8bca76a1..bd583a48 100644 --- a/geniusyield-dex-api/CHANGELOG.md +++ b/geniusyield-dex-api/CHANGELOG.md @@ -1,5 +1,10 @@ # Revision history for geniusyield-dex-api +## 0.2.1.0 -- 2024-05-07 + +* Adds `placePartialOrder''`, `placePartialOrderWithVersion''` to also return for order's NFT token. +* Exports `placePartialOrder''`, `placePartialOrderWithVersion`, `placePartialOrderWithVersion'` and `placePartialOrderWithVersion''`. + ## 0.2.0.0 -- 2024-04-17 * Adds support for v1.1 family of scripts. diff --git a/geniusyield-dex-api/geniusyield-dex-api.cabal b/geniusyield-dex-api/geniusyield-dex-api.cabal index cd76ce36..4ccea27f 100644 --- a/geniusyield-dex-api/geniusyield-dex-api.cabal +++ b/geniusyield-dex-api/geniusyield-dex-api.cabal @@ -4,7 +4,7 @@ name: geniusyield-dex-api -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.2.0.0 +version: 0.2.1.0 synopsis: API code to interact with GeniusYield DEX. description: API code to interact with GeniusYield DEX. Learn more about GeniusYield by visiting https://www.geniusyield.co/. diff --git a/geniusyield-server-lib/CHANGELOG.md b/geniusyield-server-lib/CHANGELOG.md index 355b584c..39c620be 100644 --- a/geniusyield-server-lib/CHANGELOG.md +++ b/geniusyield-server-lib/CHANGELOG.md @@ -1,5 +1,13 @@ # Revision history for geniusyield-server-lib +## 0.3.0 -- 2024-05-07 + +* Adds TapTools OHLCV endpoint. +* Adds NFT token in response of place order family of endpoints. +* Adds GET variant for getting details of an order from it's NFT token identifier. +* Clarifies which endpoints require `maestroToken` field to be set. +* Clarifies which endpoints require signing key to be configured in the server to derive for wallet's address, likewise it is clarified that which endpoints use fields such as `collateral`, etc. from server's configuration. + ## 0.2.0 -- 2024-04-22 * Uses latest version of `geniusyield-dex-api` which adds support of v1.1 script. diff --git a/geniusyield-server-lib/geniusyield-server-lib.cabal b/geniusyield-server-lib/geniusyield-server-lib.cabal index 72b2cb9c..4be9547d 100644 --- a/geniusyield-server-lib/geniusyield-server-lib.cabal +++ b/geniusyield-server-lib/geniusyield-server-lib.cabal @@ -1,6 +1,6 @@ cabal-version: 3.6 name: geniusyield-server-lib -version: 0.2.0 +version: 0.3.0 synopsis: GeniusYield server library description: Library for GeniusYield server. license: Apache-2.0 From c5c3b900e3c50d251b5c6a82bbdbf42ab93639be Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Tue, 7 May 2024 18:34:47 +0530 Subject: [PATCH 5/5] style: remove a todo comment Related to #57. --- .../GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs index 76fa2894..ba6fcd49 100644 --- a/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs +++ b/geniusyield-server-lib/src/GeniusYield/Server/Dex/HistoricalPrices/TapTools/Client.hs @@ -137,7 +137,7 @@ data TapToolsException deriving anyclass (Exception) handleTapToolsError ∷ Text → Either ClientError a → IO a -handleTapToolsError locationInfo = either (throwIO . TapToolsApiError locationInfo . hideServantClientErrorHeader (fromString $ symbolVal (Proxy @TapToolsApiKeyHeaderName))) pure -- TODO: Check if api-key is actually hidden. +handleTapToolsError locationInfo = either (throwIO . TapToolsApiError locationInfo . hideServantClientErrorHeader (fromString $ symbolVal (Proxy @TapToolsApiKeyHeaderName))) pure tapToolsOHLCV ∷ TapToolsEnv → Maybe TapToolsUnit → TapToolsInterval → Maybe Natural → IO [TapToolsOHLCV] tapToolsOHLCV env@(tteApiKey → apiKey) ttu tti mttni = _tapToolsOHLCV apiKey ttu tti mttni & runTapToolsClient env >>= handleTapToolsError "tapToolsOHLCV"