utxorpc-client-0.0.4.1: quick-start/Main.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-}
module Main (main) where
import Control.Exception (bracket, throwIO, try, SomeException)
import Control.Lens ((^.), (.~), (&))
import Control.Monad (void, when, forM_)
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
import Data.ProtoLens (Message (..))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as Base16
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Int (Int64)
import Data.List (find)
-- import Data.Maybe (isJust, fromMaybe)
-- import Proto.Utxorpc.V1alpha.Query.Query
import qualified Proto.Utxorpc.V1alpha.Query.Query_Fields as QF
-- import Proto.Utxorpc.V1alpha.Cardano.Cardano
import qualified Proto.Utxorpc.V1alpha.Cardano.Cardano_Fields as CF
import Proto.Utxorpc.V1alpha.Sync.Sync (FollowTipResponse)
import qualified Proto.Utxorpc.V1alpha.Sync.Sync as S
import qualified Proto.Utxorpc.V1alpha.Sync.Sync_Fields as SF
import Proto.Utxorpc.V1alpha.Submit.Submit (WaitForTxResponse, WatchMempoolResponse, Stage(..), TxPredicate, AnyChainTxPattern)
import qualified Proto.Utxorpc.V1alpha.Submit.Submit as Sub
import qualified Proto.Utxorpc.V1alpha.Submit.Submit_Fields as SubF
import Proto.Utxorpc.V1alpha.Watch.Watch (WatchTxResponse, WatchTxResponse'Action(..))
import qualified Proto.Utxorpc.V1alpha.Watch.Watch as W
import qualified Proto.Utxorpc.V1alpha.Watch.Watch_Fields as WF
import Proto.Utxorpc.V1alpha.Cardano.Cardano (AddressPattern, AssetPattern, TxPattern)
import qualified Proto.Utxorpc.V1alpha.Cardano.Cardano as C
import UnliftIO.Exception (throwString)
import GHC.Stack (HasCallStack)
import Utxorpc.Client
-- Test helpers
assertEqual :: (Eq a, Show a) => String -> a -> a -> IO ()
assertEqual msg expected actual =
when (expected /= actual) $
throwString $ msg ++ ": expected " ++ show expected ++ ", got " ++ show actual
assertNotNull :: HasCallStack => String -> Maybe a -> IO ()
assertNotNull msg Nothing = throwString $ msg ++ " is null"
assertNotNull _ (Just _) = return ()
assertTrue :: HasCallStack => String -> Bool -> IO ()
-- BigInt helper - extracts Int64 from BigInt protobuf message
getBigInt :: C.BigInt -> Int64
getBigInt bigIntMsg = case bigIntMsg ^. CF.maybe'bigInt of
Just (C.BigInt'Int val) -> val
_ -> error "Expected BigInt'Int variant"
assertTrue msg False = throwString $ msg ++ " is false"
assertTrue _ True = return ()
assertSingle :: HasCallStack => String -> [a] -> IO a
assertSingle msg [] = throwString $ msg ++ " is empty"
assertSingle _ [x] = return x
assertSingle msg xs = throwString $ msg ++ " has " ++ show (length xs) ++ " items, expected 1"
main :: IO ()
main = do
putStrLn "Testing Query and Submit endpoints with Dolos"
-- Connect to Dolos UTxO RPC service
let mkClient = simpleUtxorpcClient "127.0.0.1" 50051 False
bracket mkClient closeClient $ \case
Left clientErr -> throwIO clientErr
Right client -> do
-- Query tests
testReadParams client
testReadUtxosByOutputRef client
testSearchUtxosByAddress client
testSearchUtxosByPaymentPart client
testSearchUtxosByDelegationPart client
testSearchUtxosByPolicyID client
testSearchUtxosByAsset client
-- Sync tests
testSyncFollowTip client
testSyncReadTip client
testSyncFetchBlock client
testSyncDumpHistory client
-- Submit tests
testSubmitTx client
testWaitForTx client
-- WatchMempool tests
-- Submit transaction once for all WatchMempool tests
putStrLn "\n=== Submitting transaction for WatchMempool tests ==="
let txCborHex = "84A300D9010282825820F3627A04A2C0020720ACBD47E8CC90A1D64E9DAC9FCA7F12EB78DB8174E1FBE601825820B8BE6DF6FE61C465ED957D1901BC095C03E31612D1BC70EBFCC05CAC984A0088000182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D40D1E15021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F952585840191 02F26AF35BF37D9B220F71D68CACF28C596F6F74C45C1732820B61F3CBAED991C2D20E61FA38179AC228A1673B670F32544015D91AF74D786AA24C07CE900F5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack $ filter (/= ' ') txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage & SubF.raw .~ txCborBytes
submitRequest = defMessage & SubF.tx .~ anyChainTx
submitResult <- submitTx (submitClient client) submitRequest
case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
if not (BS.null txHash) then do
putStrLn $ " Transaction submitted! Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
-- Now run all WatchMempool tests - they'll all detect this transaction
testWatchMempoolByAddress client
testWatchMempoolByPaymentPart client
testWatchMempoolByDelegationPart client
testWatchMempoolByPolicyId client
testWatchMempoolByAsset client
else
throwString "Transaction submission returned no refs"
err -> throwString $ "Failed to submit transaction: " ++ show err
-- Watch tests - each test submits its own transaction
putStrLn "\n=== Running Watch tests (each submits and watches for blockchain application) ==="
testWatchTxForAddress client
testWatchTxForPaymentPart client
testWatchTxForDelegationPart client
testWatchTxForPolicyId client
testWatchTxForAsset client
putStrLn "\n✅ All tests passed!"
where
closeClient (Left _) = return ()
closeClient (Right client) = void (close client)
testReadParams :: UtxorpcClient -> IO ()
testReadParams client = do
putStrLn "\n=== Test: ReadParams ==="
result <- readParams (queryClient client) defMessage
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Get the parameters
let params = response ^. QF.values . QF.cardano
-- Show some actual values from the response
putStrLn $ " coinsPerUtxoByte: " ++ show (params ^. CF.coinsPerUtxoByte)
putStrLn $ " maxTxSize: " ++ show (params ^. CF.maxTxSize)
putStrLn $ " minFeeCoefficient: " ++ show (params ^. CF.minFeeCoefficient)
putStrLn $ " protocolVersion: " ++ show (params ^. CF.protocolVersion . CF.major)
-- Assert all expected parameter values for Preview testnet
-- BigInt fields need to be accessed via their bigInt oneof field using getBigInt helper
assertEqual "coinsPerUtxoByte" 4310 (getBigInt $ params ^. CF.coinsPerUtxoByte)
assertEqual "maxTxSize" 16384 (params ^. CF.maxTxSize)
assertEqual "minFeeCoefficient" 44 (getBigInt $ params ^. CF.minFeeCoefficient)
assertEqual "minFeeConstant" 155381 (getBigInt $ params ^. CF.minFeeConstant)
assertEqual "maxBlockBodySize" 90112 (params ^. CF.maxBlockBodySize)
assertEqual "maxBlockHeaderSize" 1100 (params ^. CF.maxBlockHeaderSize)
assertEqual "stakeKeyDeposit" 2000000 (getBigInt $ params ^. CF.stakeKeyDeposit)
assertEqual "poolDeposit" 500000000 (getBigInt $ params ^. CF.poolDeposit)
assertEqual "desiredNumberOfPools" 500 (params ^. CF.desiredNumberOfPools)
assertEqual "minPoolCost" 170000000 (getBigInt $ params ^. CF.minPoolCost)
assertEqual "maxValueSize" 5000 (params ^. CF.maxValueSize)
assertEqual "collateralPercentage" 150 (params ^. CF.collateralPercentage)
assertEqual "maxCollateralInputs" 3 (params ^. CF.maxCollateralInputs)
-- Protocol version
assertEqual "protocolVersion.major" 9 (params ^. CF.protocolVersion . CF.major)
-- Rational parameters
assertEqual "poolInfluence.numerator" 5033165 (params ^. CF.poolInfluence . CF.numerator)
assertEqual "poolInfluence.denominator" 16777216 (params ^. CF.poolInfluence . CF.denominator)
assertEqual "monetaryExpansion.numerator" 6442451 (params ^. CF.monetaryExpansion . CF.numerator)
assertEqual "monetaryExpansion.denominator" 2147483648 (params ^. CF.monetaryExpansion . CF.denominator)
assertEqual "treasuryExpansion.numerator" 13421773 (params ^. CF.treasuryExpansion . CF.numerator)
assertEqual "treasuryExpansion.denominator" 67108864 (params ^. CF.treasuryExpansion . CF.denominator)
-- Prices
assertEqual "prices.steps.numerator" 721 (params ^. CF.prices . CF.steps . CF.numerator)
assertEqual "prices.steps.denominator" 10000000 (params ^. CF.prices . CF.steps . CF.denominator)
assertEqual "prices.memory.numerator" 577 (params ^. CF.prices . CF.memory . CF.numerator)
assertEqual "prices.memory.denominator" 10000 (params ^. CF.prices . CF.memory . CF.denominator)
-- Execution units
assertEqual "maxExecutionUnitsPerTransaction.steps" 10000000000
(params ^. CF.maxExecutionUnitsPerTransaction . CF.steps)
assertEqual "maxExecutionUnitsPerTransaction.memory" 14000000
(params ^. CF.maxExecutionUnitsPerTransaction . CF.memory)
assertEqual "maxExecutionUnitsPerBlock.steps" 20000000000
(params ^. CF.maxExecutionUnitsPerBlock . CF.steps)
assertEqual "maxExecutionUnitsPerBlock.memory" 62000000
(params ^. CF.maxExecutionUnitsPerBlock . CF.memory)
-- Governance parameters
assertEqual "committeeTermLimit" 365 (params ^. CF.committeeTermLimit)
assertEqual "governanceActionValidityPeriod" 30 (params ^. CF.governanceActionValidityPeriod)
assertEqual "governanceActionDeposit" 100000000000 (getBigInt $ params ^. CF.governanceActionDeposit)
assertEqual "drepDeposit" 500000000 (getBigInt $ params ^. CF.drepDeposit)
assertEqual "drepInactivityPeriod" 20 (params ^. CF.drepInactivityPeriod)
-- Voting thresholds
let poolThresholds = params ^. CF.poolVotingThresholds . CF.thresholds
assertEqual "poolVotingThresholds count" 5 (length poolThresholds)
forM_ poolThresholds $ \threshold -> do
assertEqual "poolThreshold.numerator" 51 (threshold ^. CF.numerator)
assertEqual "poolThreshold.denominator" 100 (threshold ^. CF.denominator)
let drepThresholds = params ^. CF.drepVotingThresholds . CF.thresholds
assertEqual "drepVotingThresholds count" 10 (length drepThresholds)
-- Cost models
let plutusV1Values = params ^. CF.costModels . CF.plutusV1 . CF.values
when (100788 `notElem` plutusV1Values) $ throwString "plutusV1 missing 100788"
when (420 `notElem` plutusV1Values) $ throwString "plutusV1 missing 420"
when (1 `notElem` plutusV1Values) $ throwString "plutusV1 missing 1"
when (1000 `notElem` plutusV1Values) $ throwString "plutusV1 missing 1000"
putStrLn "✓ ReadParams test passed!"
err -> throwString $ "ReadParams failed: " ++ show err
testReadUtxosByOutputRef :: UtxorpcClient -> IO ()
testReadUtxosByOutputRef client = do
putStrLn "\n=== Test: ReadUtxosByOutputRef ==="
-- Arrange
let txHashHex = "9874bdf4ad47b2d30a2146fc4ba1f94859e58e772683e75001aca6e85de7690d"
txHash = case Base16.decode txHashHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
outputIndex = 0
txoRef = defMessage
& CF.hash .~ txHash
& CF.index .~ outputIndex
request = defMessage
& QF.keys .~ [txoRef]
-- Act
result <- readUtxos (queryClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let items = response ^. QF.items
utxo <- assertSingle "utxos" items
assertNotNull "ledgerTip" (Just $ response ^. QF.ledgerTip)
-- Show actual data from response
let output = utxo ^. QF.cardano
putStrLn $ " UTXO coin value: " ++ show (output ^. CF.coin) ++ " lovelace"
putStrLn $ " TxHash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
-- Verify the UTXO reference matches what we requested
assertEqual "txHash" txHash (utxo ^. QF.txoRef . CF.hash)
assertEqual "outputIndex" outputIndex (utxo ^. QF.txoRef . CF.index)
-- Verify native bytes
let expectedNativeBytes = "82583900729c67d0de8cde3c0afc768fb0fcb1596e8cfcbf781b553efcd228813b7bb577937983e016d4e8429ff48cf386d6818883f9e88b62a804e01a05f5e100"
let actualNativeBytesHex = Base16.encode (utxo ^. QF.nativeBytes)
assertEqual "nativeBytes" (T.toLower $ T.pack expectedNativeBytes) (T.decodeUtf8 actualNativeBytesHex)
-- Verify parsed state
let output = utxo ^. QF.cardano
assertTrue "coin > 0" ((getBigInt $ output ^. CF.coin) > 0)
putStrLn "✓ ReadUtxosByOutputRef test passed!"
err -> throwString $ "ReadUtxosByOutputRef failed: " ++ show err
testSearchUtxosByAddress :: UtxorpcClient -> IO ()
testSearchUtxosByAddress client = do
putStrLn "\n=== Test: SearchUtxosByAddress ==="
-- Arrange
let testAddressHex = "0053fbfffab7b001281917de77f18a8087413be03401db4aa2a7dbf0ae1591d34d5b4b2728d04a80fdd041bb52edb334dacbf25aa27877e738"
addressBytes = case Base16.decode testAddressHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
addressPattern = defMessage
& CF.exactAddress .~ addressBytes
cardanoPattern = defMessage
& CF.address .~ addressPattern
anyPattern = defMessage
& QF.cardano .~ cardanoPattern
predicate = defMessage
& QF.match .~ anyPattern
request = defMessage
& QF.predicate .~ predicate
& QF.maxItems .~ 10
-- Act
result <- searchUtxos (queryClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let items = response ^. QF.items
assertNotNull "ledgerTip" (Just $ response ^. QF.ledgerTip)
assertTrue "found UTXOs" (length items > 0)
-- Show actual data from response
putStrLn $ " Found " ++ show (length items) ++ " UTXOs for address"
when (length items > 0) $ do
let firstUtxo = head items
let firstOutput = firstUtxo ^. QF.cardano
putStrLn $ " First UTXO coin: " ++ show (firstOutput ^. CF.coin) ++ " lovelace"
-- Verify all returned UTXOs belong to the searched address
forM_ items $ \utxo -> do
let output = utxo ^. QF.cardano
assertEqual "address matches" addressBytes (output ^. CF.address)
putStrLn $ "✓ SearchUtxosByAddress test passed! Found " ++ show (length items) ++ " UTXOs"
err -> throwString $ "SearchUtxosByAddress failed: " ++ show err
testSearchUtxosByPaymentPart :: UtxorpcClient -> IO ()
testSearchUtxosByPaymentPart client = do
putStrLn "\n=== Test: SearchUtxosByPaymentPart ==="
-- Arrange
let paymentCredHex = "53fbfffab7b001281917de77f18a8087413be03401db4aa2a7dbf0ae"
paymentCredBytes = case Base16.decode paymentCredHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
addressPattern = defMessage
& CF.paymentPart .~ paymentCredBytes
cardanoPattern = defMessage
& CF.address .~ addressPattern
anyPattern = defMessage
& QF.cardano .~ cardanoPattern
predicate = defMessage
& QF.match .~ anyPattern
request = defMessage
& QF.predicate .~ predicate
& QF.maxItems .~ 5
-- Act
result <- searchUtxos (queryClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let items = response ^. QF.items
assertNotNull "ledgerTip" (Just $ response ^. QF.ledgerTip)
assertTrue "found UTXOs" (length items > 0)
-- Show actual data from response
putStrLn $ " Found " ++ show (length items) ++ " UTXOs matching payment credential"
-- Verify all returned UTXOs have the correct payment credential
forM_ items $ \utxo -> do
let output = utxo ^. QF.cardano
let utxoAddressBytes = output ^. CF.address
-- Payment credential is bytes 1-28 (after network byte)
let utxoPaymentCred = BS.take 28 $ BS.drop 1 utxoAddressBytes
assertEqual "payment credential matches" paymentCredBytes utxoPaymentCred
putStrLn $ "✓ SearchUtxosByPaymentPart test passed! Found " ++ show (length items) ++ " UTXOs"
err -> throwString $ "SearchUtxosByPaymentPart failed: " ++ show err
testSearchUtxosByDelegationPart :: UtxorpcClient -> IO ()
testSearchUtxosByDelegationPart client = do
putStrLn "\n=== Test: SearchUtxosByDelegationPart ==="
-- Arrange
let delegationCredHex = "1591d34d5b4b2728d04a80fdd041bb52edb334dacbf25aa27877e738"
delegationCredBytes = case Base16.decode delegationCredHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
addressPattern = defMessage
& CF.delegationPart .~ delegationCredBytes
cardanoPattern = defMessage
& CF.address .~ addressPattern
anyPattern = defMessage
& QF.cardano .~ cardanoPattern
predicate = defMessage
& QF.match .~ anyPattern
request = defMessage
& QF.predicate .~ predicate
& QF.maxItems .~ 5
-- Act
result <- searchUtxos (queryClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let items = response ^. QF.items
assertNotNull "ledgerTip" (Just $ response ^. QF.ledgerTip)
assertTrue "found UTXOs" (length items > 0)
-- Show actual data from response
putStrLn $ " Found " ++ show (length items) ++ " UTXOs matching delegation credential"
-- Verify all returned UTXOs have the correct delegation credential
forM_ items $ \utxo -> do
let output = utxo ^. QF.cardano
let utxoAddressBytes = output ^. CF.address
-- Base address should be 57 bytes: 1 (network) + 28 (payment) + 28 (delegation)
assertEqual "address length" 57 (BS.length utxoAddressBytes)
-- Delegation credential is bytes 29-56 (last 28 bytes)
let utxoDelegationCred = BS.take 28 $ BS.drop 29 utxoAddressBytes
assertEqual "delegation credential matches" delegationCredBytes utxoDelegationCred
putStrLn $ "✓ SearchUtxosByDelegationPart test passed! Found " ++ show (length items) ++ " UTXOs"
err -> throwString $ "SearchUtxosByDelegationPart failed: " ++ show err
testSearchUtxosByPolicyID :: UtxorpcClient -> IO ()
testSearchUtxosByPolicyID client = do
putStrLn "\n=== Test: SearchUtxosByPolicyID ==="
-- Arrange
let policyIdHex = "047e0f912c4260fe66ae271e5ae494dcd5f79635bbbb1386be195f4e"
policyIdBytes = case Base16.decode policyIdHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
assetPattern = defMessage
& CF.policyId .~ policyIdBytes
cardanoPattern = defMessage
& CF.asset .~ assetPattern
anyPattern = defMessage
& QF.cardano .~ cardanoPattern
predicate = defMessage
& QF.match .~ anyPattern
request = defMessage
& QF.predicate .~ predicate
& QF.maxItems .~ 10
-- Act
result <- searchUtxos (queryClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let items = response ^. QF.items
assertNotNull "ledgerTip" (Just $ response ^. QF.ledgerTip)
assertTrue "found UTXOs" (length items > 0)
-- Show actual data from response
putStrLn $ " Found " ++ show (length items) ++ " UTXOs containing policy ID"
putStrLn $ " Policy ID: " ++ T.unpack (T.decodeUtf8 $ Base16.encode policyIdBytes)
-- Verify all returned UTXOs contain the searched policy ID
forM_ items $ \utxo -> do
let output = utxo ^. QF.cardano
let assetGroups = output ^. CF.assets
assertTrue "has assets" (length assetGroups > 0)
-- Check that at least one asset group has the expected policy ID
let hasExpectedPolicy = any (\ag -> (ag ^. CF.policyId) == policyIdBytes) assetGroups
assertTrue "contains expected policy ID" hasExpectedPolicy
putStrLn $ "✓ SearchUtxosByPolicyID test passed! Found " ++ show (length items) ++ " UTXOs"
err -> throwString $ "SearchUtxosByPolicyID failed: " ++ show err
testSearchUtxosByAsset :: UtxorpcClient -> IO ()
testSearchUtxosByAsset client = do
putStrLn "\n=== Test: SearchUtxosByAsset ==="
-- Arrange
let policyIdHex = "047e0f912c4260fe66ae271e5ae494dcd5f79635bbbb1386be195f4e"
assetNameHex = "414c4c45594b41545a3030303630" -- "ALLEYKATZ00060"
policyIdBytes = case Base16.decode policyIdHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
assetNameOnlyBytes = case Base16.decode assetNameHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
-- Concatenate policy ID and asset name for the assetName field
fullAssetNameBytes = policyIdBytes <> assetNameOnlyBytes
assetPattern = defMessage
& CF.assetName .~ fullAssetNameBytes
cardanoPattern = defMessage
& CF.asset .~ assetPattern
anyPattern = defMessage
& QF.cardano .~ cardanoPattern
predicate = defMessage
& QF.match .~ anyPattern
request = defMessage
& QF.predicate .~ predicate
& QF.maxItems .~ 10
-- Act
result <- searchUtxos (queryClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let items = response ^. QF.items
assertNotNull "ledgerTip" (Just $ response ^. QF.ledgerTip)
assertTrue "found UTXOs" (length items > 0)
-- Show actual data from response
putStrLn $ " Found " ++ show (length items) ++ " UTXOs containing specific asset"
putStrLn $ " Asset: " ++ T.unpack (T.decodeUtf8 assetNameHex) ++ " (ALLEYKATZ00060)"
-- Verify all returned UTXOs contain the exact asset
forM_ items $ \utxo -> do
let output = utxo ^. QF.cardano
let assetGroups = output ^. CF.assets
-- Find the asset group with our policy ID
let maybeAssetGroup = find (\ag -> (ag ^. CF.policyId) == policyIdBytes) assetGroups
case maybeAssetGroup of
Just assetGroup -> do
let assets = assetGroup ^. CF.assets
-- Check that this group contains our asset name (just the name part, not policy+name)
let hasExpectedAsset = any (\a -> (a ^. CF.name) == assetNameOnlyBytes) assets
assertTrue "contains expected asset name" hasExpectedAsset
Nothing -> throwString "Asset group with expected policy ID not found"
putStrLn $ "✓ SearchUtxosByAsset test passed! Found " ++ show (length items) ++ " UTXOs"
err -> throwString $ "SearchUtxosByAsset failed: " ++ show err
testSyncFollowTip :: UtxorpcClient -> IO ()
testSyncFollowTip client = do
putStrLn "\n=== Test: SyncFollowTip ==="
-- Arrange - Use the same test point as the Node SDK
let testSlot = 85213090
testHashHex = "e50842b1cc3ac813cb88d1533c3dea0f92e0ea945f53487c1d960c2210d0c3ba"
testHashBytes = case Base16.decode testHashHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
intersectPoint = defMessage
& SF.slot .~ testSlot
& SF.hash .~ testHashBytes
request = defMessage
& SF.intersect .~ [intersectPoint]
-- Act - Follow the same pattern as the working example but catch the planned exception
result <- try $ followTip (syncClient client) (0 :: Int) request streamHandler
case result of
Left (exc :: SomeException) ->
if "FollowTip test completed successfully" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ SyncFollowTip test passed! Validated block assertions and stopped"
else
putStrLn $ "SyncFollowTip failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (messageCount, _headers, _trailers)) -> do
putStrLn $ "✓ SyncFollowTip test passed! Received " ++ show messageCount ++ " messages"
err -> putStrLn $ "SyncFollowTip failed: " ++ show err
where
streamHandler count _headers (response :: FollowTipResponse) = do
putStrLn $ "Received stream message #" ++ show (count + 1)
-- Get the block from the response
let maybeBlock = case response ^. SF.maybe'action of
Just action -> case action of
S.FollowTipResponse'Apply applyAction -> Just applyAction
S.FollowTipResponse'Undo undoAction -> Just undoAction
_ -> Nothing
Nothing -> Nothing
case maybeBlock of
Just block -> do
-- Assert block has cardano data
let cardanoBlock = block ^. SF.cardano
let blockHeader = cardanoBlock ^. CF.header
let blockSlot = blockHeader ^. SF.slot
let blockHash = blockHeader ^. SF.hash
-- Assert that slot is a positive number (like other tests)
assertTrue ("followTip slot > 0 for message #" ++ show (count + 1)) (blockSlot > 0)
-- Assert that hash is not empty (like other tests)
assertTrue ("followTip hash not empty for message #" ++ show (count + 1)) (BS.length blockHash > 0)
-- Assert that height is reasonable (like other tests)
let blockHeight = blockHeader ^. SF.height
assertTrue ("followTip height > 0 for message #" ++ show (count + 1)) (blockHeight > 0)
putStrLn $ "✓ Message #" ++ show (count + 1) ++ " - Slot: " ++ show blockSlot ++
", Height: " ++ show blockHeight ++
", Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode blockHash) ++ "..."
-- Stop after validating a few successful messages (like Node SDK tests)
if count >= 2 then
throwString "FollowTip test completed successfully - stopping stream"
else
return (count + 1)
Nothing -> do
putStrLn $ "⚠ Message #" ++ show (count + 1) ++ " - No block data (might be reset action)"
return (count + 1)
testSyncReadTip :: UtxorpcClient -> IO ()
testSyncReadTip client = do
putStrLn "\n=== Test: SyncReadTip ==="
-- Arrange
let request = defMessage
-- Act
result <- readTip (syncClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert - ReadTipResponse has a tip field, not slot/hash directly
let tip = response ^. SF.tip
let tipSlot = tip ^. SF.slot
let tipHash = tip ^. SF.hash
-- Show actual data from response
putStrLn $ " Current tip slot: " ++ show tipSlot
putStrLn $ " Current tip hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode tipHash) ++ "..."
-- Assert that slot is a positive number
assertTrue "slot > 0" (tipSlot > 0)
-- Assert that hash is not empty
assertTrue "hash not empty" (BS.length tipHash > 0)
putStrLn $ "✓ SyncReadTip test passed!"
err -> throwString $ "SyncReadTip failed: " ++ show err
testSyncFetchBlock :: UtxorpcClient -> IO ()
testSyncFetchBlock client = do
putStrLn "\n=== Test: SyncFetchBlock ==="
-- Arrange - Use the same test point as the Node SDK
let testSlot = 85213090
testHashHex = "e50842b1cc3ac813cb88d1533c3dea0f92e0ea945f53487c1d960c2210d0c3ba"
testHashBytes = case Base16.decode testHashHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
blockRef = defMessage
& SF.slot .~ testSlot
& SF.hash .~ testHashBytes
request = defMessage
& SF.ref .~ [blockRef]
-- Act
result <- fetchBlock (syncClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let blocks = response ^. SF.block
block <- assertSingle "blocks" blocks
-- Assert block has cardano data
let cardanoBlock = block ^. SF.cardano
let blockHeader = cardanoBlock ^. CF.header
let blockBody = cardanoBlock ^. CF.body
let txCount = length (blockBody ^. CF.tx)
-- Show actual data from response
putStrLn $ " Block slot: " ++ show (blockHeader ^. SF.slot)
putStrLn $ " Block height: " ++ show (blockHeader ^. SF.height)
putStrLn $ " Block hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode testHashBytes) ++ "..."
putStrLn $ " Transaction count: " ++ show txCount
assertEqual "block slot" testSlot (blockHeader ^. SF.slot)
assertEqual "block height" 3399486 (blockHeader ^. SF.height)
-- Assert block body exists (even if empty)
assertNotNull "block body" (Just blockBody)
putStrLn "✓ SyncFetchBlock test passed!"
err -> throwString $ "SyncFetchBlock failed: " ++ show err
testSyncDumpHistory :: UtxorpcClient -> IO ()
testSyncDumpHistory client = do
putStrLn "\n=== Test: SyncDumpHistory ==="
-- Arrange - Use the same test point as the Node SDK
let testSlot = 85213090
testHashHex = "e50842b1cc3ac813cb88d1533c3dea0f92e0ea945f53487c1d960c2210d0c3ba"
testHashBytes = case Base16.decode testHashHex of
Right bs -> bs
Left err -> error $ "Invalid hex: " ++ err
startToken = defMessage
& SF.slot .~ testSlot
& SF.hash .~ testHashBytes
request = defMessage
& SF.startToken .~ startToken
& SF.maxItems .~ 1
-- Act
result <- dumpHistory (syncClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert
let blocks = response ^. SF.block
block <- assertSingle "blocks" blocks
-- Assert block has cardano data
let cardanoBlock = block ^. SF.cardano
let blockHeader = cardanoBlock ^. CF.header
let blockBody = cardanoBlock ^. CF.body
let txCount = length (blockBody ^. CF.tx)
-- Show actual data from response
putStrLn $ " Block slot: " ++ show (blockHeader ^. SF.slot)
putStrLn $ " Block height: " ++ show (blockHeader ^. SF.height)
putStrLn $ " Transaction count: " ++ show txCount
assertEqual "block slot" testSlot (blockHeader ^. SF.slot)
assertEqual "block height" 3399486 (blockHeader ^. SF.height)
-- Assert block body exists (even if empty)
assertNotNull "block body" (Just blockBody)
putStrLn "✓ SyncDumpHistory test passed!"
err -> throwString $ "SyncDumpHistory failed: " ++ show err
-- ============================================================================
-- Submit Tests
-- ============================================================================
testSubmitTx :: UtxorpcClient -> IO ()
testSubmitTx client = do
putStrLn "\n=== Test: SubmitTx ==="
-- Arrange - Use a pre-built signed transaction CBOR hex
let txCborHex = "84A300D9010282825820AAD611BED4B7089272AEE943CF0975544C46626272929EB50BD6A7D319B43BD303825820970727113A6B9D6E54E2541E5FFFD86D6CF7B7CEA4F6793821DE2C0D58B8D6B5030182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D35FFD4C021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F95258584046563C4A3D87995D5A1EC03632BF1AF532A1468F7F2403C1055676776D03B8F986EC667A2D67329D97052C9216CF9752E38430E1430F0FEE0FFF958E25F6020AF5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
-- Create AnyChain with Cardano raw CBOR
anyChainTx = defMessage
& SubF.raw .~ txCborBytes
-- Create request with the transaction
request = defMessage
& SubF.tx .~ anyChainTx
-- Act
result <- submitTx (submitClient client) request
case result of
Right (Right (Right (_headers, _trailers, Right response))) -> do
putStrLn "✓ Response received from server"
-- Assert.NotNull(response) - implicit (we have response)
-- Assert.NotNull(response.Refs)
let txHash = response ^. SubF.ref
assertNotNull "response.Refs" (if BS.null txHash then Nothing else Just txHash)
-- Assert.Equal(32, response.Refs.Length)
assertEqual "Tx hash length" 32 (BS.length txHash)
putStrLn $ " Transaction submitted successfully!"
putStrLn $ " Tx hash: " ++ T.unpack (T.decodeUtf8 $ Base16.encode txHash)
putStrLn "✓ SubmitTx test passed!"
err -> throwString $ "SubmitTx failed: " ++ show err
testWaitForTx :: UtxorpcClient -> IO ()
testWaitForTx client = do
putStrLn "\n=== Test: WaitForTx ==="
-- Arrange - First submit a transaction to wait for
let txCborHex = "84A300D90102828258207478ED8D8B53CDB520E257AADAFD4CA0EC8671ECA1AE0EEE6D045F9ABE357A78018258208841ABB27C76AF2841727ACF562149EBE47957A71C5C12C8A6A1FAE089CCAAD8020182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D3D2B3A2021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F952585840AC13B5036324F1273A576834F4A11474CC0FF7C79F31B1E1BD79301E671172CC94B7BCA8C816F80BE343A2389998E0E861C53F501614AD5186355B65F228EF0BF5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage
& SubF.raw .~ txCborBytes
submitRequest = defMessage
& SubF.tx .~ anyChainTx
-- Submit the transaction first
submitResult <- submitTx (submitClient client) submitRequest
txHash <- case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
assertTrue "has tx ref" (not $ BS.null txHash)
return txHash
err -> throwString $ "Failed to submit tx for WaitForTx test: " ++ show err
putStrLn $ " Submitted tx hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
-- Create WaitForTxRequest with tx hash
let waitRequest = defMessage
& SubF.ref .~ [txHash]
-- Act - Wait for the transaction (collect at least 2 stages)
result <- try $ waitForTx (submitClient client) [] waitRequest streamHandler
case result of
Left (exc :: SomeException) ->
if "WaitForTx test completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WaitForTx test passed! Validated stages and stopped"
else
throwString $ "WaitForTx failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (stages, _headers, _trailers)) -> do
-- Assert.True(stages.Count >= 1)
assertTrue "stages.Count >= 1" (length stages >= 1)
let firstStage = head stages
-- Assert.NotNull(firstStage) - implicit
-- Assert.NotNull(firstStage.Ref)
let stageRef = firstStage ^. SubF.ref
assertNotNull "firstStage.Ref" (if BS.null stageRef then Nothing else Just stageRef)
-- Assert.Equal(txHash, firstStage.Ref)
assertEqual "firstStage.Ref matches txHash" txHash stageRef
-- Assert.True(firstStage.Stage >= Stage.Acknowledged)
let stageNum = firstStage ^. SubF.stage
assertTrue "firstStage.Stage >= Acknowledged" (stageNum >= STAGE_ACKNOWLEDGED)
putStrLn $ "✓ WaitForTx test passed! Received " ++ show (length stages) ++ " stage updates"
err -> throwString $ "WaitForTx failed: " ++ show err
where
streamHandler :: [WaitForTxResponse] -> a -> WaitForTxResponse -> IO [WaitForTxResponse]
streamHandler stages _headers response = do
let stageNum = response ^. SubF.stage
putStrLn $ " Stage update #" ++ show (length stages + 1) ++ ": " ++ show stageNum
let newStages = stages ++ [response]
-- Stop after 2 stages: if (stages.Count >= 2) break
if length newStages >= 2 then
throwString "WaitForTx test completed - stopping stream"
else
return newStages
testWatchMempoolByAddress :: UtxorpcClient -> IO ()
testWatchMempoolByAddress client = do
putStrLn "\n=== Test: WatchMempoolByAddress ==="
-- Arrange
let receiverAddressHex = "0053fbfffab7b001281917de77f18a8087413be03401db4aa2a7dbf0ae1591d34d5b4b2728d04a80fdd041bb52edb334dacbf25aa27877e738"
receiverAddressBytes = case Base16.decode receiverAddressHex of
Right bs -> bs
Left err -> error $ "Invalid address hex: " ++ err
-- Create address predicate for exact address match
addressPattern = (defMessage :: AddressPattern)
& CF.exactAddress .~ receiverAddressBytes
txPattern = (defMessage :: TxPattern)
& CF.hasAddress .~ addressPattern
anyChainPattern = (defMessage :: AnyChainTxPattern)
& SubF.cardano .~ txPattern
predicate = (defMessage :: TxPredicate)
& SubF.match .~ anyChainPattern
watchRequest = defMessage
& SubF.predicate .~ predicate
putStrLn $ " Watching for address: " ++ T.unpack (T.take 20 $ T.decodeUtf8 $ Base16.encode receiverAddressBytes) ++ "..."
-- Act - Watch mempool (transaction already submitted)
result <- try $ watchMempool (submitClient client) [] watchRequest streamHandler
case result of
Left (exc :: SomeException) ->
if "WatchMempoolByAddress completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchMempoolByAddress test passed! Found matching transaction"
else
throwString $ "WatchMempoolByAddress failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
-- Assert.True(events.Count > 0)
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
-- Assert.NotNull(firstEvent.Tx)
let maybeTx = firstEvent ^. SubF.maybe'tx
assertNotNull "firstEvent.Tx" maybeTx
case maybeTx of
Just txInfo -> do
-- Assert.NotNull(firstEvent.Tx?.Ref)
let txRef = txInfo ^. SubF.ref
assertNotNull "firstEvent.Tx.Ref" (if BS.null txRef then Nothing else Just txRef)
-- Assert.True(firstEvent.Tx?.Stage >= Stage.Acknowledged)
let stage = txInfo ^. SubF.stage
assertTrue "firstEvent.Tx.Stage >= Acknowledged" (stage >= STAGE_ACKNOWLEDGED)
putStrLn $ "✓ WatchMempoolByAddress test passed! Found " ++ show (length events) ++ " event(s)"
Nothing -> throwString "firstEvent.Tx is null"
err -> throwString $ "WatchMempoolByAddress failed: " ++ show err
where
streamHandler :: [WatchMempoolResponse] -> a -> WatchMempoolResponse -> IO [WatchMempoolResponse]
streamHandler events _headers response = do
let maybeTx = response ^. SubF.maybe'tx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
let stage = txInfo ^. SubF.stage
putStrLn $ " Found transaction in mempool!"
putStrLn $ " Tx ref: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txRef) ++ "..."
putStrLn $ " Stage: " ++ show stage
let newEvents = events ++ [response]
-- Stop after 1 event: if (events.Count >= 1) break
if length newEvents >= 1 then
throwString "WatchMempoolByAddress completed - stopping stream"
else
return newEvents
Nothing -> do
putStrLn " Waiting for matching transaction..."
return events
testWatchMempoolByPaymentPart :: UtxorpcClient -> IO ()
testWatchMempoolByPaymentPart client = do
putStrLn "\n=== Test: WatchMempoolByPaymentPart ==="
-- Arrange - Extract payment credential from address
let paymentCredHex = "53fbfffab7b001281917de77f18a8087413be03401db4aa2a7dbf0ae"
paymentCredBytes = case Base16.decode paymentCredHex of
Right bs -> bs
Left err -> error $ "Invalid payment cred hex: " ++ err
addressPattern = (defMessage :: AddressPattern)
& CF.paymentPart .~ paymentCredBytes
txPattern = (defMessage :: TxPattern)
& CF.hasAddress .~ addressPattern
anyChainPattern = (defMessage :: AnyChainTxPattern)
& SubF.cardano .~ txPattern
predicate = (defMessage :: TxPredicate)
& SubF.match .~ anyChainPattern
watchRequest = defMessage
& SubF.predicate .~ predicate
putStrLn $ " Watching for payment credential: " ++ T.unpack (T.decodeUtf8 $ Base16.encode paymentCredBytes)
-- Act - Watch mempool
result <- try $ watchMempool (submitClient client) [] watchRequest streamHandler
case result of
Left (exc :: SomeException) ->
if "WatchMempoolByPaymentPart completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchMempoolByPaymentPart test passed! Found matching transaction"
else
throwString $ "WatchMempoolByPaymentPart failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeTx = firstEvent ^. SubF.maybe'tx
assertNotNull "firstEvent.Tx" maybeTx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
assertNotNull "firstEvent.Tx.Ref" (if BS.null txRef then Nothing else Just txRef)
let stage = txInfo ^. SubF.stage
assertTrue "firstEvent.Tx.Stage >= Acknowledged" (stage >= STAGE_ACKNOWLEDGED)
putStrLn $ "✓ WatchMempoolByPaymentPart test passed!"
Nothing -> throwString "firstEvent.Tx is null"
err -> throwString $ "WatchMempoolByPaymentPart failed: " ++ show err
where
streamHandler :: [WatchMempoolResponse] -> a -> WatchMempoolResponse -> IO [WatchMempoolResponse]
streamHandler events _headers response = do
let maybeTx = response ^. SubF.maybe'tx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
let stage = txInfo ^. SubF.stage
putStrLn $ " Found transaction matching payment credential!"
putStrLn $ " Tx ref: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txRef) ++ "..."
putStrLn $ " Stage: " ++ show stage
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchMempoolByPaymentPart completed - stopping stream"
else
return newEvents
Nothing -> return events
testWatchMempoolByDelegationPart :: UtxorpcClient -> IO ()
testWatchMempoolByDelegationPart client = do
putStrLn "\n=== Test: WatchMempoolByDelegationPart ==="
-- Arrange - Extract delegation credential from address
let delegationCredHex = "1591d34d5b4b2728d04a80fdd041bb52edb334dacbf25aa27877e738"
delegationCredBytes = case Base16.decode delegationCredHex of
Right bs -> bs
Left err -> error $ "Invalid delegation cred hex: " ++ err
addressPattern = (defMessage :: AddressPattern)
& CF.delegationPart .~ delegationCredBytes
txPattern = (defMessage :: TxPattern)
& CF.hasAddress .~ addressPattern
anyChainPattern = (defMessage :: AnyChainTxPattern)
& SubF.cardano .~ txPattern
predicate = (defMessage :: TxPredicate)
& SubF.match .~ anyChainPattern
watchRequest = defMessage
& SubF.predicate .~ predicate
putStrLn $ " Watching for delegation credential: " ++ T.unpack (T.decodeUtf8 $ Base16.encode delegationCredBytes)
-- Act - Watch mempool
result <- try $ watchMempool (submitClient client) [] watchRequest streamHandler
case result of
Left (exc :: SomeException) ->
if "WatchMempoolByDelegationPart completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchMempoolByDelegationPart test passed! Found matching transaction"
else
throwString $ "WatchMempoolByDelegationPart failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeTx = firstEvent ^. SubF.maybe'tx
assertNotNull "firstEvent.Tx" maybeTx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
assertNotNull "firstEvent.Tx.Ref" (if BS.null txRef then Nothing else Just txRef)
let stage = txInfo ^. SubF.stage
assertTrue "firstEvent.Tx.Stage >= Acknowledged" (stage >= STAGE_ACKNOWLEDGED)
putStrLn $ "✓ WatchMempoolByDelegationPart test passed!"
Nothing -> throwString "firstEvent.Tx is null"
err -> throwString $ "WatchMempoolByDelegationPart failed: " ++ show err
where
streamHandler :: [WatchMempoolResponse] -> a -> WatchMempoolResponse -> IO [WatchMempoolResponse]
streamHandler events _headers response = do
let maybeTx = response ^. SubF.maybe'tx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
let stage = txInfo ^. SubF.stage
putStrLn $ " Found transaction matching delegation credential!"
putStrLn $ " Tx ref: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txRef) ++ "..."
putStrLn $ " Stage: " ++ show stage
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchMempoolByDelegationPart completed - stopping stream"
else
return newEvents
Nothing -> return events
testWatchMempoolByPolicyId :: UtxorpcClient -> IO ()
testWatchMempoolByPolicyId client = do
putStrLn "\n=== Test: WatchMempoolByPolicyId ==="
-- Arrange
let policyIdHex = "8b05e87a51c1d4a0fa888d2bb14dbc25e8c343ea379a171b63aa84a0"
policyIdBytes = case Base16.decode policyIdHex of
Right bs -> bs
Left err -> error $ "Invalid policy ID hex: " ++ err
assetPattern = (defMessage :: AssetPattern)
& CF.policyId .~ policyIdBytes
txPattern = (defMessage :: TxPattern)
& CF.movesAsset .~ assetPattern
anyChainPattern = (defMessage :: AnyChainTxPattern)
& SubF.cardano .~ txPattern
predicate = (defMessage :: TxPredicate)
& SubF.match .~ anyChainPattern
watchRequest = defMessage
& SubF.predicate .~ predicate
putStrLn $ " Watching for policy ID: " ++ T.unpack (T.decodeUtf8 $ Base16.encode policyIdBytes)
-- Act - Watch mempool
result <- try $ watchMempool (submitClient client) [] watchRequest streamHandler
case result of
Left (exc :: SomeException) ->
if "WatchMempoolByPolicyId completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchMempoolByPolicyId test passed! Found matching transaction"
else
throwString $ "WatchMempoolByPolicyId failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeTx = firstEvent ^. SubF.maybe'tx
assertNotNull "firstEvent.Tx" maybeTx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
assertNotNull "firstEvent.Tx.Ref" (if BS.null txRef then Nothing else Just txRef)
let stage = txInfo ^. SubF.stage
assertTrue "firstEvent.Tx.Stage >= Acknowledged" (stage >= STAGE_ACKNOWLEDGED)
putStrLn $ "✓ WatchMempoolByPolicyId test passed!"
Nothing -> throwString "firstEvent.Tx is null"
err -> throwString $ "WatchMempoolByPolicyId failed: " ++ show err
where
streamHandler :: [WatchMempoolResponse] -> a -> WatchMempoolResponse -> IO [WatchMempoolResponse]
streamHandler events _headers response = do
let maybeTx = response ^. SubF.maybe'tx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
let stage = txInfo ^. SubF.stage
putStrLn $ " Found transaction with policy ID!"
putStrLn $ " Tx ref: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txRef) ++ "..."
putStrLn $ " Stage: " ++ show stage
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchMempoolByPolicyId completed - stopping stream"
else
return newEvents
Nothing -> return events
testWatchMempoolByAsset :: UtxorpcClient -> IO ()
testWatchMempoolByAsset client = do
putStrLn "\n=== Test: WatchMempoolByAsset ==="
-- Arrange
let policyIdHex = "8b05e87a51c1d4a0fa888d2bb14dbc25e8c343ea379a171b63aa84a0"
assetNameHex = "434e4354" -- "CNCT" in hex
policyIdBytes = case Base16.decode policyIdHex of
Right bs -> bs
Left err -> error $ "Invalid policy ID hex: " ++ err
assetNameOnlyBytes = case Base16.decode assetNameHex of
Right bs -> bs
Left err -> error $ "Invalid asset name hex: " ++ err
-- Concatenate policy ID and asset name
fullAssetNameBytes = policyIdBytes <> assetNameOnlyBytes
assetPattern = (defMessage :: AssetPattern)
& CF.policyId .~ policyIdBytes
& CF.assetName .~ assetNameOnlyBytes
txPattern = (defMessage :: TxPattern)
& CF.movesAsset .~ assetPattern
anyChainPattern = (defMessage :: AnyChainTxPattern)
& SubF.cardano .~ txPattern
predicate = (defMessage :: TxPredicate)
& SubF.match .~ anyChainPattern
watchRequest = defMessage
& SubF.predicate .~ predicate
putStrLn $ " Watching for asset: " ++ T.unpack (T.decodeUtf8 assetNameHex) ++ " (CNCT)"
-- Act - Watch mempool
result <- try $ watchMempool (submitClient client) [] watchRequest streamHandler
case result of
Left (exc :: SomeException) ->
if "WatchMempoolByAsset completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchMempoolByAsset test passed! Found matching transaction"
else
throwString $ "WatchMempoolByAsset failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeTx = firstEvent ^. SubF.maybe'tx
assertNotNull "firstEvent.Tx" maybeTx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
assertNotNull "firstEvent.Tx.Ref" (if BS.null txRef then Nothing else Just txRef)
let stage = txInfo ^. SubF.stage
assertTrue "firstEvent.Tx.Stage >= Acknowledged" (stage >= STAGE_ACKNOWLEDGED)
putStrLn $ "✓ WatchMempoolByAsset test passed!"
Nothing -> throwString "firstEvent.Tx is null"
err -> throwString $ "WatchMempoolByAsset failed: " ++ show err
where
streamHandler :: [WatchMempoolResponse] -> a -> WatchMempoolResponse -> IO [WatchMempoolResponse]
streamHandler events _headers response = do
let maybeTx = response ^. SubF.maybe'tx
case maybeTx of
Just txInfo -> do
let txRef = txInfo ^. SubF.ref
let stage = txInfo ^. SubF.stage
putStrLn $ " Found transaction with specific asset!"
putStrLn $ " Tx ref: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txRef) ++ "..."
putStrLn $ " Stage: " ++ show stage
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchMempoolByAsset completed - stopping stream"
else
return newEvents
Nothing -> return events
-- ============================================================================
-- Watch Tests
-- ============================================================================
testWatchTxForAddress :: UtxorpcClient -> IO ()
testWatchTxForAddress client = do
putStrLn "\n=== Test: WatchTxForAddress ==="
-- Arrange
let receiverAddressHex = "0053fbfffab7b001281917de77f18a8087413be03401db4aa2a7dbf0ae1591d34d5b4b2728d04a80fdd041bb52edb334dacbf25aa27877e738"
receiverAddressBytes = case Base16.decode receiverAddressHex of
Right bs -> bs
Left err -> error $ "Invalid address hex: " ++ err
-- Create address predicate for exact address match
addressPattern = (defMessage :: AddressPattern)
& CF.exactAddress .~ receiverAddressBytes
txPattern = (defMessage :: TxPattern)
& CF.hasAddress .~ addressPattern
anyChainPattern = (defMessage :: W.AnyChainTxPattern)
& WF.cardano .~ txPattern
predicate = (defMessage :: W.TxPredicate)
& WF.match .~ anyChainPattern
watchRequest = defMessage
& WF.predicate .~ predicate
putStrLn $ " Watching for address: " ++ T.unpack (T.take 20 $ T.decodeUtf8 $ Base16.encode receiverAddressBytes) ++ "..."
-- Create MVar to store watcher result
resultVar <- newEmptyMVar
-- Start watching in background (before submitting transaction)
_ <- forkIO $ do
result <- try $ watchTx (watchClient client) [] watchRequest streamHandler
putMVar resultVar result
-- Give the watcher time to start
putStrLn " Started watching, waiting 1 second for watcher to initialize..."
threadDelay 1000000 -- 1 second in microseconds
-- Submit transaction
putStrLn " Submitting transaction..."
let txCborHex = "84A300D90102828258209E0CC477DF20C37C684E47B9701F995C8733B07901DE5447FE4F61EDF14F6BCB01825820176A54A6E3911FF5C65134928079E40783C8A88F1143091687F3FE7775EEF55F000182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D4478888021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F952585840DBBF46C2B615D9EE1689F6339A537A3A16507C2566E067A717F0D379D9944F8FEF1045AA525B71EB6E29877BFAEA83F295F25707B0F7AF41222E3F33C9861C0CF5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage & SubF.raw .~ txCborBytes
submitRequest = defMessage & SubF.tx .~ anyChainTx
submitResult <- submitTx (submitClient client) submitRequest
case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
when (not (BS.null txHash)) $ do
putStrLn $ " Transaction submitted! Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
_ -> putStrLn " Warning: Transaction submission may have issues"
putStrLn " Waiting for Watch event (transaction to be applied to block)..."
-- Wait for watcher result
result <- takeMVar resultVar
-- Assert
case result of
Left (exc :: SomeException) ->
if "WatchTxForAddress completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchTxForAddress test passed! Found matching transaction in block"
else
throwString $ "WatchTxForAddress failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
-- Assert.True(events.Count > 0)
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
-- Assert.NotNull(firstEvent)
let maybeAction = firstEvent ^. WF.maybe'action
assertNotNull "firstEvent.Action" maybeAction
-- Assert.Equal(WatchTxAction.Apply, firstEvent.Action)
case maybeAction of
Just (WatchTxResponse'Apply _) ->
putStrLn $ "✓ WatchTxForAddress test passed! Found " ++ show (length events) ++ " Apply event(s)"
_ -> throwString "Expected Apply action"
err -> throwString $ "WatchTxForAddress failed: " ++ show err
where
streamHandler :: [WatchTxResponse] -> a -> WatchTxResponse -> IO [WatchTxResponse]
streamHandler events _headers response = do
let maybeAction = response ^. WF.maybe'action
case maybeAction of
Just (WatchTxResponse'Apply _) -> do
putStrLn $ " Found Apply action for transaction!"
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchTxForAddress completed - stopping stream"
else
return newEvents
_ -> return events
testWatchTxForPaymentPart :: UtxorpcClient -> IO ()
testWatchTxForPaymentPart client = do
putStrLn "\n=== Test: WatchTxForPaymentPart ==="
-- Arrange - Extract payment credential from address
let paymentCredHex = "53fbfffab7b001281917de77f18a8087413be03401db4aa2a7dbf0ae"
paymentCredBytes = case Base16.decode paymentCredHex of
Right bs -> bs
Left err -> error $ "Invalid payment cred hex: " ++ err
addressPattern = (defMessage :: AddressPattern)
& CF.paymentPart .~ paymentCredBytes
txPattern = (defMessage :: TxPattern)
& CF.hasAddress .~ addressPattern
anyChainPattern = (defMessage :: W.AnyChainTxPattern)
& WF.cardano .~ txPattern
predicate = (defMessage :: W.TxPredicate)
& WF.match .~ anyChainPattern
watchRequest = defMessage
& WF.predicate .~ predicate
putStrLn $ " Watching for payment credential: " ++ T.unpack (T.decodeUtf8 $ Base16.encode paymentCredBytes)
-- Create MVar to store watcher result
resultVar <- newEmptyMVar
-- Start watching in background
_ <- forkIO $ do
result <- try $ watchTx (watchClient client) [] watchRequest streamHandler
putMVar resultVar result
-- Give the watcher time to start
putStrLn " Started watching, waiting 1 second..."
threadDelay 1000000
-- Submit transaction
putStrLn " Submitting transaction..."
let txCborHex = "84A300D9010282825820896E271D8CC28CB59E9E9D0464C06D3C2A48A0766438AD9FFBABC9597C4E072101825820CE5996492EA64AE09B9D06A062FD8C98D2231A325FF48071C811729E8B7D420E000182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D481F2FB021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F952585840A92445E02162D6F7212713A41124B8ED260DB632EC37A9F77BEB1EE813E108813F216A06CEBCB42708D1C87CD20F861F395B59871FE9A0CB9CC6A294C6FE4104F5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage & SubF.raw .~ txCborBytes
submitRequest = defMessage & SubF.tx .~ anyChainTx
submitResult <- submitTx (submitClient client) submitRequest
case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
when (not (BS.null txHash)) $ do
putStrLn $ " Transaction submitted! Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
_ -> putStrLn " Warning: Transaction submission may have issues"
putStrLn " Waiting for Watch event..."
-- Wait for watcher result
result <- takeMVar resultVar
-- Assert
case result of
Left (exc :: SomeException) ->
if "WatchTxForPaymentPart completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchTxForPaymentPart test passed! Found matching transaction"
else
throwString $ "WatchTxForPaymentPart failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeAction = firstEvent ^. WF.maybe'action
assertNotNull "firstEvent.Action" maybeAction
case maybeAction of
Just (WatchTxResponse'Apply _) ->
putStrLn $ "✓ WatchTxForPaymentPart test passed!"
_ -> throwString "Expected Apply action"
err -> throwString $ "WatchTxForPaymentPart failed: " ++ show err
where
streamHandler :: [WatchTxResponse] -> a -> WatchTxResponse -> IO [WatchTxResponse]
streamHandler events _headers response = do
let maybeAction = response ^. WF.maybe'action
case maybeAction of
Just (WatchTxResponse'Apply _) -> do
putStrLn $ " Found Apply action for transaction matching payment credential!"
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchTxForPaymentPart completed - stopping stream"
else
return newEvents
_ -> return events
testWatchTxForDelegationPart :: UtxorpcClient -> IO ()
testWatchTxForDelegationPart client = do
putStrLn "\n=== Test: WatchTxForDelegationPart ==="
-- Arrange - Extract delegation credential from address
let delegationCredHex = "1591d34d5b4b2728d04a80fdd041bb52edb334dacbf25aa27877e738"
delegationCredBytes = case Base16.decode delegationCredHex of
Right bs -> bs
Left err -> error $ "Invalid delegation cred hex: " ++ err
addressPattern = (defMessage :: AddressPattern)
& CF.delegationPart .~ delegationCredBytes
txPattern = (defMessage :: TxPattern)
& CF.hasAddress .~ addressPattern
anyChainPattern = (defMessage :: W.AnyChainTxPattern)
& WF.cardano .~ txPattern
predicate = (defMessage :: W.TxPredicate)
& WF.match .~ anyChainPattern
watchRequest = defMessage
& WF.predicate .~ predicate
putStrLn $ " Watching for delegation credential: " ++ T.unpack (T.decodeUtf8 $ Base16.encode delegationCredBytes)
-- Create MVar to store watcher result
resultVar <- newEmptyMVar
-- Start watching in background
_ <- forkIO $ do
result <- try $ watchTx (watchClient client) [] watchRequest streamHandler
putMVar resultVar result
-- Give the watcher time to start
putStrLn " Started watching, waiting 1 second..."
threadDelay 1000000
-- Submit transaction
putStrLn " Submitting transaction..."
let txCborHex = "84A300D90102828258201F0716BFC556282969C9C3CC41E8F598A4D668AAE0120554BBFBE1D6DA2AD1CC01825820488B24DAE71016C45A8BEE133185D0FA22DC18F19C24C464898810B1944D5A28000182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D4BC5D6E021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F95258584054224628C469C35313471E6C274C93218D0B40093307E9B70A0A246F697D0870CB81E9333FAB6FE7B297ECC0ADA9E9145F0B46AA3E2395A3DECF42970D1B510DF5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage & SubF.raw .~ txCborBytes
submitRequest = defMessage & SubF.tx .~ anyChainTx
submitResult <- submitTx (submitClient client) submitRequest
case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
when (not (BS.null txHash)) $ do
putStrLn $ " Transaction submitted! Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
_ -> putStrLn " Warning: Transaction submission may have issues"
putStrLn " Waiting for Watch event..."
-- Wait for watcher result
result <- takeMVar resultVar
-- Assert
case result of
Left (exc :: SomeException) ->
if "WatchTxForDelegationPart completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchTxForDelegationPart test passed! Found matching transaction"
else
throwString $ "WatchTxForDelegationPart failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeAction = firstEvent ^. WF.maybe'action
assertNotNull "firstEvent.Action" maybeAction
case maybeAction of
Just (WatchTxResponse'Apply _) ->
putStrLn $ "✓ WatchTxForDelegationPart test passed!"
_ -> throwString "Expected Apply action"
err -> throwString $ "WatchTxForDelegationPart failed: " ++ show err
where
streamHandler :: [WatchTxResponse] -> a -> WatchTxResponse -> IO [WatchTxResponse]
streamHandler events _headers response = do
let maybeAction = response ^. WF.maybe'action
case maybeAction of
Just (WatchTxResponse'Apply _) -> do
putStrLn $ " Found Apply action for transaction matching delegation credential!"
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchTxForDelegationPart completed - stopping stream"
else
return newEvents
_ -> return events
testWatchTxForPolicyId :: UtxorpcClient -> IO ()
testWatchTxForPolicyId client = do
putStrLn "\n=== Test: WatchTxForPolicyId ==="
-- Arrange
let policyIdHex = "8b05e87a51c1d4a0fa888d2bb14dbc25e8c343ea379a171b63aa84a0"
policyIdBytes = case Base16.decode policyIdHex of
Right bs -> bs
Left err -> error $ "Invalid policy ID hex: " ++ err
assetPattern = (defMessage :: AssetPattern)
& CF.policyId .~ policyIdBytes
txPattern = (defMessage :: TxPattern)
& CF.movesAsset .~ assetPattern
anyChainPattern = (defMessage :: W.AnyChainTxPattern)
& WF.cardano .~ txPattern
predicate = (defMessage :: W.TxPredicate)
& WF.match .~ anyChainPattern
watchRequest = defMessage
& WF.predicate .~ predicate
putStrLn $ " Watching for policy ID: " ++ T.unpack (T.decodeUtf8 $ Base16.encode policyIdBytes)
-- Create MVar to store watcher result
resultVar <- newEmptyMVar
-- Start watching in background
_ <- forkIO $ do
result <- try $ watchTx (watchClient client) [] watchRequest streamHandler
putMVar resultVar result
-- Give the watcher time to start
putStrLn " Started watching, waiting 1 second..."
threadDelay 1000000
-- Submit transaction
putStrLn " Submitting transaction..."
let txCborHex = "84A300D90102828258209E0CC477DF20C37C684E47B9701F995C8733B07901DE5447FE4F61EDF14F6BCB01825820176A54A6E3911FF5C65134928079E40783C8A88F1143091687F3FE7775EEF55F000182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D4478888021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F952585840DBBF46C2B615D9EE1689F6339A537A3A16507C2566E067A717F0D379D9944F8FEF1045AA525B71EB6E29877BFAEA83F295F25707B0F7AF41222E3F33C9861C0CF5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage & SubF.raw .~ txCborBytes
submitRequest = defMessage & SubF.tx .~ anyChainTx
submitResult <- submitTx (submitClient client) submitRequest
case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
when (not (BS.null txHash)) $ do
putStrLn $ " Transaction submitted! Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
_ -> putStrLn " Warning: Transaction submission may have issues"
putStrLn " Waiting for Watch event..."
-- Wait for watcher result
result <- takeMVar resultVar
-- Assert
case result of
Left (exc :: SomeException) ->
if "WatchTxForPolicyId completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchTxForPolicyId test passed! Found matching transaction"
else
throwString $ "WatchTxForPolicyId failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeAction = firstEvent ^. WF.maybe'action
assertNotNull "firstEvent.Action" maybeAction
case maybeAction of
Just (WatchTxResponse'Apply _) ->
putStrLn $ "✓ WatchTxForPolicyId test passed!"
_ -> throwString "Expected Apply action"
err -> throwString $ "WatchTxForPolicyId failed: " ++ show err
where
streamHandler :: [WatchTxResponse] -> a -> WatchTxResponse -> IO [WatchTxResponse]
streamHandler events _headers response = do
let maybeAction = response ^. WF.maybe'action
case maybeAction of
Just (WatchTxResponse'Apply _) -> do
putStrLn $ " Found Apply action for transaction with policy ID!"
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchTxForPolicyId completed - stopping stream"
else
return newEvents
_ -> return events
testWatchTxForAsset :: UtxorpcClient -> IO ()
testWatchTxForAsset client = do
putStrLn "\n=== Test: WatchTxForAsset ==="
-- Arrange
let policyIdHex = "8b05e87a51c1d4a0fa888d2bb14dbc25e8c343ea379a171b63aa84a0"
assetNameHex = "434e4354" -- "CNCT" in hex
policyIdBytes = case Base16.decode policyIdHex of
Right bs -> bs
Left err -> error $ "Invalid policy ID hex: " ++ err
assetNameOnlyBytes = case Base16.decode assetNameHex of
Right bs -> bs
Left err -> error $ "Invalid asset name hex: " ++ err
assetPattern = (defMessage :: AssetPattern)
& CF.policyId .~ policyIdBytes
& CF.assetName .~ assetNameOnlyBytes
txPattern = (defMessage :: TxPattern)
& CF.movesAsset .~ assetPattern
anyChainPattern = (defMessage :: W.AnyChainTxPattern)
& WF.cardano .~ txPattern
predicate = (defMessage :: W.TxPredicate)
& WF.match .~ anyChainPattern
watchRequest = defMessage
& WF.predicate .~ predicate
putStrLn $ " Watching for asset: " ++ T.unpack (T.decodeUtf8 assetNameHex) ++ " (CNCT)"
-- Create MVar to store watcher result
resultVar <- newEmptyMVar
-- Start watching in background
_ <- forkIO $ do
result <- try $ watchTx (watchClient client) [] watchRequest streamHandler
putMVar resultVar result
-- Give the watcher time to start
putStrLn " Started watching, waiting 1 second..."
threadDelay 1000000
-- Submit transaction
putStrLn " Submitting transaction..."
let txCborHex = "84A300D90102828258209E0CC477DF20C37C684E47B9701F995C8733B07901DE5447FE4F61EDF14F6BCB01825820176A54A6E3911FF5C65134928079E40783C8A88F1143091687F3FE7775EEF55F000182A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011A005B8D80A20058390053FBFFFAB7B001281917DE77F18A8087413BE03401DB4AA2A7DBF0AE1591D34D5B4B2728D04A80FDD041BB52EDB334DACBF25AA27877E738011B00000001D4478888021A00029E8DA100D9010281825820D504D361777F985265A6F0A727E601E70F837574652760456B41731FF4F952585840DBBF46C2B615D9EE1689F6339A537A3A16507C2566E067A717F0D379D9944F8FEF1045AA525B71EB6E29877BFAEA83F295F25707B0F7AF41222E3F33C9861C0CF5F6"
txCborBytes = case Base16.decode (T.encodeUtf8 $ T.pack txCborHex) of
Right bs -> bs
Left err -> error $ "Invalid CBOR hex: " ++ err
anyChainTx = defMessage & SubF.raw .~ txCborBytes
submitRequest = defMessage & SubF.tx .~ anyChainTx
submitResult <- submitTx (submitClient client) submitRequest
case submitResult of
Right (Right (Right (_headers, _trailers, Right response))) -> do
let txHash = response ^. SubF.ref
when (not (BS.null txHash)) $ do
putStrLn $ " Transaction submitted! Hash: " ++ T.unpack (T.take 16 $ T.decodeUtf8 $ Base16.encode txHash) ++ "..."
_ -> putStrLn " Warning: Transaction submission may have issues"
putStrLn " Waiting for Watch event..."
-- Wait for watcher result
result <- takeMVar resultVar
-- Assert
case result of
Left (exc :: SomeException) ->
if "WatchTxForAsset completed" `T.isInfixOf` T.pack (show exc) then
putStrLn "✓ WatchTxForAsset test passed! Found matching transaction"
else
throwString $ "WatchTxForAsset failed: " ++ show exc
Right streamResult -> case streamResult of
Right (Right (events, _headers, _trailers)) -> do
assertTrue "events.Count > 0" (length events > 0)
let firstEvent = head events
let maybeAction = firstEvent ^. WF.maybe'action
assertNotNull "firstEvent.Action" maybeAction
case maybeAction of
Just (WatchTxResponse'Apply _) ->
putStrLn $ "✓ WatchTxForAsset test passed!"
_ -> throwString "Expected Apply action"
err -> throwString $ "WatchTxForAsset failed: " ++ show err
where
streamHandler :: [WatchTxResponse] -> a -> WatchTxResponse -> IO [WatchTxResponse]
streamHandler events _headers response = do
let maybeAction = response ^. WF.maybe'action
case maybeAction of
Just (WatchTxResponse'Apply _) -> do
putStrLn $ " Found Apply action for transaction with specific asset!"
let newEvents = events ++ [response]
if length newEvents >= 1 then
throwString "WatchTxForAsset completed - stopping stream"
else
return newEvents
_ -> return events