diff --git a/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java b/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java index 179f136ed2..d72d8ff219 100644 --- a/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java +++ b/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java @@ -24,11 +24,10 @@ import org.bitcoinj.wallet.CoinSelector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -39,13 +38,100 @@ import static com.google.common.base.Preconditions.checkNotNull; class SavingsWalletCoinSelector implements CoinSelector { private static final Logger log = LoggerFactory.getLogger(SavingsWalletCoinSelector.class); protected final NetworkParameters params; + @Nullable + private AddressEntryList addressEntryList; + @Nullable + private Set
savingsWalletAddressSet; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public SavingsWalletCoinSelector(NetworkParameters params) { + public SavingsWalletCoinSelector(NetworkParameters params, AddressEntryList addressEntryList) { this.params = params; + this.addressEntryList = addressEntryList; + } + + protected SavingsWalletCoinSelector(NetworkParameters params) { + this.params = params; + } + + protected boolean matchesRequirement(TransactionOutput transactionOutput) { + if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) { + Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); + log.trace("only lookup in savings wallet address entries"); + log.trace(addressOutput.toString()); + + if (savingsWalletAddressSet != null && savingsWalletAddressSet.contains(addressOutput)) { + return true; + } else { + log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " + + addressOutput.toString() + " / " + addressOutput.toString()); + return false; + } + } else { + return false; + } + } + + private static boolean isInBlockChainOrPending(Transaction tx) { + // Pick chain-included transactions and transactions that are pending. + TransactionConfidence confidence = tx.getConfidence(); + TransactionConfidence.ConfidenceType type = confidence.getConfidenceType(); + + log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers()); + return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || + type.equals(TransactionConfidence.ConfidenceType.PENDING); + } + + /** + * Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. + */ + protected boolean shouldSelect(Transaction tx) { + return isInBlockChainOrPending(tx); + } + + @Override + public CoinSelection select(Coin target, List candidates) { + log.trace("candidates.size: " + candidates.size()); + long targetAsLong = target.longValue(); + log.trace("value needed: " + targetAsLong); + HashSet selected = new HashSet<>(); + // Sort the inputs by age*value so we get the highest "coindays" spent. + ArrayList sortedOutputs = new ArrayList<>(candidates); + // When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting + // them in order to improve performance. + if (!target.equals(NetworkParameters.MAX_MONEY)) { + sortOutputs(sortedOutputs); + } + // Now iterate over the sorted outputs until we have got as close to the target as possible or a little + // bit over (excessive value will be change). + long total = 0; + if (addressEntryList != null) { + savingsWalletAddressSet = addressEntryList.stream() + .filter(addressEntry1 -> addressEntry1.getContext() == AddressEntry.Context.SAVINGS) + .map(AddressEntry::getAddress) + .collect(Collectors.toSet()); + } + for (TransactionOutput output : sortedOutputs) { + if (total >= targetAsLong) { + break; + } + // Only pick chain-included transactions, or transactions that are ours and pending. + // Only select outputs from our defined address(es) + if (!shouldSelect(output.getParentTransaction()) || !matchesRequirement(output)) { + continue; + } + + selected.add(output); + total += output.getValue().longValue(); + + log.debug("adding up outputs: output/total: " + output.getValue().longValue() + "/" + total); + } + // Total may be lower than target here, if the given candidates were insufficient to create to requested + // transaction. + return new CoinSelection(Coin.valueOf(total), selected); } @VisibleForTesting @@ -71,61 +157,4 @@ class SavingsWalletCoinSelector implements CoinSelector { }); } - private static boolean isInBlockChainOrPending(Transaction tx) { - // Pick chain-included transactions and transactions that are pending. - TransactionConfidence confidence = tx.getConfidence(); - TransactionConfidence.ConfidenceType type = confidence.getConfidenceType(); - - log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers()); - return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || - type.equals(TransactionConfidence.ConfidenceType.PENDING); - } - - /** - * Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. - */ - protected boolean shouldSelect(Transaction tx) { - return isInBlockChainOrPending(tx); - } - - protected boolean matchesRequirement(TransactionOutput transactionOutput) { - return (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()); - } - - @Override - public CoinSelection select(Coin target, List candidates) { - log.trace("candidates.size: " + candidates.size()); - long targetAsLong = target.longValue(); - log.trace("value needed: " + targetAsLong); - HashSet selected = new HashSet<>(); - // Sort the inputs by age*value so we get the highest "coindays" spent. - ArrayList sortedOutputs = new ArrayList<>(candidates); - // When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting - // them in order to improve performance. - if (!target.equals(NetworkParameters.MAX_MONEY)) { - sortOutputs(sortedOutputs); - } - // Now iterate over the sorted outputs until we have got as close to the target as possible or a little - // bit over (excessive value will be change). - long total = 0; - for (TransactionOutput output : sortedOutputs) { - if (total >= targetAsLong) { - break; - } - // Only pick chain-included transactions, or transactions that are ours and pending. - // Only select outputs from our defined address(es) - if (!shouldSelect(output.getParentTransaction()) || !matchesRequirement(output)) { - continue; - } - - selected.add(output); - total += output.getValue().longValue(); - - log.debug("adding up outputs: output/total: " + output.getValue().longValue() + "/" + total); - } - // Total may be lower than target here, if the given candidates were insufficient to create to requested - // transaction. - return new CoinSelection(Coin.valueOf(total), selected); - } - } diff --git a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java index 932fb8cf20..8965ad04e0 100644 --- a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java +++ b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java @@ -101,6 +101,7 @@ public class TradeWalletService { private WalletAppKit walletAppKit; @Nullable private KeyParameter aesKey; + private AddressEntryList addressEntryList; /////////////////////////////////////////////////////////////////////////////////////////// @@ -158,7 +159,7 @@ public class TradeWalletService { sendRequest.shuffleOutputs = false; sendRequest.aesKey = aesKey; if (useSavingsWallet) - sendRequest.coinSelector = new SavingsWalletCoinSelector(params); + sendRequest.coinSelector = new SavingsWalletCoinSelector(params, addressEntryList); else sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); // We use a fixed fee @@ -1064,4 +1065,8 @@ public class TradeWalletService { throw new WalletException(t); } } + + public void setAddressEntryList(AddressEntryList addressEntryList) { + this.addressEntryList = addressEntryList; + } } diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 9f8ad2bb14..be7d4247e7 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -188,6 +188,7 @@ public class WalletService { // set after wallet is ready tradeWalletService.setWalletAppKit(walletAppKit); + tradeWalletService.setAddressEntryList(addressEntryList); timeoutTimer.stop(); // onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java index 613ecf05ed..b53089fa39 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java @@ -205,25 +205,6 @@ public class DepositView extends ActivatableView { }; } - private Coin getAmountAsCoin() { - Coin senderAmount = formatter.parseToCoin(amountTextField.getText()); - if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) { - senderAmount = Coin.ZERO; - /* new Popup() - .warning("The amount is lower than the transaction fee and the min. possible tx value (dust).") - .show();*/ - } - return senderAmount; - } - - @NotNull - private String getBitcoinURI() { - return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(), - getAmountAsCoin(), - paymentLabel, - null); - } - @Override protected void activate() { tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener); @@ -236,6 +217,9 @@ public class DepositView extends ActivatableView { addressTextField.setAmountAsCoin(formatter.parseToCoin(t)); updateQRCode(); }); + + if (tableView.getSelectionModel().getSelectedItem() == null && !sortedList.isEmpty()) + tableView.getSelectionModel().select(0); } @Override @@ -309,6 +293,24 @@ public class DepositView extends ActivatableView { .forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter))); } + private Coin getAmountAsCoin() { + Coin senderAmount = formatter.parseToCoin(amountTextField.getText()); + if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) { + senderAmount = Coin.ZERO; + /* new Popup() + .warning("The amount is lower than the transaction fee and the min. possible tx value (dust).") + .show();*/ + } + return senderAmount; + } + + @NotNull + private String getBitcoinURI() { + return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(), + getAmountAsCoin(), + paymentLabel, + null); + } /////////////////////////////////////////////////////////////////////////////////////////// // ColumnCellFactories diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index 934744c47b..f683d0ea13 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -228,7 +228,6 @@ class CreateOfferDataModel extends ActivatableDataModel { calculateVolume(); calculateTotalToPay(); - updateBalance(); } void onTabSelected(boolean isSelected) { @@ -394,11 +393,13 @@ class CreateOfferDataModel extends ActivatableDataModel { } void calculateTotalToPay() { - if (securityDepositAsCoin != null) { + if (direction != null && amountAsCoin.get() != null) { Coin feeAndSecDeposit = offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin); - Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get()); + Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amountAsCoin.get()); Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDepositAndAmount; totalToPayAsCoin.set(required); + log.debug("totalToPayAsCoin " + totalToPayAsCoin.get().toFriendlyString()); + updateBalance(); } } @@ -407,11 +408,12 @@ class CreateOfferDataModel extends ActivatableDataModel { if (useSavingsWallet) { Coin savingWalletBalance = walletService.getSavingWalletBalance(); totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); - - if (totalToPayAsCoin.get() != null && totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) - balance.set(totalToPayAsCoin.get()); - else - balance.set(totalAvailableBalance); + if (totalToPayAsCoin.get() != null) { + if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) + balance.set(totalToPayAsCoin.get()); + else + balance.set(totalAvailableBalance); + } } else { balance.set(tradeWalletBalance); } @@ -422,6 +424,8 @@ class CreateOfferDataModel extends ActivatableDataModel { missingCoin.set(Coin.ZERO); } + log.debug("missingCoin " + missingCoin.get().toFriendlyString()); + isWalletFunded.set(isBalanceSufficient(balance.get())); if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) { walletFundedNotification = new Notification() diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java index 843c05fe56..be2f81cdb4 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java @@ -176,7 +176,6 @@ class TakeOfferDataModel extends ActivatableDataModel { calculateVolume(); calculateTotalToPay(); - updateBalance(); balanceListener = new BalanceListener(addressEntry.getAddress()) { @Override @@ -325,11 +324,21 @@ class TakeOfferDataModel extends ActivatableDataModel { } } + void setAmount(Coin amount) { + amountAsCoin.set(amount); + calculateTotalToPay(); + } + void calculateTotalToPay() { - if (getDirection() == Offer.Direction.SELL) - totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin)); - else - totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get())); + if (offer != null && amountAsCoin.get() != null) { + if (getDirection() == Offer.Direction.SELL) + totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin)); + else + totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get())); + + updateBalance(); + log.debug("totalToPayAsCoin " + totalToPayAsCoin.get().toFriendlyString()); + } } void updateBalance() { @@ -337,11 +346,12 @@ class TakeOfferDataModel extends ActivatableDataModel { if (useSavingsWallet) { Coin savingWalletBalance = walletService.getSavingWalletBalance(); totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); - - if (totalToPayAsCoin.get() != null && totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) - balance.set(totalToPayAsCoin.get()); - else - balance.set(totalAvailableBalance); + if (totalToPayAsCoin.get() != null) { + if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) + balance.set(totalToPayAsCoin.get()); + else + balance.set(totalAvailableBalance); + } } else { balance.set(tradeWalletBalance); } @@ -350,17 +360,18 @@ class TakeOfferDataModel extends ActivatableDataModel { if (missingCoin.get().isNegative()) missingCoin.set(Coin.ZERO); } + log.debug("missingCoin " + missingCoin.get().toFriendlyString()); isWalletFunded.set(isBalanceSufficient(balance.get())); if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) { - walletFundedNotification = new Notification() - .headLine("Trading wallet update") - .notification("Your trading wallet is sufficiently funded.\n" + - "Amount: " + formatter.formatCoinWithCode(totalToPayAsCoin.get())) - .autoClose(); + walletFundedNotification = new Notification() + .headLine("Trading wallet update") + .notification("Your trading wallet is sufficiently funded.\n" + + "Amount: " + formatter.formatCoinWithCode(totalToPayAsCoin.get())) + .autoClose(); - walletFundedNotification.show(); - } + walletFundedNotification.show(); + } } private boolean isBalanceSufficient(Coin balance) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java index 9f83b7e738..d1b9708e24 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java @@ -520,7 +520,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } private void setAmountToModel() { - dataModel.amountAsCoin.set(formatter.parseToCoinWith4Decimals(amount.get())); + dataModel.setAmount(formatter.parseToCoinWith4Decimals(amount.get())); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index db97460e4b..339299f870 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -228,7 +228,7 @@ public class SellerStep3View extends TradeStepView { if (model.p2PService.isBootstrapped()) { Preferences preferences = model.dataModel.preferences; String key = "confirmPaymentReceived"; - if (preferences.showAgain(key)) { + if (!BitsquareApp.DEV_MODE && preferences.showAgain(key)) { new Popup() .headLine("Confirm that you have received the payment") .confirmation("Have you received the " + CurrencyUtil.getNameByCode(model.dataModel.getCurrencyCode()) +