/* * This file is part of Bitsquare. * * Bitsquare 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. * * Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.trade.protocol.trade.tasks.offerer; import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.common.UserThread; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.trade.OffererTrade; import io.bitsquare.trade.Trade; import io.bitsquare.trade.protocol.trade.tasks.TradeTask; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // The buyer waits for the msg from the seller that he has published the deposit tx. // In error case he might not get that msg so we check additionally the balance of our inputs, if it is zero, it means the deposit // is already published. We set then the DEPOSIT_LOCKED state, so the user get informed that he is already in the critical state and need // to request support. public class SetupDepositBalanceListener extends TradeTask { private static final Logger log = LoggerFactory.getLogger(SetupDepositBalanceListener.class); private Subscription tradeStateSubscription; private BalanceListener balanceListener; public SetupDepositBalanceListener(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @Override protected void run() { try { runInterceptHook(); WalletService walletService = processModel.getWalletService(); Address address = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); balanceListener = new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { updateBalance(balance); } }; walletService.addBalanceListener(balanceListener); tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { log.debug("tradeStateSubscription newValue " + newValue); if (newValue == Trade.State.OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG || newValue == Trade.State.DEPOSIT_SEEN_IN_NETWORK) { walletService.removeBalanceListener(balanceListener); // hack to remove tradeStateSubscription at callback UserThread.execute(this::unSubscribe); } }); updateBalance(walletService.getBalanceForAddress(address)); // we complete immediately, our object stays alive because the balanceListener is stored in the WalletService complete(); } catch (Throwable t) { failed(t); } } private void unSubscribe() { tradeStateSubscription.unsubscribe(); } private void updateBalance(Coin balance) { log.debug("updateBalance " + balance.toFriendlyString()); log.debug("pre tradeState " + trade.getState().toString()); Trade.State tradeState = trade.getState(); if (balance.compareTo(Coin.ZERO) == 0) { if (trade instanceof OffererTrade) { processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer()); if (tradeState == Trade.State.OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) { trade.setState(Trade.State.DEPOSIT_SEEN_IN_NETWORK); } else if (tradeState.getPhase() == Trade.Phase.PREPARATION) { processModel.getTradeManager().removePreparedTrade(trade); } else if (tradeState.getPhase().ordinal() < Trade.Phase.DEPOSIT_PAID.ordinal()) { // TODO need to evaluate if that is correct processModel.getTradeManager().addTradeToFailedTrades(trade); } } } log.debug("tradeState " + trade.getState().toString()); } }