Add market price feed

This commit is contained in:
Manfred Karrer 2016-02-10 01:59:46 +01:00
parent 30f7ca69f8
commit d2e8c6afd7
37 changed files with 676 additions and 102 deletions

View File

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

View File

@ -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<BlockchainApiProvider> providers;
private final boolean isMainNet;
private final ArrayList<FeeProvider> 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<Coin> requestFeeFromBlockchain(String transactionId) {
public SettableFuture<Coin> requestFee(String transactionId) {
Log.traceCall(transactionId);
long startTime = System.currentTimeMillis();
final SettableFuture<Coin> resultFuture = SettableFuture.create();
if (isMainNet) {
for (BlockchainApiProvider provider : providers) {
GetFeeRequest getFeeRequest = new GetFeeRequest();
SettableFuture<Coin> future = getFeeRequest.requestFee(transactionId, provider);
Futures.addCallback(future, new FutureCallback<Coin>() {
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<Coin> future = getFeeRequest.request(transactionId, provider);
Futures.addCallback(future, new FutureCallback<Coin>() {
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;
}

View File

@ -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<Coin> requestFee(String transactionId, BlockchainApiProvider provider) {
public SettableFuture<Coin> request(String transactionId, FeeProvider provider) {
final SettableFuture<Coin> resultFuture = SettableFuture.create();
return requestFee(transactionId, provider, resultFuture);
return request(transactionId, provider, resultFuture);
}
private SettableFuture<Coin> requestFee(String transactionId, BlockchainApiProvider provider, SettableFuture<Coin> resultFuture) {
private SettableFuture<Coin> request(String transactionId, FeeProvider provider, SettableFuture<Coin> resultFuture) {
ListenableFuture<Coin> 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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Map<String, MarketPrice>> requestAllPrices(PriceProvider provider) {
final SettableFuture<Map<String, MarketPrice>> resultFuture = SettableFuture.create();
ListenableFuture<Map<String, MarketPrice>> future = executorService.submit(() -> {
Thread.currentThread().setName("requestAllPrices-" + provider.toString());
return provider.getAllPrices();
});
Futures.addCallback(future, new FutureCallback<Map<String, MarketPrice>>() {
public void onSuccess(Map<String, MarketPrice> 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<MarketPrice> requestPrice(String currencyCode, PriceProvider provider) {
final SettableFuture<MarketPrice> resultFuture = SettableFuture.create();
return requestPrice(currencyCode, provider, resultFuture);
}
private SettableFuture<MarketPrice> requestPrice(String currencyCode, PriceProvider provider, SettableFuture<MarketPrice> resultFuture) {
Log.traceCall(currencyCode);
ListenableFuture<MarketPrice> future = executorService.submit(() -> {
Thread.currentThread().setName("requestPrice-" + provider.toString());
return provider.getPrice(currencyCode);
});
Futures.addCallback(future, new FutureCallback<MarketPrice>() {
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;
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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<String, MarketPrice> cache = new HashMap<>();
private Consumer<Double> 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<Type> typeProperty = new SimpleObjectProperty<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MarketPriceFeed() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void init(Consumer<Double> 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<Type> 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<MarketPrice> future = getPriceRequest.requestPrice(currencyCode, provider);
Futures.addCallback(future, new FutureCallback<MarketPrice>() {
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<Map<String, MarketPrice>> future = getPriceRequest.requestAllPrices(provider);
Futures.addCallback(future, new FutureCallback<Map<String, MarketPrice>>() {
public void onSuccess(Map<String, MarketPrice> marketPriceMap) {
UserThread.execute(() -> {
cache.putAll(marketPriceMap);
resultHandler.run();
});
}
public void onFailure(@NotNull Throwable throwable) {
UserThread.execute(() -> faultHandler.handleFault("Could not load marketPrices", throwable));
}
});
}
}

View File

@ -0,0 +1,7 @@
package io.bitsquare.btc.pricefeed;
class PriceRequestException extends Exception {
public PriceRequestException(String message) {
super(message);
}
}

View File

@ -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<String, MarketPrice> getAllPrices() throws IOException, HttpException {
Map<String, MarketPrice> marketPriceMap = new HashMap<>();
LinkedTreeMap<String, Object> treeMap = new Gson().fromJson(httpClient.requestWithGET("all"), LinkedTreeMap.class);
Map<String, String> 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<String, Object> 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{" +
'}';
}
}

View File

@ -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<String, MarketPrice> getAllPrices() throws IOException, HttpException;
MarketPrice getPrice(String currencyCode) throws IOException, HttpException;
}

View File

@ -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<TradeCurrency> getCryptoCurrency(String currencyCode) {
return getSortedCryptoCurrencies().stream().filter(e -> e.getCode().equals(currencyCode)).findAny();
}
public static List<TradeCurrency> getSortedCryptoCurrencies() {
final List<TradeCurrency> result = new ArrayList<>();
result.add(new CryptoCurrency("ETH", "Ethereum"));

View File

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

View File

@ -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<Coin> future = blockchainService.requestFeeFromBlockchain(transactionId);
SettableFuture<Coin> future = blockchainService.requestFee(transactionId);
Futures.addCallback(future, new FutureCallback<Coin>() {
public void onSuccess(Coin fee) {
log.debug(fee.toFriendlyString());

View File

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

View File

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

View File

@ -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<StackPane, MainViewModel> {
setTopAnchor(this, 0d);
}};
Tuple3<TextField, Label, VBox> 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<TextField, VBox> availableBalanceBox = getBalanceBox("Available balance");
availableBalanceBox.first.textProperty().bind(model.availableBalance);
@ -130,7 +149,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
Tuple2<TextField, VBox> 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<StackPane, MainViewModel> {
return new Tuple2(textField, vBox);
}
private Tuple3<TextField, Label, VBox> 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<String> persistedFilesCorrupted) {
this.persistedFilesCorrupted = persistedFilesCorrupted;
}

View File

@ -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<MarketPriceFeed.Type> 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<Boolean> 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() {

View File

@ -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> tradeCurrency = new SimpleObjectProperty<>(CurrencyUtil.getDefaultTradeCurrency());
private final List<XYChart.Data> 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());
}
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -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

View File

@ -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<TabPane, Void> {
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<TabPane, Void> {
// 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<Tab>) change -> {
@ -105,14 +118,17 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
private void loadView(Class<? extends View> 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() {

View File

@ -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

View File

@ -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<Coin> future = blockchainService.requestFeeFromBlockchain(transactionId);
Futures.addCallback(future, new FutureCallback<Coin>() {
public void onSuccess(Coin fee) {
UserThread.execute(() -> feeFromFundingTxProperty.set(fee));
}
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
SettableFuture<Coin> future = blockchainService.requestFee(transactionId);
Futures.addCallback(future, new FutureCallback<Coin>() {
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() {

View File

@ -201,6 +201,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
this.closeHandler = closeHandler;
}
public void onTabSelected() {
model.dataModel.onTabSelected();
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions

View File

@ -105,7 +105,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
@Inject
public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator,
P2PService p2PService,
P2PService p2PService,
BSFormatter formatter) {
super(dataModel);

View File

@ -197,6 +197,10 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
this.offerActionHandler = offerActionHandler;
}
public void onTabSelected() {
model.onTabSelected();
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions

View File

@ -19,6 +19,7 @@ package io.bitsquare.gui.main.offer.offerbook;
import com.google.inject.Inject;
import io.bitsquare.app.Version;
import io.bitsquare.btc.pricefeed.MarketPriceFeed;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.gui.common.model.ActivatableViewModel;
@ -52,6 +53,7 @@ class OfferBookViewModel extends ActivatableViewModel {
private final OfferBook offerBook;
private final Preferences preferences;
private final P2PService p2PService;
private MarketPriceFeed marketPriceFeed;
final BSFormatter formatter;
private final FilteredList<OfferBookListItem> 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();
}

View File

@ -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

View File

@ -310,6 +310,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
new Popup().warning("You have already funds paid in.\nIn the <Funds/Open for withdrawal> section you can withdraw those funds.").show();*/
}
public void onTabSelected() {
model.dataModel.onTabSelected();
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -55,24 +55,24 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 3, "Preferences");
addTitledGroupBg(root, gridRow, 4, "Preferences");
tradeCurrencyComboBox = addLabelComboBox(root, gridRow, "Preferred currency:", Layout.FIRST_ROW_DISTANCE).second;
languageComboBox = addLabelComboBox(root, ++gridRow, "Language:").second;
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
blockExplorerComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin block explorer:").second;
autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow, "Auto select arbitrators by language:", "").second;
// TODO need a bit extra work to separate trade and non trade tx fees before it can be used
/*transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
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

View File

@ -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

View File

@ -12,6 +12,7 @@
<logger name="io.bitsquare.storage.Storage" level="WARN"/>
<logger name="io.bitsquare.storage.FileManager" level="WARN"/>
<logger name="io.bitsquare.locale.BSResources" level="ERROR"/>
<!-- <logger name="io.bitsquare.p2p.peers.PeerGroup" level="TRACE"/>
@ -30,7 +31,6 @@
<logger name="io.bitsquare.storage.Storage" level="WARN"/>
<logger name="io.bitsquare.gui.util.Profiler" level="ERROR"/>
<logger name="io.bitsquare.locale.BSResources" level="ERROR"/>
<logger name="io.bitsquare.temp.storage.RemoteStorage" level="WARN"/>
<logger name="io.bitsquare.storage.FileManager" level="WARN"/>

View File

@ -1,4 +1,4 @@
package io.bitsquare.btc.blockchain;
package io.bitsquare.http;
import java.io.*;
import java.net.HttpURLConnection;

View File

@ -1,4 +1,4 @@
package io.bitsquare.btc.blockchain;
package io.bitsquare.http;
public class HttpException extends Exception {
public HttpException(String message) {

View File

@ -484,8 +484,15 @@ public class Connection implements MessageListener {
closeConnectionReason = CloseConnectionReason.TERMINATED;
} else {
closeConnectionReason = CloseConnectionReason.UNKNOWN_EXCEPTION;
String message;
if (e.getMessage() != null)
message = e.getMessage();
else
message = e.toString();
log.warn("Unknown reason for exception at socket {}\n\tconnection={}\n\tException=",
socket.toString(), this, e.getMessage());
socket.toString(), this, message);
e.printStackTrace();
}