diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 9eb3417d60d..c9661460714 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1042,7 +1042,7 @@ funds.tx.noTxAvailable=No transactions available funds.tx.revert=Revert funds.tx.txSent=Transaction successfully sent to a new address in the local Bisq wallet. funds.tx.direction.self=Sent to yourself -funds.tx.daoTxFee=Miner fee for DAO tx +funds.tx.daoTxFee=Miner fee for BSQ tx funds.tx.reimbursementRequestTxFee=Reimbursement request funds.tx.compensationRequestTxFee=Compensation request funds.tx.dustAttackTx=Received dust @@ -1227,7 +1227,7 @@ settings.preferences.editCustomExplorer.headline=Explorer Settings settings.preferences.editCustomExplorer.description=Choose a system defined explorer from the list on the left, and/or \ customize to suit your own preferences. settings.preferences.editCustomExplorer.available=Available explorers -settings.preferences.editCustomExplorer.chosen = Chosen explorer settings +settings.preferences.editCustomExplorer.chosen=Chosen explorer settings settings.preferences.editCustomExplorer.name=Name settings.preferences.editCustomExplorer.txUrl=Transaction URL settings.preferences.editCustomExplorer.addressUrl=Address URL @@ -1390,9 +1390,26 @@ account.menu.paymentAccount=National currency accounts account.menu.altCoinsAccountView=Altcoin accounts account.menu.password=Wallet password account.menu.seedWords=Wallet seed +account.menu.walletInfo=Wallet info account.menu.backup=Backup account.menu.notifications=Notifications +account.menu.walletInfo.balance.headLine=Wallet balances +account.menu.walletInfo.balance.info=This shows the internal wallet balance including unconfirmed transactions.\n\ + For Bitcoin the sum of the 'available balance' and the 'reserved for offers balance' must match the internal wallet balance \ + displayed here. +account.menu.walletInfo.xpub.headLine=Watch keys (xpub keys) +account.menu.walletInfo.walletSelector={0} {1} wallet +account.menu.walletInfo.path.headLine=HD keychain paths +account.menu.walletInfo.path.info=If you import the seed words in another wallet (like Electrum) you need to define the \ + path. Use that only in emergency cases when you lost access to the Bisq wallet and the data directory.\n\ + Spending funds from another wallet can easily screw up the Bisq internal data structures associated with the wallet \ + data and can lead to failed trades.\n\ + Do NEVER send BSQ from another wallet as that lead very likely to an invalid BSQ transaction and your \ + BSQ get burned. + +account.menu.walletInfo.openDetails=Show raw wallet details and private keys + ## TODO should we rename the following to a gereric name? account.arbitratorRegistration.pubKey=Public key diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml b/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml index e7b9c5b0fed..cf17546702d 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.fxml @@ -44,7 +44,12 @@ AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/> - + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java index 71cc5822578..531f3ac3c09 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java @@ -30,6 +30,7 @@ import bisq.desktop.main.account.content.notifications.MobileNotificationsView; import bisq.desktop.main.account.content.password.PasswordView; import bisq.desktop.main.account.content.seedwords.SeedWordsView; +import bisq.desktop.main.account.content.walletinfo.WalletInfoView; import bisq.desktop.main.account.register.arbitrator.ArbitratorRegistrationView; import bisq.desktop.main.account.register.mediator.MediatorRegistrationView; import bisq.desktop.main.account.register.refundagent.RefundAgentRegistrationView; @@ -67,7 +68,7 @@ public class AccountView extends ActivatableView { @FXML Tab fiatAccountsTab, altcoinAccountsTab, notificationTab, - passwordTab, seedwordsTab, backupTab; + passwordTab, seedWordsTab, walletInfoTab, backupTab; private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; @@ -101,7 +102,8 @@ public void initialize() { altcoinAccountsTab.setText(Res.get("account.menu.altCoinsAccountView").toUpperCase()); notificationTab.setText(Res.get("account.menu.notifications").toUpperCase()); passwordTab.setText(Res.get("account.menu.password").toUpperCase()); - seedwordsTab.setText(Res.get("account.menu.seedWords").toUpperCase()); + seedWordsTab.setText(Res.get("account.menu.seedWords").toUpperCase()); + walletInfoTab.setText(Res.get("account.menu.walletInfo").toUpperCase()); backupTab.setText(Res.get("account.menu.backup").toUpperCase()); navigationListener = viewPath -> { @@ -161,8 +163,10 @@ public void initialize() { navigation.navigateTo(MainView.class, AccountView.class, MobileNotificationsView.class); } else if (newValue == passwordTab && selectedTab != passwordTab) { navigation.navigateTo(MainView.class, AccountView.class, PasswordView.class); - } else if (newValue == seedwordsTab && selectedTab != seedwordsTab) { + } else if (newValue == seedWordsTab && selectedTab != seedWordsTab) { navigation.navigateTo(MainView.class, AccountView.class, SeedWordsView.class); + } else if (newValue == walletInfoTab && selectedTab != walletInfoTab) { + navigation.navigateTo(MainView.class, AccountView.class, WalletInfoView.class); } else if (newValue == backupTab && selectedTab != backupTab) { navigation.navigateTo(MainView.class, AccountView.class, BackupView.class); } @@ -251,8 +255,10 @@ else if (root.getSelectionModel().getSelectedItem() == notificationTab) navigation.navigateTo(MainView.class, AccountView.class, MobileNotificationsView.class); else if (root.getSelectionModel().getSelectedItem() == passwordTab) navigation.navigateTo(MainView.class, AccountView.class, PasswordView.class); - else if (root.getSelectionModel().getSelectedItem() == seedwordsTab) + else if (root.getSelectionModel().getSelectedItem() == seedWordsTab) navigation.navigateTo(MainView.class, AccountView.class, SeedWordsView.class); + else if (root.getSelectionModel().getSelectedItem() == walletInfoTab) + navigation.navigateTo(MainView.class, AccountView.class, WalletInfoView.class); else if (root.getSelectionModel().getSelectedItem() == backupTab) navigation.navigateTo(MainView.class, AccountView.class, BackupView.class); else @@ -314,7 +320,9 @@ private void loadView(Class viewClass) { } else if (view instanceof PasswordView) { selectedTab = passwordTab; } else if (view instanceof SeedWordsView) { - selectedTab = seedwordsTab; + selectedTab = seedWordsTab; + } else if (view instanceof WalletInfoView) { + selectedTab = walletInfoTab; } else if (view instanceof BackupView) { selectedTab = backupTab; } else { diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/walletinfo/WalletInfoView.fxml b/desktop/src/main/java/bisq/desktop/main/account/content/walletinfo/WalletInfoView.fxml new file mode 100644 index 00000000000..2f00ffb2163 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/walletinfo/WalletInfoView.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/walletinfo/WalletInfoView.java b/desktop/src/main/java/bisq/desktop/main/account/content/walletinfo/WalletInfoView.java new file mode 100644 index 00000000000..265992eb1a9 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/account/content/walletinfo/WalletInfoView.java @@ -0,0 +1,170 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.walletinfo; + +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.ShowWalletDataWindow; +import bisq.desktop.util.Layout; + +import bisq.core.btc.listeners.BalanceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.WalletService; +import bisq.core.btc.wallet.WalletsManager; +import bisq.core.locale.Res; +import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; + +import bisq.common.config.Config; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.script.Script; +import org.bitcoinj.wallet.DeterministicKeyChain; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; + +import static bisq.desktop.util.FormBuilder.addButtonAfterGroup; +import static bisq.desktop.util.FormBuilder.addMultilineLabel; +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; +import static bisq.desktop.util.FormBuilder.addTopLabelTextField; +import static org.bitcoinj.wallet.Wallet.BalanceType.ESTIMATED_SPENDABLE; + +@FxmlView +public class WalletInfoView extends ActivatableView { + + private final WalletsManager walletsManager; + private final BtcWalletService btcWalletService; + private final BsqWalletService bsqWalletService; + private final CoinFormatter btcFormatter; + private final BsqFormatter bsqFormatter; + private int gridRow = 0; + private Button openDetailsButton; + private TextField btcTextField, bsqTextField; + private BalanceListener btcWalletBalanceListener; + private BalanceListener bsqWalletBalanceListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private WalletInfoView(WalletsManager walletsManager, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, + BsqFormatter bsqFormatter) { + this.walletsManager = walletsManager; + this.btcWalletService = btcWalletService; + this.bsqWalletService = bsqWalletService; + this.btcFormatter = btcFormatter; + this.bsqFormatter = bsqFormatter; + } + + @Override + public void initialize() { + addTitledGroupBg(root, gridRow, 3, Res.get("account.menu.walletInfo.balance.headLine")); + addMultilineLabel(root, gridRow, Res.get("account.menu.walletInfo.balance.info"), Layout.FIRST_ROW_DISTANCE, Double.MAX_VALUE); + btcTextField = addTopLabelTextField(root, ++gridRow, "BTC", -Layout.FLOATING_LABEL_DISTANCE).second; + bsqTextField = addTopLabelTextField(root, ++gridRow, "BSQ", -Layout.FLOATING_LABEL_DISTANCE).second; + + addTitledGroupBg(root, ++gridRow, 3, Res.get("account.menu.walletInfo.xpub.headLine"), Layout.GROUP_DISTANCE); + addXpubKeys(btcWalletService, "BTC", gridRow, Layout.FIRST_ROW_AND_GROUP_DISTANCE); + ++gridRow; // update gridRow + addXpubKeys(bsqWalletService, "BSQ", ++gridRow, -Layout.FLOATING_LABEL_DISTANCE); + + addTitledGroupBg(root, ++gridRow, 4, Res.get("account.menu.walletInfo.path.headLine"), Layout.GROUP_DISTANCE); + addMultilineLabel(root, gridRow, Res.get("account.menu.walletInfo.path.info"), Layout.FIRST_ROW_AND_GROUP_DISTANCE, Double.MAX_VALUE); + addTopLabelTextField(root, ++gridRow, Res.get("account.menu.walletInfo.walletSelector", "BTC", "legacy"), "44'/0'/0'", -Layout.FLOATING_LABEL_DISTANCE); + addTopLabelTextField(root, ++gridRow, Res.get("account.menu.walletInfo.walletSelector", "BTC", "segwit"), "44'/0'/1'", -Layout.FLOATING_LABEL_DISTANCE); + addTopLabelTextField(root, ++gridRow, Res.get("account.menu.walletInfo.walletSelector", "BSQ", ""), "44'/142'/0'", -Layout.FLOATING_LABEL_DISTANCE); + + openDetailsButton = addButtonAfterGroup(root, ++gridRow, Res.get("account.menu.walletInfo.openDetails")); + + btcWalletBalanceListener = new BalanceListener() { + @Override + public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) { + updateBalances(btcWalletService); + } + }; + bsqWalletBalanceListener = new BalanceListener() { + @Override + public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) { + updateBalances(bsqWalletService); + } + }; + } + + + @Override + protected void activate() { + btcWalletService.addBalanceListener(btcWalletBalanceListener); + bsqWalletService.addBalanceListener(bsqWalletBalanceListener); + updateBalances(btcWalletService); + updateBalances(bsqWalletService); + + openDetailsButton.setOnAction(e -> { + if (walletsManager.areWalletsAvailable()) { + new ShowWalletDataWindow(walletsManager).width(root.getWidth()).show(); + } else { + new Popup().warning(Res.get("popup.warning.walletNotInitialized")).show(); + } + }); + } + + @Override + protected void deactivate() { + btcWalletService.removeBalanceListener(btcWalletBalanceListener); + bsqWalletService.removeBalanceListener(bsqWalletBalanceListener); + openDetailsButton.setOnAction(null); + } + + private void addXpubKeys(WalletService walletService, String currency, int gridRow, double top) { + int row = gridRow; + double topDist = top; + for (DeterministicKeyChain chain : walletService.getWallet().getActiveKeyChains()) { + Script.ScriptType outputScriptType = chain.getOutputScriptType(); + String type = outputScriptType == Script.ScriptType.P2WPKH ? "segwit" : "legacy"; + String key = chain.getWatchingKey().serializePubB58(Config.baseCurrencyNetworkParameters(), outputScriptType); + addTopLabelTextField(root, row, + Res.get("account.menu.walletInfo.walletSelector", currency, type), + key, topDist); + row++; + topDist = -Layout.FLOATING_LABEL_DISTANCE; + } + } + + private void updateBalances(WalletService walletService) { + if (walletService instanceof BtcWalletService) { + btcTextField.setText(btcFormatter.formatCoinWithCode(walletService.getBalance(ESTIMATED_SPENDABLE))); + } else { + bsqTextField.setText(bsqFormatter.formatCoinWithCode(walletService.getBalance(ESTIMATED_SPENDABLE))); + } + } + +} + diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java index 92039095069..f6f25715602 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java @@ -54,7 +54,7 @@ public void show() { if (headLine == null) headLine = Res.get("showWalletDataWindow.walletData"); - width = 660; + width = 1000; createGridPane(); addHeadLine(); addContent();