From d2e8c6afd72c6743ab7695b2e213821880a79961 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 10 Feb 2016 01:59:46 +0100 Subject: [PATCH] Add market price feed --- .../java/io/bitsquare/btc/BitcoinModule.java | 2 + .../btc/blockchain/BlockchainService.java | 52 ++---- .../btc/blockchain/GetFeeRequest.java | 13 +- .../providers/BlockTrailProvider.java | 6 +- .../providers/BlockrIOProvider.java | 7 +- ...chainApiProvider.java => FeeProvider.java} | 5 +- .../providers/TradeBlockProvider.java | 6 +- .../btc/pricefeed/GetPriceRequest.java | 67 +++++++ .../bitsquare/btc/pricefeed/MarketPrice.java | 71 +++++++ .../btc/pricefeed/MarketPriceFeed.java | 176 ++++++++++++++++++ .../btc/pricefeed/PriceRequestException.java | 7 + .../BitcoinAveragePriceProvider.java | 63 +++++++ .../pricefeed/providers/PriceProvider.java | 14 ++ .../io/bitsquare/locale/CurrencyUtil.java | 12 ++ .../java/io/bitsquare/trade/offer/Offer.java | 2 +- .../btc/blockchain/BlockchainServiceTest.java | 6 +- .../btc/pricefeed/MarketPriceFeedTest.java | 29 +++ .../java/io/bitsquare/app/BitsquareApp.java | 4 +- .../java/io/bitsquare/gui/main/MainView.java | 40 +++- .../io/bitsquare/gui/main/MainViewModel.java | 20 ++ .../charts/MarketsChartsViewModel.java | 8 +- .../gui/main/offer/BuyOfferView.java | 5 +- .../bitsquare/gui/main/offer/OfferView.java | 34 +++- .../gui/main/offer/SellOfferView.java | 5 +- .../createoffer/CreateOfferDataModel.java | 48 +++-- .../offer/createoffer/CreateOfferView.java | 3 + .../createoffer/CreateOfferViewModel.java | 2 +- .../main/offer/offerbook/OfferBookView.java | 4 + .../offer/offerbook/OfferBookViewModel.java | 14 +- .../offer/takeoffer/TakeOfferDataModel.java | 9 +- .../main/offer/takeoffer/TakeOfferView.java | 5 + .../settings/preferences/PreferencesView.java | 8 +- .../io/bitsquare/gui/util/BSFormatter.java | 16 ++ gui/src/main/resources/logback.xml | 2 +- .../java/io/bitsquare/http}/HttpClient.java | 2 +- .../io/bitsquare/http}/HttpException.java | 2 +- .../io/bitsquare/p2p/network/Connection.java | 9 +- 37 files changed, 676 insertions(+), 102 deletions(-) rename core/src/main/java/io/bitsquare/btc/blockchain/providers/{BlockchainApiProvider.java => FeeProvider.java} (56%) create mode 100644 core/src/main/java/io/bitsquare/btc/pricefeed/GetPriceRequest.java create mode 100644 core/src/main/java/io/bitsquare/btc/pricefeed/MarketPrice.java create mode 100644 core/src/main/java/io/bitsquare/btc/pricefeed/MarketPriceFeed.java create mode 100644 core/src/main/java/io/bitsquare/btc/pricefeed/PriceRequestException.java create mode 100644 core/src/main/java/io/bitsquare/btc/pricefeed/providers/BitcoinAveragePriceProvider.java create mode 100644 core/src/main/java/io/bitsquare/btc/pricefeed/providers/PriceProvider.java create mode 100644 core/src/test/java/io/bitsquare/btc/pricefeed/MarketPriceFeedTest.java rename {core/src/main/java/io/bitsquare/btc/blockchain => network/src/main/java/io/bitsquare/http}/HttpClient.java (97%) rename {core/src/main/java/io/bitsquare/btc/blockchain => network/src/main/java/io/bitsquare/http}/HttpException.java (76%) diff --git a/core/src/main/java/io/bitsquare/btc/BitcoinModule.java b/core/src/main/java/io/bitsquare/btc/BitcoinModule.java index 1347f4a071..1b1d3e685f 100644 --- a/core/src/main/java/io/bitsquare/btc/BitcoinModule.java +++ b/core/src/main/java/io/bitsquare/btc/BitcoinModule.java @@ -20,6 +20,7 @@ package io.bitsquare.btc; import com.google.inject.Singleton; import io.bitsquare.app.AppModule; import io.bitsquare.btc.blockchain.BlockchainService; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; @@ -51,6 +52,7 @@ public class BitcoinModule extends AppModule { bind(TradeWalletService.class).in(Singleton.class); bind(WalletService.class).in(Singleton.class); bind(BlockchainService.class).in(Singleton.class); + bind(MarketPriceFeed.class).in(Singleton.class); } } diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java b/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java index 67e8320c20..920cbea86f 100644 --- a/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java @@ -5,13 +5,10 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.SettableFuture; import com.google.inject.Inject; import io.bitsquare.app.Log; -import io.bitsquare.btc.BitcoinNetwork; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.blockchain.providers.BlockTrailProvider; -import io.bitsquare.btc.blockchain.providers.BlockchainApiProvider; import io.bitsquare.btc.blockchain.providers.BlockrIOProvider; +import io.bitsquare.btc.blockchain.providers.FeeProvider; import io.bitsquare.btc.blockchain.providers.TradeBlockProvider; -import io.bitsquare.user.Preferences; import org.bitcoinj.core.Coin; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -23,48 +20,35 @@ import java.util.Arrays; public class BlockchainService { private static final Logger log = LoggerFactory.getLogger(BlockchainService.class); - private final ArrayList providers; - private final boolean isMainNet; + private final ArrayList feeProviders = new ArrayList<>(Arrays.asList(new BlockrIOProvider(), new BlockTrailProvider(), new TradeBlockProvider())); @Inject - public BlockchainService(Preferences preferences) { - isMainNet = preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET; - providers = new ArrayList<>(Arrays.asList(new BlockrIOProvider(), new BlockTrailProvider(), new TradeBlockProvider())); - } - public BlockchainService() { - isMainNet = false; - providers = new ArrayList<>(Arrays.asList(new BlockrIOProvider(), new BlockTrailProvider(), new TradeBlockProvider())); } - public SettableFuture requestFeeFromBlockchain(String transactionId) { + public SettableFuture requestFee(String transactionId) { Log.traceCall(transactionId); long startTime = System.currentTimeMillis(); final SettableFuture resultFuture = SettableFuture.create(); - if (isMainNet) { - for (BlockchainApiProvider provider : providers) { - GetFeeRequest getFeeRequest = new GetFeeRequest(); - SettableFuture future = getFeeRequest.requestFee(transactionId, provider); - Futures.addCallback(future, new FutureCallback() { - public void onSuccess(Coin fee) { - if (!resultFuture.isDone()) { - log.info("Request fee from providers done after {} ms.", (System.currentTimeMillis() - startTime)); - resultFuture.set(fee); - } + for (FeeProvider provider : feeProviders) { + GetFeeRequest getFeeRequest = new GetFeeRequest(); + SettableFuture future = getFeeRequest.request(transactionId, provider); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(Coin fee) { + if (!resultFuture.isDone()) { + log.info("Request fee from providers done after {} ms.", (System.currentTimeMillis() - startTime)); + resultFuture.set(fee); } + } - public void onFailure(@NotNull Throwable throwable) { - if (!resultFuture.isDone()) { - log.warn("Could not get the fee from any provider after repeated requests."); - resultFuture.setException(throwable); - } + public void onFailure(@NotNull Throwable throwable) { + if (!resultFuture.isDone()) { + log.warn("Could not get the fee from any provider after repeated requests."); + resultFuture.setException(throwable); } - }); - } - } else { - // For regtest/testnet we dont care of the check and set the expected value - resultFuture.set(FeePolicy.getMinRequiredFeeForFundingTx()); + } + }); } return resultFuture; } diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java b/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java index b333e84e52..2e7c5f68d8 100644 --- a/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java @@ -1,9 +1,10 @@ package io.bitsquare.btc.blockchain; import com.google.common.util.concurrent.*; -import io.bitsquare.btc.blockchain.providers.BlockchainApiProvider; +import io.bitsquare.btc.blockchain.providers.FeeProvider; import io.bitsquare.common.UserThread; import io.bitsquare.common.util.Utilities; +import io.bitsquare.http.HttpException; import org.bitcoinj.core.Coin; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -12,7 +13,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Timer; -public class GetFeeRequest { +class GetFeeRequest { private static final Logger log = LoggerFactory.getLogger(GetFeeRequest.class); private final ListeningExecutorService executorService; private Timer timer; @@ -22,12 +23,12 @@ public class GetFeeRequest { executorService = Utilities.getListeningExecutorService("GetFeeRequest", 5, 10, 120L); } - public SettableFuture requestFee(String transactionId, BlockchainApiProvider provider) { + public SettableFuture request(String transactionId, FeeProvider provider) { final SettableFuture resultFuture = SettableFuture.create(); - return requestFee(transactionId, provider, resultFuture); + return request(transactionId, provider, resultFuture); } - private SettableFuture requestFee(String transactionId, BlockchainApiProvider provider, SettableFuture resultFuture) { + private SettableFuture request(String transactionId, FeeProvider provider, SettableFuture resultFuture) { ListenableFuture future = executorService.submit(() -> { Thread.currentThread().setName("requestFee-" + provider.toString()); try { @@ -52,7 +53,7 @@ public class GetFeeRequest { faults++; if (!resultFuture.isDone()) { if (faults < 4) { - requestFee(transactionId, provider, resultFuture); + request(transactionId, provider, resultFuture); } else { resultFuture.setException(throwable); } diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java index 039f3f3fad..f78111ab7b 100644 --- a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java @@ -3,15 +3,15 @@ package io.bitsquare.btc.blockchain.providers; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.bitsquare.app.Log; -import io.bitsquare.btc.blockchain.HttpClient; -import io.bitsquare.btc.blockchain.HttpException; +import io.bitsquare.http.HttpClient; +import io.bitsquare.http.HttpException; import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -public class BlockTrailProvider implements BlockchainApiProvider { +public class BlockTrailProvider implements FeeProvider { private static final Logger log = LoggerFactory.getLogger(BlockTrailProvider.class); private final HttpClient httpClient; diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java index e2f2412730..d544d812ea 100644 --- a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java @@ -3,15 +3,15 @@ package io.bitsquare.btc.blockchain.providers; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.bitsquare.app.Log; -import io.bitsquare.btc.blockchain.HttpClient; -import io.bitsquare.btc.blockchain.HttpException; +import io.bitsquare.http.HttpClient; +import io.bitsquare.http.HttpException; import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -public class BlockrIOProvider implements BlockchainApiProvider { +public class BlockrIOProvider implements FeeProvider { private static final Logger log = LoggerFactory.getLogger(BlockrIOProvider.class); private final HttpClient httpClient; @@ -20,6 +20,7 @@ public class BlockrIOProvider implements BlockchainApiProvider { httpClient = new HttpClient("https://btc.blockr.io/api/v1/tx/info/"); } + //https://api.bitcoinaverage.com/ticker/global/EUR/ @Override public Coin getFee(String transactionId) throws IOException, HttpException { Log.traceCall("transactionId=" + transactionId); diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockchainApiProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/FeeProvider.java similarity index 56% rename from core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockchainApiProvider.java rename to core/src/main/java/io/bitsquare/btc/blockchain/providers/FeeProvider.java index 948caaf7a3..7cebe68330 100644 --- a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockchainApiProvider.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/FeeProvider.java @@ -1,11 +1,10 @@ package io.bitsquare.btc.blockchain.providers; -import io.bitsquare.btc.blockchain.HttpException; +import io.bitsquare.http.HttpException; import org.bitcoinj.core.Coin; import java.io.IOException; -import java.io.Serializable; -public interface BlockchainApiProvider extends Serializable { +public interface FeeProvider { Coin getFee(String transactionId) throws IOException, HttpException; } diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java index 4bdde84384..2f03644b89 100644 --- a/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java @@ -3,15 +3,15 @@ package io.bitsquare.btc.blockchain.providers; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.bitsquare.app.Log; -import io.bitsquare.btc.blockchain.HttpClient; -import io.bitsquare.btc.blockchain.HttpException; +import io.bitsquare.http.HttpClient; +import io.bitsquare.http.HttpException; import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -public class TradeBlockProvider implements BlockchainApiProvider { +public class TradeBlockProvider implements FeeProvider { private static final Logger log = LoggerFactory.getLogger(TradeBlockProvider.class); private final HttpClient httpClient; diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/GetPriceRequest.java b/core/src/main/java/io/bitsquare/btc/pricefeed/GetPriceRequest.java new file mode 100644 index 0000000000..5392f694b0 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/GetPriceRequest.java @@ -0,0 +1,67 @@ +package io.bitsquare.btc.pricefeed; + +import com.google.common.util.concurrent.*; +import io.bitsquare.app.Log; +import io.bitsquare.btc.pricefeed.providers.PriceProvider; +import io.bitsquare.common.util.Utilities; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +class GetPriceRequest { + private static final Logger log = LoggerFactory.getLogger(GetPriceRequest.class); + private final ListeningExecutorService executorService = Utilities.getListeningExecutorService("GetPriceRequest", 5, 10, 120L); + + public GetPriceRequest() { + } + + public SettableFuture> requestAllPrices(PriceProvider provider) { + final SettableFuture> resultFuture = SettableFuture.create(); + ListenableFuture> future = executorService.submit(() -> { + Thread.currentThread().setName("requestAllPrices-" + provider.toString()); + return provider.getAllPrices(); + }); + + Futures.addCallback(future, new FutureCallback>() { + public void onSuccess(Map marketPrice) { + log.debug("Received marketPrice of {}\nfrom provider {}", marketPrice, provider); + resultFuture.set(marketPrice); + } + + public void onFailure(@NotNull Throwable throwable) { + resultFuture.setException(throwable); + } + }); + + return resultFuture; + } + + public SettableFuture requestPrice(String currencyCode, PriceProvider provider) { + final SettableFuture resultFuture = SettableFuture.create(); + return requestPrice(currencyCode, provider, resultFuture); + } + + private SettableFuture requestPrice(String currencyCode, PriceProvider provider, SettableFuture resultFuture) { + Log.traceCall(currencyCode); + ListenableFuture future = executorService.submit(() -> { + Thread.currentThread().setName("requestPrice-" + provider.toString()); + return provider.getPrice(currencyCode); + }); + + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(MarketPrice marketPrice) { + log.debug("Received marketPrice of {}\nfor currencyCode {}\nfrom provider {}", + marketPrice, currencyCode, provider); + resultFuture.set(marketPrice); + } + + public void onFailure(@NotNull Throwable throwable) { + resultFuture.setException(throwable); + } + }); + + return resultFuture; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/MarketPrice.java b/core/src/main/java/io/bitsquare/btc/pricefeed/MarketPrice.java new file mode 100644 index 0000000000..b821a61052 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/MarketPrice.java @@ -0,0 +1,71 @@ +package io.bitsquare.btc.pricefeed; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.lang.Double.parseDouble; + +public class MarketPrice { + private static final Logger log = LoggerFactory.getLogger(MarketPrice.class); + public final String currencyCode; + private final double ask; + private final double bid; + private final double last; + + public MarketPrice(String currencyCode, String ask, String bid, String last) { + this.currencyCode = currencyCode; + this.ask = parseDouble(ask); + this.bid = parseDouble(bid); + this.last = parseDouble(last); + } + + public double getPrice(MarketPriceFeed.Type type) { + switch (type) { + case ASK: + return ask; + case BID: + return (bid); + case LAST: + return last; + } + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MarketPrice)) return false; + + MarketPrice that = (MarketPrice) o; + + if (Double.compare(that.ask, ask) != 0) return false; + if (Double.compare(that.bid, bid) != 0) return false; + if (Double.compare(that.last, last) != 0) return false; + return !(currencyCode != null ? !currencyCode.equals(that.currencyCode) : that.currencyCode != null); + + } + + @Override + public int hashCode() { + int result; + long temp; + result = currencyCode != null ? currencyCode.hashCode() : 0; + temp = Double.doubleToLongBits(ask); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(bid); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(last); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "MarketPrice{" + + "currencyCode='" + currencyCode + '\'' + + ", ask='" + ask + '\'' + + ", bid='" + bid + '\'' + + ", last='" + last + '\'' + + '}'; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/MarketPriceFeed.java b/core/src/main/java/io/bitsquare/btc/pricefeed/MarketPriceFeed.java new file mode 100644 index 0000000000..f75d53c521 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/MarketPriceFeed.java @@ -0,0 +1,176 @@ +package io.bitsquare.btc.pricefeed; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; +import com.google.inject.Inject; +import io.bitsquare.app.Log; +import io.bitsquare.btc.pricefeed.providers.BitcoinAveragePriceProvider; +import io.bitsquare.btc.pricefeed.providers.PriceProvider; +import io.bitsquare.common.UserThread; +import io.bitsquare.common.handlers.FaultHandler; +import io.bitsquare.common.util.Utilities; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class MarketPriceFeed { + private static final Logger log = LoggerFactory.getLogger(MarketPriceFeed.class); + + /////////////////////////////////////////////////////////////////////////////////////////// + // Enum + /////////////////////////////////////////////////////////////////////////////////////////// + + public enum Type { + ASK("Ask"), + BID("Bid"), + LAST("Last"); + + public final String name; + + Type(String name) { + this.name = name; + } + } + + // TODO + // https://poloniex.com/public?command=returnTicker 33 kb + private static long PERIOD = 30; + + private final ScheduledThreadPoolExecutor executorService = Utilities.getScheduledThreadPoolExecutor("MarketPriceFeed", 5, 10, 120L); + private final Map cache = new HashMap<>(); + private Consumer priceConsumer; + private FaultHandler faultHandler; + private PriceProvider fiatPriceProvider = new BitcoinAveragePriceProvider(); + private Type type; + private String currencyCode; + transient private final StringProperty currencyCodeProperty = new SimpleStringProperty(); + transient private final ObjectProperty typeProperty = new SimpleObjectProperty<>(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public MarketPriceFeed() { + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void init(Consumer resultHandler, FaultHandler faultHandler) { + this.priceConsumer = resultHandler; + this.faultHandler = faultHandler; + + requestAllPrices(fiatPriceProvider, () -> { + applyPrice(); + executorService.scheduleAtFixedRate(() -> requestPrice(fiatPriceProvider), PERIOD, PERIOD, TimeUnit.SECONDS); + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setter + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setType(Type type) { + this.type = type; + typeProperty.set(type); + applyPrice(); + } + + public void setCurrencyCode(String currencyCode) { + this.currencyCode = currencyCode; + currencyCodeProperty.set(currencyCode); + applyPrice(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getter + /////////////////////////////////////////////////////////////////////////////////////////// + + public Type getType() { + return type; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public StringProperty currencyCodeProperty() { + return currencyCodeProperty; + } + + public ObjectProperty typeProperty() { + return typeProperty; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void applyPrice() { + if (priceConsumer != null && currencyCode != null && type != null) { + if (cache.containsKey(currencyCode)) { + MarketPrice marketPrice = cache.get(currencyCode); + log.debug("applyPrice type=" + type); + priceConsumer.accept(marketPrice.getPrice(type)); + } else { + String errorMessage = "We don't have a price for currencyCode " + currencyCode; + log.debug(errorMessage); + faultHandler.handleFault(errorMessage, new PriceRequestException(errorMessage)); + } + } + } + + private void requestPrice(PriceProvider provider) { + Log.traceCall(); + GetPriceRequest getPriceRequest = new GetPriceRequest(); + SettableFuture future = getPriceRequest.requestPrice(currencyCode, provider); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(MarketPrice marketPrice) { + UserThread.execute(() -> { + cache.put(marketPrice.currencyCode, marketPrice); + log.debug("marketPrice updated " + marketPrice); + priceConsumer.accept(marketPrice.getPrice(type)); + }); + } + + public void onFailure(@NotNull Throwable throwable) { + log.debug("Could not load marketPrice\n" + throwable.getMessage()); + } + }); + } + + private void requestAllPrices(PriceProvider provider, Runnable resultHandler) { + Log.traceCall(); + GetPriceRequest getPriceRequest = new GetPriceRequest(); + SettableFuture> future = getPriceRequest.requestAllPrices(provider); + Futures.addCallback(future, new FutureCallback>() { + public void onSuccess(Map marketPriceMap) { + UserThread.execute(() -> { + cache.putAll(marketPriceMap); + resultHandler.run(); + }); + } + + public void onFailure(@NotNull Throwable throwable) { + UserThread.execute(() -> faultHandler.handleFault("Could not load marketPrices", throwable)); + } + }); + } +} diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/PriceRequestException.java b/core/src/main/java/io/bitsquare/btc/pricefeed/PriceRequestException.java new file mode 100644 index 0000000000..0d8db122ee --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/PriceRequestException.java @@ -0,0 +1,7 @@ +package io.bitsquare.btc.pricefeed; + +class PriceRequestException extends Exception { + public PriceRequestException(String message) { + super(message); + } +} diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/providers/BitcoinAveragePriceProvider.java b/core/src/main/java/io/bitsquare/btc/pricefeed/providers/BitcoinAveragePriceProvider.java new file mode 100644 index 0000000000..b69d5764fd --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/providers/BitcoinAveragePriceProvider.java @@ -0,0 +1,63 @@ +package io.bitsquare.btc.pricefeed.providers; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.internal.LinkedTreeMap; +import io.bitsquare.app.Log; +import io.bitsquare.btc.pricefeed.MarketPrice; +import io.bitsquare.http.HttpClient; +import io.bitsquare.http.HttpException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class BitcoinAveragePriceProvider implements PriceProvider { + private static final Logger log = LoggerFactory.getLogger(BitcoinAveragePriceProvider.class); + + private final HttpClient httpClient = new HttpClient("https://api.bitcoinaverage.com/ticker/global/"); + + public BitcoinAveragePriceProvider() { + } + + @Override + public Map getAllPrices() throws IOException, HttpException { + Map marketPriceMap = new HashMap<>(); + LinkedTreeMap treeMap = new Gson().fromJson(httpClient.requestWithGET("all"), LinkedTreeMap.class); + Map temp = new HashMap<>(); + treeMap.entrySet().stream().forEach(e -> { + Object value = e.getValue(); + + // We need to check the type as we get an unexpected "timestamp" object at the end: + if (value instanceof LinkedTreeMap) { + LinkedTreeMap treeMap2 = (LinkedTreeMap) value; + temp.clear(); + treeMap2.entrySet().stream().forEach(e2 -> temp.put(e2.getKey(), e2.getValue().toString())); + marketPriceMap.put(e.getKey(), + new MarketPrice(e.getKey(), temp.get("ask"), temp.get("bid"), temp.get("last"))); + } + }); + return marketPriceMap; + } + + @Override + public MarketPrice getPrice(String currencyCode) throws IOException, HttpException { + Log.traceCall("currencyCode=" + currencyCode); + JsonObject jsonObject = new JsonParser() + .parse(httpClient.requestWithGET(currencyCode)) + .getAsJsonObject(); + return new MarketPrice(currencyCode, + jsonObject.get("ask").getAsString(), + jsonObject.get("bid").getAsString(), + jsonObject.get("last").getAsString()); + } + + @Override + public String toString() { + return "BitcoinAveragePriceProvider{" + + '}'; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/pricefeed/providers/PriceProvider.java b/core/src/main/java/io/bitsquare/btc/pricefeed/providers/PriceProvider.java new file mode 100644 index 0000000000..36a9e618a0 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/pricefeed/providers/PriceProvider.java @@ -0,0 +1,14 @@ +package io.bitsquare.btc.pricefeed.providers; + +import io.bitsquare.btc.pricefeed.MarketPrice; +import io.bitsquare.http.HttpException; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; + +public interface PriceProvider extends Serializable { + Map getAllPrices() throws IOException, HttpException; + + MarketPrice getPrice(String currencyCode) throws IOException, HttpException; +} diff --git a/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java b/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java index fe8e459d5f..399944df17 100644 --- a/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java +++ b/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java @@ -154,6 +154,18 @@ public class CurrencyUtil { )); } + public static boolean isFiatCurrency(String currencyCode) { + return !(isCryptoCurrency(currencyCode)) && Currency.getInstance(currencyCode) != null; + } + + public static boolean isCryptoCurrency(String currencyCode) { + return getSortedCryptoCurrencies().stream().filter(e -> e.getCode().equals(currencyCode)).findAny().isPresent(); + } + + public static Optional getCryptoCurrency(String currencyCode) { + return getSortedCryptoCurrencies().stream().filter(e -> e.getCode().equals(currencyCode)).findAny(); + } + public static List getSortedCryptoCurrencies() { final List result = new ArrayList<>(); result.add(new CryptoCurrency("ETH", "Ethereum")); diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index 7033fcd920..b8a7023f74 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -150,7 +150,7 @@ public final class Offer implements PubKeyProtectedExpirablePayload { // we don't need to fill it as the error message is only relevant locally, so we don't store it in the transmitted object errorMessageProperty = new SimpleStringProperty(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.error("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java b/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java index e0354b6aac..d4b82f48a7 100644 --- a/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java +++ b/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java @@ -5,23 +5,25 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.SettableFuture; import org.bitcoinj.core.Coin; import org.jetbrains.annotations.NotNull; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static junit.framework.TestCase.assertTrue; +@Ignore public class BlockchainServiceTest { private static final Logger log = LoggerFactory.getLogger(BlockchainServiceTest.class); @Test - public void testIsMinSpendableAmount() throws InterruptedException { + public void testGetFee() throws InterruptedException { BlockchainService blockchainService = new BlockchainService(); // that tx has 0.001 BTC as fee String transactionId = "38d176d0b1079b99fcb59859401d6b1679d2fa18fd8989d2c244b3682e52fce6"; - SettableFuture future = blockchainService.requestFeeFromBlockchain(transactionId); + SettableFuture future = blockchainService.requestFee(transactionId); Futures.addCallback(future, new FutureCallback() { public void onSuccess(Coin fee) { log.debug(fee.toFriendlyString()); diff --git a/core/src/test/java/io/bitsquare/btc/pricefeed/MarketPriceFeedTest.java b/core/src/test/java/io/bitsquare/btc/pricefeed/MarketPriceFeedTest.java new file mode 100644 index 0000000000..d4c731c34c --- /dev/null +++ b/core/src/test/java/io/bitsquare/btc/pricefeed/MarketPriceFeedTest.java @@ -0,0 +1,29 @@ +package io.bitsquare.btc.pricefeed; + +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static junit.framework.TestCase.assertTrue; + +@Ignore +public class MarketPriceFeedTest { + private static final Logger log = LoggerFactory.getLogger(MarketPriceFeedTest.class); + + @Test + public void testGetPrice() throws InterruptedException { + MarketPriceFeed marketPriceFeed = new MarketPriceFeed(); + marketPriceFeed.setCurrencyCode("EUR"); + marketPriceFeed.init(tradeCurrency -> { + log.debug(tradeCurrency.toString()); + assertTrue(true); + }, + (errorMessage, throwable) -> { + log.debug(errorMessage); + assertTrue(false); + } + ); + Thread.sleep(10000); + } +} diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index 659614d9cd..54038068a8 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -76,7 +76,7 @@ import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY; public class BitsquareApp extends Application { private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class); - public static final boolean DEV_MODE = false; + public static final boolean DEV_MODE = true; public static final boolean IS_RELEASE_VERSION = !DEV_MODE && true; private static Environment env; @@ -153,7 +153,7 @@ public class BitsquareApp extends Application { mainView.setPersistedFilesCorrupted(corruptedDatabaseFiles); });*/ - scene = new Scene(mainView.getRoot(), 1000, 740); + scene = new Scene(mainView.getRoot(), 1100, 740); scene.getStylesheets().setAll( "/io/bitsquare/gui/bitsquare.css", "/io/bitsquare/gui/images.css"); diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainView.java b/gui/src/main/java/io/bitsquare/gui/main/MainView.java index 8bafd0a9ed..8fd01865e3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainView.java @@ -19,8 +19,10 @@ package io.bitsquare.gui.main; import io.bitsquare.BitsquareException; import io.bitsquare.app.BitsquareApp; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.common.UserThread; import io.bitsquare.common.util.Tuple2; +import io.bitsquare.common.util.Tuple3; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.view.*; import io.bitsquare.gui.main.account.AccountView; @@ -47,6 +49,7 @@ import javax.inject.Inject; import javax.inject.Named; import java.util.List; +import static javafx.beans.binding.Bindings.createStringBinding; import static javafx.scene.layout.AnchorPane.*; @FxmlView @@ -121,6 +124,22 @@ public class MainView extends InitializableView { setTopAnchor(this, 0d); }}; + Tuple3 marketPriceBox = getMarketPriceBox("Market price"); + marketPriceBox.first.textProperty().bind(createStringBinding( + () -> { + return model.marketPrice.get() + " " + + model.marketPriceCurrency.get() + "/BTC"; + }, + model.marketPriceCurrency, model.marketPrice)); + marketPriceBox.second.textProperty().bind(createStringBinding( + () -> { + MarketPriceFeed.Type type = model.typeProperty.get(); + return type != null ? "Market price (" + type.name + ")" : ""; + }, + model.marketPriceCurrency, model.typeProperty)); + HBox.setMargin(marketPriceBox.third, new Insets(0, 20, 0, 0)); + + Tuple2 availableBalanceBox = getBalanceBox("Available balance"); availableBalanceBox.first.textProperty().bind(model.availableBalance); @@ -130,7 +149,7 @@ public class MainView extends InitializableView { Tuple2 lockedBalanceBox = getBalanceBox("Locked balance"); lockedBalanceBox.first.textProperty().bind(model.lockedBalance); - HBox rightNavPane = new HBox(availableBalanceBox.second, reservedBalanceBox.second, lockedBalanceBox.second, + HBox rightNavPane = new HBox(marketPriceBox.third, availableBalanceBox.second, reservedBalanceBox.second, lockedBalanceBox.second, settingsButton, accountButton) {{ setSpacing(10); setRightAnchor(this, 10d); @@ -225,6 +244,25 @@ public class MainView extends InitializableView { return new Tuple2(textField, vBox); } + private Tuple3 getMarketPriceBox(String text) { + TextField textField = new TextField(); + textField.setEditable(false); + textField.setPrefWidth(140); + textField.setMouseTransparent(true); + textField.setFocusTraversable(false); + textField.setStyle("-fx-alignment: center; -fx-background-color: -bs-bg-grey;"); + + Label label = new Label(text); + label.setId("nav-balance-label"); + label.setPadding(new Insets(0, 5, 0, 5)); + label.setPrefWidth(textField.getPrefWidth()); + VBox vBox = new VBox(); + vBox.setSpacing(3); + vBox.setPadding(new Insets(11, 0, 0, 0)); + vBox.getChildren().addAll(textField, label); + return new Tuple3(textField, label, vBox); + } + public void setPersistedFilesCorrupted(List persistedFilesCorrupted) { this.persistedFilesCorrupted = persistedFilesCorrupted; } 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 b64ccc32ec..b89c2970bd 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -28,6 +28,7 @@ import io.bitsquare.arbitration.Dispute; import io.bitsquare.arbitration.DisputeManager; import io.bitsquare.btc.*; import io.bitsquare.btc.listeners.BalanceListener; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.common.UserThread; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.model.ViewModel; @@ -99,6 +100,9 @@ public class MainViewModel implements ViewModel { final DoubleProperty btcSyncProgress = new SimpleDoubleProperty(-1); final StringProperty walletServiceErrorMsg = new SimpleStringProperty(); final StringProperty btcSplashSyncIconId = new SimpleStringProperty(); + final StringProperty marketPrice = new SimpleStringProperty("N/A"); + final StringProperty marketPriceCurrency = new SimpleStringProperty(""); + final ObjectProperty typeProperty = new SimpleObjectProperty<>(MarketPriceFeed.Type.LAST); final StringProperty availableBalance = new SimpleStringProperty(); final StringProperty reservedBalance = new SimpleStringProperty(); final StringProperty lockedBalance = new SimpleStringProperty(); @@ -123,6 +127,7 @@ public class MainViewModel implements ViewModel { final StringProperty p2PNetworkLabelId = new SimpleStringProperty("footer-pane"); private MonadicBinding allServicesDone, tradesAndUIReady; + private MarketPriceFeed marketPriceFeed; private final User user; private int numBTCPeers = 0; private Timer checkForBtcSyncStateTimer; @@ -138,10 +143,12 @@ public class MainViewModel implements ViewModel { @Inject public MainViewModel(WalletService walletService, TradeWalletService tradeWalletService, + MarketPriceFeed marketPriceFeed, ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager, OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences, User user, AlertManager alertManager, WalletPasswordPopup walletPasswordPopup, Navigation navigation, BSFormatter formatter) { + this.marketPriceFeed = marketPriceFeed; this.user = user; this.walletService = walletService; this.tradeWalletService = tradeWalletService; @@ -497,6 +504,19 @@ public class MainViewModel implements ViewModel { okPayAccount.setCountry(CountryUtil.getDefaultCountry()); user.addPaymentAccount(okPayAccount); } + + if (marketPriceFeed.getCurrencyCode() == null) + marketPriceFeed.setCurrencyCode(preferences.getPreferredTradeCurrency().getCode()); + if (marketPriceFeed.getType() == null) + marketPriceFeed.setType(MarketPriceFeed.Type.LAST); + marketPriceFeed.init(price -> { + marketPrice.set(formatter.formatMarketPrice(price)); + }, + (errorMessage, throwable) -> { + marketPrice.set("N/A"); + }); + marketPriceCurrency.bind(marketPriceFeed.currencyCodeProperty()); + typeProperty.bind(marketPriceFeed.typeProperty()); } private void checkPeriodicallyForBtcSyncState() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/markets/charts/MarketsChartsViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/markets/charts/MarketsChartsViewModel.java index e72a14ce4c..53a9d9545c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/markets/charts/MarketsChartsViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/markets/charts/MarketsChartsViewModel.java @@ -19,6 +19,7 @@ package io.bitsquare.gui.main.markets.charts; import com.google.common.math.LongMath; import com.google.inject.Inject; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.gui.common.model.ActivatableViewModel; import io.bitsquare.gui.main.offer.offerbook.OfferBook; import io.bitsquare.gui.main.offer.offerbook.OfferBookListItem; @@ -41,6 +42,7 @@ class MarketsChartsViewModel extends ActivatableViewModel { private final OfferBook offerBook; private final Preferences preferences; + private MarketPriceFeed marketPriceFeed; final ObjectProperty tradeCurrency = new SimpleObjectProperty<>(CurrencyUtil.getDefaultTradeCurrency()); private final List buyData = new ArrayList(); @@ -56,9 +58,10 @@ class MarketsChartsViewModel extends ActivatableViewModel { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public MarketsChartsViewModel(OfferBook offerBook, Preferences preferences) { + public MarketsChartsViewModel(OfferBook offerBook, Preferences preferences, MarketPriceFeed marketPriceFeed) { this.offerBook = offerBook; this.preferences = preferences; + this.marketPriceFeed = marketPriceFeed; offerBookListItems = offerBook.getOfferBookListItems(); listChangeListener = c -> updateChartData(offerBookListItems); @@ -66,9 +69,11 @@ class MarketsChartsViewModel extends ActivatableViewModel { @Override protected void activate() { + marketPriceFeed.setType(MarketPriceFeed.Type.LAST); offerBookListItems.addListener(listChangeListener); offerBook.fillOfferBookListItems(); updateChartData(offerBookListItems); + marketPriceFeed.setCurrencyCode(tradeCurrency.get().getCode()); } @Override @@ -134,6 +139,7 @@ class MarketsChartsViewModel extends ActivatableViewModel { public void onSetTradeCurrency(TradeCurrency tradeCurrency) { this.tradeCurrency.set(tradeCurrency); updateChartData(offerBookListItems); + marketPriceFeed.setCurrencyCode(tradeCurrency.getCode()); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/BuyOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/BuyOfferView.java index b676c6afa4..5a6e52f84e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/BuyOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/BuyOfferView.java @@ -17,6 +17,7 @@ package io.bitsquare.gui.main.offer; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.ViewLoader; @@ -27,8 +28,8 @@ import javax.inject.Inject; public class BuyOfferView extends OfferView { @Inject - public BuyOfferView(ViewLoader viewLoader, Navigation navigation) { - super(viewLoader, navigation); + public BuyOfferView(ViewLoader viewLoader, Navigation navigation, MarketPriceFeed marketPriceFeed) { + super(viewLoader, navigation, marketPriceFeed); } @Override diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/OfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/OfferView.java index e9d6a37ae0..faaec3bd1f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/OfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/OfferView.java @@ -17,6 +17,7 @@ package io.bitsquare.gui.main.offer; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.common.UserThread; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.view.ActivatableView; @@ -50,15 +51,16 @@ public abstract class OfferView extends ActivatableView { private final ViewLoader viewLoader; private final Navigation navigation; + private MarketPriceFeed marketPriceFeed; private final Offer.Direction direction; - private Tab createOfferTab; - private Tab takeOfferTab; + private Tab takeOfferTab, createOfferTab, offerBookTab; private TradeCurrency tradeCurrency; private boolean createOfferViewOpen, takeOfferViewOpen; - protected OfferView(ViewLoader viewLoader, Navigation navigation) { + protected OfferView(ViewLoader viewLoader, Navigation navigation, MarketPriceFeed marketPriceFeed) { this.viewLoader = viewLoader; this.navigation = navigation; + this.marketPriceFeed = marketPriceFeed; this.direction = (this instanceof BuyOfferView) ? Offer.Direction.BUY : Offer.Direction.SELL; } @@ -76,8 +78,19 @@ public abstract class OfferView extends ActivatableView { // UserThread.execute needed as focus-out event is called after selectedIndexProperty changed // TODO Find a way to do that in the InputTextField directly, but a tab change does not trigger any event... TabPane tabPane = root; - tabPane.getSelectionModel().selectedIndexProperty() - .addListener((observableValue, oldValue, newValue) -> UserThread.execute(InputTextField::hideErrorMessageDisplay)); + tabPane.getSelectionModel().selectedItemProperty() + .addListener((observableValue, oldValue, newValue) -> { + UserThread.execute(InputTextField::hideErrorMessageDisplay); + if (newValue != null) { + if (newValue.equals(createOfferTab) && createOfferView != null) { + createOfferView.onTabSelected(); + } else if (newValue.equals(takeOfferTab) && takeOfferView != null) { + takeOfferView.onTabSelected(); + } else if (newValue.equals(offerBookTab) && offerBookView != null) { + offerBookView.onTabSelected(); + } + } + }); // We want to get informed when a tab get closed tabPane.getTabs().addListener((ListChangeListener) change -> { @@ -105,14 +118,17 @@ public abstract class OfferView extends ActivatableView { private void loadView(Class viewClass) { TabPane tabPane = root; View view; + boolean isBuy = direction == Offer.Direction.BUY; + + marketPriceFeed.setType(isBuy ? MarketPriceFeed.Type.ASK : MarketPriceFeed.Type.BID); if (viewClass == OfferBookView.class && offerBookView == null) { view = viewLoader.load(viewClass); // Offerbook must not be cached by ViewLoader as we use 2 instances for sell and buy screens. - final Tab tab = new Tab(direction == Offer.Direction.BUY ? "Buy bitcoin" : "Sell bitcoin"); - tab.setClosable(false); - tab.setContent(view.getRoot()); - tabPane.getTabs().add(tab); + offerBookTab = new Tab(isBuy ? "Buy bitcoin" : "Sell bitcoin"); + offerBookTab.setClosable(false); + offerBookTab.setContent(view.getRoot()); + tabPane.getTabs().add(offerBookTab); offerBookView = (OfferBookView) view; OfferActionHandler offerActionHandler = new OfferActionHandler() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/SellOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/SellOfferView.java index 420e2d0b07..3d707dfb1f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/SellOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/SellOfferView.java @@ -17,6 +17,7 @@ package io.bitsquare.gui.main.offer; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.ViewLoader; @@ -27,8 +28,8 @@ import javax.inject.Inject; public class SellOfferView extends OfferView { @Inject - public SellOfferView(ViewLoader viewLoader, Navigation navigation) { - super(viewLoader, navigation); + public SellOfferView(ViewLoader viewLoader, Navigation navigation, MarketPriceFeed marketPriceFeed) { + super(viewLoader, navigation, marketPriceFeed); } @Override 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 a559bbb4aa..d821b60812 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 @@ -22,12 +22,10 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.SettableFuture; import com.google.inject.Inject; import io.bitsquare.arbitration.Arbitrator; -import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.FeePolicy; -import io.bitsquare.btc.TradeWalletService; -import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.*; import io.bitsquare.btc.blockchain.BlockchainService; import io.bitsquare.btc.listeners.BalanceListener; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.gui.common.model.ActivatableDataModel; @@ -72,6 +70,7 @@ class CreateOfferDataModel extends ActivatableDataModel { private final User user; private final KeyRing keyRing; private final P2PService p2PService; + private MarketPriceFeed marketPriceFeed; private final WalletPasswordPopup walletPasswordPopup; private BlockchainService blockchainService; private final BSFormatter formatter; @@ -112,7 +111,7 @@ class CreateOfferDataModel extends ActivatableDataModel { @Inject CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService, - Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, + Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, MarketPriceFeed marketPriceFeed, WalletPasswordPopup walletPasswordPopup, BlockchainService blockchainService, BSFormatter formatter) { this.openOfferManager = openOfferManager; this.walletService = walletService; @@ -121,6 +120,7 @@ class CreateOfferDataModel extends ActivatableDataModel { this.user = user; this.keyRing = keyRing; this.p2PService = p2PService; + this.marketPriceFeed = marketPriceFeed; this.walletPasswordPopup = walletPasswordPopup; this.blockchainService = blockchainService; this.formatter = formatter; @@ -223,8 +223,13 @@ class CreateOfferDataModel extends ActivatableDataModel { PaymentAccount account = user.findFirstPaymentAccountWithCurrency(tradeCurrency); if (account != null) paymentAccount = account; + + marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get()); } + void onTabSelected() { + marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get()); + } /////////////////////////////////////////////////////////////////////////////////////////// // UI actions @@ -283,9 +288,12 @@ class CreateOfferDataModel extends ActivatableDataModel { public void onCurrencySelected(TradeCurrency tradeCurrency) { if (tradeCurrency != null) { this.tradeCurrency = tradeCurrency; - tradeCurrencyCode.set(tradeCurrency.getCode()); + String code = tradeCurrency.getCode(); + tradeCurrencyCode.set(code); paymentAccount.setSelectedTradeCurrency(tradeCurrency); + + marketPriceFeed.setCurrencyCode(code); } } @@ -330,24 +338,28 @@ class CreateOfferDataModel extends ActivatableDataModel { return feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0; } - + /////////////////////////////////////////////////////////////////////////////////////////// // Utils /////////////////////////////////////////////////////////////////////////////////////////// private void requestFeeFromBlockchain(String transactionId) { - SettableFuture future = blockchainService.requestFeeFromBlockchain(transactionId); - Futures.addCallback(future, new FutureCallback() { - public void onSuccess(Coin fee) { - UserThread.execute(() -> feeFromFundingTxProperty.set(fee)); - } + if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) { + SettableFuture future = blockchainService.requestFee(transactionId); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(Coin fee) { + UserThread.execute(() -> feeFromFundingTxProperty.set(fee)); + } - public void onFailure(@NotNull Throwable throwable) { - UserThread.execute(() -> new Popup() - .warning("We did not get a response for the request of the mining fee used in the funding transaction.") - .show()); - } - }); + public void onFailure(@NotNull Throwable throwable) { + UserThread.execute(() -> new Popup() + .warning("We did not get a response for the request of the mining fee used in the funding transaction.") + .show()); + } + }); + } else { + feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx()); + } } void calculateVolume() { 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 cab467c0bd..96d07e9a75 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 @@ -201,6 +201,9 @@ public class CreateOfferView extends ActivatableViewAndModel filteredItems; @@ -72,7 +74,7 @@ class OfferBookViewModel extends ActivatableViewModel { @Inject public OfferBookViewModel(User user, OpenOfferManager openOfferManager, OfferBook offerBook, - Preferences preferences, P2PService p2PService, + Preferences preferences, P2PService p2PService, MarketPriceFeed marketPriceFeed, BSFormatter formatter) { super(); @@ -81,6 +83,7 @@ class OfferBookViewModel extends ActivatableViewModel { this.offerBook = offerBook; this.preferences = preferences; this.p2PService = p2PService; + this.marketPriceFeed = marketPriceFeed; this.formatter = formatter; offerBookListItems = offerBook.getOfferBookListItems(); @@ -91,6 +94,7 @@ class OfferBookViewModel extends ActivatableViewModel { tradeCurrency = CurrencyUtil.getDefaultTradeCurrency(); tradeCurrencyCode.set(tradeCurrency.getCode()); + marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get()); } @Override @@ -115,6 +119,10 @@ class OfferBookViewModel extends ActivatableViewModel { this.direction = direction; } + void onTabSelected() { + marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get()); + } + /////////////////////////////////////////////////////////////////////////////////////////// // UI actions @@ -122,7 +130,9 @@ class OfferBookViewModel extends ActivatableViewModel { public void onSetTradeCurrency(TradeCurrency tradeCurrency) { this.tradeCurrency = tradeCurrency; - tradeCurrencyCode.set(tradeCurrency.getCode()); + String code = tradeCurrency.getCode(); + tradeCurrencyCode.set(code); + marketPriceFeed.setCurrencyCode(code); filterList(); } 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 5dd776f0b7..d2115269a8 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 @@ -25,6 +25,7 @@ import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; +import io.bitsquare.btc.pricefeed.MarketPriceFeed; import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.gui.common.model.ActivatableDataModel; import io.bitsquare.gui.popups.WalletPasswordPopup; @@ -63,6 +64,7 @@ class TakeOfferDataModel extends ActivatableDataModel { private final User user; private final WalletPasswordPopup walletPasswordPopup; private final Preferences preferences; + private MarketPriceFeed marketPriceFeed; private final Coin offerFeeAsCoin; private final Coin networkFeeAsCoin; @@ -90,13 +92,14 @@ class TakeOfferDataModel extends ActivatableDataModel { @Inject TakeOfferDataModel(TradeManager tradeManager, TradeWalletService tradeWalletService, WalletService walletService, User user, WalletPasswordPopup walletPasswordPopup, - Preferences preferences) { + Preferences preferences, MarketPriceFeed marketPriceFeed) { this.tradeManager = tradeManager; this.tradeWalletService = tradeWalletService; this.walletService = walletService; this.user = user; this.walletPasswordPopup = walletPasswordPopup; this.preferences = preferences; + this.marketPriceFeed = marketPriceFeed; offerFeeAsCoin = FeePolicy.getCreateOfferFee(); networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); @@ -152,12 +155,16 @@ class TakeOfferDataModel extends ActivatableDataModel { }; offer.resetState(); + marketPriceFeed.setCurrencyCode(offer.getCurrencyCode()); } void checkOfferAvailability(ResultHandler resultHandler) { tradeManager.checkOfferAvailability(offer, resultHandler); } + void onTabSelected() { + marketPriceFeed.setCurrencyCode(offer.getCurrencyCode()); + } /////////////////////////////////////////////////////////////////////////////////////////// // UI actions diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java index 8b2dccbd62..72f30c6612 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java @@ -310,6 +310,11 @@ public class TakeOfferView extends ActivatableViewAndModel section you can withdraw those funds.").show();*/ } + public void onTabSelected() { + model.dataModel.onTabSelected(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // UI actions /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java b/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java index dcbe5dc3a0..6238f3c009 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java @@ -55,24 +55,24 @@ public class PreferencesView extends ActivatableViewAndModel { model.onFocusOutTransactionFeeTextField(oldValue, newValue); };*/ - addTitledGroupBg(root, ++gridRow, 5, "Display options", Layout.GROUP_DISTANCE); + addTitledGroupBg(root, ++gridRow, 4, "Display options", Layout.GROUP_DISTANCE); useAnimationsCheckBox = addLabelCheckBox(root, gridRow, "Use animations:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; useEffectsCheckBox = addLabelCheckBox(root, ++gridRow, "Use effects:", "").second; showPlaceOfferConfirmationCheckBox = addLabelCheckBox(root, ++gridRow, "Show confirmation at place offer:", "").second; showTakeOfferConfirmationCheckBox = addLabelCheckBox(root, ++gridRow, "Show confirmation at take offer:", "").second; - autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow, "Auto select arbitrators by language:", "").second; } @Override diff --git a/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java b/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java index 8f463aca39..b07b2b4d72 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java +++ b/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java @@ -261,6 +261,22 @@ public class BSFormatter { return parseToFiat(input, currencyCode).equals(parseToFiatWith2Decimals(input, currencyCode)); } + public String formatMarketPrice(double price) { + if (price > 0) { + String str = String.valueOf(price); + int decPoint = str.indexOf("."); + if (decPoint > 0) { + while (str.length() < decPoint + 3) { + str += "0"; + } + return str.substring(0, decPoint + 3); + } else { + return str; + } + } else { + return "N/A"; + } + } /////////////////////////////////////////////////////////////////////////////////////////// // Other diff --git a/gui/src/main/resources/logback.xml b/gui/src/main/resources/logback.xml index 64127e0bae..de468733bc 100644 --- a/gui/src/main/resources/logback.xml +++ b/gui/src/main/resources/logback.xml @@ -12,6 +12,7 @@ +