mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-19 15:26:03 -04:00
Add market price feed
This commit is contained in:
parent
30f7ca69f8
commit
d2e8c6afd7
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package io.bitsquare.btc.pricefeed;
|
||||
|
||||
class PriceRequestException extends Exception {
|
||||
public PriceRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -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{" +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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"));
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -201,6 +201,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
this.closeHandler = closeHandler;
|
||||
}
|
||||
|
||||
public void onTabSelected() {
|
||||
model.dataModel.onTabSelected();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI actions
|
||||
|
@ -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);
|
||||
|
||||
|
@ -197,6 +197,10 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
this.offerActionHandler = offerActionHandler;
|
||||
}
|
||||
|
||||
public void onTabSelected() {
|
||||
model.onTabSelected();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI actions
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"/>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package io.bitsquare.btc.blockchain;
|
||||
package io.bitsquare.http;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
@ -1,4 +1,4 @@
|
||||
package io.bitsquare.btc.blockchain;
|
||||
package io.bitsquare.http;
|
||||
|
||||
public class HttpException extends Exception {
|
||||
public HttpException(String message) {
|
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user