diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java
index e6acd109b0..cad7f9d5a5 100644
--- a/core/src/main/java/bisq/core/api/CoreApi.java
+++ b/core/src/main/java/bisq/core/api/CoreApi.java
@@ -568,8 +568,8 @@ public class CoreApi {
         coreTradesService.confirmPaymentReceived(tradeId, resultHandler, errorMessageHandler);
     }
 
-    public void keepFunds(String tradeId) {
-        coreTradesService.keepFunds(tradeId);
+    public void closeTrade(String tradeId) {
+        coreTradesService.closeTrade(tradeId);
     }
 
     public void withdrawFunds(String tradeId, String address, String memo) {
diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java
index 3264b2683f..3365ed771c 100644
--- a/core/src/main/java/bisq/core/api/CoreTradesService.java
+++ b/core/src/main/java/bisq/core/api/CoreTradesService.java
@@ -155,7 +155,7 @@ class CoreTradesService {
         }
     }
 
-    void keepFunds(String tradeId) {
+    void closeTrade(String tradeId) {
         coreWalletsService.verifyWalletsAreAvailable();
         coreWalletsService.verifyEncryptedWalletIsUnlocked();
 
diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java
index 07cdf01e3e..a1cabc4395 100644
--- a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java
@@ -812,6 +812,13 @@ public class XmrWalletService {
         return getAddressEntryListAsImmutableList().stream().filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext()).collect(Collectors.toList());
     }
 
+    public List<XmrAddressEntry> getAddressEntriesForOpenOffer() {
+        return getAddressEntryListAsImmutableList().stream()
+                .filter(addressEntry -> XmrAddressEntry.Context.OFFER_FUNDING == addressEntry.getContext() ||
+                        XmrAddressEntry.Context.RESERVED_FOR_TRADE == addressEntry.getContext())
+                .collect(Collectors.toList());
+    }
+
     public List<XmrAddressEntry> getAddressEntriesForTrade() {
         return getAddressEntryListAsImmutableList().stream()
                 .filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() || XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext())
diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
index 0b39f4bc4c..df2060183a 100644
--- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
@@ -248,7 +248,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
 
     private void cleanUpAddressEntries() {
         Set<String> openOffersIdSet = openOffers.getList().stream().map(OpenOffer::getId).collect(Collectors.toSet());
-        btcWalletService.getAddressEntriesForOpenOffer().stream()
+        xmrWalletService.getAddressEntriesForOpenOffer().stream()
                 .filter(e -> !openOffersIdSet.contains(e.getOfferId()))
                 .forEach(e -> {
                     log.warn("We found an outdated addressEntry for openOffer {} (openOffers does not contain that " +
@@ -568,8 +568,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
         openOffer.setState(OpenOffer.State.CANCELED);
         openOffers.remove(openOffer);
         closedTradableManager.add(openOffer);
-        log.info("onRemoved offerId={}", offer.getId());
         xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
+        log.info("onRemoved offerId={}", offer.getId());
         requestPersistence();
     }
 
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
index 139d3f128b..3c00c503b4 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
@@ -407,11 +407,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
           Contract contract = dispute.getContract();
 
           // verify sender is co-signer and receiver is arbitrator
-          System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
-          System.out.println(disputeResult);
-          System.out.println(disputeResult.getWinner());
-          System.out.println(contract.getBuyerNodeAddress());
-          System.out.println(contract.getSellerNodeAddress());
+//          System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
+//          System.out.println(disputeResult);
+//          System.out.println(disputeResult.getWinner());
+//          System.out.println(contract.getBuyerNodeAddress());
+//          System.out.println(contract.getSellerNodeAddress());
           boolean senderIsWinner = (disputeResult.getWinner() == Winner.BUYER && contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress())) || (disputeResult.getWinner() == Winner.SELLER && contract.getSellerNodeAddress().equals(request.getSenderNodeAddress()));
           boolean senderIsCosigner = senderIsWinner || disputeResult.isLoserPublisher();
           boolean receiverIsArbitrator = pubKeyRing.equals(dispute.getAgentPubKeyRing());
diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java
index a9c3bcdc33..8fb9defeb2 100644
--- a/core/src/main/java/bisq/core/trade/Trade.java
+++ b/core/src/main/java/bisq/core/trade/Trade.java
@@ -50,6 +50,7 @@ import bisq.common.util.Utilities;
 import com.google.common.base.Preconditions;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.Message;
+import common.utils.GenUtils;
 import org.bitcoinj.core.Coin;
 import org.bitcoinj.core.Transaction;
 
@@ -90,9 +91,12 @@ import monero.common.MoneroError;
 import monero.daemon.MoneroDaemon;
 import monero.daemon.model.MoneroTx;
 import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroCheckTx;
 import monero.wallet.model.MoneroDestination;
 import monero.wallet.model.MoneroMultisigSignResult;
+import monero.wallet.model.MoneroTransferQuery;
 import monero.wallet.model.MoneroTxConfig;
+import monero.wallet.model.MoneroTxQuery;
 import monero.wallet.model.MoneroTxSet;
 import monero.wallet.model.MoneroTxWallet;
 import monero.wallet.model.MoneroWalletListener;
@@ -127,7 +131,7 @@ public abstract class Trade implements Tradable, Model {
 
         // deposit published
         ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED),
-        DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN(Phase.DEPOSITS_PUBLISHED),
+        DEPOSIT_TXS_SEEN_IN_NETWORK(Phase.DEPOSITS_PUBLISHED),
 
         // deposit confirmed
         DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN(Phase.DEPOSITS_CONFIRMED),
@@ -157,8 +161,8 @@ public abstract class Trade implements Tradable, Model {
         SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
         SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
         BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
-        BUYER_SAW_PAYOUT_TX_IN_NETWORK(Phase.PAYOUT_PUBLISHED),
         BUYER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED),
+        PAYOUT_TX_SEEN_IN_NETWORK(Phase.PAYOUT_PUBLISHED),
 
         // trade completed
         WITHDRAW_COMPLETED(Phase.WITHDRAWN);
@@ -310,9 +314,6 @@ public abstract class Trade implements Tradable, Model {
     @Nullable
     @Getter
     @Setter
-    private String payoutTxId;
-    @Getter
-    @Setter
     private long amountAsLong;
     @Setter
     private long price;
@@ -366,9 +367,6 @@ public abstract class Trade implements Tradable, Model {
     // Added in v1.2.0
     @Nullable
     transient private Transaction delayedPayoutTx;
-
-    @Nullable
-    transient private MoneroTxWallet payoutTx;
     @Nullable
     transient private Coin tradeAmount;
 
@@ -412,12 +410,24 @@ public abstract class Trade implements Tradable, Model {
     @Getter
     transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty();
 
-
     // Added in XMR integration
     private transient List<TradeListener> tradeListeners; // notified on fully validated trade messages
     transient MoneroWalletListener depositTxListener;
+    transient MoneroWalletListener payoutTxListener;
     transient Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked
     transient Boolean takerDepositLocked;
+    @Nullable
+    transient private MoneroTxWallet payoutTx;
+    @Getter
+    @Setter
+    private String payoutTxId;
+    @Nullable
+    @Getter
+    @Setter
+    private String payoutTxHex;
+    @Getter
+    @Setter
+    private String payoutTxKey;
     private Long startTime; // cache
 
     ///////////////////////////////////////////////////////////////////////////////////////////
@@ -547,6 +557,8 @@ public abstract class Trade implements Tradable, Model {
         Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
         Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
         Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
+        Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
+        Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxHex(payoutTxKey));
         Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
         Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
         Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name()));
@@ -560,6 +572,8 @@ public abstract class Trade implements Tradable, Model {
         trade.setPeriodState(TradePeriodState.fromProto(proto.getPeriodState()));
         trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
         trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
+        trade.setPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()));
+        trade.setPayoutTxKey(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxKey()));
         trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
         trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
         trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()));
@@ -705,7 +719,7 @@ public abstract class Trade implements Tradable, Model {
         BigInteger buyerDepositAmount = multisigWallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
         BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount());
 
-        // parse payout tx
+        // describe payout tx
         MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
         if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack
         MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
@@ -747,8 +761,8 @@ public abstract class Trade implements Tradable, Model {
         }
 
         // update trade state
-        getSelf().setPayoutTxHex(payoutTxHex);
         setPayoutTx(describedTxSet.getTxs().get(0));
+        setPayoutTxHex(payoutTxHex);
 
         // submit payout tx
         if (publish) {
@@ -807,17 +821,17 @@ public abstract class Trade implements Tradable, Model {
 
         // handle deposit txs seen
         if (txs.size() == 2) {
-            setStatePublished();
+            setStateDepositsPublished();
             boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
             getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
             getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
 
             // check if deposit txs unlocked
             if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
-                setStateConfirmed();
+                setStateDepositsConfirmed();
                 long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
                 if (havenoWallet.getHeight() >= unlockHeight) {
-                    setStateUnlocked();
+                    setStateDepositsUnlocked();
                     return;
                 }
             }
@@ -844,7 +858,7 @@ public abstract class Trade implements Tradable, Model {
 
                 // skip if deposit txs not seen
                 if (txs.size() != 2) return;
-                setStatePublished();
+                setStateDepositsPublished();
 
                 // update deposit txs
                 boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
@@ -854,7 +868,7 @@ public abstract class Trade implements Tradable, Model {
                 // check if deposit txs confirmed and compute unlock height
                 if (txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed() && unlockHeight == null) {
                     log.info("Multisig deposits confirmed for trade {}", getId());
-                    setStateConfirmed();
+                    setStateDepositsConfirmed();
                     unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
                 }
 
@@ -863,7 +877,7 @@ public abstract class Trade implements Tradable, Model {
                     log.info("Multisig deposits unlocked for trade {}", getId());
                     xmrWalletService.removeWalletListener(depositTxListener); // remove listener when notified
                     depositTxListener = null; // prevent re-applying trade state in subsequent requests
-                    setStateUnlocked();
+                    setStateDepositsUnlocked();
                 }
             }
         };
@@ -872,6 +886,51 @@ public abstract class Trade implements Tradable, Model {
         xmrWalletService.addWalletListener(depositTxListener);
     }
 
+    public void listenForPayoutTx() {
+        log.info("Listening for payout tx for trade {}", getId());
+
+        // check if payout tx already seen
+        if (getState().ordinal() >= Trade.State.PAYOUT_TX_SEEN_IN_NETWORK.ordinal()) {
+          log.warn("We had a payout tx already set. tradeId={}, state={}", getId(), getState());
+          return;
+        }
+
+        // get payout address entry
+        Optional<XmrAddressEntry> optionalPayoutEntry = xmrWalletService.getAddressEntry(getId(), XmrAddressEntry.Context.TRADE_PAYOUT);
+        if (!optionalPayoutEntry.isPresent()) throw new RuntimeException("Trade does not have address entry for payout");
+        XmrAddressEntry payoutEntry = optionalPayoutEntry.get();
+
+        // watch for payout tx on loop
+        new Thread(() -> { // TODO: use thread manager
+            boolean found = false;
+            while (!found) {
+                if (getPayoutTxKey() != null) {
+
+                    // get txs to payout address
+                    List<MoneroTxWallet> txs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery()
+                            .setTransferQuery(new MoneroTransferQuery()
+                                    .setAccountIndex(0)
+                                    .setSubaddressIndex(payoutEntry.getSubaddressIndex())
+                                    .setIsIncoming(true)));
+
+                    // check for payout tx
+                    for (MoneroTxWallet tx : txs) {
+                        MoneroCheckTx txCheck = xmrWalletService.getWallet().checkTxKey(tx.getHash(), getPayoutTxKey(), payoutEntry.getAddressString());
+                        if (txCheck.isGood() && txCheck.receivedAmount.compareTo(new BigInteger("0")) > 0) {
+                            found = true;
+                            setPayoutTx(tx);
+                            setStateIfValidTransitionTo(Trade.State.PAYOUT_TX_SEEN_IN_NETWORK);
+                            return;
+                        }
+                    }
+                }
+
+                // wait to loop
+                GenUtils.waitFor(xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs());
+            }
+        }).start();
+    }
+
     @Nullable
     public MoneroTx getTakerDepositTx() {
         String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
@@ -1045,6 +1104,7 @@ public abstract class Trade implements Tradable, Model {
     public void setPayoutTx(MoneroTxWallet payoutTx) {
         this.payoutTx = payoutTx;
         payoutTxId = payoutTx.getHash();
+        payoutTxKey = payoutTx.getKey();
     }
 
     public void setErrorMessage(String errorMessage) {
@@ -1275,7 +1335,6 @@ public abstract class Trade implements Tradable, Model {
     }
 
     public boolean isPayoutPublished() {
-        if (getState() == Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG) return true; // TODO: this is a hack because seller has not seen signed payout tx. replace when payout process refactored
         return getState().getPhase().ordinal() >= Phase.PAYOUT_PUBLISHED.ordinal() || isWithdrawn();
     }
 
@@ -1397,15 +1456,15 @@ public abstract class Trade implements Tradable, Model {
         return tradeVolumeProperty;
     }
 
-    private void setStatePublished() {
-        if (!isDepositPublished()) setState(State.DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN);
+    private void setStateDepositsPublished() {
+        if (!isDepositPublished()) setState(State.DEPOSIT_TXS_SEEN_IN_NETWORK);
     }
 
-    private void setStateConfirmed() {
+    private void setStateDepositsConfirmed() {
         if (!isDepositConfirmed()) setState(State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN);
     }
 
-    private void setStateUnlocked() {
+    private void setStateDepositsUnlocked() {
         if (!isDepositUnlocked()) setState(State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
     }
 
diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java
index 9da4dcdf80..793613f811 100644
--- a/core/src/main/java/bisq/core/trade/TradeManager.java
+++ b/core/src/main/java/bisq/core/trade/TradeManager.java
@@ -284,7 +284,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
         xmrWalletService.getAddressEntriesForAvailableBalanceStream()
                 .filter(addressEntry -> addressEntry.getOfferId() != null)
                 .forEach(addressEntry -> {
-                    log.warn("Swapping pending OFFER_FUNDING entries at startup. offerId={}", addressEntry.getOfferId());
+                    log.warn("Swapping pending {} entries at startup. offerId={}", addressEntry.getContext(), addressEntry.getOfferId());
                     xmrWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), XmrAddressEntry.Context.OFFER_FUNDING);
                 });
 
@@ -837,6 +837,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
 
     // If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
     public void onTradeCompleted(Trade trade) {
+        if (trade.getState() == Trade.State.WITHDRAW_COMPLETED) return;
         closedTradableManager.add(trade);
         trade.setState(Trade.State.WITHDRAW_COMPLETED);
         maybeRemoveTrade(trade);
diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java
index e11337ed06..970fdce233 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java
@@ -17,6 +17,9 @@
 
 package bisq.core.trade.protocol;
 
+import bisq.common.UserThread;
+import bisq.common.handlers.ErrorMessageHandler;
+import bisq.common.handlers.ResultHandler;
 import bisq.core.trade.BuyerTrade;
 import bisq.core.trade.Trade;
 import bisq.core.trade.messages.PaymentAccountKeyResponse;
@@ -25,20 +28,16 @@ import bisq.core.trade.messages.SignContractResponse;
 import bisq.core.trade.messages.TradeMessage;
 import bisq.core.trade.protocol.FluentProtocol.Condition;
 import bisq.core.trade.protocol.tasks.ApplyFilter;
-import bisq.core.trade.protocol.tasks.BuyerSendPayoutTxPublishedMessage;
 import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
+import bisq.core.trade.protocol.tasks.BuyerProcessPaymentAccountKeyResponse;
 import bisq.core.trade.protocol.tasks.BuyerProcessPaymentReceivedMessage;
 import bisq.core.trade.protocol.tasks.BuyerSendPaymentAccountKeyRequestToArbitrator;
 import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
-import bisq.core.trade.protocol.tasks.BuyerSetupPayoutTxListener;
-import bisq.core.trade.protocol.tasks.BuyerProcessPaymentAccountKeyResponse;
+import bisq.core.trade.protocol.tasks.BuyerSendPayoutTxPublishedMessage;
 import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
+import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
 import bisq.core.util.Validator;
 import bisq.network.p2p.NodeAddress;
-import bisq.common.UserThread;
-import bisq.common.handlers.ErrorMessageHandler;
-import bisq.common.handlers.ResultHandler;
-
 import lombok.extern.slf4j.Slf4j;
 import org.fxmisc.easybind.EasyBind;
 
@@ -70,17 +69,20 @@ public abstract class BuyerProtocol extends DisputeProtocol {
         // request key to decrypt seller's payment account payload after first confirmation
         sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent.STARTUP, false);
 
+        // listen for deposit txs
         given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED)
                 .with(BuyerEvent.STARTUP))
                 .setup(tasks(SetupDepositTxsListener.class))
                 .executeTasks();
 
+        // listen for payout tx
         given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
                 .with(BuyerEvent.STARTUP))
-                .setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup?
+                .setup(tasks(SetupPayoutTxListener.class))
                 .executeTasks();
 
-        given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
+        // send payment sent message
+        given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) // TODO: remove payment received phase?
                 .anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG, Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG)
                 .with(BuyerEvent.STARTUP))
                 .setup(tasks(BuyerSendPaymentSentMessage.class))
diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java
index 163e95c885..5fe790d151 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java
@@ -22,6 +22,7 @@ import bisq.core.trade.Trade;
 import bisq.core.trade.messages.PaymentSentMessage;
 import bisq.core.trade.messages.SignContractResponse;
 import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent;
 import bisq.core.trade.protocol.FluentProtocol.Condition;
 import bisq.core.trade.protocol.tasks.ApplyFilter;
 import bisq.core.trade.protocol.tasks.SellerMaybeSendPayoutTxPublishedMessage;
@@ -30,6 +31,7 @@ import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
 import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessage;
 import bisq.core.trade.protocol.tasks.SellerSendPaymentAccountPayloadKey;
 import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
+import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
 import bisq.network.p2p.NodeAddress;
 import bisq.common.handlers.ErrorMessageHandler;
 import bisq.common.handlers.ResultHandler;
@@ -60,11 +62,17 @@ public abstract class SellerProtocol extends DisputeProtocol {
             sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent.STARTUP);
         }
 
-        // listen for changes to deposit txs
+        // listen for deposit txs
         given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED)
                 .with(SellerEvent.STARTUP))
                 .setup(tasks(SetupDepositTxsListener.class))
                 .executeTasks();
+
+        // listen for payout tx
+        given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
+                .with(BuyerEvent.STARTUP))
+                .setup(tasks(SetupPayoutTxListener.class))
+                .executeTasks();
     }
 
     @Override
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
index e05d7448f0..f4d358385e 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
@@ -335,7 +335,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
                 latchTrade();
                 Validator.checkTradeId(processModel.getOfferId(), response);
                 processModel.setTradeMessage(response);
-                expect(anyState(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS, Trade.State.DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN)
+                expect(anyState(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS, Trade.State.DEPOSIT_TXS_SEEN_IN_NETWORK)
                         .with(response)
                         .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
                         .setup(tasks(
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
index e7c6fc6577..2261be44c2 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
@@ -124,10 +124,6 @@ public final class TradingPeer implements PersistablePayload {
     @Nullable
     private String depositTxKey;
     @Nullable
-    transient private MoneroTxWallet payoutTx;
-    @Nullable
-    private String payoutTxHex;
-    @Nullable
     private String updatedMultisigHex;
     
     public TradingPeer() {
@@ -164,7 +160,6 @@ public final class TradingPeer implements PersistablePayload {
         Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
         Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
         Optional.ofNullable(exchangedMultisigHex).ifPresent(e -> builder.setExchangedMultisigHex(exchangedMultisigHex));
-        Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
         Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
         Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
         Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
@@ -216,7 +211,6 @@ public final class TradingPeer implements PersistablePayload {
             tradingPeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
             tradingPeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
             tradingPeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
-            tradingPeer.setPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()));
             tradingPeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
             return tradingPeer;
         }
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparePaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparePaymentSentMessage.java
index dd4139923c..ca7b436dba 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparePaymentSentMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparePaymentSentMessage.java
@@ -65,11 +65,16 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
 
             // create payout tx if we have seller's updated multisig hex
             if (trade.getTradingPeer().getUpdatedMultisigHex() != null) {
+
+                // create payout tx
                 log.info("Buyer creating unsigned payout tx");
                 multisigWallet.importMultisigHex(trade.getTradingPeer().getUpdatedMultisigHex());
                 MoneroTxWallet payoutTx = trade.createPayoutTx();
-                trade.getBuyer().setPayoutTx(payoutTx);
-                trade.getBuyer().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
+                trade.setPayoutTx(payoutTx);
+                trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
+
+                // start listening for published payout tx
+                trade.listenForPayoutTx();
             } else {
                 if (trade.getSelf().getUpdatedMultisigHex() == null) trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); // only export multisig hex once
             }
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessPaymentReceivedMessage.java
index 3c7183f8b0..ff64d71717 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessPaymentReceivedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessPaymentReceivedMessage.java
@@ -59,7 +59,7 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
             if (trade.getPhase().ordinal() < Trade.Phase.PAYOUT_PUBLISHED.ordinal()) {
 
                 // publish payout tx if signed. otherwise verify, sign, and publish payout tx
-                boolean previouslySigned = trade.getBuyer().getPayoutTxHex() != null;
+                boolean previouslySigned = trade.getPayoutTxHex() != null;
                 if (previouslySigned) {
                     log.info("Buyer publishing signed payout tx from seller");
                     XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
@@ -67,14 +67,17 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
                     List<String> txHashes = multisigWallet.submitMultisigTxHex(message.getPayoutTxHex());
                     trade.setPayoutTx(multisigWallet.getTx(txHashes.get(0)));
                     XmrWalletService.printTxs("payoutTx received from peer", trade.getPayoutTx());
-                    trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
+                    trade.setStateIfValidTransitionTo(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
                     walletService.closeMultisigWallet(trade.getId());
                 } else {
                     log.info("Buyer verifying, signing, and publishing seller's payout tx");
                     trade.verifyPayoutTx(message.getPayoutTxHex(), true, true);
-                    trade.setState(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
-                    // TODO (woodser): send PayoutTxPublishedMessage to arbitrator and seller
+                    trade.setStateIfValidTransitionTo(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
+                    // TODO (woodser): send PayoutTxPublishedMessage to seller
                 }
+
+                // mark address entries as available
+                processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
             } else {
                 log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
             }
@@ -89,7 +92,6 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
             }
 
             processModel.getTradeManager().requestPersistence();
-
             complete();
         } catch (Throwable t) {
             failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java
index 334d6c3d5a..a8c3ec1664 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java
@@ -62,7 +62,7 @@ public class BuyerSendPaymentSentMessage extends SendMailboxMessageTask {
                     trade.getCounterCurrencyTxId(),
                     trade.getCounterCurrencyExtraData(),
                     deterministicId,
-                    trade.getBuyer().getPayoutTxHex(),
+                    trade.getPayoutTxHex(),
                     trade.getBuyer().getUpdatedMultisigHex(),
                     trade.getSelf().getPaymentAccountKey()
             );
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPayoutTxPublishedMessage.java
index 843124d947..bef3626b90 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPayoutTxPublishedMessage.java
@@ -50,13 +50,13 @@ public class BuyerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
 
     @Override
     protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
-        checkNotNull(trade.getSelf().getPayoutTxHex(), "Payout tx must not be null");
+        checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
         return new PayoutTxPublishedMessage(
                 tradeId,
                 processModel.getMyNodeAddress(),
                 trade.isMaker(),
                 null, // TODO: send witness data?
-                trade.getSelf().getPayoutTxHex()
+                trade.getPayoutTxHex()
         );
     }
 
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSetupPayoutTxListener.java
deleted file mode 100644
index a122455551..0000000000
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSetupPayoutTxListener.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * This file is part of Haveno.
- *
- * Haveno is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * Haveno is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Haveno. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package bisq.core.trade.protocol.tasks;
-
-import bisq.core.trade.Trade;
-import bisq.common.taskrunner.TaskRunner;
-
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class BuyerSetupPayoutTxListener extends SetupPayoutTxListener {
-    public BuyerSetupPayoutTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
-        super(taskHandler, trade);
-    }
-
-    @Override
-    protected void run() {
-        try {
-            runInterceptHook();
-
-            super.run();
-
-        } catch (Throwable t) {
-            failed(t);
-        }
-    }
-
-    @Override
-    protected void setState() {
-        trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_PAYOUT_TX_IN_NETWORK);
-
-        processModel.getTradeManager().requestPersistence();
-    }
-}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendPayoutTxPublishedMessage.java
index 905b6b5492..215144df70 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendPayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendPayoutTxPublishedMessage.java
@@ -65,13 +65,13 @@ public class SellerMaybeSendPayoutTxPublishedMessage extends SendMailboxMessageT
 
     @Override
     protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
-        checkNotNull(trade.getSelf().getPayoutTxHex(), "Payout tx must not be null");
+        checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
         return new PayoutTxPublishedMessage(
                 tradeId,
                 processModel.getMyNodeAddress(),
                 trade.isMaker(),
                 null, // TODO: send witness data?
-                trade.getSelf().getPayoutTxHex()
+                trade.getPayoutTxHex()
         );
     }
 
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java
index 6816845f12..7fc18cdfaa 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java
@@ -38,17 +38,21 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
             runInterceptHook();
 
             // verify, sign, and publish payout tx if given. otherwise create payout tx
-            if (trade.getBuyer().getPayoutTxHex() != null) {
+            if (trade.getPayoutTxHex() != null) {
                 log.info("Seller verifying, signing, and publishing payout tx");
-                trade.verifyPayoutTx(trade.getBuyer().getPayoutTxHex(), true, true);
+                trade.verifyPayoutTx(trade.getPayoutTxHex(), true, true);
             } else {
+
+                // create unsigned payout tx
                 log.info("Seller creating unsigned payout tx");
                 MoneroTxWallet payoutTx = trade.createPayoutTx();
                 System.out.println("created payout tx: " + payoutTx);
-                trade.getSeller().setPayoutTx(payoutTx);
-                trade.getSeller().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
-            }
+                trade.setPayoutTx(payoutTx);
+                trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
 
+                // start listening for published payout tx
+                trade.listenForPayoutTx();
+            }
             complete();
         } catch (Throwable t) {
             failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessPaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessPaymentSentMessage.java
index e576fcf659..2db1e918b6 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessPaymentSentMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessPaymentSentMessage.java
@@ -43,7 +43,7 @@ public class SellerProcessPaymentSentMessage extends TradeTask {
             checkNotNull(message);
 
             // store buyer info
-            trade.getBuyer().setPayoutTxHex(message.getPayoutTxHex());
+            trade.setPayoutTxHex(message.getPayoutTxHex());
             trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
 
             // decrypt buyer's payment account payload
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java
index 8c610ad987..08f5d86dec 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java
@@ -42,7 +42,7 @@ public class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
         try {
             runInterceptHook();
 
-            if (trade.getSeller().getPayoutTxHex() == null) {
+            if (trade.getPayoutTxHex() == null) {
                 log.error("Payout tx is null");
                 failed("Payout tx is null");
                 return;
@@ -56,12 +56,12 @@ public class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
 
     @Override
     protected TradeMailboxMessage getTradeMailboxMessage(String id) {
-        checkNotNull(trade.getSeller().getPayoutTxHex(), "Payout tx must not be null");
+        checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
         return new PaymentReceivedMessage(
                 id,
                 processModel.getMyNodeAddress(),
                 signedWitness,
-                trade.getSeller().getPayoutTxHex()
+                trade.getPayoutTxHex()
         );
     }
 
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
index 4fb078633d..24056e560c 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
@@ -17,120 +17,50 @@
 
 package bisq.core.trade.protocol.tasks;
 
-import bisq.core.btc.listeners.AddressConfidenceListener;
-import bisq.core.btc.wallet.XmrWalletService;
-import bisq.core.trade.Trade;
-
 import bisq.common.UserThread;
 import bisq.common.taskrunner.TaskRunner;
-
-import org.bitcoinj.core.TransactionConfidence;
-
+import bisq.core.trade.Trade;
+import lombok.extern.slf4j.Slf4j;
+import org.fxmisc.easybind.EasyBind;
 import org.fxmisc.easybind.Subscription;
 
-import java.util.List;
-
-import lombok.extern.slf4j.Slf4j;
-
-
-
-import monero.wallet.model.MoneroTransferQuery;
-import monero.wallet.model.MoneroTxQuery;
-import monero.wallet.model.MoneroTxWallet;
-
 @Slf4j
-public abstract class SetupPayoutTxListener extends TradeTask {
-    // Use instance fields to not get eaten up by the GC
-    private Subscription tradeStateSubscription;
-    private AddressConfidenceListener confidenceListener;
+public class SetupPayoutTxListener extends TradeTask {
 
-    public SetupPayoutTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
+    private Subscription tradeStateSubscription;
+
+    @SuppressWarnings({ "unused" })
+    public SetupPayoutTxListener(TaskRunner taskHandler, Trade trade) {
         super(taskHandler, trade);
     }
 
-
-    protected abstract void setState();
-
     @Override
     protected void run() {
         try {
             runInterceptHook();
-            System.out.println("NEED TO IMPLEMENT PAYOUT TX LISTENER!"); // TODO (woodser): implement SetupPayoutTxListener
-//            if (!trade.isPayoutPublished()) {
-//                BtcWalletService walletService = processModel.getBtcWalletService();
-//                String id = processModel.getOffer().getId();
-//                Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
-//
-//                TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
-//                if (isInNetwork(confidence)) {
-//                    applyConfidence(confidence);
-//                } else {
-//                    confidenceListener = new AddressConfidenceListener(address) {
-//                        @Override
-//                        public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
-//                            if (isInNetwork(confidence))
-//                                applyConfidence(confidence);
-//                        }
-//                    };
-//                    walletService.addAddressConfidenceListener(confidenceListener);
-//
-//                    tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
-//                        if (trade.isPayoutPublished()) {
-//                            processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
-//
-//                            // hack to remove tradeStateSubscription at callback
-//                            UserThread.execute(this::unSubscribe);
-//                        }
-//                    });
-//                }
-//            }
 
-            // we complete immediately, our object stays alive because the balanceListener is stored in the WalletService
+            // skip if payout already published
+            if (!trade.isPayoutPublished()) {
+
+                // listen for payout tx
+                trade.listenForPayoutTx();
+                tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
+                    if (trade.isPayoutPublished()) {
+
+                        // cleanup on trade completion
+                        processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
+                        UserThread.execute(this::unSubscribe); // unsubscribe
+                    }
+                });
+            }
+
             complete();
         } catch (Throwable t) {
             failed(t);
         }
     }
 
-    private void applyPayoutTx(int accountIdx) {
-        if (trade.getPayoutTx() == null) {
-
-            // get txs with transfers to payout subaddress
-            List<MoneroTxWallet> txs = processModel.getProvider().getXmrWalletService().getWallet().getTxs(new MoneroTxQuery()
-                    .setTransferQuery(new MoneroTransferQuery().setAccountIndex(accountIdx).setSubaddressIndex(0).setIsIncoming(true)));  // TODO (woodser): hardcode account 0 as savings wallet, subaddress 0 trade accounts in config
-
-            // resolve payout tx if multiple txs sent to payout address
-            MoneroTxWallet payoutTx;
-            if (txs.size() > 1) {
-                throw new RuntimeException("Need to resolve multiple payout txs");  // TODO (woodser)
-            } else {
-                payoutTx = txs.get(0);
-            }
-
-            trade.setPayoutTx(payoutTx);
-            XmrWalletService.printTxs("payoutTx received from network", payoutTx);
-            setState();
-        } else {
-            log.info("We had the payout tx already set. tradeId={}, state={}", trade.getId(), trade.getState());
-        }
-
-        processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
-
-        // need delay as it can be called inside the handler before the listener and tradeStateSubscription are actually set.
-        UserThread.execute(this::unSubscribe);
-    }
-
-    private boolean isInNetwork(TransactionConfidence confidence) {
-        return confidence != null &&
-                (confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) ||
-                        confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING));
-    }
-
     private void unSubscribe() {
-        if (tradeStateSubscription != null)
-            tradeStateSubscription.unsubscribe();
-
-        if (confidenceListener != null)
-            processModel.getBtcWalletService().removeAddressConfidenceListener(confidenceListener);
+        if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe();
     }
 }
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java
index 085fe2fe94..28da0c1694 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java
@@ -17,17 +17,16 @@
 
 package bisq.core.trade.protocol.tasks.mediation;
 
-import bisq.core.support.dispute.mediation.MediationResultState;
-import bisq.core.trade.Trade;
-import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
-
 import bisq.common.taskrunner.TaskRunner;
-
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
 import lombok.extern.slf4j.Slf4j;
 
 @Slf4j
-public class SetupMediatedPayoutTxListener extends SetupPayoutTxListener {
-    public SetupMediatedPayoutTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
+public class SetupMediatedPayoutTxListener extends TradeTask {
+
+    @SuppressWarnings({ "unused" })
+    public SetupMediatedPayoutTxListener(TaskRunner taskHandler, Trade trade) {
         super(taskHandler, trade);
     }
 
@@ -35,20 +34,10 @@ public class SetupMediatedPayoutTxListener extends SetupPayoutTxListener {
     protected void run() {
         try {
             runInterceptHook();
-
-            super.run();
-
+            if (true) throw new RuntimeException("Not implemented");
+            complete();
         } catch (Throwable t) {
             failed(t);
         }
     }
-
-    @Override
-    protected void setState() {
-        trade.setMediationResultState(MediationResultState.PAYOUT_TX_SEEN_IN_NETWORK);
-        if (trade.getPayoutTx() != null) {
-            processModel.getTradeManager().closeDisputedTrade(trade.getId(), Trade.DisputeState.MEDIATION_CLOSED);
-        }
-        processModel.getTradeManager().requestPersistence();
-    }
 }
diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java
index 7da9e6ddc1..93991c72ec 100644
--- a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java
+++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java
@@ -176,11 +176,12 @@ class GrpcTradesService extends TradesImplBase {
         }
     }
 
+    // TODO: rename KeepFundsRequest to CloseTradeRequest
     @Override
     public void keepFunds(KeepFundsRequest req,
                           StreamObserver<KeepFundsReply> responseObserver) {
         try {
-            coreApi.keepFunds(req.getTradeId());
+            coreApi.closeTrade(req.getTradeId());
             var reply = KeepFundsReply.newBuilder().build();
             responseObserver.onNext(reply);
             responseObserver.onCompleted();
diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java
index 042a4c77c6..76f7171255 100644
--- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java
+++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java
@@ -30,7 +30,6 @@ import bisq.core.trade.protocol.tasks.ApplyFilter;
 import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
 import bisq.core.trade.protocol.tasks.BuyerProcessPaymentReceivedMessage;
 import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
-import bisq.core.trade.protocol.tasks.BuyerSetupPayoutTxListener;
 import bisq.core.trade.protocol.tasks.MakerSetLockTime;
 import bisq.core.trade.protocol.tasks.MakerRemoveOpenOffer;
 import bisq.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
@@ -38,6 +37,7 @@ import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
 import bisq.core.trade.protocol.tasks.SellerPublishDepositTx;
 import bisq.core.trade.protocol.tasks.SellerPublishTradeStatistics;
 import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessage;
+import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
 import bisq.core.trade.protocol.tasks.TakerVerifyMakerFeePayment;
 import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
 import bisq.common.taskrunner.Task;
@@ -123,7 +123,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
 
                         ApplyFilter.class,
                         BuyerPreparePaymentSentMessage.class,
-                        BuyerSetupPayoutTxListener.class,
+                        SetupPayoutTxListener.class,
                         BuyerSendPaymentSentMessage.class,
 
                         BuyerProcessPaymentReceivedMessage.class
@@ -142,7 +142,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
                         ApplyFilter.class,
                         TakerVerifyMakerFeePayment.class,
                         BuyerPreparePaymentSentMessage.class,
-                        BuyerSetupPayoutTxListener.class,
+                        SetupPayoutTxListener.class,
                         BuyerSendPaymentSentMessage.class,
 
                         BuyerProcessPaymentReceivedMessage.class)
diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java
index b9a10220ec..9d0c63923b 100644
--- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java
+++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java
@@ -200,7 +200,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
                 trade.getAssetTxProofResult() != null &&
                 trade.getAssetTxProofResult() != AssetTxProofResult.UNDEFINED;
 
-        if (trade.getPayoutTx() != null)
+        if (trade.getPayoutTxId() != null)
             rows++;
         boolean showDisputedTx = arbitrationManager.findOwnDispute(trade.getId()).isPresent() &&
                 arbitrationManager.findOwnDispute(trade.getId()).get().getDisputePayoutTxId() != null;
@@ -283,9 +283,9 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
             addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), // TODO (woodser): separate UI labels for deposit tx ids
                     trade.getTakerDepositTx().getHash());
 
-        if (trade.getPayoutTx() != null)
+        if (trade.getPayoutTxId() != null)
             addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.payoutTxId"),
-                    trade.getPayoutTx().getHash());
+                    trade.getPayoutTxId());
         if (showDisputedTx)
             addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.disputedPayoutTxId"),
                     arbitrationManager.findOwnDispute(trade.getId()).get().getDisputePayoutTxId());
diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java
index 03ce2eafc7..4ded5304ec 100644
--- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java
@@ -91,7 +91,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 
 import monero.daemon.model.MoneroTx;
 import monero.wallet.MoneroWallet;
-import monero.wallet.model.MoneroTxWallet;
 
 public class PendingTradesDataModel extends ActivatableDataModel {
     @Getter
@@ -465,11 +464,10 @@ public class PendingTradesDataModel extends ActivatableDataModel {
 
       byte[] payoutTxSerialized = null;
       String payoutTxHashAsString = null;
-      MoneroTxWallet payoutTx = trade.getPayoutTx();
       MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
       String updatedMultisigHex = multisigWallet.exportMultisigHex();
       xmrWalletService.closeMultisigWallet(trade.getId()); // close multisig wallet
-      if (payoutTx != null) {
+      if (trade.getPayoutTxId() != null) {
 //          payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
 //          payoutTxHashAsString = payoutTx.getHashAsString();
       }
diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java
index 503844f4ea..0af842bb49 100644
--- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java
@@ -420,7 +420,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
 
             // deposit published
             case ARBITRATOR_PUBLISHED_DEPOSIT_TXS:
-            case DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN:
+            case DEPOSIT_TXS_SEEN_IN_NETWORK:
             case DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN: // TODO: separate step to wait for first confirmation
                 buyerState.set(BuyerState.STEP1);
                 sellerState.set(SellerState.STEP1);
@@ -472,7 +472,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
             // buyer step 4
             case BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG:
                 // Alternatively the maker could have seen the payout tx earlier before he received the PAYOUT_TX_PUBLISHED_MSG:
-            case BUYER_SAW_PAYOUT_TX_IN_NETWORK:
+            case PAYOUT_TX_SEEN_IN_NETWORK:
                 // Alternatively the buyer could fully sign and publish the payout tx
             case BUYER_PUBLISHED_PAYOUT_TX:
                 buyerState.set(BuyerState.STEP4);
diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java
index 36305c16a2..c2fcf7894b 100644
--- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java
+++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java
@@ -203,7 +203,7 @@ public class TradeStepInfo {
                 footerLabel.setVisible(false);
         }
 
-        if (trade != null && trade.getPayoutTx() != null) {
+        if (trade != null && trade.getPayoutTxId() != null) {
             button.setDisable(true);
         }
     }
diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java
index 2bc6736002..59f2b914e7 100644
--- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java
+++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java
@@ -653,7 +653,7 @@ public abstract class TradeStepView extends AnchorPane {
             return;
         }
 
-        if (trade.getPayoutTx() != null) {
+        if (trade.getPayoutTxId() != null) {
             return;
         }
 
diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto
index 82f1b88518..c9afda4bd0 100644
--- a/proto/src/main/proto/pb.proto
+++ b/proto/src/main/proto/pb.proto
@@ -1648,7 +1648,7 @@ message Trade {
         STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 10;
         SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 11;
         ARBITRATOR_PUBLISHED_DEPOSIT_TXS = 12;
-        DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN = 13;
+        DEPOSIT_TXS_SEEN_IN_NETWORK = 13;
         DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN = 14;
         DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN = 15;
         BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 16;
@@ -1668,8 +1668,8 @@ message Trade {
         SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 30;
         SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 31;
         BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 32;
-        BUYER_SAW_PAYOUT_TX_IN_NETWORK = 33;
-        BUYER_PUBLISHED_PAYOUT_TX = 34;
+        BUYER_PUBLISHED_PAYOUT_TX = 33;
+        PAYOUT_TX_SEEN_IN_NETWORK = 34;
         WITHDRAW_COMPLETED = 35;
     }
 
@@ -1712,31 +1712,33 @@ message Trade {
     string taker_fee_tx_id = 3;
     reserved 4;
     string payout_tx_id = 5;
-    int64 amount_as_long = 6;
-    int64 tx_fee_as_long = 7;
-    int64 taker_fee_as_long = 8;
-    int64 take_offer_date = 9;
-    int64 price = 10;
-    State state = 11;
-    DisputeState dispute_state = 12;
-    TradePeriodState period_state = 13;
-    Contract contract = 14;
-    string contract_as_json = 15;
-    bytes contract_hash = 16;
-    NodeAddress arbitrator_node_address = 17;
-    NodeAddress mediator_node_address = 18;
+    string payout_tx_hex = 6;
+    string payout_tx_key = 7;
+    int64 amount_as_long = 8;
+    int64 tx_fee_as_long = 9;
+    int64 taker_fee_as_long = 10;
+    int64 take_offer_date = 11;
+    int64 price = 12;
+    State state = 13;
+    DisputeState dispute_state = 14;
+    TradePeriodState period_state = 15;
+    Contract contract = 16;
+    string contract_as_json = 17;
+    bytes contract_hash = 18;
+    NodeAddress arbitrator_node_address = 19;
+    NodeAddress mediator_node_address = 20;
     string error_message = 21;
-    string counter_currency_tx_id = 24;
-    repeated ChatMessage chat_message = 25;
-    MediationResultState mediation_result_state = 26;
-    int64 lock_time = 27;
-    bytes delayed_payout_tx_bytes = 28;
-    NodeAddress refund_agent_node_address = 29;
-    RefundResultState refund_result_state = 30;
-    int64 last_refresh_request_date = 31 [deprecated = true];
-    string counter_currency_extra_data = 32;
-    string asset_tx_proof_result = 33; // name of AssetTxProofResult enum
-    string uid = 34;
+    string counter_currency_tx_id = 22;
+    repeated ChatMessage chat_message = 23;
+    MediationResultState mediation_result_state = 24;
+    int64 lock_time = 25;
+    bytes delayed_payout_tx_bytes = 26;
+    NodeAddress refund_agent_node_address = 27;
+    RefundResultState refund_result_state = 28;
+    int64 last_refresh_request_date = 29 [deprecated = true];
+    string counter_currency_extra_data = 30;
+    string asset_tx_proof_result = 31; // name of AssetTxProofResult enum
+    string uid = 32;
 }
 
 message BuyerAsMakerTrade {
@@ -1819,11 +1821,10 @@ message TradingPeer {
     string prepared_multisig_hex = 1005;
     string made_multisig_hex = 1006;
     string exchanged_multisig_hex = 1007;
-    string payout_tx_hex = 1008;
-    string deposit_tx_hash = 1009;
-    string deposit_tx_hex = 1010;
-    string deposit_tx_key = 1011;
-    string updated_multisig_hex = 1012;
+    string deposit_tx_hash = 1008;
+    string deposit_tx_hex = 1009;
+    string deposit_tx_key = 1010;
+    string updated_multisig_hex = 1011;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////