diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -14,7 +14,7 @@
 import KatipLogger (katipLogger)
 import Network.GRPC.Client (CIHeaderList)
 import Network.HTTP2.Frame (ErrorCode)
-import Proto.Utxorpc.V1alpha.Sync.Sync_Fields
+import Proto.Utxorpc.V1alpha.Sync.Sync_Fields (hash, slot, startToken, maxItems, intersect)
 import Safe (readMay)
 import SimpleLogger (simpleLogger)
 import System.Environment (getArgs)
@@ -127,7 +127,7 @@
         & intersect
           .~ [ blockRef,
                defMessage
-                 & index .~ 41562539
+                 & slot .~ 41562539
                  & hash
                    .~ TE.encodeUtf8
                      "e4599d275375e54257e7fd922d2f486cf47dd90692d1a7e531804a4e90893346"
@@ -135,7 +135,7 @@
 
     blockRef =
       defMessage
-        & index .~ 41561535
+        & slot .~ 41561535
         & hash
           .~ TE.encodeUtf8
             "91ec40dfc09449d918ec7b5311d5ddba318e8a7c337eaf23c9916c6463c30fbe"
diff --git a/quick-start/Main.hs b/quick-start/Main.hs
--- a/quick-start/Main.hs
+++ b/quick-start/Main.hs
@@ -1,46 +1,1636 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-
-module Main (main) where
-
-import Control.Exception (bracket, throwIO)
-import Control.Lens.Operators ((&), (.~))
-import Control.Monad (void)
-import Data.ProtoLens (Message (..))
-import qualified Data.Text.Encoding as T
-import Proto.Utxorpc.V1alpha.Sync.Sync (FetchBlockRequest)
-import Proto.Utxorpc.V1alpha.Sync.Sync_Fields (hash, index, ref)
-import UnliftIO.Exception (throwString)
-import Utxorpc.Client (close, fetchBlock, simpleUtxorpcClient, syncClient)
-
-main :: IO ()
-main = do
-  -- Connect to a UTxO RPC service
-  let mkClient = simpleUtxorpcClient "127.0.0.1" 3000 True
-  -- Bracket making a request with closing the client connection
-  bracket mkClient closeClient $ \case
-    -- Panic if connection could not be established
-    Left clientErr -> throwIO clientErr
-    Right client -> do
-      -- Make a unary request
-      result <- fetchBlock (syncClient client) fetchBlockRequest
-      case result of
-        -- Handle success
-        Right (Right (Right (_headers, _trailers, fetchBlockResponse))) ->
-          print fetchBlockResponse
-        err -> throwString $ show err
-  where
-    fetchBlockRequest :: FetchBlockRequest
-    fetchBlockRequest =
-      defMessage
-        & ref
-          .~ [ defMessage
-                 & index .~ 40608434
-                 & hash
-                   .~ T.encodeUtf8
-                     "3e4947072df1ed22a0518cb717c2904ebf0952b0c33292b402fae25d9562022e"
-             ]
-
-    -- Close the client connection if it was established
-    closeClient (Left _) = return ()
-    closeClient (Right client) = void (close client)
+{-# 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
diff --git a/src/Utxorpc/Client.hs b/src/Utxorpc/Client.hs
--- a/src/Utxorpc/Client.hs
+++ b/src/Utxorpc/Client.hs
@@ -161,7 +161,8 @@
     (loggedUnary logger readParamsRPC client)
     (loggedUnary logger readUtxosRPC client)
     (loggedUnary logger searchUtxosRPC client)
-    (loggedSStream logger streamUtxosRPC client)
+    (loggedUnary logger readGenesisRPC client)
+    (loggedUnary logger readEraSummaryRPC client)
 
 readParamsRPC :: RPC QueryService "readParams"
 readParamsRPC = RPC
@@ -172,9 +173,12 @@
 searchUtxosRPC :: RPC QueryService "searchUtxos"
 searchUtxosRPC = RPC
 
-streamUtxosRPC :: RPC QueryService "streamUtxos"
-streamUtxosRPC = RPC
+readGenesisRPC :: RPC QueryService "readGenesis"
+readGenesisRPC = RPC
 
+readEraSummaryRPC :: RPC QueryService "readEraSummary"
+readEraSummaryRPC = RPC
+
 {--------------------------------------
   SUBMIT
 --------------------------------------}
@@ -209,15 +213,19 @@
     (loggedUnary logger fetchBlockRPC client)
     (loggedUnary logger dumpHistoryRPC client)
     (loggedSStream logger followTipRPC client)
+    (loggedUnary logger readTipRPC client)
 
-fetchBlockRPC :: RPC ChainSyncService "fetchBlock"
+fetchBlockRPC :: RPC SyncService "fetchBlock"
 fetchBlockRPC = RPC
 
-dumpHistoryRPC :: RPC ChainSyncService "dumpHistory"
+dumpHistoryRPC :: RPC SyncService "dumpHistory"
 dumpHistoryRPC = RPC
 
-followTipRPC :: RPC ChainSyncService "followTip"
+followTipRPC :: RPC SyncService "followTip"
 followTipRPC = RPC
+
+readTipRPC :: RPC SyncService "readTip"
+readTipRPC = RPC
 
 {--------------------------------------
   WATCH
diff --git a/src/Utxorpc/Types.hs b/src/Utxorpc/Types.hs
--- a/src/Utxorpc/Types.hs
+++ b/src/Utxorpc/Types.hs
@@ -76,7 +76,8 @@
   { readParams :: ReadParamsRequest -> UnaryReply ReadParamsResponse,
     readUtxos :: ReadUtxosRequest -> UnaryReply ReadUtxosResponse,
     searchUtxos :: SearchUtxosRequest -> UnaryReply SearchUtxosResponse,
-    streamUtxos :: ServerStreamCall ReadUtxosRequest ReadUtxosResponse
+    readGenesis :: ReadGenesisRequest -> UnaryReply ReadGenesisResponse,
+    readEraSummary :: ReadEraSummaryRequest -> UnaryReply ReadEraSummaryResponse
   }
 
 {---------------------------------------
@@ -99,7 +100,8 @@
 data SyncClient = SyncClient
   { fetchBlock :: FetchBlockRequest -> UnaryReply FetchBlockResponse,
     dumpHistory :: DumpHistoryRequest -> UnaryReply DumpHistoryResponse,
-    followTip :: ServerStreamCall FollowTipRequest FollowTipResponse
+    followTip :: ServerStreamCall FollowTipRequest FollowTipResponse,
+    readTip :: ReadTipRequest -> UnaryReply ReadTipResponse
   }
 
 {---------------------------------------
diff --git a/test/logged_test.hs b/test/logged_test.hs
--- a/test/logged_test.hs
+++ b/test/logged_test.hs
@@ -22,7 +22,7 @@
 
 type UnaryReply = FetchBlockResponse
 
-type UnaryRpc = RPC ChainSyncService "fetchBlock"
+type UnaryRpc = RPC SyncService "fetchBlock"
 
 unaryRpc :: UnaryRpc
 unaryRpc = RPC
@@ -37,7 +37,7 @@
 streamReplies :: Int -> [StreamReply]
 streamReplies n = replicate n defMessage
 
-type StreamRpc = RPC ChainSyncService "followTip"
+type StreamRpc = RPC SyncService "followTip"
 
 streamRpc :: StreamRpc
 streamRpc = RPC
diff --git a/utxorpc-client.cabal b/utxorpc-client.cabal
--- a/utxorpc-client.cabal
+++ b/utxorpc-client.cabal
@@ -1,7 +1,7 @@
 cabal-version: 3.0
 
 name:           utxorpc-client
-version:        0.0.2.0
+version:        0.0.4.1
 synopsis:       An SDK for clients of the UTxO RPC specification.
 description:    An SDK to reduce boilerplate, improve ease-of-use, and support logging for `utxorpc`.
                 To get started, see the documentation for `Utxorpc.Client` below.
@@ -52,11 +52,11 @@
     , http2-grpc-proto-lens < 0.2
     , http2-grpc-types < 0.6
     , proto-lens < 0.8
-    , utxorpc >= 0.0.5 && < 0.0.6
+    , utxorpc >= 0.0.18 && < 0.0.19
     , uuid < 1.4
   default-language: Haskell2010
 
-executable client-example
+executable example
   main-is:
     Main.hs
   other-modules:
@@ -92,12 +92,12 @@
     , time < 1.13
     , transformers < 0.7
     , unliftio < 0.3
-    , utxorpc >= 0.0.5 && < 0.0.6
+    , utxorpc >= 0.0.18 && < 0.0.19
     , utxorpc-client
     , uuid < 1.4
   default-language: Haskell2010
 
-executable client-quick-start
+executable quickstart
   main-is:
     Main.hs
   hs-source-dirs:
@@ -115,15 +115,17 @@
     -Wunused-packages
   build-depends:
     , base >=4.7 && <5
+    , base16-bytestring < 1.1
+    , bytestring < 0.13
     , lens < 5.3
     , proto-lens < 0.8
     , text < 2.2
     , unliftio < 0.3
-    , utxorpc >= 0.0.5 && < 0.0.6
+    , utxorpc >= 0.0.18 && < 0.0.19
     , utxorpc-client
   default-language: Haskell2010
 
-test-suite logged_test_client
+test-suite unit
   type:
     exitcode-stdio-1.0
   main-is:
@@ -154,6 +156,6 @@
     , http2-grpc-types >= 0.5.0.0 && < 0.6
     , hspec >= 2.11.7 && < 2.12
     , proto-lens < 0.8
-    , utxorpc >= 0.0.5 && < 0.0.6
+    , utxorpc >= 0.0.18 && < 0.0.19
     , uuid < 1.4
   default-language: Haskell2010
