diff --git a/core/src/main/java/io/bitsquare/btc/FeePolicy.java b/core/src/main/java/io/bitsquare/btc/FeePolicy.java index a1261dda00..2f1f00ab30 100644 --- a/core/src/main/java/io/bitsquare/btc/FeePolicy.java +++ b/core/src/main/java/io/bitsquare/btc/FeePolicy.java @@ -36,12 +36,12 @@ public class FeePolicy { // disputed payout tx: 408 bytes // We set a fixed fee to make the needed amounts in the trade predictable. - // We use 0.0003 BTC (0.12 EUR @ 400 EUR/BTC) which is for our tx sizes about 75-150 satoshi/byte + // We use 0.0002 BTC (0.08 EUR @ 400 EUR/BTC) which is for our tx sizes about 50-90 satoshi/byte // We cannot make that user defined as it need to be the same for both users, so we can only change that in // software updates // TODO before Beta we should get a good future proof guess as a change causes incompatible versions public static Coin getFixedTxFeeForTrades() { - return Coin.valueOf(30_000); + return Coin.valueOf(20_000); } // For non trade transactions (withdrawal) we use the default fee calculation @@ -50,7 +50,7 @@ public class FeePolicy { // The BitcoinJ fee calculation use kb so a tx size < 1kb will still pay the fee for a kb tx. // Our payout tx has about 370 bytes so we get a fee/kb value of about 90 satoshi/byte making it high priority // Other payout transactions (E.g. arbitrators many collected transactions) will go with 30 satoshi/byte if > 1kb - private static Coin FEE_PER_KB = Coin.valueOf(30_000); // 0.0003 BTC about 0.12 EUR @ 400 EUR/BTC + private static Coin FEE_PER_KB = Coin.valueOf(20_000); // 0.0002 BTC about 0.08 EUR @ 400 EUR/BTC public static void setFeePerKb(Coin feePerKb) { FEE_PER_KB = feePerKb; @@ -84,8 +84,8 @@ public class FeePolicy { // TODO will be increased once we get higher limits - // 0.01 BTC; about 0.4 EUR @ 400 EUR/BTC + // 0.02 BTC; about 8 EUR @ 400 EUR/BTC public static Coin getSecurityDeposit() { - return Coin.valueOf(1_000_000); + return Coin.valueOf(2_000_000); } } diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index be7d4247e7..3a7d77b92b 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -333,6 +333,12 @@ public class WalletService { .findAny(); } + public List getTradeAddressEntryList() { + return getAddressEntryList().stream() + .filter(e -> e.getContext().equals(AddressEntry.Context.TRADE)) + .collect(Collectors.toList()); + } + /////////////////////////////////////////////////////////////////////////////////////////// // SavingsAddressEntry @@ -493,6 +499,10 @@ public class WalletService { return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO; } + public Coin getBalanceForAddressEntryWithTradeId(String tradeId) { + return getBalanceForAddress(getTradeAddressEntry(tradeId).getAddress()); + } + private Coin getBalance(List transactionOutputs, Address address) { Coin balance = Coin.ZERO; for (TransactionOutput transactionOutput : transactionOutputs) { @@ -539,7 +549,8 @@ public class WalletService { Coin fee; try { wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey)); - fee = Coin.ZERO; + // We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases... + fee = FeePolicy.getFixedTxFeeForTrades(); } catch (InsufficientMoneyException e) { log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " + "We subtract that fee from the receivers amount to make the transaction possible."); @@ -556,7 +567,8 @@ public class WalletService { Coin fee; try { wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey)); - fee = Coin.ZERO; + // We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases... + fee = FeePolicy.getFixedTxFeeForTrades(); } catch (InsufficientMoneyException e) { log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " + "We subtract that fee from the receivers amount to make the transaction possible."); diff --git a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java index 1ff2431fd7..8f1da41c64 100644 --- a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java +++ b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java @@ -67,18 +67,18 @@ public final class PaymentMethod implements Persistable, Comparable { public static PaymentMethod BLOCK_CHAINS; public static final List ALL_VALUES = new ArrayList<>(Arrays.asList( - OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("0.4")), // tx instant so min. wait time - PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("0.4")), - SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("0.3")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays - NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("0.3")), - SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("0.3")), - SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("0.3")), - SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("0.4")), - ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("0.4")), + OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("1")), // tx instant so min. wait time + SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("0.5")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays + NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("0.5")), + SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("0.5")), + SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("0.5")), + PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("0.75")), + SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("0.75")), + ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("0.75")), /* FED_WIRE = new PaymentMethod(FED_WIRE_ID, 0, DAY, Coin.parseCoin("0.1")),*/ /* TRANSFER_WISE = new PaymentMethod(TRANSFER_WISE_ID, 0, DAY, Coin.parseCoin("0.1")),*/ /* US_POSTAL_MONEY_ORDER = new PaymentMethod(US_POSTAL_MONEY_ORDER_ID, 0, DAY, Coin.parseCoin("0.1")),*/ - BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("0.5")) + BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("1")) )); diff --git a/core/src/main/java/io/bitsquare/trade/TradableCollections.java b/core/src/main/java/io/bitsquare/trade/TradableCollections.java deleted file mode 100644 index fa4704993f..0000000000 --- a/core/src/main/java/io/bitsquare/trade/TradableCollections.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.bitsquare.trade; - -import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.WalletService; -import io.bitsquare.trade.offer.OpenOfferManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class TradableCollections { - private static final Logger log = LoggerFactory.getLogger(TradableCollections.class); - - public static List getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) { - Set reservedTrades = getNotCompletedTradableItems(openOfferManager, tradeManager).stream() - .map(tradable -> tradable.getOffer().getId()) - .collect(Collectors.toSet()); - - List list = new ArrayList<>(); - list.addAll(walletService.getAddressEntryList().stream() - .filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive()) - .filter(e -> !reservedTrades.contains(e.getOfferId())) - .collect(Collectors.toList())); - return list; - } - - public static Set getNotCompletedTradableItems(OpenOfferManager openOfferManager, TradeManager tradeManager) { - return Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) - .filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID) - .collect(Collectors.toSet()); - } -} diff --git a/core/src/main/java/io/bitsquare/trade/TradableHelper.java b/core/src/main/java/io/bitsquare/trade/TradableHelper.java new file mode 100644 index 0000000000..1ba75c4bcb --- /dev/null +++ b/core/src/main/java/io/bitsquare/trade/TradableHelper.java @@ -0,0 +1,110 @@ +package io.bitsquare.trade; + +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.WalletService; +import io.bitsquare.trade.closed.ClosedTradableManager; +import io.bitsquare.trade.failed.FailedTradesManager; +import io.bitsquare.trade.offer.Offer; +import io.bitsquare.trade.offer.OpenOffer; +import io.bitsquare.trade.offer.OpenOfferManager; +import org.bitcoinj.core.Coin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TradableHelper { + private static final Logger log = LoggerFactory.getLogger(TradableHelper.class); + + public static List getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) { + Set reservedTradeIds = getNotCompletedTradableItems(openOfferManager, tradeManager).stream() + .map(tradable -> tradable.getOffer().getId()) + .collect(Collectors.toSet()); + + return walletService.getAddressEntryList().stream() + .filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive()) + .filter(e -> !reservedTradeIds.contains(e.getOfferId())) + .collect(Collectors.toList()); + } + + public static Set getNotCompletedTradableItems(OpenOfferManager openOfferManager, TradeManager tradeManager) { + return Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) + .filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID) + .collect(Collectors.toSet()); + } + + public static Coin getBalanceInOpenOffer(OpenOffer openOffer) { + Offer offer = openOffer.getOffer(); + Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb()); + // For the seller we add the trade amount + if (offer.getDirection() == Offer.Direction.SELL) + balance = balance.add(offer.getAmount()); + + return balance; + } + + public static Coin getBalanceInTrade(Trade trade, WalletService walletService) { + AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId()); + Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb()); + // For the seller we add the trade amount + if (trade.getContract() != null && + trade.getTradeAmount() != null && + trade.getContract().getSellerPayoutAddressString().equals(addressEntry.getAddressString())) + balance = balance.add(trade.getTradeAmount()); + return balance; + } + + public static Coin getAvailableBalance(AddressEntry addressEntry, WalletService walletService, + OpenOfferManager openOfferManager, TradeManager tradeManager, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager) { + Coin balance; + Coin totalBalance = walletService.getBalanceForAddress(addressEntry.getAddress()); + String id = addressEntry.getOfferId(); + Optional openOfferOptional = openOfferManager.getOpenOfferById(id); + Optional tradeOptional = tradeManager.getTradeById(id); + Optional closedTradableOptional = closedTradableManager.getTradableById(id); + Optional failedTradesOptional = failedTradesManager.getTradeById(id); + + if (openOfferOptional.isPresent()) { + balance = totalBalance.subtract(TradableHelper.getBalanceInOpenOffer(openOfferOptional.get())); + } else if (tradeOptional.isPresent()) { + Trade trade = tradeOptional.get(); + if (trade.getState().getPhase() != Trade.Phase.PAYOUT_PAID) + balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(trade, walletService)); + else + balance = totalBalance; + } else if (closedTradableOptional.isPresent()) { + Tradable tradable = closedTradableOptional.get(); + Coin balanceInTrade = Coin.ZERO; + if (tradable instanceof OpenOffer) + balanceInTrade = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable); + else if (tradable instanceof Trade) + balanceInTrade = TradableHelper.getBalanceInTrade((Trade) tradable, walletService); + balance = totalBalance.subtract(balanceInTrade); + } else if (failedTradesOptional.isPresent()) { + balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(failedTradesOptional.get(), walletService)); + } else { + balance = totalBalance; + } + return balance; + } + + public static Coin getReservedBalance(Tradable tradable, WalletService walletService) { + AddressEntry addressEntry = walletService.getTradeAddressEntry(tradable.getId()); + Coin balance = walletService.getBalanceForAddress(addressEntry.getAddress()); + if (tradable instanceof Trade) { + Trade trade = (Trade) tradable; + if (trade.getState().getPhase().ordinal() < Trade.Phase.PAYOUT_PAID.ordinal()) + balance = TradableHelper.getBalanceInTrade(trade, walletService); + } else if (tradable instanceof OpenOffer) { + balance = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable); + } + return balance; + } +} diff --git a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java index b243257c43..d59db8a2b0 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java @@ -147,7 +147,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe public void closeAllOpenOffers(@Nullable Runnable completeHandler) { openOffers.forEach(openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer())); if (completeHandler != null) - UserThread.runAfter(completeHandler::run, openOffers.size() * 200 + 300, TimeUnit.MILLISECONDS); + UserThread.runAfter(completeHandler::run, openOffers.size() * 100 + 200, TimeUnit.MILLISECONDS); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index 7fb9020ace..93b3a38511 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -55,9 +55,11 @@ import io.bitsquare.p2p.network.Connection; import io.bitsquare.p2p.network.ConnectionListener; import io.bitsquare.p2p.peers.keepalive.messages.Ping; import io.bitsquare.payment.OKPayAccount; -import io.bitsquare.trade.TradableCollections; +import io.bitsquare.trade.TradableHelper; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; +import io.bitsquare.trade.closed.ClosedTradableManager; +import io.bitsquare.trade.failed.FailedTradesManager; import io.bitsquare.trade.offer.OpenOffer; import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.user.Preferences; @@ -80,6 +82,7 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; +import java.util.stream.Stream; public class MainViewModel implements ViewModel { private static final Logger log = LoggerFactory.getLogger(MainViewModel.class); @@ -137,6 +140,8 @@ public class MainViewModel implements ViewModel { private MonadicBinding allServicesDone, tradesAndUIReady; private final PriceFeed priceFeed; + private final ClosedTradableManager closedTradableManager; + private final FailedTradesManager failedTradesManager; private final User user; private int numBtcPeers = 0; private Timer checkNumberOfBtcPeersTimer; @@ -153,11 +158,14 @@ public class MainViewModel implements ViewModel { public MainViewModel(WalletService walletService, TradeWalletService tradeWalletService, PriceFeed priceFeed, ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager, - OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences, + OpenOfferManager openOfferManager, ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, DisputeManager disputeManager, Preferences preferences, User user, AlertManager alertManager, WalletPasswordWindow walletPasswordWindow, NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock, KeyRing keyRing, Navigation navigation, BSFormatter formatter) { this.priceFeed = priceFeed; + this.closedTradableManager = closedTradableManager; + this.failedTradesManager = failedTradesManager; this.user = user; this.walletService = walletService; this.tradeWalletService = tradeWalletService; @@ -670,7 +678,7 @@ public class MainViewModel implements ViewModel { } private void swapPendingTradeAddressEntriesToSavingsWallet() { - TradableCollections.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService).stream() + TradableHelper.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService).stream() .filter(addressEntry -> addressEntry.getOfferId() != null) .forEach(addressEntry -> walletService.swapTradeToSavings(addressEntry.getOfferId())); } @@ -691,12 +699,36 @@ public class MainViewModel implements ViewModel { updateLockedBalance(); } + private void updateAvailableBalance() { + Optional totalAvailableOptional = Stream.concat(walletService.getSavingsAddressEntryList().stream(), walletService.getTradeAddressEntryList().stream()) + .filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isPositive()) + .map(addressEntry -> TradableHelper.getAvailableBalance(addressEntry, + walletService, + openOfferManager, + tradeManager, + closedTradableManager, + failedTradesManager)) + .filter(balance -> balance.isPositive()) + .reduce(Coin::add); + + + /*Optional totalAvailableOptional = TradableHelper.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService) + .stream() + .map(e -> walletService.getBalanceForAddress(e.getAddress())) + .reduce(Coin::add);*/ + if (totalAvailableOptional.isPresent()) + availableBalance.set(formatter.formatCoinWithCode(totalAvailableOptional.get())); + else + availableBalance.set(formatter.formatCoinWithCode(Coin.ZERO)); + } + private void updateReservedBalance() { - Coin sum = Coin.valueOf(TradableCollections.getNotCompletedTradableItems(openOfferManager, tradeManager).stream() - .map(tradable -> walletService.getTradeAddressEntry(tradable.getId())) - .map(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress())) + Coin sum = Coin.valueOf(TradableHelper.getNotCompletedTradableItems(openOfferManager, tradeManager).stream() + .filter(tradable -> tradable instanceof OpenOffer) + .map(tradable -> TradableHelper.getReservedBalance(tradable, walletService)) .mapToLong(Coin::getValue) .sum()); + reservedBalance.set(formatter.formatCoinWithCode(sum)); } @@ -725,17 +757,6 @@ public class MainViewModel implements ViewModel { lockedBalance.set(formatter.formatCoinWithCode(sum)); } - private void updateAvailableBalance() { - Optional totalAvailableOptional = TradableCollections.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService) - .stream() - .map(e -> walletService.getBalanceForAddress(e.getAddress())).reduce(Coin::add); - if (totalAvailableOptional.isPresent()) - availableBalance.set(formatter.formatCoinWithCode(totalAvailableOptional.get())); - else - availableBalance.set(formatter.formatCoinWithCode(Coin.ZERO)); - } - - private void onDisputesChangeListener(List addedList, @Nullable List removedList) { if (removedList != null) { removedList.stream().forEach(dispute -> { diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java index cd231ad389..9182b11604 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java @@ -83,19 +83,6 @@ public class FundsView extends ActivatableViewAndModel { navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); else if (root.getSelectionModel().getSelectedItem() == transactionsTab) navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); - - String key = "tradeWalletInfoAtFunds"; - /* if (!BitsquareApp.DEV_MODE) - new Popup().backgroundInfo("Bitsquare does not use a single application wallet, but dedicated wallets for every trade.\n\n" + - "Funding of the wallet will be done when needed, for instance when you create or take an offer.\n" + - "Withdrawing funds can be done after a trade is completed.\n\n" + - "Dedicated wallets help protect user privacy and prevent leaking information of previous trades to other " + - "traders.") - .actionButtonText("Visit FAQ web page") - .onAction(() -> Utilities.openWebPage("https://bitsquare.io/faq")) - .closeButtonText("I understand") - .dontShowAgainId(key, preferences) - .show();*/ } @Override 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 b53089fa39..310f51603f 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 @@ -132,7 +132,7 @@ public class DepositView extends ActivatableView { .compareTo(o2.getProgressIndicator().getProgress())); usageColumn.setComparator((a, b) -> (a.getNumTxOutputs() < b.getNumTxOutputs()) ? -1 : ((a.getNumTxOutputs() == b.getNumTxOutputs()) ? 0 : 1)); tableView.getSortOrder().add(usageColumn); - + tableView.setItems(sortedList); titledGroupBg = addTitledGroupBg(gridPane, gridRow, 3, "Fund your wallet"); @@ -209,7 +209,7 @@ public class DepositView extends ActivatableView { protected void activate() { tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); - tableView.setItems(sortedList); + updateList(); walletService.addBalanceListener(balanceListener); diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java index ced9a55be2..3b6077b23e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java @@ -18,11 +18,11 @@ package io.bitsquare.gui.main.funds.reserved; import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.trade.Tradable; +import io.bitsquare.trade.TradableHelper; import io.bitsquare.trade.Trade; import io.bitsquare.trade.offer.OpenOffer; import javafx.beans.property.SimpleStringProperty; @@ -62,60 +62,50 @@ public class ReservedListItem { balanceListener = new BalanceListener(getAddress()) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { - updateBalance(balance); + updateBalance(); } }; walletService.addBalanceListener(balanceListener); - updateBalance(walletService.getBalanceForAddress(getAddress())); + updateBalance(); } public void cleanup() { walletService.removeBalanceListener(balanceListener); } - private void updateBalance(Coin balance) { - this.balance = balance; - if (balance != null) { - balanceLabel.setText(formatter.formatCoin(balance)); + private void updateBalance() { + balance = TradableHelper.getReservedBalance(tradable, walletService); + if (balance != null) + balanceLabel.setText(formatter.formatCoin(this.balance)); - if (tradable instanceof Trade) { - Trade trade = (Trade) tradable; - Trade.Phase phase = trade.getState().getPhase(); - switch (phase) { - case PREPARATION: - case TAKER_FEE_PAID: - fundsInfo = "Reserved in local wallet"; - break; - case DEPOSIT_REQUESTED: - case DEPOSIT_PAID: - case FIAT_SENT: - case FIAT_RECEIVED: - fundsInfo = "Locked in MultiSig"; - // We ignore the tx fee as it will be paid by both (once deposit, once payout) - Coin balanceInDeposit = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb()); - // For the seller we add the trade amount - if (trade.getContract() != null && - trade.getTradeAmount() != null && - trade.getContract().getSellerPayoutAddressString().equals(addressString)) - balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount()); - - balanceLabel.setText(formatter.formatCoin(balanceInDeposit)); - break; - case PAYOUT_PAID: - fundsInfo = "Received in local wallet"; - break; - case WITHDRAWN: - log.error("Invalid state at updateBalance (WITHDRAWN)"); - break; - case DISPUTE: - log.error("Invalid state at updateBalance (DISPUTE)"); - break; - default: - log.warn("Not supported tradePhase: " + phase); - } - } else if (tradable instanceof OpenOffer) { - fundsInfo = "Reserved in local wallet"; + if (tradable instanceof Trade) { + Trade trade = (Trade) tradable; + Trade.Phase phase = trade.getState().getPhase(); + switch (phase) { + case PREPARATION: + case TAKER_FEE_PAID: + fundsInfo = "Reserved in local wallet"; + break; + case DEPOSIT_REQUESTED: + case DEPOSIT_PAID: + case FIAT_SENT: + case FIAT_RECEIVED: + fundsInfo = "Locked in MultiSig"; + break; + case PAYOUT_PAID: + fundsInfo = "Received in local wallet"; + break; + case WITHDRAWN: + log.error("Invalid state at updateBalance (WITHDRAWN)"); + break; + case DISPUTE: + log.error("Invalid state at updateBalance (DISPUTE)"); + break; + default: + log.warn("Not supported tradePhase: " + phase); } + } else if (tradable instanceof OpenOffer) { + fundsInfo = "Reserved in local wallet"; } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java index de596d6ab0..4faea3f8fb 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java @@ -30,7 +30,7 @@ import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.trade.Tradable; -import io.bitsquare.trade.TradableCollections; +import io.bitsquare.trade.TradableHelper; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.offer.OpenOffer; @@ -38,6 +38,7 @@ import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.user.Preferences; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; @@ -68,6 +69,8 @@ public class ReservedView extends ActivatableView { private final ObservableList observableList = FXCollections.observableArrayList(); private final SortedList sortedList = new SortedList<>(observableList); private BalanceListener balanceListener; + private ListChangeListener openOfferListChangeListener; + private ListChangeListener tradeListChangeListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -86,7 +89,6 @@ public class ReservedView extends ActivatableView { this.tradeDetailsWindow = tradeDetailsWindow; } - @Override public void initialize() { tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); @@ -100,7 +102,12 @@ public class ReservedView extends ActivatableView { addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); detailsColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId())); balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance())); - dateColumn.setComparator((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate())); + dateColumn.setComparator((o1, o2) -> { + if (getTradable(o1).isPresent() && getTradable(o2).isPresent()) + return getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()); + else + return 0; + }); tableView.getSortOrder().add(dateColumn); dateColumn.setSortType(TableColumn.SortType.DESCENDING); @@ -110,10 +117,14 @@ public class ReservedView extends ActivatableView { updateList(); } }; + openOfferListChangeListener = c -> updateList(); + tradeListChangeListener = c -> updateList(); } @Override protected void activate() { + openOfferManager.getOpenOffers().addListener(openOfferListChangeListener); + tradeManager.getTrades().addListener(tradeListChangeListener); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); tableView.setItems(sortedList); updateList(); @@ -123,6 +134,8 @@ public class ReservedView extends ActivatableView { @Override protected void deactivate() { + openOfferManager.getOpenOffers().removeListener(openOfferListChangeListener); + tradeManager.getTrades().removeListener(tradeListChangeListener); sortedList.comparatorProperty().unbind(); observableList.forEach(ReservedListItem::cleanup); walletService.removeBalanceListener(balanceListener); @@ -135,9 +148,7 @@ public class ReservedView extends ActivatableView { private void updateList() { observableList.forEach(ReservedListItem::cleanup); - - - observableList.setAll(TradableCollections.getNotCompletedTradableItems(openOfferManager, tradeManager).stream() + observableList.setAll(TradableHelper.getNotCompletedTradableItems(openOfferManager, tradeManager).stream() .map(tradable -> new ReservedListItem(tradable, walletService.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter)) .collect(Collectors.toList())); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java index 752a5bf4aa..5839130fb3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java @@ -21,6 +21,11 @@ import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.gui.util.BSFormatter; +import io.bitsquare.trade.TradableHelper; +import io.bitsquare.trade.TradeManager; +import io.bitsquare.trade.closed.ClosedTradableManager; +import io.bitsquare.trade.failed.FailedTradesManager; +import io.bitsquare.trade.offer.OpenOfferManager; import javafx.scene.control.Label; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; @@ -31,13 +36,25 @@ public class WithdrawalListItem { private final Label balanceLabel; private final AddressEntry addressEntry; private final WalletService walletService; + private final OpenOfferManager openOfferManager; + private final TradeManager tradeManager; + private final ClosedTradableManager closedTradableManager; + private final FailedTradesManager failedTradesManager; private final BSFormatter formatter; private Coin balance; private final String addressString; - public WithdrawalListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) { + public WithdrawalListItem(AddressEntry addressEntry, WalletService walletService, + OpenOfferManager openOfferManager, TradeManager tradeManager, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, + BSFormatter formatter) { this.addressEntry = addressEntry; this.walletService = walletService; + this.openOfferManager = openOfferManager; + this.tradeManager = tradeManager; + this.closedTradableManager = closedTradableManager; + this.failedTradesManager = failedTradesManager; this.formatter = formatter; addressString = addressEntry.getAddressString(); @@ -46,23 +63,28 @@ public class WithdrawalListItem { balanceListener = new BalanceListener(getAddress()) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { - updateBalance(balance); + updateBalance(); } }; walletService.addBalanceListener(balanceListener); - updateBalance(walletService.getBalanceForAddress(getAddress())); + updateBalance(); } public void cleanup() { walletService.removeBalanceListener(balanceListener); } - private void updateBalance(Coin balance) { - this.balance = balance; - if (balance != null) { - balanceLabel.setText(formatter.formatCoin(balance)); - } + private void updateBalance() { + balance = TradableHelper.getAvailableBalance(addressEntry, + walletService, + openOfferManager, + tradeManager, + closedTradableManager, + failedTradesManager); + + if (balance != null) + balanceLabel.setText(formatter.formatCoin(this.balance)); } public final String getLabel() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java index 157bd7741d..cd9d45ea11 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java @@ -34,7 +34,6 @@ import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.validation.BtcAddressValidator; import io.bitsquare.trade.Tradable; -import io.bitsquare.trade.TradableCollections; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.closed.ClosedTradableManager; @@ -61,6 +60,7 @@ import org.spongycastle.crypto.params.KeyParameter; import javax.inject.Inject; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; @FxmlView public class WithdrawalView extends ActivatableView { @@ -197,7 +197,7 @@ public class WithdrawalView extends ActivatableView { withdrawToTextField.getText(), senderAmount, null); Coin receiverAmount = senderAmount.subtract(requiredFee); if (BitsquareApp.DEV_MODE) { - doWithdraw(receiverAmount, callback); + doWithdraw(senderAmount, callback); } else { new Popup().headLine("Confirm withdrawal request") .confirmation("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" + @@ -283,8 +283,11 @@ public class WithdrawalView extends ActivatableView { private void updateList() { observableList.forEach(WithdrawalListItem::cleanup); - observableList.setAll(TradableCollections.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService).stream() - .map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, formatter)) + observableList.setAll(Stream.concat(walletService.getSavingsAddressEntryList().stream(), walletService.getTradeAddressEntryList().stream()) + .filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isPositive()) + .map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, openOfferManager, tradeManager, + closedTradableManager, failedTradesManager, formatter)) + .filter(item -> item.getBalance().isPositive()) .collect(Collectors.toList())); } 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 f683d0ea13..9f8a955e93 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 @@ -318,9 +318,11 @@ class CreateOfferDataModel extends ActivatableDataModel { } } - void useSavingsWalletForFunding() { - useSavingsWallet = true; + void fundFromSavingsWallet() { + this.useSavingsWallet = true; updateBalance(); + if (!isWalletFunded.get()) + this.useSavingsWallet = false; } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index 24cb88048f..1989cb4bc7 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -20,7 +20,6 @@ package io.bitsquare.gui.main.offer.createoffer; import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.app.BitsquareApp; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.common.UserThread; import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple3; @@ -296,13 +295,11 @@ public class CreateOfferView extends ActivatableViewAndModel model.useSavingsWalletForFunding()); + fundFromSavingsWalletButton.setOnAction(e -> model.fundFromSavingsWallet()); Label label = new Label("OR"); label.setPadding(new Insets(5, 0, 0, 0)); fundFromExternalWalletButton = new Button("Pay in funds from external wallet"); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java index 9384df146b..714c617fa3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java @@ -375,8 +375,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel model.useSavingsWalletForFunding()); + fundFromSavingsWalletButton.setOnAction(e -> model.fundFromSavingsWallet()); Label label = new Label("OR"); label.setPadding(new Insets(5, 0, 0, 0)); fundFromExternalWalletButton = new Button("Pay in funds from external wallet"); 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 d1b9708e24..93b1ba2fcc 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 @@ -207,8 +207,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel im updateSpinnerInfo(); } - boolean useSavingsWalletForFunding() { - dataModel.useSavingsWalletForFunding(); + boolean fundFromSavingsWallet() { + dataModel.fundFromSavingsWallet(); if (dataModel.isWalletFunded.get()) { updateButtonDisableState(); return true;