From 920e05562c54608eb01cff50ee28f0b5f32e9464 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Sun, 5 Jun 2022 11:24:44 -0500 Subject: [PATCH] Feat: XMR subaddresses per account incremente subaddress index at the start of the trade Add subaccounts Rename XmrAccountHelper to XmrAccountDelegate Add map as final non null value Persist subaccounts in User Add display of used subaddresses and tradeId in account summary. Main address and account index are the unique key for sub accounts. Use the initial subaddress chosen by the user. chimp1984 code review patch. News badge/info for Account, disable old ones. Show XMR subaddress popup info at Account (news badge), not startup. --- .../core/payment/CryptoCurrencyAccount.java | 4 + .../bisq/core/payment/PaymentAccount.java | 21 + .../bisq/core/payment/XmrAccountDelegate.java | 156 +++++++ .../java/bisq/core/trade/TradeManager.java | 10 + .../protocol/bisq_v1/DisputeProtocol.java | 7 + .../bisq_v1/SellerAsMakerProtocol.java | 2 + .../bisq_v1/SellerAsTakerProtocol.java | 2 + .../protocol/bisq_v1/model/ProcessModel.java | 37 +- .../tasks/seller/MaybeCreateSubAccount.java | 103 +++++ core/src/main/java/bisq/core/user/User.java | 5 + .../main/java/bisq/core/user/UserPayload.java | 67 ++- .../resources/i18n/displayStrings.properties | 34 +- .../components/paymentmethods/AssetsForm.java | 71 +--- .../components/paymentmethods/XmrForm.java | 382 ++++++++++++++++++ .../main/java/bisq/desktop/main/MainView.java | 6 +- .../java/bisq/desktop/main/MainViewModel.java | 17 - .../desktop/main/account/AccountView.java | 11 +- .../altcoinaccounts/AltCoinAccountsView.java | 75 +++- .../java/bisq/desktop/main/dao/DaoView.java | 3 - .../pendingtrades/steps/TradeStepView.java | 2 +- .../steps/seller/SellerStep3View.java | 6 +- .../presentation/AccountPresentation.java | 3 +- .../main/presentation/DaoPresentation.java | 11 +- .../presentation/SettingsPresentation.java | 10 +- .../desktop/main/settings/SettingsView.java | 2 - .../settings/preferences/PreferencesView.java | 3 +- proto/src/main/proto/pb.proto | 8 + 27 files changed, 925 insertions(+), 133 deletions(-) create mode 100644 core/src/main/java/bisq/core/payment/XmrAccountDelegate.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/MaybeCreateSubAccount.java create mode 100644 desktop/src/main/java/bisq/desktop/components/paymentmethods/XmrForm.java diff --git a/core/src/main/java/bisq/core/payment/CryptoCurrencyAccount.java b/core/src/main/java/bisq/core/payment/CryptoCurrencyAccount.java index 5493592538..b04aad1f4e 100644 --- a/core/src/main/java/bisq/core/payment/CryptoCurrencyAccount.java +++ b/core/src/main/java/bisq/core/payment/CryptoCurrencyAccount.java @@ -47,4 +47,8 @@ public final class CryptoCurrencyAccount extends AssetAccount { public @NonNull List getSupportedCurrencies() { return SUPPORTED_CURRENCIES; } + + public PaymentAccountPayload getPaymentAccountPayload() { + return paymentAccountPayload; + } } diff --git a/core/src/main/java/bisq/core/payment/PaymentAccount.java b/core/src/main/java/bisq/core/payment/PaymentAccount.java index d48ccf3485..25414d1b28 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccount.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccount.java @@ -24,11 +24,14 @@ import bisq.core.proto.CoreProtoResolver; import bisq.common.proto.ProtoUtil; import bisq.common.proto.persistable.PersistablePayload; +import bisq.common.util.CollectionUtils; import bisq.common.util.Utilities; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -69,6 +72,11 @@ public abstract class PaymentAccount implements PersistablePayload { @Nullable protected TradeCurrency selectedTradeCurrency; + // Was added at v1.9.2 + @Setter + @Nullable + protected Map extraData; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -100,6 +108,7 @@ public abstract class PaymentAccount implements PersistablePayload { .setAccountName(accountName) .addAllTradeCurrencies(ProtoUtil.collectionToProto(tradeCurrencies, protobuf.TradeCurrency.class)); Optional.ofNullable(selectedTradeCurrency).ifPresent(selectedTradeCurrency -> builder.setSelectedTradeCurrency((protobuf.TradeCurrency) selectedTradeCurrency.toProtoMessage())); + Optional.ofNullable(extraData).ifPresent(builder::putAllExtraData); return builder.build(); } @@ -130,6 +139,11 @@ public abstract class PaymentAccount implements PersistablePayload { if (proto.hasSelectedTradeCurrency()) account.setSelectedTradeCurrency(TradeCurrency.fromProto(proto.getSelectedTradeCurrency())); + if (CollectionUtils.isEmpty(proto.getExtraDataMap())) { + account.setExtraData(null); + } else { + account.setExtraData(new HashMap<>(proto.getExtraDataMap())); + } return account; } catch (RuntimeException e) { log.warn("Could not load account: {}, exception: {}", paymentMethodId, e.toString()); @@ -265,4 +279,11 @@ public abstract class PaymentAccount implements PersistablePayload { @NonNull public abstract List getSupportedCurrencies(); + + public Map getOrCreateExtraData() { + if (extraData == null) { + extraData = new HashMap<>(); + } + return extraData; + } } diff --git a/core/src/main/java/bisq/core/payment/XmrAccountDelegate.java b/core/src/main/java/bisq/core/payment/XmrAccountDelegate.java new file mode 100644 index 0000000000..c9b4251940 --- /dev/null +++ b/core/src/main/java/bisq/core/payment/XmrAccountDelegate.java @@ -0,0 +1,156 @@ +package bisq.core.payment; + +import bisq.core.xmr.knaccc.monero.address.WalletAddress; + +import java.util.Map; + +import lombok.Getter; +import lombok.experimental.Delegate; +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Delegate for AssetAccount with convenient methods for managing the map entries and creating subAccounts. + */ +@Slf4j +public class XmrAccountDelegate { + public static final String USE_XMR_SUB_ADDRESSES = "UseXMmrSubAddresses"; + private static final String KEY_MAIN_ADDRESS = "XmrMainAddress"; + private static final String KEY_PRIVATE_VIEW_KEY = "XmrPrivateViewKey"; + private static final String KEY_ACCOUNT_INDEX = "XmrAccountIndex"; + private static final String KEY_SUB_ADDRESS_INDEX = "XmrSubAddressIndex"; + private static final String KEY_SUB_ADDRESS = "XmrSubAddress"; + private static final String KEY_TRADE_ID = "TradeId"; + + public static boolean isUsingSubAddresses(PaymentAccount paymentAccount) { + return paymentAccount.extraData != null && + paymentAccount.extraData.getOrDefault(USE_XMR_SUB_ADDRESSES, "0").equals("1"); + } + + public static long getSubAddressIndexAsLong(PaymentAccount paymentAccount) { + checkNotNull(paymentAccount.extraData, "paymentAccount.extraData must not be null"); + // We let it throw in case the value is not a number + try { + return Long.parseLong(paymentAccount.extraData.get(KEY_SUB_ADDRESS_INDEX)); + } catch (Throwable t) { + log.error("Could not parse value " + paymentAccount.extraData.get(KEY_SUB_ADDRESS_INDEX + " to long value."), t); + throw new RuntimeException(t); + } + } + + @Getter + @Delegate + private final AssetAccount account; + + public XmrAccountDelegate(AssetAccount account) { + this.account = account; + } + + public void createAndSetNewSubAddress() { + long accountIndex = Long.parseLong(getAccountIndex()); + long subAddressIndex = Long.parseLong(getSubAddressIndex()); + // If both subAddressIndex and accountIndex would be 0 it would be the main address + // and the walletAddress.getSubaddressBase58 call would return an error. + checkArgument(subAddressIndex >= 0 && accountIndex >= 0 && (subAddressIndex + accountIndex > 0), + "accountIndex and/or subAddressIndex are invalid"); + String privateViewKey = getPrivateViewKey(); + String mainAddress = getMainAddress(); + if (mainAddress.isEmpty() || privateViewKey.isEmpty()) { + return; + } + try { + WalletAddress walletAddress = new WalletAddress(mainAddress); + long ts = System.currentTimeMillis(); + String subAddress = walletAddress.getSubaddressBase58(privateViewKey, accountIndex, subAddressIndex); + log.info("Created new subAddress {}. Took {} ms.", subAddress, System.currentTimeMillis() - ts); + setSubAddress(subAddress); + } catch (WalletAddress.InvalidWalletAddressException e) { + log.error("WalletAddress.getSubaddressBase58 failed", e); + throw new RuntimeException(e); + } + } + + public void reset() { + getMap().remove(USE_XMR_SUB_ADDRESSES); + getMap().remove(KEY_MAIN_ADDRESS); + getMap().remove(KEY_PRIVATE_VIEW_KEY); + getMap().remove(KEY_ACCOUNT_INDEX); + getMap().remove(KEY_SUB_ADDRESS_INDEX); + getMap().remove(KEY_SUB_ADDRESS); + getMap().remove(KEY_TRADE_ID); + + account.setAddress(""); + } + + public boolean isUsingSubAddresses() { + return XmrAccountDelegate.isUsingSubAddresses(account); + } + + public void setIsUsingSubAddresses(boolean value) { + getMap().put(USE_XMR_SUB_ADDRESSES, value ? "1" : "0"); + } + + public String getSubAddress() { + return getMap().getOrDefault(KEY_SUB_ADDRESS, ""); + } + + public void setSubAddress(String subAddress) { + getMap().put(KEY_SUB_ADDRESS, subAddress); + account.setAddress(subAddress); + } + + // Unique ID for subAccount used as key in our global subAccount map. + public String getSubAccountId() { + return getMainAddress() + getAccountIndex(); + } + + public String getMainAddress() { + return getMap().getOrDefault(KEY_MAIN_ADDRESS, ""); + } + + public void setMainAddress(String mainAddress) { + getMap().put(KEY_MAIN_ADDRESS, mainAddress); + } + + public String getPrivateViewKey() { + return getMap().getOrDefault(KEY_PRIVATE_VIEW_KEY, ""); + } + + public void setPrivateViewKey(String privateViewKey) { + getMap().put(KEY_PRIVATE_VIEW_KEY, privateViewKey); + } + + public String getAccountIndex() { + return getMap().getOrDefault(KEY_ACCOUNT_INDEX, ""); + } + + public void setAccountIndex(String newValue) { + getMap().put(KEY_ACCOUNT_INDEX, newValue); + } + + public String getSubAddressIndex() { + return getMap().getOrDefault(KEY_SUB_ADDRESS_INDEX, ""); + } + + public long getSubAddressIndexAsLong() { + return XmrAccountDelegate.getSubAddressIndexAsLong(account); + } + + public void setSubAddressIndex(String subAddressIndex) { + getMap().put(KEY_SUB_ADDRESS_INDEX, subAddressIndex); + } + + public String getTradeId() { + return getMap().getOrDefault(KEY_TRADE_ID, ""); + } + + public void setTradeId(String tradeId) { + getMap().put(KEY_TRADE_ID, tradeId); + } + + private Map getMap() { + return account.getOrCreateExtraData(); + } +} diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index acfef2fb26..21d1615110 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -27,6 +27,8 @@ import bisq.core.offer.OfferDirection; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.offer.availability.OfferAvailabilityModel; +import bisq.core.payment.PaymentAccount; +import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; @@ -110,6 +112,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -163,6 +166,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi @Getter private final LongProperty numPendingTrades = new SimpleLongProperty(); private final ReferralIdService referralIdService; + private final CorePersistenceProtoResolver corePersistenceProtoResolver; private final DumpDelayedPayoutTx dumpDelayedPayoutTx; @Getter private final boolean allowFaultyDelayedTxs; @@ -191,6 +195,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi ClockWatcher clockWatcher, PersistenceManager> persistenceManager, ReferralIdService referralIdService, + CorePersistenceProtoResolver corePersistenceProtoResolver, DumpDelayedPayoutTx dumpDelayedPayoutTx, @Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) { this.user = user; @@ -210,6 +215,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi this.provider = provider; this.clockWatcher = clockWatcher; this.referralIdService = referralIdService; + this.corePersistenceProtoResolver = corePersistenceProtoResolver; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.allowFaultyDelayedTxs = allowFaultyDelayedTxs; this.persistenceManager = persistenceManager; @@ -931,4 +937,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi AddressEntry.Context.TRADE_PAYOUT); return true; } + + public PaymentAccount cloneAccount(PaymentAccount paymentAccount) { + return Objects.requireNonNull(PaymentAccount.fromProto(paymentAccount.toProtoMessage(), corePersistenceProtoResolver)); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java index ee549e1cde..651a8422a4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java @@ -67,6 +67,13 @@ public class DisputeProtocol extends TradeProtocol { this.processModel = trade.getProcessModel(); } + @Override + protected void onInitialized() { + super.onInitialized(); + processModel.applyPaymentAccount(trade); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // TradeProtocol implementation /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java index 97841a715c..1da30e3e0d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java @@ -33,6 +33,7 @@ import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerProcessesInputsForDepos import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerRemovesOpenOffer; import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerSetsLockTime; import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerVerifyTakerFeePayment; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.MaybeCreateSubAccount; import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignsDelayedPayoutTx; @@ -72,6 +73,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc .with(message) .from(peer)) .setup(tasks( + MaybeCreateSubAccount.class, MakerProcessesInputsForDepositTxRequest.class, ApplyFilter.class, getVerifyPeersFeePaymentClass(), diff --git a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java index 8044ebf1d4..fae836be76 100644 --- a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java @@ -27,6 +27,7 @@ import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRespons import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse; import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.MaybeCreateSubAccount; import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignsDelayedPayoutTx; @@ -72,6 +73,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc .with(TakerEvent.TAKE_OFFER) .from(trade.getTradingPeerNodeAddress())) .setup(tasks( + MaybeCreateSubAccount.class, ApplyFilter.class, getVerifyPeersFeePaymentClass(), CreateTakerFeeTx.class, diff --git a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java index c321912485..17b7f20fa1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java @@ -118,7 +118,6 @@ public class ProcessModel implements ProtocolModel { // Persistable Immutable private final TradingPeer tradingPeer; private final String offerId; - private final String accountId; private final PubKeyRing pubKeyRing; // Persistable Mutable @@ -161,6 +160,15 @@ public class ProcessModel implements ProtocolModel { @Setter private long sellerPayoutAmountFromMediation; + // Was changed at v1.9.2 from immutable to mutable + @Setter + private String accountId; + + // Was added at v1.9.2 + @Setter + @Nullable + private PaymentAccount paymentAccount; + // We want to indicate the user the state of the message delivery of the // CounterCurrencyTransferStartedMessage. As well we do an automatic re-send in case it was not ACKed yet. @@ -180,14 +188,18 @@ public class ProcessModel implements ProtocolModel { this.tradingPeer = tradingPeer != null ? tradingPeer : new TradingPeer(); } - public void applyTransient(Provider provider, - TradeManager tradeManager, - Offer offer) { + public void applyTransient(Provider provider, TradeManager tradeManager, Offer offer) { this.offer = offer; this.provider = provider; this.tradeManager = tradeManager; } + public void applyPaymentAccount(Trade trade) { + paymentAccount = trade instanceof MakerTrade ? + getUser().getPaymentAccount(offer.getMakerPaymentAccountId()) : + getUser().getPaymentAccount(trade.getTakerPaymentAccountId()); + } + /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER @@ -216,6 +228,7 @@ public class ProcessModel implements ProtocolModel { Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey))); Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage())); Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); + Optional.ofNullable(paymentAccount).ifPresent(e -> builder.setPaymentAccount(e.toProtoMessage())); return builder.build(); } @@ -247,6 +260,9 @@ public class ProcessModel implements ProtocolModel { MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString); processModel.setPaymentStartedMessageState(paymentStartedMessageState); + if (proto.hasPaymentAccount()) { + processModel.setPaymentAccount(PaymentAccount.fromProto(proto.getPaymentAccount(), coreProtoResolver)); + } return processModel; } @@ -271,12 +287,13 @@ public class ProcessModel implements ProtocolModel { @Nullable public PaymentAccountPayload getPaymentAccountPayload(Trade trade) { - PaymentAccount paymentAccount; - if (trade instanceof MakerTrade) - paymentAccount = getUser().getPaymentAccount(offer.getMakerPaymentAccountId()); - else - paymentAccount = getUser().getPaymentAccount(trade.getTakerPaymentAccountId()); - return paymentAccount != null ? paymentAccount.getPaymentAccountPayload() : null; + if (paymentAccount == null) { + // Persisted trades pre v 1.9.2 have no paymentAccount set, so it will be null. + // We do not need to persist it (though it would not hurt as well). + applyPaymentAccount(trade); + } + + return paymentAccount.getPaymentAccountPayload(); } public Coin getFundsNeededForTrade() { diff --git a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/MaybeCreateSubAccount.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/MaybeCreateSubAccount.java new file mode 100644 index 0000000000..fccbe8592d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/MaybeCreateSubAccount.java @@ -0,0 +1,103 @@ +/* + * 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.core.trade.protocol.bisq_v1.tasks.seller; + +import bisq.core.payment.AssetAccount; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.XmrAccountDelegate; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; + +import bisq.common.taskrunner.TaskRunner; + +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MaybeCreateSubAccount extends TradeTask { + + public MaybeCreateSubAccount(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + PaymentAccount parentAccount = Objects.requireNonNull(processModel.getPaymentAccount()); + + // This is a seller task, so no need to check for it + if (!trade.getOffer().isXmr() || + parentAccount.getExtraData() == null || + parentAccount.getExtraData().isEmpty() || + !XmrAccountDelegate.isUsingSubAddresses(parentAccount)) { + complete(); + return; + } + + // In case we are a seller using XMR sub addresses we clone the account, add it as xmrAccount and + // increment from the highest subAddressIndex from all our subAccounts grouped by the subAccountId (mainAddress + accountIndex). + PaymentAccount paymentAccount = processModel.getTradeManager().cloneAccount(Objects.requireNonNull(parentAccount)); + XmrAccountDelegate xmrAccountDelegate = new XmrAccountDelegate((AssetAccount) paymentAccount); + // We overwrite some fields + xmrAccountDelegate.setId(UUID.randomUUID().toString()); + xmrAccountDelegate.setTradeId(trade.getId()); + xmrAccountDelegate.setCreationDate(new Date().getTime()); + // We add our cloned account as xmrAccount and apply the incremented index and subAddress. + + // We need to store that globally, so we use the user object. + Map> subAccountsBySubAccountId = processModel.getUser().getSubAccountsById(); + subAccountsBySubAccountId.putIfAbsent(xmrAccountDelegate.getSubAccountId(), new HashSet<>()); + Set subAccounts = subAccountsBySubAccountId.get(xmrAccountDelegate.getSubAccountId()); + + // At first subAccount we use the index of the parent account and decrement by 1 as we will increment later in the code + long initialSubAccountIndex = xmrAccountDelegate.getSubAddressIndexAsLong() - 1; + long maxSubAddressIndex = subAccounts.stream() + .mapToLong(XmrAccountDelegate::getSubAddressIndexAsLong) + .max() + .orElse(initialSubAccountIndex); + + // Always increment, use the (decremented) initialSubAccountIndex or the next after max + ++maxSubAddressIndex; + + // Prefix subAddressIndex to account name + xmrAccountDelegate.setAccountName("[" + maxSubAddressIndex + "] " + parentAccount.getAccountName()); + xmrAccountDelegate.setSubAddressIndex(String.valueOf(maxSubAddressIndex)); + xmrAccountDelegate.createAndSetNewSubAddress(); + subAccounts.add(xmrAccountDelegate.getAccount()); + + // Now we set our xmrAccount as paymentAccount + processModel.setPaymentAccount(xmrAccountDelegate.getAccount()); + // We got set the accountId from the parent account at the ProcessModel constructor. We update it to the subAccounts id. + processModel.setAccountId(xmrAccountDelegate.getId()); + processModel.getUser().requestPersistence(); + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} + diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 22555eef91..619d4e607a 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -51,6 +51,7 @@ import javafx.collections.SetChangeListener; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -538,4 +539,8 @@ public class User implements PersistedDataHost { public Cookie getCookie() { return userPayload.getCookie(); } + + public Map> getSubAccountsById() { + return userPayload.getSubAccountsById(); + } } diff --git a/core/src/main/java/bisq/core/user/UserPayload.java b/core/src/main/java/bisq/core/user/UserPayload.java index 1946f945a7..a1f4b2e0a0 100644 --- a/core/src/main/java/bisq/core/user/UserPayload.java +++ b/core/src/main/java/bisq/core/user/UserPayload.java @@ -31,14 +31,15 @@ import bisq.common.proto.ProtoUtil; import bisq.common.proto.persistable.PersistableEnvelope; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -46,7 +47,6 @@ import javax.annotation.Nullable; @Slf4j @Data -@AllArgsConstructor public class UserPayload implements PersistableEnvelope { @Nullable private String accountId; @@ -81,14 +81,55 @@ public class UserPayload implements PersistableEnvelope { @Nullable private List acceptedRefundAgents = new ArrayList<>(); - // Added at 1.5.3 + // Added at v1.5.3 // Generic map for persisting various UI states. We keep values un-typed as string to // provide sufficient flexibility. private Cookie cookie = new Cookie(); + // Was added at v1.9.2 + // Key is in case of XMR subAccounts the subAccountId (mainAddress + accountIndex). This creates unique sets of + // mainAddress + accountIndex combinations. + private Map> subAccountsById = new HashMap<>(); + public UserPayload() { } + public UserPayload(String accountId, + Set paymentAccounts, + PaymentAccount currentPaymentAccount, + List acceptedLanguageLocaleCodes, + Alert developersAlert, + Alert displayedAlert, + Filter developersFilter, + Arbitrator registeredArbitrator, + Mediator registeredMediator, + List acceptedArbitrators, + List acceptedMediators, + PriceAlertFilter priceAlertFilter, + List marketAlertFilters, + RefundAgent registeredRefundAgent, + List acceptedRefundAgents, + Cookie cookie, + Map> subAccountsById) { + this.accountId = accountId; + this.paymentAccounts = paymentAccounts; + this.currentPaymentAccount = currentPaymentAccount; + this.acceptedLanguageLocaleCodes = acceptedLanguageLocaleCodes; + this.developersAlert = developersAlert; + this.displayedAlert = displayedAlert; + this.developersFilter = developersFilter; + this.registeredArbitrator = registeredArbitrator; + this.registeredMediator = registeredMediator; + this.acceptedArbitrators = acceptedArbitrators; + this.acceptedMediators = acceptedMediators; + this.priceAlertFilter = priceAlertFilter; + this.marketAlertFilters = marketAlertFilters; + this.registeredRefundAgent = registeredRefundAgent; + this.acceptedRefundAgents = acceptedRefundAgents; + this.cookie = cookie; + this.subAccountsById = subAccountsById; + } + @Override public protobuf.PersistableEnvelope toProtoMessage() { protobuf.UserPayload.Builder builder = protobuf.UserPayload.newBuilder(); @@ -125,10 +166,27 @@ public class UserPayload implements PersistableEnvelope { .ifPresent(e -> builder.addAllAcceptedRefundAgents(ProtoUtil.collectionToProto(acceptedRefundAgents, message -> ((protobuf.StoragePayload) message).getRefundAgent()))); Optional.ofNullable(cookie).ifPresent(e -> builder.putAllCookie(cookie.toProtoMessage())); + + // We transform our map to a list of SubAccountEntries because protobuf has no good support for maps + builder.addAllSubAccountMapEntries(subAccountsById.entrySet().stream() + .map(mapEntry -> protobuf.SubAccountMapEntry.newBuilder() + .setKey(mapEntry.getKey()) + .addAllValue(mapEntry.getValue().stream() + .map(PaymentAccount::toProtoMessage) + .collect(Collectors.toList())) + .build()) + .collect(Collectors.toList())); return protobuf.PersistableEnvelope.newBuilder().setUserPayload(builder).build(); } public static UserPayload fromProto(protobuf.UserPayload proto, CoreProtoResolver coreProtoResolver) { + // We map the protobuf list to our map (due weak protobuf support for maps) + Map> subAccounts = proto.getSubAccountMapEntriesList().stream() + .collect(Collectors.toMap(protobuf.SubAccountMapEntry::getKey, + subAccountMapEntry -> subAccountMapEntry.getValueList().stream() + .map(subAccount -> PaymentAccount.fromProto(subAccount, coreProtoResolver)) + .collect(Collectors.toSet()))); + return new UserPayload( ProtoUtil.stringOrNullFromProto(proto.getAccountId()), proto.getPaymentAccountsList().isEmpty() ? new HashSet<>() : new HashSet<>(proto.getPaymentAccountsList().stream() @@ -156,7 +214,8 @@ public class UserPayload implements PersistableEnvelope { proto.getAcceptedRefundAgentsList().isEmpty() ? new ArrayList<>() : new ArrayList<>(proto.getAcceptedRefundAgentsList().stream() .map(RefundAgent::fromProto) .collect(Collectors.toList())), - Cookie.fromProto(proto.getCookieMap()) + Cookie.fromProto(proto.getCookieMap()), + subAccounts ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 50eecbee7d..990b9ef944 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1648,10 +1648,17 @@ but you need to enable it in Settings.\n\n\ See the wiki for more information about the auto-confirm feature: [HYPERLINK:https://bisq.wiki/Trading_Monero#Auto-confirming_trades].\n\n\ Please be aware that when changing the Bisq data directory you must change the Monero wallet and address to avoid \ vulnerabilities related to transaction key re-use. More details can be found here: [HYPERLINK:https://www.getmonero.org/2018/09/25/a-post-mortum-of-the-burning-bug.html]. - -account.altcoin.popup.xmr.dataDirWarning=Please be aware that when changing the Bisq data directory you must change the Monero wallet and address to avoid \ - vulnerabilities related to transaction key re-use. More details can be found here: [HYPERLINK:https://www.getmonero.org/2018/09/25/a-post-mortum-of-the-burning-bug.html]. - +account.altcoin.popup.xmr.dataDirWarningHeadline=Security recommendation for Monero traders +account.altcoin.popup.xmr.dataDirWarning=With v1.9.2 subaddress [HYPERLINK:https://monerodocs.org/public-address/subaddress] support has been added to Bisq.\n\n\ + It is recommended to create a new XMR account using subaddresses. This creates a new subaddress at each trade.\n\n\ + If changing the Bisq data directory you need to ensure to not re-use the address due known vulnerabilities [HYPERLINK:https://www.getmonero.org/2018/09/25/a-post-mortum-of-the-burning-bug.html].\n\ + Best way to achieve that, is by using a different Monero wallet for each Bisq instance. Otherwise, make sure to increment \ + the highest ever used subaddress index or use a new mainaddress or account index. +account.altcoin.popup.xmr.subAddressHeadline=Monero Subaddresses +account.altcoin.popup.xmr.subAddressInfo=Information about monero subaddresses can be found here: \ + [HYPERLINK:https://monerodocs.org/public-address/subaddress]\n\n\ + Your main XMR wallet address and View Key [HYPERLINK:https://www.getmonero.org/resources/user-guides/view_only.html] \ + are needed for Bisq to derive a new subaddress per trade. # suppress inspection "UnusedProperty" account.altcoin.popup.msr.msg=Trading MSR on Bisq requires that you understand and fulfill \ the following requirements:\n\n\ @@ -3442,6 +3449,16 @@ payment.altcoin.tradeInstant.popup=For instant trading it is required that both to complete the trade in less than 1 hour.\n\n\ If you have offers open and you are not available please disable \ those offers under the 'Portfolio' screen. +payment.altcoin.useSubAddresses=Use subaddresses +payment.altcoin.xmrSubAddress=Subaddress +payment.altcoin.xmrAccountIndex=Account Index +payment.altcoin.initialXmrSubAddressIndex=Initial Subaddress Index +payment.altcoin.xmrSubAddressesUsed=Subaddresses used in trades +payment.altcoin.xmrMainAddress=Main address +payment.altcoin.privateViewKey=Private view key + +payment.altcoin.usedSubaddressList=Index: {0} Subaddress: {1} Used in trade: {2} + payment.altcoin=Altcoin payment.select.altcoin=Select or search Altcoin payment.select.altcoin.bsq.warning=You can also trade BSQ with the new BSQ Swap protocol.\n\n\ @@ -4298,9 +4315,6 @@ news.bsqSwap.description=BSQ swaps is a new trade protocol for atomically swappi mediation or arbitration support. No account setup is required either.\n\n\ See more about BSQ swaps in documentation [HYPERLINK:https://bisq.wiki/BSQ_swaps]. news.mediationRules.title=Rules for Successful Trading -news.mediationRules.info=We'd like to ask you to make yourself familiar with Bisq's trading \ - rules [HYPERLINK:https://bisq.wiki/Trading_rules], and the penalties [HYPERLINK:https://bisq.wiki/Table_of_penalties] \ - for breaking them. Please check the following linked resources.\n\n\ - "KNOW THE PENALTIES TO AVOID THE PENALTIES"\n\n\ - Sincerely,\n\ - The Bisq Support Team -- on Matrix: [HYPERLINK:https://bisq.chat]. +news.mediationRules.info=Please make yourself familiar with Bisq's trading \ + rules [HYPERLINK:https://bisq.wiki/Trading_rules], [HYPERLINK:https://bisq.wiki/Table_of_penalties].\n\ + If you have questions feel free to get in touch with the Bisq Support team [HYPERLINK:https://bisq.chat]. diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java index 8c30cd1ea1..8c089bc5e9 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java @@ -17,16 +17,12 @@ package bisq.desktop.components.paymentmethods; -import bisq.desktop.components.AutocompleteComboBox; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.FormBuilder; -import bisq.desktop.util.Layout; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.dao.governance.asset.AssetService; -import bisq.core.filter.FilterManager; -import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.payment.AssetAccount; @@ -49,24 +45,18 @@ import javafx.scene.layout.VBox; import javafx.geometry.Insets; -import javafx.util.StringConverter; - import static bisq.desktop.util.DisplayUtils.createAssetsAccountName; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; import static bisq.desktop.util.FormBuilder.addLabelCheckBox; -import static bisq.desktop.util.GUIUtil.getComboBoxButtonCell; public class AssetsForm extends PaymentMethodForm { - public static final String INSTANT_TRADE_NEWS = "instantTradeNews0.9.5"; - private final AssetAccount assetAccount; - private final AltCoinAddressValidator altCoinAddressValidator; - private final AssetService assetService; - private final FilterManager filterManager; - - private InputTextField addressInputTextField; - private CheckBox tradeInstantCheckBox; - private boolean tradeInstant; + protected final AssetAccount assetAccount; + protected final AltCoinAddressValidator altCoinAddressValidator; + protected final AssetService assetService; + protected InputTextField addressInputTextField; + protected CheckBox tradeInstantCheckBox; + protected boolean tradeInstant; public static int addFormForBuyer(GridPane gridPane, int gridRow, @@ -84,13 +74,11 @@ public class AssetsForm extends PaymentMethodForm { GridPane gridPane, int gridRow, CoinFormatter formatter, - AssetService assetService, - FilterManager filterManager) { + AssetService assetService) { super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter); this.assetAccount = (AssetAccount) paymentAccount; this.altCoinAddressValidator = altCoinAddressValidator; this.assetService = assetService; - this.filterManager = filterManager; tradeInstant = paymentAccount instanceof InstantCryptoCurrencyAccount; } @@ -99,9 +87,6 @@ public class AssetsForm extends PaymentMethodForm { public void addFormForAddAccount() { gridRowFrom = gridRow + 1; - addTradeCurrencyComboBox(); - currencyComboBox.setPrefWidth(250); - tradeInstantCheckBox = addLabelCheckBox(gridPane, ++gridRow, Res.get("payment.altcoin.tradeInstantCheckbox"), 10); tradeInstantCheckBox.setSelected(tradeInstant); @@ -114,7 +99,6 @@ public class AssetsForm extends PaymentMethodForm { gridPane.getChildren().remove(tradeInstantCheckBox); tradeInstantCheckBox.setPadding(new Insets(0, 40, 0, 0)); - gridPane.getChildren().add(tradeInstantCheckBox); addressInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, @@ -196,45 +180,4 @@ public class AssetsForm extends PaymentMethodForm { && assetAccount.getSingleTradeCurrency() != null); } } - - @Override - protected void addTradeCurrencyComboBox() { - currencyComboBox = FormBuilder.addLabelAutocompleteComboBox(gridPane, ++gridRow, Res.get("payment.altcoin"), - Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - currencyComboBox.setPromptText(Res.get("payment.select.altcoin")); - currencyComboBox.setButtonCell(getComboBoxButtonCell(Res.get("payment.select.altcoin"), currencyComboBox)); - - currencyComboBox.getEditor().focusedProperty().addListener(observable -> - currencyComboBox.setPromptText("")); - - ((AutocompleteComboBox) currencyComboBox).setAutocompleteItems( - CurrencyUtil.getActiveSortedCryptoCurrencies(assetService, filterManager)); - currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 10)); - - currencyComboBox.setConverter(new StringConverter<>() { - @Override - public String toString(TradeCurrency tradeCurrency) { - return tradeCurrency != null ? tradeCurrency.getNameAndCode() : ""; - } - - @Override - public TradeCurrency fromString(String s) { - return currencyComboBox.getItems().stream(). - filter(item -> item.getNameAndCode().equals(s)). - findAny().orElse(null); - } - }); - - ((AutocompleteComboBox) currencyComboBox).setOnChangeConfirmed(e -> { - addressInputTextField.resetValidation(); - addressInputTextField.validate(); - TradeCurrency tradeCurrency = currencyComboBox.getSelectionModel().getSelectedItem(); - paymentAccount.setSingleTradeCurrency(tradeCurrency); - updateFromInputs(); - - if (tradeCurrency != null && tradeCurrency.getCode().equals("BSQ")) { - new Popup().information(Res.get("payment.select.altcoin.bsq.warning")).show(); - } - }); - } } diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/XmrForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/XmrForm.java new file mode 100644 index 0000000000..7e3517d646 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/XmrForm.java @@ -0,0 +1,382 @@ +/* + * 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.components.paymentmethods; + +import bisq.desktop.components.BisqTextArea; +import bisq.desktop.components.InputTextField; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.Layout; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.dao.governance.asset.AssetService; +import bisq.core.locale.Res; +import bisq.core.locale.TradeCurrency; +import bisq.core.payment.AssetAccount; +import bisq.core.payment.InstantCryptoCurrencyAccount; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.XmrAccountDelegate; +import bisq.core.payment.payload.AssetsAccountPayload; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.payment.validation.AltCoinAddressValidator; +import bisq.core.user.DontShowAgainLookup; +import bisq.core.user.User; +import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.InputValidator; +import bisq.core.util.validation.IntegerValidator; +import bisq.core.util.validation.RegexValidator; + +import bisq.asset.AddressValidationResult; +import bisq.asset.CryptoNoteAddressValidator; + +import bisq.common.util.Tuple3; +import bisq.common.util.Utilities; + +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.xmr.knaccc.monero.address.WalletAddress.PUBLIC_ADDRESS_PREFIX; +import static bisq.desktop.util.DisplayUtils.createAssetsAccountName; +import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField; +import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; +import static bisq.desktop.util.FormBuilder.addLabelCheckBox; + +@Slf4j +public class XmrForm extends AssetsForm { + private InputTextField privateViewKeyInputTextField, accountIndex, subAddressIndex, mainAddressTextField, subAddressTextField; + private CheckBox useSubAddressesCheckBox; + private boolean disableUpdates = false; + private final XmrWalletAddressValidator mainAddressValidator = new XmrWalletAddressValidator(); + private final IntegerValidator accountIndexValidator = new IntegerValidator(0, 99); + private final IntegerValidator subAddressIndexValidator = new IntegerValidator(0, 9999); + private final RegexValidator regexValidator = new RegexValidator(); + private final XmrAccountDelegate xmrAccountDelegate; + private final User user; + + public static int addFormForBuyer(GridPane gridPane, + int gridRow, + PaymentAccountPayload paymentAccountPayload, + String labelTitle) { + addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, labelTitle, + ((AssetsAccountPayload) paymentAccountPayload).getAddress()); + return gridRow; + } + + public XmrForm(PaymentAccount paymentAccount, + AccountAgeWitnessService accountAgeWitnessService, + AltCoinAddressValidator altCoinAddressValidator, + InputValidator inputValidator, + GridPane gridPane, + int gridRow, + CoinFormatter formatter, + AssetService assetService, + User user) { + super(paymentAccount, accountAgeWitnessService, altCoinAddressValidator, inputValidator, gridPane, gridRow, formatter, assetService); + + this.user = user; + xmrAccountDelegate = new XmrAccountDelegate(assetAccount); + } + + @Override + public void addFormForAddAccount() { + gridRowFrom = gridRow + 1; + + tradeInstantCheckBox = addLabelCheckBox(gridPane, ++gridRow, Res.get("payment.altcoin.tradeInstantCheckbox"), 10); + tradeInstantCheckBox.setSelected(tradeInstant); + tradeInstantCheckBox.setOnAction(e -> { + tradeInstant = tradeInstantCheckBox.isSelected(); + if (tradeInstant) + new Popup().information(Res.get("payment.altcoin.tradeInstant.popup")).show(); + paymentLimitationsTextField.setText(getLimitationsText()); + }); + tradeInstantCheckBox.setPadding(new Insets(0, 40, 0, 0)); + + useSubAddressesCheckBox = addLabelCheckBox(gridPane, ++gridRow, Res.get("payment.altcoin.useSubAddresses"), 10); + useSubAddressesCheckBox.setPadding(new Insets(0, 40, 0, 0)); + useSubAddressesCheckBox.setSelected(xmrAccountDelegate.isUsingSubAddresses()); + useSubAddressesCheckBox.setOnAction(e -> { + disableUpdates = true; + xmrAccountDelegate.reset(); + xmrAccountDelegate.setIsUsingSubAddresses(useSubAddressesCheckBox.isSelected()); + if (useSubAddressesCheckBox.isSelected()) { + xmrAccountDelegate.setAccountIndex("0"); + xmrAccountDelegate.setSubAddressIndex("1"); + maybeShowXmrSubAddressInfo(); + } + setFieldManagement(xmrAccountDelegate.isUsingSubAddresses()); + mainAddressTextField.setText(xmrAccountDelegate.getMainAddress()); + privateViewKeyInputTextField.setText(xmrAccountDelegate.getPrivateViewKey()); + accountIndex.setText(xmrAccountDelegate.getAccountIndex()); + subAddressIndex.setText(xmrAccountDelegate.getSubAddressIndex()); + subAddressTextField.setText(xmrAccountDelegate.getSubAddress()); + addressInputTextField.setText(xmrAccountDelegate.getSubAddress()); + disableUpdates = false; + updateFromInputs(); + }); + + mainAddressTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.altcoin.xmrMainAddress")); + mainAddressTextField.setValidator(mainAddressValidator); + mainAddressTextField.textProperty().addListener((ov, oldValue, newValue) -> { + updateFromInputs(); + }); + + privateViewKeyInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.altcoin.privateViewKey")); + regexValidator.setPattern("[a-fA-F0-9]{64}|^$"); + regexValidator.setErrorMessage(Res.get("portfolio.pending.step2_buyer.confirmStart.proof.invalidInput")); + privateViewKeyInputTextField.setValidator(regexValidator); + privateViewKeyInputTextField.textProperty().addListener((ov, oldValue, newValue) -> { + updateFromInputs(); + }); + + HBox hBox = new HBox(); + hBox.setSpacing(10); + + accountIndex = new InputTextField(); + accountIndex.setLabelFloat(true); + accountIndex.setPromptText(Res.get("payment.altcoin.xmrAccountIndex")); + accountIndex.setPrefWidth(100); + accountIndex.setValidator(accountIndexValidator); + accountIndex.textProperty().addListener((ov, oldValue, newValue) -> { + updateFromInputs(); + }); + + subAddressIndex = new InputTextField(); + subAddressIndex.setLabelFloat(true); + subAddressIndex.setPromptText(Res.get("payment.altcoin.initialXmrSubAddressIndex")); + subAddressIndex.setPrefWidth(130); + subAddressIndex.setValidator(subAddressIndexValidator); + subAddressIndex.textProperty().addListener((ov, oldValue, newValue) -> { + updateFromInputs(); + }); + + subAddressTextField = new InputTextField(); + subAddressTextField.setLabelFloat(true); + subAddressTextField.setPromptText(Res.get("payment.altcoin.xmrSubAddress")); + subAddressTextField.setDisable(true); // this field gets calculated, so read-only + subAddressTextField.setPrefWidth(750); + hBox.getChildren().addAll(accountIndex, subAddressIndex, subAddressTextField); + GridPane.setRowIndex(hBox, ++gridRow); + GridPane.setColumnIndex(hBox, 0); + GridPane.setMargin(hBox, new Insets(0 + Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0)); + gridPane.getChildren().add(hBox); + + // subAddressTextField and addressInputTextField share the same row, they are used interchangably + // depending on if subaddresses are in use + addressInputTextField = FormBuilder.addInputTextField(gridPane, gridRow, Res.get("payment.altcoin.address")); + addressInputTextField.setValidator(altCoinAddressValidator); + addressInputTextField.textProperty().addListener((ov, oldValue, newValue) -> { + updateFromInputs(); + }); + + setFieldManagement(xmrAccountDelegate.isUsingSubAddresses()); + addLimitations(false); + addAccountNameTextFieldWithAutoFillToggleButton(); + } + + void setFieldManagement(boolean useSubAddresses) { + useSubAddressesCheckBox.setManaged(true); + useSubAddressesCheckBox.setVisible(true); + mainAddressTextField.setManaged(useSubAddresses); + mainAddressTextField.setVisible(useSubAddresses); + privateViewKeyInputTextField.setManaged(useSubAddresses); + privateViewKeyInputTextField.setVisible(useSubAddresses); + accountIndex.setManaged(useSubAddresses); + accountIndex.setVisible(useSubAddresses); + subAddressIndex.setManaged(useSubAddresses); + subAddressIndex.setVisible(useSubAddresses); + subAddressTextField.setManaged(useSubAddresses); + subAddressTextField.setVisible(useSubAddresses); + addressInputTextField.setManaged(!useSubAddresses); + addressInputTextField.setVisible(!useSubAddresses); + } + + @Override + public PaymentAccount getPaymentAccount() { + if (tradeInstant) { + InstantCryptoCurrencyAccount instantCryptoCurrencyAccount = new InstantCryptoCurrencyAccount(); + instantCryptoCurrencyAccount.init(); + instantCryptoCurrencyAccount.setAccountName(paymentAccount.getAccountName()); + instantCryptoCurrencyAccount.setSaltAsHex(paymentAccount.getSaltAsHex()); + instantCryptoCurrencyAccount.setSalt(paymentAccount.getSalt()); + instantCryptoCurrencyAccount.setSingleTradeCurrency(paymentAccount.getSingleTradeCurrency()); + instantCryptoCurrencyAccount.setSelectedTradeCurrency(paymentAccount.getSelectedTradeCurrency()); + instantCryptoCurrencyAccount.setAddress(xmrAccountDelegate.getAddress()); + instantCryptoCurrencyAccount.setExtraData(paymentAccount.getExtraData()); + return instantCryptoCurrencyAccount; + } else { + return paymentAccount; + } + } + + @Override + public void updateFromInputs() { + if (disableUpdates) { + return; + } + disableUpdates = true; + if (xmrAccountDelegate.isUsingSubAddresses()) { + xmrAccountDelegate.setMainAddress(mainAddressTextField.getText()); + xmrAccountDelegate.setPrivateViewKey(privateViewKeyInputTextField.getText()); + xmrAccountDelegate.setAccountIndex(accountIndex.getText()); + xmrAccountDelegate.setSubAddressIndex(subAddressIndex.getText()); + if (accountIndex.validate() && subAddressIndex.validate() + && mainAddressTextField.validate() + && privateViewKeyInputTextField.validate() + && mainAddressTextField.getText().length() > 0 + && privateViewKeyInputTextField.getText().length() > 0) { + try { + xmrAccountDelegate.createAndSetNewSubAddress(); + } catch (Exception ex) { + log.warn(ex.toString()); + } + subAddressTextField.setText(xmrAccountDelegate.getSubAddress()); + } else { + xmrAccountDelegate.setSubAddress(""); + subAddressTextField.setText(""); + } + } else { + // legacy XMR (no subAddress) + xmrAccountDelegate.setAddress(addressInputTextField.getText()); + } + super.updateFromInputs(); + disableUpdates = false; + } + + @Override + protected void autoFillNameTextField() { + if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) { + accountNameTextField.setText(createAssetsAccountName(paymentAccount, xmrAccountDelegate.isUsingSubAddresses() ? + xmrAccountDelegate.getMainAddress() : xmrAccountDelegate.getAddress())); + } + } + + @Override + public void addFormForEditAccount() { + gridRowFrom = gridRow; + addAccountNameTextFieldWithAutoFillToggleButton(); + addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"), + Res.get(xmrAccountDelegate.getAccount().getPaymentMethod().getId())); + final TradeCurrency singleTradeCurrency = xmrAccountDelegate.getAccount().getSingleTradeCurrency(); + final String nameAndCode = singleTradeCurrency != null ? singleTradeCurrency.getNameAndCode() : ""; + addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.altcoin"), nameAndCode); + + if (xmrAccountDelegate.isUsingSubAddresses()) { + Tuple3 xmrMainAddress = addCompactTopLabelTextField(gridPane, ++gridRow, + Res.get("payment.altcoin.xmrMainAddress"), xmrAccountDelegate.getMainAddress()); + xmrMainAddress.second.setMouseTransparent(false); + addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.altcoin.xmrAccountIndex"), xmrAccountDelegate.getAccountIndex()) + .second.setMouseTransparent(false); + addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.altcoin.initialXmrSubAddressIndex"), xmrAccountDelegate.getSubAddressIndex()) + .second.setMouseTransparent(false); + + Map> subAccountsByMainAddress = user.getSubAccountsById(); + List subAccounts = new ArrayList<>(subAccountsByMainAddress.getOrDefault(xmrAccountDelegate.getSubAccountId(), new HashSet<>())); + if (subAccounts.size() == 0) { + addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.altcoin.xmrSubAddress"), xmrAccountDelegate.getSubAddress()) + .second.setMouseTransparent(false); + } else { + StringBuilder subAddressReport = new StringBuilder(); + subAccounts.sort(Comparator.comparing(PaymentAccount::getCreationDate)); + for (PaymentAccount account : subAccounts) { + XmrAccountDelegate delegate = new XmrAccountDelegate((AssetAccount) account); + subAddressReport.append(Res.get("payment.altcoin.usedSubaddressList", + delegate.getSubAddressIndex(), + delegate.getSubAddress(), + Utilities.getShortId(delegate.getTradeId()))) + .append(System.lineSeparator()); + } + GridPane gridPane2 = new GridPane(); + gridPane2.getColumnConstraints().add(gridPane.getColumnConstraints().get(0)); + TitledPane titledPane = new TitledPane(Res.get("payment.altcoin.xmrSubAddressesUsed"), gridPane2); + titledPane.setExpanded(false); + gridPane.add(titledPane, 0, ++gridRow); + TextArea subAddressTextArea = new BisqTextArea(); + gridPane2.add(subAddressTextArea, 0, 1); + subAddressTextArea.setMinHeight(70); + subAddressTextArea.setText(subAddressReport.toString()); + subAddressTextArea.setEditable(false); + } + } else { + addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.altcoin.address"), xmrAccountDelegate.getAddress()) + .second.setMouseTransparent(false); + } + addLimitations(true); + } + + @Override + public void updateAllInputsValid() { + TradeCurrency selectedTradeCurrency = xmrAccountDelegate.getAccount().getSelectedTradeCurrency(); + if (selectedTradeCurrency != null) { + altCoinAddressValidator.setCurrencyCode(selectedTradeCurrency.getCode()); + if (xmrAccountDelegate.isUsingSubAddresses()) { + // monero using subaddresses + allInputsValid.set(isAccountNameValid() + && altCoinAddressValidator.validate(xmrAccountDelegate.getSubAddress()).isValid + && mainAddressValidator.validate(xmrAccountDelegate.getMainAddress()).isValid + && regexValidator.validate(xmrAccountDelegate.getPrivateViewKey()).isValid + && accountIndexValidator.validate(xmrAccountDelegate.getAccountIndex()).isValid + && subAddressIndexValidator.validate(xmrAccountDelegate.getSubAddressIndex()).isValid + && xmrAccountDelegate.getAccount().getSingleTradeCurrency() != null); + } else { + // fixed monero address + allInputsValid.set(isAccountNameValid() + && altCoinAddressValidator.validate(xmrAccountDelegate.getAddress()).isValid + && xmrAccountDelegate.getAccount().getSingleTradeCurrency() != null); + } + } + } + + private void maybeShowXmrSubAddressInfo() { + String key = "xmrSubAddressInfo"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup() + .headLine(Res.get("account.altcoin.popup.xmr.subAddressHeadline")) + .attention(Res.get("account.altcoin.popup.xmr.subAddressInfo")) + .dontShowAgainId(key) + .show(); + } + } + + private static class XmrWalletAddressValidator extends InputValidator { + // enforce that the main wallet address uses PUBLIC_ADDRESS_PREFIX (not a subaddress) + private final CryptoNoteAddressValidator validator = new CryptoNoteAddressValidator(PUBLIC_ADDRESS_PREFIX); + + @Override + public ValidationResult validate(String input) { + AddressValidationResult adr = validator.validate(input); + return new ValidationResult(adr.isValid(), adr.getMessage()); + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java index 77a0c2154f..9158d35d94 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainView.java +++ b/desktop/src/main/java/bisq/desktop/main/MainView.java @@ -190,6 +190,7 @@ public class MainView extends InitializableView JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton); JFXBadge supportButtonWithBadge = new JFXBadge(supportButton); JFXBadge settingsButtonWithBadge = new JFXBadge(settingsButton); + JFXBadge accountButtonWithBadge = new JFXBadge(accountButton); JFXBadge daoButtonWithBadge = new JFXBadge(daoButton); Locale locale = GlobalSettings.getLocale(); @@ -322,7 +323,7 @@ public class MainView extends InitializableView HBox.setHgrow(primaryNav, Priority.SOMETIMES); HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButtonWithBadge, - getNavigationSpacer(), accountButton, getNavigationSpacer(), daoButtonWithBadge); + getNavigationSpacer(), accountButtonWithBadge, getNavigationSpacer(), daoButtonWithBadge); secondaryNav.getStyleClass().add("nav-secondary"); HBox.setHgrow(secondaryNav, Priority.SOMETIMES); @@ -369,6 +370,9 @@ public class MainView extends InitializableView setupBadge(settingsButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowSettingsUpdatesNotification()); settingsButtonWithBadge.getStyleClass().add("new"); + setupBadge(accountButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowAccountUpdatesNotification()); + accountButtonWithBadge.getStyleClass().add("new"); + setupBadge(daoButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowDaoUpdatesNotification()); daoButtonWithBadge.getStyleClass().add("new"); diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index b4cae5695b..c1bcaa62d3 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -534,13 +534,11 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { if (p2PService.isBootstrapped()) { setupInvalidOpenOffersHandler(); - maybeShowXmrTxKeyReUseWarning(); } else { p2PService.addP2PServiceListener(new BootstrapListener() { @Override public void onUpdatedDataReceived() { setupInvalidOpenOffersHandler(); - maybeShowXmrTxKeyReUseWarning(); } }); } @@ -568,21 +566,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { } } - private void maybeShowXmrTxKeyReUseWarning() { - String key = "xmrTxKeyReUse"; - // If we do not have a XMR account we set showAgain to true to avoid the popup at next restart because we show - // the information at account creation as well. - if (user.getPaymentAccounts() != null && - user.getPaymentAccounts().stream() - .noneMatch(a -> a.getSingleTradeCurrency() != null && a.getSingleTradeCurrency().getCode().equals("XMR"))) { - preferences.dontShowAgain(key, true); - } - - if (preferences.showAgain(key)) { - new Popup().information(Res.get("account.altcoin.popup.xmr.dataDirWarning")).dontShowAgainId(key).show(); - } - } - private void setupP2PNumPeersWatcher() { p2PService.getNumConnectedPeers().addListener((observable, oldValue, newValue) -> { int numPeers = (int) newValue; 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 40993a913e..427f96c50f 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java @@ -266,12 +266,21 @@ public class AccountView extends ActivatableView { } String key = "accountPrivacyInfo"; - if (!DevEnv.isDevMode()) + if (DontShowAgainLookup.showAgain(key)) { + // for newbs: the welcome to your bisq account page new Popup() .headLine(Res.get("account.info.headline")) .backgroundInfo(Res.get("account.info.msg")) .dontShowAgainId(key) .show(); + } else { + // news badge leads to the XMR subaddress info page (added in v1.9.2) + new Popup() + .headLine(Res.get("account.altcoin.popup.xmr.dataDirWarningHeadline")) + .backgroundInfo(Res.get("account.altcoin.popup.xmr.dataDirWarning")) + .dontShowAgainId("accountSubAddressInfo") + .show(); + } } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java index 9d107e1d38..1060788f4b 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java @@ -18,9 +18,11 @@ package bisq.desktop.main.account.content.altcoinaccounts; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutocompleteComboBox; import bisq.desktop.components.TitledGroupBg; import bisq.desktop.components.paymentmethods.AssetsForm; import bisq.desktop.components.paymentmethods.PaymentMethodForm; +import bisq.desktop.components.paymentmethods.XmrForm; import bisq.desktop.main.account.content.PaymentAccountsView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.FormBuilder; @@ -38,6 +40,7 @@ import bisq.core.payment.PaymentAccountFactory; import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.validation.AltCoinAddressValidator; import bisq.core.user.Preferences; +import bisq.core.user.User; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.InputValidator; @@ -55,6 +58,7 @@ import javax.inject.Named; import javafx.stage.Stage; import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.layout.GridPane; @@ -62,13 +66,15 @@ import javafx.scene.layout.VBox; import javafx.collections.ObservableList; +import javafx.util.StringConverter; + import java.util.Optional; -import static bisq.desktop.components.paymentmethods.AssetsForm.INSTANT_TRADE_NEWS; import static bisq.desktop.util.FormBuilder.add2ButtonsAfterGroup; import static bisq.desktop.util.FormBuilder.add3ButtonsAfterGroup; import static bisq.desktop.util.FormBuilder.addTitledGroupBg; import static bisq.desktop.util.FormBuilder.addTopLabelListView; +import static bisq.desktop.util.GUIUtil.getComboBoxButtonCell; @FxmlView public class AltCoinAccountsView extends PaymentAccountsView { @@ -78,12 +84,14 @@ public class AltCoinAccountsView extends PaymentAccountsView currencyComboBox; @Inject public AltCoinAccountsView(AltCoinAccountsViewModel model, @@ -93,6 +101,7 @@ public class AltCoinAccountsView extends PaymentAccountsView tuple2 = add2ButtonsAfterGroup(root, ++gridRow, Res.get("shared.saveNewAccount"), Res.get("shared.cancel")); @@ -262,9 +270,23 @@ public class AltCoinAccountsView extends PaymentAccountsViewaddLabelAutocompleteComboBox(gridPane, ++gridRow, Res.get("payment.altcoin"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + currencyComboBox.setPromptText(Res.get("payment.select.altcoin")); + currencyComboBox.setButtonCell(getComboBoxButtonCell(Res.get("payment.select.altcoin"), currencyComboBox)); + + currencyComboBox.getEditor().focusedProperty().addListener(observable -> + currencyComboBox.setPromptText("")); + + ((AutocompleteComboBox) currencyComboBox).setAutocompleteItems( + CurrencyUtil.getActiveSortedCryptoCurrencies(assetService, filterManager)); + currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 10)); + + currencyComboBox.setConverter(new StringConverter<>() { + @Override + public String toString(TradeCurrency tradeCurrency) { + return tradeCurrency != null ? tradeCurrency.getNameAndCode() : ""; + } + + @Override + public TradeCurrency fromString(String s) { + return currencyComboBox.getItems().stream(). + filter(item -> item.getNameAndCode().equals(s)). + findAny().orElse(null); + } + }); + + if (selectedCurrency != null) { + currencyComboBox.setValue(selectedCurrency); + } + ((AutocompleteComboBox) currencyComboBox).setOnChangeConfirmed(e -> { + if (currencyComboBox.getValue() != null) { + addNewAccount(); + } + }); + } + + } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java index b9f33a5e38..955fc9fcd7 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java @@ -147,9 +147,6 @@ public class DaoView extends ActivatableView { if (preferences.showAgain(DaoPresentation.DAO_NEWS)) { preferences.dontShowAgain(DaoPresentation.DAO_NEWS, true); - new Popup().headLine(Res.get("news.bsqSwap.title")) - .information(Res.get("news.bsqSwap.description")) - .show(); } navigation.addListener(navigationListener); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 46036e62e7..97cbc7d4f7 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -685,7 +685,7 @@ public abstract class TradeStepView extends AnchorPane { } protected boolean isXmrTrade() { - return getCurrencyCode(trade).equals("XMR"); + return checkNotNull(trade.getOffer()).isXmr(); } private void updateTradePeriodState(Trade.TradePeriodState tradePeriodState) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 2d9c46e5b5..5b24b48cff 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -225,10 +225,8 @@ public class SellerStep3View extends TradeStepView { .orElse(""); if (myPaymentAccountPayload instanceof AssetsAccountPayload) { - if (myPaymentDetails.isEmpty()) { - // Not expected - myPaymentDetails = ((AssetsAccountPayload) myPaymentAccountPayload).getAddress(); - } + // for altcoins always display the receiving address + myPaymentDetails = ((AssetsAccountPayload) myPaymentAccountPayload).getAddress(); peersPaymentDetails = peersPaymentAccountPayload != null ? ((AssetsAccountPayload) peersPaymentAccountPayload).getAddress() : "NA"; myTitle = Res.get("portfolio.pending.step3_seller.yourAddress", currencyName); diff --git a/desktop/src/main/java/bisq/desktop/main/presentation/AccountPresentation.java b/desktop/src/main/java/bisq/desktop/main/presentation/AccountPresentation.java index f7af11f387..3cf3a0ae86 100644 --- a/desktop/src/main/java/bisq/desktop/main/presentation/AccountPresentation.java +++ b/desktop/src/main/java/bisq/desktop/main/presentation/AccountPresentation.java @@ -37,7 +37,7 @@ import javafx.collections.MapChangeListener; @Singleton public class AccountPresentation { - public static final String ACCOUNT_NEWS = "accountNews"; + public static final String ACCOUNT_NEWS = "accountNews_XmrSubAddresses"; private Preferences preferences; @@ -64,6 +64,7 @@ public class AccountPresentation { } public void setup() { + // devs enable this when a news badge is required showNotification.set(preferences.showAgain(ACCOUNT_NEWS)); } diff --git a/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java b/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java index 05fe6d0406..b1a6791ec3 100644 --- a/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java +++ b/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java @@ -31,7 +31,7 @@ import lombok.Getter; @Singleton public class DaoPresentation implements DaoStateListener { - public static final String DAO_NEWS = "daoNews_BsqSwaps"; + public static final String DAO_NEWS = "daoNews"; private final Preferences preferences; private final Navigation navigation; @@ -65,7 +65,9 @@ public class DaoPresentation implements DaoStateListener { preferences.getDontShowAgainMapAsObservable().addListener((MapChangeListener) change -> { if (change.getKey().equals(DAO_NEWS) && DevEnv.isDaoActivated()) { - showNotification.set(!change.wasAdded()); + // devs enable this when a news badge is required + // showNotification.set(!change.wasAdded()); + showNotification.set(false); } }); @@ -127,8 +129,9 @@ public class DaoPresentation implements DaoStateListener { } public void setup() { - if (DevEnv.isDaoActivated()) - showNotification.set(preferences.showAgain(DAO_NEWS)); + // devs enable this when a news badge is required + //showNotification.set(DevEnv.isDaoActivated() && preferences.showAgain(DAO_NEWS)); + showNotification.set(false); this.btcWalletService.getChainHeightProperty().addListener(walletChainHeightListener); daoStateService.addDaoStateListener(this); diff --git a/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java b/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java index f1eab5c2f9..9e6c6c6042 100644 --- a/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java +++ b/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java @@ -31,7 +31,7 @@ import javafx.collections.MapChangeListener; @Singleton public class SettingsPresentation { - public static final String SETTINGS_BADGE_KEY = "settingsPrivacyFeature"; + public static final String SETTINGS_BADGE_KEY = "settingsNews"; private Preferences preferences; @@ -44,7 +44,9 @@ public class SettingsPresentation { preferences.getDontShowAgainMapAsObservable().addListener((MapChangeListener) change -> { if (change.getKey().equals(SETTINGS_BADGE_KEY)) { - showNotification.set(!change.wasAdded()); + // devs enable this when a news badge is required + // showNotification.set(!change.wasAdded()); + showNotification.set(false); } }); } @@ -58,6 +60,8 @@ public class SettingsPresentation { } public void setup() { - showNotification.set(preferences.showAgain(SETTINGS_BADGE_KEY)); + // devs enable this when a news badge is required + // showNotification.set(preferences.showAgain(SETTINGS_BADGE_KEY)); + showNotification.set(false); } } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java index 771df416cd..1e636227a3 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java @@ -24,14 +24,12 @@ import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.View; import bisq.desktop.common.view.ViewLoader; import bisq.desktop.main.MainView; -import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.presentation.SettingsPresentation; import bisq.desktop.main.settings.about.AboutView; import bisq.desktop.main.settings.network.NetworkSettingsView; import bisq.desktop.main.settings.preferences.PreferencesView; import bisq.core.locale.Res; -import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import javax.inject.Inject; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index d12eec46fc..e51d4b293f 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -226,7 +226,8 @@ public class PreferencesView extends ActivatableViewAndModel cookie = 16; + repeated SubAccountMapEntry sub_account_map_entries = 17; +} + +message SubAccountMapEntry { + string key = 1; + repeated PaymentAccount value = 2; } message BaseBlock { @@ -2414,6 +2421,7 @@ message PaymentAccount { repeated TradeCurrency trade_currencies = 5; TradeCurrency selected_trade_currency = 6; PaymentAccountPayload payment_account_payload = 7; + map extra_data = 8; } message PaymentMethod {