support funding tabs: receive, send, transactions

This commit is contained in:
woodser 2022-07-14 09:05:48 -04:00
parent c71c61d1bb
commit fb3745c6df
18 changed files with 432 additions and 723 deletions

View file

@ -171,7 +171,7 @@ class CoreWalletsService {
String getXmrNewSubaddress() {
accountService.checkAccountOpen();
return xmrWalletService.getWallet().createSubaddress(0).getAddress();
return xmrWalletService.getNewAddressEntry().getAddressString();
}
List<MoneroTxWallet> getXmrTxs() {

View file

@ -25,8 +25,8 @@ public class XmrBalanceListener {
public XmrBalanceListener() {
}
public XmrBalanceListener(Integer accountIndex) {
this.subaddressIndex = accountIndex;
public XmrBalanceListener(Integer subaddressIndex) {
this.subaddressIndex = subaddressIndex;
}
public Integer getSubaddressIndex() {

View file

@ -55,6 +55,7 @@ import monero.wallet.model.MoneroCheckTx;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroSubaddress;
import monero.wallet.model.MoneroTransferQuery;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
@ -70,6 +71,7 @@ public class XmrWalletService {
// Monero configuration
// TODO: don't hard code configuration, inject into classes?
public static final int NUM_BLOCKS_UNLOCK = 10;
private static final MoneroNetworkType MONERO_NETWORK_TYPE = getMoneroNetworkType();
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
@ -634,6 +636,7 @@ public class XmrWalletService {
// clear wallets
wallet = null;
multisigWallets.clear();
walletListeners.clear();
}
private void backupWallet(String walletName) {
@ -649,7 +652,17 @@ public class XmrWalletService {
}
// ----------------------------- LEGACY APP -------------------------------
public XmrAddressEntry getNewAddressEntry() {
return getOrCreateAddressEntry(XmrAddressEntry.Context.AVAILABLE, Optional.empty());
}
public XmrAddressEntry getFreshAddressEntry() {
List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries();
if (unusedAddressEntries.isEmpty()) return getNewAddressEntry();
else return unusedAddressEntries.get(0);
}
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE);
if (!available.isPresent()) return null;
@ -761,12 +774,21 @@ public class XmrWalletService {
return xmrAddressEntryList.getAddressEntriesAsListImmutable();
}
public boolean isSubaddressUnused(int subaddressIndex) {
return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0;
// return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed()
// does not include unconfirmed funds
public List<XmrAddressEntry> getUnusedAddressEntries() {
return getAvailableAddressEntries().stream()
.filter(e -> isSubaddressUnused(e.getSubaddressIndex()))
.collect(Collectors.toList());
}
public boolean isSubaddressUnused(int subaddressIndex) {
return getNumTxOutputsForSubaddress(subaddressIndex) == 0;
}
public Coin getBalanceForAddress(String address) {
return getBalanceForSubaddress(wallet.getAddressIndex(address).getIndex());
}
// TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
public Coin getBalanceForSubaddress(int subaddressIndex) {
// get subaddress balance
@ -786,6 +808,24 @@ public class XmrWalletService {
return Coin.valueOf(balance.longValueExact());
}
public int getNumTxOutputsForSubaddress(int subaddressIndex) {
// get txs with transfers to the subaddress
List<MoneroTxWallet> txs = wallet.getTxs(new MoneroTxQuery()
.setTransferQuery((new MoneroTransferQuery()
.setAccountIndex(0)
.setSubaddressIndex(subaddressIndex)
.setIsIncoming(true)))
.setIncludeOutputs(true));
// count num outputs
int numUnspentOutputs = 0;
for (MoneroTxWallet tx : txs) {
numUnspentOutputs += tx.isConfirmed() ? tx.getOutputs().size() : 1; // TODO: monero-project does not provide outputs for unconfirmed txs
}
return numUnspentOutputs;
}
public Coin getAvailableConfirmedBalance() {
return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO;
}

View file

@ -26,7 +26,6 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.support.dispute.refund.RefundResultState;
import bisq.core.support.messages.ChatMessage;
@ -886,7 +885,7 @@ public abstract class Trade implements Tradable, Model {
// check if deposit txs unlocked
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(0).getHeight()) + 9;
long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(0).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK - 1;
if (havenoWallet.getHeight() >= unlockHeight) {
setConfirmedState();
return;
@ -926,7 +925,7 @@ public abstract class Trade implements Tradable, Model {
// compute unlock height
if (unlockHeight == null && txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(0).getHeight()) + 9;
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(0).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK - 1;
}
// check if txs unlocked

View file

@ -25,6 +25,10 @@ public class ParsingUtils {
return centinerosToAtomicUnits(coin.value);
}
public static double coinToXmr(Coin coin) {
return atomicUnitsToXmr(coinToAtomicUnits(coin));
}
public static BigInteger centinerosToAtomicUnits(long centineros) {
return BigInteger.valueOf(centineros).multiply(ParsingUtils.CENTINEROS_AU_MULTIPLIER);
}
@ -39,7 +43,11 @@ public class ParsingUtils {
public static long atomicUnitsToCentineros(BigInteger atomicUnits) {
return atomicUnits.divide(CENTINEROS_AU_MULTIPLIER).longValueExact();
}
}
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
return Coin.valueOf(atomicUnitsToCentineros(atomicUnits));
}
public static double atomicUnitsToXmr(BigInteger atomicUnits) {
return new BigDecimal(atomicUnits).divide(new BigDecimal(XMR_AU_MULTIPLIER)).doubleValue();

View file

@ -127,7 +127,7 @@ shared.noDateAvailable=No date available
shared.noDetailsAvailable=No details available
shared.notUsedYet=Not used yet
shared.date=Date
shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3} ({4} satoshis/vbyte)\nTransaction vsize: {5} vKb\n\nThe recipient will receive: {6}\n\nAre you sure you want to withdraw this amount?
shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3}\n\nThe recipient will receive: {4}\n\nAre you sure you want to withdraw this amount?
# suppress inspection "TrailingSpacesInProperty"
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
shared.copyToClipboard=Copy to clipboard

View file

@ -20,19 +20,17 @@ package bisq.desktop.components;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.user.BlockChainExplorer;
import bisq.core.user.Preferences;
import bisq.common.util.Utilities;
import org.bitcoinj.core.TransactionConfidence;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import java.math.BigInteger;
import com.jfoenix.controls.JFXTextField;
import javafx.scene.control.Label;
@ -42,21 +40,23 @@ import javafx.scene.layout.AnchorPane;
import lombok.Getter;
import lombok.Setter;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
import javax.annotation.Nullable;
public class TxIdTextField extends AnchorPane {
@Setter
private static Preferences preferences;
@Setter
private static BtcWalletService walletService;
private static XmrWalletService xmrWalletService;
@Getter
private final TextField textField;
private final Tooltip progressIndicatorTooltip;
private final TxConfidenceIndicator txConfidenceIndicator;
private final Label copyIcon, blockExplorerIcon, missingTxWarningIcon;
private TxConfidenceListener txConfidenceListener;
private MoneroWalletListener txUpdater;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -113,8 +113,10 @@ public class TxIdTextField extends AnchorPane {
}
public void setup(@Nullable String txId) {
if (txConfidenceListener != null)
walletService.removeTxConfidenceListener(txConfidenceListener);
if (txUpdater != null) {
xmrWalletService.removeWalletListener(txUpdater);
txUpdater = null;
}
if (txId == null) {
textField.setText(Res.get("shared.na"));
@ -128,15 +130,22 @@ public class TxIdTextField extends AnchorPane {
missingTxWarningIcon.setManaged(true);
return;
}
txConfidenceListener = new TxConfidenceListener(txId) {
// listen for tx updates
// TODO: this only listens for new blocks, listen for double spend
txUpdater = new MoneroWalletListener() {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
public void onNewBlock(long height) {
updateConfidence(txId);
}
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
updateConfidence(txId);
}
};
walletService.addTxConfidenceListener(txConfidenceListener);
updateConfidence(walletService.getConfidenceForTxId(txId));
xmrWalletService.addWalletListener(txUpdater);
updateConfidence(txId);
textField.setText(txId);
textField.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId));
@ -145,9 +154,10 @@ public class TxIdTextField extends AnchorPane {
}
public void cleanup() {
if (walletService != null && txConfidenceListener != null)
walletService.removeTxConfidenceListener(txConfidenceListener);
if (xmrWalletService != null && txUpdater != null) {
xmrWalletService.removeWalletListener(txUpdater);
txUpdater = null;
}
textField.setOnMouseClicked(null);
blockExplorerIcon.setOnMouseClicked(null);
copyIcon.setOnMouseClicked(null);
@ -165,9 +175,15 @@ public class TxIdTextField extends AnchorPane {
}
}
private void updateConfidence(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, progressIndicatorTooltip, txConfidenceIndicator);
if (confidence != null) {
private void updateConfidence(String txId) {
MoneroTxWallet tx = null;
try {
tx = xmrWalletService.getWallet().getTx(txId);
} catch (Exception e) {
// do nothing
}
GUIUtil.updateConfidence(tx, progressIndicatorTooltip, txConfidenceIndicator);
if (tx != null) {
if (txConfidenceIndicator.getProgress() != 0) {
txConfidenceIndicator.setVisible(true);
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);

View file

@ -200,8 +200,8 @@ 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)) {
@ -304,7 +304,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
});
HBox primaryNav = new HBox(marketButton, getNavigationSeparator(), buyButton, getNavigationSeparator(),
sellButton, getNavigationSeparator(), portfolioButtonWithBadge, getNavigationSeparator());
sellButton, getNavigationSeparator(), portfolioButtonWithBadge, getNavigationSeparator(), fundsButton);
primaryNav.setAlignment(Pos.CENTER_LEFT);
primaryNav.getStyleClass().add("nav-primary");

View file

@ -43,7 +43,7 @@ import bisq.core.alert.PrivateNotificationManager;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CryptoCurrency;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
@ -153,7 +153,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
@Inject
public MainViewModel(HavenoSetup bisqSetup,
CoreMoneroConnectionsService connectionService,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
User user,
BalancePresentation balancePresentation,
TradePresentation tradePresentation,
@ -202,7 +202,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
TxIdTextField.setPreferences(preferences);
TxIdTextField.setWalletService(btcWalletService);
TxIdTextField.setXmrWalletService(xmrWalletService);
GUIUtil.setFeeService(feeService);
GUIUtil.setPreferences(preferences);

View file

@ -17,117 +17,60 @@
package bisq.desktop.main.funds.deposit;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.listeners.TxConfidenceListener;
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.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import com.google.common.base.Suppliers;
import javafx.scene.control.Tooltip;
import java.math.BigInteger;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Coin;
@Slf4j
class DepositListItem {
private final StringProperty balance = new SimpleStringProperty();
private final BtcWalletService walletService;
private final XmrAddressEntry addressEntry;
private final XmrWalletService xmrWalletService;
private Coin balanceAsCoin;
private final String addressString;
private String usage = "-";
private TxConfidenceListener txConfidenceListener;
private BalanceListener balanceListener;
private XmrBalanceListener balanceListener;
private int numTxOutputs = 0;
private final Supplier<LazyFields> lazyFieldsSupplier;
private static class LazyFields {
TxConfidenceIndicator txConfidenceIndicator;
Tooltip tooltip;
}
DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter) {
this.xmrWalletService = xmrWalletService;
this.addressEntry = addressEntry;
private LazyFields lazy() {
return lazyFieldsSupplier.get();
}
DepositListItem(AddressEntry addressEntry, BtcWalletService walletService, CoinFormatter formatter) {
this.walletService = walletService;
addressString = addressEntry.getAddressString();
Address address = addressEntry.getAddress();
TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
// 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);
if (confidence != null) {
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
}
}});
if (confidence != null) {
txConfidenceListener = new TxConfidenceListener(confidence.getTransactionHash().toString()) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
}
};
walletService.addTxConfidenceListener(txConfidenceListener);
}
balanceListener = new BalanceListener(address) {
balanceListener = new XmrBalanceListener(addressEntry.getSubaddressIndex()) {
@Override
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
DepositListItem.this.balanceAsCoin = balanceAsCoin;
public void onBalanceChanged(BigInteger balance) {
DepositListItem.this.balanceAsCoin = ParsingUtils.atomicUnitsToCoin(balance);
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
var confidence = walletService.getConfidenceForTxId(tx.getTxId().toString());
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
updateUsage(address);
updateUsage(addressEntry.getSubaddressIndex());
}
};
walletService.addBalanceListener(balanceListener);
xmrWalletService.addBalanceListener(balanceListener);
balanceAsCoin = walletService.getBalanceForAddress(address);
balanceAsCoin = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); // TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
balanceAsCoin = Coin.valueOf(ParsingUtils.atomicUnitsToCentineros(balanceAsCoin.longValue())); // in centineros
balance.set(formatter.formatCoin(balanceAsCoin));
updateUsage(address);
updateUsage(addressEntry.getSubaddressIndex());
}
private void updateUsage(Address address) {
numTxOutputs = walletService.getNumTxOutputsForAddress(address);
private void updateUsage(int subaddressIndex) {
numTxOutputs = xmrWalletService.getNumTxOutputsForSubaddress(addressEntry.getSubaddressIndex());
usage = numTxOutputs == 0 ? Res.get("funds.deposit.unused") : Res.get("funds.deposit.usedInTx", numTxOutputs);
}
public void cleanup() {
walletService.removeTxConfidenceListener(txConfidenceListener);
walletService.removeBalanceListener(balanceListener);
}
public TxConfidenceIndicator getTxConfidenceIndicator() {
return lazy().txConfidenceIndicator;
xmrWalletService.removeBalanceListener(balanceListener);
}
public String getAddressString() {
return addressString;
return addressEntry.getAddressString();
}
public String getUsage() {
@ -149,4 +92,8 @@ class DepositListItem {
public int getNumTxOutputs() {
return numTxOutputs;
}
public int getNumConfirmationsSinceFirstUsed() {
throw new RuntimeException("Not implemented");
}
}

View file

@ -30,9 +30,9 @@ import bisq.desktop.main.overlays.windows.QRCodeWindow;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
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.user.Preferences;
import bisq.core.util.FormattingUtils;
@ -41,21 +41,16 @@ import bisq.core.util.coin.CoinFormatter;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.config.Config;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Transaction;
import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;
import javax.inject.Inject;
import javax.inject.Named;
import monero.wallet.model.MoneroTxConfig;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
@ -85,7 +80,7 @@ import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
@ -105,17 +100,16 @@ public class DepositView extends ActivatableView<VBox, Void> {
private ImageView qrCodeImageView;
private AddressTextField addressTextField;
private Button generateNewAddressButton;
private CheckBox generateNewAddressSegwitCheckbox;
private TitledGroupBg titledGroupBg;
private InputTextField amountTextField;
private final BtcWalletService walletService;
private final XmrWalletService xmrWalletService;
private final Preferences preferences;
private final CoinFormatter formatter;
private String paymentLabelString;
private final ObservableList<DepositListItem> observableList = FXCollections.observableArrayList();
private final SortedList<DepositListItem> sortedList = new SortedList<>(observableList);
private BalanceListener balanceListener;
private XmrBalanceListener balanceListener;
private Subscription amountTextFieldSubscription;
private ChangeListener<DepositListItem> tableViewSelectionListener;
private int gridRow = 0;
@ -125,10 +119,10 @@ public class DepositView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private DepositView(BtcWalletService walletService,
private DepositView(XmrWalletService xmrWalletService,
Preferences preferences,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter) {
this.walletService = walletService;
this.xmrWalletService = xmrWalletService;
this.preferences = preferences;
this.formatter = formatter;
}
@ -143,7 +137,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
usageColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.usage")));
// trigger creation of at least 1 savings address
walletService.getFreshAddressEntry();
xmrWalletService.getFreshAddressEntry();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.deposit.noAddresses")));
@ -161,7 +155,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString));
balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsCoin));
confirmationsColumn.setComparator(Comparator.comparingDouble(o -> o.getTxConfidenceIndicator().getProgress()));
confirmationsColumn.setComparator(Comparator.comparingInt(o -> o.getNumConfirmationsSinceFirstUsed()));
usageColumn.setComparator(Comparator.comparingInt(DepositListItem::getNumTxOutputs));
tableView.getSortOrder().add(usageColumn);
tableView.setItems(sortedList);
@ -174,7 +168,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
() -> UserThread.runAfter(
() -> new QRCodeWindow(getBitcoinURI()).show(),
() -> new QRCodeWindow(getPaymentUri()).show(),
200, TimeUnit.MILLISECONDS)));
GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setRowSpan(qrCodeImageView, 4);
@ -201,23 +195,17 @@ public class DepositView extends ActivatableView<VBox, Void> {
Tuple3<Button, CheckBox, HBox> buttonCheckBoxHBox = addButtonCheckBoxWithBox(gridPane, ++gridRow,
Res.get("funds.deposit.generateAddress"),
Res.get("funds.deposit.generateAddressSegwit"),
null,
15);
buttonCheckBoxHBox.third.setSpacing(25);
generateNewAddressButton = buttonCheckBoxHBox.first;
generateNewAddressSegwitCheckbox = buttonCheckBoxHBox.second;
generateNewAddressSegwitCheckbox.setAllowIndeterminate(false);
generateNewAddressSegwitCheckbox.setSelected(true);
generateNewAddressButton.setOnAction(event -> {
boolean segwit = generateNewAddressSegwitCheckbox.isSelected();
NetworkParameters params = Config.baseCurrencyNetworkParameters();
boolean hasUnUsedAddress = observableList.stream().anyMatch(e -> e.getNumTxOutputs() == 0
&& (Address.fromString(params, e.getAddressString()) instanceof SegwitAddress) == segwit);
boolean hasUnUsedAddress = observableList.stream().anyMatch(e -> e.getNumTxOutputs() == 0);
if (hasUnUsedAddress) {
new Popup().warning(Res.get("funds.deposit.selectUnused")).show();
} else {
AddressEntry newSavingsAddressEntry = walletService.getFreshAddressEntry(segwit);
XmrAddressEntry newSavingsAddressEntry = xmrWalletService.getNewAddressEntry();
updateList();
observableList.stream()
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
@ -226,9 +214,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
}
});
balanceListener = new BalanceListener() {
balanceListener = new XmrBalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
public void onBalanceChanged(BigInteger balance) {
updateList();
}
};
@ -243,7 +231,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
updateList();
walletService.addBalanceListener(balanceListener);
xmrWalletService.addBalanceListener(balanceListener);
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
addressTextField.setAmountAsCoin(ParsingUtils.parseToCoin(t, formatter));
updateQRCode();
@ -258,7 +246,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
tableView.getSelectionModel().selectedItemProperty().removeListener(tableViewSelectionListener);
sortedList.comparatorProperty().unbind();
observableList.forEach(DepositListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
xmrWalletService.removeBalanceListener(balanceListener);
amountTextFieldSubscription.unsubscribe();
}
@ -267,7 +255,6 @@ public class DepositView extends ActivatableView<VBox, Void> {
// UI handlers
///////////////////////////////////////////////////////////////////////////////////////////
private void fillForm(String address) {
titledGroupBg.setVisible(true);
titledGroupBg.setManaged(true);
@ -287,7 +274,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
private void updateQRCode() {
if (addressTextField.getAddress() != null && !addressTextField.getAddress().isEmpty()) {
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.from(getPaymentUri())
.withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border
.to(ImageType.PNG)
.stream()
@ -309,8 +296,8 @@ public class DepositView extends ActivatableView<VBox, Void> {
private void updateList() {
observableList.forEach(DepositListItem::cleanup);
observableList.clear();
walletService.getAvailableAddressEntries()
.forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter)));
xmrWalletService.getAvailableAddressEntries()
.forEach(e -> observableList.add(new DepositListItem(e, xmrWalletService, formatter)));
}
private Coin getAmountAsCoin() {
@ -318,10 +305,11 @@ public class DepositView extends ActivatableView<VBox, Void> {
}
@NotNull
private String getBitcoinURI() {
return GUIUtil.getBitcoinURI(addressTextField.getAddress(),
getAmountAsCoin(),
paymentLabelString);
private String getPaymentUri() {
return xmrWalletService.getWallet().createPaymentUri(new MoneroTxConfig()
.setAddress(addressTextField.getAddress())
.setAmount(ParsingUtils.coinToAtomicUnits(getAmountAsCoin()))
.setNote(paymentLabelString));
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -434,7 +422,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getTxConfidenceIndicator());
//setGraphic(item.getTxConfidenceIndicator());
} else {
setGraphic(null);
}

View file

@ -17,34 +17,34 @@
package bisq.desktop.main.funds.transactions;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.XmrWalletService;
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.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Coin;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import javafx.scene.control.Tooltip;
import java.math.BigInteger;
import java.util.Date;
import java.util.function.Supplier;
import java.util.Optional;
import javafx.scene.control.Tooltip;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import monero.wallet.model.MoneroIncomingTransfer;
import monero.wallet.model.MoneroOutgoingTransfer;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
import org.bitcoinj.core.Coin;
@Slf4j
class TransactionsListItem {
private final XmrWalletService xmrWalletService;
private final CoinFormatter formatter;
private String dateString;
private final Date date;
@ -54,12 +54,11 @@ class TransactionsListItem {
private String details = "";
private String addressString = "";
private String direction = "";
private TxConfidenceListener txConfidenceListener;
private boolean received;
private boolean detailsAvailable;
private Coin amountAsCoin = Coin.ZERO;
private String memo = "";
private int confirmations = 0;
private long confirmations = 0;
@Getter
private final boolean isDustAttackTx;
private boolean initialTxConfidenceVisibility = true;
@ -77,187 +76,140 @@ class TransactionsListItem {
// used at exportCSV
TransactionsListItem() {
date = null;
xmrWalletService = null;
txId = null;
formatter = null;
isDustAttackTx = false;
lazyFieldsSupplier = null;
}
TransactionsListItem(MoneroTxWallet transaction,
TransactionsListItem(MoneroTxWallet tx,
XmrWalletService xmrWalletService,
TransactionAwareTradable transactionAwareTradable,
CoinFormatter formatter,
long ignoreDustThreshold) {
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
// 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);
// 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);
// outgoing = true;
// break;
// }
// } else {
// addressString = WalletService.getAddressStringFromOutput(output);
// outgoing = (valueSentToMe.getValue() < valueSentFromMe.getValue());
// if (!outgoing) {
// direction = Res.get("funds.tx.direction.receivedWith");
// received = true;
// }
// }
// }
//
// if (outgoing) {
// direction = Res.get("funds.tx.direction.sentTo");
// received = false;
// }
// }
//
//
// 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;
// }
// // 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);
this.formatter = formatter;
this.memo = tx.getNote();
this.txId = tx.getHash();
Optional<Tradable> optionalTradable = Optional.ofNullable(transactionAwareTradable)
.map(TransactionAwareTradable::asTradable);
Coin valueSentToMe = ParsingUtils.atomicUnitsToCoin(tx.getIncomingAmount() == null ? new BigInteger("0") : tx.getIncomingAmount());
Coin valueSentFromMe = ParsingUtils.atomicUnitsToCoin(tx.getOutgoingAmount() == null ? new BigInteger("0") : tx.getOutgoingAmount());
if (tx.getTransfers().get(0).isIncoming()) {
addressString = ((MoneroIncomingTransfer) tx.getTransfers().get(0)).getAddress();
} else {
MoneroOutgoingTransfer transfer = (MoneroOutgoingTransfer) tx.getTransfers().get(0);
if (transfer.getDestinations() != null) addressString = transfer.getDestinations().get(0).getAddress();
else addressString = "unavailable";
}
if (valueSentFromMe.isZero()) {
amountAsCoin = valueSentToMe;
direction = Res.get("funds.tx.direction.receivedWith");
received = true;
} else {
amountAsCoin = valueSentFromMe.multiply(-1);
received = false;
direction = Res.get("funds.tx.direction.sentTo");
}
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;
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.getSelf().getDepositTxHash() != null &&
trade.getSelf().getDepositTxHash().equals(txId)) {
details = Res.get("funds.tx.multiSigDeposit", tradeId);
} else if (trade.getPayoutTxId() != null &&
trade.getPayoutTxId().equals(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);
}
} 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 {
details = Res.get("funds.tx.unknown", tradeId);
}
}
}
}
} else {
if (amountAsCoin.isZero()) {
details = Res.get("funds.tx.noFundsFromDispute");
}
}
this.date = new Date(0); // TODO: convert height to date
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);
GUIUtil.updateConfidence(tx, tooltip, txConfidenceIndicator);
confirmations = tx.getNumConfirmations();
}});
// listen for tx updates
// TODO: this only listens for new blocks, listen for double spend
xmrWalletService.addWalletListener(new MoneroWalletListener() {
@Override
public void onNewBlock(long height) {
MoneroTxWallet tx = xmrWalletService.getWallet().getTx(txId);
GUIUtil.updateConfidence(tx, lazy().tooltip, lazy().txConfidenceIndicator);
confirmations = tx.getNumConfirmations();
}
});
}
public void cleanup() {
// TODO (woodser): remove wallet listener
//xmrWalletService.removeTxConfidenceListener(txConfidenceListener);
}
public TxConfidenceIndicator getTxConfidenceIndicator() {
return lazy().txConfidenceIndicator;
}
@ -309,8 +261,8 @@ class TransactionsListItem {
return tradable;
}
public String getNumConfirmations() {
return String.valueOf(confirmations);
public long getNumConfirmations() {
return confirmations;
}
public String getMemo() {

View file

@ -29,10 +29,9 @@ import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.util.GUIUtil;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.user.Preferences;
@ -40,13 +39,10 @@ import bisq.network.p2p.P2PService;
import bisq.common.util.Utilities;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.wallet.listeners.WalletChangeEventListener;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import javax.inject.Inject;
import monero.wallet.model.MoneroWalletListener;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.fxml.FXML;
@ -77,11 +73,9 @@ import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.math.BigInteger;
import java.util.Comparator;
import javax.annotation.Nullable;
@FxmlView
public class TransactionsView extends ActivatableView<VBox, Void> {
@ -100,33 +94,40 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final DisplayedTransactions displayedTransactions;
private final SortedList<TransactionsListItem> sortedDisplayedTransactions;
private final BtcWalletService btcWalletService;
private final P2PService p2PService;
private final CoreMoneroConnectionsService connectionService;
private final XmrWalletService xmrWalletService;
private final Preferences preferences;
private final TradeDetailsWindow tradeDetailsWindow;
private final OfferDetailsWindow offerDetailsWindow;
private WalletChangeEventListener walletChangeEventListener;
private EventHandler<KeyEvent> keyEventEventHandler;
private Scene scene;
private TransactionsUpdater transactionsUpdater = new TransactionsUpdater();
private class TransactionsUpdater extends MoneroWalletListener {
@Override
public void onNewBlock(long height) {
displayedTransactions.update();
}
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
displayedTransactions.update();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private TransactionsView(BtcWalletService btcWalletService,
private TransactionsView(XmrWalletService xmrWalletService,
P2PService p2PService,
CoreMoneroConnectionsService connectionService,
Preferences preferences,
TradeDetailsWindow tradeDetailsWindow,
OfferDetailsWindow offerDetailsWindow,
DisplayedTransactionsFactory displayedTransactionsFactory) {
this.btcWalletService = btcWalletService;
this.p2PService = p2PService;
this.connectionService = connectionService;
this.xmrWalletService = xmrWalletService;
this.preferences = preferences;
this.tradeDetailsWindow = tradeDetailsWindow;
this.offerDetailsWindow = offerDetailsWindow;
@ -168,16 +169,12 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
addressColumn.setComparator(Comparator.comparing(item -> item.getDirection() + item.getAddressString()));
transactionColumn.setComparator(Comparator.comparing(TransactionsListItem::getTxId));
amountColumn.setComparator(Comparator.comparing(TransactionsListItem::getAmountAsCoin));
confidenceColumn.setComparator(Comparator.comparingDouble(item -> item.getTxConfidenceIndicator().getProgress()));
confidenceColumn.setComparator(Comparator.comparingLong(item -> item.getNumConfirmations()));
memoColumn.setComparator(Comparator.comparing(TransactionsListItem::getMemo));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
walletChangeEventListener = wallet -> {
displayedTransactions.update();
};
keyEventEventHandler = event -> {
// Not intended to be public to users as the feature is not well tested
if (Utilities.isAltOrCtrlPressed(KeyCode.R, event)) {
@ -202,7 +199,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
tableView.setItems(sortedDisplayedTransactions);
displayedTransactions.update();
btcWalletService.addChangeEventListener(walletChangeEventListener);
xmrWalletService.addWalletListener(transactionsUpdater);
scene = root.getScene();
if (scene != null)
@ -226,7 +223,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
columns[3] = item.getTxId();
columns[4] = item.getAmount();
columns[5] = item.getMemo() == null ? "" : item.getMemo();
columns[6] = item.getNumConfirmations();
columns[6] = String.valueOf(item.getNumConfirmations());
return columns;
};
@ -239,7 +236,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
protected void deactivate() {
sortedDisplayedTransactions.comparatorProperty().unbind();
displayedTransactions.forEach(TransactionsListItem::cleanup);
btcWalletService.removeChangeEventListener(walletChangeEventListener);
xmrWalletService.removeWalletListener(transactionsUpdater);
if (scene != null)
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
@ -505,49 +502,15 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override
public void updateItem(final TransactionsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
TransactionConfidence confidence = btcWalletService.getConfidenceForTxId(item.getTxId());
if (confidence != null) {
if (confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING) {
if (button == null) {
button = new AutoTooltipButton(Res.get("funds.tx.revert"));
setGraphic(button);
}
button.setOnAction(e -> revertTransaction(item.getTxId(), item.getTradable()));
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
};
}
});
}
private void revertTransaction(String txId, @Nullable Tradable tradable) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
try {
btcWalletService.doubleSpendTransaction(txId, () -> {
if (tradable != null)
btcWalletService.swapAnyTradeEntryContextToAvailableEntry(tradable.getId());
new Popup().information(Res.get("funds.tx.txSent")).show();
}, errorMessage -> new Popup().warning(errorMessage).show());
} catch (Throwable e) {
new Popup().warning(e.getMessage()).show();
}
}
}
}

View file

@ -23,6 +23,7 @@ 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.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Coin;
@ -71,7 +72,8 @@ class WithdrawalListItem {
}
private void updateBalance() {
balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex());
balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); // TODO: Coin represents centineros everywhere, but here it's atomic units. reconcile
balance = Coin.valueOf(ParsingUtils.atomicUnitsToCentineros(balance.longValue())); // in centineros
if (balance != null)
balanceLabel.setText(formatter.formatCoin(this.balance));
}

View file

@ -26,16 +26,20 @@ 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.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.setup.WalletsSetup;
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;
@ -43,17 +47,15 @@ import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.validation.BtcAddressValidator;
import bisq.network.p2p.P2PService;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import bisq.common.util.Tuple4;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.util.concurrent.FutureCallback;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet;
import org.apache.commons.lang3.StringUtils;
@ -69,7 +71,6 @@ import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
@ -88,12 +89,12 @@ import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import org.bouncycastle.crypto.params.KeyParameter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -109,36 +110,27 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML
TableColumn<WithdrawalListItem, WithdrawalListItem> addressColumn, balanceColumn, selectColumn;
private RadioButton useAllInputsRadioButton, useCustomInputsRadioButton, feeExcludedRadioButton, feeIncludedRadioButton;
private RadioButton useAllInputsRadioButton, useCustomInputsRadioButton;
private Label amountLabel;
private TextField amountTextField, withdrawFromTextField, withdrawToTextField, withdrawMemoTextField, transactionFeeInputTextField;
private TextField amountTextField, withdrawFromTextField, withdrawToTextField, withdrawMemoTextField;
private final XmrWalletService xmrWalletService;
private final TradeManager tradeManager;
private final P2PService p2PService;
private final WalletsSetup walletsSetup;
private final CoinFormatter formatter;
private final Preferences preferences;
private final BtcAddressValidator btcAddressValidator;
private final WalletPasswordWindow walletPasswordWindow;
private final ObservableList<WithdrawalListItem> observableList = FXCollections.observableArrayList();
private final SortedList<WithdrawalListItem> sortedList = new SortedList<>(observableList);
private final Set<WithdrawalListItem> selectedItems = new HashSet<>();
private XmrBalanceListener balanceListener;
private Set<String> fromAddresses = new HashSet<>();
private Coin totalAvailableAmountOfSelectedItems = Coin.ZERO;
private Coin amountAsCoin = Coin.ZERO;
private ChangeListener<String> amountListener;
private ChangeListener<Boolean> amountFocusListener, useCustomFeeCheckboxListener, transactionFeeFocusedListener;
private ChangeListener<Toggle> feeToggleGroupListener, inputsToggleGroupListener;
private ChangeListener<Number> transactionFeeChangeListener;
private ToggleGroup feeToggleGroup, inputsToggleGroup;
private ToggleButton useCustomFee;
private ChangeListener<Boolean> amountFocusListener;
private ChangeListener<Toggle> inputsToggleGroupListener;
private ToggleGroup inputsToggleGroup;
private final BooleanProperty useAllInputs = new SimpleBooleanProperty(true);
private boolean feeExcluded;
private int rowIndex = 0;
private final FeeService feeService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
@ -154,16 +146,11 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
BtcAddressValidator btcAddressValidator,
WalletPasswordWindow walletPasswordWindow,
FeeService feeService) {
// throw new RuntimeException("WithdrawalView needs updated to use XMR wallet");
this.xmrWalletService = xmrWalletService;
this.tradeManager = tradeManager;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.formatter = formatter;
this.preferences = preferences;
this.btcAddressValidator = btcAddressValidator;
this.walletPasswordWindow = walletPasswordWindow;
this.feeService = feeService;
}
@Override
@ -189,20 +176,13 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
useAllInputsRadioButton = labelRadioButtonRadioButtonTuple3.second;
useCustomInputsRadioButton = labelRadioButtonRadioButtonTuple3.third;
feeToggleGroup = new ToggleGroup();
final Tuple4<Label, TextField, RadioButton, RadioButton> feeTuple3 = addTopLabelTextFieldRadioButtonRadioButton(gridPane, ++rowIndex, feeToggleGroup,
final Tuple2<Label, InputTextField> feeTuple3 = addTopLabelInputTextField(gridPane, ++rowIndex,
Res.get("funds.withdrawal.receiverAmount", Res.getBaseCurrencyCode()),
"",
Res.get("funds.withdrawal.feeExcluded"),
Res.get("funds.withdrawal.feeIncluded"),
0);
amountLabel = feeTuple3.first;
amountTextField = feeTuple3.second;
amountTextField.setMinWidth(180);
feeExcludedRadioButton = feeTuple3.third;
feeIncludedRadioButton = feeTuple3.fourth;
withdrawFromTextField = addTopLabelTextField(gridPane, ++rowIndex,
Res.get("funds.withdrawal.fromLabel", Res.getBaseCurrencyCode())).second;
@ -213,52 +193,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawMemoTextField = addTopLabelInputTextField(gridPane, ++rowIndex,
Res.get("funds.withdrawal.memoLabel", Res.getBaseCurrencyCode())).second;
Tuple3<Label, InputTextField, ToggleButton> customFeeTuple = addTopLabelInputTextFieldSlideToggleButton(gridPane, ++rowIndex,
Res.get("funds.withdrawal.txFee"), Res.get("funds.withdrawal.useCustomFeeValue"));
transactionFeeInputTextField = customFeeTuple.second;
useCustomFee = customFeeTuple.third;
useCustomFeeCheckboxListener = (observable, oldValue, newValue) -> {
transactionFeeInputTextField.setEditable(newValue);
if (!newValue) {
try {
transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerVbyte().value));
} catch (Exception e) {
e.printStackTrace();
}
}
};
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
if (oldValue && !newValue) {
String estimatedFee = String.valueOf(feeService.getTxFeePerVbyte().value);
try {
int withdrawalTxFeePerVbyte = Integer.parseInt(transactionFeeInputTextField.getText());
final long minFeePerVbyte = feeService.getMinFeePerVByte();
if (withdrawalTxFeePerVbyte < minFeePerVbyte) {
new Popup().warning(Res.get("funds.withdrawal.txFeeMin", minFeePerVbyte)).show();
transactionFeeInputTextField.setText(estimatedFee);
} else if (withdrawalTxFeePerVbyte > 5000) {
new Popup().warning(Res.get("funds.withdrawal.txFeeTooLarge")).show();
transactionFeeInputTextField.setText(estimatedFee);
} else {
preferences.setWithdrawalTxFeeInVbytes(withdrawalTxFeePerVbyte);
}
} catch (NumberFormatException t) {
log.error(t.toString());
t.printStackTrace();
new Popup().warning(Res.get("validation.integerOnly")).show();
transactionFeeInputTextField.setText(estimatedFee);
} catch (Throwable t) {
log.error(t.toString());
t.printStackTrace();
new Popup().warning(Res.get("validation.inputError", t.getMessage())).show();
transactionFeeInputTextField.setText(estimatedFee);
}
}
};
transactionFeeChangeListener = (observable, oldValue, newValue) -> transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerVbyte().value));
final Button withdrawButton = addButton(gridPane, ++rowIndex, Res.get("funds.withdrawal.withdrawButton"), 15);
withdrawButton.setOnAction(event -> onWithdraw());
@ -304,14 +238,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
amountTextField.setText("");
}
};
feeExcludedRadioButton.setToggleGroup(feeToggleGroup);
feeIncludedRadioButton.setToggleGroup(feeToggleGroup);
feeToggleGroupListener = (observable, oldValue, newValue) -> {
feeExcluded = newValue == feeExcludedRadioButton;
amountLabel.setText(feeExcluded ?
Res.get("funds.withdrawal.receiverAmount") :
Res.get("funds.withdrawal.senderAmount"));
};
amountLabel.setText(Res.get("funds.withdrawal.receiverAmount"));
}
private void updateInputSelection() {
@ -333,22 +260,11 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
amountTextField.textProperty().addListener(amountListener);
amountTextField.focusedProperty().addListener(amountFocusListener);
xmrWalletService.addBalanceListener(balanceListener);
feeToggleGroup.selectedToggleProperty().addListener(feeToggleGroupListener);
inputsToggleGroup.selectedToggleProperty().addListener(inputsToggleGroupListener);
if (feeToggleGroup.getSelectedToggle() == null)
feeToggleGroup.selectToggle(feeIncludedRadioButton);
if (inputsToggleGroup.getSelectedToggle() == null)
inputsToggleGroup.selectToggle(useAllInputsRadioButton);
useCustomFee.setSelected(false);
transactionFeeInputTextField.setEditable(false);
transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerVbyte().value));
feeService.feeUpdateCounterProperty().addListener(transactionFeeChangeListener);
useCustomFee.selectedProperty().addListener(useCustomFeeCheckboxListener);
transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
updateInputSelection();
GUIUtil.requestFocus(withdrawToTextField);
}
@ -360,12 +276,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
xmrWalletService.removeBalanceListener(balanceListener);
amountTextField.textProperty().removeListener(amountListener);
amountTextField.focusedProperty().removeListener(amountFocusListener);
feeToggleGroup.selectedToggleProperty().removeListener(feeToggleGroupListener);
inputsToggleGroup.selectedToggleProperty().removeListener(inputsToggleGroupListener);
transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
if (transactionFeeChangeListener != null)
feeService.feeUpdateCounterProperty().removeListener(transactionFeeChangeListener);
useCustomFee.selectedProperty().removeListener(useCustomFeeCheckboxListener);
}
@ -374,108 +285,72 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
private void onWithdraw() {
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();
// }
// }
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, xmrWalletService.getConnectionsService())) {
try {
// get withdraw address
final String withdrawToAddress = withdrawToTextField.getText();
// get receiver amount
Coin receiverAmount = amountAsCoin;
if (!receiverAmount.isPositive()) throw new RuntimeException(Res.get("portfolio.pending.step5_buyer.amountTooLow"));
// create tx
MoneroTxWallet tx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount)) // TODO: rename to centinerosToAtomicUnits()?
.setAddress(withdrawToAddress));
// create confirmation message
Coin fee = ParsingUtils.atomicUnitsToCoin(tx.getFee());
Coin sendersAmount = receiverAmount.add(fee);
String messageText = Res.get("shared.sendFundsDetailsWithFee",
formatter.formatCoinWithCode(sendersAmount),
withdrawFromTextField.getText(),
withdrawToAddress,
formatter.formatCoinWithCode(fee),
formatter.formatCoinWithCode(receiverAmount));
// popup confirmation message
new Popup().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest"))
.confirmation(messageText)
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
// relay tx
try {
xmrWalletService.getWallet().relayTx(tx);
String key = "showTransactionSent";
if (DontShowAgainLookup.showAgain(key)) {
new TxDetails(tx.getHash(), withdrawToAddress, formatter.formatCoinWithCode(sendersAmount))
.dontShowAgainId(key)
.show();
}
log.debug("onWithdraw onSuccess tx ID:{}", tx.getHash());
List<Trade> trades = new ArrayList<>(tradeManager.getObservableList());
trades.stream()
.filter(Trade::isPayoutPublished)
.forEach(trade -> xmrWalletService.getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT)
.ifPresent(addressEntry -> {
if (xmrWalletService.getBalanceForAddress(addressEntry.getAddressString()).isZero())
tradeManager.onTradeCompleted(trade);
}));
} catch (Exception e) {
e.printStackTrace();
}
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (Throwable e) {
if (e.getMessage().contains("enough")) new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show();
else {
e.printStackTrace();
log.error(e.toString());
new Popup().warning(e.toString()).show();
}
}
}
}
private void selectForWithdrawal(WithdrawalListItem item) {
@ -484,10 +359,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
else
selectedItems.remove(item);
fromAddresses = selectedItems.stream()
.map(WithdrawalListItem::getAddressString)
.collect(Collectors.toSet());
if (!selectedItems.isEmpty()) {
totalAvailableAmountOfSelectedItems = Coin.valueOf(selectedItems.stream().mapToLong(e -> e.getBalance().getValue()).sum());
if (totalAvailableAmountOfSelectedItems.isPositive()) {
@ -533,7 +404,6 @@ 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(xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.map(addressEntry -> new WithdrawalListItem(addressEntry, xmrWalletService, formatter))
@ -542,51 +412,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
updateInputSelection();
}
private void doWithdraw(Coin amount, Coin fee, FutureCallback<Transaction> 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) {
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() {
withdrawFromTextField.setText("");
withdrawFromTextField.setPromptText(Res.get("funds.withdrawal.selectAddress"));
@ -603,37 +428,10 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawMemoTextField.setText("");
withdrawMemoTextField.setPromptText(Res.get("funds.withdrawal.memo"));
transactionFeeInputTextField.setText("");
transactionFeeInputTextField.setPromptText(Res.get("funds.withdrawal.useCustomFeeValueInfo"));
selectedItems.clear();
tableView.getSelectionModel().clearSelection();
}
private boolean areInputsValid(Coin sendersAmount) {
if (!sendersAmount.isPositive()) {
new Popup().warning(Res.get("validation.negative")).show();
return false;
}
if (!btcAddressValidator.validate(withdrawToTextField.getText()).isValid) {
new Popup().warning(Res.get("validation.btc.invalidAddress")).show();
return false;
}
if (!totalAvailableAmountOfSelectedItems.isPositive()) {
new Popup().warning(Res.get("funds.withdrawal.warn.noSourceAddressSelected")).show();
return false;
}
if (sendersAmount.compareTo(totalAvailableAmountOfSelectedItems) > 0) {
new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds")).show();
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -21,7 +21,7 @@ import bisq.desktop.components.TxIdTextField;
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
@ -86,7 +86,7 @@ public class MarketPricePresentation {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MarketPricePresentation(BtcWalletService btcWalletService,
public MarketPricePresentation(XmrWalletService xmrWalletService,
PriceFeedService priceFeedService,
Preferences preferences,
FeeService feeService) {
@ -96,7 +96,7 @@ public class MarketPricePresentation {
TxIdTextField.setPreferences(preferences);
// TODO
TxIdTextField.setWalletService(btcWalletService);
TxIdTextField.setXmrWalletService(xmrWalletService);
GUIUtil.setFeeService(feeService);
}

View file

@ -1047,11 +1047,12 @@ public class FormBuilder {
String checkBoxTitle,
double top) {
Button button = new AutoTooltipButton(buttonTitle);
CheckBox checkBox = new AutoTooltipCheckBox(checkBoxTitle);
CheckBox checkBox = checkBoxTitle == null ? null : new AutoTooltipCheckBox(checkBoxTitle);
HBox hBox = new HBox(20);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.getChildren().addAll(button, checkBox);
hBox.getChildren().add(button);
if (checkBox != null) hBox.getChildren().add(button);
GridPane.setRowIndex(hBox, rowIndex);
hBox.setPadding(new Insets(top, 0, 0, 0));
gridPane.getChildren().add(hBox);

View file

@ -32,6 +32,7 @@ import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil;
@ -134,7 +135,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTxWallet;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -566,34 +567,28 @@ public class GUIUtil {
};
}
public static void updateConfidence(TransactionConfidence confidence,
public static void updateConfidence(MoneroTxWallet tx,
Tooltip tooltip,
TxConfidenceIndicator txConfidenceIndicator) {
if (confidence != null) {
switch (confidence.getConfidenceType()) {
case UNKNOWN:
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
break;
case PENDING:
tooltip.setText(Res.get("confidence.seen", confidence.numBroadcastPeers()));
txConfidenceIndicator.setProgress(-1.0);
break;
case BUILDING:
tooltip.setText(Res.get("confidence.confirmed", confidence.getDepthInBlocks()));
txConfidenceIndicator.setProgress(Math.min(1, confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
break;
if (tx != null) {
if (!tx.isRelayed()) {
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
} else if (tx.isFailed()) {
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
} else if (tx.isConfirmed()) {
tooltip.setText(Res.get("confidence.confirmed", tx.getNumConfirmations()));
txConfidenceIndicator.setProgress(Math.min(1, tx.getNumConfirmations() / (double) XmrWalletService.NUM_BLOCKS_UNLOCK));
} else {
tooltip.setText(Res.get("confidence.seen", 0)); // TODO: replace with numBroadcastPeers
txConfidenceIndicator.setProgress(-1.0);
}
txConfidenceIndicator.setPrefSize(24, 24);
}
}
public static void openWebPage(String target) {
openWebPage(target, true, null);
}