Skip to content

Commit

Permalink
feat(#400): ogmios provider (wip)
Browse files Browse the repository at this point in the history
sourabhxyz committed Jan 28, 2025

Verified

This commit was signed with the committer’s verified signature.
sourabhxyz Sourabh
1 parent 6cc7335 commit 256a7d6
Showing 3 changed files with 182 additions and 1 deletion.
1 change: 1 addition & 0 deletions atlas-cardano.cabal
Original file line number Diff line number Diff line change
@@ -85,6 +85,7 @@ library
GeniusYield.Providers.Kupo
GeniusYield.Providers.Maestro
GeniusYield.Providers.Node
GeniusYield.Providers.Ogmios
GeniusYield.Providers.Sentry
GeniusYield.ReadJSON
GeniusYield.Scripts.TestToken
174 changes: 174 additions & 0 deletions src/GeniusYield/Providers/Ogmios.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
{- |
Module : GeniusYield.Providers.Ogmios
Description : Ogmios provider for remote node connection
Copyright : (c) 2025 GYELD GMBH
License : Apache 2.0
Maintainer : [email protected]
Stability : develop
-}
module GeniusYield.Providers.Ogmios (
OgmiosApiEnv,
newOgmiosApiEnv,
OgmiosProviderException (..),
ogmiosSubmitTx,
ogmiosProtocolParameters,
) where

import Cardano.Api qualified as Api
import Control.Concurrent (threadDelay)
import Control.Monad ((<=<))
import Data.Aeson (Value (Null), encode, object, withObject, (.:), (.=))
import Data.Char (toLower)
import Data.Map.Strict qualified as Map
import Data.Maybe (listToMaybe)
import Data.Text qualified as Text
import Data.Word (Word64)
import Deriving.Aeson
import GeniusYield.Imports
import GeniusYield.Providers.Common (
SubmitTxException (..),
datumFromCBOR,
extractAssetClass,
newServantClientEnv,
)
import GeniusYield.Types
import GeniusYield.Types qualified as GYTypes (PlutusVersion (..))
import GeniusYield.Types.Script (GYAnyScript (..))
import Servant.API (
Capture,
Get,
Header,
Headers (getResponse),
JSON,
Post,
QueryFlag,
QueryParam,
ReqBody,
ResponseHeader (Header),
lookupResponseHeader,
(:>),
type (:<|>) (..),
)
import Servant.Client (
ClientEnv,
ClientError,
ClientM,
client,
runClientM,
)

newtype OgmiosApiEnv = OgmiosApiEnv ClientEnv

-- | Returns a new 'OgmiosApiEnv' given the base url to query from.
newOgmiosApiEnv :: String -> IO OgmiosApiEnv
newOgmiosApiEnv baseUrl = OgmiosApiEnv <$> newServantClientEnv baseUrl

-- | Exceptions.
data OgmiosProviderException
= -- | Error from the Ogmios API.
OgmiosApiError !Text !ClientError
| -- TODO: Is OgmiosAbsurdResponse needed?

-- | Received an absurd response from Ogmios. This shouldn't ever happen.
OgmiosAbsurdResponse !Text
deriving stock (Eq, Show)
deriving anyclass Exception

{-# INLINEABLE runOgmiosClient #-}
runOgmiosClient :: OgmiosApiEnv -> ClientM a -> IO (Either ClientError a)
runOgmiosClient (OgmiosApiEnv cEnv) c = runClientM c cEnv

{-# INLINEABLE handleOgmiosError #-}
handleOgmiosError :: Text -> Either ClientError a -> IO a
handleOgmiosError locationInfo = either (throwIO . OgmiosApiError locationInfo) pure

-- TODO: Remove these comments.
class ToJSONRPC a where
toMethod :: a -> Text

-- TODO: Does empty map work same as Nothing?
toParams :: a -> Maybe Value

instance ToJSONRPC GYTx where
toMethod = const "submitTransaction"
toParams tx = Just $ toJSON $ Map.fromList [("transaction" :: Text, Map.fromList [("cbor" :: Text, txToHex tx)])]

newtype OgmiosRequest a = OgmiosRequest a

instance ToJSONRPC a => ToJSON (OgmiosRequest a) where
toJSON (OgmiosRequest a) =
object
[ "jsonrpc" .= ("2.0" :: Text)
, "method" .= toMethod a
, "params" .= toParams a
]

newtype OgmiosResponse a = OgmiosResponse
{ response :: Either Value a
}
deriving stock Show

instance FromJSON a => FromJSON (OgmiosResponse a) where
parseJSON = withObject "OgmiosResponse" $ \o -> do
result <- o .: "result"
case result of
Null -> OgmiosResponse . Left <$> o .: "error"
_ -> OgmiosResponse . Right <$> parseJSON result

newtype TxIdResponse = TxIdResponse
{ id :: GYTxId
}
deriving stock (Show, Generic)
deriving anyclass FromJSON

newtype TxSubmissionResponse = TxSubmissionResponse
{ transaction :: TxIdResponse
}
deriving stock (Show, Generic)
deriving anyclass FromJSON

submitTx :: OgmiosRequest GYTx -> ClientM (OgmiosResponse TxSubmissionResponse)
protocolParams :: OgmiosRequest OgmiosPP -> ClientM Value

data OgmiosPP = OgmiosPP

instance ToJSONRPC OgmiosPP where
toMethod = const "queryLedgerState/protocolParameters"
toParams = const Nothing

type OgmiosApi = ReqBody '[JSON] (OgmiosRequest GYTx) :> Post '[JSON] (OgmiosResponse TxSubmissionResponse) :<|> ReqBody '[JSON] (OgmiosRequest OgmiosPP) :> Post '[JSON] Value

submitTx :<|> protocolParams = client @OgmiosApi Proxy

-- | Submit a transaction to the node via Ogmios.
ogmiosSubmitTx :: OgmiosApiEnv -> GYSubmitTx
ogmiosSubmitTx env tx = do
let debreq = OgmiosRequest tx
putStrLn $ "Request body: " <> show (encode debreq)
OgmiosResponse rs <-
handleOgmiosError fn
<=< runOgmiosClient env
$ submitTx (OgmiosRequest tx)
case rs of
-- TODO: Does error message look similar in case of say Maestro?
Left err -> throwIO . SubmitTxException . Text.pack . show $ err
Right (TxSubmissionResponse (TxIdResponse txId)) -> pure txId
where
fn = "ogmiosSubmitTx"

-- | Fetch protocol parameters.
ogmiosProtocolParameters :: OgmiosApiEnv -> IO (Value)
ogmiosProtocolParameters env = do
val <-
handleOgmiosError fn
<=< runOgmiosClient env
$ protocolParams (OgmiosRequest OgmiosPP)
putStrLn $ "Response body: " <> show val
pure val
where
-- case val of
-- -- TODO: Does error message look similar in case of say Maestro?
-- Left err -> throwIO . SubmitTxException . Text.pack . show $ err
-- Right (TxSubmissionResponse (TxIdResponse txId)) -> pure txId

fn = "ogmiosProtocolParameters"
8 changes: 7 additions & 1 deletion src/GeniusYield/Types/Tx.hs
Original file line number Diff line number Diff line change
@@ -98,6 +98,9 @@ import GeniusYield.Types.PlutusVersion (

newtype GYTx = GYTx (Api.Tx ApiEra)

instance IsString GYTx where
fromString = either error id . txFromHexE

{- |
>>> txToApi <$> (Aeson.fromJSON @GYTx $ Aeson.toJSON tx)
@@ -150,7 +153,10 @@ instance Printf.PrintfArg GYTx where
Just (ShelleyTx ShelleyBasedEraConway (AlonzoTx {body = TxBodyConstr ConwayTxBodyRaw {ctbrSpendInputs = fromList [TxIn (TxId {unTxId = SafeHash "677b32cca6387836fc53ec35b4060800893c22edc1e1d20ff74c42e67aca1e21"}) (TxIx {unTxIx = 1}),TxIn (TxId {unTxId = SafeHash "f13e16fafb7df5fbdff775d949a28edd586a3e0426739bc782c9f5d82ecdb70a"}) (TxIx {unTxIx = 0})], ctbrCollateralInputs = fromList [TxIn (TxId {unTxId = SafeHash "c816519a759e300acc16d1e2812500392c85ae6d5af886dd154c9084a610a120"}) (TxIx {unTxIx = 0})], ctbrReferenceInputs = fromList [TxIn (TxId {unTxId = SafeHash "16647d6365020555d905d6e0edcf08b90a567886f875b40b3d7cec1c70482624"}) (TxIx {unTxIx = 0}),TxIn (TxId {unTxId = SafeHash "16647d6365020555d905d6e0edcf08b90a567886f875b40b3d7cec1c70482624"}) (TxIx {unTxIx = 1})], ctbrOutputs = StrictSeq {fromStrict = fromList [Sized {sizedValue = (Addr Testnet (ScriptHashObj (ScriptHash "44376a5f63342097a4f20401088c62da272639e60644a9ec1d70f444")) (StakeRefBase (KeyHashObj (KeyHash {unKeyHash = "1d3554e12c8aed91818a0600a57bea9d50e509beda567387d1247315"}))),MaryValue (Coin 103400000) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash "53827a77e4ed3d5c211706708c0aa9b9a3be19db901b1cbf7fa515b8"},fromList [("b7f1e540a130b7d9010c9ad87f284d914ab9753ae846c9b5221436086efe5f01",1)])])),DatumHash (SafeHash "7caffd4aa6d4942ad42cf8d109feee13994ab5dc3bd657a571b70d679538d575"),SNothing), sizedSize = 167},Sized {sizedValue = (Addr Testnet (KeyHashObj (KeyHash {unKeyHash = "99f8985db8b9076f61ecec59eca67e30224dc20afb40491aecc6aa97"})) (StakeRefBase (KeyHashObj (KeyHash {unKeyHash = "1d3554e12c8aed91818a0600a57bea9d50e509beda567387d1247315"}))),MaryValue (Coin 997296677) (MultiAsset (fromList [])),NoDatum,SNothing), sizedSize = 65}]}, ctbrCollateralReturn = SJust (Sized {sizedValue = (Addr Testnet (KeyHashObj (KeyHash {unKeyHash = "99f8985db8b9076f61ecec59eca67e30224dc20afb40491aecc6aa97"})) (StakeRefBase (KeyHashObj (KeyHash {unKeyHash = "1d3554e12c8aed91818a0600a57bea9d50e509beda567387d1247315"}))),MaryValue (Coin 4486868) (MultiAsset (fromList [])),NoDatum,SNothing), sizedSize = 65}), ctbrTotalCollateral = SJust (Coin 513132), ctbrCerts = OSet {osSSeq = StrictSeq {fromStrict = fromList []}, osSet = fromList []}, ctbrWithdrawals = Withdrawals {unWithdrawals = fromList []}, ctbrTxfee = Coin 342088, ctbrVldt = ValidityInterval {invalidBefore = SNothing, invalidHereafter = SNothing}, ctbrReqSignerHashes = fromList [], ctbrMint = MultiAsset (fromList [(PolicyID {policyID = ScriptHash "53827a77e4ed3d5c211706708c0aa9b9a3be19db901b1cbf7fa515b8"},fromList [("b7f1e540a130b7d9010c9ad87f284d914ab9753ae846c9b5221436086efe5f01",1)])]), ctbrScriptIntegrityHash = SJust (SafeHash "a23cebd1aef6f5c9a3bb5c4469bc0b5c316a7090c6306109ebe6ce1d088b3fe5"), ctbrAuxDataHash = SJust (AuxiliaryDataHash {unsafeAuxiliaryDataHash = SafeHash "37c0555635ab7e45ec39bd8feb873080d036c9a67c4cdd0c85e1c5291b0482f2"}), ctbrTxNetworkId = SNothing, ctbrVotingProcedures = VotingProcedures {unVotingProcedures = fromList []}, ctbrProposalProcedures = OSet {osSSeq = StrictSeq {fromStrict = fromList []}, osSet = fromList []}, ctbrCurrentTreasuryValue = SNothing, ctbrTreasuryDonation = Coin 0} (blake2b_256: SafeHash "dfd37a5f16ecb4203ff240e0c426890f8c400e1ddfbdf1accaeb8cc348fa3b5c"), wits = AlonzoTxWitsRaw {atwrAddrTxWits = fromList [WitVKeyInternal {wvkKey = VKey (VerKeyEd25519DSIGN "e8807993d91ac035385bea2cc7577876d1ed3ca05b78ac0fc1be65b741c61957"), wvkSig = SignedDSIGN (SigEd25519DSIGN "9a37152ba1fe5b8a2026eca077d1e154795812bdc8008c43cd6028d08485062f91a980f88f0739cfb1763117debe6673f0fc0ffb2362659b89449a1b30a49d08"), wvkKeyHash = KeyHash {unKeyHash = "99f8985db8b9076f61ecec59eca67e30224dc20afb40491aecc6aa97"}, wvkBytes = "\130X \232\128y\147\217\SUB\192\&58[\234,\199Wxv\209\237<\160[x\172\SI\193\190e\183A\198\EMWX@\154\&7\NAK+\161\254[\138 &\236\160w\209\225TyX\DC2\189\200\NUL\140C\205`(\208\132\133\ACK/\145\169\128\248\143\a9\207\177v1\ETB\222\190fs\240\252\SI\251#be\155\137D\154\ESC0\164\157\b"}], atwrBootAddrTxWits = fromList [], atwrScriptTxWits = fromList [], atwrDatsTxWits = TxDatsConstr TxDatsRaw {unTxDatsRaw = fromList [(SafeHash "7caffd4aa6d4942ad42cf8d109feee13994ab5dc3bd657a571b70d679538d575",DataConstr Constr 0 [B "\153\248\152]\184\185\aoa\236\236Y\236\166~0\"M\194\n\251@I\SUB\236\198\170\151",Constr 0 [Constr 0 [B "\153\248\152]\184\185\aoa\236\236Y\236\166~0\"M\194\n\251@I\SUB\236\198\170\151"],Constr 0 [Constr 0 [Constr 0 [B "\GS5T\225,\138\237\145\129\138\ACK\NUL\165{\234\157P\229\t\190\218Vs\135\209$s\NAK"]]]],Constr 0 [B "",B ""],I 100000000,I 100000000,Constr 0 [B "\198\230[\167\135\139/\142\160\173\&9(}>/\210V\220\\A`\252\EM\189\244\196\216~",B "tGENS"],Constr 0 [I 1,I 1],B "\183\241\229@\161\&0\183\217\SOH\f\154\216\DEL(M\145J\185u:\232F\201\181\"\DC46\bn\254_\SOH",Constr 1 [],Constr 1 [],I 0,I 1000000,I 1000000,Constr 0 [I 1000000,I 300000,I 0],I 0] (blake2b_256: SafeHash "7caffd4aa6d4942ad42cf8d109feee13994ab5dc3bd657a571b70d679538d575"))]} (blake2b_256: SafeHash "f9be8c20a8c55a5c744f293db49f89505a82a2ce89ad86479f95983e044b4fe9"), atwrRdmrsTxWits = RedeemersConstr fromList [(ConwayMinting (AsIx {unAsIx = 0}),(DataConstr Constr 0 [Constr 0 [Constr 0 [B "g{2\204\166\&8x6\252S\236\&5\180\ACK\b\NUL\137<\"\237\193\225\210\SI\247LB\230z\202\RS!"],I 1]] (blake2b_256: SafeHash "63392b71d2cdffc553e10e7804c08897c9eb5a2ca6d83e647bf523796ca35741"),WrapExUnits {unWrapExUnits = ExUnits' {exUnitsMem' = 726550, exUnitsSteps' = 231770400}}))] (blake2b_256: SafeHash "df0708c4c44f7ff380ded920ebe4e51be34b100e9235df0294cb64948c047c0f")} (blake2b_256: SafeHash "0a0052247e0995d8010860a20560f5cd9faf78b057fa6cd1f367fd900f8248fa"), isValid = IsValid True, auxiliaryData = SJust (AuxiliaryDataConstr AlonzoTxAuxDataRaw {atadrMetadata = fromList [(674,Map [(S "msg",List [S "GeniusYield: Order placed"])])], atadrTimelock = StrictSeq {fromStrict = fromList []}, atadrPlutus = fromList []} (blake2b_256: SafeHash "37c0555635ab7e45ec39bd8feb873080d036c9a67c4cdd0c85e1c5291b0482f2"))}))
-}
txFromHex :: String -> Maybe GYTx
txFromHex s = rightToMaybe $ txFromHexBS $ BS8.pack s
txFromHex s = rightToMaybe $ txFromHexE s

txFromHexE :: String -> Either String GYTx
txFromHexE s = txFromHexBS $ BS8.pack s

{- |

0 comments on commit 256a7d6

Please sign in to comment.