This commit is contained in:
woodser 2021-05-04 20:20:30 -04:00
parent 8a38081c04
commit a22edd60f8
241 changed files with 10631 additions and 4905 deletions

View file

@ -36,7 +36,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BisqAppMain extends BisqExecutable {
public static final String DEFAULT_APP_NAME = "Bisq";
public static final String DEFAULT_APP_NAME = "Haveno";
private BisqApp application;

View file

@ -28,8 +28,6 @@ import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipToggleButton;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.market.MarketView;
import bisq.desktop.main.market.offerbook.OfferBookChartView;
import bisq.desktop.main.offer.BuyOfferView;
@ -180,12 +178,12 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
ToggleButton buyButton = new NavButton(BuyOfferView.class, Res.get("mainView.menu.buyBtc").toUpperCase());
ToggleButton sellButton = new NavButton(SellOfferView.class, Res.get("mainView.menu.sellBtc").toUpperCase());
ToggleButton portfolioButton = new NavButton(PortfolioView.class, Res.get("mainView.menu.portfolio").toUpperCase());
ToggleButton fundsButton = new NavButton(FundsView.class, Res.get("mainView.menu.funds").toUpperCase());
// ToggleButton fundsButton = new NavButton(FundsView.class, Res.get("mainView.menu.funds").toUpperCase());
ToggleButton supportButton = new NavButton(SupportView.class, Res.get("mainView.menu.support"));
ToggleButton settingsButton = new NavButton(SettingsView.class, Res.get("mainView.menu.settings"));
ToggleButton accountButton = new NavButton(AccountView.class, Res.get("mainView.menu.account"));
ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao"));
// ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao"));
JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton);
JFXBadge supportButtonWithBadge = new JFXBadge(supportButton);
@ -208,17 +206,17 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
sellButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT4, keyEvent)) {
portfolioButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT5, keyEvent)) {
fundsButton.fire();
// } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT5, keyEvent)) {
// fundsButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT6, keyEvent)) {
supportButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT7, keyEvent)) {
settingsButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT8, keyEvent)) {
accountButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) {
if (daoButton.isVisible())
daoButton.fire();
// } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) {
// if (daoButton.isVisible())
// daoButton.fire();
}
});
}
@ -315,14 +313,14 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
});
HBox primaryNav = new HBox(marketButton, getNavigationSeparator(), buyButton, getNavigationSeparator(),
sellButton, getNavigationSeparator(), portfolioButtonWithBadge, getNavigationSeparator(), fundsButton);
sellButton, getNavigationSeparator(), portfolioButtonWithBadge, getNavigationSeparator());
primaryNav.setAlignment(Pos.CENTER_LEFT);
primaryNav.getStyleClass().add("nav-primary");
HBox.setHgrow(primaryNav, Priority.SOMETIMES);
HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButtonWithBadge,
getNavigationSpacer(), accountButton, getNavigationSpacer(), daoButton);
getNavigationSpacer(), accountButton, getNavigationSpacer());
secondaryNav.getStyleClass().add("nav-secondary");
HBox.setHgrow(secondaryNav, Priority.SOMETIMES);
@ -364,7 +362,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
baseApplicationContainer.setBottom(createFooter());
setupBadge(portfolioButtonWithBadge, model.getNumPendingTrades(), model.getShowPendingTradesNotification());
setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification());
// setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification());
setupBadge(settingsButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowSettingsUpdatesNotification());
navigation.addListener((viewPath, data) -> {

View file

@ -38,8 +38,8 @@ import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.validation.HexStringValidator;
import bisq.core.util.validation.IntegerValidator;

View file

@ -24,18 +24,17 @@ import bisq.desktop.components.TitledGroupBg;
import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse;
import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest;
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx;
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
@ -45,11 +44,12 @@ import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositT
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
@ -58,19 +58,19 @@ import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
@ -118,7 +118,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
addGroup("PlaceOfferProtocol",
FXCollections.observableArrayList(Arrays.asList(
ValidateOffer.class,
CreateMakerFeeTx.class,
MakerCreateFeeTx.class,
AddToOfferBook.class)
));
@ -127,7 +127,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
TakerCreateFeeTx.class, // TODO (woodser): rename to TakerCreateFeeTx
SellerAsTakerCreatesDepositTxInputs.class,
TakerSendInputsForDepositTxRequest.class,
@ -143,7 +143,8 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerProcessDelayedPayoutTxSignatureResponse.class,
SellerSignsDelayedPayoutTx.class,
SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
TakerProcessesMakerDepositTxMessage.class,
SellerPublishesDepositTx.class,
SellerPublishesTradeStatistics.class,
@ -153,8 +154,8 @@ public class DebugView extends InitializableView<GridPane, Void> {
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
SellerSignAndFinalizePayoutTx.class,
SellerBroadcastPayoutTx.class,
SellerSignAndPublishPayoutTx.class,
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
SellerSendPayoutTxPublishedMessage.class
)
@ -168,7 +169,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
BuyerAsMakerCreatesAndSignsDepositTx.class,
BuyerSetupDepositTxListener.class,
MakerSetupDepositTxsListener.class,
BuyerAsMakerSendsInputsForDepositTxResponse.class,
BuyerProcessDelayedPayoutTxSignatureRequest.class,
@ -182,7 +183,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
ApplyFilter.class,
MakerVerifyTakerFeePayment.class,
BuyerSignPayoutTx.class,
BuyerCreateAndSignPayoutTx.class,
BuyerSetupPayoutTxListener.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
@ -195,7 +196,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
TakerCreateFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.class,
TakerSendInputsForDepositTxRequest.class,
@ -205,7 +206,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
BuyerAsTakerSignsDepositTx.class,
BuyerSetupDepositTxListener.class,
TakerSetupDepositTxsListener.class,
BuyerAsTakerSendsDepositTxMessage.class,
BuyerProcessDelayedPayoutTxSignatureRequest.class,
@ -218,7 +219,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
BuyerSignPayoutTx.class,
BuyerCreateAndSignPayoutTx.class,
BuyerSetupPayoutTxListener.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
@ -234,8 +235,9 @@ public class DebugView extends InitializableView<GridPane, Void> {
MakerCreateAndSignContract.class,
SellerAsMakerCreatesUnsignedDepositTx.class,
SellerAsMakerSendsInputsForDepositTxResponse.class,
SetupDepositTxsListener.class,
SellerAsMakerProcessDepositTxMessage.class,
//SellerAsMakerProcessDepositTxMessage.class,
MakerRemovesOpenOffer.class,
SellerAsMakerFinalizesDepositTx.class,
SellerCreatesDelayedPayoutTx.class,
@ -244,7 +246,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerProcessDelayedPayoutTxSignatureResponse.class,
SellerSignsDelayedPayoutTx.class,
SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class,
SellerPublishesTradeStatistics.class,
@ -254,8 +256,8 @@ public class DebugView extends InitializableView<GridPane, Void> {
ApplyFilter.class,
MakerVerifyTakerFeePayment.class,
SellerSignAndFinalizePayoutTx.class,
SellerBroadcastPayoutTx.class,
SellerSignAndPublishPayoutTx.class,
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
SellerSendPayoutTxPublishedMessage.class
)
));

View file

@ -136,7 +136,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
@Override
public void initialize() {
paymentLabelString = Res.get("funds.deposit.fundBisqWallet");
paymentLabelString = Res.get("funds.deposit.fundHavenoWallet");
addressColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.address")));
balanceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.balanceWithCur", Res.getBaseCurrencyCode())));
confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations")));

View file

@ -17,20 +17,17 @@
package bisq.desktop.main.funds.locked;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.util.DisplayUtils;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletService;
import bisq.core.locale.Res;
import bisq.core.trade.Trade;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javafx.scene.control.Label;
@ -67,22 +64,24 @@ class LockedListItem {
this.btcWalletService = btcWalletService;
this.formatter = formatter;
if (trade.getDepositTx() != null && !trade.getDepositTx().getOutputs().isEmpty()) {
address = WalletService.getAddressFromOutput(trade.getDepositTx().getOutput(0));
addressString = address != null ? address.toString() : "";
} else {
address = null;
addressString = "";
}
balanceLabel = new AutoTooltipLabel();
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
}
};
btcWalletService.addBalanceListener(balanceListener);
updateBalance();
throw new RuntimeException("Cannot listen to multisig deposits in xmr without exchanging multisig info");
// if (trade.getDepositTx() != null && !trade.getDepositTx().getOutputs().isEmpty()) {
// address = WalletService.getAddressFromOutput(trade.getDepositTx().getOutput(0));
// addressString = address != null ? address.toString() : "";
// } else {
// address = null;
// addressString = "";
// }
// balanceLabel = new AutoTooltipLabel();
// balanceListener = new BalanceListener(address) {
// @Override
// public void onBalanceChanged(Coin balance, Transaction tx) {
// updateBalance();
// }
// };
// btcWalletService.addBalanceListener(balanceListener);
// updateBalance();
}
LockedListItem() {

View file

@ -17,25 +17,27 @@
package bisq.desktop.main.funds.transactions;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.Tradable;
import org.bitcoinj.core.Transaction;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import monero.wallet.model.MoneroTxWallet;
class DisplayedTransactions extends ObservableListDecorator<TransactionsListItem> {
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final TradableRepository tradableRepository;
private final TransactionListItemFactory transactionListItemFactory;
private final TransactionAwareTradableFactory transactionAwareTradableFactory;
DisplayedTransactions(BtcWalletService btcWalletService, TradableRepository tradableRepository,
DisplayedTransactions(XmrWalletService xmrWalletService, TradableRepository tradableRepository,
TransactionListItemFactory transactionListItemFactory,
TransactionAwareTradableFactory transactionAwareTradableFactory) {
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.tradableRepository = tradableRepository;
this.transactionListItemFactory = transactionListItemFactory;
this.transactionAwareTradableFactory = transactionAwareTradableFactory;
@ -49,13 +51,13 @@ class DisplayedTransactions extends ObservableListDecorator<TransactionsListItem
}
private List<TransactionsListItem> getTransactionListItems() {
Set<Transaction> transactions = btcWalletService.getTransactions(false);
List<MoneroTxWallet> transactions = xmrWalletService.getTransactions(false);
return transactions.stream()
.map(this::convertTransactionToListItem)
.collect(Collectors.toList());
}
private TransactionsListItem convertTransactionToListItem(Transaction transaction) {
private TransactionsListItem convertTransactionToListItem(MoneroTxWallet transaction) {
Set<Tradable> tradables = tradableRepository.getAll();
TransactionAwareTradable maybeTradable = tradables.stream()

View file

@ -17,31 +17,31 @@
package bisq.desktop.main.funds.transactions;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class DisplayedTransactionsFactory {
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final TradableRepository tradableRepository;
private final TransactionListItemFactory transactionListItemFactory;
private final TransactionAwareTradableFactory transactionAwareTradableFactory;
@Inject
DisplayedTransactionsFactory(BtcWalletService btcWalletService,
DisplayedTransactionsFactory(XmrWalletService xmrWalletService,
TradableRepository tradableRepository,
TransactionListItemFactory transactionListItemFactory,
TransactionAwareTradableFactory transactionAwareTradableFactory) {
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.tradableRepository = tradableRepository;
this.transactionListItemFactory = transactionListItemFactory;
this.transactionAwareTradableFactory = transactionAwareTradableFactory;
}
DisplayedTransactions create() {
return new DisplayedTransactions(btcWalletService, tradableRepository, transactionListItemFactory,
return new DisplayedTransactions(xmrWalletService, tradableRepository, transactionListItemFactory,
transactionAwareTradableFactory);
}
}

View file

@ -19,7 +19,9 @@ package bisq.desktop.main.funds.transactions;
import bisq.core.trade.Tradable;
import org.bitcoinj.core.Transaction;
import monero.wallet.model.MoneroTxWallet;
class DummyTransactionAwareTradable implements TransactionAwareTradable {
private final Tradable delegate;
@ -29,7 +31,7 @@ class DummyTransactionAwareTradable implements TransactionAwareTradable {
}
@Override
public boolean isRelatedToTransaction(Transaction transaction) {
public boolean isRelatedToTransaction(MoneroTxWallet transaction) {
return false;
}

View file

@ -21,7 +21,9 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.trade.Tradable;
import org.bitcoinj.core.Transaction;
import monero.wallet.model.MoneroTxWallet;
class TransactionAwareOpenOffer implements TransactionAwareTradable {
private final OpenOffer delegate;
@ -30,11 +32,11 @@ class TransactionAwareOpenOffer implements TransactionAwareTradable {
this.delegate = delegate;
}
public boolean isRelatedToTransaction(Transaction transaction) {
public boolean isRelatedToTransaction(MoneroTxWallet transaction) {
Offer offer = delegate.getOffer();
String paymentTxId = offer.getOfferFeePaymentTxId();
String txId = transaction.getTxId().toString();
String txId = transaction.getHash();
return paymentTxId.equals(txId);
}

View file

@ -19,10 +19,12 @@ package bisq.desktop.main.funds.transactions;
import bisq.core.trade.Tradable;
import org.bitcoinj.core.Transaction;
import monero.wallet.model.MoneroTxWallet;
interface TransactionAwareTradable {
boolean isRelatedToTransaction(Transaction transaction);
boolean isRelatedToTransaction(MoneroTxWallet transaction);
Tradable asTradable();
}

View file

@ -17,7 +17,7 @@
package bisq.desktop.main.funds.transactions;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.refund.RefundManager;
@ -34,17 +34,17 @@ import javax.inject.Singleton;
public class TransactionAwareTradableFactory {
private final ArbitrationManager arbitrationManager;
private final RefundManager refundManager;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final PubKeyRing pubKeyRing;
@Inject
TransactionAwareTradableFactory(ArbitrationManager arbitrationManager,
RefundManager refundManager,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
PubKeyRing pubKeyRing) {
this.arbitrationManager = arbitrationManager;
this.refundManager = refundManager;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.pubKeyRing = pubKeyRing;
}
@ -55,7 +55,7 @@ public class TransactionAwareTradableFactory {
return new TransactionAwareTrade((Trade) delegate,
arbitrationManager,
refundManager,
btcWalletService,
xmrWalletService,
pubKeyRing);
} else {
return new DummyTransactionAwareTradable(delegate);

View file

@ -17,79 +17,81 @@
package bisq.desktop.main.funds.transactions;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Contract;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.common.crypto.PubKeyRing;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import javafx.collections.ObservableList;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.model.MoneroTxWallet;
@Slf4j
class TransactionAwareTrade implements TransactionAwareTradable {
private final Trade trade;
private final ArbitrationManager arbitrationManager;
private final RefundManager refundManager;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final PubKeyRing pubKeyRing;
TransactionAwareTrade(Trade trade,
ArbitrationManager arbitrationManager,
RefundManager refundManager,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
PubKeyRing pubKeyRing) {
this.trade = trade;
this.arbitrationManager = arbitrationManager;
this.refundManager = refundManager;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.pubKeyRing = pubKeyRing;
}
@Override
public boolean isRelatedToTransaction(Transaction transaction) {
Sha256Hash hash = transaction.getTxId();
String txId = hash.toString();
public boolean isRelatedToTransaction(MoneroTxWallet transaction) {
String txId = transaction.getHash();
boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId());
boolean isOfferFeeTx = isOfferFeeTx(txId);
boolean isDepositTx = isDepositTx(hash);
boolean isPayoutTx = isPayoutTx(hash);
boolean isMakerDepositTx = isMakerDepositTx(txId);
boolean isTakerDepositTx = isTakerDepositTx(txId);
boolean isPayoutTx = isPayoutTx(txId);
boolean isDisputedPayoutTx = isDisputedPayoutTx(txId);
boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId);
boolean isRefundPayoutTx = isRefundPayoutTx(txId);
return isTakerOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx ||
isDisputedPayoutTx || isDelayedPayoutTx || isRefundPayoutTx;
return isTakerOfferFeeTx || isOfferFeeTx || isMakerDepositTx || isTakerDepositTx ||
isPayoutTx || isDisputedPayoutTx;
}
private boolean isPayoutTx(Sha256Hash txId) {
return Optional.ofNullable(trade.getPayoutTx())
.map(Transaction::getTxId)
.map(hash -> hash.equals(txId))
.orElse(false);
private boolean isPayoutTx(String txId) {
return Optional.ofNullable(trade.getPayoutTx())
.map(MoneroTxWallet::getHash)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isDepositTx(Sha256Hash txId) {
return Optional.ofNullable(trade.getDepositTx())
.map(Transaction::getTxId)
.map(hash -> hash.equals(txId))
.orElse(false);
private boolean isMakerDepositTx(String txId) {
return Optional.ofNullable(trade.getMakerDepositTx())
.map(MoneroTxWallet::getHash)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isTakerDepositTx(String txId) {
return Optional.ofNullable(trade.getTakerDepositTx())
.map(MoneroTxWallet::getHash)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isOfferFeeTx(String txId) {
@ -117,59 +119,59 @@ class TransactionAwareTrade implements TransactionAwareTradable {
});
}
boolean isDelayedPayoutTx(String txId) {
Transaction transaction = btcWalletService.getTransaction(txId);
if (transaction == null)
return false;
if (transaction.getLockTime() == 0)
return false;
if (transaction.getInputs() == null)
return false;
return transaction.getInputs().stream()
.anyMatch(input -> {
TransactionOutput connectedOutput = input.getConnectedOutput();
if (connectedOutput == null) {
return false;
}
Transaction parentTransaction = connectedOutput.getParentTransaction();
if (parentTransaction == null) {
return false;
}
return isDepositTx(parentTransaction.getTxId());
});
}
private boolean isRefundPayoutTx(String txId) {
String tradeId = trade.getId();
ObservableList<Dispute> disputes = refundManager.getDisputesAsObservableList();
boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId);
if (isAnyDisputeRelatedToThis) {
Transaction tx = btcWalletService.getTransaction(txId);
if (tx != null) {
for (TransactionOutput txo : tx.getOutputs()) {
if (btcWalletService.isTransactionOutputMine(txo)) {
try {
Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
Contract contract = checkNotNull(trade.getContract());
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
contract.getBuyerPayoutAddressString() :
contract.getSellerPayoutAddressString();
if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
return true;
}
} catch (RuntimeException ignore) {
}
}
}
}
}
return false;
}
// boolean isDelayedPayoutTx(String txId) {
// Transaction transaction = btcWalletService.getTransaction(txId);
// if (transaction == null)
// return false;
//
// if (transaction.getLockTime() == 0)
// return false;
//
// if (transaction.getInputs() == null)
// return false;
//
// return transaction.getInputs().stream()
// .anyMatch(input -> {
// TransactionOutput connectedOutput = input.getConnectedOutput();
// if (connectedOutput == null) {
// return false;
// }
// Transaction parentTransaction = connectedOutput.getParentTransaction();
// if (parentTransaction == null) {
// return false;
// }
// return isDepositTx(parentTransaction.getTxId());
// });
// }
//
// private boolean isRefundPayoutTx(String txId) {
// String tradeId = trade.getId();
// ObservableList<Dispute> disputes = refundManager.getDisputesAsObservableList();
//
// boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId);
//
// if (isAnyDisputeRelatedToThis) {
// Transaction tx = btcWalletService.getTransaction(txId);
// if (tx != null) {
// for (TransactionOutput txo : tx.getOutputs()) {
// if (btcWalletService.isTransactionOutputMine(txo)) {
// try {
// Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
// Contract contract = checkNotNull(trade.getContract());
// String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
// contract.getBuyerPayoutAddressString() :
// contract.getSellerPayoutAddressString();
// if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
// return true;
// }
// } catch (RuntimeException ignore) {
// }
// }
// }
// }
// }
// return false;
// }
@Override
public Tradable asTradable() {

View file

@ -18,14 +18,12 @@
package bisq.desktop.main.funds.transactions;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
@ -33,30 +31,34 @@ import javax.inject.Singleton;
import javax.annotation.Nullable;
import monero.wallet.model.MoneroTxWallet;
@Singleton
public class TransactionListItemFactory {
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final BsqWalletService bsqWalletService;
private final DaoFacade daoFacade;
private final CoinFormatter formatter;
private final Preferences preferences;
@Inject
TransactionListItemFactory(BtcWalletService btcWalletService,
TransactionListItemFactory(XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
DaoFacade daoFacade,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
Preferences preferences) {
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.bsqWalletService = bsqWalletService;
this.daoFacade = daoFacade;
this.formatter = formatter;
this.preferences = preferences;
}
TransactionsListItem create(Transaction transaction, @Nullable TransactionAwareTradable tradable) {
TransactionsListItem create(MoneroTxWallet transaction, @Nullable TransactionAwareTradable tradable) {
return new TransactionsListItem(transaction,
btcWalletService,
xmrWalletService,
bsqWalletService,
tradable,
daoFacade,

View file

@ -18,34 +18,21 @@
package bisq.desktop.main.funds.transactions;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;
import com.google.common.base.Suppliers;
import javafx.scene.control.Tooltip;
import java.util.Date;
import java.util.Optional;
import java.util.function.Supplier;
import lombok.Getter;
@ -53,9 +40,13 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import monero.wallet.model.MoneroTxWallet;
@Slf4j
class TransactionsListItem {
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final CoinFormatter formatter;
private String dateString;
private final Date date;
@ -88,218 +79,220 @@ class TransactionsListItem {
// used at exportCSV
TransactionsListItem() {
date = null;
btcWalletService = null;
xmrWalletService = null;
txId = null;
formatter = null;
isDustAttackTx = false;
lazyFieldsSupplier = null;
}
TransactionsListItem(Transaction transaction,
BtcWalletService btcWalletService,
TransactionsListItem(MoneroTxWallet transaction,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
TransactionAwareTradable transactionAwareTradable,
DaoFacade daoFacade,
CoinFormatter formatter,
long ignoreDustThreshold) {
this.btcWalletService = btcWalletService;
this.formatter = formatter;
this.memo = transaction.getMemo();
txId = transaction.getTxId().toString();
Optional<Tradable> optionalTradable = Optional.ofNullable(transactionAwareTradable)
.map(TransactionAwareTradable::asTradable);
Coin valueSentToMe = btcWalletService.getValueSentToMeForTransaction(transaction);
Coin valueSentFromMe = btcWalletService.getValueSentFromMeForTransaction(transaction);
// TODO check and refactor
boolean txFeeForBsqPayment = false;
boolean withdrawalFromBSQWallet = false;
if (valueSentToMe.isZero()) {
amountAsCoin = valueSentFromMe.multiply(-1);
for (TransactionOutput output : transaction.getOutputs()) {
if (!btcWalletService.isTransactionOutputMine(output)) {
received = false;
if (WalletService.isOutputScriptConvertibleToAddress(output)) {
addressString = WalletService.getAddressStringFromOutput(output);
if (bsqWalletService.isTransactionOutputMine(output)) {
txFeeForBsqPayment = true;
} else {
direction = Res.get("funds.tx.direction.sentTo");
}
break;
}
}
}
} else if (valueSentFromMe.isZero()) {
amountAsCoin = valueSentToMe;
direction = Res.get("funds.tx.direction.receivedWith");
received = true;
for (TransactionOutput output : transaction.getOutputs()) {
if (btcWalletService.isTransactionOutputMine(output) &&
WalletService.isOutputScriptConvertibleToAddress(output)) {
addressString = WalletService.getAddressStringFromOutput(output);
break;
}
}
} else {
amountAsCoin = valueSentToMe.subtract(valueSentFromMe);
boolean outgoing = false;
for (TransactionOutput output : transaction.getOutputs()) {
if (!btcWalletService.isTransactionOutputMine(output)) {
if (WalletService.isOutputScriptConvertibleToAddress(output)) {
addressString = WalletService.getAddressStringFromOutput(output);
if (bsqWalletService.isTransactionOutputMine(output)) {
outgoing = false;
txFeeForBsqPayment = true;
Optional<TxType> txTypeOptional = daoFacade.getOptionalTxType(txId);
if (txTypeOptional.isPresent()) {
if (txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST))
details = Res.get("funds.tx.compensationRequestTxFee");
else if (txTypeOptional.get().equals(TxType.REIMBURSEMENT_REQUEST))
details = Res.get("funds.tx.reimbursementRequestTxFee");
else
details = Res.get("funds.tx.daoTxFee");
}
} else {
outgoing = true;
}
break;
}
} else {
addressString = WalletService.getAddressStringFromOutput(output);
outgoing = (valueSentToMe.getValue() < valueSentFromMe.getValue());
if (!outgoing) {
direction = Res.get("funds.tx.direction.receivedWith");
received = true;
withdrawalFromBSQWallet = true;
}
}
}
if (outgoing) {
direction = Res.get("funds.tx.direction.sentTo");
received = false;
}
}
if (txFeeForBsqPayment) {
// direction = Res.get("funds.tx.txFeePaymentForBsqTx");
direction = Res.get("funds.tx.direction.sentTo");
//addressString = "";
}
if (optionalTradable.isPresent()) {
tradable = optionalTradable.get();
detailsAvailable = true;
String tradeId = tradable.getShortId();
if (tradable instanceof OpenOffer) {
details = Res.get("funds.tx.createOfferFee", tradeId);
} else if (tradable instanceof Trade) {
Trade trade = (Trade) tradable;
TransactionAwareTrade transactionAwareTrade = (TransactionAwareTrade) transactionAwareTradable;
if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().equals(txId)) {
details = Res.get("funds.tx.takeOfferFee", tradeId);
} else {
Offer offer = trade.getOffer();
String offerFeePaymentTxID = offer.getOfferFeePaymentTxId();
if (offerFeePaymentTxID != null && offerFeePaymentTxID.equals(txId)) {
details = Res.get("funds.tx.createOfferFee", tradeId);
} else if (trade.getDepositTx() != null &&
trade.getDepositTx().getTxId().equals(Sha256Hash.wrap(txId))) {
details = Res.get("funds.tx.multiSigDeposit", tradeId);
} else if (trade.getPayoutTx() != null &&
trade.getPayoutTx().getTxId().equals(Sha256Hash.wrap(txId))) {
details = Res.get("funds.tx.multiSigPayout", tradeId);
if (amountAsCoin.isZero()) {
initialTxConfidenceVisibility = false;
}
} else {
Trade.DisputeState disputeState = trade.getDisputeState();
if (disputeState == Trade.DisputeState.DISPUTE_CLOSED) {
if (valueSentToMe.isPositive()) {
details = Res.get("funds.tx.disputePayout", tradeId);
} else {
details = Res.get("funds.tx.disputeLost", tradeId);
initialTxConfidenceVisibility = false;
}
} else if (disputeState == Trade.DisputeState.REFUND_REQUEST_CLOSED ||
disputeState == Trade.DisputeState.REFUND_REQUESTED ||
disputeState == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
if (valueSentToMe.isPositive()) {
details = Res.get("funds.tx.refund", tradeId);
} else {
// We have spent the deposit tx outputs to the Bisq donation address to enable
// the refund process (refund agent -> reimbursement). As the funds have left our wallet
// already when funding the deposit tx we show 0 BTC as amount.
// Confirmation is not known from the BitcoinJ side (not 100% clear why) as no funds
// left our wallet nor we received funds. So we set indicator invisible.
amountAsCoin = Coin.ZERO;
details = Res.get("funds.tx.collateralForRefund", tradeId);
initialTxConfidenceVisibility = false;
}
} else {
if (transactionAwareTrade.isDelayedPayoutTx(txId)) {
details = Res.get("funds.tx.timeLockedPayoutTx", tradeId);
initialTxConfidenceVisibility = false;
} else {
details = Res.get("funds.tx.unknown", tradeId);
}
}
}
}
}
} else {
if (amountAsCoin.isZero()) {
details = Res.get("funds.tx.noFundsFromDispute");
initialTxConfidenceVisibility = false;
} else if (withdrawalFromBSQWallet) {
details = Res.get("funds.tx.withdrawnFromBSQWallet");
} else if (!txFeeForBsqPayment) {
details = received ? Res.get("funds.tx.receivedFunds") : Res.get("funds.tx.withdrawnFromWallet");
} else if (details.isEmpty()) {
details = Res.get("funds.tx.txFeePaymentForBsqTx");
}
}
// Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
date = transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime();
dateString = DisplayUtils.formatDateTime(date);
isDustAttackTx = received && valueSentToMe.value < ignoreDustThreshold;
if (isDustAttackTx) {
details = Res.get("funds.tx.dustAttackTx");
}
// confidence
lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
tooltip = new Tooltip(Res.get("shared.notUsedYet"));
txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setTooltip(tooltip);
txConfidenceIndicator.setVisible(initialTxConfidenceVisibility);
TransactionConfidence confidence = transaction.getConfidence();
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
}});
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
}
};
btcWalletService.addTxConfidenceListener(txConfidenceListener);
throw new RuntimeException("TransactionsListItem needs updated to use XMR wallet");
// this.btcWalletService = btcWalletService;
// this.formatter = formatter;
// this.memo = transaction.getMemo();
//
// txId = transaction.getTxId().toString();
//
// Optional<Tradable> optionalTradable = Optional.ofNullable(transactionAwareTradable)
// .map(TransactionAwareTradable::asTradable);
//
// Coin valueSentToMe = btcWalletService.getValueSentToMeForTransaction(transaction);
// Coin valueSentFromMe = btcWalletService.getValueSentFromMeForTransaction(transaction);
//
// // TODO check and refactor
// boolean txFeeForBsqPayment = false;
// boolean withdrawalFromBSQWallet = false;
// if (valueSentToMe.isZero()) {
// amountAsCoin = valueSentFromMe.multiply(-1);
// for (TransactionOutput output : transaction.getOutputs()) {
// if (!btcWalletService.isTransactionOutputMine(output)) {
// received = false;
// if (WalletService.isOutputScriptConvertibleToAddress(output)) {
// addressString = WalletService.getAddressStringFromOutput(output);
// if (bsqWalletService.isTransactionOutputMine(output)) {
// txFeeForBsqPayment = true;
// } else {
// direction = Res.get("funds.tx.direction.sentTo");
// }
// break;
// }
// }
// }
// } else if (valueSentFromMe.isZero()) {
// amountAsCoin = valueSentToMe;
// direction = Res.get("funds.tx.direction.receivedWith");
// received = true;
// for (TransactionOutput output : transaction.getOutputs()) {
// if (btcWalletService.isTransactionOutputMine(output) &&
// WalletService.isOutputScriptConvertibleToAddress(output)) {
// addressString = WalletService.getAddressStringFromOutput(output);
// break;
// }
// }
// } else {
// amountAsCoin = valueSentToMe.subtract(valueSentFromMe);
// boolean outgoing = false;
// for (TransactionOutput output : transaction.getOutputs()) {
// if (!btcWalletService.isTransactionOutputMine(output)) {
// if (WalletService.isOutputScriptConvertibleToAddress(output)) {
// addressString = WalletService.getAddressStringFromOutput(output);
// if (bsqWalletService.isTransactionOutputMine(output)) {
// outgoing = false;
// txFeeForBsqPayment = true;
//
// Optional<TxType> txTypeOptional = daoFacade.getOptionalTxType(txId);
// if (txTypeOptional.isPresent()) {
// if (txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST))
// details = Res.get("funds.tx.compensationRequestTxFee");
// else if (txTypeOptional.get().equals(TxType.REIMBURSEMENT_REQUEST))
// details = Res.get("funds.tx.reimbursementRequestTxFee");
// else
// details = Res.get("funds.tx.daoTxFee");
// }
// } else {
// outgoing = true;
// }
// break;
// }
// } else {
// addressString = WalletService.getAddressStringFromOutput(output);
// outgoing = (valueSentToMe.getValue() < valueSentFromMe.getValue());
// if (!outgoing) {
// direction = Res.get("funds.tx.direction.receivedWith");
// received = true;
// withdrawalFromBSQWallet = true;
// }
// }
// }
//
// if (outgoing) {
// direction = Res.get("funds.tx.direction.sentTo");
// received = false;
// }
// }
//
// if (txFeeForBsqPayment) {
// // direction = Res.get("funds.tx.txFeePaymentForBsqTx");
// direction = Res.get("funds.tx.direction.sentTo");
// //addressString = "";
// }
//
// if (optionalTradable.isPresent()) {
// tradable = optionalTradable.get();
// detailsAvailable = true;
// String tradeId = tradable.getShortId();
// if (tradable instanceof OpenOffer) {
// details = Res.get("funds.tx.createOfferFee", tradeId);
// } else if (tradable instanceof Trade) {
// Trade trade = (Trade) tradable;
// TransactionAwareTrade transactionAwareTrade = (TransactionAwareTrade) transactionAwareTradable;
// if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().equals(txId)) {
// details = Res.get("funds.tx.takeOfferFee", tradeId);
// } else {
// Offer offer = trade.getOffer();
// String offerFeePaymentTxID = offer.getOfferFeePaymentTxId();
// if (offerFeePaymentTxID != null && offerFeePaymentTxID.equals(txId)) {
// details = Res.get("funds.tx.createOfferFee", tradeId);
// } else if (trade.getDepositTx() != null &&
// trade.getDepositTx().getTxId().equals(Sha256Hash.wrap(txId))) {
// details = Res.get("funds.tx.multiSigDeposit", tradeId);
// } else if (trade.getPayoutTx() != null &&
// trade.getPayoutTx().getTxId().equals(Sha256Hash.wrap(txId))) {
// details = Res.get("funds.tx.multiSigPayout", tradeId);
//
// if (amountAsCoin.isZero()) {
// initialTxConfidenceVisibility = false;
// }
// } else {
// Trade.DisputeState disputeState = trade.getDisputeState();
// if (disputeState == Trade.DisputeState.DISPUTE_CLOSED) {
// if (valueSentToMe.isPositive()) {
// details = Res.get("funds.tx.disputePayout", tradeId);
// } else {
// details = Res.get("funds.tx.disputeLost", tradeId);
// initialTxConfidenceVisibility = false;
// }
// } else if (disputeState == Trade.DisputeState.REFUND_REQUEST_CLOSED ||
// disputeState == Trade.DisputeState.REFUND_REQUESTED ||
// disputeState == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
// if (valueSentToMe.isPositive()) {
// details = Res.get("funds.tx.refund", tradeId);
// } else {
// // We have spent the deposit tx outputs to the Bisq donation address to enable
// // the refund process (refund agent -> reimbursement). As the funds have left our wallet
// // already when funding the deposit tx we show 0 BTC as amount.
// // Confirmation is not known from the BitcoinJ side (not 100% clear why) as no funds
// // left our wallet nor we received funds. So we set indicator invisible.
// amountAsCoin = Coin.ZERO;
// details = Res.get("funds.tx.collateralForRefund", tradeId);
// initialTxConfidenceVisibility = false;
// }
// } else {
// if (transactionAwareTrade.isDelayedPayoutTx(txId)) {
// details = Res.get("funds.tx.timeLockedPayoutTx", tradeId);
// initialTxConfidenceVisibility = false;
// } else {
// details = Res.get("funds.tx.unknown", tradeId);
// }
// }
// }
// }
// }
// } else {
// if (amountAsCoin.isZero()) {
// details = Res.get("funds.tx.noFundsFromDispute");
// initialTxConfidenceVisibility = false;
// } else if (withdrawalFromBSQWallet) {
// details = Res.get("funds.tx.withdrawnFromBSQWallet");
// } else if (!txFeeForBsqPayment) {
// details = received ? Res.get("funds.tx.receivedFunds") : Res.get("funds.tx.withdrawnFromWallet");
// } else if (details.isEmpty()) {
// details = Res.get("funds.tx.txFeePaymentForBsqTx");
// }
// }
// // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
// date = transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime();
// dateString = DisplayUtils.formatDateTime(date);
//
// isDustAttackTx = received && valueSentToMe.value < ignoreDustThreshold;
// if (isDustAttackTx) {
// details = Res.get("funds.tx.dustAttackTx");
// }
//
// // confidence
// lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{
// txConfidenceIndicator = new TxConfidenceIndicator();
// txConfidenceIndicator.setId("funds-confidence");
// tooltip = new Tooltip(Res.get("shared.notUsedYet"));
// txConfidenceIndicator.setProgress(0);
// txConfidenceIndicator.setTooltip(tooltip);
// txConfidenceIndicator.setVisible(initialTxConfidenceVisibility);
//
// TransactionConfidence confidence = transaction.getConfidence();
// GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
// confirmations = confidence.getDepthInBlocks();
// }});
//
// txConfidenceListener = new TxConfidenceListener(txId) {
// @Override
// public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
// GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
// confirmations = confidence.getDepthInBlocks();
// }
// };
// btcWalletService.addTxConfidenceListener(txConfidenceListener);
}
public void cleanup() {
btcWalletService.removeTxConfidenceListener(txConfidenceListener);
// TODO (woodser): remove wallet listener
//xmrWalletService.removeTxConfidenceListener(txConfidenceListener);
}

View file

@ -19,26 +19,26 @@ package bisq.desktop.main.funds.withdrawal;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javafx.scene.control.Label;
import java.math.BigInteger;
import lombok.Getter;
import lombok.Setter;
class WithdrawalListItem {
private final BalanceListener balanceListener;
private final XmrBalanceListener balanceListener;
private final Label balanceLabel;
private final AddressEntry addressEntry;
private final BtcWalletService walletService;
private final XmrAddressEntry addressEntry;
private final XmrWalletService walletService;
private final CoinFormatter formatter;
private Coin balance;
private final String addressString;
@ -46,7 +46,7 @@ class WithdrawalListItem {
@Getter
private boolean isSelected;
public WithdrawalListItem(AddressEntry addressEntry, BtcWalletService walletService,
public WithdrawalListItem(XmrAddressEntry addressEntry, XmrWalletService walletService,
CoinFormatter formatter) {
this.addressEntry = addressEntry;
this.walletService = walletService;
@ -55,9 +55,9 @@ class WithdrawalListItem {
// balance
balanceLabel = new AutoTooltipLabel();
balanceListener = new BalanceListener(getAddress()) {
balanceListener = new XmrBalanceListener(addressEntry.getAccountIndex()) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
public void onBalanceChanged(BigInteger balance) {
updateBalance();
}
};
@ -71,7 +71,7 @@ class WithdrawalListItem {
}
private void updateBalance() {
balance = walletService.getBalanceForAddress(addressEntry.getAddress());
balance = walletService.getBalanceForAccount(addressEntry.getAccountIndex());
if (balance != null)
balanceLabel.setText(formatter.formatCoin(this.balance));
}
@ -81,7 +81,7 @@ class WithdrawalListItem {
return Res.getWithCol("shared.offerId") + " " + addressEntry.getShortOfferId();
else if (addressEntry.isTrade())
return Res.getWithCol("shared.tradeId") + " " + addressEntry.getShortOfferId();
else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR)
else if (addressEntry.getContext() == XmrAddressEntry.Context.ARBITRATOR)
return Res.get("funds.withdrawal.arbitrationFee");
else
return "-";
@ -102,11 +102,7 @@ class WithdrawalListItem {
return addressEntry.hashCode();
}
private Address getAddress() {
return addressEntry.getAddress();
}
public AddressEntry getAddressEntry() {
public XmrAddressEntry getAddressEntry() {
return addressEntry;
}

View file

@ -26,23 +26,16 @@ import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.TxDetails;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.exceptions.InsufficientFundsException;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.provider.fee.FeeService;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
@ -51,15 +44,11 @@ import bisq.core.util.validation.BtcAddressValidator;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
import bisq.common.util.Tuple3;
import bisq.common.util.Tuple4;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.wallet.Wallet;
import javax.inject.Inject;
import javax.inject.Named;
@ -101,18 +90,14 @@ import javafx.util.Callback;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import static bisq.desktop.util.FormBuilder.*;
import static com.google.common.base.Preconditions.checkNotNull;
@FxmlView
public class WithdrawalView extends ActivatableView<VBox, Void> {
@ -128,7 +113,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private Label amountLabel;
private TextField amountTextField, withdrawFromTextField, withdrawToTextField, withdrawMemoTextField, transactionFeeInputTextField;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final TradeManager tradeManager;
private final P2PService p2PService;
private final WalletsSetup walletsSetup;
@ -139,7 +124,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private final ObservableList<WithdrawalListItem> observableList = FXCollections.observableArrayList();
private final SortedList<WithdrawalListItem> sortedList = new SortedList<>(observableList);
private final Set<WithdrawalListItem> selectedItems = new HashSet<>();
private BalanceListener balanceListener;
private XmrBalanceListener balanceListener;
private Set<String> fromAddresses = new HashSet<>();
private Coin totalAvailableAmountOfSelectedItems = Coin.ZERO;
private Coin amountAsCoin = Coin.ZERO;
@ -160,7 +145,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private WithdrawalView(BtcWalletService btcWalletService,
private WithdrawalView(XmrWalletService xmrWalletService,
TradeManager tradeManager,
P2PService p2PService,
WalletsSetup walletsSetup,
@ -169,7 +154,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
BtcAddressValidator btcAddressValidator,
WalletPasswordWindow walletPasswordWindow,
FeeService feeService) {
this.btcWalletService = btcWalletService;
// throw new RuntimeException("WithdrawalView needs updated to use XMR wallet");
this.xmrWalletService = xmrWalletService;
this.tradeManager = tradeManager;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
@ -295,9 +281,9 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
balanceColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(balanceColumn);
balanceListener = new BalanceListener() {
balanceListener = new XmrBalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
public void onBalanceChanged(BigInteger balance) {
updateList();
}
};
@ -346,7 +332,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
amountTextField.textProperty().addListener(amountListener);
amountTextField.focusedProperty().addListener(amountFocusListener);
btcWalletService.addBalanceListener(balanceListener);
xmrWalletService.addBalanceListener(balanceListener);
feeToggleGroup.selectedToggleProperty().addListener(feeToggleGroupListener);
inputsToggleGroup.selectedToggleProperty().addListener(inputsToggleGroupListener);
@ -371,7 +357,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
protected void deactivate() {
sortedList.comparatorProperty().unbind();
observableList.forEach(WithdrawalListItem::cleanup);
btcWalletService.removeBalanceListener(balanceListener);
xmrWalletService.removeBalanceListener(balanceListener);
amountTextField.textProperty().removeListener(amountListener);
amountTextField.focusedProperty().removeListener(amountFocusListener);
feeToggleGroup.selectedToggleProperty().removeListener(feeToggleGroupListener);
@ -388,107 +374,108 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
private void onWithdraw() {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
try {
final String withdrawToAddress = withdrawToTextField.getText();
final Coin sendersAmount;
// We do not know sendersAmount if senderPaysFee is true. We repeat fee calculation after first attempt if senderPaysFee is true.
Transaction feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin);
if (feeExcluded && feeEstimationTransaction != null) {
feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin.add(feeEstimationTransaction.getFee()));
}
checkNotNull(feeEstimationTransaction, "feeEstimationTransaction must not be null");
Coin dust = btcWalletService.getDust(feeEstimationTransaction);
Coin fee = feeEstimationTransaction.getFee().add(dust);
Coin receiverAmount;
// amountAsCoin is what the user typed into the withdrawal field.
// this can be interpreted as either the senders amount or receivers amount depending
// on a radio button "fee excluded / fee included".
// therefore we calculate the actual sendersAmount and receiverAmount as follows:
if (feeExcluded) {
receiverAmount = amountAsCoin;
sendersAmount = receiverAmount.add(fee);
} else {
sendersAmount = amountAsCoin.add(dust); // sendersAmount bumped up to UTXO size when dust is in play
receiverAmount = sendersAmount.subtract(fee);
}
if (dust.isPositive()) {
log.info("Dust output ({} satoshi) was detected, the dust amount has been added to the fee (was {}, now {})",
dust.value,
feeEstimationTransaction.getFee(),
fee.value);
}
if (areInputsValid(sendersAmount)) {
int txVsize = feeEstimationTransaction.getVsize();
log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txVsize, fee.toPlainString());
if (receiverAmount.isPositive()) {
double vkb = txVsize / 1000d;
String messageText = Res.get("shared.sendFundsDetailsWithFee",
formatter.formatCoinWithCode(sendersAmount),
withdrawFromTextField.getText(),
withdrawToAddress,
formatter.formatCoinWithCode(fee),
Double.parseDouble(transactionFeeInputTextField.getText()),
vkb,
formatter.formatCoinWithCode(receiverAmount));
if (dust.isPositive()) {
messageText = Res.get("shared.sendFundsDetailsDust",
dust.value, dust.value > 1 ? "s" : "")
+ messageText;
}
new Popup().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest"))
.confirmation(messageText)
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> doWithdraw(sendersAmount, fee, new FutureCallback<>() {
@Override
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
if (transaction != null) {
String key = "showTransactionSent";
if (DontShowAgainLookup.showAgain(key)) {
new TxDetails(transaction.getTxId().toString(), withdrawToAddress, formatter.formatCoinWithCode(sendersAmount))
.dontShowAgainId(key)
.show();
}
log.debug("onWithdraw onSuccess tx ID:{}", transaction.getTxId().toString());
} else {
log.error("onWithdraw transaction is null");
}
List<Trade> trades = new ArrayList<>(tradeManager.getObservableList());
trades.stream()
.filter(Trade::isPayoutPublished)
.forEach(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT)
.ifPresent(addressEntry -> {
if (btcWalletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
tradeManager.onTradeCompleted(trade);
}));
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error("onWithdraw onFailure");
}
}))
.closeButtonText(Res.get("shared.cancel"))
.show();
} else {
new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show();
}
}
} catch (InsufficientFundsException e) {
new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show();
} catch (Throwable e) {
e.printStackTrace();
log.error(e.toString());
new Popup().warning(e.toString()).show();
}
}
throw new RuntimeException("WithdrawalView.onWithdraw() not updated to XMR");
// if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
// try {
// final String withdrawToAddress = withdrawToTextField.getText();
// final Coin sendersAmount;
//
// // We do not know sendersAmount if senderPaysFee is true. We repeat fee calculation after first attempt if senderPaysFee is true.
// Transaction feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin);
// if (feeExcluded && feeEstimationTransaction != null) {
// feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin.add(feeEstimationTransaction.getFee()));
// }
// checkNotNull(feeEstimationTransaction, "feeEstimationTransaction must not be null");
//
// Coin dust = btcWalletService.getDust(feeEstimationTransaction);
// Coin fee = feeEstimationTransaction.getFee().add(dust);
// Coin receiverAmount;
// // amountAsCoin is what the user typed into the withdrawal field.
// // this can be interpreted as either the senders amount or receivers amount depending
// // on a radio button "fee excluded / fee included".
// // therefore we calculate the actual sendersAmount and receiverAmount as follows:
// if (feeExcluded) {
// receiverAmount = amountAsCoin;
// sendersAmount = receiverAmount.add(fee);
// } else {
// sendersAmount = amountAsCoin.add(dust); // sendersAmount bumped up to UTXO size when dust is in play
// receiverAmount = sendersAmount.subtract(fee);
// }
// if (dust.isPositive()) {
// log.info("Dust output ({} satoshi) was detected, the dust amount has been added to the fee (was {}, now {})",
// dust.value,
// feeEstimationTransaction.getFee(),
// fee.value);
// }
//
// if (areInputsValid(sendersAmount)) {
// int txVsize = feeEstimationTransaction.getVsize();
// log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txVsize, fee.toPlainString());
//
// if (receiverAmount.isPositive()) {
// double vkb = txVsize / 1000d;
//
// String messageText = Res.get("shared.sendFundsDetailsWithFee",
// formatter.formatCoinWithCode(sendersAmount),
// withdrawFromTextField.getText(),
// withdrawToAddress,
// formatter.formatCoinWithCode(fee),
// Double.parseDouble(transactionFeeInputTextField.getText()),
// vkb,
// formatter.formatCoinWithCode(receiverAmount));
// if (dust.isPositive()) {
// messageText = Res.get("shared.sendFundsDetailsDust",
// dust.value, dust.value > 1 ? "s" : "")
// + messageText;
// }
//
// new Popup().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest"))
// .confirmation(messageText)
// .actionButtonText(Res.get("shared.yes"))
// .onAction(() -> doWithdraw(sendersAmount, fee, new FutureCallback<>() {
// @Override
// public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
// if (transaction != null) {
// String key = "showTransactionSent";
// if (DontShowAgainLookup.showAgain(key)) {
// new TxDetails(transaction.getTxId().toString(), withdrawToAddress, formatter.formatCoinWithCode(sendersAmount))
// .dontShowAgainId(key)
// .show();
// }
// log.debug("onWithdraw onSuccess tx ID:{}", transaction.getTxId().toString());
// } else {
// log.error("onWithdraw transaction is null");
// }
//
// List<Trade> trades = new ArrayList<>(tradeManager.getObservableList());
// trades.stream()
// .filter(Trade::isPayoutPublished)
// .forEach(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT)
// .ifPresent(addressEntry -> {
// if (btcWalletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
// tradeManager.onTradeCompleted(trade);
// }));
// }
//
// @Override
// public void onFailure(@NotNull Throwable t) {
// log.error("onWithdraw onFailure");
// }
// }))
// .closeButtonText(Res.get("shared.cancel"))
// .show();
// } else {
// new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show();
// }
// }
// } catch (InsufficientFundsException e) {
// new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show();
// } catch (Throwable e) {
// e.printStackTrace();
// log.error(e.toString());
// new Popup().warning(e.toString()).show();
// }
// }
}
private void selectForWithdrawal(WithdrawalListItem item) {
@ -546,55 +533,58 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
//throw new RuntimeException("WithdrawalView.updateList() needs updated to use XMR");
observableList.forEach(WithdrawalListItem::cleanup);
observableList.setAll(btcWalletService.getAddressEntriesForAvailableBalanceStream()
.map(addressEntry -> new WithdrawalListItem(addressEntry, btcWalletService, formatter))
observableList.setAll(xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.map(addressEntry -> new WithdrawalListItem(addressEntry, xmrWalletService, formatter))
.collect(Collectors.toList()));
updateInputSelection();
}
private void doWithdraw(Coin amount, Coin fee, FutureCallback<Transaction> callback) {
if (btcWalletService.isEncrypted()) {
UserThread.runAfter(() -> walletPasswordWindow.onAesKey(aesKey ->
sendFunds(amount, fee, aesKey, callback))
.show(), 300, TimeUnit.MILLISECONDS);
} else {
sendFunds(amount, fee, null, callback);
}
throw new RuntimeException("WithdrawalView.doWithdraw() not updated to XMR");
// if (xmrWalletService.isEncrypted()) {
// UserThread.runAfter(() -> walletPasswordWindow.onAesKey(aesKey ->
// sendFunds(amount, fee, aesKey, callback))
// .show(), 300, TimeUnit.MILLISECONDS);
// } else {
// sendFunds(amount, fee, null, callback);
// }
}
private void sendFunds(Coin amount, Coin fee, KeyParameter aesKey, FutureCallback<Transaction> callback) {
try {
String memo = withdrawMemoTextField.getText();
if (memo.isEmpty()) {
memo = null;
}
Transaction transaction = btcWalletService.sendFundsForMultipleAddresses(fromAddresses,
withdrawToTextField.getText(),
amount,
fee,
null,
aesKey,
memo,
callback);
reset();
updateList();
} catch (AddressFormatException e) {
new Popup().warning(Res.get("validation.btc.invalidAddress")).show();
} catch (Wallet.DustySendRequested e) {
new Popup().warning(Res.get("validation.amountBelowDust",
formatter.formatCoinWithCode(Restrictions.getMinNonDustOutput()))).show();
} catch (AddressEntryException e) {
new Popup().error(e.getMessage()).show();
} catch (InsufficientMoneyException e) {
log.warn(e.getMessage());
new Popup().warning(Res.get("funds.withdrawal.notEnoughFunds") + "\n\nError message:\n" + e.getMessage()).show();
} catch (Throwable e) {
log.warn(e.toString());
new Popup().warning(e.toString()).show();
}
throw new RuntimeException("WithdrawalView.sendFunds() not updated to XMR");
// try {
// String memo = withdrawMemoTextField.getText();
// if (memo.isEmpty()) {
// memo = null;
// }
// Transaction transaction = btcWalletService.sendFundsForMultipleAddresses(fromAddresses,
// withdrawToTextField.getText(),
// amount,
// fee,
// null,
// aesKey,
// memo,
// callback);
//
// reset();
// updateList();
// } catch (AddressFormatException e) {
// new Popup().warning(Res.get("validation.btc.invalidAddress")).show();
// } catch (Wallet.DustySendRequested e) {
// new Popup().warning(Res.get("validation.amountBelowDust",
// formatter.formatCoinWithCode(Restrictions.getMinNonDustOutput()))).show();
// } catch (AddressEntryException e) {
// new Popup().error(e.getMessage()).show();
// } catch (InsufficientMoneyException e) {
// log.warn(e.getMessage());
// new Popup().warning(Res.get("funds.withdrawal.notEnoughFunds") + "\n\nError message:\n" + e.getMessage()).show();
// } catch (Throwable e) {
// log.warn(e.toString());
// new Popup().warning(e.toString()).show();
// }
}
private void reset() {

View file

@ -23,12 +23,12 @@ import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.TxFeeEstimationService;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.TradeCurrency;
import bisq.core.monetary.Price;
@ -58,7 +58,6 @@ import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import com.google.inject.Inject;
@ -81,6 +80,8 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
@ -98,6 +99,7 @@ import static java.util.Comparator.comparing;
public abstract class MutableOfferDataModel extends OfferDataModel implements BsqBalanceListener {
private final CreateOfferService createOfferService;
protected final OpenOfferManager openOfferManager;
private final XmrWalletService xmrWalletService;
private final BsqWalletService bsqWalletService;
private final Preferences preferences;
protected final User user;
@ -109,7 +111,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
private final CoinFormatter btcFormatter;
private final Navigation navigation;
private final String offerId;
private final BalanceListener btcBalanceListener;
private final XmrBalanceListener xmrBalanceListener;
private final SetChangeListener<PaymentAccount> paymentAccountsChangeListener;
protected OfferPayload.Direction direction;
@ -152,7 +154,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
public MutableOfferDataModel(CreateOfferService createOfferService,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
Preferences preferences,
User user,
@ -163,8 +165,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
TradeStatisticsManager tradeStatisticsManager,
Navigation navigation) {
super(btcWalletService, offerUtil);
super(xmrWalletService, offerUtil);
this.xmrWalletService = xmrWalletService;
this.createOfferService = createOfferService;
this.openOfferManager = openOfferManager;
this.bsqWalletService = bsqWalletService;
@ -180,14 +183,14 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
offerId = createOfferService.getRandomOfferId();
shortOfferId = Utilities.getShortId(offerId);
addressEntry = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
addressEntry = xmrWalletService.getOrCreateAddressEntry(offerId, XmrAddressEntry.Context.OFFER_FUNDING);
useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice());
buyerSecurityDeposit.set(Restrictions.getMinBuyerSecurityDepositAsPercent());
btcBalanceListener = new BalanceListener(getAddressEntry().getAddress()) {
xmrBalanceListener = new XmrBalanceListener(getAddressEntry().getAccountIndex()) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
public void onBalanceChanged(BigInteger balance) {
updateBalance();
}
};
@ -211,13 +214,13 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
}
private void addListeners() {
btcWalletService.addBalanceListener(btcBalanceListener);
xmrWalletService.addBalanceListener(xmrBalanceListener);
bsqWalletService.addBsqBalanceListener(this);
user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener);
}
private void removeListeners() {
btcWalletService.removeBalanceListener(btcBalanceListener);
xmrWalletService.removeBalanceListener(xmrBalanceListener);
bsqWalletService.removeBsqBalanceListener(this);
user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener);
}
@ -482,7 +485,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
return direction == OfferPayload.Direction.BUY;
}
AddressEntry getAddressEntry() {
XmrAddressEntry getAddressEntry() {
return addressEntry;
}
@ -618,7 +621,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
}
void swapTradeToSavings() {
btcWalletService.resetAddressEntriesForOpenOffer(offerId);
xmrWalletService.resetAddressEntriesForOpenOffer(offerId);
}
private void fillPaymentAccounts() {

View file

@ -1282,8 +1282,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
@NotNull
private String getBitcoinURI() {
return GUIUtil.getBitcoinURI(addressTextField.getAddress(), model.getDataModel().getMissingCoin().get(),
model.getPaymentLabel());
return "TODO"; // TODO (woodser): wallet.createPaymentUri();
// return GUIUtil.getBitcoinURI(addressTextField.getAddress(), model.getDataModel().getMissingCoin().get(),
// model.getPaymentLabel());
}
private void addAmountPriceFields() {

View file

@ -19,8 +19,8 @@ package bisq.desktop.main.offer;
import bisq.desktop.common.model.ActivatableDataModel;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OfferUtil;
import org.bitcoinj.core.Coin;
@ -41,7 +41,7 @@ import static bisq.core.util.coin.CoinUtil.minCoin;
* needed in that UI element.
*/
public abstract class OfferDataModel extends ActivatableDataModel {
protected final BtcWalletService btcWalletService;
protected final XmrWalletService xmrWalletService;
protected final OfferUtil offerUtil;
@Getter
@ -56,18 +56,18 @@ public abstract class OfferDataModel extends ActivatableDataModel {
protected final BooleanProperty showWalletFundedNotification = new SimpleBooleanProperty();
@Getter
protected Coin totalAvailableBalance;
protected AddressEntry addressEntry;
protected XmrAddressEntry addressEntry;
protected boolean useSavingsWallet;
public OfferDataModel(BtcWalletService btcWalletService, OfferUtil offerUtil) {
this.btcWalletService = btcWalletService;
public OfferDataModel(XmrWalletService xmrWalletService, OfferUtil offerUtil) {
this.xmrWalletService = xmrWalletService;
this.offerUtil = offerUtil;
}
protected void updateBalance() {
Coin tradeWalletBalance = btcWalletService.getBalanceForAddress(addressEntry.getAddress());
Coin tradeWalletBalance = xmrWalletService.getBalanceForAccount(addressEntry.getAccountIndex());
if (useSavingsWallet) {
Coin savingWalletBalance = btcWalletService.getSavingWalletBalance();
Coin savingWalletBalance = xmrWalletService.getSavingWalletBalance();
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
if (totalToPayAsCoin.get() != null) {
balance.set(minCoin(totalToPayAsCoin.get(), totalAvailableBalance));

View file

@ -26,7 +26,7 @@ import bisq.desktop.main.offer.MutableOfferDataModel;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOfferManager;
@ -55,7 +55,7 @@ class CreateOfferDataModel extends MutableOfferDataModel {
public CreateOfferDataModel(CreateOfferService createOfferService,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
Preferences preferences,
User user,
@ -69,7 +69,7 @@ class CreateOfferDataModel extends MutableOfferDataModel {
super(createOfferService,
openOfferManager,
offerUtil,
btcWalletService,
xmrWalletService,
bsqWalletService,
preferences,
user,

View file

@ -25,11 +25,11 @@ import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.TxFeeEstimationService;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
@ -56,8 +56,6 @@ import bisq.network.p2p.P2PService;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.wallet.Wallet;
import com.google.inject.Inject;
@ -69,6 +67,8 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import java.math.BigInteger;
import java.util.Set;
import lombok.Getter;
@ -111,7 +111,7 @@ class TakeOfferDataModel extends OfferDataModel {
private final ObjectProperty<Coin> amount = new SimpleObjectProperty<>();
final ObjectProperty<Volume> volume = new SimpleObjectProperty<>();
private BalanceListener balanceListener;
private XmrBalanceListener balanceListener;
private PaymentAccount paymentAccount;
private boolean isTabSelected;
Price tradePrice;
@ -134,7 +134,7 @@ class TakeOfferDataModel extends OfferDataModel {
TakeOfferDataModel(TradeManager tradeManager,
OfferBook offerBook,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
User user, FeeService feeService,
MempoolService mempoolService,
@ -146,7 +146,7 @@ class TakeOfferDataModel extends OfferDataModel {
Navigation navigation,
P2PService p2PService
) {
super(btcWalletService, offerUtil);
super(xmrWalletService, offerUtil);
this.tradeManager = tradeManager;
this.offerBook = offerBook;
@ -207,7 +207,7 @@ class TakeOfferDataModel extends OfferDataModel {
void initWithData(Offer offer) {
this.offer = offer;
tradePrice = offer.getPrice();
addressEntry = btcWalletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING);
addressEntry = xmrWalletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
checkNotNull(addressEntry, "addressEntry must not be null");
ObservableList<PaymentAccount> possiblePaymentAccounts = getPossiblePaymentAccounts();
@ -265,11 +265,40 @@ class TakeOfferDataModel extends OfferDataModel {
calculateVolume();
calculateTotalToPay();
balanceListener = new BalanceListener(addressEntry.getAddress()) {
balanceListener = new XmrBalanceListener(addressEntry.getAccountIndex()) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
public void onBalanceChanged(BigInteger balance) {
updateBalance();
}
// public void onBalanceChanged(Coin balance, Transaction tx) {
// updateBalance();
//
// /*if (isMainNet.get()) {
// SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
// Futures.addCallback(future, new FutureCallback<Coin>() {
// public void onSuccess(Coin fee) {
// UserThread.execute(() -> setFeeFromFundingTx(fee));
// }
//
// public void onFailure(@NotNull Throwable throwable) {
// UserThread.execute(() -> new Popup<>()
// .warning("We did not get a response for the request of the mining fee used " +
// "in the funding transaction.\n\n" +
// "Are you sure you used a sufficiently high fee of at least " +
// formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?")
// .actionButtonText("Yes, I used a sufficiently high fee.")
// .onAction(() -> setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx()))
// .closeButtonText("No. Let's cancel that payment.")
// .onClose(() -> setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI))
// .show());
// }
// });
// } else {
// setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx());
// isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0);
// }*/
// }
};
offer.resetState();
@ -301,7 +330,7 @@ class TakeOfferDataModel extends OfferDataModel {
offerBook.removeOffer(checkNotNull(offer), tradeManager);
}
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
//xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId()); // TODO (woodser): this removes address entries for reserved trades before completion. how doesn't this delete the multisig address entry in bisq before completion?
}
@ -335,7 +364,6 @@ class TakeOfferDataModel extends OfferDataModel {
tradeManager.onTakeOffer(amount.get(),
txFeeFromFeeService,
getTakerFee(),
isCurrencyForTakerFeeBtc(),
tradePrice.getValue(),
fundsNeededForTrade,
offer,
@ -363,7 +391,7 @@ class TakeOfferDataModel extends OfferDataModel {
// So that would require more thoughts how to deal with all those cases.
public void estimateTxVsize() {
int txVsize = 0;
if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) {
if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(new BigInteger("0")) > 0) {
Coin fundsNeededForTrade = getFundsNeededForTrade();
if (isBuyOffer())
fundsNeededForTrade = fundsNeededForTrade.add(amount.get());
@ -469,11 +497,11 @@ class TakeOfferDataModel extends OfferDataModel {
///////////////////////////////////////////////////////////////////////////////////////////
private void addListeners() {
btcWalletService.addBalanceListener(balanceListener);
xmrWalletService.addBalanceListener(balanceListener);
}
private void removeListeners() {
btcWalletService.removeBalanceListener(balanceListener);
xmrWalletService.removeBalanceListener(balanceListener);
}
@ -554,7 +582,7 @@ class TakeOfferDataModel extends OfferDataModel {
public void swapTradeToSavings() {
log.debug("swapTradeToSavings, offerId={}", offer.getId());
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
}
// We use the sum of the vsize of the trade fee and the deposit tx to get an average.
@ -648,7 +676,7 @@ class TakeOfferDataModel extends OfferDataModel {
return txFeeFromFeeService; //feeService.getTxFee(169);
}
public AddressEntry getAddressEntry() {
public XmrAddressEntry getAddressEntry() {
return addressEntry;
}

View file

@ -1086,9 +1086,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
@NotNull
private String getBitcoinURI() {
return GUIUtil.getBitcoinURI(model.dataModel.getAddressEntry().getAddressString(),
model.dataModel.getMissingCoin().get(),
model.getPaymentLabel());
return "TODO";
// return GUIUtil.getBitcoinURI(model.dataModel.getAddressEntry().getAddressString(),
// model.dataModel.getMissingCoin().get(),
// model.getPaymentLabel());
}
private void addAmountPriceFields() {

View file

@ -233,11 +233,11 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
showTransactionPublishedScreen.set(false);
dataModel.onTakeOffer(trade -> {
this.trade = trade;
takeOfferCompleted.set(true);
trade.stateProperty().addListener(tradeStateListener);
applyTradeState();
trade.errorMessageProperty().addListener(tradeErrorListener);
applyTradeErrorMessage(trade.getErrorMessage());
takeOfferCompleted.set(true);
});
updateButtonDisableState();
@ -448,13 +448,16 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
}
private void applyTradeState() {
if (trade.isDepositPublished()) {
if (trade.getDepositTx() != null) {
if (trade.isTakerFeePublished()) {
if (trade.getTakerFeeTxId() != null) {
if (takeOfferSucceededHandler != null)
takeOfferSucceededHandler.run();
showTransactionPublishedScreen.set(true);
updateSpinnerInfo();
} else {
final String msg = "trade.getTakerFeeTxId() must not be null.";
DevEnv.logErrorAndThrowIfDevMode(msg);
}
}
}

View file

@ -33,7 +33,6 @@ import bisq.core.payment.payload.PaymentMethod;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.agent.DisputeAgentLookupMap;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager;
@ -201,15 +200,17 @@ public class ContractWindow extends Overlay<ContractWindow> {
title = Res.get("shared.selectedArbitrator");
break;
case MEDIATION:
agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress());
title = Res.get("shared.selectedMediator");
break;
throw new RuntimeException("Mediation type not adapted to XMR");
// agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress());
// title = Res.get("shared.selectedMediator");
// break;
case TRADE:
break;
case REFUND:
agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress());
title = Res.get("shared.selectedRefundAgent");
break;
throw new RuntimeException("Refund type not adapted to XMR");
//agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress());
//title = Res.get("shared.selectedRefundAgent");
//break;
}
}
@ -247,7 +248,7 @@ public class ContractWindow extends Overlay<ContractWindow> {
}
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.makerFeeTxId"), offer.getOfferFeePaymentTxId());
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), contract.getTakerFeeTxID());
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), "TAKER FEE TX ID NOT PART OF CONTRACT"); // TODO (woodser): should taker fee tx id be part of contract?
if (dispute.getDepositTxSerialized() != null)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), dispute.getDepositTxId());
@ -273,8 +274,6 @@ public class ContractWindow extends Overlay<ContractWindow> {
viewContractButton.setOnAction(e -> {
TextArea textArea = new BisqTextArea();
String contractAsJson = dispute.getContractAsJson();
contractAsJson += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey());
contractAsJson += "\nSellerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getSellerMultiSigPubKey());
textArea.setText(contractAsJson);
textArea.setPrefHeight(50);
textArea.setEditable(false);

View file

@ -23,35 +23,25 @@ import bisq.desktop.components.BisqTextArea;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.support.dispute.DisputeSummaryVerification;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.Layout;
import bisq.core.btc.TxFeeEstimationService;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.dao.DaoFacade;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.provider.mempool.MempoolService;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Contract;
import bisq.core.trade.TradeDataValidation;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
@ -59,12 +49,11 @@ import bisq.common.handlers.ResultHandler;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import javafx.scene.Scene;
import javafx.scene.control.Button;
@ -85,28 +74,30 @@ import javafx.geometry.Insets;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import static bisq.desktop.util.FormBuilder.*;
import static bisq.desktop.util.FormBuilder.add2ButtonsWithBox;
import static bisq.desktop.util.FormBuilder.addConfirmationLabelLabel;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
@Slf4j
public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
private final CoinFormatter formatter;
private final ArbitrationManager arbitrationManager;
private final MediationManager mediationManager;
private final RefundManager refundManager;
private final TradeWalletService tradeWalletService;
private final BtcWalletService btcWalletService;
private final TxFeeEstimationService txFeeEstimationService;
private final MempoolService mempoolService;
private final DaoFacade daoFacade;
private final XmrWalletService walletService;
private final TradeWalletService tradeWalletService; // TODO (woodser): remove for xmr or adapt to get/create multisig wallets for tx creation utils
private Dispute dispute;
private Optional<Runnable> finalizeDisputeHandlerOptional = Optional.empty();
private ToggleGroup tradeAmountToggleGroup, reasonToggleGroup;
@ -122,13 +113,12 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
// Dispute object of other trade peer. The dispute field is the one from which we opened the close dispute window.
private Optional<Dispute> peersDisputeOptional;
private String role;
private Label delayedPayoutTxStatus;
private TextArea summaryNotesTextArea;
private ChangeListener<Boolean> customRadioButtonSelectedListener;
private ChangeListener<Toggle> reasonToggleSelectionListener;
private InputTextField buyerPayoutAmountInputTextField, sellerPayoutAmountInputTextField;
private ChangeListener<Boolean> buyerPayoutAmountListener, sellerPayoutAmountListener;
private ChangeListener<String> buyerPayoutAmountListener, sellerPayoutAmountListener;
private CheckBox isLoserPublisherCheckBox;
private ChangeListener<Toggle> tradeAmountToggleGroupListener;
@ -139,22 +129,16 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
@Inject
public DisputeSummaryWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
ArbitrationManager arbitrationManager,
MediationManager mediationManager,
RefundManager refundManager,
TradeWalletService tradeWalletService,
BtcWalletService btcWalletService,
TxFeeEstimationService txFeeEstimationService,
MempoolService mempoolService,
DaoFacade daoFacade) {
XmrWalletService walletService,
TradeWalletService tradeWalletService) {
this.formatter = formatter;
this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
this.refundManager = refundManager;
this.walletService = walletService;
this.tradeWalletService = tradeWalletService;
this.btcWalletService = btcWalletService;
this.txFeeEstimationService = txFeeEstimationService;
this.mempoolService = mempoolService;
this.daoFacade = daoFacade;
type = Type.Confirmation;
}
@ -166,7 +150,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
width = 1150;
createGridPane();
addContent();
checkDelayedPayoutTransaction();
display();
if (DevEnv.isDevMode()) {
@ -176,6 +159,12 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
}
}
public DisputeSummaryWindow onFinalizeDispute(Runnable finalizeDisputeHandler) {
this.finalizeDisputeHandlerOptional = Optional.of(finalizeDisputeHandler);
return this;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@ -288,14 +277,24 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
addConfirmationLabelLabel(gridPane, rowIndex, Res.get("shared.tradeId"), dispute.getShortTradeId(),
Layout.TWICE_FIRST_ROW_DISTANCE);
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.openDate"), DisplayUtils.formatDateTime(dispute.getOpeningDate()));
role = dispute.getRoleString();
if (dispute.isDisputeOpenerIsMaker()) {
if (dispute.isDisputeOpenerIsBuyer())
role = Res.get("support.buyerOfferer");
else
role = Res.get("support.sellerOfferer");
} else {
if (dispute.isDisputeOpenerIsBuyer())
role = Res.get("support.buyerTaker");
else
role = Res.get("support.sellerTaker");
}
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.role"), role);
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradeAmount"),
formatter.formatCoinWithCode(contract.getTradeAmount()));
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"),
FormattingUtils.formatPrice(contract.getTradePrice()));
FormattingUtils.formatPrice(contract.getTradePrice()));
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradeVolume"),
DisplayUtils.formatVolumeWithCode(contract.getTradeVolume()));
DisplayUtils.formatVolumeWithCode(contract.getTradeVolume()));
String securityDeposit = Res.getWithColAndCap("shared.buyer") +
" " +
formatter.formatCoinWithCode(contract.getOfferPayload().getBuyerSecurityDeposit()) +
@ -304,26 +303,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
" " +
formatter.formatCoinWithCode(contract.getOfferPayload().getSellerSecurityDeposit());
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), securityDeposit);
boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager;
if (isMediationDispute) {
if (dispute.getTradePeriodEnd().getTime() > 0) {
String status = DisplayUtils.formatDateTime(dispute.getTradePeriodEnd());
Label tradePeriodEnd = addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.tradePeriodEnd"), status).second;
if (dispute.getTradePeriodEnd().toInstant().isAfter(Instant.now())) {
tradePeriodEnd.getStyleClass().add("alert"); // highlight field when the trade period is still active
}
}
if (dispute.getExtraDataMap() != null && dispute.getExtraDataMap().size() > 0) {
String extraDataSummary = "";
for (Map.Entry<String, String> entry : dispute.getExtraDataMap().entrySet()) {
extraDataSummary += "[" + entry.getKey() + ":" + entry.getValue() + "] ";
}
addConfirmationLabelLabelWithCopyIcon(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.extraInfo"), extraDataSummary);
}
} else {
delayedPayoutTxStatus = addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.delayedPayoutStatus"), "Checking...").second;
}
}
private void addTradeAmountPayoutControls() {
@ -355,16 +334,16 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
tradeAmountToggleGroupListener = (observable, oldValue, newValue) -> applyPayoutAmounts(newValue);
tradeAmountToggleGroup.selectedToggleProperty().addListener(tradeAmountToggleGroupListener);
buyerPayoutAmountListener = (observable, oldValue, newValue) -> applyCustomAmounts(buyerPayoutAmountInputTextField, oldValue, newValue);
sellerPayoutAmountListener = (observable, oldValue, newValue) -> applyCustomAmounts(sellerPayoutAmountInputTextField, oldValue, newValue);
buyerPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(buyerPayoutAmountInputTextField);
sellerPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(sellerPayoutAmountInputTextField);
customRadioButtonSelectedListener = (observable, oldValue, newValue) -> {
buyerPayoutAmountInputTextField.setEditable(newValue);
sellerPayoutAmountInputTextField.setEditable(newValue);
if (newValue) {
buyerPayoutAmountInputTextField.focusedProperty().addListener(buyerPayoutAmountListener);
sellerPayoutAmountInputTextField.focusedProperty().addListener(sellerPayoutAmountListener);
buyerPayoutAmountInputTextField.textProperty().addListener(buyerPayoutAmountListener);
sellerPayoutAmountInputTextField.textProperty().addListener(sellerPayoutAmountListener);
} else {
removePayoutAmountListeners();
}
@ -374,10 +353,10 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
private void removePayoutAmountListeners() {
if (buyerPayoutAmountInputTextField != null && buyerPayoutAmountListener != null)
buyerPayoutAmountInputTextField.focusedProperty().removeListener(buyerPayoutAmountListener);
buyerPayoutAmountInputTextField.textProperty().removeListener(buyerPayoutAmountListener);
if (sellerPayoutAmountInputTextField != null && sellerPayoutAmountListener != null)
sellerPayoutAmountInputTextField.focusedProperty().removeListener(sellerPayoutAmountListener);
sellerPayoutAmountInputTextField.textProperty().removeListener(sellerPayoutAmountListener);
}
@ -405,66 +384,36 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
}
}
private void applyCustomAmounts(InputTextField inputTextField, boolean oldFocusValue, boolean newFocusValue) {
// We only apply adjustments at focus out, otherwise we cannot enter certain values if we update at each
// keystroke.
if (!oldFocusValue || newFocusValue) {
return;
}
private void applyCustomAmounts(InputTextField inputTextField) {
// // We only apply adjustments at focus out, otherwise we cannot enter certain values if we update at each
// // keystroke.
// if (!oldFocusValue || newFocusValue) {
// return;
// }
Contract contract = dispute.getContract();
boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager;
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
// mediated payout. For Refund agent cases we do not have that restriction.
Coin minRefundAtDispute = isMediationDispute ? Restrictions.getMinRefundAtMediatedDispute() : Coin.ZERO;
Offer offer = new Offer(contract.getOfferPayload());
Coin totalAvailable = contract.getTradeAmount()
Coin available = contract.getTradeAmount()
.add(offer.getBuyerSecurityDeposit())
.add(offer.getSellerSecurityDeposit());
Coin availableForPayout = totalAvailable.subtract(minRefundAtDispute);
Coin enteredAmount = ParsingUtils.parseToCoin(inputTextField.getText(), formatter);
if (enteredAmount.compareTo(minRefundAtDispute) < 0) {
enteredAmount = minRefundAtDispute;
inputTextField.setText(formatter.formatCoin(enteredAmount));
if (enteredAmount.compareTo(available) > 0) {
enteredAmount = available;
Coin finalEnteredAmount = enteredAmount;
inputTextField.setText(formatter.formatCoin(finalEnteredAmount));
}
if (enteredAmount.isPositive() && !Restrictions.isAboveDust(enteredAmount)) {
enteredAmount = Restrictions.getMinNonDustOutput();
inputTextField.setText(formatter.formatCoin(enteredAmount));
}
if (enteredAmount.compareTo(availableForPayout) > 0) {
enteredAmount = availableForPayout;
inputTextField.setText(formatter.formatCoin(enteredAmount));
}
Coin counterPartAsCoin = totalAvailable.subtract(enteredAmount);
Coin counterPartAsCoin = available.subtract(enteredAmount);
String formattedCounterPartAmount = formatter.formatCoin(counterPartAsCoin);
Coin buyerAmount;
Coin sellerAmount;
if (inputTextField == buyerPayoutAmountInputTextField) {
buyerAmount = enteredAmount;
sellerAmount = counterPartAsCoin;
Coin sellerAmountFromField = ParsingUtils.parseToCoin(sellerPayoutAmountInputTextField.getText(), formatter);
Coin totalAmountFromFields = enteredAmount.add(sellerAmountFromField);
// RefundAgent can enter less then available
if (isMediationDispute ||
totalAmountFromFields.compareTo(totalAvailable) > 0) {
sellerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
} else {
sellerAmount = sellerAmountFromField;
}
sellerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
} else {
sellerAmount = enteredAmount;
buyerAmount = counterPartAsCoin;
Coin buyerAmountFromField = ParsingUtils.parseToCoin(buyerPayoutAmountInputTextField.getText(), formatter);
Coin totalAmountFromFields = enteredAmount.add(buyerAmountFromField);
// RefundAgent can enter less then available
if (isMediationDispute ||
totalAmountFromFields.compareTo(totalAvailable) > 0) {
buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
} else {
buyerAmount = buyerAmountFromField;
}
buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
}
disputeResult.setBuyerPayoutAmount(buyerAmount);
@ -632,100 +581,138 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
}
private void addButtons(Contract contract) {
Tuple3<Button, Button, HBox> tuple = add2ButtonsWithBox(gridPane, ++rowIndex,
Res.get("disputeSummaryWindow.close.button"),
Res.get("shared.cancel"), 15, true);
Button closeTicketButton = tuple.first;
closeTicketButton.disableProperty().bind(Bindings.createBooleanBinding(
() -> tradeAmountToggleGroup.getSelectedToggle() == null
|| summaryNotesTextArea.getText() == null
|| summaryNotesTextArea.getText().length() == 0
|| !isPayoutAmountValid(),
tradeAmountToggleGroup.selectedToggleProperty(),
summaryNotesTextArea.textProperty(),
buyerPayoutAmountInputTextField.textProperty(),
sellerPayoutAmountInputTextField.textProperty()));
Tuple3<Button, Button, HBox> tuple = add2ButtonsWithBox(gridPane, ++rowIndex,
Res.get("disputeSummaryWindow.close.button"),
Res.get("shared.cancel"), 15, true);
Button closeTicketButton = tuple.first;
closeTicketButton.disableProperty().bind(Bindings.createBooleanBinding(
() -> tradeAmountToggleGroup.getSelectedToggle() == null
|| summaryNotesTextArea.getText() == null
|| summaryNotesTextArea.getText().length() == 0
|| !isPayoutAmountValid(),
tradeAmountToggleGroup.selectedToggleProperty(),
summaryNotesTextArea.textProperty(),
buyerPayoutAmountInputTextField.textProperty(),
sellerPayoutAmountInputTextField.textProperty()));
Button cancelButton = tuple.second;
Button cancelButton = tuple.second;
closeTicketButton.setOnAction(e -> {
if (dispute.getDepositTxSerialized() == null) {
log.warn("dispute.getDepositTxSerialized is null");
closeTicketButton.setOnAction(e -> {
// TODO (woodser): create disputed payout tx after showing payout tx confirmation, within doCloseIfValid() (see upstream/master)
if (!dispute.isMediationDispute()) {
try {
System.out.println(disputeResult);
XmrAddressEntry arbitratorAddressEntry = walletService.getArbitratorAddressEntry();
MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(dispute.getTradeId());
System.out.println("Arbitrator payout address entry: " + arbitratorAddressEntry.getAddressString());
//dispute.getContract().getArbitratorPubKeyRing(); // TODO: support arbitrator pub key ring in contract?
//disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey());
// TODO (woodser): don't send signed tx if opener is not co-signer?
// // determine if opener is co-signer
// boolean openerIsWinner = (contract.getBuyerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == Winner.BUYER) || (contract.getSellerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == Winner.SELLER);
// boolean openerIsCosigner = openerIsWinner || disputeResult.isLoserPublisher();
// if (!openerIsCosigner) throw new RuntimeException("Need to query non-opener for updated multisig hex before creating tx");
// arbitrator creates and signs dispute payout tx if dispute is in context of opener, otherwise opener's peer must request payout tx by providing updated multisig hex
boolean isOpener = dispute.isOpener();
System.out.println("Is dispute opener: " + isOpener);
if (isOpener) {
MoneroTxWallet arbitratorPayoutTx = ArbitrationManager.arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet);
System.out.println("Created arbitrator-signed payout tx: " + arbitratorPayoutTx);
if (arbitratorPayoutTx != null) disputeResult.setArbitratorSignedPayoutTxHex(arbitratorPayoutTx.getTxSet().getMultisigTxHex());
}
// send arbitrator's updated multisig hex with dispute result
disputeResult.setArbitratorUpdatedMultisigHex(multisigWallet.getMultisigHex());
} catch (AddressFormatException e2) {
log.error("Error at close dispute", e2);
return;
}
}
if (dispute.getSupportType() == SupportType.REFUND &&
peersDisputeOptional.isPresent() &&
!peersDisputeOptional.get().isClosed()) {
showPayoutTxConfirmation(contract,
disputeResult,
() -> doCloseIfValid(closeTicketButton));
} else {
doCloseIfValid(closeTicketButton);
}
});
// // TODO (woodser): handle with showPayoutTxConfirmation() / doCloseIfValid() in order to have confirmation window (see upstream/master)
doClose(closeTicketButton);
cancelButton.setOnAction(e -> {
dispute.setDisputeResult(disputeResult);
checkNotNull(getDisputeManager(dispute)).requestPersistence();
hide();
});
// if (dispute.getDepositTxSerialized() == null) {
// log.warn("dispute.getDepositTxSerialized is null");
// return;
// }
//
// if (dispute.getSupportType() == SupportType.REFUND &&
// peersDisputeOptional.isPresent() &&
// !peersDisputeOptional.get().isClosed()) {
// showPayoutTxConfirmation(contract,
// disputeResult,
// () -> doCloseIfValid(closeTicketButton));
// } else {
// doCloseIfValid(closeTicketButton);
// }
});
cancelButton.setOnAction(e -> {
dispute.setDisputeResult(disputeResult);
checkNotNull(getDisputeManager(dispute)).requestPersistence();
hide();
});
}
private void showPayoutTxConfirmation(Contract contract, DisputeResult disputeResult, ResultHandler resultHandler) {
Coin buyerPayoutAmount = disputeResult.getBuyerPayoutAmount();
String buyerPayoutAddressString = contract.getBuyerPayoutAddressString();
Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount();
String sellerPayoutAddressString = contract.getSellerPayoutAddressString();
Coin outputAmount = buyerPayoutAmount.add(sellerPayoutAmount);
Tuple2<Coin, Integer> feeTuple = txFeeEstimationService.getEstimatedFeeAndTxVsize(outputAmount, btcWalletService);
Coin fee = feeTuple.first;
Integer txVsize = feeTuple.second;
double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);
double vkb = txVsize / 1000d;
Coin inputAmount = outputAmount.add(fee);
String buyerDetails = "";
if (buyerPayoutAmount.isPositive()) {
buyerDetails = Res.get("disputeSummaryWindow.close.txDetails.buyer",
formatter.formatCoinWithCode(buyerPayoutAmount),
buyerPayoutAddressString);
}
String sellerDetails = "";
if (sellerPayoutAmount.isPositive()) {
sellerDetails = Res.get("disputeSummaryWindow.close.txDetails.seller",
formatter.formatCoinWithCode(sellerPayoutAmount),
sellerPayoutAddressString);
}
if (outputAmount.isPositive()) {
new Popup().width(900)
.headLine(Res.get("disputeSummaryWindow.close.txDetails.headline"))
.confirmation(Res.get("disputeSummaryWindow.close.txDetails",
formatter.formatCoinWithCode(inputAmount),
buyerDetails,
sellerDetails,
formatter.formatCoinWithCode(fee),
feePerVbyte,
vkb))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
doPayout(buyerPayoutAmount,
sellerPayoutAmount,
fee,
buyerPayoutAddressString,
sellerPayoutAddressString,
resultHandler);
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} else {
// No payout will be made
new Popup().headLine(Res.get("disputeSummaryWindow.close.noPayout.headline"))
.confirmation(Res.get("disputeSummaryWindow.close.noPayout.text"))
.actionButtonText(Res.get("shared.yes"))
.onAction(resultHandler::handleResult)
.closeButtonText(Res.get("shared.cancel"))
.show();
}
throw new RuntimeException("DisputeSummaryWindow.showPayoutTxConfimration() needs updated for XMR");
// Coin buyerPayoutAmount = disputeResult.getBuyerPayoutAmount();
// String buyerPayoutAddressString = contract.getBuyerPayoutAddressString();
// Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount();
// String sellerPayoutAddressString = contract.getSellerPayoutAddressString();
// Coin outputAmount = buyerPayoutAmount.add(sellerPayoutAmount);
// Tuple2<Coin, Integer> feeTuple = txFeeEstimationService.getEstimatedFeeAndTxSize(outputAmount, feeService, btcWalletService);
// Coin fee = feeTuple.first;
// Integer txSize = feeTuple.second;
// double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
// double kb = txSize / 1000d;
// Coin inputAmount = outputAmount.add(fee);
// String buyerDetails = "";
// if (buyerPayoutAmount.isPositive()) {
// buyerDetails = Res.get("disputeSummaryWindow.close.txDetails.buyer",
// formatter.formatCoinWithCode(buyerPayoutAmount),
// buyerPayoutAddressString);
// }
// String sellerDetails = "";
// if (sellerPayoutAmount.isPositive()) {
// sellerDetails = Res.get("disputeSummaryWindow.close.txDetails.seller",
// formatter.formatCoinWithCode(sellerPayoutAmount),
// sellerPayoutAddressString);
// }
// if (outputAmount.isPositive()) {
// new Popup().width(900)
// .headLine(Res.get("disputeSummaryWindow.close.txDetails.headline"))
// .confirmation(Res.get("disputeSummaryWindow.close.txDetails",
// formatter.formatCoinWithCode(inputAmount),
// buyerDetails,
// sellerDetails,
// formatter.formatCoinWithCode(fee),
// feePerByte,
// kb))
// .actionButtonText(Res.get("shared.yes"))
// .onAction(() -> {
// doPayout(buyerPayoutAmount,
// sellerPayoutAmount,
// fee,
// buyerPayoutAddressString,
// sellerPayoutAddressString,
// resultHandler);
// })
// .closeButtonText(Res.get("shared.cancel"))
// .show();
// } else {
// // No payout will be made
// new Popup().headLine(Res.get("disputeSummaryWindow.close.noPayout.headline"))
// .confirmation(Res.get("disputeSummaryWindow.close.noPayout.text"))
// .actionButtonText(Res.get("shared.yes"))
// .onAction(resultHandler::handleResult)
// .closeButtonText(Res.get("shared.cancel"))
// .show();
// }
}
private void doPayout(Coin buyerPayoutAmount,
@ -734,82 +721,82 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
String buyerPayoutAddressString,
String sellerPayoutAddressString,
ResultHandler resultHandler) {
try {
Transaction tx = btcWalletService.createRefundPayoutTx(buyerPayoutAmount,
sellerPayoutAmount,
fee,
buyerPayoutAddressString,
sellerPayoutAddressString);
tradeWalletService.broadcastTx(tx, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
resultHandler.handleResult();
}
@Override
public void onFailure(TxBroadcastException exception) {
log.error("TxBroadcastException at doPayout", exception);
new Popup().error(exception.toString()).show();
}
});
} catch (InsufficientMoneyException | WalletException | TransactionVerificationException e) {
log.error("Exception at doPayout", e);
new Popup().error(e.toString()).show();
}
throw new RuntimeException("DisputeSummaryWindow.doPayout() needs updated for XMR");
// try {
// Transaction tx = btcWalletService.createRefundPayoutTx(buyerPayoutAmount,
// sellerPayoutAmount,
// fee,
// buyerPayoutAddressString,
// sellerPayoutAddressString);
// tradeWalletService.broadcastTx(tx, new TxBroadcaster.Callback() {
// @Override
// public void onSuccess(Transaction transaction) {
// resultHandler.handleResult();
// }
//
// @Override
// public void onFailure(TxBroadcastException exception) {
// log.error("TxBroadcastException at doPayout", exception);
// new Popup().error(exception.toString()).show();
// }
// });
// } catch (InsufficientMoneyException | WalletException | TransactionVerificationException e) {
// log.error("Exception at doPayout", e);
// new Popup().error(e.toString()).show();
// }
}
private void doCloseIfValid(Button closeTicketButton) {
var disputeManager = checkNotNull(getDisputeManager(dispute));
try {
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList());
doClose(closeTicketButton);
} catch (TradeDataValidation.AddressException exception) {
String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx();
String tradeId = dispute.getTradeId();
// For mediators we do not enforce that the case cannot be closed to stay flexible,
// but for refund agents we do.
if (disputeManager instanceof MediationManager) {
new Popup().width(900)
.warning(Res.get("support.warning.disputesWithInvalidDonationAddress",
addressAsString,
daoFacade.getAllDonationAddresses(),
tradeId,
Res.get("support.warning.disputesWithInvalidDonationAddress.mediator")))
.onAction(() -> {
doClose(closeTicketButton);
})
.actionButtonText(Res.get("shared.yes"))
.closeButtonText(Res.get("shared.no"))
.show();
} else {
new Popup().width(900)
.warning(Res.get("support.warning.disputesWithInvalidDonationAddress",
addressAsString,
daoFacade.getAllDonationAddresses(),
tradeId,
Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent")))
.show();
}
} catch (TradeDataValidation.DisputeReplayException exception) {
if (disputeManager instanceof MediationManager) {
log.error("Closing of ticket failed as mediator", exception);
new Popup().width(900)
.warning(exception.getMessage())
.onAction(() -> {
doClose(closeTicketButton);
})
.actionButtonText(Res.get("shared.yes"))
.closeButtonText(Res.get("shared.no"))
.show();
} else {
log.error("Closing of ticket failed", exception);
new Popup().width(900)
.warning(exception.getMessage())
.show();
}
}
throw new RuntimeException("DisputeSummaryWindow.doCloseIfValid() needs updated for XMR");
// var disputeManager = checkNotNull(getDisputeManager(dispute));
// try {
// TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
// TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList());
// doClose(closeTicketButton);
// } catch (TradeDataValidation.AddressException exception) {
// String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx();
// String tradeId = dispute.getTradeId();
//
// // For mediators we do not enforce that the case cannot be closed to stay flexible,
// // but for refund agents we do.
// if (disputeManager instanceof MediationManager) {
// new Popup().width(900)
// .warning(Res.get("support.warning.disputesWithInvalidDonationAddress",
// addressAsString,
// daoFacade.getAllDonationAddresses(),
// tradeId,
// Res.get("support.warning.disputesWithInvalidDonationAddress.mediator")))
// .onAction(() -> {
// doClose(closeTicketButton);
// })
// .actionButtonText(Res.get("shared.yes"))
// .closeButtonText(Res.get("shared.no"))
// .show();
// } else {
// new Popup().width(900)
// .warning(Res.get("support.warning.disputesWithInvalidDonationAddress",
// addressAsString,
// daoFacade.getAllDonationAddresses(),
// tradeId,
// Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent")))
// .show();
// }
// } catch (TradeDataValidation.DisputeReplayException exception) {
// if (disputeManager instanceof MediationManager) {
// new Popup().width(900)
// .warning(exception.getMessage())
// .onAction(() -> {
// doClose(closeTicketButton);
// })
// .actionButtonText(Res.get("shared.yes"))
// .closeButtonText(Res.get("shared.no"))
// .show();
// } else {
// new Popup().width(900)
// .warning(exception.getMessage())
// .show();
// }
// }
}
private void doClose(Button closeTicketButton) {
@ -818,43 +805,58 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
return;
}
boolean isRefundAgent = disputeManager instanceof RefundManager;
// boolean isRefundAgent = disputeManager instanceof RefundManager;
disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected());
disputeResult.setCloseDate(new Date());
dispute.setDisputeResult(disputeResult);
dispute.setIsClosed();
DisputeResult.Reason reason = disputeResult.getReason();
// DisputeResult.Reason reason = disputeResult.getReason();
summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty());
String role = isRefundAgent ? Res.get("shared.refundAgent") : Res.get("shared.mediator");
String agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)).getFullAddress();
Contract contract = dispute.getContract();
String currencyCode = contract.getOfferPayload().getCurrencyCode();
String amount = formatter.formatCoinWithCode(contract.getTradeAmount());
String textToSign = Res.get("disputeSummaryWindow.close.msg",
DisplayUtils.formatDateTime(disputeResult.getCloseDate()),
role,
agentNodeAddress,
dispute.getShortTradeId(),
currencyCode,
amount,
formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()),
formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()),
Res.get("disputeSummaryWindow.reason." + reason.name()),
disputeResult.summaryNotesProperty().get()
);
if (reason == DisputeResult.Reason.OPTION_TRADE &&
dispute.getChatMessages().size() > 1 &&
dispute.getChatMessages().get(1).isSystemMessage()) {
textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n";
}
// TODO (woodser): not used for xmr? calls setArbitratorSignature()
String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign);
// String role = isRefundAgent ? Res.get("shared.refundAgent") : Res.get("shared.mediator");
// String agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)).getFullAddress();
// Contract contract = dispute.getContract();
// String currencyCode = contract.getOfferPayload().getCurrencyCode();
// String amount = formatter.formatCoinWithCode(contract.getTradeAmount());
//
//
// String textToSign = Res.get("disputeSummaryWindow.close.msg",
// DisplayUtils.formatDateTime(disputeResult.getCloseDate()),
// role,
// agentNodeAddress,
// dispute.getShortTradeId(),
// currencyCode,
// amount,
// formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()),
// formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()),
// Res.get("disputeSummaryWindow.reason." + reason.name()),
// disputeResult.summaryNotesProperty().get()
// );
//
// if (reason == DisputeResult.Reason.OPTION_TRADE &&
// dispute.getChatMessages().size() > 1 &&
// dispute.getChatMessages().get(1).isSystemMessage()) {
// textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n";
// }
//
// String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign);
//
// if (isRefundAgent) {
// summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration");
// } else {
// summaryText += Res.get("disputeSummaryWindow.close.nextStepsForMediation");
// }
if (isRefundAgent) {
summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration");
} else {
String summaryText = Res.get("disputeSummaryWindow.close.msg",
DisplayUtils.formatDateTime(disputeResult.getCloseDate()),
formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()),
formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()),
disputeResult.summaryNotesProperty().get());
if (dispute.isMediationDispute()) {
summaryText += Res.get("disputeSummaryWindow.close.nextStepsForMediation");
}
@ -877,19 +879,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
}
private DisputeManager<? extends DisputeList<Dispute>> getDisputeManager(Dispute dispute) {
if (dispute.getSupportType() != null) {
switch (dispute.getSupportType()) {
case ARBITRATION:
return null;
case MEDIATION:
return mediationManager;
case TRADE:
break;
case REFUND:
return refundManager;
}
}
return null;
return dispute.isMediationDispute() ? mediationManager : arbitrationManager;
}
@ -910,31 +900,25 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
Coin buyerSecurityDeposit = offer.getBuyerSecurityDeposit();
Coin sellerSecurityDeposit = offer.getSellerSecurityDeposit();
Coin tradeAmount = contract.getTradeAmount();
boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager;
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
// mediated payout. For Refund agent cases we do not have that restriction.
Coin minRefundAtDispute = isMediationDispute ? Restrictions.getMinRefundAtMediatedDispute() : Coin.ZERO;
Coin maxPayoutAmount = tradeAmount
.add(buyerSecurityDeposit)
.add(sellerSecurityDeposit)
.subtract(minRefundAtDispute);
if (selectedTradeAmountToggle == buyerGetsTradeAmountRadioButton) {
disputeResult.setBuyerPayoutAmount(tradeAmount.add(buyerSecurityDeposit));
disputeResult.setSellerPayoutAmount(sellerSecurityDeposit);
disputeResult.setWinner(DisputeResult.Winner.BUYER);
} else if (selectedTradeAmountToggle == buyerGetsAllRadioButton) {
disputeResult.setBuyerPayoutAmount(maxPayoutAmount);
disputeResult.setSellerPayoutAmount(minRefundAtDispute);
disputeResult.setBuyerPayoutAmount(tradeAmount
.add(buyerSecurityDeposit)
.add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser (see post v1.1.7)
disputeResult.setSellerPayoutAmount(Coin.ZERO);
disputeResult.setWinner(DisputeResult.Winner.BUYER);
} else if (selectedTradeAmountToggle == sellerGetsTradeAmountRadioButton) {
disputeResult.setBuyerPayoutAmount(buyerSecurityDeposit);
disputeResult.setSellerPayoutAmount(tradeAmount.add(sellerSecurityDeposit));
disputeResult.setWinner(DisputeResult.Winner.SELLER);
} else if (selectedTradeAmountToggle == sellerGetsAllRadioButton) {
disputeResult.setBuyerPayoutAmount(minRefundAtDispute);
disputeResult.setSellerPayoutAmount(maxPayoutAmount);
disputeResult.setBuyerPayoutAmount(Coin.ZERO);
disputeResult.setSellerPayoutAmount(tradeAmount
.add(sellerSecurityDeposit)
.add(buyerSecurityDeposit));
disputeResult.setWinner(DisputeResult.Winner.SELLER);
}
@ -955,50 +939,20 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
buyerPayoutAmountInputTextField.setText(formatter.formatCoin(buyerPayoutAmount));
sellerPayoutAmountInputTextField.setText(formatter.formatCoin(sellerPayoutAmount));
boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager;
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
// mediated payout. For Refund agent cases we do not have that restriction.
Coin minRefundAtDispute = isMediationDispute ? Restrictions.getMinRefundAtMediatedDispute() : Coin.ZERO;
Coin maxPayoutAmount = tradeAmount
.add(buyerSecurityDeposit)
.add(sellerSecurityDeposit)
.subtract(minRefundAtDispute);
if (buyerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit)) &&
sellerPayoutAmount.equals(sellerSecurityDeposit)) {
buyerGetsTradeAmountRadioButton.setSelected(true);
} else if (buyerPayoutAmount.equals(maxPayoutAmount) &&
sellerPayoutAmount.equals(minRefundAtDispute)) {
} else if (buyerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)) &&
sellerPayoutAmount.equals(Coin.ZERO)) { // TODO (woodser): apply min payout to incentivize loser (see post v1.1.7)
buyerGetsAllRadioButton.setSelected(true);
} else if (sellerPayoutAmount.equals(tradeAmount.add(sellerSecurityDeposit))
&& buyerPayoutAmount.equals(buyerSecurityDeposit)) {
sellerGetsTradeAmountRadioButton.setSelected(true);
} else if (sellerPayoutAmount.equals(maxPayoutAmount)
&& buyerPayoutAmount.equals(minRefundAtDispute)) {
} else if (sellerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit))
&& buyerPayoutAmount.equals(Coin.ZERO)) {
sellerGetsAllRadioButton.setSelected(true);
} else {
customRadioButton.setSelected(true);
}
}
private void checkDelayedPayoutTransaction() {
if (dispute.getDelayedPayoutTxId() == null)
return;
mempoolService.checkTxIsConfirmed(dispute.getDelayedPayoutTxId(), (validator -> {
long confirms = validator.parseJsonValidateTx();
log.info("Mempool check confirmation status of DelayedPayoutTxId returned: [{}]", confirms);
displayPayoutStatus(confirms);
}));
}
private void displayPayoutStatus(long nConfirmStatus) {
if (delayedPayoutTxStatus != null) {
String status = Res.get("confidence.unknown");
if (nConfirmStatus == 0)
status = Res.get("confidence.seen", 1);
else if (nConfirmStatus > 0)
status = Res.get("confidence.confirmed", nConfirmStatus);
delayedPayoutTxStatus.setText(status);
}
}
}

View file

@ -711,24 +711,25 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
}
private void importFromMediationTicket(String tradeId) {
clearInputFields();
Optional<Dispute> optionalDispute = mediationManager.findDispute(tradeId);
if (optionalDispute.isPresent()) {
Dispute dispute = optionalDispute.get();
depositTxHex.setText(dispute.getDepositTxId());
if (dispute.disputeResultProperty().get() != null) {
buyerPayoutAmount.setText(dispute.disputeResultProperty().get().getBuyerPayoutAmount().toPlainString());
sellerPayoutAmount.setText(dispute.disputeResultProperty().get().getSellerPayoutAmount().toPlainString());
}
buyerAddressString.setText(dispute.getContract().getBuyerPayoutAddressString());
sellerAddressString.setText(dispute.getContract().getSellerPayoutAddressString());
buyerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getBuyerMultiSigPubKey()));
sellerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getSellerMultiSigPubKey()));
// switch back to the inputs pane
hideAllPanes();
inputsGridPane.setVisible(true);
UserThread.execute(() -> new Popup().warning("Ticket imported. You still need to enter the multisig amount and specify if it is a legacy Tx").show());
}
throw new RuntimeException("ManualPayoutTxWindow.importFromMediationTicket() not adapted to XMR");
// clearInputFields();
// Optional<Dispute> optionalDispute = mediationManager.findDispute(tradeId);
// if (optionalDispute.isPresent()) {
// Dispute dispute = optionalDispute.get();
// depositTxHex.setText(dispute.getDepositTxId());
// if (dispute.disputeResultProperty().get() != null) {
// buyerPayoutAmount.setText(dispute.disputeResultProperty().get().getBuyerPayoutAmount().toPlainString());
// sellerPayoutAmount.setText(dispute.disputeResultProperty().get().getSellerPayoutAmount().toPlainString());
// }
// buyerAddressString.setText(dispute.getContract().getBuyerPayoutAddressString());
// sellerAddressString.setText(dispute.getContract().getSellerPayoutAddressString());
// buyerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getBuyerMultiSigPubKey()));
// sellerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getSellerMultiSigPubKey()));
// // switch back to the inputs pane
// hideAllPanes();
// inputsGridPane.setVisible(true);
// UserThread.execute(() -> new Popup().warning("Ticket imported. You still need to enter the multisig amount and specify if it is a legacy Tx").show());
// }
}
private String generateSignature() {

View file

@ -46,9 +46,6 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import javax.inject.Inject;
import javax.inject.Named;
@ -199,6 +196,12 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
trade.getAssetTxProofResult() != null &&
trade.getAssetTxProofResult() != AssetTxProofResult.UNDEFINED;
if (trade.getTakerFeeTxId() != null)
rows++;
if (trade.getMakerDepositTx() != null)
rows++;
if (trade.getTakerDepositTx() != null)
rows++;
if (trade.getPayoutTx() != null)
rows++;
boolean showDisputedTx = arbitrationManager.findOwnDispute(trade.getId()).isPresent() &&
@ -287,25 +290,16 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.makerFeeTxId"), offer.getOfferFeePaymentTxId());
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), trade.getTakerFeeTxId());
String depositTxId = trade.getDepositTxId();
Transaction depositTx = trade.getDepositTx();
String depositTxIdFromTx = depositTx != null ? depositTx.getTxId().toString() : null;
TxIdTextField depositTxIdTextField = addLabelTxIdTextField(gridPane, ++rowIndex,
Res.get("shared.depositTransactionId"), depositTxId).second;
if (depositTxId == null || !depositTxId.equals(depositTxIdFromTx)) {
depositTxIdTextField.getTextField().setId("address-text-field-error");
log.error("trade.getDepositTxId() and trade.getDepositTx().getTxId().toString() are not the same. " +
"trade.getDepositTxId()={}, trade.getDepositTx().getTxId().toString()={}, depositTx={}",
depositTxId, depositTxIdFromTx, depositTx);
}
Transaction delayedPayoutTx = trade.getDelayedPayoutTx(btcWalletService);
String delayedPayoutTxString = delayedPayoutTx != null ? delayedPayoutTx.getTxId().toString() : null;
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.delayedPayoutTxId"), delayedPayoutTxString);
if (trade.getMakerDepositTx() != null)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), // TODO (woodser): separate UI labels for deposit tx ids
trade.getMakerDepositTx().getHash());
if (trade.getTakerDepositTx() != null)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), // TODO (woodser): separate UI labels for deposit tx ids
trade.getTakerDepositTx().getHash());
if (trade.getPayoutTx() != null)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.payoutTxId"),
trade.getPayoutTx().getTxId().toString());
trade.getPayoutTx().getHash());
if (showDisputedTx)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.disputedPayoutTxId"),
arbitrationManager.findOwnDispute(trade.getId()).get().getDisputePayoutTxId());
@ -347,21 +341,18 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
textArea.setText(trade.getContractAsJson());
String data = "Contract as json:\n";
data += trade.getContractAsJson();
data += "\n\nOther detail data:";
data += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey());
data += "\nSellerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getSellerMultiSigPubKey());
if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode())) {
data += "\n\nBuyersAccountAge: " + buyersAccountAge;
data += "\nSellersAccountAge: " + sellersAccountAge;
}
if (depositTx != null) {
String depositTxAsHex = Utils.HEX.encode(depositTx.bitcoinSerialize(true));
data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex;
}
// TODO (woodser): include maker and taker deposit tx hex in contract?
// if (depositTx != null) {
// String depositTxAsHex = Utils.HEX.encode(depositTx.bitcoinSerialize(true));
// data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex;
// }
data += "\n\nSelected mediator: " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress());
data += "\nSelected arbitrator (refund agent): " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress());
data += "\n\nSelected arbitrator: " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getArbitratorNodeAddress().getFullAddress());
textArea.setText(data);
textArea.setPrefHeight(50);

View file

@ -433,7 +433,10 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().contains(filterString)) {
return true;
}
if (trade.getDepositTxId() != null && trade.getDepositTxId().contains(filterString)) {
if (trade.getMakerDepositTxId() != null && trade.getMakerDepositTxId().contains(filterString)) {
return true;
}
if (trade.getTakerDepositTxId() != null && trade.getTakerDepositTxId().contains(filterString)) {
return true;
}
if (trade.getPayoutTxId() != null && trade.getPayoutTxId().contains(filterString)) {

View file

@ -23,7 +23,7 @@ import bisq.desktop.main.offer.MutableOfferDataModel;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
@ -48,7 +48,7 @@ class DuplicateOfferDataModel extends MutableOfferDataModel {
DuplicateOfferDataModel(CreateOfferService createOfferService,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
XmrWalletService btcWalletService,
BsqWalletService bsqWalletService,
Preferences preferences,
User user,

View file

@ -23,8 +23,8 @@ import bisq.desktop.main.offer.MutableOfferDataModel;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.TradeCurrency;
import bisq.core.offer.CreateOfferService;
@ -65,7 +65,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
EditOfferDataModel(CreateOfferService createOfferService,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
Preferences preferences,
User user,
@ -81,7 +81,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
super(createOfferService,
openOfferManager,
offerUtil,
btcWalletService,
xmrWalletService,
bsqWalletService,
preferences,
user,

View file

@ -282,7 +282,10 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().contains(filterString)) {
return true;
}
if (trade.getDepositTxId() != null && trade.getDepositTxId().contains(filterString)) {
if (trade.getMakerDepositTxId() != null && trade.getMakerDepositTxId().contains(filterString)) {
return true;
}
if (trade.getTakerDepositTxId() != null && trade.getTakerDepositTxId().contains(filterString)) {
return true;
}
if (trade.getPayoutTxId() != null && trade.getPayoutTxId().contains(filterString)) {
@ -319,8 +322,8 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
private String checkTxs() {
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
log.info("Initiated unfail of trade {}", trade.getId());
if (trade.getDepositTx() == null) {
log.info("Check unfail found no depositTx for trade {}", trade.getId());
if (trade.getMakerDepositTx() == null || trade.getTakerDepositTx() == null) {
log.info("Check unfail found no deposit tx(s) for trade {}", trade.getId());
return Res.get("portfolio.failed.depositTxNull");
}
if (trade.getDelayedPayoutTxBytes() == null) {

View file

@ -24,14 +24,13 @@ import bisq.desktop.main.overlays.notifications.NotificationCenter;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.main.support.SupportView;
import bisq.desktop.main.support.dispute.client.arbitration.ArbitrationClientView;
import bisq.desktop.main.support.dispute.client.mediation.MediationClientView;
import bisq.desktop.main.support.dispute.client.refund.RefundClientView;
import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
@ -42,21 +41,16 @@ import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeAlreadyOpenException;
import bisq.core.support.dispute.DisputeList;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.support.messages.ChatMessage;
import bisq.core.support.traderchat.TraderChatManager;
import bisq.core.trade.BuyerTrade;
import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeDataValidation;
import bisq.core.trade.TradeManager;
import bisq.core.trade.protocol.BuyerProtocol;
import bisq.core.trade.protocol.DisputeProtocol;
import bisq.core.trade.protocol.SellerProtocol;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.network.p2p.P2PService;
@ -66,8 +60,6 @@ import bisq.common.handlers.FaultHandler;
import bisq.common.handlers.ResultHandler;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import com.google.inject.Inject;
@ -84,7 +76,6 @@ import javafx.collections.ObservableList;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import lombok.Getter;
@ -94,17 +85,21 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
public class PendingTradesDataModel extends ActivatableDataModel {
@Getter
public final TradeManager tradeManager;
public final BtcWalletService btcWalletService;
public final XmrWalletService xmrWalletService;
public final ArbitrationManager arbitrationManager;
public final MediationManager mediationManager;
public final RefundManager refundManager;
private final P2PService p2PService;
private final WalletsSetup walletsSetup;
@Getter
private final AccountAgeWitnessService accountAgeWitnessService;
public final DaoFacade daoFacade;
public final Navigation navigation;
public final WalletPasswordWindow walletPasswordWindow;
private final NotificationCenter notificationCenter;
@ -115,7 +110,8 @@ public class PendingTradesDataModel extends ActivatableDataModel {
private boolean isMaker;
final ObjectProperty<PendingTradesListItem> selectedItemProperty = new SimpleObjectProperty<>();
public final StringProperty txId = new SimpleStringProperty();
public final StringProperty makerTxId = new SimpleStringProperty();
public final StringProperty takerTxId = new SimpleStringProperty();
@Getter
private final TraderChatManager traderChatManager;
@ -132,31 +128,29 @@ public class PendingTradesDataModel extends ActivatableDataModel {
@Inject
public PendingTradesDataModel(TradeManager tradeManager,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
PubKeyRing pubKeyRing,
ArbitrationManager arbitrationManager,
MediationManager mediationManager,
RefundManager refundManager,
TraderChatManager traderChatManager,
Preferences preferences,
P2PService p2PService,
WalletsSetup walletsSetup,
AccountAgeWitnessService accountAgeWitnessService,
DaoFacade daoFacade,
Navigation navigation,
WalletPasswordWindow walletPasswordWindow,
NotificationCenter notificationCenter,
OfferUtil offerUtil) {
this.tradeManager = tradeManager;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.pubKeyRing = pubKeyRing;
this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
this.refundManager = refundManager;
this.traderChatManager = traderChatManager;
this.preferences = preferences;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.accountAgeWitnessService = accountAgeWitnessService;
this.daoFacade = daoFacade;
this.navigation = navigation;
this.walletPasswordWindow = walletPasswordWindow;
this.notificationCenter = notificationCenter;
@ -290,10 +284,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
return Coin.ZERO;
}
} else {
if (trade.isCurrencyForTakerFeeBtc())
return trade.getTakerFee();
else
return Coin.ZERO; // getTradeFeeAsBsq is used for BSQ
return trade.getTakerFee();
}
} else {
log.error("Trade is null at getTotalFees");
@ -316,10 +307,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
return Coin.ZERO;
}
} else {
if (trade.isCurrencyForTakerFeeBtc())
return trade.getTxFee().multiply(3);
else
return trade.getTxFee().multiply(3).subtract(trade.getTakerFee()); // BSQ will be used as part of the miner fee
return trade.getTxFee().multiply(3);
}
} else {
log.error("Trade is null at getTotalFees");
@ -343,10 +331,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
return Coin.ZERO;
}
} else {
if (trade.isCurrencyForTakerFeeBtc())
return Coin.ZERO; // getTradeFeeInBTC is used for BTC
else
return trade.getTakerFee();
return Coin.ZERO; // getTradeFeeInBTC is used for BTC
}
} else {
log.error("Trade is null at getTotalFees");
@ -414,15 +399,18 @@ public class PendingTradesDataModel extends ActivatableDataModel {
return;
}
Transaction depositTx = selectedTrade.getDepositTx();
MoneroTxWallet makerDepositTx = selectedTrade.getMakerDepositTx();
MoneroTxWallet takerDepositTx = selectedTrade.getTakerDepositTx();
String tradeId = selectedTrade.getId();
tradeStateChangeListener = (observable, oldValue, newValue) -> {
if (depositTx != null) {
txId.set(depositTx.getTxId().toString());
if (makerDepositTx != null && takerDepositTx != null) { // TODO (woodser): this treats separate deposit ids as one unit, being both available or unavailable
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
notificationCenter.setSelectedTradeId(tradeId);
selectedTrade.stateProperty().removeListener(tradeStateChangeListener);
} else {
txId.set("");
makerTxId.set("");
takerTxId.set("");
}
};
selectedTrade.stateProperty().addListener(tradeStateChangeListener);
@ -434,15 +422,18 @@ public class PendingTradesDataModel extends ActivatableDataModel {
}
isMaker = tradeManager.isMyOffer(offer);
if (depositTx != null) {
txId.set(depositTx.getTxId().toString());
if (makerDepositTx != null && takerDepositTx != null) {
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
} else {
txId.set("");
makerTxId.set("");
takerTxId.set("");
}
notificationCenter.setSelectedTradeId(tradeId);
} else {
selectedTrade = null;
txId.set("");
makerTxId.set("");
takerTxId.set("");
notificationCenter.setSelectedTradeId(null);
}
selectedItemProperty.set(item);
@ -455,72 +446,96 @@ public class PendingTradesDataModel extends ActivatableDataModel {
return;
}
doOpenDispute(isSupportTicket, trade.getDepositTx());
doOpenDispute(isSupportTicket, trade);
}
private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) {
// We do not support opening a dispute if the deposit tx is null. Traders have to use the support channel at keybase
// in such cases. The mediators or arbitrators could not help anyway with a payout in such cases.
if (depositTx == null) {
log.error("Deposit tx must not be null");
new Popup().instruction(Res.get("portfolio.pending.error.depositTxNull")).show();
return;
}
String depositTxId = depositTx.getTxId().toString();
private void doOpenDispute(boolean isSupportTicket, Trade trade) {
if (trade == null) {
log.warn("trade is null at doOpenDispute");
return;
}
Trade trade = getTrade();
if (trade == null) {
log.warn("trade is null at doOpenDispute");
return;
// We do not support opening a dispute if the deposit tx is null. Traders have to use the support channel at keybase
// in such cases. The mediators or arbitrators could not help anyway with a payout in such cases.
String depositTxId = null;
if (isMaker) {
if (trade.getMakerDepositTxId() == null) {
log.error("Deposit tx must not be null");
new Popup().instruction(Res.get("portfolio.pending.error.depositTxNull")).show();
return;
}
Offer offer = trade.getOffer();
if (offer == null) {
log.warn("offer is null at doOpenDispute");
return;
depositTxId = trade.getMakerDepositTxId();
} else {
if (trade.getTakerDepositTxId() == null) {
log.error("Deposit tx must not be null");
new Popup().instruction(Res.get("portfolio.pending.error.depositTxNull")).show();
return;
}
depositTxId = trade.getTakerDepositTxId();
}
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
return;
}
Offer offer = trade.getOffer();
if (offer == null) {
log.warn("offer is null at doOpenDispute");
return;
}
byte[] payoutTxSerialized = null;
String payoutTxHashAsString = null;
Transaction payoutTx = trade.getPayoutTx();
if (payoutTx != null) {
payoutTxSerialized = payoutTx.bitcoinSerialize();
payoutTxHashAsString = payoutTx.getTxId().toString();
}
Trade.DisputeState disputeState = trade.getDisputeState();
DisputeManager<? extends DisputeList<Dispute>> disputeManager;
boolean useMediation;
boolean useRefundAgent;
// In case we re-open a dispute we allow Trade.DisputeState.MEDIATION_REQUESTED
useMediation = disputeState == Trade.DisputeState.NO_DISPUTE || disputeState == Trade.DisputeState.MEDIATION_REQUESTED;
// In case we re-open a dispute we allow Trade.DisputeState.REFUND_REQUESTED
useRefundAgent = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.REFUND_REQUESTED;
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
return;
}
AtomicReference<String> donationAddressString = new AtomicReference<>("");
Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
try {
TradeDataValidation.validateDelayedPayoutTx(trade,
delayedPayoutTx,
daoFacade,
btcWalletService,
donationAddressString::set);
} catch (TradeDataValidation.ValidationException e) {
// The peer sent us an invalid donation address. We do not return here as we don't want to break
// mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get
// a popup displayed to react.
log.error("DelayedPayoutTxValidation failed. {}", e.toString());
byte[] payoutTxSerialized = null;
String payoutTxHashAsString = null;
MoneroTxWallet payoutTx = trade.getPayoutTx();
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(trade.getId());
String updatedMultisigHex = multisigWallet.getMultisigHex();
if (payoutTx != null) {
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
// payoutTxHashAsString = payoutTx.getHashAsString();
}
Trade.DisputeState disputeState = trade.getDisputeState();
DisputeManager<? extends DisputeList<Dispute>> disputeManager;
boolean useMediation;
boolean useArbitration;
// If mediation is not activated we use arbitration
if (false) { // TODO (woodser): use mediation for xmr? if (MediationManager.isMediationActivated()) {
// In case we re-open a dispute we allow Trade.DisputeState.MEDIATION_REQUESTED or
useMediation = disputeState == Trade.DisputeState.NO_DISPUTE || disputeState == Trade.DisputeState.MEDIATION_REQUESTED;
// in case of arbitration disputeState == Trade.DisputeState.ARBITRATION_REQUESTED
useArbitration = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.DISPUTE_REQUESTED;
} else {
useMediation = false;
useArbitration = true;
}
if (useRefundAgent) {
// We don't allow to continue and publish payout tx and open refund agent case.
// In case it was caused by some bug we want to prevent a wrong payout. In case its a scam attempt we
// want to protect the refund agent.
return;
}
}
// if (useMediation) {
// // If no dispute state set we start with mediation
// disputeManager = mediationManager;
// PubKeyRing mediatorPubKeyRing = trade.getMediatorPubKeyRing();
// checkNotNull(mediatorPubKeyRing, "mediatorPubKeyRing must not be null");
// byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr
// String depositTxHashAsString = null; // depositTx.getHashAsString(); // TODO (woodser): two deposit txs for dispute
// Dispute dispute = new Dispute(new Date().getTime(),
// trade.getId(),
// pubKeyRing.hashCode(), // traderId
// true,
// (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
// isMaker,
// pubKeyRing,
// trade.getDate().getTime(),
// trade.getMaxTradePeriodDate().getTime(),
// trade.getContract(),
// trade.getContractHash(),
// depositTxSerialized,
// payoutTxSerialized,
// depositTxHashAsString,
// payoutTxHashAsString,
// trade.getContractAsJson(),
// trade.getMakerContractSignature(),
// trade.getTakerContractSignature(),
// mediatorPubKeyRing,
// isSupportTicket,
// SupportType.MEDIATION);
ResultHandler resultHandler;
if (useMediation) {
@ -529,10 +544,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
disputeManager = mediationManager;
PubKeyRing mediatorPubKeyRing = trade.getMediatorPubKeyRing();
checkNotNull(mediatorPubKeyRing, "mediatorPubKeyRing must not be null");
byte[] depositTxSerialized = depositTx.bitcoinSerialize();
byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr
Dispute dispute = new Dispute(new Date().getTime(),
trade.getId(),
pubKeyRing.hashCode(), // traderId
true,
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
isMaker,
pubKeyRing,
@ -553,98 +569,61 @@ public class PendingTradesDataModel extends ActivatableDataModel {
dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId());
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get());
if (delayedPayoutTx != null) {
dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString());
}
trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED);
sendOpenDisputeMessage(disputeManager, resultHandler, dispute);
sendOpenNewDisputeMessage(dispute, false, disputeManager, updatedMultisigHex);
tradeManager.requestPersistence();
} else if (useRefundAgent) {
resultHandler = () -> navigation.navigateTo(MainView.class, SupportView.class, RefundClientView.class);
} else if (useArbitration) {
// Only if we have completed mediation we allow arbitration
disputeManager = arbitrationManager;
PubKeyRing arbitratorPubKeyRing = trade.getArbitratorPubKeyRing();
checkNotNull(arbitratorPubKeyRing, "arbitratorPubKeyRing must not be null");
byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); TODO (woodser)
String depositTxHashAsString = null; // depositTx.getHashAsString(); TODO (woodser)
Dispute dispute = new Dispute(new Date().getTime(),
trade.getId(),
pubKeyRing.hashCode(), // traderId,
true,
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
isMaker,
pubKeyRing,
trade.getDate().getTime(),
trade.getMaxTradePeriodDate().getTime(),
trade.getContract(),
trade.getContractHash(),
depositTxSerialized,
payoutTxSerialized,
depositTxHashAsString,
payoutTxHashAsString,
trade.getContractAsJson(),
trade.getMakerContractSignature(),
trade.getTakerContractSignature(),
arbitratorPubKeyRing,
isSupportTicket,
SupportType.ARBITRATION);
if (delayedPayoutTx == null) {
log.error("Delayed payout tx is missing");
return;
}
// We only require for refund agent a confirmed deposit tx. For mediation we tolerate a unconfirmed tx as
// no harm can be done to the mediator (refund agent who would accept a invalid deposit tx might reimburse
// the traders but the funds never have been spent).
TransactionConfidence confidenceForTxId = btcWalletService.getConfidenceForTxId(depositTxId);
if (confidenceForTxId == null || confidenceForTxId.getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING) {
log.error("Confidence for deposit tx must be BUILDING, confidenceForTxId={}", confidenceForTxId);
new Popup().instruction(Res.get("portfolio.pending.error.depositTxNotConfirmed")).show();
return;
}
long lockTime = delayedPayoutTx.getLockTime();
int bestChainHeight = btcWalletService.getBestChainHeight();
long remaining = lockTime - bestChainHeight;
if (remaining > 0) {
new Popup().instruction(Res.get("portfolio.pending.timeLockNotOver",
FormattingUtils.getDateFromBlockHeight(remaining), remaining))
.show();
return;
}
disputeManager = refundManager;
PubKeyRing refundAgentPubKeyRing = trade.getRefundAgentPubKeyRing();
checkNotNull(refundAgentPubKeyRing, "refundAgentPubKeyRing must not be null");
byte[] depositTxSerialized = depositTx.bitcoinSerialize();
String depositTxHashAsString = depositTx.getTxId().toString();
Dispute dispute = new Dispute(new Date().getTime(),
trade.getId(),
pubKeyRing.hashCode(), // traderId
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
isMaker,
pubKeyRing,
trade.getDate().getTime(),
trade.getMaxTradePeriodDate().getTime(),
trade.getContract(),
trade.getContractHash(),
depositTxSerialized,
payoutTxSerialized,
depositTxHashAsString,
payoutTxHashAsString,
trade.getContractAsJson(),
trade.getMakerContractSignature(),
trade.getTakerContractSignature(),
refundAgentPubKeyRing,
isSupportTicket,
SupportType.REFUND);
dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId());
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
String tradeId = dispute.getTradeId();
mediationManager.findDispute(tradeId)
.ifPresent(mediatorsDispute -> {
DisputeResult mediatorsDisputeResult = mediatorsDispute.getDisputeResultProperty().get();
ChatMessage mediatorsResultMessage = mediatorsDisputeResult.getChatMessage();
if (mediatorsResultMessage != null) {
String mediatorAddress = Res.get("support.mediatorsAddress",
mediatorsDispute.getContract().getRefundAgentNodeAddress().getFullAddress());
String message = mediatorAddress + "\n\n" + mediatorsResultMessage.getMessage();
dispute.setMediatorsDisputeResult(message);
}
});
dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get());
dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString());
trade.setDisputeState(Trade.DisputeState.REFUND_REQUESTED);
((DisputeProtocol) tradeManager.getTradeProtocol(trade)).onPublishDelayedPayoutTx(() -> {
log.info("DelayedPayoutTx published and message sent to peer");
sendOpenDisputeMessage(disputeManager, resultHandler, dispute);
},
errorMessage -> new Popup().error(errorMessage).show());
trade.setDisputeState(Trade.DisputeState.DISPUTE_REQUESTED);
sendOpenNewDisputeMessage(dispute, false, disputeManager, updatedMultisigHex);
} else {
log.warn("Invalid dispute state {}", disputeState.name());
}
tradeManager.requestPersistence();
}
private void sendOpenNewDisputeMessage(Dispute dispute, boolean reOpen, DisputeManager<? extends DisputeList<Dispute>> disputeManager, String senderMultisigHex) {
disputeManager.sendOpenNewDisputeMessage(dispute, reOpen, senderMultisigHex,
() -> navigation.navigateTo(MainView.class, SupportView.class, ArbitrationClientView.class), (errorMessage, throwable) -> {
if ((throwable instanceof DisputeAlreadyOpenException)) {
errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg");
new Popup().warning(errorMessage)
.actionButtonText(Res.get("portfolio.pending.openAgainDispute.button"))
.onAction(() -> sendOpenNewDisputeMessage(dispute, true, disputeManager, senderMultisigHex))
.closeButtonText(Res.get("shared.cancel")).show();
} else {
new Popup().warning(errorMessage).show();
}
});
}
public boolean isReadyForTxBroadcast() {
return GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup);
}
@ -660,28 +639,5 @@ public class PendingTradesDataModel extends ActivatableDataModel {
public boolean isSignWitnessTrade() {
return accountAgeWitnessService.isSignWitnessTrade(selectedTrade);
}
private void sendOpenDisputeMessage(DisputeManager<? extends DisputeList<Dispute>> disputeManager,
ResultHandler resultHandler,
Dispute dispute) {
disputeManager.sendOpenNewDisputeMessage(dispute,
false,
resultHandler,
(errorMessage, throwable) -> {
if ((throwable instanceof DisputeAlreadyOpenException)) {
errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg");
new Popup().warning(errorMessage)
.actionButtonText(Res.get("portfolio.pending.openAgainDispute.button"))
.onAction(() -> disputeManager.sendOpenNewDisputeMessage(dispute,
true,
resultHandler,
(e, t) -> log.error(e)))
.closeButtonText(Res.get("shared.cancel"))
.show();
} else {
new Popup().warning(errorMessage).show();
}
});
}
}

View file

@ -408,14 +408,12 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
Res.get("portfolio.pending.failedTrade.taker.missingTakerFeeTx");
}
if (trade.getDepositTx() == null) {
if (trade.getMakerDepositTx() == null) {
return Res.get("portfolio.pending.failedTrade.missingDepositTx");
}
if (trade.getDelayedPayoutTx() == null) {
return isMyRoleBuyer ?
Res.get("portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx") :
Res.get("portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx");
if (trade.getTakerDepositTx() == null) {
return Res.get("portfolio.pending.failedTrade.missingDepositTx"); // TODO (woodser): use .missingTakerDepositTx, .missingMakerDepositTx
}
if (trade.hasErrorMessage()) {

View file

@ -20,13 +20,11 @@ package bisq.desktop.main.portfolio.pendingtrades;
import bisq.desktop.Navigation;
import bisq.desktop.common.model.ActivatableWithDataModel;
import bisq.desktop.common.model.ViewModel;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.Res;
import bisq.core.network.MessageState;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
@ -45,7 +43,6 @@ import bisq.core.util.validation.BtcAddressValidator;
import bisq.network.p2p.P2PService;
import bisq.common.ClockWatcher;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import org.bitcoinj.core.Coin;
@ -64,7 +61,6 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.Getter;
@ -209,27 +205,29 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
}
public void checkTakerFeeTx(Trade trade) {
mempoolStatus.setValue(-1);
mempoolService.validateOfferTakerTx(trade, (txValidator -> {
mempoolStatus.setValue(txValidator.isFail() ? 0 : 1);
if (txValidator.isFail()) {
String errorMessage = "Validation of Taker Tx returned: " + txValidator.toString();
log.warn(errorMessage);
// prompt user to open mediation
if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) {
UserThread.runAfter(() -> {
Popup popup = new Popup();
popup.headLine(Res.get("portfolio.pending.openSupportTicket.headline"))
.message(Res.get("portfolio.pending.invalidTx", errorMessage))
.actionButtonText(Res.get("portfolio.pending.openSupportTicket.headline"))
.onAction(dataModel::onOpenSupportTicket)
.closeButtonText(Res.get("shared.cancel"))
.onClose(popup::hide)
.show();
}, 100, TimeUnit.MILLISECONDS);
}
}
}));
log.warn("PendingTradesViewModel.checkTakerFeeTx() needs adapted to XMR");
return; // TODO (woodser): PendingTradesViewModel.checkTakerFeeTx() needs adapted to XMR, use common TradeDataValidation utility
// mempoolStatus.setValue(-1);
// mempoolService.validateOfferTakerTx(trade, (txValidator -> {
// mempoolStatus.setValue(txValidator.isFail() ? 0 : 1);
// if (txValidator.isFail()) {
// String errorMessage = "Validation of Taker Tx returned: " + txValidator.toString();
// log.warn(errorMessage);
// // prompt user to open mediation
// if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) {
// UserThread.runAfter(() -> {
// Popup popup = new Popup();
// popup.headLine(Res.get("portfolio.pending.openSupportTicket.headline"))
// .message(Res.get("portfolio.pending.invalidTx", errorMessage))
// .actionButtonText(Res.get("portfolio.pending.openSupportTicket.headline"))
// .onAction(dataModel::onOpenSupportTicket)
// .closeButtonText(Res.get("shared.cancel"))
// .onClose(popup::hide)
// .show();
// }, 100, TimeUnit.MILLISECONDS);
// }
// }
// }));
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -329,7 +327,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
if (trade != null && dataModel.getOffer() != null && trade.getTradeAmount() != null) {
checkNotNull(dataModel.getTrade());
if (dataModel.isMaker() && dataModel.getOffer().isCurrencyForMakerFeeBtc() ||
!dataModel.isMaker() && dataModel.getTrade().isCurrencyForTakerFeeBtc()) {
!dataModel.isMaker()) {
Coin tradeFeeInBTC = dataModel.getTradeFeeInBTC();
Coin minTradeFee = dataModel.isMaker() ?
@ -432,20 +430,21 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
// #################### Phase DEPOSIT_PAID
case SELLER_PUBLISHED_DEPOSIT_TX:
case TAKER_PUBLISHED_DEPOSIT_TX:
case TAKER_SAW_DEPOSIT_TX_IN_NETWORK:
// DEPOSIT_TX_PUBLISHED_MSG
// seller perspective
case SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG:
case SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG:
case SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG:
case SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG:
// taker perspective
case TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG:
case TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG:
case TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG:
case TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG:
// buyer perspective
case BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG:
// maker perspective
case MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG:
// Alternatively the maker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
case BUYER_SAW_DEPOSIT_TX_IN_NETWORK:
case MAKER_SAW_DEPOSIT_TX_IN_NETWORK:
buyerState.set(BuyerState.STEP1);
sellerState.set(SellerState.STEP1);
break;

View file

@ -32,10 +32,11 @@ import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.trade.Contract;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade;
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.network.p2p.BootstrapListener;
@ -43,9 +44,6 @@ import bisq.common.ClockWatcher;
import bisq.common.UserThread;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
@ -97,16 +95,17 @@ public abstract class TradeStepView extends AnchorPane {
protected int gridRow = 0;
private TextField timeLeftTextField;
private ProgressBar timeLeftProgressBar;
private TxIdTextField txIdTextField;
private TxIdTextField makerTxIdTextField;
private TxIdTextField takerTxIdTextField;
private TradeStepInfo tradeStepInfo;
private Subscription txIdSubscription;
private Subscription makerTxIdSubscription;
private Subscription takerTxIdSubscription;
private ClockWatcher.Listener clockListener;
private final ChangeListener<String> errorMessageListener;
protected Label infoLabel;
private Popup acceptMediationResultPopup;
private BootstrapListener bootstrapListener;
private TradeSubView.ChatCallback chatCallback;
private final NewBestBlockListener newBestBlockListener;
private ChangeListener<Boolean> pendingTradesInitializedListener;
@ -170,21 +169,32 @@ public abstract class TradeStepView extends AnchorPane {
}
};
newBestBlockListener = block -> {
checkIfLockTimeIsOver();
};
// newBestBlockListener = block -> {
// checkIfLockTimeIsOver();
// };
}
public void activate() {
if (txIdTextField != null) {
if (txIdSubscription != null)
txIdSubscription.unsubscribe();
if (makerTxIdTextField != null) {
if (makerTxIdSubscription != null)
makerTxIdSubscription.unsubscribe();
txIdSubscription = EasyBind.subscribe(model.dataModel.txId, id -> {
makerTxIdSubscription = EasyBind.subscribe(model.dataModel.makerTxId, id -> {
if (!id.isEmpty())
txIdTextField.setup(id);
makerTxIdTextField.setup(id);
else
txIdTextField.cleanup();
makerTxIdTextField.cleanup();
});
}
if (takerTxIdTextField != null) {
if (takerTxIdSubscription != null)
takerTxIdSubscription.unsubscribe();
takerTxIdSubscription = EasyBind.subscribe(model.dataModel.takerTxId, id -> {
if (!id.isEmpty())
takerTxIdTextField.setup(id);
else
takerTxIdTextField.cleanup();
});
}
trade.errorMessageProperty().addListener(errorMessageListener);
@ -241,8 +251,8 @@ public abstract class TradeStepView extends AnchorPane {
}
protected void onPendingTradesInitialized() {
model.dataModel.btcWalletService.addNewBestBlockListener(newBestBlockListener);
checkIfLockTimeIsOver();
// model.dataModel.xmrWalletService.addNewBestBlockListener(newBestBlockListener); // TODO (woodser): different listener?
// checkIfLockTimeIsOver();
}
private void registerSubscriptions() {
@ -274,38 +284,38 @@ public abstract class TradeStepView extends AnchorPane {
}
public void deactivate() {
if (txIdSubscription != null)
txIdSubscription.unsubscribe();
if (makerTxIdSubscription != null)
makerTxIdSubscription.unsubscribe();
if (takerTxIdSubscription != null)
takerTxIdSubscription.unsubscribe();
if (txIdTextField != null)
txIdTextField.cleanup();
if (makerTxIdTextField != null)
makerTxIdTextField.cleanup();
if (takerTxIdTextField != null)
takerTxIdTextField.cleanup();
if (errorMessageListener != null)
trade.errorMessageProperty().removeListener(errorMessageListener);
if (errorMessageListener != null)
trade.errorMessageProperty().removeListener(errorMessageListener);
if (disputeStateSubscription != null)
disputeStateSubscription.unsubscribe();
if (disputeStateSubscription != null)
disputeStateSubscription.unsubscribe();
if (mediationResultStateSubscription != null)
mediationResultStateSubscription.unsubscribe();
if (mediationResultStateSubscription != null)
mediationResultStateSubscription.unsubscribe();
if (tradePeriodStateSubscription != null)
tradePeriodStateSubscription.unsubscribe();
if (tradePeriodStateSubscription != null)
tradePeriodStateSubscription.unsubscribe();
if (clockListener != null)
model.clockWatcher.removeListener(clockListener);
if (clockListener != null)
model.clockWatcher.removeListener(clockListener);
if (tradeStepInfo != null)
tradeStepInfo.setOnAction(null);
if (tradeStepInfo != null)
tradeStepInfo.setOnAction(null);
if (newBestBlockListener != null) {
model.dataModel.btcWalletService.removeNewBestBlockListener(newBestBlockListener);
}
if (acceptMediationResultPopup != null) {
acceptMediationResultPopup.hide();
acceptMediationResultPopup = null;
}
if (acceptMediationResultPopup != null) {
acceptMediationResultPopup.hide();
acceptMediationResultPopup = null;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -322,19 +332,35 @@ public abstract class TradeStepView extends AnchorPane {
Res.get("portfolio.pending.tradeInformation"));
GridPane.setColumnSpan(tradeInfoTitledGroupBg, 2);
final Tuple3<Label, TxIdTextField, VBox> labelTxIdTextFieldVBoxTuple3 =
// maker
final Tuple3<Label, TxIdTextField, VBox> labelMakerTxIdTextFieldVBoxTuple3 =
addTopLabelTxIdTextField(gridPane, gridRow,
Res.get("shared.depositTransactionId"), // TODO (woodser): need separate labels for maker and taker deposit tx ids
Layout.COMPACT_FIRST_ROW_DISTANCE);
GridPane.setColumnSpan(labelMakerTxIdTextFieldVBoxTuple3.third, 2);
makerTxIdTextField = labelMakerTxIdTextFieldVBoxTuple3.second;
String makerId = model.dataModel.makerTxId.get();
if (!makerId.isEmpty())
makerTxIdTextField.setup(makerId);
else
makerTxIdTextField.cleanup();
// taker
final Tuple3<Label, TxIdTextField, VBox> labelTakerTxIdTextFieldVBoxTuple3 =
addTopLabelTxIdTextField(gridPane, gridRow,
Res.get("shared.depositTransactionId"),
Layout.COMPACT_FIRST_ROW_DISTANCE);
GridPane.setColumnSpan(labelTxIdTextFieldVBoxTuple3.third, 2);
txIdTextField = labelTxIdTextFieldVBoxTuple3.second;
GridPane.setColumnSpan(labelTakerTxIdTextFieldVBoxTuple3.third, 2);
takerTxIdTextField = labelTakerTxIdTextFieldVBoxTuple3.second;
String id = model.dataModel.txId.get();
if (!id.isEmpty())
txIdTextField.setup(id);
String takerId = model.dataModel.takerTxId.get();
if (!takerId.isEmpty())
takerTxIdTextField.setup(takerId);
else
txIdTextField.cleanup();
takerTxIdTextField.cleanup();
if (model.dataModel.getTrade() != null) {
checkNotNull(model.dataModel.getTrade().getOffer(), "Offer must not be null in TradeStepView");
@ -439,6 +465,33 @@ public abstract class TradeStepView extends AnchorPane {
switch (disputeState) {
case NO_DISPUTE:
break;
case DISPUTE_REQUESTED:
if (tradeStepInfo != null) {
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
}
applyOnDisputeOpened();
ownDispute = model.dataModel.arbitrationManager.findOwnDispute(trade.getId());
ownDispute.ifPresent(dispute -> {
if (tradeStepInfo != null)
tradeStepInfo.setState(TradeStepInfo.State.IN_ARBITRATION_SELF_REQUESTED);
});
break;
case DISPUTE_STARTED_BY_PEER:
if (tradeStepInfo != null) {
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
}
applyOnDisputeOpened();
ownDispute = model.dataModel.arbitrationManager.findOwnDispute(trade.getId());
ownDispute.ifPresent(dispute -> {
if (tradeStepInfo != null)
tradeStepInfo.setState(TradeStepInfo.State.IN_ARBITRATION_PEER_REQUESTED);
});
break;
case DISPUTE_CLOSED:
break;
case MEDIATION_REQUESTED:
if (tradeStepInfo != null) {
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
@ -465,53 +518,55 @@ public abstract class TradeStepView extends AnchorPane {
});
break;
case MEDIATION_CLOSED:
if (tradeStepInfo != null) {
tradeStepInfo.setOnAction(e -> {
updateMediationResultState(false);
});
}
if (tradeStepInfo != null) {
tradeStepInfo.setOnAction(e -> {
updateMediationResultState(false);
});
}
if (tradeStepInfo != null) {
tradeStepInfo.setState(TradeStepInfo.State.MEDIATION_RESULT);
}
if (tradeStepInfo != null) {
tradeStepInfo.setState(TradeStepInfo.State.MEDIATION_RESULT);
}
updateMediationResultState(true);
break;
updateMediationResultState(true);
break;
case REFUND_REQUESTED:
if (tradeStepInfo != null) {
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
}
applyOnDisputeOpened();
ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId());
ownDispute.ifPresent(dispute -> {
if (tradeStepInfo != null)
tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_SELF_REQUESTED);
});
if (acceptMediationResultPopup != null) {
acceptMediationResultPopup.hide();
acceptMediationResultPopup = null;
}
break;
throw new RuntimeException("Unhandled case: " + Trade.DisputeState.REFUND_REQUESTED);
// if (tradeStepInfo != null) {
// tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
// }
// applyOnDisputeOpened();
//
// ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId());
// ownDispute.ifPresent(dispute -> {
// if (tradeStepInfo != null)
// tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_SELF_REQUESTED);
// });
//
// if (acceptMediationResultPopup != null) {
// acceptMediationResultPopup.hide();
// acceptMediationResultPopup = null;
// }
//
// break;
case REFUND_REQUEST_STARTED_BY_PEER:
if (tradeStepInfo != null) {
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
}
applyOnDisputeOpened();
ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId());
ownDispute.ifPresent(dispute -> {
if (tradeStepInfo != null)
tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_PEER_REQUESTED);
});
if (acceptMediationResultPopup != null) {
acceptMediationResultPopup.hide();
acceptMediationResultPopup = null;
}
break;
throw new RuntimeException("Unhandled case: " + Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER);
// if (tradeStepInfo != null) {
// tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
// }
// applyOnDisputeOpened();
//
// ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId());
// ownDispute.ifPresent(dispute -> {
// if (tradeStepInfo != null)
// tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_PEER_REQUESTED);
// });
//
// if (acceptMediationResultPopup != null) {
// acceptMediationResultPopup.hide();
// acceptMediationResultPopup = null;
// }
// break;
case REFUND_REQUEST_CLOSED:
break;
default:
@ -587,16 +642,20 @@ public abstract class TradeStepView extends AnchorPane {
return;
}
if (trade.getDepositTx() == null) {
log.error("trade.getDepositTx() was null at openMediationResultPopup. " +
"We add the trade to failed trades. TradeId={}", trade.getId());
new Popup().warning(Res.get("portfolio.pending.mediationResult.error.depositTxNull")).show();
return;
} else if (trade.getDelayedPayoutTx() == null) {
log.error("trade.getDelayedPayoutTx() was null at openMediationResultPopup. " +
"We add the trade to failed trades. TradeId={}", trade.getId());
new Popup().warning(Res.get("portfolio.pending.mediationResult.error.delayedPayoutTxNull")).show();
return;
if (trade instanceof MakerTrade && trade.getMakerDepositTx() == null) {
log.error("trade.getMakerDepositTx() was null at openMediationResultPopup. " +
"We add the trade to failed trades. TradeId={}", trade.getId());
//model.dataModel.addTradeToFailedTrades(); // TODO (woodser): new way to move trade to failed trades?
model.dataModel.onMoveInvalidTradeToFailedTrades(trade);;
new Popup().warning(Res.get("portfolio.pending.mediationResult.error.depositTxNull")).show(); // TODO (woodser): separate error messages for maker/taker
return;
} else if (trade instanceof TakerTrade && trade.getTakerDepositTx() == null) {
log.error("trade.getTakerDepositTx() was null at openMediationResultPopup. " +
"We add the trade to failed trades. TradeId={}", trade.getId());
//model.dataModel.addTradeToFailedTrades();
model.dataModel.onMoveInvalidTradeToFailedTrades(trade);;
new Popup().warning(Res.get("portfolio.pending.mediationResult.error.depositTxNull")).show();
return;
}
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
@ -607,10 +666,6 @@ public abstract class TradeStepView extends AnchorPane {
String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount;
String peersPayoutAmount = isMyRoleBuyer ? sellerPayoutAmount : buyerPayoutAmount;
long lockTime = trade.getDelayedPayoutTx().getLockTime();
int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight();
long remaining = lockTime - bestChainHeight;
String actionButtonText = hasSelfAccepted() ?
Res.get("portfolio.pending.mediationResult.popup.alreadyAccepted") : Res.get("shared.accept");
@ -627,15 +682,15 @@ public abstract class TradeStepView extends AnchorPane {
case SIG_MSG_IN_MAILBOX:
case SIG_MSG_SEND_FAILED:
message = Res.get("portfolio.pending.mediationResult.popup.selfAccepted.lockTimeOver",
FormattingUtils.getDateFromBlockHeight(remaining),
lockTime);
"N/A", // TODO (woodser): no timelocked tx in xmr, so part of popup message is n/a
-1);
break;
default:
message = Res.get("portfolio.pending.mediationResult.popup.info",
myPayoutAmount,
peersPayoutAmount,
FormattingUtils.getDateFromBlockHeight(remaining),
lockTime);
"N/A", // TODO (woodser): no timelocked tx in xmr, so part of popup message is n/a
-1);
break;
}
@ -720,19 +775,19 @@ public abstract class TradeStepView extends AnchorPane {
}
}
private void checkIfLockTimeIsOver() {
if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED) {
Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
if (delayedPayoutTx != null) {
long lockTime = delayedPayoutTx.getLockTime();
int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight();
long remaining = lockTime - bestChainHeight;
if (remaining <= 0) {
openMediationResultPopup(Res.get("portfolio.pending.mediationResult.popup.headline", trade.getShortId()));
}
}
}
}
// private void checkIfLockTimeIsOver() {
// if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED) {
// Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
// if (delayedPayoutTx != null) {
// long lockTime = delayedPayoutTx.getLockTime();
// int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight();
// long remaining = lockTime - bestChainHeight;
// if (remaining <= 0) {
// openMediationResultPopup(Res.get("portfolio.pending.mediationResult.popup.headline", trade.getShortId()));
// }
// }
// }
// }
protected void checkForTimeout() {
long unconfirmedHours = Duration.between(trade.getTakeOfferDate().toInstant(), Instant.now()).toHours();

View file

@ -17,12 +17,10 @@
package bisq.desktop.main.portfolio.pendingtrades.steps.buyer;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
import bisq.core.locale.Res;
import bisq.core.trade.TradeDataValidation;
public class BuyerStep1View extends TradeStepView {
@ -37,8 +35,8 @@ public class BuyerStep1View extends TradeStepView {
@Override
protected void onPendingTradesInitialized() {
super.onPendingTradesInitialized();
validatePayoutTx();
validateDepositInputs();
//validatePayoutTx(); // TODO (woodser): no payout tx in xmr integration, do something else?
//validateDepositInputs();
checkForTimeout();
}
@ -80,32 +78,32 @@ public class BuyerStep1View extends TradeStepView {
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void validatePayoutTx() {
try {
TradeDataValidation.validateDelayedPayoutTx(trade,
trade.getDelayedPayoutTx(),
model.dataModel.daoFacade,
model.dataModel.btcWalletService);
} catch (TradeDataValidation.MissingTxException ignore) {
// We don't react on those errors as a failed trade might get listed initially but getting removed from the
// trade manager after initPendingTrades which happens after activate might be called.
} catch (TradeDataValidation.ValidationException e) {
if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
}
}
}
// Verify that deposit tx inputs are matching the trade fee txs outputs.
private void validateDepositInputs() {
try {
TradeDataValidation.validateDepositInputs(trade);
} catch (TradeDataValidation.ValidationException e) {
if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
}
}
}
// private void validatePayoutTx() {
// try {
// TradeDataValidation.validateDelayedPayoutTx(trade,
// trade.getDelayedPayoutTx(),
// model.dataModel.daoFacade,
// model.dataModel.btcWalletService);
// } catch (TradeDataValidation.MissingTxException ignore) {
// // We don't react on those errors as a failed trade might get listed initially but getting removed from the
// // trade manager after initPendingTrades which happens after activate might be called.
// } catch (TradeDataValidation.ValidationException e) {
// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
// }
// }
// }
//
// // Verify that deposit tx inputs are matching the trade fee txs outputs.
// private void validateDepositInputs() {
// try {
// TradeDataValidation.validateDepositInputs(trade);
// } catch (TradeDataValidation.ValidationException e) {
// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
// }
// }
// }
}

View file

@ -81,7 +81,6 @@ import bisq.core.payment.payload.PaymentMethod;
import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload;
import bisq.core.payment.payload.WesternUnionAccountPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeDataValidation;
import bisq.core.user.DontShowAgainLookup;
import bisq.common.Timer;
@ -202,7 +201,7 @@ public class BuyerStep2View extends TradeStepView {
@Override
protected void onPendingTradesInitialized() {
super.onPendingTradesInitialized();
validatePayoutTx();
//validatePayoutTx(); // TODO (woodser): no payout tx in xmr integration, do something else?
model.checkTakerFeeTx(trade);
}
@ -608,19 +607,19 @@ public class BuyerStep2View extends TradeStepView {
}
}
private void validatePayoutTx() {
try {
TradeDataValidation.validateDelayedPayoutTx(trade,
trade.getDelayedPayoutTx(),
model.dataModel.daoFacade,
model.dataModel.btcWalletService);
} catch (TradeDataValidation.MissingTxException ignore) {
// We don't react on those errors as a failed trade might get listed initially but getting removed from the
// trade manager after initPendingTrades which happens after activate might be called.
} catch (TradeDataValidation.ValidationException e) {
if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
}
}
}
// private void validatePayoutTx() {
// try {
// TradeDataValidation.validateDelayedPayoutTx(trade,
// trade.getDelayedPayoutTx(),
// model.dataModel.daoFacade,
// model.dataModel.btcWalletService);
// } catch (TradeDataValidation.MissingTxException ignore) {
// // We don't react on those errors as a failed trade might get listed initially but getting removed from the
// // trade manager after initPendingTrades which happens after activate might be called.
// } catch (TradeDataValidation.ValidationException e) {
// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
// }
// }
// }
}

View file

@ -31,26 +31,17 @@ import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
import bisq.desktop.util.Layout;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.exceptions.InsufficientFundsException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.locale.Res;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.user.DontShowAgainLookup;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.core.util.validation.BtcAddressValidator;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.handlers.FaultHandler;
import bisq.common.handlers.ResultHandler;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import com.jfoenix.controls.JFXBadge;
@ -144,7 +135,7 @@ public class BuyerStep4View extends TradeStepView {
HBox hBox = new HBox();
hBox.setSpacing(10);
useSavingsWalletButton = new AutoTooltipButton(Res.get("portfolio.pending.step5_buyer.moveToBisqWallet"));
useSavingsWalletButton = new AutoTooltipButton(Res.get("portfolio.pending.step5_buyer.moveToHavenoWallet"));
useSavingsWalletButton.setDefaultButton(true);
useSavingsWalletButton.getStyleClass().add("action-button");
Label label = new AutoTooltipLabel(Res.get("shared.OR"));
@ -194,63 +185,64 @@ public class BuyerStep4View extends TradeStepView {
}
private void reviewWithdrawal() {
Coin amount = trade.getPayoutAmount();
BtcWalletService walletService = model.dataModel.btcWalletService;
AddressEntry fromAddressesEntry = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
String fromAddresses = fromAddressesEntry.getAddressString();
String toAddresses = withdrawAddressTextField.getText();
if (new BtcAddressValidator().validate(toAddresses).isValid) {
Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress());
try {
Transaction feeEstimationTransaction = walletService.getFeeEstimationTransaction(fromAddresses, toAddresses, amount, AddressEntry.Context.TRADE_PAYOUT);
Coin fee = feeEstimationTransaction.getFee();
Coin receiverAmount = amount.subtract(fee);
if (balance.isZero()) {
new Popup().warning(Res.get("portfolio.pending.step5_buyer.alreadyWithdrawn")).show();
model.dataModel.tradeManager.onTradeCompleted(trade);
} else {
if (toAddresses.isEmpty()) {
validateWithdrawAddress();
} else if (Restrictions.isAboveDust(receiverAmount)) {
CoinFormatter formatter = model.btcFormatter;
int txVsize = feeEstimationTransaction.getVsize();
double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);
double vkb = txVsize / 1000d;
String recAmount = formatter.formatCoinWithCode(receiverAmount);
new Popup().headLine(Res.get("portfolio.pending.step5_buyer.confirmWithdrawal"))
.confirmation(Res.get("shared.sendFundsDetailsWithFee",
formatter.formatCoinWithCode(amount),
fromAddresses,
toAddresses,
formatter.formatCoinWithCode(fee),
feePerVbyte,
vkb,
recAmount))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> doWithdrawal(amount, fee))
.closeButtonText(Res.get("shared.cancel"))
.onClose(() -> {
useSavingsWalletButton.setDisable(false);
withdrawToExternalWalletButton.setDisable(false);
})
.show();
} else {
new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show();
}
}
} catch (AddressFormatException e) {
validateWithdrawAddress();
} catch (AddressEntryException e) {
log.error(e.getMessage());
} catch (InsufficientFundsException e) {
log.error(e.getMessage());
e.printStackTrace();
new Popup().warning(e.getMessage()).show();
}
} else {
new Popup().warning(Res.get("validation.btc.invalidAddress")).show();
}
throw new RuntimeException("BuyerStep4View.reviewWithdrawal() not yet updated for XMR");
// Coin amount = trade.getPayoutAmount();
// BtcWalletService walletService = model.dataModel.btcWalletService;
//
// AddressEntry fromAddressesEntry = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
// String fromAddresses = fromAddressesEntry.getAddressString();
// String toAddresses = withdrawAddressTextField.getText();
// if (new BtcAddressValidator().validate(toAddresses).isValid) {
// Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress());
// try {
// Transaction feeEstimationTransaction = walletService.getFeeEstimationTransaction(fromAddresses, toAddresses, amount, AddressEntry.Context.TRADE_PAYOUT);
// Coin fee = feeEstimationTransaction.getFee();
// Coin receiverAmount = amount.subtract(fee);
// if (balance.isZero()) {
// new Popup().warning(Res.get("portfolio.pending.step5_buyer.alreadyWithdrawn")).show();
// model.dataModel.tradeManager.onTradeCompleted(trade);
// } else {
// if (toAddresses.isEmpty()) {
// validateWithdrawAddress();
// } else if (Restrictions.isAboveDust(receiverAmount)) {
// CoinFormatter formatter = model.btcFormatter;
// int txVsize = feeEstimationTransaction.getVsize();
// double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);
// double vkb = txVsize / 1000d;
// String recAmount = formatter.formatCoinWithCode(receiverAmount);
// new Popup().headLine(Res.get("portfolio.pending.step5_buyer.confirmWithdrawal"))
// .confirmation(Res.get("shared.sendFundsDetailsWithFee",
// formatter.formatCoinWithCode(amount),
// fromAddresses,
// toAddresses,
// formatter.formatCoinWithCode(fee),
// feePerVbyte,
// vkb,
// recAmount))
// .actionButtonText(Res.get("shared.yes"))
// .onAction(() -> doWithdrawal(amount, fee))
// .closeButtonText(Res.get("shared.cancel"))
// .onClose(() -> {
// useSavingsWalletButton.setDisable(false);
// withdrawToExternalWalletButton.setDisable(false);
// })
// .show();
// } else {
// new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show();
// }
// }
// } catch (AddressFormatException e) {
// validateWithdrawAddress();
// } catch (AddressEntryException e) {
// log.error(e.getMessage());
// } catch (InsufficientFundsException e) {
// log.error(e.getMessage());
// e.printStackTrace();
// new Popup().warning(e.getMessage()).show();
// }
// } else {
// new Popup().warning(Res.get("validation.btc.invalidAddress")).show();
// }
}
private void doWithdrawal(Coin amount, Coin fee) {
@ -264,12 +256,13 @@ public class BuyerStep4View extends TradeStepView {
else
new Popup().error(errorMessage).show();
};
if (model.dataModel.btcWalletService.isEncrypted()) {
UserThread.runAfter(() -> model.dataModel.walletPasswordWindow.onAesKey(aesKey ->
doWithdrawRequest(toAddress, amount, fee, aesKey, resultHandler, faultHandler))
.show(), 300, TimeUnit.MILLISECONDS);
} else
doWithdrawRequest(toAddress, amount, fee, null, resultHandler, faultHandler);
if (true) throw new RuntimeException("BuyerStep4View.doWithdrawal() not yet updated for XMR");
// if (model.dataModel.btcWalletService.isEncrypted()) {
// UserThread.runAfter(() -> model.dataModel.walletPasswordWindow.onAesKey(aesKey ->
// doWithdrawRequest(toAddress, amount, fee, aesKey, resultHandler, faultHandler))
// .show(), 300, TimeUnit.MILLISECONDS);
// } else
// doWithdrawRequest(toAddress, amount, fee, null, resultHandler, faultHandler);
}
private void doWithdrawRequest(String toAddress,
@ -296,7 +289,7 @@ public class BuyerStep4View extends TradeStepView {
private void handleTradeCompleted() {
useSavingsWalletButton.setDisable(true);
withdrawToExternalWalletButton.setDisable(true);
model.dataModel.btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
model.dataModel.xmrWalletService.swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT);
openTradeFeedbackWindow();
}

View file

@ -17,13 +17,14 @@
package bisq.desktop.main.portfolio.pendingtrades.steps.seller;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
import bisq.core.locale.Res;
import bisq.core.trade.TradeDataValidation;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SellerStep1View extends TradeStepView {
///////////////////////////////////////////////////////////////////////////////////////////
@ -37,7 +38,8 @@ public class SellerStep1View extends TradeStepView {
@Override
protected void onPendingTradesInitialized() {
super.onPendingTradesInitialized();
validateDepositInputs();
//validateDepositInputs();
log.warn("Need to validate fee and/or deposit txs in SellerStep1View for XMR?"); // TODO (woodser): need to validate fee and/or deposit txs in SellerStep1View?
checkForTimeout();
}
@ -77,16 +79,16 @@ public class SellerStep1View extends TradeStepView {
// Private
///////////////////////////////////////////////////////////////////////////////////////////
// Verify that deposit tx inputs are matching the trade fee txs outputs.
private void validateDepositInputs() {
try {
TradeDataValidation.validateDepositInputs(trade);
} catch (TradeDataValidation.ValidationException e) {
if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
}
}
}
// // Verify that deposit tx inputs are matching the trade fee txs outputs.
// private void validateDepositInputs() {
// try {
// TradeDataValidation.validateDepositInputs(trade);
// } catch (TradeDataValidation.ValidationException e) {
// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) {
// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show();
// }
// }
// }
}

View file

@ -33,7 +33,6 @@ import bisq.common.crypto.Hash;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
import java.security.KeyPair;
import java.security.PublicKey;
import lombok.extern.slf4j.Slf4j;
@ -49,23 +48,24 @@ public class DisputeSummaryVerification {
public static String signAndApply(DisputeManager<? extends DisputeList<Dispute>> disputeManager,
DisputeResult disputeResult,
String textToSign) {
throw new RuntimeException("DisputeSummaryVerification.signAndApply() not implemented");
byte[] hash = Hash.getSha256Hash(textToSign);
KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair();
String sigAsHex;
try {
byte[] signature = Sig.sign(signatureKeyPair.getPrivate(), hash);
sigAsHex = Utilities.encodeToHex(signature);
disputeResult.setArbitratorSignature(signature);
} catch (CryptoException e) {
sigAsHex = "Signing failed";
}
return Res.get("disputeSummaryWindow.close.msgWithSig",
textToSign,
SEPARATOR1,
sigAsHex,
SEPARATOR2);
// byte[] hash = Hash.getSha256Hash(textToSign);
// KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair();
// String sigAsHex;
// try {
// byte[] signature = Sig.sign(signatureKeyPair.getPrivate(), hash);
// sigAsHex = Utilities.encodeToHex(signature);
// disputeResult.setArbitratorSignature(signature);
// } catch (CryptoException e) {
// sigAsHex = "Signing failed";
// }
//
// return Res.get("disputeSummaryWindow.close.msgWithSig",
// textToSign,
// SEPARATOR1,
// sigAsHex,
// SEPARATOR2);
}
public static String verifySignature(String input,

View file

@ -18,7 +18,6 @@
package bisq.desktop.main.support.dispute.agent.arbitration;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.ContractWindow;
import bisq.desktop.main.overlays.windows.DisputeSummaryWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
@ -27,7 +26,6 @@ import bisq.desktop.main.support.dispute.agent.DisputeAgentView;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.alert.PrivateNotificationManager;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeSession;
@ -96,11 +94,11 @@ public class ArbitratorView extends DisputeAgentView {
// Only cases with protocolVersion 1 are candidates for legacy arbitration.
// This code path is not tested and it is not assumed that it is still be used as old arbitrators would use
// their old Bisq version if still cases are pending.
if (protocolVersion == 1) {
// if (protocolVersion == 1) {
chatPopup.closeChat();
disputeSummaryWindow.show(dispute);
} else {
new Popup().warning(Res.get("support.wrongVersion", protocolVersion)).show();
}
// } else {
// new Popup().warning(Res.get("support.wrongVersion", protocolVersion)).show();
// }
}
}

View file

@ -113,7 +113,8 @@ public class MediationClientView extends DisputeClientView {
@Override
protected NodeAddress getAgentNodeAddress(Contract contract) {
return contract.getMediatorNodeAddress();
throw new RuntimeException("MediationClientView.getAgentNodeAddress() not implementd for XMR");
//return contract.getMediatorNodeAddress();
}
@Override

View file

@ -81,7 +81,8 @@ public class RefundClientView extends DisputeClientView {
@Override
protected NodeAddress getAgentNodeAddress(Contract contract) {
return contract.getRefundAgentNodeAddress();
throw new RuntimeException("RefundClientView.getAgentNodeAddress() not implementd for XMR");
//return contract.getRefundAgentNodeAddress();
}
@Override