@@ -6,10 +6,11 @@ module BotPlutusInterface.Server (
66 WebSocketEndpoint ,
77 ActivateContractEndpoint ,
88 RawTxEndpoint ,
9+ TxIdCapture (TxIdCapture ),
910) where
1011
1112import BotPlutusInterface.Contract (runContract )
12- import BotPlutusInterface.Files (txFileName )
13+ import BotPlutusInterface.Files (txFileName , txIdToText )
1314import BotPlutusInterface.Types (
1415 AppState (AppState ),
1516 ContractEnvironment (.. ),
@@ -25,15 +26,19 @@ import Control.Monad.Error.Class (throwError)
2526import Control.Monad.IO.Class (liftIO )
2627import Data.Aeson (FromJSON , ToJSON (toJSON ))
2728import Data.Aeson qualified as JSON
29+ import Data.Bifunctor (bimap )
2830import Data.ByteString.Lazy qualified as LBS
2931import Data.Either.Combinators (leftToMaybe )
3032import Data.Kind (Type )
3133import Data.Map qualified as Map
3234import Data.Maybe (catMaybes )
3335import Data.Proxy (Proxy (Proxy ))
3436import Data.Row (Row )
35- import Data.Text (Text , unpack )
37+ import Data.String (fromString )
38+ import Data.Text (Text , pack , unpack )
39+ import Data.Text.Encoding (encodeUtf8 )
3640import Data.UUID.V4 qualified as UUID
41+ import Ledger.TxId (TxId (TxId ))
3742import Network.WebSockets (
3843 Connection ,
3944 PendingConnection ,
@@ -56,11 +61,24 @@ import Plutus.PAB.Webserver.Types (
5661 ContractActivationArgs (.. ),
5762 InstanceStatusToClient (ContractFinished , NewObservableState ),
5863 )
59- import Servant.API (Capture , Get , JSON , Post , ReqBody , (:<|>) (.. ), (:>) )
64+ import Plutus.V1.Ledger.Bytes (LedgerBytes (LedgerBytes ), fromHex )
65+ import PlutusTx.Prelude (lengthOfByteString )
66+ import Servant.API (
67+ Capture ,
68+ FromHttpApiData (parseUrlPiece ),
69+ Get ,
70+ JSON ,
71+ Post ,
72+ ReqBody ,
73+ ToHttpApiData (toUrlPiece ),
74+ (:<|>) ((:<|>) ),
75+ (:>) ,
76+ )
6077import Servant.API.WebSocket (WebSocketPending )
6178import Servant.Server (Application , Handler , Server , err404 , serve )
62- import System.Directory (canonicalizePath , doesFileExist , makeAbsolute )
63- import System.FilePath (takeDirectory , (</>) )
79+ import System.Directory (doesFileExist , makeAbsolute )
80+ import System.FilePath ((</>) )
81+ import Test.QuickCheck (Arbitrary (arbitrary ), elements , vectorOf )
6482import Wallet.Types (ContractInstanceId (.. ))
6583import Prelude
6684
@@ -85,9 +103,30 @@ type ActivateContractEndpoint a =
85103
86104type RawTxEndpoint =
87105 " raw-tx"
88- :> Capture " txId " Text
106+ :> Capture " tx-id " TxIdCapture
89107 :> Get '[JSON ] RawTx
90108
109+ newtype TxIdCapture = TxIdCapture TxId
110+ deriving newtype (Eq , Show )
111+
112+ instance FromHttpApiData TxIdCapture where
113+ parseUrlPiece :: Text -> Either Text TxIdCapture
114+ parseUrlPiece t = bimap pack bytesToTxIdCapture $ checkLength =<< fromHex (encodeUtf8 t)
115+ where
116+ checkLength :: LedgerBytes -> Either String LedgerBytes
117+ checkLength b@ (LedgerBytes bs) =
118+ if lengthOfByteString bs == 32
119+ then Right b
120+ else Left " Invalid length"
121+ bytesToTxIdCapture :: LedgerBytes -> TxIdCapture
122+ bytesToTxIdCapture (LedgerBytes b) = TxIdCapture $ TxId b
123+
124+ instance ToHttpApiData TxIdCapture where
125+ toUrlPiece (TxIdCapture txId) = txIdToText txId
126+
127+ instance Arbitrary TxIdCapture where
128+ arbitrary = TxIdCapture . fromString <$> vectorOf 64 (elements " 0123456789abcdefABCDEF" )
129+
91130server :: HasDefinitions t => PABConfig -> AppState -> Server (API t )
92131server pabConfig state =
93132 websocketHandler state
@@ -234,20 +273,17 @@ handleContract pabConf state@(AppState st) contract = liftIO $ do
234273 pure contractInstanceID
235274
236275-- | This handler will allow to retrieve raw transactions from the pcTxFileDir if pcEnableTxEndpoint is True
237- rawTxHandler :: PABConfig -> Text -> Handler RawTx
238- rawTxHandler config txId = do
276+ rawTxHandler :: PABConfig -> TxIdCapture -> Handler RawTx
277+ rawTxHandler config ( TxIdCapture txId) = do
239278 -- Check that endpoint is enabled
240279 assert config. pcEnableTxEndpoint
241280 -- Absolute path to pcTxFileDir that is specified in the config
242281 txFolderPath <- liftIO $ makeAbsolute (unpack config. pcTxFileDir)
243282
244- -- Add/Set .raw extension on path
245- let suppliedPath :: FilePath
246- suppliedPath = txFolderPath </> unpack (txFileName txId " .raw" )
247- -- Resolve path indirections
248- path <- liftIO $ canonicalizePath suppliedPath
249- -- ensure it does not try to escape txFolderPath
250- assert (takeDirectory path == txFolderPath)
283+ -- Create full path
284+ let path :: FilePath
285+ path = txFolderPath </> unpack (txFileName txId " raw" )
286+
251287 -- ensure file exists
252288 fileExists <- liftIO $ doesFileExist path
253289 assert fileExists
0 commit comments