Savings wallet (WIP)

This commit is contained in:
Manfred Karrer 2016-04-02 00:32:03 +02:00
parent f07aa9ba6a
commit 091eae4965
19 changed files with 288 additions and 169 deletions

View file

@ -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);
}
}

View file

@ -333,6 +333,12 @@ public class WalletService {
.findAny();
}
public List<AddressEntry> 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<TransactionOutput> 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.");

View file

@ -67,18 +67,18 @@ public final class PaymentMethod implements Persistable, Comparable {
public static PaymentMethod BLOCK_CHAINS;
public static final List<PaymentMethod> 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"))
));

View file

@ -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<AddressEntry> getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) {
Set<String> reservedTrades = getNotCompletedTradableItems(openOfferManager, tradeManager).stream()
.map(tradable -> tradable.getOffer().getId())
.collect(Collectors.toSet());
List<AddressEntry> 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<Tradable> 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());
}
}

View file

@ -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<AddressEntry> getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) {
Set<String> 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<Tradable> 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<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(id);
Optional<Trade> tradeOptional = tradeManager.getTradeById(id);
Optional<Tradable> closedTradableOptional = closedTradableManager.getTradableById(id);
Optional<Trade> 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;
}
}

View file

@ -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);
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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<Boolean> 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<Coin> 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<Coin> 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<Coin> 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<? extends Dispute> addedList, @Nullable List<? extends Dispute> removedList) {
if (removedList != null) {
removedList.stream().forEach(dispute -> {

View file

@ -83,19 +83,6 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
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

View file

@ -132,7 +132,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
.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<VBox, Void> {
protected void activate() {
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
walletService.addBalanceListener(balanceListener);

View file

@ -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";
}
}

View file

@ -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<VBox, Void> {
private final ObservableList<ReservedListItem> observableList = FXCollections.observableArrayList();
private final SortedList<ReservedListItem> sortedList = new SortedList<>(observableList);
private BalanceListener balanceListener;
private ListChangeListener<OpenOffer> openOfferListChangeListener;
private ListChangeListener<Trade> tradeListChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -86,7 +89,6 @@ public class ReservedView extends ActivatableView<VBox, Void> {
this.tradeDetailsWindow = tradeDetailsWindow;
}
@Override
public void initialize() {
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -100,7 +102,12 @@ public class ReservedView extends ActivatableView<VBox, Void> {
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<VBox, Void> {
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<VBox, Void> {
@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<VBox, Void> {
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()));
}

View file

@ -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() {

View file

@ -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<VBox, Void> {
@ -197,7 +197,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
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<VBox, Void> {
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()));
}

View file

@ -318,9 +318,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
}
}
void useSavingsWalletForFunding() {
useSavingsWallet = true;
void fundFromSavingsWallet() {
this.useSavingsWallet = true;
updateBalance();
if (!isWalletFunded.get())
this.useSavingsWallet = false;
}

View file

@ -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<AnchorPane, CreateO
model.totalToPay.get() + " to your local Bitsquare trading wallet.\n" +
"The amount is the sum of " + tradeAmountText + "the security deposit, the trading fee and " +
"the bitcoin mining fee.\n\n" +
"Please send from your external Bitcoin wallet the exact amount to the address: " +
"You can choose between 2 options:\n" +
"Either you transfer from your Bitsquare wallet the funds or if you prefer better privacy by " +
"separating all trade transactions you can send from your external Bitcoin wallet the exact amount to the address: " +
model.getAddressAsString() + "\n" +
"(you can copy the address in the screen below after closing that popup)\n\n" +
"Make sure you use a sufficiently high mining fee of at least " +
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) +
" to avoid problems that your transaction does not get confirmed in the blockchain.\n" +
"Transactions with a lower fee will not be accepted.\n\n" +
"You can see the status of your incoming payment and all the details in the screen below.")
.dontShowAgainId(key, preferences)
.show();
@ -804,7 +801,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
fundFromSavingsWalletButton = new Button("Transfer funds from Bitsquare wallet");
fundFromSavingsWalletButton.setDefaultButton(true);
fundFromSavingsWalletButton.setDefaultButton(false);
fundFromSavingsWalletButton.setOnAction(e -> 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");

View file

@ -375,8 +375,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
updateSpinnerInfo();
}
boolean useSavingsWalletForFunding() {
dataModel.useSavingsWalletForFunding();
boolean fundFromSavingsWallet() {
dataModel.fundFromSavingsWallet();
if (dataModel.isWalletFunded.get()) {
updateButtonDisableState();
return true;

View file

@ -252,9 +252,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
this.paymentAccount = paymentAccount;
}
void useSavingsWalletForFunding() {
void fundFromSavingsWallet() {
useSavingsWallet = true;
updateBalance();
if (!isWalletFunded.get())
this.useSavingsWallet = false;
}

View file

@ -20,7 +20,6 @@ package io.bitsquare.gui.main.offer.takeoffer;
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;
@ -309,12 +308,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
model.totalToPay.get() + " to your local Bitsquare trading wallet.\n" +
"The amount is the sum of " + tradeAmountText + "the security deposit, the trading fee and " +
"the bitcoin mining fee.\n\n" +
"Please send from your external Bitcoin wallet the exact amount to the address: " +
model.dataModel.getAddressEntry().getAddressString() + "\n(you can copy the address in the screen below after closing that popup)\n\n" +
"Make sure you use a sufficiently high mining fee of at least " +
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) +
" to avoid problems that your transaction does not get confirmed in the blockchain.\n" +
"Transactions with a lower fee will not be accepted.\n\n" +
"You can choose between 2 options:\n" +
"Either you transfer from your Bitsquare wallet the funds or if you prefer better privacy by " +
"separating all trade transactions you can send from your external Bitcoin wallet the exact amount to the address: " +
model.dataModel.getAddressEntry().getAddressString() + "\n" +
"(you can copy the address in the screen below after closing that popup)\n\n" +
"You can see the status of your incoming payment and all the details in the screen below.")
.dontShowAgainId(key, preferences)
.show();
@ -726,7 +724,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
fundFromSavingsWalletButton = new Button("Transfer funds from Bitsquare wallet");
fundFromSavingsWalletButton.setDefaultButton(true);
fundFromSavingsWalletButton.setDefaultButton(false);
fundFromSavingsWalletButton.setOnAction(e -> 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");

View file

@ -207,8 +207,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
updateSpinnerInfo();
}
boolean useSavingsWalletForFunding() {
dataModel.useSavingsWalletForFunding();
boolean fundFromSavingsWallet() {
dataModel.fundFromSavingsWallet();
if (dataModel.isWalletFunded.get()) {
updateButtonDisableState();
return true;