diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
index 89b7d2fdd0..a0957211d6 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt
@@ -98,14 +98,13 @@ class NodeMonitorModel {
         stateMachineUpdates.startWith(currentStateMachines).subscribe(stateMachineUpdatesSubject)
 
         // Vault snapshot (force single page load with MAX_PAGE_SIZE) + updates
-        val (_, vaultUpdates) = proxy.vaultTrackBy<ContractState>(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL),
+        val (statesSnapshot, vaultUpdates) = proxy.vaultTrackBy<ContractState>(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL),
                 PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE))
-
-        val vaultSnapshot = proxy.vaultQueryBy<ContractState>(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED),
-                PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE))
-        // We have to fetch the snapshot separately since vault query API doesn't allow different criteria for snapshot and updates.
-        // TODO : This will create a small window of opportunity for inconsistent updates, might need to change the vault API to handle this case.
-        val initialVaultUpdate = Vault.Update(setOf(), vaultSnapshot.states.toSet())
+        val unconsumedStates = statesSnapshot.states.filterIndexed { index, _ ->
+            statesSnapshot.statesMetadata[index].status == Vault.StateStatus.UNCONSUMED
+        }.toSet()
+        val consumedStates = statesSnapshot.states.toSet() - unconsumedStates
+        val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates)
         vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)
 
         // Transactions
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt
index 9e4fb5cf03..6c5ff06851 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt
@@ -11,13 +11,14 @@
 package net.corda.client.jfx.model
 
 import javafx.beans.value.ObservableValue
+import javafx.collections.FXCollections
 import javafx.collections.ObservableMap
 import net.corda.client.jfx.utils.*
 import net.corda.core.contracts.ContractState
 import net.corda.core.contracts.StateAndRef
 import net.corda.core.contracts.StateRef
-import net.corda.core.crypto.SecureHash
 import net.corda.core.transactions.SignedTransaction
+import net.corda.core.transactions.WireTransaction
 import org.fxmisc.easybind.EasyBind
 
 /**
@@ -27,7 +28,8 @@ import org.fxmisc.easybind.EasyBind
  */
 data class PartiallyResolvedTransaction(
         val transaction: SignedTransaction,
-        val inputs: List<ObservableValue<InputResolution>>) {
+        val inputs: List<ObservableValue<InputResolution>>,
+        val outputs: List<ObservableValue<out OutputResolution>>) {
     val id = transaction.id
 
     sealed class InputResolution {
@@ -39,21 +41,49 @@ data class PartiallyResolvedTransaction(
         }
     }
 
+    sealed class OutputResolution {
+        abstract val stateRef: StateRef
+
+        data class Unresolved(override val stateRef: StateRef) : OutputResolution()
+        data class Resolved(val stateAndRef: StateAndRef<ContractState>) : OutputResolution() {
+            override val stateRef: StateRef get() = stateAndRef.ref
+        }
+    }
+
     companion object {
         fun fromSignedTransaction(
                 transaction: SignedTransaction,
-                transactions: ObservableMap<SecureHash, SignedTransaction>
+                stateMap: ObservableMap<StateRef, StateAndRef<ContractState>>
         ) = PartiallyResolvedTransaction(
                 transaction = transaction,
-                inputs = transaction.tx.inputs.map { stateRef ->
-                    EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
+                inputs = transaction.inputs.map { stateRef ->
+                    EasyBind.map(stateMap.getObservableValue(stateRef)) {
                         if (it == null) {
                             InputResolution.Unresolved(stateRef)
                         } else {
-                            InputResolution.Resolved(it.tx.outRef(stateRef.index))
+                            InputResolution.Resolved(it)
+                        }
+                    }
+                },
+                outputs = if (transaction.coreTransaction is WireTransaction) {
+                    transaction.tx.outRefsOfType<ContractState>().map {
+                        OutputResolution.Resolved(it).lift()
+                    }
+                } else {
+                    // Transaction will have the same number of outputs as inputs
+                    val outputCount = transaction.coreTransaction.inputs.size
+                    val stateRefs = (0 until outputCount).map { StateRef(transaction.id, it) }
+                    stateRefs.map { stateRef ->
+                        EasyBind.map(stateMap.getObservableValue(stateRef)) {
+                            if (it == null) {
+                                OutputResolution.Unresolved(stateRef)
+                            } else {
+                                OutputResolution.Resolved(it)
+                            }
                         }
                     }
                 }
+
         )
     }
 }
@@ -64,9 +94,13 @@ data class PartiallyResolvedTransaction(
 class TransactionDataModel {
     private val transactions by observable(NodeMonitorModel::transactions)
     private val collectedTransactions = transactions.recordInSequence()
-    private val transactionMap = transactions.recordAsAssociation(SignedTransaction::id)
+    private val vaultUpdates by observable(NodeMonitorModel::vaultUpdates)
+    private val stateMap = vaultUpdates.fold(FXCollections.observableHashMap<StateRef, StateAndRef<ContractState>>()) { map, update ->
+        val states = update.consumed + update.produced
+        states.forEach { map[it.ref] = it }
+    }
 
     val partiallyResolvedTransactions = collectedTransactions.map {
-        PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
+        PartiallyResolvedTransaction.fromSignedTransaction(it, stateMap)
     }
 }
diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
index c939f427b4..3e505a2f02 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt
@@ -96,13 +96,31 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
 
         /**
          * A SHA-256 hash value consisting of 32 0x00 bytes.
+         * This field provides more intuitive access from Java.
          */
-        val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() }))
+        @JvmField
+        val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 0.toByte() }))
+
+        /**
+         * A SHA-256 hash value consisting of 32 0x00 bytes.
+         * This function is provided for API stability.
+         */
+        @Suppress("Unused")
+        fun getZeroHash(): SHA256 = zeroHash
 
         /**
          * A SHA-256 hash value consisting of 32 0xFF bytes.
+         * This field provides more intuitive access from Java.
          */
-        val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
+        @JvmField
+        val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
+
+        /**
+         * A SHA-256 hash value consisting of 32 0xFF bytes.
+         * This function is provided for API stability.
+         */
+        @Suppress("Unused")
+        fun getAllOnesHash(): SHA256 = allOnesHash
     }
 
     // In future, maybe SHA3, truncated hashes etc.
diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt
index cb3cd88d3a..c6fffc0552 100644
--- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt
+++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt
@@ -40,6 +40,7 @@ import net.corda.client.jfx.utils.*
 import net.corda.core.contracts.ContractState
 import net.corda.core.identity.Party
 import net.corda.core.node.NodeInfo
+import net.corda.core.transactions.WireTransaction
 import net.corda.core.utilities.toBase58String
 import net.corda.explorer.formatters.PartyNameFormatter
 import net.corda.explorer.model.CordaView
@@ -91,7 +92,11 @@ class Network : CordaView() {
                     .map { it as? PartiallyResolvedTransaction.InputResolution.Resolved }
                     .filterNotNull()
                     .map { it.stateAndRef.state.data }.getParties()
-            val outputParties = it.transaction.tx.outputStates.observable().getParties()
+            val outputParties = it.transaction.coreTransaction.let {
+                if (it is WireTransaction) it.outputStates.observable().getParties()
+                // For ContractUpgradeWireTransaction and NotaryChangeWireTransaction the output parties are the same as input parties
+                else inputParties
+            }
             val signingParties = it.transaction.sigs.map { it.by.toKnownParty() }
             // Input parties fire a bullets to all output parties, then to the signing parties and then signing parties to output parties.
             // !! This is a rough guess of how the message moves in the network.
diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt
index 6723e0a799..cf47c68ea2 100644
--- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt
+++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt
@@ -37,6 +37,8 @@ import net.corda.core.crypto.toStringShort
 import net.corda.core.identity.AbstractParty
 import net.corda.core.identity.CordaX500Name
 import net.corda.core.identity.Party
+import net.corda.core.transactions.SignedTransaction
+import net.corda.core.transactions.WireTransaction
 import net.corda.core.utilities.toBase58String
 import net.corda.sample.businessnetwork.iou.IOUState
 import net.corda.explorer.AmountDiff
@@ -82,7 +84,7 @@ class TransactionViewer : CordaView("Transactions") {
             val tx: PartiallyResolvedTransaction,
             val id: SecureHash,
             val inputs: Inputs,
-            val outputs: ObservableList<StateAndRef<ContractState>>,
+            val outputs: Outputs,
             val inputParties: ObservableList<List<ObservableValue<Party?>>>,
             val outputParties: ObservableList<List<ObservableValue<Party?>>>,
             val commandTypes: List<Class<CommandData>>,
@@ -90,6 +92,7 @@ class TransactionViewer : CordaView("Transactions") {
     )
 
     data class Inputs(val resolved: ObservableList<StateAndRef<ContractState>>, val unresolved: ObservableList<StateRef>)
+    data class Outputs(val resolved: ObservableList<StateAndRef<ContractState>>, val unresolved: ObservableList<StateRef>)
 
     override fun onDock() {
         txIdToScroll?.let {
@@ -116,38 +119,42 @@ class TransactionViewer : CordaView("Transactions") {
      */
     init {
         val transactions = transactions.map {
-            val resolved = it.inputs.sequence()
+            val resolvedInputs = it.inputs.sequence()
                     .map { it as? PartiallyResolvedTransaction.InputResolution.Resolved }
                     .filterNotNull()
                     .map { it.stateAndRef }
-            val unresolved = it.inputs.sequence()
+            val unresolvedInputs = it.inputs.sequence()
                     .map { it as? PartiallyResolvedTransaction.InputResolution.Unresolved }
                     .filterNotNull()
                     .map { it.stateRef }
-            val outputs = it.transaction.tx.outputs
-                    .mapIndexed { index, transactionState ->
-                        val stateRef = StateRef(it.id, index)
-                        StateAndRef(transactionState, stateRef)
-                    }.observable()
+            val resolvedOutputs = it.outputs.sequence()
+                    .map { it as? PartiallyResolvedTransaction.OutputResolution.Resolved }
+                    .filterNotNull()
+                    .map { it.stateAndRef }
+            val unresolvedOutputs = it.inputs.sequence()
+                    .map { it as? PartiallyResolvedTransaction.InputResolution.Unresolved }
+                    .filterNotNull()
+                    .map { it.stateRef }
+            val commands = if (it.transaction.coreTransaction is WireTransaction) it.transaction.tx.commands else emptyList()
             Transaction(
                     tx = it,
                     id = it.id,
-                    inputs = Inputs(resolved, unresolved),
-                    outputs = outputs,
-                    inputParties = resolved.getParties(),
-                    outputParties = outputs.getParties(),
-                    commandTypes = it.transaction.tx.commands.map { it.value.javaClass },
+                    inputs = Inputs(resolvedInputs, unresolvedInputs),
+                    outputs = Outputs(resolvedOutputs, unresolvedOutputs),
+                    inputParties = resolvedInputs.getParties(),
+                    outputParties = resolvedOutputs.getParties(),
+                    commandTypes = commands.map { it.value.javaClass },
                     totalValueEquiv = ::calculateTotalEquiv.lift(myIdentity,
                             reportingExchange,
-                            resolved.map { it.state.data }.lift(),
-                            it.transaction.tx.outputStates.lift())
+                            resolvedInputs.map { it.state.data }.lift(),
+                            resolvedOutputs.map { it.state.data }.lift())
             )
         }
 
         val searchField = SearchField(transactions,
                 "Transaction ID" to { tx, s -> "${tx.id}".contains(s, true) },
                 "Input" to { tx, s -> tx.inputs.resolved.any { it.state.contract.contains(s, true) } },
-                "Output" to { tx, s -> tx.outputs.any { it.state.contract.contains(s, true) } },
+                "Output" to { tx, s -> tx.outputs.resolved.any { it.state.contract.contains(s, true) } },
                 "Input Party" to { tx, s -> tx.inputParties.any { it.any { it.value?.name?.organisation?.contains(s, true) == true } } },
                 "Output Party" to { tx, s -> tx.outputParties.any { it.any { it.value?.name?.organisation?.contains(s, true) == true } } },
                 "Command Type" to { tx, s -> tx.commandTypes.any { it.simpleName.contains(s, true) } }
@@ -174,7 +181,15 @@ class TransactionViewer : CordaView("Transactions") {
                     text += "Unresolved(${it.unresolved.size})"
                 }
             }
-            column("Output", Transaction::outputs).cellFormat { text = it.toText() }
+            column("Output", Transaction::outputs).cellFormat {
+                text = it.resolved.toText()
+                if (!it.unresolved.isEmpty()) {
+                    if (!text.isBlank()) {
+                        text += ", "
+                    }
+                    text += "Unresolved(${it.unresolved.size})"
+                }
+            }
             column("Input Party", Transaction::inputParties).setCustomCellFactory {
                 label {
                     text = it.formatJoinPartyNames(formatter = PartyNameFormatter.short)
@@ -251,14 +266,14 @@ class TransactionViewer : CordaView("Transactions") {
             val signatureData = transaction.tx.transaction.sigs.map { it.by }
             // Bind count to TitlePane
             inputPane.text = "Input (${transaction.inputs.resolved.count()})"
-            outputPane.text = "Output (${transaction.outputs.count()})"
+            outputPane.text = "Output (${transaction.outputs.resolved.count()})"
             signaturesPane.text = "Signatures (${signatureData.count()})"
 
             inputs.cellCache { getCell(it) }
             outputs.cellCache { getCell(it) }
 
             inputs.items = transaction.inputs.resolved
-            outputs.items = transaction.outputs.observable()
+            outputs.items = transaction.outputs.resolved
 
             signatures.children.addAll(signatureData.map { signature ->
                 val party = signature.toKnownParty()